UNPKG

@ezdevlol/memfs

Version:

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

164 lines (163 loc) 5.34 kB
import { assertName } from '../node-to-fsa/util'; import { assertType } from '../crud/util'; import { newExistsError, newFile404Error, newFolder404Error, newMissingError } from './util'; export class FsaCrud { root; constructor(root) { this.root = root; } async getDir(collection, create) { let parent = undefined; let dir = await this.root; try { for (const name of collection) { const child = await dir.getDirectoryHandle(name, { create }); parent = dir; dir = child; } return [dir, parent]; } catch (error) { if (error.name === 'NotFoundError') throw newFolder404Error(collection); throw error; } } async getFile(collection, id) { const [dir] = await this.getDir(collection, false); try { const file = await dir.getFileHandle(id, { create: false }); return [dir, file]; } catch (error) { if (error.name === 'NotFoundError') throw newFile404Error(collection, id); throw error; } } put = async (collection, id, data, options) => { assertType(collection, 'put', 'crudfs'); assertName(id, 'put', 'crudfs'); const [dir] = await this.getDir(collection, true); let file; switch (options?.throwIf) { case 'exists': { try { file = await dir.getFileHandle(id, { create: false }); throw newExistsError(); } catch (e) { if (e.name !== 'NotFoundError') throw e; file = await dir.getFileHandle(id, { create: true }); } break; } case 'missing': { try { file = await dir.getFileHandle(id, { create: false }); } catch (e) { if (e.name === 'NotFoundError') throw newMissingError(); throw e; } break; } default: { file = await dir.getFileHandle(id, { create: true }); } } const writable = await file.createWritable(); await writable.write(data); await writable.close(); }; get = async (collection, id) => { assertType(collection, 'get', 'crudfs'); assertName(id, 'get', 'crudfs'); const [, file] = await this.getFile(collection, id); const blob = await file.getFile(); const buffer = await blob.arrayBuffer(); return new Uint8Array(buffer); }; del = async (collection, id, silent) => { assertType(collection, 'del', 'crudfs'); assertName(id, 'del', 'crudfs'); try { const [dir] = await this.getFile(collection, id); await dir.removeEntry(id, { recursive: false }); } catch (error) { if (!silent) throw error; } }; info = async (collection, id) => { assertType(collection, 'info', 'crudfs'); if (id) { assertName(id, 'info', 'crudfs'); const [, file] = await this.getFile(collection, id); const blob = await file.getFile(); return { type: 'resource', id, size: blob.size, modified: blob.lastModified, }; } else { await this.getDir(collection, false); return { type: 'collection', id: '', }; } }; drop = async (collection, silent) => { assertType(collection, 'drop', 'crudfs'); try { const [dir, parent] = await this.getDir(collection, false); if (parent) { await parent.removeEntry(dir.name, { recursive: true }); } else { const root = await this.root; for await (const name of root.keys()) await root.removeEntry(name, { recursive: true }); } } catch (error) { if (!silent) throw error; } }; scan = async function* (collection) { assertType(collection, 'scan', 'crudfs'); const [dir] = await this.getDir(collection, false); for await (const [id, handle] of dir.entries()) { if (handle.kind === 'file') { yield { type: 'resource', id, }; } else if (handle.kind === 'directory') { yield { type: 'collection', id, }; } } }; list = async (collection) => { const entries = []; for await (const entry of this.scan(collection)) entries.push(entry); return entries; }; from = async (collection) => { assertType(collection, 'from', 'crudfs'); const [dir] = await this.getDir(collection, true); return new FsaCrud(dir); }; }