@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,48 @@
1
+ import { Stream } from "effect";
2
+ import { UploadistaError } from "../errors";
3
+
4
+ /**
5
+ * Converts a ReadableStream to an Effect Stream.
6
+ *
7
+ * This utility function wraps a ReadableStream in an Effect Stream, providing
8
+ * proper error handling and integration with the Effect ecosystem. It's used
9
+ * throughout the upload system to convert raw streams into Effect-compatible
10
+ * streams for processing.
11
+ *
12
+ * The function handles:
13
+ * - Stream conversion with proper error mapping
14
+ * - UploadistaError creation for stream errors
15
+ * - Integration with Effect Stream processing
16
+ *
17
+ * @param data - The ReadableStream to convert
18
+ * @returns Effect Stream that can be processed with Effect operations
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * // Convert a file stream to Effect Stream
23
+ * const fileStream = new ReadableStream(...);
24
+ * const effectStream = convertToStream(fileStream);
25
+ *
26
+ * // Process with Effect operations
27
+ * const processedStream = effectStream.pipe(
28
+ * Stream.map((chunk) => processChunk(chunk)),
29
+ * Stream.filter((chunk) => chunk.length > 0)
30
+ * );
31
+ *
32
+ * // Run the stream
33
+ * await Stream.runForEach(processedStream, (chunk) =>
34
+ * Effect.logInfo(`Processed chunk: ${chunk.length} bytes`)
35
+ * );
36
+ * ```
37
+ */
38
+ export function convertToStream<T>(data: ReadableStream<T>) {
39
+ return Stream.fromReadableStream(
40
+ () => data,
41
+ (error) =>
42
+ new UploadistaError({
43
+ code: "UNKNOWN_ERROR",
44
+ status: 500,
45
+ body: String(error),
46
+ })
47
+ );
48
+ }
@@ -0,0 +1,214 @@
1
+ import { Effect, Metric, MetricBoundaries } from "effect";
2
+ import {
3
+ type EventEmitter,
4
+ type InputFile,
5
+ type KvStore,
6
+ type UploadEvent,
7
+ UploadEventType,
8
+ type UploadFile,
9
+ type UploadFileDataStoresShape,
10
+ } from "../types";
11
+ import type { GenerateIdShape } from "../utils/generate-id";
12
+
13
+ /**
14
+ * Creates a new upload and initializes it in the storage system.
15
+ *
16
+ * This function handles the initial upload creation process including:
17
+ * - Generating a unique upload ID
18
+ * - Routing to appropriate data store based on storage ID
19
+ * - Creating the upload record in the data store
20
+ * - Storing upload metadata in KV store
21
+ * - Emitting upload started events
22
+ * - Parsing and validating metadata
23
+ *
24
+ * The function includes comprehensive observability with:
25
+ * - Effect tracing spans for performance monitoring
26
+ * - Metrics tracking for upload creation, file sizes, and success rates
27
+ * - Structured logging for debugging and monitoring
28
+ * - Error handling with proper UploadistaError types
29
+ *
30
+ * @param inputFile - Input file configuration including storage, size, type, etc.
31
+ * @param clientId - Client identifier (null for anonymous uploads)
32
+ * @param dataStoreService - Service for routing to appropriate data stores
33
+ * @param kvStore - KV store for upload metadata persistence
34
+ * @param eventEmitter - Event emitter for upload lifecycle events
35
+ * @param generateId - ID generator for creating unique upload identifiers
36
+ * @returns Effect that yields the created UploadFile
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * // Create a new upload
41
+ * const inputFile: InputFile = {
42
+ * storageId: "s3-production",
43
+ * size: 1024000,
44
+ * type: "image/jpeg",
45
+ * fileName: "photo.jpg",
46
+ * metadata: JSON.stringify({ category: "photos" })
47
+ * };
48
+ *
49
+ * const createEffect = createUpload(
50
+ * inputFile,
51
+ * "client-123",
52
+ * {
53
+ * dataStoreService,
54
+ * kvStore,
55
+ * eventEmitter,
56
+ * generateId
57
+ * }
58
+ * );
59
+ *
60
+ * // Run with dependencies
61
+ * const upload = await Effect.runPromise(
62
+ * createEffect.pipe(
63
+ * Effect.provide(dataStoreLayer),
64
+ * Effect.provide(kvStoreLayer),
65
+ * Effect.provide(eventEmitterLayer),
66
+ * Effect.provide(generateIdLayer)
67
+ * )
68
+ * );
69
+ * ```
70
+ */
71
+ export const createUpload = (
72
+ inputFile: InputFile,
73
+ clientId: string | null,
74
+ {
75
+ dataStoreService,
76
+ kvStore,
77
+ eventEmitter,
78
+ generateId,
79
+ }: {
80
+ dataStoreService: UploadFileDataStoresShape;
81
+ kvStore: KvStore<UploadFile>;
82
+ eventEmitter: EventEmitter<UploadEvent>;
83
+ generateId: GenerateIdShape;
84
+ }
85
+ ) =>
86
+ Effect.gen(function* () {
87
+ // Get datastore using Effect
88
+ const dataStore = yield* dataStoreService.getDataStore(
89
+ inputFile.storageId,
90
+ clientId
91
+ );
92
+
93
+ const id = yield* generateId.generateId();
94
+ const { size, type, fileName, lastModified, metadata, flow } = inputFile;
95
+
96
+ let parsedMetadata: Record<string, string> = {};
97
+ if (metadata) {
98
+ try {
99
+ parsedMetadata = JSON.parse(metadata) as Record<string, string>;
100
+ } catch {
101
+ parsedMetadata = {};
102
+ }
103
+ }
104
+
105
+ const metadataObject: Record<string, string> = {
106
+ ...parsedMetadata,
107
+ type,
108
+ fileName: fileName ?? "",
109
+ };
110
+ if (lastModified) {
111
+ metadataObject.lastModified = lastModified.toString();
112
+ }
113
+
114
+ const file: UploadFile = {
115
+ id,
116
+ size,
117
+ metadata: metadataObject,
118
+ offset: 0,
119
+ creationDate: new Date().toISOString(),
120
+ storage: {
121
+ id: inputFile.storageId,
122
+
123
+ type,
124
+ path: "",
125
+ bucket: dataStore.bucket,
126
+ },
127
+ flow,
128
+ };
129
+
130
+ // Create file using Effect
131
+ const fileCreated = yield* dataStore.create(file);
132
+
133
+ // Store in KV store
134
+ yield* kvStore.set(id, fileCreated);
135
+
136
+ // Emit event
137
+ yield* eventEmitter.emit(id, {
138
+ type: UploadEventType.UPLOAD_STARTED,
139
+ data: fileCreated,
140
+ flow: fileCreated.flow,
141
+ });
142
+
143
+ return fileCreated;
144
+ }).pipe(
145
+ // Add tracing span for the entire create operation
146
+ Effect.withSpan("upload-create", {
147
+ attributes: {
148
+ "upload.file_name": inputFile.fileName ?? "unknown",
149
+ "upload.file_size": inputFile.size?.toString() ?? "0",
150
+ "upload.storage_id": inputFile.storageId,
151
+ "upload.mime_type": inputFile.type,
152
+ "upload.has_flow": inputFile.flow ? "true" : "false",
153
+ },
154
+ }),
155
+ // Track upload creation metrics
156
+ Effect.tap((file) =>
157
+ Effect.gen(function* () {
158
+ // Increment upload created counter
159
+ yield* Metric.increment(
160
+ Metric.counter("upload_created_total", {
161
+ description: "Total number of uploads created",
162
+ })
163
+ );
164
+
165
+ // Record file size
166
+ if (file.size) {
167
+ const fileSizeHistogram = Metric.histogram(
168
+ "upload_file_size_bytes",
169
+ MetricBoundaries.exponential({
170
+ start: 1024,
171
+ factor: 2,
172
+ count: 25,
173
+ })
174
+ );
175
+ yield* Metric.update(fileSizeHistogram, file.size);
176
+ }
177
+
178
+ // Track active uploads gauge
179
+ const activeUploadsGauge = Metric.gauge("active_uploads");
180
+ yield* Metric.increment(activeUploadsGauge);
181
+ })
182
+ ),
183
+ // Add structured logging
184
+ Effect.tap((file) =>
185
+ Effect.logInfo("Upload created").pipe(
186
+ Effect.annotateLogs({
187
+ "upload.id": file.id,
188
+ "upload.file_name": inputFile.fileName ?? "unknown",
189
+ "upload.file_size": inputFile.size?.toString() ?? "0",
190
+ "upload.storage_id": inputFile.storageId,
191
+ })
192
+ )
193
+ ),
194
+ // Handle errors with logging and metrics
195
+ Effect.tapError((error) =>
196
+ Effect.gen(function* () {
197
+ // Log error
198
+ yield* Effect.logError("Upload creation failed").pipe(
199
+ Effect.annotateLogs({
200
+ "upload.file_name": inputFile.fileName ?? "unknown",
201
+ "upload.storage_id": inputFile.storageId,
202
+ error: String(error),
203
+ })
204
+ );
205
+
206
+ // Track failed upload metric
207
+ yield* Metric.increment(
208
+ Metric.counter("upload_failed_total", {
209
+ description: "Total number of uploads that failed",
210
+ })
211
+ );
212
+ })
213
+ )
214
+ );
@@ -0,0 +1,3 @@
1
+ export * from "./mime";
2
+ export * from "./upload-server";
3
+ export * from "./upload-strategy-negotiator";
@@ -0,0 +1,436 @@
1
+ /**
2
+ * Helper to check if buffer matches a byte pattern at given offset
3
+ */
4
+ function checkBytes(
5
+ buffer: Uint8Array,
6
+ pattern: number[],
7
+ offset = 0,
8
+ ): boolean {
9
+ if (buffer.length < offset + pattern.length) return false;
10
+ return pattern.every((byte, i) => buffer[offset + i] === byte);
11
+ }
12
+
13
+ /**
14
+ * Helper to check if buffer matches a string pattern at given offset
15
+ */
16
+ function checkString(
17
+ buffer: Uint8Array,
18
+ str: string,
19
+ offset = 0,
20
+ ): boolean {
21
+ if (buffer.length < offset + str.length) return false;
22
+ for (let i = 0; i < str.length; i++) {
23
+ if (buffer[offset + i] !== str.charCodeAt(i)) return false;
24
+ }
25
+ return true;
26
+ }
27
+
28
+ /**
29
+ * Detect MIME type from buffer using magic bytes (file signatures).
30
+ * Supports a wide range of common file types including images, videos, audio, documents, and archives.
31
+ *
32
+ * @param buffer - File content as Uint8Array
33
+ * @param filename - Optional filename for extension-based fallback
34
+ * @returns Detected MIME type or "application/octet-stream" if unknown
35
+ */
36
+ export const detectMimeType = (
37
+ buffer: Uint8Array,
38
+ filename?: string,
39
+ ): string => {
40
+ if (buffer.length === 0) {
41
+ return "application/octet-stream";
42
+ }
43
+
44
+ // ===== IMAGES =====
45
+
46
+ // PNG: 89 50 4E 47 0D 0A 1A 0A
47
+ if (checkBytes(buffer, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) {
48
+ return "image/png";
49
+ }
50
+
51
+ // JPEG: FF D8 FF
52
+ if (checkBytes(buffer, [0xff, 0xd8, 0xff])) {
53
+ return "image/jpeg";
54
+ }
55
+
56
+ // GIF87a or GIF89a
57
+ if (checkString(buffer, "GIF87a") || checkString(buffer, "GIF89a")) {
58
+ return "image/gif";
59
+ }
60
+
61
+ // WebP: RIFF....WEBP
62
+ if (
63
+ checkBytes(buffer, [0x52, 0x49, 0x46, 0x46]) &&
64
+ buffer.length >= 12 &&
65
+ checkString(buffer, "WEBP", 8)
66
+ ) {
67
+ return "image/webp";
68
+ }
69
+
70
+ // AVIF: ....ftypavif or ....ftypavis
71
+ if (
72
+ buffer.length >= 12 &&
73
+ checkBytes(buffer, [0x00, 0x00, 0x00], 0) &&
74
+ checkString(buffer, "ftyp", 4) &&
75
+ (checkString(buffer, "avif", 8) || checkString(buffer, "avis", 8))
76
+ ) {
77
+ return "image/avif";
78
+ }
79
+
80
+ // HEIC/HEIF: ....ftypheic or ....ftypheif or ....ftypmif1
81
+ if (
82
+ buffer.length >= 12 &&
83
+ checkString(buffer, "ftyp", 4) &&
84
+ (checkString(buffer, "heic", 8) ||
85
+ checkString(buffer, "heif", 8) ||
86
+ checkString(buffer, "mif1", 8))
87
+ ) {
88
+ return "image/heic";
89
+ }
90
+
91
+ // BMP: 42 4D
92
+ if (checkBytes(buffer, [0x42, 0x4d])) {
93
+ return "image/bmp";
94
+ }
95
+
96
+ // TIFF (little-endian): 49 49 2A 00
97
+ if (checkBytes(buffer, [0x49, 0x49, 0x2a, 0x00])) {
98
+ return "image/tiff";
99
+ }
100
+
101
+ // TIFF (big-endian): 4D 4D 00 2A
102
+ if (checkBytes(buffer, [0x4d, 0x4d, 0x00, 0x2a])) {
103
+ return "image/tiff";
104
+ }
105
+
106
+ // ICO: 00 00 01 00
107
+ if (checkBytes(buffer, [0x00, 0x00, 0x01, 0x00])) {
108
+ return "image/x-icon";
109
+ }
110
+
111
+ // SVG (XML-based, check for <svg or <?xml)
112
+ if (buffer.length >= 5) {
113
+ const text = new TextDecoder("utf-8", { fatal: false }).decode(
114
+ buffer.slice(0, Math.min(1024, buffer.length)),
115
+ );
116
+ if (text.includes("<svg") || (text.includes("<?xml") && text.includes("<svg"))) {
117
+ return "image/svg+xml";
118
+ }
119
+ }
120
+
121
+ // ===== VIDEOS =====
122
+
123
+ // MP4/M4V/M4A: ....ftyp
124
+ if (buffer.length >= 12 && checkString(buffer, "ftyp", 4)) {
125
+ const subtype = new TextDecoder().decode(buffer.slice(8, 12));
126
+ if (
127
+ subtype.startsWith("mp4") ||
128
+ subtype.startsWith("M4") ||
129
+ subtype.startsWith("isom")
130
+ ) {
131
+ return "video/mp4";
132
+ }
133
+ }
134
+
135
+ // WebM: 1A 45 DF A3
136
+ if (checkBytes(buffer, [0x1a, 0x45, 0xdf, 0xa3])) {
137
+ return "video/webm";
138
+ }
139
+
140
+ // AVI: RIFF....AVI
141
+ if (
142
+ checkBytes(buffer, [0x52, 0x49, 0x46, 0x46]) &&
143
+ buffer.length >= 12 &&
144
+ checkString(buffer, "AVI ", 8)
145
+ ) {
146
+ return "video/x-msvideo";
147
+ }
148
+
149
+ // MOV (QuickTime): ....moov or ....mdat or ....free
150
+ if (
151
+ buffer.length >= 8 &&
152
+ (checkString(buffer, "moov", 4) ||
153
+ checkString(buffer, "mdat", 4) ||
154
+ checkString(buffer, "free", 4))
155
+ ) {
156
+ return "video/quicktime";
157
+ }
158
+
159
+ // MKV: 1A 45 DF A3 (same as WebM but check for Matroska)
160
+ if (
161
+ checkBytes(buffer, [0x1a, 0x45, 0xdf, 0xa3]) &&
162
+ buffer.length >= 100
163
+ ) {
164
+ const text = new TextDecoder("utf-8", { fatal: false }).decode(
165
+ buffer.slice(0, 100),
166
+ );
167
+ if (text.includes("matroska")) {
168
+ return "video/x-matroska";
169
+ }
170
+ }
171
+
172
+ // ===== AUDIO =====
173
+
174
+ // MP3: FF FB or FF F3 or FF F2 or ID3
175
+ if (
176
+ checkBytes(buffer, [0xff, 0xfb]) ||
177
+ checkBytes(buffer, [0xff, 0xf3]) ||
178
+ checkBytes(buffer, [0xff, 0xf2]) ||
179
+ checkString(buffer, "ID3")
180
+ ) {
181
+ return "audio/mpeg";
182
+ }
183
+
184
+ // WAV: RIFF....WAVE
185
+ if (
186
+ checkBytes(buffer, [0x52, 0x49, 0x46, 0x46]) &&
187
+ buffer.length >= 12 &&
188
+ checkString(buffer, "WAVE", 8)
189
+ ) {
190
+ return "audio/wav";
191
+ }
192
+
193
+ // FLAC: 66 4C 61 43 (fLaC)
194
+ if (checkString(buffer, "fLaC")) {
195
+ return "audio/flac";
196
+ }
197
+
198
+ // OGG: 4F 67 67 53 (OggS)
199
+ if (checkString(buffer, "OggS")) {
200
+ return "audio/ogg";
201
+ }
202
+
203
+ // M4A: ....ftypM4A
204
+ if (
205
+ buffer.length >= 12 &&
206
+ checkString(buffer, "ftyp", 4) &&
207
+ checkString(buffer, "M4A", 8)
208
+ ) {
209
+ return "audio/mp4";
210
+ }
211
+
212
+ // ===== DOCUMENTS =====
213
+
214
+ // PDF: 25 50 44 46 (%PDF)
215
+ if (checkString(buffer, "%PDF")) {
216
+ return "application/pdf";
217
+ }
218
+
219
+ // ===== ARCHIVES =====
220
+
221
+ // ZIP: 50 4B 03 04 or 50 4B 05 06 (empty archive) or 50 4B 07 08 (spanned archive)
222
+ if (
223
+ checkBytes(buffer, [0x50, 0x4b, 0x03, 0x04]) ||
224
+ checkBytes(buffer, [0x50, 0x4b, 0x05, 0x06]) ||
225
+ checkBytes(buffer, [0x50, 0x4b, 0x07, 0x08])
226
+ ) {
227
+ // Could be ZIP, DOCX, XLSX, PPTX, JAR, APK, etc.
228
+ // Check for Office formats
229
+ if (buffer.length >= 1024) {
230
+ const text = new TextDecoder("utf-8", { fatal: false }).decode(buffer);
231
+ if (text.includes("word/")) return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
232
+ if (text.includes("xl/")) return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
233
+ if (text.includes("ppt/")) return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
234
+ }
235
+ return "application/zip";
236
+ }
237
+
238
+ // RAR: 52 61 72 21 1A 07 (Rar!)
239
+ if (checkBytes(buffer, [0x52, 0x61, 0x72, 0x21, 0x1a, 0x07])) {
240
+ return "application/x-rar-compressed";
241
+ }
242
+
243
+ // 7Z: 37 7A BC AF 27 1C
244
+ if (checkBytes(buffer, [0x37, 0x7a, 0xbc, 0xaf, 0x27, 0x1c])) {
245
+ return "application/x-7z-compressed";
246
+ }
247
+
248
+ // GZIP: 1F 8B
249
+ if (checkBytes(buffer, [0x1f, 0x8b])) {
250
+ return "application/gzip";
251
+ }
252
+
253
+ // TAR (ustar): "ustar" at offset 257
254
+ if (buffer.length >= 262 && checkString(buffer, "ustar", 257)) {
255
+ return "application/x-tar";
256
+ }
257
+
258
+ // ===== FONTS =====
259
+
260
+ // WOFF: 77 4F 46 46 (wOFF)
261
+ if (checkString(buffer, "wOFF")) {
262
+ return "font/woff";
263
+ }
264
+
265
+ // WOFF2: 77 4F 46 32 (wOF2)
266
+ if (checkString(buffer, "wOF2")) {
267
+ return "font/woff2";
268
+ }
269
+
270
+ // TTF: 00 01 00 00 00
271
+ if (checkBytes(buffer, [0x00, 0x01, 0x00, 0x00, 0x00])) {
272
+ return "font/ttf";
273
+ }
274
+
275
+ // OTF: 4F 54 54 4F (OTTO)
276
+ if (checkString(buffer, "OTTO")) {
277
+ return "font/otf";
278
+ }
279
+
280
+ // ===== TEXT =====
281
+
282
+ // JSON (basic check for { or [)
283
+ if (buffer.length >= 1) {
284
+ const firstByte = buffer[0];
285
+ if (firstByte === 0x7b || firstByte === 0x5b) {
286
+ // { or [
287
+ try {
288
+ const text = new TextDecoder("utf-8").decode(
289
+ buffer.slice(0, Math.min(1024, buffer.length)),
290
+ );
291
+ JSON.parse(text.trim());
292
+ return "application/json";
293
+ } catch {
294
+ // Not valid JSON
295
+ }
296
+ }
297
+ }
298
+
299
+ // Fallback to extension-based detection
300
+ if (filename) {
301
+ const ext = filename.split(".").pop()?.toLowerCase();
302
+ switch (ext) {
303
+ // Images
304
+ case "jpg":
305
+ case "jpeg":
306
+ return "image/jpeg";
307
+ case "png":
308
+ return "image/png";
309
+ case "gif":
310
+ return "image/gif";
311
+ case "webp":
312
+ return "image/webp";
313
+ case "avif":
314
+ return "image/avif";
315
+ case "heic":
316
+ case "heif":
317
+ return "image/heic";
318
+ case "bmp":
319
+ return "image/bmp";
320
+ case "tiff":
321
+ case "tif":
322
+ return "image/tiff";
323
+ case "ico":
324
+ return "image/x-icon";
325
+ case "svg":
326
+ return "image/svg+xml";
327
+
328
+ // Videos
329
+ case "mp4":
330
+ case "m4v":
331
+ return "video/mp4";
332
+ case "webm":
333
+ return "video/webm";
334
+ case "avi":
335
+ return "video/x-msvideo";
336
+ case "mov":
337
+ return "video/quicktime";
338
+ case "mkv":
339
+ return "video/x-matroska";
340
+
341
+ // Audio
342
+ case "mp3":
343
+ return "audio/mpeg";
344
+ case "wav":
345
+ return "audio/wav";
346
+ case "flac":
347
+ return "audio/flac";
348
+ case "ogg":
349
+ return "audio/ogg";
350
+ case "m4a":
351
+ return "audio/mp4";
352
+
353
+ // Documents
354
+ case "pdf":
355
+ return "application/pdf";
356
+ case "docx":
357
+ return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
358
+ case "xlsx":
359
+ return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
360
+ case "pptx":
361
+ return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
362
+
363
+ // Archives
364
+ case "zip":
365
+ return "application/zip";
366
+ case "rar":
367
+ return "application/x-rar-compressed";
368
+ case "7z":
369
+ return "application/x-7z-compressed";
370
+ case "gz":
371
+ case "gzip":
372
+ return "application/gzip";
373
+ case "tar":
374
+ return "application/x-tar";
375
+
376
+ // Fonts
377
+ case "woff":
378
+ return "font/woff";
379
+ case "woff2":
380
+ return "font/woff2";
381
+ case "ttf":
382
+ return "font/ttf";
383
+ case "otf":
384
+ return "font/otf";
385
+
386
+ // Text
387
+ case "txt":
388
+ return "text/plain";
389
+ case "json":
390
+ return "application/json";
391
+ case "xml":
392
+ return "application/xml";
393
+ case "html":
394
+ case "htm":
395
+ return "text/html";
396
+ case "css":
397
+ return "text/css";
398
+ case "js":
399
+ return "application/javascript";
400
+ case "csv":
401
+ return "text/csv";
402
+
403
+ default:
404
+ return "application/octet-stream";
405
+ }
406
+ }
407
+
408
+ return "application/octet-stream";
409
+ };
410
+
411
+ /**
412
+ * Compare two MIME types with lenient matching.
413
+ * Matches on major type (e.g., "image/*") to allow for minor variations.
414
+ *
415
+ * @param declared - MIME type provided by client
416
+ * @param detected - MIME type detected from file content
417
+ * @returns true if MIME types are compatible
418
+ *
419
+ * @example
420
+ * compareMimeTypes("image/png", "image/apng") // true
421
+ * compareMimeTypes("image/jpeg", "image/png") // true (both images)
422
+ * compareMimeTypes("image/png", "application/pdf") // false
423
+ */
424
+ export function compareMimeTypes(declared: string, detected: string): boolean {
425
+ // Exact match
426
+ if (declared === detected) {
427
+ return true;
428
+ }
429
+
430
+ // Extract major types (e.g., "image" from "image/png")
431
+ const declaredMajor = declared.split("/")[0];
432
+ const detectedMajor = detected.split("/")[0];
433
+
434
+ // Compare major types for lenient matching
435
+ return declaredMajor === detectedMajor;
436
+ }