@uploadista/core 0.0.2

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 (359) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/.turbo/turbo-check.log +231 -0
  3. package/.turbo/turbo-format.log +5 -0
  4. package/LICENSE +21 -0
  5. package/README.md +1120 -0
  6. package/dist/chunk-CUT6urMc.cjs +1 -0
  7. package/dist/debounce-C2SeqcxD.js +2 -0
  8. package/dist/debounce-C2SeqcxD.js.map +1 -0
  9. package/dist/debounce-LZK7yS7Z.cjs +1 -0
  10. package/dist/errors/index.cjs +1 -0
  11. package/dist/errors/index.d.cts +3 -0
  12. package/dist/errors/index.d.ts +3 -0
  13. package/dist/errors/index.d.ts.map +1 -0
  14. package/dist/errors/index.js +2 -0
  15. package/dist/errors/uploadista-error.d.ts +209 -0
  16. package/dist/errors/uploadista-error.d.ts.map +1 -0
  17. package/dist/errors/uploadista-error.js +322 -0
  18. package/dist/errors-8i_aMxOE.js +1 -0
  19. package/dist/errors-CRm1FHHT.cjs +0 -0
  20. package/dist/flow/edge.d.ts +47 -0
  21. package/dist/flow/edge.d.ts.map +1 -0
  22. package/dist/flow/edge.js +40 -0
  23. package/dist/flow/event.d.ts +206 -0
  24. package/dist/flow/event.d.ts.map +1 -0
  25. package/dist/flow/event.js +53 -0
  26. package/dist/flow/flow-server.d.ts +223 -0
  27. package/dist/flow/flow-server.d.ts.map +1 -0
  28. package/dist/flow/flow-server.js +614 -0
  29. package/dist/flow/flow.d.ts +238 -0
  30. package/dist/flow/flow.d.ts.map +1 -0
  31. package/dist/flow/flow.js +629 -0
  32. package/dist/flow/index.cjs +1 -0
  33. package/dist/flow/index.d.cts +6 -0
  34. package/dist/flow/index.d.ts +24 -0
  35. package/dist/flow/index.d.ts.map +1 -0
  36. package/dist/flow/index.js +24 -0
  37. package/dist/flow/node.d.ts +136 -0
  38. package/dist/flow/node.d.ts.map +1 -0
  39. package/dist/flow/node.js +153 -0
  40. package/dist/flow/nodes/index.d.ts +8 -0
  41. package/dist/flow/nodes/index.d.ts.map +1 -0
  42. package/dist/flow/nodes/index.js +7 -0
  43. package/dist/flow/nodes/input-node.d.ts +78 -0
  44. package/dist/flow/nodes/input-node.d.ts.map +1 -0
  45. package/dist/flow/nodes/input-node.js +233 -0
  46. package/dist/flow/nodes/storage-node.d.ts +67 -0
  47. package/dist/flow/nodes/storage-node.d.ts.map +1 -0
  48. package/dist/flow/nodes/storage-node.js +94 -0
  49. package/dist/flow/nodes/streaming-input-node.d.ts +69 -0
  50. package/dist/flow/nodes/streaming-input-node.d.ts.map +1 -0
  51. package/dist/flow/nodes/streaming-input-node.js +156 -0
  52. package/dist/flow/nodes/transform-node.d.ts +85 -0
  53. package/dist/flow/nodes/transform-node.d.ts.map +1 -0
  54. package/dist/flow/nodes/transform-node.js +107 -0
  55. package/dist/flow/parallel-scheduler.d.ts +175 -0
  56. package/dist/flow/parallel-scheduler.d.ts.map +1 -0
  57. package/dist/flow/parallel-scheduler.js +193 -0
  58. package/dist/flow/plugins/credential-provider.d.ts +47 -0
  59. package/dist/flow/plugins/credential-provider.d.ts.map +1 -0
  60. package/dist/flow/plugins/credential-provider.js +24 -0
  61. package/dist/flow/plugins/image-ai-plugin.d.ts +61 -0
  62. package/dist/flow/plugins/image-ai-plugin.d.ts.map +1 -0
  63. package/dist/flow/plugins/image-ai-plugin.js +21 -0
  64. package/dist/flow/plugins/image-plugin.d.ts +52 -0
  65. package/dist/flow/plugins/image-plugin.d.ts.map +1 -0
  66. package/dist/flow/plugins/image-plugin.js +22 -0
  67. package/dist/flow/plugins/types/describe-image-node.d.ts +16 -0
  68. package/dist/flow/plugins/types/describe-image-node.d.ts.map +1 -0
  69. package/dist/flow/plugins/types/describe-image-node.js +9 -0
  70. package/dist/flow/plugins/types/index.d.ts +9 -0
  71. package/dist/flow/plugins/types/index.d.ts.map +1 -0
  72. package/dist/flow/plugins/types/index.js +8 -0
  73. package/dist/flow/plugins/types/optimize-node.d.ts +20 -0
  74. package/dist/flow/plugins/types/optimize-node.d.ts.map +1 -0
  75. package/dist/flow/plugins/types/optimize-node.js +11 -0
  76. package/dist/flow/plugins/types/remove-background-node.d.ts +16 -0
  77. package/dist/flow/plugins/types/remove-background-node.d.ts.map +1 -0
  78. package/dist/flow/plugins/types/remove-background-node.js +9 -0
  79. package/dist/flow/plugins/types/resize-node.d.ts +21 -0
  80. package/dist/flow/plugins/types/resize-node.d.ts.map +1 -0
  81. package/dist/flow/plugins/types/resize-node.js +16 -0
  82. package/dist/flow/plugins/zip-plugin.d.ts +62 -0
  83. package/dist/flow/plugins/zip-plugin.d.ts.map +1 -0
  84. package/dist/flow/plugins/zip-plugin.js +21 -0
  85. package/dist/flow/typed-flow.d.ts +90 -0
  86. package/dist/flow/typed-flow.d.ts.map +1 -0
  87. package/dist/flow/typed-flow.js +59 -0
  88. package/dist/flow/types/flow-file.d.ts +45 -0
  89. package/dist/flow/types/flow-file.d.ts.map +1 -0
  90. package/dist/flow/types/flow-file.js +27 -0
  91. package/dist/flow/types/flow-job.d.ts +118 -0
  92. package/dist/flow/types/flow-job.d.ts.map +1 -0
  93. package/dist/flow/types/flow-job.js +11 -0
  94. package/dist/flow/types/flow-types.d.ts +321 -0
  95. package/dist/flow/types/flow-types.d.ts.map +1 -0
  96. package/dist/flow/types/flow-types.js +52 -0
  97. package/dist/flow/types/index.d.ts +4 -0
  98. package/dist/flow/types/index.d.ts.map +1 -0
  99. package/dist/flow/types/index.js +3 -0
  100. package/dist/flow/types/run-args.d.ts +38 -0
  101. package/dist/flow/types/run-args.d.ts.map +1 -0
  102. package/dist/flow/types/run-args.js +30 -0
  103. package/dist/flow/types/type-validator.d.ts +26 -0
  104. package/dist/flow/types/type-validator.d.ts.map +1 -0
  105. package/dist/flow/types/type-validator.js +134 -0
  106. package/dist/flow/utils/resolve-upload-metadata.d.ts +11 -0
  107. package/dist/flow/utils/resolve-upload-metadata.d.ts.map +1 -0
  108. package/dist/flow/utils/resolve-upload-metadata.js +28 -0
  109. package/dist/flow-2zXnEiWL.cjs +1 -0
  110. package/dist/flow-CRaKy7Vj.js +2 -0
  111. package/dist/flow-CRaKy7Vj.js.map +1 -0
  112. package/dist/generate-id-Dm-Vboxq.d.ts +34 -0
  113. package/dist/generate-id-Dm-Vboxq.d.ts.map +1 -0
  114. package/dist/generate-id-LjJRLD6N.d.cts +34 -0
  115. package/dist/generate-id-LjJRLD6N.d.cts.map +1 -0
  116. package/dist/generate-id-xHp_Z7Cl.cjs +1 -0
  117. package/dist/generate-id-yohS1ZDk.js +2 -0
  118. package/dist/generate-id-yohS1ZDk.js.map +1 -0
  119. package/dist/index-BO8GZlbD.d.cts +1040 -0
  120. package/dist/index-BO8GZlbD.d.cts.map +1 -0
  121. package/dist/index-BoGG5KAY.d.ts +1 -0
  122. package/dist/index-BtBZHVmz.d.cts +1 -0
  123. package/dist/index-D-CoVpkZ.d.ts +1004 -0
  124. package/dist/index-D-CoVpkZ.d.ts.map +1 -0
  125. package/dist/index.cjs +1 -0
  126. package/dist/index.d.cts +6 -0
  127. package/dist/index.d.ts +5 -0
  128. package/dist/index.d.ts.map +1 -0
  129. package/dist/index.js +5 -0
  130. package/dist/logger/logger.cjs +1 -0
  131. package/dist/logger/logger.d.cts +8 -0
  132. package/dist/logger/logger.d.cts.map +1 -0
  133. package/dist/logger/logger.d.ts +5 -0
  134. package/dist/logger/logger.d.ts.map +1 -0
  135. package/dist/logger/logger.js +10 -0
  136. package/dist/logger/logger.js.map +1 -0
  137. package/dist/semaphore-0ZwjVpyF.js +2 -0
  138. package/dist/semaphore-0ZwjVpyF.js.map +1 -0
  139. package/dist/semaphore-BHprIjFI.d.cts +37 -0
  140. package/dist/semaphore-BHprIjFI.d.cts.map +1 -0
  141. package/dist/semaphore-DThupBkc.d.ts +37 -0
  142. package/dist/semaphore-DThupBkc.d.ts.map +1 -0
  143. package/dist/semaphore-DVrONiAV.cjs +1 -0
  144. package/dist/stream-limiter-CoWKv39w.js +2 -0
  145. package/dist/stream-limiter-CoWKv39w.js.map +1 -0
  146. package/dist/stream-limiter-JgOwmkMa.cjs +1 -0
  147. package/dist/streams/multi-stream.cjs +1 -0
  148. package/dist/streams/multi-stream.d.cts +91 -0
  149. package/dist/streams/multi-stream.d.cts.map +1 -0
  150. package/dist/streams/multi-stream.d.ts +86 -0
  151. package/dist/streams/multi-stream.d.ts.map +1 -0
  152. package/dist/streams/multi-stream.js +149 -0
  153. package/dist/streams/multi-stream.js.map +1 -0
  154. package/dist/streams/stream-limiter.cjs +1 -0
  155. package/dist/streams/stream-limiter.d.cts +36 -0
  156. package/dist/streams/stream-limiter.d.cts.map +1 -0
  157. package/dist/streams/stream-limiter.d.ts +27 -0
  158. package/dist/streams/stream-limiter.d.ts.map +1 -0
  159. package/dist/streams/stream-limiter.js +49 -0
  160. package/dist/streams/stream-splitter.cjs +1 -0
  161. package/dist/streams/stream-splitter.d.cts +68 -0
  162. package/dist/streams/stream-splitter.d.cts.map +1 -0
  163. package/dist/streams/stream-splitter.d.ts +51 -0
  164. package/dist/streams/stream-splitter.d.ts.map +1 -0
  165. package/dist/streams/stream-splitter.js +175 -0
  166. package/dist/streams/stream-splitter.js.map +1 -0
  167. package/dist/types/data-store-registry.d.ts +13 -0
  168. package/dist/types/data-store-registry.d.ts.map +1 -0
  169. package/dist/types/data-store-registry.js +4 -0
  170. package/dist/types/data-store.d.ts +316 -0
  171. package/dist/types/data-store.d.ts.map +1 -0
  172. package/dist/types/data-store.js +157 -0
  173. package/dist/types/event-broadcaster.d.ts +28 -0
  174. package/dist/types/event-broadcaster.d.ts.map +1 -0
  175. package/dist/types/event-broadcaster.js +6 -0
  176. package/dist/types/event-emitter.d.ts +378 -0
  177. package/dist/types/event-emitter.d.ts.map +1 -0
  178. package/dist/types/event-emitter.js +223 -0
  179. package/dist/types/index.cjs +1 -0
  180. package/dist/types/index.d.cts +6 -0
  181. package/dist/types/index.d.ts +10 -0
  182. package/dist/types/index.d.ts.map +1 -0
  183. package/dist/types/index.js +9 -0
  184. package/dist/types/input-file.d.ts +104 -0
  185. package/dist/types/input-file.d.ts.map +1 -0
  186. package/dist/types/input-file.js +27 -0
  187. package/dist/types/kv-store.d.ts +281 -0
  188. package/dist/types/kv-store.d.ts.map +1 -0
  189. package/dist/types/kv-store.js +234 -0
  190. package/dist/types/middleware.d.ts +17 -0
  191. package/dist/types/middleware.d.ts.map +1 -0
  192. package/dist/types/middleware.js +21 -0
  193. package/dist/types/upload-event.d.ts +105 -0
  194. package/dist/types/upload-event.d.ts.map +1 -0
  195. package/dist/types/upload-event.js +71 -0
  196. package/dist/types/upload-file.d.ts +136 -0
  197. package/dist/types/upload-file.d.ts.map +1 -0
  198. package/dist/types/upload-file.js +34 -0
  199. package/dist/types/websocket.d.ts +144 -0
  200. package/dist/types/websocket.d.ts.map +1 -0
  201. package/dist/types/websocket.js +40 -0
  202. package/dist/types-BT-cvi7T.cjs +1 -0
  203. package/dist/types-DhU2j-XF.js +2 -0
  204. package/dist/types-DhU2j-XF.js.map +1 -0
  205. package/dist/upload/convert-to-stream.d.ts +38 -0
  206. package/dist/upload/convert-to-stream.d.ts.map +1 -0
  207. package/dist/upload/convert-to-stream.js +43 -0
  208. package/dist/upload/convert-upload-to-flow-file.d.ts +14 -0
  209. package/dist/upload/convert-upload-to-flow-file.d.ts.map +1 -0
  210. package/dist/upload/convert-upload-to-flow-file.js +21 -0
  211. package/dist/upload/create-upload.d.ts +68 -0
  212. package/dist/upload/create-upload.d.ts.map +1 -0
  213. package/dist/upload/create-upload.js +157 -0
  214. package/dist/upload/index.cjs +1 -0
  215. package/dist/upload/index.d.cts +6 -0
  216. package/dist/upload/index.d.ts +4 -0
  217. package/dist/upload/index.d.ts.map +1 -0
  218. package/dist/upload/index.js +3 -0
  219. package/dist/upload/mime.d.ts +24 -0
  220. package/dist/upload/mime.d.ts.map +1 -0
  221. package/dist/upload/mime.js +351 -0
  222. package/dist/upload/upload-chunk.d.ts +58 -0
  223. package/dist/upload/upload-chunk.d.ts.map +1 -0
  224. package/dist/upload/upload-chunk.js +277 -0
  225. package/dist/upload/upload-server.d.ts +221 -0
  226. package/dist/upload/upload-server.d.ts.map +1 -0
  227. package/dist/upload/upload-server.js +181 -0
  228. package/dist/upload/upload-strategy-negotiator.d.ts +148 -0
  229. package/dist/upload/upload-strategy-negotiator.d.ts.map +1 -0
  230. package/dist/upload/upload-strategy-negotiator.js +217 -0
  231. package/dist/upload/upload-url.d.ts +68 -0
  232. package/dist/upload/upload-url.d.ts.map +1 -0
  233. package/dist/upload/upload-url.js +142 -0
  234. package/dist/upload/write-to-store.d.ts +77 -0
  235. package/dist/upload/write-to-store.d.ts.map +1 -0
  236. package/dist/upload/write-to-store.js +147 -0
  237. package/dist/upload-DLuICjpP.cjs +1 -0
  238. package/dist/upload-DaXO34dE.js +2 -0
  239. package/dist/upload-DaXO34dE.js.map +1 -0
  240. package/dist/uploadista-error-BB-Wdiz9.cjs +22 -0
  241. package/dist/uploadista-error-BVsVxqvz.js +23 -0
  242. package/dist/uploadista-error-BVsVxqvz.js.map +1 -0
  243. package/dist/uploadista-error-CwxYs4EB.d.ts +52 -0
  244. package/dist/uploadista-error-CwxYs4EB.d.ts.map +1 -0
  245. package/dist/uploadista-error-kKlhLRhY.d.cts +52 -0
  246. package/dist/uploadista-error-kKlhLRhY.d.cts.map +1 -0
  247. package/dist/utils/checksum.d.ts +22 -0
  248. package/dist/utils/checksum.d.ts.map +1 -0
  249. package/dist/utils/checksum.js +49 -0
  250. package/dist/utils/debounce.cjs +1 -0
  251. package/dist/utils/debounce.d.cts +38 -0
  252. package/dist/utils/debounce.d.cts.map +1 -0
  253. package/dist/utils/debounce.d.ts +36 -0
  254. package/dist/utils/debounce.d.ts.map +1 -0
  255. package/dist/utils/debounce.js +73 -0
  256. package/dist/utils/generate-id.cjs +1 -0
  257. package/dist/utils/generate-id.d.cts +2 -0
  258. package/dist/utils/generate-id.d.ts +32 -0
  259. package/dist/utils/generate-id.d.ts.map +1 -0
  260. package/dist/utils/generate-id.js +23 -0
  261. package/dist/utils/md5.cjs +1 -0
  262. package/dist/utils/md5.d.cts +73 -0
  263. package/dist/utils/md5.d.cts.map +1 -0
  264. package/dist/utils/md5.d.ts +71 -0
  265. package/dist/utils/md5.d.ts.map +1 -0
  266. package/dist/utils/md5.js +417 -0
  267. package/dist/utils/md5.js.map +1 -0
  268. package/dist/utils/once.cjs +1 -0
  269. package/dist/utils/once.d.cts +25 -0
  270. package/dist/utils/once.d.cts.map +1 -0
  271. package/dist/utils/once.d.ts +21 -0
  272. package/dist/utils/once.d.ts.map +1 -0
  273. package/dist/utils/once.js +54 -0
  274. package/dist/utils/once.js.map +1 -0
  275. package/dist/utils/semaphore.cjs +1 -0
  276. package/dist/utils/semaphore.d.cts +3 -0
  277. package/dist/utils/semaphore.d.ts +78 -0
  278. package/dist/utils/semaphore.d.ts.map +1 -0
  279. package/dist/utils/semaphore.js +134 -0
  280. package/dist/utils/throttle.cjs +1 -0
  281. package/dist/utils/throttle.d.cts +24 -0
  282. package/dist/utils/throttle.d.cts.map +1 -0
  283. package/dist/utils/throttle.d.ts +18 -0
  284. package/dist/utils/throttle.d.ts.map +1 -0
  285. package/dist/utils/throttle.js +20 -0
  286. package/dist/utils/throttle.js.map +1 -0
  287. package/docs/PARALLEL_EXECUTION.md +206 -0
  288. package/docs/PARALLEL_EXECUTION_QUICKSTART.md +142 -0
  289. package/docs/PARALLEL_EXECUTION_REFACTOR.md +184 -0
  290. package/package.json +80 -0
  291. package/src/errors/__tests__/uploadista-error.test.ts +251 -0
  292. package/src/errors/index.ts +2 -0
  293. package/src/errors/uploadista-error.ts +394 -0
  294. package/src/flow/README.md +352 -0
  295. package/src/flow/edge.test.ts +146 -0
  296. package/src/flow/edge.ts +60 -0
  297. package/src/flow/event.ts +229 -0
  298. package/src/flow/flow-server.ts +1089 -0
  299. package/src/flow/flow.ts +1050 -0
  300. package/src/flow/index.ts +28 -0
  301. package/src/flow/node.ts +249 -0
  302. package/src/flow/nodes/index.ts +8 -0
  303. package/src/flow/nodes/input-node.ts +296 -0
  304. package/src/flow/nodes/storage-node.ts +128 -0
  305. package/src/flow/nodes/transform-node.ts +154 -0
  306. package/src/flow/parallel-scheduler.ts +259 -0
  307. package/src/flow/plugins/credential-provider.ts +48 -0
  308. package/src/flow/plugins/image-ai-plugin.ts +66 -0
  309. package/src/flow/plugins/image-plugin.ts +60 -0
  310. package/src/flow/plugins/types/describe-image-node.ts +16 -0
  311. package/src/flow/plugins/types/index.ts +9 -0
  312. package/src/flow/plugins/types/optimize-node.ts +18 -0
  313. package/src/flow/plugins/types/remove-background-node.ts +18 -0
  314. package/src/flow/plugins/types/resize-node.ts +26 -0
  315. package/src/flow/plugins/zip-plugin.ts +69 -0
  316. package/src/flow/typed-flow.ts +279 -0
  317. package/src/flow/types/flow-file.ts +51 -0
  318. package/src/flow/types/flow-job.ts +138 -0
  319. package/src/flow/types/flow-types.ts +353 -0
  320. package/src/flow/types/index.ts +6 -0
  321. package/src/flow/types/run-args.ts +40 -0
  322. package/src/flow/types/type-validator.ts +204 -0
  323. package/src/flow/utils/resolve-upload-metadata.ts +48 -0
  324. package/src/index.ts +5 -0
  325. package/src/logger/logger.ts +14 -0
  326. package/src/streams/stream-limiter.test.ts +150 -0
  327. package/src/streams/stream-limiter.ts +75 -0
  328. package/src/types/data-store.ts +427 -0
  329. package/src/types/event-broadcaster.ts +39 -0
  330. package/src/types/event-emitter.ts +349 -0
  331. package/src/types/index.ts +9 -0
  332. package/src/types/input-file.ts +107 -0
  333. package/src/types/kv-store.ts +375 -0
  334. package/src/types/middleware.ts +54 -0
  335. package/src/types/upload-event.ts +75 -0
  336. package/src/types/upload-file.ts +139 -0
  337. package/src/types/websocket.ts +65 -0
  338. package/src/upload/convert-to-stream.ts +48 -0
  339. package/src/upload/create-upload.ts +214 -0
  340. package/src/upload/index.ts +3 -0
  341. package/src/upload/mime.ts +436 -0
  342. package/src/upload/upload-chunk.ts +364 -0
  343. package/src/upload/upload-server.ts +390 -0
  344. package/src/upload/upload-strategy-negotiator.ts +316 -0
  345. package/src/upload/upload-url.ts +173 -0
  346. package/src/upload/write-to-store.ts +211 -0
  347. package/src/utils/checksum.ts +61 -0
  348. package/src/utils/debounce.test.ts +126 -0
  349. package/src/utils/debounce.ts +89 -0
  350. package/src/utils/generate-id.ts +35 -0
  351. package/src/utils/md5.ts +475 -0
  352. package/src/utils/once.test.ts +83 -0
  353. package/src/utils/once.ts +63 -0
  354. package/src/utils/throttle.test.ts +101 -0
  355. package/src/utils/throttle.ts +29 -0
  356. package/tsconfig.json +20 -0
  357. package/tsconfig.tsbuildinfo +1 -0
  358. package/tsdown.config.ts +25 -0
  359. package/vitest.config.ts +15 -0
@@ -0,0 +1,364 @@
1
+ import { Effect, Metric, MetricBoundaries } from "effect";
2
+ import { UploadistaError } from "../errors/uploadista-error";
3
+ import {
4
+ type DataStore,
5
+ type EventEmitter,
6
+ type KvStore,
7
+ type UploadEvent,
8
+ UploadEventType,
9
+ type UploadFile,
10
+ type UploadFileDataStoresShape,
11
+ } from "../types";
12
+ import { computeChecksum } from "../utils/checksum";
13
+ import { compareMimeTypes, detectMimeType } from "./mime";
14
+ import { writeToStore } from "./write-to-store";
15
+
16
+ /**
17
+ * Uploads a chunk of data for an existing upload.
18
+ *
19
+ * This function handles the core chunk upload logic including:
20
+ * - Retrieving upload metadata from KV store
21
+ * - Routing to appropriate data store based on storage ID
22
+ * - Writing chunk data to storage with progress tracking
23
+ * - Updating upload offset and metadata
24
+ * - Emitting progress events
25
+ * - Validating upload completion (checksum, MIME type)
26
+ *
27
+ * The function includes comprehensive observability with:
28
+ * - Effect tracing spans for performance monitoring
29
+ * - Metrics tracking for chunk size, throughput, and success rates
30
+ * - Structured logging for debugging and monitoring
31
+ * - Error handling with proper UploadistaError types
32
+ *
33
+ * @param uploadId - Unique identifier for the upload
34
+ * @param clientId - Client identifier (null for anonymous uploads)
35
+ * @param chunk - ReadableStream containing the chunk data to upload
36
+ * @param dataStoreService - Service for routing to appropriate data stores
37
+ * @param kvStore - KV store for upload metadata persistence
38
+ * @param eventEmitter - Event emitter for progress and validation events
39
+ * @returns Effect that yields the updated UploadFile with new offset
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * // Upload a chunk for an existing upload
44
+ * const uploadChunkEffect = uploadChunk(
45
+ * "upload-123",
46
+ * "client-456",
47
+ * chunkStream,
48
+ * {
49
+ * dataStoreService,
50
+ * kvStore,
51
+ * eventEmitter
52
+ * }
53
+ * );
54
+ *
55
+ * // Run with dependencies
56
+ * const result = await Effect.runPromise(
57
+ * uploadChunkEffect.pipe(
58
+ * Effect.provide(dataStoreLayer),
59
+ * Effect.provide(kvStoreLayer),
60
+ * Effect.provide(eventEmitterLayer)
61
+ * )
62
+ * );
63
+ * ```
64
+ */
65
+ export const uploadChunk = (
66
+ uploadId: string,
67
+ clientId: string | null,
68
+ chunk: ReadableStream,
69
+ {
70
+ dataStoreService,
71
+ kvStore,
72
+ eventEmitter,
73
+ }: {
74
+ dataStoreService: UploadFileDataStoresShape;
75
+ kvStore: KvStore<UploadFile>;
76
+ eventEmitter: EventEmitter<UploadEvent>;
77
+ }
78
+ ) =>
79
+ Effect.gen(function* () {
80
+ // Get file from KV store
81
+ const file = yield* kvStore.get(uploadId);
82
+
83
+ // Get datastore
84
+ const dataStore = yield* dataStoreService.getDataStore(
85
+ file.storage.id,
86
+ clientId
87
+ );
88
+
89
+ // Note: AbortController could be used for cancellation if needed
90
+
91
+ // Write to store using writeToStore Effect
92
+ const controller = new AbortController();
93
+
94
+ const chunkSize = yield* writeToStore({
95
+ dataStore,
96
+ data: chunk,
97
+ upload: file,
98
+ maxFileSize: 100_000_000,
99
+ controller,
100
+ uploadProgressInterval: 200,
101
+ eventEmitter,
102
+ });
103
+
104
+ file.offset = chunkSize;
105
+
106
+ // Update KV store
107
+ yield* kvStore.set(uploadId, file);
108
+
109
+ // Emit progress event
110
+ yield* eventEmitter.emit(file.id, {
111
+ type: UploadEventType.UPLOAD_PROGRESS,
112
+ data: {
113
+ id: file.id,
114
+ progress: file.offset,
115
+ total: file.size ?? 0,
116
+ },
117
+ flow: file.flow,
118
+ });
119
+
120
+ // Check if upload is complete and run validation
121
+ if (file.size && file.offset === file.size) {
122
+ yield* validateUpload({
123
+ file,
124
+ dataStore,
125
+ eventEmitter,
126
+ });
127
+ }
128
+
129
+ return file;
130
+ }).pipe(
131
+ // Add tracing span for chunk upload
132
+ Effect.withSpan("upload-chunk", {
133
+ attributes: {
134
+ "upload.id": uploadId,
135
+ "chunk.upload_id": uploadId,
136
+ },
137
+ }),
138
+ // Track chunk upload metrics
139
+ Effect.tap((file) =>
140
+ Effect.gen(function* () {
141
+ // Increment chunk uploaded counter
142
+ yield* Metric.increment(
143
+ Metric.counter("chunk_uploaded_total", {
144
+ description: "Total number of chunks uploaded",
145
+ })
146
+ );
147
+
148
+ // Record chunk size
149
+ const chunkSize = file.offset;
150
+ const chunkSizeHistogram = Metric.histogram(
151
+ "chunk_size_bytes",
152
+ MetricBoundaries.linear({
153
+ start: 262_144,
154
+ width: 262_144,
155
+ count: 20,
156
+ })
157
+ );
158
+ yield* Metric.update(chunkSizeHistogram, chunkSize);
159
+
160
+ // Update throughput gauge
161
+ if (file.size && file.size > 0) {
162
+ const throughput = chunkSize; // bytes processed
163
+ const throughputGauge = Metric.gauge(
164
+ "upload_throughput_bytes_per_second"
165
+ );
166
+ yield* Metric.set(throughputGauge, throughput);
167
+ }
168
+ })
169
+ ),
170
+ // Add structured logging for chunk progress
171
+ Effect.tap((file) =>
172
+ Effect.logDebug("Chunk uploaded").pipe(
173
+ Effect.annotateLogs({
174
+ "upload.id": file.id,
175
+ "chunk.size": file.offset.toString(),
176
+ "chunk.progress":
177
+ file.size && file.size > 0
178
+ ? ((file.offset / file.size) * 100).toFixed(2)
179
+ : "0",
180
+ "upload.total_size": file.size?.toString() ?? "0",
181
+ })
182
+ )
183
+ ),
184
+ // Handle errors with logging
185
+ Effect.tapError((error) =>
186
+ Effect.logError("Chunk upload failed").pipe(
187
+ Effect.annotateLogs({
188
+ "upload.id": uploadId,
189
+ error: String(error),
190
+ })
191
+ )
192
+ )
193
+ );
194
+
195
+ /**
196
+ * Validates an upload after completion.
197
+ *
198
+ * Performs comprehensive validation including:
199
+ * - Checksum validation (if provided) using the specified algorithm
200
+ * - MIME type validation (if required by data store capabilities)
201
+ * - File size validation against data store limits
202
+ *
203
+ * Validation results are emitted as events and failures result in:
204
+ * - Cleanup of uploaded data from storage
205
+ * - Removal of metadata from KV store
206
+ * - Appropriate error responses
207
+ *
208
+ * The function respects data store capabilities for validation limits
209
+ * and provides detailed error information for debugging.
210
+ *
211
+ * @param file - The upload file to validate
212
+ * @param dataStore - Data store containing the uploaded file
213
+ * @param eventEmitter - Event emitter for validation events
214
+ * @returns Effect that completes validation or fails with UploadistaError
215
+ *
216
+ * @example
217
+ * ```typescript
218
+ * // Validate upload after completion
219
+ * const validationEffect = validateUpload({
220
+ * file: completedUpload,
221
+ * dataStore: s3DataStore,
222
+ * eventEmitter: progressEmitter
223
+ * });
224
+ *
225
+ * // Run validation
226
+ * await Effect.runPromise(validationEffect);
227
+ * ```
228
+ */
229
+ const validateUpload = ({
230
+ file,
231
+ dataStore,
232
+ eventEmitter,
233
+ }: {
234
+ file: UploadFile;
235
+ dataStore: DataStore<UploadFile>;
236
+ eventEmitter: EventEmitter<UploadEvent>;
237
+ }): Effect.Effect<void, UploadistaError, never> =>
238
+ Effect.gen(function* () {
239
+ const capabilities = dataStore.getCapabilities();
240
+
241
+ // Check if file exceeds max validation size
242
+ if (
243
+ capabilities.maxValidationSize &&
244
+ file.size &&
245
+ file.size > capabilities.maxValidationSize
246
+ ) {
247
+ yield* eventEmitter.emit(file.id, {
248
+ type: UploadEventType.UPLOAD_VALIDATION_WARNING,
249
+ data: {
250
+ id: file.id,
251
+ message: `File size (${file.size} bytes) exceeds max validation size (${capabilities.maxValidationSize} bytes). Validation skipped.`,
252
+ },
253
+ flow: file.flow,
254
+ });
255
+ return;
256
+ }
257
+
258
+ // Read file from datastore for validation
259
+ const fileBytes = yield* dataStore.read(file.id);
260
+
261
+ // Validate checksum if provided
262
+ if (file.checksum && file.checksumAlgorithm) {
263
+ const computedChecksum = yield* computeChecksum(
264
+ fileBytes,
265
+ file.checksumAlgorithm
266
+ );
267
+
268
+ if (computedChecksum !== file.checksum) {
269
+ // Emit validation failure event
270
+ yield* eventEmitter.emit(file.id, {
271
+ type: UploadEventType.UPLOAD_VALIDATION_FAILED,
272
+ data: {
273
+ id: file.id,
274
+ reason: "checksum_mismatch",
275
+ expected: file.checksum,
276
+ actual: computedChecksum,
277
+ },
278
+ flow: file.flow,
279
+ });
280
+
281
+ // Clean up file and remove from KV store
282
+ yield* dataStore.remove(file.id);
283
+
284
+ // Fail with checksum mismatch error
285
+ return yield* UploadistaError.fromCode("CHECKSUM_MISMATCH", {
286
+ body: `Checksum validation failed. Expected: ${file.checksum}, Got: ${computedChecksum}`,
287
+ details: {
288
+ uploadId: file.id,
289
+ expected: file.checksum,
290
+ actual: computedChecksum,
291
+ algorithm: file.checksumAlgorithm,
292
+ },
293
+ }).toEffect();
294
+ }
295
+
296
+ // Emit checksum validation success
297
+ yield* eventEmitter.emit(file.id, {
298
+ type: UploadEventType.UPLOAD_VALIDATION_SUCCESS,
299
+ data: {
300
+ id: file.id,
301
+ validationType: "checksum",
302
+ algorithm: file.checksumAlgorithm,
303
+ },
304
+ flow: file.flow,
305
+ });
306
+ }
307
+
308
+ // Validate MIME type if required by capabilities
309
+ if (capabilities.requiresMimeTypeValidation) {
310
+ const detectedMimeType = detectMimeType(fileBytes);
311
+ const declaredMimeType = file.metadata?.type as string | undefined;
312
+
313
+ if (
314
+ declaredMimeType &&
315
+ !compareMimeTypes(declaredMimeType, detectedMimeType)
316
+ ) {
317
+ // Emit validation failure event
318
+ yield* eventEmitter.emit(file.id, {
319
+ type: UploadEventType.UPLOAD_VALIDATION_FAILED,
320
+ data: {
321
+ id: file.id,
322
+ reason: "mimetype_mismatch",
323
+ expected: declaredMimeType,
324
+ actual: detectedMimeType,
325
+ },
326
+ flow: file.flow,
327
+ });
328
+
329
+ // Clean up file and remove from KV store
330
+ yield* dataStore.remove(file.id);
331
+
332
+ // Fail with MIME type mismatch error
333
+ return yield* UploadistaError.fromCode("MIMETYPE_MISMATCH", {
334
+ body: `MIME type validation failed. Expected: ${declaredMimeType}, Detected: ${detectedMimeType}`,
335
+ details: {
336
+ uploadId: file.id,
337
+ expected: declaredMimeType,
338
+ actual: detectedMimeType,
339
+ },
340
+ }).toEffect();
341
+ }
342
+
343
+ // Emit MIME type validation success
344
+ yield* eventEmitter.emit(file.id, {
345
+ type: UploadEventType.UPLOAD_VALIDATION_SUCCESS,
346
+ data: {
347
+ id: file.id,
348
+ validationType: "mimetype",
349
+ },
350
+ flow: file.flow,
351
+ });
352
+ }
353
+ }).pipe(
354
+ Effect.withSpan("validate-upload", {
355
+ attributes: {
356
+ "upload.id": file.id,
357
+ "validation.checksum_provided": file.checksum ? "true" : "false",
358
+ "validation.mime_required": dataStore.getCapabilities()
359
+ .requiresMimeTypeValidation
360
+ ? "true"
361
+ : "false",
362
+ },
363
+ })
364
+ );