@openrails/contentkit-ui 0.0.0-stage → 0.68.1

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 (367) hide show
  1. package/README.md +470 -2
  2. package/dist/appearance.d.ts +38 -0
  3. package/dist/appearance.js +15 -0
  4. package/dist/appearance.js.map +1 -0
  5. package/dist/assets/hash.worker-BYfo-7MQ.js.map +1 -0
  6. package/dist/client/aspect.d.ts +6 -0
  7. package/dist/client/aspect.js +17 -0
  8. package/dist/client/aspect.js.map +1 -0
  9. package/dist/client/client.d.ts +53 -0
  10. package/dist/client/client.js +36 -0
  11. package/dist/client/client.js.map +1 -0
  12. package/dist/client/codes.d.ts +12 -0
  13. package/dist/client/codes.js +19 -0
  14. package/dist/client/codes.js.map +1 -0
  15. package/dist/client/content/bans.d.ts +15 -0
  16. package/dist/client/content/bans.js +40 -0
  17. package/dist/client/content/bans.js.map +1 -0
  18. package/dist/client/content/comments.d.ts +36 -0
  19. package/dist/client/content/comments.js +95 -0
  20. package/dist/client/content/comments.js.map +1 -0
  21. package/dist/client/content/favorites.d.ts +14 -0
  22. package/dist/client/content/favorites.js +42 -0
  23. package/dist/client/content/favorites.js.map +1 -0
  24. package/dist/client/content/moderation.d.ts +18 -0
  25. package/dist/client/content/moderation.js +37 -0
  26. package/dist/client/content/moderation.js.map +1 -0
  27. package/dist/client/content/polls.d.ts +47 -0
  28. package/dist/client/content/polls.js +151 -0
  29. package/dist/client/content/polls.js.map +1 -0
  30. package/dist/client/content/posts.d.ts +44 -0
  31. package/dist/client/content/posts.js +92 -0
  32. package/dist/client/content/posts.js.map +1 -0
  33. package/dist/client/content/reactions.d.ts +12 -0
  34. package/dist/client/content/reactions.js +36 -0
  35. package/dist/client/content/reactions.js.map +1 -0
  36. package/dist/client/content/types.d.ts +91 -0
  37. package/dist/client/content/types.js +8 -0
  38. package/dist/client/content/types.js.map +1 -0
  39. package/dist/client/crop.d.ts +32 -0
  40. package/dist/client/crop.js +103 -0
  41. package/dist/client/crop.js.map +1 -0
  42. package/dist/client/errors.d.ts +54 -0
  43. package/dist/client/errors.js +80 -0
  44. package/dist/client/errors.js.map +1 -0
  45. package/dist/client/gallery.d.ts +43 -0
  46. package/dist/client/gallery.js +86 -0
  47. package/dist/client/gallery.js.map +1 -0
  48. package/dist/client/generated/error-codes.d.ts +5 -0
  49. package/dist/client/generated/error-codes.js +131 -0
  50. package/dist/client/generated/error-codes.js.map +1 -0
  51. package/dist/client/generated/routes.d.ts +1086 -0
  52. package/dist/client/generated/routes.js +1400 -0
  53. package/dist/client/generated/routes.js.map +1 -0
  54. package/dist/client/generated/wire.d.ts +630 -0
  55. package/dist/client/generated/wire.js +6 -0
  56. package/dist/client/generated/wire.js.map +1 -0
  57. package/dist/client/http.d.ts +83 -0
  58. package/dist/client/http.js +82 -0
  59. package/dist/client/http.js.map +1 -0
  60. package/dist/client/image.d.ts +24 -0
  61. package/dist/client/image.js +65 -0
  62. package/dist/client/image.js.map +1 -0
  63. package/dist/client/index.d.ts +33 -0
  64. package/dist/client/media/api.d.ts +36 -0
  65. package/dist/client/media/api.js +61 -0
  66. package/dist/client/media/api.js.map +1 -0
  67. package/dist/client/media/client.d.ts +247 -0
  68. package/dist/client/media/client.js +407 -0
  69. package/dist/client/media/client.js.map +1 -0
  70. package/dist/client/media/encode.d.ts +3 -0
  71. package/dist/client/media/encode.js +8 -0
  72. package/dist/client/media/encode.js.map +1 -0
  73. package/dist/client/media/hash-stream.d.ts +6 -0
  74. package/dist/client/media/hash-stream.js +16 -0
  75. package/dist/client/media/hash-stream.js.map +1 -0
  76. package/dist/client/media/hash.d.ts +12 -0
  77. package/dist/client/media/hash.js +48 -0
  78. package/dist/client/media/hash.js.map +1 -0
  79. package/dist/client/media/hash.worker-inline.js +18 -0
  80. package/dist/client/media/hash.worker-inline.js.map +1 -0
  81. package/dist/client/media/pacer.d.ts +21 -0
  82. package/dist/client/media/pacer.js +25 -0
  83. package/dist/client/media/pacer.js.map +1 -0
  84. package/dist/client/media/queue.d.ts +112 -0
  85. package/dist/client/media/queue.js +274 -0
  86. package/dist/client/media/queue.js.map +1 -0
  87. package/dist/client/media/ref.d.ts +6 -0
  88. package/dist/client/media/ref.js +10 -0
  89. package/dist/client/media/ref.js.map +1 -0
  90. package/dist/client/media/rules.d.ts +54 -0
  91. package/dist/client/media/rules.js +143 -0
  92. package/dist/client/media/rules.js.map +1 -0
  93. package/dist/client/media/transport.d.ts +13 -0
  94. package/dist/client/media/transport.js +33 -0
  95. package/dist/client/media/transport.js.map +1 -0
  96. package/dist/client/media/windows.d.ts +15 -0
  97. package/dist/client/media/windows.js +39 -0
  98. package/dist/client/media/windows.js.map +1 -0
  99. package/dist/client/playback.d.ts +200 -0
  100. package/dist/client/playback.js +142 -0
  101. package/dist/client/playback.js.map +1 -0
  102. package/dist/client/player.d.ts +102 -0
  103. package/dist/client/player.js +155 -0
  104. package/dist/client/player.js.map +1 -0
  105. package/dist/client/public.d.ts +25 -0
  106. package/dist/client/public.js +18 -0
  107. package/dist/client/public.js.map +1 -0
  108. package/dist/client/rendition.d.ts +19 -0
  109. package/dist/client/rendition.js +16 -0
  110. package/dist/client/rendition.js.map +1 -0
  111. package/dist/client/route.d.ts +39 -0
  112. package/dist/client/route.js +31 -0
  113. package/dist/client/route.js.map +1 -0
  114. package/dist/client/taxonomy.d.ts +30 -0
  115. package/dist/client/taxonomy.js +77 -0
  116. package/dist/client/taxonomy.js.map +1 -0
  117. package/dist/client.js +33 -0
  118. package/dist/components/encode-progress.d.ts +15 -0
  119. package/dist/components/encode-progress.js +49 -0
  120. package/dist/components/encode-progress.js.map +1 -0
  121. package/dist/components/hover-preview.d.ts +29 -0
  122. package/dist/components/hover-preview.js +49 -0
  123. package/dist/components/hover-preview.js.map +1 -0
  124. package/dist/components/image-crop-dialog.d.ts +48 -0
  125. package/dist/components/image-crop-dialog.js +359 -0
  126. package/dist/components/image-crop-dialog.js.map +1 -0
  127. package/dist/components/lazy-mount.d.ts +10 -0
  128. package/dist/components/lazy-mount.js +22 -0
  129. package/dist/components/lazy-mount.js.map +1 -0
  130. package/dist/components/media-folder-editor.d.ts +59 -0
  131. package/dist/components/media-folder-editor.js +611 -0
  132. package/dist/components/media-folder-editor.js.map +1 -0
  133. package/dist/components/media-gallery.d.ts +54 -0
  134. package/dist/components/media-gallery.js +597 -0
  135. package/dist/components/media-gallery.js.map +1 -0
  136. package/dist/components/media-readiness-notice.d.ts +24 -0
  137. package/dist/components/media-readiness-notice.js +61 -0
  138. package/dist/components/media-readiness-notice.js.map +1 -0
  139. package/dist/components/player-controls.d.ts +71 -0
  140. package/dist/components/player-controls.js +264 -0
  141. package/dist/components/player-controls.js.map +1 -0
  142. package/dist/components/progress.d.ts +5 -0
  143. package/dist/components/progress.js +14 -0
  144. package/dist/components/progress.js.map +1 -0
  145. package/dist/components/rendition-img.d.ts +34 -0
  146. package/dist/components/rendition-img.js +78 -0
  147. package/dist/components/rendition-img.js.map +1 -0
  148. package/dist/components/slot-editor.d.ts +95 -0
  149. package/dist/components/slot-editor.js +182 -0
  150. package/dist/components/slot-editor.js.map +1 -0
  151. package/dist/components/slot-image.d.ts +23 -0
  152. package/dist/components/slot-image.js +48 -0
  153. package/dist/components/slot-image.js.map +1 -0
  154. package/dist/components/slot-upload.d.ts +41 -0
  155. package/dist/components/slot-upload.js +215 -0
  156. package/dist/components/slot-upload.js.map +1 -0
  157. package/dist/components/social/bans.d.ts +42 -0
  158. package/dist/components/social/bans.js +300 -0
  159. package/dist/components/social/bans.js.map +1 -0
  160. package/dist/components/social/comments.d.ts +42 -0
  161. package/dist/components/social/comments.js +517 -0
  162. package/dist/components/social/comments.js.map +1 -0
  163. package/dist/components/social/engagement.d.ts +45 -0
  164. package/dist/components/social/engagement.js +129 -0
  165. package/dist/components/social/engagement.js.map +1 -0
  166. package/dist/components/social/index.d.ts +6 -0
  167. package/dist/components/social/moderation.d.ts +32 -0
  168. package/dist/components/social/moderation.js +383 -0
  169. package/dist/components/social/moderation.js.map +1 -0
  170. package/dist/components/social/parts.d.ts +50 -0
  171. package/dist/components/social/parts.js +106 -0
  172. package/dist/components/social/parts.js.map +1 -0
  173. package/dist/components/social/poll-editor.d.ts +34 -0
  174. package/dist/components/social/poll-editor.js +567 -0
  175. package/dist/components/social/poll-editor.js.map +1 -0
  176. package/dist/components/social/poll.d.ts +30 -0
  177. package/dist/components/social/poll.js +252 -0
  178. package/dist/components/social/poll.js.map +1 -0
  179. package/dist/components/sortable-list.d.ts +29 -0
  180. package/dist/components/sortable-list.js +121 -0
  181. package/dist/components/sortable-list.js.map +1 -0
  182. package/dist/components/video-mini-player.d.ts +35 -0
  183. package/dist/components/video-mini-player.js +236 -0
  184. package/dist/components/video-mini-player.js.map +1 -0
  185. package/dist/components/video-player.d.ts +146 -0
  186. package/dist/components/video-player.js +630 -0
  187. package/dist/components/video-player.js.map +1 -0
  188. package/dist/components/video-poster-picker.d.ts +59 -0
  189. package/dist/components/video-poster-picker.js +335 -0
  190. package/dist/components/video-poster-picker.js.map +1 -0
  191. package/dist/components/video-poster.d.ts +24 -0
  192. package/dist/components/video-poster.js +46 -0
  193. package/dist/components/video-poster.js.map +1 -0
  194. package/dist/i18n/context.d.ts +4 -0
  195. package/dist/i18n/context.js +11 -0
  196. package/dist/i18n/context.js.map +1 -0
  197. package/dist/i18n/messages.d.ts +40 -0
  198. package/dist/i18n/messages.js +94 -0
  199. package/dist/i18n/messages.js.map +1 -0
  200. package/dist/index.d.ts +23 -0
  201. package/dist/index.js +41 -0
  202. package/dist/locales/de.d.ts +2 -0
  203. package/dist/locales/de.js +271 -0
  204. package/dist/locales/de.js.map +1 -0
  205. package/dist/locales/en.d.ts +457 -0
  206. package/dist/locales/en.js +271 -0
  207. package/dist/locales/en.js.map +1 -0
  208. package/dist/locales/es.d.ts +2 -0
  209. package/dist/locales/es.js +271 -0
  210. package/dist/locales/es.js.map +1 -0
  211. package/dist/locales/ja.d.ts +2 -0
  212. package/dist/locales/ja.js +271 -0
  213. package/dist/locales/ja.js.map +1 -0
  214. package/dist/locales/ko.d.ts +2 -0
  215. package/dist/locales/ko.js +271 -0
  216. package/dist/locales/ko.js.map +1 -0
  217. package/dist/locales/social/de.d.ts +2 -0
  218. package/dist/locales/social/de.js +198 -0
  219. package/dist/locales/social/de.js.map +1 -0
  220. package/dist/locales/social/en.d.ts +195 -0
  221. package/dist/locales/social/en.js +198 -0
  222. package/dist/locales/social/en.js.map +1 -0
  223. package/dist/locales/social/es.d.ts +2 -0
  224. package/dist/locales/social/es.js +198 -0
  225. package/dist/locales/social/es.js.map +1 -0
  226. package/dist/locales/social/ja.d.ts +2 -0
  227. package/dist/locales/social/ja.js +198 -0
  228. package/dist/locales/social/ja.js.map +1 -0
  229. package/dist/locales/social/ko.d.ts +2 -0
  230. package/dist/locales/social/ko.js +198 -0
  231. package/dist/locales/social/ko.js.map +1 -0
  232. package/dist/locales/social/zh.d.ts +2 -0
  233. package/dist/locales/social/zh.js +198 -0
  234. package/dist/locales/social/zh.js.map +1 -0
  235. package/dist/locales/zh.d.ts +2 -0
  236. package/dist/locales/zh.js +271 -0
  237. package/dist/locales/zh.js.map +1 -0
  238. package/dist/provider.d.ts +22 -0
  239. package/dist/provider.js +59 -0
  240. package/dist/provider.js.map +1 -0
  241. package/dist/react/canonical.d.ts +33 -0
  242. package/dist/react/canonical.js +78 -0
  243. package/dist/react/canonical.js.map +1 -0
  244. package/dist/react/comments.d.ts +82 -0
  245. package/dist/react/comments.js +155 -0
  246. package/dist/react/comments.js.map +1 -0
  247. package/dist/react/config.d.ts +13 -0
  248. package/dist/react/config.js +14 -0
  249. package/dist/react/config.js.map +1 -0
  250. package/dist/react/context.d.ts +55 -0
  251. package/dist/react/context.js +42 -0
  252. package/dist/react/context.js.map +1 -0
  253. package/dist/react/crop.d.ts +49 -0
  254. package/dist/react/crop.js +107 -0
  255. package/dist/react/crop.js.map +1 -0
  256. package/dist/react/engagement.d.ts +38 -0
  257. package/dist/react/engagement.js +88 -0
  258. package/dist/react/engagement.js.map +1 -0
  259. package/dist/react/folder.d.ts +103 -0
  260. package/dist/react/folder.js +240 -0
  261. package/dist/react/folder.js.map +1 -0
  262. package/dist/react/gallery.d.ts +140 -0
  263. package/dist/react/gallery.js +477 -0
  264. package/dist/react/gallery.js.map +1 -0
  265. package/dist/react/index.d.ts +21 -0
  266. package/dist/react/inline-preview.d.ts +24 -0
  267. package/dist/react/inline-preview.js +76 -0
  268. package/dist/react/inline-preview.js.map +1 -0
  269. package/dist/react/player.d.ts +70 -0
  270. package/dist/react/player.js +214 -0
  271. package/dist/react/player.js.map +1 -0
  272. package/dist/react/polls.d.ts +74 -0
  273. package/dist/react/polls.js +176 -0
  274. package/dist/react/polls.js.map +1 -0
  275. package/dist/react/posts.d.ts +59 -0
  276. package/dist/react/posts.js +149 -0
  277. package/dist/react/posts.js.map +1 -0
  278. package/dist/react/provider.d.ts +27 -0
  279. package/dist/react/provider.js +49 -0
  280. package/dist/react/provider.js.map +1 -0
  281. package/dist/react/public.d.ts +39 -0
  282. package/dist/react/public.js +70 -0
  283. package/dist/react/public.js.map +1 -0
  284. package/dist/react/read.d.ts +52 -0
  285. package/dist/react/read.js +144 -0
  286. package/dist/react/read.js.map +1 -0
  287. package/dist/react/resources.d.ts +73 -0
  288. package/dist/react/resources.js +247 -0
  289. package/dist/react/resources.js.map +1 -0
  290. package/dist/react/slot.d.ts +104 -0
  291. package/dist/react/slot.js +185 -0
  292. package/dist/react/slot.js.map +1 -0
  293. package/dist/react/store.d.ts +51 -0
  294. package/dist/react/store.js +165 -0
  295. package/dist/react/store.js.map +1 -0
  296. package/dist/react/upload.d.ts +86 -0
  297. package/dist/react/upload.js +155 -0
  298. package/dist/react/upload.js.map +1 -0
  299. package/dist/react/use-resource.d.ts +44 -0
  300. package/dist/react/use-resource.js +74 -0
  301. package/dist/react/use-resource.js.map +1 -0
  302. package/dist/react/video.d.ts +118 -0
  303. package/dist/react/video.js +184 -0
  304. package/dist/react/video.js.map +1 -0
  305. package/dist/react/viewport.d.ts +19 -0
  306. package/dist/react/viewport.js +30 -0
  307. package/dist/react/viewport.js.map +1 -0
  308. package/dist/react.js +21 -0
  309. package/dist/scope.d.ts +14 -0
  310. package/dist/scope.js +30 -0
  311. package/dist/scope.js.map +1 -0
  312. package/dist/styles.css +3 -0
  313. package/dist/ui/alert-dialog.d.ts +18 -0
  314. package/dist/ui/alert-dialog.js +86 -0
  315. package/dist/ui/alert-dialog.js.map +1 -0
  316. package/dist/ui/alert.d.ts +10 -0
  317. package/dist/ui/alert.js +38 -0
  318. package/dist/ui/alert.js.map +1 -0
  319. package/dist/ui/avatar.d.ts +11 -0
  320. package/dist/ui/avatar.js +31 -0
  321. package/dist/ui/avatar.js.map +1 -0
  322. package/dist/ui/badge.d.ts +7 -0
  323. package/dist/ui/badge.js +31 -0
  324. package/dist/ui/badge.js.map +1 -0
  325. package/dist/ui/button.d.ts +8 -0
  326. package/dist/ui/button.js +46 -0
  327. package/dist/ui/button.js.map +1 -0
  328. package/dist/ui/dialog.d.ts +17 -0
  329. package/dist/ui/dialog.js +91 -0
  330. package/dist/ui/dialog.js.map +1 -0
  331. package/dist/ui/dropdown-menu.d.ts +15 -0
  332. package/dist/ui/dropdown-menu.js +105 -0
  333. package/dist/ui/dropdown-menu.js.map +1 -0
  334. package/dist/ui/input.d.ts +3 -0
  335. package/dist/ui/input.js +17 -0
  336. package/dist/ui/input.js.map +1 -0
  337. package/dist/ui/label.d.ts +3 -0
  338. package/dist/ui/label.js +15 -0
  339. package/dist/ui/label.js.map +1 -0
  340. package/dist/ui/progress.d.ts +7 -0
  341. package/dist/ui/progress.js +31 -0
  342. package/dist/ui/progress.js.map +1 -0
  343. package/dist/ui/select.d.ts +15 -0
  344. package/dist/ui/select.js +98 -0
  345. package/dist/ui/select.js.map +1 -0
  346. package/dist/ui/skeleton.d.ts +2 -0
  347. package/dist/ui/skeleton.js +14 -0
  348. package/dist/ui/skeleton.js.map +1 -0
  349. package/dist/ui/slider.d.ts +3 -0
  350. package/dist/ui/slider.js +35 -0
  351. package/dist/ui/slider.js.map +1 -0
  352. package/dist/ui/switch.d.ts +5 -0
  353. package/dist/ui/switch.js +20 -0
  354. package/dist/ui/switch.js.map +1 -0
  355. package/dist/ui/tabs.d.ts +10 -0
  356. package/dist/ui/tabs.js +47 -0
  357. package/dist/ui/tabs.js.map +1 -0
  358. package/dist/ui/textarea.d.ts +3 -0
  359. package/dist/ui/textarea.js +15 -0
  360. package/dist/ui/textarea.js.map +1 -0
  361. package/dist/ui/toggle-group.d.ts +5 -0
  362. package/dist/ui/toggle-group.js +23 -0
  363. package/dist/ui/toggle-group.js.map +1 -0
  364. package/dist/urls/index.d.ts +100 -0
  365. package/dist/urls.js +118 -0
  366. package/dist/urls.js.map +1 -0
  367. package/package.json +120 -3
@@ -0,0 +1,274 @@
1
+ import { ContentKitError as e, toContentKitError as t } from "../errors.js";
2
+ import { fill as n } from "../public.js";
3
+ import { samePath as r, stem as i } from "./client.js";
4
+ //#region src/client/media/queue.ts
5
+ var a = 2 ** 31 - 1, o = class {
6
+ client;
7
+ o;
8
+ items = [];
9
+ blocked;
10
+ running = /* @__PURE__ */ new Map();
11
+ listeners = /* @__PURE__ */ new Set();
12
+ snap;
13
+ started;
14
+ staging = /* @__PURE__ */ new Map();
15
+ poll;
16
+ disposed = !1;
17
+ batch;
18
+ constructor(e, t) {
19
+ this.client = e, this.o = t, this.started = t.autoStart ?? !0, this.publish();
20
+ }
21
+ subscribe = (e) => (this.listeners.add(e), () => this.listeners.delete(e));
22
+ getSnapshot = () => this.snap;
23
+ add(e, t = {}) {
24
+ let r = [...e].map((e) => ({
25
+ id: crypto.randomUUID(),
26
+ file: e,
27
+ path: t.path?.(e) ?? n(this.o.path, { name: e.name }),
28
+ status: "queued",
29
+ state: t.state?.(e)
30
+ }));
31
+ return this.items.push(...r), this.changed(), r;
32
+ }
33
+ move(e, t) {
34
+ let n = this.items.findIndex((t) => t.id === e);
35
+ if (n < 0) return;
36
+ let [r] = this.items.splice(n, 1);
37
+ this.items.splice(Math.max(0, Math.min(t, this.items.length)), 0, r), this.changed();
38
+ }
39
+ update(e, t) {
40
+ let n = this.find(e);
41
+ n?.unattached && t.path && t.path !== n.path && this.client.commit(this.o.ref, [{
42
+ op: "rename",
43
+ path: n.path,
44
+ to: t.path
45
+ }]).then((n) => {
46
+ let a = n.find((e) => r(e.path, i(t.path)));
47
+ a && this.patch(e, { path: a.path });
48
+ }, () => {}), this.patch(e, t);
49
+ }
50
+ remove(e) {
51
+ let t = this.find(e);
52
+ if (!t) return;
53
+ this.running.get(e)?.abort(), t.state && this.client.discard(t.state).catch(() => {});
54
+ let n = this.staging.get(e);
55
+ (t.unattached || n) && (n ?? Promise.resolve()).then((e) => {
56
+ if (e || t.unattached) return this.client.commit(this.o.ref, [{
57
+ op: "remove",
58
+ path: e?.path ?? t.path
59
+ }]);
60
+ }).catch(() => {}), this.o.onState?.(t, null), this.items = this.items.filter((t) => t.id !== e), this.changed();
61
+ }
62
+ retry(e) {
63
+ this.patch(e, {
64
+ status: "queued",
65
+ error: void 0
66
+ });
67
+ }
68
+ start() {
69
+ this.started = !0, this.blocked = void 0, this.changed();
70
+ }
71
+ pause() {
72
+ this.started = !1;
73
+ for (let e of this.running.values()) e.abort();
74
+ this.changed();
75
+ }
76
+ async commit(e, t = {}) {
77
+ await Promise.all(this.staging.values());
78
+ let n = this.items;
79
+ if (t.head) {
80
+ let e = n.findIndex((e) => e.status !== "uploaded" && e.status !== "committed");
81
+ e >= 0 && (n = n.slice(0, e));
82
+ }
83
+ let r = n.filter((e) => e.status === "uploaded");
84
+ if (r.length === 0) return [];
85
+ let i = r.flatMap((e) => {
86
+ let t = {
87
+ op: "attach",
88
+ path: e.path,
89
+ index: a,
90
+ ...e.meta ? { meta: e.meta } : {}
91
+ };
92
+ if (e.unattached) return [t];
93
+ let n = {
94
+ op: "put",
95
+ path: e.path,
96
+ blob: e.result.blob,
97
+ create_id: e.id,
98
+ index: a,
99
+ ...e.meta ? { meta: e.meta } : {}
100
+ };
101
+ return e.result.processOnUpload ? [n, t] : [n];
102
+ }), o = Object.fromEntries(r.map((e) => [e.result.blob, {
103
+ file: e.file,
104
+ type: e.result.type
105
+ }])), s = JSON.stringify(i);
106
+ this.batch?.input !== s && (this.batch = {
107
+ input: s,
108
+ operationID: crypto.randomUUID()
109
+ });
110
+ let c = this.batch, l = await this.client.commit(this.o.ref, i, {
111
+ signal: e,
112
+ sources: o,
113
+ operationID: c.operationID,
114
+ onState: (e) => {
115
+ c.input = JSON.stringify(e.ops), c.operationID = e.operation_id;
116
+ let t = !1;
117
+ for (let n of e.ops) {
118
+ if (n.op !== "put" || !n.blob) continue;
119
+ let e = r.find((e) => e.id === n.create_id);
120
+ if (!e?.result) continue;
121
+ let i = this.find(e.id);
122
+ i?.result && i.result.blob === e.result.blob && i.result.blob !== n.blob && (t = this.set(i.id, { result: {
123
+ ...i.result,
124
+ blob: n.blob
125
+ } }) || t);
126
+ }
127
+ t && this.changed();
128
+ }
129
+ });
130
+ for (let e of r) this.set(e.id, {
131
+ status: "committed",
132
+ unattached: !1
133
+ });
134
+ return this.changed(), l.filter((e) => !e.unattached);
135
+ }
136
+ dispose() {
137
+ this.started = !1, this.disposed = !0;
138
+ for (let e of this.running.values()) e.abort();
139
+ clearTimeout(this.poll), this.listeners.clear();
140
+ }
141
+ stage(t) {
142
+ let n = this.find(t);
143
+ if (!n?.result) return;
144
+ let { blob: a, type: o } = n.result, s = {
145
+ op: "put",
146
+ path: n.path,
147
+ blob: a,
148
+ create_id: n.id,
149
+ unattached: !0,
150
+ ...n.meta ? { meta: n.meta } : {}
151
+ }, c = this.client.commit(this.o.ref, [s], { sources: { [a]: {
152
+ file: n.file,
153
+ type: o
154
+ } } }).then((e) => {
155
+ let a = e.find((e) => e.unattached && r(e.path, i(n.path)));
156
+ return a && this.find(t) && this.patch(t, {
157
+ unattached: !0,
158
+ path: a.path
159
+ }), this.schedulePoll(), a;
160
+ }, (n) => {
161
+ n instanceof e && n.code === "conflict" && this.patch(t, {
162
+ status: "failed",
163
+ error: n
164
+ });
165
+ }).finally(() => this.staging.delete(t));
166
+ this.staging.set(t, c);
167
+ }
168
+ schedulePoll() {
169
+ this.poll || this.disposed || (this.poll = setTimeout(() => {
170
+ this.poll = void 0, this.refresh().finally(() => {
171
+ this.items.some((e) => e.unattached && !e.processed) && this.schedulePoll();
172
+ });
173
+ }, this.o.pollInterval ?? 2e3));
174
+ }
175
+ async refresh() {
176
+ let e = this.items.filter((e) => e.unattached && !e.processed);
177
+ if (e.length === 0) return;
178
+ let t;
179
+ try {
180
+ t = (await this.client.read(this.o.ref, { editor: !0 })).files;
181
+ } catch {
182
+ return;
183
+ }
184
+ let n = new Map(t.filter((e) => e.upload).map((e) => [e.path, e])), r = !1;
185
+ for (let t of e) {
186
+ let e = n.get(t.path);
187
+ e && (r = this.set(t.id, {
188
+ processing: e,
189
+ processed: !!e.failed || !e.pending?.length && !e.staged
190
+ }) || r);
191
+ }
192
+ r && this.changed();
193
+ }
194
+ find(e) {
195
+ return this.items.find((t) => t.id === e);
196
+ }
197
+ patch(e, t) {
198
+ this.set(e, t) && this.changed();
199
+ }
200
+ set(e, t) {
201
+ let n = this.items.findIndex((t) => t.id === e);
202
+ return n >= 0 && (this.items[n] = {
203
+ ...this.items[n],
204
+ ...t
205
+ }), n >= 0;
206
+ }
207
+ changed() {
208
+ this.pump(), this.publish();
209
+ for (let e of this.listeners) e();
210
+ }
211
+ publish() {
212
+ this.snap = {
213
+ items: [...this.items],
214
+ blocked: this.blocked,
215
+ ready: this.running.size === 0 && this.items.length > 0 && this.items.every((e) => e.status === "uploaded" || e.status === "committed")
216
+ };
217
+ }
218
+ pump() {
219
+ if (!this.started || this.blocked) return;
220
+ let e = this.o.concurrency ?? 2;
221
+ for (let t of this.items) {
222
+ if (this.running.size >= e) return;
223
+ t.status === "queued" && !this.running.has(t.id) && this.run(t.id);
224
+ }
225
+ }
226
+ run(e) {
227
+ let n = this.find(e), r = new AbortController();
228
+ this.running.set(e, r), this.set(e, {
229
+ status: "uploading",
230
+ progress: void 0,
231
+ error: void 0
232
+ }), this.client.upload(n.file, {
233
+ ref: this.o.ref,
234
+ path: n.path,
235
+ signal: r.signal,
236
+ resume: n.state,
237
+ onProgress: (t) => this.patch(e, { progress: t }),
238
+ onState: (t) => {
239
+ this.patch(e, { state: t ?? void 0 });
240
+ let n = this.find(e);
241
+ n && this.o.onState?.(n, t);
242
+ }
243
+ }).then((t) => {
244
+ this.running.delete(e), this.patch(e, {
245
+ status: "uploaded",
246
+ result: t,
247
+ path: t.path,
248
+ state: void 0
249
+ }), t.processOnUpload && this.stage(e);
250
+ }, (n) => {
251
+ this.running.delete(e);
252
+ let r = t(n);
253
+ if (r.code === "aborted") {
254
+ this.patch(e, { status: "queued" });
255
+ return;
256
+ }
257
+ if (r.blocksQueue) {
258
+ this.blocked = r, this.patch(e, {
259
+ status: "queued",
260
+ error: r
261
+ });
262
+ return;
263
+ }
264
+ this.patch(e, {
265
+ status: "failed",
266
+ error: r
267
+ });
268
+ });
269
+ }
270
+ };
271
+ //#endregion
272
+ export { o as UploadQueue };
273
+
274
+ //# sourceMappingURL=queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.js","names":[],"sources":["../../../src/client/media/queue.ts"],"sourcesContent":["import { ContentKitError, toContentKitError } from \"../errors.js\";\nimport type { FileInfo, Op, RefBody } from \"../generated/wire.js\";\nimport { fill } from \"../public.js\";\nimport { samePath, stem, type MediaClient, type Progress, type UploadedFile, type UploadState } from \"./client.js\";\n\nexport type ItemStatus = \"queued\" | \"uploading\" | \"uploaded\" | \"failed\" | \"committed\";\n\nexport interface QueueItem {\n readonly id: string;\n readonly file: File;\n /** The upload path the commit puts; the server's canonical one once uploaded. */\n path: string;\n meta?: Record<string, unknown>;\n status: ItemStatus;\n progress?: Progress;\n error?: ContentKitError;\n result?: UploadedFile;\n /** Resumable multipart state while uploading or after a failure. */\n state?: UploadState;\n /**\n * Committed unattached for processing on upload (the host's\n * ProcessOnUpload): commit() attaches it, remove() discards it.\n */\n unattached?: boolean;\n /** The server's view of an unattached upload while it processes (editor read): dims, pending, failed, progress. */\n processing?: FileInfo;\n /** Processing finished (placed and outputs written, or failed). */\n processed?: boolean;\n}\n\nexport interface QueueSnapshot {\n readonly items: readonly QueueItem[];\n /** A rate, quota or permission refusal: no new uploads start until start(). */\n readonly blocked?: ContentKitError;\n /** Every item is uploaded (or committed) and none is in flight. */\n readonly ready: boolean;\n}\n\nexport interface QueueOptions {\n ref: RefBody;\n /** Each file's upload path: a template whose {name} is the file's name, e.g. \"originals/{name}\". */\n path: string;\n /** Files uploading at once. Default 2. */\n concurrency?: number;\n /** Start uploads as files are added. Default true. */\n autoStart?: boolean;\n /** Called with every item state change, to persist resumable state. */\n onState?: (item: QueueItem, state: UploadState | null) => void;\n /** Milliseconds between processing polls of unattached uploads. Default 2000. */\n pollInterval?: number;\n}\n\n/** An op index past the end of the list: the upload is appended. */\nconst END = 2 ** 31 - 1;\n\n/**\n * An ordered file queue for one item: uploads in the background, keeps the\n * order the user arranges, and commits the uploads as puts appended in that\n * order. Framework-free; the React hooks wrap it.\n */\nexport class UploadQueue {\n private items: QueueItem[] = [];\n private blocked?: ContentKitError;\n private running = new Map<string, AbortController>();\n private listeners = new Set<() => void>();\n private snap!: QueueSnapshot;\n private started: boolean;\n /** In-flight unattached commits, by item id. */\n private staging = new Map<string, Promise<FileInfo | undefined>>();\n private poll?: ReturnType<typeof setTimeout>;\n private disposed = false;\n private batch?: { input: string; operationID: string };\n\n constructor(\n private readonly client: MediaClient,\n private readonly o: QueueOptions,\n ) {\n this.started = o.autoStart ?? true;\n this.publish();\n }\n\n subscribe = (fn: () => void): (() => void) => {\n this.listeners.add(fn);\n return () => this.listeners.delete(fn);\n };\n\n getSnapshot = (): QueueSnapshot => this.snap;\n\n /** Queues files; state restores a multipart upload saved through onState before a reload. */\n add(\n files: Iterable<File>,\n opts: { path?: (f: File) => string; state?: (f: File) => UploadState | undefined } = {},\n ): QueueItem[] {\n const added = [...files].map(\n (file): QueueItem => ({\n id: crypto.randomUUID(),\n file,\n path: opts.path?.(file) ?? fill(this.o.path, { name: file.name }),\n status: \"queued\",\n state: opts.state?.(file),\n }),\n );\n this.items.push(...added);\n this.changed();\n return added;\n }\n\n /** Moves an item to index (before commit). */\n move(id: string, index: number): void {\n const from = this.items.findIndex((i) => i.id === id);\n if (from < 0) return;\n const [item] = this.items.splice(from, 1);\n this.items.splice(Math.max(0, Math.min(index, this.items.length)), 0, item!);\n this.changed();\n }\n\n update(id: string, patch: Pick<Partial<QueueItem>, \"path\" | \"meta\">): void {\n const item = this.find(id);\n if (item?.unattached && patch.path && patch.path !== item.path) {\n void this.client.commit(this.o.ref, [{ op: \"rename\", path: item.path, to: patch.path }]).then(\n (files) => {\n const f = files.find((x) => samePath(x.path, stem(patch.path!)));\n if (f) this.patch(id, { path: f.path });\n },\n () => {},\n );\n }\n this.patch(id, patch);\n }\n\n /**\n * Removes an item, discarding its multipart upload if one is open, or its\n * unattached upload (the commit removes it and its outputs).\n */\n remove(id: string): void {\n const item = this.find(id);\n if (!item) return;\n this.running.get(id)?.abort();\n if (item.state) void this.client.discard(item.state).catch(() => {});\n const staged = this.staging.get(id);\n if (item.unattached || staged) {\n void (staged ?? Promise.resolve())\n .then((file) => {\n if (file || item.unattached) {\n return this.client.commit(this.o.ref, [{ op: \"remove\", path: file?.path ?? item.path }]);\n }\n })\n .catch(() => {});\n }\n this.o.onState?.(item, null);\n this.items = this.items.filter((i) => i.id !== id);\n this.changed();\n }\n\n /** Re-queues a failed item; a multipart upload resumes from its state. */\n retry(id: string): void {\n this.patch(id, { status: \"queued\", error: undefined });\n }\n\n /** Starts uploading (autoStart false) or lifts a block after a refusal. */\n start(): void {\n this.started = true;\n this.blocked = undefined;\n this.changed();\n }\n\n /** Stops starting uploads and pauses running ones (they stay resumable). */\n pause(): void {\n this.started = false;\n for (const c of this.running.values()) c.abort();\n this.changed();\n }\n\n /**\n * Commits every uploaded item, in queue order, after the item's uploads:\n * unattached ones are attached (no reprocessing), the rest put. With head,\n * only the uploaded items before the first one still uploading or failed\n * (a draft committing files as they finish). Returns the item's attached\n * uploads in order.\n */\n async commit(signal?: AbortSignal, o: { head?: boolean } = {}): Promise<FileInfo[]> {\n await Promise.all(this.staging.values());\n let items = this.items;\n if (o.head) {\n // Only the uploaded head of the queue, so files land in the order added.\n const end = items.findIndex((i) => i.status !== \"uploaded\" && i.status !== \"committed\");\n if (end >= 0) items = items.slice(0, end);\n }\n const ready = items.filter((i) => i.status === \"uploaded\");\n if (ready.length === 0) return [];\n const ops = ready.flatMap((i): Op[] => {\n const attach: Op = { op: \"attach\", path: i.path, index: END, ...(i.meta ? { meta: i.meta } : {}) };\n if (i.unattached) return [attach];\n const put: Op = { op: \"put\", path: i.path, blob: i.result!.blob, create_id: i.id, index: END, ...(i.meta ? { meta: i.meta } : {}) };\n // A lost staging response may hide a successful unattached create.\n return i.result!.processOnUpload ? [put, attach] : [put];\n });\n const sources = Object.fromEntries(ready.map((i) => [i.result!.blob, { file: i.file, type: i.result!.type }]));\n const input = JSON.stringify(ops);\n if (this.batch?.input !== input) this.batch = { input, operationID: crypto.randomUUID() };\n const batch = this.batch;\n const files = await this.client.commit(this.o.ref, ops, {\n signal,\n sources,\n operationID: batch.operationID,\n onState: (body) => {\n batch.input = JSON.stringify(body.ops);\n batch.operationID = body.operation_id;\n let changed = false;\n for (const op of body.ops) {\n if (op.op !== \"put\" || !op.blob) continue;\n const original = ready.find((i) => i.id === op.create_id);\n if (!original?.result) continue;\n const current = this.find(original.id);\n if (current?.result && current.result.blob === original.result.blob && current.result.blob !== op.blob) {\n changed = this.set(current.id, { result: { ...current.result, blob: op.blob } }) || changed;\n }\n }\n if (changed) this.changed();\n },\n });\n for (const i of ready) this.set(i.id, { status: \"committed\", unattached: false });\n this.changed();\n return files.filter((f) => !f.unattached);\n }\n\n /** Aborts running uploads and stops polling; call on unmount. */\n dispose(): void {\n this.started = false;\n this.disposed = true;\n for (const c of this.running.values()) c.abort();\n clearTimeout(this.poll);\n this.listeners.clear();\n }\n\n /** Commits an uploaded file unattached, so the host processes it now. */\n private stage(id: string): void {\n const item = this.find(id);\n if (!item?.result) return;\n const { blob, type } = item.result;\n const op: Op = { op: \"put\", path: item.path, blob, create_id: item.id, unattached: true, ...(item.meta ? { meta: item.meta } : {}) };\n const p = this.client\n .commit(this.o.ref, [op], { sources: { [blob]: { file: item.file, type } } })\n .then(\n (files) => {\n const f = files.find((x) => x.unattached && samePath(x.path, stem(item.path)));\n if (f && this.find(id)) this.patch(id, { unattached: true, path: f.path });\n this.schedulePoll();\n return f;\n },\n (error: unknown) => {\n if (error instanceof ContentKitError && error.code === \"conflict\") {\n this.patch(id, { status: \"failed\", error });\n }\n // Other failures leave it uploaded; commit() retries the same create.\n return undefined;\n },\n )\n .finally(() => this.staging.delete(id));\n this.staging.set(id, p);\n }\n\n private schedulePoll(): void {\n if (this.poll || this.disposed) return;\n this.poll = setTimeout(() => {\n this.poll = undefined;\n void this.refresh().finally(() => {\n if (this.items.some((i) => i.unattached && !i.processed)) this.schedulePoll();\n });\n }, this.o.pollInterval ?? 2000);\n }\n\n /** Reads the processing state of unattached uploads still processing. */\n private async refresh(): Promise<void> {\n const pending = this.items.filter((i) => i.unattached && !i.processed);\n if (pending.length === 0) return;\n let files: FileInfo[];\n try {\n files = (await this.client.read(this.o.ref, { editor: true })).files;\n } catch {\n return;\n }\n const byPath = new Map(files.filter((f) => f.upload).map((f) => [f.path, f]));\n let changed = false;\n for (const i of pending) {\n const f = byPath.get(i.path);\n if (!f) continue;\n changed = this.set(i.id, { processing: f, processed: !!f.failed || (!f.pending?.length && !f.staged) }) || changed;\n }\n if (changed) this.changed();\n }\n\n private find(id: string): QueueItem | undefined {\n return this.items.find((i) => i.id === id);\n }\n\n private patch(id: string, patch: Partial<QueueItem>): void {\n if (this.set(id, patch)) this.changed();\n }\n\n private set(id: string, patch: Partial<QueueItem>): boolean {\n const i = this.items.findIndex((x) => x.id === id);\n if (i >= 0) this.items[i] = { ...this.items[i]!, ...patch };\n return i >= 0;\n }\n\n private changed(): void {\n this.pump();\n this.publish();\n for (const fn of this.listeners) fn();\n }\n\n private publish(): void {\n this.snap = {\n items: [...this.items],\n blocked: this.blocked,\n ready:\n this.running.size === 0 &&\n this.items.length > 0 &&\n this.items.every((i) => i.status === \"uploaded\" || i.status === \"committed\"),\n };\n }\n\n private pump(): void {\n if (!this.started || this.blocked) return;\n const limit = this.o.concurrency ?? 2;\n for (const item of this.items) {\n if (this.running.size >= limit) return;\n if (item.status === \"queued\" && !this.running.has(item.id)) this.run(item.id);\n }\n }\n\n private run(id: string): void {\n const item = this.find(id)!;\n const ctl = new AbortController();\n this.running.set(id, ctl);\n this.set(id, { status: \"uploading\", progress: undefined, error: undefined });\n this.client\n .upload(item.file, {\n ref: this.o.ref,\n path: item.path,\n signal: ctl.signal,\n resume: item.state,\n onProgress: (progress) => this.patch(id, { progress }),\n onState: (state) => {\n this.patch(id, { state: state ?? undefined });\n const cur = this.find(id);\n if (cur) this.o.onState?.(cur, state);\n },\n })\n .then(\n (result) => {\n this.running.delete(id);\n this.patch(id, { status: \"uploaded\", result, path: result.path, state: undefined });\n if (result.processOnUpload) this.stage(id);\n },\n (e: unknown) => {\n this.running.delete(id);\n const error = toContentKitError(e);\n if (error.code === \"aborted\") {\n this.patch(id, { status: \"queued\" });\n return;\n }\n if (error.blocksQueue) {\n // Refused for every file: keep it queued behind the block.\n this.blocked = error;\n this.patch(id, { status: \"queued\", error });\n return;\n }\n this.patch(id, { status: \"failed\", error });\n },\n );\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /** Content ids are canonical lowercase UUIDv7s (contentref.ValidateID). */
2
+ export declare const isContentId: (id: string) => boolean;
3
+ /** Refuses a ref whose id ContentKit would reject, before any request. */
4
+ export declare function checkRef(ref: {
5
+ id: string;
6
+ } | undefined): void;
@@ -0,0 +1,10 @@
1
+ import { ContentKitError as e } from "../errors.js";
2
+ //#region src/client/media/ref.ts
3
+ var t = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/, n = (e) => t.test(e);
4
+ function r(t) {
5
+ if (t && !n(t.id)) throw new e("invalid_request", `content id must be a canonical UUIDv7, got "${t.id}"`);
6
+ }
7
+ //#endregion
8
+ export { r as checkRef, n as isContentId };
9
+
10
+ //# sourceMappingURL=ref.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ref.js","names":[],"sources":["../../../src/client/media/ref.ts"],"sourcesContent":["import { ContentKitError } from \"../errors.js\";\n\nconst contentId = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;\n\n/** Content ids are canonical lowercase UUIDv7s (contentref.ValidateID). */\nexport const isContentId = (id: string) => contentId.test(id);\n\n/** Refuses a ref whose id ContentKit would reject, before any request. */\nexport function checkRef(ref: { id: string } | undefined) {\n if (ref && !isContentId(ref.id)) throw new ContentKitError(\"invalid_request\", `content id must be a canonical UUIDv7, got \"${ref.id}\"`);\n}\n"],"mappings":";;AAEA,IAAM,IAAY,yEAGL,KAAe,MAAe,EAAU,KAAK,CAAE;AAG5D,SAAgB,EAAS,GAAiC;CACxD,IAAI,KAAO,CAAC,EAAY,EAAI,EAAE,GAAG,MAAM,IAAI,EAAgB,mBAAmB,+CAA+C,EAAI,GAAG,EAAE;AACxI"}
@@ -0,0 +1,54 @@
1
+ import { ContentKitError } from '../errors.js';
2
+ import { PresetRule, ReadResult, UploadRule } from '../generated/wire.js';
3
+ import { PublicPreset } from '../public.js';
4
+ /** An editor read's upload rules (a viewer read has none). */
5
+ export declare const uploadRules: (read: ReadResult | null | undefined) => UploadRule[];
6
+ /** A pattern path ("originals/{name}") holds many files; a literal ("cover") one. */
7
+ export declare const isPattern: (path: string) => boolean;
8
+ /** The directory of a pattern ("originals/"), or the literal itself. */
9
+ export declare const ruleDir: (path: string) => string;
10
+ /** The rule an upload path (with or without its extension) falls under: a literal wins over a pattern. */
11
+ export declare function ruleFor<R extends {
12
+ path: string;
13
+ }>(rules: readonly R[], path: string): R | undefined;
14
+ /** Uploads of the read under rule (attached, in manifest order). */
15
+ export declare function filesFor(read: ReadResult | null | undefined, rule: {
16
+ path: string;
17
+ }, rules?: readonly {
18
+ path: string;
19
+ }[]): import('../index.js').FileInfo[];
20
+ /** The file with a content type from its extension when the browser left it empty or nonstandard (.mkv, .mov, .srt). */
21
+ export declare function withMediaType(file: File, allowed?: readonly string[]): File;
22
+ /** `accept` for a file input from the rules' types (plus the extensions browsers leave untyped). */
23
+ export declare function acceptOf(rules: readonly UploadRule[]): string | undefined;
24
+ export interface Screened {
25
+ /** Files to upload, each with its rule (typed by extension when needed). */
26
+ accepted: {
27
+ file: File;
28
+ rule: UploadRule;
29
+ }[];
30
+ /** Files the server would refuse, with the refusal it would answer. */
31
+ refused: {
32
+ file: File;
33
+ error: ContentKitError;
34
+ }[];
35
+ }
36
+ /**
37
+ * Checks files against the rules before any upload: a type some rule takes
38
+ * (the first that does), its size, and the rule's file cap counting
39
+ * have(rule) files already there. A rule without types, size or cap lets
40
+ * that check through; the server decides.
41
+ */
42
+ export declare function screenFiles(files: Iterable<File>, rules: readonly UploadRule[], have?: (rule: UploadRule) => number): Screened;
43
+ /** name, or name-2, name-3… so its stem is not among taken (names or paths without the directory). */
44
+ export declare function uniqueName(name: string, taken: Iterable<string>): string;
45
+ /** "16:9" style label of a width/height ratio: 2.4 → "2.4:1", 0.4167 → "1:2.4". */
46
+ export declare function ratioLabel(r: number): string;
47
+ /** The kind's public preset for an upload path: the one named name, else the first with an aspect, else the first. */
48
+ export declare function presetFor(presets: readonly PresetRule[], kind: string, path: string, name?: string): PresetRule | undefined;
49
+ /**
50
+ * The kind's default image for a preset, at every width: what the gateway
51
+ * serves for an item without its own (a URL from the template never names a
52
+ * current file). Empty for a preview preset.
53
+ */
54
+ export declare function defaultImage(rule: PresetRule, id: string): PublicPreset;
@@ -0,0 +1,143 @@
1
+ import { ContentKitError as e } from "../errors.js";
2
+ import { fill as t, publicURL as n } from "../public.js";
3
+ import { ratio as r } from "../aspect.js";
4
+ import { stem as i } from "./client.js";
5
+ //#region src/client/media/rules.ts
6
+ var a = (e) => e?.uploads ?? [], o = "{name}", s = (e) => e.endsWith(o), c = (e) => s(e) ? e.slice(0, -6) : e;
7
+ function l(e, t) {
8
+ let n = i(t);
9
+ return e.find((e) => !s(e.path) && e.path === n) ?? e.find((e) => {
10
+ if (!s(e.path)) return !1;
11
+ let t = c(e.path);
12
+ return n.startsWith(t) && n.length > t.length && !n.slice(t.length).includes("/");
13
+ });
14
+ }
15
+ function u(e, t, n = [t]) {
16
+ return (e?.files ?? []).filter((e) => e.upload && !e.unattached && l(n, e.path)?.path === t.path);
17
+ }
18
+ var d = {
19
+ jpg: "image/jpeg",
20
+ jpeg: "image/jpeg",
21
+ png: "image/png",
22
+ webp: "image/webp",
23
+ gif: "image/gif",
24
+ avif: "image/avif",
25
+ mp4: "video/mp4",
26
+ m4v: "video/mp4",
27
+ mov: "video/quicktime",
28
+ mkv: "video/x-matroska",
29
+ webm: "video/webm",
30
+ m4a: "audio/mp4",
31
+ mp3: "audio/mpeg",
32
+ vtt: "text/vtt",
33
+ srt: "application/x-subrip",
34
+ ass: "text/x-ass",
35
+ ssa: "text/x-ssa",
36
+ zip: "application/zip"
37
+ };
38
+ function f(e, t) {
39
+ if (e.type && (!t || t.includes(e.type))) return e;
40
+ let n = d[e.name.split(".").pop()?.toLowerCase() ?? ""];
41
+ return n && n !== e.type ? new File([e], e.name, {
42
+ type: n,
43
+ lastModified: e.lastModified
44
+ }) : e;
45
+ }
46
+ function p(e) {
47
+ let t = [...new Set(e.flatMap((e) => e.types))];
48
+ if (!t.length) return;
49
+ let n = Object.entries(d).filter(([, e]) => t.includes(e)).map(([e]) => `.${e}`);
50
+ return [...t, ...n].join(",");
51
+ }
52
+ function m(t, n, r = () => 0) {
53
+ let i = {
54
+ accepted: [],
55
+ refused: []
56
+ }, a = /* @__PURE__ */ new Map(), o = [...new Set(n.flatMap((e) => e.types))];
57
+ for (let s of t) {
58
+ let t = f(s, o), c = n.find((e) => !e.types.length || e.types.includes(t.type));
59
+ if (!c) {
60
+ let n = {
61
+ type: t.type || void 0,
62
+ allowed: o
63
+ };
64
+ i.refused.push({
65
+ file: t,
66
+ error: new e("type_not_allowed", `${t.name}: type not allowed`, {
67
+ status: 415,
68
+ details: n
69
+ })
70
+ });
71
+ continue;
72
+ }
73
+ if (c.max_bytes > 0 && t.size > c.max_bytes) {
74
+ let n = {
75
+ type: t.type,
76
+ size: t.size,
77
+ max_bytes: c.max_bytes
78
+ };
79
+ i.refused.push({
80
+ file: t,
81
+ error: new e("too_large", `${t.name}: too large`, {
82
+ status: 413,
83
+ details: n
84
+ })
85
+ });
86
+ continue;
87
+ }
88
+ let l = a.get(c) ?? r(c);
89
+ if (c.max && l >= c.max) {
90
+ let n = { max: c.max };
91
+ i.refused.push({
92
+ file: t,
93
+ error: new e("too_many_files", `${t.name}: at most ${c.max} files`, {
94
+ status: 409,
95
+ details: n
96
+ })
97
+ });
98
+ continue;
99
+ }
100
+ a.set(c, l + 1), i.accepted.push({
101
+ file: t,
102
+ rule: c
103
+ });
104
+ }
105
+ return i;
106
+ }
107
+ function h(e, t) {
108
+ let n = new Set([...t].map(i));
109
+ if (!n.has(i(e))) return e;
110
+ let r = e.lastIndexOf("."), a = r > 0 ? e.slice(0, r) : e, o = r > 0 ? e.slice(r) : "";
111
+ for (let e = 2;; e++) {
112
+ let t = `${a}-${e}${o}`;
113
+ if (!n.has(i(t))) return t;
114
+ }
115
+ }
116
+ function g(e) {
117
+ let t = (e) => String(Math.round(e * 100) / 100);
118
+ return e >= 1 ? `${t(e)}:1` : `1:${t(1 / e)}`;
119
+ }
120
+ function _(e, t, n, r) {
121
+ let i = e.filter((e) => e.kind === t && l([{ path: e.from }], n));
122
+ return (r ? i.find((e) => e.name === r) : void 0) ?? i.find((e) => e.aspect) ?? i[0];
123
+ }
124
+ function v(e, i) {
125
+ let a = r(e.aspect);
126
+ return e.first || e.to.includes(o) ? {
127
+ preset: e.name,
128
+ aspect: e.aspect,
129
+ renditions: []
130
+ } : {
131
+ preset: e.name,
132
+ aspect: e.aspect,
133
+ renditions: e.widths.map((r) => ({
134
+ url: n(e.base, e.namespace, e.kind, i, t(e.to, { w: r })),
135
+ w: r,
136
+ ...a ? { h: Math.round(r / a) } : {}
137
+ }))
138
+ };
139
+ }
140
+ //#endregion
141
+ export { p as acceptOf, v as defaultImage, u as filesFor, s as isPattern, _ as presetFor, g as ratioLabel, c as ruleDir, l as ruleFor, m as screenFiles, h as uniqueName, a as uploadRules, f as withMediaType };
142
+
143
+ //# sourceMappingURL=rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.js","names":[],"sources":["../../../src/client/media/rules.ts"],"sourcesContent":["import { ContentKitError } from \"../errors.js\";\nimport type { ErrorDetails, PresetRule, ReadResult, UploadRule } from \"../generated/wire.js\";\nimport { fill, publicURL, type PublicPreset } from \"../public.js\";\nimport { ratio } from \"../aspect.js\";\nimport { stem } from \"./client.js\";\n\n/** An editor read's upload rules (a viewer read has none). */\nexport const uploadRules = (read: ReadResult | null | undefined): UploadRule[] => read?.uploads ?? [];\n\nconst NAME = \"{name}\";\n\n/** A pattern path (\"originals/{name}\") holds many files; a literal (\"cover\") one. */\nexport const isPattern = (path: string) => path.endsWith(NAME);\n\n/** The directory of a pattern (\"originals/\"), or the literal itself. */\nexport const ruleDir = (path: string) => (isPattern(path) ? path.slice(0, -NAME.length) : path);\n\n/** The rule an upload path (with or without its extension) falls under: a literal wins over a pattern. */\nexport function ruleFor<R extends { path: string }>(rules: readonly R[], path: string): R | undefined {\n const s = stem(path);\n return (\n rules.find((r) => !isPattern(r.path) && r.path === s) ??\n rules.find((r) => {\n if (!isPattern(r.path)) return false;\n const dir = ruleDir(r.path);\n return s.startsWith(dir) && s.length > dir.length && !s.slice(dir.length).includes(\"/\");\n })\n );\n}\n\n/** Uploads of the read under rule (attached, in manifest order). */\nexport function filesFor(read: ReadResult | null | undefined, rule: { path: string }, rules: readonly { path: string }[] = [rule]) {\n return (read?.files ?? []).filter((f) => f.upload && !f.unattached && ruleFor(rules, f.path)?.path === rule.path);\n}\n\nconst BY_EXTENSION: Record<string, string> = {\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n png: \"image/png\",\n webp: \"image/webp\",\n gif: \"image/gif\",\n avif: \"image/avif\",\n mp4: \"video/mp4\",\n m4v: \"video/mp4\",\n mov: \"video/quicktime\",\n mkv: \"video/x-matroska\",\n webm: \"video/webm\",\n m4a: \"audio/mp4\",\n mp3: \"audio/mpeg\",\n vtt: \"text/vtt\",\n srt: \"application/x-subrip\",\n ass: \"text/x-ass\",\n ssa: \"text/x-ssa\",\n zip: \"application/zip\",\n};\n\n/** The file with a content type from its extension when the browser left it empty or nonstandard (.mkv, .mov, .srt). */\nexport function withMediaType(file: File, allowed?: readonly string[]): File {\n if (file.type && (!allowed || allowed.includes(file.type))) return file;\n const type = BY_EXTENSION[file.name.split(\".\").pop()?.toLowerCase() ?? \"\"];\n return type && type !== file.type ? new File([file], file.name, { type, lastModified: file.lastModified }) : file;\n}\n\n/** `accept` for a file input from the rules' types (plus the extensions browsers leave untyped). */\nexport function acceptOf(rules: readonly UploadRule[]): string | undefined {\n const types = [...new Set(rules.flatMap((r) => r.types))];\n if (!types.length) return undefined;\n const exts = Object.entries(BY_EXTENSION).filter(([, t]) => types.includes(t)).map(([e]) => `.${e}`);\n return [...types, ...exts].join(\",\");\n}\n\nexport interface Screened {\n /** Files to upload, each with its rule (typed by extension when needed). */\n accepted: { file: File; rule: UploadRule }[];\n /** Files the server would refuse, with the refusal it would answer. */\n refused: { file: File; error: ContentKitError }[];\n}\n\n/**\n * Checks files against the rules before any upload: a type some rule takes\n * (the first that does), its size, and the rule's file cap counting\n * have(rule) files already there. A rule without types, size or cap lets\n * that check through; the server decides.\n */\nexport function screenFiles(files: Iterable<File>, rules: readonly UploadRule[], have: (rule: UploadRule) => number = () => 0): Screened {\n const out: Screened = { accepted: [], refused: [] };\n const counts = new Map<UploadRule, number>();\n const allowed = [...new Set(rules.flatMap((r) => r.types))];\n for (const picked of files) {\n const file = withMediaType(picked, allowed);\n const rule = rules.find((r) => !r.types.length || r.types.includes(file.type));\n if (!rule) {\n const details: ErrorDetails = { type: file.type || undefined, allowed };\n out.refused.push({ file, error: new ContentKitError(\"type_not_allowed\", `${file.name}: type not allowed`, { status: 415, details }) });\n continue;\n }\n if (rule.max_bytes > 0 && file.size > rule.max_bytes) {\n const details: ErrorDetails = { type: file.type, size: file.size, max_bytes: rule.max_bytes };\n out.refused.push({ file, error: new ContentKitError(\"too_large\", `${file.name}: too large`, { status: 413, details }) });\n continue;\n }\n const n = counts.get(rule) ?? have(rule);\n if (rule.max && n >= rule.max) {\n const details: ErrorDetails = { max: rule.max };\n out.refused.push({ file, error: new ContentKitError(\"too_many_files\", `${file.name}: at most ${rule.max} files`, { status: 409, details }) });\n continue;\n }\n counts.set(rule, n + 1);\n out.accepted.push({ file, rule });\n }\n return out;\n}\n\n/** name, or name-2, name-3… so its stem is not among taken (names or paths without the directory). */\nexport function uniqueName(name: string, taken: Iterable<string>): string {\n const stems = new Set([...taken].map(stem));\n if (!stems.has(stem(name))) return name;\n const dot = name.lastIndexOf(\".\");\n const base = dot > 0 ? name.slice(0, dot) : name;\n const ext = dot > 0 ? name.slice(dot) : \"\";\n for (let n = 2; ; n++) {\n const candidate = `${base}-${n}${ext}`;\n if (!stems.has(stem(candidate))) return candidate;\n }\n}\n\n/** \"16:9\" style label of a width/height ratio: 2.4 → \"2.4:1\", 0.4167 → \"1:2.4\". */\nexport function ratioLabel(r: number): string {\n const round = (v: number) => String(Math.round(v * 100) / 100);\n return r >= 1 ? `${round(r)}:1` : `1:${round(1 / r)}`;\n}\n\n/** The kind's public preset for an upload path: the one named name, else the first with an aspect, else the first. */\nexport function presetFor(presets: readonly PresetRule[], kind: string, path: string, name?: string): PresetRule | undefined {\n const at = presets.filter((p) => p.kind === kind && ruleFor([{ path: p.from }], path));\n return (name ? at.find((p) => p.name === name) : undefined) ?? at.find((p) => p.aspect) ?? at[0];\n}\n\n/**\n * The kind's default image for a preset, at every width: what the gateway\n * serves for an item without its own (a URL from the template never names a\n * current file). Empty for a preview preset.\n */\nexport function defaultImage(rule: PresetRule, id: string): PublicPreset {\n const r = ratio(rule.aspect);\n if (rule.first || rule.to.includes(NAME)) return { preset: rule.name, aspect: rule.aspect, renditions: [] };\n return {\n preset: rule.name,\n aspect: rule.aspect,\n renditions: rule.widths.map((w) => ({ url: publicURL(rule.base, rule.namespace, rule.kind, id, fill(rule.to, { w })), w, ...(r ? { h: Math.round(w / r) } : {}) })),\n };\n}\n"],"mappings":";;;;;AAOA,IAAa,KAAe,MAAsD,GAAM,WAAW,CAAC,GAE9F,IAAO,UAGA,KAAa,MAAiB,EAAK,SAAS,CAAI,GAGhD,KAAW,MAAkB,EAAU,CAAI,IAAI,EAAK,MAAM,GAAG,EAAY,IAAI;AAG1F,SAAgB,EAAoC,GAAqB,GAA6B;CACpG,IAAM,IAAI,EAAK,CAAI;CACnB,OACE,EAAM,MAAM,MAAM,CAAC,EAAU,EAAE,IAAI,KAAK,EAAE,SAAS,CAAC,KACpD,EAAM,MAAM,MAAM;EAChB,IAAI,CAAC,EAAU,EAAE,IAAI,GAAG,OAAO;EAC/B,IAAM,IAAM,EAAQ,EAAE,IAAI;EAC1B,OAAO,EAAE,WAAW,CAAG,KAAK,EAAE,SAAS,EAAI,UAAU,CAAC,EAAE,MAAM,EAAI,MAAM,CAAC,CAAC,SAAS,GAAG;CACxF,CAAC;AAEL;AAGA,SAAgB,EAAS,GAAqC,GAAwB,IAAqC,CAAC,CAAI,GAAG;CACjI,QAAQ,GAAM,SAAS,CAAC,EAAA,CAAG,QAAQ,MAAM,EAAE,UAAU,CAAC,EAAE,cAAc,EAAQ,GAAO,EAAE,IAAI,CAAC,EAAE,SAAS,EAAK,IAAI;AAClH;AAEA,IAAM,IAAuC;CAC3C,KAAK;CACL,MAAM;CACN,KAAK;CACL,MAAM;CACN,KAAK;CACL,MAAM;CACN,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,MAAM;CACN,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;AACP;AAGA,SAAgB,EAAc,GAAY,GAAmC;CAC3E,IAAI,EAAK,SAAS,CAAC,KAAW,EAAQ,SAAS,EAAK,IAAI,IAAI,OAAO;CACnE,IAAM,IAAO,EAAa,EAAK,KAAK,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,YAAY,KAAK;CACvE,OAAO,KAAQ,MAAS,EAAK,OAAO,IAAI,KAAK,CAAC,CAAI,GAAG,EAAK,MAAM;EAAE;EAAM,cAAc,EAAK;CAAa,CAAC,IAAI;AAC/G;AAGA,SAAgB,EAAS,GAAkD;CACzE,IAAM,IAAQ,CAAC,GAAG,IAAI,IAAI,EAAM,SAAS,MAAM,EAAE,KAAK,CAAC,CAAC;CACxD,IAAI,CAAC,EAAM,QAAQ;CACnB,IAAM,IAAO,OAAO,QAAQ,CAAY,CAAC,CAAC,QAAQ,GAAG,OAAO,EAAM,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,GAAG;CACnG,OAAO,CAAC,GAAG,GAAO,GAAG,CAAI,CAAC,CAAC,KAAK,GAAG;AACrC;AAeA,SAAgB,EAAY,GAAuB,GAA8B,UAA2C,GAAa;CACvI,IAAM,IAAgB;EAAE,UAAU,CAAC;EAAG,SAAS,CAAC;CAAE,GAC5C,oBAAS,IAAI,IAAwB,GACrC,IAAU,CAAC,GAAG,IAAI,IAAI,EAAM,SAAS,MAAM,EAAE,KAAK,CAAC,CAAC;CAC1D,KAAK,IAAM,KAAU,GAAO;EAC1B,IAAM,IAAO,EAAc,GAAQ,CAAO,GACpC,IAAO,EAAM,MAAM,MAAM,CAAC,EAAE,MAAM,UAAU,EAAE,MAAM,SAAS,EAAK,IAAI,CAAC;EAC7E,IAAI,CAAC,GAAM;GACT,IAAM,IAAwB;IAAE,MAAM,EAAK,QAAQ,KAAA;IAAW;GAAQ;GACtE,EAAI,QAAQ,KAAK;IAAE;IAAM,OAAO,IAAI,EAAgB,oBAAoB,GAAG,EAAK,KAAK,qBAAqB;KAAE,QAAQ;KAAK;IAAQ,CAAC;GAAE,CAAC;GACrI;EACF;EACA,IAAI,EAAK,YAAY,KAAK,EAAK,OAAO,EAAK,WAAW;GACpD,IAAM,IAAwB;IAAE,MAAM,EAAK;IAAM,MAAM,EAAK;IAAM,WAAW,EAAK;GAAU;GAC5F,EAAI,QAAQ,KAAK;IAAE;IAAM,OAAO,IAAI,EAAgB,aAAa,GAAG,EAAK,KAAK,cAAc;KAAE,QAAQ;KAAK;IAAQ,CAAC;GAAE,CAAC;GACvH;EACF;EACA,IAAM,IAAI,EAAO,IAAI,CAAI,KAAK,EAAK,CAAI;EACvC,IAAI,EAAK,OAAO,KAAK,EAAK,KAAK;GAC7B,IAAM,IAAwB,EAAE,KAAK,EAAK,IAAI;GAC9C,EAAI,QAAQ,KAAK;IAAE;IAAM,OAAO,IAAI,EAAgB,kBAAkB,GAAG,EAAK,KAAK,YAAY,EAAK,IAAI,SAAS;KAAE,QAAQ;KAAK;IAAQ,CAAC;GAAE,CAAC;GAC5I;EACF;EAEA,AADA,EAAO,IAAI,GAAM,IAAI,CAAC,GACtB,EAAI,SAAS,KAAK;GAAE;GAAM;EAAK,CAAC;CAClC;CACA,OAAO;AACT;AAGA,SAAgB,EAAW,GAAc,GAAiC;CACxE,IAAM,IAAQ,IAAI,IAAI,CAAC,GAAG,CAAK,CAAC,CAAC,IAAI,CAAI,CAAC;CAC1C,IAAI,CAAC,EAAM,IAAI,EAAK,CAAI,CAAC,GAAG,OAAO;CACnC,IAAM,IAAM,EAAK,YAAY,GAAG,GAC1B,IAAO,IAAM,IAAI,EAAK,MAAM,GAAG,CAAG,IAAI,GACtC,IAAM,IAAM,IAAI,EAAK,MAAM,CAAG,IAAI;CACxC,KAAK,IAAI,IAAI,IAAK,KAAK;EACrB,IAAM,IAAY,GAAG,EAAK,GAAG,IAAI;EACjC,IAAI,CAAC,EAAM,IAAI,EAAK,CAAS,CAAC,GAAG,OAAO;CAC1C;AACF;AAGA,SAAgB,EAAW,GAAmB;CAC5C,IAAM,KAAS,MAAc,OAAO,KAAK,MAAM,IAAI,GAAG,IAAI,GAAG;CAC7D,OAAO,KAAK,IAAI,GAAG,EAAM,CAAC,EAAE,MAAM,KAAK,EAAM,IAAI,CAAC;AACpD;AAGA,SAAgB,EAAU,GAAgC,GAAc,GAAc,GAAuC;CAC3H,IAAM,IAAK,EAAQ,QAAQ,MAAM,EAAE,SAAS,KAAQ,EAAQ,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG,CAAI,CAAC;CACrF,QAAQ,IAAO,EAAG,MAAM,MAAM,EAAE,SAAS,CAAI,IAAI,KAAA,MAAc,EAAG,MAAM,MAAM,EAAE,MAAM,KAAK,EAAG;AAChG;AAOA,SAAgB,EAAa,GAAkB,GAA0B;CACvE,IAAM,IAAI,EAAM,EAAK,MAAM;CAE3B,OADI,EAAK,SAAS,EAAK,GAAG,SAAS,CAAI,IAAU;EAAE,QAAQ,EAAK;EAAM,QAAQ,EAAK;EAAQ,YAAY,CAAC;CAAE,IACnG;EACL,QAAQ,EAAK;EACb,QAAQ,EAAK;EACb,YAAY,EAAK,OAAO,KAAK,OAAO;GAAE,KAAK,EAAU,EAAK,MAAM,EAAK,WAAW,EAAK,MAAM,GAAI,EAAK,EAAK,IAAI,EAAE,KAAE,CAAC,CAAC;GAAG;GAAG,GAAI,IAAI,EAAE,GAAG,KAAK,MAAM,IAAI,CAAC,EAAE,IAAI,CAAC;EAAG,EAAE;CACpK;AACF"}
@@ -0,0 +1,13 @@
1
+ import { RequestReply } from '../generated/wire.js';
2
+ /**
3
+ * Sends a presigned request with its body. Resolves on 2xx; rejects with an
4
+ * ContentKitError (storage, network or aborted). onProgress reports bytes sent.
5
+ */
6
+ export type Transport = (req: RequestReply, body: Blob, opts: {
7
+ signal?: AbortSignal;
8
+ onProgress?: (sent: number) => void;
9
+ }) => Promise<void>;
10
+ /** XMLHttpRequest when available (upload progress), otherwise fetch. */
11
+ export declare const defaultTransport: Transport;
12
+ export declare const xhrTransport: Transport;
13
+ export declare const fetchTransport: Transport;
@@ -0,0 +1,33 @@
1
+ import { ContentKitError as e, aborted as t } from "../errors.js";
2
+ //#region src/client/media/transport.ts
3
+ var n = (e, t, n) => typeof XMLHttpRequest > "u" ? i(e, t, n) : r(e, t, n), r = (n, r, { signal: i, onProgress: o }) => new Promise((s, c) => {
4
+ if (i?.aborted) return c(t(i));
5
+ let l = new XMLHttpRequest(), u = () => l.abort(), d = (e) => {
6
+ i?.removeEventListener("abort", u), e ? c(e) : s();
7
+ };
8
+ l.open(n.method, n.url);
9
+ for (let [e, t] of Object.entries(n.headers)) l.setRequestHeader(e, t);
10
+ l.upload.onprogress = (e) => o?.(e.loaded), l.onload = () => d(l.status >= 200 && l.status < 300 ? void 0 : a(l.status, l.responseText)), l.onerror = () => d(new e("network", "connection to storage failed")), l.ontimeout = l.onerror, l.onabort = () => d(t(i)), i?.addEventListener("abort", u, { once: !0 }), l.send(r);
11
+ }), i = async (n, r, { signal: i, onProgress: o }) => {
12
+ let s;
13
+ try {
14
+ s = await fetch(n.url, {
15
+ method: n.method,
16
+ headers: n.headers,
17
+ body: r,
18
+ signal: i ?? null
19
+ });
20
+ } catch (n) {
21
+ throw i?.aborted ? t(i) : new e("network", "connection to storage failed", { cause: n });
22
+ }
23
+ if (!s.ok) throw a(s.status, await s.text().catch(() => ""));
24
+ await s.body?.cancel(), o?.(r.size);
25
+ };
26
+ function a(t, n) {
27
+ let r = /<Code>([^<]+)<\/Code>/.exec(n)?.[1];
28
+ return new e("storage", `storage refused the upload (${t}${r ? " " + r : ""})`, { status: t });
29
+ }
30
+ //#endregion
31
+ export { n as defaultTransport, i as fetchTransport, r as xhrTransport };
32
+
33
+ //# sourceMappingURL=transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.js","names":[],"sources":["../../../src/client/media/transport.ts"],"sourcesContent":["import { ContentKitError, aborted } from \"../errors.js\";\nimport type { RequestReply } from \"../generated/wire.js\";\n\n/**\n * Sends a presigned request with its body. Resolves on 2xx; rejects with an\n * ContentKitError (storage, network or aborted). onProgress reports bytes sent.\n */\nexport type Transport = (\n req: RequestReply,\n body: Blob,\n opts: { signal?: AbortSignal; onProgress?: (sent: number) => void },\n) => Promise<void>;\n\n/** XMLHttpRequest when available (upload progress), otherwise fetch. */\nexport const defaultTransport: Transport = (req, body, opts) =>\n typeof XMLHttpRequest === \"undefined\" ? fetchTransport(req, body, opts) : xhrTransport(req, body, opts);\n\nexport const xhrTransport: Transport = (req, body, { signal, onProgress }) =>\n new Promise((resolve, reject) => {\n if (signal?.aborted) return reject(aborted(signal));\n const xhr = new XMLHttpRequest();\n const onAbort = () => xhr.abort();\n const done = (err?: ContentKitError) => {\n signal?.removeEventListener(\"abort\", onAbort);\n if (err) reject(err);\n else resolve();\n };\n xhr.open(req.method, req.url);\n for (const [k, v] of Object.entries(req.headers)) xhr.setRequestHeader(k, v);\n xhr.upload.onprogress = (e) => onProgress?.(e.loaded);\n xhr.onload = () =>\n done(xhr.status >= 200 && xhr.status < 300 ? undefined : storageError(xhr.status, xhr.responseText));\n xhr.onerror = () => done(new ContentKitError(\"network\", \"connection to storage failed\"));\n xhr.ontimeout = xhr.onerror;\n xhr.onabort = () => done(aborted(signal));\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n xhr.send(body);\n });\n\nexport const fetchTransport: Transport = async (req, body, { signal, onProgress }) => {\n let res: Response;\n try {\n res = await fetch(req.url, { method: req.method, headers: req.headers, body, signal: signal ?? null });\n } catch (err) {\n if (signal?.aborted) throw aborted(signal);\n throw new ContentKitError(\"network\", \"connection to storage failed\", { cause: err });\n }\n if (!res.ok) throw storageError(res.status, await res.text().catch(() => \"\"));\n await res.body?.cancel();\n onProgress?.(body.size);\n};\n\nfunction storageError(status: number, body: string): ContentKitError {\n const code = /<Code>([^<]+)<\\/Code>/.exec(body)?.[1];\n return new ContentKitError(\"storage\", `storage refused the upload (${status}${code ? \" \" + code : \"\"})`, { status });\n}\n"],"mappings":";;AAcA,IAAa,KAA+B,GAAK,GAAM,MACrD,OAAO,iBAAmB,MAAc,EAAe,GAAK,GAAM,CAAI,IAAI,EAAa,GAAK,GAAM,CAAI,GAE3F,KAA2B,GAAK,GAAM,EAAE,WAAQ,oBAC3D,IAAI,SAAS,GAAS,MAAW;CAC/B,IAAI,GAAQ,SAAS,OAAO,EAAO,EAAQ,CAAM,CAAC;CAClD,IAAM,IAAM,IAAI,eAAe,GACzB,UAAgB,EAAI,MAAM,GAC1B,KAAQ,MAA0B;EAEtC,AADA,GAAQ,oBAAoB,SAAS,CAAO,GACxC,IAAK,EAAO,CAAG,IACd,EAAQ;CACf;CACA,EAAI,KAAK,EAAI,QAAQ,EAAI,GAAG;CAC5B,KAAK,IAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,EAAI,OAAO,GAAG,EAAI,iBAAiB,GAAG,CAAC;CAQ3E,AAPA,EAAI,OAAO,cAAc,MAAM,IAAa,EAAE,MAAM,GACpD,EAAI,eACF,EAAK,EAAI,UAAU,OAAO,EAAI,SAAS,MAAM,KAAA,IAAY,EAAa,EAAI,QAAQ,EAAI,YAAY,CAAC,GACrG,EAAI,gBAAgB,EAAK,IAAI,EAAgB,WAAW,8BAA8B,CAAC,GACvF,EAAI,YAAY,EAAI,SACpB,EAAI,gBAAgB,EAAK,EAAQ,CAAM,CAAC,GACxC,GAAQ,iBAAiB,SAAS,GAAS,EAAE,MAAM,GAAK,CAAC,GACzD,EAAI,KAAK,CAAI;AACf,CAAC,GAEU,IAA4B,OAAO,GAAK,GAAM,EAAE,WAAQ,oBAAiB;CACpF,IAAI;CACJ,IAAI;EACF,IAAM,MAAM,MAAM,EAAI,KAAK;GAAE,QAAQ,EAAI;GAAQ,SAAS,EAAI;GAAS;GAAM,QAAQ,KAAU;EAAK,CAAC;CACvG,SAAS,GAAK;EAEZ,MADI,GAAQ,UAAe,EAAQ,CAAM,IACnC,IAAI,EAAgB,WAAW,gCAAgC,EAAE,OAAO,EAAI,CAAC;CACrF;CACA,IAAI,CAAC,EAAI,IAAI,MAAM,EAAa,EAAI,QAAQ,MAAM,EAAI,KAAK,CAAC,CAAC,YAAY,EAAE,CAAC;CAE5E,AADA,MAAM,EAAI,MAAM,OAAO,GACvB,IAAa,EAAK,IAAI;AACxB;AAEA,SAAS,EAAa,GAAgB,GAA+B;CACnE,IAAM,IAAO,wBAAwB,KAAK,CAAI,CAAC,GAAG;CAClD,OAAO,IAAI,EAAgB,WAAW,+BAA+B,IAAS,IAAO,MAAM,IAAO,GAAG,IAAI,EAAE,UAAO,CAAC;AACrH"}
@@ -0,0 +1,15 @@
1
+ import { FileInfo, ReadResult } from '../generated/wire.js';
2
+ /** Files per window when a read is split into windows. */
3
+ export declare const READ_CHUNK = 50;
4
+ /** Offsets of chunk-sized windows covering files [start, end), clipped to total when known. */
5
+ export declare function chunkOffsets(start: number, end: number, total?: number, chunk?: number): number[];
6
+ /**
7
+ * One item's window reads as one read: the newest window's listing and
8
+ * metadata, each file's URL from the newest window that signed it, and the
9
+ * earliest expiry. Every window lists every file; only its range has URLs.
10
+ */
11
+ export declare function mergeReads(reads: readonly (ReadResult | null | undefined)[]): ReadResult | null;
12
+ /** An upload the worker has not finished: staged, or with work pending, and not failed. */
13
+ export declare const isProcessing: (f: FileInfo) => boolean;
14
+ /** An editor read with uploads still processing (not stalled because the item is full). */
15
+ export declare function processing(read: ReadResult | null | undefined): boolean;