|
| 1 | +# Virtual File System |
| 2 | + |
| 3 | +<!--introduced_in=REPLACEME--> |
| 4 | + |
| 5 | +<!-- YAML |
| 6 | +added: REPLACEME |
| 7 | +--> |
| 8 | + |
| 9 | +> Stability: 1 - Experimental |
| 10 | +
|
| 11 | +<!-- source_link=lib/vfs.js --> |
| 12 | + |
| 13 | +The `node:vfs` module provides an in-memory virtual file system with a |
| 14 | +`node:fs`-like API. It is useful for tests, fixtures, embedded assets, and other |
| 15 | +scenarios where you need a self-contained file system without touching the |
| 16 | +actual file-system. |
| 17 | + |
| 18 | +To access it: |
| 19 | + |
| 20 | +```mjs |
| 21 | +import vfs from 'node:vfs'; |
| 22 | +``` |
| 23 | + |
| 24 | +```cjs |
| 25 | +const vfs = require('node:vfs'); |
| 26 | +``` |
| 27 | + |
| 28 | +This module is only available under the `node:` scheme, and only when Node.js |
| 29 | +is started with the `--experimental-vfs` flag. |
| 30 | + |
| 31 | +## Basic usage |
| 32 | + |
| 33 | +```cjs |
| 34 | +const vfs = require('node:vfs'); |
| 35 | + |
| 36 | +const myVfs = vfs.create(); |
| 37 | +myVfs.mkdirSync('/dir', { recursive: true }); |
| 38 | +myVfs.writeFileSync('/dir/hello.txt', 'Hello, VFS!'); |
| 39 | + |
| 40 | +console.log(myVfs.readFileSync('/dir/hello.txt', 'utf8')); // 'Hello, VFS!' |
| 41 | +``` |
| 42 | + |
| 43 | +`vfs.create()` returns a [`VirtualFileSystem`][] instance backed by a |
| 44 | +[`MemoryProvider`][] by default. The instance exposes synchronous, |
| 45 | +callback-based, and promise-based file system methods that mirror the |
| 46 | +shape of the [`node:fs`][] API. All paths are POSIX-style and absolute |
| 47 | +(starting with `/`). |
| 48 | + |
| 49 | +## `vfs.create([provider][, options])` |
| 50 | + |
| 51 | +<!-- YAML |
| 52 | +added: REPLACEME |
| 53 | +--> |
| 54 | + |
| 55 | +* `provider` {VirtualProvider} The provider to use. **Default:** |
| 56 | + `new MemoryProvider()`. |
| 57 | +* `options` {Object} |
| 58 | + * `emitExperimentalWarning` {boolean} Whether to emit the experimental |
| 59 | + warning when the instance is created. **Default:** `true`. |
| 60 | +* Returns: {VirtualFileSystem} |
| 61 | + |
| 62 | +Convenience factory equivalent to `new VirtualFileSystem(provider, options)`. |
| 63 | + |
| 64 | +```cjs |
| 65 | +const vfs = require('node:vfs'); |
| 66 | + |
| 67 | +// Default in-memory provider |
| 68 | +const memoryVfs = vfs.create(); |
| 69 | + |
| 70 | +// Explicit provider |
| 71 | +const realVfs = vfs.create(new vfs.RealFSProvider('/tmp/sandbox')); |
| 72 | +``` |
| 73 | + |
| 74 | +## Class: `VirtualFileSystem` |
| 75 | + |
| 76 | +<!-- YAML |
| 77 | +added: REPLACEME |
| 78 | +--> |
| 79 | + |
| 80 | +A `VirtualFileSystem` wraps a [`VirtualProvider`][] and exposes a |
| 81 | +`node:fs`-like API. Each instance maintains its own file tree. |
| 82 | + |
| 83 | +### `new VirtualFileSystem([provider][, options])` |
| 84 | + |
| 85 | +<!-- YAML |
| 86 | +added: REPLACEME |
| 87 | +--> |
| 88 | + |
| 89 | +* `provider` {VirtualProvider} The provider to use. **Default:** |
| 90 | + `new MemoryProvider()`. |
| 91 | +* `options` {Object} |
| 92 | + * `emitExperimentalWarning` {boolean} Whether to emit the experimental |
| 93 | + warning. **Default:** `true`. |
| 94 | + |
| 95 | +### `vfs.provider` |
| 96 | + |
| 97 | +<!-- YAML |
| 98 | +added: REPLACEME |
| 99 | +--> |
| 100 | + |
| 101 | +* {VirtualProvider} |
| 102 | + |
| 103 | +The provider backing this VFS instance. |
| 104 | + |
| 105 | +### `vfs.readonly` |
| 106 | + |
| 107 | +<!-- YAML |
| 108 | +added: REPLACEME |
| 109 | +--> |
| 110 | + |
| 111 | +* {boolean} |
| 112 | + |
| 113 | +`true` when the underlying provider is read-only. |
| 114 | + |
| 115 | +### APIs |
| 116 | + |
| 117 | +`VirtualFileSystem` implements the following methods, with the same |
| 118 | +signatures as their [`node:fs`][] counterparts: |
| 119 | + |
| 120 | +#### Synchronous API |
| 121 | + |
| 122 | +* `existsSync(path)` |
| 123 | +* `statSync(path[, options])` |
| 124 | +* `lstatSync(path[, options])` |
| 125 | +* `readFileSync(path[, options])` |
| 126 | +* `writeFileSync(path, data[, options])` |
| 127 | +* `appendFileSync(path, data[, options])` |
| 128 | +* `readdirSync(path[, options])` |
| 129 | +* `mkdirSync(path[, options])` |
| 130 | +* `rmdirSync(path)` |
| 131 | +* `unlinkSync(path)` |
| 132 | +* `renameSync(oldPath, newPath)` |
| 133 | +* `copyFileSync(src, dest[, mode])` |
| 134 | +* `realpathSync(path[, options])` |
| 135 | +* `readlinkSync(path[, options])` |
| 136 | +* `symlinkSync(target, path[, type])` |
| 137 | +* `accessSync(path[, mode])` |
| 138 | +* `rmSync(path[, options])` |
| 139 | +* `truncateSync(path[, len])` |
| 140 | +* `ftruncateSync(fd[, len])` |
| 141 | +* `linkSync(existingPath, newPath)` |
| 142 | +* `chmodSync(path, mode)` |
| 143 | +* `chownSync(path, uid, gid)` |
| 144 | +* `utimesSync(path, atime, mtime)` |
| 145 | +* `lutimesSync(path, atime, mtime)` |
| 146 | +* `mkdtempSync(prefix)` |
| 147 | +* `opendirSync(path[, options])` |
| 148 | +* `openAsBlob(path[, options])` |
| 149 | +* File-descriptor ops: `openSync`, `closeSync`, `readSync`, `writeSync`, |
| 150 | + `fstatSync` |
| 151 | +* Streams: `createReadStream`, `createWriteStream` |
| 152 | +* Watchers: `watch`, `watchFile`, `unwatchFile` |
| 153 | + |
| 154 | +#### Callback API |
| 155 | + |
| 156 | +`readFile`, `writeFile`, `stat`, `lstat`, `readdir`, `realpath`, `readlink`, |
| 157 | +`access`, `open`, `close`, `read`, `write`, `rm`, `fstat`, `truncate`, |
| 158 | +`ftruncate`, `link`, `mkdtemp`, `opendir`. Each takes a Node.js-style |
| 159 | +callback `(err, ...result) => {}`. |
| 160 | + |
| 161 | +#### Promise API |
| 162 | + |
| 163 | +`vfs.promises` exposes the promise-based variants: |
| 164 | + |
| 165 | +```cjs |
| 166 | +const vfs = require('node:vfs'); |
| 167 | + |
| 168 | +async function example() { |
| 169 | + const myVfs = vfs.create(); |
| 170 | + await myVfs.promises.writeFile('/file.txt', 'hello'); |
| 171 | + const data = await myVfs.promises.readFile('/file.txt', 'utf8'); |
| 172 | + return data; |
| 173 | +} |
| 174 | +example(); |
| 175 | +``` |
| 176 | + |
| 177 | +The promise namespace mirrors `fs.promises` and includes `readFile`, |
| 178 | +`writeFile`, `appendFile`, `stat`, `lstat`, `readdir`, `mkdir`, `rmdir`, |
| 179 | +`unlink`, `rename`, `copyFile`, `realpath`, `readlink`, `symlink`, |
| 180 | +`access`, `rm`, `truncate`, `link`, `mkdtemp`, `chmod`, `chown`, `lchown`, |
| 181 | +`utimes`, `lutimes`, `open`, `lchmod`, and `watch`. |
| 182 | + |
| 183 | +## Class: `VirtualProvider` |
| 184 | + |
| 185 | +<!-- YAML |
| 186 | +added: REPLACEME |
| 187 | +--> |
| 188 | + |
| 189 | +The base class for all VFS providers. Subclasses implement the essential |
| 190 | +primitives (`open`, `stat`, `readdir`, `mkdir`, `rmdir`, `unlink`, |
| 191 | +`rename`, ...) and inherit default implementations of the derived |
| 192 | +The base class for all VFS providers. Subclasses implement the essential |
| 193 | +primitives (such as `open`, `stat`, `readdir`, `mkdir`, `rmdir`, `unlink`, |
| 194 | +`rename`, etc.) and inherit default implementations of the derived |
| 195 | +methods (such as `readFile`, `writeFile`, `exists`, `copyFile`, `access`, etc.). |
| 196 | + |
| 197 | +### Capability flags |
| 198 | + |
| 199 | +* `provider.readonly` {boolean} **Default:** `false`. |
| 200 | +* `provider.supportsSymlinks` {boolean} **Default:** `false`. |
| 201 | +* `provider.supportsWatch` {boolean} **Default:** `false`. |
| 202 | + |
| 203 | +### Creating custom providers |
| 204 | + |
| 205 | +```cjs |
| 206 | +const { VirtualProvider } = require('node:vfs'); |
| 207 | + |
| 208 | +class StaticProvider extends VirtualProvider { |
| 209 | + get readonly() { return true; } |
| 210 | + |
| 211 | + statSync(path) { /* ... */ } |
| 212 | + openSync(path, flags) { /* ... */ } |
| 213 | + readdirSync(path, options) { /* ... */ } |
| 214 | + // ... |
| 215 | +} |
| 216 | +``` |
| 217 | + |
| 218 | +The base class throws `ERR_METHOD_NOT_IMPLEMENTED` for any primitive |
| 219 | +that has not been overridden, and rejects writes from a `readonly` |
| 220 | +provider with `EROFS`. |
| 221 | + |
| 222 | +## Class: `MemoryProvider` |
| 223 | + |
| 224 | +<!-- YAML |
| 225 | +added: REPLACEME |
| 226 | +--> |
| 227 | + |
| 228 | +The default in-memory provider. Stores files, directories, and symbolic |
| 229 | +links in a `Map`-backed tree, supports symlinks (`supportsSymlinks === |
| 230 | +true`), and supports watching (`supportsWatch === true`). |
| 231 | + |
| 232 | +### `memoryProvider.setReadOnly()` |
| 233 | + |
| 234 | +<!-- YAML |
| 235 | +added: REPLACEME |
| 236 | +--> |
| 237 | + |
| 238 | +Locks the provider into read-only mode. Subsequent writes through any |
| 239 | +[`VirtualFileSystem`][] using this provider throw `EROFS`. There is no |
| 240 | +way to revert the provider to writable. |
| 241 | + |
| 242 | +```cjs |
| 243 | +const vfs = require('node:vfs'); |
| 244 | + |
| 245 | +const provider = new vfs.MemoryProvider(); |
| 246 | +const myVfs = vfs.create(provider); |
| 247 | +myVfs.writeFileSync('/seed.txt', 'initial'); |
| 248 | + |
| 249 | +provider.setReadOnly(); |
| 250 | + |
| 251 | +myVfs.writeFileSync('/x.txt', 'fail'); // throws EROFS |
| 252 | +``` |
| 253 | + |
| 254 | +## Class: `RealFSProvider` |
| 255 | + |
| 256 | +<!-- YAML |
| 257 | +added: REPLACEME |
| 258 | +--> |
| 259 | + |
| 260 | +A provider that wraps a directory (i.e. one on the actual file system) and exposes its |
| 261 | +contents through the VFS API. All VFS paths are resolved relative to |
| 262 | +the root and verified to stay inside it; symbolic links resolving |
| 263 | +outside the root are rejected. |
| 264 | + |
| 265 | +### `new RealFSProvider(rootPath)` |
| 266 | + |
| 267 | +<!-- YAML |
| 268 | +added: REPLACEME |
| 269 | +--> |
| 270 | + |
| 271 | +* `rootPath` {string} The absolute file-system path to use as the root. |
| 272 | + Must be a non-empty string. |
| 273 | + |
| 274 | +```cjs |
| 275 | +const vfs = require('node:vfs'); |
| 276 | + |
| 277 | +const realVfs = vfs.create(new vfs.RealFSProvider('/tmp/sandbox')); |
| 278 | +realVfs.writeFileSync('/file.txt', 'hello'); // writes /tmp/sandbox/file.txt |
| 279 | +``` |
| 280 | + |
| 281 | +### `realFSProvider.rootPath` |
| 282 | + |
| 283 | +<!-- YAML |
| 284 | +added: REPLACEME |
| 285 | +--> |
| 286 | + |
| 287 | +* {string} |
| 288 | + |
| 289 | +The resolved absolute path used as the root. |
| 290 | + |
| 291 | +## Implementation details |
| 292 | + |
| 293 | +### `Stats` objects |
| 294 | + |
| 295 | +VFS `Stats` objects are real instances of [`fs.Stats`][] (or |
| 296 | +[`fs.BigIntStats`][] when `{ bigint: true }` is requested). Their |
| 297 | +fields use synthetic but stable values: |
| 298 | + |
| 299 | +* `dev` is `4085` (the VFS device id). |
| 300 | +* `ino` is monotonically increasing per process. |
| 301 | +* `blksize` is `4096`. |
| 302 | +* `blocks` is `Math.ceil(size / 512)`. |
| 303 | +* Times default to the moment the entry was created/last modified. |
| 304 | + |
| 305 | +[`MemoryProvider`]: #class-memoryprovider |
| 306 | +[`VirtualFileSystem`]: #class-virtualfilesystem |
| 307 | +[`VirtualProvider`]: #class-virtualprovider |
| 308 | +[`fs.BigIntStats`]: fs.md#class-fsbigintstats |
| 309 | +[`fs.Stats`]: fs.md#class-fsstats |
| 310 | +[`node:fs`]: fs.md |
0 commit comments