From 576a7a42be514640e6dc6cb584efc987977c04e8 Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Tue, 1 Sep 2026 18:02:24 -0700 Subject: [PATCH] vfs: return FileHandle from fs.promises.open Wrap mounted virtual file descriptors in the public FileHandle interface while delegating operations to the underlying provider handle. Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com> Assisted-by: codex:gpt-5.6-sol --- lib/internal/vfs/fd.js | 39 +++++++++++++++++++++++++++ lib/internal/vfs/setup.js | 5 ++-- test/parallel/test-vfs-fs-promises.js | 3 +++ 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/lib/internal/vfs/fd.js b/lib/internal/vfs/fd.js index bd36ad218f48..1d1ffaabc78d 100644 --- a/lib/internal/vfs/fd.js +++ b/lib/internal/vfs/fd.js @@ -1,6 +1,8 @@ 'use strict'; const { + FunctionPrototypeBind, + ObjectDefineProperty, SafeMap, Symbol, } = primordials; @@ -46,6 +48,42 @@ class VirtualFD { get entry() { return this[kEntry]; } + + getAsyncId() { + return this[kFd]; + } + + async close() { + await this[kEntry].close(); + closeVirtualFd(this[kFd]); + } + + closeSync() { + this[kEntry].closeSync(); + closeVirtualFd(this[kFd]); + } +} + +const vfsFileHandleMethods = [ + 'appendFile', 'chmod', 'chown', 'datasync', 'sync', 'read', 'readv', + 'readFile', 'stat', 'truncate', 'utimes', 'write', 'writev', 'writeFile', +]; +let FileHandle; + +function createVfsFileHandle(vfd) { + FileHandle ??= require('internal/fs/promises').FileHandle; + const fileHandle = new FileHandle(vfd); + const entry = vfd.entry; + for (let i = 0; i < vfsFileHandleMethods.length; i++) { + const method = vfsFileHandleMethods[i]; + ObjectDefineProperty(fileHandle, method, { + __proto__: null, + configurable: true, + value: FunctionPrototypeBind(entry[method], entry), + writable: true, + }); + } + return fileHandle; } /** @@ -81,6 +119,7 @@ function closeVirtualFd(fd) { module.exports = { VFS_FD_MASK, VirtualFD, + createVfsFileHandle, openVirtualFd, getVirtualFd, closeVirtualFd, diff --git a/lib/internal/vfs/setup.js b/lib/internal/vfs/setup.js index 979990d1a855..a8bf9712ea41 100644 --- a/lib/internal/vfs/setup.js +++ b/lib/internal/vfs/setup.js @@ -30,7 +30,7 @@ const { getLayerIdFromPath, getNormalizedVfsRoot, } = require('internal/vfs/router'); -const { getVirtualFd, closeVirtualFd } = require('internal/vfs/fd'); +const { getVirtualFd, closeVirtualFd, createVfsFileHandle } = require('internal/vfs/fd'); const { assertEncoding, setVfsHandlers } = require('internal/fs/utils'); const permission = require('internal/process/permission'); const { getOptionValue } = require('internal/options'); @@ -764,8 +764,7 @@ function createVfsHandlers() { const r = findVFSForPath(pathStr); if (r !== null) { const fd = r.vfs.openSync(r.path, flags, mode); - const vfd = getVirtualFd(fd); - return PromiseResolve(vfd.entry); + return PromiseResolve(createVfsFileHandle(getVirtualFd(fd))); } } return undefined; diff --git a/test/parallel/test-vfs-fs-promises.js b/test/parallel/test-vfs-fs-promises.js index a5761d4ca5dd..af39fdf92b42 100644 --- a/test/parallel/test-vfs-fs-promises.js +++ b/test/parallel/test-vfs-fs-promises.js @@ -86,6 +86,9 @@ const vfs = require('node:vfs'); // FileHandle via fsp.open const handle = await fsp.open(p('src/hello.txt'), 'r'); + assert.strictEqual(handle.constructor.name, 'FileHandle'); + assert.strictEqual(typeof handle.fd, 'number'); + assert.strictEqual(typeof handle.createReadStream, 'function'); assert.strictEqual(await handle.readFile('utf8'), 'hello'); await handle.close();