UNPKG

flydrive

Version:

File storage library with unified API to manage files across multiple cloud storage providers like S3, GCS, R2 and so on

195 lines (194 loc) 7.26 kB
import { n as DriveFile, t as DriveDirectory } from "./drive_directory-Av1MhRnI.js"; import * as fsp from "node:fs/promises"; import { createReadStream, existsSync, rmSync } from "node:fs"; import { RuntimeException } from "@poppinss/utils/exception"; import { dirname, join, relative } from "node:path"; import string from "@poppinss/utils/string"; import { debuglog } from "node:util"; import etag from "etag"; import mimeTypes from "mime-types"; import { fileURLToPath } from "node:url"; import { Retrier } from "@humanwhocodes/retry"; var debug_default = debuglog("flydrive:fs"); const RETRY_ERROR_CODES = new Set(["ENFILE", "EMFILE"]); var FSDriver = class { #rootUrl; #retrier = new Retrier((error) => error.code && RETRY_ERROR_CODES.has(error.code)); constructor(options) { this.options = options; this.#rootUrl = typeof options.location === "string" ? options.location : fileURLToPath(options.location); debug_default("driver config %O", options); } #read(key) { const location = join(this.#rootUrl, key); return this.#retrier.retry(() => fsp.readFile(location)); } async #readDir(location, recursive) { try { return await fsp.readdir(location, { recursive, withFileTypes: true }); } catch (error) { if (error.code !== "ENOENT") throw error; return []; } } #write(key, contents, options) { const location = join(this.#rootUrl, key); return this.#retrier.retry(async () => { await fsp.mkdir(dirname(location), { recursive: true }); await fsp.writeFile(location, contents, options); }); } existsSync(key) { debug_default("checking if file exists %s:%s", this.#rootUrl, key); return existsSync(join(this.#rootUrl, key)); } async exists(key) { debug_default("checking if file exists %s:%s", this.#rootUrl, key); const location = join(this.#rootUrl, key); try { return (await fsp.stat(location)).isFile(); } catch (error) { if (error.code === "ENOENT") return false; throw error; } } async get(key) { debug_default("reading file contents %s:%s", this.#rootUrl, key); return this.#read(key).then((value) => value.toString("utf-8")); } async getStream(key) { debug_default("reading file contents as a stream %s:%s", this.#rootUrl, key); return createReadStream(join(this.#rootUrl, key)); } async getBytes(key) { debug_default("reading file contents as array buffer %s:%s", this.#rootUrl, key); return this.#read(key).then((value) => new Uint8Array(value.buffer)); } async getMetaData(key) { debug_default("fetching file metadata %s:%s", this.#rootUrl, key); const location = join(this.#rootUrl, key); const stats = await fsp.stat(location); if (stats.isDirectory()) throw new RuntimeException(`Cannot get metadata of a directory "${key}"`); return { contentLength: stats.size, contentType: mimeTypes.lookup(key) || void 0, etag: etag(stats), lastModified: stats.mtime }; } async getVisibility(_) { return this.options.visibility; } async getUrl(key) { const location = join(this.#rootUrl, key); const generateURL = this.options.urlBuilder?.generateURL; if (generateURL) { debug_default("generating public URL %s:%s", this.#rootUrl, key); return generateURL(key, location); } throw new RuntimeException("Cannot generate URL. The \"fs\" driver does not support it"); } async getSignedUrl(key, options) { const location = join(this.#rootUrl, key); const normalizedOptions = Object.assign({ expiresIn: "30 mins" }, options); const generateSignedURL = this.options.urlBuilder?.generateSignedURL; if (generateSignedURL) { debug_default("generating signed URL %s:%s", this.#rootUrl, key); return generateSignedURL(key, location, normalizedOptions); } throw new RuntimeException("Cannot generate signed URL. The \"fs\" driver does not support it"); } async getSignedUploadUrl(key, options) { const location = join(this.#rootUrl, key); const normalizedOptions = Object.assign({ expiresIn: "30 mins" }, options); const generateSignedUploadURL = this.options.urlBuilder?.generateSignedUploadURL; if (generateSignedUploadURL) { debug_default("generating signed upload URL %s:%s", this.#rootUrl, key); return generateSignedUploadURL(key, location, normalizedOptions); } throw new RuntimeException("Cannot generate signed upload URL. The \"fs\" driver does not support it"); } async setVisibility(_, __) {} put(key, contents, options) { debug_default("creating/updating file %s:%s", this.#rootUrl, key); return this.#write(key, contents, { signal: options?.signal }); } putStream(key, contents, options) { debug_default("creating/updating file using readable stream %s:%s", this.#rootUrl, key); return new Promise((resolve, reject) => { contents.once("error", (error) => reject(error)); return this.#write(key, contents, { signal: options?.signal }).then(resolve).catch(reject); }); } copy(source, destination) { debug_default("copying file from %s to %s", source, destination); const sourceLocation = join(this.#rootUrl, source); const destinationLocation = join(this.#rootUrl, destination); return this.#retrier.retry(async () => { await fsp.mkdir(dirname(destinationLocation), { recursive: true }); await fsp.copyFile(sourceLocation, destinationLocation); }); } move(source, destination) { debug_default("moving file from %s to %s", source, destination); const sourceLocation = join(this.#rootUrl, source); const destinationLocation = join(this.#rootUrl, destination); return this.#retrier.retry(async () => { await fsp.mkdir(dirname(destinationLocation), { recursive: true }); await fsp.copyFile(sourceLocation, destinationLocation); await fsp.unlink(sourceLocation); }); } delete(key) { debug_default("deleting file %s:%s", this.#rootUrl, key); const location = join(this.#rootUrl, key); return this.#retrier.retry(async () => { try { await fsp.unlink(location); } catch (error) { if (error.code !== "ENOENT") throw error; } }); } deleteAll(prefix) { debug_default("deleting all files in folder %s:%s", this.#rootUrl, prefix); const location = join(this.#rootUrl, prefix); return this.#retrier.retry(async () => { return fsp.rm(location, { recursive: true, force: true }); }); } clearSync() { rmSync(this.#rootUrl, { recursive: true, force: true }); } async listAll(prefix, options) { const self = this; const location = join(this.#rootUrl, prefix); const { recursive } = Object.assign({ recursive: false }, options); debug_default("listing files from folder %s:%s %O", this.#rootUrl, prefix, options); const files = await this.#readDir(location, recursive); function* filesGenerator() { for (const file of files) { const relativeName = string.toUnixSlash(relative(self.#rootUrl, join(file.parentPath ?? ("path" in file ? file.path : ""), file.name))); if (file.isFile()) yield new DriveFile(relativeName, self); else if (!recursive) yield new DriveDirectory(relativeName); } } return { paginationToken: void 0, objects: { [Symbol.iterator]: filesGenerator } }; } bucket(_bucket) { throw new RuntimeException("Cannot switch bucket. The \"fs\" driver does not support it."); } }; export { FSDriver as t };