@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
@@ -0,0 +1,451 @@
1
+ import type { NetworkCondition, NetworkMonitor } from "./network-monitor";
2
+ import type { ConnectionMetrics } from "./services/http-client";
3
+
4
+ export interface ChunkingStrategy {
5
+ name: string;
6
+ minChunkSize: number;
7
+ maxChunkSize: number;
8
+ initialChunkSize: number;
9
+ adaptationRate: number; // how quickly to adapt (0-1)
10
+ }
11
+
12
+ export interface DatastoreConstraints {
13
+ minChunkSize: number;
14
+ maxChunkSize: number;
15
+ optimalChunkSize: number;
16
+ requiresOrderedChunks?: boolean;
17
+ }
18
+
19
+ export interface SmartChunkerConfig {
20
+ enabled?: boolean;
21
+ fallbackChunkSize?: number;
22
+ minChunkSize?: number;
23
+ maxChunkSize?: number;
24
+ initialChunkSize?: number;
25
+ targetUtilization?: number; // target bandwidth utilization (0-1)
26
+ adaptationRate?: number;
27
+ conservativeMode?: boolean;
28
+ connectionPoolingAware?: boolean; // enable connection pooling optimizations
29
+ datastoreConstraints?: DatastoreConstraints;
30
+ }
31
+
32
+ export interface ChunkSizeDecision {
33
+ size: number;
34
+ strategy: string;
35
+ reason: string;
36
+ networkCondition: NetworkCondition;
37
+ }
38
+
39
+ const DEFAULT_STRATEGIES: Record<string, ChunkingStrategy> = {
40
+ conservative: {
41
+ name: "conservative",
42
+ minChunkSize: 64 * 1024, // 64 KB
43
+ maxChunkSize: 2 * 1024 * 1024, // 2 MB
44
+ initialChunkSize: 256 * 1024, // 256 KB
45
+ adaptationRate: 0.1,
46
+ },
47
+ balanced: {
48
+ name: "balanced",
49
+ minChunkSize: 128 * 1024, // 128 KB
50
+ maxChunkSize: 8 * 1024 * 1024, // 8 MB
51
+ initialChunkSize: 512 * 1024, // 512 KB
52
+ adaptationRate: 0.2,
53
+ },
54
+ aggressive: {
55
+ name: "aggressive",
56
+ minChunkSize: 256 * 1024, // 256 KB
57
+ maxChunkSize: 32 * 1024 * 1024, // 32 MB
58
+ initialChunkSize: 1024 * 1024, // 1 MB
59
+ adaptationRate: 0.3,
60
+ },
61
+ };
62
+
63
+ const S3_OPTIMIZED_STRATEGIES: Record<string, ChunkingStrategy> = {
64
+ conservative: {
65
+ name: "s3-conservative",
66
+ minChunkSize: 5 * 1024 * 1024, // 5MB - S3 minimum
67
+ maxChunkSize: 64 * 1024 * 1024, // 64MB
68
+ initialChunkSize: 8 * 1024 * 1024, // 8MB
69
+ adaptationRate: 0.1,
70
+ },
71
+ balanced: {
72
+ name: "s3-balanced",
73
+ minChunkSize: 5 * 1024 * 1024, // 5MB - S3 minimum
74
+ maxChunkSize: 128 * 1024 * 1024, // 128MB
75
+ initialChunkSize: 16 * 1024 * 1024, // 16MB
76
+ adaptationRate: 0.2,
77
+ },
78
+ aggressive: {
79
+ name: "s3-aggressive",
80
+ minChunkSize: 5 * 1024 * 1024, // 5MB - S3 minimum
81
+ maxChunkSize: 256 * 1024 * 1024, // 256MB
82
+ initialChunkSize: 32 * 1024 * 1024, // 32MB
83
+ adaptationRate: 0.3,
84
+ },
85
+ };
86
+
87
+ export class SmartChunker {
88
+ private config: Required<Omit<SmartChunkerConfig, "datastoreConstraints">> & {
89
+ datastoreConstraints?: DatastoreConstraints;
90
+ };
91
+ private networkMonitor: NetworkMonitor;
92
+ private currentChunkSize: number;
93
+ private lastDecision: ChunkSizeDecision | null = null;
94
+ private consecutiveFailures = 0;
95
+ private consecutiveSuccesses = 0;
96
+ private connectionMetrics: ConnectionMetrics | null = null;
97
+
98
+ constructor(networkMonitor: NetworkMonitor, config: SmartChunkerConfig = {}) {
99
+ this.networkMonitor = networkMonitor;
100
+ this.config = {
101
+ enabled: config.enabled ?? true,
102
+ fallbackChunkSize: config.fallbackChunkSize ?? 1024 * 1024, // 1 MB
103
+ minChunkSize: config.minChunkSize ?? 64 * 1024, // 64 KB
104
+ maxChunkSize: config.maxChunkSize ?? 32 * 1024 * 1024, // 32 MB
105
+ initialChunkSize: config.initialChunkSize ?? 512 * 1024, // 512 KB
106
+ targetUtilization: config.targetUtilization ?? 0.85, // 85%
107
+ adaptationRate: config.adaptationRate ?? 0.2,
108
+ conservativeMode: config.conservativeMode ?? false,
109
+ connectionPoolingAware: config.connectionPoolingAware ?? true, // Enable by default
110
+ datastoreConstraints: config.datastoreConstraints,
111
+ };
112
+
113
+ this.currentChunkSize = this.getEffectiveInitialChunkSize();
114
+ }
115
+
116
+ private getEffectiveInitialChunkSize(): number {
117
+ if (this.config.datastoreConstraints) {
118
+ return Math.max(
119
+ this.config.initialChunkSize,
120
+ this.config.datastoreConstraints.optimalChunkSize,
121
+ );
122
+ }
123
+ return this.config.initialChunkSize;
124
+ }
125
+
126
+ private applyDatastoreConstraints(size: number): number {
127
+ if (this.config.datastoreConstraints) {
128
+ return Math.max(
129
+ this.config.datastoreConstraints.minChunkSize,
130
+ Math.min(this.config.datastoreConstraints.maxChunkSize, size),
131
+ );
132
+ }
133
+ return size;
134
+ }
135
+
136
+ getNextChunkSize(remainingBytes?: number): ChunkSizeDecision {
137
+ if (!this.config.enabled) {
138
+ return {
139
+ size: this.config.fallbackChunkSize,
140
+ strategy: "fixed",
141
+ reason: "Smart chunking disabled",
142
+ networkCondition: { type: "unknown", confidence: 0 },
143
+ };
144
+ }
145
+
146
+ const networkCondition = this.networkMonitor.getNetworkCondition();
147
+
148
+ let newSize = this.currentChunkSize;
149
+ let strategy = "adaptive";
150
+ let reason = "";
151
+
152
+ // If we don't have enough data, use initial strategy
153
+ if (networkCondition.type === "unknown") {
154
+ newSize = this.config.initialChunkSize;
155
+ strategy = "initial";
156
+ reason = "Insufficient network data";
157
+ } else {
158
+ const chunkingStrategy = this.selectStrategy(networkCondition);
159
+ newSize = this.calculateOptimalChunkSize(
160
+ networkCondition,
161
+ chunkingStrategy,
162
+ );
163
+ strategy = chunkingStrategy.name;
164
+ reason = `Network condition: ${networkCondition.type} (confidence: ${Math.round(networkCondition.confidence * 100)}%)`;
165
+ }
166
+
167
+ // Apply remaining bytes limit
168
+ if (remainingBytes && remainingBytes < newSize) {
169
+ newSize = remainingBytes;
170
+ reason += `, limited by remaining bytes (${remainingBytes})`;
171
+ }
172
+
173
+ // Apply datastore constraints first
174
+ newSize = this.applyDatastoreConstraints(newSize);
175
+
176
+ // Ensure bounds
177
+ newSize = Math.max(
178
+ this.config.minChunkSize,
179
+ Math.min(this.config.maxChunkSize, newSize),
180
+ );
181
+
182
+ this.currentChunkSize = newSize;
183
+ this.lastDecision = {
184
+ size: newSize,
185
+ strategy,
186
+ reason,
187
+ networkCondition,
188
+ };
189
+
190
+ return this.lastDecision;
191
+ }
192
+
193
+ recordChunkResult(size: number, duration: number, success: boolean): void {
194
+ // Record the result in network monitor
195
+ this.networkMonitor.recordUpload(size, duration, success);
196
+
197
+ // Update our internal state
198
+ if (success) {
199
+ this.consecutiveSuccesses++;
200
+ this.consecutiveFailures = 0;
201
+ } else {
202
+ this.consecutiveFailures++;
203
+ this.consecutiveSuccesses = 0;
204
+ }
205
+
206
+ // Adjust chunk size based on recent performance
207
+ this.adaptChunkSize(success, duration, size);
208
+ }
209
+
210
+ getCurrentChunkSize(): number {
211
+ return this.currentChunkSize;
212
+ }
213
+
214
+ getLastDecision(): ChunkSizeDecision | null {
215
+ return this.lastDecision;
216
+ }
217
+
218
+ reset(): void {
219
+ this.currentChunkSize = this.config.initialChunkSize;
220
+ this.consecutiveFailures = 0;
221
+ this.consecutiveSuccesses = 0;
222
+ this.lastDecision = null;
223
+ this.connectionMetrics = null;
224
+ }
225
+
226
+ /**
227
+ * Update connection metrics for connection pooling aware optimizations
228
+ */
229
+ updateConnectionMetrics(metrics: ConnectionMetrics): void {
230
+ this.connectionMetrics = metrics;
231
+ }
232
+
233
+ /**
234
+ * Get insights about connection pooling impact on chunking
235
+ */
236
+ getConnectionPoolingInsights(): {
237
+ isOptimized: boolean;
238
+ reuseRate: number;
239
+ recommendedMinChunkSize: number;
240
+ connectionOverhead: number;
241
+ } {
242
+ if (!this.connectionMetrics || !this.config.connectionPoolingAware) {
243
+ return {
244
+ isOptimized: false,
245
+ reuseRate: 0,
246
+ recommendedMinChunkSize: this.config.minChunkSize,
247
+ connectionOverhead: 0,
248
+ };
249
+ }
250
+
251
+ const reuseRate = this.connectionMetrics.reuseRate;
252
+ const avgConnectionTime = this.connectionMetrics.averageConnectionTime;
253
+
254
+ // With good connection reuse, we can afford smaller chunks
255
+ const connectionOverhead = (1 - reuseRate) * avgConnectionTime;
256
+ const recommendedMinChunkSize = Math.max(
257
+ this.config.minChunkSize,
258
+ Math.floor(connectionOverhead * 10000), // 10KB per ms of overhead
259
+ );
260
+
261
+ return {
262
+ isOptimized: reuseRate > 0.7,
263
+ reuseRate,
264
+ recommendedMinChunkSize,
265
+ connectionOverhead,
266
+ };
267
+ }
268
+
269
+ private selectStrategy(networkCondition: NetworkCondition): ChunkingStrategy {
270
+ const fallbackStrategy: ChunkingStrategy = {
271
+ name: "fallback",
272
+ minChunkSize: 128 * 1024,
273
+ maxChunkSize: 4 * 1024 * 1024,
274
+ initialChunkSize: 512 * 1024,
275
+ adaptationRate: 0.2,
276
+ };
277
+
278
+ // Use S3-optimized strategies if datastore constraints indicate S3 (5MB minimum)
279
+ const isS3Like =
280
+ this.config.datastoreConstraints?.minChunkSize === 5 * 1024 * 1024;
281
+ const strategiesSource = isS3Like
282
+ ? S3_OPTIMIZED_STRATEGIES
283
+ : DEFAULT_STRATEGIES;
284
+
285
+ if (this.config.conservativeMode) {
286
+ return strategiesSource.conservative ?? fallbackStrategy;
287
+ }
288
+
289
+ // Enhanced strategy selection with connection pooling awareness
290
+ let baseStrategy: ChunkingStrategy;
291
+
292
+ switch (networkCondition.type) {
293
+ case "fast":
294
+ baseStrategy =
295
+ networkCondition.confidence > 0.7
296
+ ? (strategiesSource.aggressive ?? fallbackStrategy)
297
+ : (strategiesSource.balanced ?? fallbackStrategy);
298
+ break;
299
+ case "slow":
300
+ baseStrategy = strategiesSource.conservative ?? fallbackStrategy;
301
+ break;
302
+ case "unstable":
303
+ baseStrategy = strategiesSource.conservative ?? fallbackStrategy;
304
+ break;
305
+ default:
306
+ baseStrategy = strategiesSource.balanced ?? fallbackStrategy;
307
+ }
308
+
309
+ // Apply connection pooling optimizations
310
+ if (this.config.connectionPoolingAware && this.connectionMetrics) {
311
+ return this.optimizeStrategyForConnectionPooling(baseStrategy);
312
+ }
313
+
314
+ return baseStrategy;
315
+ }
316
+
317
+ /**
318
+ * Optimize chunking strategy based on connection pooling performance
319
+ */
320
+ private optimizeStrategyForConnectionPooling(
321
+ strategy: ChunkingStrategy,
322
+ ): ChunkingStrategy {
323
+ if (!this.connectionMetrics) return strategy;
324
+
325
+ const insights = this.getConnectionPoolingInsights();
326
+ const reuseRate = insights.reuseRate;
327
+
328
+ // High connection reuse allows for more aggressive chunking
329
+ if (reuseRate > 0.8) {
330
+ return {
331
+ ...strategy,
332
+ name: `${strategy.name}-pooled-aggressive`,
333
+ minChunkSize: Math.max(strategy.minChunkSize * 0.5, 32 * 1024), // Smaller min chunks
334
+ adaptationRate: Math.min(strategy.adaptationRate * 1.3, 0.5), // Faster adaptation
335
+ };
336
+ }
337
+
338
+ // Good connection reuse allows moderate optimization
339
+ if (reuseRate > 0.5) {
340
+ return {
341
+ ...strategy,
342
+ name: `${strategy.name}-pooled-moderate`,
343
+ minChunkSize: Math.max(strategy.minChunkSize * 0.75, 64 * 1024),
344
+ adaptationRate: Math.min(strategy.adaptationRate * 1.1, 0.4),
345
+ };
346
+ }
347
+
348
+ // Poor connection reuse requires conservative approach
349
+ return {
350
+ ...strategy,
351
+ name: `${strategy.name}-pooled-conservative`,
352
+ minChunkSize: Math.max(
353
+ strategy.minChunkSize * 1.5,
354
+ insights.recommendedMinChunkSize,
355
+ ),
356
+ adaptationRate: strategy.adaptationRate * 0.8,
357
+ };
358
+ }
359
+
360
+ private calculateOptimalChunkSize(
361
+ networkCondition: NetworkCondition,
362
+ strategy: ChunkingStrategy,
363
+ ): number {
364
+ let targetSize = this.currentChunkSize;
365
+
366
+ // Base calculation on current throughput
367
+ const optimalThroughput = this.networkMonitor.getOptimalThroughput();
368
+
369
+ if (optimalThroughput > 0) {
370
+ // Calculate target chunk duration (aim for 2-5 seconds per chunk)
371
+ const targetDuration = this.getTargetChunkDuration(networkCondition);
372
+ const theoreticalSize =
373
+ optimalThroughput * targetDuration * this.config.targetUtilization;
374
+
375
+ // Blend current size with theoretical optimal size
376
+ const blendFactor = strategy.adaptationRate;
377
+ targetSize =
378
+ this.currentChunkSize * (1 - blendFactor) +
379
+ theoreticalSize * blendFactor;
380
+ }
381
+
382
+ // Apply strategy constraints
383
+ targetSize = Math.max(
384
+ strategy.minChunkSize,
385
+ Math.min(strategy.maxChunkSize, targetSize),
386
+ );
387
+
388
+ // Apply failure-based adjustments
389
+ if (this.consecutiveFailures > 0) {
390
+ // Reduce size on failures
391
+ const reductionFactor = Math.min(0.5, this.consecutiveFailures * 0.2);
392
+ targetSize *= 1 - reductionFactor;
393
+ } else if (this.consecutiveSuccesses > 2) {
394
+ // Gradually increase size on consistent success
395
+ const increaseFactor = Math.min(0.3, this.consecutiveSuccesses * 0.05);
396
+ targetSize *= 1 + increaseFactor;
397
+ }
398
+
399
+ return Math.round(targetSize);
400
+ }
401
+
402
+ private getTargetChunkDuration(networkCondition: NetworkCondition): number {
403
+ switch (networkCondition.type) {
404
+ case "fast":
405
+ return 3; // 3 seconds for fast connections
406
+ case "slow":
407
+ return 5; // 5 seconds for slow connections to reduce overhead
408
+ case "unstable":
409
+ return 2; // 2 seconds for unstable connections for quick recovery
410
+ default:
411
+ return 3; // Default to 3 seconds
412
+ }
413
+ }
414
+
415
+ private adaptChunkSize(
416
+ success: boolean,
417
+ duration: number,
418
+ size: number,
419
+ ): void {
420
+ if (!success) {
421
+ // On failure, be more conservative
422
+ this.currentChunkSize = Math.max(
423
+ this.config.minChunkSize,
424
+ this.currentChunkSize * 0.8,
425
+ );
426
+ return;
427
+ }
428
+
429
+ // On success, check if we should adjust based on performance
430
+ const throughput = size / (duration / 1000); // bytes per second
431
+ const metrics = this.networkMonitor.getCurrentMetrics();
432
+
433
+ if (metrics.averageSpeed > 0) {
434
+ const utilizationRatio = throughput / metrics.averageSpeed;
435
+
436
+ if (utilizationRatio < this.config.targetUtilization * 0.8) {
437
+ // We're not utilizing bandwidth well, try larger chunks
438
+ this.currentChunkSize = Math.min(
439
+ this.config.maxChunkSize,
440
+ this.currentChunkSize * 1.1,
441
+ );
442
+ } else if (utilizationRatio > this.config.targetUtilization * 1.2) {
443
+ // We might be overloading, try smaller chunks
444
+ this.currentChunkSize = Math.max(
445
+ this.config.minChunkSize,
446
+ this.currentChunkSize * 0.95,
447
+ );
448
+ }
449
+ }
450
+ }
451
+ }
@@ -0,0 +1,186 @@
1
+ import type { IdGenerationService } from "../services/id-generation-service";
2
+ import type { StorageService } from "../services/storage-service";
3
+ import {
4
+ type PreviousUpload,
5
+ previousUploadSchema,
6
+ } from "../types/previous-upload";
7
+
8
+ /**
9
+ * Client-side storage interface for managing upload resumption data.
10
+ *
11
+ * Provides methods to store, retrieve, and manage previous upload information,
12
+ * enabling the client to resume interrupted uploads from where they left off.
13
+ * This is essential for implementing reliable upload resumption across sessions.
14
+ *
15
+ * Storage keys are namespaced with "uploadista::" prefix and organized by
16
+ * file fingerprint to allow quick lookup of resumable uploads.
17
+ *
18
+ * @example Finding resumable uploads
19
+ * ```typescript
20
+ * const storage = createClientStorage(localStorage);
21
+ *
22
+ * // Find all previous uploads
23
+ * const allUploads = await storage.findAllUploads();
24
+ *
25
+ * // Find uploads for a specific file
26
+ * const fingerprint = await computeFingerprint(file);
27
+ * const matches = await storage.findUploadsByFingerprint(fingerprint);
28
+ *
29
+ * if (matches.length > 0) {
30
+ * // Resume from the most recent upload
31
+ * const uploadId = matches[0].uploadId;
32
+ * await resumeUpload(uploadId);
33
+ * }
34
+ * ```
35
+ */
36
+ export type ClientStorage = {
37
+ /**
38
+ * Retrieves all stored upload records from client storage.
39
+ *
40
+ * Useful for debugging or displaying a list of resumable uploads to the user.
41
+ *
42
+ * @returns Array of all previous upload records
43
+ */
44
+ findAllUploads: () => Promise<PreviousUpload[]>;
45
+
46
+ /**
47
+ * Finds previous upload records matching a specific file fingerprint.
48
+ *
49
+ * This is the primary method for discovering resumable uploads.
50
+ * Returns uploads sorted by most recent first.
51
+ *
52
+ * @param fingerprint - The file fingerprint to search for
53
+ * @returns Array of matching upload records, or empty array if none found
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * const fingerprint = await computeFingerprint(file);
58
+ * const previous = await storage.findUploadsByFingerprint(fingerprint);
59
+ *
60
+ * if (previous.length > 0) {
61
+ * console.log(`Found ${previous.length} resumable uploads`);
62
+ * console.log(`Last upload was ${previous[0].offset} bytes`);
63
+ * }
64
+ * ```
65
+ */
66
+ findUploadsByFingerprint: (fingerprint: string) => Promise<PreviousUpload[]>;
67
+
68
+ /**
69
+ * Removes an upload record from client storage.
70
+ *
71
+ * Called after an upload completes successfully or is explicitly cancelled
72
+ * to clean up storage and prevent stale resumption attempts.
73
+ *
74
+ * @param clientStorageKey - The storage key returned by addUpload
75
+ *
76
+ * @example Cleanup after successful upload
77
+ * ```typescript
78
+ * await uploadFile(file);
79
+ * await storage.removeUpload(storageKey);
80
+ * ```
81
+ */
82
+ removeUpload: (clientStorageKey: string) => Promise<void>;
83
+
84
+ /**
85
+ * Stores an upload record in client storage for future resumption.
86
+ *
87
+ * Creates a namespaced storage key that includes the file fingerprint,
88
+ * making it easy to find resumable uploads later.
89
+ *
90
+ * @param fingerprint - File fingerprint for organizing uploads
91
+ * @param upload - Upload metadata to store (uploadId, offset, etc.)
92
+ * @param options - Options object containing ID generation service
93
+ * @returns The storage key that can be used to remove this upload later, or undefined if storage failed
94
+ *
95
+ * @example Storing upload progress
96
+ * ```typescript
97
+ * const fingerprint = await computeFingerprint(file);
98
+ * const key = await storage.addUpload(
99
+ * fingerprint,
100
+ * { uploadId: 'abc123', offset: 1024000 },
101
+ * { generateId: idService }
102
+ * );
103
+ *
104
+ * // Later, remove when complete
105
+ * if (key) await storage.removeUpload(key);
106
+ * ```
107
+ */
108
+ addUpload: (
109
+ fingerprint: string,
110
+ upload: PreviousUpload,
111
+ { generateId }: { generateId: IdGenerationService },
112
+ ) => Promise<string | undefined>;
113
+ };
114
+
115
+ /**
116
+ * Creates a ClientStorage instance using the provided storage service.
117
+ *
118
+ * This factory function wraps a platform-specific StorageService (e.g., localStorage,
119
+ * AsyncStorage) with the ClientStorage interface, providing a consistent API
120
+ * for upload resumption across different platforms.
121
+ *
122
+ * @param storageService - Platform-specific storage implementation
123
+ * @returns ClientStorage instance for managing upload records
124
+ *
125
+ * @example Browser with localStorage
126
+ * ```typescript
127
+ * const storage = createClientStorage({
128
+ * find: async (prefix) => {
129
+ * const items: Record<string, string> = {};
130
+ * for (let i = 0; i < localStorage.length; i++) {
131
+ * const key = localStorage.key(i);
132
+ * if (key?.startsWith(prefix)) {
133
+ * items[key] = localStorage.getItem(key) || '';
134
+ * }
135
+ * }
136
+ * return items;
137
+ * },
138
+ * setItem: async (key, value) => localStorage.setItem(key, value),
139
+ * removeItem: async (key) => localStorage.removeItem(key),
140
+ * });
141
+ * ```
142
+ *
143
+ * @example React Native with AsyncStorage
144
+ * ```typescript
145
+ * const storage = createClientStorage({
146
+ * find: async (prefix) => {
147
+ * const keys = await AsyncStorage.getAllKeys();
148
+ * const matching = keys.filter(k => k.startsWith(prefix));
149
+ * const pairs = await AsyncStorage.multiGet(matching);
150
+ * return Object.fromEntries(pairs);
151
+ * },
152
+ * setItem: async (key, value) => AsyncStorage.setItem(key, value),
153
+ * removeItem: async (key) => AsyncStorage.removeItem(key),
154
+ * });
155
+ * ```
156
+ */
157
+ export function createClientStorage(
158
+ storageService: StorageService,
159
+ ): ClientStorage {
160
+ return {
161
+ findAllUploads: async () => {
162
+ const items = await storageService.find("uploadista::");
163
+ return Object.values(items).map((item) =>
164
+ previousUploadSchema.parse(JSON.parse(item)),
165
+ );
166
+ },
167
+ findUploadsByFingerprint: async (fingerprint: string) => {
168
+ const items = await storageService.find(`uploadista::${fingerprint}`);
169
+ return Object.values(items).map((item) =>
170
+ previousUploadSchema.parse(JSON.parse(item)),
171
+ );
172
+ },
173
+ removeUpload: (clientStorageKey: string) =>
174
+ storageService.removeItem(clientStorageKey),
175
+ addUpload: async (
176
+ fingerprint: string,
177
+ upload: PreviousUpload,
178
+ { generateId }: { generateId: IdGenerationService },
179
+ ) => {
180
+ const key = generateId.generate();
181
+ const clientStorageKey = `uploadista::${fingerprint}::${key}`;
182
+ await storageService.setItem(clientStorageKey, JSON.stringify(upload));
183
+ return clientStorageKey;
184
+ },
185
+ };
186
+ }
@@ -0,0 +1,33 @@
1
+ import type { StorageService } from "../services/storage-service";
2
+
3
+ /**
4
+ * In-memory fallback storage service for Expo
5
+ * Used when AsyncStorage is not available or for testing
6
+ */
7
+ export function createInMemoryStorageService(): StorageService {
8
+ const storage = new Map<string, string>();
9
+
10
+ return {
11
+ async getItem(key: string): Promise<string | null> {
12
+ return storage.get(key) ?? null;
13
+ },
14
+
15
+ async setItem(key: string, value: string): Promise<void> {
16
+ storage.set(key, value);
17
+ },
18
+
19
+ async removeItem(key: string): Promise<void> {
20
+ storage.delete(key);
21
+ },
22
+
23
+ async findAll(): Promise<Record<string, string>> {
24
+ return Object.fromEntries(storage.entries());
25
+ },
26
+
27
+ async find(prefix: string): Promise<Record<string, string>> {
28
+ return Object.fromEntries(
29
+ Array.from(storage.entries()).filter(([key]) => key.startsWith(prefix)),
30
+ );
31
+ },
32
+ };
33
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./client-storage";
2
+ export * from "./in-memory-storage-service";
@@ -0,0 +1,5 @@
1
+ export interface BufferedChunk {
2
+ data: Uint8Array;
3
+ size: number;
4
+ timestamp: number;
5
+ }
@@ -0,0 +1,11 @@
1
+ export interface ChunkMetrics {
2
+ chunkIndex: number;
3
+ size: number;
4
+ duration: number;
5
+ speed: number; // bytes per second
6
+ success: boolean;
7
+ retryCount: number;
8
+ timestamp: number;
9
+ networkCondition?: string;
10
+ chunkingStrategy?: string;
11
+ }
@@ -0,0 +1,14 @@
1
+ import type { UploadFile } from "@uploadista/core/types";
2
+
3
+ export type FlowResult<TOutput = UploadFile> =
4
+ | {
5
+ type: "success";
6
+ value: TOutput;
7
+ }
8
+ | {
9
+ type: "error";
10
+ error: Error;
11
+ }
12
+ | {
13
+ type: "cancelled";
14
+ };