@jsvfs/adapter-node-fs
Version:
The official `jsvfs` adapter for Node's `fs` module.
227 lines • 8.58 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.NodeFSAdapter = void 0;
/**
* [[include:packages/adapter-node-fs/README.md]]
* @packageDocumentation
* @module @jsvfs/adapter-node-fs
*/
const extras_1 = require("@jsvfs/extras");
const fs_1 = require("fs");
const path_1 = require("path");
const { link, mkdir, readFile, readlink, rmdir, symlink, unlink, writeFile } = fs_1.promises;
/** An adapter for NodeJS local filesystems using the `fs` module. */
class NodeFSAdapter {
/** Creates an instance of Node file system adapter.
* @param {NodeFSAdapterOpts} [opts] - Options for this instance.
*/
constructor(opts = {}) {
this.root = typeof opts.cwd === 'string' ? path_1.resolve(opts.cwd) : process.cwd();
this.flushEnabled = typeof opts.flushEnabled === 'boolean' ? opts.flushEnabled : false;
this.handle = 'node-fs';
this.journal = new extras_1.Journal();
}
/** Snapshot of the underlying file system; an asynchronous iterable which returns an entry of path and data.
* @param {string} [path='/'] - The current path as the tree is descended.
* @param {boolean} [read=true] - Whether to retrieve the underlying data.
* @returns {AsyncGenerator<[string, SnapshotEntry]>} The asynchronous iterable to get the snapshot.
*/
async *snapshot(path = '/') {
let result = [];
try {
result = await fs_1.promises.readdir(path === '/' ? this.root : path_1.join(this.root, path), { withFileTypes: true });
}
catch (error) {
this.journal.push({
level: 'error',
message: `Could not read directory '${path_1.join(this.root, path)}'.`,
op: 'snapshot',
error
});
}
for (const entry of result) {
const newPath = path_1.posix.join(path, entry.name);
try {
switch (true) {
case entry.isDirectory():
yield [newPath, { type: 'folder' }];
for await (const [path, data] of this.snapshot(newPath)) {
yield [path, data];
}
break;
case entry.isFile():
yield [newPath, {
type: 'file',
contents: await readFile(path_1.join(this.root, newPath))
}];
break;
case entry.isSymbolicLink():
yield [newPath, {
type: 'softlink',
contents: path_1.relative(path_1.join(this.root, newPath), await readlink(path_1.join(this.root, newPath), 'utf8'))
.replace(this.root, '')
.replace(/\\+|\/+/gu, '/')
}];
break;
}
}
catch (error) {
this.journal.push({
level: 'error',
message: `Could not get contents of '${path_1.join(this.root, newPath)}'.`,
op: 'snapshot',
error
});
}
}
}
/** Read a file from persistent storage; assumes 'utf8' file encoding. */
async read(path) {
const newPath = path_1.join(this.root, path);
try {
return await readFile(newPath);
}
catch (error) {
this.journal.push({
level: 'error',
message: `Could not get contents of '${newPath}'.`,
op: 'read',
error
});
return Buffer.alloc(0);
}
}
/** Create a file or write the contents of a file to persistent storage. */
async write(path, contents) {
const newPath = path_1.join(this.root, path);
try {
const parent = path_1.dirname(newPath);
if (typeof contents === 'undefined')
contents = Buffer.alloc(0);
try {
await mkdir(parent, { recursive: true });
}
catch (error) {
this.journal.push({
level: 'warn',
message: `Could not create directory '${parent}'.`,
op: 'write',
error
});
}
await writeFile(newPath, contents);
}
catch (error) {
this.journal.push({
level: 'error',
message: `Could not get contents of '${newPath}'.`,
op: 'write',
error
});
}
}
/** Make a directory or directory tree in persistent storage. */
async mkdir(path) {
const newPath = path_1.join(this.root, path);
try {
await mkdir(newPath, { recursive: true });
}
catch (error) {
this.journal.push({
level: 'warn',
message: `Could not create directory '${newPath}'.`,
op: 'mkdir',
error
});
}
}
/** Create a link in persistent storage. */
async link(from, to, type) {
const newFrom = path_1.join(this.root, from);
const newTo = path_1.join(this.root, to);
try {
switch (type) {
case 'hardlink':
await link(newTo, newFrom);
break;
case 'softlink':
await symlink(newTo, newFrom);
break;
}
}
catch (error) {
this.journal.push({
level: 'error',
message: `Could not create link from '${newFrom}' to '${newTo}'.`,
op: 'link',
error
});
}
}
/** Remove items from persistent storage. */
async remove(path, type) {
const newPath = path_1.join(this.root, path);
try {
switch (type) {
case 'root':
// Ignore root; removal of root is probably unintentional.
break;
case 'folder':
await rmdir(newPath, { recursive: true });
break;
default:
await unlink(newPath);
break;
}
}
catch (error) {
this.journal.push({
level: 'error',
message: `Could not remove ${type} at path '${newPath}'.`,
op: 'remove',
error
});
}
}
/** Flush the underlying file system to prepare for a commit. This is a destructive operation unless flush is disabled. */
async flush() {
if (this.flushEnabled) {
let result = [];
try {
result = await fs_1.promises.readdir(this.root, { withFileTypes: true });
}
catch (error) {
this.journal.push({
level: 'error',
message: `Could not read directory '${this.root}'.`,
op: 'flush',
error
});
}
for (const entry of result) {
const path = path_1.join(this.root, entry.name);
try {
switch (true) {
case entry.isDirectory():
await rmdir(path, { recursive: true });
break;
case entry.isFile():
case entry.isSymbolicLink():
await unlink(path);
break;
}
}
catch (error) {
this.journal.push({
level: 'error',
message: `Could not remove item at path '${path}'.`,
op: 'remove',
error
});
}
}
}
}
}
exports.NodeFSAdapter = NodeFSAdapter;
//# sourceMappingURL=index.js.map