@loom-io/node-filesystem-adapter
Version:
A file system wrapper for Node.js and Bun
137 lines (136 loc) • 4.44 kB
JavaScript
import { FileHandler } from './file-handler.js';
import * as fs from 'node:fs/promises';
import { DirectoryNotEmptyException, PathNotFoundException } from '@loom-io/core';
import { dirname, join, normalize, resolve, sep } from 'node:path';
import { isNodeErrnoExpression } from '../utils/error-handling.js';
import { ObjectDirent } from './object-dirent.js';
export class Adapter {
rootdir;
constructor(rootdir = process.cwd()) {
const fullPath = resolve(rootdir.toString());
this.rootdir = fullPath.endsWith(sep) ? fullPath : normalize(`${fullPath}/`);
}
get raw() {
return fs;
}
getFullPath(path) {
if (path.match(/^[A-Za-z]{1}:/) && !['', '\\'].includes(this.rootdir)) {
return join(this.rootdir, path.slice(2));
}
return join(this.rootdir || '', path);
}
getRelativePath(path) {
return path.replace(this.rootdir, sep);
}
async exists(path, ref) {
const fullPath = this.getFullPath(path);
try {
await fs.access(fullPath, ref);
return true;
}
catch {
return false;
}
}
async fileExists(path) {
return this.exists(path, fs.constants.F_OK);
}
async dirExists(path) {
return this.exists(path, fs.constants.R_OK);
}
async mkdir(path) {
const fullPath = this.getFullPath(path);
await fs.mkdir(fullPath, { recursive: true });
}
async readdir(path) {
const fullPath = this.getFullPath(path.toString());
const paths = await fs.readdir(fullPath, { withFileTypes: true });
return paths.map((dirent) => new ObjectDirent(dirent, this.rootdir.toString()));
}
async rmdir(path, options = {}) {
try {
path = path.trim();
const fullPath = this.getFullPath(path);
if (options.recursive || options.force) {
await fs.rm(fullPath, options);
if (path === sep || path === '') {
await fs.mkdir(this.rootdir);
}
}
else {
if (path !== sep && path !== '') {
await fs.rmdir(fullPath);
}
}
}
catch (err) {
if (isNodeErrnoExpression(err)) {
switch (err.code) {
case 'ENOTEMPTY':
throw new DirectoryNotEmptyException(path);
case 'ENOENT':
throw new PathNotFoundException(path);
}
}
throw err;
}
}
async stat(path) {
const fullPath = this.getFullPath(path);
const { size, mtime, birthtime, ctime, atime } = await fs.stat(fullPath);
return {
size,
atime,
mtime,
ctime,
birthtime,
};
}
async readFile(path, encoding) {
const fullPath = this.getFullPath(path);
return await fs.readFile(fullPath, encoding);
}
async writeFile(path, data) {
const fullPath = this.getFullPath(path);
try {
await fs.writeFile(fullPath, data);
}
catch (err) {
if (isNodeErrnoExpression(err)) {
switch (err.code) {
case 'ENOENT':
throw new PathNotFoundException(fullPath);
}
}
throw err;
}
}
async deleteFile(path) {
const fullPath = this.getFullPath(path.toString());
await fs.rm(fullPath);
}
async openFile(path, mode = 'r') {
const fullPath = this.getFullPath(path);
const fileHandle = await fs.open(fullPath, mode);
return new FileHandler(fileHandle);
}
async isCopyable(adapter) {
return adapter instanceof Adapter;
}
async copyFile(from, to) {
try {
const fromPath = this.getFullPath(from);
const toPath = this.getFullPath(to);
await fs.copyFile(fromPath, toPath);
}
catch (err) {
if (isNodeErrnoExpression(err)) {
if (err.code === 'ENOENT') {
const srcExists = await this.fileExists(from);
throw new PathNotFoundException(srcExists ? dirname(to) : from);
}
}
throw err;
}
}
}