@fragno-dev/upload 0.1.1 → 0.1.3

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 (301) hide show
  1. package/README.md +148 -12
  2. package/dist/browser/client/clients.js +17 -9
  3. package/dist/browser/client/clients.js.map +1 -1
  4. package/dist/browser/client/helpers.d.ts +15 -6
  5. package/dist/browser/client/helpers.d.ts.map +1 -1
  6. package/dist/browser/client/helpers.js +176 -30
  7. package/dist/browser/client/helpers.js.map +1 -1
  8. package/dist/browser/client/node_modules/.pnpm/{@nanostores_query@0.3.4_nanostores@1.1.0 → @nanostores_query@0.3.4_nanostores@1.2.0}/node_modules/@nanostores/query/dist/nanoquery.js +6 -6
  9. package/dist/browser/client/node_modules/.pnpm/{@nanostores_query@0.3.4_nanostores@1.1.0 → @nanostores_query@0.3.4_nanostores@1.2.0}/node_modules/@nanostores/query/dist/nanoquery.js.map +1 -1
  10. package/dist/browser/client/node_modules/.pnpm/{@nanostores_solid@1.1.1_nanostores@1.1.0_solid-js@1.9.10 → @nanostores_solid@1.1.1_nanostores@1.2.0_solid-js@1.9.10}/node_modules/@nanostores/solid/dist/index.js +2 -2
  11. package/dist/browser/client/node_modules/.pnpm/{@nanostores_solid@1.1.1_nanostores@1.1.0_solid-js@1.9.10 → @nanostores_solid@1.1.1_nanostores@1.2.0_solid-js@1.9.10}/node_modules/@nanostores/solid/dist/index.js.map +1 -1
  12. package/dist/browser/client/node_modules/.pnpm/{nanostores@1.1.0 → nanostores@1.2.0}/node_modules/nanostores/atom/index.js +2 -1
  13. package/dist/browser/client/node_modules/.pnpm/nanostores@1.2.0/node_modules/nanostores/atom/index.js.map +1 -0
  14. package/dist/browser/client/node_modules/.pnpm/nanostores@1.2.0/node_modules/nanostores/clean-stores/index.js +6 -0
  15. package/dist/browser/client/node_modules/.pnpm/{nanostores@1.1.0 → nanostores@1.2.0}/node_modules/nanostores/clean-stores/index.js.map +1 -1
  16. package/dist/browser/client/node_modules/.pnpm/{nanostores@1.1.0 → nanostores@1.2.0}/node_modules/nanostores/computed/index.js +8 -5
  17. package/dist/browser/client/node_modules/.pnpm/nanostores@1.2.0/node_modules/nanostores/computed/index.js.map +1 -0
  18. package/dist/browser/client/node_modules/.pnpm/{nanostores@1.1.0 → nanostores@1.2.0}/node_modules/nanostores/lifecycle/index.js +1 -1
  19. package/dist/browser/client/node_modules/.pnpm/{nanostores@1.1.0 → nanostores@1.2.0}/node_modules/nanostores/lifecycle/index.js.map +1 -1
  20. package/dist/browser/client/node_modules/.pnpm/{nanostores@1.1.0 → nanostores@1.2.0}/node_modules/nanostores/listen-keys/index.js +1 -1
  21. package/dist/browser/client/node_modules/.pnpm/{nanostores@1.1.0 → nanostores@1.2.0}/node_modules/nanostores/listen-keys/index.js.map +1 -1
  22. package/dist/browser/client/node_modules/.pnpm/{nanostores@1.1.0 → nanostores@1.2.0}/node_modules/nanostores/map/index.js +1 -1
  23. package/dist/browser/client/node_modules/.pnpm/{nanostores@1.1.0 → nanostores@1.2.0}/node_modules/nanostores/map/index.js.map +1 -1
  24. package/dist/browser/client/node_modules/.pnpm/{nanostores@1.1.0 → nanostores@1.2.0}/node_modules/nanostores/task/index.js +1 -1
  25. package/dist/browser/client/node_modules/.pnpm/{nanostores@1.1.0 → nanostores@1.2.0}/node_modules/nanostores/task/index.js.map +1 -1
  26. package/dist/browser/client/node_modules/.pnpm/nanostores@1.2.0/node_modules/nanostores/warn/index.js +16 -0
  27. package/dist/browser/client/node_modules/.pnpm/nanostores@1.2.0/node_modules/nanostores/warn/index.js.map +1 -0
  28. package/dist/browser/client/packages/fragment-upload/src/definition.js +1 -42
  29. package/dist/browser/client/packages/fragment-upload/src/definition.js.map +1 -1
  30. package/dist/browser/client/packages/fragment-upload/src/routes/files.js +12 -5
  31. package/dist/browser/client/packages/fragment-upload/src/routes/files.js.map +1 -1
  32. package/dist/browser/client/packages/fragment-upload/src/routes/shared.js +3 -4
  33. package/dist/browser/client/packages/fragment-upload/src/routes/shared.js.map +1 -1
  34. package/dist/browser/client/packages/fragment-upload/src/routes/uploads.js +32 -21
  35. package/dist/browser/client/packages/fragment-upload/src/routes/uploads.js.map +1 -1
  36. package/dist/browser/client/packages/fragment-upload/src/schema.js +33 -3
  37. package/dist/browser/client/packages/fragment-upload/src/schema.js.map +1 -1
  38. package/dist/browser/client/packages/fragment-upload/src/types.d.ts +1 -2
  39. package/dist/browser/client/packages/fragment-upload/src/types.d.ts.map +1 -1
  40. package/dist/browser/client/packages/fragno/dist/client/client.js +28 -12
  41. package/dist/browser/client/packages/fragno/dist/client/client.js.map +1 -1
  42. package/dist/browser/client/packages/fragno/dist/client/client.svelte.js +11 -3
  43. package/dist/browser/client/packages/fragno/dist/client/client.svelte.js.map +1 -1
  44. package/dist/browser/client/packages/fragno/dist/client/react.js +104 -12
  45. package/dist/browser/client/packages/fragno/dist/client/react.js.map +1 -1
  46. package/dist/browser/client/packages/fragno/dist/client/solid.js +25 -11
  47. package/dist/browser/client/packages/fragno/dist/client/solid.js.map +1 -1
  48. package/dist/browser/client/packages/fragno/dist/client/vanilla.js +21 -1
  49. package/dist/browser/client/packages/fragno/dist/client/vanilla.js.map +1 -1
  50. package/dist/browser/client/packages/fragno/dist/client/vue.js +19 -11
  51. package/dist/browser/client/packages/fragno/dist/client/vue.js.map +1 -1
  52. package/dist/browser/client/react.d.ts +215 -192
  53. package/dist/browser/client/react.d.ts.map +1 -1
  54. package/dist/browser/client/react.js.map +1 -1
  55. package/dist/browser/client/solid.d.ts +218 -196
  56. package/dist/browser/client/solid.d.ts.map +1 -1
  57. package/dist/browser/client/solid.js.map +1 -1
  58. package/dist/browser/client/svelte.d.ts +216 -193
  59. package/dist/browser/client/svelte.d.ts.map +1 -1
  60. package/dist/browser/client/svelte.js.map +1 -1
  61. package/dist/browser/client/vanilla.d.ts +217 -195
  62. package/dist/browser/client/vanilla.d.ts.map +1 -1
  63. package/dist/browser/client/vanilla.js.map +1 -1
  64. package/dist/browser/client/vue.d.ts +217 -194
  65. package/dist/browser/client/vue.d.ts.map +1 -1
  66. package/dist/browser/client/vue.js.map +1 -1
  67. package/dist/cli/commands/files/delete.d.ts +4 -4
  68. package/dist/cli/commands/files/delete.d.ts.map +1 -1
  69. package/dist/cli/commands/files/delete.js +8 -10
  70. package/dist/cli/commands/files/delete.js.map +1 -1
  71. package/dist/cli/commands/files/download-url.d.ts +4 -4
  72. package/dist/cli/commands/files/download-url.d.ts.map +1 -1
  73. package/dist/cli/commands/files/download-url.js +8 -10
  74. package/dist/cli/commands/files/download-url.js.map +1 -1
  75. package/dist/cli/commands/files/download.d.ts +4 -4
  76. package/dist/cli/commands/files/download.d.ts.map +1 -1
  77. package/dist/cli/commands/files/download.js +10 -12
  78. package/dist/cli/commands/files/download.js.map +1 -1
  79. package/dist/cli/commands/files/get.d.ts +4 -4
  80. package/dist/cli/commands/files/get.d.ts.map +1 -1
  81. package/dist/cli/commands/files/get.js +8 -10
  82. package/dist/cli/commands/files/get.js.map +1 -1
  83. package/dist/cli/commands/files/list.d.ts +4 -4
  84. package/dist/cli/commands/files/list.d.ts.map +1 -1
  85. package/dist/cli/commands/files/list.js +6 -8
  86. package/dist/cli/commands/files/list.js.map +1 -1
  87. package/dist/cli/commands/files/update.d.ts +4 -4
  88. package/dist/cli/commands/files/update.d.ts.map +1 -1
  89. package/dist/cli/commands/files/update.js +8 -10
  90. package/dist/cli/commands/files/update.js.map +1 -1
  91. package/dist/cli/commands/files/upload.d.ts +4 -4
  92. package/dist/cli/commands/files/upload.d.ts.map +1 -1
  93. package/dist/cli/commands/files/upload.js +10 -12
  94. package/dist/cli/commands/files/upload.js.map +1 -1
  95. package/dist/cli/commands/uploads/abort.d.ts +2 -2
  96. package/dist/cli/commands/uploads/abort.d.ts.map +1 -1
  97. package/dist/cli/commands/uploads/abort.js.map +1 -1
  98. package/dist/cli/commands/uploads/complete.d.ts +2 -2
  99. package/dist/cli/commands/uploads/complete.d.ts.map +1 -1
  100. package/dist/cli/commands/uploads/complete.js.map +1 -1
  101. package/dist/cli/commands/uploads/content.d.ts +2 -2
  102. package/dist/cli/commands/uploads/content.d.ts.map +1 -1
  103. package/dist/cli/commands/uploads/content.js +1 -1
  104. package/dist/cli/commands/uploads/content.js.map +1 -1
  105. package/dist/cli/commands/uploads/create.d.ts +4 -4
  106. package/dist/cli/commands/uploads/create.d.ts.map +1 -1
  107. package/dist/cli/commands/uploads/create.js +8 -11
  108. package/dist/cli/commands/uploads/create.js.map +1 -1
  109. package/dist/cli/commands/uploads/get.d.ts +2 -2
  110. package/dist/cli/commands/uploads/get.d.ts.map +1 -1
  111. package/dist/cli/commands/uploads/get.js.map +1 -1
  112. package/dist/cli/commands/uploads/parts-complete.d.ts +2 -2
  113. package/dist/cli/commands/uploads/parts-complete.d.ts.map +1 -1
  114. package/dist/cli/commands/uploads/parts-complete.js.map +1 -1
  115. package/dist/cli/commands/uploads/parts-list.d.ts +2 -2
  116. package/dist/cli/commands/uploads/parts-list.d.ts.map +1 -1
  117. package/dist/cli/commands/uploads/parts-list.js.map +1 -1
  118. package/dist/cli/commands/uploads/parts-urls.d.ts +2 -2
  119. package/dist/cli/commands/uploads/parts-urls.d.ts.map +1 -1
  120. package/dist/cli/commands/uploads/parts-urls.js.map +1 -1
  121. package/dist/cli/commands/uploads/progress.d.ts +2 -2
  122. package/dist/cli/commands/uploads/progress.d.ts.map +1 -1
  123. package/dist/cli/commands/uploads/progress.js.map +1 -1
  124. package/dist/cli/commands/uploads/transfer.d.ts +4 -4
  125. package/dist/cli/commands/uploads/transfer.d.ts.map +1 -1
  126. package/dist/cli/commands/uploads/transfer.js +9 -12
  127. package/dist/cli/commands/uploads/transfer.js.map +1 -1
  128. package/dist/cli/index.d.ts +13 -13
  129. package/dist/cli/index.d.ts.map +1 -1
  130. package/dist/cli/index.js +14 -14
  131. package/dist/cli/index.js.map +1 -1
  132. package/dist/cli/utils/client.js +22 -5
  133. package/dist/cli/utils/client.js.map +1 -1
  134. package/dist/cli/utils/options.js +7 -43
  135. package/dist/cli/utils/options.js.map +1 -1
  136. package/dist/node/cli/commands/files/delete.d.ts +4 -4
  137. package/dist/node/cli/commands/files/delete.d.ts.map +1 -1
  138. package/dist/node/cli/commands/files/delete.js +8 -10
  139. package/dist/node/cli/commands/files/delete.js.map +1 -1
  140. package/dist/node/cli/commands/files/download-url.d.ts +4 -4
  141. package/dist/node/cli/commands/files/download-url.d.ts.map +1 -1
  142. package/dist/node/cli/commands/files/download-url.js +8 -10
  143. package/dist/node/cli/commands/files/download-url.js.map +1 -1
  144. package/dist/node/cli/commands/files/download.d.ts +4 -4
  145. package/dist/node/cli/commands/files/download.d.ts.map +1 -1
  146. package/dist/node/cli/commands/files/download.js +9 -11
  147. package/dist/node/cli/commands/files/download.js.map +1 -1
  148. package/dist/node/cli/commands/files/get.d.ts +4 -4
  149. package/dist/node/cli/commands/files/get.d.ts.map +1 -1
  150. package/dist/node/cli/commands/files/get.js +8 -10
  151. package/dist/node/cli/commands/files/get.js.map +1 -1
  152. package/dist/node/cli/commands/files/list.d.ts +4 -4
  153. package/dist/node/cli/commands/files/list.d.ts.map +1 -1
  154. package/dist/node/cli/commands/files/list.js +6 -8
  155. package/dist/node/cli/commands/files/list.js.map +1 -1
  156. package/dist/node/cli/commands/files/update.d.ts +4 -4
  157. package/dist/node/cli/commands/files/update.d.ts.map +1 -1
  158. package/dist/node/cli/commands/files/update.js +8 -10
  159. package/dist/node/cli/commands/files/update.js.map +1 -1
  160. package/dist/node/cli/commands/files/upload.d.ts +4 -4
  161. package/dist/node/cli/commands/files/upload.d.ts.map +1 -1
  162. package/dist/node/cli/commands/files/upload.js +9 -11
  163. package/dist/node/cli/commands/files/upload.js.map +1 -1
  164. package/dist/node/cli/commands/uploads/abort.d.ts +2 -2
  165. package/dist/node/cli/commands/uploads/abort.d.ts.map +1 -1
  166. package/dist/node/cli/commands/uploads/abort.js.map +1 -1
  167. package/dist/node/cli/commands/uploads/complete.d.ts +2 -2
  168. package/dist/node/cli/commands/uploads/complete.d.ts.map +1 -1
  169. package/dist/node/cli/commands/uploads/complete.js.map +1 -1
  170. package/dist/node/cli/commands/uploads/content.d.ts +2 -2
  171. package/dist/node/cli/commands/uploads/content.d.ts.map +1 -1
  172. package/dist/node/cli/commands/uploads/content.js.map +1 -1
  173. package/dist/node/cli/commands/uploads/create.d.ts +4 -4
  174. package/dist/node/cli/commands/uploads/create.d.ts.map +1 -1
  175. package/dist/node/cli/commands/uploads/create.js +8 -11
  176. package/dist/node/cli/commands/uploads/create.js.map +1 -1
  177. package/dist/node/cli/commands/uploads/get.d.ts +2 -2
  178. package/dist/node/cli/commands/uploads/get.d.ts.map +1 -1
  179. package/dist/node/cli/commands/uploads/get.js.map +1 -1
  180. package/dist/node/cli/commands/uploads/parts-complete.d.ts +2 -2
  181. package/dist/node/cli/commands/uploads/parts-complete.d.ts.map +1 -1
  182. package/dist/node/cli/commands/uploads/parts-complete.js.map +1 -1
  183. package/dist/node/cli/commands/uploads/parts-list.d.ts +2 -2
  184. package/dist/node/cli/commands/uploads/parts-list.d.ts.map +1 -1
  185. package/dist/node/cli/commands/uploads/parts-list.js.map +1 -1
  186. package/dist/node/cli/commands/uploads/parts-urls.d.ts +2 -2
  187. package/dist/node/cli/commands/uploads/parts-urls.d.ts.map +1 -1
  188. package/dist/node/cli/commands/uploads/parts-urls.js.map +1 -1
  189. package/dist/node/cli/commands/uploads/progress.d.ts +2 -2
  190. package/dist/node/cli/commands/uploads/progress.d.ts.map +1 -1
  191. package/dist/node/cli/commands/uploads/progress.js.map +1 -1
  192. package/dist/node/cli/commands/uploads/transfer.d.ts +4 -4
  193. package/dist/node/cli/commands/uploads/transfer.d.ts.map +1 -1
  194. package/dist/node/cli/commands/uploads/transfer.js +8 -11
  195. package/dist/node/cli/commands/uploads/transfer.js.map +1 -1
  196. package/dist/node/cli/index.d.ts +13 -13
  197. package/dist/node/cli/index.d.ts.map +1 -1
  198. package/dist/node/cli/index.js +14 -14
  199. package/dist/node/cli/index.js.map +1 -1
  200. package/dist/node/cli/utils/client.js +22 -5
  201. package/dist/node/cli/utils/client.js.map +1 -1
  202. package/dist/node/cli/utils/options.js +7 -43
  203. package/dist/node/cli/utils/options.js.map +1 -1
  204. package/dist/node/client/clients.d.ts +217 -194
  205. package/dist/node/client/clients.d.ts.map +1 -1
  206. package/dist/node/client/clients.js +17 -9
  207. package/dist/node/client/clients.js.map +1 -1
  208. package/dist/node/client/helpers.d.ts +15 -6
  209. package/dist/node/client/helpers.d.ts.map +1 -1
  210. package/dist/node/client/helpers.js +176 -30
  211. package/dist/node/client/helpers.js.map +1 -1
  212. package/dist/node/client/react.d.ts +217 -194
  213. package/dist/node/client/react.d.ts.map +1 -1
  214. package/dist/node/client/react.js.map +1 -1
  215. package/dist/node/client/solid.d.ts +218 -196
  216. package/dist/node/client/solid.d.ts.map +1 -1
  217. package/dist/node/client/solid.js.map +1 -1
  218. package/dist/node/client/svelte.d.ts +216 -193
  219. package/dist/node/client/svelte.d.ts.map +1 -1
  220. package/dist/node/client/svelte.js.map +1 -1
  221. package/dist/node/client/vanilla.d.ts +217 -195
  222. package/dist/node/client/vanilla.d.ts.map +1 -1
  223. package/dist/node/client/vanilla.js.map +1 -1
  224. package/dist/node/client/vue.d.ts +217 -194
  225. package/dist/node/client/vue.d.ts.map +1 -1
  226. package/dist/node/client/vue.js.map +1 -1
  227. package/dist/node/config.d.ts +6 -6
  228. package/dist/node/config.d.ts.map +1 -1
  229. package/dist/node/config.js.map +1 -1
  230. package/dist/node/definition.d.ts +588 -219
  231. package/dist/node/definition.d.ts.map +1 -1
  232. package/dist/node/definition.js +27 -3
  233. package/dist/node/definition.js.map +1 -1
  234. package/dist/node/file-key.d.ts +19 -0
  235. package/dist/node/file-key.d.ts.map +1 -0
  236. package/dist/node/file-key.js +47 -0
  237. package/dist/node/file-key.js.map +1 -0
  238. package/dist/node/index.d.ts +582 -175
  239. package/dist/node/index.d.ts.map +1 -1
  240. package/dist/node/index.js +3 -2
  241. package/dist/node/index.js.map +1 -1
  242. package/dist/node/routes/files.js +99 -64
  243. package/dist/node/routes/files.js.map +1 -1
  244. package/dist/node/routes/index.d.ts +1497 -721
  245. package/dist/node/routes/index.d.ts.map +1 -1
  246. package/dist/node/routes/shared.js +5 -9
  247. package/dist/node/routes/shared.js.map +1 -1
  248. package/dist/node/routes/uploads.js +105 -47
  249. package/dist/node/routes/uploads.js.map +1 -1
  250. package/dist/node/schema.d.ts +6 -6
  251. package/dist/node/schema.d.ts.map +1 -1
  252. package/dist/node/schema.js +12 -3
  253. package/dist/node/schema.js.map +1 -1
  254. package/dist/node/services/files.d.ts +6 -2
  255. package/dist/node/services/files.d.ts.map +1 -1
  256. package/dist/node/services/files.js +22 -20
  257. package/dist/node/services/files.js.map +1 -1
  258. package/dist/node/services/helpers.js +37 -15
  259. package/dist/node/services/helpers.js.map +1 -1
  260. package/dist/node/services/uploads.d.ts +10 -5
  261. package/dist/node/services/uploads.d.ts.map +1 -1
  262. package/dist/node/services/uploads.js +340 -63
  263. package/dist/node/services/uploads.js.map +1 -1
  264. package/dist/node/storage/fs.d.ts.map +1 -1
  265. package/dist/node/storage/fs.js +16 -10
  266. package/dist/node/storage/fs.js.map +1 -1
  267. package/dist/node/storage/object-key.js +36 -0
  268. package/dist/node/storage/object-key.js.map +1 -0
  269. package/dist/node/storage/r2-binding.d.ts +59 -0
  270. package/dist/node/storage/r2-binding.d.ts.map +1 -0
  271. package/dist/node/storage/r2-binding.js +245 -0
  272. package/dist/node/storage/r2-binding.js.map +1 -0
  273. package/dist/node/storage/r2.d.ts +6 -5
  274. package/dist/node/storage/r2.d.ts.map +1 -1
  275. package/dist/node/storage/s3.d.ts.map +1 -1
  276. package/dist/node/storage/s3.js +16 -10
  277. package/dist/node/storage/s3.js.map +1 -1
  278. package/dist/node/storage/types.d.ts +6 -5
  279. package/dist/node/storage/types.d.ts.map +1 -1
  280. package/dist/node/types.d.ts +1 -2
  281. package/dist/node/types.d.ts.map +1 -1
  282. package/package.json +26 -46
  283. package/dist/browser/client/node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/atom/index.js.map +0 -1
  284. package/dist/browser/client/node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/clean-stores/index.js +0 -6
  285. package/dist/browser/client/node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/computed/index.js.map +0 -1
  286. package/dist/browser/client/packages/fragment-upload/src/keys.d.ts +0 -7
  287. package/dist/browser/client/packages/fragment-upload/src/keys.d.ts.map +0 -1
  288. package/dist/browser/client/packages/fragment-upload/src/keys.js +0 -28
  289. package/dist/browser/client/packages/fragment-upload/src/keys.js.map +0 -1
  290. package/dist/browser/index-BdjKPO4J.d.ts +0 -177
  291. package/dist/browser/index-BdjKPO4J.d.ts.map +0 -1
  292. package/dist/browser/index.js +0 -3
  293. package/dist/browser/src-vdNJUbjT.js +0 -1982
  294. package/dist/browser/src-vdNJUbjT.js.map +0 -1
  295. package/dist/cli/keys.js +0 -32
  296. package/dist/cli/keys.js.map +0 -1
  297. package/dist/node/keys.d.ts +0 -12
  298. package/dist/node/keys.d.ts.map +0 -1
  299. package/dist/node/keys.js +0 -63
  300. package/dist/node/keys.js.map +0 -1
  301. package/dist/tsconfig.tsbuildinfo +0 -1
package/README.md CHANGED
@@ -1,6 +1,148 @@
1
- # Fragno Fragment
1
+ # Upload Fragment
2
2
 
3
- You've created a new [Fragno](https://fragno.dev/) fragment!
3
+ Full-stack upload fragment for Fragno. It supports direct-to-storage uploads (signed URLs), proxy
4
+ uploads, and one-shot multipart form uploads. Strategy selection is driven by the storage adapter in
5
+ `storage.initUpload` and the configured thresholds.
6
+
7
+ ## Upload Inventory
8
+
9
+ Routes below are relative to the fragment mount (for example, `/api/uploads`). Upload session
10
+ responses include the resolved `provider` plus canonical provider-sticky follow-up URLs:
11
+ `statusEndpoint`, `progressEndpoint`, `completeEndpoint`, `abortEndpoint`, and the strategy-specific
12
+ `partsEndpoint`, `partsCompleteEndpoint`, or `contentEndpoint` when applicable.
13
+
14
+ **Direct single (signed URL to storage)** Steps:
15
+
16
+ 1. `POST /uploads` returns `uploadUrl`, `uploadHeaders`, and canonical follow-up URLs. Writes:
17
+ `upload`.
18
+ 2. Client `PUT` to the signed URL. Writes: none.
19
+ 3. `POST /uploads/:uploadId/complete`. Writes: `upload`, `file`.
20
+ 4. Optional `POST /uploads/:uploadId/progress`. Writes: `upload`.
21
+
22
+ Storage ops: `initUpload` (before DB availability checks), optional `finalizeUpload` (before
23
+ completion writes). DB ops: after `initUpload`, read `file` + `upload` (availability), write
24
+ `upload`, then mark complete + insert `file`.
25
+
26
+ **Direct multipart (signed part URLs)** Steps:
27
+
28
+ 1. `POST /uploads` creates the upload and returns canonical follow-up URLs. Writes: `upload`.
29
+ 2. `POST /uploads/:uploadId/parts` returns signed part URLs. Writes: none.
30
+ 3. Client `PUT` to part URLs. Writes: none.
31
+ 4. `POST /uploads/:uploadId/parts/complete`. Writes: `upload_part`, `upload`.
32
+ 5. `POST /uploads/:uploadId/complete`. Writes: `upload`, `file`.
33
+
34
+ Storage ops: `initUpload` (before DB availability checks), `getPartUploadUrls`,
35
+ `completeMultipartUpload`. DB ops: after `initUpload`, read `file` + `upload` (availability) and
36
+ write `upload`; later read `upload`, write `upload_part` rows + progress, then mark complete +
37
+ insert `file`.
38
+
39
+ **Proxy stream (upload session)** Steps:
40
+
41
+ 1. `POST /uploads` creates the upload and returns canonical follow-up URLs. Writes: `upload`.
42
+ 2. `PUT /uploads/:uploadId/content` streams content through the fragment. Writes: `upload`, `file`.
43
+
44
+ Storage ops: `initUpload` (before DB availability checks), `writeStream`. DB ops: after
45
+ `initUpload`, read `file` + `upload` (availability) and write `upload`; then mark complete + insert
46
+ `file` (or mark failed on storage error).
47
+
48
+ **One-shot multipart form (server mediated)** Steps:
49
+
50
+ 1. `POST /files` with `multipart/form-data`. Writes: `upload`, `file`.
51
+ 2. Server chooses proxy stream or server-side `PUT` to `uploadUrl`. Writes: none.
52
+ 3. Upload finalizes on success. Writes: none.
53
+
54
+ Storage ops: `initUpload`, `writeStream` OR server `PUT` to `uploadUrl`, optional `finalizeUpload`
55
+ (before DB writes). DB ops: after storage succeeds, read availability, then create completed
56
+ `upload` + `file` (or failed `upload` on error).
57
+
58
+ Note: If `initUpload` returns `direct-multipart`, this route responds with
59
+ `409 UPLOAD_INVALID_STATE` and you should use the multipart session flow instead.
60
+
61
+ **Helpers (status, progress, abort)** Steps:
62
+
63
+ 1. `GET /uploads/:uploadId` fetches status. Writes: none.
64
+ 2. `POST /uploads/:uploadId/progress` records progress. Writes: `upload`.
65
+ 3. `POST /uploads/:uploadId/abort` cancels the upload. Writes: `upload`.
66
+
67
+ Helper behavior note:
68
+
69
+ - Official helpers use the returned canonical follow-up URLs instead of reconstructing
70
+ `/uploads/:uploadId/...` paths locally.
71
+ - For proxy uploads (`PUT /uploads/:uploadId/content`), helpers try streamed upload first and then a
72
+ buffered fallback.
73
+ - If both attempts fail at the transport layer, helpers throw an actionable error suggesting
74
+ `POST /files` (one-shot multipart) or switching to a direct strategy.
75
+
76
+ ## Upload Flow Diagram
77
+
78
+ ```mermaid
79
+ flowchart TD
80
+ C[Client]
81
+ F[Upload Fragment]
82
+ S[(Storage Adapter)]
83
+ D[(DB)]
84
+
85
+ C -->|"POST /uploads"| F
86
+ F -->|"storage.initUpload"| S
87
+ F -->|"DB read: availability"| D
88
+ F -->|"DB write: create upload"| D
89
+
90
+ C -->|"PUT uploadUrl (direct single)"| S
91
+ C -->|"POST /uploads/:uploadId/complete"| F
92
+ F -->|"DB read: upload"| D
93
+ F -->|"storage.finalizeUpload (optional)"| S
94
+ F -->|"DB write: complete upload + file"| D
95
+
96
+ C -->|"POST /uploads/:uploadId/parts"| F
97
+ F -->|"DB read: upload"| D
98
+ F -->|"storage.getPartUploadUrls"| S
99
+ C -->|"PUT part URLs"| S
100
+ C -->|"POST /uploads/:uploadId/parts/complete"| F
101
+ F -->|"DB write: upload_part + progress"| D
102
+ C -->|"POST /uploads/:uploadId/complete"| F
103
+ F -->|"storage.completeMultipartUpload"| S
104
+ F -->|"DB write: complete upload + file"| D
105
+
106
+ C -->|"PUT /uploads/:uploadId/content (proxy)"| F
107
+ F -->|"storage.writeStream"| S
108
+ F -->|"DB write: complete upload + file"| D
109
+
110
+ C -->|"POST /files (multipart form)"| F
111
+ F -->|"storage.initUpload"| S
112
+ F -->|"storage.writeStream OR server PUT uploadUrl"| S
113
+ F -->|"storage.finalizeUpload (optional)"| S
114
+ F -->|"DB read: availability"| D
115
+ F -->|"DB write: completed upload + file"| D
116
+ ```
117
+
118
+ ## Durable Hooks and DB Entities
119
+
120
+ Durable hooks are persisted to the internal `fragno_hooks` table when `uow.triggerHook(...)` is
121
+ called inside a DB transaction. Hook execution is out-of-band and may re-read or update upload
122
+ entities.
123
+
124
+ **onUploadTimeout (scheduled when `POST /uploads` creates a new upload)** Steps:
125
+
126
+ 1. Writes: `fragno_hooks` (process time = `upload.expiresAt`, payload includes `uploadId`).
127
+ 2. When executed, reads: `upload`. If still active and expired, writes: `upload` (status `expired`,
128
+ error fields, timestamps).
129
+ 3. Invokes `config.onUploadFailed` (no DB writes from the hook itself).
130
+
131
+ **onFileReady (after `upload` completes + `file` is created)** Steps:
132
+
133
+ 1. Writes: `fragno_hooks`.
134
+ 2. Hook execution is notification-only; no DB writes.
135
+
136
+ **onUploadFailed (after upload failure or abort)** Steps:
137
+
138
+ 1. Writes: `fragno_hooks`.
139
+ 2. Hook execution is notification-only; no DB writes.
140
+
141
+ **onFileDeleted (after `file` is marked deleted)** Steps:
142
+
143
+ 1. Writes: `fragno_hooks`.
144
+ 2. Hook execution deletes the storage object via `storage.deleteObject`.
145
+ 3. Invokes `config.onFileDeleted` (the storage delete is retried durably first).
4
146
 
5
147
  ## Build
6
148
 
@@ -22,10 +164,10 @@ node packages/fragment-upload/bin/run.js --help
22
164
  ```bash
23
165
  # Use the CLI (base URL points to the mounted upload fragment)
24
166
  fragno-upload --help
25
- fragno-upload uploads create -b https://host.example.com/api/uploads --file-key s~Zm9v --filename demo.txt --size-bytes 10 --content-type text/plain
26
- fragno-upload uploads transfer -b https://host.example.com/api/uploads -f ./demo.txt --file-key s~Zm9v
27
- fragno-upload files list -b https://host.example.com/api/uploads --prefix s~Zm9v.
28
- fragno-upload files download -b https://host.example.com/api/uploads --file-key s~Zm9v -o ./download.txt
167
+ fragno-upload uploads create -b https://host.example.com/api/uploads --provider r2-binding --file-key users/42/avatar --filename demo.txt --size-bytes 10 --content-type text/plain
168
+ fragno-upload uploads transfer -b https://host.example.com/api/uploads -f ./demo.txt --provider r2-binding --file-key users/42/avatar
169
+ fragno-upload files list -b https://host.example.com/api/uploads --provider r2-binding --prefix users/42/
170
+ fragno-upload files download -b https://host.example.com/api/uploads --provider r2-binding --file-key users/42/avatar -o ./download.txt
29
171
  ```
30
172
 
31
173
  Environment defaults:
@@ -35,9 +177,3 @@ Environment defaults:
35
177
  - `FRAGNO_UPLOAD_TIMEOUT_MS`
36
178
  - `FRAGNO_UPLOAD_RETRIES`
37
179
  - `FRAGNO_UPLOAD_RETRY_DELAY_MS`
38
-
39
- ## Next Steps
40
-
41
- - Define your routes in `src/index.ts`
42
- - Add framework-specific clients in `src/client/`
43
- - See `AGENTS.md` for detailed development patterns
@@ -14,7 +14,7 @@ function createUploadFragmentClients(config = {}) {
14
14
  });
15
15
  return {
16
16
  useFiles: builder.createHook("/files"),
17
- useFile: builder.createHook("/files/:fileKey"),
17
+ useFile: builder.createHook("/files/by-key"),
18
18
  useCreateUpload: builder.createMutator("POST", "/uploads"),
19
19
  useUploadStatus: builder.createHook("/uploads/:uploadId"),
20
20
  useCompleteUpload: builder.createMutator("POST", "/uploads/:uploadId/complete", (invalidate, params) => {
@@ -28,16 +28,24 @@ function createUploadFragmentClients(config = {}) {
28
28
  if (!uploadId) return;
29
29
  invalidate("GET", "/uploads/:uploadId", { pathParams: { uploadId } });
30
30
  }),
31
- useUpdateFile: builder.createMutator("PATCH", "/files/:fileKey", (invalidate, params) => {
32
- const fileKey = params.pathParams.fileKey;
33
- if (!fileKey) return;
34
- invalidate("GET", "/files/:fileKey", { pathParams: { fileKey } });
31
+ useUpdateFile: builder.createMutator("PATCH", "/files/by-key", (invalidate, params) => {
32
+ const provider = params.queryParams?.["provider"];
33
+ const key = params.queryParams?.["key"];
34
+ if (!provider || !key) return;
35
+ invalidate("GET", "/files/by-key", { queryParams: {
36
+ provider,
37
+ key
38
+ } });
35
39
  invalidate("GET", "/files", {});
36
40
  }),
37
- useDeleteFile: builder.createMutator("DELETE", "/files/:fileKey", (invalidate, params) => {
38
- const fileKey = params.pathParams.fileKey;
39
- if (!fileKey) return;
40
- invalidate("GET", "/files/:fileKey", { pathParams: { fileKey } });
41
+ useDeleteFile: builder.createMutator("DELETE", "/files/by-key", (invalidate, params) => {
42
+ const provider = params.queryParams?.["provider"];
43
+ const key = params.queryParams?.["key"];
44
+ if (!provider || !key) return;
45
+ invalidate("GET", "/files/by-key", { queryParams: {
46
+ provider,
47
+ key
48
+ } });
41
49
  invalidate("GET", "/files", {});
42
50
  }),
43
51
  useUploadHelpers: builder.createStore(helpers)
@@ -1 +1 @@
1
- {"version":3,"file":"clients.js","names":["createClientBuilder","FragnoPublicClientConfig","uploadFragmentDefinition","uploadRoutes","createUploadHelpers","createUploadFragmentClients","config","builder","fetcher","defaultOptions","getFetcher","helpers","buildUrl","path","useFiles","createHook","useFile","useCreateUpload","createMutator","useUploadStatus","useCompleteUpload","invalidate","params","uploadId","pathParams","useAbortUpload","useUpdateFile","fileKey","useDeleteFile","useUploadHelpers","createStore"],"sources":["../../../src/client/clients.ts"],"sourcesContent":["import { createClientBuilder } from \"@fragno-dev/core/client\";\nimport type { FragnoPublicClientConfig } from \"@fragno-dev/core/client\";\nimport { uploadFragmentDefinition } from \"../definition\";\nimport { uploadRoutes } from \"../routes\";\nimport { createUploadHelpers } from \"./helpers\";\n\nexport function createUploadFragmentClients(config: FragnoPublicClientConfig = {}) {\n const builder = createClientBuilder(uploadFragmentDefinition, config, uploadRoutes);\n const { fetcher, defaultOptions } = builder.getFetcher();\n const helpers = createUploadHelpers({\n buildUrl: (path) => builder.buildUrl(path),\n fetcher,\n defaultOptions,\n });\n\n return {\n useFiles: builder.createHook(\"/files\"),\n useFile: builder.createHook(\"/files/:fileKey\"),\n useCreateUpload: builder.createMutator(\"POST\", \"/uploads\"),\n useUploadStatus: builder.createHook(\"/uploads/:uploadId\"),\n useCompleteUpload: builder.createMutator(\n \"POST\",\n \"/uploads/:uploadId/complete\",\n (invalidate, params) => {\n const uploadId = params.pathParams.uploadId;\n if (!uploadId) {\n return;\n }\n invalidate(\"GET\", \"/uploads/:uploadId\", { pathParams: { uploadId } });\n invalidate(\"GET\", \"/files\", {});\n },\n ),\n useAbortUpload: builder.createMutator(\n \"POST\",\n \"/uploads/:uploadId/abort\",\n (invalidate, params) => {\n const uploadId = params.pathParams.uploadId;\n if (!uploadId) {\n return;\n }\n invalidate(\"GET\", \"/uploads/:uploadId\", { pathParams: { uploadId } });\n },\n ),\n useUpdateFile: builder.createMutator(\"PATCH\", \"/files/:fileKey\", (invalidate, params) => {\n const fileKey = params.pathParams.fileKey;\n if (!fileKey) {\n return;\n }\n invalidate(\"GET\", \"/files/:fileKey\", { pathParams: { fileKey } });\n invalidate(\"GET\", \"/files\", {});\n }),\n useDeleteFile: builder.createMutator(\"DELETE\", \"/files/:fileKey\", (invalidate, params) => {\n const fileKey = params.pathParams.fileKey;\n if (!fileKey) {\n return;\n }\n invalidate(\"GET\", \"/files/:fileKey\", { pathParams: { fileKey } });\n invalidate(\"GET\", \"/files\", {});\n }),\n useUploadHelpers: builder.createStore(helpers),\n };\n}\n"],"mappings":";;;;;;AAMA,SAAgBK,4BAA4BC,SAAmC,EAAE,EAAE;CACjF,MAAMC,UAAUP,oBAAoBE,0BAA0BI,QAAQH,aAAa;CACnF,MAAM,EAAEK,SAASC,mBAAmBF,QAAQG,YAAY;CACxD,MAAMC,UAAUP,oBAAoB;EAClCQ,WAAWC,SAASN,QAAQK,SAASC,KAAK;EAC1CL;EACAC;EACD,CAAC;AAEF,QAAO;EACLK,UAAUP,QAAQQ,WAAW,SAAS;EACtCC,SAAST,QAAQQ,WAAW,kBAAkB;EAC9CE,iBAAiBV,QAAQW,cAAc,QAAQ,WAAW;EAC1DC,iBAAiBZ,QAAQQ,WAAW,qBAAqB;EACzDK,mBAAmBb,QAAQW,cACzB,QACA,gCACCG,YAAYC,WAAW;GACtB,MAAMC,WAAWD,OAAOE,WAAWD;AACnC,OAAI,CAACA,SACH;AAEFF,cAAW,OAAO,sBAAsB,EAAEG,YAAY,EAAED,UAAS,EAAG,CAAC;AACrEF,cAAW,OAAO,UAAU,EAAE,CAAC;IAElC;EACDI,gBAAgBlB,QAAQW,cACtB,QACA,6BACCG,YAAYC,WAAW;GACtB,MAAMC,WAAWD,OAAOE,WAAWD;AACnC,OAAI,CAACA,SACH;AAEFF,cAAW,OAAO,sBAAsB,EAAEG,YAAY,EAAED,UAAS,EAAG,CAAC;IAExE;EACDG,eAAenB,QAAQW,cAAc,SAAS,oBAAoBG,YAAYC,WAAW;GACvF,MAAMK,UAAUL,OAAOE,WAAWG;AAClC,OAAI,CAACA,QACH;AAEFN,cAAW,OAAO,mBAAmB,EAAEG,YAAY,EAAEG,SAAQ,EAAG,CAAC;AACjEN,cAAW,OAAO,UAAU,EAAE,CAAC;IAC/B;EACFO,eAAerB,QAAQW,cAAc,UAAU,oBAAoBG,YAAYC,WAAW;GACxF,MAAMK,UAAUL,OAAOE,WAAWG;AAClC,OAAI,CAACA,QACH;AAEFN,cAAW,OAAO,mBAAmB,EAAEG,YAAY,EAAEG,SAAQ,EAAG,CAAC;AACjEN,cAAW,OAAO,UAAU,EAAE,CAAC;IAC/B;EACFQ,kBAAkBtB,QAAQuB,YAAYnB,QAAO;EAC9C"}
1
+ {"version":3,"file":"clients.js","names":["createClientBuilder","FragnoPublicClientConfig","uploadFragmentDefinition","uploadRoutes","createUploadHelpers","createUploadFragmentClients","config","builder","fetcher","defaultOptions","getFetcher","helpers","buildUrl","path","useFiles","createHook","useFile","useCreateUpload","createMutator","useUploadStatus","useCompleteUpload","invalidate","params","uploadId","pathParams","useAbortUpload","useUpdateFile","provider","queryParams","key","useDeleteFile","useUploadHelpers","createStore"],"sources":["../../../src/client/clients.ts"],"sourcesContent":["import { createClientBuilder } from \"@fragno-dev/core/client\";\nimport type { FragnoPublicClientConfig } from \"@fragno-dev/core/client\";\n\nimport { uploadFragmentDefinition } from \"../definition\";\nimport { uploadRoutes } from \"../routes\";\nimport { createUploadHelpers } from \"./helpers\";\n\nexport function createUploadFragmentClients(config: FragnoPublicClientConfig = {}) {\n const builder = createClientBuilder(uploadFragmentDefinition, config, uploadRoutes);\n const { fetcher, defaultOptions } = builder.getFetcher();\n const helpers = createUploadHelpers({\n buildUrl: (path) => builder.buildUrl(path),\n fetcher,\n defaultOptions,\n });\n\n return {\n useFiles: builder.createHook(\"/files\"),\n useFile: builder.createHook(\"/files/by-key\"),\n useCreateUpload: builder.createMutator(\"POST\", \"/uploads\"),\n useUploadStatus: builder.createHook(\"/uploads/:uploadId\"),\n useCompleteUpload: builder.createMutator(\n \"POST\",\n \"/uploads/:uploadId/complete\",\n (invalidate, params) => {\n const uploadId = params.pathParams.uploadId;\n if (!uploadId) {\n return;\n }\n invalidate(\"GET\", \"/uploads/:uploadId\", { pathParams: { uploadId } });\n invalidate(\"GET\", \"/files\", {});\n },\n ),\n useAbortUpload: builder.createMutator(\n \"POST\",\n \"/uploads/:uploadId/abort\",\n (invalidate, params) => {\n const uploadId = params.pathParams.uploadId;\n if (!uploadId) {\n return;\n }\n invalidate(\"GET\", \"/uploads/:uploadId\", { pathParams: { uploadId } });\n },\n ),\n useUpdateFile: builder.createMutator(\"PATCH\", \"/files/by-key\", (invalidate, params) => {\n const provider = params.queryParams?.[\"provider\"];\n const key = params.queryParams?.[\"key\"];\n if (!provider || !key) {\n return;\n }\n invalidate(\"GET\", \"/files/by-key\", { queryParams: { provider, key } });\n invalidate(\"GET\", \"/files\", {});\n }),\n useDeleteFile: builder.createMutator(\"DELETE\", \"/files/by-key\", (invalidate, params) => {\n const provider = params.queryParams?.[\"provider\"];\n const key = params.queryParams?.[\"key\"];\n if (!provider || !key) {\n return;\n }\n invalidate(\"GET\", \"/files/by-key\", { queryParams: { provider, key } });\n invalidate(\"GET\", \"/files\", {});\n }),\n useUploadHelpers: builder.createStore(helpers),\n };\n}\n"],"mappings":";;;;;;AAOA,SAAgBK,4BAA4BC,SAAmC,EAAE,EAAE;CACjF,MAAMC,UAAUP,oBAAoBE,0BAA0BI,QAAQH,aAAa;CACnF,MAAM,EAAEK,SAASC,mBAAmBF,QAAQG,YAAY;CACxD,MAAMC,UAAUP,oBAAoB;EAClCQ,WAAWC,SAASN,QAAQK,SAASC,KAAK;EAC1CL;EACAC;EACD,CAAC;AAEF,QAAO;EACLK,UAAUP,QAAQQ,WAAW,SAAS;EACtCC,SAAST,QAAQQ,WAAW,gBAAgB;EAC5CE,iBAAiBV,QAAQW,cAAc,QAAQ,WAAW;EAC1DC,iBAAiBZ,QAAQQ,WAAW,qBAAqB;EACzDK,mBAAmBb,QAAQW,cACzB,QACA,gCACCG,YAAYC,WAAW;GACtB,MAAMC,WAAWD,OAAOE,WAAWD;AACnC,OAAI,CAACA,SACH;AAEFF,cAAW,OAAO,sBAAsB,EAAEG,YAAY,EAAED,UAAS,EAAG,CAAC;AACrEF,cAAW,OAAO,UAAU,EAAE,CAAC;IAElC;EACDI,gBAAgBlB,QAAQW,cACtB,QACA,6BACCG,YAAYC,WAAW;GACtB,MAAMC,WAAWD,OAAOE,WAAWD;AACnC,OAAI,CAACA,SACH;AAEFF,cAAW,OAAO,sBAAsB,EAAEG,YAAY,EAAED,UAAS,EAAG,CAAC;IAExE;EACDG,eAAenB,QAAQW,cAAc,SAAS,kBAAkBG,YAAYC,WAAW;GACrF,MAAMK,WAAWL,OAAOM,cAAc;GACtC,MAAMC,MAAMP,OAAOM,cAAc;AACjC,OAAI,CAACD,YAAY,CAACE,IAChB;AAEFR,cAAW,OAAO,iBAAiB,EAAEO,aAAa;IAAED;IAAUE;IAAI,EAAG,CAAC;AACtER,cAAW,OAAO,UAAU,EAAE,CAAC;IAC/B;EACFS,eAAevB,QAAQW,cAAc,UAAU,kBAAkBG,YAAYC,WAAW;GACtF,MAAMK,WAAWL,OAAOM,cAAc;GACtC,MAAMC,MAAMP,OAAOM,cAAc;AACjC,OAAI,CAACD,YAAY,CAACE,IAChB;AAEFR,cAAW,OAAO,iBAAiB,EAAEO,aAAa;IAAED;IAAUE;IAAI,EAAG,CAAC;AACtER,cAAW,OAAO,UAAU,EAAE,CAAC;IAC/B;EACFU,kBAAkBxB,QAAQyB,YAAYrB,QAAO;EAC9C"}
@@ -1,4 +1,3 @@
1
- import { FileKeyEncoded, FileKeyParts } from "./packages/fragment-upload/src/keys.js";
2
1
  import { UploadChecksum } from "./packages/fragment-upload/src/storage/types.js";
3
2
  import { FileMetadata, FileVisibility, UploadStrategy } from "./packages/fragment-upload/src/types.js";
4
3
 
@@ -10,8 +9,8 @@ type UploadProgress = {
10
9
  totalParts?: number;
11
10
  };
12
11
  type CreateUploadAndTransferOptions = {
13
- keyParts?: FileKeyParts;
14
- fileKey?: FileKeyEncoded;
12
+ provider: string;
13
+ fileKey: string;
15
14
  filename?: string;
16
15
  contentType?: string;
17
16
  checksum?: UploadChecksum | null;
@@ -21,10 +20,16 @@ type CreateUploadAndTransferOptions = {
21
20
  metadata?: Record<string, unknown>;
22
21
  onProgress?: (progress: UploadProgress) => void;
23
22
  };
23
+ type DownloadMethod = "signed-url" | "content";
24
+ type DownloadFileOptions = {
25
+ provider: string;
26
+ method: DownloadMethod;
27
+ };
24
28
  type UploadCreateResponse = {
25
29
  uploadId: string;
26
30
  fileKey: string;
27
- status: "created";
31
+ provider: string;
32
+ status: "created" | "in_progress";
28
33
  strategy: UploadStrategy;
29
34
  expiresAt: string;
30
35
  upload: {
@@ -34,8 +39,12 @@ type UploadCreateResponse = {
34
39
  uploadHeaders?: Record<string, string>;
35
40
  partSizeBytes?: number;
36
41
  maxParts?: number;
42
+ statusEndpoint: string;
43
+ progressEndpoint: string;
37
44
  partsEndpoint?: string;
45
+ partsCompleteEndpoint?: string;
38
46
  completeEndpoint: string;
47
+ abortEndpoint: string;
39
48
  contentEndpoint?: string;
40
49
  };
41
50
  };
@@ -44,8 +53,8 @@ type UploadHelpers = {
44
53
  upload: UploadCreateResponse;
45
54
  file: FileMetadata;
46
55
  }>;
47
- downloadFile: (fileKeyOrParts: FileKeyEncoded | FileKeyParts) => Promise<Response>;
56
+ downloadFile: (fileKey: string, options: DownloadFileOptions) => Promise<Response>;
48
57
  };
49
58
  //#endregion
50
- export { CreateUploadAndTransferOptions, UploadCreateResponse, UploadHelpers };
59
+ export { CreateUploadAndTransferOptions, DownloadFileOptions, UploadCreateResponse, UploadHelpers };
51
60
  //# sourceMappingURL=helpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","names":[],"sources":["../../../src/client/helpers.ts"],"sourcesContent":[],"mappings":";;;;;KAIY,cAAA;;EAAA,UAAA,EAAA,MAAc;EAOd,aAAA,EAAA,MAAA;EAA8B,UAAA,CAAA,EAAA,MAAA;;AAE9B,KAFA,8BAAA,GAEA;UAGC,CAAA,EAJA,YAIA;SAEE,CAAA,EALH,cAKG;UAEF,CAAA,EAAA,MAAA;aACa,CAAA,EAAA,MAAA;EAAc,QAAA,CAAA,EAL3B,cAK2B,GAAA,IAAA;EAG5B,IAAA,CAAA,EAAA,MAAA,EAAA;EAAoB,UAAA,CAAA,EANjB,cAMiB;YAIpB,CAAA,EAAA,MAAA;UAMQ,CAAA,EAdP,MAcO,CAAA,MAAA,EAAA,OAAA,CAAA;EAAM,UAAA,CAAA,EAAA,CAAA,QAAA,EAbA,cAaA,EAAA,GAAA,IAAA;AAS1B,CAAA;AAAyB,KAnBb,oBAAA,GAmBa;UAEf,EAAA,MAAA;SACG,EAAA,MAAA;QACY,EAAA,SAAA;UAA4B,EAnBzC,cAmByC;WAA9C,EAAA,MAAA;QAC0B,EAAA;IAAiB,IAAA,EAAA,QAAA,GAAA,WAAA;IAAyB,SAAA,EAAA,QAAA,GAAA,OAAA;IAAR,SAAA,CAAA,EAAA,MAAA;IAAO,aAAA,CAAA,EAdtD,MAcsD,CAAA,MAAA,EAAA,MAAA,CAAA;;;;;;;;KAL9D,aAAA;kCAEF,eACG,mCACN;YAAkB;UAA4B;;iCACpB,iBAAiB,iBAAiB,QAAQ"}
1
+ {"version":3,"file":"helpers.d.ts","names":[],"sources":["../../../src/client/helpers.ts"],"sourcesContent":[],"mappings":";;;;KAGY,cAAA;;EAAA,UAAA,EAAA,MAAc;EAOd,aAAA,EAAA,MAAA;EAA8B,UAAA,CAAA,EAAA,MAAA;;AAO3B,KAPH,8BAAA,GAOG;UAEF,EAAA,MAAA;SACa,EAAA,MAAA;EAAc,QAAA,CAAA,EAAA,MAAA;EAG5B,WAAA,CAAA,EAAA,MAAc;EAEd,QAAA,CAAA,EAVC,cAUkB,GAAA,IAErB;EAGE,IAAA,CAAA,EAAA,MAAA,EAAA;EAAoB,UAAA,CAAA,EAbjB,cAaiB;YAKpB,CAAA,EAAA,MAAA;UAMQ,CAAA,EAtBP,MAsBO,CAAA,MAAA,EAAA,OAAA,CAAA;EAAM,UAAA,CAAA,EAAA,CAAA,QAAA,EArBA,cAqBA,EAAA,GAAA,IAAA;AAa1B,CAAA;AAAyB,KA/Bb,cAAA,GA+Ba,YAAA,GAAA,SAAA;AAEf,KA/BE,mBAAA,GA+BF;UACG,EAAA,MAAA;QACY,EA/Bf,cA+Be;;AAAlB,KA5BK,oBAAA,GA4BL;UACoC,EAAA,MAAA;SAAgC,EAAA,MAAA;UAAR,EAAA,MAAA;EAAO,MAAA,EAAA,SAAA,GAAA,aAAA;YAxB9D;;;;;;oBAMQ;;;;;;;;;;;;KAaR,aAAA;kCAEF,eACG,mCACN;YAAkB;UAA4B;;2CACV,wBAAwB,QAAQ"}
@@ -1,7 +1,8 @@
1
- import { encodeFileKey } from "./packages/fragment-upload/src/keys.js";
2
-
3
1
  //#region src/client/helpers.ts
4
2
  const DEFAULT_CONTENT_TYPE = "application/octet-stream";
3
+ const PROXY_UPLOAD_STRATEGY_HINT = "Server selected proxy upload strategy for this file (no direct upload URL was returned). If you expected direct-to-storage upload, your active provider/config does not support direct upload for this request.";
4
+ const PROXY_UPLOAD_RECOVERY_HINT = "Verify the /uploads/:uploadId/content endpoint is reachable from the client, or switch provider/config so /uploads returns a direct strategy.";
5
+ const DOWNLOAD_METHOD_HINT = "Pick the download method explicitly: use 'signed-url' for GET /files/by-key/download-url, otherwise use 'content' for GET /files/by-key/content.";
5
6
  const mergeHeaders = (base, next) => {
6
7
  const merged = new Headers(base ?? void 0);
7
8
  if (!next) return merged;
@@ -25,6 +26,74 @@ const readJsonSafely = async (response) => {
25
26
  return null;
26
27
  }
27
28
  };
29
+ const toErrorMessage = (error) => {
30
+ if (error instanceof Error && error.message) return error.message;
31
+ if (typeof error === "string" && error.length > 0) return error;
32
+ return "Unknown network error";
33
+ };
34
+ const readMessageFromPayload = (payload) => {
35
+ if (payload && typeof payload === "object" && "message" in payload && typeof payload.message === "string" && payload.message.length > 0) return payload.message;
36
+ return null;
37
+ };
38
+ const toAbsoluteUrl = (url) => {
39
+ try {
40
+ if (typeof window !== "undefined" && window.location?.origin) return new URL(url, window.location.origin);
41
+ return new URL(url, "http://fragno.local");
42
+ } catch {
43
+ return null;
44
+ }
45
+ };
46
+ const validateProxyContentUrl = (url) => {
47
+ const parsed = toAbsoluteUrl(url);
48
+ if (!parsed) return `Proxy upload endpoint '${url}' is not a valid URL.`;
49
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return `Proxy upload endpoint '${url}' must use http:// or https://.`;
50
+ if (typeof window !== "undefined" && window.location?.protocol === "https:" && parsed.protocol === "http:") return `Proxy upload endpoint '${url}' uses http:// on a https:// page.`;
51
+ return null;
52
+ };
53
+ const buildProxyUploadErrorMessage = (input) => {
54
+ const detailLines = [];
55
+ if (typeof input.status === "number") detailLines.push(`response status ${input.status}`);
56
+ if (input.streamError !== void 0) detailLines.push(`streamed upload error: ${toErrorMessage(input.streamError)}`);
57
+ if (input.fallbackError !== void 0) detailLines.push(`buffered upload error: ${toErrorMessage(input.fallbackError)}`);
58
+ const details = detailLines.length > 0 ? ` Details: ${detailLines.join(" | ")}.` : "";
59
+ return `${PROXY_UPLOAD_STRATEGY_HINT} Proxy upload to '${input.endpointUrl}' failed.${details} ${PROXY_UPLOAD_RECOVERY_HINT}`;
60
+ };
61
+ const buildDownloadRequestErrorMessage = (input) => {
62
+ const detailLines = [];
63
+ if (typeof input.status === "number") detailLines.push(`response status ${input.status}`);
64
+ const payloadMessage = readMessageFromPayload(input.payload);
65
+ if (payloadMessage) detailLines.push(`message: ${payloadMessage}`);
66
+ if (input.requestError !== void 0) detailLines.push(`request error: ${toErrorMessage(input.requestError)}`);
67
+ const details = detailLines.length > 0 ? ` Details: ${detailLines.join(" | ")}.` : "";
68
+ const hint = input.hint ? ` ${input.hint}` : "";
69
+ return `Download request to '${input.endpointUrl}' failed.${details}${hint}`;
70
+ };
71
+ const sanitizeSignedDownloadUrl = (url) => {
72
+ try {
73
+ const parsed = new URL(url);
74
+ parsed.search = "";
75
+ parsed.hash = "";
76
+ return parsed.toString();
77
+ } catch {
78
+ return "<redacted-signed-url>";
79
+ }
80
+ };
81
+ const readErrorCodeFromPayload = (payload) => {
82
+ if (payload && typeof payload === "object" && "code" in payload) {
83
+ const code = payload.code;
84
+ if (typeof code === "string" && code.length > 0) return code;
85
+ }
86
+ return null;
87
+ };
88
+ const hasText = (value) => typeof value === "string" && value.trim().length > 0;
89
+ const requireUploadEndpoint = (endpoint, label) => {
90
+ if (!hasText(endpoint)) throw new Error(`Missing ${label} endpoint for upload`);
91
+ return endpoint;
92
+ };
93
+ const buildByKeyQuery = (provider, fileKey) => new URLSearchParams({
94
+ provider,
95
+ key: fileKey
96
+ }).toString();
28
97
  const createUploadHelpers = (input) => {
29
98
  const { buildUrl, fetcher, defaultOptions } = input;
30
99
  const fetchJson = async (path, init, expectedErrorCode) => {
@@ -38,9 +107,9 @@ const createUploadHelpers = (input) => {
38
107
  }
39
108
  return await response.json();
40
109
  };
41
- const reportProgress = async (uploadId, progress, onProgress) => {
110
+ const reportProgress = async (progressEndpoint, progress, onProgress) => {
42
111
  onProgress?.(progress);
43
- await fetchJson(`/uploads/${uploadId}/progress`, {
112
+ await fetchJson(progressEndpoint, {
44
113
  method: "POST",
45
114
  headers: { "Content-Type": "application/json" },
46
115
  body: JSON.stringify({
@@ -53,12 +122,13 @@ const createUploadHelpers = (input) => {
53
122
  const filename = options.filename ?? (typeof File !== "undefined" && file instanceof File && file.name ? file.name : "upload");
54
123
  const contentType = options.contentType ?? (file.type && file.type.length > 0 ? file.type : void 0) ?? DEFAULT_CONTENT_TYPE;
55
124
  const sizeBytes = file.size;
56
- if (!options.keyParts && !options.fileKey) throw new Error("File key parts or file key is required");
125
+ if (!hasText(options.provider)) throw new Error("Provider is required");
126
+ if (!hasText(options.fileKey)) throw new Error("File key is required");
57
127
  const upload = await fetchJson("/uploads", {
58
128
  method: "POST",
59
129
  headers: { "Content-Type": "application/json" },
60
130
  body: JSON.stringify({
61
- keyParts: options.keyParts,
131
+ provider: options.provider,
62
132
  fileKey: options.fileKey,
63
133
  filename,
64
134
  sizeBytes,
@@ -73,6 +143,7 @@ const createUploadHelpers = (input) => {
73
143
  const totalBytes = sizeBytes;
74
144
  let bytesUploaded = 0;
75
145
  let partsUploaded = 0;
146
+ const progressEndpoint = requireUploadEndpoint(upload.upload.progressEndpoint, "progress");
76
147
  if (upload.strategy === "direct-single") {
77
148
  if (!upload.upload.uploadUrl) throw new Error("Missing upload URL for direct upload");
78
149
  const uploadResponse = await fetcher(upload.upload.uploadUrl, {
@@ -83,7 +154,7 @@ const createUploadHelpers = (input) => {
83
154
  if (!uploadResponse.ok) throw new Error(`Direct upload failed (${uploadResponse.status})`);
84
155
  bytesUploaded = totalBytes;
85
156
  partsUploaded = 1;
86
- await reportProgress(upload.uploadId, {
157
+ await reportProgress(progressEndpoint, {
87
158
  bytesUploaded,
88
159
  totalBytes,
89
160
  partsUploaded,
@@ -101,6 +172,7 @@ const createUploadHelpers = (input) => {
101
172
  if (upload.strategy === "direct-multipart") {
102
173
  const partSizeBytes = upload.upload.partSizeBytes;
103
174
  if (!partSizeBytes || !upload.upload.partsEndpoint) throw new Error("Missing multipart configuration for upload");
175
+ const partsCompleteEndpoint = requireUploadEndpoint(upload.upload.partsCompleteEndpoint, "multipart completion");
104
176
  const totalParts = Math.ceil(totalBytes / partSizeBytes);
105
177
  if (upload.upload.maxParts && totalParts > upload.upload.maxParts) throw new Error("Multipart upload exceeds maximum parts");
106
178
  const partNumbers = Array.from({ length: totalParts }, (_, i) => i + 1);
@@ -135,14 +207,14 @@ const createUploadHelpers = (input) => {
135
207
  });
136
208
  bytesUploaded += partSize;
137
209
  partsUploaded += 1;
138
- await reportProgress(upload.uploadId, {
210
+ await reportProgress(progressEndpoint, {
139
211
  bytesUploaded,
140
212
  totalBytes,
141
213
  partsUploaded,
142
214
  totalParts
143
215
  }, options.onProgress);
144
216
  }
145
- await fetchJson(`/uploads/${upload.uploadId}/parts/complete`, {
217
+ await fetchJson(partsCompleteEndpoint, {
146
218
  method: "POST",
147
219
  headers: { "Content-Type": "application/json" },
148
220
  body: JSON.stringify({ parts: completedParts })
@@ -157,6 +229,9 @@ const createUploadHelpers = (input) => {
157
229
  };
158
230
  }
159
231
  if (!upload.upload.contentEndpoint) throw new Error("Missing proxy content endpoint for upload");
232
+ const proxyContentUrl = buildUrl(upload.upload.contentEndpoint);
233
+ const proxyUrlValidationError = validateProxyContentUrl(proxyContentUrl);
234
+ if (proxyUrlValidationError) throw new Error(`${proxyUrlValidationError} ${PROXY_UPLOAD_RECOVERY_HINT}`);
160
235
  const source = file.stream();
161
236
  const stream = new ReadableStream({ start(controller) {
162
237
  const reader = source.getReader();
@@ -190,14 +265,27 @@ const createUploadHelpers = (input) => {
190
265
  requestInit.duplex = "half";
191
266
  let proxyResponse;
192
267
  try {
193
- proxyResponse = await fetcher(buildUrl(upload.upload.contentEndpoint), requestInit);
194
- } catch (_error) {
195
- const fallbackResponse = await fetcher(buildUrl(upload.upload.contentEndpoint), buildRequestInit(defaultOptions, {
196
- method: "PUT",
197
- headers: { "Content-Type": DEFAULT_CONTENT_TYPE },
198
- body: file
268
+ proxyResponse = await fetcher(proxyContentUrl, requestInit);
269
+ } catch (streamError) {
270
+ let fallbackResponse;
271
+ try {
272
+ fallbackResponse = await fetcher(proxyContentUrl, buildRequestInit(defaultOptions, {
273
+ method: "PUT",
274
+ headers: { "Content-Type": DEFAULT_CONTENT_TYPE },
275
+ body: file
276
+ }));
277
+ } catch (fallbackError) {
278
+ throw new Error(buildProxyUploadErrorMessage({
279
+ endpointUrl: proxyContentUrl,
280
+ streamError,
281
+ fallbackError
282
+ }));
283
+ }
284
+ if (!fallbackResponse.ok) throw new Error(buildProxyUploadErrorMessage({
285
+ endpointUrl: proxyContentUrl,
286
+ status: fallbackResponse.status,
287
+ streamError
199
288
  }));
200
- if (!fallbackResponse.ok) throw new Error(`Proxy upload failed (${fallbackResponse.status})`);
201
289
  options.onProgress?.({
202
290
  bytesUploaded: totalBytes,
203
291
  totalBytes,
@@ -208,28 +296,86 @@ const createUploadHelpers = (input) => {
208
296
  file: await fallbackResponse.json()
209
297
  };
210
298
  }
211
- if (!proxyResponse.ok) throw new Error(`Proxy upload failed (${proxyResponse.status})`);
299
+ if (!proxyResponse.ok) throw new Error(buildProxyUploadErrorMessage({
300
+ endpointUrl: proxyContentUrl,
301
+ status: proxyResponse.status
302
+ }));
212
303
  return {
213
304
  upload,
214
305
  file: await proxyResponse.json()
215
306
  };
216
307
  };
217
- const downloadFile = async (fileKeyOrParts) => {
218
- const fileKey = Array.isArray(fileKeyOrParts) ? encodeFileKey(fileKeyOrParts) : fileKeyOrParts;
219
- const downloadUrlResponse = await fetcher(buildUrl(`/files/${fileKey}/download-url`), buildRequestInit(defaultOptions, { method: "GET" }));
220
- if (downloadUrlResponse.ok) {
308
+ const downloadFile = async (fileKey, options) => {
309
+ if (!hasText(fileKey)) throw new Error("File key is required");
310
+ if (!options || !hasText(options.provider)) throw new Error("Download provider is required");
311
+ if (!options || options.method !== "signed-url" && options.method !== "content") throw new Error(`Download method is required. ${DOWNLOAD_METHOD_HINT}`);
312
+ const byKeyQuery = buildByKeyQuery(options.provider, fileKey);
313
+ if (options.method === "signed-url") {
314
+ const downloadUrlEndpoint = buildUrl(`/files/by-key/download-url?${byKeyQuery}`);
315
+ let downloadUrlResponse;
316
+ try {
317
+ downloadUrlResponse = await fetcher(downloadUrlEndpoint, buildRequestInit(defaultOptions, { method: "GET" }));
318
+ } catch (error) {
319
+ throw new Error(buildDownloadRequestErrorMessage({
320
+ endpointUrl: downloadUrlEndpoint,
321
+ requestError: error
322
+ }));
323
+ }
324
+ if (!downloadUrlResponse.ok) {
325
+ const errorPayload = await readJsonSafely(downloadUrlResponse);
326
+ const hint = readErrorCodeFromPayload(errorPayload) === "SIGNED_URL_UNSUPPORTED" ? "Requested method 'signed-url' is unsupported by this storage adapter. This is a programming error. Use method 'content' when streaming downloads are available." : void 0;
327
+ throw new Error(buildDownloadRequestErrorMessage({
328
+ endpointUrl: downloadUrlEndpoint,
329
+ status: downloadUrlResponse.status,
330
+ payload: errorPayload,
331
+ hint
332
+ }));
333
+ }
221
334
  const payload = await downloadUrlResponse.json();
222
- return fetcher(payload.url, {
223
- method: "GET",
224
- headers: payload.headers
225
- });
335
+ const sanitizedUrl = sanitizeSignedDownloadUrl(payload.url);
336
+ let response;
337
+ try {
338
+ response = await fetcher(payload.url, {
339
+ method: "GET",
340
+ headers: payload.headers
341
+ });
342
+ } catch (error) {
343
+ throw new Error(buildDownloadRequestErrorMessage({
344
+ endpointUrl: sanitizedUrl,
345
+ requestError: error
346
+ }));
347
+ }
348
+ if (!response.ok) {
349
+ const payloadError = await readJsonSafely(response);
350
+ throw new Error(buildDownloadRequestErrorMessage({
351
+ endpointUrl: sanitizedUrl,
352
+ status: response.status,
353
+ payload: payloadError
354
+ }));
355
+ }
356
+ return response;
226
357
  }
227
- const errorPayload = await readJsonSafely(downloadUrlResponse);
228
- if ((typeof errorPayload === "object" && errorPayload ? errorPayload.code : void 0) !== "SIGNED_URL_UNSUPPORTED") {
229
- const message = typeof errorPayload === "object" && errorPayload && "message" in errorPayload ? String(errorPayload.message) : `Download failed (${downloadUrlResponse.status})`;
230
- throw new Error(message);
358
+ const contentEndpoint = buildUrl(`/files/by-key/content?${byKeyQuery}`);
359
+ let contentResponse;
360
+ try {
361
+ contentResponse = await fetcher(contentEndpoint, buildRequestInit(defaultOptions, { method: "GET" }));
362
+ } catch (error) {
363
+ throw new Error(buildDownloadRequestErrorMessage({
364
+ endpointUrl: contentEndpoint,
365
+ requestError: error
366
+ }));
367
+ }
368
+ if (!contentResponse.ok) {
369
+ const contentError = await readJsonSafely(contentResponse);
370
+ const hint = readErrorCodeFromPayload(contentError) === "SIGNED_URL_UNSUPPORTED" ? "The 'content' download endpoint is unsupported by this storage adapter. This request used method 'content'. Use method 'signed-url' when signed downloads are available." : void 0;
371
+ throw new Error(buildDownloadRequestErrorMessage({
372
+ endpointUrl: contentEndpoint,
373
+ status: contentResponse.status,
374
+ payload: contentError,
375
+ hint
376
+ }));
231
377
  }
232
- return fetcher(buildUrl(`/files/${fileKey}/content`), buildRequestInit(defaultOptions, { method: "GET" }));
378
+ return contentResponse;
233
379
  };
234
380
  return {
235
381
  createUploadAndTransfer,