@ezdevlol/memfs
Version:
In-memory file-system with Node's fs API.
222 lines (221 loc) • 7.63 kB
JavaScript
import { assertName } from '../node-to-fsa/util';
import { assertType } from '../crud/util';
import { newExistsError, newFile404Error, newFolder404Error, newMissingError } from '../fsa-to-crud/util';
export class NodeCrud {
options;
fs;
dir;
separator;
constructor(options) {
this.options = options;
this.separator = options.separator ?? '/';
let dir = options.dir;
const last = dir[dir.length - 1];
if (last !== this.separator)
dir = dir + this.separator;
this.dir = dir;
this.fs = options.fs;
}
async checkDir(collection) {
const dir = this.dir + (collection.length ? collection.join(this.separator) + this.separator : '');
const fs = this.fs;
try {
const stats = await fs.stat(dir);
if (!stats.isDirectory())
throw newFolder404Error(collection);
return dir;
}
catch (error) {
if (error && typeof error === 'object') {
switch (error.code) {
case 'ENOENT':
case 'ENOTDIR':
throw newFolder404Error(collection);
}
}
throw error;
}
}
put = async (collection, id, data, options) => {
assertType(collection, 'put', 'crudfs');
assertName(id, 'put', 'crudfs');
const dir = this.dir + (collection.length ? collection.join(this.separator) + this.separator : '');
const fs = this.fs;
if (dir.length > 1)
await fs.mkdir(dir, { recursive: true });
const filename = dir + id;
switch (options?.throwIf) {
case 'exists': {
try {
await fs.writeFile(filename, data, { flag: 64 /* FLAG.O_CREAT */ | 128 /* FLAG.O_EXCL */ });
}
catch (error) {
if (error && typeof error === 'object' && error.code === 'EEXIST')
throw newExistsError();
throw error;
}
break;
}
case 'missing': {
try {
await fs.writeFile(filename, data, { flag: 2 /* FLAG.O_RDWR */ });
}
catch (error) {
if (error && typeof error === 'object' && error.code === 'ENOENT')
throw newMissingError();
throw error;
}
break;
}
default: {
await fs.writeFile(filename, data);
}
}
};
get = async (collection, id) => {
assertType(collection, 'get', 'crudfs');
assertName(id, 'get', 'crudfs');
const dir = await this.checkDir(collection);
const filename = dir + id;
const fs = this.fs;
try {
const buf = (await fs.readFile(filename));
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
}
catch (error) {
if (error && typeof error === 'object') {
switch (error.code) {
case 'ENOENT':
throw newFile404Error(collection, id);
}
}
throw error;
}
};
del = async (collection, id, silent) => {
assertType(collection, 'del', 'crudfs');
assertName(id, 'del', 'crudfs');
try {
const dir = await this.checkDir(collection);
const filename = dir + id;
await this.fs.unlink(filename);
}
catch (error) {
if (!!silent)
return;
if (error && typeof error === 'object') {
switch (error.code) {
case 'ENOENT':
throw newFile404Error(collection, id);
}
}
throw error;
}
};
info = async (collection, id) => {
assertType(collection, 'info', 'crudfs');
if (id) {
assertName(id, 'info', 'crudfs');
await this.checkDir(collection);
try {
const stats = await this.fs.stat(this.dir + collection.join(this.separator) + this.separator + id);
if (!stats.isFile())
throw newFile404Error(collection, id);
return {
type: 'resource',
id,
size: stats.size,
modified: stats.mtimeMs,
};
}
catch (error) {
if (error && typeof error === 'object') {
switch (error.code) {
case 'ENOENT':
throw newFile404Error(collection, id);
}
}
throw error;
}
}
else {
await this.checkDir(collection);
try {
const stats = await this.fs.stat(this.dir + collection.join(this.separator));
if (!stats.isDirectory())
throw newFolder404Error(collection);
return {
type: 'collection',
id: '',
};
}
catch (error) {
if (error && typeof error === 'object') {
switch (error.code) {
case 'ENOENT':
case 'ENOTDIR':
throw newFolder404Error(collection);
}
}
throw error;
}
}
};
drop = async (collection, silent) => {
assertType(collection, 'drop', 'crudfs');
try {
const dir = await this.checkDir(collection);
const isRoot = dir === this.dir;
if (isRoot) {
const list = (await this.fs.readdir(dir));
for (const entry of list)
await this.fs.rmdir(dir + entry, { recursive: true });
}
else {
await this.fs.rmdir(dir, { recursive: true });
}
}
catch (error) {
if (!silent)
throw error;
}
};
scan = async function* (collection) {
assertType(collection, 'scan', 'crudfs');
const dir = await this.checkDir(collection);
const dirents = (await this.fs.readdir(dir, { withFileTypes: true }));
for await (const entry of dirents) {
if (entry.isFile()) {
yield {
type: 'resource',
id: '' + entry.name,
};
}
else if (entry.isDirectory()) {
yield {
type: 'collection',
id: '' + entry.name,
};
}
}
};
list = async (collection) => {
const entries = [];
for await (const entry of this.scan(collection))
entries.push(entry);
return entries;
};
from = async (collection) => {
assertType(collection, 'from', 'crudfs');
const dir = this.dir + (collection.length ? collection.join(this.separator) + this.separator : '');
const fs = this.fs;
if (dir.length > 1)
await fs.mkdir(dir, { recursive: true });
await this.checkDir(collection);
return new NodeCrud({
dir,
fs: this.fs,
separator: this.separator,
});
};
}