@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.
- package/.turbo/turbo-build.log +62 -0
- package/dist/from-file.cjs +12 -0
- package/dist/from-file.cjs.map +1 -0
- package/dist/from-file.d.cts +8 -0
- package/dist/from-file.d.cts.map +1 -0
- package/dist/from-file.d.mts +8 -0
- package/dist/from-file.d.mts.map +1 -0
- package/dist/from-file.mjs +11 -0
- package/dist/from-file.mjs.map +1 -0
- package/dist/from-node-readable.cjs +10 -0
- package/dist/from-node-readable.cjs.map +1 -0
- package/dist/from-node-readable.d.cts +7 -0
- package/dist/from-node-readable.d.cts.map +1 -0
- package/dist/from-node-readable.d.mts +7 -0
- package/dist/from-node-readable.d.mts.map +1 -0
- package/dist/from-node-readable.mjs +9 -0
- package/dist/from-node-readable.mjs.map +1 -0
- package/dist/network-multiplier.cjs +26 -0
- package/dist/network-multiplier.cjs.map +1 -0
- package/dist/network-multiplier.d.cts +25 -0
- package/dist/network-multiplier.d.cts.map +1 -0
- package/dist/network-multiplier.d.mts +25 -0
- package/dist/network-multiplier.d.mts.map +1 -0
- package/dist/network-multiplier.mjs +25 -0
- package/dist/network-multiplier.mjs.map +1 -0
- package/dist/optimal-part-size.cjs +14 -0
- package/dist/optimal-part-size.cjs.map +1 -0
- package/dist/optimal-part-size.d.cts +23 -0
- package/dist/optimal-part-size.d.cts.map +1 -0
- package/dist/optimal-part-size.d.mts +23 -0
- package/dist/optimal-part-size.d.mts.map +1 -0
- package/dist/optimal-part-size.mjs +13 -0
- package/dist/optimal-part-size.mjs.map +1 -0
- package/dist/s3-multipart-upload.cjs +60 -0
- package/dist/s3-multipart-upload.cjs.map +1 -0
- package/dist/s3-multipart-upload.d.cts +41 -0
- package/dist/s3-multipart-upload.d.cts.map +1 -0
- package/dist/s3-multipart-upload.d.mts +41 -0
- package/dist/s3-multipart-upload.d.mts.map +1 -0
- package/dist/s3-multipart-upload.mjs +58 -0
- package/dist/s3-multipart-upload.mjs.map +1 -0
- package/dist/simple-http-upload.cjs +26 -0
- package/dist/simple-http-upload.cjs.map +1 -0
- package/dist/simple-http-upload.d.cts +13 -0
- package/dist/simple-http-upload.d.cts.map +1 -0
- package/dist/simple-http-upload.d.mts +13 -0
- package/dist/simple-http-upload.d.mts.map +1 -0
- package/dist/simple-http-upload.mjs +25 -0
- package/dist/simple-http-upload.mjs.map +1 -0
- package/package.json +73 -0
- package/src/protocols/s3-multipart-upload.test.ts +127 -0
- package/src/protocols/s3-multipart-upload.ts +92 -0
- package/src/protocols/simple-http-upload.test.ts +75 -0
- package/src/protocols/simple-http-upload.ts +38 -0
- package/src/resilience/network-multiplier.test.ts +57 -0
- package/src/resilience/network-multiplier.ts +52 -0
- package/src/resilience/optimal-part-size.test.ts +57 -0
- package/src/resilience/optimal-part-size.ts +34 -0
- package/src/sources/from-file.test.ts +37 -0
- package/src/sources/from-file.ts +6 -0
- package/src/sources/from-node-readable.test.ts +43 -0
- package/src/sources/from-node-readable.ts +5 -0
- package/tsconfig.json +8 -0
- package/tsdown.config.ts +16 -0
- 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
|
+
[34mℹ[39m tsdown [2mv0.21.0[22m powered by rolldown [2mv1.0.0-rc.7[22m
|
|
6
|
+
[34mℹ[39m config file: [4m/home/runner/work/Tranquilload/Tranquilload/packages/tranquilload-adapters/tsdown.config.ts[24m
|
|
7
|
+
[34mℹ[39m entry: [34msrc/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[39m
|
|
8
|
+
[34mℹ[39m tsconfig: [34mtsconfig.json[39m
|
|
9
|
+
[34mℹ[39m Build start
|
|
10
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22m[1ms3-multipart-upload.cjs[22m [2m2.15 kB[22m [2m│ gzip: 0.90 kB[22m
|
|
11
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22m[1msimple-http-upload.cjs[22m [2m0.93 kB[22m [2m│ gzip: 0.49 kB[22m
|
|
12
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22m[1mnetwork-multiplier.cjs[22m [2m0.92 kB[22m [2m│ gzip: 0.49 kB[22m
|
|
13
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22m[1moptimal-part-size.cjs[22m [2m0.59 kB[22m [2m│ gzip: 0.32 kB[22m
|
|
14
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22m[1mfrom-node-readable.cjs[22m [2m0.35 kB[22m [2m│ gzip: 0.23 kB[22m
|
|
15
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22m[1mfrom-file.cjs[22m [2m0.28 kB[22m [2m│ gzip: 0.22 kB[22m
|
|
16
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22ms3-multipart-upload.cjs.map [2m4.38 kB[22m [2m│ gzip: 1.64 kB[22m
|
|
17
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22mnetwork-multiplier.cjs.map [2m2.21 kB[22m [2m│ gzip: 1.00 kB[22m
|
|
18
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22msimple-http-upload.cjs.map [2m1.65 kB[22m [2m│ gzip: 0.80 kB[22m
|
|
19
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22moptimal-part-size.cjs.map [2m1.45 kB[22m [2m│ gzip: 0.71 kB[22m
|
|
20
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22mfrom-node-readable.cjs.map [2m0.41 kB[22m [2m│ gzip: 0.26 kB[22m
|
|
21
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22mfrom-file.cjs.map [2m0.38 kB[22m [2m│ gzip: 0.27 kB[22m
|
|
22
|
+
[34mℹ[39m [33m[CJS][39m 12 files, total: 15.69 kB
|
|
23
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22ms3-multipart-upload.d.cts.map [2m0.69 kB[22m [2m│ gzip: 0.33 kB[22m
|
|
24
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22mnetwork-multiplier.d.cts.map [2m0.31 kB[22m [2m│ gzip: 0.22 kB[22m
|
|
25
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22msimple-http-upload.d.cts.map [2m0.30 kB[22m [2m│ gzip: 0.20 kB[22m
|
|
26
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22moptimal-part-size.d.cts.map [2m0.25 kB[22m [2m│ gzip: 0.19 kB[22m
|
|
27
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22mfrom-file.d.cts.map [2m0.18 kB[22m [2m│ gzip: 0.15 kB[22m
|
|
28
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22mfrom-node-readable.d.cts.map [2m0.18 kB[22m [2m│ gzip: 0.15 kB[22m
|
|
29
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22m[32m[1ms3-multipart-upload.d.cts[22m[39m [2m1.20 kB[22m [2m│ gzip: 0.49 kB[22m
|
|
30
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22m[32m[1mnetwork-multiplier.d.cts[22m[39m [2m0.97 kB[22m [2m│ gzip: 0.49 kB[22m
|
|
31
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22m[32m[1moptimal-part-size.d.cts[22m[39m [2m0.87 kB[22m [2m│ gzip: 0.46 kB[22m
|
|
32
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22m[32m[1msimple-http-upload.d.cts[22m[39m [2m0.44 kB[22m [2m│ gzip: 0.28 kB[22m
|
|
33
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22m[32m[1mfrom-node-readable.d.cts[22m[39m [2m0.26 kB[22m [2m│ gzip: 0.18 kB[22m
|
|
34
|
+
[34mℹ[39m [33m[CJS][39m [2mdist/[22m[32m[1mfrom-file.d.cts[22m[39m [2m0.21 kB[22m [2m│ gzip: 0.18 kB[22m
|
|
35
|
+
[34mℹ[39m [33m[CJS][39m 12 files, total: 5.86 kB
|
|
36
|
+
[32m✔[39m Build complete in [32m2822ms[39m
|
|
37
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22m[1ms3-multipart-upload.mjs[22m [2m1.96 kB[22m [2m│ gzip: 0.84 kB[22m
|
|
38
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22m[1mnetwork-multiplier.mjs[22m [2m0.83 kB[22m [2m│ gzip: 0.44 kB[22m
|
|
39
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22m[1msimple-http-upload.mjs[22m [2m0.77 kB[22m [2m│ gzip: 0.43 kB[22m
|
|
40
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22m[1moptimal-part-size.mjs[22m [2m0.49 kB[22m [2m│ gzip: 0.27 kB[22m
|
|
41
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22m[1mfrom-node-readable.mjs[22m [2m0.25 kB[22m [2m│ gzip: 0.17 kB[22m
|
|
42
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22m[1mfrom-file.mjs[22m [2m0.20 kB[22m [2m│ gzip: 0.17 kB[22m
|
|
43
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22ms3-multipart-upload.mjs.map [2m4.29 kB[22m [2m│ gzip: 1.62 kB[22m
|
|
44
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22mnetwork-multiplier.mjs.map [2m2.21 kB[22m [2m│ gzip: 1.00 kB[22m
|
|
45
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22msimple-http-upload.mjs.map [2m1.59 kB[22m [2m│ gzip: 0.78 kB[22m
|
|
46
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22moptimal-part-size.mjs.map [2m1.44 kB[22m [2m│ gzip: 0.71 kB[22m
|
|
47
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22ms3-multipart-upload.d.mts.map [2m0.69 kB[22m [2m│ gzip: 0.33 kB[22m
|
|
48
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22mfrom-node-readable.mjs.map [2m0.39 kB[22m [2m│ gzip: 0.25 kB[22m
|
|
49
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22mfrom-file.mjs.map [2m0.38 kB[22m [2m│ gzip: 0.27 kB[22m
|
|
50
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22mnetwork-multiplier.d.mts.map [2m0.31 kB[22m [2m│ gzip: 0.22 kB[22m
|
|
51
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22msimple-http-upload.d.mts.map [2m0.30 kB[22m [2m│ gzip: 0.20 kB[22m
|
|
52
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22moptimal-part-size.d.mts.map [2m0.25 kB[22m [2m│ gzip: 0.19 kB[22m
|
|
53
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22mfrom-file.d.mts.map [2m0.18 kB[22m [2m│ gzip: 0.15 kB[22m
|
|
54
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22mfrom-node-readable.d.mts.map [2m0.18 kB[22m [2m│ gzip: 0.15 kB[22m
|
|
55
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22m[32m[1ms3-multipart-upload.d.mts[22m[39m [2m1.20 kB[22m [2m│ gzip: 0.49 kB[22m
|
|
56
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22m[32m[1mnetwork-multiplier.d.mts[22m[39m [2m0.97 kB[22m [2m│ gzip: 0.49 kB[22m
|
|
57
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22m[32m[1moptimal-part-size.d.mts[22m[39m [2m0.87 kB[22m [2m│ gzip: 0.46 kB[22m
|
|
58
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22m[32m[1msimple-http-upload.d.mts[22m[39m [2m0.44 kB[22m [2m│ gzip: 0.28 kB[22m
|
|
59
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22m[32m[1mfrom-node-readable.d.mts[22m[39m [2m0.26 kB[22m [2m│ gzip: 0.18 kB[22m
|
|
60
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22m[32m[1mfrom-file.d.mts[22m[39m [2m0.21 kB[22m [2m│ gzip: 0.18 kB[22m
|
|
61
|
+
[34mℹ[39m [34m[ESM][39m 24 files, total: 20.66 kB
|
|
62
|
+
[32m✔[39m Build complete in [32m2826ms[39m
|
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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
|