@velajs/storage 0.3.1 → 0.3.2

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.
Files changed (99) hide show
  1. package/CHANGELOG.md +6 -1
  2. package/dist/body-BMUcBeQ0.js +93 -0
  3. package/dist/body-BMUcBeQ0.js.map +1 -0
  4. package/dist/client/index.d.ts +49 -46
  5. package/dist/client/index.js +207 -228
  6. package/dist/client/index.js.map +1 -0
  7. package/dist/drivers/memory/index.d.ts +17 -13
  8. package/dist/drivers/memory/index.js +190 -197
  9. package/dist/drivers/memory/index.js.map +1 -0
  10. package/dist/drivers/r2/index.d.ts +7 -4
  11. package/dist/drivers/r2/index.js +162 -160
  12. package/dist/drivers/r2/index.js.map +1 -0
  13. package/dist/drivers/r2-http/index.d.ts +23 -19
  14. package/dist/drivers/r2-http/index.js +52 -45
  15. package/dist/drivers/r2-http/index.js.map +1 -0
  16. package/dist/drivers/s3/index.d.ts +79 -5
  17. package/dist/drivers/s3/index.js +2 -3
  18. package/dist/index.d.ts +271 -17
  19. package/dist/index.js +1078 -16
  20. package/dist/index.js.map +1 -0
  21. package/dist/middleware/index.d.ts +177 -15
  22. package/dist/middleware/index.js +553 -8
  23. package/dist/middleware/index.js.map +1 -0
  24. package/dist/protocol.types-C5PPBD12.d.ts +80 -0
  25. package/dist/r2.types-Bw4Ft83t.d.ts +71 -0
  26. package/dist/retry-DXtO6R9D.js +119 -0
  27. package/dist/retry-DXtO6R9D.js.map +1 -0
  28. package/dist/s3-DrE9NrnD.js +557 -0
  29. package/dist/s3-DrE9NrnD.js.map +1 -0
  30. package/dist/storage.error-DVCOkJhc.js +49 -0
  31. package/dist/storage.error-DVCOkJhc.js.map +1 -0
  32. package/dist/storage.types-BJN7Yp1J.d.ts +288 -0
  33. package/dist/storagesdk/index.d.ts +73 -69
  34. package/dist/storagesdk/index.js +117 -130
  35. package/dist/storagesdk/index.js.map +1 -0
  36. package/dist/stored-file-2Yl07eRc.js +105 -0
  37. package/dist/stored-file-2Yl07eRc.js.map +1 -0
  38. package/dist/testing/index.d.ts +12 -8
  39. package/dist/testing/index.js +36 -53
  40. package/dist/testing/index.js.map +1 -0
  41. package/package.json +62 -48
  42. package/dist/base64.d.ts +0 -8
  43. package/dist/base64.js +0 -25
  44. package/dist/decorators/inject-storage.decorator.d.ts +0 -17
  45. package/dist/decorators/inject-storage.decorator.js +0 -21
  46. package/dist/drivers/r2/r2.types.d.ts +0 -67
  47. package/dist/drivers/r2/r2.types.js +0 -5
  48. package/dist/drivers/s3/s3-client.d.ts +0 -33
  49. package/dist/drivers/s3/s3-client.js +0 -154
  50. package/dist/drivers/s3/s3.driver.d.ts +0 -4
  51. package/dist/drivers/s3/s3.driver.js +0 -407
  52. package/dist/drivers/s3/s3.types.d.ts +0 -28
  53. package/dist/drivers/s3/s3.types.js +0 -1
  54. package/dist/drivers/s3/xml.d.ts +0 -6
  55. package/dist/drivers/s3/xml.js +0 -52
  56. package/dist/http/authorizer.types.d.ts +0 -59
  57. package/dist/http/authorizer.types.js +0 -1
  58. package/dist/http/http-helpers.d.ts +0 -22
  59. package/dist/http/http-helpers.js +0 -80
  60. package/dist/http/protocol.types.d.ts +0 -76
  61. package/dist/http/protocol.types.js +0 -1
  62. package/dist/internal/body.d.ts +0 -18
  63. package/dist/internal/body.js +0 -90
  64. package/dist/internal/retry.d.ts +0 -41
  65. package/dist/internal/retry.js +0 -127
  66. package/dist/internal/stored-file.d.ts +0 -27
  67. package/dist/internal/stored-file.js +0 -114
  68. package/dist/middleware/cache.d.ts +0 -50
  69. package/dist/middleware/cache.js +0 -101
  70. package/dist/middleware/compose.d.ts +0 -12
  71. package/dist/middleware/compose.js +0 -11
  72. package/dist/middleware/compression.d.ts +0 -16
  73. package/dist/middleware/compression.js +0 -101
  74. package/dist/middleware/encryption.d.ts +0 -14
  75. package/dist/middleware/encryption.js +0 -134
  76. package/dist/middleware/failover.d.ts +0 -18
  77. package/dist/middleware/failover.js +0 -52
  78. package/dist/middleware/retry.d.ts +0 -14
  79. package/dist/middleware/retry.js +0 -47
  80. package/dist/middleware/versioning.d.ts +0 -36
  81. package/dist/middleware/versioning.js +0 -89
  82. package/dist/middleware/wrap.d.ts +0 -11
  83. package/dist/middleware/wrap.js +0 -46
  84. package/dist/object-key.d.ts +0 -17
  85. package/dist/object-key.js +0 -42
  86. package/dist/storage.controller.d.ts +0 -21
  87. package/dist/storage.controller.js +0 -386
  88. package/dist/storage.error.d.ts +0 -38
  89. package/dist/storage.error.js +0 -52
  90. package/dist/storage.facade.d.ts +0 -47
  91. package/dist/storage.facade.js +0 -443
  92. package/dist/storage.module.d.ts +0 -46
  93. package/dist/storage.module.js +0 -113
  94. package/dist/storage.service.d.ts +0 -42
  95. package/dist/storage.service.js +0 -86
  96. package/dist/storage.tokens.d.ts +0 -13
  97. package/dist/storage.tokens.js +0 -26
  98. package/dist/storage.types.d.ts +0 -245
  99. package/dist/storage.types.js +0 -1
package/dist/index.js CHANGED
@@ -1,16 +1,1078 @@
1
- // Public surface of @velajs/storage (the `.` entry).
2
- // NOTE: drivers live behind their own subpaths (`./drivers/*`) so importing
3
- // the contract/module never pulls `aws4fetch` or a specific backend.
4
- // --- Facade ---------------------------------------------------------------
5
- export { Storage, createStorage } from "./storage.facade.js";
6
- // --- DI: module / service / tokens / decorator ----------------------------
7
- export { StorageModule } from "./storage.module.js";
8
- export { StorageService, lazyDriver } from "./storage.service.js";
9
- export { InjectStorage } from "./decorators/inject-storage.decorator.js";
10
- export { DEFAULT_STORAGE_NAME, storageDriverBuilder, storageToken } from "./storage.tokens.js";
11
- // --- HTTP controller (P1) ---
12
- export { createStorageController } from "./storage.controller.js";
13
- // --- Errors ---------------------------------------------------------------
14
- export { StorageError, isAbort } from "./storage.error.js";
15
- // --- Key helpers ----------------------------------------------------------
16
- export { isSafeKey, sanitizeKey, normalizePrefix, joinKey, stripPrefix } from "./object-key.js";
1
+ import { a as isStream, i as countingStream, n as chunkStream, s as toStream, t as byteLengthOf } from "./body-BMUcBeQ0.js";
2
+ import { n as isAbort, t as StorageError } from "./storage.error-DVCOkJhc.js";
3
+ import { n as runWithRetry, t as normalizeRetry } from "./retry-DXtO6R9D.js";
4
+ import { Controller, Get, Inject, Injectable, InjectionToken, Post, Req, defineModule, lazyProvider, stableHash } from "@velajs/vela";
5
+ //#region src/object-key.ts
6
+ const CONTROL_CHARS = /[\u0000-\u001f\u007f]/;
7
+ /**
8
+ * True when `key` is a safe object key:
9
+ * - non-empty, not longer than 1024 bytes (S3's limit)
10
+ * - no leading `/`
11
+ * - no `.` / `..` path segments (traversal)
12
+ * - no control characters
13
+ * - no backslashes (Windows-style separators)
14
+ */
15
+ function isSafeKey(key) {
16
+ if (typeof key !== "string" || key.length === 0 || key.length > 1024) return false;
17
+ if (key.startsWith("/")) return false;
18
+ if (key.includes("\\")) return false;
19
+ if (CONTROL_CHARS.test(key)) return false;
20
+ for (const segment of key.split("/")) if (segment === "." || segment === "..") return false;
21
+ return true;
22
+ }
23
+ /** Return `key` if safe, else throw `StorageError('InvalidKey')`. */
24
+ function sanitizeKey(key) {
25
+ if (!isSafeKey(key)) throw new StorageError("InvalidKey", `unsafe object key: ${JSON.stringify(key)}`);
26
+ return key;
27
+ }
28
+ /** Normalize a prefix: drop leading/trailing slashes, collapse to '' when empty. */
29
+ function normalizePrefix(prefix) {
30
+ if (!prefix) return "";
31
+ return prefix.replace(/^\/+/, "").replace(/\/+$/, "");
32
+ }
33
+ /** Join a normalized prefix and a key with a single '/'. */
34
+ function joinKey(prefix, key) {
35
+ return prefix ? `${prefix}/${key}` : key;
36
+ }
37
+ /** Strip a normalized prefix from a stored key (for surfacing to callers). */
38
+ function stripPrefix(prefix, key) {
39
+ if (!prefix) return key;
40
+ const withSlash = `${prefix}/`;
41
+ return key.startsWith(withSlash) ? key.slice(withSlash.length) : key;
42
+ }
43
+ //#endregion
44
+ //#region src/storage.facade.ts
45
+ const DEFAULT_PART_SIZE = 5 * 1024 * 1024;
46
+ const DEFAULT_CONCURRENCY = 4;
47
+ const MULTIPART_THRESHOLD = 16 * 1024 * 1024;
48
+ /**
49
+ * The consumer-facing storage handle. Wraps a {@link StorageDriver} with
50
+ * capability-gating (throws before any driver call), retry/timeout/abort,
51
+ * prefix scoping, generic `move`/`deleteMany` fallbacks, multipart
52
+ * orchestration, progress synthesis, and observability hooks.
53
+ *
54
+ * Framework-free — `StorageService` builds and caches one of these per bucket.
55
+ */
56
+ var Storage = class Storage {
57
+ #driver;
58
+ #prefix;
59
+ #readonly;
60
+ #opts;
61
+ constructor(opts) {
62
+ this.#opts = opts;
63
+ this.#driver = opts.driver;
64
+ this.#prefix = normalizePrefix(opts.prefix);
65
+ this.#readonly = opts.readonly ?? false;
66
+ }
67
+ get raw() {
68
+ return this.#driver.raw;
69
+ }
70
+ get driver() {
71
+ return this.#driver;
72
+ }
73
+ get capabilities() {
74
+ const d = this.#driver;
75
+ return {
76
+ rangeRead: !!d.supportsRange,
77
+ uploadProgress: !!d.reportsUploadProgress,
78
+ delimiter: !!d.supportsDelimiter,
79
+ metadata: !!d.supportsMetadata,
80
+ cacheControl: !!d.supportsCacheControl,
81
+ serverSideCopy: !!d.supportsServerSideCopy,
82
+ multipart: typeof d.createMultipartUpload === "function",
83
+ signedMultipart: d.signedMultipart != null,
84
+ signedUrl: d.signedUrl ?? {
85
+ supported: false,
86
+ upload: false
87
+ }
88
+ };
89
+ }
90
+ async upload(key, body, opts) {
91
+ this.#assertWritable();
92
+ this.#assertUploadSupported(opts);
93
+ const d = this.#driver;
94
+ const path = this.#path(key);
95
+ const len = byteLengthOf(body);
96
+ const wantsMultipart = opts?.multipart != null && opts.multipart !== false;
97
+ const unknownStream = isStream(body) && len === void 0;
98
+ const bigKnown = len != null && len > MULTIPART_THRESHOLD;
99
+ const canMultipart = typeof d.createMultipartUpload === "function";
100
+ if (wantsMultipart && !canMultipart) throw new StorageError("Unsupported", `${d.name}: multipart not supported`);
101
+ if ((wantsMultipart || unknownStream || bigKnown) && canMultipart) return {
102
+ ...await this.#exec("upload", key, false, opts, () => this.#putMultipart(path, body, opts)),
103
+ key
104
+ };
105
+ const streaming = isStream(body);
106
+ let outBody = body;
107
+ if (opts?.onProgress && !d.reportsUploadProgress && streaming) outBody = countingStream(body, (loaded) => opts.onProgress({
108
+ loaded,
109
+ total: len
110
+ }));
111
+ const result = await this.#exec("upload", key, !streaming, opts, (signal) => d.upload(path, outBody, {
112
+ ...opts,
113
+ signal
114
+ }));
115
+ if (opts?.onProgress && !d.reportsUploadProgress && !streaming && len != null) opts.onProgress({
116
+ loaded: len,
117
+ total: len
118
+ });
119
+ return {
120
+ ...result,
121
+ key
122
+ };
123
+ }
124
+ async #putMultipart(path, body, opts) {
125
+ const mp = typeof opts?.multipart === "object" ? opts.multipart : {};
126
+ const partSize = mp.partSize ?? DEFAULT_PART_SIZE;
127
+ const concurrency = Math.max(1, mp.concurrency ?? DEFAULT_CONCURRENCY);
128
+ const total = byteLengthOf(body);
129
+ const upload = await this.#driver.createMultipartUpload(path, {
130
+ contentType: opts?.contentType,
131
+ cacheControl: opts?.cacheControl,
132
+ metadata: opts?.metadata,
133
+ signal: opts?.signal
134
+ });
135
+ try {
136
+ const parts = [];
137
+ let uploadedBytes = 0;
138
+ let partNumber = 0;
139
+ const inflight = /* @__PURE__ */ new Set();
140
+ for await (const chunk of chunkStream(toStream(body), partSize)) {
141
+ partNumber += 1;
142
+ const n = partNumber;
143
+ const size = chunk.byteLength;
144
+ const tracked = (async () => {
145
+ const up = await upload.uploadPart(n, chunk, { signal: opts?.signal });
146
+ parts[n - 1] = {
147
+ partNumber: n,
148
+ etag: up.etag,
149
+ size: up.size ?? size
150
+ };
151
+ uploadedBytes += size;
152
+ opts?.onProgress?.({
153
+ loaded: uploadedBytes,
154
+ total
155
+ });
156
+ })().finally(() => inflight.delete(tracked));
157
+ inflight.add(tracked);
158
+ if (inflight.size >= concurrency) await Promise.race(inflight);
159
+ }
160
+ await Promise.all(inflight);
161
+ return await upload.complete(parts, { signal: opts?.signal });
162
+ } catch (e) {
163
+ await upload.abort().catch(() => {});
164
+ throw StorageError.wrap(e);
165
+ }
166
+ }
167
+ async download(key, opts) {
168
+ if (opts?.range) this.#assertRange();
169
+ const file = await this.#exec("download", key, true, opts, (signal) => this.#driver.download(this.#path(key), {
170
+ ...opts,
171
+ signal
172
+ }));
173
+ return this.#relabel(file, key);
174
+ }
175
+ async head(key, opts) {
176
+ const file = await this.#exec("head", key, true, opts, (signal) => this.#driver.head(this.#path(key), {
177
+ ...opts,
178
+ signal
179
+ }));
180
+ return this.#relabel(file, key);
181
+ }
182
+ exists(key, opts) {
183
+ return this.#exec("exists", key, true, opts, (signal) => this.#driver.exists(this.#path(key), {
184
+ ...opts,
185
+ signal
186
+ }));
187
+ }
188
+ async delete(keyOrKeys, opts) {
189
+ this.#assertWritable();
190
+ if (Array.isArray(keyOrKeys)) return this.#deleteMany(keyOrKeys, opts);
191
+ const key = keyOrKeys;
192
+ return this.#exec("delete", key, true, opts, (signal) => this.#driver.delete(this.#path(key), {
193
+ ...opts,
194
+ signal
195
+ }));
196
+ }
197
+ async #deleteMany(keys, opts) {
198
+ const d = this.#driver;
199
+ if (d.deleteMany) {
200
+ const res = await this.#exec("deleteMany", void 0, true, opts, (signal) => d.deleteMany(keys.map((k) => this.#path(k)), {
201
+ ...opts,
202
+ signal
203
+ }));
204
+ return {
205
+ deleted: res.deleted.map((k) => this.#strip(k)),
206
+ errors: res.errors?.map((e) => ({
207
+ key: this.#strip(e.key),
208
+ error: e.error
209
+ }))
210
+ };
211
+ }
212
+ const concurrency = Math.max(1, opts?.concurrency ?? 8);
213
+ const stopOnError = opts?.stopOnError ?? false;
214
+ const deleted = [];
215
+ const errors = [];
216
+ let index = 0;
217
+ let stopped = false;
218
+ const worker = async () => {
219
+ while (index < keys.length && !stopped) {
220
+ const key = keys[index++];
221
+ try {
222
+ await d.delete(this.#path(key), { signal: opts?.signal });
223
+ deleted.push(key);
224
+ } catch (e) {
225
+ const error = StorageError.wrap(e);
226
+ errors.push({
227
+ key,
228
+ error
229
+ });
230
+ if (stopOnError) {
231
+ stopped = true;
232
+ throw error;
233
+ }
234
+ }
235
+ }
236
+ };
237
+ const runners = Array.from({ length: Math.min(concurrency, keys.length) }, worker);
238
+ if (stopOnError) await Promise.all(runners);
239
+ else await Promise.allSettled(runners);
240
+ return {
241
+ deleted,
242
+ errors: errors.length ? errors : void 0
243
+ };
244
+ }
245
+ async copy(from, to, opts) {
246
+ this.#assertWritable();
247
+ return this.#exec("copy", to, true, opts, (signal) => this.#driver.copy(this.#path(from), this.#path(to), {
248
+ ...opts,
249
+ signal
250
+ }));
251
+ }
252
+ async move(from, to, opts) {
253
+ this.#assertWritable();
254
+ const f = this.#path(from);
255
+ const t = this.#path(to);
256
+ if (f === t) return;
257
+ if (this.#driver.move) {
258
+ await this.#exec("move", to, true, opts, (signal) => this.#driver.move(f, t, {
259
+ ...opts,
260
+ signal
261
+ }));
262
+ return;
263
+ }
264
+ await this.#exec("move", to, true, opts, async (signal) => {
265
+ await this.#driver.copy(f, t, {
266
+ ...opts,
267
+ signal
268
+ });
269
+ await this.#driver.delete(f, {
270
+ ...opts,
271
+ signal
272
+ });
273
+ });
274
+ }
275
+ async list(opts) {
276
+ if (opts?.delimiter) this.#assertDelimiter();
277
+ const scopedPrefix = this.#listPrefix(opts?.prefix);
278
+ const res = await this.#exec("list", void 0, true, opts, (signal) => this.#driver.list({
279
+ ...opts,
280
+ prefix: scopedPrefix,
281
+ signal
282
+ }));
283
+ return {
284
+ items: res.items.map((f) => this.#relabel(f, this.#strip(f.key))),
285
+ prefixes: res.prefixes?.map((p) => this.#strip(p)),
286
+ cursor: res.cursor
287
+ };
288
+ }
289
+ async *listAll(opts) {
290
+ let cursor = opts?.cursor;
291
+ do {
292
+ const page = await this.list({
293
+ ...opts,
294
+ cursor
295
+ });
296
+ for (const item of page.items) yield item;
297
+ cursor = page.cursor;
298
+ } while (cursor);
299
+ }
300
+ async url(key, opts) {
301
+ if (!this.#driver.signedUrl?.supported) throw new StorageError("Unsupported", `${this.#driver.name}: url() not supported`);
302
+ return this.#exec("url", key, true, opts, (signal) => this.#driver.url(this.#path(key), {
303
+ ...opts,
304
+ signal
305
+ }));
306
+ }
307
+ async signedUploadUrl(key, opts) {
308
+ this.#assertWritable();
309
+ if (!this.#driver.signedUrl?.upload) throw new StorageError("Unsupported", `${this.#driver.name}: signed uploads not supported`);
310
+ return this.#exec("signedUploadUrl", key, true, opts, (signal) => this.#driver.signedUploadUrl(this.#path(key), {
311
+ ...opts,
312
+ signal
313
+ }));
314
+ }
315
+ /** Prefix-bound view of the driver's presigned-multipart capability. */
316
+ get signedMultipart() {
317
+ const cap = this.#driver.signedMultipart;
318
+ if (!cap) return void 0;
319
+ const path = (k) => this.#path(k);
320
+ return {
321
+ create: (key, o) => cap.create(path(key), o),
322
+ signPart: (key, uploadId, partNumber, o) => cap.signPart(path(key), uploadId, partNumber, o),
323
+ complete: (key, uploadId, parts) => cap.complete(path(key), uploadId, parts),
324
+ abort: (key, uploadId) => cap.abort(path(key), uploadId)
325
+ };
326
+ }
327
+ async createMultipartUpload(key, opts) {
328
+ this.#assertWritable();
329
+ if (typeof this.#driver.createMultipartUpload !== "function") throw new StorageError("Unsupported", `${this.#driver.name}: multipart not supported`);
330
+ return this.#driver.createMultipartUpload(this.#path(key), opts);
331
+ }
332
+ resumeMultipartUpload(key, uploadId) {
333
+ this.#assertWritable();
334
+ if (typeof this.#driver.resumeMultipartUpload !== "function") throw new StorageError("Unsupported", `${this.#driver.name}: multipart resume not supported`);
335
+ return this.#driver.resumeMultipartUpload(this.#path(key), uploadId);
336
+ }
337
+ /** A key-bound handle over the common single-object operations. */
338
+ file(key) {
339
+ return {
340
+ key,
341
+ upload: (body, o) => this.upload(key, body, o),
342
+ download: (o) => this.download(key, o),
343
+ head: (o) => this.head(key, o),
344
+ exists: (o) => this.exists(key, o),
345
+ delete: (o) => this.delete(key, o),
346
+ url: (o) => this.url(key, o),
347
+ signedUploadUrl: (o) => this.signedUploadUrl(key, o)
348
+ };
349
+ }
350
+ /** A read-only clone of this handle (mutations throw `ReadOnly`). */
351
+ readonly() {
352
+ return new Storage({
353
+ ...this.#opts,
354
+ readonly: true
355
+ });
356
+ }
357
+ #path(key) {
358
+ return joinKey(this.#prefix, sanitizeKey(key));
359
+ }
360
+ #strip(storedKey) {
361
+ return stripPrefix(this.#prefix, storedKey);
362
+ }
363
+ #listPrefix(prefix) {
364
+ if (!this.#prefix) return prefix;
365
+ return prefix ? `${this.#prefix}/${prefix}` : `${this.#prefix}/`;
366
+ }
367
+ #relabel(file, logicalKey) {
368
+ if (file.key === logicalKey) return file;
369
+ return {
370
+ key: logicalKey,
371
+ name: logicalKey.slice(logicalKey.lastIndexOf("/") + 1),
372
+ size: file.size,
373
+ type: file.type,
374
+ lastModified: file.lastModified,
375
+ etag: file.etag,
376
+ metadata: file.metadata,
377
+ arrayBuffer: () => file.arrayBuffer(),
378
+ text: () => file.text(),
379
+ blob: () => file.blob(),
380
+ stream: () => file.stream()
381
+ };
382
+ }
383
+ #assertWritable() {
384
+ if (this.#readonly) throw new StorageError("ReadOnly", "storage handle is read-only");
385
+ }
386
+ #assertUploadSupported(opts) {
387
+ const d = this.#driver;
388
+ if (opts?.metadata && Object.keys(opts.metadata).length > 0 && !d.supportsMetadata) throw new StorageError("Unsupported", `${d.name}: metadata not supported`);
389
+ if (opts?.cacheControl && !d.supportsCacheControl) throw new StorageError("Unsupported", `${d.name}: cacheControl not supported`);
390
+ }
391
+ #assertRange() {
392
+ if (!this.#driver.supportsRange) throw new StorageError("Unsupported", `${this.#driver.name}: range reads not supported`);
393
+ }
394
+ #assertDelimiter() {
395
+ if (!this.#driver.supportsDelimiter) throw new StorageError("Unsupported", `${this.#driver.name}: delimiter not supported`);
396
+ }
397
+ async #exec(type, key, replayable, opts, fn) {
398
+ const started = Date.now();
399
+ const retries = replayable ? opts?.retries ?? this.#opts.retries : 0;
400
+ const maxRetries = normalizeRetry(retries).max;
401
+ const hooks = this.#opts.hooks;
402
+ try {
403
+ const result = await runWithRetry(fn, {
404
+ retries,
405
+ timeout: opts?.timeout ?? this.#opts.timeout,
406
+ signal: opts?.signal ?? this.#opts.signal
407
+ }, (info) => hooks?.onRetry?.({
408
+ type,
409
+ key,
410
+ attempt: info.attempt,
411
+ maxRetries,
412
+ delayMs: info.delayMs,
413
+ error: info.error
414
+ }));
415
+ hooks?.onOperation?.({
416
+ type,
417
+ key,
418
+ status: "success",
419
+ durationMs: Date.now() - started
420
+ });
421
+ return result;
422
+ } catch (e) {
423
+ const error = StorageError.wrap(e);
424
+ hooks?.onError?.({
425
+ type,
426
+ key,
427
+ error
428
+ });
429
+ hooks?.onOperation?.({
430
+ type,
431
+ key,
432
+ status: "error",
433
+ durationMs: Date.now() - started,
434
+ error
435
+ });
436
+ throw error;
437
+ }
438
+ }
439
+ };
440
+ function createStorage(opts) {
441
+ return new Storage(opts);
442
+ }
443
+ //#endregion
444
+ //#region src/http/http-helpers.ts
445
+ /** Map a core error code to (wire code, HTTP status). */
446
+ function mapError(code) {
447
+ switch (code) {
448
+ case "NotFound": return {
449
+ wire: "not_found",
450
+ status: 404
451
+ };
452
+ case "AccessDenied":
453
+ case "ReadOnly": return {
454
+ wire: "forbidden",
455
+ status: 403
456
+ };
457
+ case "Unsupported": return {
458
+ wire: "capability_unsupported",
459
+ status: 400
460
+ };
461
+ case "InvalidKey": return {
462
+ wire: "invalid_key",
463
+ status: 400
464
+ };
465
+ case "InvalidRequest":
466
+ case "Aborted": return {
467
+ wire: "invalid_request",
468
+ status: 400
469
+ };
470
+ case "Conflict": return {
471
+ wire: "conflict",
472
+ status: 409
473
+ };
474
+ case "RateLimited": return {
475
+ wire: "rate_limited",
476
+ status: 429
477
+ };
478
+ case "Timeout": return {
479
+ wire: "upstream_error",
480
+ status: 504
481
+ };
482
+ default: return {
483
+ wire: "upstream_error",
484
+ status: 502
485
+ };
486
+ }
487
+ }
488
+ function serializeStored(file) {
489
+ return {
490
+ key: file.key,
491
+ name: file.name,
492
+ size: file.size,
493
+ type: file.type,
494
+ lastModified: file.lastModified,
495
+ etag: file.etag,
496
+ metadata: file.metadata
497
+ };
498
+ }
499
+ function clampExpiry(requested, def, max) {
500
+ return Math.min(requested ?? def, max);
501
+ }
502
+ /** Parse an HTTP `Range` header (single range) into a {@link ByteRange}. */
503
+ function parseRange(header) {
504
+ if (!header) return void 0;
505
+ const m = /^bytes=(\d+)-(\d*)$/.exec(header.trim());
506
+ if (!m) return void 0;
507
+ return {
508
+ start: Number(m[1]),
509
+ end: m[2] === "" ? void 0 : Number(m[2])
510
+ };
511
+ }
512
+ /** Content-Disposition header — defaults to `attachment` (anti stored-XSS). */
513
+ function dispositionHeader(disposition, name) {
514
+ const filename = `filename*=UTF-8''${encodeURIComponent(name)}`;
515
+ return disposition === "inline" ? `inline; ${filename}` : `attachment; ${filename}`;
516
+ }
517
+ //#endregion
518
+ //#region \0@oxc-project+runtime@0.139.0/helpers/esm/decorateMetadata.js
519
+ function __decorateMetadata(k, v) {
520
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
521
+ }
522
+ //#endregion
523
+ //#region \0@oxc-project+runtime@0.139.0/helpers/esm/decorateParam.js
524
+ function __decorateParam(paramIndex, decorator) {
525
+ return function(target, key) {
526
+ decorator(target, key, paramIndex);
527
+ };
528
+ }
529
+ //#endregion
530
+ //#region \0@oxc-project+runtime@0.139.0/helpers/esm/decorate.js
531
+ function __decorate(decorators, target, key, desc) {
532
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
533
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
534
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
535
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
536
+ }
537
+ //#endregion
538
+ //#region src/storage.controller.ts
539
+ /**
540
+ * Build a vela controller exposing presigned/multipart upload + download
541
+ * endpoints for one bucket. Factory-decorated (like better-auth's catch-all)
542
+ * so `basePath` bakes in at decoration time. Consumes the {@link StorageService}
543
+ * facade so gating/retry/prefix apply uniformly on the HTTP path.
544
+ */
545
+ function createStorageController(basePath, serviceToken, http) {
546
+ let StorageController = class StorageController {
547
+ svc;
548
+ constructor(svc) {
549
+ this.svc = svc;
550
+ }
551
+ get storage() {
552
+ return this.svc.storage;
553
+ }
554
+ async authorize(c, action) {
555
+ if (!http.authorize) {
556
+ if (http.defaultPolicy === "allow") return {};
557
+ throw new StorageError("AccessDenied", "storage: no authorizer configured (default deny)");
558
+ }
559
+ const res = await http.authorize(action, {
560
+ req: c.req.raw,
561
+ ctx: c,
562
+ driver: http.driverName
563
+ });
564
+ if (res === false) throw new StorageError("AccessDenied", "not allowed");
565
+ if (res === true) return {};
566
+ return res;
567
+ }
568
+ fail(c, e) {
569
+ const err = e instanceof StorageError ? e : StorageError.wrap(e);
570
+ const { wire, status } = mapError(err.code);
571
+ const message = err.internal || status >= 500 ? "storage backend error" : err.message;
572
+ return c.json({ error: {
573
+ code: wire,
574
+ message
575
+ } }, status);
576
+ }
577
+ async signUpload(c) {
578
+ try {
579
+ const b = await c.req.json();
580
+ const ov = await this.authorize(c, {
581
+ type: "sign-upload",
582
+ key: b.key,
583
+ contentType: b.contentType,
584
+ size: b.size
585
+ });
586
+ const key = sanitizeKey(ov.key ?? b.key);
587
+ const upload = await this.storage.signedUploadUrl(key, {
588
+ expiresIn: clampExpiry(ov.expiresIn ?? b.expiresIn, http.defaultExpiresIn, http.maxExpiresIn),
589
+ contentType: b.contentType,
590
+ maxSize: ov.maxSize ?? http.maxUploadSize
591
+ });
592
+ return c.json({
593
+ key,
594
+ upload
595
+ });
596
+ } catch (e) {
597
+ return this.fail(c, e);
598
+ }
599
+ }
600
+ async multipartCreate(c) {
601
+ try {
602
+ const b = await c.req.json();
603
+ const ov = await this.authorize(c, {
604
+ type: "multipart-create",
605
+ key: b.key,
606
+ contentType: b.contentType
607
+ });
608
+ const key = sanitizeKey(ov.key ?? b.key);
609
+ const sm = this.storage.signedMultipart;
610
+ if (!sm) throw new StorageError("Unsupported", "presigned multipart not supported");
611
+ const created = await sm.create(key, {
612
+ contentType: b.contentType,
613
+ metadata: ov.metadata ?? b.metadata,
614
+ partSize: b.partSize
615
+ });
616
+ return c.json({
617
+ key,
618
+ uploadId: created.uploadId,
619
+ partSize: created.partSize
620
+ });
621
+ } catch (e) {
622
+ return this.fail(c, e);
623
+ }
624
+ }
625
+ async multipartSignPart(c) {
626
+ try {
627
+ const b = await c.req.json();
628
+ const ov = await this.authorize(c, {
629
+ type: "multipart-sign-part",
630
+ key: b.key,
631
+ uploadId: b.uploadId,
632
+ partNumber: b.partNumber
633
+ });
634
+ const key = sanitizeKey(ov.key ?? b.key);
635
+ const sm = this.storage.signedMultipart;
636
+ if (!sm) throw new StorageError("Unsupported", "presigned multipart not supported");
637
+ const part = await sm.signPart(key, b.uploadId, b.partNumber, { expiresIn: clampExpiry(ov.expiresIn, http.defaultExpiresIn, http.maxExpiresIn) });
638
+ return c.json({
639
+ partNumber: b.partNumber,
640
+ method: "PUT",
641
+ url: part.url,
642
+ headers: part.headers
643
+ });
644
+ } catch (e) {
645
+ return this.fail(c, e);
646
+ }
647
+ }
648
+ async multipartComplete(c) {
649
+ try {
650
+ const b = await c.req.json();
651
+ const key = sanitizeKey((await this.authorize(c, {
652
+ type: "multipart-complete",
653
+ key: b.key,
654
+ uploadId: b.uploadId
655
+ })).key ?? b.key);
656
+ const sm = this.storage.signedMultipart;
657
+ if (!sm) throw new StorageError("Unsupported", "presigned multipart not supported");
658
+ return c.json(await sm.complete(key, b.uploadId, b.parts));
659
+ } catch (e) {
660
+ return this.fail(c, e);
661
+ }
662
+ }
663
+ async multipartAbort(c) {
664
+ try {
665
+ const b = await c.req.json();
666
+ const key = sanitizeKey((await this.authorize(c, {
667
+ type: "multipart-abort",
668
+ key: b.key,
669
+ uploadId: b.uploadId
670
+ })).key ?? b.key);
671
+ const sm = this.storage.signedMultipart;
672
+ if (!sm) throw new StorageError("Unsupported", "presigned multipart not supported");
673
+ await sm.abort(key, b.uploadId);
674
+ return c.json({ ok: true });
675
+ } catch (e) {
676
+ return this.fail(c, e);
677
+ }
678
+ }
679
+ async list(c) {
680
+ try {
681
+ const prefix = c.req.query("prefix");
682
+ const ov = await this.authorize(c, {
683
+ type: "list",
684
+ prefix
685
+ });
686
+ const limit = c.req.query("limit");
687
+ const res = await this.storage.list({
688
+ prefix: ov.prefix ?? prefix,
689
+ cursor: c.req.query("cursor"),
690
+ delimiter: c.req.query("delimiter"),
691
+ limit: limit ? Math.min(Number(limit), http.maxListLimit) : void 0
692
+ });
693
+ return c.json({
694
+ items: res.items.map(serializeStored),
695
+ prefixes: res.prefixes,
696
+ cursor: res.cursor
697
+ });
698
+ } catch (e) {
699
+ return this.fail(c, e);
700
+ }
701
+ }
702
+ async head(c) {
703
+ try {
704
+ const key = sanitizeKey(c.req.query("key") ?? "");
705
+ const ov = await this.authorize(c, {
706
+ type: "head",
707
+ key
708
+ });
709
+ const file = await this.storage.head(sanitizeKey(ov.key ?? key));
710
+ return c.json(serializeStored(file));
711
+ } catch (e) {
712
+ return this.fail(c, e);
713
+ }
714
+ }
715
+ async delete(c) {
716
+ try {
717
+ const keys = (await c.req.json()).keys.map((k) => sanitizeKey(k));
718
+ const effective = ((await this.authorize(c, {
719
+ type: "delete",
720
+ keys
721
+ })).keys ?? keys).map((k) => sanitizeKey(k));
722
+ return c.json(await this.storage.delete(effective, { concurrency: http.deleteConcurrency }));
723
+ } catch (e) {
724
+ return this.fail(c, e);
725
+ }
726
+ }
727
+ async download(c) {
728
+ try {
729
+ const key = sanitizeKey(c.req.query("key") ?? "");
730
+ const disp = c.req.query("disposition");
731
+ const ov = await this.authorize(c, {
732
+ type: "download",
733
+ key
734
+ });
735
+ const ekey = sanitizeKey(ov.key ?? key);
736
+ const name = ekey.slice(ekey.lastIndexOf("/") + 1);
737
+ if (http.download === "redirect" && this.storage.capabilities.signedUrl.supported) {
738
+ const url = await this.storage.url(ekey, {
739
+ expiresIn: clampExpiry(ov.expiresIn, http.defaultExpiresIn, http.maxExpiresIn),
740
+ responseContentDisposition: dispositionHeader(disp, name)
741
+ });
742
+ return c.redirect(url, 302);
743
+ }
744
+ const range = parseRange(c.req.header("range"));
745
+ const file = await this.storage.download(ekey, range ? { range } : void 0);
746
+ const headers = new Headers({
747
+ "content-type": file.type,
748
+ "content-length": String(file.size),
749
+ "content-disposition": dispositionHeader(disp, file.name)
750
+ });
751
+ if (file.etag) headers.set("etag", file.etag);
752
+ if (range) {
753
+ headers.set("content-range", `bytes ${range.start}-${range.start + file.size - 1}/*`);
754
+ return new Response(file.stream(), {
755
+ status: 206,
756
+ headers
757
+ });
758
+ }
759
+ return new Response(file.stream(), {
760
+ status: 200,
761
+ headers
762
+ });
763
+ } catch (e) {
764
+ return this.fail(c, e);
765
+ }
766
+ }
767
+ async signDownload(c) {
768
+ try {
769
+ const b = await c.req.json();
770
+ const ov = await this.authorize(c, {
771
+ type: "download",
772
+ key: b.key
773
+ });
774
+ const key = sanitizeKey(ov.key ?? b.key);
775
+ const expiresIn = clampExpiry(ov.expiresIn ?? b.expiresIn, http.defaultExpiresIn, http.maxExpiresIn);
776
+ const url = await this.storage.url(key, { expiresIn });
777
+ return c.json({
778
+ url,
779
+ expiresAt: Date.now() + expiresIn * 1e3
780
+ });
781
+ } catch (e) {
782
+ return this.fail(c, e);
783
+ }
784
+ }
785
+ };
786
+ __decorate([
787
+ Post("/sign-upload"),
788
+ __decorateParam(0, Req()),
789
+ __decorateMetadata("design:type", Function),
790
+ __decorateMetadata("design:paramtypes", [Object]),
791
+ __decorateMetadata("design:returntype", Promise)
792
+ ], StorageController.prototype, "signUpload", null);
793
+ __decorate([
794
+ Post("/multipart/create"),
795
+ __decorateParam(0, Req()),
796
+ __decorateMetadata("design:type", Function),
797
+ __decorateMetadata("design:paramtypes", [Object]),
798
+ __decorateMetadata("design:returntype", Promise)
799
+ ], StorageController.prototype, "multipartCreate", null);
800
+ __decorate([
801
+ Post("/multipart/sign-part"),
802
+ __decorateParam(0, Req()),
803
+ __decorateMetadata("design:type", Function),
804
+ __decorateMetadata("design:paramtypes", [Object]),
805
+ __decorateMetadata("design:returntype", Promise)
806
+ ], StorageController.prototype, "multipartSignPart", null);
807
+ __decorate([
808
+ Post("/multipart/complete"),
809
+ __decorateParam(0, Req()),
810
+ __decorateMetadata("design:type", Function),
811
+ __decorateMetadata("design:paramtypes", [Object]),
812
+ __decorateMetadata("design:returntype", Promise)
813
+ ], StorageController.prototype, "multipartComplete", null);
814
+ __decorate([
815
+ Post("/multipart/abort"),
816
+ __decorateParam(0, Req()),
817
+ __decorateMetadata("design:type", Function),
818
+ __decorateMetadata("design:paramtypes", [Object]),
819
+ __decorateMetadata("design:returntype", Promise)
820
+ ], StorageController.prototype, "multipartAbort", null);
821
+ __decorate([
822
+ Get("/list"),
823
+ __decorateParam(0, Req()),
824
+ __decorateMetadata("design:type", Function),
825
+ __decorateMetadata("design:paramtypes", [Object]),
826
+ __decorateMetadata("design:returntype", Promise)
827
+ ], StorageController.prototype, "list", null);
828
+ __decorate([
829
+ Get("/head"),
830
+ __decorateParam(0, Req()),
831
+ __decorateMetadata("design:type", Function),
832
+ __decorateMetadata("design:paramtypes", [Object]),
833
+ __decorateMetadata("design:returntype", Promise)
834
+ ], StorageController.prototype, "head", null);
835
+ __decorate([
836
+ Post("/delete"),
837
+ __decorateParam(0, Req()),
838
+ __decorateMetadata("design:type", Function),
839
+ __decorateMetadata("design:paramtypes", [Object]),
840
+ __decorateMetadata("design:returntype", Promise)
841
+ ], StorageController.prototype, "delete", null);
842
+ __decorate([
843
+ Get("/download"),
844
+ __decorateParam(0, Req()),
845
+ __decorateMetadata("design:type", Function),
846
+ __decorateMetadata("design:paramtypes", [Object]),
847
+ __decorateMetadata("design:returntype", Promise)
848
+ ], StorageController.prototype, "download", null);
849
+ __decorate([
850
+ Post("/sign-download"),
851
+ __decorateParam(0, Req()),
852
+ __decorateMetadata("design:type", Function),
853
+ __decorateMetadata("design:paramtypes", [Object]),
854
+ __decorateMetadata("design:returntype", Promise)
855
+ ], StorageController.prototype, "signDownload", null);
856
+ StorageController = __decorate([
857
+ Controller(basePath),
858
+ Injectable(),
859
+ __decorateParam(0, Inject(serviceToken)),
860
+ __decorateMetadata("design:paramtypes", [Object])
861
+ ], StorageController);
862
+ return StorageController;
863
+ }
864
+ //#endregion
865
+ //#region src/storage.service.ts
866
+ /**
867
+ * Build the real driver on first property access and delegate to it. This is
868
+ * the edge-binding-safe seam: the driver factory (which may read Cloudflare
869
+ * bindings / `c.env` secrets) does not run at module load — only on the first
870
+ * storage operation, which happens at request time.
871
+ *
872
+ * Functions are bound to the built driver so class-based drivers keep `this`.
873
+ */
874
+ function lazyDriver(build) {
875
+ let built;
876
+ const ensure = () => built ??= build();
877
+ return new Proxy({}, {
878
+ get(_target, prop) {
879
+ const driver = ensure();
880
+ const value = driver[prop];
881
+ return typeof value === "function" ? value.bind(driver) : value;
882
+ },
883
+ has(_target, prop) {
884
+ return prop in ensure();
885
+ }
886
+ });
887
+ }
888
+ let StorageService = class StorageService {
889
+ storage;
890
+ name;
891
+ constructor(build, options) {
892
+ this.name = options.name;
893
+ this.storage = createStorage({
894
+ driver: lazyDriver(build),
895
+ prefix: options.prefix,
896
+ readonly: options.readonly,
897
+ hooks: options.hooks
898
+ });
899
+ }
900
+ upload(key, body, opts) {
901
+ return this.storage.upload(key, body, opts);
902
+ }
903
+ download(key, opts) {
904
+ return this.storage.download(key, opts);
905
+ }
906
+ head(key, opts) {
907
+ return this.storage.head(key, opts);
908
+ }
909
+ exists(key, opts) {
910
+ return this.storage.exists(key, opts);
911
+ }
912
+ delete(key, opts) {
913
+ return this.storage.delete(key, opts);
914
+ }
915
+ deleteMany(keys, opts) {
916
+ return this.storage.delete(keys, opts);
917
+ }
918
+ copy(from, to, opts) {
919
+ return this.storage.copy(from, to, opts);
920
+ }
921
+ move(from, to, opts) {
922
+ return this.storage.move(from, to, opts);
923
+ }
924
+ list(opts) {
925
+ return this.storage.list(opts);
926
+ }
927
+ url(key, opts) {
928
+ return this.storage.url(key, opts);
929
+ }
930
+ signedUploadUrl(key, opts) {
931
+ return this.storage.signedUploadUrl(key, opts);
932
+ }
933
+ };
934
+ StorageService = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [Function, Object])], StorageService);
935
+ //#endregion
936
+ //#region src/storage.tokens.ts
937
+ /** Bucket name used when a registration omits `name`. */
938
+ const DEFAULT_STORAGE_NAME = "default";
939
+ const builderTokens = /* @__PURE__ */ new Map();
940
+ const serviceTokens = /* @__PURE__ */ new Map();
941
+ /** Internal token holding the lazy driver-construction thunk for `name`. */
942
+ function storageDriverBuilder(name) {
943
+ let token = builderTokens.get(name);
944
+ if (!token) {
945
+ token = new InjectionToken(`vela.storage.Builder:${name}`);
946
+ builderTokens.set(name, token);
947
+ }
948
+ return token;
949
+ }
950
+ /**
951
+ * Token that resolves to the {@link StorageService} for a named bucket.
952
+ * The default bucket uses the `StorageService` class token directly (see
953
+ * `@InjectStorage()`); named buckets use this.
954
+ */
955
+ function storageToken(name) {
956
+ let token = serviceTokens.get(name);
957
+ if (!token) {
958
+ token = new InjectionToken(`vela.storage.Service:${name}`);
959
+ serviceTokens.set(name, token);
960
+ }
961
+ return token;
962
+ }
963
+ //#endregion
964
+ //#region src/storage.module.ts
965
+ /**
966
+ * The stable, identifying subset of the HTTP options folded into the instance
967
+ * key — matches what `buildControllers` bakes into the mounted controller so
968
+ * two registrations that differ only in HTTP behavior get distinct keys.
969
+ */
970
+ function httpKeyPart(http) {
971
+ if (!http || http.mountController === false) return {};
972
+ return {
973
+ basePath: http.basePath ?? "/api/storage",
974
+ download: http.download ?? "redirect",
975
+ defaultPolicy: http.defaultPolicy ?? "deny"
976
+ };
977
+ }
978
+ function buildControllers(name, serviceToken, http) {
979
+ if (!http || http.mountController === false) return [];
980
+ const resolved = {
981
+ driverName: name,
982
+ authorize: http.authorize,
983
+ defaultPolicy: http.defaultPolicy ?? "deny",
984
+ download: http.download ?? "redirect",
985
+ defaultExpiresIn: http.defaultExpiresIn ?? 900,
986
+ maxExpiresIn: http.maxExpiresIn ?? 3600,
987
+ maxUploadSize: http.maxUploadSize,
988
+ maxListLimit: http.maxListLimit ?? 1e3,
989
+ deleteConcurrency: http.deleteConcurrency ?? 8
990
+ };
991
+ return [createStorageController(http.basePath ?? "/api/storage", serviceToken, resolved)];
992
+ }
993
+ const { ConfigurableModuleClass } = defineModule({
994
+ name: "Storage",
995
+ key: (o) => stableHash({
996
+ name: o.name ?? "default",
997
+ prefix: o.prefix ?? "",
998
+ readonly: !!o.readonly,
999
+ hooks: !!o.hooks,
1000
+ ...httpKeyPart(o.http)
1001
+ }),
1002
+ setup: ({ OPTIONS, options }) => {
1003
+ const name = options.name ?? "default";
1004
+ const builderToken = storageDriverBuilder(name);
1005
+ const serviceToken = name === "default" ? StorageService : storageToken(name);
1006
+ const svcOptions = {
1007
+ name,
1008
+ prefix: options.prefix,
1009
+ readonly: options.readonly,
1010
+ hooks: options.hooks
1011
+ };
1012
+ return {
1013
+ providers: [lazyProvider({
1014
+ provide: builderToken,
1015
+ inject: [OPTIONS],
1016
+ useFactory: (o) => o.driver()
1017
+ }), {
1018
+ provide: serviceToken,
1019
+ useFactory: (build) => new StorageService(build, svcOptions),
1020
+ inject: [builderToken]
1021
+ }],
1022
+ controllers: buildControllers(name, serviceToken, options.http),
1023
+ exports: [serviceToken, builderToken]
1024
+ };
1025
+ }
1026
+ });
1027
+ var StorageModule = class StorageModule {
1028
+ /** Synchronous registration — the driver is provided directly. */
1029
+ static forRoot(options) {
1030
+ const { driver, ...rest } = options;
1031
+ return {
1032
+ ...ConfigurableModuleClass.forRoot({
1033
+ ...rest,
1034
+ driver: () => driver
1035
+ }),
1036
+ module: StorageModule
1037
+ };
1038
+ }
1039
+ /** Deferred / DI-driven registration — `useFactory` runs lazily on first use. */
1040
+ static forRootAsync(options) {
1041
+ const { useFactory, inject, imports, key, ...structural } = options;
1042
+ return {
1043
+ ...ConfigurableModuleClass.forRootAsync({
1044
+ imports,
1045
+ inject,
1046
+ key,
1047
+ ...structural,
1048
+ useFactory: (...deps) => ({ driver: () => useFactory(...deps) })
1049
+ }),
1050
+ module: StorageModule
1051
+ };
1052
+ }
1053
+ };
1054
+ //#endregion
1055
+ //#region src/decorators/inject-storage.decorator.ts
1056
+ /**
1057
+ * Inject the {@link StorageService} for a named bucket.
1058
+ *
1059
+ * ```ts
1060
+ * class UploadService {
1061
+ * constructor(
1062
+ * @InjectStorage() private readonly uploads: StorageService, // default bucket
1063
+ * @InjectStorage('backups') private readonly backups: StorageService, // named bucket
1064
+ * ) {}
1065
+ * }
1066
+ * ```
1067
+ *
1068
+ * The default bucket resolves the `StorageService` class token directly (so
1069
+ * plain `@Inject(StorageService)` and `Test.overrideProvider(StorageService)`
1070
+ * keep working); named buckets resolve their per-name token.
1071
+ */
1072
+ function InjectStorage(name = DEFAULT_STORAGE_NAME) {
1073
+ return Inject(name === "default" ? StorageService : storageToken(name));
1074
+ }
1075
+ //#endregion
1076
+ export { DEFAULT_STORAGE_NAME, InjectStorage, Storage, StorageError, StorageModule, StorageService, createStorage, createStorageController, isAbort, isSafeKey, joinKey, lazyDriver, normalizePrefix, sanitizeKey, storageDriverBuilder, storageToken, stripPrefix };
1077
+
1078
+ //# sourceMappingURL=index.js.map