@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
package/dist/index.js
ADDED
|
@@ -0,0 +1,1316 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __esm = (fn, res) => function __init() {
|
|
6
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
7
|
+
};
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
21
|
+
|
|
22
|
+
// src/types.ts
|
|
23
|
+
import {
|
|
24
|
+
type,
|
|
25
|
+
fileMetadata,
|
|
26
|
+
uploadOptions,
|
|
27
|
+
signedUrlOptions,
|
|
28
|
+
listFilesOptions,
|
|
29
|
+
listFilesResult,
|
|
30
|
+
localStorageConfig,
|
|
31
|
+
s3StorageConfig,
|
|
32
|
+
r2StorageConfig,
|
|
33
|
+
gcsStorageConfig,
|
|
34
|
+
storageProviderConfig
|
|
35
|
+
} from "@parsrun/types";
|
|
36
|
+
var StorageError, StorageErrorCodes;
|
|
37
|
+
var init_types = __esm({
|
|
38
|
+
"src/types.ts"() {
|
|
39
|
+
"use strict";
|
|
40
|
+
StorageError = class extends Error {
|
|
41
|
+
constructor(message, code, statusCode, cause) {
|
|
42
|
+
super(message);
|
|
43
|
+
this.code = code;
|
|
44
|
+
this.statusCode = statusCode;
|
|
45
|
+
this.cause = cause;
|
|
46
|
+
this.name = "StorageError";
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
StorageErrorCodes = {
|
|
50
|
+
NOT_FOUND: "NOT_FOUND",
|
|
51
|
+
ACCESS_DENIED: "ACCESS_DENIED",
|
|
52
|
+
BUCKET_NOT_FOUND: "BUCKET_NOT_FOUND",
|
|
53
|
+
INVALID_KEY: "INVALID_KEY",
|
|
54
|
+
UPLOAD_FAILED: "UPLOAD_FAILED",
|
|
55
|
+
DOWNLOAD_FAILED: "DOWNLOAD_FAILED",
|
|
56
|
+
DELETE_FAILED: "DELETE_FAILED",
|
|
57
|
+
COPY_FAILED: "COPY_FAILED",
|
|
58
|
+
LIST_FAILED: "LIST_FAILED",
|
|
59
|
+
PRESIGN_FAILED: "PRESIGN_FAILED",
|
|
60
|
+
QUOTA_EXCEEDED: "QUOTA_EXCEEDED",
|
|
61
|
+
INVALID_CONFIG: "INVALID_CONFIG",
|
|
62
|
+
ADAPTER_NOT_AVAILABLE: "ADAPTER_NOT_AVAILABLE"
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// src/adapters/memory.ts
|
|
68
|
+
var memory_exports = {};
|
|
69
|
+
__export(memory_exports, {
|
|
70
|
+
MemoryAdapter: () => MemoryAdapter,
|
|
71
|
+
createMemoryAdapter: () => createMemoryAdapter
|
|
72
|
+
});
|
|
73
|
+
function createMemoryAdapter(config) {
|
|
74
|
+
return new MemoryAdapter({ ...config, type: "memory" });
|
|
75
|
+
}
|
|
76
|
+
var MemoryAdapter;
|
|
77
|
+
var init_memory = __esm({
|
|
78
|
+
"src/adapters/memory.ts"() {
|
|
79
|
+
"use strict";
|
|
80
|
+
init_types();
|
|
81
|
+
MemoryAdapter = class {
|
|
82
|
+
type = "memory";
|
|
83
|
+
bucket;
|
|
84
|
+
files = /* @__PURE__ */ new Map();
|
|
85
|
+
maxSize;
|
|
86
|
+
currentSize = 0;
|
|
87
|
+
basePath;
|
|
88
|
+
constructor(config) {
|
|
89
|
+
this.bucket = config.bucket;
|
|
90
|
+
this.maxSize = config.maxSize ?? Infinity;
|
|
91
|
+
this.basePath = config.basePath ?? "";
|
|
92
|
+
}
|
|
93
|
+
getFullKey(key) {
|
|
94
|
+
return this.basePath ? `${this.basePath}/${key}` : key;
|
|
95
|
+
}
|
|
96
|
+
validateKey(key) {
|
|
97
|
+
if (!key || key.includes("..") || key.startsWith("/")) {
|
|
98
|
+
throw new StorageError(
|
|
99
|
+
`Invalid key: ${key}`,
|
|
100
|
+
StorageErrorCodes.INVALID_KEY
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
dataToUint8Array(data) {
|
|
105
|
+
if (data instanceof Uint8Array) {
|
|
106
|
+
return data;
|
|
107
|
+
}
|
|
108
|
+
if (typeof data === "string") {
|
|
109
|
+
return new TextEncoder().encode(data);
|
|
110
|
+
}
|
|
111
|
+
if (data instanceof Blob) {
|
|
112
|
+
return data.arrayBuffer().then((buffer) => new Uint8Array(buffer));
|
|
113
|
+
}
|
|
114
|
+
return this.streamToUint8Array(data);
|
|
115
|
+
}
|
|
116
|
+
async streamToUint8Array(stream) {
|
|
117
|
+
const reader = stream.getReader();
|
|
118
|
+
const chunks = [];
|
|
119
|
+
while (true) {
|
|
120
|
+
const { done, value } = await reader.read();
|
|
121
|
+
if (done) break;
|
|
122
|
+
chunks.push(value);
|
|
123
|
+
}
|
|
124
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
125
|
+
const result = new Uint8Array(totalLength);
|
|
126
|
+
let offset = 0;
|
|
127
|
+
for (const chunk of chunks) {
|
|
128
|
+
result.set(chunk, offset);
|
|
129
|
+
offset += chunk.length;
|
|
130
|
+
}
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
async upload(key, data, options) {
|
|
134
|
+
this.validateKey(key);
|
|
135
|
+
const fullKey = this.getFullKey(key);
|
|
136
|
+
const uint8Data = await this.dataToUint8Array(data);
|
|
137
|
+
const existingFile = this.files.get(fullKey);
|
|
138
|
+
const sizeDiff = uint8Data.length - (existingFile?.data.length ?? 0);
|
|
139
|
+
if (this.currentSize + sizeDiff > this.maxSize) {
|
|
140
|
+
throw new StorageError(
|
|
141
|
+
"Storage quota exceeded",
|
|
142
|
+
StorageErrorCodes.QUOTA_EXCEEDED
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
const metadata = {
|
|
146
|
+
key: fullKey,
|
|
147
|
+
size: uint8Data.length,
|
|
148
|
+
contentType: options?.contentType ?? this.guessContentType(key),
|
|
149
|
+
lastModified: /* @__PURE__ */ new Date(),
|
|
150
|
+
etag: this.generateEtag(uint8Data),
|
|
151
|
+
metadata: options?.metadata ?? void 0
|
|
152
|
+
};
|
|
153
|
+
this.files.set(fullKey, { data: uint8Data, metadata });
|
|
154
|
+
this.currentSize += sizeDiff;
|
|
155
|
+
return metadata;
|
|
156
|
+
}
|
|
157
|
+
async download(key, _options) {
|
|
158
|
+
this.validateKey(key);
|
|
159
|
+
const fullKey = this.getFullKey(key);
|
|
160
|
+
const file = this.files.get(fullKey);
|
|
161
|
+
if (!file) {
|
|
162
|
+
throw new StorageError(
|
|
163
|
+
`File not found: ${key}`,
|
|
164
|
+
StorageErrorCodes.NOT_FOUND,
|
|
165
|
+
404
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
return file.data;
|
|
169
|
+
}
|
|
170
|
+
async downloadStream(key, _options) {
|
|
171
|
+
const data = await this.download(key);
|
|
172
|
+
return new ReadableStream({
|
|
173
|
+
start(controller) {
|
|
174
|
+
controller.enqueue(data);
|
|
175
|
+
controller.close();
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
async head(key) {
|
|
180
|
+
this.validateKey(key);
|
|
181
|
+
const fullKey = this.getFullKey(key);
|
|
182
|
+
const file = this.files.get(fullKey);
|
|
183
|
+
return file?.metadata ?? null;
|
|
184
|
+
}
|
|
185
|
+
async exists(key) {
|
|
186
|
+
this.validateKey(key);
|
|
187
|
+
const fullKey = this.getFullKey(key);
|
|
188
|
+
return this.files.has(fullKey);
|
|
189
|
+
}
|
|
190
|
+
async delete(key) {
|
|
191
|
+
this.validateKey(key);
|
|
192
|
+
const fullKey = this.getFullKey(key);
|
|
193
|
+
const file = this.files.get(fullKey);
|
|
194
|
+
if (file) {
|
|
195
|
+
this.currentSize -= file.data.length;
|
|
196
|
+
this.files.delete(fullKey);
|
|
197
|
+
}
|
|
198
|
+
return { success: true, key: fullKey };
|
|
199
|
+
}
|
|
200
|
+
async deleteMany(keys) {
|
|
201
|
+
const deleted = [];
|
|
202
|
+
const errors = [];
|
|
203
|
+
for (const key of keys) {
|
|
204
|
+
try {
|
|
205
|
+
await this.delete(key);
|
|
206
|
+
deleted.push(this.getFullKey(key));
|
|
207
|
+
} catch (err) {
|
|
208
|
+
errors.push({
|
|
209
|
+
key: this.getFullKey(key),
|
|
210
|
+
error: err instanceof Error ? err.message : "Unknown error"
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return { deleted, errors };
|
|
215
|
+
}
|
|
216
|
+
async list(options) {
|
|
217
|
+
const prefix = options?.prefix ? this.getFullKey(options.prefix) : this.basePath;
|
|
218
|
+
const delimiter = options?.delimiter ?? "/";
|
|
219
|
+
const maxKeys = options?.maxKeys ?? 1e3;
|
|
220
|
+
const files = [];
|
|
221
|
+
const prefixSet = /* @__PURE__ */ new Set();
|
|
222
|
+
for (const [key, file] of this.files) {
|
|
223
|
+
if (prefix && !key.startsWith(prefix)) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const relativePath = prefix ? key.slice(prefix.length) : key;
|
|
227
|
+
if (delimiter) {
|
|
228
|
+
const delimiterIndex = relativePath.indexOf(delimiter);
|
|
229
|
+
if (delimiterIndex !== -1) {
|
|
230
|
+
const commonPrefix = key.slice(0, prefix.length + delimiterIndex + 1);
|
|
231
|
+
prefixSet.add(commonPrefix);
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
files.push(file.metadata);
|
|
236
|
+
if (files.length >= maxKeys) {
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
files,
|
|
242
|
+
prefixes: Array.from(prefixSet),
|
|
243
|
+
isTruncated: files.length >= maxKeys,
|
|
244
|
+
continuationToken: void 0
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
async copy(sourceKey, destKey, options) {
|
|
248
|
+
this.validateKey(sourceKey);
|
|
249
|
+
this.validateKey(destKey);
|
|
250
|
+
const sourceFullKey = this.getFullKey(sourceKey);
|
|
251
|
+
const destFullKey = this.getFullKey(destKey);
|
|
252
|
+
const sourceFile = this.files.get(sourceFullKey);
|
|
253
|
+
if (!sourceFile) {
|
|
254
|
+
throw new StorageError(
|
|
255
|
+
`Source file not found: ${sourceKey}`,
|
|
256
|
+
StorageErrorCodes.NOT_FOUND,
|
|
257
|
+
404
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
const newMetadata = options?.metadataDirective === "REPLACE" ? {
|
|
261
|
+
key: destFullKey,
|
|
262
|
+
size: sourceFile.data.length,
|
|
263
|
+
contentType: options.contentType ?? sourceFile.metadata.contentType,
|
|
264
|
+
lastModified: /* @__PURE__ */ new Date(),
|
|
265
|
+
etag: sourceFile.metadata.etag,
|
|
266
|
+
metadata: options.metadata ?? void 0
|
|
267
|
+
} : {
|
|
268
|
+
...sourceFile.metadata,
|
|
269
|
+
key: destFullKey,
|
|
270
|
+
lastModified: /* @__PURE__ */ new Date()
|
|
271
|
+
};
|
|
272
|
+
this.files.set(destFullKey, {
|
|
273
|
+
data: sourceFile.data,
|
|
274
|
+
metadata: newMetadata
|
|
275
|
+
});
|
|
276
|
+
this.currentSize += sourceFile.data.length;
|
|
277
|
+
return newMetadata;
|
|
278
|
+
}
|
|
279
|
+
async move(sourceKey, destKey) {
|
|
280
|
+
const metadata = await this.copy(sourceKey, destKey);
|
|
281
|
+
await this.delete(sourceKey);
|
|
282
|
+
return metadata;
|
|
283
|
+
}
|
|
284
|
+
async getPresignedUrl(key, options) {
|
|
285
|
+
this.validateKey(key);
|
|
286
|
+
const fullKey = this.getFullKey(key);
|
|
287
|
+
const expiresIn = options?.expiresIn ?? 3600;
|
|
288
|
+
const expires = Date.now() + expiresIn * 1e3;
|
|
289
|
+
return `memory://${this.bucket}/${fullKey}?expires=${expires}`;
|
|
290
|
+
}
|
|
291
|
+
async getUploadUrl(key, options) {
|
|
292
|
+
return this.getPresignedUrl(key, options);
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Clear all files (useful for testing)
|
|
296
|
+
*/
|
|
297
|
+
clear() {
|
|
298
|
+
this.files.clear();
|
|
299
|
+
this.currentSize = 0;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Get current storage size
|
|
303
|
+
*/
|
|
304
|
+
getSize() {
|
|
305
|
+
return this.currentSize;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Get file count
|
|
309
|
+
*/
|
|
310
|
+
getFileCount() {
|
|
311
|
+
return this.files.size;
|
|
312
|
+
}
|
|
313
|
+
guessContentType(key) {
|
|
314
|
+
const ext = key.split(".").pop()?.toLowerCase();
|
|
315
|
+
const contentTypes = {
|
|
316
|
+
txt: "text/plain",
|
|
317
|
+
html: "text/html",
|
|
318
|
+
css: "text/css",
|
|
319
|
+
js: "application/javascript",
|
|
320
|
+
json: "application/json",
|
|
321
|
+
xml: "application/xml",
|
|
322
|
+
pdf: "application/pdf",
|
|
323
|
+
zip: "application/zip",
|
|
324
|
+
png: "image/png",
|
|
325
|
+
jpg: "image/jpeg",
|
|
326
|
+
jpeg: "image/jpeg",
|
|
327
|
+
gif: "image/gif",
|
|
328
|
+
webp: "image/webp",
|
|
329
|
+
svg: "image/svg+xml",
|
|
330
|
+
mp3: "audio/mpeg",
|
|
331
|
+
mp4: "video/mp4",
|
|
332
|
+
webm: "video/webm"
|
|
333
|
+
};
|
|
334
|
+
return contentTypes[ext ?? ""] ?? "application/octet-stream";
|
|
335
|
+
}
|
|
336
|
+
generateEtag(data) {
|
|
337
|
+
let hash = 0;
|
|
338
|
+
for (const byte of data) {
|
|
339
|
+
hash = (hash << 5) - hash + byte | 0;
|
|
340
|
+
}
|
|
341
|
+
return `"${Math.abs(hash).toString(16)}"`;
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// src/adapters/s3.ts
|
|
348
|
+
var s3_exports = {};
|
|
349
|
+
__export(s3_exports, {
|
|
350
|
+
S3Adapter: () => S3Adapter,
|
|
351
|
+
createDOSpacesAdapter: () => createDOSpacesAdapter,
|
|
352
|
+
createS3Adapter: () => createS3Adapter
|
|
353
|
+
});
|
|
354
|
+
function createS3Adapter(config) {
|
|
355
|
+
return new S3Adapter(config);
|
|
356
|
+
}
|
|
357
|
+
function createDOSpacesAdapter(config) {
|
|
358
|
+
return new S3Adapter({
|
|
359
|
+
...config,
|
|
360
|
+
type: "do-spaces",
|
|
361
|
+
endpoint: `https://${config.region}.digitaloceanspaces.com`,
|
|
362
|
+
bucket: config.spaceName ?? config.bucket
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
var S3Adapter;
|
|
366
|
+
var init_s3 = __esm({
|
|
367
|
+
"src/adapters/s3.ts"() {
|
|
368
|
+
"use strict";
|
|
369
|
+
init_types();
|
|
370
|
+
S3Adapter = class {
|
|
371
|
+
type;
|
|
372
|
+
bucket;
|
|
373
|
+
client = null;
|
|
374
|
+
config;
|
|
375
|
+
basePath;
|
|
376
|
+
constructor(config) {
|
|
377
|
+
this.type = config.type;
|
|
378
|
+
this.bucket = config.bucket;
|
|
379
|
+
this.config = config;
|
|
380
|
+
this.basePath = config.basePath ?? "";
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Lazy load S3 client
|
|
384
|
+
*/
|
|
385
|
+
async getClient() {
|
|
386
|
+
if (this.client) return this.client;
|
|
387
|
+
try {
|
|
388
|
+
const { S3Client } = await import("@aws-sdk/client-s3");
|
|
389
|
+
const clientConfig = {
|
|
390
|
+
region: this.config.region,
|
|
391
|
+
credentials: {
|
|
392
|
+
accessKeyId: this.config.accessKeyId,
|
|
393
|
+
secretAccessKey: this.config.secretAccessKey
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
if (this.config.endpoint) {
|
|
397
|
+
clientConfig.endpoint = this.config.endpoint;
|
|
398
|
+
}
|
|
399
|
+
if (this.config.forcePathStyle !== void 0) {
|
|
400
|
+
clientConfig.forcePathStyle = this.config.forcePathStyle;
|
|
401
|
+
}
|
|
402
|
+
this.client = new S3Client(clientConfig);
|
|
403
|
+
return this.client;
|
|
404
|
+
} catch {
|
|
405
|
+
throw new StorageError(
|
|
406
|
+
"AWS SDK not installed. Run: npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner",
|
|
407
|
+
StorageErrorCodes.ADAPTER_NOT_AVAILABLE
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
getFullKey(key) {
|
|
412
|
+
return this.basePath ? `${this.basePath}/${key}` : key;
|
|
413
|
+
}
|
|
414
|
+
validateKey(key) {
|
|
415
|
+
if (!key || key.includes("..") || key.startsWith("/")) {
|
|
416
|
+
throw new StorageError(
|
|
417
|
+
`Invalid key: ${key}`,
|
|
418
|
+
StorageErrorCodes.INVALID_KEY
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
async dataToBody(data) {
|
|
423
|
+
if (data instanceof Uint8Array || typeof data === "string") {
|
|
424
|
+
return data;
|
|
425
|
+
}
|
|
426
|
+
if (data instanceof Blob) {
|
|
427
|
+
const buffer = await data.arrayBuffer();
|
|
428
|
+
return new Uint8Array(buffer);
|
|
429
|
+
}
|
|
430
|
+
return data;
|
|
431
|
+
}
|
|
432
|
+
async upload(key, data, options) {
|
|
433
|
+
this.validateKey(key);
|
|
434
|
+
const fullKey = this.getFullKey(key);
|
|
435
|
+
const client = await this.getClient();
|
|
436
|
+
const { PutObjectCommand } = await import("@aws-sdk/client-s3");
|
|
437
|
+
const body = await this.dataToBody(data);
|
|
438
|
+
const params = {
|
|
439
|
+
Bucket: this.bucket,
|
|
440
|
+
Key: fullKey,
|
|
441
|
+
Body: body,
|
|
442
|
+
ContentType: options?.contentType,
|
|
443
|
+
ContentDisposition: options?.contentDisposition,
|
|
444
|
+
CacheControl: options?.cacheControl,
|
|
445
|
+
ContentEncoding: options?.contentEncoding,
|
|
446
|
+
Metadata: options?.metadata
|
|
447
|
+
};
|
|
448
|
+
if (options?.acl) {
|
|
449
|
+
params.ACL = options.acl;
|
|
450
|
+
}
|
|
451
|
+
try {
|
|
452
|
+
const result = await client.send(new PutObjectCommand(params));
|
|
453
|
+
let size = 0;
|
|
454
|
+
if (typeof body === "string") {
|
|
455
|
+
size = new TextEncoder().encode(body).length;
|
|
456
|
+
} else if (body instanceof Uint8Array) {
|
|
457
|
+
size = body.length;
|
|
458
|
+
}
|
|
459
|
+
return {
|
|
460
|
+
key: fullKey,
|
|
461
|
+
size,
|
|
462
|
+
contentType: options?.contentType ?? void 0,
|
|
463
|
+
lastModified: /* @__PURE__ */ new Date(),
|
|
464
|
+
etag: result.ETag ?? void 0,
|
|
465
|
+
metadata: options?.metadata ?? void 0
|
|
466
|
+
};
|
|
467
|
+
} catch (err) {
|
|
468
|
+
throw new StorageError(
|
|
469
|
+
`Upload failed: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
470
|
+
StorageErrorCodes.UPLOAD_FAILED,
|
|
471
|
+
void 0,
|
|
472
|
+
err
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
async download(key, options) {
|
|
477
|
+
this.validateKey(key);
|
|
478
|
+
const fullKey = this.getFullKey(key);
|
|
479
|
+
const client = await this.getClient();
|
|
480
|
+
const { GetObjectCommand } = await import("@aws-sdk/client-s3");
|
|
481
|
+
try {
|
|
482
|
+
const params = {
|
|
483
|
+
Bucket: this.bucket,
|
|
484
|
+
Key: fullKey
|
|
485
|
+
};
|
|
486
|
+
if (options?.rangeStart !== void 0 || options?.rangeEnd !== void 0) {
|
|
487
|
+
const start = options.rangeStart ?? 0;
|
|
488
|
+
const end = options.rangeEnd ?? "";
|
|
489
|
+
params.Range = `bytes=${start}-${end}`;
|
|
490
|
+
}
|
|
491
|
+
if (options?.ifNoneMatch) {
|
|
492
|
+
params.IfNoneMatch = options.ifNoneMatch;
|
|
493
|
+
}
|
|
494
|
+
if (options?.ifModifiedSince) {
|
|
495
|
+
params.IfModifiedSince = options.ifModifiedSince;
|
|
496
|
+
}
|
|
497
|
+
const result = await client.send(new GetObjectCommand(params));
|
|
498
|
+
if (!result.Body) {
|
|
499
|
+
throw new StorageError(
|
|
500
|
+
"Empty response body",
|
|
501
|
+
StorageErrorCodes.DOWNLOAD_FAILED
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
const stream = result.Body;
|
|
505
|
+
return this.streamToUint8Array(stream);
|
|
506
|
+
} catch (err) {
|
|
507
|
+
if (err instanceof Error && "name" in err && err.name === "NoSuchKey") {
|
|
508
|
+
throw new StorageError(
|
|
509
|
+
`File not found: ${key}`,
|
|
510
|
+
StorageErrorCodes.NOT_FOUND,
|
|
511
|
+
404
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
throw new StorageError(
|
|
515
|
+
`Download failed: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
516
|
+
StorageErrorCodes.DOWNLOAD_FAILED,
|
|
517
|
+
void 0,
|
|
518
|
+
err
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
async downloadStream(key, options) {
|
|
523
|
+
this.validateKey(key);
|
|
524
|
+
const fullKey = this.getFullKey(key);
|
|
525
|
+
const client = await this.getClient();
|
|
526
|
+
const { GetObjectCommand } = await import("@aws-sdk/client-s3");
|
|
527
|
+
try {
|
|
528
|
+
const params = {
|
|
529
|
+
Bucket: this.bucket,
|
|
530
|
+
Key: fullKey
|
|
531
|
+
};
|
|
532
|
+
if (options?.rangeStart !== void 0 || options?.rangeEnd !== void 0) {
|
|
533
|
+
const start = options.rangeStart ?? 0;
|
|
534
|
+
const end = options.rangeEnd ?? "";
|
|
535
|
+
params.Range = `bytes=${start}-${end}`;
|
|
536
|
+
}
|
|
537
|
+
const result = await client.send(new GetObjectCommand(params));
|
|
538
|
+
if (!result.Body) {
|
|
539
|
+
throw new StorageError(
|
|
540
|
+
"Empty response body",
|
|
541
|
+
StorageErrorCodes.DOWNLOAD_FAILED
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
return result.Body;
|
|
545
|
+
} catch (err) {
|
|
546
|
+
if (err instanceof Error && "name" in err && err.name === "NoSuchKey") {
|
|
547
|
+
throw new StorageError(
|
|
548
|
+
`File not found: ${key}`,
|
|
549
|
+
StorageErrorCodes.NOT_FOUND,
|
|
550
|
+
404
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
throw err;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
async head(key) {
|
|
557
|
+
this.validateKey(key);
|
|
558
|
+
const fullKey = this.getFullKey(key);
|
|
559
|
+
const client = await this.getClient();
|
|
560
|
+
const { HeadObjectCommand } = await import("@aws-sdk/client-s3");
|
|
561
|
+
try {
|
|
562
|
+
const result = await client.send(
|
|
563
|
+
new HeadObjectCommand({
|
|
564
|
+
Bucket: this.bucket,
|
|
565
|
+
Key: fullKey
|
|
566
|
+
})
|
|
567
|
+
);
|
|
568
|
+
return {
|
|
569
|
+
key: fullKey,
|
|
570
|
+
size: result.ContentLength ?? 0,
|
|
571
|
+
contentType: result.ContentType ?? void 0,
|
|
572
|
+
lastModified: result.LastModified ?? void 0,
|
|
573
|
+
etag: result.ETag ?? void 0,
|
|
574
|
+
metadata: result.Metadata ?? void 0
|
|
575
|
+
};
|
|
576
|
+
} catch (err) {
|
|
577
|
+
if (err instanceof Error && "name" in err && (err.name === "NoSuchKey" || err.name === "NotFound")) {
|
|
578
|
+
return null;
|
|
579
|
+
}
|
|
580
|
+
throw err;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
async exists(key) {
|
|
584
|
+
const metadata = await this.head(key);
|
|
585
|
+
return metadata !== null;
|
|
586
|
+
}
|
|
587
|
+
async delete(key) {
|
|
588
|
+
this.validateKey(key);
|
|
589
|
+
const fullKey = this.getFullKey(key);
|
|
590
|
+
const client = await this.getClient();
|
|
591
|
+
const { DeleteObjectCommand } = await import("@aws-sdk/client-s3");
|
|
592
|
+
try {
|
|
593
|
+
await client.send(
|
|
594
|
+
new DeleteObjectCommand({
|
|
595
|
+
Bucket: this.bucket,
|
|
596
|
+
Key: fullKey
|
|
597
|
+
})
|
|
598
|
+
);
|
|
599
|
+
return { success: true, key: fullKey };
|
|
600
|
+
} catch (err) {
|
|
601
|
+
throw new StorageError(
|
|
602
|
+
`Delete failed: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
603
|
+
StorageErrorCodes.DELETE_FAILED,
|
|
604
|
+
void 0,
|
|
605
|
+
err
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
async deleteMany(keys) {
|
|
610
|
+
const client = await this.getClient();
|
|
611
|
+
const { DeleteObjectsCommand } = await import("@aws-sdk/client-s3");
|
|
612
|
+
const objects = keys.map((key) => ({
|
|
613
|
+
Key: this.getFullKey(key)
|
|
614
|
+
}));
|
|
615
|
+
try {
|
|
616
|
+
const result = await client.send(
|
|
617
|
+
new DeleteObjectsCommand({
|
|
618
|
+
Bucket: this.bucket,
|
|
619
|
+
Delete: { Objects: objects }
|
|
620
|
+
})
|
|
621
|
+
);
|
|
622
|
+
const deleted = result.Deleted?.map((d) => d.Key ?? "") ?? [];
|
|
623
|
+
const errors = result.Errors?.map((e) => ({
|
|
624
|
+
key: e.Key ?? "",
|
|
625
|
+
error: e.Message ?? "Unknown error"
|
|
626
|
+
})) ?? [];
|
|
627
|
+
return { deleted, errors };
|
|
628
|
+
} catch (err) {
|
|
629
|
+
throw new StorageError(
|
|
630
|
+
`Batch delete failed: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
631
|
+
StorageErrorCodes.DELETE_FAILED,
|
|
632
|
+
void 0,
|
|
633
|
+
err
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
async list(options) {
|
|
638
|
+
const client = await this.getClient();
|
|
639
|
+
const { ListObjectsV2Command } = await import("@aws-sdk/client-s3");
|
|
640
|
+
const prefix = options?.prefix ? this.getFullKey(options.prefix) : this.basePath || void 0;
|
|
641
|
+
try {
|
|
642
|
+
const result = await client.send(
|
|
643
|
+
new ListObjectsV2Command({
|
|
644
|
+
Bucket: this.bucket,
|
|
645
|
+
Prefix: prefix,
|
|
646
|
+
Delimiter: options?.delimiter,
|
|
647
|
+
MaxKeys: options?.maxKeys,
|
|
648
|
+
ContinuationToken: options?.continuationToken
|
|
649
|
+
})
|
|
650
|
+
);
|
|
651
|
+
const files = result.Contents?.map((item) => ({
|
|
652
|
+
key: item.Key ?? "",
|
|
653
|
+
size: item.Size ?? 0,
|
|
654
|
+
contentType: void 0,
|
|
655
|
+
lastModified: item.LastModified ?? void 0,
|
|
656
|
+
etag: item.ETag ?? void 0,
|
|
657
|
+
metadata: void 0
|
|
658
|
+
})) ?? [];
|
|
659
|
+
const prefixes = result.CommonPrefixes?.map((p) => p.Prefix ?? "") ?? [];
|
|
660
|
+
return {
|
|
661
|
+
files,
|
|
662
|
+
prefixes,
|
|
663
|
+
isTruncated: result.IsTruncated ?? false,
|
|
664
|
+
continuationToken: result.NextContinuationToken ?? void 0
|
|
665
|
+
};
|
|
666
|
+
} catch (err) {
|
|
667
|
+
throw new StorageError(
|
|
668
|
+
`List failed: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
669
|
+
StorageErrorCodes.LIST_FAILED,
|
|
670
|
+
void 0,
|
|
671
|
+
err
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
async copy(sourceKey, destKey, options) {
|
|
676
|
+
this.validateKey(sourceKey);
|
|
677
|
+
this.validateKey(destKey);
|
|
678
|
+
const client = await this.getClient();
|
|
679
|
+
const { CopyObjectCommand } = await import("@aws-sdk/client-s3");
|
|
680
|
+
const sourceFullKey = this.getFullKey(sourceKey);
|
|
681
|
+
const destFullKey = this.getFullKey(destKey);
|
|
682
|
+
const sourceBucket = options?.sourceBucket ?? this.bucket;
|
|
683
|
+
try {
|
|
684
|
+
const result = await client.send(
|
|
685
|
+
new CopyObjectCommand({
|
|
686
|
+
Bucket: this.bucket,
|
|
687
|
+
Key: destFullKey,
|
|
688
|
+
CopySource: `${sourceBucket}/${sourceFullKey}`,
|
|
689
|
+
MetadataDirective: options?.metadataDirective,
|
|
690
|
+
ContentType: options?.contentType,
|
|
691
|
+
Metadata: options?.metadata
|
|
692
|
+
})
|
|
693
|
+
);
|
|
694
|
+
const metadata = await this.head(destKey);
|
|
695
|
+
return metadata ?? {
|
|
696
|
+
key: destFullKey,
|
|
697
|
+
size: 0,
|
|
698
|
+
contentType: options?.contentType ?? void 0,
|
|
699
|
+
lastModified: result.CopyObjectResult?.LastModified ?? /* @__PURE__ */ new Date(),
|
|
700
|
+
etag: result.CopyObjectResult?.ETag ?? void 0,
|
|
701
|
+
metadata: options?.metadata ?? void 0
|
|
702
|
+
};
|
|
703
|
+
} catch (err) {
|
|
704
|
+
throw new StorageError(
|
|
705
|
+
`Copy failed: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
706
|
+
StorageErrorCodes.COPY_FAILED,
|
|
707
|
+
void 0,
|
|
708
|
+
err
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
async move(sourceKey, destKey) {
|
|
713
|
+
const metadata = await this.copy(sourceKey, destKey);
|
|
714
|
+
await this.delete(sourceKey);
|
|
715
|
+
return metadata;
|
|
716
|
+
}
|
|
717
|
+
async getPresignedUrl(key, options) {
|
|
718
|
+
this.validateKey(key);
|
|
719
|
+
const fullKey = this.getFullKey(key);
|
|
720
|
+
const client = await this.getClient();
|
|
721
|
+
try {
|
|
722
|
+
const { getSignedUrl } = await import("@aws-sdk/s3-request-presigner");
|
|
723
|
+
const { GetObjectCommand } = await import("@aws-sdk/client-s3");
|
|
724
|
+
const command = new GetObjectCommand({
|
|
725
|
+
Bucket: this.bucket,
|
|
726
|
+
Key: fullKey,
|
|
727
|
+
ResponseCacheControl: options?.responseCacheControl,
|
|
728
|
+
ResponseContentType: options?.responseContentType,
|
|
729
|
+
ResponseContentDisposition: options?.contentDisposition
|
|
730
|
+
});
|
|
731
|
+
return getSignedUrl(client, command, {
|
|
732
|
+
expiresIn: options?.expiresIn ?? 3600
|
|
733
|
+
});
|
|
734
|
+
} catch (err) {
|
|
735
|
+
throw new StorageError(
|
|
736
|
+
`Presigned URL failed: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
737
|
+
StorageErrorCodes.PRESIGN_FAILED,
|
|
738
|
+
void 0,
|
|
739
|
+
err
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
async getUploadUrl(key, options) {
|
|
744
|
+
this.validateKey(key);
|
|
745
|
+
const fullKey = this.getFullKey(key);
|
|
746
|
+
const client = await this.getClient();
|
|
747
|
+
try {
|
|
748
|
+
const { getSignedUrl } = await import("@aws-sdk/s3-request-presigner");
|
|
749
|
+
const { PutObjectCommand } = await import("@aws-sdk/client-s3");
|
|
750
|
+
const command = new PutObjectCommand({
|
|
751
|
+
Bucket: this.bucket,
|
|
752
|
+
Key: fullKey,
|
|
753
|
+
ContentType: options?.contentType,
|
|
754
|
+
ContentDisposition: options?.contentDisposition
|
|
755
|
+
});
|
|
756
|
+
return getSignedUrl(client, command, {
|
|
757
|
+
expiresIn: options?.expiresIn ?? 3600
|
|
758
|
+
});
|
|
759
|
+
} catch (err) {
|
|
760
|
+
throw new StorageError(
|
|
761
|
+
`Upload URL failed: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
762
|
+
StorageErrorCodes.PRESIGN_FAILED,
|
|
763
|
+
void 0,
|
|
764
|
+
err
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
async streamToUint8Array(stream) {
|
|
769
|
+
const reader = stream.getReader();
|
|
770
|
+
const chunks = [];
|
|
771
|
+
while (true) {
|
|
772
|
+
const { done, value } = await reader.read();
|
|
773
|
+
if (done) break;
|
|
774
|
+
if (value) chunks.push(value);
|
|
775
|
+
}
|
|
776
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
777
|
+
const result = new Uint8Array(totalLength);
|
|
778
|
+
let offset = 0;
|
|
779
|
+
for (const chunk of chunks) {
|
|
780
|
+
result.set(chunk, offset);
|
|
781
|
+
offset += chunk.length;
|
|
782
|
+
}
|
|
783
|
+
return result;
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
// src/adapters/r2.ts
|
|
790
|
+
var r2_exports = {};
|
|
791
|
+
__export(r2_exports, {
|
|
792
|
+
R2Adapter: () => R2Adapter,
|
|
793
|
+
createR2Adapter: () => createR2Adapter
|
|
794
|
+
});
|
|
795
|
+
function createR2Adapter(config) {
|
|
796
|
+
return new R2Adapter(config);
|
|
797
|
+
}
|
|
798
|
+
var R2Adapter;
|
|
799
|
+
var init_r2 = __esm({
|
|
800
|
+
"src/adapters/r2.ts"() {
|
|
801
|
+
"use strict";
|
|
802
|
+
init_types();
|
|
803
|
+
R2Adapter = class {
|
|
804
|
+
type = "r2";
|
|
805
|
+
bucket;
|
|
806
|
+
binding = null;
|
|
807
|
+
s3Adapter = null;
|
|
808
|
+
config;
|
|
809
|
+
basePath;
|
|
810
|
+
constructor(config) {
|
|
811
|
+
this.bucket = config.bucket;
|
|
812
|
+
this.config = config;
|
|
813
|
+
this.basePath = config.basePath ?? "";
|
|
814
|
+
this.binding = config.binding ?? null;
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Get S3 adapter for fallback
|
|
818
|
+
*/
|
|
819
|
+
async getS3Adapter() {
|
|
820
|
+
if (this.s3Adapter) return this.s3Adapter;
|
|
821
|
+
const { S3Adapter: S3Adapter2 } = await Promise.resolve().then(() => (init_s3(), s3_exports));
|
|
822
|
+
this.s3Adapter = new S3Adapter2({
|
|
823
|
+
type: "s3",
|
|
824
|
+
bucket: this.bucket,
|
|
825
|
+
region: "auto",
|
|
826
|
+
accessKeyId: this.config.accessKeyId,
|
|
827
|
+
secretAccessKey: this.config.secretAccessKey,
|
|
828
|
+
endpoint: `https://${this.config.accountId}.r2.cloudflarestorage.com`
|
|
829
|
+
});
|
|
830
|
+
return this.s3Adapter;
|
|
831
|
+
}
|
|
832
|
+
getFullKey(key) {
|
|
833
|
+
return this.basePath ? `${this.basePath}/${key}` : key;
|
|
834
|
+
}
|
|
835
|
+
validateKey(key) {
|
|
836
|
+
if (!key || key.includes("..") || key.startsWith("/")) {
|
|
837
|
+
throw new StorageError(
|
|
838
|
+
`Invalid key: ${key}`,
|
|
839
|
+
StorageErrorCodes.INVALID_KEY
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
async dataToBody(data) {
|
|
844
|
+
if (data instanceof Uint8Array) {
|
|
845
|
+
const buffer = new ArrayBuffer(data.length);
|
|
846
|
+
new Uint8Array(buffer).set(data);
|
|
847
|
+
return buffer;
|
|
848
|
+
}
|
|
849
|
+
return data;
|
|
850
|
+
}
|
|
851
|
+
async upload(key, data, options) {
|
|
852
|
+
this.validateKey(key);
|
|
853
|
+
const fullKey = this.getFullKey(key);
|
|
854
|
+
if (this.binding) {
|
|
855
|
+
const body = await this.dataToBody(data);
|
|
856
|
+
const httpMetadata = {};
|
|
857
|
+
if (options?.contentType) httpMetadata.contentType = options.contentType;
|
|
858
|
+
if (options?.contentDisposition) httpMetadata.contentDisposition = options.contentDisposition;
|
|
859
|
+
if (options?.cacheControl) httpMetadata.cacheControl = options.cacheControl;
|
|
860
|
+
if (options?.contentEncoding) httpMetadata.contentEncoding = options.contentEncoding;
|
|
861
|
+
const putOptions = {
|
|
862
|
+
httpMetadata
|
|
863
|
+
};
|
|
864
|
+
if (options?.metadata) {
|
|
865
|
+
putOptions.customMetadata = options.metadata;
|
|
866
|
+
}
|
|
867
|
+
try {
|
|
868
|
+
const result = await this.binding.put(fullKey, body, putOptions);
|
|
869
|
+
return {
|
|
870
|
+
key: fullKey,
|
|
871
|
+
size: result.size,
|
|
872
|
+
contentType: result.httpMetadata?.contentType ?? void 0,
|
|
873
|
+
lastModified: result.uploaded,
|
|
874
|
+
etag: result.etag,
|
|
875
|
+
metadata: result.customMetadata ?? void 0
|
|
876
|
+
};
|
|
877
|
+
} catch (err) {
|
|
878
|
+
throw new StorageError(
|
|
879
|
+
`Upload failed: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
880
|
+
StorageErrorCodes.UPLOAD_FAILED,
|
|
881
|
+
void 0,
|
|
882
|
+
err
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
const s3 = await this.getS3Adapter();
|
|
887
|
+
return s3.upload(key, data, options);
|
|
888
|
+
}
|
|
889
|
+
async download(key, options) {
|
|
890
|
+
this.validateKey(key);
|
|
891
|
+
const fullKey = this.getFullKey(key);
|
|
892
|
+
if (this.binding) {
|
|
893
|
+
try {
|
|
894
|
+
const getOptions = {};
|
|
895
|
+
if (options?.rangeStart !== void 0 || options?.rangeEnd !== void 0) {
|
|
896
|
+
const rangeOpts = {
|
|
897
|
+
offset: options.rangeStart ?? 0
|
|
898
|
+
};
|
|
899
|
+
if (options.rangeEnd !== void 0) {
|
|
900
|
+
rangeOpts.length = options.rangeEnd - (options.rangeStart ?? 0) + 1;
|
|
901
|
+
}
|
|
902
|
+
getOptions.range = rangeOpts;
|
|
903
|
+
}
|
|
904
|
+
if (options?.ifNoneMatch || options?.ifModifiedSince) {
|
|
905
|
+
const conditional = {};
|
|
906
|
+
if (options.ifNoneMatch) conditional.etagDoesNotMatch = options.ifNoneMatch;
|
|
907
|
+
if (options.ifModifiedSince) conditional.uploadedAfter = options.ifModifiedSince;
|
|
908
|
+
getOptions.onlyIf = conditional;
|
|
909
|
+
}
|
|
910
|
+
const result = await this.binding.get(fullKey, getOptions);
|
|
911
|
+
if (!result) {
|
|
912
|
+
throw new StorageError(
|
|
913
|
+
`File not found: ${key}`,
|
|
914
|
+
StorageErrorCodes.NOT_FOUND,
|
|
915
|
+
404
|
|
916
|
+
);
|
|
917
|
+
}
|
|
918
|
+
const buffer = await result.arrayBuffer();
|
|
919
|
+
return new Uint8Array(buffer);
|
|
920
|
+
} catch (err) {
|
|
921
|
+
if (err instanceof StorageError) throw err;
|
|
922
|
+
throw new StorageError(
|
|
923
|
+
`Download failed: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
924
|
+
StorageErrorCodes.DOWNLOAD_FAILED,
|
|
925
|
+
void 0,
|
|
926
|
+
err
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
const s3 = await this.getS3Adapter();
|
|
931
|
+
return s3.download(key, options);
|
|
932
|
+
}
|
|
933
|
+
async downloadStream(key, options) {
|
|
934
|
+
this.validateKey(key);
|
|
935
|
+
const fullKey = this.getFullKey(key);
|
|
936
|
+
if (this.binding) {
|
|
937
|
+
try {
|
|
938
|
+
const getOptions = {};
|
|
939
|
+
if (options?.rangeStart !== void 0 || options?.rangeEnd !== void 0) {
|
|
940
|
+
const rangeOpts = {
|
|
941
|
+
offset: options.rangeStart ?? 0
|
|
942
|
+
};
|
|
943
|
+
if (options.rangeEnd !== void 0) {
|
|
944
|
+
rangeOpts.length = options.rangeEnd - (options.rangeStart ?? 0) + 1;
|
|
945
|
+
}
|
|
946
|
+
getOptions.range = rangeOpts;
|
|
947
|
+
}
|
|
948
|
+
const result = await this.binding.get(fullKey, getOptions);
|
|
949
|
+
if (!result) {
|
|
950
|
+
throw new StorageError(
|
|
951
|
+
`File not found: ${key}`,
|
|
952
|
+
StorageErrorCodes.NOT_FOUND,
|
|
953
|
+
404
|
|
954
|
+
);
|
|
955
|
+
}
|
|
956
|
+
return result.body;
|
|
957
|
+
} catch (err) {
|
|
958
|
+
if (err instanceof StorageError) throw err;
|
|
959
|
+
throw new StorageError(
|
|
960
|
+
`Download failed: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
961
|
+
StorageErrorCodes.DOWNLOAD_FAILED,
|
|
962
|
+
void 0,
|
|
963
|
+
err
|
|
964
|
+
);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
const s3 = await this.getS3Adapter();
|
|
968
|
+
return s3.downloadStream(key, options);
|
|
969
|
+
}
|
|
970
|
+
async head(key) {
|
|
971
|
+
this.validateKey(key);
|
|
972
|
+
const fullKey = this.getFullKey(key);
|
|
973
|
+
if (this.binding) {
|
|
974
|
+
try {
|
|
975
|
+
const result = await this.binding.head(fullKey);
|
|
976
|
+
if (!result) {
|
|
977
|
+
return null;
|
|
978
|
+
}
|
|
979
|
+
return {
|
|
980
|
+
key: fullKey,
|
|
981
|
+
size: result.size,
|
|
982
|
+
contentType: result.httpMetadata?.contentType ?? void 0,
|
|
983
|
+
lastModified: result.uploaded,
|
|
984
|
+
etag: result.etag,
|
|
985
|
+
metadata: result.customMetadata ?? void 0
|
|
986
|
+
};
|
|
987
|
+
} catch {
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
const s3 = await this.getS3Adapter();
|
|
992
|
+
return s3.head(key);
|
|
993
|
+
}
|
|
994
|
+
async exists(key) {
|
|
995
|
+
const metadata = await this.head(key);
|
|
996
|
+
return metadata !== null;
|
|
997
|
+
}
|
|
998
|
+
async delete(key) {
|
|
999
|
+
this.validateKey(key);
|
|
1000
|
+
const fullKey = this.getFullKey(key);
|
|
1001
|
+
if (this.binding) {
|
|
1002
|
+
try {
|
|
1003
|
+
await this.binding.delete(fullKey);
|
|
1004
|
+
return { success: true, key: fullKey };
|
|
1005
|
+
} catch (err) {
|
|
1006
|
+
throw new StorageError(
|
|
1007
|
+
`Delete failed: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
1008
|
+
StorageErrorCodes.DELETE_FAILED,
|
|
1009
|
+
void 0,
|
|
1010
|
+
err
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
const s3 = await this.getS3Adapter();
|
|
1015
|
+
return s3.delete(key);
|
|
1016
|
+
}
|
|
1017
|
+
async deleteMany(keys) {
|
|
1018
|
+
if (this.binding) {
|
|
1019
|
+
const fullKeys = keys.map((key) => this.getFullKey(key));
|
|
1020
|
+
try {
|
|
1021
|
+
await this.binding.delete(fullKeys);
|
|
1022
|
+
return { deleted: fullKeys, errors: [] };
|
|
1023
|
+
} catch (err) {
|
|
1024
|
+
return {
|
|
1025
|
+
deleted: [],
|
|
1026
|
+
errors: fullKeys.map((key) => ({
|
|
1027
|
+
key,
|
|
1028
|
+
error: err instanceof Error ? err.message : "Unknown error"
|
|
1029
|
+
}))
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
const s3 = await this.getS3Adapter();
|
|
1034
|
+
return s3.deleteMany(keys);
|
|
1035
|
+
}
|
|
1036
|
+
async list(options) {
|
|
1037
|
+
if (this.binding) {
|
|
1038
|
+
try {
|
|
1039
|
+
const listOpts = {
|
|
1040
|
+
include: ["httpMetadata", "customMetadata"]
|
|
1041
|
+
};
|
|
1042
|
+
const prefix = options?.prefix ? this.getFullKey(options.prefix) : this.basePath || null;
|
|
1043
|
+
if (prefix) listOpts.prefix = prefix;
|
|
1044
|
+
if (options?.delimiter) listOpts.delimiter = options.delimiter;
|
|
1045
|
+
if (options?.maxKeys) listOpts.limit = options.maxKeys;
|
|
1046
|
+
if (options?.continuationToken) listOpts.cursor = options.continuationToken;
|
|
1047
|
+
const result = await this.binding.list(listOpts);
|
|
1048
|
+
const files = result.objects.map((obj) => ({
|
|
1049
|
+
key: obj.key,
|
|
1050
|
+
size: obj.size,
|
|
1051
|
+
contentType: obj.httpMetadata?.contentType ?? void 0,
|
|
1052
|
+
lastModified: obj.uploaded,
|
|
1053
|
+
etag: obj.etag,
|
|
1054
|
+
metadata: obj.customMetadata ?? void 0
|
|
1055
|
+
}));
|
|
1056
|
+
return {
|
|
1057
|
+
files,
|
|
1058
|
+
prefixes: result.delimitedPrefixes,
|
|
1059
|
+
isTruncated: result.truncated,
|
|
1060
|
+
continuationToken: result.cursor ?? void 0
|
|
1061
|
+
};
|
|
1062
|
+
} catch (err) {
|
|
1063
|
+
throw new StorageError(
|
|
1064
|
+
`List failed: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
1065
|
+
StorageErrorCodes.LIST_FAILED,
|
|
1066
|
+
void 0,
|
|
1067
|
+
err
|
|
1068
|
+
);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
const s3 = await this.getS3Adapter();
|
|
1072
|
+
return s3.list(options);
|
|
1073
|
+
}
|
|
1074
|
+
async copy(sourceKey, destKey, options) {
|
|
1075
|
+
if (this.binding) {
|
|
1076
|
+
const data = await this.download(sourceKey);
|
|
1077
|
+
const sourceMetadata = await this.head(sourceKey);
|
|
1078
|
+
const uploadOptions2 = options?.metadataDirective === "REPLACE" ? {
|
|
1079
|
+
contentType: options.contentType,
|
|
1080
|
+
metadata: options.metadata
|
|
1081
|
+
} : {
|
|
1082
|
+
contentType: sourceMetadata?.contentType,
|
|
1083
|
+
metadata: sourceMetadata?.metadata
|
|
1084
|
+
};
|
|
1085
|
+
return this.upload(destKey, data, uploadOptions2);
|
|
1086
|
+
}
|
|
1087
|
+
const s3 = await this.getS3Adapter();
|
|
1088
|
+
return s3.copy(sourceKey, destKey, options);
|
|
1089
|
+
}
|
|
1090
|
+
async move(sourceKey, destKey) {
|
|
1091
|
+
const metadata = await this.copy(sourceKey, destKey);
|
|
1092
|
+
await this.delete(sourceKey);
|
|
1093
|
+
return metadata;
|
|
1094
|
+
}
|
|
1095
|
+
async getPresignedUrl(key, options) {
|
|
1096
|
+
const s3 = await this.getS3Adapter();
|
|
1097
|
+
return s3.getPresignedUrl(key, options);
|
|
1098
|
+
}
|
|
1099
|
+
async getUploadUrl(key, options) {
|
|
1100
|
+
const s3 = await this.getS3Adapter();
|
|
1101
|
+
return s3.getUploadUrl(key, options);
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Get public URL for a file (if custom domain is configured)
|
|
1105
|
+
*/
|
|
1106
|
+
getPublicUrl(key) {
|
|
1107
|
+
if (!this.config.customDomain) {
|
|
1108
|
+
return null;
|
|
1109
|
+
}
|
|
1110
|
+
const fullKey = this.getFullKey(key);
|
|
1111
|
+
return `https://${this.config.customDomain}/${fullKey}`;
|
|
1112
|
+
}
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
// src/index.ts
|
|
1118
|
+
init_types();
|
|
1119
|
+
init_memory();
|
|
1120
|
+
init_s3();
|
|
1121
|
+
init_r2();
|
|
1122
|
+
init_types();
|
|
1123
|
+
async function createStorage(config) {
|
|
1124
|
+
switch (config.type) {
|
|
1125
|
+
case "memory": {
|
|
1126
|
+
const { MemoryAdapter: MemoryAdapter2 } = await Promise.resolve().then(() => (init_memory(), memory_exports));
|
|
1127
|
+
return new MemoryAdapter2(config);
|
|
1128
|
+
}
|
|
1129
|
+
case "s3":
|
|
1130
|
+
case "do-spaces": {
|
|
1131
|
+
const { S3Adapter: S3Adapter2 } = await Promise.resolve().then(() => (init_s3(), s3_exports));
|
|
1132
|
+
return new S3Adapter2(config);
|
|
1133
|
+
}
|
|
1134
|
+
case "r2": {
|
|
1135
|
+
const { R2Adapter: R2Adapter2 } = await Promise.resolve().then(() => (init_r2(), r2_exports));
|
|
1136
|
+
return new R2Adapter2(config);
|
|
1137
|
+
}
|
|
1138
|
+
default:
|
|
1139
|
+
throw new StorageError(
|
|
1140
|
+
`Unknown storage type: ${config.type}`,
|
|
1141
|
+
StorageErrorCodes.INVALID_CONFIG
|
|
1142
|
+
);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
function createStorageSync(config) {
|
|
1146
|
+
switch (config.type) {
|
|
1147
|
+
case "memory": {
|
|
1148
|
+
const { MemoryAdapter: MemoryAdapter2 } = (init_memory(), __toCommonJS(memory_exports));
|
|
1149
|
+
return new MemoryAdapter2(config);
|
|
1150
|
+
}
|
|
1151
|
+
case "s3":
|
|
1152
|
+
case "do-spaces": {
|
|
1153
|
+
const { S3Adapter: S3Adapter2 } = (init_s3(), __toCommonJS(s3_exports));
|
|
1154
|
+
return new S3Adapter2(config);
|
|
1155
|
+
}
|
|
1156
|
+
case "r2": {
|
|
1157
|
+
const { R2Adapter: R2Adapter2 } = (init_r2(), __toCommonJS(r2_exports));
|
|
1158
|
+
return new R2Adapter2(config);
|
|
1159
|
+
}
|
|
1160
|
+
default:
|
|
1161
|
+
throw new StorageError(
|
|
1162
|
+
`Unknown storage type: ${config.type}`,
|
|
1163
|
+
StorageErrorCodes.INVALID_CONFIG
|
|
1164
|
+
);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
var StorageUtils = {
|
|
1168
|
+
/**
|
|
1169
|
+
* Get file extension from key
|
|
1170
|
+
*/
|
|
1171
|
+
getExtension(key) {
|
|
1172
|
+
const parts = key.split(".");
|
|
1173
|
+
const lastPart = parts[parts.length - 1];
|
|
1174
|
+
return parts.length > 1 && lastPart ? lastPart.toLowerCase() : "";
|
|
1175
|
+
},
|
|
1176
|
+
/**
|
|
1177
|
+
* Get file name from key
|
|
1178
|
+
*/
|
|
1179
|
+
getFileName(key) {
|
|
1180
|
+
return key.split("/").pop() ?? key;
|
|
1181
|
+
},
|
|
1182
|
+
/**
|
|
1183
|
+
* Get directory from key
|
|
1184
|
+
*/
|
|
1185
|
+
getDirectory(key) {
|
|
1186
|
+
const parts = key.split("/");
|
|
1187
|
+
parts.pop();
|
|
1188
|
+
return parts.join("/");
|
|
1189
|
+
},
|
|
1190
|
+
/**
|
|
1191
|
+
* Join paths safely
|
|
1192
|
+
*/
|
|
1193
|
+
joinPath(...parts) {
|
|
1194
|
+
return parts.filter(Boolean).map((p) => p.replace(/^\/|\/$/g, "")).join("/");
|
|
1195
|
+
},
|
|
1196
|
+
/**
|
|
1197
|
+
* Normalize key (remove leading slash, handle ..)
|
|
1198
|
+
*/
|
|
1199
|
+
normalizeKey(key) {
|
|
1200
|
+
return key.replace(/^\/+/, "").split("/").filter((p) => p !== ".." && p !== ".").join("/");
|
|
1201
|
+
},
|
|
1202
|
+
/**
|
|
1203
|
+
* Generate a unique key with timestamp
|
|
1204
|
+
*/
|
|
1205
|
+
generateUniqueKey(prefix, extension) {
|
|
1206
|
+
const timestamp = Date.now();
|
|
1207
|
+
const random = Math.random().toString(36).slice(2, 10);
|
|
1208
|
+
const ext = extension ? `.${extension}` : "";
|
|
1209
|
+
return `${prefix}/${timestamp}-${random}${ext}`;
|
|
1210
|
+
},
|
|
1211
|
+
/**
|
|
1212
|
+
* Guess content type from file extension
|
|
1213
|
+
*/
|
|
1214
|
+
guessContentType(key) {
|
|
1215
|
+
const ext = StorageUtils.getExtension(key);
|
|
1216
|
+
const contentTypes = {
|
|
1217
|
+
// Text
|
|
1218
|
+
txt: "text/plain",
|
|
1219
|
+
html: "text/html",
|
|
1220
|
+
htm: "text/html",
|
|
1221
|
+
css: "text/css",
|
|
1222
|
+
csv: "text/csv",
|
|
1223
|
+
xml: "text/xml",
|
|
1224
|
+
// Application
|
|
1225
|
+
js: "application/javascript",
|
|
1226
|
+
mjs: "application/javascript",
|
|
1227
|
+
json: "application/json",
|
|
1228
|
+
pdf: "application/pdf",
|
|
1229
|
+
zip: "application/zip",
|
|
1230
|
+
gzip: "application/gzip",
|
|
1231
|
+
gz: "application/gzip",
|
|
1232
|
+
tar: "application/x-tar",
|
|
1233
|
+
doc: "application/msword",
|
|
1234
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
1235
|
+
xls: "application/vnd.ms-excel",
|
|
1236
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
1237
|
+
ppt: "application/vnd.ms-powerpoint",
|
|
1238
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
1239
|
+
// Images
|
|
1240
|
+
png: "image/png",
|
|
1241
|
+
jpg: "image/jpeg",
|
|
1242
|
+
jpeg: "image/jpeg",
|
|
1243
|
+
gif: "image/gif",
|
|
1244
|
+
webp: "image/webp",
|
|
1245
|
+
svg: "image/svg+xml",
|
|
1246
|
+
ico: "image/x-icon",
|
|
1247
|
+
bmp: "image/bmp",
|
|
1248
|
+
tiff: "image/tiff",
|
|
1249
|
+
tif: "image/tiff",
|
|
1250
|
+
// Audio
|
|
1251
|
+
mp3: "audio/mpeg",
|
|
1252
|
+
wav: "audio/wav",
|
|
1253
|
+
ogg: "audio/ogg",
|
|
1254
|
+
flac: "audio/flac",
|
|
1255
|
+
aac: "audio/aac",
|
|
1256
|
+
// Video
|
|
1257
|
+
mp4: "video/mp4",
|
|
1258
|
+
webm: "video/webm",
|
|
1259
|
+
avi: "video/x-msvideo",
|
|
1260
|
+
mov: "video/quicktime",
|
|
1261
|
+
mkv: "video/x-matroska",
|
|
1262
|
+
// Fonts
|
|
1263
|
+
woff: "font/woff",
|
|
1264
|
+
woff2: "font/woff2",
|
|
1265
|
+
ttf: "font/ttf",
|
|
1266
|
+
otf: "font/otf",
|
|
1267
|
+
eot: "application/vnd.ms-fontobject"
|
|
1268
|
+
};
|
|
1269
|
+
return contentTypes[ext] ?? "application/octet-stream";
|
|
1270
|
+
},
|
|
1271
|
+
/**
|
|
1272
|
+
* Format file size to human readable string
|
|
1273
|
+
*/
|
|
1274
|
+
formatSize(bytes) {
|
|
1275
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
1276
|
+
let size = bytes;
|
|
1277
|
+
let unitIndex = 0;
|
|
1278
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
1279
|
+
size /= 1024;
|
|
1280
|
+
unitIndex++;
|
|
1281
|
+
}
|
|
1282
|
+
return `${size.toFixed(unitIndex > 0 ? 2 : 0)} ${units[unitIndex]}`;
|
|
1283
|
+
},
|
|
1284
|
+
/**
|
|
1285
|
+
* Parse file size string to bytes
|
|
1286
|
+
*/
|
|
1287
|
+
parseSize(size) {
|
|
1288
|
+
const units = {
|
|
1289
|
+
b: 1,
|
|
1290
|
+
kb: 1024,
|
|
1291
|
+
mb: 1024 * 1024,
|
|
1292
|
+
gb: 1024 * 1024 * 1024,
|
|
1293
|
+
tb: 1024 * 1024 * 1024 * 1024
|
|
1294
|
+
};
|
|
1295
|
+
const match = size.toLowerCase().match(/^(\d+(?:\.\d+)?)\s*([a-z]+)?$/);
|
|
1296
|
+
if (!match || !match[1]) return 0;
|
|
1297
|
+
const value = parseFloat(match[1]);
|
|
1298
|
+
const unit = match[2] ?? "b";
|
|
1299
|
+
return Math.floor(value * (units[unit] ?? 1));
|
|
1300
|
+
}
|
|
1301
|
+
};
|
|
1302
|
+
export {
|
|
1303
|
+
MemoryAdapter,
|
|
1304
|
+
R2Adapter,
|
|
1305
|
+
S3Adapter,
|
|
1306
|
+
StorageError,
|
|
1307
|
+
StorageErrorCodes,
|
|
1308
|
+
StorageUtils,
|
|
1309
|
+
createDOSpacesAdapter,
|
|
1310
|
+
createMemoryAdapter,
|
|
1311
|
+
createR2Adapter,
|
|
1312
|
+
createS3Adapter,
|
|
1313
|
+
createStorage,
|
|
1314
|
+
createStorageSync
|
|
1315
|
+
};
|
|
1316
|
+
//# sourceMappingURL=index.js.map
|