emdash 0.35.0 → 0.37.0

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 (971) hide show
  1. package/dist/activation-K0kGBWuJ.mjs +382 -0
  2. package/dist/activation-K0kGBWuJ.mjs.map +1 -0
  3. package/dist/{adapters-Bb1vG41D.d.mts → adapters-BDQwg236.d.mts} +6 -4
  4. package/dist/{adapters-Bb1vG41D.d.mts.map → adapters-BDQwg236.d.mts.map} +1 -1
  5. package/dist/{allowed-origins-DW7sYyqg.mjs → allowed-origins-BGglilcs.mjs} +2 -2
  6. package/dist/{allowed-origins-DW7sYyqg.mjs.map → allowed-origins-BGglilcs.mjs.map} +1 -1
  7. package/dist/api/route-utils.d.mts +3 -3
  8. package/dist/api/route-utils.mjs +23 -21
  9. package/dist/api/route-utils.mjs.map +1 -1
  10. package/dist/api/schemas/index.d.mts +2 -2
  11. package/dist/api/schemas/index.mjs +5 -5
  12. package/dist/api-Dz6rgoa3.mjs +10040 -0
  13. package/dist/api-Dz6rgoa3.mjs.map +1 -0
  14. package/dist/{api-tokens-KO24G1Fd.mjs → api-tokens-BSsQACNe.mjs} +2 -2
  15. package/dist/{api-tokens-KO24G1Fd.mjs.map → api-tokens-BSsQACNe.mjs.map} +1 -1
  16. package/dist/{apply-kUDt80-G.mjs → apply-B4SIOxR1.mjs} +34 -35
  17. package/dist/{apply-kUDt80-G.mjs.map → apply-B4SIOxR1.mjs.map} +1 -1
  18. package/dist/astro/image-endpoint.mjs +2 -2
  19. package/dist/astro/image-endpoint.mjs.map +1 -1
  20. package/dist/astro/index.d.mts +12 -12
  21. package/dist/astro/index.d.mts.map +1 -1
  22. package/dist/astro/index.mjs +128 -44
  23. package/dist/astro/index.mjs.map +1 -1
  24. package/dist/astro/middleware/auth.d.mts +10 -10
  25. package/dist/astro/middleware/auth.d.mts.map +1 -1
  26. package/dist/astro/middleware/auth.mjs +10 -7
  27. package/dist/astro/middleware/auth.mjs.map +1 -1
  28. package/dist/astro/middleware/media-usage-write-fence.mjs +2 -2
  29. package/dist/astro/middleware/redirect.mjs +4 -4
  30. package/dist/astro/middleware/request-context.mjs +3 -3
  31. package/dist/astro/middleware/setup.mjs +1 -1
  32. package/dist/astro/middleware.d.mts +11 -12
  33. package/dist/astro/middleware.d.mts.map +1 -1
  34. package/dist/astro/middleware.mjs +237 -1412
  35. package/dist/astro/middleware.mjs.map +1 -1
  36. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +6 -6
  37. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +6 -6
  38. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +3 -3
  39. package/dist/astro/routes/api/admin/api-tokens/index.d.mts.map +1 -1
  40. package/dist/astro/routes/api/admin/api-tokens/index.mjs +5 -5
  41. package/dist/astro/routes/api/admin/api-tokens/index.mjs.map +1 -1
  42. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +4 -4
  43. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +9 -9
  44. package/dist/astro/routes/api/admin/byline-fields/index.mjs +9 -9
  45. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +9 -9
  46. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +16 -16
  47. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +16 -16
  48. package/dist/astro/routes/api/admin/bylines/index.mjs +16 -16
  49. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +13 -13
  50. package/dist/astro/routes/api/admin/comments/_id_.mjs +5 -5
  51. package/dist/astro/routes/api/admin/comments/bulk.mjs +10 -10
  52. package/dist/astro/routes/api/admin/comments/counts.mjs +5 -5
  53. package/dist/astro/routes/api/admin/comments/index.mjs +10 -10
  54. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +4 -4
  55. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
  56. package/dist/astro/routes/api/admin/media-usage/activation.d.mts +9 -0
  57. package/dist/astro/routes/api/admin/media-usage/activation.d.mts.map +1 -0
  58. package/dist/astro/routes/api/admin/media-usage/activation.mjs +46 -0
  59. package/dist/astro/routes/api/admin/media-usage/activation.mjs.map +1 -0
  60. package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs +14 -13
  61. package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs.map +1 -1
  62. package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs +14 -13
  63. package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs.map +1 -1
  64. package/dist/astro/routes/api/admin/media-usage/progress.d.mts +9 -0
  65. package/dist/astro/routes/api/admin/media-usage/progress.d.mts.map +1 -0
  66. package/dist/astro/routes/api/admin/media-usage/progress.mjs +44 -0
  67. package/dist/astro/routes/api/admin/media-usage/progress.mjs.map +1 -0
  68. package/dist/astro/routes/api/admin/media-usage/repair.mjs +16 -11
  69. package/dist/astro/routes/api/admin/media-usage/repair.mjs.map +1 -1
  70. package/dist/astro/routes/api/admin/media-usage/work/index.mjs +14 -13
  71. package/dist/astro/routes/api/admin/media-usage/work/index.mjs.map +1 -1
  72. package/dist/astro/routes/api/admin/media-usage/work/retry.mjs +14 -13
  73. package/dist/astro/routes/api/admin/media-usage/work/retry.mjs.map +1 -1
  74. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +4 -4
  75. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs.map +1 -1
  76. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +4 -4
  77. package/dist/astro/routes/api/admin/oauth-clients/index.mjs.map +1 -1
  78. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +55 -49
  79. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
  80. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +55 -49
  81. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
  82. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +53 -47
  83. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
  84. package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +4 -4
  85. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +53 -47
  86. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs.map +1 -1
  87. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +54 -48
  88. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
  89. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +54 -48
  90. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
  91. package/dist/astro/routes/api/admin/plugins/index.mjs +53 -47
  92. package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
  93. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +2 -2
  94. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +53 -47
  95. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
  96. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +54 -48
  97. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
  98. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +53 -47
  99. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
  100. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +54 -48
  101. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
  102. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.d.mts.map +1 -1
  103. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +60 -50
  104. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
  105. package/dist/astro/routes/api/admin/plugins/registry/artifact.d.mts.map +1 -1
  106. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +179 -139
  107. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
  108. package/dist/astro/routes/api/admin/plugins/registry/install.d.mts.map +1 -1
  109. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +61 -51
  110. package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
  111. package/dist/astro/routes/api/admin/plugins/registry/verify.d.mts +8 -0
  112. package/dist/astro/routes/api/admin/plugins/registry/verify.d.mts.map +1 -0
  113. package/dist/astro/routes/api/admin/plugins/registry/verify.mjs +93 -0
  114. package/dist/astro/routes/api/admin/plugins/registry/verify.mjs.map +1 -0
  115. package/dist/astro/routes/api/admin/plugins/updates.mjs +53 -47
  116. package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
  117. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +53 -47
  118. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
  119. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +2 -2
  120. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +53 -47
  121. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
  122. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +2 -2
  123. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +1 -1
  124. package/dist/astro/routes/api/admin/users/_id_/index.mjs +7 -7
  125. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +3 -3
  126. package/dist/astro/routes/api/admin/users/index.mjs +6 -6
  127. package/dist/astro/routes/api/auth/dev-bypass.mjs +3 -3
  128. package/dist/astro/routes/api/auth/invite/accept.mjs +1 -1
  129. package/dist/astro/routes/api/auth/invite/complete.mjs +11 -11
  130. package/dist/astro/routes/api/auth/invite/index.mjs +8 -8
  131. package/dist/astro/routes/api/auth/invite/register-options.mjs +10 -10
  132. package/dist/astro/routes/api/auth/logout.mjs +2 -2
  133. package/dist/astro/routes/api/auth/magic-link/send.mjs +10 -10
  134. package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
  135. package/dist/astro/routes/api/auth/me.d.mts.map +1 -1
  136. package/dist/astro/routes/api/auth/me.mjs +19 -9
  137. package/dist/astro/routes/api/auth/me.mjs.map +1 -1
  138. package/dist/astro/routes/api/auth/mode.mjs +2 -2
  139. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +4 -4
  140. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
  141. package/dist/astro/routes/api/auth/passkey/_id_.mjs +6 -6
  142. package/dist/astro/routes/api/auth/passkey/index.mjs +1 -1
  143. package/dist/astro/routes/api/auth/passkey/options.mjs +12 -12
  144. package/dist/astro/routes/api/auth/passkey/register/options.mjs +10 -10
  145. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +11 -11
  146. package/dist/astro/routes/api/auth/passkey/verify.mjs +11 -11
  147. package/dist/astro/routes/api/auth/signup/complete.mjs +11 -11
  148. package/dist/astro/routes/api/auth/signup/request.mjs +10 -10
  149. package/dist/astro/routes/api/auth/signup/verify.mjs +1 -1
  150. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +14 -14
  151. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +13 -13
  152. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +2 -2
  153. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.d.mts.map +1 -1
  154. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +14 -5
  155. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs.map +1 -1
  156. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
  157. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +2 -2
  158. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +11 -11
  159. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +13 -10
  160. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
  161. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +17 -15
  162. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs.map +1 -1
  163. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs +17 -15
  164. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs.map +1 -1
  165. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
  166. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +2 -2
  167. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +8 -8
  168. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +14 -14
  169. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +2 -2
  170. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.d.mts.map +1 -1
  171. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +14 -5
  172. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs.map +1 -1
  173. package/dist/astro/routes/api/content/_collection_/_id_.mjs +8 -8
  174. package/dist/astro/routes/api/content/_collection_/authors.mjs +2 -2
  175. package/dist/astro/routes/api/content/_collection_/index.mjs +8 -8
  176. package/dist/astro/routes/api/content/_collection_/trash.mjs +7 -7
  177. package/dist/astro/routes/api/dashboard.mjs +8 -8
  178. package/dist/astro/routes/api/dev/emails.mjs +2 -2
  179. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  180. package/dist/astro/routes/api/import/probe.mjs +17 -17
  181. package/dist/astro/routes/api/import/wordpress/analyze.mjs +2 -2
  182. package/dist/astro/routes/api/import/wordpress/execute.d.mts +11 -11
  183. package/dist/astro/routes/api/import/wordpress/execute.mjs +13 -13
  184. package/dist/astro/routes/api/import/wordpress/media.d.mts +10 -10
  185. package/dist/astro/routes/api/import/wordpress/media.mjs +11 -11
  186. package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -9
  187. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +11 -11
  188. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +12 -10
  189. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs.map +1 -1
  190. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  191. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +17 -17
  192. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +11 -11
  193. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +27 -27
  194. package/dist/astro/routes/api/manifest.mjs +4 -4
  195. package/dist/astro/routes/api/manifest.mjs.map +1 -1
  196. package/dist/astro/routes/api/mcp.mjs +188 -65
  197. package/dist/astro/routes/api/mcp.mjs.map +1 -1
  198. package/dist/astro/routes/api/media/_id_/confirm.mjs +11 -11
  199. package/dist/astro/routes/api/media/_id_/replace.d.mts +8 -0
  200. package/dist/astro/routes/api/media/_id_/replace.d.mts.map +1 -0
  201. package/dist/astro/routes/api/media/_id_/replace.mjs +91 -0
  202. package/dist/astro/routes/api/media/_id_/replace.mjs.map +1 -0
  203. package/dist/astro/routes/api/media/_id_/upload.mjs +6 -6
  204. package/dist/astro/routes/api/media/_id_/usage.mjs +18 -13
  205. package/dist/astro/routes/api/media/_id_/usage.mjs.map +1 -1
  206. package/dist/astro/routes/api/media/_id_.d.mts.map +1 -1
  207. package/dist/astro/routes/api/media/_id_.mjs +21 -13
  208. package/dist/astro/routes/api/media/_id_.mjs.map +1 -1
  209. package/dist/astro/routes/api/media/file/_...key_.d.mts.map +1 -1
  210. package/dist/astro/routes/api/media/file/_...key_.mjs +3 -2
  211. package/dist/astro/routes/api/media/file/_...key_.mjs.map +1 -1
  212. package/dist/astro/routes/api/media/folders/_id_.d.mts +10 -0
  213. package/dist/astro/routes/api/media/folders/_id_.d.mts.map +1 -0
  214. package/dist/astro/routes/api/media/folders/_id_.mjs +52 -0
  215. package/dist/astro/routes/api/media/folders/_id_.mjs.map +1 -0
  216. package/dist/astro/routes/api/media/folders/index.d.mts +9 -0
  217. package/dist/astro/routes/api/media/folders/index.d.mts.map +1 -0
  218. package/dist/astro/routes/api/media/folders/index.mjs +41 -0
  219. package/dist/astro/routes/api/media/folders/index.mjs.map +1 -0
  220. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +2 -2
  221. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +2 -2
  222. package/dist/astro/routes/api/media/providers/index.mjs +2 -2
  223. package/dist/astro/routes/api/media/upload-url.mjs +16 -14
  224. package/dist/astro/routes/api/media/upload-url.mjs.map +1 -1
  225. package/dist/astro/routes/api/media.d.mts.map +1 -1
  226. package/dist/astro/routes/api/media.mjs +59 -27
  227. package/dist/astro/routes/api/media.mjs.map +1 -1
  228. package/dist/astro/routes/api/menus/_name_/items/_id_.d.mts.map +1 -1
  229. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +20 -13
  230. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs.map +1 -1
  231. package/dist/astro/routes/api/menus/_name_/items.d.mts.map +1 -1
  232. package/dist/astro/routes/api/menus/_name_/items.mjs +15 -11
  233. package/dist/astro/routes/api/menus/_name_/items.mjs.map +1 -1
  234. package/dist/astro/routes/api/menus/_name_/reorder.d.mts.map +1 -1
  235. package/dist/astro/routes/api/menus/_name_/reorder.mjs +15 -11
  236. package/dist/astro/routes/api/menus/_name_/reorder.mjs.map +1 -1
  237. package/dist/astro/routes/api/menus/_name_/translations.d.mts.map +1 -1
  238. package/dist/astro/routes/api/menus/_name_/translations.mjs +16 -12
  239. package/dist/astro/routes/api/menus/_name_/translations.mjs.map +1 -1
  240. package/dist/astro/routes/api/menus/_name_.d.mts.map +1 -1
  241. package/dist/astro/routes/api/menus/_name_.mjs +21 -14
  242. package/dist/astro/routes/api/menus/_name_.mjs.map +1 -1
  243. package/dist/astro/routes/api/menus/index.d.mts.map +1 -1
  244. package/dist/astro/routes/api/menus/index.mjs +15 -11
  245. package/dist/astro/routes/api/menus/index.mjs.map +1 -1
  246. package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
  247. package/dist/astro/routes/api/oauth/device/authorize.mjs +5 -5
  248. package/dist/astro/routes/api/oauth/device/code.mjs +8 -8
  249. package/dist/astro/routes/api/oauth/device/token.mjs +7 -7
  250. package/dist/astro/routes/api/oauth/register.mjs +2 -2
  251. package/dist/astro/routes/api/oauth/token/refresh.mjs +5 -5
  252. package/dist/astro/routes/api/oauth/token/revoke.mjs +5 -5
  253. package/dist/astro/routes/api/oauth/token.mjs +5 -5
  254. package/dist/astro/routes/api/openapi.json.mjs +253 -40
  255. package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
  256. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +4 -4
  257. package/dist/astro/routes/api/redirects/404s/index.mjs +10 -10
  258. package/dist/astro/routes/api/redirects/404s/summary.mjs +10 -10
  259. package/dist/astro/routes/api/redirects/_id_.mjs +11 -11
  260. package/dist/astro/routes/api/redirects/index.mjs +11 -11
  261. package/dist/astro/routes/api/relations/_id_/index.mjs +17 -15
  262. package/dist/astro/routes/api/relations/_id_/index.mjs.map +1 -1
  263. package/dist/astro/routes/api/relations/_id_/translations.mjs +12 -10
  264. package/dist/astro/routes/api/relations/_id_/translations.mjs.map +1 -1
  265. package/dist/astro/routes/api/relations/index.mjs +17 -15
  266. package/dist/astro/routes/api/relations/index.mjs.map +1 -1
  267. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +2 -2
  268. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
  269. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +53 -47
  270. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
  271. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +53 -47
  272. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs.map +1 -1
  273. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +53 -47
  274. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
  275. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +53 -47
  276. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
  277. package/dist/astro/routes/api/schema/collections/index.mjs +53 -47
  278. package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
  279. package/dist/astro/routes/api/schema/collections/reorder.mjs +53 -47
  280. package/dist/astro/routes/api/schema/collections/reorder.mjs.map +1 -1
  281. package/dist/astro/routes/api/schema/index.mjs +10 -8
  282. package/dist/astro/routes/api/schema/index.mjs.map +1 -1
  283. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +53 -47
  284. package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
  285. package/dist/astro/routes/api/schema/orphans/index.mjs +53 -47
  286. package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
  287. package/dist/astro/routes/api/search/enable.mjs +10 -10
  288. package/dist/astro/routes/api/search/index.mjs +9 -9
  289. package/dist/astro/routes/api/search/rebuild.mjs +10 -10
  290. package/dist/astro/routes/api/search/stats.mjs +5 -5
  291. package/dist/astro/routes/api/search/suggest.mjs +9 -9
  292. package/dist/astro/routes/api/sections/_slug_.mjs +9 -9
  293. package/dist/astro/routes/api/sections/index.mjs +9 -9
  294. package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +6 -6
  295. package/dist/astro/routes/api/settings/backups/archives/index.mjs +5 -5
  296. package/dist/astro/routes/api/settings/backups/export.mjs +6 -6
  297. package/dist/astro/routes/api/settings/backups/index.mjs +7 -7
  298. package/dist/astro/routes/api/settings/email.mjs +5 -5
  299. package/dist/astro/routes/api/settings/email.mjs.map +1 -1
  300. package/dist/astro/routes/api/settings.d.mts.map +1 -1
  301. package/dist/astro/routes/api/settings.mjs +20 -16
  302. package/dist/astro/routes/api/settings.mjs.map +1 -1
  303. package/dist/astro/routes/api/setup/admin-verify.mjs +12 -12
  304. package/dist/astro/routes/api/setup/admin.mjs +11 -11
  305. package/dist/astro/routes/api/setup/dev-bypass.d.mts.map +1 -1
  306. package/dist/astro/routes/api/setup/dev-bypass.mjs +35 -28
  307. package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
  308. package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
  309. package/dist/astro/routes/api/setup/index.mjs +32 -30
  310. package/dist/astro/routes/api/setup/index.mjs.map +1 -1
  311. package/dist/astro/routes/api/setup/status.mjs +3 -3
  312. package/dist/astro/routes/api/snapshot.mjs +7 -7
  313. package/dist/astro/routes/api/taxonomies/_name_/reorder.d.mts.map +1 -1
  314. package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs +20 -16
  315. package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs.map +1 -1
  316. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.d.mts.map +1 -1
  317. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +21 -17
  318. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs.map +1 -1
  319. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.d.mts.map +1 -1
  320. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +25 -18
  321. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs.map +1 -1
  322. package/dist/astro/routes/api/taxonomies/_name_/terms/index.d.mts.map +1 -1
  323. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +20 -16
  324. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs.map +1 -1
  325. package/dist/astro/routes/api/taxonomies/_name_/translations.mjs +14 -14
  326. package/dist/astro/routes/api/taxonomies/_name_.d.mts.map +1 -1
  327. package/dist/astro/routes/api/taxonomies/_name_.mjs +26 -19
  328. package/dist/astro/routes/api/taxonomies/_name_.mjs.map +1 -1
  329. package/dist/astro/routes/api/taxonomies/index.d.mts.map +1 -1
  330. package/dist/astro/routes/api/taxonomies/index.mjs +20 -16
  331. package/dist/astro/routes/api/taxonomies/index.mjs.map +1 -1
  332. package/dist/astro/routes/api/themes/preview.mjs +5 -5
  333. package/dist/astro/routes/api/typegen.mjs +4 -4
  334. package/dist/astro/routes/api/well-known/auth.mjs +2 -2
  335. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
  336. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
  337. package/dist/astro/routes/api/widget-areas/_name_/reorder.d.mts.map +1 -1
  338. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +10 -8
  339. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs.map +1 -1
  340. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.d.mts.map +1 -1
  341. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +17 -13
  342. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
  343. package/dist/astro/routes/api/widget-areas/_name_/widgets.d.mts.map +1 -1
  344. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +15 -12
  345. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
  346. package/dist/astro/routes/api/widget-areas/_name_.d.mts.map +1 -1
  347. package/dist/astro/routes/api/widget-areas/_name_.mjs +8 -6
  348. package/dist/astro/routes/api/widget-areas/_name_.mjs.map +1 -1
  349. package/dist/astro/routes/api/widget-areas/index.d.mts.map +1 -1
  350. package/dist/astro/routes/api/widget-areas/index.mjs +15 -12
  351. package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
  352. package/dist/astro/routes/api/widget-components.mjs +2 -2
  353. package/dist/astro/routes/robots.txt.mjs +7 -7
  354. package/dist/astro/routes/sitemap-_collection_.xml.mjs +10 -10
  355. package/dist/astro/routes/sitemap.xml.mjs +8 -8
  356. package/dist/astro/types.d.mts +37 -17
  357. package/dist/astro/types.d.mts.map +1 -1
  358. package/dist/auth/providers/github.d.mts +1 -1
  359. package/dist/auth/providers/google.d.mts +1 -1
  360. package/dist/{authorize-DUElxgOQ.mjs → authorize-Bkf_UHhD.mjs} +2 -2
  361. package/dist/{authorize-DUElxgOQ.mjs.map → authorize-Bkf_UHhD.mjs.map} +1 -1
  362. package/dist/{backup-DWYpJlLj.mjs → backup-rwVAl9NI.mjs} +5 -5
  363. package/dist/{backup-DWYpJlLj.mjs.map → backup-rwVAl9NI.mjs.map} +1 -1
  364. package/dist/{bundle-vn56sLos.mjs → bundle-C8S2Zl6U.mjs} +4 -4
  365. package/dist/{bundle-vn56sLos.mjs.map → bundle-C8S2Zl6U.mjs.map} +1 -1
  366. package/dist/{byline-yjvWq4ip.mjs → byline-DuhF7NWl.mjs} +27 -14
  367. package/dist/byline-DuhF7NWl.mjs.map +1 -0
  368. package/dist/{byline-fields-txaLGoME.mjs → byline-fields-BovY4KYM.mjs} +2 -2
  369. package/dist/{byline-fields-txaLGoME.mjs.map → byline-fields-BovY4KYM.mjs.map} +1 -1
  370. package/dist/{byline-registry-DDun8sxh.mjs → byline-registry-Bpu9-MVs.mjs} +2 -2
  371. package/dist/{byline-registry-DDun8sxh.mjs.map → byline-registry-Bpu9-MVs.mjs.map} +1 -1
  372. package/dist/{bylines-DjXS5p0P.mjs → bylines-CJ5nd6Lw.mjs} +7 -7
  373. package/dist/{bylines-DjXS5p0P.mjs.map → bylines-CJ5nd6Lw.mjs.map} +1 -1
  374. package/dist/{bylines-Bu7pchQ4.mjs → bylines-RwBAeOF1.mjs} +3 -3
  375. package/dist/{bylines-Bu7pchQ4.mjs.map → bylines-RwBAeOF1.mjs.map} +1 -1
  376. package/dist/{cache-C46-3Irl.mjs → cache-BF5Kvn59.mjs} +4 -4
  377. package/dist/{cache-C46-3Irl.mjs.map → cache-BF5Kvn59.mjs.map} +1 -1
  378. package/dist/capture-triggers-CNW1q3HK.mjs +362 -0
  379. package/dist/capture-triggers-CNW1q3HK.mjs.map +1 -0
  380. package/dist/{challenge-store-SIL5DsF2.mjs → challenge-store-DQN_Yrck.mjs} +5 -3
  381. package/dist/challenge-store-DQN_Yrck.mjs.map +1 -0
  382. package/dist/chrome-tags-D8fiZG94.mjs +24 -0
  383. package/dist/chrome-tags-D8fiZG94.mjs.map +1 -0
  384. package/dist/{chunks-fiZ8G9RJ.mjs → chunks-C4szrD8o.mjs} +2 -2
  385. package/dist/{chunks-fiZ8G9RJ.mjs.map → chunks-C4szrD8o.mjs.map} +1 -1
  386. package/dist/cli/index.mjs +37 -53
  387. package/dist/cli/index.mjs.map +1 -1
  388. package/dist/client/cf-access.d.mts +1 -1
  389. package/dist/client/index.d.mts +74 -4
  390. package/dist/client/index.d.mts.map +1 -1
  391. package/dist/client/index.mjs +54 -2
  392. package/dist/client/index.mjs.map +1 -1
  393. package/dist/{collection-deletion-DQPvMD8R.mjs → collection-deletion-Bj07NHbj.mjs} +13 -356
  394. package/dist/collection-deletion-Bj07NHbj.mjs.map +1 -0
  395. package/dist/{comment-reaction-D-2J84UE.mjs → comment-reaction-BhrvSHml.mjs} +2 -2
  396. package/dist/{comment-reaction-D-2J84UE.mjs.map → comment-reaction-BhrvSHml.mjs.map} +1 -1
  397. package/dist/{comment-Dxlep6cE.mjs → comment-vxOzWbZN.mjs} +2 -2
  398. package/dist/{comment-Dxlep6cE.mjs.map → comment-vxOzWbZN.mjs.map} +1 -1
  399. package/dist/{comments-CTfZtLmM.mjs → comments-Ct4ensnf.mjs} +2 -2
  400. package/dist/{comments-CTfZtLmM.mjs.map → comments-Ct4ensnf.mjs.map} +1 -1
  401. package/dist/{components-CQy3lh9x.mjs → components-DJbo1z0S.mjs} +1 -1
  402. package/dist/{components-CQy3lh9x.mjs.map → components-DJbo1z0S.mjs.map} +1 -1
  403. package/dist/{config-COQzTe5Q.d.mts → config-B9eZ340f.d.mts} +1 -1
  404. package/dist/{config-COQzTe5Q.d.mts.map → config-B9eZ340f.d.mts.map} +1 -1
  405. package/dist/config-it29yDLA.mjs +1163 -0
  406. package/dist/config-it29yDLA.mjs.map +1 -0
  407. package/dist/{content-ZHCc1ngN.mjs → content-DjvM0T92.mjs} +86 -45
  408. package/dist/content-DjvM0T92.mjs.map +1 -0
  409. package/dist/{content-list-query-wJ2WN8Vp.d.mts → content-list-query-Btqd4uV2.d.mts} +1 -1
  410. package/dist/{content-list-query-wJ2WN8Vp.d.mts.map → content-list-query-Btqd4uV2.d.mts.map} +1 -1
  411. package/dist/{content-refresh-B_8SJzuU.mjs → content-refresh-Bwp-8yGy.mjs} +1139 -111
  412. package/dist/content-refresh-Bwp-8yGy.mjs.map +1 -0
  413. package/dist/{media-usage-BZCydfvu.mjs → content-repair-Czdp7jz5.mjs} +5 -196
  414. package/dist/content-repair-Czdp7jz5.mjs.map +1 -0
  415. package/dist/{context-ZldbZcle.mjs → context-CX6gxOLW.mjs} +55 -20
  416. package/dist/context-CX6gxOLW.mjs.map +1 -0
  417. package/dist/{cron-Y9aLtgrA.mjs → cron-C6o7VppJ.mjs} +1 -1
  418. package/dist/{cron-Y9aLtgrA.mjs.map → cron-C6o7VppJ.mjs.map} +1 -1
  419. package/dist/{dashboard-DvHbk_cv.mjs → dashboard-C2kTeeRI.mjs} +5 -5
  420. package/dist/{dashboard-DvHbk_cv.mjs.map → dashboard-C2kTeeRI.mjs.map} +1 -1
  421. package/dist/db/index.d.mts +3 -3
  422. package/dist/db/index.mjs +3 -3
  423. package/dist/db/index.mjs.map +1 -1
  424. package/dist/db/libsql-migrations.d.mts +1 -1
  425. package/dist/db/libsql-migrations.mjs +4 -4
  426. package/dist/db/libsql.d.mts +1 -1
  427. package/dist/db/postgres-migrations.d.mts +1 -1
  428. package/dist/db/postgres-migrations.mjs +4 -4
  429. package/dist/db/postgres.d.mts +1 -1
  430. package/dist/db/postgres.d.mts.map +1 -1
  431. package/dist/db/postgres.mjs +19 -3
  432. package/dist/db/postgres.mjs.map +1 -1
  433. package/dist/db/sqlite-migrations.d.mts +1 -1
  434. package/dist/db/sqlite-migrations.mjs +4 -4
  435. package/dist/db/sqlite.d.mts +1 -1
  436. package/dist/db/sqlite.mjs +3 -3
  437. package/dist/db/sqlite.mjs.map +1 -1
  438. package/dist/{default-DdDl3fxv.mjs → default-Clra32mw.mjs} +1 -1
  439. package/dist/{default-DdDl3fxv.mjs.map → default-Clra32mw.mjs.map} +1 -1
  440. package/dist/{deferred-tasks-SDWxrgzJ.d.mts → deferred-tasks-CvDETTJO.d.mts} +1 -1
  441. package/dist/{deferred-tasks-SDWxrgzJ.d.mts.map → deferred-tasks-CvDETTJO.d.mts.map} +1 -1
  442. package/dist/dev-typegen-DzOPfsNj.mjs +29 -0
  443. package/dist/dev-typegen-DzOPfsNj.mjs.map +1 -0
  444. package/dist/{device-flow-C0GdD_z8.mjs → device-flow-D2oCWvLJ.mjs} +5 -5
  445. package/dist/{device-flow-C0GdD_z8.mjs.map → device-flow-D2oCWvLJ.mjs.map} +1 -1
  446. package/dist/{direct-executor-CDXeJCSs.mjs → direct-executor-Bfa5_ToE.mjs} +3 -3
  447. package/dist/{direct-executor-CDXeJCSs.mjs.map → direct-executor-Bfa5_ToE.mjs.map} +1 -1
  448. package/dist/{dist-D_6CKR8z.mjs → dist-89xCrGqi.mjs} +1 -1
  449. package/dist/{dist-D_6CKR8z.mjs.map → dist-89xCrGqi.mjs.map} +1 -1
  450. package/dist/{email-console-BHNjIwiB.mjs → email-console-Dzb6uGYa.mjs} +1 -1
  451. package/dist/{email-console-BHNjIwiB.mjs.map → email-console-Dzb6uGYa.mjs.map} +1 -1
  452. package/dist/{enrich-_ySMEvEE.mjs → enrich-BX0IDp4z.mjs} +3 -3
  453. package/dist/{enrich-_ySMEvEE.mjs.map → enrich-BX0IDp4z.mjs.map} +1 -1
  454. package/dist/{error-DupyJZE2.mjs → error-DFrQjppn.mjs} +2 -2
  455. package/dist/{error-DupyJZE2.mjs.map → error-DFrQjppn.mjs.map} +1 -1
  456. package/dist/{errors-CLUPm7aD.mjs → errors-DsVoDUjJ.mjs} +24 -2
  457. package/dist/errors-DsVoDUjJ.mjs.map +1 -0
  458. package/dist/{escape-DUY2r2Zs.mjs → escape-HmHTC1Is.mjs} +1 -1
  459. package/dist/{escape-DUY2r2Zs.mjs.map → escape-HmHTC1Is.mjs.map} +1 -1
  460. package/dist/{field-defs-cache-BzwBlJrR.mjs → field-defs-cache-BM4SIRqH.mjs} +5 -5
  461. package/dist/{field-defs-cache-BzwBlJrR.mjs.map → field-defs-cache-BM4SIRqH.mjs.map} +1 -1
  462. package/dist/focal-point-BeWFo6Xx.mjs +23 -0
  463. package/dist/focal-point-BeWFo6Xx.mjs.map +1 -0
  464. package/dist/{fts-manager-DyZEohZC.mjs → fts-manager-Do1BlW2r.mjs} +3 -3
  465. package/dist/{fts-manager-DyZEohZC.mjs.map → fts-manager-Do1BlW2r.mjs.map} +1 -1
  466. package/dist/{hash-Cg0TXJPV.mjs → hash-3JJPlHF2.mjs} +1 -1
  467. package/dist/{hash-Cg0TXJPV.mjs.map → hash-3JJPlHF2.mjs.map} +1 -1
  468. package/dist/hook-result-DCh2WUpe.d.mts +31 -0
  469. package/dist/hook-result-DCh2WUpe.d.mts.map +1 -0
  470. package/dist/{hreflang-DwT2E0Sa.d.mts → hreflang-B6hbAuuO.d.mts} +2 -2
  471. package/dist/{hreflang-DwT2E0Sa.d.mts.map → hreflang-B6hbAuuO.d.mts.map} +1 -1
  472. package/dist/{hreflang-BBe83kKa.mjs → hreflang-DQbPZliA.mjs} +7 -7
  473. package/dist/{hreflang-BBe83kKa.mjs.map → hreflang-DQbPZliA.mjs.map} +1 -1
  474. package/dist/{identity-Ys6Dlc9C.mjs → identity-DksvEazf.mjs} +3 -3
  475. package/dist/{identity-Ys6Dlc9C.mjs.map → identity-DksvEazf.mjs.map} +1 -1
  476. package/dist/{import-DPkLmeXO.mjs → import-CJpaBBBT.mjs} +6 -6
  477. package/dist/{import-DPkLmeXO.mjs.map → import-CJpaBBBT.mjs.map} +1 -1
  478. package/dist/{index-BVBZj4-7.d.mts → index-BJNMCWmu.d.mts} +267 -82
  479. package/dist/index-BJNMCWmu.d.mts.map +1 -0
  480. package/dist/{index-C9KuQyO-.d.mts → index-Du8K7bWa.d.mts} +3 -3
  481. package/dist/{index-C9KuQyO-.d.mts.map → index-Du8K7bWa.d.mts.map} +1 -1
  482. package/dist/index.d.mts +22 -21
  483. package/dist/index.mjs +79 -73
  484. package/dist/{init-lock-6b309ZrF.mjs → init-lock-DJkX6Hto.mjs} +1 -1
  485. package/dist/{init-lock-6b309ZrF.mjs.map → init-lock-DJkX6Hto.mjs.map} +1 -1
  486. package/dist/{load-YJ2xl3ra.mjs → load-CawHjTOH.mjs} +2 -2
  487. package/dist/{load-YJ2xl3ra.mjs.map → load-CawHjTOH.mjs.map} +1 -1
  488. package/dist/{loader-CdG_Ci3H.mjs → loader-Z8vl7zTq.mjs} +48 -13
  489. package/dist/loader-Z8vl7zTq.mjs.map +1 -0
  490. package/dist/{manifest-builder-B252yQUf.mjs → manifest-builder-COtzuca2.mjs} +2 -2
  491. package/dist/{manifest-builder-B252yQUf.mjs.map → manifest-builder-COtzuca2.mjs.map} +1 -1
  492. package/dist/{manifest-schema-BXbUAHod.mjs → manifest-schema-BNWwR8Vd.mjs} +5 -5
  493. package/dist/manifest-schema-BNWwR8Vd.mjs.map +1 -0
  494. package/dist/media/image-endpoint.d.mts +4 -2
  495. package/dist/media/image-endpoint.d.mts.map +1 -1
  496. package/dist/media/image-endpoint.mjs +6 -4
  497. package/dist/media/image-endpoint.mjs.map +1 -1
  498. package/dist/media/index.d.mts +1 -1
  499. package/dist/media/index.mjs +13 -3
  500. package/dist/media/index.mjs.map +1 -1
  501. package/dist/media/local-runtime.d.mts +14 -12
  502. package/dist/media/local-runtime.d.mts.map +1 -1
  503. package/dist/media/local-runtime.mjs +12 -6
  504. package/dist/media/local-runtime.mjs.map +1 -1
  505. package/dist/{media-CzhAn2--.mjs → media-BBm8Wg3f.mjs} +69 -12
  506. package/dist/media-BBm8Wg3f.mjs.map +1 -0
  507. package/dist/{media-BWWWb8kg.mjs → media-BHjus86Y.mjs} +133 -9
  508. package/dist/media-BHjus86Y.mjs.map +1 -0
  509. package/dist/{media-allowlist-D6x-Us02.mjs → media-allowlist-CoTBWoDp.mjs} +3 -2
  510. package/dist/media-allowlist-CoTBWoDp.mjs.map +1 -0
  511. package/dist/media-folders-BGkATs8n.mjs +210 -0
  512. package/dist/media-folders-BGkATs8n.mjs.map +1 -0
  513. package/dist/{media-upload-CHBVn6m-.mjs → media-upload-BCidR-EY.mjs} +9 -9
  514. package/dist/{media-upload-CHBVn6m-.mjs.map → media-upload-BCidR-EY.mjs.map} +1 -1
  515. package/dist/{media-url-BvoX88FA.mjs → media-url-DHoPr0hq.mjs} +1 -1
  516. package/dist/{media-url-BvoX88FA.mjs.map → media-url-DHoPr0hq.mjs.map} +1 -1
  517. package/dist/media-usage-CdY5jhSR.mjs +1833 -0
  518. package/dist/media-usage-CdY5jhSR.mjs.map +1 -0
  519. package/dist/media-usage-activation-BgeMtK5C.mjs +78 -0
  520. package/dist/media-usage-activation-BgeMtK5C.mjs.map +1 -0
  521. package/dist/{media-usage-work-CLo_6tct.mjs → media-usage-work-CnGCdHxx.mjs} +4 -4
  522. package/dist/{media-usage-work-CLo_6tct.mjs.map → media-usage-work-CnGCdHxx.mjs.map} +1 -1
  523. package/dist/{media-usage-work-DrCbqBcl.mjs → media-usage-work-D6D9sMCA.mjs} +221 -59
  524. package/dist/media-usage-work-D6D9sMCA.mjs.map +1 -0
  525. package/dist/{media-usage-write-fence-aHEgN1zB.mjs → media-usage-write-fence-hjeD4u7s.mjs} +2 -2
  526. package/dist/{media-usage-write-fence-aHEgN1zB.mjs.map → media-usage-write-fence-hjeD4u7s.mjs.map} +1 -1
  527. package/dist/{menus-DSo5QeW0.mjs → menus-6qSwTNxG.mjs} +375 -107
  528. package/dist/menus-6qSwTNxG.mjs.map +1 -0
  529. package/dist/{menus-TCNSd2vp.mjs → menus-uvPLXMZf.mjs} +4 -4
  530. package/dist/{menus-TCNSd2vp.mjs.map → menus-uvPLXMZf.mjs.map} +1 -1
  531. package/dist/migrations/index.d.mts +1 -1
  532. package/dist/migrations/index.mjs +3 -3
  533. package/dist/{mime-DfIVjNkr.mjs → mime-CUIG6x84.mjs} +1 -1
  534. package/dist/mime-CUIG6x84.mjs.map +1 -0
  535. package/dist/{mode-BDoH_WcF.mjs → mode-DN_WneRx.mjs} +1 -1
  536. package/dist/{mode-BDoH_WcF.mjs.map → mode-DN_WneRx.mjs.map} +1 -1
  537. package/dist/node-sqlite-compat-BuJXIeMz.mjs +46 -0
  538. package/dist/node-sqlite-compat-BuJXIeMz.mjs.map +1 -0
  539. package/dist/{normalize-Bc-s_r-a.mjs → normalize-C2xNjTSG.mjs} +36 -5
  540. package/dist/normalize-C2xNjTSG.mjs.map +1 -0
  541. package/dist/{oauth-authorization-iOikmrJd.mjs → oauth-authorization-Dj6Kwc21.mjs} +5 -5
  542. package/dist/{oauth-authorization-iOikmrJd.mjs.map → oauth-authorization-Dj6Kwc21.mjs.map} +1 -1
  543. package/dist/{oauth-clients-CvI0nHMJ.mjs → oauth-clients-FD7I0J8m.mjs} +1 -1
  544. package/dist/{oauth-clients-CvI0nHMJ.mjs.map → oauth-clients-FD7I0J8m.mjs.map} +1 -1
  545. package/dist/{oauth-state-store-Ba8KZlwW.mjs → oauth-state-store-DkmDOmHs.mjs} +1 -1
  546. package/dist/{oauth-state-store-Ba8KZlwW.mjs.map → oauth-state-store-DkmDOmHs.mjs.map} +1 -1
  547. package/dist/{oauth-user-lookup-C5SQwGYC.mjs → oauth-user-lookup-CvIwGpYd.mjs} +1 -1
  548. package/dist/{oauth-user-lookup-C5SQwGYC.mjs.map → oauth-user-lookup-CvIwGpYd.mjs.map} +1 -1
  549. package/dist/object-cache/memory.d.mts +1 -1
  550. package/dist/{object-cache-71tPfV4A.mjs → object-cache-DVKnyOfy.mjs} +19 -3
  551. package/dist/object-cache-DVKnyOfy.mjs.map +1 -0
  552. package/dist/{options-CBXIHUoT.mjs → options-BHPeIbBd.mjs} +1 -1
  553. package/dist/{options-CBXIHUoT.mjs.map → options-BHPeIbBd.mjs.map} +1 -1
  554. package/dist/{options--hhz-jZ1.d.mts → options-CYb4y7hM.d.mts} +24 -11
  555. package/dist/options-CYb4y7hM.d.mts.map +1 -0
  556. package/dist/page/index.d.mts +2 -2
  557. package/dist/{parse-DFbuc-Fh.mjs → parse-Ckpg-Qfp.mjs} +2 -2
  558. package/dist/{parse-DFbuc-Fh.mjs.map → parse-Ckpg-Qfp.mjs.map} +1 -1
  559. package/dist/{passkey-config-DFAmUyuE.mjs → passkey-config-O_lj3y0k.mjs} +1 -1
  560. package/dist/{passkey-config-DFAmUyuE.mjs.map → passkey-config-O_lj3y0k.mjs.map} +1 -1
  561. package/dist/{patterns-CiyXeDgr.mjs → patterns-D9iDO3j8.mjs} +1 -1
  562. package/dist/{patterns-CiyXeDgr.mjs.map → patterns-D9iDO3j8.mjs.map} +1 -1
  563. package/dist/{placeholder-Dp0K3RkD.d.mts → placeholder-4X9BrRrp.d.mts} +7 -1
  564. package/dist/placeholder-4X9BrRrp.d.mts.map +1 -0
  565. package/dist/plugin-types.d.mts +8 -3
  566. package/dist/plugin-types.d.mts.map +1 -1
  567. package/dist/plugin-utils.d.mts +10 -10
  568. package/dist/plugins/adapt-sandbox-entry.d.mts +10 -10
  569. package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
  570. package/dist/{policy-TluUIxuu.mjs → policy-Dhtwntrt.mjs} +2 -2
  571. package/dist/{policy-TluUIxuu.mjs.map → policy-Dhtwntrt.mjs.map} +1 -1
  572. package/dist/{portable-text-imBW5cHK.mjs → portable-text-U85hOafW.mjs} +1 -1
  573. package/dist/{portable-text-imBW5cHK.mjs.map → portable-text-U85hOafW.mjs.map} +1 -1
  574. package/dist/{preview-PW-nOnq4.mjs → preview-B_vJLvf8.mjs} +2 -2
  575. package/dist/{preview-PW-nOnq4.mjs.map → preview-B_vJLvf8.mjs.map} +1 -1
  576. package/dist/{protocol-DjsJHTlK.d.mts → protocol-JdD2YhY9.d.mts} +2 -2
  577. package/dist/{protocol-DjsJHTlK.d.mts.map → protocol-JdD2YhY9.d.mts.map} +1 -1
  578. package/dist/{public-url-CWoFhQT8.mjs → public-url-CarhZJgf.mjs} +1 -1
  579. package/dist/{public-url-CWoFhQT8.mjs.map → public-url-CarhZJgf.mjs.map} +1 -1
  580. package/dist/{query-zTVAR1Sl.mjs → query-BZKZcG5b.mjs} +15 -15
  581. package/dist/{query-zTVAR1Sl.mjs.map → query-BZKZcG5b.mjs.map} +1 -1
  582. package/dist/{query-BWs7O5Il.mjs → query-BndaFxxp.mjs} +5 -5
  583. package/dist/{query-BWs7O5Il.mjs.map → query-BndaFxxp.mjs.map} +1 -1
  584. package/dist/{rate-limit-Bx4g9m3p.mjs → rate-limit-BZmATC6W.mjs} +2 -2
  585. package/dist/{rate-limit-Bx4g9m3p.mjs.map → rate-limit-BZmATC6W.mjs.map} +1 -1
  586. package/dist/{redirect-COsS6SWJ.mjs → redirect-CZkbozu9.mjs} +2 -2
  587. package/dist/{redirect-COsS6SWJ.mjs.map → redirect-CZkbozu9.mjs.map} +1 -1
  588. package/dist/{redirect-BgAOGHDq.mjs → redirect-CzqnLxK-.mjs} +1 -1
  589. package/dist/{redirect-BgAOGHDq.mjs.map → redirect-CzqnLxK-.mjs.map} +1 -1
  590. package/dist/{redirects-DTDHWH31.mjs → redirects-BLNOWQTn.mjs} +64 -29
  591. package/dist/redirects-BLNOWQTn.mjs.map +1 -0
  592. package/dist/{redirects-DyChwDkG.mjs → redirects-CqzNM0KM.mjs} +5 -5
  593. package/dist/{redirects-DyChwDkG.mjs.map → redirects-CqzNM0KM.mjs.map} +1 -1
  594. package/dist/{registry-Cqa2I749.mjs → registry-AZAsRsor.mjs} +30 -126
  595. package/dist/registry-AZAsRsor.mjs.map +1 -0
  596. package/dist/{relation-BnHB0T4J.mjs → relation-04DfKRTO.mjs} +2 -2
  597. package/dist/{relation-BnHB0T4J.mjs.map → relation-04DfKRTO.mjs.map} +1 -1
  598. package/dist/{relations-BBApI4e2.mjs → relations-C4htk5sb.mjs} +7 -7
  599. package/dist/relations-C4htk5sb.mjs.map +1 -0
  600. package/dist/{relations-D5sM5fW0.mjs → relations-CcFwDJdJ.mjs} +4 -4
  601. package/dist/{relations-D5sM5fW0.mjs.map → relations-CcFwDJdJ.mjs.map} +1 -1
  602. package/dist/{relations-BHo5A8Zf.d.mts → relations-DnRK4YIT.d.mts} +347 -54
  603. package/dist/relations-DnRK4YIT.d.mts.map +1 -0
  604. package/dist/{request-cache-BSUptuJR.mjs → request-cache-DH9zdCOD.mjs} +1 -1
  605. package/dist/{request-cache-BSUptuJR.mjs.map → request-cache-DH9zdCOD.mjs.map} +1 -1
  606. package/dist/request-context.d.mts +1 -1
  607. package/dist/{request-meta-DgZJh3zG.mjs → request-meta-DYxLlnmz.mjs} +2 -2
  608. package/dist/{request-meta-DgZJh3zG.mjs.map → request-meta-DYxLlnmz.mjs.map} +1 -1
  609. package/dist/{resolve-DOhVEvty.mjs → resolve-prZ3mP7l.mjs} +1 -1
  610. package/dist/{resolve-DOhVEvty.mjs.map → resolve-prZ3mP7l.mjs.map} +1 -1
  611. package/dist/{runner-C1T11CfC.mjs → runner-BLIDKpmA.mjs} +471 -290
  612. package/dist/runner-BLIDKpmA.mjs.map +1 -0
  613. package/dist/{runner-BYHZn3Jy.d.mts → runner-YKfIMnaF.d.mts} +3 -2
  614. package/dist/runner-YKfIMnaF.d.mts.map +1 -0
  615. package/dist/runtime.d.mts +11 -11
  616. package/dist/runtime.mjs +1 -1
  617. package/dist/{schema-DZ5YVVZj.mjs → schema-CGnBeLiW.mjs} +3 -3
  618. package/dist/{schema-DZ5YVVZj.mjs.map → schema-CGnBeLiW.mjs.map} +1 -1
  619. package/dist/{schema-2AOsSn2v.mjs → schema-DYr-Vd-L.mjs} +36 -6
  620. package/dist/{schema-2AOsSn2v.mjs.map → schema-DYr-Vd-L.mjs.map} +1 -1
  621. package/dist/{scopes-8ClPF2bC.mjs → scopes-BhWvBavj.mjs} +3 -3
  622. package/dist/{scopes-8ClPF2bC.mjs.map → scopes-BhWvBavj.mjs.map} +1 -1
  623. package/dist/{search-u1QYdOCG.mjs → search-C-qDk4kx.mjs} +4 -4
  624. package/dist/{search-u1QYdOCG.mjs.map → search-C-qDk4kx.mjs.map} +1 -1
  625. package/dist/{secrets-BEgsAQEU.mjs → secrets-rEtHt-6z.mjs} +3 -3
  626. package/dist/{secrets-BEgsAQEU.mjs.map → secrets-rEtHt-6z.mjs.map} +1 -1
  627. package/dist/{sections-DmHviGyM.mjs → sections-B_nYvlwn.mjs} +2 -2
  628. package/dist/{sections-DmHviGyM.mjs.map → sections-B_nYvlwn.mjs.map} +1 -1
  629. package/dist/seed/index.d.mts +2 -2
  630. package/dist/seed/index.mjs +25 -23
  631. package/dist/seo/index.d.mts +3 -3
  632. package/dist/seo/index.mjs +17 -15
  633. package/dist/seo/index.mjs.map +1 -1
  634. package/dist/{seo-Dq4G-RrO.mjs → seo-926NRIgt.mjs} +3 -3
  635. package/dist/{seo-Dq4G-RrO.mjs.map → seo-926NRIgt.mjs.map} +1 -1
  636. package/dist/{seo-AFpVvTqr.mjs → seo-CVWkdv6x.mjs} +1 -1
  637. package/dist/{seo-AFpVvTqr.mjs.map → seo-CVWkdv6x.mjs.map} +1 -1
  638. package/dist/{service-BpArayPr.mjs → service-CJl3ZAYs.mjs} +3 -3
  639. package/dist/{service-BpArayPr.mjs.map → service-CJl3ZAYs.mjs.map} +1 -1
  640. package/dist/{session-user-BEorKVT-.mjs → session-user-BlMgqCHS.mjs} +1 -1
  641. package/dist/{session-user-BEorKVT-.mjs.map → session-user-BlMgqCHS.mjs.map} +1 -1
  642. package/dist/{settings-xRdoUZr0.mjs → settings--wobrkkC.mjs} +21 -8
  643. package/dist/settings--wobrkkC.mjs.map +1 -0
  644. package/dist/{settings-DE9xtzqd.mjs → settings-Cwm6o2jY.mjs} +3 -3
  645. package/dist/{settings-DE9xtzqd.mjs.map → settings-Cwm6o2jY.mjs.map} +1 -1
  646. package/dist/{setup-complete-BnT8pq_C.mjs → setup-complete-DQbClIN2.mjs} +2 -2
  647. package/dist/{setup-complete-BnT8pq_C.mjs.map → setup-complete-DQbClIN2.mjs.map} +1 -1
  648. package/dist/{setup-nonce-CBitf_oQ.mjs → setup-nonce-DZCFS2sn.mjs} +1 -1
  649. package/dist/{setup-nonce-CBitf_oQ.mjs.map → setup-nonce-DZCFS2sn.mjs.map} +1 -1
  650. package/dist/{single-flight-cache-B0eLKS6K.mjs → single-flight-cache-BPuGPdcN.mjs} +2 -2
  651. package/dist/{single-flight-cache-B0eLKS6K.mjs.map → single-flight-cache-BPuGPdcN.mjs.map} +1 -1
  652. package/dist/{site-url-DjpIXMXJ.mjs → site-url-BrxA_E9H.mjs} +3 -3
  653. package/dist/{site-url-DjpIXMXJ.mjs.map → site-url-BrxA_E9H.mjs.map} +1 -1
  654. package/dist/{snapshot-BBlGkb2q.mjs → snapshot--OCDTqdb.mjs} +1 -1
  655. package/dist/{snapshot-BBlGkb2q.mjs.map → snapshot--OCDTqdb.mjs.map} +1 -1
  656. package/dist/{ssrf-Cpvg_n3A.mjs → ssrf-5sIM5jzl.mjs} +23 -6
  657. package/dist/{ssrf-Cpvg_n3A.mjs.map → ssrf-5sIM5jzl.mjs.map} +1 -1
  658. package/dist/{state-DYwPue40.mjs → state-BpFVsJZB.mjs} +12 -4
  659. package/dist/state-BpFVsJZB.mjs.map +1 -0
  660. package/dist/{status-CWVYpbD8.mjs → status-B777-dXz.mjs} +1 -1
  661. package/dist/{status-CWVYpbD8.mjs.map → status-B777-dXz.mjs.map} +1 -1
  662. package/dist/storage/local.d.mts +2 -1
  663. package/dist/storage/local.d.mts.map +1 -1
  664. package/dist/storage/local.mjs +1 -1
  665. package/dist/storage/local.mjs.map +1 -1
  666. package/dist/storage/s3.d.mts +2 -1
  667. package/dist/storage/s3.d.mts.map +1 -1
  668. package/dist/storage/s3.mjs +3 -2
  669. package/dist/storage/s3.mjs.map +1 -1
  670. package/dist/{target-DixqqV5i.mjs → target-BmR0iTwz.mjs} +1 -1
  671. package/dist/{target-DixqqV5i.mjs.map → target-BmR0iTwz.mjs.map} +1 -1
  672. package/dist/{taxonomies-OHUIiIpI.mjs → taxonomies-DkAkMupr.mjs} +17 -14
  673. package/dist/taxonomies-DkAkMupr.mjs.map +1 -0
  674. package/dist/{taxonomies-BlrncHi9.mjs → taxonomies-Wfy_Lg12.mjs} +43 -17
  675. package/dist/taxonomies-Wfy_Lg12.mjs.map +1 -0
  676. package/dist/{taxonomy-BqCZ1TdU.mjs → taxonomy-tJepplLL.mjs} +5 -5
  677. package/dist/{taxonomy-BqCZ1TdU.mjs.map → taxonomy-tJepplLL.mjs.map} +1 -1
  678. package/dist/{tokens-C39klX8R.mjs → tokens-BCO1oruF.mjs} +1 -1
  679. package/dist/{tokens-C39klX8R.mjs.map → tokens-BCO1oruF.mjs.map} +1 -1
  680. package/dist/{transaction-NQj4VJ7Z.mjs → transaction-0KOpOyMu.mjs} +2 -1
  681. package/dist/{transaction-NQj4VJ7Z.mjs.map → transaction-0KOpOyMu.mjs.map} +1 -1
  682. package/dist/{transport-DBJwa-Si.mjs → transport-Dr3qtwXC.mjs} +4 -3
  683. package/dist/{transport-DBJwa-Si.mjs.map → transport-Dr3qtwXC.mjs.map} +1 -1
  684. package/dist/{transport-RQE36APG.d.mts → transport-xh6YKW2G.d.mts} +1 -1
  685. package/dist/{transport-RQE36APG.d.mts.map → transport-xh6YKW2G.d.mts.map} +1 -1
  686. package/dist/{trusted-proxy-CEVekpby.mjs → trusted-proxy-BR5F1Otp.mjs} +1 -1
  687. package/dist/{trusted-proxy-CEVekpby.mjs.map → trusted-proxy-BR5F1Otp.mjs.map} +1 -1
  688. package/dist/{types-BW1syrlw.mjs → types-3qNFPlci.mjs} +1 -1
  689. package/dist/{types-BW1syrlw.mjs.map → types-3qNFPlci.mjs.map} +1 -1
  690. package/dist/types-75KIqzL9.mjs.map +1 -1
  691. package/dist/{types-DrJrVCT1.d.mts → types-B6GrPic4.d.mts} +9 -3
  692. package/dist/{types-DrJrVCT1.d.mts.map → types-B6GrPic4.d.mts.map} +1 -1
  693. package/dist/{types-DN64_zzZ.d.mts → types-BuHW4xDE.d.mts} +11 -10
  694. package/dist/types-BuHW4xDE.d.mts.map +1 -0
  695. package/dist/{types-DWRhk9xZ.d.mts → types-C00oyCZp.d.mts} +1 -1
  696. package/dist/{types-DWRhk9xZ.d.mts.map → types-C00oyCZp.d.mts.map} +1 -1
  697. package/dist/{types-BntycPTF.d.mts → types-CDtkNSFQ.d.mts} +1 -1
  698. package/dist/{types-BntycPTF.d.mts.map → types-CDtkNSFQ.d.mts.map} +1 -1
  699. package/dist/{types-Dfk8KI5R.d.mts → types-CPeXo1wF.d.mts} +1 -1
  700. package/dist/{types-Dfk8KI5R.d.mts.map → types-CPeXo1wF.d.mts.map} +1 -1
  701. package/dist/types-CQPWhPxE.mjs.map +1 -1
  702. package/dist/{types-BcqdcT-r.d.mts → types-CXFkwEGC.d.mts} +3 -2
  703. package/dist/types-CXFkwEGC.d.mts.map +1 -0
  704. package/dist/{types-CtzytVXn.mjs → types-DM3vioBY.mjs} +1 -1
  705. package/dist/{types-CtzytVXn.mjs.map → types-DM3vioBY.mjs.map} +1 -1
  706. package/dist/{types-CXnOzDe1.d.mts → types-DczELMop.d.mts} +1 -1
  707. package/dist/{types-CXnOzDe1.d.mts.map → types-DczELMop.d.mts.map} +1 -1
  708. package/dist/types-DfuTR6dL.mjs +3 -0
  709. package/dist/{types-DQIDRKF2.d.mts → types-FV23hEoW.d.mts} +10 -1
  710. package/dist/types-FV23hEoW.d.mts.map +1 -0
  711. package/dist/{types-C2pMvO62.d.mts → types-xDMtUFnO.d.mts} +2 -1
  712. package/dist/types-xDMtUFnO.d.mts.map +1 -0
  713. package/dist/{upload-attempts-D4FS8LT6.mjs → upload-attempts-BsqoQ1fn.mjs} +1 -1
  714. package/dist/{upload-attempts-D4FS8LT6.mjs.map → upload-attempts-BsqoQ1fn.mjs.map} +1 -1
  715. package/dist/{url-pattern-BHS0V5Ng.mjs → url-pattern-D4c2czpJ.mjs} +1 -1
  716. package/dist/{url-pattern-BHS0V5Ng.mjs.map → url-pattern-D4c2czpJ.mjs.map} +1 -1
  717. package/dist/{user-DJZfUJVE.mjs → user-BTZ40aq6.mjs} +2 -2
  718. package/dist/{user-DJZfUJVE.mjs.map → user-BTZ40aq6.mjs.map} +1 -1
  719. package/dist/{utils-D4vD_Y98.mjs → utils-BKvNjjhY.mjs} +1 -1
  720. package/dist/{utils-D4vD_Y98.mjs.map → utils-BKvNjjhY.mjs.map} +1 -1
  721. package/dist/{validate-BMLBvxOC.mjs → validate-DE_LRwo9.mjs} +2 -2
  722. package/dist/{validate-BMLBvxOC.mjs.map → validate-DE_LRwo9.mjs.map} +1 -1
  723. package/dist/{validate-CTqt5nuE.d.mts → validate-DUP27OYD.d.mts} +5 -5
  724. package/dist/{validate-CTqt5nuE.d.mts.map → validate-DUP27OYD.d.mts.map} +1 -1
  725. package/dist/{validation-npvENUCB.mjs → validation-BMImFJ11.mjs} +10 -8
  726. package/dist/{validation-npvENUCB.mjs.map → validation-BMImFJ11.mjs.map} +1 -1
  727. package/dist/version-Bii3suaO.mjs +7 -0
  728. package/dist/{version-DB_fqCSI.mjs.map → version-Bii3suaO.mjs.map} +1 -1
  729. package/dist/{widgets-BD3ho7Rq.mjs → widgets-D3gJ10xU.mjs} +18 -5
  730. package/dist/widgets-D3gJ10xU.mjs.map +1 -0
  731. package/dist/{wxr-taxonomies-Ca68766D.d.mts → wxr-taxonomies-B2Og_6rU.d.mts} +1 -1
  732. package/dist/{wxr-taxonomies-Ca68766D.d.mts.map → wxr-taxonomies-B2Og_6rU.d.mts.map} +1 -1
  733. package/dist/{wxr-taxonomies-B1R3vBoD.mjs → wxr-taxonomies-Cxngsjwo.mjs} +4 -4
  734. package/dist/{wxr-taxonomies-B1R3vBoD.mjs.map → wxr-taxonomies-Cxngsjwo.mjs.map} +1 -1
  735. package/dist/{zod-generator-LC-on_Vb.mjs → zod-generator-BuYSQJB7.mjs} +32 -22
  736. package/dist/zod-generator-BuYSQJB7.mjs.map +1 -0
  737. package/package.json +20 -9
  738. package/src/api/errors.ts +22 -0
  739. package/src/api/handlers/content.ts +49 -11
  740. package/src/api/handlers/index.ts +18 -0
  741. package/src/api/handlers/manifest.ts +161 -12
  742. package/src/api/handlers/media-allowlist.ts +1 -0
  743. package/src/api/handlers/media-folders.ts +130 -0
  744. package/src/api/handlers/media-usage-activation.ts +102 -0
  745. package/src/api/handlers/media-usage.ts +108 -0
  746. package/src/api/handlers/media.ts +92 -2
  747. package/src/api/handlers/registry.ts +483 -561
  748. package/src/api/handlers/taxonomies.ts +16 -9
  749. package/src/api/openapi/document.ts +277 -7
  750. package/src/api/parse.ts +1 -1
  751. package/src/api/schemas/auth.ts +4 -4
  752. package/src/api/schemas/bylines.ts +3 -3
  753. package/src/api/schemas/comments.ts +1 -1
  754. package/src/api/schemas/common.ts +3 -2
  755. package/src/api/schemas/content.ts +78 -22
  756. package/src/api/schemas/media-usage.ts +77 -0
  757. package/src/api/schemas/media.ts +121 -0
  758. package/src/api/schemas/redirects.ts +6 -1
  759. package/src/api/schemas/schema.ts +4 -0
  760. package/src/api/schemas/setup.ts +1 -1
  761. package/src/api/schemas/taxonomies.ts +2 -2
  762. package/src/api/schemas/users.ts +1 -1
  763. package/src/api/types.ts +23 -10
  764. package/src/astro/dev-typegen.ts +35 -0
  765. package/src/astro/image-endpoint.ts +2 -2
  766. package/src/astro/integration/dev-typegen.ts +91 -0
  767. package/src/astro/integration/index.ts +9 -44
  768. package/src/astro/integration/routes.ts +23 -0
  769. package/src/astro/integration/vite-config.ts +23 -9
  770. package/src/astro/middleware/auth.ts +4 -0
  771. package/src/astro/middleware.ts +5 -7
  772. package/src/astro/routes/api/admin/api-tokens/index.ts +4 -1
  773. package/src/astro/routes/api/admin/media-usage/activation.ts +42 -0
  774. package/src/astro/routes/api/admin/media-usage/progress.ts +37 -0
  775. package/src/astro/routes/api/admin/oauth-clients/[id].ts +1 -1
  776. package/src/astro/routes/api/admin/oauth-clients/index.ts +1 -1
  777. package/src/astro/routes/api/admin/plugins/registry/[id]/update.ts +4 -0
  778. package/src/astro/routes/api/admin/plugins/registry/artifact.ts +247 -101
  779. package/src/astro/routes/api/admin/plugins/registry/install.ts +4 -0
  780. package/src/astro/routes/api/admin/plugins/registry/verify.ts +60 -0
  781. package/src/astro/routes/api/auth/me.ts +14 -2
  782. package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +8 -2
  783. package/src/astro/routes/api/content/[collection]/[id]/publish.ts +2 -1
  784. package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +6 -2
  785. package/src/astro/routes/api/manifest.ts +1 -1
  786. package/src/astro/routes/api/media/[id]/replace.ts +114 -0
  787. package/src/astro/routes/api/media/[id].ts +3 -0
  788. package/src/astro/routes/api/media/file/[...key].ts +4 -1
  789. package/src/astro/routes/api/media/folders/[id].ts +55 -0
  790. package/src/astro/routes/api/media/folders/index.ts +37 -0
  791. package/src/astro/routes/api/media/upload-url.ts +8 -4
  792. package/src/astro/routes/api/media.ts +66 -10
  793. package/src/astro/routes/api/menus/[name]/items/[id].ts +7 -2
  794. package/src/astro/routes/api/menus/[name]/items.ts +4 -1
  795. package/src/astro/routes/api/menus/[name]/reorder.ts +4 -1
  796. package/src/astro/routes/api/menus/[name]/translations.ts +4 -1
  797. package/src/astro/routes/api/menus/[name].ts +7 -2
  798. package/src/astro/routes/api/menus/index.ts +4 -1
  799. package/src/astro/routes/api/settings/email.ts +1 -1
  800. package/src/astro/routes/api/settings.ts +4 -1
  801. package/src/astro/routes/api/setup/dev-bypass.ts +12 -0
  802. package/src/astro/routes/api/taxonomies/[name]/reorder.ts +4 -1
  803. package/src/astro/routes/api/taxonomies/[name]/terms/[slug]/translations.ts +4 -1
  804. package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +7 -2
  805. package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +4 -1
  806. package/src/astro/routes/api/taxonomies/[name].ts +7 -2
  807. package/src/astro/routes/api/taxonomies/index.ts +4 -1
  808. package/src/astro/routes/api/widget-areas/[name]/reorder.ts +3 -1
  809. package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +5 -2
  810. package/src/astro/routes/api/widget-areas/[name]/widgets.ts +3 -1
  811. package/src/astro/routes/api/widget-areas/[name].ts +3 -1
  812. package/src/astro/routes/api/widget-areas/index.ts +3 -1
  813. package/src/astro/types.ts +34 -6
  814. package/src/auth/challenge-store.ts +3 -1
  815. package/src/aws-sdk.d.ts +1 -0
  816. package/src/cache/chrome-tags.ts +24 -0
  817. package/src/client/index.ts +140 -2
  818. package/src/client/transport.ts +6 -2
  819. package/src/components/EmDashImage.astro +156 -51
  820. package/src/components/EmDashMedia.astro +2 -1
  821. package/src/components/Gallery.astro +7 -0
  822. package/src/components/InlinePortableTextEditor.tsx +136 -2
  823. package/src/components/LiveSearch.astro +23 -0
  824. package/src/components/inline-code-block.tsx +180 -49
  825. package/src/components/widgets/Archives.astro +2 -37
  826. package/src/content/converters/gallery.ts +2 -0
  827. package/src/content/converters/index.ts +2 -0
  828. package/src/content/converters/mark-safety.ts +1 -0
  829. package/src/content/converters/portable-text-identity.ts +139 -0
  830. package/src/content/converters/portable-text-to-prosemirror.ts +202 -69
  831. package/src/content/converters/prosemirror-to-portable-text.ts +96 -25
  832. package/src/content/converters/types.ts +3 -0
  833. package/src/database/connection.ts +2 -31
  834. package/src/database/index.ts +1 -1
  835. package/src/database/json-recordset.ts +16 -0
  836. package/src/database/migrations/017_authorization_codes.ts +14 -4
  837. package/src/database/migrations/019_i18n.ts +1 -1
  838. package/src/database/migrations/071_restore_content_bylines_table.ts +43 -0
  839. package/src/database/migrations/072_media_folders.ts +72 -0
  840. package/src/database/migrations/073_media_focal_point.ts +57 -0
  841. package/src/database/migrations/074_content_deleted_scheduled_index.ts +55 -0
  842. package/src/database/migrations/runner.ts +11 -3
  843. package/src/database/repositories/byline.ts +32 -16
  844. package/src/database/repositories/content.ts +119 -51
  845. package/src/database/repositories/media-folders.ts +115 -0
  846. package/src/database/repositories/media-usage-work.ts +395 -111
  847. package/src/database/repositories/media-usage.ts +1104 -47
  848. package/src/database/repositories/media.ts +156 -29
  849. package/src/database/repositories/plugin-storage.ts +74 -9
  850. package/src/database/repositories/types.ts +8 -7
  851. package/src/database/transaction.ts +1 -0
  852. package/src/database/types.ts +13 -0
  853. package/src/db/adapters.ts +10 -3
  854. package/src/db/node-sqlite-compat.ts +78 -0
  855. package/src/db/postgres.ts +23 -0
  856. package/src/db/sqlite.ts +3 -3
  857. package/src/emdash-runtime.ts +186 -251
  858. package/src/fields/boolean.ts +1 -1
  859. package/src/fields/datetime.ts +1 -1
  860. package/src/fields/file.ts +1 -1
  861. package/src/fields/image.ts +7 -1
  862. package/src/fields/integer.ts +1 -1
  863. package/src/fields/json.ts +1 -1
  864. package/src/fields/multiselect.ts +1 -1
  865. package/src/fields/number.ts +1 -1
  866. package/src/fields/portable-text.ts +1 -1
  867. package/src/fields/richtext.ts +1 -1
  868. package/src/fields/select.ts +1 -1
  869. package/src/fields/slug.ts +1 -1
  870. package/src/fields/text.ts +1 -1
  871. package/src/fields/textarea.ts +1 -1
  872. package/src/fields/types.ts +8 -4
  873. package/src/index.ts +30 -3
  874. package/src/loader.ts +63 -10
  875. package/src/mcp/server.ts +228 -24
  876. package/src/media/focal-point.ts +37 -0
  877. package/src/media/image-endpoint.ts +5 -2
  878. package/src/media/local-runtime.ts +8 -0
  879. package/src/media/mime.ts +1 -0
  880. package/src/media/normalize.ts +25 -4
  881. package/src/media/types.ts +10 -0
  882. package/src/media/usage/activation.ts +162 -35
  883. package/src/media/usage/capture-triggers.ts +19 -3
  884. package/src/media/usage/cleanup.ts +9 -8
  885. package/src/media/usage/collection-deletion-processor.ts +7 -3
  886. package/src/media/usage/collection-deletion.ts +10 -0
  887. package/src/media/usage/content-refresh.ts +258 -39
  888. package/src/media/usage/content-snapshots.ts +128 -31
  889. package/src/media/usage/extractor.ts +32 -13
  890. package/src/media/usage/maintenance-engine.ts +166 -0
  891. package/src/media/usage/reconciliation-processor.ts +106 -29
  892. package/src/media/usage/reconciliation.ts +90 -7
  893. package/src/media/usage/work-processor.ts +342 -101
  894. package/src/menus/index.ts +16 -0
  895. package/src/object-cache/index.ts +44 -2
  896. package/src/plugin-types.ts +18 -1
  897. package/src/plugins/hooks.ts +4 -1
  898. package/src/plugins/index.ts +7 -0
  899. package/src/plugins/manager.ts +2 -1
  900. package/src/plugins/manifest-schema.ts +1 -1
  901. package/src/plugins/routes.ts +3 -1
  902. package/src/plugins/sandbox/hook-result.ts +52 -0
  903. package/src/plugins/sandbox/index.ts +10 -0
  904. package/src/plugins/save-rejection.ts +20 -0
  905. package/src/plugins/scheduler/node.ts +1 -13
  906. package/src/plugins/scheduler/types.ts +0 -2
  907. package/src/plugins/state.ts +13 -3
  908. package/src/plugins/types.ts +6 -0
  909. package/src/registry/artifact-checksum.ts +15 -0
  910. package/src/registry/artifact-fetch.ts +530 -0
  911. package/src/registry/artifact-verification.ts +85 -0
  912. package/src/registry/authoritative-records.ts +180 -0
  913. package/src/registry/config.ts +24 -6
  914. package/src/registry/record-labels.ts +28 -0
  915. package/src/registry/types.ts +12 -15
  916. package/src/schema/registry.ts +26 -3
  917. package/src/schema/types.ts +1 -0
  918. package/src/schema/zod-generator.ts +28 -17
  919. package/src/security/ssrf.ts +26 -4
  920. package/src/seed/apply.ts +4 -4
  921. package/src/settings/index.ts +16 -0
  922. package/src/storage/local.ts +1 -0
  923. package/src/storage/s3.ts +2 -0
  924. package/src/storage/types.ts +2 -0
  925. package/src/taxonomies/index.ts +40 -20
  926. package/src/ui.ts +2 -0
  927. package/src/widgets/archives.ts +67 -0
  928. package/src/widgets/index.ts +16 -0
  929. package/dist/api-BeBglVxU.mjs +0 -4695
  930. package/dist/api-BeBglVxU.mjs.map +0 -1
  931. package/dist/byline-yjvWq4ip.mjs.map +0 -1
  932. package/dist/challenge-store-SIL5DsF2.mjs.map +0 -1
  933. package/dist/collection-deletion-DQPvMD8R.mjs.map +0 -1
  934. package/dist/content-ZHCc1ngN.mjs.map +0 -1
  935. package/dist/content-refresh-B_8SJzuU.mjs.map +0 -1
  936. package/dist/context-ZldbZcle.mjs.map +0 -1
  937. package/dist/errors-CLUPm7aD.mjs.map +0 -1
  938. package/dist/index-BVBZj4-7.d.mts.map +0 -1
  939. package/dist/loader-CdG_Ci3H.mjs.map +0 -1
  940. package/dist/manifest-schema-BXbUAHod.mjs.map +0 -1
  941. package/dist/media-BWWWb8kg.mjs.map +0 -1
  942. package/dist/media-CzhAn2--.mjs.map +0 -1
  943. package/dist/media-allowlist-D6x-Us02.mjs.map +0 -1
  944. package/dist/media-usage-BZCydfvu.mjs.map +0 -1
  945. package/dist/media-usage-work-DrCbqBcl.mjs.map +0 -1
  946. package/dist/menus-DSo5QeW0.mjs.map +0 -1
  947. package/dist/mime-DfIVjNkr.mjs.map +0 -1
  948. package/dist/normalize-Bc-s_r-a.mjs.map +0 -1
  949. package/dist/object-cache-71tPfV4A.mjs.map +0 -1
  950. package/dist/options--hhz-jZ1.d.mts.map +0 -1
  951. package/dist/placeholder-Dp0K3RkD.d.mts.map +0 -1
  952. package/dist/redirects-DTDHWH31.mjs.map +0 -1
  953. package/dist/registry-Cqa2I749.mjs.map +0 -1
  954. package/dist/relations-BBApI4e2.mjs.map +0 -1
  955. package/dist/relations-BHo5A8Zf.d.mts.map +0 -1
  956. package/dist/runner-BYHZn3Jy.d.mts.map +0 -1
  957. package/dist/runner-C1T11CfC.mjs.map +0 -1
  958. package/dist/settings-xRdoUZr0.mjs.map +0 -1
  959. package/dist/state-DYwPue40.mjs.map +0 -1
  960. package/dist/taxonomies-BlrncHi9.mjs.map +0 -1
  961. package/dist/taxonomies-OHUIiIpI.mjs.map +0 -1
  962. package/dist/types-BcqdcT-r.d.mts.map +0 -1
  963. package/dist/types-C2pMvO62.d.mts.map +0 -1
  964. package/dist/types-CP1CflWf.mjs +0 -3
  965. package/dist/types-DN64_zzZ.d.mts.map +0 -1
  966. package/dist/types-DQIDRKF2.d.mts.map +0 -1
  967. package/dist/version-DB_fqCSI.mjs +0 -7
  968. package/dist/widgets-BD3ho7Rq.mjs.map +0 -1
  969. package/dist/zod-generator-LC-on_Vb.mjs.map +0 -1
  970. /package/dist/{api-tokens-rIXC6LdE.mjs → api-tokens-BfYts-sq.mjs} +0 -0
  971. /package/dist/{ssrf-DpXgKsna.mjs → ssrf-Dg3cIT-b.mjs} +0 -0
@@ -9,11 +9,21 @@ import {
9
9
  } from "kysely";
10
10
  import { ulid } from "ulidx";
11
11
 
12
+ import {
13
+ MEDIA_USAGE_ACTIVATION_RUNTIME_GENERATION,
14
+ MediaUsageActivationVersionMismatchError,
15
+ } from "../../media/usage/activation.js";
12
16
  import { isMediaUsageProjectionFingerprint } from "../../media/usage/projection-fingerprint.js";
13
17
  import type { MediaUsageContentSourceVariant } from "../../media/usage/source-key.js";
14
- import type { MediaKind, MediaUsageReferenceType } from "../../media/usage/types.js";
18
+ import {
19
+ CONTENT_SOURCE_SCHEMA_VERSION,
20
+ type MediaKind,
21
+ type MediaUsageReferenceType,
22
+ } from "../../media/usage/types.js";
23
+ import { getRequestContext } from "../../request-context.js";
15
24
  import { chunks, SQL_BATCH_SIZE } from "../../utils/chunks.js";
16
25
  import { isPostgres } from "../dialect-helpers.js";
26
+ import { jsonTextValues } from "../json-recordset.js";
17
27
  import { withTransaction } from "../transaction.js";
18
28
  import type {
19
29
  Database,
@@ -33,11 +43,30 @@ type MediaUsageSourceNullableStringColumn =
33
43
  | "updated_at"
34
44
  | "last_attempted_at"
35
45
  | "last_error_code";
46
+ type BatchSourceExpectedColumn =
47
+ | "source.collection_id"
48
+ | "source.source_fingerprint"
49
+ | "source.source_updated_at"
50
+ | "source.source_version"
51
+ | "source.identity_version"
52
+ | "source.revision_id"
53
+ | "source.last_attempted_at"
54
+ | "source.last_error_code";
55
+ type BatchInputExpectedColumn =
56
+ | "input.expected_collection_id"
57
+ | "input.expected_source_fingerprint"
58
+ | "input.expected_source_updated_at"
59
+ | "input.expected_source_version"
60
+ | "input.expected_identity_version"
61
+ | "input.expected_revision_id"
62
+ | "input.expected_last_attempted_at"
63
+ | "input.expected_last_error_code";
36
64
  const OCCURRENCE_BIND_COLUMNS = 13;
65
+ const D1_MAX_BOUND_PARAMETERS = 100;
37
66
  export const MEDIA_USAGE_GENERATION_WRITE_LEASE_MS = 60 * 60 * 1000;
38
67
  const OCCURRENCE_INSERT_BATCH_SIZE = Math.max(
39
68
  1,
40
- Math.floor(SQL_BATCH_SIZE / OCCURRENCE_BIND_COLUMNS),
69
+ Math.floor(D1_MAX_BOUND_PARAMETERS / OCCURRENCE_BIND_COLUMNS),
41
70
  );
42
71
 
43
72
  function cleanupDeleteBatchSize(cleanupLease: MediaUsageCleanupLease | undefined): number {
@@ -48,6 +77,84 @@ function canIssueCleanupStatement(canIssueStatement: (() => boolean) | undefined
48
77
  return canIssueStatement?.() ?? true;
49
78
  }
50
79
 
80
+ function nullableRefMatch(
81
+ left: "content.live_revision_id" | "content.draft_revision_id",
82
+ right: "input.revision_id",
83
+ ): RawBuilder<boolean> {
84
+ const leftRef = sql.ref(left);
85
+ const rightRef = sql.ref(right);
86
+ return sql<boolean>`(
87
+ (${leftRef} IS NULL AND ${rightRef} IS NULL)
88
+ OR ${leftRef} = ${rightRef}
89
+ )`;
90
+ }
91
+
92
+ const MAX_JSON_BIND_BYTES = 1_900_000;
93
+ const MEDIA_USAGE_EVENT_QUERY_CEILING = 900;
94
+ const MEDIA_USAGE_QUERY_RESERVE = 150;
95
+
96
+ function chunkJsonRows<T>(rows: readonly T[]): T[][] {
97
+ const batches: T[][] = [];
98
+ let batch: T[] = [];
99
+ let batchBytes = 2;
100
+ for (const row of rows) {
101
+ const rowBytes =
102
+ new TextEncoder().encode(JSON.stringify(row)).byteLength + (batch.length > 0 ? 1 : 0);
103
+ if (batch.length > 0 && batchBytes + rowBytes > MAX_JSON_BIND_BYTES) {
104
+ batches.push(batch);
105
+ batch = [];
106
+ batchBytes = 2;
107
+ }
108
+ batch.push(row);
109
+ batchBytes += rowBytes;
110
+ }
111
+ if (batch.length > 0) batches.push(batch);
112
+ return batches;
113
+ }
114
+
115
+ function mediaUsageQueryBudgetAllows(queries: number): boolean {
116
+ const metrics = getRequestContext()?.metrics;
117
+ return (
118
+ !metrics ||
119
+ metrics.dbCount + queries + MEDIA_USAGE_QUERY_RESERVE <= MEDIA_USAGE_EVENT_QUERY_CEILING
120
+ );
121
+ }
122
+
123
+ function mergeInto<T>(target: Set<T>, source: ReadonlySet<T>): void {
124
+ for (const value of source) target.add(value);
125
+ }
126
+
127
+ function batchOccurrenceRows(
128
+ prepared: readonly {
129
+ projection: { occurrences: readonly MediaUsageOccurrenceInput[] };
130
+ generation: string;
131
+ leaseToken: string;
132
+ row: { source_key: string };
133
+ }[],
134
+ now: string,
135
+ ) {
136
+ return prepared.flatMap((item) =>
137
+ item.projection.occurrences.map((occurrence) => ({
138
+ id: ulid(),
139
+ source_key: item.row.source_key,
140
+ generation: item.generation,
141
+ field_slug: occurrence.fieldSlug,
142
+ field_path: occurrence.fieldPath,
143
+ occurrence_index: occurrence.occurrenceIndex ?? 0,
144
+ reference_type: occurrence.referenceType,
145
+ media_id: occurrence.mediaId,
146
+ provider: occurrence.provider,
147
+ provider_asset_id: occurrence.providerAssetId,
148
+ media_kind: occurrence.mediaKind ?? null,
149
+ mime_type: occurrence.mimeType ?? null,
150
+ created_at: now,
151
+ lease_token: item.leaseToken,
152
+ })),
153
+ );
154
+ }
155
+
156
+ type BatchOccurrenceRow = ReturnType<typeof batchOccurrenceRows>[number];
157
+
51
158
  function cleanupDurationSeconds(value: number): number {
52
159
  if (!Number.isSafeInteger(value) || value < 0) {
53
160
  throw new Error("Media usage cleanup duration must be a non-negative whole number of seconds");
@@ -168,6 +275,15 @@ export interface MediaUsageGuardedReplaceResult {
168
275
  source: MediaUsageSource | null;
169
276
  }
170
277
 
278
+ export interface MediaUsageNewSourceProjection {
279
+ source: MediaUsageSourceInput;
280
+ occurrences: readonly MediaUsageOccurrenceInput[];
281
+ }
282
+
283
+ export interface MediaUsageExistingSourceProjection extends MediaUsageNewSourceProjection {
284
+ expectedSource: MediaUsageSource;
285
+ }
286
+
171
287
  export interface MediaUsageGuardedDeleteResult {
172
288
  deleted: boolean;
173
289
  source: MediaUsageSource | null;
@@ -340,6 +456,12 @@ export interface MediaUsageCollectionIndexStatusScope {
340
456
  reconciliationRequired: boolean;
341
457
  }
342
458
 
459
+ export interface MediaUsageCollectionProgress {
460
+ status: "indexing" | "ready" | "needs_attention";
461
+ readyCollections: number;
462
+ totalCollections: number;
463
+ }
464
+
343
465
  export interface MediaUsageEntrySource {
344
466
  source: MediaUsageSource;
345
467
  occurrences: MediaUsageOccurrence[];
@@ -537,14 +659,15 @@ export class MediaUsageRepository {
537
659
  const uniqueSourceKeys = [...new Set(sourceKeys)];
538
660
  const sources = new Map<string, MediaUsageSource>();
539
661
  if (uniqueSourceKeys.length === 0) return sources;
540
-
541
- for (const sourceKeyBatch of chunks(uniqueSourceKeys, SQL_BATCH_SIZE)) {
542
- const rows = await this.db
543
- .selectFrom("_emdash_media_usage_sources")
544
- .selectAll()
545
- .where("source_key", "in", sourceKeyBatch)
546
- .execute();
547
- for (const row of rows) {
662
+ for (const sourceKeyBatch of chunkJsonRows(uniqueSourceKeys)) {
663
+ const input = jsonTextValues(this.db, sourceKeyBatch);
664
+ const result = await sql<Selectable<MediaUsageSourceTable>>`
665
+ WITH requested AS (${input})
666
+ SELECT source.*
667
+ FROM _emdash_media_usage_sources AS source
668
+ INNER JOIN requested ON requested.value = source.source_key
669
+ `.execute(this.db);
670
+ for (const row of result.rows) {
548
671
  const source = rowToSource(row);
549
672
  sources.set(source.sourceKey, source);
550
673
  }
@@ -619,6 +742,441 @@ export class MediaUsageRepository {
619
742
  };
620
743
  }
621
744
 
745
+ async replaceNewSourcesBatch(
746
+ projections: readonly MediaUsageNewSourceProjection[],
747
+ ): Promise<Set<string>> {
748
+ const unique = [
749
+ ...new Map(
750
+ projections.map((projection) => [projection.source.sourceKey, projection]),
751
+ ).values(),
752
+ ];
753
+ if (unique.length === 0) return new Set();
754
+ const collectionSlug = unique[0]?.source.collectionSlug;
755
+ const collectionId = unique[0]?.source.collectionId;
756
+ if (!collectionSlug || !collectionId) {
757
+ throw new Error("Canonical media usage batch requires collection identity");
758
+ }
759
+ validateIdentifier(collectionSlug, "collection slug");
760
+ if (
761
+ unique.some(
762
+ (projection) =>
763
+ projection.source.collectionSlug !== collectionSlug ||
764
+ projection.source.collectionId !== collectionId,
765
+ )
766
+ ) {
767
+ throw new Error("Canonical media usage batch must contain one collection");
768
+ }
769
+
770
+ const now = new Date().toISOString();
771
+ const expiresAt = new Date(Date.now() + MEDIA_USAGE_GENERATION_WRITE_LEASE_MS).toISOString();
772
+ const prepared = unique.map((projection) => {
773
+ const generation = ulid();
774
+ return {
775
+ projection,
776
+ generation,
777
+ leaseToken: ulid(),
778
+ row: this.buildSourceRow(projection.source, generation, now),
779
+ };
780
+ });
781
+ const sourceRows = prepared.map((item) => ({
782
+ ...item.row,
783
+ lease_token: item.leaseToken,
784
+ }));
785
+ const occurrenceRows = batchOccurrenceRows(prepared, now);
786
+ const estimatedQueries =
787
+ 2 + chunkJsonRows(occurrenceRows).length + chunkJsonRows(sourceRows).length;
788
+ if (!mediaUsageQueryBudgetAllows(estimatedQueries)) {
789
+ if (unique.length === 1) return new Set();
790
+ const midpoint = Math.ceil(unique.length / 2);
791
+ const inserted = new Set<string>();
792
+ mergeInto(inserted, await this.replaceNewSourcesBatch(unique.slice(0, midpoint)));
793
+ mergeInto(inserted, await this.replaceNewSourcesBatch(unique.slice(midpoint)));
794
+ return inserted;
795
+ }
796
+
797
+ const leasesPayload = JSON.stringify(
798
+ prepared.map((item) => ({
799
+ source_key: item.row.source_key,
800
+ collection_id: item.row.collection_id,
801
+ collection_slug: item.row.collection_slug,
802
+ generation: item.generation,
803
+ lease_token: item.leaseToken,
804
+ expires_at: expiresAt,
805
+ created_at: now,
806
+ })),
807
+ );
808
+ const leases = this.generationWriteBatchInput(leasesPayload);
809
+ await sql`
810
+ WITH input AS (${leases})
811
+ INSERT INTO _emdash_media_usage_generation_writes (
812
+ source_key, generation, lease_token, expires_at, created_at
813
+ )
814
+ SELECT source_key, generation, lease_token, expires_at, created_at
815
+ FROM input
816
+ WHERE EXISTS (
817
+ SELECT 1
818
+ FROM _emdash_collections AS collection
819
+ INNER JOIN _emdash_media_usage_index_status AS status
820
+ ON status.collection_id = collection.id
821
+ AND status.scope_key = collection.slug
822
+ WHERE collection.id = input.collection_id
823
+ AND collection.slug = input.collection_slug
824
+ AND status.adapter_id = 'content-media'
825
+ AND status.scope_type = 'collection'
826
+ AND status.capture_state = 'active'
827
+ AND NOT EXISTS (
828
+ SELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion
829
+ WHERE deletion.collection_id = input.collection_id
830
+ )
831
+ )
832
+ `.execute(this.db);
833
+
834
+ try {
835
+ await this.insertBatchOccurrences(occurrenceRows);
836
+ const tableName = `ec_${collectionSlug}`;
837
+ validateIdentifier(tableName, "content table");
838
+ const inserted = new Set<string>();
839
+ for (const sourceRowBatch of chunkJsonRows(sourceRows)) {
840
+ const input = this.sourceBatchInput(JSON.stringify(sourceRowBatch));
841
+ const result = await sql<{ source_key: string }>`
842
+ WITH input AS (${input})
843
+ INSERT INTO _emdash_media_usage_sources (
844
+ source_key, source_type, collection_id, collection_slug, content_id,
845
+ source_variant, locale, translation_group, content_slug, content_title,
846
+ content_status, content_scheduled_at, content_deleted_at, revision_id,
847
+ current_generation, schema_version, source_updated_at, source_version,
848
+ source_fingerprint, identity_version, source_completeness,
849
+ last_attempted_at, last_error_code, indexed_at, updated_at
850
+ )
851
+ SELECT
852
+ input.source_key, input.source_type, input.collection_id, input.collection_slug,
853
+ input.content_id, input.source_variant, input.locale, input.translation_group,
854
+ input.content_slug, input.content_title, input.content_status,
855
+ input.content_scheduled_at, input.content_deleted_at, input.revision_id,
856
+ input.current_generation, input.schema_version, input.source_updated_at,
857
+ input.source_version, input.source_fingerprint, input.identity_version,
858
+ input.source_completeness, input.last_attempted_at, input.last_error_code,
859
+ input.indexed_at, input.updated_at
860
+ FROM input
861
+ WHERE EXISTS (
862
+ SELECT 1
863
+ FROM _emdash_media_usage_generation_writes AS writer
864
+ WHERE writer.source_key = input.source_key
865
+ AND writer.generation = input.current_generation
866
+ AND writer.lease_token = input.lease_token
867
+ AND ${this.generationWriteLeaseExpiryIsInFuture("writer.expires_at")}
868
+ )
869
+ AND EXISTS (
870
+ SELECT 1
871
+ FROM _emdash_collections AS collection
872
+ INNER JOIN _emdash_media_usage_index_status AS status
873
+ ON status.collection_id = collection.id
874
+ AND status.scope_key = collection.slug
875
+ WHERE collection.id = input.collection_id
876
+ AND collection.slug = input.collection_slug
877
+ AND status.adapter_id = 'content-media'
878
+ AND status.scope_type = 'collection'
879
+ AND status.capture_state = 'active'
880
+ AND NOT EXISTS (
881
+ SELECT 1
882
+ FROM _emdash_media_usage_collection_deletions AS deletion
883
+ WHERE deletion.collection_id = input.collection_id
884
+ )
885
+ )
886
+ AND EXISTS (
887
+ SELECT 1
888
+ FROM ${sql.ref(tableName)} AS content
889
+ WHERE content.id = input.content_id
890
+ AND content.version = input.source_version
891
+ AND content.updated_at = input.source_updated_at
892
+ AND (
893
+ (input.source_variant = 'columns' AND ${nullableRefMatch("content.live_revision_id", "input.revision_id")})
894
+ OR
895
+ (input.source_variant = 'draft_overlay' AND ${nullableRefMatch("content.draft_revision_id", "input.revision_id")})
896
+ )
897
+ )
898
+ ON CONFLICT (source_key) DO NOTHING
899
+ RETURNING source_key
900
+ `.execute(this.db);
901
+ for (const row of result.rows) inserted.add(row.source_key);
902
+ }
903
+ return inserted;
904
+ } finally {
905
+ await sql`
906
+ WITH input AS (${leases})
907
+ DELETE FROM _emdash_media_usage_generation_writes AS writer
908
+ WHERE EXISTS (
909
+ SELECT 1
910
+ FROM input
911
+ WHERE input.source_key = writer.source_key
912
+ AND input.generation = writer.generation
913
+ AND input.lease_token = writer.lease_token
914
+ )
915
+ `.execute(this.db);
916
+ }
917
+ }
918
+
919
+ async replaceExistingSourcesBatch(
920
+ projections: readonly MediaUsageExistingSourceProjection[],
921
+ ): Promise<Set<string>> {
922
+ const unique = [
923
+ ...new Map(
924
+ projections.map((projection) => [projection.source.sourceKey, projection]),
925
+ ).values(),
926
+ ];
927
+ if (unique.length === 0) return new Set();
928
+ const collectionSlug = unique[0]?.source.collectionSlug;
929
+ const collectionId = unique[0]?.source.collectionId;
930
+ if (!collectionSlug || !collectionId) {
931
+ throw new Error("Canonical media usage batch requires collection identity");
932
+ }
933
+ validateIdentifier(collectionSlug, "collection slug");
934
+ if (
935
+ unique.some(
936
+ (projection) =>
937
+ projection.source.collectionSlug !== collectionSlug ||
938
+ projection.source.collectionId !== collectionId,
939
+ )
940
+ ) {
941
+ throw new Error("Canonical media usage batch must contain one collection");
942
+ }
943
+
944
+ const now = new Date().toISOString();
945
+ const expiresAt = new Date(Date.now() + MEDIA_USAGE_GENERATION_WRITE_LEASE_MS).toISOString();
946
+ const prepared = unique.map((projection) => {
947
+ const generation = ulid();
948
+ return {
949
+ projection,
950
+ generation,
951
+ leaseToken: ulid(),
952
+ row: this.buildSourceRow(projection.source, generation, now),
953
+ };
954
+ });
955
+ const sourceRows = prepared.map((item) => ({
956
+ ...item.row,
957
+ lease_token: item.leaseToken,
958
+ expected_generation: item.projection.expectedSource.currentGeneration,
959
+ expected_collection_id: item.projection.expectedSource.collectionId,
960
+ expected_updated_at: item.projection.expectedSource.updatedAt,
961
+ expected_source_fingerprint: item.projection.expectedSource.sourceFingerprint,
962
+ expected_source_updated_at: item.projection.expectedSource.sourceUpdatedAt,
963
+ expected_source_version: item.projection.expectedSource.sourceVersion,
964
+ expected_identity_version: item.projection.expectedSource.identityVersion,
965
+ expected_revision_id: item.projection.expectedSource.revisionId,
966
+ expected_source_completeness: item.projection.expectedSource.sourceCompleteness,
967
+ expected_last_attempted_at: item.projection.expectedSource.lastAttemptedAt,
968
+ expected_last_error_code: item.projection.expectedSource.lastErrorCode,
969
+ }));
970
+ const occurrenceRows = batchOccurrenceRows(prepared, now);
971
+ const estimatedQueries =
972
+ 2 + chunkJsonRows(occurrenceRows).length + chunkJsonRows(sourceRows).length;
973
+ if (!mediaUsageQueryBudgetAllows(estimatedQueries)) {
974
+ if (unique.length === 1) return new Set();
975
+ const midpoint = Math.ceil(unique.length / 2);
976
+ const replaced = new Set<string>();
977
+ mergeInto(replaced, await this.replaceExistingSourcesBatch(unique.slice(0, midpoint)));
978
+ mergeInto(replaced, await this.replaceExistingSourcesBatch(unique.slice(midpoint)));
979
+ return replaced;
980
+ }
981
+ const leasesPayload = JSON.stringify(
982
+ prepared.map((item) => ({
983
+ source_key: item.row.source_key,
984
+ collection_id: item.row.collection_id,
985
+ collection_slug: item.row.collection_slug,
986
+ generation: item.generation,
987
+ lease_token: item.leaseToken,
988
+ expires_at: expiresAt,
989
+ created_at: now,
990
+ })),
991
+ );
992
+ const leases = this.generationWriteBatchInput(leasesPayload);
993
+ await sql`
994
+ WITH input AS (${leases})
995
+ INSERT INTO _emdash_media_usage_generation_writes (
996
+ source_key, generation, lease_token, expires_at, created_at
997
+ )
998
+ SELECT source_key, generation, lease_token, expires_at, created_at
999
+ FROM input
1000
+ WHERE EXISTS (
1001
+ SELECT 1
1002
+ FROM _emdash_collections AS collection
1003
+ INNER JOIN _emdash_media_usage_index_status AS status
1004
+ ON status.collection_id = collection.id
1005
+ AND status.scope_key = collection.slug
1006
+ WHERE collection.id = input.collection_id
1007
+ AND collection.slug = input.collection_slug
1008
+ AND status.adapter_id = 'content-media'
1009
+ AND status.scope_type = 'collection'
1010
+ AND status.capture_state = 'active'
1011
+ AND NOT EXISTS (
1012
+ SELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion
1013
+ WHERE deletion.collection_id = input.collection_id
1014
+ )
1015
+ )
1016
+ `.execute(this.db);
1017
+
1018
+ try {
1019
+ await this.insertBatchOccurrences(occurrenceRows);
1020
+ const tableName = `ec_${collectionSlug}`;
1021
+ validateIdentifier(tableName, "content table");
1022
+ const replaced = new Set<string>();
1023
+ for (const sourceRowBatch of chunkJsonRows(sourceRows)) {
1024
+ const input = this.sourceBatchInput(JSON.stringify(sourceRowBatch));
1025
+ const result = await sql<{ source_key: string }>`
1026
+ WITH input AS (${input})
1027
+ UPDATE _emdash_media_usage_sources AS source
1028
+ SET source_type = input.source_type,
1029
+ collection_id = input.collection_id,
1030
+ collection_slug = input.collection_slug,
1031
+ content_id = input.content_id,
1032
+ source_variant = input.source_variant,
1033
+ locale = input.locale,
1034
+ translation_group = input.translation_group,
1035
+ content_slug = input.content_slug,
1036
+ content_title = input.content_title,
1037
+ content_status = input.content_status,
1038
+ content_scheduled_at = input.content_scheduled_at,
1039
+ content_deleted_at = input.content_deleted_at,
1040
+ revision_id = input.revision_id,
1041
+ current_generation = input.current_generation,
1042
+ schema_version = input.schema_version,
1043
+ source_updated_at = input.source_updated_at,
1044
+ source_version = input.source_version,
1045
+ source_fingerprint = input.source_fingerprint,
1046
+ identity_version = input.identity_version,
1047
+ source_completeness = input.source_completeness,
1048
+ last_attempted_at = input.last_attempted_at,
1049
+ last_error_code = input.last_error_code,
1050
+ indexed_at = input.indexed_at,
1051
+ updated_at = input.updated_at
1052
+ FROM input
1053
+ WHERE source.source_key = input.source_key
1054
+ AND source.current_generation = input.expected_generation
1055
+ AND ${this.batchRefsMatch("source.collection_id", "input.expected_collection_id")}
1056
+ AND source.updated_at = input.expected_updated_at
1057
+ AND ${this.batchRefsMatch("source.source_fingerprint", "input.expected_source_fingerprint")}
1058
+ AND ${this.batchRefsMatch("source.source_updated_at", "input.expected_source_updated_at")}
1059
+ AND ${this.batchRefsMatch("source.source_version", "input.expected_source_version")}
1060
+ AND ${this.batchRefsMatch("source.identity_version", "input.expected_identity_version")}
1061
+ AND ${this.batchRefsMatch("source.revision_id", "input.expected_revision_id")}
1062
+ AND source.source_completeness = input.expected_source_completeness
1063
+ AND ${this.batchRefsMatch("source.last_attempted_at", "input.expected_last_attempted_at")}
1064
+ AND ${this.batchRefsMatch("source.last_error_code", "input.expected_last_error_code")}
1065
+ AND EXISTS (
1066
+ SELECT 1
1067
+ FROM _emdash_media_usage_generation_writes AS writer
1068
+ WHERE writer.source_key = input.source_key
1069
+ AND writer.generation = input.current_generation
1070
+ AND writer.lease_token = input.lease_token
1071
+ AND ${this.generationWriteLeaseExpiryIsInFuture("writer.expires_at")}
1072
+ )
1073
+ AND EXISTS (
1074
+ SELECT 1
1075
+ FROM _emdash_collections AS collection
1076
+ INNER JOIN _emdash_media_usage_index_status AS status
1077
+ ON status.collection_id = collection.id
1078
+ AND status.scope_key = collection.slug
1079
+ WHERE collection.id = input.collection_id
1080
+ AND collection.slug = input.collection_slug
1081
+ AND status.adapter_id = 'content-media'
1082
+ AND status.scope_type = 'collection'
1083
+ AND status.capture_state = 'active'
1084
+ AND NOT EXISTS (
1085
+ SELECT 1
1086
+ FROM _emdash_media_usage_collection_deletions AS deletion
1087
+ WHERE deletion.collection_id = input.collection_id
1088
+ )
1089
+ )
1090
+ AND EXISTS (
1091
+ SELECT 1
1092
+ FROM ${sql.ref(tableName)} AS content
1093
+ WHERE content.id = input.content_id
1094
+ AND content.version = input.source_version
1095
+ AND content.updated_at = input.source_updated_at
1096
+ AND (
1097
+ (input.source_variant = 'columns' AND ${nullableRefMatch("content.live_revision_id", "input.revision_id")})
1098
+ OR
1099
+ (input.source_variant = 'draft_overlay' AND ${nullableRefMatch("content.draft_revision_id", "input.revision_id")})
1100
+ )
1101
+ )
1102
+ RETURNING ${isPostgres(this.db) ? sql`source.source_key` : sql`source_key`} AS source_key
1103
+ `.execute(this.db);
1104
+ for (const row of result.rows) replaced.add(row.source_key);
1105
+ }
1106
+ return replaced;
1107
+ } finally {
1108
+ await sql`
1109
+ WITH input AS (${leases})
1110
+ DELETE FROM _emdash_media_usage_generation_writes AS writer
1111
+ WHERE EXISTS (
1112
+ SELECT 1
1113
+ FROM input
1114
+ WHERE input.source_key = writer.source_key
1115
+ AND input.generation = writer.generation
1116
+ AND input.lease_token = writer.lease_token
1117
+ )
1118
+ `.execute(this.db);
1119
+ }
1120
+ }
1121
+
1122
+ async matchingExistingSourcesBatch(
1123
+ projections: readonly MediaUsageExistingSourceProjection[],
1124
+ ): Promise<Set<string>> {
1125
+ if (projections.length === 0) return new Set();
1126
+ const now = new Date().toISOString();
1127
+ const rows = projections.map((projection) => ({
1128
+ ...this.buildSourceRow(projection.source, projection.expectedSource.currentGeneration, now),
1129
+ expected_generation: projection.expectedSource.currentGeneration,
1130
+ expected_collection_id: projection.expectedSource.collectionId,
1131
+ expected_updated_at: projection.expectedSource.updatedAt,
1132
+ expected_source_fingerprint: projection.expectedSource.sourceFingerprint,
1133
+ expected_source_updated_at: projection.expectedSource.sourceUpdatedAt,
1134
+ expected_source_version: projection.expectedSource.sourceVersion,
1135
+ expected_identity_version: projection.expectedSource.identityVersion,
1136
+ expected_revision_id: projection.expectedSource.revisionId,
1137
+ expected_source_completeness: projection.expectedSource.sourceCompleteness,
1138
+ expected_last_attempted_at: projection.expectedSource.lastAttemptedAt,
1139
+ expected_last_error_code: projection.expectedSource.lastErrorCode,
1140
+ }));
1141
+ if (!mediaUsageQueryBudgetAllows(chunkJsonRows(rows).length)) {
1142
+ if (projections.length === 1) return new Set();
1143
+ const midpoint = Math.ceil(projections.length / 2);
1144
+ const matched = new Set<string>();
1145
+ mergeInto(matched, await this.matchingExistingSourcesBatch(projections.slice(0, midpoint)));
1146
+ mergeInto(matched, await this.matchingExistingSourcesBatch(projections.slice(midpoint)));
1147
+ return matched;
1148
+ }
1149
+ const matched = new Set<string>();
1150
+ for (const batch of chunkJsonRows(rows)) {
1151
+ const input = this.sourceBatchInput(JSON.stringify(batch));
1152
+ const result = await sql<{ source_key: string }>`
1153
+ WITH input AS (${input})
1154
+ SELECT source.source_key
1155
+ FROM _emdash_media_usage_sources AS source
1156
+ INNER JOIN input ON input.source_key = source.source_key
1157
+ WHERE source.current_generation = input.expected_generation
1158
+ AND ${this.batchRefsMatch("source.collection_id", "input.expected_collection_id")}
1159
+ AND source.updated_at = input.expected_updated_at
1160
+ AND ${this.batchRefsMatch("source.source_fingerprint", "input.expected_source_fingerprint")}
1161
+ AND ${this.batchRefsMatch("source.source_updated_at", "input.expected_source_updated_at")}
1162
+ AND ${this.batchRefsMatch("source.source_version", "input.expected_source_version")}
1163
+ AND ${this.batchRefsMatch("source.identity_version", "input.expected_identity_version")}
1164
+ AND ${this.batchRefsMatch("source.revision_id", "input.expected_revision_id")}
1165
+ AND source.source_completeness = input.expected_source_completeness
1166
+ AND ${this.batchRefsMatch("source.last_attempted_at", "input.expected_last_attempted_at")}
1167
+ AND ${this.batchRefsMatch("source.last_error_code", "input.expected_last_error_code")}
1168
+ AND EXISTS (
1169
+ SELECT 1
1170
+ FROM _emdash_collections AS collection
1171
+ WHERE collection.id = source.collection_id
1172
+ AND collection.slug = source.collection_slug
1173
+ )
1174
+ `.execute(this.db);
1175
+ for (const row of result.rows) matched.add(row.source_key);
1176
+ }
1177
+ return matched;
1178
+ }
1179
+
622
1180
  async markSourceAttempted(source: MediaUsageSourceInput): Promise<MediaUsageSource> {
623
1181
  if (source.collectionId !== undefined && source.collectionId !== null) {
624
1182
  const expectedSource = await this.findSource(source.sourceKey);
@@ -762,6 +1320,129 @@ export class MediaUsageRepository {
762
1320
  }));
763
1321
  }
764
1322
 
1323
+ async findCollectionProgress(): Promise<MediaUsageCollectionProgress | null> {
1324
+ const result = await sql<{
1325
+ activation_active: boolean | number;
1326
+ activation_generation: number | string | null;
1327
+ needs_attention: boolean | number;
1328
+ ready_collections: number | string;
1329
+ total_collections: number | string;
1330
+ cleanup_pending: boolean | number;
1331
+ }>`
1332
+ WITH collection_progress AS (
1333
+ SELECT
1334
+ CASE WHEN status.status = 'complete'
1335
+ AND status.schema_version = ${CONTENT_SOURCE_SCHEMA_VERSION}
1336
+ AND status.reconciliation_required = 0
1337
+ AND status.capture_state = 'active'
1338
+ AND NOT EXISTS (
1339
+ SELECT 1 FROM _emdash_media_usage_work AS work
1340
+ WHERE work.collection_id = collection.id
1341
+ AND work.collection_slug = collection.slug
1342
+ )
1343
+ THEN 1 ELSE 0 END AS is_ready,
1344
+ CASE WHEN status.collection_id IS NULL
1345
+ OR COALESCE(status.capture_state, '') <> 'active'
1346
+ OR status.status NOT IN ('complete', 'never', 'running', 'partial', 'failed', 'stale')
1347
+ OR status.status = 'failed'
1348
+ OR (
1349
+ status.reconciliation_required = 0
1350
+ AND (
1351
+ status.status <> 'complete'
1352
+ OR COALESCE(status.schema_version, -1) <> ${CONTENT_SOURCE_SCHEMA_VERSION}
1353
+ )
1354
+ AND NOT EXISTS (
1355
+ SELECT 1 FROM _emdash_media_usage_work AS work
1356
+ WHERE work.collection_id = collection.id
1357
+ AND work.collection_slug = collection.slug
1358
+ )
1359
+ )
1360
+ OR EXISTS (
1361
+ SELECT 1 FROM _emdash_media_usage_work AS work
1362
+ WHERE work.collection_id = collection.id
1363
+ AND work.collection_slug = collection.slug
1364
+ AND work.state = 'failed'
1365
+ )
1366
+ OR EXISTS (
1367
+ SELECT 1 FROM _emdash_media_usage_reconciliations AS reconciliation
1368
+ WHERE reconciliation.collection_id = collection.id
1369
+ AND reconciliation.collection_slug = collection.slug
1370
+ AND reconciliation.state = 'failed'
1371
+ AND status.reconciliation_required = 1
1372
+ AND (
1373
+ reconciliation.target_epoch IS NULL
1374
+ OR reconciliation.target_epoch >= status.change_epoch
1375
+ )
1376
+ )
1377
+ THEN 1 ELSE 0 END AS needs_attention
1378
+ FROM _emdash_collections AS collection
1379
+ LEFT JOIN _emdash_media_usage_index_status AS status
1380
+ ON status.adapter_id = 'content-media'
1381
+ AND status.scope_type = 'collection'
1382
+ AND status.collection_id = collection.id
1383
+ AND status.scope_key = collection.slug
1384
+ WHERE NOT EXISTS (
1385
+ SELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion
1386
+ WHERE deletion.collection_id = collection.id
1387
+ AND deletion.collection_slug = collection.slug
1388
+ )
1389
+ )
1390
+ SELECT
1391
+ (
1392
+ SELECT activation.runtime_generation
1393
+ FROM _emdash_media_usage_activation AS activation
1394
+ WHERE activation.task_key = 'incremental_capture'
1395
+ ) AS activation_generation,
1396
+ EXISTS (
1397
+ SELECT 1 FROM _emdash_media_usage_activation AS activation
1398
+ WHERE activation.task_key = 'incremental_capture'
1399
+ AND activation.state = 'active'
1400
+ ) AS activation_active,
1401
+ COUNT(*) AS total_collections,
1402
+ COALESCE(SUM(is_ready), 0) AS ready_collections,
1403
+ EXISTS (
1404
+ SELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion
1405
+ WHERE deletion.state IN ('pending', 'retry', 'leased')
1406
+ ) AS cleanup_pending,
1407
+ CASE WHEN COALESCE(MAX(needs_attention), 0) = 1 OR EXISTS (
1408
+ SELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion
1409
+ WHERE deletion.state = 'failed'
1410
+ ) THEN 1 ELSE 0 END AS needs_attention
1411
+ FROM collection_progress
1412
+ `.execute(this.db);
1413
+ const row = result.rows[0];
1414
+ if (!row) throw new Error("Media usage progress query returned no result");
1415
+ if (!row.activation_active) return null;
1416
+ if (Number(row.activation_generation) !== MEDIA_USAGE_ACTIVATION_RUNTIME_GENERATION) {
1417
+ throw new MediaUsageActivationVersionMismatchError(
1418
+ "Media usage activation runtime generation is incompatible",
1419
+ );
1420
+ }
1421
+ const readyCollections = Number(row.ready_collections);
1422
+ const totalCollections = Number(row.total_collections);
1423
+ const cleanupPending = Number(row.cleanup_pending);
1424
+ const needsAttention = Number(row.needs_attention);
1425
+ if (
1426
+ !Number.isSafeInteger(readyCollections) ||
1427
+ !Number.isSafeInteger(totalCollections) ||
1428
+ readyCollections < 0 ||
1429
+ totalCollections < readyCollections ||
1430
+ (cleanupPending !== 0 && cleanupPending !== 1) ||
1431
+ (needsAttention !== 0 && needsAttention !== 1)
1432
+ ) {
1433
+ throw new Error("Media usage progress query returned invalid counts");
1434
+ }
1435
+ return {
1436
+ status: needsAttention
1437
+ ? "needs_attention"
1438
+ : readyCollections === totalCollections && cleanupPending === 0
1439
+ ? "ready"
1440
+ : "indexing",
1441
+ readyCollections,
1442
+ totalCollections,
1443
+ };
1444
+ }
1445
+
765
1446
  async findCurrentEntryUsagePageByMediaId(
766
1447
  mediaId: string,
767
1448
  options: FindMediaUsageOptions = {},
@@ -1120,12 +1801,21 @@ export class MediaUsageRepository {
1120
1801
  };
1121
1802
  }
1122
1803
 
1804
+ /**
1805
+ * Scans for reclaimable occurrences, or resolves to `null` when the sweep was
1806
+ * never issued because the lease is gone or the tick is out of budget. An
1807
+ * empty array means the scan ran and found nothing, which lets the caller
1808
+ * retire the sweep window; `null` must leave the cursor where it was.
1809
+ */
1123
1810
  async findMediaUsageCleanupCandidates(input: {
1124
1811
  cutoff: string;
1125
1812
  cursor: MediaUsageCleanupCursor | null;
1126
1813
  limit: number;
1127
1814
  cleanupLease?: MediaUsageCleanupLease;
1128
- }): Promise<MediaUsageCleanupCandidate[]> {
1815
+ canIssueStatement?: () => boolean;
1816
+ }): Promise<MediaUsageCleanupCandidate[] | null> {
1817
+ if (!(await this.admitCleanupScan(input.cleanupLease, input.canIssueStatement))) return null;
1818
+
1129
1819
  let query = this.db
1130
1820
  .selectFrom("_emdash_media_usage as u")
1131
1821
  .leftJoin("_emdash_media_usage_sources as s", "s.source_key", "u.source_key")
@@ -1147,19 +1837,11 @@ export class MediaUsageRepository {
1147
1837
  .orderBy("u.created_at", "asc")
1148
1838
  .orderBy("u.id", "asc")
1149
1839
  .limit(Math.max(0, Math.floor(input.limit)));
1150
- if (input.cleanupLease) {
1151
- query = query.where(this.activeCleanupLeaseExpression(input.cleanupLease));
1152
- }
1153
1840
 
1154
1841
  if (input.cursor) {
1842
+ const cursor = input.cursor;
1155
1843
  query = query.where((eb) =>
1156
- eb.or([
1157
- eb("u.created_at", ">", input.cursor!.createdAt),
1158
- eb.and([
1159
- eb("u.created_at", "=", input.cursor!.createdAt),
1160
- eb("u.id", ">", input.cursor!.id),
1161
- ]),
1162
- ]),
1844
+ eb(eb.refTuple("u.created_at", "u.id"), ">", eb.tuple(cursor.createdAt, cursor.id)),
1163
1845
  );
1164
1846
  }
1165
1847
 
@@ -1247,9 +1929,9 @@ export class MediaUsageRepository {
1247
1929
  options.canIssueStatement,
1248
1930
  );
1249
1931
  }
1250
- if (!canIssueCleanupStatement(options.canIssueStatement)) return 0;
1932
+ if (!(await this.admitCleanupScan(options.cleanupLease, options.canIssueStatement))) return 0;
1251
1933
 
1252
- let query = this.db
1934
+ const rows = await this.db
1253
1935
  .selectFrom("_emdash_media_usage as u")
1254
1936
  .leftJoin("_emdash_media_usage_sources as s", (join) =>
1255
1937
  join.onRef("s.source_key", "=", "u.source_key"),
@@ -1265,11 +1947,8 @@ export class MediaUsageRepository {
1265
1947
  .where(this.noActiveGenerationWriteExpression("u"))
1266
1948
  .orderBy("u.created_at", "asc")
1267
1949
  .orderBy("u.id", "asc")
1268
- .limit(batchLimit);
1269
- if (options.cleanupLease) {
1270
- query = query.where(this.activeCleanupLeaseExpression(options.cleanupLease));
1271
- }
1272
- const rows = await query.execute();
1950
+ .limit(batchLimit)
1951
+ .execute();
1273
1952
 
1274
1953
  return this.deleteOrphanCandidateIds(
1275
1954
  rows.map((row) => row.id),
@@ -1294,9 +1973,9 @@ export class MediaUsageRepository {
1294
1973
  options.canIssueStatement,
1295
1974
  );
1296
1975
  }
1297
- if (!canIssueCleanupStatement(options.canIssueStatement)) return 0;
1976
+ if (!(await this.admitCleanupScan(options.cleanupLease, options.canIssueStatement))) return 0;
1298
1977
 
1299
- let query = this.db
1978
+ const rows = await this.db
1300
1979
  .selectFrom("_emdash_media_usage as u")
1301
1980
  .innerJoin("_emdash_media_usage_sources as s", (join) =>
1302
1981
  join.onRef("s.source_key", "=", "u.source_key"),
@@ -1313,11 +1992,8 @@ export class MediaUsageRepository {
1313
1992
  .where(this.noActiveGenerationWriteExpression("u"))
1314
1993
  .orderBy("u.created_at", "asc")
1315
1994
  .orderBy("u.id", "asc")
1316
- .limit(batchLimit);
1317
- if (options.cleanupLease) {
1318
- query = query.where(this.activeCleanupLeaseExpression(options.cleanupLease));
1319
- }
1320
- const rows = await query.execute();
1995
+ .limit(batchLimit)
1996
+ .execute();
1321
1997
 
1322
1998
  return this.deleteStaleCandidateIds(
1323
1999
  rows.map((row) => row.id),
@@ -1342,9 +2018,9 @@ export class MediaUsageRepository {
1342
2018
  options.canIssueStatement,
1343
2019
  );
1344
2020
  }
1345
- if (!canIssueCleanupStatement(options.canIssueStatement)) return 0;
2021
+ if (!(await this.admitCleanupScan(options.cleanupLease, options.canIssueStatement))) return 0;
1346
2022
 
1347
- let query = this.db
2023
+ const rows = await this.db
1348
2024
  .selectFrom("_emdash_media_usage as u")
1349
2025
  .innerJoin("_emdash_media_usage_sources as s", (join) =>
1350
2026
  join.onRef("s.source_key", "=", "u.source_key"),
@@ -1361,11 +2037,8 @@ export class MediaUsageRepository {
1361
2037
  .where(this.noActiveGenerationWriteExpression("u"))
1362
2038
  .orderBy("u.created_at", "asc")
1363
2039
  .orderBy("u.id", "asc")
1364
- .limit(batchLimit);
1365
- if (options.cleanupLease) {
1366
- query = query.where(this.activeCleanupLeaseExpression(options.cleanupLease));
1367
- }
1368
- const rows = await query.execute();
2040
+ .limit(batchLimit)
2041
+ .execute();
1369
2042
 
1370
2043
  return this.deleteAbandonedCandidateIds(
1371
2044
  rows.map((row) => row.id),
@@ -1381,16 +2054,16 @@ export class MediaUsageRepository {
1381
2054
  canIssueStatement?: () => boolean,
1382
2055
  ): Promise<number> {
1383
2056
  const batchLimit = Math.floor(limit);
1384
- if (batchLimit <= 0 || !canIssueCleanupStatement(canIssueStatement)) return 0;
1385
- let query = this.db
2057
+ if (batchLimit <= 0) return 0;
2058
+ if (!(await this.admitCleanupScan(cleanupLease, canIssueStatement))) return 0;
2059
+ const rows = await this.db
1386
2060
  .selectFrom("_emdash_media_usage_generation_writes")
1387
2061
  .select("lease_token")
1388
2062
  .where(this.generationWriteLeaseHasExpired("expires_at"))
1389
2063
  .orderBy("expires_at", "asc")
1390
2064
  .orderBy("lease_token", "asc")
1391
- .limit(batchLimit);
1392
- if (cleanupLease) query = query.where(this.activeCleanupLeaseExpression(cleanupLease));
1393
- const rows = await query.execute();
2065
+ .limit(batchLimit)
2066
+ .execute();
1394
2067
  if (rows.length === 0 || !canIssueCleanupStatement(canIssueStatement)) return 0;
1395
2068
  let deleteQuery = this.db
1396
2069
  .deleteFrom("_emdash_media_usage_generation_writes")
@@ -1694,6 +2367,7 @@ export class MediaUsageRepository {
1694
2367
  last_error_code: sql<
1695
2368
  string | null
1696
2369
  >`CASE WHEN ${canComplete} THEN NULL ELSE last_error_code END`,
2370
+ cursor: sql<string | null>`CASE WHEN ${canComplete} THEN NULL ELSE cursor END`,
1697
2371
  last_incremental_success_at: now,
1698
2372
  updated_at: now,
1699
2373
  })
@@ -1715,6 +2389,94 @@ export class MediaUsageRepository {
1715
2389
  return Number(result.numUpdatedRows ?? 0) > 0;
1716
2390
  }
1717
2391
 
2392
+ async prepareIncrementalFinalization(
2393
+ input: MediaUsageIncrementalStatusIdentity,
2394
+ ): Promise<
2395
+ { outcome: "marked"; marker: string } | { outcome: "not_required" } | { outcome: "lost" }
2396
+ > {
2397
+ const observed = await this.db
2398
+ .selectFrom("_emdash_media_usage_index_status")
2399
+ .select(["change_epoch", "reconciliation_required"])
2400
+ .where("adapter_id", "=", "content-media")
2401
+ .where("scope_type", "=", "collection")
2402
+ .where("scope_key", "=", input.collectionSlug)
2403
+ .where("collection_id", "=", input.collectionId)
2404
+ .where("capture_state", "=", "active")
2405
+ .executeTakeFirst();
2406
+ if (!observed) return { outcome: "lost" };
2407
+ if (observed.reconciliation_required !== 0) return { outcome: "not_required" };
2408
+ const marker = `incremental-finalize:${String(observed.change_epoch)}:${ulid()}`;
2409
+ const result = await this.db
2410
+ .updateTable("_emdash_media_usage_index_status")
2411
+ .set({ cursor: marker, updated_at: this.sortableUtcTimestamp() })
2412
+ .where("adapter_id", "=", "content-media")
2413
+ .where("scope_type", "=", "collection")
2414
+ .where("scope_key", "=", input.collectionSlug)
2415
+ .where("collection_id", "=", input.collectionId)
2416
+ .where("capture_state", "=", "active")
2417
+ .where("reconciliation_required", "=", 0)
2418
+ .where("change_epoch", "=", observed.change_epoch)
2419
+ .where(
2420
+ sql<boolean>`EXISTS (
2421
+ SELECT 1
2422
+ FROM _emdash_collections AS collection
2423
+ WHERE collection.id = ${input.collectionId}
2424
+ AND collection.slug = ${input.collectionSlug}
2425
+ )`,
2426
+ )
2427
+ .where(
2428
+ sql<boolean>`NOT EXISTS (
2429
+ SELECT 1
2430
+ FROM _emdash_media_usage_collection_deletions AS deletion
2431
+ WHERE deletion.collection_id = ${input.collectionId}
2432
+ )`,
2433
+ )
2434
+ .executeTakeFirst();
2435
+ return Number(result.numUpdatedRows ?? 0) > 0
2436
+ ? { outcome: "marked", marker }
2437
+ : { outcome: "lost" };
2438
+ }
2439
+
2440
+ async recoverIncrementalFinalizations(): Promise<number> {
2441
+ const markerMatchesEpoch = isPostgres(this.db)
2442
+ ? sql<boolean>`status.cursor LIKE ('incremental-finalize:' || status.change_epoch::text || ':%')`
2443
+ : sql<boolean>`status.cursor LIKE ('incremental-finalize:' || CAST(status.change_epoch AS text) || ':%')`;
2444
+ const now = new Date().toISOString();
2445
+ const result = await sql<{ collection_id: string }>`
2446
+ UPDATE _emdash_media_usage_index_status AS status
2447
+ SET status = 'complete',
2448
+ completed_at = ${now},
2449
+ cursor = NULL,
2450
+ last_error_code = NULL,
2451
+ last_incremental_success_at = ${now},
2452
+ updated_at = ${now}
2453
+ WHERE status.adapter_id = 'content-media'
2454
+ AND status.scope_type = 'collection'
2455
+ AND status.capture_state = 'active'
2456
+ AND status.reconciliation_required = 0
2457
+ AND status.cursor IS NOT NULL
2458
+ AND ${markerMatchesEpoch}
2459
+ AND EXISTS (
2460
+ SELECT 1
2461
+ FROM _emdash_collections AS collection
2462
+ WHERE collection.id = status.collection_id
2463
+ AND collection.slug = status.scope_key
2464
+ )
2465
+ AND NOT EXISTS (
2466
+ SELECT 1
2467
+ FROM _emdash_media_usage_collection_deletions AS deletion
2468
+ WHERE deletion.collection_id = status.collection_id
2469
+ )
2470
+ AND NOT EXISTS (
2471
+ SELECT 1
2472
+ FROM _emdash_media_usage_work AS work
2473
+ WHERE work.collection_id = status.collection_id
2474
+ )
2475
+ RETURNING collection_id
2476
+ `.execute(this.db);
2477
+ return result.rows.length;
2478
+ }
2479
+
1718
2480
  async recordIncrementalFailure(
1719
2481
  input: MediaUsageIncrementalStatusIdentity & {
1720
2482
  contentId: string;
@@ -1778,6 +2540,71 @@ export class MediaUsageRepository {
1778
2540
  return Number(result.numUpdatedRows ?? 0) > 0;
1779
2541
  }
1780
2542
 
2543
+ async recordIncrementalFailuresByCollection(input: {
2544
+ collectionIds: readonly string[];
2545
+ errorCode: string;
2546
+ }): Promise<Set<string>> {
2547
+ const collectionIds = [...new Set(input.collectionIds)];
2548
+ if (collectionIds.length === 0) return new Set();
2549
+ const collections = jsonTextValues(this.db, collectionIds);
2550
+ const now = new Date().toISOString();
2551
+ const result = await sql<{ collection_id: string }>`
2552
+ WITH failed_collections AS (${collections})
2553
+ UPDATE _emdash_media_usage_index_status AS status
2554
+ SET status = CASE
2555
+ WHEN EXISTS (
2556
+ SELECT 1
2557
+ FROM _emdash_media_usage_reconciliations AS reconciliation
2558
+ WHERE reconciliation.collection_id = status.collection_id
2559
+ AND reconciliation.run_token = status.cursor
2560
+ ) THEN status.status
2561
+ WHEN status.reconciliation_required = 0 THEN 'partial'
2562
+ WHEN status.status = 'running' THEN 'stale'
2563
+ ELSE status.status
2564
+ END,
2565
+ completed_at = CASE
2566
+ WHEN EXISTS (
2567
+ SELECT 1
2568
+ FROM _emdash_media_usage_reconciliations AS reconciliation
2569
+ WHERE reconciliation.collection_id = status.collection_id
2570
+ AND reconciliation.run_token = status.cursor
2571
+ ) THEN status.completed_at
2572
+ WHEN status.reconciliation_required = 0 OR status.status = 'running' THEN NULL
2573
+ ELSE status.completed_at
2574
+ END,
2575
+ cursor = CASE
2576
+ WHEN EXISTS (
2577
+ SELECT 1
2578
+ FROM _emdash_media_usage_reconciliations AS reconciliation
2579
+ WHERE reconciliation.collection_id = status.collection_id
2580
+ AND reconciliation.run_token = status.cursor
2581
+ ) THEN status.cursor
2582
+ WHEN status.status = 'running' THEN NULL
2583
+ ELSE status.cursor
2584
+ END,
2585
+ last_error_code = ${input.errorCode},
2586
+ updated_at = ${now}
2587
+ WHERE status.adapter_id = 'content-media'
2588
+ AND status.scope_type = 'collection'
2589
+ AND status.collection_id IN (SELECT value FROM failed_collections)
2590
+ AND EXISTS (
2591
+ SELECT 1
2592
+ FROM _emdash_media_usage_work AS work
2593
+ WHERE work.collection_id = status.collection_id
2594
+ AND work.state = 'failed'
2595
+ AND work.last_error_code = ${input.errorCode}
2596
+ )
2597
+ AND EXISTS (
2598
+ SELECT 1
2599
+ FROM _emdash_collections AS collection
2600
+ WHERE collection.id = status.collection_id
2601
+ AND collection.slug = status.scope_key
2602
+ )
2603
+ RETURNING collection_id
2604
+ `.execute(this.db);
2605
+ return new Set(result.rows.map((row) => row.collection_id));
2606
+ }
2607
+
1781
2608
  async findIndexStatus(
1782
2609
  identity: MediaUsageIndexStatusIdentity,
1783
2610
  ): Promise<MediaUsageIndexStatus | null> {
@@ -2071,6 +2898,43 @@ export class MediaUsageRepository {
2071
2898
  )`;
2072
2899
  }
2073
2900
 
2901
+ /**
2902
+ * Gates a bounded cleanup scan on the lease and the tick's statement budget.
2903
+ *
2904
+ * The budget is re-read after the lease row because that read is itself a
2905
+ * statement: an isolate suspended across it can resume with the budget spent.
2906
+ */
2907
+ private async admitCleanupScan(
2908
+ cleanupLease: MediaUsageCleanupLease | undefined,
2909
+ canIssueStatement: (() => boolean) | undefined,
2910
+ ): Promise<boolean> {
2911
+ if (!canIssueCleanupStatement(canIssueStatement)) return false;
2912
+ if (!cleanupLease) return true;
2913
+ return (
2914
+ (await this.holdsCleanupLease(cleanupLease)) && canIssueCleanupStatement(canIssueStatement)
2915
+ );
2916
+ }
2917
+
2918
+ /**
2919
+ * Reads the lease row on its own so a lost lease can short-circuit a sweep.
2920
+ *
2921
+ * As a row-level `EXISTS` inside a `LIMIT`ed scan this predicate is constant
2922
+ * across every candidate row: when it is false the limit is unreachable and
2923
+ * the scan reads the whole `created_at` range before returning nothing.
2924
+ * Mutating statements still carry {@link activeCleanupLeaseExpression},
2925
+ * which is what actually fences a displaced owner.
2926
+ */
2927
+ private async holdsCleanupLease(cleanupLease: MediaUsageCleanupLease): Promise<boolean> {
2928
+ let query = this.db
2929
+ .selectFrom("_emdash_media_usage_cleanup")
2930
+ .select("task_key")
2931
+ .where("task_key", "=", "projection_gc")
2932
+ .where("lease_token", "=", cleanupLease.leaseToken)
2933
+ .where(this.cleanupLeaseExpiryIsInFuture("_emdash_media_usage_cleanup.lease_expires_at"));
2934
+ if (isPostgres(this.db)) query = query.forUpdate();
2935
+ return (await query.executeTakeFirst()) !== undefined;
2936
+ }
2937
+
2074
2938
  private activeCleanupLeaseExpression(cleanupLease: MediaUsageCleanupLease) {
2075
2939
  const rowLock = isPostgres(this.db) ? sql` FOR UPDATE` : sql``;
2076
2940
  return sql<boolean>`EXISTS (
@@ -2204,6 +3068,199 @@ export class MediaUsageRepository {
2204
3068
  }
2205
3069
  }
2206
3070
 
3071
+ private async insertBatchOccurrences(rows: readonly BatchOccurrenceRow[]): Promise<void> {
3072
+ for (const rowBatch of chunkJsonRows(rows)) {
3073
+ const input = this.occurrenceBatchInput(JSON.stringify(rowBatch));
3074
+ await sql`
3075
+ WITH input AS (${input})
3076
+ INSERT INTO _emdash_media_usage (
3077
+ id, source_key, generation, field_slug, field_path, occurrence_index,
3078
+ reference_type, media_id, provider, provider_asset_id, media_kind,
3079
+ mime_type, created_at
3080
+ )
3081
+ SELECT
3082
+ id, source_key, generation, field_slug, field_path, occurrence_index,
3083
+ reference_type, media_id, provider, provider_asset_id, media_kind,
3084
+ mime_type, created_at
3085
+ FROM input
3086
+ WHERE EXISTS (
3087
+ SELECT 1
3088
+ FROM _emdash_media_usage_generation_writes AS writer
3089
+ WHERE writer.source_key = input.source_key
3090
+ AND writer.generation = input.generation
3091
+ AND writer.lease_token = input.lease_token
3092
+ AND ${this.generationWriteLeaseExpiryIsInFuture("writer.expires_at")}
3093
+ )
3094
+ `.execute(this.db);
3095
+ }
3096
+ }
3097
+
3098
+ private occurrenceBatchInput(payload: string): RawBuilder<unknown> {
3099
+ if (isPostgres(this.db)) {
3100
+ return sql`
3101
+ SELECT
3102
+ entry.value ->> 'id' AS id,
3103
+ entry.value ->> 'source_key' AS source_key,
3104
+ entry.value ->> 'generation' AS generation,
3105
+ entry.value ->> 'field_slug' AS field_slug,
3106
+ entry.value ->> 'field_path' AS field_path,
3107
+ CAST(entry.value ->> 'occurrence_index' AS integer) AS occurrence_index,
3108
+ entry.value ->> 'reference_type' AS reference_type,
3109
+ entry.value ->> 'media_id' AS media_id,
3110
+ entry.value ->> 'provider' AS provider,
3111
+ entry.value ->> 'provider_asset_id' AS provider_asset_id,
3112
+ entry.value ->> 'media_kind' AS media_kind,
3113
+ entry.value ->> 'mime_type' AS mime_type,
3114
+ entry.value ->> 'created_at' AS created_at,
3115
+ entry.value ->> 'lease_token' AS lease_token
3116
+ FROM jsonb_array_elements(${payload}::jsonb) AS entry(value)
3117
+ `;
3118
+ }
3119
+ return sql`
3120
+ SELECT
3121
+ json_extract(entry.value, '$.id') AS id,
3122
+ json_extract(entry.value, '$.source_key') AS source_key,
3123
+ json_extract(entry.value, '$.generation') AS generation,
3124
+ json_extract(entry.value, '$.field_slug') AS field_slug,
3125
+ json_extract(entry.value, '$.field_path') AS field_path,
3126
+ CAST(json_extract(entry.value, '$.occurrence_index') AS integer) AS occurrence_index,
3127
+ json_extract(entry.value, '$.reference_type') AS reference_type,
3128
+ json_extract(entry.value, '$.media_id') AS media_id,
3129
+ json_extract(entry.value, '$.provider') AS provider,
3130
+ json_extract(entry.value, '$.provider_asset_id') AS provider_asset_id,
3131
+ json_extract(entry.value, '$.media_kind') AS media_kind,
3132
+ json_extract(entry.value, '$.mime_type') AS mime_type,
3133
+ json_extract(entry.value, '$.created_at') AS created_at,
3134
+ json_extract(entry.value, '$.lease_token') AS lease_token
3135
+ FROM json_each(${payload}) AS entry
3136
+ `;
3137
+ }
3138
+
3139
+ private sourceBatchInput(payload: string): RawBuilder<unknown> {
3140
+ if (isPostgres(this.db)) {
3141
+ return sql`
3142
+ SELECT
3143
+ entry.value ->> 'source_key' AS source_key,
3144
+ entry.value ->> 'source_type' AS source_type,
3145
+ entry.value ->> 'collection_id' AS collection_id,
3146
+ entry.value ->> 'collection_slug' AS collection_slug,
3147
+ entry.value ->> 'content_id' AS content_id,
3148
+ entry.value ->> 'source_variant' AS source_variant,
3149
+ entry.value ->> 'locale' AS locale,
3150
+ entry.value ->> 'translation_group' AS translation_group,
3151
+ entry.value ->> 'content_slug' AS content_slug,
3152
+ entry.value ->> 'content_title' AS content_title,
3153
+ entry.value ->> 'content_status' AS content_status,
3154
+ entry.value ->> 'content_scheduled_at' AS content_scheduled_at,
3155
+ entry.value ->> 'content_deleted_at' AS content_deleted_at,
3156
+ entry.value ->> 'revision_id' AS revision_id,
3157
+ entry.value ->> 'current_generation' AS current_generation,
3158
+ CAST(entry.value ->> 'schema_version' AS integer) AS schema_version,
3159
+ entry.value ->> 'source_updated_at' AS source_updated_at,
3160
+ CAST(entry.value ->> 'source_version' AS bigint) AS source_version,
3161
+ entry.value ->> 'source_fingerprint' AS source_fingerprint,
3162
+ CAST(entry.value ->> 'identity_version' AS integer) AS identity_version,
3163
+ entry.value ->> 'source_completeness' AS source_completeness,
3164
+ entry.value ->> 'last_attempted_at' AS last_attempted_at,
3165
+ entry.value ->> 'last_error_code' AS last_error_code,
3166
+ entry.value ->> 'indexed_at' AS indexed_at,
3167
+ entry.value ->> 'updated_at' AS updated_at,
3168
+ entry.value ->> 'lease_token' AS lease_token,
3169
+ entry.value ->> 'expected_generation' AS expected_generation,
3170
+ entry.value ->> 'expected_collection_id' AS expected_collection_id,
3171
+ entry.value ->> 'expected_updated_at' AS expected_updated_at,
3172
+ entry.value ->> 'expected_source_fingerprint' AS expected_source_fingerprint,
3173
+ entry.value ->> 'expected_source_updated_at' AS expected_source_updated_at,
3174
+ CAST(entry.value ->> 'expected_source_version' AS bigint) AS expected_source_version,
3175
+ CAST(entry.value ->> 'expected_identity_version' AS integer) AS expected_identity_version,
3176
+ entry.value ->> 'expected_revision_id' AS expected_revision_id,
3177
+ entry.value ->> 'expected_source_completeness' AS expected_source_completeness,
3178
+ entry.value ->> 'expected_last_attempted_at' AS expected_last_attempted_at,
3179
+ entry.value ->> 'expected_last_error_code' AS expected_last_error_code
3180
+ FROM jsonb_array_elements(${payload}::jsonb) AS entry(value)
3181
+ `;
3182
+ }
3183
+ return sql`
3184
+ SELECT
3185
+ json_extract(entry.value, '$.source_key') AS source_key,
3186
+ json_extract(entry.value, '$.source_type') AS source_type,
3187
+ json_extract(entry.value, '$.collection_id') AS collection_id,
3188
+ json_extract(entry.value, '$.collection_slug') AS collection_slug,
3189
+ json_extract(entry.value, '$.content_id') AS content_id,
3190
+ json_extract(entry.value, '$.source_variant') AS source_variant,
3191
+ json_extract(entry.value, '$.locale') AS locale,
3192
+ json_extract(entry.value, '$.translation_group') AS translation_group,
3193
+ json_extract(entry.value, '$.content_slug') AS content_slug,
3194
+ json_extract(entry.value, '$.content_title') AS content_title,
3195
+ json_extract(entry.value, '$.content_status') AS content_status,
3196
+ json_extract(entry.value, '$.content_scheduled_at') AS content_scheduled_at,
3197
+ json_extract(entry.value, '$.content_deleted_at') AS content_deleted_at,
3198
+ json_extract(entry.value, '$.revision_id') AS revision_id,
3199
+ json_extract(entry.value, '$.current_generation') AS current_generation,
3200
+ CAST(json_extract(entry.value, '$.schema_version') AS integer) AS schema_version,
3201
+ json_extract(entry.value, '$.source_updated_at') AS source_updated_at,
3202
+ CAST(json_extract(entry.value, '$.source_version') AS integer) AS source_version,
3203
+ json_extract(entry.value, '$.source_fingerprint') AS source_fingerprint,
3204
+ CAST(json_extract(entry.value, '$.identity_version') AS integer) AS identity_version,
3205
+ json_extract(entry.value, '$.source_completeness') AS source_completeness,
3206
+ json_extract(entry.value, '$.last_attempted_at') AS last_attempted_at,
3207
+ json_extract(entry.value, '$.last_error_code') AS last_error_code,
3208
+ json_extract(entry.value, '$.indexed_at') AS indexed_at,
3209
+ json_extract(entry.value, '$.updated_at') AS updated_at,
3210
+ json_extract(entry.value, '$.lease_token') AS lease_token,
3211
+ json_extract(entry.value, '$.expected_generation') AS expected_generation,
3212
+ json_extract(entry.value, '$.expected_collection_id') AS expected_collection_id,
3213
+ json_extract(entry.value, '$.expected_updated_at') AS expected_updated_at,
3214
+ json_extract(entry.value, '$.expected_source_fingerprint') AS expected_source_fingerprint,
3215
+ json_extract(entry.value, '$.expected_source_updated_at') AS expected_source_updated_at,
3216
+ CAST(json_extract(entry.value, '$.expected_source_version') AS integer) AS expected_source_version,
3217
+ CAST(json_extract(entry.value, '$.expected_identity_version') AS integer) AS expected_identity_version,
3218
+ json_extract(entry.value, '$.expected_revision_id') AS expected_revision_id,
3219
+ json_extract(entry.value, '$.expected_source_completeness') AS expected_source_completeness,
3220
+ json_extract(entry.value, '$.expected_last_attempted_at') AS expected_last_attempted_at,
3221
+ json_extract(entry.value, '$.expected_last_error_code') AS expected_last_error_code
3222
+ FROM json_each(${payload}) AS entry
3223
+ `;
3224
+ }
3225
+
3226
+ private generationWriteBatchInput(payload: string): RawBuilder<unknown> {
3227
+ if (isPostgres(this.db)) {
3228
+ return sql`
3229
+ SELECT
3230
+ entry.value ->> 'source_key' AS source_key,
3231
+ entry.value ->> 'collection_id' AS collection_id,
3232
+ entry.value ->> 'collection_slug' AS collection_slug,
3233
+ entry.value ->> 'generation' AS generation,
3234
+ entry.value ->> 'lease_token' AS lease_token,
3235
+ entry.value ->> 'expires_at' AS expires_at,
3236
+ entry.value ->> 'created_at' AS created_at
3237
+ FROM jsonb_array_elements(${payload}::jsonb) AS entry(value)
3238
+ `;
3239
+ }
3240
+ return sql`
3241
+ SELECT
3242
+ json_extract(entry.value, '$.source_key') AS source_key,
3243
+ json_extract(entry.value, '$.collection_id') AS collection_id,
3244
+ json_extract(entry.value, '$.collection_slug') AS collection_slug,
3245
+ json_extract(entry.value, '$.generation') AS generation,
3246
+ json_extract(entry.value, '$.lease_token') AS lease_token,
3247
+ json_extract(entry.value, '$.expires_at') AS expires_at,
3248
+ json_extract(entry.value, '$.created_at') AS created_at
3249
+ FROM json_each(${payload}) AS entry
3250
+ `;
3251
+ }
3252
+
3253
+ private batchRefsMatch(
3254
+ left: BatchSourceExpectedColumn,
3255
+ right: BatchInputExpectedColumn,
3256
+ ): RawBuilder<boolean> {
3257
+ const leftRef = sql.ref(left);
3258
+ const rightRef = sql.ref(right);
3259
+ return isPostgres(this.db)
3260
+ ? sql<boolean>`${leftRef} IS NOT DISTINCT FROM ${rightRef}`
3261
+ : sql<boolean>`${leftRef} IS ${rightRef}`;
3262
+ }
3263
+
2207
3264
  private async lockCanonicalSourceCollection(
2208
3265
  db: DatabaseExecutor,
2209
3266
  source: MediaUsageSourceInput,