@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,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
|