VirtualFileSystem.ts overview

Runtime-neutral virtual filesystem contracts and constructors.

Details

Volumes own isolated namespaces. Callers carry path context and credentials, while file and directory handles use Scope for deterministic release.

Since v0.1.0


constructors

fromFixture

Builds a fresh volume from a validated final-state fixture.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { Effect } from "effect"
 
const encoder = new TextEncoder()
 
const program = Effect.gen(function* () {
  // Parent directories must be listed explicitly, in any order.
  const volume = yield* Vfs.fromFixture({
    entries: [
      { kind: "directory", path: "/project" },
      {
        kind: "file",
        path: "/project/package.json",
        bytes: encoder.encode(`{"version":"1.2.3"}`)
      },
      { kind: "symlink", path: "/project/latest", target: "/project/package.json" }
    ]
  })
 
  // A fixture's directories list their entries in the byte order of their names.
  const listing = yield* (yield* volume.caller()).readDirectory("/project")
 
  return listing.value.map((entry) => new TextDecoder().decode(entry.name))
})
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// [ 'latest', 'package.json' ]

See

  • The fixtures guide at /guides/fixtures-and-snapshots.
  • Crypto.Crypto for the required secure random-byte service.

Signature

declare const fromFixture: {
  (options?: VolumeOptions): (fixture: Fixture) => Effect.Effect<Volume, VfsError, Crypto.Crypto>
  (fixture: Fixture, options?: VolumeOptions): Effect.Effect<Volume, VfsError, Crypto.Crypto>
}

Source

Since v0.1.0

fromSnapshot

Restores a fresh volume from an opaque snapshot under the supplied destination limits.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { Effect } from "effect"
 
const program = Effect.gen(function* () {
  const original = yield* Vfs.make()
  const source = yield* original.caller()
 
  yield* source.writeFile("/seed.txt", new TextEncoder().encode("one"), {
    access: "write",
    create: "exclusive"
  })
 
  const restored = yield* Vfs.fromSnapshot(yield* original.snapshot)
  const copy = yield* restored.caller()
 
  // Writing to the restore leaves the original untouched: the two are independent.
  yield* copy.writeFile("/seed.txt", new TextEncoder().encode("two"), {
    access: "write",
    truncate: true
  })
 
  return [
    new TextDecoder().decode(yield* source.readFile("/seed.txt")),
    new TextDecoder().decode(yield* copy.readFile("/seed.txt"))
  ]
})
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// [ 'one', 'two' ]

See

  • makeOverlay for a copy-on-write workspace over the same base.
  • Crypto.Crypto for the required secure random-byte service.

Signature

declare const fromSnapshot: {
  (options?: VolumeOptions): (snapshot: Snapshot) => Effect.Effect<Volume, VfsError, Crypto.Crypto>
  (snapshot: Snapshot, options?: VolumeOptions): Effect.Effect<Volume, VfsError, Crypto.Crypto>
}

Source

Since v0.1.0

make

Creates a fresh empty volume and captures the current Effect Clock.

Details

Each execution creates independent storage. Snapshot image failures cannot arise because this constructor does not accept persisted input. Provide Crypto.Crypto to generate the volume identity and reference-key secret.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { Effect } from "effect"
 
const program = Effect.gen(function* () {
  const volume = yield* Vfs.make()
  const caller = yield* volume.caller()
 
  yield* caller.mkdir("/work")
  yield* caller.writeFile("/work/notes.txt", new Uint8Array([104, 105]), { access: "write", create: "exclusive" })
 
  return yield* caller.readFile("/work/notes.txt")
})
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// Uint8Array(2) [ 104, 105 ]

See

  • The testing guide at /guides/testing-with-an-isolated-filesystem.

Signature

declare const make: (options?: VolumeOptions) => Effect.Effect<Volume, VfsError, Crypto.Crypto>

Source

Since v0.1.0

makeOverlay

Creates an isolated writable volume relative to one immutable snapshot base.

Details

Workspaces made from the same snapshot share unchanged regular-file payloads. The first content mutation copies the whole file into workspace-private storage. Metadata, namespace state, coordination, handles, and watches are always private to the new workspace.

Invalid base snapshots and invalid volume limits fail with VfsError. Each execution creates a fresh workspace.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { Effect } from "effect"
 
const program = Effect.gen(function* () {
  const base = yield* Vfs.make()
  const origin = yield* base.caller()
 
  yield* origin.writeFile("/config.json", new TextEncoder().encode("{}"), {
    access: "write",
    create: "exclusive"
  })
 
  const workspace = yield* Vfs.makeOverlay(yield* base.snapshot)
 
  yield* (yield* workspace.caller()).writeFile("/config.json", new TextEncoder().encode("[]"), {
    access: "write",
    truncate: true
  })
 
  // The write landed in the workspace only; the base still reads its own value.
  return [
    new TextDecoder().decode(yield* origin.readFile("/config.json")),
    (yield* workspace.changes()).map((change) => change._tag)
  ]
})
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// [ '{}', [ 'Updated' ] ]

See

  • The overlay guide at /guides/overlay-filesystems.
  • Crypto.Crypto for the required secure random-byte service.

Signature

declare const makeOverlay: {
  (options?: VolumeOptions): (base: Snapshot) => Effect.Effect<OverlayVolume, VfsError, Crypto.Crypto>
  (base: Snapshot, options?: VolumeOptions): Effect.Effect<OverlayVolume, VfsError, Crypto.Crypto>
}

Source

Since v0.1.0

pathFromBytes

Creates an opaque byte path by copying the input when the Effect executes.

Gotchas

Shared-memory-backed and detached views fail with InvalidArgument.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { Effect } from "effect"
 
// Any byte except NUL is accepted. Empty, NUL-bearing, detached, and shared-memory
// input all fail with `InvalidArgument`.
const program = Effect.gen(function* () {
  const path = yield* Vfs.pathFromBytes(new Uint8Array([47, 116, 109, 112]))
 
  const rejected = yield* Vfs.pathFromBytes(new Uint8Array([47, 0])).pipe(
    Effect.as("accepted"),
    Effect.catchTag("VfsError", (error) => Effect.succeed(error.code))
  )
 
  return [yield* Vfs.pathToBytes(path), rejected]
})
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// [ Uint8Array(4) [ 47, 116, 109, 112 ], 'InvalidArgument' ]

Signature

declare const pathFromBytes: (bytes: Uint8Array) => Effect.Effect<BytePath, FsFailure>

Source

Since v0.1.0

getters

pathToBytes

Copies the bytes held by an opaque byte path.

Signature

declare const pathToBytes: (path: BytePath) => Effect.Effect<Uint8Array, FsFailure>

Source

Since v0.1.0

models

Caller (interface)

A filesystem caller with its own identity, creation mask, and current directory.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { Effect } from "effect"
 
// Whole-file reads and writes need no scope. `writeFile` always takes an
// options bag; `access` is required and `create` decides what a missing file means.
const program = Effect.gen(function* () {
  const volume = yield* Vfs.make()
  const caller = yield* volume.caller()
 
  yield* caller.mkdir("/work")
  yield* caller.writeFile("/work/notes.txt", new TextEncoder().encode("hello"), {
    access: "write",
    create: "exclusive"
  })
 
  return (yield* caller.readDirectory("/work")).value.map((entry) => new TextDecoder().decode(entry.name))
})
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// [ 'notes.txt' ]

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { Effect } from "effect"
 
// Reach for a handle when a whole-file write will not do. A handle carries a
// cursor and lives until its scope closes.
const program = Effect.gen(function* () {
  const caller = yield* (yield* Vfs.make()).caller()
 
  const handle = yield* caller.open("/log.txt", {
    access: "readWrite",
    create: "exclusive"
  })
 
  yield* handle.write(new TextEncoder().encode("first "))
  yield* handle.write(new TextEncoder().encode("second"))
  yield* handle.seek(0n, "start")
 
  return new TextDecoder().decode(yield* handle.read(64))
}).pipe(Effect.scoped)
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// first second

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { Effect } from "effect"
 
// A derived caller holds the directory's identity, not its path, so renaming
// the directory does not redirect later relative writes.
const program = Effect.gen(function* () {
  const caller = yield* (yield* Vfs.make()).caller()
 
  yield* caller.mkdir("/build")
 
  const work = yield* caller.withDirectory("/build")
 
  yield* caller.rename("/build", "/dist")
  yield* work.writeFile("out.txt", new TextEncoder().encode("done"), {
    access: "write",
    create: "exclusive"
  })
 
  return (yield* caller.readDirectory("/dist")).value.map((entry) => new TextDecoder().decode(entry.name))
}).pipe(Effect.scoped)
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// [ 'out.txt' ]

See

  • The filesystem model at /concepts/filesystem-model.

Signature

export interface Caller {
  readonly [CallerId]: true
  /** Backing volume configuration; these are not subtree quotas. */
  readonly limits: VolumeLimits
  /** Watches current authorized names, rebasing events when each change commits. */
  readonly watch: (
    target: TargetInput,
    options?: WatchModule.CallerWatchOptions
  ) => Effect.Effect<Stream.Stream<Change, FsFailure>, FsFailure, Scope.Scope>
  /**
   * Narrows authority to a directory identity and resets the working directory. Absolute paths and symbolic
   * links start at this root; `..` stops here. Nested roots retain their ancestor boundaries. References and
   * direct handles check current membership on every use, including privileged callers. Root rename preserves
   * authority; deletion permanently invalidates it. Existing hard links can share contents across roots.
   * Independently scoped descendants and handles retain their own lifetime when their creator closes.
   */
  readonly withRoot: (directory: TargetInput) => Effect.Effect<Caller, FsFailure, Scope.Scope>
  /** The reference of the caller's visible root directory. */
  readonly root: Effect.Effect<ObjectReference, FsFailure>
  /** The reference of a child of a directory, without following it. */
  readonly lookup: (entry: EntryInput) => Effect.Effect<ObjectReference, FsFailure>
  /** The reference of a directory's parent; the root is its own parent. */
  readonly parent: (directory: TargetInput) => Effect.Effect<ObjectReference, FsFailure>
  /** Metadata and revision of a target. A path follows a final symbolic link unless its target says otherwise. */
  readonly stat: (target: TargetInput) => Effect.Effect<Metadata, FsFailure>
  /** The entries of a directory with their references, and the directory's revision. */
  readonly readDirectory: (
    directory: TargetInput
  ) => Effect.Effect<ObjectObservation<ReadonlyArray<DirectoryEntry>>, FsFailure>
  /**
   * Every entry below a directory, as a stream that reads one directory at a time and holds no handle. Entries
   * arrive depth first, each directory's entries in the byte order of their names; symbolic links are reported,
   * never followed. Each directory is read in one observation, so the walk sees each listing whole but no
   * snapshot of the tree, and it refreshes no access time. Each directory below the root is reached by name, as a
   * path lookup reaches it, so it needs search permission on every directory above it, and one that goes away or
   * is renamed out of the tree before the walk reads it is skipped. A failure below the root names the entry's
   * path under the path the root was given by, or under the root when it was given by reference or handle.
   */
  readonly walk: (directory: TargetInput, options?: WalkOptions) => Stream.Stream<WalkEntry, WalkFailure>
  /** The bytes a symbolic link points at. A path never follows the final link. */
  readonly readLink: (target: TargetInput) => Effect.Effect<Uint8Array, FsFailure>
  /** The canonical absolute path of a target. */
  readonly realPath: (target: TargetInput) => Effect.Effect<BytePath, FsFailure>
  /** The bits of `bits` the caller may exercise on the target: READ, WRITE, EXECUTE as 4, 2, 1. */
  readonly access: (target: TargetInput, bits?: number) => Effect.Effect<number, FsFailure>
  /** The whole content of a regular file. */
  readonly readFile: (target: TargetInput) => Effect.Effect<Uint8Array, FsFailure>
  /**
   * Writes a whole file in one operation and returns its object reference, creating or replacing it as the options say.
   * The curried form is `writeFile(bytes, options)(entry)`; the caller remains bound.
   */
  readonly writeFile: {
    (bytes: Uint8Array, options: WriteFileOptions): (entry: EntryInput) => Effect.Effect<ObjectReference, FsFailure>
    (entry: EntryInput, bytes: Uint8Array, options: WriteFileOptions): Effect.Effect<ObjectReference, FsFailure>
  }
  /**
   * Opens a file. On a target, the file must exist and `create` is rejected. On an entry, the name is looked up
   * and created in one gate hold, and the result reports whether it was created.
   */
  readonly open: {
    (
      target: Target | PathInput | ObjectReference | FileHandle,
      options: OpenOptions
    ): Effect.Effect<FileHandle, FsFailure, Scope.Scope>
    (entry: Entry, options: OpenEntryOptions): Effect.Effect<OpenEntryResult, FsFailure, Scope.Scope>
  }
  /**
   * Creates a directory. With `recursive`, every missing directory on a path is created in one change, so a
   * failure creates none and a watcher sees them all at once; symbolic links on the way are followed, dot names
   * are walked, and a final name that is already a directory (or a link to one) succeeds without a change. The
   * result names the directory the path ends on, with its parent's revision before and after the call, so a path
   * that leaves a directory it created, such as `new/..`, still reports the change. A name on the way that is not
   * a directory fails with `NotDirectory`, a final one with `AlreadyExists`, and only names the caller wrote are
   * created, so a dangling link fails with `NotFound`.
   */
  readonly mkdir: (entry: EntryInput, options?: MkdirOptions) => Effect.Effect<ReferenceEntryResult, FsFailure>
  /** Creates a symbolic link to `target`, stored as given. */
  readonly symlink: (
    target: PathInput,
    entry: EntryInput,
    options?: SymlinkOptions
  ) => Effect.Effect<ReferenceEntryResult, FsFailure>
  /** Adds a name for an existing file or symbolic link. A path source follows a final symbolic link only when its target says so. */
  readonly link: {
    (entry: EntryInput): (source: TargetInput) => Effect.Effect<ReferenceEntryResult, FsFailure>
    (source: TargetInput, entry: EntryInput): Effect.Effect<ReferenceEntryResult, FsFailure>
  }
  /** Removes a file or symbolic link. */
  readonly unlink: (entry: EntryInput) => Effect.Effect<DirectoryChange, FsFailure>
  /** Removes an empty directory. */
  readonly rmdir: (entry: EntryInput) => Effect.Effect<DirectoryChange, FsFailure>
  /**
   * Removes a file, a symbolic link, or an empty directory. With `recursive`, a directory that has entries is
   * emptied first, entries before their directories, each removal its own change; it stops at the first failure,
   * whose path names the entry it stopped at, and leaves what it has not reached. Each entry is removed by its name
   * in a directory reached by name, so a subtree renamed out of the target is left alone. A directory with entries
   * must be readable, and no permission is changed to make it so. With `force`, a missing target is success and the
   * result is `undefined`; a failure below the target is still a failure.
   */
  readonly remove: {
    (entry: EntryInput, options?: RemoveOptions & { readonly force?: false }): Effect.Effect<DirectoryChange, FsFailure>
    (entry: EntryInput, options: RemoveOptions): Effect.Effect<DirectoryChange | undefined, FsFailure>
  }
  /** Moves an entry, replacing a compatible destination. */
  readonly rename: {
    (to: EntryInput): (from: EntryInput) => Effect.Effect<RenameReferenceResult, FsFailure>
    (from: EntryInput, to: EntryInput): Effect.Effect<RenameReferenceResult, FsFailure>
  }
  /** Sets the permission bits of a target. */
  readonly chmod: {
    (mode: number): (target: TargetInput) => Effect.Effect<void, FsFailure>
    (target: TargetInput, mode: number): Effect.Effect<void, FsFailure>
  }
  /** Changes the owner or group of a target. */
  readonly chown: {
    (owner: OwnerUpdate): (target: TargetInput) => Effect.Effect<void, FsFailure>
    (target: TargetInput, owner: OwnerUpdate): Effect.Effect<void, FsFailure>
  }
  /** Sets the access and modification times of a target. */
  readonly utimes: {
    (times: Times): (target: TargetInput) => Effect.Effect<void, FsFailure>
    (target: TargetInput, times: Times): Effect.Effect<void, FsFailure>
  }
  /** Sets the size of a regular file. */
  readonly truncate: {
    (length: bigint): (target: TargetInput) => Effect.Effect<void, FsFailure>
    (target: TargetInput, length: bigint): Effect.Effect<void, FsFailure>
  }
  /**
   * Changes a target's size, owner, mode and times as one change. Every check passes before any attribute
   * applies, so a failure changes nothing. The attributes apply as size, owner, mode, then times, the POSIX
   * composition of chown then chmod, so a requested mode wins over the set-ID clearing an owner change or a
   * resize triggers. A protocol adapter that sanitises the requested mode, as NFS does after Linux knfsd,
   * does so before it calls setattr, and passes the revision it read as `expected` so the change refuses a
   * target another change has moved.
   */
  readonly setattr: {
    (attributes: SetattrOptions): (target: TargetInput) => Effect.Effect<void, FsFailure>
    (target: TargetInput, attributes: SetattrOptions): Effect.Effect<void, FsFailure>
  }
  /** A caller whose working directory is the target, released with the scope. */
  readonly withDirectory: (directory: TargetInput) => Effect.Effect<Caller, FsFailure, Scope.Scope>
  /** A handle on a directory, released with the scope. */
  readonly openDirectory: (directory: TargetInput) => Effect.Effect<DirectoryHandle, FsFailure, Scope.Scope>
}

Source

Since v0.1.0

CallerWatchOptions (type alias)

Options for a caller watch.

Signature

export type CallerWatchOptions = {
  readonly recursive?: boolean
  readonly alias?: "all" | "resolved"
}

Source

Since v0.9.0

Change (type alias)

One committed change observed through a watch subscription. Rescan means the subscriber lost events; it names / for a volume-wide watch and the scope's current path for a scoped one.

Signature

export type Change =
  | { readonly _tag: "Create"; readonly path: BytePath }
  | { readonly _tag: "Update"; readonly path: BytePath }
  | { readonly _tag: "Remove"; readonly path: BytePath }
  | { readonly _tag: "Rescan"; readonly path: BytePath }

Source

Since v0.1.0

DirectoryChange (type alias)

A directory revision transition captured inside one coordinated operation.

Signature

export type DirectoryChange = { readonly before: bigint; readonly after: bigint }

Source

Since v0.4.0

DirectoryEntry (interface)

One owned directory name paired with the referenced child object.

Signature

export interface DirectoryEntry {
  readonly name: Uint8Array
  readonly reference: ObjectReference
}

Source

Since v0.1.0

DirectoryHandle (interface)

A scoped directory capability that can be used for metadata and relative lookup.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { Effect } from "effect"
 
// The handle anchors relative paths to the directory itself, so a rename in
// between does not send the write somewhere else.
const program = Effect.gen(function* () {
  const caller = yield* (yield* Vfs.make()).caller()
 
  yield* caller.mkdir("/project")
 
  const directory = yield* caller.openDirectory("/project")
 
  yield* caller.rename("/project", "/renamed")
  yield* caller.writeFile(
    Vfs.Target.Path({ path: "notes.txt", relativeTo: directory }),
    new TextEncoder().encode("kept"),
    {
      access: "write",
      create: "exclusive"
    }
  )
 
  return (yield* caller.readDirectory("/renamed")).value.map((entry) => new TextDecoder().decode(entry.name))
}).pipe(Effect.scoped)
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// [ 'notes.txt' ]

Signature

export interface DirectoryHandle {
  readonly [DirectoryHandleId]: true
  readonly stat: Effect.Effect<Metadata, FsFailure>
  readonly close: Effect.Effect<void, FsFailure>
}

Source

Since v0.1.0

FileHandle (interface)

A scoped regular-file capability with an independent bigint cursor.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { Effect } from "effect"
 
const program = Effect.gen(function* () {
  const caller = yield* (yield* Vfs.make()).caller()
 
  const handle = yield* caller.open("/data.bin", {
    access: "readWrite",
    create: "exclusive"
  })
 
  yield* handle.write(new Uint8Array([1, 2, 3, 4]))
 
  // `pread` reads at an offset and leaves the cursor where writing left it.
  const slice = yield* handle.pread(2, 1n)
 
  return [slice.bytes, slice.eof, yield* handle.seek(0n, "current")]
}).pipe(Effect.scoped)
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// [ Uint8Array(2) [ 2, 3 ], false, 4n ]

Signature

export interface FileHandle {
  readonly [FileHandleId]: true
  /** Reads up to `maximumBytes` from the cursor and advances it by the returned length. */
  readonly read: (maximumBytes: number) => Effect.Effect<Uint8Array, FsFailure>
  /** Reads at `offset` without changing the cursor. */
  readonly pread: (maximumBytes: number, offset: bigint) => Effect.Effect<ReadResult, FsFailure>
  /** Writes at the cursor and advances it, or writes at end of file when opened for append. */
  readonly write: (bytes: Uint8Array) => Effect.Effect<number, FsFailure>
  /**
   * Writes at `offset` without changing the cursor. Append mode does not affect positional writes.
   * The curried form is `pwrite(offset)(bytes)`.
   */
  readonly pwrite: {
    (offset: bigint): (bytes: Uint8Array) => Effect.Effect<number, FsFailure>
    (bytes: Uint8Array, offset: bigint): Effect.Effect<number, FsFailure>
  }
  /**
   * Moves the cursor and returns its new offset. `data` finds content and `hole` finds end of file.
   * The curried form is `seek(mode)(offset)`.
   */
  readonly seek: {
    (mode: SeekMode): (offset: bigint) => Effect.Effect<bigint, FsFailure>
    (offset: bigint, mode: SeekMode): Effect.Effect<bigint, FsFailure>
  }
  /** Sets the file length without changing the cursor. */
  readonly truncate: (length: bigint) => Effect.Effect<void, FsFailure>
  /** Reads metadata for the open file. */
  readonly stat: Effect.Effect<Metadata, FsFailure>
  /** Checks handle and provider health. Durable mutations have already committed; memory volumes have nothing to flush. */
  readonly sync: Effect.Effect<void, FsFailure>
  /** Closes the handle. A repeated explicit close fails; scope cleanup remains safe. */
  readonly close: Effect.Effect<void, FsFailure>
}

Source

Since v0.1.0

Fixture (type alias)

A complete filesystem fixture accepted by fromFixture.

Signature

export type Fixture = {
  readonly entries: readonly (
    | {
        readonly path: string | BytePath
        readonly kind: "directory"
        readonly metadata?: {
          readonly uid?: number
          readonly gid?: number
          readonly mode?: number
          readonly atimeNs?: bigint
          readonly mtimeNs?: bigint
          readonly ctimeNs?: bigint
          readonly birthtimeNs?: bigint
        }
      }
    | {
        readonly path: string | BytePath
        readonly kind: "file"
        readonly bytes: Uint8Array<ArrayBufferLike>
        readonly metadata?: {
          readonly uid?: number
          readonly gid?: number
          readonly mode?: number
          readonly atimeNs?: bigint
          readonly mtimeNs?: bigint
          readonly ctimeNs?: bigint
          readonly birthtimeNs?: bigint
        }
      }
    | {
        readonly path: string | BytePath
        readonly kind: "symlink"
        readonly target: string | BytePath
        readonly metadata?: {
          readonly uid?: number
          readonly gid?: number
          readonly mode?: number
          readonly atimeNs?: bigint
          readonly mtimeNs?: bigint
          readonly ctimeNs?: bigint
          readonly birthtimeNs?: bigint
        }
      }
    | {
        readonly kind: "hardLink"
        readonly path: string | BytePath
        readonly target: string | BytePath
      }
  )[]
  readonly rootMetadata?: {
    readonly uid?: number
    readonly gid?: number
    readonly mode?: number
    readonly atimeNs?: bigint
    readonly mtimeNs?: bigint
    readonly ctimeNs?: bigint
    readonly birthtimeNs?: bigint
  }
}

Source

Since v0.1.0

FsCode (type alias)

A portable virtual filesystem error code.

Signature

export type FsCode =
  | "NotFound"
  | "AlreadyExists"
  | "NotEmpty"
  | "NotDirectory"
  | "AccessDenied"
  | "NotPermitted"
  | "InvalidHandle"
  | "ForeignHandle"
  | "InvalidReference"
  | "ForeignReference"
  | "StaleReference"
  | "ClosedCaller"
  | "InvalidArgument"
  | "InvalidPathEncoding"
  | "PathTooLong"
  | "NoSpace"
  | "IsDirectory"
  | "FileTooLarge"
  | "NoData"
  | "SymlinkLoop"
  | "UnrepresentableName"
  | "StorageRejected"
  | "OutcomeUnknown"
  | "VolumeUnavailable"
  | "VolumeBusy"

Source

Since v0.1.0

Identity (type alias)

A caller identity used for permission checks.

Signature

export type Identity = {
  readonly uid: number
  readonly gid: number
  readonly groups: readonly number[]
  readonly privileged: boolean
}

Source

Since v0.1.0

Metadata (type alias)

Metadata for a directory, regular file, or symbolic link.

Signature

export type Metadata = {
  readonly kind: "file" | "directory" | "symlink"
  readonly ino: bigint
  readonly nlink: number
  readonly size: bigint
  readonly uid: number
  readonly gid: number
  readonly mode: number
  readonly atimeNs: bigint
  readonly mtimeNs: bigint
  readonly ctimeNs: bigint
  readonly birthtimeNs: bigint
  readonly revision: bigint
}

Source

Since v0.1.0

MkdirOptions (type alias)

Settings for creating a directory through a parent object reference. exactMode skips the caller's umask for an explicit creation mode; permission policy still applies. recursive creates every missing directory on a path in one change, as mkdir -p does.

Signature

export type MkdirOptions = {
  readonly mode?: number
  readonly exactMode?: boolean
  readonly times?: {
    readonly access:
      | { readonly kind: "now" }
      | { readonly kind: "omit" }
      | { readonly kind: "value"; readonly nanoseconds: bigint }
    readonly modification:
      | { readonly kind: "now" }
      | { readonly kind: "omit" }
      | { readonly kind: "value"; readonly nanoseconds: bigint }
  }
  readonly recursive?: boolean
}

Source

Since v0.4.0

ObjectObservation (interface)

A value and the revision of the object from the same coordinated observation.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { Effect } from "effect"
 
// References survive renames, and each observation carries the revision it
// matched, so a caller can tell whether what it read is still current.
const program = Effect.gen(function* () {
  const caller = yield* (yield* Vfs.make()).caller()
 
  yield* caller.mkdir("/work")
 
  const root = yield* caller.root
  const work = yield* caller.lookup(Vfs.Entry(root, "work"))
 
  const before = yield* caller.stat(work)
 
  yield* caller.rename("/work", "/moved")
 
  const after = yield* caller.stat(work)
 
  return [before.kind, after.kind, after.revision > before.revision]
})
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// [ 'directory', 'directory', true ]

Signature

export interface ObjectObservation<A> {
  readonly value: A
  readonly revision: bigint
}

Source

Since v0.1.0

ObjectReference (interface)

An opaque identity for one object in one live volume.

Signature

export interface ObjectReference {
  readonly [ObjectReferenceId]: true
}

Source

Since v0.1.0

OpenEntryOptions (type alias)

Settings for atomically looking up or creating and opening one referenced child. exactMode skips the caller's umask for an explicit creation mode; permission policy still applies. Initial mode, times, initialSize, and owner apply only to a newly created file. expectedChild: null requires an absent direct child. An observation requires the same direct object, revision, and timestamps; a mismatch fails with StaleReference before changing the file. Omission skips this observation check. This condition checks the named entry before any final symbolic-link traversal.

Signature

export type OpenEntryOptions = {
  readonly access: "read" | "write" | "readWrite"
  readonly expected?: ObjectReference | null
  readonly mode?: number
  readonly create?: "never" | "ifMissing" | "exclusive"
  readonly append?: boolean
  readonly truncate?: boolean
  readonly exactMode?: boolean
  readonly times?: {
    readonly access:
      | { readonly kind: "now" }
      | { readonly kind: "omit" }
      | { readonly kind: "value"; readonly nanoseconds: bigint }
    readonly modification:
      | { readonly kind: "now" }
      | { readonly kind: "omit" }
      | { readonly kind: "value"; readonly nanoseconds: bigint }
  }
  readonly owner?: { readonly uid?: number; readonly gid?: number }
  readonly followFinalSymlink?: boolean
  readonly initialSize?: bigint
  readonly expectedChild?: {
    readonly reference: ObjectReference
    readonly revision: bigint
    readonly atimeNs: bigint
    readonly mtimeNs: bigint
  } | null
}

Source

Since v0.4.0

OpenEntryResult (interface)

A scoped child-open result captured inside one coordinated operation.

Signature

export interface OpenEntryResult {
  readonly handle: FileHandle
  readonly reference: ObjectReference
  readonly created: boolean
  readonly directory: DirectoryChange
}

Source

Since v0.4.0

OpenOptions (type alias)

Options of open on a target.

Signature

export type OpenOptions = {
  readonly access: "read" | "write" | "readWrite"
  readonly mode?: number
  readonly create?: "never" | "ifMissing" | "exclusive"
  readonly append?: boolean
  readonly truncate?: boolean
}

Source

Since v0.1.0

OverlayCapture (interface)

A complete snapshot and final-difference summary captured from one committed state.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { Effect } from "effect"
 
const program = Effect.gen(function* () {
  const base = yield* Vfs.make()
  const workspace = yield* Vfs.makeOverlay(yield* base.snapshot)
 
  yield* (yield* workspace.caller()).writeFile("/out.txt", new TextEncoder().encode("built"), {
    access: "write",
    create: "exclusive"
  })
 
  // One committed state: the summary describes exactly the snapshot beside it,
  // so restoring from it cannot disagree with the change list.
  const captured = yield* workspace.capture()
  const restored = yield* Vfs.fromSnapshot(captured.snapshot)
  const reader = yield* restored.caller()
 
  return [captured.changes.map((change) => change._tag), new TextDecoder().decode(yield* reader.readFile("/out.txt"))]
})
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// [ [ 'Added' ], 'built' ]

Signature

export interface OverlayCapture {
  /** Complete version 1 snapshot owned by this capture. */
  readonly snapshot: Snapshot
  /** Owned summary that describes the same state as `snapshot`. */
  readonly changes: ReadonlyArray<OverlayChange>
}

Source

Since v0.1.0

OverlayChange (type alias)

A path-oriented final-state difference from an overlay's immutable base.

Signature

export type OverlayChange =
  | {
      readonly _tag: "Added"
      readonly path: BytePath
      readonly kind: "file" | "directory" | "symlink"
    }
  | {
      readonly _tag: "Removed"
      readonly path: BytePath
      readonly kind: "file" | "directory" | "symlink"
    }
  | {
      readonly _tag: "Replaced"
      readonly path: BytePath
      readonly beforeKind: "file" | "directory" | "symlink"
      readonly afterKind: "file" | "directory" | "symlink"
      readonly differences: readonly (
        "uid" | "gid" | "mode" | "atimeNs" | "mtimeNs" | "ctimeNs" | "birthtimeNs" | "content"
      )[]
    }
  | {
      readonly _tag: "Renamed"
      readonly from: BytePath
      readonly to: BytePath
      readonly kind: "file" | "directory" | "symlink"
      readonly differences: readonly (
        "uid" | "gid" | "mode" | "atimeNs" | "mtimeNs" | "ctimeNs" | "birthtimeNs" | "content"
      )[]
    }
  | {
      readonly _tag: "Updated"
      readonly path: BytePath
      readonly kind: "file" | "directory" | "symlink"
      readonly differences: readonly (
        "uid" | "gid" | "mode" | "atimeNs" | "mtimeNs" | "ctimeNs" | "birthtimeNs" | "content"
      )[]
    }

Source

Since v0.1.0

OverlayChangesOptions (type alias)

Filtering options for an overlay final-difference summary.

Signature

export type OverlayChangesOptions = { readonly includeTimestamps?: boolean }

Source

Since v0.1.0

OverlayDifference (type alias)

A content, ownership, permission, or timestamp field that differs from the overlay base.

Signature

export type OverlayDifference =
  "uid" | "gid" | "mode" | "atimeNs" | "mtimeNs" | "ctimeNs" | "birthtimeNs" | "content"

Source

Since v0.1.0

OverlayNodeKind (type alias)

A filesystem entry kind reported by an overlay summary.

Signature

export type OverlayNodeKind = "file" | "directory" | "symlink"

Source

Since v0.1.0

OverlayVolume (interface)

An ordinary volume with final-state inspection relative to one immutable snapshot base.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { Effect } from "effect"
 
const program = Effect.gen(function* () {
  const base = yield* Vfs.make()
  const workspace = yield* Vfs.makeOverlay(yield* base.snapshot)
 
  yield* (yield* workspace.caller()).writeFile("/added.txt", new Uint8Array([1]), {
    access: "write",
    create: "exclusive"
  })
 
  // `capture` pairs one committed snapshot with the summary describing it.
  const captured = yield* workspace.capture()
 
  return captured.changes.map((change) => change._tag)
})
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// [ 'Added' ]

Signature

export interface OverlayVolume extends Volume {
  /** Computes final differences from the immutable base when this reusable effect executes. */
  readonly changes: (options?: OverlayChangesOptions) => Effect.Effect<ReadonlyArray<OverlayChange>, VfsError>
  /** Captures one committed state when this reusable effect executes. */
  readonly capture: (options?: OverlayChangesOptions) => Effect.Effect<OverlayCapture, VfsError>
}

Source

Since v0.1.0

OwnerUpdate (type alias)

An owner update for chown operations.

Signature

export type OwnerUpdate = { readonly uid?: number; readonly gid?: number }

Source

Since v0.1.0

PathInput (type alias)

A UTF-8 string path or an opaque byte-preserving path.

Details

Every operation accepts both. Pass a string unless a name is not valid UTF-8; strings are the normal case and byte paths are the escape hatch.

See

  • BytePath for the byte-preserving form.

Signature

type PathInput = string | BytePath

Source

Since v0.1.0

ReadResult (interface)

The bytes a positional read returned and whether they reach the end of the file.

Signature

export interface ReadResult {
  readonly bytes: Uint8Array
  readonly eof: boolean
}

Source

Since v0.6.0

ReferenceEntryResult (interface)

The identity and parent-directory transition produced by a named entry mutation.

Signature

export interface ReferenceEntryResult {
  readonly reference: ObjectReference
  readonly directory: DirectoryChange
}

Source

Since v0.4.0

ReferenceKey (type alias)

The serialisable name of one object in one volume.

Signature

export type ReferenceKey = {
  readonly identity: Uint8Array<ArrayBufferLike>
  readonly epoch: Uint8Array<ArrayBufferLike>
  readonly ino: bigint
  readonly tag: Uint8Array<ArrayBufferLike>
}

Source

Since v0.6.0

RemoveOptions (type alias)

Settings for removing an entry.

Signature

export type RemoveOptions = {
  readonly expected?: ObjectReference
  readonly recursive?: boolean
  readonly force?: boolean
}

Source

Since v0.6.0

RenameReferenceResult (type alias)

The directory transitions produced by a same-directory or cross-directory rename.

Signature

export type RenameReferenceResult =
  | {
      readonly _tag: "DifferentDirectories"
      readonly sourceDirectory: { readonly before: bigint; readonly after: bigint }
      readonly destinationDirectory: { readonly before: bigint; readonly after: bigint }
    }
  | {
      readonly _tag: "SameDirectory"
      readonly directory: { readonly before: bigint; readonly after: bigint }
    }

Source

Since v0.4.0

RootCallerOptions (type alias)

Options for creating a root caller on a volume.

Signature

export type RootCallerOptions = {
  readonly identity?: {
    readonly uid: number
    readonly gid: number
    readonly groups: readonly number[]
    readonly privileged: boolean
  }
  readonly umask?: number
}

Source

Since v0.1.0

SeekMode (type alias)

The origin used by a file handle seek operation.

Signature

export type SeekMode = "start" | "end" | "current" | "data" | "hole"

Source

Since v0.1.0

SetattrOptions (type alias)

The attributes setattr changes together.

Signature

export type SetattrOptions = {
  readonly expected?: { readonly revision: bigint }
  readonly size?: bigint
  readonly mode?: number
  readonly times?: {
    readonly access:
      | { readonly kind: "now" }
      | { readonly kind: "omit" }
      | { readonly kind: "value"; readonly nanoseconds: bigint }
    readonly modification:
      | { readonly kind: "now" }
      | { readonly kind: "omit" }
      | { readonly kind: "value"; readonly nanoseconds: bigint }
  }
  readonly owner?: { readonly uid?: number; readonly gid?: number }
}

Source

Since v0.6.0

SymlinkOptions (type alias)

Settings for creating a symbolic link through a parent object reference.

Signature

export type SymlinkOptions = {
  readonly times?: {
    readonly access:
      | { readonly kind: "now" }
      | { readonly kind: "omit" }
      | { readonly kind: "value"; readonly nanoseconds: bigint }
    readonly modification:
      | { readonly kind: "now" }
      | { readonly kind: "omit" }
      | { readonly kind: "value"; readonly nanoseconds: bigint }
  }
}

Source

Since v0.4.0

Times (type alias)

Access and modification time updates for utimes operations.

Signature

export type Times = {
  readonly access:
    | { readonly kind: "now" }
    | { readonly kind: "omit" }
    | { readonly kind: "value"; readonly nanoseconds: bigint }
  readonly modification:
    | { readonly kind: "now" }
    | { readonly kind: "omit" }
    | { readonly kind: "value"; readonly nanoseconds: bigint }
}

Source

Since v0.1.0

Volume (interface)

An isolated virtual filesystem namespace that creates callers, snapshots, and watch streams.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { Effect, Fiber, Option, Stream } from "effect"
 
const program = Effect.gen(function* () {
  const volume = yield* Vfs.make()
  const caller = yield* volume.caller()
 
  // `watch` reports future changes only; nothing is replayed.
  const watcher = yield* volume
    .watch()
    .pipe(Effect.flatMap(Stream.runHead), Effect.forkChild({ startImmediately: true }))
 
  yield* caller.mkdir("/logs")
 
  const change = yield* Fiber.join(watcher)
 
  return Option.getOrElse(
    Option.map(change, (event) => event._tag),
    () => "none"
  )
}).pipe(Effect.scoped)
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// Create

Signature

export interface Volume {
  /** The strongest failure boundary survived by acknowledged writes. */
  readonly durability: VolumeDurability
  /** Stable logical identity, preserved across reconstruction only when supplied explicitly. */
  readonly identity: VolumeIdentity
  /** Fresh identity for this uninterrupted storage lifetime. */
  readonly incarnation: VolumeIncarnation
  /** Effective static limits, where `undefined` means unlimited. */
  readonly limits: VolumeLimits
  /** Samples content bytes and directory entries together from the current committed state. */
  readonly usage: Effect.Effect<VolumeUsage, FsFailure>
  /**
   * Opens a scoped stream of future changes, registered before it returns; events are not replayed. Without a
   * scope it covers the volume and `Rescan` at `/` requires a full rescan; with one it covers that object (and its
   * subtree unless `recursive` is `false`), `Rescan` names the object's current path, and the stream ends after
   * `Remove` for the object once its last name is gone.
   */
  readonly watch: (options?: WatchOptions) => Effect.Effect<Stream.Stream<Change>, FsFailure, Scope.Scope>
  /** Captures an isolated snapshot of the reachable namespace and metadata. */
  readonly snapshot: Effect.Effect<Snapshot, VfsError>
  /**
   * The serialisable key of an object this volume issued. Fails with `InvalidReference` for a value that is not a
   * reference, `ForeignReference` for another volume's, and `StaleReference` once the object is gone.
   */
  readonly referenceKey: (reference: ObjectReference) => Effect.Effect<ReferenceKey, FsFailure>
  /**
   * The object a key names, as the same reference traversal returns. Fails with `InvalidReference` for a malformed
   * key or one whose tag this volume did not compute, `ForeignReference` for a key another volume or another epoch
   * of this one minted, and `StaleReference` once the object is gone. It checks no permission: holding the volume
   * already grants every caller, and the tag keeps a key holder from reaching an object no key was issued for.
   */
  readonly resolveReferenceKey: (key: ReferenceKey) => Effect.Effect<ObjectReference, FsFailure>
  readonly [VolumeId]: true
  /** Creates a caller rooted at `/` with independent credentials, umask, and current directory. */
  readonly caller: (options?: RootCallerOptions) => Effect.Effect<Caller, VfsError>
}

Source

Since v0.1.0

VolumeDurability (type alias)

The failure boundary survived by an acknowledged volume write.

Signature

export type VolumeDurability =
  | "memory-only"
  | "survives-process-crash"
  | "survives-operating-system-crash"
  | "survives-power-loss"

Source

Since v0.1.0

VolumeIdentity (type alias)

Stable logical identity of a volume as a branded 128-bit lowercase hexadecimal string.

Signature

export type VolumeIdentity = string & Brand.Brand<"@effect-vfs/core/VolumeIdentity">

Source

Since v0.1.0

VolumeIncarnation (type alias)

One uninterrupted storage lifetime as a branded 128-bit lowercase hexadecimal string.

Signature

export type VolumeIncarnation = string & Brand.Brand<"@effect-vfs/core/VolumeIncarnation">

Source

Since v0.1.0

VolumeLimits (interface)

Effective capacity and path limits of a live volume. An absent limit is unlimited. maxFileBytes always includes the engine's maximum representable file size.

Signature

export interface VolumeLimits {
  readonly maxBytes: ByteSize.ByteSize | undefined
  readonly maxFileBytes: ByteSize.ByteSize
  readonly maxEntries: number | undefined
  readonly maxPathBytes: ByteSize.ByteSize | undefined
  /** Waiting budget; at most this many plus one callers are admitted at once. */
  readonly maxPendingOperations: number
  /** Maximum retained events per watch subscriber, including the rescan marker. */
  readonly maxWatchEvents: number
}

Source

Since v0.1.0

VolumeOptions (type alias)

Execution policy, capacity, admission, watch retention, and path limits for a volume. execution defaults to "cooperative"; "sync" opts memory operations into non-yielding coordination. maxPendingOperations defaults to 64 and maxWatchEvents defaults to 256.

Signature

export type VolumeOptions = {
  readonly maxEntries?: number
  readonly maxPathBytes?: ByteSize.ByteSize
  readonly maxFileBytes?: ByteSize.ByteSize
  readonly identity?: string & Brand.Brand<"@effect-vfs/core/VolumeIdentity">
  readonly execution?: "cooperative" | "sync"
  readonly maxPendingOperations?: number
  readonly maxWatchEvents?: number
  readonly maxBytes?: ByteSize.ByteSize
}

Source

Since v0.1.0

VolumeUsage (interface)

Live logical content and directory-entry usage of a volume. Open unlinked files remain in usedBytes until their last handle closes.

Signature

export interface VolumeUsage {
  readonly usedBytes: bigint
  readonly entries: number
}

Source

Since v0.1.0

WalkEntry (interface)

One entry a walk reached: its path below the walk's root, its name, the object it names and the directory it was listed in, its kind, and its depth.

Details

path is relative to the walk's root, with no leading slash (a/b for b in the root's child a), so it reads the same whichever way the root was addressed; the root itself is not an entry. depth is 1 for a child of the root. A symbolic link is reported as a link and never followed.

Signature

export interface WalkEntry {
  readonly path: BytePath
  readonly name: Uint8Array
  readonly reference: ObjectReference
  readonly directory: ObjectReference
  readonly kind: Metadata["kind"]
  readonly depth: number
}

Source

Since v0.6.0

WalkFailure (type alias)

What a walk fails with: a failure of the directory it lists, or LimitExceeded when it would pass one of its bounds, which field names.

Signature

type WalkFailure = FsFailure | (VfsError & { readonly code: "LimitExceeded" })

Source

Since v0.6.0

WalkOptions (type alias)

Settings for walking a directory tree.

Signature

export type WalkOptions = {
  readonly maxEntries?: number
  readonly maxDepth?: number
  readonly maxBytes?: ByteSize.ByteSize
  readonly order?: "pre" | "post"
}

Source

Since v0.6.0

WatchOptions (type alias)

Options of Volume.watch.

Signature

export type WatchOptions = { readonly recursive?: boolean; readonly scope?: ObjectReference }

Source

Since v0.6.0

WriteFileOptions

Options for an atomic whole-file write, including replacement and final mode controls.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { Effect } from "effect"
 
const program = Effect.gen(function* () {
  const caller = yield* (yield* Vfs.make()).caller()
  const bytes = new TextEncoder().encode("v2")
 
  // `access` is required. `create: "exclusive"` refuses to replace an entry,
  // while `truncate` overwrites one that already exists.
  yield* caller.writeFile("/app.conf", new TextEncoder().encode("v1"), {
    access: "write",
    create: "exclusive",
    mode: 0o640
  })
 
  const refused = yield* caller
    .writeFile("/app.conf", bytes, {
      access: "write",
      create: "exclusive"
    })
    .pipe(
      Effect.as("replaced"),
      Effect.catchTag("VfsError", (error) => Effect.succeed(error.code))
    )
 
  yield* caller.writeFile("/app.conf", bytes, { access: "write", truncate: true })
 
  return [refused, new TextDecoder().decode(yield* caller.readFile("/app.conf"))]
})
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// [ 'AlreadyExists', 'v2' ]

Signature

declare const WriteFileOptions: Schema.Struct<{
  readonly access: Schema.Literals<readonly ["read", "write", "readWrite"]>
  readonly create: Schema.optionalKey<Schema.Literals<readonly ["never", "ifMissing", "exclusive"]>>
  readonly mode: Schema.optionalKey<Schema.Natural>
  readonly append: Schema.optionalKey<Schema.Boolean>
  readonly truncate: Schema.optionalKey<Schema.Boolean>
  readonly followFinalSymlink: Schema.optionalKey<Schema.Boolean>
  readonly replaceFinalSymlink: Schema.optionalKey<Schema.Boolean>
  readonly finalMode: Schema.optionalKey<Schema.Natural>
}>

Source

Since v0.1.0

WriteFileOptions (type alias)

Options of writeFile.

Signature

export type WriteFileOptions = {
  readonly access: "read" | "write" | "readWrite"
  readonly mode?: number
  readonly create?: "never" | "ifMissing" | "exclusive"
  readonly append?: boolean
  readonly truncate?: boolean
  readonly followFinalSymlink?: boolean
  readonly replaceFinalSymlink?: boolean
  readonly finalMode?: number
}

Source

Since v0.1.0

ordering

VolumeDurabilityOrder

Weakest-to-strongest ordering for volume durability guarantees.

Signature

declare const VolumeDurabilityOrder: Order.Order<
  "memory-only" | "survives-process-crash" | "survives-operating-system-crash" | "survives-power-loss"
>

Source

Since v0.1.0

predicates

isVolumeDurabilityAtLeast

Returns whether actual survives at least the failure boundary required by required.

Signature

declare const isVolumeDurabilityAtLeast: {
  (required: VolumeModule.VolumeDurability): (actual: VolumeModule.VolumeDurability) => boolean
  (actual: VolumeModule.VolumeDurability, required: VolumeModule.VolumeDurability): boolean
}

Source

Since v0.1.0

schemas

CallerWatchOptions

Caller watch publication and alias options.

Signature

declare const CallerWatchOptions: Schema.Struct<{
  readonly recursive: Schema.optionalKey<Schema.Boolean>
  readonly alias: Schema.optionalKey<Schema.Literals<readonly ["all", "resolved"]>>
}>

Source

Since v0.9.0

Change

A committed namespace or content change emitted by a volume watch stream.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { Effect, Fiber, Stream } from "effect"
 
const program = Effect.gen(function* () {
  const volume = yield* Vfs.make()
  const caller = yield* volume.caller()
 
  const watcher = yield* volume.watch().pipe(
    Effect.flatMap((stream) => Stream.runCollect(Stream.take(stream, 2))),
    Effect.forkChild({ startImmediately: true })
  )
 
  yield* caller.mkdir("/logs")
  yield* caller.rmdir("/logs")
 
  // Each event pairs the kind of change with the absolute path it happened to.
  const events = yield* Fiber.join(watcher)
 
  return yield* Effect.forEach(events, (change) =>
    Effect.map(Vfs.pathToBytes(change.path), (bytes) => `${change._tag} ${new TextDecoder().decode(bytes)}`)
  )
}).pipe(Effect.scoped)
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// [ 'Create /logs', 'Remove /logs' ]

Signature

declare const Change: Schema.TaggedUnion<{
  readonly Create: Schema.TaggedStruct<"Create", { readonly path: Schema.declare<BytePath, BytePath> }>
  readonly Update: Schema.TaggedStruct<"Update", { readonly path: Schema.declare<BytePath, BytePath> }>
  readonly Remove: Schema.TaggedStruct<"Remove", { readonly path: Schema.declare<BytePath, BytePath> }>
  readonly Rescan: Schema.TaggedStruct<"Rescan", { readonly path: Schema.declare<BytePath, BytePath> }>
}>

Source

Since v0.1.0

DirectoryChange

A directory revision transition captured inside one coordinated operation.

Signature

declare const DirectoryChange: Schema.Struct<{ readonly before: Schema.BigInt; readonly after: Schema.BigInt }>

Source

Since v0.4.0

Fixture

Schema for a complete fixture namespace with optional metadata and forward hard links.

Details

Fixture paths must be absolute, unique, and explicitly include their parent directories.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { Effect, Schema } from "effect"
 
// Fixtures must list every parent directory before its children.
const program = Schema.decodeUnknownEffect(Vfs.Fixture)({
  entries: [{ kind: "file", path: "/etc/hosts", bytes: new TextEncoder().encode("127.0.0.1") }]
}).pipe(
  Effect.flatMap(Vfs.fromFixture),
  Effect.as("built"),
  Effect.catchTag("VfsError", (error) => Effect.succeed(`rejected: ${error.code}`))
)
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// rejected: InvalidStructure

Signature

declare const Fixture: Schema.Struct<{
  readonly rootMetadata: Schema.optionalKey<
    Schema.Struct<{
      readonly uid: Schema.optionalKey<Schema.Natural>
      readonly gid: Schema.optionalKey<Schema.Natural>
      readonly mode: Schema.optionalKey<Schema.Natural>
      readonly atimeNs: Schema.optionalKey<Schema.BigInt>
      readonly mtimeNs: Schema.optionalKey<Schema.BigInt>
      readonly ctimeNs: Schema.optionalKey<Schema.BigInt>
      readonly birthtimeNs: Schema.optionalKey<Schema.BigInt>
    }>
  >
  readonly entries: Schema.$Array<
    Schema.toTaggedUnion<
      "kind",
      readonly [
        Schema.Struct<{
          readonly kind: Schema.Literal<"directory">
          readonly path: Schema.Union<readonly [Schema.String, Schema.declare<BytePath, BytePath>]>
          readonly metadata: Schema.optionalKey<
            Schema.Struct<{
              readonly uid: Schema.optionalKey<Schema.Natural>
              readonly gid: Schema.optionalKey<Schema.Natural>
              readonly mode: Schema.optionalKey<Schema.Natural>
              readonly atimeNs: Schema.optionalKey<Schema.BigInt>
              readonly mtimeNs: Schema.optionalKey<Schema.BigInt>
              readonly ctimeNs: Schema.optionalKey<Schema.BigInt>
              readonly birthtimeNs: Schema.optionalKey<Schema.BigInt>
            }>
          >
        }>,
        Schema.Struct<{
          readonly kind: Schema.Literal<"file">
          readonly path: Schema.Union<readonly [Schema.String, Schema.declare<BytePath, BytePath>]>
          readonly bytes: Schema.Uint8Array
          readonly metadata: Schema.optionalKey<
            Schema.Struct<{
              readonly uid: Schema.optionalKey<Schema.Natural>
              readonly gid: Schema.optionalKey<Schema.Natural>
              readonly mode: Schema.optionalKey<Schema.Natural>
              readonly atimeNs: Schema.optionalKey<Schema.BigInt>
              readonly mtimeNs: Schema.optionalKey<Schema.BigInt>
              readonly ctimeNs: Schema.optionalKey<Schema.BigInt>
              readonly birthtimeNs: Schema.optionalKey<Schema.BigInt>
            }>
          >
        }>,
        Schema.Struct<{
          readonly kind: Schema.Literal<"symlink">
          readonly path: Schema.Union<readonly [Schema.String, Schema.declare<BytePath, BytePath>]>
          readonly target: Schema.Union<readonly [Schema.String, Schema.declare<BytePath, BytePath>]>
          readonly metadata: Schema.optionalKey<
            Schema.Struct<{
              readonly uid: Schema.optionalKey<Schema.Natural>
              readonly gid: Schema.optionalKey<Schema.Natural>
              readonly mode: Schema.optionalKey<Schema.Natural>
              readonly atimeNs: Schema.optionalKey<Schema.BigInt>
              readonly mtimeNs: Schema.optionalKey<Schema.BigInt>
              readonly ctimeNs: Schema.optionalKey<Schema.BigInt>
              readonly birthtimeNs: Schema.optionalKey<Schema.BigInt>
            }>
          >
        }>,
        Schema.Struct<{
          readonly kind: Schema.Literal<"hardLink">
          readonly path: Schema.Union<readonly [Schema.String, Schema.declare<BytePath, BytePath>]>
          readonly target: Schema.Union<readonly [Schema.String, Schema.declare<BytePath, BytePath>]>
        }>
      ]
    >
  >
}>

Source

Since v0.1.0

FsCode

Schema for portable virtual filesystem error codes.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { Effect, Schema } from "effect"
 
const asCode = Schema.decodeUnknownEffect(Vfs.FsCode)
 
// Useful when a code has crossed a process boundary and arrives back as data.
const program = Effect.gen(function* () {
  const payload = JSON.parse(`{"code":"NotFound","other":"Nonsense"}`)
 
  const known = yield* asCode(payload.code)
 
  const unknown = yield* asCode(payload.other).pipe(Effect.orElseSucceed(() => "not a code"))
 
  return [known, unknown]
})
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// [ 'NotFound', 'not a code' ]

Signature

declare const FsCode: Schema.Literals<
  readonly [
    "NotFound",
    "AlreadyExists",
    "NotEmpty",
    "NotDirectory",
    "AccessDenied",
    "NotPermitted",
    "InvalidHandle",
    "ForeignHandle",
    "InvalidReference",
    "ForeignReference",
    "StaleReference",
    "ClosedCaller",
    "InvalidArgument",
    "InvalidPathEncoding",
    "PathTooLong",
    "NoSpace",
    "IsDirectory",
    "FileTooLarge",
    "NoData",
    "SymlinkLoop",
    "UnrepresentableName",
    "StorageRejected",
    "OutcomeUnknown",
    "VolumeUnavailable",
    "VolumeBusy"
  ]
>

Source

Since v0.1.0

Identity

Schema for a caller's numeric identity, supplementary groups, and explicit privilege.

Signature

declare const Identity: Schema.Struct<{
  readonly uid: Schema.Natural
  readonly gid: Schema.Natural
  readonly groups: Schema.$Array<Schema.Natural>
  readonly privileged: Schema.Boolean
}>

Source

Since v0.1.0

Metadata

Schema for filesystem node metadata with bigint inode, size, and nanosecond fields.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { Effect } from "effect"
 
// Sizes and inode numbers are bigint; times are nanosecond timestamps.
const program = Effect.gen(function* () {
  const caller = yield* (yield* Vfs.make()).caller()
 
  yield* caller.writeFile("/f", new Uint8Array([1, 2, 3]), {
    access: "write",
    create: "exclusive"
  })
 
  const metadata = yield* caller.stat("/f")
 
  return [metadata.kind, metadata.size, metadata.nlink]
})
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// [ 'file', 3n, 1 ]

Signature

declare const Metadata: Schema.Struct<{
  readonly kind: Schema.Literals<readonly ["directory", "file", "symlink"]>
  readonly ino: Schema.BigInt
  readonly nlink: Schema.Natural
  readonly size: Schema.BigInt
  readonly uid: Schema.Natural
  readonly gid: Schema.Natural
  readonly mode: Schema.Natural
  readonly atimeNs: Schema.BigInt
  readonly mtimeNs: Schema.BigInt
  readonly ctimeNs: Schema.BigInt
  readonly birthtimeNs: Schema.BigInt
  readonly revision: Schema.BigInt
}>

Source

Since v0.1.0

MkdirOptions

Settings for creating a directory through a parent object reference. exactMode skips the caller's umask for an explicit creation mode; permission policy still applies. recursive creates every missing directory on a path in one change, as mkdir -p does.

Signature

declare const MkdirOptions: Schema.Struct<{
  readonly mode: Schema.optionalKey<Schema.Natural>
  readonly exactMode: Schema.optionalKey<Schema.Boolean>
  readonly times: Schema.optionalKey<
    Schema.Struct<{
      readonly access: Schema.Union<
        readonly [
          Schema.Struct<{ readonly kind: Schema.Literal<"now"> }>,
          Schema.Struct<{ readonly kind: Schema.Literal<"omit"> }>,
          Schema.Struct<{ readonly kind: Schema.Literal<"value">; readonly nanoseconds: Schema.BigInt }>
        ]
      >
      readonly modification: Schema.Union<
        readonly [
          Schema.Struct<{ readonly kind: Schema.Literal<"now"> }>,
          Schema.Struct<{ readonly kind: Schema.Literal<"omit"> }>,
          Schema.Struct<{ readonly kind: Schema.Literal<"value">; readonly nanoseconds: Schema.BigInt }>
        ]
      >
    }>
  >
  readonly recursive: Schema.optionalKey<Schema.Boolean>
}>

Source

Since v0.4.0

OpenEntryOptions

Settings for atomically looking up or creating and opening one referenced child. exactMode skips the caller's umask for an explicit creation mode; permission policy still applies.

Signature

declare const OpenEntryOptions: Schema.Struct<{
  readonly access: Schema.Literals<readonly ["read", "write", "readWrite"]>
  readonly create: Schema.optionalKey<Schema.Literals<readonly ["never", "ifMissing", "exclusive"]>>
  readonly mode: Schema.optionalKey<Schema.Natural>
  readonly append: Schema.optionalKey<Schema.Boolean>
  readonly truncate: Schema.optionalKey<Schema.Boolean>
  readonly followFinalSymlink: Schema.optionalKey<Schema.Boolean>
  readonly times: Schema.optionalKey<
    Schema.Struct<{
      readonly access: Schema.Union<
        readonly [
          Schema.Struct<{ readonly kind: Schema.Literal<"now"> }>,
          Schema.Struct<{ readonly kind: Schema.Literal<"omit"> }>,
          Schema.Struct<{ readonly kind: Schema.Literal<"value">; readonly nanoseconds: Schema.BigInt }>
        ]
      >
      readonly modification: Schema.Union<
        readonly [
          Schema.Struct<{ readonly kind: Schema.Literal<"now"> }>,
          Schema.Struct<{ readonly kind: Schema.Literal<"omit"> }>,
          Schema.Struct<{ readonly kind: Schema.Literal<"value">; readonly nanoseconds: Schema.BigInt }>
        ]
      >
    }>
  >
  readonly initialSize: Schema.optionalKey<Schema.BigInt>
  readonly exactMode: Schema.optionalKey<Schema.Boolean>
  readonly owner: Schema.optionalKey<
    Schema.Struct<{
      readonly uid: Schema.optionalKey<Schema.Natural>
      readonly gid: Schema.optionalKey<Schema.Natural>
    }>
  >
  readonly expected: Schema.optionalKey<Schema.NullOr<Schema.declare<ObjectReference, ObjectReference>>>
  readonly expectedChild: Schema.optionalKey<
    Schema.NullOr<
      Schema.Struct<{
        readonly reference: Schema.declare<ObjectReference, ObjectReference>
        readonly revision: Schema.BigInt
        readonly atimeNs: Schema.BigInt
        readonly mtimeNs: Schema.BigInt
      }>
    >
  >
}>

Source

Since v0.4.0

OpenOptions

Schema for file access, creation, append, truncate, and symlink behavior.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { Effect } from "effect"
 
// `access` is required; `create` decides whether a missing file is an error.
const program = Effect.gen(function* () {
  const caller = yield* (yield* Vfs.make()).caller()
 
  const handle = yield* caller.open("/log.txt", {
    access: "readWrite",
    create: "ifMissing",
    append: true
  })
 
  yield* handle.write(new Uint8Array([1, 2]))
 
  return (yield* handle.stat).size
}).pipe(Effect.scoped)
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// 2n

Signature

declare const OpenOptions: Schema.Struct<{
  readonly access: Schema.Literals<readonly ["read", "write", "readWrite"]>
  readonly create: Schema.optionalKey<Schema.Literals<readonly ["never", "ifMissing", "exclusive"]>>
  readonly mode: Schema.optionalKey<Schema.Natural>
  readonly append: Schema.optionalKey<Schema.Boolean>
  readonly truncate: Schema.optionalKey<Schema.Boolean>
}>

Source

Since v0.1.0

OverlayChange

Schema for a final-state overlay difference.

Details

Paths retain arbitrary non-NUL bytes. Renames are reported only when retained base identity makes the removed and added names unambiguous.

Signature

declare const OverlayChange: Schema.Union<
  readonly [
    Schema.TaggedStruct<
      "Added",
      {
        readonly path: Schema.declare<BytePath, BytePath>
        readonly kind: Schema.Literals<readonly ["directory", "file", "symlink"]>
      }
    >,
    Schema.TaggedStruct<
      "Removed",
      {
        readonly path: Schema.declare<BytePath, BytePath>
        readonly kind: Schema.Literals<readonly ["directory", "file", "symlink"]>
      }
    >,
    Schema.TaggedStruct<
      "Replaced",
      {
        readonly path: Schema.declare<BytePath, BytePath>
        readonly beforeKind: Schema.Literals<readonly ["directory", "file", "symlink"]>
        readonly afterKind: Schema.Literals<readonly ["directory", "file", "symlink"]>
        readonly differences: Schema.$Array<
          Schema.Literals<readonly ["content", "mode", "uid", "gid", "atimeNs", "mtimeNs", "ctimeNs", "birthtimeNs"]>
        >
      }
    >,
    Schema.TaggedStruct<
      "Renamed",
      {
        readonly from: Schema.declare<BytePath, BytePath>
        readonly to: Schema.declare<BytePath, BytePath>
        readonly kind: Schema.Literals<readonly ["directory", "file", "symlink"]>
        readonly differences: Schema.$Array<
          Schema.Literals<readonly ["content", "mode", "uid", "gid", "atimeNs", "mtimeNs", "ctimeNs", "birthtimeNs"]>
        >
      }
    >,
    Schema.TaggedStruct<
      "Updated",
      {
        readonly path: Schema.declare<BytePath, BytePath>
        readonly kind: Schema.Literals<readonly ["directory", "file", "symlink"]>
        readonly differences: Schema.$Array<
          Schema.Literals<readonly ["content", "mode", "uid", "gid", "atimeNs", "mtimeNs", "ctimeNs", "birthtimeNs"]>
        >
      }
    >
  ]
>

Source

Since v0.1.0

OverlayChangesOptions

Schema for overlay summary filtering.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { Effect } from "effect"
 
const program = Effect.gen(function* () {
  const base = yield* Vfs.make()
  const workspace = yield* Vfs.makeOverlay(yield* base.snapshot)
 
  yield* (yield* workspace.caller()).mkdir("/out")
 
  // Timestamps are excluded by default, since they change on every write.
  const plain = yield* workspace.changes()
  const timed = yield* workspace.changes({ includeTimestamps: true })
 
  return [plain.length, timed.length]
})
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// [ 1, 2 ]

Signature

declare const OverlayChangesOptions: Schema.Struct<{ readonly includeTimestamps: Schema.optionalKey<Schema.Boolean> }>

Source

Since v0.1.0

OverlayDifference

Schema for observable fields that can differ from an overlay's immutable base.

Signature

declare const OverlayDifference: Schema.Literals<
  readonly ["content", "mode", "uid", "gid", "atimeNs", "mtimeNs", "ctimeNs", "birthtimeNs"]
>

Source

Since v0.1.0

OverlayNodeKind

Schema for the filesystem entry kinds reported by overlay summaries.

Signature

declare const OverlayNodeKind: Schema.Literals<readonly ["directory", "file", "symlink"]>

Source

Since v0.1.0

OwnerUpdate

Schema for an owner update. Omitted fields retain their existing values.

Signature

declare const OwnerUpdate: Schema.Struct<{
  readonly uid: Schema.optionalKey<Schema.Natural>
  readonly gid: Schema.optionalKey<Schema.Natural>
}>

Source

Since v0.1.0

ReferenceKey

Schema for the serialisable name of one object in one volume: the volume's identity, the epoch of its inode numbers, the object's inode number, and a tag only the volume can compute, so a key cannot be guessed from another.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { Effect, Schema } from "effect"
 
const Json = Schema.fromJsonString(Vfs.ReferenceKey)
 
const program = Effect.gen(function* () {
  const volume = yield* Vfs.make()
  const caller = yield* volume.caller()
  yield* caller.writeFile("/f", new Uint8Array([1]), { access: "write", create: "exclusive" })
 
  // The key survives a wire; resolving it returns the same reference.
  const reference = yield* caller.lookup("/f")
  const text = yield* Schema.encodeEffect(Json)(yield* volume.referenceKey(reference))
  const key = yield* Schema.decodeEffect(Json)(text)
 
  return (yield* volume.resolveReferenceKey(key)) === reference
})
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// true

Signature

declare const ReferenceKey: Schema.Struct<{
  readonly identity: Schema.Uint8ArrayFromBase64
  readonly epoch: Schema.Uint8ArrayFromBase64
  readonly ino: Schema.BigIntFromString
  readonly tag: Schema.Uint8ArrayFromBase64
}>

Source

Since v0.6.0

RemoveOptions

Settings for removing an entry: whether a directory goes with everything under it, and whether a missing target is success.

Signature

declare const RemoveOptions: Schema.Struct<{
  readonly recursive: Schema.optionalKey<Schema.Boolean>
  readonly force: Schema.optionalKey<Schema.Boolean>
  readonly expected: Schema.optionalKey<Schema.suspend<Schema.declare<ObjectReference, ObjectReference>>>
}>

Source

Since v0.6.0

RenameReferenceResult

The directory transitions produced by a same-directory or cross-directory rename.

Signature

declare const RenameReferenceResult: Schema.TaggedUnion<{
  readonly DifferentDirectories: Schema.TaggedStruct<
    "DifferentDirectories",
    {
      readonly sourceDirectory: Schema.Struct<{ readonly before: Schema.BigInt; readonly after: Schema.BigInt }>
      readonly destinationDirectory: Schema.Struct<{ readonly before: Schema.BigInt; readonly after: Schema.BigInt }>
    }
  >
  readonly SameDirectory: Schema.TaggedStruct<
    "SameDirectory",
    { readonly directory: Schema.Struct<{ readonly before: Schema.BigInt; readonly after: Schema.BigInt }> }
  >
}>

Source

Since v0.4.0

RootCallerOptions

Schema for root caller credentials and creation mask.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { Effect } from "effect"
 
const program = Effect.gen(function* () {
  const volume = yield* Vfs.make()
 
  // Omitting `identity` yields a privileged root caller.
  const root = yield* volume.caller({ umask: 0 })
  const user = yield* volume.caller({
    identity: { uid: 1000, gid: 1000, groups: [], privileged: false },
    umask: 0o022
  })
 
  yield* root.mkdir("/home", { mode: 0o777 })
 
  // The umask clears its bits from the requested mode.
  yield* user.mkdir("/home/user", { mode: 0o777 })
 
  return ((yield* user.stat("/home/user")).mode & 0o777).toString(8)
})
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// 755

Signature

declare const RootCallerOptions: Schema.Struct<{
  readonly identity: Schema.optionalKey<
    Schema.Struct<{
      readonly uid: Schema.Natural
      readonly gid: Schema.Natural
      readonly groups: Schema.$Array<Schema.Natural>
      readonly privileged: Schema.Boolean
    }>
  >
  readonly umask: Schema.optionalKey<Schema.Natural>
}>

Source

Since v0.1.0

SeekMode

Schema for file seek origins, including dense-file data and hole queries.

Signature

declare const SeekMode: Schema.Literals<readonly ["start", "current", "end", "data", "hole"]>

Source

Since v0.1.0

SetattrOptions

The attributes setattr changes together: a regular file's size, the permission mode, the owner, and the access and modification times. An omitted attribute keeps its current value. expected pins the revision the caller last observed and fails as StaleReference naming expected when the target has moved on.

Signature

declare const SetattrOptions: Schema.Struct<{
  readonly size: Schema.optionalKey<Schema.BigInt>
  readonly mode: Schema.optionalKey<Schema.Natural>
  readonly owner: Schema.optionalKey<
    Schema.Struct<{
      readonly uid: Schema.optionalKey<Schema.Natural>
      readonly gid: Schema.optionalKey<Schema.Natural>
    }>
  >
  readonly times: Schema.optionalKey<
    Schema.Struct<{
      readonly access: Schema.Union<
        readonly [
          Schema.Struct<{ readonly kind: Schema.Literal<"now"> }>,
          Schema.Struct<{ readonly kind: Schema.Literal<"omit"> }>,
          Schema.Struct<{ readonly kind: Schema.Literal<"value">; readonly nanoseconds: Schema.BigInt }>
        ]
      >
      readonly modification: Schema.Union<
        readonly [
          Schema.Struct<{ readonly kind: Schema.Literal<"now"> }>,
          Schema.Struct<{ readonly kind: Schema.Literal<"omit"> }>,
          Schema.Struct<{ readonly kind: Schema.Literal<"value">; readonly nanoseconds: Schema.BigInt }>
        ]
      >
    }>
  >
  readonly expected: Schema.optionalKey<Schema.Struct<{ readonly revision: Schema.BigInt }>>
}>

Source

Since v0.6.0

SnapshotDeltaFromBytes

Creates an Effect Schema codec between owned bytes and opaque snapshot deltas. Omission uses SnapshotDeltaLimits.default.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
import { Effect, Schema } from "effect"
 
// Omitting limits uses `SnapshotDeltaLimits.default`.
const codec = Vfs.SnapshotDeltaFromBytes()
 
const program = Effect.gen(function* () {
  const volume = yield* Vfs.make()
  const base = yield* volume.snapshot
 
  yield* (yield* volume.caller()).mkdir("/out")
 
  const delta = yield* Vfs.diffSnapshots(base, yield* volume.snapshot)
  const bytes = yield* Schema.encodeEffect(codec)(delta)
 
  // The delta survives the trip through bytes and still applies to its base.
  const decoded = yield* Schema.decodeEffect(codec)(bytes)
 
  return (yield* Vfs.inspectSnapshotDelta(base, decoded)).map((change) => change._tag)
}).pipe(Effect.provide(NodeCrypto.layer))
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// [ 'Added' ]

Signature

declare const SnapshotDeltaFromBytes: (
  limits?: SnapshotDeltaModel.SnapshotDeltaLimits
) => Schema.decodeTo<
  Schema.declare<SnapshotDeltaModel.SnapshotDelta, SnapshotDeltaModel.SnapshotDelta>,
  Schema.Uint8Array,
  never,
  never
>

Source

Since v0.1.0

SymlinkOptions

Settings for creating a symbolic link through a parent object reference.

Signature

declare const SymlinkOptions: Schema.Struct<{
  readonly times: Schema.optionalKey<
    Schema.Struct<{
      readonly access: Schema.Union<
        readonly [
          Schema.Struct<{ readonly kind: Schema.Literal<"now"> }>,
          Schema.Struct<{ readonly kind: Schema.Literal<"omit"> }>,
          Schema.Struct<{ readonly kind: Schema.Literal<"value">; readonly nanoseconds: Schema.BigInt }>
        ]
      >
      readonly modification: Schema.Union<
        readonly [
          Schema.Struct<{ readonly kind: Schema.Literal<"now"> }>,
          Schema.Struct<{ readonly kind: Schema.Literal<"omit"> }>,
          Schema.Struct<{ readonly kind: Schema.Literal<"value">; readonly nanoseconds: Schema.BigInt }>
        ]
      >
    }>
  >
}>

Source

Since v0.4.0

TimeUpdate

Schema for setting a timestamp to the clock, retaining it, or supplying nanoseconds.

Signature

declare const TimeUpdate: Schema.Union<
  readonly [
    Schema.Struct<{ readonly kind: Schema.Literal<"now"> }>,
    Schema.Struct<{ readonly kind: Schema.Literal<"omit"> }>,
    Schema.Struct<{ readonly kind: Schema.Literal<"value">; readonly nanoseconds: Schema.BigInt }>
  ]
>

Source

Since v0.1.0

Times

Schema for independent access and modification time updates.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { Effect } from "effect"
 
const program = Effect.gen(function* () {
  const caller = yield* (yield* Vfs.make()).caller()
 
  yield* caller.writeFile("/f", new Uint8Array([1]), {
    access: "write",
    create: "exclusive"
  })
 
  // Each field is set to a value, to the current clock, or left alone.
  yield* caller.utimes("/f", {
    access: { kind: "value", nanoseconds: 1_000n },
    modification: { kind: "omit" }
  })
 
  const metadata = yield* caller.stat("/f")
 
  return [metadata.atimeNs, metadata.mtimeNs === 1_000n]
})
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// [ 1000n, false ]

Signature

declare const Times: Schema.Struct<{
  readonly access: Schema.Union<
    readonly [
      Schema.Struct<{ readonly kind: Schema.Literal<"now"> }>,
      Schema.Struct<{ readonly kind: Schema.Literal<"omit"> }>,
      Schema.Struct<{ readonly kind: Schema.Literal<"value">; readonly nanoseconds: Schema.BigInt }>
    ]
  >
  readonly modification: Schema.Union<
    readonly [
      Schema.Struct<{ readonly kind: Schema.Literal<"now"> }>,
      Schema.Struct<{ readonly kind: Schema.Literal<"omit"> }>,
      Schema.Struct<{ readonly kind: Schema.Literal<"value">; readonly nanoseconds: Schema.BigInt }>
    ]
  >
}>

Source

Since v0.1.0

VolumeDurability

Schema for the failure boundary survived by an acknowledged volume write.

Signature

declare const VolumeDurability: Schema.Literals<
  readonly ["memory-only", "survives-process-crash", "survives-operating-system-crash", "survives-power-loss"]
>

Source

Since v0.1.0

VolumeIdentity

Schema for the stable logical identity of a volume.

Signature

declare const VolumeIdentity: Schema.brand<Schema.String, "@effect-vfs/core/VolumeIdentity">

Source

Since v0.1.0

VolumeIncarnation

Schema for one uninterrupted storage lifetime of a volume.

Signature

declare const VolumeIncarnation: Schema.brand<Schema.String, "@effect-vfs/core/VolumeIncarnation">

Source

Since v0.1.0

VolumeOptions

Schema for optional volume capacity and path limits.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { ByteSize, Effect } from "effect"
 
// Every limit is optional, and each one is enforced once set.
const program = Effect.gen(function* () {
  const volume = yield* Vfs.make({ maxFileBytes: ByteSize.bytes(4) })
  const caller = yield* volume.caller()
 
  return yield* caller
    .writeFile("/big.bin", new Uint8Array(16), {
      access: "write",
      create: "exclusive"
    })
    .pipe(
      Effect.as("written"),
      Effect.catchTag("VfsError", (error) => Effect.succeed(error.code))
    )
})
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// FileTooLarge

Signature

declare const VolumeOptions: Schema.Struct<{
  readonly execution: Schema.optionalKey<Schema.Literals<readonly ["cooperative", "sync"]>>
  readonly identity: Schema.optionalKey<Schema.brand<Schema.String, "@effect-vfs/core/VolumeIdentity">>
  readonly maxEntries: Schema.optionalKey<Schema.Natural>
  readonly maxPendingOperations: Schema.optionalKey<Schema.Int>
  readonly maxWatchEvents: Schema.optionalKey<Schema.Int>
  readonly maxBytes: Schema.optionalKey<Schema.ByteSize>
  readonly maxFileBytes: Schema.optionalKey<Schema.ByteSize>
  readonly maxPathBytes: Schema.optionalKey<Schema.ByteSize>
}>

Source

Since v0.1.0

WalkOptions

Settings for walking a directory tree: the order and the optional depth, entry, and byte bounds.

Signature

declare const WalkOptions: Schema.Struct<{
  readonly order: Schema.optionalKey<Schema.Literals<readonly ["pre", "post"]>>
  readonly maxDepth: Schema.optionalKey<Schema.Natural>
  readonly maxEntries: Schema.optionalKey<Schema.Natural>
  readonly maxBytes: Schema.optionalKey<Schema.ByteSize>
}>

Source

Since v0.6.0

WatchOptions

Schema for the options of Volume.watch: an object reference to narrow the watch to, and whether its whole subtree is reported.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { Effect, Fiber, Stream } from "effect"
 
const program = Effect.gen(function* () {
  const volume = yield* Vfs.make()
  const caller = yield* volume.caller()
  yield* caller.mkdir("/work")
  const work = yield* caller.lookup("/work")
 
  const watcher = yield* volume.watch({ scope: work }).pipe(
    Effect.flatMap((stream) => Stream.runCollect(Stream.take(stream, 3))),
    Effect.forkChild({ startImmediately: true })
  )
 
  // Outside the scope, so not reported.
  yield* caller.mkdir("/elsewhere")
  // The scope is the directory, not its path: the rename is reported and the watch goes on.
  yield* caller.rename("/work", "/renamed")
  yield* caller.mkdir("/renamed/out")
 
  const events = yield* Fiber.join(watcher)
 
  return yield* Effect.forEach(events, (change) =>
    Effect.map(Vfs.pathToBytes(change.path), (bytes) => `${change._tag} ${new TextDecoder().decode(bytes)}`)
  )
}).pipe(Effect.scoped)
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// [ 'Remove /work', 'Create /renamed', 'Create /renamed/out' ]

Signature

declare const WatchOptions: Schema.Struct<{
  readonly scope: Schema.optionalKey<Schema.declare<ObjectReference, ObjectReference>>
  readonly recursive: Schema.optionalKey<Schema.Boolean>
}>

Source

Since v0.6.0

serialization

decodeSnapshot

Decodes version 1 snapshot bytes while enforcing explicit input and payload limits.

Details

The same decoder as decodeSnapshotSink, run over one chunk.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { ByteSize, Effect } from "effect"
 
// Limits are mandatory: decoding is the boundary where outside input arrives.
const program = Effect.gen(function* () {
  const volume = yield* Vfs.make()
  const bytes = yield* Vfs.encodeSnapshot(yield* volume.snapshot)
 
  return yield* Vfs.decodeSnapshot(bytes, {
    maxEncodedBytes: ByteSize.bytes(1),
    maxRecords: 10_000,
    maxEntries: 10_000,
    maxDecodedBytes: ByteSize.megabytes(16)
  }).pipe(
    Effect.as("accepted"),
    Effect.catchTag("VfsError", (error) => Effect.succeed([error.code, error.field]))
  )
})
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// [ 'LimitExceeded', 'encodedBytes' ]

See

  • DecodeLimits for what each bound protects.

Signature

declare const decodeSnapshot: {
  (limits: DecodeLimits): (input: Uint8Array) => Effect.Effect<Snapshot, ImageFailure | ArgumentFailure>
  (input: Uint8Array, limits: DecodeLimits): Effect.Effect<Snapshot, ImageFailure | ArgumentFailure>
}

Source

Since v0.1.0

decodeSnapshotSink

A sink that decodes a stream of version 1 snapshot chunks while enforcing explicit input and payload limits.

Details

Chunks may split lines, and UTF-8 sequences, anywhere. Each line is checked as it ends, so input that breaks a limit or a rule is refused before the sink holds more than one line of it: maxLineBytes bounds that line, and the other limits bound what the accepted lines add up to. A line is refused at the byte where it first crosses maxLineBytes or maxEncodedBytes, so the field a refusal names does not depend on how the input is chunked. Every line must end in a newline, the last included; a carriage return, an empty line, a byte-order mark and malformed UTF-8 are refused as InvalidEncoding.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { ByteSize, Effect, Stream } from "effect"
 
const limits: Vfs.DecodeLimits = {
  maxEncodedBytes: ByteSize.megabytes(4),
  maxRecords: 10_000,
  maxEntries: 10_000,
  maxDecodedBytes: ByteSize.megabytes(16),
  maxLineBytes: ByteSize.megabytes(1)
}
 
const program = Effect.gen(function* () {
  const volume = yield* Vfs.fromFixture({
    entries: [{ kind: "file", path: "/f", bytes: new Uint8Array([1, 2, 3]) }]
  })
 
  // Rechunked to single bytes: the sink joins lines across chunks.
  const snapshot = yield* Vfs.encodeSnapshotStream(yield* volume.snapshot).pipe(
    Stream.flattenIterable,
    Stream.rechunk(1),
    Stream.map((byte) => new Uint8Array([byte])),
    Stream.run(Vfs.decodeSnapshotSink(limits))
  )
 
  return yield* (yield* (yield* Vfs.fromSnapshot(snapshot)).caller()).readFile("/f")
})
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// Uint8Array(3) [ 1, 2, 3 ]

See

  • DecodeLimits for what each bound protects.
  • decodeSnapshot for decoding bytes already in memory.

Signature

declare const decodeSnapshotSink: (
  limits: DecodeLimits
) => Sink.Sink<Snapshot, Uint8Array, never, ImageFailure | ArgumentFailure>

Source

Since v0.6.0

encodeSnapshot

Encodes a snapshot as owned UTF-8 bytes in the version 1 snapshot format.

Details

The bytes are the chunks of encodeSnapshotStream collected into one array: newline-delimited JSON, a header line and then one line per node. Given limits, encoding fails with LimitExceeded wherever decodeSnapshot would refuse the bytes under the same limits.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { Effect } from "effect"
 
// Bytes are portable: store or transmit them, then decode under explicit limits.
const program = Effect.gen(function* () {
  const volume = yield* Vfs.make()
  const caller = yield* volume.caller()
 
  yield* caller.writeFile("/f", new Uint8Array([1, 2, 3]), {
    access: "write",
    create: "exclusive"
  })
 
  const bytes = yield* Vfs.encodeSnapshot(yield* volume.snapshot)
 
  // The first line is the header carrying the version 1 snapshot format.
  return JSON.parse(new TextDecoder().decode(bytes).split("\n")[0]!).version
})
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// 1

See

  • decodeSnapshot for reading the bytes back under limits.

Signature

declare const encodeSnapshot: {
  (limits?: DecodeLimits): (snapshot: Snapshot) => Effect.Effect<Uint8Array, ImageFailure | ArgumentFailure>
  (snapshot: Snapshot, limits?: DecodeLimits): Effect.Effect<Uint8Array, ImageFailure | ArgumentFailure>
}

Source

Since v0.1.0

encodeSnapshotStream

Streams a snapshot's version 1 encoding as owned UTF-8 chunks: a header line, then one line per node, each ending in a newline.

Details

The encoding is newline-delimited JSON. The first line names the format and version; each later line is one node in inode order, with names and payloads as canonical base64. Nodes are encoded as the stream is pulled, so writing a large snapshot to a file or a socket never holds its whole encoding. A chunk holds at most 128 lines and 64 KiB, so a pull holds at most that and the next line; a line longer than 64 KiB is a chunk of its own.

Given limits, the stream fails with LimitExceeded at the first line that decodeSnapshot would refuse under the same limits, naming the same field, so bytes it completes are known to decode without decoding them.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { Effect, Stream } from "effect"
 
const program = Effect.gen(function* () {
  const volume = yield* Vfs.fromFixture({
    entries: [{ kind: "file", path: "/f", bytes: new Uint8Array([1]) }]
  })
 
  const text = yield* Vfs.encodeSnapshotStream(yield* volume.snapshot).pipe(Stream.decodeText, Stream.mkString)
 
  // A header line, then the root and the file, each line ending in a newline.
  return text.split("\n").map((line) => (line === "" ? "end" : (JSON.parse(line)._tag ?? "header")))
})
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// [ 'header', 'directory', 'file', 'end' ]

See

  • encodeSnapshot for the same bytes collected into one array.
  • decodeSnapshotSink for reading a stream back under limits.

Signature

declare const encodeSnapshotStream: {
  (limits?: DecodeLimits): (snapshot: Snapshot) => Stream.Stream<Uint8Array, ImageFailure | ArgumentFailure>
  (snapshot: Snapshot, limits?: DecodeLimits): Stream.Stream<Uint8Array, ImageFailure | ArgumentFailure>
}

Source

Since v0.6.0

services

Caller

The caller an application operates through, as an Effect service. A caller supplied through the service keeps its own volume, credentials, umask and working directory; layer makes a root caller on the Volume in context.

Signature

declare const Caller: Context.Service<Caller, Caller> & {
  readonly layer: (options?: RootCallerOptions) => Layer.Layer<Caller, VfsError, Volume>
}

Source

Since v0.6.0

Volume

The volume an application runs against, as an Effect service. The static layers wrap the explicit constructors; each call builds a fresh layer, so share one layer value to share one volume.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { Effect } from "effect"
 
const program = Effect.gen(function* () {
  const fs = yield* Vfs.Caller
  yield* fs.mkdir("/work")
 
  return (yield* fs.readDirectory("/")).value.map((entry) => new TextDecoder().decode(entry.name))
})
 
Effect.runPromise(
  program.pipe(Effect.provide(Vfs.Caller.layer()), Effect.provide(Vfs.Volume.layer()), Effect.provide(BunCrypto.layer))
).then(console.log)
// [ 'work' ]

Signature

declare const Volume: Context.Service<Volume, Volume> & {
  readonly layer: (options?: VolumeOptions) => Layer.Layer<Volume, VfsError, Crypto.Crypto>
  readonly layerFromSnapshot: {
    (options?: VolumeOptions): (snapshot: Snapshot) => Layer.Layer<Volume, VfsError, Crypto.Crypto>
    (snapshot: Snapshot, options?: VolumeOptions): Layer.Layer<Volume, VfsError, Crypto.Crypto>
  }
  readonly layerFromFixture: {
    (options?: VolumeOptions): (fixture: Fixture) => Layer.Layer<Volume, VfsError, Crypto.Crypto>
    (fixture: Fixture, options?: VolumeOptions): Layer.Layer<Volume, VfsError, Crypto.Crypto>
  }
  readonly layerOverlay: {
    (options?: VolumeOptions): (base: Snapshot) => Layer.Layer<Volume, VfsError, Crypto.Crypto>
    (base: Snapshot, options?: VolumeOptions): Layer.Layer<Volume, VfsError, Crypto.Crypto>
  }
  readonly layerLive: (options: LiveVolumeOptions) => Layer.Layer<Volume, VfsError, LiveImageStore | Crypto.Crypto>
}

Source

Since v0.6.0

snapshots

applySnapshotDelta

Applies an exact delta to its semantically matching base and returns a new snapshot. Requires the platform-neutral Crypto.Crypto service for base identity. Provide NodeCrypto.layer, BunCrypto.layer, or your own via Crypto.make.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
import { Effect } from "effect"
 
const program = Effect.gen(function* () {
  const volume = yield* Vfs.make()
  const caller = yield* volume.caller()
  const base = yield* volume.snapshot
 
  yield* caller.writeFile("/f", new Uint8Array([7]), {
    access: "write",
    create: "exclusive"
  })
 
  const delta = yield* Vfs.diffSnapshots(base, yield* volume.snapshot)
 
  // Applying the delta to the same base reproduces the target snapshot.
  const applied = yield* Vfs.applySnapshotDelta(base, delta)
  const restored = yield* Vfs.fromSnapshot(applied)
  const reader = yield* restored.caller()
 
  return yield* reader.readFile("/f")
}).pipe(Effect.provide(NodeCrypto.layer))
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// Uint8Array(1) [ 7 ]

See

  • SnapshotDeltaFromBytes for moving a delta across a boundary.

Signature

declare const applySnapshotDelta: {
  (
    delta: SnapshotDeltaModel.SnapshotDelta,
    limits?: SnapshotDeltaModel.SnapshotDeltaLimits
  ): (base: Snapshot) => Effect.Effect<Snapshot, VfsError | PlatformError.PlatformError, Crypto.Crypto>
  (
    base: Snapshot,
    delta: SnapshotDeltaModel.SnapshotDelta,
    limits?: SnapshotDeltaModel.SnapshotDeltaLimits
  ): Effect.Effect<Snapshot, VfsError | PlatformError.PlatformError, Crypto.Crypto>
}

Source

Since v0.1.0

diffSnapshots

Computes an exact portable delta between two immutable snapshots. Requires the platform-neutral Crypto.Crypto service for base identity. Provide NodeCrypto.layer, BunCrypto.layer, or your own via Crypto.make.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
import { Effect } from "effect"
 
// Two snapshots in, one opaque delta out. `inspectSnapshotDelta` reads it.
const program = Effect.gen(function* () {
  const volume = yield* Vfs.make()
  const caller = yield* volume.caller()
  const base = yield* volume.snapshot
 
  yield* caller.writeFile("/added.txt", new Uint8Array([1]), {
    access: "write",
    create: "exclusive"
  })
 
  const delta = yield* Vfs.diffSnapshots(base, yield* volume.snapshot)
 
  const changes = yield* Vfs.inspectSnapshotDelta(base, delta)
 
  return new TextDecoder().decode(yield* Vfs.pathToBytes(changes[0]!.path))
}).pipe(Effect.provide(NodeCrypto.layer))
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// /added.txt

See

  • inspectSnapshotDelta to read a delta, applySnapshotDelta to apply one.

Signature

declare const diffSnapshots: {
  (
    target: Snapshot,
    limits?: SnapshotDeltaModel.SnapshotDeltaLimits
  ): (
    base: Snapshot
  ) => Effect.Effect<SnapshotDeltaModel.SnapshotDelta, VfsError | PlatformError.PlatformError, Crypto.Crypto>
  (
    base: Snapshot,
    target: Snapshot,
    limits?: SnapshotDeltaModel.SnapshotDeltaLimits
  ): Effect.Effect<SnapshotDeltaModel.SnapshotDelta, VfsError | PlatformError.PlatformError, Crypto.Crypto>
}

Source

Since v0.1.0

inspectSnapshotDelta

Verifies an exact snapshot delta against its base and derives an owned path-oriented summary. Requires the platform-neutral Crypto.Crypto service for base identity. Provide NodeCrypto.layer, BunCrypto.layer, or your own via Crypto.make.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
import { Effect } from "effect"
 
// Inspection verifies the delta against its base before describing it.
const program = Effect.gen(function* () {
  const volume = yield* Vfs.make()
  const caller = yield* volume.caller()
  const base = yield* volume.snapshot
 
  yield* caller.mkdir("/logs")
 
  const delta = yield* Vfs.diffSnapshots(base, yield* volume.snapshot)
  const changes = yield* Vfs.inspectSnapshotDelta(base, delta)
 
  return changes.map((change) => (change._tag === "Updated" ? change.afterKind : change.kind))
}).pipe(Effect.provide(NodeCrypto.layer))
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// [ 'directory' ]

See

  • SnapshotChangesOptions for including timestamp differences.

Signature

declare const inspectSnapshotDelta: {
  (
    delta: SnapshotDeltaModel.SnapshotDelta,
    options?: SnapshotDeltaModel.SnapshotChangesOptions,
    limits?: SnapshotDeltaModel.SnapshotDeltaLimits
  ): (
    base: Snapshot
  ) => Effect.Effect<
    ReadonlyArray<SnapshotDeltaModel.SnapshotChange>,
    VfsError | PlatformError.PlatformError,
    Crypto.Crypto
  >
  (
    base: Snapshot,
    delta: SnapshotDeltaModel.SnapshotDelta,
    options?: SnapshotDeltaModel.SnapshotChangesOptions,
    limits?: SnapshotDeltaModel.SnapshotDeltaLimits
  ): Effect.Effect<
    ReadonlyArray<SnapshotDeltaModel.SnapshotChange>,
    VfsError | PlatformError.PlatformError,
    Crypto.Crypto
  >
}

Source

Since v0.1.0

mergeSnapshotDeltas

Merges two snapshot deltas that share one base into a single delta, reporting path-level conflicts as data.

Changes only one side made are taken as they are. Where both sides changed one path the merge keeps the change when both agree, combines metadata fields that only one side changed, and otherwise reports a conflict and leaves the base node in place. Timestamps never conflict. The merged delta is bound to the same base as its inputs and applies with applySnapshotDelta. Pass resolutions to take one side at paths an earlier merge reported. Requires the platform-neutral Crypto.Crypto service for base identity. Provide NodeCrypto.layer, BunCrypto.layer, or your own via Crypto.make.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { Effect } from "effect"
 
const encoder = new TextEncoder()
 
const program = Effect.gen(function* () {
  const source = yield* Vfs.fromFixture({
    entries: [{ kind: "file", path: "/shared.txt", bytes: encoder.encode("v0") }]
  })
  const base = yield* source.snapshot
 
  // Two agents work on separate overlays of one base.
  const ours = yield* Vfs.makeOverlay(base)
  const theirs = yield* Vfs.makeOverlay(base)
  yield* (yield* ours.caller()).writeFile("/ours.txt", encoder.encode("a"), { access: "write", create: "exclusive" })
  yield* (yield* theirs.caller()).writeFile("/shared.txt", encoder.encode("v1"), { access: "write", truncate: true })
 
  const result = yield* Vfs.mergeSnapshotDeltas(
    base,
    yield* Vfs.diffSnapshots(base, (yield* ours.capture()).snapshot),
    yield* Vfs.diffSnapshots(base, (yield* theirs.capture()).snapshot)
  )
 
  // Disjoint changes merge without conflicts; the delta applies to the shared base.
  const merged = yield* Vfs.applySnapshotDelta(base, result.delta).pipe(Effect.flatMap(Vfs.fromSnapshot()))
  const reader = yield* merged.caller()
 
  return [result.conflicts.length, new TextDecoder().decode(yield* reader.readFile("/shared.txt"))]
})
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// [ 0, 'v1' ]

See

  • diffSnapshots to build each side's delta, applySnapshotDelta to apply the merged one.

Signature

declare const mergeSnapshotDeltas: {
  (
    ours: SnapshotDeltaModel.SnapshotDelta,
    theirs: SnapshotDeltaModel.SnapshotDelta,
    options?: SnapshotDeltaModel.MergeOptions
  ): (
    base: Snapshot
  ) => Effect.Effect<SnapshotDeltaModel.MergeResult, VfsError | PlatformError.PlatformError, Crypto.Crypto>
  (
    base: Snapshot,
    ours: SnapshotDeltaModel.SnapshotDelta,
    theirs: SnapshotDeltaModel.SnapshotDelta,
    options?: SnapshotDeltaModel.MergeOptions
  ): Effect.Effect<SnapshotDeltaModel.MergeResult, VfsError | PlatformError.PlatformError, Crypto.Crypto>
}

Source

Since v0.8.0

snapshotEntries

Streams the tree at root in a snapshot as fixture entries, read from the snapshot itself without restoring a volume.

Details

root resolves as a privileged caller at / of a volume restored from the snapshot with default options would resolve it, following intermediate symbolic links but not a final one and applying no path byte limit. Entries come in sorted pre-order, children in the byte order of their names, with paths rooted at root, so the root entry's path is /. A path stays a string while every name on it is UTF-8 and is a BytePath otherwise. A file or symbolic link that more than one name reaches is an entry at its first path and a hardLink to that path at the others. Each file's bytes are an owned copy, made when the stream pulls its entry. Every entry after the root's is a fixture entry for a volume holding the same tree.

Example

import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { Effect, Stream } from "effect"
 
const program = Effect.gen(function* () {
  const volume = yield* Vfs.fromFixture({
    entries: [
      { kind: "directory", path: "/src" },
      { kind: "file", path: "/src/b.txt", bytes: new Uint8Array([2]) },
      { kind: "file", path: "/src/a.txt", bytes: new Uint8Array([1]) },
      { kind: "hardLink", path: "/src/c.txt", target: "/src/a.txt" }
    ]
  })
 
  const entries = yield* Stream.runCollect(Vfs.snapshotEntries(yield* volume.snapshot, "/src"))
 
  return entries.map((entry) => `${entry.kind} ${String(entry.path)}`)
})
 
Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
// [ 'directory /', 'file /a.txt', 'file /b.txt', 'hardLink /c.txt' ]

See

  • fromFixture for building a volume from the entries.

Signature

declare const snapshotEntries: {
  (root: PathInput): (snapshot: Snapshot) => Stream.Stream<Fixture["entries"][number], FsFailure | ImageFailure>
  (snapshot: Snapshot, root: PathInput): Stream.Stream<Fixture["entries"][number], FsFailure | ImageFailure>
}

Source

Since v0.6.0