@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,390 @@
1
+ import { Context, Effect, Layer } from "effect";
2
+ import type { UploadistaError } from "../errors";
3
+ import type {
4
+ DataStore,
5
+ DataStoreCapabilities,
6
+ EventEmitter,
7
+ InputFile,
8
+ KvStore,
9
+ Middleware,
10
+ UploadEvent,
11
+ UploadFile,
12
+ WebSocketConnection,
13
+ } from "../types";
14
+ import {
15
+ UploadEventEmitter,
16
+ UploadFileDataStores,
17
+ UploadFileKVStore,
18
+ } from "../types";
19
+ import { GenerateId, type GenerateIdShape } from "../utils/generate-id";
20
+ import { createUpload } from "./create-upload";
21
+ import { uploadChunk } from "./upload-chunk";
22
+ import { arrayBuffer, fetchFile } from "./upload-url";
23
+
24
+ /**
25
+ * Legacy configuration options for UploadServer.
26
+ *
27
+ * @deprecated Use Effect Layers instead of this configuration object.
28
+ * This type is kept for backward compatibility.
29
+ *
30
+ * @property dataStore - DataStore instance or factory function
31
+ * @property kvStore - KV store for upload metadata
32
+ * @property eventEmitter - Event emitter for upload progress
33
+ * @property generateId - Optional ID generator (defaults to UUID)
34
+ * @property middlewares - Optional request middlewares
35
+ * @property withTracing - Enable Effect tracing for debugging
36
+ */
37
+ export type UploadServerOptions = {
38
+ dataStore:
39
+ | ((storageId: string) => Promise<DataStore<UploadFile>>)
40
+ | DataStore<UploadFile>;
41
+ kvStore: KvStore<UploadFile>;
42
+ eventEmitter: EventEmitter<UploadEvent>;
43
+ generateId?: GenerateIdShape;
44
+ middlewares?: Middleware[];
45
+ withTracing?: boolean;
46
+ };
47
+
48
+ /**
49
+ * UploadServer service interface.
50
+ *
51
+ * This is the core upload handling service that provides all file upload operations.
52
+ * It manages upload lifecycle, resumable uploads, progress tracking, and storage integration.
53
+ *
54
+ * All operations return Effect types for composable, type-safe error handling.
55
+ *
56
+ * @property createUpload - Initiates a new upload and returns metadata
57
+ * @property uploadChunk - Uploads a chunk of data for an existing upload
58
+ * @property getCapabilities - Returns storage backend capabilities
59
+ * @property upload - Complete upload in one operation (create + upload data)
60
+ * @property uploadFromUrl - Uploads a file from a remote URL
61
+ * @property getUpload - Retrieves upload metadata by ID
62
+ * @property read - Reads the complete uploaded file data
63
+ * @property delete - Deletes an upload and its data
64
+ * @property subscribeToUploadEvents - Subscribes WebSocket to upload progress events
65
+ * @property unsubscribeFromUploadEvents - Unsubscribes from upload events
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * // Basic upload flow
70
+ * const program = Effect.gen(function* () {
71
+ * const server = yield* UploadServer;
72
+ *
73
+ * // 1. Create upload
74
+ * const inputFile: InputFile = {
75
+ * storageId: "s3-production",
76
+ * size: 1024000,
77
+ * type: "image/jpeg",
78
+ * fileName: "photo.jpg"
79
+ * };
80
+ * const upload = yield* server.createUpload(inputFile, "client123");
81
+ *
82
+ * // 2. Upload chunks
83
+ * const chunk = new ReadableStream(...);
84
+ * const updated = yield* server.uploadChunk(upload.id, "client123", chunk);
85
+ *
86
+ * // 3. Read the uploaded file
87
+ * const data = yield* server.read(upload.id, "client123");
88
+ *
89
+ * return upload;
90
+ * });
91
+ *
92
+ * // Upload with WebSocket progress tracking
93
+ * const uploadWithProgress = Effect.gen(function* () {
94
+ * const server = yield* UploadServer;
95
+ *
96
+ * // Subscribe to progress events
97
+ * yield* server.subscribeToUploadEvents(uploadId, websocket);
98
+ *
99
+ * // Upload (events will be emitted automatically)
100
+ * const result = yield* server.upload(inputFile, clientId, stream);
101
+ *
102
+ * // Unsubscribe when done
103
+ * yield* server.unsubscribeFromUploadEvents(uploadId);
104
+ *
105
+ * return result;
106
+ * });
107
+ *
108
+ * // Upload from URL
109
+ * const urlUpload = Effect.gen(function* () {
110
+ * const server = yield* UploadServer;
111
+ *
112
+ * const inputFile: InputFile = {
113
+ * storageId: "s3-production",
114
+ * size: 0, // Unknown initially
115
+ * type: "image/png",
116
+ * fileName: "remote-image.png"
117
+ * };
118
+ *
119
+ * const upload = yield* server.uploadFromUrl(
120
+ * inputFile,
121
+ * "client123",
122
+ * "https://example.com/image.png"
123
+ * );
124
+ *
125
+ * return upload;
126
+ * });
127
+ * ```
128
+ */
129
+ export type UploadServerShape = {
130
+ createUpload: (
131
+ inputFile: InputFile,
132
+ clientId: string | null,
133
+ ) => Effect.Effect<UploadFile, UploadistaError>;
134
+ uploadChunk: (
135
+ uploadId: string,
136
+ clientId: string | null,
137
+ chunk: ReadableStream,
138
+ ) => Effect.Effect<UploadFile, UploadistaError>;
139
+ getCapabilities: (
140
+ storageId: string,
141
+ clientId: string | null,
142
+ ) => Effect.Effect<DataStoreCapabilities, UploadistaError>;
143
+ upload: (
144
+ file: InputFile,
145
+ clientId: string | null,
146
+ stream: ReadableStream,
147
+ ) => Effect.Effect<UploadFile, UploadistaError>;
148
+ uploadFromUrl: (
149
+ inputFile: InputFile,
150
+ clientId: string | null,
151
+ url: string,
152
+ ) => Effect.Effect<UploadFile, UploadistaError>;
153
+ getUpload: (uploadId: string) => Effect.Effect<UploadFile, UploadistaError>;
154
+ read: (
155
+ uploadId: string,
156
+ clientId: string | null,
157
+ ) => Effect.Effect<Uint8Array, UploadistaError>;
158
+ delete: (
159
+ uploadId: string,
160
+ clientId: string | null,
161
+ ) => Effect.Effect<void, UploadistaError>;
162
+ subscribeToUploadEvents: (
163
+ uploadId: string,
164
+ connection: WebSocketConnection,
165
+ ) => Effect.Effect<void, UploadistaError>;
166
+ unsubscribeFromUploadEvents: (
167
+ uploadId: string,
168
+ ) => Effect.Effect<void, UploadistaError>;
169
+ };
170
+
171
+ /**
172
+ * Effect-TS context tag for the UploadServer service.
173
+ *
174
+ * Use this tag to access the UploadServer in an Effect context.
175
+ * The server must be provided via a Layer or dependency injection.
176
+ *
177
+ * @example
178
+ * ```typescript
179
+ * // Access UploadServer in an Effect
180
+ * const uploadEffect = Effect.gen(function* () {
181
+ * const server = yield* UploadServer;
182
+ * const upload = yield* server.createUpload(inputFile, clientId);
183
+ * return upload;
184
+ * });
185
+ *
186
+ * // Provide UploadServer layer
187
+ * const program = uploadEffect.pipe(
188
+ * Effect.provide(uploadServer),
189
+ * Effect.provide(uploadFileKvStore),
190
+ * Effect.provide(dataStoreLayer),
191
+ * Effect.provide(eventEmitterLayer)
192
+ * );
193
+ * ```
194
+ */
195
+ export class UploadServer extends Context.Tag("UploadServer")<
196
+ UploadServer,
197
+ UploadServerShape
198
+ >() {}
199
+
200
+ /**
201
+ * Creates the UploadServer implementation.
202
+ *
203
+ * This function constructs the UploadServer service by composing all required
204
+ * dependencies (KV store, data stores, event emitter, ID generator). It implements
205
+ * all upload operations defined in UploadServerShape.
206
+ *
207
+ * The server automatically handles:
208
+ * - Upload lifecycle management (create, resume, complete)
209
+ * - Progress tracking and event emission
210
+ * - Storage backend routing based on storageId
211
+ * - Error handling with proper UploadistaError types
212
+ *
213
+ * @returns An Effect that yields the UploadServerShape implementation
214
+ *
215
+ * @example
216
+ * ```typescript
217
+ * // Create a custom UploadServer layer
218
+ * const myUploadServer = Layer.effect(
219
+ * UploadServer,
220
+ * createUploadServer()
221
+ * );
222
+ *
223
+ * // Use in a program
224
+ * const program = Effect.gen(function* () {
225
+ * const server = yield* UploadServer;
226
+ * // Use server operations...
227
+ * }).pipe(Effect.provide(myUploadServer));
228
+ * ```
229
+ */
230
+ export function createUploadServer() {
231
+ return Effect.gen(function* () {
232
+ const kvStore = yield* UploadFileKVStore;
233
+ const eventEmitter = yield* UploadEventEmitter;
234
+ const generateId = yield* GenerateId;
235
+ const dataStoreService = yield* UploadFileDataStores;
236
+
237
+ return {
238
+ upload: (
239
+ inputFile: InputFile,
240
+ clientId: string | null,
241
+ stream: ReadableStream,
242
+ ) =>
243
+ Effect.gen(function* () {
244
+ const fileCreated = yield* createUpload(inputFile, clientId, {
245
+ dataStoreService,
246
+ kvStore,
247
+ eventEmitter,
248
+ generateId,
249
+ });
250
+ return yield* uploadChunk(fileCreated.id, clientId, stream, {
251
+ dataStoreService,
252
+ kvStore,
253
+ eventEmitter,
254
+ });
255
+ }),
256
+ uploadFromUrl: (
257
+ inputFile: InputFile,
258
+ clientId: string | null,
259
+ url: string,
260
+ ) =>
261
+ Effect.gen(function* () {
262
+ const response = yield* fetchFile(url);
263
+ const buffer = yield* arrayBuffer(response);
264
+
265
+ // Create a readable stream from the buffer
266
+ const stream = new ReadableStream({
267
+ start(controller) {
268
+ controller.enqueue(new Uint8Array(buffer));
269
+ controller.close();
270
+ },
271
+ });
272
+
273
+ const fileCreated = yield* createUpload(
274
+ { ...inputFile, size: buffer.byteLength },
275
+ clientId,
276
+ {
277
+ dataStoreService,
278
+ kvStore,
279
+ eventEmitter,
280
+ generateId,
281
+ },
282
+ );
283
+ return yield* uploadChunk(fileCreated.id, clientId, stream, {
284
+ dataStoreService,
285
+ kvStore,
286
+ eventEmitter,
287
+ });
288
+ }),
289
+ createUpload: (inputFile: InputFile, clientId: string | null) =>
290
+ Effect.gen(function* () {
291
+ const fileCreated = yield* createUpload(inputFile, clientId, {
292
+ dataStoreService,
293
+ kvStore,
294
+ eventEmitter,
295
+ generateId,
296
+ });
297
+ return fileCreated;
298
+ }),
299
+ uploadChunk: (
300
+ uploadId: string,
301
+ clientId: string | null,
302
+ chunk: ReadableStream,
303
+ ) =>
304
+ Effect.gen(function* () {
305
+ const file = yield* uploadChunk(uploadId, clientId, chunk, {
306
+ dataStoreService,
307
+ kvStore,
308
+ eventEmitter,
309
+ });
310
+ return file;
311
+ }),
312
+ getUpload: (uploadId: string) =>
313
+ Effect.gen(function* () {
314
+ const file = yield* kvStore.get(uploadId);
315
+ return file;
316
+ }),
317
+ read: (uploadId: string, clientId: string | null) =>
318
+ Effect.gen(function* () {
319
+ const upload = yield* kvStore.get(uploadId);
320
+ const dataStore = yield* dataStoreService.getDataStore(
321
+ upload.storage.id,
322
+ clientId,
323
+ );
324
+ return yield* dataStore.read(uploadId);
325
+ }),
326
+ delete: (uploadId: string, clientId: string | null) =>
327
+ Effect.gen(function* () {
328
+ const upload = yield* kvStore.get(uploadId);
329
+ const dataStore = yield* dataStoreService.getDataStore(
330
+ upload.storage.id,
331
+ clientId,
332
+ );
333
+ yield* dataStore.remove(uploadId);
334
+ yield* kvStore.delete(uploadId);
335
+ return;
336
+ }),
337
+ getCapabilities: (storageId: string, clientId: string | null) =>
338
+ Effect.gen(function* () {
339
+ const dataStore = yield* dataStoreService.getDataStore(
340
+ storageId,
341
+ clientId,
342
+ );
343
+ return dataStore.getCapabilities();
344
+ }),
345
+ subscribeToUploadEvents: (
346
+ uploadId: string,
347
+ connection: WebSocketConnection,
348
+ ) =>
349
+ Effect.gen(function* () {
350
+ yield* eventEmitter.subscribe(uploadId, connection);
351
+ }),
352
+ unsubscribeFromUploadEvents: (uploadId: string) =>
353
+ Effect.gen(function* () {
354
+ yield* eventEmitter.unsubscribe(uploadId);
355
+ }),
356
+ } satisfies UploadServerShape;
357
+ });
358
+ }
359
+
360
+ /**
361
+ * Pre-built UploadServer Effect Layer.
362
+ *
363
+ * This layer provides a ready-to-use UploadServer implementation that can be
364
+ * composed with other layers to build a complete upload system.
365
+ *
366
+ * Required dependencies:
367
+ * - UploadFileKVStore: For storing upload metadata
368
+ * - UploadFileDataStores: For routing to storage backends
369
+ * - UploadEventEmitter: For progress events
370
+ * - GenerateId: For creating upload IDs
371
+ *
372
+ * @example
373
+ * ```typescript
374
+ * // Compose a complete upload system
375
+ * const fullUploadSystem = Layer.mergeAll(
376
+ * uploadServer,
377
+ * uploadFileKvStore,
378
+ * dataStoreLayer,
379
+ * uploadEventEmitter,
380
+ * generateIdLayer
381
+ * );
382
+ *
383
+ * // Use in application
384
+ * const app = Effect.gen(function* () {
385
+ * const server = yield* UploadServer;
386
+ * // Perform uploads...
387
+ * }).pipe(Effect.provide(fullUploadSystem));
388
+ * ```
389
+ */
390
+ export const uploadServer = Layer.effect(UploadServer, createUploadServer());
@@ -0,0 +1,316 @@
1
+ import type { DataStoreCapabilities, UploadStrategy } from "../types";
2
+
3
+ /**
4
+ * Configuration options for upload strategy negotiation.
5
+ *
6
+ * @property fileSize - Size of the file to be uploaded in bytes
7
+ * @property preferredStrategy - Preferred upload strategy (single, parallel, resumable)
8
+ * @property preferredChunkSize - Preferred chunk size in bytes
9
+ * @property parallelUploads - Number of parallel upload connections
10
+ * @property minChunkSizeForParallel - Minimum file size to consider parallel uploads
11
+ */
12
+ export type UploadStrategyOptions = {
13
+ fileSize: number;
14
+ preferredStrategy?: UploadStrategy;
15
+ preferredChunkSize?: number;
16
+ parallelUploads?: number;
17
+ minChunkSizeForParallel?: number;
18
+ };
19
+
20
+ /**
21
+ * Result of upload strategy negotiation.
22
+ *
23
+ * @property strategy - The negotiated upload strategy
24
+ * @property chunkSize - The negotiated chunk size in bytes
25
+ * @property parallelUploads - The negotiated number of parallel uploads
26
+ * @property reasoning - Array of reasoning strings explaining the decisions
27
+ * @property warnings - Array of warning messages about adjustments made
28
+ */
29
+ export type NegotiatedStrategy = {
30
+ strategy: UploadStrategy;
31
+ chunkSize: number;
32
+ parallelUploads: number;
33
+ reasoning: string[];
34
+ warnings: string[];
35
+ };
36
+
37
+ /**
38
+ * Negotiates the optimal upload strategy based on data store capabilities and file characteristics.
39
+ *
40
+ * This class analyzes data store capabilities, file size, and user preferences to determine
41
+ * the best upload strategy (single, parallel, resumable) and optimal parameters like chunk size
42
+ * and parallel connection count.
43
+ *
44
+ * The negotiator considers:
45
+ * - Data store capabilities (parallel uploads, resumable uploads, concatenation)
46
+ * - File size and chunk size constraints
47
+ * - User preferences and requirements
48
+ * - Performance optimization opportunities
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * // Create negotiator for S3 data store
53
+ * const negotiator = new UploadStrategyNegotiator(
54
+ * s3Capabilities,
55
+ * (strategy) => s3Capabilities.supportsStrategy(strategy)
56
+ * );
57
+ *
58
+ * // Negotiate strategy for large file
59
+ * const result = negotiator.negotiateStrategy({
60
+ * fileSize: 100_000_000, // 100MB
61
+ * preferredStrategy: "parallel",
62
+ * preferredChunkSize: 5_000_000, // 5MB chunks
63
+ * parallelUploads: 4
64
+ * });
65
+ *
66
+ * console.log(result.strategy); // "parallel"
67
+ * console.log(result.chunkSize); // 5_000_000
68
+ * console.log(result.reasoning); // ["Using preferred strategy: parallel", ...]
69
+ * ```
70
+ */
71
+ export class UploadStrategyNegotiator {
72
+ /**
73
+ * Creates a new upload strategy negotiator.
74
+ *
75
+ * @param capabilities - Data store capabilities and constraints
76
+ * @param validateUploadStrategy - Function to validate if a strategy is supported
77
+ */
78
+ constructor(
79
+ private capabilities: DataStoreCapabilities,
80
+ private validateUploadStrategy: (strategy: UploadStrategy) => boolean
81
+ ) {}
82
+
83
+ /**
84
+ * Negotiates the optimal upload strategy based on options and data store capabilities.
85
+ *
86
+ * This method analyzes the provided options and data store capabilities to determine
87
+ * the best upload strategy, chunk size, and parallel upload settings. It considers
88
+ * user preferences, file size, and data store constraints to make optimal decisions.
89
+ *
90
+ * The negotiation process:
91
+ * 1. Validates preferred strategy against data store capabilities
92
+ * 2. Automatically selects strategy based on file size and capabilities
93
+ * 3. Adjusts chunk size to fit within data store constraints
94
+ * 4. Validates parallel upload settings
95
+ * 5. Ensures final strategy is supported by the data store
96
+ *
97
+ * @param options - Upload strategy options including file size and preferences
98
+ * @returns Negotiated strategy with reasoning and warnings
99
+ *
100
+ * @example
101
+ * ```typescript
102
+ * const result = negotiator.negotiateStrategy({
103
+ * fileSize: 50_000_000, // 50MB
104
+ * preferredStrategy: "parallel",
105
+ * preferredChunkSize: 5_000_000, // 5MB
106
+ * parallelUploads: 3
107
+ * });
108
+ *
109
+ * console.log(result.strategy); // "parallel"
110
+ * console.log(result.chunkSize); // 5_000_000
111
+ * console.log(result.parallelUploads); // 3
112
+ * console.log(result.reasoning); // ["Using preferred strategy: parallel", ...]
113
+ * console.log(result.warnings); // [] (no warnings)
114
+ * ```
115
+ */
116
+ negotiateStrategy(options: UploadStrategyOptions): NegotiatedStrategy {
117
+ const reasoning: string[] = [];
118
+ const warnings: string[] = [];
119
+
120
+ let strategy: UploadStrategy = "single";
121
+ let chunkSize =
122
+ options.preferredChunkSize ??
123
+ this.capabilities.optimalChunkSize ??
124
+ 1024 * 1024;
125
+ let parallelUploads = options.parallelUploads ?? 1;
126
+
127
+ // Check if data store supports the preferred strategy
128
+ if (options.preferredStrategy) {
129
+ if (!this.validateUploadStrategy(options.preferredStrategy)) {
130
+ warnings.push(
131
+ `Preferred strategy '${options.preferredStrategy}' not supported by data store, falling back`
132
+ );
133
+ } else {
134
+ strategy = options.preferredStrategy;
135
+ reasoning.push(`Using preferred strategy: ${strategy}`);
136
+ }
137
+ }
138
+
139
+ // Automatic strategy selection based on capabilities and file size
140
+ if (
141
+ !options.preferredStrategy ||
142
+ !this.validateUploadStrategy(options.preferredStrategy)
143
+ ) {
144
+ if (
145
+ this.capabilities.supportsParallelUploads &&
146
+ options.fileSize > (options.minChunkSizeForParallel ?? 10 * 1024 * 1024)
147
+ ) {
148
+ strategy = "parallel";
149
+ reasoning.push(
150
+ `Selected parallel upload for large file (${options.fileSize} bytes)`
151
+ );
152
+ } else {
153
+ strategy = "single";
154
+ reasoning.push(
155
+ this.capabilities.supportsParallelUploads
156
+ ? `Selected single upload for small file (${options.fileSize} bytes)`
157
+ : "Selected single upload (parallel not supported by data store)"
158
+ );
159
+ }
160
+ }
161
+
162
+ // Validate and adjust chunk size based on data store constraints
163
+ if (
164
+ this.capabilities.minChunkSize &&
165
+ chunkSize < this.capabilities.minChunkSize
166
+ ) {
167
+ warnings.push(
168
+ `Chunk size ${chunkSize} below minimum ${this.capabilities.minChunkSize}, adjusting`
169
+ );
170
+ chunkSize = this.capabilities.minChunkSize;
171
+ }
172
+
173
+ if (
174
+ this.capabilities.maxChunkSize &&
175
+ chunkSize > this.capabilities.maxChunkSize
176
+ ) {
177
+ warnings.push(
178
+ `Chunk size ${chunkSize} above maximum ${this.capabilities.maxChunkSize}, adjusting`
179
+ );
180
+ chunkSize = this.capabilities.maxChunkSize;
181
+ }
182
+
183
+ // Validate parallel upload settings
184
+ if (strategy === "parallel") {
185
+ if (
186
+ this.capabilities.maxConcurrentUploads &&
187
+ parallelUploads > this.capabilities.maxConcurrentUploads
188
+ ) {
189
+ warnings.push(
190
+ `Parallel uploads ${parallelUploads} exceeds maximum ${this.capabilities.maxConcurrentUploads}, adjusting`
191
+ );
192
+ parallelUploads = this.capabilities.maxConcurrentUploads;
193
+ }
194
+
195
+ // Check if file would exceed max parts limit
196
+ if (this.capabilities.maxParts) {
197
+ const estimatedParts = Math.ceil(options.fileSize / chunkSize);
198
+ if (estimatedParts > this.capabilities.maxParts) {
199
+ const minChunkForParts = Math.ceil(
200
+ options.fileSize / this.capabilities.maxParts
201
+ );
202
+ warnings.push(
203
+ `Estimated parts ${estimatedParts} exceeds maximum ${this.capabilities.maxParts}, increasing chunk size`
204
+ );
205
+ chunkSize = Math.max(chunkSize, minChunkForParts);
206
+ }
207
+ }
208
+ }
209
+
210
+ // Final validation - ensure strategy is still valid after adjustments
211
+ if (!this.validateUploadStrategy(strategy)) {
212
+ warnings.push(
213
+ `Final strategy validation failed, falling back to single upload`
214
+ );
215
+ strategy = "single";
216
+ parallelUploads = 1;
217
+ }
218
+
219
+ // Add capability information to reasoning
220
+ reasoning.push(
221
+ `Data store capabilities: parallel=${this.capabilities.supportsParallelUploads}, concatenation=${this.capabilities.supportsConcatenation}, resumable=${this.capabilities.supportsResumableUploads}`
222
+ );
223
+
224
+ return {
225
+ strategy,
226
+ chunkSize,
227
+ parallelUploads: strategy === "parallel" ? parallelUploads : 1,
228
+ reasoning,
229
+ warnings,
230
+ };
231
+ }
232
+
233
+ /**
234
+ * Gets the data store capabilities used by this negotiator.
235
+ *
236
+ * @returns The data store capabilities and constraints
237
+ */
238
+ getDataStoreCapabilities(): DataStoreCapabilities {
239
+ return this.capabilities;
240
+ }
241
+
242
+ /**
243
+ * Validates upload strategy configuration against data store capabilities.
244
+ *
245
+ * This method checks if the provided configuration is valid for the current
246
+ * data store capabilities without performing the actual negotiation. It's
247
+ * useful for pre-validation before attempting to negotiate a strategy.
248
+ *
249
+ * @param options - Upload strategy options to validate
250
+ * @returns Validation result with validity flag and error messages
251
+ *
252
+ * @example
253
+ * ```typescript
254
+ * const validation = negotiator.validateConfiguration({
255
+ * fileSize: 10_000_000,
256
+ * preferredStrategy: "parallel",
257
+ * preferredChunkSize: 1_000_000,
258
+ * parallelUploads: 5
259
+ * });
260
+ *
261
+ * if (!validation.valid) {
262
+ * console.log("Configuration errors:", validation.errors);
263
+ * // Handle validation errors
264
+ * }
265
+ * ```
266
+ */
267
+ validateConfiguration(options: UploadStrategyOptions): {
268
+ valid: boolean;
269
+ errors: string[];
270
+ } {
271
+ const errors: string[] = [];
272
+
273
+ if (
274
+ options.preferredStrategy &&
275
+ !this.validateUploadStrategy(options.preferredStrategy)
276
+ ) {
277
+ errors.push(
278
+ `Preferred strategy '${options.preferredStrategy}' not supported by data store`
279
+ );
280
+ }
281
+
282
+ if (options.preferredChunkSize) {
283
+ if (
284
+ this.capabilities.minChunkSize &&
285
+ options.preferredChunkSize < this.capabilities.minChunkSize
286
+ ) {
287
+ errors.push(
288
+ `Chunk size ${options.preferredChunkSize} below data store minimum ${this.capabilities.minChunkSize}`
289
+ );
290
+ }
291
+ if (
292
+ this.capabilities.maxChunkSize &&
293
+ options.preferredChunkSize > this.capabilities.maxChunkSize
294
+ ) {
295
+ errors.push(
296
+ `Chunk size ${options.preferredChunkSize} above data store maximum ${this.capabilities.maxChunkSize}`
297
+ );
298
+ }
299
+ }
300
+
301
+ if (
302
+ options.parallelUploads &&
303
+ this.capabilities.maxConcurrentUploads &&
304
+ options.parallelUploads > this.capabilities.maxConcurrentUploads
305
+ ) {
306
+ errors.push(
307
+ `Parallel uploads ${options.parallelUploads} exceeds data store maximum ${this.capabilities.maxConcurrentUploads}`
308
+ );
309
+ }
310
+
311
+ return {
312
+ valid: errors.length === 0,
313
+ errors,
314
+ };
315
+ }
316
+ }