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 maxEntries

Signature

declare const TransferError: {
  new (args: {
    readonly code: TransferError["code"]
    readonly field?: string
    readonly path?: Vfs.PathInput
  }): TransferError
  prototype: TransferError
}

Source

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 maxEntries

Signature

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
}

Source

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.md

Signature

type Entry = Vfs.Fixture["entries"][number]

Source

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)
// skip

Signature

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
}

Source

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)
// allow

Signature

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
}

Source

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)
// 10000

Signature

export interface ReadOptions {
  /** Source work policy. Omission uses `TreeTransferLimits.default`. */
  readonly limits?: TreeTransferLimits | undefined
}

Source

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
}

Source

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))
// true

Signature

export type SinkCapabilities = {
  readonly byteNames: boolean
  readonly timestampPrecision: "nanosecond" | "millisecond"
  readonly changeAndBirthTimes: boolean
  readonly owner: boolean
  readonly symlinkMetadata: boolean
  readonly hardLinks: "exact" | "bestEffort"
}

Source

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)
// /fifo

Signature

export type SkippedEntry = {
  readonly path: string | Vfs.BytePath
  readonly reason: "UnrepresentableName" | "UnsupportedEntryType" | "NameCollision"
}

Source

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)
// 2

Signature

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
}

Source

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)
// 8

Signature

export type TreeTransferLimits = {
  readonly maxEntries: number
  readonly maxBytes: ByteSize.ByteSize
  readonly maxFileBytes: ByteSize.ByteSize
  readonly maxDepth: number
  readonly maxPathBytes: ByteSize.ByteSize
}

Source

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)
// true

Signature

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
}

Source

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)
// overwrite

Signature

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
}

Source

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)
// millisecond

Signature

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"
  }>
}

Source

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)
// UnsupportedEntryType

Signature

declare const SkippedEntry: Schema.Struct<{
  readonly path: Schema.Union<readonly [Schema.String, typeof Vfs.BytePath]>
  readonly reason: Schema.Literals<readonly ["UnrepresentableName", "UnsupportedEntryType", "NameCollision"]>
}>

Source

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)
// 2

Signature

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
}>

Source

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 100000

Signature

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 }

Source

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>
}

Source

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>
}

Source

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>
}

Source

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>
}

Source

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>
}

Source

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)
// 1

Signature

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>
}

Source

Since v0.6.0