Skip to content

Commit 0453725

Browse files
committed
vfs: add Symbol.dispose support for automatic unmount
VirtualFileSystem now supports the Explicit Resource Management proposal. The mount() method returns the VFS instance, allowing use with the 'using' declaration for automatic cleanup when leaving scope.
1 parent b437e54 commit 0453725

File tree

3 files changed

+84
-0
lines changed

3 files changed

+84
-0
lines changed

doc/api/vfs.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ added: REPLACEME
241241
-->
242242

243243
* `prefix` {string} The path prefix where the VFS will be mounted.
244+
* Returns: {VirtualFileSystem} The VFS instance (for chaining or `using`).
244245

245246
Mounts the virtual file system at the specified path prefix. After mounting,
246247
files in the VFS can be accessed via the `fs` module using paths that start
@@ -263,6 +264,24 @@ myVfs.mount('/virtual');
263264
require('node:fs').readFileSync('/virtual/data.txt', 'utf8'); // 'Hello'
264265
```
265266

267+
The VFS supports the [Explicit Resource Management][] proposal. Use the `using`
268+
declaration to automatically unmount when leaving scope:
269+
270+
```cjs
271+
const vfs = require('node:vfs');
272+
const fs = require('node:fs');
273+
274+
{
275+
using myVfs = vfs.create();
276+
myVfs.writeFileSync('/data.txt', 'Hello');
277+
myVfs.mount('/virtual');
278+
279+
fs.readFileSync('/virtual/data.txt', 'utf8'); // 'Hello'
280+
} // VFS is automatically unmounted here
281+
282+
fs.existsSync('/virtual/data.txt'); // false - VFS is unmounted
283+
```
284+
266285
### `vfs.mounted`
267286

268287
<!-- YAML
@@ -874,5 +893,6 @@ This is particularly dangerous because:
874893
* **Limit VFS usage**: Only use VFS in controlled environments where you trust
875894
all loaded modules.
876895

896+
[Explicit Resource Management]: https://github.com/tc39/proposal-explicit-resource-management
877897
[Security considerations]: #security-considerations
878898
[Single Executable Applications]: single-executable-applications.md

lib/internal/vfs/file_system.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const {
44
ObjectFreeze,
55
Symbol,
6+
SymbolDispose,
67
} = primordials;
78

89
const {
@@ -233,6 +234,7 @@ class VirtualFileSystem {
233234
if (this[kVirtualCwdEnabled]) {
234235
this._hookProcessCwd();
235236
}
237+
return this;
236238
}
237239

238240
/**
@@ -249,6 +251,16 @@ class VirtualFileSystem {
249251
this[kVirtualCwd] = null; // Reset virtual cwd on unmount
250252
}
251253

254+
/**
255+
* Disposes of the VFS by unmounting it.
256+
* Supports the Explicit Resource Management proposal (using declaration).
257+
*/
258+
[SymbolDispose]() {
259+
if (this[kMounted]) {
260+
this.unmount();
261+
}
262+
}
263+
252264
/**
253265
* Hooks process.chdir and process.cwd to support virtual cwd.
254266
* @private

test/parallel/test-vfs-basic.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,3 +297,55 @@ const vfs = require('node:vfs');
297297
assert.strictEqual(myVfs.existsSync('/dest.txt'), true);
298298
assert.strictEqual(myVfs.readFileSync('/dest.txt', 'utf8'), 'copy me');
299299
}
300+
301+
// Test Symbol.dispose unmounts the VFS
302+
{
303+
const myVfs = vfs.create();
304+
myVfs.writeFileSync('/file.txt', 'content');
305+
myVfs.mount('/virtual');
306+
307+
assert.strictEqual(myVfs.mounted, true);
308+
309+
// Call Symbol.dispose directly
310+
myVfs[Symbol.dispose]();
311+
312+
assert.strictEqual(myVfs.mounted, false);
313+
}
314+
315+
// Test Symbol.dispose is safe to call when not mounted
316+
{
317+
const myVfs = vfs.create();
318+
myVfs.writeFileSync('/file.txt', 'content');
319+
320+
assert.strictEqual(myVfs.mounted, false);
321+
322+
// Should not throw
323+
myVfs[Symbol.dispose]();
324+
325+
assert.strictEqual(myVfs.mounted, false);
326+
}
327+
328+
// Test Symbol.dispose is safe to call multiple times
329+
{
330+
const myVfs = vfs.create();
331+
myVfs.writeFileSync('/file.txt', 'content');
332+
myVfs.mount('/virtual');
333+
334+
myVfs[Symbol.dispose]();
335+
assert.strictEqual(myVfs.mounted, false);
336+
337+
// Should not throw when called again
338+
myVfs[Symbol.dispose]();
339+
assert.strictEqual(myVfs.mounted, false);
340+
}
341+
342+
// Test mount() returns the VFS instance for chaining
343+
{
344+
const myVfs = vfs.create();
345+
myVfs.writeFileSync('/file.txt', 'content');
346+
347+
const result = myVfs.mount('/virtual');
348+
assert.strictEqual(result, myVfs);
349+
350+
myVfs.unmount();
351+
}

0 commit comments

Comments
 (0)