UNPKG

@ezdevlol/memfs

Version:

In-memory file-system with Node's fs API.

179 lines (178 loc) 6.31 kB
import { promisify } from './util'; import { EventEmitter } from 'events'; export class FileHandle extends EventEmitter { fs; refs = 1; closePromise = null; closeResolve; closeReject; position = 0; fd; constructor(fs, fd) { super(); this.fs = fs; this.fd = fd; } getAsyncId() { // Return a unique async ID for this file handle // In a real implementation, this would be provided by the underlying system return this.fd; } appendFile(data, options) { return promisify(this.fs, 'appendFile')(this.fd, data, options); } chmod(mode) { return promisify(this.fs, 'fchmod')(this.fd, mode); } chown(uid, gid) { return promisify(this.fs, 'fchown')(this.fd, uid, gid); } close() { if (this.fd === -1) { return Promise.resolve(); } if (this.closePromise) { return this.closePromise; } this.refs--; if (this.refs === 0) { const currentFd = this.fd; this.fd = -1; this.closePromise = promisify(this.fs, 'close')(currentFd).finally(() => { this.closePromise = null; }); } else { this.closePromise = new Promise((resolve, reject) => { this.closeResolve = resolve; this.closeReject = reject; }).finally(() => { this.closePromise = null; this.closeReject = undefined; this.closeResolve = undefined; }); } this.emit('close'); return this.closePromise; } datasync() { return promisify(this.fs, 'fdatasync')(this.fd); } createReadStream(options) { return this.fs.createReadStream('', { ...options, fd: this }); } createWriteStream(options) { return this.fs.createWriteStream('', { ...options, fd: this }); } readableWebStream(options = {}) { const { type = 'bytes' } = options; let position = 0; let locked = false; if (this.fd === -1) { throw new Error('The FileHandle is closed'); } if (this.closePromise) { throw new Error('The FileHandle is closing'); } if (locked) { throw new Error('The FileHandle is locked'); } locked = true; this.ref(); return new ReadableStream({ type: 'bytes', autoAllocateChunkSize: 16384, pull: async (controller) => { try { const view = controller.byobRequest?.view; if (!view) { // Fallback for when BYOB is not available const buffer = new Uint8Array(16384); const result = await this.read(buffer, 0, buffer.length, position); if (result.bytesRead === 0) { controller.close(); this.unref(); return; } position += result.bytesRead; controller.enqueue(buffer.slice(0, result.bytesRead)); return; } const result = await this.read(view, view.byteOffset, view.byteLength, position); if (result.bytesRead === 0) { controller.close(); this.unref(); return; } position += result.bytesRead; controller.byobRequest.respond(result.bytesRead); } catch (error) { controller.error(error); this.unref(); } }, cancel: async () => { this.unref(); }, }); } async read(buffer, offset, length, position) { const readPosition = position !== null && position !== undefined ? position : this.position; const result = await promisify(this.fs, 'read', bytesRead => ({ bytesRead, buffer }))(this.fd, buffer, offset, length, readPosition); // Update internal position only if position was null/undefined if (position === null || position === undefined) { this.position += result.bytesRead; } return result; } readv(buffers, position) { return promisify(this.fs, 'readv', bytesRead => ({ bytesRead, buffers }))(this.fd, buffers, position); } readFile(options) { return promisify(this.fs, 'readFile')(this.fd, options); } stat(options) { return promisify(this.fs, 'fstat')(this.fd, options); } sync() { return promisify(this.fs, 'fsync')(this.fd); } truncate(len) { return promisify(this.fs, 'ftruncate')(this.fd, len); } utimes(atime, mtime) { return promisify(this.fs, 'futimes')(this.fd, atime, mtime); } async write(buffer, offset, length, position) { const writePosition = position !== null && position !== undefined ? position : this.position; const result = await promisify(this.fs, 'write', bytesWritten => ({ bytesWritten, buffer }))(this.fd, buffer, offset, length, writePosition); // Update internal position only if position was null/undefined if (position === null || position === undefined) { this.position += result.bytesWritten; } return result; } writev(buffers, position) { return promisify(this.fs, 'writev', bytesWritten => ({ bytesWritten, buffers }))(this.fd, buffers, position); } writeFile(data, options) { return promisify(this.fs, 'writeFile')(this.fd, data, options); } // Implement Symbol.asyncDispose if available (ES2023+) async [Symbol.asyncDispose]() { await this.close(); } ref() { this.refs++; } unref() { this.refs--; if (this.refs === 0) { this.fd = -1; if (this.closeResolve) { promisify(this.fs, 'close')(this.fd).then(this.closeResolve, this.closeReject); } } } }