FileSystemTesting.ts overview
Observes or rejects selected Effect FileSystem calls before real operations.
Since v0.9.0
constructors
make
Builds a decorator over any Effect FileSystem, including host services.
The factory runs once each time this constructor Effect executes. Allocate counters inside the factory for independent builds. Reusing a built service shares its state; capturing an external counter deliberately shares that counter even across builds. Normal Layer memoization also shares a build.
Helpers are rebuilt with FileSystem.make, replacing any custom derived
helpers on the supplied service with Effect's standard implementations.
Handler failures preserve their typed errors. Delegated operations retain
their interruption and scope ownership. Rejection before delegation cannot
simulate partial writes or crashes.
Example
import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
import { FileSystemTesting, MemoryFileSystem } from "@effect-vfs/memory"
import { Effect, Ref } from "effect"
const program = Effect.gen(function* () {
const base = yield* MemoryFileSystem.make
const { fileSystem, state } = yield* FileSystemTesting.make(
base,
Effect.fnUntraced(function* () {
const writes = yield* Ref.make(0)
return {
state: writes,
handlers: { writeFile: () => Ref.update(writes, (count) => count + 1) }
}
})
)
yield* fileSystem.writeFileString("/manifest.json", "{}")
return yield* Ref.get(state)
})
Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
// 1Signature
declare const make: {
<State, E, R>(
factory: () => Effect.Effect<Configuration<State>, E, R>
): (base: FileSystem.FileSystem) => Effect.Effect<Built<State>, E, R>
<State, E, R>(
base: FileSystem.FileSystem,
factory: () => Effect.Effect<Configuration<State>, E, R>
): Effect.Effect<Built<State>, E, R>
}Since v0.9.0
models
Built (interface)
A decorated service and the factory's state for assertions.
Example
import { FileSystemTesting } from "@effect-vfs/memory"
import type { Ref } from "effect"
const counter = (built: FileSystemTesting.Built<Ref.Ref<number>>) => built.stateSignature
export interface Built<State> {
readonly fileSystem: FileSystem.FileSystem
readonly state: State
}Since v0.9.0
Configuration (interface)
Handlers and test-owned state allocated by the factory for one service build.
Example
import { FileSystemTesting } from "@effect-vfs/memory"
import { Effect, Ref } from "effect"
const factory = Effect.fnUntraced(function* () {
const writes = yield* Ref.make(0)
return {
state: writes,
handlers: { writeFile: () => Ref.update(writes, (count) => count + 1) }
} satisfies FileSystemTesting.Configuration<Ref.Ref<number>>
})Signature
export interface Configuration<State> {
readonly handlers: Handlers
readonly state: State
}Since v0.9.0
Handlers (type alias)
Optional handlers with the original method arguments and scope requirements. Success delegates to the real operation; failure prevents delegation. Handlers run once per execution, before the backing method is called.
Derived helpers route through these handlers: exists through access,
readFileString through readFile, writeFileString through writeFile,
consumed streams and sinks through open, and glob traversal through
readDirectory, stat, and readLink.
Returned file handles and watch are unchanged. Internal calls made by the
backing service, including temporary-resource cleanup, bypass these handlers.
A write spy counts public write calls, not every filesystem mutation.
Example
import { FileSystemTesting } from "@effect-vfs/memory"
import { Effect, PlatformError } from "effect"
const handlers: FileSystemTesting.Handlers = {
writeFile: (path) =>
path === "/blocked"
? Effect.fail(
PlatformError.systemError({
_tag: "Unknown",
module: "FileSystem",
method: "writeFile",
pathOrDescriptor: path,
description: "Injected failure"
})
)
: Effect.void
}Signature
export type Handlers = {
readonly access?: (
path: string,
options?:
| {
readonly ok?: boolean | undefined
readonly readable?: boolean | undefined
readonly writable?: boolean | undefined
}
| undefined,
) => Effect.Effect<void, PlatformError.PlatformError, never>
readonly copy?: (
fromPath: string,
toPath: string,
options?:
| {
readonly overwrite?: boolean | undefined
readonly preserveTimestamps?: boolean | undefined
}
| undefined,
) => Effect.Effect<void, PlatformError.PlatformError, never>
readonly copyFile?: (
fromPath: string,
toPath: string,
) => Effect.Effect<void, PlatformError.PlatformError, never>
readonly chmod?: (
path: string,
mode: number,
) => Effect.Effect<void, PlatformError.PlatformError, never>
readonly chown?: (
path: string,
uid: number,
gid: number,
) => Effect.Effect<void, PlatformError.PlatformError, never>
readonly glob?: (
pattern: string,
options?:
| { readonly root?: string | undefined; readonly exclude?: ReadonlyArray<string> | undefined }
| undefined,
) => Effect.Effect<void, PlatformError.PlatformError, never>
readonly link?: (
fromPath: string,
toPath: string,
) => Effect.Effect<void, PlatformError.PlatformError, never>
readonly makeDirectory?: (
path: string,
options?:
{ readonly recursive?: boolean | undefined; readonly mode?: number | undefined } | undefined,
) => Effect.Effect<void, PlatformError.PlatformError, never>
readonly makeTempDirectory?: (
options?:
{ readonly directory?: string | undefined; readonly prefix?: string | undefined } | undefined,
) => Effect.Effect<void, PlatformError.PlatformError, never>
readonly makeTempDirectoryScoped?: (
options?:
{ readonly directory?: string | undefined; readonly prefix?: string | undefined } | undefined,
) => Effect.Effect<void, PlatformError.PlatformError, Scope.Scope>
readonly makeTempFile?: (
options?:
| {
readonly directory?: string | undefined
readonly prefix?: string | undefined
readonly suffix?: string | undefined
}
| undefined,
) => Effect.Effect<void, PlatformError.PlatformError, never>
readonly makeTempFileScoped?: (
options?:
| {
readonly directory?: string | undefined
readonly prefix?: string | undefined
readonly suffix?: string | undefined
}
| undefined,
) => Effect.Effect<void, PlatformError.PlatformError, Scope.Scope>
readonly open?: (
path: string,
options?:
| {
readonly flag?: FileSystem.OpenFlag | undefined
readonly mode?: number | undefined
readonly noFollow?: boolean | undefined
}
| undefined,
) => Effect.Effect<void, PlatformError.PlatformError, Scope.Scope>
readonly readDirectory?: (
path: string,
options?: { readonly recursive?: boolean | undefined } | undefined,
) => Effect.Effect<void, PlatformError.PlatformError, never>
readonly readFile?: (path: string) => Effect.Effect<void, PlatformError.PlatformError, never>
readonly readLink?: (path: string) => Effect.Effect<void, PlatformError.PlatformError, never>
readonly realPath?: (path: string) => Effect.Effect<void, PlatformError.PlatformError, never>
readonly remove?: (
path: string,
options?:
| { readonly recursive?: boolean | undefined; readonly force?: boolean | undefined }
| undefined,
) => Effect.Effect<void, PlatformError.PlatformError, never>
readonly rename?: (
oldPath: string,
newPath: string,
) => Effect.Effect<void, PlatformError.PlatformError, never>
readonly stat?: (path: string) => Effect.Effect<void, PlatformError.PlatformError, never>
readonly symlink?: (
fromPath: string,
toPath: string,
) => Effect.Effect<void, PlatformError.PlatformError, never>
readonly truncate?: (
path: string,
length?: number | undefined,
) => Effect.Effect<void, PlatformError.PlatformError, never>
readonly utimes?: (
path: string,
atime: number | Date,
mtime: number | Date,
) => Effect.Effect<void, PlatformError.PlatformError, never>
readonly writeFile?: (
path: string,
data: Uint8Array<ArrayBufferLike>,
options?:
| { readonly flag?: FileSystem.OpenFlag | undefined; readonly mode?: number | undefined }
| undefined,
) => Effect.Effect<void, PlatformError.PlatformError, never>
}Since v0.9.0