MemoryFileSystem.ts overview

Provides an in-memory implementation of Effect's FileSystem service.

The service uses @effect-vfs/core for filesystem state and exposes that state through Effect's path-based FileSystem interface. It is intended for tests, build tools, and programs that need filesystem behavior without host filesystem I/O.

Since v0.1.0


constructors

bind

Creates a FileSystem.FileSystem service backed by an existing core volume.

Details

The binding creates its own root caller and file-descriptor table. It does not add /tmp or otherwise modify the volume. Use this when an adapter and direct core callers must share filesystem state. Bindings share namespace and content changes, but keep independent caller state, descriptors, and file cursors.

The caller defaults to a privileged uid and gid of 0 with umask 0. Invalid caller options fail with a VfsError whose code is InvalidArgument and whose field names the option. Filesystem operations translate core failures to Effect PlatformError values.

Example

import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { MemoryFileSystem } from "@effect-vfs/memory"
import { Effect, FileSystem, Layer } from "effect"
 
const encoder = new TextEncoder()
 
// A volume seeded with a fixture, exposed through Effect's `FileSystem`.
const seeded = Layer.effect(
  FileSystem.FileSystem,
  Effect.gen(function* () {
    const volume = yield* Vfs.fromFixture({
      entries: [
        { kind: "directory", path: "/project" },
        {
          kind: "file",
          path: "/project/package.json",
          bytes: encoder.encode(`{"version":"1.2.3"}`)
        }
      ]
    })
 
    return yield* MemoryFileSystem.bind(volume)
  })
)
 
const program = Effect.gen(function* () {
  const fs = yield* FileSystem.FileSystem
 
  return yield* fs.readFileString("/project/package.json")
})
 
Effect.runPromise(program.pipe(Effect.provide(seeded), Effect.provide(NodeCrypto.layer))).then(console.log)
// {"version":"1.2.3"}

See

  • make for a service backed by a fresh volume with /tmp.

Signature

declare const bind: {
  (options?: Vfs.RootCallerOptions): (volume: Vfs.Volume) => Effect.Effect<FileSystem.FileSystem, Vfs.VfsError>
  (volume: Vfs.Volume, options?: Vfs.RootCallerOptions): Effect.Effect<FileSystem.FileSystem, Vfs.VfsError>
}

Source

Since v0.1.0

bindCaller

Creates a FileSystem service that borrows an existing caller.

Preserves the caller's root, working directory, credentials, and umask. The binding neither closes the caller nor creates /tmp; file and watch resources retain their own scoped lifetimes.

Example

import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { MemoryFileSystem } from "@effect-vfs/memory"
import { Effect } from "effect"
 
const program = Effect.gen(function* () {
  const volume = yield* Vfs.make()
  const owner = yield* volume.caller()
  yield* owner.mkdir("/agent")
  const caller = yield* owner.withRoot("/agent")
  const fs = yield* MemoryFileSystem.bindCaller(caller)
  yield* fs.writeFileString("/output.txt", "done")
  return yield* fs.readFileString("/output.txt")
}).pipe(Effect.scoped)
 
Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
// done

Signature

declare const bindCaller: (caller: Vfs.Caller) => Effect.Effect<FileSystem.FileSystem>

Source

Since v0.9.0

make

Creates a FileSystem.FileSystem service backed by a fresh in-memory volume.

When to use

Use when you need the service value directly. The volume starts with an empty /tmp directory and uses / as its working directory.

Example

import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
import { MemoryFileSystem } from "@effect-vfs/memory"
import { Effect } from "effect"
 
const program = Effect.gen(function* () {
  const fs = yield* MemoryFileSystem.make
 
  yield* fs.writeFileString("/tmp/greeting.txt", "hello")
 
  return yield* fs.readFileString("/tmp/greeting.txt")
})
 
Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
// hello

See

  • layer for a Layer, bind for an existing volume.
  • Crypto.Crypto for the required secure random-byte service.

Signature

declare const make: Effect.Effect<FileSystem.FileSystem, never, Crypto.Crypto>

Source

Since v0.1.0

guards

isWatchOverflow

Identifies a watch stream failure caused by lost events. Open a new watch before rescanning its path, and repeat if the new watch also overflows.

Example

import { MemoryFileSystem } from "@effect-vfs/memory"
import * as PlatformError from "effect/PlatformError"
 
const error = PlatformError.systemError({
  _tag: "Unknown",
  module: "FileSystem",
  method: "watch",
  description: "WatchOverflow"
})
 
console.log(MemoryFileSystem.isWatchOverflow(error))
// true

Signature

declare const isWatchOverflow: (error: PlatformError.PlatformError) => boolean

Source

Since v0.4.0

layers

layer

Provides a FileSystem.FileSystem backed by a fresh in-memory volume.

When to use

Use when you need to replace the host filesystem in an Effect program.

Gotchas

Reusing this layer value within a single layer graph shares one volume through layer memoization. Wrap it with Layer.fresh when each use needs separate state. Separate graphs, such as one per test, already get separate volumes. Provide Crypto.Crypto to build the underlying volume.

Example

import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
import { MemoryFileSystem } from "@effect-vfs/memory"
import { Effect, FileSystem } from "effect"
 
const writeManifest = Effect.gen(function* () {
  const fs = yield* FileSystem.FileSystem
 
  yield* fs.makeDirectory("/dist", { recursive: true })
  yield* fs.writeFileString("/dist/manifest.json", `{"version":"1.2.3"}`)
 
  return yield* fs.readFileString("/dist/manifest.json")
})
 
Effect.runPromise(writeManifest.pipe(Effect.provide(MemoryFileSystem.layer), Effect.provide(NodeCrypto.layer))).then(
  console.log
)
// {"version":"1.2.3"}

Example

import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
import { MemoryFileSystem } from "@effect-vfs/memory"
import { Effect, FileSystem, Layer } from "effect"
 
const write = Effect.gen(function* () {
  const fs = yield* FileSystem.FileSystem
 
  yield* fs.writeFileString("/shared.txt", "written")
})
 
const exists = Effect.gen(function* () {
  const fs = yield* FileSystem.FileSystem
 
  return yield* fs.exists("/shared.txt")
})
 
const program = Effect.gen(function* () {
  // One layer value in one graph is memoized, so both effects share a volume.
  const shared = yield* Effect.andThen(write, exists).pipe(Effect.provide(MemoryFileSystem.layer))
 
  // `Layer.fresh` builds the layer again, giving each use its own volume.
  const isolated = yield* Effect.andThen(
    write.pipe(Effect.provide(Layer.fresh(MemoryFileSystem.layer))),
    exists.pipe(Effect.provide(Layer.fresh(MemoryFileSystem.layer)))
  )
 
  return [shared, isolated]
})
 
Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
// [ true, false ]

See

  • make for the service directly, bind for an existing volume.
  • The testing guide at /guides/testing-with-an-isolated-filesystem.

Signature

declare const layer: Layer.Layer<FileSystem.FileSystem, never, Crypto.Crypto>

Source

Since v0.1.0

layerFromFixture

Provides a FileSystem.FileSystem backed by a fresh fixture volume.

Volume limits and caller options use the existing core types. Invalid fixtures and options remain typed VfsError construction failures. Provide Crypto.Crypto to build the volume. This layer adds no entries; include /tmp in the fixture when temporary-file operations need it.

Separate builds get separate volumes. Reusing one layer value within a single layer graph shares its volume. Use Layer.fresh for independent state in that graph. Merge with Path.layer when a consumer also needs POSIX paths.

Example

import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
import { MemoryFileSystem } from "@effect-vfs/memory"
import { Effect, FileSystem, Layer, Path } from "effect"
 
const seeded = MemoryFileSystem.layerFromFixture({
  entries: [{ kind: "file", path: "/greeting.txt", bytes: new TextEncoder().encode("hello") }]
})
const services = Layer.merge(seeded, Path.layer)
const program = Effect.gen(function* () {
  const fs = yield* FileSystem.FileSystem
  const path = yield* Path.Path
  return yield* fs.readFileString(path.join("/", "greeting.txt"))
})
 
Effect.runPromise(program.pipe(Effect.provide(services), Effect.provide(NodeCrypto.layer))).then(console.log)
// hello

See

  • bind when direct core callers must share the volume.

Signature

declare const layerFromFixture: {
  (
    volumeOptions?: Vfs.VolumeOptions,
    callerOptions?: Vfs.RootCallerOptions
  ): (fixture: Vfs.Fixture) => Layer.Layer<FileSystem.FileSystem, Vfs.VfsError, Crypto.Crypto>
  (
    fixture: Vfs.Fixture,
    volumeOptions?: Vfs.VolumeOptions,
    callerOptions?: Vfs.RootCallerOptions
  ): Layer.Layer<FileSystem.FileSystem, Vfs.VfsError, Crypto.Crypto>
}

Source

Since v0.8.0