TreeTransfer.ts overview
Streams directory trees between callers, snapshots, and new volumes.
Sources emit Entry values in sorted pre-order, rooted at the transfer root:
the root itself is /, and hard links name an earlier entry. Sinks write those
entries under a destination and return a TransferReport. Compose them with
Effect's own Stream operators, for example Stream.filter to exclude paths
or Stream.concat to merge several sources.
Since v0.6.0
errors
TransferError
Creates a transfer failure that is not a filesystem error.
Example
import { TreeTransfer } from "@effect-vfs/memory"
const error = new TreeTransfer.TransferError({ code: "LimitExceeded", field: "maxEntries" })
console.log(error.code, error.field)
// LimitExceeded maxEntriesSignature
declare const TransferError: {
new (args: {
readonly code: TransferError["code"]
readonly field?: string
readonly path?: Vfs.PathInput
}): TransferError
prototype: TransferError
}Since v0.6.0
TransferError (interface)
A transfer failure that is not a filesystem error.
Details
LimitExceeded names the exceeded field of the source limits.
InvalidEntry reports an entry a sink cannot place, such as a missing root,
a child before its parent, or a hard link to an unwritten target.
InvalidArgument reports an incomplete or malformed limits policy or fidelity requirements.
UnrepresentableName, UnsupportedEntryType, and NameCollision report
entries a filesystem adapter cannot carry; the unsupported: "skip" option
turns them into reported skips instead. EscapingSymlink reports a symbolic
link whose target leaves the transferred tree. DestinationConflict reports an
existing destination entry that an overwrite cannot safely replace.
UnsupportedFidelity names a required guarantee the destination or options cannot meet,
before any destination change. Malformed requirements fail InvalidArgument with field required.
Example
import { TreeTransfer } from "@effect-vfs/memory"
const error = new TreeTransfer.TransferError({ code: "LimitExceeded", field: "maxEntries" })
console.log(error.code, error.field)
// LimitExceeded maxEntriesSignature
export interface TransferError extends Cause.YieldableError {
readonly _tag: "TransferError"
readonly code:
| "LimitExceeded"
| "InvalidEntry"
| "InvalidArgument"
| "UnrepresentableName"
| "UnsupportedEntryType"
| "NameCollision"
| "EscapingSymlink"
| "DestinationConflict"
| "UnsupportedFidelity"
readonly field?: string
readonly path?: Vfs.PathInput
}Since v0.6.0
models
Entry (type alias)
One transferred tree entry: a core fixture entry whose path is rooted at the transfer root.
Details
Paths are strings while every component is UTF-8, and BytePath values
otherwise. Entries carry the source's full metadata; sinks decide what to apply.
Example
import type { TreeTransfer } from "@effect-vfs/memory"
const entry: TreeTransfer.Entry = { kind: "file", path: "/README.md", bytes: new Uint8Array() }
console.log(entry.path)
// /README.mdSignature
type Entry = Vfs.Fixture["entries"][number]Since v0.6.0
FileSystemReadOptions (interface)
Options for a source that reads an Effect FileSystem.
Details
unsupported: "fail" (the default) fails on FIFOs, sockets, devices, and host
names that Effect's FileSystem could not decode as UTF-8. "skip" omits them
and passes each one to onSkip, which logs a warning by default.
Example
import { TreeTransfer } from "@effect-vfs/memory"
import { Effect } from "effect"
const options: TreeTransfer.FileSystemReadOptions = {
unsupported: "skip",
onSkip: (skipped) => Effect.log(`skipped ${String(skipped.path)}`)
}
console.log(options.unsupported)
// skipSignature
export interface FileSystemReadOptions extends ReadOptions {
/** How entries the source cannot carry are treated. Defaults to `"fail"`. */
readonly unsupported?: "fail" | "skip" | undefined
/** Receives each skipped entry. Defaults to logging a warning. */
readonly onSkip?: ((skipped: SkippedEntry) => Effect.Effect<void>) | undefined
}Since v0.6.0
FileSystemWriteOptions (interface)
Options for a sink that writes through an Effect FileSystem.
Details
escaping: "reject" (the default) fails before any symbolic link is created
when a link target is absolute or resolves outside the transferred tree,
following .. and links within the tree. unsupported: "skip" records
non-UTF-8 names and name collisions in the report instead of failing.
Example
import { TreeTransfer } from "@effect-vfs/memory"
const options: TreeTransfer.FileSystemWriteOptions = { escaping: "allow", unsupported: "skip" }
console.log(options.escaping)
// allowSignature
export interface FileSystemWriteOptions extends WriteOptions {
/** How symbolic links that leave the tree are treated. Defaults to `"reject"`. */
readonly escaping?: "reject" | "allow" | undefined
/** How entries the destination cannot carry are treated. Defaults to `"fail"`. */
readonly unsupported?: "fail" | "skip" | undefined
}Since v0.6.0
ReadOptions (interface)
Options for a source.
Example
import { TreeTransfer } from "@effect-vfs/memory"
const options: TreeTransfer.ReadOptions = { limits: TreeTransfer.TreeTransferLimits.constrained }
console.log(options.limits?.maxEntries)
// 10000Signature
export interface ReadOptions {
/** Source work policy. Omission uses `TreeTransferLimits.default`. */
readonly limits?: TreeTransferLimits | undefined
}Since v0.6.0
RequiredFidelity (interface)
Minimum destination guarantees for the metadata supplied by an entry stream.
Boolean true requires a capability; false or omission adds no constraint.
Nanosecond precision satisfies millisecond requirements, and exact hard links
satisfy best-effort requirements. Precision applies to the selected times;
requiring precision with times: "none" fails. Owner and special-bit
requirements need their corresponding options enabled. Requirements never
enable options or recover metadata that a source has already omitted.
Overwrite retains existing directory modes; requirements do not change that merge policy.
Preserving metadata does not preserve Caller authority or create an execution sandbox.
Example
import { TreeTransfer } from "@effect-vfs/memory"
const options: TreeTransfer.WriteOptions = {
times: "all",
required: { hardLinks: "exact", timestampPrecision: "nanosecond", times: "all" }
}Signature
export interface RequiredFidelity extends Partial<SinkCapabilities> {
/** Minimum timestamp selection. `"all"` means access and modification times on live sinks. */
readonly times?: "none" | "mtime" | "all"
/** Requires setuid, setgid, and sticky-bit preservation. */
readonly specialBits?: boolean
}Since v0.10.0
SinkCapabilities (type alias)
What a destination can preserve: caller for toCaller, volume for
toVolume, and fileSystem for toFileSystem.
Example
import { TreeTransfer } from "@effect-vfs/memory"
const lossless = (capabilities: TreeTransfer.SinkCapabilities) => capabilities.byteNames && capabilities.owner
console.log(lossless(TreeTransfer.SinkCapabilities.volume))
// trueSignature
export type SinkCapabilities = {
readonly byteNames: boolean
readonly timestampPrecision: "nanosecond" | "millisecond"
readonly changeAndBirthTimes: boolean
readonly owner: boolean
readonly symlinkMetadata: boolean
readonly hardLinks: "exact" | "bestEffort"
}Since v0.6.0
SkippedEntry (type alias)
An entry that was left out of a transfer, with the reason.
Example
import type { TreeTransfer } from "@effect-vfs/memory"
const skipped: TreeTransfer.SkippedEntry = { path: "/fifo", reason: "UnsupportedEntryType" }
console.log(skipped.path)
// /fifoSignature
export type SkippedEntry = {
readonly path: string | Vfs.BytePath
readonly reason: "UnrepresentableName" | "UnsupportedEntryType" | "NameCollision"
}Since v0.6.0
TransferReport (type alias)
Counts of what a sink wrote, and what it left out.
Details
skipped lists entries the sink omitted under unsupported: "skip", and
descendants of omitted directories. hardLinksDegraded counts hard links the
destination could not create, written instead as independent copies.
Example
import type { TreeTransfer } from "@effect-vfs/memory"
import { ByteSize } from "effect"
const report: TreeTransfer.TransferReport = {
entries: 2,
files: 1,
bytes: ByteSize.bytes(5),
skipped: [],
hardLinksDegraded: 0
}
console.log(report.entries)
// 2Signature
export type TransferReport = {
readonly entries: number
readonly files: number
readonly bytes: ByteSize.ByteSize
readonly skipped: readonly {
readonly path: string | Vfs.BytePath
readonly reason: "UnrepresentableName" | "UnsupportedEntryType" | "NameCollision"
}[]
readonly hardLinksDegraded: number
}Since v0.6.0
TreeTransferLimits (type alias)
A complete source work policy.
Details
maxEntries counts every emitted entry including the root. maxBytes counts
file contents and symbolic-link targets. maxDepth counts components below the
root, and maxPathBytes measures the rooted entry path. A supplied policy must
set every field.
Example
import { TreeTransfer } from "@effect-vfs/memory"
import { ByteSize } from "effect"
const limits: TreeTransfer.TreeTransferLimits = {
maxEntries: 500,
maxBytes: ByteSize.mebibytes(4),
maxFileBytes: ByteSize.mebibytes(1),
maxDepth: 8,
maxPathBytes: ByteSize.bytes(512)
}
console.log(limits.maxDepth)
// 8Signature
export type TreeTransferLimits = {
readonly maxEntries: number
readonly maxBytes: ByteSize.ByteSize
readonly maxFileBytes: ByteSize.ByteSize
readonly maxDepth: number
readonly maxPathBytes: ByteSize.ByteSize
}Since v0.6.0
VolumeTransferOptions (interface)
Options for building a new volume from streamed entries.
Details
Owners and setuid, setgid, and sticky bits come from the source, so they are
dropped unless requested: without owner: true every entry is owned by uid and
gid 0, and without specialBits: true modes keep only permission bits. All four
timestamps are kept.
Example
import { TreeTransfer } from "@effect-vfs/memory"
import { ByteSize } from "effect"
const exact: TreeTransfer.VolumeTransferOptions = {
volume: { maxBytes: ByteSize.mebibytes(64) },
owner: true,
specialBits: true
}
console.log(exact.owner)
// trueSignature
export interface VolumeTransferOptions {
/** Minimum preservation guarantees, checked before consuming entries. */
readonly required?: RequiredFidelity | undefined
/** Options for the new volume. */
readonly volume?: Vfs.VolumeOptions | undefined
/** Keeps source owners. Defaults to `false`. */
readonly owner?: boolean | undefined
/** Keeps setuid, setgid, and sticky bits. Defaults to `false`. */
readonly specialBits?: boolean | undefined
}Since v0.6.0
WriteOptions (interface)
Options for a sink that writes into an existing volume.
Details
existing: "reject" (the default) claims the destination with an exclusive
create and removes it if the transfer fails or is interrupted.
existing: "overwrite" merges into existing directories, replaces files and
symbolic links, and never removes anything on failure. A file and directory
clash always fails. Deleted source entries are never propagated.
Repeated stream paths fail InvalidEntry, even with overwrite or skip policies.
These policies apply to destination conflicts and unsupported entries only.
times defaults to "mtime". Permission bits are always applied; setuid,
setgid, and sticky bits only with specialBits: true. Directory modes and
times are applied after their children are written.
Example
import { TreeTransfer } from "@effect-vfs/memory"
const refresh: TreeTransfer.WriteOptions = { existing: "overwrite", times: "all" }
console.log(refresh.existing)
// overwriteSignature
export interface WriteOptions {
/** Minimum preservation guarantees, checked before writing. Omission keeps existing behavior. */
readonly required?: RequiredFidelity | undefined
/** How an existing destination is treated. Defaults to `"reject"`. */
readonly existing?: "reject" | "overwrite" | undefined
/** Which source timestamps are applied. Defaults to `"mtime"`. */
readonly times?: "none" | "mtime" | "all" | undefined
/** Applies setuid, setgid, and sticky bits. Defaults to `false`. */
readonly specialBits?: boolean | undefined
}Since v0.6.0
schemas
SinkCapabilities
Schema for what a destination can preserve.
Example
import { TreeTransfer } from "@effect-vfs/memory"
console.log(TreeTransfer.SinkCapabilities.fileSystem.timestampPrecision)
// millisecondSignature
declare const SinkCapabilities: Schema.Struct<{
readonly byteNames: Schema.Boolean
readonly timestampPrecision: Schema.Literals<readonly ["nanosecond", "millisecond"]>
readonly changeAndBirthTimes: Schema.Boolean
readonly owner: Schema.Boolean
readonly symlinkMetadata: Schema.Boolean
readonly hardLinks: Schema.Literals<readonly ["exact", "bestEffort"]>
}> & {
caller: Readonly<{
readonly byteNames: true
readonly timestampPrecision: "nanosecond"
readonly changeAndBirthTimes: false
readonly owner: false
readonly symlinkMetadata: true
readonly hardLinks: "exact"
}>
volume: Readonly<{
readonly byteNames: true
readonly timestampPrecision: "nanosecond"
readonly changeAndBirthTimes: true
readonly owner: true
readonly symlinkMetadata: true
readonly hardLinks: "exact"
}>
fileSystem: Readonly<{
readonly byteNames: false
readonly timestampPrecision: "millisecond"
readonly changeAndBirthTimes: false
readonly owner: false
readonly symlinkMetadata: false
readonly hardLinks: "bestEffort"
}>
}Since v0.6.0
SkippedEntry
Schema for an entry that was left out of a transfer.
Example
import { TreeTransfer } from "@effect-vfs/memory"
import { Schema } from "effect"
const skipped = Schema.decodeUnknownSync(TreeTransfer.SkippedEntry)({
path: "/run.sock",
reason: "UnsupportedEntryType"
})
console.log(skipped.reason)
// UnsupportedEntryTypeSignature
declare const SkippedEntry: Schema.Struct<{
readonly path: Schema.Union<readonly [Schema.String, typeof Vfs.BytePath]>
readonly reason: Schema.Literals<readonly ["UnrepresentableName", "UnsupportedEntryType", "NameCollision"]>
}>Since v0.6.0
TransferReport
Schema for the counts a sink returns.
Example
import { TreeTransfer } from "@effect-vfs/memory"
import { ByteSize, Schema } from "effect"
const report = Schema.decodeUnknownSync(TreeTransfer.TransferReport)({
entries: 3,
files: 2,
bytes: ByteSize.bytes(10),
skipped: [],
hardLinksDegraded: 0
})
console.log(report.files)
// 2Signature
declare const TransferReport: Schema.Struct<{
readonly entries: Schema.Natural
readonly files: Schema.Natural
readonly bytes: Schema.ByteSize
readonly skipped: Schema.$Array<typeof SkippedEntry>
readonly hardLinksDegraded: Schema.Natural
}>Since v0.6.0
TreeTransferLimits
Schema for a complete source work policy, with frozen presets.
Details
Sources use default when no limits are passed. constrained suits
memory-sensitive hosts. Spread a preset to change individual fields.
Example
import { TreeTransfer } from "@effect-vfs/memory"
const shallow = { ...TreeTransfer.TreeTransferLimits.constrained, maxDepth: 4 }
console.log(shallow.maxDepth, TreeTransfer.TreeTransferLimits.default.maxEntries)
// 4 100000Signature
declare const TreeTransferLimits: Schema.Struct<{
readonly maxEntries: Schema.Natural
readonly maxBytes: Schema.ByteSize
readonly maxFileBytes: Schema.ByteSize
readonly maxDepth: Schema.Natural
readonly maxPathBytes: Schema.ByteSize
}> & { default: TreeTransferLimits; constrained: TreeTransferLimits }Since v0.6.0
sinks
toCaller
Writes streamed entries under destination through a live caller.
Details
Writes use the caller's permissions and apply the WriteOptions policy.
Each file write is atomic; the transfer as a whole is not. See WriteOptions
for cleanup on failure.
Example
import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { TreeTransfer } from "@effect-vfs/memory"
import { Effect, Stream } from "effect"
const program = Effect.gen(function* () {
const source = yield* Vfs.fromFixture({
entries: [
{ kind: "directory", path: "/project" },
{ kind: "file", path: "/project/a.txt", bytes: new TextEncoder().encode("a") }
]
})
const workspace = yield* (yield* Vfs.make()).caller()
const report = yield* Stream.run(
TreeTransfer.fromCaller(yield* source.caller(), "/project"),
TreeTransfer.toCaller(workspace, "/copy")
)
const listing = yield* workspace.readDirectory("/copy")
return [report.files, listing.value.map((entry) => new TextDecoder().decode(entry.name))]
})
Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
// [ 1, [ 'a.txt' ] ]Signature
declare const toCaller: {
(
destination: Vfs.PathInput,
options?: WriteOptions
): (caller: Vfs.Caller) => Sink.Sink<TransferReport, Entry, never, TransferError | Vfs.VfsError>
(
caller: Vfs.Caller,
destination: Vfs.PathInput,
options?: WriteOptions
): Sink.Sink<TransferReport, Entry, never, TransferError | Vfs.VfsError>
}Since v0.6.0
toFileSystem
Writes streamed entries under destination through an application-provided
Effect FileSystem.
Details
Applies the WriteOptions policy with the same claim-and-remove cleanup
as toCaller. Symbolic links are created after every other entry, once
escape checks pass. Owners, change and birth times, and symbolic-link metadata
are not written; times are truncated to milliseconds. A hard link the host
refuses, or a hard link to a symbolic link, is written as a copy and counted in
hardLinksDegraded. Paths use / separators.
Example
import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { MemoryFileSystem, TreeTransfer } from "@effect-vfs/memory"
import { Effect, Stream } from "effect"
const program = Effect.gen(function* () {
const fs = yield* MemoryFileSystem.make
const source = yield* Vfs.fromFixture({
entries: [
{ kind: "directory", path: "/dist" },
{ kind: "file", path: "/dist/app.js", bytes: new TextEncoder().encode("run()") }
]
})
yield* Stream.run(TreeTransfer.fromCaller(yield* source.caller(), "/dist"), TreeTransfer.toFileSystem(fs, "/out"))
return yield* fs.readFileString("/out/app.js")
})
Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
// run()Signature
declare const toFileSystem: {
(
destination: string,
options?: FileSystemWriteOptions
): (
fs: FileSystem.FileSystem
) => Sink.Sink<TransferReport, Entry, never, TransferError | Vfs.VfsError | PlatformError.PlatformError>
(
fs: FileSystem.FileSystem,
destination: string,
options?: FileSystemWriteOptions
): Sink.Sink<TransferReport, Entry, never, TransferError | Vfs.VfsError | PlatformError.PlatformError>
}Since v0.6.0
toVolume
Builds a new volume from streamed entries in one step.
Details
Nothing is visible unless every entry is accepted. The first entry must be the
/ directory, and entries must satisfy the same placement rules as the live
sinks. Timestamps are kept exactly, including change and birth times; owners and
special mode bits follow VolumeTransferOptions. Every entry is held in
memory until the volume is built.
Example
import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { TreeTransfer } from "@effect-vfs/memory"
import { Effect } from "effect"
const program = Effect.gen(function* () {
const source = yield* Vfs.fromFixture({
entries: [
{ kind: "directory", path: "/dist" },
{ kind: "file", path: "/dist/app.js", bytes: new TextEncoder().encode("run()") }
]
})
const volume = yield* TreeTransfer.toVolume(TreeTransfer.fromCaller(yield* source.caller(), "/dist"))
const listing = yield* (yield* volume.caller()).readDirectory("/")
return listing.value.map((entry) => new TextDecoder().decode(entry.name))
})
Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
// [ 'app.js' ]Signature
declare const toVolume: {
(
options?: VolumeTransferOptions
): <E, R>(
entries: Stream.Stream<Entry, E, R>
) => Effect.Effect<Vfs.Volume, E | TransferError | Vfs.VfsError, R | Crypto.Crypto>
<E, R>(
entries: Stream.Stream<Entry, E, R>,
options?: VolumeTransferOptions
): Effect.Effect<Vfs.Volume, E | TransferError | Vfs.VfsError, R | Crypto.Crypto>
}Since v0.6.0
sources
fromCaller
Streams the tree at root through a live caller.
Details
Reads use the caller's permissions. Each entry's metadata is read before its
contents, so entries carry the source's original access time. The reads
themselves refresh source access times under relatime, so only a read whose
access time is due changes the source, and on a durable volume only such a
read commits. Use
fromSnapshot for a point-in-time read that never changes the source.
Hard links within the tree become hardLink entries naming the first alias.
Example
import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { TreeTransfer } from "@effect-vfs/memory"
import { Effect, Stream } from "effect"
const program = Effect.gen(function* () {
const volume = yield* Vfs.fromFixture({
entries: [
{ kind: "directory", path: "/project" },
{ kind: "file", path: "/project/a.txt", bytes: new TextEncoder().encode("a") }
]
})
const entries = yield* Stream.runCollect(TreeTransfer.fromCaller(yield* volume.caller(), "/project"))
return entries.map((entry) => entry.path)
})
Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
// [ '/', '/a.txt' ]Signature
declare const fromCaller: {
(
root: Vfs.PathInput,
options?: ReadOptions
): (caller: Vfs.Caller) => Stream.Stream<Entry, TransferError | Vfs.VfsError>
(caller: Vfs.Caller, root: Vfs.PathInput, options?: ReadOptions): Stream.Stream<Entry, TransferError | Vfs.VfsError>
}Since v0.6.0
fromFileSystem
Streams the tree at root through an application-provided Effect FileSystem.
Details
Effect's FileSystem decodes names as UTF-8 strings, reports times in
milliseconds, has no change time, and follows symbolic links in stat. A name
is treated as a link when its resolved path differs from its expected path,
and link entries carry no metadata. Hard links are detected when the host
reports inode numbers and link counts. Files are read in bounded chunks, so an
oversized file fails maxFileBytes without being read whole. A FIFO swapped in
after its type is checked can block the read.
Example
import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
import { MemoryFileSystem, TreeTransfer } from "@effect-vfs/memory"
import { Effect, Stream } from "effect"
const program = Effect.gen(function* () {
const fs = yield* MemoryFileSystem.make
yield* fs.makeDirectory("/project")
yield* fs.writeFileString("/project/a.txt", "a")
const entries = yield* Stream.runCollect(TreeTransfer.fromFileSystem(fs, "/project"))
return entries.map((entry) => entry.path)
})
Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
// [ '/', '/a.txt' ]Signature
declare const fromFileSystem: {
(
root: string,
options?: FileSystemReadOptions
): (fs: FileSystem.FileSystem) => Stream.Stream<Entry, TransferError | PlatformError.PlatformError>
(
fs: FileSystem.FileSystem,
root: string,
options?: FileSystemReadOptions
): Stream.Stream<Entry, TransferError | PlatformError.PlatformError>
}Since v0.6.0
fromSnapshot
Streams the tree at root from an immutable snapshot.
Details
Walks the snapshot's own value through VirtualFileSystem.snapshotEntries,
resolving root as a privileged caller would, so no volume is restored and
the original volume is never read or changed. The limits apply entry by
entry, after each entry's bytes are copied, so a directory past
maxEntries fails at the entry that overflows, after the entries before
it, where fromCaller fails before emitting any of its listing.
Example
import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { TreeTransfer } from "@effect-vfs/memory"
import { Effect, Stream } from "effect"
const program = Effect.gen(function* () {
const volume = yield* Vfs.fromFixture({ entries: [{ kind: "directory", path: "/dist" }] })
const entries = yield* Stream.runCollect(TreeTransfer.fromSnapshot(yield* volume.snapshot, "/dist"))
return entries.length
})
Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
// 1Signature
declare const fromSnapshot: {
(
root: Vfs.PathInput,
options?: ReadOptions
): (snapshot: Vfs.Snapshot) => Stream.Stream<Entry, TransferError | Vfs.VfsError>
(
snapshot: Vfs.Snapshot,
root: Vfs.PathInput,
options?: ReadOptions
): Stream.Stream<Entry, TransferError | Vfs.VfsError>
}Since v0.6.0