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
JavaScript
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 };