@arkstack/filesystem 0.4.3 → 0.5.0

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,3 @@
1
1
  # @arkstack/filesystem
2
2
 
3
- Shared Filesystem utilities for ArkStack.
3
+ Filesystem module for Arkstack, providing shared file storage and filesystem utitlities for the framework.
@@ -1,7 +1,5 @@
1
- import "../FtpDriver-CfUSZ1xr.js";
2
- import { Storage } from "../index.js";
1
+ import { t as Storage } from "../src-wGEPrkk5.js";
3
2
  import { Command } from "@h3ravel/musket";
4
-
5
3
  //#region src/commands/StorageLinkCommand.ts
6
4
  var StorageLinkCommand = class extends Command {
7
5
  signature = `storage:link
@@ -12,7 +10,7 @@ var StorageLinkCommand = class extends Command {
12
10
  Storage.link(this.options());
13
11
  }
14
12
  };
15
-
16
13
  //#endregion
17
14
  export { StorageLinkCommand };
15
+
18
16
  //# sourceMappingURL=StorageLinkCommand.js.map
@@ -1 +1 @@
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,AAAU,YAAY;;;CAGtB,AAAU,cAAc;CAExB,MAAM,SAAU;AACZ,UAAQ,KAAK,KAAK,SAAS,CAAC"}
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"}
package/dist/index.js CHANGED
@@ -1,282 +1,2 @@
1
- import { t as FtpDriver } from "./FtpDriver-CfUSZ1xr.js";
2
- import { DriveManager } from "flydrive";
3
- import { appUrl, config } from "@arkstack/common";
4
- import { rmSync, symlinkSync } from "node:fs";
5
- import { FSDriver } from "flydrive/drivers/fs";
6
- import path from "node:path";
7
- import { Logger } from "@h3ravel/shared";
8
- import { S3Driver } from "flydrive/drivers/s3";
9
-
10
- //#region src/index.ts
11
- var Storage = class Storage {
12
- driver;
13
- services = {};
14
- diskName;
15
- constructor() {
16
- for (const diskName in config("filesystem.disks")) {
17
- const diskConfig = config("filesystem.disks")[diskName];
18
- const driverFactory = this.driversMap[diskConfig.driver];
19
- if (!driverFactory) throw new Error(`Unsupported driver: ${diskConfig.driver}`);
20
- this.services[diskName] = () => driverFactory(diskConfig);
21
- }
22
- this.diskName = config("filesystem.default");
23
- this.driver = new DriveManager({
24
- default: config("filesystem.default"),
25
- services: this.services
26
- });
27
- }
28
- /**
29
- * Static method to get a disk instance directly from the Storage class without needing to instantiate it first.
30
- *
31
- * @param diskName The name of the disk to use. If not provided, the default disk will be used.
32
- * @returns A Storage instance
33
- */
34
- static disk(diskName) {
35
- const storage = new Storage();
36
- if (diskName) {
37
- storage.diskName = diskName;
38
- storage.driver = new DriveManager({
39
- default: diskName,
40
- services: storage.services
41
- });
42
- }
43
- return storage;
44
- }
45
- /**
46
- * Generate a unique name for the file based on random numbers and original extension
47
- *
48
- * @param file The file object containing the original name
49
- * @returns A unique file name
50
- */
51
- static generateName = (file) => {
52
- const name = file.originalname || file.name || "file";
53
- if (typeof config("filesystem.fileNameGenerator") === "function") return config("filesystem.fileNameGenerator")(name);
54
- return Math.floor(Math.random() * 999999999999).toString() + "_" + Math.floor(Math.random() * 999999999999) + "." + name.split(".").pop();
55
- };
56
- /**
57
- * Save the file to the storage and return the public URL and the file path
58
- *
59
- * @param file The file object containing the file data
60
- * @param filePath The path where the file should be saved
61
- * @param fileName The name to save the file as (optional)
62
- * @returns A tuple containing the public URL and the file path
63
- */
64
- static saveFile = async (file, filePath = "", fileName) => {
65
- return new Storage().saveFile(file, filePath, fileName);
66
- };
67
- /**
68
- * Save the file to the storage and return the public URL and the file path
69
- *
70
- * @param file The file object containing the file data
71
- * @param filePath The path where the file should be saved
72
- * @param fileName The name to save the file as (optional)
73
- * @returns A tuple containing the public URL and the file path
74
- */
75
- saveFile = async (file, filePath = "", fileName) => {
76
- const name = fileName || Storage.generateName(file);
77
- const drive = this.driver.use();
78
- if (file instanceof File && !file.buffer) file.buffer = Buffer.from(await file.arrayBuffer());
79
- await drive.put(path.join(filePath, name), file.buffer);
80
- const url = await drive.getUrl(path.join(filePath, name));
81
- return [url, this.diskName === "local" ? path.join(filePath, name) : url];
82
- };
83
- /**
84
- * Return a boolean indicating if the file exists
85
- */
86
- exists(key) {
87
- return this.driver.use().exists(key);
88
- }
89
- /**
90
- * Return contents of a object for the given key as a UTF-8 string.
91
- * Should throw "E_CANNOT_READ_FILE" error when the file
92
- * does not exists.
93
- */
94
- get(key) {
95
- return this.driver.use().get(key);
96
- }
97
- /**
98
- * Return contents of a object for the given key as a Readable stream.
99
- * Should throw "E_CANNOT_READ_FILE" error when the file
100
- * does not exists.
101
- */
102
- getStream(key) {
103
- return this.driver.use().getStream(key);
104
- }
105
- /**
106
- * Return contents of an object for the given key as an Uint8Array.
107
- * Should throw "E_CANNOT_READ_FILE" error when the file
108
- * does not exists.
109
- */
110
- getBytes(key) {
111
- return this.driver.use().getBytes(key);
112
- }
113
- /**
114
- * Return metadata of an object for the given key.
115
- */
116
- getMetaData(key) {
117
- return this.driver.use().getMetaData(key);
118
- }
119
- /**
120
- * Return the visibility of the file
121
- */
122
- getVisibility(key) {
123
- return this.driver.use().getVisibility(key);
124
- }
125
- /**
126
- * Return the public URL to access the file
127
- */
128
- getUrl(key) {
129
- return this.driver.use().getUrl(key);
130
- }
131
- /**
132
- * Return the signed/temporary URL to access the file
133
- */
134
- getSignedUrl(key, options) {
135
- return this.driver.use().getSignedUrl(key, options);
136
- }
137
- /**
138
- * Return the signed/temporary URL that can be used to directly upload
139
- * the file contents to the storage.
140
- */
141
- getSignedUploadUrl(key, options) {
142
- return this.driver.use().getSignedUploadUrl(key, options);
143
- }
144
- /**
145
- * Update the visibility of the file
146
- */
147
- setVisibility(key, visibility) {
148
- return this.driver.use().setVisibility(key, visibility);
149
- }
150
- /**
151
- * Write object to the destination with the provided
152
- * contents.
153
- */
154
- put(key, contents, options) {
155
- if (!(contents instanceof Uint8Array) && typeof contents !== "string") contents = contents.buffer;
156
- return this.driver.use().put(key, contents, options);
157
- }
158
- /**
159
- * Write object to the destination with the provided
160
- * contents as a readable stream
161
- */
162
- putStream(key, contents, options) {
163
- return this.driver.use().putStream(key, contents, options);
164
- }
165
- /**
166
- * Copy the file from within the disk root location. Both
167
- * the "source" and "destination" will be the key names
168
- * and not absolute paths.
169
- */
170
- copy(source, destination, options) {
171
- return this.driver.use().copy(source, destination, options);
172
- }
173
- /**
174
- * Move the file from within the disk root location. Both
175
- * the "source" and "destination" will be the key names
176
- * and not absolute paths.
177
- */
178
- move(source, destination, options) {
179
- return this.driver.use().move(source, destination, options);
180
- }
181
- /**
182
- * Delete the file for the given key. Should not throw
183
- * error when file does not exist in first place
184
- */
185
- delete(key) {
186
- return this.driver.use().delete(key);
187
- }
188
- /**
189
- * Delete the files and directories matching the provided prefix.
190
- */
191
- deleteAll(prefix) {
192
- return this.driver.use().deleteAll(prefix);
193
- }
194
- /**
195
- * The list all method must return an array of objects with
196
- * the ability to paginate results (if supported).
197
- */
198
- listAll(prefix, options) {
199
- return this.driver.use().listAll(prefix, options);
200
- }
201
- /**
202
- * Switch bucket at runtime if supported.
203
- */
204
- bucket(bucket) {
205
- return this.driver.use().bucket(bucket);
206
- }
207
- /**
208
- * Create symbolic links for all configured links in the application configuration.
209
- */
210
- static link({ force = false } = {}) {
211
- for (const link in config("filesystem.links")) {
212
- const target = config("filesystem.links")[link];
213
- const unlink = link.replace(process.cwd(), "");
214
- const untarget = target.replace(process.cwd(), "");
215
- try {
216
- if (force) rmSync(link, {
217
- recursive: true,
218
- force: true
219
- });
220
- symlinkSync(target, link);
221
- Logger.log([
222
- [" SUCCESS ", "bgGreen"],
223
- [`[${unlink}]`, "green"],
224
- ["is now linked to", "white"],
225
- [`[${untarget}].`, "green"]
226
- ], " ");
227
- } catch (error) {
228
- if (error.code === "EEXIST") Logger.log([
229
- [" INFO ", "bgBlue"],
230
- [`[${unlink}]`, "green"],
231
- ["is already linked to", "white"],
232
- [`[${untarget}].`, "green"]
233
- ], " ");
234
- else Logger.log([
235
- [" ERROR ", "bgRed"],
236
- ["Failed to create symbolic link from", "white"],
237
- [`[${unlink}]`, "green"],
238
- ["to", "white"],
239
- [`[${untarget}]`, "green"],
240
- [error.message, "red"]
241
- ], " ");
242
- }
243
- }
244
- }
245
- driversMap = {
246
- local: (conf) => new FSDriver({
247
- location: new URL(conf.root, import.meta.url),
248
- visibility: "public",
249
- urlBuilder: {
250
- async generateURL(key, _path) {
251
- return appUrl(key);
252
- },
253
- async generateSignedURL(key, _path, _opts) {
254
- return appUrl(key);
255
- }
256
- }
257
- }),
258
- s3: (conf) => new S3Driver({
259
- credentials: {
260
- accessKeyId: conf.key,
261
- secretAccessKey: conf.secret
262
- },
263
- endpoint: conf.endpoint,
264
- region: conf.region,
265
- bucket: conf.bucket,
266
- visibility: "private",
267
- cdnUrl: conf.url
268
- }),
269
- ftp: (conf) => new FtpDriver({
270
- host: conf.host,
271
- username: conf.username,
272
- password: conf.password,
273
- port: conf.port,
274
- verbose: conf.verbose,
275
- privateKey: conf.privateKey
276
- })
277
- };
278
- };
279
-
280
- //#endregion
1
+ import { t as Storage } from "./src-wGEPrkk5.js";
281
2
  export { Storage };
282
- //# sourceMappingURL=index.js.map
@@ -0,0 +1,510 @@
1
+ import { DriveDirectory, DriveFile, DriveManager } from "flydrive";
2
+ import { appUrl, config } from "@arkstack/common";
3
+ import { createReadStream, createWriteStream, rmSync, symlinkSync } from "node:fs";
4
+ import { FSDriver } from "flydrive/drivers/fs";
5
+ import Client from "ssh2-sftp-client";
6
+ import { Readable } from "node:stream";
7
+ import path from "node:path";
8
+ import { Logger } from "@h3ravel/shared";
9
+ import { S3Driver } from "flydrive/drivers/s3";
10
+ //#region src/FtpDriver.ts
11
+ var FtpDriver = class FtpDriver {
12
+ config;
13
+ constructor(config) {
14
+ if (typeof config === "string") {
15
+ const url = new URL(config);
16
+ this.config = {
17
+ host: url.hostname,
18
+ username: url.username,
19
+ password: url.password,
20
+ port: url.port ? parseInt(url.port, 10) : 22,
21
+ verbose: url.searchParams.get("verbose") === "true"
22
+ };
23
+ } else this.config = config;
24
+ }
25
+ getConfig() {
26
+ return this.config;
27
+ }
28
+ async init() {
29
+ const client = new Client();
30
+ await client.connect({
31
+ host: this.config.host,
32
+ username: this.config.username,
33
+ password: this.config.password,
34
+ port: this.config.port || 22
35
+ });
36
+ return client;
37
+ }
38
+ async load(handle) {
39
+ const client = await this.init();
40
+ try {
41
+ return await handle(client);
42
+ } catch (e) {
43
+ if (this.config.verbose) throw e;
44
+ } finally {
45
+ await client.end();
46
+ }
47
+ return null;
48
+ }
49
+ /**
50
+ * Return a boolean value indicating if the file exists
51
+ * or not.
52
+ */
53
+ async exists(key) {
54
+ return !!await this.load((client) => {
55
+ return client.stat(key);
56
+ }).catch(() => {
57
+ return null;
58
+ });
59
+ }
60
+ /**
61
+ * Return the file contents as a UTF-8 string. Throw an exception
62
+ * if the file is missing.
63
+ */
64
+ async get(key) {
65
+ const stream = await this.getStream(key);
66
+ const chunks = [];
67
+ for await (const chunk of stream) chunks.push(chunk);
68
+ return Buffer.concat(chunks).toString("utf-8");
69
+ }
70
+ /**
71
+ * Return the file contents as a Readable stream. Throw an exception
72
+ * if the file is missing.
73
+ */
74
+ async getStream(key) {
75
+ const dst = createWriteStream("/tmp/" + path.basename(key));
76
+ await this.load((client) => {
77
+ return client.get(key, dst);
78
+ }).catch(() => {
79
+ return null;
80
+ });
81
+ return createReadStream("/tmp/" + path.basename(key));
82
+ }
83
+ /**
84
+ * Return the file contents as a Uint8Array. Throw an exception
85
+ * if the file is missing.
86
+ */
87
+ async getBytes(key) {
88
+ const content = await this.get(key);
89
+ return new Uint8Array(Buffer.from(content, "utf-8"));
90
+ }
91
+ /**
92
+ * Return metadata of the file. Throw an exception
93
+ * if the file is missing.
94
+ */
95
+ async getMetaData(key) {
96
+ const stat = await this.load((client) => {
97
+ return client.stat(key);
98
+ }).catch(() => {
99
+ return null;
100
+ });
101
+ if (!stat) throw new Error("E_CANNOT_READ_FILE");
102
+ return {
103
+ contentType: void 0,
104
+ contentLength: stat.size,
105
+ etag: key,
106
+ lastModified: new Date(stat.modifyTime)
107
+ };
108
+ }
109
+ /**
110
+ * Return visibility of the file. Infer visibility from the initial
111
+ * config, when the driver does not support the concept of visibility.
112
+ */
113
+ async getVisibility(key) {
114
+ return "private";
115
+ }
116
+ /**
117
+ * Return the public URL of the file. Throw an exception when the driver
118
+ * does not support generating URLs.
119
+ */
120
+ async getUrl(key) {
121
+ throw new Error("E_URL_GENERATION_UNSUPPORTED");
122
+ }
123
+ /**
124
+ * Return the signed URL to serve a private file. Throw exception
125
+ * when the driver does not support generating URLs.
126
+ */
127
+ async getSignedUrl(key, options) {
128
+ throw new Error("E_URL_GENERATION_UNSUPPORTED");
129
+ }
130
+ /**
131
+ * Return the signed/temporary URL that can be used to directly upload
132
+ * the file contents to the storage.
133
+ */
134
+ async getSignedUploadUrl(key, options) {
135
+ throw new Error("E_URL_GENERATION_UNSUPPORTED");
136
+ }
137
+ /**
138
+ * Update the visibility of the file. Result in a NOOP
139
+ * when the driver does not support the concept of
140
+ * visibility.
141
+ */
142
+ async setVisibility(key, visibility) {}
143
+ /**
144
+ * Create a new file or update an existing file. The contents
145
+ * will be a UTF-8 string or "Uint8Array".
146
+ */
147
+ async put(key, contents, options) {
148
+ if (contents instanceof Uint8Array) contents = Buffer.from(contents);
149
+ const stream = Readable.from(contents);
150
+ await this.putStream(key, stream, options);
151
+ }
152
+ /**
153
+ * Create a new file or update an existing file. The contents
154
+ * will be a Readable stream.
155
+ */
156
+ async putStream(key, contents, options) {
157
+ const dst = createWriteStream("/tmp/" + path.basename(key), { encoding: options?.contentEncoding || "utf-8" });
158
+ await new Promise((resolve, reject) => {
159
+ contents.pipe(dst);
160
+ contents.on("error", reject);
161
+ dst.on("finish", resolve);
162
+ dst.on("error", reject);
163
+ });
164
+ await this.load((client) => {
165
+ return client.put("/tmp/" + path.basename(key), key);
166
+ });
167
+ }
168
+ /**
169
+ * Copy the existing file to the destination. Make sure the new file
170
+ * has the same visibility as the existing file. It might require
171
+ * manually fetching the visibility of the "source" file.
172
+ */
173
+ async copy(source, destination, options) {
174
+ await this.load((client) => {
175
+ return client.rcopy(source, destination);
176
+ });
177
+ }
178
+ /**
179
+ * Move the existing file to the destination. Make sure the new file
180
+ * has the same visibility as the existing file. It might require
181
+ * manually fetching the visibility of the "source" file.
182
+ */
183
+ async move(source, destination, options) {
184
+ await this.load((client) => {
185
+ return client.rename(source, destination);
186
+ });
187
+ }
188
+ /**
189
+ * Delete an existing file. Do not throw an error if the
190
+ * file is already missing
191
+ */
192
+ async delete(key) {
193
+ await this.load((client) => {
194
+ return client.delete(key);
195
+ }).catch(() => {
196
+ return null;
197
+ });
198
+ }
199
+ /**
200
+ * Delete all files inside a folder. Do not throw an error
201
+ * if the folder does not exist or is empty.
202
+ */
203
+ async deleteAll(prefix) {
204
+ await this.load((client) => {
205
+ return client.rmdir(prefix, true);
206
+ }).catch(() => {
207
+ return null;
208
+ });
209
+ }
210
+ /**
211
+ * Switch bucket at runtime if supported.
212
+ */
213
+ bucket(config) {
214
+ return new FtpDriver(config);
215
+ }
216
+ /**
217
+ * List all files from a given folder or the root of the storage.
218
+ * Do not throw an error if the request folder does not exist.
219
+ */
220
+ async listAll(prefix, options) {
221
+ const data = await this.load((client) => {
222
+ return client.list(prefix);
223
+ }).catch(() => {
224
+ return null;
225
+ });
226
+ return data ? { objects: data.map((file) => {
227
+ if (file.type === "d") return new DriveDirectory(file.name);
228
+ else return new DriveFile(file.name, this, {
229
+ contentType: void 0,
230
+ contentLength: file.size,
231
+ etag: file.name,
232
+ lastModified: new Date(file.modifyTime)
233
+ });
234
+ }) } : { objects: [] };
235
+ }
236
+ };
237
+ //#endregion
238
+ //#region src/index.ts
239
+ var Storage = class Storage {
240
+ driver;
241
+ services = {};
242
+ diskName;
243
+ 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
+ }
250
+ this.diskName = config("filesystem.default");
251
+ this.driver = new DriveManager({
252
+ default: config("filesystem.default"),
253
+ services: this.services
254
+ });
255
+ }
256
+ /**
257
+ * Static method to get a disk instance directly from the Storage class without needing to instantiate it first.
258
+ *
259
+ * @param diskName The name of the disk to use. If not provided, the default disk will be used.
260
+ * @returns A Storage instance
261
+ */
262
+ static disk(diskName) {
263
+ const storage = new Storage();
264
+ if (diskName) {
265
+ storage.diskName = diskName;
266
+ storage.driver = new DriveManager({
267
+ default: diskName,
268
+ services: storage.services
269
+ });
270
+ }
271
+ return storage;
272
+ }
273
+ /**
274
+ * Generate a unique name for the file based on random numbers and original extension
275
+ *
276
+ * @param file The file object containing the original name
277
+ * @returns A unique file name
278
+ */
279
+ static generateName = (file) => {
280
+ const name = file.originalname || file.name || "file";
281
+ if (typeof config("filesystem.fileNameGenerator") === "function") return config("filesystem.fileNameGenerator")(name);
282
+ return Math.floor(Math.random() * 999999999999).toString() + "_" + Math.floor(Math.random() * 999999999999) + "." + name.split(".").pop();
283
+ };
284
+ /**
285
+ * Save the file to the storage and return the public URL and the file path
286
+ *
287
+ * @param file The file object containing the file data
288
+ * @param filePath The path where the file should be saved
289
+ * @param fileName The name to save the file as (optional)
290
+ * @returns A tuple containing the public URL and the file path
291
+ */
292
+ static saveFile = async (file, filePath = "", fileName) => {
293
+ return new Storage().saveFile(file, filePath, fileName);
294
+ };
295
+ /**
296
+ * Save the file to the storage and return the public URL and the file path
297
+ *
298
+ * @param file The file object containing the file data
299
+ * @param filePath The path where the file should be saved
300
+ * @param fileName The name to save the file as (optional)
301
+ * @returns A tuple containing the public URL and the file path
302
+ */
303
+ saveFile = async (file, filePath = "", fileName) => {
304
+ const name = fileName || Storage.generateName(file);
305
+ const drive = this.driver.use();
306
+ if (file instanceof File && !file.buffer) file.buffer = Buffer.from(await file.arrayBuffer());
307
+ await drive.put(path.join(filePath, name), file.buffer);
308
+ const url = await drive.getUrl(path.join(filePath, name));
309
+ return [url, this.diskName === "local" ? path.join(filePath, name) : url];
310
+ };
311
+ /**
312
+ * Return a boolean indicating if the file exists
313
+ */
314
+ exists(key) {
315
+ return this.driver.use().exists(key);
316
+ }
317
+ /**
318
+ * Return contents of a object for the given key as a UTF-8 string.
319
+ * Should throw "E_CANNOT_READ_FILE" error when the file
320
+ * does not exists.
321
+ */
322
+ get(key) {
323
+ return this.driver.use().get(key);
324
+ }
325
+ /**
326
+ * Return contents of a object for the given key as a Readable stream.
327
+ * Should throw "E_CANNOT_READ_FILE" error when the file
328
+ * does not exists.
329
+ */
330
+ getStream(key) {
331
+ return this.driver.use().getStream(key);
332
+ }
333
+ /**
334
+ * Return contents of an object for the given key as an Uint8Array.
335
+ * Should throw "E_CANNOT_READ_FILE" error when the file
336
+ * does not exists.
337
+ */
338
+ getBytes(key) {
339
+ return this.driver.use().getBytes(key);
340
+ }
341
+ /**
342
+ * Return metadata of an object for the given key.
343
+ */
344
+ getMetaData(key) {
345
+ return this.driver.use().getMetaData(key);
346
+ }
347
+ /**
348
+ * Return the visibility of the file
349
+ */
350
+ getVisibility(key) {
351
+ return this.driver.use().getVisibility(key);
352
+ }
353
+ /**
354
+ * Return the public URL to access the file
355
+ */
356
+ getUrl(key) {
357
+ return this.driver.use().getUrl(key);
358
+ }
359
+ /**
360
+ * Return the signed/temporary URL to access the file
361
+ */
362
+ getSignedUrl(key, options) {
363
+ return this.driver.use().getSignedUrl(key, options);
364
+ }
365
+ /**
366
+ * Return the signed/temporary URL that can be used to directly upload
367
+ * the file contents to the storage.
368
+ */
369
+ getSignedUploadUrl(key, options) {
370
+ return this.driver.use().getSignedUploadUrl(key, options);
371
+ }
372
+ /**
373
+ * Update the visibility of the file
374
+ */
375
+ setVisibility(key, visibility) {
376
+ return this.driver.use().setVisibility(key, visibility);
377
+ }
378
+ /**
379
+ * Write object to the destination with the provided
380
+ * contents.
381
+ */
382
+ put(key, contents, options) {
383
+ if (!(contents instanceof Uint8Array) && typeof contents !== "string") contents = contents.buffer;
384
+ return this.driver.use().put(key, contents, options);
385
+ }
386
+ /**
387
+ * Write object to the destination with the provided
388
+ * contents as a readable stream
389
+ */
390
+ putStream(key, contents, options) {
391
+ return this.driver.use().putStream(key, contents, options);
392
+ }
393
+ /**
394
+ * Copy the file from within the disk root location. Both
395
+ * the "source" and "destination" will be the key names
396
+ * and not absolute paths.
397
+ */
398
+ copy(source, destination, options) {
399
+ return this.driver.use().copy(source, destination, options);
400
+ }
401
+ /**
402
+ * Move the file from within the disk root location. Both
403
+ * the "source" and "destination" will be the key names
404
+ * and not absolute paths.
405
+ */
406
+ move(source, destination, options) {
407
+ return this.driver.use().move(source, destination, options);
408
+ }
409
+ /**
410
+ * Delete the file for the given key. Should not throw
411
+ * error when file does not exist in first place
412
+ */
413
+ delete(key) {
414
+ return this.driver.use().delete(key);
415
+ }
416
+ /**
417
+ * Delete the files and directories matching the provided prefix.
418
+ */
419
+ deleteAll(prefix) {
420
+ return this.driver.use().deleteAll(prefix);
421
+ }
422
+ /**
423
+ * The list all method must return an array of objects with
424
+ * the ability to paginate results (if supported).
425
+ */
426
+ listAll(prefix, options) {
427
+ return this.driver.use().listAll(prefix, options);
428
+ }
429
+ /**
430
+ * Switch bucket at runtime if supported.
431
+ */
432
+ bucket(bucket) {
433
+ return this.driver.use().bucket(bucket);
434
+ }
435
+ /**
436
+ * Create symbolic links for all configured links in the application configuration.
437
+ */
438
+ static link({ force = false } = {}) {
439
+ for (const link in config("filesystem.links")) {
440
+ const target = config("filesystem.links")[link];
441
+ const unlink = link.replace(process.cwd(), "");
442
+ const untarget = target.replace(process.cwd(), "");
443
+ try {
444
+ if (force) rmSync(link, {
445
+ recursive: true,
446
+ force: true
447
+ });
448
+ symlinkSync(target, link);
449
+ Logger.log([
450
+ [" SUCCESS ", "bgGreen"],
451
+ [`[${unlink}]`, "green"],
452
+ ["is now linked to", "white"],
453
+ [`[${untarget}].`, "green"]
454
+ ], " ");
455
+ } catch (error) {
456
+ if (error.code === "EEXIST") Logger.log([
457
+ [" INFO ", "bgBlue"],
458
+ [`[${unlink}]`, "green"],
459
+ ["is already linked to", "white"],
460
+ [`[${untarget}].`, "green"]
461
+ ], " ");
462
+ else Logger.log([
463
+ [" ERROR ", "bgRed"],
464
+ ["Failed to create symbolic link from", "white"],
465
+ [`[${unlink}]`, "green"],
466
+ ["to", "white"],
467
+ [`[${untarget}]`, "green"],
468
+ [error.message, "red"]
469
+ ], " ");
470
+ }
471
+ }
472
+ }
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
+ };
507
+ //#endregion
508
+ export { Storage as t };
509
+
510
+ //# sourceMappingURL=src-wGEPrkk5.js.map
@@ -0,0 +1 @@
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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"}
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@arkstack/filesystem",
3
- "version": "0.4.3",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
- "description": "Shared Filesystem utilities for ArkStack.",
5
+ "description": "Filesystem module for Arkstack, providing shared file storage and filesystem utitlities for the framework.",
6
6
  "homepage": "https://arkstack.toneflix.net",
7
7
  "repository": {
8
8
  "type": "git",
@@ -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/contract": "^0.4.3",
43
- "@arkstack/common": "^0.4.3"
42
+ "@arkstack/common": "^0.5.0",
43
+ "@arkstack/contract": "^0.5.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/ssh2-sftp-client": "^9.0.6"
@@ -1,237 +0,0 @@
1
- import { DriveDirectory, DriveFile } from "flydrive";
2
- import { createReadStream, createWriteStream } from "node:fs";
3
- import Client from "ssh2-sftp-client";
4
- import { Readable } from "node:stream";
5
- import path from "node:path";
6
-
7
- //#region src/FtpDriver.ts
8
- var FtpDriver = class FtpDriver {
9
- config;
10
- constructor(config) {
11
- if (typeof config === "string") {
12
- const url = new URL(config);
13
- this.config = {
14
- host: url.hostname,
15
- username: url.username,
16
- password: url.password,
17
- port: url.port ? parseInt(url.port, 10) : 22,
18
- verbose: url.searchParams.get("verbose") === "true"
19
- };
20
- } else this.config = config;
21
- }
22
- getConfig() {
23
- return this.config;
24
- }
25
- async init() {
26
- const client = new Client();
27
- await client.connect({
28
- host: this.config.host,
29
- username: this.config.username,
30
- password: this.config.password,
31
- port: this.config.port || 22
32
- });
33
- return client;
34
- }
35
- async load(handle) {
36
- const client = await this.init();
37
- try {
38
- return await handle(client);
39
- } catch (e) {
40
- if (this.config.verbose) throw e;
41
- } finally {
42
- await client.end();
43
- }
44
- return null;
45
- }
46
- /**
47
- * Return a boolean value indicating if the file exists
48
- * or not.
49
- */
50
- async exists(key) {
51
- return !!await this.load((client) => {
52
- return client.stat(key);
53
- }).catch(() => {
54
- return null;
55
- });
56
- }
57
- /**
58
- * Return the file contents as a UTF-8 string. Throw an exception
59
- * if the file is missing.
60
- */
61
- async get(key) {
62
- const stream = await this.getStream(key);
63
- const chunks = [];
64
- for await (const chunk of stream) chunks.push(chunk);
65
- return Buffer.concat(chunks).toString("utf-8");
66
- }
67
- /**
68
- * Return the file contents as a Readable stream. Throw an exception
69
- * if the file is missing.
70
- */
71
- async getStream(key) {
72
- const dst = createWriteStream("/tmp/" + path.basename(key));
73
- await this.load((client) => {
74
- return client.get(key, dst);
75
- }).catch(() => {
76
- return null;
77
- });
78
- return createReadStream("/tmp/" + path.basename(key));
79
- }
80
- /**
81
- * Return the file contents as a Uint8Array. Throw an exception
82
- * if the file is missing.
83
- */
84
- async getBytes(key) {
85
- const content = await this.get(key);
86
- return new Uint8Array(Buffer.from(content, "utf-8"));
87
- }
88
- /**
89
- * Return metadata of the file. Throw an exception
90
- * if the file is missing.
91
- */
92
- async getMetaData(key) {
93
- const stat = await this.load((client) => {
94
- return client.stat(key);
95
- }).catch(() => {
96
- return null;
97
- });
98
- if (!stat) throw new Error("E_CANNOT_READ_FILE");
99
- return {
100
- contentType: void 0,
101
- contentLength: stat.size,
102
- etag: key,
103
- lastModified: new Date(stat.modifyTime)
104
- };
105
- }
106
- /**
107
- * Return visibility of the file. Infer visibility from the initial
108
- * config, when the driver does not support the concept of visibility.
109
- */
110
- async getVisibility(key) {
111
- return "private";
112
- }
113
- /**
114
- * Return the public URL of the file. Throw an exception when the driver
115
- * does not support generating URLs.
116
- */
117
- async getUrl(key) {
118
- throw new Error("E_URL_GENERATION_UNSUPPORTED");
119
- }
120
- /**
121
- * Return the signed URL to serve a private file. Throw exception
122
- * when the driver does not support generating URLs.
123
- */
124
- async getSignedUrl(key, options) {
125
- throw new Error("E_URL_GENERATION_UNSUPPORTED");
126
- }
127
- /**
128
- * Return the signed/temporary URL that can be used to directly upload
129
- * the file contents to the storage.
130
- */
131
- async getSignedUploadUrl(key, options) {
132
- throw new Error("E_URL_GENERATION_UNSUPPORTED");
133
- }
134
- /**
135
- * Update the visibility of the file. Result in a NOOP
136
- * when the driver does not support the concept of
137
- * visibility.
138
- */
139
- async setVisibility(key, visibility) {}
140
- /**
141
- * Create a new file or update an existing file. The contents
142
- * will be a UTF-8 string or "Uint8Array".
143
- */
144
- async put(key, contents, options) {
145
- if (contents instanceof Uint8Array) contents = Buffer.from(contents);
146
- const stream = Readable.from(contents);
147
- await this.putStream(key, stream, options);
148
- }
149
- /**
150
- * Create a new file or update an existing file. The contents
151
- * will be a Readable stream.
152
- */
153
- async putStream(key, contents, options) {
154
- const dst = createWriteStream("/tmp/" + path.basename(key), { encoding: options?.contentEncoding || "utf-8" });
155
- await new Promise((resolve, reject) => {
156
- contents.pipe(dst);
157
- contents.on("error", reject);
158
- dst.on("finish", resolve);
159
- dst.on("error", reject);
160
- });
161
- await this.load((client) => {
162
- return client.put("/tmp/" + path.basename(key), key);
163
- });
164
- }
165
- /**
166
- * Copy the existing file to the destination. Make sure the new file
167
- * has the same visibility as the existing file. It might require
168
- * manually fetching the visibility of the "source" file.
169
- */
170
- async copy(source, destination, options) {
171
- await this.load((client) => {
172
- return client.rcopy(source, destination);
173
- });
174
- }
175
- /**
176
- * Move the existing file to the destination. Make sure the new file
177
- * has the same visibility as the existing file. It might require
178
- * manually fetching the visibility of the "source" file.
179
- */
180
- async move(source, destination, options) {
181
- await this.load((client) => {
182
- return client.rename(source, destination);
183
- });
184
- }
185
- /**
186
- * Delete an existing file. Do not throw an error if the
187
- * file is already missing
188
- */
189
- async delete(key) {
190
- await this.load((client) => {
191
- return client.delete(key);
192
- }).catch(() => {
193
- return null;
194
- });
195
- }
196
- /**
197
- * Delete all files inside a folder. Do not throw an error
198
- * if the folder does not exist or is empty.
199
- */
200
- async deleteAll(prefix) {
201
- await this.load((client) => {
202
- return client.rmdir(prefix, true);
203
- }).catch(() => {
204
- return null;
205
- });
206
- }
207
- /**
208
- * Switch bucket at runtime if supported.
209
- */
210
- bucket(config) {
211
- return new FtpDriver(config);
212
- }
213
- /**
214
- * List all files from a given folder or the root of the storage.
215
- * Do not throw an error if the request folder does not exist.
216
- */
217
- async listAll(prefix, options) {
218
- const data = await this.load((client) => {
219
- return client.list(prefix);
220
- }).catch(() => {
221
- return null;
222
- });
223
- return data ? { objects: data.map((file) => {
224
- if (file.type === "d") return new DriveDirectory(file.name);
225
- else return new DriveFile(file.name, this, {
226
- contentType: void 0,
227
- contentLength: file.size,
228
- etag: file.name,
229
- lastModified: new Date(file.modifyTime)
230
- });
231
- }) } : { objects: [] };
232
- }
233
- };
234
-
235
- //#endregion
236
- export { FtpDriver as t };
237
- //# sourceMappingURL=FtpDriver-CfUSZ1xr.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"FtpDriver-CfUSZ1xr.js","names":[],"sources":["../src/FtpDriver.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"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"StorageLinkCommand.d.ts","names":[],"sources":["../../src/commands/StorageLinkCommand.ts"],"mappings":";;;cAGa,kBAAA,SAA2B,OAAA;EAAA,UAC1B,SAAA;EAAA,UAGA,WAAA;EAEJ,MAAA,CAAA,GAAM,OAAA;AAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;UAYU,QAAA;EACN,YAAA;EACA,MAAA,EAAQ,MAAA;EACR,QAAA;AAAA;AAAA,cAGS,OAAA,YAAmB,cAAA;EAC5B,MAAA,EAAQ,YAAA;EACR,QAAA,EAAU,MAAA,eAAqB,cAAA;EAC/B,QAAA;;SA2BO,IAAA,kBAAA,CAAwB,QAAA,GAAW,CAAA,GAAI,OAAA;EAAA,OAoBvC,YAAA,GAAgB,IAAA;IAAQ,IAAA;IAAe,YAAA;EAAA;EAAA,OAoBvC,QAAA,GACH,IAAA,EAAM,QAAA,EACN,QAAA,WACA,QAAA,cACD,OAAA;EAYH,QAAA,GACI,IAAA,EAAM,QAAA,EACN,QAAA,WACA,QAAA,cACD,OAAA;EAmBH,MAAA,CAAQ,GAAA,WAAc,OAAA;EAQtB,GAAA,CAAK,GAAA,WAAc,OAAA;EAQnB,SAAA,CAAW,GAAA,WAAc,OAAA,CAAQ,QAAA;EAQjC,QAAA,CAAU,GAAA,WAAc,OAAA,CAAQ,UAAA;EAMhC,WAAA,CAAa,GAAA,WAAc,OAAA,CAAQ,cAAA;EAMnC,aAAA,CAAe,GAAA,WAAc,OAAA,CAAQ,gBAAA;EAMrC,MAAA,CAAQ,GAAA,WAAc,OAAA;EAMtB,YAAA,CAAc,GAAA,UAAa,OAAA,GAAU,gBAAA,GAAmB,OAAA;EAOxD,kBAAA,CAAoB,GAAA,UAAa,OAAA,GAAU,gBAAA,GAAmB,OAAA;EAM9D,aAAA,CAAe,GAAA,UAAa,UAAA,EAAY,gBAAA,GAAmB,OAAA;EAO3D,GAAA,CAAK,GAAA,UAAa,QAAA,WAAmB,UAAA,GAAa,QAAA,EAAU,OAAA,GAAU,YAAA,GAAe,OAAA;EAWrF,SAAA,CAAW,GAAA,UAAa,QAAA,EAAU,QAAA,EAAU,OAAA,GAAU,YAAA,GAAe,OAAA;EAQrE,IAAA,CAAM,MAAA,UAAgB,WAAA,UAAqB,OAAA,GAAU,YAAA,GAAe,OAAA;EAQpE,IAAA,CAAM,MAAA,UAAgB,WAAA,UAAqB,OAAA,GAAU,YAAA,GAAe,OAAA;EAOpE,MAAA,CAAQ,GAAA,WAAc,OAAA;EAMtB,SAAA,CAAW,MAAA,WAAiB,OAAA;EAO5B,OAAA,CAAS,MAAA,UAAgB,OAAA;IACrB,SAAA;IACA,eAAA;EAAA,IACA,OAAA;IACA,eAAA;IACA,OAAA,EAAS,QAAA,CAAS,SAAA,GAAY,cAAA;EAAA;EAOlC,MAAA,CAAQ,MAAA,WAAiB,cAAA;EAAA,OAOlB,IAAA,CAAA;IAAQ;EAAA;IAAmB,KAAA;EAAA;EAAA,QAuC1B,UAAA;AAAA"}
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["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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"}