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
@@ -0,0 +1,1833 @@
1
+ import { c as __exportAll } from "./runner-77GOZCnH.mjs";
2
+ import { t as validateIdentifier } from "./validate-BpwoDOgt.mjs";
3
+ import { p as tableExists, s as isPostgres } from "./dialect-helpers-BSbfaO3F.mjs";
4
+ import { getRequestContext } from "./request-context.mjs";
5
+ import { i as InvalidCursorError } from "./types-CQPWhPxE.mjs";
6
+ import { t as MediaRepository } from "./media-D6SeAfBl.mjs";
7
+ import { t as withTransaction } from "./transaction-0KOpOyMu.mjs";
8
+ import { a as continueMediaUsageActivation, n as MediaUsageActivationVersionMismatchError, s as getMediaUsageActivationStatus, t as MEDIA_USAGE_ACTIVATION_RUNTIME_GENERATION } from "./activation-K0kGBWuJ.mjs";
9
+ import { _ as buildContentMediaUsageFieldFingerprint, b as jsonTextValues, d as refreshContentMediaUsageForWorkBatch, n as CONTENT_MEDIA_USAGE_COLLECTION_SCOPE, r as contentRefreshKey, t as CONTENT_MEDIA_USAGE_ADAPTER_ID, v as loadContentMediaUsageFields, x as CONTENT_SOURCE_SCHEMA_VERSION, y as MediaUsageRepository } from "./content-refresh-DLOfIhYc.mjs";
10
+ import { t as MediaUsageWorkRepository } from "./media-usage-work-C-fhM30c.mjs";
11
+ import { n as repairContentMediaUsageCollection, t as repairContentMediaUsageAll } from "./content-repair-BCxzy0r4.mjs";
12
+ import { n as collectionDeletionCurrentTimestamp, r as deleteActivatedMediaUsageCollection, t as MediaUsageCollectionDeletionRepository } from "./collection-deletion-m_oi2c0N.mjs";
13
+ import { t as ErrorCode } from "./errors-n2j49uNf.mjs";
14
+ import { sql } from "kysely";
15
+ import { ulid } from "ulidx";
16
+
17
+ //#region src/media/usage/collection-deletion-processor.ts
18
+ const MEDIA_USAGE_COLLECTION_DELETION_LIMITS = Object.freeze({
19
+ candidatesPerTick: 4,
20
+ rowsPerBatch: 50,
21
+ leaseDurationSeconds: 300,
22
+ maxAttempts: 5,
23
+ retryBaseSeconds: 30,
24
+ retryMaxSeconds: 900
25
+ });
26
+ async function processDueMediaUsageCollectionDeletions(db) {
27
+ const repository = new MediaUsageCollectionDeletionRepository(db);
28
+ const candidates = await repository.findDue(MEDIA_USAGE_COLLECTION_DELETION_LIMITS.candidatesPerTick);
29
+ if (candidates.length === 0) return {
30
+ candidateCount: await repository.hasNonterminal() ? 1 : 0,
31
+ claimedCount: 0,
32
+ outcome: "idle"
33
+ };
34
+ let claim = null;
35
+ for (const candidate of candidates) {
36
+ claim = await repository.claim({
37
+ collectionId: candidate.collectionId,
38
+ phase: candidate.phase,
39
+ leaseDurationSeconds: MEDIA_USAGE_COLLECTION_DELETION_LIMITS.leaseDurationSeconds
40
+ });
41
+ if (claim?.leaseToken) break;
42
+ }
43
+ if (!claim?.leaseToken) return {
44
+ candidateCount: candidates.length,
45
+ claimedCount: 0,
46
+ outcome: "claim_lost"
47
+ };
48
+ try {
49
+ const processed = await processClaimedDeletion(db, claim);
50
+ if (!processed.finalized && !processed.released && !await repository.release(claim)) return {
51
+ candidateCount: candidates.length,
52
+ claimedCount: 1,
53
+ outcome: "claim_lost"
54
+ };
55
+ return {
56
+ candidateCount: candidates.length,
57
+ claimedCount: 1,
58
+ outcome: processed.finalized ? "finalized" : "progress"
59
+ };
60
+ } catch (error) {
61
+ const terminal = claim.attemptCount + 1 >= MEDIA_USAGE_COLLECTION_DELETION_LIMITS.maxAttempts;
62
+ if (!await repository.recordFailure({
63
+ collectionId: claim.collectionId,
64
+ leaseToken: claim.leaseToken,
65
+ errorCode: "MEDIA_USAGE_COLLECTION_DELETION_FAILED",
66
+ terminal,
67
+ retryDelaySeconds: retryDelaySeconds$2(claim.attemptCount)
68
+ })) return {
69
+ candidateCount: candidates.length,
70
+ claimedCount: 1,
71
+ outcome: "claim_lost"
72
+ };
73
+ console.error("[media-usage:collection-deletion] Processing failed:", error);
74
+ return {
75
+ candidateCount: candidates.length,
76
+ claimedCount: 1,
77
+ outcome: terminal ? "failed" : "retry"
78
+ };
79
+ }
80
+ }
81
+ async function processClaimedDeletion(db, claim) {
82
+ if (claim.phase === "fence" || claim.phase === "registry" || claim.phase === "table") {
83
+ await deleteActivatedMediaUsageCollection(db, {
84
+ collectionId: claim.collectionId,
85
+ collectionSlug: claim.collectionSlug,
86
+ forceDelete: claim.forceDelete
87
+ }, {
88
+ frontPhaseLimit: 1,
89
+ claimed: claim
90
+ });
91
+ return {
92
+ finalized: false,
93
+ released: true
94
+ };
95
+ }
96
+ if (claim.phase === "work") await processWorkBatch(db, claim);
97
+ if (claim.phase === "sources") await processSourceBatch(db, claim);
98
+ if (claim.phase === "status") await processStatus(db, claim);
99
+ if (claim.phase === "finalize") {
100
+ await finalizeDeletion(db, claim);
101
+ return {
102
+ finalized: true,
103
+ released: true
104
+ };
105
+ }
106
+ return {
107
+ finalized: false,
108
+ released: false
109
+ };
110
+ }
111
+ async function processWorkBatch(db, claim) {
112
+ await withTransaction(db, async (trx) => {
113
+ 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();
114
+ const batch = rows.slice(0, MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch);
115
+ 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();
116
+ await updateDeletion(trx, claim, {
117
+ phase: rows.length > MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch ? "work" : "sources",
118
+ work_cursor: rows.length > MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch ? batch.at(-1).content_id : null
119
+ });
120
+ });
121
+ return false;
122
+ }
123
+ async function processSourceBatch(db, claim) {
124
+ await withTransaction(db, async (trx) => {
125
+ let sourceKey = claim.sourceKey;
126
+ if (!sourceKey) {
127
+ 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();
128
+ if (!source) {
129
+ await updateDeletion(trx, claim, {
130
+ phase: "status",
131
+ source_key: null,
132
+ occurrence_cursor: null
133
+ });
134
+ return;
135
+ }
136
+ sourceKey = source.source_key;
137
+ await updateDeletion(trx, claim, {
138
+ source_key: sourceKey,
139
+ occurrence_cursor: null
140
+ });
141
+ }
142
+ 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();
143
+ const batch = occurrences.slice(0, MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch);
144
+ 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();
145
+ if (occurrences.length > MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch) {
146
+ await updateDeletion(trx, claim, {
147
+ source_key: sourceKey,
148
+ occurrence_cursor: batch.at(-1).id
149
+ });
150
+ return;
151
+ }
152
+ 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();
153
+ await updateDeletion(trx, claim, {
154
+ source_key: null,
155
+ occurrence_cursor: null
156
+ });
157
+ });
158
+ return false;
159
+ }
160
+ async function processStatus(db, claim) {
161
+ await withTransaction(db, async (trx) => {
162
+ if (await exactCleanupRowsRemain(trx, claim, false)) throw new Error("Collection deletion cleanup is incomplete");
163
+ await trx.deleteFrom("_emdash_media_usage_reconciliations").where("collection_id", "=", claim.collectionId).where("collection_slug", "=", claim.collectionSlug).where(liveLeaseGuard(trx, claim)).execute();
164
+ 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();
165
+ await updateDeletion(trx, claim, { phase: "finalize" });
166
+ });
167
+ return false;
168
+ }
169
+ async function finalizeDeletion(db, claim) {
170
+ if (await tableExists(db, `ec_${claim.collectionSlug}`)) throw new Error("Collection table still exists during deletion finalization");
171
+ if (await exactCleanupRowsRemain(db, claim)) throw new Error("Collection deletion is incomplete");
172
+ 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");
173
+ 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();
174
+ if (Number(result.numDeletedRows ?? 0) !== 1) throw new Error("Collection deletion lost finalization");
175
+ return true;
176
+ }
177
+ async function exactCleanupRowsRemain(db, claim, includeStatus = true) {
178
+ const row = (await sql`
179
+ SELECT
180
+ EXISTS (
181
+ SELECT 1 FROM _emdash_media_usage_work
182
+ WHERE collection_id = ${claim.collectionId}
183
+ ) AS work_present,
184
+ EXISTS (
185
+ SELECT 1 FROM _emdash_media_usage_sources
186
+ WHERE source_type = 'content' AND collection_id = ${claim.collectionId}
187
+ ) AS source_present,
188
+ EXISTS (
189
+ SELECT 1 FROM _emdash_media_usage_index_status
190
+ WHERE adapter_id = 'content-media'
191
+ AND scope_type = 'collection'
192
+ AND scope_key = ${claim.collectionSlug}
193
+ AND collection_id = ${claim.collectionId}
194
+ ) AS status_present
195
+ `.execute(db)).rows[0];
196
+ return Boolean(row?.work_present) || Boolean(row?.source_present) || includeStatus && Boolean(row?.status_present);
197
+ }
198
+ async function updateDeletion(db, claim, values) {
199
+ const result = await db.updateTable("_emdash_media_usage_collection_deletions").set({
200
+ ...values,
201
+ attempt_count: 0,
202
+ last_error_code: null,
203
+ updated_at: collectionDeletionCurrentTimestamp(db)
204
+ }).where("collection_id", "=", claim.collectionId).where("state", "=", "leased").where("lease_token", "=", claim.leaseToken).where(liveLeaseGuard(db, claim)).executeTakeFirst();
205
+ if (Number(result.numUpdatedRows ?? 0) !== 1) throw new Error("Collection deletion lease was lost");
206
+ }
207
+ function liveLeaseGuard(db, claim) {
208
+ return isPostgres(db) ? sql`EXISTS (
209
+ SELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion
210
+ WHERE deletion.collection_id = ${claim.collectionId}
211
+ AND deletion.state = 'leased'
212
+ AND deletion.lease_token = ${claim.leaseToken}
213
+ AND deletion.lease_expires_at::timestamptz > clock_timestamp()
214
+ )` : sql`EXISTS (
215
+ SELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion
216
+ WHERE deletion.collection_id = ${claim.collectionId}
217
+ AND deletion.state = 'leased'
218
+ AND deletion.lease_token = ${claim.leaseToken}
219
+ AND deletion.lease_expires_at > strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
220
+ )`;
221
+ }
222
+ function retryDelaySeconds$2(attemptCount) {
223
+ return Math.min(MEDIA_USAGE_COLLECTION_DELETION_LIMITS.retryMaxSeconds, MEDIA_USAGE_COLLECTION_DELETION_LIMITS.retryBaseSeconds * 2 ** attemptCount);
224
+ }
225
+
226
+ //#endregion
227
+ //#region src/media/usage/reconciliation.ts
228
+ const ACTIVATION_KEY = "incremental_capture";
229
+ const CONTENT_ADAPTER_ID = "content-media";
230
+ const COLLECTION_SCOPE = "collection";
231
+ const MAX_CANDIDATES = 100;
232
+ const MAX_PORTABLE_DURATION_SECONDS = 365 * 24 * 60 * 60;
233
+ const STABLE_ERROR_CODE_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
234
+ var MediaUsageReconciliationRepository = class {
235
+ constructor(db) {
236
+ this.db = db;
237
+ }
238
+ async findByIdentity(collectionId, runToken) {
239
+ assertIdentity({
240
+ collectionId,
241
+ runToken
242
+ });
243
+ const row = await this.db.selectFrom("_emdash_media_usage_reconciliations").selectAll().where("collection_id", "=", collectionId).where("run_token", "=", runToken).executeTakeFirst();
244
+ return row ? rowToRecord(row) : null;
245
+ }
246
+ async beginRun(claim) {
247
+ const now = timestampOffset(this.db, 0);
248
+ const sameRun = sql`status = 'running' AND cursor = ${claim.runToken}`;
249
+ return (await this.db.updateTable("_emdash_media_usage_index_status as status").set({
250
+ status: "running",
251
+ started_at: sql`CASE WHEN ${sameRun} THEN started_at ELSE ${now} END`,
252
+ completed_at: null,
253
+ cursor: claim.runToken,
254
+ indexed_source_count: 0,
255
+ failed_source_count: 0,
256
+ last_error_code: null,
257
+ change_epoch: sql`CASE WHEN ${sameRun} THEN change_epoch ELSE change_epoch + 1 END`,
258
+ reconciliation_required: 1,
259
+ updated_at: now
260
+ }).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;
261
+ }
262
+ async findScanUpperId(claim) {
263
+ const tableName = contentTableName(claim.collectionSlug);
264
+ return (await sql`
265
+ SELECT content.id
266
+ FROM ${sql.ref(tableName)} AS content
267
+ WHERE ${this.liveClaimExistsSql(claim)}
268
+ ORDER BY content.id DESC
269
+ LIMIT 1
270
+ `.execute(this.db)).rows[0]?.id ?? null;
271
+ }
272
+ async initializeScan(input) {
273
+ const result = await this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
274
+ target_epoch: input.targetEpoch,
275
+ field_fingerprint: input.fieldFingerprint,
276
+ phase: "scan",
277
+ scan_cursor: null,
278
+ scan_upper_id: input.scanUpperId,
279
+ source_cursor: null,
280
+ source_upper_key: null,
281
+ attempt_count: 0,
282
+ last_error_code: null,
283
+ updated_at: timestampOffset(this.db, 0)
284
+ }).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();
285
+ return Number(result.numUpdatedRows ?? 0) === 1;
286
+ }
287
+ async findScanPage(reconciliation, limit) {
288
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1e3) throw new Error("Reconciliation scan page limit must be from 1 to 1000");
289
+ if (!reconciliation.leaseToken || reconciliation.targetEpoch === null) return [];
290
+ const tableName = contentTableName(reconciliation.collectionSlug);
291
+ const lowerBound = reconciliation.scanCursor ? sql`AND content.id > ${reconciliation.scanCursor}` : sql``;
292
+ const upperBound = reconciliation.scanUpperId ? sql`AND content.id <= ${reconciliation.scanUpperId}` : sql`AND 1 = 0`;
293
+ return (await sql`
294
+ SELECT content.id
295
+ FROM ${sql.ref(tableName)} AS content
296
+ WHERE 1 = 1
297
+ ${lowerBound}
298
+ ${upperBound}
299
+ AND ${this.liveClaimExistsSql(reconciliation)}
300
+ ORDER BY content.id ASC
301
+ LIMIT ${limit}
302
+ `.execute(this.db)).rows.map((row) => row.id);
303
+ }
304
+ async checkpointScan(input) {
305
+ let query = this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
306
+ scan_cursor: input.nextCursor,
307
+ attempt_count: 0,
308
+ last_error_code: null,
309
+ updated_at: timestampOffset(this.db, 0)
310
+ }).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));
311
+ query = input.previousCursor ? query.where("reconciliation.scan_cursor", "=", input.previousCursor) : query.where("reconciliation.scan_cursor", "is", null);
312
+ const result = await query.executeTakeFirst();
313
+ return Number(result.numUpdatedRows ?? 0) === 1;
314
+ }
315
+ async ownsRun(claim, targetEpoch) {
316
+ const result = await sql`
317
+ SELECT ${this.statusOwnsRun(claim, targetEpoch)} AS owned
318
+ `.execute(this.db);
319
+ return Boolean(result.rows[0]?.owned);
320
+ }
321
+ async restartRun(claim, previousEpoch) {
322
+ const now = timestampOffset(this.db, 0);
323
+ const interruptedRestart = sql`status = 'running'
324
+ AND cursor = ${claim.runToken}
325
+ AND change_epoch > ${previousEpoch}`;
326
+ return (await this.db.updateTable("_emdash_media_usage_index_status as status").set({
327
+ status: "running",
328
+ started_at: sql`CASE WHEN ${interruptedRestart} THEN started_at ELSE ${now} END`,
329
+ completed_at: null,
330
+ cursor: claim.runToken,
331
+ last_error_code: null,
332
+ change_epoch: sql`CASE WHEN ${interruptedRestart} THEN change_epoch ELSE change_epoch + 1 END`,
333
+ reconciliation_required: 1,
334
+ updated_at: now
335
+ }).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;
336
+ }
337
+ async restartScan(input) {
338
+ const result = await this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
339
+ target_epoch: input.targetEpoch,
340
+ field_fingerprint: input.fieldFingerprint,
341
+ phase: "scan",
342
+ scan_cursor: null,
343
+ scan_upper_id: input.scanUpperId,
344
+ source_cursor: null,
345
+ source_upper_key: null,
346
+ attempt_count: 0,
347
+ next_attempt_at: timestampOffset(this.db, 0),
348
+ last_error_code: null,
349
+ updated_at: timestampOffset(this.db, 0)
350
+ }).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();
351
+ return Number(result.numUpdatedRows ?? 0) === 1;
352
+ }
353
+ async findWorkBarrier(collectionId) {
354
+ if (!collectionId) throw new Error("Reconciliation work barrier requires a collection ID");
355
+ 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();
356
+ if (failed) return {
357
+ state: "failed",
358
+ errorCode: failed.last_error_code ?? "MEDIA_USAGE_PROCESSING_FAILED"
359
+ };
360
+ return await this.db.selectFrom("_emdash_media_usage_work").select("content_id").where("collection_id", "=", collectionId).limit(1).executeTakeFirst() ? { state: "pending" } : { state: "empty" };
361
+ }
362
+ async findSourceUpperKey(claim, targetEpoch) {
363
+ 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;
364
+ }
365
+ async transitionToSources(input) {
366
+ const result = await this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
367
+ phase: "sources",
368
+ source_cursor: null,
369
+ source_upper_key: input.sourceUpperKey,
370
+ attempt_count: 0,
371
+ last_error_code: null,
372
+ updated_at: timestampOffset(this.db, 0)
373
+ }).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();
374
+ return Number(result.numUpdatedRows ?? 0) === 1;
375
+ }
376
+ async findSourcePage(reconciliation, limit) {
377
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1e3) throw new Error("Reconciliation source page limit must be from 1 to 1000");
378
+ if (!reconciliation.leaseToken || reconciliation.targetEpoch === null) return [];
379
+ let query = this.db.selectFrom("_emdash_media_usage_sources as source").select([
380
+ "source.source_key",
381
+ "source.content_id",
382
+ "source.source_variant"
383
+ ]).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));
384
+ if (reconciliation.sourceCursor) query = query.where("source.source_key", ">", reconciliation.sourceCursor);
385
+ if (reconciliation.sourceUpperKey) query = query.where("source.source_key", "<=", reconciliation.sourceUpperKey);
386
+ else query = query.where(sql`1 = 0`);
387
+ return (await query.orderBy("source.source_key").limit(limit).execute()).map((row) => ({
388
+ sourceKey: row.source_key,
389
+ contentId: row.content_id,
390
+ sourceVariant: row.source_variant
391
+ }));
392
+ }
393
+ async findMissingContentIds(collectionSlug, contentIds) {
394
+ const unique = [...new Set(contentIds)];
395
+ if (unique.length === 0) return [];
396
+ if (unique.length > 1e3 || unique.some((contentId) => !contentId)) throw new Error("Reconciliation source page has invalid content identity");
397
+ const tableName = contentTableName(collectionSlug);
398
+ const existing = await sql`
399
+ WITH requested AS (${jsonTextValues(this.db, unique)})
400
+ SELECT content.id
401
+ FROM ${sql.ref(tableName)} AS content
402
+ INNER JOIN requested ON requested.value = content.id
403
+ `.execute(this.db);
404
+ const present = new Set(existing.rows.map((row) => row.id));
405
+ return unique.filter((contentId) => !present.has(contentId));
406
+ }
407
+ async checkpointSources(input) {
408
+ let query = this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
409
+ source_cursor: input.nextCursor,
410
+ attempt_count: 0,
411
+ last_error_code: null,
412
+ updated_at: timestampOffset(this.db, 0)
413
+ }).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));
414
+ query = input.previousCursor ? query.where("reconciliation.source_cursor", "=", input.previousCursor) : query.where("reconciliation.source_cursor", "is", null);
415
+ const result = await query.executeTakeFirst();
416
+ return Number(result.numUpdatedRows ?? 0) === 1;
417
+ }
418
+ async finishFailedCoverage(collectionId, runToken) {
419
+ assertIdentity({
420
+ collectionId,
421
+ runToken
422
+ });
423
+ const now = timestampOffset(this.db, 0);
424
+ const result = await this.db.updateTable("_emdash_media_usage_index_status as status").set({
425
+ status: sql`CASE WHEN EXISTS (
426
+ SELECT 1 FROM _emdash_media_usage_sources AS source
427
+ WHERE source.source_type = 'content'
428
+ AND source.collection_id = ${collectionId}
429
+ AND source.identity_version = 1
430
+ LIMIT 1
431
+ ) THEN 'partial' ELSE 'failed' END`,
432
+ completed_at: null,
433
+ cursor: null,
434
+ last_error_code: sql`CASE WHEN (
435
+ SELECT reconciliation.last_error_code
436
+ FROM _emdash_media_usage_reconciliations AS reconciliation
437
+ WHERE reconciliation.collection_id = ${collectionId}
438
+ AND reconciliation.run_token = ${runToken}
439
+ ) = 'MEDIA_USAGE_RECONCILIATION_ENTRY_FAILED'
440
+ THEN COALESCE(
441
+ (SELECT work.last_error_code
442
+ FROM _emdash_media_usage_work AS work
443
+ WHERE work.collection_id = ${collectionId} AND work.state = 'failed'
444
+ ORDER BY work.content_id LIMIT 1),
445
+ 'MEDIA_USAGE_RECONCILIATION_ENTRY_FAILED'
446
+ )
447
+ ELSE (
448
+ SELECT reconciliation.last_error_code
449
+ FROM _emdash_media_usage_reconciliations AS reconciliation
450
+ WHERE reconciliation.collection_id = ${collectionId}
451
+ AND reconciliation.run_token = ${runToken}
452
+ ) END`,
453
+ reconciliation_required: 1,
454
+ updated_at: now
455
+ }).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(sql`status.change_epoch = (
456
+ SELECT reconciliation.target_epoch
457
+ FROM _emdash_media_usage_reconciliations AS reconciliation
458
+ WHERE reconciliation.collection_id = ${collectionId}
459
+ AND reconciliation.run_token = ${runToken}
460
+ )`).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();
461
+ return Number(result.numUpdatedRows ?? 0) === 1;
462
+ }
463
+ async finalizeCoverage(input) {
464
+ const now = timestampOffset(this.db, 0);
465
+ const result = await this.db.updateTable("_emdash_media_usage_index_status as status").set({
466
+ status: "complete",
467
+ schema_version: input.schemaVersion,
468
+ completed_at: now,
469
+ cursor: null,
470
+ last_error_code: null,
471
+ reconciliation_required: 0,
472
+ updated_at: now
473
+ }).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();
474
+ return Number(result.numUpdatedRows ?? 0) === 1;
475
+ }
476
+ async deleteFinalized(claim) {
477
+ 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();
478
+ return Number(result.numDeletedRows ?? 0) === 1;
479
+ }
480
+ async deleteOneObsolete() {
481
+ return (await sql`
482
+ DELETE FROM _emdash_media_usage_reconciliations
483
+ WHERE (collection_id, run_token) IN (
484
+ SELECT reconciliation.collection_id, reconciliation.run_token
485
+ FROM _emdash_media_usage_reconciliations AS reconciliation
486
+ INNER JOIN _emdash_media_usage_index_status AS status
487
+ ON status.collection_id = reconciliation.collection_id
488
+ AND status.scope_key = reconciliation.collection_slug
489
+ WHERE status.adapter_id = ${CONTENT_ADAPTER_ID}
490
+ AND status.scope_type = ${COLLECTION_SCOPE}
491
+ AND status.reconciliation_required = 0
492
+ ORDER BY reconciliation.updated_at, reconciliation.collection_id
493
+ LIMIT 1
494
+ )
495
+ RETURNING collection_id
496
+ `.execute(this.db)).rows.length === 1;
497
+ }
498
+ liveClaimExists(claim) {
499
+ return this.liveClaimExistsSql(claim);
500
+ }
501
+ liveClaimExistsSql(claim) {
502
+ return sql`EXISTS (
503
+ SELECT 1
504
+ FROM _emdash_media_usage_reconciliations AS reconciliation
505
+ INNER JOIN _emdash_collections AS collection
506
+ ON collection.id = reconciliation.collection_id
507
+ AND collection.slug = reconciliation.collection_slug
508
+ WHERE reconciliation.collection_id = ${claim.collectionId}
509
+ AND reconciliation.collection_slug = ${claim.collectionSlug}
510
+ AND reconciliation.run_token = ${claim.runToken}
511
+ AND reconciliation.state = 'leased'
512
+ AND reconciliation.lease_token = ${claim.leaseToken}
513
+ AND ${liveLease(this.db, "reconciliation.lease_expires_at")}
514
+ AND EXISTS (
515
+ SELECT 1 FROM _emdash_media_usage_activation AS activation
516
+ WHERE activation.task_key = ${ACTIVATION_KEY}
517
+ AND activation.state = 'active'
518
+ )
519
+ AND NOT EXISTS (
520
+ SELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion
521
+ WHERE deletion.collection_id = reconciliation.collection_id
522
+ )
523
+ )`;
524
+ }
525
+ statusOwnsRun(claim, targetEpoch) {
526
+ return sql`EXISTS (
527
+ SELECT 1 FROM _emdash_media_usage_index_status AS status
528
+ WHERE status.adapter_id = ${CONTENT_ADAPTER_ID}
529
+ AND status.scope_type = ${COLLECTION_SCOPE}
530
+ AND status.collection_id = ${claim.collectionId}
531
+ AND status.scope_key = ${claim.collectionSlug}
532
+ AND status.capture_state = 'active'
533
+ AND status.reconciliation_required = 1
534
+ AND status.status = 'running'
535
+ AND status.cursor = ${claim.runToken}
536
+ AND status.change_epoch = ${targetEpoch}
537
+ )`;
538
+ }
539
+ async seedNextCandidate() {
540
+ const runToken = ulid();
541
+ const now = timestampOffset(this.db, 0);
542
+ return (await sql`
543
+ INSERT INTO _emdash_media_usage_reconciliations (
544
+ collection_id,
545
+ collection_slug,
546
+ run_token,
547
+ next_attempt_at,
548
+ updated_at
549
+ )
550
+ SELECT status.collection_id, status.scope_key, ${runToken}, ${now}, ${now}
551
+ FROM _emdash_media_usage_index_status AS status
552
+ INNER JOIN _emdash_collections AS collection
553
+ ON collection.id = status.collection_id
554
+ AND collection.slug = status.scope_key
555
+ WHERE status.adapter_id = ${CONTENT_ADAPTER_ID}
556
+ AND status.scope_type = ${COLLECTION_SCOPE}
557
+ AND status.capture_state = 'active'
558
+ AND status.reconciliation_required = 1
559
+ AND status.collection_id IS NOT NULL
560
+ AND EXISTS (
561
+ SELECT 1 FROM _emdash_media_usage_activation AS activation
562
+ WHERE activation.task_key = ${ACTIVATION_KEY}
563
+ AND activation.state = 'active'
564
+ )
565
+ AND NOT EXISTS (
566
+ SELECT 1 FROM _emdash_media_usage_reconciliations AS existing
567
+ WHERE existing.collection_id = status.collection_id
568
+ )
569
+ AND NOT EXISTS (
570
+ SELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion
571
+ WHERE deletion.collection_id = status.collection_id
572
+ )
573
+ ORDER BY status.collection_id
574
+ LIMIT 1
575
+ ON CONFLICT (collection_id) DO NOTHING
576
+ RETURNING collection_id
577
+ `.execute(this.db)).rows.length === 1;
578
+ }
579
+ async findDue(limit) {
580
+ assertLimit(limit);
581
+ const nextAttemptIsDue = timestampIsDue(this.db, "next_attempt_at");
582
+ return (await sql`
583
+ WITH pending_candidates AS (
584
+ SELECT * FROM _emdash_media_usage_reconciliations
585
+ WHERE state = 'pending' AND ${nextAttemptIsDue}
586
+ ORDER BY next_attempt_at, updated_at, collection_id
587
+ LIMIT ${limit}
588
+ ), retry_candidates AS (
589
+ SELECT * FROM _emdash_media_usage_reconciliations
590
+ WHERE state = 'retry' AND ${nextAttemptIsDue}
591
+ ORDER BY next_attempt_at, updated_at, collection_id
592
+ LIMIT ${limit}
593
+ ), leased_candidates AS (
594
+ SELECT * FROM _emdash_media_usage_reconciliations
595
+ WHERE state = 'leased' AND ${timestampIsDue(this.db, "lease_expires_at")}
596
+ ORDER BY lease_expires_at, updated_at, collection_id
597
+ LIMIT ${limit}
598
+ ), candidates AS (
599
+ SELECT * FROM pending_candidates
600
+ UNION ALL SELECT * FROM retry_candidates
601
+ UNION ALL SELECT * FROM leased_candidates
602
+ )
603
+ SELECT * FROM candidates
604
+ ORDER BY CASE WHEN state = 'leased' THEN lease_expires_at ELSE next_attempt_at END,
605
+ updated_at,
606
+ collection_id
607
+ LIMIT ${limit}
608
+ `.execute(this.db)).rows.map(rowToRecord);
609
+ }
610
+ async hasDeferredCandidate() {
611
+ return await this.db.selectFrom("_emdash_media_usage_reconciliations").select("collection_id").where("state", "in", [
612
+ "pending",
613
+ "retry",
614
+ "leased"
615
+ ]).limit(1).executeTakeFirst() !== void 0;
616
+ }
617
+ async wakeDrainedBarrierCandidate() {
618
+ const now = timestampOffset(this.db, 0);
619
+ return (await sql`
620
+ UPDATE _emdash_media_usage_reconciliations
621
+ SET next_attempt_at = ${now}, updated_at = ${now}
622
+ WHERE collection_id = (
623
+ SELECT reconciliation.collection_id
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 reconciliation.state = 'pending'
629
+ AND reconciliation.target_epoch IS NOT NULL
630
+ AND NOT ${timestampIsDue(this.db, "reconciliation.next_attempt_at")}
631
+ AND status.adapter_id = ${CONTENT_ADAPTER_ID}
632
+ AND status.scope_type = ${COLLECTION_SCOPE}
633
+ AND status.capture_state = 'active'
634
+ AND status.reconciliation_required = 1
635
+ AND status.status = 'running'
636
+ AND status.cursor = reconciliation.run_token
637
+ AND status.change_epoch = reconciliation.target_epoch
638
+ AND NOT EXISTS (
639
+ SELECT 1 FROM _emdash_media_usage_work AS work
640
+ WHERE work.collection_id = reconciliation.collection_id
641
+ )
642
+ AND NOT EXISTS (
643
+ SELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion
644
+ WHERE deletion.collection_id = reconciliation.collection_id
645
+ )
646
+ ORDER BY reconciliation.next_attempt_at, reconciliation.updated_at,
647
+ reconciliation.collection_id
648
+ LIMIT 1
649
+ )
650
+ AND state = 'pending'
651
+ AND NOT ${timestampIsDue(this.db, "_emdash_media_usage_reconciliations.next_attempt_at")}
652
+ AND EXISTS (
653
+ SELECT 1 FROM _emdash_media_usage_activation AS activation
654
+ WHERE activation.task_key = ${ACTIVATION_KEY}
655
+ AND activation.state = 'active'
656
+ )
657
+ AND NOT EXISTS (
658
+ SELECT 1 FROM _emdash_media_usage_work AS work
659
+ WHERE work.collection_id = _emdash_media_usage_reconciliations.collection_id
660
+ )
661
+ RETURNING collection_id
662
+ `.execute(this.db)).rows.length === 1;
663
+ }
664
+ async findFailed(limit) {
665
+ assertLimit(limit);
666
+ 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([
667
+ eb("status.reconciliation_required", "=", 0),
668
+ eb.and([eb("status.status", "=", "running"), eb("status.cursor", "=", eb.ref("reconciliation.run_token"))]),
669
+ eb.and([eb("status.cursor", "is", null), eb("status.change_epoch", ">", eb.ref("reconciliation.target_epoch"))])
670
+ ])).orderBy("reconciliation.updated_at").orderBy("reconciliation.collection_id").limit(limit).execute()).map(rowToRecord);
671
+ }
672
+ async claim(input) {
673
+ assertIdentity(input);
674
+ assertDuration(input.leaseDurationSeconds, "lease duration");
675
+ const leaseToken = ulid();
676
+ const row = await this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
677
+ state: "leased",
678
+ lease_token: leaseToken,
679
+ lease_expires_at: timestampOffset(this.db, input.leaseDurationSeconds),
680
+ updated_at: timestampOffset(this.db, 0)
681
+ }).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();
682
+ return row ? {
683
+ ...rowToRecord(row),
684
+ leaseToken
685
+ } : null;
686
+ }
687
+ async release(input) {
688
+ assertLeaseIdentity(input);
689
+ assertDuration(input.delaySeconds, "release delay", true);
690
+ const result = await this.db.updateTable("_emdash_media_usage_reconciliations").set({
691
+ state: "pending",
692
+ next_attempt_at: timestampOffset(this.db, input.delaySeconds),
693
+ lease_token: null,
694
+ lease_expires_at: null,
695
+ updated_at: timestampOffset(this.db, 0)
696
+ }).where("collection_id", "=", input.collectionId).where("run_token", "=", input.runToken).where("state", "=", "leased").where("lease_token", "=", input.leaseToken).where(liveLease(this.db)).executeTakeFirst();
697
+ return Number(result.numUpdatedRows ?? 0) === 1;
698
+ }
699
+ async recordFailure(input) {
700
+ assertLeaseIdentity(input);
701
+ if (!STABLE_ERROR_CODE_PATTERN.test(input.errorCode)) throw new Error("Reconciliation failure requires a stable error code");
702
+ assertDuration(input.retryDelaySeconds, "retry delay", true);
703
+ const result = await this.db.updateTable("_emdash_media_usage_reconciliations").set({
704
+ state: input.terminal ? "failed" : sql`CASE WHEN attempt_count >= 4 THEN 'failed' ELSE 'retry' END`,
705
+ ...input.terminal ? { target_epoch: sql`COALESCE(
706
+ target_epoch,
707
+ (SELECT status.change_epoch
708
+ FROM _emdash_media_usage_index_status AS status
709
+ WHERE status.adapter_id = ${CONTENT_ADAPTER_ID}
710
+ AND status.scope_type = ${COLLECTION_SCOPE}
711
+ AND status.collection_id = ${input.collectionId}
712
+ AND status.status = 'running'
713
+ AND status.cursor = ${input.runToken})
714
+ )` } : {},
715
+ attempt_count: sql`attempt_count + 1`,
716
+ next_attempt_at: timestampOffset(this.db, input.retryDelaySeconds),
717
+ lease_token: null,
718
+ lease_expires_at: null,
719
+ last_error_code: input.errorCode,
720
+ updated_at: timestampOffset(this.db, 0)
721
+ }).where("collection_id", "=", input.collectionId).where("run_token", "=", input.runToken).where("state", "=", "leased").where("lease_token", "=", input.leaseToken).where(liveLease(this.db)).executeTakeFirst();
722
+ return Number(result.numUpdatedRows ?? 0) === 1;
723
+ }
724
+ async recordEntryFailure(claim) {
725
+ const result = await this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
726
+ state: "failed",
727
+ attempt_count: sql`attempt_count + 1`,
728
+ next_attempt_at: timestampOffset(this.db, 0),
729
+ lease_token: null,
730
+ lease_expires_at: null,
731
+ last_error_code: "MEDIA_USAGE_RECONCILIATION_ENTRY_FAILED",
732
+ updated_at: timestampOffset(this.db, 0)
733
+ }).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();
734
+ return Number(result.numUpdatedRows ?? 0) === 1;
735
+ }
736
+ async resetFailedForNewEpoch(observed) {
737
+ const collectionId = "collection_id" in observed ? observed.collection_id : observed.collectionId;
738
+ const runToken = "run_token" in observed ? observed.run_token : observed.runToken;
739
+ const targetEpoch = "target_epoch" in observed ? observed.target_epoch : observed.targetEpoch;
740
+ if (targetEpoch === null) return false;
741
+ const now = timestampOffset(this.db, 0);
742
+ const result = await this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
743
+ state: "pending",
744
+ phase: "scan",
745
+ target_epoch: null,
746
+ field_fingerprint: null,
747
+ scan_cursor: null,
748
+ scan_upper_id: null,
749
+ source_cursor: null,
750
+ source_upper_key: null,
751
+ attempt_count: 0,
752
+ next_attempt_at: now,
753
+ lease_token: null,
754
+ lease_expires_at: null,
755
+ last_error_code: null,
756
+ updated_at: now
757
+ }).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) => status.or([status("status.cursor", "is", null), status.and([status("status.status", "=", "running"), status("status.cursor", "=", runToken)])])).where("status.change_epoch", ">", targetEpoch))).executeTakeFirst();
758
+ return Number(result.numUpdatedRows ?? 0) === 1;
759
+ }
760
+ };
761
+ function rowToRecord(row) {
762
+ if (!isState(row.state) || !isPhase(row.phase) || !Number.isSafeInteger(row.attempt_count)) throw new Error("Invalid media usage reconciliation lifecycle");
763
+ return {
764
+ collectionId: row.collection_id,
765
+ collectionSlug: row.collection_slug,
766
+ runToken: row.run_token,
767
+ targetEpoch: row.target_epoch,
768
+ fieldFingerprint: row.field_fingerprint,
769
+ state: row.state,
770
+ phase: row.phase,
771
+ scanCursor: row.scan_cursor,
772
+ scanUpperId: row.scan_upper_id,
773
+ sourceCursor: row.source_cursor,
774
+ sourceUpperKey: row.source_upper_key,
775
+ attemptCount: row.attempt_count,
776
+ nextAttemptAt: row.next_attempt_at,
777
+ leaseToken: row.lease_token,
778
+ leaseExpiresAt: row.lease_expires_at,
779
+ lastErrorCode: row.last_error_code,
780
+ createdAt: row.created_at,
781
+ updatedAt: row.updated_at
782
+ };
783
+ }
784
+ function isState(state) {
785
+ return state === "pending" || state === "retry" || state === "leased" || state === "failed";
786
+ }
787
+ function isPhase(phase) {
788
+ return phase === "scan" || phase === "sources";
789
+ }
790
+ function assertLimit(limit) {
791
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_CANDIDATES) throw new Error("Reconciliation candidate limit must be from 1 to 100");
792
+ }
793
+ function assertIdentity(input) {
794
+ if (!input.collectionId || !input.runToken) throw new Error("Reconciliation requires an exact collection and run token");
795
+ }
796
+ function assertLeaseIdentity(input) {
797
+ assertIdentity(input);
798
+ if (!input.leaseToken) throw new Error("Reconciliation requires a lease token");
799
+ }
800
+ function assertDuration(value, label, allowZero = false) {
801
+ if (!Number.isSafeInteger(value) || value < (allowZero ? 0 : 1) || value > MAX_PORTABLE_DURATION_SECONDS) throw new Error(`Reconciliation ${label} is outside the portable range`);
802
+ }
803
+ function liveLease(db, column = "lease_expires_at") {
804
+ const expiry = sql.ref(column);
805
+ 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')`;
806
+ }
807
+ function timestampIsDue(db, column) {
808
+ const value = sql.ref(column);
809
+ 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')`;
810
+ }
811
+ function timestampOffset(db, offsetSeconds) {
812
+ if (isPostgres(db)) return sql`to_char(
813
+ (clock_timestamp() AT TIME ZONE 'UTC') + (${offsetSeconds} * INTERVAL '1 second'),
814
+ 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
815
+ )`;
816
+ return sql`strftime(
817
+ '%Y-%m-%dT%H:%M:%fZ',
818
+ 'now',
819
+ ${`${offsetSeconds >= 0 ? "+" : ""}${offsetSeconds} seconds`}
820
+ )`;
821
+ }
822
+ function contentTableName(collectionSlug) {
823
+ validateIdentifier(collectionSlug, "collection slug");
824
+ const tableName = `ec_${collectionSlug}`;
825
+ validateIdentifier(tableName, "content table");
826
+ return tableName;
827
+ }
828
+
829
+ //#endregion
830
+ //#region src/media/usage/reconciliation-processor.ts
831
+ const MEDIA_USAGE_RECONCILIATION_LIMITS = Object.freeze({
832
+ candidatesPerTick: 4,
833
+ scanPageSize: 1e3,
834
+ sourcePageSize: 1e3,
835
+ leaseDurationSeconds: 60,
836
+ maxAttempts: 5,
837
+ retryBaseSeconds: 30,
838
+ retryMaxSeconds: 900,
839
+ retryJitterRatio: .25
840
+ });
841
+ async function processDueMediaUsageReconciliationDetailed(db, options = {}) {
842
+ if (!options.activationKnownActive) {
843
+ if ((await db.selectFrom("_emdash_media_usage_activation").select("state").where("task_key", "=", "incremental_capture").executeTakeFirst())?.state !== "active") return {
844
+ outcome: "inactive",
845
+ consumedUnit: false,
846
+ hasDeferredCandidate: false
847
+ };
848
+ }
849
+ const reconciliation = new MediaUsageReconciliationRepository(db);
850
+ if (await reconciliation.deleteOneObsolete()) return {
851
+ outcome: "completed",
852
+ consumedUnit: true,
853
+ hasDeferredCandidate: false
854
+ };
855
+ const [failed] = await reconciliation.findFailed(1);
856
+ if (failed) {
857
+ if (await reconciliation.finishFailedCoverage(failed.collectionId, failed.runToken)) return {
858
+ outcome: "failed",
859
+ consumedUnit: true,
860
+ hasDeferredCandidate: false
861
+ };
862
+ if (await reconciliation.resetFailedForNewEpoch(failed)) return {
863
+ outcome: "advanced",
864
+ consumedUnit: true,
865
+ hasDeferredCandidate: false
866
+ };
867
+ }
868
+ const seeded = await reconciliation.seedNextCandidate();
869
+ const candidates = await reconciliation.findDue(MEDIA_USAGE_RECONCILIATION_LIMITS.candidatesPerTick);
870
+ let claim = null;
871
+ for (const candidate of candidates) {
872
+ claim = await reconciliation.claim({
873
+ collectionId: candidate.collectionId,
874
+ runToken: candidate.runToken,
875
+ leaseDurationSeconds: MEDIA_USAGE_RECONCILIATION_LIMITS.leaseDurationSeconds
876
+ });
877
+ if (claim) break;
878
+ }
879
+ if (!claim) return {
880
+ outcome: candidates.length === 0 ? "not_due" : "claim_lost",
881
+ consumedUnit: seeded,
882
+ hasDeferredCandidate: candidates.length === 0 && await reconciliation.hasDeferredCandidate()
883
+ };
884
+ try {
885
+ return {
886
+ outcome: await processClaimedReconciliation(db, claim),
887
+ consumedUnit: true,
888
+ hasDeferredCandidate: false
889
+ };
890
+ } catch (error) {
891
+ const terminal = claim.attemptCount + 1 >= MEDIA_USAGE_RECONCILIATION_LIMITS.maxAttempts;
892
+ if (!await reconciliation.recordFailure({
893
+ collectionId: claim.collectionId,
894
+ runToken: claim.runToken,
895
+ leaseToken: claim.leaseToken,
896
+ errorCode: "MEDIA_USAGE_RECONCILIATION_FAILED",
897
+ retryDelaySeconds: retryDelaySeconds$1(claim.attemptCount),
898
+ terminal
899
+ })) return {
900
+ outcome: "claim_lost",
901
+ consumedUnit: true,
902
+ hasDeferredCandidate: false
903
+ };
904
+ if (terminal) await reconciliation.finishFailedCoverage(claim.collectionId, claim.runToken);
905
+ console.error("[media-usage:reconciliation] Processing failed:", error);
906
+ return {
907
+ outcome: terminal ? "failed" : "retry",
908
+ consumedUnit: true,
909
+ hasDeferredCandidate: false
910
+ };
911
+ }
912
+ }
913
+ async function processClaimedMediaUsageReconciliationScan(db, claim, options = {}, knownCurrent) {
914
+ const reconciliation = new MediaUsageReconciliationRepository(db);
915
+ let current = knownCurrent ?? await reconciliation.findByIdentity(claim.collectionId, claim.runToken);
916
+ if (!current || current.leaseToken !== claim.leaseToken || current.phase !== "scan") return "deferred";
917
+ let fields;
918
+ let fieldFingerprint;
919
+ if (current.targetEpoch === null) {
920
+ const targetEpoch = await reconciliation.beginRun(claim);
921
+ if (targetEpoch === null) {
922
+ await reconciliation.release({
923
+ ...claim,
924
+ delaySeconds: 30
925
+ });
926
+ return "deferred";
927
+ }
928
+ fields = await loadContentMediaUsageFields(db, claim.collectionSlug, claim.collectionId);
929
+ fieldFingerprint = await buildContentMediaUsageFieldFingerprint(fields);
930
+ const scanUpperId = fields.extractionFields.length === 0 ? null : await reconciliation.findScanUpperId(claim);
931
+ if (!await reconciliation.initializeScan({
932
+ claim,
933
+ targetEpoch,
934
+ fieldFingerprint,
935
+ scanUpperId
936
+ })) return "deferred";
937
+ current = {
938
+ ...current,
939
+ targetEpoch,
940
+ fieldFingerprint,
941
+ scanCursor: null,
942
+ scanUpperId,
943
+ sourceCursor: null,
944
+ sourceUpperKey: null
945
+ };
946
+ } else {
947
+ fields = await loadContentMediaUsageFields(db, claim.collectionSlug, claim.collectionId);
948
+ fieldFingerprint = await buildContentMediaUsageFieldFingerprint(fields);
949
+ }
950
+ if (current.fieldFingerprint !== fieldFingerprint || current.targetEpoch === null) return "restart_required";
951
+ const contentIds = await reconciliation.findScanPage(current, MEDIA_USAGE_RECONCILIATION_LIMITS.scanPageSize);
952
+ if (contentIds.length === 0) {
953
+ if (options.releaseOnExhausted ?? true) await reconciliation.release({
954
+ ...claim,
955
+ delaySeconds: 30
956
+ });
957
+ return "exhausted";
958
+ }
959
+ await new MediaUsageWorkRepository(db).enqueueReconciliationPage({
960
+ collectionId: claim.collectionId,
961
+ collectionSlug: claim.collectionSlug,
962
+ runToken: claim.runToken,
963
+ leaseToken: claim.leaseToken,
964
+ changeEpoch: current.targetEpoch,
965
+ phase: "scan",
966
+ contentIds
967
+ });
968
+ const nextCursor = contentIds.at(-1);
969
+ if (!await reconciliation.checkpointScan({
970
+ claim,
971
+ targetEpoch: current.targetEpoch,
972
+ previousCursor: current.scanCursor,
973
+ nextCursor
974
+ })) return "deferred";
975
+ if (!await reconciliation.release({
976
+ ...claim,
977
+ delaySeconds: 0
978
+ })) return "deferred";
979
+ return "advanced";
980
+ }
981
+ async function processClaimedReconciliation(db, claim) {
982
+ const reconciliation = new MediaUsageReconciliationRepository(db);
983
+ let current = claim;
984
+ if (current.targetEpoch !== null && !await reconciliation.ownsRun(claim, current.targetEpoch)) return restartReconciliation(db, reconciliation, claim, current);
985
+ if (current.targetEpoch !== null) await new MediaUsageWorkRepository(db).deleteObsoleteReconciliationWork({
986
+ collectionId: claim.collectionId,
987
+ collectionSlug: claim.collectionSlug,
988
+ runToken: claim.runToken,
989
+ leaseToken: claim.leaseToken,
990
+ targetEpoch: current.targetEpoch
991
+ });
992
+ if (current.phase === "scan") {
993
+ const outcome = await processClaimedMediaUsageReconciliationScan(db, claim, { releaseOnExhausted: false }, current);
994
+ if (outcome === "restart_required") {
995
+ current = await reconciliation.findByIdentity(claim.collectionId, claim.runToken) ?? current;
996
+ return restartReconciliation(db, reconciliation, claim, current);
997
+ }
998
+ if (outcome !== "exhausted") return outcome;
999
+ if (current.targetEpoch === null || current.fieldFingerprint === null) current = await reconciliation.findByIdentity(claim.collectionId, claim.runToken) ?? current;
1000
+ const barrier = await reconciliation.findWorkBarrier(claim.collectionId);
1001
+ if (barrier.state === "failed") return failReconciliation(reconciliation, claim, barrier.errorCode, true);
1002
+ if (barrier.state === "pending") {
1003
+ await reconciliation.release({
1004
+ ...claim,
1005
+ delaySeconds: 30
1006
+ });
1007
+ return "deferred";
1008
+ }
1009
+ if (current.targetEpoch === null || current.fieldFingerprint === null) return "claim_lost";
1010
+ const sourceUpperKey = await reconciliation.findSourceUpperKey(claim, current.targetEpoch);
1011
+ if (!await reconciliation.transitionToSources({
1012
+ claim,
1013
+ targetEpoch: current.targetEpoch,
1014
+ fieldFingerprint: current.fieldFingerprint,
1015
+ sourceUpperKey
1016
+ })) return "claim_lost";
1017
+ if (!await reconciliation.release({
1018
+ ...claim,
1019
+ delaySeconds: 0
1020
+ })) return "claim_lost";
1021
+ return "advanced";
1022
+ }
1023
+ return processSourcePhase(db, reconciliation, claim, current);
1024
+ }
1025
+ async function processSourcePhase(db, reconciliation, claim, current) {
1026
+ if (current.targetEpoch === null || current.fieldFingerprint === null) return "claim_lost";
1027
+ const fields = await loadContentMediaUsageFields(db, claim.collectionSlug, claim.collectionId);
1028
+ const fieldFingerprint = await buildContentMediaUsageFieldFingerprint(fields);
1029
+ if (fieldFingerprint !== current.fieldFingerprint) return restartReconciliation(db, reconciliation, claim, current, fields, fieldFingerprint);
1030
+ const page = await reconciliation.findSourcePage(current, MEDIA_USAGE_RECONCILIATION_LIMITS.sourcePageSize);
1031
+ if (page.length > 0) {
1032
+ let enqueuedMissingContent = false;
1033
+ if (page.some((source) => !source.contentId || source.sourceVariant !== "columns" && source.sourceVariant !== "draft_overlay")) return failReconciliation(reconciliation, claim, "MEDIA_USAGE_RECONCILIATION_INVALID_SOURCE", false);
1034
+ const contentIds = [...new Set(page.map((source) => source.contentId))];
1035
+ const enqueueIds = fields.extractionFields.length === 0 ? contentIds : await reconciliation.findMissingContentIds(claim.collectionSlug, contentIds);
1036
+ if (enqueueIds.length > 0) {
1037
+ await new MediaUsageWorkRepository(db).enqueueReconciliationPage({
1038
+ collectionId: claim.collectionId,
1039
+ collectionSlug: claim.collectionSlug,
1040
+ runToken: claim.runToken,
1041
+ leaseToken: claim.leaseToken,
1042
+ changeEpoch: current.targetEpoch,
1043
+ phase: "sources",
1044
+ contentIds: enqueueIds
1045
+ });
1046
+ enqueuedMissingContent = true;
1047
+ }
1048
+ if (!await reconciliation.checkpointSources({
1049
+ claim,
1050
+ targetEpoch: current.targetEpoch,
1051
+ previousCursor: current.sourceCursor,
1052
+ nextCursor: page.at(-1).sourceKey
1053
+ })) return "claim_lost";
1054
+ if (enqueuedMissingContent || page.length === MEDIA_USAGE_RECONCILIATION_LIMITS.sourcePageSize) {
1055
+ if (!await reconciliation.release({
1056
+ ...claim,
1057
+ delaySeconds: 0
1058
+ })) return "claim_lost";
1059
+ return "advanced";
1060
+ }
1061
+ }
1062
+ const barrier = await reconciliation.findWorkBarrier(claim.collectionId);
1063
+ if (barrier.state === "failed") return failReconciliation(reconciliation, claim, barrier.errorCode, true);
1064
+ if (barrier.state === "pending") {
1065
+ await reconciliation.release({
1066
+ ...claim,
1067
+ delaySeconds: 30
1068
+ });
1069
+ return "deferred";
1070
+ }
1071
+ if (!await reconciliation.finalizeCoverage({
1072
+ claim,
1073
+ targetEpoch: current.targetEpoch,
1074
+ fieldFingerprint,
1075
+ schemaVersion: CONTENT_SOURCE_SCHEMA_VERSION
1076
+ })) return "claim_lost";
1077
+ if (!await reconciliation.deleteFinalized(claim)) return "claim_lost";
1078
+ return "completed";
1079
+ }
1080
+ async function restartReconciliation(db, reconciliation, claim, current, fields, fieldFingerprint) {
1081
+ if (current.targetEpoch === null) return "claim_lost";
1082
+ const discoveredFields = fields ?? await loadContentMediaUsageFields(db, claim.collectionSlug, claim.collectionId);
1083
+ const fingerprint = fieldFingerprint ?? await buildContentMediaUsageFieldFingerprint(discoveredFields);
1084
+ const targetEpoch = await reconciliation.restartRun(claim, current.targetEpoch);
1085
+ if (targetEpoch === null) {
1086
+ await reconciliation.release({
1087
+ ...claim,
1088
+ delaySeconds: 30
1089
+ });
1090
+ return "deferred";
1091
+ }
1092
+ const scanUpperId = discoveredFields.extractionFields.length === 0 ? null : await reconciliation.findScanUpperId(claim);
1093
+ if (!await reconciliation.restartScan({
1094
+ claim,
1095
+ previousEpoch: current.targetEpoch,
1096
+ targetEpoch,
1097
+ fieldFingerprint: fingerprint,
1098
+ scanUpperId
1099
+ })) return "claim_lost";
1100
+ if (!await reconciliation.release({
1101
+ ...claim,
1102
+ delaySeconds: 0
1103
+ })) return "claim_lost";
1104
+ return "advanced";
1105
+ }
1106
+ async function failReconciliation(reconciliation, claim, errorCode, entryFailure) {
1107
+ if (!(entryFailure ? await reconciliation.recordEntryFailure(claim) : await reconciliation.recordFailure({
1108
+ collectionId: claim.collectionId,
1109
+ runToken: claim.runToken,
1110
+ leaseToken: claim.leaseToken,
1111
+ errorCode,
1112
+ retryDelaySeconds: 0,
1113
+ terminal: true
1114
+ }))) return "claim_lost";
1115
+ await reconciliation.finishFailedCoverage(claim.collectionId, claim.runToken);
1116
+ return "failed";
1117
+ }
1118
+ function retryDelaySeconds$1(attemptCount) {
1119
+ const exponential = Math.min(MEDIA_USAGE_RECONCILIATION_LIMITS.retryMaxSeconds, MEDIA_USAGE_RECONCILIATION_LIMITS.retryBaseSeconds * 2 ** attemptCount);
1120
+ const jitter = Math.floor(exponential * MEDIA_USAGE_RECONCILIATION_LIMITS.retryJitterRatio * Math.random());
1121
+ return Math.min(MEDIA_USAGE_RECONCILIATION_LIMITS.retryMaxSeconds, exponential + jitter);
1122
+ }
1123
+
1124
+ //#endregion
1125
+ //#region src/media/usage/work-processor.ts
1126
+ const MEDIA_USAGE_WORK_PROCESSING_LIMITS = Object.freeze({
1127
+ candidatesPerTick: 1e3,
1128
+ leaseDurationSeconds: 1200,
1129
+ maxAttempts: 5,
1130
+ retryBaseSeconds: 30,
1131
+ retryMaxSeconds: 900,
1132
+ retryJitterRatio: .25
1133
+ });
1134
+ async function processMediaUsageWorkAfterWrite(db, collectionSlug, contentId) {
1135
+ if (!await isIncrementalCaptureActive(db)) return {
1136
+ outcome: "inactive",
1137
+ claimed: false
1138
+ };
1139
+ await new MediaUsageRepository(db).recoverIncrementalFinalizations();
1140
+ const repo = new MediaUsageWorkRepository(db);
1141
+ const work = await repo.findWorkForContent(collectionSlug, contentId);
1142
+ if (!work) return {
1143
+ outcome: "not_due",
1144
+ claimed: false
1145
+ };
1146
+ const claimed = await repo.claimWork({
1147
+ collectionId: work.collectionId,
1148
+ contentId: work.contentId,
1149
+ workVersion: work.workVersion,
1150
+ leaseDurationSeconds: MEDIA_USAGE_WORK_PROCESSING_LIMITS.leaseDurationSeconds
1151
+ });
1152
+ if (!claimed) return {
1153
+ outcome: "claim_lost",
1154
+ claimed: false
1155
+ };
1156
+ try {
1157
+ return (await runClaimedBatch(db, [claimed])).get(workResultKey(claimed)) ?? {
1158
+ outcome: "claim_lost",
1159
+ claimed: false
1160
+ };
1161
+ } catch (error) {
1162
+ const outcome = (await repo.retryClaimedWorkBatch({
1163
+ work: [workLease(claimed)],
1164
+ errorCode: "MEDIA_USAGE_PROCESSING_FAILED",
1165
+ retryDelaySeconds: MEDIA_USAGE_WORK_PROCESSING_LIMITS.retryBaseSeconds,
1166
+ maxAttempts: MEDIA_USAGE_WORK_PROCESSING_LIMITS.maxAttempts
1167
+ })).get(workIdentityKey(claimed)) ?? "superseded";
1168
+ if (outcome === "failed") await new MediaUsageRepository(db).recordIncrementalFailure({
1169
+ collectionId: claimed.collectionId,
1170
+ collectionSlug: claimed.collectionSlug,
1171
+ contentId: claimed.contentId,
1172
+ workVersion: claimed.workVersion,
1173
+ errorCode: "MEDIA_USAGE_PROCESSING_FAILED"
1174
+ });
1175
+ console.error("[media-usage:work] Immediate processing failed:", error);
1176
+ return {
1177
+ outcome,
1178
+ claimed: true
1179
+ };
1180
+ }
1181
+ }
1182
+ async function processDueMediaUsageWork(db, options = {}) {
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
+ };
1194
+ if (!options.activationKnownActive && !await isIncrementalCaptureActive(db)) {
1195
+ result.durationMs = Date.now() - startedAt;
1196
+ return result;
1197
+ }
1198
+ await new MediaUsageRepository(db).recoverIncrementalFinalizations();
1199
+ const repo = new MediaUsageWorkRepository(db);
1200
+ const candidates = await repo.claimDueWorkBatch({
1201
+ limit: MEDIA_USAGE_WORK_PROCESSING_LIMITS.candidatesPerTick,
1202
+ leaseDurationSeconds: MEDIA_USAGE_WORK_PROCESSING_LIMITS.leaseDurationSeconds
1203
+ });
1204
+ result.candidateCount = candidates.length > 0 || !await repo.hasNonterminalWork() ? candidates.length : 1;
1205
+ const processedBatch = await processClaimedCollections(db, candidates);
1206
+ for (const candidate of candidates) {
1207
+ const processed = processedBatch.get(workResultKey(candidate)) ?? {
1208
+ outcome: "claim_lost",
1209
+ claimed: false
1210
+ };
1211
+ if (processed.claimed) result.claimedCount++;
1212
+ if (processed.outcome === "completed") result.completedCount++;
1213
+ if (processed.outcome === "retry") result.retryCount++;
1214
+ if (processed.outcome === "failed") result.failedCount++;
1215
+ if (processed.outcome === "superseded") result.supersededCount++;
1216
+ if (processed.outcome === "obsolete") result.obsoleteCount++;
1217
+ }
1218
+ result.durationMs = Date.now() - startedAt;
1219
+ return result;
1220
+ }
1221
+ async function processClaimedCollections(db, candidates) {
1222
+ const processed = /* @__PURE__ */ new Map();
1223
+ const byCollection = /* @__PURE__ */ new Map();
1224
+ for (const candidate of candidates) {
1225
+ const key = collectionResultKey(candidate);
1226
+ const collection = byCollection.get(key) ?? [];
1227
+ collection.push(candidate);
1228
+ byCollection.set(key, collection);
1229
+ }
1230
+ for (const collection of byCollection.values()) try {
1231
+ for (const [key, result] of await runClaimedBatch(db, collection)) processed.set(key, result);
1232
+ } catch (error) {
1233
+ for (const [key, result] of await retryFailedClaimedCollection(db, collection)) processed.set(key, result);
1234
+ console.error("[media-usage:work] Collection processing failed:", error);
1235
+ }
1236
+ return processed;
1237
+ }
1238
+ async function retryFailedClaimedCollection(db, candidates) {
1239
+ const transitioned = await new MediaUsageWorkRepository(db).retryClaimedWorkBatch({
1240
+ work: candidates.map(workLease),
1241
+ errorCode: "MEDIA_USAGE_PROCESSING_FAILED",
1242
+ retryDelaySeconds: MEDIA_USAGE_WORK_PROCESSING_LIMITS.retryBaseSeconds,
1243
+ maxAttempts: MEDIA_USAGE_WORK_PROCESSING_LIMITS.maxAttempts
1244
+ });
1245
+ const processed = /* @__PURE__ */ new Map();
1246
+ const failedCollectionIds = /* @__PURE__ */ new Set();
1247
+ for (const candidate of candidates) {
1248
+ const state = transitioned.get(workIdentityKey(candidate));
1249
+ if (state === "failed") failedCollectionIds.add(candidate.collectionId);
1250
+ processed.set(workResultKey(candidate), {
1251
+ outcome: state ?? "superseded",
1252
+ claimed: true
1253
+ });
1254
+ }
1255
+ await new MediaUsageRepository(db).recordIncrementalFailuresByCollection({
1256
+ collectionIds: [...failedCollectionIds],
1257
+ errorCode: "MEDIA_USAGE_PROCESSING_FAILED"
1258
+ });
1259
+ return processed;
1260
+ }
1261
+ async function runClaimedBatch(db, candidates) {
1262
+ return isPostgres(db) ? withTransaction(db, (trx) => processClaimedBatch(trx, new MediaUsageWorkRepository(trx), candidates)) : processClaimedBatch(db, new MediaUsageWorkRepository(db), candidates);
1263
+ }
1264
+ async function processClaimedBatch(db, repo, candidates) {
1265
+ const results = /* @__PURE__ */ new Map();
1266
+ const locked = await repo.lockClaimedWorkBatch(candidates.map(workLease));
1267
+ const owned = [];
1268
+ for (const candidate of candidates) if (locked.has(workIdentityKey(candidate))) owned.push(candidate);
1269
+ else results.set(workResultKey(candidate), {
1270
+ outcome: "superseded",
1271
+ claimed: true
1272
+ });
1273
+ const currentCollections = await findCurrentCollectionIdentities(db, owned);
1274
+ const current = [];
1275
+ const obsolete = [];
1276
+ for (const candidate of owned) if (currentCollections.has(collectionResultKey(candidate))) current.push(candidate);
1277
+ else obsolete.push(candidate);
1278
+ const obsoleteCompleted = await repo.completeWorkBatch(obsolete.map(workLease));
1279
+ for (const candidate of obsolete) results.set(workResultKey(candidate), {
1280
+ outcome: obsoleteCompleted.has(workIdentityKey(candidate)) ? "obsolete" : "superseded",
1281
+ claimed: true
1282
+ });
1283
+ if (current.length === 0) return results;
1284
+ const refreshes = await refreshContentMediaUsageForWorkBatch(db, current, { shouldContinue: canContinueBulkWork });
1285
+ const successful = [];
1286
+ const unstarted = [];
1287
+ const failedRefreshes = [];
1288
+ for (const candidate of current) {
1289
+ const refresh = refreshes.get(contentRefreshKey(candidate.collectionId, candidate.contentId));
1290
+ if (refresh?.success) successful.push(candidate);
1291
+ else if (!refresh) unstarted.push(candidate);
1292
+ else failedRefreshes.push({
1293
+ candidate,
1294
+ refresh
1295
+ });
1296
+ }
1297
+ const terminalFailureCollections = /* @__PURE__ */ new Map();
1298
+ const failureGroups = /* @__PURE__ */ new Map();
1299
+ for (const failure of failedRefreshes) {
1300
+ const errorCode = processingErrorCode(failure.refresh.errorCode);
1301
+ const terminal = errorCode === "MEDIA_USAGE_RESOURCE_LIMIT" || failure.candidate.attemptCount + 1 >= MEDIA_USAGE_WORK_PROCESSING_LIMITS.maxAttempts;
1302
+ const delay = retryDelaySeconds(failure.candidate.attemptCount);
1303
+ const key = `${errorCode}\u0000${terminal ? "terminal" : String(delay)}`;
1304
+ const group = failureGroups.get(key) ?? {
1305
+ errorCode,
1306
+ retryDelaySeconds: delay,
1307
+ terminal,
1308
+ candidates: []
1309
+ };
1310
+ group.candidates.push(failure.candidate);
1311
+ failureGroups.set(key, group);
1312
+ }
1313
+ for (const group of failureGroups.values()) {
1314
+ const transitioned = await repo.retryClaimedWorkBatch({
1315
+ work: group.candidates.map(workLease),
1316
+ errorCode: group.errorCode,
1317
+ retryDelaySeconds: group.retryDelaySeconds,
1318
+ maxAttempts: group.terminal ? 1 : MEDIA_USAGE_WORK_PROCESSING_LIMITS.maxAttempts
1319
+ });
1320
+ for (const candidate of group.candidates) {
1321
+ const state = transitioned.get(workIdentityKey(candidate));
1322
+ if (state === "failed") {
1323
+ const collections = terminalFailureCollections.get(group.errorCode) ?? /* @__PURE__ */ new Set();
1324
+ collections.add(candidate.collectionId);
1325
+ terminalFailureCollections.set(group.errorCode, collections);
1326
+ }
1327
+ results.set(workResultKey(candidate), {
1328
+ outcome: state ?? "superseded",
1329
+ claimed: true
1330
+ });
1331
+ }
1332
+ }
1333
+ for (const [errorCode, collectionIds] of terminalFailureCollections) await new MediaUsageRepository(db).recordIncrementalFailuresByCollection({
1334
+ collectionIds: [...collectionIds],
1335
+ errorCode
1336
+ });
1337
+ const released = await repo.releaseClaimedWorkBatch(unstarted.map(workLease));
1338
+ for (const candidate of unstarted) results.set(workResultKey(candidate), {
1339
+ outcome: released.has(workIdentityKey(candidate)) ? "not_due" : "superseded",
1340
+ claimed: true
1341
+ });
1342
+ const usage = new MediaUsageRepository(db);
1343
+ const successfulByCollection = /* @__PURE__ */ new Map();
1344
+ for (const candidate of successful) {
1345
+ const key = collectionResultKey(candidate);
1346
+ const collection = successfulByCollection.get(key) ?? [];
1347
+ collection.push(candidate);
1348
+ successfulByCollection.set(key, collection);
1349
+ }
1350
+ const readyToComplete = [];
1351
+ const deferredCompletion = [];
1352
+ const lostFinalization = [];
1353
+ let pendingFinalizationQueries = 2;
1354
+ for (const collection of successfulByCollection.values()) {
1355
+ const first = collection[0];
1356
+ if (!first) continue;
1357
+ const metrics = getRequestContext()?.metrics;
1358
+ if (metrics && metrics.dbCount + pendingFinalizationQueries + 4 > 900) {
1359
+ deferredCompletion.push(...collection);
1360
+ continue;
1361
+ }
1362
+ if ((await usage.prepareIncrementalFinalization({
1363
+ collectionId: first.collectionId,
1364
+ collectionSlug: first.collectionSlug
1365
+ })).outcome !== "lost") {
1366
+ readyToComplete.push(...collection);
1367
+ pendingFinalizationQueries += 2;
1368
+ continue;
1369
+ }
1370
+ lostFinalization.push(...collection);
1371
+ pendingFinalizationQueries += 2;
1372
+ }
1373
+ const lostTransitions = await repo.retryClaimedWorkBatch({
1374
+ work: lostFinalization.map(workLease),
1375
+ errorCode: "MEDIA_USAGE_GENERATION_CONFLICT",
1376
+ retryDelaySeconds: MEDIA_USAGE_WORK_PROCESSING_LIMITS.retryBaseSeconds,
1377
+ maxAttempts: MEDIA_USAGE_WORK_PROCESSING_LIMITS.maxAttempts
1378
+ });
1379
+ const lostFailedCollections = /* @__PURE__ */ new Set();
1380
+ for (const candidate of lostFinalization) {
1381
+ const state = lostTransitions.get(workIdentityKey(candidate));
1382
+ if (state === "failed") lostFailedCollections.add(candidate.collectionId);
1383
+ results.set(workResultKey(candidate), {
1384
+ outcome: state ?? "superseded",
1385
+ claimed: true
1386
+ });
1387
+ }
1388
+ await usage.recordIncrementalFailuresByCollection({
1389
+ collectionIds: [...lostFailedCollections],
1390
+ errorCode: "MEDIA_USAGE_GENERATION_CONFLICT"
1391
+ });
1392
+ const deferred = await repo.releaseClaimedWorkBatch(deferredCompletion.map(workLease));
1393
+ for (const candidate of deferredCompletion) results.set(workResultKey(candidate), {
1394
+ outcome: deferred.has(workIdentityKey(candidate)) ? "not_due" : "superseded",
1395
+ claimed: true
1396
+ });
1397
+ const completed = await repo.completeWorkBatch(readyToComplete.map(workLease));
1398
+ const completedCollections = /* @__PURE__ */ new Map();
1399
+ for (const candidate of readyToComplete) {
1400
+ const didComplete = completed.has(workIdentityKey(candidate));
1401
+ results.set(workResultKey(candidate), {
1402
+ outcome: didComplete ? "completed" : "superseded",
1403
+ claimed: true
1404
+ });
1405
+ if (didComplete) completedCollections.set(collectionResultKey(candidate), candidate);
1406
+ }
1407
+ for (const collection of completedCollections.values()) await usage.recordIncrementalSuccess({
1408
+ collectionId: collection.collectionId,
1409
+ collectionSlug: collection.collectionSlug
1410
+ });
1411
+ return results;
1412
+ }
1413
+ async function findCurrentCollectionIdentities(db, candidates) {
1414
+ const identities = new Map(candidates.map((candidate) => [collectionResultKey(candidate), candidate]));
1415
+ const current = /* @__PURE__ */ new Set();
1416
+ for (const batch of chunkCollectionIds(Array.from(identities.values(), (item) => item.collectionId))) {
1417
+ const rows = await db.selectFrom("_emdash_collections").select(["id", "slug"]).where("id", "in", batch).execute();
1418
+ for (const row of rows) current.add(`${row.id}\u0000${row.slug}`);
1419
+ }
1420
+ return current;
1421
+ }
1422
+ function workLease(work) {
1423
+ if (!work.leaseToken) throw new Error("Claimed media usage work requires a lease token");
1424
+ return {
1425
+ collectionId: work.collectionId,
1426
+ contentId: work.contentId,
1427
+ workVersion: work.workVersion,
1428
+ leaseToken: work.leaseToken
1429
+ };
1430
+ }
1431
+ function workIdentityKey(work) {
1432
+ return `${work.collectionId}\u0000${work.contentId}\u0000${String(work.workVersion)}`;
1433
+ }
1434
+ function workResultKey(work) {
1435
+ return workIdentityKey(work);
1436
+ }
1437
+ function collectionResultKey(work) {
1438
+ return `${work.collectionId}\u0000${work.collectionSlug}`;
1439
+ }
1440
+ function chunkCollectionIds(ids) {
1441
+ const unique = [...new Set(ids)];
1442
+ const batches = [];
1443
+ for (let index = 0; index < unique.length; index += 50) batches.push(unique.slice(index, index + 50));
1444
+ return batches;
1445
+ }
1446
+ function canContinueBulkWork() {
1447
+ const metrics = getRequestContext()?.metrics;
1448
+ return !metrics || metrics.dbCount + 150 <= 900;
1449
+ }
1450
+ async function isIncrementalCaptureActive(db) {
1451
+ return (await db.selectFrom("_emdash_media_usage_activation").select("state").where("task_key", "=", "incremental_capture").executeTakeFirst())?.state === "active";
1452
+ }
1453
+ function retryDelaySeconds(attemptCount) {
1454
+ const exponential = Math.min(MEDIA_USAGE_WORK_PROCESSING_LIMITS.retryMaxSeconds, MEDIA_USAGE_WORK_PROCESSING_LIMITS.retryBaseSeconds * 2 ** attemptCount);
1455
+ const jitter = Math.floor(exponential * MEDIA_USAGE_WORK_PROCESSING_LIMITS.retryJitterRatio * Math.random());
1456
+ return Math.min(MEDIA_USAGE_WORK_PROCESSING_LIMITS.retryMaxSeconds, exponential + jitter);
1457
+ }
1458
+ function processingErrorCode(errorCode) {
1459
+ if (errorCode === "DRAFT_REVISION_NOT_FOUND" || errorCode === "DRAFT_REVISION_MISMATCH" || errorCode === "DRAFT_REVISION_INVALID") return "MEDIA_USAGE_SNAPSHOT_FAILED";
1460
+ if (errorCode === "CONTENT_USAGE_GENERATION_CONFLICT") return "MEDIA_USAGE_GENERATION_CONFLICT";
1461
+ if (errorCode === "CONTENT_USAGE_RESOURCE_LIMIT") return "MEDIA_USAGE_RESOURCE_LIMIT";
1462
+ return "MEDIA_USAGE_PROCESSING_FAILED";
1463
+ }
1464
+
1465
+ //#endregion
1466
+ //#region src/media/usage/maintenance-engine.ts
1467
+ const MEDIA_USAGE_MAINTENANCE_LIMITS = Object.freeze({
1468
+ eventQueryCeiling: 900,
1469
+ maxStepQueries: 150
1470
+ });
1471
+ const TASK_CLASSES = [
1472
+ "collection_deletion",
1473
+ "entry_work",
1474
+ "reconciliation"
1475
+ ];
1476
+ async function runMediaUsageMaintenanceStep(db) {
1477
+ const activation = await db.selectFrom("_emdash_media_usage_activation").select(["state", "runtime_generation"]).where("task_key", "=", "incremental_capture").executeTakeFirst();
1478
+ if (activation?.state !== "active" || activation.runtime_generation !== MEDIA_USAGE_ACTIVATION_RUNTIME_GENERATION) return runActivationStep(db);
1479
+ let blocked = false;
1480
+ let blockedReconciliation = false;
1481
+ let madeProgress = false;
1482
+ for (const taskClass of TASK_CLASSES) {
1483
+ const metrics = getRequestContext()?.metrics;
1484
+ if (metrics && !canStartMediaUsageMaintenanceStep(metrics)) return madeProgress ? {
1485
+ state: "progress",
1486
+ continuation: { kind: "immediate" }
1487
+ } : {
1488
+ state: "blocked",
1489
+ continuation: { kind: "immediate" }
1490
+ };
1491
+ const outcome = await runTaskClass(db, taskClass);
1492
+ if (outcome === "inactive") return inactiveResult();
1493
+ if (outcome === "progress") madeProgress = true;
1494
+ if (outcome === "blocked") {
1495
+ blocked = true;
1496
+ if (taskClass === "reconciliation") blockedReconciliation = true;
1497
+ }
1498
+ }
1499
+ if (madeProgress) return {
1500
+ state: "progress",
1501
+ continuation: { kind: "immediate" }
1502
+ };
1503
+ if (blockedReconciliation && await new MediaUsageReconciliationRepository(db).wakeDrainedBarrierCandidate()) return {
1504
+ state: "progress",
1505
+ continuation: { kind: "immediate" }
1506
+ };
1507
+ if (blocked) return {
1508
+ state: "blocked",
1509
+ continuation: {
1510
+ kind: "delayed",
1511
+ delaySeconds: 30
1512
+ }
1513
+ };
1514
+ return {
1515
+ state: "idle",
1516
+ continuation: { kind: "none" }
1517
+ };
1518
+ }
1519
+ async function runActivationStep(db) {
1520
+ try {
1521
+ const result = await continueMediaUsageActivation(db);
1522
+ if (result.outcome === "activating" || result.outcome === "active") return {
1523
+ state: "progress",
1524
+ continuation: { kind: "immediate" }
1525
+ };
1526
+ if (result.outcome === "lease_active" || result.outcome === "conflict") return {
1527
+ state: "blocked",
1528
+ continuation: {
1529
+ kind: "delayed",
1530
+ delaySeconds: 30
1531
+ }
1532
+ };
1533
+ return inactiveResult();
1534
+ } catch (error) {
1535
+ if (error instanceof MediaUsageActivationVersionMismatchError) return inactiveResult();
1536
+ throw error;
1537
+ }
1538
+ }
1539
+ async function runTaskClass(db, taskClass) {
1540
+ if (taskClass === "entry_work") {
1541
+ const result = await processDueMediaUsageWork(db, { activationKnownActive: true });
1542
+ if (result.claimedCount > 0) return "progress";
1543
+ return result.candidateCount > 0 ? "blocked" : "idle";
1544
+ }
1545
+ if (taskClass === "collection_deletion") {
1546
+ const result = await processDueMediaUsageCollectionDeletions(db);
1547
+ if (result.claimedCount > 0) return "progress";
1548
+ return result.candidateCount > 0 ? "blocked" : "idle";
1549
+ }
1550
+ const result = await processDueMediaUsageReconciliationDetailed(db, { activationKnownActive: true });
1551
+ if (result.outcome === "inactive") return "inactive";
1552
+ if (result.consumedUnit) return "progress";
1553
+ return result.outcome === "claim_lost" || result.hasDeferredCandidate ? "blocked" : "idle";
1554
+ }
1555
+ function inactiveResult() {
1556
+ return {
1557
+ state: "inactive",
1558
+ continuation: { kind: "none" }
1559
+ };
1560
+ }
1561
+ function canStartMediaUsageMaintenanceStep(metrics) {
1562
+ return metrics.dbCount + MEDIA_USAGE_MAINTENANCE_LIMITS.maxStepQueries <= MEDIA_USAGE_MAINTENANCE_LIMITS.eventQueryCeiling;
1563
+ }
1564
+
1565
+ //#endregion
1566
+ //#region src/api/handlers/media-usage.ts
1567
+ var media_usage_exports = /* @__PURE__ */ __exportAll({
1568
+ aggregateMediaUsageCoverageStatus: () => aggregateMediaUsageCoverageStatus,
1569
+ handleMediaUsageDetails: () => handleMediaUsageDetails,
1570
+ handleMediaUsageProgress: () => handleMediaUsageProgress,
1571
+ handleMediaUsageProgressAdvance: () => handleMediaUsageProgressAdvance,
1572
+ handleMediaUsageRepair: () => handleMediaUsageRepair,
1573
+ handleMediaUsageSummaries: () => handleMediaUsageSummaries,
1574
+ toMediaUsageRepairResponse: () => toMediaUsageRepairResponse
1575
+ });
1576
+ async function handleMediaUsageProgress(db) {
1577
+ try {
1578
+ const progress = await new MediaUsageRepository(db).findCollectionProgress();
1579
+ if (!progress) return {
1580
+ success: false,
1581
+ error: {
1582
+ code: ErrorCode.MEDIA_USAGE_PROGRESS_NOT_ACTIVE,
1583
+ message: "Media Usage is not active"
1584
+ }
1585
+ };
1586
+ return {
1587
+ success: true,
1588
+ data: progress
1589
+ };
1590
+ } catch (error) {
1591
+ if (error instanceof MediaUsageActivationVersionMismatchError) return {
1592
+ success: false,
1593
+ error: {
1594
+ code: ErrorCode.MEDIA_USAGE_ACTIVATION_VERSION_MISMATCH,
1595
+ message: "Media Usage activation version does not match this runtime"
1596
+ }
1597
+ };
1598
+ console.error("[media-usage] progress read failed:", error);
1599
+ return {
1600
+ success: false,
1601
+ error: {
1602
+ code: ErrorCode.MEDIA_USAGE_PROGRESS_READ_ERROR,
1603
+ message: "Failed to read media usage progress"
1604
+ }
1605
+ };
1606
+ }
1607
+ }
1608
+ async function handleMediaUsageProgressAdvance(db) {
1609
+ try {
1610
+ const step = await runMediaUsageMaintenanceStep(db);
1611
+ const activation = await getMediaUsageActivationStatus(db);
1612
+ if (activation.state === "expanded") return {
1613
+ success: false,
1614
+ error: {
1615
+ code: ErrorCode.MEDIA_USAGE_PROGRESS_NOT_ACTIVE,
1616
+ message: "Media Usage activation has not started"
1617
+ }
1618
+ };
1619
+ const progress = activation.state === "active" ? await new MediaUsageRepository(db).findCollectionProgress() : null;
1620
+ return {
1621
+ success: true,
1622
+ data: {
1623
+ activation,
1624
+ progress,
1625
+ nextRequestInMs: continuationDelayMs(step.continuation, progress)
1626
+ }
1627
+ };
1628
+ } catch (error) {
1629
+ if (error instanceof MediaUsageActivationVersionMismatchError) return {
1630
+ success: false,
1631
+ error: {
1632
+ code: ErrorCode.MEDIA_USAGE_ACTIVATION_VERSION_MISMATCH,
1633
+ message: "Media Usage activation version does not match this runtime"
1634
+ }
1635
+ };
1636
+ console.error("[media-usage] progress advance failed:", error);
1637
+ return {
1638
+ success: false,
1639
+ error: {
1640
+ code: ErrorCode.MEDIA_USAGE_PROGRESS_ADVANCE_ERROR,
1641
+ message: "Failed to advance media usage progress"
1642
+ }
1643
+ };
1644
+ }
1645
+ }
1646
+ function continuationDelayMs(continuation, progress) {
1647
+ if (progress?.status === "needs_attention") return null;
1648
+ if (continuation.kind === "immediate") return 0;
1649
+ if (continuation.kind === "delayed") return 3e4;
1650
+ if (progress?.status === "indexing") return 0;
1651
+ return null;
1652
+ }
1653
+ function aggregateMediaUsageCoverageStatus(scopes) {
1654
+ const statuses = scopes.map(normalizeMediaUsageCoverageStatus);
1655
+ if (statuses.every((status) => status === "complete")) return "complete";
1656
+ if (statuses.includes("unknown")) return "unknown";
1657
+ if (statuses.includes("running")) return "running";
1658
+ if (statuses.includes("stale")) return "stale";
1659
+ if (statuses.includes("partial")) return "partial";
1660
+ if (statuses.every((status) => status === "never")) return "never";
1661
+ if (statuses.every((status) => status === "failed")) return "failed";
1662
+ return "partial";
1663
+ }
1664
+ async function handleMediaUsageSummaries(db, mediaIds, options) {
1665
+ if (mediaIds.length === 0) return {
1666
+ success: true,
1667
+ data: {}
1668
+ };
1669
+ try {
1670
+ const repository = new MediaUsageRepository(db);
1671
+ const coverage = await loadMediaUsageCoverage(repository);
1672
+ const counts = options.includeCount && coverage.status === "complete" ? await repository.findActiveEntryCountsByMediaIds(mediaIds) : null;
1673
+ const summaries = {};
1674
+ for (const mediaId of new Set(mediaIds)) summaries[mediaId] = {
1675
+ count: counts ? counts.get(mediaId) ?? 0 : null,
1676
+ coverage
1677
+ };
1678
+ return {
1679
+ success: true,
1680
+ data: summaries
1681
+ };
1682
+ } catch (error) {
1683
+ console.error("[media-usage] summary read failed:", error);
1684
+ return {
1685
+ success: false,
1686
+ error: {
1687
+ code: ErrorCode.MEDIA_USAGE_READ_ERROR,
1688
+ message: "Failed to read media usage"
1689
+ }
1690
+ };
1691
+ }
1692
+ }
1693
+ async function handleMediaUsageDetails(db, mediaId, options) {
1694
+ try {
1695
+ if (!await new MediaRepository(db).findById(mediaId)) return {
1696
+ success: false,
1697
+ error: {
1698
+ code: ErrorCode.NOT_FOUND,
1699
+ message: `Media item not found: ${mediaId}`
1700
+ }
1701
+ };
1702
+ const repository = new MediaUsageRepository(db);
1703
+ const coverage = await loadMediaUsageCoverage(repository);
1704
+ const page = await repository.findCurrentEntryUsagePageByMediaId(mediaId, options);
1705
+ return {
1706
+ success: true,
1707
+ data: {
1708
+ items: page.items.map(toMediaUsageEntryDetail),
1709
+ ...page.nextCursor ? { nextCursor: page.nextCursor } : {},
1710
+ coverage
1711
+ }
1712
+ };
1713
+ } catch (error) {
1714
+ if (error instanceof InvalidCursorError) return {
1715
+ success: false,
1716
+ error: {
1717
+ code: ErrorCode.INVALID_CURSOR,
1718
+ message: error.message
1719
+ }
1720
+ };
1721
+ console.error("[media-usage] detail read failed:", error);
1722
+ return {
1723
+ success: false,
1724
+ error: {
1725
+ code: ErrorCode.MEDIA_USAGE_READ_ERROR,
1726
+ message: "Failed to read media usage"
1727
+ }
1728
+ };
1729
+ }
1730
+ }
1731
+ async function handleMediaUsageRepair(db, input) {
1732
+ try {
1733
+ let result;
1734
+ if (input.scope === "collection") result = await repairContentMediaUsageCollection(db, { collectionSlug: input.collection });
1735
+ else if (input.scope === "all") result = await repairContentMediaUsageAll(db);
1736
+ else return {
1737
+ success: false,
1738
+ error: {
1739
+ code: ErrorCode.VALIDATION_ERROR,
1740
+ message: "Invalid media usage repair request"
1741
+ }
1742
+ };
1743
+ return {
1744
+ success: true,
1745
+ data: toMediaUsageRepairResponse(result)
1746
+ };
1747
+ } catch (error) {
1748
+ console.error("[media-usage] repair failed:", error);
1749
+ return {
1750
+ success: false,
1751
+ error: {
1752
+ code: ErrorCode.MEDIA_USAGE_REPAIR_ERROR,
1753
+ message: "Failed to repair media usage"
1754
+ }
1755
+ };
1756
+ }
1757
+ }
1758
+ function toMediaUsageRepairResponse(result) {
1759
+ const collections = "collections" in result ? result.collections : [result];
1760
+ return {
1761
+ status: result.status,
1762
+ indexedSourceCount: result.indexedSourceCount,
1763
+ failedSourceCount: result.failedSourceCount,
1764
+ skippedSourceCount: result.skippedSourceCount,
1765
+ deletedSourceCount: result.deletedSourceCount,
1766
+ collections: collections.map(toMediaUsageRepairCollectionSummary)
1767
+ };
1768
+ }
1769
+ function toMediaUsageRepairCollectionSummary(result) {
1770
+ return {
1771
+ collection: result.scope.scopeKey,
1772
+ status: result.status,
1773
+ indexedSourceCount: result.indexedSourceCount,
1774
+ failedSourceCount: result.failedSourceCount,
1775
+ skippedSourceCount: result.skippedSourceCount,
1776
+ deletedSourceCount: result.deletedSourceCount,
1777
+ lastErrorCode: result.lastErrorCode,
1778
+ startedAt: result.startedAt,
1779
+ completedAt: result.completedAt
1780
+ };
1781
+ }
1782
+ function normalizeMediaUsageCoverageStatus(scope) {
1783
+ if (scope.status === null) return "never";
1784
+ if (scope.status === "complete") {
1785
+ if (scope.reconciliationRequired) return "stale";
1786
+ return scope.schemaVersion === CONTENT_SOURCE_SCHEMA_VERSION ? "complete" : "stale";
1787
+ }
1788
+ if (scope.status === "never" || scope.status === "running" || scope.status === "partial" || scope.status === "failed" || scope.status === "stale") return scope.status;
1789
+ return "unknown";
1790
+ }
1791
+ async function loadMediaUsageCoverage(repository) {
1792
+ return {
1793
+ scope: "all_content_collections",
1794
+ status: aggregateMediaUsageCoverageStatus(await repository.findCollectionIndexStatusScopes({
1795
+ adapterId: CONTENT_MEDIA_USAGE_ADAPTER_ID,
1796
+ scopeType: CONTENT_MEDIA_USAGE_COLLECTION_SCOPE
1797
+ }))
1798
+ };
1799
+ }
1800
+ function toMediaUsageEntryDetail(group) {
1801
+ const preferred = group.sources.find(({ source }) => source.sourceVariant === "draft_overlay") ?? group.sources.find(({ source }) => source.sourceVariant === "columns");
1802
+ if (!preferred) throw new Error("Media usage entry has no supported source");
1803
+ return {
1804
+ collection: group.collectionSlug,
1805
+ contentId: group.contentId,
1806
+ title: preferred.source.contentTitle,
1807
+ slug: preferred.source.contentSlug,
1808
+ locale: preferred.source.locale,
1809
+ status: preferred.source.contentStatus,
1810
+ scheduledAt: preferred.source.contentScheduledAt,
1811
+ deletedAt: group.contentDeletedAt,
1812
+ sources: group.sources.flatMap(({ source, occurrences }) => {
1813
+ if (source.sourceVariant !== "columns" && source.sourceVariant !== "draft_overlay") return [];
1814
+ return [{
1815
+ variant: source.sourceVariant,
1816
+ occurrences: occurrences.map((occurrence) => ({
1817
+ fieldSlug: occurrence.fieldSlug,
1818
+ fieldPath: occurrence.fieldPath,
1819
+ occurrenceIndex: occurrence.occurrenceIndex,
1820
+ referenceType: normalizeMediaUsageReferenceType(occurrence.referenceType)
1821
+ }))
1822
+ }];
1823
+ })
1824
+ };
1825
+ }
1826
+ function normalizeMediaUsageReferenceType(referenceType) {
1827
+ if (referenceType === "image_field" || referenceType === "file_field" || referenceType === "portable_text_image") return referenceType;
1828
+ return "unknown";
1829
+ }
1830
+
1831
+ //#endregion
1832
+ export { handleMediaUsageSummaries as a, handleMediaUsageRepair as i, handleMediaUsageProgress as n, media_usage_exports as o, handleMediaUsageProgressAdvance as r, processMediaUsageWorkAfterWrite as s, handleMediaUsageDetails as t };
1833
+ //# sourceMappingURL=media-usage-BTRJvS6V.mjs.map