@kumix/storage 0.1.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 +291 -0
- package/dist/cloudinary.d.ts +93 -0
- package/dist/cloudinary.d.ts.map +1 -0
- package/dist/cloudinary.js +516 -0
- package/dist/cloudinary.js.map +1 -0
- package/dist/config-J6AdDh_c.d.ts +594 -0
- package/dist/config-J6AdDh_c.d.ts.map +1 -0
- package/dist/config-t-NVJICl.js +410 -0
- package/dist/config-t-NVJICl.js.map +1 -0
- package/dist/helpers.d.ts +764 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +1042 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/s3.d.ts +109 -0
- package/dist/s3.d.ts.map +1 -0
- package/dist/s3.js +779 -0
- package/dist/s3.js.map +1 -0
- package/package.json +94 -0
package/dist/s3.js
ADDED
|
@@ -0,0 +1,779 @@
|
|
|
1
|
+
import { s as loadS3Config } from "./config-t-NVJICl.js";
|
|
2
|
+
import { buildPublicUrl, getMimeType } from "./helpers.js";
|
|
3
|
+
import { CopyObjectCommand, DeleteObjectCommand, DeleteObjectsCommand, GetObjectCommand, HeadObjectCommand, ListObjectsV2Command, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
|
|
4
|
+
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
5
|
+
//#region src/operations/file-operations.ts
|
|
6
|
+
/**
|
|
7
|
+
* S3 file operations implementation
|
|
8
|
+
* Provides comprehensive file management operations for S3-compatible storage providers
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* S3 file operations implementation
|
|
12
|
+
* Handles all file-related operations for S3-compatible storage providers
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
var FileOperations = class {
|
|
16
|
+
client;
|
|
17
|
+
config;
|
|
18
|
+
constructor(client, config) {
|
|
19
|
+
this.client = client;
|
|
20
|
+
this.config = config;
|
|
21
|
+
}
|
|
22
|
+
async upload(options) {
|
|
23
|
+
try {
|
|
24
|
+
const contentType = options.contentType || getMimeType(options.key);
|
|
25
|
+
const command = new PutObjectCommand({
|
|
26
|
+
Bucket: this.config.bucket,
|
|
27
|
+
Key: options.key,
|
|
28
|
+
Body: options.file,
|
|
29
|
+
ContentType: contentType,
|
|
30
|
+
Metadata: options.metadata,
|
|
31
|
+
CacheControl: options.cacheControl,
|
|
32
|
+
ContentDisposition: options.contentDisposition,
|
|
33
|
+
ACL: options.acl,
|
|
34
|
+
Expires: options.expires
|
|
35
|
+
});
|
|
36
|
+
const result = await this.client.send(command);
|
|
37
|
+
const publicUrl = this.getPublicUrl(options.key);
|
|
38
|
+
return {
|
|
39
|
+
success: true,
|
|
40
|
+
key: options.key,
|
|
41
|
+
url: publicUrl,
|
|
42
|
+
publicUrl,
|
|
43
|
+
etag: result.ETag?.replace(/"/g, ""),
|
|
44
|
+
size: typeof options.file === "string" ? Buffer.byteLength(options.file, "utf8") : options.file.length
|
|
45
|
+
};
|
|
46
|
+
} catch (error) {
|
|
47
|
+
return {
|
|
48
|
+
success: false,
|
|
49
|
+
error: error instanceof Error ? error.message : "Upload failed"
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async download(options) {
|
|
54
|
+
try {
|
|
55
|
+
const command = new GetObjectCommand({
|
|
56
|
+
Bucket: this.config.bucket,
|
|
57
|
+
Key: options.key,
|
|
58
|
+
Range: options.range
|
|
59
|
+
});
|
|
60
|
+
const result = await this.client.send(command);
|
|
61
|
+
if (!result.Body) return {
|
|
62
|
+
success: false,
|
|
63
|
+
error: "No data received"
|
|
64
|
+
};
|
|
65
|
+
const chunks = [];
|
|
66
|
+
const reader = result.Body.transformToWebStream().getReader();
|
|
67
|
+
while (true) {
|
|
68
|
+
const { done, value } = await reader.read();
|
|
69
|
+
if (done) break;
|
|
70
|
+
chunks.push(value);
|
|
71
|
+
}
|
|
72
|
+
const data = Buffer.concat(chunks);
|
|
73
|
+
return {
|
|
74
|
+
success: true,
|
|
75
|
+
content: data,
|
|
76
|
+
data,
|
|
77
|
+
contentType: result.ContentType,
|
|
78
|
+
contentLength: result.ContentLength,
|
|
79
|
+
lastModified: result.LastModified,
|
|
80
|
+
etag: result.ETag?.replace(/"/g, ""),
|
|
81
|
+
metadata: result.Metadata
|
|
82
|
+
};
|
|
83
|
+
} catch (error) {
|
|
84
|
+
return {
|
|
85
|
+
success: false,
|
|
86
|
+
error: error instanceof Error ? error.message : "Download failed"
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async delete(options) {
|
|
91
|
+
try {
|
|
92
|
+
const command = new DeleteObjectCommand({
|
|
93
|
+
Bucket: this.config.bucket,
|
|
94
|
+
Key: options.key
|
|
95
|
+
});
|
|
96
|
+
await this.client.send(command);
|
|
97
|
+
return { success: true };
|
|
98
|
+
} catch (error) {
|
|
99
|
+
return {
|
|
100
|
+
success: false,
|
|
101
|
+
error: error instanceof Error ? error.message : "Delete failed"
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async batchDelete(options) {
|
|
106
|
+
try {
|
|
107
|
+
const command = new DeleteObjectsCommand({
|
|
108
|
+
Bucket: this.config.bucket,
|
|
109
|
+
Delete: {
|
|
110
|
+
Objects: options.keys.map((key) => ({ Key: key })),
|
|
111
|
+
Quiet: false
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
const result = await this.client.send(command);
|
|
115
|
+
const deleted = result.Deleted?.map((obj) => obj.Key).filter(Boolean);
|
|
116
|
+
const errors = result.Errors?.map((err) => ({
|
|
117
|
+
key: err.Key || "",
|
|
118
|
+
error: err.Message || "Unknown error"
|
|
119
|
+
})) || [];
|
|
120
|
+
return {
|
|
121
|
+
success: true,
|
|
122
|
+
deleted,
|
|
123
|
+
errors: errors.length > 0 ? errors : void 0
|
|
124
|
+
};
|
|
125
|
+
} catch (error) {
|
|
126
|
+
return {
|
|
127
|
+
success: false,
|
|
128
|
+
errors: options.keys.map((key) => ({
|
|
129
|
+
key,
|
|
130
|
+
error: error instanceof Error ? error.message : "Batch delete failed"
|
|
131
|
+
}))
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async list(options = {}) {
|
|
136
|
+
try {
|
|
137
|
+
const command = new ListObjectsV2Command({
|
|
138
|
+
Bucket: this.config.bucket,
|
|
139
|
+
Prefix: options.prefix,
|
|
140
|
+
Delimiter: options.delimiter,
|
|
141
|
+
MaxKeys: options.maxKeys,
|
|
142
|
+
ContinuationToken: options.continuationToken
|
|
143
|
+
});
|
|
144
|
+
const result = await this.client.send(command);
|
|
145
|
+
return {
|
|
146
|
+
success: true,
|
|
147
|
+
files: result.Contents?.map((obj) => ({
|
|
148
|
+
key: obj.Key,
|
|
149
|
+
size: obj.Size || 0,
|
|
150
|
+
lastModified: obj.LastModified || /* @__PURE__ */ new Date(),
|
|
151
|
+
etag: obj.ETag?.replace(/"/g, "") || "",
|
|
152
|
+
contentType: getMimeType(obj.Key)
|
|
153
|
+
})) || [],
|
|
154
|
+
isTruncated: result.IsTruncated,
|
|
155
|
+
nextContinuationToken: result.NextContinuationToken,
|
|
156
|
+
commonPrefixes: result.CommonPrefixes?.map((cp) => cp.Prefix)
|
|
157
|
+
};
|
|
158
|
+
} catch (error) {
|
|
159
|
+
return {
|
|
160
|
+
success: false,
|
|
161
|
+
error: error instanceof Error ? error.message : "List failed"
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
async exists(key) {
|
|
166
|
+
try {
|
|
167
|
+
const command = new HeadObjectCommand({
|
|
168
|
+
Bucket: this.config.bucket,
|
|
169
|
+
Key: key
|
|
170
|
+
});
|
|
171
|
+
const result = await this.client.send(command);
|
|
172
|
+
return {
|
|
173
|
+
exists: true,
|
|
174
|
+
fileInfo: {
|
|
175
|
+
key,
|
|
176
|
+
size: result.ContentLength || 0,
|
|
177
|
+
lastModified: result.LastModified || /* @__PURE__ */ new Date(),
|
|
178
|
+
etag: result.ETag?.replace(/"/g, "") || "",
|
|
179
|
+
contentType: result.ContentType,
|
|
180
|
+
metadata: result.Metadata
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
} catch (error) {
|
|
184
|
+
if (error instanceof Error && error.name === "NotFound") return { exists: false };
|
|
185
|
+
return {
|
|
186
|
+
exists: false,
|
|
187
|
+
error: error instanceof Error ? error.message : "Check existence failed"
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
async copy(options) {
|
|
192
|
+
try {
|
|
193
|
+
const command = new CopyObjectCommand({
|
|
194
|
+
Bucket: this.config.bucket,
|
|
195
|
+
CopySource: `${this.config.bucket}/${options.sourceKey}`,
|
|
196
|
+
Key: options.destinationKey,
|
|
197
|
+
Metadata: options.metadata,
|
|
198
|
+
MetadataDirective: options.metadataDirective || "COPY"
|
|
199
|
+
});
|
|
200
|
+
return {
|
|
201
|
+
success: true,
|
|
202
|
+
etag: (await this.client.send(command)).CopyObjectResult?.ETag?.replace(/"/g, "")
|
|
203
|
+
};
|
|
204
|
+
} catch (error) {
|
|
205
|
+
return {
|
|
206
|
+
success: false,
|
|
207
|
+
error: error instanceof Error ? error.message : "Copy failed"
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
async move(options) {
|
|
212
|
+
try {
|
|
213
|
+
const copyResult = await this.copy({
|
|
214
|
+
sourceKey: options.sourceKey,
|
|
215
|
+
destinationKey: options.destinationKey,
|
|
216
|
+
metadata: options.metadata,
|
|
217
|
+
metadataDirective: options.metadataDirective
|
|
218
|
+
});
|
|
219
|
+
if (!copyResult.success) return {
|
|
220
|
+
success: false,
|
|
221
|
+
error: copyResult.error || "Move failed during copy operation"
|
|
222
|
+
};
|
|
223
|
+
const deleteResult = await this.delete({ key: options.sourceKey });
|
|
224
|
+
if (!deleteResult.success) return {
|
|
225
|
+
success: false,
|
|
226
|
+
error: deleteResult.error || "Move failed during delete operation"
|
|
227
|
+
};
|
|
228
|
+
return {
|
|
229
|
+
success: true,
|
|
230
|
+
etag: copyResult.etag
|
|
231
|
+
};
|
|
232
|
+
} catch (error) {
|
|
233
|
+
return {
|
|
234
|
+
success: false,
|
|
235
|
+
error: error instanceof Error ? error.message : "Move failed"
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
async duplicate(options) {
|
|
240
|
+
return this.copy(options);
|
|
241
|
+
}
|
|
242
|
+
async getPresignedUrl(options) {
|
|
243
|
+
try {
|
|
244
|
+
const expiresIn = options.expiresIn || 3600;
|
|
245
|
+
let command;
|
|
246
|
+
if (options.operation === "get") command = new GetObjectCommand({
|
|
247
|
+
Bucket: this.config.bucket,
|
|
248
|
+
Key: options.key
|
|
249
|
+
});
|
|
250
|
+
else command = new PutObjectCommand({
|
|
251
|
+
Bucket: this.config.bucket,
|
|
252
|
+
Key: options.key,
|
|
253
|
+
ContentType: options.contentType
|
|
254
|
+
});
|
|
255
|
+
return {
|
|
256
|
+
success: true,
|
|
257
|
+
url: await getSignedUrl(this.client, command, { expiresIn }),
|
|
258
|
+
expiresAt: new Date(Date.now() + expiresIn * 1e3)
|
|
259
|
+
};
|
|
260
|
+
} catch (error) {
|
|
261
|
+
return {
|
|
262
|
+
success: false,
|
|
263
|
+
error: error instanceof Error ? error.message : "Presigned URL generation failed"
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
getPublicUrl(key) {
|
|
268
|
+
const isCloudflare = this.config.provider === "cloudflare-r2";
|
|
269
|
+
const isSupabase = this.config.provider === "supabase";
|
|
270
|
+
if (this.config.publicUrl) return buildPublicUrl(this.config.publicUrl, isCloudflare ? "" : this.config.bucket, key, isSupabase);
|
|
271
|
+
if (this.config.endpoint) return buildPublicUrl(this.config.endpoint, this.config.bucket, key, isSupabase);
|
|
272
|
+
return `https://${this.config.bucket}.s3.${this.config.region}.amazonaws.com/${key}`;
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
//#endregion
|
|
276
|
+
//#region src/operations/folder-operations.ts
|
|
277
|
+
/**
|
|
278
|
+
* S3 folder operations implementation
|
|
279
|
+
* Provides comprehensive folder management operations for S3-compatible storage providers
|
|
280
|
+
*/
|
|
281
|
+
/**
|
|
282
|
+
* S3 folder operations implementation
|
|
283
|
+
* Handles all folder-related operations for S3-compatible storage providers
|
|
284
|
+
* @internal
|
|
285
|
+
*/
|
|
286
|
+
var FolderOperations = class {
|
|
287
|
+
client;
|
|
288
|
+
config;
|
|
289
|
+
constructor(client, config) {
|
|
290
|
+
this.client = client;
|
|
291
|
+
this.config = config;
|
|
292
|
+
}
|
|
293
|
+
async createFolder(options) {
|
|
294
|
+
try {
|
|
295
|
+
const folderPath = options.path.endsWith("/") ? options.path : `${options.path}/`;
|
|
296
|
+
const command = new PutObjectCommand({
|
|
297
|
+
Bucket: this.config.bucket,
|
|
298
|
+
Key: folderPath,
|
|
299
|
+
Body: "",
|
|
300
|
+
ContentType: "application/x-directory"
|
|
301
|
+
});
|
|
302
|
+
await this.client.send(command);
|
|
303
|
+
return {
|
|
304
|
+
success: true,
|
|
305
|
+
path: folderPath
|
|
306
|
+
};
|
|
307
|
+
} catch (error) {
|
|
308
|
+
return {
|
|
309
|
+
success: false,
|
|
310
|
+
error: error instanceof Error ? error.message : "Create folder failed"
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
async deleteFolder(options) {
|
|
315
|
+
try {
|
|
316
|
+
const folderPath = options.path.endsWith("/") ? options.path : `${options.path}/`;
|
|
317
|
+
if (options.recursive) {
|
|
318
|
+
const listCommand = new ListObjectsV2Command({
|
|
319
|
+
Bucket: this.config.bucket,
|
|
320
|
+
Prefix: folderPath
|
|
321
|
+
});
|
|
322
|
+
const listResult = await this.client.send(listCommand);
|
|
323
|
+
if (!listResult.Contents || listResult.Contents.length === 0) return {
|
|
324
|
+
success: true,
|
|
325
|
+
deletedFiles: []
|
|
326
|
+
};
|
|
327
|
+
const deleteCommand = new DeleteObjectsCommand({
|
|
328
|
+
Bucket: this.config.bucket,
|
|
329
|
+
Delete: {
|
|
330
|
+
Objects: listResult.Contents.map((obj) => ({ Key: obj.Key })),
|
|
331
|
+
Quiet: false
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
return {
|
|
335
|
+
success: true,
|
|
336
|
+
deletedFiles: (await this.client.send(deleteCommand)).Deleted?.map((obj) => obj.Key).filter(Boolean) || []
|
|
337
|
+
};
|
|
338
|
+
} else {
|
|
339
|
+
const deleteCommand = new DeleteObjectsCommand({
|
|
340
|
+
Bucket: this.config.bucket,
|
|
341
|
+
Delete: {
|
|
342
|
+
Objects: [{ Key: folderPath }],
|
|
343
|
+
Quiet: false
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
await this.client.send(deleteCommand);
|
|
347
|
+
return {
|
|
348
|
+
success: true,
|
|
349
|
+
deletedFiles: [folderPath]
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
} catch (error) {
|
|
353
|
+
return {
|
|
354
|
+
success: false,
|
|
355
|
+
error: error instanceof Error ? error.message : "Delete folder failed"
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
async listFolders(options = {}) {
|
|
360
|
+
try {
|
|
361
|
+
const command = new ListObjectsV2Command({
|
|
362
|
+
Bucket: this.config.bucket,
|
|
363
|
+
Prefix: options.prefix,
|
|
364
|
+
Delimiter: options.delimiter || "/",
|
|
365
|
+
MaxKeys: options.maxKeys,
|
|
366
|
+
ContinuationToken: options.continuationToken
|
|
367
|
+
});
|
|
368
|
+
const result = await this.client.send(command);
|
|
369
|
+
return {
|
|
370
|
+
success: true,
|
|
371
|
+
folders: result.CommonPrefixes?.map((cp) => {
|
|
372
|
+
const path = cp.Prefix;
|
|
373
|
+
return {
|
|
374
|
+
name: path.split("/").filter(Boolean).pop() || "",
|
|
375
|
+
path,
|
|
376
|
+
size: 0,
|
|
377
|
+
fileCount: 0,
|
|
378
|
+
lastModified: /* @__PURE__ */ new Date()
|
|
379
|
+
};
|
|
380
|
+
}) || [],
|
|
381
|
+
files: result.Contents?.map((obj) => ({
|
|
382
|
+
key: obj.Key,
|
|
383
|
+
size: obj.Size || 0,
|
|
384
|
+
lastModified: obj.LastModified || /* @__PURE__ */ new Date(),
|
|
385
|
+
etag: obj.ETag?.replace(/"/g, "") || "",
|
|
386
|
+
contentType: getMimeType(obj.Key)
|
|
387
|
+
})) || [],
|
|
388
|
+
isTruncated: result.IsTruncated,
|
|
389
|
+
nextContinuationToken: result.NextContinuationToken
|
|
390
|
+
};
|
|
391
|
+
} catch (error) {
|
|
392
|
+
return {
|
|
393
|
+
success: false,
|
|
394
|
+
error: error instanceof Error ? error.message : "List folders failed"
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
async folderExists(path) {
|
|
399
|
+
try {
|
|
400
|
+
const folderPath = path.endsWith("/") ? path : `${path}/`;
|
|
401
|
+
const command = new ListObjectsV2Command({
|
|
402
|
+
Bucket: this.config.bucket,
|
|
403
|
+
Prefix: folderPath,
|
|
404
|
+
MaxKeys: 1
|
|
405
|
+
});
|
|
406
|
+
const result = await this.client.send(command);
|
|
407
|
+
if (result.Contents && result.Contents.length > 0 || result.CommonPrefixes && result.CommonPrefixes.length > 0) return {
|
|
408
|
+
exists: true,
|
|
409
|
+
folderInfo: {
|
|
410
|
+
name: folderPath.split("/").filter(Boolean).pop() || "",
|
|
411
|
+
path: folderPath,
|
|
412
|
+
size: 0,
|
|
413
|
+
fileCount: 0,
|
|
414
|
+
lastModified: /* @__PURE__ */ new Date()
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
return { exists: false };
|
|
418
|
+
} catch (error) {
|
|
419
|
+
return {
|
|
420
|
+
exists: false,
|
|
421
|
+
error: error instanceof Error ? error.message : "Check folder existence failed"
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
async renameFolder(options) {
|
|
426
|
+
try {
|
|
427
|
+
const oldPath = options.oldPath.endsWith("/") ? options.oldPath : `${options.oldPath}/`;
|
|
428
|
+
const newPath = options.newPath.endsWith("/") ? options.newPath : `${options.newPath}/`;
|
|
429
|
+
const listCommand = new ListObjectsV2Command({
|
|
430
|
+
Bucket: this.config.bucket,
|
|
431
|
+
Prefix: oldPath
|
|
432
|
+
});
|
|
433
|
+
const listResult = await this.client.send(listCommand);
|
|
434
|
+
if (!listResult.Contents || listResult.Contents.length === 0) return {
|
|
435
|
+
success: true,
|
|
436
|
+
movedFiles: []
|
|
437
|
+
};
|
|
438
|
+
const movedFiles = [];
|
|
439
|
+
for (const obj of listResult.Contents) {
|
|
440
|
+
const oldKey = obj.Key;
|
|
441
|
+
const newKey = oldKey.replace(oldPath, newPath);
|
|
442
|
+
const copyCommand = new CopyObjectCommand({
|
|
443
|
+
Bucket: this.config.bucket,
|
|
444
|
+
Key: newKey,
|
|
445
|
+
CopySource: `${this.config.bucket}/${oldKey}`
|
|
446
|
+
});
|
|
447
|
+
await this.client.send(copyCommand);
|
|
448
|
+
movedFiles.push(newKey);
|
|
449
|
+
}
|
|
450
|
+
const deleteCommand = new DeleteObjectsCommand({
|
|
451
|
+
Bucket: this.config.bucket,
|
|
452
|
+
Delete: {
|
|
453
|
+
Objects: listResult.Contents.map((obj) => ({ Key: obj.Key })),
|
|
454
|
+
Quiet: true
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
await this.client.send(deleteCommand);
|
|
458
|
+
return {
|
|
459
|
+
success: true,
|
|
460
|
+
movedFiles
|
|
461
|
+
};
|
|
462
|
+
} catch (error) {
|
|
463
|
+
return {
|
|
464
|
+
success: false,
|
|
465
|
+
error: error instanceof Error ? error.message : "Rename folder failed"
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
async copyFolder(options) {
|
|
470
|
+
try {
|
|
471
|
+
const sourcePath = options.sourcePath.endsWith("/") ? options.sourcePath : `${options.sourcePath}/`;
|
|
472
|
+
const destPath = options.destinationPath.endsWith("/") ? options.destinationPath : `${options.destinationPath}/`;
|
|
473
|
+
const listCommand = new ListObjectsV2Command({
|
|
474
|
+
Bucket: this.config.bucket,
|
|
475
|
+
Prefix: sourcePath
|
|
476
|
+
});
|
|
477
|
+
const listResult = await this.client.send(listCommand);
|
|
478
|
+
if (!listResult.Contents || listResult.Contents.length === 0) return {
|
|
479
|
+
success: true,
|
|
480
|
+
copiedFiles: []
|
|
481
|
+
};
|
|
482
|
+
const copiedFiles = [];
|
|
483
|
+
for (const obj of listResult.Contents) {
|
|
484
|
+
const sourceKey = obj.Key;
|
|
485
|
+
const destKey = sourceKey.replace(sourcePath, destPath);
|
|
486
|
+
const copyCommand = new CopyObjectCommand({
|
|
487
|
+
Bucket: this.config.bucket,
|
|
488
|
+
Key: destKey,
|
|
489
|
+
CopySource: `${this.config.bucket}/${sourceKey}`
|
|
490
|
+
});
|
|
491
|
+
await this.client.send(copyCommand);
|
|
492
|
+
copiedFiles.push(destKey);
|
|
493
|
+
}
|
|
494
|
+
return {
|
|
495
|
+
success: true,
|
|
496
|
+
copiedFiles
|
|
497
|
+
};
|
|
498
|
+
} catch (error) {
|
|
499
|
+
return {
|
|
500
|
+
success: false,
|
|
501
|
+
error: error instanceof Error ? error.message : "Copy folder failed"
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
//#endregion
|
|
507
|
+
//#region src/providers/s3.ts
|
|
508
|
+
/**
|
|
509
|
+
* S3-compatible storage provider implementation
|
|
510
|
+
* Provides cloud storage operations using AWS S3 SDK for S3-compatible services
|
|
511
|
+
*/
|
|
512
|
+
/**
|
|
513
|
+
* S3-compatible storage provider
|
|
514
|
+
* Implements StorageInterface for AWS S3 and S3-compatible services
|
|
515
|
+
* @internal
|
|
516
|
+
*/
|
|
517
|
+
var S3Provider = class {
|
|
518
|
+
client;
|
|
519
|
+
config;
|
|
520
|
+
fileOps;
|
|
521
|
+
folderOps;
|
|
522
|
+
constructor(config) {
|
|
523
|
+
this.config = config;
|
|
524
|
+
this.client = new S3Client({
|
|
525
|
+
region: config.region,
|
|
526
|
+
credentials: {
|
|
527
|
+
accessKeyId: config.accessKeyId,
|
|
528
|
+
secretAccessKey: config.secretAccessKey
|
|
529
|
+
},
|
|
530
|
+
endpoint: config.endpoint,
|
|
531
|
+
forcePathStyle: config.forcePathStyle || false
|
|
532
|
+
});
|
|
533
|
+
this.fileOps = new FileOperations(this.client, this.config);
|
|
534
|
+
this.folderOps = new FolderOperations(this.client, this.config);
|
|
535
|
+
}
|
|
536
|
+
async upload(options) {
|
|
537
|
+
return this.fileOps.upload(options);
|
|
538
|
+
}
|
|
539
|
+
async download(options) {
|
|
540
|
+
return this.fileOps.download(options);
|
|
541
|
+
}
|
|
542
|
+
async delete(options) {
|
|
543
|
+
return this.fileOps.delete(options);
|
|
544
|
+
}
|
|
545
|
+
async batchDelete(options) {
|
|
546
|
+
return this.fileOps.batchDelete(options);
|
|
547
|
+
}
|
|
548
|
+
async list(options) {
|
|
549
|
+
return this.fileOps.list(options);
|
|
550
|
+
}
|
|
551
|
+
async exists(key) {
|
|
552
|
+
return this.fileOps.exists(key);
|
|
553
|
+
}
|
|
554
|
+
async copy(options) {
|
|
555
|
+
return this.fileOps.copy(options);
|
|
556
|
+
}
|
|
557
|
+
async move(options) {
|
|
558
|
+
return this.fileOps.move(options);
|
|
559
|
+
}
|
|
560
|
+
async duplicate(options) {
|
|
561
|
+
return this.fileOps.duplicate(options);
|
|
562
|
+
}
|
|
563
|
+
async getPresignedUrl(options) {
|
|
564
|
+
return this.fileOps.getPresignedUrl(options);
|
|
565
|
+
}
|
|
566
|
+
getPublicUrl(key) {
|
|
567
|
+
return this.fileOps.getPublicUrl(key);
|
|
568
|
+
}
|
|
569
|
+
async createFolder(options) {
|
|
570
|
+
return this.folderOps.createFolder(options);
|
|
571
|
+
}
|
|
572
|
+
async deleteFolder(options) {
|
|
573
|
+
return this.folderOps.deleteFolder(options);
|
|
574
|
+
}
|
|
575
|
+
async listFolders(options) {
|
|
576
|
+
return this.folderOps.listFolders(options);
|
|
577
|
+
}
|
|
578
|
+
async folderExists(path) {
|
|
579
|
+
return this.folderOps.folderExists(path);
|
|
580
|
+
}
|
|
581
|
+
async renameFolder(options) {
|
|
582
|
+
return this.folderOps.renameFolder(options);
|
|
583
|
+
}
|
|
584
|
+
async copyFolder(options) {
|
|
585
|
+
return this.folderOps.copyFolder(options);
|
|
586
|
+
}
|
|
587
|
+
};
|
|
588
|
+
//#endregion
|
|
589
|
+
//#region src/services/s3.ts
|
|
590
|
+
/**
|
|
591
|
+
* S3 storage service implementation
|
|
592
|
+
* High-level service wrapper for S3-compatible storage operations with convenience methods
|
|
593
|
+
*/
|
|
594
|
+
/**
|
|
595
|
+
* S3 storage service
|
|
596
|
+
* High-level service for S3-compatible storage operations with convenience methods
|
|
597
|
+
* @public
|
|
598
|
+
*/
|
|
599
|
+
var S3Service = class {
|
|
600
|
+
provider;
|
|
601
|
+
config;
|
|
602
|
+
constructor(config) {
|
|
603
|
+
this.config = config || loadS3Config();
|
|
604
|
+
this.provider = this.createProvider(this.config);
|
|
605
|
+
}
|
|
606
|
+
createProvider(config) {
|
|
607
|
+
switch (config.provider) {
|
|
608
|
+
case "aws":
|
|
609
|
+
case "cloudflare-r2":
|
|
610
|
+
case "minio":
|
|
611
|
+
case "digitalocean":
|
|
612
|
+
case "supabase":
|
|
613
|
+
case "custom": return new S3Provider(config);
|
|
614
|
+
default: throw new Error(`Unsupported S3 provider: ${config.provider}`);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
getConfig() {
|
|
618
|
+
return { ...this.config };
|
|
619
|
+
}
|
|
620
|
+
getBucket() {
|
|
621
|
+
return this.config.bucket;
|
|
622
|
+
}
|
|
623
|
+
getRegion() {
|
|
624
|
+
return this.config.region;
|
|
625
|
+
}
|
|
626
|
+
getProvider() {
|
|
627
|
+
return this.config.provider;
|
|
628
|
+
}
|
|
629
|
+
getPublicUrl(key) {
|
|
630
|
+
return this.provider.getPublicUrl(key);
|
|
631
|
+
}
|
|
632
|
+
async upload(options) {
|
|
633
|
+
return this.provider.upload(options);
|
|
634
|
+
}
|
|
635
|
+
async download(options) {
|
|
636
|
+
return this.provider.download(options);
|
|
637
|
+
}
|
|
638
|
+
async delete(options) {
|
|
639
|
+
return this.provider.delete(options);
|
|
640
|
+
}
|
|
641
|
+
async batchDelete(options) {
|
|
642
|
+
return this.provider.batchDelete(options);
|
|
643
|
+
}
|
|
644
|
+
async list(options) {
|
|
645
|
+
return this.provider.list(options);
|
|
646
|
+
}
|
|
647
|
+
async exists(key) {
|
|
648
|
+
return this.provider.exists(key);
|
|
649
|
+
}
|
|
650
|
+
async copy(options) {
|
|
651
|
+
return this.provider.copy(options);
|
|
652
|
+
}
|
|
653
|
+
async move(options) {
|
|
654
|
+
return this.provider.move(options);
|
|
655
|
+
}
|
|
656
|
+
async duplicate(options) {
|
|
657
|
+
return this.provider.duplicate(options);
|
|
658
|
+
}
|
|
659
|
+
async getPresignedUrl(options) {
|
|
660
|
+
return this.provider.getPresignedUrl(options);
|
|
661
|
+
}
|
|
662
|
+
async createFolder(options) {
|
|
663
|
+
return this.provider.createFolder(options);
|
|
664
|
+
}
|
|
665
|
+
async deleteFolder(options) {
|
|
666
|
+
return this.provider.deleteFolder(options);
|
|
667
|
+
}
|
|
668
|
+
async listFolders(options) {
|
|
669
|
+
return this.provider.listFolders(options);
|
|
670
|
+
}
|
|
671
|
+
async folderExists(path) {
|
|
672
|
+
return this.provider.folderExists(path);
|
|
673
|
+
}
|
|
674
|
+
async renameFolder(options) {
|
|
675
|
+
return this.provider.renameFolder(options);
|
|
676
|
+
}
|
|
677
|
+
async copyFolder(options) {
|
|
678
|
+
return this.provider.copyFolder(options);
|
|
679
|
+
}
|
|
680
|
+
async uploadFile(key, file, options) {
|
|
681
|
+
return this.upload({
|
|
682
|
+
key,
|
|
683
|
+
file,
|
|
684
|
+
...options
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
async downloadFile(key) {
|
|
688
|
+
return this.download({ key });
|
|
689
|
+
}
|
|
690
|
+
async deleteFile(key) {
|
|
691
|
+
return this.delete({ key });
|
|
692
|
+
}
|
|
693
|
+
async deleteFiles(keys) {
|
|
694
|
+
return this.batchDelete({ keys });
|
|
695
|
+
}
|
|
696
|
+
async listFiles(prefix, maxKeys) {
|
|
697
|
+
return this.list({
|
|
698
|
+
prefix,
|
|
699
|
+
maxKeys
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
async fileExists(key) {
|
|
703
|
+
return (await this.exists(key)).exists;
|
|
704
|
+
}
|
|
705
|
+
async copyFile(sourceKey, destinationKey, options) {
|
|
706
|
+
return this.copy({
|
|
707
|
+
sourceKey,
|
|
708
|
+
destinationKey,
|
|
709
|
+
...options
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
async moveFile(sourceKey, destinationKey, options) {
|
|
713
|
+
return this.move({
|
|
714
|
+
sourceKey,
|
|
715
|
+
destinationKey,
|
|
716
|
+
...options
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
async duplicateFile(sourceKey, destinationKey, options) {
|
|
720
|
+
return this.duplicate({
|
|
721
|
+
sourceKey,
|
|
722
|
+
destinationKey,
|
|
723
|
+
...options
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
async renameFile(sourceKey, newKey, options) {
|
|
727
|
+
return this.moveFile(sourceKey, newKey, options);
|
|
728
|
+
}
|
|
729
|
+
async getDownloadUrl(key, expiresIn) {
|
|
730
|
+
return this.getPresignedUrl({
|
|
731
|
+
key,
|
|
732
|
+
operation: "get",
|
|
733
|
+
expiresIn
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
async getUploadUrl(key, contentType, expiresIn) {
|
|
737
|
+
return this.getPresignedUrl({
|
|
738
|
+
key,
|
|
739
|
+
operation: "put",
|
|
740
|
+
contentType,
|
|
741
|
+
expiresIn
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
async createFolderPath(path) {
|
|
745
|
+
return this.createFolder({ path });
|
|
746
|
+
}
|
|
747
|
+
async deleteFolderPath(path, recursive = false) {
|
|
748
|
+
return this.deleteFolder({
|
|
749
|
+
path,
|
|
750
|
+
recursive
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
async folderPathExists(path) {
|
|
754
|
+
return (await this.folderExists(path)).exists;
|
|
755
|
+
}
|
|
756
|
+
async renameFolderPath(oldPath, newPath) {
|
|
757
|
+
return this.renameFolder({
|
|
758
|
+
oldPath,
|
|
759
|
+
newPath
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
async copyFolderPath(sourcePath, destinationPath, recursive = true) {
|
|
763
|
+
return this.copyFolder({
|
|
764
|
+
sourcePath,
|
|
765
|
+
destinationPath,
|
|
766
|
+
recursive
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
//#endregion
|
|
771
|
+
//#region src/s3.ts
|
|
772
|
+
function createS3(config, env) {
|
|
773
|
+
if (config) return new S3Service(config);
|
|
774
|
+
return new S3Service(loadS3Config(env));
|
|
775
|
+
}
|
|
776
|
+
//#endregion
|
|
777
|
+
export { S3Service, createS3 };
|
|
778
|
+
|
|
779
|
+
//# sourceMappingURL=s3.js.map
|