@arkstack/filesystem 0.5.2 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,71 @@
1
1
  # @arkstack/filesystem
2
2
 
3
+ [![@arkstack/filesystem](https://img.shields.io/npm/dt/@arkstack/filesystem?style=flat-square&label=@arkstack/filesystem&link=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2F@arkstack/filesystem)](https://www.npmjs.com/package/@arkstack/filesystem)
4
+
3
5
  Filesystem module for Arkstack, providing shared file storage and filesystem utitlities for the framework.
6
+
7
+ ## Custom Drivers
8
+
9
+ @arkstack/filesystem allows you to configure and use custom storage drivers.
10
+
11
+ **CloudinaryFileDriver.ts**
12
+
13
+ ```ts
14
+ import type { DriverContract, ObjectVisibility } from 'flydrive/types';
15
+ import type { CustomDiskConfig } from '@arkstack/filesystem';
16
+
17
+ export class CloudinaryFileDriver implements DriverContract {
18
+ constructor(private config?: CustomDiskConfig) {}
19
+ async exists(key: string) {}
20
+ async get(key: string) {}
21
+ async getStream(key: string) {}
22
+ async getBytes(key: string) {}
23
+ async getMetaData(key: string) {}
24
+ async getVisibility(): Promise<ObjectVisibility> {}
25
+ async getUrl(key: string) {}
26
+ async getSignedUrl(key: string) {}
27
+ async getSignedUploadUrl(key: string) {}
28
+ async setVisibility() {}
29
+ async put() {}
30
+ async putStream() {}
31
+ async copy() {}
32
+ async move() {}
33
+ async delete() {}
34
+ async deleteAll() {}
35
+ async listAll() {}
36
+ async bucket() {}
37
+ }
38
+ ```
39
+
40
+ **src/config/filesystem.ts**
41
+
42
+ ```ts
43
+ import { CloudinaryFileDriver } from '../CloudinaryFileDriver';
44
+ export default () => {
45
+ return {
46
+ default: 'images',
47
+ disks: {
48
+ images: {
49
+ //...Other Disks Here
50
+ driver: 'cloudinary',
51
+ },
52
+ },
53
+ links: {},
54
+ custom_drivers: {
55
+ cloudinary: CloudinaryFileDriver,
56
+ },
57
+ };
58
+ };
59
+ ```
60
+
61
+ To improve type safety and auto complete, you may augment the `CustomDiskDriverRegistry`
62
+
63
+ **env.d.ts**
64
+
65
+ ```ts
66
+ declare module '@arkstack/filesystem' {
67
+ interface CustomDiskDriverRegistry {
68
+ cloudinary: { cloud_name: string; api_key: string; api_secret: string };
69
+ }
70
+ }
71
+ ```
@@ -7,5 +7,4 @@ declare class StorageLinkCommand extends Command {
7
7
  handle(): Promise<void>;
8
8
  }
9
9
  //#endregion
10
- export { StorageLinkCommand };
11
- //# sourceMappingURL=StorageLinkCommand.d.ts.map
10
+ export { StorageLinkCommand };
@@ -1,4 +1,4 @@
1
- import { t as Storage } from "../src-wGEPrkk5.js";
1
+ import { t as Storage } from "../src-DNh_3BpA.js";
2
2
  import { Command } from "@h3ravel/musket";
3
3
  //#region src/commands/StorageLinkCommand.ts
4
4
  var StorageLinkCommand = class extends Command {
@@ -12,5 +12,3 @@ var StorageLinkCommand = class extends Command {
12
12
  };
13
13
  //#endregion
14
14
  export { StorageLinkCommand };
15
-
16
- //# sourceMappingURL=StorageLinkCommand.js.map
package/dist/index.d.ts CHANGED
@@ -1,41 +1,291 @@
1
1
  import { DriveDirectory, DriveFile, DriveManager } from "flydrive";
2
2
  import { Readable } from "node:stream";
3
3
  import { DriverContract, ObjectMetaData, ObjectVisibility, SignedURLOptions, WriteOptions } from "flydrive/types";
4
+ import { GCSDriverOptions } from "flydrive/drivers/gcs/types";
4
5
 
5
- //#region src/index.d.ts
6
+ //#region src/types.d.ts
6
7
  interface FileLike {
7
8
  originalname: string;
8
9
  buffer: Buffer;
9
10
  mimetype: string;
10
11
  }
11
- declare class Storage implements DriverContract {
12
+ interface CustomDiskDriverRegistry {}
13
+ type GcsDiskDriverConfig = GCSDriverOptions;
14
+ interface FtpDriverConfig {
15
+ host: string;
16
+ username: string;
17
+ password: string;
18
+ port?: number;
19
+ verbose?: boolean | undefined;
20
+ privateKey?: string | undefined;
21
+ }
22
+ interface S3DriverConfig {
23
+ credentials?: {
24
+ accessKeyId: string;
25
+ secretAccessKey: string;
26
+ sessionToken?: string | undefined;
27
+ credentialScope?: string | undefined;
28
+ accountId?: string | undefined;
29
+ };
30
+ url?: string;
31
+ key?: string;
32
+ secret?: string;
33
+ endpoint?: string;
34
+ region?: string;
35
+ bucket: string;
36
+ visibility: ObjectVisibility;
37
+ cdnUrl?: string;
38
+ }
39
+ interface LocalDriverConfig {
40
+ root?: string;
41
+ location?: string | URL;
42
+ visibility: ObjectVisibility;
43
+ url?: string;
44
+ }
45
+ type CustomDiskConfig = keyof CustomDiskDriverRegistry extends never ? {
46
+ driver: string;
47
+ [key: string]: any;
48
+ } : { [K in keyof CustomDiskDriverRegistry]: CustomDiskDriverRegistry[K] & {
49
+ driver: K;
50
+ } }[keyof CustomDiskDriverRegistry];
51
+ type DiskConfig = LocalDriverConfig & {
52
+ driver: 'local' | 'public';
53
+ } | FtpDriverConfig & {
54
+ driver: 'ftp';
55
+ } | S3DriverConfig & {
56
+ driver: 's3';
57
+ } | CustomDiskConfig;
58
+ type DriverConfig<K extends 'ftp' | 'local' | 'gcs' | 's3' | (string & {}) = string & {}> = K extends 'ftp' ? FtpDriverConfig : K extends 's3' ? S3DriverConfig : K extends 'gcs' ? GcsDiskDriverConfig : K extends 'local' ? LocalDriverConfig : K extends keyof CustomDiskDriverRegistry ? CustomDiskDriverRegistry[K] : DiskConfig;
59
+ type KnownDisks = {
60
+ local: LocalDriverConfig & {
61
+ driver: 'local';
62
+ };
63
+ public: LocalDriverConfig & {
64
+ driver: 'local';
65
+ };
66
+ ftp: FtpDriverConfig & {
67
+ driver: 'ftp';
68
+ };
69
+ gcs: GcsDiskDriverConfig & {
70
+ driver: 'gcs';
71
+ };
72
+ s3: S3DriverConfig & {
73
+ driver: 's3';
74
+ };
75
+ };
76
+ interface FilesystemConfig {
77
+ default: 'local' | 'ftp' | 'gcs' | 's3' | keyof CustomDiskDriverRegistry | (string & {});
78
+ disks: KnownDisks & CustomDiskDriverRegistry;
79
+ links: Record<string, string>;
80
+ custom_drivers?: Record<keyof CustomDiskDriverRegistry | (string & {}), DriverContract | (new (config?: CustomDiskConfig) => DriverContract)>;
81
+ fileNameGenerator?: (originalName: string) => string;
82
+ }
83
+ //#endregion
84
+ //#region src/Storage.d.ts
85
+ declare class Storage<D extends keyof KnownDisks | keyof CustomDiskDriverRegistry = keyof KnownDisks | keyof CustomDiskDriverRegistry> implements DriverContract {
12
86
  driver: DriveManager<any>;
13
87
  services: Record<string, () => DriverContract>;
14
- diskName: string;
88
+ diskName: D;
89
+ driverName: FilesystemConfig['disks'][D]['driver'];
15
90
  constructor();
16
- static disk<K extends string>(diskName?: K): Storage;
91
+ /**
92
+ * Static method to get a disk instance directly from the Storage class without needing to instantiate it first.
93
+ *
94
+ * @param diskName The name of the disk to use. If not provided, the default disk will be used.
95
+ * @returns A Storage instance
96
+ */
97
+ static disk<K extends keyof KnownDisks | keyof CustomDiskDriverRegistry>(diskName?: K): Storage<K>;
98
+ /**
99
+ * Generate a unique name for the file based on random numbers and original extension
100
+ *
101
+ * @param file The file object containing the original name
102
+ * @returns A unique file name
103
+ */
17
104
  static generateName: (file: {
18
105
  name?: string;
19
106
  originalname?: string;
20
107
  }) => string;
108
+ /**
109
+ * Save the file to the storage and return the public URL and the file path
110
+ *
111
+ * @param file The file object containing the file data
112
+ * @param filePath The path where the file should be saved
113
+ * @param fileName The name to save the file as (optional)
114
+ * @returns A tuple containing the public URL and the file path
115
+ */
21
116
  static saveFile: (file: FileLike, filePath?: string, fileName?: string) => Promise<[string, string]>;
117
+ /**
118
+ * Save the file to the storage and return the public URL and the file path
119
+ *
120
+ * @param file The file object containing the file data
121
+ * @param filePath The path where the file should be saved
122
+ * @param fileName The name to save the file as (optional)
123
+ * @returns A tuple containing the public URL and the file path
124
+ */
22
125
  saveFile: (file: FileLike, filePath?: string, fileName?: string) => Promise<[string, string]>;
126
+ /**
127
+ * Return a boolean indicating if the file exists
128
+ *
129
+ * @param key
130
+ * @returns
131
+ */
23
132
  exists(key: string): Promise<boolean>;
133
+ /**
134
+ * Return contents of a object for the given key as a UTF-8 string.
135
+ * Should throw "E_CANNOT_READ_FILE" error when the file
136
+ * does not exists.
137
+ *
138
+ * @param key
139
+ * @returns
140
+ */
24
141
  get(key: string): Promise<string>;
142
+ /**
143
+ * Get the name of the disk currently in use.
144
+ *
145
+ * @returns
146
+ */
147
+ getDiskName(): D;
148
+ /**
149
+ * Get the name of the driver currently in use.
150
+ *
151
+ * @returns
152
+ */
153
+ getDriverName(): (KnownDisks & CustomDiskDriverRegistry)[D]["driver"];
154
+ /**
155
+ * Get the driver currently in use.
156
+ *
157
+ * @returns
158
+ */
159
+ getDriver(): DriveManager<any>;
160
+ /**
161
+ * Return contents of a object for the given key as a Readable stream.
162
+ * Should throw "E_CANNOT_READ_FILE" error when the file
163
+ * does not exists.
164
+ *
165
+ * @param key
166
+ * @returns
167
+ */
25
168
  getStream(key: string): Promise<Readable>;
169
+ /**
170
+ * Return contents of an object for the given key as an Uint8Array.
171
+ * Should throw "E_CANNOT_READ_FILE" error when the file
172
+ * does not exists.
173
+ *
174
+ * @param key
175
+ * @returns
176
+ */
26
177
  getBytes(key: string): Promise<Uint8Array>;
178
+ /**
179
+ * Return metadata of an object for the given key.
180
+ *
181
+ * @param key
182
+ * @returns
183
+ */
27
184
  getMetaData(key: string): Promise<ObjectMetaData>;
185
+ /**
186
+ * Return the visibility of the file
187
+ *
188
+ * @param key
189
+ * @returns
190
+ */
28
191
  getVisibility(key: string): Promise<ObjectVisibility>;
192
+ /**
193
+ * Return the public URL to access the file
194
+ *
195
+ * @param key
196
+ * @returns
197
+ */
29
198
  getUrl(key: string): Promise<string>;
199
+ /**
200
+ * Return the signed/temporary URL to access the file
201
+ *
202
+ * @param key
203
+ * @param options
204
+ * @returns
205
+ */
30
206
  getSignedUrl(key: string, options?: SignedURLOptions): Promise<string>;
207
+ /**
208
+ * Return the signed/temporary URL that can be used to directly upload
209
+ * the file contents to the storage.
210
+ *
211
+ * @param key
212
+ * @param options
213
+ * @returns
214
+ */
31
215
  getSignedUploadUrl(key: string, options?: SignedURLOptions): Promise<string>;
216
+ /**
217
+ * Update the visibility of the file
218
+ *
219
+ * @param key
220
+ * @param visibility
221
+ * @returns
222
+ */
32
223
  setVisibility(key: string, visibility: ObjectVisibility): Promise<void>;
224
+ /**
225
+ * Write object to the destination with the provided
226
+ * contents.
227
+ *
228
+ * @param key
229
+ * @param contents
230
+ * @param options
231
+ * @returns
232
+ */
33
233
  put(key: string, contents: string | Uint8Array | FileLike, options?: WriteOptions): Promise<void>;
234
+ /**
235
+ * Write object to the destination with the provided
236
+ * contents as a readable stream
237
+ *
238
+ * @param key
239
+ * @param contents
240
+ * @param options
241
+ * @returns
242
+ */
34
243
  putStream(key: string, contents: Readable, options?: WriteOptions): Promise<void>;
244
+ /**
245
+ * Copy the file from within the disk root location. Both
246
+ * the "source" and "destination" will be the key names
247
+ * and not absolute paths.
248
+ *
249
+ * @param source
250
+ * @param destination
251
+ * @param options
252
+ * @returns
253
+ */
35
254
  copy(source: string, destination: string, options?: WriteOptions): Promise<void>;
255
+ /**
256
+ * Move the file from within the disk root location. Both
257
+ * the "source" and "destination" will be the key names
258
+ * and not absolute paths.
259
+ *
260
+ * @param source
261
+ * @param destination
262
+ * @param options
263
+ * @returns
264
+ */
36
265
  move(source: string, destination: string, options?: WriteOptions): Promise<void>;
266
+ /**
267
+ * Delete the file for the given key. Should not throw
268
+ * error when file does not exist in first place
269
+ *
270
+ * @param key
271
+ * @returns
272
+ */
37
273
  delete(key: string): Promise<void>;
274
+ /**
275
+ * Delete the files and directories matching the provided prefix.
276
+ *
277
+ * @param prefix
278
+ * @returns
279
+ */
38
280
  deleteAll(prefix: string): Promise<void>;
281
+ /**
282
+ * The list all method must return an array of objects with
283
+ * the ability to paginate results (if supported).
284
+ *
285
+ * @param prefix
286
+ * @param options
287
+ * @returns
288
+ */
39
289
  listAll(prefix: string, options?: {
40
290
  recursive?: boolean;
41
291
  paginationToken?: string;
@@ -43,14 +293,151 @@ declare class Storage implements DriverContract {
43
293
  paginationToken?: string;
44
294
  objects: Iterable<DriveFile | DriveDirectory>;
45
295
  }>;
296
+ /**
297
+ * Switch bucket at runtime if supported.
298
+ *
299
+ * @param bucket
300
+ * @returns
301
+ */
46
302
  bucket(bucket: string): DriverContract;
303
+ /**
304
+ * Create symbolic links for all configured links in the application configuration.
305
+ *
306
+ * @param param0
307
+ */
47
308
  static link({
48
309
  force
49
310
  }?: {
50
311
  force?: boolean;
51
312
  }): void;
52
- private driversMap;
53
313
  }
54
314
  //#endregion
55
- export { Storage };
56
- //# sourceMappingURL=index.d.ts.map
315
+ //#region src/FtpDriver.d.ts
316
+ declare class FtpDriver implements DriverContract {
317
+ private config;
318
+ constructor(config: string | {
319
+ host: string;
320
+ username: string;
321
+ password: string;
322
+ port?: number;
323
+ verbose?: boolean;
324
+ privateKey?: string;
325
+ });
326
+ getConfig(): {
327
+ host: string;
328
+ username: string;
329
+ password: string;
330
+ port?: number;
331
+ verbose?: boolean;
332
+ privateKey?: string;
333
+ };
334
+ private init;
335
+ private load;
336
+ /**
337
+ * Return a boolean value indicating if the file exists
338
+ * or not.
339
+ */
340
+ exists(key: string): Promise<boolean>;
341
+ /**
342
+ * Return the file contents as a UTF-8 string. Throw an exception
343
+ * if the file is missing.
344
+ */
345
+ get(key: string): Promise<string>;
346
+ /**
347
+ * Return the file contents as a Readable stream. Throw an exception
348
+ * if the file is missing.
349
+ */
350
+ getStream(key: string): Promise<Readable>;
351
+ /**
352
+ * Return the file contents as a Uint8Array. Throw an exception
353
+ * if the file is missing.
354
+ */
355
+ getBytes(key: string): Promise<Uint8Array>;
356
+ /**
357
+ * Return metadata of the file. Throw an exception
358
+ * if the file is missing.
359
+ */
360
+ getMetaData(key: string): Promise<ObjectMetaData>;
361
+ /**
362
+ * Return visibility of the file. Infer visibility from the initial
363
+ * config, when the driver does not support the concept of visibility.
364
+ */
365
+ getVisibility(key: string): Promise<ObjectVisibility>;
366
+ /**
367
+ * Return the public URL of the file. Throw an exception when the driver
368
+ * does not support generating URLs.
369
+ */
370
+ getUrl(key: string): Promise<string>;
371
+ /**
372
+ * Return the signed URL to serve a private file. Throw exception
373
+ * when the driver does not support generating URLs.
374
+ */
375
+ getSignedUrl(key: string, options?: SignedURLOptions): Promise<string>;
376
+ /**
377
+ * Return the signed/temporary URL that can be used to directly upload
378
+ * the file contents to the storage.
379
+ */
380
+ getSignedUploadUrl(key: string, options?: SignedURLOptions): Promise<string>;
381
+ /**
382
+ * Update the visibility of the file. Result in a NOOP
383
+ * when the driver does not support the concept of
384
+ * visibility.
385
+ */
386
+ setVisibility(key: string, visibility: ObjectVisibility): Promise<void>;
387
+ /**
388
+ * Create a new file or update an existing file. The contents
389
+ * will be a UTF-8 string or "Uint8Array".
390
+ */
391
+ put(key: string, contents: string | Uint8Array, options?: WriteOptions): Promise<void>;
392
+ /**
393
+ * Create a new file or update an existing file. The contents
394
+ * will be a Readable stream.
395
+ */
396
+ putStream(key: string, contents: Readable, options?: WriteOptions): Promise<void>;
397
+ /**
398
+ * Copy the existing file to the destination. Make sure the new file
399
+ * has the same visibility as the existing file. It might require
400
+ * manually fetching the visibility of the "source" file.
401
+ */
402
+ copy(source: string, destination: string, options?: WriteOptions): Promise<void>;
403
+ /**
404
+ * Move the existing file to the destination. Make sure the new file
405
+ * has the same visibility as the existing file. It might require
406
+ * manually fetching the visibility of the "source" file.
407
+ */
408
+ move(source: string, destination: string, options?: WriteOptions): Promise<void>;
409
+ /**
410
+ * Delete an existing file. Do not throw an error if the
411
+ * file is already missing
412
+ */
413
+ delete(key: string): Promise<void>;
414
+ /**
415
+ * Delete all files inside a folder. Do not throw an error
416
+ * if the folder does not exist or is empty.
417
+ */
418
+ deleteAll(prefix: string): Promise<void>;
419
+ /**
420
+ * Switch bucket at runtime if supported.
421
+ */
422
+ bucket(config: string | {
423
+ host: string;
424
+ username: string;
425
+ password: string;
426
+ port?: number;
427
+ verbose?: boolean;
428
+ privateKey?: string;
429
+ }): DriverContract;
430
+ /**
431
+ * List all files from a given folder or the root of the storage.
432
+ * Do not throw an error if the request folder does not exist.
433
+ */
434
+ listAll(prefix: string, options?: {
435
+ recursive?: boolean;
436
+ paginationToken?: string;
437
+ }): Promise<{
438
+ paginationToken?: string;
439
+ objects: Iterable<DriveFile | DriveDirectory>;
440
+ }>;
441
+ }
442
+ //#endregion
443
+ export { CustomDiskConfig, CustomDiskDriverRegistry, DiskConfig, DriverConfig, FileLike, FilesystemConfig, FtpDriver, FtpDriverConfig, GcsDiskDriverConfig, KnownDisks, LocalDriverConfig, S3DriverConfig, Storage };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { t as Storage } from "./src-wGEPrkk5.js";
2
- export { Storage };
1
+ import { n as FtpDriver, t as Storage } from "./src-DNh_3BpA.js";
2
+ export { FtpDriver, Storage };
@@ -1,12 +1,14 @@
1
1
  import { DriveDirectory, DriveFile, DriveManager } from "flydrive";
2
- import { appUrl, config } from "@arkstack/common";
3
2
  import { createReadStream, createWriteStream, rmSync, symlinkSync } from "node:fs";
3
+ import { Arkstack } from "@arkstack/contract";
4
4
  import { FSDriver } from "flydrive/drivers/fs";
5
5
  import Client from "ssh2-sftp-client";
6
6
  import { Readable } from "node:stream";
7
7
  import path from "node:path";
8
- import { Logger } from "@h3ravel/shared";
8
+ import { GCSDriver } from "flydrive/drivers/gcs";
9
9
  import { S3Driver } from "flydrive/drivers/s3";
10
+ import { appUrl, config } from "@arkstack/common";
11
+ import { Logger } from "@h3ravel/shared";
10
12
  //#region src/FtpDriver.ts
11
13
  var FtpDriver = class FtpDriver {
12
14
  config;
@@ -235,19 +237,112 @@ var FtpDriver = class FtpDriver {
235
237
  }
236
238
  };
237
239
  //#endregion
238
- //#region src/index.ts
240
+ //#region src/Driver.ts
241
+ var Driver = class Driver {
242
+ config;
243
+ static customDrivers = /* @__PURE__ */ new Map();
244
+ constructor(config) {
245
+ this.config = config;
246
+ }
247
+ static make(config) {
248
+ const name = config.driver;
249
+ if (![
250
+ "local",
251
+ "ftp",
252
+ "s3",
253
+ "gcs"
254
+ ].includes(name) && !this.customDrivers.has(name)) throw new Error(`Unsupported driver: ${name}`);
255
+ const driver = new Driver(config);
256
+ if (this.customDrivers.has(name)) return driver.custom(name);
257
+ return driver[name].call(driver);
258
+ }
259
+ local() {
260
+ const config = this.config;
261
+ return new FSDriver({
262
+ location: config.location ?? new URL(config.root, import.meta.url),
263
+ visibility: config.visibility ?? "public",
264
+ urlBuilder: {
265
+ async generateURL(key, _path) {
266
+ if (config.url) return `${config.url}/key`.replace(/^(https?:\/)\/+/, "$1/").replace(/([^:]\/)\/+/g, "$1");
267
+ return appUrl(key);
268
+ },
269
+ async generateSignedURL(key, _path, _opts) {
270
+ if (config.url) return `${config.url}/key`.replace(/^(https?:\/)\/+/, "$1/").replace(/([^:]\/)\/+/g, "$1");
271
+ return appUrl(key);
272
+ }
273
+ }
274
+ });
275
+ }
276
+ s3() {
277
+ const config = this.config;
278
+ return new S3Driver({
279
+ credentials: config.credentials ?? {
280
+ accessKeyId: config.key,
281
+ secretAccessKey: config.secret
282
+ },
283
+ endpoint: config.endpoint,
284
+ region: config.region,
285
+ bucket: config.bucket,
286
+ visibility: "private",
287
+ cdnUrl: config.cdnUrl ?? config.url
288
+ });
289
+ }
290
+ gcs() {
291
+ const { driver: _driver, ...options } = this.config;
292
+ return new GCSDriver(options);
293
+ }
294
+ ftp() {
295
+ const config = this.config;
296
+ return new FtpDriver({
297
+ host: config.host,
298
+ username: config.username,
299
+ password: config.password,
300
+ port: config.port,
301
+ verbose: config.verbose,
302
+ privateKey: config.privateKey
303
+ });
304
+ }
305
+ custom(name) {
306
+ if (!Driver.customDrivers.has(name)) throw new Error(`Unsupported driver: ${name} has not been registered`);
307
+ const DriverInstance = Driver.customDrivers.get(name);
308
+ if (typeof DriverInstance === "function") {
309
+ const config = this.config;
310
+ return new DriverInstance(config);
311
+ }
312
+ return DriverInstance;
313
+ }
314
+ /**
315
+ * Register a new custom driver
316
+ *
317
+ * @param name
318
+ * @param driver
319
+ */
320
+ static registerDriver(name, driver) {
321
+ Driver.customDrivers.set(name, driver);
322
+ }
323
+ /**
324
+ * Unregister a new custom driver
325
+ *
326
+ * @param name
327
+ */
328
+ static removeDriver(name) {
329
+ Driver.customDrivers.delete(name);
330
+ }
331
+ };
332
+ //#endregion
333
+ //#region src/Storage.ts
239
334
  var Storage = class Storage {
240
335
  driver;
241
336
  services = {};
242
337
  diskName;
338
+ driverName;
243
339
  constructor() {
244
- for (const diskName in config("filesystem.disks")) {
245
- const diskConfig = config("filesystem.disks")[diskName];
246
- const driverFactory = this.driversMap[diskConfig.driver];
247
- if (!driverFactory) throw new Error(`Unsupported driver: ${diskConfig.driver}`);
248
- this.services[diskName] = () => driverFactory(diskConfig);
249
- }
340
+ const disks = Object.entries(config("filesystem.disks", {}));
341
+ const customDrivers = config("filesystem.custom_drivers", {});
342
+ for (const [name, driver] of Object.entries(customDrivers)) Driver.registerDriver(name, driver);
343
+ for (const [disk, config] of disks) this.services[disk] = () => Driver.make(config);
250
344
  this.diskName = config("filesystem.default");
345
+ this.driverName = config(`filesystem.disks.${this.diskName}.driver`);
251
346
  this.driver = new DriveManager({
252
347
  default: config("filesystem.default"),
253
348
  services: this.services
@@ -263,6 +358,7 @@ var Storage = class Storage {
263
358
  const storage = new Storage();
264
359
  if (diskName) {
265
360
  storage.diskName = diskName;
361
+ storage.driverName = config(`filesystem.disks.${diskName}.driver`);
266
362
  storage.driver = new DriveManager({
267
363
  default: diskName,
268
364
  services: storage.services
@@ -306,10 +402,13 @@ var Storage = class Storage {
306
402
  if (file instanceof File && !file.buffer) file.buffer = Buffer.from(await file.arrayBuffer());
307
403
  await drive.put(path.join(filePath, name), file.buffer);
308
404
  const url = await drive.getUrl(path.join(filePath, name));
309
- return [url, this.diskName === "local" ? path.join(filePath, name) : url];
405
+ return [url, this.driverName === "local" ? path.join(filePath, name) : url];
310
406
  };
311
407
  /**
312
408
  * Return a boolean indicating if the file exists
409
+ *
410
+ * @param key
411
+ * @returns
313
412
  */
314
413
  exists(key) {
315
414
  return this.driver.use().exists(key);
@@ -318,14 +417,44 @@ var Storage = class Storage {
318
417
  * Return contents of a object for the given key as a UTF-8 string.
319
418
  * Should throw "E_CANNOT_READ_FILE" error when the file
320
419
  * does not exists.
420
+ *
421
+ * @param key
422
+ * @returns
321
423
  */
322
424
  get(key) {
323
425
  return this.driver.use().get(key);
324
426
  }
325
427
  /**
428
+ * Get the name of the disk currently in use.
429
+ *
430
+ * @returns
431
+ */
432
+ getDiskName() {
433
+ return this.diskName;
434
+ }
435
+ /**
436
+ * Get the name of the driver currently in use.
437
+ *
438
+ * @returns
439
+ */
440
+ getDriverName() {
441
+ return this.driverName;
442
+ }
443
+ /**
444
+ * Get the driver currently in use.
445
+ *
446
+ * @returns
447
+ */
448
+ getDriver() {
449
+ return this.driver;
450
+ }
451
+ /**
326
452
  * Return contents of a object for the given key as a Readable stream.
327
453
  * Should throw "E_CANNOT_READ_FILE" error when the file
328
454
  * does not exists.
455
+ *
456
+ * @param key
457
+ * @returns
329
458
  */
330
459
  getStream(key) {
331
460
  return this.driver.use().getStream(key);
@@ -334,30 +463,46 @@ var Storage = class Storage {
334
463
  * Return contents of an object for the given key as an Uint8Array.
335
464
  * Should throw "E_CANNOT_READ_FILE" error when the file
336
465
  * does not exists.
466
+ *
467
+ * @param key
468
+ * @returns
337
469
  */
338
470
  getBytes(key) {
339
471
  return this.driver.use().getBytes(key);
340
472
  }
341
473
  /**
342
474
  * Return metadata of an object for the given key.
475
+ *
476
+ * @param key
477
+ * @returns
343
478
  */
344
479
  getMetaData(key) {
345
480
  return this.driver.use().getMetaData(key);
346
481
  }
347
482
  /**
348
483
  * Return the visibility of the file
484
+ *
485
+ * @param key
486
+ * @returns
349
487
  */
350
488
  getVisibility(key) {
351
489
  return this.driver.use().getVisibility(key);
352
490
  }
353
491
  /**
354
492
  * Return the public URL to access the file
493
+ *
494
+ * @param key
495
+ * @returns
355
496
  */
356
497
  getUrl(key) {
357
498
  return this.driver.use().getUrl(key);
358
499
  }
359
500
  /**
360
501
  * Return the signed/temporary URL to access the file
502
+ *
503
+ * @param key
504
+ * @param options
505
+ * @returns
361
506
  */
362
507
  getSignedUrl(key, options) {
363
508
  return this.driver.use().getSignedUrl(key, options);
@@ -365,12 +510,20 @@ var Storage = class Storage {
365
510
  /**
366
511
  * Return the signed/temporary URL that can be used to directly upload
367
512
  * the file contents to the storage.
513
+ *
514
+ * @param key
515
+ * @param options
516
+ * @returns
368
517
  */
369
518
  getSignedUploadUrl(key, options) {
370
519
  return this.driver.use().getSignedUploadUrl(key, options);
371
520
  }
372
521
  /**
373
522
  * Update the visibility of the file
523
+ *
524
+ * @param key
525
+ * @param visibility
526
+ * @returns
374
527
  */
375
528
  setVisibility(key, visibility) {
376
529
  return this.driver.use().setVisibility(key, visibility);
@@ -378,6 +531,11 @@ var Storage = class Storage {
378
531
  /**
379
532
  * Write object to the destination with the provided
380
533
  * contents.
534
+ *
535
+ * @param key
536
+ * @param contents
537
+ * @param options
538
+ * @returns
381
539
  */
382
540
  put(key, contents, options) {
383
541
  if (!(contents instanceof Uint8Array) && typeof contents !== "string") contents = contents.buffer;
@@ -386,6 +544,11 @@ var Storage = class Storage {
386
544
  /**
387
545
  * Write object to the destination with the provided
388
546
  * contents as a readable stream
547
+ *
548
+ * @param key
549
+ * @param contents
550
+ * @param options
551
+ * @returns
389
552
  */
390
553
  putStream(key, contents, options) {
391
554
  return this.driver.use().putStream(key, contents, options);
@@ -394,6 +557,11 @@ var Storage = class Storage {
394
557
  * Copy the file from within the disk root location. Both
395
558
  * the "source" and "destination" will be the key names
396
559
  * and not absolute paths.
560
+ *
561
+ * @param source
562
+ * @param destination
563
+ * @param options
564
+ * @returns
397
565
  */
398
566
  copy(source, destination, options) {
399
567
  return this.driver.use().copy(source, destination, options);
@@ -402,6 +570,11 @@ var Storage = class Storage {
402
570
  * Move the file from within the disk root location. Both
403
571
  * the "source" and "destination" will be the key names
404
572
  * and not absolute paths.
573
+ *
574
+ * @param source
575
+ * @param destination
576
+ * @param options
577
+ * @returns
405
578
  */
406
579
  move(source, destination, options) {
407
580
  return this.driver.use().move(source, destination, options);
@@ -409,12 +582,18 @@ var Storage = class Storage {
409
582
  /**
410
583
  * Delete the file for the given key. Should not throw
411
584
  * error when file does not exist in first place
585
+ *
586
+ * @param key
587
+ * @returns
412
588
  */
413
589
  delete(key) {
414
590
  return this.driver.use().delete(key);
415
591
  }
416
592
  /**
417
593
  * Delete the files and directories matching the provided prefix.
594
+ *
595
+ * @param prefix
596
+ * @returns
418
597
  */
419
598
  deleteAll(prefix) {
420
599
  return this.driver.use().deleteAll(prefix);
@@ -422,24 +601,33 @@ var Storage = class Storage {
422
601
  /**
423
602
  * The list all method must return an array of objects with
424
603
  * the ability to paginate results (if supported).
604
+ *
605
+ * @param prefix
606
+ * @param options
607
+ * @returns
425
608
  */
426
609
  listAll(prefix, options) {
427
610
  return this.driver.use().listAll(prefix, options);
428
611
  }
429
612
  /**
430
613
  * Switch bucket at runtime if supported.
614
+ *
615
+ * @param bucket
616
+ * @returns
431
617
  */
432
618
  bucket(bucket) {
433
619
  return this.driver.use().bucket(bucket);
434
620
  }
435
621
  /**
436
622
  * Create symbolic links for all configured links in the application configuration.
623
+ *
624
+ * @param param0
437
625
  */
438
626
  static link({ force = false } = {}) {
439
627
  for (const link in config("filesystem.links")) {
440
628
  const target = config("filesystem.links")[link];
441
- const unlink = link.replace(process.cwd(), "");
442
- const untarget = target.replace(process.cwd(), "");
629
+ const unlink = link.replace(Arkstack.rootDir(), "");
630
+ const untarget = target.replace(Arkstack.rootDir(), "");
443
631
  try {
444
632
  if (force) rmSync(link, {
445
633
  recursive: true,
@@ -470,41 +658,6 @@ var Storage = class Storage {
470
658
  }
471
659
  }
472
660
  }
473
- driversMap = {
474
- local: (conf) => new FSDriver({
475
- location: new URL(conf.root, import.meta.url),
476
- visibility: "public",
477
- urlBuilder: {
478
- async generateURL(key, _path) {
479
- return appUrl(key);
480
- },
481
- async generateSignedURL(key, _path, _opts) {
482
- return appUrl(key);
483
- }
484
- }
485
- }),
486
- s3: (conf) => new S3Driver({
487
- credentials: {
488
- accessKeyId: conf.key,
489
- secretAccessKey: conf.secret
490
- },
491
- endpoint: conf.endpoint,
492
- region: conf.region,
493
- bucket: conf.bucket,
494
- visibility: "private",
495
- cdnUrl: conf.url
496
- }),
497
- ftp: (conf) => new FtpDriver({
498
- host: conf.host,
499
- username: conf.username,
500
- password: conf.password,
501
- port: conf.port,
502
- verbose: conf.verbose,
503
- privateKey: conf.privateKey
504
- })
505
- };
506
661
  };
507
662
  //#endregion
508
- export { Storage as t };
509
-
510
- //# sourceMappingURL=src-wGEPrkk5.js.map
663
+ export { FtpDriver as n, Storage as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkstack/filesystem",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "type": "module",
5
5
  "description": "Filesystem module for Arkstack, providing shared file storage and filesystem utitlities for the framework.",
6
6
  "homepage": "https://arkstack.toneflix.net",
@@ -31,16 +31,17 @@
31
31
  "./package.json": "./package.json"
32
32
  },
33
33
  "peerDependencies": {
34
- "@h3ravel/musket": "^0.10.1"
34
+ "@h3ravel/musket": "^2.2.1"
35
35
  },
36
36
  "dependencies": {
37
- "@h3ravel/shared": "^0.27.13",
37
+ "@h3ravel/shared": "^2.1.3",
38
+ "@google-cloud/storage": "^7.21.0",
38
39
  "@aws-sdk/client-s3": "^3.1011.0",
39
40
  "@aws-sdk/s3-request-presigner": "^3.1011.0",
40
41
  "flydrive": "^2.0.0",
41
42
  "ssh2-sftp-client": "^12.1.0",
42
- "@arkstack/common": "^0.5.2",
43
- "@arkstack/contract": "^0.5.2"
43
+ "@arkstack/common": "^0.5.3",
44
+ "@arkstack/contract": "^0.5.3"
44
45
  },
45
46
  "devDependencies": {
46
47
  "@types/ssh2-sftp-client": "^9.0.6"
@@ -1 +0,0 @@
1
- {"version":3,"file":"StorageLinkCommand.js","names":[],"sources":["../../src/commands/StorageLinkCommand.ts"],"sourcesContent":["import { Command } from '@h3ravel/musket'\nimport { Storage } from '../'\n\nexport class StorageLinkCommand extends Command {\n protected signature = `storage:link\n {--force : Remove existing links before creating new ones.}\n `\n protected description = 'Create symbolic links for filesystem.links configuration.'\n\n async handle () {\n Storage.link(this.options())\n }\n}"],"mappings":";;;AAGA,IAAa,qBAAb,cAAwC,QAAQ;CAC5C,YAAsB;;;CAGtB,cAAwB;CAExB,MAAM,SAAU;EACZ,QAAQ,KAAK,KAAK,SAAS,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"src-wGEPrkk5.js","names":[],"sources":["../src/FtpDriver.ts","../src/index.ts"],"sourcesContent":["import { DriveDirectory, DriveFile } from 'flydrive'\nimport type {\n DriverContract,\n ObjectMetaData,\n ObjectVisibility,\n SignedURLOptions,\n WriteOptions,\n} from 'flydrive/types'\nimport { createReadStream, createWriteStream } from 'node:fs'\n\nimport Client from 'ssh2-sftp-client'\nimport { Readable } from 'node:stream'\nimport path from 'node:path'\n\nexport class FtpDriver implements DriverContract {\n private config: {\n host: string\n username: string\n password: string\n port?: number\n verbose?: boolean\n privateKey?: string\n }\n\n constructor(config: string | {\n host: string\n username: string\n password: string\n port?: number\n verbose?: boolean\n privateKey?: string\n }) {\n if (typeof config === 'string') {\n const url = new URL(config)\n\n this.config = {\n host: url.hostname,\n username: url.username,\n password: url.password,\n port: url.port ? parseInt(url.port, 10) : 22,\n verbose: url.searchParams.get('verbose') === 'true',\n }\n } else {\n this.config = config\n }\n }\n\n getConfig () {\n return this.config\n }\n\n private async init () {\n const client = new Client()\n\n await client.connect({\n host: this.config.host,\n username: this.config.username,\n password: this.config.password,\n port: this.config.port || 22,\n })\n\n return client\n }\n\n private async load<T> (handle: (client: Client) => Promise<T>): Promise<T> {\n const client = await this.init()\n\n try {\n return await handle(client)\n } catch (e) {\n if (this.config.verbose) {\n throw e\n }\n } finally {\n await client.end()\n }\n\n return null as any\n }\n\n /**\n * Return a boolean value indicating if the file exists\n * or not.\n */\n async exists (key: string): Promise<boolean> {\n const client = await this.load((client) => {\n return client.stat(key)\n }).catch(() => {\n return null\n })\n\n return !!client\n }\n\n /**\n * Return the file contents as a UTF-8 string. Throw an exception\n * if the file is missing.\n */\n async get (key: string): Promise<string> {\n const stream = await this.getStream(key)\n\n const chunks: Uint8Array[] = []\n for await (const chunk of stream) {\n chunks.push(chunk)\n }\n\n return Buffer.concat(chunks).toString('utf-8')\n }\n\n /**\n * Return the file contents as a Readable stream. Throw an exception\n * if the file is missing.\n */\n async getStream (key: string): Promise<Readable> {\n const dst = createWriteStream('/tmp/' + path.basename(key))\n await this.load((client) => {\n return client.get(key, dst)\n }).catch(() => {\n return null\n })\n\n return createReadStream('/tmp/' + path.basename(key))\n }\n\n /**\n * Return the file contents as a Uint8Array. Throw an exception\n * if the file is missing.\n */\n async getBytes (key: string): Promise<Uint8Array> {\n const content = await this.get(key)\n\n return new Uint8Array(Buffer.from(content, 'utf-8'))\n }\n\n /**\n * Return metadata of the file. Throw an exception\n * if the file is missing.\n */\n async getMetaData (key: string): Promise<ObjectMetaData> {\n const stat = await this.load((client) => {\n return client.stat(key)\n }).catch(() => {\n return null\n })\n\n if (!stat) {\n throw new Error('E_CANNOT_READ_FILE')\n }\n\n return {\n contentType: undefined,\n contentLength: stat.size,\n etag: key,\n lastModified: new Date(stat.modifyTime),\n }\n }\n\n /**\n * Return visibility of the file. Infer visibility from the initial\n * config, when the driver does not support the concept of visibility.\n */\n async getVisibility (key: string): Promise<ObjectVisibility> {\n void key\n\n return 'private'\n }\n\n /**\n * Return the public URL of the file. Throw an exception when the driver\n * does not support generating URLs.\n */\n async getUrl (key: string): Promise<string> {\n void key\n throw new Error('E_URL_GENERATION_UNSUPPORTED')\n }\n\n /**\n * Return the signed URL to serve a private file. Throw exception\n * when the driver does not support generating URLs.\n */\n async getSignedUrl (key: string, options?: SignedURLOptions): Promise<string> {\n void key\n void options\n throw new Error('E_URL_GENERATION_UNSUPPORTED')\n }\n /**\n * Return the signed/temporary URL that can be used to directly upload\n * the file contents to the storage.\n */\n async getSignedUploadUrl (key: string, options?: SignedURLOptions): Promise<string> {\n void key\n void options\n throw new Error('E_URL_GENERATION_UNSUPPORTED')\n }\n\n /**\n * Update the visibility of the file. Result in a NOOP\n * when the driver does not support the concept of\n * visibility.\n */\n async setVisibility (key: string, visibility: ObjectVisibility): Promise<void> {\n void key\n void visibility\n }\n\n /**\n * Create a new file or update an existing file. The contents\n * will be a UTF-8 string or \"Uint8Array\".\n */\n async put (key: string, contents: string | Uint8Array, options?: WriteOptions): Promise<void> {\n if (contents instanceof Uint8Array) {\n contents = Buffer.from(contents)\n }\n\n const stream = Readable.from(contents)\n\n await this.putStream(key, stream, options)\n }\n\n /**\n * Create a new file or update an existing file. The contents\n * will be a Readable stream.\n */\n async putStream (key: string, contents: Readable, options?: WriteOptions): Promise<void> {\n const dst = createWriteStream('/tmp/' + path.basename(key), {\n encoding: options?.contentEncoding as never || 'utf-8',\n })\n\n await new Promise((resolve, reject) => {\n contents.pipe(dst)\n contents.on('error', reject)\n dst.on('finish', resolve)\n dst.on('error', reject)\n })\n\n await this.load((client) => {\n return client.put('/tmp/' + path.basename(key), key)\n })\n }\n\n /**\n * Copy the existing file to the destination. Make sure the new file\n * has the same visibility as the existing file. It might require\n * manually fetching the visibility of the \"source\" file.\n */\n async copy (source: string, destination: string, options?: WriteOptions): Promise<void> {\n void options\n await this.load((client) => {\n return client.rcopy(source, destination)\n })\n }\n\n /**\n * Move the existing file to the destination. Make sure the new file\n * has the same visibility as the existing file. It might require\n * manually fetching the visibility of the \"source\" file.\n */\n async move (source: string, destination: string, options?: WriteOptions): Promise<void> {\n void options\n await this.load((client) => {\n return client.rename(source, destination)\n })\n }\n\n /**\n * Delete an existing file. Do not throw an error if the\n * file is already missing\n */\n async delete (key: string): Promise<void> {\n await this.load((client) => {\n return client.delete(key)\n }).catch(() => {\n return null\n })\n }\n\n /**\n * Delete all files inside a folder. Do not throw an error\n * if the folder does not exist or is empty.\n */\n async deleteAll (prefix: string): Promise<void> {\n await this.load((client) => {\n return client.rmdir(prefix, true)\n }).catch(() => {\n return null\n })\n }\n\n\n /**\n * Switch bucket at runtime if supported.\n */\n bucket (config: string | {\n host: string\n username: string\n password: string\n port?: number\n verbose?: boolean\n privateKey?: string\n }): DriverContract {\n return new FtpDriver(config)\n }\n\n /**\n * List all files from a given folder or the root of the storage.\n * Do not throw an error if the request folder does not exist.\n */\n async listAll (\n prefix: string,\n options?: {\n recursive?: boolean\n paginationToken?: string\n }\n ): Promise<{\n paginationToken?: string\n objects: Iterable<DriveFile | DriveDirectory>\n }> {\n void options\n\n const data = await this.load((client) => {\n return client.list(prefix)\n }).catch(() => {\n return null\n })\n\n return data ? {\n objects: data.map((file) => {\n if (file.type === 'd') {\n return new DriveDirectory(file.name)\n } else {\n return new DriveFile(file.name, this, {\n contentType: undefined,\n contentLength: file.size,\n etag: file.name,\n lastModified: new Date(file.modifyTime),\n })\n }\n })\n } : { objects: [] }\n }\n\n}\n","import { DriveDirectory, DriveFile, DriveManager } from 'flydrive'\nimport { DriverContract, ObjectMetaData, ObjectVisibility, SignedURLOptions, WriteOptions } from 'flydrive/types'\nimport { appUrl, config } from '@arkstack/common'\nimport { rmSync, symlinkSync } from 'node:fs'\n\nimport { FSDriver } from 'flydrive/drivers/fs'\nimport { FtpDriver } from './FtpDriver'\nimport { Logger } from '@h3ravel/shared'\nimport { Readable } from 'node:stream'\nimport { S3Driver } from 'flydrive/drivers/s3'\nimport path from 'node:path'\n\ninterface FileLike {\n originalname: string\n buffer: Buffer\n mimetype: string\n}\n\nexport class Storage implements DriverContract {\n driver: DriveManager<any>\n services: Record<string, () => DriverContract> = {}\n diskName: string\n\n constructor() {\n for (const diskName in config('filesystem.disks')) {\n const diskConfig = config('filesystem.disks')[diskName]\n const driverFactory = this.driversMap[diskConfig.driver]\n\n if (!driverFactory) {\n throw new Error(`Unsupported driver: ${diskConfig.driver}`)\n }\n\n this.services[diskName] = () => driverFactory(diskConfig)\n }\n\n this.diskName = config('filesystem.default')\n this.driver = new DriveManager({\n default: config('filesystem.default'),\n services: this.services\n })\n }\n\n /**\n * Static method to get a disk instance directly from the Storage class without needing to instantiate it first.\n * \n * @param diskName The name of the disk to use. If not provided, the default disk will be used.\n * @returns A Storage instance\n */\n static disk<K extends string> (diskName?: K): Storage {\n const storage = new Storage()\n\n if (diskName) {\n storage.diskName = diskName\n storage.driver = new DriveManager({\n default: diskName,\n services: storage.services\n })\n }\n\n return storage\n }\n\n /**\n * Generate a unique name for the file based on random numbers and original extension\n * \n * @param file The file object containing the original name\n * @returns A unique file name\n */\n static generateName = (file: { name?: string; originalname?: string }): string => {\n const name = file.originalname || file.name || 'file'\n\n if (typeof config('filesystem.fileNameGenerator') === 'function') {\n return config('filesystem.fileNameGenerator')(name)\n }\n\n return Math.floor(Math.random() * 999999999999).toString() +\n '_' + Math.floor(Math.random() * 999999999999) +\n '.' + (name).split('.').pop()\n }\n\n /**\n * Save the file to the storage and return the public URL and the file path\n * \n * @param file The file object containing the file data\n * @param filePath The path where the file should be saved\n * @param fileName The name to save the file as (optional)\n * @returns A tuple containing the public URL and the file path\n */\n static saveFile = async (\n file: FileLike,\n filePath: string = '',\n fileName?: string\n ): Promise<[string, string]> => {\n return new Storage().saveFile(file, filePath, fileName)\n }\n\n /**\n * Save the file to the storage and return the public URL and the file path\n * \n * @param file The file object containing the file data\n * @param filePath The path where the file should be saved\n * @param fileName The name to save the file as (optional)\n * @returns A tuple containing the public URL and the file path\n */\n saveFile = async (\n file: FileLike,\n filePath: string = '',\n fileName?: string\n ): Promise<[string, string]> => {\n const name = fileName || Storage.generateName(file)\n const drive = this.driver.use()\n\n if (file instanceof File && !file.buffer) {\n file.buffer = Buffer.from(await file.arrayBuffer())\n }\n\n await drive.put(path.join(filePath, name), file.buffer)\n\n const url = await drive.getUrl(path.join(filePath, name))\n const pth = this.diskName === 'local' ? path.join(filePath, name) : url\n\n return [url, pth]\n }\n\n /**\n * Return a boolean indicating if the file exists\n */\n exists (key: string): Promise<boolean> {\n return this.driver.use().exists(key)\n }\n /**\n * Return contents of a object for the given key as a UTF-8 string.\n * Should throw \"E_CANNOT_READ_FILE\" error when the file\n * does not exists.\n */\n get (key: string): Promise<string> {\n return this.driver.use().get(key)\n }\n /**\n * Return contents of a object for the given key as a Readable stream.\n * Should throw \"E_CANNOT_READ_FILE\" error when the file\n * does not exists.\n */\n getStream (key: string): Promise<Readable> {\n return this.driver.use().getStream(key)\n }\n /**\n * Return contents of an object for the given key as an Uint8Array.\n * Should throw \"E_CANNOT_READ_FILE\" error when the file\n * does not exists.\n */\n getBytes (key: string): Promise<Uint8Array> {\n return this.driver.use().getBytes(key)\n }\n /**\n * Return metadata of an object for the given key.\n */\n getMetaData (key: string): Promise<ObjectMetaData> {\n return this.driver.use().getMetaData(key)\n }\n /**\n * Return the visibility of the file\n */\n getVisibility (key: string): Promise<ObjectVisibility> {\n return this.driver.use().getVisibility(key)\n }\n /**\n * Return the public URL to access the file\n */\n getUrl (key: string): Promise<string> {\n return this.driver.use().getUrl(key)\n }\n /**\n * Return the signed/temporary URL to access the file\n */\n getSignedUrl (key: string, options?: SignedURLOptions): Promise<string> {\n return this.driver.use().getSignedUrl(key, options)\n }\n /**\n * Return the signed/temporary URL that can be used to directly upload\n * the file contents to the storage.\n */\n getSignedUploadUrl (key: string, options?: SignedURLOptions): Promise<string> {\n return this.driver.use().getSignedUploadUrl(key, options)\n }\n /**\n * Update the visibility of the file\n */\n setVisibility (key: string, visibility: ObjectVisibility): Promise<void> {\n return this.driver.use().setVisibility(key, visibility)\n }\n /**\n * Write object to the destination with the provided\n * contents.\n */\n put (key: string, contents: string | Uint8Array | FileLike, options?: WriteOptions): Promise<void> {\n if (!(contents instanceof Uint8Array) && typeof contents !== 'string') {\n contents = contents.buffer\n }\n\n return this.driver.use().put(key, contents, options)\n }\n /**\n * Write object to the destination with the provided\n * contents as a readable stream\n */\n putStream (key: string, contents: Readable, options?: WriteOptions): Promise<void> {\n return this.driver.use().putStream(key, contents, options)\n }\n /**\n * Copy the file from within the disk root location. Both\n * the \"source\" and \"destination\" will be the key names\n * and not absolute paths.\n */\n copy (source: string, destination: string, options?: WriteOptions): Promise<void> {\n return this.driver.use().copy(source, destination, options)\n }\n /**\n * Move the file from within the disk root location. Both\n * the \"source\" and \"destination\" will be the key names\n * and not absolute paths.\n */\n move (source: string, destination: string, options?: WriteOptions): Promise<void> {\n return this.driver.use().move(source, destination, options)\n }\n /**\n * Delete the file for the given key. Should not throw\n * error when file does not exist in first place\n */\n delete (key: string): Promise<void> {\n return this.driver.use().delete(key)\n }\n /**\n * Delete the files and directories matching the provided prefix.\n */\n deleteAll (prefix: string): Promise<void> {\n return this.driver.use().deleteAll(prefix)\n }\n /**\n * The list all method must return an array of objects with\n * the ability to paginate results (if supported).\n */\n listAll (prefix: string, options?: {\n recursive?: boolean;\n paginationToken?: string;\n }): Promise<{\n paginationToken?: string;\n objects: Iterable<DriveFile | DriveDirectory>;\n }> {\n return this.driver.use().listAll(prefix, options)\n }\n /**\n * Switch bucket at runtime if supported.\n */\n bucket (bucket: string): DriverContract {\n return (this.driver.use() as any).bucket(bucket)\n }\n\n /**\n * Create symbolic links for all configured links in the application configuration.\n */\n static link ({ force = false }: { force?: boolean } = {}): void {\n for (const link in config('filesystem.links')) {\n const target = config('filesystem.links')[link]\n\n const unlink = link.replace(process.cwd(), '')\n const untarget = target.replace(process.cwd(), '')\n\n try {\n if (force) rmSync(link, { recursive: true, force: true })\n symlinkSync(target, link)\n\n Logger.log([\n [' SUCCESS ', 'bgGreen'],\n [`[${unlink}]`, 'green'],\n ['is now linked to', 'white'],\n [`[${untarget}].`, 'green']\n ], ' ')\n } catch (error: any) {\n if (error.code === 'EEXIST') {\n Logger.log([\n [' INFO ', 'bgBlue'],\n [`[${unlink}]`, 'green'],\n ['is already linked to', 'white'],\n [`[${untarget}].`, 'green']\n ], ' ')\n } else {\n Logger.log([\n [' ERROR ', 'bgRed'],\n ['Failed to create symbolic link from', 'white'],\n [`[${unlink}]`, 'green'],\n ['to', 'white'],\n [`[${untarget}]`, 'green'],\n [error.message, 'red']\n ], ' ')\n }\n }\n }\n }\n\n private driversMap: Record<string, (conf: Record<string, any>) => DriverContract> = {\n local: (conf: Record<string, any>) => new FSDriver({\n location: new URL(conf.root, import.meta.url),\n visibility: 'public',\n urlBuilder: {\n async generateURL (key: string, _path: string) {\n return appUrl(key)\n },\n\n async generateSignedURL (key: string, _path: string, _opts: SignedURLOptions) {\n return appUrl(key)\n },\n },\n }),\n s3: (conf: Record<string, any>) => new S3Driver({\n credentials: {\n accessKeyId: conf.key,\n secretAccessKey: conf.secret,\n },\n endpoint: conf.endpoint,\n region: conf.region,\n bucket: conf.bucket,\n visibility: 'private',\n cdnUrl: conf.url,\n }),\n ftp: (conf: Record<string, any>) => new FtpDriver({\n host: conf.host,\n username: conf.username,\n password: conf.password,\n port: conf.port,\n verbose: conf.verbose,\n privateKey: conf.privateKey,\n }),\n }\n}"],"mappings":"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"}