@devbro/neko-storage 0.1.4 → 0.1.5

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
@@ -39,22 +39,119 @@ let is_file_deleted = await storage.delete('path/to/file/filename.ext');
39
39
 
40
40
  ## available drivers
41
41
 
42
- local
42
+ ### Local Storage
43
+
44
+ Store files on the local file system.
43
45
 
44
46
  ```ts
45
- import { Storage, StorageFactory } from '@devbro/neko-storage';
47
+ import { LocalStorageProvider, Storage } from '@devbro/neko-storage';
46
48
 
47
49
  const basePath = path.resolve(os.tmpdir(), `test-storage-${randomUUID()}`);
48
- const storage: Storage = StorageFactory.create({ engine: 'local', basePath });
50
+ const provider = new LocalStorageProvider({ engine: 'local', basePath });
51
+ const storage = new Storage(provider);
49
52
  ```
50
53
 
51
- AWS S3
54
+ ### AWS S3
55
+
56
+ Store files in Amazon S3 buckets.
52
57
 
53
58
  ```ts
54
- import { Storage, StorageFactory } from '@devbro/neko-storage';
59
+ import { AWSS3StorageProvider, Storage } from '@devbro/neko-storage';
60
+
61
+ const provider = new AWSS3StorageProvider({
62
+ engine: 's3',
63
+ bucket: 'your-bucket-name',
64
+ s3Config: {
65
+ region: 'us-east-1',
66
+ credentials: {
67
+ accessKeyId: 'YOUR_ACCESS_KEY',
68
+ secretAccessKey: 'YOUR_SECRET_KEY',
69
+ },
70
+ },
71
+ });
72
+ const storage = new Storage(provider);
73
+ ```
74
+
75
+ ### Google Cloud Storage (GCP)
76
+
77
+ Store files in Google Cloud Storage buckets.
78
+
79
+ ```ts
80
+ import { GCPStorageProvider, Storage } from '@devbro/neko-storage';
81
+
82
+ const provider = new GCPStorageProvider({
83
+ engine: 'gcp',
84
+ bucket: 'your-bucket-name',
85
+ gcpConfig: {
86
+ projectId: 'your-project-id',
87
+ keyFilename: '/path/to/service-account-key.json',
88
+ // Or use credentials object directly
89
+ // credentials: {...}
90
+ },
91
+ });
92
+ const storage = new Storage(provider);
93
+ ```
94
+
95
+ ### Azure Blob Storage
96
+
97
+ Store files in Azure Blob Storage containers.
55
98
 
56
- const s3Config : AWSS3Config = ???;
57
- const storage: Storage = StorageFactory.create({ engine: 's3', { s3Config } });
99
+ ```ts
100
+ import { AzureBlobStorageProvider, Storage } from '@devbro/neko-storage';
101
+
102
+ const provider = new AzureBlobStorageProvider({
103
+ engine: 'azure',
104
+ azureConfig: {
105
+ accountName: 'your-storage-account',
106
+ accountKey: 'YOUR_ACCOUNT_KEY', // Or use sasToken instead
107
+ containerName: 'your-container-name',
108
+ },
109
+ });
110
+ const storage = new Storage(provider);
111
+ ```
112
+
113
+ ### FTP
114
+
115
+ Store files on FTP servers.
116
+
117
+ ```ts
118
+ import { FTPStorageProvider, Storage } from '@devbro/neko-storage';
119
+
120
+ const provider = new FTPStorageProvider({
121
+ engine: 'ftp',
122
+ basePath: '/remote/path',
123
+ ftpConfig: {
124
+ host: 'ftp.example.com',
125
+ port: 21,
126
+ user: 'username',
127
+ password: 'password',
128
+ secure: false, // Set to true for FTPS
129
+ },
130
+ });
131
+ const storage = new Storage(provider);
132
+ ```
133
+
134
+ ### SFTP
135
+
136
+ Store files on SFTP servers via SSH.
137
+
138
+ ```ts
139
+ import { SFTPStorageProvider, Storage } from '@devbro/neko-storage';
140
+
141
+ const provider = new SFTPStorageProvider({
142
+ engine: 'sftp',
143
+ basePath: '/remote/path',
144
+ sftpConfig: {
145
+ host: 'sftp.example.com',
146
+ port: 22,
147
+ username: 'username',
148
+ password: 'password',
149
+ // Or use private key authentication
150
+ // privateKey: fs.readFileSync('/path/to/private-key'),
151
+ // passphrase: 'key-passphrase',
152
+ },
153
+ });
154
+ const storage = new Storage(provider);
58
155
  ```
59
156
 
60
157
  More driver available upon request or through PR.
@@ -3,6 +3,7 @@ import { Stream } from 'stream';
3
3
  import { Metadata } from './types.mjs';
4
4
  import { StorageProviderInterface } from './StorageProviderInterface.mjs';
5
5
  import '@aws-sdk/client-s3';
6
+ import '@google-cloud/storage';
6
7
 
7
8
  declare class Storage {
8
9
  protected provider: StorageProviderInterface;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/Storage.mts"],"sourcesContent":["import { ReadStream } from 'fs';\nimport { Stream } from 'stream';\nimport { Metadata } from './types.mjs';\nimport { StorageConfig } from './types.mjs';\nimport { StorageProviderInterface } from './StorageProviderInterface.mjs';\n\nexport class Storage {\n constructor(protected provider: StorageProviderInterface) {}\n\n exists(path: string): Promise<boolean> {\n return this.provider.exists(path);\n }\n \n put(path: string, content: string | object | Stream | Buffer): Promise<boolean> {\n return this.provider.put(path, content);\n }\n \n getJson(path: string): Promise<object> {\n return this.provider.getJson(path);\n }\n \n getString(path: string): Promise<string> {\n return this.provider.getString(path);\n }\n \n getBuffer(path: string): Promise<Buffer> {\n return this.provider.getBuffer(path);\n }\n \n getStream(path: string): Promise<ReadStream> {\n return this.provider.getStream(path);\n }\n \n delete(path: string): Promise<boolean> {\n return this.provider.delete(path);\n }\n \n metadata(path: string): Promise<Metadata> {\n return this.provider.metadata(path);\n }\n}\n"],"mappings":";;AAMO,MAAM,QAAQ;AAAA,EACnB,YAAsB,UAAoC;AAApC;AAAA,EAAqC;AAAA,EAP7D,OAMqB;AAAA;AAAA;AAAA,EAGnB,OAAO,MAAgC;AACrC,WAAO,KAAK,SAAS,OAAO,IAAI;AAAA,EAClC;AAAA,EAEA,IAAI,MAAc,SAA8D;AAC9E,WAAO,KAAK,SAAS,IAAI,MAAM,OAAO;AAAA,EACxC;AAAA,EAEA,QAAQ,MAA+B;AACrC,WAAO,KAAK,SAAS,QAAQ,IAAI;AAAA,EACnC;AAAA,EAEA,UAAU,MAA+B;AACvC,WAAO,KAAK,SAAS,UAAU,IAAI;AAAA,EACrC;AAAA,EAEA,UAAU,MAA+B;AACvC,WAAO,KAAK,SAAS,UAAU,IAAI;AAAA,EACrC;AAAA,EAEA,UAAU,MAAmC;AAC3C,WAAO,KAAK,SAAS,UAAU,IAAI;AAAA,EACrC;AAAA,EAEA,OAAO,MAAgC;AACrC,WAAO,KAAK,SAAS,OAAO,IAAI;AAAA,EAClC;AAAA,EAEA,SAAS,MAAiC;AACxC,WAAO,KAAK,SAAS,SAAS,IAAI;AAAA,EACpC;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/Storage.mts"],"sourcesContent":["import { ReadStream } from 'fs';\nimport { Stream } from 'stream';\nimport { Metadata } from './types.mjs';\nimport { StorageConfig } from './types.mjs';\nimport { StorageProviderInterface } from './StorageProviderInterface.mjs';\n\nexport class Storage {\n constructor(protected provider: StorageProviderInterface) {}\n\n exists(path: string): Promise<boolean> {\n return this.provider.exists(path);\n }\n\n put(path: string, content: string | object | Stream | Buffer): Promise<boolean> {\n return this.provider.put(path, content);\n }\n\n getJson(path: string): Promise<object> {\n return this.provider.getJson(path);\n }\n\n getString(path: string): Promise<string> {\n return this.provider.getString(path);\n }\n\n getBuffer(path: string): Promise<Buffer> {\n return this.provider.getBuffer(path);\n }\n\n getStream(path: string): Promise<ReadStream> {\n return this.provider.getStream(path);\n }\n\n delete(path: string): Promise<boolean> {\n return this.provider.delete(path);\n }\n\n metadata(path: string): Promise<Metadata> {\n return this.provider.metadata(path);\n }\n}\n"],"mappings":";;AAMO,MAAM,QAAQ;AAAA,EACnB,YAAsB,UAAoC;AAApC;AAAA,EAAqC;AAAA,EAP7D,OAMqB;AAAA;AAAA;AAAA,EAGnB,OAAO,MAAgC;AACrC,WAAO,KAAK,SAAS,OAAO,IAAI;AAAA,EAClC;AAAA,EAEA,IAAI,MAAc,SAA8D;AAC9E,WAAO,KAAK,SAAS,IAAI,MAAM,OAAO;AAAA,EACxC;AAAA,EAEA,QAAQ,MAA+B;AACrC,WAAO,KAAK,SAAS,QAAQ,IAAI;AAAA,EACnC;AAAA,EAEA,UAAU,MAA+B;AACvC,WAAO,KAAK,SAAS,UAAU,IAAI;AAAA,EACrC;AAAA,EAEA,UAAU,MAA+B;AACvC,WAAO,KAAK,SAAS,UAAU,IAAI;AAAA,EACrC;AAAA,EAEA,UAAU,MAAmC;AAC3C,WAAO,KAAK,SAAS,UAAU,IAAI;AAAA,EACrC;AAAA,EAEA,OAAO,MAAgC;AACrC,WAAO,KAAK,SAAS,OAAO,IAAI;AAAA,EAClC;AAAA,EAEA,SAAS,MAAiC;AACxC,WAAO,KAAK,SAAS,SAAS,IAAI;AAAA,EACpC;AACF;","names":[]}
@@ -4,6 +4,7 @@ import 'fs';
4
4
  import 'stream';
5
5
  import './types.mjs';
6
6
  import '@aws-sdk/client-s3';
7
+ import '@google-cloud/storage';
7
8
 
8
9
  declare class StorageProviderFactory {
9
10
  static instance: FlexibleFactory<StorageProviderInterface>;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/StorageProviderFactory.mts"],"sourcesContent":["import { Storage } from './Storage.mjs';\nimport { FlexibleFactory } from '@devbro/neko-helper';\nimport { StorageProviderInterface } from './StorageProviderInterface.mjs';\n\nexport class StorageProviderFactory {\n static instance: FlexibleFactory<StorageProviderInterface> = new FlexibleFactory<StorageProviderInterface>();\n\n static register(key: string, factory: (...args: any[]) => StorageProviderInterface): void {\n StorageProviderFactory.instance.register(key, factory);\n }\n\n static create<T>(key: string, ...args: any[]): StorageProviderInterface {\n return StorageProviderFactory.instance.create(key, ...args);\n }\n}\n"],"mappings":";;AACA,SAAS,uBAAuB;AAGzB,MAAM,uBAAuB;AAAA,EAJpC,OAIoC;AAAA;AAAA;AAAA,EAClC,OAAO,WAAsD,IAAI,gBAA0C;AAAA,EAE3G,OAAO,SAAS,KAAa,SAA6D;AACxF,2BAAuB,SAAS,SAAS,KAAK,OAAO;AAAA,EACvD;AAAA,EAEA,OAAO,OAAU,QAAgB,MAAuC;AACtE,WAAO,uBAAuB,SAAS,OAAO,KAAK,GAAG,IAAI;AAAA,EAC5D;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/StorageProviderFactory.mts"],"sourcesContent":["import { Storage } from './Storage.mjs';\nimport { FlexibleFactory } from '@devbro/neko-helper';\nimport { StorageProviderInterface } from './StorageProviderInterface.mjs';\n\nexport class StorageProviderFactory {\n static instance: FlexibleFactory<StorageProviderInterface> =\n new FlexibleFactory<StorageProviderInterface>();\n\n static register(key: string, factory: (...args: any[]) => StorageProviderInterface): void {\n StorageProviderFactory.instance.register(key, factory);\n }\n\n static create<T>(key: string, ...args: any[]): StorageProviderInterface {\n return StorageProviderFactory.instance.create(key, ...args);\n }\n}\n"],"mappings":";;AACA,SAAS,uBAAuB;AAGzB,MAAM,uBAAuB;AAAA,EAJpC,OAIoC;AAAA;AAAA;AAAA,EAClC,OAAO,WACL,IAAI,gBAA0C;AAAA,EAEhD,OAAO,SAAS,KAAa,SAA6D;AACxF,2BAAuB,SAAS,SAAS,KAAK,OAAO;AAAA,EACvD;AAAA,EAEA,OAAO,OAAU,QAAgB,MAAuC;AACtE,WAAO,uBAAuB,SAAS,OAAO,KAAK,GAAG,IAAI;AAAA,EAC5D;AACF;","names":[]}
@@ -2,6 +2,7 @@ import { ReadStream } from 'fs';
2
2
  import { Stream } from 'stream';
3
3
  import { Metadata } from './types.mjs';
4
4
  import '@aws-sdk/client-s3';
5
+ import '@google-cloud/storage';
5
6
 
6
7
  interface StorageProviderInterface {
7
8
  exists(path: string): Promise<boolean>;
package/dist/index.d.mts CHANGED
@@ -1,10 +1,15 @@
1
- export { Metadata, StorageConfig } from './types.mjs';
1
+ export { AzureStorageConfig, FTPConfig, GCPStorageConfig, LocalStorageConfig, Metadata, S3ClientConfig, SFTPConfig, StorageConfig } from './types.mjs';
2
2
  export { Storage } from './Storage.mjs';
3
3
  export { AWSS3StorageProvider } from './providers/AWSS3StorageProvider.mjs';
4
4
  export { LocalStorageProvider } from './providers/LocalStorageProvider.mjs';
5
+ export { GCPStorageProvider } from './providers/GCPStorageProvider.mjs';
6
+ export { AzureBlobStorageProvider } from './providers/AzureBlobStorageProvider.mjs';
7
+ export { FTPStorageProvider } from './providers/FTPStorageProvider.mjs';
8
+ export { SFTPStorageProvider } from './providers/SFTPStorageProvider.mjs';
5
9
  export { StorageProviderFactory } from './StorageProviderFactory.mjs';
6
10
  export { StorageProviderInterface } from './StorageProviderInterface.mjs';
7
11
  import '@aws-sdk/client-s3';
12
+ import '@google-cloud/storage';
8
13
  import 'fs';
9
14
  import 'stream';
10
15
  import '@devbro/neko-helper';
package/dist/index.js CHANGED
@@ -32,7 +32,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
32
32
  var index_exports = {};
33
33
  __export(index_exports, {
34
34
  AWSS3StorageProvider: () => AWSS3StorageProvider,
35
+ AzureBlobStorageProvider: () => AzureBlobStorageProvider,
36
+ FTPStorageProvider: () => FTPStorageProvider,
37
+ GCPStorageProvider: () => GCPStorageProvider,
35
38
  LocalStorageProvider: () => LocalStorageProvider,
39
+ SFTPStorageProvider: () => SFTPStorageProvider,
36
40
  Storage: () => Storage,
37
41
  StorageProviderFactory: () => StorageProviderFactory
38
42
  });
@@ -78,7 +82,7 @@ var import_stream = __toESM(require("stream"), 1);
78
82
  var AWSS3StorageProvider = class {
79
83
  constructor(config) {
80
84
  this.config = config;
81
- this.s3 = new import_client_s3.S3Client(this.config?.s3Config || {});
85
+ this.s3 = new import_client_s3.S3Client(this.config);
82
86
  }
83
87
  static {
84
88
  __name(this, "AWSS3StorageProvider");
@@ -86,7 +90,7 @@ var AWSS3StorageProvider = class {
86
90
  s3;
87
91
  async exists(path2) {
88
92
  try {
89
- await this.s3.send(new import_client_s3.HeadObjectCommand({ Bucket: this.config?.bucket, Key: path2 }));
93
+ await this.s3.send(new import_client_s3.HeadObjectCommand({ Bucket: this.config.bucket, Key: path2 }));
90
94
  return true;
91
95
  } catch (error) {
92
96
  if (error.name === "NotFound") {
@@ -250,6 +254,436 @@ var LocalStorageProvider = class {
250
254
  }
251
255
  };
252
256
 
257
+ // src/providers/GCPStorageProvider.mts
258
+ var import_storage = require("@google-cloud/storage");
259
+ var import_stream3 = __toESM(require("stream"), 1);
260
+ var mime2 = __toESM(require("mime-types"), 1);
261
+ var GCPStorageProvider = class {
262
+ constructor(config) {
263
+ this.config = config;
264
+ const { bucket, ...gcpOptions } = config;
265
+ this.storage = new import_storage.Storage(gcpOptions);
266
+ }
267
+ static {
268
+ __name(this, "GCPStorageProvider");
269
+ }
270
+ storage;
271
+ async exists(path2) {
272
+ try {
273
+ const file = this.storage.bucket(this.config.bucket).file(path2);
274
+ const [exists] = await file.exists();
275
+ return exists;
276
+ } catch (error) {
277
+ return false;
278
+ }
279
+ }
280
+ async put(path2, content) {
281
+ const file = this.storage.bucket(this.config.bucket).file(path2);
282
+ let data;
283
+ if (typeof content === "string" || content instanceof Buffer) {
284
+ data = content;
285
+ } else if (typeof content === "object" && !(content instanceof import_stream3.default)) {
286
+ data = JSON.stringify(content);
287
+ } else if (content instanceof import_stream3.default) {
288
+ data = content;
289
+ } else {
290
+ throw new Error("Unsupported content type");
291
+ }
292
+ if (data instanceof import_stream3.default) {
293
+ await new Promise((resolve, reject) => {
294
+ data.pipe(file.createWriteStream()).on("finish", () => resolve()).on("error", reject);
295
+ });
296
+ } else {
297
+ await file.save(data);
298
+ }
299
+ return true;
300
+ }
301
+ async getJson(path2) {
302
+ const data = await this.getString(path2);
303
+ return JSON.parse(data);
304
+ }
305
+ async getString(path2) {
306
+ const file = this.storage.bucket(this.config.bucket).file(path2);
307
+ const [content] = await file.download();
308
+ return content.toString("utf-8");
309
+ }
310
+ async getBuffer(path2) {
311
+ const file = this.storage.bucket(this.config.bucket).file(path2);
312
+ const [content] = await file.download();
313
+ return content;
314
+ }
315
+ async getStream(path2) {
316
+ const file = this.storage.bucket(this.config.bucket).file(path2);
317
+ return file.createReadStream();
318
+ }
319
+ async delete(path2) {
320
+ const file = this.storage.bucket(this.config.bucket).file(path2);
321
+ await file.delete();
322
+ return true;
323
+ }
324
+ async metadata(path2) {
325
+ const file = this.storage.bucket(this.config.bucket).file(path2);
326
+ const [metadata] = await file.getMetadata();
327
+ return {
328
+ size: typeof metadata.size === "number" ? metadata.size : parseInt(metadata.size || "0", 10),
329
+ mimeType: metadata.contentType || mime2.lookup(path2) || "unknown",
330
+ lastModifiedDate: metadata.updated || (/* @__PURE__ */ new Date(0)).toISOString()
331
+ };
332
+ }
333
+ };
334
+
335
+ // src/providers/AzureBlobStorageProvider.mts
336
+ var import_storage_blob = require("@azure/storage-blob");
337
+ var import_stream4 = __toESM(require("stream"), 1);
338
+ var mime3 = __toESM(require("mime-types"), 1);
339
+ var AzureBlobStorageProvider = class {
340
+ constructor(config) {
341
+ this.config = config;
342
+ const { accountName, accountKey, sasToken } = config;
343
+ if (accountKey) {
344
+ const sharedKeyCredential = new import_storage_blob.StorageSharedKeyCredential(accountName, accountKey);
345
+ this.blobServiceClient = new import_storage_blob.BlobServiceClient(
346
+ `https://${accountName}.blob.core.windows.net`,
347
+ sharedKeyCredential
348
+ );
349
+ } else if (sasToken) {
350
+ this.blobServiceClient = new import_storage_blob.BlobServiceClient(
351
+ `https://${accountName}.blob.core.windows.net?${sasToken}`
352
+ );
353
+ } else {
354
+ throw new Error("Either accountKey or sasToken is required for Azure Blob Storage");
355
+ }
356
+ }
357
+ static {
358
+ __name(this, "AzureBlobStorageProvider");
359
+ }
360
+ blobServiceClient;
361
+ async exists(path2) {
362
+ try {
363
+ const containerClient = this.blobServiceClient.getContainerClient(this.config.containerName);
364
+ const blobClient = containerClient.getBlobClient(path2);
365
+ return await blobClient.exists();
366
+ } catch (error) {
367
+ return false;
368
+ }
369
+ }
370
+ async put(path2, content) {
371
+ const containerClient = this.blobServiceClient.getContainerClient(this.config.containerName);
372
+ const blockBlobClient = containerClient.getBlockBlobClient(path2);
373
+ let data;
374
+ if (typeof content === "string" || content instanceof Buffer) {
375
+ data = content;
376
+ } else if (typeof content === "object" && !(content instanceof import_stream4.default)) {
377
+ data = JSON.stringify(content);
378
+ } else if (content instanceof import_stream4.default) {
379
+ data = content;
380
+ } else {
381
+ throw new Error("Unsupported content type");
382
+ }
383
+ if (data instanceof import_stream4.default) {
384
+ await blockBlobClient.uploadStream(data);
385
+ } else {
386
+ const buffer = typeof data === "string" ? Buffer.from(data) : data;
387
+ await blockBlobClient.upload(buffer, buffer.length);
388
+ }
389
+ return true;
390
+ }
391
+ async getJson(path2) {
392
+ const data = await this.getString(path2);
393
+ return JSON.parse(data);
394
+ }
395
+ async getString(path2) {
396
+ const buffer = await this.getBuffer(path2);
397
+ return buffer.toString("utf-8");
398
+ }
399
+ async getBuffer(path2) {
400
+ const containerClient = this.blobServiceClient.getContainerClient(this.config.containerName);
401
+ const blobClient = containerClient.getBlobClient(path2);
402
+ const downloadResponse = await blobClient.download();
403
+ if (!downloadResponse.readableStreamBody) {
404
+ throw new Error("Failed to download blob");
405
+ }
406
+ return await this.streamToBuffer(downloadResponse.readableStreamBody);
407
+ }
408
+ async getStream(path2) {
409
+ const containerClient = this.blobServiceClient.getContainerClient(this.config.containerName);
410
+ const blobClient = containerClient.getBlobClient(path2);
411
+ const downloadResponse = await blobClient.download();
412
+ if (!downloadResponse.readableStreamBody) {
413
+ throw new Error("Failed to download blob");
414
+ }
415
+ return downloadResponse.readableStreamBody;
416
+ }
417
+ async delete(path2) {
418
+ const containerClient = this.blobServiceClient.getContainerClient(this.config.containerName);
419
+ const blobClient = containerClient.getBlobClient(path2);
420
+ await blobClient.delete();
421
+ return true;
422
+ }
423
+ async metadata(path2) {
424
+ const containerClient = this.blobServiceClient.getContainerClient(this.config.containerName);
425
+ const blobClient = containerClient.getBlobClient(path2);
426
+ const properties = await blobClient.getProperties();
427
+ return {
428
+ size: properties.contentLength || 0,
429
+ mimeType: properties.contentType || mime3.lookup(path2) || "unknown",
430
+ lastModifiedDate: properties.lastModified?.toISOString() || (/* @__PURE__ */ new Date(0)).toISOString()
431
+ };
432
+ }
433
+ async streamToBuffer(readableStream) {
434
+ return new Promise((resolve, reject) => {
435
+ const chunks = [];
436
+ readableStream.on("data", (chunk) => {
437
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
438
+ });
439
+ readableStream.on("end", () => {
440
+ resolve(Buffer.concat(chunks));
441
+ });
442
+ readableStream.on("error", reject);
443
+ });
444
+ }
445
+ };
446
+
447
+ // src/providers/FTPStorageProvider.mts
448
+ var import_basic_ftp = require("basic-ftp");
449
+ var import_stream5 = __toESM(require("stream"), 1);
450
+ var mime4 = __toESM(require("mime-types"), 1);
451
+ var FTPStorageProvider = class {
452
+ constructor(config) {
453
+ this.config = config;
454
+ }
455
+ static {
456
+ __name(this, "FTPStorageProvider");
457
+ }
458
+ async getClient() {
459
+ const client = new import_basic_ftp.Client();
460
+ await client.access({
461
+ host: this.config.host,
462
+ port: this.config.port || 21,
463
+ user: this.config.user || "anonymous",
464
+ password: this.config.password || "",
465
+ secure: this.config.secure || false
466
+ });
467
+ return client;
468
+ }
469
+ async exists(path2) {
470
+ const client = await this.getClient();
471
+ try {
472
+ await client.size(path2);
473
+ return true;
474
+ } catch (error) {
475
+ return false;
476
+ } finally {
477
+ client.close();
478
+ }
479
+ }
480
+ async put(path2, content) {
481
+ const client = await this.getClient();
482
+ try {
483
+ let stream;
484
+ if (typeof content === "string" || content instanceof Buffer) {
485
+ const readable = new import_stream5.Readable();
486
+ readable.push(typeof content === "string" ? Buffer.from(content) : content);
487
+ readable.push(null);
488
+ stream = readable;
489
+ } else if (typeof content === "object" && !(content instanceof import_stream5.default)) {
490
+ const readable = new import_stream5.Readable();
491
+ readable.push(Buffer.from(JSON.stringify(content)));
492
+ readable.push(null);
493
+ stream = readable;
494
+ } else if (content instanceof import_stream5.default) {
495
+ stream = content;
496
+ } else {
497
+ throw new Error("Unsupported content type");
498
+ }
499
+ await client.uploadFrom(stream, path2);
500
+ return true;
501
+ } finally {
502
+ client.close();
503
+ }
504
+ }
505
+ async getJson(path2) {
506
+ const data = await this.getString(path2);
507
+ return JSON.parse(data);
508
+ }
509
+ async getString(path2) {
510
+ const buffer = await this.getBuffer(path2);
511
+ return buffer.toString("utf-8");
512
+ }
513
+ async getBuffer(path2) {
514
+ const client = await this.getClient();
515
+ try {
516
+ const chunks = [];
517
+ const writable = new import_stream5.PassThrough();
518
+ writable.on("data", (chunk) => {
519
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
520
+ });
521
+ await client.downloadTo(writable, path2);
522
+ return Buffer.concat(chunks);
523
+ } finally {
524
+ client.close();
525
+ }
526
+ }
527
+ async getStream(path2) {
528
+ const client = await this.getClient();
529
+ const passThrough = new import_stream5.PassThrough();
530
+ client.downloadTo(passThrough, path2).then(() => client.close()).catch((error) => {
531
+ client.close();
532
+ passThrough.destroy(error);
533
+ });
534
+ passThrough.on("close", () => {
535
+ try {
536
+ client.close();
537
+ } catch {
538
+ }
539
+ });
540
+ return passThrough;
541
+ }
542
+ async delete(path2) {
543
+ const client = await this.getClient();
544
+ try {
545
+ await client.remove(path2);
546
+ return true;
547
+ } finally {
548
+ client.close();
549
+ }
550
+ }
551
+ async metadata(path2) {
552
+ const client = await this.getClient();
553
+ try {
554
+ const size = await client.size(path2);
555
+ const lastMod = await client.lastMod(path2);
556
+ return {
557
+ size,
558
+ mimeType: mime4.lookup(path2) || "unknown",
559
+ lastModifiedDate: lastMod?.toISOString() || (/* @__PURE__ */ new Date(0)).toISOString()
560
+ };
561
+ } finally {
562
+ client.close();
563
+ }
564
+ }
565
+ };
566
+
567
+ // src/providers/SFTPStorageProvider.mts
568
+ var import_ssh2_sftp_client = __toESM(require("ssh2-sftp-client"), 1);
569
+ var import_stream6 = __toESM(require("stream"), 1);
570
+ var mime5 = __toESM(require("mime-types"), 1);
571
+ var SFTPStorageProvider = class {
572
+ constructor(config) {
573
+ this.config = config;
574
+ }
575
+ static {
576
+ __name(this, "SFTPStorageProvider");
577
+ }
578
+ async getClient() {
579
+ const client = new import_ssh2_sftp_client.default();
580
+ await client.connect({
581
+ host: this.config.host,
582
+ port: this.config.port || 22,
583
+ username: this.config.username,
584
+ password: this.config.password,
585
+ privateKey: this.config.privateKey,
586
+ passphrase: this.config.passphrase
587
+ });
588
+ return client;
589
+ }
590
+ async exists(path2) {
591
+ const client = await this.getClient();
592
+ try {
593
+ const result = await client.exists(path2);
594
+ return result !== false;
595
+ } catch (error) {
596
+ return false;
597
+ } finally {
598
+ await client.end();
599
+ }
600
+ }
601
+ async put(path2, content) {
602
+ const client = await this.getClient();
603
+ try {
604
+ let data;
605
+ if (typeof content === "string") {
606
+ data = content;
607
+ } else if (content instanceof Buffer) {
608
+ data = content;
609
+ } else if (typeof content === "object" && !(content instanceof import_stream6.default)) {
610
+ data = Buffer.from(JSON.stringify(content));
611
+ } else if (content instanceof import_stream6.default) {
612
+ data = content;
613
+ } else {
614
+ throw new Error("Unsupported content type");
615
+ }
616
+ await client.put(data, path2);
617
+ return true;
618
+ } finally {
619
+ await client.end();
620
+ }
621
+ }
622
+ async getJson(path2) {
623
+ const data = await this.getString(path2);
624
+ return JSON.parse(data);
625
+ }
626
+ async getString(path2) {
627
+ const buffer = await this.getBuffer(path2);
628
+ return buffer.toString("utf-8");
629
+ }
630
+ async getBuffer(path2) {
631
+ const client = await this.getClient();
632
+ try {
633
+ const buffer = await client.get(path2);
634
+ return buffer;
635
+ } finally {
636
+ await client.end();
637
+ }
638
+ }
639
+ async getStream(path2) {
640
+ const client = await this.getClient();
641
+ const passThrough = new import_stream6.PassThrough();
642
+ client.get(path2).then((data) => {
643
+ if (data instanceof Buffer) {
644
+ const readable = new import_stream6.Readable();
645
+ readable.push(data);
646
+ readable.push(null);
647
+ readable.pipe(passThrough);
648
+ } else if (data instanceof import_stream6.default) {
649
+ data.pipe(passThrough);
650
+ }
651
+ return client.end();
652
+ }).catch((error) => {
653
+ client.end().catch(() => {
654
+ });
655
+ passThrough.destroy(error);
656
+ });
657
+ passThrough.on("close", () => {
658
+ client.end().catch(() => {
659
+ });
660
+ });
661
+ return passThrough;
662
+ }
663
+ async delete(path2) {
664
+ const client = await this.getClient();
665
+ try {
666
+ await client.delete(path2);
667
+ return true;
668
+ } finally {
669
+ await client.end();
670
+ }
671
+ }
672
+ async metadata(path2) {
673
+ const client = await this.getClient();
674
+ try {
675
+ const stats = await client.stat(path2);
676
+ return {
677
+ size: stats.size || 0,
678
+ mimeType: mime5.lookup(path2) || "unknown",
679
+ lastModifiedDate: new Date((stats.modifyTime || 0) * 1e3).toISOString()
680
+ };
681
+ } finally {
682
+ await client.end();
683
+ }
684
+ }
685
+ };
686
+
253
687
  // src/StorageProviderFactory.mts
254
688
  var import_neko_helper = require("@devbro/neko-helper");
255
689
  var StorageProviderFactory = class _StorageProviderFactory {
@@ -267,7 +701,11 @@ var StorageProviderFactory = class _StorageProviderFactory {
267
701
  // Annotate the CommonJS export names for ESM import in node:
268
702
  0 && (module.exports = {
269
703
  AWSS3StorageProvider,
704
+ AzureBlobStorageProvider,
705
+ FTPStorageProvider,
706
+ GCPStorageProvider,
270
707
  LocalStorageProvider,
708
+ SFTPStorageProvider,
271
709
  Storage,
272
710
  StorageProviderFactory
273
711
  });