@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.
@@ -0,0 +1,818 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // src/types.ts
12
+ import {
13
+ type,
14
+ fileMetadata,
15
+ uploadOptions,
16
+ signedUrlOptions,
17
+ listFilesOptions,
18
+ listFilesResult,
19
+ localStorageConfig,
20
+ s3StorageConfig,
21
+ r2StorageConfig,
22
+ gcsStorageConfig,
23
+ storageProviderConfig
24
+ } from "@parsrun/types";
25
+ var StorageError, StorageErrorCodes;
26
+ var init_types = __esm({
27
+ "src/types.ts"() {
28
+ "use strict";
29
+ StorageError = class extends Error {
30
+ constructor(message, code, statusCode, cause) {
31
+ super(message);
32
+ this.code = code;
33
+ this.statusCode = statusCode;
34
+ this.cause = cause;
35
+ this.name = "StorageError";
36
+ }
37
+ };
38
+ StorageErrorCodes = {
39
+ NOT_FOUND: "NOT_FOUND",
40
+ ACCESS_DENIED: "ACCESS_DENIED",
41
+ BUCKET_NOT_FOUND: "BUCKET_NOT_FOUND",
42
+ INVALID_KEY: "INVALID_KEY",
43
+ UPLOAD_FAILED: "UPLOAD_FAILED",
44
+ DOWNLOAD_FAILED: "DOWNLOAD_FAILED",
45
+ DELETE_FAILED: "DELETE_FAILED",
46
+ COPY_FAILED: "COPY_FAILED",
47
+ LIST_FAILED: "LIST_FAILED",
48
+ PRESIGN_FAILED: "PRESIGN_FAILED",
49
+ QUOTA_EXCEEDED: "QUOTA_EXCEEDED",
50
+ INVALID_CONFIG: "INVALID_CONFIG",
51
+ ADAPTER_NOT_AVAILABLE: "ADAPTER_NOT_AVAILABLE"
52
+ };
53
+ }
54
+ });
55
+
56
+ // src/adapters/s3.ts
57
+ var s3_exports = {};
58
+ __export(s3_exports, {
59
+ S3Adapter: () => S3Adapter,
60
+ createDOSpacesAdapter: () => createDOSpacesAdapter,
61
+ createS3Adapter: () => createS3Adapter
62
+ });
63
+ function createS3Adapter(config) {
64
+ return new S3Adapter(config);
65
+ }
66
+ function createDOSpacesAdapter(config) {
67
+ return new S3Adapter({
68
+ ...config,
69
+ type: "do-spaces",
70
+ endpoint: `https://${config.region}.digitaloceanspaces.com`,
71
+ bucket: config.spaceName ?? config.bucket
72
+ });
73
+ }
74
+ var S3Adapter;
75
+ var init_s3 = __esm({
76
+ "src/adapters/s3.ts"() {
77
+ "use strict";
78
+ init_types();
79
+ S3Adapter = class {
80
+ type;
81
+ bucket;
82
+ client = null;
83
+ config;
84
+ basePath;
85
+ constructor(config) {
86
+ this.type = config.type;
87
+ this.bucket = config.bucket;
88
+ this.config = config;
89
+ this.basePath = config.basePath ?? "";
90
+ }
91
+ /**
92
+ * Lazy load S3 client
93
+ */
94
+ async getClient() {
95
+ if (this.client) return this.client;
96
+ try {
97
+ const { S3Client } = await import("@aws-sdk/client-s3");
98
+ const clientConfig = {
99
+ region: this.config.region,
100
+ credentials: {
101
+ accessKeyId: this.config.accessKeyId,
102
+ secretAccessKey: this.config.secretAccessKey
103
+ }
104
+ };
105
+ if (this.config.endpoint) {
106
+ clientConfig.endpoint = this.config.endpoint;
107
+ }
108
+ if (this.config.forcePathStyle !== void 0) {
109
+ clientConfig.forcePathStyle = this.config.forcePathStyle;
110
+ }
111
+ this.client = new S3Client(clientConfig);
112
+ return this.client;
113
+ } catch {
114
+ throw new StorageError(
115
+ "AWS SDK not installed. Run: npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner",
116
+ StorageErrorCodes.ADAPTER_NOT_AVAILABLE
117
+ );
118
+ }
119
+ }
120
+ getFullKey(key) {
121
+ return this.basePath ? `${this.basePath}/${key}` : key;
122
+ }
123
+ validateKey(key) {
124
+ if (!key || key.includes("..") || key.startsWith("/")) {
125
+ throw new StorageError(
126
+ `Invalid key: ${key}`,
127
+ StorageErrorCodes.INVALID_KEY
128
+ );
129
+ }
130
+ }
131
+ async dataToBody(data) {
132
+ if (data instanceof Uint8Array || typeof data === "string") {
133
+ return data;
134
+ }
135
+ if (data instanceof Blob) {
136
+ const buffer = await data.arrayBuffer();
137
+ return new Uint8Array(buffer);
138
+ }
139
+ return data;
140
+ }
141
+ async upload(key, data, options) {
142
+ this.validateKey(key);
143
+ const fullKey = this.getFullKey(key);
144
+ const client = await this.getClient();
145
+ const { PutObjectCommand } = await import("@aws-sdk/client-s3");
146
+ const body = await this.dataToBody(data);
147
+ const params = {
148
+ Bucket: this.bucket,
149
+ Key: fullKey,
150
+ Body: body,
151
+ ContentType: options?.contentType,
152
+ ContentDisposition: options?.contentDisposition,
153
+ CacheControl: options?.cacheControl,
154
+ ContentEncoding: options?.contentEncoding,
155
+ Metadata: options?.metadata
156
+ };
157
+ if (options?.acl) {
158
+ params.ACL = options.acl;
159
+ }
160
+ try {
161
+ const result = await client.send(new PutObjectCommand(params));
162
+ let size = 0;
163
+ if (typeof body === "string") {
164
+ size = new TextEncoder().encode(body).length;
165
+ } else if (body instanceof Uint8Array) {
166
+ size = body.length;
167
+ }
168
+ return {
169
+ key: fullKey,
170
+ size,
171
+ contentType: options?.contentType ?? void 0,
172
+ lastModified: /* @__PURE__ */ new Date(),
173
+ etag: result.ETag ?? void 0,
174
+ metadata: options?.metadata ?? void 0
175
+ };
176
+ } catch (err) {
177
+ throw new StorageError(
178
+ `Upload failed: ${err instanceof Error ? err.message : "Unknown error"}`,
179
+ StorageErrorCodes.UPLOAD_FAILED,
180
+ void 0,
181
+ err
182
+ );
183
+ }
184
+ }
185
+ async download(key, options) {
186
+ this.validateKey(key);
187
+ const fullKey = this.getFullKey(key);
188
+ const client = await this.getClient();
189
+ const { GetObjectCommand } = await import("@aws-sdk/client-s3");
190
+ try {
191
+ const params = {
192
+ Bucket: this.bucket,
193
+ Key: fullKey
194
+ };
195
+ if (options?.rangeStart !== void 0 || options?.rangeEnd !== void 0) {
196
+ const start = options.rangeStart ?? 0;
197
+ const end = options.rangeEnd ?? "";
198
+ params.Range = `bytes=${start}-${end}`;
199
+ }
200
+ if (options?.ifNoneMatch) {
201
+ params.IfNoneMatch = options.ifNoneMatch;
202
+ }
203
+ if (options?.ifModifiedSince) {
204
+ params.IfModifiedSince = options.ifModifiedSince;
205
+ }
206
+ const result = await client.send(new GetObjectCommand(params));
207
+ if (!result.Body) {
208
+ throw new StorageError(
209
+ "Empty response body",
210
+ StorageErrorCodes.DOWNLOAD_FAILED
211
+ );
212
+ }
213
+ const stream = result.Body;
214
+ return this.streamToUint8Array(stream);
215
+ } catch (err) {
216
+ if (err instanceof Error && "name" in err && err.name === "NoSuchKey") {
217
+ throw new StorageError(
218
+ `File not found: ${key}`,
219
+ StorageErrorCodes.NOT_FOUND,
220
+ 404
221
+ );
222
+ }
223
+ throw new StorageError(
224
+ `Download failed: ${err instanceof Error ? err.message : "Unknown error"}`,
225
+ StorageErrorCodes.DOWNLOAD_FAILED,
226
+ void 0,
227
+ err
228
+ );
229
+ }
230
+ }
231
+ async downloadStream(key, options) {
232
+ this.validateKey(key);
233
+ const fullKey = this.getFullKey(key);
234
+ const client = await this.getClient();
235
+ const { GetObjectCommand } = await import("@aws-sdk/client-s3");
236
+ try {
237
+ const params = {
238
+ Bucket: this.bucket,
239
+ Key: fullKey
240
+ };
241
+ if (options?.rangeStart !== void 0 || options?.rangeEnd !== void 0) {
242
+ const start = options.rangeStart ?? 0;
243
+ const end = options.rangeEnd ?? "";
244
+ params.Range = `bytes=${start}-${end}`;
245
+ }
246
+ const result = await client.send(new GetObjectCommand(params));
247
+ if (!result.Body) {
248
+ throw new StorageError(
249
+ "Empty response body",
250
+ StorageErrorCodes.DOWNLOAD_FAILED
251
+ );
252
+ }
253
+ return result.Body;
254
+ } catch (err) {
255
+ if (err instanceof Error && "name" in err && err.name === "NoSuchKey") {
256
+ throw new StorageError(
257
+ `File not found: ${key}`,
258
+ StorageErrorCodes.NOT_FOUND,
259
+ 404
260
+ );
261
+ }
262
+ throw err;
263
+ }
264
+ }
265
+ async head(key) {
266
+ this.validateKey(key);
267
+ const fullKey = this.getFullKey(key);
268
+ const client = await this.getClient();
269
+ const { HeadObjectCommand } = await import("@aws-sdk/client-s3");
270
+ try {
271
+ const result = await client.send(
272
+ new HeadObjectCommand({
273
+ Bucket: this.bucket,
274
+ Key: fullKey
275
+ })
276
+ );
277
+ return {
278
+ key: fullKey,
279
+ size: result.ContentLength ?? 0,
280
+ contentType: result.ContentType ?? void 0,
281
+ lastModified: result.LastModified ?? void 0,
282
+ etag: result.ETag ?? void 0,
283
+ metadata: result.Metadata ?? void 0
284
+ };
285
+ } catch (err) {
286
+ if (err instanceof Error && "name" in err && (err.name === "NoSuchKey" || err.name === "NotFound")) {
287
+ return null;
288
+ }
289
+ throw err;
290
+ }
291
+ }
292
+ async exists(key) {
293
+ const metadata = await this.head(key);
294
+ return metadata !== null;
295
+ }
296
+ async delete(key) {
297
+ this.validateKey(key);
298
+ const fullKey = this.getFullKey(key);
299
+ const client = await this.getClient();
300
+ const { DeleteObjectCommand } = await import("@aws-sdk/client-s3");
301
+ try {
302
+ await client.send(
303
+ new DeleteObjectCommand({
304
+ Bucket: this.bucket,
305
+ Key: fullKey
306
+ })
307
+ );
308
+ return { success: true, key: fullKey };
309
+ } catch (err) {
310
+ throw new StorageError(
311
+ `Delete failed: ${err instanceof Error ? err.message : "Unknown error"}`,
312
+ StorageErrorCodes.DELETE_FAILED,
313
+ void 0,
314
+ err
315
+ );
316
+ }
317
+ }
318
+ async deleteMany(keys) {
319
+ const client = await this.getClient();
320
+ const { DeleteObjectsCommand } = await import("@aws-sdk/client-s3");
321
+ const objects = keys.map((key) => ({
322
+ Key: this.getFullKey(key)
323
+ }));
324
+ try {
325
+ const result = await client.send(
326
+ new DeleteObjectsCommand({
327
+ Bucket: this.bucket,
328
+ Delete: { Objects: objects }
329
+ })
330
+ );
331
+ const deleted = result.Deleted?.map((d) => d.Key ?? "") ?? [];
332
+ const errors = result.Errors?.map((e) => ({
333
+ key: e.Key ?? "",
334
+ error: e.Message ?? "Unknown error"
335
+ })) ?? [];
336
+ return { deleted, errors };
337
+ } catch (err) {
338
+ throw new StorageError(
339
+ `Batch delete failed: ${err instanceof Error ? err.message : "Unknown error"}`,
340
+ StorageErrorCodes.DELETE_FAILED,
341
+ void 0,
342
+ err
343
+ );
344
+ }
345
+ }
346
+ async list(options) {
347
+ const client = await this.getClient();
348
+ const { ListObjectsV2Command } = await import("@aws-sdk/client-s3");
349
+ const prefix = options?.prefix ? this.getFullKey(options.prefix) : this.basePath || void 0;
350
+ try {
351
+ const result = await client.send(
352
+ new ListObjectsV2Command({
353
+ Bucket: this.bucket,
354
+ Prefix: prefix,
355
+ Delimiter: options?.delimiter,
356
+ MaxKeys: options?.maxKeys,
357
+ ContinuationToken: options?.continuationToken
358
+ })
359
+ );
360
+ const files = result.Contents?.map((item) => ({
361
+ key: item.Key ?? "",
362
+ size: item.Size ?? 0,
363
+ contentType: void 0,
364
+ lastModified: item.LastModified ?? void 0,
365
+ etag: item.ETag ?? void 0,
366
+ metadata: void 0
367
+ })) ?? [];
368
+ const prefixes = result.CommonPrefixes?.map((p) => p.Prefix ?? "") ?? [];
369
+ return {
370
+ files,
371
+ prefixes,
372
+ isTruncated: result.IsTruncated ?? false,
373
+ continuationToken: result.NextContinuationToken ?? void 0
374
+ };
375
+ } catch (err) {
376
+ throw new StorageError(
377
+ `List failed: ${err instanceof Error ? err.message : "Unknown error"}`,
378
+ StorageErrorCodes.LIST_FAILED,
379
+ void 0,
380
+ err
381
+ );
382
+ }
383
+ }
384
+ async copy(sourceKey, destKey, options) {
385
+ this.validateKey(sourceKey);
386
+ this.validateKey(destKey);
387
+ const client = await this.getClient();
388
+ const { CopyObjectCommand } = await import("@aws-sdk/client-s3");
389
+ const sourceFullKey = this.getFullKey(sourceKey);
390
+ const destFullKey = this.getFullKey(destKey);
391
+ const sourceBucket = options?.sourceBucket ?? this.bucket;
392
+ try {
393
+ const result = await client.send(
394
+ new CopyObjectCommand({
395
+ Bucket: this.bucket,
396
+ Key: destFullKey,
397
+ CopySource: `${sourceBucket}/${sourceFullKey}`,
398
+ MetadataDirective: options?.metadataDirective,
399
+ ContentType: options?.contentType,
400
+ Metadata: options?.metadata
401
+ })
402
+ );
403
+ const metadata = await this.head(destKey);
404
+ return metadata ?? {
405
+ key: destFullKey,
406
+ size: 0,
407
+ contentType: options?.contentType ?? void 0,
408
+ lastModified: result.CopyObjectResult?.LastModified ?? /* @__PURE__ */ new Date(),
409
+ etag: result.CopyObjectResult?.ETag ?? void 0,
410
+ metadata: options?.metadata ?? void 0
411
+ };
412
+ } catch (err) {
413
+ throw new StorageError(
414
+ `Copy failed: ${err instanceof Error ? err.message : "Unknown error"}`,
415
+ StorageErrorCodes.COPY_FAILED,
416
+ void 0,
417
+ err
418
+ );
419
+ }
420
+ }
421
+ async move(sourceKey, destKey) {
422
+ const metadata = await this.copy(sourceKey, destKey);
423
+ await this.delete(sourceKey);
424
+ return metadata;
425
+ }
426
+ async getPresignedUrl(key, options) {
427
+ this.validateKey(key);
428
+ const fullKey = this.getFullKey(key);
429
+ const client = await this.getClient();
430
+ try {
431
+ const { getSignedUrl } = await import("@aws-sdk/s3-request-presigner");
432
+ const { GetObjectCommand } = await import("@aws-sdk/client-s3");
433
+ const command = new GetObjectCommand({
434
+ Bucket: this.bucket,
435
+ Key: fullKey,
436
+ ResponseCacheControl: options?.responseCacheControl,
437
+ ResponseContentType: options?.responseContentType,
438
+ ResponseContentDisposition: options?.contentDisposition
439
+ });
440
+ return getSignedUrl(client, command, {
441
+ expiresIn: options?.expiresIn ?? 3600
442
+ });
443
+ } catch (err) {
444
+ throw new StorageError(
445
+ `Presigned URL failed: ${err instanceof Error ? err.message : "Unknown error"}`,
446
+ StorageErrorCodes.PRESIGN_FAILED,
447
+ void 0,
448
+ err
449
+ );
450
+ }
451
+ }
452
+ async getUploadUrl(key, options) {
453
+ this.validateKey(key);
454
+ const fullKey = this.getFullKey(key);
455
+ const client = await this.getClient();
456
+ try {
457
+ const { getSignedUrl } = await import("@aws-sdk/s3-request-presigner");
458
+ const { PutObjectCommand } = await import("@aws-sdk/client-s3");
459
+ const command = new PutObjectCommand({
460
+ Bucket: this.bucket,
461
+ Key: fullKey,
462
+ ContentType: options?.contentType,
463
+ ContentDisposition: options?.contentDisposition
464
+ });
465
+ return getSignedUrl(client, command, {
466
+ expiresIn: options?.expiresIn ?? 3600
467
+ });
468
+ } catch (err) {
469
+ throw new StorageError(
470
+ `Upload URL failed: ${err instanceof Error ? err.message : "Unknown error"}`,
471
+ StorageErrorCodes.PRESIGN_FAILED,
472
+ void 0,
473
+ err
474
+ );
475
+ }
476
+ }
477
+ async streamToUint8Array(stream) {
478
+ const reader = stream.getReader();
479
+ const chunks = [];
480
+ while (true) {
481
+ const { done, value } = await reader.read();
482
+ if (done) break;
483
+ if (value) chunks.push(value);
484
+ }
485
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
486
+ const result = new Uint8Array(totalLength);
487
+ let offset = 0;
488
+ for (const chunk of chunks) {
489
+ result.set(chunk, offset);
490
+ offset += chunk.length;
491
+ }
492
+ return result;
493
+ }
494
+ };
495
+ }
496
+ });
497
+
498
+ // src/adapters/r2.ts
499
+ init_types();
500
+ var R2Adapter = class {
501
+ type = "r2";
502
+ bucket;
503
+ binding = null;
504
+ s3Adapter = null;
505
+ config;
506
+ basePath;
507
+ constructor(config) {
508
+ this.bucket = config.bucket;
509
+ this.config = config;
510
+ this.basePath = config.basePath ?? "";
511
+ this.binding = config.binding ?? null;
512
+ }
513
+ /**
514
+ * Get S3 adapter for fallback
515
+ */
516
+ async getS3Adapter() {
517
+ if (this.s3Adapter) return this.s3Adapter;
518
+ const { S3Adapter: S3Adapter2 } = await Promise.resolve().then(() => (init_s3(), s3_exports));
519
+ this.s3Adapter = new S3Adapter2({
520
+ type: "s3",
521
+ bucket: this.bucket,
522
+ region: "auto",
523
+ accessKeyId: this.config.accessKeyId,
524
+ secretAccessKey: this.config.secretAccessKey,
525
+ endpoint: `https://${this.config.accountId}.r2.cloudflarestorage.com`
526
+ });
527
+ return this.s3Adapter;
528
+ }
529
+ getFullKey(key) {
530
+ return this.basePath ? `${this.basePath}/${key}` : key;
531
+ }
532
+ validateKey(key) {
533
+ if (!key || key.includes("..") || key.startsWith("/")) {
534
+ throw new StorageError(
535
+ `Invalid key: ${key}`,
536
+ StorageErrorCodes.INVALID_KEY
537
+ );
538
+ }
539
+ }
540
+ async dataToBody(data) {
541
+ if (data instanceof Uint8Array) {
542
+ const buffer = new ArrayBuffer(data.length);
543
+ new Uint8Array(buffer).set(data);
544
+ return buffer;
545
+ }
546
+ return data;
547
+ }
548
+ async upload(key, data, options) {
549
+ this.validateKey(key);
550
+ const fullKey = this.getFullKey(key);
551
+ if (this.binding) {
552
+ const body = await this.dataToBody(data);
553
+ const httpMetadata = {};
554
+ if (options?.contentType) httpMetadata.contentType = options.contentType;
555
+ if (options?.contentDisposition) httpMetadata.contentDisposition = options.contentDisposition;
556
+ if (options?.cacheControl) httpMetadata.cacheControl = options.cacheControl;
557
+ if (options?.contentEncoding) httpMetadata.contentEncoding = options.contentEncoding;
558
+ const putOptions = {
559
+ httpMetadata
560
+ };
561
+ if (options?.metadata) {
562
+ putOptions.customMetadata = options.metadata;
563
+ }
564
+ try {
565
+ const result = await this.binding.put(fullKey, body, putOptions);
566
+ return {
567
+ key: fullKey,
568
+ size: result.size,
569
+ contentType: result.httpMetadata?.contentType ?? void 0,
570
+ lastModified: result.uploaded,
571
+ etag: result.etag,
572
+ metadata: result.customMetadata ?? void 0
573
+ };
574
+ } catch (err) {
575
+ throw new StorageError(
576
+ `Upload failed: ${err instanceof Error ? err.message : "Unknown error"}`,
577
+ StorageErrorCodes.UPLOAD_FAILED,
578
+ void 0,
579
+ err
580
+ );
581
+ }
582
+ }
583
+ const s3 = await this.getS3Adapter();
584
+ return s3.upload(key, data, options);
585
+ }
586
+ async download(key, options) {
587
+ this.validateKey(key);
588
+ const fullKey = this.getFullKey(key);
589
+ if (this.binding) {
590
+ try {
591
+ const getOptions = {};
592
+ if (options?.rangeStart !== void 0 || options?.rangeEnd !== void 0) {
593
+ const rangeOpts = {
594
+ offset: options.rangeStart ?? 0
595
+ };
596
+ if (options.rangeEnd !== void 0) {
597
+ rangeOpts.length = options.rangeEnd - (options.rangeStart ?? 0) + 1;
598
+ }
599
+ getOptions.range = rangeOpts;
600
+ }
601
+ if (options?.ifNoneMatch || options?.ifModifiedSince) {
602
+ const conditional = {};
603
+ if (options.ifNoneMatch) conditional.etagDoesNotMatch = options.ifNoneMatch;
604
+ if (options.ifModifiedSince) conditional.uploadedAfter = options.ifModifiedSince;
605
+ getOptions.onlyIf = conditional;
606
+ }
607
+ const result = await this.binding.get(fullKey, getOptions);
608
+ if (!result) {
609
+ throw new StorageError(
610
+ `File not found: ${key}`,
611
+ StorageErrorCodes.NOT_FOUND,
612
+ 404
613
+ );
614
+ }
615
+ const buffer = await result.arrayBuffer();
616
+ return new Uint8Array(buffer);
617
+ } catch (err) {
618
+ if (err instanceof StorageError) throw err;
619
+ throw new StorageError(
620
+ `Download failed: ${err instanceof Error ? err.message : "Unknown error"}`,
621
+ StorageErrorCodes.DOWNLOAD_FAILED,
622
+ void 0,
623
+ err
624
+ );
625
+ }
626
+ }
627
+ const s3 = await this.getS3Adapter();
628
+ return s3.download(key, options);
629
+ }
630
+ async downloadStream(key, options) {
631
+ this.validateKey(key);
632
+ const fullKey = this.getFullKey(key);
633
+ if (this.binding) {
634
+ try {
635
+ const getOptions = {};
636
+ if (options?.rangeStart !== void 0 || options?.rangeEnd !== void 0) {
637
+ const rangeOpts = {
638
+ offset: options.rangeStart ?? 0
639
+ };
640
+ if (options.rangeEnd !== void 0) {
641
+ rangeOpts.length = options.rangeEnd - (options.rangeStart ?? 0) + 1;
642
+ }
643
+ getOptions.range = rangeOpts;
644
+ }
645
+ const result = await this.binding.get(fullKey, getOptions);
646
+ if (!result) {
647
+ throw new StorageError(
648
+ `File not found: ${key}`,
649
+ StorageErrorCodes.NOT_FOUND,
650
+ 404
651
+ );
652
+ }
653
+ return result.body;
654
+ } catch (err) {
655
+ if (err instanceof StorageError) throw err;
656
+ throw new StorageError(
657
+ `Download failed: ${err instanceof Error ? err.message : "Unknown error"}`,
658
+ StorageErrorCodes.DOWNLOAD_FAILED,
659
+ void 0,
660
+ err
661
+ );
662
+ }
663
+ }
664
+ const s3 = await this.getS3Adapter();
665
+ return s3.downloadStream(key, options);
666
+ }
667
+ async head(key) {
668
+ this.validateKey(key);
669
+ const fullKey = this.getFullKey(key);
670
+ if (this.binding) {
671
+ try {
672
+ const result = await this.binding.head(fullKey);
673
+ if (!result) {
674
+ return null;
675
+ }
676
+ return {
677
+ key: fullKey,
678
+ size: result.size,
679
+ contentType: result.httpMetadata?.contentType ?? void 0,
680
+ lastModified: result.uploaded,
681
+ etag: result.etag,
682
+ metadata: result.customMetadata ?? void 0
683
+ };
684
+ } catch {
685
+ return null;
686
+ }
687
+ }
688
+ const s3 = await this.getS3Adapter();
689
+ return s3.head(key);
690
+ }
691
+ async exists(key) {
692
+ const metadata = await this.head(key);
693
+ return metadata !== null;
694
+ }
695
+ async delete(key) {
696
+ this.validateKey(key);
697
+ const fullKey = this.getFullKey(key);
698
+ if (this.binding) {
699
+ try {
700
+ await this.binding.delete(fullKey);
701
+ return { success: true, key: fullKey };
702
+ } catch (err) {
703
+ throw new StorageError(
704
+ `Delete failed: ${err instanceof Error ? err.message : "Unknown error"}`,
705
+ StorageErrorCodes.DELETE_FAILED,
706
+ void 0,
707
+ err
708
+ );
709
+ }
710
+ }
711
+ const s3 = await this.getS3Adapter();
712
+ return s3.delete(key);
713
+ }
714
+ async deleteMany(keys) {
715
+ if (this.binding) {
716
+ const fullKeys = keys.map((key) => this.getFullKey(key));
717
+ try {
718
+ await this.binding.delete(fullKeys);
719
+ return { deleted: fullKeys, errors: [] };
720
+ } catch (err) {
721
+ return {
722
+ deleted: [],
723
+ errors: fullKeys.map((key) => ({
724
+ key,
725
+ error: err instanceof Error ? err.message : "Unknown error"
726
+ }))
727
+ };
728
+ }
729
+ }
730
+ const s3 = await this.getS3Adapter();
731
+ return s3.deleteMany(keys);
732
+ }
733
+ async list(options) {
734
+ if (this.binding) {
735
+ try {
736
+ const listOpts = {
737
+ include: ["httpMetadata", "customMetadata"]
738
+ };
739
+ const prefix = options?.prefix ? this.getFullKey(options.prefix) : this.basePath || null;
740
+ if (prefix) listOpts.prefix = prefix;
741
+ if (options?.delimiter) listOpts.delimiter = options.delimiter;
742
+ if (options?.maxKeys) listOpts.limit = options.maxKeys;
743
+ if (options?.continuationToken) listOpts.cursor = options.continuationToken;
744
+ const result = await this.binding.list(listOpts);
745
+ const files = result.objects.map((obj) => ({
746
+ key: obj.key,
747
+ size: obj.size,
748
+ contentType: obj.httpMetadata?.contentType ?? void 0,
749
+ lastModified: obj.uploaded,
750
+ etag: obj.etag,
751
+ metadata: obj.customMetadata ?? void 0
752
+ }));
753
+ return {
754
+ files,
755
+ prefixes: result.delimitedPrefixes,
756
+ isTruncated: result.truncated,
757
+ continuationToken: result.cursor ?? void 0
758
+ };
759
+ } catch (err) {
760
+ throw new StorageError(
761
+ `List failed: ${err instanceof Error ? err.message : "Unknown error"}`,
762
+ StorageErrorCodes.LIST_FAILED,
763
+ void 0,
764
+ err
765
+ );
766
+ }
767
+ }
768
+ const s3 = await this.getS3Adapter();
769
+ return s3.list(options);
770
+ }
771
+ async copy(sourceKey, destKey, options) {
772
+ if (this.binding) {
773
+ const data = await this.download(sourceKey);
774
+ const sourceMetadata = await this.head(sourceKey);
775
+ const uploadOptions2 = options?.metadataDirective === "REPLACE" ? {
776
+ contentType: options.contentType,
777
+ metadata: options.metadata
778
+ } : {
779
+ contentType: sourceMetadata?.contentType,
780
+ metadata: sourceMetadata?.metadata
781
+ };
782
+ return this.upload(destKey, data, uploadOptions2);
783
+ }
784
+ const s3 = await this.getS3Adapter();
785
+ return s3.copy(sourceKey, destKey, options);
786
+ }
787
+ async move(sourceKey, destKey) {
788
+ const metadata = await this.copy(sourceKey, destKey);
789
+ await this.delete(sourceKey);
790
+ return metadata;
791
+ }
792
+ async getPresignedUrl(key, options) {
793
+ const s3 = await this.getS3Adapter();
794
+ return s3.getPresignedUrl(key, options);
795
+ }
796
+ async getUploadUrl(key, options) {
797
+ const s3 = await this.getS3Adapter();
798
+ return s3.getUploadUrl(key, options);
799
+ }
800
+ /**
801
+ * Get public URL for a file (if custom domain is configured)
802
+ */
803
+ getPublicUrl(key) {
804
+ if (!this.config.customDomain) {
805
+ return null;
806
+ }
807
+ const fullKey = this.getFullKey(key);
808
+ return `https://${this.config.customDomain}/${fullKey}`;
809
+ }
810
+ };
811
+ function createR2Adapter(config) {
812
+ return new R2Adapter(config);
813
+ }
814
+ export {
815
+ R2Adapter,
816
+ createR2Adapter
817
+ };
818
+ //# sourceMappingURL=r2.js.map