UNPKG

@loom-io/node-filesystem-adapter

Version:

A file system wrapper for Node.js and Bun

137 lines (136 loc) 4.44 kB
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; } } }