@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
package/CHANGELOG.md CHANGED
@@ -1,10 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.2
4
+
5
+ ### Patch Changes
6
+
7
+ - cb3140a: Modernize the package build, validation, and release toolchain.
8
+
3
9
  ## 0.2.0 (2026-07-04)
4
10
 
5
11
  - Rebuilt on vela 1.11 `defineModule` + `lazyProvider` (hand-rolled forRoot/forRootAsync deleted; public API byte-identical). Requires `@velajs/vela >=1.11.0`.
6
12
 
7
-
8
13
  ## 0.1.0 (unreleased)
9
14
 
10
15
  - Initial release: edge-first, driver-based object/file storage for Vela.
@@ -0,0 +1,93 @@
1
+ //#region src/internal/body.ts
2
+ const encoder = new TextEncoder();
3
+ /** Byte length when known synchronously; `undefined` for unknown-length streams. */
4
+ function byteLengthOf(body) {
5
+ if (typeof body === "string") return encoder.encode(body).byteLength;
6
+ if (body instanceof Blob) return body.size;
7
+ if (body instanceof ArrayBuffer) return body.byteLength;
8
+ if (ArrayBuffer.isView(body)) return body.byteLength;
9
+ }
10
+ /** True for the unknown-length stream case. */
11
+ function isStream(body) {
12
+ return body instanceof ReadableStream;
13
+ }
14
+ /** Buffer any body into bytes (used for hashing / part chunking). */
15
+ async function toBytes(body) {
16
+ if (typeof body === "string") return encoder.encode(body);
17
+ if (body instanceof Uint8Array) return body;
18
+ if (body instanceof ArrayBuffer) return new Uint8Array(body);
19
+ if (ArrayBuffer.isView(body)) {
20
+ const slice = body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength);
21
+ return new Uint8Array(slice);
22
+ }
23
+ if (body instanceof Blob) return new Uint8Array(await body.arrayBuffer());
24
+ return new Uint8Array(await new Response(body).arrayBuffer());
25
+ }
26
+ /** View any body as a byte stream without buffering when it already is one. */
27
+ function toStream(body) {
28
+ if (body instanceof ReadableStream) return body;
29
+ return new Response(body).body ?? emptyStream();
30
+ }
31
+ function emptyStream() {
32
+ return new ReadableStream({ start(controller) {
33
+ controller.close();
34
+ } });
35
+ }
36
+ /** Wrap a stream so `onLoaded` is called with the cumulative byte count. */
37
+ function countingStream(src, onLoaded) {
38
+ let loaded = 0;
39
+ const reader = src.getReader();
40
+ return new ReadableStream({
41
+ async pull(controller) {
42
+ const { done, value } = await reader.read();
43
+ if (done) {
44
+ controller.close();
45
+ return;
46
+ }
47
+ loaded += value.byteLength;
48
+ onLoaded(loaded);
49
+ controller.enqueue(value);
50
+ },
51
+ cancel(reason) {
52
+ return reader.cancel(reason);
53
+ }
54
+ });
55
+ }
56
+ /** Concatenate ordered byte chunks into one buffer. */
57
+ function concatChunks(chunks) {
58
+ let total = 0;
59
+ for (const c of chunks) total += c.byteLength;
60
+ const out = new Uint8Array(total);
61
+ let offset = 0;
62
+ for (const c of chunks) {
63
+ out.set(c, offset);
64
+ offset += c.byteLength;
65
+ }
66
+ return out;
67
+ }
68
+ /**
69
+ * Yield fixed-size (`partSize`) chunks from a byte stream, buffering only one
70
+ * part at a time. The final chunk may be smaller. Empty input yields nothing.
71
+ */
72
+ async function* chunkStream(src, partSize) {
73
+ const reader = src.getReader();
74
+ let buffer = /* @__PURE__ */ new Uint8Array(0);
75
+ try {
76
+ for (;;) {
77
+ const { done, value } = await reader.read();
78
+ if (done) break;
79
+ buffer = buffer.length === 0 ? value : concatChunks([buffer, value]);
80
+ while (buffer.byteLength >= partSize) {
81
+ yield buffer.subarray(0, partSize);
82
+ buffer = buffer.subarray(partSize);
83
+ }
84
+ }
85
+ if (buffer.byteLength > 0) yield buffer;
86
+ } finally {
87
+ reader.releaseLock();
88
+ }
89
+ }
90
+ //#endregion
91
+ export { isStream as a, countingStream as i, chunkStream as n, toBytes as o, concatChunks as r, toStream as s, byteLengthOf as t };
92
+
93
+ //# sourceMappingURL=body-BMUcBeQ0.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"body-BMUcBeQ0.js","names":[],"sources":["../src/internal/body.ts"],"sourcesContent":["import type { Body } from '../storage.types';\n\nconst encoder = new TextEncoder();\n\n/** Byte length when known synchronously; `undefined` for unknown-length streams. */\nexport function byteLengthOf(body: Body): number | undefined {\n if (typeof body === 'string') return encoder.encode(body).byteLength;\n if (body instanceof Blob) return body.size; // File extends Blob\n if (body instanceof ArrayBuffer) return body.byteLength;\n if (ArrayBuffer.isView(body)) return body.byteLength;\n return undefined; // ReadableStream\n}\n\n/** True for the unknown-length stream case. */\nexport function isStream(body: Body): body is ReadableStream<Uint8Array> {\n return body instanceof ReadableStream;\n}\n\n/** Buffer any body into bytes (used for hashing / part chunking). */\nexport async function toBytes(body: Body): Promise<Uint8Array> {\n if (typeof body === 'string') return encoder.encode(body);\n if (body instanceof Uint8Array) return body;\n if (body instanceof ArrayBuffer) return new Uint8Array(body);\n if (ArrayBuffer.isView(body)) {\n // Copy into a fresh ArrayBuffer-backed view (the source buffer is\n // ArrayBufferLike, which may be a SharedArrayBuffer).\n const slice = body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength);\n return new Uint8Array(slice as ArrayBuffer);\n }\n if (body instanceof Blob) return new Uint8Array(await body.arrayBuffer());\n return new Uint8Array(await new Response(body).arrayBuffer()); // ReadableStream\n}\n\n/** View any body as a byte stream without buffering when it already is one. */\nexport function toStream(body: Body): ReadableStream<Uint8Array> {\n if (body instanceof ReadableStream) return body;\n const res = new Response(body as BodyInit);\n return res.body ?? emptyStream();\n}\n\nfunction emptyStream(): ReadableStream<Uint8Array> {\n return new ReadableStream<Uint8Array>({\n start(controller) {\n controller.close();\n },\n });\n}\n\n/** Wrap a stream so `onLoaded` is called with the cumulative byte count. */\nexport function countingStream(\n src: ReadableStream<Uint8Array>,\n onLoaded: (loaded: number) => void,\n): ReadableStream<Uint8Array> {\n let loaded = 0;\n const reader = src.getReader();\n return new ReadableStream<Uint8Array>({\n async pull(controller) {\n const { done, value } = await reader.read();\n if (done) {\n controller.close();\n return;\n }\n loaded += value.byteLength;\n onLoaded(loaded);\n controller.enqueue(value);\n },\n cancel(reason) {\n return reader.cancel(reason);\n },\n });\n}\n\n/** Concatenate ordered byte chunks into one buffer. */\nexport function concatChunks(chunks: Uint8Array[]): Uint8Array {\n let total = 0;\n for (const c of chunks) total += c.byteLength;\n const out = new Uint8Array(total);\n let offset = 0;\n for (const c of chunks) {\n out.set(c, offset);\n offset += c.byteLength;\n }\n return out;\n}\n\n/**\n * Yield fixed-size (`partSize`) chunks from a byte stream, buffering only one\n * part at a time. The final chunk may be smaller. Empty input yields nothing.\n */\nexport async function* chunkStream(\n src: ReadableStream<Uint8Array>,\n partSize: number,\n): AsyncGenerator<Uint8Array> {\n const reader = src.getReader();\n let buffer: Uint8Array = new Uint8Array(0);\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer = buffer.length === 0 ? value : concatChunks([buffer, value]);\n while (buffer.byteLength >= partSize) {\n yield buffer.subarray(0, partSize);\n buffer = buffer.subarray(partSize);\n }\n }\n if (buffer.byteLength > 0) yield buffer;\n } finally {\n reader.releaseLock();\n }\n}\n"],"mappings":";AAEA,MAAM,UAAU,IAAI,YAAY;;AAGhC,SAAgB,aAAa,MAAgC;CAC3D,IAAI,OAAO,SAAS,UAAU,OAAO,QAAQ,OAAO,IAAI,CAAC,CAAC;CAC1D,IAAI,gBAAgB,MAAM,OAAO,KAAK;CACtC,IAAI,gBAAgB,aAAa,OAAO,KAAK;CAC7C,IAAI,YAAY,OAAO,IAAI,GAAG,OAAO,KAAK;AAE5C;;AAGA,SAAgB,SAAS,MAAgD;CACvE,OAAO,gBAAgB;AACzB;;AAGA,eAAsB,QAAQ,MAAiC;CAC7D,IAAI,OAAO,SAAS,UAAU,OAAO,QAAQ,OAAO,IAAI;CACxD,IAAI,gBAAgB,YAAY,OAAO;CACvC,IAAI,gBAAgB,aAAa,OAAO,IAAI,WAAW,IAAI;CAC3D,IAAI,YAAY,OAAO,IAAI,GAAG;EAG5B,MAAM,QAAQ,KAAK,OAAO,MAAM,KAAK,YAAY,KAAK,aAAa,KAAK,UAAU;EAClF,OAAO,IAAI,WAAW,KAAoB;CAC5C;CACA,IAAI,gBAAgB,MAAM,OAAO,IAAI,WAAW,MAAM,KAAK,YAAY,CAAC;CACxE,OAAO,IAAI,WAAW,MAAM,IAAI,SAAS,IAAI,CAAC,CAAC,YAAY,CAAC;AAC9D;;AAGA,SAAgB,SAAS,MAAwC;CAC/D,IAAI,gBAAgB,gBAAgB,OAAO;CAE3C,OAAO,IADS,SAAS,IAChB,CAAC,CAAC,QAAQ,YAAY;AACjC;AAEA,SAAS,cAA0C;CACjD,OAAO,IAAI,eAA2B,EACpC,MAAM,YAAY;EAChB,WAAW,MAAM;CACnB,EACF,CAAC;AACH;;AAGA,SAAgB,eACd,KACA,UAC4B;CAC5B,IAAI,SAAS;CACb,MAAM,SAAS,IAAI,UAAU;CAC7B,OAAO,IAAI,eAA2B;EACpC,MAAM,KAAK,YAAY;GACrB,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,KAAK;GAC1C,IAAI,MAAM;IACR,WAAW,MAAM;IACjB;GACF;GACA,UAAU,MAAM;GAChB,SAAS,MAAM;GACf,WAAW,QAAQ,KAAK;EAC1B;EACA,OAAO,QAAQ;GACb,OAAO,OAAO,OAAO,MAAM;EAC7B;CACF,CAAC;AACH;;AAGA,SAAgB,aAAa,QAAkC;CAC7D,IAAI,QAAQ;CACZ,KAAK,MAAM,KAAK,QAAQ,SAAS,EAAE;CACnC,MAAM,MAAM,IAAI,WAAW,KAAK;CAChC,IAAI,SAAS;CACb,KAAK,MAAM,KAAK,QAAQ;EACtB,IAAI,IAAI,GAAG,MAAM;EACjB,UAAU,EAAE;CACd;CACA,OAAO;AACT;;;;;AAMA,gBAAuB,YACrB,KACA,UAC4B;CAC5B,MAAM,SAAS,IAAI,UAAU;CAC7B,IAAI,yBAAqB,IAAI,WAAW,CAAC;CACzC,IAAI;EACF,SAAS;GACP,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,KAAK;GAC1C,IAAI,MAAM;GACV,SAAS,OAAO,WAAW,IAAI,QAAQ,aAAa,CAAC,QAAQ,KAAK,CAAC;GACnE,OAAO,OAAO,cAAc,UAAU;IACpC,MAAM,OAAO,SAAS,GAAG,QAAQ;IACjC,SAAS,OAAO,SAAS,QAAQ;GACnC;EACF;EACA,IAAI,OAAO,aAAa,GAAG,MAAM;CACnC,UAAU;EACR,OAAO,YAAY;CACrB;AACF"}
@@ -1,50 +1,53 @@
1
- import type { UploadResult } from '../storage.types';
2
- import type { ListResponse } from '../http/protocol.types';
3
- import type { DeleteManyResult } from '../storage.types';
4
- export interface StorageClientOptions {
5
- /** Base URL of the mounted controller, e.g. `/api/storage`. */
6
- baseUrl: string;
7
- /** Per-request headers (auth token/cookie). */
8
- headers?: () => Record<string, string> | Promise<Record<string, string>>;
9
- /** Override fetch (SSR/tests). */
10
- fetch?: typeof fetch;
11
- /** Files larger than this switch to multipart. Default 16 MiB. */
12
- multipartThreshold?: number;
1
+ import { A as UploadResult, o as DeleteManyResult } from "../storage.types-BJN7Yp1J.js";
2
+ import { n as ListResponse } from "../protocol.types-C5PPBD12.js";
3
+ //#region src/client/index.d.ts
4
+ interface StorageClientOptions {
5
+ /** Base URL of the mounted controller, e.g. `/api/storage`. */
6
+ baseUrl: string;
7
+ /** Per-request headers (auth token/cookie). */
8
+ headers?: () => Record<string, string> | Promise<Record<string, string>>;
9
+ /** Override fetch (SSR/tests). */
10
+ fetch?: typeof fetch;
11
+ /** Files larger than this switch to multipart. Default 16 MiB. */
12
+ multipartThreshold?: number;
13
13
  }
14
- export interface ClientUploadOptions {
15
- /** Object key. Defaults to the file's name. The server may override it. */
16
- key?: string;
17
- contentType?: string;
18
- metadata?: Record<string, string>;
19
- multipart?: boolean | {
20
- partSize?: number;
21
- concurrency?: number;
22
- };
23
- onProgress?: (p: {
24
- loaded: number;
25
- total: number;
26
- }) => void;
27
- signal?: AbortSignal;
28
- /** Stable id enabling resume across reloads (multipart only). */
29
- resumeKey?: string;
14
+ interface ClientUploadOptions {
15
+ /** Object key. Defaults to the file's name. The server may override it. */
16
+ key?: string;
17
+ contentType?: string;
18
+ metadata?: Record<string, string>;
19
+ multipart?: boolean | {
20
+ partSize?: number;
21
+ concurrency?: number;
22
+ };
23
+ onProgress?: (p: {
24
+ loaded: number;
25
+ total: number;
26
+ }) => void;
27
+ signal?: AbortSignal;
28
+ /** Stable id enabling resume across reloads (multipart only). */
29
+ resumeKey?: string;
30
30
  }
31
- export declare class StorageClientError extends Error {
32
- readonly code: string;
33
- readonly status: number;
34
- constructor(code: string, message: string, status: number);
35
- static from(res: Response): Promise<StorageClientError>;
31
+ declare class StorageClientError extends Error {
32
+ readonly code: string;
33
+ readonly status: number;
34
+ constructor(code: string, message: string, status: number);
35
+ static from(res: Response): Promise<StorageClientError>;
36
36
  }
37
- export declare class StorageClient {
38
- #private;
39
- private readonly options;
40
- constructor(options: StorageClientOptions);
41
- upload(file: File | Blob, opts?: ClientUploadOptions): Promise<UploadResult>;
42
- list(query?: {
43
- prefix?: string;
44
- cursor?: string;
45
- limit?: number;
46
- delimiter?: string;
47
- }): Promise<ListResponse>;
48
- delete(keys: string[]): Promise<DeleteManyResult>;
49
- downloadUrl(key: string): Promise<string>;
37
+ declare class StorageClient {
38
+ #private;
39
+ private readonly options;
40
+ constructor(options: StorageClientOptions);
41
+ upload(file: File | Blob, opts?: ClientUploadOptions): Promise<UploadResult>;
42
+ list(query?: {
43
+ prefix?: string;
44
+ cursor?: string;
45
+ limit?: number;
46
+ delimiter?: string;
47
+ }): Promise<ListResponse>;
48
+ delete(keys: string[]): Promise<DeleteManyResult>;
49
+ downloadUrl(key: string): Promise<string>;
50
50
  }
51
+ //#endregion
52
+ export { ClientUploadOptions, StorageClient, StorageClientError, StorageClientOptions };
53
+ //# sourceMappingURL=index.d.ts.map
@@ -1,243 +1,222 @@
1
- // Browser upload client for @velajs/storage. Framework-agnostic, zero-dep.
2
- // Ships from the `./client` subpath (DOM code, not edge code). Talks to the
3
- // StorageController's JSON control-plane over fetch, and uploads bytes DIRECTLY
4
- // to the bucket via presigned URLs (XHR for progress; fetch has no upload
5
- // progress event). Wire types are imported type-only.
6
- const DEFAULT_MULTIPART_THRESHOLD = 16 * 1024 * 1024; // 16 MiB
1
+ //#region src/client/index.ts
2
+ const DEFAULT_MULTIPART_THRESHOLD = 16 * 1024 * 1024;
7
3
  const DEFAULT_CONCURRENCY = 4;
8
- export class StorageClientError extends Error {
9
- code;
10
- status;
11
- constructor(code, message, status){
12
- super(message), this.code = code, this.status = status;
13
- this.name = 'StorageClientError';
14
- }
15
- static async from(res) {
16
- let code = 'upstream_error';
17
- let message = `request failed: ${res.status}`;
18
- try {
19
- const body = await res.json();
20
- if (body?.error) {
21
- code = body.error.code;
22
- message = body.error.message;
23
- }
24
- } catch {
25
- /* non-JSON body */ }
26
- return new StorageClientError(code, message, res.status);
27
- }
28
- }
4
+ var StorageClientError = class StorageClientError extends Error {
5
+ code;
6
+ status;
7
+ constructor(code, message, status) {
8
+ super(message);
9
+ this.code = code;
10
+ this.status = status;
11
+ this.name = "StorageClientError";
12
+ }
13
+ static async from(res) {
14
+ let code = "upstream_error";
15
+ let message = `request failed: ${res.status}`;
16
+ try {
17
+ const body = await res.json();
18
+ if (body?.error) {
19
+ code = body.error.code;
20
+ message = body.error.message;
21
+ }
22
+ } catch {}
23
+ return new StorageClientError(code, message, res.status);
24
+ }
25
+ };
29
26
  async function pool(items, limit, worker) {
30
- let i = 0;
31
- const run = async ()=>{
32
- while(i < items.length){
33
- const index = i++;
34
- await worker(items[index], index);
35
- }
36
- };
37
- await Promise.all(Array.from({
38
- length: Math.min(Math.max(1, limit), items.length || 1)
39
- }, run));
27
+ let i = 0;
28
+ const run = async () => {
29
+ while (i < items.length) {
30
+ const index = i++;
31
+ await worker(items[index], index);
32
+ }
33
+ };
34
+ await Promise.all(Array.from({ length: Math.min(Math.max(1, limit), items.length || 1) }, run));
40
35
  }
41
36
  function xhrSend(o) {
42
- return new Promise((resolve, reject)=>{
43
- const xhr = new XMLHttpRequest();
44
- xhr.open(o.method, o.url, true);
45
- for (const [k, v] of Object.entries(o.headers ?? {}))xhr.setRequestHeader(k, v);
46
- if (o.onProgress) {
47
- xhr.upload.onprogress = (e)=>o.onProgress({
48
- loaded: e.loaded,
49
- total: e.total
50
- });
51
- }
52
- xhr.onload = ()=>xhr.status >= 200 && xhr.status < 300 ? resolve({
53
- status: xhr.status,
54
- getHeader: (n)=>xhr.getResponseHeader(n)
55
- }) : reject(new StorageClientError('upstream_error', `upload failed: ${xhr.status}`, xhr.status));
56
- xhr.onerror = ()=>reject(new StorageClientError('upstream_error', 'network error', 0));
57
- o.signal?.addEventListener('abort', ()=>xhr.abort(), {
58
- once: true
59
- });
60
- xhr.send(o.body);
61
- });
37
+ return new Promise((resolve, reject) => {
38
+ const xhr = new XMLHttpRequest();
39
+ xhr.open(o.method, o.url, true);
40
+ for (const [k, v] of Object.entries(o.headers ?? {})) xhr.setRequestHeader(k, v);
41
+ if (o.onProgress) xhr.upload.onprogress = (e) => o.onProgress({
42
+ loaded: e.loaded,
43
+ total: e.total
44
+ });
45
+ xhr.onload = () => xhr.status >= 200 && xhr.status < 300 ? resolve({
46
+ status: xhr.status,
47
+ getHeader: (n) => xhr.getResponseHeader(n)
48
+ }) : reject(new StorageClientError("upstream_error", `upload failed: ${xhr.status}`, xhr.status));
49
+ xhr.onerror = () => reject(new StorageClientError("upstream_error", "network error", 0));
50
+ o.signal?.addEventListener("abort", () => xhr.abort(), { once: true });
51
+ xhr.send(o.body);
52
+ });
62
53
  }
63
54
  function loadSession(resumeKey, fingerprint) {
64
- if (!resumeKey || typeof localStorage === 'undefined') return undefined;
65
- try {
66
- const raw = localStorage.getItem(`vela.storage.resume:${resumeKey}`);
67
- if (!raw) return undefined;
68
- const s = JSON.parse(raw);
69
- return s.fingerprint === fingerprint ? s : undefined;
70
- } catch {
71
- return undefined;
72
- }
55
+ if (!resumeKey || typeof localStorage === "undefined") return void 0;
56
+ try {
57
+ const raw = localStorage.getItem(`vela.storage.resume:${resumeKey}`);
58
+ if (!raw) return void 0;
59
+ const s = JSON.parse(raw);
60
+ return s.fingerprint === fingerprint ? s : void 0;
61
+ } catch {
62
+ return;
63
+ }
73
64
  }
74
65
  function saveSession(resumeKey, fingerprint, s) {
75
- if (!resumeKey || typeof localStorage === 'undefined') return;
76
- try {
77
- localStorage.setItem(`vela.storage.resume:${resumeKey}`, JSON.stringify({
78
- ...s,
79
- fingerprint
80
- }));
81
- } catch {
82
- /* quota / private mode — resume is best-effort */ }
66
+ if (!resumeKey || typeof localStorage === "undefined") return;
67
+ try {
68
+ localStorage.setItem(`vela.storage.resume:${resumeKey}`, JSON.stringify({
69
+ ...s,
70
+ fingerprint
71
+ }));
72
+ } catch {}
83
73
  }
84
74
  function clearSession(resumeKey) {
85
- if (!resumeKey || typeof localStorage === 'undefined') return;
86
- try {
87
- localStorage.removeItem(`vela.storage.resume:${resumeKey}`);
88
- } catch {
89
- /* ignore */ }
90
- }
91
- export class StorageClient {
92
- options;
93
- constructor(options){
94
- this.options = options;
95
- }
96
- async upload(file, opts = {}) {
97
- const threshold = this.options.multipartThreshold ?? DEFAULT_MULTIPART_THRESHOLD;
98
- const multipart = opts.multipart === true || opts.multipart !== false && file.size > threshold;
99
- return multipart ? this.#multipartUpload(file, opts) : this.#simpleUpload(file, opts);
100
- }
101
- list(query = {}) {
102
- const qs = new URLSearchParams();
103
- for (const [k, v] of Object.entries(query))if (v != null) qs.set(k, String(v));
104
- return this.#json('GET', `/list?${qs.toString()}`);
105
- }
106
- delete(keys) {
107
- return this.#json('POST', '/delete', {
108
- keys
109
- });
110
- }
111
- async downloadUrl(key) {
112
- const r = await this.#json('POST', '/sign-download', {
113
- key
114
- });
115
- return r.url;
116
- }
117
- // ---- internals ----
118
- async #simpleUpload(file, opts) {
119
- const key = opts.key ?? fileName(file);
120
- const { upload } = await this.#json('POST', '/sign-upload', {
121
- key,
122
- contentType: opts.contentType ?? (file.type || undefined),
123
- size: file.size,
124
- metadata: opts.metadata
125
- });
126
- if (upload.method === 'PUT') {
127
- await xhrSend({
128
- method: 'PUT',
129
- url: upload.url,
130
- body: file,
131
- headers: {
132
- 'content-type': file.type || 'application/octet-stream',
133
- ...upload.headers
134
- },
135
- signal: opts.signal,
136
- onProgress: opts.onProgress
137
- });
138
- } else {
139
- const form = new FormData();
140
- for (const [k, v] of Object.entries(upload.fields))form.append(k, v);
141
- form.append('file', file); // 'file' must be last for an S3/R2 POST policy
142
- await xhrSend({
143
- method: 'POST',
144
- url: upload.url,
145
- body: form,
146
- signal: opts.signal,
147
- onProgress: opts.onProgress
148
- });
149
- }
150
- return {
151
- key,
152
- size: file.size,
153
- contentType: opts.contentType ?? (file.type || 'application/octet-stream')
154
- };
155
- }
156
- async #multipartUpload(file, opts) {
157
- const key = opts.key ?? fileName(file);
158
- const fingerprint = `${fileName(file)}:${file.size}`;
159
- const concurrency = typeof opts.multipart === 'object' && opts.multipart.concurrency || DEFAULT_CONCURRENCY;
160
- let session = loadSession(opts.resumeKey, fingerprint);
161
- if (!session) {
162
- const created = await this.#json('POST', '/multipart/create', {
163
- key,
164
- contentType: opts.contentType ?? (file.type || undefined),
165
- metadata: opts.metadata,
166
- partSize: typeof opts.multipart === 'object' ? opts.multipart.partSize : undefined
167
- });
168
- session = {
169
- key: created.key,
170
- uploadId: created.uploadId,
171
- partSize: created.partSize,
172
- uploaded: {}
173
- };
174
- }
175
- const partSize = session.partSize;
176
- const count = Math.ceil(file.size / partSize) || 1;
177
- const loaded = new Array(count + 1).fill(0);
178
- for (const n of Object.keys(session.uploaded))loaded[Number(n)] = partBytes(Number(n), partSize, file.size);
179
- const report = ()=>opts.onProgress?.({
180
- loaded: loaded.reduce((a, b)=>a + b, 0),
181
- total: file.size
182
- });
183
- const todo = Array.from({
184
- length: count
185
- }, (_, i)=>i + 1).filter((n)=>!session.uploaded[n]);
186
- await pool(todo, concurrency, async (n)=>{
187
- const blob = file.slice((n - 1) * partSize, Math.min(n * partSize, file.size));
188
- const signed = await this.#json('POST', '/multipart/sign-part', {
189
- key: session.key,
190
- uploadId: session.uploadId,
191
- partNumber: n
192
- });
193
- const res = await xhrSend({
194
- method: 'PUT',
195
- url: signed.url,
196
- body: blob,
197
- headers: signed.headers,
198
- signal: opts.signal,
199
- onProgress: (p)=>{
200
- loaded[n] = p.loaded;
201
- report();
202
- }
203
- });
204
- const etag = res.getHeader('ETag') ?? res.getHeader('etag');
205
- if (!etag) {
206
- throw new StorageClientError('invalid_request', 'missing ETag on part upload (check bucket CORS ExposeHeaders: ETag)', res.status);
207
- }
208
- session.uploaded[n] = etag;
209
- saveSession(opts.resumeKey, fingerprint, session);
210
- });
211
- const parts = Object.entries(session.uploaded).map(([n, etag])=>({
212
- partNumber: Number(n),
213
- etag
214
- })).sort((a, b)=>a.partNumber - b.partNumber);
215
- const result = await this.#json('POST', '/multipart/complete', {
216
- key: session.key,
217
- uploadId: session.uploadId,
218
- parts
219
- });
220
- clearSession(opts.resumeKey);
221
- return result;
222
- }
223
- async #json(method, path, body) {
224
- const fetchImpl = this.options.fetch ?? fetch;
225
- const res = await fetchImpl(this.options.baseUrl + path, {
226
- method,
227
- headers: {
228
- 'content-type': 'application/json',
229
- ...await this.options.headers?.() ?? {}
230
- },
231
- body: body !== undefined ? JSON.stringify(body) : undefined
232
- });
233
- if (!res.ok) throw await StorageClientError.from(res);
234
- return await res.json();
235
- }
75
+ if (!resumeKey || typeof localStorage === "undefined") return;
76
+ try {
77
+ localStorage.removeItem(`vela.storage.resume:${resumeKey}`);
78
+ } catch {}
236
79
  }
80
+ var StorageClient = class {
81
+ options;
82
+ constructor(options) {
83
+ this.options = options;
84
+ }
85
+ async upload(file, opts = {}) {
86
+ const threshold = this.options.multipartThreshold ?? DEFAULT_MULTIPART_THRESHOLD;
87
+ return opts.multipart === true || opts.multipart !== false && file.size > threshold ? this.#multipartUpload(file, opts) : this.#simpleUpload(file, opts);
88
+ }
89
+ list(query = {}) {
90
+ const qs = new URLSearchParams();
91
+ for (const [k, v] of Object.entries(query)) if (v != null) qs.set(k, String(v));
92
+ return this.#json("GET", `/list?${qs.toString()}`);
93
+ }
94
+ delete(keys) {
95
+ return this.#json("POST", "/delete", { keys });
96
+ }
97
+ async downloadUrl(key) {
98
+ return (await this.#json("POST", "/sign-download", { key })).url;
99
+ }
100
+ async #simpleUpload(file, opts) {
101
+ const key = opts.key ?? fileName(file);
102
+ const { upload } = await this.#json("POST", "/sign-upload", {
103
+ key,
104
+ contentType: opts.contentType ?? (file.type || void 0),
105
+ size: file.size,
106
+ metadata: opts.metadata
107
+ });
108
+ if (upload.method === "PUT") await xhrSend({
109
+ method: "PUT",
110
+ url: upload.url,
111
+ body: file,
112
+ headers: {
113
+ "content-type": file.type || "application/octet-stream",
114
+ ...upload.headers
115
+ },
116
+ signal: opts.signal,
117
+ onProgress: opts.onProgress
118
+ });
119
+ else {
120
+ const form = new FormData();
121
+ for (const [k, v] of Object.entries(upload.fields)) form.append(k, v);
122
+ form.append("file", file);
123
+ await xhrSend({
124
+ method: "POST",
125
+ url: upload.url,
126
+ body: form,
127
+ signal: opts.signal,
128
+ onProgress: opts.onProgress
129
+ });
130
+ }
131
+ return {
132
+ key,
133
+ size: file.size,
134
+ contentType: opts.contentType ?? (file.type || "application/octet-stream")
135
+ };
136
+ }
137
+ async #multipartUpload(file, opts) {
138
+ const key = opts.key ?? fileName(file);
139
+ const fingerprint = `${fileName(file)}:${file.size}`;
140
+ const concurrency = typeof opts.multipart === "object" && opts.multipart.concurrency || DEFAULT_CONCURRENCY;
141
+ let session = loadSession(opts.resumeKey, fingerprint);
142
+ if (!session) {
143
+ const created = await this.#json("POST", "/multipart/create", {
144
+ key,
145
+ contentType: opts.contentType ?? (file.type || void 0),
146
+ metadata: opts.metadata,
147
+ partSize: typeof opts.multipart === "object" ? opts.multipart.partSize : void 0
148
+ });
149
+ session = {
150
+ key: created.key,
151
+ uploadId: created.uploadId,
152
+ partSize: created.partSize,
153
+ uploaded: {}
154
+ };
155
+ }
156
+ const partSize = session.partSize;
157
+ const count = Math.ceil(file.size / partSize) || 1;
158
+ const loaded = new Array(count + 1).fill(0);
159
+ for (const n of Object.keys(session.uploaded)) loaded[Number(n)] = partBytes(Number(n), partSize, file.size);
160
+ const report = () => opts.onProgress?.({
161
+ loaded: loaded.reduce((a, b) => a + b, 0),
162
+ total: file.size
163
+ });
164
+ await pool(Array.from({ length: count }, (_, i) => i + 1).filter((n) => !session.uploaded[n]), concurrency, async (n) => {
165
+ const blob = file.slice((n - 1) * partSize, Math.min(n * partSize, file.size));
166
+ const signed = await this.#json("POST", "/multipart/sign-part", {
167
+ key: session.key,
168
+ uploadId: session.uploadId,
169
+ partNumber: n
170
+ });
171
+ const res = await xhrSend({
172
+ method: "PUT",
173
+ url: signed.url,
174
+ body: blob,
175
+ headers: signed.headers,
176
+ signal: opts.signal,
177
+ onProgress: (p) => {
178
+ loaded[n] = p.loaded;
179
+ report();
180
+ }
181
+ });
182
+ const etag = res.getHeader("ETag") ?? res.getHeader("etag");
183
+ if (!etag) throw new StorageClientError("invalid_request", "missing ETag on part upload (check bucket CORS ExposeHeaders: ETag)", res.status);
184
+ session.uploaded[n] = etag;
185
+ saveSession(opts.resumeKey, fingerprint, session);
186
+ });
187
+ const parts = Object.entries(session.uploaded).map(([n, etag]) => ({
188
+ partNumber: Number(n),
189
+ etag
190
+ })).sort((a, b) => a.partNumber - b.partNumber);
191
+ const result = await this.#json("POST", "/multipart/complete", {
192
+ key: session.key,
193
+ uploadId: session.uploadId,
194
+ parts
195
+ });
196
+ clearSession(opts.resumeKey);
197
+ return result;
198
+ }
199
+ async #json(method, path, body) {
200
+ const res = await (this.options.fetch ?? fetch)(this.options.baseUrl + path, {
201
+ method,
202
+ headers: {
203
+ "content-type": "application/json",
204
+ ...await this.options.headers?.() ?? {}
205
+ },
206
+ body: body !== void 0 ? JSON.stringify(body) : void 0
207
+ });
208
+ if (!res.ok) throw await StorageClientError.from(res);
209
+ return await res.json();
210
+ }
211
+ };
237
212
  function fileName(file) {
238
- return 'name' in file && typeof file.name === 'string' && file.name ? file.name : 'upload.bin';
213
+ return "name" in file && typeof file.name === "string" && file.name ? file.name : "upload.bin";
239
214
  }
240
215
  function partBytes(partNumber, partSize, total) {
241
- const start = (partNumber - 1) * partSize;
242
- return Math.max(0, Math.min(partSize, total - start));
216
+ const start = (partNumber - 1) * partSize;
217
+ return Math.max(0, Math.min(partSize, total - start));
243
218
  }
219
+ //#endregion
220
+ export { StorageClient, StorageClientError };
221
+
222
+ //# sourceMappingURL=index.js.map