@uploadista/client-core 0.0.3

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 (235) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/LICENSE +21 -0
  3. package/README.md +100 -0
  4. package/dist/auth/auth-http-client.d.ts +50 -0
  5. package/dist/auth/auth-http-client.d.ts.map +1 -0
  6. package/dist/auth/auth-http-client.js +110 -0
  7. package/dist/auth/direct-auth.d.ts +38 -0
  8. package/dist/auth/direct-auth.d.ts.map +1 -0
  9. package/dist/auth/direct-auth.js +95 -0
  10. package/dist/auth/index.d.ts +6 -0
  11. package/dist/auth/index.d.ts.map +1 -0
  12. package/dist/auth/index.js +5 -0
  13. package/dist/auth/no-auth.d.ts +26 -0
  14. package/dist/auth/no-auth.d.ts.map +1 -0
  15. package/dist/auth/no-auth.js +33 -0
  16. package/dist/auth/saas-auth.d.ts +80 -0
  17. package/dist/auth/saas-auth.d.ts.map +1 -0
  18. package/dist/auth/saas-auth.js +167 -0
  19. package/dist/auth/types.d.ts +101 -0
  20. package/dist/auth/types.d.ts.map +1 -0
  21. package/dist/auth/types.js +8 -0
  22. package/dist/chunk-buffer.d.ts +209 -0
  23. package/dist/chunk-buffer.d.ts.map +1 -0
  24. package/dist/chunk-buffer.js +236 -0
  25. package/dist/client/create-uploadista-client.d.ts +369 -0
  26. package/dist/client/create-uploadista-client.d.ts.map +1 -0
  27. package/dist/client/create-uploadista-client.js +518 -0
  28. package/dist/client/index.d.ts +4 -0
  29. package/dist/client/index.d.ts.map +1 -0
  30. package/dist/client/index.js +3 -0
  31. package/dist/client/uploadista-api.d.ts +284 -0
  32. package/dist/client/uploadista-api.d.ts.map +1 -0
  33. package/dist/client/uploadista-api.js +444 -0
  34. package/dist/client/uploadista-websocket-manager.d.ts +110 -0
  35. package/dist/client/uploadista-websocket-manager.d.ts.map +1 -0
  36. package/dist/client/uploadista-websocket-manager.js +207 -0
  37. package/dist/error.d.ts +106 -0
  38. package/dist/error.d.ts.map +1 -0
  39. package/dist/error.js +69 -0
  40. package/dist/index.d.ts +9 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +12 -0
  43. package/dist/logger.d.ts +70 -0
  44. package/dist/logger.d.ts.map +1 -0
  45. package/dist/logger.js +59 -0
  46. package/dist/mock-data-store.d.ts +30 -0
  47. package/dist/mock-data-store.d.ts.map +1 -0
  48. package/dist/mock-data-store.js +88 -0
  49. package/dist/network-monitor.d.ts +262 -0
  50. package/dist/network-monitor.d.ts.map +1 -0
  51. package/dist/network-monitor.js +291 -0
  52. package/dist/services/abort-controller-service.d.ts +19 -0
  53. package/dist/services/abort-controller-service.d.ts.map +1 -0
  54. package/dist/services/abort-controller-service.js +4 -0
  55. package/dist/services/checksum-service.d.ts +4 -0
  56. package/dist/services/checksum-service.d.ts.map +1 -0
  57. package/dist/services/checksum-service.js +1 -0
  58. package/dist/services/file-reader-service.d.ts +38 -0
  59. package/dist/services/file-reader-service.d.ts.map +1 -0
  60. package/dist/services/file-reader-service.js +4 -0
  61. package/dist/services/fingerprint-service.d.ts +4 -0
  62. package/dist/services/fingerprint-service.d.ts.map +1 -0
  63. package/dist/services/fingerprint-service.js +1 -0
  64. package/dist/services/http-client.d.ts +182 -0
  65. package/dist/services/http-client.d.ts.map +1 -0
  66. package/dist/services/http-client.js +1 -0
  67. package/dist/services/id-generation-service.d.ts +10 -0
  68. package/dist/services/id-generation-service.d.ts.map +1 -0
  69. package/dist/services/id-generation-service.js +1 -0
  70. package/dist/services/index.d.ts +11 -0
  71. package/dist/services/index.d.ts.map +1 -0
  72. package/dist/services/index.js +10 -0
  73. package/dist/services/platform-service.d.ts +48 -0
  74. package/dist/services/platform-service.d.ts.map +1 -0
  75. package/dist/services/platform-service.js +10 -0
  76. package/dist/services/service-container.d.ts +25 -0
  77. package/dist/services/service-container.d.ts.map +1 -0
  78. package/dist/services/service-container.js +1 -0
  79. package/dist/services/storage-service.d.ts +26 -0
  80. package/dist/services/storage-service.d.ts.map +1 -0
  81. package/dist/services/storage-service.js +1 -0
  82. package/dist/services/websocket-service.d.ts +36 -0
  83. package/dist/services/websocket-service.d.ts.map +1 -0
  84. package/dist/services/websocket-service.js +4 -0
  85. package/dist/smart-chunker.d.ts +72 -0
  86. package/dist/smart-chunker.d.ts.map +1 -0
  87. package/dist/smart-chunker.js +317 -0
  88. package/dist/storage/client-storage.d.ts +148 -0
  89. package/dist/storage/client-storage.d.ts.map +1 -0
  90. package/dist/storage/client-storage.js +62 -0
  91. package/dist/storage/in-memory-storage-service.d.ts +7 -0
  92. package/dist/storage/in-memory-storage-service.d.ts.map +1 -0
  93. package/dist/storage/in-memory-storage-service.js +24 -0
  94. package/dist/storage/index.d.ts +3 -0
  95. package/dist/storage/index.d.ts.map +1 -0
  96. package/dist/storage/index.js +2 -0
  97. package/dist/types/buffered-chunk.d.ts +6 -0
  98. package/dist/types/buffered-chunk.d.ts.map +1 -0
  99. package/dist/types/buffered-chunk.js +1 -0
  100. package/dist/types/chunk-metrics.d.ts +12 -0
  101. package/dist/types/chunk-metrics.d.ts.map +1 -0
  102. package/dist/types/chunk-metrics.js +1 -0
  103. package/dist/types/flow-result.d.ts +11 -0
  104. package/dist/types/flow-result.d.ts.map +1 -0
  105. package/dist/types/flow-result.js +1 -0
  106. package/dist/types/flow-upload-config.d.ts +54 -0
  107. package/dist/types/flow-upload-config.d.ts.map +1 -0
  108. package/dist/types/flow-upload-config.js +1 -0
  109. package/dist/types/flow-upload-item.d.ts +16 -0
  110. package/dist/types/flow-upload-item.d.ts.map +1 -0
  111. package/dist/types/flow-upload-item.js +1 -0
  112. package/dist/types/flow-upload-options.d.ts +41 -0
  113. package/dist/types/flow-upload-options.d.ts.map +1 -0
  114. package/dist/types/flow-upload-options.js +1 -0
  115. package/dist/types/index.d.ts +14 -0
  116. package/dist/types/index.d.ts.map +1 -0
  117. package/dist/types/index.js +13 -0
  118. package/dist/types/multi-flow-upload-options.d.ts +33 -0
  119. package/dist/types/multi-flow-upload-options.d.ts.map +1 -0
  120. package/dist/types/multi-flow-upload-options.js +1 -0
  121. package/dist/types/multi-flow-upload-state.d.ts +9 -0
  122. package/dist/types/multi-flow-upload-state.d.ts.map +1 -0
  123. package/dist/types/multi-flow-upload-state.js +1 -0
  124. package/dist/types/performance-insights.d.ts +11 -0
  125. package/dist/types/performance-insights.d.ts.map +1 -0
  126. package/dist/types/performance-insights.js +1 -0
  127. package/dist/types/previous-upload.d.ts +20 -0
  128. package/dist/types/previous-upload.d.ts.map +1 -0
  129. package/dist/types/previous-upload.js +9 -0
  130. package/dist/types/upload-options.d.ts +40 -0
  131. package/dist/types/upload-options.d.ts.map +1 -0
  132. package/dist/types/upload-options.js +1 -0
  133. package/dist/types/upload-response.d.ts +6 -0
  134. package/dist/types/upload-response.d.ts.map +1 -0
  135. package/dist/types/upload-response.js +1 -0
  136. package/dist/types/upload-result.d.ts +57 -0
  137. package/dist/types/upload-result.d.ts.map +1 -0
  138. package/dist/types/upload-result.js +1 -0
  139. package/dist/types/upload-session-metrics.d.ts +16 -0
  140. package/dist/types/upload-session-metrics.d.ts.map +1 -0
  141. package/dist/types/upload-session-metrics.js +1 -0
  142. package/dist/upload/chunk-upload.d.ts +40 -0
  143. package/dist/upload/chunk-upload.d.ts.map +1 -0
  144. package/dist/upload/chunk-upload.js +82 -0
  145. package/dist/upload/flow-upload.d.ts +48 -0
  146. package/dist/upload/flow-upload.d.ts.map +1 -0
  147. package/dist/upload/flow-upload.js +240 -0
  148. package/dist/upload/index.d.ts +3 -0
  149. package/dist/upload/index.d.ts.map +1 -0
  150. package/dist/upload/index.js +2 -0
  151. package/dist/upload/parallel-upload.d.ts +65 -0
  152. package/dist/upload/parallel-upload.d.ts.map +1 -0
  153. package/dist/upload/parallel-upload.js +231 -0
  154. package/dist/upload/single-upload.d.ts +118 -0
  155. package/dist/upload/single-upload.d.ts.map +1 -0
  156. package/dist/upload/single-upload.js +332 -0
  157. package/dist/upload/upload-manager.d.ts +30 -0
  158. package/dist/upload/upload-manager.d.ts.map +1 -0
  159. package/dist/upload/upload-manager.js +57 -0
  160. package/dist/upload/upload-metrics.d.ts +37 -0
  161. package/dist/upload/upload-metrics.d.ts.map +1 -0
  162. package/dist/upload/upload-metrics.js +236 -0
  163. package/dist/upload/upload-storage.d.ts +32 -0
  164. package/dist/upload/upload-storage.d.ts.map +1 -0
  165. package/dist/upload/upload-storage.js +46 -0
  166. package/dist/upload/upload-strategy.d.ts +66 -0
  167. package/dist/upload/upload-strategy.d.ts.map +1 -0
  168. package/dist/upload/upload-strategy.js +171 -0
  169. package/dist/upload/upload-utils.d.ts +26 -0
  170. package/dist/upload/upload-utils.d.ts.map +1 -0
  171. package/dist/upload/upload-utils.js +80 -0
  172. package/package.json +29 -0
  173. package/src/__tests__/smart-chunking.test.ts +399 -0
  174. package/src/auth/__tests__/auth-http-client.test.ts +327 -0
  175. package/src/auth/__tests__/direct-auth.test.ts +135 -0
  176. package/src/auth/__tests__/no-auth.test.ts +40 -0
  177. package/src/auth/__tests__/saas-auth.test.ts +337 -0
  178. package/src/auth/auth-http-client.ts +150 -0
  179. package/src/auth/direct-auth.ts +121 -0
  180. package/src/auth/index.ts +5 -0
  181. package/src/auth/no-auth.ts +39 -0
  182. package/src/auth/saas-auth.ts +218 -0
  183. package/src/auth/types.ts +105 -0
  184. package/src/chunk-buffer.ts +287 -0
  185. package/src/client/create-uploadista-client.ts +901 -0
  186. package/src/client/index.ts +3 -0
  187. package/src/client/uploadista-api.ts +857 -0
  188. package/src/client/uploadista-websocket-manager.ts +275 -0
  189. package/src/error.ts +149 -0
  190. package/src/index.ts +13 -0
  191. package/src/logger.ts +104 -0
  192. package/src/mock-data-store.ts +97 -0
  193. package/src/network-monitor.ts +445 -0
  194. package/src/services/abort-controller-service.ts +21 -0
  195. package/src/services/checksum-service.ts +3 -0
  196. package/src/services/file-reader-service.ts +44 -0
  197. package/src/services/fingerprint-service.ts +6 -0
  198. package/src/services/http-client.ts +229 -0
  199. package/src/services/id-generation-service.ts +9 -0
  200. package/src/services/index.ts +10 -0
  201. package/src/services/platform-service.ts +65 -0
  202. package/src/services/service-container.ts +24 -0
  203. package/src/services/storage-service.ts +29 -0
  204. package/src/services/websocket-service.ts +33 -0
  205. package/src/smart-chunker.ts +451 -0
  206. package/src/storage/client-storage.ts +186 -0
  207. package/src/storage/in-memory-storage-service.ts +33 -0
  208. package/src/storage/index.ts +2 -0
  209. package/src/types/buffered-chunk.ts +5 -0
  210. package/src/types/chunk-metrics.ts +11 -0
  211. package/src/types/flow-result.ts +14 -0
  212. package/src/types/flow-upload-config.ts +56 -0
  213. package/src/types/flow-upload-item.ts +16 -0
  214. package/src/types/flow-upload-options.ts +56 -0
  215. package/src/types/index.ts +13 -0
  216. package/src/types/multi-flow-upload-options.ts +39 -0
  217. package/src/types/multi-flow-upload-state.ts +9 -0
  218. package/src/types/performance-insights.ts +7 -0
  219. package/src/types/previous-upload.ts +22 -0
  220. package/src/types/upload-options.ts +56 -0
  221. package/src/types/upload-response.ts +6 -0
  222. package/src/types/upload-result.ts +60 -0
  223. package/src/types/upload-session-metrics.ts +15 -0
  224. package/src/upload/chunk-upload.ts +151 -0
  225. package/src/upload/flow-upload.ts +367 -0
  226. package/src/upload/index.ts +2 -0
  227. package/src/upload/parallel-upload.ts +387 -0
  228. package/src/upload/single-upload.ts +554 -0
  229. package/src/upload/upload-manager.ts +106 -0
  230. package/src/upload/upload-metrics.ts +340 -0
  231. package/src/upload/upload-storage.ts +87 -0
  232. package/src/upload/upload-strategy.ts +296 -0
  233. package/src/upload/upload-utils.ts +114 -0
  234. package/tsconfig.json +23 -0
  235. package/tsconfig.tsbuildinfo +1 -0
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@uploadista/client-core",
3
+ "type": "module",
4
+ "version": "0.0.3",
5
+ "description": "Platform-agnostic core upload client logic for Uploadista",
6
+ "license": "MIT",
7
+ "author": "Uploadista",
8
+ "exports": {
9
+ ".": "./src/index.ts",
10
+ "./types": "./src/types/index.ts",
11
+ "./services": "./src/services/index.ts",
12
+ "./upload": "./src/upload/index.ts"
13
+ },
14
+ "dependencies": {
15
+ "js-base64": "3.7.8",
16
+ "zod": "4.1.12",
17
+ "@uploadista/core": "0.0.3"
18
+ },
19
+ "devDependencies": {
20
+ "vitest": "3.2.4",
21
+ "@uploadista/typescript-config": "0.0.3"
22
+ },
23
+ "scripts": {
24
+ "build": "tsc -b",
25
+ "format": "biome format --write ./src",
26
+ "lint": "biome lint --write ./src",
27
+ "check": "biome check --write ./src"
28
+ }
29
+ }
@@ -0,0 +1,399 @@
1
+ import { beforeEach, describe, expect, it } from "vitest";
2
+ import { SmartChunker } from "../../../core/src/smart-chunker";
3
+ import { ChunkBuffer } from "../chunk-buffer";
4
+ import { NetworkMonitor } from "../network-monitor";
5
+ import { UploadMetrics } from "../upload/upload-metrics";
6
+
7
+ describe("NetworkMonitor", () => {
8
+ let monitor: NetworkMonitor;
9
+
10
+ beforeEach(() => {
11
+ monitor = new NetworkMonitor();
12
+ });
13
+
14
+ it("should initialize with empty metrics", () => {
15
+ const metrics = monitor.getCurrentMetrics();
16
+ expect(metrics.totalRequests).toBe(0);
17
+ expect(metrics.averageSpeed).toBe(0);
18
+ expect(metrics.successRate).toBe(0);
19
+ });
20
+
21
+ it("should record successful uploads", () => {
22
+ monitor.recordUpload(1024, 1000, true); // 1KB in 1 second
23
+
24
+ const metrics = monitor.getCurrentMetrics();
25
+ expect(metrics.totalRequests).toBe(1);
26
+ expect(metrics.successRate).toBe(1);
27
+ expect(metrics.averageSpeed).toBe(1024); // 1KB/s
28
+ });
29
+
30
+ it("should detect network conditions", () => {
31
+ // Record slow uploads
32
+ for (let i = 0; i < 5; i++) {
33
+ monitor.recordUpload(1024, 2000, true); // 1KB in 2 seconds = 512 B/s
34
+ }
35
+
36
+ const condition = monitor.getNetworkCondition();
37
+ expect(condition.type).toBe("slow");
38
+ expect(condition.confidence).toBeGreaterThan(0);
39
+ });
40
+
41
+ it("should detect fast network conditions", () => {
42
+ // Record fast uploads
43
+ for (let i = 0; i < 5; i++) {
44
+ monitor.recordUpload(10 * 1024 * 1024, 1000, true); // 10MB in 1 second = 10MB/s
45
+ }
46
+
47
+ const condition = monitor.getNetworkCondition();
48
+ expect(condition.type).toBe("fast");
49
+ expect(condition.confidence).toBeGreaterThan(0);
50
+ });
51
+
52
+ it("should detect unstable network", () => {
53
+ // Record variable upload speeds
54
+ monitor.recordUpload(1024, 1000, true); // 1KB/s
55
+ monitor.recordUpload(10 * 1024 * 1024, 1000, true); // 10MB/s
56
+ monitor.recordUpload(1024, 1000, true); // 1KB/s
57
+ monitor.recordUpload(10 * 1024 * 1024, 1000, true); // 10MB/s
58
+ monitor.recordUpload(1024, 1000, true); // 1KB/s
59
+
60
+ const condition = monitor.getNetworkCondition();
61
+ expect(condition.type).toBe("unstable");
62
+ });
63
+ });
64
+
65
+ describe("SmartChunker", () => {
66
+ let monitor: NetworkMonitor;
67
+ let chunker: SmartChunker;
68
+
69
+ beforeEach(() => {
70
+ monitor = new NetworkMonitor();
71
+ chunker = new SmartChunker(monitor);
72
+ });
73
+
74
+ it("should start with initial chunk size", () => {
75
+ const decision = chunker.getNextChunkSize();
76
+ expect(decision.size).toBe(512 * 1024); // Default initial size
77
+ expect(decision.strategy).toBe("initial");
78
+ });
79
+
80
+ it("should adapt chunk size based on performance", () => {
81
+ // Simulate successful uploads
82
+ chunker.recordChunkResult(512 * 1024, 1000, true);
83
+ chunker.recordChunkResult(512 * 1024, 1000, true);
84
+ chunker.recordChunkResult(512 * 1024, 1000, true);
85
+
86
+ const decision = chunker.getNextChunkSize();
87
+ expect(decision.size).toBeGreaterThan(0);
88
+ });
89
+
90
+ it("should reduce chunk size on failures", () => {
91
+ // First, establish some baseline by recording network data
92
+ monitor.recordUpload(512 * 1024, 1000, true);
93
+ monitor.recordUpload(512 * 1024, 1000, true);
94
+ monitor.recordUpload(512 * 1024, 1000, true);
95
+ monitor.recordUpload(512 * 1024, 1000, true);
96
+ monitor.recordUpload(512 * 1024, 1000, true);
97
+
98
+ const initialDecision = chunker.getNextChunkSize();
99
+ const initialSize = initialDecision.size;
100
+
101
+ // Record failures
102
+ chunker.recordChunkResult(initialSize, 5000, false);
103
+ chunker.recordChunkResult(initialSize, 5000, false);
104
+
105
+ const newDecision = chunker.getNextChunkSize();
106
+ expect(newDecision.size).toBeLessThan(initialSize);
107
+ });
108
+
109
+ it("should respect minimum and maximum bounds", () => {
110
+ const config = {
111
+ minChunkSize: 64 * 1024,
112
+ maxChunkSize: 2 * 1024 * 1024,
113
+ };
114
+
115
+ const boundedChunker = new SmartChunker(monitor, config);
116
+
117
+ // Force very small size
118
+ for (let i = 0; i < 10; i++) {
119
+ boundedChunker.recordChunkResult(1024, 10000, false);
120
+ }
121
+
122
+ const smallDecision = boundedChunker.getNextChunkSize();
123
+ expect(smallDecision.size).toBeGreaterThanOrEqual(config.minChunkSize);
124
+
125
+ // Reset and force very large size
126
+ boundedChunker.reset();
127
+ for (let i = 0; i < 10; i++) {
128
+ boundedChunker.recordChunkResult(10 * 1024 * 1024, 100, true);
129
+ }
130
+
131
+ const largeDecision = boundedChunker.getNextChunkSize();
132
+ expect(largeDecision.size).toBeLessThanOrEqual(config.maxChunkSize);
133
+ });
134
+
135
+ it("should limit chunk size by remaining bytes", () => {
136
+ const remainingBytes = 100 * 1024; // 100KB remaining
137
+ const decision = chunker.getNextChunkSize(remainingBytes);
138
+ expect(decision.size).toBeLessThanOrEqual(remainingBytes);
139
+ });
140
+
141
+ it("should respect datastore constraints (S3-like)", () => {
142
+ const s3Constraints = {
143
+ minChunkSize: 5 * 1024 * 1024, // 5MB - S3 minimum
144
+ maxChunkSize: 5 * 1024 * 1024 * 1024, // 5GB - S3 maximum
145
+ optimalChunkSize: 16 * 1024 * 1024, // 16MB optimal
146
+ };
147
+
148
+ const s3AwareChunker = new SmartChunker(monitor, {
149
+ datastoreConstraints: s3Constraints,
150
+ });
151
+
152
+ const decision = s3AwareChunker.getNextChunkSize();
153
+
154
+ // Should use optimal chunk size as initial, which is >= 5MB minimum
155
+ expect(decision.size).toBeGreaterThanOrEqual(s3Constraints.minChunkSize);
156
+ expect(decision.size).toBeLessThanOrEqual(s3Constraints.maxChunkSize);
157
+ });
158
+
159
+ it("should use S3-optimized strategies when S3 constraints are present", () => {
160
+ const s3Constraints = {
161
+ minChunkSize: 5 * 1024 * 1024, // 5MB - S3 minimum (this triggers S3 mode)
162
+ maxChunkSize: 5 * 1024 * 1024 * 1024, // 5GB
163
+ optimalChunkSize: 16 * 1024 * 1024, // 16MB
164
+ };
165
+
166
+ const s3AwareChunker = new SmartChunker(monitor, {
167
+ datastoreConstraints: s3Constraints,
168
+ });
169
+
170
+ // Simulate fast network conditions
171
+ for (let i = 0; i < 5; i++) {
172
+ monitor.recordUpload(10 * 1024 * 1024, 1000, true); // 10MB/s
173
+ }
174
+
175
+ const decision = s3AwareChunker.getNextChunkSize();
176
+ expect(decision.strategy).toContain("s3-"); // Should use S3-optimized strategy
177
+ expect(decision.size).toBeGreaterThanOrEqual(5 * 1024 * 1024); // Never below 5MB
178
+ });
179
+
180
+ it("should enforce minimum chunk size even with failures", () => {
181
+ const s3Constraints = {
182
+ minChunkSize: 5 * 1024 * 1024, // 5MB
183
+ maxChunkSize: 5 * 1024 * 1024 * 1024, // 5GB
184
+ optimalChunkSize: 16 * 1024 * 1024, // 16MB
185
+ };
186
+
187
+ const s3AwareChunker = new SmartChunker(monitor, {
188
+ datastoreConstraints: s3Constraints,
189
+ });
190
+
191
+ // Record many failures to try to force smaller chunks
192
+ for (let i = 0; i < 10; i++) {
193
+ s3AwareChunker.recordChunkResult(16 * 1024 * 1024, 10000, false);
194
+ }
195
+
196
+ const decision = s3AwareChunker.getNextChunkSize();
197
+ // Even with failures, should never go below datastore minimum
198
+ expect(decision.size).toBeGreaterThanOrEqual(s3Constraints.minChunkSize);
199
+ });
200
+ });
201
+
202
+ describe("UploadMetrics", () => {
203
+ let metrics: UploadMetrics;
204
+
205
+ beforeEach(() => {
206
+ metrics = new UploadMetrics();
207
+ });
208
+
209
+ it("should track upload session", () => {
210
+ const uploadId = "test-upload";
211
+ const totalSize = 1024 * 1024; // 1MB
212
+
213
+ metrics.startSession(uploadId, totalSize, true);
214
+
215
+ // Record some chunks
216
+ metrics.recordChunk({
217
+ chunkIndex: 0,
218
+ size: 512 * 1024,
219
+ duration: 1000,
220
+ speed: 512 * 1024, // 512 KB/s
221
+ success: true,
222
+ retryCount: 0,
223
+ });
224
+
225
+ metrics.recordChunk({
226
+ chunkIndex: 1,
227
+ size: 512 * 1024,
228
+ duration: 1000,
229
+ speed: 512 * 1024, // 512 KB/s
230
+ success: true,
231
+ retryCount: 0,
232
+ });
233
+
234
+ const sessionMetrics = metrics.endSession();
235
+ expect(sessionMetrics).toBeDefined();
236
+ expect(sessionMetrics?.uploadId).toBe(uploadId);
237
+ expect(sessionMetrics?.totalSize).toBe(totalSize);
238
+ expect(sessionMetrics?.chunksCompleted).toBe(2);
239
+ });
240
+
241
+ it("should generate performance insights", () => {
242
+ // Record multiple chunks with varying performance and sizes
243
+ for (let i = 0; i < 10; i++) {
244
+ const chunkSize = (128 + i * 128) * 1024; // Varying chunk sizes from 128KB to 1.25MB
245
+ metrics.recordChunk({
246
+ chunkIndex: i,
247
+ size: chunkSize,
248
+ duration: 1000 + Math.random() * 500, // 1-1.5 seconds
249
+ speed: chunkSize / (1 + Math.random() * 0.5),
250
+ success: true,
251
+ retryCount: 0,
252
+ });
253
+ }
254
+
255
+ const insights = metrics.getPerformanceInsights();
256
+ expect(insights.overallEfficiency).toBeGreaterThan(0);
257
+ expect(insights.networkStability).toBeGreaterThan(0);
258
+ expect(insights.recommendations).toBeInstanceOf(Array);
259
+ expect(insights.optimalChunkSizeRange.min).toBeGreaterThan(0);
260
+ expect(insights.optimalChunkSizeRange.max).toBeGreaterThanOrEqual(
261
+ insights.optimalChunkSizeRange.min,
262
+ );
263
+ });
264
+
265
+ it("should export all metrics data", () => {
266
+ metrics.startSession("test", 1024, true);
267
+ metrics.recordChunk({
268
+ chunkIndex: 0,
269
+ size: 1024,
270
+ duration: 1000,
271
+ speed: 1024,
272
+ success: true,
273
+ retryCount: 0,
274
+ });
275
+
276
+ const exported = metrics.exportMetrics();
277
+ expect(exported.session).toBeDefined();
278
+ expect(exported.chunks).toBeInstanceOf(Array);
279
+ expect(exported.chunks).toHaveLength(1);
280
+ expect(exported.insights).toBeDefined();
281
+ });
282
+ });
283
+
284
+ describe("Integration", () => {
285
+ it("should work together for adaptive chunking", () => {
286
+ const monitor = new NetworkMonitor();
287
+ const chunker = new SmartChunker(monitor);
288
+ const metrics = new UploadMetrics();
289
+
290
+ // Start a session
291
+ metrics.startSession("integration-test", 10 * 1024 * 1024, true);
292
+
293
+ // Simulate upload process
294
+ for (let i = 0; i < 10; i++) {
295
+ const decision = chunker.getNextChunkSize();
296
+ const chunkSize = decision.size;
297
+
298
+ // Simulate upload (vary performance)
299
+ const duration = 1000 + Math.random() * 2000; // 1-3 seconds
300
+ const success = Math.random() > 0.1; // 90% success rate
301
+
302
+ // Record in monitor and chunker
303
+ monitor.recordUpload(chunkSize, duration, success);
304
+ chunker.recordChunkResult(chunkSize, duration, success);
305
+
306
+ // Record in metrics
307
+ metrics.recordChunk({
308
+ chunkIndex: i,
309
+ size: chunkSize,
310
+ duration,
311
+ speed: success ? chunkSize / (duration / 1000) : 0,
312
+ success,
313
+ retryCount: success ? 0 : 1,
314
+ networkCondition: monitor.getNetworkCondition().type,
315
+ chunkingStrategy: decision.strategy,
316
+ });
317
+ }
318
+
319
+ const sessionMetrics = metrics.endSession();
320
+ const networkCondition = monitor.getNetworkCondition();
321
+ const insights = metrics.getPerformanceInsights();
322
+
323
+ expect(sessionMetrics).toBeDefined();
324
+ expect(networkCondition.type).toBeDefined();
325
+ expect(insights.recommendations.length).toBeGreaterThan(0);
326
+ });
327
+ });
328
+
329
+ describe("ChunkBuffer", () => {
330
+ it("should buffer small chunks until threshold is met", () => {
331
+ const buffer = new ChunkBuffer({
332
+ minThreshold: 5 * 1024 * 1024, // 5MB threshold like S3
333
+ });
334
+
335
+ // Add several small chunks
336
+ const chunk1 = new Uint8Array(1024 * 1024); // 1MB
337
+ const chunk2 = new Uint8Array(2 * 1024 * 1024); // 2MB
338
+ const chunk3 = new Uint8Array(1024 * 1024); // 1MB - total 4MB so far
339
+ const chunk4 = new Uint8Array(2 * 1024 * 1024); // 2MB - this will make it 6MB, >= 5MB threshold
340
+
341
+ expect(buffer.add(chunk1)).toBeNull(); // 1MB - not enough yet
342
+ expect(buffer.add(chunk2)).toBeNull(); // 3MB - still not enough
343
+ expect(buffer.add(chunk3)).toBeNull(); // 4MB - still not enough
344
+
345
+ const result = buffer.add(chunk4); // Now 6MB total, should flush
346
+ expect(result).not.toBeNull();
347
+ expect(result?.size).toBe(6 * 1024 * 1024); // 6MB total
348
+ expect(result?.data.length).toBe(6 * 1024 * 1024);
349
+ });
350
+
351
+ it("should flush on timeout even if threshold not met", async () => {
352
+ const buffer = new ChunkBuffer({
353
+ minThreshold: 5 * 1024 * 1024, // 5MB
354
+ timeoutMs: 100, // 100ms timeout
355
+ });
356
+
357
+ const smallChunk = new Uint8Array(1024 * 1024); // 1MB
358
+ expect(buffer.add(smallChunk)).toBeNull();
359
+
360
+ // Wait for timeout
361
+ await new Promise((resolve) => setTimeout(resolve, 150));
362
+
363
+ // Should flush due to timeout
364
+ expect(buffer.shouldFlush()).toBe(true);
365
+ const result = buffer.flush();
366
+ expect(result).not.toBeNull();
367
+ expect(result?.size).toBe(1024 * 1024); // 1MB
368
+ });
369
+
370
+ it("should flush when max buffer size is reached", () => {
371
+ const buffer = new ChunkBuffer({
372
+ minThreshold: 10 * 1024 * 1024, // 10MB threshold
373
+ maxBufferSize: 6 * 1024 * 1024, // 6MB max buffer
374
+ });
375
+
376
+ const chunk = new Uint8Array(3 * 1024 * 1024); // 3MB chunks
377
+
378
+ expect(buffer.add(chunk)).toBeNull(); // 3MB - not max yet
379
+ const result = buffer.add(chunk); // 6MB - should hit max buffer
380
+
381
+ expect(result).not.toBeNull();
382
+ expect(result?.size).toBe(6 * 1024 * 1024);
383
+ });
384
+
385
+ it("should provide accurate buffer info", () => {
386
+ const buffer = new ChunkBuffer({
387
+ minThreshold: 5 * 1024 * 1024,
388
+ });
389
+
390
+ const chunk = new Uint8Array(2 * 1024 * 1024); // 2MB
391
+ buffer.add(chunk);
392
+
393
+ const info = buffer.getBufferInfo();
394
+ expect(info.size).toBe(2 * 1024 * 1024);
395
+ expect(info.chunkCount).toBe(1);
396
+ expect(info.isReadyToFlush).toBe(false);
397
+ expect(info.timeSinceLastAdd).toBeLessThan(100); // Recent
398
+ });
399
+ });