emdash 0.39.0 → 0.40.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 (1650) hide show
  1. package/dist/action-token-DYwe4Pk0.mjs +26 -0
  2. package/dist/action-token-DYwe4Pk0.mjs.map +1 -0
  3. package/dist/activation-LOkDM5WT.mjs +382 -0
  4. package/dist/activation-LOkDM5WT.mjs.map +1 -0
  5. package/dist/adapters-B64LbYcB.d.mts +171 -0
  6. package/dist/adapters-B64LbYcB.d.mts.map +1 -0
  7. package/dist/allowed-origins-CHB8oZNM.mjs +116 -0
  8. package/dist/allowed-origins-CHB8oZNM.mjs.map +1 -0
  9. package/dist/api/route-utils.d.mts +4 -4
  10. package/dist/api/route-utils.mjs +49 -40
  11. package/dist/api/route-utils.mjs.map +1 -1
  12. package/dist/api/schemas/index.d.mts +2 -2
  13. package/dist/api/schemas/index.mjs +11 -7
  14. package/dist/api-cFFZpyS0.mjs +4718 -0
  15. package/dist/api-cFFZpyS0.mjs.map +1 -0
  16. package/dist/api-tokens-CE1ECIg0.mjs +3 -0
  17. package/dist/api-tokens-s4i4aUp_.mjs +166 -0
  18. package/dist/api-tokens-s4i4aUp_.mjs.map +1 -0
  19. package/dist/apply-CoT603H3.mjs +1163 -0
  20. package/dist/apply-CoT603H3.mjs.map +1 -0
  21. package/dist/approvals-CqMfNxak.mjs +460 -0
  22. package/dist/approvals-CqMfNxak.mjs.map +1 -0
  23. package/dist/artifact-fetch-DZMOGZ3-.mjs +818 -0
  24. package/dist/artifact-fetch-DZMOGZ3-.mjs.map +1 -0
  25. package/dist/astro/index.d.mts +14 -14
  26. package/dist/astro/index.mjs +62 -12
  27. package/dist/astro/index.mjs.map +1 -1
  28. package/dist/astro/middleware/auth.d.mts +14 -12
  29. package/dist/astro/middleware/auth.d.mts.map +1 -1
  30. package/dist/astro/middleware/auth.mjs +26 -11
  31. package/dist/astro/middleware/auth.mjs.map +1 -1
  32. package/dist/astro/middleware/media-usage-write-fence.d.mts +9 -1
  33. package/dist/astro/middleware/media-usage-write-fence.d.mts.map +1 -1
  34. package/dist/astro/middleware/media-usage-write-fence.mjs +95 -13
  35. package/dist/astro/middleware/media-usage-write-fence.mjs.map +1 -1
  36. package/dist/astro/middleware/redirect.mjs +8 -8
  37. package/dist/astro/middleware/request-context.d.mts.map +1 -1
  38. package/dist/astro/middleware/request-context.mjs +145 -47
  39. package/dist/astro/middleware/request-context.mjs.map +1 -1
  40. package/dist/astro/middleware/setup.mjs +1 -1
  41. package/dist/astro/middleware.d.mts +12 -12
  42. package/dist/astro/middleware.d.mts.map +1 -1
  43. package/dist/astro/middleware.mjs +129 -122
  44. package/dist/astro/middleware.mjs.map +1 -1
  45. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +14 -8
  46. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs.map +1 -1
  47. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +14 -8
  48. package/dist/astro/routes/api/admin/allowed-domains/index.mjs.map +1 -1
  49. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +5 -3
  50. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs.map +1 -1
  51. package/dist/astro/routes/api/admin/api-tokens/index.mjs +6 -4
  52. package/dist/astro/routes/api/admin/api-tokens/index.mjs.map +1 -1
  53. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +6 -4
  54. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs.map +1 -1
  55. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +17 -11
  56. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs.map +1 -1
  57. package/dist/astro/routes/api/admin/byline-fields/index.mjs +17 -11
  58. package/dist/astro/routes/api/admin/byline-fields/index.mjs.map +1 -1
  59. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +17 -11
  60. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs.map +1 -1
  61. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +25 -19
  62. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs.map +1 -1
  63. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +25 -19
  64. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs.map +1 -1
  65. package/dist/astro/routes/api/admin/bylines/index.mjs +25 -19
  66. package/dist/astro/routes/api/admin/bylines/index.mjs.map +1 -1
  67. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +19 -13
  68. package/dist/astro/routes/api/admin/comments/_id_/status.mjs.map +1 -1
  69. package/dist/astro/routes/api/admin/comments/_id_.mjs +7 -5
  70. package/dist/astro/routes/api/admin/comments/_id_.mjs.map +1 -1
  71. package/dist/astro/routes/api/admin/comments/bulk.mjs +18 -12
  72. package/dist/astro/routes/api/admin/comments/bulk.mjs.map +1 -1
  73. package/dist/astro/routes/api/admin/comments/counts.mjs +7 -5
  74. package/dist/astro/routes/api/admin/comments/counts.mjs.map +1 -1
  75. package/dist/astro/routes/api/admin/comments/index.mjs +18 -12
  76. package/dist/astro/routes/api/admin/comments/index.mjs.map +1 -1
  77. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +6 -4
  78. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs.map +1 -1
  79. package/dist/astro/routes/api/admin/hooks/exclusive/index.d.mts.map +1 -1
  80. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +5 -6
  81. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs.map +1 -1
  82. package/dist/astro/routes/api/admin/media-usage/activation.mjs +21 -15
  83. package/dist/astro/routes/api/admin/media-usage/activation.mjs.map +1 -1
  84. package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs +23 -17
  85. package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs.map +1 -1
  86. package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs +23 -17
  87. package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs.map +1 -1
  88. package/dist/astro/routes/api/admin/media-usage/progress.mjs +26 -15
  89. package/dist/astro/routes/api/admin/media-usage/progress.mjs.map +1 -1
  90. package/dist/astro/routes/api/admin/media-usage/repair.mjs +32 -19
  91. package/dist/astro/routes/api/admin/media-usage/repair.mjs.map +1 -1
  92. package/dist/astro/routes/api/admin/media-usage/work/index.mjs +23 -17
  93. package/dist/astro/routes/api/admin/media-usage/work/index.mjs.map +1 -1
  94. package/dist/astro/routes/api/admin/media-usage/work/retry.mjs +23 -17
  95. package/dist/astro/routes/api/admin/media-usage/work/retry.mjs.map +1 -1
  96. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +5 -3
  97. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs.map +1 -1
  98. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +5 -3
  99. package/dist/astro/routes/api/admin/oauth-clients/index.mjs.map +1 -1
  100. package/dist/astro/routes/api/admin/plugins/_id_/disable.d.mts.map +1 -1
  101. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +10 -8
  102. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
  103. package/dist/astro/routes/api/admin/plugins/_id_/enable.d.mts.map +1 -1
  104. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +10 -8
  105. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
  106. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +72 -61
  107. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
  108. package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +6 -4
  109. package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs.map +1 -1
  110. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +71 -60
  111. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs.map +1 -1
  112. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.d.mts.map +1 -1
  113. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +74 -63
  114. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
  115. package/dist/astro/routes/api/admin/plugins/_id_/update.d.mts.map +1 -1
  116. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +84 -73
  117. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
  118. package/dist/astro/routes/api/admin/plugins/index.mjs +72 -61
  119. package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
  120. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +4 -2
  121. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs.map +1 -1
  122. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +71 -60
  123. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
  124. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.d.mts.map +1 -1
  125. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +83 -72
  126. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
  127. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +71 -60
  128. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
  129. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.d.mts.map +1 -1
  130. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +74 -63
  131. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
  132. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +85 -74
  133. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
  134. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +8 -6
  135. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
  136. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +76 -65
  137. package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
  138. package/dist/astro/routes/api/admin/plugins/registry/publisher-handle.d.mts +8 -0
  139. package/dist/astro/routes/api/admin/plugins/registry/publisher-handle.d.mts.map +1 -0
  140. package/dist/astro/routes/api/admin/plugins/registry/publisher-handle.mjs +221 -0
  141. package/dist/astro/routes/api/admin/plugins/registry/publisher-handle.mjs.map +1 -0
  142. package/dist/astro/routes/api/admin/plugins/registry/verify.mjs +72 -61
  143. package/dist/astro/routes/api/admin/plugins/registry/verify.mjs.map +1 -1
  144. package/dist/astro/routes/api/admin/plugins/updates.mjs +71 -60
  145. package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
  146. package/dist/astro/routes/api/admin/scheduled-policy-rejections/_collection_/_id_.mjs +6 -4
  147. package/dist/astro/routes/api/admin/scheduled-policy-rejections/_collection_/_id_.mjs.map +1 -1
  148. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +71 -60
  149. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
  150. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +4 -2
  151. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs.map +1 -1
  152. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +71 -60
  153. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
  154. package/dist/astro/routes/api/admin/transfer/_...path_.d.mts +12 -0
  155. package/dist/astro/routes/api/admin/transfer/_...path_.d.mts.map +1 -0
  156. package/dist/astro/routes/api/admin/transfer/_...path_.mjs +144 -0
  157. package/dist/astro/routes/api/admin/transfer/_...path_.mjs.map +1 -0
  158. package/dist/astro/routes/api/admin/transfer/approvals/_id_/approve.d.mts +8 -0
  159. package/dist/astro/routes/api/admin/transfer/approvals/_id_/approve.d.mts.map +1 -0
  160. package/dist/astro/routes/api/admin/transfer/approvals/_id_/approve.mjs +68 -0
  161. package/dist/astro/routes/api/admin/transfer/approvals/_id_/approve.mjs.map +1 -0
  162. package/dist/astro/routes/api/admin/transfer/approvals/_id_/deny.d.mts +8 -0
  163. package/dist/astro/routes/api/admin/transfer/approvals/_id_/deny.d.mts.map +1 -0
  164. package/dist/astro/routes/api/admin/transfer/approvals/_id_/deny.mjs +68 -0
  165. package/dist/astro/routes/api/admin/transfer/approvals/_id_/deny.mjs.map +1 -0
  166. package/dist/astro/routes/api/admin/transfer/approvals/index.d.mts +8 -0
  167. package/dist/astro/routes/api/admin/transfer/approvals/index.d.mts.map +1 -0
  168. package/dist/astro/routes/api/admin/transfer/approvals/index.mjs +67 -0
  169. package/dist/astro/routes/api/admin/transfer/approvals/index.mjs.map +1 -0
  170. package/dist/astro/routes/api/admin/transfer/capabilities.d.mts +8 -0
  171. package/dist/astro/routes/api/admin/transfer/capabilities.d.mts.map +1 -0
  172. package/dist/astro/routes/api/admin/transfer/capabilities.mjs +70 -0
  173. package/dist/astro/routes/api/admin/transfer/capabilities.mjs.map +1 -0
  174. package/dist/astro/routes/api/admin/transfer/exports/_id_/advance.d.mts +8 -0
  175. package/dist/astro/routes/api/admin/transfer/exports/_id_/advance.d.mts.map +1 -0
  176. package/dist/astro/routes/api/admin/transfer/exports/_id_/advance.mjs +67 -0
  177. package/dist/astro/routes/api/admin/transfer/exports/_id_/advance.mjs.map +1 -0
  178. package/dist/astro/routes/api/admin/transfer/exports/_id_/archive.d.mts +8 -0
  179. package/dist/astro/routes/api/admin/transfer/exports/_id_/archive.d.mts.map +1 -0
  180. package/dist/astro/routes/api/admin/transfer/exports/_id_/archive.mjs +70 -0
  181. package/dist/astro/routes/api/admin/transfer/exports/_id_/archive.mjs.map +1 -0
  182. package/dist/astro/routes/api/admin/transfer/exports/_id_/files/_...path_.d.mts +8 -0
  183. package/dist/astro/routes/api/admin/transfer/exports/_id_/files/_...path_.d.mts.map +1 -0
  184. package/dist/astro/routes/api/admin/transfer/exports/_id_/files/_...path_.mjs +69 -0
  185. package/dist/astro/routes/api/admin/transfer/exports/_id_/files/_...path_.mjs.map +1 -0
  186. package/dist/astro/routes/api/admin/transfer/exports/_id_/index.d.mts +8 -0
  187. package/dist/astro/routes/api/admin/transfer/exports/_id_/index.d.mts.map +1 -0
  188. package/dist/astro/routes/api/admin/transfer/exports/_id_/index.mjs +66 -0
  189. package/dist/astro/routes/api/admin/transfer/exports/_id_/index.mjs.map +1 -0
  190. package/dist/astro/routes/api/admin/transfer/exports/_id_/manifest.d.mts +8 -0
  191. package/dist/astro/routes/api/admin/transfer/exports/_id_/manifest.d.mts.map +1 -0
  192. package/dist/astro/routes/api/admin/transfer/exports/_id_/manifest.mjs +69 -0
  193. package/dist/astro/routes/api/admin/transfer/exports/_id_/manifest.mjs.map +1 -0
  194. package/dist/astro/routes/api/admin/transfer/exports/index.d.mts +9 -0
  195. package/dist/astro/routes/api/admin/transfer/exports/index.d.mts.map +1 -0
  196. package/dist/astro/routes/api/admin/transfer/exports/index.mjs +91 -0
  197. package/dist/astro/routes/api/admin/transfer/exports/index.mjs.map +1 -0
  198. package/dist/astro/routes/api/admin/transfer/imports/_id_/abandon.d.mts +8 -0
  199. package/dist/astro/routes/api/admin/transfer/imports/_id_/abandon.d.mts.map +1 -0
  200. package/dist/astro/routes/api/admin/transfer/imports/_id_/abandon.mjs +66 -0
  201. package/dist/astro/routes/api/admin/transfer/imports/_id_/abandon.mjs.map +1 -0
  202. package/dist/astro/routes/api/admin/transfer/imports/_id_/advance.d.mts +8 -0
  203. package/dist/astro/routes/api/admin/transfer/imports/_id_/advance.d.mts.map +1 -0
  204. package/dist/astro/routes/api/admin/transfer/imports/_id_/advance.mjs +70 -0
  205. package/dist/astro/routes/api/admin/transfer/imports/_id_/advance.mjs.map +1 -0
  206. package/dist/astro/routes/api/admin/transfer/imports/_id_/analyze.d.mts +8 -0
  207. package/dist/astro/routes/api/admin/transfer/imports/_id_/analyze.d.mts.map +1 -0
  208. package/dist/astro/routes/api/admin/transfer/imports/_id_/analyze.mjs +78 -0
  209. package/dist/astro/routes/api/admin/transfer/imports/_id_/analyze.mjs.map +1 -0
  210. package/dist/astro/routes/api/admin/transfer/imports/_id_/cancel.d.mts +8 -0
  211. package/dist/astro/routes/api/admin/transfer/imports/_id_/cancel.d.mts.map +1 -0
  212. package/dist/astro/routes/api/admin/transfer/imports/_id_/cancel.mjs +66 -0
  213. package/dist/astro/routes/api/admin/transfer/imports/_id_/cancel.mjs.map +1 -0
  214. package/dist/astro/routes/api/admin/transfer/imports/_id_/execute.d.mts +8 -0
  215. package/dist/astro/routes/api/admin/transfer/imports/_id_/execute.d.mts.map +1 -0
  216. package/dist/astro/routes/api/admin/transfer/imports/_id_/execute.mjs +77 -0
  217. package/dist/astro/routes/api/admin/transfer/imports/_id_/execute.mjs.map +1 -0
  218. package/dist/astro/routes/api/admin/transfer/imports/_id_/files/_...path_.d.mts +8 -0
  219. package/dist/astro/routes/api/admin/transfer/imports/_id_/files/_...path_.d.mts.map +1 -0
  220. package/dist/astro/routes/api/admin/transfer/imports/_id_/files/_...path_.mjs +77 -0
  221. package/dist/astro/routes/api/admin/transfer/imports/_id_/files/_...path_.mjs.map +1 -0
  222. package/dist/astro/routes/api/admin/transfer/imports/_id_/index.d.mts +8 -0
  223. package/dist/astro/routes/api/admin/transfer/imports/_id_/index.d.mts.map +1 -0
  224. package/dist/astro/routes/api/admin/transfer/imports/_id_/index.mjs +66 -0
  225. package/dist/astro/routes/api/admin/transfer/imports/_id_/index.mjs.map +1 -0
  226. package/dist/astro/routes/api/admin/transfer/imports/_id_/missing.d.mts +8 -0
  227. package/dist/astro/routes/api/admin/transfer/imports/_id_/missing.d.mts.map +1 -0
  228. package/dist/astro/routes/api/admin/transfer/imports/_id_/missing.mjs +70 -0
  229. package/dist/astro/routes/api/admin/transfer/imports/_id_/missing.mjs.map +1 -0
  230. package/dist/astro/routes/api/admin/transfer/imports/_id_/plan.d.mts +8 -0
  231. package/dist/astro/routes/api/admin/transfer/imports/_id_/plan.d.mts.map +1 -0
  232. package/dist/astro/routes/api/admin/transfer/imports/_id_/plan.mjs +67 -0
  233. package/dist/astro/routes/api/admin/transfer/imports/_id_/plan.mjs.map +1 -0
  234. package/dist/astro/routes/api/admin/transfer/imports/_id_/receipt.d.mts +8 -0
  235. package/dist/astro/routes/api/admin/transfer/imports/_id_/receipt.d.mts.map +1 -0
  236. package/dist/astro/routes/api/admin/transfer/imports/_id_/receipt.mjs +66 -0
  237. package/dist/astro/routes/api/admin/transfer/imports/_id_/receipt.mjs.map +1 -0
  238. package/dist/astro/routes/api/admin/transfer/imports/index.d.mts +9 -0
  239. package/dist/astro/routes/api/admin/transfer/imports/index.d.mts.map +1 -0
  240. package/dist/astro/routes/api/admin/transfer/imports/index.mjs +99 -0
  241. package/dist/astro/routes/api/admin/transfer/imports/index.mjs.map +1 -0
  242. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +3 -1
  243. package/dist/astro/routes/api/admin/users/_id_/disable.mjs.map +1 -1
  244. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +3 -1
  245. package/dist/astro/routes/api/admin/users/_id_/enable.mjs.map +1 -1
  246. package/dist/astro/routes/api/admin/users/_id_/index.mjs +14 -8
  247. package/dist/astro/routes/api/admin/users/_id_/index.mjs.map +1 -1
  248. package/dist/astro/routes/api/admin/users/_id_/send-recovery.d.mts.map +1 -1
  249. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +16 -6
  250. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs.map +1 -1
  251. package/dist/astro/routes/api/admin/users/index.mjs +14 -8
  252. package/dist/astro/routes/api/admin/users/index.mjs.map +1 -1
  253. package/dist/astro/routes/api/auth/dev-bypass.mjs +5 -3
  254. package/dist/astro/routes/api/auth/dev-bypass.mjs.map +1 -1
  255. package/dist/astro/routes/api/auth/invite/accept.mjs +3 -1
  256. package/dist/astro/routes/api/auth/invite/accept.mjs.map +1 -1
  257. package/dist/astro/routes/api/auth/invite/complete.mjs +19 -13
  258. package/dist/astro/routes/api/auth/invite/complete.mjs.map +1 -1
  259. package/dist/astro/routes/api/auth/invite/index.d.mts.map +1 -1
  260. package/dist/astro/routes/api/auth/invite/index.mjs +24 -12
  261. package/dist/astro/routes/api/auth/invite/index.mjs.map +1 -1
  262. package/dist/astro/routes/api/auth/invite/register-options.mjs +18 -12
  263. package/dist/astro/routes/api/auth/invite/register-options.mjs.map +1 -1
  264. package/dist/astro/routes/api/auth/logout.mjs +4 -2
  265. package/dist/astro/routes/api/auth/logout.mjs.map +1 -1
  266. package/dist/astro/routes/api/auth/magic-link/send.d.mts.map +1 -1
  267. package/dist/astro/routes/api/auth/magic-link/send.mjs +29 -15
  268. package/dist/astro/routes/api/auth/magic-link/send.mjs.map +1 -1
  269. package/dist/astro/routes/api/auth/magic-link/verify.d.mts +2 -1
  270. package/dist/astro/routes/api/auth/magic-link/verify.d.mts.map +1 -1
  271. package/dist/astro/routes/api/auth/magic-link/verify.mjs +37 -17
  272. package/dist/astro/routes/api/auth/magic-link/verify.mjs.map +1 -1
  273. package/dist/astro/routes/api/auth/me.mjs +15 -9
  274. package/dist/astro/routes/api/auth/me.mjs.map +1 -1
  275. package/dist/astro/routes/api/auth/mode.mjs +4 -2
  276. package/dist/astro/routes/api/auth/mode.mjs.map +1 -1
  277. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +4 -4
  278. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
  279. package/dist/astro/routes/api/auth/passkey/_id_.mjs +14 -8
  280. package/dist/astro/routes/api/auth/passkey/_id_.mjs.map +1 -1
  281. package/dist/astro/routes/api/auth/passkey/index.mjs +3 -1
  282. package/dist/astro/routes/api/auth/passkey/index.mjs.map +1 -1
  283. package/dist/astro/routes/api/auth/passkey/options.d.mts.map +1 -1
  284. package/dist/astro/routes/api/auth/passkey/options.mjs +21 -24
  285. package/dist/astro/routes/api/auth/passkey/options.mjs.map +1 -1
  286. package/dist/astro/routes/api/auth/passkey/register/options.mjs +18 -12
  287. package/dist/astro/routes/api/auth/passkey/register/options.mjs.map +1 -1
  288. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +19 -13
  289. package/dist/astro/routes/api/auth/passkey/register/verify.mjs.map +1 -1
  290. package/dist/astro/routes/api/auth/passkey/verify.mjs +19 -13
  291. package/dist/astro/routes/api/auth/passkey/verify.mjs.map +1 -1
  292. package/dist/astro/routes/api/auth/signup/complete.mjs +19 -13
  293. package/dist/astro/routes/api/auth/signup/complete.mjs.map +1 -1
  294. package/dist/astro/routes/api/auth/signup/request.mjs +18 -12
  295. package/dist/astro/routes/api/auth/signup/request.mjs.map +1 -1
  296. package/dist/astro/routes/api/auth/signup/verify.mjs +3 -1
  297. package/dist/astro/routes/api/auth/signup/verify.mjs.map +1 -1
  298. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +14 -11
  299. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs.map +1 -1
  300. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +21 -15
  301. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs.map +1 -1
  302. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +4 -2
  303. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs.map +1 -1
  304. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +18 -13
  305. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs.map +1 -1
  306. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +4 -3
  307. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs.map +1 -1
  308. package/dist/astro/routes/api/content/_collection_/_id_/lock.mjs +18 -13
  309. package/dist/astro/routes/api/content/_collection_/_id_/lock.mjs.map +1 -1
  310. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +4 -2
  311. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs.map +1 -1
  312. package/dist/astro/routes/api/content/_collection_/_id_/plugin-extensions/_pluginId_/_kind_/_extensionId_.mjs +10 -8
  313. package/dist/astro/routes/api/content/_collection_/_id_/plugin-extensions/_pluginId_/_kind_/_extensionId_.mjs.map +1 -1
  314. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.d.mts.map +1 -1
  315. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +68 -16
  316. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs.map +1 -1
  317. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +18 -13
  318. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
  319. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +31 -22
  320. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs.map +1 -1
  321. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs +31 -22
  322. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs.map +1 -1
  323. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +4 -3
  324. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs.map +1 -1
  325. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +4 -2
  326. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs.map +1 -1
  327. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +18 -13
  328. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs.map +1 -1
  329. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.d.mts.map +1 -1
  330. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +38 -19
  331. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs.map +1 -1
  332. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +4 -2
  333. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs.map +1 -1
  334. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +18 -13
  335. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs.map +1 -1
  336. package/dist/astro/routes/api/content/_collection_/_id_.mjs +18 -13
  337. package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
  338. package/dist/astro/routes/api/content/_collection_/authors.mjs +4 -2
  339. package/dist/astro/routes/api/content/_collection_/authors.mjs.map +1 -1
  340. package/dist/astro/routes/api/content/_collection_/index.mjs +15 -10
  341. package/dist/astro/routes/api/content/_collection_/index.mjs.map +1 -1
  342. package/dist/astro/routes/api/content/_collection_/trash.mjs +15 -9
  343. package/dist/astro/routes/api/content/_collection_/trash.mjs.map +1 -1
  344. package/dist/astro/routes/api/dashboard.mjs +11 -9
  345. package/dist/astro/routes/api/dashboard.mjs.map +1 -1
  346. package/dist/astro/routes/api/dev/emails.mjs +4 -2
  347. package/dist/astro/routes/api/dev/emails.mjs.map +1 -1
  348. package/dist/astro/routes/api/health.mjs +5 -3
  349. package/dist/astro/routes/api/health.mjs.map +1 -1
  350. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  351. package/dist/astro/routes/api/import/probe.mjs +27 -21
  352. package/dist/astro/routes/api/import/probe.mjs.map +1 -1
  353. package/dist/astro/routes/api/import/wordpress/analyze.mjs +5 -3
  354. package/dist/astro/routes/api/import/wordpress/analyze.mjs.map +1 -1
  355. package/dist/astro/routes/api/import/wordpress/execute.d.mts +13 -13
  356. package/dist/astro/routes/api/import/wordpress/execute.mjs +18 -16
  357. package/dist/astro/routes/api/import/wordpress/execute.mjs.map +1 -1
  358. package/dist/astro/routes/api/import/wordpress/media.d.mts +12 -12
  359. package/dist/astro/routes/api/import/wordpress/media.mjs +19 -13
  360. package/dist/astro/routes/api/import/wordpress/media.mjs.map +1 -1
  361. package/dist/astro/routes/api/import/wordpress/prepare.mjs +18 -12
  362. package/dist/astro/routes/api/import/wordpress/prepare.mjs.map +1 -1
  363. package/dist/astro/routes/api/import/wordpress/rewrite-url-helpers.d.mts +5 -1
  364. package/dist/astro/routes/api/import/wordpress/rewrite-url-helpers.d.mts.map +1 -1
  365. package/dist/astro/routes/api/import/wordpress/rewrite-url-helpers.mjs +4 -2
  366. package/dist/astro/routes/api/import/wordpress/rewrite-url-helpers.mjs.map +1 -1
  367. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +13 -13
  368. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +29 -15
  369. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs.map +1 -1
  370. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  371. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +27 -21
  372. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs.map +1 -1
  373. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +13 -13
  374. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +38 -32
  375. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
  376. package/dist/astro/routes/api/manifest.mjs +6 -4
  377. package/dist/astro/routes/api/manifest.mjs.map +1 -1
  378. package/dist/astro/routes/api/mcp.d.mts.map +1 -1
  379. package/dist/astro/routes/api/mcp.mjs +717 -67
  380. package/dist/astro/routes/api/mcp.mjs.map +1 -1
  381. package/dist/astro/routes/api/media/_id_/confirm.mjs +19 -13
  382. package/dist/astro/routes/api/media/_id_/confirm.mjs.map +1 -1
  383. package/dist/astro/routes/api/media/_id_/replace.mjs +16 -10
  384. package/dist/astro/routes/api/media/_id_/replace.mjs.map +1 -1
  385. package/dist/astro/routes/api/media/_id_/upload.mjs +8 -6
  386. package/dist/astro/routes/api/media/_id_/upload.mjs.map +1 -1
  387. package/dist/astro/routes/api/media/_id_/usage.mjs +34 -21
  388. package/dist/astro/routes/api/media/_id_/usage.mjs.map +1 -1
  389. package/dist/astro/routes/api/media/_id_.d.mts.map +1 -1
  390. package/dist/astro/routes/api/media/_id_.mjs +37 -26
  391. package/dist/astro/routes/api/media/_id_.mjs.map +1 -1
  392. package/dist/astro/routes/api/media/asset/_id_/_filename_.mjs +5 -3
  393. package/dist/astro/routes/api/media/asset/_id_/_filename_.mjs.map +1 -1
  394. package/dist/astro/routes/api/media/file/_...key_.d.mts.map +1 -1
  395. package/dist/astro/routes/api/media/file/_...key_.mjs +31 -2
  396. package/dist/astro/routes/api/media/file/_...key_.mjs.map +1 -1
  397. package/dist/astro/routes/api/media/folders/_id_.mjs +16 -10
  398. package/dist/astro/routes/api/media/folders/_id_.mjs.map +1 -1
  399. package/dist/astro/routes/api/media/folders/index.mjs +16 -10
  400. package/dist/astro/routes/api/media/folders/index.mjs.map +1 -1
  401. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +4 -2
  402. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs.map +1 -1
  403. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +4 -2
  404. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs.map +1 -1
  405. package/dist/astro/routes/api/media/providers/index.mjs +4 -2
  406. package/dist/astro/routes/api/media/providers/index.mjs.map +1 -1
  407. package/dist/astro/routes/api/media/upload-url.mjs +18 -12
  408. package/dist/astro/routes/api/media/upload-url.mjs.map +1 -1
  409. package/dist/astro/routes/api/media.mjs +36 -24
  410. package/dist/astro/routes/api/media.mjs.map +1 -1
  411. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +18 -12
  412. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs.map +1 -1
  413. package/dist/astro/routes/api/menus/_name_/items.mjs +18 -12
  414. package/dist/astro/routes/api/menus/_name_/items.mjs.map +1 -1
  415. package/dist/astro/routes/api/menus/_name_/reorder.mjs +18 -12
  416. package/dist/astro/routes/api/menus/_name_/reorder.mjs.map +1 -1
  417. package/dist/astro/routes/api/menus/_name_/translations.mjs +18 -12
  418. package/dist/astro/routes/api/menus/_name_/translations.mjs.map +1 -1
  419. package/dist/astro/routes/api/menus/_name_.mjs +18 -12
  420. package/dist/astro/routes/api/menus/_name_.mjs.map +1 -1
  421. package/dist/astro/routes/api/menus/index.mjs +18 -12
  422. package/dist/astro/routes/api/menus/index.mjs.map +1 -1
  423. package/dist/astro/routes/api/oauth/authorize.d.mts.map +1 -1
  424. package/dist/astro/routes/api/oauth/authorize.mjs +9 -6
  425. package/dist/astro/routes/api/oauth/authorize.mjs.map +1 -1
  426. package/dist/astro/routes/api/oauth/device/authorize.mjs +7 -5
  427. package/dist/astro/routes/api/oauth/device/authorize.mjs.map +1 -1
  428. package/dist/astro/routes/api/oauth/device/code.mjs +10 -8
  429. package/dist/astro/routes/api/oauth/device/code.mjs.map +1 -1
  430. package/dist/astro/routes/api/oauth/device/token.mjs +9 -7
  431. package/dist/astro/routes/api/oauth/device/token.mjs.map +1 -1
  432. package/dist/astro/routes/api/oauth/register.mjs +4 -2
  433. package/dist/astro/routes/api/oauth/register.mjs.map +1 -1
  434. package/dist/astro/routes/api/oauth/token/refresh.mjs +7 -5
  435. package/dist/astro/routes/api/oauth/token/refresh.mjs.map +1 -1
  436. package/dist/astro/routes/api/oauth/token/revoke.mjs +7 -5
  437. package/dist/astro/routes/api/oauth/token/revoke.mjs.map +1 -1
  438. package/dist/astro/routes/api/oauth/token.mjs +7 -5
  439. package/dist/astro/routes/api/oauth/token.mjs.map +1 -1
  440. package/dist/astro/routes/api/openapi.json.mjs +497 -12
  441. package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
  442. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +10 -8
  443. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs.map +1 -1
  444. package/dist/astro/routes/api/redirects/404s/index.mjs +21 -15
  445. package/dist/astro/routes/api/redirects/404s/index.mjs.map +1 -1
  446. package/dist/astro/routes/api/redirects/404s/summary.mjs +21 -15
  447. package/dist/astro/routes/api/redirects/404s/summary.mjs.map +1 -1
  448. package/dist/astro/routes/api/redirects/_id_.mjs +21 -15
  449. package/dist/astro/routes/api/redirects/_id_.mjs.map +1 -1
  450. package/dist/astro/routes/api/redirects/index.mjs +21 -15
  451. package/dist/astro/routes/api/redirects/index.mjs.map +1 -1
  452. package/dist/astro/routes/api/relations/_id_/index.mjs +31 -22
  453. package/dist/astro/routes/api/relations/_id_/index.mjs.map +1 -1
  454. package/dist/astro/routes/api/relations/_id_/translations.mjs +21 -16
  455. package/dist/astro/routes/api/relations/_id_/translations.mjs.map +1 -1
  456. package/dist/astro/routes/api/relations/index.mjs +31 -22
  457. package/dist/astro/routes/api/relations/index.mjs.map +1 -1
  458. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +4 -2
  459. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs.map +1 -1
  460. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +4 -3
  461. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs.map +1 -1
  462. package/dist/astro/routes/api/schema/block-types/_slug_/index.d.mts +9 -0
  463. package/dist/astro/routes/api/schema/block-types/_slug_/index.d.mts.map +1 -0
  464. package/dist/astro/routes/api/schema/block-types/_slug_/index.mjs +102 -0
  465. package/dist/astro/routes/api/schema/block-types/_slug_/index.mjs.map +1 -0
  466. package/dist/astro/routes/api/schema/block-types/_slug_/versions/_version_/activate.d.mts +8 -0
  467. package/dist/astro/routes/api/schema/block-types/_slug_/versions/_version_/activate.d.mts.map +1 -0
  468. package/dist/astro/routes/api/schema/block-types/_slug_/versions/_version_/activate.mjs +96 -0
  469. package/dist/astro/routes/api/schema/block-types/_slug_/versions/_version_/activate.mjs.map +1 -0
  470. package/dist/astro/routes/api/schema/block-types/index.d.mts +9 -0
  471. package/dist/astro/routes/api/schema/block-types/index.d.mts.map +1 -0
  472. package/dist/astro/routes/api/schema/block-types/index.mjs +102 -0
  473. package/dist/astro/routes/api/schema/block-types/index.mjs.map +1 -0
  474. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +71 -60
  475. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
  476. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +71 -60
  477. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs.map +1 -1
  478. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +71 -60
  479. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
  480. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +71 -60
  481. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
  482. package/dist/astro/routes/api/schema/collections/index.mjs +71 -60
  483. package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
  484. package/dist/astro/routes/api/schema/collections/reorder.mjs +71 -60
  485. package/dist/astro/routes/api/schema/collections/reorder.mjs.map +1 -1
  486. package/dist/astro/routes/api/schema/index.mjs +17 -14
  487. package/dist/astro/routes/api/schema/index.mjs.map +1 -1
  488. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +71 -60
  489. package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
  490. package/dist/astro/routes/api/schema/orphans/index.mjs +71 -60
  491. package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
  492. package/dist/astro/routes/api/search/enable.mjs +20 -14
  493. package/dist/astro/routes/api/search/enable.mjs.map +1 -1
  494. package/dist/astro/routes/api/search/index.mjs +19 -13
  495. package/dist/astro/routes/api/search/index.mjs.map +1 -1
  496. package/dist/astro/routes/api/search/rebuild.mjs +20 -14
  497. package/dist/astro/routes/api/search/rebuild.mjs.map +1 -1
  498. package/dist/astro/routes/api/search/stats.mjs +10 -8
  499. package/dist/astro/routes/api/search/stats.mjs.map +1 -1
  500. package/dist/astro/routes/api/search/suggest.mjs +19 -13
  501. package/dist/astro/routes/api/search/suggest.mjs.map +1 -1
  502. package/dist/astro/routes/api/sections/_slug_.mjs +19 -13
  503. package/dist/astro/routes/api/sections/_slug_.mjs.map +1 -1
  504. package/dist/astro/routes/api/sections/index.mjs +19 -13
  505. package/dist/astro/routes/api/sections/index.mjs.map +1 -1
  506. package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +8 -7
  507. package/dist/astro/routes/api/settings/backups/archives/_name_.mjs.map +1 -1
  508. package/dist/astro/routes/api/settings/backups/archives/index.mjs +8 -6
  509. package/dist/astro/routes/api/settings/backups/archives/index.mjs.map +1 -1
  510. package/dist/astro/routes/api/settings/backups/export.mjs +8 -7
  511. package/dist/astro/routes/api/settings/backups/export.mjs.map +1 -1
  512. package/dist/astro/routes/api/settings/backups/index.mjs +9 -8
  513. package/dist/astro/routes/api/settings/backups/index.mjs.map +1 -1
  514. package/dist/astro/routes/api/settings/email.mjs +6 -4
  515. package/dist/astro/routes/api/settings/email.mjs.map +1 -1
  516. package/dist/astro/routes/api/settings.mjs +25 -19
  517. package/dist/astro/routes/api/settings.mjs.map +1 -1
  518. package/dist/astro/routes/api/setup/admin-verify.mjs +20 -14
  519. package/dist/astro/routes/api/setup/admin-verify.mjs.map +1 -1
  520. package/dist/astro/routes/api/setup/admin.mjs +19 -13
  521. package/dist/astro/routes/api/setup/admin.mjs.map +1 -1
  522. package/dist/astro/routes/api/setup/dev-bypass.mjs +43 -37
  523. package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
  524. package/dist/astro/routes/api/setup/dev-reset.mjs +4 -2
  525. package/dist/astro/routes/api/setup/dev-reset.mjs.map +1 -1
  526. package/dist/astro/routes/api/setup/index.mjs +48 -38
  527. package/dist/astro/routes/api/setup/index.mjs.map +1 -1
  528. package/dist/astro/routes/api/setup/status.mjs +5 -3
  529. package/dist/astro/routes/api/setup/status.mjs.map +1 -1
  530. package/dist/astro/routes/api/snapshot.mjs +10 -8
  531. package/dist/astro/routes/api/snapshot.mjs.map +1 -1
  532. package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs +25 -19
  533. package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs.map +1 -1
  534. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +25 -19
  535. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs.map +1 -1
  536. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +25 -19
  537. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs.map +1 -1
  538. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +25 -19
  539. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs.map +1 -1
  540. package/dist/astro/routes/api/taxonomies/_name_/translations.mjs +24 -18
  541. package/dist/astro/routes/api/taxonomies/_name_/translations.mjs.map +1 -1
  542. package/dist/astro/routes/api/taxonomies/_name_.mjs +25 -19
  543. package/dist/astro/routes/api/taxonomies/_name_.mjs.map +1 -1
  544. package/dist/astro/routes/api/taxonomies/bulk-tag.d.mts +8 -0
  545. package/dist/astro/routes/api/taxonomies/bulk-tag.d.mts.map +1 -0
  546. package/dist/astro/routes/api/taxonomies/bulk-tag.mjs +295 -0
  547. package/dist/astro/routes/api/taxonomies/bulk-tag.mjs.map +1 -0
  548. package/dist/astro/routes/api/taxonomies/index.mjs +25 -19
  549. package/dist/astro/routes/api/taxonomies/index.mjs.map +1 -1
  550. package/dist/astro/routes/api/themes/preview.mjs +7 -5
  551. package/dist/astro/routes/api/themes/preview.mjs.map +1 -1
  552. package/dist/astro/routes/api/typegen.d.mts.map +1 -1
  553. package/dist/astro/routes/api/typegen.mjs +12 -9
  554. package/dist/astro/routes/api/typegen.mjs.map +1 -1
  555. package/dist/astro/routes/api/visual-editing/action-token.mjs +7 -5
  556. package/dist/astro/routes/api/visual-editing/action-token.mjs.map +1 -1
  557. package/dist/astro/routes/api/visual-editing/content/_collection_/_id_/publish.mjs +22 -16
  558. package/dist/astro/routes/api/visual-editing/content/_collection_/_id_/publish.mjs.map +1 -1
  559. package/dist/astro/routes/api/visual-editing/toolbar-labels.mjs +3 -1
  560. package/dist/astro/routes/api/visual-editing/toolbar-labels.mjs.map +1 -1
  561. package/dist/astro/routes/api/well-known/auth.mjs +2 -2
  562. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
  563. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
  564. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +16 -10
  565. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs.map +1 -1
  566. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +20 -14
  567. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
  568. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +20 -14
  569. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
  570. package/dist/astro/routes/api/widget-areas/_name_.mjs +10 -8
  571. package/dist/astro/routes/api/widget-areas/_name_.mjs.map +1 -1
  572. package/dist/astro/routes/api/widget-areas/index.mjs +20 -14
  573. package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
  574. package/dist/astro/routes/api/widget-components.mjs +4 -2
  575. package/dist/astro/routes/api/widget-components.mjs.map +1 -1
  576. package/dist/astro/routes/robots.txt.mjs +10 -10
  577. package/dist/astro/routes/sitemap-_collection_.xml.mjs +13 -13
  578. package/dist/astro/routes/sitemap.xml.mjs +11 -11
  579. package/dist/astro/types.d.mts +21 -17
  580. package/dist/astro/types.d.mts.map +1 -1
  581. package/dist/audit-RfCugiSB.mjs +127 -0
  582. package/dist/audit-RfCugiSB.mjs.map +1 -0
  583. package/dist/auth/providers/github.d.mts +1 -1
  584. package/dist/auth/providers/google.d.mts +1 -1
  585. package/dist/authoritative-records-CoJvBXAD.mjs +5172 -0
  586. package/dist/authoritative-records-CoJvBXAD.mjs.map +1 -0
  587. package/dist/authorize-seCifOMJ.mjs +49 -0
  588. package/dist/authorize-seCifOMJ.mjs.map +1 -0
  589. package/dist/backup-CBu0LrXf.mjs +246 -0
  590. package/dist/backup-CBu0LrXf.mjs.map +1 -0
  591. package/dist/block-type-registry-DUI_3KxT.mjs +6709 -0
  592. package/dist/block-type-registry-DUI_3KxT.mjs.map +1 -0
  593. package/dist/block-types-BmZmLmAD.mjs +132 -0
  594. package/dist/block-types-BmZmLmAD.mjs.map +1 -0
  595. package/dist/block-values-C_VIPr5t.mjs +175 -0
  596. package/dist/block-values-C_VIPr5t.mjs.map +1 -0
  597. package/dist/byline-DYmkXqvo.mjs +909 -0
  598. package/dist/byline-DYmkXqvo.mjs.map +1 -0
  599. package/dist/byline-fields-D28AT-Fh.mjs +123 -0
  600. package/dist/byline-fields-D28AT-Fh.mjs.map +1 -0
  601. package/dist/byline-registry-gnKscbQX.mjs +406 -0
  602. package/dist/byline-registry-gnKscbQX.mjs.map +1 -0
  603. package/dist/bylines-BPwJqoYH.mjs +232 -0
  604. package/dist/bylines-BPwJqoYH.mjs.map +1 -0
  605. package/dist/bylines-ukLaYpVZ.mjs +214 -0
  606. package/dist/bylines-ukLaYpVZ.mjs.map +1 -0
  607. package/dist/cache-m7UEkRFV.mjs +108 -0
  608. package/dist/cache-m7UEkRFV.mjs.map +1 -0
  609. package/dist/canonical-YhEQezjg.mjs +174 -0
  610. package/dist/canonical-YhEQezjg.mjs.map +1 -0
  611. package/dist/capture-triggers-kJyl_yyM.mjs +362 -0
  612. package/dist/capture-triggers-kJyl_yyM.mjs.map +1 -0
  613. package/dist/challenge-store-ppU6Uv13.mjs +51 -0
  614. package/dist/challenge-store-ppU6Uv13.mjs.map +1 -0
  615. package/dist/checksum-B2IINIlN.mjs +162 -0
  616. package/dist/checksum-B2IINIlN.mjs.map +1 -0
  617. package/dist/chrome-tags-DApCQSIk.mjs +24 -0
  618. package/dist/chrome-tags-DApCQSIk.mjs.map +1 -0
  619. package/dist/chunks-Dms7Uxbt.mjs +25 -0
  620. package/dist/chunks-Dms7Uxbt.mjs.map +1 -0
  621. package/dist/cli/index.mjs +1471 -56
  622. package/dist/cli/index.mjs.map +1 -1
  623. package/dist/client/cf-access.d.mts +1 -1
  624. package/dist/client/index.d.mts +1050 -4
  625. package/dist/client/index.d.mts.map +1 -1
  626. package/dist/client/index.mjs +193 -11
  627. package/dist/client/index.mjs.map +1 -1
  628. package/dist/collection-deletion-BIm28mo3.mjs +436 -0
  629. package/dist/collection-deletion-BIm28mo3.mjs.map +1 -0
  630. package/dist/collection-deletion-processor-Br6rxs3u.mjs +217 -0
  631. package/dist/collection-deletion-processor-Br6rxs3u.mjs.map +1 -0
  632. package/dist/collection-slugs-cache-CjGk__zp.mjs +85 -0
  633. package/dist/collection-slugs-cache-CjGk__zp.mjs.map +1 -0
  634. package/dist/comment-CrBl22OF.mjs +313 -0
  635. package/dist/comment-CrBl22OF.mjs.map +1 -0
  636. package/dist/comment-reaction-JEQ2Fpd8.mjs +85 -0
  637. package/dist/comment-reaction-JEQ2Fpd8.mjs.map +1 -0
  638. package/dist/comments-2ah9du9y.mjs +195 -0
  639. package/dist/comments-2ah9du9y.mjs.map +1 -0
  640. package/dist/comments-fHZ8ON9O.mjs +99 -0
  641. package/dist/comments-fHZ8ON9O.mjs.map +1 -0
  642. package/dist/components-CXbq7CSM.mjs +108 -0
  643. package/dist/components-CXbq7CSM.mjs.map +1 -0
  644. package/dist/config-5ii2haYu.d.mts +6528 -0
  645. package/dist/config-5ii2haYu.d.mts.map +1 -0
  646. package/dist/config-CvmjJcVw.mjs +73 -0
  647. package/dist/config-CvmjJcVw.mjs.map +1 -0
  648. package/dist/config-IhybvkIQ.mjs +242 -0
  649. package/dist/config-IhybvkIQ.mjs.map +1 -0
  650. package/dist/config-Py43JK9r.d.mts +39 -0
  651. package/dist/config-Py43JK9r.d.mts.map +1 -0
  652. package/dist/content-DFaIEDj9.mjs +2281 -0
  653. package/dist/content-DFaIEDj9.mjs.map +1 -0
  654. package/dist/content-list-query-Dra4Dalc.d.mts +26 -0
  655. package/dist/content-list-query-Dra4Dalc.d.mts.map +1 -0
  656. package/dist/content-policy-DFNw91-d.mjs +46 -0
  657. package/dist/content-policy-DFNw91-d.mjs.map +1 -0
  658. package/dist/content-repair-Q8IUrpLL.mjs +442 -0
  659. package/dist/content-repair-Q8IUrpLL.mjs.map +1 -0
  660. package/dist/context-BeGccdUX.mjs +1921 -0
  661. package/dist/context-BeGccdUX.mjs.map +1 -0
  662. package/dist/cron-7DCJwPXq.mjs +284 -0
  663. package/dist/cron-7DCJwPXq.mjs.map +1 -0
  664. package/dist/dashboard-DjrB-xxZ.mjs +151 -0
  665. package/dist/dashboard-DjrB-xxZ.mjs.map +1 -0
  666. package/dist/db/index.d.mts +3 -3
  667. package/dist/db/index.mjs +3 -3
  668. package/dist/db/libsql-migrations.d.mts +1 -1
  669. package/dist/db/libsql-migrations.mjs +6 -6
  670. package/dist/db/libsql.d.mts +1 -1
  671. package/dist/db/postgres-migrations.d.mts +1 -1
  672. package/dist/db/postgres-migrations.mjs +6 -6
  673. package/dist/db/postgres.d.mts +1 -1
  674. package/dist/db/sqlite-migrations.d.mts +1 -1
  675. package/dist/db/sqlite-migrations.mjs +6 -6
  676. package/dist/db/sqlite.d.mts +1 -1
  677. package/dist/db/sqlite.mjs +2 -2
  678. package/dist/db/sqlite.mjs.map +1 -1
  679. package/dist/default-DQ6vsviu.mjs +81 -0
  680. package/dist/default-DQ6vsviu.mjs.map +1 -0
  681. package/dist/deferred-tasks-BPxXUB4x.d.mts +9 -0
  682. package/dist/deferred-tasks-BPxXUB4x.d.mts.map +1 -0
  683. package/dist/device-flow-CRd6DXmW.mjs +466 -0
  684. package/dist/device-flow-CRd6DXmW.mjs.map +1 -0
  685. package/dist/dialect-helpers-7_IqPdh9.mjs +307 -0
  686. package/dist/dialect-helpers-7_IqPdh9.mjs.map +1 -0
  687. package/dist/digest-BarSh-pN.mjs +194 -0
  688. package/dist/digest-BarSh-pN.mjs.map +1 -0
  689. package/dist/direct-executor-BmuFJJZV.mjs +104 -0
  690. package/dist/direct-executor-BmuFJJZV.mjs.map +1 -0
  691. package/dist/dist-BNwANFJR.mjs +810 -0
  692. package/dist/dist-BNwANFJR.mjs.map +1 -0
  693. package/dist/email-console-bJn3ctqA.mjs +50 -0
  694. package/dist/email-console-bJn3ctqA.mjs.map +1 -0
  695. package/dist/email-locale-CN6WAMx0.mjs +25 -0
  696. package/dist/email-locale-CN6WAMx0.mjs.map +1 -0
  697. package/dist/emdash-runtime-DAjghyEG.mjs +4186 -0
  698. package/dist/emdash-runtime-DAjghyEG.mjs.map +1 -0
  699. package/dist/enrich-Dl3kZ-lP.mjs +194 -0
  700. package/dist/enrich-Dl3kZ-lP.mjs.map +1 -0
  701. package/dist/entry-lock-BJYdBfXb.mjs +1097 -0
  702. package/dist/entry-lock-BJYdBfXb.mjs.map +1 -0
  703. package/dist/entry-lock-DHQOxN7R.mjs +163 -0
  704. package/dist/entry-lock-DHQOxN7R.mjs.map +1 -0
  705. package/dist/entry-lock-DVhCt49A.d.mts +4368 -0
  706. package/dist/entry-lock-DVhCt49A.d.mts.map +1 -0
  707. package/dist/entry-locks-D0foLdjQ.mjs +167 -0
  708. package/dist/entry-locks-D0foLdjQ.mjs.map +1 -0
  709. package/dist/error-CfvOHP0M.mjs +90 -0
  710. package/dist/error-CfvOHP0M.mjs.map +1 -0
  711. package/dist/errors-BF0_LFhW.mjs +460 -0
  712. package/dist/errors-BF0_LFhW.mjs.map +1 -0
  713. package/dist/errors-DgE1zJGG.mjs +120 -0
  714. package/dist/errors-DgE1zJGG.mjs.map +1 -0
  715. package/dist/escape-CuaZhZvL.mjs +9 -0
  716. package/dist/escape-CuaZhZvL.mjs.map +1 -0
  717. package/dist/fence-CMDXJYc_.mjs +162 -0
  718. package/dist/fence-CMDXJYc_.mjs.map +1 -0
  719. package/dist/field-defs-cache-HIgBbqZZ.mjs +77 -0
  720. package/dist/field-defs-cache-HIgBbqZZ.mjs.map +1 -0
  721. package/dist/fts-manager-S-K0laoS.mjs +443 -0
  722. package/dist/fts-manager-S-K0laoS.mjs.map +1 -0
  723. package/dist/gc-DjKY0Ay1.mjs +75 -0
  724. package/dist/gc-DjKY0Ay1.mjs.map +1 -0
  725. package/dist/hash-SDAW6zC-.mjs +37 -0
  726. package/dist/hash-SDAW6zC-.mjs.map +1 -0
  727. package/dist/hook-result-B5bXDgS2.d.mts +31 -0
  728. package/dist/hook-result-B5bXDgS2.d.mts.map +1 -0
  729. package/dist/hreflang-D5uNnsGJ.d.mts +50 -0
  730. package/dist/hreflang-D5uNnsGJ.d.mts.map +1 -0
  731. package/dist/hreflang-DMFBL2eb.mjs +105 -0
  732. package/dist/hreflang-DMFBL2eb.mjs.map +1 -0
  733. package/dist/http-route-dispatch-BitqzwMy.mjs +339 -0
  734. package/dist/http-route-dispatch-BitqzwMy.mjs.map +1 -0
  735. package/dist/identity-IyNWwXr6.mjs +35 -0
  736. package/dist/identity-IyNWwXr6.mjs.map +1 -0
  737. package/dist/import-C3JZplWq.mjs +2020 -0
  738. package/dist/import-C3JZplWq.mjs.map +1 -0
  739. package/dist/index-D-hdtLe8.d.mts +623 -0
  740. package/dist/index-D-hdtLe8.d.mts.map +1 -0
  741. package/dist/index.d.mts +24 -24
  742. package/dist/index.mjs +105 -93
  743. package/dist/install-finalization-DfM1fj5x.mjs +58 -0
  744. package/dist/install-finalization-DfM1fj5x.mjs.map +1 -0
  745. package/dist/keys-yxLm5l35.mjs +61 -0
  746. package/dist/keys-yxLm5l35.mjs.map +1 -0
  747. package/dist/kinds-CRu_T5T5.mjs +961 -0
  748. package/dist/kinds-CRu_T5T5.mjs.map +1 -0
  749. package/dist/lifecycle-BzeGPeQ-.mjs +25 -0
  750. package/dist/lifecycle-BzeGPeQ-.mjs.map +1 -0
  751. package/dist/load-XgJiE6_3.mjs +28 -0
  752. package/dist/load-XgJiE6_3.mjs.map +1 -0
  753. package/dist/loader-BzqeR1ZN.mjs +1086 -0
  754. package/dist/loader-BzqeR1ZN.mjs.map +1 -0
  755. package/dist/loops-BQSzv5Zn.mjs +203 -0
  756. package/dist/loops-BQSzv5Zn.mjs.map +1 -0
  757. package/dist/manifest-builder-B8zR-73J.mjs +189 -0
  758. package/dist/manifest-builder-B8zR-73J.mjs.map +1 -0
  759. package/dist/manifest-schema-CY3zng_M.mjs +492 -0
  760. package/dist/manifest-schema-CY3zng_M.mjs.map +1 -0
  761. package/dist/media/image-endpoint.mjs +1 -1
  762. package/dist/media/index.d.mts +1 -1
  763. package/dist/media/index.mjs +3 -3
  764. package/dist/media/local-runtime.d.mts +14 -14
  765. package/dist/media/local-runtime.d.mts.map +1 -1
  766. package/dist/media/local-runtime.mjs +15 -16
  767. package/dist/media/local-runtime.mjs.map +1 -1
  768. package/dist/media-Pu1L6xir.mjs +408 -0
  769. package/dist/media-Pu1L6xir.mjs.map +1 -0
  770. package/dist/media-Qp5S3eH3.mjs +355 -0
  771. package/dist/media-Qp5S3eH3.mjs.map +1 -0
  772. package/dist/media-allowlist-BSoa9Vvm.mjs +44 -0
  773. package/dist/media-allowlist-BSoa9Vvm.mjs.map +1 -0
  774. package/dist/media-folders-BE0oo2eX.mjs +210 -0
  775. package/dist/media-folders-BE0oo2eX.mjs.map +1 -0
  776. package/dist/media-h7tOiDLj.mjs +221 -0
  777. package/dist/media-h7tOiDLj.mjs.map +1 -0
  778. package/dist/media-url-jArBpzIG.mjs +43 -0
  779. package/dist/media-url-jArBpzIG.mjs.map +1 -0
  780. package/dist/media-usage-My-Vi-ex.mjs +1629 -0
  781. package/dist/media-usage-My-Vi-ex.mjs.map +1 -0
  782. package/dist/media-usage-activation-HKp7zk7W.mjs +78 -0
  783. package/dist/media-usage-activation-HKp7zk7W.mjs.map +1 -0
  784. package/dist/media-usage-work-C2OZLCAd.mjs +586 -0
  785. package/dist/media-usage-work-C2OZLCAd.mjs.map +1 -0
  786. package/dist/media-usage-work-DwUNbgvS.mjs +152 -0
  787. package/dist/media-usage-work-DwUNbgvS.mjs.map +1 -0
  788. package/dist/menus-BM7BPLOb.mjs +139 -0
  789. package/dist/menus-BM7BPLOb.mjs.map +1 -0
  790. package/dist/menus-ITeAbcIF.mjs +753 -0
  791. package/dist/menus-ITeAbcIF.mjs.map +1 -0
  792. package/dist/menus-TsSGmSR4.mjs +3608 -0
  793. package/dist/menus-TsSGmSR4.mjs.map +1 -0
  794. package/dist/migrations/index.d.mts +1 -1
  795. package/dist/migrations/index.mjs +5 -5
  796. package/dist/mime-ChLqYwDk.mjs +36 -0
  797. package/dist/mime-ChLqYwDk.mjs.map +1 -0
  798. package/dist/mode-CCuKXUci.mjs +23 -0
  799. package/dist/mode-CCuKXUci.mjs.map +1 -0
  800. package/dist/node-sqlite-compat-CONMsl-x.mjs +46 -0
  801. package/dist/node-sqlite-compat-CONMsl-x.mjs.map +1 -0
  802. package/dist/normalize-CO2P8-iE.mjs +191 -0
  803. package/dist/normalize-CO2P8-iE.mjs.map +1 -0
  804. package/dist/oauth-authorization-DlMIL5_h.mjs +274 -0
  805. package/dist/oauth-authorization-DlMIL5_h.mjs.map +1 -0
  806. package/dist/oauth-clients-Djq9r_KP.mjs +266 -0
  807. package/dist/oauth-clients-Djq9r_KP.mjs.map +1 -0
  808. package/dist/oauth-state-store-BbjnZXNC.mjs +50 -0
  809. package/dist/oauth-state-store-BbjnZXNC.mjs.map +1 -0
  810. package/dist/oauth-user-lookup-vue9a64A.mjs +26 -0
  811. package/dist/oauth-user-lookup-vue9a64A.mjs.map +1 -0
  812. package/dist/object-cache/memory.d.mts +1 -1
  813. package/dist/object-cache-CqWNY5uP.mjs +582 -0
  814. package/dist/object-cache-CqWNY5uP.mjs.map +1 -0
  815. package/dist/options-DhrI7eMv.mjs +208 -0
  816. package/dist/options-DhrI7eMv.mjs.map +1 -0
  817. package/dist/options-SQCHQCPH.d.mts +81 -0
  818. package/dist/options-SQCHQCPH.d.mts.map +1 -0
  819. package/dist/page/index.d.mts +2 -2
  820. package/dist/page/index.mjs +9 -9
  821. package/dist/parse-CVgO-Qai.d.mts +168 -0
  822. package/dist/parse-CVgO-Qai.d.mts.map +1 -0
  823. package/dist/parse-DY6OD1j_.mjs +81 -0
  824. package/dist/parse-DY6OD1j_.mjs.map +1 -0
  825. package/dist/passkey-config-X_H8128a.mjs +46 -0
  826. package/dist/passkey-config-X_H8128a.mjs.map +1 -0
  827. package/dist/paths-JxokKIbr.mjs +77 -0
  828. package/dist/paths-JxokKIbr.mjs.map +1 -0
  829. package/dist/patterns-BhAvqO0S.mjs +145 -0
  830. package/dist/patterns-BhAvqO0S.mjs.map +1 -0
  831. package/dist/placeholder-MSEd8gty.d.mts +306 -0
  832. package/dist/placeholder-MSEd8gty.d.mts.map +1 -0
  833. package/dist/plan-xjcWrAah.mjs +135 -0
  834. package/dist/plan-xjcWrAah.mjs.map +1 -0
  835. package/dist/plugin-test-runtime.d.mts +38 -15
  836. package/dist/plugin-test-runtime.d.mts.map +1 -1
  837. package/dist/plugin-test-runtime.mjs +112 -99
  838. package/dist/plugin-types.d.mts +2 -2
  839. package/dist/plugin-utils.d.mts +12 -12
  840. package/dist/plugins/adapt-sandbox-entry.d.mts +12 -12
  841. package/dist/plugins/adapt-sandbox-entry.mjs +3 -3
  842. package/dist/plugins/host.d.mts +5 -5
  843. package/dist/plugins/host.mjs +45 -37
  844. package/dist/plugins-DUXUt8B9.mjs +277 -0
  845. package/dist/plugins-DUXUt8B9.mjs.map +1 -0
  846. package/dist/policy-CUvwAARc.mjs +45 -0
  847. package/dist/policy-CUvwAARc.mjs.map +1 -0
  848. package/dist/portable-text-5O_ZGUjB.mjs +307 -0
  849. package/dist/portable-text-5O_ZGUjB.mjs.map +1 -0
  850. package/dist/preview-CIUcPYr6.mjs +107 -0
  851. package/dist/preview-CIUcPYr6.mjs.map +1 -0
  852. package/dist/protocol-B97DZaFr.d.mts +61 -0
  853. package/dist/protocol-B97DZaFr.d.mts.map +1 -0
  854. package/dist/public-submission-BFBUrqst.mjs +250 -0
  855. package/dist/public-submission-BFBUrqst.mjs.map +1 -0
  856. package/dist/public-url-Bn7U0D7-.mjs +101 -0
  857. package/dist/public-url-Bn7U0D7-.mjs.map +1 -0
  858. package/dist/query-B48peoRT.mjs +890 -0
  859. package/dist/query-B48peoRT.mjs.map +1 -0
  860. package/dist/query-C4IH0uhR.mjs +41 -0
  861. package/dist/query-C4IH0uhR.mjs.map +1 -0
  862. package/dist/rate-limit-BZ6nybgc.mjs +120 -0
  863. package/dist/rate-limit-BZ6nybgc.mjs.map +1 -0
  864. package/dist/receipt-B4wJj5Jq.mjs +43 -0
  865. package/dist/receipt-B4wJj5Jq.mjs.map +1 -0
  866. package/dist/redirect-Cd1E7VUt.mjs +17 -0
  867. package/dist/redirect-Cd1E7VUt.mjs.map +1 -0
  868. package/dist/redirect-iJf5fltD.mjs +415 -0
  869. package/dist/redirect-iJf5fltD.mjs.map +1 -0
  870. package/dist/redirects-CYu1u1aK.mjs +446 -0
  871. package/dist/redirects-CYu1u1aK.mjs.map +1 -0
  872. package/dist/redirects-Cv9apQKj.mjs +148 -0
  873. package/dist/redirects-Cv9apQKj.mjs.map +1 -0
  874. package/dist/registry/testing.mjs +3 -3
  875. package/dist/relation-M4NUCb0b.mjs +334 -0
  876. package/dist/relation-M4NUCb0b.mjs.map +1 -0
  877. package/dist/relations-CCif47Oq.mjs +403 -0
  878. package/dist/relations-CCif47Oq.mjs.map +1 -0
  879. package/dist/relations-CK9CYUyr.mjs +310 -0
  880. package/dist/relations-CK9CYUyr.mjs.map +1 -0
  881. package/dist/request-cache-DzLYKm_x.mjs +104 -0
  882. package/dist/request-cache-DzLYKm_x.mjs.map +1 -0
  883. package/dist/request-context.d.mts +1 -1
  884. package/dist/request-meta-sZXt3sRo.mjs +141 -0
  885. package/dist/request-meta-sZXt3sRo.mjs.map +1 -0
  886. package/dist/resolve-2WbQsCvT.mjs +199 -0
  887. package/dist/resolve-2WbQsCvT.mjs.map +1 -0
  888. package/dist/route-wire--1hLuACz.mjs +197 -0
  889. package/dist/route-wire--1hLuACz.mjs.map +1 -0
  890. package/dist/routes-CUsM40Cc.mjs +272 -0
  891. package/dist/routes-CUsM40Cc.mjs.map +1 -0
  892. package/dist/runner-1y4J9fJB.mjs +5742 -0
  893. package/dist/runner-1y4J9fJB.mjs.map +1 -0
  894. package/dist/runner-D5_LzKXI.d.mts +68 -0
  895. package/dist/runner-D5_LzKXI.d.mts.map +1 -0
  896. package/dist/runtime.d.mts +13 -13
  897. package/dist/runtime.mjs +4 -4
  898. package/dist/schema-BABiRdsH.mjs +13 -0
  899. package/dist/schema-BABiRdsH.mjs.map +1 -0
  900. package/dist/schema-Du3MzCG0.mjs +446 -0
  901. package/dist/schema-Du3MzCG0.mjs.map +1 -0
  902. package/dist/scopes-Dbk-ElhT.mjs +34 -0
  903. package/dist/scopes-Dbk-ElhT.mjs.map +1 -0
  904. package/dist/search-Bt1PL1jf.mjs +448 -0
  905. package/dist/search-Bt1PL1jf.mjs.map +1 -0
  906. package/dist/secrets-CIi0nFiR.mjs +341 -0
  907. package/dist/secrets-CIi0nFiR.mjs.map +1 -0
  908. package/dist/sections-Ddptx_o9.mjs +346 -0
  909. package/dist/sections-Ddptx_o9.mjs.map +1 -0
  910. package/dist/seed/index.d.mts +2 -2
  911. package/dist/seed/index.mjs +35 -30
  912. package/dist/seo/index.d.mts +3 -3
  913. package/dist/seo/index.mjs +25 -21
  914. package/dist/seo/index.mjs.map +1 -1
  915. package/dist/seo-CZT2oLtT.mjs +114 -0
  916. package/dist/seo-CZT2oLtT.mjs.map +1 -0
  917. package/dist/seo-D5w3-Azy.mjs +133 -0
  918. package/dist/seo-D5w3-Azy.mjs.map +1 -0
  919. package/dist/service-DuykXPyW.mjs +150 -0
  920. package/dist/service-DuykXPyW.mjs.map +1 -0
  921. package/dist/session-user-BhaVp8w0.mjs +51 -0
  922. package/dist/session-user-BhaVp8w0.mjs.map +1 -0
  923. package/dist/settings-2xNYr90o.mjs +51 -0
  924. package/dist/settings-2xNYr90o.mjs.map +1 -0
  925. package/dist/settings-CgjRWGW9.mjs +439 -0
  926. package/dist/settings-CgjRWGW9.mjs.map +1 -0
  927. package/dist/settings-DYidIB26.mjs +61 -0
  928. package/dist/settings-DYidIB26.mjs.map +1 -0
  929. package/dist/setup-complete-bOogcHWG.mjs +26 -0
  930. package/dist/setup-complete-bOogcHWG.mjs.map +1 -0
  931. package/dist/setup-nonce-0EGuKM4s.mjs +25 -0
  932. package/dist/setup-nonce-0EGuKM4s.mjs.map +1 -0
  933. package/dist/site-settings-DOgRUoFX.mjs +50 -0
  934. package/dist/site-settings-DOgRUoFX.mjs.map +1 -0
  935. package/dist/site-url-BU_bbxwj.mjs +16 -0
  936. package/dist/site-url-BU_bbxwj.mjs.map +1 -0
  937. package/dist/snapshot-CugwUqKQ.mjs +237 -0
  938. package/dist/snapshot-CugwUqKQ.mjs.map +1 -0
  939. package/dist/ssrf-CJUnJymJ.mjs +406 -0
  940. package/dist/ssrf-CJUnJymJ.mjs.map +1 -0
  941. package/dist/stage-DgrBz_du.mjs +662 -0
  942. package/dist/stage-DgrBz_du.mjs.map +1 -0
  943. package/dist/state-DDiPySSG.mjs +153 -0
  944. package/dist/state-DDiPySSG.mjs.map +1 -0
  945. package/dist/status-DZKIG4a9.mjs +30 -0
  946. package/dist/status-DZKIG4a9.mjs.map +1 -0
  947. package/dist/storage/local.d.mts +1 -1
  948. package/dist/storage/local.mjs +1 -1
  949. package/dist/storage/s3.d.mts +1 -1
  950. package/dist/storage/s3.mjs +1 -1
  951. package/dist/storage-query-Bdv7A62k.d.mts +1576 -0
  952. package/dist/storage-query-Bdv7A62k.d.mts.map +1 -0
  953. package/dist/tar-_1AgTYFu.mjs +449 -0
  954. package/dist/tar-_1AgTYFu.mjs.map +1 -0
  955. package/dist/target-_7pn2Gbg.mjs +27 -0
  956. package/dist/target-_7pn2Gbg.mjs.map +1 -0
  957. package/dist/taxonomies-C1IwNe8Q.mjs +697 -0
  958. package/dist/taxonomies-C1IwNe8Q.mjs.map +1 -0
  959. package/dist/taxonomies-C_XMoRcX.mjs +939 -0
  960. package/dist/taxonomies-C_XMoRcX.mjs.map +1 -0
  961. package/dist/taxonomy-C3Fu1YeH.mjs +525 -0
  962. package/dist/taxonomy-C3Fu1YeH.mjs.map +1 -0
  963. package/dist/tokens-u78jETkX.mjs +171 -0
  964. package/dist/tokens-u78jETkX.mjs.map +1 -0
  965. package/dist/transfer-BhUCrgDc.mjs +8675 -0
  966. package/dist/transfer-BhUCrgDc.mjs.map +1 -0
  967. package/dist/transfer-DIGvR9Af.mjs +166 -0
  968. package/dist/transfer-DIGvR9Af.mjs.map +1 -0
  969. package/dist/transfer-download-Pml7RMio.mjs +33 -0
  970. package/dist/transfer-download-Pml7RMio.mjs.map +1 -0
  971. package/dist/transformations-9gfpwY65.mjs +196 -0
  972. package/dist/transformations-9gfpwY65.mjs.map +1 -0
  973. package/dist/transport-BIfH7_hx.d.mts +42 -0
  974. package/dist/transport-BIfH7_hx.d.mts.map +1 -0
  975. package/dist/transport-DKWIq53q.mjs +136 -0
  976. package/dist/transport-DKWIq53q.mjs.map +1 -0
  977. package/dist/trusted-proxy-tKiVtyTD.mjs +51 -0
  978. package/dist/trusted-proxy-tKiVtyTD.mjs.map +1 -0
  979. package/dist/turnstile-DpsoyOfg.mjs +53 -0
  980. package/dist/turnstile-DpsoyOfg.mjs.map +1 -0
  981. package/dist/types-B4sUC2p1.d.mts +198 -0
  982. package/dist/types-B4sUC2p1.d.mts.map +1 -0
  983. package/dist/types-B7Lmsh1E.d.mts +887 -0
  984. package/dist/types-B7Lmsh1E.d.mts.map +1 -0
  985. package/dist/types-BZakh7Ym.mjs +22 -0
  986. package/dist/types-BZakh7Ym.mjs.map +1 -0
  987. package/dist/types-B_CppEmv.d.mts +264 -0
  988. package/dist/types-B_CppEmv.d.mts.map +1 -0
  989. package/dist/types-BcrBrE6-.mjs +159 -0
  990. package/dist/types-BcrBrE6-.mjs.map +1 -0
  991. package/dist/types-CAZgq9fO.d.mts +124 -0
  992. package/dist/types-CAZgq9fO.d.mts.map +1 -0
  993. package/dist/types-Cd-O03VG.d.mts +363 -0
  994. package/dist/types-Cd-O03VG.d.mts.map +1 -0
  995. package/dist/types-Cr-Kg-3D.d.mts +1641 -0
  996. package/dist/types-Cr-Kg-3D.d.mts.map +1 -0
  997. package/dist/types-D2fvWd1i.d.mts +359 -0
  998. package/dist/types-D2fvWd1i.d.mts.map +1 -0
  999. package/dist/types-D7HUsguf.mjs +77 -0
  1000. package/dist/types-D7HUsguf.mjs.map +1 -0
  1001. package/dist/types-DA1Te3Oy.d.mts +198 -0
  1002. package/dist/types-DA1Te3Oy.d.mts.map +1 -0
  1003. package/dist/types-DhV6Q5bP.mjs +18 -0
  1004. package/dist/types-DhV6Q5bP.mjs.map +1 -0
  1005. package/dist/types-DnnVSl6Y.d.mts +85 -0
  1006. package/dist/types-DnnVSl6Y.d.mts.map +1 -0
  1007. package/dist/types-VT4-BugW.mjs +16 -0
  1008. package/dist/types-VT4-BugW.mjs.map +1 -0
  1009. package/dist/upload-attempts-B68ZGNu9.mjs +32 -0
  1010. package/dist/upload-attempts-B68ZGNu9.mjs.map +1 -0
  1011. package/dist/url-pattern-CCeZMGOh.mjs +33 -0
  1012. package/dist/url-pattern-CCeZMGOh.mjs.map +1 -0
  1013. package/dist/user-DDSL-sGw.mjs +171 -0
  1014. package/dist/user-DDSL-sGw.mjs.map +1 -0
  1015. package/dist/utils-DdhmO9w9.mjs +439 -0
  1016. package/dist/utils-DdhmO9w9.mjs.map +1 -0
  1017. package/dist/validate-CHosPEhs.mjs +453 -0
  1018. package/dist/validate-CHosPEhs.mjs.map +1 -0
  1019. package/dist/validate-CM79QXYE.d.mts +432 -0
  1020. package/dist/validate-CM79QXYE.d.mts.map +1 -0
  1021. package/dist/validation-BFecQczG.mjs +218 -0
  1022. package/dist/validation-BFecQczG.mjs.map +1 -0
  1023. package/dist/version-BRZXVifl.mjs +9 -0
  1024. package/dist/version-BRZXVifl.mjs.map +1 -0
  1025. package/dist/version-DUQDHgrf.mjs +7 -0
  1026. package/dist/version-DUQDHgrf.mjs.map +1 -0
  1027. package/dist/widgets-WdV72brA.mjs +126 -0
  1028. package/dist/widgets-WdV72brA.mjs.map +1 -0
  1029. package/dist/wxr-taxonomies-BHTdqkmZ.d.mts +34 -0
  1030. package/dist/wxr-taxonomies-BHTdqkmZ.d.mts.map +1 -0
  1031. package/dist/wxr-taxonomies-DP6ESgMN.mjs +372 -0
  1032. package/dist/wxr-taxonomies-DP6ESgMN.mjs.map +1 -0
  1033. package/dist/zod-generator-D6uE4WiS.mjs +389 -0
  1034. package/dist/zod-generator-D6uE4WiS.mjs.map +1 -0
  1035. package/package.json +6 -5
  1036. package/src/api/authorize.ts +17 -0
  1037. package/src/api/email-locale.ts +24 -0
  1038. package/src/api/errors.ts +16 -0
  1039. package/src/api/handlers/api-tokens.ts +18 -3
  1040. package/src/api/handlers/block-types.ts +129 -0
  1041. package/src/api/handlers/bulk-tag.ts +309 -0
  1042. package/src/api/handlers/comments.ts +0 -28
  1043. package/src/api/handlers/index.ts +10 -0
  1044. package/src/api/handlers/manifest.ts +8 -1
  1045. package/src/api/handlers/marketplace.ts +22 -16
  1046. package/src/api/handlers/media-usage.ts +31 -12
  1047. package/src/api/handlers/media.ts +19 -14
  1048. package/src/api/handlers/schema.ts +33 -8
  1049. package/src/api/handlers/settings.ts +2 -2
  1050. package/src/api/handlers/snapshot.ts +4 -0
  1051. package/src/api/handlers/taxonomies.ts +132 -83
  1052. package/src/api/handlers/transfer.ts +1347 -0
  1053. package/src/api/handlers/validate-media-fields.ts +124 -12
  1054. package/src/api/media-usage-write-fence.ts +5 -26
  1055. package/src/api/openapi/document.ts +614 -5
  1056. package/src/api/schemas/auth.ts +7 -1
  1057. package/src/api/schemas/common.ts +5 -0
  1058. package/src/api/schemas/content.ts +20 -1
  1059. package/src/api/schemas/media-usage.ts +11 -0
  1060. package/src/api/schemas/menus.ts +1 -1
  1061. package/src/api/schemas/schema.ts +117 -0
  1062. package/src/api/schemas/settings.ts +3 -3
  1063. package/src/api/schemas/taxonomies.ts +18 -0
  1064. package/src/api/schemas/transfer.ts +249 -0
  1065. package/src/api/transfer-download.ts +36 -0
  1066. package/src/api/types.ts +3 -0
  1067. package/src/astro/integration/routes.ts +31 -0
  1068. package/src/astro/integration/vite-config.ts +26 -0
  1069. package/src/astro/middleware/auth.ts +30 -7
  1070. package/src/astro/middleware/media-usage-write-fence.ts +106 -11
  1071. package/src/astro/middleware/request-context.ts +16 -2
  1072. package/src/astro/middleware.ts +38 -18
  1073. package/src/astro/routes/admin.astro +3 -1
  1074. package/src/astro/routes/api/admin/hooks/exclusive/index.ts +1 -5
  1075. package/src/astro/routes/api/admin/plugins/[id]/disable.ts +4 -3
  1076. package/src/astro/routes/api/admin/plugins/[id]/enable.ts +4 -3
  1077. package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +4 -3
  1078. package/src/astro/routes/api/admin/plugins/[id]/update.ts +4 -3
  1079. package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +4 -3
  1080. package/src/astro/routes/api/admin/plugins/registry/[id]/uninstall.ts +4 -3
  1081. package/src/astro/routes/api/admin/plugins/registry/[id]/update.ts +3 -3
  1082. package/src/astro/routes/api/admin/plugins/registry/install.ts +3 -3
  1083. package/src/astro/routes/api/admin/plugins/registry/publisher-handle.ts +59 -0
  1084. package/src/astro/routes/api/admin/transfer/[...path].ts +143 -0
  1085. package/src/astro/routes/api/admin/transfer/approvals/[id]/approve.ts +35 -0
  1086. package/src/astro/routes/api/admin/transfer/approvals/[id]/deny.ts +35 -0
  1087. package/src/astro/routes/api/admin/transfer/approvals/index.ts +33 -0
  1088. package/src/astro/routes/api/admin/transfer/capabilities.ts +36 -0
  1089. package/src/astro/routes/api/admin/transfer/exports/[id]/advance.ts +34 -0
  1090. package/src/astro/routes/api/admin/transfer/exports/[id]/archive.ts +35 -0
  1091. package/src/astro/routes/api/admin/transfer/exports/[id]/files/[...path].ts +40 -0
  1092. package/src/astro/routes/api/admin/transfer/exports/[id]/index.ts +25 -0
  1093. package/src/astro/routes/api/admin/transfer/exports/[id]/manifest.ts +38 -0
  1094. package/src/astro/routes/api/admin/transfer/exports/index.ts +64 -0
  1095. package/src/astro/routes/api/admin/transfer/imports/[id]/abandon.ts +28 -0
  1096. package/src/astro/routes/api/admin/transfer/imports/[id]/advance.ts +37 -0
  1097. package/src/astro/routes/api/admin/transfer/imports/[id]/analyze.ts +51 -0
  1098. package/src/astro/routes/api/admin/transfer/imports/[id]/cancel.ts +27 -0
  1099. package/src/astro/routes/api/admin/transfer/imports/[id]/execute.ts +48 -0
  1100. package/src/astro/routes/api/admin/transfer/imports/[id]/files/[...path].ts +54 -0
  1101. package/src/astro/routes/api/admin/transfer/imports/[id]/index.ts +25 -0
  1102. package/src/astro/routes/api/admin/transfer/imports/[id]/missing.ts +34 -0
  1103. package/src/astro/routes/api/admin/transfer/imports/[id]/plan.ts +29 -0
  1104. package/src/astro/routes/api/admin/transfer/imports/[id]/receipt.ts +25 -0
  1105. package/src/astro/routes/api/admin/transfer/imports/index.ts +85 -0
  1106. package/src/astro/routes/api/admin/users/[id]/send-recovery.ts +9 -1
  1107. package/src/astro/routes/api/auth/invite/index.ts +15 -1
  1108. package/src/astro/routes/api/auth/magic-link/send.ts +9 -1
  1109. package/src/astro/routes/api/auth/magic-link/verify.ts +37 -39
  1110. package/src/astro/routes/api/auth/passkey/options.ts +2 -21
  1111. package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +64 -13
  1112. package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +16 -1
  1113. package/src/astro/routes/api/import/wordpress/rewrite-url-helpers.ts +11 -4
  1114. package/src/astro/routes/api/mcp.ts +1 -0
  1115. package/src/astro/routes/api/media/[id].ts +5 -15
  1116. package/src/astro/routes/api/media/file/[...key].ts +30 -5
  1117. package/src/astro/routes/api/oauth/authorize.ts +4 -0
  1118. package/src/astro/routes/api/schema/block-types/[slug]/index.ts +29 -0
  1119. package/src/astro/routes/api/schema/block-types/[slug]/versions/[version]/activate.ts +31 -0
  1120. package/src/astro/routes/api/schema/block-types/index.ts +29 -0
  1121. package/src/astro/routes/api/taxonomies/bulk-tag.ts +29 -0
  1122. package/src/astro/routes/api/typegen.ts +8 -4
  1123. package/src/astro/types.ts +4 -0
  1124. package/src/auth/api-tokens.ts +1 -0
  1125. package/src/auth/rate-limit.ts +2 -1
  1126. package/src/auth/scopes.ts +11 -0
  1127. package/src/cleanup.ts +14 -0
  1128. package/src/cli/client-factory.ts +6 -1
  1129. package/src/cli/commands/export-seed.ts +41 -15
  1130. package/src/cli/commands/seed.ts +6 -2
  1131. package/src/cli/commands/site.ts +556 -0
  1132. package/src/cli/index.ts +2 -0
  1133. package/src/cli/site/archive.ts +273 -0
  1134. package/src/cli/site/export.ts +311 -0
  1135. package/src/cli/site/import.ts +815 -0
  1136. package/src/cli/site/shared.ts +241 -0
  1137. package/src/client/index.ts +414 -11
  1138. package/src/client/portable-text.ts +41 -0
  1139. package/src/comments/public-submission.ts +19 -11
  1140. package/src/components/Blocks.astro +46 -0
  1141. package/src/components/Gallery.astro +2 -1
  1142. package/src/components/Image.astro +51 -6
  1143. package/src/components/InlinePortableTextEditor.tsx +28 -18
  1144. package/src/components/blocks.ts +44 -0
  1145. package/src/components/index.ts +14 -4
  1146. package/src/content/converters/gallery.ts +54 -16
  1147. package/src/content/converters/image-link.ts +27 -0
  1148. package/src/content/converters/index.ts +1 -0
  1149. package/src/content/converters/portable-text-to-prosemirror.ts +10 -7
  1150. package/src/content/converters/prosemirror-to-portable-text.ts +14 -0
  1151. package/src/content/converters/types.ts +17 -0
  1152. package/src/database/content-datetime.ts +19 -2
  1153. package/src/database/migrations/035_bounded_404_log.ts +28 -39
  1154. package/src/database/migrations/081_redirect_write_guards.ts +7 -4
  1155. package/src/database/migrations/083_block_types.ts +58 -0
  1156. package/src/database/migrations/084_site_transfer.ts +264 -0
  1157. package/src/database/migrations/085_taxonomy_def_groups.ts +155 -0
  1158. package/src/database/migrations/runner.ts +6 -0
  1159. package/src/database/repositories/audit.ts +10 -1
  1160. package/src/database/repositories/byline.ts +4 -1
  1161. package/src/database/repositories/content.ts +83 -11
  1162. package/src/database/repositories/media-usage.ts +43 -13
  1163. package/src/database/repositories/media.ts +32 -1
  1164. package/src/database/repositories/options.ts +12 -0
  1165. package/src/database/repositories/revision.ts +6 -3
  1166. package/src/database/repositories/taxonomy-def.ts +126 -0
  1167. package/src/database/repositories/taxonomy.ts +65 -26
  1168. package/src/database/types.ts +133 -1
  1169. package/src/db/sqlite.ts +1 -1
  1170. package/src/emdash-runtime.ts +287 -51
  1171. package/src/import/utils.ts +8 -1
  1172. package/src/import/wxr-taxonomies.ts +11 -67
  1173. package/src/index.ts +26 -1
  1174. package/src/loader.ts +9 -5
  1175. package/src/mcp/server.ts +521 -29
  1176. package/src/mcp/transfer.ts +569 -0
  1177. package/src/media/local-runtime.ts +5 -18
  1178. package/src/media/upload-attempts.ts +5 -0
  1179. package/src/media/url.ts +15 -0
  1180. package/src/media/usage/content-fields.ts +69 -5
  1181. package/src/media/usage/content-refresh.ts +13 -66
  1182. package/src/media/usage/content-repair.ts +10 -2
  1183. package/src/media/usage/extractor.ts +74 -3
  1184. package/src/media/usage/projection-fingerprint.ts +14 -0
  1185. package/src/media/usage/schema-invalidation.ts +72 -0
  1186. package/src/media/usage/site-settings.ts +52 -0
  1187. package/src/media/usage/types.ts +2 -0
  1188. package/src/object-cache/index.ts +69 -4
  1189. package/src/plugin-test-runtime.ts +1 -0
  1190. package/src/plugins/context.ts +104 -56
  1191. package/src/plugins/cron.ts +6 -4
  1192. package/src/plugins/define-plugin.ts +11 -1
  1193. package/src/plugins/email-console.ts +2 -0
  1194. package/src/plugins/email.ts +16 -0
  1195. package/src/plugins/hooks.ts +33 -9
  1196. package/src/plugins/manifest-schema.ts +1 -0
  1197. package/src/plugins/route-error.ts +57 -0
  1198. package/src/plugins/routes.ts +5 -60
  1199. package/src/plugins/sandbox/types.ts +22 -2
  1200. package/src/plugins/types.ts +4 -0
  1201. package/src/registry/artifact-fetch.ts +17 -4
  1202. package/src/registry/publisher-handle.ts +273 -0
  1203. package/src/schema/block-type-contract.ts +478 -0
  1204. package/src/schema/block-type-registry.ts +689 -0
  1205. package/src/schema/block-types.ts +110 -0
  1206. package/src/schema/block-values.ts +293 -0
  1207. package/src/schema/byline-registry.ts +1 -1
  1208. package/src/schema/index.ts +28 -0
  1209. package/src/schema/registry.ts +462 -12
  1210. package/src/schema/types.ts +12 -1
  1211. package/src/schema/zod-generator.ts +92 -9
  1212. package/src/search/fts-manager.ts +40 -0
  1213. package/src/seed/apply.ts +173 -49
  1214. package/src/seed/types.ts +23 -3
  1215. package/src/seed/validate.ts +131 -7
  1216. package/src/settings/index.ts +32 -9
  1217. package/src/settings/types.ts +15 -0
  1218. package/src/taxonomies/index.ts +72 -83
  1219. package/src/transfer/analyze/index.ts +45 -0
  1220. package/src/transfer/analyze/issues.ts +55 -0
  1221. package/src/transfer/analyze/plan.ts +327 -0
  1222. package/src/transfer/analyze/read.ts +134 -0
  1223. package/src/transfer/analyze/step.ts +551 -0
  1224. package/src/transfer/analyze/target.ts +269 -0
  1225. package/src/transfer/analyze/unique-keys.ts +228 -0
  1226. package/src/transfer/analyze/validate.ts +1412 -0
  1227. package/src/transfer/analyze/values.ts +227 -0
  1228. package/src/transfer/analyze/write-rules.ts +196 -0
  1229. package/src/transfer/audit.ts +38 -0
  1230. package/src/transfer/auth.ts +25 -0
  1231. package/src/transfer/container/tar.ts +242 -0
  1232. package/src/transfer/domain.ts +248 -0
  1233. package/src/transfer/errors.ts +134 -0
  1234. package/src/transfer/export/exporter.ts +1067 -0
  1235. package/src/transfer/export/fence.ts +84 -0
  1236. package/src/transfer/export/media.ts +165 -0
  1237. package/src/transfer/export/readers.ts +1164 -0
  1238. package/src/transfer/export/verify.ts +677 -0
  1239. package/src/transfer/fence.ts +193 -0
  1240. package/src/transfer/format/canonical.ts +151 -0
  1241. package/src/transfer/format/column-codec.ts +275 -0
  1242. package/src/transfer/format/columns.ts +707 -0
  1243. package/src/transfer/format/digest.ts +287 -0
  1244. package/src/transfer/format/features.ts +91 -0
  1245. package/src/transfer/format/issues.ts +39 -0
  1246. package/src/transfer/format/kinds.ts +936 -0
  1247. package/src/transfer/format/limits.ts +87 -0
  1248. package/src/transfer/format/manifest.ts +297 -0
  1249. package/src/transfer/format/media-refs.ts +716 -0
  1250. package/src/transfer/format/paths.ts +73 -0
  1251. package/src/transfer/format/plan.ts +162 -0
  1252. package/src/transfer/format/receipt.ts +43 -0
  1253. package/src/transfer/format/records.ts +127 -0
  1254. package/src/transfer/format/settings.ts +94 -0
  1255. package/src/transfer/format/transformations.ts +260 -0
  1256. package/src/transfer/format/version.ts +9 -0
  1257. package/src/transfer/gc.ts +103 -0
  1258. package/src/transfer/import/advance.ts +522 -0
  1259. package/src/transfer/import/context.ts +281 -0
  1260. package/src/transfer/import/entries.ts +186 -0
  1261. package/src/transfer/import/ids.ts +40 -0
  1262. package/src/transfer/import/index.ts +15 -0
  1263. package/src/transfer/import/media.ts +141 -0
  1264. package/src/transfer/import/rebuild.ts +300 -0
  1265. package/src/transfer/import/rows.ts +114 -0
  1266. package/src/transfer/import/scaffold.ts +221 -0
  1267. package/src/transfer/import/sql.ts +134 -0
  1268. package/src/transfer/import/stages.ts +329 -0
  1269. package/src/transfer/import/writers.ts +370 -0
  1270. package/src/transfer/index.ts +472 -0
  1271. package/src/transfer/ops/approvals.ts +359 -0
  1272. package/src/transfer/ops/budget.ts +82 -0
  1273. package/src/transfer/ops/collation.ts +21 -0
  1274. package/src/transfer/ops/identity-map.ts +142 -0
  1275. package/src/transfer/ops/media-blobs.ts +135 -0
  1276. package/src/transfer/ops/operations.ts +842 -0
  1277. package/src/transfer/ops/package-index.ts +159 -0
  1278. package/src/transfer/ops/staged-files.ts +231 -0
  1279. package/src/transfer/ops/states.ts +265 -0
  1280. package/src/transfer/ops/time.ts +54 -0
  1281. package/src/transfer/schema-importer.ts +34 -0
  1282. package/src/transfer/site-id.ts +30 -0
  1283. package/src/transfer/staging/assembler.ts +217 -0
  1284. package/src/transfer/staging/keys.ts +94 -0
  1285. package/src/transfer/staging/package.ts +229 -0
  1286. package/src/transfer/staging/stage.ts +231 -0
  1287. package/src/ui.ts +19 -0
  1288. package/src/visual-editing/toolbar.ts +125 -37
  1289. package/dist/action-token-On1sot_c.mjs +0 -26
  1290. package/dist/action-token-On1sot_c.mjs.map +0 -1
  1291. package/dist/activation-CaMfdBt1.mjs +0 -382
  1292. package/dist/activation-CaMfdBt1.mjs.map +0 -1
  1293. package/dist/adapters-Cq10K4_c.d.mts +0 -171
  1294. package/dist/adapters-Cq10K4_c.d.mts.map +0 -1
  1295. package/dist/allowed-origins-1Sa-2wZK.mjs +0 -116
  1296. package/dist/allowed-origins-1Sa-2wZK.mjs.map +0 -1
  1297. package/dist/api-DmjKxPCY.mjs +0 -4831
  1298. package/dist/api-DmjKxPCY.mjs.map +0 -1
  1299. package/dist/api-tokens-BIXItFKV.mjs +0 -163
  1300. package/dist/api-tokens-BIXItFKV.mjs.map +0 -1
  1301. package/dist/api-tokens-DS1VzN7_.mjs +0 -3
  1302. package/dist/apply-drpfmqrR.mjs +0 -1005
  1303. package/dist/apply-drpfmqrR.mjs.map +0 -1
  1304. package/dist/artifact-fetch-COiMhVgk.mjs +0 -813
  1305. package/dist/artifact-fetch-COiMhVgk.mjs.map +0 -1
  1306. package/dist/authoritative-records-DbqshfKD.mjs +0 -5172
  1307. package/dist/authoritative-records-DbqshfKD.mjs.map +0 -1
  1308. package/dist/authorize-BXpfMpp0.mjs +0 -40
  1309. package/dist/authorize-BXpfMpp0.mjs.map +0 -1
  1310. package/dist/backup-BsPQdwnS.mjs +0 -246
  1311. package/dist/backup-BsPQdwnS.mjs.map +0 -1
  1312. package/dist/byline-Doh-wt99.mjs +0 -906
  1313. package/dist/byline-Doh-wt99.mjs.map +0 -1
  1314. package/dist/byline-fields-CsrdvaLV.mjs +0 -123
  1315. package/dist/byline-fields-CsrdvaLV.mjs.map +0 -1
  1316. package/dist/byline-registry-D6lWzXk6.mjs +0 -406
  1317. package/dist/byline-registry-D6lWzXk6.mjs.map +0 -1
  1318. package/dist/bylines-Cop0hkoy.mjs +0 -214
  1319. package/dist/bylines-Cop0hkoy.mjs.map +0 -1
  1320. package/dist/bylines-kpBzHPr8.mjs +0 -232
  1321. package/dist/bylines-kpBzHPr8.mjs.map +0 -1
  1322. package/dist/cache-B0pOlXdM.mjs +0 -108
  1323. package/dist/cache-B0pOlXdM.mjs.map +0 -1
  1324. package/dist/capture-triggers-gxQxnxrJ.mjs +0 -362
  1325. package/dist/capture-triggers-gxQxnxrJ.mjs.map +0 -1
  1326. package/dist/challenge-store-CkRdSdd8.mjs +0 -51
  1327. package/dist/challenge-store-CkRdSdd8.mjs.map +0 -1
  1328. package/dist/checksum-Bsi2D9M1.mjs +0 -162
  1329. package/dist/checksum-Bsi2D9M1.mjs.map +0 -1
  1330. package/dist/chrome-tags-D7LGB91U.mjs +0 -24
  1331. package/dist/chrome-tags-D7LGB91U.mjs.map +0 -1
  1332. package/dist/chunks-BmWyou79.mjs +0 -25
  1333. package/dist/chunks-BmWyou79.mjs.map +0 -1
  1334. package/dist/collection-deletion-C3Xixmkx.mjs +0 -436
  1335. package/dist/collection-deletion-C3Xixmkx.mjs.map +0 -1
  1336. package/dist/collection-slugs-cache-D9auiKmI.mjs +0 -85
  1337. package/dist/collection-slugs-cache-D9auiKmI.mjs.map +0 -1
  1338. package/dist/comment-DcT8qdPK.mjs +0 -313
  1339. package/dist/comment-DcT8qdPK.mjs.map +0 -1
  1340. package/dist/comment-reaction-DY5-y8Y5.mjs +0 -85
  1341. package/dist/comment-reaction-DY5-y8Y5.mjs.map +0 -1
  1342. package/dist/comments-B67VRNGw.mjs +0 -204
  1343. package/dist/comments-B67VRNGw.mjs.map +0 -1
  1344. package/dist/comments-BVzzt6x5.mjs +0 -99
  1345. package/dist/comments-BVzzt6x5.mjs.map +0 -1
  1346. package/dist/components-DIl9Qzh6.mjs +0 -108
  1347. package/dist/components-DIl9Qzh6.mjs.map +0 -1
  1348. package/dist/config-B2acLeC5.mjs +0 -242
  1349. package/dist/config-B2acLeC5.mjs.map +0 -1
  1350. package/dist/config-CGVeT8Hc.mjs +0 -73
  1351. package/dist/config-CGVeT8Hc.mjs.map +0 -1
  1352. package/dist/config-D3HhLON3.d.mts +0 -39
  1353. package/dist/config-D3HhLON3.d.mts.map +0 -1
  1354. package/dist/config-DS7e4EZ9.d.mts +0 -6387
  1355. package/dist/config-DS7e4EZ9.d.mts.map +0 -1
  1356. package/dist/content-DZ3B5qtA.mjs +0 -2216
  1357. package/dist/content-DZ3B5qtA.mjs.map +0 -1
  1358. package/dist/content-list-query-CbPdzsna.d.mts +0 -26
  1359. package/dist/content-list-query-CbPdzsna.d.mts.map +0 -1
  1360. package/dist/content-policy-CM4skrNi.mjs +0 -46
  1361. package/dist/content-policy-CM4skrNi.mjs.map +0 -1
  1362. package/dist/content-refresh-BHLHJgyt.mjs +0 -3599
  1363. package/dist/content-refresh-BHLHJgyt.mjs.map +0 -1
  1364. package/dist/content-repair-BVll5WNl.mjs +0 -440
  1365. package/dist/content-repair-BVll5WNl.mjs.map +0 -1
  1366. package/dist/context-B195kjqZ.mjs +0 -1835
  1367. package/dist/context-B195kjqZ.mjs.map +0 -1
  1368. package/dist/cron-BcR3RN4K.mjs +0 -282
  1369. package/dist/cron-BcR3RN4K.mjs.map +0 -1
  1370. package/dist/dashboard-CZD4G7k_.mjs +0 -151
  1371. package/dist/dashboard-CZD4G7k_.mjs.map +0 -1
  1372. package/dist/default-BtlxWgh9.mjs +0 -81
  1373. package/dist/default-BtlxWgh9.mjs.map +0 -1
  1374. package/dist/deferred-tasks-Ba9fphlE.d.mts +0 -9
  1375. package/dist/deferred-tasks-Ba9fphlE.d.mts.map +0 -1
  1376. package/dist/device-flow-3E5l0fpH.mjs +0 -466
  1377. package/dist/device-flow-3E5l0fpH.mjs.map +0 -1
  1378. package/dist/dialect-helpers-CtYdsX4W.mjs +0 -307
  1379. package/dist/dialect-helpers-CtYdsX4W.mjs.map +0 -1
  1380. package/dist/direct-executor-ZI8E01Yt.mjs +0 -104
  1381. package/dist/direct-executor-ZI8E01Yt.mjs.map +0 -1
  1382. package/dist/dist-2hDLsLeq.mjs +0 -810
  1383. package/dist/dist-2hDLsLeq.mjs.map +0 -1
  1384. package/dist/email-console-gc-E3Eqj.mjs +0 -50
  1385. package/dist/email-console-gc-E3Eqj.mjs.map +0 -1
  1386. package/dist/emdash-runtime-DZGinqTL.mjs +0 -4041
  1387. package/dist/emdash-runtime-DZGinqTL.mjs.map +0 -1
  1388. package/dist/enrich-C2Mk0lwE.mjs +0 -194
  1389. package/dist/enrich-C2Mk0lwE.mjs.map +0 -1
  1390. package/dist/entry-lock-D3C99MXS.mjs +0 -1520
  1391. package/dist/entry-lock-D3C99MXS.mjs.map +0 -1
  1392. package/dist/entry-lock-D9rnj2Lu.mjs +0 -163
  1393. package/dist/entry-lock-D9rnj2Lu.mjs.map +0 -1
  1394. package/dist/entry-lock-DX7QkqEg.d.mts +0 -3503
  1395. package/dist/entry-lock-DX7QkqEg.d.mts.map +0 -1
  1396. package/dist/entry-locks-CO_KaoU5.mjs +0 -167
  1397. package/dist/entry-locks-CO_KaoU5.mjs.map +0 -1
  1398. package/dist/error-CCTCskie.mjs +0 -90
  1399. package/dist/error-CCTCskie.mjs.map +0 -1
  1400. package/dist/errors-W3PA8HqB.mjs +0 -447
  1401. package/dist/errors-W3PA8HqB.mjs.map +0 -1
  1402. package/dist/escape-DN4Qmjsl.mjs +0 -9
  1403. package/dist/escape-DN4Qmjsl.mjs.map +0 -1
  1404. package/dist/field-defs-cache-B337z8UJ.mjs +0 -77
  1405. package/dist/field-defs-cache-B337z8UJ.mjs.map +0 -1
  1406. package/dist/fts-manager-DMb0sbIQ.mjs +0 -413
  1407. package/dist/fts-manager-DMb0sbIQ.mjs.map +0 -1
  1408. package/dist/hash-CLVFzWvx.mjs +0 -37
  1409. package/dist/hash-CLVFzWvx.mjs.map +0 -1
  1410. package/dist/hook-result-KY1jq21o.d.mts +0 -31
  1411. package/dist/hook-result-KY1jq21o.d.mts.map +0 -1
  1412. package/dist/hreflang-BgS2FqhH.d.mts +0 -50
  1413. package/dist/hreflang-BgS2FqhH.d.mts.map +0 -1
  1414. package/dist/hreflang-CrJwqoeb.mjs +0 -105
  1415. package/dist/hreflang-CrJwqoeb.mjs.map +0 -1
  1416. package/dist/http-route-dispatch-DAKlanXb.mjs +0 -339
  1417. package/dist/http-route-dispatch-DAKlanXb.mjs.map +0 -1
  1418. package/dist/identity-DRGSHEEr.mjs +0 -35
  1419. package/dist/identity-DRGSHEEr.mjs.map +0 -1
  1420. package/dist/import-DFr5B_hC.mjs +0 -2020
  1421. package/dist/import-DFr5B_hC.mjs.map +0 -1
  1422. package/dist/index-CDwKDkTq.d.mts +0 -623
  1423. package/dist/index-CDwKDkTq.d.mts.map +0 -1
  1424. package/dist/install-finalization-5Os-QZ-2.mjs +0 -58
  1425. package/dist/install-finalization-5Os-QZ-2.mjs.map +0 -1
  1426. package/dist/lifecycle-7e-Lt_kX.mjs +0 -25
  1427. package/dist/lifecycle-7e-Lt_kX.mjs.map +0 -1
  1428. package/dist/load-Cu5bvCZh.mjs +0 -28
  1429. package/dist/load-Cu5bvCZh.mjs.map +0 -1
  1430. package/dist/loader-t4TxaFiQ.mjs +0 -1004
  1431. package/dist/loader-t4TxaFiQ.mjs.map +0 -1
  1432. package/dist/manifest-builder-D31UoJCu.mjs +0 -189
  1433. package/dist/manifest-builder-D31UoJCu.mjs.map +0 -1
  1434. package/dist/manifest-schema-Bkh8SeHd.mjs +0 -491
  1435. package/dist/manifest-schema-Bkh8SeHd.mjs.map +0 -1
  1436. package/dist/media-TBlKCwo7.mjs +0 -334
  1437. package/dist/media-TBlKCwo7.mjs.map +0 -1
  1438. package/dist/media-allowlist-s60eAIf6.mjs +0 -44
  1439. package/dist/media-allowlist-s60eAIf6.mjs.map +0 -1
  1440. package/dist/media-folders-ClIRLUQv.mjs +0 -210
  1441. package/dist/media-folders-ClIRLUQv.mjs.map +0 -1
  1442. package/dist/media-url-DY_ijO3B.mjs +0 -43
  1443. package/dist/media-url-DY_ijO3B.mjs.map +0 -1
  1444. package/dist/media-usage-C4NolTpQ.mjs +0 -1833
  1445. package/dist/media-usage-C4NolTpQ.mjs.map +0 -1
  1446. package/dist/media-usage-activation-CAhmb14O.mjs +0 -78
  1447. package/dist/media-usage-activation-CAhmb14O.mjs.map +0 -1
  1448. package/dist/media-usage-work-Cu5DqewW.mjs +0 -586
  1449. package/dist/media-usage-work-Cu5DqewW.mjs.map +0 -1
  1450. package/dist/media-usage-work-DJr3rOEN.mjs +0 -152
  1451. package/dist/media-usage-work-DJr3rOEN.mjs.map +0 -1
  1452. package/dist/media-usage-write-fence-DW5oHT7q.mjs +0 -42
  1453. package/dist/media-usage-write-fence-DW5oHT7q.mjs.map +0 -1
  1454. package/dist/menus-DbgU9z5Q.mjs +0 -3732
  1455. package/dist/menus-DbgU9z5Q.mjs.map +0 -1
  1456. package/dist/menus-Wy8m5NcH.mjs +0 -753
  1457. package/dist/menus-Wy8m5NcH.mjs.map +0 -1
  1458. package/dist/mime-CCYRC4r1.mjs +0 -36
  1459. package/dist/mime-CCYRC4r1.mjs.map +0 -1
  1460. package/dist/mode-CkmOhFqj.mjs +0 -23
  1461. package/dist/mode-CkmOhFqj.mjs.map +0 -1
  1462. package/dist/node-sqlite-compat-vpVaILnb.mjs +0 -46
  1463. package/dist/node-sqlite-compat-vpVaILnb.mjs.map +0 -1
  1464. package/dist/normalize-dChXQjwS.mjs +0 -191
  1465. package/dist/normalize-dChXQjwS.mjs.map +0 -1
  1466. package/dist/oauth-authorization-DZ-R-hmx.mjs +0 -274
  1467. package/dist/oauth-authorization-DZ-R-hmx.mjs.map +0 -1
  1468. package/dist/oauth-clients-BMPA8vjl.mjs +0 -266
  1469. package/dist/oauth-clients-BMPA8vjl.mjs.map +0 -1
  1470. package/dist/oauth-state-store-0gIdb48-.mjs +0 -50
  1471. package/dist/oauth-state-store-0gIdb48-.mjs.map +0 -1
  1472. package/dist/oauth-user-lookup-ChVW4UWu.mjs +0 -26
  1473. package/dist/oauth-user-lookup-ChVW4UWu.mjs.map +0 -1
  1474. package/dist/object-cache-DlSVbn5Y.mjs +0 -534
  1475. package/dist/object-cache-DlSVbn5Y.mjs.map +0 -1
  1476. package/dist/options-D5WcorD5.mjs +0 -200
  1477. package/dist/options-D5WcorD5.mjs.map +0 -1
  1478. package/dist/options-DAknChnw.d.mts +0 -77
  1479. package/dist/options-DAknChnw.d.mts.map +0 -1
  1480. package/dist/parse-Cr379Av9.mjs +0 -81
  1481. package/dist/parse-Cr379Av9.mjs.map +0 -1
  1482. package/dist/parse-tj9XLNO_.d.mts +0 -165
  1483. package/dist/parse-tj9XLNO_.d.mts.map +0 -1
  1484. package/dist/passkey-config-BD-wMaCf.mjs +0 -46
  1485. package/dist/passkey-config-BD-wMaCf.mjs.map +0 -1
  1486. package/dist/patterns-Do3iqi7N.mjs +0 -145
  1487. package/dist/patterns-Do3iqi7N.mjs.map +0 -1
  1488. package/dist/placeholder-CubPFn_L.d.mts +0 -306
  1489. package/dist/placeholder-CubPFn_L.d.mts.map +0 -1
  1490. package/dist/plugins-DHurtdXQ.mjs +0 -277
  1491. package/dist/plugins-DHurtdXQ.mjs.map +0 -1
  1492. package/dist/policy-lyZnjpwz.mjs +0 -45
  1493. package/dist/policy-lyZnjpwz.mjs.map +0 -1
  1494. package/dist/portable-text-oAwodbPS.mjs +0 -286
  1495. package/dist/portable-text-oAwodbPS.mjs.map +0 -1
  1496. package/dist/preview-Cup2bj8c.mjs +0 -107
  1497. package/dist/preview-Cup2bj8c.mjs.map +0 -1
  1498. package/dist/protocol-C1CNiKX4.d.mts +0 -61
  1499. package/dist/protocol-C1CNiKX4.d.mts.map +0 -1
  1500. package/dist/public-submission-Cs-pFMQK.mjs +0 -245
  1501. package/dist/public-submission-Cs-pFMQK.mjs.map +0 -1
  1502. package/dist/public-url-BWVHsfQv.mjs +0 -101
  1503. package/dist/public-url-BWVHsfQv.mjs.map +0 -1
  1504. package/dist/query-D-lxiOgn.mjs +0 -890
  1505. package/dist/query-D-lxiOgn.mjs.map +0 -1
  1506. package/dist/query-D7kHKOws.mjs +0 -41
  1507. package/dist/query-D7kHKOws.mjs.map +0 -1
  1508. package/dist/rate-limit-CqJn-w4h.mjs +0 -120
  1509. package/dist/rate-limit-CqJn-w4h.mjs.map +0 -1
  1510. package/dist/redirect-BrChFPIu.mjs +0 -17
  1511. package/dist/redirect-BrChFPIu.mjs.map +0 -1
  1512. package/dist/redirect-D2Yo-b6S.mjs +0 -415
  1513. package/dist/redirect-D2Yo-b6S.mjs.map +0 -1
  1514. package/dist/redirects-BCiTaIQ-.mjs +0 -146
  1515. package/dist/redirects-BCiTaIQ-.mjs.map +0 -1
  1516. package/dist/redirects-ByItliuu.mjs +0 -644
  1517. package/dist/redirects-ByItliuu.mjs.map +0 -1
  1518. package/dist/registry-CkuPoZs7.mjs +0 -1222
  1519. package/dist/registry-CkuPoZs7.mjs.map +0 -1
  1520. package/dist/relation-XTAtOpJO.mjs +0 -334
  1521. package/dist/relation-XTAtOpJO.mjs.map +0 -1
  1522. package/dist/relations-B4w-G5U9.mjs +0 -309
  1523. package/dist/relations-B4w-G5U9.mjs.map +0 -1
  1524. package/dist/relations-O63JWBRj.mjs +0 -403
  1525. package/dist/relations-O63JWBRj.mjs.map +0 -1
  1526. package/dist/request-cache-DaMhSQdx.mjs +0 -104
  1527. package/dist/request-cache-DaMhSQdx.mjs.map +0 -1
  1528. package/dist/request-meta-C8yl_sIs.mjs +0 -141
  1529. package/dist/request-meta-C8yl_sIs.mjs.map +0 -1
  1530. package/dist/resolve-BJIS8wKr.mjs +0 -199
  1531. package/dist/resolve-BJIS8wKr.mjs.map +0 -1
  1532. package/dist/route-wire-D66L2oaZ.mjs +0 -197
  1533. package/dist/route-wire-D66L2oaZ.mjs.map +0 -1
  1534. package/dist/routes-DYsTe-oR.mjs +0 -321
  1535. package/dist/routes-DYsTe-oR.mjs.map +0 -1
  1536. package/dist/runner-3gfxRsJG.d.mts +0 -68
  1537. package/dist/runner-3gfxRsJG.d.mts.map +0 -1
  1538. package/dist/runner-ClrleR8A.mjs +0 -5502
  1539. package/dist/runner-ClrleR8A.mjs.map +0 -1
  1540. package/dist/schema-CPhPM9Do.mjs +0 -420
  1541. package/dist/schema-CPhPM9Do.mjs.map +0 -1
  1542. package/dist/schema-DEERRrKS.mjs +0 -9
  1543. package/dist/schema-DEERRrKS.mjs.map +0 -1
  1544. package/dist/scopes-Bn8_rCSt.mjs +0 -27
  1545. package/dist/scopes-Bn8_rCSt.mjs.map +0 -1
  1546. package/dist/search-lami_Ota.mjs +0 -448
  1547. package/dist/search-lami_Ota.mjs.map +0 -1
  1548. package/dist/secrets-DNgVgz_e.mjs +0 -341
  1549. package/dist/secrets-DNgVgz_e.mjs.map +0 -1
  1550. package/dist/sections-BIpd4Cxf.mjs +0 -346
  1551. package/dist/sections-BIpd4Cxf.mjs.map +0 -1
  1552. package/dist/seo-BtU70b7l.mjs +0 -114
  1553. package/dist/seo-BtU70b7l.mjs.map +0 -1
  1554. package/dist/seo-_4qmazxz.mjs +0 -133
  1555. package/dist/seo-_4qmazxz.mjs.map +0 -1
  1556. package/dist/service-Bpy5iTX0.mjs +0 -150
  1557. package/dist/service-Bpy5iTX0.mjs.map +0 -1
  1558. package/dist/session-user-BQeS2Mrb.mjs +0 -51
  1559. package/dist/session-user-BQeS2Mrb.mjs.map +0 -1
  1560. package/dist/settings-DmroR53o.mjs +0 -420
  1561. package/dist/settings-DmroR53o.mjs.map +0 -1
  1562. package/dist/settings-g2F2a3Ub.mjs +0 -51
  1563. package/dist/settings-g2F2a3Ub.mjs.map +0 -1
  1564. package/dist/setup-complete-BrZaXpVV.mjs +0 -26
  1565. package/dist/setup-complete-BrZaXpVV.mjs.map +0 -1
  1566. package/dist/setup-nonce-Cb9-884O.mjs +0 -25
  1567. package/dist/setup-nonce-Cb9-884O.mjs.map +0 -1
  1568. package/dist/site-url-DjhYCaep.mjs +0 -16
  1569. package/dist/site-url-DjhYCaep.mjs.map +0 -1
  1570. package/dist/snapshot-C32Xuv3o.mjs +0 -233
  1571. package/dist/snapshot-C32Xuv3o.mjs.map +0 -1
  1572. package/dist/ssrf-Co1GGFZz.mjs +0 -406
  1573. package/dist/ssrf-Co1GGFZz.mjs.map +0 -1
  1574. package/dist/state-C7gCoiHr.mjs +0 -153
  1575. package/dist/state-C7gCoiHr.mjs.map +0 -1
  1576. package/dist/status-DLoBmcDz.mjs +0 -30
  1577. package/dist/status-DLoBmcDz.mjs.map +0 -1
  1578. package/dist/storage-query-CPctdMKX.d.mts +0 -1519
  1579. package/dist/storage-query-CPctdMKX.d.mts.map +0 -1
  1580. package/dist/target-CUKlGrXb.mjs +0 -27
  1581. package/dist/target-CUKlGrXb.mjs.map +0 -1
  1582. package/dist/taxonomies-BpqpXJUE.mjs +0 -707
  1583. package/dist/taxonomies-BpqpXJUE.mjs.map +0 -1
  1584. package/dist/taxonomies-ClCo4IGO.mjs +0 -899
  1585. package/dist/taxonomies-ClCo4IGO.mjs.map +0 -1
  1586. package/dist/taxonomy-Dj1vpw5E.mjs +0 -508
  1587. package/dist/taxonomy-Dj1vpw5E.mjs.map +0 -1
  1588. package/dist/tokens-Ns02GAma.mjs +0 -171
  1589. package/dist/tokens-Ns02GAma.mjs.map +0 -1
  1590. package/dist/transport-B8P1xsqk.mjs +0 -136
  1591. package/dist/transport-B8P1xsqk.mjs.map +0 -1
  1592. package/dist/transport-BLHZZcD_.d.mts +0 -42
  1593. package/dist/transport-BLHZZcD_.d.mts.map +0 -1
  1594. package/dist/trusted-proxy-Cg7SuD15.mjs +0 -51
  1595. package/dist/trusted-proxy-Cg7SuD15.mjs.map +0 -1
  1596. package/dist/turnstile-DWCSh_Qc.mjs +0 -53
  1597. package/dist/turnstile-DWCSh_Qc.mjs.map +0 -1
  1598. package/dist/types-B3RkqvDW.mjs +0 -156
  1599. package/dist/types-B3RkqvDW.mjs.map +0 -1
  1600. package/dist/types-BtuppOTV.mjs +0 -16
  1601. package/dist/types-BtuppOTV.mjs.map +0 -1
  1602. package/dist/types-CCR3ivXE.d.mts +0 -363
  1603. package/dist/types-CCR3ivXE.d.mts.map +0 -1
  1604. package/dist/types-CSHgU_-_.mjs +0 -18
  1605. package/dist/types-CSHgU_-_.mjs.map +0 -1
  1606. package/dist/types-CbwqGOXm.d.mts +0 -766
  1607. package/dist/types-CbwqGOXm.d.mts.map +0 -1
  1608. package/dist/types-D4RGIc2I.d.mts +0 -198
  1609. package/dist/types-D4RGIc2I.d.mts.map +0 -1
  1610. package/dist/types-DEHidMEk.d.mts +0 -1637
  1611. package/dist/types-DEHidMEk.d.mts.map +0 -1
  1612. package/dist/types-DQeU2SR7.d.mts +0 -279
  1613. package/dist/types-DQeU2SR7.d.mts.map +0 -1
  1614. package/dist/types-DxQWoQLA.d.mts +0 -264
  1615. package/dist/types-DxQWoQLA.d.mts.map +0 -1
  1616. package/dist/types-FBBnGZ2Y.d.mts +0 -198
  1617. package/dist/types-FBBnGZ2Y.d.mts.map +0 -1
  1618. package/dist/types-ZCnlropY.mjs +0 -69
  1619. package/dist/types-ZCnlropY.mjs.map +0 -1
  1620. package/dist/types-_Qtyawiy.d.mts +0 -124
  1621. package/dist/types-_Qtyawiy.d.mts.map +0 -1
  1622. package/dist/types-gC6FAoa_.d.mts +0 -75
  1623. package/dist/types-gC6FAoa_.d.mts.map +0 -1
  1624. package/dist/types-k--LneIF.mjs +0 -22
  1625. package/dist/types-k--LneIF.mjs.map +0 -1
  1626. package/dist/upload-attempts-2xdnoKGe.mjs +0 -27
  1627. package/dist/upload-attempts-2xdnoKGe.mjs.map +0 -1
  1628. package/dist/url-pattern-3m3reMzr.mjs +0 -33
  1629. package/dist/url-pattern-3m3reMzr.mjs.map +0 -1
  1630. package/dist/user-DroRntkx.mjs +0 -171
  1631. package/dist/user-DroRntkx.mjs.map +0 -1
  1632. package/dist/utils-CC2oNGV8.mjs +0 -435
  1633. package/dist/utils-CC2oNGV8.mjs.map +0 -1
  1634. package/dist/validate-BCP9HNDn.mjs +0 -384
  1635. package/dist/validate-BCP9HNDn.mjs.map +0 -1
  1636. package/dist/validate-CyQ7I3xF.d.mts +0 -410
  1637. package/dist/validate-CyQ7I3xF.d.mts.map +0 -1
  1638. package/dist/validation-DBy6WF94.mjs +0 -216
  1639. package/dist/validation-DBy6WF94.mjs.map +0 -1
  1640. package/dist/version-B57E4Uo7.mjs +0 -7
  1641. package/dist/version-B57E4Uo7.mjs.map +0 -1
  1642. package/dist/widgets-wQ7Xcgq8.mjs +0 -126
  1643. package/dist/widgets-wQ7Xcgq8.mjs.map +0 -1
  1644. package/dist/wxr-taxonomies-DIHfvEDt.mjs +0 -406
  1645. package/dist/wxr-taxonomies-DIHfvEDt.mjs.map +0 -1
  1646. package/dist/wxr-taxonomies-DU2ZSiGc.d.mts +0 -34
  1647. package/dist/wxr-taxonomies-DU2ZSiGc.d.mts.map +0 -1
  1648. package/dist/zod-generator-D_yHIWfs.mjs +0 -329
  1649. package/dist/zod-generator-D_yHIWfs.mjs.map +0 -1
  1650. /package/dist/{ssrf-BRpTDMV6.mjs → ssrf-DpBH7aOt.mjs} +0 -0
@@ -1,3599 +0,0 @@
1
- import { t as validateIdentifier } from "./validate-DANX1-Qi.mjs";
2
- import { l as isPostgres, v as tableExists } from "./dialect-helpers-CtYdsX4W.mjs";
3
- import { i as isI18nEnabled } from "./config-CGVeT8Hc.mjs";
4
- import { getRequestContext } from "./request-context.mjs";
5
- import { n as chunks, t as SQL_BATCH_SIZE } from "./chunks-BmWyou79.mjs";
6
- import { i as InvalidCursorError, o as decodeCursor, s as encodeCursor } from "./types-BkUg2yHx.mjs";
7
- import { t as withTransaction } from "./transaction-BcJO4Tmc.mjs";
8
- import { t as INTERNAL_MEDIA_PREFIX } from "./normalize-dChXQjwS.mjs";
9
- import { n as normalizeMime } from "./mime-CCYRC4r1.mjs";
10
- import { n as MediaUsageActivationVersionMismatchError, t as MEDIA_USAGE_ACTIVATION_RUNTIME_GENERATION } from "./activation-CaMfdBt1.mjs";
11
- import { sql } from "kysely";
12
- import { ulid } from "ulidx";
13
-
14
- //#region src/media/usage/projection-fingerprint.ts
15
- const MEDIA_USAGE_PROJECTION_FINGERPRINT_VERSION = 1;
16
- const FINGERPRINT_PREFIX = `media-usage-projection:v${MEDIA_USAGE_PROJECTION_FINGERPRINT_VERSION}:sha256:`;
17
- const FINGERPRINT_PATTERN = new RegExp(`^${FINGERPRINT_PREFIX}[a-f0-9]{64}$`);
18
- async function buildMediaUsageProjectionFingerprint(input) {
19
- if (!input.collectionId) throw new Error("Media usage projection fingerprints require a collection identity");
20
- const canonicalOccurrences = input.occurrences.map((occurrence) => ({
21
- fieldSlug: occurrence.fieldSlug,
22
- fieldPath: occurrence.fieldPath,
23
- occurrenceIndex: occurrence.occurrenceIndex ?? 0,
24
- referenceType: occurrence.referenceType,
25
- mediaId: occurrence.mediaId,
26
- provider: occurrence.provider,
27
- providerAssetId: occurrence.providerAssetId,
28
- mediaKind: occurrence.mediaKind ?? null,
29
- mimeType: occurrence.mimeType ?? null
30
- })).map((occurrence) => ({
31
- occurrence,
32
- key: canonicalJson(occurrence)
33
- })).toSorted((a, b) => compareCanonicalStrings(a.key, b.key)).map(({ occurrence }) => occurrence);
34
- return buildCanonicalSha256Fingerprint(FINGERPRINT_PREFIX, {
35
- fingerprintVersion: MEDIA_USAGE_PROJECTION_FINGERPRINT_VERSION,
36
- collectionId: input.collectionId,
37
- extractionSchema: normalizeExtractionFields(input.extractionFields),
38
- source: {
39
- sourceKey: input.source.sourceKey,
40
- sourceType: input.source.sourceType,
41
- collectionSlug: input.source.collectionSlug ?? null,
42
- contentId: input.source.contentId ?? null,
43
- sourceVariant: input.source.sourceVariant,
44
- locale: input.source.locale ?? null,
45
- translationGroup: input.source.translationGroup ?? null,
46
- contentSlug: input.source.contentSlug ?? null,
47
- contentTitle: input.source.contentTitle ?? null,
48
- contentStatus: input.source.contentStatus ?? null,
49
- contentScheduledAt: input.source.contentScheduledAt ?? null,
50
- contentDeletedAt: input.source.contentDeletedAt ?? null,
51
- revisionId: input.source.revisionId ?? null,
52
- schemaVersion: input.source.schemaVersion ?? 1,
53
- sourceCompleteness: input.source.sourceCompleteness ?? "complete"
54
- },
55
- occurrences: canonicalOccurrences
56
- });
57
- }
58
- async function buildCanonicalSha256Fingerprint(prefix, payload) {
59
- const encodedPayload = new TextEncoder().encode(canonicalJson(payload));
60
- const digest = await crypto.subtle.digest("SHA-256", encodedPayload);
61
- return {
62
- fingerprint: `${prefix}${Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")}`,
63
- byteLength: encodedPayload.byteLength
64
- };
65
- }
66
- function normalizeExtractionFields(fields) {
67
- return fields.map((field) => {
68
- if (field.type !== "repeater") return {
69
- slug: field.slug,
70
- type: field.type
71
- };
72
- return {
73
- slug: field.slug,
74
- type: field.type,
75
- subFields: (field.validation?.subFields ?? []).map((subField) => ({
76
- slug: subField.slug,
77
- type: subField.type
78
- })).toSorted((a, b) => compareCanonicalStrings(a.slug, b.slug))
79
- };
80
- }).toSorted((a, b) => compareCanonicalStrings(String(a.slug), String(b.slug)));
81
- }
82
- function compareCanonicalStrings(a, b) {
83
- return a < b ? -1 : a > b ? 1 : 0;
84
- }
85
- function isMediaUsageProjectionFingerprint(value) {
86
- return typeof value === "string" && FINGERPRINT_PATTERN.test(value);
87
- }
88
- function canonicalJson(value) {
89
- return JSON.stringify(canonicalize(value));
90
- }
91
- function canonicalize(value) {
92
- if (value === void 0) return null;
93
- if (typeof value === "bigint") return value.toString();
94
- if (typeof value === "number") return Number.isFinite(value) ? value : null;
95
- if (Array.isArray(value)) return value.map((item) => canonicalize(item));
96
- if (!isRecord$3(value)) return value;
97
- const canonical = {};
98
- for (const key of Object.keys(value).toSorted()) canonical[key] = canonicalize(value[key]);
99
- return canonical;
100
- }
101
- function isRecord$3(value) {
102
- return typeof value === "object" && value !== null && !Array.isArray(value);
103
- }
104
-
105
- //#endregion
106
- //#region src/media/usage/types.ts
107
- const CONTENT_SOURCE_SCHEMA_VERSION = 2;
108
-
109
- //#endregion
110
- //#region src/database/json-recordset.ts
111
- function jsonTextValues(db, values) {
112
- const payload = JSON.stringify([...new Set(values)]);
113
- return isPostgres(db) ? sql`SELECT value::text AS value FROM jsonb_array_elements_text(${payload}::jsonb) AS value` : sql`SELECT value AS value FROM json_each(${payload})`;
114
- }
115
-
116
- //#endregion
117
- //#region src/database/repositories/media-usage.ts
118
- const OCCURRENCE_BIND_COLUMNS = 13;
119
- const D1_MAX_BOUND_PARAMETERS = 100;
120
- const MEDIA_USAGE_GENERATION_WRITE_LEASE_MS = 3600 * 1e3;
121
- const OCCURRENCE_INSERT_BATCH_SIZE = Math.max(1, Math.floor(D1_MAX_BOUND_PARAMETERS / OCCURRENCE_BIND_COLUMNS));
122
- function cleanupDeleteBatchSize(cleanupLease) {
123
- return cleanupLease ? SQL_BATCH_SIZE - 3 : SQL_BATCH_SIZE;
124
- }
125
- function canIssueCleanupStatement(canIssueStatement) {
126
- return canIssueStatement?.() ?? true;
127
- }
128
- function nullableRefMatch(left, right) {
129
- const leftRef = sql.ref(left);
130
- const rightRef = sql.ref(right);
131
- return sql`(
132
- (${leftRef} IS NULL AND ${rightRef} IS NULL)
133
- OR ${leftRef} = ${rightRef}
134
- )`;
135
- }
136
- const MAX_JSON_BIND_BYTES = 19e5;
137
- const MEDIA_USAGE_EVENT_QUERY_CEILING = 900;
138
- const MEDIA_USAGE_QUERY_RESERVE = 150;
139
- function chunkJsonRows(rows) {
140
- const batches = [];
141
- let batch = [];
142
- let batchBytes = 2;
143
- for (const row of rows) {
144
- const rowBytes = new TextEncoder().encode(JSON.stringify(row)).byteLength + (batch.length > 0 ? 1 : 0);
145
- if (batch.length > 0 && batchBytes + rowBytes > MAX_JSON_BIND_BYTES) {
146
- batches.push(batch);
147
- batch = [];
148
- batchBytes = 2;
149
- }
150
- batch.push(row);
151
- batchBytes += rowBytes;
152
- }
153
- if (batch.length > 0) batches.push(batch);
154
- return batches;
155
- }
156
- function mediaUsageQueryBudgetAllows(queries) {
157
- const metrics = getRequestContext()?.metrics;
158
- return !metrics || metrics.dbCount + queries + MEDIA_USAGE_QUERY_RESERVE <= MEDIA_USAGE_EVENT_QUERY_CEILING;
159
- }
160
- function mergeInto(target, source) {
161
- for (const value of source) target.add(value);
162
- }
163
- function batchOccurrenceRows(prepared, now) {
164
- return prepared.flatMap((item) => item.projection.occurrences.map((occurrence) => ({
165
- id: ulid(),
166
- source_key: item.row.source_key,
167
- generation: item.generation,
168
- field_slug: occurrence.fieldSlug,
169
- field_path: occurrence.fieldPath,
170
- occurrence_index: occurrence.occurrenceIndex ?? 0,
171
- reference_type: occurrence.referenceType,
172
- media_id: occurrence.mediaId,
173
- provider: occurrence.provider,
174
- provider_asset_id: occurrence.providerAssetId,
175
- media_kind: occurrence.mediaKind ?? null,
176
- mime_type: occurrence.mimeType ?? null,
177
- created_at: now,
178
- lease_token: item.leaseToken
179
- })));
180
- }
181
- function cleanupDurationSeconds(value) {
182
- if (!Number.isSafeInteger(value) || value < 0) throw new Error("Media usage cleanup duration must be a non-negative whole number of seconds");
183
- return value;
184
- }
185
- const CONTENT_SOURCE_ELIGIBILITY = sql`(
186
- s.source_variant = 'draft_overlay'
187
- OR (
188
- s.source_variant = 'columns'
189
- AND (
190
- s.content_status = 'published'
191
- OR NOT EXISTS (
192
- SELECT 1
193
- FROM _emdash_media_usage_sources AS overlay
194
- WHERE overlay.source_type = 'content'
195
- AND overlay.collection_slug = s.collection_slug
196
- AND overlay.content_id = s.content_id
197
- AND overlay.source_variant = 'draft_overlay'
198
- AND ${contentSourceMatchesActiveCollection("overlay", "s.collection_id")}
199
- )
200
- )
201
- )
202
- )`;
203
- function contentSourceMatchesActiveCollection(source, currentCollectionId) {
204
- return sql`(
205
- NOT EXISTS (
206
- SELECT 1
207
- FROM _emdash_media_usage_activation AS activation
208
- WHERE activation.task_key = 'incremental_capture'
209
- AND activation.state = 'active'
210
- )
211
- OR (
212
- ${sql.ref(`${source}.collection_id`)} = ${sql.ref(currentCollectionId)}
213
- AND ${sql.ref(`${source}.identity_version`)} = 1
214
- )
215
- )`;
216
- }
217
- /** Persistence-only repository for the internal media usage projection tables. */
218
- var MediaUsageRepository = class {
219
- constructor(db) {
220
- this.db = db;
221
- }
222
- async replaceSource(source, occurrences) {
223
- const generation = ulid();
224
- if (!await this.withGenerationWriteLease(source, generation, async (leaseToken, now) => {
225
- await withTransaction(this.db, async (trx) => {
226
- if (!await this.lockCanonicalSourceCollection(trx, source)) throw new Error(`Media usage collection is no longer current for ${source.sourceKey}`);
227
- await this.insertOccurrences(trx, source.sourceKey, generation, occurrences, now);
228
- if (!await this.upsertSource(trx, source, generation, now, leaseToken)) throw new Error(`Media usage generation lease expired for ${source.sourceKey}`);
229
- });
230
- })) throw new Error(`Media usage collection is no longer current for ${source.sourceKey}`);
231
- const replaced = await this.findSource(source.sourceKey);
232
- if (!replaced) throw new Error(`Media usage source ${source.sourceKey} was not persisted`);
233
- return replaced;
234
- }
235
- async replaceSourceIfCurrent(source, occurrences, expectedCurrentGeneration) {
236
- if (expectedCurrentGeneration !== null && await this.projectionMatchesCurrentGeneration(source, expectedCurrentGeneration)) return {
237
- replaced: false,
238
- unchanged: true,
239
- source: null
240
- };
241
- const generation = ulid();
242
- let replaced = false;
243
- await this.withGenerationWriteLease(source, generation, async (leaseToken, now) => {
244
- const row = this.buildSourceRow(source, generation, now);
245
- await withTransaction(this.db, async (trx) => {
246
- if (!await this.lockCanonicalSourceCollection(trx, source)) return;
247
- await this.insertOccurrences(trx, source.sourceKey, generation, occurrences, now);
248
- if (expectedCurrentGeneration === null) {
249
- replaced = await this.insertSourceIfAbsent(trx, row, leaseToken);
250
- return;
251
- }
252
- replaced = await this.updateSourceIfGeneration(trx, row, expectedCurrentGeneration, leaseToken);
253
- });
254
- });
255
- return {
256
- replaced,
257
- unchanged: false,
258
- source: replaced ? null : await this.findSource(source.sourceKey)
259
- };
260
- }
261
- async findSource(sourceKey) {
262
- const row = await this.db.selectFrom("_emdash_media_usage_sources").selectAll().where("source_key", "=", sourceKey).executeTakeFirst();
263
- return row ? rowToSource(row) : null;
264
- }
265
- async findSources(sourceKeys) {
266
- const uniqueSourceKeys = [...new Set(sourceKeys)];
267
- const sources = /* @__PURE__ */ new Map();
268
- if (uniqueSourceKeys.length === 0) return sources;
269
- for (const sourceKeyBatch of chunkJsonRows(uniqueSourceKeys)) {
270
- const result = await sql`
271
- WITH requested AS (${jsonTextValues(this.db, sourceKeyBatch)})
272
- SELECT source.*
273
- FROM _emdash_media_usage_sources AS source
274
- INNER JOIN requested ON requested.value = source.source_key
275
- `.execute(this.db);
276
- for (const row of result.rows) {
277
- const source = rowToSource(row);
278
- sources.set(source.sourceKey, source);
279
- }
280
- }
281
- return sources;
282
- }
283
- async measureSourceGenerationDeletion(sourceKey, generation, maxOccurrences) {
284
- if (!Number.isSafeInteger(maxOccurrences) || maxOccurrences < 0) throw new Error("Media usage deletion measurement requires a non-negative row limit");
285
- const payload = sql`
286
- COALESCE(field_slug, '') || COALESCE(field_path, '') ||
287
- COALESCE(reference_type, '') || COALESCE(media_id, '') ||
288
- COALESCE(provider, '') || COALESCE(provider_asset_id, '') ||
289
- COALESCE(media_kind, '') || COALESCE(mime_type, '')
290
- `;
291
- const occurrenceBytes = isPostgres(this.db) ? sql`octet_length(${payload})` : sql`length(CAST(${payload} AS BLOB))`;
292
- const rows = await this.db.selectFrom("_emdash_media_usage").select(occurrenceBytes.as("occurrence_bytes")).where("source_key", "=", sourceKey).where("generation", "=", generation).limit(maxOccurrences + 1).execute();
293
- return {
294
- occurrenceCount: rows.length,
295
- occurrenceBytes: rows.reduce((total, row) => total + Number(row.occurrence_bytes), 0),
296
- exceedsOccurrenceLimit: rows.length > maxOccurrences
297
- };
298
- }
299
- async replaceSourceIfMatching(source, occurrences, expectedSource) {
300
- if (expectedSource !== null && await this.projectionMatchesExpectedSource(source, expectedSource)) return {
301
- replaced: false,
302
- unchanged: true,
303
- source: null
304
- };
305
- const generation = ulid();
306
- let replaced = false;
307
- await this.withGenerationWriteLease(source, generation, async (leaseToken, now) => {
308
- const row = this.buildSourceRow(source, generation, now);
309
- await withTransaction(this.db, async (trx) => {
310
- if (!await this.lockCanonicalSourceCollection(trx, source)) return;
311
- await this.insertOccurrences(trx, source.sourceKey, generation, occurrences, now);
312
- if (expectedSource === null) {
313
- replaced = await this.insertSourceIfAbsent(trx, row, leaseToken);
314
- return;
315
- }
316
- replaced = await this.updateSourceIfMatching(trx, row, expectedSource, leaseToken);
317
- });
318
- });
319
- return {
320
- replaced,
321
- unchanged: false,
322
- source: replaced ? null : await this.findSource(source.sourceKey)
323
- };
324
- }
325
- async replaceNewSourcesBatch(projections) {
326
- const unique = [...new Map(projections.map((projection) => [projection.source.sourceKey, projection])).values()];
327
- if (unique.length === 0) return /* @__PURE__ */ new Set();
328
- const collectionSlug = unique[0]?.source.collectionSlug;
329
- const collectionId = unique[0]?.source.collectionId;
330
- if (!collectionSlug || !collectionId) throw new Error("Canonical media usage batch requires collection identity");
331
- validateIdentifier(collectionSlug, "collection slug");
332
- if (unique.some((projection) => projection.source.collectionSlug !== collectionSlug || projection.source.collectionId !== collectionId)) throw new Error("Canonical media usage batch must contain one collection");
333
- const now = (/* @__PURE__ */ new Date()).toISOString();
334
- const expiresAt = new Date(Date.now() + MEDIA_USAGE_GENERATION_WRITE_LEASE_MS).toISOString();
335
- const prepared = unique.map((projection) => {
336
- const generation = ulid();
337
- return {
338
- projection,
339
- generation,
340
- leaseToken: ulid(),
341
- row: this.buildSourceRow(projection.source, generation, now)
342
- };
343
- });
344
- const sourceRows = prepared.map((item) => ({
345
- ...item.row,
346
- lease_token: item.leaseToken
347
- }));
348
- const occurrenceRows = batchOccurrenceRows(prepared, now);
349
- if (!mediaUsageQueryBudgetAllows(2 + chunkJsonRows(occurrenceRows).length + chunkJsonRows(sourceRows).length)) {
350
- if (unique.length === 1) return /* @__PURE__ */ new Set();
351
- const midpoint = Math.ceil(unique.length / 2);
352
- const inserted = /* @__PURE__ */ new Set();
353
- mergeInto(inserted, await this.replaceNewSourcesBatch(unique.slice(0, midpoint)));
354
- mergeInto(inserted, await this.replaceNewSourcesBatch(unique.slice(midpoint)));
355
- return inserted;
356
- }
357
- const leasesPayload = JSON.stringify(prepared.map((item) => ({
358
- source_key: item.row.source_key,
359
- collection_id: item.row.collection_id,
360
- collection_slug: item.row.collection_slug,
361
- generation: item.generation,
362
- lease_token: item.leaseToken,
363
- expires_at: expiresAt,
364
- created_at: now
365
- })));
366
- const leases = this.generationWriteBatchInput(leasesPayload);
367
- await sql`
368
- WITH input AS (${leases})
369
- INSERT INTO _emdash_media_usage_generation_writes (
370
- source_key, generation, lease_token, expires_at, created_at
371
- )
372
- SELECT source_key, generation, lease_token, expires_at, created_at
373
- FROM input
374
- WHERE EXISTS (
375
- SELECT 1
376
- FROM _emdash_collections AS collection
377
- INNER JOIN _emdash_media_usage_index_status AS status
378
- ON status.collection_id = collection.id
379
- AND status.scope_key = collection.slug
380
- WHERE collection.id = input.collection_id
381
- AND collection.slug = input.collection_slug
382
- AND status.adapter_id = 'content-media'
383
- AND status.scope_type = 'collection'
384
- AND status.capture_state = 'active'
385
- AND NOT EXISTS (
386
- SELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion
387
- WHERE deletion.collection_id = input.collection_id
388
- )
389
- )
390
- `.execute(this.db);
391
- try {
392
- await this.insertBatchOccurrences(occurrenceRows);
393
- const tableName = `ec_${collectionSlug}`;
394
- validateIdentifier(tableName, "content table");
395
- const inserted = /* @__PURE__ */ new Set();
396
- for (const sourceRowBatch of chunkJsonRows(sourceRows)) {
397
- const result = await sql`
398
- WITH input AS (${this.sourceBatchInput(JSON.stringify(sourceRowBatch))})
399
- INSERT INTO _emdash_media_usage_sources (
400
- source_key, source_type, collection_id, collection_slug, content_id,
401
- source_variant, locale, translation_group, content_slug, content_title,
402
- content_status, content_scheduled_at, content_deleted_at, revision_id,
403
- current_generation, schema_version, source_updated_at, source_version,
404
- source_fingerprint, identity_version, source_completeness,
405
- last_attempted_at, last_error_code, indexed_at, updated_at
406
- )
407
- SELECT
408
- input.source_key, input.source_type, input.collection_id, input.collection_slug,
409
- input.content_id, input.source_variant, input.locale, input.translation_group,
410
- input.content_slug, input.content_title, input.content_status,
411
- input.content_scheduled_at, input.content_deleted_at, input.revision_id,
412
- input.current_generation, input.schema_version, input.source_updated_at,
413
- input.source_version, input.source_fingerprint, input.identity_version,
414
- input.source_completeness, input.last_attempted_at, input.last_error_code,
415
- input.indexed_at, input.updated_at
416
- FROM input
417
- WHERE EXISTS (
418
- SELECT 1
419
- FROM _emdash_media_usage_generation_writes AS writer
420
- WHERE writer.source_key = input.source_key
421
- AND writer.generation = input.current_generation
422
- AND writer.lease_token = input.lease_token
423
- AND ${this.generationWriteLeaseExpiryIsInFuture("writer.expires_at")}
424
- )
425
- AND EXISTS (
426
- SELECT 1
427
- FROM _emdash_collections AS collection
428
- INNER JOIN _emdash_media_usage_index_status AS status
429
- ON status.collection_id = collection.id
430
- AND status.scope_key = collection.slug
431
- WHERE collection.id = input.collection_id
432
- AND collection.slug = input.collection_slug
433
- AND status.adapter_id = 'content-media'
434
- AND status.scope_type = 'collection'
435
- AND status.capture_state = 'active'
436
- AND NOT EXISTS (
437
- SELECT 1
438
- FROM _emdash_media_usage_collection_deletions AS deletion
439
- WHERE deletion.collection_id = input.collection_id
440
- )
441
- )
442
- AND EXISTS (
443
- SELECT 1
444
- FROM ${sql.ref(tableName)} AS content
445
- WHERE content.id = input.content_id
446
- AND content.version = input.source_version
447
- AND content.updated_at = input.source_updated_at
448
- AND (
449
- (input.source_variant = 'columns' AND ${nullableRefMatch("content.live_revision_id", "input.revision_id")})
450
- OR
451
- (input.source_variant = 'draft_overlay' AND ${nullableRefMatch("content.draft_revision_id", "input.revision_id")})
452
- )
453
- )
454
- ON CONFLICT (source_key) DO NOTHING
455
- RETURNING source_key
456
- `.execute(this.db);
457
- for (const row of result.rows) inserted.add(row.source_key);
458
- }
459
- return inserted;
460
- } finally {
461
- await sql`
462
- WITH input AS (${leases})
463
- DELETE FROM _emdash_media_usage_generation_writes AS writer
464
- WHERE EXISTS (
465
- SELECT 1
466
- FROM input
467
- WHERE input.source_key = writer.source_key
468
- AND input.generation = writer.generation
469
- AND input.lease_token = writer.lease_token
470
- )
471
- `.execute(this.db);
472
- }
473
- }
474
- async replaceExistingSourcesBatch(projections) {
475
- const unique = [...new Map(projections.map((projection) => [projection.source.sourceKey, projection])).values()];
476
- if (unique.length === 0) return /* @__PURE__ */ new Set();
477
- const collectionSlug = unique[0]?.source.collectionSlug;
478
- const collectionId = unique[0]?.source.collectionId;
479
- if (!collectionSlug || !collectionId) throw new Error("Canonical media usage batch requires collection identity");
480
- validateIdentifier(collectionSlug, "collection slug");
481
- if (unique.some((projection) => projection.source.collectionSlug !== collectionSlug || projection.source.collectionId !== collectionId)) throw new Error("Canonical media usage batch must contain one collection");
482
- const now = (/* @__PURE__ */ new Date()).toISOString();
483
- const expiresAt = new Date(Date.now() + MEDIA_USAGE_GENERATION_WRITE_LEASE_MS).toISOString();
484
- const prepared = unique.map((projection) => {
485
- const generation = ulid();
486
- return {
487
- projection,
488
- generation,
489
- leaseToken: ulid(),
490
- row: this.buildSourceRow(projection.source, generation, now)
491
- };
492
- });
493
- const sourceRows = prepared.map((item) => ({
494
- ...item.row,
495
- lease_token: item.leaseToken,
496
- expected_generation: item.projection.expectedSource.currentGeneration,
497
- expected_collection_id: item.projection.expectedSource.collectionId,
498
- expected_updated_at: item.projection.expectedSource.updatedAt,
499
- expected_source_fingerprint: item.projection.expectedSource.sourceFingerprint,
500
- expected_source_updated_at: item.projection.expectedSource.sourceUpdatedAt,
501
- expected_source_version: item.projection.expectedSource.sourceVersion,
502
- expected_identity_version: item.projection.expectedSource.identityVersion,
503
- expected_revision_id: item.projection.expectedSource.revisionId,
504
- expected_source_completeness: item.projection.expectedSource.sourceCompleteness,
505
- expected_last_attempted_at: item.projection.expectedSource.lastAttemptedAt,
506
- expected_last_error_code: item.projection.expectedSource.lastErrorCode
507
- }));
508
- const occurrenceRows = batchOccurrenceRows(prepared, now);
509
- if (!mediaUsageQueryBudgetAllows(2 + chunkJsonRows(occurrenceRows).length + chunkJsonRows(sourceRows).length)) {
510
- if (unique.length === 1) return /* @__PURE__ */ new Set();
511
- const midpoint = Math.ceil(unique.length / 2);
512
- const replaced = /* @__PURE__ */ new Set();
513
- mergeInto(replaced, await this.replaceExistingSourcesBatch(unique.slice(0, midpoint)));
514
- mergeInto(replaced, await this.replaceExistingSourcesBatch(unique.slice(midpoint)));
515
- return replaced;
516
- }
517
- const leasesPayload = JSON.stringify(prepared.map((item) => ({
518
- source_key: item.row.source_key,
519
- collection_id: item.row.collection_id,
520
- collection_slug: item.row.collection_slug,
521
- generation: item.generation,
522
- lease_token: item.leaseToken,
523
- expires_at: expiresAt,
524
- created_at: now
525
- })));
526
- const leases = this.generationWriteBatchInput(leasesPayload);
527
- await sql`
528
- WITH input AS (${leases})
529
- INSERT INTO _emdash_media_usage_generation_writes (
530
- source_key, generation, lease_token, expires_at, created_at
531
- )
532
- SELECT source_key, generation, lease_token, expires_at, created_at
533
- FROM input
534
- WHERE EXISTS (
535
- SELECT 1
536
- FROM _emdash_collections AS collection
537
- INNER JOIN _emdash_media_usage_index_status AS status
538
- ON status.collection_id = collection.id
539
- AND status.scope_key = collection.slug
540
- WHERE collection.id = input.collection_id
541
- AND collection.slug = input.collection_slug
542
- AND status.adapter_id = 'content-media'
543
- AND status.scope_type = 'collection'
544
- AND status.capture_state = 'active'
545
- AND NOT EXISTS (
546
- SELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion
547
- WHERE deletion.collection_id = input.collection_id
548
- )
549
- )
550
- `.execute(this.db);
551
- try {
552
- await this.insertBatchOccurrences(occurrenceRows);
553
- const tableName = `ec_${collectionSlug}`;
554
- validateIdentifier(tableName, "content table");
555
- const replaced = /* @__PURE__ */ new Set();
556
- for (const sourceRowBatch of chunkJsonRows(sourceRows)) {
557
- const result = await sql`
558
- WITH input AS (${this.sourceBatchInput(JSON.stringify(sourceRowBatch))})
559
- UPDATE _emdash_media_usage_sources AS source
560
- SET source_type = input.source_type,
561
- collection_id = input.collection_id,
562
- collection_slug = input.collection_slug,
563
- content_id = input.content_id,
564
- source_variant = input.source_variant,
565
- locale = input.locale,
566
- translation_group = input.translation_group,
567
- content_slug = input.content_slug,
568
- content_title = input.content_title,
569
- content_status = input.content_status,
570
- content_scheduled_at = input.content_scheduled_at,
571
- content_deleted_at = input.content_deleted_at,
572
- revision_id = input.revision_id,
573
- current_generation = input.current_generation,
574
- schema_version = input.schema_version,
575
- source_updated_at = input.source_updated_at,
576
- source_version = input.source_version,
577
- source_fingerprint = input.source_fingerprint,
578
- identity_version = input.identity_version,
579
- source_completeness = input.source_completeness,
580
- last_attempted_at = input.last_attempted_at,
581
- last_error_code = input.last_error_code,
582
- indexed_at = input.indexed_at,
583
- updated_at = input.updated_at
584
- FROM input
585
- WHERE source.source_key = input.source_key
586
- AND source.current_generation = input.expected_generation
587
- AND ${this.batchRefsMatch("source.collection_id", "input.expected_collection_id")}
588
- AND source.updated_at = input.expected_updated_at
589
- AND ${this.batchRefsMatch("source.source_fingerprint", "input.expected_source_fingerprint")}
590
- AND ${this.batchRefsMatch("source.source_updated_at", "input.expected_source_updated_at")}
591
- AND ${this.batchRefsMatch("source.source_version", "input.expected_source_version")}
592
- AND ${this.batchRefsMatch("source.identity_version", "input.expected_identity_version")}
593
- AND ${this.batchRefsMatch("source.revision_id", "input.expected_revision_id")}
594
- AND source.source_completeness = input.expected_source_completeness
595
- AND ${this.batchRefsMatch("source.last_attempted_at", "input.expected_last_attempted_at")}
596
- AND ${this.batchRefsMatch("source.last_error_code", "input.expected_last_error_code")}
597
- AND EXISTS (
598
- SELECT 1
599
- FROM _emdash_media_usage_generation_writes AS writer
600
- WHERE writer.source_key = input.source_key
601
- AND writer.generation = input.current_generation
602
- AND writer.lease_token = input.lease_token
603
- AND ${this.generationWriteLeaseExpiryIsInFuture("writer.expires_at")}
604
- )
605
- AND EXISTS (
606
- SELECT 1
607
- FROM _emdash_collections AS collection
608
- INNER JOIN _emdash_media_usage_index_status AS status
609
- ON status.collection_id = collection.id
610
- AND status.scope_key = collection.slug
611
- WHERE collection.id = input.collection_id
612
- AND collection.slug = input.collection_slug
613
- AND status.adapter_id = 'content-media'
614
- AND status.scope_type = 'collection'
615
- AND status.capture_state = 'active'
616
- AND NOT EXISTS (
617
- SELECT 1
618
- FROM _emdash_media_usage_collection_deletions AS deletion
619
- WHERE deletion.collection_id = input.collection_id
620
- )
621
- )
622
- AND EXISTS (
623
- SELECT 1
624
- FROM ${sql.ref(tableName)} AS content
625
- WHERE content.id = input.content_id
626
- AND content.version = input.source_version
627
- AND content.updated_at = input.source_updated_at
628
- AND (
629
- (input.source_variant = 'columns' AND ${nullableRefMatch("content.live_revision_id", "input.revision_id")})
630
- OR
631
- (input.source_variant = 'draft_overlay' AND ${nullableRefMatch("content.draft_revision_id", "input.revision_id")})
632
- )
633
- )
634
- RETURNING ${isPostgres(this.db) ? sql`source.source_key` : sql`source_key`} AS source_key
635
- `.execute(this.db);
636
- for (const row of result.rows) replaced.add(row.source_key);
637
- }
638
- return replaced;
639
- } finally {
640
- await sql`
641
- WITH input AS (${leases})
642
- DELETE FROM _emdash_media_usage_generation_writes AS writer
643
- WHERE EXISTS (
644
- SELECT 1
645
- FROM input
646
- WHERE input.source_key = writer.source_key
647
- AND input.generation = writer.generation
648
- AND input.lease_token = writer.lease_token
649
- )
650
- `.execute(this.db);
651
- }
652
- }
653
- async matchingExistingSourcesBatch(projections) {
654
- if (projections.length === 0) return /* @__PURE__ */ new Set();
655
- const now = (/* @__PURE__ */ new Date()).toISOString();
656
- const rows = projections.map((projection) => ({
657
- ...this.buildSourceRow(projection.source, projection.expectedSource.currentGeneration, now),
658
- expected_generation: projection.expectedSource.currentGeneration,
659
- expected_collection_id: projection.expectedSource.collectionId,
660
- expected_updated_at: projection.expectedSource.updatedAt,
661
- expected_source_fingerprint: projection.expectedSource.sourceFingerprint,
662
- expected_source_updated_at: projection.expectedSource.sourceUpdatedAt,
663
- expected_source_version: projection.expectedSource.sourceVersion,
664
- expected_identity_version: projection.expectedSource.identityVersion,
665
- expected_revision_id: projection.expectedSource.revisionId,
666
- expected_source_completeness: projection.expectedSource.sourceCompleteness,
667
- expected_last_attempted_at: projection.expectedSource.lastAttemptedAt,
668
- expected_last_error_code: projection.expectedSource.lastErrorCode
669
- }));
670
- if (!mediaUsageQueryBudgetAllows(chunkJsonRows(rows).length)) {
671
- if (projections.length === 1) return /* @__PURE__ */ new Set();
672
- const midpoint = Math.ceil(projections.length / 2);
673
- const matched = /* @__PURE__ */ new Set();
674
- mergeInto(matched, await this.matchingExistingSourcesBatch(projections.slice(0, midpoint)));
675
- mergeInto(matched, await this.matchingExistingSourcesBatch(projections.slice(midpoint)));
676
- return matched;
677
- }
678
- const matched = /* @__PURE__ */ new Set();
679
- for (const batch of chunkJsonRows(rows)) {
680
- const result = await sql`
681
- WITH input AS (${this.sourceBatchInput(JSON.stringify(batch))})
682
- SELECT source.source_key
683
- FROM _emdash_media_usage_sources AS source
684
- INNER JOIN input ON input.source_key = source.source_key
685
- WHERE source.current_generation = input.expected_generation
686
- AND ${this.batchRefsMatch("source.collection_id", "input.expected_collection_id")}
687
- AND source.updated_at = input.expected_updated_at
688
- AND ${this.batchRefsMatch("source.source_fingerprint", "input.expected_source_fingerprint")}
689
- AND ${this.batchRefsMatch("source.source_updated_at", "input.expected_source_updated_at")}
690
- AND ${this.batchRefsMatch("source.source_version", "input.expected_source_version")}
691
- AND ${this.batchRefsMatch("source.identity_version", "input.expected_identity_version")}
692
- AND ${this.batchRefsMatch("source.revision_id", "input.expected_revision_id")}
693
- AND source.source_completeness = input.expected_source_completeness
694
- AND ${this.batchRefsMatch("source.last_attempted_at", "input.expected_last_attempted_at")}
695
- AND ${this.batchRefsMatch("source.last_error_code", "input.expected_last_error_code")}
696
- AND EXISTS (
697
- SELECT 1
698
- FROM _emdash_collections AS collection
699
- WHERE collection.id = source.collection_id
700
- AND collection.slug = source.collection_slug
701
- )
702
- `.execute(this.db);
703
- for (const row of result.rows) matched.add(row.source_key);
704
- }
705
- return matched;
706
- }
707
- async markSourceAttempted(source) {
708
- if (source.collectionId !== void 0 && source.collectionId !== null) {
709
- const expectedSource = await this.findSource(source.sourceKey);
710
- if (!(await this.markSourceAttemptedIfMatching(source, expectedSource)).attempted) throw new Error(`Canonical media usage source ${source.sourceKey} is no longer current`);
711
- const attempted = await this.findSource(source.sourceKey);
712
- if (!attempted) throw new Error(`Media usage source ${source.sourceKey} was not persisted`);
713
- return attempted;
714
- }
715
- const generation = ulid();
716
- await this.withGenerationWriteLease(source, generation, async (leaseToken, now) => {
717
- const row = this.buildAttemptedSourceRow(source, generation, now);
718
- const updates = this.attemptedSourceUpdateSet(source, row);
719
- if (((await this.db.insertInto("_emdash_media_usage_sources").values(row).onConflict((oc) => oc.column("source_key").doUpdateSet(updates)).executeTakeFirst()).numInsertedOrUpdatedRows ?? 0n) <= 0n) throw new Error(`Media usage generation lease expired for ${source.sourceKey}`);
720
- });
721
- const attempted = await this.findSource(source.sourceKey);
722
- if (!attempted) throw new Error(`Media usage source ${source.sourceKey} was not persisted`);
723
- return attempted;
724
- }
725
- async markSourceAttemptedIfMatching(source, expectedSource) {
726
- const generation = ulid();
727
- let attempted = false;
728
- if (expectedSource === null) await this.withGenerationWriteLease(source, generation, async (leaseToken, now) => {
729
- const row = this.buildAttemptedSourceRow(source, generation, now);
730
- await withTransaction(this.db, async (trx) => {
731
- if (!await this.lockCanonicalSourceCollection(trx, source)) return;
732
- attempted = await this.persistSourceIfWriteLease(trx, row, leaseToken, sql`ON CONFLICT (source_key) DO NOTHING`);
733
- });
734
- });
735
- else {
736
- const row = this.buildAttemptedSourceRow(source, generation, (/* @__PURE__ */ new Date()).toISOString());
737
- await withTransaction(this.db, async (trx) => {
738
- if (!await this.lockCanonicalSourceCollection(trx, source)) return;
739
- attempted = await this.updateAttemptedSourceIfMatching(trx, source, row, expectedSource);
740
- });
741
- }
742
- return {
743
- attempted,
744
- source: attempted ? null : await this.findSource(source.sourceKey)
745
- };
746
- }
747
- async findActiveEntryCountsByMediaIds(mediaIds) {
748
- const uniqueMediaIds = [...new Set(mediaIds)];
749
- const counts = new Map(uniqueMediaIds.map((mediaId) => [mediaId, 0]));
750
- for (const mediaIdBatch of chunks(uniqueMediaIds, SQL_BATCH_SIZE)) {
751
- const visibleEntries = this.currentContentMediaUsageBaseQuery().select([
752
- "u.media_id as media_id",
753
- "s.collection_slug as collection_slug",
754
- "s.content_id as content_id"
755
- ]).where("u.media_id", "in", mediaIdBatch).where((eb) => eb.not(eb.exists(eb.selectFrom("_emdash_media_usage_sources as deleted_source").select("deleted_source.source_key").where("deleted_source.source_type", "=", "content").whereRef("deleted_source.collection_slug", "=", "s.collection_slug").whereRef("deleted_source.content_id", "=", "s.content_id").where("deleted_source.source_variant", "in", ["columns", "draft_overlay"]).where(contentSourceMatchesActiveCollection("deleted_source", "collection.id")).where("deleted_source.content_deleted_at", "is not", null)))).distinct().as("visible_entries");
756
- const rows = await this.db.selectFrom(visibleEntries).select("media_id").select((eb) => eb.fn.countAll().as("usage_count")).groupBy("media_id").execute();
757
- for (const row of rows) if (row.media_id !== null) counts.set(row.media_id, Number(row.usage_count));
758
- }
759
- return counts;
760
- }
761
- async findCollectionIndexStatusScopes(identity) {
762
- return (await this.db.selectFrom("_emdash_collections as collection").leftJoin("_emdash_media_usage_index_status as status", (join) => join.on("status.adapter_id", "=", identity.adapterId).on("status.scope_type", "=", identity.scopeType).onRef("status.scope_key", "=", "collection.slug")).select([
763
- "collection.slug as collection_slug",
764
- "status.status as status",
765
- "status.schema_version as schema_version",
766
- "status.reconciliation_required as reconciliation_required"
767
- ]).orderBy("collection.slug", "asc").execute()).map((row) => ({
768
- collectionSlug: row.collection_slug,
769
- status: row.status,
770
- schemaVersion: row.schema_version === null ? null : Number(row.schema_version),
771
- reconciliationRequired: row.reconciliation_required !== null && Number(row.reconciliation_required) !== 0
772
- }));
773
- }
774
- async findCollectionProgress() {
775
- const row = (await sql`
776
- WITH collection_progress AS (
777
- SELECT
778
- CASE WHEN status.status = 'complete'
779
- AND status.schema_version = ${CONTENT_SOURCE_SCHEMA_VERSION}
780
- AND status.reconciliation_required = 0
781
- AND status.capture_state = 'active'
782
- AND NOT EXISTS (
783
- SELECT 1 FROM _emdash_media_usage_work AS work
784
- WHERE work.collection_id = collection.id
785
- AND work.collection_slug = collection.slug
786
- )
787
- THEN 1 ELSE 0 END AS is_ready,
788
- CASE WHEN status.collection_id IS NULL
789
- OR COALESCE(status.capture_state, '') <> 'active'
790
- OR status.status NOT IN ('complete', 'never', 'running', 'partial', 'failed', 'stale')
791
- OR status.status = 'failed'
792
- OR (
793
- status.reconciliation_required = 0
794
- AND (
795
- status.status <> 'complete'
796
- OR COALESCE(status.schema_version, -1) <> ${CONTENT_SOURCE_SCHEMA_VERSION}
797
- )
798
- AND NOT EXISTS (
799
- SELECT 1 FROM _emdash_media_usage_work AS work
800
- WHERE work.collection_id = collection.id
801
- AND work.collection_slug = collection.slug
802
- )
803
- )
804
- OR EXISTS (
805
- SELECT 1 FROM _emdash_media_usage_work AS work
806
- WHERE work.collection_id = collection.id
807
- AND work.collection_slug = collection.slug
808
- AND work.state = 'failed'
809
- )
810
- OR EXISTS (
811
- SELECT 1 FROM _emdash_media_usage_reconciliations AS reconciliation
812
- WHERE reconciliation.collection_id = collection.id
813
- AND reconciliation.collection_slug = collection.slug
814
- AND reconciliation.state = 'failed'
815
- AND status.reconciliation_required = 1
816
- AND (
817
- reconciliation.target_epoch IS NULL
818
- OR reconciliation.target_epoch >= status.change_epoch
819
- )
820
- )
821
- THEN 1 ELSE 0 END AS needs_attention
822
- FROM _emdash_collections AS collection
823
- LEFT JOIN _emdash_media_usage_index_status AS status
824
- ON status.adapter_id = 'content-media'
825
- AND status.scope_type = 'collection'
826
- AND status.collection_id = collection.id
827
- AND status.scope_key = collection.slug
828
- WHERE NOT EXISTS (
829
- SELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion
830
- WHERE deletion.collection_id = collection.id
831
- AND deletion.collection_slug = collection.slug
832
- )
833
- )
834
- SELECT
835
- (
836
- SELECT activation.runtime_generation
837
- FROM _emdash_media_usage_activation AS activation
838
- WHERE activation.task_key = 'incremental_capture'
839
- ) AS activation_generation,
840
- EXISTS (
841
- SELECT 1 FROM _emdash_media_usage_activation AS activation
842
- WHERE activation.task_key = 'incremental_capture'
843
- AND activation.state = 'active'
844
- ) AS activation_active,
845
- COUNT(*) AS total_collections,
846
- COALESCE(SUM(is_ready), 0) AS ready_collections,
847
- EXISTS (
848
- SELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion
849
- WHERE deletion.state IN ('pending', 'retry', 'leased')
850
- ) AS cleanup_pending,
851
- CASE WHEN COALESCE(MAX(needs_attention), 0) = 1 OR EXISTS (
852
- SELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion
853
- WHERE deletion.state = 'failed'
854
- ) THEN 1 ELSE 0 END AS needs_attention
855
- FROM collection_progress
856
- `.execute(this.db)).rows[0];
857
- if (!row) throw new Error("Media usage progress query returned no result");
858
- if (!row.activation_active) return null;
859
- if (Number(row.activation_generation) !== MEDIA_USAGE_ACTIVATION_RUNTIME_GENERATION) throw new MediaUsageActivationVersionMismatchError("Media usage activation runtime generation is incompatible");
860
- const readyCollections = Number(row.ready_collections);
861
- const totalCollections = Number(row.total_collections);
862
- const cleanupPending = Number(row.cleanup_pending);
863
- const needsAttention = Number(row.needs_attention);
864
- if (!Number.isSafeInteger(readyCollections) || !Number.isSafeInteger(totalCollections) || readyCollections < 0 || totalCollections < readyCollections || cleanupPending !== 0 && cleanupPending !== 1 || needsAttention !== 0 && needsAttention !== 1) throw new Error("Media usage progress query returned invalid counts");
865
- return {
866
- status: needsAttention ? "needs_attention" : readyCollections === totalCollections && cleanupPending === 0 ? "ready" : "indexing",
867
- readyCollections,
868
- totalCollections
869
- };
870
- }
871
- async findCurrentEntryUsagePageByMediaId(mediaId, options = {}) {
872
- const requestedLimit = Math.floor(options.limit ?? 50);
873
- const limit = Number.isFinite(requestedLimit) ? Math.min(Math.max(1, requestedLimit), 100) : 50;
874
- const cursor = options.cursor ? decodeCursor(options.cursor) : null;
875
- if (cursor && (cursor.orderValue.length === 0 || cursor.id.length === 0)) throw new InvalidCursorError(options.cursor ?? "");
876
- let matchedGroups = this.currentContentMediaUsageBaseQuery().select([
877
- "collection.id as collection_id",
878
- "s.collection_slug as collection_slug",
879
- "s.content_id as content_id"
880
- ]).where("u.media_id", "=", mediaId).distinct();
881
- if (cursor) matchedGroups = matchedGroups.where((eb) => eb.or([eb("s.collection_slug", ">", cursor.orderValue), eb.and([eb("s.collection_slug", "=", cursor.orderValue), eb("s.content_id", ">", cursor.id)])]));
882
- matchedGroups = matchedGroups.orderBy("s.collection_slug", "asc").orderBy("s.content_id", "asc").limit(limit + 1);
883
- const rows = await this.db.with("matched_groups", () => matchedGroups).with("page_groups", (db) => db.selectFrom("matched_groups").selectAll().orderBy("collection_slug", "asc").orderBy("content_id", "asc").limit(limit)).with("entry_state", (db) => db.selectFrom("page_groups as page").crossJoin("_emdash_media_usage_sources as state").select([
884
- "page.collection_id",
885
- "page.collection_slug",
886
- "page.content_id"
887
- ]).select((eb) => eb.fn.max("state.content_deleted_at").as("entry_deleted_at")).whereRef("page.collection_slug", "=", "state.collection_slug").whereRef("page.content_id", "=", "state.content_id").where("state.source_type", "=", "content").where("state.source_variant", "in", ["columns", "draft_overlay"]).where(contentSourceMatchesActiveCollection("state", "page.collection_id")).groupBy([
888
- "page.collection_id",
889
- "page.collection_slug",
890
- "page.content_id"
891
- ])).selectFrom("entry_state as page").crossJoin("_emdash_media_usage_sources as s").crossJoin("_emdash_media_usage as u").whereRef("page.collection_slug", "=", "s.collection_slug").whereRef("page.content_id", "=", "s.content_id").where(contentSourceMatchesActiveCollection("s", "page.collection_id")).whereRef("s.source_key", "=", "u.source_key").whereRef("s.current_generation", "=", "u.generation").select(currentUsageSelect).select("page.entry_deleted_at").select(sql`CASE
892
- WHEN (SELECT COUNT(*) FROM matched_groups) > ${limit} THEN 1
893
- ELSE 0
894
- END`.as("has_more")).where("u.media_id", "=", mediaId).where("s.source_type", "=", "content").where("s.collection_slug", "is not", null).where("s.content_id", "is not", null).where("s.source_variant", "in", ["columns", "draft_overlay"]).where(CONTENT_SOURCE_ELIGIBILITY).orderBy("s.collection_slug", "asc").orderBy("s.content_id", "asc").orderBy("s.source_variant", "asc").orderBy("s.source_key", "asc").orderBy("u.field_path", "asc").orderBy("u.occurrence_index", "asc").orderBy("u.id", "asc").execute();
895
- const items = groupUsageRows(rows);
896
- const result = { items };
897
- if (Number(rows[0]?.has_more ?? 0) === 1 && items.length > 0) {
898
- const last = items.at(-1);
899
- result.nextCursor = encodeCursor(last.collectionSlug, last.contentId);
900
- }
901
- return result;
902
- }
903
- async findCurrentUsageByMediaId(mediaId) {
904
- return (await this.db.selectFrom("_emdash_media_usage_sources as s").innerJoin("_emdash_media_usage as u", (join) => join.onRef("u.source_key", "=", "s.source_key").onRef("u.generation", "=", "s.current_generation")).select(currentUsageSelect).where("u.media_id", "=", mediaId).orderBy("s.source_key", "asc").orderBy("u.field_path", "asc").orderBy("u.occurrence_index", "asc").execute()).map(rowToUsageRecord);
905
- }
906
- async findCurrentUsageByProviderAsset(provider, providerAssetId) {
907
- return (await this.db.selectFrom("_emdash_media_usage_sources as s").innerJoin("_emdash_media_usage as u", (join) => join.onRef("u.source_key", "=", "s.source_key").onRef("u.generation", "=", "s.current_generation")).select(currentUsageSelect).where("u.provider", "=", provider).where("u.provider_asset_id", "=", providerAssetId).orderBy("s.source_key", "asc").orderBy("u.field_path", "asc").orderBy("u.occurrence_index", "asc").execute()).map(rowToUsageRecord);
908
- }
909
- async findCurrentUsagePageByMediaId(mediaId, options = {}) {
910
- return this.findCurrentUsagePage((query) => query.where("u.media_id", "=", mediaId), options);
911
- }
912
- async findCurrentUsagePageByProviderAsset(provider, providerAssetId, options = {}) {
913
- return this.findCurrentUsagePage((query) => query.where("u.provider", "=", provider).where("u.provider_asset_id", "=", providerAssetId), options);
914
- }
915
- async deleteSource(sourceKey) {
916
- return this.deleteSources([sourceKey]);
917
- }
918
- async deleteSourceIfCurrent(sourceKey, expectedCurrentGeneration) {
919
- let deleted = false;
920
- await withTransaction(this.db, async (trx) => {
921
- await this.lockCleanupBeforeSourceDelete(trx);
922
- const result = await trx.deleteFrom("_emdash_media_usage_sources").where("source_key", "=", sourceKey).where("current_generation", "=", expectedCurrentGeneration).executeTakeFirst();
923
- deleted = Number(result.numDeletedRows ?? 0) > 0;
924
- if (!deleted) return;
925
- await this.deleteSourceGenerationOccurrences(trx, sourceKey, expectedCurrentGeneration);
926
- });
927
- return {
928
- deleted,
929
- source: await this.findSource(sourceKey)
930
- };
931
- }
932
- async deleteSourceIfMatching(sourceKey, expectedSource) {
933
- let deleted = false;
934
- await withTransaction(this.db, async (trx) => {
935
- await this.lockCleanupBeforeSourceDelete(trx);
936
- const result = await trx.deleteFrom("_emdash_media_usage_sources").where("source_key", "=", sourceKey).where(this.sourceMatchExpression(expectedSource)).where(this.currentCollectionExists(expectedSource.collectionId, expectedSource.collectionSlug)).executeTakeFirst();
937
- deleted = Number(result.numDeletedRows ?? 0) > 0;
938
- if (!deleted) return;
939
- await this.deleteSourceGenerationOccurrences(trx, sourceKey, expectedSource.currentGeneration);
940
- });
941
- return {
942
- deleted,
943
- source: await this.findSource(sourceKey)
944
- };
945
- }
946
- async deleteSourceIfMatchingContentAbsent(sourceKey, expectedSource, collectionSlug, contentId) {
947
- validateIdentifier(collectionSlug, "collection slug");
948
- const tableName = `ec_${collectionSlug}`;
949
- let deleted = false;
950
- await withTransaction(this.db, async (trx) => {
951
- await this.lockCleanupBeforeSourceDelete(trx);
952
- const result = await trx.deleteFrom("_emdash_media_usage_sources").where("source_key", "=", sourceKey).where(this.sourceMatchExpression(expectedSource)).where(this.currentCollectionExists(expectedSource.collectionId, expectedSource.collectionSlug)).where(sql`NOT EXISTS (SELECT 1 FROM ${sql.ref(tableName)} WHERE id = ${contentId})`).executeTakeFirst();
953
- deleted = Number(result.numDeletedRows ?? 0) > 0;
954
- if (!deleted) return;
955
- await this.deleteSourceGenerationOccurrences(trx, sourceKey, expectedSource.currentGeneration);
956
- });
957
- const contentPresent = deleted ? false : await this.contentRowExists(tableName, contentId);
958
- return {
959
- deleted,
960
- contentPresent,
961
- source: deleted || contentPresent ? null : await this.findSource(sourceKey)
962
- };
963
- }
964
- async deleteSources(sourceKeys) {
965
- return this.deleteSourceKeys(sourceKeys);
966
- }
967
- async deleteContentSources(collectionSlug, contentId) {
968
- const sourceKeys = (await this.db.selectFrom("_emdash_media_usage_sources").select("source_key").where("source_type", "=", "content").where("collection_slug", "=", collectionSlug).where("content_id", "=", contentId).execute()).map((row) => row.source_key);
969
- return this.deleteSourceKeys(sourceKeys);
970
- }
971
- async deleteCollectionSources(collectionSlug) {
972
- let deleted = 0;
973
- while (true) {
974
- const sourceRows = await this.db.selectFrom("_emdash_media_usage_sources").select("source_key").where("source_type", "=", "content").where("collection_slug", "=", collectionSlug).orderBy("source_key", "asc").limit(SQL_BATCH_SIZE).execute();
975
- if (sourceRows.length === 0) break;
976
- deleted += await this.deleteSourceKeys(sourceRows.map((row) => row.source_key));
977
- }
978
- return deleted;
979
- }
980
- async findCollectionContentSources(collectionSlug, collectionId) {
981
- let query = this.db.selectFrom("_emdash_media_usage_sources").selectAll().where("source_type", "=", "content").where("collection_slug", "=", collectionSlug).orderBy("source_key", "asc");
982
- if (collectionId !== void 0) query = query.where("collection_id", "=", collectionId);
983
- return (await query.execute()).map((row) => rowToSource(row));
984
- }
985
- async claimMediaUsageCleanup(input) {
986
- const leaseDurationSeconds = cleanupDurationSeconds(input.leaseDurationSeconds);
987
- const nextEligibleDelaySeconds = cleanupDurationSeconds(input.nextEligibleDelaySeconds);
988
- const sweepSafetyWindowSeconds = cleanupDurationSeconds(input.sweepSafetyWindowSeconds);
989
- const claimedAt = this.cleanupTimestampOffset(0);
990
- const leaseExpiresAt = this.cleanupTimestampOffset(leaseDurationSeconds);
991
- const nextEligibleAt = this.cleanupTimestampOffset(nextEligibleDelaySeconds);
992
- const sweepBeforeAt = this.cleanupTimestampOffset(-sweepSafetyWindowSeconds);
993
- const row = await this.db.updateTable("_emdash_media_usage_cleanup").set({
994
- lease_token: input.leaseToken,
995
- lease_expires_at: leaseExpiresAt,
996
- next_eligible_at: nextEligibleAt,
997
- last_started_at: claimedAt,
998
- updated_at: claimedAt,
999
- scan_before_at: sql`CASE
1000
- WHEN scan_before_at IS NULL THEN ${sweepBeforeAt}
1001
- ELSE scan_before_at
1002
- END`
1003
- }).where("task_key", "=", "projection_gc").where(this.cleanupTimestampIsDue("next_eligible_at")).where((eb) => eb.or([eb("lease_token", "is", null), this.cleanupTimestampIsDue("lease_expires_at")])).returning([
1004
- "cursor_created_at",
1005
- "cursor_id",
1006
- "last_started_at",
1007
- "scan_before_at",
1008
- "consecutive_failures"
1009
- ]).executeTakeFirst();
1010
- if (!row) return null;
1011
- if (!row.last_started_at || !row.scan_before_at) throw new Error("Media usage cleanup claim did not persist its database timestamps");
1012
- return {
1013
- leaseToken: input.leaseToken,
1014
- cursor: row.cursor_created_at && row.cursor_id ? {
1015
- createdAt: row.cursor_created_at,
1016
- id: row.cursor_id
1017
- } : null,
1018
- claimedAt: row.last_started_at,
1019
- scanBeforeAt: row.scan_before_at,
1020
- consecutiveFailures: row.consecutive_failures
1021
- };
1022
- }
1023
- /**
1024
- * Scans for reclaimable occurrences, or resolves to `null` when the sweep was
1025
- * never issued because the lease is gone or the tick is out of budget. An
1026
- * empty array means the scan ran and found nothing, which lets the caller
1027
- * retire the sweep window; `null` must leave the cursor where it was.
1028
- */
1029
- async findMediaUsageCleanupCandidates(input) {
1030
- if (!await this.admitCleanupScan(input.cleanupLease, input.canIssueStatement)) return null;
1031
- let query = this.db.selectFrom("_emdash_media_usage as u").leftJoin("_emdash_media_usage_sources as s", "s.source_key", "u.source_key").leftJoin("_emdash_media_usage_generation_writes as writer", (join) => join.onRef("writer.source_key", "=", "u.source_key").onRef("writer.generation", "=", "u.generation")).select([
1032
- "u.id as id",
1033
- "u.source_key as source_key",
1034
- "u.generation as generation",
1035
- "u.created_at as created_at",
1036
- "s.current_generation as current_generation",
1037
- "s.indexed_at as indexed_at",
1038
- "writer.expires_at as write_lease_expires_at"
1039
- ]).where("u.created_at", "<", input.cutoff).orderBy("u.created_at", "asc").orderBy("u.id", "asc").limit(Math.max(0, Math.floor(input.limit)));
1040
- if (input.cursor) {
1041
- const cursor = input.cursor;
1042
- query = query.where((eb) => eb(eb.refTuple("u.created_at", "u.id"), ">", eb.tuple(cursor.createdAt, cursor.id)));
1043
- }
1044
- return (await query.execute()).map((row) => ({
1045
- id: row.id,
1046
- sourceKey: row.source_key,
1047
- generation: row.generation,
1048
- createdAt: row.created_at,
1049
- currentGeneration: row.current_generation,
1050
- indexedAt: row.indexed_at,
1051
- writeLeaseExpiresAt: row.write_lease_expires_at
1052
- }));
1053
- }
1054
- async completeMediaUsageCleanup(input) {
1055
- const updates = {
1056
- lease_token: null,
1057
- lease_expires_at: null,
1058
- cursor_created_at: input.sweepComplete ? null : input.nextCursor?.createdAt ?? null,
1059
- cursor_id: input.sweepComplete ? null : input.nextCursor?.id ?? null,
1060
- ...input.sweepComplete ? { scan_before_at: null } : {},
1061
- consecutive_failures: 0,
1062
- last_completed_at: this.cleanupTimestampOffset(0),
1063
- last_candidate_count: input.candidateCount,
1064
- last_deleted_orphans: input.deletedOrphans,
1065
- last_deleted_stale: input.deletedStale,
1066
- last_deleted_abandoned: input.deletedAbandoned,
1067
- last_deleted_write_leases: input.deletedWriteLeases,
1068
- last_backlog_lower_bound: input.backlogLowerBound,
1069
- last_scan_has_more: input.scanHasMore ? 1 : 0,
1070
- last_duration_ms: input.durationMs,
1071
- last_error_code: null,
1072
- updated_at: this.cleanupTimestampOffset(0)
1073
- };
1074
- const result = await this.db.updateTable("_emdash_media_usage_cleanup").set(updates).where("task_key", "=", "projection_gc").where("lease_token", "=", input.leaseToken).where(this.cleanupLeaseExpiryIsInFuture("_emdash_media_usage_cleanup.lease_expires_at")).executeTakeFirst();
1075
- return Number(result.numUpdatedRows ?? 0) > 0;
1076
- }
1077
- async failMediaUsageCleanup(input) {
1078
- const retryDelaySeconds = cleanupDurationSeconds(input.retryDelaySeconds);
1079
- const result = await this.db.updateTable("_emdash_media_usage_cleanup").set({
1080
- lease_token: null,
1081
- lease_expires_at: null,
1082
- next_eligible_at: this.cleanupTimestampOffset(retryDelaySeconds),
1083
- consecutive_failures: input.consecutiveFailures,
1084
- last_completed_at: this.cleanupTimestampOffset(0),
1085
- last_duration_ms: input.durationMs,
1086
- last_error_code: input.errorCode,
1087
- updated_at: this.cleanupTimestampOffset(0)
1088
- }).where("task_key", "=", "projection_gc").where("lease_token", "=", input.leaseToken).where(this.cleanupLeaseExpiryIsInFuture("_emdash_media_usage_cleanup.lease_expires_at")).executeTakeFirst();
1089
- return Number(result.numUpdatedRows ?? 0) > 0;
1090
- }
1091
- async deleteOrphanOccurrencesOlderThan(cutoff, limit, options = {}) {
1092
- const batchLimit = Math.floor(limit);
1093
- if (batchLimit <= 0) return 0;
1094
- if (options.candidateIds) return this.deleteOrphanCandidateIds(options.candidateIds.slice(0, batchLimit), cutoff, options.cleanupLease, options.canIssueStatement);
1095
- if (!await this.admitCleanupScan(options.cleanupLease, options.canIssueStatement)) return 0;
1096
- const rows = await this.db.selectFrom("_emdash_media_usage as u").leftJoin("_emdash_media_usage_sources as s", (join) => join.onRef("s.source_key", "=", "u.source_key")).leftJoin("_emdash_media_usage_generation_writes as writer", (join) => join.onRef("writer.source_key", "=", "u.source_key").onRef("writer.generation", "=", "u.generation")).select("u.id").where("s.source_key", "is", null).where("u.created_at", "<", cutoff).where(this.noActiveGenerationWriteExpression("u")).orderBy("u.created_at", "asc").orderBy("u.id", "asc").limit(batchLimit).execute();
1097
- return this.deleteOrphanCandidateIds(rows.map((row) => row.id), cutoff, options.cleanupLease, options.canIssueStatement);
1098
- }
1099
- async deleteStaleGenerationsOlderThan(cutoff, limit, options = {}) {
1100
- const batchLimit = Math.floor(limit);
1101
- if (batchLimit <= 0) return 0;
1102
- if (options.candidateIds) return this.deleteStaleCandidateIds(options.candidateIds.slice(0, batchLimit), cutoff, options.cleanupLease, options.canIssueStatement);
1103
- if (!await this.admitCleanupScan(options.cleanupLease, options.canIssueStatement)) return 0;
1104
- const rows = await this.db.selectFrom("_emdash_media_usage as u").innerJoin("_emdash_media_usage_sources as s", (join) => join.onRef("s.source_key", "=", "u.source_key")).leftJoin("_emdash_media_usage_generation_writes as writer", (join) => join.onRef("writer.source_key", "=", "u.source_key").onRef("writer.generation", "=", "u.generation")).select("u.id").where("u.created_at", "<", cutoff).whereRef("u.generation", "!=", "s.current_generation").whereRef("u.created_at", "<", "s.indexed_at").where(this.noActiveGenerationWriteExpression("u")).orderBy("u.created_at", "asc").orderBy("u.id", "asc").limit(batchLimit).execute();
1105
- return this.deleteStaleCandidateIds(rows.map((row) => row.id), cutoff, options.cleanupLease, options.canIssueStatement);
1106
- }
1107
- async deleteAbandonedGenerationsOlderThan(cutoff, limit, options = {}) {
1108
- const batchLimit = Math.floor(limit);
1109
- if (batchLimit <= 0) return 0;
1110
- if (options.candidateIds) return this.deleteAbandonedCandidateIds(options.candidateIds.slice(0, batchLimit), cutoff, options.cleanupLease, options.canIssueStatement);
1111
- if (!await this.admitCleanupScan(options.cleanupLease, options.canIssueStatement)) return 0;
1112
- const rows = await this.db.selectFrom("_emdash_media_usage as u").innerJoin("_emdash_media_usage_sources as s", (join) => join.onRef("s.source_key", "=", "u.source_key")).leftJoin("_emdash_media_usage_generation_writes as writer", (join) => join.onRef("writer.source_key", "=", "u.source_key").onRef("writer.generation", "=", "u.generation")).select("u.id").where("u.created_at", "<", cutoff).whereRef("u.generation", "!=", "s.current_generation").whereRef("u.created_at", ">=", "s.indexed_at").where(this.noActiveGenerationWriteExpression("u")).orderBy("u.created_at", "asc").orderBy("u.id", "asc").limit(batchLimit).execute();
1113
- return this.deleteAbandonedCandidateIds(rows.map((row) => row.id), cutoff, options.cleanupLease, options.canIssueStatement);
1114
- }
1115
- async deleteExpiredGenerationWriteLeases(limit, cleanupLease, canIssueStatement) {
1116
- const batchLimit = Math.floor(limit);
1117
- if (batchLimit <= 0) return 0;
1118
- if (!await this.admitCleanupScan(cleanupLease, canIssueStatement)) return 0;
1119
- const rows = await this.db.selectFrom("_emdash_media_usage_generation_writes").select("lease_token").where(this.generationWriteLeaseHasExpired("expires_at")).orderBy("expires_at", "asc").orderBy("lease_token", "asc").limit(batchLimit).execute();
1120
- if (rows.length === 0 || !canIssueCleanupStatement(canIssueStatement)) return 0;
1121
- let deleteQuery = this.db.deleteFrom("_emdash_media_usage_generation_writes").where("lease_token", "in", rows.map((row) => row.lease_token)).where(this.generationWriteLeaseHasExpired("expires_at"));
1122
- if (cleanupLease) deleteQuery = deleteQuery.where(this.activeCleanupLeaseExpression(cleanupLease));
1123
- const result = await deleteQuery.executeTakeFirst();
1124
- return Number(result.numDeletedRows ?? 0);
1125
- }
1126
- async upsertIndexStatus(input) {
1127
- const now = input.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
1128
- const row = {
1129
- adapter_id: input.adapterId,
1130
- scope_type: input.scopeType,
1131
- scope_key: input.scopeKey,
1132
- status: input.status,
1133
- schema_version: input.schemaVersion ?? 1,
1134
- started_at: input.startedAt ?? null,
1135
- completed_at: input.completedAt ?? null,
1136
- cursor: input.cursor ?? null,
1137
- indexed_source_count: input.indexedSourceCount ?? 0,
1138
- failed_source_count: input.failedSourceCount ?? 0,
1139
- last_error_code: input.lastErrorCode ?? null,
1140
- updated_at: now
1141
- };
1142
- await this.db.insertInto("_emdash_media_usage_index_status").values(row).onConflict((oc) => oc.columns([
1143
- "adapter_id",
1144
- "scope_type",
1145
- "scope_key"
1146
- ]).doUpdateSet({
1147
- status: row.status,
1148
- schema_version: row.schema_version,
1149
- started_at: row.started_at,
1150
- completed_at: row.completed_at,
1151
- cursor: row.cursor,
1152
- indexed_source_count: row.indexed_source_count,
1153
- failed_source_count: row.failed_source_count,
1154
- last_error_code: row.last_error_code,
1155
- updated_at: row.updated_at
1156
- })).execute();
1157
- const status = await this.findIndexStatus(input);
1158
- if (!status) throw new Error(`Media usage index status ${input.adapterId}:${input.scopeType}:${input.scopeKey} was not persisted`);
1159
- return status;
1160
- }
1161
- async invalidateIndexStatusForSchemaChange(collectionSlug) {
1162
- const result = await this.db.updateTable("_emdash_media_usage_index_status as status").set({
1163
- change_epoch: sql`change_epoch + 1`,
1164
- status: "stale",
1165
- completed_at: null,
1166
- cursor: null,
1167
- last_error_code: "CONTENT_USAGE_STALE",
1168
- reconciliation_required: 1,
1169
- updated_at: this.sortableUtcTimestamp()
1170
- }).where("status.adapter_id", "=", "content-media").where("status.scope_type", "=", "collection").where("status.scope_key", "=", collectionSlug).where("status.capture_state", "=", "active").where((eb) => eb.exists(eb.selectFrom("_emdash_collections as collection").select("collection.id").whereRef("collection.id", "=", "status.collection_id").whereRef("collection.slug", "=", "status.scope_key"))).where(sql`EXISTS (
1171
- SELECT 1
1172
- FROM _emdash_media_usage_activation AS activation
1173
- WHERE activation.task_key = 'incremental_capture'
1174
- AND activation.state = 'active'
1175
- )`).executeTakeFirst();
1176
- return Number(result.numUpdatedRows ?? 0) === 1;
1177
- }
1178
- async beginIndexStatusRepair(input) {
1179
- return this.upsertIndexStatus({
1180
- adapterId: input.adapterId,
1181
- scopeType: input.scopeType,
1182
- scopeKey: input.scopeKey,
1183
- status: "running",
1184
- schemaVersion: input.schemaVersion,
1185
- startedAt: input.startedAt,
1186
- completedAt: null,
1187
- cursor: input.runToken,
1188
- indexedSourceCount: 0,
1189
- failedSourceCount: 0,
1190
- lastErrorCode: null,
1191
- updatedAt: input.updatedAt
1192
- });
1193
- }
1194
- async finalizeIndexStatusRepairIfRunning(input) {
1195
- const updates = {
1196
- status: input.status,
1197
- completed_at: input.completedAt,
1198
- cursor: null,
1199
- indexed_source_count: input.indexedSourceCount ?? 0,
1200
- failed_source_count: input.failedSourceCount ?? 0,
1201
- last_error_code: input.lastErrorCode ?? null,
1202
- updated_at: input.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
1203
- };
1204
- if (input.schemaVersion !== void 0) updates.schema_version = input.schemaVersion;
1205
- const result = await this.db.updateTable("_emdash_media_usage_index_status").set(updates).where("adapter_id", "=", input.adapterId).where("scope_type", "=", input.scopeType).where("scope_key", "=", input.scopeKey).where("status", "=", "running").where("cursor", "=", input.runToken).executeTakeFirst();
1206
- return {
1207
- finalized: Number(result.numUpdatedRows ?? 0) > 0,
1208
- status: await this.findIndexStatus(input)
1209
- };
1210
- }
1211
- async beginIndexStatusRepairAtCurrentEpoch(input) {
1212
- const now = this.sortableUtcTimestamp();
1213
- const row = await this.db.updateTable("_emdash_media_usage_index_status").set({
1214
- status: "running",
1215
- schema_version: input.schemaVersion,
1216
- started_at: now,
1217
- completed_at: null,
1218
- cursor: input.runToken,
1219
- indexed_source_count: 0,
1220
- failed_source_count: 0,
1221
- last_error_code: null,
1222
- reconciliation_required: 1,
1223
- updated_at: now
1224
- }).where("adapter_id", "=", input.adapterId).where("scope_type", "=", input.scopeType).where("scope_key", "=", input.scopeKey).where("collection_id", "=", input.collectionId).where("capture_state", "=", "active").where(sql`EXISTS (
1225
- SELECT 1
1226
- FROM _emdash_collections AS collection
1227
- WHERE collection.id = ${input.collectionId}
1228
- AND collection.slug = ${input.scopeKey}
1229
- )`).where(sql`EXISTS (
1230
- SELECT 1
1231
- FROM _emdash_media_usage_activation AS activation
1232
- WHERE activation.task_key = 'incremental_capture'
1233
- AND activation.state = 'active'
1234
- )`).returning(["change_epoch", "started_at"]).executeTakeFirst();
1235
- if (!row?.started_at) return null;
1236
- return {
1237
- changeEpoch: row.change_epoch,
1238
- startedAt: row.started_at
1239
- };
1240
- }
1241
- async finalizeIndexStatusRepairAtEpoch(input) {
1242
- const now = this.sortableUtcTimestamp();
1243
- const updates = {
1244
- status: input.status,
1245
- schema_version: input.schemaVersion,
1246
- completed_at: now,
1247
- cursor: null,
1248
- indexed_source_count: input.indexedSourceCount,
1249
- failed_source_count: input.failedSourceCount,
1250
- last_error_code: input.lastErrorCode,
1251
- reconciliation_required: input.status === "complete" ? 0 : 1,
1252
- updated_at: now
1253
- };
1254
- let query = this.db.updateTable("_emdash_media_usage_index_status").set(updates).where("adapter_id", "=", input.adapterId).where("scope_type", "=", input.scopeType).where("scope_key", "=", input.scopeKey).where("collection_id", "=", input.collectionId).where("status", "=", "running").where("cursor", "=", input.runToken).where("change_epoch", "=", input.startingEpoch).where(sql`EXISTS (
1255
- SELECT 1
1256
- FROM _emdash_collections AS collection
1257
- WHERE collection.id = ${input.collectionId}
1258
- AND collection.slug = ${input.scopeKey}
1259
- )`);
1260
- if (input.status === "complete") query = query.where(sql`NOT EXISTS (
1261
- SELECT 1
1262
- FROM _emdash_media_usage_work AS work
1263
- WHERE work.collection_id = ${input.collectionId}
1264
- )`);
1265
- const result = await query.executeTakeFirst();
1266
- const finalized = Number(result.numUpdatedRows ?? 0) > 0;
1267
- if (!finalized) await this.db.updateTable("_emdash_media_usage_index_status").set({
1268
- status: "stale",
1269
- completed_at: null,
1270
- cursor: null,
1271
- last_error_code: "CONTENT_USAGE_REPAIR_CONFLICT",
1272
- reconciliation_required: 1,
1273
- updated_at: this.sortableUtcTimestamp()
1274
- }).where("adapter_id", "=", input.adapterId).where("scope_type", "=", input.scopeType).where("scope_key", "=", input.scopeKey).where("collection_id", "=", input.collectionId).where("status", "=", "running").where("cursor", "=", input.runToken).execute();
1275
- return {
1276
- finalized,
1277
- status: await this.findIndexStatusForCollection(input, input.collectionId)
1278
- };
1279
- }
1280
- async recordIncrementalSuccess(input) {
1281
- const observed = await this.db.selectFrom("_emdash_media_usage_index_status").select("change_epoch").where("adapter_id", "=", "content-media").where("scope_type", "=", "collection").where("scope_key", "=", input.collectionSlug).where("collection_id", "=", input.collectionId).where("capture_state", "=", "active").where(sql`EXISTS (
1282
- SELECT 1
1283
- FROM _emdash_collections AS collection
1284
- WHERE collection.id = ${input.collectionId}
1285
- AND collection.slug = ${input.collectionSlug}
1286
- )`).executeTakeFirst();
1287
- if (!observed) return false;
1288
- const canComplete = sql`(
1289
- reconciliation_required = 0
1290
- AND status IN ('complete', 'stale', 'partial')
1291
- AND NOT EXISTS (
1292
- SELECT 1
1293
- FROM _emdash_media_usage_work AS work
1294
- WHERE work.collection_id = ${input.collectionId}
1295
- )
1296
- )`;
1297
- const now = this.sortableUtcTimestamp();
1298
- const result = await this.db.updateTable("_emdash_media_usage_index_status").set({
1299
- status: sql`CASE WHEN ${canComplete} THEN 'complete' ELSE status END`,
1300
- completed_at: sql`CASE WHEN ${canComplete} THEN ${now} ELSE completed_at END`,
1301
- last_error_code: sql`CASE WHEN ${canComplete} THEN NULL ELSE last_error_code END`,
1302
- cursor: sql`CASE WHEN ${canComplete} THEN NULL ELSE cursor END`,
1303
- last_incremental_success_at: now,
1304
- updated_at: now
1305
- }).where("adapter_id", "=", "content-media").where("scope_type", "=", "collection").where("scope_key", "=", input.collectionSlug).where("collection_id", "=", input.collectionId).where("change_epoch", "=", observed.change_epoch).where("capture_state", "=", "active").where(sql`EXISTS (
1306
- SELECT 1
1307
- FROM _emdash_collections AS collection
1308
- WHERE collection.id = ${input.collectionId}
1309
- AND collection.slug = ${input.collectionSlug}
1310
- )`).executeTakeFirst();
1311
- return Number(result.numUpdatedRows ?? 0) > 0;
1312
- }
1313
- async prepareIncrementalFinalization(input) {
1314
- const observed = await this.db.selectFrom("_emdash_media_usage_index_status").select(["change_epoch", "reconciliation_required"]).where("adapter_id", "=", "content-media").where("scope_type", "=", "collection").where("scope_key", "=", input.collectionSlug).where("collection_id", "=", input.collectionId).where("capture_state", "=", "active").executeTakeFirst();
1315
- if (!observed) return { outcome: "lost" };
1316
- if (observed.reconciliation_required !== 0) return { outcome: "not_required" };
1317
- const marker = `incremental-finalize:${String(observed.change_epoch)}:${ulid()}`;
1318
- const result = await this.db.updateTable("_emdash_media_usage_index_status").set({
1319
- cursor: marker,
1320
- updated_at: this.sortableUtcTimestamp()
1321
- }).where("adapter_id", "=", "content-media").where("scope_type", "=", "collection").where("scope_key", "=", input.collectionSlug).where("collection_id", "=", input.collectionId).where("capture_state", "=", "active").where("reconciliation_required", "=", 0).where("change_epoch", "=", observed.change_epoch).where(sql`EXISTS (
1322
- SELECT 1
1323
- FROM _emdash_collections AS collection
1324
- WHERE collection.id = ${input.collectionId}
1325
- AND collection.slug = ${input.collectionSlug}
1326
- )`).where(sql`NOT EXISTS (
1327
- SELECT 1
1328
- FROM _emdash_media_usage_collection_deletions AS deletion
1329
- WHERE deletion.collection_id = ${input.collectionId}
1330
- )`).executeTakeFirst();
1331
- return Number(result.numUpdatedRows ?? 0) > 0 ? {
1332
- outcome: "marked",
1333
- marker
1334
- } : { outcome: "lost" };
1335
- }
1336
- async recoverIncrementalFinalizations() {
1337
- const markerMatchesEpoch = isPostgres(this.db) ? sql`status.cursor LIKE ('incremental-finalize:' || status.change_epoch::text || ':%')` : sql`status.cursor LIKE ('incremental-finalize:' || CAST(status.change_epoch AS text) || ':%')`;
1338
- const now = (/* @__PURE__ */ new Date()).toISOString();
1339
- return (await sql`
1340
- UPDATE _emdash_media_usage_index_status AS status
1341
- SET status = 'complete',
1342
- completed_at = ${now},
1343
- cursor = NULL,
1344
- last_error_code = NULL,
1345
- last_incremental_success_at = ${now},
1346
- updated_at = ${now}
1347
- WHERE status.adapter_id = 'content-media'
1348
- AND status.scope_type = 'collection'
1349
- AND status.capture_state = 'active'
1350
- AND status.reconciliation_required = 0
1351
- AND status.cursor IS NOT NULL
1352
- AND ${markerMatchesEpoch}
1353
- AND EXISTS (
1354
- SELECT 1
1355
- FROM _emdash_collections AS collection
1356
- WHERE collection.id = status.collection_id
1357
- AND collection.slug = status.scope_key
1358
- )
1359
- AND NOT EXISTS (
1360
- SELECT 1
1361
- FROM _emdash_media_usage_collection_deletions AS deletion
1362
- WHERE deletion.collection_id = status.collection_id
1363
- )
1364
- AND NOT EXISTS (
1365
- SELECT 1
1366
- FROM _emdash_media_usage_work AS work
1367
- WHERE work.collection_id = status.collection_id
1368
- )
1369
- RETURNING collection_id
1370
- `.execute(this.db)).rows.length;
1371
- }
1372
- async recordIncrementalFailure(input) {
1373
- const now = this.sortableUtcTimestamp();
1374
- const automaticRunOwnsCoverage = sql`EXISTS (
1375
- SELECT 1
1376
- FROM _emdash_media_usage_reconciliations AS reconciliation
1377
- WHERE reconciliation.collection_id = ${input.collectionId}
1378
- AND reconciliation.run_token = cursor
1379
- )`;
1380
- const result = await this.db.updateTable("_emdash_media_usage_index_status").set({
1381
- status: sql`CASE
1382
- WHEN ${automaticRunOwnsCoverage} THEN status
1383
- WHEN reconciliation_required = 0 THEN 'partial'
1384
- WHEN status = 'running' THEN 'stale'
1385
- ELSE status
1386
- END`,
1387
- completed_at: sql`CASE
1388
- WHEN ${automaticRunOwnsCoverage} THEN completed_at
1389
- WHEN reconciliation_required = 0 OR status = 'running' THEN NULL
1390
- ELSE completed_at
1391
- END`,
1392
- cursor: sql`CASE
1393
- WHEN ${automaticRunOwnsCoverage} THEN cursor
1394
- WHEN status = 'running' THEN NULL
1395
- ELSE cursor
1396
- END`,
1397
- last_error_code: input.errorCode,
1398
- updated_at: now
1399
- }).where("adapter_id", "=", "content-media").where("scope_type", "=", "collection").where("scope_key", "=", input.collectionSlug).where("collection_id", "=", input.collectionId).where(sql`EXISTS (
1400
- SELECT 1
1401
- FROM _emdash_media_usage_work AS work
1402
- WHERE work.collection_id = ${input.collectionId}
1403
- AND work.content_id = ${input.contentId}
1404
- AND work.work_version = ${input.workVersion}
1405
- AND work.state = 'failed'
1406
- AND work.last_error_code = ${input.errorCode}
1407
- )`).where(sql`EXISTS (
1408
- SELECT 1
1409
- FROM _emdash_collections AS collection
1410
- WHERE collection.id = ${input.collectionId}
1411
- AND collection.slug = ${input.collectionSlug}
1412
- )`).executeTakeFirst();
1413
- return Number(result.numUpdatedRows ?? 0) > 0;
1414
- }
1415
- async recordIncrementalFailuresByCollection(input) {
1416
- const collectionIds = [...new Set(input.collectionIds)];
1417
- if (collectionIds.length === 0) return /* @__PURE__ */ new Set();
1418
- const collections = jsonTextValues(this.db, collectionIds);
1419
- const now = (/* @__PURE__ */ new Date()).toISOString();
1420
- const result = await sql`
1421
- WITH failed_collections AS (${collections})
1422
- UPDATE _emdash_media_usage_index_status AS status
1423
- SET status = CASE
1424
- WHEN EXISTS (
1425
- SELECT 1
1426
- FROM _emdash_media_usage_reconciliations AS reconciliation
1427
- WHERE reconciliation.collection_id = status.collection_id
1428
- AND reconciliation.run_token = status.cursor
1429
- ) THEN status.status
1430
- WHEN status.reconciliation_required = 0 THEN 'partial'
1431
- WHEN status.status = 'running' THEN 'stale'
1432
- ELSE status.status
1433
- END,
1434
- completed_at = CASE
1435
- WHEN EXISTS (
1436
- SELECT 1
1437
- FROM _emdash_media_usage_reconciliations AS reconciliation
1438
- WHERE reconciliation.collection_id = status.collection_id
1439
- AND reconciliation.run_token = status.cursor
1440
- ) THEN status.completed_at
1441
- WHEN status.reconciliation_required = 0 OR status.status = 'running' THEN NULL
1442
- ELSE status.completed_at
1443
- END,
1444
- cursor = CASE
1445
- WHEN EXISTS (
1446
- SELECT 1
1447
- FROM _emdash_media_usage_reconciliations AS reconciliation
1448
- WHERE reconciliation.collection_id = status.collection_id
1449
- AND reconciliation.run_token = status.cursor
1450
- ) THEN status.cursor
1451
- WHEN status.status = 'running' THEN NULL
1452
- ELSE status.cursor
1453
- END,
1454
- last_error_code = ${input.errorCode},
1455
- updated_at = ${now}
1456
- WHERE status.adapter_id = 'content-media'
1457
- AND status.scope_type = 'collection'
1458
- AND status.collection_id IN (SELECT value FROM failed_collections)
1459
- AND EXISTS (
1460
- SELECT 1
1461
- FROM _emdash_media_usage_work AS work
1462
- WHERE work.collection_id = status.collection_id
1463
- AND work.state = 'failed'
1464
- AND work.last_error_code = ${input.errorCode}
1465
- )
1466
- AND EXISTS (
1467
- SELECT 1
1468
- FROM _emdash_collections AS collection
1469
- WHERE collection.id = status.collection_id
1470
- AND collection.slug = status.scope_key
1471
- )
1472
- RETURNING collection_id
1473
- `.execute(this.db);
1474
- return new Set(result.rows.map((row) => row.collection_id));
1475
- }
1476
- async findIndexStatus(identity) {
1477
- const row = await this.db.selectFrom("_emdash_media_usage_index_status").selectAll().where("adapter_id", "=", identity.adapterId).where("scope_type", "=", identity.scopeType).where("scope_key", "=", identity.scopeKey).executeTakeFirst();
1478
- return row ? rowToIndexStatus(row) : null;
1479
- }
1480
- async findIndexStatusForCollection(identity, collectionId) {
1481
- const row = await this.db.selectFrom("_emdash_media_usage_index_status").selectAll().where("adapter_id", "=", identity.adapterId).where("scope_type", "=", identity.scopeType).where("scope_key", "=", identity.scopeKey).where("collection_id", "=", collectionId).executeTakeFirst();
1482
- return row ? rowToIndexStatus(row) : null;
1483
- }
1484
- async deleteIndexStatus(identity, collectionId) {
1485
- let query = this.db.deleteFrom("_emdash_media_usage_index_status").where("adapter_id", "=", identity.adapterId).where("scope_type", "=", identity.scopeType).where("scope_key", "=", identity.scopeKey);
1486
- if (collectionId !== void 0) query = query.where("collection_id", "=", collectionId);
1487
- const result = await query.executeTakeFirst();
1488
- return Number(result.numDeletedRows ?? 0);
1489
- }
1490
- sortableUtcTimestamp() {
1491
- return isPostgres(this.db) ? sql`to_char(clock_timestamp() AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')` : sql`strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;
1492
- }
1493
- async findCurrentUsagePage(applyFilter, options) {
1494
- const limit = Math.min(Math.max(1, options.limit ?? 50), 100);
1495
- let query = applyFilter(this.currentUsageBaseQuery()).orderBy("u.id", "asc").limit(limit + 1);
1496
- if (options.cursor) {
1497
- const { id } = decodeCursor(options.cursor);
1498
- query = query.where("u.id", ">", id);
1499
- }
1500
- const rows = await query.execute();
1501
- const items = rows.slice(0, limit).map(rowToUsageRecord);
1502
- const result = { items };
1503
- if (rows.length > limit && items.length > 0) {
1504
- const last = items.at(-1);
1505
- result.nextCursor = encodeCursor(last.occurrence.id, last.occurrence.id);
1506
- }
1507
- return result;
1508
- }
1509
- currentUsageBaseQuery() {
1510
- return this.db.selectFrom("_emdash_media_usage_sources as s").innerJoin("_emdash_media_usage as u", (join) => join.onRef("u.source_key", "=", "s.source_key").onRef("u.generation", "=", "s.current_generation")).select(currentUsageSelect);
1511
- }
1512
- currentContentMediaUsageBaseQuery() {
1513
- return this.db.selectFrom("_emdash_media_usage as u").crossJoin("_emdash_media_usage_sources as s").innerJoin("_emdash_collections as collection", "collection.slug", "s.collection_slug").whereRef("s.source_key", "=", "u.source_key").whereRef("s.current_generation", "=", "u.generation").where("s.source_type", "=", "content").where("s.collection_slug", "is not", null).where("s.content_id", "is not", null).where("s.source_variant", "in", ["columns", "draft_overlay"]).where(contentSourceMatchesActiveCollection("s", "collection.id")).where(CONTENT_SOURCE_ELIGIBILITY);
1514
- }
1515
- async deleteOrphanCandidateIds(ids, cutoff, cleanupLease, canIssueStatement) {
1516
- let deleted = 0;
1517
- for (const idBatch of chunks([...ids], cleanupDeleteBatchSize(cleanupLease))) {
1518
- if (!canIssueCleanupStatement(canIssueStatement)) break;
1519
- if (cleanupLease) {
1520
- await this.markOrphanCandidatesForCleanup(idBatch, cutoff, cleanupLease);
1521
- if (!canIssueCleanupStatement(canIssueStatement)) break;
1522
- }
1523
- let query = this.db.deleteFrom("_emdash_media_usage").where("id", "in", idBatch).where("created_at", "<", cutoff).where(sql`NOT EXISTS (SELECT 1 FROM _emdash_media_usage_sources source WHERE source.source_key = _emdash_media_usage.source_key)`).where(this.noActiveGenerationWriteExpression());
1524
- if (cleanupLease) query = query.where("cleanup_lease_token", "=", cleanupLease.leaseToken).where(this.activeCleanupLeaseExpression(cleanupLease));
1525
- const result = await query.executeTakeFirst();
1526
- deleted += Number(result.numDeletedRows ?? 0);
1527
- }
1528
- return deleted;
1529
- }
1530
- async deleteStaleCandidateIds(ids, cutoff, cleanupLease, canIssueStatement) {
1531
- let deleted = 0;
1532
- for (const idBatch of chunks([...ids], cleanupDeleteBatchSize(cleanupLease))) {
1533
- if (!canIssueCleanupStatement(canIssueStatement)) break;
1534
- if (cleanupLease) {
1535
- await this.markStaleCandidatesForCleanup(idBatch, cutoff, cleanupLease);
1536
- if (!canIssueCleanupStatement(canIssueStatement)) break;
1537
- }
1538
- let query = this.db.deleteFrom("_emdash_media_usage").where("id", "in", idBatch).where("created_at", "<", cutoff).where((eb) => eb.exists(eb.selectFrom("_emdash_media_usage_sources as source").select("source.source_key").whereRef("source.source_key", "=", "_emdash_media_usage.source_key").whereRef("source.current_generation", "!=", "_emdash_media_usage.generation").whereRef("_emdash_media_usage.created_at", "<", "source.indexed_at"))).where(this.noActiveGenerationWriteExpression());
1539
- if (cleanupLease) query = query.where("cleanup_lease_token", "=", cleanupLease.leaseToken).where(this.activeCleanupLeaseExpression(cleanupLease));
1540
- const result = await query.executeTakeFirst();
1541
- deleted += Number(result.numDeletedRows ?? 0);
1542
- }
1543
- return deleted;
1544
- }
1545
- async deleteAbandonedCandidateIds(ids, cutoff, cleanupLease, canIssueStatement) {
1546
- let deleted = 0;
1547
- for (const idBatch of chunks([...ids], cleanupDeleteBatchSize(cleanupLease))) {
1548
- if (!canIssueCleanupStatement(canIssueStatement)) break;
1549
- if (cleanupLease) {
1550
- await this.markAbandonedCandidatesForCleanup(idBatch, cutoff, cleanupLease);
1551
- if (!canIssueCleanupStatement(canIssueStatement)) break;
1552
- }
1553
- let query = this.db.deleteFrom("_emdash_media_usage").where("id", "in", idBatch).where("created_at", "<", cutoff).where((eb) => eb.exists(eb.selectFrom("_emdash_media_usage_sources as source").select("source.source_key").whereRef("source.source_key", "=", "_emdash_media_usage.source_key").whereRef("source.current_generation", "!=", "_emdash_media_usage.generation").whereRef("_emdash_media_usage.created_at", ">=", "source.indexed_at"))).where(this.noActiveGenerationWriteExpression());
1554
- if (cleanupLease) query = query.where("cleanup_lease_token", "=", cleanupLease.leaseToken).where(this.activeCleanupLeaseExpression(cleanupLease));
1555
- const result = await query.executeTakeFirst();
1556
- deleted += Number(result.numDeletedRows ?? 0);
1557
- }
1558
- return deleted;
1559
- }
1560
- async markOrphanCandidatesForCleanup(ids, cutoff, cleanupLease) {
1561
- await this.db.updateTable("_emdash_media_usage").set({ cleanup_lease_token: cleanupLease.leaseToken }).where("id", "in", ids).where("created_at", "<", cutoff).where(sql`NOT EXISTS (SELECT 1 FROM _emdash_media_usage_sources source WHERE source.source_key = _emdash_media_usage.source_key)`).where(this.noActiveGenerationWriteExpression()).where(this.activeCleanupLeaseExpression(cleanupLease)).execute();
1562
- }
1563
- async markStaleCandidatesForCleanup(ids, cutoff, cleanupLease) {
1564
- await this.db.updateTable("_emdash_media_usage").set({ cleanup_lease_token: cleanupLease.leaseToken }).where("id", "in", ids).where("created_at", "<", cutoff).where((eb) => eb.exists(eb.selectFrom("_emdash_media_usage_sources as source").select("source.source_key").whereRef("source.source_key", "=", "_emdash_media_usage.source_key").whereRef("source.current_generation", "!=", "_emdash_media_usage.generation").whereRef("_emdash_media_usage.created_at", "<", "source.indexed_at"))).where(this.noActiveGenerationWriteExpression()).where(this.activeCleanupLeaseExpression(cleanupLease)).execute();
1565
- }
1566
- async markAbandonedCandidatesForCleanup(ids, cutoff, cleanupLease) {
1567
- await this.db.updateTable("_emdash_media_usage").set({ cleanup_lease_token: cleanupLease.leaseToken }).where("id", "in", ids).where("created_at", "<", cutoff).where((eb) => eb.exists(eb.selectFrom("_emdash_media_usage_sources as source").select("source.source_key").whereRef("source.source_key", "=", "_emdash_media_usage.source_key").whereRef("source.current_generation", "!=", "_emdash_media_usage.generation").whereRef("_emdash_media_usage.created_at", ">=", "source.indexed_at"))).where(this.noActiveGenerationWriteExpression()).where(this.activeCleanupLeaseExpression(cleanupLease)).execute();
1568
- }
1569
- noActiveGenerationWriteExpression(usageTable = "_emdash_media_usage") {
1570
- return sql`NOT EXISTS (
1571
- SELECT 1
1572
- FROM _emdash_media_usage_generation_writes AS writer
1573
- WHERE writer.source_key = ${sql.ref(`${usageTable}.source_key`)}
1574
- AND writer.generation = ${sql.ref(`${usageTable}.generation`)}
1575
- AND ${this.generationWriteLeaseExpiryIsInFuture("writer.expires_at")}
1576
- )`;
1577
- }
1578
- /**
1579
- * Gates a bounded cleanup scan on the lease and the tick's statement budget.
1580
- *
1581
- * The budget is re-read after the lease row because that read is itself a
1582
- * statement: an isolate suspended across it can resume with the budget spent.
1583
- */
1584
- async admitCleanupScan(cleanupLease, canIssueStatement) {
1585
- if (!canIssueCleanupStatement(canIssueStatement)) return false;
1586
- if (!cleanupLease) return true;
1587
- return await this.holdsCleanupLease(cleanupLease) && canIssueCleanupStatement(canIssueStatement);
1588
- }
1589
- /**
1590
- * Reads the lease row on its own so a lost lease can short-circuit a sweep.
1591
- *
1592
- * As a row-level `EXISTS` inside a `LIMIT`ed scan this predicate is constant
1593
- * across every candidate row: when it is false the limit is unreachable and
1594
- * the scan reads the whole `created_at` range before returning nothing.
1595
- * Mutating statements still carry {@link activeCleanupLeaseExpression},
1596
- * which is what actually fences a displaced owner.
1597
- */
1598
- async holdsCleanupLease(cleanupLease) {
1599
- let query = this.db.selectFrom("_emdash_media_usage_cleanup").select("task_key").where("task_key", "=", "projection_gc").where("lease_token", "=", cleanupLease.leaseToken).where(this.cleanupLeaseExpiryIsInFuture("_emdash_media_usage_cleanup.lease_expires_at"));
1600
- if (isPostgres(this.db)) query = query.forUpdate();
1601
- return await query.executeTakeFirst() !== void 0;
1602
- }
1603
- activeCleanupLeaseExpression(cleanupLease) {
1604
- const rowLock = isPostgres(this.db) ? sql` FOR UPDATE` : sql``;
1605
- return sql`EXISTS (
1606
- SELECT 1
1607
- FROM _emdash_media_usage_cleanup AS cleanup
1608
- WHERE cleanup.task_key = 'projection_gc'
1609
- AND cleanup.lease_token = ${cleanupLease.leaseToken}
1610
- AND ${this.cleanupLeaseExpiryIsInFuture("cleanup.lease_expires_at")}
1611
- ${rowLock}
1612
- )`;
1613
- }
1614
- cleanupLeaseExpiryIsInFuture(column) {
1615
- const leaseExpiresAt = sql.ref(column);
1616
- return isPostgres(this.db) ? sql`${leaseExpiresAt}::timestamptz > clock_timestamp()` : sql`${leaseExpiresAt} > strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;
1617
- }
1618
- cleanupTimestampIsDue(column) {
1619
- const timestamp = sql.ref(column);
1620
- return isPostgres(this.db) ? sql`${timestamp}::timestamptz <= clock_timestamp()` : sql`${timestamp} <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;
1621
- }
1622
- cleanupTimestampOffset(offsetSeconds) {
1623
- if (isPostgres(this.db)) return sql`to_char(
1624
- (clock_timestamp() AT TIME ZONE 'UTC') + (${offsetSeconds} * INTERVAL '1 second'),
1625
- 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
1626
- )`;
1627
- return sql`strftime(
1628
- '%Y-%m-%dT%H:%M:%fZ',
1629
- 'now',
1630
- ${`${offsetSeconds >= 0 ? "+" : ""}${offsetSeconds} seconds`}
1631
- )`;
1632
- }
1633
- generationWriteLeaseHasExpired(column) {
1634
- const leaseExpiresAt = sql.ref(column);
1635
- return isPostgres(this.db) ? sql`${leaseExpiresAt}::timestamptz <= clock_timestamp()` : sql`${leaseExpiresAt} <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;
1636
- }
1637
- async deleteSourceKeys(sourceKeys) {
1638
- const uniqueSourceKeys = [...new Set(sourceKeys)];
1639
- if (uniqueSourceKeys.length === 0) return 0;
1640
- return withTransaction(this.db, async (trx) => {
1641
- await this.lockCleanupBeforeSourceDelete(trx);
1642
- let deleted = 0;
1643
- for (const sourceKeyBatch of chunks(uniqueSourceKeys, SQL_BATCH_SIZE)) {
1644
- const result = await trx.deleteFrom("_emdash_media_usage_sources").where("source_key", "in", sourceKeyBatch).executeTakeFirst();
1645
- deleted += Number(result.numDeletedRows ?? 0);
1646
- await trx.updateTable("_emdash_media_usage").set({ cleanup_lease_token: null }).where("source_key", "in", sourceKeyBatch).execute();
1647
- await trx.deleteFrom("_emdash_media_usage").where("source_key", "in", sourceKeyBatch).execute();
1648
- }
1649
- return deleted;
1650
- });
1651
- }
1652
- async deleteSourceGenerationOccurrences(db, sourceKey, generation) {
1653
- await db.updateTable("_emdash_media_usage").set({ cleanup_lease_token: null }).where("source_key", "=", sourceKey).where("generation", "=", generation).execute();
1654
- await db.deleteFrom("_emdash_media_usage").where("source_key", "=", sourceKey).where("generation", "=", generation).execute();
1655
- }
1656
- async lockCleanupBeforeSourceDelete(db) {
1657
- if (!isPostgres(this.db)) return;
1658
- await sql`
1659
- SELECT 1
1660
- FROM _emdash_media_usage_cleanup
1661
- WHERE task_key = 'projection_gc'
1662
- FOR SHARE
1663
- `.execute(db);
1664
- }
1665
- async insertOccurrences(db, sourceKey, generation, occurrences, now) {
1666
- if (occurrences.length === 0) return;
1667
- const rows = occurrences.map((occurrence) => ({
1668
- id: ulid(),
1669
- source_key: sourceKey,
1670
- generation,
1671
- field_slug: occurrence.fieldSlug,
1672
- field_path: occurrence.fieldPath,
1673
- occurrence_index: occurrence.occurrenceIndex ?? 0,
1674
- reference_type: occurrence.referenceType,
1675
- media_id: occurrence.mediaId,
1676
- provider: occurrence.provider,
1677
- provider_asset_id: occurrence.providerAssetId,
1678
- media_kind: occurrence.mediaKind ?? null,
1679
- mime_type: occurrence.mimeType ?? null,
1680
- created_at: now
1681
- }));
1682
- for (const rowBatch of chunks(rows, OCCURRENCE_INSERT_BATCH_SIZE)) await db.insertInto("_emdash_media_usage").values(rowBatch).execute();
1683
- }
1684
- async insertBatchOccurrences(rows) {
1685
- for (const rowBatch of chunkJsonRows(rows)) await sql`
1686
- WITH input AS (${this.occurrenceBatchInput(JSON.stringify(rowBatch))})
1687
- INSERT INTO _emdash_media_usage (
1688
- id, source_key, generation, field_slug, field_path, occurrence_index,
1689
- reference_type, media_id, provider, provider_asset_id, media_kind,
1690
- mime_type, created_at
1691
- )
1692
- SELECT
1693
- id, source_key, generation, field_slug, field_path, occurrence_index,
1694
- reference_type, media_id, provider, provider_asset_id, media_kind,
1695
- mime_type, created_at
1696
- FROM input
1697
- WHERE EXISTS (
1698
- SELECT 1
1699
- FROM _emdash_media_usage_generation_writes AS writer
1700
- WHERE writer.source_key = input.source_key
1701
- AND writer.generation = input.generation
1702
- AND writer.lease_token = input.lease_token
1703
- AND ${this.generationWriteLeaseExpiryIsInFuture("writer.expires_at")}
1704
- )
1705
- `.execute(this.db);
1706
- }
1707
- occurrenceBatchInput(payload) {
1708
- if (isPostgres(this.db)) return sql`
1709
- SELECT
1710
- entry.value ->> 'id' AS id,
1711
- entry.value ->> 'source_key' AS source_key,
1712
- entry.value ->> 'generation' AS generation,
1713
- entry.value ->> 'field_slug' AS field_slug,
1714
- entry.value ->> 'field_path' AS field_path,
1715
- CAST(entry.value ->> 'occurrence_index' AS integer) AS occurrence_index,
1716
- entry.value ->> 'reference_type' AS reference_type,
1717
- entry.value ->> 'media_id' AS media_id,
1718
- entry.value ->> 'provider' AS provider,
1719
- entry.value ->> 'provider_asset_id' AS provider_asset_id,
1720
- entry.value ->> 'media_kind' AS media_kind,
1721
- entry.value ->> 'mime_type' AS mime_type,
1722
- entry.value ->> 'created_at' AS created_at,
1723
- entry.value ->> 'lease_token' AS lease_token
1724
- FROM jsonb_array_elements(${payload}::jsonb) AS entry(value)
1725
- `;
1726
- return sql`
1727
- SELECT
1728
- json_extract(entry.value, '$.id') AS id,
1729
- json_extract(entry.value, '$.source_key') AS source_key,
1730
- json_extract(entry.value, '$.generation') AS generation,
1731
- json_extract(entry.value, '$.field_slug') AS field_slug,
1732
- json_extract(entry.value, '$.field_path') AS field_path,
1733
- CAST(json_extract(entry.value, '$.occurrence_index') AS integer) AS occurrence_index,
1734
- json_extract(entry.value, '$.reference_type') AS reference_type,
1735
- json_extract(entry.value, '$.media_id') AS media_id,
1736
- json_extract(entry.value, '$.provider') AS provider,
1737
- json_extract(entry.value, '$.provider_asset_id') AS provider_asset_id,
1738
- json_extract(entry.value, '$.media_kind') AS media_kind,
1739
- json_extract(entry.value, '$.mime_type') AS mime_type,
1740
- json_extract(entry.value, '$.created_at') AS created_at,
1741
- json_extract(entry.value, '$.lease_token') AS lease_token
1742
- FROM json_each(${payload}) AS entry
1743
- `;
1744
- }
1745
- sourceBatchInput(payload) {
1746
- if (isPostgres(this.db)) return sql`
1747
- SELECT
1748
- entry.value ->> 'source_key' AS source_key,
1749
- entry.value ->> 'source_type' AS source_type,
1750
- entry.value ->> 'collection_id' AS collection_id,
1751
- entry.value ->> 'collection_slug' AS collection_slug,
1752
- entry.value ->> 'content_id' AS content_id,
1753
- entry.value ->> 'source_variant' AS source_variant,
1754
- entry.value ->> 'locale' AS locale,
1755
- entry.value ->> 'translation_group' AS translation_group,
1756
- entry.value ->> 'content_slug' AS content_slug,
1757
- entry.value ->> 'content_title' AS content_title,
1758
- entry.value ->> 'content_status' AS content_status,
1759
- entry.value ->> 'content_scheduled_at' AS content_scheduled_at,
1760
- entry.value ->> 'content_deleted_at' AS content_deleted_at,
1761
- entry.value ->> 'revision_id' AS revision_id,
1762
- entry.value ->> 'current_generation' AS current_generation,
1763
- CAST(entry.value ->> 'schema_version' AS integer) AS schema_version,
1764
- entry.value ->> 'source_updated_at' AS source_updated_at,
1765
- CAST(entry.value ->> 'source_version' AS bigint) AS source_version,
1766
- entry.value ->> 'source_fingerprint' AS source_fingerprint,
1767
- CAST(entry.value ->> 'identity_version' AS integer) AS identity_version,
1768
- entry.value ->> 'source_completeness' AS source_completeness,
1769
- entry.value ->> 'last_attempted_at' AS last_attempted_at,
1770
- entry.value ->> 'last_error_code' AS last_error_code,
1771
- entry.value ->> 'indexed_at' AS indexed_at,
1772
- entry.value ->> 'updated_at' AS updated_at,
1773
- entry.value ->> 'lease_token' AS lease_token,
1774
- entry.value ->> 'expected_generation' AS expected_generation,
1775
- entry.value ->> 'expected_collection_id' AS expected_collection_id,
1776
- entry.value ->> 'expected_updated_at' AS expected_updated_at,
1777
- entry.value ->> 'expected_source_fingerprint' AS expected_source_fingerprint,
1778
- entry.value ->> 'expected_source_updated_at' AS expected_source_updated_at,
1779
- CAST(entry.value ->> 'expected_source_version' AS bigint) AS expected_source_version,
1780
- CAST(entry.value ->> 'expected_identity_version' AS integer) AS expected_identity_version,
1781
- entry.value ->> 'expected_revision_id' AS expected_revision_id,
1782
- entry.value ->> 'expected_source_completeness' AS expected_source_completeness,
1783
- entry.value ->> 'expected_last_attempted_at' AS expected_last_attempted_at,
1784
- entry.value ->> 'expected_last_error_code' AS expected_last_error_code
1785
- FROM jsonb_array_elements(${payload}::jsonb) AS entry(value)
1786
- `;
1787
- return sql`
1788
- SELECT
1789
- json_extract(entry.value, '$.source_key') AS source_key,
1790
- json_extract(entry.value, '$.source_type') AS source_type,
1791
- json_extract(entry.value, '$.collection_id') AS collection_id,
1792
- json_extract(entry.value, '$.collection_slug') AS collection_slug,
1793
- json_extract(entry.value, '$.content_id') AS content_id,
1794
- json_extract(entry.value, '$.source_variant') AS source_variant,
1795
- json_extract(entry.value, '$.locale') AS locale,
1796
- json_extract(entry.value, '$.translation_group') AS translation_group,
1797
- json_extract(entry.value, '$.content_slug') AS content_slug,
1798
- json_extract(entry.value, '$.content_title') AS content_title,
1799
- json_extract(entry.value, '$.content_status') AS content_status,
1800
- json_extract(entry.value, '$.content_scheduled_at') AS content_scheduled_at,
1801
- json_extract(entry.value, '$.content_deleted_at') AS content_deleted_at,
1802
- json_extract(entry.value, '$.revision_id') AS revision_id,
1803
- json_extract(entry.value, '$.current_generation') AS current_generation,
1804
- CAST(json_extract(entry.value, '$.schema_version') AS integer) AS schema_version,
1805
- json_extract(entry.value, '$.source_updated_at') AS source_updated_at,
1806
- CAST(json_extract(entry.value, '$.source_version') AS integer) AS source_version,
1807
- json_extract(entry.value, '$.source_fingerprint') AS source_fingerprint,
1808
- CAST(json_extract(entry.value, '$.identity_version') AS integer) AS identity_version,
1809
- json_extract(entry.value, '$.source_completeness') AS source_completeness,
1810
- json_extract(entry.value, '$.last_attempted_at') AS last_attempted_at,
1811
- json_extract(entry.value, '$.last_error_code') AS last_error_code,
1812
- json_extract(entry.value, '$.indexed_at') AS indexed_at,
1813
- json_extract(entry.value, '$.updated_at') AS updated_at,
1814
- json_extract(entry.value, '$.lease_token') AS lease_token,
1815
- json_extract(entry.value, '$.expected_generation') AS expected_generation,
1816
- json_extract(entry.value, '$.expected_collection_id') AS expected_collection_id,
1817
- json_extract(entry.value, '$.expected_updated_at') AS expected_updated_at,
1818
- json_extract(entry.value, '$.expected_source_fingerprint') AS expected_source_fingerprint,
1819
- json_extract(entry.value, '$.expected_source_updated_at') AS expected_source_updated_at,
1820
- CAST(json_extract(entry.value, '$.expected_source_version') AS integer) AS expected_source_version,
1821
- CAST(json_extract(entry.value, '$.expected_identity_version') AS integer) AS expected_identity_version,
1822
- json_extract(entry.value, '$.expected_revision_id') AS expected_revision_id,
1823
- json_extract(entry.value, '$.expected_source_completeness') AS expected_source_completeness,
1824
- json_extract(entry.value, '$.expected_last_attempted_at') AS expected_last_attempted_at,
1825
- json_extract(entry.value, '$.expected_last_error_code') AS expected_last_error_code
1826
- FROM json_each(${payload}) AS entry
1827
- `;
1828
- }
1829
- generationWriteBatchInput(payload) {
1830
- if (isPostgres(this.db)) return sql`
1831
- SELECT
1832
- entry.value ->> 'source_key' AS source_key,
1833
- entry.value ->> 'collection_id' AS collection_id,
1834
- entry.value ->> 'collection_slug' AS collection_slug,
1835
- entry.value ->> 'generation' AS generation,
1836
- entry.value ->> 'lease_token' AS lease_token,
1837
- entry.value ->> 'expires_at' AS expires_at,
1838
- entry.value ->> 'created_at' AS created_at
1839
- FROM jsonb_array_elements(${payload}::jsonb) AS entry(value)
1840
- `;
1841
- return sql`
1842
- SELECT
1843
- json_extract(entry.value, '$.source_key') AS source_key,
1844
- json_extract(entry.value, '$.collection_id') AS collection_id,
1845
- json_extract(entry.value, '$.collection_slug') AS collection_slug,
1846
- json_extract(entry.value, '$.generation') AS generation,
1847
- json_extract(entry.value, '$.lease_token') AS lease_token,
1848
- json_extract(entry.value, '$.expires_at') AS expires_at,
1849
- json_extract(entry.value, '$.created_at') AS created_at
1850
- FROM json_each(${payload}) AS entry
1851
- `;
1852
- }
1853
- batchRefsMatch(left, right) {
1854
- const leftRef = sql.ref(left);
1855
- const rightRef = sql.ref(right);
1856
- return isPostgres(this.db) ? sql`${leftRef} IS NOT DISTINCT FROM ${rightRef}` : sql`${leftRef} IS ${rightRef}`;
1857
- }
1858
- async lockCanonicalSourceCollection(db, source) {
1859
- if (source.collectionId === void 0 || source.collectionId === null) return true;
1860
- if (!source.collectionSlug) return false;
1861
- if (!isPostgres(this.db)) return true;
1862
- return await db.selectFrom("_emdash_collections").select("id").where("id", "=", source.collectionId).where("slug", "=", source.collectionSlug).forKeyShare().executeTakeFirst() !== void 0;
1863
- }
1864
- async upsertSource(db, source, generation, now, leaseToken) {
1865
- const row = this.buildSourceRow(source, generation, now);
1866
- return this.persistSourceIfWriteLease(db, row, leaseToken, sql`
1867
- ON CONFLICT (source_key) DO UPDATE SET
1868
- source_type = excluded.source_type,
1869
- collection_id = excluded.collection_id,
1870
- collection_slug = excluded.collection_slug,
1871
- content_id = excluded.content_id,
1872
- source_variant = excluded.source_variant,
1873
- locale = excluded.locale,
1874
- translation_group = excluded.translation_group,
1875
- content_slug = excluded.content_slug,
1876
- content_title = excluded.content_title,
1877
- content_status = excluded.content_status,
1878
- content_scheduled_at = excluded.content_scheduled_at,
1879
- content_deleted_at = excluded.content_deleted_at,
1880
- revision_id = excluded.revision_id,
1881
- current_generation = excluded.current_generation,
1882
- schema_version = excluded.schema_version,
1883
- source_updated_at = excluded.source_updated_at,
1884
- source_version = excluded.source_version,
1885
- source_fingerprint = excluded.source_fingerprint,
1886
- identity_version = excluded.identity_version,
1887
- source_completeness = excluded.source_completeness,
1888
- last_attempted_at = excluded.last_attempted_at,
1889
- last_error_code = excluded.last_error_code,
1890
- indexed_at = excluded.indexed_at,
1891
- updated_at = excluded.updated_at
1892
- `);
1893
- }
1894
- async insertSourceIfAbsent(db, row, leaseToken) {
1895
- return this.persistSourceIfWriteLease(db, row, leaseToken, sql`ON CONFLICT (source_key) DO NOTHING`);
1896
- }
1897
- async persistSourceIfWriteLease(db, row, leaseToken, conflict) {
1898
- const result = await sql`
1899
- INSERT INTO _emdash_media_usage_sources (
1900
- source_key,
1901
- source_type,
1902
- collection_id,
1903
- collection_slug,
1904
- content_id,
1905
- source_variant,
1906
- locale,
1907
- translation_group,
1908
- content_slug,
1909
- content_title,
1910
- content_status,
1911
- content_scheduled_at,
1912
- content_deleted_at,
1913
- revision_id,
1914
- current_generation,
1915
- schema_version,
1916
- source_updated_at,
1917
- source_version,
1918
- source_fingerprint,
1919
- identity_version,
1920
- source_completeness,
1921
- last_attempted_at,
1922
- last_error_code,
1923
- indexed_at,
1924
- updated_at
1925
- )
1926
- SELECT
1927
- ${row.source_key},
1928
- ${row.source_type},
1929
- ${row.collection_id},
1930
- ${row.collection_slug},
1931
- ${row.content_id},
1932
- ${row.source_variant},
1933
- ${row.locale},
1934
- ${row.translation_group},
1935
- ${row.content_slug},
1936
- ${row.content_title},
1937
- ${row.content_status},
1938
- ${row.content_scheduled_at},
1939
- ${row.content_deleted_at},
1940
- ${row.revision_id},
1941
- ${row.current_generation},
1942
- ${row.schema_version},
1943
- ${row.source_updated_at},
1944
- ${row.source_version},
1945
- ${row.source_fingerprint},
1946
- ${row.identity_version},
1947
- ${row.source_completeness},
1948
- ${row.last_attempted_at},
1949
- ${row.last_error_code},
1950
- ${row.indexed_at},
1951
- ${row.updated_at}
1952
- WHERE EXISTS (
1953
- SELECT 1
1954
- FROM _emdash_media_usage_generation_writes
1955
- WHERE source_key = ${row.source_key}
1956
- AND generation = ${row.current_generation}
1957
- AND lease_token = ${leaseToken}
1958
- AND ${this.generationWriteLeaseExpiryIsInFuture("expires_at")}
1959
- )
1960
- AND ${this.currentCollectionExists(row.collection_id, row.collection_slug)}
1961
- AND ${this.currentCanonicalContentExists(row)}
1962
- ${conflict}
1963
- `.execute(db);
1964
- return Number(result.numAffectedRows ?? 0) > 0;
1965
- }
1966
- generationWriteLeaseExpression(row, leaseToken) {
1967
- return (eb) => eb.exists(eb.selectFrom("_emdash_media_usage_generation_writes").select("source_key").where("source_key", "=", row.source_key).where("generation", "=", row.current_generation).where("lease_token", "=", leaseToken).where(this.generationWriteLeaseExpiryIsInFuture("_emdash_media_usage_generation_writes.expires_at")));
1968
- }
1969
- generationWriteLeaseExpiryIsInFuture(column) {
1970
- const leaseExpiresAt = sql.ref(column);
1971
- return isPostgres(this.db) ? sql`${leaseExpiresAt}::timestamptz > clock_timestamp()` : sql`${leaseExpiresAt} > strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;
1972
- }
1973
- async withGenerationWriteLease(source, generation, write) {
1974
- const leaseToken = ulid();
1975
- const owner = (await sql`
1976
- INSERT INTO _emdash_media_usage_generation_writes (
1977
- source_key, generation, lease_token, expires_at, created_at
1978
- )
1979
- SELECT
1980
- ${source.sourceKey},
1981
- ${generation},
1982
- ${leaseToken},
1983
- ${this.generationWriteLeaseTimestampOffset(MEDIA_USAGE_GENERATION_WRITE_LEASE_MS / 1e3)},
1984
- ${this.generationWriteLeaseTimestampOffset(0)}
1985
- WHERE ${this.currentCollectionExists(source.collectionId ?? null, source.collectionSlug ?? null)}
1986
- RETURNING created_at
1987
- `.execute(this.db)).rows[0];
1988
- if (!owner) return false;
1989
- try {
1990
- await write(leaseToken, owner.created_at);
1991
- return true;
1992
- } finally {
1993
- try {
1994
- await this.db.deleteFrom("_emdash_media_usage_generation_writes").where("source_key", "=", source.sourceKey).where("generation", "=", generation).where("lease_token", "=", leaseToken).execute();
1995
- } catch (error) {
1996
- console.error("[media-usage] Failed to release generation write lease:", error);
1997
- }
1998
- }
1999
- }
2000
- generationWriteLeaseTimestampOffset(offsetSeconds) {
2001
- if (isPostgres(this.db)) return sql`to_char(
2002
- (clock_timestamp() AT TIME ZONE 'UTC') + (${offsetSeconds} * INTERVAL '1 second'),
2003
- 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
2004
- )`;
2005
- return sql`strftime(
2006
- '%Y-%m-%dT%H:%M:%fZ',
2007
- 'now',
2008
- ${`${offsetSeconds >= 0 ? "+" : ""}${offsetSeconds} seconds`}
2009
- )`;
2010
- }
2011
- async updateSourceIfGeneration(db, row, expectedCurrentGeneration, leaseToken) {
2012
- const result = await db.updateTable("_emdash_media_usage_sources").set(this.sourceUpdateSet(row)).where("source_key", "=", row.source_key).where("current_generation", "=", expectedCurrentGeneration).where(this.generationWriteLeaseExpression(row, leaseToken)).where(this.currentCollectionExists(row.collection_id, row.collection_slug)).where(this.currentCanonicalContentExists(row)).executeTakeFirst();
2013
- return Number(result.numUpdatedRows ?? 0) > 0;
2014
- }
2015
- async updateSourceIfMatching(db, row, expectedSource, leaseToken) {
2016
- const result = await db.updateTable("_emdash_media_usage_sources").set(this.sourceUpdateSet(row)).where("source_key", "=", row.source_key).where(this.sourceMatchExpression(expectedSource)).where(this.generationWriteLeaseExpression(row, leaseToken)).where(this.currentCollectionExists(row.collection_id, row.collection_slug)).where(this.currentCanonicalContentExists(row)).executeTakeFirst();
2017
- return Number(result.numUpdatedRows ?? 0) > 0;
2018
- }
2019
- async updateAttemptedSourceIfMatching(db, source, row, expectedSource) {
2020
- const result = await db.updateTable("_emdash_media_usage_sources").set(this.attemptedSourceUpdateSet(source, row)).where("source_key", "=", row.source_key).where(this.sourceMatchExpression(expectedSource)).where(this.currentCollectionExists(row.collection_id, row.collection_slug)).where(this.currentCanonicalContentExists(row)).executeTakeFirst();
2021
- return Number(result.numUpdatedRows ?? 0) > 0;
2022
- }
2023
- sourceMatchExpression(expectedSource) {
2024
- return (eb) => eb.and([
2025
- eb("current_generation", "=", expectedSource.currentGeneration),
2026
- eb("source_completeness", "=", expectedSource.sourceCompleteness),
2027
- this.nullableStringExpression(eb, "collection_id", expectedSource.collectionId),
2028
- this.nullableStringExpression(eb, "updated_at", expectedSource.updatedAt),
2029
- this.nullableStringExpression(eb, "source_fingerprint", expectedSource.sourceFingerprint),
2030
- this.nullableStringExpression(eb, "source_updated_at", expectedSource.sourceUpdatedAt),
2031
- this.nullableNumberExpression(eb, "source_version", expectedSource.sourceVersion),
2032
- this.nullableNumberExpression(eb, "identity_version", expectedSource.identityVersion),
2033
- this.nullableStringExpression(eb, "revision_id", expectedSource.revisionId),
2034
- this.nullableStringExpression(eb, "last_attempted_at", expectedSource.lastAttemptedAt),
2035
- this.nullableStringExpression(eb, "last_error_code", expectedSource.lastErrorCode)
2036
- ]);
2037
- }
2038
- async projectionMatchesCurrentGeneration(source, expectedCurrentGeneration) {
2039
- const fingerprint = source.sourceFingerprint;
2040
- if (!isMediaUsageProjectionFingerprint(fingerprint)) return false;
2041
- return await this.db.selectFrom("_emdash_media_usage_sources").select("source_key").where("source_key", "=", source.sourceKey).where("current_generation", "=", expectedCurrentGeneration).where("source_fingerprint", "=", fingerprint).where("source_completeness", "=", source.sourceCompleteness ?? "complete").where("last_error_code", "is", null).where(this.currentCollectionExists(source.collectionId ?? null, source.collectionSlug ?? null)).executeTakeFirst() !== void 0;
2042
- }
2043
- async projectionMatchesExpectedSource(source, expectedSource) {
2044
- const fingerprint = source.sourceFingerprint;
2045
- if (!isMediaUsageProjectionFingerprint(fingerprint) || expectedSource.sourceFingerprint !== fingerprint || expectedSource.sourceCompleteness !== (source.sourceCompleteness ?? "complete") || expectedSource.lastErrorCode !== null) return false;
2046
- return await this.db.selectFrom("_emdash_media_usage_sources").select("source_key").where("source_key", "=", source.sourceKey).where(this.sourceMatchExpression(expectedSource)).where(this.currentCollectionExists(source.collectionId ?? null, source.collectionSlug ?? null)).executeTakeFirst() !== void 0;
2047
- }
2048
- nullableStringExpression(eb, column, value) {
2049
- return value === null ? eb(column, "is", null) : eb(column, "=", value);
2050
- }
2051
- currentCollectionExists(collectionId, collectionSlug) {
2052
- if (collectionId === null) return sql`1 = 1`;
2053
- return sql`EXISTS (
2054
- SELECT 1
2055
- FROM _emdash_collections
2056
- WHERE id = ${collectionId}
2057
- AND slug = ${collectionSlug}
2058
- )`;
2059
- }
2060
- currentCanonicalContentExists(row) {
2061
- if (row.collection_id === null || row.identity_version !== 1 || row.source_type !== "content") return sql`1 = 1`;
2062
- if (!row.collection_slug || !row.content_id || row.source_version === null || row.source_updated_at === null) return sql`1 = 0`;
2063
- validateIdentifier(row.collection_slug, "collection slug");
2064
- const tableName = `ec_${row.collection_slug}`;
2065
- validateIdentifier(tableName, "content table");
2066
- const revisionColumn = row.source_variant === "columns" ? "live_revision_id" : row.source_variant === "draft_overlay" ? "draft_revision_id" : null;
2067
- if (!revisionColumn) return sql`1 = 0`;
2068
- const revision = sql.ref(`content.${revisionColumn}`);
2069
- const revisionMatches = row.revision_id === null ? sql`${revision} IS NULL` : sql`${revision} = ${row.revision_id}`;
2070
- return sql`EXISTS (
2071
- SELECT 1
2072
- FROM ${sql.ref(tableName)} AS content
2073
- WHERE content.id = ${row.content_id}
2074
- AND content.version = ${row.source_version}
2075
- AND content.updated_at = ${row.source_updated_at}
2076
- AND ${revisionMatches}
2077
- )`;
2078
- }
2079
- nullableNumberExpression(eb, column, value) {
2080
- return value === null ? eb(column, "is", null) : eb(column, "=", value);
2081
- }
2082
- async contentRowExists(tableName, contentId) {
2083
- return (await sql`
2084
- SELECT id
2085
- FROM ${sql.ref(tableName)}
2086
- WHERE id = ${contentId}
2087
- LIMIT 1
2088
- `.execute(this.db)).rows.length > 0;
2089
- }
2090
- buildSourceRow(source, generation, now) {
2091
- return {
2092
- source_key: source.sourceKey,
2093
- source_type: source.sourceType,
2094
- collection_id: source.collectionId ?? null,
2095
- collection_slug: source.collectionSlug ?? null,
2096
- content_id: source.contentId ?? null,
2097
- source_variant: source.sourceVariant,
2098
- locale: source.locale ?? null,
2099
- translation_group: source.translationGroup ?? null,
2100
- content_slug: source.contentSlug ?? null,
2101
- content_title: source.contentTitle ?? null,
2102
- content_status: source.contentStatus ?? null,
2103
- content_scheduled_at: source.contentScheduledAt ?? null,
2104
- content_deleted_at: source.contentDeletedAt ?? null,
2105
- revision_id: source.revisionId ?? null,
2106
- current_generation: generation,
2107
- schema_version: source.schemaVersion ?? 1,
2108
- source_updated_at: source.sourceUpdatedAt ?? null,
2109
- source_version: source.sourceVersion ?? null,
2110
- source_fingerprint: source.sourceFingerprint ?? null,
2111
- identity_version: source.identityVersion ?? null,
2112
- source_completeness: source.sourceCompleteness ?? "complete",
2113
- last_attempted_at: source.lastAttemptedAt ?? now,
2114
- last_error_code: null,
2115
- indexed_at: now,
2116
- updated_at: now
2117
- };
2118
- }
2119
- buildAttemptedSourceRow(source, generation, now) {
2120
- return {
2121
- source_key: source.sourceKey,
2122
- source_type: source.sourceType,
2123
- collection_id: source.collectionId ?? null,
2124
- collection_slug: source.collectionSlug ?? null,
2125
- content_id: source.contentId ?? null,
2126
- source_variant: source.sourceVariant,
2127
- locale: source.locale ?? null,
2128
- translation_group: source.translationGroup ?? null,
2129
- content_slug: source.contentSlug ?? null,
2130
- content_title: source.contentTitle ?? null,
2131
- content_status: source.contentStatus ?? null,
2132
- content_scheduled_at: source.contentScheduledAt ?? null,
2133
- content_deleted_at: source.contentDeletedAt ?? null,
2134
- revision_id: source.revisionId ?? null,
2135
- current_generation: generation,
2136
- schema_version: source.schemaVersion ?? 1,
2137
- source_updated_at: source.sourceUpdatedAt ?? null,
2138
- source_version: source.sourceVersion ?? null,
2139
- source_fingerprint: source.sourceFingerprint ?? null,
2140
- identity_version: source.identityVersion ?? null,
2141
- source_completeness: source.sourceCompleteness ?? (source.lastErrorCode ? "failed" : "unknown"),
2142
- last_attempted_at: source.lastAttemptedAt ?? now,
2143
- last_error_code: source.lastErrorCode ?? null,
2144
- indexed_at: now,
2145
- updated_at: now
2146
- };
2147
- }
2148
- attemptedSourceUpdateSet(source, row) {
2149
- const updates = {
2150
- source_type: row.source_type,
2151
- source_variant: row.source_variant,
2152
- source_completeness: row.source_completeness,
2153
- last_attempted_at: row.last_attempted_at,
2154
- last_error_code: row.last_error_code,
2155
- updated_at: row.updated_at
2156
- };
2157
- if (source.collectionSlug !== void 0) updates.collection_slug = row.collection_slug;
2158
- if (source.collectionId !== void 0) updates.collection_id = row.collection_id;
2159
- if (source.contentId !== void 0) updates.content_id = row.content_id;
2160
- if (source.locale !== void 0) updates.locale = row.locale;
2161
- if (source.translationGroup !== void 0) updates.translation_group = row.translation_group;
2162
- if (source.contentSlug !== void 0) updates.content_slug = row.content_slug;
2163
- if (source.contentTitle !== void 0) updates.content_title = row.content_title;
2164
- if (source.contentStatus !== void 0) updates.content_status = row.content_status;
2165
- if (source.contentScheduledAt !== void 0) updates.content_scheduled_at = row.content_scheduled_at;
2166
- if (source.contentDeletedAt !== void 0) updates.content_deleted_at = row.content_deleted_at;
2167
- if (source.revisionId !== void 0) updates.revision_id = row.revision_id;
2168
- if (source.schemaVersion !== void 0) updates.schema_version = row.schema_version;
2169
- if (source.sourceUpdatedAt !== void 0) updates.source_updated_at = row.source_updated_at;
2170
- if (source.sourceVersion !== void 0) updates.source_version = row.source_version;
2171
- if (source.sourceFingerprint !== void 0) updates.source_fingerprint = row.source_fingerprint;
2172
- if (source.identityVersion !== void 0) updates.identity_version = row.identity_version;
2173
- return updates;
2174
- }
2175
- sourceUpdateSet(row) {
2176
- return {
2177
- source_type: row.source_type,
2178
- collection_id: row.collection_id,
2179
- collection_slug: row.collection_slug,
2180
- content_id: row.content_id,
2181
- source_variant: row.source_variant,
2182
- locale: row.locale,
2183
- translation_group: row.translation_group,
2184
- content_slug: row.content_slug,
2185
- content_title: row.content_title,
2186
- content_status: row.content_status,
2187
- content_scheduled_at: row.content_scheduled_at,
2188
- content_deleted_at: row.content_deleted_at,
2189
- revision_id: row.revision_id,
2190
- current_generation: row.current_generation,
2191
- schema_version: row.schema_version,
2192
- source_updated_at: row.source_updated_at,
2193
- source_version: row.source_version,
2194
- source_fingerprint: row.source_fingerprint,
2195
- identity_version: row.identity_version,
2196
- source_completeness: row.source_completeness,
2197
- last_attempted_at: row.last_attempted_at,
2198
- last_error_code: row.last_error_code,
2199
- indexed_at: row.indexed_at,
2200
- updated_at: row.updated_at
2201
- };
2202
- }
2203
- };
2204
- const currentUsageSelect = [
2205
- "s.source_key as source_key",
2206
- "s.source_type as source_type",
2207
- "s.collection_id as collection_id",
2208
- "s.collection_slug as collection_slug",
2209
- "s.content_id as content_id",
2210
- "s.source_variant as source_variant",
2211
- "s.locale as locale",
2212
- "s.translation_group as translation_group",
2213
- "s.content_slug as content_slug",
2214
- "s.content_title as content_title",
2215
- "s.content_status as content_status",
2216
- "s.content_scheduled_at as content_scheduled_at",
2217
- "s.content_deleted_at as content_deleted_at",
2218
- "s.revision_id as revision_id",
2219
- "s.current_generation as current_generation",
2220
- "s.schema_version as schema_version",
2221
- "s.source_updated_at as source_updated_at",
2222
- "s.source_version as source_version",
2223
- "s.source_fingerprint as source_fingerprint",
2224
- "s.identity_version as identity_version",
2225
- "s.source_completeness as source_completeness",
2226
- "s.last_attempted_at as last_attempted_at",
2227
- "s.last_error_code as last_error_code",
2228
- "s.indexed_at as indexed_at",
2229
- "s.created_at as source_created_at",
2230
- "s.updated_at as source_row_updated_at",
2231
- "u.id as occurrence_id",
2232
- "u.generation as generation",
2233
- "u.field_slug as field_slug",
2234
- "u.field_path as field_path",
2235
- "u.occurrence_index as occurrence_index",
2236
- "u.reference_type as reference_type",
2237
- "u.media_id as media_id",
2238
- "u.provider as provider",
2239
- "u.provider_asset_id as provider_asset_id",
2240
- "u.media_kind as media_kind",
2241
- "u.mime_type as mime_type",
2242
- "u.created_at as occurrence_created_at"
2243
- ];
2244
- function groupUsageRows(rows) {
2245
- const groups = [];
2246
- for (const row of rows) {
2247
- if (row.collection_slug === null || row.content_id === null) continue;
2248
- const record = rowToUsageRecord(row);
2249
- let group = groups.at(-1);
2250
- if (!group || group.collectionSlug !== row.collection_slug || group.contentId !== row.content_id) {
2251
- group = {
2252
- collectionSlug: row.collection_slug,
2253
- contentId: row.content_id,
2254
- contentDeletedAt: row.entry_deleted_at,
2255
- sources: []
2256
- };
2257
- groups.push(group);
2258
- }
2259
- let source = group.sources.at(-1);
2260
- if (!source || source.source.sourceKey !== record.source.sourceKey) {
2261
- source = {
2262
- source: record.source,
2263
- occurrences: []
2264
- };
2265
- group.sources.push(source);
2266
- }
2267
- source.occurrences.push(record.occurrence);
2268
- }
2269
- return groups;
2270
- }
2271
- function rowToSource(row) {
2272
- return {
2273
- sourceKey: row.source_key,
2274
- sourceType: row.source_type,
2275
- collectionId: row.collection_id,
2276
- collectionSlug: row.collection_slug,
2277
- contentId: row.content_id,
2278
- sourceVariant: row.source_variant,
2279
- locale: row.locale,
2280
- translationGroup: row.translation_group,
2281
- contentSlug: row.content_slug,
2282
- contentTitle: row.content_title,
2283
- contentStatus: row.content_status,
2284
- contentScheduledAt: row.content_scheduled_at,
2285
- contentDeletedAt: row.content_deleted_at,
2286
- revisionId: row.revision_id,
2287
- currentGeneration: row.current_generation,
2288
- schemaVersion: Number(row.schema_version),
2289
- sourceUpdatedAt: row.source_updated_at,
2290
- sourceVersion: row.source_version === null ? null : Number(row.source_version),
2291
- sourceFingerprint: row.source_fingerprint,
2292
- identityVersion: row.identity_version === null ? null : Number(row.identity_version),
2293
- sourceCompleteness: row.source_completeness,
2294
- lastAttemptedAt: row.last_attempted_at,
2295
- lastErrorCode: row.last_error_code,
2296
- indexedAt: row.indexed_at,
2297
- createdAt: row.created_at,
2298
- updatedAt: row.updated_at
2299
- };
2300
- }
2301
- function rowToOccurrence(row) {
2302
- return {
2303
- id: row.id,
2304
- sourceKey: row.source_key,
2305
- generation: row.generation,
2306
- fieldSlug: row.field_slug,
2307
- fieldPath: row.field_path,
2308
- occurrenceIndex: Number(row.occurrence_index),
2309
- referenceType: row.reference_type,
2310
- mediaId: row.media_id,
2311
- provider: row.provider,
2312
- providerAssetId: row.provider_asset_id,
2313
- mediaKind: row.media_kind,
2314
- mimeType: row.mime_type,
2315
- createdAt: row.created_at
2316
- };
2317
- }
2318
- function rowToUsageRecord(row) {
2319
- return {
2320
- source: rowToSource({
2321
- source_key: row.source_key,
2322
- source_type: row.source_type,
2323
- collection_id: row.collection_id,
2324
- collection_slug: row.collection_slug,
2325
- content_id: row.content_id,
2326
- source_variant: row.source_variant,
2327
- locale: row.locale,
2328
- translation_group: row.translation_group,
2329
- content_slug: row.content_slug,
2330
- content_title: row.content_title,
2331
- content_status: row.content_status,
2332
- content_scheduled_at: row.content_scheduled_at,
2333
- content_deleted_at: row.content_deleted_at,
2334
- revision_id: row.revision_id,
2335
- current_generation: row.current_generation,
2336
- schema_version: row.schema_version,
2337
- source_updated_at: row.source_updated_at,
2338
- source_version: row.source_version,
2339
- source_fingerprint: row.source_fingerprint,
2340
- identity_version: row.identity_version,
2341
- source_completeness: row.source_completeness,
2342
- last_attempted_at: row.last_attempted_at,
2343
- last_error_code: row.last_error_code,
2344
- indexed_at: row.indexed_at,
2345
- created_at: row.source_created_at,
2346
- updated_at: row.source_row_updated_at
2347
- }),
2348
- occurrence: rowToOccurrence({
2349
- id: row.occurrence_id,
2350
- source_key: row.source_key,
2351
- generation: row.generation,
2352
- field_slug: row.field_slug,
2353
- field_path: row.field_path,
2354
- occurrence_index: row.occurrence_index,
2355
- reference_type: row.reference_type,
2356
- media_id: row.media_id,
2357
- provider: row.provider,
2358
- provider_asset_id: row.provider_asset_id,
2359
- media_kind: row.media_kind,
2360
- mime_type: row.mime_type,
2361
- created_at: row.occurrence_created_at,
2362
- cleanup_lease_token: null
2363
- })
2364
- };
2365
- }
2366
- function rowToIndexStatus(row) {
2367
- return {
2368
- adapterId: row.adapter_id,
2369
- scopeType: row.scope_type,
2370
- scopeKey: row.scope_key,
2371
- status: row.status,
2372
- schemaVersion: Number(row.schema_version),
2373
- startedAt: row.started_at,
2374
- completedAt: row.completed_at,
2375
- cursor: row.cursor,
2376
- indexedSourceCount: Number(row.indexed_source_count),
2377
- failedSourceCount: Number(row.failed_source_count),
2378
- lastErrorCode: row.last_error_code,
2379
- updatedAt: row.updated_at
2380
- };
2381
- }
2382
-
2383
- //#endregion
2384
- //#region src/media/usage/content-fields.ts
2385
- async function buildContentMediaUsageFieldFingerprint(discovery) {
2386
- return (await buildCanonicalSha256Fingerprint("media-usage-fields:v1:sha256:", {
2387
- fingerprintVersion: 1,
2388
- contentSourceSchemaVersion: CONTENT_SOURCE_SCHEMA_VERSION,
2389
- extractionFields: discovery.extractionFields.map((field) => ({
2390
- slug: field.slug,
2391
- type: field.type,
2392
- ...field.type === "repeater" ? { subFields: (field.validation?.subFields ?? []).map((subField) => ({
2393
- slug: subField.slug,
2394
- type: subField.type
2395
- })).toSorted(compareFieldIdentity) } : {}
2396
- })).toSorted(compareFieldIdentity),
2397
- displayFieldSlugs: discovery.displayFieldSlugs.toSorted(compareStrings)
2398
- })).fingerprint;
2399
- }
2400
- var MediaUsageFieldDiscoveryError = class extends Error {
2401
- constructor(message, code) {
2402
- super(message);
2403
- this.code = code;
2404
- this.name = "MediaUsageFieldDiscoveryError";
2405
- }
2406
- };
2407
- const DISPLAY_FIELD_SLUGS = ["title", "name"];
2408
- const SUPPORTED_TOP_LEVEL_TYPES = [
2409
- "file",
2410
- "image",
2411
- "portableText"
2412
- ];
2413
- async function loadContentMediaUsageFields(db, collectionSlug, collectionId) {
2414
- validateIdentifier(collectionSlug, "collection slug");
2415
- let query = db.selectFrom("_emdash_fields").innerJoin("_emdash_collections", "_emdash_collections.id", "_emdash_fields.collection_id").select([
2416
- "_emdash_fields.slug",
2417
- "_emdash_fields.type",
2418
- "_emdash_fields.validation"
2419
- ]).where("_emdash_collections.slug", "=", collectionSlug);
2420
- if (collectionId !== void 0) query = query.where("_emdash_collections.id", "=", collectionId);
2421
- const rows = await query.execute();
2422
- const extractionFields = [];
2423
- const rowBySlug = /* @__PURE__ */ new Map();
2424
- for (const row of rows) {
2425
- rowBySlug.set(row.slug, row);
2426
- if (isSupportedTopLevelType(row.type)) {
2427
- validateIdentifier(row.slug, "media usage field slug");
2428
- extractionFields.push({
2429
- slug: row.slug,
2430
- type: row.type
2431
- });
2432
- continue;
2433
- }
2434
- if (row.type === "repeater") {
2435
- validateIdentifier(row.slug, "media usage field slug");
2436
- const subFields = normalizeRepeaterImageSubFields(row.validation);
2437
- if (subFields.length > 0) extractionFields.push({
2438
- slug: row.slug,
2439
- type: "repeater",
2440
- validation: { subFields }
2441
- });
2442
- }
2443
- }
2444
- extractionFields.sort((a, b) => a.slug.localeCompare(b.slug));
2445
- return {
2446
- extractionFields,
2447
- displayFieldSlugs: DISPLAY_FIELD_SLUGS.filter((slug) => {
2448
- if (!rowBySlug.has(slug)) return false;
2449
- validateIdentifier(slug, "media usage display field slug");
2450
- return true;
2451
- })
2452
- };
2453
- }
2454
- function normalizeRepeaterImageSubFields(rawValidation) {
2455
- const validation = parseValidation(rawValidation);
2456
- if (!isRecord$2(validation) || !Array.isArray(validation.subFields)) return [];
2457
- const subFields = [];
2458
- for (const subField of validation.subFields) {
2459
- if (!isRecord$2(subField) || subField.type !== "image") continue;
2460
- if (typeof subField.slug !== "string") continue;
2461
- validateIdentifier(subField.slug, "media usage repeater sub-field slug");
2462
- subFields.push({
2463
- slug: subField.slug,
2464
- type: "image"
2465
- });
2466
- }
2467
- return subFields.toSorted((a, b) => a.slug.localeCompare(b.slug));
2468
- }
2469
- function parseValidation(rawValidation) {
2470
- if (!rawValidation) return null;
2471
- try {
2472
- return JSON.parse(rawValidation);
2473
- } catch {
2474
- throw new MediaUsageFieldDiscoveryError("Repeater field validation must be valid JSON before media usage can be discovered", "INVALID_REPEATER_VALIDATION");
2475
- }
2476
- }
2477
- function isSupportedTopLevelType(value) {
2478
- return SUPPORTED_TOP_LEVEL_TYPES.includes(value);
2479
- }
2480
- function isRecord$2(value) {
2481
- return typeof value === "object" && value !== null && !Array.isArray(value);
2482
- }
2483
- function compareFieldIdentity(a, b) {
2484
- return compareStrings(a.slug, b.slug);
2485
- }
2486
- function compareStrings(a, b) {
2487
- return a < b ? -1 : a > b ? 1 : 0;
2488
- }
2489
-
2490
- //#endregion
2491
- //#region src/media/usage/extractor.ts
2492
- const URL_LIKE_RE = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
2493
- function extractMediaUsageOccurrences({ fields, data }) {
2494
- const occurrences = [];
2495
- const seen = /* @__PURE__ */ new Set();
2496
- for (const field of fields) {
2497
- const value = data[field.slug];
2498
- if (field.type === "image") {
2499
- addImageOccurrences(occurrences, seen, field.slug, field.slug, value);
2500
- continue;
2501
- }
2502
- if (field.type === "file") {
2503
- addOccurrence(occurrences, seen, {
2504
- fieldSlug: field.slug,
2505
- fieldPath: field.slug,
2506
- referenceType: "file_field",
2507
- value,
2508
- fallbackKind: null
2509
- });
2510
- continue;
2511
- }
2512
- if (field.type === "repeater") {
2513
- extractRepeaterOccurrences(occurrences, seen, field.slug, value, field.validation?.subFields);
2514
- continue;
2515
- }
2516
- if (field.type === "portableText") extractPortableTextOccurrences(occurrences, seen, field.slug, value);
2517
- }
2518
- return occurrences;
2519
- }
2520
- function extractRepeaterOccurrences(occurrences, seen, fieldSlug, value, subFields) {
2521
- if (!Array.isArray(value) || !Array.isArray(subFields)) return;
2522
- for (const [itemIndex, item] of value.entries()) {
2523
- if (!isRecord$1(item)) continue;
2524
- for (const subField of subFields) {
2525
- if (subField.type !== "image") continue;
2526
- addImageOccurrences(occurrences, seen, fieldSlug, `${fieldSlug}[${itemIndex}].${subField.slug}`, item[subField.slug]);
2527
- }
2528
- }
2529
- }
2530
- function addImageOccurrences(occurrences, seen, fieldSlug, fieldPath, value) {
2531
- addOccurrence(occurrences, seen, {
2532
- fieldSlug,
2533
- fieldPath,
2534
- referenceType: "image_field",
2535
- value,
2536
- fallbackKind: "image"
2537
- });
2538
- if (isRecord$1(value) && value.darkVariant != null) addOccurrence(occurrences, seen, {
2539
- fieldSlug,
2540
- fieldPath: `${fieldPath}.darkVariant`,
2541
- referenceType: "image_field",
2542
- value: value.darkVariant,
2543
- fallbackKind: "image"
2544
- });
2545
- }
2546
- function extractPortableTextOccurrences(occurrences, seen, fieldSlug, value) {
2547
- if (!Array.isArray(value)) return;
2548
- for (const [blockIndex, block] of value.entries()) {
2549
- if (!isRecord$1(block)) continue;
2550
- if (block._type === "image") {
2551
- addPortableTextAssetOccurrence(occurrences, seen, fieldSlug, `${fieldSlug}[${blockIndex}]`, block.asset);
2552
- continue;
2553
- }
2554
- if (block._type === "gallery" && Array.isArray(block.images)) for (const [imageIndex, image] of block.images.entries()) {
2555
- if (!isRecord$1(image)) continue;
2556
- addPortableTextAssetOccurrence(occurrences, seen, fieldSlug, `${fieldSlug}[${blockIndex}].images[${imageIndex}]`, image.asset);
2557
- }
2558
- }
2559
- }
2560
- function addPortableTextAssetOccurrence(occurrences, seen, fieldSlug, pathPrefix, asset) {
2561
- if (!isRecord$1(asset)) return;
2562
- const provider = normalizeProvider(asset.provider);
2563
- const ref = readPortableTextAssetRef(asset, provider);
2564
- if (!ref) return;
2565
- addRefOccurrence(occurrences, seen, {
2566
- fieldSlug,
2567
- fieldPath: `${pathPrefix}.asset.${ref.key}`,
2568
- referenceType: "portable_text_image",
2569
- ref: buildMediaRef({
2570
- id: ref.id,
2571
- provider,
2572
- mimeType: normalizeMimeValue(asset.mimeType),
2573
- fallbackKind: "image"
2574
- })
2575
- });
2576
- }
2577
- function addOccurrence(occurrences, seen, input) {
2578
- const ref = readMediaRef(input.value, input.fallbackKind);
2579
- if (!ref) return;
2580
- addRefOccurrence(occurrences, seen, {
2581
- fieldSlug: input.fieldSlug,
2582
- fieldPath: input.fieldPath,
2583
- referenceType: input.referenceType,
2584
- ref
2585
- });
2586
- }
2587
- function addRefOccurrence(occurrences, seen, input) {
2588
- if (!input.ref) return;
2589
- const occurrence = {
2590
- fieldSlug: input.fieldSlug,
2591
- fieldPath: input.fieldPath,
2592
- occurrenceIndex: 0,
2593
- referenceType: input.referenceType,
2594
- mediaId: input.ref.mediaId,
2595
- provider: input.ref.provider,
2596
- providerAssetId: input.ref.providerAssetId,
2597
- mediaKind: input.ref.mediaKind,
2598
- mimeType: input.ref.mimeType
2599
- };
2600
- const key = [
2601
- occurrence.fieldSlug,
2602
- occurrence.fieldPath,
2603
- occurrence.occurrenceIndex,
2604
- occurrence.referenceType,
2605
- occurrence.provider,
2606
- occurrence.providerAssetId,
2607
- occurrence.mediaId ?? ""
2608
- ].join("\0");
2609
- if (seen.has(key)) return;
2610
- seen.add(key);
2611
- occurrences.push(occurrence);
2612
- }
2613
- function readMediaRef(value, fallbackKind) {
2614
- if (typeof value === "string") {
2615
- const id = normalizeLocalMediaId(value);
2616
- return id ? buildMediaRef({
2617
- id,
2618
- provider: "local",
2619
- mimeType: null,
2620
- fallbackKind
2621
- }) : null;
2622
- }
2623
- if (!isRecord$1(value)) return null;
2624
- const provider = normalizeProvider(value.provider);
2625
- const id = provider === "local" ? normalizeLocalMediaId(value.id) : normalizeStableId(value.id);
2626
- if (!id) return null;
2627
- return buildMediaRef({
2628
- id,
2629
- provider,
2630
- mimeType: normalizeMimeValue(value.mimeType),
2631
- fallbackKind
2632
- });
2633
- }
2634
- function buildMediaRef(input) {
2635
- const provider = normalizeProvider(input.provider);
2636
- if (provider === "external") return null;
2637
- return {
2638
- mediaId: provider === "local" ? input.id : null,
2639
- provider,
2640
- providerAssetId: input.id,
2641
- mediaKind: mediaKindFromMime(input.mimeType) ?? input.fallbackKind,
2642
- mimeType: input.mimeType
2643
- };
2644
- }
2645
- function readPortableTextAssetRef(asset, provider) {
2646
- const normalizeId = provider === "local" ? normalizeLocalMediaId : normalizeStableId;
2647
- const ref = normalizeId(asset._ref);
2648
- if (ref) return {
2649
- key: "_ref",
2650
- id: ref
2651
- };
2652
- const id = normalizeId(asset.id);
2653
- if (id) return {
2654
- key: "id",
2655
- id
2656
- };
2657
- return null;
2658
- }
2659
- function normalizeProvider(value) {
2660
- return readString$1(value)?.trim() || "local";
2661
- }
2662
- function normalizeLocalMediaId(value) {
2663
- const id = normalizeStableId(value);
2664
- if (!id) return null;
2665
- return id.includes("/") ? null : id;
2666
- }
2667
- function normalizeStableId(value) {
2668
- if (typeof value !== "string") return null;
2669
- const trimmed = value.trim();
2670
- if (!trimmed) return null;
2671
- if (URL_LIKE_RE.test(trimmed)) return null;
2672
- if (trimmed.startsWith(INTERNAL_MEDIA_PREFIX)) return null;
2673
- return trimmed;
2674
- }
2675
- function normalizeMimeValue(value) {
2676
- if (typeof value !== "string") return null;
2677
- const normalized = normalizeMime(value);
2678
- return normalized.includes("/") ? normalized : null;
2679
- }
2680
- function mediaKindFromMime(mimeType) {
2681
- if (!mimeType) return null;
2682
- if (mimeType.startsWith("image/")) return "image";
2683
- if (mimeType.startsWith("video/")) return "video";
2684
- if (mimeType.startsWith("audio/")) return "audio";
2685
- if (mimeType.startsWith("font/") || mimeType.startsWith("application/font-")) return "font";
2686
- if (mimeType.startsWith("text/")) return "text";
2687
- if (isDocumentMime(mimeType)) return "document";
2688
- if (isArchiveMime(mimeType)) return "archive";
2689
- return "other";
2690
- }
2691
- function isDocumentMime(mimeType) {
2692
- return mimeType === "application/pdf" || mimeType === "application/msword" || mimeType === "application/rtf" || mimeType === "application/vnd.ms-excel" || mimeType === "application/vnd.ms-powerpoint" || mimeType.startsWith("application/vnd.openxmlformats-officedocument.");
2693
- }
2694
- function isArchiveMime(mimeType) {
2695
- return mimeType === "application/zip" || mimeType === "application/gzip" || mimeType === "application/x-tar" || mimeType === "application/x-7z-compressed" || mimeType === "application/x-rar-compressed" || mimeType === "application/vnd.rar";
2696
- }
2697
- function readString$1(value) {
2698
- return typeof value === "string" ? value : null;
2699
- }
2700
- function isRecord$1(value) {
2701
- return typeof value === "object" && value !== null && !Array.isArray(value);
2702
- }
2703
-
2704
- //#endregion
2705
- //#region src/media/usage/source-key.ts
2706
- const MEDIA_USAGE_CONTENT_SOURCE_VARIANTS = ["columns", "draft_overlay"];
2707
- function buildContentMediaUsageSourceKey(input) {
2708
- if (input.collectionId) return `content:${input.collectionId}:${input.contentId}:${input.sourceVariant}`;
2709
- return `content:${input.collectionSlug}:${input.contentId}:${input.sourceVariant}`;
2710
- }
2711
-
2712
- //#endregion
2713
- //#region src/media/usage/content-snapshots.ts
2714
- const CONTENT_COLLECTION_ID_RESULT = "__emdash_media_usage_collection_id";
2715
- const CONTENT_SYSTEM_COLUMNS = [
2716
- "id",
2717
- "slug",
2718
- "status",
2719
- "created_at",
2720
- "updated_at",
2721
- "published_at",
2722
- "scheduled_at",
2723
- "deleted_at",
2724
- "version",
2725
- "live_revision_id",
2726
- "draft_revision_id",
2727
- "locale",
2728
- "translation_group"
2729
- ];
2730
- async function loadContentMediaUsageSnapshots(db, collectionSlug, contentId, fieldDiscovery, options = {}) {
2731
- return (await loadContentMediaUsageSnapshotsBatch(db, collectionSlug, [contentId], fieldDiscovery, options)).get(contentId) ?? {
2732
- success: false,
2733
- error: "CONTENT_NOT_FOUND"
2734
- };
2735
- }
2736
- async function loadContentMediaUsageSnapshotsBatch(db, collectionSlug, contentIds, fieldDiscovery, options = {}, control = {}) {
2737
- validateIdentifier(collectionSlug, "collection slug");
2738
- if (options.identityVersion !== void 0 && !options.collectionId) throw new Error("Canonical media usage snapshots require a collection identity");
2739
- const discovery = fieldDiscovery ?? await loadContentMediaUsageFields(db, collectionSlug);
2740
- const rows = await loadContentRows(db, collectionSlug, contentIds, [...discovery.extractionFields.map((field) => field.slug), ...discovery.displayFieldSlugs], options.collectionId);
2741
- const revisions = await loadRevisionRows(db, Array.from(rows.values(), (row) => readNullableString(row.draft_revision_id)).filter((revisionId) => revisionId !== null));
2742
- const results = /* @__PURE__ */ new Map();
2743
- let occurrenceCount = 0;
2744
- let projectionBytes = 0;
2745
- for (const contentId of new Set(contentIds)) {
2746
- if (control.shouldContinue && !control.shouldContinue()) break;
2747
- const row = rows.get(contentId);
2748
- const result = row ? await buildContentMediaUsageSnapshots(row, collectionSlug, discovery, options, revisions) : {
2749
- success: false,
2750
- error: "CONTENT_NOT_FOUND"
2751
- };
2752
- const snapshots = result.success ? result.snapshots : result.snapshots ?? [];
2753
- const itemOccurrenceCount = snapshots.reduce((total, snapshot) => total + snapshot.occurrences.length, 0);
2754
- const itemProjectionBytes = snapshots.reduce((total, snapshot) => total + snapshot.projectionByteLength, 0);
2755
- if (results.size > 0 && (control.maxOccurrenceCount !== void 0 && occurrenceCount + itemOccurrenceCount > control.maxOccurrenceCount || control.maxProjectionBytes !== void 0 && projectionBytes + itemProjectionBytes > control.maxProjectionBytes)) break;
2756
- results.set(contentId, result);
2757
- occurrenceCount += itemOccurrenceCount;
2758
- projectionBytes += itemProjectionBytes;
2759
- }
2760
- return results;
2761
- }
2762
- async function buildContentMediaUsageSnapshots(row, collectionSlug, discovery, options, revisions) {
2763
- const collectionId = readString(row[CONTENT_COLLECTION_ID_RESULT]);
2764
- if (!collectionId) throw new Error("Media usage snapshot query did not return a collection identity");
2765
- const columnsData = projectData(row, discovery.extractionFields.map((field) => field.slug));
2766
- const displayData = projectRawData(row, discovery.displayFieldSlugs);
2767
- const occurrences = extractMediaUsageOccurrences({
2768
- fields: discovery.extractionFields,
2769
- data: columnsData
2770
- });
2771
- const columnsRevisionId = readNullableString(row.live_revision_id);
2772
- const columnsSource = buildContentSource({
2773
- collectionId: options.collectionId,
2774
- collectionSlug,
2775
- identityVersion: options.identityVersion,
2776
- row,
2777
- displayData,
2778
- sourceVariant: "columns",
2779
- revisionId: columnsRevisionId
2780
- });
2781
- const columnsProjection = await buildMediaUsageProjectionFingerprint({
2782
- collectionId,
2783
- source: columnsSource,
2784
- occurrences,
2785
- extractionFields: discovery.extractionFields
2786
- });
2787
- columnsSource.sourceFingerprint = columnsProjection.fingerprint;
2788
- const snapshots = [{
2789
- source: columnsSource,
2790
- occurrences,
2791
- fields: discovery.extractionFields,
2792
- projectionByteLength: columnsProjection.byteLength
2793
- }];
2794
- const draftRevisionId = readNullableString(row.draft_revision_id);
2795
- if (draftRevisionId) {
2796
- const attemptedDraftSource = buildContentSource({
2797
- collectionId: options.collectionId,
2798
- collectionSlug,
2799
- identityVersion: options.identityVersion,
2800
- row,
2801
- displayData,
2802
- sourceVariant: "draft_overlay",
2803
- revisionId: draftRevisionId
2804
- });
2805
- const revisionResult = revisions.get(draftRevisionId);
2806
- if (!revisionResult) return {
2807
- success: false,
2808
- error: "DRAFT_REVISION_NOT_FOUND",
2809
- source: attemptedDraftSource,
2810
- snapshots
2811
- };
2812
- if (!revisionResult.success) return {
2813
- success: false,
2814
- error: "DRAFT_REVISION_INVALID",
2815
- source: attemptedDraftSource,
2816
- snapshots
2817
- };
2818
- const revision = revisionResult.revision;
2819
- if (revision.collection !== collectionSlug || revision.entryId !== row.id) return {
2820
- success: false,
2821
- error: "DRAFT_REVISION_MISMATCH",
2822
- source: attemptedDraftSource,
2823
- snapshots
2824
- };
2825
- const revisionData = stripRevisionMetadata(revision.data);
2826
- const draftOverlayData = {
2827
- ...columnsData,
2828
- ...revisionData
2829
- };
2830
- const draftDisplayData = {
2831
- ...displayData,
2832
- ...projectPresentData(revisionData, discovery.displayFieldSlugs)
2833
- };
2834
- const draftContentSlug = readNullableString(revision.data._slug) ?? readNullableString(row.slug);
2835
- const draftOccurrences = extractMediaUsageOccurrences({
2836
- fields: discovery.extractionFields,
2837
- data: draftOverlayData
2838
- });
2839
- const draftSource = buildContentSource({
2840
- collectionId: options.collectionId,
2841
- collectionSlug,
2842
- identityVersion: options.identityVersion,
2843
- row,
2844
- displayData: draftDisplayData,
2845
- sourceVariant: "draft_overlay",
2846
- revisionId: draftRevisionId,
2847
- contentSlug: draftContentSlug
2848
- });
2849
- const draftProjection = await buildMediaUsageProjectionFingerprint({
2850
- collectionId,
2851
- source: draftSource,
2852
- occurrences: draftOccurrences,
2853
- extractionFields: discovery.extractionFields
2854
- });
2855
- draftSource.sourceFingerprint = draftProjection.fingerprint;
2856
- snapshots.push({
2857
- source: draftSource,
2858
- occurrences: draftOccurrences,
2859
- fields: discovery.extractionFields,
2860
- projectionByteLength: draftProjection.byteLength
2861
- });
2862
- }
2863
- return {
2864
- success: true,
2865
- snapshots
2866
- };
2867
- }
2868
- async function loadContentRows(db, collectionSlug, contentIds, fieldSlugs, expectedCollectionId) {
2869
- const tableName = getContentTableName(collectionSlug);
2870
- const columnRefs = uniqueColumns([...CONTENT_SYSTEM_COLUMNS, ...fieldSlugs]).map((column) => sql.ref(`content.${column}`));
2871
- const rows = /* @__PURE__ */ new Map();
2872
- const uniqueContentIds = [...new Set(contentIds)];
2873
- if (uniqueContentIds.length === 0) return rows;
2874
- const result = await sql`
2875
- WITH requested AS (${jsonTextValues(db, uniqueContentIds)})
2876
- SELECT
2877
- ${sql.join(columnRefs, sql`, `)},
2878
- collection.id AS __emdash_media_usage_collection_id
2879
- FROM ${sql.ref(tableName)} AS content
2880
- INNER JOIN requested ON requested.value = content.id
2881
- INNER JOIN _emdash_collections AS collection
2882
- ON collection.slug = ${collectionSlug}
2883
- ${expectedCollectionId ? sql`AND collection.id = ${expectedCollectionId}` : sql``}
2884
- `.execute(db);
2885
- for (const row of result.rows) {
2886
- const contentId = readString(row.id);
2887
- if (contentId) rows.set(contentId, row);
2888
- }
2889
- return rows;
2890
- }
2891
- async function loadRevisionRows(db, revisionIds) {
2892
- const revisions = /* @__PURE__ */ new Map();
2893
- const uniqueRevisionIds = [...new Set(revisionIds)];
2894
- if (uniqueRevisionIds.length === 0) return revisions;
2895
- const rows = await sql`
2896
- WITH requested AS (${jsonTextValues(db, uniqueRevisionIds)})
2897
- SELECT revision.id, revision.collection, revision.entry_id, revision.data
2898
- FROM revisions AS revision
2899
- INNER JOIN requested ON requested.value = revision.id
2900
- `.execute(db);
2901
- for (const row of rows.rows) {
2902
- const data = parseRevisionData(row.data);
2903
- revisions.set(row.id, data ? {
2904
- success: true,
2905
- revision: {
2906
- id: row.id,
2907
- collection: row.collection,
2908
- entryId: row.entry_id,
2909
- data
2910
- }
2911
- } : { success: false });
2912
- }
2913
- return revisions;
2914
- }
2915
- function buildContentSource(input) {
2916
- const { collectionId, collectionSlug, identityVersion, row, displayData, sourceVariant, revisionId } = input;
2917
- const contentId = readString(row.id) ?? "";
2918
- const contentSlug = input.contentSlug ?? readNullableString(row.slug);
2919
- return {
2920
- sourceKey: buildContentMediaUsageSourceKey({
2921
- collectionId,
2922
- collectionSlug,
2923
- contentId,
2924
- sourceVariant
2925
- }),
2926
- sourceType: "content",
2927
- collectionId,
2928
- collectionSlug,
2929
- contentId,
2930
- sourceVariant,
2931
- locale: readNullableString(row.locale),
2932
- translationGroup: readNullableString(row.translation_group),
2933
- contentSlug,
2934
- contentTitle: deriveContentTitle(displayData, contentSlug, contentId),
2935
- contentStatus: readNullableString(row.status),
2936
- contentScheduledAt: readNullableString(row.scheduled_at),
2937
- contentDeletedAt: readNullableString(row.deleted_at),
2938
- revisionId,
2939
- schemaVersion: CONTENT_SOURCE_SCHEMA_VERSION,
2940
- sourceUpdatedAt: readNullableString(row.updated_at),
2941
- sourceVersion: readNumber(row.version),
2942
- identityVersion
2943
- };
2944
- }
2945
- function projectData(row, fieldSlugs) {
2946
- const data = {};
2947
- for (const fieldSlug of fieldSlugs) data[fieldSlug] = deserializeValue(row[fieldSlug] ?? null);
2948
- return data;
2949
- }
2950
- function projectRawData(row, fieldSlugs) {
2951
- const data = {};
2952
- for (const fieldSlug of fieldSlugs) data[fieldSlug] = row[fieldSlug] ?? null;
2953
- return data;
2954
- }
2955
- function projectPresentData(row, fieldSlugs) {
2956
- const data = {};
2957
- for (const fieldSlug of fieldSlugs) if (Object.hasOwn(row, fieldSlug)) data[fieldSlug] = row[fieldSlug];
2958
- return data;
2959
- }
2960
- function uniqueColumns(columns) {
2961
- const unique = [...new Set(columns)];
2962
- for (const column of unique) validateIdentifier(column, "content media usage column");
2963
- return unique;
2964
- }
2965
- function getContentTableName(collectionSlug) {
2966
- validateIdentifier(collectionSlug, "collection slug");
2967
- return `ec_${collectionSlug}`;
2968
- }
2969
- function deserializeValue(value) {
2970
- if (typeof value === "string" && (value.startsWith("{") || value.startsWith("["))) try {
2971
- return JSON.parse(value);
2972
- } catch {
2973
- return value;
2974
- }
2975
- return value;
2976
- }
2977
- function parseRevisionData(value) {
2978
- if (typeof value === "string") try {
2979
- const parsed = JSON.parse(value);
2980
- return isRecord(parsed) ? parsed : null;
2981
- } catch {
2982
- return null;
2983
- }
2984
- return isRecord(value) ? value : null;
2985
- }
2986
- function stripRevisionMetadata(data) {
2987
- const stripped = {};
2988
- for (const [key, value] of Object.entries(data)) if (!key.startsWith("_")) stripped[key] = value;
2989
- return stripped;
2990
- }
2991
- function deriveContentTitle(displayData, contentSlug, contentId) {
2992
- for (const fieldSlug of ["title", "name"]) {
2993
- const value = displayData[fieldSlug];
2994
- if (typeof value === "string" && value.trim()) return value;
2995
- }
2996
- return contentSlug ?? contentId;
2997
- }
2998
- function readString(value) {
2999
- return typeof value === "string" ? value : null;
3000
- }
3001
- function readNullableString(value) {
3002
- return value === null || value === void 0 ? null : readString(value);
3003
- }
3004
- function readNumber(value) {
3005
- if (typeof value === "number" && Number.isFinite(value)) return value;
3006
- if (typeof value === "bigint") return Number(value);
3007
- if (typeof value === "string" && value) {
3008
- const parsed = Number(value);
3009
- return Number.isFinite(parsed) ? parsed : null;
3010
- }
3011
- return null;
3012
- }
3013
- function isRecord(value) {
3014
- return typeof value === "object" && value !== null && !Array.isArray(value);
3015
- }
3016
-
3017
- //#endregion
3018
- //#region src/media/usage/content-refresh.ts
3019
- const CONTENT_MEDIA_USAGE_ADAPTER_ID = "content-media";
3020
- const CONTENT_MEDIA_USAGE_COLLECTION_SCOPE = "collection";
3021
- const CONTENT_USAGE_LOCKS_KEY = Symbol.for("emdash.mediaUsage.contentLocks");
3022
- const CONTENT_USAGE_COLLECTION_LOCKS_KEY = Symbol.for("emdash.mediaUsage.collectionLocks");
3023
- const CONTENT_USAGE_REFRESH_MAX_ATTEMPTS = 2;
3024
- const MEDIA_USAGE_PROJECTION_ADMISSION_LIMITS = Object.freeze({
3025
- maxOccurrenceMutationUnitsPerClaim: 500,
3026
- maxProjectionMutationBytesPerVariant: 2e6,
3027
- maxProjectionMutationBytesPerClaim: 4e6,
3028
- maxOccurrenceMutationUnitsPerBatch: 5e4,
3029
- maxProjectionMutationBytesPerBatch: 16e6
3030
- });
3031
- const ZERO_RESULT = {
3032
- success: true,
3033
- refreshedSourceCount: 0,
3034
- deletedSourceCount: 0,
3035
- failedSourceCount: 0
3036
- };
3037
- function createContentMediaUsageAdmissionBudget(limits = {}) {
3038
- return {
3039
- remainingOccurrenceMutationUnits: limits.maxOccurrenceMutationUnits ?? MEDIA_USAGE_PROJECTION_ADMISSION_LIMITS.maxOccurrenceMutationUnitsPerClaim,
3040
- remainingProjectionMutationBytes: limits.maxProjectionMutationBytes ?? MEDIA_USAGE_PROJECTION_ADMISSION_LIMITS.maxProjectionMutationBytesPerClaim,
3041
- hasReservedMutation: false
3042
- };
3043
- }
3044
- async function planContentMediaUsageProjectionAdmission(repo, snapshots, observedSources, canonicalSourceKeys, budget) {
3045
- const snapshotSourceKeys = new Set(snapshots.map((snapshot) => snapshot.source.sourceKey));
3046
- const absentSources = canonicalSourceKeys.filter((sourceKey) => !snapshotSourceKeys.has(sourceKey)).map((sourceKey) => observedSources.get(sourceKey)).filter((source) => source !== void 0);
3047
- let deletionOccurrenceUnits = 0;
3048
- let deletionBytes = 0;
3049
- let largestDeletionBytes = 0;
3050
- for (const source of absentSources) {
3051
- const measurement = await repo.measureSourceGenerationDeletion(source.sourceKey, source.currentGeneration, MEDIA_USAGE_PROJECTION_ADMISSION_LIMITS.maxOccurrenceMutationUnitsPerClaim);
3052
- if (measurement.exceedsOccurrenceLimit) return budget.hasReservedMutation ? { outcome: "claim_budget_deferred" } : { outcome: "intrinsic_resource_limit" };
3053
- deletionOccurrenceUnits += measurement.occurrenceCount;
3054
- const sourceDeletionBytes = storedMediaUsageSourceByteLength(source) + measurement.occurrenceBytes * 2;
3055
- deletionBytes += sourceDeletionBytes;
3056
- largestDeletionBytes = Math.max(largestDeletionBytes, sourceDeletionBytes);
3057
- }
3058
- const noOpSourceKeys = /* @__PURE__ */ new Set();
3059
- let cost = projectionAdmissionCost(snapshots, noOpSourceKeys, deletionOccurrenceUnits, deletionBytes, largestDeletionBytes);
3060
- if (exceedsProjectionAdmissionLimits(cost)) {
3061
- for (const snapshot of snapshots) {
3062
- const expectedSource = observedSources.get(snapshot.source.sourceKey);
3063
- if (expectedSource && await repo.projectionMatchesExpectedSource(snapshot.source, expectedSource)) noOpSourceKeys.add(snapshot.source.sourceKey);
3064
- }
3065
- cost = projectionAdmissionCost(snapshots, noOpSourceKeys, deletionOccurrenceUnits, deletionBytes, largestDeletionBytes);
3066
- }
3067
- if (exceedsProjectionAdmissionLimits(cost)) return budget.hasReservedMutation ? { outcome: "claim_budget_deferred" } : { outcome: "intrinsic_resource_limit" };
3068
- if (cost.occurrenceMutationUnits > budget.remainingOccurrenceMutationUnits || cost.projectionMutationBytes > budget.remainingProjectionMutationBytes) return { outcome: "claim_budget_deferred" };
3069
- budget.remainingOccurrenceMutationUnits -= cost.occurrenceMutationUnits;
3070
- budget.remainingProjectionMutationBytes -= cost.projectionMutationBytes;
3071
- if (cost.occurrenceMutationUnits > 0 || cost.projectionMutationBytes > 0) budget.hasReservedMutation = true;
3072
- return {
3073
- outcome: "admitted",
3074
- noOpSourceKeys,
3075
- absentSources,
3076
- ...cost
3077
- };
3078
- }
3079
- function projectionAdmissionCost(snapshots, noOpSourceKeys, deletionOccurrenceUnits, deletionBytes, largestDeletionBytes) {
3080
- return snapshots.reduce((cost, snapshot) => {
3081
- if (noOpSourceKeys.has(snapshot.source.sourceKey)) return cost;
3082
- cost.occurrenceMutationUnits += snapshot.occurrences.length;
3083
- cost.projectionMutationBytes += snapshot.projectionByteLength;
3084
- cost.largestProjectionMutationBytes = Math.max(cost.largestProjectionMutationBytes, snapshot.projectionByteLength);
3085
- return cost;
3086
- }, {
3087
- occurrenceMutationUnits: deletionOccurrenceUnits,
3088
- projectionMutationBytes: deletionBytes,
3089
- largestProjectionMutationBytes: largestDeletionBytes
3090
- });
3091
- }
3092
- function exceedsProjectionAdmissionLimits(cost) {
3093
- return cost.occurrenceMutationUnits > MEDIA_USAGE_PROJECTION_ADMISSION_LIMITS.maxOccurrenceMutationUnitsPerClaim || cost.largestProjectionMutationBytes > MEDIA_USAGE_PROJECTION_ADMISSION_LIMITS.maxProjectionMutationBytesPerVariant || cost.projectionMutationBytes > MEDIA_USAGE_PROJECTION_ADMISSION_LIMITS.maxProjectionMutationBytesPerClaim;
3094
- }
3095
- function storedMediaUsageSourceByteLength(source) {
3096
- return new TextEncoder().encode(JSON.stringify(source)).byteLength;
3097
- }
3098
- async function refreshContentMediaUsage(db, collectionSlug, contentId) {
3099
- validateIdentifier(collectionSlug, "collection slug");
3100
- return withContentUsageCollectionLock(collectionSlug, () => withContentUsageLock(collectionSlug, contentId, () => refreshContentMediaUsageUnlocked(db, collectionSlug, contentId, {})));
3101
- }
3102
- async function refreshContentMediaUsageForWorkBatch(db, items, options = {}) {
3103
- const results = /* @__PURE__ */ new Map();
3104
- const batchBudget = createContentMediaUsageAdmissionBudget({
3105
- maxOccurrenceMutationUnits: MEDIA_USAGE_PROJECTION_ADMISSION_LIMITS.maxOccurrenceMutationUnitsPerBatch,
3106
- maxProjectionMutationBytes: MEDIA_USAGE_PROJECTION_ADMISSION_LIMITS.maxProjectionMutationBytesPerBatch
3107
- });
3108
- const collections = /* @__PURE__ */ new Map();
3109
- for (const item of items) {
3110
- const key = `${item.collectionId}\u0000${item.collectionSlug}`;
3111
- const collectionItems = collections.get(key) ?? [];
3112
- collectionItems.push(item);
3113
- collections.set(key, collectionItems);
3114
- }
3115
- for (const collectionItems of collections.values()) {
3116
- if (options.shouldContinue && !options.shouldContinue()) break;
3117
- const first = collectionItems[0];
3118
- if (!first) continue;
3119
- validateIdentifier(first.collectionSlug, "collection slug");
3120
- if (!first.collectionId) throw new Error("Durable media usage work requires a collection identity");
3121
- await withContentUsageCollectionLock(first.collectionSlug, async () => {
3122
- const fieldDiscovery = await loadContentMediaUsageFields(db, first.collectionSlug, first.collectionId);
3123
- const sourceKeys = collectionItems.flatMap((item) => contentSourceKeys(item.collectionSlug, item.contentId, item.collectionId));
3124
- const repo = new MediaUsageRepository(db);
3125
- const observedSources = await repo.findSources(sourceKeys);
3126
- const snapshots = await loadContentMediaUsageSnapshotsBatch(db, first.collectionSlug, collectionItems.map((item) => item.contentId), fieldDiscovery, {
3127
- collectionId: first.collectionId,
3128
- identityVersion: 1
3129
- }, {
3130
- shouldContinue: options.shouldContinue,
3131
- maxOccurrenceCount: MEDIA_USAGE_PROJECTION_ADMISSION_LIMITS.maxOccurrenceMutationUnitsPerBatch,
3132
- maxProjectionBytes: MEDIA_USAGE_PROJECTION_ADMISSION_LIMITS.maxProjectionMutationBytesPerBatch
3133
- });
3134
- const newSourceProjections = [];
3135
- const newSourceKeys = /* @__PURE__ */ new Map();
3136
- const existingSourceProjections = [];
3137
- const unchangedSourceProjections = [];
3138
- const existingSourceKeys = /* @__PURE__ */ new Map();
3139
- for (const item of collectionItems) {
3140
- if (options.shouldContinue && !options.shouldContinue()) break;
3141
- const snapshotsResult = snapshots.get(item.contentId);
3142
- const itemSourceKeys = contentSourceKeys(item.collectionSlug, item.contentId, item.collectionId);
3143
- if (!snapshotsResult?.success) continue;
3144
- const snapshotsByKey = new Map(snapshotsResult.snapshots.map((snapshot) => [snapshot.source.sourceKey, snapshot]));
3145
- const existing = itemSourceKeys.map((sourceKey) => observedSources.get(sourceKey)).filter((source) => source !== void 0);
3146
- const allNew = existing.length === 0;
3147
- const allExisting = existing.length === snapshotsResult.snapshots.length && existing.every((source) => snapshotsByKey.has(source.sourceKey));
3148
- if (!allNew && !allExisting) continue;
3149
- const admission = await planContentMediaUsageProjectionAdmission(repo, snapshotsResult.snapshots, observedSources, itemSourceKeys, batchBudget);
3150
- if (admission.outcome === "claim_budget_deferred") break;
3151
- if (admission.outcome !== "admitted") {
3152
- results.set(contentRefreshKey(item.collectionId, item.contentId), admissionFailureResult(admission.outcome));
3153
- continue;
3154
- }
3155
- const key = contentRefreshKey(item.collectionId, item.contentId);
3156
- if (allNew) {
3157
- newSourceProjections.push(...snapshotsResult.snapshots.map((snapshot) => ({
3158
- source: snapshot.source,
3159
- occurrences: snapshot.occurrences
3160
- })));
3161
- newSourceKeys.set(key, snapshotsResult.snapshots.map((snapshot) => snapshot.source.sourceKey));
3162
- continue;
3163
- }
3164
- const changed = [];
3165
- const unchanged = [];
3166
- for (const snapshot of snapshotsResult.snapshots) {
3167
- const expectedSource = observedSources.get(snapshot.source.sourceKey);
3168
- if (!expectedSource) continue;
3169
- if (expectedSource.sourceFingerprint === snapshot.source.sourceFingerprint && expectedSource.sourceCompleteness === (snapshot.source.sourceCompleteness ?? "complete") && expectedSource.lastErrorCode === null) {
3170
- unchanged.push(snapshot.source.sourceKey);
3171
- unchangedSourceProjections.push({
3172
- source: snapshot.source,
3173
- occurrences: snapshot.occurrences,
3174
- expectedSource
3175
- });
3176
- continue;
3177
- }
3178
- changed.push(snapshot.source.sourceKey);
3179
- existingSourceProjections.push({
3180
- source: snapshot.source,
3181
- occurrences: snapshot.occurrences,
3182
- expectedSource
3183
- });
3184
- }
3185
- existingSourceKeys.set(key, {
3186
- allCount: snapshotsResult.snapshots.length,
3187
- changed,
3188
- unchanged
3189
- });
3190
- }
3191
- const insertedSourceKeys = await repo.replaceNewSourcesBatch(newSourceProjections);
3192
- const replacedSourceKeys = await repo.replaceExistingSourcesBatch(existingSourceProjections);
3193
- const matchedSourceKeys = await repo.matchingExistingSourcesBatch(unchangedSourceProjections);
3194
- for (const [key, expectedSourceKeys] of newSourceKeys) results.set(key, expectedSourceKeys.every((sourceKey) => insertedSourceKeys.has(sourceKey)) ? {
3195
- success: true,
3196
- refreshedSourceCount: expectedSourceKeys.length,
3197
- deletedSourceCount: 0,
3198
- failedSourceCount: 0
3199
- } : generationConflictResult({
3200
- refreshedSourceCount: 0,
3201
- deletedSourceCount: 0
3202
- }));
3203
- for (const [key, expected] of existingSourceKeys) results.set(key, expected.changed.every((sourceKey) => replacedSourceKeys.has(sourceKey)) && expected.unchanged.every((sourceKey) => matchedSourceKeys.has(sourceKey)) ? {
3204
- success: true,
3205
- refreshedSourceCount: expected.allCount,
3206
- deletedSourceCount: 0,
3207
- failedSourceCount: 0
3208
- } : generationConflictResult({
3209
- refreshedSourceCount: 0,
3210
- deletedSourceCount: 0
3211
- }));
3212
- for (const item of collectionItems) {
3213
- if (options.shouldContinue && !options.shouldContinue()) break;
3214
- const key = contentRefreshKey(item.collectionId, item.contentId);
3215
- if (!snapshots.has(item.contentId)) continue;
3216
- if (results.has(key)) continue;
3217
- const result = await withContentUsageLock(item.collectionSlug, item.contentId, () => refreshContentMediaUsageUnlocked(db, item.collectionSlug, item.contentId, {
3218
- collectionId: item.collectionId,
3219
- durableWork: true,
3220
- fieldDiscovery,
3221
- observedSources,
3222
- snapshotsResult: snapshots.get(item.contentId)
3223
- }));
3224
- results.set(key, result);
3225
- }
3226
- });
3227
- }
3228
- return results;
3229
- }
3230
- async function refreshContentMediaUsageUnlocked(db, collectionSlug, contentId, options) {
3231
- try {
3232
- let conflictResult = null;
3233
- if (options.durableWork) options.admissionBudget = createContentMediaUsageAdmissionBudget();
3234
- for (let attempt = 0; attempt < CONTENT_USAGE_REFRESH_MAX_ATTEMPTS; attempt++) {
3235
- const result = await refreshContentMediaUsageAttempt(db, collectionSlug, contentId, options);
3236
- if (result.errorCode !== "CONTENT_USAGE_GENERATION_CONFLICT") return result;
3237
- conflictResult = result;
3238
- if (options.admissionBudget?.hasReservedMutation) break;
3239
- }
3240
- if (options.durableWork) return generationConflictResult({
3241
- refreshedSourceCount: conflictResult?.refreshedSourceCount ?? 0,
3242
- deletedSourceCount: conflictResult?.deletedSourceCount ?? 0
3243
- });
3244
- return markGenerationConflict(db, collectionSlug, {
3245
- refreshedSourceCount: conflictResult?.refreshedSourceCount ?? 0,
3246
- deletedSourceCount: conflictResult?.deletedSourceCount ?? 0
3247
- });
3248
- } catch (error) {
3249
- console.error(`[media-usage] Failed to refresh ${collectionSlug}/${contentId}:`, error);
3250
- if (!options.durableWork) await markContentMediaUsageCollectionStaleSafely(db, collectionSlug, "CONTENT_USAGE_REFRESH_ERROR");
3251
- return {
3252
- success: false,
3253
- refreshedSourceCount: 0,
3254
- deletedSourceCount: 0,
3255
- failedSourceCount: 0,
3256
- errorCode: "CONTENT_USAGE_REFRESH_ERROR"
3257
- };
3258
- }
3259
- }
3260
- async function refreshContentMediaUsageAttempt(db, collectionSlug, contentId, options) {
3261
- const repo = new MediaUsageRepository(db);
3262
- const canonicalSourceKeys = contentSourceKeys(collectionSlug, contentId, options.collectionId);
3263
- const observedSources = options.observedSources ?? await repo.findSources(canonicalSourceKeys);
3264
- const snapshotsResult = options.snapshotsResult ?? await loadContentMediaUsageSnapshots(db, collectionSlug, contentId, options.fieldDiscovery, options.collectionId ? {
3265
- collectionId: options.collectionId,
3266
- identityVersion: 1
3267
- } : void 0);
3268
- if (!snapshotsResult.success) {
3269
- if (snapshotsResult.error === "CONTENT_NOT_FOUND" && options.collectionId) {
3270
- if (!options.admissionBudget) throw new Error("Durable media usage work requires an admission budget");
3271
- const admission = await planContentMediaUsageProjectionAdmission(repo, [], observedSources, canonicalSourceKeys, options.admissionBudget);
3272
- if (admission.outcome !== "admitted") return admissionFailureResult(admission.outcome);
3273
- return deleteCanonicalContentSourcesIfAbsent(repo, admission.absentSources, collectionSlug, contentId);
3274
- }
3275
- if (snapshotsResult.error === "CONTENT_NOT_FOUND" && !await contentCollectionExists(db, collectionSlug)) {
3276
- const deletedSourceCount = await repo.deleteContentSources(collectionSlug, contentId);
3277
- return {
3278
- ...ZERO_RESULT,
3279
- deletedSourceCount
3280
- };
3281
- }
3282
- return options.durableWork ? snapshotFailureResult(snapshotsResult) : markSnapshotFailure(db, collectionSlug, snapshotsResult);
3283
- }
3284
- if (!options.collectionId && !await contentCollectionExists(db, collectionSlug)) {
3285
- const deletedSourceCount = await repo.deleteContentSources(collectionSlug, contentId);
3286
- return {
3287
- ...ZERO_RESULT,
3288
- deletedSourceCount
3289
- };
3290
- }
3291
- const admission = options.admissionBudget ? await planContentMediaUsageProjectionAdmission(repo, snapshotsResult.snapshots, observedSources, canonicalSourceKeys, options.admissionBudget) : null;
3292
- if (admission && admission.outcome !== "admitted") return admissionFailureResult(admission.outcome);
3293
- let refreshedSourceCount = 0;
3294
- for (const snapshot of snapshotsResult.snapshots) {
3295
- if (admission?.outcome === "admitted" && admission.noOpSourceKeys.has(snapshot.source.sourceKey)) {
3296
- refreshedSourceCount++;
3297
- continue;
3298
- }
3299
- const result = await repo.replaceSourceIfMatching(snapshot.source, snapshot.occurrences, observedSources.get(snapshot.source.sourceKey) ?? null);
3300
- if (result.unchanged) {
3301
- refreshedSourceCount++;
3302
- continue;
3303
- }
3304
- if (!result.replaced) return generationConflictResult({
3305
- refreshedSourceCount,
3306
- deletedSourceCount: 0
3307
- });
3308
- refreshedSourceCount++;
3309
- }
3310
- if (!options.collectionId && !await contentCollectionExists(db, collectionSlug)) {
3311
- const deletedSourceCount = await repo.deleteContentSources(collectionSlug, contentId);
3312
- return {
3313
- ...ZERO_RESULT,
3314
- deletedSourceCount
3315
- };
3316
- }
3317
- const expectedSourceKeys = new Set(snapshotsResult.snapshots.map((snapshot) => snapshot.source.sourceKey));
3318
- const absentSources = admission?.outcome === "admitted" ? admission.absentSources : canonicalSourceKeys.filter((sourceKey) => !expectedSourceKeys.has(sourceKey)).map((sourceKey) => observedSources.get(sourceKey)).filter((source) => source !== void 0);
3319
- let deletedSourceCount = 0;
3320
- for (const expectedSource of absentSources) {
3321
- const result = await repo.deleteSourceIfMatching(expectedSource.sourceKey, expectedSource);
3322
- if (result.deleted) {
3323
- deletedSourceCount++;
3324
- continue;
3325
- }
3326
- if (result.source) return generationConflictResult({
3327
- refreshedSourceCount,
3328
- deletedSourceCount
3329
- });
3330
- }
3331
- return {
3332
- success: true,
3333
- refreshedSourceCount,
3334
- deletedSourceCount,
3335
- failedSourceCount: 0
3336
- };
3337
- }
3338
- function contentRefreshKey(collectionId, contentId) {
3339
- return `${collectionId}\u0000${contentId}`;
3340
- }
3341
- function contentSourceKeys(collectionSlug, contentId, collectionId) {
3342
- return MEDIA_USAGE_CONTENT_SOURCE_VARIANTS.map((sourceVariant) => buildContentMediaUsageSourceKey({
3343
- collectionId,
3344
- collectionSlug,
3345
- contentId,
3346
- sourceVariant
3347
- }));
3348
- }
3349
- function admissionFailureResult(outcome) {
3350
- return {
3351
- ...generationConflictResult({
3352
- refreshedSourceCount: 0,
3353
- deletedSourceCount: 0
3354
- }),
3355
- errorCode: outcome === "intrinsic_resource_limit" ? "CONTENT_USAGE_RESOURCE_LIMIT" : "CONTENT_USAGE_GENERATION_CONFLICT"
3356
- };
3357
- }
3358
- async function markGenerationConflict(db, collectionSlug, counts) {
3359
- await markContentMediaUsageCollectionStaleSafely(db, collectionSlug, "CONTENT_USAGE_GENERATION_CONFLICT");
3360
- return {
3361
- success: false,
3362
- refreshedSourceCount: counts.refreshedSourceCount,
3363
- deletedSourceCount: counts.deletedSourceCount,
3364
- failedSourceCount: 0,
3365
- errorCode: "CONTENT_USAGE_GENERATION_CONFLICT"
3366
- };
3367
- }
3368
- function generationConflictResult(counts) {
3369
- return {
3370
- success: false,
3371
- refreshedSourceCount: counts.refreshedSourceCount,
3372
- deletedSourceCount: counts.deletedSourceCount,
3373
- failedSourceCount: 0,
3374
- errorCode: "CONTENT_USAGE_GENERATION_CONFLICT"
3375
- };
3376
- }
3377
- async function contentCollectionExists(db, collectionSlug, collectionId) {
3378
- let query = db.selectFrom("_emdash_collections").select("id").where("slug", "=", collectionSlug);
3379
- if (collectionId) query = query.where("id", "=", collectionId);
3380
- return await query.executeTakeFirst() !== void 0;
3381
- }
3382
- async function deleteContentMediaUsage(db, collectionSlug, contentId) {
3383
- validateIdentifier(collectionSlug, "collection slug");
3384
- return withContentUsageCollectionLock(collectionSlug, () => withContentUsageLock(collectionSlug, contentId, () => deleteContentMediaUsageUnlocked(db, collectionSlug, contentId)));
3385
- }
3386
- async function deleteContentMediaUsageUnlocked(db, collectionSlug, contentId) {
3387
- try {
3388
- const deletedSourceCount = await new MediaUsageRepository(db).deleteContentSources(collectionSlug, contentId);
3389
- return {
3390
- ...ZERO_RESULT,
3391
- deletedSourceCount
3392
- };
3393
- } catch (error) {
3394
- console.error(`[media-usage] Failed to delete usage for ${collectionSlug}/${contentId}:`, error);
3395
- await markContentMediaUsageCollectionStaleSafely(db, collectionSlug, "CONTENT_USAGE_DELETE_ERROR");
3396
- return {
3397
- success: false,
3398
- refreshedSourceCount: 0,
3399
- deletedSourceCount: 0,
3400
- failedSourceCount: 0,
3401
- errorCode: "CONTENT_USAGE_DELETE_ERROR"
3402
- };
3403
- }
3404
- }
3405
- async function deleteContentMediaUsageCollection(db, collectionSlug) {
3406
- validateIdentifier(collectionSlug, "collection slug");
3407
- return withContentUsageCollectionLock(collectionSlug, () => deleteContentMediaUsageCollectionUnlocked(db, collectionSlug));
3408
- }
3409
- async function deleteContentMediaUsageCollectionUnlocked(db, collectionSlug) {
3410
- try {
3411
- const repo = new MediaUsageRepository(db);
3412
- const deletedSourceCount = await repo.deleteCollectionSources(collectionSlug);
3413
- await repo.deleteIndexStatus({
3414
- adapterId: CONTENT_MEDIA_USAGE_ADAPTER_ID,
3415
- scopeType: CONTENT_MEDIA_USAGE_COLLECTION_SCOPE,
3416
- scopeKey: collectionSlug
3417
- });
3418
- return {
3419
- ...ZERO_RESULT,
3420
- deletedSourceCount
3421
- };
3422
- } catch (error) {
3423
- console.error(`[media-usage] Failed to delete usage for collection ${collectionSlug}:`, error);
3424
- try {
3425
- await new MediaUsageRepository(db).deleteIndexStatus({
3426
- adapterId: CONTENT_MEDIA_USAGE_ADAPTER_ID,
3427
- scopeType: CONTENT_MEDIA_USAGE_COLLECTION_SCOPE,
3428
- scopeKey: collectionSlug
3429
- });
3430
- } catch (statusError) {
3431
- console.error(`[media-usage] Failed to clear usage status for deleted collection ${collectionSlug}:`, statusError);
3432
- }
3433
- return {
3434
- success: false,
3435
- refreshedSourceCount: 0,
3436
- deletedSourceCount: 0,
3437
- failedSourceCount: 0,
3438
- errorCode: "CONTENT_USAGE_DELETE_ERROR"
3439
- };
3440
- }
3441
- }
3442
- async function refreshContentMediaUsageAfterWrite(db, collectionSlug, contentId) {
3443
- const result = await refreshContentMediaUsage(db, collectionSlug, contentId);
3444
- if (!result.success) console.error(`[media-usage] Usage refresh for ${collectionSlug}/${contentId} finished with ${result.errorCode}`);
3445
- }
3446
- async function markContentMediaUsageCollectionStale(db, collectionSlug, lastErrorCode) {
3447
- validateIdentifier(collectionSlug, "collection slug");
3448
- const repo = new MediaUsageRepository(db);
3449
- const identity = {
3450
- adapterId: CONTENT_MEDIA_USAGE_ADAPTER_ID,
3451
- scopeType: CONTENT_MEDIA_USAGE_COLLECTION_SCOPE,
3452
- scopeKey: collectionSlug
3453
- };
3454
- const existing = await repo.findIndexStatus(identity);
3455
- await repo.upsertIndexStatus({
3456
- ...identity,
3457
- status: "stale",
3458
- schemaVersion: existing?.schemaVersion ?? CONTENT_SOURCE_SCHEMA_VERSION,
3459
- startedAt: existing?.startedAt ?? null,
3460
- completedAt: existing?.completedAt ?? null,
3461
- cursor: existing?.cursor ?? null,
3462
- indexedSourceCount: existing?.indexedSourceCount ?? 0,
3463
- failedSourceCount: existing?.failedSourceCount ?? 0,
3464
- lastErrorCode
3465
- });
3466
- }
3467
- async function invalidateContentMediaUsageSchemaChange(db, collectionSlug) {
3468
- validateIdentifier(collectionSlug, "collection slug");
3469
- if (!await tableExists(db, "_emdash_media_usage_activation")) return false;
3470
- if ((await db.selectFrom("_emdash_media_usage_activation").select("state").where("task_key", "=", "incremental_capture").executeTakeFirst())?.state !== "active") return false;
3471
- if (!await new MediaUsageRepository(db).invalidateIndexStatusForSchemaChange(collectionSlug)) throw new Error(`Cannot invalidate media usage coverage for collection ${collectionSlug}`);
3472
- return true;
3473
- }
3474
- async function findNonTranslatableSiblingContentIds(db, collectionSlug, updatedContentId, translationGroup, updatedData, options = {}) {
3475
- if (!isI18nEnabled() || !updatedData || !translationGroup) return [];
3476
- validateIdentifier(collectionSlug, "collection slug");
3477
- const collection = await db.selectFrom("_emdash_collections").select("id").where("slug", "=", collectionSlug).executeTakeFirst();
3478
- if (!collection) return [];
3479
- const touchedNonTranslatableSlugs = (await db.selectFrom("_emdash_fields").select("slug").where("collection_id", "=", collection.id).where("translatable", "=", 0).execute()).filter((field) => options.absentAsCleared || field.slug in updatedData).map((field) => field.slug);
3480
- if (touchedNonTranslatableSlugs.length === 0) return [];
3481
- const usageFields = await loadContentMediaUsageFields(db, collectionSlug);
3482
- const usageRelevantSlugs = new Set([...usageFields.extractionFields.map((field) => field.slug), ...usageFields.displayFieldSlugs]);
3483
- if (!touchedNonTranslatableSlugs.some((slug) => usageRelevantSlugs.has(slug))) return [];
3484
- const tableName = `ec_${collectionSlug}`;
3485
- return (await sql`
3486
- SELECT id
3487
- FROM ${sql.ref(tableName)}
3488
- WHERE translation_group = ${translationGroup}
3489
- AND id != ${updatedContentId}
3490
- ORDER BY id ASC
3491
- `.execute(db)).rows.map((row) => row.id);
3492
- }
3493
- async function markSnapshotFailure(db, collectionSlug, result) {
3494
- const repo = new MediaUsageRepository(db);
3495
- if (result.source) await repo.markSourceAttempted({
3496
- ...result.source,
3497
- sourceCompleteness: "failed",
3498
- lastErrorCode: result.error
3499
- });
3500
- await markContentMediaUsageCollectionStale(db, collectionSlug, result.error);
3501
- return {
3502
- success: false,
3503
- refreshedSourceCount: 0,
3504
- deletedSourceCount: 0,
3505
- failedSourceCount: result.source ? 1 : 0,
3506
- errorCode: result.error
3507
- };
3508
- }
3509
- function snapshotFailureResult(result) {
3510
- return {
3511
- success: false,
3512
- refreshedSourceCount: 0,
3513
- deletedSourceCount: 0,
3514
- failedSourceCount: result.source ? 1 : 0,
3515
- errorCode: result.error
3516
- };
3517
- }
3518
- async function deleteCanonicalContentSourcesIfAbsent(repo, observedSources, collectionSlug, contentId) {
3519
- let deletedSourceCount = 0;
3520
- for (const source of observedSources) {
3521
- const result = await repo.deleteSourceIfMatchingContentAbsent(source.sourceKey, source, collectionSlug, contentId);
3522
- if (result.deleted) {
3523
- deletedSourceCount++;
3524
- continue;
3525
- }
3526
- if (result.contentPresent || result.source) return generationConflictResult({
3527
- refreshedSourceCount: 0,
3528
- deletedSourceCount
3529
- });
3530
- }
3531
- return {
3532
- ...ZERO_RESULT,
3533
- deletedSourceCount
3534
- };
3535
- }
3536
- async function markContentMediaUsageCollectionStaleSafely(db, collectionSlug, lastErrorCode) {
3537
- try {
3538
- await markContentMediaUsageCollectionStale(db, collectionSlug, lastErrorCode);
3539
- return true;
3540
- } catch (error) {
3541
- console.error(`[media-usage] Failed to mark ${collectionSlug} stale:`, error);
3542
- return false;
3543
- }
3544
- }
3545
- async function withContentUsageLock(collectionSlug, contentId, fn) {
3546
- const locks = getContentUsageLocks();
3547
- const lockKey = `${collectionSlug}\0${contentId}`;
3548
- const previous = locks.get(lockKey) ?? Promise.resolve();
3549
- let releaseCurrent;
3550
- const current = new Promise((resolve) => {
3551
- releaseCurrent = resolve;
3552
- });
3553
- const next = previous.catch(() => {}).then(() => current);
3554
- locks.set(lockKey, next);
3555
- try {
3556
- await previous.catch(() => {});
3557
- return await fn();
3558
- } finally {
3559
- releaseCurrent();
3560
- if (locks.get(lockKey) === next) locks.delete(lockKey);
3561
- }
3562
- }
3563
- async function withContentUsageCollectionLock(collectionSlug, fn) {
3564
- const locks = getContentUsageCollectionLocks();
3565
- const previous = locks.get(collectionSlug) ?? Promise.resolve();
3566
- let releaseCurrent;
3567
- const current = new Promise((resolve) => {
3568
- releaseCurrent = resolve;
3569
- });
3570
- const next = previous.catch(() => {}).then(() => current);
3571
- locks.set(collectionSlug, next);
3572
- try {
3573
- await previous.catch(() => {});
3574
- return await fn();
3575
- } finally {
3576
- releaseCurrent();
3577
- if (locks.get(collectionSlug) === next) locks.delete(collectionSlug);
3578
- }
3579
- }
3580
- function getContentUsageLocks() {
3581
- const global = globalThis;
3582
- const existing = global[CONTENT_USAGE_LOCKS_KEY];
3583
- if (existing instanceof Map) return existing;
3584
- const locks = /* @__PURE__ */ new Map();
3585
- global[CONTENT_USAGE_LOCKS_KEY] = locks;
3586
- return locks;
3587
- }
3588
- function getContentUsageCollectionLocks() {
3589
- const global = globalThis;
3590
- const existing = global[CONTENT_USAGE_COLLECTION_LOCKS_KEY];
3591
- if (existing instanceof Map) return existing;
3592
- const locks = /* @__PURE__ */ new Map();
3593
- global[CONTENT_USAGE_COLLECTION_LOCKS_KEY] = locks;
3594
- return locks;
3595
- }
3596
-
3597
- //#endregion
3598
- export { buildContentMediaUsageFieldFingerprint as _, deleteContentMediaUsageCollection as a, jsonTextValues as b, markContentMediaUsageCollectionStale as c, refreshContentMediaUsageForWorkBatch as d, withContentUsageCollectionLock as f, MediaUsageFieldDiscoveryError as g, buildContentMediaUsageSourceKey as h, deleteContentMediaUsage as i, markContentMediaUsageCollectionStaleSafely as l, MEDIA_USAGE_CONTENT_SOURCE_VARIANTS as m, CONTENT_MEDIA_USAGE_COLLECTION_SCOPE as n, findNonTranslatableSiblingContentIds as o, loadContentMediaUsageSnapshots as p, contentRefreshKey as r, invalidateContentMediaUsageSchemaChange as s, CONTENT_MEDIA_USAGE_ADAPTER_ID as t, refreshContentMediaUsageAfterWrite as u, loadContentMediaUsageFields as v, CONTENT_SOURCE_SCHEMA_VERSION as x, MediaUsageRepository as y };
3599
- //# sourceMappingURL=content-refresh-BHLHJgyt.mjs.map