Skip to content

vfs: add minimal node:vfs subsystem#63115

Open
mcollina wants to merge 15 commits intonodejs:mainfrom
mcollina:vfs-minimal
Open

vfs: add minimal node:vfs subsystem#63115
mcollina wants to merge 15 commits intonodejs:mainfrom
mcollina:vfs-minimal

Conversation

@mcollina
Copy link
Copy Markdown
Member

@mcollina mcollina commented May 4, 2026

Adds an experimental node:vfs builtin (gated behind --experimental-vfs) with VirtualFileSystem, VirtualProvider, MemoryProvider, and RealFSProvider. No integration with node:fs, the module loader, or SEA those are intended to land in follow-up PRs.

Extracted from: #61478

Approximate line counts: code ~4k / docs ~1k / tests ~5k — total ~10k lines, with tests being the largest share.

@nodejs-github-bot
Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/config
  • @nodejs/loaders
  • @nodejs/startup

@nodejs-github-bot nodejs-github-bot added lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. labels May 4, 2026
@bakkot
Copy link
Copy Markdown
Contributor

bakkot commented May 4, 2026

No integration with node:fs

The docs in this PR claim that you can call myVfs.mount('/virtual') and subsequently regular node:fs operations which read from paths beginning with /virtual will read from the VFS. That sounds like integration to me. Are the docs wrong, or am I misunderstanding what "no integration" means?

mcollina added 13 commits May 5, 2026 10:33
Adds the node:vfs builtin module with VirtualFileSystem and
provider classes. No integration with fs, modules, or SEA.

Assisted-by: Claude-Opus4.7
Signed-off-by: Matteo Collina <hello@matteocollina.com>
Adapts tests that exercised behavior through fs integration so
they call the VFS API directly instead.

Assisted-by: Claude-Opus4.7
Signed-off-by: Matteo Collina <hello@matteocollina.com>
Cover VirtualDir iteration and disposal, MemoryFileHandle read/write
methods via the provider, and the VirtualProvider base class
(capability flags, readonly stubs, default implementations).

Assisted-by: Claude-Opus4.7
Signed-off-by: Matteo Collina <hello@matteocollina.com>
Covers MemoryProvider, copyFile mode, rm edge cases, hardlinks,
bigint read positions, and parent timestamps via the VFS API.

Assisted-by: Claude-Opus4.7
Signed-off-by: Matteo Collina <hello@matteocollina.com>
Cover the callback-style async API, additional read/write stream
flows, the promises.watch async iterable, and async methods of
RealFSProvider.

Assisted-by: Claude-Opus4.7
Signed-off-by: Matteo Collina <hello@matteocollina.com>
Removes the unused createEXDEV error helper, adds direct tests for
MemoryProvider numeric flags / symlink loops / utimes variants, and
adds a base-class VirtualFileHandle test.

Assisted-by: Claude-Opus4.7
Signed-off-by: Matteo Collina <hello@matteocollina.com>
Adds targeted tests covering the lazy population, dynamic content
provider, readonly-mode, and symlink-traversal paths in MemoryProvider;
the path-escape and RealFileHandle EBADF paths in RealFSProvider; the
abort/buffer-encoding/recursive watch paths in VFSWatcher; and the
empty-file / EBADF fd / explicit-fd-with-start paths in the streams.

Assisted-by: Claude-Opus4.7
Signed-off-by: Matteo Collina <hello@matteocollina.com>
Adds direct unit tests for stats default-option paths (including the
process.getuid?.() fallback), file-handle base-class branches, the
empty-options provider write/append paths, the access-mode permission
denials, the watcher closed-state and async-iterable resolver-drain
branches, and various RealFSProvider escape and EBADF paths.

Brings overall branch coverage from 89% to 95.7%, and stats.js to
100% branch coverage.

Assisted-by: Claude-Opus4.7
Signed-off-by: Matteo Collina <hello@matteocollina.com>
Replaces the -coverage / -branches / -misc suffixes with focused
files named after the API or behaviour they exercise. Splits the
larger multi-topic files into one-topic-per-file.

Renames:
- callbacks.js → callback-api.js
- stats-defaults.js → stats-helpers.js
- file-handle-base.js → virtual-file-handle.js
- provider-base.js → virtual-provider.js
- provider-memory.js → memory-provider.js
- real-provider-async.js → real-provider-promises.js
- mkdir-recursive-return.js → mkdir.js

New files (split out of -coverage/-branches/-misc):
- access-modes, create, link, mkdtemp, rename, symlinks, utimes,
  write-options
- memory-file-handle, memory-provider-dynamic, memory-provider-flags
- real-provider-handle, real-provider-symlinks, real-provider-watch
- stream-errors, stream-explicit-fd
- watch, watch-abort-signal, watch-encoding, watch-promises,
  watch-recursive

Coverage maintained at 97.6% line / 95.2% branch / 95.3% function.

Assisted-by: Claude-Opus4.7
Signed-off-by: Matteo Collina <hello@matteocollina.com>
Adds an --experimental-vfs runtime option that gates loading of the
node:vfs builtin module, matching the pattern used by node:quic and
node:stream/iter. Without the flag, require('node:vfs') / import
'node:vfs' throw ERR_UNKNOWN_BUILTIN_MODULE.

All VFS test files are updated to pass --experimental-vfs.

Assisted-by: Claude-Opus4.7
Signed-off-by: Matteo Collina <hello@matteocollina.com>
Fixes the JS lint warnings on the VFS subsystem and tests:
primordial alphabetical ordering, em-dash → hyphen, error-codes
multiline destructuring, removal of unused JSDoc @returns, and the
test-side mustSucceed / async-iife-no-unused-result rules.

Assisted-by: Claude-Opus4.7
Signed-off-by: Matteo Collina <hello@matteocollina.com>
internal/vfs/fd.js doesn't require any internal/vfs/* modules so
there's no circular dependency to defer. Replace the getLazy wrapper
with a direct import.

Assisted-by: Claude-Opus4.7
Signed-off-by: Matteo Collina <hello@matteocollina.com>
Removes mount/unmount, virtualCwd, overlay mode, fs/module
integration sections, SEA usage, and worker-thread guidance
since none of that ships in this PR.

Assisted-by: Claude-Opus4.7
Signed-off-by: Matteo Collina <hello@matteocollina.com>
@mcollina mcollina marked this pull request as ready for review May 5, 2026 10:04
@mcollina
Copy link
Copy Markdown
Member Author

mcollina commented May 5, 2026

No integration with node:fs

The docs in this PR claim that you can call myVfs.mount('/virtual') and subsequently regular node:fs operations which read from paths beginning with /virtual will read from the VFS. That sounds like integration to me. Are the docs wrong, or am I misunderstanding what "no integration" means?

Fixed, good spot.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 5, 2026

Codecov Report

❌ Patch coverage is 96.67992% with 192 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.89%. Comparing base (9390c81) to head (117d09a).
⚠️ Report is 41 commits behind head on main.

Files with missing lines Patch % Lines
lib/internal/vfs/providers/memory.js 93.55% 62 Missing and 4 partials ⚠️
lib/internal/vfs/file_system.js 96.68% 38 Missing ⚠️
lib/internal/vfs/watcher.js 95.20% 31 Missing and 2 partials ⚠️
lib/internal/vfs/providers/real.js 96.54% 17 Missing ⚠️
lib/internal/vfs/streams.js 95.46% 16 Missing ⚠️
lib/internal/vfs/provider.js 98.38% 10 Missing ⚠️
lib/internal/vfs/file_handle.js 98.88% 7 Missing and 1 partial ⚠️
lib/internal/vfs/dir.js 98.16% 2 Missing ⚠️
lib/internal/vfs/fd.js 97.70% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #63115      +/-   ##
==========================================
+ Coverage   89.65%   89.89%   +0.24%     
==========================================
  Files         712      725      +13     
  Lines      220829   230242    +9413     
  Branches    42375    43400    +1025     
==========================================
+ Hits       197986   206982    +8996     
- Misses      14655    15065     +410     
- Partials     8188     8195       +7     
Files with missing lines Coverage Δ
lib/internal/bootstrap/realm.js 96.22% <100.00%> (+<0.01%) ⬆️
lib/internal/process/pre_execution.js 98.28% <100.00%> (-0.11%) ⬇️
lib/internal/vfs/errors.js 100.00% <100.00%> (ø)
lib/internal/vfs/stats.js 100.00% <100.00%> (ø)
lib/vfs.js 100.00% <100.00%> (ø)
src/node_builtins.cc 76.32% <100.00%> (+0.23%) ⬆️
src/node_options.cc 76.63% <100.00%> (+0.02%) ⬆️
src/node_options.h 98.00% <100.00%> (+0.01%) ⬆️
lib/internal/vfs/dir.js 98.16% <98.16%> (ø)
lib/internal/vfs/fd.js 97.70% <97.70%> (ø)
... and 7 more

... and 69 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Adds 'vfs' to the C++ cannot_be_required list so existing tests
(test-code-cache, test-process-get-builtin, test-require-resolve)
treat it like other flagged experimental modules. Adds the flag to
doc/node.1 and reorders the entry in doc/api/cli.md.

Assisted-by: Claude-Opus4.7
Signed-off-by: Matteo Collina <hello@matteocollina.com>
@mcollina mcollina added help wanted Issues that need assistance from volunteers or PRs that need help to proceed. request-ci Add this label to start a Jenkins CI on a PR. labels May 6, 2026
@github-actions github-actions Bot removed the request-ci Add this label to start a Jenkins CI on a PR. label May 6, 2026
@nodejs-github-bot
Copy link
Copy Markdown
Collaborator

The first block used persistent: false plus setTimeout to trigger the
write. The watcher's poll timer was unref'd, so on slow runners the
write timer could fire before the first poll and the change event
would be missed. Use the same await-once pattern as the other blocks
in the file with a content-length change so the size-based stat-change
detector always fires.

Assisted-by: Claude-Opus4.7
Signed-off-by: Matteo Collina <hello@matteocollina.com>
@mcollina mcollina added the request-ci Add this label to start a Jenkins CI on a PR. label May 7, 2026
@github-actions github-actions Bot removed the request-ci Add this label to start a Jenkins CI on a PR. label May 7, 2026
@nodejs-github-bot
Copy link
Copy Markdown
Collaborator

Comment thread doc/api/vfs.md
Comment on lines +15 to +16
scenarios where you need a self-contained file system without touching the
real disk.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

There are likely no disk involved :p

Suggested change
scenarios where you need a self-contained file system without touching the
real disk.
scenarios where you need a self-contained file system without touching the
actual file-system.

Comment thread doc/api/vfs.md
Comment on lines +80 to +81
A `VirtualFileSystem` wraps a [`VirtualProvider`][] and exposes an
`fs`-like API. Each instance maintains its own file tree.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
A `VirtualFileSystem` wraps a [`VirtualProvider`][] and exposes an
`fs`-like API. Each instance maintains its own file tree.
A `VirtualFileSystem` wraps a [`VirtualProvider`][] and exposes a
`node:fs`-like API. Each instance maintains its own file tree.

]);
// Modules that will only be enabled at run time.
const experimentalModuleList = new SafeSet(['ffi', 'sqlite', 'quic', 'stream/iter', 'zlib/iter']);
const experimentalModuleList = new SafeSet(['ffi', 'sqlite', 'quic', 'stream/iter', 'zlib/iter', 'vfs']);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should probably make this multiline at this point (and use ASCII order)

Suggested change
const experimentalModuleList = new SafeSet(['ffi', 'sqlite', 'quic', 'stream/iter', 'zlib/iter', 'vfs']);
const experimentalModuleList = new SafeSet([
'ffi',
'quic',
'sqlite',
'stream/iter',
'vfs',
'zlib/iter',
]);

@aduh95
Copy link
Copy Markdown
Contributor

aduh95 commented May 7, 2026

Can you add a test for #63158?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

help wanted Issues that need assistance from volunteers or PRs that need help to proceed. lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants