@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,57 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { computeOptimalPartSize } from './optimal-part-size.js'
3
+
4
+ const MB = 1024 * 1024
5
+
6
+ describe('computeOptimalPartSize', () => {
7
+ it('returns minPartSize when totalBytes is undefined', () => {
8
+ expect(
9
+ computeOptimalPartSize({
10
+ totalBytes: undefined,
11
+ targetPartCount: 10,
12
+ minPartSize: 5 * MB,
13
+ })
14
+ ).toBe(5 * MB)
15
+ })
16
+
17
+ it('returns optimal size when raw > minPartSize (AC1: 100MB / 10 = 10MB > 5MB)', () => {
18
+ expect(
19
+ computeOptimalPartSize({
20
+ totalBytes: 100 * MB,
21
+ targetPartCount: 10,
22
+ minPartSize: 5 * MB,
23
+ })
24
+ ).toBe(10 * MB)
25
+ })
26
+
27
+ it('returns minPartSize when file is too small (AC2: totalBytes/targetPartCount < minPartSize)', () => {
28
+ expect(
29
+ computeOptimalPartSize({
30
+ totalBytes: 8 * MB,
31
+ targetPartCount: 10,
32
+ minPartSize: 5 * MB,
33
+ })
34
+ ).toBe(5 * MB)
35
+ })
36
+
37
+ it('clamps to maxPartSize when raw part size exceeds max', () => {
38
+ expect(
39
+ computeOptimalPartSize({
40
+ totalBytes: 100 * MB,
41
+ targetPartCount: 2,
42
+ minPartSize: 5 * MB,
43
+ maxPartSize: 20 * MB,
44
+ })
45
+ ).toBe(20 * MB)
46
+ })
47
+
48
+ it('returns exact value when totalBytes / targetPartCount === minPartSize', () => {
49
+ expect(
50
+ computeOptimalPartSize({
51
+ totalBytes: 50 * MB,
52
+ targetPartCount: 10,
53
+ minPartSize: 5 * MB,
54
+ })
55
+ ).toBe(5 * MB)
56
+ })
57
+ })
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Optimal part size calculator for multipart uploads.
3
+ *
4
+ * Pure function — computes the ideal chunk size given file size,
5
+ * target part count, and protocol constraints (min/max part size).
6
+ *
7
+ * @module
8
+ */
9
+
10
+ export 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
+
21
+ export function computeOptimalPartSize(options: OptimalPartSizeOptions): number {
22
+ const { totalBytes, targetPartCount, minPartSize, maxPartSize } = options
23
+
24
+ if (totalBytes === undefined) return minPartSize
25
+
26
+ const raw = Math.ceil(totalBytes / targetPartCount)
27
+ let result = Math.max(raw, minPartSize)
28
+
29
+ if (maxPartSize !== undefined) {
30
+ result = Math.min(result, maxPartSize)
31
+ }
32
+
33
+ return result
34
+ }
@@ -0,0 +1,37 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { fromFile } from './from-file.js'
3
+
4
+ describe('fromFile', () => {
5
+ it('returns totalBytes equal to file.size', () => {
6
+ const bytes = new Uint8Array([1, 2, 3, 4, 5])
7
+ const file = new File([bytes], 'test.bin', { type: 'application/octet-stream' })
8
+
9
+ const result = fromFile(file)
10
+
11
+ expect(result.totalBytes).toBe(5)
12
+ })
13
+
14
+ it('stream yields all file bytes', async () => {
15
+ const bytes = new Uint8Array([10, 20, 30, 40])
16
+ const file = new File([bytes], 'test.bin')
17
+
18
+ const { stream } = fromFile(file)
19
+
20
+ const reader = stream.getReader()
21
+ const chunks: Uint8Array[] = []
22
+ while (true) {
23
+ const { done, value } = await reader.read()
24
+ if (done) break
25
+ chunks.push(value)
26
+ }
27
+
28
+ const all = new Uint8Array(chunks.reduce((acc, c) => acc + c.length, 0))
29
+ let offset = 0
30
+ for (const chunk of chunks) {
31
+ all.set(chunk, offset)
32
+ offset += chunk.length
33
+ }
34
+
35
+ expect(Array.from(all)).toEqual([10, 20, 30, 40])
36
+ })
37
+ })
@@ -0,0 +1,6 @@
1
+ export function fromFile(file: File): { stream: ReadableStream<Uint8Array>; totalBytes: number } {
2
+ return {
3
+ stream: file.stream(),
4
+ totalBytes: file.size,
5
+ }
6
+ }
@@ -0,0 +1,43 @@
1
+ import { describe, it, expect } from "vitest"
2
+ import { Readable } from "node:stream"
3
+ import { fromNodeReadable } from "./from-node-readable.js"
4
+
5
+ describe("fromNodeReadable", () => {
6
+ it("streams all bytes from a Node Readable", async () => {
7
+ const bytes = new Uint8Array([1, 2, 3, 4, 5])
8
+ const readable = Readable.from([Buffer.from(bytes)])
9
+
10
+ const webStream = fromNodeReadable(readable)
11
+
12
+ const reader = webStream.getReader()
13
+ const chunks: Uint8Array[] = []
14
+ while (true) {
15
+ const { done, value } = await reader.read()
16
+ if (done) break
17
+ chunks.push(value)
18
+ }
19
+
20
+ const totalLength = chunks.reduce((n, c) => n + c.length, 0)
21
+ const all = new Uint8Array(totalLength)
22
+ let offset = 0
23
+ for (const chunk of chunks) {
24
+ all.set(chunk, offset)
25
+ offset += chunk.length
26
+ }
27
+
28
+ expect(Array.from(all)).toEqual([1, 2, 3, 4, 5])
29
+ })
30
+
31
+ it("propagates Readable errors to the ReadableStream", async () => {
32
+ const readable = new Readable({
33
+ read() {
34
+ this.emit("error", new Error("read failure"))
35
+ },
36
+ })
37
+
38
+ const webStream = fromNodeReadable(readable)
39
+ const reader = webStream.getReader()
40
+
41
+ await expect(reader.read()).rejects.toThrow("read failure")
42
+ })
43
+ })
@@ -0,0 +1,5 @@
1
+ import { Readable } from "node:stream"
2
+
3
+ export function fromNodeReadable(readable: Readable): ReadableStream<Uint8Array> {
4
+ return Readable.toWeb(readable) as ReadableStream<Uint8Array>
5
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "dist"
6
+ },
7
+ "include": ["src/**/*.ts"]
8
+ }
@@ -0,0 +1,16 @@
1
+ import { defineConfig } from 'tsdown'
2
+
3
+ export default defineConfig({
4
+ entry: {
5
+ 'from-file': 'src/sources/from-file.ts',
6
+ 'from-node-readable': 'src/sources/from-node-readable.ts',
7
+ 's3-multipart-upload': 'src/protocols/s3-multipart-upload.ts',
8
+ 'simple-http-upload': 'src/protocols/simple-http-upload.ts',
9
+ 'network-multiplier': 'src/resilience/network-multiplier.ts',
10
+ 'optimal-part-size': 'src/resilience/optimal-part-size.ts',
11
+ },
12
+ format: ['esm', 'cjs'],
13
+ dts: true,
14
+ clean: true,
15
+ sourcemap: true,
16
+ })
@@ -0,0 +1,13 @@
1
+ import { defineConfig } from 'vitest/config'
2
+ import path from 'path'
3
+
4
+ export default defineConfig({
5
+ resolve: {
6
+ alias: {
7
+ '@tranquilload/core': path.resolve(__dirname, '../tranquilload-core/src'),
8
+ },
9
+ },
10
+ test: {
11
+ include: ['src/**/*.test.ts'],
12
+ },
13
+ })