From 1174c945c74de60a4c348ce7c81821d7fb793518 Mon Sep 17 00:00:00 2001 From: LiviaMedeiros Date: Fri, 22 Sep 2023 15:29:36 +0800 Subject: [PATCH 1/6] fs: add `fs.openAsBlobSync` and `fsPromises.openAsBlob` make `fs.openAsBlob` callback-based --- lib/fs.js | 44 +++++++++++++++++++++++++++++++------ lib/internal/fs/promises.js | 10 +++++++++ 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/lib/fs.js b/lib/fs.js index 60594afe9809ad..f2ed02d04e182e 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -33,7 +33,6 @@ const { ObjectDefineProperties, ObjectDefineProperty, Promise, - PromiseResolve, ReflectApply, SafeMap, SafeSet, @@ -572,17 +571,47 @@ function openSync(path, flags, mode) { * @param {{ * type?: string; * }} [options] - * @returns {Promise} + * @param {( + * err?: Error, + * blob?: Blob + * ) => any} callback + * @returns {void} + */ +function openAsBlob(path, options, callback) { + if (callback === undefined) { + options = kEmptyObject; + callback = options; + } else { + options ??= kEmptyObject; + } + + validateObject(options, 'options'); + const type = options.type || ''; + validateString(type, 'options.type'); + + let blob; + let cbError = null; + try { + blob = createBlobFromFilePath(pathModule.toNamespacedPath(path), { type }); + } catch (err) { + cbError = err; + } + callback(cbError, blob); +} + +/** + * @param {string | Buffer | URL } path + * @param {{ + * type?: string; + * }} [options] + * @returns {Blob} */ -function openAsBlob(path, options = kEmptyObject) { +function openAsBlobSync(path, options = kEmptyObject) { validateObject(options, 'options'); const type = options.type || ''; validateString(type, 'options.type'); - // The underlying implementation here returns the Blob synchronously for now. - // To give ourselves flexibility to maybe return the Blob asynchronously, - // this API returns a Promise. path = getValidatedPath(path); - return PromiseResolve(createBlobFromFilePath(pathModule.toNamespacedPath(path), { type })); + return createBlobFromFilePath(pathModule.toNamespacedPath(path), { type }); } /** @@ -3093,6 +3122,7 @@ module.exports = fs = { open, openSync, openAsBlob, + openAsBlobSync, readdir, readdirSync, read, diff --git a/lib/internal/fs/promises.js b/lib/internal/fs/promises.js index bc506b9be82fbc..426b0ae76c2dd9 100644 --- a/lib/internal/fs/promises.js +++ b/lib/internal/fs/promises.js @@ -95,6 +95,7 @@ const { kFSWatchStart, watch } = require('internal/fs/watchers'); const nonNativeWatcher = require('internal/fs/recursive_watch'); const { isIterable } = require('internal/streams/utils'); const assert = require('internal/assert'); +const { createBlobFromFilePath } = require('internal/blob'); const kHandle = Symbol('kHandle'); const kFd = Symbol('kFd'); @@ -756,6 +757,14 @@ async function fsync(handle) { return binding.fsync(handle.fd, kUsePromises); } +async function openAsBlob(path, options = kEmptyObject) { + validateObject(options, 'options'); + const type = options.type || ''; + validateString(type, 'options.type'); + path = getValidatedPath(path); + return createBlobFromFilePath(pathModule.toNamespacedPath(path), { type }); +} + async function mkdir(path, options) { if (typeof options === 'number' || typeof options === 'string') { options = { mode: options }; @@ -1075,6 +1084,7 @@ module.exports = { opendir: promisify(opendir), rename, truncate, + openAsBlob, rm, rmdir, mkdir, From a48718ba028488776830c350c5ff6d84602ba197 Mon Sep 17 00:00:00 2001 From: LiviaMedeiros Date: Fri, 22 Sep 2023 16:16:13 +0800 Subject: [PATCH 2/6] squash: adjust tests --- lib/fs.js | 5 +++++ test/fixtures/permission/fs-read.js | 2 +- test/parallel/test-blob-file-backed.js | 2 +- test/parallel/test-permission-fs-supported.js | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/fs.js b/lib/fs.js index f2ed02d04e182e..09db97a291867d 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -588,12 +588,17 @@ function openAsBlob(path, options, callback) { validateObject(options, 'options'); const type = options.type || ''; validateString(type, 'options.type'); + validateFunction(callback, 'cb'); let blob; let cbError = null; try { blob = createBlobFromFilePath(pathModule.toNamespacedPath(path), { type }); } catch (err) { + // Permission errors should be thrown instead of being passed to callback + if (err.code === 'ERR_ACCESS_DENIED') { + throw err; + } cbError = err; } callback(cbError, blob); diff --git a/test/fixtures/permission/fs-read.js b/test/fixtures/permission/fs-read.js index 37ab086ebb2895..d55fc9f3b54cf8 100644 --- a/test/fixtures/permission/fs-read.js +++ b/test/fixtures/permission/fs-read.js @@ -253,7 +253,7 @@ const regularFile = __filename; // fs.openAsBlob { assert.throws(() => { - fs.openAsBlob(blockedFile); + fs.openAsBlob(blockedFile, () => {}); }, common.expectsError({ code: 'ERR_ACCESS_DENIED', permission: 'FileSystemRead', diff --git a/test/parallel/test-blob-file-backed.js b/test/parallel/test-blob-file-backed.js index 0532bda07b6e80..7eddadb63aac79 100644 --- a/test/parallel/test-blob-file-backed.js +++ b/test/parallel/test-blob-file-backed.js @@ -9,7 +9,7 @@ const { const { TextDecoder } = require('util'); const { writeFileSync, - openAsBlob, + promises: { openAsBlob }, } = require('fs'); const { diff --git a/test/parallel/test-permission-fs-supported.js b/test/parallel/test-permission-fs-supported.js index 1062117798b800..b1a946ed4f2a66 100644 --- a/test/parallel/test-permission-fs-supported.js +++ b/test/parallel/test-permission-fs-supported.js @@ -36,7 +36,7 @@ const supportedApis = [ ...syncAndAsyncAPI('mkdir'), ...syncAndAsyncAPI('mkdtemp'), ...syncAndAsyncAPI('open'), - 'openAsBlob', + ...syncAndAsyncAPI('openAsBlob'), ...syncAndAsyncAPI('mkdtemp'), ...syncAndAsyncAPI('readdir'), ...syncAndAsyncAPI('readFile'), From fe64e583b046cc18d1eec2108a946350d946d884 Mon Sep 17 00:00:00 2001 From: LiviaMedeiros Date: Sat, 23 Sep 2023 18:25:06 +0800 Subject: [PATCH 3/6] squash: adjust docs --- doc/api/fs.md | 91 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 65 insertions(+), 26 deletions(-) diff --git a/doc/api/fs.md b/doc/api/fs.md index 07d5ec9a439835..cbef98d6233306 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -1236,6 +1236,44 @@ by [Naming Files, Paths, and Namespaces][]. Under NTFS, if the filename contains a colon, Node.js will open a file system stream, as described by [this MSDN page][MSDN-Using-Streams]. +### `fsPromises.openAsBlob(path[, options])` + + + +> Stability: 1 - Experimental + +* `path` {string|Buffer|URL} +* `options` {Object} + * `type` {string} An optional mime type for the blob. +* Return: {Promise} containing {Blob} + +Returns a {Blob} whose data is backed by the given file. + +The file must not be modified after the {Blob} is created. Any modifications +will cause reading the {Blob} data to fail with a `DOMException` error. +Synchronous stat operations on the file when the `Blob` is created, and before +each read in order to detect whether the file data has been modified on disk. + +```mjs +import { openAsBlob } from 'node:fs/promises'; + +const blob = await openAsBlob('the.file.txt'); +const ab = await blob.arrayBuffer(); +blob.stream(); +``` + +```cjs +const { openAsBlob } = require('node:fs/promises'); + +(async () => { + const blob = await openAsBlob('the.file.txt'); + const ab = await blob.arrayBuffer(); + blob.stream(); +})(); +``` + ### `fsPromises.opendir(path[, options])` > Stability: 1 - Experimental @@ -3412,32 +3454,12 @@ added: v19.8.0 * `path` {string|Buffer|URL} * `options` {Object} * `type` {string} An optional mime type for the blob. -* Return: {Promise} containing {Blob} - -Returns a {Blob} whose data is backed by the given file. - -The file must not be modified after the {Blob} is created. Any modifications -will cause reading the {Blob} data to fail with a `DOMException` error. -Synchronous stat operations on the file when the `Blob` is created, and before -each read in order to detect whether the file data has been modified on disk. - -```mjs -import { openAsBlob } from 'node:fs'; - -const blob = await openAsBlob('the.file.txt'); -const ab = await blob.arrayBuffer(); -blob.stream(); -``` +* `callback` {Function} + * `err` {Error} + * `blob` {Blob} -```cjs -const { openAsBlob } = require('node:fs'); - -(async () => { - const blob = await openAsBlob('the.file.txt'); - const ab = await blob.arrayBuffer(); - blob.stream(); -})(); -``` +For detailed information, see the documentation of the Promises version +of this API: [`fsPromises.openAsBlob()`][]. ### `fs.opendir(path[, options], callback)` @@ -5602,6 +5624,22 @@ this API: [`fs.mkdtemp()`][]. The optional `options` argument can be a string specifying an encoding, or an object with an `encoding` property specifying the character encoding to use. +### `fs.openAsBlobSync(path[, options])` + + + +> Stability: 1 - Experimental + +* `path` {string|Buffer|URL} +* `options` {Object} + * `type` {string} An optional mime type for the blob. +* Returns: {Blob} + +For detailed information, see the documentation of the Promises version +of this API: [`fsPromises.openAsBlob()`][]. + ### `fs.opendirSync(path[, options])`