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.Cryptofor 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>
}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
makeOverlayfor a copy-on-write workspace over the same base.Crypto.Cryptofor 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>
}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>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.Cryptofor 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>
}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>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>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 secondExample
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>
}Since v0.1.0
CallerWatchOptions (type alias)
Options for a caller watch.
Signature
export type CallerWatchOptions = {
readonly recursive?: boolean
readonly alias?: "all" | "resolved"
}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 }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 }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
}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>
}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>
}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
}
}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"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
}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
}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
}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
}Since v0.1.0
ObjectReference (interface)
An opaque identity for one object in one live volume.
Signature
export interface ObjectReference {
readonly [ObjectReferenceId]: true
}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
}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
}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
}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>
}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"
)[]
}Since v0.1.0
OverlayChangesOptions (type alias)
Filtering options for an overlay final-difference summary.
Signature
export type OverlayChangesOptions = { readonly includeTimestamps?: boolean }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"Since v0.1.0
OverlayNodeKind (type alias)
A filesystem entry kind reported by an overlay summary.
Signature
export type OverlayNodeKind = "file" | "directory" | "symlink"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>
}Since v0.1.0
OwnerUpdate (type alias)
An owner update for chown operations.
Signature
export type OwnerUpdate = { readonly uid?: number; readonly gid?: number }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
BytePathfor the byte-preserving form.
Signature
type PathInput = string | BytePathSince 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
}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
}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>
}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
}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 }
}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
}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"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 }
}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 }
}
}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 }
}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)
// CreateSignature
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>
}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"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">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">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
}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
}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
}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
}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" })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"
}Since v0.6.0
WatchOptions (type alias)
Options of Volume.watch.
Signature
export type WatchOptions = { readonly recursive?: boolean; readonly scope?: ObjectReference }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>
}>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
}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"
>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
}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"]>>
}>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> }>
}>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 }>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: InvalidStructureSignature
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>]>
}>
]
>
>
}>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"
]
>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
}>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
}>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>
}>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
}>
>
>
}>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)
// 2nSignature
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>
}>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"]>
>
}
>
]
>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> }>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"]
>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"]>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>
}>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)
// trueSignature
declare const ReferenceKey: Schema.Struct<{
readonly identity: Schema.Uint8ArrayFromBase64
readonly epoch: Schema.Uint8ArrayFromBase64
readonly ino: Schema.BigIntFromString
readonly tag: Schema.Uint8ArrayFromBase64
}>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>>>
}>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 }> }
>
}>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)
// 755Signature
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>
}>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"]>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 }>>
}>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
>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 }>
]
>
}>
>
}>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 }>
]
>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 }>
]
>
}>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"]
>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">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">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)
// FileTooLargeSignature
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>
}>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>
}>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>
}>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
DecodeLimitsfor 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>
}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
DecodeLimitsfor what each bound protects.decodeSnapshotfor decoding bytes already in memory.
Signature
declare const decodeSnapshotSink: (
limits: DecodeLimits
) => Sink.Sink<Snapshot, Uint8Array, never, ImageFailure | ArgumentFailure>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)
// 1See
decodeSnapshotfor 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>
}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
encodeSnapshotfor the same bytes collected into one array.decodeSnapshotSinkfor 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>
}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>
}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>
}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
SnapshotDeltaFromBytesfor 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>
}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.txtSee
inspectSnapshotDeltato read a delta,applySnapshotDeltato 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>
}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
SnapshotChangesOptionsfor 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
>
}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
diffSnapshotsto build each side's delta,applySnapshotDeltato 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>
}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
fromFixturefor 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>
}Since v0.6.0