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