fs-zoo
Version:
File system abstractions and implementations
277 lines (276 loc) • 10.6 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;
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);
if (typeof data === 'undefined') {
const throwIf = options?.throwIf;
// Determine entry type
let entry = 'none';
try {
await dir.getFileHandle(id, { create: false });
entry = 'file';
}
catch (e) {
if (e.name === 'NotFoundError') {
// no file, maybe directory
try {
await dir.getDirectoryHandle(id, { create: false });
entry = 'dir';
}
catch (e2) {
if (e2.name === 'NotFoundError')
entry = 'none';
else
throw e2;
}
}
else if (e.name === 'TypeMismatchError') {
// a directory exists at this path
entry = 'dir';
}
else {
throw e;
}
}
switch (throwIf) {
case 'exists': {
if (entry !== 'none')
throw (0, util_2.newExistsError)();
await dir.getDirectoryHandle(id, { create: true });
return;
}
case 'missing': {
if (entry !== 'dir')
throw (0, util_2.newMissingError)();
return; // directory exists, nothing to do
}
default: {
if (entry === 'none')
await dir.getDirectoryHandle(id, { create: true });
else if (entry === 'file')
throw (0, util_2.newExistsError)();
return;
}
}
}
let file;
switch (options?.throwIf) {
case 'exists': {
try {
file = await dir.getFileHandle(id, { create: false });
throw (0, util_2.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 (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: typeof pos !== 'undefined' });
switch (typeof pos) {
case 'undefined': {
await writable.write(data);
break;
}
case 'number': {
if (pos === -1) {
const blob = await file.getFile();
pos = blob.size;
}
await writable.write({ type: 'write', data, position: pos });
break;
}
default: {
throw new Error(`Invalid position: ${pos}`);
}
}
await writable.close();
};
this.getStream = async (path) => {
const [collection, id] = (0, util_1.parseId)(path);
const file = await this._get(collection, id);
return file.stream();
};
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);
await dir.removeEntry(id, { recursive: false });
}
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 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.name === 'NotFoundError')
throw (0, util_2.newFile404Error)(collection, id);
if (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.name === 'NotFoundError')
throw (0, util_2.newFile404Error)(collection, id);
throw e;
}
}
};
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) {
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;
}
};
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 await (const [id, handle] of dir.entries()) {
if (handle.kind === 'file') {
yield {
type: 'resource',
id,
};
}
else if (handle.kind === 'directory') {
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 FsaCrud(dir);
};
}
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.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.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();
}
}
exports.FsaCrud = FsaCrud;