@parsrun/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 +176 -0
- package/dist/adapters/memory.d.ts +67 -0
- package/dist/adapters/memory.js +311 -0
- package/dist/adapters/memory.js.map +1 -0
- package/dist/adapters/r2.d.ts +180 -0
- package/dist/adapters/r2.js +818 -0
- package/dist/adapters/r2.js.map +1 -0
- package/dist/adapters/s3.d.ts +76 -0
- package/dist/adapters/s3.js +473 -0
- package/dist/adapters/s3.js.map +1 -0
- package/dist/index.d.ts +111 -0
- package/dist/index.js +1316 -0
- package/dist/index.js.map +1 -0
- package/dist/types-CCTK5LsZ.d.ts +290 -0
- package/package.json +68 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
import {
|
|
3
|
+
type,
|
|
4
|
+
fileMetadata,
|
|
5
|
+
uploadOptions,
|
|
6
|
+
signedUrlOptions,
|
|
7
|
+
listFilesOptions,
|
|
8
|
+
listFilesResult,
|
|
9
|
+
localStorageConfig,
|
|
10
|
+
s3StorageConfig,
|
|
11
|
+
r2StorageConfig,
|
|
12
|
+
gcsStorageConfig,
|
|
13
|
+
storageProviderConfig
|
|
14
|
+
} from "@parsrun/types";
|
|
15
|
+
var StorageError = class extends Error {
|
|
16
|
+
constructor(message, code, statusCode, cause) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.code = code;
|
|
19
|
+
this.statusCode = statusCode;
|
|
20
|
+
this.cause = cause;
|
|
21
|
+
this.name = "StorageError";
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var StorageErrorCodes = {
|
|
25
|
+
NOT_FOUND: "NOT_FOUND",
|
|
26
|
+
ACCESS_DENIED: "ACCESS_DENIED",
|
|
27
|
+
BUCKET_NOT_FOUND: "BUCKET_NOT_FOUND",
|
|
28
|
+
INVALID_KEY: "INVALID_KEY",
|
|
29
|
+
UPLOAD_FAILED: "UPLOAD_FAILED",
|
|
30
|
+
DOWNLOAD_FAILED: "DOWNLOAD_FAILED",
|
|
31
|
+
DELETE_FAILED: "DELETE_FAILED",
|
|
32
|
+
COPY_FAILED: "COPY_FAILED",
|
|
33
|
+
LIST_FAILED: "LIST_FAILED",
|
|
34
|
+
PRESIGN_FAILED: "PRESIGN_FAILED",
|
|
35
|
+
QUOTA_EXCEEDED: "QUOTA_EXCEEDED",
|
|
36
|
+
INVALID_CONFIG: "INVALID_CONFIG",
|
|
37
|
+
ADAPTER_NOT_AVAILABLE: "ADAPTER_NOT_AVAILABLE"
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// src/adapters/s3.ts
|
|
41
|
+
var S3Adapter = class {
|
|
42
|
+
type;
|
|
43
|
+
bucket;
|
|
44
|
+
client = null;
|
|
45
|
+
config;
|
|
46
|
+
basePath;
|
|
47
|
+
constructor(config) {
|
|
48
|
+
this.type = config.type;
|
|
49
|
+
this.bucket = config.bucket;
|
|
50
|
+
this.config = config;
|
|
51
|
+
this.basePath = config.basePath ?? "";
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Lazy load S3 client
|
|
55
|
+
*/
|
|
56
|
+
async getClient() {
|
|
57
|
+
if (this.client) return this.client;
|
|
58
|
+
try {
|
|
59
|
+
const { S3Client } = await import("@aws-sdk/client-s3");
|
|
60
|
+
const clientConfig = {
|
|
61
|
+
region: this.config.region,
|
|
62
|
+
credentials: {
|
|
63
|
+
accessKeyId: this.config.accessKeyId,
|
|
64
|
+
secretAccessKey: this.config.secretAccessKey
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
if (this.config.endpoint) {
|
|
68
|
+
clientConfig.endpoint = this.config.endpoint;
|
|
69
|
+
}
|
|
70
|
+
if (this.config.forcePathStyle !== void 0) {
|
|
71
|
+
clientConfig.forcePathStyle = this.config.forcePathStyle;
|
|
72
|
+
}
|
|
73
|
+
this.client = new S3Client(clientConfig);
|
|
74
|
+
return this.client;
|
|
75
|
+
} catch {
|
|
76
|
+
throw new StorageError(
|
|
77
|
+
"AWS SDK not installed. Run: npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner",
|
|
78
|
+
StorageErrorCodes.ADAPTER_NOT_AVAILABLE
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
getFullKey(key) {
|
|
83
|
+
return this.basePath ? `${this.basePath}/${key}` : key;
|
|
84
|
+
}
|
|
85
|
+
validateKey(key) {
|
|
86
|
+
if (!key || key.includes("..") || key.startsWith("/")) {
|
|
87
|
+
throw new StorageError(
|
|
88
|
+
`Invalid key: ${key}`,
|
|
89
|
+
StorageErrorCodes.INVALID_KEY
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async dataToBody(data) {
|
|
94
|
+
if (data instanceof Uint8Array || typeof data === "string") {
|
|
95
|
+
return data;
|
|
96
|
+
}
|
|
97
|
+
if (data instanceof Blob) {
|
|
98
|
+
const buffer = await data.arrayBuffer();
|
|
99
|
+
return new Uint8Array(buffer);
|
|
100
|
+
}
|
|
101
|
+
return data;
|
|
102
|
+
}
|
|
103
|
+
async upload(key, data, options) {
|
|
104
|
+
this.validateKey(key);
|
|
105
|
+
const fullKey = this.getFullKey(key);
|
|
106
|
+
const client = await this.getClient();
|
|
107
|
+
const { PutObjectCommand } = await import("@aws-sdk/client-s3");
|
|
108
|
+
const body = await this.dataToBody(data);
|
|
109
|
+
const params = {
|
|
110
|
+
Bucket: this.bucket,
|
|
111
|
+
Key: fullKey,
|
|
112
|
+
Body: body,
|
|
113
|
+
ContentType: options?.contentType,
|
|
114
|
+
ContentDisposition: options?.contentDisposition,
|
|
115
|
+
CacheControl: options?.cacheControl,
|
|
116
|
+
ContentEncoding: options?.contentEncoding,
|
|
117
|
+
Metadata: options?.metadata
|
|
118
|
+
};
|
|
119
|
+
if (options?.acl) {
|
|
120
|
+
params.ACL = options.acl;
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
const result = await client.send(new PutObjectCommand(params));
|
|
124
|
+
let size = 0;
|
|
125
|
+
if (typeof body === "string") {
|
|
126
|
+
size = new TextEncoder().encode(body).length;
|
|
127
|
+
} else if (body instanceof Uint8Array) {
|
|
128
|
+
size = body.length;
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
key: fullKey,
|
|
132
|
+
size,
|
|
133
|
+
contentType: options?.contentType ?? void 0,
|
|
134
|
+
lastModified: /* @__PURE__ */ new Date(),
|
|
135
|
+
etag: result.ETag ?? void 0,
|
|
136
|
+
metadata: options?.metadata ?? void 0
|
|
137
|
+
};
|
|
138
|
+
} catch (err) {
|
|
139
|
+
throw new StorageError(
|
|
140
|
+
`Upload failed: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
141
|
+
StorageErrorCodes.UPLOAD_FAILED,
|
|
142
|
+
void 0,
|
|
143
|
+
err
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async download(key, options) {
|
|
148
|
+
this.validateKey(key);
|
|
149
|
+
const fullKey = this.getFullKey(key);
|
|
150
|
+
const client = await this.getClient();
|
|
151
|
+
const { GetObjectCommand } = await import("@aws-sdk/client-s3");
|
|
152
|
+
try {
|
|
153
|
+
const params = {
|
|
154
|
+
Bucket: this.bucket,
|
|
155
|
+
Key: fullKey
|
|
156
|
+
};
|
|
157
|
+
if (options?.rangeStart !== void 0 || options?.rangeEnd !== void 0) {
|
|
158
|
+
const start = options.rangeStart ?? 0;
|
|
159
|
+
const end = options.rangeEnd ?? "";
|
|
160
|
+
params.Range = `bytes=${start}-${end}`;
|
|
161
|
+
}
|
|
162
|
+
if (options?.ifNoneMatch) {
|
|
163
|
+
params.IfNoneMatch = options.ifNoneMatch;
|
|
164
|
+
}
|
|
165
|
+
if (options?.ifModifiedSince) {
|
|
166
|
+
params.IfModifiedSince = options.ifModifiedSince;
|
|
167
|
+
}
|
|
168
|
+
const result = await client.send(new GetObjectCommand(params));
|
|
169
|
+
if (!result.Body) {
|
|
170
|
+
throw new StorageError(
|
|
171
|
+
"Empty response body",
|
|
172
|
+
StorageErrorCodes.DOWNLOAD_FAILED
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
const stream = result.Body;
|
|
176
|
+
return this.streamToUint8Array(stream);
|
|
177
|
+
} catch (err) {
|
|
178
|
+
if (err instanceof Error && "name" in err && err.name === "NoSuchKey") {
|
|
179
|
+
throw new StorageError(
|
|
180
|
+
`File not found: ${key}`,
|
|
181
|
+
StorageErrorCodes.NOT_FOUND,
|
|
182
|
+
404
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
throw new StorageError(
|
|
186
|
+
`Download failed: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
187
|
+
StorageErrorCodes.DOWNLOAD_FAILED,
|
|
188
|
+
void 0,
|
|
189
|
+
err
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
async downloadStream(key, options) {
|
|
194
|
+
this.validateKey(key);
|
|
195
|
+
const fullKey = this.getFullKey(key);
|
|
196
|
+
const client = await this.getClient();
|
|
197
|
+
const { GetObjectCommand } = await import("@aws-sdk/client-s3");
|
|
198
|
+
try {
|
|
199
|
+
const params = {
|
|
200
|
+
Bucket: this.bucket,
|
|
201
|
+
Key: fullKey
|
|
202
|
+
};
|
|
203
|
+
if (options?.rangeStart !== void 0 || options?.rangeEnd !== void 0) {
|
|
204
|
+
const start = options.rangeStart ?? 0;
|
|
205
|
+
const end = options.rangeEnd ?? "";
|
|
206
|
+
params.Range = `bytes=${start}-${end}`;
|
|
207
|
+
}
|
|
208
|
+
const result = await client.send(new GetObjectCommand(params));
|
|
209
|
+
if (!result.Body) {
|
|
210
|
+
throw new StorageError(
|
|
211
|
+
"Empty response body",
|
|
212
|
+
StorageErrorCodes.DOWNLOAD_FAILED
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
return result.Body;
|
|
216
|
+
} catch (err) {
|
|
217
|
+
if (err instanceof Error && "name" in err && err.name === "NoSuchKey") {
|
|
218
|
+
throw new StorageError(
|
|
219
|
+
`File not found: ${key}`,
|
|
220
|
+
StorageErrorCodes.NOT_FOUND,
|
|
221
|
+
404
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
throw err;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
async head(key) {
|
|
228
|
+
this.validateKey(key);
|
|
229
|
+
const fullKey = this.getFullKey(key);
|
|
230
|
+
const client = await this.getClient();
|
|
231
|
+
const { HeadObjectCommand } = await import("@aws-sdk/client-s3");
|
|
232
|
+
try {
|
|
233
|
+
const result = await client.send(
|
|
234
|
+
new HeadObjectCommand({
|
|
235
|
+
Bucket: this.bucket,
|
|
236
|
+
Key: fullKey
|
|
237
|
+
})
|
|
238
|
+
);
|
|
239
|
+
return {
|
|
240
|
+
key: fullKey,
|
|
241
|
+
size: result.ContentLength ?? 0,
|
|
242
|
+
contentType: result.ContentType ?? void 0,
|
|
243
|
+
lastModified: result.LastModified ?? void 0,
|
|
244
|
+
etag: result.ETag ?? void 0,
|
|
245
|
+
metadata: result.Metadata ?? void 0
|
|
246
|
+
};
|
|
247
|
+
} catch (err) {
|
|
248
|
+
if (err instanceof Error && "name" in err && (err.name === "NoSuchKey" || err.name === "NotFound")) {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
throw err;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
async exists(key) {
|
|
255
|
+
const metadata = await this.head(key);
|
|
256
|
+
return metadata !== null;
|
|
257
|
+
}
|
|
258
|
+
async delete(key) {
|
|
259
|
+
this.validateKey(key);
|
|
260
|
+
const fullKey = this.getFullKey(key);
|
|
261
|
+
const client = await this.getClient();
|
|
262
|
+
const { DeleteObjectCommand } = await import("@aws-sdk/client-s3");
|
|
263
|
+
try {
|
|
264
|
+
await client.send(
|
|
265
|
+
new DeleteObjectCommand({
|
|
266
|
+
Bucket: this.bucket,
|
|
267
|
+
Key: fullKey
|
|
268
|
+
})
|
|
269
|
+
);
|
|
270
|
+
return { success: true, key: fullKey };
|
|
271
|
+
} catch (err) {
|
|
272
|
+
throw new StorageError(
|
|
273
|
+
`Delete failed: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
274
|
+
StorageErrorCodes.DELETE_FAILED,
|
|
275
|
+
void 0,
|
|
276
|
+
err
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
async deleteMany(keys) {
|
|
281
|
+
const client = await this.getClient();
|
|
282
|
+
const { DeleteObjectsCommand } = await import("@aws-sdk/client-s3");
|
|
283
|
+
const objects = keys.map((key) => ({
|
|
284
|
+
Key: this.getFullKey(key)
|
|
285
|
+
}));
|
|
286
|
+
try {
|
|
287
|
+
const result = await client.send(
|
|
288
|
+
new DeleteObjectsCommand({
|
|
289
|
+
Bucket: this.bucket,
|
|
290
|
+
Delete: { Objects: objects }
|
|
291
|
+
})
|
|
292
|
+
);
|
|
293
|
+
const deleted = result.Deleted?.map((d) => d.Key ?? "") ?? [];
|
|
294
|
+
const errors = result.Errors?.map((e) => ({
|
|
295
|
+
key: e.Key ?? "",
|
|
296
|
+
error: e.Message ?? "Unknown error"
|
|
297
|
+
})) ?? [];
|
|
298
|
+
return { deleted, errors };
|
|
299
|
+
} catch (err) {
|
|
300
|
+
throw new StorageError(
|
|
301
|
+
`Batch delete failed: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
302
|
+
StorageErrorCodes.DELETE_FAILED,
|
|
303
|
+
void 0,
|
|
304
|
+
err
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
async list(options) {
|
|
309
|
+
const client = await this.getClient();
|
|
310
|
+
const { ListObjectsV2Command } = await import("@aws-sdk/client-s3");
|
|
311
|
+
const prefix = options?.prefix ? this.getFullKey(options.prefix) : this.basePath || void 0;
|
|
312
|
+
try {
|
|
313
|
+
const result = await client.send(
|
|
314
|
+
new ListObjectsV2Command({
|
|
315
|
+
Bucket: this.bucket,
|
|
316
|
+
Prefix: prefix,
|
|
317
|
+
Delimiter: options?.delimiter,
|
|
318
|
+
MaxKeys: options?.maxKeys,
|
|
319
|
+
ContinuationToken: options?.continuationToken
|
|
320
|
+
})
|
|
321
|
+
);
|
|
322
|
+
const files = result.Contents?.map((item) => ({
|
|
323
|
+
key: item.Key ?? "",
|
|
324
|
+
size: item.Size ?? 0,
|
|
325
|
+
contentType: void 0,
|
|
326
|
+
lastModified: item.LastModified ?? void 0,
|
|
327
|
+
etag: item.ETag ?? void 0,
|
|
328
|
+
metadata: void 0
|
|
329
|
+
})) ?? [];
|
|
330
|
+
const prefixes = result.CommonPrefixes?.map((p) => p.Prefix ?? "") ?? [];
|
|
331
|
+
return {
|
|
332
|
+
files,
|
|
333
|
+
prefixes,
|
|
334
|
+
isTruncated: result.IsTruncated ?? false,
|
|
335
|
+
continuationToken: result.NextContinuationToken ?? void 0
|
|
336
|
+
};
|
|
337
|
+
} catch (err) {
|
|
338
|
+
throw new StorageError(
|
|
339
|
+
`List failed: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
340
|
+
StorageErrorCodes.LIST_FAILED,
|
|
341
|
+
void 0,
|
|
342
|
+
err
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
async copy(sourceKey, destKey, options) {
|
|
347
|
+
this.validateKey(sourceKey);
|
|
348
|
+
this.validateKey(destKey);
|
|
349
|
+
const client = await this.getClient();
|
|
350
|
+
const { CopyObjectCommand } = await import("@aws-sdk/client-s3");
|
|
351
|
+
const sourceFullKey = this.getFullKey(sourceKey);
|
|
352
|
+
const destFullKey = this.getFullKey(destKey);
|
|
353
|
+
const sourceBucket = options?.sourceBucket ?? this.bucket;
|
|
354
|
+
try {
|
|
355
|
+
const result = await client.send(
|
|
356
|
+
new CopyObjectCommand({
|
|
357
|
+
Bucket: this.bucket,
|
|
358
|
+
Key: destFullKey,
|
|
359
|
+
CopySource: `${sourceBucket}/${sourceFullKey}`,
|
|
360
|
+
MetadataDirective: options?.metadataDirective,
|
|
361
|
+
ContentType: options?.contentType,
|
|
362
|
+
Metadata: options?.metadata
|
|
363
|
+
})
|
|
364
|
+
);
|
|
365
|
+
const metadata = await this.head(destKey);
|
|
366
|
+
return metadata ?? {
|
|
367
|
+
key: destFullKey,
|
|
368
|
+
size: 0,
|
|
369
|
+
contentType: options?.contentType ?? void 0,
|
|
370
|
+
lastModified: result.CopyObjectResult?.LastModified ?? /* @__PURE__ */ new Date(),
|
|
371
|
+
etag: result.CopyObjectResult?.ETag ?? void 0,
|
|
372
|
+
metadata: options?.metadata ?? void 0
|
|
373
|
+
};
|
|
374
|
+
} catch (err) {
|
|
375
|
+
throw new StorageError(
|
|
376
|
+
`Copy failed: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
377
|
+
StorageErrorCodes.COPY_FAILED,
|
|
378
|
+
void 0,
|
|
379
|
+
err
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
async move(sourceKey, destKey) {
|
|
384
|
+
const metadata = await this.copy(sourceKey, destKey);
|
|
385
|
+
await this.delete(sourceKey);
|
|
386
|
+
return metadata;
|
|
387
|
+
}
|
|
388
|
+
async getPresignedUrl(key, options) {
|
|
389
|
+
this.validateKey(key);
|
|
390
|
+
const fullKey = this.getFullKey(key);
|
|
391
|
+
const client = await this.getClient();
|
|
392
|
+
try {
|
|
393
|
+
const { getSignedUrl } = await import("@aws-sdk/s3-request-presigner");
|
|
394
|
+
const { GetObjectCommand } = await import("@aws-sdk/client-s3");
|
|
395
|
+
const command = new GetObjectCommand({
|
|
396
|
+
Bucket: this.bucket,
|
|
397
|
+
Key: fullKey,
|
|
398
|
+
ResponseCacheControl: options?.responseCacheControl,
|
|
399
|
+
ResponseContentType: options?.responseContentType,
|
|
400
|
+
ResponseContentDisposition: options?.contentDisposition
|
|
401
|
+
});
|
|
402
|
+
return getSignedUrl(client, command, {
|
|
403
|
+
expiresIn: options?.expiresIn ?? 3600
|
|
404
|
+
});
|
|
405
|
+
} catch (err) {
|
|
406
|
+
throw new StorageError(
|
|
407
|
+
`Presigned URL failed: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
408
|
+
StorageErrorCodes.PRESIGN_FAILED,
|
|
409
|
+
void 0,
|
|
410
|
+
err
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
async getUploadUrl(key, options) {
|
|
415
|
+
this.validateKey(key);
|
|
416
|
+
const fullKey = this.getFullKey(key);
|
|
417
|
+
const client = await this.getClient();
|
|
418
|
+
try {
|
|
419
|
+
const { getSignedUrl } = await import("@aws-sdk/s3-request-presigner");
|
|
420
|
+
const { PutObjectCommand } = await import("@aws-sdk/client-s3");
|
|
421
|
+
const command = new PutObjectCommand({
|
|
422
|
+
Bucket: this.bucket,
|
|
423
|
+
Key: fullKey,
|
|
424
|
+
ContentType: options?.contentType,
|
|
425
|
+
ContentDisposition: options?.contentDisposition
|
|
426
|
+
});
|
|
427
|
+
return getSignedUrl(client, command, {
|
|
428
|
+
expiresIn: options?.expiresIn ?? 3600
|
|
429
|
+
});
|
|
430
|
+
} catch (err) {
|
|
431
|
+
throw new StorageError(
|
|
432
|
+
`Upload URL failed: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
433
|
+
StorageErrorCodes.PRESIGN_FAILED,
|
|
434
|
+
void 0,
|
|
435
|
+
err
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
async streamToUint8Array(stream) {
|
|
440
|
+
const reader = stream.getReader();
|
|
441
|
+
const chunks = [];
|
|
442
|
+
while (true) {
|
|
443
|
+
const { done, value } = await reader.read();
|
|
444
|
+
if (done) break;
|
|
445
|
+
if (value) chunks.push(value);
|
|
446
|
+
}
|
|
447
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
448
|
+
const result = new Uint8Array(totalLength);
|
|
449
|
+
let offset = 0;
|
|
450
|
+
for (const chunk of chunks) {
|
|
451
|
+
result.set(chunk, offset);
|
|
452
|
+
offset += chunk.length;
|
|
453
|
+
}
|
|
454
|
+
return result;
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
function createS3Adapter(config) {
|
|
458
|
+
return new S3Adapter(config);
|
|
459
|
+
}
|
|
460
|
+
function createDOSpacesAdapter(config) {
|
|
461
|
+
return new S3Adapter({
|
|
462
|
+
...config,
|
|
463
|
+
type: "do-spaces",
|
|
464
|
+
endpoint: `https://${config.region}.digitaloceanspaces.com`,
|
|
465
|
+
bucket: config.spaceName ?? config.bucket
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
export {
|
|
469
|
+
S3Adapter,
|
|
470
|
+
createDOSpacesAdapter,
|
|
471
|
+
createS3Adapter
|
|
472
|
+
};
|
|
473
|
+
//# sourceMappingURL=s3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/types.ts","../../src/adapters/s3.ts"],"sourcesContent":["/**\n * @parsrun/storage - Type Definitions\n * Storage abstraction types for edge-compatible storage\n */\n\n// Re-export types from @parsrun/types for convenience\nexport {\n type,\n fileMetadata,\n uploadOptions as parsUploadOptions,\n signedUrlOptions,\n listFilesOptions,\n listFilesResult,\n localStorageConfig,\n s3StorageConfig,\n r2StorageConfig,\n gcsStorageConfig,\n storageProviderConfig,\n type FileMetadata as ParsFileMetadata,\n type UploadOptions as ParsUploadOptions,\n type SignedUrlOptions,\n type ListFilesOptions,\n type ListFilesResult,\n type LocalStorageConfig,\n type S3StorageConfig,\n type R2StorageConfig,\n type GcsStorageConfig,\n type StorageProviderConfig,\n} from \"@parsrun/types\";\n\n/**\n * Storage adapter type\n */\nexport type StorageAdapterType = \"s3\" | \"r2\" | \"do-spaces\" | \"memory\" | \"custom\";\n\n/**\n * File metadata\n */\nexport interface FileMetadata {\n /** File key/path */\n key: string;\n /** File size in bytes */\n size: number;\n /** Content type (MIME type) */\n contentType: string | undefined;\n /** Last modified date */\n lastModified: Date | undefined;\n /** ETag (entity tag) */\n etag: string | undefined;\n /** Custom metadata */\n metadata: Record<string, string> | undefined;\n}\n\n/**\n * Upload options\n */\nexport interface UploadOptions {\n /** Content type override */\n contentType?: string | undefined;\n /** Content disposition */\n contentDisposition?: string | undefined;\n /** Cache control header */\n cacheControl?: string | undefined;\n /** Content encoding */\n contentEncoding?: string | undefined;\n /** Custom metadata */\n metadata?: Record<string, string> | undefined;\n /** ACL (access control list) */\n acl?: \"private\" | \"public-read\" | undefined;\n}\n\n/**\n * Download options\n */\nexport interface DownloadOptions {\n /** Range start (for partial downloads) */\n rangeStart?: number | undefined;\n /** Range end (for partial downloads) */\n rangeEnd?: number | undefined;\n /** If-None-Match (ETag) */\n ifNoneMatch?: string | undefined;\n /** If-Modified-Since */\n ifModifiedSince?: Date | undefined;\n}\n\n/**\n * List options\n */\nexport interface ListOptions {\n /** Prefix to filter by */\n prefix?: string | undefined;\n /** Delimiter for hierarchy */\n delimiter?: string | undefined;\n /** Maximum keys to return */\n maxKeys?: number | undefined;\n /** Continuation token for pagination */\n continuationToken?: string | undefined;\n}\n\n/**\n * List result\n */\nexport interface ListResult {\n /** List of files */\n files: FileMetadata[];\n /** Common prefixes (for hierarchical listing) */\n prefixes: string[];\n /** Whether there are more results */\n isTruncated: boolean;\n /** Continuation token for next page */\n continuationToken: string | undefined;\n}\n\n/**\n * Presigned URL options\n */\nexport interface PresignedUrlOptions {\n /** URL expiration in seconds (default: 3600) */\n expiresIn?: number | undefined;\n /** Content type (for upload URLs) */\n contentType?: string | undefined;\n /** Content disposition */\n contentDisposition?: string | undefined;\n /** Response cache control */\n responseCacheControl?: string | undefined;\n /** Response content type */\n responseContentType?: string | undefined;\n}\n\n/**\n * Copy options\n */\nexport interface CopyOptions {\n /** Source bucket (if different from destination) */\n sourceBucket?: string | undefined;\n /** Metadata directive */\n metadataDirective?: \"COPY\" | \"REPLACE\" | undefined;\n /** New metadata (if REPLACE) */\n metadata?: Record<string, string> | undefined;\n /** New content type (if REPLACE) */\n contentType?: string | undefined;\n}\n\n/**\n * Delete result\n */\nexport interface DeleteResult {\n /** Whether deletion was successful */\n success: boolean;\n /** Deleted key */\n key: string;\n}\n\n/**\n * Batch delete result\n */\nexport interface BatchDeleteResult {\n /** Successfully deleted keys */\n deleted: string[];\n /** Failed deletions */\n errors: Array<{ key: string; error: string }>;\n}\n\n/**\n * Storage adapter interface\n * All storage adapters must implement this interface\n */\nexport interface StorageAdapter {\n /** Adapter type */\n readonly type: StorageAdapterType;\n\n /** Bucket name */\n readonly bucket: string;\n\n /**\n * Upload a file\n * @param key - File key/path\n * @param data - File data\n * @param options - Upload options\n */\n upload(\n key: string,\n data: Uint8Array | ReadableStream<Uint8Array> | Blob | string,\n options?: UploadOptions\n ): Promise<FileMetadata>;\n\n /**\n * Download a file\n * @param key - File key/path\n * @param options - Download options\n */\n download(key: string, options?: DownloadOptions): Promise<Uint8Array>;\n\n /**\n * Download a file as a stream\n * @param key - File key/path\n * @param options - Download options\n */\n downloadStream(\n key: string,\n options?: DownloadOptions\n ): Promise<ReadableStream<Uint8Array>>;\n\n /**\n * Get file metadata\n * @param key - File key/path\n */\n head(key: string): Promise<FileMetadata | null>;\n\n /**\n * Check if file exists\n * @param key - File key/path\n */\n exists(key: string): Promise<boolean>;\n\n /**\n * Delete a file\n * @param key - File key/path\n */\n delete(key: string): Promise<DeleteResult>;\n\n /**\n * Delete multiple files\n * @param keys - File keys/paths\n */\n deleteMany(keys: string[]): Promise<BatchDeleteResult>;\n\n /**\n * List files\n * @param options - List options\n */\n list(options?: ListOptions): Promise<ListResult>;\n\n /**\n * Copy a file\n * @param sourceKey - Source key\n * @param destKey - Destination key\n * @param options - Copy options\n */\n copy(sourceKey: string, destKey: string, options?: CopyOptions): Promise<FileMetadata>;\n\n /**\n * Move/rename a file\n * @param sourceKey - Source key\n * @param destKey - Destination key\n */\n move(sourceKey: string, destKey: string): Promise<FileMetadata>;\n\n /**\n * Generate a presigned URL for download\n * @param key - File key/path\n * @param options - URL options\n */\n getPresignedUrl(key: string, options?: PresignedUrlOptions): Promise<string>;\n\n /**\n * Generate a presigned URL for upload\n * @param key - File key/path\n * @param options - URL options\n */\n getUploadUrl(key: string, options?: PresignedUrlOptions): Promise<string>;\n}\n\n/**\n * Storage configuration\n */\nexport interface StorageConfig {\n /** Default bucket name */\n bucket: string;\n /** Storage adapter type */\n type: StorageAdapterType;\n /** Base path prefix for all operations */\n basePath?: string | undefined;\n /** Public URL prefix */\n publicUrl?: string | undefined;\n}\n\n/**\n * S3 configuration\n */\nexport interface S3Config extends StorageConfig {\n type: \"s3\" | \"do-spaces\";\n /** AWS region */\n region: string;\n /** Access key ID */\n accessKeyId: string;\n /** Secret access key */\n secretAccessKey: string;\n /** Custom endpoint (for DO Spaces, MinIO, etc.) */\n endpoint?: string | undefined;\n /** Force path style (for MinIO) */\n forcePathStyle?: boolean | undefined;\n}\n\n/**\n * R2 configuration\n */\nexport interface R2Config extends StorageConfig {\n type: \"r2\";\n /** Cloudflare account ID */\n accountId: string;\n /** R2 access key ID */\n accessKeyId: string;\n /** R2 secret access key */\n secretAccessKey: string;\n /** Custom domain for public access */\n customDomain?: string | undefined;\n}\n\n/**\n * Memory storage configuration (for testing)\n */\nexport interface MemoryConfig extends StorageConfig {\n type: \"memory\";\n /** Maximum storage size in bytes */\n maxSize?: number | undefined;\n}\n\n/**\n * Storage error\n */\nexport class StorageError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n public readonly statusCode?: number | undefined,\n public readonly cause?: unknown\n ) {\n super(message);\n this.name = \"StorageError\";\n }\n}\n\n/**\n * Common storage error codes\n */\nexport const StorageErrorCodes = {\n NOT_FOUND: \"NOT_FOUND\",\n ACCESS_DENIED: \"ACCESS_DENIED\",\n BUCKET_NOT_FOUND: \"BUCKET_NOT_FOUND\",\n INVALID_KEY: \"INVALID_KEY\",\n UPLOAD_FAILED: \"UPLOAD_FAILED\",\n DOWNLOAD_FAILED: \"DOWNLOAD_FAILED\",\n DELETE_FAILED: \"DELETE_FAILED\",\n COPY_FAILED: \"COPY_FAILED\",\n LIST_FAILED: \"LIST_FAILED\",\n PRESIGN_FAILED: \"PRESIGN_FAILED\",\n QUOTA_EXCEEDED: \"QUOTA_EXCEEDED\",\n INVALID_CONFIG: \"INVALID_CONFIG\",\n ADAPTER_NOT_AVAILABLE: \"ADAPTER_NOT_AVAILABLE\",\n} as const;\n","/**\n * @parsrun/storage - S3 Adapter\n * S3-compatible storage adapter (AWS S3, DigitalOcean Spaces, MinIO, etc.)\n */\n\nimport {\n type BatchDeleteResult,\n type CopyOptions,\n type DeleteResult,\n type DownloadOptions,\n type FileMetadata,\n type ListOptions,\n type ListResult,\n type PresignedUrlOptions,\n type S3Config,\n type StorageAdapter,\n type UploadOptions,\n StorageError,\n StorageErrorCodes,\n} from \"../types.js\";\n\n// Types for AWS SDK (dynamically imported)\ntype S3Client = import(\"@aws-sdk/client-s3\").S3Client;\ntype PutObjectCommandInput = import(\"@aws-sdk/client-s3\").PutObjectCommandInput;\n\n/**\n * S3 Storage Adapter\n * Works with AWS S3, DigitalOcean Spaces, MinIO, and other S3-compatible services\n *\n * @example\n * ```typescript\n * // AWS S3\n * const s3 = new S3Adapter({\n * type: 's3',\n * bucket: 'my-bucket',\n * region: 'us-east-1',\n * accessKeyId: process.env.AWS_ACCESS_KEY_ID,\n * secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,\n * });\n *\n * // DigitalOcean Spaces\n * const spaces = new S3Adapter({\n * type: 'do-spaces',\n * bucket: 'my-space',\n * region: 'nyc3',\n * accessKeyId: process.env.DO_SPACES_KEY,\n * secretAccessKey: process.env.DO_SPACES_SECRET,\n * endpoint: 'https://nyc3.digitaloceanspaces.com',\n * });\n *\n * await s3.upload('file.txt', 'Hello, World!');\n * ```\n */\nexport class S3Adapter implements StorageAdapter {\n readonly type: \"s3\" | \"do-spaces\";\n readonly bucket: string;\n\n private client: S3Client | null = null;\n private config: S3Config;\n private basePath: string;\n\n constructor(config: S3Config) {\n this.type = config.type;\n this.bucket = config.bucket;\n this.config = config;\n this.basePath = config.basePath ?? \"\";\n }\n\n /**\n * Lazy load S3 client\n */\n private async getClient(): Promise<S3Client> {\n if (this.client) return this.client;\n\n try {\n const { S3Client } = await import(\"@aws-sdk/client-s3\");\n\n const clientConfig: import(\"@aws-sdk/client-s3\").S3ClientConfig = {\n region: this.config.region,\n credentials: {\n accessKeyId: this.config.accessKeyId,\n secretAccessKey: this.config.secretAccessKey,\n },\n };\n\n // Only add optional properties if defined\n if (this.config.endpoint) {\n clientConfig.endpoint = this.config.endpoint;\n }\n if (this.config.forcePathStyle !== undefined) {\n clientConfig.forcePathStyle = this.config.forcePathStyle;\n }\n\n this.client = new S3Client(clientConfig);\n\n return this.client;\n } catch {\n throw new StorageError(\n \"AWS SDK not installed. Run: npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner\",\n StorageErrorCodes.ADAPTER_NOT_AVAILABLE\n );\n }\n }\n\n private getFullKey(key: string): string {\n return this.basePath ? `${this.basePath}/${key}` : key;\n }\n\n private validateKey(key: string): void {\n if (!key || key.includes(\"..\") || key.startsWith(\"/\")) {\n throw new StorageError(\n `Invalid key: ${key}`,\n StorageErrorCodes.INVALID_KEY\n );\n }\n }\n\n private async dataToBody(\n data: Uint8Array | ReadableStream<Uint8Array> | Blob | string\n ): Promise<Uint8Array | ReadableStream<Uint8Array> | string> {\n if (data instanceof Uint8Array || typeof data === \"string\") {\n return data;\n }\n\n if (data instanceof Blob) {\n const buffer = await data.arrayBuffer();\n return new Uint8Array(buffer);\n }\n\n return data;\n }\n\n async upload(\n key: string,\n data: Uint8Array | ReadableStream<Uint8Array> | Blob | string,\n options?: UploadOptions\n ): Promise<FileMetadata> {\n this.validateKey(key);\n const fullKey = this.getFullKey(key);\n\n const client = await this.getClient();\n const { PutObjectCommand } = await import(\"@aws-sdk/client-s3\");\n\n const body = await this.dataToBody(data);\n\n const params: PutObjectCommandInput = {\n Bucket: this.bucket,\n Key: fullKey,\n Body: body as Uint8Array | string,\n ContentType: options?.contentType,\n ContentDisposition: options?.contentDisposition,\n CacheControl: options?.cacheControl,\n ContentEncoding: options?.contentEncoding,\n Metadata: options?.metadata,\n };\n\n if (options?.acl) {\n params.ACL = options.acl;\n }\n\n try {\n const result = await client.send(new PutObjectCommand(params));\n\n // Get size\n let size = 0;\n if (typeof body === \"string\") {\n size = new TextEncoder().encode(body).length;\n } else if (body instanceof Uint8Array) {\n size = body.length;\n }\n\n return {\n key: fullKey,\n size,\n contentType: options?.contentType ?? undefined,\n lastModified: new Date(),\n etag: result.ETag ?? undefined,\n metadata: options?.metadata ?? undefined,\n };\n } catch (err) {\n throw new StorageError(\n `Upload failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n StorageErrorCodes.UPLOAD_FAILED,\n undefined,\n err\n );\n }\n }\n\n async download(key: string, options?: DownloadOptions): Promise<Uint8Array> {\n this.validateKey(key);\n const fullKey = this.getFullKey(key);\n\n const client = await this.getClient();\n const { GetObjectCommand } = await import(\"@aws-sdk/client-s3\");\n\n try {\n const params: import(\"@aws-sdk/client-s3\").GetObjectCommandInput = {\n Bucket: this.bucket,\n Key: fullKey,\n };\n\n if (options?.rangeStart !== undefined || options?.rangeEnd !== undefined) {\n const start = options.rangeStart ?? 0;\n const end = options.rangeEnd ?? \"\";\n params.Range = `bytes=${start}-${end}`;\n }\n\n if (options?.ifNoneMatch) {\n params.IfNoneMatch = options.ifNoneMatch;\n }\n\n if (options?.ifModifiedSince) {\n params.IfModifiedSince = options.ifModifiedSince;\n }\n\n const result = await client.send(new GetObjectCommand(params));\n\n if (!result.Body) {\n throw new StorageError(\n \"Empty response body\",\n StorageErrorCodes.DOWNLOAD_FAILED\n );\n }\n\n // Convert to Uint8Array\n const stream = result.Body as ReadableStream<Uint8Array>;\n return this.streamToUint8Array(stream);\n } catch (err) {\n if (\n err instanceof Error &&\n \"name\" in err &&\n err.name === \"NoSuchKey\"\n ) {\n throw new StorageError(\n `File not found: ${key}`,\n StorageErrorCodes.NOT_FOUND,\n 404\n );\n }\n throw new StorageError(\n `Download failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n StorageErrorCodes.DOWNLOAD_FAILED,\n undefined,\n err\n );\n }\n }\n\n async downloadStream(\n key: string,\n options?: DownloadOptions\n ): Promise<ReadableStream<Uint8Array>> {\n this.validateKey(key);\n const fullKey = this.getFullKey(key);\n\n const client = await this.getClient();\n const { GetObjectCommand } = await import(\"@aws-sdk/client-s3\");\n\n try {\n const params: import(\"@aws-sdk/client-s3\").GetObjectCommandInput = {\n Bucket: this.bucket,\n Key: fullKey,\n };\n\n if (options?.rangeStart !== undefined || options?.rangeEnd !== undefined) {\n const start = options.rangeStart ?? 0;\n const end = options.rangeEnd ?? \"\";\n params.Range = `bytes=${start}-${end}`;\n }\n\n const result = await client.send(new GetObjectCommand(params));\n\n if (!result.Body) {\n throw new StorageError(\n \"Empty response body\",\n StorageErrorCodes.DOWNLOAD_FAILED\n );\n }\n\n return result.Body as ReadableStream<Uint8Array>;\n } catch (err) {\n if (\n err instanceof Error &&\n \"name\" in err &&\n err.name === \"NoSuchKey\"\n ) {\n throw new StorageError(\n `File not found: ${key}`,\n StorageErrorCodes.NOT_FOUND,\n 404\n );\n }\n throw err;\n }\n }\n\n async head(key: string): Promise<FileMetadata | null> {\n this.validateKey(key);\n const fullKey = this.getFullKey(key);\n\n const client = await this.getClient();\n const { HeadObjectCommand } = await import(\"@aws-sdk/client-s3\");\n\n try {\n const result = await client.send(\n new HeadObjectCommand({\n Bucket: this.bucket,\n Key: fullKey,\n })\n );\n\n return {\n key: fullKey,\n size: result.ContentLength ?? 0,\n contentType: result.ContentType ?? undefined,\n lastModified: result.LastModified ?? undefined,\n etag: result.ETag ?? undefined,\n metadata: result.Metadata ?? undefined,\n };\n } catch (err) {\n if (\n err instanceof Error &&\n \"name\" in err &&\n (err.name === \"NoSuchKey\" || err.name === \"NotFound\")\n ) {\n return null;\n }\n throw err;\n }\n }\n\n async exists(key: string): Promise<boolean> {\n const metadata = await this.head(key);\n return metadata !== null;\n }\n\n async delete(key: string): Promise<DeleteResult> {\n this.validateKey(key);\n const fullKey = this.getFullKey(key);\n\n const client = await this.getClient();\n const { DeleteObjectCommand } = await import(\"@aws-sdk/client-s3\");\n\n try {\n await client.send(\n new DeleteObjectCommand({\n Bucket: this.bucket,\n Key: fullKey,\n })\n );\n\n return { success: true, key: fullKey };\n } catch (err) {\n throw new StorageError(\n `Delete failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n StorageErrorCodes.DELETE_FAILED,\n undefined,\n err\n );\n }\n }\n\n async deleteMany(keys: string[]): Promise<BatchDeleteResult> {\n const client = await this.getClient();\n const { DeleteObjectsCommand } = await import(\"@aws-sdk/client-s3\");\n\n const objects = keys.map((key) => ({\n Key: this.getFullKey(key),\n }));\n\n try {\n const result = await client.send(\n new DeleteObjectsCommand({\n Bucket: this.bucket,\n Delete: { Objects: objects },\n })\n );\n\n const deleted = result.Deleted?.map((d) => d.Key ?? \"\") ?? [];\n const errors =\n result.Errors?.map((e) => ({\n key: e.Key ?? \"\",\n error: e.Message ?? \"Unknown error\",\n })) ?? [];\n\n return { deleted, errors };\n } catch (err) {\n throw new StorageError(\n `Batch delete failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n StorageErrorCodes.DELETE_FAILED,\n undefined,\n err\n );\n }\n }\n\n async list(options?: ListOptions): Promise<ListResult> {\n const client = await this.getClient();\n const { ListObjectsV2Command } = await import(\"@aws-sdk/client-s3\");\n\n const prefix = options?.prefix\n ? this.getFullKey(options.prefix)\n : this.basePath || undefined;\n\n try {\n const result = await client.send(\n new ListObjectsV2Command({\n Bucket: this.bucket,\n Prefix: prefix,\n Delimiter: options?.delimiter,\n MaxKeys: options?.maxKeys,\n ContinuationToken: options?.continuationToken,\n })\n );\n\n const files: FileMetadata[] =\n result.Contents?.map((item) => ({\n key: item.Key ?? \"\",\n size: item.Size ?? 0,\n contentType: undefined,\n lastModified: item.LastModified ?? undefined,\n etag: item.ETag ?? undefined,\n metadata: undefined,\n })) ?? [];\n\n const prefixes =\n result.CommonPrefixes?.map((p) => p.Prefix ?? \"\") ?? [];\n\n return {\n files,\n prefixes,\n isTruncated: result.IsTruncated ?? false,\n continuationToken: result.NextContinuationToken ?? undefined,\n };\n } catch (err) {\n throw new StorageError(\n `List failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n StorageErrorCodes.LIST_FAILED,\n undefined,\n err\n );\n }\n }\n\n async copy(\n sourceKey: string,\n destKey: string,\n options?: CopyOptions\n ): Promise<FileMetadata> {\n this.validateKey(sourceKey);\n this.validateKey(destKey);\n\n const client = await this.getClient();\n const { CopyObjectCommand } = await import(\"@aws-sdk/client-s3\");\n\n const sourceFullKey = this.getFullKey(sourceKey);\n const destFullKey = this.getFullKey(destKey);\n const sourceBucket = options?.sourceBucket ?? this.bucket;\n\n try {\n const result = await client.send(\n new CopyObjectCommand({\n Bucket: this.bucket,\n Key: destFullKey,\n CopySource: `${sourceBucket}/${sourceFullKey}`,\n MetadataDirective: options?.metadataDirective,\n ContentType: options?.contentType,\n Metadata: options?.metadata,\n })\n );\n\n // Get metadata of copied object\n const metadata = await this.head(destKey);\n\n return metadata ?? {\n key: destFullKey,\n size: 0,\n contentType: options?.contentType ?? undefined,\n lastModified: result.CopyObjectResult?.LastModified ?? new Date(),\n etag: result.CopyObjectResult?.ETag ?? undefined,\n metadata: options?.metadata ?? undefined,\n };\n } catch (err) {\n throw new StorageError(\n `Copy failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n StorageErrorCodes.COPY_FAILED,\n undefined,\n err\n );\n }\n }\n\n async move(sourceKey: string, destKey: string): Promise<FileMetadata> {\n const metadata = await this.copy(sourceKey, destKey);\n await this.delete(sourceKey);\n return metadata;\n }\n\n async getPresignedUrl(\n key: string,\n options?: PresignedUrlOptions\n ): Promise<string> {\n this.validateKey(key);\n const fullKey = this.getFullKey(key);\n\n const client = await this.getClient();\n\n try {\n const { getSignedUrl } = await import(\"@aws-sdk/s3-request-presigner\");\n const { GetObjectCommand } = await import(\"@aws-sdk/client-s3\");\n\n const command = new GetObjectCommand({\n Bucket: this.bucket,\n Key: fullKey,\n ResponseCacheControl: options?.responseCacheControl,\n ResponseContentType: options?.responseContentType,\n ResponseContentDisposition: options?.contentDisposition,\n });\n\n return getSignedUrl(client, command, {\n expiresIn: options?.expiresIn ?? 3600,\n });\n } catch (err) {\n throw new StorageError(\n `Presigned URL failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n StorageErrorCodes.PRESIGN_FAILED,\n undefined,\n err\n );\n }\n }\n\n async getUploadUrl(\n key: string,\n options?: PresignedUrlOptions\n ): Promise<string> {\n this.validateKey(key);\n const fullKey = this.getFullKey(key);\n\n const client = await this.getClient();\n\n try {\n const { getSignedUrl } = await import(\"@aws-sdk/s3-request-presigner\");\n const { PutObjectCommand } = await import(\"@aws-sdk/client-s3\");\n\n const command = new PutObjectCommand({\n Bucket: this.bucket,\n Key: fullKey,\n ContentType: options?.contentType,\n ContentDisposition: options?.contentDisposition,\n });\n\n return getSignedUrl(client, command, {\n expiresIn: options?.expiresIn ?? 3600,\n });\n } catch (err) {\n throw new StorageError(\n `Upload URL failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n StorageErrorCodes.PRESIGN_FAILED,\n undefined,\n err\n );\n }\n }\n\n private async streamToUint8Array(\n stream: ReadableStream<Uint8Array>\n ): Promise<Uint8Array> {\n const reader = stream.getReader();\n const chunks: Uint8Array[] = [];\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value) chunks.push(value);\n }\n\n const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.length;\n }\n\n return result;\n }\n}\n\n/**\n * Create an S3 storage adapter\n */\nexport function createS3Adapter(config: S3Config): S3Adapter {\n return new S3Adapter(config);\n}\n\n/**\n * Create a DigitalOcean Spaces adapter\n */\nexport function createDOSpacesAdapter(\n config: Omit<S3Config, \"type\" | \"endpoint\"> & {\n region: string;\n spaceName?: string;\n }\n): S3Adapter {\n return new S3Adapter({\n ...config,\n type: \"do-spaces\",\n endpoint: `https://${config.region}.digitaloceanspaces.com`,\n bucket: config.spaceName ?? config.bucket,\n });\n}\n"],"mappings":";AAMA;AAAA,EACE;AAAA,EACA;AAAA,EACiB;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAWK;AAqSA,IAAM,eAAN,cAA2B,MAAM;AAAA,EACtC,YACE,SACgB,MACA,YACA,OAChB;AACA,UAAM,OAAO;AAJG;AACA;AACA;AAGhB,SAAK,OAAO;AAAA,EACd;AACF;AAKO,IAAM,oBAAoB;AAAA,EAC/B,WAAW;AAAA,EACX,eAAe;AAAA,EACf,kBAAkB;AAAA,EAClB,aAAa;AAAA,EACb,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,eAAe;AAAA,EACf,aAAa;AAAA,EACb,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,uBAAuB;AACzB;;;ACzSO,IAAM,YAAN,MAA0C;AAAA,EACtC;AAAA,EACA;AAAA,EAED,SAA0B;AAAA,EAC1B;AAAA,EACA;AAAA,EAER,YAAY,QAAkB;AAC5B,SAAK,OAAO,OAAO;AACnB,SAAK,SAAS,OAAO;AACrB,SAAK,SAAS;AACd,SAAK,WAAW,OAAO,YAAY;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,YAA+B;AAC3C,QAAI,KAAK,OAAQ,QAAO,KAAK;AAE7B,QAAI;AACF,YAAM,EAAE,SAAS,IAAI,MAAM,OAAO,oBAAoB;AAEtD,YAAM,eAA4D;AAAA,QAChE,QAAQ,KAAK,OAAO;AAAA,QACpB,aAAa;AAAA,UACX,aAAa,KAAK,OAAO;AAAA,UACzB,iBAAiB,KAAK,OAAO;AAAA,QAC/B;AAAA,MACF;AAGA,UAAI,KAAK,OAAO,UAAU;AACxB,qBAAa,WAAW,KAAK,OAAO;AAAA,MACtC;AACA,UAAI,KAAK,OAAO,mBAAmB,QAAW;AAC5C,qBAAa,iBAAiB,KAAK,OAAO;AAAA,MAC5C;AAEA,WAAK,SAAS,IAAI,SAAS,YAAY;AAEvC,aAAO,KAAK;AAAA,IACd,QAAQ;AACN,YAAM,IAAI;AAAA,QACR;AAAA,QACA,kBAAkB;AAAA,MACpB;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,WAAW,KAAqB;AACtC,WAAO,KAAK,WAAW,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK;AAAA,EACrD;AAAA,EAEQ,YAAY,KAAmB;AACrC,QAAI,CAAC,OAAO,IAAI,SAAS,IAAI,KAAK,IAAI,WAAW,GAAG,GAAG;AACrD,YAAM,IAAI;AAAA,QACR,gBAAgB,GAAG;AAAA,QACnB,kBAAkB;AAAA,MACpB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,WACZ,MAC2D;AAC3D,QAAI,gBAAgB,cAAc,OAAO,SAAS,UAAU;AAC1D,aAAO;AAAA,IACT;AAEA,QAAI,gBAAgB,MAAM;AACxB,YAAM,SAAS,MAAM,KAAK,YAAY;AACtC,aAAO,IAAI,WAAW,MAAM;AAAA,IAC9B;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,OACJ,KACA,MACA,SACuB;AACvB,SAAK,YAAY,GAAG;AACpB,UAAM,UAAU,KAAK,WAAW,GAAG;AAEnC,UAAM,SAAS,MAAM,KAAK,UAAU;AACpC,UAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,oBAAoB;AAE9D,UAAM,OAAO,MAAM,KAAK,WAAW,IAAI;AAEvC,UAAM,SAAgC;AAAA,MACpC,QAAQ,KAAK;AAAA,MACb,KAAK;AAAA,MACL,MAAM;AAAA,MACN,aAAa,SAAS;AAAA,MACtB,oBAAoB,SAAS;AAAA,MAC7B,cAAc,SAAS;AAAA,MACvB,iBAAiB,SAAS;AAAA,MAC1B,UAAU,SAAS;AAAA,IACrB;AAEA,QAAI,SAAS,KAAK;AAChB,aAAO,MAAM,QAAQ;AAAA,IACvB;AAEA,QAAI;AACF,YAAM,SAAS,MAAM,OAAO,KAAK,IAAI,iBAAiB,MAAM,CAAC;AAG7D,UAAI,OAAO;AACX,UAAI,OAAO,SAAS,UAAU;AAC5B,eAAO,IAAI,YAAY,EAAE,OAAO,IAAI,EAAE;AAAA,MACxC,WAAW,gBAAgB,YAAY;AACrC,eAAO,KAAK;AAAA,MACd;AAEA,aAAO;AAAA,QACL,KAAK;AAAA,QACL;AAAA,QACA,aAAa,SAAS,eAAe;AAAA,QACrC,cAAc,oBAAI,KAAK;AAAA,QACvB,MAAM,OAAO,QAAQ;AAAA,QACrB,UAAU,SAAS,YAAY;AAAA,MACjC;AAAA,IACF,SAAS,KAAK;AACZ,YAAM,IAAI;AAAA,QACR,kBAAkB,eAAe,QAAQ,IAAI,UAAU,eAAe;AAAA,QACtE,kBAAkB;AAAA,QAClB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,SAAS,KAAa,SAAgD;AAC1E,SAAK,YAAY,GAAG;AACpB,UAAM,UAAU,KAAK,WAAW,GAAG;AAEnC,UAAM,SAAS,MAAM,KAAK,UAAU;AACpC,UAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,oBAAoB;AAE9D,QAAI;AACF,YAAM,SAA6D;AAAA,QACjE,QAAQ,KAAK;AAAA,QACb,KAAK;AAAA,MACP;AAEA,UAAI,SAAS,eAAe,UAAa,SAAS,aAAa,QAAW;AACxE,cAAM,QAAQ,QAAQ,cAAc;AACpC,cAAM,MAAM,QAAQ,YAAY;AAChC,eAAO,QAAQ,SAAS,KAAK,IAAI,GAAG;AAAA,MACtC;AAEA,UAAI,SAAS,aAAa;AACxB,eAAO,cAAc,QAAQ;AAAA,MAC/B;AAEA,UAAI,SAAS,iBAAiB;AAC5B,eAAO,kBAAkB,QAAQ;AAAA,MACnC;AAEA,YAAM,SAAS,MAAM,OAAO,KAAK,IAAI,iBAAiB,MAAM,CAAC;AAE7D,UAAI,CAAC,OAAO,MAAM;AAChB,cAAM,IAAI;AAAA,UACR;AAAA,UACA,kBAAkB;AAAA,QACpB;AAAA,MACF;AAGA,YAAM,SAAS,OAAO;AACtB,aAAO,KAAK,mBAAmB,MAAM;AAAA,IACvC,SAAS,KAAK;AACZ,UACE,eAAe,SACf,UAAU,OACV,IAAI,SAAS,aACb;AACA,cAAM,IAAI;AAAA,UACR,mBAAmB,GAAG;AAAA,UACtB,kBAAkB;AAAA,UAClB;AAAA,QACF;AAAA,MACF;AACA,YAAM,IAAI;AAAA,QACR,oBAAoB,eAAe,QAAQ,IAAI,UAAU,eAAe;AAAA,QACxE,kBAAkB;AAAA,QAClB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,eACJ,KACA,SACqC;AACrC,SAAK,YAAY,GAAG;AACpB,UAAM,UAAU,KAAK,WAAW,GAAG;AAEnC,UAAM,SAAS,MAAM,KAAK,UAAU;AACpC,UAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,oBAAoB;AAE9D,QAAI;AACF,YAAM,SAA6D;AAAA,QACjE,QAAQ,KAAK;AAAA,QACb,KAAK;AAAA,MACP;AAEA,UAAI,SAAS,eAAe,UAAa,SAAS,aAAa,QAAW;AACxE,cAAM,QAAQ,QAAQ,cAAc;AACpC,cAAM,MAAM,QAAQ,YAAY;AAChC,eAAO,QAAQ,SAAS,KAAK,IAAI,GAAG;AAAA,MACtC;AAEA,YAAM,SAAS,MAAM,OAAO,KAAK,IAAI,iBAAiB,MAAM,CAAC;AAE7D,UAAI,CAAC,OAAO,MAAM;AAChB,cAAM,IAAI;AAAA,UACR;AAAA,UACA,kBAAkB;AAAA,QACpB;AAAA,MACF;AAEA,aAAO,OAAO;AAAA,IAChB,SAAS,KAAK;AACZ,UACE,eAAe,SACf,UAAU,OACV,IAAI,SAAS,aACb;AACA,cAAM,IAAI;AAAA,UACR,mBAAmB,GAAG;AAAA,UACtB,kBAAkB;AAAA,UAClB;AAAA,QACF;AAAA,MACF;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,KAAK,KAA2C;AACpD,SAAK,YAAY,GAAG;AACpB,UAAM,UAAU,KAAK,WAAW,GAAG;AAEnC,UAAM,SAAS,MAAM,KAAK,UAAU;AACpC,UAAM,EAAE,kBAAkB,IAAI,MAAM,OAAO,oBAAoB;AAE/D,QAAI;AACF,YAAM,SAAS,MAAM,OAAO;AAAA,QAC1B,IAAI,kBAAkB;AAAA,UACpB,QAAQ,KAAK;AAAA,UACb,KAAK;AAAA,QACP,CAAC;AAAA,MACH;AAEA,aAAO;AAAA,QACL,KAAK;AAAA,QACL,MAAM,OAAO,iBAAiB;AAAA,QAC9B,aAAa,OAAO,eAAe;AAAA,QACnC,cAAc,OAAO,gBAAgB;AAAA,QACrC,MAAM,OAAO,QAAQ;AAAA,QACrB,UAAU,OAAO,YAAY;AAAA,MAC/B;AAAA,IACF,SAAS,KAAK;AACZ,UACE,eAAe,SACf,UAAU,QACT,IAAI,SAAS,eAAe,IAAI,SAAS,aAC1C;AACA,eAAO;AAAA,MACT;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,KAA+B;AAC1C,UAAM,WAAW,MAAM,KAAK,KAAK,GAAG;AACpC,WAAO,aAAa;AAAA,EACtB;AAAA,EAEA,MAAM,OAAO,KAAoC;AAC/C,SAAK,YAAY,GAAG;AACpB,UAAM,UAAU,KAAK,WAAW,GAAG;AAEnC,UAAM,SAAS,MAAM,KAAK,UAAU;AACpC,UAAM,EAAE,oBAAoB,IAAI,MAAM,OAAO,oBAAoB;AAEjE,QAAI;AACF,YAAM,OAAO;AAAA,QACX,IAAI,oBAAoB;AAAA,UACtB,QAAQ,KAAK;AAAA,UACb,KAAK;AAAA,QACP,CAAC;AAAA,MACH;AAEA,aAAO,EAAE,SAAS,MAAM,KAAK,QAAQ;AAAA,IACvC,SAAS,KAAK;AACZ,YAAM,IAAI;AAAA,QACR,kBAAkB,eAAe,QAAQ,IAAI,UAAU,eAAe;AAAA,QACtE,kBAAkB;AAAA,QAClB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,WAAW,MAA4C;AAC3D,UAAM,SAAS,MAAM,KAAK,UAAU;AACpC,UAAM,EAAE,qBAAqB,IAAI,MAAM,OAAO,oBAAoB;AAElE,UAAM,UAAU,KAAK,IAAI,CAAC,SAAS;AAAA,MACjC,KAAK,KAAK,WAAW,GAAG;AAAA,IAC1B,EAAE;AAEF,QAAI;AACF,YAAM,SAAS,MAAM,OAAO;AAAA,QAC1B,IAAI,qBAAqB;AAAA,UACvB,QAAQ,KAAK;AAAA,UACb,QAAQ,EAAE,SAAS,QAAQ;AAAA,QAC7B,CAAC;AAAA,MACH;AAEA,YAAM,UAAU,OAAO,SAAS,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC;AAC5D,YAAM,SACJ,OAAO,QAAQ,IAAI,CAAC,OAAO;AAAA,QACzB,KAAK,EAAE,OAAO;AAAA,QACd,OAAO,EAAE,WAAW;AAAA,MACtB,EAAE,KAAK,CAAC;AAEV,aAAO,EAAE,SAAS,OAAO;AAAA,IAC3B,SAAS,KAAK;AACZ,YAAM,IAAI;AAAA,QACR,wBAAwB,eAAe,QAAQ,IAAI,UAAU,eAAe;AAAA,QAC5E,kBAAkB;AAAA,QAClB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,KAAK,SAA4C;AACrD,UAAM,SAAS,MAAM,KAAK,UAAU;AACpC,UAAM,EAAE,qBAAqB,IAAI,MAAM,OAAO,oBAAoB;AAElE,UAAM,SAAS,SAAS,SACpB,KAAK,WAAW,QAAQ,MAAM,IAC9B,KAAK,YAAY;AAErB,QAAI;AACF,YAAM,SAAS,MAAM,OAAO;AAAA,QAC1B,IAAI,qBAAqB;AAAA,UACvB,QAAQ,KAAK;AAAA,UACb,QAAQ;AAAA,UACR,WAAW,SAAS;AAAA,UACpB,SAAS,SAAS;AAAA,UAClB,mBAAmB,SAAS;AAAA,QAC9B,CAAC;AAAA,MACH;AAEA,YAAM,QACJ,OAAO,UAAU,IAAI,CAAC,UAAU;AAAA,QAC9B,KAAK,KAAK,OAAO;AAAA,QACjB,MAAM,KAAK,QAAQ;AAAA,QACnB,aAAa;AAAA,QACb,cAAc,KAAK,gBAAgB;AAAA,QACnC,MAAM,KAAK,QAAQ;AAAA,QACnB,UAAU;AAAA,MACZ,EAAE,KAAK,CAAC;AAEV,YAAM,WACJ,OAAO,gBAAgB,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC;AAExD,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA,aAAa,OAAO,eAAe;AAAA,QACnC,mBAAmB,OAAO,yBAAyB;AAAA,MACrD;AAAA,IACF,SAAS,KAAK;AACZ,YAAM,IAAI;AAAA,QACR,gBAAgB,eAAe,QAAQ,IAAI,UAAU,eAAe;AAAA,QACpE,kBAAkB;AAAA,QAClB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,KACJ,WACA,SACA,SACuB;AACvB,SAAK,YAAY,SAAS;AAC1B,SAAK,YAAY,OAAO;AAExB,UAAM,SAAS,MAAM,KAAK,UAAU;AACpC,UAAM,EAAE,kBAAkB,IAAI,MAAM,OAAO,oBAAoB;AAE/D,UAAM,gBAAgB,KAAK,WAAW,SAAS;AAC/C,UAAM,cAAc,KAAK,WAAW,OAAO;AAC3C,UAAM,eAAe,SAAS,gBAAgB,KAAK;AAEnD,QAAI;AACF,YAAM,SAAS,MAAM,OAAO;AAAA,QAC1B,IAAI,kBAAkB;AAAA,UACpB,QAAQ,KAAK;AAAA,UACb,KAAK;AAAA,UACL,YAAY,GAAG,YAAY,IAAI,aAAa;AAAA,UAC5C,mBAAmB,SAAS;AAAA,UAC5B,aAAa,SAAS;AAAA,UACtB,UAAU,SAAS;AAAA,QACrB,CAAC;AAAA,MACH;AAGA,YAAM,WAAW,MAAM,KAAK,KAAK,OAAO;AAExC,aAAO,YAAY;AAAA,QACjB,KAAK;AAAA,QACL,MAAM;AAAA,QACN,aAAa,SAAS,eAAe;AAAA,QACrC,cAAc,OAAO,kBAAkB,gBAAgB,oBAAI,KAAK;AAAA,QAChE,MAAM,OAAO,kBAAkB,QAAQ;AAAA,QACvC,UAAU,SAAS,YAAY;AAAA,MACjC;AAAA,IACF,SAAS,KAAK;AACZ,YAAM,IAAI;AAAA,QACR,gBAAgB,eAAe,QAAQ,IAAI,UAAU,eAAe;AAAA,QACpE,kBAAkB;AAAA,QAClB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,KAAK,WAAmB,SAAwC;AACpE,UAAM,WAAW,MAAM,KAAK,KAAK,WAAW,OAAO;AACnD,UAAM,KAAK,OAAO,SAAS;AAC3B,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,gBACJ,KACA,SACiB;AACjB,SAAK,YAAY,GAAG;AACpB,UAAM,UAAU,KAAK,WAAW,GAAG;AAEnC,UAAM,SAAS,MAAM,KAAK,UAAU;AAEpC,QAAI;AACF,YAAM,EAAE,aAAa,IAAI,MAAM,OAAO,+BAA+B;AACrE,YAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,oBAAoB;AAE9D,YAAM,UAAU,IAAI,iBAAiB;AAAA,QACnC,QAAQ,KAAK;AAAA,QACb,KAAK;AAAA,QACL,sBAAsB,SAAS;AAAA,QAC/B,qBAAqB,SAAS;AAAA,QAC9B,4BAA4B,SAAS;AAAA,MACvC,CAAC;AAED,aAAO,aAAa,QAAQ,SAAS;AAAA,QACnC,WAAW,SAAS,aAAa;AAAA,MACnC,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,YAAM,IAAI;AAAA,QACR,yBAAyB,eAAe,QAAQ,IAAI,UAAU,eAAe;AAAA,QAC7E,kBAAkB;AAAA,QAClB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,aACJ,KACA,SACiB;AACjB,SAAK,YAAY,GAAG;AACpB,UAAM,UAAU,KAAK,WAAW,GAAG;AAEnC,UAAM,SAAS,MAAM,KAAK,UAAU;AAEpC,QAAI;AACF,YAAM,EAAE,aAAa,IAAI,MAAM,OAAO,+BAA+B;AACrE,YAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,oBAAoB;AAE9D,YAAM,UAAU,IAAI,iBAAiB;AAAA,QACnC,QAAQ,KAAK;AAAA,QACb,KAAK;AAAA,QACL,aAAa,SAAS;AAAA,QACtB,oBAAoB,SAAS;AAAA,MAC/B,CAAC;AAED,aAAO,aAAa,QAAQ,SAAS;AAAA,QACnC,WAAW,SAAS,aAAa;AAAA,MACnC,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,YAAM,IAAI;AAAA,QACR,sBAAsB,eAAe,QAAQ,IAAI,UAAU,eAAe;AAAA,QAC1E,kBAAkB;AAAA,QAClB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,mBACZ,QACqB;AACrB,UAAM,SAAS,OAAO,UAAU;AAChC,UAAM,SAAuB,CAAC;AAE9B,WAAO,MAAM;AACX,YAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,UAAI,KAAM;AACV,UAAI,MAAO,QAAO,KAAK,KAAK;AAAA,IAC9B;AAEA,UAAM,cAAc,OAAO,OAAO,CAAC,KAAK,UAAU,MAAM,MAAM,QAAQ,CAAC;AACvE,UAAM,SAAS,IAAI,WAAW,WAAW;AACzC,QAAI,SAAS;AAEb,eAAW,SAAS,QAAQ;AAC1B,aAAO,IAAI,OAAO,MAAM;AACxB,gBAAU,MAAM;AAAA,IAClB;AAEA,WAAO;AAAA,EACT;AACF;AAKO,SAAS,gBAAgB,QAA6B;AAC3D,SAAO,IAAI,UAAU,MAAM;AAC7B;AAKO,SAAS,sBACd,QAIW;AACX,SAAO,IAAI,UAAU;AAAA,IACnB,GAAG;AAAA,IACH,MAAM;AAAA,IACN,UAAU,WAAW,OAAO,MAAM;AAAA,IAClC,QAAQ,OAAO,aAAa,OAAO;AAAA,EACrC,CAAC;AACH;","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { R2Bucket as R2Bucket$1 } from './adapters/r2.js';
|
|
2
|
+
export { R2Adapter, createR2Adapter } from './adapters/r2.js';
|
|
3
|
+
import { S as S3Config, R as R2Config, M as MemoryConfig, a as StorageAdapter } from './types-CCTK5LsZ.js';
|
|
4
|
+
export { B as BatchDeleteResult, C as CopyOptions, d as DeleteResult, D as DownloadOptions, F as FileMetadata, L as ListOptions, c as ListResult, P as PresignedUrlOptions, b as StorageAdapterType, e as StorageConfig, f as StorageError, g as StorageErrorCodes, U as UploadOptions } from './types-CCTK5LsZ.js';
|
|
5
|
+
export { MemoryAdapter, createMemoryAdapter } from './adapters/memory.js';
|
|
6
|
+
export { S3Adapter, createDOSpacesAdapter, createS3Adapter } from './adapters/s3.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Combined storage configuration
|
|
10
|
+
*/
|
|
11
|
+
type AnyStorageConfig = S3Config | R2Config | MemoryConfig;
|
|
12
|
+
/**
|
|
13
|
+
* R2 binding type
|
|
14
|
+
*/
|
|
15
|
+
type R2Bucket = R2Bucket$1;
|
|
16
|
+
/**
|
|
17
|
+
* Create a storage adapter based on configuration
|
|
18
|
+
*
|
|
19
|
+
* @param config - Storage configuration
|
|
20
|
+
* @returns Storage adapter instance
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* // Memory (for testing)
|
|
25
|
+
* const memory = createStorage({ type: 'memory', bucket: 'test' });
|
|
26
|
+
*
|
|
27
|
+
* // AWS S3
|
|
28
|
+
* const s3 = createStorage({
|
|
29
|
+
* type: 's3',
|
|
30
|
+
* bucket: 'my-bucket',
|
|
31
|
+
* region: 'us-east-1',
|
|
32
|
+
* accessKeyId: 'xxx',
|
|
33
|
+
* secretAccessKey: 'xxx',
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* // Cloudflare R2
|
|
37
|
+
* const r2 = createStorage({
|
|
38
|
+
* type: 'r2',
|
|
39
|
+
* bucket: 'my-bucket',
|
|
40
|
+
* accountId: 'xxx',
|
|
41
|
+
* accessKeyId: 'xxx',
|
|
42
|
+
* secretAccessKey: 'xxx',
|
|
43
|
+
* });
|
|
44
|
+
*
|
|
45
|
+
* // DigitalOcean Spaces
|
|
46
|
+
* const spaces = createStorage({
|
|
47
|
+
* type: 'do-spaces',
|
|
48
|
+
* bucket: 'my-space',
|
|
49
|
+
* region: 'nyc3',
|
|
50
|
+
* accessKeyId: 'xxx',
|
|
51
|
+
* secretAccessKey: 'xxx',
|
|
52
|
+
* endpoint: 'https://nyc3.digitaloceanspaces.com',
|
|
53
|
+
* });
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
declare function createStorage(config: AnyStorageConfig & {
|
|
57
|
+
binding?: R2Bucket;
|
|
58
|
+
}): Promise<StorageAdapter>;
|
|
59
|
+
/**
|
|
60
|
+
* Create storage adapter synchronously
|
|
61
|
+
* Note: Some adapters may require async initialization for full functionality
|
|
62
|
+
*
|
|
63
|
+
* @param config - Storage configuration
|
|
64
|
+
* @returns Storage adapter instance
|
|
65
|
+
*/
|
|
66
|
+
declare function createStorageSync(config: AnyStorageConfig & {
|
|
67
|
+
binding?: R2Bucket;
|
|
68
|
+
}): StorageAdapter;
|
|
69
|
+
/**
|
|
70
|
+
* Storage utilities
|
|
71
|
+
*/
|
|
72
|
+
declare const StorageUtils: {
|
|
73
|
+
/**
|
|
74
|
+
* Get file extension from key
|
|
75
|
+
*/
|
|
76
|
+
getExtension(key: string): string;
|
|
77
|
+
/**
|
|
78
|
+
* Get file name from key
|
|
79
|
+
*/
|
|
80
|
+
getFileName(key: string): string;
|
|
81
|
+
/**
|
|
82
|
+
* Get directory from key
|
|
83
|
+
*/
|
|
84
|
+
getDirectory(key: string): string;
|
|
85
|
+
/**
|
|
86
|
+
* Join paths safely
|
|
87
|
+
*/
|
|
88
|
+
joinPath(...parts: string[]): string;
|
|
89
|
+
/**
|
|
90
|
+
* Normalize key (remove leading slash, handle ..)
|
|
91
|
+
*/
|
|
92
|
+
normalizeKey(key: string): string;
|
|
93
|
+
/**
|
|
94
|
+
* Generate a unique key with timestamp
|
|
95
|
+
*/
|
|
96
|
+
generateUniqueKey(prefix: string, extension?: string): string;
|
|
97
|
+
/**
|
|
98
|
+
* Guess content type from file extension
|
|
99
|
+
*/
|
|
100
|
+
guessContentType(key: string): string;
|
|
101
|
+
/**
|
|
102
|
+
* Format file size to human readable string
|
|
103
|
+
*/
|
|
104
|
+
formatSize(bytes: number): string;
|
|
105
|
+
/**
|
|
106
|
+
* Parse file size string to bytes
|
|
107
|
+
*/
|
|
108
|
+
parseSize(size: string): number;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export { type AnyStorageConfig, MemoryConfig, R2Bucket$1 as R2Bucket, R2Config, S3Config, StorageAdapter, StorageUtils, createStorage, createStorageSync };
|