@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,857 @@
1
+ import type { FlowData, FlowJob } from "@uploadista/core/flow";
2
+ import type {
3
+ DataStoreCapabilities,
4
+ InputFile,
5
+ UploadFile,
6
+ } from "@uploadista/core/types";
7
+ import { AuthHttpClient, type AuthManager } from "../auth";
8
+ import { UploadistaError, type UploadistaErrorName } from "../error";
9
+ import type { Logger } from "../logger";
10
+ import { defaultClientCapabilities } from "../mock-data-store";
11
+ import type { AbortControllerLike } from "../services/abort-controller-service";
12
+ import type {
13
+ ConnectionMetrics,
14
+ DetailedConnectionMetrics,
15
+ HttpClient,
16
+ RequestBody,
17
+ } from "../services/http-client";
18
+ import type {
19
+ WebSocketFactory,
20
+ WebSocketLike,
21
+ } from "../services/websocket-service";
22
+
23
+ // Error response type - matches server format
24
+ type ErrorResponse = {
25
+ error?: string;
26
+ message?: string;
27
+ code?: string;
28
+ details?: unknown;
29
+ timestamp?: string;
30
+ };
31
+
32
+ /**
33
+ * Maps server error codes to client error names
34
+ * If no mapping exists, uses a default error name based on context
35
+ */
36
+ const mapServerErrorCodeToClientName = (
37
+ serverCode: string | undefined,
38
+ defaultName: UploadistaErrorName,
39
+ ): UploadistaErrorName => {
40
+ if (!serverCode) return defaultName;
41
+
42
+ // Map common server error codes to client error names
43
+ const errorMap: Record<string, UploadistaErrorName> = {
44
+ FILE_NOT_FOUND: "UPLOAD_NOT_FOUND",
45
+ UPLOAD_ID_NOT_FOUND: "UPLOAD_NOT_FOUND",
46
+ FLOW_JOB_NOT_FOUND: "JOB_NOT_FOUND",
47
+ FLOW_NODE_ERROR: "FLOW_RUN_FAILED",
48
+ FLOW_STRUCTURE_ERROR: "FLOW_RUN_FAILED",
49
+ FLOW_CYCLE_ERROR: "FLOW_RUN_FAILED",
50
+ FLOW_INPUT_VALIDATION_ERROR: "FLOW_RUN_FAILED",
51
+ FLOW_OUTPUT_VALIDATION_ERROR: "FLOW_RUN_FAILED",
52
+ VALIDATION_ERROR: "CREATE_UPLOAD_FAILED",
53
+ DATASTORE_NOT_FOUND: "FLOW_RUN_FAILED",
54
+ };
55
+
56
+ return errorMap[serverCode] || defaultName;
57
+ };
58
+
59
+ /**
60
+ * Response from upload-related API calls.
61
+ *
62
+ * Contains the upload metadata and HTTP status code.
63
+ */
64
+ export type UploadistaUploadResponse = {
65
+ /** Upload file metadata, undefined if request failed */
66
+ upload?: UploadFile;
67
+ /** HTTP status code */
68
+ status: number;
69
+ };
70
+
71
+ /**
72
+ * Response from delete upload API call.
73
+ */
74
+ export type UploadistaDeleteUploadResponse =
75
+ | {
76
+ /** Successfully deleted (no content) */
77
+ status: 204;
78
+ }
79
+ | {
80
+ /** Other status codes (e.g., 404, 500) */
81
+ status: number;
82
+ };
83
+
84
+ /**
85
+ * Response from flow retrieval API call.
86
+ */
87
+ export type FlowResponse = {
88
+ /** HTTP status code */
89
+ status: number;
90
+ /** Flow configuration and metadata */
91
+ flow: FlowData;
92
+ };
93
+
94
+ /**
95
+ * Unified Uploadista API interface combining upload and flow operations.
96
+ *
97
+ * This low-level API provides direct access to server endpoints for:
98
+ * - Upload CRUD operations (create, get, delete, patch chunks)
99
+ * - Flow operations (get, run, continue)
100
+ * - Job status tracking
101
+ * - WebSocket connections for real-time updates
102
+ * - Server capabilities discovery
103
+ * - Connection pooling metrics
104
+ *
105
+ * Most applications should use the higher-level {@link UploadistaClient} instead,
106
+ * which provides a more convenient interface with automatic retry, resumption,
107
+ * and smart chunking.
108
+ *
109
+ * @example Direct API usage (advanced)
110
+ * ```typescript
111
+ * const api = createUploadistaApi(baseUrl, basePath, {
112
+ * httpClient,
113
+ * logger,
114
+ * authManager,
115
+ * webSocketFactory,
116
+ * });
117
+ *
118
+ * // Create an upload
119
+ * const { upload } = await api.createUpload({
120
+ * storageId: 'my-storage',
121
+ * size: 1024000,
122
+ * metadata: { filename: 'test.txt' },
123
+ * });
124
+ *
125
+ * // Upload a chunk
126
+ * const chunk = new Uint8Array(1024);
127
+ * await api.uploadChunk(upload.id, chunk, {});
128
+ *
129
+ * // Check status
130
+ * const { upload: updated } = await api.getUpload(upload.id);
131
+ * console.log(`Progress: ${updated.offset}/${updated.size}`);
132
+ * ```
133
+ *
134
+ * @see {@link createUploadistaApi} for creating an instance
135
+ */
136
+ export type UploadistaApi = {
137
+ /**
138
+ * Retrieves upload metadata and current status.
139
+ *
140
+ * @param uploadId - Unique upload identifier
141
+ * @returns Upload metadata including current offset and status
142
+ * @throws {UploadistaError} If upload not found or request fails
143
+ */
144
+ getUpload: (uploadId: string) => Promise<UploadistaUploadResponse>;
145
+
146
+ /**
147
+ * Deletes an upload and its associated data.
148
+ *
149
+ * @param uploadId - Unique upload identifier
150
+ * @returns Response with status 204 on success
151
+ * @throws {UploadistaError} If upload not found or deletion fails
152
+ */
153
+ deleteUpload: (uploadId: string) => Promise<UploadistaDeleteUploadResponse>;
154
+
155
+ /**
156
+ * Creates a new upload on the server.
157
+ *
158
+ * @param body - Upload configuration including storageId, size, and metadata
159
+ * @returns Created upload metadata with unique ID
160
+ * @throws {UploadistaError} If creation fails or validation errors occur
161
+ */
162
+ createUpload: (body: InputFile) => Promise<UploadistaUploadResponse>;
163
+
164
+ /**
165
+ * Uploads a chunk of data to an existing upload.
166
+ *
167
+ * @param uploadId - Upload identifier to append data to
168
+ * @param data - Chunk data bytes, or null to finalize without data
169
+ * @param options - Upload options including abort controller and progress callback
170
+ * @returns Updated upload metadata with new offset
171
+ * @throws {UploadistaError} If chunk upload fails or upload is locked
172
+ */
173
+ uploadChunk: (
174
+ uploadId: string,
175
+ data: Uint8Array | null,
176
+ options: {
177
+ abortController?: AbortControllerLike;
178
+ onProgress?: (bytes: number, total: number) => void;
179
+ },
180
+ ) => Promise<UploadistaUploadResponse>;
181
+
182
+ /**
183
+ * Retrieves flow configuration and metadata.
184
+ *
185
+ * @param flowId - Unique flow identifier
186
+ * @returns Flow configuration including nodes and edges
187
+ * @throws {UploadistaError} If flow not found
188
+ */
189
+ getFlow: (flowId: string) => Promise<FlowResponse>;
190
+
191
+ /**
192
+ * Executes a flow with the provided inputs.
193
+ *
194
+ * @param flowId - Flow to execute
195
+ * @param storageId - Storage backend to use for flow outputs
196
+ * @param inputs - Input data for flow nodes (keyed by node ID)
197
+ * @returns Job metadata including job ID and initial state
198
+ * @throws {UploadistaError} If flow execution fails or inputs are invalid
199
+ */
200
+ runFlow: (
201
+ flowId: string,
202
+ storageId: string,
203
+ inputs: Record<string, unknown>,
204
+ ) => Promise<{ status: number; job: FlowJob }>;
205
+
206
+ /**
207
+ * Continues a paused flow execution with new data.
208
+ *
209
+ * Used for interactive flows that wait for user input or external data.
210
+ *
211
+ * @param jobId - Job identifier for the paused flow
212
+ * @param nodeId - Node ID where execution should continue
213
+ * @param newData - Data to provide to the node
214
+ * @param options - Options including content type for binary data
215
+ * @returns Updated job metadata
216
+ * @throws {UploadistaError} If job not found or continuation fails
217
+ */
218
+ continueFlow: (
219
+ jobId: string,
220
+ nodeId: string,
221
+ newData: unknown,
222
+ options?: {
223
+ contentType?: "application/json" | "application/octet-stream";
224
+ },
225
+ ) => Promise<FlowJob>;
226
+
227
+ /**
228
+ * Retrieves current job status and outputs.
229
+ *
230
+ * Works for both upload and flow jobs.
231
+ *
232
+ * @param jobId - Job identifier
233
+ * @returns Job metadata including state, progress, and outputs
234
+ * @throws {UploadistaError} If job not found
235
+ */
236
+ getJobStatus: (jobId: string) => Promise<FlowJob>;
237
+
238
+ /**
239
+ * Opens a WebSocket connection for upload progress events.
240
+ *
241
+ * @param uploadId - Upload to monitor
242
+ * @returns WebSocket instance for receiving real-time updates
243
+ */
244
+ openUploadWebSocket: (uploadId: string) => Promise<WebSocketLike>;
245
+
246
+ /**
247
+ * Opens a WebSocket connection for flow job events.
248
+ *
249
+ * @param jobId - Flow job to monitor
250
+ * @returns WebSocket instance for receiving real-time updates
251
+ */
252
+ openFlowWebSocket: (jobId: string) => Promise<WebSocketLike>;
253
+
254
+ /**
255
+ * Closes a WebSocket connection.
256
+ *
257
+ * @param ws - WebSocket instance to close
258
+ */
259
+ closeWebSocket: (ws: WebSocketLike) => void;
260
+
261
+ /**
262
+ * Returns current connection pool metrics.
263
+ *
264
+ * @returns Basic metrics including active connections and reuse rate
265
+ */
266
+ getConnectionMetrics: () => ConnectionMetrics;
267
+
268
+ /**
269
+ * Returns detailed connection pool metrics with health diagnostics.
270
+ *
271
+ * @returns Comprehensive metrics including health status and recommendations
272
+ */
273
+ getDetailedConnectionMetrics: () => DetailedConnectionMetrics;
274
+
275
+ /**
276
+ * Pre-warms connections to the specified URLs.
277
+ *
278
+ * Useful for reducing latency on first upload by establishing
279
+ * connections ahead of time.
280
+ *
281
+ * @param urls - URLs to pre-connect to
282
+ */
283
+ warmupConnections: (urls: string[]) => Promise<void>;
284
+
285
+ /**
286
+ * Fetches server capabilities for the specified storage backend.
287
+ *
288
+ * Returns information about chunk size constraints, supported features,
289
+ * and storage-specific requirements. Falls back to default capabilities
290
+ * if the request fails.
291
+ *
292
+ * @param storageId - Storage backend identifier
293
+ * @returns Storage capabilities including chunk size limits
294
+ */
295
+ getCapabilities: (storageId: string) => Promise<DataStoreCapabilities>;
296
+ };
297
+
298
+ /**
299
+ * Creates an Uploadista API instance for direct server communication.
300
+ *
301
+ * This factory creates a low-level API client that handles:
302
+ * - HTTP requests to upload and flow endpoints
303
+ * - Authentication via AuthManager (optional)
304
+ * - WebSocket connections for real-time updates
305
+ * - Error mapping from server to client error types
306
+ * - Connection pooling and metrics
307
+ *
308
+ * Most applications should use {@link createUploadistaClient} instead,
309
+ * which wraps this API with higher-level features like automatic retry,
310
+ * resumption, and smart chunking.
311
+ *
312
+ * @param baseURL - Base URL of the Uploadista server (e.g., "https://upload.example.com")
313
+ * @param uploadistBasePath - Base path for endpoints, typically "uploadista"
314
+ * @param options - Configuration object
315
+ * @param options.httpClient - HTTP client for making requests
316
+ * @param options.logger - Optional logger for debugging
317
+ * @param options.authManager - Optional authentication manager
318
+ * @param options.webSocketFactory - Factory for creating WebSocket connections
319
+ * @returns UploadistaApi instance
320
+ *
321
+ * @example Basic API instance
322
+ * ```typescript
323
+ * import { createUploadistaApi } from '@uploadista/client-core';
324
+ *
325
+ * const api = createUploadistaApi(
326
+ * 'https://upload.example.com',
327
+ * 'uploadista',
328
+ * {
329
+ * httpClient: myHttpClient,
330
+ * logger: console,
331
+ * webSocketFactory: {
332
+ * create: (url) => new WebSocket(url),
333
+ * },
334
+ * }
335
+ * );
336
+ *
337
+ * // Use the API directly
338
+ * const { upload } = await api.createUpload({
339
+ * storageId: 'my-storage',
340
+ * size: 1024,
341
+ * });
342
+ * ```
343
+ *
344
+ * @example With authentication
345
+ * ```typescript
346
+ * const authManager = new DirectAuthManager(authConfig, platformService, logger);
347
+ *
348
+ * const api = createUploadistaApi(baseUrl, 'uploadista', {
349
+ * httpClient,
350
+ * logger,
351
+ * authManager, // Automatically adds auth headers to requests
352
+ * webSocketFactory,
353
+ * });
354
+ * ```
355
+ *
356
+ * @see {@link UploadistaApi} for the API interface
357
+ * @see {@link createUploadistaClient} for the high-level client
358
+ */
359
+ export function createUploadistaApi(
360
+ baseURL: string,
361
+ uploadistBasePath: string,
362
+ {
363
+ httpClient: baseHttpClient,
364
+ logger,
365
+ authManager,
366
+ webSocketFactory,
367
+ }: {
368
+ httpClient: HttpClient;
369
+ logger?: Logger;
370
+ authManager?: AuthManager;
371
+ webSocketFactory: WebSocketFactory;
372
+ },
373
+ ): UploadistaApi {
374
+ // Create base HTTP client with connection pooling
375
+
376
+ // Wrap with auth if auth manager is provided
377
+ const httpClient = authManager
378
+ ? new AuthHttpClient(baseHttpClient, authManager)
379
+ : baseHttpClient;
380
+
381
+ // Construct endpoint URLs
382
+ const uploadEndpoint = `${baseURL}/${uploadistBasePath}/api/upload`;
383
+ const flowEndpoint = `${baseURL}/${uploadistBasePath}/api/flow`;
384
+ const jobsEndpoint = `${baseURL}/${uploadistBasePath}/api/jobs`;
385
+
386
+ // WebSocket URLs
387
+ const wsBaseURL = baseURL.replace("http", "ws");
388
+ const uploadWsURL = `${wsBaseURL}/uploadista/ws/upload`;
389
+ const flowWsURL = `${wsBaseURL}/uploadista/ws/flow`;
390
+
391
+ /**
392
+ * Helper function to extract auth token for WebSocket connection.
393
+ * Supports both DirectAuthManager (extracts from headers) and SaasAuthManager (gets cached token).
394
+ */
395
+ const getAuthTokenForWebSocket = async (
396
+ manager: AuthManager,
397
+ jobId?: string,
398
+ ): Promise<string | null> => {
399
+ logger?.log(`Getting auth token for WebSocket (jobId: ${jobId})`);
400
+
401
+ // Check if this is a SaasAuthManager (has attachToken method)
402
+ if ("attachToken" in manager) {
403
+ logger?.log("Detected SaasAuthManager, calling attachToken");
404
+ const headers = await manager.attachToken({}, jobId);
405
+ const authHeader = headers.Authorization;
406
+ if (authHeader?.startsWith("Bearer ")) {
407
+ logger?.log("Successfully extracted Bearer token from SaasAuthManager");
408
+ return authHeader.substring(7); // Remove "Bearer " prefix
409
+ }
410
+ logger?.log(
411
+ `No valid Authorization header from SaasAuthManager: ${authHeader}`,
412
+ );
413
+ }
414
+
415
+ // Check if this is a DirectAuthManager (has attachCredentials method)
416
+ if ("attachCredentials" in manager) {
417
+ logger?.log("Detected DirectAuthManager, calling attachCredentials");
418
+ const headers = await manager.attachCredentials({});
419
+ const authHeader = headers.Authorization;
420
+ if (authHeader) {
421
+ logger?.log(
422
+ "Successfully extracted Authorization header from DirectAuthManager",
423
+ );
424
+ // Support both "Bearer token" and plain token formats
425
+ return authHeader.startsWith("Bearer ")
426
+ ? authHeader.substring(7)
427
+ : authHeader;
428
+ }
429
+ logger?.log(`No Authorization header from DirectAuthManager`);
430
+ }
431
+
432
+ logger?.log("No auth token could be extracted from auth manager");
433
+ return null;
434
+ };
435
+
436
+ return {
437
+ // Upload operations
438
+ getUpload: async (uploadId: string) => {
439
+ const res = await httpClient.request(`${uploadEndpoint}/${uploadId}`);
440
+
441
+ if (!res.ok) {
442
+ const errorData = (await res.json().catch(() => ({}))) as ErrorResponse;
443
+ const errorName = mapServerErrorCodeToClientName(
444
+ errorData.code,
445
+ "UPLOAD_NOT_FOUND",
446
+ );
447
+ const errorMessage =
448
+ errorData.error ||
449
+ errorData.message ||
450
+ `Upload ${uploadId} not found`;
451
+
452
+ throw new UploadistaError({
453
+ name: errorName,
454
+ message: errorData.code
455
+ ? `${errorMessage} (${errorData.code})`
456
+ : errorMessage,
457
+ status: res.status,
458
+ });
459
+ }
460
+
461
+ const data = (await res.json()) as UploadFile;
462
+ return { status: res.status, upload: data };
463
+ },
464
+
465
+ deleteUpload: async (uploadId: string) => {
466
+ const res = await httpClient.request(`${uploadEndpoint}/${uploadId}`, {
467
+ method: "DELETE",
468
+ });
469
+
470
+ if (!res.ok) {
471
+ const errorData = (await res.json().catch(() => ({}))) as ErrorResponse;
472
+ const errorName = mapServerErrorCodeToClientName(
473
+ errorData.code,
474
+ "DELETE_UPLOAD_FAILED",
475
+ );
476
+ const errorMessage =
477
+ errorData.error ||
478
+ errorData.message ||
479
+ `Failed to delete upload ${uploadId}`;
480
+
481
+ throw new UploadistaError({
482
+ name: errorName,
483
+ message: errorData.code
484
+ ? `${errorMessage} (${errorData.code})`
485
+ : errorMessage,
486
+ status: res.status,
487
+ });
488
+ }
489
+
490
+ return { status: res.status };
491
+ },
492
+
493
+ createUpload: async (data: InputFile) => {
494
+ logger?.log(`createUpload ${JSON.stringify(data)}`);
495
+ const res = await httpClient.request(uploadEndpoint, {
496
+ method: "POST",
497
+ headers: {
498
+ "Content-Type": "application/json",
499
+ },
500
+ body: JSON.stringify(data),
501
+ });
502
+
503
+ if (!res.ok) {
504
+ const errorData = (await res.json().catch(() => ({}))) as ErrorResponse;
505
+ const errorName = mapServerErrorCodeToClientName(
506
+ errorData.code,
507
+ "CREATE_UPLOAD_FAILED",
508
+ );
509
+ const errorMessage =
510
+ errorData.error || errorData.message || "Failed to create upload";
511
+
512
+ throw new UploadistaError({
513
+ name: errorName,
514
+ message: errorData.code
515
+ ? `${errorMessage} (${errorData.code})`
516
+ : errorMessage,
517
+ status: res.status,
518
+ });
519
+ }
520
+
521
+ const responseData = (await res.json()) as UploadFile;
522
+ logger?.log(JSON.stringify(responseData));
523
+ return { status: res.status, upload: responseData };
524
+ },
525
+
526
+ uploadChunk: async (uploadId, data, { abortController }) => {
527
+ try {
528
+ const res = await httpClient.request(`${uploadEndpoint}/${uploadId}`, {
529
+ method: "PATCH",
530
+ headers: {
531
+ "Content-Type": "application/octet-stream",
532
+ },
533
+ body: data,
534
+ signal: abortController?.signal,
535
+ });
536
+
537
+ if (!res.ok) {
538
+ const errorData = (await res
539
+ .json()
540
+ .catch(() => ({}))) as ErrorResponse;
541
+ throw new UploadistaError({
542
+ name: "NETWORK_ERROR",
543
+ message:
544
+ errorData.error || errorData.message || "Unknown network error",
545
+ status: res.status,
546
+ });
547
+ }
548
+
549
+ const responseData = (await res.json()) as UploadFile;
550
+ return { status: res.status, upload: responseData };
551
+ } catch (err) {
552
+ if (err instanceof UploadistaError) {
553
+ throw err;
554
+ }
555
+ throw new UploadistaError({
556
+ name: "NETWORK_ERROR",
557
+ message: "Network error",
558
+ cause: err as Error,
559
+ });
560
+ }
561
+ },
562
+
563
+ // Flow operations
564
+ getFlow: async (flowId: string) => {
565
+ const res = await httpClient.request(`${flowEndpoint}/${flowId}`);
566
+
567
+ if (!res.ok) {
568
+ const errorData = (await res.json().catch(() => ({}))) as ErrorResponse;
569
+ const errorName = mapServerErrorCodeToClientName(
570
+ errorData.code,
571
+ "FLOW_NOT_FOUND",
572
+ );
573
+ const errorMessage =
574
+ errorData.error || errorData.message || `Flow ${flowId} not found`;
575
+
576
+ throw new UploadistaError({
577
+ name: errorName,
578
+ message: errorData.code
579
+ ? `${errorMessage} (${errorData.code})`
580
+ : errorMessage,
581
+ status: res.status,
582
+ });
583
+ }
584
+
585
+ const data = (await res.json()) as FlowData;
586
+ logger?.log(`getFlow: ${flowId}`);
587
+ return { status: res.status, flow: data };
588
+ },
589
+
590
+ runFlow: async (
591
+ flowId: string,
592
+ storageId: string,
593
+ inputs: Record<string, unknown>,
594
+ ) => {
595
+ logger?.log(`runFlow: ${flowId} with storage: ${storageId}`);
596
+ const res = await httpClient.request(
597
+ `${flowEndpoint}/${flowId}/${storageId}`,
598
+ {
599
+ method: "POST",
600
+ headers: {
601
+ "Content-Type": "application/json",
602
+ },
603
+ body: JSON.stringify({ inputs }),
604
+ },
605
+ );
606
+
607
+ if (!res.ok) {
608
+ const errorData = (await res.json().catch(() => ({}))) as ErrorResponse;
609
+ const errorName = mapServerErrorCodeToClientName(
610
+ errorData.code,
611
+ "FLOW_RUN_FAILED",
612
+ );
613
+ const errorMessage =
614
+ errorData.error ||
615
+ errorData.message ||
616
+ `Failed to run flow ${flowId}`;
617
+
618
+ throw new UploadistaError({
619
+ name: errorName,
620
+ message: errorData.code
621
+ ? `${errorMessage} (${errorData.code})`
622
+ : errorMessage,
623
+ status: res.status,
624
+ });
625
+ }
626
+
627
+ const data = (await res.json()) as FlowJob;
628
+ logger?.log(`runFlow response: ${JSON.stringify(data)}`);
629
+ return { status: res.status, job: data };
630
+ },
631
+
632
+ continueFlow: async (
633
+ jobId: string,
634
+ nodeId: string,
635
+ newData: unknown,
636
+ options?: {
637
+ contentType?: "application/json" | "application/octet-stream";
638
+ },
639
+ ) => {
640
+ logger?.log(`continueFlow: ${jobId} at node: ${nodeId}`);
641
+
642
+ const contentType = options?.contentType || "application/json";
643
+
644
+ let body: RequestBody;
645
+ if (contentType === "application/octet-stream") {
646
+ // For octet-stream, newData should be a Uint8Array or similar
647
+ body = newData as RequestBody;
648
+ } else {
649
+ // For JSON, wrap newData in an object
650
+ body = JSON.stringify({ newData });
651
+ }
652
+
653
+ const res = await httpClient.request(
654
+ `${jobsEndpoint}/${jobId}/continue/${nodeId}`,
655
+ {
656
+ method: "PATCH",
657
+ headers: {
658
+ "Content-Type": contentType,
659
+ },
660
+ body,
661
+ },
662
+ );
663
+
664
+ if (!res.ok) {
665
+ const errorData = (await res.json().catch(() => ({}))) as ErrorResponse;
666
+ const errorName = mapServerErrorCodeToClientName(
667
+ errorData.code,
668
+ "FLOW_CONTINUE_FAILED",
669
+ );
670
+ const errorMessage =
671
+ errorData.error ||
672
+ errorData.message ||
673
+ `Failed to continue flow for job ${jobId}`;
674
+
675
+ throw new UploadistaError({
676
+ name: errorName,
677
+ message: errorData.code
678
+ ? `${errorMessage} (${errorData.code})`
679
+ : errorMessage,
680
+ status: res.status,
681
+ });
682
+ }
683
+
684
+ const data = (await res.json()) as FlowJob;
685
+ return data;
686
+ },
687
+
688
+ // Unified job operations
689
+ getJobStatus: async (jobId: string) => {
690
+ const res = await httpClient.request(`${jobsEndpoint}/${jobId}/status`);
691
+
692
+ if (!res.ok) {
693
+ const errorData = (await res.json().catch(() => ({}))) as ErrorResponse;
694
+ const errorName = mapServerErrorCodeToClientName(
695
+ errorData.code,
696
+ "JOB_NOT_FOUND",
697
+ );
698
+ const errorMessage =
699
+ errorData.error || errorData.message || `Job ${jobId} not found`;
700
+
701
+ throw new UploadistaError({
702
+ name: errorName,
703
+ message: errorData.code
704
+ ? `${errorMessage} (${errorData.code})`
705
+ : errorMessage,
706
+ status: res.status,
707
+ });
708
+ }
709
+
710
+ const data = (await res.json()) as FlowJob;
711
+ return data;
712
+ },
713
+
714
+ // WebSocket operations
715
+ openUploadWebSocket: async (uploadId: string) => {
716
+ let wsUrl = `${uploadWsURL}/${uploadId}`;
717
+
718
+ // Attach auth token if auth manager is configured
719
+ // Note: For cookie-based auth (e.g., HttpOnly cookies with better-auth),
720
+ // no token is needed as cookies are automatically sent by the browser
721
+ if (authManager) {
722
+ try {
723
+ const token = await getAuthTokenForWebSocket(authManager, uploadId);
724
+ if (token) {
725
+ wsUrl += `?token=${encodeURIComponent(token)}`;
726
+ logger?.log(`WebSocket token attached for upload: ${uploadId}`);
727
+ } else {
728
+ // No token means cookie-based auth - this is fine
729
+ logger?.log(
730
+ `No token for upload WebSocket (using cookie-based auth): ${uploadId}`,
731
+ );
732
+ }
733
+ } catch (error) {
734
+ const errorMessage =
735
+ error instanceof Error ? error.message : String(error);
736
+ logger?.log(
737
+ `Error getting auth token for upload WebSocket: ${errorMessage}`,
738
+ );
739
+ // Don't throw - allow cookie-based auth to proceed
740
+ logger?.log(
741
+ `Proceeding with cookie-based authentication for upload WebSocket: ${uploadId}`,
742
+ );
743
+ }
744
+ }
745
+
746
+ const ws = webSocketFactory.create(wsUrl);
747
+
748
+ ws.onopen = () => {
749
+ logger?.log(`Upload WebSocket connection opened for: ${uploadId}`);
750
+ };
751
+
752
+ ws.onclose = () => {
753
+ logger?.log(`Upload WebSocket connection closed for: ${uploadId}`);
754
+ };
755
+
756
+ ws.onerror = (error) => {
757
+ logger?.log(`Upload WebSocket error for ${uploadId}: ${error}`);
758
+ };
759
+
760
+ return ws;
761
+ },
762
+
763
+ openFlowWebSocket: async (jobId: string) => {
764
+ let wsUrl = `${flowWsURL}/${jobId}`;
765
+
766
+ // Attach auth token if auth manager is configured
767
+ // Note: For cookie-based auth (e.g., HttpOnly cookies with better-auth),
768
+ // no token is needed as cookies are automatically sent by the browser
769
+ if (authManager) {
770
+ try {
771
+ const token = await getAuthTokenForWebSocket(authManager, jobId);
772
+ if (token) {
773
+ wsUrl += `?token=${encodeURIComponent(token)}`;
774
+ logger?.log(`WebSocket token attached for flow job: ${jobId}`);
775
+ } else {
776
+ // No token means cookie-based auth - this is fine
777
+ logger?.log(
778
+ `No token for flow WebSocket (using cookie-based auth): ${jobId}`,
779
+ );
780
+ }
781
+ } catch (error) {
782
+ const errorMessage =
783
+ error instanceof Error ? error.message : String(error);
784
+ logger?.log(
785
+ `Error getting auth token for flow WebSocket: ${errorMessage}`,
786
+ );
787
+ // Don't throw - allow cookie-based auth to proceed
788
+ logger?.log(
789
+ `Proceeding with cookie-based authentication for flow WebSocket: ${jobId}`,
790
+ );
791
+ }
792
+ }
793
+
794
+ const ws = webSocketFactory.create(wsUrl);
795
+
796
+ ws.onopen = () => {
797
+ logger?.log(`Flow WebSocket connection opened for job: ${jobId}`);
798
+ };
799
+
800
+ ws.onclose = () => {
801
+ logger?.log(`Flow WebSocket connection closed for job: ${jobId}`);
802
+ };
803
+
804
+ ws.onerror = (error) => {
805
+ logger?.log(`Flow WebSocket error for job ${jobId}: ${error}`);
806
+ };
807
+
808
+ return ws;
809
+ },
810
+
811
+ closeWebSocket: (ws: WebSocketLike) => {
812
+ ws.close();
813
+ },
814
+
815
+ // Connection metrics
816
+ getConnectionMetrics: () => {
817
+ return httpClient.getMetrics();
818
+ },
819
+
820
+ getDetailedConnectionMetrics: () => {
821
+ return httpClient.getDetailedMetrics();
822
+ },
823
+
824
+ warmupConnections: async (urls: string[]) => {
825
+ return httpClient.warmupConnections(urls);
826
+ },
827
+
828
+ // Capabilities
829
+ getCapabilities: async (storageId: string) => {
830
+ const capabilitiesUrl = `${uploadEndpoint}/capabilities?storageId=${encodeURIComponent(storageId)}`;
831
+
832
+ try {
833
+ const response = await httpClient.request(capabilitiesUrl, {
834
+ method: "GET",
835
+ headers: {
836
+ "Content-Type": "application/json",
837
+ },
838
+ });
839
+
840
+ if (!response.ok) {
841
+ logger?.log(
842
+ `Failed to fetch capabilities: ${response.status} ${response.statusText}`,
843
+ );
844
+ return defaultClientCapabilities;
845
+ }
846
+
847
+ const data = await response.json();
848
+ return (data as { capabilities: DataStoreCapabilities }).capabilities;
849
+ } catch (error) {
850
+ logger?.log(
851
+ `Failed to fetch server capabilities, using defaults: ${error}`,
852
+ );
853
+ return defaultClientCapabilities;
854
+ }
855
+ },
856
+ };
857
+ }