emdash 0.34.0 → 0.36.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 (800) hide show
  1. package/dist/activation-K0kGBWuJ.mjs +382 -0
  2. package/dist/activation-K0kGBWuJ.mjs.map +1 -0
  3. package/dist/{adapters-34EN2eXO.d.mts → adapters-CzL3H752.d.mts} +10 -3
  4. package/dist/adapters-CzL3H752.d.mts.map +1 -0
  5. package/dist/{allowed-origins-DW7sYyqg.mjs → allowed-origins-maylGaHv.mjs} +2 -2
  6. package/dist/{allowed-origins-DW7sYyqg.mjs.map → allowed-origins-maylGaHv.mjs.map} +1 -1
  7. package/dist/api/route-utils.d.mts +3 -3
  8. package/dist/api/route-utils.mjs +22 -20
  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-B0w3uBiG.mjs +2269 -0
  13. package/dist/api-B0w3uBiG.mjs.map +1 -0
  14. package/dist/{api-tokens-CFXu8F_2.mjs → api-tokens-9YrrCoE6.mjs} +2 -2
  15. package/dist/{api-tokens-CFXu8F_2.mjs.map → api-tokens-9YrrCoE6.mjs.map} +1 -1
  16. package/dist/{apply-D5zfeD09.mjs → apply-B_VMpJ2P.mjs} +34 -35
  17. package/dist/{apply-D5zfeD09.mjs.map → apply-B_VMpJ2P.mjs.map} +1 -1
  18. package/dist/astro/index.d.mts +12 -12
  19. package/dist/astro/index.d.mts.map +1 -1
  20. package/dist/astro/index.mjs +92 -25
  21. package/dist/astro/index.mjs.map +1 -1
  22. package/dist/astro/middleware/auth.d.mts +10 -10
  23. package/dist/astro/middleware/auth.d.mts.map +1 -1
  24. package/dist/astro/middleware/auth.mjs +10 -7
  25. package/dist/astro/middleware/auth.mjs.map +1 -1
  26. package/dist/astro/middleware/media-usage-write-fence.mjs +2 -2
  27. package/dist/astro/middleware/redirect.mjs +4 -4
  28. package/dist/astro/middleware/request-context.mjs +3 -3
  29. package/dist/astro/middleware/setup.mjs +1 -1
  30. package/dist/astro/middleware.d.mts +11 -12
  31. package/dist/astro/middleware.d.mts.map +1 -1
  32. package/dist/astro/middleware.mjs +255 -1323
  33. package/dist/astro/middleware.mjs.map +1 -1
  34. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +6 -6
  35. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +6 -6
  36. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +3 -3
  37. package/dist/astro/routes/api/admin/api-tokens/index.mjs +4 -4
  38. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +4 -4
  39. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +9 -9
  40. package/dist/astro/routes/api/admin/byline-fields/index.mjs +9 -9
  41. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +9 -9
  42. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +15 -15
  43. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +15 -15
  44. package/dist/astro/routes/api/admin/bylines/index.mjs +15 -15
  45. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +12 -12
  46. package/dist/astro/routes/api/admin/comments/_id_.mjs +4 -4
  47. package/dist/astro/routes/api/admin/comments/bulk.mjs +9 -9
  48. package/dist/astro/routes/api/admin/comments/counts.mjs +4 -4
  49. package/dist/astro/routes/api/admin/comments/index.mjs +9 -9
  50. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +4 -4
  51. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
  52. package/dist/astro/routes/api/admin/media-usage/activation.d.mts +9 -0
  53. package/dist/astro/routes/api/admin/media-usage/activation.d.mts.map +1 -0
  54. package/dist/astro/routes/api/admin/media-usage/activation.mjs +46 -0
  55. package/dist/astro/routes/api/admin/media-usage/activation.mjs.map +1 -0
  56. package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs +14 -13
  57. package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs.map +1 -1
  58. package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs +14 -13
  59. package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs.map +1 -1
  60. package/dist/astro/routes/api/admin/media-usage/progress.d.mts +9 -0
  61. package/dist/astro/routes/api/admin/media-usage/progress.d.mts.map +1 -0
  62. package/dist/astro/routes/api/admin/media-usage/progress.mjs +44 -0
  63. package/dist/astro/routes/api/admin/media-usage/progress.mjs.map +1 -0
  64. package/dist/astro/routes/api/admin/media-usage/repair.mjs +16 -11
  65. package/dist/astro/routes/api/admin/media-usage/repair.mjs.map +1 -1
  66. package/dist/astro/routes/api/admin/media-usage/work/index.mjs +14 -13
  67. package/dist/astro/routes/api/admin/media-usage/work/index.mjs.map +1 -1
  68. package/dist/astro/routes/api/admin/media-usage/work/retry.mjs +14 -13
  69. package/dist/astro/routes/api/admin/media-usage/work/retry.mjs.map +1 -1
  70. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +3 -3
  71. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +3 -3
  72. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +54 -48
  73. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
  74. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +54 -48
  75. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
  76. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +52 -46
  77. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
  78. package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +4 -4
  79. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +52 -46
  80. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs.map +1 -1
  81. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +53 -47
  82. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
  83. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +53 -47
  84. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
  85. package/dist/astro/routes/api/admin/plugins/index.mjs +52 -46
  86. package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
  87. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +2 -2
  88. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +52 -46
  89. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
  90. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +53 -47
  91. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
  92. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +52 -46
  93. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
  94. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +53 -47
  95. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
  96. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +54 -48
  97. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
  98. package/dist/astro/routes/api/admin/plugins/registry/artifact.d.mts.map +1 -1
  99. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +172 -133
  100. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
  101. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +54 -48
  102. package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
  103. package/dist/astro/routes/api/admin/plugins/updates.mjs +52 -46
  104. package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
  105. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +52 -46
  106. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
  107. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +2 -2
  108. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +52 -46
  109. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
  110. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +2 -2
  111. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +1 -1
  112. package/dist/astro/routes/api/admin/users/_id_/index.mjs +7 -7
  113. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +3 -3
  114. package/dist/astro/routes/api/admin/users/index.mjs +6 -6
  115. package/dist/astro/routes/api/auth/dev-bypass.d.mts.map +1 -1
  116. package/dist/astro/routes/api/auth/dev-bypass.mjs +3 -7
  117. package/dist/astro/routes/api/auth/dev-bypass.mjs.map +1 -1
  118. package/dist/astro/routes/api/auth/invite/accept.mjs +1 -1
  119. package/dist/astro/routes/api/auth/invite/complete.mjs +11 -11
  120. package/dist/astro/routes/api/auth/invite/index.mjs +8 -8
  121. package/dist/astro/routes/api/auth/invite/register-options.mjs +10 -10
  122. package/dist/astro/routes/api/auth/logout.mjs +2 -2
  123. package/dist/astro/routes/api/auth/magic-link/send.mjs +10 -10
  124. package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
  125. package/dist/astro/routes/api/auth/me.d.mts.map +1 -1
  126. package/dist/astro/routes/api/auth/me.mjs +19 -9
  127. package/dist/astro/routes/api/auth/me.mjs.map +1 -1
  128. package/dist/astro/routes/api/auth/mode.mjs +2 -2
  129. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +4 -4
  130. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
  131. package/dist/astro/routes/api/auth/passkey/_id_.mjs +6 -6
  132. package/dist/astro/routes/api/auth/passkey/index.mjs +1 -1
  133. package/dist/astro/routes/api/auth/passkey/options.mjs +12 -12
  134. package/dist/astro/routes/api/auth/passkey/register/options.mjs +10 -10
  135. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +11 -11
  136. package/dist/astro/routes/api/auth/passkey/verify.mjs +11 -11
  137. package/dist/astro/routes/api/auth/signup/complete.mjs +11 -11
  138. package/dist/astro/routes/api/auth/signup/request.mjs +10 -10
  139. package/dist/astro/routes/api/auth/signup/verify.mjs +1 -1
  140. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +13 -13
  141. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +12 -12
  142. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +2 -2
  143. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +3 -3
  144. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
  145. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +2 -2
  146. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +11 -11
  147. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +8 -8
  148. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +16 -14
  149. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs.map +1 -1
  150. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs +16 -14
  151. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs.map +1 -1
  152. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
  153. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +2 -2
  154. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +8 -8
  155. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +13 -13
  156. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +2 -2
  157. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +3 -3
  158. package/dist/astro/routes/api/content/_collection_/_id_.mjs +8 -8
  159. package/dist/astro/routes/api/content/_collection_/authors.mjs +2 -2
  160. package/dist/astro/routes/api/content/_collection_/index.mjs +8 -8
  161. package/dist/astro/routes/api/content/_collection_/trash.mjs +7 -7
  162. package/dist/astro/routes/api/dashboard.mjs +7 -7
  163. package/dist/astro/routes/api/dev/emails.mjs +2 -2
  164. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  165. package/dist/astro/routes/api/import/probe.mjs +16 -16
  166. package/dist/astro/routes/api/import/wordpress/analyze.mjs +2 -2
  167. package/dist/astro/routes/api/import/wordpress/execute.d.mts +11 -11
  168. package/dist/astro/routes/api/import/wordpress/execute.mjs +12 -12
  169. package/dist/astro/routes/api/import/wordpress/media.d.mts +10 -10
  170. package/dist/astro/routes/api/import/wordpress/media.mjs +11 -11
  171. package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -9
  172. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +11 -11
  173. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +12 -10
  174. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs.map +1 -1
  175. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  176. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +16 -16
  177. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +11 -11
  178. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +26 -26
  179. package/dist/astro/routes/api/manifest.mjs +4 -4
  180. package/dist/astro/routes/api/mcp.mjs +42 -42
  181. package/dist/astro/routes/api/media/_id_/confirm.mjs +11 -11
  182. package/dist/astro/routes/api/media/_id_/upload.mjs +6 -6
  183. package/dist/astro/routes/api/media/_id_/usage.mjs +18 -13
  184. package/dist/astro/routes/api/media/_id_/usage.mjs.map +1 -1
  185. package/dist/astro/routes/api/media/_id_.d.mts.map +1 -1
  186. package/dist/astro/routes/api/media/_id_.mjs +21 -13
  187. package/dist/astro/routes/api/media/_id_.mjs.map +1 -1
  188. package/dist/astro/routes/api/media/file/_...key_.mjs +1 -1
  189. package/dist/astro/routes/api/media/folders/_id_.d.mts +10 -0
  190. package/dist/astro/routes/api/media/folders/_id_.d.mts.map +1 -0
  191. package/dist/astro/routes/api/media/folders/_id_.mjs +52 -0
  192. package/dist/astro/routes/api/media/folders/_id_.mjs.map +1 -0
  193. package/dist/astro/routes/api/media/folders/index.d.mts +9 -0
  194. package/dist/astro/routes/api/media/folders/index.d.mts.map +1 -0
  195. package/dist/astro/routes/api/media/folders/index.mjs +41 -0
  196. package/dist/astro/routes/api/media/folders/index.mjs.map +1 -0
  197. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +2 -2
  198. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +2 -2
  199. package/dist/astro/routes/api/media/providers/index.mjs +2 -2
  200. package/dist/astro/routes/api/media/upload-url.mjs +9 -9
  201. package/dist/astro/routes/api/media.d.mts.map +1 -1
  202. package/dist/astro/routes/api/media.mjs +28 -19
  203. package/dist/astro/routes/api/media.mjs.map +1 -1
  204. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +8 -8
  205. package/dist/astro/routes/api/menus/_name_/items.mjs +8 -8
  206. package/dist/astro/routes/api/menus/_name_/reorder.mjs +8 -8
  207. package/dist/astro/routes/api/menus/_name_/translations.mjs +8 -8
  208. package/dist/astro/routes/api/menus/_name_.mjs +8 -8
  209. package/dist/astro/routes/api/menus/index.mjs +8 -8
  210. package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
  211. package/dist/astro/routes/api/oauth/device/authorize.mjs +5 -5
  212. package/dist/astro/routes/api/oauth/device/code.mjs +8 -8
  213. package/dist/astro/routes/api/oauth/device/token.mjs +7 -7
  214. package/dist/astro/routes/api/oauth/register.mjs +2 -2
  215. package/dist/astro/routes/api/oauth/token/refresh.mjs +5 -5
  216. package/dist/astro/routes/api/oauth/token/revoke.mjs +5 -5
  217. package/dist/astro/routes/api/oauth/token.mjs +5 -5
  218. package/dist/astro/routes/api/openapi.json.mjs +153 -6
  219. package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
  220. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +4 -4
  221. package/dist/astro/routes/api/redirects/404s/index.mjs +10 -10
  222. package/dist/astro/routes/api/redirects/404s/summary.mjs +10 -10
  223. package/dist/astro/routes/api/redirects/_id_.mjs +11 -11
  224. package/dist/astro/routes/api/redirects/index.mjs +11 -11
  225. package/dist/astro/routes/api/relations/_id_/index.mjs +16 -14
  226. package/dist/astro/routes/api/relations/_id_/index.mjs.map +1 -1
  227. package/dist/astro/routes/api/relations/_id_/translations.mjs +11 -9
  228. package/dist/astro/routes/api/relations/_id_/translations.mjs.map +1 -1
  229. package/dist/astro/routes/api/relations/index.mjs +16 -14
  230. package/dist/astro/routes/api/relations/index.mjs.map +1 -1
  231. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +2 -2
  232. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
  233. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +52 -46
  234. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
  235. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +52 -46
  236. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs.map +1 -1
  237. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +52 -46
  238. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
  239. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +52 -46
  240. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
  241. package/dist/astro/routes/api/schema/collections/index.mjs +52 -46
  242. package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
  243. package/dist/astro/routes/api/schema/collections/reorder.mjs +52 -46
  244. package/dist/astro/routes/api/schema/collections/reorder.mjs.map +1 -1
  245. package/dist/astro/routes/api/schema/index.mjs +10 -8
  246. package/dist/astro/routes/api/schema/index.mjs.map +1 -1
  247. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +52 -46
  248. package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
  249. package/dist/astro/routes/api/schema/orphans/index.mjs +52 -46
  250. package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
  251. package/dist/astro/routes/api/search/enable.mjs +10 -10
  252. package/dist/astro/routes/api/search/index.mjs +9 -9
  253. package/dist/astro/routes/api/search/rebuild.mjs +10 -10
  254. package/dist/astro/routes/api/search/stats.mjs +5 -5
  255. package/dist/astro/routes/api/search/suggest.mjs +9 -9
  256. package/dist/astro/routes/api/sections/_slug_.mjs +9 -9
  257. package/dist/astro/routes/api/sections/index.mjs +9 -9
  258. package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +6 -6
  259. package/dist/astro/routes/api/settings/backups/archives/index.mjs +5 -5
  260. package/dist/astro/routes/api/settings/backups/export.mjs +6 -6
  261. package/dist/astro/routes/api/settings/backups/index.mjs +7 -7
  262. package/dist/astro/routes/api/settings/email.mjs +4 -4
  263. package/dist/astro/routes/api/settings.mjs +13 -13
  264. package/dist/astro/routes/api/setup/admin-verify.mjs +12 -12
  265. package/dist/astro/routes/api/setup/admin.mjs +11 -11
  266. package/dist/astro/routes/api/setup/dev-bypass.d.mts.map +1 -1
  267. package/dist/astro/routes/api/setup/dev-bypass.mjs +35 -31
  268. package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
  269. package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
  270. package/dist/astro/routes/api/setup/index.d.mts.map +1 -1
  271. package/dist/astro/routes/api/setup/index.mjs +31 -35
  272. package/dist/astro/routes/api/setup/index.mjs.map +1 -1
  273. package/dist/astro/routes/api/setup/status.mjs +3 -3
  274. package/dist/astro/routes/api/snapshot.mjs +7 -7
  275. package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs +13 -13
  276. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +13 -13
  277. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +13 -13
  278. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +13 -13
  279. package/dist/astro/routes/api/taxonomies/_name_/translations.mjs +13 -13
  280. package/dist/astro/routes/api/taxonomies/_name_.mjs +13 -13
  281. package/dist/astro/routes/api/taxonomies/index.mjs +13 -13
  282. package/dist/astro/routes/api/themes/preview.mjs +5 -5
  283. package/dist/astro/routes/api/typegen.mjs +4 -4
  284. package/dist/astro/routes/api/well-known/auth.mjs +2 -2
  285. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
  286. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
  287. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +7 -7
  288. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +10 -10
  289. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +10 -10
  290. package/dist/astro/routes/api/widget-areas/_name_.mjs +5 -5
  291. package/dist/astro/routes/api/widget-areas/index.mjs +10 -10
  292. package/dist/astro/routes/api/widget-components.mjs +2 -2
  293. package/dist/astro/routes/robots.txt.mjs +6 -6
  294. package/dist/astro/routes/sitemap-_collection_.xml.mjs +9 -9
  295. package/dist/astro/routes/sitemap.xml.mjs +7 -7
  296. package/dist/astro/types.d.mts +19 -14
  297. package/dist/astro/types.d.mts.map +1 -1
  298. package/dist/auth/providers/github.d.mts +1 -1
  299. package/dist/auth/providers/google.d.mts +1 -1
  300. package/dist/{authorize-1iFJzPaZ.mjs → authorize-ByYs-Uxs.mjs} +2 -2
  301. package/dist/{authorize-1iFJzPaZ.mjs.map → authorize-ByYs-Uxs.mjs.map} +1 -1
  302. package/dist/{backup-LHjIAVNP.mjs → backup-ChyHfIoX.mjs} +5 -5
  303. package/dist/{backup-LHjIAVNP.mjs.map → backup-ChyHfIoX.mjs.map} +1 -1
  304. package/dist/{bundle-CBEXrBnb.mjs → bundle-DZFhFurR.mjs} +7 -7
  305. package/dist/{bundle-CBEXrBnb.mjs.map → bundle-DZFhFurR.mjs.map} +1 -1
  306. package/dist/{byline-pEcoDps8.mjs → byline-Cpnwrje1.mjs} +6 -6
  307. package/dist/{byline-pEcoDps8.mjs.map → byline-Cpnwrje1.mjs.map} +1 -1
  308. package/dist/{byline-fields-C5S8wygO.mjs → byline-fields-CAyA_Umd.mjs} +2 -2
  309. package/dist/{byline-fields-C5S8wygO.mjs.map → byline-fields-CAyA_Umd.mjs.map} +1 -1
  310. package/dist/{byline-registry-DDun8sxh.mjs → byline-registry-Bpu9-MVs.mjs} +2 -2
  311. package/dist/{byline-registry-DDun8sxh.mjs.map → byline-registry-Bpu9-MVs.mjs.map} +1 -1
  312. package/dist/{bylines-BWemdX2k.mjs → bylines-BSo9B9RX.mjs} +7 -7
  313. package/dist/{bylines-BWemdX2k.mjs.map → bylines-BSo9B9RX.mjs.map} +1 -1
  314. package/dist/{bylines-DJfSfXCL.mjs → bylines-CHjvehVX.mjs} +3 -3
  315. package/dist/{bylines-DJfSfXCL.mjs.map → bylines-CHjvehVX.mjs.map} +1 -1
  316. package/dist/{cache-CBJaDy_E.mjs → cache-5VirJ2Cn.mjs} +4 -4
  317. package/dist/{cache-CBJaDy_E.mjs.map → cache-5VirJ2Cn.mjs.map} +1 -1
  318. package/dist/capture-triggers-CNW1q3HK.mjs +362 -0
  319. package/dist/capture-triggers-CNW1q3HK.mjs.map +1 -0
  320. package/dist/{challenge-store-CZyFVaad.mjs → challenge-store-D5pSY2S1.mjs} +1 -1
  321. package/dist/{challenge-store-CZyFVaad.mjs.map → challenge-store-D5pSY2S1.mjs.map} +1 -1
  322. package/dist/{chunks-DfPax1I0.mjs → chunks-bNiX6_DR.mjs} +2 -2
  323. package/dist/{chunks-DfPax1I0.mjs.map → chunks-bNiX6_DR.mjs.map} +1 -1
  324. package/dist/cli/index.mjs +568 -84
  325. package/dist/cli/index.mjs.map +1 -1
  326. package/dist/client/cf-access.d.mts +1 -1
  327. package/dist/client/index.d.mts +74 -4
  328. package/dist/client/index.d.mts.map +1 -1
  329. package/dist/client/index.mjs +54 -2
  330. package/dist/client/index.mjs.map +1 -1
  331. package/dist/{collection-deletion-C8BugSGz.mjs → collection-deletion-m_oi2c0N.mjs} +13 -356
  332. package/dist/collection-deletion-m_oi2c0N.mjs.map +1 -0
  333. package/dist/{comment-Dxlep6cE.mjs → comment-DhvLEdhQ.mjs} +1 -1
  334. package/dist/{comment-Dxlep6cE.mjs.map → comment-DhvLEdhQ.mjs.map} +1 -1
  335. package/dist/{comment-reaction-D6qfHKBc.mjs → comment-reaction-CRuItEjt.mjs} +2 -2
  336. package/dist/{comment-reaction-D6qfHKBc.mjs.map → comment-reaction-CRuItEjt.mjs.map} +1 -1
  337. package/dist/{comments-CTOuhTSX.mjs → comments-CLj4bi8I.mjs} +2 -2
  338. package/dist/{comments-CTOuhTSX.mjs.map → comments-CLj4bi8I.mjs.map} +1 -1
  339. package/dist/{components-CQy3lh9x.mjs → components-v4U4DT2y.mjs} +1 -1
  340. package/dist/{components-CQy3lh9x.mjs.map → components-v4U4DT2y.mjs.map} +1 -1
  341. package/dist/config-DrfghTPe.d.mts +34 -0
  342. package/dist/config-DrfghTPe.d.mts.map +1 -0
  343. package/dist/{content-BNfwh7xX.mjs → content-CP-SeXNa.mjs} +5 -4
  344. package/dist/content-CP-SeXNa.mjs.map +1 -0
  345. package/dist/{content-list-query-DhQ84eVV.d.mts → content-list-query-BhejKVqb.d.mts} +1 -1
  346. package/dist/{content-list-query-DhQ84eVV.d.mts.map → content-list-query-BhejKVqb.d.mts.map} +1 -1
  347. package/dist/{content-refresh-DNERP4-f.mjs → content-refresh-DLOfIhYc.mjs} +1094 -93
  348. package/dist/content-refresh-DLOfIhYc.mjs.map +1 -0
  349. package/dist/{media-usage-De3qvwjc.mjs → content-repair-BCxzy0r4.mjs} +5 -196
  350. package/dist/content-repair-BCxzy0r4.mjs.map +1 -0
  351. package/dist/{context-DRgOvSnW.mjs → context-BR5hRWiK.mjs} +13 -13
  352. package/dist/{context-DRgOvSnW.mjs.map → context-BR5hRWiK.mjs.map} +1 -1
  353. package/dist/{cron-Y9aLtgrA.mjs → cron-CWfWrWKG.mjs} +1 -1
  354. package/dist/{cron-Y9aLtgrA.mjs.map → cron-CWfWrWKG.mjs.map} +1 -1
  355. package/dist/{dashboard-CB8J89rJ.mjs → dashboard-CkIV2rnQ.mjs} +5 -5
  356. package/dist/{dashboard-CB8J89rJ.mjs.map → dashboard-CkIV2rnQ.mjs.map} +1 -1
  357. package/dist/db/index.d.mts +4 -4
  358. package/dist/db/index.mjs +32 -9
  359. package/dist/db/index.mjs.map +1 -1
  360. package/dist/db/libsql-migrations.d.mts +11 -0
  361. package/dist/db/libsql-migrations.d.mts.map +1 -0
  362. package/dist/db/libsql-migrations.mjs +57 -0
  363. package/dist/db/libsql-migrations.mjs.map +1 -0
  364. package/dist/db/libsql.d.mts +1 -1
  365. package/dist/db/postgres-migrations.d.mts +10 -0
  366. package/dist/db/postgres-migrations.d.mts.map +1 -0
  367. package/dist/db/postgres-migrations.mjs +47 -0
  368. package/dist/db/postgres-migrations.mjs.map +1 -0
  369. package/dist/db/postgres.d.mts +1 -1
  370. package/dist/db/sqlite-migrations.d.mts +10 -0
  371. package/dist/db/sqlite-migrations.d.mts.map +1 -0
  372. package/dist/db/sqlite-migrations.mjs +28 -0
  373. package/dist/db/sqlite-migrations.mjs.map +1 -0
  374. package/dist/db/sqlite.d.mts +1 -1
  375. package/dist/db/sqlite.mjs +3 -3
  376. package/dist/db/sqlite.mjs.map +1 -1
  377. package/dist/{default-DdDl3fxv.mjs → default-YMEfcQUx.mjs} +1 -1
  378. package/dist/{default-DdDl3fxv.mjs.map → default-YMEfcQUx.mjs.map} +1 -1
  379. package/dist/{deferred-tasks-BMsa9F64.d.mts → deferred-tasks-DfqxH5Xf.d.mts} +1 -1
  380. package/dist/{deferred-tasks-BMsa9F64.d.mts.map → deferred-tasks-DfqxH5Xf.d.mts.map} +1 -1
  381. package/dist/{device-flow-CaioYGK3.mjs → device-flow-CojjSBwb.mjs} +5 -5
  382. package/dist/{device-flow-CaioYGK3.mjs.map → device-flow-CojjSBwb.mjs.map} +1 -1
  383. package/dist/direct-executor-BWeWiRYB.mjs +85 -0
  384. package/dist/direct-executor-BWeWiRYB.mjs.map +1 -0
  385. package/dist/{dist-D_6CKR8z.mjs → dist-89xCrGqi.mjs} +1 -1
  386. package/dist/{dist-D_6CKR8z.mjs.map → dist-89xCrGqi.mjs.map} +1 -1
  387. package/dist/{email-console-BHNjIwiB.mjs → email-console-CEJgR_LB.mjs} +1 -1
  388. package/dist/{email-console-BHNjIwiB.mjs.map → email-console-CEJgR_LB.mjs.map} +1 -1
  389. package/dist/{enrich-_ySMEvEE.mjs → enrich-BX0IDp4z.mjs} +3 -3
  390. package/dist/{enrich-_ySMEvEE.mjs.map → enrich-BX0IDp4z.mjs.map} +1 -1
  391. package/dist/{error-DupyJZE2.mjs → error-D9w94fjV.mjs} +2 -2
  392. package/dist/{error-DupyJZE2.mjs.map → error-D9w94fjV.mjs.map} +1 -1
  393. package/dist/{errors-CLUPm7aD.mjs → errors-n2j49uNf.mjs} +22 -2
  394. package/dist/errors-n2j49uNf.mjs.map +1 -0
  395. package/dist/{escape-C_aLKP3H.mjs → escape-DN4Qmjsl.mjs} +1 -1
  396. package/dist/{escape-C_aLKP3H.mjs.map → escape-DN4Qmjsl.mjs.map} +1 -1
  397. package/dist/{field-defs-cache-hg6QkjV9.mjs → field-defs-cache-Bh4q6oar.mjs} +5 -5
  398. package/dist/{field-defs-cache-hg6QkjV9.mjs.map → field-defs-cache-Bh4q6oar.mjs.map} +1 -1
  399. package/dist/focal-point-BeWFo6Xx.mjs +23 -0
  400. package/dist/focal-point-BeWFo6Xx.mjs.map +1 -0
  401. package/dist/{fts-manager-CZ-5dVwJ.mjs → fts-manager-BhOdW0bi.mjs} +3 -3
  402. package/dist/{fts-manager-CZ-5dVwJ.mjs.map → fts-manager-BhOdW0bi.mjs.map} +1 -1
  403. package/dist/{hash-Cg0TXJPV.mjs → hash-CTcngWvj.mjs} +1 -1
  404. package/dist/{hash-Cg0TXJPV.mjs.map → hash-CTcngWvj.mjs.map} +1 -1
  405. package/dist/{hreflang-D3Uehfv2.d.mts → hreflang-7pTHsLZJ.d.mts} +2 -2
  406. package/dist/{hreflang-D3Uehfv2.d.mts.map → hreflang-7pTHsLZJ.d.mts.map} +1 -1
  407. package/dist/{hreflang-DNswoYsi.mjs → hreflang-Huc6O5wa.mjs} +7 -7
  408. package/dist/{hreflang-DNswoYsi.mjs.map → hreflang-Huc6O5wa.mjs.map} +1 -1
  409. package/dist/identity-DXmb4aVp.mjs +35 -0
  410. package/dist/identity-DXmb4aVp.mjs.map +1 -0
  411. package/dist/{import-B41u8Rot.mjs → import-C4_O3KXU.mjs} +5 -5
  412. package/dist/{import-B41u8Rot.mjs.map → import-C4_O3KXU.mjs.map} +1 -1
  413. package/dist/{index-Cof4-Fab.d.mts → index-6bRee8zb.d.mts} +3 -3
  414. package/dist/{index-Cof4-Fab.d.mts.map → index-6bRee8zb.d.mts.map} +1 -1
  415. package/dist/{index-C3Y-rsyN.d.mts → index-DWbJrsAg.d.mts} +138 -83
  416. package/dist/index-DWbJrsAg.d.mts.map +1 -0
  417. package/dist/index.d.mts +21 -20
  418. package/dist/index.mjs +78 -72
  419. package/dist/{init-lock-6b309ZrF.mjs → init-lock-DJkX6Hto.mjs} +1 -1
  420. package/dist/{init-lock-6b309ZrF.mjs.map → init-lock-DJkX6Hto.mjs.map} +1 -1
  421. package/dist/{load-DmvJrtxS.mjs → load-Bo7fZAbg.mjs} +2 -2
  422. package/dist/{load-DmvJrtxS.mjs.map → load-Bo7fZAbg.mjs.map} +1 -1
  423. package/dist/{loader-DLBqRiGP.mjs → loader-ZsjE4paL.mjs} +2 -2
  424. package/dist/{loader-DLBqRiGP.mjs.map → loader-ZsjE4paL.mjs.map} +1 -1
  425. package/dist/manifest-builder-QXuqvVMP.mjs +189 -0
  426. package/dist/manifest-builder-QXuqvVMP.mjs.map +1 -0
  427. package/dist/{manifest-schema-BXbUAHod.mjs → manifest-schema-CUYPfCEH.mjs} +1 -1
  428. package/dist/{manifest-schema-BXbUAHod.mjs.map → manifest-schema-CUYPfCEH.mjs.map} +1 -1
  429. package/dist/media/image-endpoint.mjs +1 -1
  430. package/dist/media/index.d.mts +1 -1
  431. package/dist/media/index.mjs +13 -3
  432. package/dist/media/index.mjs.map +1 -1
  433. package/dist/media/local-runtime.d.mts +14 -12
  434. package/dist/media/local-runtime.d.mts.map +1 -1
  435. package/dist/media/local-runtime.mjs +11 -5
  436. package/dist/media/local-runtime.mjs.map +1 -1
  437. package/dist/{media-BWWWb8kg.mjs → media-BjwNuLEu.mjs} +100 -6
  438. package/dist/media-BjwNuLEu.mjs.map +1 -0
  439. package/dist/{media-CzhAn2--.mjs → media-D6SeAfBl.mjs} +41 -12
  440. package/dist/media-D6SeAfBl.mjs.map +1 -0
  441. package/dist/{media-allowlist-BD6HhgXA.mjs → media-allowlist-D35v7bmL.mjs} +3 -2
  442. package/dist/media-allowlist-D35v7bmL.mjs.map +1 -0
  443. package/dist/media-folders-D-0CmRZj.mjs +210 -0
  444. package/dist/media-folders-D-0CmRZj.mjs.map +1 -0
  445. package/dist/{media-upload-9yas9zGH.mjs → media-upload-C7bLXGcI.mjs} +9 -9
  446. package/dist/{media-upload-9yas9zGH.mjs.map → media-upload-C7bLXGcI.mjs.map} +1 -1
  447. package/dist/{media-url-BvoX88FA.mjs → media-url-DH7giMx_.mjs} +1 -1
  448. package/dist/{media-url-BvoX88FA.mjs.map → media-url-DH7giMx_.mjs.map} +1 -1
  449. package/dist/media-usage-BTRJvS6V.mjs +1833 -0
  450. package/dist/media-usage-BTRJvS6V.mjs.map +1 -0
  451. package/dist/media-usage-activation-Dx0kPV6b.mjs +78 -0
  452. package/dist/media-usage-activation-Dx0kPV6b.mjs.map +1 -0
  453. package/dist/{media-usage-work-DrCbqBcl.mjs → media-usage-work-C-fhM30c.mjs} +221 -59
  454. package/dist/media-usage-work-C-fhM30c.mjs.map +1 -0
  455. package/dist/{media-usage-work-B9Bksh_b.mjs → media-usage-work-DWb2XPMI.mjs} +4 -4
  456. package/dist/{media-usage-work-B9Bksh_b.mjs.map → media-usage-work-DWb2XPMI.mjs.map} +1 -1
  457. package/dist/{media-usage-write-fence-aHEgN1zB.mjs → media-usage-write-fence-BwoJVRDC.mjs} +2 -2
  458. package/dist/{media-usage-write-fence-aHEgN1zB.mjs.map → media-usage-write-fence-BwoJVRDC.mjs.map} +1 -1
  459. package/dist/{menus-fxvEDYE6.mjs → menus-CD7EFLwz.mjs} +3 -3
  460. package/dist/{menus-fxvEDYE6.mjs.map → menus-CD7EFLwz.mjs.map} +1 -1
  461. package/dist/{menus-CC-yn1F9.mjs → menus-CNg0QlDt.mjs} +42 -26
  462. package/dist/menus-CNg0QlDt.mjs.map +1 -0
  463. package/dist/migrations/index.d.mts +22 -0
  464. package/dist/migrations/index.d.mts.map +1 -0
  465. package/dist/migrations/index.mjs +7 -0
  466. package/dist/{mime-DfIVjNkr.mjs → mime-CUIG6x84.mjs} +1 -1
  467. package/dist/mime-CUIG6x84.mjs.map +1 -0
  468. package/dist/{mode-Bpe-tNNy.mjs → mode-DaUM_MB2.mjs} +1 -1
  469. package/dist/{mode-Bpe-tNNy.mjs.map → mode-DaUM_MB2.mjs.map} +1 -1
  470. package/dist/node-sqlite-compat-Udb4Y-rs.mjs +46 -0
  471. package/dist/node-sqlite-compat-Udb4Y-rs.mjs.map +1 -0
  472. package/dist/{normalize-Bc-s_r-a.mjs → normalize-CSxdKBVa.mjs} +22 -1
  473. package/dist/normalize-CSxdKBVa.mjs.map +1 -0
  474. package/dist/{oauth-authorization-BXO1MKej.mjs → oauth-authorization-Dpsb-fMJ.mjs} +5 -5
  475. package/dist/{oauth-authorization-BXO1MKej.mjs.map → oauth-authorization-Dpsb-fMJ.mjs.map} +1 -1
  476. package/dist/{oauth-clients-C_DNqtRu.mjs → oauth-clients-Bxnoe_67.mjs} +1 -1
  477. package/dist/{oauth-clients-C_DNqtRu.mjs.map → oauth-clients-Bxnoe_67.mjs.map} +1 -1
  478. package/dist/{oauth-state-store-DwAxMtk4.mjs → oauth-state-store-0C4s0K6w.mjs} +1 -1
  479. package/dist/{oauth-state-store-DwAxMtk4.mjs.map → oauth-state-store-0C4s0K6w.mjs.map} +1 -1
  480. package/dist/{oauth-user-lookup-BxfZizdk.mjs → oauth-user-lookup-wlGYSeqX.mjs} +1 -1
  481. package/dist/{oauth-user-lookup-BxfZizdk.mjs.map → oauth-user-lookup-wlGYSeqX.mjs.map} +1 -1
  482. package/dist/object-cache/memory.d.mts +1 -1
  483. package/dist/{options-CBXIHUoT.mjs → options-BHPeIbBd.mjs} +1 -1
  484. package/dist/{options-CBXIHUoT.mjs.map → options-BHPeIbBd.mjs.map} +1 -1
  485. package/dist/{options-DRe1pIBL.d.mts → options-BhZIeFY1.d.mts} +3 -3
  486. package/dist/{options-DRe1pIBL.d.mts.map → options-BhZIeFY1.d.mts.map} +1 -1
  487. package/dist/page/index.d.mts +2 -2
  488. package/dist/{parse-DFbuc-Fh.mjs → parse-BRJg1kz8.mjs} +2 -2
  489. package/dist/{parse-DFbuc-Fh.mjs.map → parse-BRJg1kz8.mjs.map} +1 -1
  490. package/dist/{passkey-config-_2GOwwK8.mjs → passkey-config-B3P0nHRR.mjs} +1 -1
  491. package/dist/{passkey-config-_2GOwwK8.mjs.map → passkey-config-B3P0nHRR.mjs.map} +1 -1
  492. package/dist/{patterns-CiyXeDgr.mjs → patterns-D9iDO3j8.mjs} +1 -1
  493. package/dist/{patterns-CiyXeDgr.mjs.map → patterns-D9iDO3j8.mjs.map} +1 -1
  494. package/dist/{placeholder-CzW7Lshs.d.mts → placeholder-BR4ZhVJl.d.mts} +7 -1
  495. package/dist/placeholder-BR4ZhVJl.d.mts.map +1 -0
  496. package/dist/plugin-types.d.mts +1 -1
  497. package/dist/plugin-utils.d.mts +10 -10
  498. package/dist/plugins/adapt-sandbox-entry.d.mts +10 -10
  499. package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
  500. package/dist/policy-BVRganJQ.mjs +45 -0
  501. package/dist/policy-BVRganJQ.mjs.map +1 -0
  502. package/dist/{portable-text-DhKbDAGr.mjs → portable-text-C8Tvp4_g.mjs} +1 -1
  503. package/dist/{portable-text-DhKbDAGr.mjs.map → portable-text-C8Tvp4_g.mjs.map} +1 -1
  504. package/dist/{preview-PW-nOnq4.mjs → preview-dn2dUyM0.mjs} +2 -2
  505. package/dist/{preview-PW-nOnq4.mjs.map → preview-dn2dUyM0.mjs.map} +1 -1
  506. package/dist/protocol-CuBEsqgE.d.mts +52 -0
  507. package/dist/protocol-CuBEsqgE.d.mts.map +1 -0
  508. package/dist/{public-url-CWoFhQT8.mjs → public-url-DRz_AinV.mjs} +1 -1
  509. package/dist/{public-url-CWoFhQT8.mjs.map → public-url-DRz_AinV.mjs.map} +1 -1
  510. package/dist/{query-BgjfiLUQ.mjs → query-D18vTdha.mjs} +14 -14
  511. package/dist/{query-BgjfiLUQ.mjs.map → query-D18vTdha.mjs.map} +1 -1
  512. package/dist/{query-vgZNwIhh.mjs → query-tZDEdtCN.mjs} +4 -4
  513. package/dist/{query-vgZNwIhh.mjs.map → query-tZDEdtCN.mjs.map} +1 -1
  514. package/dist/{rate-limit-L5anykgo.mjs → rate-limit-on7KPo-v.mjs} +2 -2
  515. package/dist/{rate-limit-L5anykgo.mjs.map → rate-limit-on7KPo-v.mjs.map} +1 -1
  516. package/dist/{redirect-COsS6SWJ.mjs → redirect-CZkbozu9.mjs} +2 -2
  517. package/dist/{redirect-COsS6SWJ.mjs.map → redirect-CZkbozu9.mjs.map} +1 -1
  518. package/dist/{redirect-KBNE3UEs.mjs → redirect-DrbEmU9a.mjs} +1 -1
  519. package/dist/{redirect-KBNE3UEs.mjs.map → redirect-DrbEmU9a.mjs.map} +1 -1
  520. package/dist/{redirects-CufRQt0Q.mjs → redirects-BahWiu8Z.mjs} +5 -5
  521. package/dist/{redirects-CufRQt0Q.mjs.map → redirects-BahWiu8Z.mjs.map} +1 -1
  522. package/dist/{redirects-cV9g7V-0.mjs → redirects-CRIM55KH.mjs} +6 -6
  523. package/dist/{redirects-cV9g7V-0.mjs.map → redirects-CRIM55KH.mjs.map} +1 -1
  524. package/dist/{api-DdAsirBe.mjs → registry-BiO5WwPq.mjs} +483 -2462
  525. package/dist/registry-BiO5WwPq.mjs.map +1 -0
  526. package/dist/{registry-C581V4TP.mjs → registry-Cx-jEJOS.mjs} +9 -123
  527. package/dist/registry-Cx-jEJOS.mjs.map +1 -0
  528. package/dist/{relation-DtmAQH7s.mjs → relation-A6wLF_DS.mjs} +2 -2
  529. package/dist/{relation-DtmAQH7s.mjs.map → relation-A6wLF_DS.mjs.map} +1 -1
  530. package/dist/{relations-BBApI4e2.mjs → relations-BTPM2saV.mjs} +2 -2
  531. package/dist/{relations-BBApI4e2.mjs.map → relations-BTPM2saV.mjs.map} +1 -1
  532. package/dist/{relations-DItI3MN9.d.mts → relations-C7B46bcU.d.mts} +189 -30
  533. package/dist/relations-C7B46bcU.d.mts.map +1 -0
  534. package/dist/{relations-DICSyfsh.mjs → relations-Cj5hZ6Zg.mjs} +4 -4
  535. package/dist/{relations-DICSyfsh.mjs.map → relations-Cj5hZ6Zg.mjs.map} +1 -1
  536. package/dist/{request-cache-BSUptuJR.mjs → request-cache-DH9zdCOD.mjs} +1 -1
  537. package/dist/{request-cache-BSUptuJR.mjs.map → request-cache-DH9zdCOD.mjs.map} +1 -1
  538. package/dist/request-context.d.mts +1 -1
  539. package/dist/{request-meta-DgZJh3zG.mjs → request-meta-DRp5ENd_.mjs} +2 -2
  540. package/dist/{request-meta-DgZJh3zG.mjs.map → request-meta-DRp5ENd_.mjs.map} +1 -1
  541. package/dist/{resolve-DOhVEvty.mjs → resolve-DxUpTMmE.mjs} +1 -1
  542. package/dist/{resolve-DOhVEvty.mjs.map → resolve-DxUpTMmE.mjs.map} +1 -1
  543. package/dist/{runner-BiDvq5dv.mjs → runner-77GOZCnH.mjs} +443 -289
  544. package/dist/runner-77GOZCnH.mjs.map +1 -0
  545. package/dist/{runner-CgQbELYC.d.mts → runner-zgqA_CqP.d.mts} +16 -3
  546. package/dist/runner-zgqA_CqP.d.mts.map +1 -0
  547. package/dist/runtime.d.mts +11 -11
  548. package/dist/runtime.mjs +1 -1
  549. package/dist/{schema-BFyTg38T.mjs → schema-BY-oVntF.mjs} +3 -3
  550. package/dist/{schema-BFyTg38T.mjs.map → schema-BY-oVntF.mjs.map} +1 -1
  551. package/dist/{schema-CeQSNwB3.mjs → schema-Cw9kVK0I.mjs} +35 -5
  552. package/dist/{schema-CeQSNwB3.mjs.map → schema-Cw9kVK0I.mjs.map} +1 -1
  553. package/dist/{scopes-BVeuhGBE.mjs → scopes-vMR1uIbt.mjs} +3 -3
  554. package/dist/{scopes-BVeuhGBE.mjs.map → scopes-vMR1uIbt.mjs.map} +1 -1
  555. package/dist/{search-BpuPKZb0.mjs → search-CfT3KxO5.mjs} +4 -4
  556. package/dist/{search-BpuPKZb0.mjs.map → search-CfT3KxO5.mjs.map} +1 -1
  557. package/dist/{secrets-dtYs2hDo.mjs → secrets-BH2Qow1-.mjs} +3 -3
  558. package/dist/{secrets-dtYs2hDo.mjs.map → secrets-BH2Qow1-.mjs.map} +1 -1
  559. package/dist/{sections-COctk6hf.mjs → sections-DTfjyf1z.mjs} +2 -2
  560. package/dist/{sections-COctk6hf.mjs.map → sections-DTfjyf1z.mjs.map} +1 -1
  561. package/dist/seed/index.d.mts +2 -2
  562. package/dist/seed/index.mjs +24 -22
  563. package/dist/seo/index.d.mts +3 -3
  564. package/dist/seo/index.mjs +16 -14
  565. package/dist/seo/index.mjs.map +1 -1
  566. package/dist/{seo-AFpVvTqr.mjs → seo-BSiTIXzd.mjs} +1 -1
  567. package/dist/{seo-AFpVvTqr.mjs.map → seo-BSiTIXzd.mjs.map} +1 -1
  568. package/dist/{seo-CvDxfvFb.mjs → seo-Cm-dtvgk.mjs} +2 -2
  569. package/dist/{seo-CvDxfvFb.mjs.map → seo-Cm-dtvgk.mjs.map} +1 -1
  570. package/dist/{service-xMiMSTsR.mjs → service-DYsDHEKe.mjs} +3 -3
  571. package/dist/{service-xMiMSTsR.mjs.map → service-DYsDHEKe.mjs.map} +1 -1
  572. package/dist/{session-user-BzVemLkw.mjs → session-user-CNAfvsdw.mjs} +1 -1
  573. package/dist/{session-user-BzVemLkw.mjs.map → session-user-CNAfvsdw.mjs.map} +1 -1
  574. package/dist/{settings-B372MV0p.mjs → settings-BgN-0DT-.mjs} +6 -6
  575. package/dist/{settings-B372MV0p.mjs.map → settings-BgN-0DT-.mjs.map} +1 -1
  576. package/dist/{settings-BE9aqn_9.mjs → settings-DjtkUo__.mjs} +3 -3
  577. package/dist/{settings-BE9aqn_9.mjs.map → settings-DjtkUo__.mjs.map} +1 -1
  578. package/dist/{setup-complete-DF7TAfD5.mjs → setup-complete-CcpXaPU7.mjs} +2 -2
  579. package/dist/{setup-complete-DF7TAfD5.mjs.map → setup-complete-CcpXaPU7.mjs.map} +1 -1
  580. package/dist/{setup-nonce-Do-U_cg9.mjs → setup-nonce-Reyw30jg.mjs} +1 -1
  581. package/dist/{setup-nonce-Do-U_cg9.mjs.map → setup-nonce-Reyw30jg.mjs.map} +1 -1
  582. package/dist/{single-flight-cache-B0eLKS6K.mjs → single-flight-cache-BPuGPdcN.mjs} +2 -2
  583. package/dist/{single-flight-cache-B0eLKS6K.mjs.map → single-flight-cache-BPuGPdcN.mjs.map} +1 -1
  584. package/dist/{site-url-Bowsi9T-.mjs → site-url-Cs_V9n3V.mjs} +3 -3
  585. package/dist/{site-url-Bowsi9T-.mjs.map → site-url-Cs_V9n3V.mjs.map} +1 -1
  586. package/dist/{snapshot-B5h6aPMh.mjs → snapshot-DSB8P7Lm.mjs} +1 -1
  587. package/dist/{snapshot-B5h6aPMh.mjs.map → snapshot-DSB8P7Lm.mjs.map} +1 -1
  588. package/dist/{ssrf-Cpvg_n3A.mjs → ssrf-DqebVE34.mjs} +21 -5
  589. package/dist/{ssrf-Cpvg_n3A.mjs.map → ssrf-DqebVE34.mjs.map} +1 -1
  590. package/dist/{state-DYwPue40.mjs → state-BJfU9Uzw.mjs} +12 -4
  591. package/dist/state-BJfU9Uzw.mjs.map +1 -0
  592. package/dist/{status-CWVYpbD8.mjs → status-DDu8HIQl.mjs} +1 -1
  593. package/dist/{status-CWVYpbD8.mjs.map → status-DDu8HIQl.mjs.map} +1 -1
  594. package/dist/storage/local.d.mts +1 -1
  595. package/dist/storage/local.mjs +3 -3
  596. package/dist/storage/s3.d.mts +1 -1
  597. package/dist/storage/s3.mjs +1 -1
  598. package/dist/target-D0o26Azo.mjs +27 -0
  599. package/dist/target-D0o26Azo.mjs.map +1 -0
  600. package/dist/{taxonomies-Dk3MAklL.mjs → taxonomies-CPGxo-bn.mjs} +5 -5
  601. package/dist/{taxonomies-Dk3MAklL.mjs.map → taxonomies-CPGxo-bn.mjs.map} +1 -1
  602. package/dist/{taxonomies-B0o9ExNV.mjs → taxonomies-kmyudNGP.mjs} +7 -7
  603. package/dist/{taxonomies-B0o9ExNV.mjs.map → taxonomies-kmyudNGP.mjs.map} +1 -1
  604. package/dist/{taxonomy-CPeM8d2o.mjs → taxonomy-8X09dLN5.mjs} +4 -4
  605. package/dist/{taxonomy-CPeM8d2o.mjs.map → taxonomy-8X09dLN5.mjs.map} +1 -1
  606. package/dist/{tokens-C39klX8R.mjs → tokens-gTS-IVUp.mjs} +1 -1
  607. package/dist/{tokens-C39klX8R.mjs.map → tokens-gTS-IVUp.mjs.map} +1 -1
  608. package/dist/{transaction-NQj4VJ7Z.mjs → transaction-0KOpOyMu.mjs} +2 -1
  609. package/dist/{transaction-NQj4VJ7Z.mjs.map → transaction-0KOpOyMu.mjs.map} +1 -1
  610. package/dist/{transport-B_rfeU-a.d.mts → transport-_6R1ub7t.d.mts} +1 -1
  611. package/dist/{transport-B_rfeU-a.d.mts.map → transport-_6R1ub7t.d.mts.map} +1 -1
  612. package/dist/{transport--CfOflrp.mjs → transport-fX59xaJz.mjs} +4 -3
  613. package/dist/{transport--CfOflrp.mjs.map → transport-fX59xaJz.mjs.map} +1 -1
  614. package/dist/{trusted-proxy-CEVekpby.mjs → trusted-proxy-fe6nEgR4.mjs} +1 -1
  615. package/dist/{trusted-proxy-CEVekpby.mjs.map → trusted-proxy-fe6nEgR4.mjs.map} +1 -1
  616. package/dist/types-75KIqzL9.mjs.map +1 -1
  617. package/dist/{types-BW1syrlw.mjs → types-BXUmsINL.mjs} +1 -1
  618. package/dist/{types-BW1syrlw.mjs.map → types-BXUmsINL.mjs.map} +1 -1
  619. package/dist/{types-CPx2lIGo.d.mts → types-Blb-MRC3.d.mts} +1 -1
  620. package/dist/{types-CPx2lIGo.d.mts.map → types-Blb-MRC3.d.mts.map} +1 -1
  621. package/dist/{types-C3_AbkDP.d.mts → types-Br-ERaxh.d.mts} +2 -1
  622. package/dist/types-Br-ERaxh.d.mts.map +1 -0
  623. package/dist/{types-BPuloofb.d.mts → types-BzBd3PeX.d.mts} +1 -1
  624. package/dist/{types-BPuloofb.d.mts.map → types-BzBd3PeX.d.mts.map} +1 -1
  625. package/dist/{types-DefV3OiO.d.mts → types-CLqvnd05.d.mts} +1 -1
  626. package/dist/{types-DefV3OiO.d.mts.map → types-CLqvnd05.d.mts.map} +1 -1
  627. package/dist/{types-X0MThr7x.d.mts → types-D5VVLr7J.d.mts} +1 -1
  628. package/dist/{types-X0MThr7x.d.mts.map → types-D5VVLr7J.d.mts.map} +1 -1
  629. package/dist/{types-CAGdePf3.d.mts → types-DHxzBjbD.d.mts} +10 -1
  630. package/dist/types-DHxzBjbD.d.mts.map +1 -0
  631. package/dist/{types-D4_1Ga4L.d.mts → types-DJQxln7q.d.mts} +3 -3
  632. package/dist/{types-D4_1Ga4L.d.mts.map → types-DJQxln7q.d.mts.map} +1 -1
  633. package/dist/{types-CtzytVXn.mjs → types-DM3vioBY.mjs} +1 -1
  634. package/dist/{types-CtzytVXn.mjs.map → types-DM3vioBY.mjs.map} +1 -1
  635. package/dist/{types-CFUfyt1s.d.mts → types-D_DWe7k9.d.mts} +1 -1
  636. package/dist/{types-CFUfyt1s.d.mts.map → types-D_DWe7k9.d.mts.map} +1 -1
  637. package/dist/{types-Cs-TFLNf.d.mts → types-Vg6f5i3s.d.mts} +3 -3
  638. package/dist/{types-Cs-TFLNf.d.mts.map → types-Vg6f5i3s.d.mts.map} +1 -1
  639. package/dist/{upload-attempts-hlne3iA1.mjs → upload-attempts-CNyGSepg.mjs} +1 -1
  640. package/dist/{upload-attempts-hlne3iA1.mjs.map → upload-attempts-CNyGSepg.mjs.map} +1 -1
  641. package/dist/{url-pattern-BHS0V5Ng.mjs → url-pattern-K-KdXcSe.mjs} +1 -1
  642. package/dist/{url-pattern-BHS0V5Ng.mjs.map → url-pattern-K-KdXcSe.mjs.map} +1 -1
  643. package/dist/{user-sHgJ62SP.mjs → user-CtYY0tp2.mjs} +2 -2
  644. package/dist/{user-sHgJ62SP.mjs.map → user-CtYY0tp2.mjs.map} +1 -1
  645. package/dist/{utils-D4vD_Y98.mjs → utils-HztC73hq.mjs} +1 -1
  646. package/dist/{utils-D4vD_Y98.mjs.map → utils-HztC73hq.mjs.map} +1 -1
  647. package/dist/{validate-BkqIG_Uj.mjs → validate-Dd-IUjnZ.mjs} +2 -2
  648. package/dist/{validate-BkqIG_Uj.mjs.map → validate-Dd-IUjnZ.mjs.map} +1 -1
  649. package/dist/{validate-DeGtBN84.d.mts → validate-DlqdOZXB.d.mts} +5 -5
  650. package/dist/{validate-DeGtBN84.d.mts.map → validate-DlqdOZXB.d.mts.map} +1 -1
  651. package/dist/{validation-CiJb_X5o.mjs → validation-D83n7Npi.mjs} +10 -8
  652. package/dist/{validation-CiJb_X5o.mjs.map → validation-D83n7Npi.mjs.map} +1 -1
  653. package/dist/version-ri-sCyAp.mjs +7 -0
  654. package/dist/{version-x0NjTScG.mjs.map → version-ri-sCyAp.mjs.map} +1 -1
  655. package/dist/{widgets-Cwzvww0W.mjs → widgets-CpmZd9Q-.mjs} +4 -4
  656. package/dist/{widgets-Cwzvww0W.mjs.map → widgets-CpmZd9Q-.mjs.map} +1 -1
  657. package/dist/{wxr-taxonomies-CmC0ZsDr.d.mts → wxr-taxonomies-BFd0y72_.d.mts} +1 -1
  658. package/dist/{wxr-taxonomies-CmC0ZsDr.d.mts.map → wxr-taxonomies-BFd0y72_.d.mts.map} +1 -1
  659. package/dist/{wxr-taxonomies-QZjWxjyk.mjs → wxr-taxonomies-Df4vxWcU.mjs} +4 -4
  660. package/dist/{wxr-taxonomies-QZjWxjyk.mjs.map → wxr-taxonomies-Df4vxWcU.mjs.map} +1 -1
  661. package/dist/{zod-generator-D11MhxG4.mjs → zod-generator-DylzusHh.mjs} +24 -18
  662. package/dist/zod-generator-DylzusHh.mjs.map +1 -0
  663. package/package.json +32 -8
  664. package/src/api/errors.ts +20 -0
  665. package/src/api/handlers/index.ts +17 -0
  666. package/src/api/handlers/media-allowlist.ts +1 -0
  667. package/src/api/handlers/media-folders.ts +130 -0
  668. package/src/api/handlers/media-usage-activation.ts +102 -0
  669. package/src/api/handlers/media-usage.ts +108 -0
  670. package/src/api/handlers/media.ts +62 -2
  671. package/src/api/handlers/registry.ts +134 -316
  672. package/src/api/openapi/document.ts +192 -2
  673. package/src/api/schemas/media-usage.ts +77 -0
  674. package/src/api/schemas/media.ts +69 -0
  675. package/src/astro/integration/index.ts +46 -14
  676. package/src/astro/integration/routes.ts +18 -0
  677. package/src/astro/integration/runtime.ts +3 -0
  678. package/src/astro/integration/vite-config.ts +2 -7
  679. package/src/astro/middleware/auth.ts +4 -0
  680. package/src/astro/middleware/scoped-db.ts +21 -0
  681. package/src/astro/middleware.ts +67 -17
  682. package/src/astro/routes/api/admin/media-usage/activation.ts +42 -0
  683. package/src/astro/routes/api/admin/media-usage/progress.ts +37 -0
  684. package/src/astro/routes/api/admin/plugins/registry/artifact.ts +246 -96
  685. package/src/astro/routes/api/auth/dev-bypass.ts +0 -4
  686. package/src/astro/routes/api/auth/me.ts +14 -2
  687. package/src/astro/routes/api/media/[id].ts +3 -0
  688. package/src/astro/routes/api/media/folders/[id].ts +55 -0
  689. package/src/astro/routes/api/media/folders/index.ts +37 -0
  690. package/src/astro/routes/api/media.ts +12 -2
  691. package/src/astro/routes/api/setup/dev-bypass.ts +14 -7
  692. package/src/astro/routes/api/setup/index.ts +5 -13
  693. package/src/astro/types.ts +11 -1
  694. package/src/cli/commands/migrate.ts +593 -0
  695. package/src/cli/index.ts +2 -23
  696. package/src/client/index.ts +140 -2
  697. package/src/client/transport.ts +6 -2
  698. package/src/components/EmDashImage.astro +154 -50
  699. package/src/components/Gallery.astro +7 -0
  700. package/src/components/InlinePortableTextEditor.tsx +130 -1
  701. package/src/components/LiveSearch.astro +23 -0
  702. package/src/components/inline-code-block.tsx +180 -49
  703. package/src/content/converters/gallery.ts +2 -0
  704. package/src/content/converters/types.ts +2 -0
  705. package/src/database/connection.ts +2 -31
  706. package/src/database/index.ts +9 -3
  707. package/src/database/json-recordset.ts +16 -0
  708. package/src/database/migrations/019_i18n.ts +1 -1
  709. package/src/database/migrations/071_restore_content_bylines_table.ts +43 -0
  710. package/src/database/migrations/072_media_folders.ts +72 -0
  711. package/src/database/migrations/073_media_focal_point.ts +57 -0
  712. package/src/database/migrations/policy.ts +70 -0
  713. package/src/database/migrations/runner.ts +57 -5
  714. package/src/database/repositories/content.ts +5 -4
  715. package/src/database/repositories/media-folders.ts +115 -0
  716. package/src/database/repositories/media-usage-work.ts +395 -111
  717. package/src/database/repositories/media-usage.ts +1038 -10
  718. package/src/database/repositories/media.ts +97 -29
  719. package/src/database/transaction.ts +1 -0
  720. package/src/database/types.ts +13 -0
  721. package/src/db/adapters.ts +52 -4
  722. package/src/db/index.ts +3 -1
  723. package/src/db/libsql-migrations.ts +72 -0
  724. package/src/db/node-sqlite-compat.ts +78 -0
  725. package/src/db/postgres-migrations.ts +59 -0
  726. package/src/db/sqlite-migrations.ts +37 -0
  727. package/src/db/sqlite.ts +3 -3
  728. package/src/emdash-runtime.ts +154 -120
  729. package/src/fields/image.ts +7 -1
  730. package/src/fields/types.ts +6 -2
  731. package/src/i18n/normalize.ts +31 -0
  732. package/src/index.ts +12 -1
  733. package/src/media/focal-point.ts +37 -0
  734. package/src/media/local-runtime.ts +8 -0
  735. package/src/media/mime.ts +1 -0
  736. package/src/media/normalize.ts +10 -0
  737. package/src/media/types.ts +10 -0
  738. package/src/media/usage/activation.ts +162 -35
  739. package/src/media/usage/capture-triggers.ts +19 -3
  740. package/src/media/usage/collection-deletion-processor.ts +7 -3
  741. package/src/media/usage/collection-deletion.ts +10 -0
  742. package/src/media/usage/content-refresh.ts +258 -39
  743. package/src/media/usage/content-snapshots.ts +128 -31
  744. package/src/media/usage/extractor.ts +32 -13
  745. package/src/media/usage/maintenance-engine.ts +166 -0
  746. package/src/media/usage/reconciliation-processor.ts +106 -29
  747. package/src/media/usage/reconciliation.ts +90 -7
  748. package/src/media/usage/work-processor.ts +342 -101
  749. package/src/migrations/config-loader.ts +205 -0
  750. package/src/migrations/direct-executor.ts +141 -0
  751. package/src/migrations/identity.ts +46 -0
  752. package/src/migrations/index.ts +19 -0
  753. package/src/migrations/integration-metadata.ts +67 -0
  754. package/src/migrations/manifest-builder.ts +55 -0
  755. package/src/migrations/manifest-writer.ts +63 -0
  756. package/src/migrations/manifest.ts +197 -0
  757. package/src/migrations/protocol.ts +59 -0
  758. package/src/migrations/target.ts +35 -0
  759. package/src/plugins/index.ts +1 -0
  760. package/src/plugins/save-rejection.ts +20 -0
  761. package/src/plugins/scheduler/node.ts +1 -13
  762. package/src/plugins/scheduler/types.ts +0 -2
  763. package/src/plugins/state.ts +13 -3
  764. package/src/registry/artifact-fetch.ts +530 -0
  765. package/src/registry/config.ts +24 -6
  766. package/src/registry/types.ts +12 -15
  767. package/src/schema/types.ts +1 -0
  768. package/src/schema/zod-generator.ts +12 -4
  769. package/src/security/ssrf.ts +21 -2
  770. package/src/seed/apply.ts +4 -4
  771. package/src/ui.ts +2 -0
  772. package/src/virtual-modules.d.ts +8 -0
  773. package/dist/adapters-34EN2eXO.d.mts.map +0 -1
  774. package/dist/api-DdAsirBe.mjs.map +0 -1
  775. package/dist/collection-deletion-C8BugSGz.mjs.map +0 -1
  776. package/dist/content-BNfwh7xX.mjs.map +0 -1
  777. package/dist/content-refresh-DNERP4-f.mjs.map +0 -1
  778. package/dist/errors-CLUPm7aD.mjs.map +0 -1
  779. package/dist/index-C3Y-rsyN.d.mts.map +0 -1
  780. package/dist/media-BWWWb8kg.mjs.map +0 -1
  781. package/dist/media-CzhAn2--.mjs.map +0 -1
  782. package/dist/media-allowlist-BD6HhgXA.mjs.map +0 -1
  783. package/dist/media-usage-De3qvwjc.mjs.map +0 -1
  784. package/dist/media-usage-work-DrCbqBcl.mjs.map +0 -1
  785. package/dist/menus-CC-yn1F9.mjs.map +0 -1
  786. package/dist/mime-DfIVjNkr.mjs.map +0 -1
  787. package/dist/normalize-Bc-s_r-a.mjs.map +0 -1
  788. package/dist/placeholder-CzW7Lshs.d.mts.map +0 -1
  789. package/dist/registry-C581V4TP.mjs.map +0 -1
  790. package/dist/relations-DItI3MN9.d.mts.map +0 -1
  791. package/dist/runner-BiDvq5dv.mjs.map +0 -1
  792. package/dist/runner-CgQbELYC.d.mts.map +0 -1
  793. package/dist/state-DYwPue40.mjs.map +0 -1
  794. package/dist/types-C3_AbkDP.d.mts.map +0 -1
  795. package/dist/types-CAGdePf3.d.mts.map +0 -1
  796. package/dist/version-x0NjTScG.mjs +0 -7
  797. package/dist/zod-generator-D11MhxG4.mjs.map +0 -1
  798. /package/dist/{api-tokens-CyTsLszZ.mjs → api-tokens-B5NHLNUN.mjs} +0 -0
  799. /package/dist/{ssrf-DpXgKsna.mjs → ssrf-BPKzkm6O.mjs} +0 -0
  800. /package/dist/{types-CP1CflWf.mjs → types-D6OcIp2P.mjs} +0 -0
@@ -1,100 +1,106 @@
1
- import { a as runMigrations, n as MIGRATION_RACE_WAIT_MS, t as ConcurrentMigrationTimeoutError } from "../runner-BiDvq5dv.mjs";
2
- import { t as validateIdentifier } from "../validate-BpwoDOgt.mjs";
3
- import { c as isSqlite, p as tableExists, s as isPostgres, u as listTablesLike } from "../dialect-helpers-BSbfaO3F.mjs";
1
+ import { r as MIGRATION_RACE_WAIT_MS, t as ConcurrentMigrationTimeoutError } from "../runner-77GOZCnH.mjs";
2
+ import { c as isSqlite, u as listTablesLike } from "../dialect-helpers-BSbfaO3F.mjs";
4
3
  import { r as getI18nConfig, s as setI18nConfig } from "../config-C3EBstHi.mjs";
5
- import { C as createHookPipeline, M as AuditRepository, T as definePlugin, _ as PluginRouteRegistry, b as toRouteCallerInfo, c as getSandboxRouteErrorDetails, t as getMenu, v as buildRouteMeta, w as resolveExclusiveHooks, x as EmailPipeline, y as parseRouteInput } from "../menus-CC-yn1F9.mjs";
4
+ import { C as isContentSaveRejection, D as definePlugin, E as resolveExclusiveHooks, P as AuditRepository, T as createHookPipeline, _ as PluginRouteRegistry, b as toRouteCallerInfo, c as getSandboxRouteErrorDetails, t as getMenu, v as buildRouteMeta, x as EmailPipeline, y as parseRouteInput } from "../menus-CNg0QlDt.mjs";
6
5
  import { n as createDeferredTaskTracker, t as after } from "../after-CBU2vkoO.mjs";
7
6
  import { createRequestMetrics, getRequestContext, runWithContext } from "../request-context.mjs";
8
7
  import { o as getLastContentWriteAt } from "../object-cache-71tPfV4A.mjs";
9
8
  import { i as MAX_COLLECTION_LIST_COLUMNS } from "../types-75KIqzL9.mjs";
10
- import { i as RevisionRepository, t as ContentRepository } from "../content-BNfwh7xX.mjs";
9
+ import { i as RevisionRepository, t as ContentRepository } from "../content-CP-SeXNa.mjs";
11
10
  import { n as isMissingTableError } from "../db-errors-Cl-sKYTm.mjs";
12
11
  import "../slugify-B994zkXC.mjs";
13
12
  import "../base64-CvLhUKQv.mjs";
14
13
  import { n as ContentMutationConflictError } from "../types-CQPWhPxE.mjs";
15
- import { t as MediaRepository } from "../media-CzhAn2--.mjs";
16
- import "../user-sHgJ62SP.mjs";
17
- import { t as withTransaction } from "../transaction-NQj4VJ7Z.mjs";
18
- import "../taxonomy-CPeM8d2o.mjs";
19
- import "../comment-Dxlep6cE.mjs";
20
- import { t as OptionsRepository } from "../options-CBXIHUoT.mjs";
21
- import { r as createSiteInfo } from "../context-DRgOvSnW.mjs";
22
- import "../menus-fxvEDYE6.mjs";
23
- import "../redirect-COsS6SWJ.mjs";
24
- import { i as setRequestCacheEntry, r as requestCached } from "../request-cache-BSUptuJR.mjs";
25
- import "../byline-registry-DDun8sxh.mjs";
26
- import { n as initWithLock, t as createInitLock } from "../init-lock-6b309ZrF.mjs";
27
- import "../field-defs-cache-hg6QkjV9.mjs";
28
- import "../byline-pEcoDps8.mjs";
29
- import "../relation-DtmAQH7s.mjs";
30
- import { n as normalizeMediaValue } from "../normalize-Bc-s_r-a.mjs";
31
- import "../dist-D_6CKR8z.mjs";
32
- import "../enrich-_ySMEvEE.mjs";
33
- import "../seo-CvDxfvFb.mjs";
34
- import { r as singleFlightCached, t as createSingleFlightCache } from "../single-flight-cache-B0eLKS6K.mjs";
35
- import "../cache-CBJaDy_E.mjs";
36
- import { t as FTSManager } from "../fts-manager-CZ-5dVwJ.mjs";
37
- import "../resolve-DOhVEvty.mjs";
14
+ import { t as MediaRepository } from "../media-D6SeAfBl.mjs";
15
+ import "../user-CtYY0tp2.mjs";
16
+ import "../taxonomy-8X09dLN5.mjs";
17
+ import "../comment-DhvLEdhQ.mjs";
18
+ import { t as OptionsRepository } from "../options-BHPeIbBd.mjs";
19
+ import { r as createSiteInfo } from "../context-BR5hRWiK.mjs";
20
+ import "../menus-CD7EFLwz.mjs";
21
+ import "../redirect-CZkbozu9.mjs";
22
+ import { i as setRequestCacheEntry, r as requestCached } from "../request-cache-DH9zdCOD.mjs";
23
+ import "../byline-registry-Bpu9-MVs.mjs";
24
+ import { n as initWithLock, t as createInitLock } from "../init-lock-DJkX6Hto.mjs";
25
+ import "../field-defs-cache-Bh4q6oar.mjs";
26
+ import "../byline-Cpnwrje1.mjs";
27
+ import "../relation-A6wLF_DS.mjs";
28
+ import { n as normalizeMediaValue } from "../normalize-CSxdKBVa.mjs";
29
+ import "../dist-89xCrGqi.mjs";
30
+ import "../enrich-BX0IDp4z.mjs";
31
+ import "../seo-Cm-dtvgk.mjs";
32
+ import { r as singleFlightCached, t as createSingleFlightCache } from "../single-flight-cache-BPuGPdcN.mjs";
33
+ import "../cache-5VirJ2Cn.mjs";
34
+ import { t as FTSManager } from "../fts-manager-BhOdW0bi.mjs";
35
+ import "../resolve-DxUpTMmE.mjs";
38
36
  import { createRecorder, flushRecorder, isInstrumentationEnabled, kyselyLogOption } from "../database/instrumentation.mjs";
39
- import { s as getDb } from "../loader-DLBqRiGP.mjs";
40
- import { a as getTaxonomyDefs, o as getTaxonomyTerms } from "../taxonomies-B0o9ExNV.mjs";
41
- import { $ as handleContentUnschedule, A as handleMediaUpdate, B as handleContentDelete, D as handleMediaDelete, E as handleMediaCreate, F as handleContentAuthors, G as handleContentList, H as handleContentDuplicate, I as handleContentCompare, J as handleContentPublish, K as handleContentListTrashed, L as handleContentCountScheduled, M as handleRevisionList, N as handleRevisionRestore, O as handleMediaGet, Q as handleContentUnpublish, R as handleContentCountTrashed, U as handleContentGet, V as handleContentDiscardDraft, W as handleContentGetIncludingTrashed, X as handleContentSchedule, Y as handleContentRestore, Z as handleContentTranslations, _ as loadBundleFromR2, et as handleContentUpdate, j as handleRevisionGet, k as handleMediaList, q as handleContentPermanentDelete, s as normalizeRegistryConfig, tt as validateRev, v as syncDeclaredStorageIndexes, z as handleContentCreate } from "../api-DdAsirBe.mjs";
42
- import { n as recordSchedulerHeartbeatSafely } from "../dashboard-CB8J89rJ.mjs";
43
- import { r as hashString } from "../hash-Cg0TXJPV.mjs";
44
- import { _ as loadContentMediaUsageFields, a as findNonTranslatableSiblingContentIds, g as buildContentMediaUsageFieldFingerprint, l as refreshContentMediaUsageAfterWrite, r as deleteContentMediaUsage, s as markContentMediaUsageCollectionStale, u as refreshContentMediaUsageForWork, v as CONTENT_SOURCE_SCHEMA_VERSION, y as MediaUsageRepository } from "../content-refresh-DNERP4-f.mjs";
45
- import { t as MediaUsageWorkRepository } from "../media-usage-work-DrCbqBcl.mjs";
46
- import "../media-usage-De3qvwjc.mjs";
47
- import { n as collectionDeletionCurrentTimestamp, r as deleteActivatedMediaUsageCollection, t as MediaUsageCollectionDeletionRepository } from "../collection-deletion-C8BugSGz.mjs";
48
- import "../media-usage-work-B9Bksh_b.mjs";
49
- import { n as SchemaRegistry } from "../registry-C581V4TP.mjs";
50
- import "../query-vgZNwIhh.mjs";
51
- import "../zod-generator-D11MhxG4.mjs";
52
- import "../schema-BFyTg38T.mjs";
53
- import "../schema-CeQSNwB3.mjs";
54
- import "../seo-AFpVvTqr.mjs";
55
- import { t as PluginStateRepository } from "../state-DYwPue40.mjs";
56
- import "../sections-COctk6hf.mjs";
57
- import { i as getSiteSettings, o as invalidateSiteSettingsCache } from "../settings-B372MV0p.mjs";
58
- import "../settings-BE9aqn_9.mjs";
59
- import "../taxonomies-Dk3MAklL.mjs";
60
- import { r as normalizeManifestRoute } from "../manifest-schema-BXbUAHod.mjs";
61
- import "../types-CP1CflWf.mjs";
62
- import "../ssrf-Cpvg_n3A.mjs";
63
- import "../error-DupyJZE2.mjs";
64
- import "../parse-DFbuc-Fh.mjs";
65
- import "../media-BWWWb8kg.mjs";
66
- import "../redirects-cV9g7V-0.mjs";
67
- import "../relations-BBApI4e2.mjs";
68
- import "../status-CWVYpbD8.mjs";
69
- import { a as invalidateUrlPatternCache } from "../query-BgjfiLUQ.mjs";
70
- import "../import-B41u8Rot.mjs";
71
- import { t as getTrustedProxyHeaders } from "../trusted-proxy-CEVekpby.mjs";
72
- import { n as sanitizeHeadersForSandbox, t as extractRequestMeta } from "../request-meta-DgZJh3zG.mjs";
73
- import "../ssrf-DpXgKsna.mjs";
74
- import { n as CronExecutor } from "../cron-Y9aLtgrA.mjs";
75
- import { r as devConsoleEmailDeliver, t as DEV_CONSOLE_EMAIL_PLUGIN_ID } from "../email-console-BHNjIwiB.mjs";
76
- import { n as assertMediaUsageActivationWriteAllowed } from "../media-usage-write-fence-aHEgN1zB.mjs";
77
- import "../utils-D4vD_Y98.mjs";
78
- import "../tokens-C39klX8R.mjs";
79
- import "../preview-PW-nOnq4.mjs";
80
- import "../hreflang-DNswoYsi.mjs";
81
- import "../comment-reaction-D6qfHKBc.mjs";
82
- import "../bylines-BWemdX2k.mjs";
83
- import { n as getWidgetAreas } from "../widgets-Cwzvww0W.mjs";
84
- import "../validate-BkqIG_Uj.mjs";
85
- import "../apply-D5zfeD09.mjs";
86
- import "../load-DmvJrtxS.mjs";
87
- import "../search-BpuPKZb0.mjs";
37
+ import { s as getDb } from "../loader-ZsjE4paL.mjs";
38
+ import { a as getTaxonomyDefs, o as getTaxonomyTerms } from "../taxonomies-kmyudNGP.mjs";
39
+ import { A as handleContentPublish, C as handleContentDiscardDraft, D as handleContentList, E as handleContentGetIncludingTrashed, F as handleContentUnschedule, I as handleContentUpdate, L as validateRev, M as handleContentSchedule, N as handleContentTranslations, O as handleContentListTrashed, P as handleContentUnpublish, S as handleContentDelete, T as handleContentGet, _ as handleContentAuthors, b as handleContentCountTrashed, c as handleMediaCreate, d as handleMediaList, f as handleMediaUpdate, h as handleRevisionRestore, j as handleContentRestore, k as handleContentPermanentDelete, l as handleMediaDelete, m as handleRevisionList, p as handleRevisionGet, u as handleMediaGet, v as handleContentCompare, w as handleContentDuplicate, x as handleContentCreate, y as handleContentCountScheduled } from "../api-B0w3uBiG.mjs";
40
+ import { n as recordSchedulerHeartbeatSafely } from "../dashboard-CkIV2rnQ.mjs";
41
+ import { r as hashString } from "../hash-CTcngWvj.mjs";
42
+ import "../media-folders-D-0CmRZj.mjs";
43
+ import "../capture-triggers-CNW1q3HK.mjs";
44
+ import { r as activateMediaUsageCapture } from "../activation-K0kGBWuJ.mjs";
45
+ import { c as markContentMediaUsageCollectionStale, i as deleteContentMediaUsage, o as findNonTranslatableSiblingContentIds, u as refreshContentMediaUsageAfterWrite, y as MediaUsageRepository } from "../content-refresh-DLOfIhYc.mjs";
46
+ import "../media-usage-work-C-fhM30c.mjs";
47
+ import "../content-repair-BCxzy0r4.mjs";
48
+ import "../collection-deletion-m_oi2c0N.mjs";
49
+ import { s as processMediaUsageWorkAfterWrite } from "../media-usage-BTRJvS6V.mjs";
50
+ import { t as ErrorCode } from "../errors-n2j49uNf.mjs";
51
+ import "../media-usage-activation-Dx0kPV6b.mjs";
52
+ import "../media-usage-work-DWb2XPMI.mjs";
53
+ import { n as SchemaRegistry } from "../registry-Cx-jEJOS.mjs";
54
+ import "../query-tZDEdtCN.mjs";
55
+ import "../zod-generator-DylzusHh.mjs";
56
+ import "../schema-BY-oVntF.mjs";
57
+ import "../schema-Cw9kVK0I.mjs";
58
+ import "../seo-BSiTIXzd.mjs";
59
+ import { t as PluginStateRepository } from "../state-BJfU9Uzw.mjs";
60
+ import "../sections-DTfjyf1z.mjs";
61
+ import { i as getSiteSettings, o as invalidateSiteSettingsCache } from "../settings-BgN-0DT-.mjs";
62
+ import "../settings-DjtkUo__.mjs";
63
+ import "../taxonomies-CPGxo-bn.mjs";
64
+ import { r as normalizeManifestRoute } from "../manifest-schema-CUYPfCEH.mjs";
65
+ import { s as normalizeRegistryConfig, v as loadBundleFromR2, y as syncDeclaredStorageIndexes } from "../registry-BiO5WwPq.mjs";
66
+ import "../types-D6OcIp2P.mjs";
67
+ import "../ssrf-DqebVE34.mjs";
68
+ import "../error-D9w94fjV.mjs";
69
+ import "../parse-BRJg1kz8.mjs";
70
+ import "../media-BjwNuLEu.mjs";
71
+ import "../redirects-CRIM55KH.mjs";
72
+ import "../relations-BTPM2saV.mjs";
73
+ import "../status-DDu8HIQl.mjs";
74
+ import { a as invalidateUrlPatternCache } from "../query-D18vTdha.mjs";
75
+ import "../import-C4_O3KXU.mjs";
76
+ import { t as getTrustedProxyHeaders } from "../trusted-proxy-fe6nEgR4.mjs";
77
+ import { n as sanitizeHeadersForSandbox, t as extractRequestMeta } from "../request-meta-DRp5ENd_.mjs";
78
+ import "../ssrf-BPKzkm6O.mjs";
79
+ import { n as CronExecutor } from "../cron-CWfWrWKG.mjs";
80
+ import { r as devConsoleEmailDeliver, t as DEV_CONSOLE_EMAIL_PLUGIN_ID } from "../email-console-CEJgR_LB.mjs";
81
+ import { n as assertMediaUsageActivationWriteAllowed } from "../media-usage-write-fence-BwoJVRDC.mjs";
82
+ import "../utils-HztC73hq.mjs";
83
+ import "../tokens-gTS-IVUp.mjs";
84
+ import "../preview-dn2dUyM0.mjs";
85
+ import "../hreflang-Huc6O5wa.mjs";
86
+ import "../comment-reaction-CRuItEjt.mjs";
87
+ import "../bylines-BSo9B9RX.mjs";
88
+ import { n as getWidgetAreas } from "../widgets-CpmZd9Q-.mjs";
89
+ import "../validate-Dd-IUjnZ.mjs";
90
+ import "../apply-B_VMpJ2P.mjs";
91
+ import "../load-Bo7fZAbg.mjs";
92
+ import "../search-CfT3KxO5.mjs";
88
93
  import "../index.mjs";
89
- import { n as VERSION, t as COMMIT } from "../version-x0NjTScG.mjs";
90
- import { t as getAuthMode } from "../mode-Bpe-tNNy.mjs";
91
- import "../snapshot-B5h6aPMh.mjs";
92
- import { u as maybeRunScheduledBackup } from "../backup-LHjIAVNP.mjs";
93
- import { t as cleanupExpiredChallenges } from "../challenge-store-CZyFVaad.mjs";
94
- import { t as removeUploadAttempt } from "../upload-attempts-hlne3iA1.mjs";
95
- import { a as validateEncryptionKeyAtStartup } from "../secrets-dtYs2hDo.mjs";
94
+ import { i as resolveRuntimeMigrationMode, n as enforceRuntimeMigrationPolicy, t as PendingMigrationsError } from "../policy-BVRganJQ.mjs";
95
+ import { n as VERSION, t as COMMIT } from "../version-ri-sCyAp.mjs";
96
+ import { t as getAuthMode } from "../mode-DaUM_MB2.mjs";
97
+ import "../snapshot-DSB8P7Lm.mjs";
98
+ import { u as maybeRunScheduledBackup } from "../backup-ChyHfIoX.mjs";
99
+ import { t as cleanupExpiredChallenges } from "../challenge-store-D5pSY2S1.mjs";
100
+ import { t as removeUploadAttempt } from "../upload-attempts-CNyGSepg.mjs";
101
+ import { a as validateEncryptionKeyAtStartup } from "../secrets-BH2Qow1-.mjs";
96
102
  import { isRecord } from "../plugin-utils.mjs";
97
- import { t as resolveSessionUser } from "../session-user-BzVemLkw.mjs";
103
+ import { t as resolveSessionUser } from "../session-user-CNAfvsdw.mjs";
98
104
  import { Kysely, sql } from "kysely";
99
105
  import { ulid } from "ulidx";
100
106
  import { z } from "zod";
@@ -157,1145 +163,6 @@ async function warnAboutUnconfiguredTaxonomyLocales(db, configuredLocales, defin
157
163
  console.warn(`EmDash: Taxonomy rows use locales outside the configured locales (${supportedLocales.join(", ")}): ${details}. Locale-scoped reads may not return these rows. Review and repair them explicitly: ${REPAIR_GUIDE}`);
158
164
  }
159
165
 
160
- //#endregion
161
- //#region src/media/usage/collection-deletion-processor.ts
162
- const MEDIA_USAGE_COLLECTION_DELETION_LIMITS = Object.freeze({
163
- candidatesPerTick: 4,
164
- deletionsPerTick: 1,
165
- rowsPerBatch: 50,
166
- leaseDurationSeconds: 300,
167
- maxAttempts: 5,
168
- retryBaseSeconds: 30,
169
- retryMaxSeconds: 900,
170
- maxQueriesPerTick: 30
171
- });
172
- async function processDueMediaUsageCollectionDeletions(db) {
173
- const repository = new MediaUsageCollectionDeletionRepository(db);
174
- const candidates = await repository.findDue(MEDIA_USAGE_COLLECTION_DELETION_LIMITS.candidatesPerTick);
175
- if (candidates.length === 0) return {
176
- candidateCount: 0,
177
- claimedCount: 0,
178
- outcome: "idle"
179
- };
180
- let claim = null;
181
- for (const candidate of candidates) {
182
- claim = await repository.claim({
183
- collectionId: candidate.collectionId,
184
- phase: candidate.phase,
185
- leaseDurationSeconds: MEDIA_USAGE_COLLECTION_DELETION_LIMITS.leaseDurationSeconds
186
- });
187
- if (claim?.leaseToken) break;
188
- }
189
- if (!claim?.leaseToken) return {
190
- candidateCount: candidates.length,
191
- claimedCount: 0,
192
- outcome: "claim_lost"
193
- };
194
- try {
195
- const processed = await processClaimedDeletion(db, claim);
196
- if (!processed.finalized && !processed.released && !await repository.release(claim)) return {
197
- candidateCount: candidates.length,
198
- claimedCount: 1,
199
- outcome: "claim_lost"
200
- };
201
- return {
202
- candidateCount: candidates.length,
203
- claimedCount: 1,
204
- outcome: processed.finalized ? "finalized" : "progress"
205
- };
206
- } catch (error) {
207
- const terminal = claim.attemptCount + 1 >= MEDIA_USAGE_COLLECTION_DELETION_LIMITS.maxAttempts;
208
- if (!await repository.recordFailure({
209
- collectionId: claim.collectionId,
210
- leaseToken: claim.leaseToken,
211
- errorCode: "MEDIA_USAGE_COLLECTION_DELETION_FAILED",
212
- terminal,
213
- retryDelaySeconds: retryDelaySeconds$2(claim.attemptCount)
214
- })) return {
215
- candidateCount: candidates.length,
216
- claimedCount: 1,
217
- outcome: "claim_lost"
218
- };
219
- console.error("[media-usage:collection-deletion] Processing failed:", error);
220
- return {
221
- candidateCount: candidates.length,
222
- claimedCount: 1,
223
- outcome: terminal ? "failed" : "retry"
224
- };
225
- }
226
- }
227
- async function processClaimedDeletion(db, claim) {
228
- if (claim.phase === "fence" || claim.phase === "registry" || claim.phase === "table") {
229
- await deleteActivatedMediaUsageCollection(db, {
230
- collectionId: claim.collectionId,
231
- collectionSlug: claim.collectionSlug,
232
- forceDelete: claim.forceDelete
233
- }, {
234
- frontPhaseLimit: 1,
235
- claimed: claim
236
- });
237
- return {
238
- finalized: false,
239
- released: true
240
- };
241
- }
242
- if (claim.phase === "work") await processWorkBatch(db, claim);
243
- if (claim.phase === "sources") await processSourceBatch(db, claim);
244
- if (claim.phase === "status") await processStatus(db, claim);
245
- if (claim.phase === "finalize") {
246
- await finalizeDeletion(db, claim);
247
- return {
248
- finalized: true,
249
- released: true
250
- };
251
- }
252
- return {
253
- finalized: false,
254
- released: false
255
- };
256
- }
257
- async function processWorkBatch(db, claim) {
258
- await withTransaction(db, async (trx) => {
259
- const rows = await trx.selectFrom("_emdash_media_usage_work").select("content_id").where("collection_id", "=", claim.collectionId).$if(claim.workCursor !== null, (query) => query.where("content_id", ">", claim.workCursor)).orderBy("content_id", "asc").limit(MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch + 1).execute();
260
- const batch = rows.slice(0, MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch);
261
- if (batch.length > 0) await trx.deleteFrom("_emdash_media_usage_work").where("collection_id", "=", claim.collectionId).where("content_id", "in", batch.map((row) => row.content_id)).where(liveLeaseGuard(trx, claim)).execute();
262
- await updateDeletion(trx, claim, {
263
- phase: rows.length > MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch ? "work" : "sources",
264
- work_cursor: rows.length > MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch ? batch.at(-1).content_id : null
265
- });
266
- });
267
- return false;
268
- }
269
- async function processSourceBatch(db, claim) {
270
- await withTransaction(db, async (trx) => {
271
- let sourceKey = claim.sourceKey;
272
- if (!sourceKey) {
273
- const source = await trx.selectFrom("_emdash_media_usage_sources").select("source_key").where("source_type", "=", "content").where("collection_id", "=", claim.collectionId).orderBy("source_key", "asc").limit(1).executeTakeFirst();
274
- if (!source) {
275
- await updateDeletion(trx, claim, {
276
- phase: "status",
277
- source_key: null,
278
- occurrence_cursor: null
279
- });
280
- return;
281
- }
282
- sourceKey = source.source_key;
283
- await updateDeletion(trx, claim, {
284
- source_key: sourceKey,
285
- occurrence_cursor: null
286
- });
287
- }
288
- const occurrences = await trx.selectFrom("_emdash_media_usage").select("id").where("source_key", "=", sourceKey).$if(claim.occurrenceCursor !== null, (query) => query.where("id", ">", claim.occurrenceCursor)).orderBy("id", "asc").limit(MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch + 1).execute();
289
- const batch = occurrences.slice(0, MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch);
290
- if (batch.length > 0) await trx.deleteFrom("_emdash_media_usage").where("source_key", "=", sourceKey).where("id", "in", batch.map((row) => row.id)).where(liveLeaseGuard(trx, claim)).execute();
291
- if (occurrences.length > MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch) {
292
- await updateDeletion(trx, claim, {
293
- source_key: sourceKey,
294
- occurrence_cursor: batch.at(-1).id
295
- });
296
- return;
297
- }
298
- await trx.deleteFrom("_emdash_media_usage_sources").where("source_key", "=", sourceKey).where("source_type", "=", "content").where("collection_id", "=", claim.collectionId).where(liveLeaseGuard(trx, claim)).execute();
299
- await updateDeletion(trx, claim, {
300
- source_key: null,
301
- occurrence_cursor: null
302
- });
303
- });
304
- return false;
305
- }
306
- async function processStatus(db, claim) {
307
- await withTransaction(db, async (trx) => {
308
- if (await exactCleanupRowsRemain(trx, claim, false)) throw new Error("Collection deletion cleanup is incomplete");
309
- await trx.deleteFrom("_emdash_media_usage_reconciliations").where("collection_id", "=", claim.collectionId).where("collection_slug", "=", claim.collectionSlug).where(liveLeaseGuard(trx, claim)).execute();
310
- await trx.deleteFrom("_emdash_media_usage_index_status").where("adapter_id", "=", "content-media").where("scope_type", "=", "collection").where("scope_key", "=", claim.collectionSlug).where("collection_id", "=", claim.collectionId).where(liveLeaseGuard(trx, claim)).execute();
311
- await updateDeletion(trx, claim, { phase: "finalize" });
312
- });
313
- return false;
314
- }
315
- async function finalizeDeletion(db, claim) {
316
- if (await tableExists(db, `ec_${claim.collectionSlug}`)) throw new Error("Collection table still exists during deletion finalization");
317
- if (await exactCleanupRowsRemain(db, claim)) throw new Error("Collection deletion is incomplete");
318
- if (await db.selectFrom("_emdash_collections").select("id").where("id", "=", claim.collectionId).where("slug", "=", claim.collectionSlug).executeTakeFirst()) throw new Error("Collection registry identity still exists");
319
- const result = await db.deleteFrom("_emdash_media_usage_collection_deletions").where("collection_id", "=", claim.collectionId).where("collection_slug", "=", claim.collectionSlug).where("state", "=", "leased").where("phase", "=", "finalize").where("lease_token", "=", claim.leaseToken).where(liveLeaseGuard(db, claim)).executeTakeFirst();
320
- if (Number(result.numDeletedRows ?? 0) !== 1) throw new Error("Collection deletion lost finalization");
321
- return true;
322
- }
323
- async function exactCleanupRowsRemain(db, claim, includeStatus = true) {
324
- const row = (await sql`
325
- SELECT
326
- EXISTS (
327
- SELECT 1 FROM _emdash_media_usage_work
328
- WHERE collection_id = ${claim.collectionId}
329
- ) AS work_present,
330
- EXISTS (
331
- SELECT 1 FROM _emdash_media_usage_sources
332
- WHERE source_type = 'content' AND collection_id = ${claim.collectionId}
333
- ) AS source_present,
334
- EXISTS (
335
- SELECT 1 FROM _emdash_media_usage_index_status
336
- WHERE adapter_id = 'content-media'
337
- AND scope_type = 'collection'
338
- AND scope_key = ${claim.collectionSlug}
339
- AND collection_id = ${claim.collectionId}
340
- ) AS status_present
341
- `.execute(db)).rows[0];
342
- return Boolean(row?.work_present) || Boolean(row?.source_present) || includeStatus && Boolean(row?.status_present);
343
- }
344
- async function updateDeletion(db, claim, values) {
345
- const result = await db.updateTable("_emdash_media_usage_collection_deletions").set({
346
- ...values,
347
- attempt_count: 0,
348
- last_error_code: null,
349
- updated_at: collectionDeletionCurrentTimestamp(db)
350
- }).where("collection_id", "=", claim.collectionId).where("state", "=", "leased").where("lease_token", "=", claim.leaseToken).where(liveLeaseGuard(db, claim)).executeTakeFirst();
351
- if (Number(result.numUpdatedRows ?? 0) !== 1) throw new Error("Collection deletion lease was lost");
352
- }
353
- function liveLeaseGuard(db, claim) {
354
- return isPostgres(db) ? sql`EXISTS (
355
- SELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion
356
- WHERE deletion.collection_id = ${claim.collectionId}
357
- AND deletion.state = 'leased'
358
- AND deletion.lease_token = ${claim.leaseToken}
359
- AND deletion.lease_expires_at::timestamptz > clock_timestamp()
360
- )` : sql`EXISTS (
361
- SELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion
362
- WHERE deletion.collection_id = ${claim.collectionId}
363
- AND deletion.state = 'leased'
364
- AND deletion.lease_token = ${claim.leaseToken}
365
- AND deletion.lease_expires_at > strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
366
- )`;
367
- }
368
- function retryDelaySeconds$2(attemptCount) {
369
- return Math.min(MEDIA_USAGE_COLLECTION_DELETION_LIMITS.retryMaxSeconds, MEDIA_USAGE_COLLECTION_DELETION_LIMITS.retryBaseSeconds * 2 ** attemptCount);
370
- }
371
-
372
- //#endregion
373
- //#region src/media/usage/reconciliation.ts
374
- const ACTIVATION_KEY = "incremental_capture";
375
- const CONTENT_ADAPTER_ID = "content-media";
376
- const COLLECTION_SCOPE = "collection";
377
- const MAX_CANDIDATES = 100;
378
- const MAX_PORTABLE_DURATION_SECONDS = 365 * 24 * 60 * 60;
379
- const STABLE_ERROR_CODE_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
380
- var MediaUsageReconciliationRepository = class {
381
- constructor(db) {
382
- this.db = db;
383
- }
384
- async findByIdentity(collectionId, runToken) {
385
- assertIdentity({
386
- collectionId,
387
- runToken
388
- });
389
- const row = await this.db.selectFrom("_emdash_media_usage_reconciliations").selectAll().where("collection_id", "=", collectionId).where("run_token", "=", runToken).executeTakeFirst();
390
- return row ? rowToRecord(row) : null;
391
- }
392
- async beginRun(claim) {
393
- const now = timestampOffset(this.db, 0);
394
- const sameRun = sql`status = 'running' AND cursor = ${claim.runToken}`;
395
- return (await this.db.updateTable("_emdash_media_usage_index_status as status").set({
396
- status: "running",
397
- started_at: sql`CASE WHEN ${sameRun} THEN started_at ELSE ${now} END`,
398
- completed_at: null,
399
- cursor: claim.runToken,
400
- indexed_source_count: 0,
401
- failed_source_count: 0,
402
- last_error_code: null,
403
- change_epoch: sql`CASE WHEN ${sameRun} THEN change_epoch ELSE change_epoch + 1 END`,
404
- reconciliation_required: 1,
405
- updated_at: now
406
- }).where("status.adapter_id", "=", CONTENT_ADAPTER_ID).where("status.scope_type", "=", COLLECTION_SCOPE).where("status.collection_id", "=", claim.collectionId).where("status.scope_key", "=", claim.collectionSlug).where("status.capture_state", "=", "active").where("status.reconciliation_required", "=", 1).where((eb) => eb.or([eb("status.status", "!=", "running"), eb("status.cursor", "=", claim.runToken)])).where(this.liveClaimExists(claim)).returning("change_epoch").executeTakeFirst())?.change_epoch ?? null;
407
- }
408
- async findScanUpperId(claim) {
409
- const tableName = contentTableName(claim.collectionSlug);
410
- return (await sql`
411
- SELECT content.id
412
- FROM ${sql.ref(tableName)} AS content
413
- WHERE ${this.liveClaimExistsSql(claim)}
414
- ORDER BY content.id DESC
415
- LIMIT 1
416
- `.execute(this.db)).rows[0]?.id ?? null;
417
- }
418
- async initializeScan(input) {
419
- const result = await this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
420
- target_epoch: input.targetEpoch,
421
- field_fingerprint: input.fieldFingerprint,
422
- phase: "scan",
423
- scan_cursor: null,
424
- scan_upper_id: input.scanUpperId,
425
- source_cursor: null,
426
- source_upper_key: null,
427
- attempt_count: 0,
428
- last_error_code: null,
429
- updated_at: timestampOffset(this.db, 0)
430
- }).where("reconciliation.collection_id", "=", input.claim.collectionId).where("reconciliation.run_token", "=", input.claim.runToken).where("reconciliation.target_epoch", "is", null).where("reconciliation.state", "=", "leased").where("reconciliation.lease_token", "=", input.claim.leaseToken).where(liveLease(this.db)).where(this.statusOwnsRun(input.claim, input.targetEpoch)).executeTakeFirst();
431
- return Number(result.numUpdatedRows ?? 0) === 1;
432
- }
433
- async findScanPage(reconciliation, limit) {
434
- if (!Number.isSafeInteger(limit) || limit < 1 || limit > 50) throw new Error("Reconciliation scan page limit must be from 1 to 50");
435
- if (!reconciliation.leaseToken || reconciliation.targetEpoch === null) return [];
436
- const tableName = contentTableName(reconciliation.collectionSlug);
437
- const lowerBound = reconciliation.scanCursor ? sql`AND content.id > ${reconciliation.scanCursor}` : sql``;
438
- const upperBound = reconciliation.scanUpperId ? sql`AND content.id <= ${reconciliation.scanUpperId}` : sql`AND 1 = 0`;
439
- return (await sql`
440
- SELECT content.id
441
- FROM ${sql.ref(tableName)} AS content
442
- WHERE 1 = 1
443
- ${lowerBound}
444
- ${upperBound}
445
- AND ${this.liveClaimExistsSql(reconciliation)}
446
- ORDER BY content.id ASC
447
- LIMIT ${limit}
448
- `.execute(this.db)).rows.map((row) => row.id);
449
- }
450
- async checkpointScan(input) {
451
- let query = this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
452
- scan_cursor: input.nextCursor,
453
- attempt_count: 0,
454
- last_error_code: null,
455
- updated_at: timestampOffset(this.db, 0)
456
- }).where("reconciliation.collection_id", "=", input.claim.collectionId).where("reconciliation.run_token", "=", input.claim.runToken).where("reconciliation.target_epoch", "=", input.targetEpoch).where("reconciliation.state", "=", "leased").where("reconciliation.phase", "=", "scan").where("reconciliation.lease_token", "=", input.claim.leaseToken).where(liveLease(this.db)).where(this.statusOwnsRun(input.claim, input.targetEpoch));
457
- query = input.previousCursor ? query.where("reconciliation.scan_cursor", "=", input.previousCursor) : query.where("reconciliation.scan_cursor", "is", null);
458
- const result = await query.executeTakeFirst();
459
- return Number(result.numUpdatedRows ?? 0) === 1;
460
- }
461
- async ownsRun(claim, targetEpoch) {
462
- const result = await sql`
463
- SELECT ${this.statusOwnsRun(claim, targetEpoch)} AS owned
464
- `.execute(this.db);
465
- return Boolean(result.rows[0]?.owned);
466
- }
467
- async restartRun(claim, previousEpoch) {
468
- const now = timestampOffset(this.db, 0);
469
- const interruptedRestart = sql`status = 'running'
470
- AND cursor = ${claim.runToken}
471
- AND change_epoch > ${previousEpoch}`;
472
- return (await this.db.updateTable("_emdash_media_usage_index_status as status").set({
473
- status: "running",
474
- started_at: sql`CASE WHEN ${interruptedRestart} THEN started_at ELSE ${now} END`,
475
- completed_at: null,
476
- cursor: claim.runToken,
477
- last_error_code: null,
478
- change_epoch: sql`CASE WHEN ${interruptedRestart} THEN change_epoch ELSE change_epoch + 1 END`,
479
- reconciliation_required: 1,
480
- updated_at: now
481
- }).where("status.adapter_id", "=", CONTENT_ADAPTER_ID).where("status.scope_type", "=", COLLECTION_SCOPE).where("status.collection_id", "=", claim.collectionId).where("status.scope_key", "=", claim.collectionSlug).where("status.capture_state", "=", "active").where("status.reconciliation_required", "=", 1).where((eb) => eb.or([eb("status.status", "!=", "running"), eb("status.cursor", "=", claim.runToken)])).where(this.liveClaimExists(claim)).returning("change_epoch").executeTakeFirst())?.change_epoch ?? null;
482
- }
483
- async restartScan(input) {
484
- const result = await this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
485
- target_epoch: input.targetEpoch,
486
- field_fingerprint: input.fieldFingerprint,
487
- phase: "scan",
488
- scan_cursor: null,
489
- scan_upper_id: input.scanUpperId,
490
- source_cursor: null,
491
- source_upper_key: null,
492
- attempt_count: 0,
493
- next_attempt_at: timestampOffset(this.db, 0),
494
- last_error_code: null,
495
- updated_at: timestampOffset(this.db, 0)
496
- }).where("reconciliation.collection_id", "=", input.claim.collectionId).where("reconciliation.run_token", "=", input.claim.runToken).where("reconciliation.target_epoch", "=", input.previousEpoch).where("reconciliation.state", "=", "leased").where("reconciliation.lease_token", "=", input.claim.leaseToken).where(liveLease(this.db)).where(this.statusOwnsRun(input.claim, input.targetEpoch)).executeTakeFirst();
497
- return Number(result.numUpdatedRows ?? 0) === 1;
498
- }
499
- async findWorkBarrier(collectionId) {
500
- if (!collectionId) throw new Error("Reconciliation work barrier requires a collection ID");
501
- const failed = await this.db.selectFrom("_emdash_media_usage_work").select("last_error_code").where("collection_id", "=", collectionId).where("state", "=", "failed").orderBy("content_id").limit(1).executeTakeFirst();
502
- if (failed) return {
503
- state: "failed",
504
- errorCode: failed.last_error_code ?? "MEDIA_USAGE_PROCESSING_FAILED"
505
- };
506
- return await this.db.selectFrom("_emdash_media_usage_work").select("content_id").where("collection_id", "=", collectionId).limit(1).executeTakeFirst() ? { state: "pending" } : { state: "empty" };
507
- }
508
- async findSourceUpperKey(claim, targetEpoch) {
509
- return (await this.db.selectFrom("_emdash_media_usage_sources as source").select("source.source_key").where("source.source_type", "=", "content").where("source.collection_id", "=", claim.collectionId).where("source.identity_version", "=", 1).where(this.liveClaimExists(claim)).where(this.statusOwnsRun(claim, targetEpoch)).orderBy("source.source_key", "desc").limit(1).executeTakeFirst())?.source_key ?? null;
510
- }
511
- async transitionToSources(input) {
512
- const result = await this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
513
- phase: "sources",
514
- source_cursor: null,
515
- source_upper_key: input.sourceUpperKey,
516
- attempt_count: 0,
517
- last_error_code: null,
518
- updated_at: timestampOffset(this.db, 0)
519
- }).where("reconciliation.collection_id", "=", input.claim.collectionId).where("reconciliation.run_token", "=", input.claim.runToken).where("reconciliation.target_epoch", "=", input.targetEpoch).where("reconciliation.field_fingerprint", "=", input.fieldFingerprint).where("reconciliation.state", "=", "leased").where("reconciliation.phase", "=", "scan").where("reconciliation.lease_token", "=", input.claim.leaseToken).where(liveLease(this.db)).where(this.statusOwnsRun(input.claim, input.targetEpoch)).where((eb) => eb.not(eb.exists(eb.selectFrom("_emdash_media_usage_work as work").select("work.content_id").where("work.collection_id", "=", input.claim.collectionId)))).executeTakeFirst();
520
- return Number(result.numUpdatedRows ?? 0) === 1;
521
- }
522
- async findSourcePage(reconciliation, limit) {
523
- if (!Number.isSafeInteger(limit) || limit < 1 || limit > 50) throw new Error("Reconciliation source page limit must be from 1 to 50");
524
- if (!reconciliation.leaseToken || reconciliation.targetEpoch === null) return [];
525
- let query = this.db.selectFrom("_emdash_media_usage_sources as source").select([
526
- "source.source_key",
527
- "source.content_id",
528
- "source.source_variant"
529
- ]).where("source.source_type", "=", "content").where("source.collection_id", "=", reconciliation.collectionId).where("source.identity_version", "=", 1).where(this.liveClaimExists(reconciliation)).where(this.statusOwnsRun(reconciliation, reconciliation.targetEpoch));
530
- if (reconciliation.sourceCursor) query = query.where("source.source_key", ">", reconciliation.sourceCursor);
531
- if (reconciliation.sourceUpperKey) query = query.where("source.source_key", "<=", reconciliation.sourceUpperKey);
532
- else query = query.where(sql`1 = 0`);
533
- return (await query.orderBy("source.source_key").limit(limit).execute()).map((row) => ({
534
- sourceKey: row.source_key,
535
- contentId: row.content_id,
536
- sourceVariant: row.source_variant
537
- }));
538
- }
539
- async findMissingContentIds(collectionSlug, contentIds) {
540
- const unique = [...new Set(contentIds)];
541
- if (unique.length === 0) return [];
542
- if (unique.length > 50 || unique.some((contentId) => !contentId)) throw new Error("Reconciliation source page has invalid content identity");
543
- const tableName = contentTableName(collectionSlug);
544
- const existing = await sql`
545
- SELECT id FROM ${sql.ref(tableName)} WHERE id IN (${sql.join(unique)})
546
- `.execute(this.db);
547
- const present = new Set(existing.rows.map((row) => row.id));
548
- return unique.filter((contentId) => !present.has(contentId));
549
- }
550
- async checkpointSources(input) {
551
- let query = this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
552
- source_cursor: input.nextCursor,
553
- attempt_count: 0,
554
- last_error_code: null,
555
- updated_at: timestampOffset(this.db, 0)
556
- }).where("reconciliation.collection_id", "=", input.claim.collectionId).where("reconciliation.run_token", "=", input.claim.runToken).where("reconciliation.target_epoch", "=", input.targetEpoch).where("reconciliation.state", "=", "leased").where("reconciliation.phase", "=", "sources").where("reconciliation.lease_token", "=", input.claim.leaseToken).where(liveLease(this.db)).where(this.statusOwnsRun(input.claim, input.targetEpoch));
557
- query = input.previousCursor ? query.where("reconciliation.source_cursor", "=", input.previousCursor) : query.where("reconciliation.source_cursor", "is", null);
558
- const result = await query.executeTakeFirst();
559
- return Number(result.numUpdatedRows ?? 0) === 1;
560
- }
561
- async finishFailedCoverage(collectionId, runToken) {
562
- assertIdentity({
563
- collectionId,
564
- runToken
565
- });
566
- const now = timestampOffset(this.db, 0);
567
- const result = await this.db.updateTable("_emdash_media_usage_index_status as status").set({
568
- status: sql`CASE WHEN EXISTS (
569
- SELECT 1 FROM _emdash_media_usage_sources AS source
570
- WHERE source.source_type = 'content'
571
- AND source.collection_id = ${collectionId}
572
- AND source.identity_version = 1
573
- LIMIT 1
574
- ) THEN 'partial' ELSE 'failed' END`,
575
- completed_at: null,
576
- cursor: null,
577
- last_error_code: sql`CASE WHEN (
578
- SELECT reconciliation.last_error_code
579
- FROM _emdash_media_usage_reconciliations AS reconciliation
580
- WHERE reconciliation.collection_id = ${collectionId}
581
- AND reconciliation.run_token = ${runToken}
582
- ) = 'MEDIA_USAGE_RECONCILIATION_ENTRY_FAILED'
583
- THEN COALESCE(
584
- (SELECT work.last_error_code
585
- FROM _emdash_media_usage_work AS work
586
- WHERE work.collection_id = ${collectionId} AND work.state = 'failed'
587
- ORDER BY work.content_id LIMIT 1),
588
- 'MEDIA_USAGE_RECONCILIATION_ENTRY_FAILED'
589
- )
590
- ELSE (
591
- SELECT reconciliation.last_error_code
592
- FROM _emdash_media_usage_reconciliations AS reconciliation
593
- WHERE reconciliation.collection_id = ${collectionId}
594
- AND reconciliation.run_token = ${runToken}
595
- ) END`,
596
- reconciliation_required: 1,
597
- updated_at: now
598
- }).where("status.adapter_id", "=", CONTENT_ADAPTER_ID).where("status.scope_type", "=", COLLECTION_SCOPE).where("status.collection_id", "=", collectionId).where("status.status", "=", "running").where("status.cursor", "=", runToken).where((eb) => eb.exists(eb.selectFrom("_emdash_media_usage_reconciliations as reconciliation").select("reconciliation.collection_id").where("reconciliation.collection_id", "=", collectionId).where("reconciliation.run_token", "=", runToken).where("reconciliation.state", "=", "failed"))).executeTakeFirst();
599
- return Number(result.numUpdatedRows ?? 0) === 1;
600
- }
601
- async finalizeCoverage(input) {
602
- const now = timestampOffset(this.db, 0);
603
- const result = await this.db.updateTable("_emdash_media_usage_index_status as status").set({
604
- status: "complete",
605
- schema_version: input.schemaVersion,
606
- completed_at: now,
607
- cursor: null,
608
- last_error_code: null,
609
- reconciliation_required: 0,
610
- updated_at: now
611
- }).where("status.adapter_id", "=", CONTENT_ADAPTER_ID).where("status.scope_type", "=", COLLECTION_SCOPE).where("status.collection_id", "=", input.claim.collectionId).where("status.scope_key", "=", input.claim.collectionSlug).where("status.capture_state", "=", "active").where("status.reconciliation_required", "=", 1).where("status.status", "=", "running").where("status.cursor", "=", input.claim.runToken).where("status.change_epoch", "=", input.targetEpoch).where((eb) => eb.not(eb.exists(eb.selectFrom("_emdash_media_usage_work as work").select("work.content_id").where("work.collection_id", "=", input.claim.collectionId)))).where((eb) => eb.exists(eb.selectFrom("_emdash_media_usage_reconciliations as reconciliation").innerJoin("_emdash_collections as collection", (join) => join.onRef("collection.id", "=", "reconciliation.collection_id").onRef("collection.slug", "=", "reconciliation.collection_slug")).select("reconciliation.collection_id").where("reconciliation.collection_id", "=", input.claim.collectionId).where("reconciliation.run_token", "=", input.claim.runToken).where("reconciliation.target_epoch", "=", input.targetEpoch).where("reconciliation.field_fingerprint", "=", input.fieldFingerprint).where("reconciliation.state", "=", "leased").where("reconciliation.phase", "=", "sources").where("reconciliation.lease_token", "=", input.claim.leaseToken).where(liveLease(this.db, "reconciliation.lease_expires_at")).where((inner) => inner.not(inner.exists(inner.selectFrom("_emdash_media_usage_collection_deletions as deletion").select("deletion.collection_id").where("deletion.collection_id", "=", input.claim.collectionId)))))).where((eb) => eb.exists(eb.selectFrom("_emdash_media_usage_activation as activation").select("activation.task_key").where("activation.task_key", "=", ACTIVATION_KEY).where("activation.state", "=", "active"))).executeTakeFirst();
612
- return Number(result.numUpdatedRows ?? 0) === 1;
613
- }
614
- async deleteFinalized(claim) {
615
- const result = await this.db.deleteFrom("_emdash_media_usage_reconciliations as reconciliation").where("reconciliation.collection_id", "=", claim.collectionId).where("reconciliation.run_token", "=", claim.runToken).where("reconciliation.state", "=", "leased").where("reconciliation.lease_token", "=", claim.leaseToken).where((eb) => eb.exists(eb.selectFrom("_emdash_media_usage_index_status as status").select("status.collection_id").where("status.collection_id", "=", claim.collectionId).where("status.scope_key", "=", claim.collectionSlug).where("status.status", "=", "complete").where("status.reconciliation_required", "=", 0))).executeTakeFirst();
616
- return Number(result.numDeletedRows ?? 0) === 1;
617
- }
618
- async deleteOneObsolete() {
619
- return (await sql`
620
- DELETE FROM _emdash_media_usage_reconciliations
621
- WHERE (collection_id, run_token) IN (
622
- SELECT reconciliation.collection_id, reconciliation.run_token
623
- FROM _emdash_media_usage_reconciliations AS reconciliation
624
- INNER JOIN _emdash_media_usage_index_status AS status
625
- ON status.collection_id = reconciliation.collection_id
626
- AND status.scope_key = reconciliation.collection_slug
627
- WHERE status.adapter_id = ${CONTENT_ADAPTER_ID}
628
- AND status.scope_type = ${COLLECTION_SCOPE}
629
- AND status.reconciliation_required = 0
630
- ORDER BY reconciliation.updated_at, reconciliation.collection_id
631
- LIMIT 1
632
- )
633
- RETURNING collection_id
634
- `.execute(this.db)).rows.length === 1;
635
- }
636
- liveClaimExists(claim) {
637
- return this.liveClaimExistsSql(claim);
638
- }
639
- liveClaimExistsSql(claim) {
640
- return sql`EXISTS (
641
- SELECT 1
642
- FROM _emdash_media_usage_reconciliations AS reconciliation
643
- INNER JOIN _emdash_collections AS collection
644
- ON collection.id = reconciliation.collection_id
645
- AND collection.slug = reconciliation.collection_slug
646
- WHERE reconciliation.collection_id = ${claim.collectionId}
647
- AND reconciliation.collection_slug = ${claim.collectionSlug}
648
- AND reconciliation.run_token = ${claim.runToken}
649
- AND reconciliation.state = 'leased'
650
- AND reconciliation.lease_token = ${claim.leaseToken}
651
- AND ${liveLease(this.db, "reconciliation.lease_expires_at")}
652
- AND EXISTS (
653
- SELECT 1 FROM _emdash_media_usage_activation AS activation
654
- WHERE activation.task_key = ${ACTIVATION_KEY}
655
- AND activation.state = 'active'
656
- )
657
- AND NOT EXISTS (
658
- SELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion
659
- WHERE deletion.collection_id = reconciliation.collection_id
660
- )
661
- )`;
662
- }
663
- statusOwnsRun(claim, targetEpoch) {
664
- return sql`EXISTS (
665
- SELECT 1 FROM _emdash_media_usage_index_status AS status
666
- WHERE status.adapter_id = ${CONTENT_ADAPTER_ID}
667
- AND status.scope_type = ${COLLECTION_SCOPE}
668
- AND status.collection_id = ${claim.collectionId}
669
- AND status.scope_key = ${claim.collectionSlug}
670
- AND status.capture_state = 'active'
671
- AND status.reconciliation_required = 1
672
- AND status.status = 'running'
673
- AND status.cursor = ${claim.runToken}
674
- AND status.change_epoch = ${targetEpoch}
675
- )`;
676
- }
677
- async seedNextCandidate() {
678
- const runToken = ulid();
679
- const now = timestampOffset(this.db, 0);
680
- return (await sql`
681
- INSERT INTO _emdash_media_usage_reconciliations (
682
- collection_id,
683
- collection_slug,
684
- run_token,
685
- next_attempt_at,
686
- updated_at
687
- )
688
- SELECT status.collection_id, status.scope_key, ${runToken}, ${now}, ${now}
689
- FROM _emdash_media_usage_index_status AS status
690
- INNER JOIN _emdash_collections AS collection
691
- ON collection.id = status.collection_id
692
- AND collection.slug = status.scope_key
693
- WHERE status.adapter_id = ${CONTENT_ADAPTER_ID}
694
- AND status.scope_type = ${COLLECTION_SCOPE}
695
- AND status.capture_state = 'active'
696
- AND status.reconciliation_required = 1
697
- AND status.collection_id IS NOT NULL
698
- AND EXISTS (
699
- SELECT 1 FROM _emdash_media_usage_activation AS activation
700
- WHERE activation.task_key = ${ACTIVATION_KEY}
701
- AND activation.state = 'active'
702
- )
703
- AND NOT EXISTS (
704
- SELECT 1 FROM _emdash_media_usage_reconciliations AS existing
705
- WHERE existing.collection_id = status.collection_id
706
- )
707
- AND NOT EXISTS (
708
- SELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion
709
- WHERE deletion.collection_id = status.collection_id
710
- )
711
- ORDER BY status.collection_id
712
- LIMIT 1
713
- ON CONFLICT (collection_id) DO NOTHING
714
- RETURNING collection_id
715
- `.execute(this.db)).rows.length === 1;
716
- }
717
- async findDue(limit) {
718
- assertLimit(limit);
719
- const nextAttemptIsDue = timestampIsDue(this.db, "next_attempt_at");
720
- return (await sql`
721
- WITH pending_candidates AS (
722
- SELECT * FROM _emdash_media_usage_reconciliations
723
- WHERE state = 'pending' AND ${nextAttemptIsDue}
724
- ORDER BY next_attempt_at, updated_at, collection_id
725
- LIMIT ${limit}
726
- ), retry_candidates AS (
727
- SELECT * FROM _emdash_media_usage_reconciliations
728
- WHERE state = 'retry' AND ${nextAttemptIsDue}
729
- ORDER BY next_attempt_at, updated_at, collection_id
730
- LIMIT ${limit}
731
- ), leased_candidates AS (
732
- SELECT * FROM _emdash_media_usage_reconciliations
733
- WHERE state = 'leased' AND ${timestampIsDue(this.db, "lease_expires_at")}
734
- ORDER BY lease_expires_at, updated_at, collection_id
735
- LIMIT ${limit}
736
- ), candidates AS (
737
- SELECT * FROM pending_candidates
738
- UNION ALL SELECT * FROM retry_candidates
739
- UNION ALL SELECT * FROM leased_candidates
740
- )
741
- SELECT * FROM candidates
742
- ORDER BY CASE WHEN state = 'leased' THEN lease_expires_at ELSE next_attempt_at END,
743
- updated_at,
744
- collection_id
745
- LIMIT ${limit}
746
- `.execute(this.db)).rows.map(rowToRecord);
747
- }
748
- async findFailed(limit) {
749
- assertLimit(limit);
750
- return (await this.db.selectFrom("_emdash_media_usage_reconciliations as reconciliation").innerJoin("_emdash_media_usage_index_status as status", (join) => join.onRef("status.collection_id", "=", "reconciliation.collection_id").onRef("status.scope_key", "=", "reconciliation.collection_slug")).selectAll("reconciliation").where("reconciliation.state", "=", "failed").where("status.adapter_id", "=", CONTENT_ADAPTER_ID).where("status.scope_type", "=", COLLECTION_SCOPE).where((eb) => eb.or([
751
- eb("status.reconciliation_required", "=", 0),
752
- eb.and([eb("status.status", "=", "running"), eb("status.cursor", "=", eb.ref("reconciliation.run_token"))]),
753
- eb.and([eb("status.cursor", "is", null), eb("status.change_epoch", ">", eb.ref("reconciliation.target_epoch"))])
754
- ])).orderBy("reconciliation.updated_at").orderBy("reconciliation.collection_id").limit(limit).execute()).map(rowToRecord);
755
- }
756
- async claim(input) {
757
- assertIdentity(input);
758
- assertDuration(input.leaseDurationSeconds, "lease duration");
759
- const leaseToken = ulid();
760
- const row = await this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
761
- state: "leased",
762
- lease_token: leaseToken,
763
- lease_expires_at: timestampOffset(this.db, input.leaseDurationSeconds),
764
- updated_at: timestampOffset(this.db, 0)
765
- }).where("reconciliation.collection_id", "=", input.collectionId).where("reconciliation.run_token", "=", input.runToken).where((eb) => eb.or([eb.and([eb("reconciliation.state", "in", ["pending", "retry"]), timestampIsDue(this.db, "reconciliation.next_attempt_at")]), eb.and([eb("reconciliation.state", "=", "leased"), timestampIsDue(this.db, "reconciliation.lease_expires_at")])])).where((eb) => eb.exists(eb.selectFrom("_emdash_media_usage_activation as activation").select("activation.task_key").where("activation.task_key", "=", ACTIVATION_KEY).where("activation.state", "=", "active"))).where((eb) => eb.exists(eb.selectFrom("_emdash_media_usage_index_status as status").innerJoin("_emdash_collections as collection", (join) => join.onRef("collection.id", "=", "status.collection_id").onRef("collection.slug", "=", "status.scope_key")).select("status.collection_id").whereRef("status.collection_id", "=", "reconciliation.collection_id").whereRef("status.scope_key", "=", "reconciliation.collection_slug").where("status.adapter_id", "=", CONTENT_ADAPTER_ID).where("status.scope_type", "=", COLLECTION_SCOPE).where("status.capture_state", "=", "active").where("status.reconciliation_required", "=", 1))).where((eb) => eb.not(eb.exists(eb.selectFrom("_emdash_media_usage_collection_deletions as deletion").select("deletion.collection_id").whereRef("deletion.collection_id", "=", "reconciliation.collection_id")))).returningAll().executeTakeFirst();
766
- return row ? {
767
- ...rowToRecord(row),
768
- leaseToken
769
- } : null;
770
- }
771
- async release(input) {
772
- assertLeaseIdentity(input);
773
- assertDuration(input.delaySeconds, "release delay", true);
774
- const result = await this.db.updateTable("_emdash_media_usage_reconciliations").set({
775
- state: "pending",
776
- next_attempt_at: timestampOffset(this.db, input.delaySeconds),
777
- lease_token: null,
778
- lease_expires_at: null,
779
- updated_at: timestampOffset(this.db, 0)
780
- }).where("collection_id", "=", input.collectionId).where("run_token", "=", input.runToken).where("state", "=", "leased").where("lease_token", "=", input.leaseToken).where(liveLease(this.db)).executeTakeFirst();
781
- return Number(result.numUpdatedRows ?? 0) === 1;
782
- }
783
- async recordFailure(input) {
784
- assertLeaseIdentity(input);
785
- if (!STABLE_ERROR_CODE_PATTERN.test(input.errorCode)) throw new Error("Reconciliation failure requires a stable error code");
786
- assertDuration(input.retryDelaySeconds, "retry delay", true);
787
- const result = await this.db.updateTable("_emdash_media_usage_reconciliations").set({
788
- state: input.terminal ? "failed" : sql`CASE WHEN attempt_count >= 4 THEN 'failed' ELSE 'retry' END`,
789
- ...input.terminal ? { target_epoch: sql`COALESCE(
790
- target_epoch,
791
- (SELECT status.change_epoch
792
- FROM _emdash_media_usage_index_status AS status
793
- WHERE status.adapter_id = ${CONTENT_ADAPTER_ID}
794
- AND status.scope_type = ${COLLECTION_SCOPE}
795
- AND status.collection_id = ${input.collectionId}
796
- AND status.status = 'running'
797
- AND status.cursor = ${input.runToken})
798
- )` } : {},
799
- attempt_count: sql`attempt_count + 1`,
800
- next_attempt_at: timestampOffset(this.db, input.retryDelaySeconds),
801
- lease_token: null,
802
- lease_expires_at: null,
803
- last_error_code: input.errorCode,
804
- updated_at: timestampOffset(this.db, 0)
805
- }).where("collection_id", "=", input.collectionId).where("run_token", "=", input.runToken).where("state", "=", "leased").where("lease_token", "=", input.leaseToken).where(liveLease(this.db)).executeTakeFirst();
806
- return Number(result.numUpdatedRows ?? 0) === 1;
807
- }
808
- async recordEntryFailure(claim) {
809
- const result = await this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
810
- state: "failed",
811
- attempt_count: sql`attempt_count + 1`,
812
- next_attempt_at: timestampOffset(this.db, 0),
813
- lease_token: null,
814
- lease_expires_at: null,
815
- last_error_code: "MEDIA_USAGE_RECONCILIATION_ENTRY_FAILED",
816
- updated_at: timestampOffset(this.db, 0)
817
- }).where("reconciliation.collection_id", "=", claim.collectionId).where("reconciliation.run_token", "=", claim.runToken).where("reconciliation.state", "=", "leased").where("reconciliation.lease_token", "=", claim.leaseToken).where(liveLease(this.db)).where((eb) => eb.exists(eb.selectFrom("_emdash_media_usage_work as work").select("work.content_id").where("work.collection_id", "=", claim.collectionId).where("work.state", "=", "failed"))).executeTakeFirst();
818
- return Number(result.numUpdatedRows ?? 0) === 1;
819
- }
820
- async resetFailedForNewEpoch(observed) {
821
- const collectionId = "collection_id" in observed ? observed.collection_id : observed.collectionId;
822
- const runToken = "run_token" in observed ? observed.run_token : observed.runToken;
823
- const targetEpoch = "target_epoch" in observed ? observed.target_epoch : observed.targetEpoch;
824
- if (targetEpoch === null) return false;
825
- const now = timestampOffset(this.db, 0);
826
- const result = await this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
827
- state: "pending",
828
- phase: "scan",
829
- target_epoch: null,
830
- field_fingerprint: null,
831
- scan_cursor: null,
832
- scan_upper_id: null,
833
- source_cursor: null,
834
- source_upper_key: null,
835
- attempt_count: 0,
836
- next_attempt_at: now,
837
- lease_token: null,
838
- lease_expires_at: null,
839
- last_error_code: null,
840
- updated_at: now
841
- }).where("reconciliation.collection_id", "=", collectionId).where("reconciliation.run_token", "=", runToken).where("reconciliation.state", "=", "failed").where("reconciliation.target_epoch", "=", targetEpoch).where((eb) => eb.exists(eb.selectFrom("_emdash_media_usage_index_status as status").select("status.collection_id").whereRef("status.collection_id", "=", "reconciliation.collection_id").whereRef("status.scope_key", "=", "reconciliation.collection_slug").where("status.adapter_id", "=", CONTENT_ADAPTER_ID).where("status.scope_type", "=", COLLECTION_SCOPE).where("status.capture_state", "=", "active").where("status.reconciliation_required", "=", 1).where("status.cursor", "is", null).where("status.change_epoch", ">", targetEpoch))).executeTakeFirst();
842
- return Number(result.numUpdatedRows ?? 0) === 1;
843
- }
844
- };
845
- function rowToRecord(row) {
846
- if (!isState(row.state) || !isPhase(row.phase) || !Number.isSafeInteger(row.attempt_count)) throw new Error("Invalid media usage reconciliation lifecycle");
847
- return {
848
- collectionId: row.collection_id,
849
- collectionSlug: row.collection_slug,
850
- runToken: row.run_token,
851
- targetEpoch: row.target_epoch,
852
- fieldFingerprint: row.field_fingerprint,
853
- state: row.state,
854
- phase: row.phase,
855
- scanCursor: row.scan_cursor,
856
- scanUpperId: row.scan_upper_id,
857
- sourceCursor: row.source_cursor,
858
- sourceUpperKey: row.source_upper_key,
859
- attemptCount: row.attempt_count,
860
- nextAttemptAt: row.next_attempt_at,
861
- leaseToken: row.lease_token,
862
- leaseExpiresAt: row.lease_expires_at,
863
- lastErrorCode: row.last_error_code,
864
- createdAt: row.created_at,
865
- updatedAt: row.updated_at
866
- };
867
- }
868
- function isState(state) {
869
- return state === "pending" || state === "retry" || state === "leased" || state === "failed";
870
- }
871
- function isPhase(phase) {
872
- return phase === "scan" || phase === "sources";
873
- }
874
- function assertLimit(limit) {
875
- if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_CANDIDATES) throw new Error("Reconciliation candidate limit must be from 1 to 100");
876
- }
877
- function assertIdentity(input) {
878
- if (!input.collectionId || !input.runToken) throw new Error("Reconciliation requires an exact collection and run token");
879
- }
880
- function assertLeaseIdentity(input) {
881
- assertIdentity(input);
882
- if (!input.leaseToken) throw new Error("Reconciliation requires a lease token");
883
- }
884
- function assertDuration(value, label, allowZero = false) {
885
- if (!Number.isSafeInteger(value) || value < (allowZero ? 0 : 1) || value > MAX_PORTABLE_DURATION_SECONDS) throw new Error(`Reconciliation ${label} is outside the portable range`);
886
- }
887
- function liveLease(db, column = "lease_expires_at") {
888
- const expiry = sql.ref(column);
889
- return isPostgres(db) ? sql`${expiry} > to_char(statement_timestamp() AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')` : sql`${expiry} > strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;
890
- }
891
- function timestampIsDue(db, column) {
892
- const value = sql.ref(column);
893
- return isPostgres(db) ? sql`${value} <= to_char(statement_timestamp() AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')` : sql`${value} <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;
894
- }
895
- function timestampOffset(db, offsetSeconds) {
896
- if (isPostgres(db)) return sql`to_char(
897
- (clock_timestamp() AT TIME ZONE 'UTC') + (${offsetSeconds} * INTERVAL '1 second'),
898
- 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
899
- )`;
900
- return sql`strftime(
901
- '%Y-%m-%dT%H:%M:%fZ',
902
- 'now',
903
- ${`${offsetSeconds >= 0 ? "+" : ""}${offsetSeconds} seconds`}
904
- )`;
905
- }
906
- function contentTableName(collectionSlug) {
907
- validateIdentifier(collectionSlug, "collection slug");
908
- const tableName = `ec_${collectionSlug}`;
909
- validateIdentifier(tableName, "content table");
910
- return tableName;
911
- }
912
-
913
- //#endregion
914
- //#region src/media/usage/reconciliation-processor.ts
915
- const MEDIA_USAGE_RECONCILIATION_LIMITS = Object.freeze({
916
- candidatesPerTick: 4,
917
- pageSize: 50,
918
- leaseDurationSeconds: 60,
919
- maxAttempts: 5,
920
- retryBaseSeconds: 30,
921
- retryMaxSeconds: 900,
922
- retryJitterRatio: .25,
923
- maxQueriesPerTick: 20
924
- });
925
- async function processDueMediaUsageReconciliation(db) {
926
- if ((await db.selectFrom("_emdash_media_usage_activation").select("state").where("task_key", "=", "incremental_capture").executeTakeFirst())?.state !== "active") return "inactive";
927
- const reconciliation = new MediaUsageReconciliationRepository(db);
928
- if (await reconciliation.deleteOneObsolete()) return "completed";
929
- const [failed] = await reconciliation.findFailed(1);
930
- if (failed) {
931
- if (await reconciliation.finishFailedCoverage(failed.collectionId, failed.runToken)) return "failed";
932
- if (await reconciliation.resetFailedForNewEpoch(failed)) return "advanced";
933
- }
934
- await reconciliation.seedNextCandidate();
935
- const candidates = await reconciliation.findDue(MEDIA_USAGE_RECONCILIATION_LIMITS.candidatesPerTick);
936
- let claim = null;
937
- for (const candidate of candidates) {
938
- claim = await reconciliation.claim({
939
- collectionId: candidate.collectionId,
940
- runToken: candidate.runToken,
941
- leaseDurationSeconds: MEDIA_USAGE_RECONCILIATION_LIMITS.leaseDurationSeconds
942
- });
943
- if (claim) break;
944
- }
945
- if (!claim) return candidates.length === 0 ? "not_due" : "claim_lost";
946
- try {
947
- return await processClaimedReconciliation(db, claim);
948
- } catch (error) {
949
- const terminal = claim.attemptCount + 1 >= MEDIA_USAGE_RECONCILIATION_LIMITS.maxAttempts;
950
- if (!await reconciliation.recordFailure({
951
- collectionId: claim.collectionId,
952
- runToken: claim.runToken,
953
- leaseToken: claim.leaseToken,
954
- errorCode: "MEDIA_USAGE_RECONCILIATION_FAILED",
955
- retryDelaySeconds: retryDelaySeconds$1(claim.attemptCount),
956
- terminal
957
- })) return "claim_lost";
958
- if (terminal) await reconciliation.finishFailedCoverage(claim.collectionId, claim.runToken);
959
- console.error("[media-usage:reconciliation] Processing failed:", error);
960
- return terminal ? "failed" : "retry";
961
- }
962
- }
963
- async function processClaimedMediaUsageReconciliationScan(db, claim, options = {}) {
964
- const reconciliation = new MediaUsageReconciliationRepository(db);
965
- let current = await reconciliation.findByIdentity(claim.collectionId, claim.runToken);
966
- if (!current || current.leaseToken !== claim.leaseToken || current.phase !== "scan") return "deferred";
967
- let fields;
968
- let fieldFingerprint;
969
- if (current.targetEpoch === null) {
970
- const targetEpoch = await reconciliation.beginRun(claim);
971
- if (targetEpoch === null) {
972
- await reconciliation.release({
973
- ...claim,
974
- delaySeconds: 30
975
- });
976
- return "deferred";
977
- }
978
- fields = await loadContentMediaUsageFields(db, claim.collectionSlug, claim.collectionId);
979
- fieldFingerprint = await buildContentMediaUsageFieldFingerprint(fields);
980
- const scanUpperId = fields.extractionFields.length === 0 ? null : await reconciliation.findScanUpperId(claim);
981
- if (!await reconciliation.initializeScan({
982
- claim,
983
- targetEpoch,
984
- fieldFingerprint,
985
- scanUpperId
986
- })) return "deferred";
987
- current = await reconciliation.findByIdentity(claim.collectionId, claim.runToken);
988
- if (!current) return "deferred";
989
- } else {
990
- fields = await loadContentMediaUsageFields(db, claim.collectionSlug, claim.collectionId);
991
- fieldFingerprint = await buildContentMediaUsageFieldFingerprint(fields);
992
- }
993
- if (current.fieldFingerprint !== fieldFingerprint || current.targetEpoch === null) return "restart_required";
994
- const contentIds = await reconciliation.findScanPage(current, 50);
995
- if (contentIds.length === 0) {
996
- if (options.releaseOnExhausted ?? true) await reconciliation.release({
997
- ...claim,
998
- delaySeconds: 30
999
- });
1000
- return "exhausted";
1001
- }
1002
- await new MediaUsageWorkRepository(db).enqueueReconciliationPage({
1003
- collectionId: claim.collectionId,
1004
- collectionSlug: claim.collectionSlug,
1005
- runToken: claim.runToken,
1006
- leaseToken: claim.leaseToken,
1007
- changeEpoch: current.targetEpoch,
1008
- phase: "scan",
1009
- contentIds
1010
- });
1011
- const nextCursor = contentIds.at(-1);
1012
- if (!await reconciliation.checkpointScan({
1013
- claim,
1014
- targetEpoch: current.targetEpoch,
1015
- previousCursor: current.scanCursor,
1016
- nextCursor
1017
- })) return "deferred";
1018
- if (!await reconciliation.release({
1019
- ...claim,
1020
- delaySeconds: 0
1021
- })) return "deferred";
1022
- return "advanced";
1023
- }
1024
- async function processClaimedReconciliation(db, claim) {
1025
- const reconciliation = new MediaUsageReconciliationRepository(db);
1026
- let current = await reconciliation.findByIdentity(claim.collectionId, claim.runToken);
1027
- if (!current || current.leaseToken !== claim.leaseToken) return "claim_lost";
1028
- if (current.targetEpoch !== null && !await reconciliation.ownsRun(claim, current.targetEpoch)) return restartReconciliation(db, reconciliation, claim, current);
1029
- if (current.phase === "scan") {
1030
- const outcome = await processClaimedMediaUsageReconciliationScan(db, claim, { releaseOnExhausted: false });
1031
- if (outcome === "restart_required") {
1032
- current = await reconciliation.findByIdentity(claim.collectionId, claim.runToken) ?? current;
1033
- return restartReconciliation(db, reconciliation, claim, current);
1034
- }
1035
- if (outcome !== "exhausted") return outcome;
1036
- current = await reconciliation.findByIdentity(claim.collectionId, claim.runToken) ?? current;
1037
- const barrier = await reconciliation.findWorkBarrier(claim.collectionId);
1038
- if (barrier.state === "failed") return failReconciliation(reconciliation, claim, barrier.errorCode, true);
1039
- if (barrier.state === "pending") {
1040
- await reconciliation.release({
1041
- ...claim,
1042
- delaySeconds: 30
1043
- });
1044
- return "deferred";
1045
- }
1046
- if (current.targetEpoch === null || current.fieldFingerprint === null) return "claim_lost";
1047
- const sourceUpperKey = await reconciliation.findSourceUpperKey(claim, current.targetEpoch);
1048
- if (!await reconciliation.transitionToSources({
1049
- claim,
1050
- targetEpoch: current.targetEpoch,
1051
- fieldFingerprint: current.fieldFingerprint,
1052
- sourceUpperKey
1053
- })) return "claim_lost";
1054
- if (!await reconciliation.release({
1055
- ...claim,
1056
- delaySeconds: 0
1057
- })) return "claim_lost";
1058
- return "advanced";
1059
- }
1060
- return processSourcePhase(db, reconciliation, claim, current);
1061
- }
1062
- async function processSourcePhase(db, reconciliation, claim, current) {
1063
- if (current.targetEpoch === null || current.fieldFingerprint === null) return "claim_lost";
1064
- const fields = await loadContentMediaUsageFields(db, claim.collectionSlug, claim.collectionId);
1065
- const fieldFingerprint = await buildContentMediaUsageFieldFingerprint(fields);
1066
- if (fieldFingerprint !== current.fieldFingerprint) return restartReconciliation(db, reconciliation, claim, current, fields, fieldFingerprint);
1067
- const page = await reconciliation.findSourcePage(current, MEDIA_USAGE_RECONCILIATION_LIMITS.pageSize);
1068
- if (page.length > 0) {
1069
- if (page.some((source) => !source.contentId || source.sourceVariant !== "columns" && source.sourceVariant !== "draft_overlay")) return failReconciliation(reconciliation, claim, "MEDIA_USAGE_RECONCILIATION_INVALID_SOURCE", false);
1070
- const contentIds = [...new Set(page.map((source) => source.contentId))];
1071
- const enqueueIds = fields.extractionFields.length === 0 ? contentIds : await reconciliation.findMissingContentIds(claim.collectionSlug, contentIds);
1072
- if (enqueueIds.length > 0) await new MediaUsageWorkRepository(db).enqueueReconciliationPage({
1073
- collectionId: claim.collectionId,
1074
- collectionSlug: claim.collectionSlug,
1075
- runToken: claim.runToken,
1076
- leaseToken: claim.leaseToken,
1077
- changeEpoch: current.targetEpoch,
1078
- phase: "sources",
1079
- contentIds: enqueueIds
1080
- });
1081
- if (!await reconciliation.checkpointSources({
1082
- claim,
1083
- targetEpoch: current.targetEpoch,
1084
- previousCursor: current.sourceCursor,
1085
- nextCursor: page.at(-1).sourceKey
1086
- })) return "claim_lost";
1087
- if (!await reconciliation.release({
1088
- ...claim,
1089
- delaySeconds: 0
1090
- })) return "claim_lost";
1091
- return "advanced";
1092
- }
1093
- const barrier = await reconciliation.findWorkBarrier(claim.collectionId);
1094
- if (barrier.state === "failed") return failReconciliation(reconciliation, claim, barrier.errorCode, true);
1095
- if (barrier.state === "pending") {
1096
- await reconciliation.release({
1097
- ...claim,
1098
- delaySeconds: 30
1099
- });
1100
- return "deferred";
1101
- }
1102
- if (!await reconciliation.finalizeCoverage({
1103
- claim,
1104
- targetEpoch: current.targetEpoch,
1105
- fieldFingerprint,
1106
- schemaVersion: CONTENT_SOURCE_SCHEMA_VERSION
1107
- })) return "claim_lost";
1108
- if (!await reconciliation.deleteFinalized(claim)) return "claim_lost";
1109
- return "completed";
1110
- }
1111
- async function restartReconciliation(db, reconciliation, claim, current, fields, fieldFingerprint) {
1112
- if (current.targetEpoch === null) return "claim_lost";
1113
- const discoveredFields = fields ?? await loadContentMediaUsageFields(db, claim.collectionSlug, claim.collectionId);
1114
- const fingerprint = fieldFingerprint ?? await buildContentMediaUsageFieldFingerprint(discoveredFields);
1115
- const targetEpoch = await reconciliation.restartRun(claim, current.targetEpoch);
1116
- if (targetEpoch === null) {
1117
- await reconciliation.release({
1118
- ...claim,
1119
- delaySeconds: 30
1120
- });
1121
- return "deferred";
1122
- }
1123
- const scanUpperId = discoveredFields.extractionFields.length === 0 ? null : await reconciliation.findScanUpperId(claim);
1124
- if (!await reconciliation.restartScan({
1125
- claim,
1126
- previousEpoch: current.targetEpoch,
1127
- targetEpoch,
1128
- fieldFingerprint: fingerprint,
1129
- scanUpperId
1130
- })) return "claim_lost";
1131
- if (!await reconciliation.release({
1132
- ...claim,
1133
- delaySeconds: 0
1134
- })) return "claim_lost";
1135
- return "advanced";
1136
- }
1137
- async function failReconciliation(reconciliation, claim, errorCode, entryFailure) {
1138
- if (!(entryFailure ? await reconciliation.recordEntryFailure(claim) : await reconciliation.recordFailure({
1139
- collectionId: claim.collectionId,
1140
- runToken: claim.runToken,
1141
- leaseToken: claim.leaseToken,
1142
- errorCode,
1143
- retryDelaySeconds: 0,
1144
- terminal: true
1145
- }))) return "claim_lost";
1146
- await reconciliation.finishFailedCoverage(claim.collectionId, claim.runToken);
1147
- return "failed";
1148
- }
1149
- function retryDelaySeconds$1(attemptCount) {
1150
- const exponential = Math.min(MEDIA_USAGE_RECONCILIATION_LIMITS.retryMaxSeconds, MEDIA_USAGE_RECONCILIATION_LIMITS.retryBaseSeconds * 2 ** attemptCount);
1151
- const jitter = Math.floor(exponential * MEDIA_USAGE_RECONCILIATION_LIMITS.retryJitterRatio * Math.random());
1152
- return Math.min(MEDIA_USAGE_RECONCILIATION_LIMITS.retryMaxSeconds, exponential + jitter);
1153
- }
1154
-
1155
- //#endregion
1156
- //#region src/media/usage/work-processor.ts
1157
- const MEDIA_USAGE_WORK_PROCESSING_LIMITS = Object.freeze({
1158
- candidatesPerTick: 4,
1159
- jobsPerTick: 1,
1160
- maxTickDurationMs: 5e3,
1161
- leaseDurationSeconds: 60,
1162
- maxAttempts: 5,
1163
- retryBaseSeconds: 30,
1164
- retryMaxSeconds: 900,
1165
- retryJitterRatio: .25,
1166
- ordinaryStatementsPerJob: 20
1167
- });
1168
- async function processMediaUsageWorkAfterWrite(db, collectionSlug, contentId) {
1169
- if (!await isIncrementalCaptureActive(db)) return {
1170
- outcome: "inactive",
1171
- claimed: false
1172
- };
1173
- const repo = new MediaUsageWorkRepository(db);
1174
- const work = await repo.findWorkForContent(collectionSlug, contentId);
1175
- if (!work) return {
1176
- outcome: "not_due",
1177
- claimed: false
1178
- };
1179
- return processCandidate(db, repo, work);
1180
- }
1181
- async function processDueMediaUsageWork(db) {
1182
- const startedAt = Date.now();
1183
- const result = {
1184
- candidateCount: 0,
1185
- claimedCount: 0,
1186
- completedCount: 0,
1187
- retryCount: 0,
1188
- failedCount: 0,
1189
- supersededCount: 0,
1190
- obsoleteCount: 0,
1191
- durationMs: 0,
1192
- admissionClosed: false
1193
- };
1194
- if (!await isIncrementalCaptureActive(db)) {
1195
- result.durationMs = Date.now() - startedAt;
1196
- return result;
1197
- }
1198
- const repo = new MediaUsageWorkRepository(db);
1199
- const candidates = await repo.findDueWork(MEDIA_USAGE_WORK_PROCESSING_LIMITS.candidatesPerTick);
1200
- result.candidateCount = candidates.length;
1201
- for (const candidate of candidates) {
1202
- if (result.claimedCount >= MEDIA_USAGE_WORK_PROCESSING_LIMITS.jobsPerTick || Date.now() - startedAt >= MEDIA_USAGE_WORK_PROCESSING_LIMITS.maxTickDurationMs) {
1203
- result.admissionClosed = true;
1204
- break;
1205
- }
1206
- const processed = await processCandidate(db, repo, candidate);
1207
- if (processed.claimed) result.claimedCount++;
1208
- if (processed.outcome === "completed") result.completedCount++;
1209
- if (processed.outcome === "retry") result.retryCount++;
1210
- if (processed.outcome === "failed") result.failedCount++;
1211
- if (processed.outcome === "superseded") result.supersededCount++;
1212
- if (processed.outcome === "obsolete") result.obsoleteCount++;
1213
- }
1214
- result.durationMs = Date.now() - startedAt;
1215
- return result;
1216
- }
1217
- async function processCandidate(db, repo, candidate) {
1218
- const claimed = await repo.claimWork({
1219
- collectionId: candidate.collectionId,
1220
- contentId: candidate.contentId,
1221
- workVersion: candidate.workVersion,
1222
- leaseDurationSeconds: MEDIA_USAGE_WORK_PROCESSING_LIMITS.leaseDurationSeconds
1223
- });
1224
- if (!claimed?.leaseToken) return {
1225
- outcome: "claim_lost",
1226
- claimed: false
1227
- };
1228
- const lease = {
1229
- collectionId: claimed.collectionId,
1230
- contentId: claimed.contentId,
1231
- workVersion: claimed.workVersion,
1232
- leaseToken: claimed.leaseToken
1233
- };
1234
- if (!await collectionIdentityIsCurrent(db, claimed.collectionId, claimed.collectionSlug)) return {
1235
- outcome: await repo.completeWork(lease) ? "obsolete" : "superseded",
1236
- claimed: true
1237
- };
1238
- const refresh = await refreshContentMediaUsageForWork(db, claimed.collectionId, claimed.collectionSlug, claimed.contentId);
1239
- if (refresh.success) {
1240
- const completed = await repo.completeWork(lease);
1241
- if (completed) await new MediaUsageRepository(db).recordIncrementalSuccess({
1242
- collectionId: claimed.collectionId,
1243
- collectionSlug: claimed.collectionSlug
1244
- });
1245
- return {
1246
- outcome: completed ? "completed" : "superseded",
1247
- claimed: true
1248
- };
1249
- }
1250
- if (!await collectionIdentityIsCurrent(db, claimed.collectionId, claimed.collectionSlug)) return {
1251
- outcome: await repo.completeWork(lease) ? "obsolete" : "superseded",
1252
- claimed: true
1253
- };
1254
- const errorCode = processingErrorCode(refresh.errorCode);
1255
- if (errorCode === "MEDIA_USAGE_RESOURCE_LIMIT" || claimed.attemptCount + 1 >= MEDIA_USAGE_WORK_PROCESSING_LIMITS.maxAttempts) {
1256
- const failed = await repo.failWork({
1257
- ...lease,
1258
- errorCode
1259
- });
1260
- if (failed) await new MediaUsageRepository(db).recordIncrementalFailure({
1261
- collectionId: claimed.collectionId,
1262
- collectionSlug: claimed.collectionSlug,
1263
- contentId: claimed.contentId,
1264
- workVersion: claimed.workVersion,
1265
- errorCode
1266
- });
1267
- return {
1268
- outcome: failed ? "failed" : "superseded",
1269
- claimed: true
1270
- };
1271
- }
1272
- return {
1273
- outcome: await repo.retryWork({
1274
- ...lease,
1275
- errorCode,
1276
- retryDelaySeconds: retryDelaySeconds(claimed.attemptCount)
1277
- }) ? "retry" : "superseded",
1278
- claimed: true
1279
- };
1280
- }
1281
- async function isIncrementalCaptureActive(db) {
1282
- return (await db.selectFrom("_emdash_media_usage_activation").select("state").where("task_key", "=", "incremental_capture").executeTakeFirst())?.state === "active";
1283
- }
1284
- async function collectionIdentityIsCurrent(db, collectionId, collectionSlug) {
1285
- return await db.selectFrom("_emdash_collections").select("id").where("id", "=", collectionId).where("slug", "=", collectionSlug).executeTakeFirst() !== void 0;
1286
- }
1287
- function retryDelaySeconds(attemptCount) {
1288
- const exponential = Math.min(MEDIA_USAGE_WORK_PROCESSING_LIMITS.retryMaxSeconds, MEDIA_USAGE_WORK_PROCESSING_LIMITS.retryBaseSeconds * 2 ** attemptCount);
1289
- const jitter = Math.floor(exponential * MEDIA_USAGE_WORK_PROCESSING_LIMITS.retryJitterRatio * Math.random());
1290
- return Math.min(MEDIA_USAGE_WORK_PROCESSING_LIMITS.retryMaxSeconds, exponential + jitter);
1291
- }
1292
- function processingErrorCode(errorCode) {
1293
- if (errorCode === "DRAFT_REVISION_NOT_FOUND" || errorCode === "DRAFT_REVISION_MISMATCH" || errorCode === "DRAFT_REVISION_INVALID") return "MEDIA_USAGE_SNAPSHOT_FAILED";
1294
- if (errorCode === "CONTENT_USAGE_GENERATION_CONFLICT") return "MEDIA_USAGE_GENERATION_CONFLICT";
1295
- if (errorCode === "CONTENT_USAGE_RESOURCE_LIMIT") return "MEDIA_USAGE_RESOURCE_LIMIT";
1296
- return "MEDIA_USAGE_PROCESSING_FAILED";
1297
- }
1298
-
1299
166
  //#endregion
1300
167
  //#region src/plugins/sandbox/runner-options.ts
1301
168
  /**
@@ -1823,6 +690,28 @@ const LIST_COLUMN_FIELD_TYPES = new Set([
1823
690
  "multiSelect"
1824
691
  ]);
1825
692
  /**
693
+ * A `ContentSaveRejectedError` carries a message the plugin wrote for the
694
+ * editor; every other exception stays internal and is replaced by a generic
695
+ * message so hook internals cannot leak through the API.
696
+ */
697
+ function beforeSaveFailure(error) {
698
+ if (isContentSaveRejection(error)) return {
699
+ success: false,
700
+ error: {
701
+ code: ErrorCode.SAVE_REJECTED,
702
+ message: error.message
703
+ }
704
+ };
705
+ console.error("EmDash: content:beforeSave hook failed:", error);
706
+ return {
707
+ success: false,
708
+ error: {
709
+ code: ErrorCode.CONTENT_HOOK_ERROR,
710
+ message: "A plugin hook failed while saving content"
711
+ }
712
+ };
713
+ }
714
+ /**
1826
715
  * Convert a ContentItem to Record<string, unknown> for hook consumption.
1827
716
  * Hooks receive the full item as a flat record.
1828
717
  */
@@ -1905,49 +794,6 @@ const marketplaceManifestCache = /* @__PURE__ */ new Map();
1905
794
  /** Route metadata for sandboxed plugins: pluginId -> routeName -> RouteMeta */
1906
795
  const sandboxedRouteMetaCache = /* @__PURE__ */ new Map();
1907
796
  let sandboxRunner = null;
1908
- const MEDIA_USAGE_MAINTENANCE_QUERY_RESERVATIONS = Object.freeze({
1909
- entryWork: MEDIA_USAGE_WORK_PROCESSING_LIMITS.ordinaryStatementsPerJob,
1910
- collectionDeletion: MEDIA_USAGE_COLLECTION_DELETION_LIMITS.maxQueriesPerTick,
1911
- reconciliation: MEDIA_USAGE_RECONCILIATION_LIMITS.maxQueriesPerTick,
1912
- maxClassQueries: Math.max(MEDIA_USAGE_WORK_PROCESSING_LIMITS.ordinaryStatementsPerJob, MEDIA_USAGE_COLLECTION_DELETION_LIMITS.maxQueriesPerTick, MEDIA_USAGE_RECONCILIATION_LIMITS.maxQueriesPerTick),
1913
- eventCeiling: 40
1914
- });
1915
- async function runScheduledMediaUsageLane(db) {
1916
- if ((getRequestContext()?.metrics?.dbCount ?? 0) + 1 + MEDIA_USAGE_MAINTENANCE_QUERY_RESERVATIONS.maxClassQueries > MEDIA_USAGE_MAINTENANCE_QUERY_RESERVATIONS.eventCeiling) return {
1917
- outcome: "admission_closed",
1918
- taskClass: null,
1919
- turn: null
1920
- };
1921
- const activation = await db.updateTable("_emdash_media_usage_activation").set({ media_usage_maintenance_turn: sql`(media_usage_maintenance_turn + 1) % 3` }).where("task_key", "=", "incremental_capture").where("state", "=", "active").returning("media_usage_maintenance_turn").executeTakeFirst();
1922
- if (!activation) return {
1923
- outcome: "inactive",
1924
- taskClass: null,
1925
- turn: null
1926
- };
1927
- const turn = activation.media_usage_maintenance_turn;
1928
- if (turn === 0) {
1929
- await processDueMediaUsageWork(db);
1930
- return {
1931
- outcome: "processed",
1932
- taskClass: "entry_work",
1933
- turn
1934
- };
1935
- }
1936
- if (turn === 1) {
1937
- await processDueMediaUsageCollectionDeletions(db);
1938
- return {
1939
- outcome: "processed",
1940
- taskClass: "collection_deletion",
1941
- turn
1942
- };
1943
- }
1944
- await processDueMediaUsageReconciliation(db);
1945
- return {
1946
- outcome: "processed",
1947
- taskClass: "reconciliation",
1948
- turn
1949
- };
1950
- }
1951
797
  /**
1952
798
  * EmDashRuntime - singleton per worker
1953
799
  */
@@ -2115,9 +961,6 @@ var EmDashRuntime = class EmDashRuntime {
2115
961
  await recordSchedulerHeartbeatSafely(this.db);
2116
962
  return { published };
2117
963
  }
2118
- async runScheduledMediaUsageTasks() {
2119
- return runScheduledMediaUsageLane(this.db);
2120
- }
2121
964
  /**
2122
965
  * Materialize plugin-declared storage indexes, once per process.
2123
966
  *
@@ -2417,7 +1260,7 @@ var EmDashRuntime = class EmDashRuntime {
2417
1260
  });
2418
1261
  }
2419
1262
  };
2420
- const db = await phase("rt.db", "DB init + migrations", () => EmDashRuntime.getDatabase(deps));
1263
+ const db = await phase("rt.db", "DB init + migration policy", () => EmDashRuntime.getDatabase(deps));
2421
1264
  const resolveDb = () => {
2422
1265
  return getRequestContext()?.db ?? db;
2423
1266
  };
@@ -2436,6 +1279,14 @@ var EmDashRuntime = class EmDashRuntime {
2436
1279
  const ownsConfiguredDb = !!deps.config.database && !(reqCtx?.dbIsIsolated && reqCtx.db);
2437
1280
  let readDb = db;
2438
1281
  let readDbDisposable;
1282
+ const disposeReadDb = async () => {
1283
+ const disposable = readDbDisposable;
1284
+ readDbDisposable = void 0;
1285
+ if (!disposable) return;
1286
+ try {
1287
+ await disposable.destroy();
1288
+ } catch {}
1289
+ };
2439
1290
  if (ownsConfiguredDb && deps.createCoalescingDialect && deps.config.database) try {
2440
1291
  const dialect = deps.createCoalescingDialect(deps.config.database.config);
2441
1292
  if (dialect) {
@@ -2449,6 +1300,10 @@ var EmDashRuntime = class EmDashRuntime {
2449
1300
  readDb = db;
2450
1301
  }
2451
1302
  const optionsRepo = new OptionsRepository(readDb);
1303
+ let missingManualSchemaError;
1304
+ const captureMissingManualSchema = (error) => {
1305
+ if ((deps.migrationMode ?? "auto") === "manual" && isMissingTableError(error)) missingManualSchemaError ??= error;
1306
+ };
2452
1307
  const readSiteInfo = async () => {
2453
1308
  const siteOpts = await optionsRepo.getMany([
2454
1309
  "emdash:site_title",
@@ -2468,11 +1323,15 @@ var EmDashRuntime = class EmDashRuntime {
2468
1323
  try {
2469
1324
  const states = await readDb.selectFrom("_plugin_state").select(["plugin_id", "status"]).execute();
2470
1325
  pluginStates = new Map(states.map((s) => [s.plugin_id, s.status]));
2471
- } catch {}
1326
+ } catch (error) {
1327
+ captureMissingManualSchema(error);
1328
+ }
2472
1329
  }), phase("rt.site", "Site info options", async () => {
2473
1330
  try {
2474
1331
  siteInfo = await readSiteInfo();
2475
- } catch {}
1332
+ } catch (error) {
1333
+ captureMissingManualSchema(error);
1334
+ }
2476
1335
  })];
2477
1336
  if (ownsConfiguredDb) coldStartReads.push(phase("rt.seedcheck", "Auto-seed gate", async () => {
2478
1337
  try {
@@ -2488,21 +1347,33 @@ var EmDashRuntime = class EmDashRuntime {
2488
1347
  collectionCount: collectionCount.count,
2489
1348
  setupDone
2490
1349
  };
2491
- } catch {}
1350
+ } catch (error) {
1351
+ captureMissingManualSchema(error);
1352
+ }
2492
1353
  }));
2493
1354
  await Promise.all(coldStartReads);
1355
+ if (missingManualSchemaError) {
1356
+ await disposeReadDb();
1357
+ throw missingManualSchemaError;
1358
+ }
2494
1359
  if (localeCasingRepairVersion && (configuredLocales.some((locale) => locale.includes("-") || locale !== locale.toLowerCase()) || storedLocaleCasingRepairVersion !== void 0) && storedLocaleCasingRepairVersion !== localeCasingRepairVersion) await phase("rt.locale", "Repair locale casing", async () => {
2495
1360
  await repairLocaleCasing(db, configuredLocales);
2496
1361
  await new OptionsRepository(db).set(LOCALE_CASING_REPAIR_OPTION, localeCasingRepairVersion);
2497
1362
  });
2498
1363
  if (seedGate.collectionCount === 0 && !seedGate.setupDone) {
1364
+ try {
1365
+ if ((await activateMediaUsageCapture(db, { writersDrained: true })).outcome !== "active") throw new Error("Fresh-site media usage activation did not complete");
1366
+ } catch (error) {
1367
+ await disposeReadDb();
1368
+ throw error;
1369
+ }
2499
1370
  const seedKey = deps.config.database?.entrypoint ?? "default";
2500
1371
  const seedHolder = getSeedHolder();
2501
1372
  try {
2502
1373
  await initWithLock(seedHolder.lock, () => seedHolder.done.has(seedKey) ? true : void 0, async () => {
2503
- const { applySeed } = await import("../apply-D5zfeD09.mjs").then((n) => n.n);
2504
- const { loadSeed } = await import("../load-DmvJrtxS.mjs").then((n) => n.r);
2505
- const { validateSeed } = await import("../validate-BkqIG_Uj.mjs").then((n) => n.n);
1374
+ const { applySeed } = await import("../apply-B_VMpJ2P.mjs").then((n) => n.n);
1375
+ const { loadSeed } = await import("../load-Bo7fZAbg.mjs").then((n) => n.r);
1376
+ const { validateSeed } = await import("../validate-Dd-IUjnZ.mjs").then((n) => n.n);
2506
1377
  const seed = await loadSeed();
2507
1378
  if (validateSeed(seed).valid) {
2508
1379
  await applySeed(db, seed, { onConflict: "skip" });
@@ -2519,9 +1390,7 @@ var EmDashRuntime = class EmDashRuntime {
2519
1390
  } catch {}
2520
1391
  } catch {}
2521
1392
  }
2522
- if (readDbDisposable) try {
2523
- await readDbDisposable.destroy();
2524
- } catch {}
1393
+ await disposeReadDb();
2525
1394
  const enabledPlugins = /* @__PURE__ */ new Set();
2526
1395
  for (const plugin of deps.plugins) {
2527
1396
  const status = pluginStates.get(plugin.id);
@@ -2634,11 +1503,6 @@ var EmDashRuntime = class EmDashRuntime {
2634
1503
  if (deps.createScheduler) {
2635
1504
  const scheduler = deps.createScheduler(cronExecutor);
2636
1505
  cronScheduler = scheduler;
2637
- const runMediaUsageMaintenance = async () => {
2638
- const runtime = runtimeRef.current;
2639
- if (runtime) await runtime.runScheduledMediaUsageTasks();
2640
- else await runScheduledMediaUsageLane(db);
2641
- };
2642
1506
  scheduler.setSystemCleanup(async () => {
2643
1507
  try {
2644
1508
  const runtime = runtimeRef.current;
@@ -2662,13 +1526,7 @@ var EmDashRuntime = class EmDashRuntime {
2662
1526
  }
2663
1527
  await maybeRunScheduledBackup(db, storage ?? void 0);
2664
1528
  await recordSchedulerHeartbeatSafely(db);
2665
- if (!scheduler.setMediaUsageMaintenance) try {
2666
- await runMediaUsageMaintenance();
2667
- } catch (error) {
2668
- console.error("[media-usage] Scheduled maintenance failed:", error);
2669
- }
2670
1529
  });
2671
- scheduler.setMediaUsageMaintenance?.(runMediaUsageMaintenance);
2672
1530
  scheduler.start();
2673
1531
  }
2674
1532
  } catch (error) {
@@ -2743,9 +1601,9 @@ var EmDashRuntime = class EmDashRuntime {
2743
1601
  log: kyselyLogOption()
2744
1602
  });
2745
1603
  try {
2746
- await runMigrations(db);
1604
+ await enforceRuntimeMigrationPolicy(db, deps.migrationMode ?? "auto");
2747
1605
  } catch (error) {
2748
- if (!(error instanceof ConcurrentMigrationTimeoutError)) holder.failures.set(cacheKey, {
1606
+ if (!(error instanceof ConcurrentMigrationTimeoutError) && !(error instanceof PendingMigrationsError)) holder.failures.set(cacheKey, {
2749
1607
  at: Date.now(),
2750
1608
  message: error instanceof Error ? error.message : String(error)
2751
1609
  });
@@ -3330,10 +2188,14 @@ var EmDashRuntime = class EmDashRuntime {
3330
2188
  }
3331
2189
  async handleContentCreate(collection, body) {
3332
2190
  let processedData = body.data;
3333
- if (this.hooks.hasHooks("content:beforeSave")) processedData = (await this.hooks.runContentBeforeSave(body.data, collection, true)).content;
2191
+ if (this.hooks.hasHooks("content:beforeSave")) try {
2192
+ processedData = (await this.hooks.runContentBeforeSave(body.data, collection, true)).content;
2193
+ } catch (error) {
2194
+ return beforeSaveFailure(error);
2195
+ }
3334
2196
  processedData = await this.runSandboxedBeforeSave(processedData, collection, true);
3335
2197
  processedData = await this.normalizeMediaFields(collection, processedData);
3336
- const { validateContentData } = await import("../validation-CiJb_X5o.mjs");
2198
+ const { validateContentData } = await import("../validation-D83n7Npi.mjs");
3337
2199
  const validation = await validateContentData(this.db, collection, processedData, { partial: false });
3338
2200
  if (!validation.ok) return {
3339
2201
  success: false,
@@ -3373,10 +2235,14 @@ var EmDashRuntime = class EmDashRuntime {
3373
2235
  const { _rev: _discardedRev, ...bodyWithoutRev } = body;
3374
2236
  let processedData = bodyWithoutRev.data;
3375
2237
  if (bodyWithoutRev.data) {
3376
- if (this.hooks.hasHooks("content:beforeSave")) processedData = (await this.hooks.runContentBeforeSave(bodyWithoutRev.data, collection, false)).content;
2238
+ if (this.hooks.hasHooks("content:beforeSave")) try {
2239
+ processedData = (await this.hooks.runContentBeforeSave(bodyWithoutRev.data, collection, false)).content;
2240
+ } catch (error) {
2241
+ return beforeSaveFailure(error);
2242
+ }
3377
2243
  processedData = await this.runSandboxedBeforeSave(processedData, collection, false);
3378
2244
  processedData = await this.normalizeMediaFields(collection, processedData);
3379
- const { validateContentData } = await import("../validation-CiJb_X5o.mjs");
2245
+ const { validateContentData } = await import("../validation-D83n7Npi.mjs");
3380
2246
  const validation = await validateContentData(this.db, collection, processedData, { partial: true });
3381
2247
  if (!validation.ok) return {
3382
2248
  success: false,
@@ -3692,11 +2558,11 @@ var EmDashRuntime = class EmDashRuntime {
3692
2558
  }
3693
2559
  async refreshContentUsageAfterSuccessfulWrite(collection, contentIds) {
3694
2560
  for (const contentId of new Set(contentIds)) try {
3695
- if ((await processMediaUsageWorkAfterWrite(this.db, collection, contentId)).outcome !== "inactive") return;
2561
+ if ((await processMediaUsageWorkAfterWrite(this.db, collection, contentId)).outcome !== "inactive") continue;
3696
2562
  await refreshContentMediaUsageAfterWrite(this.db, collection, contentId);
3697
2563
  } catch (error) {
3698
2564
  console.error(`[media-usage] Failed after content write ${collection}/${contentId}:`, error);
3699
- return;
2565
+ continue;
3700
2566
  }
3701
2567
  }
3702
2568
  async deleteContentUsageAfterSuccessfulPermanentDelete(collection, contentId) {
@@ -3917,7 +2783,7 @@ var EmDashRuntime = class EmDashRuntime {
3917
2783
  const value = result[field.slug];
3918
2784
  if (value == null) continue;
3919
2785
  try {
3920
- const normalized = await normalizeMediaValue(value, getProvider);
2786
+ const normalized = field.type === "image" ? await normalizeImageValue(value, getProvider) : await normalizeMediaValue(value, getProvider);
3921
2787
  if (normalized) result[field.slug] = normalized;
3922
2788
  } catch {}
3923
2789
  }
@@ -3934,7 +2800,7 @@ var EmDashRuntime = class EmDashRuntime {
3934
2800
  const subValue = normalizedItem[slug];
3935
2801
  if (subValue == null) continue;
3936
2802
  try {
3937
- const normalized = await normalizeMediaValue(subValue, getProvider);
2803
+ const normalized = await normalizeImageValue(subValue, getProvider);
3938
2804
  if (normalized) normalizedItem[slug] = normalized;
3939
2805
  } catch {}
3940
2806
  }
@@ -3960,7 +2826,7 @@ var EmDashRuntime = class EmDashRuntime {
3960
2826
  result = record;
3961
2827
  }
3962
2828
  } catch (error) {
3963
- console.error(`EmDash: Sandboxed plugin ${id} beforeSave hook error:`, error);
2829
+ console.error(`EmDash: Sandboxed plugin ${id} beforeSave hook threw; a sandboxed plugin cannot cancel a save, so the save continues:`, error);
3964
2830
  }
3965
2831
  }
3966
2832
  return result;
@@ -4193,6 +3059,33 @@ var EmDashRuntime = class EmDashRuntime {
4193
3059
  return status === void 0 || status === "active";
4194
3060
  }
4195
3061
  };
3062
+ /**
3063
+ * Normalize an image field value together with its `darkVariant`. The media
3064
+ * normalizer only keeps the media item's own keys, so the variant is normalized
3065
+ * separately and reattached.
3066
+ */
3067
+ async function normalizeImageValue(value, getProvider) {
3068
+ const primary = await normalizePrimaryImageValue(value, getProvider);
3069
+ if (!primary || !isRecord(value) || value.darkVariant == null) return primary;
3070
+ const darkVariant = await normalizeMediaValue(value.darkVariant, getProvider);
3071
+ return darkVariant ? {
3072
+ ...primary,
3073
+ darkVariant
3074
+ } : primary;
3075
+ }
3076
+ /**
3077
+ * Normalize the primary image of an image field value.
3078
+ *
3079
+ * A legacy string URL that the admin upgraded to `{ id: "", src: url }` so it
3080
+ * can carry a dark variant still has to normalize as a string: as an object it
3081
+ * counts as local media, which strips `src` and leaves nothing behind. The
3082
+ * upgrade outlives the variant — an editor can add one and remove it again —
3083
+ * so the shape decides, not the presence of `darkVariant`.
3084
+ */
3085
+ async function normalizePrimaryImageValue(value, getProvider) {
3086
+ if (isRecord(value) && !value.id && typeof value.src === "string" && !value.provider) return normalizeMediaValue(value.src, getProvider);
3087
+ return normalizeMediaValue(value, getProvider);
3088
+ }
4196
3089
 
4197
3090
  //#endregion
4198
3091
  //#region src/media/url.ts
@@ -4234,6 +3127,21 @@ function createPublicMediaUrlResolver(storage) {
4234
3127
  * middleware.ts.
4235
3128
  */
4236
3129
  const ASTRO_COOKIES_SYMBOL = Symbol.for("astro.cookies");
3130
+ /**
3131
+ * Whether the request ended authenticated, for the db adapter's `commit()`.
3132
+ * `isAuthenticated` is captured before the route runs, so it misses
3133
+ * login/signup/invite requests that create the Astro session mid-request;
3134
+ * `session.set()` writes the `astro-session` cookie into the jar synchronously,
3135
+ * so scanning the outgoing cookies detects them. Only outgoing cookies count —
3136
+ * a stale `astro-session` cookie arriving on an anonymous render must not
3137
+ * qualify, or its replica-read bookmark would overwrite a fresher one from an
3138
+ * earlier authenticated write.
3139
+ */
3140
+ function requestEndedAuthenticated(isAuthenticated, cookies) {
3141
+ if (isAuthenticated) return true;
3142
+ for (const header of cookies.headers()) if (header.startsWith("astro-session=")) return true;
3143
+ return false;
3144
+ }
4237
3145
  /** Hold the real adapter close behind both response and deferred-task completion. */
4238
3146
  function coordinateScopedDbLifecycle(scoped) {
4239
3147
  if (!scoped.close) return { lifecycle: scoped };
@@ -4580,10 +3488,11 @@ function getPlugins() {
4580
3488
  /**
4581
3489
  * Build runtime dependencies from virtual modules
4582
3490
  */
4583
- function buildDependencies(config) {
3491
+ function buildDependencies(config, migrationMode) {
4584
3492
  const sandboxModule = virtualSandboxRunnerModule;
4585
3493
  return {
4586
3494
  config,
3495
+ migrationMode,
4587
3496
  plugins: getPlugins(),
4588
3497
  createDialect,
4589
3498
  createCoalescingDialect,
@@ -4604,10 +3513,10 @@ function buildDependencies(config) {
4604
3513
  * cached instance) push nothing — callers should treat an empty array
4605
3514
  * as "warm, nothing to report".
4606
3515
  */
4607
- async function getRuntime(config, initTimings) {
3516
+ async function getRuntime(config, migrationMode, initTimings) {
4608
3517
  const holder = getRuntimeHolder();
4609
3518
  return initWithLock(holder.lock, () => holder.instance, async (isCurrentClaim) => {
4610
- const deps = buildDependencies(config);
3519
+ const deps = buildDependencies(config, migrationMode);
4611
3520
  const runtime = await EmDashRuntime.create(deps, initTimings);
4612
3521
  if (isCurrentClaim()) holder.instance = runtime;
4613
3522
  else runtime.stopCron().catch((error) => {
@@ -4638,15 +3547,6 @@ async function runScheduledTasks(options = {}) {
4638
3547
  if (!config) return { published: [] };
4639
3548
  return runOutsideRequest(config, (runtime) => runtime.runScheduledTasks(options));
4640
3549
  }
4641
- async function runScheduledMediaUsageTasks() {
4642
- const config = getConfig();
4643
- if (!config) return {
4644
- outcome: "inactive",
4645
- taskClass: null,
4646
- turn: null
4647
- };
4648
- return runOutsideRequest(config, (runtime) => runtime.runScheduledMediaUsageTasks());
4649
- }
4650
3550
  /**
4651
3551
  * Run a callback against the EmDash runtime outside any HTTP request — from a
4652
3552
  * Cloudflare Queue consumer, a `scheduled()` handler, or any other
@@ -4702,7 +3602,8 @@ async function withEmDashRuntime(run) {
4702
3602
  * event scoping return null and keep using the singleton.
4703
3603
  */
4704
3604
  async function runOutsideRequest(config, fn) {
4705
- if (getRequestContext()) return runOutsideRequestWithRuntime(config, await getRuntime(config), fn);
3605
+ const migrationMode = resolveConfiguredMigrationMode(config);
3606
+ if (getRequestContext()) return runOutsideRequestWithRuntime(config, await getRuntime(config, migrationMode), fn);
4706
3607
  const deferredTasks = createDeferredTaskTracker(() => {});
4707
3608
  return runWithContext({
4708
3609
  editMode: false,
@@ -4711,7 +3612,7 @@ async function runOutsideRequest(config, fn) {
4711
3612
  }, async () => {
4712
3613
  return runOutsideRequestWithRuntime(config, await (async () => {
4713
3614
  try {
4714
- return await getRuntime(config);
3615
+ return await getRuntime(config, migrationMode);
4715
3616
  } finally {
4716
3617
  deferredTasks.settle();
4717
3618
  await deferredTasks.settled;
@@ -4774,6 +3675,24 @@ const NOOP_COOKIE_JAR = {
4774
3675
  * that path, so the value is never used — it exists to satisfy the contract.
4775
3676
  */
4776
3677
  const CRON_EVENT_URL = new URL("https://cron.emdash.internal/");
3678
+ function resolveConfiguredMigrationMode(config) {
3679
+ const processOverride = typeof process !== "undefined" && process.env ? process.env.EMDASH_MIGRATIONS_MODE : void 0;
3680
+ const importMetaOverride = import.meta.env.EMDASH_MIGRATIONS_MODE;
3681
+ return resolveRuntimeMigrationMode(config.migrations, {
3682
+ dev: import.meta.env.DEV,
3683
+ override: processOverride ?? importMetaOverride
3684
+ });
3685
+ }
3686
+ function pendingMigrationsResponse(error) {
3687
+ console.error("[emdash] database migrations are pending:", error.pending.join(", "));
3688
+ return migrationRequiredResponse();
3689
+ }
3690
+ function migrationRequiredResponse() {
3691
+ return new Response("Database migrations are required. Apply the deployment migration manifest and retry.", {
3692
+ status: 503,
3693
+ headers: { "Retry-After": "60" }
3694
+ });
3695
+ }
4777
3696
  /**
4778
3697
  * Baseline security headers applied to all responses.
4779
3698
  * Admin routes get additional headers (strict CSP) from auth middleware.
@@ -4889,6 +3808,8 @@ const onRequest = defineMiddleware(async (context, next) => {
4889
3808
  const queryRecorder = isInstrumentationEnabled() ? createRecorder(url.pathname, request.method, request.headers.get("x-perf-phase") ?? "default") : void 0;
4890
3809
  const metrics = createRequestMetrics(performance.now());
4891
3810
  const run = async () => {
3811
+ const config = getConfig();
3812
+ const migrationMode = config ? resolveConfiguredMigrationMode(config) : "auto";
4892
3813
  const isEmDashRoute = url.pathname.startsWith("/_emdash");
4893
3814
  const isPublicRuntimeRoute = PUBLIC_RUNTIME_ROUTES.has(url.pathname) || SITEMAP_COLLECTION_RE.test(url.pathname);
4894
3815
  const hasEditCookie = cookies.get("emdash-edit-mode")?.value === "true";
@@ -4899,15 +3820,16 @@ const onRequest = defineMiddleware(async (context, next) => {
4899
3820
  const hasBearerAuth = (request.headers.get("authorization") ?? "").toLowerCase().startsWith("bearer ");
4900
3821
  const isWrite = request.method !== "GET" && request.method !== "HEAD";
4901
3822
  const isAuthenticated = !!sessionUser || hasBearerAuth;
3823
+ const endedAuthenticated = () => requestEndedAuthenticated(isAuthenticated, cookies);
4902
3824
  const canUseCachedBinding = !isAuthenticated && !isWrite && !playgroundDb && !isEmDashRoute && !hasEditCookie && !hasPreviewToken;
4903
3825
  if (!isEmDashRoute && !isPublicRuntimeRoute && !hasEditCookie && !hasPreviewToken) {
4904
3826
  if (!sessionUser && !playgroundDb) {
4905
3827
  const timings = [];
4906
3828
  const mwStart = performance.now();
4907
- if (!isSetupVerified() && !context.isPrerendered) {
3829
+ if (migrationMode === "auto" && !isSetupVerified() && !context.isPrerendered) {
4908
3830
  const t0 = performance.now();
4909
3831
  try {
4910
- const { getDb } = await import("../loader-DLBqRiGP.mjs").then((n) => n.c);
3832
+ const { getDb } = await import("../loader-ZsjE4paL.mjs").then((n) => n.c);
4911
3833
  await (await getDb()).selectFrom("_emdash_migrations").selectAll().limit(1).execute();
4912
3834
  markSetupVerified();
4913
3835
  } catch (error) {
@@ -4920,12 +3842,11 @@ const onRequest = defineMiddleware(async (context, next) => {
4920
3842
  desc: "Setup probe"
4921
3843
  });
4922
3844
  }
4923
- const config = getConfig();
4924
3845
  if (config) {
4925
3846
  const initSubTimings = [];
4926
3847
  const t0 = performance.now();
4927
3848
  try {
4928
- const runtime = await getRuntime(config, initSubTimings);
3849
+ const runtime = await getRuntime(config, migrationMode, initSubTimings);
4929
3850
  markSetupVerified();
4930
3851
  locals.emdash = {
4931
3852
  handlePublicPluginApiRoute: createPublicPluginApiRouteHandler(runtime),
@@ -4935,6 +3856,11 @@ const onRequest = defineMiddleware(async (context, next) => {
4935
3856
  storage: runtime.storage
4936
3857
  };
4937
3858
  } catch (error) {
3859
+ if (error instanceof PendingMigrationsError) return pendingMigrationsResponse(error);
3860
+ if (migrationMode === "manual" && isMissingTableError(error)) {
3861
+ console.error("[emdash] database schema is unavailable in manual migration mode:", error);
3862
+ return migrationRequiredResponse();
3863
+ }
4938
3864
  if (Date.now() - lastRuntimeInitErrorLogAt >= RUNTIME_INIT_ERROR_LOG_INTERVAL_MS) {
4939
3865
  lastRuntimeInitErrorLogAt = Date.now();
4940
3866
  console.error("[emdash] runtime init failed (page renders without CMS data):", error);
@@ -4951,6 +3877,7 @@ const onRequest = defineMiddleware(async (context, next) => {
4951
3877
  const anonScoped = createRequestScopedDb$1({
4952
3878
  config: config?.database?.config,
4953
3879
  isAuthenticated,
3880
+ endedAuthenticated,
4954
3881
  isWrite,
4955
3882
  canUseCachedBinding,
4956
3883
  cookies,
@@ -4995,7 +3922,6 @@ const onRequest = defineMiddleware(async (context, next) => {
4995
3922
  return runAnon();
4996
3923
  }
4997
3924
  }
4998
- const config = getConfig();
4999
3925
  if (!config) {
5000
3926
  console.error("EmDash: No configuration found");
5001
3927
  return finalizeResponse(await next());
@@ -5006,7 +3932,7 @@ const onRequest = defineMiddleware(async (context, next) => {
5006
3932
  try {
5007
3933
  const initSubTimings = [];
5008
3934
  let t0 = performance.now();
5009
- const runtime = await getRuntime(config, initSubTimings);
3935
+ const runtime = await getRuntime(config, migrationMode, initSubTimings);
5010
3936
  timings.push({
5011
3937
  name: "rt",
5012
3938
  dur: performance.now() - t0,
@@ -5075,12 +4001,18 @@ const onRequest = defineMiddleware(async (context, next) => {
5075
4001
  setPluginStatus: runtime.setPluginStatus.bind(runtime)
5076
4002
  };
5077
4003
  } catch (error) {
4004
+ if (error instanceof PendingMigrationsError) return pendingMigrationsResponse(error);
4005
+ if (migrationMode === "manual" && isMissingTableError(error)) {
4006
+ console.error("[emdash] database schema is unavailable in manual migration mode:", error);
4007
+ return migrationRequiredResponse();
4008
+ }
5078
4009
  console.error("EmDash middleware error:", error);
5079
4010
  }
5080
4011
  const lastContentWriteAt = canUseCachedBinding && config?.database?.needsLastContentWriteAt ? await getLastContentWriteAt() : void 0;
5081
4012
  const scoped = createRequestScopedDb$1({
5082
4013
  config: config?.database?.config,
5083
4014
  isAuthenticated,
4015
+ endedAuthenticated,
5084
4016
  isWrite,
5085
4017
  canUseCachedBinding,
5086
4018
  cookies: context.cookies,
@@ -5148,5 +4080,5 @@ const onRequest = defineMiddleware(async (context, next) => {
5148
4080
  });
5149
4081
 
5150
4082
  //#endregion
5151
- export { onRequest as default, onRequest, runScheduledMediaUsageTasks, runScheduledTasks, withEmDashRuntime };
4083
+ export { onRequest as default, onRequest, runScheduledTasks, withEmDashRuntime };
5152
4084
  //# sourceMappingURL=middleware.mjs.map