Initial import with skill sheet working

This commit is contained in:
2024-12-04 00:11:23 +01:00
commit 9050c80ab4
4488 changed files with 671048 additions and 0 deletions

201
node_modules/@humanfs/core/LICENSE generated vendored Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

140
node_modules/@humanfs/core/README.md generated vendored Normal file
View File

@ -0,0 +1,140 @@
# `@humanfs/core`
by [Nicholas C. Zakas](https://humanwhocodes.com)
If you find this useful, please consider supporting my work with a [donation](https://humanwhocodes.com/donate) or [nominate me](https://stars.github.com/nominate/) for a GitHub Star.
## Description
The core functionality for humanfs that is shared across all implementations for all runtimes. The contents of this package are intentionally runtime agnostic and are not intended to be used alone.
Currently, this package simply exports the `Hfs` class, which is an abstract base class intended to be inherited from in runtime-specific hfs packages (like `@humanfs/node`).
> [!WARNING]
> This project is **experimental** and may change significantly before v1.0.0. Use at your own caution and definitely not in production!
## Installation
### Node.js
Install using your favorite package manager for Node.js:
```shell
npm install @humanfs/core
# or
pnpm install @humanfs/core
# or
yarn add @humanfs/core
# or
bun install @humanfs/core
```
Then you can import the `Hfs` and `Path` classes like this:
```js
import { Hfs, Path } from "@humanfs/core";
```
### Deno
Install using [JSR](https://jsr.io):
```shell
deno add @humanfs/core
# or
jsr add @humanfs/core
```
Then you can import the `Hfs` class like this:
```js
import { Hfs, Path } from "@humanfs/core";
```
### Browser
It's recommended to import the minified version to save bandwidth:
```js
import { Hfs, Path } from "https://cdn.skypack.dev/@humanfs/core?min";
```
However, you can also import the unminified version for debugging purposes:
```js
import { Hfs, Path } from "https://cdn.skypack.dev/@humanfs/core";
```
## Usage
### `Hfs` Class
The `Hfs` class contains all of the basic functionality for an `Hfs` instance *without* a predefined impl. This class is mostly used for creating runtime-specific impls, such as `NodeHfs` and `DenoHfs`.
You can create your own instance by providing an `impl` directly:
```js
const hfs = new Hfs({ impl: { async text() {} }});
```
The specified `impl` becomes the base impl for the instance, meaning you can always reset back to it using `resetImpl()`.
You can also inherit from `Hfs` to create your own class with a preconfigured impl, such as:
```js
class MyHfs extends Hfs {
constructor() {
super({
impl: myImpl
});
}
}
```
### `Path` Class
The `Path` class represents the path to a directory or file within a file system. It's an abstract representation that can be used even outside of traditional file systems where string paths might not make sense.
```js
const myPath = new Path(["dir", "subdir"]);
console.log(myPath.toString()); // "dir/subdir"
// add another step
myPath.push("file.txt");
console.log(myPath.toString()); // "dir/subdir/file.txt"
// get just the last step
console.log(myPath.name); // "file.txt"
// change just the last step
myPath.name = "file.json";
console.log(myPath.name); // "file.json"
console.log(myPath.toString()); // "dir/subdir/file.json"
// get the size of the path
console.log(myPath.size); // 3
// remove the last step
myPath.pop();
console.log(myPath.toString()); // "dir/subdir"
// iterate over the steps
for (const step of myPath) {
// do something
}
// create a new path from a string
const newPath = Path.fromString("/foo/bar");
```
## License
Apache 2.0

64
node_modules/@humanfs/core/dist/errors.d.ts generated vendored Normal file
View File

@ -0,0 +1,64 @@
/**
* @fileoverview Common error classes
* @author Nicholas C. Zakas
*/
/**
* Error thrown when a file or directory is not found.
*/
export class NotFoundError extends Error {
/**
* Creates a new instance.
* @param {string} message The error message.
*/
constructor(message: string);
/**
* Error code.
* @type {string}
*/
code: string;
}
/**
* Error thrown when an operation is not permitted.
*/
export class PermissionError extends Error {
/**
* Creates a new instance.
* @param {string} message The error message.
*/
constructor(message: string);
/**
* Error code.
* @type {string}
*/
code: string;
}
/**
* Error thrown when an operation is not allowed on a directory.
*/
export class DirectoryError extends Error {
/**
* Creates a new instance.
* @param {string} message The error message.
*/
constructor(message: string);
/**
* Error code.
* @type {string}
*/
code: string;
}
/**
* Error thrown when a directory is not empty.
*/
export class NotEmptyError extends Error {
/**
* Creates a new instance.
* @param {string} message The error message.
*/
constructor(message: string);
/**
* Error code.
* @type {string}
*/
code: string;
}

193
node_modules/@humanfs/core/dist/fsx.d.ts generated vendored Normal file
View File

@ -0,0 +1,193 @@
/**
* @fileoverview The main file for the hfs package.
* @author Nicholas C. Zakas
*/
/** @typedef{import("@humanfs/types").HfsImpl} HfsImpl */
/** @typedef{import("@humanfs/types").HfsDirectoryEntry} HfsDirectoryEntry */
/**
* Error to represent when a method is missing on an impl.
*/
export class NoSuchMethodError extends Error {
/**
* Creates a new instance.
* @param {string} methodName The name of the method that was missing.
*/
constructor(methodName: string);
}
/**
* Error to represent when an impl is already set.
*/
export class ImplAlreadySetError extends Error {
/**
* Creates a new instance.
*/
constructor();
}
/**
* A class representing a log entry.
*/
export class LogEntry {
/**
* Creates a new instance.
* @param {string} type The type of log entry.
* @param {any} [data] The data associated with the log entry.
*/
constructor(type: string, data?: any);
/**
* The time at which the log entry was created.
* @type {number}
*/
timestamp: number;
methodName: string;
data: any;
#private;
}
/**
* A class representing a file system utility library.
* @implements {HfsImpl}
*/
export class Hfs implements HfsImpl {
/**
* Creates a new instance.
* @param {object} options The options for the instance.
* @param {HfsImpl} options.impl The implementation to use.
*/
constructor({ impl }: {
impl: HfsImpl;
});
/**
* Starts a new log with the given name.
* @param {string} name The name of the log to start;
* @returns {void}
* @throws {Error} When the log already exists.
* @throws {TypeError} When the name is not a non-empty string.
*/
logStart(name: string): void;
/**
* Ends a log with the given name and returns the entries.
* @param {string} name The name of the log to end.
* @returns {Array<LogEntry>} The entries in the log.
* @throws {Error} When the log does not exist.
*/
logEnd(name: string): Array<LogEntry>;
/**
* Determines if the current implementation is the base implementation.
* @returns {boolean} True if the current implementation is the base implementation.
*/
isBaseImpl(): boolean;
/**
* Sets the implementation for this instance.
* @param {object} impl The implementation to use.
* @returns {void}
*/
setImpl(impl: object): void;
/**
* Resets the implementation for this instance back to its original.
* @returns {void}
*/
resetImpl(): void;
/**
* Reads the given file and returns the contents as text. Assumes UTF-8 encoding.
* @param {string} filePath The file to read.
* @returns {Promise<string|undefined>} The contents of the file.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {TypeError} When the file path is not a non-empty string.
*/
text(filePath: string): Promise<string | undefined>;
/**
* Reads the given file and returns the contents as JSON. Assumes UTF-8 encoding.
* @param {string} filePath The file to read.
* @returns {Promise<any|undefined>} The contents of the file as JSON.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {SyntaxError} When the file contents are not valid JSON.
* @throws {TypeError} When the file path is not a non-empty string.
*/
json(filePath: string): Promise<any | undefined>;
/**
* Reads the given file and returns the contents as an ArrayBuffer.
* @param {string} filePath The file to read.
* @returns {Promise<ArrayBuffer|undefined>} The contents of the file as an ArrayBuffer.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {TypeError} When the file path is not a non-empty string.
* @deprecated Use bytes() instead.
*/
arrayBuffer(filePath: string): Promise<ArrayBuffer | undefined>;
/**
* Reads the given file and returns the contents as an Uint8Array.
* @param {string} filePath The file to read.
* @returns {Promise<Uint8Array|undefined>} The contents of the file as an Uint8Array.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {TypeError} When the file path is not a non-empty string.
*/
bytes(filePath: string): Promise<Uint8Array | undefined>;
/**
* Writes the given data to the given file. Creates any necessary directories along the way.
* If the data is a string, UTF-8 encoding is used.
* @param {string} filePath The file to write.
* @param {any} contents The data to write.
* @returns {Promise<void>} A promise that resolves when the file is written.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {TypeError} When the file path is not a non-empty string.
*/
write(filePath: string, contents: any): Promise<void>;
/**
* Determines if the given file exists.
* @param {string} filePath The file to check.
* @returns {Promise<boolean>} True if the file exists.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {TypeError} When the file path is not a non-empty string.
*/
isFile(filePath: string): Promise<boolean>;
/**
* Determines if the given directory exists.
* @param {string} dirPath The directory to check.
* @returns {Promise<boolean>} True if the directory exists.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {TypeError} When the directory path is not a non-empty string.
*/
isDirectory(dirPath: string): Promise<boolean>;
/**
* Creates the given directory.
* @param {string} dirPath The directory to create.
* @returns {Promise<void>} A promise that resolves when the directory is created.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {TypeError} When the directory path is not a non-empty string.
*/
createDirectory(dirPath: string): Promise<void>;
/**
* Deletes the given file.
* @param {string} filePath The file to delete.
* @returns {Promise<void>} A promise that resolves when the file is deleted.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {TypeError} When the file path is not a non-empty string.
*/
delete(filePath: string): Promise<void>;
/**
* Deletes the given directory.
* @param {string} dirPath The directory to delete.
* @returns {Promise<void>} A promise that resolves when the directory is deleted.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {TypeError} When the directory path is not a non-empty string.
*/
deleteAll(dirPath: string): Promise<void>;
/**
* Returns a list of directory entries for the given path.
* @param {string} dirPath The path to the directory to read.
* @returns {AsyncIterable<HfsDirectoryEntry>} A promise that resolves with the
* directory entries.
* @throws {TypeError} If the directory path is not a string.
* @throws {Error} If the directory cannot be read.
*/
list(dirPath: string): AsyncIterable<HfsDirectoryEntry>;
/**
* Returns the size of the given file.
* @param {string} filePath The path to the file to read.
* @returns {Promise<number>} A promise that resolves with the size of the file.
* @throws {TypeError} If the file path is not a string.
* @throws {Error} If the file cannot be read.
*/
size(filePath: string): Promise<number>;
#private;
}
export type HfsImpl = import("@humanfs/types").HfsImpl;
export type HfsDirectoryEntry = import("@humanfs/types").HfsDirectoryEntry;

288
node_modules/@humanfs/core/dist/hfs.d.ts generated vendored Normal file
View File

@ -0,0 +1,288 @@
/**
* Error to represent when a method is missing on an impl.
*/
export class NoSuchMethodError extends Error {
/**
* Creates a new instance.
* @param {string} methodName The name of the method that was missing.
*/
constructor(methodName: string);
}
/**
* Error to represent when a method is not supported on an impl. This happens
* when a method on `Hfs` is called with one name and the corresponding method
* on the impl has a different name. (Example: `text()` and `bytes()`.)
*/
export class MethodNotSupportedError extends Error {
/**
* Creates a new instance.
* @param {string} methodName The name of the method that was missing.
*/
constructor(methodName: string);
}
/**
* Error to represent when an impl is already set.
*/
export class ImplAlreadySetError extends Error {
/**
* Creates a new instance.
*/
constructor();
}
/**
* A class representing a log entry.
*/
export class LogEntry {
/**
* Creates a new instance.
* @param {string} type The type of log entry.
* @param {any} [data] The data associated with the log entry.
*/
constructor(type: string, data?: any);
/**
* The type of log entry.
* @type {string}
*/
type: string;
/**
* The data associated with the log entry.
* @type {any}
*/
data: any;
/**
* The time at which the log entry was created.
* @type {number}
*/
timestamp: number;
}
/**
* A class representing a file system utility library.
* @implements {HfsImpl}
*/
export class Hfs implements HfsImpl {
/**
* Creates a new instance.
* @param {object} options The options for the instance.
* @param {HfsImpl} options.impl The implementation to use.
*/
constructor({ impl }: {
impl: HfsImpl;
});
/**
* Starts a new log with the given name.
* @param {string} name The name of the log to start;
* @returns {void}
* @throws {Error} When the log already exists.
* @throws {TypeError} When the name is not a non-empty string.
*/
logStart(name: string): void;
/**
* Ends a log with the given name and returns the entries.
* @param {string} name The name of the log to end.
* @returns {Array<LogEntry>} The entries in the log.
* @throws {Error} When the log does not exist.
*/
logEnd(name: string): Array<LogEntry>;
/**
* Determines if the current implementation is the base implementation.
* @returns {boolean} True if the current implementation is the base implementation.
*/
isBaseImpl(): boolean;
/**
* Sets the implementation for this instance.
* @param {object} impl The implementation to use.
* @returns {void}
*/
setImpl(impl: object): void;
/**
* Resets the implementation for this instance back to its original.
* @returns {void}
*/
resetImpl(): void;
/**
* Reads the given file and returns the contents as text. Assumes UTF-8 encoding.
* @param {string|URL} filePath The file to read.
* @returns {Promise<string|undefined>} The contents of the file.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {TypeError} When the file path is not a non-empty string.
*/
text(filePath: string | URL): Promise<string | undefined>;
/**
* Reads the given file and returns the contents as JSON. Assumes UTF-8 encoding.
* @param {string|URL} filePath The file to read.
* @returns {Promise<any|undefined>} The contents of the file as JSON.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {SyntaxError} When the file contents are not valid JSON.
* @throws {TypeError} When the file path is not a non-empty string.
*/
json(filePath: string | URL): Promise<any | undefined>;
/**
* Reads the given file and returns the contents as an ArrayBuffer.
* @param {string|URL} filePath The file to read.
* @returns {Promise<ArrayBuffer|undefined>} The contents of the file as an ArrayBuffer.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {TypeError} When the file path is not a non-empty string.
* @deprecated Use bytes() instead.
*/
arrayBuffer(filePath: string | URL): Promise<ArrayBuffer | undefined>;
/**
* Reads the given file and returns the contents as an Uint8Array.
* @param {string|URL} filePath The file to read.
* @returns {Promise<Uint8Array|undefined>} The contents of the file as an Uint8Array.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {TypeError} When the file path is not a non-empty string.
*/
bytes(filePath: string | URL): Promise<Uint8Array | undefined>;
/**
* Writes the given data to the given file. Creates any necessary directories along the way.
* If the data is a string, UTF-8 encoding is used.
* @param {string|URL} filePath The file to write.
* @param {string|ArrayBuffer|ArrayBufferView} contents The data to write.
* @returns {Promise<void>} A promise that resolves when the file is written.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {TypeError} When the file path is not a non-empty string.
*/
write(filePath: string | URL, contents: string | ArrayBuffer | ArrayBufferView): Promise<void>;
/**
* Appends the given data to the given file. Creates any necessary directories along the way.
* If the data is a string, UTF-8 encoding is used.
* @param {string|URL} filePath The file to append to.
* @param {string|ArrayBuffer|ArrayBufferView} contents The data to append.
* @returns {Promise<void>} A promise that resolves when the file is appended to.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {TypeError} When the file path is not a non-empty string.
* @throws {TypeError} When the file contents are not a string or ArrayBuffer.
* @throws {Error} When the file cannot be appended to.
*/
append(filePath: string | URL, contents: string | ArrayBuffer | ArrayBufferView): Promise<void>;
/**
* Determines if the given file exists.
* @param {string|URL} filePath The file to check.
* @returns {Promise<boolean>} True if the file exists.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {TypeError} When the file path is not a non-empty string.
*/
isFile(filePath: string | URL): Promise<boolean>;
/**
* Determines if the given directory exists.
* @param {string|URL} dirPath The directory to check.
* @returns {Promise<boolean>} True if the directory exists.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {TypeError} When the directory path is not a non-empty string.
*/
isDirectory(dirPath: string | URL): Promise<boolean>;
/**
* Creates the given directory.
* @param {string|URL} dirPath The directory to create.
* @returns {Promise<void>} A promise that resolves when the directory is created.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {TypeError} When the directory path is not a non-empty string.
*/
createDirectory(dirPath: string | URL): Promise<void>;
/**
* Deletes the given file or empty directory.
* @param {string|URL} filePath The file to delete.
* @returns {Promise<boolean>} A promise that resolves when the file or
* directory is deleted, true if the file or directory is deleted, false
* if the file or directory does not exist.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {TypeError} When the file path is not a non-empty string.
*/
delete(filePath: string | URL): Promise<boolean>;
/**
* Deletes the given file or directory recursively.
* @param {string|URL} dirPath The directory to delete.
* @returns {Promise<boolean>} A promise that resolves when the file or
* directory is deleted, true if the file or directory is deleted, false
* if the file or directory does not exist.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {TypeError} When the directory path is not a non-empty string.
*/
deleteAll(dirPath: string | URL): Promise<boolean>;
/**
* Returns a list of directory entries for the given path.
* @param {string|URL} dirPath The path to the directory to read.
* @returns {AsyncIterable<HfsDirectoryEntry>} A promise that resolves with the
* directory entries.
* @throws {TypeError} If the directory path is not a string or URL.
* @throws {Error} If the directory cannot be read.
*/
list(dirPath: string | URL): AsyncIterable<HfsDirectoryEntry>;
/**
* Walks a directory using a depth-first traversal and returns the entries
* from the traversal.
* @param {string|URL} dirPath The path to the directory to walk.
* @param {Object} [options] The options for the walk.
* @param {(entry:HfsWalkEntry) => Promise<boolean>|boolean} [options.directoryFilter] A filter function to determine
* if a directory's entries should be included in the walk.
* @param {(entry:HfsWalkEntry) => Promise<boolean>|boolean} [options.entryFilter] A filter function to determine if
* an entry should be included in the walk.
* @returns {AsyncIterable<HfsWalkEntry>} A promise that resolves with the
* directory entries.
* @throws {TypeError} If the directory path is not a string or URL.
* @throws {Error} If the directory cannot be read.
*/
walk(dirPath: string | URL, { directoryFilter, entryFilter }?: {
directoryFilter?: (entry: HfsWalkEntry) => Promise<boolean> | boolean;
entryFilter?: (entry: HfsWalkEntry) => Promise<boolean> | boolean;
}): AsyncIterable<HfsWalkEntry>;
/**
* Returns the size of the given file.
* @param {string|URL} filePath The path to the file to read.
* @returns {Promise<number>} A promise that resolves with the size of the file.
* @throws {TypeError} If the file path is not a string or URL.
* @throws {Error} If the file cannot be read.
*/
size(filePath: string | URL): Promise<number>;
/**
* Returns the last modified timestamp of the given file or directory.
* @param {string|URL} fileOrDirPath The path to the file or directory.
* @returns {Promise<Date|undefined>} A promise that resolves with the last modified date
* or undefined if the file or directory does not exist.
* @throws {TypeError} If the path is not a string or URL.
*/
lastModified(fileOrDirPath: string | URL): Promise<Date | undefined>;
/**
* Copys a file from one location to another.
* @param {string|URL} source The path to the file to copy.
* @param {string|URL} destination The path to the new file.
* @returns {Promise<void>} A promise that resolves when the file is copied.
* @throws {TypeError} If the file path is not a string or URL.
* @throws {Error} If the file cannot be copied.
*/
copy(source: string | URL, destination: string | URL): Promise<void>;
/**
* Copies a file or directory from one location to another.
* @param {string|URL} source The path to the file or directory to copy.
* @param {string|URL} destination The path to copy the file or directory to.
* @returns {Promise<void>} A promise that resolves when the file or directory is
* copied.
* @throws {TypeError} If the directory path is not a string or URL.
* @throws {Error} If the directory cannot be copied.
*/
copyAll(source: string | URL, destination: string | URL): Promise<void>;
/**
* Moves a file from the source path to the destination path.
* @param {string|URL} source The location of the file to move.
* @param {string|URL} destination The destination of the file to move.
* @returns {Promise<void>} A promise that resolves when the move is complete.
* @throws {TypeError} If the file or directory paths are not strings.
* @throws {Error} If the file or directory cannot be moved.
*/
move(source: string | URL, destination: string | URL): Promise<void>;
/**
* Moves a file or directory from one location to another.
* @param {string|URL} source The path to the file or directory to move.
* @param {string|URL} destination The path to move the file or directory to.
* @returns {Promise<void>} A promise that resolves when the file or directory is
* moved.
* @throws {TypeError} If the source is not a string or URL.
* @throws {TypeError} If the destination is not a string or URL.
* @throws {Error} If the file or directory cannot be moved.
*/
moveAll(source: string | URL, destination: string | URL): Promise<void>;
#private;
}
export type HfsImpl = import("@humanfs/types").HfsImpl;
export type HfsDirectoryEntry = import("@humanfs/types").HfsDirectoryEntry;
export type HfsWalkEntry = import("@humanfs/types").HfsWalkEntry;

3
node_modules/@humanfs/core/dist/index.d.ts generated vendored Normal file
View File

@ -0,0 +1,3 @@
export { Hfs } from "./hfs.js";
export { Path } from "./path.js";
export * from "./errors.js";

82
node_modules/@humanfs/core/dist/path.d.ts generated vendored Normal file
View File

@ -0,0 +1,82 @@
export class Path {
/**
* Creates a new path based on the argument type. If the argument is a string,
* it is assumed to be a file or directory path and is converted to a Path
* instance. If the argument is a URL, it is assumed to be a file URL and is
* converted to a Path instance. If the argument is a Path instance, it is
* copied into a new Path instance. If the argument is an array, it is assumed
* to be the steps of a path and is used to create a new Path instance.
* @param {string|URL|Path|Array<string>} pathish The value to convert to a Path instance.
* @returns {Path} A new Path instance.
* @throws {TypeError} When pathish is not a string, URL, Path, or Array.
* @throws {TypeError} When pathish is a string and is empty.
*/
static from(pathish: string | URL | Path | Array<string>): Path;
/**
* Creates a new Path instance from a string.
* @param {string} fileOrDirPath The file or directory path to convert.
* @returns {Path} A new Path instance.
* @deprecated Use Path.from() instead.
*/
static fromString(fileOrDirPath: string): Path;
/**
* Creates a new Path instance from a URL.
* @param {URL} url The URL to convert.
* @returns {Path} A new Path instance.
* @throws {TypeError} When url is not a URL instance.
* @throws {TypeError} When url.pathname is empty.
* @throws {TypeError} When url.protocol is not "file:".
* @deprecated Use Path.from() instead.
*/
static fromURL(url: URL): Path;
/**
* Creates a new instance.
* @param {Iterable<string>} [steps] The steps to use for the path.
* @throws {TypeError} When steps is not iterable.
*/
constructor(steps?: Iterable<string>);
/**
* Adds steps to the end of the path.
* @param {...string} steps The steps to add to the path.
* @returns {void}
*/
push(...steps: string[]): void;
/**
* Removes the last step from the path.
* @returns {string} The last step in the path.
*/
pop(): string;
/**
* Returns an iterator for steps in the path.
* @returns {IterableIterator<string>} An iterator for the steps in the path.
*/
steps(): IterableIterator<string>;
/**
* Sets the name (the last step) of the path.
* @type {string}
*/
set name(value: string);
/**
* Retrieves the name (the last step) of the path.
* @type {string}
*/
get name(): string;
/**
* Retrieves the size of the path.
* @type {number}
*/
get size(): number;
/**
* Returns the path as a string.
* @returns {string} The path as a string.
*/
toString(): string;
/**
* Returns an iterator for the steps in the path.
* @returns {IterableIterator<string>} An iterator for the steps in the path.
*/
[Symbol.iterator](): IterableIterator<string>;
#private;
}
export type HfsImpl = import("@humanfs/types").HfsImpl;
export type HfsDirectoryEntry = import("@humanfs/types").HfsDirectoryEntry;

52
node_modules/@humanfs/core/package.json generated vendored Normal file
View File

@ -0,0 +1,52 @@
{
"name": "@humanfs/core",
"version": "0.19.1",
"description": "The core of the humanfs library.",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
"import": {
"types": "./dist/index.d.ts",
"default": "./src/index.js"
}
},
"files": [
"dist",
"src"
],
"scripts": {
"build": "tsc",
"prepare": "npm run build",
"pretest": "npm run build",
"test": "c8 mocha tests"
},
"repository": {
"type": "git",
"url": "git+https://github.com/humanwhocodes/humanfs.git"
},
"publishConfig": {
"access": "public"
},
"keywords": [
"filesystem",
"fs",
"hfs",
"files"
],
"author": "Nicholas C. Zakas",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/humanwhocodes/humanfs/issues"
},
"homepage": "https://github.com/humanwhocodes/humanfs#readme",
"engines": {
"node": ">=18.18.0"
},
"devDependencies": {
"@humanfs/types": "^0.15.0",
"c8": "^9.0.0",
"mocha": "^10.2.0",
"typescript": "^5.2.2"
}
}

105
node_modules/@humanfs/core/src/errors.js generated vendored Normal file
View File

@ -0,0 +1,105 @@
/**
* @fileoverview Common error classes
* @author Nicholas C. Zakas
*/
/**
* Error thrown when a file or directory is not found.
*/
export class NotFoundError extends Error {
/**
* Name of the error class.
* @type {string}
*/
name = "NotFoundError";
/**
* Error code.
* @type {string}
*/
code = "ENOENT";
/**
* Creates a new instance.
* @param {string} message The error message.
*/
constructor(message) {
super(`ENOENT: No such file or directory, ${message}`);
}
}
/**
* Error thrown when an operation is not permitted.
*/
export class PermissionError extends Error {
/**
* Name of the error class.
* @type {string}
*/
name = "PermissionError";
/**
* Error code.
* @type {string}
*/
code = "EPERM";
/**
* Creates a new instance.
* @param {string} message The error message.
*/
constructor(message) {
super(`EPERM: Operation not permitted, ${message}`);
}
}
/**
* Error thrown when an operation is not allowed on a directory.
*/
export class DirectoryError extends Error {
/**
* Name of the error class.
* @type {string}
*/
name = "DirectoryError";
/**
* Error code.
* @type {string}
*/
code = "EISDIR";
/**
* Creates a new instance.
* @param {string} message The error message.
*/
constructor(message) {
super(`EISDIR: Illegal operation on a directory, ${message}`);
}
}
/**
* Error thrown when a directory is not empty.
*/
export class NotEmptyError extends Error {
/**
* Name of the error class.
* @type {string}
*/
name = "NotEmptyError";
/**
* Error code.
* @type {string}
*/
code = "ENOTEMPTY";
/**
* Creates a new instance.
* @param {string} message The error message.
*/
constructor(message) {
super(`ENOTEMPTY: Directory not empty, ${message}`);
}
}

699
node_modules/@humanfs/core/src/hfs.js generated vendored Normal file
View File

@ -0,0 +1,699 @@
/**
* @fileoverview The main file for the humanfs package.
* @author Nicholas C. Zakas
*/
/* global URL, TextDecoder, TextEncoder */
//-----------------------------------------------------------------------------
// Types
//-----------------------------------------------------------------------------
/** @typedef {import("@humanfs/types").HfsImpl} HfsImpl */
/** @typedef {import("@humanfs/types").HfsDirectoryEntry} HfsDirectoryEntry */
/** @typedef {import("@humanfs/types").HfsWalkEntry} HfsWalkEntry */
//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
const decoder = new TextDecoder();
const encoder = new TextEncoder();
/**
* Error to represent when a method is missing on an impl.
*/
export class NoSuchMethodError extends Error {
/**
* Creates a new instance.
* @param {string} methodName The name of the method that was missing.
*/
constructor(methodName) {
super(`Method "${methodName}" does not exist on impl.`);
}
}
/**
* Error to represent when a method is not supported on an impl. This happens
* when a method on `Hfs` is called with one name and the corresponding method
* on the impl has a different name. (Example: `text()` and `bytes()`.)
*/
export class MethodNotSupportedError extends Error {
/**
* Creates a new instance.
* @param {string} methodName The name of the method that was missing.
*/
constructor(methodName) {
super(`Method "${methodName}" is not supported on this impl.`);
}
}
/**
* Error to represent when an impl is already set.
*/
export class ImplAlreadySetError extends Error {
/**
* Creates a new instance.
*/
constructor() {
super(`Implementation already set.`);
}
}
/**
* Asserts that the given path is a valid file path.
* @param {any} fileOrDirPath The path to check.
* @returns {void}
* @throws {TypeError} When the path is not a non-empty string.
*/
function assertValidFileOrDirPath(fileOrDirPath) {
if (
!fileOrDirPath ||
(!(fileOrDirPath instanceof URL) && typeof fileOrDirPath !== "string")
) {
throw new TypeError("Path must be a non-empty string or URL.");
}
}
/**
* Asserts that the given file contents are valid.
* @param {any} contents The contents to check.
* @returns {void}
* @throws {TypeError} When the contents are not a string or ArrayBuffer.
*/
function assertValidFileContents(contents) {
if (
typeof contents !== "string" &&
!(contents instanceof ArrayBuffer) &&
!ArrayBuffer.isView(contents)
) {
throw new TypeError(
"File contents must be a string, ArrayBuffer, or ArrayBuffer view.",
);
}
}
/**
* Converts the given contents to Uint8Array.
* @param {any} contents The data to convert.
* @returns {Uint8Array} The converted Uint8Array.
* @throws {TypeError} When the contents are not a string or ArrayBuffer.
*/
function toUint8Array(contents) {
if (contents instanceof Uint8Array) {
return contents;
}
if (typeof contents === "string") {
return encoder.encode(contents);
}
if (contents instanceof ArrayBuffer) {
return new Uint8Array(contents);
}
if (ArrayBuffer.isView(contents)) {
const bytes = contents.buffer.slice(
contents.byteOffset,
contents.byteOffset + contents.byteLength,
);
return new Uint8Array(bytes);
}
throw new TypeError(
"Invalid contents type. Expected string or ArrayBuffer.",
);
}
//-----------------------------------------------------------------------------
// Exports
//-----------------------------------------------------------------------------
/**
* A class representing a log entry.
*/
export class LogEntry {
/**
* The type of log entry.
* @type {string}
*/
type;
/**
* The data associated with the log entry.
* @type {any}
*/
data;
/**
* The time at which the log entry was created.
* @type {number}
*/
timestamp = Date.now();
/**
* Creates a new instance.
* @param {string} type The type of log entry.
* @param {any} [data] The data associated with the log entry.
*/
constructor(type, data) {
this.type = type;
this.data = data;
}
}
/**
* A class representing a file system utility library.
* @implements {HfsImpl}
*/
export class Hfs {
/**
* The base implementation for this instance.
* @type {HfsImpl}
*/
#baseImpl;
/**
* The current implementation for this instance.
* @type {HfsImpl}
*/
#impl;
/**
* A map of log names to their corresponding entries.
* @type {Map<string,Array<LogEntry>>}
*/
#logs = new Map();
/**
* Creates a new instance.
* @param {object} options The options for the instance.
* @param {HfsImpl} options.impl The implementation to use.
*/
constructor({ impl }) {
this.#baseImpl = impl;
this.#impl = impl;
}
/**
* Logs an entry onto all currently open logs.
* @param {string} methodName The name of the method being called.
* @param {...*} args The arguments to the method.
* @returns {void}
*/
#log(methodName, ...args) {
for (const logs of this.#logs.values()) {
logs.push(new LogEntry("call", { methodName, args }));
}
}
/**
* Starts a new log with the given name.
* @param {string} name The name of the log to start;
* @returns {void}
* @throws {Error} When the log already exists.
* @throws {TypeError} When the name is not a non-empty string.
*/
logStart(name) {
if (!name || typeof name !== "string") {
throw new TypeError("Log name must be a non-empty string.");
}
if (this.#logs.has(name)) {
throw new Error(`Log "${name}" already exists.`);
}
this.#logs.set(name, []);
}
/**
* Ends a log with the given name and returns the entries.
* @param {string} name The name of the log to end.
* @returns {Array<LogEntry>} The entries in the log.
* @throws {Error} When the log does not exist.
*/
logEnd(name) {
if (this.#logs.has(name)) {
const logs = this.#logs.get(name);
this.#logs.delete(name);
return logs;
}
throw new Error(`Log "${name}" does not exist.`);
}
/**
* Determines if the current implementation is the base implementation.
* @returns {boolean} True if the current implementation is the base implementation.
*/
isBaseImpl() {
return this.#impl === this.#baseImpl;
}
/**
* Sets the implementation for this instance.
* @param {object} impl The implementation to use.
* @returns {void}
*/
setImpl(impl) {
this.#log("implSet", impl);
if (this.#impl !== this.#baseImpl) {
throw new ImplAlreadySetError();
}
this.#impl = impl;
}
/**
* Resets the implementation for this instance back to its original.
* @returns {void}
*/
resetImpl() {
this.#log("implReset");
this.#impl = this.#baseImpl;
}
/**
* Asserts that the given method exists on the current implementation.
* @param {string} methodName The name of the method to check.
* @returns {void}
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
*/
#assertImplMethod(methodName) {
if (typeof this.#impl[methodName] !== "function") {
throw new NoSuchMethodError(methodName);
}
}
/**
* Asserts that the given method exists on the current implementation, and if not,
* throws an error with a different method name.
* @param {string} methodName The name of the method to check.
* @param {string} targetMethodName The name of the method that should be reported
* as an error when methodName does not exist.
* @returns {void}
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
*/
#assertImplMethodAlt(methodName, targetMethodName) {
if (typeof this.#impl[methodName] !== "function") {
throw new MethodNotSupportedError(targetMethodName);
}
}
/**
* Calls the given method on the current implementation.
* @param {string} methodName The name of the method to call.
* @param {...any} args The arguments to the method.
* @returns {any} The return value from the method.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
*/
#callImplMethod(methodName, ...args) {
this.#log(methodName, ...args);
this.#assertImplMethod(methodName);
return this.#impl[methodName](...args);
}
/**
* Calls the given method on the current implementation and doesn't log the call.
* @param {string} methodName The name of the method to call.
* @param {...any} args The arguments to the method.
* @returns {any} The return value from the method.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
*/
#callImplMethodWithoutLog(methodName, ...args) {
this.#assertImplMethod(methodName);
return this.#impl[methodName](...args);
}
/**
* Calls the given method on the current implementation but logs a different method name.
* @param {string} methodName The name of the method to call.
* @param {string} targetMethodName The name of the method to log.
* @param {...any} args The arguments to the method.
* @returns {any} The return value from the method.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
*/
#callImplMethodAlt(methodName, targetMethodName, ...args) {
this.#log(targetMethodName, ...args);
this.#assertImplMethodAlt(methodName, targetMethodName);
return this.#impl[methodName](...args);
}
/**
* Reads the given file and returns the contents as text. Assumes UTF-8 encoding.
* @param {string|URL} filePath The file to read.
* @returns {Promise<string|undefined>} The contents of the file.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {TypeError} When the file path is not a non-empty string.
*/
async text(filePath) {
assertValidFileOrDirPath(filePath);
const result = await this.#callImplMethodAlt("bytes", "text", filePath);
return result ? decoder.decode(result) : undefined;
}
/**
* Reads the given file and returns the contents as JSON. Assumes UTF-8 encoding.
* @param {string|URL} filePath The file to read.
* @returns {Promise<any|undefined>} The contents of the file as JSON.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {SyntaxError} When the file contents are not valid JSON.
* @throws {TypeError} When the file path is not a non-empty string.
*/
async json(filePath) {
assertValidFileOrDirPath(filePath);
const result = await this.#callImplMethodAlt("bytes", "json", filePath);
return result ? JSON.parse(decoder.decode(result)) : undefined;
}
/**
* Reads the given file and returns the contents as an ArrayBuffer.
* @param {string|URL} filePath The file to read.
* @returns {Promise<ArrayBuffer|undefined>} The contents of the file as an ArrayBuffer.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {TypeError} When the file path is not a non-empty string.
* @deprecated Use bytes() instead.
*/
async arrayBuffer(filePath) {
assertValidFileOrDirPath(filePath);
const result = await this.#callImplMethodAlt(
"bytes",
"arrayBuffer",
filePath,
);
return result?.buffer;
}
/**
* Reads the given file and returns the contents as an Uint8Array.
* @param {string|URL} filePath The file to read.
* @returns {Promise<Uint8Array|undefined>} The contents of the file as an Uint8Array.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {TypeError} When the file path is not a non-empty string.
*/
async bytes(filePath) {
assertValidFileOrDirPath(filePath);
return this.#callImplMethod("bytes", filePath);
}
/**
* Writes the given data to the given file. Creates any necessary directories along the way.
* If the data is a string, UTF-8 encoding is used.
* @param {string|URL} filePath The file to write.
* @param {string|ArrayBuffer|ArrayBufferView} contents The data to write.
* @returns {Promise<void>} A promise that resolves when the file is written.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {TypeError} When the file path is not a non-empty string.
*/
async write(filePath, contents) {
assertValidFileOrDirPath(filePath);
assertValidFileContents(contents);
this.#log("write", filePath, contents);
let value = toUint8Array(contents);
return this.#callImplMethodWithoutLog("write", filePath, value);
}
/**
* Appends the given data to the given file. Creates any necessary directories along the way.
* If the data is a string, UTF-8 encoding is used.
* @param {string|URL} filePath The file to append to.
* @param {string|ArrayBuffer|ArrayBufferView} contents The data to append.
* @returns {Promise<void>} A promise that resolves when the file is appended to.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {TypeError} When the file path is not a non-empty string.
* @throws {TypeError} When the file contents are not a string or ArrayBuffer.
* @throws {Error} When the file cannot be appended to.
*/
async append(filePath, contents) {
assertValidFileOrDirPath(filePath);
assertValidFileContents(contents);
this.#log("append", filePath, contents);
let value = toUint8Array(contents);
return this.#callImplMethodWithoutLog("append", filePath, value);
}
/**
* Determines if the given file exists.
* @param {string|URL} filePath The file to check.
* @returns {Promise<boolean>} True if the file exists.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {TypeError} When the file path is not a non-empty string.
*/
async isFile(filePath) {
assertValidFileOrDirPath(filePath);
return this.#callImplMethod("isFile", filePath);
}
/**
* Determines if the given directory exists.
* @param {string|URL} dirPath The directory to check.
* @returns {Promise<boolean>} True if the directory exists.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {TypeError} When the directory path is not a non-empty string.
*/
async isDirectory(dirPath) {
assertValidFileOrDirPath(dirPath);
return this.#callImplMethod("isDirectory", dirPath);
}
/**
* Creates the given directory.
* @param {string|URL} dirPath The directory to create.
* @returns {Promise<void>} A promise that resolves when the directory is created.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {TypeError} When the directory path is not a non-empty string.
*/
async createDirectory(dirPath) {
assertValidFileOrDirPath(dirPath);
return this.#callImplMethod("createDirectory", dirPath);
}
/**
* Deletes the given file or empty directory.
* @param {string|URL} filePath The file to delete.
* @returns {Promise<boolean>} A promise that resolves when the file or
* directory is deleted, true if the file or directory is deleted, false
* if the file or directory does not exist.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {TypeError} When the file path is not a non-empty string.
*/
async delete(filePath) {
assertValidFileOrDirPath(filePath);
return this.#callImplMethod("delete", filePath);
}
/**
* Deletes the given file or directory recursively.
* @param {string|URL} dirPath The directory to delete.
* @returns {Promise<boolean>} A promise that resolves when the file or
* directory is deleted, true if the file or directory is deleted, false
* if the file or directory does not exist.
* @throws {NoSuchMethodError} When the method does not exist on the current implementation.
* @throws {TypeError} When the directory path is not a non-empty string.
*/
async deleteAll(dirPath) {
assertValidFileOrDirPath(dirPath);
return this.#callImplMethod("deleteAll", dirPath);
}
/**
* Returns a list of directory entries for the given path.
* @param {string|URL} dirPath The path to the directory to read.
* @returns {AsyncIterable<HfsDirectoryEntry>} A promise that resolves with the
* directory entries.
* @throws {TypeError} If the directory path is not a string or URL.
* @throws {Error} If the directory cannot be read.
*/
async *list(dirPath) {
assertValidFileOrDirPath(dirPath);
yield* await this.#callImplMethod("list", dirPath);
}
/**
* Walks a directory using a depth-first traversal and returns the entries
* from the traversal.
* @param {string|URL} dirPath The path to the directory to walk.
* @param {Object} [options] The options for the walk.
* @param {(entry:HfsWalkEntry) => Promise<boolean>|boolean} [options.directoryFilter] A filter function to determine
* if a directory's entries should be included in the walk.
* @param {(entry:HfsWalkEntry) => Promise<boolean>|boolean} [options.entryFilter] A filter function to determine if
* an entry should be included in the walk.
* @returns {AsyncIterable<HfsWalkEntry>} A promise that resolves with the
* directory entries.
* @throws {TypeError} If the directory path is not a string or URL.
* @throws {Error} If the directory cannot be read.
*/
async *walk(
dirPath,
{ directoryFilter = () => true, entryFilter = () => true } = {},
) {
assertValidFileOrDirPath(dirPath);
this.#log("walk", dirPath, { directoryFilter, entryFilter });
// inner function for recursion without additional logging
const walk = async function* (
dirPath,
{ directoryFilter, entryFilter, parentPath = "", depth = 1 },
) {
let dirEntries;
try {
dirEntries = await this.#callImplMethodWithoutLog(
"list",
dirPath,
);
} catch (error) {
// if the directory does not exist then return an empty array
if (error.code === "ENOENT") {
return;
}
// otherwise, rethrow the error
throw error;
}
for await (const listEntry of dirEntries) {
const walkEntry = {
path: listEntry.name,
depth,
...listEntry,
};
if (parentPath) {
walkEntry.path = `${parentPath}/${walkEntry.path}`;
}
// first emit the entry but only if the entry filter returns true
let shouldEmitEntry = entryFilter(walkEntry);
if (shouldEmitEntry.then) {
shouldEmitEntry = await shouldEmitEntry;
}
if (shouldEmitEntry) {
yield walkEntry;
}
// if it's a directory then yield the entry and walk the directory
if (listEntry.isDirectory) {
// if the directory filter returns false, skip the directory
let shouldWalkDirectory = directoryFilter(walkEntry);
if (shouldWalkDirectory.then) {
shouldWalkDirectory = await shouldWalkDirectory;
}
if (!shouldWalkDirectory) {
continue;
}
// make sure there's a trailing slash on the directory path before appending
const directoryPath =
dirPath instanceof URL
? new URL(
listEntry.name,
dirPath.href.endsWith("/")
? dirPath.href
: `${dirPath.href}/`,
)
: `${dirPath.endsWith("/") ? dirPath : `${dirPath}/`}${listEntry.name}`;
yield* walk(directoryPath, {
directoryFilter,
entryFilter,
parentPath: walkEntry.path,
depth: depth + 1,
});
}
}
}.bind(this);
yield* walk(dirPath, { directoryFilter, entryFilter });
}
/**
* Returns the size of the given file.
* @param {string|URL} filePath The path to the file to read.
* @returns {Promise<number>} A promise that resolves with the size of the file.
* @throws {TypeError} If the file path is not a string or URL.
* @throws {Error} If the file cannot be read.
*/
async size(filePath) {
assertValidFileOrDirPath(filePath);
return this.#callImplMethod("size", filePath);
}
/**
* Returns the last modified timestamp of the given file or directory.
* @param {string|URL} fileOrDirPath The path to the file or directory.
* @returns {Promise<Date|undefined>} A promise that resolves with the last modified date
* or undefined if the file or directory does not exist.
* @throws {TypeError} If the path is not a string or URL.
*/
async lastModified(fileOrDirPath) {
assertValidFileOrDirPath(fileOrDirPath);
return this.#callImplMethod("lastModified", fileOrDirPath);
}
/**
* Copys a file from one location to another.
* @param {string|URL} source The path to the file to copy.
* @param {string|URL} destination The path to the new file.
* @returns {Promise<void>} A promise that resolves when the file is copied.
* @throws {TypeError} If the file path is not a string or URL.
* @throws {Error} If the file cannot be copied.
*/
async copy(source, destination) {
assertValidFileOrDirPath(source);
assertValidFileOrDirPath(destination);
return this.#callImplMethod("copy", source, destination);
}
/**
* Copies a file or directory from one location to another.
* @param {string|URL} source The path to the file or directory to copy.
* @param {string|URL} destination The path to copy the file or directory to.
* @returns {Promise<void>} A promise that resolves when the file or directory is
* copied.
* @throws {TypeError} If the directory path is not a string or URL.
* @throws {Error} If the directory cannot be copied.
*/
async copyAll(source, destination) {
assertValidFileOrDirPath(source);
assertValidFileOrDirPath(destination);
return this.#callImplMethod("copyAll", source, destination);
}
/**
* Moves a file from the source path to the destination path.
* @param {string|URL} source The location of the file to move.
* @param {string|URL} destination The destination of the file to move.
* @returns {Promise<void>} A promise that resolves when the move is complete.
* @throws {TypeError} If the file or directory paths are not strings.
* @throws {Error} If the file or directory cannot be moved.
*/
async move(source, destination) {
assertValidFileOrDirPath(source);
assertValidFileOrDirPath(destination);
return this.#callImplMethod("move", source, destination);
}
/**
* Moves a file or directory from one location to another.
* @param {string|URL} source The path to the file or directory to move.
* @param {string|URL} destination The path to move the file or directory to.
* @returns {Promise<void>} A promise that resolves when the file or directory is
* moved.
* @throws {TypeError} If the source is not a string or URL.
* @throws {TypeError} If the destination is not a string or URL.
* @throws {Error} If the file or directory cannot be moved.
*/
async moveAll(source, destination) {
assertValidFileOrDirPath(source);
assertValidFileOrDirPath(destination);
return this.#callImplMethod("moveAll", source, destination);
}
}

8
node_modules/@humanfs/core/src/index.js generated vendored Normal file
View File

@ -0,0 +1,8 @@
/**
* @fileoverview API entrypoint for hfs/core
* @author Nicholas C. Zakas
*/
export { Hfs } from "./hfs.js";
export { Path } from "./path.js";
export * from "./errors.js";

237
node_modules/@humanfs/core/src/path.js generated vendored Normal file
View File

@ -0,0 +1,237 @@
/**
* @fileoverview The Path class.
* @author Nicholas C. Zakas
*/
/* globals URL */
//-----------------------------------------------------------------------------
// Types
//-----------------------------------------------------------------------------
/** @typedef{import("@humanfs/types").HfsImpl} HfsImpl */
/** @typedef{import("@humanfs/types").HfsDirectoryEntry} HfsDirectoryEntry */
//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
/**
* Normalizes a path to use forward slashes.
* @param {string} filePath The path to normalize.
* @returns {string} The normalized path.
*/
function normalizePath(filePath) {
let startIndex = 0;
let endIndex = filePath.length;
if (/[a-z]:\//i.test(filePath)) {
startIndex = 3;
}
if (filePath.startsWith("./")) {
startIndex = 2;
}
if (filePath.startsWith("/")) {
startIndex = 1;
}
if (filePath.endsWith("/")) {
endIndex = filePath.length - 1;
}
return filePath.slice(startIndex, endIndex).replace(/\\/g, "/");
}
/**
* Asserts that the given name is a non-empty string, no equal to "." or "..",
* and does not contain a forward slash or backslash.
* @param {string} name The name to check.
* @returns {void}
* @throws {TypeError} When name is not valid.
*/
function assertValidName(name) {
if (typeof name !== "string") {
throw new TypeError("name must be a string");
}
if (!name) {
throw new TypeError("name cannot be empty");
}
if (name === ".") {
throw new TypeError(`name cannot be "."`);
}
if (name === "..") {
throw new TypeError(`name cannot be ".."`);
}
if (name.includes("/") || name.includes("\\")) {
throw new TypeError(
`name cannot contain a slash or backslash: "${name}"`,
);
}
}
//-----------------------------------------------------------------------------
// Exports
//-----------------------------------------------------------------------------
export class Path {
/**
* The steps in the path.
* @type {Array<string>}
*/
#steps;
/**
* Creates a new instance.
* @param {Iterable<string>} [steps] The steps to use for the path.
* @throws {TypeError} When steps is not iterable.
*/
constructor(steps = []) {
if (typeof steps[Symbol.iterator] !== "function") {
throw new TypeError("steps must be iterable");
}
this.#steps = [...steps];
this.#steps.forEach(assertValidName);
}
/**
* Adds steps to the end of the path.
* @param {...string} steps The steps to add to the path.
* @returns {void}
*/
push(...steps) {
steps.forEach(assertValidName);
this.#steps.push(...steps);
}
/**
* Removes the last step from the path.
* @returns {string} The last step in the path.
*/
pop() {
return this.#steps.pop();
}
/**
* Returns an iterator for steps in the path.
* @returns {IterableIterator<string>} An iterator for the steps in the path.
*/
steps() {
return this.#steps.values();
}
/**
* Returns an iterator for the steps in the path.
* @returns {IterableIterator<string>} An iterator for the steps in the path.
*/
[Symbol.iterator]() {
return this.steps();
}
/**
* Retrieves the name (the last step) of the path.
* @type {string}
*/
get name() {
return this.#steps[this.#steps.length - 1];
}
/**
* Sets the name (the last step) of the path.
* @type {string}
*/
set name(value) {
assertValidName(value);
this.#steps[this.#steps.length - 1] = value;
}
/**
* Retrieves the size of the path.
* @type {number}
*/
get size() {
return this.#steps.length;
}
/**
* Returns the path as a string.
* @returns {string} The path as a string.
*/
toString() {
return this.#steps.join("/");
}
/**
* Creates a new path based on the argument type. If the argument is a string,
* it is assumed to be a file or directory path and is converted to a Path
* instance. If the argument is a URL, it is assumed to be a file URL and is
* converted to a Path instance. If the argument is a Path instance, it is
* copied into a new Path instance. If the argument is an array, it is assumed
* to be the steps of a path and is used to create a new Path instance.
* @param {string|URL|Path|Array<string>} pathish The value to convert to a Path instance.
* @returns {Path} A new Path instance.
* @throws {TypeError} When pathish is not a string, URL, Path, or Array.
* @throws {TypeError} When pathish is a string and is empty.
*/
static from(pathish) {
if (typeof pathish === "string") {
if (!pathish) {
throw new TypeError("argument cannot be empty");
}
return Path.fromString(pathish);
}
if (pathish instanceof URL) {
return Path.fromURL(pathish);
}
if (pathish instanceof Path || Array.isArray(pathish)) {
return new Path(pathish);
}
throw new TypeError("argument must be a string, URL, Path, or Array");
}
/**
* Creates a new Path instance from a string.
* @param {string} fileOrDirPath The file or directory path to convert.
* @returns {Path} A new Path instance.
* @deprecated Use Path.from() instead.
*/
static fromString(fileOrDirPath) {
return new Path(normalizePath(fileOrDirPath).split("/"));
}
/**
* Creates a new Path instance from a URL.
* @param {URL} url The URL to convert.
* @returns {Path} A new Path instance.
* @throws {TypeError} When url is not a URL instance.
* @throws {TypeError} When url.pathname is empty.
* @throws {TypeError} When url.protocol is not "file:".
* @deprecated Use Path.from() instead.
*/
static fromURL(url) {
if (!(url instanceof URL)) {
throw new TypeError("url must be a URL instance");
}
if (!url.pathname || url.pathname === "/") {
throw new TypeError("url.pathname cannot be empty");
}
if (url.protocol !== "file:") {
throw new TypeError(`url.protocol must be "file:"`);
}
// Remove leading slash in pathname
return new Path(normalizePath(url.pathname.slice(1)).split("/"));
}
}

201
node_modules/@humanfs/node/LICENSE generated vendored Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

141
node_modules/@humanfs/node/README.md generated vendored Normal file
View File

@ -0,0 +1,141 @@
# `@humanfs/node`
by [Nicholas C. Zakas](https://humanwhocodes.com)
If you find this useful, please consider supporting my work with a [donation](https://humanwhocodes.com/donate) or [nominate me](https://stars.github.com/nominate/) for a GitHub Star.
## Description
The `hfs` bindings for use in Node.js and Node.js-compatible runtimes.
> [!WARNING]
> This project is **experimental** and may change significantly before v1.0.0. Use at your own caution and definitely not in production!
## Installation
Install using your favorite package manager:
```shell
npm install @humanfs/node
# or
pnpm install @humanfs/node
# or
yarn add @humanfs/node
# or
bun install @humanfs/node
```
## Usage
The easiest way to use hfs in your project is to import the `hfs` object:
```js
import { hfs } from "@humanfs/node";
```
Then, you can use the API methods:
```js
// 1. Files
// read from a text file
const text = await hfs.text("file.txt");
// read from a JSON file
const json = await hfs.json("file.json");
// read raw bytes from a text file
const arrayBuffer = await hfs.arrayBuffer("file.txt");
// write text to a file
await hfs.write("file.txt", "Hello world!");
// write bytes to a file
await hfs.write("file.txt", new TextEncoder().encode("Hello world!"));
// append text to a file
await hfs.append("file.txt", "Hello world!");
// append bytes to a file
await hfs.append("file.txt", new TextEncoder().encode("Hello world!"));
// does the file exist?
const found = await hfs.isFile("file.txt");
// how big is the file?
const size = await hfs.size("file.txt");
// when was the file modified?
const mtime = await hfs.lastModified("file.txt");
// copy a file from one location to another
await hfs.copy("file.txt", "file-copy.txt");
// move a file from one location to another
await hfs.move("file.txt", "renamed.txt");
// delete a file
await hfs.delete("file.txt");
// 2. Directories
// create a directory
await hfs.createDirectory("dir");
// create a directory recursively
await hfs.createDirectory("dir/subdir");
// does the directory exist?
const dirFound = await hfs.isDirectory("dir");
// copy the entire directory
hfs.copyAll("from-dir", "to-dir");
// move the entire directory
hfs.moveAll("from-dir", "to-dir");
// delete a directory
await hfs.delete("dir");
// delete a non-empty directory
await hfs.deleteAll("dir");
```
If you'd like to create your own instance, import the `NodeHfs` constructor:
```js
import { NodeHfs } from "@humanfs/node";
import fsp from "fs/promises";
const hfs = new NodeHfs();
// optionally specify the fs/promises object to use
const hfs = new NodeHfs({ fsp });
```
If you'd like to use just the impl, import the `NodeHfsImpl` constructor:
```js
import { NodeHfsImpl } from "@humanfs/node";
import fsp from "fs/promises";
const hfs = new NodeHfsImpl();
// optionally specify the fs/promises object to use
const hfs = new NodeHfsImpl({ fsp });
```
## Errors Handled
* `ENOENT` - in most cases, these errors are handled silently.
* `ENFILE` and `EMFILE` - calls that result in these errors are retried for up to 60 seconds before giving up for good.
## License
Apache 2.0

2
node_modules/@humanfs/node/dist/index.d.ts generated vendored Normal file
View File

@ -0,0 +1,2 @@
export * from "./node-hfs.js";
export { Hfs } from "@humanfs/core";

150
node_modules/@humanfs/node/dist/node-fsx.d.ts generated vendored Normal file
View File

@ -0,0 +1,150 @@
/// <reference types="node" resolution-mode="require"/>
/**
* A class representing the Node.js implementation of Hfs.
* @implements {HfsImpl}
*/
export class NodeHfsImpl implements HfsImpl {
/**
* Creates a new instance.
* @param {object} [options] The options for the instance.
* @param {Fsp} [options.fsp] The file system module to use.
*/
constructor({ fsp }?: {
fsp?: Fsp;
});
/**
* Reads a file and returns the contents as a string. Assumes UTF-8 encoding.
* @param {string} filePath The path to the file to read.
* @returns {Promise<string|undefined>} A promise that resolves with the contents of
* the file or undefined if the file doesn't exist.
* @throws {TypeError} If the file path is not a string.
* @throws {RangeError} If the file path is empty.
* @throws {RangeError} If the file path is not absolute.
* @throws {RangeError} If the file path is not a file.
* @throws {RangeError} If the file path is not readable.
*/
text(filePath: string): Promise<string | undefined>;
/**
* Reads a file and returns the contents as a JSON object. Assumes UTF-8 encoding.
* @param {string} filePath The path to the file to read.
* @returns {Promise<object|undefined>} A promise that resolves with the contents of
* the file or undefined if the file doesn't exist.
* @throws {SyntaxError} If the file contents are not valid JSON.
* @throws {Error} If the file cannot be read.
* @throws {TypeError} If the file path is not a string.
*/
json(filePath: string): Promise<object | undefined>;
/**
* Reads a file and returns the contents as an ArrayBuffer.
* @param {string} filePath The path to the file to read.
* @returns {Promise<ArrayBuffer|undefined>} A promise that resolves with the contents
* of the file or undefined if the file doesn't exist.
* @throws {Error} If the file cannot be read.
* @throws {TypeError} If the file path is not a string.
* @deprecated Use bytes() instead.
*/
arrayBuffer(filePath: string): Promise<ArrayBuffer | undefined>;
/**
* Reads a file and returns the contents as an Uint8Array.
* @param {string} filePath The path to the file to read.
* @returns {Promise<Uint8Array|undefined>} A promise that resolves with the contents
* of the file or undefined if the file doesn't exist.
* @throws {Error} If the file cannot be read.
* @throws {TypeError} If the file path is not a string.
*/
bytes(filePath: string): Promise<Uint8Array | undefined>;
/**
* Writes a value to a file. If the value is a string, UTF-8 encoding is used.
* @param {string} filePath The path to the file to write.
* @param {string|ArrayBuffer|ArrayBufferView} contents The contents to write to the
* file.
* @returns {Promise<void>} A promise that resolves when the file is
* written.
* @throws {TypeError} If the file path is not a string.
* @throws {Error} If the file cannot be written.
*/
write(filePath: string, contents: string | ArrayBuffer | ArrayBufferView): Promise<void>;
/**
* Checks if a file exists.
* @param {string} filePath The path to the file to check.
* @returns {Promise<boolean>} A promise that resolves with true if the
* file exists or false if it does not.
* @throws {Error} If the operation fails with a code other than ENOENT.
*/
isFile(filePath: string): Promise<boolean>;
/**
* Checks if a directory exists.
* @param {string} dirPath The path to the directory to check.
* @returns {Promise<boolean>} A promise that resolves with true if the
* directory exists or false if it does not.
* @throws {Error} If the operation fails with a code other than ENOENT.
*/
isDirectory(dirPath: string): Promise<boolean>;
/**
* Creates a directory recursively.
* @param {string} dirPath The path to the directory to create.
* @returns {Promise<void>} A promise that resolves when the directory is
* created.
*/
createDirectory(dirPath: string): Promise<void>;
/**
* Deletes a file or empty directory.
* @param {string} fileOrDirPath The path to the file or directory to
* delete.
* @returns {Promise<void>} A promise that resolves when the file or
* directory is deleted.
* @throws {TypeError} If the file or directory path is not a string.
* @throws {Error} If the file or directory cannot be deleted.
* @throws {Error} If the file or directory is not found.
*/
delete(fileOrDirPath: string): Promise<void>;
/**
* Deletes a file or directory recursively.
* @param {string} fileOrDirPath The path to the file or directory to
* delete.
* @returns {Promise<void>} A promise that resolves when the file or
* directory is deleted.
* @throws {TypeError} If the file or directory path is not a string.
* @throws {Error} If the file or directory cannot be deleted.
* @throws {Error} If the file or directory is not found.
*/
deleteAll(fileOrDirPath: string): Promise<void>;
/**
* Returns a list of directory entries for the given path.
* @param {string} dirPath The path to the directory to read.
* @returns {AsyncIterable<HfsDirectoryEntry>} A promise that resolves with the
* directory entries.
* @throws {TypeError} If the directory path is not a string.
* @throws {Error} If the directory cannot be read.
*/
list(dirPath: string): AsyncIterable<HfsDirectoryEntry>;
/**
* Returns the size of a file.
* @param {string} filePath The path to the file to read.
* @returns {Promise<number|undefined>} A promise that resolves with the size of the
* file in bytes or undefined if the file doesn't exist.
*/
size(filePath: string): Promise<number | undefined>;
#private;
}
/**
* A class representing a file system utility library.
* @implements {HfsImpl}
*/
export class NodeHfs extends Hfs implements HfsImpl {
/**
* Creates a new instance.
* @param {object} [options] The options for the instance.
* @param {Fsp} [options.fsp] The file system module to use.
*/
constructor({ fsp }?: {
fsp?: Fsp;
});
}
export const hfs: NodeHfs;
export type HfsImpl = import("@humanfs/types").HfsImpl;
export type HfsDirectoryEntry = import("@humanfs/types").HfsDirectoryEntry;
export type Fsp = typeof nativeFsp;
export type Dirent = import("fs").Dirent;
import { Hfs } from "@humanfs/core";
import nativeFsp from "node:fs/promises";

176
node_modules/@humanfs/node/dist/node-hfs.d.ts generated vendored Normal file
View File

@ -0,0 +1,176 @@
/// <reference types="node" resolution-mode="require"/>
/**
* A class representing the Node.js implementation of Hfs.
* @implements {HfsImpl}
*/
export class NodeHfsImpl implements HfsImpl {
/**
* Creates a new instance.
* @param {object} [options] The options for the instance.
* @param {Fsp} [options.fsp] The file system module to use.
*/
constructor({ fsp }?: {
fsp?: Fsp;
});
/**
* Reads a file and returns the contents as an Uint8Array.
* @param {string|URL} filePath The path to the file to read.
* @returns {Promise<Uint8Array|undefined>} A promise that resolves with the contents
* of the file or undefined if the file doesn't exist.
* @throws {Error} If the file cannot be read.
* @throws {TypeError} If the file path is not a string.
*/
bytes(filePath: string | URL): Promise<Uint8Array | undefined>;
/**
* Writes a value to a file. If the value is a string, UTF-8 encoding is used.
* @param {string|URL} filePath The path to the file to write.
* @param {Uint8Array} contents The contents to write to the
* file.
* @returns {Promise<void>} A promise that resolves when the file is
* written.
* @throws {TypeError} If the file path is not a string.
* @throws {Error} If the file cannot be written.
*/
write(filePath: string | URL, contents: Uint8Array): Promise<void>;
/**
* Appends a value to a file. If the value is a string, UTF-8 encoding is used.
* @param {string|URL} filePath The path to the file to append to.
* @param {Uint8Array} contents The contents to append to the
* file.
* @returns {Promise<void>} A promise that resolves when the file is
* written.
* @throws {TypeError} If the file path is not a string.
* @throws {Error} If the file cannot be appended to.
*/
append(filePath: string | URL, contents: Uint8Array): Promise<void>;
/**
* Checks if a file exists.
* @param {string|URL} filePath The path to the file to check.
* @returns {Promise<boolean>} A promise that resolves with true if the
* file exists or false if it does not.
* @throws {Error} If the operation fails with a code other than ENOENT.
*/
isFile(filePath: string | URL): Promise<boolean>;
/**
* Checks if a directory exists.
* @param {string|URL} dirPath The path to the directory to check.
* @returns {Promise<boolean>} A promise that resolves with true if the
* directory exists or false if it does not.
* @throws {Error} If the operation fails with a code other than ENOENT.
*/
isDirectory(dirPath: string | URL): Promise<boolean>;
/**
* Creates a directory recursively.
* @param {string|URL} dirPath The path to the directory to create.
* @returns {Promise<void>} A promise that resolves when the directory is
* created.
*/
createDirectory(dirPath: string | URL): Promise<void>;
/**
* Deletes a file or empty directory.
* @param {string|URL} fileOrDirPath The path to the file or directory to
* delete.
* @returns {Promise<boolean>} A promise that resolves when the file or
* directory is deleted, true if the file or directory is deleted, false
* if the file or directory does not exist.
* @throws {TypeError} If the file or directory path is not a string.
* @throws {Error} If the file or directory cannot be deleted.
*/
delete(fileOrDirPath: string | URL): Promise<boolean>;
/**
* Deletes a file or directory recursively.
* @param {string|URL} fileOrDirPath The path to the file or directory to
* delete.
* @returns {Promise<boolean>} A promise that resolves when the file or
* directory is deleted, true if the file or directory is deleted, false
* if the file or directory does not exist.
* @throws {TypeError} If the file or directory path is not a string.
* @throws {Error} If the file or directory cannot be deleted.
*/
deleteAll(fileOrDirPath: string | URL): Promise<boolean>;
/**
* Returns a list of directory entries for the given path.
* @param {string|URL} dirPath The path to the directory to read.
* @returns {AsyncIterable<HfsDirectoryEntry>} A promise that resolves with the
* directory entries.
* @throws {TypeError} If the directory path is not a string.
* @throws {Error} If the directory cannot be read.
*/
list(dirPath: string | URL): AsyncIterable<HfsDirectoryEntry>;
/**
* Returns the size of a file. This method handles ENOENT errors
* and returns undefined in that case.
* @param {string|URL} filePath The path to the file to read.
* @returns {Promise<number|undefined>} A promise that resolves with the size of the
* file in bytes or undefined if the file doesn't exist.
*/
size(filePath: string | URL): Promise<number | undefined>;
/**
* Returns the last modified date of a file or directory. This method handles ENOENT errors
* and returns undefined in that case.
* @param {string|URL} fileOrDirPath The path to the file to read.
* @returns {Promise<Date|undefined>} A promise that resolves with the last modified
* date of the file or directory, or undefined if the file doesn't exist.
*/
lastModified(fileOrDirPath: string | URL): Promise<Date | undefined>;
/**
* Copies a file from one location to another.
* @param {string|URL} source The path to the file to copy.
* @param {string|URL} destination The path to copy the file to.
* @returns {Promise<void>} A promise that resolves when the file is copied.
* @throws {Error} If the source file does not exist.
* @throws {Error} If the source file is a directory.
* @throws {Error} If the destination file is a directory.
*/
copy(source: string | URL, destination: string | URL): Promise<void>;
/**
* Copies a file or directory from one location to another.
* @param {string|URL} source The path to the file or directory to copy.
* @param {string|URL} destination The path to copy the file or directory to.
* @returns {Promise<void>} A promise that resolves when the file or directory is
* copied.
* @throws {Error} If the source file or directory does not exist.
* @throws {Error} If the destination file or directory is a directory.
*/
copyAll(source: string | URL, destination: string | URL): Promise<void>;
/**
* Moves a file from the source path to the destination path.
* @param {string|URL} source The location of the file to move.
* @param {string|URL} destination The destination of the file to move.
* @returns {Promise<void>} A promise that resolves when the move is complete.
* @throws {TypeError} If the file paths are not strings.
* @throws {Error} If the file cannot be moved.
*/
move(source: string | URL, destination: string | URL): Promise<void>;
/**
* Moves a file or directory from the source path to the destination path.
* @param {string|URL} source The location of the file or directory to move.
* @param {string|URL} destination The destination of the file or directory to move.
* @returns {Promise<void>} A promise that resolves when the move is complete.
* @throws {TypeError} If the file paths are not strings.
* @throws {Error} If the file or directory cannot be moved.
*/
moveAll(source: string | URL, destination: string | URL): Promise<void>;
#private;
}
/**
* A class representing a file system utility library.
* @implements {HfsImpl}
*/
export class NodeHfs extends Hfs implements HfsImpl {
/**
* Creates a new instance.
* @param {object} [options] The options for the instance.
* @param {Fsp} [options.fsp] The file system module to use.
*/
constructor({ fsp }?: {
fsp?: Fsp;
});
}
export const hfs: NodeHfs;
export type HfsImpl = import("@humanfs/types").HfsImpl;
export type HfsDirectoryEntry = import("@humanfs/types").HfsDirectoryEntry;
export type Fsp = typeof nativeFsp;
export type Dirent = import("fs").Dirent;
import { Hfs } from "@humanfs/core";
import nativeFsp from "node:fs/promises";

View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,138 @@
# Retry utility
by [Nicholas C. Zakas](https://humanwhocodes.com)
If you find this useful, please consider supporting my work with a [donation](https://humanwhocodes.com/donate) or [nominate me](https://stars.github.com/nominate/) for a GitHub Star.
## Description
A utility for retrying failed async JavaScript calls based on the error returned.
## Usage
### Node.js
Install using [npm][npm] or [yarn][yarn]:
```
npm install @humanwhocodes/retry
# or
yarn add @humanwhocodes/retry
```
Import into your Node.js project:
```js
// CommonJS
const { Retrier } = require("@humanwhocodes/retry");
// ESM
import { Retrier } from "@humanwhocodes/retry";
```
### Deno
Install using [JSR](https://jsr.io):
```shell
deno add @humanwhocodes/retry
#or
jsr add @humanwhocodes/retry
```
Then import into your Deno project:
```js
import { Retrier } from "@humanwhocodes/retry";
```
### Bun
Install using this command:
```
bun add @humanwhocodes/retry
```
Import into your Bun project:
```js
import { Retrier } from "@humanwhocodes/retry";
```
### Browser
It's recommended to import the minified version to save bandwidth:
```js
import { Retrier } from "https://cdn.skypack.dev/@humanwhocodes/retry?min";
```
However, you can also import the unminified version for debugging purposes:
```js
import { Retrier } from "https://cdn.skypack.dev/@humanwhocodes/retry";
```
## API
After importing, create a new instance of `Retrier` and specify the function to run on the error. This function should return `true` if you want the call retried and `false` if not.
```js
// this instance will retry if the specific error code is found
const retrier = new Retrier(error => {
return error.code === "ENFILE" || error.code === "EMFILE";
});
```
Then, call the `retry()` method around the function you'd like to retry, such as:
```js
import fs from "fs/promises";
const retrier = new Retrier(error => {
return error.code === "ENFILE" || error.code === "EMFILE";
});
const text = await retrier.retry(() => fs.readFile("README.md", "utf8"));
```
The `retry()` method will either pass through the result on success or wait and retry on failure. Any error that isn't caught by the retrier is automatically rejected so the end result is a transparent passing through of both success and failure.
You can also pass an `AbortSignal` to cancel a retry:
```js
import fs from "fs/promises";
const controller = new AbortController();
const retrier = new Retrier(error => {
return error.code === "ENFILE" || error.code === "EMFILE";
});
const text = await retrier.retry(
() => fs.readFile("README.md", "utf8"),
{ signal: controller.signal }
);
```
## Developer Setup
1. Fork the repository
2. Clone your fork
3. Run `npm install` to setup dependencies
4. Run `npm test` to run tests
## License
Apache 2.0
## Prior Art
This utility is inspired by, and contains code from [`graceful-fs`](https://github.com/isaacs/node-graceful-fs).
[npm]: https://npmjs.com/
[yarn]: https://yarnpkg.com/

View File

@ -0,0 +1,303 @@
'use strict';
/**
* @fileoverview A utility for retrying failed async method calls.
*/
/* global setTimeout, clearTimeout */
//-----------------------------------------------------------------------------
// Constants
//-----------------------------------------------------------------------------
const MAX_TASK_TIMEOUT = 60000;
const MAX_TASK_DELAY = 100;
//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
/*
* The following logic has been extracted from graceful-fs.
*
* The ISC License
*
* Copyright (c) 2011-2023 Isaac Z. Schlueter, Ben Noordhuis, and Contributors
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
* IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/**
* Checks if it is time to retry a task based on the timestamp and last attempt time.
* @param {RetryTask} task The task to check.
* @param {number} maxDelay The maximum delay for the queue.
* @returns {boolean} true if it is time to retry, false otherwise.
*/
function isTimeToRetry(task, maxDelay) {
const timeSinceLastAttempt = Date.now() - task.lastAttempt;
const timeSinceStart = Math.max(task.lastAttempt - task.timestamp, 1);
const desiredDelay = Math.min(timeSinceStart * 1.2, maxDelay);
return timeSinceLastAttempt >= desiredDelay;
}
/**
* Checks if it is time to bail out based on the given timestamp.
* @param {RetryTask} task The task to check.
* @param {number} timeout The timeout for the queue.
* @returns {boolean} true if it is time to bail, false otherwise.
*/
function isTimeToBail(task, timeout) {
return task.age > timeout;
}
/**
* A class to represent a task in the retry queue.
*/
class RetryTask {
/**
* The unique ID for the task.
* @type {string}
*/
id = Math.random().toString(36).slice(2);
/**
* The function to call.
* @type {Function}
*/
fn;
/**
* The error that was thrown.
* @type {Error}
*/
error;
/**
* The timestamp of the task.
* @type {number}
*/
timestamp = Date.now();
/**
* The timestamp of the last attempt.
* @type {number}
*/
lastAttempt = this.timestamp;
/**
* The resolve function for the promise.
* @type {Function}
*/
resolve;
/**
* The reject function for the promise.
* @type {Function}
*/
reject;
/**
* The AbortSignal to monitor for cancellation.
* @type {AbortSignal|undefined}
*/
signal;
/**
* Creates a new instance.
* @param {Function} fn The function to call.
* @param {Error} error The error that was thrown.
* @param {Function} resolve The resolve function for the promise.
* @param {Function} reject The reject function for the promise.
* @param {AbortSignal|undefined} signal The AbortSignal to monitor for cancellation.
*/
constructor(fn, error, resolve, reject, signal) {
this.fn = fn;
this.error = error;
this.timestamp = Date.now();
this.lastAttempt = Date.now();
this.resolve = resolve;
this.reject = reject;
this.signal = signal;
}
/**
* Gets the age of the task.
* @returns {number} The age of the task in milliseconds.
* @readonly
*/
get age() {
return Date.now() - this.timestamp;
}
}
//-----------------------------------------------------------------------------
// Exports
//-----------------------------------------------------------------------------
/**
* A class that manages a queue of retry jobs.
*/
class Retrier {
/**
* Represents the queue for processing tasks.
* @type {Array<RetryTask>}
*/
#queue = [];
/**
* The timeout for the queue.
* @type {number}
*/
#timeout;
/**
* The maximum delay for the queue.
* @type {number}
*/
#maxDelay;
/**
* The setTimeout() timer ID.
* @type {NodeJS.Timeout|undefined}
*/
#timerId;
/**
* The function to call.
* @type {Function}
*/
#check;
/**
* Creates a new instance.
* @param {Function} check The function to call.
* @param {object} [options] The options for the instance.
* @param {number} [options.timeout] The timeout for the queue.
* @param {number} [options.maxDelay] The maximum delay for the queue.
*/
constructor(check, { timeout = MAX_TASK_TIMEOUT, maxDelay = MAX_TASK_DELAY } = {}) {
if (typeof check !== "function") {
throw new Error("Missing function to check errors");
}
this.#check = check;
this.#timeout = timeout;
this.#maxDelay = maxDelay;
}
/**
* Adds a new retry job to the queue.
* @param {Function} fn The function to call.
* @param {object} [options] The options for the job.
* @param {AbortSignal} [options.signal] The AbortSignal to monitor for cancellation.
* @returns {Promise<any>} A promise that resolves when the queue is
* processed.
*/
retry(fn, { signal } = {}) {
signal?.throwIfAborted();
let result;
try {
result = fn();
} catch (/** @type {any} */ error) {
return Promise.reject(new Error(`Synchronous error: ${error.message}`, { cause: error }));
}
// if the result is not a promise then reject an error
if (!result || typeof result.then !== "function") {
return Promise.reject(new Error("Result is not a promise."));
}
// call the original function and catch any ENFILE or EMFILE errors
// @ts-ignore because we know it's any
return Promise.resolve(result).catch(error => {
if (!this.#check(error)) {
throw error;
}
return new Promise((resolve, reject) => {
this.#queue.push(new RetryTask(fn, error, resolve, reject, signal));
signal?.addEventListener("abort", () => {
reject(signal.reason);
});
this.#processQueue();
});
});
}
/**
* Processes the queue.
* @returns {void}
*/
#processQueue() {
// clear any timer because we're going to check right now
clearTimeout(this.#timerId);
this.#timerId = undefined;
// if there's nothing in the queue, we're done
const task = this.#queue.shift();
if (!task) {
return;
}
const processAgain = () => {
this.#timerId = setTimeout(() => this.#processQueue(), 0);
};
// if it's time to bail, then bail
if (isTimeToBail(task, this.#timeout)) {
task.reject(task.error);
processAgain();
return;
}
// if it's not time to retry, then wait and try again
if (!isTimeToRetry(task, this.#maxDelay)) {
this.#queue.push(task);
processAgain();
return;
}
// otherwise, try again
task.lastAttempt = Date.now();
// Promise.resolve needed in case it's a thenable but not a Promise
Promise.resolve(task.fn())
// @ts-ignore because we know it's any
.then(result => task.resolve(result))
// @ts-ignore because we know it's any
.catch(error => {
if (!this.#check(error)) {
task.reject(error);
return;
}
// update the task timestamp and push to back of queue to try again
task.lastAttempt = Date.now();
this.#queue.push(task);
})
.finally(() => this.#processQueue());
}
}
exports.Retrier = Retrier;

View File

@ -0,0 +1,28 @@
/**
* A class that manages a queue of retry jobs.
*/
export class Retrier {
/**
* Creates a new instance.
* @param {Function} check The function to call.
* @param {object} [options] The options for the instance.
* @param {number} [options.timeout] The timeout for the queue.
* @param {number} [options.maxDelay] The maximum delay for the queue.
*/
constructor(check: Function, { timeout, maxDelay }?: {
timeout?: number | undefined;
maxDelay?: number | undefined;
} | undefined);
/**
* Adds a new retry job to the queue.
* @param {Function} fn The function to call.
* @param {object} [options] The options for the job.
* @param {AbortSignal} [options.signal] The AbortSignal to monitor for cancellation.
* @returns {Promise<any>} A promise that resolves when the queue is
* processed.
*/
retry(fn: Function, { signal }?: {
signal?: AbortSignal | undefined;
} | undefined): Promise<any>;
#private;
}

View File

@ -0,0 +1,28 @@
/**
* A class that manages a queue of retry jobs.
*/
export class Retrier {
/**
* Creates a new instance.
* @param {Function} check The function to call.
* @param {object} [options] The options for the instance.
* @param {number} [options.timeout] The timeout for the queue.
* @param {number} [options.maxDelay] The maximum delay for the queue.
*/
constructor(check: Function, { timeout, maxDelay }?: {
timeout?: number | undefined;
maxDelay?: number | undefined;
} | undefined);
/**
* Adds a new retry job to the queue.
* @param {Function} fn The function to call.
* @param {object} [options] The options for the job.
* @param {AbortSignal} [options.signal] The AbortSignal to monitor for cancellation.
* @returns {Promise<any>} A promise that resolves when the queue is
* processed.
*/
retry(fn: Function, { signal }?: {
signal?: AbortSignal | undefined;
} | undefined): Promise<any>;
#private;
}

View File

@ -0,0 +1,302 @@
// @ts-self-types="./retrier.d.ts"
/**
* @fileoverview A utility for retrying failed async method calls.
*/
/* global setTimeout, clearTimeout */
//-----------------------------------------------------------------------------
// Constants
//-----------------------------------------------------------------------------
const MAX_TASK_TIMEOUT = 60000;
const MAX_TASK_DELAY = 100;
//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
/*
* The following logic has been extracted from graceful-fs.
*
* The ISC License
*
* Copyright (c) 2011-2023 Isaac Z. Schlueter, Ben Noordhuis, and Contributors
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
* IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/**
* Checks if it is time to retry a task based on the timestamp and last attempt time.
* @param {RetryTask} task The task to check.
* @param {number} maxDelay The maximum delay for the queue.
* @returns {boolean} true if it is time to retry, false otherwise.
*/
function isTimeToRetry(task, maxDelay) {
const timeSinceLastAttempt = Date.now() - task.lastAttempt;
const timeSinceStart = Math.max(task.lastAttempt - task.timestamp, 1);
const desiredDelay = Math.min(timeSinceStart * 1.2, maxDelay);
return timeSinceLastAttempt >= desiredDelay;
}
/**
* Checks if it is time to bail out based on the given timestamp.
* @param {RetryTask} task The task to check.
* @param {number} timeout The timeout for the queue.
* @returns {boolean} true if it is time to bail, false otherwise.
*/
function isTimeToBail(task, timeout) {
return task.age > timeout;
}
/**
* A class to represent a task in the retry queue.
*/
class RetryTask {
/**
* The unique ID for the task.
* @type {string}
*/
id = Math.random().toString(36).slice(2);
/**
* The function to call.
* @type {Function}
*/
fn;
/**
* The error that was thrown.
* @type {Error}
*/
error;
/**
* The timestamp of the task.
* @type {number}
*/
timestamp = Date.now();
/**
* The timestamp of the last attempt.
* @type {number}
*/
lastAttempt = this.timestamp;
/**
* The resolve function for the promise.
* @type {Function}
*/
resolve;
/**
* The reject function for the promise.
* @type {Function}
*/
reject;
/**
* The AbortSignal to monitor for cancellation.
* @type {AbortSignal|undefined}
*/
signal;
/**
* Creates a new instance.
* @param {Function} fn The function to call.
* @param {Error} error The error that was thrown.
* @param {Function} resolve The resolve function for the promise.
* @param {Function} reject The reject function for the promise.
* @param {AbortSignal|undefined} signal The AbortSignal to monitor for cancellation.
*/
constructor(fn, error, resolve, reject, signal) {
this.fn = fn;
this.error = error;
this.timestamp = Date.now();
this.lastAttempt = Date.now();
this.resolve = resolve;
this.reject = reject;
this.signal = signal;
}
/**
* Gets the age of the task.
* @returns {number} The age of the task in milliseconds.
* @readonly
*/
get age() {
return Date.now() - this.timestamp;
}
}
//-----------------------------------------------------------------------------
// Exports
//-----------------------------------------------------------------------------
/**
* A class that manages a queue of retry jobs.
*/
class Retrier {
/**
* Represents the queue for processing tasks.
* @type {Array<RetryTask>}
*/
#queue = [];
/**
* The timeout for the queue.
* @type {number}
*/
#timeout;
/**
* The maximum delay for the queue.
* @type {number}
*/
#maxDelay;
/**
* The setTimeout() timer ID.
* @type {NodeJS.Timeout|undefined}
*/
#timerId;
/**
* The function to call.
* @type {Function}
*/
#check;
/**
* Creates a new instance.
* @param {Function} check The function to call.
* @param {object} [options] The options for the instance.
* @param {number} [options.timeout] The timeout for the queue.
* @param {number} [options.maxDelay] The maximum delay for the queue.
*/
constructor(check, { timeout = MAX_TASK_TIMEOUT, maxDelay = MAX_TASK_DELAY } = {}) {
if (typeof check !== "function") {
throw new Error("Missing function to check errors");
}
this.#check = check;
this.#timeout = timeout;
this.#maxDelay = maxDelay;
}
/**
* Adds a new retry job to the queue.
* @param {Function} fn The function to call.
* @param {object} [options] The options for the job.
* @param {AbortSignal} [options.signal] The AbortSignal to monitor for cancellation.
* @returns {Promise<any>} A promise that resolves when the queue is
* processed.
*/
retry(fn, { signal } = {}) {
signal?.throwIfAborted();
let result;
try {
result = fn();
} catch (/** @type {any} */ error) {
return Promise.reject(new Error(`Synchronous error: ${error.message}`, { cause: error }));
}
// if the result is not a promise then reject an error
if (!result || typeof result.then !== "function") {
return Promise.reject(new Error("Result is not a promise."));
}
// call the original function and catch any ENFILE or EMFILE errors
// @ts-ignore because we know it's any
return Promise.resolve(result).catch(error => {
if (!this.#check(error)) {
throw error;
}
return new Promise((resolve, reject) => {
this.#queue.push(new RetryTask(fn, error, resolve, reject, signal));
signal?.addEventListener("abort", () => {
reject(signal.reason);
});
this.#processQueue();
});
});
}
/**
* Processes the queue.
* @returns {void}
*/
#processQueue() {
// clear any timer because we're going to check right now
clearTimeout(this.#timerId);
this.#timerId = undefined;
// if there's nothing in the queue, we're done
const task = this.#queue.shift();
if (!task) {
return;
}
const processAgain = () => {
this.#timerId = setTimeout(() => this.#processQueue(), 0);
};
// if it's time to bail, then bail
if (isTimeToBail(task, this.#timeout)) {
task.reject(task.error);
processAgain();
return;
}
// if it's not time to retry, then wait and try again
if (!isTimeToRetry(task, this.#maxDelay)) {
this.#queue.push(task);
processAgain();
return;
}
// otherwise, try again
task.lastAttempt = Date.now();
// Promise.resolve needed in case it's a thenable but not a Promise
Promise.resolve(task.fn())
// @ts-ignore because we know it's any
.then(result => task.resolve(result))
// @ts-ignore because we know it's any
.catch(error => {
if (!this.#check(error)) {
task.reject(error);
return;
}
// update the task timestamp and push to back of queue to try again
task.lastAttempt = Date.now();
this.#queue.push(task);
})
.finally(() => this.#processQueue());
}
}
export { Retrier };

View File

@ -0,0 +1 @@
class RetryTask{id=Math.random().toString(36).slice(2);fn;error;timestamp=Date.now();lastAttempt=this.timestamp;resolve;reject;signal;constructor(t,e,r,s,i){this.fn=t,this.error=e,this.timestamp=Date.now(),this.lastAttempt=Date.now(),this.resolve=r,this.reject=s,this.signal=i}get age(){return Date.now()-this.timestamp}}class Retrier{#t=[];#e;#r;#s;#i;constructor(t,{timeout:e=6e4,maxDelay:r=100}={}){if("function"!=typeof t)throw new Error("Missing function to check errors");this.#i=t,this.#e=e,this.#r=r}retry(t,{signal:e}={}){let r;e?.throwIfAborted();try{r=t()}catch(t){return Promise.reject(new Error(`Synchronous error: ${t.message}`,{cause:t}))}return r&&"function"==typeof r.then?Promise.resolve(r).catch((r=>{if(!this.#i(r))throw r;return new Promise(((s,i)=>{this.#t.push(new RetryTask(t,r,s,i,e)),e?.addEventListener("abort",(()=>{i(e.reason)})),this.#o()}))})):Promise.reject(new Error("Result is not a promise."))}#o(){clearTimeout(this.#s),this.#s=void 0;const t=this.#t.shift();if(!t)return;const e=()=>{this.#s=setTimeout((()=>this.#o()),0)};return function(t,e){return t.age>e}(t,this.#e)?(t.reject(t.error),void e()):function(t,e){const r=Date.now()-t.lastAttempt,s=Math.max(t.lastAttempt-t.timestamp,1);return r>=Math.min(1.2*s,e)}(t,this.#r)?(t.lastAttempt=Date.now(),void Promise.resolve(t.fn()).then((e=>t.resolve(e))).catch((e=>{this.#i(e)?(t.lastAttempt=Date.now(),this.#t.push(t)):t.reject(e)})).finally((()=>this.#o()))):(this.#t.push(t),void e())}}export{Retrier};

View File

@ -0,0 +1,301 @@
/**
* @fileoverview A utility for retrying failed async method calls.
*/
/* global setTimeout, clearTimeout */
//-----------------------------------------------------------------------------
// Constants
//-----------------------------------------------------------------------------
const MAX_TASK_TIMEOUT = 60000;
const MAX_TASK_DELAY = 100;
//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
/*
* The following logic has been extracted from graceful-fs.
*
* The ISC License
*
* Copyright (c) 2011-2023 Isaac Z. Schlueter, Ben Noordhuis, and Contributors
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
* IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/**
* Checks if it is time to retry a task based on the timestamp and last attempt time.
* @param {RetryTask} task The task to check.
* @param {number} maxDelay The maximum delay for the queue.
* @returns {boolean} true if it is time to retry, false otherwise.
*/
function isTimeToRetry(task, maxDelay) {
const timeSinceLastAttempt = Date.now() - task.lastAttempt;
const timeSinceStart = Math.max(task.lastAttempt - task.timestamp, 1);
const desiredDelay = Math.min(timeSinceStart * 1.2, maxDelay);
return timeSinceLastAttempt >= desiredDelay;
}
/**
* Checks if it is time to bail out based on the given timestamp.
* @param {RetryTask} task The task to check.
* @param {number} timeout The timeout for the queue.
* @returns {boolean} true if it is time to bail, false otherwise.
*/
function isTimeToBail(task, timeout) {
return task.age > timeout;
}
/**
* A class to represent a task in the retry queue.
*/
class RetryTask {
/**
* The unique ID for the task.
* @type {string}
*/
id = Math.random().toString(36).slice(2);
/**
* The function to call.
* @type {Function}
*/
fn;
/**
* The error that was thrown.
* @type {Error}
*/
error;
/**
* The timestamp of the task.
* @type {number}
*/
timestamp = Date.now();
/**
* The timestamp of the last attempt.
* @type {number}
*/
lastAttempt = this.timestamp;
/**
* The resolve function for the promise.
* @type {Function}
*/
resolve;
/**
* The reject function for the promise.
* @type {Function}
*/
reject;
/**
* The AbortSignal to monitor for cancellation.
* @type {AbortSignal|undefined}
*/
signal;
/**
* Creates a new instance.
* @param {Function} fn The function to call.
* @param {Error} error The error that was thrown.
* @param {Function} resolve The resolve function for the promise.
* @param {Function} reject The reject function for the promise.
* @param {AbortSignal|undefined} signal The AbortSignal to monitor for cancellation.
*/
constructor(fn, error, resolve, reject, signal) {
this.fn = fn;
this.error = error;
this.timestamp = Date.now();
this.lastAttempt = Date.now();
this.resolve = resolve;
this.reject = reject;
this.signal = signal;
}
/**
* Gets the age of the task.
* @returns {number} The age of the task in milliseconds.
* @readonly
*/
get age() {
return Date.now() - this.timestamp;
}
}
//-----------------------------------------------------------------------------
// Exports
//-----------------------------------------------------------------------------
/**
* A class that manages a queue of retry jobs.
*/
class Retrier {
/**
* Represents the queue for processing tasks.
* @type {Array<RetryTask>}
*/
#queue = [];
/**
* The timeout for the queue.
* @type {number}
*/
#timeout;
/**
* The maximum delay for the queue.
* @type {number}
*/
#maxDelay;
/**
* The setTimeout() timer ID.
* @type {NodeJS.Timeout|undefined}
*/
#timerId;
/**
* The function to call.
* @type {Function}
*/
#check;
/**
* Creates a new instance.
* @param {Function} check The function to call.
* @param {object} [options] The options for the instance.
* @param {number} [options.timeout] The timeout for the queue.
* @param {number} [options.maxDelay] The maximum delay for the queue.
*/
constructor(check, { timeout = MAX_TASK_TIMEOUT, maxDelay = MAX_TASK_DELAY } = {}) {
if (typeof check !== "function") {
throw new Error("Missing function to check errors");
}
this.#check = check;
this.#timeout = timeout;
this.#maxDelay = maxDelay;
}
/**
* Adds a new retry job to the queue.
* @param {Function} fn The function to call.
* @param {object} [options] The options for the job.
* @param {AbortSignal} [options.signal] The AbortSignal to monitor for cancellation.
* @returns {Promise<any>} A promise that resolves when the queue is
* processed.
*/
retry(fn, { signal } = {}) {
signal?.throwIfAborted();
let result;
try {
result = fn();
} catch (/** @type {any} */ error) {
return Promise.reject(new Error(`Synchronous error: ${error.message}`, { cause: error }));
}
// if the result is not a promise then reject an error
if (!result || typeof result.then !== "function") {
return Promise.reject(new Error("Result is not a promise."));
}
// call the original function and catch any ENFILE or EMFILE errors
// @ts-ignore because we know it's any
return Promise.resolve(result).catch(error => {
if (!this.#check(error)) {
throw error;
}
return new Promise((resolve, reject) => {
this.#queue.push(new RetryTask(fn, error, resolve, reject, signal));
signal?.addEventListener("abort", () => {
reject(signal.reason);
});
this.#processQueue();
});
});
}
/**
* Processes the queue.
* @returns {void}
*/
#processQueue() {
// clear any timer because we're going to check right now
clearTimeout(this.#timerId);
this.#timerId = undefined;
// if there's nothing in the queue, we're done
const task = this.#queue.shift();
if (!task) {
return;
}
const processAgain = () => {
this.#timerId = setTimeout(() => this.#processQueue(), 0);
};
// if it's time to bail, then bail
if (isTimeToBail(task, this.#timeout)) {
task.reject(task.error);
processAgain();
return;
}
// if it's not time to retry, then wait and try again
if (!isTimeToRetry(task, this.#maxDelay)) {
this.#queue.push(task);
processAgain();
return;
}
// otherwise, try again
task.lastAttempt = Date.now();
// Promise.resolve needed in case it's a thenable but not a Promise
Promise.resolve(task.fn())
// @ts-ignore because we know it's any
.then(result => task.resolve(result))
// @ts-ignore because we know it's any
.catch(error => {
if (!this.#check(error)) {
task.reject(error);
return;
}
// update the task timestamp and push to back of queue to try again
task.lastAttempt = Date.now();
this.#queue.push(task);
})
.finally(() => this.#processQueue());
}
}
export { Retrier };

View File

@ -0,0 +1,77 @@
{
"name": "@humanwhocodes/retry",
"version": "0.3.1",
"description": "A utility to retry failed async methods.",
"type": "module",
"main": "dist/retrier.cjs",
"module": "dist/retrier.js",
"types": "dist/retrier.d.ts",
"exports": {
"require": {
"types": "./dist/retrier.d.cts",
"default": "./dist/retrier.cjs"
},
"import": {
"types": "./dist/retrier.d.ts",
"default": "./dist/retrier.js"
}
},
"files": [
"dist"
],
"engines": {
"node": ">=18.18"
},
"publishConfig": {
"access": "public"
},
"gitHooks": {
"pre-commit": "lint-staged"
},
"lint-staged": {
"*.js": [
"eslint --fix"
]
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/nzakas"
},
"scripts": {
"build:cts-types": "node -e \"fs.copyFileSync('dist/retrier.d.ts', 'dist/retrier.d.cts')\"",
"build": "rollup -c && tsc && npm run build:cts-types",
"prepare": "npm run build",
"lint": "eslint src/ tests/",
"pretest": "npm run build",
"test:unit": "mocha tests/retrier.test.js",
"test:build": "node tests/pkg.test.cjs && node tests/pkg.test.mjs",
"test:jsr": "npx jsr@latest publish --dry-run",
"test:emfile": "node tools/check-emfile-handling.js",
"test": "npm run test:unit && npm run test:build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/humanwhocodes/retry.git"
},
"keywords": [
"nodejs",
"retry",
"async",
"promises"
],
"author": "Nicholas C. Zaks",
"license": "Apache-2.0",
"devDependencies": {
"@eslint/js": "^8.49.0",
"@rollup/plugin-terser": "0.4.4",
"@tsconfig/node16": "^16.1.1",
"@types/mocha": "^10.0.3",
"@types/node": "20.12.6",
"eslint": "^8.21.0",
"lint-staged": "15.2.1",
"mocha": "^10.3.0",
"rollup": "3.29.4",
"typescript": "5.4.4",
"yorkie": "2.0.0"
}
}

57
node_modules/@humanfs/node/package.json generated vendored Normal file
View File

@ -0,0 +1,57 @@
{
"name": "@humanfs/node",
"version": "0.16.6",
"description": "The Node.js bindings of the humanfs library.",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
"import": {
"types": "./dist/index.d.ts",
"default": "./src/index.js"
}
},
"files": [
"dist",
"src"
],
"scripts": {
"build": "tsc",
"prepare": "npm run build",
"pretest": "npm run build",
"test": "mocha ./tests/"
},
"repository": {
"type": "git",
"url": "git+https://github.com/humanwhocodes/humanfs.git"
},
"publishConfig": {
"access": "public"
},
"keywords": [
"filesystem",
"fs",
"hfs",
"files"
],
"author": "Nicholas C. Zakas",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/humanwhocodes/humanfs/issues"
},
"homepage": "https://github.com/humanwhocodes/humanfs#readme",
"engines": {
"node": ">=18.18.0"
},
"devDependencies": {
"@types/node": "^20.9.4",
"@humanfs/test": "^0.15.0",
"@humanfs/types": "^0.15.0",
"mocha": "^10.2.0",
"typescript": "^5.2.2"
},
"dependencies": {
"@humanwhocodes/retry": "^0.3.0",
"@humanfs/core": "^0.19.1"
}
}

7
node_modules/@humanfs/node/src/index.js generated vendored Normal file
View File

@ -0,0 +1,7 @@
/**
* @fileoverview This file exports everything for this package.
* @author Nicholas C. Zakas
*/
export * from "./node-hfs.js";
export { Hfs } from "@humanfs/core";

452
node_modules/@humanfs/node/src/node-hfs.js generated vendored Normal file
View File

@ -0,0 +1,452 @@
/**
* @fileoverview The main file for the hfs package.
* @author Nicholas C. Zakas
*/
/* global Buffer:readonly, URL */
//-----------------------------------------------------------------------------
// Types
//-----------------------------------------------------------------------------
/** @typedef {import("@humanfs/types").HfsImpl} HfsImpl */
/** @typedef {import("@humanfs/types").HfsDirectoryEntry} HfsDirectoryEntry */
/** @typedef {import("node:fs/promises")} Fsp */
/** @typedef {import("fs").Dirent} Dirent */
//-----------------------------------------------------------------------------
// Imports
//-----------------------------------------------------------------------------
import { Hfs } from "@humanfs/core";
import path from "node:path";
import { Retrier } from "@humanwhocodes/retry";
import nativeFsp from "node:fs/promises";
import { fileURLToPath } from "node:url";
//-----------------------------------------------------------------------------
// Constants
//-----------------------------------------------------------------------------
const RETRY_ERROR_CODES = new Set(["ENFILE", "EMFILE"]);
//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
/**
* A class representing a directory entry.
* @implements {HfsDirectoryEntry}
*/
class NodeHfsDirectoryEntry {
/**
* The name of the directory entry.
* @type {string}
*/
name;
/**
* True if the entry is a file.
* @type {boolean}
*/
isFile;
/**
* True if the entry is a directory.
* @type {boolean}
*/
isDirectory;
/**
* True if the entry is a symbolic link.
* @type {boolean}
*/
isSymlink;
/**
* Creates a new instance.
* @param {Dirent} dirent The directory entry to wrap.
*/
constructor(dirent) {
this.name = dirent.name;
this.isFile = dirent.isFile();
this.isDirectory = dirent.isDirectory();
this.isSymlink = dirent.isSymbolicLink();
}
}
//-----------------------------------------------------------------------------
// Exports
//-----------------------------------------------------------------------------
/**
* A class representing the Node.js implementation of Hfs.
* @implements {HfsImpl}
*/
export class NodeHfsImpl {
/**
* The file system module to use.
* @type {Fsp}
*/
#fsp;
/**
* The retryer object used for retrying operations.
* @type {Retrier}
*/
#retrier;
/**
* Creates a new instance.
* @param {object} [options] The options for the instance.
* @param {Fsp} [options.fsp] The file system module to use.
*/
constructor({ fsp = nativeFsp } = {}) {
this.#fsp = fsp;
this.#retrier = new Retrier(error => RETRY_ERROR_CODES.has(error.code));
}
/**
* Reads a file and returns the contents as an Uint8Array.
* @param {string|URL} filePath The path to the file to read.
* @returns {Promise<Uint8Array|undefined>} A promise that resolves with the contents
* of the file or undefined if the file doesn't exist.
* @throws {Error} If the file cannot be read.
* @throws {TypeError} If the file path is not a string.
*/
bytes(filePath) {
return this.#retrier
.retry(() => this.#fsp.readFile(filePath))
.then(buffer => new Uint8Array(buffer.buffer))
.catch(error => {
if (error.code === "ENOENT") {
return undefined;
}
throw error;
});
}
/**
* Writes a value to a file. If the value is a string, UTF-8 encoding is used.
* @param {string|URL} filePath The path to the file to write.
* @param {Uint8Array} contents The contents to write to the
* file.
* @returns {Promise<void>} A promise that resolves when the file is
* written.
* @throws {TypeError} If the file path is not a string.
* @throws {Error} If the file cannot be written.
*/
async write(filePath, contents) {
const value = Buffer.from(contents);
return this.#retrier
.retry(() => this.#fsp.writeFile(filePath, value))
.catch(error => {
// the directory may not exist, so create it
if (error.code === "ENOENT") {
const dirPath = path.dirname(
filePath instanceof URL
? fileURLToPath(filePath)
: filePath,
);
return this.#fsp
.mkdir(dirPath, { recursive: true })
.then(() => this.#fsp.writeFile(filePath, value));
}
throw error;
});
}
/**
* Appends a value to a file. If the value is a string, UTF-8 encoding is used.
* @param {string|URL} filePath The path to the file to append to.
* @param {Uint8Array} contents The contents to append to the
* file.
* @returns {Promise<void>} A promise that resolves when the file is
* written.
* @throws {TypeError} If the file path is not a string.
* @throws {Error} If the file cannot be appended to.
*/
async append(filePath, contents) {
const value = Buffer.from(contents);
return this.#retrier
.retry(() => this.#fsp.appendFile(filePath, value))
.catch(error => {
// the directory may not exist, so create it
if (error.code === "ENOENT") {
const dirPath = path.dirname(
filePath instanceof URL
? fileURLToPath(filePath)
: filePath,
);
return this.#fsp
.mkdir(dirPath, { recursive: true })
.then(() => this.#fsp.appendFile(filePath, value));
}
throw error;
});
}
/**
* Checks if a file exists.
* @param {string|URL} filePath The path to the file to check.
* @returns {Promise<boolean>} A promise that resolves with true if the
* file exists or false if it does not.
* @throws {Error} If the operation fails with a code other than ENOENT.
*/
isFile(filePath) {
return this.#fsp
.stat(filePath)
.then(stat => stat.isFile())
.catch(error => {
if (error.code === "ENOENT") {
return false;
}
throw error;
});
}
/**
* Checks if a directory exists.
* @param {string|URL} dirPath The path to the directory to check.
* @returns {Promise<boolean>} A promise that resolves with true if the
* directory exists or false if it does not.
* @throws {Error} If the operation fails with a code other than ENOENT.
*/
isDirectory(dirPath) {
return this.#fsp
.stat(dirPath)
.then(stat => stat.isDirectory())
.catch(error => {
if (error.code === "ENOENT") {
return false;
}
throw error;
});
}
/**
* Creates a directory recursively.
* @param {string|URL} dirPath The path to the directory to create.
* @returns {Promise<void>} A promise that resolves when the directory is
* created.
*/
async createDirectory(dirPath) {
await this.#fsp.mkdir(dirPath, { recursive: true });
}
/**
* Deletes a file or empty directory.
* @param {string|URL} fileOrDirPath The path to the file or directory to
* delete.
* @returns {Promise<boolean>} A promise that resolves when the file or
* directory is deleted, true if the file or directory is deleted, false
* if the file or directory does not exist.
* @throws {TypeError} If the file or directory path is not a string.
* @throws {Error} If the file or directory cannot be deleted.
*/
delete(fileOrDirPath) {
return this.#fsp
.rm(fileOrDirPath)
.then(() => true)
.catch(error => {
if (error.code === "ERR_FS_EISDIR") {
return this.#fsp.rmdir(fileOrDirPath).then(() => true);
}
if (error.code === "ENOENT") {
return false;
}
throw error;
});
}
/**
* Deletes a file or directory recursively.
* @param {string|URL} fileOrDirPath The path to the file or directory to
* delete.
* @returns {Promise<boolean>} A promise that resolves when the file or
* directory is deleted, true if the file or directory is deleted, false
* if the file or directory does not exist.
* @throws {TypeError} If the file or directory path is not a string.
* @throws {Error} If the file or directory cannot be deleted.
*/
deleteAll(fileOrDirPath) {
return this.#fsp
.rm(fileOrDirPath, { recursive: true })
.then(() => true)
.catch(error => {
if (error.code === "ENOENT") {
return false;
}
throw error;
});
}
/**
* Returns a list of directory entries for the given path.
* @param {string|URL} dirPath The path to the directory to read.
* @returns {AsyncIterable<HfsDirectoryEntry>} A promise that resolves with the
* directory entries.
* @throws {TypeError} If the directory path is not a string.
* @throws {Error} If the directory cannot be read.
*/
async *list(dirPath) {
const entries = await this.#fsp.readdir(dirPath, {
withFileTypes: true,
});
for (const entry of entries) {
yield new NodeHfsDirectoryEntry(entry);
}
}
/**
* Returns the size of a file. This method handles ENOENT errors
* and returns undefined in that case.
* @param {string|URL} filePath The path to the file to read.
* @returns {Promise<number|undefined>} A promise that resolves with the size of the
* file in bytes or undefined if the file doesn't exist.
*/
size(filePath) {
return this.#fsp
.stat(filePath)
.then(stat => stat.size)
.catch(error => {
if (error.code === "ENOENT") {
return undefined;
}
throw error;
});
}
/**
* Returns the last modified date of a file or directory. This method handles ENOENT errors
* and returns undefined in that case.
* @param {string|URL} fileOrDirPath The path to the file to read.
* @returns {Promise<Date|undefined>} A promise that resolves with the last modified
* date of the file or directory, or undefined if the file doesn't exist.
*/
lastModified(fileOrDirPath) {
return this.#fsp
.stat(fileOrDirPath)
.then(stat => stat.mtime)
.catch(error => {
if (error.code === "ENOENT") {
return undefined;
}
throw error;
});
}
/**
* Copies a file from one location to another.
* @param {string|URL} source The path to the file to copy.
* @param {string|URL} destination The path to copy the file to.
* @returns {Promise<void>} A promise that resolves when the file is copied.
* @throws {Error} If the source file does not exist.
* @throws {Error} If the source file is a directory.
* @throws {Error} If the destination file is a directory.
*/
copy(source, destination) {
return this.#fsp.copyFile(source, destination);
}
/**
* Copies a file or directory from one location to another.
* @param {string|URL} source The path to the file or directory to copy.
* @param {string|URL} destination The path to copy the file or directory to.
* @returns {Promise<void>} A promise that resolves when the file or directory is
* copied.
* @throws {Error} If the source file or directory does not exist.
* @throws {Error} If the destination file or directory is a directory.
*/
async copyAll(source, destination) {
// for files use copy() and exit
if (await this.isFile(source)) {
return this.copy(source, destination);
}
const sourceStr =
source instanceof URL ? fileURLToPath(source) : source;
const destinationStr =
destination instanceof URL
? fileURLToPath(destination)
: destination;
// for directories, create the destination directory and copy each entry
await this.createDirectory(destination);
for await (const entry of this.list(source)) {
const fromEntryPath = path.join(sourceStr, entry.name);
const toEntryPath = path.join(destinationStr, entry.name);
if (entry.isDirectory) {
await this.copyAll(fromEntryPath, toEntryPath);
} else {
await this.copy(fromEntryPath, toEntryPath);
}
}
}
/**
* Moves a file from the source path to the destination path.
* @param {string|URL} source The location of the file to move.
* @param {string|URL} destination The destination of the file to move.
* @returns {Promise<void>} A promise that resolves when the move is complete.
* @throws {TypeError} If the file paths are not strings.
* @throws {Error} If the file cannot be moved.
*/
move(source, destination) {
return this.#fsp.stat(source).then(stat => {
if (stat.isDirectory()) {
throw new Error(
`EISDIR: illegal operation on a directory, move '${source}' -> '${destination}'`,
);
}
return this.#fsp.rename(source, destination);
});
}
/**
* Moves a file or directory from the source path to the destination path.
* @param {string|URL} source The location of the file or directory to move.
* @param {string|URL} destination The destination of the file or directory to move.
* @returns {Promise<void>} A promise that resolves when the move is complete.
* @throws {TypeError} If the file paths are not strings.
* @throws {Error} If the file or directory cannot be moved.
*/
async moveAll(source, destination) {
return this.#fsp.rename(source, destination);
}
}
/**
* A class representing a file system utility library.
* @implements {HfsImpl}
*/
export class NodeHfs extends Hfs {
/**
* Creates a new instance.
* @param {object} [options] The options for the instance.
* @param {Fsp} [options.fsp] The file system module to use.
*/
constructor({ fsp } = {}) {
super({ impl: new NodeHfsImpl({ fsp }) });
}
}
export const hfs = new NodeHfs();