fs-zoo
Version:
File system abstractions and implementations
265 lines (264 loc) • 9.36 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.FsaCrud = void 0;
const util_1 = require("../crud/util");
const util_2 = require("./util");
class FsaCrud {
constructor(root) {
this.root = root;
}
async _dir(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 && typeof error === 'object' && error.name === 'NotFoundError')
throw (0, util_2.newFolder404Error)(collection);
throw error;
}
}
async _file(collection, id) {
const [dir] = await this._dir(collection, false);
try {
const file = await dir.getFileHandle(id, { create: false });
return [dir, file];
}
catch (error) {
if (error && typeof error === 'object' && error.name === 'NotFoundError')
throw (0, util_2.newFile404Error)(collection, id);
throw error;
}
}
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 await file.getFile();
}
async write(path, options) {
const [collection, id] = (0, util_1.parseId)(path);
(0, util_1.assertType)(collection, 'write', 'crudfs');
(0, util_1.assertName)(id, 'write', 'crudfs');
const [dir] = await this._dir(collection, true);
let file;
switch (options?.throwIf) {
case 'exists': {
try {
file = await dir.getFileHandle(id, { create: false });
throw (0, util_2.newExistsError)();
}
catch (e) {
if (e && typeof e === 'object' && 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 && typeof e === 'object' && e.name === 'NotFoundError')
throw (0, util_2.newMissingError)();
throw e;
}
break;
}
default: {
file = await dir.getFileHandle(id, { create: true });
}
}
let pos = options?.pos;
const writable = await file.createWritable({ keepExistingData: pos !== void 0 });
if (pos === -1) {
const blob = await file.getFile();
pos = blob.size;
}
else
pos ?? (pos = 0);
return new WritableStream({
write: async (chunk) => {
await writable.write({ type: 'write', data: chunk, position: pos });
pos += chunk.length;
},
close: async () => {
await writable.close();
},
});
}
async dir(path, options) {
const [collection, id] = (0, util_1.parseId)(path);
(0, util_1.assertType)(collection, 'dir', 'crudfs');
(0, util_1.assertName)(id, 'dir', 'crudfs');
let info;
try {
info = await this.info(path);
}
catch (e) {
if (e && typeof e === 'object') {
const name = e.name;
if (name !== 'ResourceNotFound' && name !== 'CollectionNotFound')
throw e;
}
}
if (info && info.type !== 'collection')
throw (0, util_2.newExistsError)();
const throwIf = options?.throwIf;
if (throwIf === 'exists') {
if (info)
throw (0, util_2.newExistsError)();
return;
}
if (throwIf === 'missing') {
if (!info)
throw (0, util_2.newMissingError)();
}
const [dir] = await this._dir(collection, true);
await dir.getDirectoryHandle(id, { create: true });
}
async put(path, data, options) {
const writable = await this.write(path, options);
const writer = writable.getWriter();
await writer.write(data);
await writer.close();
}
async read(path) {
const [collection, id] = (0, util_1.parseId)(path);
const file = await this._get(collection, id);
return file.stream();
}
async file(path) {
const [collection, id] = (0, util_1.parseId)(path);
(0, util_1.assertType)(collection, 'get', 'crudfs');
(0, util_1.assertName)(id, 'get', 'crudfs');
return await this._get(collection, id);
}
async del(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);
await dir.removeEntry(id, { recursive: false });
}
catch (error) {
if (!silent)
throw error;
}
}
async info(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 permissions = async (item) => {
const [read, write] = await Promise.all([
item.queryPermission?.({ mode: 'read' }),
item.queryPermission?.({ mode: 'readwrite' }),
]);
return {
readable: read?.state === 'granted',
writable: write?.state === 'granted',
};
};
try {
const [dir] = await this._dir(collection, false);
if (isRootPath) {
return {
type: 'collection',
id: '',
...(await permissions(dir)),
};
}
const file = await dir.getFileHandle(id);
const [blob, perms] = await Promise.all([file.getFile(), permissions(file)]);
return {
type: 'resource',
id,
size: blob.size,
modified: blob.lastModified,
...perms,
};
}
catch (error) {
if (error && typeof error === 'object' && error.name === 'NotFoundError')
throw (0, util_2.newFile404Error)(collection, id);
if (error && typeof error === 'object' && error.name !== 'TypeMismatchError')
throw error;
try {
const [cdir] = await this._dir([...collection, id], false);
return {
type: 'collection',
id: '',
...(await permissions(cdir)),
};
}
catch (e) {
if (error && typeof error === 'object' && error.name === 'NotFoundError')
throw (0, util_2.newFile404Error)(collection, id);
throw e;
}
}
}
async drop(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) {
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;
}
}
async *scan(path) {
const collection = (0, util_1.parseParts)(path);
(0, util_1.assertType)(collection, 'scan', 'crudfs');
const [dir] = await this._dir(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,
};
}
}
}
async list(path) {
const entries = [];
for await (const entry of this.scan(path))
entries.push(entry);
return entries;
}
async from(path) {
const collection = (0, util_1.parseParts)(path);
(0, util_1.assertType)(collection, 'from', 'crudfs');
const [dir] = await this._dir(collection, true);
return new FsaCrud(dir);
}
}
exports.FsaCrud = FsaCrud;
;