fs-zoo
Version:
File system abstractions and implementations
170 lines (169 loc) • 6.09 kB
JavaScript
"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;