@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,173 @@
1
+ import { Effect, Metric } from "effect";
2
+ import { UploadistaError } from "../errors";
3
+
4
+ /**
5
+ * Fetches a file from a remote URL.
6
+ *
7
+ * This function handles HTTP requests to remote URLs for file uploads,
8
+ * including proper error handling, metrics tracking, and observability.
9
+ *
10
+ * Features:
11
+ * - HTTP request with proper error handling
12
+ * - Effect tracing for performance monitoring
13
+ * - Metrics tracking for URL-based uploads
14
+ * - Structured logging for debugging
15
+ * - Response validation and error reporting
16
+ *
17
+ * @param url - The remote URL to fetch the file from
18
+ * @returns Effect that yields the Response object
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * // Fetch a file from URL
23
+ * const fetchEffect = fetchFile("https://example.com/image.jpg");
24
+ *
25
+ * // Run with error handling
26
+ * const response = await Effect.runPromise(
27
+ * fetchEffect.pipe(
28
+ * Effect.catchAll((error) =>
29
+ * Effect.logError("Failed to fetch file").pipe(
30
+ * Effect.andThen(Effect.fail(error))
31
+ * )
32
+ * )
33
+ * )
34
+ * );
35
+ * ```
36
+ */
37
+ export const fetchFile = (url: string) => {
38
+ return Effect.tryPromise({
39
+ try: async () => {
40
+ return await fetch(url);
41
+ },
42
+ catch: (error) => {
43
+ return UploadistaError.fromCode("UNKNOWN_ERROR", {
44
+ cause: error,
45
+ });
46
+ },
47
+ }).pipe(
48
+ // Add tracing span for URL fetch
49
+ Effect.withSpan("upload-fetch-url", {
50
+ attributes: {
51
+ "upload.url": url,
52
+ "upload.operation": "fetch",
53
+ },
54
+ }),
55
+ // Track URL fetch metrics
56
+ Effect.tap((response) =>
57
+ Effect.gen(function* () {
58
+ // Increment URL upload counter
59
+ yield* Metric.increment(
60
+ Metric.counter("upload_from_url_total", {
61
+ description: "Total number of URL-based uploads",
62
+ })
63
+ );
64
+
65
+ // Track success/failure
66
+ if (response.ok) {
67
+ yield* Metric.increment(
68
+ Metric.counter("upload_from_url_success_total", {
69
+ description: "Total number of successful URL-based uploads",
70
+ })
71
+ );
72
+ }
73
+ })
74
+ ),
75
+ // Add structured logging
76
+ Effect.tap((response) =>
77
+ Effect.logInfo("URL fetch completed").pipe(
78
+ Effect.annotateLogs({
79
+ "upload.url": url,
80
+ "response.status": response.status.toString(),
81
+ "response.ok": response.ok.toString(),
82
+ "response.content_length":
83
+ response.headers.get("content-length") ?? "unknown",
84
+ })
85
+ )
86
+ ),
87
+ // Handle errors with logging and metrics
88
+ Effect.tapError((error) =>
89
+ Effect.gen(function* () {
90
+ // Track failed URL upload
91
+ yield* Metric.increment(
92
+ Metric.counter("upload_from_url_failed_total", {
93
+ description: "Total number of failed URL-based uploads",
94
+ })
95
+ );
96
+
97
+ // Log error
98
+ yield* Effect.logError("URL fetch failed").pipe(
99
+ Effect.annotateLogs({
100
+ "upload.url": url,
101
+ error: String(error),
102
+ })
103
+ );
104
+ })
105
+ )
106
+ );
107
+ };
108
+
109
+ /**
110
+ * Converts a Response object to an ArrayBuffer.
111
+ *
112
+ * This function safely converts HTTP response data to binary format
113
+ * for processing and storage, with proper error handling and observability.
114
+ *
115
+ * Features:
116
+ * - Safe conversion from Response to ArrayBuffer
117
+ * - Effect tracing for performance monitoring
118
+ * - Structured logging for debugging
119
+ * - Error handling with proper UploadistaError types
120
+ *
121
+ * @param response - The HTTP Response object to convert
122
+ * @returns Effect that yields the ArrayBuffer data
123
+ *
124
+ * @example
125
+ * ```typescript
126
+ * // Convert response to buffer
127
+ * const bufferEffect = arrayBuffer(response);
128
+ *
129
+ * // Use in upload pipeline
130
+ * const buffer = await Effect.runPromise(
131
+ * bufferEffect.pipe(
132
+ * Effect.tap((buffer) =>
133
+ * Effect.logInfo(`Buffer size: ${buffer.byteLength} bytes`)
134
+ * )
135
+ * )
136
+ * );
137
+ * ```
138
+ */
139
+ export const arrayBuffer = (response: Response) => {
140
+ return Effect.tryPromise({
141
+ try: async () => {
142
+ return await response.arrayBuffer();
143
+ },
144
+ catch: (error) => {
145
+ return UploadistaError.fromCode("UNKNOWN_ERROR", {
146
+ cause: error,
147
+ });
148
+ },
149
+ }).pipe(
150
+ // Add tracing span for buffer conversion
151
+ Effect.withSpan("upload-convert-to-buffer", {
152
+ attributes: {
153
+ "upload.operation": "arrayBuffer",
154
+ },
155
+ }),
156
+ // Add structured logging
157
+ Effect.tap((buffer) =>
158
+ Effect.logDebug("Response converted to array buffer").pipe(
159
+ Effect.annotateLogs({
160
+ "buffer.size": buffer.byteLength.toString(),
161
+ })
162
+ )
163
+ ),
164
+ // Handle errors with logging
165
+ Effect.tapError((error) =>
166
+ Effect.logError("Failed to convert response to array buffer").pipe(
167
+ Effect.annotateLogs({
168
+ error: String(error),
169
+ })
170
+ )
171
+ )
172
+ );
173
+ };
@@ -0,0 +1,211 @@
1
+ import { Effect, Ref } from "effect";
2
+ import { UploadistaError } from "../errors";
3
+ import { StreamLimiterEffect } from "../streams/stream-limiter";
4
+ import type { DataStore, UploadEvent, UploadFile } from "../types";
5
+ import { type EventEmitter, UploadEventType } from "../types";
6
+ import { convertToStream } from "./convert-to-stream";
7
+
8
+ /**
9
+ * Configuration options for writing data to a data store.
10
+ *
11
+ * @property data - The stream of data to write
12
+ * @property upload - Upload file metadata
13
+ * @property dataStore - Target data store for writing
14
+ * @property maxFileSize - Maximum allowed file size in bytes
15
+ * @property controller - AbortController for cancellation
16
+ * @property eventEmitter - Event emitter for progress tracking
17
+ * @property uploadProgressInterval - Progress emission interval in milliseconds (default: 200)
18
+ */
19
+ type WriteToStoreOptions = {
20
+ data: ReadableStream<Uint8Array>;
21
+ upload: UploadFile;
22
+ dataStore: DataStore<UploadFile>;
23
+ maxFileSize: number;
24
+ controller: AbortController;
25
+ eventEmitter: EventEmitter<UploadEvent>;
26
+ uploadProgressInterval?: number;
27
+ };
28
+
29
+ /**
30
+ * Writes data stream to a data store with progress tracking and size limits.
31
+ *
32
+ * This function handles the core data writing logic including:
33
+ * - Stream conversion and processing
34
+ * - File size validation and limiting
35
+ * - Progress tracking with throttled events
36
+ * - Abort signal handling for cancellation
37
+ * - Error handling and cleanup
38
+ *
39
+ * The function includes comprehensive observability with:
40
+ * - Effect tracing spans for performance monitoring
41
+ * - Structured logging for debugging and monitoring
42
+ * - Progress event emission with throttling
43
+ * - Error handling with proper UploadistaError types
44
+ *
45
+ * @param data - The stream of data to write to storage
46
+ * @param upload - Upload file metadata containing ID, offset, etc.
47
+ * @param dataStore - Target data store for writing the data
48
+ * @param maxFileSize - Maximum allowed file size in bytes
49
+ * @param controller - AbortController for handling cancellation
50
+ * @param eventEmitter - Event emitter for progress tracking
51
+ * @param uploadProgressInterval - Progress emission interval in milliseconds (default: 200)
52
+ * @returns Effect that yields the number of bytes written
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * // Write data to store with progress tracking
57
+ * const writeEffect = writeToStore({
58
+ * data: fileStream,
59
+ * upload: uploadMetadata,
60
+ * dataStore: s3DataStore,
61
+ * maxFileSize: 100_000_000, // 100MB
62
+ * controller: abortController,
63
+ * eventEmitter: progressEmitter,
64
+ * uploadProgressInterval: 500 // Emit progress every 500ms
65
+ * });
66
+ *
67
+ * // Run with error handling
68
+ * const bytesWritten = await Effect.runPromise(
69
+ * writeEffect.pipe(
70
+ * Effect.catchAll((error) =>
71
+ * Effect.logError("Failed to write to store").pipe(
72
+ * Effect.andThen(Effect.fail(error))
73
+ * )
74
+ * )
75
+ * )
76
+ * );
77
+ * ```
78
+ */
79
+ export function writeToStore({
80
+ data,
81
+ upload,
82
+ dataStore,
83
+ maxFileSize,
84
+ controller,
85
+ eventEmitter,
86
+ uploadProgressInterval = 200,
87
+ }: WriteToStoreOptions) {
88
+ return Effect.gen(function* () {
89
+ const stream = convertToStream(data);
90
+ // Check if already aborted
91
+ if (controller.signal.aborted) {
92
+ return yield* Effect.fail(UploadistaError.fromCode("ABORTED"));
93
+ }
94
+
95
+ // Create an AbortController to manage the stream pipeline
96
+ const abortController = new AbortController();
97
+ const { signal } = abortController;
98
+
99
+ // Set up abort handling
100
+ const onAbort = () => {
101
+ // stream.cancel();
102
+ abortController.abort();
103
+ };
104
+
105
+ controller.signal.addEventListener("abort", onAbort, { once: true });
106
+
107
+ return yield* Effect.acquireUseRelease(
108
+ Effect.sync(() => ({ signal, onAbort })),
109
+ ({ signal: _signal }) =>
110
+ Effect.gen(function* () {
111
+ // Create a ref to track the last progress emission time for throttling
112
+ const lastEmitTime = yield* Ref.make(0);
113
+
114
+ // Create the stream limiter
115
+ const limiter = StreamLimiterEffect.limit({
116
+ maxSize: maxFileSize,
117
+ });
118
+
119
+ // Pipe the data through the limiter
120
+ const limitedStream = limiter(stream);
121
+
122
+ // Write to the data store with progress tracking
123
+ const offset = yield* dataStore.write(
124
+ {
125
+ stream: limitedStream,
126
+ file_id: upload.id,
127
+ offset: upload.offset,
128
+ },
129
+ {
130
+ onProgress: (newOffset: number) => {
131
+ // Simple throttling using timestamp check
132
+ const now = Date.now();
133
+ Ref.get(lastEmitTime)
134
+ .pipe(
135
+ Effect.flatMap((lastTime) => {
136
+ if (now - lastTime >= uploadProgressInterval) {
137
+ return Effect.gen(function* () {
138
+ yield* Ref.set(lastEmitTime, now);
139
+ yield* eventEmitter.emit(upload.id, {
140
+ type: UploadEventType.UPLOAD_PROGRESS,
141
+ data: {
142
+ id: upload.id,
143
+ progress: newOffset,
144
+ total: upload.size ?? 0,
145
+ },
146
+ flow: upload.flow,
147
+ });
148
+ });
149
+ }
150
+ return Effect.void;
151
+ }),
152
+ Effect.runPromise
153
+ )
154
+ .catch(() => {
155
+ // Ignore errors during progress emission
156
+ });
157
+ },
158
+ }
159
+ );
160
+
161
+ return offset;
162
+ }).pipe(
163
+ Effect.catchAll((error) => {
164
+ if (error instanceof Error && error.name === "AbortError") {
165
+ return Effect.fail(UploadistaError.fromCode("ABORTED"));
166
+ }
167
+ if (error instanceof UploadistaError) {
168
+ return Effect.fail(error);
169
+ }
170
+ return Effect.fail(
171
+ UploadistaError.fromCode("FILE_WRITE_ERROR", { cause: error })
172
+ );
173
+ })
174
+ ),
175
+ ({ onAbort }) =>
176
+ Effect.sync(() => {
177
+ controller.signal.removeEventListener("abort", onAbort);
178
+ })
179
+ );
180
+ }).pipe(
181
+ // Add tracing span for write operation
182
+ Effect.withSpan("upload-write-to-store", {
183
+ attributes: {
184
+ "upload.id": upload.id,
185
+ "upload.offset": upload.offset.toString(),
186
+ "upload.max_file_size": maxFileSize.toString(),
187
+ "upload.file_size": upload.size?.toString() ?? "0",
188
+ },
189
+ }),
190
+ // Add structured logging for write operation
191
+ Effect.tap((offset) =>
192
+ Effect.logDebug("Data written to store").pipe(
193
+ Effect.annotateLogs({
194
+ "upload.id": upload.id,
195
+ "write.offset": offset.toString(),
196
+ "write.bytes_written": (offset - upload.offset).toString(),
197
+ })
198
+ )
199
+ ),
200
+ // Handle errors with logging
201
+ Effect.tapError((error) =>
202
+ Effect.logError("Failed to write to store").pipe(
203
+ Effect.annotateLogs({
204
+ "upload.id": upload.id,
205
+ "upload.offset": upload.offset.toString(),
206
+ error: error instanceof UploadistaError ? error.code : String(error),
207
+ })
208
+ )
209
+ )
210
+ );
211
+ }
@@ -0,0 +1,61 @@
1
+ import { Effect } from "effect";
2
+ import { UploadistaError } from "../errors/uploadista-error";
3
+
4
+ /**
5
+ * Supported checksum algorithms
6
+ */
7
+ const SUPPORTED_ALGORITHMS = ["sha256"] as const;
8
+ export type ChecksumAlgorithm = (typeof SUPPORTED_ALGORITHMS)[number];
9
+
10
+ /**
11
+ * Check if a checksum algorithm is supported
12
+ */
13
+ export function isSupportedAlgorithm(algorithm: string): algorithm is ChecksumAlgorithm {
14
+ return SUPPORTED_ALGORITHMS.includes(algorithm as ChecksumAlgorithm);
15
+ }
16
+
17
+ /**
18
+ * Compute checksum of file bytes using the Web Crypto API.
19
+ * This works across all modern platforms: browsers, Node.js 15+, Deno, Bun, and Cloudflare Workers.
20
+ *
21
+ * @param bytes - File content as Uint8Array
22
+ * @param algorithm - Hashing algorithm to use (currently only 'sha256' is supported)
23
+ * @returns Effect that resolves to hex-encoded checksum string
24
+ */
25
+ export function computeChecksum(
26
+ bytes: Uint8Array,
27
+ algorithm: string,
28
+ ): Effect.Effect<string, UploadistaError> {
29
+ return Effect.gen(function* () {
30
+ // Validate algorithm is supported
31
+ if (!isSupportedAlgorithm(algorithm)) {
32
+ return yield* UploadistaError.fromCode("UNSUPPORTED_CHECKSUM_ALGORITHM", {
33
+ body: `Checksum algorithm '${algorithm}' is not supported. Supported algorithms: ${SUPPORTED_ALGORITHMS.join(", ")}`,
34
+ details: { algorithm, supportedAlgorithms: SUPPORTED_ALGORITHMS },
35
+ }).toEffect();
36
+ }
37
+
38
+ // Map algorithm name to Web Crypto API algorithm name
39
+ const webCryptoAlgorithm = algorithm.toUpperCase().replace(/\d+/, "-$&"); // "sha256" -> "SHA-256"
40
+
41
+ // Compute hash using Web Crypto API (available in browsers, Node.js 15+, Deno, Bun, Cloudflare Workers)
42
+ // Pass Uint8Array directly - it's a valid BufferSource
43
+ const hashBuffer = yield* Effect.tryPromise({
44
+ try: () => crypto.subtle.digest(webCryptoAlgorithm, bytes as BufferSource),
45
+ catch: (error) =>
46
+ UploadistaError.fromCode("UNKNOWN_ERROR", {
47
+ body: `Failed to compute checksum: ${error instanceof Error ? error.message : "Unknown error"}`,
48
+ cause: error,
49
+ details: { algorithm },
50
+ }),
51
+ });
52
+
53
+ // Convert ArrayBuffer to hex string
54
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
55
+ const hashHex = hashArray
56
+ .map((byte) => byte.toString(16).padStart(2, "0"))
57
+ .join("");
58
+
59
+ return hashHex;
60
+ });
61
+ }
@@ -0,0 +1,126 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { debounce } from "./debounce";
3
+
4
+ describe("debounce", () => {
5
+ beforeEach(() => {
6
+ vi.useFakeTimers();
7
+ });
8
+
9
+ afterEach(() => {
10
+ vi.useRealTimers();
11
+ });
12
+
13
+ it("should delay function execution by the specified time", () => {
14
+ const mockFn = vi.fn();
15
+ const debouncedFn = debounce(mockFn, 100);
16
+
17
+ debouncedFn();
18
+ expect(mockFn).not.toHaveBeenCalled();
19
+
20
+ vi.advanceTimersByTime(99);
21
+ expect(mockFn).not.toHaveBeenCalled();
22
+
23
+ vi.advanceTimersByTime(1);
24
+ expect(mockFn).toHaveBeenCalledOnce();
25
+ });
26
+
27
+ it("should reset delay on subsequent calls", () => {
28
+ const mockFn = vi.fn();
29
+ const debouncedFn = debounce(mockFn, 100);
30
+
31
+ debouncedFn();
32
+ vi.advanceTimersByTime(50);
33
+ debouncedFn(); // This should reset the timer
34
+
35
+ vi.advanceTimersByTime(99);
36
+ expect(mockFn).not.toHaveBeenCalled();
37
+
38
+ vi.advanceTimersByTime(1);
39
+ expect(mockFn).toHaveBeenCalledOnce();
40
+ });
41
+
42
+ it("should call function immediately with leading: true", () => {
43
+ const mockFn = vi.fn();
44
+ const debouncedFn = debounce(mockFn, 100, { leading: true });
45
+
46
+ debouncedFn();
47
+ expect(mockFn).toHaveBeenCalledOnce();
48
+
49
+ vi.advanceTimersByTime(100);
50
+ expect(mockFn).toHaveBeenCalledOnce(); // Should not be called again
51
+ });
52
+
53
+ it("should not call function on trailing edge with trailing: false", () => {
54
+ const mockFn = vi.fn();
55
+ const debouncedFn = debounce(mockFn, 100, { trailing: false });
56
+
57
+ debouncedFn();
58
+ vi.advanceTimersByTime(100);
59
+ expect(mockFn).not.toHaveBeenCalled();
60
+ });
61
+
62
+ it("should call function on both leading and trailing edges when both options are true", () => {
63
+ const mockFn = vi.fn();
64
+ const debouncedFn = debounce(mockFn, 100, {
65
+ leading: true,
66
+ trailing: true,
67
+ });
68
+
69
+ debouncedFn();
70
+ expect(mockFn).toHaveBeenCalledOnce();
71
+
72
+ debouncedFn(); // Second call within delay
73
+ vi.advanceTimersByTime(100);
74
+ expect(mockFn).toHaveBeenCalledTimes(2);
75
+ });
76
+
77
+ it("should preserve function arguments", () => {
78
+ const mockFn = vi.fn();
79
+ const debouncedFn = debounce(mockFn, 100);
80
+
81
+ debouncedFn("arg1", "arg2", 123);
82
+ vi.advanceTimersByTime(100);
83
+
84
+ expect(mockFn).toHaveBeenCalledWith("arg1", "arg2", 123);
85
+ });
86
+
87
+ it("should preserve function context (this)", () => {
88
+ const obj = {
89
+ value: 42,
90
+ fn: vi.fn(function (this: { value: number }) {
91
+ return this.value;
92
+ }),
93
+ };
94
+
95
+ const debouncedFn = debounce(obj.fn, 100);
96
+ debouncedFn.call(obj);
97
+
98
+ vi.advanceTimersByTime(100);
99
+ expect(obj.fn).toHaveBeenCalledOnce();
100
+ });
101
+
102
+ it("should handle multiple rapid calls correctly", () => {
103
+ const mockFn = vi.fn();
104
+ const debouncedFn = debounce(mockFn, 100);
105
+
106
+ // Make 5 rapid calls
107
+ for (let i = 0; i < 5; i++) {
108
+ debouncedFn(i);
109
+ }
110
+
111
+ vi.advanceTimersByTime(100);
112
+ expect(mockFn).toHaveBeenCalledOnce();
113
+ expect(mockFn).toHaveBeenCalledWith(4); // Should be called with the last argument
114
+ });
115
+
116
+ it("should use default options when none provided", () => {
117
+ const mockFn = vi.fn();
118
+ const debouncedFn = debounce(mockFn, 100);
119
+
120
+ debouncedFn();
121
+ expect(mockFn).not.toHaveBeenCalled(); // leading: false by default
122
+
123
+ vi.advanceTimersByTime(100);
124
+ expect(mockFn).toHaveBeenCalledOnce(); // trailing: true by default
125
+ });
126
+ });
@@ -0,0 +1,89 @@
1
+ type DebounceOptions = {
2
+ // Whether the first call to the debounced function is run immediately.
3
+ leading?: boolean;
4
+
5
+ // Whether the last call to the debounced function is run after delay
6
+ // milliseconds have elapsed since the last call.
7
+ trailing?: boolean;
8
+ };
9
+
10
+ const defaultDebounceOptions: DebounceOptions = {
11
+ leading: false,
12
+ trailing: true,
13
+ };
14
+
15
+ /**
16
+ * Returns `fn` wrapped by a function that delays invoking `fn` for `delay`
17
+ * milliseconds since the last call. Set `options.leading` to invoke `fn` on
18
+ * the leading edge of the delay, and/or set `options.trailing` to invoke `fn`
19
+ * on the trailing edge of the delay (true by default).
20
+ *
21
+ * Example for `debounce(fn, 30, {leading: true, trailing: true})`,
22
+ * where `fn` is called twice, with the second call made 20 ms after the first:
23
+ *
24
+ * Time: 0 20 50 (ms)
25
+ * Timeline: |----------------------------------------------------------------|
26
+ * ^ ^ ^
27
+ * | | |
28
+ * | First call. | Second call 20ms after the | End of delay.
29
+ * | (instant leading | first. | (trailing edge
30
+ * | edge call) | | call)
31
+ * |-----------------------------|
32
+ * | 30 ms delay for debounce. |
33
+ *
34
+ *
35
+ * Note that if both `options.leading` and `options.trailing` are true, `fn`
36
+ * will only be invoked on the trailing edge if the debounced function is called
37
+ * more than once during the delay.
38
+ *
39
+ * @param fn - Function to debounce.
40
+ * @param delay - Milliseconds to delay calling `fn` since the last call.
41
+ * @param debounceOptions - See `DebounceOptions` and `defaultDebounceOptions`.
42
+ * @returns A debounced `fn`.
43
+ */
44
+ export function debounce<T, A extends unknown[]>(
45
+ fn: (this: T, ...args: A) => void,
46
+ delay: number,
47
+ debounceOptions: DebounceOptions = {},
48
+ ): (this: T, ...args: A) => void {
49
+ const options = { ...defaultDebounceOptions, ...debounceOptions };
50
+
51
+ let timer: ReturnType<typeof setTimeout> | undefined;
52
+
53
+ return function (this: T, ...args: A): void {
54
+ if (options.leading && !timer) {
55
+ // Leading edge.
56
+ // Call fn on the leading edge, when debouncing hasn't started yet.
57
+ console.log("leading");
58
+ fn.apply(this, args);
59
+
60
+ // Debounce the next call.
61
+ timer = setTimeout(() => {
62
+ timer = undefined;
63
+ }, delay);
64
+ } else {
65
+ // Trailing edge.
66
+ // Postpone calling fn until the delay has elapsed since the last call.
67
+ // Each call clears any previously delayed call and resets the delay, so
68
+ // the postponed call will always be the last one.
69
+ clearTimeout(timer);
70
+ timer = setTimeout(() => {
71
+ if (options.trailing) {
72
+ // Call fn on the trailing edge.
73
+ fn.apply(this, args);
74
+
75
+ if (options.leading) {
76
+ // Debounce next leading call since a trailing call was just made.
77
+ setTimeout(() => {
78
+ timer = undefined;
79
+ }, delay);
80
+ }
81
+ } else {
82
+ // No trailing call. Since the delay has elapsed since the last call,
83
+ // immediately reset the debouncing delay.
84
+ timer = undefined;
85
+ }
86
+ }, delay);
87
+ }
88
+ };
89
+ }
@@ -0,0 +1,35 @@
1
+ import { Context, Effect, Layer } from "effect";
2
+
3
+ export type GenerateIdShape = {
4
+ generateId: () => Effect.Effect<string>;
5
+ };
6
+
7
+ // Declaring a tag for a service that generates random id
8
+ export class GenerateId extends Context.Tag("UploadistaGenerateIdService")<
9
+ GenerateId,
10
+ { readonly generateId: () => Effect.Effect<string> }
11
+ >() {}
12
+
13
+ /**
14
+ * Effect-based ID generation services
15
+ */
16
+ export const GenerateIdService = GenerateId.Service;
17
+
18
+ /**
19
+ * Generates a random UUID using Effect
20
+ * @returns Effect that produces a random UUID string
21
+ */
22
+ export const GenerateIdRandom = GenerateId.of({
23
+ generateId: () => Effect.succeed(crypto.randomUUID()),
24
+ });
25
+
26
+ export const GenerateIdLive = Layer.succeed(GenerateId, GenerateIdRandom);
27
+
28
+ /**
29
+ * Generates a timestamp-based ID using Effect
30
+ * @returns Effect that produces a timestamp-based ID
31
+ */
32
+ export const GenerateIdTimestamp = GenerateId.of({
33
+ generateId: () =>
34
+ Effect.succeed(`${Date.now()}-${Math.random().toString(36).slice(2, 11)}`),
35
+ });