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

Signature

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

Source

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

Signature

export interface Built<State> {
  readonly fileSystem: FileSystem.FileSystem
  readonly state: State
}

Source

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
}

Source

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

Source

Since v0.9.0