Skip to content

feat: add MemoryFileSystem module#6573

Open
lloydrichards wants to merge 1 commit into
Effect-TS:mainfrom
lloydrichards:feat/memory-file-system
Open

feat: add MemoryFileSystem module#6573
lloydrichards wants to merge 1 commit into
Effect-TS:mainfrom
lloydrichards:feat/memory-file-system

Conversation

@lloydrichards

@lloydrichards lloydrichards commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Type

  • Feature

Description

I've been looking for an in-memory layer for the FileSystem for a little while, something useful for testing filesystem-dependent code without creating and cleaning up temp directories, and for exercising filesystem configuration or dry-run flows without touching the host filesystem. My main motivating use case is stack-effect#108.

I spoke briefly with @fubhy about this on discord and wanted to try and push this along based on his work in effect-smol (see effect-smol/pull/456) and the test suite I build in #6555. I set some clankers on this initially and have been working back and forth for the last couple of days to get it to a stage where its more a questions of improvement than implementation (all 79 tests pass).

MemoryFileSystem keeps an explicit in-memory inode model (directory entries, files, links, and per-open descriptors) on top of the existing v4 FileSystem service. The adapter implements the core filesystem operations, while FileSystem.make continues to supply its derived helpers. Consumers just need to provide MemoryFileSystem.layer in place of a host-backed filesystem.

Review guide

Its a chunky 2,650 loc module so I'm not expecting this to just get pushed in without some criticism and rework but I thought its a good place to start and inspire some action. The main implementation is intentionally self-contained:

  • Public entry point: packages/effect/src/MemoryFileSystem.ts
  • Implementation: packages/effect/src/internal/memoryFileSystem.ts
  • Contract and adapter tests: packages/effect/test/MemoryFileSystem.test.ts

The highest-value review areas are:

  • Path resolution and symlink traversal
  • Descriptor lifecycle, cursors, append/truncate behavior, and unlinking open files
  • Hard-link and inode lifetime semantics
  • Error normalization and metadata updates
  • Isolation between separately provided layers

The focused test suite currently passes. I’ve also included an architecture overview for readers who want the full model before reviewing the implementation or you can explore call stack graph below:

Call Stack Graph
---
config:
  flowchart:
    curve: monotoneX
---
flowchart LR
  F[FileSystem.make facade]
  F --> access --> withState --> resolve
  F --> stat --> withState
  stat --> resolve
  F --> realPath --> withState
  realPath --> resolve
  F --> readLink --> withState
  readLink --> resolveNoFollow[resolve: do not follow final symlink]
  resolve -. metadata .-> fileInfo
  resolveNoFollow -. raw target .-> target

  F --> readDirectory --> mutate --> resolve
  F --> readFile --> mutate
  readFile --> resolve
  readDirectory --> atime[replace inode atime] --> commit
  readFile --> atime
  F --> chmod --> validateMode --> mutate
  F --> chown --> validateOwner --> mutate
  F --> utimes --> validateTimes --> mutate
  chmod --> resolve
  chown --> resolve
  utimes --> resolve
  chmod --> inodeUpdateEvents --> commit
  chown --> inodeUpdateEvents
  utimes --> inodeUpdateEvents
  F --> truncate --> validateSize --> mutate
  truncate --> resolve --> allocateBytes --> inodeUpdateEvents
  

  F --> makeDirectory --> mutate
  makeDirectory --> resolveParent --> createDirectory --> attachDirectory --> commit
  F --> link --> mutate
  link --> resolve
  link --> resolveParent --> linkInode --> commit
  F --> symlink --> mutate
  symlink --> resolveParent
  symlink --> createSymbolicLink --> linkInode
  F --> remove --> mutate --> resolveEntry --> detachEntry[recursive detachEntry] --> reclaimInode --> commit
  F --> rename --> mutate
  rename --> resolveEntry
  rename --> resolveParent
  rename --> topologyChecks[topology checks and optional detach] --> commit
  

  copy --> copyEntryUnlocked --> validateCopy[recursive validate/copy] --> cloneInode --> commit
  copyFile --> copyFileUnlocked --> resolveCopyDestination --> cloneOrUpdate[clone or update] --> commit
  F --> copyFile --> mutate
  F --> copy --> mutate
  F --> open --> acquireRelease --> openDescriptor --> openDescriptorUnlocked --> MemoryFile
  F --> writeFile --> mutate
  MemoryFile --> closeDescriptor
  writeFile --> openDescriptorUnlocked --> writeDescriptorUnlocked --> closeDescriptorUnlocked --> commit
  

  F --> makeTempDirectory --> mutate
  makeTempDirectory --> allocateTempDirectory --> allocateToken[allocate token] --> createDirectory
  F --> makeTempDirectoryScoped --> acquireRelease
  makeTempDirectoryScoped --> makeTempDirectory
  F --> makeTempFile --> mutateInterruptibly --> allocateTempDirectory
  makeTempFile --> allocateToken --> createFile --> linkInode
  F --> makeTempFileScoped --> acquireRelease
  makeTempFileScoped --> makeTempFile
  F --> glob --> validateGlob --> withState
  glob --> resolve
  glob --> directoryWalk[sorted directory walk] --> globMatcher[glob matcher]
  F --> watch --> StreamUnwrap[Stream.unwrap] --> acquireRelease
  watch --> subscription[subscription + callback queue]
  mutate --> commit
  mutateInterruptibly --> commit
  commit --> publishWatchEvents
  

  F --> exists[exists derived] --> access
  F --> readFileString[readFileString derived] --> readFile
  F --> writeFileString[writeFileString derived] --> writeFile
  F --> stream[stream derived] --> open
  stream --> readAlloc[MemoryFile.readAlloc]
  F --> sink[sink derived] --> open
  sink --> writeAll[MemoryFile.writeAll]

classDef read fill:#123247,stroke:#55B6E8,stroke-width:1.5px,color:#E6F6FF
classDef namespace fill:#3B2B12,stroke:#E5AE4B,stroke-width:1.5px,color:#FFF4D8
classDef content fill:#153D2A,stroke:#63C287,stroke-width:1.5px,color:#E7FAED
classDef lifecycle fill:#302044,stroke:#B494E8,stroke-width:1.5px,color:#F2EBFF
classDef derived fill:#273344,stroke:#9BAFC9,stroke-width:1.5px,color:#EEF4FC
  class access,stat,realPath,readLink,readDirectory,readFile,chmod,chown,utimes,truncate,glob read
  class makeDirectory,link,symlink,remove,rename namespace
  class copyFile,copy,open,writeFile content
  class makeTempDirectory,makeTempDirectoryScoped,makeTempFile,makeTempFileScoped,watch lifecycle
  class exists,readFileString,writeFileString,stream,sink derived
  linkStyle default stroke-width:1.5px
  linkStyle 0,1,2,3,4,5,6,7,8,9,10,11,12,13 stroke-width:1px,stroke-dasharray:4 3
Loading

Related

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Added an MemoryFileSystem module providing an isolated in-memory virtual file system.
    • Supports common file operations, directories, hard links, symbolic links, POSIX metadata, temp files/directories, globbing, and watch streams.
    • Added the ability to create fresh, independent virtual volumes.
  • Bug Fixes

    • Improved correctness and consistency for in-memory filesystem behaviors (renames/removals with symlinks and hard links, exclusive create rules, concurrent appends, and watch event ordering).

@github-project-automation github-project-automation Bot moved this to Discussion Ongoing in PR Backlog Jul 24, 2026
@changeset-bot

changeset-bot Bot commented Jul 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: cda9755

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 27 packages
Name Type
effect Major
@effect/opentelemetry Major
@effect/platform-browser Major
@effect/platform-bun Major
@effect/platform-node-shared Major
@effect/platform-node Major
@effect/vitest Major
@effect/ai-anthropic Major
@effect/ai-openai-compat Major
@effect/ai-openai Major
@effect/ai-openrouter Major
@effect/atom-react Major
@effect/atom-solid Major
@effect/atom-vue Major
@effect/sql-clickhouse Major
@effect/sql-d1 Major
@effect/sql-libsql Major
@effect/sql-mssql Major
@effect/sql-mysql2 Major
@effect/sql-pg Major
@effect/sql-pglite Major
@effect/sql-sqlite-bun Major
@effect/sql-sqlite-do Major
@effect/sql-sqlite-node Major
@effect/sql-sqlite-react-native Major
@effect/sql-sqlite-wasm Major
@effect/openapi-generator Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added the 4.0 label Jul 24, 2026
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2af3b872-1c9d-4d5f-8c06-e12c6c372df2

📥 Commits

Reviewing files that changed from the base of the PR and between a52dfd4 and cda9755.

📒 Files selected for processing (4)
  • .changeset/puny-queens-spend.md
  • packages/effect/src/MemoryFileSystem.ts
  • packages/effect/src/internal/memoryFileSystem.ts
  • packages/effect/test/MemoryFileSystem.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/effect/src/MemoryFileSystem.ts
  • .changeset/puny-queens-spend.md
  • packages/effect/test/MemoryFileSystem.test.ts
  • packages/effect/src/internal/memoryFileSystem.ts

📝 Walkthrough

Walkthrough

Adds an isolated in-memory FileSystem implementation with POSIX-style operations, descriptors, globbing, temporary resources, watch streams, public constructors, and comprehensive behavior tests.

Changes

MemoryFileSystem

Layer / File(s) Summary
Volume model and state transitions
packages/effect/src/internal/memoryFileSystem.ts
Defines inode variants, descriptors, path resolution, linking, reclamation, watch state, and semaphore-guarded volume commits.
Filesystem operations and descriptors
packages/effect/src/internal/memoryFileSystem.ts
Implements directory and file mutations, copying, descriptor operations, metadata APIs, and scoped temporary resources.
Globbing, watching, and service wiring
packages/effect/src/internal/memoryFileSystem.ts, packages/effect/src/MemoryFileSystem.ts, .changeset/puny-queens-spend.md
Adds glob matching, watch subscriptions, public make and layer exports, and a minor release changeset.
Filesystem behavior validation
packages/effect/test/MemoryFileSystem.test.ts
Tests path semantics, isolation, concurrency, metadata, globbing, and ordered watch events including hard-link aliases.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant FileSystem
  participant Volume
  participant WatchStream
  FileSystem->>Volume: mutate filesystem state
  Volume->>Volume: commit normalized transition
  Volume->>WatchStream: publish filesystem event
  WatchStream-->>FileSystem: emit watched path event
Loading

Suggested labels: enhancement

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot added the enhancement New feature or request label Jul 24, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/effect/src/internal/memoryFileSystem.ts`:
- Around line 769-810: Update the recursive makeDirectory creation flow to
accumulate every newly created path instead of overwriting createdPath on each
iteration. Emit a Create event for each created directory in creation order,
including intermediate paths such as /a and /a/b, while preserving the existing
empty-event behavior when no directory is created.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 054f14df-a2f1-4ce3-8637-2eefbe37f869

📥 Commits

Reviewing files that changed from the base of the PR and between 5101e92 and a52dfd4.

📒 Files selected for processing (4)
  • .changeset/puny-queens-spend.md
  • packages/effect/src/MemoryFileSystem.ts
  • packages/effect/src/internal/memoryFileSystem.ts
  • packages/effect/test/MemoryFileSystem.test.ts

Comment thread packages/effect/src/internal/memoryFileSystem.ts Outdated
@github-project-automation github-project-automation Bot moved this from Discussion Ongoing to Waiting on Author in PR Backlog Jul 24, 2026
@lloydrichards
lloydrichards force-pushed the feat/memory-file-system branch from a52dfd4 to cda9755 Compare July 24, 2026 12:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

4.0 enhancement New feature or request

Projects

Status: Waiting on Author

Development

Successfully merging this pull request may close these issues.

1 participant