@zenfs/core
Advanced tools
Comparing version 0.13.0 to 0.14.0
import type { Cred } from '../cred.js'; | ||
import type { File } from '../file.js'; | ||
import type { FileSystem, FileSystemMetadata } from '../filesystem.js'; | ||
import type { FileSystemMetadata } from '../filesystem.js'; | ||
import { FileSystem } from '../filesystem.js'; | ||
import type { Stats } from '../stats.js'; | ||
export interface MutexLock extends PromiseWithResolvers<void> { | ||
[Symbol.dispose](): void; | ||
} | ||
/** | ||
@@ -17,4 +21,33 @@ * This class serializes access to an underlying async filesystem. | ||
readonly fs: FS; | ||
private mutex; | ||
constructor(fs: FS); | ||
/** | ||
* The current locks | ||
*/ | ||
private locks; | ||
protected addLock(path: string): MutexLock; | ||
/** | ||
* Locks `path` asynchronously. | ||
* If the path is currently locked, waits for it to be unlocked. | ||
* @internal | ||
*/ | ||
lock(path: string): Promise<MutexLock>; | ||
/** | ||
* Locks `path` asynchronously. | ||
* If the path is currently locked, an error will be thrown | ||
* @internal | ||
*/ | ||
lockSync(path: string): MutexLock; | ||
/** | ||
* Unlocks a path | ||
* @param path The path to lock | ||
* @param noThrow If true, an error will not be thrown if the path is already unlocked | ||
* @returns Whether the path was unlocked | ||
* @internal | ||
*/ | ||
unlock(path: string, noThrow?: boolean): boolean; | ||
/** | ||
* Whether `path` is locked | ||
* @internal | ||
*/ | ||
isLocked(path: string): boolean; | ||
ready(): Promise<void>; | ||
@@ -45,1 +78,16 @@ metadata(): FileSystemMetadata; | ||
} | ||
export declare const Locked: { | ||
name: string; | ||
options: { | ||
fs: { | ||
type: "object"; | ||
required: true; | ||
description: string; | ||
validator(fs: FileSystem): void; | ||
}; | ||
}; | ||
isAvailable(): true; | ||
create({ fs }: { | ||
fs: FileSystem; | ||
} & Partial<import("./backend.js").SharedConfig>): LockedFS<FileSystem>; | ||
}; |
@@ -1,3 +0,48 @@ | ||
import { ErrnoError } from '../error.js'; | ||
import { Mutex } from '../mutex.js'; | ||
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) { | ||
if (value !== null && value !== void 0) { | ||
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected."); | ||
var dispose; | ||
if (async) { | ||
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined."); | ||
dispose = value[Symbol.asyncDispose]; | ||
} | ||
if (dispose === void 0) { | ||
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined."); | ||
dispose = value[Symbol.dispose]; | ||
} | ||
if (typeof dispose !== "function") throw new TypeError("Object not disposable."); | ||
env.stack.push({ value: value, dispose: dispose, async: async }); | ||
} | ||
else if (async) { | ||
env.stack.push({ async: true }); | ||
} | ||
return value; | ||
}; | ||
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) { | ||
return function (env) { | ||
function fail(e) { | ||
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e; | ||
env.hasError = true; | ||
} | ||
function next() { | ||
while (env.stack.length) { | ||
var rec = env.stack.pop(); | ||
try { | ||
var result = rec.dispose && rec.dispose.call(rec.value); | ||
if (rec.async) return Promise.resolve(result).then(next, function(e) { fail(e); return next(); }); | ||
} | ||
catch (e) { | ||
fail(e); | ||
} | ||
} | ||
if (env.hasError) throw env.error; | ||
} | ||
return next(); | ||
}; | ||
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { | ||
var e = new Error(message); | ||
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; | ||
}); | ||
import { Errno, ErrnoError } from '../error.js'; | ||
import { FileSystem } from '../filesystem.js'; | ||
/** | ||
@@ -16,4 +61,67 @@ * This class serializes access to an underlying async filesystem. | ||
this.fs = fs; | ||
this.mutex = new Mutex(); | ||
/** | ||
* The current locks | ||
*/ | ||
this.locks = new Map(); | ||
} | ||
addLock(path) { | ||
const lock = { | ||
...Promise.withResolvers(), | ||
[Symbol.dispose]: () => { | ||
this.unlock(path); | ||
}, | ||
}; | ||
this.locks.set(path, lock); | ||
return lock; | ||
} | ||
/** | ||
* Locks `path` asynchronously. | ||
* If the path is currently locked, waits for it to be unlocked. | ||
* @internal | ||
*/ | ||
async lock(path) { | ||
if (this.locks.has(path)) { | ||
// Non-null assertion: we already checked locks has path | ||
await this.locks.get(path).promise; | ||
} | ||
return this.addLock(path); | ||
} | ||
/** | ||
* Locks `path` asynchronously. | ||
* If the path is currently locked, an error will be thrown | ||
* @internal | ||
*/ | ||
lockSync(path) { | ||
if (this.locks.has(path)) { | ||
// Non-null assertion: we already checked locks has path | ||
throw ErrnoError.With('EBUSY', path, 'lockSync'); | ||
} | ||
return this.addLock(path); | ||
} | ||
/** | ||
* Unlocks a path | ||
* @param path The path to lock | ||
* @param noThrow If true, an error will not be thrown if the path is already unlocked | ||
* @returns Whether the path was unlocked | ||
* @internal | ||
*/ | ||
unlock(path, noThrow = false) { | ||
if (!this.locks.has(path)) { | ||
if (noThrow) { | ||
return false; | ||
} | ||
throw new ErrnoError(Errno.EPERM, 'Can not unlock an already unlocked path', path); | ||
} | ||
// Non-null assertion: we already checked locks has path | ||
this.locks.get(path).resolve(); | ||
this.locks.delete(path); | ||
return true; | ||
} | ||
/** | ||
* Whether `path` is locked | ||
* @internal | ||
*/ | ||
isLocked(path) { | ||
return this.locks.has(path); | ||
} | ||
async ready() { | ||
@@ -29,127 +137,352 @@ await this.fs.ready(); | ||
async rename(oldPath, newPath, cred) { | ||
await this.mutex.lock(oldPath); | ||
await this.fs.rename(oldPath, newPath, cred); | ||
this.mutex.unlock(oldPath); | ||
const env_1 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const _ = __addDisposableResource(env_1, await this.lock(oldPath), false); | ||
await this.fs.rename(oldPath, newPath, cred); | ||
} | ||
catch (e_1) { | ||
env_1.error = e_1; | ||
env_1.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_1); | ||
} | ||
} | ||
renameSync(oldPath, newPath, cred) { | ||
if (this.mutex.isLocked(oldPath)) { | ||
throw ErrnoError.With('EBUSY', oldPath, 'rename'); | ||
const env_2 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const _ = __addDisposableResource(env_2, this.lockSync(oldPath), false); | ||
return this.fs.renameSync(oldPath, newPath, cred); | ||
} | ||
return this.fs.renameSync(oldPath, newPath, cred); | ||
catch (e_2) { | ||
env_2.error = e_2; | ||
env_2.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_2); | ||
} | ||
} | ||
async stat(path, cred) { | ||
await this.mutex.lock(path); | ||
const stats = await this.fs.stat(path, cred); | ||
this.mutex.unlock(path); | ||
return stats; | ||
const env_3 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const _ = __addDisposableResource(env_3, await this.lock(path), false); | ||
return await this.fs.stat(path, cred); | ||
} | ||
catch (e_3) { | ||
env_3.error = e_3; | ||
env_3.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_3); | ||
} | ||
} | ||
statSync(path, cred) { | ||
if (this.mutex.isLocked(path)) { | ||
throw ErrnoError.With('EBUSY', path, 'stat'); | ||
const env_4 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const _ = __addDisposableResource(env_4, this.lockSync(path), false); | ||
return this.fs.statSync(path, cred); | ||
} | ||
return this.fs.statSync(path, cred); | ||
catch (e_4) { | ||
env_4.error = e_4; | ||
env_4.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_4); | ||
} | ||
} | ||
async openFile(path, flag, cred) { | ||
await this.mutex.lock(path); | ||
const fd = await this.fs.openFile(path, flag, cred); | ||
this.mutex.unlock(path); | ||
return fd; | ||
const env_5 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const _ = __addDisposableResource(env_5, await this.lock(path), false); | ||
return await this.fs.openFile(path, flag, cred); | ||
} | ||
catch (e_5) { | ||
env_5.error = e_5; | ||
env_5.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_5); | ||
} | ||
} | ||
openFileSync(path, flag, cred) { | ||
if (this.mutex.isLocked(path)) { | ||
throw ErrnoError.With('EBUSY', path, 'openFile'); | ||
const env_6 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const _ = __addDisposableResource(env_6, this.lockSync(path), false); | ||
return this.fs.openFileSync(path, flag, cred); | ||
} | ||
return this.fs.openFileSync(path, flag, cred); | ||
catch (e_6) { | ||
env_6.error = e_6; | ||
env_6.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_6); | ||
} | ||
} | ||
async createFile(path, flag, mode, cred) { | ||
await this.mutex.lock(path); | ||
const fd = await this.fs.createFile(path, flag, mode, cred); | ||
this.mutex.unlock(path); | ||
return fd; | ||
const env_7 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const _ = __addDisposableResource(env_7, await this.lock(path), false); | ||
return await this.fs.createFile(path, flag, mode, cred); | ||
} | ||
catch (e_7) { | ||
env_7.error = e_7; | ||
env_7.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_7); | ||
} | ||
} | ||
createFileSync(path, flag, mode, cred) { | ||
if (this.mutex.isLocked(path)) { | ||
throw ErrnoError.With('EBUSY', path, 'createFile'); | ||
const env_8 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const _ = __addDisposableResource(env_8, this.lockSync(path), false); | ||
return this.fs.createFileSync(path, flag, mode, cred); | ||
} | ||
return this.fs.createFileSync(path, flag, mode, cred); | ||
catch (e_8) { | ||
env_8.error = e_8; | ||
env_8.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_8); | ||
} | ||
} | ||
async unlink(path, cred) { | ||
await this.mutex.lock(path); | ||
await this.fs.unlink(path, cred); | ||
this.mutex.unlock(path); | ||
const env_9 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const _ = __addDisposableResource(env_9, await this.lock(path), false); | ||
await this.fs.unlink(path, cred); | ||
} | ||
catch (e_9) { | ||
env_9.error = e_9; | ||
env_9.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_9); | ||
} | ||
} | ||
unlinkSync(path, cred) { | ||
if (this.mutex.isLocked(path)) { | ||
throw ErrnoError.With('EBUSY', path, 'unlink'); | ||
const env_10 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const _ = __addDisposableResource(env_10, this.lockSync(path), false); | ||
return this.fs.unlinkSync(path, cred); | ||
} | ||
return this.fs.unlinkSync(path, cred); | ||
catch (e_10) { | ||
env_10.error = e_10; | ||
env_10.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_10); | ||
} | ||
} | ||
async rmdir(path, cred) { | ||
await this.mutex.lock(path); | ||
await this.fs.rmdir(path, cred); | ||
this.mutex.unlock(path); | ||
const env_11 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const _ = __addDisposableResource(env_11, await this.lock(path), false); | ||
await this.fs.rmdir(path, cred); | ||
} | ||
catch (e_11) { | ||
env_11.error = e_11; | ||
env_11.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_11); | ||
} | ||
} | ||
rmdirSync(path, cred) { | ||
if (this.mutex.isLocked(path)) { | ||
throw ErrnoError.With('EBUSY', path, 'rmdir'); | ||
const env_12 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const _ = __addDisposableResource(env_12, this.lockSync(path), false); | ||
return this.fs.rmdirSync(path, cred); | ||
} | ||
return this.fs.rmdirSync(path, cred); | ||
catch (e_12) { | ||
env_12.error = e_12; | ||
env_12.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_12); | ||
} | ||
} | ||
async mkdir(path, mode, cred) { | ||
await this.mutex.lock(path); | ||
await this.fs.mkdir(path, mode, cred); | ||
this.mutex.unlock(path); | ||
const env_13 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const _ = __addDisposableResource(env_13, await this.lock(path), false); | ||
await this.fs.mkdir(path, mode, cred); | ||
} | ||
catch (e_13) { | ||
env_13.error = e_13; | ||
env_13.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_13); | ||
} | ||
} | ||
mkdirSync(path, mode, cred) { | ||
if (this.mutex.isLocked(path)) { | ||
throw ErrnoError.With('EBUSY', path, 'mkdir'); | ||
const env_14 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const _ = __addDisposableResource(env_14, this.lockSync(path), false); | ||
return this.fs.mkdirSync(path, mode, cred); | ||
} | ||
return this.fs.mkdirSync(path, mode, cred); | ||
catch (e_14) { | ||
env_14.error = e_14; | ||
env_14.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_14); | ||
} | ||
} | ||
async readdir(path, cred) { | ||
await this.mutex.lock(path); | ||
const files = await this.fs.readdir(path, cred); | ||
this.mutex.unlock(path); | ||
return files; | ||
const env_15 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const _ = __addDisposableResource(env_15, await this.lock(path), false); | ||
return await this.fs.readdir(path, cred); | ||
} | ||
catch (e_15) { | ||
env_15.error = e_15; | ||
env_15.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_15); | ||
} | ||
} | ||
readdirSync(path, cred) { | ||
if (this.mutex.isLocked(path)) { | ||
throw ErrnoError.With('EBUSY', path, 'readdir'); | ||
const env_16 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const _ = __addDisposableResource(env_16, this.lockSync(path), false); | ||
return this.fs.readdirSync(path, cred); | ||
} | ||
return this.fs.readdirSync(path, cred); | ||
catch (e_16) { | ||
env_16.error = e_16; | ||
env_16.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_16); | ||
} | ||
} | ||
async exists(path, cred) { | ||
await this.mutex.lock(path); | ||
const exists = await this.fs.exists(path, cred); | ||
this.mutex.unlock(path); | ||
return exists; | ||
const env_17 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const _ = __addDisposableResource(env_17, await this.lock(path), false); | ||
return await this.fs.exists(path, cred); | ||
} | ||
catch (e_17) { | ||
env_17.error = e_17; | ||
env_17.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_17); | ||
} | ||
} | ||
existsSync(path, cred) { | ||
if (this.mutex.isLocked(path)) { | ||
throw ErrnoError.With('EBUSY', path, 'exists'); | ||
const env_18 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const _ = __addDisposableResource(env_18, this.lockSync(path), false); | ||
return this.fs.existsSync(path, cred); | ||
} | ||
return this.fs.existsSync(path, cred); | ||
catch (e_18) { | ||
env_18.error = e_18; | ||
env_18.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_18); | ||
} | ||
} | ||
async link(srcpath, dstpath, cred) { | ||
await this.mutex.lock(srcpath); | ||
await this.fs.link(srcpath, dstpath, cred); | ||
this.mutex.unlock(srcpath); | ||
const env_19 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const _ = __addDisposableResource(env_19, await this.lock(srcpath), false); | ||
await this.fs.link(srcpath, dstpath, cred); | ||
} | ||
catch (e_19) { | ||
env_19.error = e_19; | ||
env_19.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_19); | ||
} | ||
} | ||
linkSync(srcpath, dstpath, cred) { | ||
if (this.mutex.isLocked(srcpath)) { | ||
throw ErrnoError.With('EBUSY', srcpath, 'link'); | ||
const env_20 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const _ = __addDisposableResource(env_20, this.lockSync(srcpath), false); | ||
return this.fs.linkSync(srcpath, dstpath, cred); | ||
} | ||
return this.fs.linkSync(srcpath, dstpath, cred); | ||
catch (e_20) { | ||
env_20.error = e_20; | ||
env_20.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_20); | ||
} | ||
} | ||
async sync(path, data, stats) { | ||
await this.mutex.lock(path); | ||
await this.fs.sync(path, data, stats); | ||
this.mutex.unlock(path); | ||
const env_21 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const _ = __addDisposableResource(env_21, await this.lock(path), false); | ||
await this.fs.sync(path, data, stats); | ||
} | ||
catch (e_21) { | ||
env_21.error = e_21; | ||
env_21.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_21); | ||
} | ||
} | ||
syncSync(path, data, stats) { | ||
if (this.mutex.isLocked(path)) { | ||
throw ErrnoError.With('EBUSY', path, 'sync'); | ||
const env_22 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const _ = __addDisposableResource(env_22, this.lockSync(path), false); | ||
return this.fs.syncSync(path, data, stats); | ||
} | ||
return this.fs.syncSync(path, data, stats); | ||
catch (e_22) { | ||
env_22.error = e_22; | ||
env_22.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_22); | ||
} | ||
} | ||
} | ||
export const Locked = { | ||
name: 'Locked', | ||
options: { | ||
fs: { | ||
type: 'object', | ||
required: true, | ||
description: '', | ||
validator(fs) { | ||
if (!(fs instanceof FileSystem)) { | ||
throw new ErrnoError(Errno.EINVAL, 'fs passed to LockedFS must be a FileSystem'); | ||
} | ||
}, | ||
}, | ||
}, | ||
isAvailable() { | ||
return true; | ||
}, | ||
create({ fs }) { | ||
return new LockedFS(fs); | ||
}, | ||
}; |
@@ -59,2 +59,5 @@ import { FileSystem } from '../filesystem.js'; | ||
await this.createParentDirectories(path, cred); | ||
if (!(await this._writable.exists(path, cred))) { | ||
await this._writable.createFile(path, 'w', 0o644, cred); | ||
} | ||
await this._writable.sync(path, data, stats); | ||
@@ -127,3 +130,3 @@ } | ||
try { | ||
return this._writable.stat(path, cred); | ||
return await this._writable.stat(path, cred); | ||
} | ||
@@ -130,0 +133,0 @@ catch (e) { |
import type { Ino } from '../../inode.js'; | ||
import '../../symbol-dispose.js'; | ||
/** | ||
@@ -3,0 +4,0 @@ * Represents a key-value store. |
import { ErrnoError } from '../../error.js'; | ||
import '../../symbol-dispose.js'; | ||
/** | ||
@@ -3,0 +4,0 @@ * A transaction for a store. |
@@ -21,2 +21,3 @@ /// <reference types="node" resolution-mode="require"/> | ||
export * as constants from './constants.js'; | ||
import '../symbol-dispose.js'; | ||
export declare class FileHandle implements promises.FileHandle { | ||
@@ -23,0 +24,0 @@ /** |
@@ -0,1 +1,46 @@ | ||
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) { | ||
if (value !== null && value !== void 0) { | ||
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected."); | ||
var dispose; | ||
if (async) { | ||
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined."); | ||
dispose = value[Symbol.asyncDispose]; | ||
} | ||
if (dispose === void 0) { | ||
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined."); | ||
dispose = value[Symbol.dispose]; | ||
} | ||
if (typeof dispose !== "function") throw new TypeError("Object not disposable."); | ||
env.stack.push({ value: value, dispose: dispose, async: async }); | ||
} | ||
else if (async) { | ||
env.stack.push({ async: true }); | ||
} | ||
return value; | ||
}; | ||
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) { | ||
return function (env) { | ||
function fail(e) { | ||
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e; | ||
env.hasError = true; | ||
} | ||
function next() { | ||
while (env.stack.length) { | ||
var rec = env.stack.pop(); | ||
try { | ||
var result = rec.dispose && rec.dispose.call(rec.value); | ||
if (rec.async) return Promise.resolve(result).then(next, function(e) { fail(e); return next(); }); | ||
} | ||
catch (e) { | ||
fail(e); | ||
} | ||
} | ||
if (env.hasError) throw env.error; | ||
} | ||
return next(); | ||
}; | ||
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { | ||
var e = new Error(message); | ||
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; | ||
}); | ||
import { Buffer } from 'buffer'; | ||
@@ -12,2 +57,3 @@ import { Errno, ErrnoError } from '../error.js'; | ||
export * as constants from './constants.js'; | ||
import '../symbol-dispose.js'; | ||
export class FileHandle { | ||
@@ -357,8 +403,15 @@ constructor(fdOrFile) { | ||
export async function truncate(path, len = 0) { | ||
const handle = await open(path, 'r+'); | ||
const env_1 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const handle = __addDisposableResource(env_1, await open(path, 'r+'), true); | ||
await handle.truncate(len); | ||
} | ||
catch (e_1) { | ||
env_1.error = e_1; | ||
env_1.hasError = true; | ||
} | ||
finally { | ||
await handle.close(); | ||
const result_1 = __disposeResources(env_1); | ||
if (result_1) | ||
await result_1; | ||
} | ||
@@ -430,9 +483,16 @@ } | ||
export async function readFile(path, _options) { | ||
const options = normalizeOptions(_options, null, 'r', 0o644); | ||
const handle = typeof path == 'object' && 'fd' in path ? path : await open(path, options.flag, options.mode); | ||
const env_2 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const options = normalizeOptions(_options, null, 'r', 0o644); | ||
const handle = __addDisposableResource(env_2, typeof path == 'object' && 'fd' in path ? path : await open(path, options.flag, options.mode), true); | ||
return await handle.readFile(options); | ||
} | ||
catch (e_2) { | ||
env_2.error = e_2; | ||
env_2.hasError = true; | ||
} | ||
finally { | ||
await handle.close(); | ||
const result_2 = __disposeResources(env_2); | ||
if (result_2) | ||
await result_2; | ||
} | ||
@@ -453,5 +513,6 @@ } | ||
export async function writeFile(path, data, _options) { | ||
const options = normalizeOptions(_options, 'utf8', 'w+', 0o644); | ||
const handle = path instanceof FileHandle ? path : await open(path.toString(), options.flag, options.mode); | ||
const env_3 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const options = normalizeOptions(_options, 'utf8', 'w+', 0o644); | ||
const handle = __addDisposableResource(env_3, path instanceof FileHandle ? path : await open(path.toString(), options.flag, options.mode), true); | ||
const _data = typeof data == 'string' ? data : data; | ||
@@ -463,4 +524,10 @@ if (typeof _data != 'string' && !(_data instanceof Uint8Array)) { | ||
} | ||
catch (e_3) { | ||
env_3.error = e_3; | ||
env_3.hasError = true; | ||
} | ||
finally { | ||
await handle.close(); | ||
const result_3 = __disposeResources(env_3); | ||
if (result_3) | ||
await result_3; | ||
} | ||
@@ -480,17 +547,24 @@ } | ||
export async function appendFile(path, data, _options) { | ||
const options = normalizeOptions(_options, 'utf8', 'a', 0o644); | ||
const flag = parseFlag(options.flag); | ||
if (!isAppendable(flag)) { | ||
throw new ErrnoError(Errno.EINVAL, 'Flag passed to appendFile must allow for appending.'); | ||
} | ||
if (typeof data != 'string' && !options.encoding) { | ||
throw new ErrnoError(Errno.EINVAL, 'Encoding not specified'); | ||
} | ||
const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength); | ||
const handle = typeof path == 'object' && 'fd' in path ? path : await open(path, options.flag, options.mode); | ||
const env_4 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const options = normalizeOptions(_options, 'utf8', 'a', 0o644); | ||
const flag = parseFlag(options.flag); | ||
if (!isAppendable(flag)) { | ||
throw new ErrnoError(Errno.EINVAL, 'Flag passed to appendFile must allow for appending.'); | ||
} | ||
if (typeof data != 'string' && !options.encoding) { | ||
throw new ErrnoError(Errno.EINVAL, 'Encoding not specified'); | ||
} | ||
const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength); | ||
const handle = __addDisposableResource(env_4, typeof path == 'object' && 'fd' in path ? path : await open(path, options.flag, options.mode), true); | ||
await handle.appendFile(encodedData, options); | ||
} | ||
catch (e_4) { | ||
env_4.error = e_4; | ||
env_4.hasError = true; | ||
} | ||
finally { | ||
await handle.close(); | ||
const result_4 = __disposeResources(env_4); | ||
if (result_4) | ||
await result_4; | ||
} | ||
@@ -607,4 +681,5 @@ } | ||
export async function readlink(path, options) { | ||
const handle = await _open(normalizePath(path), 'r', 0o644, false); | ||
const env_5 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const handle = __addDisposableResource(env_5, await _open(normalizePath(path), 'r', 0o644, false), true); | ||
const value = await handle.readFile(); | ||
@@ -614,4 +689,10 @@ const encoding = typeof options == 'object' ? options?.encoding : options; | ||
} | ||
catch (e_5) { | ||
env_5.error = e_5; | ||
env_5.hasError = true; | ||
} | ||
finally { | ||
await handle.close(); | ||
const result_5 = __disposeResources(env_5); | ||
if (result_5) | ||
await result_5; | ||
} | ||
@@ -628,8 +709,15 @@ } | ||
export async function chown(path, uid, gid) { | ||
const handle = await open(path, 'r+'); | ||
const env_6 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const handle = __addDisposableResource(env_6, await open(path, 'r+'), true); | ||
await handle.chown(uid, gid); | ||
} | ||
catch (e_6) { | ||
env_6.error = e_6; | ||
env_6.hasError = true; | ||
} | ||
finally { | ||
await handle.close(); | ||
const result_6 = __disposeResources(env_6); | ||
if (result_6) | ||
await result_6; | ||
} | ||
@@ -645,8 +733,15 @@ } | ||
export async function lchown(path, uid, gid) { | ||
const handle = await _open(path, 'r+', 0o644, false); | ||
const env_7 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const handle = __addDisposableResource(env_7, await _open(path, 'r+', 0o644, false), true); | ||
await handle.chown(uid, gid); | ||
} | ||
catch (e_7) { | ||
env_7.error = e_7; | ||
env_7.hasError = true; | ||
} | ||
finally { | ||
await handle.close(); | ||
const result_7 = __disposeResources(env_7); | ||
if (result_7) | ||
await result_7; | ||
} | ||
@@ -661,8 +756,15 @@ } | ||
export async function chmod(path, mode) { | ||
const handle = await open(path, 'r+'); | ||
const env_8 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const handle = __addDisposableResource(env_8, await open(path, 'r+'), true); | ||
await handle.chmod(mode); | ||
} | ||
catch (e_8) { | ||
env_8.error = e_8; | ||
env_8.hasError = true; | ||
} | ||
finally { | ||
await handle.close(); | ||
const result_8 = __disposeResources(env_8); | ||
if (result_8) | ||
await result_8; | ||
} | ||
@@ -677,8 +779,15 @@ } | ||
export async function lchmod(path, mode) { | ||
const handle = await _open(path, 'r+', 0o644, false); | ||
const env_9 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const handle = __addDisposableResource(env_9, await _open(path, 'r+', 0o644, false), true); | ||
await handle.chmod(mode); | ||
} | ||
catch (e_9) { | ||
env_9.error = e_9; | ||
env_9.hasError = true; | ||
} | ||
finally { | ||
await handle.close(); | ||
const result_9 = __disposeResources(env_9); | ||
if (result_9) | ||
await result_9; | ||
} | ||
@@ -694,8 +803,15 @@ } | ||
export async function utimes(path, atime, mtime) { | ||
const handle = await open(path, 'r+'); | ||
const env_10 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const handle = __addDisposableResource(env_10, await open(path, 'r+'), true); | ||
await handle.utimes(atime, mtime); | ||
} | ||
catch (e_10) { | ||
env_10.error = e_10; | ||
env_10.hasError = true; | ||
} | ||
finally { | ||
await handle.close(); | ||
const result_10 = __disposeResources(env_10); | ||
if (result_10) | ||
await result_10; | ||
} | ||
@@ -711,8 +827,15 @@ } | ||
export async function lutimes(path, atime, mtime) { | ||
const handle = await _open(path, 'r+', 0o644, false); | ||
const env_11 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const handle = __addDisposableResource(env_11, await _open(path, 'r+', 0o644, false), true); | ||
await handle.utimes(new Date(atime), new Date(mtime)); | ||
} | ||
catch (e_11) { | ||
env_11.error = e_11; | ||
env_11.hasError = true; | ||
} | ||
finally { | ||
await handle.close(); | ||
const result_11 = __disposeResources(env_11); | ||
if (result_11) | ||
await result_11; | ||
} | ||
@@ -719,0 +842,0 @@ } |
@@ -0,1 +1,46 @@ | ||
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) { | ||
if (value !== null && value !== void 0) { | ||
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected."); | ||
var dispose; | ||
if (async) { | ||
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined."); | ||
dispose = value[Symbol.asyncDispose]; | ||
} | ||
if (dispose === void 0) { | ||
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined."); | ||
dispose = value[Symbol.dispose]; | ||
} | ||
if (typeof dispose !== "function") throw new TypeError("Object not disposable."); | ||
env.stack.push({ value: value, dispose: dispose, async: async }); | ||
} | ||
else if (async) { | ||
env.stack.push({ async: true }); | ||
} | ||
return value; | ||
}; | ||
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) { | ||
return function (env) { | ||
function fail(e) { | ||
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e; | ||
env.hasError = true; | ||
} | ||
function next() { | ||
while (env.stack.length) { | ||
var rec = env.stack.pop(); | ||
try { | ||
var result = rec.dispose && rec.dispose.call(rec.value); | ||
if (rec.async) return Promise.resolve(result).then(next, function(e) { fail(e); return next(); }); | ||
} | ||
catch (e) { | ||
fail(e); | ||
} | ||
} | ||
if (env.hasError) throw env.error; | ||
} | ||
return next(); | ||
}; | ||
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { | ||
var e = new Error(message); | ||
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; | ||
}); | ||
import { Buffer } from 'buffer'; | ||
@@ -81,8 +126,17 @@ import { Errno, ErrnoError } from '../error.js'; | ||
export function truncateSync(path, len = 0) { | ||
const fd = openSync(path, 'r+'); | ||
const env_1 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
ftruncateSync(fd, len); | ||
const file = __addDisposableResource(env_1, _openSync(path, 'r+'), false); | ||
len || (len = 0); | ||
if (len < 0) { | ||
throw new ErrnoError(Errno.EINVAL); | ||
} | ||
file.truncateSync(len); | ||
} | ||
catch (e_1) { | ||
env_1.error = e_1; | ||
env_1.hasError = true; | ||
} | ||
finally { | ||
closeSync(fd); | ||
__disposeResources(env_1); | ||
} | ||
@@ -165,5 +219,6 @@ } | ||
function _readFileSync(fname, flag, resolveSymlinks) { | ||
// Get file. | ||
const file = _openSync(fname, flag, 0o644, resolveSymlinks); | ||
const env_2 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
// Get file. | ||
const file = __addDisposableResource(env_2, _openSync(fname, flag, 0o644, resolveSymlinks), false); | ||
const stat = file.statSync(); | ||
@@ -176,4 +231,8 @@ // Allocate buffer. | ||
} | ||
catch (e_2) { | ||
env_2.error = e_2; | ||
env_2.hasError = true; | ||
} | ||
finally { | ||
file.closeSync(); | ||
__disposeResources(env_2); | ||
} | ||
@@ -192,20 +251,25 @@ } | ||
export function writeFileSync(path, data, _options = {}) { | ||
const options = normalizeOptions(_options, 'utf8', 'w+', 0o644); | ||
const flag = parseFlag(options.flag); | ||
if (!isWriteable(flag)) { | ||
throw new ErrnoError(Errno.EINVAL, 'Flag passed to writeFile must allow for writing.'); | ||
} | ||
if (typeof data != 'string' && !options.encoding) { | ||
throw new ErrnoError(Errno.EINVAL, 'Encoding not specified'); | ||
} | ||
const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength); | ||
if (!encodedData) { | ||
throw new ErrnoError(Errno.EINVAL, 'Data not specified'); | ||
} | ||
const file = _openSync(typeof path == 'number' ? fd2file(path).path : path.toString(), flag, options.mode, true); | ||
const env_3 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const options = normalizeOptions(_options, 'utf8', 'w+', 0o644); | ||
const flag = parseFlag(options.flag); | ||
if (!isWriteable(flag)) { | ||
throw new ErrnoError(Errno.EINVAL, 'Flag passed to writeFile must allow for writing.'); | ||
} | ||
if (typeof data != 'string' && !options.encoding) { | ||
throw new ErrnoError(Errno.EINVAL, 'Encoding not specified'); | ||
} | ||
const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength); | ||
if (!encodedData) { | ||
throw new ErrnoError(Errno.EINVAL, 'Data not specified'); | ||
} | ||
const file = __addDisposableResource(env_3, _openSync(typeof path == 'number' ? fd2file(path).path : path.toString(), flag, options.mode, true), false); | ||
file.writeSync(encodedData, 0, encodedData.byteLength, 0); | ||
} | ||
catch (e_3) { | ||
env_3.error = e_3; | ||
env_3.hasError = true; | ||
} | ||
finally { | ||
file.closeSync(); | ||
__disposeResources(env_3); | ||
} | ||
@@ -226,17 +290,22 @@ } | ||
export function appendFileSync(filename, data, _options = {}) { | ||
const options = normalizeOptions(_options, 'utf8', 'a', 0o644); | ||
const flag = parseFlag(options.flag); | ||
if (!isAppendable(flag)) { | ||
throw new ErrnoError(Errno.EINVAL, 'Flag passed to appendFile must allow for appending.'); | ||
} | ||
if (typeof data != 'string' && !options.encoding) { | ||
throw new ErrnoError(Errno.EINVAL, 'Encoding not specified'); | ||
} | ||
const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength); | ||
const file = _openSync(typeof filename == 'number' ? fd2file(filename).path : filename.toString(), flag, options.mode, true); | ||
const env_4 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const options = normalizeOptions(_options, 'utf8', 'a', 0o644); | ||
const flag = parseFlag(options.flag); | ||
if (!isAppendable(flag)) { | ||
throw new ErrnoError(Errno.EINVAL, 'Flag passed to appendFile must allow for appending.'); | ||
} | ||
if (typeof data != 'string' && !options.encoding) { | ||
throw new ErrnoError(Errno.EINVAL, 'Encoding not specified'); | ||
} | ||
const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength); | ||
const file = __addDisposableResource(env_4, _openSync(typeof filename == 'number' ? fd2file(filename).path : filename.toString(), flag, options.mode, true), false); | ||
file.writeSync(encodedData, 0, encodedData.byteLength); | ||
} | ||
catch (e_4) { | ||
env_4.error = e_4; | ||
env_4.hasError = true; | ||
} | ||
finally { | ||
file.closeSync(); | ||
__disposeResources(env_4); | ||
} | ||
@@ -243,0 +312,0 @@ } |
@@ -6,2 +6,3 @@ /// <reference types="node" resolution-mode="require"/> | ||
import { Stats, type FileType } from './stats.js'; | ||
import './symbol-dispose.js'; | ||
declare global { | ||
@@ -8,0 +9,0 @@ interface ArrayBuffer { |
@@ -5,2 +5,3 @@ import { O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_RDWR, O_SYNC, O_TRUNC, O_WRONLY, S_IFMT } from './emulation/constants.js'; | ||
import { Stats } from './stats.js'; | ||
import './symbol-dispose.js'; | ||
const validFlags = ['r', 'r+', 'rs', 'rs+', 'w', 'wx', 'w+', 'wx+', 'a', 'ax', 'a+', 'ax+']; | ||
@@ -7,0 +8,0 @@ export function parseFlag(flag) { |
@@ -0,1 +1,46 @@ | ||
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) { | ||
if (value !== null && value !== void 0) { | ||
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected."); | ||
var dispose; | ||
if (async) { | ||
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined."); | ||
dispose = value[Symbol.asyncDispose]; | ||
} | ||
if (dispose === void 0) { | ||
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined."); | ||
dispose = value[Symbol.dispose]; | ||
} | ||
if (typeof dispose !== "function") throw new TypeError("Object not disposable."); | ||
env.stack.push({ value: value, dispose: dispose, async: async }); | ||
} | ||
else if (async) { | ||
env.stack.push({ async: true }); | ||
} | ||
return value; | ||
}; | ||
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) { | ||
return function (env) { | ||
function fail(e) { | ||
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e; | ||
env.hasError = true; | ||
} | ||
function next() { | ||
while (env.stack.length) { | ||
var rec = env.stack.pop(); | ||
try { | ||
var result = rec.dispose && rec.dispose.call(rec.value); | ||
if (rec.async) return Promise.resolve(result).then(next, function(e) { fail(e); return next(); }); | ||
} | ||
catch (e) { | ||
fail(e); | ||
} | ||
} | ||
if (env.hasError) throw env.error; | ||
} | ||
return next(); | ||
}; | ||
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { | ||
var e = new Error(message); | ||
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; | ||
}); | ||
import { rootCred } from './cred.js'; | ||
@@ -223,5 +268,6 @@ import { join } from './emulation/path.js'; | ||
else { | ||
const asyncFile = await this.openFile(path, parseFlag('r'), rootCred); | ||
const syncFile = this._sync.createFileSync(path, parseFlag('w'), stats.mode, stats.cred()); | ||
const env_1 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const asyncFile = __addDisposableResource(env_1, await this.openFile(path, parseFlag('r'), rootCred), true); | ||
const syncFile = __addDisposableResource(env_1, this._sync.createFileSync(path, parseFlag('w'), stats.mode, stats.cred()), false); | ||
const buffer = new Uint8Array(stats.size); | ||
@@ -231,5 +277,10 @@ await asyncFile.read(buffer); | ||
} | ||
catch (e_1) { | ||
env_1.error = e_1; | ||
env_1.hasError = true; | ||
} | ||
finally { | ||
await asyncFile.close(); | ||
syncFile.closeSync(); | ||
const result_1 = __disposeResources(env_1); | ||
if (result_1) | ||
await result_1; | ||
} | ||
@@ -236,0 +287,0 @@ } |
@@ -17,3 +17,2 @@ export * from './error.js'; | ||
export * from './inode.js'; | ||
export * from './mutex.js'; | ||
export * from './stats.js'; | ||
@@ -20,0 +19,0 @@ export * from './utils.js'; |
@@ -17,3 +17,2 @@ export * from './error.js'; | ||
export * from './inode.js'; | ||
export * from './mutex.js'; | ||
export * from './stats.js'; | ||
@@ -20,0 +19,0 @@ export * from './utils.js'; |
{ | ||
"name": "@zenfs/core", | ||
"version": "0.13.0", | ||
"version": "0.14.0", | ||
"description": "A filesystem, anywhere", | ||
@@ -37,3 +37,3 @@ "main": "dist/index.js", | ||
"engines": { | ||
"node": ">= 18" | ||
"node": ">= 22" | ||
}, | ||
@@ -73,5 +73,5 @@ "exports": { | ||
"eslint": "^8.36.0", | ||
"jest": "^29.5.0", | ||
"jest": "^29.7.0", | ||
"prettier": "^3.2.5", | ||
"ts-jest": "^29.1.0", | ||
"ts-jest": "^29.1.5", | ||
"typedoc": "^0.25.13", | ||
@@ -78,0 +78,0 @@ "typedoc-plugin-remove-references": "^0.0.6", |
@@ -81,5 +81,5 @@ #!/usr/bin/env node | ||
const stats = statSync(path); | ||
entries.set('/' + relative(resolvedRoot, path), stats); | ||
if (stats.isFile()) { | ||
entries.set('/' + relative(resolvedRoot, path), stats); | ||
if (options.verbose) { | ||
@@ -94,2 +94,3 @@ console.log(`${color('green', 'file')} ${path}`); | ||
} | ||
entries.set('/' + relative(resolvedRoot, path), stats); | ||
if (options.verbose) { | ||
@@ -96,0 +97,0 @@ console.log(`${color('bright_green', ' dir')} ${path}`); |
@@ -1,8 +0,13 @@ | ||
import { ErrnoError } from '../error.js'; | ||
import type { Cred } from '../cred.js'; | ||
import { Errno, ErrnoError } from '../error.js'; | ||
import type { File } from '../file.js'; | ||
import type { FileSystem, FileSystemMetadata } from '../filesystem.js'; | ||
import { Mutex } from '../mutex.js'; | ||
import type { FileSystemMetadata } from '../filesystem.js'; | ||
import { FileSystem } from '../filesystem.js'; | ||
import type { Stats } from '../stats.js'; | ||
import type { Backend } from './backend.js'; | ||
export interface MutexLock extends PromiseWithResolvers<void> { | ||
[Symbol.dispose](): void; | ||
} | ||
/** | ||
@@ -19,6 +24,77 @@ * This class serializes access to an underlying async filesystem. | ||
export class LockedFS<FS extends FileSystem> implements FileSystem { | ||
private mutex: Mutex = new Mutex(); | ||
constructor(public readonly fs: FS) {} | ||
/** | ||
* The current locks | ||
*/ | ||
private locks: Map<string, MutexLock> = new Map(); | ||
protected addLock(path: string): MutexLock { | ||
const lock: MutexLock = { | ||
...Promise.withResolvers(), | ||
[Symbol.dispose]: () => { | ||
this.unlock(path); | ||
}, | ||
}; | ||
this.locks.set(path, lock); | ||
return lock; | ||
} | ||
/** | ||
* Locks `path` asynchronously. | ||
* If the path is currently locked, waits for it to be unlocked. | ||
* @internal | ||
*/ | ||
public async lock(path: string): Promise<MutexLock> { | ||
if (this.locks.has(path)) { | ||
// Non-null assertion: we already checked locks has path | ||
await this.locks.get(path)!.promise; | ||
} | ||
return this.addLock(path); | ||
} | ||
/** | ||
* Locks `path` asynchronously. | ||
* If the path is currently locked, an error will be thrown | ||
* @internal | ||
*/ | ||
public lockSync(path: string): MutexLock { | ||
if (this.locks.has(path)) { | ||
// Non-null assertion: we already checked locks has path | ||
throw ErrnoError.With('EBUSY', path, 'lockSync'); | ||
} | ||
return this.addLock(path); | ||
} | ||
/** | ||
* Unlocks a path | ||
* @param path The path to lock | ||
* @param noThrow If true, an error will not be thrown if the path is already unlocked | ||
* @returns Whether the path was unlocked | ||
* @internal | ||
*/ | ||
public unlock(path: string, noThrow: boolean = false): boolean { | ||
if (!this.locks.has(path)) { | ||
if (noThrow) { | ||
return false; | ||
} | ||
throw new ErrnoError(Errno.EPERM, 'Can not unlock an already unlocked path', path); | ||
} | ||
// Non-null assertion: we already checked locks has path | ||
this.locks.get(path)!.resolve(); | ||
this.locks.delete(path); | ||
return true; | ||
} | ||
/** | ||
* Whether `path` is locked | ||
* @internal | ||
*/ | ||
public isLocked(path: string): boolean { | ||
return this.locks.has(path); | ||
} | ||
public async ready(): Promise<void> { | ||
@@ -36,11 +112,10 @@ await this.fs.ready(); | ||
public async rename(oldPath: string, newPath: string, cred: Cred): Promise<void> { | ||
await this.mutex.lock(oldPath); | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
using _ = await this.lock(oldPath); | ||
await this.fs.rename(oldPath, newPath, cred); | ||
this.mutex.unlock(oldPath); | ||
} | ||
public renameSync(oldPath: string, newPath: string, cred: Cred): void { | ||
if (this.mutex.isLocked(oldPath)) { | ||
throw ErrnoError.With('EBUSY', oldPath, 'rename'); | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
using _ = this.lockSync(oldPath); | ||
return this.fs.renameSync(oldPath, newPath, cred); | ||
@@ -50,12 +125,10 @@ } | ||
public async stat(path: string, cred: Cred): Promise<Stats> { | ||
await this.mutex.lock(path); | ||
const stats = await this.fs.stat(path, cred); | ||
this.mutex.unlock(path); | ||
return stats; | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
using _ = await this.lock(path); | ||
return await this.fs.stat(path, cred); | ||
} | ||
public statSync(path: string, cred: Cred): Stats { | ||
if (this.mutex.isLocked(path)) { | ||
throw ErrnoError.With('EBUSY', path, 'stat'); | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
using _ = this.lockSync(path); | ||
return this.fs.statSync(path, cred); | ||
@@ -65,12 +138,10 @@ } | ||
public async openFile(path: string, flag: string, cred: Cred): Promise<File> { | ||
await this.mutex.lock(path); | ||
const fd = await this.fs.openFile(path, flag, cred); | ||
this.mutex.unlock(path); | ||
return fd; | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
using _ = await this.lock(path); | ||
return await this.fs.openFile(path, flag, cred); | ||
} | ||
public openFileSync(path: string, flag: string, cred: Cred): File { | ||
if (this.mutex.isLocked(path)) { | ||
throw ErrnoError.With('EBUSY', path, 'openFile'); | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
using _ = this.lockSync(path); | ||
return this.fs.openFileSync(path, flag, cred); | ||
@@ -80,12 +151,10 @@ } | ||
public async createFile(path: string, flag: string, mode: number, cred: Cred): Promise<File> { | ||
await this.mutex.lock(path); | ||
const fd = await this.fs.createFile(path, flag, mode, cred); | ||
this.mutex.unlock(path); | ||
return fd; | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
using _ = await this.lock(path); | ||
return await this.fs.createFile(path, flag, mode, cred); | ||
} | ||
public createFileSync(path: string, flag: string, mode: number, cred: Cred): File { | ||
if (this.mutex.isLocked(path)) { | ||
throw ErrnoError.With('EBUSY', path, 'createFile'); | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
using _ = this.lockSync(path); | ||
return this.fs.createFileSync(path, flag, mode, cred); | ||
@@ -95,11 +164,10 @@ } | ||
public async unlink(path: string, cred: Cred): Promise<void> { | ||
await this.mutex.lock(path); | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
using _ = await this.lock(path); | ||
await this.fs.unlink(path, cred); | ||
this.mutex.unlock(path); | ||
} | ||
public unlinkSync(path: string, cred: Cred): void { | ||
if (this.mutex.isLocked(path)) { | ||
throw ErrnoError.With('EBUSY', path, 'unlink'); | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
using _ = this.lockSync(path); | ||
return this.fs.unlinkSync(path, cred); | ||
@@ -109,11 +177,10 @@ } | ||
public async rmdir(path: string, cred: Cred): Promise<void> { | ||
await this.mutex.lock(path); | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
using _ = await this.lock(path); | ||
await this.fs.rmdir(path, cred); | ||
this.mutex.unlock(path); | ||
} | ||
public rmdirSync(path: string, cred: Cred): void { | ||
if (this.mutex.isLocked(path)) { | ||
throw ErrnoError.With('EBUSY', path, 'rmdir'); | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
using _ = this.lockSync(path); | ||
return this.fs.rmdirSync(path, cred); | ||
@@ -123,11 +190,10 @@ } | ||
public async mkdir(path: string, mode: number, cred: Cred): Promise<void> { | ||
await this.mutex.lock(path); | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
using _ = await this.lock(path); | ||
await this.fs.mkdir(path, mode, cred); | ||
this.mutex.unlock(path); | ||
} | ||
public mkdirSync(path: string, mode: number, cred: Cred): void { | ||
if (this.mutex.isLocked(path)) { | ||
throw ErrnoError.With('EBUSY', path, 'mkdir'); | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
using _ = this.lockSync(path); | ||
return this.fs.mkdirSync(path, mode, cred); | ||
@@ -137,12 +203,10 @@ } | ||
public async readdir(path: string, cred: Cred): Promise<string[]> { | ||
await this.mutex.lock(path); | ||
const files = await this.fs.readdir(path, cred); | ||
this.mutex.unlock(path); | ||
return files; | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
using _ = await this.lock(path); | ||
return await this.fs.readdir(path, cred); | ||
} | ||
public readdirSync(path: string, cred: Cred): string[] { | ||
if (this.mutex.isLocked(path)) { | ||
throw ErrnoError.With('EBUSY', path, 'readdir'); | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
using _ = this.lockSync(path); | ||
return this.fs.readdirSync(path, cred); | ||
@@ -152,12 +216,10 @@ } | ||
public async exists(path: string, cred: Cred): Promise<boolean> { | ||
await this.mutex.lock(path); | ||
const exists = await this.fs.exists(path, cred); | ||
this.mutex.unlock(path); | ||
return exists; | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
using _ = await this.lock(path); | ||
return await this.fs.exists(path, cred); | ||
} | ||
public existsSync(path: string, cred: Cred): boolean { | ||
if (this.mutex.isLocked(path)) { | ||
throw ErrnoError.With('EBUSY', path, 'exists'); | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
using _ = this.lockSync(path); | ||
return this.fs.existsSync(path, cred); | ||
@@ -167,11 +229,10 @@ } | ||
public async link(srcpath: string, dstpath: string, cred: Cred): Promise<void> { | ||
await this.mutex.lock(srcpath); | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
using _ = await this.lock(srcpath); | ||
await this.fs.link(srcpath, dstpath, cred); | ||
this.mutex.unlock(srcpath); | ||
} | ||
public linkSync(srcpath: string, dstpath: string, cred: Cred): void { | ||
if (this.mutex.isLocked(srcpath)) { | ||
throw ErrnoError.With('EBUSY', srcpath, 'link'); | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
using _ = this.lockSync(srcpath); | ||
return this.fs.linkSync(srcpath, dstpath, cred); | ||
@@ -181,13 +242,34 @@ } | ||
public async sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void> { | ||
await this.mutex.lock(path); | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
using _ = await this.lock(path); | ||
await this.fs.sync(path, data, stats); | ||
this.mutex.unlock(path); | ||
} | ||
public syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void { | ||
if (this.mutex.isLocked(path)) { | ||
throw ErrnoError.With('EBUSY', path, 'sync'); | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
using _ = this.lockSync(path); | ||
return this.fs.syncSync(path, data, stats); | ||
} | ||
} | ||
export const Locked = { | ||
name: 'Locked', | ||
options: { | ||
fs: { | ||
type: 'object', | ||
required: true, | ||
description: '', | ||
validator(fs) { | ||
if (!(fs instanceof FileSystem)) { | ||
throw new ErrnoError(Errno.EINVAL, 'fs passed to LockedFS must be a FileSystem'); | ||
} | ||
}, | ||
}, | ||
}, | ||
isAvailable() { | ||
return true; | ||
}, | ||
create({ fs }) { | ||
return new LockedFS(fs); | ||
}, | ||
} satisfies Backend<LockedFS<FileSystem>, { fs: FileSystem }>; |
@@ -89,2 +89,5 @@ import type { FileSystemMetadata } from '../filesystem.js'; | ||
await this.createParentDirectories(path, cred); | ||
if (!(await this._writable.exists(path, cred))) { | ||
await this._writable.createFile(path, 'w', 0o644, cred); | ||
} | ||
await this._writable.sync(path, data, stats); | ||
@@ -164,3 +167,3 @@ } | ||
try { | ||
return this._writable.stat(path, cred); | ||
return await this._writable.stat(path, cred); | ||
} catch (e) { | ||
@@ -167,0 +170,0 @@ if (this._deletedFiles.has(path)) { |
import { ErrnoError } from '../../error.js'; | ||
import type { Ino } from '../../inode.js'; | ||
import '../../symbol-dispose.js'; | ||
@@ -4,0 +5,0 @@ /** |
@@ -21,2 +21,3 @@ import { Buffer } from 'buffer'; | ||
export * as constants from './constants.js'; | ||
import '../symbol-dispose.js'; | ||
@@ -460,8 +461,4 @@ export class FileHandle implements promises.FileHandle { | ||
export async function truncate(path: fs.PathLike, len: number = 0): Promise<void> { | ||
const handle = await open(path, 'r+'); | ||
try { | ||
await handle.truncate(len); | ||
} finally { | ||
await handle.close(); | ||
} | ||
await using handle = await open(path, 'r+'); | ||
await handle.truncate(len); | ||
} | ||
@@ -559,9 +556,4 @@ truncate satisfies typeof promises.truncate; | ||
const options = normalizeOptions(_options, null, 'r', 0o644); | ||
const handle: FileHandle | promises.FileHandle = typeof path == 'object' && 'fd' in path ? path : await open(path as string, options.flag, options.mode); | ||
try { | ||
return await handle.readFile(options); | ||
} finally { | ||
await handle.close(); | ||
} | ||
await using handle: FileHandle | promises.FileHandle = typeof path == 'object' && 'fd' in path ? path : await open(path as string, options.flag, options.mode); | ||
return await handle.readFile(options); | ||
} | ||
@@ -587,12 +579,9 @@ readFile satisfies typeof promises.readFile; | ||
const options = normalizeOptions(_options, 'utf8', 'w+', 0o644); | ||
const handle = path instanceof FileHandle ? path : await open(path.toString(), options.flag, options.mode); | ||
try { | ||
const _data = typeof data == 'string' ? data : data; | ||
if (typeof _data != 'string' && !(_data instanceof Uint8Array)) { | ||
throw new ErrnoError(Errno.EINVAL, 'Iterables and streams not supported', handle.file.path, 'writeFile'); | ||
} | ||
await handle.writeFile(_data, options); | ||
} finally { | ||
await handle.close(); | ||
await using handle = path instanceof FileHandle ? path : await open(path.toString(), options.flag, options.mode); | ||
const _data = typeof data == 'string' ? data : data; | ||
if (typeof _data != 'string' && !(_data instanceof Uint8Array)) { | ||
throw new ErrnoError(Errno.EINVAL, 'Iterables and streams not supported', handle.file.path, 'writeFile'); | ||
} | ||
await handle.writeFile(_data, options); | ||
} | ||
@@ -625,9 +614,5 @@ writeFile satisfies typeof promises.writeFile; | ||
const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding!) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength); | ||
const handle: FileHandle | promises.FileHandle = typeof path == 'object' && 'fd' in path ? path : await open(path as string, options.flag, options.mode); | ||
await using handle: FileHandle | promises.FileHandle = typeof path == 'object' && 'fd' in path ? path : await open(path as string, options.flag, options.mode); | ||
try { | ||
await handle.appendFile(encodedData, options); | ||
} finally { | ||
await handle.close(); | ||
} | ||
await handle.appendFile(encodedData, options); | ||
} | ||
@@ -783,10 +768,6 @@ appendFile satisfies typeof promises.appendFile; | ||
export async function readlink(path: fs.PathLike, options?: fs.BufferEncodingOption | fs.EncodingOption | string | null): Promise<string | Buffer> { | ||
const handle = await _open(normalizePath(path), 'r', 0o644, false); | ||
try { | ||
const value = await handle.readFile(); | ||
const encoding = typeof options == 'object' ? options?.encoding : options; | ||
return encoding == 'buffer' ? value : value.toString(encoding! as BufferEncoding); | ||
} finally { | ||
await handle.close(); | ||
} | ||
await using handle = await _open(normalizePath(path), 'r', 0o644, false); | ||
const value = await handle.readFile(); | ||
const encoding = typeof options == 'object' ? options?.encoding : options; | ||
return encoding == 'buffer' ? value : value.toString(encoding! as BufferEncoding); | ||
} | ||
@@ -804,8 +785,4 @@ readlink satisfies typeof promises.readlink; | ||
export async function chown(path: fs.PathLike, uid: number, gid: number): Promise<void> { | ||
const handle = await open(path, 'r+'); | ||
try { | ||
await handle.chown(uid, gid); | ||
} finally { | ||
await handle.close(); | ||
} | ||
await using handle = await open(path, 'r+'); | ||
await handle.chown(uid, gid); | ||
} | ||
@@ -821,8 +798,4 @@ chown satisfies typeof promises.chown; | ||
export async function lchown(path: fs.PathLike, uid: number, gid: number): Promise<void> { | ||
const handle: FileHandle = await _open(path, 'r+', 0o644, false); | ||
try { | ||
await handle.chown(uid, gid); | ||
} finally { | ||
await handle.close(); | ||
} | ||
await using handle: FileHandle = await _open(path, 'r+', 0o644, false); | ||
await handle.chown(uid, gid); | ||
} | ||
@@ -837,8 +810,4 @@ lchown satisfies typeof promises.lchown; | ||
export async function chmod(path: fs.PathLike, mode: fs.Mode): Promise<void> { | ||
const handle = await open(path, 'r+'); | ||
try { | ||
await handle.chmod(mode); | ||
} finally { | ||
await handle.close(); | ||
} | ||
await using handle = await open(path, 'r+'); | ||
await handle.chmod(mode); | ||
} | ||
@@ -853,8 +822,4 @@ chmod satisfies typeof promises.chmod; | ||
export async function lchmod(path: fs.PathLike, mode: fs.Mode): Promise<void> { | ||
const handle: FileHandle = await _open(path, 'r+', 0o644, false); | ||
try { | ||
await handle.chmod(mode); | ||
} finally { | ||
await handle.close(); | ||
} | ||
await using handle: FileHandle = await _open(path, 'r+', 0o644, false); | ||
await handle.chmod(mode); | ||
} | ||
@@ -870,8 +835,4 @@ lchmod satisfies typeof promises.lchmod; | ||
export async function utimes(path: fs.PathLike, atime: string | number | Date, mtime: string | number | Date): Promise<void> { | ||
const handle = await open(path, 'r+'); | ||
try { | ||
await handle.utimes(atime, mtime); | ||
} finally { | ||
await handle.close(); | ||
} | ||
await using handle = await open(path, 'r+'); | ||
await handle.utimes(atime, mtime); | ||
} | ||
@@ -887,8 +848,4 @@ utimes satisfies typeof promises.utimes; | ||
export async function lutimes(path: fs.PathLike, atime: fs.TimeLike, mtime: fs.TimeLike): Promise<void> { | ||
const handle: FileHandle = await _open(path, 'r+', 0o644, false); | ||
try { | ||
await handle.utimes(new Date(atime), new Date(mtime)); | ||
} finally { | ||
await handle.close(); | ||
} | ||
await using handle: FileHandle = await _open(path, 'r+', 0o644, false); | ||
await handle.utimes(new Date(atime), new Date(mtime)); | ||
} | ||
@@ -895,0 +852,0 @@ lutimes satisfies typeof promises.lutimes; |
@@ -102,8 +102,8 @@ import { Buffer } from 'buffer'; | ||
export function truncateSync(path: fs.PathLike, len: number | null = 0): void { | ||
const fd = openSync(path, 'r+'); | ||
try { | ||
ftruncateSync(fd, len); | ||
} finally { | ||
closeSync(fd); | ||
using file = _openSync(path, 'r+'); | ||
len ||= 0; | ||
if (len < 0) { | ||
throw new ErrnoError(Errno.EINVAL); | ||
} | ||
file.truncateSync(len); | ||
} | ||
@@ -198,13 +198,9 @@ truncateSync satisfies typeof fs.truncateSync; | ||
// Get file. | ||
const file = _openSync(fname, flag, 0o644, resolveSymlinks); | ||
try { | ||
const stat = file.statSync(); | ||
// Allocate buffer. | ||
const data = new Uint8Array(stat.size); | ||
file.readSync(data, 0, stat.size, 0); | ||
file.closeSync(); | ||
return data; | ||
} finally { | ||
file.closeSync(); | ||
} | ||
using file = _openSync(fname, flag, 0o644, resolveSymlinks); | ||
const stat = file.statSync(); | ||
// Allocate buffer. | ||
const data = new Uint8Array(stat.size); | ||
file.readSync(data, 0, stat.size, 0); | ||
file.closeSync(); | ||
return data; | ||
} | ||
@@ -260,8 +256,4 @@ | ||
} | ||
const file = _openSync(typeof path == 'number' ? fd2file(path).path! : path.toString(), flag, options.mode, true); | ||
try { | ||
file.writeSync(encodedData, 0, encodedData.byteLength, 0); | ||
} finally { | ||
file.closeSync(); | ||
} | ||
using file = _openSync(typeof path == 'number' ? fd2file(path).path! : path.toString(), flag, options.mode, true); | ||
file.writeSync(encodedData, 0, encodedData.byteLength, 0); | ||
} | ||
@@ -291,8 +283,4 @@ writeFileSync satisfies typeof fs.writeFileSync; | ||
const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding!) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength); | ||
const file = _openSync(typeof filename == 'number' ? fd2file(filename).path! : filename.toString(), flag, options.mode, true); | ||
try { | ||
file.writeSync(encodedData, 0, encodedData.byteLength); | ||
} finally { | ||
file.closeSync(); | ||
} | ||
using file = _openSync(typeof filename == 'number' ? fd2file(filename).path! : filename.toString(), flag, options.mode, true); | ||
file.writeSync(encodedData, 0, encodedData.byteLength); | ||
} | ||
@@ -299,0 +287,0 @@ appendFileSync satisfies typeof fs.appendFileSync; |
@@ -7,2 +7,3 @@ import type { FileReadResult } from 'node:fs/promises'; | ||
import { Stats, type FileType } from './stats.js'; | ||
import './symbol-dispose.js'; | ||
@@ -9,0 +10,0 @@ /* |
@@ -466,12 +466,7 @@ import type { ExtractProperties } from 'utilium'; | ||
} else { | ||
const asyncFile = await this.openFile(path, parseFlag('r'), rootCred); | ||
const syncFile = this._sync.createFileSync(path, parseFlag('w'), stats.mode, stats.cred()); | ||
try { | ||
const buffer = new Uint8Array(stats.size); | ||
await asyncFile.read(buffer); | ||
syncFile.writeSync(buffer, 0, stats.size); | ||
} finally { | ||
await asyncFile.close(); | ||
syncFile.closeSync(); | ||
} | ||
await using asyncFile = await this.openFile(path, parseFlag('r'), rootCred); | ||
using syncFile = this._sync.createFileSync(path, parseFlag('w'), stats.mode, stats.cred()); | ||
const buffer = new Uint8Array(stats.size); | ||
await asyncFile.read(buffer); | ||
syncFile.writeSync(buffer, 0, stats.size); | ||
} | ||
@@ -478,0 +473,0 @@ } |
@@ -17,3 +17,2 @@ export * from './error.js'; | ||
export * from './inode.js'; | ||
export * from './mutex.js'; | ||
export * from './stats.js'; | ||
@@ -20,0 +19,0 @@ export * from './utils.js'; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
1885123
21158
3