UNPKG

fs-zoo

Version:

File system abstractions and implementations

283 lines (282 loc) 10.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MemoryCrud = void 0; const util_1 = require("../crud/util"); const util_2 = require("../fsa-to-crud/util"); class MemoryResource { constructor(parent, name) { this.parent = parent; this.name = name; this.data = new Uint8Array(0); this.created = Date.now(); this.modified = Date.now(); this.readable = true; this.writable = true; } } class MemoryCollection { constructor(parent = void 0, name = '') { this.parent = parent; this.name = name; this.children = new Map(); this.created = Date.now(); this.modified = this.created; } } class MemoryCrud { constructor(root = new MemoryCollection()) { this.root = root; this.put = async (path, data, options) => { const [collection, id] = (0, util_1.parseId)(path); (0, util_1.assertType)(collection, 'put', 'crudfs'); (0, util_1.assertName)(id, 'put', 'crudfs'); const [dir] = await this._dir(collection, true); const existing = dir.children.get(id); // Directory creation when data is undefined if (typeof data === 'undefined') { switch (options?.throwIf) { case 'exists': { if (existing) throw (0, util_2.newExistsError)(); dir.children.set(id, new MemoryCollection(dir, id)); dir.modified = Date.now(); return; } case 'missing': { if (!existing || !(existing instanceof MemoryCollection)) throw (0, util_2.newMissingError)(); return; // directory already exists } default: { if (!existing) { dir.children.set(id, new MemoryCollection(dir, id)); dir.modified = Date.now(); } else if (!(existing instanceof MemoryCollection)) { // cannot replace a file with a directory throw (0, util_2.newExistsError)(); } return; } } } let child = existing; switch (options?.throwIf) { case 'exists': { if (child) throw (0, util_2.newExistsError)(); child = new MemoryResource(dir, id); dir.children.set(id, child); dir.modified = Date.now(); break; } case 'missing': { if (!child) throw (0, util_2.newMissingError)(); break; } default: { if (!child) { child = new MemoryResource(dir, id); dir.children.set(id, child); dir.modified = Date.now(); } } } // Enforce write access when modifying an existing resource if (child && !child.writable) throw new DOMException('Resource is not writable', 'NotAllowedError'); let pos = options?.pos; if (typeof pos === 'number') { if (pos === -1) pos = child.data.length; const reqLen = Math.max(child.data.length, pos + data.length); const out = new Uint8Array(reqLen); out.set(child.data, 0); out.set(data, pos); child.data = out; } else if (typeof pos === 'undefined') { child.data = new Uint8Array(data); } else { throw new Error(`Invalid position: ${pos}`); } child.modified = Date.now(); }; this.getStream = async (path) => { const [collection, id] = (0, util_1.parseId)(path); const file = await this._get(collection, id); if (!file.readable) throw new DOMException('Resource is not readable', 'NotAllowedError'); return new ReadableStream({ start(controller) { controller.enqueue(new Uint8Array(file.data)); controller.close(); }, }); }; this.del = async (path, silent) => { const [collection, id] = (0, util_1.parseId)(path); (0, util_1.assertType)(collection, 'del', 'crudfs'); (0, util_1.assertName)(id, 'del', 'crudfs'); try { const [dir] = await this._file(collection, id); const deleted = dir.children.delete(id); if (deleted) dir.modified = Date.now(); } catch (error) { if (!silent) throw error; } }; this.info = async (path) => { const [collection, id] = (0, util_1.parseId)(path); const isRootPath = !collection.length && !id; if (!isRootPath) { (0, util_1.assertType)(collection, 'info', 'crudfs'); (0, util_1.assertName)(id, 'info', 'crudfs'); } const [dir] = await this._dir(collection, false); if (isRootPath) { return { type: 'collection', id: '', modified: dir.modified, created: dir.created, readable: true, writable: true, }; } const child = dir.children.get(id); if (!child) throw (0, util_2.newFile404Error)(collection, id); if (child instanceof MemoryResource) { const [, file] = await this._file(collection, id); return { type: 'resource', id, size: file.data.length, modified: file.modified, created: file.created, readable: file.readable, writable: file.writable, }; } else { const [dir] = await this._dir(collection, false); return { type: 'collection', id: '', modified: dir.modified, created: dir.created, readable: true, writable: true, }; } }; this.drop = async (path, silent) => { const collection = (0, util_1.parseParts)(path); (0, util_1.assertType)(collection, 'drop', 'crudfs'); try { const [dir, parent] = await this._dir(collection, false); if (parent) { parent.children.delete(dir.name); parent.modified = Date.now(); } else { const root = (await this.root); root.children.clear(); root.modified = Date.now(); } } catch (error) { if (!silent) throw error; } }; this.scan = async function* (path) { const collection = (0, util_1.parseParts)(path); (0, util_1.assertType)(collection, 'scan', 'crudfs'); const [dir] = await this._dir(collection, false); for (const [id, handle] of dir.children) { if (handle instanceof MemoryResource) { yield { type: 'resource', id }; } else if (handle instanceof MemoryCollection) { yield { type: 'collection', id }; } } }; this.list = async (path) => { const entries = []; for await (const entry of this.scan(path)) entries.push(entry); return entries; }; this.from = async (path) => { const collection = (0, util_1.parseParts)(path); (0, util_1.assertType)(collection, 'from', 'crudfs'); const [dir] = await this._dir(collection, true); return new MemoryCrud(dir); }; this.snapshot = () => { const root = this.root; const out = {}; const walk = (dir, prefix) => { for (const [name, child] of dir.children) { if (child instanceof MemoryResource) { const key = `${prefix}/${name}`; out[key] = new TextDecoder().decode(child.data); } else if (child instanceof MemoryCollection) { walk(child, `${prefix}/${name}`); } } }; walk(root, ''); return out; }; } async _dir(collection, create) { let parent = undefined; let dir = (await this.root); for (const name of collection) { const child = dir.children.get(name); if (child instanceof MemoryCollection) { parent = dir; dir = child; continue; } if (!child) { if (create) { const next = new MemoryCollection(dir, name); dir.children.set(name, next); dir.modified = Date.now(); parent = dir; dir = next; continue; } throw (0, util_2.newFolder404Error)(collection); } // child exists but is a resource throw (0, util_2.newFolder404Error)(collection); } return [dir, parent]; } async _file(collection, id) { const [dir] = await this._dir(collection, false); const child = dir.children.get(id); if (!child || !(child instanceof MemoryResource)) throw (0, util_2.newFile404Error)(collection, id); return [dir, child]; } async _get(collection, id) { (0, util_1.assertType)(collection, 'get', 'crudfs'); (0, util_1.assertName)(id, 'get', 'crudfs'); const [, file] = await this._file(collection, id); return file; } } exports.MemoryCrud = MemoryCrud;