UNPKG

@zenfs/core

Version:

A filesystem, anywhere

835 lines (769 loc) 34.7 kB
import { Buffer } from 'buffer'; import type * as fs from 'node:fs'; import { Errno, ErrnoError } from '../error.js'; import type { FileContents } from '../filesystem.js'; import { BigIntStats, type Stats } from '../stats.js'; import { normalizeMode, normalizePath, type Callback } from '../utils.js'; import { R_OK } from './constants.js'; import type { Dirent } from './dir.js'; import type { Dir } from './dir.js'; import * as promises from './promises.js'; import { fd2file } from './shared.js'; import { ReadStream, WriteStream } from './streams.js'; import { FSWatcher, StatWatcher } from './watchers.js'; const nop = () => {}; /** * Asynchronous rename. No arguments other than a possible exception are given to the completion callback. */ export function rename(oldPath: fs.PathLike, newPath: fs.PathLike, cb: Callback = nop): void { promises .rename(oldPath, newPath) .then(() => cb()) .catch(cb); } rename satisfies Omit<typeof fs.rename, '__promisify__'>; /** * Test whether or not `path` exists by checking with the file system. * Then call the callback argument with either true or false. * @deprecated Use {@link stat} or {@link access} instead. */ export function exists(path: fs.PathLike, cb: (exists: boolean) => unknown = nop): void { promises .exists(path) .then(cb) .catch(() => cb(false)); } exists satisfies Omit<typeof fs.exists, '__promisify__'>; export function stat(path: fs.PathLike, callback: Callback<[Stats]>): void; export function stat(path: fs.PathLike, options: { bigint?: false }, callback: Callback<[Stats]>): void; export function stat(path: fs.PathLike, options: { bigint: true }, callback: Callback<[BigIntStats]>): void; export function stat(path: fs.PathLike, options: fs.StatOptions, callback: Callback<[Stats] | [BigIntStats]>): void; export function stat(path: fs.PathLike, options?: fs.StatOptions | Callback<[Stats]>, callback: Callback<[Stats]> | Callback<[BigIntStats]> = nop): void { callback = typeof options == 'function' ? options : callback; promises .stat(path, typeof options != 'function' ? options : {}) .then(stats => (callback as Callback<[Stats] | [BigIntStats]>)(undefined, stats as any)) .catch(callback); } stat satisfies Omit<typeof fs.stat, '__promisify__'>; /** * Asynchronous `lstat`. * `lstat()` is identical to `stat()`, except that if path is a symbolic link, * then the link itself is stat-ed, not the file that it refers to. */ export function lstat(path: fs.PathLike, callback: Callback<[Stats]>): void; export function lstat(path: fs.PathLike, options: fs.StatOptions & { bigint?: false }, callback: Callback<[Stats]>): void; export function lstat(path: fs.PathLike, options: fs.StatOptions & { bigint: true }, callback: Callback<[BigIntStats]>): void; export function lstat(path: fs.PathLike, options: fs.StatOptions, callback: Callback<[Stats | BigIntStats]>): void; export function lstat(path: fs.PathLike, options?: fs.StatOptions | Callback<[Stats]>, callback: Callback<[Stats]> | Callback<[BigIntStats]> = nop): void { callback = typeof options == 'function' ? options : callback; promises .lstat(path, typeof options != 'function' ? options : ({} as object)) .then(stats => (callback as Callback<[Stats] | [BigIntStats]>)(undefined, stats)) .catch(callback); } lstat satisfies Omit<typeof fs.lstat, '__promisify__'>; export function truncate(path: fs.PathLike, cb?: Callback): void; export function truncate(path: fs.PathLike, len: number, cb?: Callback): void; export function truncate(path: fs.PathLike, cbLen: number | Callback = 0, cb: Callback = nop): void { cb = typeof cbLen === 'function' ? cbLen : cb; const len = typeof cbLen === 'number' ? cbLen : 0; promises .truncate(path, len) .then(() => cb()) .catch(cb); } truncate satisfies Omit<typeof fs.truncate, '__promisify__'>; export function unlink(path: fs.PathLike, cb: Callback = nop): void { promises .unlink(path) .then(() => cb()) .catch(cb); } unlink satisfies Omit<typeof fs.unlink, '__promisify__'>; /** * Asynchronous file open. * Exclusive mode ensures that path is newly created. * Mode defaults to `0644` * * `flags` can be: * * * `'r'` - Open file for reading. An exception occurs if the file does not exist. * * `'r+'` - Open file for reading and writing. An exception occurs if the file does not exist. * * `'rs'` - Open file for reading in synchronous mode. Instructs the filesystem to not cache writes. * * `'rs+'` - Open file for reading and writing, and opens the file in synchronous mode. * * `'w'` - Open file for writing. The file is created (if it does not exist) or truncated (if it exists). * * `'wx'` - Like 'w' but opens the file in exclusive mode. * * `'w+'` - Open file for reading and writing. The file is created (if it does not exist) or truncated (if it exists). * * `'wx+'` - Like 'w+' but opens the file in exclusive mode. * * `'a'` - Open file for appending. The file is created if it does not exist. * * `'ax'` - Like 'a' but opens the file in exclusive mode. * * `'a+'` - Open file for reading and appending. The file is created if it does not exist. * * `'ax+'` - Like 'a+' but opens the file in exclusive mode. * * @see http://www.manpagez.com/man/2/open/ */ export function open(path: fs.PathLike, flag: string, cb?: Callback<[number]>): void; export function open(path: fs.PathLike, flag: string, mode: number | string, cb?: Callback<[number]>): void; export function open(path: fs.PathLike, flag: string, cbMode?: number | string | Callback<[number]>, cb: Callback<[number]> = nop): void { const mode = normalizeMode(cbMode, 0o644); cb = typeof cbMode === 'function' ? cbMode : cb; promises .open(path, flag, mode) .then(handle => cb(undefined, handle.fd)) .catch(cb); } open satisfies Omit<typeof fs.open, '__promisify__'>; /** * Asynchronously reads the entire contents of a file. * @option encoding The string encoding for the file contents. Defaults to `null`. * @option flag Defaults to `'r'`. * @param cb If no encoding is specified, then the raw buffer is returned. */ export function readFile(filename: fs.PathLike, cb: Callback<[Uint8Array]>): void; export function readFile(filename: fs.PathLike, options: { flag?: string }, callback?: Callback<[Uint8Array]>): void; export function readFile(filename: fs.PathLike, options: { encoding: BufferEncoding; flag?: string } | BufferEncoding, cb: Callback<[string]>): void; export function readFile(filename: fs.PathLike, options?: fs.WriteFileOptions | BufferEncoding | Callback<[Uint8Array]>, cb: Callback<[string]> | Callback<[Uint8Array]> = nop) { cb = typeof options === 'function' ? options : cb; promises .readFile(filename, typeof options === 'function' ? null : options) .then(data => (cb as Callback<[string | Uint8Array]>)(undefined, data)) .catch(cb); } readFile satisfies Omit<typeof fs.readFile, '__promisify__'>; /** * Asynchronously writes data to a file, replacing the file if it already * exists. * * The encoding option is ignored if data is a buffer. * * @option encoding Defaults to `'utf8'`. * @option mode Defaults to `0644`. * @option flag Defaults to `'w'`. */ export function writeFile(filename: fs.PathLike, data: FileContents, cb?: Callback): void; export function writeFile(filename: fs.PathLike, data: FileContents, encoding?: BufferEncoding, cb?: Callback): void; export function writeFile(filename: fs.PathLike, data: FileContents, options?: fs.WriteFileOptions, cb?: Callback): void; export function writeFile(filename: fs.PathLike, data: FileContents, cbEncOpts?: fs.WriteFileOptions | Callback, cb: Callback = nop): void { cb = typeof cbEncOpts === 'function' ? cbEncOpts : cb; promises .writeFile(filename, data, typeof cbEncOpts != 'function' ? cbEncOpts : null) .then(() => cb(undefined)) .catch(cb); } writeFile satisfies Omit<typeof fs.writeFile, '__promisify__'>; /** * Asynchronously append data to a file, creating the file if it not yet * exists. * * @option encoding Defaults to `'utf8'`. * @option mode Defaults to `0644`. * @option flag Defaults to `'a'`. */ export function appendFile(filename: fs.PathLike, data: FileContents, cb?: Callback): void; export function appendFile(filename: fs.PathLike, data: FileContents, options?: fs.EncodingOption & { mode?: fs.Mode; flag?: fs.OpenMode }, cb?: Callback): void; export function appendFile(filename: fs.PathLike, data: FileContents, encoding?: BufferEncoding, cb?: Callback): void; export function appendFile( filename: fs.PathLike, data: FileContents, cbEncOpts?: (fs.EncodingOption & { mode?: fs.Mode; flag?: fs.OpenMode }) | Callback, cb: Callback = nop ): void { const optionsOrEncoding = typeof cbEncOpts != 'function' ? cbEncOpts : undefined; cb = typeof cbEncOpts === 'function' ? cbEncOpts : cb; promises .appendFile(filename, data, optionsOrEncoding) .then(() => cb()) .catch(cb); } appendFile satisfies Omit<typeof fs.appendFile, '__promisify__'>; /** * Asynchronous `fstat`. * `fstat()` is identical to `stat()`, except that the file to be stat-ed is specified by the file descriptor `fd`. */ export function fstat(fd: number, cb: Callback<[Stats]>): void; export function fstat(fd: number, options: fs.StatOptions & { bigint?: false }, cb: Callback<[Stats]>): void; export function fstat(fd: number, options: fs.StatOptions & { bigint: true }, cb: Callback<[BigIntStats]>): void; export function fstat(fd: number, options?: fs.StatOptions | Callback<[Stats]>, cb: Callback<[Stats]> | Callback<[BigIntStats]> = nop): void { cb = typeof options == 'function' ? options : cb; fd2file(fd) .stat() .then(stats => (cb as Callback<[Stats | BigIntStats]>)(undefined, typeof options == 'object' && options?.bigint ? new BigIntStats(stats) : stats)) .catch(cb); } fstat satisfies Omit<typeof fs.fstat, '__promisify__'>; export function close(fd: number, cb: Callback = nop): void { new promises.FileHandle(fd) .close() .then(() => cb()) .catch(cb); } close satisfies Omit<typeof fs.close, '__promisify__'>; export function ftruncate(fd: number, cb?: Callback): void; export function ftruncate(fd: number, len?: number, cb?: Callback): void; export function ftruncate(fd: number, lenOrCB?: number | Callback, cb: Callback = nop): void { const length = typeof lenOrCB === 'number' ? lenOrCB : 0; cb = typeof lenOrCB === 'function' ? lenOrCB : cb; const file = fd2file(fd); if (length < 0) { throw new ErrnoError(Errno.EINVAL); } file.truncate(length) .then(() => cb()) .catch(cb); } ftruncate satisfies Omit<typeof fs.ftruncate, '__promisify__'>; export function fsync(fd: number, cb: Callback = nop): void { fd2file(fd) .sync() .then(() => cb()) .catch(cb); } fsync satisfies Omit<typeof fs.fsync, '__promisify__'>; export function fdatasync(fd: number, cb: Callback = nop): void { fd2file(fd) .datasync() .then(() => cb()) .catch(cb); } fdatasync satisfies Omit<typeof fs.fdatasync, '__promisify__'>; /** * Write buffer to the file specified by `fd`. * Note that it is unsafe to use fs.write multiple times on the same file without waiting for the callback. * @param buffer Uint8Array containing the data to write to the file. * @param offset Offset in the buffer to start reading data from. * @param length The amount of bytes to write to the file. * @param position Offset from the beginning of the file where this data should be written. * If position is null, the data will be written at the current position. * @param cb The number specifies the number of bytes written into the file. */ export function write(fd: number, buffer: Uint8Array, offset: number, length: number, cb?: Callback<[number, Uint8Array]>): void; export function write(fd: number, buffer: Uint8Array, offset: number, length: number, position?: number, cb?: Callback<[number, Uint8Array]>): void; export function write(fd: number, data: FileContents, cb?: Callback<[number, string]>): void; export function write(fd: number, data: FileContents, position?: number, cb?: Callback<[number, string]>): void; export function write(fd: number, data: FileContents, position: number | null, encoding: BufferEncoding, cb?: Callback<[number, string]>): void; export function write( fd: number, data: FileContents, cbPosOff?: number | Callback<[number, string]> | null, cbLenEnc?: number | BufferEncoding | Callback<[number, string]>, cbPosEnc?: number | BufferEncoding | Callback<[number, Uint8Array]> | Callback<[number, string]>, cb: Callback<[number, Uint8Array]> | Callback<[number, string]> = nop ): void { let buffer: Buffer, offset: number | undefined, length: number | undefined, position: number | undefined | null, encoding: BufferEncoding; const handle = new promises.FileHandle(fd); if (typeof data === 'string') { // Signature 1: (fd, string, [position?, [encoding?]], cb?) encoding = 'utf8'; switch (typeof cbPosOff) { case 'function': // (fd, string, cb) cb = cbPosOff; break; case 'number': // (fd, string, position, encoding?, cb?) position = cbPosOff; encoding = typeof cbLenEnc === 'string' ? cbLenEnc : 'utf8'; cb = typeof cbPosEnc === 'function' ? cbPosEnc : cb; break; default: // ...try to find the callback and get out of here! cb = (typeof cbLenEnc === 'function' ? cbLenEnc : typeof cbPosEnc === 'function' ? cbPosEnc : cb) as Callback<[number, Uint8Array | string]>; (cb as Callback<[number, Uint8Array | string]>)(new ErrnoError(Errno.EINVAL, 'Invalid arguments.')); return; } buffer = Buffer.from(data); offset = 0; length = buffer.length; const _cb = cb as Callback<[number, string]>; handle .write(buffer, offset, length, position) .then(({ bytesWritten }) => _cb(undefined, bytesWritten, buffer.toString(encoding))) .catch(_cb); } else { // Signature 2: (fd, buffer, offset, length, position?, cb?) buffer = Buffer.from(data.buffer); offset = cbPosOff as number; length = cbLenEnc as number; position = typeof cbPosEnc === 'number' ? cbPosEnc : null; const _cb = (typeof cbPosEnc === 'function' ? cbPosEnc : cb) as Callback<[number, Uint8Array]>; void handle .write(buffer, offset, length, position) .then(({ bytesWritten }) => _cb(undefined, bytesWritten, buffer)) .catch(_cb); } } write satisfies Omit<typeof fs.write, '__promisify__'>; /** * Read data from the file specified by `fd`. * @param buffer The buffer that the data will be written to. * @param offset The offset within the buffer where writing will start. * @param length An integer specifying the number of bytes to read. * @param position An integer specifying where to begin reading from in the file. * If position is null, data will be read from the current file position. * @param cb The number is the number of bytes read */ export function read(fd: number, buffer: Uint8Array, offset: number, length: number, position?: number, cb: Callback<[number, Uint8Array]> = nop): void { new promises.FileHandle(fd) .read(buffer, offset, length, position) .then(({ bytesRead, buffer }) => cb(undefined, bytesRead, buffer)) .catch(cb); } read satisfies Omit<typeof fs.read, '__promisify__'>; export function fchown(fd: number, uid: number, gid: number, cb: Callback = nop): void { new promises.FileHandle(fd) .chown(uid, gid) .then(() => cb()) .catch(cb); } fchown satisfies Omit<typeof fs.fchown, '__promisify__'>; export function fchmod(fd: number, mode: string | number, cb: Callback): void { new promises.FileHandle(fd) .chmod(mode) .then(() => cb()) .catch(cb); } fchmod satisfies Omit<typeof fs.fchmod, '__promisify__'>; /** * Change the file timestamps of a file referenced by the supplied file descriptor. */ export function futimes(fd: number, atime: number | Date, mtime: number | Date, cb: Callback = nop): void { new promises.FileHandle(fd) .utimes(atime, mtime) .then(() => cb()) .catch(cb); } futimes satisfies Omit<typeof fs.futimes, '__promisify__'>; export function rmdir(path: fs.PathLike, cb: Callback = nop): void { promises .rmdir(path) .then(() => cb()) .catch(cb); } rmdir satisfies Omit<typeof fs.rmdir, '__promisify__'>; /** * Asynchronous `mkdir`. * @param mode defaults to `0777` */ export function mkdir(path: fs.PathLike, mode?: fs.Mode, cb: Callback = nop): void { promises .mkdir(path, mode) .then(() => cb()) .catch(cb); } mkdir satisfies Omit<typeof fs.mkdir, '__promisify__'>; /** * Asynchronous `readdir`. Reads the contents of a directory. * The callback gets two arguments `(err, files)` where `files` is an array of * the names of the files in the directory excluding `'.'` and `'..'`. */ export function readdir(path: fs.PathLike, cb: Callback<[string[]]>): void; export function readdir(path: fs.PathLike, options: { withFileTypes?: false }, cb: Callback<[string[]]>): void; export function readdir(path: fs.PathLike, options: { withFileTypes: true }, cb: Callback<[Dirent[]]>): void; export function readdir(path: fs.PathLike, _options: { withFileTypes?: boolean } | Callback<[string[]]>, cb: Callback<[string[]]> | Callback<[Dirent[]]> = nop): void { cb = typeof _options == 'function' ? _options : cb; const options = typeof _options != 'function' ? _options : {}; promises .readdir(path, options as object) .then(entries => cb(undefined, entries as any)) .catch(cb); } readdir satisfies Omit<typeof fs.readdir, '__promisify__'>; export function link(existing: fs.PathLike, newpath: fs.PathLike, cb: Callback = nop): void { promises .link(existing, newpath) .then(() => cb()) .catch(cb); } link satisfies Omit<typeof fs.link, '__promisify__'>; /** * Asynchronous `symlink`. * @param target target path * @param path link path * Type defaults to file */ export function symlink(target: fs.PathLike, path: fs.PathLike, cb?: Callback): void; export function symlink(target: fs.PathLike, path: fs.PathLike, type?: fs.symlink.Type, cb?: Callback): void; export function symlink(target: fs.PathLike, path: fs.PathLike, typeOrCB?: fs.symlink.Type | Callback, cb: Callback = nop): void { const type = typeof typeOrCB === 'string' ? typeOrCB : 'file'; cb = typeof typeOrCB === 'function' ? typeOrCB : cb; promises .symlink(target, path, type) .then(() => cb()) .catch(cb); } symlink satisfies Omit<typeof fs.symlink, '__promisify__'>; export function readlink(path: fs.PathLike, callback: Callback<[string]>): void; export function readlink(path: fs.PathLike, options: fs.BufferEncodingOption, callback: Callback<[Uint8Array]>): void; export function readlink(path: fs.PathLike, options: fs.EncodingOption, callback: Callback<[string | Uint8Array]>): void; export function readlink(path: fs.PathLike, options: fs.EncodingOption, callback: Callback<[string]>): void; export function readlink( path: fs.PathLike, options: fs.BufferEncodingOption | fs.EncodingOption | Callback<[string]>, callback: Callback<[string]> | Callback<[Uint8Array]> = nop ): void { callback = typeof options == 'function' ? options : callback; promises .readlink(path) .then(result => (callback as Callback<[string | Uint8Array]>)(undefined, result)) .catch(callback); } readlink satisfies Omit<typeof fs.readlink, '__promisify__'>; export function chown(path: fs.PathLike, uid: number, gid: number, cb: Callback = nop): void { promises .chown(path, uid, gid) .then(() => cb()) .catch(cb); } chown satisfies Omit<typeof fs.chown, '__promisify__'>; export function lchown(path: fs.PathLike, uid: number, gid: number, cb: Callback = nop): void { promises .lchown(path, uid, gid) .then(() => cb()) .catch(cb); } lchown satisfies Omit<typeof fs.lchown, '__promisify__'>; export function chmod(path: fs.PathLike, mode: number | string, cb: Callback = nop): void { promises .chmod(path, mode) .then(() => cb()) .catch(cb); } chmod satisfies Omit<typeof fs.chmod, '__promisify__'>; export function lchmod(path: fs.PathLike, mode: number | string, cb: Callback = nop): void { promises .lchmod(path, mode) .then(() => cb()) .catch(cb); } lchmod satisfies Omit<typeof fs.lchmod, '__promisify__'>; /** * Change file timestamps of the file referenced by the supplied path. */ export function utimes(path: fs.PathLike, atime: number | Date, mtime: number | Date, cb: Callback = nop): void { promises .utimes(path, atime, mtime) .then(() => cb()) .catch(cb); } utimes satisfies Omit<typeof fs.utimes, '__promisify__'>; /** * Change file timestamps of the file referenced by the supplied path. */ export function lutimes(path: fs.PathLike, atime: number | Date, mtime: number | Date, cb: Callback = nop): void { promises .lutimes(path, atime, mtime) .then(() => cb()) .catch(cb); } lutimes satisfies Omit<typeof fs.lutimes, '__promisify__'>; /** * Asynchronous `realpath`. The callback gets two arguments * `(err, resolvedPath)`. May use `process.cwd` to resolve relative paths. */ export function realpath(path: fs.PathLike, cb?: Callback<[string]>): void; export function realpath(path: fs.PathLike, options: fs.EncodingOption, cb: Callback<[string]>): void; export function realpath(path: fs.PathLike, arg2?: Callback<[string]> | fs.EncodingOption, cb: Callback<[string]> = nop): void { cb = typeof arg2 === 'function' ? arg2 : cb; promises .realpath(path, typeof arg2 === 'function' ? null : arg2) .then(result => cb(undefined, result)) .catch(cb); } realpath satisfies Omit<typeof fs.realpath, '__promisify__' | 'native'>; export function access(path: fs.PathLike, cb: Callback): void; export function access(path: fs.PathLike, mode: number, cb: Callback): void; export function access(path: fs.PathLike, cbMode: number | Callback, cb: Callback = nop): void { const mode = typeof cbMode === 'number' ? cbMode : R_OK; cb = typeof cbMode === 'function' ? cbMode : cb; promises .access(path, mode) .then(() => cb()) .catch(cb); } access satisfies Omit<typeof fs.access, '__promisify__'>; const statWatchers: Map<string, { watcher: StatWatcher; listeners: Set<(curr: Stats, prev: Stats) => void> }> = new Map(); /** * Watch for changes on a file. The callback listener will be called each time the file is accessed. * * The `options` argument may be omitted. If provided, it should be an object with a `persistent` boolean and an `interval` number specifying the polling interval in milliseconds. * * When a change is detected, the `listener` callback is called with the current and previous `Stats` objects. * * @param path The path to the file to watch. * @param options Optional options object specifying `persistent` and `interval`. * @param listener The callback listener to be called when the file changes. */ export function watchFile(path: fs.PathLike, listener: (curr: Stats, prev: Stats) => void): void; export function watchFile(path: fs.PathLike, options: { persistent?: boolean; interval?: number }, listener: (curr: Stats, prev: Stats) => void): void; export function watchFile( path: fs.PathLike, options: { persistent?: boolean; interval?: number } | ((curr: Stats, prev: Stats) => void), listener?: (curr: Stats, prev: Stats) => void ): void { const normalizedPath = normalizePath(path.toString()); const opts = typeof options != 'function' ? options : {}; if (typeof options == 'function') { listener = options; } if (!listener) { throw new ErrnoError(Errno.EINVAL, 'No listener specified', path.toString(), 'watchFile'); } if (statWatchers.has(normalizedPath)) { const entry = statWatchers.get(normalizedPath); if (entry) { entry.listeners.add(listener); } return; } const watcher = new StatWatcher(normalizedPath, opts); watcher.on('change', (curr: Stats, prev: Stats) => { const entry = statWatchers.get(normalizedPath); if (!entry) { return; } for (const listener of entry.listeners) { listener(curr, prev); } }); statWatchers.set(normalizedPath, { watcher, listeners: new Set() }); } watchFile satisfies Omit<typeof fs.watchFile, '__promisify__'>; /** * Stop watching for changes on a file. * * If the `listener` is specified, only that particular listener is removed. * If no `listener` is specified, all listeners are removed, and the file is no longer watched. * * @param path The path to the file to stop watching. * @param listener Optional listener to remove. */ export function unwatchFile(path: fs.PathLike, listener: (curr: Stats, prev: Stats) => void = nop): void { const normalizedPath = normalizePath(path.toString()); const entry = statWatchers.get(normalizedPath); if (entry) { if (listener && listener !== nop) { entry.listeners.delete(listener); } else { // If no listener is specified, remove all listeners entry.listeners.clear(); } if (entry.listeners.size === 0) { // No more listeners, stop the watcher entry.watcher.stop(); statWatchers.delete(normalizedPath); } } } unwatchFile satisfies Omit<typeof fs.unwatchFile, '__promisify__'>; export function watch(path: fs.PathLike, listener?: (event: string, filename: string) => any): FSWatcher; export function watch(path: fs.PathLike, options: { persistent?: boolean }, listener?: (event: string, filename: string) => any): FSWatcher; export function watch(path: fs.PathLike, options?: fs.WatchOptions | ((event: string, filename: string) => any), listener?: (event: string, filename: string) => any): FSWatcher { const watcher = new FSWatcher<string>(normalizePath(path), typeof options == 'object' ? options : {}); listener = typeof options == 'function' ? options : listener; watcher.on('change', listener || nop); return watcher; } watch satisfies Omit<typeof fs.watch, '__promisify__'>; // From @types/node/fs (these types are not exported) interface StreamOptions { flags?: string; encoding?: BufferEncoding; fd?: number | promises.FileHandle; mode?: number; autoClose?: boolean; emitClose?: boolean; start?: number; signal?: AbortSignal; highWaterMark?: number; } interface FSImplementation { open?: (...args: unknown[]) => unknown; close?: (...args: unknown[]) => unknown; } interface ReadStreamOptions extends StreamOptions { fs?: FSImplementation & { read: (...args: unknown[]) => unknown; }; end?: number; } interface WriteStreamOptions extends StreamOptions { fs?: FSImplementation & { write: (...args: unknown[]) => unknown; writev?: (...args: unknown[]) => unknown; }; flush?: boolean; } /** * Opens a file in read mode and creates a Node.js-like ReadStream. * * @param path The path to the file to be opened. * @param options Options for the ReadStream and file opening (e.g., `encoding`, `highWaterMark`, `mode`). * @returns A ReadStream object for interacting with the file's contents. */ export function createReadStream(path: fs.PathLike, options?: BufferEncoding | ReadStreamOptions): ReadStream { options = typeof options == 'object' ? options : { encoding: options }; let handle: promises.FileHandle; const stream = new ReadStream({ highWaterMark: options.highWaterMark || 64 * 1024, encoding: options.encoding || 'utf8', async read(size: number) { try { handle ||= await promises.open(path, 'r', options?.mode); const result = await handle.read(new Uint8Array(size), 0, size, handle.file.position); stream.push(!result.bytesRead ? null : result.buffer.slice(0, result.bytesRead)); handle.file.position += result.bytesRead; if (!result.bytesRead) { await handle.close(); } } catch (error: any) { await handle?.close(); stream.destroy(error); } }, destroy(error, callback) { handle ?.close() .then(() => callback(error)) .catch(nop); }, }); stream.path = path.toString(); return stream; } createReadStream satisfies Omit<typeof fs.createReadStream, '__promisify__'>; /** * Opens a file in write mode and creates a Node.js-like WriteStream. * * @param path The path to the file to be opened. * @param options Options for the WriteStream and file opening (e.g., `encoding`, `highWaterMark`, `mode`). * @returns A WriteStream object for writing to the file. */ export function createWriteStream(path: fs.PathLike, options?: BufferEncoding | WriteStreamOptions): WriteStream { options = typeof options == 'object' ? options : { encoding: options }; let handle: promises.FileHandle; const stream = new WriteStream({ highWaterMark: options?.highWaterMark, async write(chunk: Uint8Array, encoding: BufferEncoding, callback: (error?: Error) => void) { try { handle ||= await promises.open(path, 'w', options?.mode || 0o666); await handle.write(chunk, 0, encoding); callback(undefined); } catch (error: any) { await handle?.close(); callback(error); } }, destroy(error, callback) { callback(error); handle ?.close() .then(() => callback(error)) .catch(callback); }, final(callback) { handle ?.close() .then(() => callback()) .catch(callback); }, }); stream.path = path.toString(); return stream; } createWriteStream satisfies Omit<typeof fs.createWriteStream, '__promisify__'>; export function rm(path: fs.PathLike, callback: Callback): void; export function rm(path: fs.PathLike, options: fs.RmOptions, callback: Callback): void; export function rm(path: fs.PathLike, options: fs.RmOptions | Callback, callback: Callback = nop): void { callback = typeof options === 'function' ? options : callback; promises .rm(path, typeof options === 'function' ? undefined : options) .then(() => callback(undefined)) .catch(callback); } rm satisfies Omit<typeof fs.rm, '__promisify__'>; /** * Asynchronously creates a unique temporary directory. * Generates six random characters to be appended behind a required prefix to create a unique temporary directory. */ export function mkdtemp(prefix: string, callback: Callback<[string]>): void; export function mkdtemp(prefix: string, options: fs.EncodingOption, callback: Callback<[string]>): void; export function mkdtemp(prefix: string, options: fs.BufferEncodingOption, callback: Callback<[Buffer]>): void; export function mkdtemp(prefix: string, options: fs.EncodingOption | fs.BufferEncodingOption | Callback<[string]>, callback: Callback<[Buffer]> | Callback<[string]> = nop): void { callback = typeof options === 'function' ? options : callback; promises .mkdtemp(prefix, typeof options != 'function' ? (options as fs.EncodingOption) : null) .then(result => (callback as Callback<[string | Buffer]>)(undefined, result)) .catch(callback); } mkdtemp satisfies Omit<typeof fs.mkdtemp, '__promisify__'>; export function copyFile(src: fs.PathLike, dest: fs.PathLike, callback: Callback): void; export function copyFile(src: fs.PathLike, dest: fs.PathLike, flags: number, callback: Callback): void; export function copyFile(src: fs.PathLike, dest: fs.PathLike, flags: number | Callback, callback: Callback = nop): void { callback = typeof flags === 'function' ? flags : callback; promises .copyFile(src, dest, typeof flags === 'function' ? undefined : flags) .then(() => callback(undefined)) .catch(callback); } copyFile satisfies Omit<typeof fs.copyFile, '__promisify__'>; type readvCb = Callback<[number, NodeJS.ArrayBufferView[]]>; export function readv(fd: number, buffers: NodeJS.ArrayBufferView[], cb: readvCb): void; export function readv(fd: number, buffers: NodeJS.ArrayBufferView[], position: number, cb: readvCb): void; export function readv(fd: number, buffers: NodeJS.ArrayBufferView[], position: number | readvCb, cb: readvCb = nop): void { cb = typeof position === 'function' ? position : cb; new promises.FileHandle(fd) .readv(buffers, typeof position === 'function' ? undefined : position) .then(({ buffers, bytesRead }) => cb(undefined, bytesRead, buffers)) .catch(cb); } readv satisfies Omit<typeof fs.readv, '__promisify__'>; type writevCb = Callback<[number, NodeJS.ArrayBufferView[]]>; export function writev(fd: number, buffers: Uint8Array[], cb: writevCb): void; export function writev(fd: number, buffers: Uint8Array[], position: number, cb: writevCb): void; export function writev(fd: number, buffers: Uint8Array[], position: number | writevCb, cb: writevCb = nop) { cb = typeof position === 'function' ? position : cb; new promises.FileHandle(fd) .writev(buffers, typeof position === 'function' ? undefined : position) .then(({ buffers, bytesWritten }) => cb(undefined, bytesWritten, buffers)) .catch(cb); } writev satisfies Omit<typeof fs.writev, '__promisify__'>; export function opendir(path: fs.PathLike, cb: Callback<[Dir]>): void; export function opendir(path: fs.PathLike, options: fs.OpenDirOptions, cb: Callback<[Dir]>): void; export function opendir(path: fs.PathLike, options: fs.OpenDirOptions | Callback<[Dir]>, cb: Callback<[Dir]> = nop): void { cb = typeof options === 'function' ? options : cb; promises .opendir(path, typeof options === 'function' ? undefined : options) .then(result => cb(undefined, result)) .catch(cb); } opendir satisfies Omit<typeof fs.opendir, '__promisify__'>; export function cp(source: fs.PathLike, destination: fs.PathLike, callback: Callback): void; export function cp(source: fs.PathLike, destination: fs.PathLike, opts: fs.CopyOptions, callback: Callback): void; export function cp(source: fs.PathLike, destination: fs.PathLike, opts: fs.CopyOptions | Callback, callback: Callback = nop): void { callback = typeof opts === 'function' ? opts : callback; promises .cp(source, destination, typeof opts === 'function' ? undefined : opts) .then(() => callback(undefined)) .catch(callback); } cp satisfies Omit<typeof fs.cp, '__promisify__'>; export function statfs(path: fs.PathLike, callback: Callback<[fs.StatsFs]>): void; export function statfs(path: fs.PathLike, options: fs.StatFsOptions & { bigint?: false }, callback: Callback<[fs.StatsFs]>): void; export function statfs(path: fs.PathLike, options: fs.StatFsOptions & { bigint: true }, callback: Callback<[fs.BigIntStatsFs]>): void; export function statfs(path: fs.PathLike, options?: fs.StatFsOptions | Callback<[fs.StatsFs]>, callback: Callback<[fs.StatsFs]> | Callback<[fs.BigIntStatsFs]> = nop): void { callback = typeof options === 'function' ? options : callback; promises .statfs(path, typeof options === 'function' ? undefined : options) .then(result => (callback as Callback<[fs.StatsFs | fs.BigIntStatsFs]>)(undefined, result)) .catch(callback); } statfs satisfies Omit<typeof fs.statfs, '__promisify__'>; export async function openAsBlob(path: fs.PathLike, options?: fs.OpenAsBlobOptions): Promise<Blob> { const handle = await promises.open(path.toString(), 'r'); const buffer = await handle.readFile(); await handle.close(); return new Blob([buffer], options); } openAsBlob satisfies typeof fs.openAsBlob;