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> PR-URL: https://github.com/nodejs/node/pull/63115 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Paolo Insogna <paolo@cowtech.it> Reviewed-By: Robert Nagy <ronagy@icloud.com> Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
310 lines
7.9 KiB
Markdown
310 lines
7.9 KiB
Markdown
# Virtual File System
|
|
|
|
<!--introduced_in=REPLACEME-->
|
|
|
|
<!-- YAML
|
|
added: REPLACEME
|
|
-->
|
|
|
|
> Stability: 1 - Experimental
|
|
|
|
<!-- source_link=lib/vfs.js -->
|
|
|
|
The `node:vfs` module provides an in-memory virtual file system with a
|
|
`node:fs`-like API. It is useful for tests, fixtures, embedded assets, and other
|
|
scenarios where you need a self-contained file system without touching the
|
|
actual file-system.
|
|
|
|
To access it:
|
|
|
|
```mjs
|
|
import vfs from 'node:vfs';
|
|
```
|
|
|
|
```cjs
|
|
const vfs = require('node:vfs');
|
|
```
|
|
|
|
This module is only available under the `node:` scheme, and only when Node.js
|
|
is started with the `--experimental-vfs` flag.
|
|
|
|
## Basic usage
|
|
|
|
```cjs
|
|
const vfs = require('node:vfs');
|
|
|
|
const myVfs = vfs.create();
|
|
myVfs.mkdirSync('/dir', { recursive: true });
|
|
myVfs.writeFileSync('/dir/hello.txt', 'Hello, VFS!');
|
|
|
|
console.log(myVfs.readFileSync('/dir/hello.txt', 'utf8')); // 'Hello, VFS!'
|
|
```
|
|
|
|
`vfs.create()` returns a [`VirtualFileSystem`][] instance backed by a
|
|
[`MemoryProvider`][] by default. The instance exposes synchronous,
|
|
callback-based, and promise-based file system methods that mirror the
|
|
shape of the [`node:fs`][] API. All paths are POSIX-style and absolute
|
|
(starting with `/`).
|
|
|
|
## `vfs.create([provider][, options])`
|
|
|
|
<!-- YAML
|
|
added: REPLACEME
|
|
-->
|
|
|
|
* `provider` {VirtualProvider} The provider to use. **Default:**
|
|
`new MemoryProvider()`.
|
|
* `options` {Object}
|
|
* `emitExperimentalWarning` {boolean} Whether to emit the experimental
|
|
warning when the instance is created. **Default:** `true`.
|
|
* Returns: {VirtualFileSystem}
|
|
|
|
Convenience factory equivalent to `new VirtualFileSystem(provider, options)`.
|
|
|
|
```cjs
|
|
const vfs = require('node:vfs');
|
|
|
|
// Default in-memory provider
|
|
const memoryVfs = vfs.create();
|
|
|
|
// Explicit provider
|
|
const realVfs = vfs.create(new vfs.RealFSProvider('/tmp/sandbox'));
|
|
```
|
|
|
|
## Class: `VirtualFileSystem`
|
|
|
|
<!-- YAML
|
|
added: REPLACEME
|
|
-->
|
|
|
|
A `VirtualFileSystem` wraps a [`VirtualProvider`][] and exposes a
|
|
`node:fs`-like API. Each instance maintains its own file tree.
|
|
|
|
### `new VirtualFileSystem([provider][, options])`
|
|
|
|
<!-- YAML
|
|
added: REPLACEME
|
|
-->
|
|
|
|
* `provider` {VirtualProvider} The provider to use. **Default:**
|
|
`new MemoryProvider()`.
|
|
* `options` {Object}
|
|
* `emitExperimentalWarning` {boolean} Whether to emit the experimental
|
|
warning. **Default:** `true`.
|
|
|
|
### `vfs.provider`
|
|
|
|
<!-- YAML
|
|
added: REPLACEME
|
|
-->
|
|
|
|
* {VirtualProvider}
|
|
|
|
The provider backing this VFS instance.
|
|
|
|
### `vfs.readonly`
|
|
|
|
<!-- YAML
|
|
added: REPLACEME
|
|
-->
|
|
|
|
* {boolean}
|
|
|
|
`true` when the underlying provider is read-only.
|
|
|
|
### APIs
|
|
|
|
`VirtualFileSystem` implements the following methods, with the same
|
|
signatures as their [`node:fs`][] counterparts:
|
|
|
|
#### Synchronous API
|
|
|
|
* `existsSync(path)`
|
|
* `statSync(path[, options])`
|
|
* `lstatSync(path[, options])`
|
|
* `readFileSync(path[, options])`
|
|
* `writeFileSync(path, data[, options])`
|
|
* `appendFileSync(path, data[, options])`
|
|
* `readdirSync(path[, options])`
|
|
* `mkdirSync(path[, options])`
|
|
* `rmdirSync(path)`
|
|
* `unlinkSync(path)`
|
|
* `renameSync(oldPath, newPath)`
|
|
* `copyFileSync(src, dest[, mode])`
|
|
* `realpathSync(path[, options])`
|
|
* `readlinkSync(path[, options])`
|
|
* `symlinkSync(target, path[, type])`
|
|
* `accessSync(path[, mode])`
|
|
* `rmSync(path[, options])`
|
|
* `truncateSync(path[, len])`
|
|
* `ftruncateSync(fd[, len])`
|
|
* `linkSync(existingPath, newPath)`
|
|
* `chmodSync(path, mode)`
|
|
* `chownSync(path, uid, gid)`
|
|
* `utimesSync(path, atime, mtime)`
|
|
* `lutimesSync(path, atime, mtime)`
|
|
* `mkdtempSync(prefix)`
|
|
* `opendirSync(path[, options])`
|
|
* `openAsBlob(path[, options])`
|
|
* File-descriptor ops: `openSync`, `closeSync`, `readSync`, `writeSync`,
|
|
`fstatSync`
|
|
* Streams: `createReadStream`, `createWriteStream`
|
|
* Watchers: `watch`, `watchFile`, `unwatchFile`
|
|
|
|
#### Callback API
|
|
|
|
`readFile`, `writeFile`, `stat`, `lstat`, `readdir`, `realpath`, `readlink`,
|
|
`access`, `open`, `close`, `read`, `write`, `rm`, `fstat`, `truncate`,
|
|
`ftruncate`, `link`, `mkdtemp`, `opendir`. Each takes a Node.js-style
|
|
callback `(err, ...result) => {}`.
|
|
|
|
#### Promise API
|
|
|
|
`vfs.promises` exposes the promise-based variants:
|
|
|
|
```cjs
|
|
const vfs = require('node:vfs');
|
|
|
|
async function example() {
|
|
const myVfs = vfs.create();
|
|
await myVfs.promises.writeFile('/file.txt', 'hello');
|
|
const data = await myVfs.promises.readFile('/file.txt', 'utf8');
|
|
return data;
|
|
}
|
|
example();
|
|
```
|
|
|
|
The promise namespace mirrors `fs.promises` and includes `readFile`,
|
|
`writeFile`, `appendFile`, `stat`, `lstat`, `readdir`, `mkdir`, `rmdir`,
|
|
`unlink`, `rename`, `copyFile`, `realpath`, `readlink`, `symlink`,
|
|
`access`, `rm`, `truncate`, `link`, `mkdtemp`, `chmod`, `chown`, `lchown`,
|
|
`utimes`, `lutimes`, `open`, `lchmod`, and `watch`.
|
|
|
|
## Class: `VirtualProvider`
|
|
|
|
<!-- YAML
|
|
added: REPLACEME
|
|
-->
|
|
|
|
The base class for all VFS providers. Subclasses implement the essential
|
|
primitives (`open`, `stat`, `readdir`, `mkdir`, `rmdir`, `unlink`,
|
|
`rename`, ...) and inherit default implementations of the derived
|
|
The base class for all VFS providers. Subclasses implement the essential
|
|
primitives (such as `open`, `stat`, `readdir`, `mkdir`, `rmdir`, `unlink`,
|
|
`rename`, etc.) and inherit default implementations of the derived
|
|
methods (such as `readFile`, `writeFile`, `exists`, `copyFile`, `access`, etc.).
|
|
|
|
### Capability flags
|
|
|
|
* `provider.readonly` {boolean} **Default:** `false`.
|
|
* `provider.supportsSymlinks` {boolean} **Default:** `false`.
|
|
* `provider.supportsWatch` {boolean} **Default:** `false`.
|
|
|
|
### Creating custom providers
|
|
|
|
```cjs
|
|
const { VirtualProvider } = require('node:vfs');
|
|
|
|
class StaticProvider extends VirtualProvider {
|
|
get readonly() { return true; }
|
|
|
|
statSync(path) { /* ... */ }
|
|
openSync(path, flags) { /* ... */ }
|
|
readdirSync(path, options) { /* ... */ }
|
|
// ...
|
|
}
|
|
```
|
|
|
|
The base class throws `ERR_METHOD_NOT_IMPLEMENTED` for any primitive
|
|
that has not been overridden, and rejects writes from a `readonly`
|
|
provider with `EROFS`.
|
|
|
|
## Class: `MemoryProvider`
|
|
|
|
<!-- YAML
|
|
added: REPLACEME
|
|
-->
|
|
|
|
The default in-memory provider. Stores files, directories, and symbolic
|
|
links in a `Map`-backed tree, supports symlinks (`supportsSymlinks ===
|
|
true`), and supports watching (`supportsWatch === true`).
|
|
|
|
### `memoryProvider.setReadOnly()`
|
|
|
|
<!-- YAML
|
|
added: REPLACEME
|
|
-->
|
|
|
|
Locks the provider into read-only mode. Subsequent writes through any
|
|
[`VirtualFileSystem`][] using this provider throw `EROFS`. There is no
|
|
way to revert the provider to writable.
|
|
|
|
```cjs
|
|
const vfs = require('node:vfs');
|
|
|
|
const provider = new vfs.MemoryProvider();
|
|
const myVfs = vfs.create(provider);
|
|
myVfs.writeFileSync('/seed.txt', 'initial');
|
|
|
|
provider.setReadOnly();
|
|
|
|
myVfs.writeFileSync('/x.txt', 'fail'); // throws EROFS
|
|
```
|
|
|
|
## Class: `RealFSProvider`
|
|
|
|
<!-- YAML
|
|
added: REPLACEME
|
|
-->
|
|
|
|
A provider that wraps a directory (i.e. one on the actual file system) and exposes its
|
|
contents through the VFS API. All VFS paths are resolved relative to
|
|
the root and verified to stay inside it; symbolic links resolving
|
|
outside the root are rejected.
|
|
|
|
### `new RealFSProvider(rootPath)`
|
|
|
|
<!-- YAML
|
|
added: REPLACEME
|
|
-->
|
|
|
|
* `rootPath` {string} The absolute file-system path to use as the root.
|
|
Must be a non-empty string.
|
|
|
|
```cjs
|
|
const vfs = require('node:vfs');
|
|
|
|
const realVfs = vfs.create(new vfs.RealFSProvider('/tmp/sandbox'));
|
|
realVfs.writeFileSync('/file.txt', 'hello'); // writes /tmp/sandbox/file.txt
|
|
```
|
|
|
|
### `realFSProvider.rootPath`
|
|
|
|
<!-- YAML
|
|
added: REPLACEME
|
|
-->
|
|
|
|
* {string}
|
|
|
|
The resolved absolute path used as the root.
|
|
|
|
## Implementation details
|
|
|
|
### `Stats` objects
|
|
|
|
VFS `Stats` objects are real instances of [`fs.Stats`][] (or
|
|
[`fs.BigIntStats`][] when `{ bigint: true }` is requested). Their
|
|
fields use synthetic but stable values:
|
|
|
|
* `dev` is `4085` (the VFS device id).
|
|
* `ino` is monotonically increasing per process.
|
|
* `blksize` is `4096`.
|
|
* `blocks` is `Math.ceil(size / 512)`.
|
|
* Times default to the moment the entry was created/last modified.
|
|
|
|
[`MemoryProvider`]: #class-memoryprovider
|
|
[`VirtualFileSystem`]: #class-virtualfilesystem
|
|
[`VirtualProvider`]: #class-virtualprovider
|
|
[`fs.BigIntStats`]: fs.md#class-fsbigintstats
|
|
[`fs.Stats`]: fs.md#class-fsstats
|
|
[`node:fs`]: fs.md
|