@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
package/README.md ADDED
@@ -0,0 +1,1120 @@
1
+ # @uploadista/core
2
+
3
+ Core engine for the Uploadista platform, providing a powerful and flexible system for file uploads and processing pipelines built with Effect-TS.
4
+
5
+ ## Overview
6
+
7
+ `@uploadista/core` is the foundation of the Uploadista ecosystem, providing:
8
+
9
+ - **Flow Engine**: A DAG-based processing pipeline for transforming files through multiple processing steps
10
+ - **Upload System**: Robust file upload handling with chunked, resumable, and parallel upload strategies
11
+ - **Stream Utilities**: Advanced stream manipulation tools for splitting, combining, and limiting data streams
12
+ - **Error Handling**: Comprehensive error types and error management with Effect-TS integration
13
+ - **Type Safety**: Full TypeScript support with Zod schema validation throughout
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @uploadista/core
19
+ # or
20
+ pnpm add @uploadista/core
21
+ # or
22
+ yarn add @uploadista/core
23
+ ```
24
+
25
+ ## Core Dependencies
26
+
27
+ - **effect**: For functional effect system and dependency injection
28
+ - **zod**: For runtime type validation and schema definition
29
+ - **@uploadista/observability**: For tracing and monitoring
30
+
31
+ ## Architecture Overview
32
+
33
+ ### Effect-TS First
34
+
35
+ `@uploadista/core` is built on Effect-TS, providing:
36
+
37
+ - **Type-safe effects**: All operations return Effect types for composable, type-safe error handling
38
+ - **Dependency injection**: Uses Effect's Context system for managing dependencies
39
+ - **Resource management**: Automatic cleanup and resource disposal
40
+ - **Observability**: Built-in tracing and logging support
41
+
42
+ ### Core Components
43
+
44
+ 1. **Flow Engine** (`/flow`): DAG-based processing pipeline with nodes and edges
45
+ 2. **Upload System** (`/upload`): File upload handling with multiple strategies
46
+ 3. **Streams** (`/streams`): Stream manipulation utilities for data processing
47
+ 4. **Types** (`/types`): Core type definitions and interfaces
48
+ 5. **Errors** (`/errors`): Comprehensive error handling system
49
+ 6. **Utils** (`/utils`): Utility functions for common operations
50
+ 7. **Logger** (`/logger`): Simple logging utilities
51
+ 8. **WebSocket** (`/websocket`): Real-time event streaming
52
+
53
+ ## Module Exports
54
+
55
+ ### Main Entry Point (`.`)
56
+
57
+ ```typescript
58
+ import {
59
+ // Flow Engine
60
+ createFlow,
61
+ createFlowNode,
62
+ FlowServer,
63
+
64
+ // Upload System
65
+ UploadServer,
66
+ createUploadServer,
67
+
68
+ // Error Handling
69
+ UploadistaError,
70
+ ERRORS,
71
+
72
+ // Types
73
+ type Flow,
74
+ type FlowNode,
75
+ type UploadFile,
76
+ type DataStore,
77
+ type KvStore,
78
+ } from "@uploadista/core";
79
+ ```
80
+
81
+ ### Errors Module (`/errors`)
82
+
83
+ ```typescript
84
+ import { UploadistaError, ERRORS } from "@uploadista/core/errors";
85
+
86
+ // Create error from error code
87
+ const error = UploadistaError.fromCode("FLOW_NODE_NOT_FOUND");
88
+
89
+ // Create error with overrides
90
+ const customError = UploadistaError.fromCode("FLOW_NODE_ERROR", {
91
+ body: "Custom error message",
92
+ cause: originalError,
93
+ details: { nodeId: "abc123" }
94
+ });
95
+
96
+ // Convert to Effect
97
+ const errorEffect = error.toEffect<void>();
98
+ ```
99
+
100
+ **Available Error Codes**:
101
+ - Upload errors: `MISSING_OFFSET`, `INVALID_OFFSET`, `ERR_SIZE_EXCEEDED`, `INVALID_LENGTH`
102
+ - Flow errors: `FLOW_NODE_NOT_FOUND`, `FLOW_NODE_ERROR`, `FLOW_CYCLE_ERROR`, `FLOW_JOB_NOT_FOUND`
103
+ - Storage errors: `DATASTORE_NOT_FOUND`, `FILE_NOT_FOUND`, `STORAGE_NOT_AUTHORIZED`
104
+ - Validation errors: `VALIDATION_ERROR`, `FLOW_INPUT_VALIDATION_ERROR`, `FLOW_OUTPUT_VALIDATION_ERROR`
105
+
106
+ ### Types Module (`/types`)
107
+
108
+ Core type definitions for the system:
109
+
110
+ ```typescript
111
+ import type {
112
+ // Storage
113
+ DataStore,
114
+ DataStoreCapabilities,
115
+ DataStoreWriteOptions,
116
+
117
+ // KV Store
118
+ KvStore,
119
+ BaseKvStore,
120
+
121
+ // Upload
122
+ UploadFile,
123
+ InputFile,
124
+ UploadEvent,
125
+
126
+ // Events
127
+ EventEmitter,
128
+ EventBroadcaster,
129
+
130
+ // WebSocket
131
+ WebSocketConnection,
132
+ WebSocketMessage,
133
+
134
+ // Middleware
135
+ Middleware,
136
+ } from "@uploadista/core/types";
137
+ ```
138
+
139
+ ### Flow Module (`/flow`)
140
+
141
+ The Flow Engine provides a powerful DAG-based processing system:
142
+
143
+ ```typescript
144
+ import {
145
+ createFlow,
146
+ createFlowNode,
147
+ createFlowEdge,
148
+ FlowServer,
149
+ NodeType,
150
+ EventType,
151
+ type Flow,
152
+ type FlowNode,
153
+ type FlowEdge,
154
+ type FlowJob,
155
+ } from "@uploadista/core/flow";
156
+ ```
157
+
158
+ #### Creating a Flow
159
+
160
+ ```typescript
161
+ import { createFlow, createFlowNode, NodeType } from "@uploadista/core/flow";
162
+ import { z } from "zod";
163
+ import { Effect } from "effect";
164
+
165
+ // Define schemas
166
+ const inputSchema = z.object({
167
+ id: z.string(),
168
+ stream: z.instanceof(Uint8Array),
169
+ metadata: z.record(z.unknown()),
170
+ });
171
+
172
+ const outputSchema = z.object({
173
+ id: z.string(),
174
+ url: z.string(),
175
+ size: z.number(),
176
+ });
177
+
178
+ // Create nodes
179
+ const inputNode = createFlowNode({
180
+ id: "input-1",
181
+ name: "File Input",
182
+ description: "Accepts incoming files",
183
+ type: NodeType.input,
184
+ inputSchema,
185
+ outputSchema: inputSchema,
186
+ run: ({ data }) => Effect.succeed({ type: "complete", data }),
187
+ });
188
+
189
+ const processNode = createFlowNode({
190
+ id: "process-1",
191
+ name: "Process File",
192
+ description: "Processes the file",
193
+ type: NodeType.process,
194
+ inputSchema,
195
+ outputSchema,
196
+ run: ({ data, storageId, jobId }) =>
197
+ Effect.gen(function* () {
198
+ // Processing logic here
199
+ const result = {
200
+ id: data.id,
201
+ url: `https://storage.example.com/${data.id}`,
202
+ size: data.stream.byteLength,
203
+ };
204
+ return { type: "complete", data: result };
205
+ }),
206
+ });
207
+
208
+ // Create flow
209
+ const flow = yield* createFlow({
210
+ flowId: "my-flow",
211
+ name: "My Processing Flow",
212
+ inputSchema,
213
+ outputSchema,
214
+ nodes: [inputNode, processNode],
215
+ edges: [
216
+ { source: "input-1", target: "process-1" }
217
+ ],
218
+ });
219
+
220
+ // Run the flow
221
+ const result = yield* flow.run({
222
+ inputs: {
223
+ "input-1": { id: "file-1", stream: new Uint8Array([...]), metadata: {} }
224
+ },
225
+ storageId: "storage-1",
226
+ jobId: "job-1",
227
+ clientId: "client-1",
228
+ });
229
+ ```
230
+
231
+ #### Flow Execution Results
232
+
233
+ Flows can complete or pause (for nodes that wait for additional data):
234
+
235
+ ```typescript
236
+ type FlowExecutionResult<TOutput> =
237
+ | { type: "completed"; result: TOutput }
238
+ | {
239
+ type: "paused";
240
+ nodeId: string;
241
+ executionState: {
242
+ executionOrder: string[];
243
+ currentIndex: number;
244
+ inputs: Record<string, unknown>;
245
+ };
246
+ };
247
+ ```
248
+
249
+ #### Node Types
250
+
251
+ - **input**: Entry point for data into the flow
252
+ - **process**: Transforms data
253
+ - **output**: Saves data to storage
254
+ - **conditional**: Routes data based on conditions
255
+ - **multiplex**: Splits data to multiple outputs
256
+ - **merge**: Combines multiple inputs
257
+
258
+ #### Node Features
259
+
260
+ **Conditional Execution**:
261
+ ```typescript
262
+ createFlowNode({
263
+ // ... other config
264
+ condition: {
265
+ field: "mimeType",
266
+ operator: "equals",
267
+ value: "image/jpeg"
268
+ }
269
+ });
270
+ ```
271
+
272
+ **Multi-Input Nodes**:
273
+ ```typescript
274
+ createFlowNode({
275
+ // ... other config
276
+ multiInput: true,
277
+ run: ({ inputs }) => {
278
+ // inputs is Record<string, unknown>
279
+ // Process all inputs together
280
+ }
281
+ });
282
+ ```
283
+
284
+ **Retry Configuration**:
285
+ ```typescript
286
+ createFlowNode({
287
+ // ... other config
288
+ retry: {
289
+ maxRetries: 3,
290
+ retryDelay: 1000,
291
+ exponentialBackoff: true
292
+ }
293
+ });
294
+ ```
295
+
296
+ **Pausable Nodes**:
297
+ ```typescript
298
+ createFlowNode({
299
+ // ... other config
300
+ pausable: true,
301
+ run: ({ data }) => {
302
+ if (needsMoreData) {
303
+ return Effect.succeed({
304
+ type: "waiting",
305
+ partialData: data
306
+ });
307
+ }
308
+ return Effect.succeed({
309
+ type: "complete",
310
+ data: processedData
311
+ });
312
+ }
313
+ });
314
+ ```
315
+
316
+ #### Flow Events
317
+
318
+ Monitor flow execution with event callbacks:
319
+
320
+ ```typescript
321
+ createFlow({
322
+ // ... other config
323
+ onEvent: (event) => Effect.gen(function* () {
324
+ switch (event.eventType) {
325
+ case EventType.FlowStart:
326
+ console.log("Flow started", event.flowId);
327
+ break;
328
+ case EventType.NodeStart:
329
+ console.log("Node started", event.nodeId);
330
+ break;
331
+ case EventType.NodeEnd:
332
+ console.log("Node completed", event.nodeId, event.result);
333
+ break;
334
+ case EventType.NodeError:
335
+ console.error("Node failed", event.nodeId, event.error);
336
+ break;
337
+ case EventType.FlowEnd:
338
+ console.log("Flow completed", event.result);
339
+ break;
340
+ }
341
+ })
342
+ });
343
+ ```
344
+
345
+ #### FlowServer
346
+
347
+ The FlowServer provides a high-level interface for managing flow execution:
348
+
349
+ ```typescript
350
+ import { FlowServer, createFlowServer } from "@uploadista/core/flow";
351
+ import { Effect, Context, Layer } from "effect";
352
+
353
+ // Create a flow provider
354
+ const flowProvider = Layer.succeed(FlowProvider, {
355
+ getFlow: (flowId, clientId) => Effect.succeed(myFlow)
356
+ });
357
+
358
+ // Build the FlowServer with dependencies
359
+ const program = Effect.gen(function* () {
360
+ const flowServer = yield* FlowServer;
361
+
362
+ // Run a flow
363
+ const job = yield* flowServer.runFlow({
364
+ flowId: "my-flow",
365
+ storageId: "storage-1",
366
+ clientId: "client-1",
367
+ inputs: { "input-1": fileData }
368
+ });
369
+
370
+ // Monitor job status
371
+ const status = yield* flowServer.getJobStatus(job.id);
372
+
373
+ // Continue a paused flow
374
+ if (status.status === "paused" && status.pausedAt) {
375
+ yield* flowServer.continueFlow({
376
+ jobId: job.id,
377
+ nodeId: status.pausedAt,
378
+ newData: additionalData,
379
+ clientId: "client-1"
380
+ });
381
+ }
382
+ });
383
+
384
+ // Run with dependencies
385
+ Effect.runPromise(
386
+ program.pipe(
387
+ Effect.provide(flowProvider),
388
+ Effect.provide(uploadFileKvStore),
389
+ Effect.provide(flowJobKvStore),
390
+ Effect.provide(eventEmitter)
391
+ )
392
+ );
393
+ ```
394
+
395
+ ### Upload Module (`/upload`)
396
+
397
+ Handles file uploads with multiple strategies:
398
+
399
+ ```typescript
400
+ import {
401
+ UploadServer,
402
+ createUploadServer,
403
+ uploadServer,
404
+ type UploadServerShape,
405
+ } from "@uploadista/core/upload";
406
+ ```
407
+
408
+ #### UploadServer
409
+
410
+ ```typescript
411
+ import { UploadServer } from "@uploadista/core/upload";
412
+ import { Effect } from "effect";
413
+
414
+ const program = Effect.gen(function* () {
415
+ const uploadServer = yield* UploadServer;
416
+
417
+ // Create an upload
418
+ const upload = yield* uploadServer.createUpload(
419
+ {
420
+ filename: "image.jpg",
421
+ size: 1024000,
422
+ mimeType: "image/jpeg",
423
+ storageId: "storage-1",
424
+ metadata: { description: "Profile photo" }
425
+ },
426
+ "client-1"
427
+ );
428
+
429
+ // Upload chunks
430
+ const completed = yield* uploadServer.uploadChunk(
431
+ upload.id,
432
+ "client-1",
433
+ readableStream
434
+ );
435
+
436
+ // Upload directly with stream
437
+ const file = yield* uploadServer.upload(
438
+ inputFile,
439
+ "client-1",
440
+ readableStream
441
+ );
442
+
443
+ // Upload from URL
444
+ const fromUrl = yield* uploadServer.uploadFromUrl(
445
+ inputFile,
446
+ "client-1",
447
+ "https://example.com/image.jpg"
448
+ );
449
+
450
+ // Read file data
451
+ const data = yield* uploadServer.read(upload.id, "client-1");
452
+
453
+ // Delete file
454
+ yield* uploadServer.delete(upload.id, "client-1");
455
+
456
+ // Get storage capabilities
457
+ const capabilities = yield* uploadServer.getCapabilities(
458
+ "storage-1",
459
+ "client-1"
460
+ );
461
+ });
462
+ ```
463
+
464
+ #### Upload Strategies
465
+
466
+ DataStores can support different upload strategies:
467
+
468
+ - **Single**: Upload file in one request
469
+ - **Parallel**: Upload chunks in parallel (S3, GCS, Azure)
470
+ - **Resumable**: Resume interrupted uploads
471
+ - **Transactional**: Atomic commit after all chunks
472
+
473
+ Check capabilities:
474
+ ```typescript
475
+ const capabilities = yield* uploadServer.getCapabilities(storageId, clientId);
476
+
477
+ if (capabilities.supportsParallelUploads) {
478
+ // Use parallel upload strategy
479
+ console.log("Max parts:", capabilities.maxParts);
480
+ console.log("Optimal chunk size:", capabilities.optimalChunkSize);
481
+ }
482
+ ```
483
+
484
+ ### Streams Module (`/streams/*`)
485
+
486
+ Advanced stream manipulation utilities:
487
+
488
+ #### StreamLimiter
489
+
490
+ Limit stream data rate or total size:
491
+
492
+ ```typescript
493
+ import { StreamLimiter } from "@uploadista/core/streams/stream-limiter";
494
+
495
+ const limiter = new StreamLimiter({
496
+ maxSize: 100 * 1024 * 1024, // 100MB max
497
+ onProgress: (bytesRead) => {
498
+ console.log(`Progress: ${bytesRead} bytes`);
499
+ }
500
+ });
501
+
502
+ const limited = inputStream.pipeThrough(limiter.transform);
503
+ ```
504
+
505
+ ### Utils Module (`/utils/*`)
506
+
507
+ Utility functions for common operations:
508
+
509
+ #### Debounce
510
+
511
+ ```typescript
512
+ import { debounce } from "@uploadista/core/utils/debounce";
513
+
514
+ const debouncedFn = debounce(
515
+ (value: string) => console.log("Search:", value),
516
+ 300,
517
+ { leading: false, trailing: true }
518
+ );
519
+
520
+ // Only logs once after 300ms of no calls
521
+ debouncedFn("a");
522
+ debouncedFn("ab");
523
+ debouncedFn("abc"); // Logs "Search: abc" after 300ms
524
+ ```
525
+
526
+ #### Throttle
527
+
528
+ ```typescript
529
+ import { throttle } from "@uploadista/core/utils/throttle";
530
+
531
+ const throttledFn = throttle(
532
+ (value: number) => console.log("Value:", value),
533
+ 1000
534
+ );
535
+
536
+ // Logs immediately, then at most once per second
537
+ throttledFn(1); // Logs immediately
538
+ throttledFn(2); // Ignored (within 1s)
539
+ throttledFn(3); // Ignored (within 1s)
540
+ // After 1s
541
+ throttledFn(4); // Logs "Value: 4"
542
+ ```
543
+
544
+ #### Once
545
+
546
+ Ensure a function runs only once:
547
+
548
+ ```typescript
549
+ import { once } from "@uploadista/core/utils/once";
550
+
551
+ const initialize = once(() => {
552
+ console.log("Initializing...");
553
+ // Expensive setup
554
+ });
555
+
556
+ initialize(); // Logs "Initializing..."
557
+ initialize(); // Does nothing
558
+ initialize(); // Does nothing
559
+ ```
560
+
561
+ #### Generate ID
562
+
563
+ Generate unique identifiers:
564
+
565
+ ```typescript
566
+ import { GenerateId } from "@uploadista/core/utils/generate-id";
567
+ import { Effect } from "effect";
568
+
569
+ const program = Effect.gen(function* () {
570
+ const generateId = yield* GenerateId;
571
+ const id = yield* generateId.generate();
572
+ console.log("ID:", id); // e.g., "abc123def456"
573
+ });
574
+ ```
575
+
576
+ ### Logger Module (`/logger/*`)
577
+
578
+ Simple logging utilities:
579
+
580
+ ```typescript
581
+ import { createLogger } from "@uploadista/core/logger/logger";
582
+
583
+ const logger = createLogger(true); // enabled
584
+ logger.log("Processing file..."); // Logs to console
585
+
586
+ const disabledLogger = createLogger(false); // disabled
587
+ disabledLogger.log("This won't log"); // Silent
588
+ ```
589
+
590
+ ### WebSocket Module (`/websocket`)
591
+
592
+ Real-time event streaming:
593
+
594
+ ```typescript
595
+ import type {
596
+ WebSocketConnection,
597
+ WebSocketMessage,
598
+ } from "@uploadista/core/websocket";
599
+
600
+ // Platform-agnostic WebSocket interface
601
+ const connection: WebSocketConnection = {
602
+ id: "conn-123",
603
+ readyState: 1, // OPEN
604
+ send: (data: string) => ws.send(data),
605
+ close: (code?: number, reason?: string) => ws.close(code, reason),
606
+ };
607
+
608
+ // Subscribe to events
609
+ yield* uploadServer.subscribeToUploadEvents(uploadId, connection);
610
+ yield* flowServer.subscribeToFlowEvents(jobId, connection);
611
+
612
+ // Message types
613
+ type WebSocketMessage =
614
+ | { type: "upload_event"; payload: UploadEvent }
615
+ | { type: "flow_event"; payload: FlowEvent }
616
+ | { type: "subscribed"; payload: { eventKey: string } }
617
+ | { type: "error"; message: string }
618
+ | { type: "ping" | "pong"; timestamp?: string };
619
+ ```
620
+
621
+ ## Plugin System
622
+
623
+ `@uploadista/core` provides plugin interfaces for extending functionality:
624
+
625
+ ### Image Plugin
626
+
627
+ Image processing operations (resize, optimize):
628
+
629
+ ```typescript
630
+ import { ImagePlugin } from "@uploadista/core/flow";
631
+ import { Effect, Layer } from "effect";
632
+
633
+ // Implement the plugin
634
+ const imagePlugin = Layer.succeed(ImagePlugin, {
635
+ resize: (input, options) => Effect.gen(function* () {
636
+ // Resize implementation
637
+ return resizedImage;
638
+ }),
639
+ optimize: (input, options) => Effect.gen(function* () {
640
+ // Optimize implementation
641
+ return optimizedImage;
642
+ })
643
+ });
644
+
645
+ // Use in flows
646
+ const resizeNode = createFlowNode({
647
+ // ... config
648
+ run: ({ data }) => Effect.gen(function* () {
649
+ const plugin = yield* ImagePlugin;
650
+ const resized = yield* plugin.resize(data.stream, {
651
+ width: 800,
652
+ height: 600,
653
+ fit: "cover"
654
+ });
655
+ return { type: "complete", data: { ...data, stream: resized } };
656
+ })
657
+ });
658
+ ```
659
+
660
+ ### Image AI Plugin
661
+
662
+ AI-powered image operations:
663
+
664
+ ```typescript
665
+ import { ImageAIPlugin } from "@uploadista/core/flow";
666
+
667
+ // Implement the plugin
668
+ const imageAIPlugin = Layer.succeed(ImageAIPlugin, {
669
+ describeImage: (input, options) => Effect.gen(function* () {
670
+ // AI description implementation
671
+ return "A beautiful sunset over mountains";
672
+ }),
673
+ removeBackground: (input, options) => Effect.gen(function* () {
674
+ // Background removal implementation
675
+ return imageWithoutBackground;
676
+ })
677
+ });
678
+ ```
679
+
680
+ ### Zip Plugin
681
+
682
+ Create ZIP archives from multiple files:
683
+
684
+ ```typescript
685
+ import { ZipPlugin } from "@uploadista/core/flow";
686
+
687
+ const zipPlugin = Layer.succeed(ZipPlugin, {
688
+ zip: (inputs, options) => Effect.gen(function* () {
689
+ // ZIP creation implementation
690
+ return zipFileData;
691
+ })
692
+ });
693
+
694
+ // Use in a merge node
695
+ const zipNode = createFlowNode({
696
+ id: "zip-1",
697
+ type: NodeType.merge,
698
+ multiInput: true,
699
+ run: ({ inputs }) => Effect.gen(function* () {
700
+ const plugin = yield* ZipPlugin;
701
+
702
+ const zipInputs = Object.entries(inputs).map(([id, data]) => ({
703
+ id,
704
+ data: data.stream,
705
+ metadata: data.metadata
706
+ }));
707
+
708
+ const zipData = yield* plugin.zip(zipInputs, {
709
+ zipName: "archive.zip",
710
+ includeMetadata: true
711
+ });
712
+
713
+ return {
714
+ type: "complete",
715
+ data: { stream: zipData, filename: "archive.zip" }
716
+ };
717
+ })
718
+ });
719
+ ```
720
+
721
+ ### Credential Provider
722
+
723
+ Secure credential management for plugins:
724
+
725
+ ```typescript
726
+ import { CredentialProvider } from "@uploadista/core/flow";
727
+
728
+ const credentialProvider = Layer.succeed(CredentialProvider, {
729
+ getCredential: (credentialId, clientId) => Effect.gen(function* () {
730
+ // Fetch credential securely
731
+ return {
732
+ id: credentialId,
733
+ value: "api-key-value",
734
+ metadata: {}
735
+ };
736
+ })
737
+ });
738
+ ```
739
+
740
+ ## Data Stores
741
+
742
+ `@uploadista/core` defines the `DataStore` interface for storage backends. Implementations are provided in separate packages:
743
+
744
+ - `@uploadista/data-stores-s3` - AWS S3
745
+ - `@uploadista/data-stores-azure` - Azure Blob Storage
746
+ - `@uploadista/data-stores-gcs` - Google Cloud Storage
747
+ - `@uploadista/data-stores-filesystem` - Local filesystem
748
+
749
+ ### DataStore Interface
750
+
751
+ ```typescript
752
+ type DataStore<TData> = {
753
+ readonly bucket?: string;
754
+ readonly path?: string;
755
+ readonly create: (file: TData) => Effect.Effect<TData, UploadistaError>;
756
+ readonly remove: (file_id: string) => Effect.Effect<void, UploadistaError>;
757
+ readonly read: (file_id: string) => Effect.Effect<Uint8Array, UploadistaError>;
758
+ readonly write: (
759
+ options: DataStoreWriteOptions,
760
+ dependencies: { onProgress?: (chunkSize: number) => void }
761
+ ) => Effect.Effect<number, UploadistaError>;
762
+ readonly deleteExpired?: Effect.Effect<number, UploadistaError>;
763
+ readonly getCapabilities: () => DataStoreCapabilities;
764
+ readonly validateUploadStrategy: (
765
+ strategy: UploadStrategy
766
+ ) => Effect.Effect<boolean, never>;
767
+ };
768
+ ```
769
+
770
+ ### Using DataStores
771
+
772
+ ```typescript
773
+ import { createDataStoreLayer } from "@uploadista/core/types";
774
+ import { S3DataStore } from "@uploadista/data-stores-s3";
775
+
776
+ // Single store
777
+ const dataStoreLayer = await createDataStoreLayer(s3Store);
778
+
779
+ // Multiple stores with routing
780
+ const dataStoreLayer = await createDataStoreLayer({
781
+ stores: {
782
+ "s3-prod": s3Store,
783
+ "azure-backup": azureStore,
784
+ },
785
+ default: "s3-prod"
786
+ });
787
+ ```
788
+
789
+ ## KV Stores
790
+
791
+ `@uploadista/core` defines the `KvStore` interface for metadata storage. Implementations are provided in separate packages:
792
+
793
+ - `@uploadista/kv-stores-cloudflare-kv` - Cloudflare KV
794
+ - `@uploadista/kv-stores-cloudflare-do` - Cloudflare Durable Objects
795
+ - `@uploadista/kv-stores-redis` - Redis
796
+ - `@uploadista/kv-stores-ioredis` - IORedis
797
+ - `@uploadista/kv-stores-memory` - In-memory (for testing)
798
+ - `@uploadista/kv-stores-filesystem` - File-based
799
+
800
+ ### KvStore Interface
801
+
802
+ ```typescript
803
+ type KvStore<TData> = {
804
+ readonly get: (key: string) => Effect.Effect<TData, UploadistaError>;
805
+ readonly set: (key: string, value: TData) => Effect.Effect<void, UploadistaError>;
806
+ readonly delete: (key: string) => Effect.Effect<void, UploadistaError>;
807
+ readonly list?: () => Effect.Effect<Array<string>, UploadistaError>;
808
+ };
809
+ ```
810
+
811
+ ### Creating Typed KV Stores
812
+
813
+ ```typescript
814
+ import { TypedKvStore, jsonSerializer } from "@uploadista/core/types";
815
+
816
+ const uploadStore = new TypedKvStore<UploadFile>(
817
+ baseKvStore,
818
+ "uploadista:upload:",
819
+ jsonSerializer.serialize,
820
+ jsonSerializer.deserialize
821
+ );
822
+
823
+ // Use with Effect
824
+ const file = yield* uploadStore.get("file-123");
825
+ yield* uploadStore.set("file-123", updatedFile);
826
+ yield* uploadStore.delete("file-123");
827
+ ```
828
+
829
+ ## Effect-TS Patterns
830
+
831
+ ### Dependency Injection
832
+
833
+ Use Effect's Context system to inject dependencies:
834
+
835
+ ```typescript
836
+ import { Effect, Context, Layer } from "effect";
837
+
838
+ // Define your service
839
+ class MyService extends Context.Tag("MyService")<
840
+ MyService,
841
+ { doSomething: () => Effect.Effect<string, never> }
842
+ >() {}
843
+
844
+ // Create implementation
845
+ const myServiceLive = Layer.succeed(MyService, {
846
+ doSomething: () => Effect.succeed("done")
847
+ });
848
+
849
+ // Use in program
850
+ const program = Effect.gen(function* () {
851
+ const service = yield* MyService;
852
+ const result = yield* service.doSomething();
853
+ console.log(result);
854
+ });
855
+
856
+ // Run with dependencies
857
+ Effect.runPromise(program.pipe(Effect.provide(myServiceLive)));
858
+ ```
859
+
860
+ ### Error Handling
861
+
862
+ All operations return Effects that can fail with `UploadistaError`:
863
+
864
+ ```typescript
865
+ import { Effect } from "effect";
866
+
867
+ const program = Effect.gen(function* () {
868
+ const uploadServer = yield* UploadServer;
869
+
870
+ // Try to upload
871
+ const result = yield* uploadServer.upload(file, clientId, stream).pipe(
872
+ Effect.catchTag("UploadistaError", (error) => {
873
+ console.error("Upload failed:", error.body);
874
+ // Handle error or provide fallback
875
+ return Effect.succeed(fallbackFile);
876
+ })
877
+ );
878
+
879
+ return result;
880
+ });
881
+ ```
882
+
883
+ ### Combining Effects
884
+
885
+ Chain multiple operations:
886
+
887
+ ```typescript
888
+ const program = Effect.gen(function* () {
889
+ const uploadServer = yield* UploadServer;
890
+ const flowServer = yield* FlowServer;
891
+
892
+ // Upload file
893
+ const file = yield* uploadServer.upload(inputFile, clientId, stream);
894
+
895
+ // Process with flow
896
+ const job = yield* flowServer.runFlow({
897
+ flowId: "process-image",
898
+ storageId: file.storage.id,
899
+ clientId,
900
+ inputs: { "input-1": file }
901
+ });
902
+
903
+ // Wait for completion (in real app, use WebSocket events)
904
+ let status = yield* flowServer.getJobStatus(job.id);
905
+ while (status.status === "running") {
906
+ yield* Effect.sleep(1000);
907
+ status = yield* flowServer.getJobStatus(job.id);
908
+ }
909
+
910
+ if (status.status === "completed") {
911
+ return status.result;
912
+ } else {
913
+ return yield* Effect.fail(
914
+ UploadistaError.fromCode("FLOW_JOB_ERROR", {
915
+ body: status.error || "Flow failed"
916
+ })
917
+ );
918
+ }
919
+ });
920
+ ```
921
+
922
+ ## Testing
923
+
924
+ The core package includes unit tests using Vitest:
925
+
926
+ ```bash
927
+ # Run tests
928
+ pnpm test
929
+
930
+ # Run tests in watch mode
931
+ pnpm test:watch
932
+
933
+ # Run tests once
934
+ pnpm test:run
935
+ ```
936
+
937
+ ### Testing with Effect
938
+
939
+ Use Effect's testing utilities:
940
+
941
+ ```typescript
942
+ import { Effect, Layer } from "effect";
943
+ import { describe, it, expect } from "vitest";
944
+
945
+ describe("MyFlow", () => {
946
+ it("should process file", async () => {
947
+ const testLayer = Layer.mergeAll(
948
+ mockDataStore,
949
+ mockKvStore,
950
+ mockEventEmitter
951
+ );
952
+
953
+ const result = await Effect.runPromise(
954
+ program.pipe(Effect.provide(testLayer))
955
+ );
956
+
957
+ expect(result).toBeDefined();
958
+ });
959
+ });
960
+ ```
961
+
962
+ ## Type Safety
963
+
964
+ Full TypeScript support with strict typing:
965
+
966
+ ```typescript
967
+ import type { Flow, FlowNode, FlowEdge } from "@uploadista/core/flow";
968
+ import type { UploadFile, DataStore } from "@uploadista/core/types";
969
+ import { z } from "zod";
970
+
971
+ // Define schemas
972
+ const myInputSchema = z.object({
973
+ file: z.instanceof(Uint8Array),
974
+ metadata: z.record(z.string()),
975
+ });
976
+
977
+ type MyInput = z.infer<typeof myInputSchema>;
978
+
979
+ // TypeScript ensures type safety
980
+ const node = createFlowNode<MyInput, ProcessedOutput>({
981
+ inputSchema: myInputSchema,
982
+ outputSchema: outputSchema,
983
+ run: ({ data }) => {
984
+ // data is typed as MyInput
985
+ return Effect.succeed({
986
+ type: "complete",
987
+ data: processedOutput // Must match ProcessedOutput type
988
+ });
989
+ }
990
+ });
991
+ ```
992
+
993
+ ## Best Practices
994
+
995
+ ### 1. Use Effect-TS Patterns
996
+
997
+ Always use Effect for async operations:
998
+
999
+ ```typescript
1000
+ // Good
1001
+ const upload = yield* uploadServer.upload(file, clientId, stream);
1002
+
1003
+ // Avoid
1004
+ const upload = await Effect.runPromise(
1005
+ uploadServer.upload(file, clientId, stream)
1006
+ );
1007
+ ```
1008
+
1009
+ ### 2. Handle Errors Properly
1010
+
1011
+ Use Effect's error handling:
1012
+
1013
+ ```typescript
1014
+ const program = uploadServer.upload(file, clientId, stream).pipe(
1015
+ Effect.retry({ times: 3 }),
1016
+ Effect.timeout(30000),
1017
+ Effect.catchAll((error) => {
1018
+ // Log and handle
1019
+ return fallbackEffect;
1020
+ })
1021
+ );
1022
+ ```
1023
+
1024
+ ### 3. Validate Inputs
1025
+
1026
+ Always validate inputs with Zod schemas:
1027
+
1028
+ ```typescript
1029
+ const inputSchema = z.object({
1030
+ filename: z.string().min(1),
1031
+ size: z.number().positive(),
1032
+ mimeType: z.string(),
1033
+ });
1034
+
1035
+ // Schema validation is automatic in Flow nodes
1036
+ ```
1037
+
1038
+ ### 4. Use Typed Stores
1039
+
1040
+ Create typed wrappers for KV stores:
1041
+
1042
+ ```typescript
1043
+ const uploadStore = new TypedKvStore<UploadFile>(
1044
+ baseStore,
1045
+ "uploads:",
1046
+ jsonSerializer.serialize,
1047
+ jsonSerializer.deserialize
1048
+ );
1049
+ ```
1050
+
1051
+ ### 5. Manage Resources
1052
+
1053
+ Use Effect's resource management:
1054
+
1055
+ ```typescript
1056
+ const program = Effect.acquireUseRelease(
1057
+ // Acquire
1058
+ Effect.sync(() => openFile(path)),
1059
+ // Use
1060
+ (file) => processFile(file),
1061
+ // Release
1062
+ (file) => Effect.sync(() => file.close())
1063
+ );
1064
+ ```
1065
+
1066
+ ## Related Packages
1067
+
1068
+ ### Data Stores
1069
+ - `@uploadista/data-stores-s3` - AWS S3 storage
1070
+ - `@uploadista/data-stores-azure` - Azure Blob Storage
1071
+ - `@uploadista/data-stores-gcs` - Google Cloud Storage
1072
+ - `@uploadista/data-stores-filesystem` - Local filesystem storage
1073
+
1074
+ ### KV Stores
1075
+ - `@uploadista/kv-stores-cloudflare-kv` - Cloudflare KV
1076
+ - `@uploadista/kv-stores-cloudflare-do` - Cloudflare Durable Objects
1077
+ - `@uploadista/kv-stores-redis` - Redis
1078
+ - `@uploadista/kv-stores-ioredis` - IORedis
1079
+ - `@uploadista/kv-stores-memory` - In-memory store
1080
+ - `@uploadista/kv-stores-filesystem` - File-based store
1081
+
1082
+ ### Flow Nodes
1083
+ - `@uploadista/flow-input-nodes` - File input nodes
1084
+ - `@uploadista/flow-output-nodes` - Storage output nodes
1085
+ - `@uploadista/flow-image-nodes` - Image processing nodes
1086
+ - `@uploadista/flow-utility-nodes` - Utility nodes (conditional, merge, etc.)
1087
+
1088
+ ### Client & Server
1089
+ - `@uploadista/client` - Browser upload client
1090
+ - `@uploadista/server` - Server-side utilities
1091
+
1092
+ ## Development
1093
+
1094
+ ### Build
1095
+
1096
+ ```bash
1097
+ pnpm build
1098
+ ```
1099
+
1100
+ ### Lint and Format
1101
+
1102
+ ```bash
1103
+ pnpm lint
1104
+ pnpm format
1105
+ pnpm check
1106
+ ```
1107
+
1108
+ ### Type Check
1109
+
1110
+ ```bash
1111
+ pnpm typecheck
1112
+ ```
1113
+
1114
+ ## License
1115
+
1116
+ See the main repository for license information.
1117
+
1118
+ ## Contributing
1119
+
1120
+ See the main repository for contribution guidelines.