@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,218 @@
1
+ import type { HttpClient } from "../services/http-client";
2
+ import { BaseAuthManager, type SaasAuthConfig } from "./types";
3
+
4
+ /**
5
+ * Token response from the auth server
6
+ */
7
+ export type TokenResponse = {
8
+ /** JWT token to use for authentication */
9
+ token: string;
10
+ /** Token expiration time in seconds (optional) */
11
+ expiresIn?: number;
12
+ };
13
+
14
+ /**
15
+ * Cached token information
16
+ */
17
+ type CachedToken = {
18
+ token: string;
19
+ expiresAt?: number; // Unix timestamp in milliseconds
20
+ };
21
+
22
+ /**
23
+ * SaaS auth manager - handles JWT token exchange with an auth server.
24
+ *
25
+ * Token exchange flow:
26
+ * 1. Client calls getCredentials() to get user credentials
27
+ * 2. Manager sends credentials to authServerUrl
28
+ * 3. Auth server validates credentials and returns JWT token
29
+ * 4. Manager caches token and attaches it to uploadista requests
30
+ * 5. Token is cached per job to minimize auth overhead
31
+ *
32
+ * Security: API keys are kept server-side in the auth server, never exposed to clients.
33
+ */
34
+ export class SaasAuthManager extends BaseAuthManager {
35
+ /** Token cache: maps job ID to cached token */
36
+ private tokenCache = new Map<string, CachedToken>();
37
+
38
+ /** Global token for requests without a specific job ID */
39
+ private globalToken: CachedToken | null = null;
40
+
41
+ constructor(
42
+ private config: SaasAuthConfig,
43
+ private httpClient: HttpClient,
44
+ ) {
45
+ super("saas");
46
+ }
47
+
48
+ /**
49
+ * Fetch a JWT token from the auth server using user credentials.
50
+ *
51
+ * @returns Token response with JWT and optional expiry
52
+ * @throws Error if auth server is unreachable or returns an error
53
+ */
54
+ async fetchToken(): Promise<TokenResponse> {
55
+ try {
56
+ // Make POST request to auth server
57
+ const response = await this.httpClient.request(
58
+ `${this.config.authServerUrl}/${this.config.clientId}`,
59
+ {
60
+ method: "GET",
61
+ headers: {
62
+ "Content-Type": "application/json",
63
+ },
64
+ },
65
+ );
66
+
67
+ // Handle error responses
68
+ if (!response.ok) {
69
+ const errorText = await response.text();
70
+ let errorMessage = `Auth server returned ${response.status}`;
71
+
72
+ try {
73
+ const errorJson = JSON.parse(errorText);
74
+ errorMessage = errorJson.error || errorJson.message || errorMessage;
75
+ } catch {
76
+ // If response is not JSON, use status text
77
+ errorMessage = errorText || response.statusText || errorMessage;
78
+ }
79
+
80
+ throw new Error(errorMessage);
81
+ }
82
+
83
+ // Parse token response
84
+ const data = (await response.json()) as TokenResponse;
85
+
86
+ if (!data.token || typeof data.token !== "string") {
87
+ throw new Error(
88
+ "Auth server response missing 'token' field or token is not a string",
89
+ );
90
+ }
91
+
92
+ return data;
93
+ } catch (error) {
94
+ // Wrap errors with context
95
+ if (error instanceof Error) {
96
+ throw new Error(`Failed to fetch auth token: ${error.message}`);
97
+ }
98
+ throw new Error(`Failed to fetch auth token: ${String(error)}`);
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Get a cached token for a specific job, or fetch a new one if not cached.
104
+ *
105
+ * @param jobId - Optional job ID to cache token for specific job
106
+ * @returns Cached or newly fetched token
107
+ */
108
+ private async getOrFetchToken(jobId?: string): Promise<string> {
109
+ // Check if we have a cached token for this job
110
+ if (jobId) {
111
+ const cached = this.tokenCache.get(jobId);
112
+ if (cached && !this.isTokenExpired(cached)) {
113
+ return cached.token;
114
+ }
115
+ }
116
+
117
+ // Check global token cache
118
+ if (!jobId && this.globalToken && !this.isTokenExpired(this.globalToken)) {
119
+ return this.globalToken.token;
120
+ }
121
+
122
+ // No valid cached token - fetch a new one
123
+ const tokenResponse = await this.fetchToken();
124
+
125
+ // Calculate expiration time if provided
126
+ const expiresAt = tokenResponse.expiresIn
127
+ ? Date.now() + tokenResponse.expiresIn * 1000
128
+ : undefined;
129
+
130
+ const cachedToken: CachedToken = {
131
+ token: tokenResponse.token,
132
+ expiresAt,
133
+ };
134
+
135
+ // Cache the token
136
+ if (jobId) {
137
+ this.tokenCache.set(jobId, cachedToken);
138
+ } else {
139
+ this.globalToken = cachedToken;
140
+ }
141
+
142
+ return tokenResponse.token;
143
+ }
144
+
145
+ /**
146
+ * Check if a cached token is expired.
147
+ * Adds a 60-second buffer to avoid using tokens that are about to expire.
148
+ */
149
+ private isTokenExpired(cached: CachedToken): boolean {
150
+ if (!cached.expiresAt) {
151
+ // No expiry set - assume token is valid
152
+ return false;
153
+ }
154
+
155
+ // Add 60-second buffer before actual expiry
156
+ const bufferMs = 60 * 1000;
157
+ return Date.now() > cached.expiresAt - bufferMs;
158
+ }
159
+
160
+ /**
161
+ * Attach JWT token to an HTTP request as Authorization Bearer header.
162
+ *
163
+ * @param headers - Existing request headers
164
+ * @param jobId - Optional job ID to use cached token for specific job
165
+ * @returns Updated headers with Authorization header
166
+ * @throws Error if token fetch fails
167
+ */
168
+ async attachToken(
169
+ headers: Record<string, string> = {},
170
+ jobId?: string,
171
+ ): Promise<Record<string, string>> {
172
+ try {
173
+ // Get token (from cache or fetch new)
174
+ const token = await this.getOrFetchToken(jobId);
175
+
176
+ // Attach as Bearer token
177
+ return {
178
+ ...headers,
179
+ Authorization: `Bearer ${token}`,
180
+ };
181
+ } catch (error) {
182
+ const message = error instanceof Error ? error.message : String(error);
183
+ throw new Error(`Failed to attach auth token: ${message}`);
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Clear cached token for a specific job.
189
+ * Should be called when a job completes to free memory.
190
+ *
191
+ * @param jobId - Job ID to clear token for
192
+ */
193
+ clearToken(jobId: string): void {
194
+ this.tokenCache.delete(jobId);
195
+ }
196
+
197
+ /**
198
+ * Clear all cached tokens.
199
+ * Useful for logout or when switching users.
200
+ */
201
+ clearAllTokens(): void {
202
+ this.tokenCache.clear();
203
+ this.globalToken = null;
204
+ }
205
+
206
+ /**
207
+ * Get cache statistics for debugging and monitoring.
208
+ */
209
+ getCacheStats(): {
210
+ cachedJobCount: number;
211
+ hasGlobalToken: boolean;
212
+ } {
213
+ return {
214
+ cachedJobCount: this.tokenCache.size,
215
+ hasGlobalToken: this.globalToken !== null,
216
+ };
217
+ }
218
+ }
@@ -0,0 +1,105 @@
1
+ export class BaseAuthManager {
2
+ constructor(private type: "direct" | "saas" | "no-auth") {}
3
+
4
+ getType() {
5
+ return this.type;
6
+ }
7
+ }
8
+ /**
9
+ * Credentials that can be attached to HTTP requests.
10
+ * Supports headers and cookies for maximum flexibility.
11
+ */
12
+ export type RequestCredentials = {
13
+ /** HTTP headers to attach (e.g., Authorization, X-API-Key) */
14
+ headers?: Record<string, string>;
15
+ /** Cookies to attach (primarily for browser environments) */
16
+ cookies?: Record<string, string>;
17
+ };
18
+
19
+ /**
20
+ * Direct auth mode configuration.
21
+ * Users provide a function that returns credentials to attach to every request.
22
+ * This mode supports any authentication protocol (OAuth, JWT, sessions, API keys, etc.)
23
+ *
24
+ * @example Bearer token
25
+ * ```typescript
26
+ * {
27
+ * mode: 'direct',
28
+ * getCredentials: async () => ({
29
+ * headers: {
30
+ * 'Authorization': `Bearer ${await getAccessToken()}`
31
+ * }
32
+ * })
33
+ * }
34
+ * ```
35
+ *
36
+ * @example API key
37
+ * ```typescript
38
+ * {
39
+ * mode: 'direct',
40
+ * getCredentials: () => ({
41
+ * headers: {
42
+ * 'X-API-Key': process.env.API_KEY
43
+ * }
44
+ * })
45
+ * }
46
+ * ```
47
+ */
48
+ export type DirectAuthConfig = {
49
+ mode: "direct";
50
+ /**
51
+ * Function called before each HTTP request to obtain credentials.
52
+ * Can be async to support token refresh or other async operations.
53
+ * Should not throw - return empty object if credentials unavailable.
54
+ */
55
+ getCredentials?: () => RequestCredentials | Promise<RequestCredentials>;
56
+ };
57
+
58
+ /**
59
+ * SaaS auth mode configuration.
60
+ * Client requests JWT tokens from a user-controlled auth server,
61
+ * which validates credentials and issues tokens using a secure API key.
62
+ *
63
+ * Token exchange flow:
64
+ * 1. Client calls getCredentials() to get user credentials
65
+ * 2. Client sends credentials to authServerUrl
66
+ * 3. Auth server validates and returns JWT token
67
+ * 4. Client attaches token to uploadista engine requests
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * {
72
+ * mode: 'saas',
73
+ * authServerUrl: 'https://auth.myapp.com/token',
74
+ * getCredentials: async () => ({
75
+ * username: await getUsername(),
76
+ * password: await getPassword()
77
+ * })
78
+ * }
79
+ * ```
80
+ */
81
+ export type SaasAuthConfig = {
82
+ mode: "saas";
83
+ /**
84
+ * URL of the user's auth server that issues JWT tokens.
85
+ * Should be a GET endpoint that accepts client id and returns { token, expiresIn }.
86
+ */
87
+ authServerUrl: string;
88
+ /**
89
+ * Function that returns user credentials to send to the auth server.
90
+ * The auth server will validate these credentials before issuing a token.
91
+ * Credentials format is client id
92
+ */
93
+ clientId: string;
94
+ };
95
+
96
+ /**
97
+ * Authentication configuration for the uploadista client.
98
+ * Supports two modes:
99
+ * - Direct: Bring your own auth (any protocol)
100
+ * - SaaS: Standard JWT token exchange with auth server
101
+ *
102
+ * Use a discriminated union to ensure type safety - TypeScript will
103
+ * enforce that the correct fields are present for each mode.
104
+ */
105
+ export type AuthConfig = DirectAuthConfig | SaasAuthConfig;
@@ -0,0 +1,287 @@
1
+ import type { BufferedChunk } from "./types/buffered-chunk";
2
+
3
+ /**
4
+ * Configuration options for ChunkBuffer.
5
+ *
6
+ * Controls how the buffer accumulates chunks before flushing them to the datastore.
7
+ * This is essential for datastores with minimum chunk size requirements (e.g., AWS S3's 5MB minimum).
8
+ */
9
+ export interface ChunkBufferConfig {
10
+ /**
11
+ * Minimum chunk size required by the datastore before flushing (in bytes).
12
+ * For example, AWS S3 requires a minimum of 5MB per multipart upload part.
13
+ */
14
+ minThreshold: number;
15
+
16
+ /**
17
+ * Maximum buffer size before forcing a flush (in bytes).
18
+ * Defaults to 2x minThreshold. Prevents memory issues with very slow uploads.
19
+ */
20
+ maxBufferSize?: number;
21
+
22
+ /**
23
+ * Maximum time to wait before flushing pending data (in milliseconds).
24
+ * Defaults to 30000ms (30 seconds). Ensures timely uploads even with slow data arrival.
25
+ */
26
+ timeoutMs?: number;
27
+ }
28
+
29
+ /**
30
+ * ChunkBuffer accumulates small chunks until they meet the minimum threshold
31
+ * required by the datastore (e.g., S3's 5MB minimum part size).
32
+ *
33
+ * This prevents inefficient upload/download cycles of incomplete parts by buffering
34
+ * small chunks in memory until they reach the datastore's minimum size requirement.
35
+ * The buffer automatically flushes when the threshold is met, the maximum buffer
36
+ * size is exceeded, or a timeout occurs.
37
+ *
38
+ * @example Basic usage with S3's 5MB minimum
39
+ * ```typescript
40
+ * const buffer = new ChunkBuffer({
41
+ * minThreshold: 5 * 1024 * 1024, // 5MB
42
+ * maxBufferSize: 10 * 1024 * 1024, // 10MB
43
+ * timeoutMs: 30000, // 30 seconds
44
+ * });
45
+ *
46
+ * // Add chunks as they arrive
47
+ * const chunk1 = new Uint8Array(2 * 1024 * 1024); // 2MB
48
+ * buffer.add(chunk1); // Returns null (below threshold)
49
+ *
50
+ * const chunk2 = new Uint8Array(3 * 1024 * 1024); // 3MB
51
+ * const buffered = buffer.add(chunk2); // Returns combined 5MB chunk
52
+ * ```
53
+ *
54
+ * @example Handling incomplete uploads
55
+ * ```typescript
56
+ * const buffer = new ChunkBuffer({ minThreshold: 5 * 1024 * 1024 });
57
+ *
58
+ * // After adding several small chunks
59
+ * buffer.add(smallChunk1);
60
+ * buffer.add(smallChunk2);
61
+ *
62
+ * // Force flush remaining data at end of upload
63
+ * if (buffer.hasPendingData()) {
64
+ * const finalChunk = buffer.flush();
65
+ * await uploadFinalChunk(finalChunk);
66
+ * }
67
+ * ```
68
+ */
69
+ export class ChunkBuffer {
70
+ private buffer: Uint8Array[] = [];
71
+ private currentSize = 0;
72
+ private config: Required<ChunkBufferConfig>;
73
+ private lastAddTime = 0;
74
+
75
+ /**
76
+ * Creates a new ChunkBuffer instance.
77
+ *
78
+ * @param config - Buffer configuration including thresholds and timeout
79
+ */
80
+ constructor(config: ChunkBufferConfig) {
81
+ this.config = {
82
+ minThreshold: config.minThreshold,
83
+ maxBufferSize: config.maxBufferSize ?? config.minThreshold * 2,
84
+ timeoutMs: config.timeoutMs ?? 30000, // 30 seconds
85
+ };
86
+ }
87
+
88
+ /**
89
+ * Adds a chunk to the buffer and returns the accumulated chunk if the flush threshold is met.
90
+ *
91
+ * The buffer will automatically flush (return the combined chunk) when:
92
+ * - The total buffered size meets or exceeds minThreshold
93
+ * - The total buffered size exceeds maxBufferSize
94
+ * - The time since the last chunk exceeds timeoutMs
95
+ *
96
+ * @param chunk - The chunk data to add to the buffer
97
+ * @returns The combined buffered chunk if flush conditions are met, null otherwise
98
+ *
99
+ * @example Progressive buffering
100
+ * ```typescript
101
+ * const buffer = new ChunkBuffer({ minThreshold: 1024 * 1024 }); // 1MB
102
+ *
103
+ * // First chunk doesn't meet threshold
104
+ * const result1 = buffer.add(new Uint8Array(512 * 1024)); // 512KB
105
+ * console.log(result1); // null
106
+ *
107
+ * // Second chunk triggers flush
108
+ * const result2 = buffer.add(new Uint8Array(512 * 1024)); // 512KB
109
+ * console.log(result2?.size); // 1048576 (1MB total)
110
+ * ```
111
+ */
112
+ add(chunk: Uint8Array): BufferedChunk | null {
113
+ this.buffer.push(chunk);
114
+ this.currentSize += chunk.length;
115
+ this.lastAddTime = Date.now();
116
+
117
+ if (this.shouldFlush()) {
118
+ return this.flush();
119
+ }
120
+
121
+ return null;
122
+ }
123
+
124
+ /**
125
+ * Forces the buffer to flush immediately, returning all accumulated data.
126
+ *
127
+ * This is typically called at the end of an upload to ensure any remaining
128
+ * buffered data is sent, even if it hasn't reached the minimum threshold.
129
+ *
130
+ * @returns The combined buffered chunk, or null if the buffer is empty
131
+ *
132
+ * @example Flushing at upload completion
133
+ * ```typescript
134
+ * const buffer = new ChunkBuffer({ minThreshold: 5 * 1024 * 1024 });
135
+ *
136
+ * // Upload file in chunks
137
+ * for (const chunk of fileChunks) {
138
+ * const buffered = buffer.add(chunk);
139
+ * if (buffered) await uploadChunk(buffered);
140
+ * }
141
+ *
142
+ * // Upload any remaining data
143
+ * const final = buffer.flush();
144
+ * if (final) await uploadChunk(final);
145
+ * ```
146
+ */
147
+ flush(): BufferedChunk | null {
148
+ if (this.buffer.length === 0) {
149
+ return null;
150
+ }
151
+
152
+ const combined = new Uint8Array(this.currentSize);
153
+ let offset = 0;
154
+
155
+ for (const chunk of this.buffer) {
156
+ combined.set(chunk, offset);
157
+ offset += chunk.length;
158
+ }
159
+
160
+ const result: BufferedChunk = {
161
+ data: combined,
162
+ size: this.currentSize,
163
+ timestamp: this.lastAddTime,
164
+ };
165
+
166
+ this.reset();
167
+ return result;
168
+ }
169
+
170
+ /**
171
+ * Checks if the buffer should be flushed based on size, max buffer, or timeout conditions.
172
+ *
173
+ * Returns true if any of these conditions are met:
174
+ * - Current size >= minThreshold
175
+ * - Current size >= maxBufferSize
176
+ * - Time since last add > timeoutMs
177
+ *
178
+ * @returns True if the buffer should be flushed
179
+ *
180
+ * @example Manual flush control
181
+ * ```typescript
182
+ * const buffer = new ChunkBuffer({ minThreshold: 1024 * 1024 });
183
+ *
184
+ * buffer.add(smallChunk);
185
+ *
186
+ * if (buffer.shouldFlush()) {
187
+ * const data = buffer.flush();
188
+ * await upload(data);
189
+ * }
190
+ * ```
191
+ */
192
+ shouldFlush(): boolean {
193
+ if (this.currentSize >= this.config.minThreshold) {
194
+ return true;
195
+ }
196
+
197
+ if (this.currentSize >= this.config.maxBufferSize) {
198
+ return true;
199
+ }
200
+
201
+ const timeSinceLastAdd = Date.now() - this.lastAddTime;
202
+ if (this.buffer.length > 0 && timeSinceLastAdd > this.config.timeoutMs) {
203
+ return true;
204
+ }
205
+
206
+ return false;
207
+ }
208
+
209
+ /**
210
+ * Returns the current buffer state without flushing.
211
+ *
212
+ * Useful for monitoring buffer status and making informed decisions
213
+ * about when to manually flush or adjust upload strategies.
214
+ *
215
+ * @returns Object containing buffer metrics
216
+ *
217
+ * @example Monitoring buffer state
218
+ * ```typescript
219
+ * const buffer = new ChunkBuffer({ minThreshold: 1024 * 1024 });
220
+ * buffer.add(chunk);
221
+ *
222
+ * const info = buffer.getBufferInfo();
223
+ * console.log(`Buffered: ${info.size} bytes in ${info.chunkCount} chunks`);
224
+ * console.log(`Ready to flush: ${info.isReadyToFlush}`);
225
+ * console.log(`Time since last add: ${info.timeSinceLastAdd}ms`);
226
+ * ```
227
+ */
228
+ getBufferInfo(): {
229
+ size: number;
230
+ chunkCount: number;
231
+ isReadyToFlush: boolean;
232
+ timeSinceLastAdd: number;
233
+ } {
234
+ return {
235
+ size: this.currentSize,
236
+ chunkCount: this.buffer.length,
237
+ isReadyToFlush: this.shouldFlush(),
238
+ timeSinceLastAdd: Date.now() - this.lastAddTime,
239
+ };
240
+ }
241
+
242
+ /**
243
+ * Checks if the buffer has any pending data that hasn't been flushed.
244
+ *
245
+ * Useful for determining if a final flush is needed at upload completion.
246
+ *
247
+ * @returns True if there are chunks waiting in the buffer
248
+ *
249
+ * @example Ensuring complete upload
250
+ * ```typescript
251
+ * // Upload all chunks
252
+ * for (const chunk of chunks) {
253
+ * const buffered = buffer.add(chunk);
254
+ * if (buffered) await upload(buffered);
255
+ * }
256
+ *
257
+ * // Don't forget the last partial chunk!
258
+ * if (buffer.hasPendingData()) {
259
+ * await upload(buffer.flush());
260
+ * }
261
+ * ```
262
+ */
263
+ hasPendingData(): boolean {
264
+ return this.buffer.length > 0;
265
+ }
266
+
267
+ /**
268
+ * Clears the buffer without returning data.
269
+ *
270
+ * This discards all buffered chunks and resets the buffer state.
271
+ * Use with caution as this will lose any pending data.
272
+ */
273
+ reset(): void {
274
+ this.buffer = [];
275
+ this.currentSize = 0;
276
+ this.lastAddTime = 0;
277
+ }
278
+
279
+ /**
280
+ * Returns the minimum threshold this buffer is configured for.
281
+ *
282
+ * @returns Minimum chunk size in bytes before flushing
283
+ */
284
+ getMinThreshold(): number {
285
+ return this.config.minThreshold;
286
+ }
287
+ }