emdash 0.35.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 (763) hide show
  1. package/dist/activation-K0kGBWuJ.mjs +382 -0
  2. package/dist/activation-K0kGBWuJ.mjs.map +1 -0
  3. package/dist/{adapters-Bb1vG41D.d.mts → adapters-CzL3H752.d.mts} +3 -3
  4. package/dist/{adapters-Bb1vG41D.d.mts.map → adapters-CzL3H752.d.mts.map} +1 -1
  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-KO24G1Fd.mjs → api-tokens-9YrrCoE6.mjs} +2 -2
  15. package/dist/{api-tokens-KO24G1Fd.mjs.map → api-tokens-9YrrCoE6.mjs.map} +1 -1
  16. package/dist/{apply-kUDt80-G.mjs → apply-B_VMpJ2P.mjs} +34 -35
  17. package/dist/{apply-kUDt80-G.mjs.map → apply-B_VMpJ2P.mjs.map} +1 -1
  18. package/dist/astro/index.d.mts +12 -12
  19. package/dist/astro/index.mjs +26 -15
  20. package/dist/astro/index.mjs.map +1 -1
  21. package/dist/astro/middleware/auth.d.mts +10 -10
  22. package/dist/astro/middleware/auth.d.mts.map +1 -1
  23. package/dist/astro/middleware/auth.mjs +10 -7
  24. package/dist/astro/middleware/auth.mjs.map +1 -1
  25. package/dist/astro/middleware/media-usage-write-fence.mjs +2 -2
  26. package/dist/astro/middleware/redirect.mjs +4 -4
  27. package/dist/astro/middleware/request-context.mjs +3 -3
  28. package/dist/astro/middleware/setup.mjs +1 -1
  29. package/dist/astro/middleware.d.mts +11 -12
  30. package/dist/astro/middleware.d.mts.map +1 -1
  31. package/dist/astro/middleware.mjs +168 -1305
  32. package/dist/astro/middleware.mjs.map +1 -1
  33. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +6 -6
  34. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +6 -6
  35. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +3 -3
  36. package/dist/astro/routes/api/admin/api-tokens/index.mjs +4 -4
  37. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +4 -4
  38. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +9 -9
  39. package/dist/astro/routes/api/admin/byline-fields/index.mjs +9 -9
  40. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +9 -9
  41. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +15 -15
  42. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +15 -15
  43. package/dist/astro/routes/api/admin/bylines/index.mjs +15 -15
  44. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +12 -12
  45. package/dist/astro/routes/api/admin/comments/_id_.mjs +4 -4
  46. package/dist/astro/routes/api/admin/comments/bulk.mjs +9 -9
  47. package/dist/astro/routes/api/admin/comments/counts.mjs +4 -4
  48. package/dist/astro/routes/api/admin/comments/index.mjs +9 -9
  49. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +4 -4
  50. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
  51. package/dist/astro/routes/api/admin/media-usage/activation.d.mts +9 -0
  52. package/dist/astro/routes/api/admin/media-usage/activation.d.mts.map +1 -0
  53. package/dist/astro/routes/api/admin/media-usage/activation.mjs +46 -0
  54. package/dist/astro/routes/api/admin/media-usage/activation.mjs.map +1 -0
  55. package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs +14 -13
  56. package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs.map +1 -1
  57. package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs +14 -13
  58. package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs.map +1 -1
  59. package/dist/astro/routes/api/admin/media-usage/progress.d.mts +9 -0
  60. package/dist/astro/routes/api/admin/media-usage/progress.d.mts.map +1 -0
  61. package/dist/astro/routes/api/admin/media-usage/progress.mjs +44 -0
  62. package/dist/astro/routes/api/admin/media-usage/progress.mjs.map +1 -0
  63. package/dist/astro/routes/api/admin/media-usage/repair.mjs +16 -11
  64. package/dist/astro/routes/api/admin/media-usage/repair.mjs.map +1 -1
  65. package/dist/astro/routes/api/admin/media-usage/work/index.mjs +14 -13
  66. package/dist/astro/routes/api/admin/media-usage/work/index.mjs.map +1 -1
  67. package/dist/astro/routes/api/admin/media-usage/work/retry.mjs +14 -13
  68. package/dist/astro/routes/api/admin/media-usage/work/retry.mjs.map +1 -1
  69. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +3 -3
  70. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +3 -3
  71. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +54 -48
  72. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
  73. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +54 -48
  74. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
  75. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +52 -46
  76. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
  77. package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +4 -4
  78. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +52 -46
  79. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs.map +1 -1
  80. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +53 -47
  81. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
  82. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +53 -47
  83. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
  84. package/dist/astro/routes/api/admin/plugins/index.mjs +52 -46
  85. package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
  86. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +2 -2
  87. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +52 -46
  88. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
  89. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +53 -47
  90. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
  91. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +52 -46
  92. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
  93. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +53 -47
  94. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
  95. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +54 -48
  96. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
  97. package/dist/astro/routes/api/admin/plugins/registry/artifact.d.mts.map +1 -1
  98. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +172 -133
  99. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
  100. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +54 -48
  101. package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
  102. package/dist/astro/routes/api/admin/plugins/updates.mjs +52 -46
  103. package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
  104. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +52 -46
  105. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
  106. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +2 -2
  107. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +52 -46
  108. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
  109. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +2 -2
  110. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +1 -1
  111. package/dist/astro/routes/api/admin/users/_id_/index.mjs +7 -7
  112. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +3 -3
  113. package/dist/astro/routes/api/admin/users/index.mjs +6 -6
  114. package/dist/astro/routes/api/auth/dev-bypass.mjs +3 -3
  115. package/dist/astro/routes/api/auth/invite/accept.mjs +1 -1
  116. package/dist/astro/routes/api/auth/invite/complete.mjs +11 -11
  117. package/dist/astro/routes/api/auth/invite/index.mjs +8 -8
  118. package/dist/astro/routes/api/auth/invite/register-options.mjs +10 -10
  119. package/dist/astro/routes/api/auth/logout.mjs +2 -2
  120. package/dist/astro/routes/api/auth/magic-link/send.mjs +10 -10
  121. package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
  122. package/dist/astro/routes/api/auth/me.d.mts.map +1 -1
  123. package/dist/astro/routes/api/auth/me.mjs +19 -9
  124. package/dist/astro/routes/api/auth/me.mjs.map +1 -1
  125. package/dist/astro/routes/api/auth/mode.mjs +2 -2
  126. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +4 -4
  127. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
  128. package/dist/astro/routes/api/auth/passkey/_id_.mjs +6 -6
  129. package/dist/astro/routes/api/auth/passkey/index.mjs +1 -1
  130. package/dist/astro/routes/api/auth/passkey/options.mjs +12 -12
  131. package/dist/astro/routes/api/auth/passkey/register/options.mjs +10 -10
  132. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +11 -11
  133. package/dist/astro/routes/api/auth/passkey/verify.mjs +11 -11
  134. package/dist/astro/routes/api/auth/signup/complete.mjs +11 -11
  135. package/dist/astro/routes/api/auth/signup/request.mjs +10 -10
  136. package/dist/astro/routes/api/auth/signup/verify.mjs +1 -1
  137. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +13 -13
  138. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +12 -12
  139. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +2 -2
  140. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +3 -3
  141. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
  142. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +2 -2
  143. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +11 -11
  144. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +8 -8
  145. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +16 -14
  146. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs.map +1 -1
  147. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs +16 -14
  148. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs.map +1 -1
  149. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
  150. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +2 -2
  151. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +8 -8
  152. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +13 -13
  153. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +2 -2
  154. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +3 -3
  155. package/dist/astro/routes/api/content/_collection_/_id_.mjs +8 -8
  156. package/dist/astro/routes/api/content/_collection_/authors.mjs +2 -2
  157. package/dist/astro/routes/api/content/_collection_/index.mjs +8 -8
  158. package/dist/astro/routes/api/content/_collection_/trash.mjs +7 -7
  159. package/dist/astro/routes/api/dashboard.mjs +7 -7
  160. package/dist/astro/routes/api/dev/emails.mjs +2 -2
  161. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  162. package/dist/astro/routes/api/import/probe.mjs +16 -16
  163. package/dist/astro/routes/api/import/wordpress/analyze.mjs +2 -2
  164. package/dist/astro/routes/api/import/wordpress/execute.d.mts +11 -11
  165. package/dist/astro/routes/api/import/wordpress/execute.mjs +12 -12
  166. package/dist/astro/routes/api/import/wordpress/media.d.mts +10 -10
  167. package/dist/astro/routes/api/import/wordpress/media.mjs +11 -11
  168. package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -9
  169. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +11 -11
  170. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +12 -10
  171. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs.map +1 -1
  172. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  173. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +16 -16
  174. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +11 -11
  175. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +26 -26
  176. package/dist/astro/routes/api/manifest.mjs +4 -4
  177. package/dist/astro/routes/api/mcp.mjs +42 -42
  178. package/dist/astro/routes/api/media/_id_/confirm.mjs +11 -11
  179. package/dist/astro/routes/api/media/_id_/upload.mjs +6 -6
  180. package/dist/astro/routes/api/media/_id_/usage.mjs +18 -13
  181. package/dist/astro/routes/api/media/_id_/usage.mjs.map +1 -1
  182. package/dist/astro/routes/api/media/_id_.d.mts.map +1 -1
  183. package/dist/astro/routes/api/media/_id_.mjs +21 -13
  184. package/dist/astro/routes/api/media/_id_.mjs.map +1 -1
  185. package/dist/astro/routes/api/media/file/_...key_.mjs +1 -1
  186. package/dist/astro/routes/api/media/folders/_id_.d.mts +10 -0
  187. package/dist/astro/routes/api/media/folders/_id_.d.mts.map +1 -0
  188. package/dist/astro/routes/api/media/folders/_id_.mjs +52 -0
  189. package/dist/astro/routes/api/media/folders/_id_.mjs.map +1 -0
  190. package/dist/astro/routes/api/media/folders/index.d.mts +9 -0
  191. package/dist/astro/routes/api/media/folders/index.d.mts.map +1 -0
  192. package/dist/astro/routes/api/media/folders/index.mjs +41 -0
  193. package/dist/astro/routes/api/media/folders/index.mjs.map +1 -0
  194. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +2 -2
  195. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +2 -2
  196. package/dist/astro/routes/api/media/providers/index.mjs +2 -2
  197. package/dist/astro/routes/api/media/upload-url.mjs +9 -9
  198. package/dist/astro/routes/api/media.d.mts.map +1 -1
  199. package/dist/astro/routes/api/media.mjs +28 -19
  200. package/dist/astro/routes/api/media.mjs.map +1 -1
  201. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +8 -8
  202. package/dist/astro/routes/api/menus/_name_/items.mjs +8 -8
  203. package/dist/astro/routes/api/menus/_name_/reorder.mjs +8 -8
  204. package/dist/astro/routes/api/menus/_name_/translations.mjs +8 -8
  205. package/dist/astro/routes/api/menus/_name_.mjs +8 -8
  206. package/dist/astro/routes/api/menus/index.mjs +8 -8
  207. package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
  208. package/dist/astro/routes/api/oauth/device/authorize.mjs +5 -5
  209. package/dist/astro/routes/api/oauth/device/code.mjs +8 -8
  210. package/dist/astro/routes/api/oauth/device/token.mjs +7 -7
  211. package/dist/astro/routes/api/oauth/register.mjs +2 -2
  212. package/dist/astro/routes/api/oauth/token/refresh.mjs +5 -5
  213. package/dist/astro/routes/api/oauth/token/revoke.mjs +5 -5
  214. package/dist/astro/routes/api/oauth/token.mjs +5 -5
  215. package/dist/astro/routes/api/openapi.json.mjs +153 -6
  216. package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
  217. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +4 -4
  218. package/dist/astro/routes/api/redirects/404s/index.mjs +10 -10
  219. package/dist/astro/routes/api/redirects/404s/summary.mjs +10 -10
  220. package/dist/astro/routes/api/redirects/_id_.mjs +11 -11
  221. package/dist/astro/routes/api/redirects/index.mjs +11 -11
  222. package/dist/astro/routes/api/relations/_id_/index.mjs +16 -14
  223. package/dist/astro/routes/api/relations/_id_/index.mjs.map +1 -1
  224. package/dist/astro/routes/api/relations/_id_/translations.mjs +11 -9
  225. package/dist/astro/routes/api/relations/_id_/translations.mjs.map +1 -1
  226. package/dist/astro/routes/api/relations/index.mjs +16 -14
  227. package/dist/astro/routes/api/relations/index.mjs.map +1 -1
  228. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +2 -2
  229. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
  230. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +52 -46
  231. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
  232. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +52 -46
  233. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs.map +1 -1
  234. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +52 -46
  235. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
  236. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +52 -46
  237. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
  238. package/dist/astro/routes/api/schema/collections/index.mjs +52 -46
  239. package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
  240. package/dist/astro/routes/api/schema/collections/reorder.mjs +52 -46
  241. package/dist/astro/routes/api/schema/collections/reorder.mjs.map +1 -1
  242. package/dist/astro/routes/api/schema/index.mjs +10 -8
  243. package/dist/astro/routes/api/schema/index.mjs.map +1 -1
  244. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +52 -46
  245. package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
  246. package/dist/astro/routes/api/schema/orphans/index.mjs +52 -46
  247. package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
  248. package/dist/astro/routes/api/search/enable.mjs +10 -10
  249. package/dist/astro/routes/api/search/index.mjs +9 -9
  250. package/dist/astro/routes/api/search/rebuild.mjs +10 -10
  251. package/dist/astro/routes/api/search/stats.mjs +5 -5
  252. package/dist/astro/routes/api/search/suggest.mjs +9 -9
  253. package/dist/astro/routes/api/sections/_slug_.mjs +9 -9
  254. package/dist/astro/routes/api/sections/index.mjs +9 -9
  255. package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +6 -6
  256. package/dist/astro/routes/api/settings/backups/archives/index.mjs +5 -5
  257. package/dist/astro/routes/api/settings/backups/export.mjs +6 -6
  258. package/dist/astro/routes/api/settings/backups/index.mjs +7 -7
  259. package/dist/astro/routes/api/settings/email.mjs +4 -4
  260. package/dist/astro/routes/api/settings.mjs +13 -13
  261. package/dist/astro/routes/api/setup/admin-verify.mjs +12 -12
  262. package/dist/astro/routes/api/setup/admin.mjs +11 -11
  263. package/dist/astro/routes/api/setup/dev-bypass.d.mts.map +1 -1
  264. package/dist/astro/routes/api/setup/dev-bypass.mjs +34 -27
  265. package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
  266. package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
  267. package/dist/astro/routes/api/setup/index.mjs +31 -29
  268. package/dist/astro/routes/api/setup/index.mjs.map +1 -1
  269. package/dist/astro/routes/api/setup/status.mjs +3 -3
  270. package/dist/astro/routes/api/snapshot.mjs +7 -7
  271. package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs +13 -13
  272. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +13 -13
  273. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +13 -13
  274. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +13 -13
  275. package/dist/astro/routes/api/taxonomies/_name_/translations.mjs +13 -13
  276. package/dist/astro/routes/api/taxonomies/_name_.mjs +13 -13
  277. package/dist/astro/routes/api/taxonomies/index.mjs +13 -13
  278. package/dist/astro/routes/api/themes/preview.mjs +5 -5
  279. package/dist/astro/routes/api/typegen.mjs +4 -4
  280. package/dist/astro/routes/api/well-known/auth.mjs +2 -2
  281. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
  282. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
  283. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +7 -7
  284. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +10 -10
  285. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +10 -10
  286. package/dist/astro/routes/api/widget-areas/_name_.mjs +5 -5
  287. package/dist/astro/routes/api/widget-areas/index.mjs +10 -10
  288. package/dist/astro/routes/api/widget-components.mjs +2 -2
  289. package/dist/astro/routes/robots.txt.mjs +6 -6
  290. package/dist/astro/routes/sitemap-_collection_.xml.mjs +9 -9
  291. package/dist/astro/routes/sitemap.xml.mjs +7 -7
  292. package/dist/astro/types.d.mts +19 -14
  293. package/dist/astro/types.d.mts.map +1 -1
  294. package/dist/auth/providers/github.d.mts +1 -1
  295. package/dist/auth/providers/google.d.mts +1 -1
  296. package/dist/{authorize-DUElxgOQ.mjs → authorize-ByYs-Uxs.mjs} +2 -2
  297. package/dist/{authorize-DUElxgOQ.mjs.map → authorize-ByYs-Uxs.mjs.map} +1 -1
  298. package/dist/{backup-DWYpJlLj.mjs → backup-ChyHfIoX.mjs} +5 -5
  299. package/dist/{backup-DWYpJlLj.mjs.map → backup-ChyHfIoX.mjs.map} +1 -1
  300. package/dist/{bundle-vn56sLos.mjs → bundle-DZFhFurR.mjs} +4 -4
  301. package/dist/{bundle-vn56sLos.mjs.map → bundle-DZFhFurR.mjs.map} +1 -1
  302. package/dist/{byline-yjvWq4ip.mjs → byline-Cpnwrje1.mjs} +6 -6
  303. package/dist/{byline-yjvWq4ip.mjs.map → byline-Cpnwrje1.mjs.map} +1 -1
  304. package/dist/{byline-fields-txaLGoME.mjs → byline-fields-CAyA_Umd.mjs} +2 -2
  305. package/dist/{byline-fields-txaLGoME.mjs.map → byline-fields-CAyA_Umd.mjs.map} +1 -1
  306. package/dist/{byline-registry-DDun8sxh.mjs → byline-registry-Bpu9-MVs.mjs} +2 -2
  307. package/dist/{byline-registry-DDun8sxh.mjs.map → byline-registry-Bpu9-MVs.mjs.map} +1 -1
  308. package/dist/{bylines-DjXS5p0P.mjs → bylines-BSo9B9RX.mjs} +7 -7
  309. package/dist/{bylines-DjXS5p0P.mjs.map → bylines-BSo9B9RX.mjs.map} +1 -1
  310. package/dist/{bylines-Bu7pchQ4.mjs → bylines-CHjvehVX.mjs} +3 -3
  311. package/dist/{bylines-Bu7pchQ4.mjs.map → bylines-CHjvehVX.mjs.map} +1 -1
  312. package/dist/{cache-C46-3Irl.mjs → cache-5VirJ2Cn.mjs} +4 -4
  313. package/dist/{cache-C46-3Irl.mjs.map → cache-5VirJ2Cn.mjs.map} +1 -1
  314. package/dist/capture-triggers-CNW1q3HK.mjs +362 -0
  315. package/dist/capture-triggers-CNW1q3HK.mjs.map +1 -0
  316. package/dist/{challenge-store-SIL5DsF2.mjs → challenge-store-D5pSY2S1.mjs} +1 -1
  317. package/dist/{challenge-store-SIL5DsF2.mjs.map → challenge-store-D5pSY2S1.mjs.map} +1 -1
  318. package/dist/{chunks-fiZ8G9RJ.mjs → chunks-bNiX6_DR.mjs} +2 -2
  319. package/dist/{chunks-fiZ8G9RJ.mjs.map → chunks-bNiX6_DR.mjs.map} +1 -1
  320. package/dist/cli/index.mjs +36 -52
  321. package/dist/cli/index.mjs.map +1 -1
  322. package/dist/client/cf-access.d.mts +1 -1
  323. package/dist/client/index.d.mts +74 -4
  324. package/dist/client/index.d.mts.map +1 -1
  325. package/dist/client/index.mjs +54 -2
  326. package/dist/client/index.mjs.map +1 -1
  327. package/dist/{collection-deletion-DQPvMD8R.mjs → collection-deletion-m_oi2c0N.mjs} +13 -356
  328. package/dist/collection-deletion-m_oi2c0N.mjs.map +1 -0
  329. package/dist/{comment-Dxlep6cE.mjs → comment-DhvLEdhQ.mjs} +1 -1
  330. package/dist/{comment-Dxlep6cE.mjs.map → comment-DhvLEdhQ.mjs.map} +1 -1
  331. package/dist/{comment-reaction-D-2J84UE.mjs → comment-reaction-CRuItEjt.mjs} +2 -2
  332. package/dist/{comment-reaction-D-2J84UE.mjs.map → comment-reaction-CRuItEjt.mjs.map} +1 -1
  333. package/dist/{comments-CTfZtLmM.mjs → comments-CLj4bi8I.mjs} +2 -2
  334. package/dist/{comments-CTfZtLmM.mjs.map → comments-CLj4bi8I.mjs.map} +1 -1
  335. package/dist/{components-CQy3lh9x.mjs → components-v4U4DT2y.mjs} +1 -1
  336. package/dist/{components-CQy3lh9x.mjs.map → components-v4U4DT2y.mjs.map} +1 -1
  337. package/dist/{config-COQzTe5Q.d.mts → config-DrfghTPe.d.mts} +1 -1
  338. package/dist/{config-COQzTe5Q.d.mts.map → config-DrfghTPe.d.mts.map} +1 -1
  339. package/dist/{content-ZHCc1ngN.mjs → content-CP-SeXNa.mjs} +5 -4
  340. package/dist/content-CP-SeXNa.mjs.map +1 -0
  341. package/dist/{content-list-query-wJ2WN8Vp.d.mts → content-list-query-BhejKVqb.d.mts} +1 -1
  342. package/dist/{content-list-query-wJ2WN8Vp.d.mts.map → content-list-query-BhejKVqb.d.mts.map} +1 -1
  343. package/dist/{content-refresh-B_8SJzuU.mjs → content-refresh-DLOfIhYc.mjs} +1094 -93
  344. package/dist/content-refresh-DLOfIhYc.mjs.map +1 -0
  345. package/dist/{media-usage-BZCydfvu.mjs → content-repair-BCxzy0r4.mjs} +5 -196
  346. package/dist/content-repair-BCxzy0r4.mjs.map +1 -0
  347. package/dist/{context-ZldbZcle.mjs → context-BR5hRWiK.mjs} +13 -13
  348. package/dist/{context-ZldbZcle.mjs.map → context-BR5hRWiK.mjs.map} +1 -1
  349. package/dist/{cron-Y9aLtgrA.mjs → cron-CWfWrWKG.mjs} +1 -1
  350. package/dist/{cron-Y9aLtgrA.mjs.map → cron-CWfWrWKG.mjs.map} +1 -1
  351. package/dist/{dashboard-DvHbk_cv.mjs → dashboard-CkIV2rnQ.mjs} +5 -5
  352. package/dist/{dashboard-DvHbk_cv.mjs.map → dashboard-CkIV2rnQ.mjs.map} +1 -1
  353. package/dist/db/index.d.mts +3 -3
  354. package/dist/db/index.mjs +3 -3
  355. package/dist/db/index.mjs.map +1 -1
  356. package/dist/db/libsql-migrations.d.mts +1 -1
  357. package/dist/db/libsql-migrations.mjs +4 -4
  358. package/dist/db/libsql.d.mts +1 -1
  359. package/dist/db/postgres-migrations.d.mts +1 -1
  360. package/dist/db/postgres-migrations.mjs +4 -4
  361. package/dist/db/postgres.d.mts +1 -1
  362. package/dist/db/sqlite-migrations.d.mts +1 -1
  363. package/dist/db/sqlite-migrations.mjs +4 -4
  364. package/dist/db/sqlite.d.mts +1 -1
  365. package/dist/db/sqlite.mjs +3 -3
  366. package/dist/db/sqlite.mjs.map +1 -1
  367. package/dist/{default-DdDl3fxv.mjs → default-YMEfcQUx.mjs} +1 -1
  368. package/dist/{default-DdDl3fxv.mjs.map → default-YMEfcQUx.mjs.map} +1 -1
  369. package/dist/{deferred-tasks-SDWxrgzJ.d.mts → deferred-tasks-DfqxH5Xf.d.mts} +1 -1
  370. package/dist/{deferred-tasks-SDWxrgzJ.d.mts.map → deferred-tasks-DfqxH5Xf.d.mts.map} +1 -1
  371. package/dist/{device-flow-C0GdD_z8.mjs → device-flow-CojjSBwb.mjs} +5 -5
  372. package/dist/{device-flow-C0GdD_z8.mjs.map → device-flow-CojjSBwb.mjs.map} +1 -1
  373. package/dist/{direct-executor-CDXeJCSs.mjs → direct-executor-BWeWiRYB.mjs} +3 -3
  374. package/dist/{direct-executor-CDXeJCSs.mjs.map → direct-executor-BWeWiRYB.mjs.map} +1 -1
  375. package/dist/{dist-D_6CKR8z.mjs → dist-89xCrGqi.mjs} +1 -1
  376. package/dist/{dist-D_6CKR8z.mjs.map → dist-89xCrGqi.mjs.map} +1 -1
  377. package/dist/{email-console-BHNjIwiB.mjs → email-console-CEJgR_LB.mjs} +1 -1
  378. package/dist/{email-console-BHNjIwiB.mjs.map → email-console-CEJgR_LB.mjs.map} +1 -1
  379. package/dist/{enrich-_ySMEvEE.mjs → enrich-BX0IDp4z.mjs} +3 -3
  380. package/dist/{enrich-_ySMEvEE.mjs.map → enrich-BX0IDp4z.mjs.map} +1 -1
  381. package/dist/{error-DupyJZE2.mjs → error-D9w94fjV.mjs} +2 -2
  382. package/dist/{error-DupyJZE2.mjs.map → error-D9w94fjV.mjs.map} +1 -1
  383. package/dist/{errors-CLUPm7aD.mjs → errors-n2j49uNf.mjs} +22 -2
  384. package/dist/errors-n2j49uNf.mjs.map +1 -0
  385. package/dist/{escape-DUY2r2Zs.mjs → escape-DN4Qmjsl.mjs} +1 -1
  386. package/dist/{escape-DUY2r2Zs.mjs.map → escape-DN4Qmjsl.mjs.map} +1 -1
  387. package/dist/{field-defs-cache-BzwBlJrR.mjs → field-defs-cache-Bh4q6oar.mjs} +5 -5
  388. package/dist/{field-defs-cache-BzwBlJrR.mjs.map → field-defs-cache-Bh4q6oar.mjs.map} +1 -1
  389. package/dist/focal-point-BeWFo6Xx.mjs +23 -0
  390. package/dist/focal-point-BeWFo6Xx.mjs.map +1 -0
  391. package/dist/{fts-manager-DyZEohZC.mjs → fts-manager-BhOdW0bi.mjs} +3 -3
  392. package/dist/{fts-manager-DyZEohZC.mjs.map → fts-manager-BhOdW0bi.mjs.map} +1 -1
  393. package/dist/{hash-Cg0TXJPV.mjs → hash-CTcngWvj.mjs} +1 -1
  394. package/dist/{hash-Cg0TXJPV.mjs.map → hash-CTcngWvj.mjs.map} +1 -1
  395. package/dist/{hreflang-DwT2E0Sa.d.mts → hreflang-7pTHsLZJ.d.mts} +2 -2
  396. package/dist/{hreflang-DwT2E0Sa.d.mts.map → hreflang-7pTHsLZJ.d.mts.map} +1 -1
  397. package/dist/{hreflang-BBe83kKa.mjs → hreflang-Huc6O5wa.mjs} +7 -7
  398. package/dist/{hreflang-BBe83kKa.mjs.map → hreflang-Huc6O5wa.mjs.map} +1 -1
  399. package/dist/{identity-Ys6Dlc9C.mjs → identity-DXmb4aVp.mjs} +3 -3
  400. package/dist/{identity-Ys6Dlc9C.mjs.map → identity-DXmb4aVp.mjs.map} +1 -1
  401. package/dist/{import-DPkLmeXO.mjs → import-C4_O3KXU.mjs} +5 -5
  402. package/dist/{import-DPkLmeXO.mjs.map → import-C4_O3KXU.mjs.map} +1 -1
  403. package/dist/{index-C9KuQyO-.d.mts → index-6bRee8zb.d.mts} +3 -3
  404. package/dist/{index-C9KuQyO-.d.mts.map → index-6bRee8zb.d.mts.map} +1 -1
  405. package/dist/{index-BVBZj4-7.d.mts → index-DWbJrsAg.d.mts} +128 -52
  406. package/dist/index-DWbJrsAg.d.mts.map +1 -0
  407. package/dist/index.d.mts +21 -21
  408. package/dist/index.mjs +78 -72
  409. package/dist/{init-lock-6b309ZrF.mjs → init-lock-DJkX6Hto.mjs} +1 -1
  410. package/dist/{init-lock-6b309ZrF.mjs.map → init-lock-DJkX6Hto.mjs.map} +1 -1
  411. package/dist/{load-YJ2xl3ra.mjs → load-Bo7fZAbg.mjs} +2 -2
  412. package/dist/{load-YJ2xl3ra.mjs.map → load-Bo7fZAbg.mjs.map} +1 -1
  413. package/dist/{loader-CdG_Ci3H.mjs → loader-ZsjE4paL.mjs} +2 -2
  414. package/dist/{loader-CdG_Ci3H.mjs.map → loader-ZsjE4paL.mjs.map} +1 -1
  415. package/dist/{manifest-builder-B252yQUf.mjs → manifest-builder-QXuqvVMP.mjs} +2 -2
  416. package/dist/{manifest-builder-B252yQUf.mjs.map → manifest-builder-QXuqvVMP.mjs.map} +1 -1
  417. package/dist/{manifest-schema-BXbUAHod.mjs → manifest-schema-CUYPfCEH.mjs} +1 -1
  418. package/dist/{manifest-schema-BXbUAHod.mjs.map → manifest-schema-CUYPfCEH.mjs.map} +1 -1
  419. package/dist/media/image-endpoint.mjs +1 -1
  420. package/dist/media/index.d.mts +1 -1
  421. package/dist/media/index.mjs +13 -3
  422. package/dist/media/index.mjs.map +1 -1
  423. package/dist/media/local-runtime.d.mts +14 -12
  424. package/dist/media/local-runtime.d.mts.map +1 -1
  425. package/dist/media/local-runtime.mjs +11 -5
  426. package/dist/media/local-runtime.mjs.map +1 -1
  427. package/dist/{media-BWWWb8kg.mjs → media-BjwNuLEu.mjs} +100 -6
  428. package/dist/media-BjwNuLEu.mjs.map +1 -0
  429. package/dist/{media-CzhAn2--.mjs → media-D6SeAfBl.mjs} +41 -12
  430. package/dist/media-D6SeAfBl.mjs.map +1 -0
  431. package/dist/{media-allowlist-D6x-Us02.mjs → media-allowlist-D35v7bmL.mjs} +3 -2
  432. package/dist/media-allowlist-D35v7bmL.mjs.map +1 -0
  433. package/dist/media-folders-D-0CmRZj.mjs +210 -0
  434. package/dist/media-folders-D-0CmRZj.mjs.map +1 -0
  435. package/dist/{media-upload-CHBVn6m-.mjs → media-upload-C7bLXGcI.mjs} +9 -9
  436. package/dist/{media-upload-CHBVn6m-.mjs.map → media-upload-C7bLXGcI.mjs.map} +1 -1
  437. package/dist/{media-url-BvoX88FA.mjs → media-url-DH7giMx_.mjs} +1 -1
  438. package/dist/{media-url-BvoX88FA.mjs.map → media-url-DH7giMx_.mjs.map} +1 -1
  439. package/dist/media-usage-BTRJvS6V.mjs +1833 -0
  440. package/dist/media-usage-BTRJvS6V.mjs.map +1 -0
  441. package/dist/media-usage-activation-Dx0kPV6b.mjs +78 -0
  442. package/dist/media-usage-activation-Dx0kPV6b.mjs.map +1 -0
  443. package/dist/{media-usage-work-DrCbqBcl.mjs → media-usage-work-C-fhM30c.mjs} +221 -59
  444. package/dist/media-usage-work-C-fhM30c.mjs.map +1 -0
  445. package/dist/{media-usage-work-CLo_6tct.mjs → media-usage-work-DWb2XPMI.mjs} +4 -4
  446. package/dist/{media-usage-work-CLo_6tct.mjs.map → media-usage-work-DWb2XPMI.mjs.map} +1 -1
  447. package/dist/{media-usage-write-fence-aHEgN1zB.mjs → media-usage-write-fence-BwoJVRDC.mjs} +2 -2
  448. package/dist/{media-usage-write-fence-aHEgN1zB.mjs.map → media-usage-write-fence-BwoJVRDC.mjs.map} +1 -1
  449. package/dist/{menus-TCNSd2vp.mjs → menus-CD7EFLwz.mjs} +3 -3
  450. package/dist/{menus-TCNSd2vp.mjs.map → menus-CD7EFLwz.mjs.map} +1 -1
  451. package/dist/{menus-DSo5QeW0.mjs → menus-CNg0QlDt.mjs} +42 -26
  452. package/dist/menus-CNg0QlDt.mjs.map +1 -0
  453. package/dist/migrations/index.d.mts +1 -1
  454. package/dist/migrations/index.mjs +3 -3
  455. package/dist/{mime-DfIVjNkr.mjs → mime-CUIG6x84.mjs} +1 -1
  456. package/dist/mime-CUIG6x84.mjs.map +1 -0
  457. package/dist/{mode-BDoH_WcF.mjs → mode-DaUM_MB2.mjs} +1 -1
  458. package/dist/{mode-BDoH_WcF.mjs.map → mode-DaUM_MB2.mjs.map} +1 -1
  459. package/dist/node-sqlite-compat-Udb4Y-rs.mjs +46 -0
  460. package/dist/node-sqlite-compat-Udb4Y-rs.mjs.map +1 -0
  461. package/dist/{normalize-Bc-s_r-a.mjs → normalize-CSxdKBVa.mjs} +22 -1
  462. package/dist/normalize-CSxdKBVa.mjs.map +1 -0
  463. package/dist/{oauth-authorization-iOikmrJd.mjs → oauth-authorization-Dpsb-fMJ.mjs} +5 -5
  464. package/dist/{oauth-authorization-iOikmrJd.mjs.map → oauth-authorization-Dpsb-fMJ.mjs.map} +1 -1
  465. package/dist/{oauth-clients-CvI0nHMJ.mjs → oauth-clients-Bxnoe_67.mjs} +1 -1
  466. package/dist/{oauth-clients-CvI0nHMJ.mjs.map → oauth-clients-Bxnoe_67.mjs.map} +1 -1
  467. package/dist/{oauth-state-store-Ba8KZlwW.mjs → oauth-state-store-0C4s0K6w.mjs} +1 -1
  468. package/dist/{oauth-state-store-Ba8KZlwW.mjs.map → oauth-state-store-0C4s0K6w.mjs.map} +1 -1
  469. package/dist/{oauth-user-lookup-C5SQwGYC.mjs → oauth-user-lookup-wlGYSeqX.mjs} +1 -1
  470. package/dist/{oauth-user-lookup-C5SQwGYC.mjs.map → oauth-user-lookup-wlGYSeqX.mjs.map} +1 -1
  471. package/dist/object-cache/memory.d.mts +1 -1
  472. package/dist/{options-CBXIHUoT.mjs → options-BHPeIbBd.mjs} +1 -1
  473. package/dist/{options-CBXIHUoT.mjs.map → options-BHPeIbBd.mjs.map} +1 -1
  474. package/dist/{options--hhz-jZ1.d.mts → options-BhZIeFY1.d.mts} +3 -3
  475. package/dist/{options--hhz-jZ1.d.mts.map → options-BhZIeFY1.d.mts.map} +1 -1
  476. package/dist/page/index.d.mts +2 -2
  477. package/dist/{parse-DFbuc-Fh.mjs → parse-BRJg1kz8.mjs} +2 -2
  478. package/dist/{parse-DFbuc-Fh.mjs.map → parse-BRJg1kz8.mjs.map} +1 -1
  479. package/dist/{passkey-config-DFAmUyuE.mjs → passkey-config-B3P0nHRR.mjs} +1 -1
  480. package/dist/{passkey-config-DFAmUyuE.mjs.map → passkey-config-B3P0nHRR.mjs.map} +1 -1
  481. package/dist/{patterns-CiyXeDgr.mjs → patterns-D9iDO3j8.mjs} +1 -1
  482. package/dist/{patterns-CiyXeDgr.mjs.map → patterns-D9iDO3j8.mjs.map} +1 -1
  483. package/dist/{placeholder-Dp0K3RkD.d.mts → placeholder-BR4ZhVJl.d.mts} +7 -1
  484. package/dist/placeholder-BR4ZhVJl.d.mts.map +1 -0
  485. package/dist/plugin-types.d.mts +1 -1
  486. package/dist/plugin-utils.d.mts +10 -10
  487. package/dist/plugins/adapt-sandbox-entry.d.mts +10 -10
  488. package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
  489. package/dist/{policy-TluUIxuu.mjs → policy-BVRganJQ.mjs} +2 -2
  490. package/dist/{policy-TluUIxuu.mjs.map → policy-BVRganJQ.mjs.map} +1 -1
  491. package/dist/{portable-text-imBW5cHK.mjs → portable-text-C8Tvp4_g.mjs} +1 -1
  492. package/dist/{portable-text-imBW5cHK.mjs.map → portable-text-C8Tvp4_g.mjs.map} +1 -1
  493. package/dist/{preview-PW-nOnq4.mjs → preview-dn2dUyM0.mjs} +2 -2
  494. package/dist/{preview-PW-nOnq4.mjs.map → preview-dn2dUyM0.mjs.map} +1 -1
  495. package/dist/{protocol-DjsJHTlK.d.mts → protocol-CuBEsqgE.d.mts} +2 -2
  496. package/dist/{protocol-DjsJHTlK.d.mts.map → protocol-CuBEsqgE.d.mts.map} +1 -1
  497. package/dist/{public-url-CWoFhQT8.mjs → public-url-DRz_AinV.mjs} +1 -1
  498. package/dist/{public-url-CWoFhQT8.mjs.map → public-url-DRz_AinV.mjs.map} +1 -1
  499. package/dist/{query-zTVAR1Sl.mjs → query-D18vTdha.mjs} +14 -14
  500. package/dist/{query-zTVAR1Sl.mjs.map → query-D18vTdha.mjs.map} +1 -1
  501. package/dist/{query-BWs7O5Il.mjs → query-tZDEdtCN.mjs} +4 -4
  502. package/dist/{query-BWs7O5Il.mjs.map → query-tZDEdtCN.mjs.map} +1 -1
  503. package/dist/{rate-limit-Bx4g9m3p.mjs → rate-limit-on7KPo-v.mjs} +2 -2
  504. package/dist/{rate-limit-Bx4g9m3p.mjs.map → rate-limit-on7KPo-v.mjs.map} +1 -1
  505. package/dist/{redirect-COsS6SWJ.mjs → redirect-CZkbozu9.mjs} +2 -2
  506. package/dist/{redirect-COsS6SWJ.mjs.map → redirect-CZkbozu9.mjs.map} +1 -1
  507. package/dist/{redirect-BgAOGHDq.mjs → redirect-DrbEmU9a.mjs} +1 -1
  508. package/dist/{redirect-BgAOGHDq.mjs.map → redirect-DrbEmU9a.mjs.map} +1 -1
  509. package/dist/{redirects-DyChwDkG.mjs → redirects-BahWiu8Z.mjs} +5 -5
  510. package/dist/{redirects-DyChwDkG.mjs.map → redirects-BahWiu8Z.mjs.map} +1 -1
  511. package/dist/{redirects-DTDHWH31.mjs → redirects-CRIM55KH.mjs} +6 -6
  512. package/dist/{redirects-DTDHWH31.mjs.map → redirects-CRIM55KH.mjs.map} +1 -1
  513. package/dist/{api-BeBglVxU.mjs → registry-BiO5WwPq.mjs} +483 -2462
  514. package/dist/registry-BiO5WwPq.mjs.map +1 -0
  515. package/dist/{registry-Cqa2I749.mjs → registry-Cx-jEJOS.mjs} +9 -123
  516. package/dist/registry-Cx-jEJOS.mjs.map +1 -0
  517. package/dist/{relation-BnHB0T4J.mjs → relation-A6wLF_DS.mjs} +2 -2
  518. package/dist/{relation-BnHB0T4J.mjs.map → relation-A6wLF_DS.mjs.map} +1 -1
  519. package/dist/{relations-BBApI4e2.mjs → relations-BTPM2saV.mjs} +2 -2
  520. package/dist/{relations-BBApI4e2.mjs.map → relations-BTPM2saV.mjs.map} +1 -1
  521. package/dist/{relations-BHo5A8Zf.d.mts → relations-C7B46bcU.d.mts} +192 -33
  522. package/dist/relations-C7B46bcU.d.mts.map +1 -0
  523. package/dist/{relations-D5sM5fW0.mjs → relations-Cj5hZ6Zg.mjs} +4 -4
  524. package/dist/{relations-D5sM5fW0.mjs.map → relations-Cj5hZ6Zg.mjs.map} +1 -1
  525. package/dist/{request-cache-BSUptuJR.mjs → request-cache-DH9zdCOD.mjs} +1 -1
  526. package/dist/{request-cache-BSUptuJR.mjs.map → request-cache-DH9zdCOD.mjs.map} +1 -1
  527. package/dist/request-context.d.mts +1 -1
  528. package/dist/{request-meta-DgZJh3zG.mjs → request-meta-DRp5ENd_.mjs} +2 -2
  529. package/dist/{request-meta-DgZJh3zG.mjs.map → request-meta-DRp5ENd_.mjs.map} +1 -1
  530. package/dist/{resolve-DOhVEvty.mjs → resolve-DxUpTMmE.mjs} +1 -1
  531. package/dist/{resolve-DOhVEvty.mjs.map → resolve-DxUpTMmE.mjs.map} +1 -1
  532. package/dist/{runner-C1T11CfC.mjs → runner-77GOZCnH.mjs} +410 -286
  533. package/dist/runner-77GOZCnH.mjs.map +1 -0
  534. package/dist/{runner-BYHZn3Jy.d.mts → runner-zgqA_CqP.d.mts} +2 -2
  535. package/dist/{runner-BYHZn3Jy.d.mts.map → runner-zgqA_CqP.d.mts.map} +1 -1
  536. package/dist/runtime.d.mts +11 -11
  537. package/dist/runtime.mjs +1 -1
  538. package/dist/{schema-DZ5YVVZj.mjs → schema-BY-oVntF.mjs} +3 -3
  539. package/dist/{schema-DZ5YVVZj.mjs.map → schema-BY-oVntF.mjs.map} +1 -1
  540. package/dist/{schema-2AOsSn2v.mjs → schema-Cw9kVK0I.mjs} +35 -5
  541. package/dist/{schema-2AOsSn2v.mjs.map → schema-Cw9kVK0I.mjs.map} +1 -1
  542. package/dist/{scopes-8ClPF2bC.mjs → scopes-vMR1uIbt.mjs} +3 -3
  543. package/dist/{scopes-8ClPF2bC.mjs.map → scopes-vMR1uIbt.mjs.map} +1 -1
  544. package/dist/{search-u1QYdOCG.mjs → search-CfT3KxO5.mjs} +4 -4
  545. package/dist/{search-u1QYdOCG.mjs.map → search-CfT3KxO5.mjs.map} +1 -1
  546. package/dist/{secrets-BEgsAQEU.mjs → secrets-BH2Qow1-.mjs} +3 -3
  547. package/dist/{secrets-BEgsAQEU.mjs.map → secrets-BH2Qow1-.mjs.map} +1 -1
  548. package/dist/{sections-DmHviGyM.mjs → sections-DTfjyf1z.mjs} +2 -2
  549. package/dist/{sections-DmHviGyM.mjs.map → sections-DTfjyf1z.mjs.map} +1 -1
  550. package/dist/seed/index.d.mts +2 -2
  551. package/dist/seed/index.mjs +24 -22
  552. package/dist/seo/index.d.mts +3 -3
  553. package/dist/seo/index.mjs +16 -14
  554. package/dist/seo/index.mjs.map +1 -1
  555. package/dist/{seo-AFpVvTqr.mjs → seo-BSiTIXzd.mjs} +1 -1
  556. package/dist/{seo-AFpVvTqr.mjs.map → seo-BSiTIXzd.mjs.map} +1 -1
  557. package/dist/{seo-Dq4G-RrO.mjs → seo-Cm-dtvgk.mjs} +2 -2
  558. package/dist/{seo-Dq4G-RrO.mjs.map → seo-Cm-dtvgk.mjs.map} +1 -1
  559. package/dist/{service-BpArayPr.mjs → service-DYsDHEKe.mjs} +3 -3
  560. package/dist/{service-BpArayPr.mjs.map → service-DYsDHEKe.mjs.map} +1 -1
  561. package/dist/{session-user-BEorKVT-.mjs → session-user-CNAfvsdw.mjs} +1 -1
  562. package/dist/{session-user-BEorKVT-.mjs.map → session-user-CNAfvsdw.mjs.map} +1 -1
  563. package/dist/{settings-xRdoUZr0.mjs → settings-BgN-0DT-.mjs} +6 -6
  564. package/dist/{settings-xRdoUZr0.mjs.map → settings-BgN-0DT-.mjs.map} +1 -1
  565. package/dist/{settings-DE9xtzqd.mjs → settings-DjtkUo__.mjs} +3 -3
  566. package/dist/{settings-DE9xtzqd.mjs.map → settings-DjtkUo__.mjs.map} +1 -1
  567. package/dist/{setup-complete-BnT8pq_C.mjs → setup-complete-CcpXaPU7.mjs} +2 -2
  568. package/dist/{setup-complete-BnT8pq_C.mjs.map → setup-complete-CcpXaPU7.mjs.map} +1 -1
  569. package/dist/{setup-nonce-CBitf_oQ.mjs → setup-nonce-Reyw30jg.mjs} +1 -1
  570. package/dist/{setup-nonce-CBitf_oQ.mjs.map → setup-nonce-Reyw30jg.mjs.map} +1 -1
  571. package/dist/{single-flight-cache-B0eLKS6K.mjs → single-flight-cache-BPuGPdcN.mjs} +2 -2
  572. package/dist/{single-flight-cache-B0eLKS6K.mjs.map → single-flight-cache-BPuGPdcN.mjs.map} +1 -1
  573. package/dist/{site-url-DjpIXMXJ.mjs → site-url-Cs_V9n3V.mjs} +3 -3
  574. package/dist/{site-url-DjpIXMXJ.mjs.map → site-url-Cs_V9n3V.mjs.map} +1 -1
  575. package/dist/{snapshot-BBlGkb2q.mjs → snapshot-DSB8P7Lm.mjs} +1 -1
  576. package/dist/{snapshot-BBlGkb2q.mjs.map → snapshot-DSB8P7Lm.mjs.map} +1 -1
  577. package/dist/{ssrf-Cpvg_n3A.mjs → ssrf-DqebVE34.mjs} +21 -5
  578. package/dist/{ssrf-Cpvg_n3A.mjs.map → ssrf-DqebVE34.mjs.map} +1 -1
  579. package/dist/{state-DYwPue40.mjs → state-BJfU9Uzw.mjs} +12 -4
  580. package/dist/state-BJfU9Uzw.mjs.map +1 -0
  581. package/dist/{status-CWVYpbD8.mjs → status-DDu8HIQl.mjs} +1 -1
  582. package/dist/{status-CWVYpbD8.mjs.map → status-DDu8HIQl.mjs.map} +1 -1
  583. package/dist/storage/local.d.mts +1 -1
  584. package/dist/storage/local.mjs +1 -1
  585. package/dist/storage/s3.d.mts +1 -1
  586. package/dist/storage/s3.mjs +1 -1
  587. package/dist/{target-DixqqV5i.mjs → target-D0o26Azo.mjs} +1 -1
  588. package/dist/{target-DixqqV5i.mjs.map → target-D0o26Azo.mjs.map} +1 -1
  589. package/dist/{taxonomies-OHUIiIpI.mjs → taxonomies-CPGxo-bn.mjs} +5 -5
  590. package/dist/{taxonomies-OHUIiIpI.mjs.map → taxonomies-CPGxo-bn.mjs.map} +1 -1
  591. package/dist/{taxonomies-BlrncHi9.mjs → taxonomies-kmyudNGP.mjs} +7 -7
  592. package/dist/{taxonomies-BlrncHi9.mjs.map → taxonomies-kmyudNGP.mjs.map} +1 -1
  593. package/dist/{taxonomy-BqCZ1TdU.mjs → taxonomy-8X09dLN5.mjs} +4 -4
  594. package/dist/{taxonomy-BqCZ1TdU.mjs.map → taxonomy-8X09dLN5.mjs.map} +1 -1
  595. package/dist/{tokens-C39klX8R.mjs → tokens-gTS-IVUp.mjs} +1 -1
  596. package/dist/{tokens-C39klX8R.mjs.map → tokens-gTS-IVUp.mjs.map} +1 -1
  597. package/dist/{transaction-NQj4VJ7Z.mjs → transaction-0KOpOyMu.mjs} +2 -1
  598. package/dist/{transaction-NQj4VJ7Z.mjs.map → transaction-0KOpOyMu.mjs.map} +1 -1
  599. package/dist/{transport-RQE36APG.d.mts → transport-_6R1ub7t.d.mts} +1 -1
  600. package/dist/{transport-RQE36APG.d.mts.map → transport-_6R1ub7t.d.mts.map} +1 -1
  601. package/dist/{transport-DBJwa-Si.mjs → transport-fX59xaJz.mjs} +4 -3
  602. package/dist/{transport-DBJwa-Si.mjs.map → transport-fX59xaJz.mjs.map} +1 -1
  603. package/dist/{trusted-proxy-CEVekpby.mjs → trusted-proxy-fe6nEgR4.mjs} +1 -1
  604. package/dist/{trusted-proxy-CEVekpby.mjs.map → trusted-proxy-fe6nEgR4.mjs.map} +1 -1
  605. package/dist/types-75KIqzL9.mjs.map +1 -1
  606. package/dist/{types-BW1syrlw.mjs → types-BXUmsINL.mjs} +1 -1
  607. package/dist/{types-BW1syrlw.mjs.map → types-BXUmsINL.mjs.map} +1 -1
  608. package/dist/{types-BntycPTF.d.mts → types-Blb-MRC3.d.mts} +1 -1
  609. package/dist/{types-BntycPTF.d.mts.map → types-Blb-MRC3.d.mts.map} +1 -1
  610. package/dist/{types-C2pMvO62.d.mts → types-Br-ERaxh.d.mts} +2 -1
  611. package/dist/types-Br-ERaxh.d.mts.map +1 -0
  612. package/dist/{types-DWRhk9xZ.d.mts → types-BzBd3PeX.d.mts} +1 -1
  613. package/dist/{types-DWRhk9xZ.d.mts.map → types-BzBd3PeX.d.mts.map} +1 -1
  614. package/dist/{types-CXnOzDe1.d.mts → types-CLqvnd05.d.mts} +1 -1
  615. package/dist/{types-CXnOzDe1.d.mts.map → types-CLqvnd05.d.mts.map} +1 -1
  616. package/dist/{types-Dfk8KI5R.d.mts → types-D5VVLr7J.d.mts} +1 -1
  617. package/dist/{types-Dfk8KI5R.d.mts.map → types-D5VVLr7J.d.mts.map} +1 -1
  618. package/dist/{types-DQIDRKF2.d.mts → types-DHxzBjbD.d.mts} +10 -1
  619. package/dist/types-DHxzBjbD.d.mts.map +1 -0
  620. package/dist/{types-DN64_zzZ.d.mts → types-DJQxln7q.d.mts} +3 -3
  621. package/dist/{types-DN64_zzZ.d.mts.map → types-DJQxln7q.d.mts.map} +1 -1
  622. package/dist/{types-CtzytVXn.mjs → types-DM3vioBY.mjs} +1 -1
  623. package/dist/{types-CtzytVXn.mjs.map → types-DM3vioBY.mjs.map} +1 -1
  624. package/dist/{types-BcqdcT-r.d.mts → types-D_DWe7k9.d.mts} +1 -1
  625. package/dist/{types-BcqdcT-r.d.mts.map → types-D_DWe7k9.d.mts.map} +1 -1
  626. package/dist/{types-DrJrVCT1.d.mts → types-Vg6f5i3s.d.mts} +3 -3
  627. package/dist/{types-DrJrVCT1.d.mts.map → types-Vg6f5i3s.d.mts.map} +1 -1
  628. package/dist/{upload-attempts-D4FS8LT6.mjs → upload-attempts-CNyGSepg.mjs} +1 -1
  629. package/dist/{upload-attempts-D4FS8LT6.mjs.map → upload-attempts-CNyGSepg.mjs.map} +1 -1
  630. package/dist/{url-pattern-BHS0V5Ng.mjs → url-pattern-K-KdXcSe.mjs} +1 -1
  631. package/dist/{url-pattern-BHS0V5Ng.mjs.map → url-pattern-K-KdXcSe.mjs.map} +1 -1
  632. package/dist/{user-DJZfUJVE.mjs → user-CtYY0tp2.mjs} +2 -2
  633. package/dist/{user-DJZfUJVE.mjs.map → user-CtYY0tp2.mjs.map} +1 -1
  634. package/dist/{utils-D4vD_Y98.mjs → utils-HztC73hq.mjs} +1 -1
  635. package/dist/{utils-D4vD_Y98.mjs.map → utils-HztC73hq.mjs.map} +1 -1
  636. package/dist/{validate-BMLBvxOC.mjs → validate-Dd-IUjnZ.mjs} +2 -2
  637. package/dist/{validate-BMLBvxOC.mjs.map → validate-Dd-IUjnZ.mjs.map} +1 -1
  638. package/dist/{validate-CTqt5nuE.d.mts → validate-DlqdOZXB.d.mts} +5 -5
  639. package/dist/{validate-CTqt5nuE.d.mts.map → validate-DlqdOZXB.d.mts.map} +1 -1
  640. package/dist/{validation-npvENUCB.mjs → validation-D83n7Npi.mjs} +10 -8
  641. package/dist/{validation-npvENUCB.mjs.map → validation-D83n7Npi.mjs.map} +1 -1
  642. package/dist/version-ri-sCyAp.mjs +7 -0
  643. package/dist/{version-DB_fqCSI.mjs.map → version-ri-sCyAp.mjs.map} +1 -1
  644. package/dist/{widgets-BD3ho7Rq.mjs → widgets-CpmZd9Q-.mjs} +4 -4
  645. package/dist/{widgets-BD3ho7Rq.mjs.map → widgets-CpmZd9Q-.mjs.map} +1 -1
  646. package/dist/{wxr-taxonomies-Ca68766D.d.mts → wxr-taxonomies-BFd0y72_.d.mts} +1 -1
  647. package/dist/{wxr-taxonomies-Ca68766D.d.mts.map → wxr-taxonomies-BFd0y72_.d.mts.map} +1 -1
  648. package/dist/{wxr-taxonomies-B1R3vBoD.mjs → wxr-taxonomies-Df4vxWcU.mjs} +4 -4
  649. package/dist/{wxr-taxonomies-B1R3vBoD.mjs.map → wxr-taxonomies-Df4vxWcU.mjs.map} +1 -1
  650. package/dist/{zod-generator-LC-on_Vb.mjs → zod-generator-DylzusHh.mjs} +24 -18
  651. package/dist/zod-generator-DylzusHh.mjs.map +1 -0
  652. package/package.json +16 -8
  653. package/src/api/errors.ts +20 -0
  654. package/src/api/handlers/index.ts +17 -0
  655. package/src/api/handlers/media-allowlist.ts +1 -0
  656. package/src/api/handlers/media-folders.ts +130 -0
  657. package/src/api/handlers/media-usage-activation.ts +102 -0
  658. package/src/api/handlers/media-usage.ts +108 -0
  659. package/src/api/handlers/media.ts +62 -2
  660. package/src/api/handlers/registry.ts +134 -316
  661. package/src/api/openapi/document.ts +192 -2
  662. package/src/api/schemas/media-usage.ts +77 -0
  663. package/src/api/schemas/media.ts +69 -0
  664. package/src/astro/integration/routes.ts +18 -0
  665. package/src/astro/integration/vite-config.ts +2 -7
  666. package/src/astro/middleware/auth.ts +4 -0
  667. package/src/astro/middleware.ts +0 -7
  668. package/src/astro/routes/api/admin/media-usage/activation.ts +42 -0
  669. package/src/astro/routes/api/admin/media-usage/progress.ts +37 -0
  670. package/src/astro/routes/api/admin/plugins/registry/artifact.ts +246 -96
  671. package/src/astro/routes/api/auth/me.ts +14 -2
  672. package/src/astro/routes/api/media/[id].ts +3 -0
  673. package/src/astro/routes/api/media/folders/[id].ts +55 -0
  674. package/src/astro/routes/api/media/folders/index.ts +37 -0
  675. package/src/astro/routes/api/media.ts +12 -2
  676. package/src/astro/routes/api/setup/dev-bypass.ts +12 -0
  677. package/src/astro/types.ts +11 -1
  678. package/src/client/index.ts +140 -2
  679. package/src/client/transport.ts +6 -2
  680. package/src/components/EmDashImage.astro +154 -50
  681. package/src/components/Gallery.astro +7 -0
  682. package/src/components/InlinePortableTextEditor.tsx +130 -1
  683. package/src/components/LiveSearch.astro +23 -0
  684. package/src/components/inline-code-block.tsx +180 -49
  685. package/src/content/converters/gallery.ts +2 -0
  686. package/src/content/converters/types.ts +2 -0
  687. package/src/database/connection.ts +2 -31
  688. package/src/database/index.ts +1 -1
  689. package/src/database/json-recordset.ts +16 -0
  690. package/src/database/migrations/019_i18n.ts +1 -1
  691. package/src/database/migrations/071_restore_content_bylines_table.ts +43 -0
  692. package/src/database/migrations/072_media_folders.ts +72 -0
  693. package/src/database/migrations/073_media_focal_point.ts +57 -0
  694. package/src/database/migrations/runner.ts +8 -2
  695. package/src/database/repositories/content.ts +5 -4
  696. package/src/database/repositories/media-folders.ts +115 -0
  697. package/src/database/repositories/media-usage-work.ts +395 -111
  698. package/src/database/repositories/media-usage.ts +1038 -10
  699. package/src/database/repositories/media.ts +97 -29
  700. package/src/database/transaction.ts +1 -0
  701. package/src/database/types.ts +13 -0
  702. package/src/db/adapters.ts +2 -2
  703. package/src/db/node-sqlite-compat.ts +78 -0
  704. package/src/db/sqlite.ts +3 -3
  705. package/src/emdash-runtime.ts +110 -105
  706. package/src/fields/image.ts +7 -1
  707. package/src/fields/types.ts +6 -2
  708. package/src/index.ts +5 -0
  709. package/src/media/focal-point.ts +37 -0
  710. package/src/media/local-runtime.ts +8 -0
  711. package/src/media/mime.ts +1 -0
  712. package/src/media/normalize.ts +10 -0
  713. package/src/media/types.ts +10 -0
  714. package/src/media/usage/activation.ts +162 -35
  715. package/src/media/usage/capture-triggers.ts +19 -3
  716. package/src/media/usage/collection-deletion-processor.ts +7 -3
  717. package/src/media/usage/collection-deletion.ts +10 -0
  718. package/src/media/usage/content-refresh.ts +258 -39
  719. package/src/media/usage/content-snapshots.ts +128 -31
  720. package/src/media/usage/extractor.ts +32 -13
  721. package/src/media/usage/maintenance-engine.ts +166 -0
  722. package/src/media/usage/reconciliation-processor.ts +106 -29
  723. package/src/media/usage/reconciliation.ts +90 -7
  724. package/src/media/usage/work-processor.ts +342 -101
  725. package/src/plugins/index.ts +1 -0
  726. package/src/plugins/save-rejection.ts +20 -0
  727. package/src/plugins/scheduler/node.ts +1 -13
  728. package/src/plugins/scheduler/types.ts +0 -2
  729. package/src/plugins/state.ts +13 -3
  730. package/src/registry/artifact-fetch.ts +530 -0
  731. package/src/registry/config.ts +24 -6
  732. package/src/registry/types.ts +12 -15
  733. package/src/schema/types.ts +1 -0
  734. package/src/schema/zod-generator.ts +12 -4
  735. package/src/security/ssrf.ts +21 -2
  736. package/src/seed/apply.ts +4 -4
  737. package/src/ui.ts +2 -0
  738. package/dist/api-BeBglVxU.mjs.map +0 -1
  739. package/dist/collection-deletion-DQPvMD8R.mjs.map +0 -1
  740. package/dist/content-ZHCc1ngN.mjs.map +0 -1
  741. package/dist/content-refresh-B_8SJzuU.mjs.map +0 -1
  742. package/dist/errors-CLUPm7aD.mjs.map +0 -1
  743. package/dist/index-BVBZj4-7.d.mts.map +0 -1
  744. package/dist/media-BWWWb8kg.mjs.map +0 -1
  745. package/dist/media-CzhAn2--.mjs.map +0 -1
  746. package/dist/media-allowlist-D6x-Us02.mjs.map +0 -1
  747. package/dist/media-usage-BZCydfvu.mjs.map +0 -1
  748. package/dist/media-usage-work-DrCbqBcl.mjs.map +0 -1
  749. package/dist/menus-DSo5QeW0.mjs.map +0 -1
  750. package/dist/mime-DfIVjNkr.mjs.map +0 -1
  751. package/dist/normalize-Bc-s_r-a.mjs.map +0 -1
  752. package/dist/placeholder-Dp0K3RkD.d.mts.map +0 -1
  753. package/dist/registry-Cqa2I749.mjs.map +0 -1
  754. package/dist/relations-BHo5A8Zf.d.mts.map +0 -1
  755. package/dist/runner-C1T11CfC.mjs.map +0 -1
  756. package/dist/state-DYwPue40.mjs.map +0 -1
  757. package/dist/types-C2pMvO62.d.mts.map +0 -1
  758. package/dist/types-DQIDRKF2.d.mts.map +0 -1
  759. package/dist/version-DB_fqCSI.mjs +0 -7
  760. package/dist/zod-generator-LC-on_Vb.mjs.map +0 -1
  761. /package/dist/{api-tokens-rIXC6LdE.mjs → api-tokens-B5NHLNUN.mjs} +0 -0
  762. /package/dist/{ssrf-DpXgKsna.mjs → ssrf-BPKzkm6O.mjs} +0 -0
  763. /package/dist/{types-CP1CflWf.mjs → types-D6OcIp2P.mjs} +0 -0
@@ -1,101 +1,106 @@
1
- import { r as MIGRATION_RACE_WAIT_MS, t as ConcurrentMigrationTimeoutError } from "../runner-C1T11CfC.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-DSo5QeW0.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-ZHCc1ngN.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-DJZfUJVE.mjs";
17
- import { t as withTransaction } from "../transaction-NQj4VJ7Z.mjs";
18
- import "../taxonomy-BqCZ1TdU.mjs";
19
- import "../comment-Dxlep6cE.mjs";
20
- import { t as OptionsRepository } from "../options-CBXIHUoT.mjs";
21
- import { r as createSiteInfo } from "../context-ZldbZcle.mjs";
22
- import "../menus-TCNSd2vp.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-BzwBlJrR.mjs";
28
- import "../byline-yjvWq4ip.mjs";
29
- import "../relation-BnHB0T4J.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-Dq4G-RrO.mjs";
34
- import { r as singleFlightCached, t as createSingleFlightCache } from "../single-flight-cache-B0eLKS6K.mjs";
35
- import "../cache-C46-3Irl.mjs";
36
- import { t as FTSManager } from "../fts-manager-DyZEohZC.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-CdG_Ci3H.mjs";
40
- import { a as getTaxonomyDefs, o as getTaxonomyTerms } from "../taxonomies-BlrncHi9.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-BeBglVxU.mjs";
42
- import { n as recordSchedulerHeartbeatSafely } from "../dashboard-DvHbk_cv.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-B_8SJzuU.mjs";
45
- import { t as MediaUsageWorkRepository } from "../media-usage-work-DrCbqBcl.mjs";
46
- import "../media-usage-BZCydfvu.mjs";
47
- import { n as collectionDeletionCurrentTimestamp, r as deleteActivatedMediaUsageCollection, t as MediaUsageCollectionDeletionRepository } from "../collection-deletion-DQPvMD8R.mjs";
48
- import "../media-usage-work-CLo_6tct.mjs";
49
- import { n as SchemaRegistry } from "../registry-Cqa2I749.mjs";
50
- import "../query-BWs7O5Il.mjs";
51
- import "../zod-generator-LC-on_Vb.mjs";
52
- import "../schema-DZ5YVVZj.mjs";
53
- import "../schema-2AOsSn2v.mjs";
54
- import "../seo-AFpVvTqr.mjs";
55
- import { t as PluginStateRepository } from "../state-DYwPue40.mjs";
56
- import "../sections-DmHviGyM.mjs";
57
- import { i as getSiteSettings, o as invalidateSiteSettingsCache } from "../settings-xRdoUZr0.mjs";
58
- import "../settings-DE9xtzqd.mjs";
59
- import "../taxonomies-OHUIiIpI.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-DTDHWH31.mjs";
67
- import "../relations-BBApI4e2.mjs";
68
- import "../status-CWVYpbD8.mjs";
69
- import { a as invalidateUrlPatternCache } from "../query-zTVAR1Sl.mjs";
70
- import "../import-DPkLmeXO.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-BBe83kKa.mjs";
81
- import "../comment-reaction-D-2J84UE.mjs";
82
- import "../bylines-DjXS5p0P.mjs";
83
- import { n as getWidgetAreas } from "../widgets-BD3ho7Rq.mjs";
84
- import "../validate-BMLBvxOC.mjs";
85
- import "../apply-kUDt80-G.mjs";
86
- import "../load-YJ2xl3ra.mjs";
87
- import "../search-u1QYdOCG.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 { i as resolveRuntimeMigrationMode, n as enforceRuntimeMigrationPolicy, t as PendingMigrationsError } from "../policy-TluUIxuu.mjs";
90
- import { n as VERSION, t as COMMIT } from "../version-DB_fqCSI.mjs";
91
- import { t as getAuthMode } from "../mode-BDoH_WcF.mjs";
92
- import "../snapshot-BBlGkb2q.mjs";
93
- import { u as maybeRunScheduledBackup } from "../backup-DWYpJlLj.mjs";
94
- import { t as cleanupExpiredChallenges } from "../challenge-store-SIL5DsF2.mjs";
95
- import { t as removeUploadAttempt } from "../upload-attempts-D4FS8LT6.mjs";
96
- import { a as validateEncryptionKeyAtStartup } from "../secrets-BEgsAQEU.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";
97
102
  import { isRecord } from "../plugin-utils.mjs";
98
- import { t as resolveSessionUser } from "../session-user-BEorKVT-.mjs";
103
+ import { t as resolveSessionUser } from "../session-user-CNAfvsdw.mjs";
99
104
  import { Kysely, sql } from "kysely";
100
105
  import { ulid } from "ulidx";
101
106
  import { z } from "zod";
@@ -158,1145 +163,6 @@ async function warnAboutUnconfiguredTaxonomyLocales(db, configuredLocales, defin
158
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}`);
159
164
  }
160
165
 
161
- //#endregion
162
- //#region src/media/usage/collection-deletion-processor.ts
163
- const MEDIA_USAGE_COLLECTION_DELETION_LIMITS = Object.freeze({
164
- candidatesPerTick: 4,
165
- deletionsPerTick: 1,
166
- rowsPerBatch: 50,
167
- leaseDurationSeconds: 300,
168
- maxAttempts: 5,
169
- retryBaseSeconds: 30,
170
- retryMaxSeconds: 900,
171
- maxQueriesPerTick: 30
172
- });
173
- async function processDueMediaUsageCollectionDeletions(db) {
174
- const repository = new MediaUsageCollectionDeletionRepository(db);
175
- const candidates = await repository.findDue(MEDIA_USAGE_COLLECTION_DELETION_LIMITS.candidatesPerTick);
176
- if (candidates.length === 0) return {
177
- candidateCount: 0,
178
- claimedCount: 0,
179
- outcome: "idle"
180
- };
181
- let claim = null;
182
- for (const candidate of candidates) {
183
- claim = await repository.claim({
184
- collectionId: candidate.collectionId,
185
- phase: candidate.phase,
186
- leaseDurationSeconds: MEDIA_USAGE_COLLECTION_DELETION_LIMITS.leaseDurationSeconds
187
- });
188
- if (claim?.leaseToken) break;
189
- }
190
- if (!claim?.leaseToken) return {
191
- candidateCount: candidates.length,
192
- claimedCount: 0,
193
- outcome: "claim_lost"
194
- };
195
- try {
196
- const processed = await processClaimedDeletion(db, claim);
197
- if (!processed.finalized && !processed.released && !await repository.release(claim)) return {
198
- candidateCount: candidates.length,
199
- claimedCount: 1,
200
- outcome: "claim_lost"
201
- };
202
- return {
203
- candidateCount: candidates.length,
204
- claimedCount: 1,
205
- outcome: processed.finalized ? "finalized" : "progress"
206
- };
207
- } catch (error) {
208
- const terminal = claim.attemptCount + 1 >= MEDIA_USAGE_COLLECTION_DELETION_LIMITS.maxAttempts;
209
- if (!await repository.recordFailure({
210
- collectionId: claim.collectionId,
211
- leaseToken: claim.leaseToken,
212
- errorCode: "MEDIA_USAGE_COLLECTION_DELETION_FAILED",
213
- terminal,
214
- retryDelaySeconds: retryDelaySeconds$2(claim.attemptCount)
215
- })) return {
216
- candidateCount: candidates.length,
217
- claimedCount: 1,
218
- outcome: "claim_lost"
219
- };
220
- console.error("[media-usage:collection-deletion] Processing failed:", error);
221
- return {
222
- candidateCount: candidates.length,
223
- claimedCount: 1,
224
- outcome: terminal ? "failed" : "retry"
225
- };
226
- }
227
- }
228
- async function processClaimedDeletion(db, claim) {
229
- if (claim.phase === "fence" || claim.phase === "registry" || claim.phase === "table") {
230
- await deleteActivatedMediaUsageCollection(db, {
231
- collectionId: claim.collectionId,
232
- collectionSlug: claim.collectionSlug,
233
- forceDelete: claim.forceDelete
234
- }, {
235
- frontPhaseLimit: 1,
236
- claimed: claim
237
- });
238
- return {
239
- finalized: false,
240
- released: true
241
- };
242
- }
243
- if (claim.phase === "work") await processWorkBatch(db, claim);
244
- if (claim.phase === "sources") await processSourceBatch(db, claim);
245
- if (claim.phase === "status") await processStatus(db, claim);
246
- if (claim.phase === "finalize") {
247
- await finalizeDeletion(db, claim);
248
- return {
249
- finalized: true,
250
- released: true
251
- };
252
- }
253
- return {
254
- finalized: false,
255
- released: false
256
- };
257
- }
258
- async function processWorkBatch(db, claim) {
259
- await withTransaction(db, async (trx) => {
260
- 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();
261
- const batch = rows.slice(0, MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch);
262
- 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();
263
- await updateDeletion(trx, claim, {
264
- phase: rows.length > MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch ? "work" : "sources",
265
- work_cursor: rows.length > MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch ? batch.at(-1).content_id : null
266
- });
267
- });
268
- return false;
269
- }
270
- async function processSourceBatch(db, claim) {
271
- await withTransaction(db, async (trx) => {
272
- let sourceKey = claim.sourceKey;
273
- if (!sourceKey) {
274
- 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();
275
- if (!source) {
276
- await updateDeletion(trx, claim, {
277
- phase: "status",
278
- source_key: null,
279
- occurrence_cursor: null
280
- });
281
- return;
282
- }
283
- sourceKey = source.source_key;
284
- await updateDeletion(trx, claim, {
285
- source_key: sourceKey,
286
- occurrence_cursor: null
287
- });
288
- }
289
- 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();
290
- const batch = occurrences.slice(0, MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch);
291
- 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();
292
- if (occurrences.length > MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch) {
293
- await updateDeletion(trx, claim, {
294
- source_key: sourceKey,
295
- occurrence_cursor: batch.at(-1).id
296
- });
297
- return;
298
- }
299
- 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();
300
- await updateDeletion(trx, claim, {
301
- source_key: null,
302
- occurrence_cursor: null
303
- });
304
- });
305
- return false;
306
- }
307
- async function processStatus(db, claim) {
308
- await withTransaction(db, async (trx) => {
309
- if (await exactCleanupRowsRemain(trx, claim, false)) throw new Error("Collection deletion cleanup is incomplete");
310
- await trx.deleteFrom("_emdash_media_usage_reconciliations").where("collection_id", "=", claim.collectionId).where("collection_slug", "=", claim.collectionSlug).where(liveLeaseGuard(trx, claim)).execute();
311
- 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();
312
- await updateDeletion(trx, claim, { phase: "finalize" });
313
- });
314
- return false;
315
- }
316
- async function finalizeDeletion(db, claim) {
317
- if (await tableExists(db, `ec_${claim.collectionSlug}`)) throw new Error("Collection table still exists during deletion finalization");
318
- if (await exactCleanupRowsRemain(db, claim)) throw new Error("Collection deletion is incomplete");
319
- 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");
320
- 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();
321
- if (Number(result.numDeletedRows ?? 0) !== 1) throw new Error("Collection deletion lost finalization");
322
- return true;
323
- }
324
- async function exactCleanupRowsRemain(db, claim, includeStatus = true) {
325
- const row = (await sql`
326
- SELECT
327
- EXISTS (
328
- SELECT 1 FROM _emdash_media_usage_work
329
- WHERE collection_id = ${claim.collectionId}
330
- ) AS work_present,
331
- EXISTS (
332
- SELECT 1 FROM _emdash_media_usage_sources
333
- WHERE source_type = 'content' AND collection_id = ${claim.collectionId}
334
- ) AS source_present,
335
- EXISTS (
336
- SELECT 1 FROM _emdash_media_usage_index_status
337
- WHERE adapter_id = 'content-media'
338
- AND scope_type = 'collection'
339
- AND scope_key = ${claim.collectionSlug}
340
- AND collection_id = ${claim.collectionId}
341
- ) AS status_present
342
- `.execute(db)).rows[0];
343
- return Boolean(row?.work_present) || Boolean(row?.source_present) || includeStatus && Boolean(row?.status_present);
344
- }
345
- async function updateDeletion(db, claim, values) {
346
- const result = await db.updateTable("_emdash_media_usage_collection_deletions").set({
347
- ...values,
348
- attempt_count: 0,
349
- last_error_code: null,
350
- updated_at: collectionDeletionCurrentTimestamp(db)
351
- }).where("collection_id", "=", claim.collectionId).where("state", "=", "leased").where("lease_token", "=", claim.leaseToken).where(liveLeaseGuard(db, claim)).executeTakeFirst();
352
- if (Number(result.numUpdatedRows ?? 0) !== 1) throw new Error("Collection deletion lease was lost");
353
- }
354
- function liveLeaseGuard(db, claim) {
355
- return isPostgres(db) ? sql`EXISTS (
356
- SELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion
357
- WHERE deletion.collection_id = ${claim.collectionId}
358
- AND deletion.state = 'leased'
359
- AND deletion.lease_token = ${claim.leaseToken}
360
- AND deletion.lease_expires_at::timestamptz > clock_timestamp()
361
- )` : sql`EXISTS (
362
- SELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion
363
- WHERE deletion.collection_id = ${claim.collectionId}
364
- AND deletion.state = 'leased'
365
- AND deletion.lease_token = ${claim.leaseToken}
366
- AND deletion.lease_expires_at > strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
367
- )`;
368
- }
369
- function retryDelaySeconds$2(attemptCount) {
370
- return Math.min(MEDIA_USAGE_COLLECTION_DELETION_LIMITS.retryMaxSeconds, MEDIA_USAGE_COLLECTION_DELETION_LIMITS.retryBaseSeconds * 2 ** attemptCount);
371
- }
372
-
373
- //#endregion
374
- //#region src/media/usage/reconciliation.ts
375
- const ACTIVATION_KEY = "incremental_capture";
376
- const CONTENT_ADAPTER_ID = "content-media";
377
- const COLLECTION_SCOPE = "collection";
378
- const MAX_CANDIDATES = 100;
379
- const MAX_PORTABLE_DURATION_SECONDS = 365 * 24 * 60 * 60;
380
- const STABLE_ERROR_CODE_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
381
- var MediaUsageReconciliationRepository = class {
382
- constructor(db) {
383
- this.db = db;
384
- }
385
- async findByIdentity(collectionId, runToken) {
386
- assertIdentity({
387
- collectionId,
388
- runToken
389
- });
390
- const row = await this.db.selectFrom("_emdash_media_usage_reconciliations").selectAll().where("collection_id", "=", collectionId).where("run_token", "=", runToken).executeTakeFirst();
391
- return row ? rowToRecord(row) : null;
392
- }
393
- async beginRun(claim) {
394
- const now = timestampOffset(this.db, 0);
395
- const sameRun = sql`status = 'running' AND cursor = ${claim.runToken}`;
396
- return (await this.db.updateTable("_emdash_media_usage_index_status as status").set({
397
- status: "running",
398
- started_at: sql`CASE WHEN ${sameRun} THEN started_at ELSE ${now} END`,
399
- completed_at: null,
400
- cursor: claim.runToken,
401
- indexed_source_count: 0,
402
- failed_source_count: 0,
403
- last_error_code: null,
404
- change_epoch: sql`CASE WHEN ${sameRun} THEN change_epoch ELSE change_epoch + 1 END`,
405
- reconciliation_required: 1,
406
- updated_at: now
407
- }).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;
408
- }
409
- async findScanUpperId(claim) {
410
- const tableName = contentTableName(claim.collectionSlug);
411
- return (await sql`
412
- SELECT content.id
413
- FROM ${sql.ref(tableName)} AS content
414
- WHERE ${this.liveClaimExistsSql(claim)}
415
- ORDER BY content.id DESC
416
- LIMIT 1
417
- `.execute(this.db)).rows[0]?.id ?? null;
418
- }
419
- async initializeScan(input) {
420
- const result = await this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
421
- target_epoch: input.targetEpoch,
422
- field_fingerprint: input.fieldFingerprint,
423
- phase: "scan",
424
- scan_cursor: null,
425
- scan_upper_id: input.scanUpperId,
426
- source_cursor: null,
427
- source_upper_key: null,
428
- attempt_count: 0,
429
- last_error_code: null,
430
- updated_at: timestampOffset(this.db, 0)
431
- }).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();
432
- return Number(result.numUpdatedRows ?? 0) === 1;
433
- }
434
- async findScanPage(reconciliation, limit) {
435
- if (!Number.isSafeInteger(limit) || limit < 1 || limit > 50) throw new Error("Reconciliation scan page limit must be from 1 to 50");
436
- if (!reconciliation.leaseToken || reconciliation.targetEpoch === null) return [];
437
- const tableName = contentTableName(reconciliation.collectionSlug);
438
- const lowerBound = reconciliation.scanCursor ? sql`AND content.id > ${reconciliation.scanCursor}` : sql``;
439
- const upperBound = reconciliation.scanUpperId ? sql`AND content.id <= ${reconciliation.scanUpperId}` : sql`AND 1 = 0`;
440
- return (await sql`
441
- SELECT content.id
442
- FROM ${sql.ref(tableName)} AS content
443
- WHERE 1 = 1
444
- ${lowerBound}
445
- ${upperBound}
446
- AND ${this.liveClaimExistsSql(reconciliation)}
447
- ORDER BY content.id ASC
448
- LIMIT ${limit}
449
- `.execute(this.db)).rows.map((row) => row.id);
450
- }
451
- async checkpointScan(input) {
452
- let query = this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
453
- scan_cursor: input.nextCursor,
454
- attempt_count: 0,
455
- last_error_code: null,
456
- updated_at: timestampOffset(this.db, 0)
457
- }).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));
458
- query = input.previousCursor ? query.where("reconciliation.scan_cursor", "=", input.previousCursor) : query.where("reconciliation.scan_cursor", "is", null);
459
- const result = await query.executeTakeFirst();
460
- return Number(result.numUpdatedRows ?? 0) === 1;
461
- }
462
- async ownsRun(claim, targetEpoch) {
463
- const result = await sql`
464
- SELECT ${this.statusOwnsRun(claim, targetEpoch)} AS owned
465
- `.execute(this.db);
466
- return Boolean(result.rows[0]?.owned);
467
- }
468
- async restartRun(claim, previousEpoch) {
469
- const now = timestampOffset(this.db, 0);
470
- const interruptedRestart = sql`status = 'running'
471
- AND cursor = ${claim.runToken}
472
- AND change_epoch > ${previousEpoch}`;
473
- return (await this.db.updateTable("_emdash_media_usage_index_status as status").set({
474
- status: "running",
475
- started_at: sql`CASE WHEN ${interruptedRestart} THEN started_at ELSE ${now} END`,
476
- completed_at: null,
477
- cursor: claim.runToken,
478
- last_error_code: null,
479
- change_epoch: sql`CASE WHEN ${interruptedRestart} THEN change_epoch ELSE change_epoch + 1 END`,
480
- reconciliation_required: 1,
481
- updated_at: now
482
- }).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;
483
- }
484
- async restartScan(input) {
485
- const result = await this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
486
- target_epoch: input.targetEpoch,
487
- field_fingerprint: input.fieldFingerprint,
488
- phase: "scan",
489
- scan_cursor: null,
490
- scan_upper_id: input.scanUpperId,
491
- source_cursor: null,
492
- source_upper_key: null,
493
- attempt_count: 0,
494
- next_attempt_at: timestampOffset(this.db, 0),
495
- last_error_code: null,
496
- updated_at: timestampOffset(this.db, 0)
497
- }).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();
498
- return Number(result.numUpdatedRows ?? 0) === 1;
499
- }
500
- async findWorkBarrier(collectionId) {
501
- if (!collectionId) throw new Error("Reconciliation work barrier requires a collection ID");
502
- 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();
503
- if (failed) return {
504
- state: "failed",
505
- errorCode: failed.last_error_code ?? "MEDIA_USAGE_PROCESSING_FAILED"
506
- };
507
- return await this.db.selectFrom("_emdash_media_usage_work").select("content_id").where("collection_id", "=", collectionId).limit(1).executeTakeFirst() ? { state: "pending" } : { state: "empty" };
508
- }
509
- async findSourceUpperKey(claim, targetEpoch) {
510
- 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;
511
- }
512
- async transitionToSources(input) {
513
- const result = await this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
514
- phase: "sources",
515
- source_cursor: null,
516
- source_upper_key: input.sourceUpperKey,
517
- attempt_count: 0,
518
- last_error_code: null,
519
- updated_at: timestampOffset(this.db, 0)
520
- }).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();
521
- return Number(result.numUpdatedRows ?? 0) === 1;
522
- }
523
- async findSourcePage(reconciliation, limit) {
524
- if (!Number.isSafeInteger(limit) || limit < 1 || limit > 50) throw new Error("Reconciliation source page limit must be from 1 to 50");
525
- if (!reconciliation.leaseToken || reconciliation.targetEpoch === null) return [];
526
- let query = this.db.selectFrom("_emdash_media_usage_sources as source").select([
527
- "source.source_key",
528
- "source.content_id",
529
- "source.source_variant"
530
- ]).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));
531
- if (reconciliation.sourceCursor) query = query.where("source.source_key", ">", reconciliation.sourceCursor);
532
- if (reconciliation.sourceUpperKey) query = query.where("source.source_key", "<=", reconciliation.sourceUpperKey);
533
- else query = query.where(sql`1 = 0`);
534
- return (await query.orderBy("source.source_key").limit(limit).execute()).map((row) => ({
535
- sourceKey: row.source_key,
536
- contentId: row.content_id,
537
- sourceVariant: row.source_variant
538
- }));
539
- }
540
- async findMissingContentIds(collectionSlug, contentIds) {
541
- const unique = [...new Set(contentIds)];
542
- if (unique.length === 0) return [];
543
- if (unique.length > 50 || unique.some((contentId) => !contentId)) throw new Error("Reconciliation source page has invalid content identity");
544
- const tableName = contentTableName(collectionSlug);
545
- const existing = await sql`
546
- SELECT id FROM ${sql.ref(tableName)} WHERE id IN (${sql.join(unique)})
547
- `.execute(this.db);
548
- const present = new Set(existing.rows.map((row) => row.id));
549
- return unique.filter((contentId) => !present.has(contentId));
550
- }
551
- async checkpointSources(input) {
552
- let query = this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
553
- source_cursor: input.nextCursor,
554
- attempt_count: 0,
555
- last_error_code: null,
556
- updated_at: timestampOffset(this.db, 0)
557
- }).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));
558
- query = input.previousCursor ? query.where("reconciliation.source_cursor", "=", input.previousCursor) : query.where("reconciliation.source_cursor", "is", null);
559
- const result = await query.executeTakeFirst();
560
- return Number(result.numUpdatedRows ?? 0) === 1;
561
- }
562
- async finishFailedCoverage(collectionId, runToken) {
563
- assertIdentity({
564
- collectionId,
565
- runToken
566
- });
567
- const now = timestampOffset(this.db, 0);
568
- const result = await this.db.updateTable("_emdash_media_usage_index_status as status").set({
569
- status: sql`CASE WHEN EXISTS (
570
- SELECT 1 FROM _emdash_media_usage_sources AS source
571
- WHERE source.source_type = 'content'
572
- AND source.collection_id = ${collectionId}
573
- AND source.identity_version = 1
574
- LIMIT 1
575
- ) THEN 'partial' ELSE 'failed' END`,
576
- completed_at: null,
577
- cursor: null,
578
- last_error_code: sql`CASE WHEN (
579
- SELECT reconciliation.last_error_code
580
- FROM _emdash_media_usage_reconciliations AS reconciliation
581
- WHERE reconciliation.collection_id = ${collectionId}
582
- AND reconciliation.run_token = ${runToken}
583
- ) = 'MEDIA_USAGE_RECONCILIATION_ENTRY_FAILED'
584
- THEN COALESCE(
585
- (SELECT work.last_error_code
586
- FROM _emdash_media_usage_work AS work
587
- WHERE work.collection_id = ${collectionId} AND work.state = 'failed'
588
- ORDER BY work.content_id LIMIT 1),
589
- 'MEDIA_USAGE_RECONCILIATION_ENTRY_FAILED'
590
- )
591
- ELSE (
592
- SELECT reconciliation.last_error_code
593
- FROM _emdash_media_usage_reconciliations AS reconciliation
594
- WHERE reconciliation.collection_id = ${collectionId}
595
- AND reconciliation.run_token = ${runToken}
596
- ) END`,
597
- reconciliation_required: 1,
598
- updated_at: now
599
- }).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();
600
- return Number(result.numUpdatedRows ?? 0) === 1;
601
- }
602
- async finalizeCoverage(input) {
603
- const now = timestampOffset(this.db, 0);
604
- const result = await this.db.updateTable("_emdash_media_usage_index_status as status").set({
605
- status: "complete",
606
- schema_version: input.schemaVersion,
607
- completed_at: now,
608
- cursor: null,
609
- last_error_code: null,
610
- reconciliation_required: 0,
611
- updated_at: now
612
- }).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();
613
- return Number(result.numUpdatedRows ?? 0) === 1;
614
- }
615
- async deleteFinalized(claim) {
616
- 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();
617
- return Number(result.numDeletedRows ?? 0) === 1;
618
- }
619
- async deleteOneObsolete() {
620
- return (await sql`
621
- DELETE FROM _emdash_media_usage_reconciliations
622
- WHERE (collection_id, run_token) IN (
623
- SELECT reconciliation.collection_id, reconciliation.run_token
624
- FROM _emdash_media_usage_reconciliations AS reconciliation
625
- INNER JOIN _emdash_media_usage_index_status AS status
626
- ON status.collection_id = reconciliation.collection_id
627
- AND status.scope_key = reconciliation.collection_slug
628
- WHERE status.adapter_id = ${CONTENT_ADAPTER_ID}
629
- AND status.scope_type = ${COLLECTION_SCOPE}
630
- AND status.reconciliation_required = 0
631
- ORDER BY reconciliation.updated_at, reconciliation.collection_id
632
- LIMIT 1
633
- )
634
- RETURNING collection_id
635
- `.execute(this.db)).rows.length === 1;
636
- }
637
- liveClaimExists(claim) {
638
- return this.liveClaimExistsSql(claim);
639
- }
640
- liveClaimExistsSql(claim) {
641
- return sql`EXISTS (
642
- SELECT 1
643
- FROM _emdash_media_usage_reconciliations AS reconciliation
644
- INNER JOIN _emdash_collections AS collection
645
- ON collection.id = reconciliation.collection_id
646
- AND collection.slug = reconciliation.collection_slug
647
- WHERE reconciliation.collection_id = ${claim.collectionId}
648
- AND reconciliation.collection_slug = ${claim.collectionSlug}
649
- AND reconciliation.run_token = ${claim.runToken}
650
- AND reconciliation.state = 'leased'
651
- AND reconciliation.lease_token = ${claim.leaseToken}
652
- AND ${liveLease(this.db, "reconciliation.lease_expires_at")}
653
- AND EXISTS (
654
- SELECT 1 FROM _emdash_media_usage_activation AS activation
655
- WHERE activation.task_key = ${ACTIVATION_KEY}
656
- AND activation.state = 'active'
657
- )
658
- AND NOT EXISTS (
659
- SELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion
660
- WHERE deletion.collection_id = reconciliation.collection_id
661
- )
662
- )`;
663
- }
664
- statusOwnsRun(claim, targetEpoch) {
665
- return sql`EXISTS (
666
- SELECT 1 FROM _emdash_media_usage_index_status AS status
667
- WHERE status.adapter_id = ${CONTENT_ADAPTER_ID}
668
- AND status.scope_type = ${COLLECTION_SCOPE}
669
- AND status.collection_id = ${claim.collectionId}
670
- AND status.scope_key = ${claim.collectionSlug}
671
- AND status.capture_state = 'active'
672
- AND status.reconciliation_required = 1
673
- AND status.status = 'running'
674
- AND status.cursor = ${claim.runToken}
675
- AND status.change_epoch = ${targetEpoch}
676
- )`;
677
- }
678
- async seedNextCandidate() {
679
- const runToken = ulid();
680
- const now = timestampOffset(this.db, 0);
681
- return (await sql`
682
- INSERT INTO _emdash_media_usage_reconciliations (
683
- collection_id,
684
- collection_slug,
685
- run_token,
686
- next_attempt_at,
687
- updated_at
688
- )
689
- SELECT status.collection_id, status.scope_key, ${runToken}, ${now}, ${now}
690
- FROM _emdash_media_usage_index_status AS status
691
- INNER JOIN _emdash_collections AS collection
692
- ON collection.id = status.collection_id
693
- AND collection.slug = status.scope_key
694
- WHERE status.adapter_id = ${CONTENT_ADAPTER_ID}
695
- AND status.scope_type = ${COLLECTION_SCOPE}
696
- AND status.capture_state = 'active'
697
- AND status.reconciliation_required = 1
698
- AND status.collection_id IS NOT NULL
699
- AND EXISTS (
700
- SELECT 1 FROM _emdash_media_usage_activation AS activation
701
- WHERE activation.task_key = ${ACTIVATION_KEY}
702
- AND activation.state = 'active'
703
- )
704
- AND NOT EXISTS (
705
- SELECT 1 FROM _emdash_media_usage_reconciliations AS existing
706
- WHERE existing.collection_id = status.collection_id
707
- )
708
- AND NOT EXISTS (
709
- SELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion
710
- WHERE deletion.collection_id = status.collection_id
711
- )
712
- ORDER BY status.collection_id
713
- LIMIT 1
714
- ON CONFLICT (collection_id) DO NOTHING
715
- RETURNING collection_id
716
- `.execute(this.db)).rows.length === 1;
717
- }
718
- async findDue(limit) {
719
- assertLimit(limit);
720
- const nextAttemptIsDue = timestampIsDue(this.db, "next_attempt_at");
721
- return (await sql`
722
- WITH pending_candidates AS (
723
- SELECT * FROM _emdash_media_usage_reconciliations
724
- WHERE state = 'pending' AND ${nextAttemptIsDue}
725
- ORDER BY next_attempt_at, updated_at, collection_id
726
- LIMIT ${limit}
727
- ), retry_candidates AS (
728
- SELECT * FROM _emdash_media_usage_reconciliations
729
- WHERE state = 'retry' AND ${nextAttemptIsDue}
730
- ORDER BY next_attempt_at, updated_at, collection_id
731
- LIMIT ${limit}
732
- ), leased_candidates AS (
733
- SELECT * FROM _emdash_media_usage_reconciliations
734
- WHERE state = 'leased' AND ${timestampIsDue(this.db, "lease_expires_at")}
735
- ORDER BY lease_expires_at, updated_at, collection_id
736
- LIMIT ${limit}
737
- ), candidates AS (
738
- SELECT * FROM pending_candidates
739
- UNION ALL SELECT * FROM retry_candidates
740
- UNION ALL SELECT * FROM leased_candidates
741
- )
742
- SELECT * FROM candidates
743
- ORDER BY CASE WHEN state = 'leased' THEN lease_expires_at ELSE next_attempt_at END,
744
- updated_at,
745
- collection_id
746
- LIMIT ${limit}
747
- `.execute(this.db)).rows.map(rowToRecord);
748
- }
749
- async findFailed(limit) {
750
- assertLimit(limit);
751
- 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([
752
- eb("status.reconciliation_required", "=", 0),
753
- eb.and([eb("status.status", "=", "running"), eb("status.cursor", "=", eb.ref("reconciliation.run_token"))]),
754
- eb.and([eb("status.cursor", "is", null), eb("status.change_epoch", ">", eb.ref("reconciliation.target_epoch"))])
755
- ])).orderBy("reconciliation.updated_at").orderBy("reconciliation.collection_id").limit(limit).execute()).map(rowToRecord);
756
- }
757
- async claim(input) {
758
- assertIdentity(input);
759
- assertDuration(input.leaseDurationSeconds, "lease duration");
760
- const leaseToken = ulid();
761
- const row = await this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
762
- state: "leased",
763
- lease_token: leaseToken,
764
- lease_expires_at: timestampOffset(this.db, input.leaseDurationSeconds),
765
- updated_at: timestampOffset(this.db, 0)
766
- }).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();
767
- return row ? {
768
- ...rowToRecord(row),
769
- leaseToken
770
- } : null;
771
- }
772
- async release(input) {
773
- assertLeaseIdentity(input);
774
- assertDuration(input.delaySeconds, "release delay", true);
775
- const result = await this.db.updateTable("_emdash_media_usage_reconciliations").set({
776
- state: "pending",
777
- next_attempt_at: timestampOffset(this.db, input.delaySeconds),
778
- lease_token: null,
779
- lease_expires_at: null,
780
- updated_at: timestampOffset(this.db, 0)
781
- }).where("collection_id", "=", input.collectionId).where("run_token", "=", input.runToken).where("state", "=", "leased").where("lease_token", "=", input.leaseToken).where(liveLease(this.db)).executeTakeFirst();
782
- return Number(result.numUpdatedRows ?? 0) === 1;
783
- }
784
- async recordFailure(input) {
785
- assertLeaseIdentity(input);
786
- if (!STABLE_ERROR_CODE_PATTERN.test(input.errorCode)) throw new Error("Reconciliation failure requires a stable error code");
787
- assertDuration(input.retryDelaySeconds, "retry delay", true);
788
- const result = await this.db.updateTable("_emdash_media_usage_reconciliations").set({
789
- state: input.terminal ? "failed" : sql`CASE WHEN attempt_count >= 4 THEN 'failed' ELSE 'retry' END`,
790
- ...input.terminal ? { target_epoch: sql`COALESCE(
791
- target_epoch,
792
- (SELECT status.change_epoch
793
- FROM _emdash_media_usage_index_status AS status
794
- WHERE status.adapter_id = ${CONTENT_ADAPTER_ID}
795
- AND status.scope_type = ${COLLECTION_SCOPE}
796
- AND status.collection_id = ${input.collectionId}
797
- AND status.status = 'running'
798
- AND status.cursor = ${input.runToken})
799
- )` } : {},
800
- attempt_count: sql`attempt_count + 1`,
801
- next_attempt_at: timestampOffset(this.db, input.retryDelaySeconds),
802
- lease_token: null,
803
- lease_expires_at: null,
804
- last_error_code: input.errorCode,
805
- updated_at: timestampOffset(this.db, 0)
806
- }).where("collection_id", "=", input.collectionId).where("run_token", "=", input.runToken).where("state", "=", "leased").where("lease_token", "=", input.leaseToken).where(liveLease(this.db)).executeTakeFirst();
807
- return Number(result.numUpdatedRows ?? 0) === 1;
808
- }
809
- async recordEntryFailure(claim) {
810
- const result = await this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
811
- state: "failed",
812
- attempt_count: sql`attempt_count + 1`,
813
- next_attempt_at: timestampOffset(this.db, 0),
814
- lease_token: null,
815
- lease_expires_at: null,
816
- last_error_code: "MEDIA_USAGE_RECONCILIATION_ENTRY_FAILED",
817
- updated_at: timestampOffset(this.db, 0)
818
- }).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();
819
- return Number(result.numUpdatedRows ?? 0) === 1;
820
- }
821
- async resetFailedForNewEpoch(observed) {
822
- const collectionId = "collection_id" in observed ? observed.collection_id : observed.collectionId;
823
- const runToken = "run_token" in observed ? observed.run_token : observed.runToken;
824
- const targetEpoch = "target_epoch" in observed ? observed.target_epoch : observed.targetEpoch;
825
- if (targetEpoch === null) return false;
826
- const now = timestampOffset(this.db, 0);
827
- const result = await this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
828
- state: "pending",
829
- phase: "scan",
830
- target_epoch: null,
831
- field_fingerprint: null,
832
- scan_cursor: null,
833
- scan_upper_id: null,
834
- source_cursor: null,
835
- source_upper_key: null,
836
- attempt_count: 0,
837
- next_attempt_at: now,
838
- lease_token: null,
839
- lease_expires_at: null,
840
- last_error_code: null,
841
- updated_at: now
842
- }).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();
843
- return Number(result.numUpdatedRows ?? 0) === 1;
844
- }
845
- };
846
- function rowToRecord(row) {
847
- if (!isState(row.state) || !isPhase(row.phase) || !Number.isSafeInteger(row.attempt_count)) throw new Error("Invalid media usage reconciliation lifecycle");
848
- return {
849
- collectionId: row.collection_id,
850
- collectionSlug: row.collection_slug,
851
- runToken: row.run_token,
852
- targetEpoch: row.target_epoch,
853
- fieldFingerprint: row.field_fingerprint,
854
- state: row.state,
855
- phase: row.phase,
856
- scanCursor: row.scan_cursor,
857
- scanUpperId: row.scan_upper_id,
858
- sourceCursor: row.source_cursor,
859
- sourceUpperKey: row.source_upper_key,
860
- attemptCount: row.attempt_count,
861
- nextAttemptAt: row.next_attempt_at,
862
- leaseToken: row.lease_token,
863
- leaseExpiresAt: row.lease_expires_at,
864
- lastErrorCode: row.last_error_code,
865
- createdAt: row.created_at,
866
- updatedAt: row.updated_at
867
- };
868
- }
869
- function isState(state) {
870
- return state === "pending" || state === "retry" || state === "leased" || state === "failed";
871
- }
872
- function isPhase(phase) {
873
- return phase === "scan" || phase === "sources";
874
- }
875
- function assertLimit(limit) {
876
- if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_CANDIDATES) throw new Error("Reconciliation candidate limit must be from 1 to 100");
877
- }
878
- function assertIdentity(input) {
879
- if (!input.collectionId || !input.runToken) throw new Error("Reconciliation requires an exact collection and run token");
880
- }
881
- function assertLeaseIdentity(input) {
882
- assertIdentity(input);
883
- if (!input.leaseToken) throw new Error("Reconciliation requires a lease token");
884
- }
885
- function assertDuration(value, label, allowZero = false) {
886
- if (!Number.isSafeInteger(value) || value < (allowZero ? 0 : 1) || value > MAX_PORTABLE_DURATION_SECONDS) throw new Error(`Reconciliation ${label} is outside the portable range`);
887
- }
888
- function liveLease(db, column = "lease_expires_at") {
889
- const expiry = sql.ref(column);
890
- 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')`;
891
- }
892
- function timestampIsDue(db, column) {
893
- const value = sql.ref(column);
894
- 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')`;
895
- }
896
- function timestampOffset(db, offsetSeconds) {
897
- if (isPostgres(db)) return sql`to_char(
898
- (clock_timestamp() AT TIME ZONE 'UTC') + (${offsetSeconds} * INTERVAL '1 second'),
899
- 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
900
- )`;
901
- return sql`strftime(
902
- '%Y-%m-%dT%H:%M:%fZ',
903
- 'now',
904
- ${`${offsetSeconds >= 0 ? "+" : ""}${offsetSeconds} seconds`}
905
- )`;
906
- }
907
- function contentTableName(collectionSlug) {
908
- validateIdentifier(collectionSlug, "collection slug");
909
- const tableName = `ec_${collectionSlug}`;
910
- validateIdentifier(tableName, "content table");
911
- return tableName;
912
- }
913
-
914
- //#endregion
915
- //#region src/media/usage/reconciliation-processor.ts
916
- const MEDIA_USAGE_RECONCILIATION_LIMITS = Object.freeze({
917
- candidatesPerTick: 4,
918
- pageSize: 50,
919
- leaseDurationSeconds: 60,
920
- maxAttempts: 5,
921
- retryBaseSeconds: 30,
922
- retryMaxSeconds: 900,
923
- retryJitterRatio: .25,
924
- maxQueriesPerTick: 20
925
- });
926
- async function processDueMediaUsageReconciliation(db) {
927
- if ((await db.selectFrom("_emdash_media_usage_activation").select("state").where("task_key", "=", "incremental_capture").executeTakeFirst())?.state !== "active") return "inactive";
928
- const reconciliation = new MediaUsageReconciliationRepository(db);
929
- if (await reconciliation.deleteOneObsolete()) return "completed";
930
- const [failed] = await reconciliation.findFailed(1);
931
- if (failed) {
932
- if (await reconciliation.finishFailedCoverage(failed.collectionId, failed.runToken)) return "failed";
933
- if (await reconciliation.resetFailedForNewEpoch(failed)) return "advanced";
934
- }
935
- await reconciliation.seedNextCandidate();
936
- const candidates = await reconciliation.findDue(MEDIA_USAGE_RECONCILIATION_LIMITS.candidatesPerTick);
937
- let claim = null;
938
- for (const candidate of candidates) {
939
- claim = await reconciliation.claim({
940
- collectionId: candidate.collectionId,
941
- runToken: candidate.runToken,
942
- leaseDurationSeconds: MEDIA_USAGE_RECONCILIATION_LIMITS.leaseDurationSeconds
943
- });
944
- if (claim) break;
945
- }
946
- if (!claim) return candidates.length === 0 ? "not_due" : "claim_lost";
947
- try {
948
- return await processClaimedReconciliation(db, claim);
949
- } catch (error) {
950
- const terminal = claim.attemptCount + 1 >= MEDIA_USAGE_RECONCILIATION_LIMITS.maxAttempts;
951
- if (!await reconciliation.recordFailure({
952
- collectionId: claim.collectionId,
953
- runToken: claim.runToken,
954
- leaseToken: claim.leaseToken,
955
- errorCode: "MEDIA_USAGE_RECONCILIATION_FAILED",
956
- retryDelaySeconds: retryDelaySeconds$1(claim.attemptCount),
957
- terminal
958
- })) return "claim_lost";
959
- if (terminal) await reconciliation.finishFailedCoverage(claim.collectionId, claim.runToken);
960
- console.error("[media-usage:reconciliation] Processing failed:", error);
961
- return terminal ? "failed" : "retry";
962
- }
963
- }
964
- async function processClaimedMediaUsageReconciliationScan(db, claim, options = {}) {
965
- const reconciliation = new MediaUsageReconciliationRepository(db);
966
- let current = await reconciliation.findByIdentity(claim.collectionId, claim.runToken);
967
- if (!current || current.leaseToken !== claim.leaseToken || current.phase !== "scan") return "deferred";
968
- let fields;
969
- let fieldFingerprint;
970
- if (current.targetEpoch === null) {
971
- const targetEpoch = await reconciliation.beginRun(claim);
972
- if (targetEpoch === null) {
973
- await reconciliation.release({
974
- ...claim,
975
- delaySeconds: 30
976
- });
977
- return "deferred";
978
- }
979
- fields = await loadContentMediaUsageFields(db, claim.collectionSlug, claim.collectionId);
980
- fieldFingerprint = await buildContentMediaUsageFieldFingerprint(fields);
981
- const scanUpperId = fields.extractionFields.length === 0 ? null : await reconciliation.findScanUpperId(claim);
982
- if (!await reconciliation.initializeScan({
983
- claim,
984
- targetEpoch,
985
- fieldFingerprint,
986
- scanUpperId
987
- })) return "deferred";
988
- current = await reconciliation.findByIdentity(claim.collectionId, claim.runToken);
989
- if (!current) return "deferred";
990
- } else {
991
- fields = await loadContentMediaUsageFields(db, claim.collectionSlug, claim.collectionId);
992
- fieldFingerprint = await buildContentMediaUsageFieldFingerprint(fields);
993
- }
994
- if (current.fieldFingerprint !== fieldFingerprint || current.targetEpoch === null) return "restart_required";
995
- const contentIds = await reconciliation.findScanPage(current, 50);
996
- if (contentIds.length === 0) {
997
- if (options.releaseOnExhausted ?? true) await reconciliation.release({
998
- ...claim,
999
- delaySeconds: 30
1000
- });
1001
- return "exhausted";
1002
- }
1003
- await new MediaUsageWorkRepository(db).enqueueReconciliationPage({
1004
- collectionId: claim.collectionId,
1005
- collectionSlug: claim.collectionSlug,
1006
- runToken: claim.runToken,
1007
- leaseToken: claim.leaseToken,
1008
- changeEpoch: current.targetEpoch,
1009
- phase: "scan",
1010
- contentIds
1011
- });
1012
- const nextCursor = contentIds.at(-1);
1013
- if (!await reconciliation.checkpointScan({
1014
- claim,
1015
- targetEpoch: current.targetEpoch,
1016
- previousCursor: current.scanCursor,
1017
- nextCursor
1018
- })) return "deferred";
1019
- if (!await reconciliation.release({
1020
- ...claim,
1021
- delaySeconds: 0
1022
- })) return "deferred";
1023
- return "advanced";
1024
- }
1025
- async function processClaimedReconciliation(db, claim) {
1026
- const reconciliation = new MediaUsageReconciliationRepository(db);
1027
- let current = await reconciliation.findByIdentity(claim.collectionId, claim.runToken);
1028
- if (!current || current.leaseToken !== claim.leaseToken) return "claim_lost";
1029
- if (current.targetEpoch !== null && !await reconciliation.ownsRun(claim, current.targetEpoch)) return restartReconciliation(db, reconciliation, claim, current);
1030
- if (current.phase === "scan") {
1031
- const outcome = await processClaimedMediaUsageReconciliationScan(db, claim, { releaseOnExhausted: false });
1032
- if (outcome === "restart_required") {
1033
- current = await reconciliation.findByIdentity(claim.collectionId, claim.runToken) ?? current;
1034
- return restartReconciliation(db, reconciliation, claim, current);
1035
- }
1036
- if (outcome !== "exhausted") return outcome;
1037
- current = await reconciliation.findByIdentity(claim.collectionId, claim.runToken) ?? current;
1038
- const barrier = await reconciliation.findWorkBarrier(claim.collectionId);
1039
- if (barrier.state === "failed") return failReconciliation(reconciliation, claim, barrier.errorCode, true);
1040
- if (barrier.state === "pending") {
1041
- await reconciliation.release({
1042
- ...claim,
1043
- delaySeconds: 30
1044
- });
1045
- return "deferred";
1046
- }
1047
- if (current.targetEpoch === null || current.fieldFingerprint === null) return "claim_lost";
1048
- const sourceUpperKey = await reconciliation.findSourceUpperKey(claim, current.targetEpoch);
1049
- if (!await reconciliation.transitionToSources({
1050
- claim,
1051
- targetEpoch: current.targetEpoch,
1052
- fieldFingerprint: current.fieldFingerprint,
1053
- sourceUpperKey
1054
- })) return "claim_lost";
1055
- if (!await reconciliation.release({
1056
- ...claim,
1057
- delaySeconds: 0
1058
- })) return "claim_lost";
1059
- return "advanced";
1060
- }
1061
- return processSourcePhase(db, reconciliation, claim, current);
1062
- }
1063
- async function processSourcePhase(db, reconciliation, claim, current) {
1064
- if (current.targetEpoch === null || current.fieldFingerprint === null) return "claim_lost";
1065
- const fields = await loadContentMediaUsageFields(db, claim.collectionSlug, claim.collectionId);
1066
- const fieldFingerprint = await buildContentMediaUsageFieldFingerprint(fields);
1067
- if (fieldFingerprint !== current.fieldFingerprint) return restartReconciliation(db, reconciliation, claim, current, fields, fieldFingerprint);
1068
- const page = await reconciliation.findSourcePage(current, MEDIA_USAGE_RECONCILIATION_LIMITS.pageSize);
1069
- if (page.length > 0) {
1070
- if (page.some((source) => !source.contentId || source.sourceVariant !== "columns" && source.sourceVariant !== "draft_overlay")) return failReconciliation(reconciliation, claim, "MEDIA_USAGE_RECONCILIATION_INVALID_SOURCE", false);
1071
- const contentIds = [...new Set(page.map((source) => source.contentId))];
1072
- const enqueueIds = fields.extractionFields.length === 0 ? contentIds : await reconciliation.findMissingContentIds(claim.collectionSlug, contentIds);
1073
- if (enqueueIds.length > 0) await new MediaUsageWorkRepository(db).enqueueReconciliationPage({
1074
- collectionId: claim.collectionId,
1075
- collectionSlug: claim.collectionSlug,
1076
- runToken: claim.runToken,
1077
- leaseToken: claim.leaseToken,
1078
- changeEpoch: current.targetEpoch,
1079
- phase: "sources",
1080
- contentIds: enqueueIds
1081
- });
1082
- if (!await reconciliation.checkpointSources({
1083
- claim,
1084
- targetEpoch: current.targetEpoch,
1085
- previousCursor: current.sourceCursor,
1086
- nextCursor: page.at(-1).sourceKey
1087
- })) return "claim_lost";
1088
- if (!await reconciliation.release({
1089
- ...claim,
1090
- delaySeconds: 0
1091
- })) return "claim_lost";
1092
- return "advanced";
1093
- }
1094
- const barrier = await reconciliation.findWorkBarrier(claim.collectionId);
1095
- if (barrier.state === "failed") return failReconciliation(reconciliation, claim, barrier.errorCode, true);
1096
- if (barrier.state === "pending") {
1097
- await reconciliation.release({
1098
- ...claim,
1099
- delaySeconds: 30
1100
- });
1101
- return "deferred";
1102
- }
1103
- if (!await reconciliation.finalizeCoverage({
1104
- claim,
1105
- targetEpoch: current.targetEpoch,
1106
- fieldFingerprint,
1107
- schemaVersion: CONTENT_SOURCE_SCHEMA_VERSION
1108
- })) return "claim_lost";
1109
- if (!await reconciliation.deleteFinalized(claim)) return "claim_lost";
1110
- return "completed";
1111
- }
1112
- async function restartReconciliation(db, reconciliation, claim, current, fields, fieldFingerprint) {
1113
- if (current.targetEpoch === null) return "claim_lost";
1114
- const discoveredFields = fields ?? await loadContentMediaUsageFields(db, claim.collectionSlug, claim.collectionId);
1115
- const fingerprint = fieldFingerprint ?? await buildContentMediaUsageFieldFingerprint(discoveredFields);
1116
- const targetEpoch = await reconciliation.restartRun(claim, current.targetEpoch);
1117
- if (targetEpoch === null) {
1118
- await reconciliation.release({
1119
- ...claim,
1120
- delaySeconds: 30
1121
- });
1122
- return "deferred";
1123
- }
1124
- const scanUpperId = discoveredFields.extractionFields.length === 0 ? null : await reconciliation.findScanUpperId(claim);
1125
- if (!await reconciliation.restartScan({
1126
- claim,
1127
- previousEpoch: current.targetEpoch,
1128
- targetEpoch,
1129
- fieldFingerprint: fingerprint,
1130
- scanUpperId
1131
- })) return "claim_lost";
1132
- if (!await reconciliation.release({
1133
- ...claim,
1134
- delaySeconds: 0
1135
- })) return "claim_lost";
1136
- return "advanced";
1137
- }
1138
- async function failReconciliation(reconciliation, claim, errorCode, entryFailure) {
1139
- if (!(entryFailure ? await reconciliation.recordEntryFailure(claim) : await reconciliation.recordFailure({
1140
- collectionId: claim.collectionId,
1141
- runToken: claim.runToken,
1142
- leaseToken: claim.leaseToken,
1143
- errorCode,
1144
- retryDelaySeconds: 0,
1145
- terminal: true
1146
- }))) return "claim_lost";
1147
- await reconciliation.finishFailedCoverage(claim.collectionId, claim.runToken);
1148
- return "failed";
1149
- }
1150
- function retryDelaySeconds$1(attemptCount) {
1151
- const exponential = Math.min(MEDIA_USAGE_RECONCILIATION_LIMITS.retryMaxSeconds, MEDIA_USAGE_RECONCILIATION_LIMITS.retryBaseSeconds * 2 ** attemptCount);
1152
- const jitter = Math.floor(exponential * MEDIA_USAGE_RECONCILIATION_LIMITS.retryJitterRatio * Math.random());
1153
- return Math.min(MEDIA_USAGE_RECONCILIATION_LIMITS.retryMaxSeconds, exponential + jitter);
1154
- }
1155
-
1156
- //#endregion
1157
- //#region src/media/usage/work-processor.ts
1158
- const MEDIA_USAGE_WORK_PROCESSING_LIMITS = Object.freeze({
1159
- candidatesPerTick: 4,
1160
- jobsPerTick: 1,
1161
- maxTickDurationMs: 5e3,
1162
- leaseDurationSeconds: 60,
1163
- maxAttempts: 5,
1164
- retryBaseSeconds: 30,
1165
- retryMaxSeconds: 900,
1166
- retryJitterRatio: .25,
1167
- ordinaryStatementsPerJob: 20
1168
- });
1169
- async function processMediaUsageWorkAfterWrite(db, collectionSlug, contentId) {
1170
- if (!await isIncrementalCaptureActive(db)) return {
1171
- outcome: "inactive",
1172
- claimed: false
1173
- };
1174
- const repo = new MediaUsageWorkRepository(db);
1175
- const work = await repo.findWorkForContent(collectionSlug, contentId);
1176
- if (!work) return {
1177
- outcome: "not_due",
1178
- claimed: false
1179
- };
1180
- return processCandidate(db, repo, work);
1181
- }
1182
- async function processDueMediaUsageWork(db) {
1183
- const startedAt = Date.now();
1184
- const result = {
1185
- candidateCount: 0,
1186
- claimedCount: 0,
1187
- completedCount: 0,
1188
- retryCount: 0,
1189
- failedCount: 0,
1190
- supersededCount: 0,
1191
- obsoleteCount: 0,
1192
- durationMs: 0,
1193
- admissionClosed: false
1194
- };
1195
- if (!await isIncrementalCaptureActive(db)) {
1196
- result.durationMs = Date.now() - startedAt;
1197
- return result;
1198
- }
1199
- const repo = new MediaUsageWorkRepository(db);
1200
- const candidates = await repo.findDueWork(MEDIA_USAGE_WORK_PROCESSING_LIMITS.candidatesPerTick);
1201
- result.candidateCount = candidates.length;
1202
- for (const candidate of candidates) {
1203
- if (result.claimedCount >= MEDIA_USAGE_WORK_PROCESSING_LIMITS.jobsPerTick || Date.now() - startedAt >= MEDIA_USAGE_WORK_PROCESSING_LIMITS.maxTickDurationMs) {
1204
- result.admissionClosed = true;
1205
- break;
1206
- }
1207
- const processed = await processCandidate(db, repo, candidate);
1208
- if (processed.claimed) result.claimedCount++;
1209
- if (processed.outcome === "completed") result.completedCount++;
1210
- if (processed.outcome === "retry") result.retryCount++;
1211
- if (processed.outcome === "failed") result.failedCount++;
1212
- if (processed.outcome === "superseded") result.supersededCount++;
1213
- if (processed.outcome === "obsolete") result.obsoleteCount++;
1214
- }
1215
- result.durationMs = Date.now() - startedAt;
1216
- return result;
1217
- }
1218
- async function processCandidate(db, repo, candidate) {
1219
- const claimed = await repo.claimWork({
1220
- collectionId: candidate.collectionId,
1221
- contentId: candidate.contentId,
1222
- workVersion: candidate.workVersion,
1223
- leaseDurationSeconds: MEDIA_USAGE_WORK_PROCESSING_LIMITS.leaseDurationSeconds
1224
- });
1225
- if (!claimed?.leaseToken) return {
1226
- outcome: "claim_lost",
1227
- claimed: false
1228
- };
1229
- const lease = {
1230
- collectionId: claimed.collectionId,
1231
- contentId: claimed.contentId,
1232
- workVersion: claimed.workVersion,
1233
- leaseToken: claimed.leaseToken
1234
- };
1235
- if (!await collectionIdentityIsCurrent(db, claimed.collectionId, claimed.collectionSlug)) return {
1236
- outcome: await repo.completeWork(lease) ? "obsolete" : "superseded",
1237
- claimed: true
1238
- };
1239
- const refresh = await refreshContentMediaUsageForWork(db, claimed.collectionId, claimed.collectionSlug, claimed.contentId);
1240
- if (refresh.success) {
1241
- const completed = await repo.completeWork(lease);
1242
- if (completed) await new MediaUsageRepository(db).recordIncrementalSuccess({
1243
- collectionId: claimed.collectionId,
1244
- collectionSlug: claimed.collectionSlug
1245
- });
1246
- return {
1247
- outcome: completed ? "completed" : "superseded",
1248
- claimed: true
1249
- };
1250
- }
1251
- if (!await collectionIdentityIsCurrent(db, claimed.collectionId, claimed.collectionSlug)) return {
1252
- outcome: await repo.completeWork(lease) ? "obsolete" : "superseded",
1253
- claimed: true
1254
- };
1255
- const errorCode = processingErrorCode(refresh.errorCode);
1256
- if (errorCode === "MEDIA_USAGE_RESOURCE_LIMIT" || claimed.attemptCount + 1 >= MEDIA_USAGE_WORK_PROCESSING_LIMITS.maxAttempts) {
1257
- const failed = await repo.failWork({
1258
- ...lease,
1259
- errorCode
1260
- });
1261
- if (failed) await new MediaUsageRepository(db).recordIncrementalFailure({
1262
- collectionId: claimed.collectionId,
1263
- collectionSlug: claimed.collectionSlug,
1264
- contentId: claimed.contentId,
1265
- workVersion: claimed.workVersion,
1266
- errorCode
1267
- });
1268
- return {
1269
- outcome: failed ? "failed" : "superseded",
1270
- claimed: true
1271
- };
1272
- }
1273
- return {
1274
- outcome: await repo.retryWork({
1275
- ...lease,
1276
- errorCode,
1277
- retryDelaySeconds: retryDelaySeconds(claimed.attemptCount)
1278
- }) ? "retry" : "superseded",
1279
- claimed: true
1280
- };
1281
- }
1282
- async function isIncrementalCaptureActive(db) {
1283
- return (await db.selectFrom("_emdash_media_usage_activation").select("state").where("task_key", "=", "incremental_capture").executeTakeFirst())?.state === "active";
1284
- }
1285
- async function collectionIdentityIsCurrent(db, collectionId, collectionSlug) {
1286
- return await db.selectFrom("_emdash_collections").select("id").where("id", "=", collectionId).where("slug", "=", collectionSlug).executeTakeFirst() !== void 0;
1287
- }
1288
- function retryDelaySeconds(attemptCount) {
1289
- const exponential = Math.min(MEDIA_USAGE_WORK_PROCESSING_LIMITS.retryMaxSeconds, MEDIA_USAGE_WORK_PROCESSING_LIMITS.retryBaseSeconds * 2 ** attemptCount);
1290
- const jitter = Math.floor(exponential * MEDIA_USAGE_WORK_PROCESSING_LIMITS.retryJitterRatio * Math.random());
1291
- return Math.min(MEDIA_USAGE_WORK_PROCESSING_LIMITS.retryMaxSeconds, exponential + jitter);
1292
- }
1293
- function processingErrorCode(errorCode) {
1294
- if (errorCode === "DRAFT_REVISION_NOT_FOUND" || errorCode === "DRAFT_REVISION_MISMATCH" || errorCode === "DRAFT_REVISION_INVALID") return "MEDIA_USAGE_SNAPSHOT_FAILED";
1295
- if (errorCode === "CONTENT_USAGE_GENERATION_CONFLICT") return "MEDIA_USAGE_GENERATION_CONFLICT";
1296
- if (errorCode === "CONTENT_USAGE_RESOURCE_LIMIT") return "MEDIA_USAGE_RESOURCE_LIMIT";
1297
- return "MEDIA_USAGE_PROCESSING_FAILED";
1298
- }
1299
-
1300
166
  //#endregion
1301
167
  //#region src/plugins/sandbox/runner-options.ts
1302
168
  /**
@@ -1824,6 +690,28 @@ const LIST_COLUMN_FIELD_TYPES = new Set([
1824
690
  "multiSelect"
1825
691
  ]);
1826
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
+ /**
1827
715
  * Convert a ContentItem to Record<string, unknown> for hook consumption.
1828
716
  * Hooks receive the full item as a flat record.
1829
717
  */
@@ -1906,49 +794,6 @@ const marketplaceManifestCache = /* @__PURE__ */ new Map();
1906
794
  /** Route metadata for sandboxed plugins: pluginId -> routeName -> RouteMeta */
1907
795
  const sandboxedRouteMetaCache = /* @__PURE__ */ new Map();
1908
796
  let sandboxRunner = null;
1909
- const MEDIA_USAGE_MAINTENANCE_QUERY_RESERVATIONS = Object.freeze({
1910
- entryWork: MEDIA_USAGE_WORK_PROCESSING_LIMITS.ordinaryStatementsPerJob,
1911
- collectionDeletion: MEDIA_USAGE_COLLECTION_DELETION_LIMITS.maxQueriesPerTick,
1912
- reconciliation: MEDIA_USAGE_RECONCILIATION_LIMITS.maxQueriesPerTick,
1913
- maxClassQueries: Math.max(MEDIA_USAGE_WORK_PROCESSING_LIMITS.ordinaryStatementsPerJob, MEDIA_USAGE_COLLECTION_DELETION_LIMITS.maxQueriesPerTick, MEDIA_USAGE_RECONCILIATION_LIMITS.maxQueriesPerTick),
1914
- eventCeiling: 40
1915
- });
1916
- async function runScheduledMediaUsageLane(db) {
1917
- if ((getRequestContext()?.metrics?.dbCount ?? 0) + 1 + MEDIA_USAGE_MAINTENANCE_QUERY_RESERVATIONS.maxClassQueries > MEDIA_USAGE_MAINTENANCE_QUERY_RESERVATIONS.eventCeiling) return {
1918
- outcome: "admission_closed",
1919
- taskClass: null,
1920
- turn: null
1921
- };
1922
- 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();
1923
- if (!activation) return {
1924
- outcome: "inactive",
1925
- taskClass: null,
1926
- turn: null
1927
- };
1928
- const turn = activation.media_usage_maintenance_turn;
1929
- if (turn === 0) {
1930
- await processDueMediaUsageWork(db);
1931
- return {
1932
- outcome: "processed",
1933
- taskClass: "entry_work",
1934
- turn
1935
- };
1936
- }
1937
- if (turn === 1) {
1938
- await processDueMediaUsageCollectionDeletions(db);
1939
- return {
1940
- outcome: "processed",
1941
- taskClass: "collection_deletion",
1942
- turn
1943
- };
1944
- }
1945
- await processDueMediaUsageReconciliation(db);
1946
- return {
1947
- outcome: "processed",
1948
- taskClass: "reconciliation",
1949
- turn
1950
- };
1951
- }
1952
797
  /**
1953
798
  * EmDashRuntime - singleton per worker
1954
799
  */
@@ -2116,9 +961,6 @@ var EmDashRuntime = class EmDashRuntime {
2116
961
  await recordSchedulerHeartbeatSafely(this.db);
2117
962
  return { published };
2118
963
  }
2119
- async runScheduledMediaUsageTasks() {
2120
- return runScheduledMediaUsageLane(this.db);
2121
- }
2122
964
  /**
2123
965
  * Materialize plugin-declared storage indexes, once per process.
2124
966
  *
@@ -2519,13 +1361,19 @@ var EmDashRuntime = class EmDashRuntime {
2519
1361
  await new OptionsRepository(db).set(LOCALE_CASING_REPAIR_OPTION, localeCasingRepairVersion);
2520
1362
  });
2521
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
+ }
2522
1370
  const seedKey = deps.config.database?.entrypoint ?? "default";
2523
1371
  const seedHolder = getSeedHolder();
2524
1372
  try {
2525
1373
  await initWithLock(seedHolder.lock, () => seedHolder.done.has(seedKey) ? true : void 0, async () => {
2526
- const { applySeed } = await import("../apply-kUDt80-G.mjs").then((n) => n.n);
2527
- const { loadSeed } = await import("../load-YJ2xl3ra.mjs").then((n) => n.r);
2528
- const { validateSeed } = await import("../validate-BMLBvxOC.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);
2529
1377
  const seed = await loadSeed();
2530
1378
  if (validateSeed(seed).valid) {
2531
1379
  await applySeed(db, seed, { onConflict: "skip" });
@@ -2655,11 +1503,6 @@ var EmDashRuntime = class EmDashRuntime {
2655
1503
  if (deps.createScheduler) {
2656
1504
  const scheduler = deps.createScheduler(cronExecutor);
2657
1505
  cronScheduler = scheduler;
2658
- const runMediaUsageMaintenance = async () => {
2659
- const runtime = runtimeRef.current;
2660
- if (runtime) await runtime.runScheduledMediaUsageTasks();
2661
- else await runScheduledMediaUsageLane(db);
2662
- };
2663
1506
  scheduler.setSystemCleanup(async () => {
2664
1507
  try {
2665
1508
  const runtime = runtimeRef.current;
@@ -2683,13 +1526,7 @@ var EmDashRuntime = class EmDashRuntime {
2683
1526
  }
2684
1527
  await maybeRunScheduledBackup(db, storage ?? void 0);
2685
1528
  await recordSchedulerHeartbeatSafely(db);
2686
- if (!scheduler.setMediaUsageMaintenance) try {
2687
- await runMediaUsageMaintenance();
2688
- } catch (error) {
2689
- console.error("[media-usage] Scheduled maintenance failed:", error);
2690
- }
2691
1529
  });
2692
- scheduler.setMediaUsageMaintenance?.(runMediaUsageMaintenance);
2693
1530
  scheduler.start();
2694
1531
  }
2695
1532
  } catch (error) {
@@ -3351,10 +2188,14 @@ var EmDashRuntime = class EmDashRuntime {
3351
2188
  }
3352
2189
  async handleContentCreate(collection, body) {
3353
2190
  let processedData = body.data;
3354
- 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
+ }
3355
2196
  processedData = await this.runSandboxedBeforeSave(processedData, collection, true);
3356
2197
  processedData = await this.normalizeMediaFields(collection, processedData);
3357
- const { validateContentData } = await import("../validation-npvENUCB.mjs");
2198
+ const { validateContentData } = await import("../validation-D83n7Npi.mjs");
3358
2199
  const validation = await validateContentData(this.db, collection, processedData, { partial: false });
3359
2200
  if (!validation.ok) return {
3360
2201
  success: false,
@@ -3394,10 +2235,14 @@ var EmDashRuntime = class EmDashRuntime {
3394
2235
  const { _rev: _discardedRev, ...bodyWithoutRev } = body;
3395
2236
  let processedData = bodyWithoutRev.data;
3396
2237
  if (bodyWithoutRev.data) {
3397
- 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
+ }
3398
2243
  processedData = await this.runSandboxedBeforeSave(processedData, collection, false);
3399
2244
  processedData = await this.normalizeMediaFields(collection, processedData);
3400
- const { validateContentData } = await import("../validation-npvENUCB.mjs");
2245
+ const { validateContentData } = await import("../validation-D83n7Npi.mjs");
3401
2246
  const validation = await validateContentData(this.db, collection, processedData, { partial: true });
3402
2247
  if (!validation.ok) return {
3403
2248
  success: false,
@@ -3713,11 +2558,11 @@ var EmDashRuntime = class EmDashRuntime {
3713
2558
  }
3714
2559
  async refreshContentUsageAfterSuccessfulWrite(collection, contentIds) {
3715
2560
  for (const contentId of new Set(contentIds)) try {
3716
- if ((await processMediaUsageWorkAfterWrite(this.db, collection, contentId)).outcome !== "inactive") return;
2561
+ if ((await processMediaUsageWorkAfterWrite(this.db, collection, contentId)).outcome !== "inactive") continue;
3717
2562
  await refreshContentMediaUsageAfterWrite(this.db, collection, contentId);
3718
2563
  } catch (error) {
3719
2564
  console.error(`[media-usage] Failed after content write ${collection}/${contentId}:`, error);
3720
- return;
2565
+ continue;
3721
2566
  }
3722
2567
  }
3723
2568
  async deleteContentUsageAfterSuccessfulPermanentDelete(collection, contentId) {
@@ -3938,7 +2783,7 @@ var EmDashRuntime = class EmDashRuntime {
3938
2783
  const value = result[field.slug];
3939
2784
  if (value == null) continue;
3940
2785
  try {
3941
- const normalized = await normalizeMediaValue(value, getProvider);
2786
+ const normalized = field.type === "image" ? await normalizeImageValue(value, getProvider) : await normalizeMediaValue(value, getProvider);
3942
2787
  if (normalized) result[field.slug] = normalized;
3943
2788
  } catch {}
3944
2789
  }
@@ -3955,7 +2800,7 @@ var EmDashRuntime = class EmDashRuntime {
3955
2800
  const subValue = normalizedItem[slug];
3956
2801
  if (subValue == null) continue;
3957
2802
  try {
3958
- const normalized = await normalizeMediaValue(subValue, getProvider);
2803
+ const normalized = await normalizeImageValue(subValue, getProvider);
3959
2804
  if (normalized) normalizedItem[slug] = normalized;
3960
2805
  } catch {}
3961
2806
  }
@@ -3981,7 +2826,7 @@ var EmDashRuntime = class EmDashRuntime {
3981
2826
  result = record;
3982
2827
  }
3983
2828
  } catch (error) {
3984
- 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);
3985
2830
  }
3986
2831
  }
3987
2832
  return result;
@@ -4214,6 +3059,33 @@ var EmDashRuntime = class EmDashRuntime {
4214
3059
  return status === void 0 || status === "active";
4215
3060
  }
4216
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
+ }
4217
3089
 
4218
3090
  //#endregion
4219
3091
  //#region src/media/url.ts
@@ -4675,15 +3547,6 @@ async function runScheduledTasks(options = {}) {
4675
3547
  if (!config) return { published: [] };
4676
3548
  return runOutsideRequest(config, (runtime) => runtime.runScheduledTasks(options));
4677
3549
  }
4678
- async function runScheduledMediaUsageTasks() {
4679
- const config = getConfig();
4680
- if (!config) return {
4681
- outcome: "inactive",
4682
- taskClass: null,
4683
- turn: null
4684
- };
4685
- return runOutsideRequest(config, (runtime) => runtime.runScheduledMediaUsageTasks());
4686
- }
4687
3550
  /**
4688
3551
  * Run a callback against the EmDash runtime outside any HTTP request — from a
4689
3552
  * Cloudflare Queue consumer, a `scheduled()` handler, or any other
@@ -4966,7 +3829,7 @@ const onRequest = defineMiddleware(async (context, next) => {
4966
3829
  if (migrationMode === "auto" && !isSetupVerified() && !context.isPrerendered) {
4967
3830
  const t0 = performance.now();
4968
3831
  try {
4969
- const { getDb } = await import("../loader-CdG_Ci3H.mjs").then((n) => n.c);
3832
+ const { getDb } = await import("../loader-ZsjE4paL.mjs").then((n) => n.c);
4970
3833
  await (await getDb()).selectFrom("_emdash_migrations").selectAll().limit(1).execute();
4971
3834
  markSetupVerified();
4972
3835
  } catch (error) {
@@ -5217,5 +4080,5 @@ const onRequest = defineMiddleware(async (context, next) => {
5217
4080
  });
5218
4081
 
5219
4082
  //#endregion
5220
- export { onRequest as default, onRequest, runScheduledMediaUsageTasks, runScheduledTasks, withEmDashRuntime };
4083
+ export { onRequest as default, onRequest, runScheduledTasks, withEmDashRuntime };
5221
4084
  //# sourceMappingURL=middleware.mjs.map