@velajs/storage 0.3.0 → 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 -404
  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 -380
  88. package/dist/storage.error.d.ts +0 -28
  89. package/dist/storage.error.js +0 -46
  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
@@ -1,443 +0,0 @@
1
- import { byteLengthOf, chunkStream, countingStream, isStream, toStream } from "./internal/body.js";
2
- import { normalizeRetry, runWithRetry } from "./internal/retry.js";
3
- import { joinKey, normalizePrefix, sanitizeKey, stripPrefix } from "./object-key.js";
4
- import { StorageError } from "./storage.error.js";
5
- const DEFAULT_PART_SIZE = 5 * 1024 * 1024; // 5 MiB (S3 minimum non-final part)
6
- const DEFAULT_CONCURRENCY = 4;
7
- // Known-length bodies larger than this are routed through multipart so we never
8
- // buffer a whole large object in memory (aws4fetch must hash the payload).
9
- const MULTIPART_THRESHOLD = 16 * 1024 * 1024;
10
- /**
11
- * The consumer-facing storage handle. Wraps a {@link StorageDriver} with
12
- * capability-gating (throws before any driver call), retry/timeout/abort,
13
- * prefix scoping, generic `move`/`deleteMany` fallbacks, multipart
14
- * orchestration, progress synthesis, and observability hooks.
15
- *
16
- * Framework-free — `StorageService` builds and caches one of these per bucket.
17
- */ export class Storage {
18
- #driver;
19
- #prefix;
20
- #readonly;
21
- #opts;
22
- constructor(opts){
23
- this.#opts = opts;
24
- this.#driver = opts.driver;
25
- this.#prefix = normalizePrefix(opts.prefix);
26
- this.#readonly = opts.readonly ?? false;
27
- }
28
- get raw() {
29
- return this.#driver.raw;
30
- }
31
- get driver() {
32
- return this.#driver;
33
- }
34
- get capabilities() {
35
- const d = this.#driver;
36
- return {
37
- rangeRead: !!d.supportsRange,
38
- uploadProgress: !!d.reportsUploadProgress,
39
- delimiter: !!d.supportsDelimiter,
40
- metadata: !!d.supportsMetadata,
41
- cacheControl: !!d.supportsCacheControl,
42
- serverSideCopy: !!d.supportsServerSideCopy,
43
- multipart: typeof d.createMultipartUpload === 'function',
44
- signedMultipart: d.signedMultipart != null,
45
- signedUrl: d.signedUrl ?? {
46
- supported: false,
47
- upload: false
48
- }
49
- };
50
- }
51
- // ---- upload ------------------------------------------------------------
52
- async upload(key, body, opts) {
53
- this.#assertWritable();
54
- this.#assertUploadSupported(opts);
55
- const d = this.#driver;
56
- const path = this.#path(key);
57
- const len = byteLengthOf(body);
58
- const wantsMultipart = opts?.multipart != null && opts.multipart !== false;
59
- const unknownStream = isStream(body) && len === undefined;
60
- const bigKnown = len != null && len > MULTIPART_THRESHOLD;
61
- const canMultipart = typeof d.createMultipartUpload === 'function';
62
- if (wantsMultipart && !canMultipart) {
63
- throw new StorageError('Unsupported', `${d.name}: multipart not supported`);
64
- }
65
- if ((wantsMultipart || unknownStream || bigKnown) && canMultipart) {
66
- const result = await this.#exec('upload', key, false, opts, ()=>this.#putMultipart(path, body, opts));
67
- return {
68
- ...result,
69
- key
70
- };
71
- }
72
- const streaming = isStream(body);
73
- let outBody = body;
74
- if (opts?.onProgress && !d.reportsUploadProgress && streaming) {
75
- outBody = countingStream(body, (loaded)=>opts.onProgress({
76
- loaded,
77
- total: len
78
- }));
79
- }
80
- const result = await this.#exec('upload', key, !streaming, opts, (signal)=>d.upload(path, outBody, {
81
- ...opts,
82
- signal
83
- }));
84
- if (opts?.onProgress && !d.reportsUploadProgress && !streaming && len != null) {
85
- opts.onProgress({
86
- loaded: len,
87
- total: len
88
- });
89
- }
90
- return {
91
- ...result,
92
- key
93
- };
94
- }
95
- async #putMultipart(path, body, opts) {
96
- const mp = typeof opts?.multipart === 'object' ? opts.multipart : {};
97
- const partSize = mp.partSize ?? DEFAULT_PART_SIZE;
98
- const concurrency = Math.max(1, mp.concurrency ?? DEFAULT_CONCURRENCY);
99
- const total = byteLengthOf(body);
100
- const upload = await this.#driver.createMultipartUpload(path, {
101
- contentType: opts?.contentType,
102
- cacheControl: opts?.cacheControl,
103
- metadata: opts?.metadata,
104
- signal: opts?.signal
105
- });
106
- try {
107
- const parts = [];
108
- let uploadedBytes = 0;
109
- let partNumber = 0;
110
- const inflight = new Set();
111
- for await (const chunk of chunkStream(toStream(body), partSize)){
112
- partNumber += 1;
113
- const n = partNumber;
114
- const size = chunk.byteLength;
115
- const task = (async ()=>{
116
- const up = await upload.uploadPart(n, chunk, {
117
- signal: opts?.signal
118
- });
119
- parts[n - 1] = {
120
- partNumber: n,
121
- etag: up.etag,
122
- size: up.size ?? size
123
- };
124
- uploadedBytes += size;
125
- opts?.onProgress?.({
126
- loaded: uploadedBytes,
127
- total
128
- });
129
- })();
130
- const tracked = task.finally(()=>inflight.delete(tracked));
131
- inflight.add(tracked);
132
- if (inflight.size >= concurrency) await Promise.race(inflight);
133
- }
134
- await Promise.all(inflight);
135
- return await upload.complete(parts, {
136
- signal: opts?.signal
137
- });
138
- } catch (e) {
139
- await upload.abort().catch(()=>{});
140
- throw StorageError.wrap(e);
141
- }
142
- }
143
- // ---- read / metadata ---------------------------------------------------
144
- async download(key, opts) {
145
- if (opts?.range) this.#assertRange();
146
- const file = await this.#exec('download', key, true, opts, (signal)=>this.#driver.download(this.#path(key), {
147
- ...opts,
148
- signal
149
- }));
150
- return this.#relabel(file, key);
151
- }
152
- async head(key, opts) {
153
- const file = await this.#exec('head', key, true, opts, (signal)=>this.#driver.head(this.#path(key), {
154
- ...opts,
155
- signal
156
- }));
157
- return this.#relabel(file, key);
158
- }
159
- exists(key, opts) {
160
- return this.#exec('exists', key, true, opts, (signal)=>this.#driver.exists(this.#path(key), {
161
- ...opts,
162
- signal
163
- }));
164
- }
165
- async delete(keyOrKeys, opts) {
166
- this.#assertWritable();
167
- if (Array.isArray(keyOrKeys)) return this.#deleteMany(keyOrKeys, opts);
168
- const key = keyOrKeys;
169
- return this.#exec('delete', key, true, opts, (signal)=>this.#driver.delete(this.#path(key), {
170
- ...opts,
171
- signal
172
- }));
173
- }
174
- async #deleteMany(keys, opts) {
175
- const d = this.#driver;
176
- if (d.deleteMany) {
177
- const res = await this.#exec('deleteMany', undefined, true, opts, (signal)=>d.deleteMany(keys.map((k)=>this.#path(k)), {
178
- ...opts,
179
- signal
180
- }));
181
- return {
182
- deleted: res.deleted.map((k)=>this.#strip(k)),
183
- errors: res.errors?.map((e)=>({
184
- key: this.#strip(e.key),
185
- error: e.error
186
- }))
187
- };
188
- }
189
- // Generic bounded fan-out fallback.
190
- const concurrency = Math.max(1, opts?.concurrency ?? 8);
191
- const stopOnError = opts?.stopOnError ?? false;
192
- const deleted = [];
193
- const errors = [];
194
- let index = 0;
195
- let stopped = false;
196
- const worker = async ()=>{
197
- while(index < keys.length && !stopped){
198
- const key = keys[index++];
199
- try {
200
- await d.delete(this.#path(key), {
201
- signal: opts?.signal
202
- });
203
- deleted.push(key);
204
- } catch (e) {
205
- const error = StorageError.wrap(e);
206
- errors.push({
207
- key,
208
- error
209
- });
210
- if (stopOnError) {
211
- stopped = true;
212
- throw error;
213
- }
214
- }
215
- }
216
- };
217
- const runners = Array.from({
218
- length: Math.min(concurrency, keys.length)
219
- }, worker);
220
- if (stopOnError) await Promise.all(runners);
221
- else await Promise.allSettled(runners);
222
- return {
223
- deleted,
224
- errors: errors.length ? errors : undefined
225
- };
226
- }
227
- async copy(from, to, opts) {
228
- this.#assertWritable();
229
- return this.#exec('copy', to, true, opts, (signal)=>this.#driver.copy(this.#path(from), this.#path(to), {
230
- ...opts,
231
- signal
232
- }));
233
- }
234
- async move(from, to, opts) {
235
- this.#assertWritable();
236
- const f = this.#path(from);
237
- const t = this.#path(to);
238
- if (f === t) return;
239
- if (this.#driver.move) {
240
- await this.#exec('move', to, true, opts, (signal)=>this.#driver.move(f, t, {
241
- ...opts,
242
- signal
243
- }));
244
- return;
245
- }
246
- await this.#exec('move', to, true, opts, async (signal)=>{
247
- await this.#driver.copy(f, t, {
248
- ...opts,
249
- signal
250
- });
251
- await this.#driver.delete(f, {
252
- ...opts,
253
- signal
254
- });
255
- });
256
- }
257
- // ---- list --------------------------------------------------------------
258
- async list(opts) {
259
- if (opts?.delimiter) this.#assertDelimiter();
260
- const scopedPrefix = this.#listPrefix(opts?.prefix);
261
- const res = await this.#exec('list', undefined, true, opts, (signal)=>this.#driver.list({
262
- ...opts,
263
- prefix: scopedPrefix,
264
- signal
265
- }));
266
- return {
267
- items: res.items.map((f)=>this.#relabel(f, this.#strip(f.key))),
268
- prefixes: res.prefixes?.map((p)=>this.#strip(p)),
269
- cursor: res.cursor
270
- };
271
- }
272
- async *listAll(opts) {
273
- let cursor = opts?.cursor;
274
- do {
275
- const page = await this.list({
276
- ...opts,
277
- cursor
278
- });
279
- for (const item of page.items)yield item;
280
- cursor = page.cursor;
281
- }while (cursor)
282
- }
283
- // ---- URLs / presign ----------------------------------------------------
284
- async url(key, opts) {
285
- if (!this.#driver.signedUrl?.supported) {
286
- throw new StorageError('Unsupported', `${this.#driver.name}: url() not supported`);
287
- }
288
- return this.#exec('url', key, true, opts, (signal)=>this.#driver.url(this.#path(key), {
289
- ...opts,
290
- signal
291
- }));
292
- }
293
- async signedUploadUrl(key, opts) {
294
- this.#assertWritable();
295
- if (!this.#driver.signedUrl?.upload) {
296
- throw new StorageError('Unsupported', `${this.#driver.name}: signed uploads not supported`);
297
- }
298
- return this.#exec('signedUploadUrl', key, true, opts, (signal)=>this.#driver.signedUploadUrl(this.#path(key), {
299
- ...opts,
300
- signal
301
- }));
302
- }
303
- /** Prefix-bound view of the driver's presigned-multipart capability. */ get signedMultipart() {
304
- const cap = this.#driver.signedMultipart;
305
- if (!cap) return undefined;
306
- const path = (k)=>this.#path(k);
307
- return {
308
- create: (key, o)=>cap.create(path(key), o),
309
- signPart: (key, uploadId, partNumber, o)=>cap.signPart(path(key), uploadId, partNumber, o),
310
- complete: (key, uploadId, parts)=>cap.complete(path(key), uploadId, parts),
311
- abort: (key, uploadId)=>cap.abort(path(key), uploadId)
312
- };
313
- }
314
- // ---- low-level multipart ----------------------------------------------
315
- async createMultipartUpload(key, opts) {
316
- this.#assertWritable();
317
- if (typeof this.#driver.createMultipartUpload !== 'function') {
318
- throw new StorageError('Unsupported', `${this.#driver.name}: multipart not supported`);
319
- }
320
- return this.#driver.createMultipartUpload(this.#path(key), opts);
321
- }
322
- resumeMultipartUpload(key, uploadId) {
323
- this.#assertWritable();
324
- if (typeof this.#driver.resumeMultipartUpload !== 'function') {
325
- throw new StorageError('Unsupported', `${this.#driver.name}: multipart resume not supported`);
326
- }
327
- return this.#driver.resumeMultipartUpload(this.#path(key), uploadId);
328
- }
329
- // ---- ergonomics --------------------------------------------------------
330
- /** A key-bound handle over the common single-object operations. */ file(key) {
331
- return {
332
- key,
333
- upload: (body, o)=>this.upload(key, body, o),
334
- download: (o)=>this.download(key, o),
335
- head: (o)=>this.head(key, o),
336
- exists: (o)=>this.exists(key, o),
337
- delete: (o)=>this.delete(key, o),
338
- url: (o)=>this.url(key, o),
339
- signedUploadUrl: (o)=>this.signedUploadUrl(key, o)
340
- };
341
- }
342
- /** A read-only clone of this handle (mutations throw `ReadOnly`). */ readonly() {
343
- return new Storage({
344
- ...this.#opts,
345
- readonly: true
346
- });
347
- }
348
- // ---- internals ---------------------------------------------------------
349
- #path(key) {
350
- return joinKey(this.#prefix, sanitizeKey(key));
351
- }
352
- #strip(storedKey) {
353
- return stripPrefix(this.#prefix, storedKey);
354
- }
355
- #listPrefix(prefix) {
356
- if (!this.#prefix) return prefix;
357
- return prefix ? `${this.#prefix}/${prefix}` : `${this.#prefix}/`;
358
- }
359
- #relabel(file, logicalKey) {
360
- if (file.key === logicalKey) return file;
361
- const name = logicalKey.slice(logicalKey.lastIndexOf('/') + 1);
362
- return {
363
- key: logicalKey,
364
- name,
365
- size: file.size,
366
- type: file.type,
367
- lastModified: file.lastModified,
368
- etag: file.etag,
369
- metadata: file.metadata,
370
- arrayBuffer: ()=>file.arrayBuffer(),
371
- text: ()=>file.text(),
372
- blob: ()=>file.blob(),
373
- stream: ()=>file.stream()
374
- };
375
- }
376
- #assertWritable() {
377
- if (this.#readonly) throw new StorageError('ReadOnly', 'storage handle is read-only');
378
- }
379
- #assertUploadSupported(opts) {
380
- const d = this.#driver;
381
- if (opts?.metadata && Object.keys(opts.metadata).length > 0 && !d.supportsMetadata) {
382
- throw new StorageError('Unsupported', `${d.name}: metadata not supported`);
383
- }
384
- if (opts?.cacheControl && !d.supportsCacheControl) {
385
- throw new StorageError('Unsupported', `${d.name}: cacheControl not supported`);
386
- }
387
- }
388
- #assertRange() {
389
- if (!this.#driver.supportsRange) {
390
- throw new StorageError('Unsupported', `${this.#driver.name}: range reads not supported`);
391
- }
392
- }
393
- #assertDelimiter() {
394
- if (!this.#driver.supportsDelimiter) {
395
- throw new StorageError('Unsupported', `${this.#driver.name}: delimiter not supported`);
396
- }
397
- }
398
- async #exec(type, key, replayable, opts, fn) {
399
- const started = Date.now();
400
- const retries = replayable ? opts?.retries ?? this.#opts.retries : 0;
401
- const maxRetries = normalizeRetry(retries).max;
402
- const hooks = this.#opts.hooks;
403
- try {
404
- const result = await runWithRetry(fn, {
405
- retries,
406
- timeout: opts?.timeout ?? this.#opts.timeout,
407
- signal: opts?.signal ?? this.#opts.signal
408
- }, (info)=>hooks?.onRetry?.({
409
- type,
410
- key,
411
- attempt: info.attempt,
412
- maxRetries,
413
- delayMs: info.delayMs,
414
- error: info.error
415
- }));
416
- hooks?.onOperation?.({
417
- type,
418
- key,
419
- status: 'success',
420
- durationMs: Date.now() - started
421
- });
422
- return result;
423
- } catch (e) {
424
- const error = StorageError.wrap(e);
425
- hooks?.onError?.({
426
- type,
427
- key,
428
- error
429
- });
430
- hooks?.onOperation?.({
431
- type,
432
- key,
433
- status: 'error',
434
- durationMs: Date.now() - started,
435
- error
436
- });
437
- throw error;
438
- }
439
- }
440
- }
441
- export function createStorage(opts) {
442
- return new Storage(opts);
443
- }
@@ -1,46 +0,0 @@
1
- import { type DynamicModule, type InferTokens, type Token } from '@velajs/vela';
2
- import type { StorageDriver, StorageHooks } from './storage.types';
3
- import type { StorageAuthorizer } from './http/authorizer.types';
4
- /** Options for mounting the HTTP upload/download controller for a bucket. */
5
- export interface StorageHttpOptions {
6
- /** Mount path (relative to vela's globalPrefix). Default `/api/storage`. */
7
- basePath?: string;
8
- /** Fine-grained per-action authorizer (deny / allow / allow-with-overrides). */
9
- authorize?: StorageAuthorizer;
10
- /** Behavior when no `authorize` is set. Default `'deny'`. */
11
- defaultPolicy?: 'deny' | 'allow';
12
- /** `redirect` to a signed URL (default) or `proxy` bytes through the Worker. */
13
- download?: 'redirect' | 'proxy';
14
- /** Set `false` to configure options without mounting the controller. Default mounts. */
15
- mountController?: boolean;
16
- defaultExpiresIn?: number;
17
- maxExpiresIn?: number;
18
- maxUploadSize?: number;
19
- maxListLimit?: number;
20
- deleteConcurrency?: number;
21
- }
22
- export interface StorageModuleOptions {
23
- driver: StorageDriver;
24
- name?: string;
25
- prefix?: string;
26
- readonly?: boolean;
27
- hooks?: StorageHooks;
28
- http?: StorageHttpOptions;
29
- }
30
- export interface StorageModuleAsyncOptions<Inject extends readonly Token<unknown>[] = readonly Token<unknown>[]> {
31
- inject?: Inject;
32
- imports?: DynamicModule['imports'];
33
- useFactory: (...deps: InferTokens<Inject>) => StorageDriver;
34
- name?: string;
35
- prefix?: string;
36
- readonly?: boolean;
37
- hooks?: StorageHooks;
38
- http?: StorageHttpOptions;
39
- key?: string;
40
- }
41
- export declare class StorageModule {
42
- /** Synchronous registration — the driver is provided directly. */
43
- static forRoot(options: StorageModuleOptions): DynamicModule;
44
- /** Deferred / DI-driven registration — `useFactory` runs lazily on first use. */
45
- static forRootAsync<const Inject extends readonly Token<unknown>[] = readonly Token<unknown>[]>(options: StorageModuleAsyncOptions<Inject>): DynamicModule;
46
- }
@@ -1,113 +0,0 @@
1
- import { defineModule, lazyProvider, stableHash } from "@velajs/vela";
2
- import { createStorageController } from "./storage.controller.js";
3
- import { StorageService } from "./storage.service.js";
4
- import { DEFAULT_STORAGE_NAME, storageDriverBuilder, storageToken } from "./storage.tokens.js";
5
- /**
6
- * The stable, identifying subset of the HTTP options folded into the instance
7
- * key — matches what `buildControllers` bakes into the mounted controller so
8
- * two registrations that differ only in HTTP behavior get distinct keys.
9
- */ function httpKeyPart(http) {
10
- if (!http || http.mountController === false) return {};
11
- return {
12
- basePath: http.basePath ?? '/api/storage',
13
- download: http.download ?? 'redirect',
14
- defaultPolicy: http.defaultPolicy ?? 'deny'
15
- };
16
- }
17
- function buildControllers(name, serviceToken, http) {
18
- if (!http || http.mountController === false) return [];
19
- const resolved = {
20
- driverName: name,
21
- authorize: http.authorize,
22
- defaultPolicy: http.defaultPolicy ?? 'deny',
23
- download: http.download ?? 'redirect',
24
- defaultExpiresIn: http.defaultExpiresIn ?? 900,
25
- maxExpiresIn: http.maxExpiresIn ?? 3600,
26
- maxUploadSize: http.maxUploadSize,
27
- maxListLimit: http.maxListLimit ?? 1000,
28
- deleteConcurrency: http.deleteConcurrency ?? 8
29
- };
30
- const basePath = http.basePath ?? '/api/storage';
31
- return [
32
- createStorageController(basePath, serviceToken, resolved)
33
- ];
34
- }
35
- const { ConfigurableModuleClass } = defineModule({
36
- name: 'Storage',
37
- // Value-shaped identity only: never the (stateful) driver thunk. Same
38
- // name+prefix+readonly+hooks+http dedups (HMR-idempotent); a distinct bucket
39
- // name coexists.
40
- key: (o)=>stableHash({
41
- name: o.name ?? DEFAULT_STORAGE_NAME,
42
- prefix: o.prefix ?? '',
43
- readonly: !!o.readonly,
44
- hooks: !!o.hooks,
45
- ...httpKeyPart(o.http)
46
- }),
47
- setup: ({ OPTIONS, options })=>{
48
- const name = options.name ?? DEFAULT_STORAGE_NAME;
49
- const builderToken = storageDriverBuilder(name);
50
- const serviceToken = name === DEFAULT_STORAGE_NAME ? StorageService : storageToken(name);
51
- const svcOptions = {
52
- name,
53
- prefix: options.prefix,
54
- readonly: options.readonly,
55
- hooks: options.hooks
56
- };
57
- return {
58
- providers: [
59
- // Deferred, memoized driver construction — the seam that keeps
60
- // Cloudflare bindings / `c.env` reads out of module load.
61
- lazyProvider({
62
- provide: builderToken,
63
- inject: [
64
- OPTIONS
65
- ],
66
- useFactory: (o)=>o.driver()
67
- }),
68
- {
69
- provide: serviceToken,
70
- useFactory: (build)=>new StorageService(build, svcOptions),
71
- inject: [
72
- builderToken
73
- ]
74
- }
75
- ],
76
- controllers: buildControllers(name, serviceToken, options.http),
77
- exports: [
78
- serviceToken,
79
- builderToken
80
- ]
81
- };
82
- }
83
- });
84
- export class StorageModule {
85
- /** Synchronous registration — the driver is provided directly. */ static forRoot(options) {
86
- const { driver, ...rest } = options;
87
- return {
88
- ...ConfigurableModuleClass.forRoot({
89
- ...rest,
90
- driver: ()=>driver
91
- }),
92
- module: StorageModule
93
- };
94
- }
95
- /** Deferred / DI-driven registration — `useFactory` runs lazily on first use. */ static forRootAsync(options) {
96
- const { useFactory, inject, imports, key, ...structural } = options;
97
- return {
98
- ...ConfigurableModuleClass.forRootAsync({
99
- imports,
100
- inject,
101
- key,
102
- ...structural,
103
- // Wrap the caller's driver factory in a thunk so resolving the options
104
- // token (at bootstrap) does NOT build the driver — only the first
105
- // storage operation does.
106
- useFactory: (...deps)=>({
107
- driver: ()=>useFactory(...deps)
108
- })
109
- }),
110
- module: StorageModule
111
- };
112
- }
113
- }
@@ -1,42 +0,0 @@
1
- import { Storage } from './storage.facade';
2
- import type { Body, DeleteManyOptions, DeleteManyResult, DownloadOptions, ListOptions, ListResult, OperationOptions, SignedUpload, SignUploadOptions, StorageDriver, StorageHooks, StoredFile, UploadOptions, UploadResult, UrlOptions } from './storage.types';
3
- /**
4
- * Build the real driver on first property access and delegate to it. This is
5
- * the edge-binding-safe seam: the driver factory (which may read Cloudflare
6
- * bindings / `c.env` secrets) does not run at module load — only on the first
7
- * storage operation, which happens at request time.
8
- *
9
- * Functions are bound to the built driver so class-based drivers keep `this`.
10
- */
11
- export declare function lazyDriver(build: () => StorageDriver): StorageDriver;
12
- export interface StorageServiceOptions {
13
- name: string;
14
- prefix?: string;
15
- readonly?: boolean;
16
- hooks?: StorageHooks;
17
- }
18
- /**
19
- * The injectable consumers reach for. Wraps one bucket's {@link Storage}
20
- * facade (lazy driver construction), plus thin forwarders for the common
21
- * single-object operations so `service.upload(...)` works without reaching
22
- * through `.storage`.
23
- *
24
- * For multiple buckets, register `StorageModule.forRoot({ name })` per bucket
25
- * and inject with `@InjectStorage(name)`.
26
- */
27
- export declare class StorageService {
28
- readonly storage: Storage;
29
- readonly name: string;
30
- constructor(build: () => StorageDriver, options: StorageServiceOptions);
31
- upload(key: string, body: Body, opts?: UploadOptions): Promise<UploadResult>;
32
- download(key: string, opts?: DownloadOptions): Promise<StoredFile>;
33
- head(key: string, opts?: OperationOptions): Promise<StoredFile>;
34
- exists(key: string, opts?: OperationOptions): Promise<boolean>;
35
- delete(key: string, opts?: OperationOptions): Promise<void>;
36
- deleteMany(keys: string[], opts?: DeleteManyOptions): Promise<DeleteManyResult>;
37
- copy(from: string, to: string, opts?: OperationOptions): Promise<void>;
38
- move(from: string, to: string, opts?: OperationOptions): Promise<void>;
39
- list(opts?: ListOptions): Promise<ListResult>;
40
- url(key: string, opts?: UrlOptions): Promise<string>;
41
- signedUploadUrl(key: string, opts: SignUploadOptions): Promise<SignedUpload>;
42
- }