UNPKG

fs-zoo

Version:

File system abstractions and implementations

170 lines (169 loc) 6.09 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.OverlayCrud = void 0; const util_1 = require("../crud/util"); class LayerMatch { constructor(fs, rest) { this.fs = fs; this.rest = rest; } } /** * An "overlay" file system, which can combine multiple CRUD-fs file systems * into one. Accepts one default file system and any number of additional * file systems to overlay on top at specific paths. * * A layer mounted at `''` (or `[]`) will be the default file system, it will * receive all requests that do not match any other layer. * * Example: * * ``` * const fs = new OverlayCrud(); * fs.overlay([], root); * fs.overlay(['usr', 'virtual'], userHome); * ``` */ class OverlayCrud { constructor(layers = {}, prefix = []) { this.prefix = prefix; this.put = async (path, data, options) => { const { fs, rest } = this._match(path); return await fs.put(rest, data, options); }; this.getStream = async (path) => { const { fs, rest } = this._match(path); return await fs.getStream(rest); }; this.del = async (path, silent) => { const { fs, rest } = this._match(path); return await fs.del(rest, silent); }; this.info = async (path) => { const { fs, rest } = this._match(path); return await fs.info(rest); }; this.drop = async (path, silent) => { const { fs, rest } = this._match(path); const baseParts = [...this.prefix, ...path.split('/')]; const nested = []; for (const [key, ofs] of Object.entries(this._layers)) { const keyParts = key ? key.split('/') : []; if (keyParts.length < baseParts.length) continue; let match = true; for (let i = 0; i < baseParts.length; i++) if (keyParts[i] !== baseParts[i]) { match = false; break; } if (!match) continue; nested.push(ofs); } try { await fs.drop(rest, silent); for (const ofs of nested) await ofs.drop('', silent); } catch (error) { if (!silent) throw error; } }; this.scan = async function* (path) { const { fs, rest } = this._match(path); const baseParts = [...this.prefix, ...(0, util_1.parseParts)(path)]; const baseLen = baseParts.length; const childNames = new Set(); for (const key of Object.keys(this._layers)) { const keyParts = key ? key.split('/') : []; if (keyParts.length <= baseLen) continue; let match = true; for (let i = 0; i < baseLen; i++) if (keyParts[i] !== baseParts[i]) { match = false; break; } if (!match) continue; const child = keyParts[baseLen]; if (!childNames.has(child)) { childNames.add(child); } } let baseError; try { for await (const entry of fs.scan(rest)) yield entry; } catch (e) { baseError = e; if (!(e && typeof e === 'object' && e.name === 'CollectionNotFound')) throw e; // If base is missing but overlays provide children, we'll synthesize entries below if (childNames.size === 0) throw e; } for (const child of childNames) yield { type: 'collection', id: child }; }; this.list = async (path) => { const entries = []; for await (const entry of this.scan(path)) entries.push(entry); return entries; }; this.from = async (path) => { return new OverlayCrud(this._layers, [...this.prefix, ...path.split('/')]); }; this._layers = {}; for (const [key, fs] of Object.entries(layers)) this.overlay(key, fs); } overlay(path, fs) { const collection = (0, util_1.parseParts)(path); const isRoot = collection.length === 0 || (collection.length === 1 && collection[0] === ''); if (!isRoot) (0, util_1.assertType)(collection, 'overlay', 'crudfs'); const key = isRoot ? '' : collection.join('/'); this._layers[key] = fs; } _match(path) { const collection = (0, util_1.parseParts)(path); let bestFs; let bestRest; const parts = [...this.prefix, ...collection]; for (const [key, fs] of Object.entries(this._layers)) { if (!key) { if (!bestFs) { bestFs = fs; bestRest = parts; } continue; } const keyParts = key ? key.split('/') : []; if (keyParts.length === 0) continue; if (keyParts.length > parts.length) continue; let match = true; for (let i = 0; i < keyParts.length; i++) if (keyParts[i] !== parts[i]) { match = false; break; } if (!match) continue; if (!bestFs || parts.length - keyParts.length < bestRest.length) { bestFs = fs; bestRest = parts.slice(keyParts.length); } } if (!bestFs) throw new Error('NO_OVERLAY'); return new LayerMatch(bestFs, bestRest.join('/')); } } exports.OverlayCrud = OverlayCrud;