@arkstack/filesystem 0.12.0 → 0.12.1

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,69 @@
1
1
  # @arkstack/filesystem
2
2
 
3
3
  Filesystem module for Arkstack, providing shared file storage and filesystem utitlities for the framework.
4
+
5
+ ## Custom Drivers
6
+
7
+ @arkstack/filesystem allows you to configure and use custom storage drivers.
8
+
9
+ **CloudinaryFileDriver.ts**
10
+
11
+ ```ts
12
+ import type { DriverContract, ObjectVisibility } from 'flydrive/types';
13
+ import type { CustomDiskConfig } from '@arkstack/filesystem';
14
+
15
+ export class CloudinaryFileDriver implements DriverContract {
16
+ constructor(private config?: CustomDiskConfig) {}
17
+ async exists(key: string) {}
18
+ async get(key: string) {}
19
+ async getStream(key: string) {}
20
+ async getBytes(key: string) {}
21
+ async getMetaData(key: string) {}
22
+ async getVisibility(): Promise<ObjectVisibility> {}
23
+ async getUrl(key: string) {}
24
+ async getSignedUrl(key: string) {}
25
+ async getSignedUploadUrl(key: string) {}
26
+ async setVisibility() {}
27
+ async put() {}
28
+ async putStream() {}
29
+ async copy() {}
30
+ async move() {}
31
+ async delete() {}
32
+ async deleteAll() {}
33
+ async listAll() {}
34
+ async bucket() {}
35
+ }
36
+ ```
37
+
38
+ **src/config/filesystem.ts**
39
+
40
+ ```ts
41
+ import { CloudinaryFileDriver } from '../CloudinaryFileDriver';
42
+ export default () => {
43
+ return {
44
+ default: 'memory',
45
+ disks: {
46
+ images: {
47
+ //...Other Disks Here
48
+ driver: 'cloudinary',
49
+ },
50
+ },
51
+ links: {},
52
+ custom_drivers: {
53
+ cloudinary: CloudinaryFileDriver,
54
+ },
55
+ };
56
+ };
57
+ ```
58
+
59
+ To improve type safety and auto complete, you may augment the `CustomDiskDriverRegistry`
60
+
61
+ **env.d.ts**
62
+
63
+ ```ts
64
+ declare module '@arkstack/filesystem' {
65
+ interface CustomDiskDriverRegistry {
66
+ cloudinary: { cloud_name: string; api_key: string; api_secret: string };
67
+ }
68
+ }
69
+ ```
@@ -1,4 +1,4 @@
1
- import { t as Storage } from "../src-MCtnsHF4.js";
1
+ import { t as Storage } from "../src-Sdprva2c.js";
2
2
  import { Command } from "@h3ravel/musket";
3
3
  //#region src/commands/StorageLinkCommand.ts
4
4
  var StorageLinkCommand = class extends Command {
package/dist/index.d.ts CHANGED
@@ -2,12 +2,80 @@ 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
4
 
5
- //#region src/index.d.ts
5
+ //#region src/types.d.ts
6
6
  interface FileLike {
7
7
  originalname: string;
8
8
  buffer: Buffer;
9
9
  mimetype: string;
10
10
  }
11
+ interface CustomDiskDriverRegistry {}
12
+ interface FtpDriverConfig {
13
+ host: string;
14
+ username: string;
15
+ password: string;
16
+ port?: number;
17
+ verbose?: boolean | undefined;
18
+ privateKey?: string | undefined;
19
+ }
20
+ interface S3DriverConfig {
21
+ credentials?: {
22
+ accessKeyId: string;
23
+ secretAccessKey: string;
24
+ sessionToken?: string | undefined;
25
+ credentialScope?: string | undefined;
26
+ accountId?: string | undefined;
27
+ };
28
+ url?: string;
29
+ key?: string;
30
+ secret?: string;
31
+ endpoint?: string;
32
+ region?: string;
33
+ bucket: string;
34
+ visibility: ObjectVisibility;
35
+ cdnUrl?: string;
36
+ }
37
+ interface LocalDriverConfig {
38
+ root?: string;
39
+ location?: string | URL;
40
+ visibility: ObjectVisibility;
41
+ }
42
+ type CustomDiskConfig = keyof CustomDiskDriverRegistry extends never ? {
43
+ driver: string;
44
+ [key: string]: any;
45
+ } : { [K in keyof CustomDiskDriverRegistry]: CustomDiskDriverRegistry[K] & {
46
+ driver: K;
47
+ } }[keyof CustomDiskDriverRegistry];
48
+ type DiskConfig = LocalDriverConfig & {
49
+ driver: 'local';
50
+ } | FtpDriverConfig & {
51
+ driver: 'ftp';
52
+ } | S3DriverConfig & {
53
+ driver: 's3';
54
+ } | CustomDiskConfig;
55
+ type DriverConfig<K extends 'ftp' | 'local' | 's3' | (string & {}) = string & {}> = K extends 'ftp' ? FtpDriverConfig : K extends 's3' ? S3DriverConfig : K extends 'local' ? LocalDriverConfig : K extends keyof CustomDiskDriverRegistry ? CustomDiskDriverRegistry[K] : DiskConfig;
56
+ type KnownDisks = {
57
+ local: LocalDriverConfig & {
58
+ driver: 'local';
59
+ };
60
+ public: LocalDriverConfig & {
61
+ driver: 'local';
62
+ };
63
+ ftp: FtpDriverConfig & {
64
+ driver: 'ftp';
65
+ };
66
+ s3: S3DriverConfig & {
67
+ driver: 's3';
68
+ };
69
+ };
70
+ interface FilesystemConfig {
71
+ default: 'local' | 'ftp' | 's3' | keyof CustomDiskDriverRegistry | (string & {});
72
+ disks: KnownDisks & CustomDiskDriverRegistry;
73
+ links: Record<string, string>;
74
+ custom_drivers?: Record<keyof CustomDiskDriverRegistry | (string & {}), DriverContract | (new (config?: CustomDiskConfig) => DriverContract)>;
75
+ fileNameGenerator?: (originalName: string) => string;
76
+ }
77
+ //#endregion
78
+ //#region src/Storage.d.ts
11
79
  declare class Storage implements DriverContract {
12
80
  driver: DriveManager<any>;
13
81
  services: Record<string, () => DriverContract>;
@@ -49,8 +117,61 @@ declare class Storage implements DriverContract {
49
117
  }?: {
50
118
  force?: boolean;
51
119
  }): void;
52
- private driversMap;
53
120
  }
54
121
  //#endregion
55
- export { Storage };
122
+ //#region src/FtpDriver.d.ts
123
+ declare class FtpDriver implements DriverContract {
124
+ private config;
125
+ constructor(config: string | {
126
+ host: string;
127
+ username: string;
128
+ password: string;
129
+ port?: number;
130
+ verbose?: boolean;
131
+ privateKey?: string;
132
+ });
133
+ getConfig(): {
134
+ host: string;
135
+ username: string;
136
+ password: string;
137
+ port?: number;
138
+ verbose?: boolean;
139
+ privateKey?: string;
140
+ };
141
+ private init;
142
+ private load;
143
+ exists(key: string): Promise<boolean>;
144
+ get(key: string): Promise<string>;
145
+ getStream(key: string): Promise<Readable>;
146
+ getBytes(key: string): Promise<Uint8Array>;
147
+ getMetaData(key: string): Promise<ObjectMetaData>;
148
+ getVisibility(key: string): Promise<ObjectVisibility>;
149
+ getUrl(key: string): Promise<string>;
150
+ getSignedUrl(key: string, options?: SignedURLOptions): Promise<string>;
151
+ getSignedUploadUrl(key: string, options?: SignedURLOptions): Promise<string>;
152
+ setVisibility(key: string, visibility: ObjectVisibility): Promise<void>;
153
+ put(key: string, contents: string | Uint8Array, options?: WriteOptions): Promise<void>;
154
+ putStream(key: string, contents: Readable, options?: WriteOptions): Promise<void>;
155
+ copy(source: string, destination: string, options?: WriteOptions): Promise<void>;
156
+ move(source: string, destination: string, options?: WriteOptions): Promise<void>;
157
+ delete(key: string): Promise<void>;
158
+ deleteAll(prefix: string): Promise<void>;
159
+ bucket(config: string | {
160
+ host: string;
161
+ username: string;
162
+ password: string;
163
+ port?: number;
164
+ verbose?: boolean;
165
+ privateKey?: string;
166
+ }): DriverContract;
167
+ listAll(prefix: string, options?: {
168
+ recursive?: boolean;
169
+ paginationToken?: string;
170
+ }): Promise<{
171
+ paginationToken?: string;
172
+ objects: Iterable<DriveFile | DriveDirectory>;
173
+ }>;
174
+ }
175
+ //#endregion
176
+ export { CustomDiskConfig, CustomDiskDriverRegistry, DiskConfig, DriverConfig, FileLike, FilesystemConfig, FtpDriver, FtpDriverConfig, KnownDisks, LocalDriverConfig, S3DriverConfig, Storage };
56
177
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { t as Storage } from "./src-MCtnsHF4.js";
2
- export { Storage };
1
+ import { n as FtpDriver, t as Storage } from "./src-Sdprva2c.js";
2
+ export { FtpDriver, Storage };
@@ -1,13 +1,13 @@
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";
4
3
  import { Arkstack } from "@arkstack/contract";
5
4
  import { FSDriver } from "flydrive/drivers/fs";
6
5
  import Client from "ssh2-sftp-client";
7
6
  import { Readable } from "node:stream";
8
7
  import path from "node:path";
9
- import { Logger } from "@h3ravel/shared";
10
8
  import { S3Driver } from "flydrive/drivers/s3";
9
+ import { appUrl, config } from "@arkstack/common";
10
+ import { Logger } from "@h3ravel/shared";
11
11
  //#region src/FtpDriver.ts
12
12
  var FtpDriver = class FtpDriver {
13
13
  config;
@@ -236,18 +236,101 @@ var FtpDriver = class FtpDriver {
236
236
  }
237
237
  };
238
238
  //#endregion
239
- //#region src/index.ts
239
+ //#region src/Driver.ts
240
+ var Driver = class Driver {
241
+ config;
242
+ static customDrivers = /* @__PURE__ */ new Map();
243
+ constructor(config) {
244
+ this.config = config;
245
+ }
246
+ static make(name, config) {
247
+ if (![
248
+ "local",
249
+ "ftp",
250
+ "s3"
251
+ ].includes(name) && !this.customDrivers.has(name)) throw new Error(`Unsupported driver: ${name}`);
252
+ const driver = new Driver(config);
253
+ if (this.customDrivers.has(name)) return driver.custom(name);
254
+ return driver["ftp"].apply(this);
255
+ }
256
+ local() {
257
+ const config = this.config;
258
+ return new FSDriver({
259
+ location: config.location ?? new URL(config.root, import.meta.url),
260
+ visibility: config.visibility ?? "public",
261
+ urlBuilder: {
262
+ async generateURL(key, _path) {
263
+ return appUrl(key);
264
+ },
265
+ async generateSignedURL(key, _path, _opts) {
266
+ return appUrl(key);
267
+ }
268
+ }
269
+ });
270
+ }
271
+ s3() {
272
+ const config = this.config;
273
+ return new S3Driver({
274
+ credentials: config.credentials ?? {
275
+ accessKeyId: config.key,
276
+ secretAccessKey: config.secret
277
+ },
278
+ endpoint: config.endpoint,
279
+ region: config.region,
280
+ bucket: config.bucket,
281
+ visibility: "private",
282
+ cdnUrl: config.cdnUrl ?? config.url
283
+ });
284
+ }
285
+ ftp() {
286
+ const config = this.config;
287
+ return new FtpDriver({
288
+ host: config.host,
289
+ username: config.username,
290
+ password: config.password,
291
+ port: config.port,
292
+ verbose: config.verbose,
293
+ privateKey: config.privateKey
294
+ });
295
+ }
296
+ custom(name) {
297
+ if (!Driver.customDrivers.has(name)) throw new Error(`Unsupported driver: ${name} has not been registered`);
298
+ const DriverInstance = Driver.customDrivers.get(name);
299
+ if (typeof DriverInstance === "function") {
300
+ const config = this.config;
301
+ return new DriverInstance(config);
302
+ }
303
+ return DriverInstance;
304
+ }
305
+ /**
306
+ * Register a new custom driver
307
+ *
308
+ * @param name
309
+ * @param driver
310
+ */
311
+ static registerDriver(name, driver) {
312
+ Driver.customDrivers.set(name, driver);
313
+ }
314
+ /**
315
+ * Unregister a new custom driver
316
+ *
317
+ * @param name
318
+ */
319
+ static removeDriver(name) {
320
+ Driver.customDrivers.delete(name);
321
+ }
322
+ };
323
+ //#endregion
324
+ //#region src/Storage.ts
240
325
  var Storage = class Storage {
241
326
  driver;
242
327
  services = {};
243
328
  diskName;
244
329
  constructor() {
245
- for (const diskName in config("filesystem.disks")) {
246
- const diskConfig = config("filesystem.disks")[diskName];
247
- const driverFactory = this.driversMap[diskConfig.driver];
248
- if (!driverFactory) throw new Error(`Unsupported driver: ${diskConfig.driver}`);
249
- this.services[diskName] = () => driverFactory(diskConfig);
250
- }
330
+ const disks = Object.entries(config("filesystem.disks", {}));
331
+ const customDrivers = config("filesystem.custom_drivers", {});
332
+ for (const [name, driver] of Object.entries(customDrivers)) Driver.registerDriver(name, driver);
333
+ for (const [disk, conf] of disks) this.services[disk] = () => Driver.make(disk, conf);
251
334
  this.diskName = config("filesystem.default");
252
335
  this.driver = new DriveManager({
253
336
  default: config("filesystem.default"),
@@ -471,41 +554,8 @@ var Storage = class Storage {
471
554
  }
472
555
  }
473
556
  }
474
- driversMap = {
475
- local: (conf) => new FSDriver({
476
- location: new URL(conf.root, import.meta.url),
477
- visibility: "public",
478
- urlBuilder: {
479
- async generateURL(key, _path) {
480
- return appUrl(key);
481
- },
482
- async generateSignedURL(key, _path, _opts) {
483
- return appUrl(key);
484
- }
485
- }
486
- }),
487
- s3: (conf) => new S3Driver({
488
- credentials: {
489
- accessKeyId: conf.key,
490
- secretAccessKey: conf.secret
491
- },
492
- endpoint: conf.endpoint,
493
- region: conf.region,
494
- bucket: conf.bucket,
495
- visibility: "private",
496
- cdnUrl: conf.url
497
- }),
498
- ftp: (conf) => new FtpDriver({
499
- host: conf.host,
500
- username: conf.username,
501
- password: conf.password,
502
- port: conf.port,
503
- verbose: conf.verbose,
504
- privateKey: conf.privateKey
505
- })
506
- };
507
557
  };
508
558
  //#endregion
509
- export { Storage as t };
559
+ export { FtpDriver as n, Storage as t };
510
560
 
511
- //# sourceMappingURL=src-MCtnsHF4.js.map
561
+ //# sourceMappingURL=src-Sdprva2c.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"src-Sdprva2c.js","names":[],"sources":["../src/FtpDriver.ts","../src/Driver.ts","../src/Storage.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 { CustomDiskConfig, CustomDiskDriverRegistry, DriverConfig, FtpDriverConfig, LocalDriverConfig, S3DriverConfig } from './types'\nimport { DriverContract, SignedURLOptions } from 'flydrive/types'\n\nimport { FSDriver } from 'flydrive/drivers/fs'\nimport { FtpDriver } from './FtpDriver'\nimport { S3Driver } from 'flydrive/drivers/s3'\nimport { appUrl } from '@arkstack/common'\n\ntype BuiltInDriverMap = { [K in keyof Driver]: Driver[K] }\n\ntype DriverFor<K extends string> = K extends keyof BuiltInDriverMap\n ? ReturnType<BuiltInDriverMap[K]>\n : ReturnType<BuiltInDriverMap['custom']>\n\nexport class Driver {\n private static customDrivers = new Map<\n keyof CustomDiskDriverRegistry | (string & {}),\n DriverContract | (new (config?: CustomDiskConfig) => DriverContract)\n >()\n\n constructor(private config: DriverConfig) { }\n\n static make<K extends 'local' | 'ftp' | 's3' | (string & {})> (\n name: K,\n config: DriverConfig<K>\n ): DriverFor<K> {\n if (!['local', 'ftp', 's3'].includes(name) && !this.customDrivers.has(name)) {\n throw new Error(`Unsupported driver: ${name}`)\n }\n\n const driver = new Driver(config as never)\n\n if (this.customDrivers.has(name)) {\n return driver.custom(name) as DriverFor<K>\n }\n\n return driver['ftp'].apply(this) as DriverFor<K>\n\n }\n\n local () {\n const config = this.config as LocalDriverConfig\n\n return new FSDriver({\n location: config.location ?? new URL(config.root!, import.meta.url),\n visibility: config.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 }\n\n s3 () {\n const config = this.config as S3DriverConfig\n\n return new S3Driver({\n credentials: config.credentials ?? {\n accessKeyId: config.key!,\n secretAccessKey: config.secret!,\n },\n endpoint: config.endpoint,\n region: config.region,\n bucket: config.bucket,\n visibility: 'private',\n cdnUrl: config.cdnUrl ?? config.url,\n })\n }\n\n ftp () {\n const config = this.config as FtpDriverConfig\n\n return new FtpDriver({\n host: config.host,\n username: config.username,\n password: config.password,\n port: config.port,\n verbose: config.verbose,\n privateKey: config.privateKey,\n })\n }\n\n custom (name: string): DriverContract {\n if (!Driver.customDrivers.has(name)) {\n throw new Error(`Unsupported driver: ${name} has not been registered`)\n }\n\n const DriverInstance = Driver.customDrivers.get(name)!\n\n if (typeof DriverInstance === 'function') {\n const config = this.config as CustomDiskConfig\n\n return new DriverInstance(config)\n }\n\n return DriverInstance\n }\n\n /**\n * Register a new custom driver\n * \n * @param name \n * @param driver \n */\n static registerDriver (name: string, driver: DriverContract) {\n Driver.customDrivers.set(name, driver)\n }\n\n /**\n * Unregister a new custom driver\n * \n * @param name \n */\n static removeDriver (name: string) {\n Driver.customDrivers.delete(name)\n }\n}","import { DriveDirectory, DriveFile, DriveManager } from 'flydrive'\nimport { DriverConfig, FileLike } from './types'\nimport { DriverContract, ObjectMetaData, ObjectVisibility, SignedURLOptions, WriteOptions } from 'flydrive/types'\nimport { rmSync, symlinkSync } from 'node:fs'\n\nimport { Arkstack } from '@arkstack/contract'\nimport { Driver } from './Driver'\nimport { Logger } from '@h3ravel/shared'\nimport { Readable } from 'node:stream'\nimport { config } from '@arkstack/common'\nimport path from 'node:path'\n\nexport class Storage implements DriverContract {\n driver: DriveManager<any>\n services: Record<string, () => DriverContract> = {}\n diskName: string\n\n constructor() {\n const disks = Object.entries(config('filesystem.disks', {})) as Array<[string, DriverConfig]>\n const customDrivers = config('filesystem.custom_drivers', {}) as Record<string, DriverContract>\n\n for (const [name, driver] of Object.entries(customDrivers)) {\n Driver.registerDriver(name, driver)\n }\n\n for (const [disk, conf] of disks) {\n this.services[disk] = () => Driver.make(disk, conf)\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(Arkstack.rootDir(), '')\n const untarget = target.replace(Arkstack.rootDir(), '')\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}"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkstack/filesystem",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
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",
@@ -39,8 +39,8 @@
39
39
  "@aws-sdk/s3-request-presigner": "^3.1011.0",
40
40
  "flydrive": "^2.0.0",
41
41
  "ssh2-sftp-client": "^12.1.0",
42
- "@arkstack/common": "^0.12.0",
43
- "@arkstack/contract": "^0.12.0"
42
+ "@arkstack/common": "^0.12.1",
43
+ "@arkstack/contract": "^0.12.1"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/ssh2-sftp-client": "^9.0.6"
@@ -1 +0,0 @@
1
- {"version":3,"file":"src-MCtnsHF4.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 { Arkstack } from '@arkstack/contract'\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(Arkstack.rootDir(), '')\n const untarget = target.replace(Arkstack.rootDir(), '')\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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"}