UNPKG

flydrive

Version:

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

362 lines (356 loc) 10.2 kB
import { FSDriver } from "./chunk-KYJJJH3D.js"; import { DriveDirectory, DriveFile, E_CANNOT_COPY_FILE, E_CANNOT_DELETE_DIRECTORY, E_CANNOT_DELETE_FILE, E_CANNOT_MOVE_FILE, E_CANNOT_SET_VISIBILITY, E_CANNOT_WRITE_FILE, KeyNormalizer, errors_exports } from "./chunk-TZSKXVQT.js"; // src/disk.ts import { unlink } from "fs/promises"; import { createReadStream } from "fs"; var Disk = class { constructor(driver) { this.driver = driver; } /** * The normalizer is used to normalize and validate keys */ #normalizer = new KeyNormalizer(); /** * Creates a new instance of the DriveFile. It can be used * to lazily fetch file contents or convert it into a * snapshot for persistence */ file(key) { return new DriveFile(key, this.driver); } /** * Creates a new instance of the DriveFile from the snapshot. */ fromSnapshot(snapshot) { return new DriveFile(snapshot.key, this.driver, { contentLength: snapshot.contentLength, etag: snapshot.etag, lastModified: new Date(snapshot.lastModified), contentType: snapshot.contentType }); } /** * Check if the file exists. This method cannot check existence * of directories. */ exists(key) { return this.file(key).exists(); } /** * Returns file contents as a UTF-8 string. Use "getArrayBuffer" method * if you need more control over the file contents decoding. */ get(key) { return this.file(key).get(); } /** * Returns file contents as a Readable stream. */ getStream(key) { return this.file(key).getStream(); } /** * Returns file contents as a Uint8Array. */ getBytes(key) { return this.file(key).getBytes(); } /** * @deprecated * @see {@link Disk.getBytes} */ getArrayBuffer(key) { return this.file(key).getArrayBuffer(); } /** * Returns metadata of the given file. */ getMetaData(key) { return this.file(key).getMetaData(); } /** * Returns the visibility of the file */ getVisibility(key) { return this.file(key).getVisibility(); } /** * Returns the public URL of the file */ getUrl(key) { return this.file(key).getUrl(); } /** * Returns a signed/temporary URL of the file */ getSignedUrl(key, options) { return this.file(key).getSignedUrl(options); } /** * Returns a signed/temporary URL that can be used to directly upload * the file contents to the storage. */ getSignedUploadUrl(key, options) { return this.file(key).getSignedUploadUrl(options); } /** * Update the visibility of the file */ async setVisibility(key, visibility) { key = this.#normalizer.normalize(key); try { return await this.driver.setVisibility(key, visibility); } catch (error) { throw new E_CANNOT_SET_VISIBILITY([key], { cause: error }); } } /** * Create new file or update an existing file. In case of an error, * the "E_CANNOT_WRITE_FILE" exception is thrown */ async put(key, contents, options) { key = this.#normalizer.normalize(key); try { return await this.driver.put(key, contents, options); } catch (error) { throw new E_CANNOT_WRITE_FILE([key], { cause: error }); } } /** * Create new file or update an existing file using a Readable Stream * In case of an error, the "E_CANNOT_WRITE_FILE" exception is thrown */ async putStream(key, contents, options) { key = this.#normalizer.normalize(key); try { return await this.driver.putStream(key, contents, options); } catch (error) { throw new E_CANNOT_WRITE_FILE([key], { cause: error }); } } /** * Copies file from the "source" to the "destination" within the * same bucket or the root location of local filesystem. * * Use "copyFromFs" method to copy files from local filesystem to * a cloud provider */ async copy(source, destination, options) { source = this.#normalizer.normalize(source); destination = this.#normalizer.normalize(destination); try { return await this.driver.copy(source, destination, options); } catch (error) { throw new E_CANNOT_COPY_FILE([source, destination], { cause: error }); } } /** * Copies file from the local filesystem to the cloud provider. */ copyFromFs(source, destination, options) { return this.putStream(destination, createReadStream(source), options); } /** * Moves file from the "source" to the "destination" within the * same bucket or the root location of local filesystem. * * Use "moveFromFs" method to move files from local filesystem to * a cloud provider */ async move(source, destination, options) { source = this.#normalizer.normalize(source); destination = this.#normalizer.normalize(destination); try { return await this.driver.move(source, destination, options); } catch (error) { throw new E_CANNOT_MOVE_FILE([source, destination], { cause: error }); } } /** * Moves file from the local filesystem to the cloud provider. */ async moveFromFs(source, destination, options) { await this.putStream(destination, createReadStream(source), options); await unlink(source); } /** * Deletes a file for the given key. Use "deleteAll" method to delete * files for a matching folder prefix. */ async delete(key) { key = this.#normalizer.normalize(key); try { return await this.driver.delete(key); } catch (error) { throw new E_CANNOT_DELETE_FILE([key], { cause: error }); } } /** * Delete all files matching the given prefix. In case of "fs" driver, * the mentioned folder will be deleted. */ async deleteAll(prefix) { prefix = prefix && prefix !== "/" ? this.#normalizer.normalize(prefix) : "/"; try { return await this.driver.deleteAll(prefix); } catch (error) { throw new E_CANNOT_DELETE_DIRECTORY([prefix], { cause: error }); } } /** * Returns a list of objects which includes and files and directories. * In case of "recursive" listing, no directories are returned. */ listAll(prefix, options) { prefix = prefix && prefix !== "/" ? this.#normalizer.normalize(prefix) : "/"; return this.driver.listAll(prefix, options); } }; // src/drive_manager.ts import { RuntimeException } from "@poppinss/utils"; // src/debug.ts import { debuglog } from "util"; var debug_default = debuglog("flydrive:core"); // src/fake_disk.ts import { join } from "path"; import { AssertionError } from "assert"; var FakeDisk = class extends Disk { constructor(disk, fakesConfig) { super( new FSDriver({ location: typeof fakesConfig.location === "string" ? join(fakesConfig.location, disk) : new URL(disk, fakesConfig.location), visibility: "public", urlBuilder: fakesConfig.urlBuilder }) ); this.disk = disk; } /** * Assert the expected file(s) exists. Otherwise an assertion * error is thrown */ assertExists(paths) { const pathsToVerify = Array.isArray(paths) ? paths : [paths]; for (let filePath of pathsToVerify) { if (!this.driver.existsSync(filePath)) { throw new AssertionError({ message: `Expected "${filePath}" to exist, but file not found.` }); } } } /** * Assert the expected file(s) to not exist. Otherwise an assertion * error is thrown */ assertMissing(paths) { const pathsToVerify = Array.isArray(paths) ? paths : [paths]; for (let filePath of pathsToVerify) { if (this.driver.existsSync(filePath)) { throw new AssertionError({ message: `Expected "${filePath}" to be missing, but file exists` }); } } } /** * Clear storage */ clear() { this.driver.clearSync(); } }; // src/drive_manager.ts var DriveManager = class { /** * Registered config */ #config; /** * A collection of cached service. We re-use disk instances for a * service, since there isn't any need to reconstruct them * everytime. */ #cachedServices = /* @__PURE__ */ new Map(); /** * A collection of fakes created for the services. */ #fakes = /* @__PURE__ */ new Map(); constructor(config) { this.#config = config; debug_default("driver manager config %O", config); } /** * Returns an instance of a Disk for the given service. By default * use the "default" service from config */ use(service) { const serviceToUse = service || this.#config.default; const fake = this.#fakes.get(serviceToUse); if (fake) { debug_default("returning fake for service %s", serviceToUse); return fake; } const cachedDisk = this.#cachedServices.get(serviceToUse); if (cachedDisk) { debug_default("use cached disk instance for service %s", serviceToUse); return cachedDisk; } const disk = new Disk(this.#config.services[serviceToUse]()); debug_default("creating disk instance for service %s", serviceToUse); this.#cachedServices.set(serviceToUse, disk); return disk; } /** * Deploy fake for a given service. The "use" method for the same service * will now return an instance of the "FakeDisk" class and not the * real implementation. */ fake(service) { const serviceToUse = service || this.#config.default; if (!this.#config.fakes) { throw new RuntimeException( 'Cannot use "drive.fake". Make sure to define fakes configuration when creating DriveManager instance' ); } this.restore(serviceToUse); debug_default("creating fake for service %s", serviceToUse); const fake = new FakeDisk(serviceToUse, this.#config.fakes); this.#fakes.set(serviceToUse, fake); return fake; } /** * Restore fake for a given service */ restore(service) { const serviceToUse = service || this.#config.default; const fake = this.#fakes.get(serviceToUse); if (fake) { debug_default("restoring fake for service %s", serviceToUse); fake.clear(); this.#fakes.delete(serviceToUse); } } }; export { Disk, DriveDirectory, DriveFile, DriveManager, KeyNormalizer, errors_exports as errors };