@zenfs/core
Advanced tools
Comparing version 0.8.1 to 0.9.0
@@ -86,10 +86,4 @@ import type { Cred } from '../cred.js'; | ||
openFile(path: string, flag: string, cred: Cred): Promise<import("../file.js").File>; | ||
createFile(path: string, flag: string, mode: number, cred: Cred): Promise<import("../file.js").File>; /** | ||
* Promise that resolves to the store | ||
*/ | ||
unlink(path: string, cred: Cred): Promise<void>; /** | ||
* An asynchronous file system which uses an async store to store its data. | ||
* @see AsyncStore | ||
* @internal | ||
*/ | ||
createFile(path: string, flag: string, mode: number, cred: Cred): Promise<import("../file.js").File>; | ||
unlink(path: string, cred: Cred): Promise<void>; | ||
rmdir(path: string, cred: Cred): Promise<void>; | ||
@@ -96,0 +90,0 @@ mkdir(path: string, mode: number, cred: Cred): Promise<void>; |
@@ -120,3 +120,3 @@ import { FileSystem } from '../filesystem.js'; | ||
if (this._deletedFiles.has(oldPath)) { | ||
throw ApiError.With('ENOENT', oldPath, 'renameSync'); | ||
throw ApiError.With('ENOENT', oldPath, 'rename'); | ||
} | ||
@@ -147,3 +147,3 @@ } | ||
if (this._deletedFiles.has(p)) { | ||
throw ApiError.With('ENOENT', p, 'statSync'); | ||
throw ApiError.With('ENOENT', p, 'stat'); | ||
} | ||
@@ -213,3 +213,3 @@ const oldStat = new Stats(this._readable.statSync(p, cred)); | ||
if (!this.existsSync(p, cred)) { | ||
throw ApiError.With('ENOENT', p, 'unlinkSync'); | ||
throw ApiError.With('ENOENT', p, 'unlink'); | ||
} | ||
@@ -245,3 +245,3 @@ if (this._writable.existsSync(p, cred)) { | ||
if (!this.existsSync(p, cred)) { | ||
throw ApiError.With('ENOENT', p, 'rmdirSync'); | ||
throw ApiError.With('ENOENT', p, 'rmdir'); | ||
} | ||
@@ -254,3 +254,3 @@ if (this._writable.existsSync(p, cred)) { | ||
if (this.readdirSync(p, cred).length > 0) { | ||
throw ApiError.With('ENOTEMPTY', p, 'rmdirSync'); | ||
throw ApiError.With('ENOTEMPTY', p, 'rmdir'); | ||
} | ||
@@ -274,3 +274,3 @@ else { | ||
if (this.existsSync(p, cred)) { | ||
throw ApiError.With('EEXIST', p, 'mkdirSync'); | ||
throw ApiError.With('EEXIST', p, 'mkdir'); | ||
} | ||
@@ -312,3 +312,3 @@ // The below will throw should any of the parent directories fail to exist on _writable. | ||
if (!dirStats.isDirectory()) { | ||
throw ApiError.With('ENOTDIR', p, 'readdirSync'); | ||
throw ApiError.With('ENOTDIR', p, 'readdir'); | ||
} | ||
@@ -432,3 +432,3 @@ // Readdir in both, check delete log on RO file system's listing, merge, return. | ||
if (!(await this.exists(p, cred))) { | ||
throw ApiError.With('ENOENT', p, 'operateOnWritableAsync'); | ||
throw ApiError.With('ENOENT', p, 'operateOnWritable'); | ||
} | ||
@@ -435,0 +435,0 @@ if (!(await this._writable.exists(p, cred))) { |
@@ -113,6 +113,6 @@ import { dirname, basename, join, resolve, sep } from '../emulation/path.js'; | ||
if (!oldDirNode.toStats().hasAccess(W_OK, cred)) { | ||
throw ApiError.With('EACCES', oldPath, 'renameSync'); | ||
throw ApiError.With('EACCES', oldPath, 'rename'); | ||
} | ||
if (!oldDirList[oldName]) { | ||
throw ApiError.With('ENOENT', oldPath, 'renameSync'); | ||
throw ApiError.With('ENOENT', oldPath, 'rename'); | ||
} | ||
@@ -155,3 +155,3 @@ const ino = oldDirList[oldName]; | ||
// If it's a directory, throw a permissions error. | ||
throw ApiError.With('EPERM', newPath, 'renameSync'); | ||
throw ApiError.With('EPERM', newPath, 'rename'); | ||
} | ||
@@ -175,3 +175,3 @@ } | ||
if (!stats.hasAccess(R_OK, cred)) { | ||
throw ApiError.With('EACCES', p, 'statSync'); | ||
throw ApiError.With('EACCES', p, 'stat'); | ||
} | ||
@@ -187,6 +187,6 @@ return stats; | ||
if (!node.toStats().hasAccess(flagToMode(flag), cred)) { | ||
throw ApiError.With('EACCES', p, 'openFileSync'); | ||
throw ApiError.With('EACCES', p, 'openFile'); | ||
} | ||
if (data === null) { | ||
throw ApiError.With('ENOENT', p, 'openFileSync'); | ||
throw ApiError.With('ENOENT', p, 'openFile'); | ||
} | ||
@@ -201,3 +201,3 @@ return new PreloadFile(this, p, flag, node.toStats(), data); | ||
if (this.readdirSync(p, cred).length > 0) { | ||
throw ApiError.With('ENOTEMPTY', p, 'rmdirSync'); | ||
throw ApiError.With('ENOTEMPTY', p, 'rmdir'); | ||
} | ||
@@ -215,3 +215,3 @@ else { | ||
if (!node.toStats().hasAccess(R_OK, cred)) { | ||
throw ApiError.With('EACCES', p, 'readdirSync'); | ||
throw ApiError.With('EACCES', p, 'readdir'); | ||
} | ||
@@ -243,7 +243,7 @@ return Object.keys(this.getDirListing(tx, node, p)); | ||
if (!existingDirNode.toStats().hasAccess(R_OK, cred)) { | ||
throw ApiError.With('EACCES', existingDir, 'linkSync'); | ||
throw ApiError.With('EACCES', existingDir, 'link'); | ||
} | ||
const newDir = dirname(newpath), newDirNode = this.findINode(tx, newDir), newListing = this.getDirListing(tx, newDirNode, newDir); | ||
if (!newDirNode.toStats().hasAccess(W_OK, cred)) { | ||
throw ApiError.With('EACCES', newDir, 'linkSync'); | ||
throw ApiError.With('EACCES', newDir, 'link'); | ||
} | ||
@@ -253,3 +253,3 @@ const ino = this._findINode(tx, existingDir, basename(existing)); | ||
if (!node.toStats().hasAccess(W_OK, cred)) { | ||
throw ApiError.With('EACCES', newpath, 'linkSync'); | ||
throw ApiError.With('EACCES', newpath, 'link'); | ||
} | ||
@@ -256,0 +256,0 @@ node.nlink++; |
@@ -7,4 +7,5 @@ /// <reference types="node" resolution-mode="require"/> | ||
import { BigIntStats, type BigIntStatsFs, type Stats, type StatsFs } from '../stats.js'; | ||
import type { Callback } from '../utils.js'; | ||
import { type Callback } from '../utils.js'; | ||
import { Dirent, type Dir } from './dir.js'; | ||
import * as promises from './promises.js'; | ||
import { PathLike } from './shared.js'; | ||
@@ -379,21 +380,46 @@ import { ReadStream, WriteStream } from './streams.js'; | ||
}, listener?: (event: string, filename: string) => any): Node.FSWatcher; | ||
/** | ||
* @todo Implement | ||
*/ | ||
export declare function createReadStream(path: PathLike, options?: { | ||
interface StreamOptions { | ||
flags?: string; | ||
encoding?: string; | ||
fd?: number; | ||
encoding?: BufferEncoding; | ||
fd?: number | promises.FileHandle; | ||
mode?: number; | ||
autoClose?: boolean; | ||
}): ReadStream; | ||
emitClose?: boolean; | ||
start?: number; | ||
signal?: AbortSignal; | ||
highWaterMark?: number; | ||
} | ||
interface FSImplementation { | ||
open?: (...args: any[]) => unknown; | ||
close?: (...args: any[]) => unknown; | ||
} | ||
interface ReadStreamOptions extends StreamOptions { | ||
fs?: FSImplementation & { | ||
read: (...args: any[]) => unknown; | ||
}; | ||
end?: number; | ||
} | ||
interface WriteStreamOptions extends StreamOptions { | ||
fs?: FSImplementation & { | ||
write: (...args: any[]) => unknown; | ||
writev?: (...args: any[]) => unknown; | ||
}; | ||
flush?: boolean; | ||
} | ||
/** | ||
* @todo Implement | ||
* 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 declare function createWriteStream(path: PathLike, options?: { | ||
flags?: string; | ||
encoding?: string; | ||
fd?: number; | ||
mode?: number; | ||
}): WriteStream; | ||
export declare function createReadStream(path: PathLike, _options?: BufferEncoding | ReadStreamOptions): ReadStream; | ||
/** | ||
* 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 declare function createWriteStream(path: PathLike, _options?: BufferEncoding | WriteStreamOptions): WriteStream; | ||
export declare function rm(path: PathLike, callback: Callback): void; | ||
@@ -411,7 +437,7 @@ export declare function rm(path: PathLike, options: Node.RmOptions, callback: Callback): void; | ||
type readvCb = Callback<[number, NodeJS.ArrayBufferView[]]>; | ||
export declare function readv(fd: number, buffers: readonly NodeJS.ArrayBufferView[], cb: readvCb): void; | ||
export declare function readv(fd: number, buffers: readonly NodeJS.ArrayBufferView[], position: number, cb: readvCb): void; | ||
export declare function readv(fd: number, buffers: NodeJS.ArrayBufferView[], cb: readvCb): void; | ||
export declare function readv(fd: number, buffers: NodeJS.ArrayBufferView[], position: number, cb: readvCb): void; | ||
type writevCb = Callback<[number, NodeJS.ArrayBufferView[]]>; | ||
export declare function writev(fd: number, buffers: NodeJS.ArrayBufferView[], cb: writevCb): void; | ||
export declare function writev(fd: number, buffers: NodeJS.ArrayBufferView[], position: number, cb: writevCb): void; | ||
export declare function writev(fd: number, buffers: Uint8Array[], cb: writevCb): void; | ||
export declare function writev(fd: number, buffers: Uint8Array[], position: number, cb: writevCb): void; | ||
export declare function opendir(path: PathLike, cb: Callback<[Dir]>): void; | ||
@@ -428,3 +454,2 @@ export declare function opendir(path: PathLike, options: Node.OpenDirOptions, cb: Callback<[Dir]>): void; | ||
}, callback: Callback<[BigIntStatsFs]>): void; | ||
export declare function openAsBlob(path: PathLike, options?: Node.OpenAsBlobOptions): Promise<Blob>; | ||
export {}; |
import { ApiError, ErrorCode } from '../ApiError.js'; | ||
import { BigIntStats } from '../stats.js'; | ||
import { nop, normalizeMode } from '../utils.js'; | ||
import { R_OK } from './constants.js'; | ||
import * as promises from './promises.js'; | ||
import { fd2file, nop, normalizeMode } from './shared.js'; | ||
import { fd2file } from './shared.js'; | ||
import { ReadStream, WriteStream } from './streams.js'; | ||
/** | ||
@@ -432,3 +434,3 @@ * Asynchronous rename. No arguments other than a possible exception are given | ||
export function watchFile(filename, optsListener, listener = nop) { | ||
throw ApiError.With('ENOTSUP', filename, 'watchFile'); | ||
throw ApiError.With('ENOSYS', filename, 'watchFile'); | ||
} | ||
@@ -440,21 +442,87 @@ watchFile; | ||
export function unwatchFile(filename, listener = nop) { | ||
throw ApiError.With('ENOTSUP', filename, 'unwatchFile'); | ||
throw ApiError.With('ENOSYS', filename, 'unwatchFile'); | ||
} | ||
unwatchFile; | ||
export function watch(filename, options, listener = nop) { | ||
throw ApiError.With('ENOTSUP', filename, 'watch'); | ||
throw ApiError.With('ENOSYS', filename, 'watch'); | ||
} | ||
watch; | ||
/** | ||
* @todo Implement | ||
* 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, options) { | ||
throw ApiError.With('ENOTSUP', path, 'createReadStream'); | ||
export function createReadStream(path, _options) { | ||
const options = typeof _options == 'object' ? _options : { encoding: _options }; | ||
let handle; | ||
const stream = new ReadStream({ | ||
highWaterMark: options.highWaterMark || 64 * 1024, | ||
encoding: options.encoding || 'utf8', | ||
async read(size) { | ||
try { | ||
handle || (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) { | ||
await handle?.close(); | ||
stream.destroy(error); | ||
} | ||
}, | ||
destroy(error, callback) { | ||
handle | ||
?.close() | ||
.then(() => callback(error)) | ||
.catch(callback); | ||
}, | ||
}); | ||
stream.path = path; | ||
return stream; | ||
} | ||
createReadStream; | ||
/** | ||
* @todo Implement | ||
* 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, options) { | ||
throw ApiError.With('ENOTSUP', path, 'createWriteStream'); | ||
export function createWriteStream(path, _options) { | ||
const options = typeof _options == 'object' ? _options : { encoding: _options }; | ||
let handle; | ||
const stream = new WriteStream({ | ||
highWaterMark: options?.highWaterMark, | ||
async write(chunk, encoding, callback) { | ||
try { | ||
handle || (handle = await promises.open(path, 'w', options?.mode || 0o666)); | ||
await handle.write(chunk, null, encoding); | ||
callback(null); | ||
} | ||
catch (error) { | ||
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; | ||
return stream; | ||
} | ||
@@ -526,7 +594,1 @@ createWriteStream; | ||
statfs; | ||
/* eslint-disable @typescript-eslint/no-unused-vars */ | ||
export function openAsBlob(path, options) { | ||
throw ApiError.With('ENOTSUP', path, 'openAsBlob'); | ||
} | ||
openAsBlob; | ||
/* eslint-enable @typescript-eslint/no-unused-vars */ |
@@ -7,3 +7,3 @@ export * from './async.js'; | ||
export * from './dir.js'; | ||
export { mountMapping, mounts, mount, umount, _toUnixTimestamp } from './shared.js'; | ||
export { mountMapping, mounts, mount, umount } from './shared.js'; | ||
export { Stats, BigIntStats, StatsFs } from '../stats.js'; |
@@ -7,3 +7,3 @@ export * from './async.js'; | ||
export * from './dir.js'; | ||
export { mountMapping, mounts, mount, umount, _toUnixTimestamp } from './shared.js'; | ||
export { mountMapping, mounts, mount, umount } from './shared.js'; | ||
export { Stats, BigIntStats, StatsFs } from '../stats.js'; |
@@ -11,10 +11,14 @@ /// <reference types="node" resolution-mode="require"/> | ||
import type { CreateReadStreamOptions, CreateWriteStreamOptions, FileChangeInfo, FileReadResult, FlagAndOpenMode } from 'node:fs/promises'; | ||
import type { ReadableStream } from 'node:stream/web'; | ||
import type { ReadableStream as TReadableStream } from 'node:stream/web'; | ||
import type { Interface as ReadlineInterface } from 'readline'; | ||
import { File } from '../file.js'; | ||
import { FileContents } from '../filesystem.js'; | ||
import { BigIntStats, type BigIntStatsFs, type Stats, type StatsFs } from '../stats.js'; | ||
import { Dirent, type Dir } from './dir.js'; | ||
import { Dir, Dirent } from './dir.js'; | ||
import type { PathLike } from './shared.js'; | ||
import { ReadStream, WriteStream } from './streams.js'; | ||
export * as constants from './constants.js'; | ||
declare global { | ||
const ReadableStream: typeof TReadableStream; | ||
} | ||
export declare class FileHandle implements promises.FileHandle { | ||
@@ -30,5 +34,7 @@ /** | ||
fd: number); | ||
private get file(); | ||
private get path(); | ||
/** | ||
* @internal | ||
*/ | ||
get file(): File; | ||
/** | ||
* Asynchronous fchown(2) - Change ownership of a file. | ||
@@ -102,3 +108,3 @@ */ | ||
*/ | ||
readableWebStream(options?: promises.ReadableWebStreamOptions): ReadableStream; | ||
readableWebStream(options?: promises.ReadableWebStreamOptions): TReadableStream<Uint8Array>; | ||
readLines(options?: promises.CreateReadStreamOptions): ReadlineInterface; | ||
@@ -159,12 +165,28 @@ [Symbol.asyncDispose](): Promise<void>; | ||
/** | ||
* See `fs.writev` promisified version. | ||
* @todo Implement | ||
* Asynchronous `writev`. Writes from multiple buffers. | ||
* @param buffers An array of Uint8Array buffers. | ||
* @param position The position in the file where to begin writing. | ||
* @returns The number of bytes written. | ||
*/ | ||
writev(buffers: NodeJS.ArrayBufferView[], position?: number): Promise<Node.WriteVResult>; | ||
writev(buffers: Uint8Array[], position?: number): Promise<Node.WriteVResult>; | ||
/** | ||
* See `fs.readv` promisified version. | ||
* @todo Implement | ||
* Asynchronous `readv`. Reads into multiple buffers. | ||
* @param buffers An array of Uint8Array buffers. | ||
* @param position The position in the file where to begin reading. | ||
* @returns The number of bytes read. | ||
*/ | ||
readv(buffers: readonly NodeJS.ArrayBufferView[], position?: number): Promise<Node.ReadVResult>; | ||
readv(buffers: NodeJS.ArrayBufferView[], position?: number): Promise<Node.ReadVResult>; | ||
/** | ||
* Creates a `ReadStream` for reading from the file. | ||
* | ||
* @param options Options for the readable stream | ||
* @returns A `ReadStream` object. | ||
*/ | ||
createReadStream(options?: CreateReadStreamOptions): ReadStream; | ||
/** | ||
* Creates a `WriteStream` for writing to the file. | ||
* | ||
* @param options Options for the writeable stream. | ||
* @returns A `WriteStream` object | ||
*/ | ||
createWriteStream(options?: CreateWriteStreamOptions): WriteStream; | ||
@@ -244,3 +266,3 @@ } | ||
/** | ||
* Synchronously writes data to a file, replacing the file if it already exists. | ||
* Asynchronously writes data to a file, replacing the file if it already exists. | ||
* | ||
@@ -381,7 +403,11 @@ * The encoding option is ignored if data is a buffer. | ||
/** | ||
* @todo Implement | ||
* Asynchronous `rm`. Removes files or directories (recursively). | ||
* @param path The path to the file or directory to remove. | ||
*/ | ||
export declare function rm(path: PathLike, options?: Node.RmOptions): Promise<void>; | ||
/** | ||
* @todo Implement | ||
* Asynchronous `mkdtemp`. Creates a unique temporary directory. | ||
* @param prefix The directory prefix. | ||
* @param options The encoding (or an object including `encoding`). | ||
* @returns The path to the created temporary directory, encoded as a string or buffer. | ||
*/ | ||
@@ -391,9 +417,28 @@ export declare function mkdtemp(prefix: string, options?: Node.EncodingOption): Promise<string>; | ||
/** | ||
* @todo Implement | ||
* Asynchronous `copyFile`. Copies a file. | ||
* @param src The source file. | ||
* @param dest The destination file. | ||
* @param mode Optional flags for the copy operation. Currently supports these flags: | ||
* * `fs.constants.COPYFILE_EXCL`: If the destination file already exists, the operation fails. | ||
*/ | ||
export declare function copyFile(src: PathLike, dest: PathLike, mode?: number): Promise<void>; | ||
/** | ||
* @todo Implement | ||
* Asynchronous `opendir`. Opens a directory. | ||
* @param path The path to the directory. | ||
* @param options Options for opening the directory. | ||
* @returns A `Dir` object representing the opened directory. | ||
*/ | ||
export declare function opendir(path: PathLike, options?: Node.OpenDirOptions): Promise<Dir>; | ||
/** | ||
* Asynchronous `cp`. Recursively copies a file or directory. | ||
* @param source The source file or directory. | ||
* @param destination The destination file or directory. | ||
* @param opts Options for the copy operation. Currently supports these options from Node.js 'fs.await cp': | ||
* * `dereference`: Dereference symbolic links. | ||
* * `errorOnExist`: Throw an error if the destination file or directory already exists. | ||
* * `filter`: A function that takes a source and destination path and returns a boolean, indicating whether to copy the given source element. | ||
* * `force`: Overwrite the destination if it exists, and overwrite existing readonly destination files. | ||
* * `preserveTimestamps`: Preserve file timestamps. | ||
* * `recursive`: If `true`, copies directories recursively. | ||
*/ | ||
export declare function cp(source: PathLike, destination: PathLike, opts?: Node.CopyOptions): Promise<void>; | ||
@@ -411,1 +456,2 @@ /** | ||
export declare function statfs(path: PathLike, opts?: Node.StatFsOptions): Promise<StatsFs | BigIntStatsFs>; | ||
export declare function openAsBlob(path: PathLike, options?: Node.OpenAsBlobOptions): Promise<Blob>; |
@@ -5,6 +5,8 @@ import { Buffer } from 'buffer'; | ||
import { BigIntStats, FileType } from '../stats.js'; | ||
import { F_OK } from './constants.js'; | ||
import { Dirent } from './dir.js'; | ||
import { normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js'; | ||
import * as constants from './constants.js'; | ||
import { Dir, Dirent } from './dir.js'; | ||
import { dirname, join, parse } from './path.js'; | ||
import { cred, fd2file, fdMap, fixError, getFdForFile, mounts, normalizeMode, normalizeOptions, normalizePath, normalizeTime, resolveMount } from './shared.js'; | ||
import { cred, fd2file, fdMap, fixError, getFdForFile, mounts, resolveMount } from './shared.js'; | ||
import { ReadStream, WriteStream } from './streams.js'; | ||
export * as constants from './constants.js'; | ||
@@ -19,8 +21,8 @@ export class FileHandle { | ||
} | ||
/** | ||
* @internal | ||
*/ | ||
get file() { | ||
return fd2file(this.fd); | ||
} | ||
get path() { | ||
return this.file.path; | ||
} | ||
/** | ||
@@ -133,9 +135,31 @@ * Asynchronous fchown(2) - Change ownership of a file. | ||
readableWebStream(options) { | ||
throw ApiError.With('ENOTSUP', this.path, 'FileHandle.readableWebStream'); | ||
// Note: using an arrow function to preserve `this` | ||
const start = async ({ close, enqueue, error }) => { | ||
try { | ||
const chunkSize = 64 * 1024, maxChunks = 1e7; | ||
let i = 0, position = 0, result; | ||
while (result.bytesRead > 0) { | ||
result = await this.read(new Uint8Array(chunkSize), 0, chunkSize, position); | ||
if (!result.bytesRead) { | ||
close(); | ||
return; | ||
} | ||
enqueue(result.buffer.slice(0, result.bytesRead)); | ||
position += result.bytesRead; | ||
if (++i >= maxChunks) { | ||
throw new ApiError(ErrorCode.EFBIG, 'Too many iterations on readable stream', this.file.path, 'FileHandle.readableWebStream'); | ||
} | ||
} | ||
} | ||
catch (e) { | ||
error(e); | ||
} | ||
}; | ||
return new ReadableStream({ start, type: options.type }); | ||
} | ||
readLines(options) { | ||
throw ApiError.With('ENOTSUP', this.path, 'FileHandle.readLines'); | ||
throw ApiError.With('ENOSYS', this.file.path, 'FileHandle.readLines'); | ||
} | ||
[Symbol.asyncDispose]() { | ||
throw ApiError.With('ENOTSUP', this.path, 'FileHandle.@@asyncDispose'); | ||
return this.close(); | ||
} | ||
@@ -197,22 +221,76 @@ async stat(opts) { | ||
} | ||
/* eslint-disable @typescript-eslint/no-unused-vars */ | ||
/** | ||
* See `fs.writev` promisified version. | ||
* @todo Implement | ||
* Asynchronous `writev`. Writes from multiple buffers. | ||
* @param buffers An array of Uint8Array buffers. | ||
* @param position The position in the file where to begin writing. | ||
* @returns The number of bytes written. | ||
*/ | ||
writev(buffers, position) { | ||
throw ApiError.With('ENOTSUP', this.path, 'FileHandle.writev'); | ||
async writev(buffers, position) { | ||
let bytesWritten = 0; | ||
for (const buffer of buffers) { | ||
bytesWritten += (await this.write(buffer, 0, buffer.length, position + bytesWritten)).bytesWritten; | ||
} | ||
return { bytesWritten, buffers }; | ||
} | ||
/** | ||
* See `fs.readv` promisified version. | ||
* @todo Implement | ||
* Asynchronous `readv`. Reads into multiple buffers. | ||
* @param buffers An array of Uint8Array buffers. | ||
* @param position The position in the file where to begin reading. | ||
* @returns The number of bytes read. | ||
*/ | ||
readv(buffers, position) { | ||
throw ApiError.With('ENOTSUP', this.path, 'FileHandle.readv'); | ||
async readv(buffers, position) { | ||
let bytesRead = 0; | ||
for (const buffer of buffers) { | ||
bytesRead += (await this.read(buffer, 0, buffer.byteLength, position + bytesRead)).bytesRead; | ||
} | ||
return { bytesRead, buffers }; | ||
} | ||
/** | ||
* Creates a `ReadStream` for reading from the file. | ||
* | ||
* @param options Options for the readable stream | ||
* @returns A `ReadStream` object. | ||
*/ | ||
createReadStream(options) { | ||
throw ApiError.With('ENOTSUP', this.path, 'createReadStream'); | ||
const streamOptions = { | ||
highWaterMark: options?.highWaterMark || 64 * 1024, | ||
encoding: options?.encoding, | ||
read: async (size) => { | ||
try { | ||
const result = await this.read(new Uint8Array(size), 0, size, this.file.position); | ||
stream.push(!result.bytesRead ? null : result.buffer.slice(0, result.bytesRead)); // Push data or null for EOF | ||
this.file.position += result.bytesRead; | ||
} | ||
catch (error) { | ||
stream.destroy(error); | ||
} | ||
}, | ||
}; | ||
const stream = new ReadStream(streamOptions); | ||
stream.path = this.file.path; | ||
return stream; | ||
} | ||
/** | ||
* Creates a `WriteStream` for writing to the file. | ||
* | ||
* @param options Options for the writeable stream. | ||
* @returns A `WriteStream` object | ||
*/ | ||
createWriteStream(options) { | ||
throw ApiError.With('ENOTSUP', this.path, 'createWriteStream'); | ||
const streamOptions = { | ||
highWaterMark: options?.highWaterMark, | ||
encoding: options?.encoding, | ||
write: async (chunk, encoding, callback) => { | ||
try { | ||
const { bytesWritten } = await this.write(chunk, null, encoding); | ||
callback(bytesWritten == chunk.length ? null : new Error('Failed to write full chunk')); | ||
} | ||
catch (error) { | ||
callback(error); | ||
} | ||
}, | ||
}; | ||
const stream = new WriteStream(streamOptions); | ||
stream.path = this.file.path; | ||
return stream; | ||
} | ||
@@ -412,3 +490,3 @@ } | ||
/** | ||
* Synchronously writes data to a file, replacing the file if it already exists. | ||
* Asynchronously writes data to a file, replacing the file if it already exists. | ||
* | ||
@@ -655,3 +733,3 @@ * The encoding option is ignored if data is a buffer. | ||
export function watch(filename, options) { | ||
throw ApiError.With('ENOTSUP', filename, 'watch'); | ||
throw ApiError.With('ENOSYS', filename, 'watch'); | ||
} | ||
@@ -664,3 +742,3 @@ watch; | ||
*/ | ||
export async function access(path, mode = F_OK) { | ||
export async function access(path, mode = constants.F_OK) { | ||
const stats = await stat(path); | ||
@@ -672,35 +750,124 @@ if (!stats.hasAccess(mode, cred)) { | ||
access; | ||
/* eslint-disable @typescript-eslint/no-unused-vars */ | ||
/** | ||
* @todo Implement | ||
* Asynchronous `rm`. Removes files or directories (recursively). | ||
* @param path The path to the file or directory to remove. | ||
*/ | ||
export async function rm(path, options) { | ||
throw ApiError.With('ENOTSUP', path, 'rm'); | ||
path = normalizePath(path); | ||
const stats = await stat(path); | ||
switch (stats.mode & constants.S_IFMT) { | ||
case constants.S_IFDIR: | ||
if (options?.recursive) { | ||
for (const entry of await readdir(path)) { | ||
await rm(join(path, entry)); | ||
} | ||
} | ||
await rmdir(path); | ||
return; | ||
case constants.S_IFREG: | ||
case constants.S_IFLNK: | ||
await unlink(path); | ||
return; | ||
case constants.S_IFBLK: | ||
case constants.S_IFCHR: | ||
case constants.S_IFIFO: | ||
case constants.S_IFSOCK: | ||
default: | ||
throw new ApiError(ErrorCode.EPERM, 'File type not supported', path, 'rm'); | ||
} | ||
} | ||
rm; | ||
export async function mkdtemp(prefix, options) { | ||
throw ApiError.With('ENOTSUP', prefix, 'mkdtemp'); | ||
const encoding = typeof options === 'object' ? options.encoding : options || 'utf8'; | ||
const fsName = `${prefix}${Date.now()}-${Math.random().toString(36).slice(2)}`; | ||
const resolvedPath = '/tmp/' + fsName; | ||
await mkdir(resolvedPath); | ||
return encoding == 'buffer' ? Buffer.from(resolvedPath) : resolvedPath; | ||
} | ||
mkdtemp; | ||
/** | ||
* @todo Implement | ||
* Asynchronous `copyFile`. Copies a file. | ||
* @param src The source file. | ||
* @param dest The destination file. | ||
* @param mode Optional flags for the copy operation. Currently supports these flags: | ||
* * `fs.constants.COPYFILE_EXCL`: If the destination file already exists, the operation fails. | ||
*/ | ||
export async function copyFile(src, dest, mode) { | ||
throw ApiError.With('ENOTSUP', src, 'copyFile'); | ||
src = normalizePath(src); | ||
dest = normalizePath(dest); | ||
if (mode && mode & constants.COPYFILE_EXCL && (await exists(dest))) { | ||
throw new ApiError(ErrorCode.EEXIST, 'Destination file already exists.', dest, 'copyFile'); | ||
} | ||
await writeFile(dest, await readFile(src)); | ||
} | ||
copyFile; | ||
/** | ||
* @todo Implement | ||
* Asynchronous `opendir`. Opens a directory. | ||
* @param path The path to the directory. | ||
* @param options Options for opening the directory. | ||
* @returns A `Dir` object representing the opened directory. | ||
*/ | ||
export async function opendir(path, options) { | ||
throw ApiError.With('ENOTSUP', path, 'opendir'); | ||
path = normalizePath(path); | ||
return new Dir(path); | ||
} | ||
opendir; | ||
/** | ||
* Asynchronous `cp`. Recursively copies a file or directory. | ||
* @param source The source file or directory. | ||
* @param destination The destination file or directory. | ||
* @param opts Options for the copy operation. Currently supports these options from Node.js 'fs.await cp': | ||
* * `dereference`: Dereference symbolic links. | ||
* * `errorOnExist`: Throw an error if the destination file or directory already exists. | ||
* * `filter`: A function that takes a source and destination path and returns a boolean, indicating whether to copy the given source element. | ||
* * `force`: Overwrite the destination if it exists, and overwrite existing readonly destination files. | ||
* * `preserveTimestamps`: Preserve file timestamps. | ||
* * `recursive`: If `true`, copies directories recursively. | ||
*/ | ||
export async function cp(source, destination, opts) { | ||
throw ApiError.With('ENOTSUP', source, 'cp'); | ||
source = normalizePath(source); | ||
destination = normalizePath(destination); | ||
const srcStats = await lstat(source); // Use lstat to follow symlinks if not dereferencing | ||
if (opts?.errorOnExist && (await exists(destination))) { | ||
throw new ApiError(ErrorCode.EEXIST, 'Destination file or directory already exists.', destination, 'cp'); | ||
} | ||
switch (srcStats.mode & constants.S_IFMT) { | ||
case constants.S_IFDIR: | ||
if (!opts?.recursive) { | ||
throw new ApiError(ErrorCode.EISDIR, source + ' is a directory (not copied)', source, 'cp'); | ||
} | ||
await mkdir(destination, { recursive: true }); // Ensure the destination directory exists | ||
for (const dirent of await readdir(source, { withFileTypes: true })) { | ||
if (opts.filter && !opts.filter(join(source, dirent.name), join(destination, dirent.name))) { | ||
continue; // Skip if the filter returns false | ||
} | ||
await cp(join(source, dirent.name), join(destination, dirent.name), opts); | ||
} | ||
break; | ||
case constants.S_IFREG: | ||
case constants.S_IFLNK: | ||
await copyFile(source, destination); | ||
break; | ||
case constants.S_IFBLK: | ||
case constants.S_IFCHR: | ||
case constants.S_IFIFO: | ||
case constants.S_IFSOCK: | ||
default: | ||
throw new ApiError(ErrorCode.EPERM, 'File type not supported', source, 'rm'); | ||
} | ||
// Optionally preserve timestamps | ||
if (opts?.preserveTimestamps) { | ||
await utimes(destination, srcStats.atime, srcStats.mtime); | ||
} | ||
} | ||
cp; | ||
export async function statfs(path, opts) { | ||
throw ApiError.With('ENOTSUP', path, 'statfs'); | ||
throw ApiError.With('ENOSYS', path, 'statfs'); | ||
} | ||
/* eslint-enable @typescript-eslint/no-unused-vars */ | ||
export async function openAsBlob(path, options) { | ||
const handle = await open(path, 'r'); | ||
const buffer = await handle.readFile(); | ||
await handle.close(); | ||
return new Blob([buffer], options); | ||
} | ||
openAsBlob; |
@@ -1,49 +0,4 @@ | ||
/// <reference types="node" resolution-mode="require"/> | ||
/// <reference types="node" resolution-mode="require"/> | ||
import { Cred } from '../cred.js'; | ||
import type { File } from '../file.js'; | ||
import { FileSystem } from '../filesystem.js'; | ||
import type { File } from '../file.js'; | ||
import type { EncodingOption, OpenMode, WriteFileOptions } from 'node:fs'; | ||
/** | ||
* converts Date or number to a integer UNIX timestamp | ||
* Grabbed from NodeJS sources (lib/fs.js) | ||
* | ||
* @internal | ||
*/ | ||
export declare function _toUnixTimestamp(time: Date | number): number; | ||
/** | ||
* Normalizes a mode | ||
* @internal | ||
*/ | ||
export declare function normalizeMode(mode: string | number | unknown, def?: number): number; | ||
/** | ||
* Normalizes a time | ||
* @internal | ||
*/ | ||
export declare function normalizeTime(time: string | number | Date): Date; | ||
/** | ||
* Normalizes a path | ||
* @internal | ||
*/ | ||
export declare function normalizePath(p: string): string; | ||
/** | ||
* Normalizes options | ||
* @param options options to normalize | ||
* @param encoding default encoding | ||
* @param flag default flag | ||
* @param mode default mode | ||
* @internal | ||
*/ | ||
export declare function normalizeOptions(options?: WriteFileOptions | (EncodingOption & { | ||
flag?: OpenMode; | ||
}), encoding?: BufferEncoding, flag?: string, mode?: number): { | ||
encoding: BufferEncoding; | ||
flag: string; | ||
mode: number; | ||
}; | ||
/** | ||
* Do nothing | ||
* @internal | ||
*/ | ||
export declare function nop(): void; | ||
export declare let cred: Cred; | ||
@@ -50,0 +5,0 @@ export declare function setCred(val: Cred): void; |
// Utilities and shared data | ||
import { resolve } from './path.js'; | ||
import { ApiError, ErrorCode } from '../ApiError.js'; | ||
import { InMemory } from '../backends/InMemory.js'; | ||
import { rootCred } from '../cred.js'; | ||
import { InMemory } from '../backends/InMemory.js'; | ||
/** | ||
* converts Date or number to a integer UNIX timestamp | ||
* Grabbed from NodeJS sources (lib/fs.js) | ||
* | ||
* @internal | ||
*/ | ||
export function _toUnixTimestamp(time) { | ||
if (typeof time === 'number') { | ||
return Math.floor(time); | ||
} | ||
if (time instanceof Date) { | ||
return Math.floor(time.getTime() / 1000); | ||
} | ||
throw new Error('Cannot parse time: ' + time); | ||
} | ||
/** | ||
* Normalizes a mode | ||
* @internal | ||
*/ | ||
export function normalizeMode(mode, def) { | ||
if (typeof mode == 'number') { | ||
return mode; | ||
} | ||
if (typeof mode == 'string') { | ||
const parsed = parseInt(mode, 8); | ||
if (!isNaN(parsed)) { | ||
return parsed; | ||
} | ||
} | ||
if (typeof def == 'number') { | ||
return def; | ||
} | ||
throw new ApiError(ErrorCode.EINVAL, 'Invalid mode: ' + mode?.toString()); | ||
} | ||
/** | ||
* Normalizes a time | ||
* @internal | ||
*/ | ||
export function normalizeTime(time) { | ||
if (time instanceof Date) { | ||
return time; | ||
} | ||
if (typeof time == 'number') { | ||
return new Date(time * 1000); | ||
} | ||
if (typeof time == 'string') { | ||
return new Date(time); | ||
} | ||
throw new ApiError(ErrorCode.EINVAL, 'Invalid time.'); | ||
} | ||
/** | ||
* Normalizes a path | ||
* @internal | ||
*/ | ||
export function normalizePath(p) { | ||
// Node doesn't allow null characters in paths. | ||
if (p.includes('\x00')) { | ||
throw new ApiError(ErrorCode.EINVAL, 'Path must be a string without null bytes.'); | ||
} | ||
if (p.length == 0) { | ||
throw new ApiError(ErrorCode.EINVAL, 'Path must not be empty.'); | ||
} | ||
return resolve(p.replaceAll(/[/\\]+/g, '/')); | ||
} | ||
/** | ||
* Normalizes options | ||
* @param options options to normalize | ||
* @param encoding default encoding | ||
* @param flag default flag | ||
* @param mode default mode | ||
* @internal | ||
*/ | ||
export function normalizeOptions(options, encoding = 'utf8', flag, mode = 0) { | ||
if (typeof options != 'object' || options === null) { | ||
return { | ||
encoding: typeof options == 'string' ? options : encoding, | ||
flag, | ||
mode, | ||
}; | ||
} | ||
return { | ||
encoding: typeof options?.encoding == 'string' ? options.encoding : encoding, | ||
flag: typeof options?.flag == 'string' ? options.flag : flag, | ||
mode: normalizeMode('mode' in options ? options?.mode : null, mode), | ||
}; | ||
} | ||
/** | ||
* Do nothing | ||
* @internal | ||
*/ | ||
export function nop() { | ||
// do nothing | ||
} | ||
import { normalizePath } from '../utils.js'; | ||
import { resolve } from './path.js'; | ||
// credentials | ||
@@ -100,0 +8,0 @@ export let cred = rootCred; |
@@ -300,7 +300,11 @@ /// <reference types="node" resolution-mode="require"/> | ||
/** | ||
* @todo Implement | ||
* Synchronous `rm`. Removes files or directories (recursively). | ||
* @param path The path to the file or directory to remove. | ||
*/ | ||
export declare function rmSync(path: PathLike): void; | ||
export declare function rmSync(path: PathLike, options?: Node.RmOptions): void; | ||
/** | ||
* @todo Implement | ||
* Synchronous `mkdtemp`. Creates a unique temporary directory. | ||
* @param prefix The directory prefix. | ||
* @param options The encoding (or an object including `encoding`). | ||
* @returns The path to the created temporary directory, encoded as a string or buffer. | ||
*/ | ||
@@ -310,23 +314,47 @@ export declare function mkdtempSync(prefix: string, options: BufferEncodingOption): Buffer; | ||
/** | ||
* @todo Implement | ||
* Synchronous `copyFile`. Copies a file. | ||
* @param src The source file. | ||
* @param dest The destination file. | ||
* @param flags Optional flags for the copy operation. Currently supports these flags: | ||
* * `fs.constants.COPYFILE_EXCL`: If the destination file already exists, the operation fails. | ||
*/ | ||
export declare function copyFileSync(src: string, dest: string, flags?: number): void; | ||
export declare function copyFileSync(src: PathLike, dest: PathLike, flags?: number): void; | ||
/** | ||
* @todo Implement | ||
* Synchronous `readv`. Reads from a file descriptor into multiple buffers. | ||
* @param fd The file descriptor. | ||
* @param buffers An array of Uint8Array buffers. | ||
* @param position The position in the file where to begin reading. | ||
* @returns The number of bytes read. | ||
*/ | ||
export declare function readvSync(fd: number, buffers: readonly Uint8Array[], position?: number): number; | ||
/** | ||
* @todo Implement | ||
* Synchronous `writev`. Writes from multiple buffers into a file descriptor. | ||
* @param fd The file descriptor. | ||
* @param buffers An array of Uint8Array buffers. | ||
* @param position The position in the file where to begin writing. | ||
* @returns The number of bytes written. | ||
*/ | ||
export declare function writevSync(fd: number, buffers: readonly Uint8Array[], position?: number): number; | ||
/** | ||
* @todo Implement | ||
* Synchronous `opendir`. Opens a directory. | ||
* @param path The path to the directory. | ||
* @param options Options for opening the directory. | ||
* @returns A `Dir` object representing the opened directory. | ||
*/ | ||
export declare function opendirSync(path: PathLike, options?: Node.OpenDirOptions): Dir; | ||
/** | ||
* @todo Implement | ||
* Synchronous `cp`. Recursively copies a file or directory. | ||
* @param source The source file or directory. | ||
* @param destination The destination file or directory. | ||
* @param opts Options for the copy operation. Currently supports these options from Node.js 'fs.cpSync': | ||
* * `dereference`: Dereference symbolic links. | ||
* * `errorOnExist`: Throw an error if the destination file or directory already exists. | ||
* * `filter`: A function that takes a source and destination path and returns a boolean, indicating whether to copy the given source element. | ||
* * `force`: Overwrite the destination if it exists, and overwrite existing readonly destination files. | ||
* * `preserveTimestamps`: Preserve file timestamps. | ||
* * `recursive`: If `true`, copies directories recursively. | ||
*/ | ||
export declare function cpSync(source: PathLike, destination: PathLike, opts?: Node.CopySyncOptions): void; | ||
/** | ||
* Synchronous statfs(2). Returns information about the mounted file system which contains path. The callback gets two arguments (err, stats) where stats is an <fs.StatFs> object. | ||
* Synchronous statfs(2). Returns information about the mounted file system which contains path. | ||
* In case of an error, the err.code will be one of Common System Errors. | ||
@@ -333,0 +361,0 @@ * @param path A path to an existing file or directory on the file system to be queried. |
@@ -5,5 +5,7 @@ import { Buffer } from 'buffer'; | ||
import { BigIntStats, FileType } from '../stats.js'; | ||
import { Dirent } from './dir.js'; | ||
import { normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js'; | ||
import { COPYFILE_EXCL, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK } from './constants.js'; | ||
import { Dir, Dirent } from './dir.js'; | ||
import { dirname, join, parse } from './path.js'; | ||
import { cred, fd2file, fdMap, fixError, getFdForFile, mounts, normalizeMode, normalizeOptions, normalizePath, normalizeTime, resolveMount } from './shared.js'; | ||
import { cred, fd2file, fdMap, fixError, getFdForFile, mounts, resolveMount } from './shared.js'; | ||
function doOp(...[name, resolveSymlinks, path, ...args]) { | ||
@@ -108,7 +110,7 @@ path = normalizePath(path); | ||
if (!parentStats.isDirectory()) { | ||
throw ApiError.With('ENOTDIR', dirname(path), '_openSync'); | ||
throw ApiError.With('ENOTDIR', dirname(path), '_open'); | ||
} | ||
return doOp('createFileSync', resolveSymlinks, path, flag, mode, cred); | ||
case ActionType.THROW: | ||
throw ApiError.With('ENOENT', path, '_openSync'); | ||
throw ApiError.With('ENOENT', path, '_open'); | ||
default: | ||
@@ -119,3 +121,3 @@ throw new ApiError(ErrorCode.EINVAL, 'Invalid FileFlag object.'); | ||
if (!stats.hasAccess(mode, cred)) { | ||
throw ApiError.With('EACCES', path, '_openSync'); | ||
throw ApiError.With('EACCES', path, '_open'); | ||
} | ||
@@ -125,3 +127,3 @@ // File exists. | ||
case ActionType.THROW: | ||
throw ApiError.With('EEXIST', path, '_openSync'); | ||
throw ApiError.With('EEXIST', path, '_open'); | ||
case ActionType.TRUNCATE: | ||
@@ -433,3 +435,3 @@ // Delete file. | ||
if (existsSync(path)) { | ||
throw ApiError.With('EEXIST', path, 'symlinkSync'); | ||
throw ApiError.With('EEXIST', path, 'symlink'); | ||
} | ||
@@ -550,52 +552,149 @@ writeFileSync(path, target); | ||
accessSync; | ||
/* eslint-disable @typescript-eslint/no-unused-vars */ | ||
/** | ||
* @todo Implement | ||
* Synchronous `rm`. Removes files or directories (recursively). | ||
* @param path The path to the file or directory to remove. | ||
*/ | ||
export function rmSync(path) { | ||
throw ApiError.With('ENOTSUP', path, 'rmSync'); | ||
export function rmSync(path, options) { | ||
path = normalizePath(path); | ||
const stats = statSync(path); | ||
switch (stats.mode & S_IFMT) { | ||
case S_IFDIR: | ||
if (options?.recursive) { | ||
for (const entry of readdirSync(path)) { | ||
rmSync(join(path, entry)); | ||
} | ||
} | ||
rmdirSync(path); | ||
return; | ||
case S_IFREG: | ||
case S_IFLNK: | ||
unlinkSync(path); | ||
return; | ||
case S_IFBLK: | ||
case S_IFCHR: | ||
case S_IFIFO: | ||
case S_IFSOCK: | ||
default: | ||
throw new ApiError(ErrorCode.EPERM, 'File type not supported', path, 'rm'); | ||
} | ||
} | ||
rmSync; | ||
export function mkdtempSync(prefix, options) { | ||
throw ApiError.With('ENOTSUP', prefix, 'mkdtempSync'); | ||
const encoding = typeof options === 'object' ? options.encoding : options || 'utf8'; | ||
const fsName = `${prefix}${Date.now()}-${Math.random().toString(36).slice(2)}`; | ||
const resolvedPath = '/tmp/' + fsName; | ||
mkdirSync(resolvedPath); | ||
return encoding == 'buffer' ? Buffer.from(resolvedPath) : resolvedPath; | ||
} | ||
mkdtempSync; | ||
/** | ||
* @todo Implement | ||
* Synchronous `copyFile`. Copies a file. | ||
* @param src The source file. | ||
* @param dest The destination file. | ||
* @param flags Optional flags for the copy operation. Currently supports these flags: | ||
* * `fs.constants.COPYFILE_EXCL`: If the destination file already exists, the operation fails. | ||
*/ | ||
export function copyFileSync(src, dest, flags) { | ||
throw ApiError.With('ENOTSUP', src, 'copyFileSync'); | ||
src = normalizePath(src); | ||
dest = normalizePath(dest); | ||
if (flags && flags & COPYFILE_EXCL && existsSync(dest)) { | ||
throw new ApiError(ErrorCode.EEXIST, 'Destination file already exists.', dest, 'copyFile'); | ||
} | ||
writeFileSync(dest, readFileSync(src)); | ||
} | ||
copyFileSync; | ||
/** | ||
* @todo Implement | ||
* Synchronous `readv`. Reads from a file descriptor into multiple buffers. | ||
* @param fd The file descriptor. | ||
* @param buffers An array of Uint8Array buffers. | ||
* @param position The position in the file where to begin reading. | ||
* @returns The number of bytes read. | ||
*/ | ||
export function readvSync(fd, buffers, position) { | ||
throw ApiError.With('ENOTSUP', fd2file(fd).path, 'readvSync'); | ||
const file = fd2file(fd); | ||
let bytesRead = 0; | ||
for (const buffer of buffers) { | ||
bytesRead += file.readSync(buffer, 0, buffer.length, position + bytesRead); | ||
} | ||
return bytesRead; | ||
} | ||
readvSync; | ||
/** | ||
* @todo Implement | ||
* Synchronous `writev`. Writes from multiple buffers into a file descriptor. | ||
* @param fd The file descriptor. | ||
* @param buffers An array of Uint8Array buffers. | ||
* @param position The position in the file where to begin writing. | ||
* @returns The number of bytes written. | ||
*/ | ||
export function writevSync(fd, buffers, position) { | ||
throw ApiError.With('ENOTSUP', fd2file(fd).path, 'writevSync'); | ||
const file = fd2file(fd); | ||
let bytesWritten = 0; | ||
for (const buffer of buffers) { | ||
bytesWritten += file.writeSync(buffer, 0, buffer.length, position + bytesWritten); | ||
} | ||
return bytesWritten; | ||
} | ||
writevSync; | ||
/** | ||
* @todo Implement | ||
* Synchronous `opendir`. Opens a directory. | ||
* @param path The path to the directory. | ||
* @param options Options for opening the directory. | ||
* @returns A `Dir` object representing the opened directory. | ||
*/ | ||
export function opendirSync(path, options) { | ||
throw ApiError.With('ENOTSUP', path, 'opendirSync'); | ||
path = normalizePath(path); | ||
return new Dir(path); // Re-use existing `Dir` class | ||
} | ||
opendirSync; | ||
/** | ||
* @todo Implement | ||
* Synchronous `cp`. Recursively copies a file or directory. | ||
* @param source The source file or directory. | ||
* @param destination The destination file or directory. | ||
* @param opts Options for the copy operation. Currently supports these options from Node.js 'fs.cpSync': | ||
* * `dereference`: Dereference symbolic links. | ||
* * `errorOnExist`: Throw an error if the destination file or directory already exists. | ||
* * `filter`: A function that takes a source and destination path and returns a boolean, indicating whether to copy the given source element. | ||
* * `force`: Overwrite the destination if it exists, and overwrite existing readonly destination files. | ||
* * `preserveTimestamps`: Preserve file timestamps. | ||
* * `recursive`: If `true`, copies directories recursively. | ||
*/ | ||
export function cpSync(source, destination, opts) { | ||
throw ApiError.With('ENOTSUP', source, 'cpSync'); | ||
source = normalizePath(source); | ||
destination = normalizePath(destination); | ||
const srcStats = lstatSync(source); // Use lstat to follow symlinks if not dereferencing | ||
if (opts?.errorOnExist && existsSync(destination)) { | ||
throw new ApiError(ErrorCode.EEXIST, 'Destination file or directory already exists.', destination, 'cp'); | ||
} | ||
switch (srcStats.mode & S_IFMT) { | ||
case S_IFDIR: | ||
if (!opts?.recursive) { | ||
throw new ApiError(ErrorCode.EISDIR, source + ' is a directory (not copied)', source, 'cp'); | ||
} | ||
mkdirSync(destination, { recursive: true }); // Ensure the destination directory exists | ||
for (const dirent of readdirSync(source, { withFileTypes: true })) { | ||
if (opts.filter && !opts.filter(join(source, dirent.name), join(destination, dirent.name))) { | ||
continue; // Skip if the filter returns false | ||
} | ||
cpSync(join(source, dirent.name), join(destination, dirent.name), opts); | ||
} | ||
break; | ||
case S_IFREG: | ||
case S_IFLNK: | ||
copyFileSync(source, destination); | ||
break; | ||
case S_IFBLK: | ||
case S_IFCHR: | ||
case S_IFIFO: | ||
case S_IFSOCK: | ||
default: | ||
throw new ApiError(ErrorCode.EPERM, 'File type not supported', source, 'rm'); | ||
} | ||
// Optionally preserve timestamps | ||
if (opts?.preserveTimestamps) { | ||
utimesSync(destination, srcStats.atime, srcStats.mtime); | ||
} | ||
} | ||
cpSync; | ||
export function statfsSync(path, options) { | ||
throw ApiError.With('ENOTSUP', path, 'statfsSync'); | ||
throw ApiError.With('ENOSYS', path, 'statfs'); | ||
} | ||
/* eslint-enable @typescript-eslint/no-unused-vars */ |
@@ -223,3 +223,6 @@ import { type Cred } from './cred.js'; | ||
} | ||
/** | ||
* Implements the non-readonly methods to throw `EROFS` | ||
*/ | ||
export declare function Readonly<T extends abstract new (...args: any[]) => FileSystem>(FS: T): (abstract new (...args: any[]) => ReadonlyFileSystem) & T; | ||
export {}; |
@@ -27,3 +27,2 @@ import { ApiError, ErrorCode } from './ApiError.js'; | ||
} | ||
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ | ||
constructor(options) { | ||
@@ -61,5 +60,2 @@ // unused | ||
export function Sync(FS) { | ||
/** | ||
* Implements the asynchronous API in terms of the synchronous API. | ||
*/ | ||
class _SyncFileSystem extends FS { | ||
@@ -244,2 +240,5 @@ async ready() { | ||
} | ||
/** | ||
* Implements the non-readonly methods to throw `EROFS` | ||
*/ | ||
export function Readonly(FS) { | ||
@@ -246,0 +245,0 @@ class _ReadonlyFileSystem extends FS { |
@@ -12,3 +12,3 @@ export * from './backends/backend.js'; | ||
export * from './filesystem.js'; | ||
export * from './FileIndex.js'; | ||
export * from './backends/FileIndex.js'; | ||
export * from './inode.js'; | ||
@@ -15,0 +15,0 @@ export * from './mutex.js'; |
@@ -12,3 +12,3 @@ export * from './backends/backend.js'; | ||
export * from './filesystem.js'; | ||
export * from './FileIndex.js'; | ||
export * from './backends/FileIndex.js'; | ||
export * from './inode.js'; | ||
@@ -15,0 +15,0 @@ export * from './mutex.js'; |
@@ -0,1 +1,3 @@ | ||
/// <reference types="node" resolution-mode="require"/> | ||
/// <reference types="node" resolution-mode="require"/> | ||
import type { OptionalTuple } from 'utilium'; | ||
@@ -44,1 +46,44 @@ import { ApiError } from './ApiError.js'; | ||
export type Callback<Args extends unknown[] = []> = (e?: ApiError, ...args: OptionalTuple<Args>) => unknown; | ||
import type { EncodingOption, OpenMode, WriteFileOptions } from 'node:fs'; | ||
/** | ||
* converts Date or number to a integer UNIX timestamp | ||
* Grabbed from NodeJS sources (lib/fs.js) | ||
* | ||
* @internal | ||
*/ | ||
export declare function _toUnixTimestamp(time: Date | number): number; | ||
/** | ||
* Normalizes a mode | ||
* @internal | ||
*/ | ||
export declare function normalizeMode(mode: string | number | unknown, def?: number): number; | ||
/** | ||
* Normalizes a time | ||
* @internal | ||
*/ | ||
export declare function normalizeTime(time: string | number | Date): Date; | ||
/** | ||
* Normalizes a path | ||
* @internal | ||
*/ | ||
export declare function normalizePath(p: string): string; | ||
/** | ||
* Normalizes options | ||
* @param options options to normalize | ||
* @param encoding default encoding | ||
* @param flag default flag | ||
* @param mode default mode | ||
* @internal | ||
*/ | ||
export declare function normalizeOptions(options?: WriteFileOptions | (EncodingOption & { | ||
flag?: OpenMode; | ||
}), encoding?: BufferEncoding, flag?: string, mode?: number): { | ||
encoding: BufferEncoding; | ||
flag: string; | ||
mode: number; | ||
}; | ||
/** | ||
* Do nothing | ||
* @internal | ||
*/ | ||
export declare function nop(): void; |
import { ApiError, ErrorCode } from './ApiError.js'; | ||
import { dirname } from './emulation/path.js'; | ||
import { dirname, resolve } from './emulation/path.js'; | ||
/** | ||
@@ -136,1 +136,94 @@ * Synchronous recursive makedir. | ||
} | ||
/** | ||
* converts Date or number to a integer UNIX timestamp | ||
* Grabbed from NodeJS sources (lib/fs.js) | ||
* | ||
* @internal | ||
*/ | ||
export function _toUnixTimestamp(time) { | ||
if (typeof time === 'number') { | ||
return Math.floor(time); | ||
} | ||
if (time instanceof Date) { | ||
return Math.floor(time.getTime() / 1000); | ||
} | ||
throw new Error('Cannot parse time: ' + time); | ||
} | ||
/** | ||
* Normalizes a mode | ||
* @internal | ||
*/ | ||
export function normalizeMode(mode, def) { | ||
if (typeof mode == 'number') { | ||
return mode; | ||
} | ||
if (typeof mode == 'string') { | ||
const parsed = parseInt(mode, 8); | ||
if (!isNaN(parsed)) { | ||
return parsed; | ||
} | ||
} | ||
if (typeof def == 'number') { | ||
return def; | ||
} | ||
throw new ApiError(ErrorCode.EINVAL, 'Invalid mode: ' + mode?.toString()); | ||
} | ||
/** | ||
* Normalizes a time | ||
* @internal | ||
*/ | ||
export function normalizeTime(time) { | ||
if (time instanceof Date) { | ||
return time; | ||
} | ||
if (typeof time == 'number') { | ||
return new Date(time * 1000); | ||
} | ||
if (typeof time == 'string') { | ||
return new Date(time); | ||
} | ||
throw new ApiError(ErrorCode.EINVAL, 'Invalid time.'); | ||
} | ||
/** | ||
* Normalizes a path | ||
* @internal | ||
*/ | ||
export function normalizePath(p) { | ||
// Node doesn't allow null characters in paths. | ||
if (p.includes('\x00')) { | ||
throw new ApiError(ErrorCode.EINVAL, 'Path must be a string without null bytes.'); | ||
} | ||
if (p.length == 0) { | ||
throw new ApiError(ErrorCode.EINVAL, 'Path must not be empty.'); | ||
} | ||
return resolve(p.replaceAll(/[/\\]+/g, '/')); | ||
} | ||
/** | ||
* Normalizes options | ||
* @param options options to normalize | ||
* @param encoding default encoding | ||
* @param flag default flag | ||
* @param mode default mode | ||
* @internal | ||
*/ | ||
export function normalizeOptions(options, encoding = 'utf8', flag, mode = 0) { | ||
if (typeof options != 'object' || options === null) { | ||
return { | ||
encoding: typeof options == 'string' ? options : encoding, | ||
flag, | ||
mode, | ||
}; | ||
} | ||
return { | ||
encoding: typeof options?.encoding == 'string' ? options.encoding : encoding, | ||
flag: typeof options?.flag == 'string' ? options.flag : flag, | ||
mode: normalizeMode('mode' in options ? options?.mode : null, mode), | ||
}; | ||
} | ||
/** | ||
* Do nothing | ||
* @internal | ||
*/ | ||
export function nop() { | ||
// do nothing | ||
} |
{ | ||
"name": "@zenfs/core", | ||
"version": "0.8.1", | ||
"version": "0.9.0", | ||
"description": "A filesystem in your browser", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
1525254
11718