VFS.Memory (VFS v0.1.0)

Copy Markdown View Source

In-memory VFS.Mountable backend.

Files live in a tree map (%{path => binary}); explicitly-created empty directories live in a dirs MapSet; mtimes tracks modification times.

Directories are recognized in two ways:

  1. Explicit — created via mkdir/3, recorded in dirs.
  2. Implicit — at least one file or explicit dir under them.

Both kinds satisfy stat/2 and readdir/2. Removing the last child of an implicit directory makes it disappear; an explicit directory persists until removed via rm/3 with recursive: true.

Construction

iex> mem = VFS.Memory.new(%{"/foo/bar" => "hello"})
iex> {:ok, "hello", _mem} = VFS.read_file(mem, "/foo/bar")
iex> :ok
:ok

Use the helpers in VFS (e.g. VFS.read_file/2, VFS.walk/3) for telemetry-instrumented access. Direct protocol calls bypass instrumentation.

Summary

Functions

Build a fresh in-memory FS, optionally seeded with files.

Types

t()

@type t() :: %VFS.Memory{
  dirs: MapSet.t(VFS.Path.t()),
  mtimes: %{required(VFS.Path.t()) => DateTime.t()},
  tree: %{required(VFS.Path.t()) => binary()}
}

Functions

new(initial \\ %{})

@spec new(%{optional(String.t()) => binary()}) :: t()

Build a fresh in-memory FS, optionally seeded with files.

Raises ArgumentError if the seed is malformed:

  • Every key must be a path binary and every value a binary — anything else fails here, at construction, instead of deferring the crash to the first stat/read.
  • The literal "/" cannot be a file (root is always a directory).
  • No two paths can be in a strict path-segment-prefix relationship (e.g. %{"/a" => "...", "/a/b" => "..."} is rejected — /a cannot simultaneously be a regular file and a directory).

Validation runs at construction so the resulting backend is internally consistent: stat, readdir, and read_file agree on every path. Without validation, externally-provided seeds (config files, DB dumps, LLM-generated inputs) could put the FS into a state that no sequence of writes could ever produce.

Examples

iex> mem = VFS.Memory.new()
iex> mem.tree
%{}

iex> mem = VFS.Memory.new(%{"/a.txt" => "hi"})
iex> Map.fetch!(mem.tree, "/a.txt")
"hi"

iex> VFS.Memory.new(%{"/a" => "f", "/a/b" => "c"})
** (ArgumentError) VFS.Memory seed has conflicting paths: "/a" is a file but "/a/b" places a child under it. A path cannot be both a file and a directory.