@tranquilload/adapters 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/.turbo/turbo-build.log +62 -0
  2. package/dist/from-file.cjs +12 -0
  3. package/dist/from-file.cjs.map +1 -0
  4. package/dist/from-file.d.cts +8 -0
  5. package/dist/from-file.d.cts.map +1 -0
  6. package/dist/from-file.d.mts +8 -0
  7. package/dist/from-file.d.mts.map +1 -0
  8. package/dist/from-file.mjs +11 -0
  9. package/dist/from-file.mjs.map +1 -0
  10. package/dist/from-node-readable.cjs +10 -0
  11. package/dist/from-node-readable.cjs.map +1 -0
  12. package/dist/from-node-readable.d.cts +7 -0
  13. package/dist/from-node-readable.d.cts.map +1 -0
  14. package/dist/from-node-readable.d.mts +7 -0
  15. package/dist/from-node-readable.d.mts.map +1 -0
  16. package/dist/from-node-readable.mjs +9 -0
  17. package/dist/from-node-readable.mjs.map +1 -0
  18. package/dist/network-multiplier.cjs +26 -0
  19. package/dist/network-multiplier.cjs.map +1 -0
  20. package/dist/network-multiplier.d.cts +25 -0
  21. package/dist/network-multiplier.d.cts.map +1 -0
  22. package/dist/network-multiplier.d.mts +25 -0
  23. package/dist/network-multiplier.d.mts.map +1 -0
  24. package/dist/network-multiplier.mjs +25 -0
  25. package/dist/network-multiplier.mjs.map +1 -0
  26. package/dist/optimal-part-size.cjs +14 -0
  27. package/dist/optimal-part-size.cjs.map +1 -0
  28. package/dist/optimal-part-size.d.cts +23 -0
  29. package/dist/optimal-part-size.d.cts.map +1 -0
  30. package/dist/optimal-part-size.d.mts +23 -0
  31. package/dist/optimal-part-size.d.mts.map +1 -0
  32. package/dist/optimal-part-size.mjs +13 -0
  33. package/dist/optimal-part-size.mjs.map +1 -0
  34. package/dist/s3-multipart-upload.cjs +60 -0
  35. package/dist/s3-multipart-upload.cjs.map +1 -0
  36. package/dist/s3-multipart-upload.d.cts +41 -0
  37. package/dist/s3-multipart-upload.d.cts.map +1 -0
  38. package/dist/s3-multipart-upload.d.mts +41 -0
  39. package/dist/s3-multipart-upload.d.mts.map +1 -0
  40. package/dist/s3-multipart-upload.mjs +58 -0
  41. package/dist/s3-multipart-upload.mjs.map +1 -0
  42. package/dist/simple-http-upload.cjs +26 -0
  43. package/dist/simple-http-upload.cjs.map +1 -0
  44. package/dist/simple-http-upload.d.cts +13 -0
  45. package/dist/simple-http-upload.d.cts.map +1 -0
  46. package/dist/simple-http-upload.d.mts +13 -0
  47. package/dist/simple-http-upload.d.mts.map +1 -0
  48. package/dist/simple-http-upload.mjs +25 -0
  49. package/dist/simple-http-upload.mjs.map +1 -0
  50. package/package.json +73 -0
  51. package/src/protocols/s3-multipart-upload.test.ts +127 -0
  52. package/src/protocols/s3-multipart-upload.ts +92 -0
  53. package/src/protocols/simple-http-upload.test.ts +75 -0
  54. package/src/protocols/simple-http-upload.ts +38 -0
  55. package/src/resilience/network-multiplier.test.ts +57 -0
  56. package/src/resilience/network-multiplier.ts +52 -0
  57. package/src/resilience/optimal-part-size.test.ts +57 -0
  58. package/src/resilience/optimal-part-size.ts +34 -0
  59. package/src/sources/from-file.test.ts +37 -0
  60. package/src/sources/from-file.ts +6 -0
  61. package/src/sources/from-node-readable.test.ts +43 -0
  62. package/src/sources/from-node-readable.ts +5 -0
  63. package/tsconfig.json +8 -0
  64. package/tsdown.config.ts +16 -0
  65. package/vitest.config.ts +13 -0
@@ -0,0 +1,62 @@
1
+
2
+ > @tranquilload/adapters@0.1.0 build /home/runner/work/Tranquilload/Tranquilload/packages/tranquilload-adapters
3
+ > tsdown
4
+
5
+ ℹ tsdown v0.21.0 powered by rolldown v1.0.0-rc.7
6
+ ℹ config file: /home/runner/work/Tranquilload/Tranquilload/packages/tranquilload-adapters/tsdown.config.ts
7
+ ℹ entry: src/sources/from-file.ts, src/sources/from-node-readable.ts, src/protocols/s3-multipart-upload.ts, src/protocols/simple-http-upload.ts, src/resilience/network-multiplier.ts, src/resilience/optimal-part-size.ts
8
+ ℹ tsconfig: tsconfig.json
9
+ ℹ Build start
10
+ ℹ [CJS] dist/s3-multipart-upload.cjs 2.15 kB │ gzip: 0.90 kB
11
+ ℹ [CJS] dist/simple-http-upload.cjs 0.93 kB │ gzip: 0.49 kB
12
+ ℹ [CJS] dist/network-multiplier.cjs 0.92 kB │ gzip: 0.49 kB
13
+ ℹ [CJS] dist/optimal-part-size.cjs 0.59 kB │ gzip: 0.32 kB
14
+ ℹ [CJS] dist/from-node-readable.cjs 0.35 kB │ gzip: 0.23 kB
15
+ ℹ [CJS] dist/from-file.cjs 0.28 kB │ gzip: 0.22 kB
16
+ ℹ [CJS] dist/s3-multipart-upload.cjs.map 4.38 kB │ gzip: 1.64 kB
17
+ ℹ [CJS] dist/network-multiplier.cjs.map 2.21 kB │ gzip: 1.00 kB
18
+ ℹ [CJS] dist/simple-http-upload.cjs.map 1.65 kB │ gzip: 0.80 kB
19
+ ℹ [CJS] dist/optimal-part-size.cjs.map 1.45 kB │ gzip: 0.71 kB
20
+ ℹ [CJS] dist/from-node-readable.cjs.map 0.41 kB │ gzip: 0.26 kB
21
+ ℹ [CJS] dist/from-file.cjs.map 0.38 kB │ gzip: 0.27 kB
22
+ ℹ [CJS] 12 files, total: 15.69 kB
23
+ ℹ [CJS] dist/s3-multipart-upload.d.cts.map 0.69 kB │ gzip: 0.33 kB
24
+ ℹ [CJS] dist/network-multiplier.d.cts.map 0.31 kB │ gzip: 0.22 kB
25
+ ℹ [CJS] dist/simple-http-upload.d.cts.map 0.30 kB │ gzip: 0.20 kB
26
+ ℹ [CJS] dist/optimal-part-size.d.cts.map 0.25 kB │ gzip: 0.19 kB
27
+ ℹ [CJS] dist/from-file.d.cts.map 0.18 kB │ gzip: 0.15 kB
28
+ ℹ [CJS] dist/from-node-readable.d.cts.map 0.18 kB │ gzip: 0.15 kB
29
+ ℹ [CJS] dist/s3-multipart-upload.d.cts 1.20 kB │ gzip: 0.49 kB
30
+ ℹ [CJS] dist/network-multiplier.d.cts 0.97 kB │ gzip: 0.49 kB
31
+ ℹ [CJS] dist/optimal-part-size.d.cts 0.87 kB │ gzip: 0.46 kB
32
+ ℹ [CJS] dist/simple-http-upload.d.cts 0.44 kB │ gzip: 0.28 kB
33
+ ℹ [CJS] dist/from-node-readable.d.cts 0.26 kB │ gzip: 0.18 kB
34
+ ℹ [CJS] dist/from-file.d.cts 0.21 kB │ gzip: 0.18 kB
35
+ ℹ [CJS] 12 files, total: 5.86 kB
36
+ ✔ Build complete in 2822ms
37
+ ℹ [ESM] dist/s3-multipart-upload.mjs 1.96 kB │ gzip: 0.84 kB
38
+ ℹ [ESM] dist/network-multiplier.mjs 0.83 kB │ gzip: 0.44 kB
39
+ ℹ [ESM] dist/simple-http-upload.mjs 0.77 kB │ gzip: 0.43 kB
40
+ ℹ [ESM] dist/optimal-part-size.mjs 0.49 kB │ gzip: 0.27 kB
41
+ ℹ [ESM] dist/from-node-readable.mjs 0.25 kB │ gzip: 0.17 kB
42
+ ℹ [ESM] dist/from-file.mjs 0.20 kB │ gzip: 0.17 kB
43
+ ℹ [ESM] dist/s3-multipart-upload.mjs.map 4.29 kB │ gzip: 1.62 kB
44
+ ℹ [ESM] dist/network-multiplier.mjs.map 2.21 kB │ gzip: 1.00 kB
45
+ ℹ [ESM] dist/simple-http-upload.mjs.map 1.59 kB │ gzip: 0.78 kB
46
+ ℹ [ESM] dist/optimal-part-size.mjs.map 1.44 kB │ gzip: 0.71 kB
47
+ ℹ [ESM] dist/s3-multipart-upload.d.mts.map 0.69 kB │ gzip: 0.33 kB
48
+ ℹ [ESM] dist/from-node-readable.mjs.map 0.39 kB │ gzip: 0.25 kB
49
+ ℹ [ESM] dist/from-file.mjs.map 0.38 kB │ gzip: 0.27 kB
50
+ ℹ [ESM] dist/network-multiplier.d.mts.map 0.31 kB │ gzip: 0.22 kB
51
+ ℹ [ESM] dist/simple-http-upload.d.mts.map 0.30 kB │ gzip: 0.20 kB
52
+ ℹ [ESM] dist/optimal-part-size.d.mts.map 0.25 kB │ gzip: 0.19 kB
53
+ ℹ [ESM] dist/from-file.d.mts.map 0.18 kB │ gzip: 0.15 kB
54
+ ℹ [ESM] dist/from-node-readable.d.mts.map 0.18 kB │ gzip: 0.15 kB
55
+ ℹ [ESM] dist/s3-multipart-upload.d.mts 1.20 kB │ gzip: 0.49 kB
56
+ ℹ [ESM] dist/network-multiplier.d.mts 0.97 kB │ gzip: 0.49 kB
57
+ ℹ [ESM] dist/optimal-part-size.d.mts 0.87 kB │ gzip: 0.46 kB
58
+ ℹ [ESM] dist/simple-http-upload.d.mts 0.44 kB │ gzip: 0.28 kB
59
+ ℹ [ESM] dist/from-node-readable.d.mts 0.26 kB │ gzip: 0.18 kB
60
+ ℹ [ESM] dist/from-file.d.mts 0.21 kB │ gzip: 0.18 kB
61
+ ℹ [ESM] 24 files, total: 20.66 kB
62
+ ✔ Build complete in 2826ms
@@ -0,0 +1,12 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/sources/from-file.ts
3
+ function fromFile(file) {
4
+ return {
5
+ stream: file.stream(),
6
+ totalBytes: file.size
7
+ };
8
+ }
9
+ //#endregion
10
+ exports.fromFile = fromFile;
11
+
12
+ //# sourceMappingURL=from-file.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"from-file.cjs","names":[],"sources":["../src/sources/from-file.ts"],"sourcesContent":["export function fromFile(file: File): { stream: ReadableStream<Uint8Array>; totalBytes: number } {\n return {\n stream: file.stream(),\n totalBytes: file.size,\n }\n}\n"],"mappings":";;AAAA,SAAgB,SAAS,MAAwE;AAC/F,QAAO;EACL,QAAQ,KAAK,QAAQ;EACrB,YAAY,KAAK;EAClB"}
@@ -0,0 +1,8 @@
1
+ //#region src/sources/from-file.d.ts
2
+ declare function fromFile(file: File): {
3
+ stream: ReadableStream<Uint8Array>;
4
+ totalBytes: number;
5
+ };
6
+ //#endregion
7
+ export { fromFile };
8
+ //# sourceMappingURL=from-file.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"from-file.d.cts","names":[],"sources":["../src/sources/from-file.ts"],"mappings":";iBAAgB,QAAA,CAAS,IAAA,EAAM,IAAA;EAAS,MAAA,EAAQ,cAAA,CAAe,UAAA;EAAa,UAAA;AAAA"}
@@ -0,0 +1,8 @@
1
+ //#region src/sources/from-file.d.ts
2
+ declare function fromFile(file: File): {
3
+ stream: ReadableStream<Uint8Array>;
4
+ totalBytes: number;
5
+ };
6
+ //#endregion
7
+ export { fromFile };
8
+ //# sourceMappingURL=from-file.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"from-file.d.mts","names":[],"sources":["../src/sources/from-file.ts"],"mappings":";iBAAgB,QAAA,CAAS,IAAA,EAAM,IAAA;EAAS,MAAA,EAAQ,cAAA,CAAe,UAAA;EAAa,UAAA;AAAA"}
@@ -0,0 +1,11 @@
1
+ //#region src/sources/from-file.ts
2
+ function fromFile(file) {
3
+ return {
4
+ stream: file.stream(),
5
+ totalBytes: file.size
6
+ };
7
+ }
8
+ //#endregion
9
+ export { fromFile };
10
+
11
+ //# sourceMappingURL=from-file.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"from-file.mjs","names":[],"sources":["../src/sources/from-file.ts"],"sourcesContent":["export function fromFile(file: File): { stream: ReadableStream<Uint8Array>; totalBytes: number } {\n return {\n stream: file.stream(),\n totalBytes: file.size,\n }\n}\n"],"mappings":";AAAA,SAAgB,SAAS,MAAwE;AAC/F,QAAO;EACL,QAAQ,KAAK,QAAQ;EACrB,YAAY,KAAK;EAClB"}
@@ -0,0 +1,10 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let node_stream = require("node:stream");
3
+ //#region src/sources/from-node-readable.ts
4
+ function fromNodeReadable(readable) {
5
+ return node_stream.Readable.toWeb(readable);
6
+ }
7
+ //#endregion
8
+ exports.fromNodeReadable = fromNodeReadable;
9
+
10
+ //# sourceMappingURL=from-node-readable.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"from-node-readable.cjs","names":["Readable"],"sources":["../src/sources/from-node-readable.ts"],"sourcesContent":["import { Readable } from \"node:stream\"\n\nexport function fromNodeReadable(readable: Readable): ReadableStream<Uint8Array> {\n return Readable.toWeb(readable) as ReadableStream<Uint8Array>\n}\n"],"mappings":";;;AAEA,SAAgB,iBAAiB,UAAgD;AAC/E,QAAOA,YAAAA,SAAS,MAAM,SAAS"}
@@ -0,0 +1,7 @@
1
+ import { Readable } from "node:stream";
2
+
3
+ //#region src/sources/from-node-readable.d.ts
4
+ declare function fromNodeReadable(readable: Readable): ReadableStream<Uint8Array>;
5
+ //#endregion
6
+ export { fromNodeReadable };
7
+ //# sourceMappingURL=from-node-readable.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"from-node-readable.d.cts","names":[],"sources":["../src/sources/from-node-readable.ts"],"mappings":";;;iBAEgB,gBAAA,CAAiB,QAAA,EAAU,QAAA,GAAW,cAAA,CAAe,UAAA"}
@@ -0,0 +1,7 @@
1
+ import { Readable } from "node:stream";
2
+
3
+ //#region src/sources/from-node-readable.d.ts
4
+ declare function fromNodeReadable(readable: Readable): ReadableStream<Uint8Array>;
5
+ //#endregion
6
+ export { fromNodeReadable };
7
+ //# sourceMappingURL=from-node-readable.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"from-node-readable.d.mts","names":[],"sources":["../src/sources/from-node-readable.ts"],"mappings":";;;iBAEgB,gBAAA,CAAiB,QAAA,EAAU,QAAA,GAAW,cAAA,CAAe,UAAA"}
@@ -0,0 +1,9 @@
1
+ import { Readable } from "node:stream";
2
+ //#region src/sources/from-node-readable.ts
3
+ function fromNodeReadable(readable) {
4
+ return Readable.toWeb(readable);
5
+ }
6
+ //#endregion
7
+ export { fromNodeReadable };
8
+
9
+ //# sourceMappingURL=from-node-readable.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"from-node-readable.mjs","names":[],"sources":["../src/sources/from-node-readable.ts"],"sourcesContent":["import { Readable } from \"node:stream\"\n\nexport function fromNodeReadable(readable: Readable): ReadableStream<Uint8Array> {\n return Readable.toWeb(readable) as ReadableStream<Uint8Array>\n}\n"],"mappings":";;AAEA,SAAgB,iBAAiB,UAAgD;AAC/E,QAAO,SAAS,MAAM,SAAS"}
@@ -0,0 +1,26 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/resilience/network-multiplier.ts
3
+ const DEFAULT_WINDOW_SIZE = 5;
4
+ const DEFAULT_TARGET_BYTES_PER_MS = 10 * 1024 * 1024 / 1e3;
5
+ function networkMultiplier(options) {
6
+ const windowSize = options?.windowSize ?? DEFAULT_WINDOW_SIZE;
7
+ const targetBytesPerMs = options?.targetBytesPerMs ?? DEFAULT_TARGET_BYTES_PER_MS;
8
+ const samples = [];
9
+ return {
10
+ record(bytes, durationMs) {
11
+ if (durationMs <= 0) return;
12
+ const throughput = bytes / durationMs;
13
+ if (samples.length >= windowSize) samples.shift();
14
+ samples.push(throughput);
15
+ },
16
+ factor() {
17
+ if (samples.length === 0) return 1;
18
+ const avg = samples.reduce((sum, s) => sum + s, 0) / samples.length;
19
+ return Math.max(.1, Math.min(1, avg / targetBytesPerMs));
20
+ }
21
+ };
22
+ }
23
+ //#endregion
24
+ exports.networkMultiplier = networkMultiplier;
25
+
26
+ //# sourceMappingURL=network-multiplier.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"network-multiplier.cjs","names":[],"sources":["../src/resilience/network-multiplier.ts"],"sourcesContent":["/**\n * Network throughput multiplier adapter.\n *\n * Measures upload throughput via a sliding window and returns a factor\n * in [0.1, 1.0] to scale chunk size dynamically.\n *\n * @module\n */\n\nexport interface NetworkMultiplierInstance {\n /** Record a completed upload measurement. Skips if durationMs <= 0. */\n record(bytes: number, durationMs: number): void\n /** Returns current throughput factor [0.1, 1.0]. Returns 1.0 with no samples. */\n factor(): number\n}\n\nexport interface NetworkMultiplierOptions {\n /** Number of recent samples to average. Default: 5 */\n windowSize?: number\n /** Throughput (bytes/ms) that maps to factor=1.0. Default: ~10 MB/s */\n targetBytesPerMs?: number\n}\n\nconst DEFAULT_WINDOW_SIZE = 5\nconst DEFAULT_TARGET_BYTES_PER_MS = (10 * 1024 * 1024) / 1000\n\nexport function networkMultiplier(\n options?: NetworkMultiplierOptions\n): NetworkMultiplierInstance {\n const windowSize = options?.windowSize ?? DEFAULT_WINDOW_SIZE\n const targetBytesPerMs =\n options?.targetBytesPerMs ?? DEFAULT_TARGET_BYTES_PER_MS\n const samples: number[] = []\n\n return {\n record(bytes: number, durationMs: number): void {\n if (durationMs <= 0) return\n const throughput = bytes / durationMs\n if (samples.length >= windowSize) {\n samples.shift()\n }\n samples.push(throughput)\n },\n\n factor(): number {\n if (samples.length === 0) return 1.0\n const avg =\n samples.reduce((sum, s) => sum + s, 0) / samples.length\n return Math.max(0.1, Math.min(1.0, avg / targetBytesPerMs))\n },\n }\n}\n"],"mappings":";;AAuBA,MAAM,sBAAsB;AAC5B,MAAM,8BAA+B,KAAK,OAAO,OAAQ;AAEzD,SAAgB,kBACd,SAC2B;CAC3B,MAAM,aAAa,SAAS,cAAc;CAC1C,MAAM,mBACJ,SAAS,oBAAoB;CAC/B,MAAM,UAAoB,EAAE;AAE5B,QAAO;EACL,OAAO,OAAe,YAA0B;AAC9C,OAAI,cAAc,EAAG;GACrB,MAAM,aAAa,QAAQ;AAC3B,OAAI,QAAQ,UAAU,WACpB,SAAQ,OAAO;AAEjB,WAAQ,KAAK,WAAW;;EAG1B,SAAiB;AACf,OAAI,QAAQ,WAAW,EAAG,QAAO;GACjC,MAAM,MACJ,QAAQ,QAAQ,KAAK,MAAM,MAAM,GAAG,EAAE,GAAG,QAAQ;AACnD,UAAO,KAAK,IAAI,IAAK,KAAK,IAAI,GAAK,MAAM,iBAAiB,CAAC;;EAE9D"}
@@ -0,0 +1,25 @@
1
+ //#region src/resilience/network-multiplier.d.ts
2
+ /**
3
+ * Network throughput multiplier adapter.
4
+ *
5
+ * Measures upload throughput via a sliding window and returns a factor
6
+ * in [0.1, 1.0] to scale chunk size dynamically.
7
+ *
8
+ * @module
9
+ */
10
+ interface NetworkMultiplierInstance {
11
+ /** Record a completed upload measurement. Skips if durationMs <= 0. */
12
+ record(bytes: number, durationMs: number): void;
13
+ /** Returns current throughput factor [0.1, 1.0]. Returns 1.0 with no samples. */
14
+ factor(): number;
15
+ }
16
+ interface NetworkMultiplierOptions {
17
+ /** Number of recent samples to average. Default: 5 */
18
+ windowSize?: number;
19
+ /** Throughput (bytes/ms) that maps to factor=1.0. Default: ~10 MB/s */
20
+ targetBytesPerMs?: number;
21
+ }
22
+ declare function networkMultiplier(options?: NetworkMultiplierOptions): NetworkMultiplierInstance;
23
+ //#endregion
24
+ export { NetworkMultiplierInstance, NetworkMultiplierOptions, networkMultiplier };
25
+ //# sourceMappingURL=network-multiplier.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"network-multiplier.d.cts","names":[],"sources":["../src/resilience/network-multiplier.ts"],"mappings":";;AASA;;;;;;;UAAiB,yBAAA;EAIT;EAFN,MAAA,CAAO,KAAA,UAAe,UAAA;EAKP;EAHf,MAAA;AAAA;AAAA,UAGe,wBAAA;EAIC;EAFhB,UAAA;EAQ+B;EAN/B,gBAAA;AAAA;AAAA,iBAMc,iBAAA,CACd,OAAA,GAAU,wBAAA,GACT,yBAAA"}
@@ -0,0 +1,25 @@
1
+ //#region src/resilience/network-multiplier.d.ts
2
+ /**
3
+ * Network throughput multiplier adapter.
4
+ *
5
+ * Measures upload throughput via a sliding window and returns a factor
6
+ * in [0.1, 1.0] to scale chunk size dynamically.
7
+ *
8
+ * @module
9
+ */
10
+ interface NetworkMultiplierInstance {
11
+ /** Record a completed upload measurement. Skips if durationMs <= 0. */
12
+ record(bytes: number, durationMs: number): void;
13
+ /** Returns current throughput factor [0.1, 1.0]. Returns 1.0 with no samples. */
14
+ factor(): number;
15
+ }
16
+ interface NetworkMultiplierOptions {
17
+ /** Number of recent samples to average. Default: 5 */
18
+ windowSize?: number;
19
+ /** Throughput (bytes/ms) that maps to factor=1.0. Default: ~10 MB/s */
20
+ targetBytesPerMs?: number;
21
+ }
22
+ declare function networkMultiplier(options?: NetworkMultiplierOptions): NetworkMultiplierInstance;
23
+ //#endregion
24
+ export { NetworkMultiplierInstance, NetworkMultiplierOptions, networkMultiplier };
25
+ //# sourceMappingURL=network-multiplier.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"network-multiplier.d.mts","names":[],"sources":["../src/resilience/network-multiplier.ts"],"mappings":";;AASA;;;;;;;UAAiB,yBAAA;EAIT;EAFN,MAAA,CAAO,KAAA,UAAe,UAAA;EAKP;EAHf,MAAA;AAAA;AAAA,UAGe,wBAAA;EAIC;EAFhB,UAAA;EAQ+B;EAN/B,gBAAA;AAAA;AAAA,iBAMc,iBAAA,CACd,OAAA,GAAU,wBAAA,GACT,yBAAA"}
@@ -0,0 +1,25 @@
1
+ //#region src/resilience/network-multiplier.ts
2
+ const DEFAULT_WINDOW_SIZE = 5;
3
+ const DEFAULT_TARGET_BYTES_PER_MS = 10 * 1024 * 1024 / 1e3;
4
+ function networkMultiplier(options) {
5
+ const windowSize = options?.windowSize ?? DEFAULT_WINDOW_SIZE;
6
+ const targetBytesPerMs = options?.targetBytesPerMs ?? DEFAULT_TARGET_BYTES_PER_MS;
7
+ const samples = [];
8
+ return {
9
+ record(bytes, durationMs) {
10
+ if (durationMs <= 0) return;
11
+ const throughput = bytes / durationMs;
12
+ if (samples.length >= windowSize) samples.shift();
13
+ samples.push(throughput);
14
+ },
15
+ factor() {
16
+ if (samples.length === 0) return 1;
17
+ const avg = samples.reduce((sum, s) => sum + s, 0) / samples.length;
18
+ return Math.max(.1, Math.min(1, avg / targetBytesPerMs));
19
+ }
20
+ };
21
+ }
22
+ //#endregion
23
+ export { networkMultiplier };
24
+
25
+ //# sourceMappingURL=network-multiplier.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"network-multiplier.mjs","names":[],"sources":["../src/resilience/network-multiplier.ts"],"sourcesContent":["/**\n * Network throughput multiplier adapter.\n *\n * Measures upload throughput via a sliding window and returns a factor\n * in [0.1, 1.0] to scale chunk size dynamically.\n *\n * @module\n */\n\nexport interface NetworkMultiplierInstance {\n /** Record a completed upload measurement. Skips if durationMs <= 0. */\n record(bytes: number, durationMs: number): void\n /** Returns current throughput factor [0.1, 1.0]. Returns 1.0 with no samples. */\n factor(): number\n}\n\nexport interface NetworkMultiplierOptions {\n /** Number of recent samples to average. Default: 5 */\n windowSize?: number\n /** Throughput (bytes/ms) that maps to factor=1.0. Default: ~10 MB/s */\n targetBytesPerMs?: number\n}\n\nconst DEFAULT_WINDOW_SIZE = 5\nconst DEFAULT_TARGET_BYTES_PER_MS = (10 * 1024 * 1024) / 1000\n\nexport function networkMultiplier(\n options?: NetworkMultiplierOptions\n): NetworkMultiplierInstance {\n const windowSize = options?.windowSize ?? DEFAULT_WINDOW_SIZE\n const targetBytesPerMs =\n options?.targetBytesPerMs ?? DEFAULT_TARGET_BYTES_PER_MS\n const samples: number[] = []\n\n return {\n record(bytes: number, durationMs: number): void {\n if (durationMs <= 0) return\n const throughput = bytes / durationMs\n if (samples.length >= windowSize) {\n samples.shift()\n }\n samples.push(throughput)\n },\n\n factor(): number {\n if (samples.length === 0) return 1.0\n const avg =\n samples.reduce((sum, s) => sum + s, 0) / samples.length\n return Math.max(0.1, Math.min(1.0, avg / targetBytesPerMs))\n },\n }\n}\n"],"mappings":";AAuBA,MAAM,sBAAsB;AAC5B,MAAM,8BAA+B,KAAK,OAAO,OAAQ;AAEzD,SAAgB,kBACd,SAC2B;CAC3B,MAAM,aAAa,SAAS,cAAc;CAC1C,MAAM,mBACJ,SAAS,oBAAoB;CAC/B,MAAM,UAAoB,EAAE;AAE5B,QAAO;EACL,OAAO,OAAe,YAA0B;AAC9C,OAAI,cAAc,EAAG;GACrB,MAAM,aAAa,QAAQ;AAC3B,OAAI,QAAQ,UAAU,WACpB,SAAQ,OAAO;AAEjB,WAAQ,KAAK,WAAW;;EAG1B,SAAiB;AACf,OAAI,QAAQ,WAAW,EAAG,QAAO;GACjC,MAAM,MACJ,QAAQ,QAAQ,KAAK,MAAM,MAAM,GAAG,EAAE,GAAG,QAAQ;AACnD,UAAO,KAAK,IAAI,IAAK,KAAK,IAAI,GAAK,MAAM,iBAAiB,CAAC;;EAE9D"}
@@ -0,0 +1,14 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/resilience/optimal-part-size.ts
3
+ function computeOptimalPartSize(options) {
4
+ const { totalBytes, targetPartCount, minPartSize, maxPartSize } = options;
5
+ if (totalBytes === void 0) return minPartSize;
6
+ const raw = Math.ceil(totalBytes / targetPartCount);
7
+ let result = Math.max(raw, minPartSize);
8
+ if (maxPartSize !== void 0) result = Math.min(result, maxPartSize);
9
+ return result;
10
+ }
11
+ //#endregion
12
+ exports.computeOptimalPartSize = computeOptimalPartSize;
13
+
14
+ //# sourceMappingURL=optimal-part-size.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"optimal-part-size.cjs","names":[],"sources":["../src/resilience/optimal-part-size.ts"],"sourcesContent":["/**\n * Optimal part size calculator for multipart uploads.\n *\n * Pure function — computes the ideal chunk size given file size,\n * target part count, and protocol constraints (min/max part size).\n *\n * @module\n */\n\nexport interface OptimalPartSizeOptions {\n /** Total file size in bytes. Pass `undefined` if unknown (streaming). */\n totalBytes?: number\n /** Desired number of parts. */\n targetPartCount: number\n /** Protocol minimum part size (e.g. 5 * 1024 * 1024 for S3). Always returned as floor. */\n minPartSize: number\n /** Optional maximum part size. Clamps result if provided. */\n maxPartSize?: number\n}\n\nexport function computeOptimalPartSize(options: OptimalPartSizeOptions): number {\n const { totalBytes, targetPartCount, minPartSize, maxPartSize } = options\n\n if (totalBytes === undefined) return minPartSize\n\n const raw = Math.ceil(totalBytes / targetPartCount)\n let result = Math.max(raw, minPartSize)\n\n if (maxPartSize !== undefined) {\n result = Math.min(result, maxPartSize)\n }\n\n return result\n}\n"],"mappings":";;AAoBA,SAAgB,uBAAuB,SAAyC;CAC9E,MAAM,EAAE,YAAY,iBAAiB,aAAa,gBAAgB;AAElE,KAAI,eAAe,KAAA,EAAW,QAAO;CAErC,MAAM,MAAM,KAAK,KAAK,aAAa,gBAAgB;CACnD,IAAI,SAAS,KAAK,IAAI,KAAK,YAAY;AAEvC,KAAI,gBAAgB,KAAA,EAClB,UAAS,KAAK,IAAI,QAAQ,YAAY;AAGxC,QAAO"}
@@ -0,0 +1,23 @@
1
+ //#region src/resilience/optimal-part-size.d.ts
2
+ /**
3
+ * Optimal part size calculator for multipart uploads.
4
+ *
5
+ * Pure function — computes the ideal chunk size given file size,
6
+ * target part count, and protocol constraints (min/max part size).
7
+ *
8
+ * @module
9
+ */
10
+ interface OptimalPartSizeOptions {
11
+ /** Total file size in bytes. Pass `undefined` if unknown (streaming). */
12
+ totalBytes?: number;
13
+ /** Desired number of parts. */
14
+ targetPartCount: number;
15
+ /** Protocol minimum part size (e.g. 5 * 1024 * 1024 for S3). Always returned as floor. */
16
+ minPartSize: number;
17
+ /** Optional maximum part size. Clamps result if provided. */
18
+ maxPartSize?: number;
19
+ }
20
+ declare function computeOptimalPartSize(options: OptimalPartSizeOptions): number;
21
+ //#endregion
22
+ export { OptimalPartSizeOptions, computeOptimalPartSize };
23
+ //# sourceMappingURL=optimal-part-size.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"optimal-part-size.d.cts","names":[],"sources":["../src/resilience/optimal-part-size.ts"],"mappings":";;AASA;;;;;;;UAAiB,sBAAA;EAQJ;EANX,UAAA;EASc;EAPd,eAAA;;EAEA,WAAA;EAKoE;EAHpE,WAAA;AAAA;AAAA,iBAGc,sBAAA,CAAuB,OAAA,EAAS,sBAAA"}
@@ -0,0 +1,23 @@
1
+ //#region src/resilience/optimal-part-size.d.ts
2
+ /**
3
+ * Optimal part size calculator for multipart uploads.
4
+ *
5
+ * Pure function — computes the ideal chunk size given file size,
6
+ * target part count, and protocol constraints (min/max part size).
7
+ *
8
+ * @module
9
+ */
10
+ interface OptimalPartSizeOptions {
11
+ /** Total file size in bytes. Pass `undefined` if unknown (streaming). */
12
+ totalBytes?: number;
13
+ /** Desired number of parts. */
14
+ targetPartCount: number;
15
+ /** Protocol minimum part size (e.g. 5 * 1024 * 1024 for S3). Always returned as floor. */
16
+ minPartSize: number;
17
+ /** Optional maximum part size. Clamps result if provided. */
18
+ maxPartSize?: number;
19
+ }
20
+ declare function computeOptimalPartSize(options: OptimalPartSizeOptions): number;
21
+ //#endregion
22
+ export { OptimalPartSizeOptions, computeOptimalPartSize };
23
+ //# sourceMappingURL=optimal-part-size.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"optimal-part-size.d.mts","names":[],"sources":["../src/resilience/optimal-part-size.ts"],"mappings":";;AASA;;;;;;;UAAiB,sBAAA;EAQJ;EANX,UAAA;EASc;EAPd,eAAA;;EAEA,WAAA;EAKoE;EAHpE,WAAA;AAAA;AAAA,iBAGc,sBAAA,CAAuB,OAAA,EAAS,sBAAA"}
@@ -0,0 +1,13 @@
1
+ //#region src/resilience/optimal-part-size.ts
2
+ function computeOptimalPartSize(options) {
3
+ const { totalBytes, targetPartCount, minPartSize, maxPartSize } = options;
4
+ if (totalBytes === void 0) return minPartSize;
5
+ const raw = Math.ceil(totalBytes / targetPartCount);
6
+ let result = Math.max(raw, minPartSize);
7
+ if (maxPartSize !== void 0) result = Math.min(result, maxPartSize);
8
+ return result;
9
+ }
10
+ //#endregion
11
+ export { computeOptimalPartSize };
12
+
13
+ //# sourceMappingURL=optimal-part-size.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"optimal-part-size.mjs","names":[],"sources":["../src/resilience/optimal-part-size.ts"],"sourcesContent":["/**\n * Optimal part size calculator for multipart uploads.\n *\n * Pure function — computes the ideal chunk size given file size,\n * target part count, and protocol constraints (min/max part size).\n *\n * @module\n */\n\nexport interface OptimalPartSizeOptions {\n /** Total file size in bytes. Pass `undefined` if unknown (streaming). */\n totalBytes?: number\n /** Desired number of parts. */\n targetPartCount: number\n /** Protocol minimum part size (e.g. 5 * 1024 * 1024 for S3). Always returned as floor. */\n minPartSize: number\n /** Optional maximum part size. Clamps result if provided. */\n maxPartSize?: number\n}\n\nexport function computeOptimalPartSize(options: OptimalPartSizeOptions): number {\n const { totalBytes, targetPartCount, minPartSize, maxPartSize } = options\n\n if (totalBytes === undefined) return minPartSize\n\n const raw = Math.ceil(totalBytes / targetPartCount)\n let result = Math.max(raw, minPartSize)\n\n if (maxPartSize !== undefined) {\n result = Math.min(result, maxPartSize)\n }\n\n return result\n}\n"],"mappings":";AAoBA,SAAgB,uBAAuB,SAAyC;CAC9E,MAAM,EAAE,YAAY,iBAAiB,aAAa,gBAAgB;AAElE,KAAI,eAAe,KAAA,EAAW,QAAO;CAErC,MAAM,MAAM,KAAK,KAAK,aAAa,gBAAgB;CACnD,IAAI,SAAS,KAAK,IAAI,KAAK,YAAY;AAEvC,KAAI,gBAAgB,KAAA,EAClB,UAAS,KAAK,IAAI,QAAQ,YAAY;AAGxC,QAAO"}
@@ -0,0 +1,60 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _tranquilload_core_errors = require("@tranquilload/core/errors");
3
+ //#region src/protocols/s3-multipart-upload.ts
4
+ const S3_MIN_PART_SIZE = 5 * 1024 * 1024;
5
+ function s3MultipartUpload(options) {
6
+ const { bucket, key, chunkSize = S3_MIN_PART_SIZE, getPresignedUrl, s3Client } = options;
7
+ if (chunkSize < 5242880) throw new Error(`S3 requires chunkSize >= ${S3_MIN_PART_SIZE} bytes (5 MiB), received ${chunkSize} bytes`);
8
+ let storedUploadId = "";
9
+ const initiate = async () => {
10
+ const result = await s3Client.createMultipartUpload({
11
+ Bucket: bucket,
12
+ Key: key
13
+ });
14
+ if (!result.UploadId) throw new Error("S3 CreateMultipartUpload did not return an UploadId");
15
+ storedUploadId = result.UploadId;
16
+ return { uploadId: storedUploadId };
17
+ };
18
+ const uploadPart = async (partNumber, chunk) => {
19
+ let url;
20
+ try {
21
+ url = await Promise.resolve(getPresignedUrl(partNumber, storedUploadId));
22
+ } catch (cause) {
23
+ throw new _tranquilload_core_errors.PresignedUrlError(cause);
24
+ }
25
+ const response = await fetch(url, {
26
+ method: "PUT",
27
+ body: chunk
28
+ });
29
+ if (!response.ok) throw new _tranquilload_core_errors.PartUploadError(partNumber, 0, /* @__PURE__ */ new Error(`S3 PUT failed: HTTP ${response.status} ${response.statusText}`));
30
+ const rawEtag = response.headers.get("ETag");
31
+ if (!rawEtag) throw new _tranquilload_core_errors.PartUploadError(partNumber, 0, /* @__PURE__ */ new Error("S3 response missing ETag header"));
32
+ return rawEtag.replace(/"/g, "");
33
+ };
34
+ const completeUpload = async (uploadId, parts) => {
35
+ try {
36
+ await s3Client.completeMultipartUpload({
37
+ Bucket: bucket,
38
+ Key: key,
39
+ UploadId: uploadId,
40
+ MultipartUpload: { Parts: parts.map((p) => ({
41
+ PartNumber: p.partNumber,
42
+ ETag: p.etag
43
+ })) }
44
+ });
45
+ } catch (cause) {
46
+ throw new _tranquilload_core_errors.CompleteUploadError(cause);
47
+ }
48
+ };
49
+ return {
50
+ chunkSize,
51
+ initiate,
52
+ uploadPart,
53
+ completeUpload
54
+ };
55
+ }
56
+ //#endregion
57
+ exports.S3_MIN_PART_SIZE = S3_MIN_PART_SIZE;
58
+ exports.s3MultipartUpload = s3MultipartUpload;
59
+
60
+ //# sourceMappingURL=s3-multipart-upload.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s3-multipart-upload.cjs","names":["PresignedUrlError","PartUploadError","CompleteUploadError"],"sources":["../src/protocols/s3-multipart-upload.ts"],"sourcesContent":["import { CompleteUploadError, PartUploadError, PresignedUrlError } from \"@tranquilload/core/errors\"\nimport type { CompletedPart } from \"@tranquilload/core/multipart\"\n\nexport const S3_MIN_PART_SIZE = 5 * 1024 * 1024 // 5 MiB\n\nexport interface S3Client {\n createMultipartUpload(params: {\n Bucket: string\n Key: string\n }): Promise<{ UploadId?: string }>\n\n completeMultipartUpload(params: {\n Bucket: string\n Key: string\n UploadId: string\n MultipartUpload: { Parts: ReadonlyArray<{ PartNumber: number; ETag: string }> }\n }): Promise<unknown>\n}\n\nexport interface S3MultipartUploadOptions {\n bucket: string\n key: string\n chunkSize?: number\n getPresignedUrl: (partNumber: number, uploadId: string) => string | Promise<string>\n s3Client: S3Client\n}\n\nexport function s3MultipartUpload(options: S3MultipartUploadOptions): {\n chunkSize: number\n initiate: () => Promise<{ uploadId: string }>\n uploadPart: (partNumber: number, chunk: Uint8Array) => Promise<string>\n completeUpload: (uploadId: string, parts: ReadonlyArray<CompletedPart>) => Promise<void>\n} {\n const { bucket, key, chunkSize = S3_MIN_PART_SIZE, getPresignedUrl, s3Client } = options\n\n if (chunkSize < S3_MIN_PART_SIZE) {\n throw new Error(\n `S3 requires chunkSize >= ${S3_MIN_PART_SIZE} bytes (5 MiB), received ${chunkSize} bytes`\n )\n }\n\n let storedUploadId = \"\"\n\n const initiate = async (): Promise<{ uploadId: string }> => {\n const result = await s3Client.createMultipartUpload({ Bucket: bucket, Key: key })\n if (!result.UploadId) throw new Error(\"S3 CreateMultipartUpload did not return an UploadId\")\n storedUploadId = result.UploadId\n return { uploadId: storedUploadId }\n }\n\n const uploadPart = async (partNumber: number, chunk: Uint8Array): Promise<string> => {\n let url: string\n try {\n url = await Promise.resolve(getPresignedUrl(partNumber, storedUploadId))\n } catch (cause) {\n throw new PresignedUrlError(cause)\n }\n const response = await fetch(url, { method: \"PUT\", body: chunk as unknown as BodyInit })\n if (!response.ok) {\n throw new PartUploadError(\n partNumber,\n 0,\n new Error(`S3 PUT failed: HTTP ${response.status} ${response.statusText}`)\n )\n }\n const rawEtag = response.headers.get(\"ETag\")\n if (!rawEtag) {\n throw new PartUploadError(partNumber, 0, new Error(\"S3 response missing ETag header\"))\n }\n return rawEtag.replace(/\"/g, \"\")\n }\n\n const completeUpload = async (\n uploadId: string,\n parts: ReadonlyArray<CompletedPart>\n ): Promise<void> => {\n try {\n await s3Client.completeMultipartUpload({\n Bucket: bucket,\n Key: key,\n UploadId: uploadId,\n MultipartUpload: {\n Parts: parts.map((p) => ({ PartNumber: p.partNumber, ETag: p.etag })),\n },\n })\n } catch (cause) {\n throw new CompleteUploadError(cause)\n }\n }\n\n return { chunkSize, initiate, uploadPart, completeUpload }\n}\n"],"mappings":";;;AAGA,MAAa,mBAAmB,IAAI,OAAO;AAwB3C,SAAgB,kBAAkB,SAKhC;CACA,MAAM,EAAE,QAAQ,KAAK,YAAY,kBAAkB,iBAAiB,aAAa;AAEjF,KAAI,YAAA,QACF,OAAM,IAAI,MACR,4BAA4B,iBAAiB,2BAA2B,UAAU,QACnF;CAGH,IAAI,iBAAiB;CAErB,MAAM,WAAW,YAA2C;EAC1D,MAAM,SAAS,MAAM,SAAS,sBAAsB;GAAE,QAAQ;GAAQ,KAAK;GAAK,CAAC;AACjF,MAAI,CAAC,OAAO,SAAU,OAAM,IAAI,MAAM,sDAAsD;AAC5F,mBAAiB,OAAO;AACxB,SAAO,EAAE,UAAU,gBAAgB;;CAGrC,MAAM,aAAa,OAAO,YAAoB,UAAuC;EACnF,IAAI;AACJ,MAAI;AACF,SAAM,MAAM,QAAQ,QAAQ,gBAAgB,YAAY,eAAe,CAAC;WACjE,OAAO;AACd,SAAM,IAAIA,0BAAAA,kBAAkB,MAAM;;EAEpC,MAAM,WAAW,MAAM,MAAM,KAAK;GAAE,QAAQ;GAAO,MAAM;GAA8B,CAAC;AACxF,MAAI,CAAC,SAAS,GACZ,OAAM,IAAIC,0BAAAA,gBACR,YACA,mBACA,IAAI,MAAM,uBAAuB,SAAS,OAAO,GAAG,SAAS,aAAa,CAC3E;EAEH,MAAM,UAAU,SAAS,QAAQ,IAAI,OAAO;AAC5C,MAAI,CAAC,QACH,OAAM,IAAIA,0BAAAA,gBAAgB,YAAY,mBAAG,IAAI,MAAM,kCAAkC,CAAC;AAExF,SAAO,QAAQ,QAAQ,MAAM,GAAG;;CAGlC,MAAM,iBAAiB,OACrB,UACA,UACkB;AAClB,MAAI;AACF,SAAM,SAAS,wBAAwB;IACrC,QAAQ;IACR,KAAK;IACL,UAAU;IACV,iBAAiB,EACf,OAAO,MAAM,KAAK,OAAO;KAAE,YAAY,EAAE;KAAY,MAAM,EAAE;KAAM,EAAE,EACtE;IACF,CAAC;WACK,OAAO;AACd,SAAM,IAAIC,0BAAAA,oBAAoB,MAAM;;;AAIxC,QAAO;EAAE;EAAW;EAAU;EAAY;EAAgB"}
@@ -0,0 +1,41 @@
1
+ import { CompletedPart } from "@tranquilload/core/multipart";
2
+
3
+ //#region src/protocols/s3-multipart-upload.d.ts
4
+ declare const S3_MIN_PART_SIZE: number;
5
+ interface S3Client {
6
+ createMultipartUpload(params: {
7
+ Bucket: string;
8
+ Key: string;
9
+ }): Promise<{
10
+ UploadId?: string;
11
+ }>;
12
+ completeMultipartUpload(params: {
13
+ Bucket: string;
14
+ Key: string;
15
+ UploadId: string;
16
+ MultipartUpload: {
17
+ Parts: ReadonlyArray<{
18
+ PartNumber: number;
19
+ ETag: string;
20
+ }>;
21
+ };
22
+ }): Promise<unknown>;
23
+ }
24
+ interface S3MultipartUploadOptions {
25
+ bucket: string;
26
+ key: string;
27
+ chunkSize?: number;
28
+ getPresignedUrl: (partNumber: number, uploadId: string) => string | Promise<string>;
29
+ s3Client: S3Client;
30
+ }
31
+ declare function s3MultipartUpload(options: S3MultipartUploadOptions): {
32
+ chunkSize: number;
33
+ initiate: () => Promise<{
34
+ uploadId: string;
35
+ }>;
36
+ uploadPart: (partNumber: number, chunk: Uint8Array) => Promise<string>;
37
+ completeUpload: (uploadId: string, parts: ReadonlyArray<CompletedPart>) => Promise<void>;
38
+ };
39
+ //#endregion
40
+ export { S3Client, S3MultipartUploadOptions, S3_MIN_PART_SIZE, s3MultipartUpload };
41
+ //# sourceMappingURL=s3-multipart-upload.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s3-multipart-upload.d.cts","names":[],"sources":["../src/protocols/s3-multipart-upload.ts"],"mappings":";;;cAGa,gBAAA;AAAA,UAEI,QAAA;EACf,qBAAA,CAAsB,MAAA;IACpB,MAAA;IACA,GAAA;EAAA,IACE,OAAA;IAAU,QAAA;EAAA;EAEd,uBAAA,CAAwB,MAAA;IACtB,MAAA;IACA,GAAA;IACA,QAAA;IACA,eAAA;MAAmB,KAAA,EAAO,aAAA;QAAgB,UAAA;QAAoB,IAAA;MAAA;IAAA;EAAA,IAC5D,OAAA;AAAA;AAAA,UAGW,wBAAA;EACf,MAAA;EACA,GAAA;EACA,SAAA;EACA,eAAA,GAAkB,UAAA,UAAoB,QAAA,sBAA8B,OAAA;EACpE,QAAA,EAAU,QAAA;AAAA;AAAA,iBAGI,iBAAA,CAAkB,OAAA,EAAS,wBAAA;EACzC,SAAA;EACA,QAAA,QAAgB,OAAA;IAAU,QAAA;EAAA;EAC1B,UAAA,GAAa,UAAA,UAAoB,KAAA,EAAO,UAAA,KAAe,OAAA;EACvD,cAAA,GAAiB,QAAA,UAAkB,KAAA,EAAO,aAAA,CAAc,aAAA,MAAmB,OAAA;AAAA"}
@@ -0,0 +1,41 @@
1
+ import { CompletedPart } from "@tranquilload/core/multipart";
2
+
3
+ //#region src/protocols/s3-multipart-upload.d.ts
4
+ declare const S3_MIN_PART_SIZE: number;
5
+ interface S3Client {
6
+ createMultipartUpload(params: {
7
+ Bucket: string;
8
+ Key: string;
9
+ }): Promise<{
10
+ UploadId?: string;
11
+ }>;
12
+ completeMultipartUpload(params: {
13
+ Bucket: string;
14
+ Key: string;
15
+ UploadId: string;
16
+ MultipartUpload: {
17
+ Parts: ReadonlyArray<{
18
+ PartNumber: number;
19
+ ETag: string;
20
+ }>;
21
+ };
22
+ }): Promise<unknown>;
23
+ }
24
+ interface S3MultipartUploadOptions {
25
+ bucket: string;
26
+ key: string;
27
+ chunkSize?: number;
28
+ getPresignedUrl: (partNumber: number, uploadId: string) => string | Promise<string>;
29
+ s3Client: S3Client;
30
+ }
31
+ declare function s3MultipartUpload(options: S3MultipartUploadOptions): {
32
+ chunkSize: number;
33
+ initiate: () => Promise<{
34
+ uploadId: string;
35
+ }>;
36
+ uploadPart: (partNumber: number, chunk: Uint8Array) => Promise<string>;
37
+ completeUpload: (uploadId: string, parts: ReadonlyArray<CompletedPart>) => Promise<void>;
38
+ };
39
+ //#endregion
40
+ export { S3Client, S3MultipartUploadOptions, S3_MIN_PART_SIZE, s3MultipartUpload };
41
+ //# sourceMappingURL=s3-multipart-upload.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s3-multipart-upload.d.mts","names":[],"sources":["../src/protocols/s3-multipart-upload.ts"],"mappings":";;;cAGa,gBAAA;AAAA,UAEI,QAAA;EACf,qBAAA,CAAsB,MAAA;IACpB,MAAA;IACA,GAAA;EAAA,IACE,OAAA;IAAU,QAAA;EAAA;EAEd,uBAAA,CAAwB,MAAA;IACtB,MAAA;IACA,GAAA;IACA,QAAA;IACA,eAAA;MAAmB,KAAA,EAAO,aAAA;QAAgB,UAAA;QAAoB,IAAA;MAAA;IAAA;EAAA,IAC5D,OAAA;AAAA;AAAA,UAGW,wBAAA;EACf,MAAA;EACA,GAAA;EACA,SAAA;EACA,eAAA,GAAkB,UAAA,UAAoB,QAAA,sBAA8B,OAAA;EACpE,QAAA,EAAU,QAAA;AAAA;AAAA,iBAGI,iBAAA,CAAkB,OAAA,EAAS,wBAAA;EACzC,SAAA;EACA,QAAA,QAAgB,OAAA;IAAU,QAAA;EAAA;EAC1B,UAAA,GAAa,UAAA,UAAoB,KAAA,EAAO,UAAA,KAAe,OAAA;EACvD,cAAA,GAAiB,QAAA,UAAkB,KAAA,EAAO,aAAA,CAAc,aAAA,MAAmB,OAAA;AAAA"}
@@ -0,0 +1,58 @@
1
+ import { CompleteUploadError, PartUploadError, PresignedUrlError } from "@tranquilload/core/errors";
2
+ //#region src/protocols/s3-multipart-upload.ts
3
+ const S3_MIN_PART_SIZE = 5 * 1024 * 1024;
4
+ function s3MultipartUpload(options) {
5
+ const { bucket, key, chunkSize = S3_MIN_PART_SIZE, getPresignedUrl, s3Client } = options;
6
+ if (chunkSize < 5242880) throw new Error(`S3 requires chunkSize >= ${S3_MIN_PART_SIZE} bytes (5 MiB), received ${chunkSize} bytes`);
7
+ let storedUploadId = "";
8
+ const initiate = async () => {
9
+ const result = await s3Client.createMultipartUpload({
10
+ Bucket: bucket,
11
+ Key: key
12
+ });
13
+ if (!result.UploadId) throw new Error("S3 CreateMultipartUpload did not return an UploadId");
14
+ storedUploadId = result.UploadId;
15
+ return { uploadId: storedUploadId };
16
+ };
17
+ const uploadPart = async (partNumber, chunk) => {
18
+ let url;
19
+ try {
20
+ url = await Promise.resolve(getPresignedUrl(partNumber, storedUploadId));
21
+ } catch (cause) {
22
+ throw new PresignedUrlError(cause);
23
+ }
24
+ const response = await fetch(url, {
25
+ method: "PUT",
26
+ body: chunk
27
+ });
28
+ if (!response.ok) throw new PartUploadError(partNumber, 0, /* @__PURE__ */ new Error(`S3 PUT failed: HTTP ${response.status} ${response.statusText}`));
29
+ const rawEtag = response.headers.get("ETag");
30
+ if (!rawEtag) throw new PartUploadError(partNumber, 0, /* @__PURE__ */ new Error("S3 response missing ETag header"));
31
+ return rawEtag.replace(/"/g, "");
32
+ };
33
+ const completeUpload = async (uploadId, parts) => {
34
+ try {
35
+ await s3Client.completeMultipartUpload({
36
+ Bucket: bucket,
37
+ Key: key,
38
+ UploadId: uploadId,
39
+ MultipartUpload: { Parts: parts.map((p) => ({
40
+ PartNumber: p.partNumber,
41
+ ETag: p.etag
42
+ })) }
43
+ });
44
+ } catch (cause) {
45
+ throw new CompleteUploadError(cause);
46
+ }
47
+ };
48
+ return {
49
+ chunkSize,
50
+ initiate,
51
+ uploadPart,
52
+ completeUpload
53
+ };
54
+ }
55
+ //#endregion
56
+ export { S3_MIN_PART_SIZE, s3MultipartUpload };
57
+
58
+ //# sourceMappingURL=s3-multipart-upload.mjs.map