UNPKG

staticql

Version:

Type-safe query engine for static content including Markdown, YAML, JSON, and more.

120 lines (119 loc) 3.8 kB
import { promises as fs } from "fs"; import { createReadStream } from "node:fs"; import * as path from "path"; import { Readable } from "node:stream"; import { joinPath } from "../utils/path.js"; /** * FsRepository: StorageRepository implementation for the local file system. */ export class FsRepository { constructor(baseDir = ".") { this.baseDir = baseDir; } /** * Checks whether a file or directory exists. * * @param filePath - Relative path from baseDir. * @returns `true` if the file exists, otherwise `false`. */ async exists(filePath) { const abs = path.resolve(this.baseDir, filePath); try { await fs.access(abs); return true; } catch { return false; } } /** * Reads the content of a file as UTF-8 text. * * @param filePath - Relative path to the file. * @returns File content as a string. */ async readFile(filePath) { const abs = path.resolve(this.baseDir, filePath); return await fs.readFile(abs, "utf-8"); } /* * Opens a file as a ReadableStream. * * @param path - Relative path to the file (from the repository base directory) * @returns Promise that resolves to a ReadableStream for the file contents */ async openFileStream(path) { const fullPath = joinPath(this.baseDir, path); const stream = createReadStream(fullPath); return Readable.toWeb(stream); } /** * Writes data to a file (string or binary). Creates parent directories if needed. * * @param filePath - Relative file path. * @param content - File content as string or Uint8Array. */ async writeFile(filePath, content) { const abs = path.resolve(this.baseDir, filePath); await fs.mkdir(path.dirname(abs), { recursive: true }); await fs.writeFile(abs, content); } /** * Lists all files under a given path or glob pattern. * * If the path points to a file, it returns an array with a single entry. * Otherwise, recursively walks the directory and returns all file paths. * * @param pathString - Path or glob pattern (e.g. "data/*.json"). * @returns List of relative file paths. */ async listFiles(pathString) { const abs = path.resolve(this.baseDir, pathString.replace(/\*.*$/, "")); const result = []; if ((await fs.stat(abs)).isFile()) { return [abs]; } for await (const file of this.walk(abs)) { result.push(file); } return result; } /** * Recursively walks through a directory and yields all file paths. * * @param pathString - Absolute path to start from. * @yields Relative file paths from baseDir. */ async *walk(pathString) { const dirHandle = await fs.opendir(pathString); for await (const entry of dirHandle) { const full = path.join(pathString, entry.name); if (entry.isDirectory()) { yield* this.walk(full); } else { yield path.relative(this.baseDir, full); } } } /** * Deletes the specified file. * * @param filePath - Relative path to the file. */ async removeFile(filePath) { const abs = path.resolve(this.baseDir, filePath); await fs.unlink(abs); } /** * Deletes the directory recursive. * * @param filePath - Relative path to the file. */ async removeDir(filePath) { const abs = path.resolve(this.baseDir, filePath); if (!(await this.exists(abs))) return; await fs.rm(abs, { recursive: true }); } }