emdash 0.33.0 → 0.34.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 (860) hide show
  1. package/dist/{adapters-BMyNSSZi.d.mts → adapters-34EN2eXO.d.mts} +26 -2
  2. package/dist/adapters-34EN2eXO.d.mts.map +1 -0
  3. package/dist/{allowed-origins-CG1pACtS.mjs → allowed-origins-DW7sYyqg.mjs} +2 -2
  4. package/dist/{allowed-origins-CG1pACtS.mjs.map → allowed-origins-DW7sYyqg.mjs.map} +1 -1
  5. package/dist/api/route-utils.d.mts +3 -3
  6. package/dist/api/route-utils.mjs +22 -21
  7. package/dist/api/route-utils.mjs.map +1 -1
  8. package/dist/api/schemas/index.d.mts +2 -2
  9. package/dist/api/schemas/index.mjs +6 -5
  10. package/dist/{api-D2hunekQ.mjs → api-DdAsirBe.mjs} +98 -407
  11. package/dist/api-DdAsirBe.mjs.map +1 -0
  12. package/dist/{api-tokens-CSND0i1z.mjs → api-tokens-CFXu8F_2.mjs} +2 -2
  13. package/dist/{api-tokens-CSND0i1z.mjs.map → api-tokens-CFXu8F_2.mjs.map} +1 -1
  14. package/dist/{apply-kwNEJW2l.mjs → apply-D5zfeD09.mjs} +131 -107
  15. package/dist/apply-D5zfeD09.mjs.map +1 -0
  16. package/dist/astro/index.d.mts +12 -12
  17. package/dist/astro/index.d.mts.map +1 -1
  18. package/dist/astro/index.mjs +93 -35
  19. package/dist/astro/index.mjs.map +1 -1
  20. package/dist/astro/middleware/auth.d.mts +10 -10
  21. package/dist/astro/middleware/auth.mjs +8 -8
  22. package/dist/astro/middleware/media-usage-write-fence.d.mts +7 -0
  23. package/dist/astro/middleware/media-usage-write-fence.d.mts.map +1 -0
  24. package/dist/astro/middleware/media-usage-write-fence.mjs +35 -0
  25. package/dist/astro/middleware/media-usage-write-fence.mjs.map +1 -0
  26. package/dist/astro/middleware/redirect.d.mts.map +1 -1
  27. package/dist/astro/middleware/redirect.mjs +10 -7
  28. package/dist/astro/middleware/redirect.mjs.map +1 -1
  29. package/dist/astro/middleware/request-context.mjs +3 -3
  30. package/dist/astro/middleware/setup.mjs +1 -1
  31. package/dist/astro/middleware.d.mts +12 -11
  32. package/dist/astro/middleware.d.mts.map +1 -1
  33. package/dist/astro/middleware.mjs +1677 -156
  34. package/dist/astro/middleware.mjs.map +1 -1
  35. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +9 -8
  36. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs.map +1 -1
  37. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +9 -8
  38. package/dist/astro/routes/api/admin/allowed-domains/index.mjs.map +1 -1
  39. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +5 -5
  40. package/dist/astro/routes/api/admin/api-tokens/index.mjs +6 -6
  41. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +6 -6
  42. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +12 -11
  43. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs.map +1 -1
  44. package/dist/astro/routes/api/admin/byline-fields/index.mjs +12 -11
  45. package/dist/astro/routes/api/admin/byline-fields/index.mjs.map +1 -1
  46. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +12 -11
  47. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs.map +1 -1
  48. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +17 -17
  49. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +17 -17
  50. package/dist/astro/routes/api/admin/bylines/index.mjs +17 -17
  51. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +14 -13
  52. package/dist/astro/routes/api/admin/comments/_id_/status.mjs.map +1 -1
  53. package/dist/astro/routes/api/admin/comments/_id_.mjs +6 -6
  54. package/dist/astro/routes/api/admin/comments/bulk.mjs +12 -11
  55. package/dist/astro/routes/api/admin/comments/bulk.mjs.map +1 -1
  56. package/dist/astro/routes/api/admin/comments/counts.mjs +6 -6
  57. package/dist/astro/routes/api/admin/comments/index.mjs +12 -11
  58. package/dist/astro/routes/api/admin/comments/index.mjs.map +1 -1
  59. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +5 -5
  60. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +4 -4
  61. package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.d.mts +8 -0
  62. package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.d.mts.map +1 -0
  63. package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs +36 -0
  64. package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs.map +1 -0
  65. package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.d.mts +8 -0
  66. package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.d.mts.map +1 -0
  67. package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs +36 -0
  68. package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs.map +1 -0
  69. package/dist/astro/routes/api/admin/media-usage/repair.mjs +15 -14
  70. package/dist/astro/routes/api/admin/media-usage/repair.mjs.map +1 -1
  71. package/dist/astro/routes/api/admin/media-usage/work/index.d.mts +8 -0
  72. package/dist/astro/routes/api/admin/media-usage/work/index.d.mts.map +1 -0
  73. package/dist/astro/routes/api/admin/media-usage/work/index.mjs +37 -0
  74. package/dist/astro/routes/api/admin/media-usage/work/index.mjs.map +1 -0
  75. package/dist/astro/routes/api/admin/media-usage/work/retry.d.mts +8 -0
  76. package/dist/astro/routes/api/admin/media-usage/work/retry.d.mts.map +1 -0
  77. package/dist/astro/routes/api/admin/media-usage/work/retry.mjs +37 -0
  78. package/dist/astro/routes/api/admin/media-usage/work/retry.mjs.map +1 -0
  79. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +5 -5
  80. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +5 -5
  81. package/dist/astro/routes/api/admin/plugins/_id_/disable.d.mts.map +1 -1
  82. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +51 -43
  83. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
  84. package/dist/astro/routes/api/admin/plugins/_id_/enable.d.mts.map +1 -1
  85. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +51 -43
  86. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
  87. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +47 -42
  88. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
  89. package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +6 -6
  90. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +47 -42
  91. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs.map +1 -1
  92. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.d.mts.map +1 -1
  93. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +50 -42
  94. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
  95. package/dist/astro/routes/api/admin/plugins/_id_/update.d.mts.map +1 -1
  96. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +50 -42
  97. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
  98. package/dist/astro/routes/api/admin/plugins/index.mjs +47 -42
  99. package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
  100. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +4 -4
  101. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +47 -42
  102. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
  103. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.d.mts.map +1 -1
  104. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +50 -42
  105. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
  106. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +47 -42
  107. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
  108. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.d.mts.map +1 -1
  109. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +50 -42
  110. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
  111. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.d.mts.map +1 -1
  112. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +51 -43
  113. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
  114. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +47 -42
  115. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
  116. package/dist/astro/routes/api/admin/plugins/registry/install.d.mts.map +1 -1
  117. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +51 -43
  118. package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
  119. package/dist/astro/routes/api/admin/plugins/updates.mjs +47 -42
  120. package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
  121. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +47 -42
  122. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
  123. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +4 -4
  124. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +47 -42
  125. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
  126. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +3 -3
  127. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +3 -3
  128. package/dist/astro/routes/api/admin/users/_id_/index.mjs +9 -8
  129. package/dist/astro/routes/api/admin/users/_id_/index.mjs.map +1 -1
  130. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +4 -4
  131. package/dist/astro/routes/api/admin/users/index.mjs +9 -8
  132. package/dist/astro/routes/api/admin/users/index.mjs.map +1 -1
  133. package/dist/astro/routes/api/auth/dev-bypass.mjs +7 -7
  134. package/dist/astro/routes/api/auth/invite/accept.mjs +3 -3
  135. package/dist/astro/routes/api/auth/invite/complete.mjs +13 -12
  136. package/dist/astro/routes/api/auth/invite/complete.mjs.map +1 -1
  137. package/dist/astro/routes/api/auth/invite/index.mjs +10 -9
  138. package/dist/astro/routes/api/auth/invite/index.mjs.map +1 -1
  139. package/dist/astro/routes/api/auth/invite/register-options.mjs +12 -11
  140. package/dist/astro/routes/api/auth/invite/register-options.mjs.map +1 -1
  141. package/dist/astro/routes/api/auth/logout.mjs +4 -4
  142. package/dist/astro/routes/api/auth/magic-link/send.mjs +12 -11
  143. package/dist/astro/routes/api/auth/magic-link/send.mjs.map +1 -1
  144. package/dist/astro/routes/api/auth/magic-link/verify.mjs +4 -4
  145. package/dist/astro/routes/api/auth/me.mjs +10 -9
  146. package/dist/astro/routes/api/auth/me.mjs.map +1 -1
  147. package/dist/astro/routes/api/auth/mode.mjs +4 -4
  148. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +3 -3
  149. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
  150. package/dist/astro/routes/api/auth/passkey/_id_.mjs +9 -8
  151. package/dist/astro/routes/api/auth/passkey/_id_.mjs.map +1 -1
  152. package/dist/astro/routes/api/auth/passkey/index.mjs +3 -3
  153. package/dist/astro/routes/api/auth/passkey/options.mjs +14 -13
  154. package/dist/astro/routes/api/auth/passkey/options.mjs.map +1 -1
  155. package/dist/astro/routes/api/auth/passkey/register/options.mjs +12 -11
  156. package/dist/astro/routes/api/auth/passkey/register/options.mjs.map +1 -1
  157. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +13 -12
  158. package/dist/astro/routes/api/auth/passkey/register/verify.mjs.map +1 -1
  159. package/dist/astro/routes/api/auth/passkey/verify.mjs +13 -12
  160. package/dist/astro/routes/api/auth/passkey/verify.mjs.map +1 -1
  161. package/dist/astro/routes/api/auth/signup/complete.mjs +13 -12
  162. package/dist/astro/routes/api/auth/signup/complete.mjs.map +1 -1
  163. package/dist/astro/routes/api/auth/signup/request.mjs +12 -11
  164. package/dist/astro/routes/api/auth/signup/request.mjs.map +1 -1
  165. package/dist/astro/routes/api/auth/signup/verify.mjs +3 -3
  166. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +15 -14
  167. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs.map +1 -1
  168. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +14 -13
  169. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs.map +1 -1
  170. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +4 -4
  171. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +5 -5
  172. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +5 -5
  173. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +4 -4
  174. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +13 -13
  175. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +11 -10
  176. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
  177. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +18 -17
  178. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs.map +1 -1
  179. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs +18 -17
  180. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs.map +1 -1
  181. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +5 -5
  182. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +4 -4
  183. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +11 -10
  184. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs.map +1 -1
  185. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.d.mts.map +1 -1
  186. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +44 -31
  187. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs.map +1 -1
  188. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +4 -4
  189. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +5 -5
  190. package/dist/astro/routes/api/content/_collection_/_id_.mjs +11 -10
  191. package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
  192. package/dist/astro/routes/api/content/_collection_/authors.mjs +4 -4
  193. package/dist/astro/routes/api/content/_collection_/index.mjs +11 -10
  194. package/dist/astro/routes/api/content/_collection_/index.mjs.map +1 -1
  195. package/dist/astro/routes/api/content/_collection_/trash.mjs +10 -9
  196. package/dist/astro/routes/api/content/_collection_/trash.mjs.map +1 -1
  197. package/dist/astro/routes/api/dashboard.mjs +10 -8
  198. package/dist/astro/routes/api/dashboard.mjs.map +1 -1
  199. package/dist/astro/routes/api/dev/emails.mjs +4 -4
  200. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  201. package/dist/astro/routes/api/import/probe.mjs +18 -16
  202. package/dist/astro/routes/api/import/probe.mjs.map +1 -1
  203. package/dist/astro/routes/api/import/wordpress/analyze.mjs +5 -5
  204. package/dist/astro/routes/api/import/wordpress/execute.d.mts +11 -11
  205. package/dist/astro/routes/api/import/wordpress/execute.mjs +15 -15
  206. package/dist/astro/routes/api/import/wordpress/execute.mjs.map +1 -1
  207. package/dist/astro/routes/api/import/wordpress/media.d.mts +10 -10
  208. package/dist/astro/routes/api/import/wordpress/media.mjs +14 -12
  209. package/dist/astro/routes/api/import/wordpress/media.mjs.map +1 -1
  210. package/dist/astro/routes/api/import/wordpress/prepare.mjs +13 -12
  211. package/dist/astro/routes/api/import/wordpress/prepare.mjs.map +1 -1
  212. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +11 -11
  213. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +14 -14
  214. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  215. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +18 -16
  216. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs.map +1 -1
  217. package/dist/astro/routes/api/import/wordpress-plugin/callback.mjs +1 -1
  218. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +11 -11
  219. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +28 -27
  220. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
  221. package/dist/astro/routes/api/manifest.mjs +5 -5
  222. package/dist/astro/routes/api/mcp.d.mts.map +1 -1
  223. package/dist/astro/routes/api/mcp.mjs +174 -72
  224. package/dist/astro/routes/api/mcp.mjs.map +1 -1
  225. package/dist/astro/routes/api/media/_id_/confirm.mjs +13 -11
  226. package/dist/astro/routes/api/media/_id_/confirm.mjs.map +1 -1
  227. package/dist/astro/routes/api/media/_id_/upload.mjs +6 -6
  228. package/dist/astro/routes/api/media/_id_/usage.mjs +17 -16
  229. package/dist/astro/routes/api/media/_id_/usage.mjs.map +1 -1
  230. package/dist/astro/routes/api/media/_id_.mjs +16 -15
  231. package/dist/astro/routes/api/media/_id_.mjs.map +1 -1
  232. package/dist/astro/routes/api/media/file/_...key_.mjs +3 -3
  233. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +4 -4
  234. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +4 -4
  235. package/dist/astro/routes/api/media/providers/index.mjs +4 -4
  236. package/dist/astro/routes/api/media/upload-url.mjs +11 -10
  237. package/dist/astro/routes/api/media/upload-url.mjs.map +1 -1
  238. package/dist/astro/routes/api/media.mjs +18 -15
  239. package/dist/astro/routes/api/media.mjs.map +1 -1
  240. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +11 -11
  241. package/dist/astro/routes/api/menus/_name_/items.mjs +11 -11
  242. package/dist/astro/routes/api/menus/_name_/reorder.mjs +11 -11
  243. package/dist/astro/routes/api/menus/_name_/translations.mjs +11 -11
  244. package/dist/astro/routes/api/menus/_name_.mjs +11 -11
  245. package/dist/astro/routes/api/menus/index.mjs +11 -11
  246. package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
  247. package/dist/astro/routes/api/oauth/device/authorize.mjs +7 -7
  248. package/dist/astro/routes/api/oauth/device/code.mjs +10 -10
  249. package/dist/astro/routes/api/oauth/device/token.mjs +9 -9
  250. package/dist/astro/routes/api/oauth/register.mjs +4 -4
  251. package/dist/astro/routes/api/oauth/token/refresh.mjs +7 -7
  252. package/dist/astro/routes/api/oauth/token/revoke.mjs +7 -7
  253. package/dist/astro/routes/api/oauth/token.mjs +7 -7
  254. package/dist/astro/routes/api/openapi.json.mjs +152 -6
  255. package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
  256. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.d.mts.map +1 -1
  257. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +8 -7
  258. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs.map +1 -1
  259. package/dist/astro/routes/api/redirects/404s/index.mjs +12 -11
  260. package/dist/astro/routes/api/redirects/404s/index.mjs.map +1 -1
  261. package/dist/astro/routes/api/redirects/404s/summary.mjs +12 -11
  262. package/dist/astro/routes/api/redirects/404s/summary.mjs.map +1 -1
  263. package/dist/astro/routes/api/redirects/_id_.mjs +13 -12
  264. package/dist/astro/routes/api/redirects/_id_.mjs.map +1 -1
  265. package/dist/astro/routes/api/redirects/index.mjs +13 -12
  266. package/dist/astro/routes/api/redirects/index.mjs.map +1 -1
  267. package/dist/astro/routes/api/relations/_id_/index.mjs +18 -17
  268. package/dist/astro/routes/api/relations/_id_/index.mjs.map +1 -1
  269. package/dist/astro/routes/api/relations/_id_/translations.mjs +13 -12
  270. package/dist/astro/routes/api/relations/_id_/translations.mjs.map +1 -1
  271. package/dist/astro/routes/api/relations/index.mjs +18 -17
  272. package/dist/astro/routes/api/relations/index.mjs.map +1 -1
  273. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +4 -4
  274. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +5 -5
  275. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +47 -42
  276. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
  277. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +47 -42
  278. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs.map +1 -1
  279. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +47 -42
  280. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
  281. package/dist/astro/routes/api/schema/collections/_slug_/index.d.mts.map +1 -1
  282. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +47 -42
  283. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
  284. package/dist/astro/routes/api/schema/collections/index.mjs +47 -42
  285. package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
  286. package/dist/astro/routes/api/schema/collections/reorder.mjs +47 -42
  287. package/dist/astro/routes/api/schema/collections/reorder.mjs.map +1 -1
  288. package/dist/astro/routes/api/schema/index.mjs +10 -9
  289. package/dist/astro/routes/api/schema/index.mjs.map +1 -1
  290. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +47 -42
  291. package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
  292. package/dist/astro/routes/api/schema/orphans/index.mjs +47 -42
  293. package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
  294. package/dist/astro/routes/api/search/enable.mjs +13 -13
  295. package/dist/astro/routes/api/search/index.mjs +12 -12
  296. package/dist/astro/routes/api/search/rebuild.mjs +13 -13
  297. package/dist/astro/routes/api/search/stats.mjs +8 -8
  298. package/dist/astro/routes/api/search/suggest.mjs +12 -12
  299. package/dist/astro/routes/api/sections/_slug_.mjs +12 -11
  300. package/dist/astro/routes/api/sections/_slug_.mjs.map +1 -1
  301. package/dist/astro/routes/api/sections/index.mjs +12 -11
  302. package/dist/astro/routes/api/sections/index.mjs.map +1 -1
  303. package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +7 -7
  304. package/dist/astro/routes/api/settings/backups/archives/index.mjs +6 -6
  305. package/dist/astro/routes/api/settings/backups/export.mjs +7 -7
  306. package/dist/astro/routes/api/settings/backups/index.mjs +8 -8
  307. package/dist/astro/routes/api/settings/email.mjs +5 -5
  308. package/dist/astro/routes/api/settings.mjs +14 -13
  309. package/dist/astro/routes/api/settings.mjs.map +1 -1
  310. package/dist/astro/routes/api/setup/admin-verify.mjs +14 -13
  311. package/dist/astro/routes/api/setup/admin-verify.mjs.map +1 -1
  312. package/dist/astro/routes/api/setup/admin.mjs +13 -12
  313. package/dist/astro/routes/api/setup/admin.mjs.map +1 -1
  314. package/dist/astro/routes/api/setup/dev-bypass.mjs +30 -28
  315. package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
  316. package/dist/astro/routes/api/setup/dev-reset.mjs +3 -3
  317. package/dist/astro/routes/api/setup/index.mjs +32 -30
  318. package/dist/astro/routes/api/setup/index.mjs.map +1 -1
  319. package/dist/astro/routes/api/setup/status.mjs +5 -5
  320. package/dist/astro/routes/api/snapshot.mjs +8 -8
  321. package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs +16 -15
  322. package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs.map +1 -1
  323. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +16 -15
  324. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs.map +1 -1
  325. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +16 -15
  326. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs.map +1 -1
  327. package/dist/astro/routes/api/taxonomies/_name_/terms/index.d.mts.map +1 -1
  328. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +18 -16
  329. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs.map +1 -1
  330. package/dist/astro/routes/api/taxonomies/_name_/translations.d.mts +8 -0
  331. package/dist/astro/routes/api/taxonomies/_name_/translations.d.mts.map +1 -0
  332. package/dist/astro/routes/api/taxonomies/_name_/translations.mjs +46 -0
  333. package/dist/astro/routes/api/taxonomies/_name_/translations.mjs.map +1 -0
  334. package/dist/astro/routes/api/taxonomies/_name_.d.mts +19 -0
  335. package/dist/astro/routes/api/taxonomies/_name_.d.mts.map +1 -0
  336. package/dist/astro/routes/api/taxonomies/_name_.mjs +88 -0
  337. package/dist/astro/routes/api/taxonomies/_name_.mjs.map +1 -0
  338. package/dist/astro/routes/api/taxonomies/index.mjs +16 -15
  339. package/dist/astro/routes/api/taxonomies/index.mjs.map +1 -1
  340. package/dist/astro/routes/api/themes/preview.mjs +6 -6
  341. package/dist/astro/routes/api/typegen.mjs +6 -6
  342. package/dist/astro/routes/api/well-known/auth.mjs +1 -1
  343. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
  344. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
  345. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +10 -9
  346. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs.map +1 -1
  347. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +12 -11
  348. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
  349. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +12 -11
  350. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
  351. package/dist/astro/routes/api/widget-areas/_name_.mjs +7 -6
  352. package/dist/astro/routes/api/widget-areas/_name_.mjs.map +1 -1
  353. package/dist/astro/routes/api/widget-areas/index.mjs +12 -11
  354. package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
  355. package/dist/astro/routes/api/widget-components.mjs +4 -4
  356. package/dist/astro/routes/robots.txt.mjs +7 -6
  357. package/dist/astro/routes/robots.txt.mjs.map +1 -1
  358. package/dist/astro/routes/sitemap-_collection_.xml.mjs +10 -10
  359. package/dist/astro/routes/sitemap.xml.mjs +8 -7
  360. package/dist/astro/routes/sitemap.xml.mjs.map +1 -1
  361. package/dist/astro/types.d.mts +35 -17
  362. package/dist/astro/types.d.mts.map +1 -1
  363. package/dist/auth/providers/github.d.mts +1 -1
  364. package/dist/auth/providers/google.d.mts +1 -1
  365. package/dist/{authorize-qRhWQnel.mjs → authorize-1iFJzPaZ.mjs} +2 -2
  366. package/dist/{authorize-qRhWQnel.mjs.map → authorize-1iFJzPaZ.mjs.map} +1 -1
  367. package/dist/{backup-CtA6LDNw.mjs → backup-LHjIAVNP.mjs} +4 -4
  368. package/dist/{backup-CtA6LDNw.mjs.map → backup-LHjIAVNP.mjs.map} +1 -1
  369. package/dist/{base64-B-PsqheR.mjs → base64-CvLhUKQv.mjs} +1 -1
  370. package/dist/{base64-B-PsqheR.mjs.map → base64-CvLhUKQv.mjs.map} +1 -1
  371. package/dist/bundle-CBEXrBnb.mjs +663 -0
  372. package/dist/bundle-CBEXrBnb.mjs.map +1 -0
  373. package/dist/{byline-fields-CNc0Yq5i.mjs → byline-fields-C5S8wygO.mjs} +2 -2
  374. package/dist/{byline-fields-CNc0Yq5i.mjs.map → byline-fields-C5S8wygO.mjs.map} +1 -1
  375. package/dist/{byline-Cp6csL84.mjs → byline-pEcoDps8.mjs} +5 -5
  376. package/dist/{byline-Cp6csL84.mjs.map → byline-pEcoDps8.mjs.map} +1 -1
  377. package/dist/{byline-registry-ZkPKqEwC.mjs → byline-registry-DDun8sxh.mjs} +2 -2
  378. package/dist/{byline-registry-ZkPKqEwC.mjs.map → byline-registry-DDun8sxh.mjs.map} +1 -1
  379. package/dist/{bylines-CEj5CdT4.mjs → bylines-BWemdX2k.mjs} +7 -7
  380. package/dist/{bylines-CEj5CdT4.mjs.map → bylines-BWemdX2k.mjs.map} +1 -1
  381. package/dist/{bylines-cf-C9lmF.mjs → bylines-DJfSfXCL.mjs} +5 -5
  382. package/dist/{bylines-cf-C9lmF.mjs.map → bylines-DJfSfXCL.mjs.map} +1 -1
  383. package/dist/{cache-C9FptTFR.mjs → cache-CBJaDy_E.mjs} +3 -3
  384. package/dist/{cache-C9FptTFR.mjs.map → cache-CBJaDy_E.mjs.map} +1 -1
  385. package/dist/{challenge-store-D9VNKELo.mjs → challenge-store-CZyFVaad.mjs} +1 -1
  386. package/dist/{challenge-store-D9VNKELo.mjs.map → challenge-store-CZyFVaad.mjs.map} +1 -1
  387. package/dist/{chunks-DbYzib_k.mjs → chunks-DfPax1I0.mjs} +2 -2
  388. package/dist/{chunks-DbYzib_k.mjs.map → chunks-DfPax1I0.mjs.map} +1 -1
  389. package/dist/cli/index.mjs +176 -698
  390. package/dist/cli/index.mjs.map +1 -1
  391. package/dist/client/cf-access.d.mts +1 -1
  392. package/dist/client/index.d.mts +74 -4
  393. package/dist/client/index.d.mts.map +1 -1
  394. package/dist/client/index.mjs +25 -2
  395. package/dist/client/index.mjs.map +1 -1
  396. package/dist/collection-deletion-C8BugSGz.mjs +779 -0
  397. package/dist/collection-deletion-C8BugSGz.mjs.map +1 -0
  398. package/dist/{comment-BIkJ6Fil.mjs → comment-Dxlep6cE.mjs} +2 -2
  399. package/dist/{comment-BIkJ6Fil.mjs.map → comment-Dxlep6cE.mjs.map} +1 -1
  400. package/dist/{comment-reaction-D-laEceb.mjs → comment-reaction-D6qfHKBc.mjs} +2 -2
  401. package/dist/{comment-reaction-D-laEceb.mjs.map → comment-reaction-D6qfHKBc.mjs.map} +1 -1
  402. package/dist/{comments-0_w6KChA.mjs → comments-CTOuhTSX.mjs} +3 -3
  403. package/dist/{comments-0_w6KChA.mjs.map → comments-CTOuhTSX.mjs.map} +1 -1
  404. package/dist/{components-BPxrRjpE.mjs → components-CQy3lh9x.mjs} +1 -1
  405. package/dist/{components-BPxrRjpE.mjs.map → components-CQy3lh9x.mjs.map} +1 -1
  406. package/dist/{config-DyF3OKg_.mjs → config-C3EBstHi.mjs} +18 -2
  407. package/dist/config-C3EBstHi.mjs.map +1 -0
  408. package/dist/{content-CJ94NEx5.mjs → content-BNfwh7xX.mjs} +509 -78
  409. package/dist/content-BNfwh7xX.mjs.map +1 -0
  410. package/dist/content-list-query-DhQ84eVV.d.mts +26 -0
  411. package/dist/content-list-query-DhQ84eVV.d.mts.map +1 -0
  412. package/dist/content-refresh-DNERP4-f.mjs +2560 -0
  413. package/dist/content-refresh-DNERP4-f.mjs.map +1 -0
  414. package/dist/{context-C_1nrqnW.mjs → context-DRgOvSnW.mjs} +25 -17
  415. package/dist/context-DRgOvSnW.mjs.map +1 -0
  416. package/dist/{cron-DRV-UC4K.mjs → cron-Y9aLtgrA.mjs} +1 -1
  417. package/dist/{cron-DRV-UC4K.mjs.map → cron-Y9aLtgrA.mjs.map} +1 -1
  418. package/dist/{dashboard-DINnNVVU.mjs → dashboard-CB8J89rJ.mjs} +44 -10
  419. package/dist/dashboard-CB8J89rJ.mjs.map +1 -0
  420. package/dist/db/index.d.mts +4 -4
  421. package/dist/db/index.mjs +2 -2
  422. package/dist/db/index.mjs.map +1 -1
  423. package/dist/db/libsql.d.mts +1 -1
  424. package/dist/db/postgres.d.mts +1 -1
  425. package/dist/db/sqlite.d.mts +1 -1
  426. package/dist/{db-errors-CcWLaRiR.mjs → db-errors-Cl-sKYTm.mjs} +1 -1
  427. package/dist/{db-errors-CcWLaRiR.mjs.map → db-errors-Cl-sKYTm.mjs.map} +1 -1
  428. package/dist/{default-BD2FG0xV.mjs → default-DdDl3fxv.mjs} +1 -1
  429. package/dist/{default-BD2FG0xV.mjs.map → default-DdDl3fxv.mjs.map} +1 -1
  430. package/dist/{deferred-tasks-C-WRCcKo.d.mts → deferred-tasks-BMsa9F64.d.mts} +1 -1
  431. package/dist/{deferred-tasks-C-WRCcKo.d.mts.map → deferred-tasks-BMsa9F64.d.mts.map} +1 -1
  432. package/dist/{device-flow-CTqd8mud.mjs → device-flow-CaioYGK3.mjs} +4 -4
  433. package/dist/{device-flow-CTqd8mud.mjs.map → device-flow-CaioYGK3.mjs.map} +1 -1
  434. package/dist/dist-D_6CKR8z.mjs +810 -0
  435. package/dist/dist-D_6CKR8z.mjs.map +1 -0
  436. package/dist/{email-console-C6WdcWbL.mjs → email-console-BHNjIwiB.mjs} +1 -1
  437. package/dist/{email-console-C6WdcWbL.mjs.map → email-console-BHNjIwiB.mjs.map} +1 -1
  438. package/dist/{enrich-CFJJgxs_.mjs → enrich-_ySMEvEE.mjs} +2 -2
  439. package/dist/{enrich-CFJJgxs_.mjs.map → enrich-_ySMEvEE.mjs.map} +1 -1
  440. package/dist/{error-oh_9i6bR.mjs → error-DupyJZE2.mjs} +3 -3
  441. package/dist/{error-oh_9i6bR.mjs.map → error-DupyJZE2.mjs.map} +1 -1
  442. package/dist/{errors-BucQ8VYz.mjs → errors-CLUPm7aD.mjs} +14 -2
  443. package/dist/errors-CLUPm7aD.mjs.map +1 -0
  444. package/dist/{escape-CXps5F_v.mjs → escape-C_aLKP3H.mjs} +1 -1
  445. package/dist/{escape-CXps5F_v.mjs.map → escape-C_aLKP3H.mjs.map} +1 -1
  446. package/dist/{field-defs-cache-WD82dMQq.mjs → field-defs-cache-hg6QkjV9.mjs} +4 -4
  447. package/dist/{field-defs-cache-WD82dMQq.mjs.map → field-defs-cache-hg6QkjV9.mjs.map} +1 -1
  448. package/dist/{fts-manager-BITakW_R.mjs → fts-manager-CZ-5dVwJ.mjs} +76 -50
  449. package/dist/fts-manager-CZ-5dVwJ.mjs.map +1 -0
  450. package/dist/{hreflang-BaLCNcDx.d.mts → hreflang-D3Uehfv2.d.mts} +2 -2
  451. package/dist/{hreflang-BaLCNcDx.d.mts.map → hreflang-D3Uehfv2.d.mts.map} +1 -1
  452. package/dist/{hreflang-BdZuVBhL.mjs → hreflang-DNswoYsi.mjs} +7 -7
  453. package/dist/{hreflang-BdZuVBhL.mjs.map → hreflang-DNswoYsi.mjs.map} +1 -1
  454. package/dist/{import-C-9uqyT3.mjs → import-B41u8Rot.mjs} +5 -5
  455. package/dist/{import-C-9uqyT3.mjs.map → import-B41u8Rot.mjs.map} +1 -1
  456. package/dist/{index-M37iz29I.d.mts → index-C3Y-rsyN.d.mts} +226 -73
  457. package/dist/index-C3Y-rsyN.d.mts.map +1 -0
  458. package/dist/{index-MGw47uWq.d.mts → index-Cof4-Fab.d.mts} +5 -10
  459. package/dist/index-Cof4-Fab.d.mts.map +1 -0
  460. package/dist/index.d.mts +20 -19
  461. package/dist/index.mjs +73 -67
  462. package/dist/{init-lock-DJkX6Hto.mjs → init-lock-6b309ZrF.mjs} +1 -1
  463. package/dist/{init-lock-DJkX6Hto.mjs.map → init-lock-6b309ZrF.mjs.map} +1 -1
  464. package/dist/{load-DV1DZuUJ.mjs → load-DmvJrtxS.mjs} +2 -2
  465. package/dist/{load-DV1DZuUJ.mjs.map → load-DmvJrtxS.mjs.map} +1 -1
  466. package/dist/{loader-CGugY7Zl.mjs → loader-DLBqRiGP.mjs} +95 -53
  467. package/dist/loader-DLBqRiGP.mjs.map +1 -0
  468. package/dist/{manifest-schema-Da7kj23y.mjs → manifest-schema-BXbUAHod.mjs} +1 -1
  469. package/dist/{manifest-schema-Da7kj23y.mjs.map → manifest-schema-BXbUAHod.mjs.map} +1 -1
  470. package/dist/media/image-endpoint.mjs +1 -1
  471. package/dist/media/index.d.mts +1 -1
  472. package/dist/media/index.mjs +3 -2
  473. package/dist/media/index.mjs.map +1 -1
  474. package/dist/media/local-runtime.d.mts +12 -12
  475. package/dist/media/local-runtime.mjs +6 -5
  476. package/dist/media/local-runtime.mjs.map +1 -1
  477. package/dist/{media-OhYL-7Io.mjs → media-BWWWb8kg.mjs} +92 -3
  478. package/dist/media-BWWWb8kg.mjs.map +1 -0
  479. package/dist/{media-BP7S-nHT.mjs → media-CzhAn2--.mjs} +2 -2
  480. package/dist/{media-BP7S-nHT.mjs.map → media-CzhAn2--.mjs.map} +1 -1
  481. package/dist/{media-allowlist-D-peH1H5.mjs → media-allowlist-BD6HhgXA.mjs} +14 -3
  482. package/dist/media-allowlist-BD6HhgXA.mjs.map +1 -0
  483. package/dist/{media-upload-DtrwU2B0.mjs → media-upload-9yas9zGH.mjs} +10 -8
  484. package/dist/{media-upload-DtrwU2B0.mjs.map → media-upload-9yas9zGH.mjs.map} +1 -1
  485. package/dist/{media-url-DYs_YLOx.mjs → media-url-BvoX88FA.mjs} +1 -1
  486. package/dist/{media-url-DYs_YLOx.mjs.map → media-url-BvoX88FA.mjs.map} +1 -1
  487. package/dist/{media-usage-BlxfTkN0.mjs → media-usage-De3qvwjc.mjs} +137 -60
  488. package/dist/media-usage-De3qvwjc.mjs.map +1 -0
  489. package/dist/media-usage-work-B9Bksh_b.mjs +152 -0
  490. package/dist/media-usage-work-B9Bksh_b.mjs.map +1 -0
  491. package/dist/media-usage-work-DrCbqBcl.mjs +424 -0
  492. package/dist/media-usage-work-DrCbqBcl.mjs.map +1 -0
  493. package/dist/media-usage-write-fence-aHEgN1zB.mjs +42 -0
  494. package/dist/media-usage-write-fence-aHEgN1zB.mjs.map +1 -0
  495. package/dist/{menus-Y54RDPIN.mjs → menus-CC-yn1F9.mjs} +351 -95
  496. package/dist/menus-CC-yn1F9.mjs.map +1 -0
  497. package/dist/{menus-BcQWxeXu.mjs → menus-fxvEDYE6.mjs} +3 -3
  498. package/dist/{menus-BcQWxeXu.mjs.map → menus-fxvEDYE6.mjs.map} +1 -1
  499. package/dist/{mode-BWyTBe3I.mjs → mode-Bpe-tNNy.mjs} +1 -1
  500. package/dist/{mode-BWyTBe3I.mjs.map → mode-Bpe-tNNy.mjs.map} +1 -1
  501. package/dist/{normalize-C-SHXmra.mjs → normalize-Bc-s_r-a.mjs} +1 -1
  502. package/dist/{normalize-C-SHXmra.mjs.map → normalize-Bc-s_r-a.mjs.map} +1 -1
  503. package/dist/{oauth-authorization-ClvktmrL.mjs → oauth-authorization-BXO1MKej.mjs} +4 -4
  504. package/dist/{oauth-authorization-ClvktmrL.mjs.map → oauth-authorization-BXO1MKej.mjs.map} +1 -1
  505. package/dist/{oauth-clients-8ao-U66l.mjs → oauth-clients-C_DNqtRu.mjs} +1 -1
  506. package/dist/{oauth-clients-8ao-U66l.mjs.map → oauth-clients-C_DNqtRu.mjs.map} +1 -1
  507. package/dist/{oauth-state-store-B1ofdTiQ.mjs → oauth-state-store-DwAxMtk4.mjs} +1 -1
  508. package/dist/{oauth-state-store-B1ofdTiQ.mjs.map → oauth-state-store-DwAxMtk4.mjs.map} +1 -1
  509. package/dist/{oauth-user-lookup-CscEuIiR.mjs → oauth-user-lookup-BxfZizdk.mjs} +1 -1
  510. package/dist/{oauth-user-lookup-CscEuIiR.mjs.map → oauth-user-lookup-BxfZizdk.mjs.map} +1 -1
  511. package/dist/object-cache/memory.d.mts +1 -1
  512. package/dist/{options-CA4eVX6P.d.mts → options-DRe1pIBL.d.mts} +4 -3
  513. package/dist/options-DRe1pIBL.d.mts.map +1 -0
  514. package/dist/page/index.d.mts +2 -2
  515. package/dist/{parse-CJKNuewM.mjs → parse-DFbuc-Fh.mjs} +2 -2
  516. package/dist/{parse-CJKNuewM.mjs.map → parse-DFbuc-Fh.mjs.map} +1 -1
  517. package/dist/{passkey-config-D-zrzMJI.mjs → passkey-config-_2GOwwK8.mjs} +1 -1
  518. package/dist/{passkey-config-D-zrzMJI.mjs.map → passkey-config-_2GOwwK8.mjs.map} +1 -1
  519. package/dist/{placeholder-DaeNS-VG.d.mts → placeholder-CzW7Lshs.d.mts} +1 -1
  520. package/dist/{placeholder-DaeNS-VG.d.mts.map → placeholder-CzW7Lshs.d.mts.map} +1 -1
  521. package/dist/plugin-types.d.mts +8 -1
  522. package/dist/plugin-types.d.mts.map +1 -1
  523. package/dist/plugin-utils.d.mts +10 -10
  524. package/dist/plugins/adapt-sandbox-entry.d.mts +10 -10
  525. package/dist/plugins/adapt-sandbox-entry.mjs +7 -4
  526. package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -1
  527. package/dist/{portable-text-B_kqBmXB.mjs → portable-text-DhKbDAGr.mjs} +1 -1
  528. package/dist/{portable-text-B_kqBmXB.mjs.map → portable-text-DhKbDAGr.mjs.map} +1 -1
  529. package/dist/{preview-BU6PaLpM.mjs → preview-PW-nOnq4.mjs} +2 -2
  530. package/dist/{preview-BU6PaLpM.mjs.map → preview-PW-nOnq4.mjs.map} +1 -1
  531. package/dist/{public-url-Ox9oOlgp.mjs → public-url-CWoFhQT8.mjs} +1 -1
  532. package/dist/{public-url-Ox9oOlgp.mjs.map → public-url-CWoFhQT8.mjs.map} +1 -1
  533. package/dist/{query-C5w7PsRQ.mjs → query-BgjfiLUQ.mjs} +31 -49
  534. package/dist/query-BgjfiLUQ.mjs.map +1 -0
  535. package/dist/{query-rC9HKv1N.mjs → query-vgZNwIhh.mjs} +3 -3
  536. package/dist/{query-rC9HKv1N.mjs.map → query-vgZNwIhh.mjs.map} +1 -1
  537. package/dist/{rate-limit-kug6q99V.mjs → rate-limit-L5anykgo.mjs} +2 -2
  538. package/dist/{rate-limit-kug6q99V.mjs.map → rate-limit-L5anykgo.mjs.map} +1 -1
  539. package/dist/{redirect-DIJxXKOG.mjs → redirect-COsS6SWJ.mjs} +2 -2
  540. package/dist/{redirect-DIJxXKOG.mjs.map → redirect-COsS6SWJ.mjs.map} +1 -1
  541. package/dist/{redirect-Egf2R9yP.mjs → redirect-KBNE3UEs.mjs} +1 -1
  542. package/dist/{redirect-Egf2R9yP.mjs.map → redirect-KBNE3UEs.mjs.map} +1 -1
  543. package/dist/{redirects-Bzm812bY.mjs → redirects-CufRQt0Q.mjs} +4 -4
  544. package/dist/{redirects-Bzm812bY.mjs.map → redirects-CufRQt0Q.mjs.map} +1 -1
  545. package/dist/{redirects-G8wAhWAf.mjs → redirects-cV9g7V-0.mjs} +133 -14
  546. package/dist/redirects-cV9g7V-0.mjs.map +1 -0
  547. package/dist/registry-C581V4TP.mjs +1275 -0
  548. package/dist/registry-C581V4TP.mjs.map +1 -0
  549. package/dist/{relation-Cbe3Sd3m.mjs → relation-DtmAQH7s.mjs} +3 -3
  550. package/dist/{relation-Cbe3Sd3m.mjs.map → relation-DtmAQH7s.mjs.map} +1 -1
  551. package/dist/{relations-Bw_E1TXo.mjs → relations-BBApI4e2.mjs} +3 -3
  552. package/dist/{relations-Bw_E1TXo.mjs.map → relations-BBApI4e2.mjs.map} +1 -1
  553. package/dist/{relations-qHwpUe66.mjs → relations-DICSyfsh.mjs} +6 -6
  554. package/dist/{relations-qHwpUe66.mjs.map → relations-DICSyfsh.mjs.map} +1 -1
  555. package/dist/{relations-G2ta7GIl.d.mts → relations-DItI3MN9.d.mts} +369 -35
  556. package/dist/relations-DItI3MN9.d.mts.map +1 -0
  557. package/dist/request-context.d.mts +1 -1
  558. package/dist/{request-meta-Df-jFbAd.mjs → request-meta-DgZJh3zG.mjs} +2 -2
  559. package/dist/{request-meta-Df-jFbAd.mjs.map → request-meta-DgZJh3zG.mjs.map} +1 -1
  560. package/dist/{resolve-DZzG4HqM.mjs → resolve-DOhVEvty.mjs} +2 -2
  561. package/dist/{resolve-DZzG4HqM.mjs.map → resolve-DOhVEvty.mjs.map} +1 -1
  562. package/dist/{runner-BFTrkYhu.mjs → runner-BiDvq5dv.mjs} +1157 -254
  563. package/dist/runner-BiDvq5dv.mjs.map +1 -0
  564. package/dist/{runner-BaADli8l.d.mts → runner-CgQbELYC.d.mts} +2 -2
  565. package/dist/{runner-BaADli8l.d.mts.map → runner-CgQbELYC.d.mts.map} +1 -1
  566. package/dist/runtime.d.mts +11 -11
  567. package/dist/runtime.mjs +4 -3
  568. package/dist/runtime.mjs.map +1 -1
  569. package/dist/{schema-CiQXr6_F.mjs → schema-BFyTg38T.mjs} +3 -3
  570. package/dist/{schema-CiQXr6_F.mjs.map → schema-BFyTg38T.mjs.map} +1 -1
  571. package/dist/schema-CeQSNwB3.mjs +390 -0
  572. package/dist/schema-CeQSNwB3.mjs.map +1 -0
  573. package/dist/{scopes-CNJVT5i5.mjs → scopes-BVeuhGBE.mjs} +3 -3
  574. package/dist/{scopes-CNJVT5i5.mjs.map → scopes-BVeuhGBE.mjs.map} +1 -1
  575. package/dist/{search-FZhx5dDU.mjs → search-BpuPKZb0.mjs} +25 -15
  576. package/dist/search-BpuPKZb0.mjs.map +1 -0
  577. package/dist/{secrets-DLd8RXee.mjs → secrets-dtYs2hDo.mjs} +3 -3
  578. package/dist/{secrets-DLd8RXee.mjs.map → secrets-dtYs2hDo.mjs.map} +1 -1
  579. package/dist/{sections-BrIrMUCn.mjs → sections-COctk6hf.mjs} +3 -3
  580. package/dist/{sections-BrIrMUCn.mjs.map → sections-COctk6hf.mjs.map} +1 -1
  581. package/dist/seed/index.d.mts +2 -2
  582. package/dist/seed/index.mjs +24 -22
  583. package/dist/seo/index.d.mts +3 -3
  584. package/dist/seo/index.mjs +16 -15
  585. package/dist/seo/index.mjs.map +1 -1
  586. package/dist/{seo-CobNfOvb.mjs → seo-AFpVvTqr.mjs} +5 -3
  587. package/dist/seo-AFpVvTqr.mjs.map +1 -0
  588. package/dist/{seo-DTHRntNC.mjs → seo-CvDxfvFb.mjs} +2 -2
  589. package/dist/{seo-DTHRntNC.mjs.map → seo-CvDxfvFb.mjs.map} +1 -1
  590. package/dist/{service-B-pYGPgR.mjs → service-xMiMSTsR.mjs} +3 -3
  591. package/dist/{service-B-pYGPgR.mjs.map → service-xMiMSTsR.mjs.map} +1 -1
  592. package/dist/{session-user-BvM9-zNl.mjs → session-user-BzVemLkw.mjs} +1 -1
  593. package/dist/{session-user-BvM9-zNl.mjs.map → session-user-BzVemLkw.mjs.map} +1 -1
  594. package/dist/{settings-kJhGiQ4z.mjs → settings-B372MV0p.mjs} +4 -4
  595. package/dist/{settings-kJhGiQ4z.mjs.map → settings-B372MV0p.mjs.map} +1 -1
  596. package/dist/{settings-DPHau-WH.mjs → settings-BE9aqn_9.mjs} +3 -3
  597. package/dist/{settings-DPHau-WH.mjs.map → settings-BE9aqn_9.mjs.map} +1 -1
  598. package/dist/{setup-complete-u50Qm8rY.mjs → setup-complete-DF7TAfD5.mjs} +1 -1
  599. package/dist/{setup-complete-u50Qm8rY.mjs.map → setup-complete-DF7TAfD5.mjs.map} +1 -1
  600. package/dist/{setup-nonce-DCjiiOD2.mjs → setup-nonce-Do-U_cg9.mjs} +1 -1
  601. package/dist/{setup-nonce-DCjiiOD2.mjs.map → setup-nonce-Do-U_cg9.mjs.map} +1 -1
  602. package/dist/{single-flight-cache-DW3w11Pu.mjs → single-flight-cache-B0eLKS6K.mjs} +2 -2
  603. package/dist/{single-flight-cache-DW3w11Pu.mjs.map → single-flight-cache-B0eLKS6K.mjs.map} +1 -1
  604. package/dist/{site-url-I-lJ5kma.mjs → site-url-Bowsi9T-.mjs} +2 -2
  605. package/dist/{site-url-I-lJ5kma.mjs.map → site-url-Bowsi9T-.mjs.map} +1 -1
  606. package/dist/slugify-B994zkXC.mjs +17 -0
  607. package/dist/slugify-B994zkXC.mjs.map +1 -0
  608. package/dist/{snapshot-I_fjLDaD.mjs → snapshot-B5h6aPMh.mjs} +1 -1
  609. package/dist/{snapshot-I_fjLDaD.mjs.map → snapshot-B5h6aPMh.mjs.map} +1 -1
  610. package/dist/{ssrf-CTa6v21i.mjs → ssrf-Cpvg_n3A.mjs} +1 -1
  611. package/dist/{ssrf-CTa6v21i.mjs.map → ssrf-Cpvg_n3A.mjs.map} +1 -1
  612. package/dist/{state-CQvoefYP.mjs → state-DYwPue40.mjs} +1 -1
  613. package/dist/{state-CQvoefYP.mjs.map → state-DYwPue40.mjs.map} +1 -1
  614. package/dist/{status-CIsmlC1E.mjs → status-CWVYpbD8.mjs} +1 -1
  615. package/dist/{status-CIsmlC1E.mjs.map → status-CWVYpbD8.mjs.map} +1 -1
  616. package/dist/storage/local.d.mts +1 -1
  617. package/dist/storage/local.mjs +1 -1
  618. package/dist/storage/s3.d.mts +1 -1
  619. package/dist/storage/s3.mjs +1 -1
  620. package/dist/{taxonomies-CLN1N92E.mjs → taxonomies-B0o9ExNV.mjs} +75 -64
  621. package/dist/taxonomies-B0o9ExNV.mjs.map +1 -0
  622. package/dist/{taxonomies-CKuaZER5.mjs → taxonomies-Dk3MAklL.mjs} +280 -56
  623. package/dist/taxonomies-Dk3MAklL.mjs.map +1 -0
  624. package/dist/{taxonomy-pOrB2kZe.mjs → taxonomy-CPeM8d2o.mjs} +131 -78
  625. package/dist/taxonomy-CPeM8d2o.mjs.map +1 -0
  626. package/dist/{tokens-DagaJGK8.mjs → tokens-C39klX8R.mjs} +2 -2
  627. package/dist/{tokens-DagaJGK8.mjs.map → tokens-C39klX8R.mjs.map} +1 -1
  628. package/dist/{transport-DVsa5YYo.mjs → transport--CfOflrp.mjs} +1 -1
  629. package/dist/{transport-DVsa5YYo.mjs.map → transport--CfOflrp.mjs.map} +1 -1
  630. package/dist/{transport-CAwTaTp_.d.mts → transport-B_rfeU-a.d.mts} +1 -1
  631. package/dist/{transport-CAwTaTp_.d.mts.map → transport-B_rfeU-a.d.mts.map} +1 -1
  632. package/dist/{trusted-proxy-D20_7fyX.mjs → trusted-proxy-CEVekpby.mjs} +1 -1
  633. package/dist/{trusted-proxy-D20_7fyX.mjs.map → trusted-proxy-CEVekpby.mjs.map} +1 -1
  634. package/dist/{types-NBI3rF-_.mjs → types-75KIqzL9.mjs} +18 -2
  635. package/dist/types-75KIqzL9.mjs.map +1 -0
  636. package/dist/{types-CbUcuxxc.d.mts → types-BPuloofb.d.mts} +1 -1
  637. package/dist/{types-CbUcuxxc.d.mts.map → types-BPuloofb.d.mts.map} +1 -1
  638. package/dist/{types-BjxTKxrZ.mjs → types-BW1syrlw.mjs} +1 -1
  639. package/dist/{types-BjxTKxrZ.mjs.map → types-BW1syrlw.mjs.map} +1 -1
  640. package/dist/{types-BnFP1M3J.d.mts → types-C3_AbkDP.d.mts} +32 -10
  641. package/dist/types-C3_AbkDP.d.mts.map +1 -0
  642. package/dist/{types-Dc4ntQjQ.d.mts → types-CAGdePf3.d.mts} +124 -1
  643. package/dist/types-CAGdePf3.d.mts.map +1 -0
  644. package/dist/{types-DLG4IFEQ.d.mts → types-CFUfyt1s.d.mts} +1 -1
  645. package/dist/{types-DLG4IFEQ.d.mts.map → types-CFUfyt1s.d.mts.map} +1 -1
  646. package/dist/{types-LoagAGzH.d.mts → types-CPx2lIGo.d.mts} +1 -1
  647. package/dist/{types-LoagAGzH.d.mts.map → types-CPx2lIGo.d.mts.map} +1 -1
  648. package/dist/{types-Cd8iuS1J.mjs → types-CQPWhPxE.mjs} +9 -12
  649. package/dist/types-CQPWhPxE.mjs.map +1 -0
  650. package/dist/{types-Cz8hNch2.d.mts → types-Cs-TFLNf.d.mts} +25 -5
  651. package/dist/types-Cs-TFLNf.d.mts.map +1 -0
  652. package/dist/types-CtzytVXn.mjs.map +1 -1
  653. package/dist/{types-BGWPMiBO.d.mts → types-D4_1Ga4L.d.mts} +44 -5
  654. package/dist/types-D4_1Ga4L.d.mts.map +1 -0
  655. package/dist/{types-C8oz_ayJ.d.mts → types-DefV3OiO.d.mts} +1 -1
  656. package/dist/{types-C8oz_ayJ.d.mts.map → types-DefV3OiO.d.mts.map} +1 -1
  657. package/dist/{types-BK6U01Ro.d.mts → types-X0MThr7x.d.mts} +1 -1
  658. package/dist/{types-BK6U01Ro.d.mts.map → types-X0MThr7x.d.mts.map} +1 -1
  659. package/dist/{upload-attempts-D8QAFLUJ.mjs → upload-attempts-hlne3iA1.mjs} +1 -1
  660. package/dist/{upload-attempts-D8QAFLUJ.mjs.map → upload-attempts-hlne3iA1.mjs.map} +1 -1
  661. package/dist/url-pattern-BHS0V5Ng.mjs +33 -0
  662. package/dist/url-pattern-BHS0V5Ng.mjs.map +1 -0
  663. package/dist/{user-DBrq4UVq.mjs → user-sHgJ62SP.mjs} +3 -3
  664. package/dist/{user-DBrq4UVq.mjs.map → user-sHgJ62SP.mjs.map} +1 -1
  665. package/dist/{utils-hLzi_2Fn.mjs → utils-D4vD_Y98.mjs} +3 -3
  666. package/dist/{utils-hLzi_2Fn.mjs.map → utils-D4vD_Y98.mjs.map} +1 -1
  667. package/dist/{validate-mKo3RxBM.mjs → validate-BkqIG_Uj.mjs} +24 -5
  668. package/dist/validate-BkqIG_Uj.mjs.map +1 -0
  669. package/dist/{validate-C3oSmA7F.d.mts → validate-DeGtBN84.d.mts} +15 -7
  670. package/dist/validate-DeGtBN84.d.mts.map +1 -0
  671. package/dist/{validation-BgpucsvF.mjs → validation-CiJb_X5o.mjs} +11 -10
  672. package/dist/{validation-BgpucsvF.mjs.map → validation-CiJb_X5o.mjs.map} +1 -1
  673. package/dist/version-x0NjTScG.mjs +7 -0
  674. package/dist/{version-C4PnpmoV.mjs.map → version-x0NjTScG.mjs.map} +1 -1
  675. package/dist/{widgets-CJJtkiKh.mjs → widgets-Cwzvww0W.mjs} +3 -3
  676. package/dist/{widgets-CJJtkiKh.mjs.map → widgets-Cwzvww0W.mjs.map} +1 -1
  677. package/dist/{wxr-taxonomies-Bkpp_2tK.d.mts → wxr-taxonomies-CmC0ZsDr.d.mts} +1 -1
  678. package/dist/{wxr-taxonomies-Bkpp_2tK.d.mts.map → wxr-taxonomies-CmC0ZsDr.d.mts.map} +1 -1
  679. package/dist/{wxr-taxonomies-9PPSy8X6.mjs → wxr-taxonomies-QZjWxjyk.mjs} +13 -32
  680. package/dist/{wxr-taxonomies-9PPSy8X6.mjs.map → wxr-taxonomies-QZjWxjyk.mjs.map} +1 -1
  681. package/dist/{zod-generator-CG64Hnbh.mjs → zod-generator-D11MhxG4.mjs} +52 -5
  682. package/dist/zod-generator-D11MhxG4.mjs.map +1 -0
  683. package/package.json +16 -8
  684. package/src/api/errors.ts +12 -0
  685. package/src/api/handlers/content.ts +127 -29
  686. package/src/api/handlers/dashboard.ts +12 -2
  687. package/src/api/handlers/index.ts +16 -0
  688. package/src/api/handlers/manifest.ts +2 -0
  689. package/src/api/handlers/media-allowlist.ts +13 -2
  690. package/src/api/handlers/media-usage-work.ts +188 -0
  691. package/src/api/handlers/media-usage.ts +5 -3
  692. package/src/api/handlers/schema.ts +9 -3
  693. package/src/api/handlers/seo.ts +5 -2
  694. package/src/api/handlers/taxonomies.ts +345 -82
  695. package/src/api/media-usage-write-fence.ts +64 -0
  696. package/src/api/openapi/document.ts +199 -0
  697. package/src/api/schemas/common.ts +3 -1
  698. package/src/api/schemas/content.ts +122 -1
  699. package/src/api/schemas/media-usage.ts +133 -0
  700. package/src/api/schemas/schema.ts +78 -3
  701. package/src/api/schemas/taxonomies.ts +40 -8
  702. package/src/api/types.ts +1 -0
  703. package/src/astro/integration/index.ts +55 -42
  704. package/src/astro/integration/routes.ts +34 -4
  705. package/src/astro/integration/runtime.ts +15 -0
  706. package/src/astro/integration/virtual-modules.ts +25 -1
  707. package/src/astro/integration/vite-config.ts +16 -1
  708. package/src/astro/middleware/media-usage-write-fence.ts +30 -0
  709. package/src/astro/middleware/redirect.ts +12 -2
  710. package/src/astro/middleware.ts +75 -8
  711. package/src/astro/routes/api/admin/media-usage/collection-deletions/index.ts +22 -0
  712. package/src/astro/routes/api/admin/media-usage/collection-deletions/retry.ts +22 -0
  713. package/src/astro/routes/api/admin/media-usage/work/index.ts +26 -0
  714. package/src/astro/routes/api/admin/media-usage/work/retry.ts +26 -0
  715. package/src/astro/routes/api/admin/plugins/[id]/disable.ts +4 -0
  716. package/src/astro/routes/api/admin/plugins/[id]/enable.ts +4 -0
  717. package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +4 -0
  718. package/src/astro/routes/api/admin/plugins/[id]/update.ts +4 -0
  719. package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +4 -0
  720. package/src/astro/routes/api/admin/plugins/registry/[id]/uninstall.ts +4 -0
  721. package/src/astro/routes/api/admin/plugins/registry/[id]/update.ts +4 -0
  722. package/src/astro/routes/api/admin/plugins/registry/install.ts +4 -0
  723. package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +67 -31
  724. package/src/astro/routes/api/import/wordpress/execute.ts +3 -10
  725. package/src/astro/routes/api/mcp.ts +1 -0
  726. package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +7 -1
  727. package/src/astro/routes/api/schema/collections/[slug]/index.ts +1 -7
  728. package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +1 -0
  729. package/src/astro/routes/api/taxonomies/[name]/translations.ts +48 -0
  730. package/src/astro/routes/api/taxonomies/[name].ts +102 -0
  731. package/src/astro/types.ts +25 -4
  732. package/src/cleanup.ts +6 -12
  733. package/src/cli/commands/doctor.ts +193 -6
  734. package/src/cli/commands/export-seed.ts +4 -1
  735. package/src/client/index.ts +119 -0
  736. package/src/components/Block.astro +12 -0
  737. package/src/components/InlinePortableTextEditor.tsx +89 -1
  738. package/src/content/converters/mark-safety.ts +120 -0
  739. package/src/content/converters/portable-text-to-prosemirror.ts +25 -16
  740. package/src/content/converters/prosemirror-to-portable-text.ts +12 -2
  741. package/src/content-list-query.ts +29 -0
  742. package/src/database/migrations/060_collection_admin_config.ts +16 -0
  743. package/src/database/migrations/061_media_usage_cleanup.ts +77 -0
  744. package/src/database/migrations/062_media_usage_cleanup_fence.ts +295 -0
  745. package/src/database/migrations/063_media_usage_incremental_work.ts +397 -0
  746. package/src/database/migrations/064_fts_plain_text.ts +253 -0
  747. package/src/database/migrations/065_media_usage_collection_deletion.ts +85 -0
  748. package/src/database/migrations/066_media_usage_reconciliation.ts +115 -0
  749. package/src/database/migrations/067_indexed_content_fields.ts +39 -0
  750. package/src/database/migrations/068_content_taxonomy_entry_groups.ts +61 -0
  751. package/src/database/migrations/069_collection_title_date_fields.ts +45 -0
  752. package/src/database/migrations/070_collection_routable.ts +18 -0
  753. package/src/database/migrations/runner.ts +22 -0
  754. package/src/database/repositories/content.ts +861 -96
  755. package/src/database/repositories/media-usage-work.ts +848 -0
  756. package/src/database/repositories/media-usage.ts +1502 -138
  757. package/src/database/repositories/revision.ts +17 -6
  758. package/src/database/repositories/taxonomy.ts +234 -103
  759. package/src/database/repositories/types.ts +51 -2
  760. package/src/database/types.ts +134 -16
  761. package/src/db/adapters.ts +28 -0
  762. package/src/db/index.ts +3 -0
  763. package/src/emdash-runtime.ts +227 -20
  764. package/src/fields/file.ts +7 -4
  765. package/src/fields/image.ts +8 -1
  766. package/src/fields/types.ts +18 -5
  767. package/src/i18n/config.ts +32 -0
  768. package/src/i18n/repair-locale-casing.ts +0 -14
  769. package/src/i18n/taxonomy-locale-diagnostic.ts +47 -0
  770. package/src/import/wxr-taxonomies.ts +7 -47
  771. package/src/index.ts +25 -2
  772. package/src/loader.ts +119 -65
  773. package/src/mcp/server.ts +242 -34
  774. package/src/media/usage/activation.ts +626 -0
  775. package/src/media/usage/capture-triggers.ts +543 -0
  776. package/src/media/usage/cleanup.ts +305 -0
  777. package/src/media/usage/collection-deletion-processor.ts +356 -0
  778. package/src/media/usage/collection-deletion.ts +836 -0
  779. package/src/media/usage/content-fields.ts +40 -3
  780. package/src/media/usage/content-refresh.ts +356 -47
  781. package/src/media/usage/content-repair.ts +180 -61
  782. package/src/media/usage/content-snapshots.ts +85 -105
  783. package/src/media/usage/projection-fingerprint.ts +129 -0
  784. package/src/media/usage/reconciliation-processor.ts +372 -0
  785. package/src/media/usage/reconciliation.ts +1117 -0
  786. package/src/media/usage/source-key.ts +4 -0
  787. package/src/media/usage/types.ts +2 -0
  788. package/src/media/usage/work-processor.ts +248 -0
  789. package/src/menus/index.ts +246 -141
  790. package/src/plugin-types.ts +8 -0
  791. package/src/plugins/adapt-sandbox-entry.ts +24 -5
  792. package/src/plugins/context.ts +24 -7
  793. package/src/plugins/index.ts +9 -0
  794. package/src/plugins/routes.ts +43 -1
  795. package/src/plugins/sandbox/index.ts +10 -1
  796. package/src/plugins/sandbox/types.ts +78 -1
  797. package/src/plugins/scheduler/node.ts +13 -1
  798. package/src/plugins/scheduler/types.ts +2 -0
  799. package/src/plugins/types.ts +39 -2
  800. package/src/query.ts +29 -37
  801. package/src/scheduler-health.ts +46 -0
  802. package/src/schema/registry.ts +777 -255
  803. package/src/schema/types.ts +50 -8
  804. package/src/schema/url-pattern.ts +33 -0
  805. package/src/schema/zod-generator.ts +77 -5
  806. package/src/search/fts-manager.ts +117 -76
  807. package/src/search/query.ts +32 -11
  808. package/src/search/types.ts +2 -0
  809. package/src/seed/apply.ts +68 -15
  810. package/src/seed/types.ts +11 -3
  811. package/src/seed/validate.ts +52 -2
  812. package/src/taxonomies/index.ts +77 -75
  813. package/src/taxonomies/term-counts.ts +21 -8
  814. package/src/utils/slugify.ts +1 -29
  815. package/src/virtual-modules.d.ts +16 -0
  816. package/dist/adapters-BMyNSSZi.d.mts.map +0 -1
  817. package/dist/api-D2hunekQ.mjs.map +0 -1
  818. package/dist/apply-kwNEJW2l.mjs.map +0 -1
  819. package/dist/config-DyF3OKg_.mjs.map +0 -1
  820. package/dist/content-CJ94NEx5.mjs.map +0 -1
  821. package/dist/content-refresh-Xw7aKmou.mjs +0 -1629
  822. package/dist/content-refresh-Xw7aKmou.mjs.map +0 -1
  823. package/dist/context-C_1nrqnW.mjs.map +0 -1
  824. package/dist/dashboard-DINnNVVU.mjs.map +0 -1
  825. package/dist/errors-BucQ8VYz.mjs.map +0 -1
  826. package/dist/fts-manager-BITakW_R.mjs.map +0 -1
  827. package/dist/index-M37iz29I.d.mts.map +0 -1
  828. package/dist/index-MGw47uWq.d.mts.map +0 -1
  829. package/dist/loader-CGugY7Zl.mjs.map +0 -1
  830. package/dist/media-OhYL-7Io.mjs.map +0 -1
  831. package/dist/media-allowlist-D-peH1H5.mjs.map +0 -1
  832. package/dist/media-usage-BlxfTkN0.mjs.map +0 -1
  833. package/dist/menus-Y54RDPIN.mjs.map +0 -1
  834. package/dist/options-CA4eVX6P.d.mts.map +0 -1
  835. package/dist/query-C5w7PsRQ.mjs.map +0 -1
  836. package/dist/redirects-G8wAhWAf.mjs.map +0 -1
  837. package/dist/registry-Dbodb4Uo.mjs +0 -840
  838. package/dist/registry-Dbodb4Uo.mjs.map +0 -1
  839. package/dist/relations-G2ta7GIl.d.mts.map +0 -1
  840. package/dist/runner-BFTrkYhu.mjs.map +0 -1
  841. package/dist/search-FZhx5dDU.mjs.map +0 -1
  842. package/dist/seo-CobNfOvb.mjs.map +0 -1
  843. package/dist/slugify-C_tqlU4G.mjs +0 -30
  844. package/dist/slugify-C_tqlU4G.mjs.map +0 -1
  845. package/dist/taxonomies-CKuaZER5.mjs.map +0 -1
  846. package/dist/taxonomies-CLN1N92E.mjs.map +0 -1
  847. package/dist/taxonomy-pOrB2kZe.mjs.map +0 -1
  848. package/dist/types-BGWPMiBO.d.mts.map +0 -1
  849. package/dist/types-BnFP1M3J.d.mts.map +0 -1
  850. package/dist/types-Cd8iuS1J.mjs.map +0 -1
  851. package/dist/types-Cz8hNch2.d.mts.map +0 -1
  852. package/dist/types-Dc4ntQjQ.d.mts.map +0 -1
  853. package/dist/types-NBI3rF-_.mjs.map +0 -1
  854. package/dist/validate-C3oSmA7F.d.mts.map +0 -1
  855. package/dist/validate-mKo3RxBM.mjs.map +0 -1
  856. package/dist/version-C4PnpmoV.mjs +0 -7
  857. package/dist/zod-generator-CG64Hnbh.mjs.map +0 -1
  858. /package/dist/{api-tokens-Dai9dND0.mjs → api-tokens-CyTsLszZ.mjs} +0 -0
  859. /package/dist/{ssrf-Cj568Dgs.mjs → ssrf-DpXgKsna.mjs} +0 -0
  860. /package/dist/{types-DKuWPYV5.mjs → types-CP1CflWf.mjs} +0 -0
@@ -1,4 +1,11 @@
1
- import type { ColumnDataType, CreateTableBuilder, Insertable, Kysely, Selectable } from "kysely";
1
+ import type {
2
+ ColumnDataType,
3
+ CreateTableBuilder,
4
+ Insertable,
5
+ Kysely,
6
+ Selectable,
7
+ Updateable,
8
+ } from "kysely";
2
9
  import { sql } from "kysely";
3
10
  import { ulid } from "ulidx";
4
11
 
@@ -6,14 +13,27 @@ import { currentTimestamp, listTablesLike, tableExists } from "../database/diale
6
13
  import { withTransaction } from "../database/transaction.js";
7
14
  import type { CollectionTable, Database, FieldTable } from "../database/types.js";
8
15
  import { validateIdentifier } from "../database/validate.js";
16
+ import {
17
+ canResumeMediaUsageCollectionCapture,
18
+ finalizeMediaUsageCollectionCapture,
19
+ installPreparedMediaUsageCollectionCapture,
20
+ markMediaUsageCollectionCaptureReady,
21
+ prepareMediaUsageCollectionCapture,
22
+ } from "../media/usage/activation.js";
23
+ import {
24
+ deleteActivatedMediaUsageCollection,
25
+ isMediaUsageCollectionSlugDeleting,
26
+ } from "../media/usage/collection-deletion.js";
9
27
  import {
10
28
  deleteContentMediaUsageCollection,
29
+ invalidateContentMediaUsageSchemaChange,
11
30
  markContentMediaUsageCollectionStaleSafely,
12
31
  } from "../media/usage/content-refresh.js";
13
32
  import { FTSManager } from "../search/fts-manager.js";
14
33
  import { chunks, SQL_BATCH_SIZE } from "../utils/chunks.js";
15
34
  import {
16
35
  type Collection,
36
+ type CollectionAdminConfig,
17
37
  type CollectionSource,
18
38
  type CollectionSupport,
19
39
  type ColumnType,
@@ -25,6 +45,7 @@ import {
25
45
  type CollectionWithFields,
26
46
  type FieldType,
27
47
  FIELD_TYPE_TO_COLUMN,
48
+ isIndexableFieldType,
28
49
  RESERVED_FIELD_SLUGS,
29
50
  RESERVED_COLLECTION_SLUGS,
30
51
  } from "./types.js";
@@ -35,6 +56,7 @@ const EC_PREFIX_PATTERN = /^ec_/;
35
56
  const SINGLE_QUOTE_PATTERN = /'/g;
36
57
  const UNDERSCORE_PATTERN = /_/g;
37
58
  const WORD_BOUNDARY_PATTERN = /\b\w/g;
59
+ const FIELD_ID_PATTERN = /^[0-9A-Z]{26}$/;
38
60
 
39
61
  /** Valid column types for runtime validation */
40
62
  const COLUMN_TYPES: ReadonlySet<string> = new Set(["TEXT", "REAL", "INTEGER", "JSON"]);
@@ -44,6 +66,10 @@ const COLUMN_TYPE_TO_DATA_TYPE = {
44
66
  INTEGER: "integer",
45
67
  JSON: "json",
46
68
  } satisfies Record<ColumnType, ColumnDataType>;
69
+ const TEXT_ALIAS_FIELD_TYPES: ReadonlySet<FieldType> = new Set(["string", "text", "slug"]);
70
+
71
+ /** Field types usable as a `titleField` — plain text that reads well as a title. */
72
+ const TITLE_FIELD_TYPES: ReadonlySet<string> = new Set(["string", "text", "slug"]);
47
73
 
48
74
  /** Valid collection source prefixes/values */
49
75
  const VALID_SOURCES: ReadonlySet<string> = new Set(["manual", "discovered", "seed"]);
@@ -69,7 +95,7 @@ const VALID_COLLECTION_SUPPORTS: ReadonlySet<string> = new Set<CollectionSupport
69
95
  "seo",
70
96
  ]);
71
97
 
72
- // Each _emdash_fields row uses 15 bound parameters. Six rows keep every
98
+ // Each _emdash_fields row uses 16 bound parameters. Six rows keep every
73
99
  // multi-row INSERT below D1's 100-parameter statement limit.
74
100
  const SEED_FIELD_INSERT_BATCH_SIZE = 6;
75
101
 
@@ -86,6 +112,15 @@ const UNORDERED_COLLECTION_RANK = 2147483647;
86
112
  */
87
113
  const collectionOrder = sql<number>`coalesce(sort_order, ${sql.lit(UNORDERED_COLLECTION_RANK)})`;
88
114
 
115
+ function assertIndexableField(type: FieldType, indexed: boolean | undefined, slug: string): void {
116
+ if (indexed && !isIndexableFieldType(type)) {
117
+ throw new SchemaError(
118
+ `Field "${slug}" cannot be indexed because type "${type}" is not a scalar query type`,
119
+ "FIELD_NOT_INDEXABLE",
120
+ );
121
+ }
122
+ }
123
+
89
124
  function isCollectionSupport(value: unknown): value is CollectionSupport {
90
125
  return typeof value === "string" && VALID_COLLECTION_SUPPORTS.has(value);
91
126
  }
@@ -105,6 +140,87 @@ function parseSupports(raw: string | null | undefined): CollectionSupport[] {
105
140
  return parsed.filter(isCollectionSupport);
106
141
  }
107
142
 
143
+ function isRecord(value: unknown): value is Record<string, unknown> {
144
+ return typeof value === "object" && value !== null && !Array.isArray(value);
145
+ }
146
+
147
+ function parseCollectionAdmin(raw: string | null | undefined): CollectionAdminConfig | undefined {
148
+ if (!raw) return undefined;
149
+ const parsed: unknown = JSON.parse(raw);
150
+ if (!isRecord(parsed)) return undefined;
151
+ const listColumns = parsed.listColumns;
152
+ return {
153
+ listColumns: Array.isArray(listColumns)
154
+ ? listColumns.filter((value): value is string => typeof value === "string")
155
+ : undefined,
156
+ };
157
+ }
158
+
159
+ export async function buildSeedCollectionCaptureFingerprint(
160
+ input: Omit<CreateCollectionInput, "source">,
161
+ fields: readonly CreateFieldInput[],
162
+ ): Promise<string> {
163
+ const supports = input.supports ?? ["drafts", "revisions"];
164
+ const hasSeo = input.hasSeo ?? supports.includes("seo") ?? false;
165
+ let maxSortOrder = -1;
166
+ const definitions = fields.map((field) => {
167
+ const sortOrder = field.sortOrder ?? maxSortOrder + 1;
168
+ maxSortOrder = Math.max(maxSortOrder, sortOrder);
169
+ return {
170
+ slug: field.slug,
171
+ label: field.label,
172
+ type: field.type,
173
+ required: field.required ?? false,
174
+ unique: field.unique ?? false,
175
+ defaultValue: field.defaultValue === undefined ? null : JSON.stringify(field.defaultValue),
176
+ validation: field.validation ? JSON.stringify(field.validation) : null,
177
+ widget: field.widget ?? null,
178
+ options: field.options ? JSON.stringify(field.options) : null,
179
+ sortOrder,
180
+ searchable: field.searchable ?? false,
181
+ translatable: field.translatable ?? true,
182
+ };
183
+ });
184
+ const payload = JSON.stringify(
185
+ canonicalizeFingerprintValue({
186
+ version: 1,
187
+ collection: {
188
+ slug: input.slug,
189
+ label: input.label,
190
+ labelSingular: input.labelSingular ?? null,
191
+ description: input.description ?? null,
192
+ icon: input.icon ?? null,
193
+ admin: input.admin ?? null,
194
+ supports,
195
+ hasSeo,
196
+ hidden: input.hidden ?? false,
197
+ sortOrder: input.sortOrder ?? null,
198
+ commentsEnabled: input.commentsEnabled ?? false,
199
+ urlPattern: input.urlPattern ?? null,
200
+ routable: input.routable ?? true,
201
+ },
202
+ fields: definitions,
203
+ }),
204
+ );
205
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(payload));
206
+ const hex = Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join(
207
+ "",
208
+ );
209
+ return `media-usage-seed:v1:sha256:${hex}`;
210
+ }
211
+
212
+ function canonicalizeFingerprintValue(value: unknown): unknown {
213
+ if (value === undefined) return { __emdashUndefined: true };
214
+ if (Array.isArray(value)) return value.map(canonicalizeFingerprintValue);
215
+ if (typeof value !== "object" || value === null) return value;
216
+
217
+ const canonical: Record<string, unknown> = {};
218
+ for (const [key, entry] of Object.entries(value).toSorted(([a], [b]) => a.localeCompare(b))) {
219
+ canonical[key] = canonicalizeFingerprintValue(entry);
220
+ }
221
+ return canonical;
222
+ }
223
+
108
224
  /**
109
225
  * Error thrown when a schema operation fails
110
226
  */
@@ -227,6 +343,61 @@ export class SchemaRegistry {
227
343
  }));
228
344
  }
229
345
 
346
+ /**
347
+ * Validate `titleField`/`dateField` against the collection's fields:
348
+ * `titleField` must be a text-like field; `dateField` must be a `datetime` field.
349
+ * Only truthy values are checked (undefined = unchanged, null/"" = cleared).
350
+ */
351
+ private async validateTitleDateFields(
352
+ collectionId: string,
353
+ collectionSlug: string,
354
+ input: { titleField?: string | null; dateField?: string | null },
355
+ db: Kysely<Database> = this.db,
356
+ ): Promise<void> {
357
+ const slugs = [input.titleField, input.dateField].filter((slug): slug is string => !!slug);
358
+ if (slugs.length === 0) return;
359
+
360
+ const rows = await db
361
+ .selectFrom("_emdash_fields")
362
+ .where("collection_id", "=", collectionId)
363
+ .where("slug", "in", slugs)
364
+ .select(["slug", "type"])
365
+ .execute();
366
+ const typeBySlug = new Map(rows.map((row) => [row.slug, row.type]));
367
+
368
+ if (input.titleField) {
369
+ const type = typeBySlug.get(input.titleField);
370
+ if (type === undefined) {
371
+ throw new SchemaError(
372
+ `titleField "${input.titleField}" is not a field on "${collectionSlug}"`,
373
+ "INVALID_TITLE_FIELD",
374
+ );
375
+ }
376
+ if (!TITLE_FIELD_TYPES.has(type)) {
377
+ throw new SchemaError(
378
+ `titleField "${input.titleField}" must be a text field (got "${type}")`,
379
+ "INVALID_TITLE_FIELD",
380
+ );
381
+ }
382
+ }
383
+
384
+ if (input.dateField) {
385
+ const type = typeBySlug.get(input.dateField);
386
+ if (type === undefined) {
387
+ throw new SchemaError(
388
+ `dateField "${input.dateField}" is not a field on "${collectionSlug}"`,
389
+ "INVALID_DATE_FIELD",
390
+ );
391
+ }
392
+ if (type !== "datetime") {
393
+ throw new SchemaError(
394
+ `dateField "${input.dateField}" must be a datetime field (got "${type}")`,
395
+ "INVALID_DATE_FIELD",
396
+ );
397
+ }
398
+ }
399
+ }
400
+
230
401
  /**
231
402
  * Create a new collection
232
403
  */
@@ -236,14 +407,26 @@ export class SchemaRegistry {
236
407
  if (RESERVED_COLLECTION_SLUGS.includes(input.slug)) {
237
408
  throw new SchemaError(`Collection slug "${input.slug}" is reserved`, "RESERVED_SLUG");
238
409
  }
410
+ if (await isMediaUsageCollectionSlugDeleting(this.db, input.slug)) {
411
+ throw new SchemaError(`Collection "${input.slug}" already exists`, "COLLECTION_EXISTS");
412
+ }
239
413
 
240
414
  // Check if collection already exists
241
415
  const existing = await this.getCollection(input.slug);
242
- if (existing) {
416
+ if (await isMediaUsageCollectionSlugDeleting(this.db, input.slug)) {
417
+ throw new SchemaError(`Collection "${input.slug}" already exists`, "COLLECTION_EXISTS");
418
+ }
419
+ if (
420
+ existing &&
421
+ !(await canResumeMediaUsageCollectionCapture(this.db, {
422
+ collectionId: existing.id,
423
+ collectionSlug: existing.slug,
424
+ }))
425
+ ) {
243
426
  throw new SchemaError(`Collection "${input.slug}" already exists`, "COLLECTION_EXISTS");
244
427
  }
245
428
 
246
- const id = ulid();
429
+ const proposedId = existing?.id ?? ulid();
247
430
 
248
431
  // Default `supports` to drafts + revisions when the caller didn't
249
432
  // specify it. Explicit empty array (`[]`) is preserved as an opt-out
@@ -259,26 +442,52 @@ export class SchemaRegistry {
259
442
  const hasSeo = input.hasSeo ?? supports.includes("seo") ?? false;
260
443
 
261
444
  await withTransaction(this.db, async (trx) => {
262
- await trx
263
- .insertInto("_emdash_collections")
264
- .values({
265
- id,
266
- slug: input.slug,
267
- label: input.label,
268
- label_singular: input.labelSingular ?? null,
269
- description: input.description ?? null,
270
- icon: input.icon ?? null,
271
- supports: JSON.stringify(supports),
272
- source: input.source ?? "manual",
273
- has_seo: hasSeo ? 1 : 0,
274
- hidden: input.hidden ? 1 : 0,
275
- sort_order: input.sortOrder ?? null,
276
- comments_enabled: input.commentsEnabled ? 1 : 0,
277
- url_pattern: input.urlPattern ?? null,
278
- })
279
- .execute();
445
+ const capture = await prepareMediaUsageCollectionCapture(trx, {
446
+ collectionId: proposedId,
447
+ collectionSlug: input.slug,
448
+ registeredCollectionId: existing?.id,
449
+ });
450
+ const values: Insertable<CollectionTable> = {
451
+ id: capture.collectionId,
452
+ slug: input.slug,
453
+ label: input.label,
454
+ label_singular: input.labelSingular ?? null,
455
+ description: input.description ?? null,
456
+ icon: input.icon ?? null,
457
+ admin_config: input.admin ? JSON.stringify(input.admin) : null,
458
+ supports: JSON.stringify(supports),
459
+ source: input.source ?? "manual",
460
+ has_seo: hasSeo ? 1 : 0,
461
+ routable: input.routable === false ? 0 : 1,
462
+ hidden: input.hidden ? 1 : 0,
463
+ sort_order: input.sortOrder ?? null,
464
+ comments_enabled: input.commentsEnabled ? 1 : 0,
465
+ url_pattern: input.urlPattern ?? null,
466
+ };
280
467
 
281
- // Create the content table for this collection
468
+ if (capture.captureRequired) {
469
+ await this.createContentTable(input.slug, trx, [], {
470
+ ifNotExists: capture.resuming,
471
+ });
472
+ await installPreparedMediaUsageCollectionCapture(trx, {
473
+ collectionId: capture.collectionId,
474
+ collectionSlug: input.slug,
475
+ });
476
+ await markMediaUsageCollectionCaptureReady(trx, {
477
+ collectionId: capture.collectionId,
478
+ collectionSlug: input.slug,
479
+ });
480
+ if (!capture.registrationExists) {
481
+ await trx.insertInto("_emdash_collections").values(values).execute();
482
+ }
483
+ await finalizeMediaUsageCollectionCapture(trx, {
484
+ collectionId: capture.collectionId,
485
+ collectionSlug: input.slug,
486
+ });
487
+ return;
488
+ }
489
+
490
+ await trx.insertInto("_emdash_collections").values(values).execute();
282
491
  await this.createContentTable(input.slug, trx);
283
492
  });
284
493
 
@@ -308,15 +517,14 @@ export class SchemaRegistry {
308
517
  if (RESERVED_COLLECTION_SLUGS.includes(input.slug)) {
309
518
  throw new SchemaError(`Collection slug "${input.slug}" is reserved`, "RESERVED_SLUG");
310
519
  }
311
-
312
- const existing = await this.getCollection(input.slug);
313
- if (existing) {
520
+ if (await isMediaUsageCollectionSlugDeleting(this.db, input.slug)) {
314
521
  throw new SchemaError(`Collection "${input.slug}" already exists`, "COLLECTION_EXISTS");
315
522
  }
316
523
 
317
524
  const fieldSlugs = new Set<string>();
318
525
  for (const field of fields) {
319
526
  this.validateSlug(field.slug, "field");
527
+ assertIndexableField(field.type, field.indexed, field.slug);
320
528
  if (RESERVED_FIELD_SLUGS.includes(field.slug)) {
321
529
  throw new SchemaError(`Field slug "${field.slug}" is reserved`, "RESERVED_SLUG");
322
530
  }
@@ -329,9 +537,25 @@ export class SchemaRegistry {
329
537
  fieldSlugs.add(field.slug);
330
538
  }
331
539
 
332
- const collectionId = ulid();
333
540
  const supports = input.supports ?? ["drafts", "revisions"];
334
541
  const hasSeo = input.hasSeo ?? supports.includes("seo") ?? false;
542
+ const creationFingerprint = await buildSeedCollectionCaptureFingerprint(input, fields);
543
+ const existing = await this.getCollection(input.slug);
544
+ if (await isMediaUsageCollectionSlugDeleting(this.db, input.slug)) {
545
+ throw new SchemaError(`Collection "${input.slug}" already exists`, "COLLECTION_EXISTS");
546
+ }
547
+ if (
548
+ existing &&
549
+ !(await canResumeMediaUsageCollectionCapture(this.db, {
550
+ collectionId: existing.id,
551
+ collectionSlug: existing.slug,
552
+ creationFingerprint,
553
+ }))
554
+ ) {
555
+ throw new SchemaError(`Collection "${input.slug}" already exists`, "COLLECTION_EXISTS");
556
+ }
557
+
558
+ const proposedCollectionId = existing?.id ?? ulid();
335
559
  let maxSortOrder = -1;
336
560
  const fieldRows: Insertable<FieldTable>[] = fields.map((field) => {
337
561
  const sortOrder = field.sortOrder ?? maxSortOrder + 1;
@@ -339,7 +563,7 @@ export class SchemaRegistry {
339
563
 
340
564
  return {
341
565
  id: ulid(),
342
- collection_id: collectionId,
566
+ collection_id: proposedCollectionId,
343
567
  slug: field.slug,
344
568
  label: field.label,
345
569
  type: field.type,
@@ -352,6 +576,7 @@ export class SchemaRegistry {
352
576
  options: field.options ? JSON.stringify(field.options) : null,
353
577
  sort_order: sortOrder,
354
578
  searchable: field.searchable ? 1 : 0,
579
+ indexed: field.indexed ? 1 : 0,
355
580
  translatable: field.translatable === false ? 0 : 1,
356
581
  };
357
582
  });
@@ -359,30 +584,79 @@ export class SchemaRegistry {
359
584
  let schemaMutated = false;
360
585
  try {
361
586
  await withTransaction(this.db, async (trx) => {
362
- await trx
363
- .insertInto("_emdash_collections")
364
- .values({
365
- id: collectionId,
366
- slug: input.slug,
367
- label: input.label,
368
- label_singular: input.labelSingular ?? null,
369
- description: input.description ?? null,
370
- icon: input.icon ?? null,
371
- supports: JSON.stringify(supports),
372
- source: "seed",
373
- has_seo: hasSeo ? 1 : 0,
374
- hidden: input.hidden ? 1 : 0,
375
- sort_order: input.sortOrder ?? null,
376
- comments_enabled: input.commentsEnabled ? 1 : 0,
377
- url_pattern: input.urlPattern ?? null,
378
- })
379
- .execute();
380
- schemaMutated = true;
381
-
382
- await this.createContentTable(input.slug, trx, fields);
587
+ const capture = await prepareMediaUsageCollectionCapture(trx, {
588
+ collectionId: proposedCollectionId,
589
+ collectionSlug: input.slug,
590
+ creationFingerprint,
591
+ registeredCollectionId: existing?.id,
592
+ });
593
+ const collectionValues: Insertable<CollectionTable> = {
594
+ id: capture.collectionId,
595
+ slug: input.slug,
596
+ label: input.label,
597
+ label_singular: input.labelSingular ?? null,
598
+ description: input.description ?? null,
599
+ icon: input.icon ?? null,
600
+ admin_config: input.admin ? JSON.stringify(input.admin) : null,
601
+ supports: JSON.stringify(supports),
602
+ source: "seed",
603
+ has_seo: hasSeo ? 1 : 0,
604
+ routable: input.routable === false ? 0 : 1,
605
+ hidden: input.hidden ? 1 : 0,
606
+ sort_order: input.sortOrder ?? null,
607
+ comments_enabled: input.commentsEnabled ? 1 : 0,
608
+ url_pattern: input.urlPattern ?? null,
609
+ };
610
+ const rows = fieldRows.map((row) => ({
611
+ ...row,
612
+ collection_id: capture.collectionId,
613
+ }));
614
+
615
+ if (capture.captureRequired) {
616
+ await this.createContentTable(input.slug, trx, fields, {
617
+ ifNotExists: capture.resuming,
618
+ });
619
+ await installPreparedMediaUsageCollectionCapture(trx, {
620
+ collectionId: capture.collectionId,
621
+ collectionSlug: input.slug,
622
+ });
623
+ await markMediaUsageCollectionCaptureReady(trx, {
624
+ collectionId: capture.collectionId,
625
+ collectionSlug: input.slug,
626
+ });
627
+ if (!capture.registrationExists) {
628
+ await trx.insertInto("_emdash_collections").values(collectionValues).execute();
629
+ schemaMutated = true;
630
+ }
631
+ } else {
632
+ await trx.insertInto("_emdash_collections").values(collectionValues).execute();
633
+ schemaMutated = true;
634
+ await this.createContentTable(input.slug, trx, fields);
635
+ }
383
636
 
384
- for (const fieldBatch of chunks(fieldRows, SEED_FIELD_INSERT_BATCH_SIZE)) {
385
- await trx.insertInto("_emdash_fields").values(fieldBatch).execute();
637
+ for (const fieldBatch of chunks(rows, SEED_FIELD_INSERT_BATCH_SIZE)) {
638
+ let insert = trx.insertInto("_emdash_fields").values(fieldBatch);
639
+ if (capture.resuming) {
640
+ insert = insert.onConflict((conflict) =>
641
+ conflict.columns(["collection_id", "slug"]).doNothing(),
642
+ );
643
+ }
644
+ await insert.execute();
645
+ }
646
+ let indexedRows: readonly { id: string; slug: string; indexed?: number }[] = rows;
647
+ if (capture.resuming) {
648
+ indexedRows = await this.assertSeedFieldDefinitions(capture.collectionId, fields, trx);
649
+ }
650
+ for (const field of indexedRows) {
651
+ if (field.indexed === 1) {
652
+ await this.createFieldIndex(input.slug, field.id, field.slug, trx);
653
+ }
654
+ }
655
+ if (capture.captureRequired) {
656
+ await finalizeMediaUsageCollectionCapture(trx, {
657
+ collectionId: capture.collectionId,
658
+ collectionSlug: input.slug,
659
+ });
386
660
  }
387
661
  });
388
662
 
@@ -399,69 +673,108 @@ export class SchemaRegistry {
399
673
  }
400
674
  }
401
675
 
676
+ private async assertSeedFieldDefinitions(
677
+ collectionId: string,
678
+ fields: readonly CreateFieldInput[],
679
+ db: Kysely<Database>,
680
+ ): Promise<Selectable<FieldTable>[]> {
681
+ const stored = await db
682
+ .selectFrom("_emdash_fields")
683
+ .selectAll()
684
+ .where("collection_id", "=", collectionId)
685
+ .execute();
686
+ if (stored.length !== fields.length) {
687
+ throw new SchemaError("Interrupted seed collection fields do not match", "CREATE_FAILED");
688
+ }
689
+
690
+ let maxSortOrder = -1;
691
+ for (const field of fields) {
692
+ const sortOrder = field.sortOrder ?? maxSortOrder + 1;
693
+ maxSortOrder = Math.max(maxSortOrder, sortOrder);
694
+ const row = stored.find((candidate) => candidate.slug === field.slug);
695
+ if (
696
+ !row ||
697
+ row.label !== field.label ||
698
+ row.type !== field.type ||
699
+ row.column_type !== FIELD_TYPE_TO_COLUMN[field.type] ||
700
+ row.required !== (field.required ? 1 : 0) ||
701
+ row.unique !== (field.unique ? 1 : 0) ||
702
+ row.default_value !==
703
+ (field.defaultValue !== undefined ? JSON.stringify(field.defaultValue) : null) ||
704
+ row.validation !== (field.validation ? JSON.stringify(field.validation) : null) ||
705
+ row.widget !== (field.widget ?? null) ||
706
+ row.options !== (field.options ? JSON.stringify(field.options) : null) ||
707
+ row.sort_order !== sortOrder ||
708
+ row.searchable !== (field.searchable ? 1 : 0) ||
709
+ row.indexed !== (field.indexed ? 1 : 0) ||
710
+ row.translatable !== (field.translatable === false ? 0 : 1)
711
+ ) {
712
+ throw new SchemaError("Interrupted seed collection fields do not match", "CREATE_FAILED");
713
+ }
714
+ }
715
+ return stored;
716
+ }
717
+
402
718
  /**
403
719
  * Update a collection
404
720
  */
405
721
  async updateCollection(slug: string, input: UpdateCollectionInput): Promise<Collection> {
406
- const existing = await this.getCollection(slug);
407
- if (!existing) {
408
- throw new SchemaError(`Collection "${slug}" not found`, "COLLECTION_NOT_FOUND");
409
- }
410
-
411
- const now = new Date().toISOString();
412
-
413
- // Derive hasSeo from supports array if supports is being updated and hasSeo not explicitly set
414
- const supportsArray = input.supports ?? existing.supports;
415
- const hasSeo =
416
- input.hasSeo !== undefined
417
- ? input.hasSeo
418
- : input.supports !== undefined
419
- ? supportsArray.includes("seo")
420
- : existing.hasSeo;
421
-
422
722
  return withTransaction(this.db, async (trx) => {
723
+ const existingRow = await trx
724
+ .selectFrom("_emdash_collections")
725
+ .where("slug", "=", slug)
726
+ .selectAll()
727
+ .executeTakeFirst();
728
+ if (!existingRow) {
729
+ throw new SchemaError(`Collection "${slug}" not found`, "COLLECTION_NOT_FOUND");
730
+ }
731
+ const existing = this.mapCollectionRow(existingRow);
732
+ await this.validateTitleDateFields(
733
+ existing.id,
734
+ slug,
735
+ {
736
+ titleField: input.titleField,
737
+ dateField: input.dateField,
738
+ },
739
+ trx,
740
+ );
741
+ const updates: Updateable<CollectionTable> = {};
742
+
743
+ if (input.label !== undefined) updates.label = input.label;
744
+ if (input.labelSingular !== undefined) updates.label_singular = input.labelSingular;
745
+ if (input.description !== undefined) updates.description = input.description;
746
+ if (input.icon !== undefined) updates.icon = input.icon;
747
+ if (input.admin !== undefined) updates.admin_config = JSON.stringify(input.admin);
748
+ if (input.supports !== undefined) updates.supports = JSON.stringify(input.supports);
749
+ if (input.urlPattern !== undefined) updates.url_pattern = input.urlPattern;
750
+ if (input.routable !== undefined) updates.routable = input.routable ? 1 : 0;
751
+ if (input.hasSeo !== undefined) {
752
+ updates.has_seo = input.hasSeo ? 1 : 0;
753
+ } else if (input.supports !== undefined) {
754
+ updates.has_seo = input.supports.includes("seo") ? 1 : 0;
755
+ }
756
+ if (input.hidden !== undefined) updates.hidden = input.hidden ? 1 : 0;
757
+ if (input.sortOrder !== undefined) updates.sort_order = input.sortOrder;
758
+ if (input.titleField !== undefined) updates.title_field = input.titleField || null;
759
+ if (input.dateField !== undefined) updates.date_field = input.dateField || null;
760
+ if (input.commentsEnabled !== undefined) {
761
+ updates.comments_enabled = input.commentsEnabled ? 1 : 0;
762
+ }
763
+ if (input.commentsModeration !== undefined) {
764
+ updates.comments_moderation = input.commentsModeration;
765
+ }
766
+ if (input.commentsClosedAfterDays !== undefined) {
767
+ updates.comments_closed_after_days = input.commentsClosedAfterDays;
768
+ }
769
+ if (input.commentsAutoApproveUsers !== undefined) {
770
+ updates.comments_auto_approve_users = input.commentsAutoApproveUsers ? 1 : 0;
771
+ }
772
+
773
+ updates.updated_at = new Date().toISOString();
423
774
  await trx
424
775
  .updateTable("_emdash_collections")
425
- .set({
426
- label: input.label ?? existing.label,
427
- label_singular: input.labelSingular ?? existing.labelSingular ?? null,
428
- description: input.description ?? existing.description ?? null,
429
- icon: input.icon ?? existing.icon ?? null,
430
- supports: input.supports
431
- ? JSON.stringify(input.supports)
432
- : JSON.stringify(existing.supports),
433
- url_pattern:
434
- input.urlPattern !== undefined
435
- ? (input.urlPattern ?? null)
436
- : (existing.urlPattern ?? null),
437
- has_seo: hasSeo ? 1 : 0,
438
- hidden: input.hidden !== undefined ? (input.hidden ? 1 : 0) : existing.hidden ? 1 : 0,
439
- sort_order:
440
- input.sortOrder !== undefined ? input.sortOrder : (existing.sortOrder ?? null),
441
- comments_enabled:
442
- input.commentsEnabled !== undefined
443
- ? input.commentsEnabled
444
- ? 1
445
- : 0
446
- : existing.commentsEnabled
447
- ? 1
448
- : 0,
449
- comments_moderation: input.commentsModeration ?? existing.commentsModeration,
450
- comments_closed_after_days:
451
- input.commentsClosedAfterDays !== undefined
452
- ? input.commentsClosedAfterDays
453
- : existing.commentsClosedAfterDays,
454
- comments_auto_approve_users:
455
- input.commentsAutoApproveUsers !== undefined
456
- ? input.commentsAutoApproveUsers
457
- ? 1
458
- : 0
459
- : existing.commentsAutoApproveUsers
460
- ? 1
461
- : 0,
462
- updated_at: now,
463
- })
464
- .where("slug", "=", slug)
776
+ .set(updates)
777
+ .where("id", "=", existing.id)
465
778
  .execute();
466
779
 
467
780
  const row = await trx
@@ -492,21 +805,31 @@ export class SchemaRegistry {
492
805
  */
493
806
  async deleteCollection(slug: string, options?: { force?: boolean }): Promise<void> {
494
807
  const existing = await this.getCollection(slug);
808
+ if (existing && !options?.force && (await this.collectionHasContent(slug))) {
809
+ throw new SchemaError(
810
+ `Collection "${slug}" has content. Use force: true to delete.`,
811
+ "COLLECTION_HAS_CONTENT",
812
+ );
813
+ }
814
+ const activated = await deleteActivatedMediaUsageCollection(this.db, {
815
+ collectionId: existing?.id,
816
+ collectionSlug: slug,
817
+ forceDelete: options?.force === true,
818
+ });
819
+ if (activated === "has_content") {
820
+ throw new SchemaError(
821
+ `Collection "${slug}" has content. Use force: true to delete.`,
822
+ "COLLECTION_HAS_CONTENT",
823
+ );
824
+ }
825
+ if (activated === "in_progress") {
826
+ throw new SchemaError(`Collection "${slug}" deletion is already in progress`, "CONFLICT");
827
+ }
828
+ if (activated === "deleted") return;
495
829
  if (!existing) {
496
830
  throw new SchemaError(`Collection "${slug}" not found`, "COLLECTION_NOT_FOUND");
497
831
  }
498
832
 
499
- // Check if collection has content
500
- if (!options?.force) {
501
- const hasContent = await this.collectionHasContent(slug);
502
- if (hasContent) {
503
- throw new SchemaError(
504
- `Collection "${slug}" has content. Use force: true to delete.`,
505
- "COLLECTION_HAS_CONTENT",
506
- );
507
- }
508
- }
509
-
510
833
  let contentTableDropped = false;
511
834
  try {
512
835
  await withTransaction(this.db, async (trx) => {
@@ -593,6 +916,7 @@ export class SchemaRegistry {
593
916
 
594
917
  const id = ulid();
595
918
  const columnType = FIELD_TYPE_TO_COLUMN[input.type];
919
+ assertIndexableField(input.type, input.indexed, input.slug);
596
920
 
597
921
  // Get max sort order
598
922
  const maxSort = await this.db
@@ -602,6 +926,10 @@ export class SchemaRegistry {
602
926
  .executeTakeFirst();
603
927
 
604
928
  const sortOrder = input.sortOrder ?? (maxSort?.max ?? -1) + 1;
929
+ const activeCoverageInvalidated = await invalidateContentMediaUsageSchemaChange(
930
+ this.db,
931
+ collectionSlug,
932
+ );
605
933
 
606
934
  let schemaMutated = false;
607
935
  try {
@@ -625,6 +953,7 @@ export class SchemaRegistry {
625
953
  options: input.options ? JSON.stringify(input.options) : null,
626
954
  sort_order: sortOrder,
627
955
  searchable: input.searchable ? 1 : 0,
956
+ indexed: input.indexed ? 1 : 0,
628
957
  translatable: input.translatable === false ? 0 : 1,
629
958
  })
630
959
  .execute();
@@ -642,6 +971,10 @@ export class SchemaRegistry {
642
971
  trx,
643
972
  );
644
973
 
974
+ if (input.indexed) {
975
+ await this.createFieldIndex(collectionSlug, id, input.slug, trx);
976
+ }
977
+
645
978
  // Read the created field via trx (not this.db) to avoid connection mutex deadlock
646
979
  const fieldRow = await trx
647
980
  .selectFrom("_emdash_fields")
@@ -663,20 +996,28 @@ export class SchemaRegistry {
663
996
 
664
997
  return field;
665
998
  });
666
- await markContentMediaUsageCollectionStaleSafely(
667
- this.db,
668
- collectionSlug,
669
- "CONTENT_USAGE_STALE",
670
- );
671
- return created;
672
- } catch (error) {
673
- if (schemaMutated) {
999
+ if (activeCoverageInvalidated) {
1000
+ await invalidateContentMediaUsageSchemaChange(this.db, collectionSlug);
1001
+ } else {
674
1002
  await markContentMediaUsageCollectionStaleSafely(
675
1003
  this.db,
676
1004
  collectionSlug,
677
1005
  "CONTENT_USAGE_STALE",
678
1006
  );
679
1007
  }
1008
+ return created;
1009
+ } catch (error) {
1010
+ if (schemaMutated) {
1011
+ if (activeCoverageInvalidated) {
1012
+ await invalidateContentMediaUsageSchemaChange(this.db, collectionSlug);
1013
+ } else {
1014
+ await markContentMediaUsageCollectionStaleSafely(
1015
+ this.db,
1016
+ collectionSlug,
1017
+ "CONTENT_USAGE_STALE",
1018
+ );
1019
+ }
1020
+ }
680
1021
  throw error;
681
1022
  }
682
1023
  }
@@ -689,87 +1030,126 @@ export class SchemaRegistry {
689
1030
  fieldSlug: string,
690
1031
  input: UpdateFieldInput,
691
1032
  ): Promise<Field> {
692
- const field = await this.getField(collectionSlug, fieldSlug);
693
- if (!field) {
694
- throw new SchemaError(
695
- `Field "${fieldSlug}" not found in collection "${collectionSlug}"`,
696
- "FIELD_NOT_FOUND",
697
- );
698
- }
699
-
700
- // `input.validation === undefined` means "no change" (keep existing);
701
- // an explicit `null` clears the column.
702
- const nextValidation = input.validation === undefined ? field.validation : input.validation;
703
-
704
- // A field-type change is only safe when the underlying column type stays
705
- // the same. There is no in-place column migration (only addColumn/
706
- // dropColumn), so a change that would alter the SQLite column affinity
707
- // (e.g. `text` TEXT -> `portableText` JSON) is rejected rather than
708
- // silently rewriting only the metadata — which would leave `column_type`
709
- // pointing at a column type the real `ec_*` column doesn't have (#1397).
710
- let nextType = field.type;
711
- let nextColumnType = field.columnType;
712
- if (input.type !== undefined && input.type !== field.type) {
713
- const newColumnType = FIELD_TYPE_TO_COLUMN[input.type];
714
- if (newColumnType !== field.columnType) {
715
- throw new SchemaError(
716
- `Cannot change field "${fieldSlug}" in collection "${collectionSlug}" from type ` +
717
- `"${field.type}" to "${input.type}": the underlying column type would change from ` +
718
- `${field.columnType} to ${newColumnType}, which requires a manual content migration. ` +
719
- `Drop and re-create the field, or migrate the column data, before changing its type.`,
720
- "FIELD_TYPE_COLUMN_CHANGE",
721
- );
722
- }
723
- nextType = input.type;
724
- nextColumnType = newColumnType;
725
- }
726
-
1033
+ let activeCoverageInvalidated = false;
727
1034
  let schemaMutated = false;
728
1035
  try {
729
1036
  const updatedField = await withTransaction(this.db, async (trx) => {
730
- await trx
731
- .updateTable("_emdash_fields")
732
- .set({
733
- type: nextType,
734
- column_type: nextColumnType,
735
- label: input.label ?? field.label,
736
- required:
737
- input.required !== undefined ? (input.required ? 1 : 0) : field.required ? 1 : 0,
738
- unique: input.unique !== undefined ? (input.unique ? 1 : 0) : field.unique ? 1 : 0,
739
- searchable:
740
- input.searchable !== undefined
741
- ? input.searchable
742
- ? 1
743
- : 0
744
- : field.searchable
745
- ? 1
746
- : 0,
747
- translatable:
748
- input.translatable !== undefined
749
- ? input.translatable
750
- ? 1
751
- : 0
752
- : field.translatable
753
- ? 1
754
- : 0,
755
- default_value:
756
- input.defaultValue !== undefined
757
- ? JSON.stringify(input.defaultValue)
758
- : field.defaultValue !== undefined
759
- ? JSON.stringify(field.defaultValue)
760
- : null,
761
- validation: nextValidation ? JSON.stringify(nextValidation) : null,
762
- widget: input.widget ?? field.widget ?? null,
763
- options: input.options
764
- ? JSON.stringify(input.options)
765
- : field.options
766
- ? JSON.stringify(field.options)
767
- : null,
768
- sort_order: input.sortOrder ?? field.sortOrder,
769
- })
770
- .where("id", "=", field.id)
771
- .execute();
772
- schemaMutated = true;
1037
+ const collectionRow = await trx
1038
+ .selectFrom("_emdash_collections")
1039
+ .where("slug", "=", collectionSlug)
1040
+ .select(["id", "title_field", "date_field"])
1041
+ .executeTakeFirst();
1042
+ const fieldRow = collectionRow
1043
+ ? await trx
1044
+ .selectFrom("_emdash_fields")
1045
+ .where("collection_id", "=", collectionRow.id)
1046
+ .where("slug", "=", fieldSlug)
1047
+ .selectAll()
1048
+ .executeTakeFirst()
1049
+ : undefined;
1050
+ if (!fieldRow) {
1051
+ throw new SchemaError(
1052
+ `Field "${fieldSlug}" not found in collection "${collectionSlug}"`,
1053
+ "FIELD_NOT_FOUND",
1054
+ );
1055
+ }
1056
+ const field = this.mapFieldRow(fieldRow);
1057
+ const updates: Updateable<FieldTable> = {};
1058
+ let nextType = field.type;
1059
+
1060
+ if (input.type !== undefined && input.type !== field.type) {
1061
+ const newColumnType = FIELD_TYPE_TO_COLUMN[input.type];
1062
+ if (newColumnType !== field.columnType) {
1063
+ throw new SchemaError(
1064
+ `Cannot change field "${fieldSlug}" in collection "${collectionSlug}" from type ` +
1065
+ `"${field.type}" to "${input.type}": the underlying column type would change from ` +
1066
+ `${field.columnType} to ${newColumnType}, which requires a manual content migration.`,
1067
+ "FIELD_TYPE_COLUMN_CHANGE",
1068
+ );
1069
+ }
1070
+ if (!TEXT_ALIAS_FIELD_TYPES.has(field.type) || !TEXT_ALIAS_FIELD_TYPES.has(input.type)) {
1071
+ throw new SchemaError(
1072
+ `Cannot change field "${fieldSlug}" in collection "${collectionSlug}" from type ` +
1073
+ `"${field.type}" to "${input.type}" without a manual content migration.`,
1074
+ "FIELD_TYPE_CHANGE_REQUIRES_MIGRATION",
1075
+ );
1076
+ }
1077
+ if (collectionRow?.title_field === fieldSlug && !TITLE_FIELD_TYPES.has(input.type)) {
1078
+ throw new SchemaError(
1079
+ `titleField "${fieldSlug}" must stay a text field, not "${input.type}"`,
1080
+ "INVALID_TITLE_FIELD",
1081
+ );
1082
+ }
1083
+ if (collectionRow?.date_field === fieldSlug && input.type !== "datetime") {
1084
+ throw new SchemaError(
1085
+ `dateField "${fieldSlug}" must stay a datetime field, not "${input.type}"`,
1086
+ "INVALID_DATE_FIELD",
1087
+ );
1088
+ }
1089
+ nextType = input.type;
1090
+ updates.type = input.type;
1091
+ updates.column_type = newColumnType;
1092
+ }
1093
+
1094
+ if (input.required !== undefined && input.required !== field.required) {
1095
+ throw new SchemaError(
1096
+ `Changing required for field "${fieldSlug}" requires a manual content migration.`,
1097
+ "FIELD_UPDATE_REQUIRES_MIGRATION",
1098
+ );
1099
+ }
1100
+ if (input.unique !== undefined && input.unique !== field.unique) {
1101
+ throw new SchemaError(
1102
+ `Changing unique for field "${fieldSlug}" requires a manual content migration.`,
1103
+ "FIELD_UPDATE_REQUIRES_MIGRATION",
1104
+ );
1105
+ }
1106
+ if (input.translatable === false && field.translatable) {
1107
+ throw new SchemaError(
1108
+ `Changing field "${fieldSlug}" to non-translatable requires a manual content migration.`,
1109
+ "FIELD_UPDATE_REQUIRES_MIGRATION",
1110
+ );
1111
+ }
1112
+
1113
+ if (input.label !== undefined) updates.label = input.label;
1114
+ if (input.required !== undefined) updates.required = input.required ? 1 : 0;
1115
+ if (input.unique !== undefined) updates.unique = input.unique ? 1 : 0;
1116
+ if (input.searchable !== undefined) updates.searchable = input.searchable ? 1 : 0;
1117
+ const indexedChanged = input.indexed !== undefined && input.indexed !== field.indexed;
1118
+ if (indexedChanged) updates.indexed = input.indexed ? 1 : 0;
1119
+ if (input.translatable !== undefined) {
1120
+ updates.translatable = input.translatable ? 1 : 0;
1121
+ }
1122
+ if (input.defaultValue !== undefined) {
1123
+ updates.default_value = JSON.stringify(input.defaultValue);
1124
+ }
1125
+ if (input.validation !== undefined) {
1126
+ updates.validation = input.validation ? JSON.stringify(input.validation) : null;
1127
+ }
1128
+ if (input.widget !== undefined) updates.widget = input.widget;
1129
+ if (input.options !== undefined) updates.options = JSON.stringify(input.options);
1130
+ if (input.sortOrder !== undefined) updates.sort_order = input.sortOrder;
1131
+
1132
+ assertIndexableField(nextType, input.indexed ?? field.indexed, fieldSlug);
1133
+ if (Object.keys(updates).length === 0) return field;
1134
+
1135
+ activeCoverageInvalidated = await invalidateContentMediaUsageSchemaChange(
1136
+ trx,
1137
+ collectionSlug,
1138
+ );
1139
+
1140
+ if (Object.keys(updates).length > 0) {
1141
+ await trx.updateTable("_emdash_fields").set(updates).where("id", "=", field.id).execute();
1142
+ schemaMutated = true;
1143
+ }
1144
+
1145
+ if (indexedChanged) {
1146
+ if (input.indexed) {
1147
+ await this.createFieldIndex(collectionSlug, field.id, fieldSlug, trx);
1148
+ } else {
1149
+ await this.dropFieldIndex(field.id, trx);
1150
+ }
1151
+ schemaMutated = true;
1152
+ }
773
1153
 
774
1154
  // Read the updated field via trx (not this.db) to avoid connection mutex deadlock
775
1155
  const updatedRow = await trx
@@ -787,26 +1167,36 @@ export class SchemaRegistry {
787
1167
 
788
1168
  // If searchable changed, sync FTS state for this collection
789
1169
  const searchableChanged =
790
- input.searchable !== undefined && input.searchable !== field.searchable;
1170
+ schemaMutated && input.searchable !== undefined && input.searchable !== field.searchable;
791
1171
  if (searchableChanged) {
792
1172
  await this.syncSearchState(collectionSlug, trx);
793
1173
  }
794
1174
 
795
1175
  return updated;
796
1176
  });
797
- await markContentMediaUsageCollectionStaleSafely(
798
- this.db,
799
- collectionSlug,
800
- "CONTENT_USAGE_STALE",
801
- );
1177
+ if (schemaMutated) {
1178
+ if (activeCoverageInvalidated) {
1179
+ await invalidateContentMediaUsageSchemaChange(this.db, collectionSlug);
1180
+ } else {
1181
+ await markContentMediaUsageCollectionStaleSafely(
1182
+ this.db,
1183
+ collectionSlug,
1184
+ "CONTENT_USAGE_STALE",
1185
+ );
1186
+ }
1187
+ }
802
1188
  return updatedField;
803
1189
  } catch (error) {
804
1190
  if (schemaMutated) {
805
- await markContentMediaUsageCollectionStaleSafely(
806
- this.db,
807
- collectionSlug,
808
- "CONTENT_USAGE_STALE",
809
- );
1191
+ if (activeCoverageInvalidated) {
1192
+ await invalidateContentMediaUsageSchemaChange(this.db, collectionSlug);
1193
+ } else {
1194
+ await markContentMediaUsageCollectionStaleSafely(
1195
+ this.db,
1196
+ collectionSlug,
1197
+ "CONTENT_USAGE_STALE",
1198
+ );
1199
+ }
810
1200
  }
811
1201
  throw error;
812
1202
  }
@@ -866,6 +1256,17 @@ export class SchemaRegistry {
866
1256
  "FIELD_NOT_FOUND",
867
1257
  );
868
1258
  }
1259
+ const activeCoverageInvalidated = await invalidateContentMediaUsageSchemaChange(
1260
+ this.db,
1261
+ collectionSlug,
1262
+ );
1263
+
1264
+ // If this field powers the collection's titleField/dateField,
1265
+ // clear that reference in the same transaction — otherwise the metadata
1266
+ // would point at a dropped column and later crash the content list sort.
1267
+ const collection = await this.getCollection(collectionSlug);
1268
+ const clearTitle = collection?.titleField === fieldSlug;
1269
+ const clearDate = collection?.dateField === fieldSlug;
869
1270
 
870
1271
  let schemaMutated = false;
871
1272
  try {
@@ -878,27 +1279,51 @@ export class SchemaRegistry {
878
1279
  await trx.deleteFrom("_emdash_fields").where("id", "=", field.id).execute();
879
1280
  schemaMutated = true;
880
1281
 
1282
+ if (clearTitle || clearDate) {
1283
+ await trx
1284
+ .updateTable("_emdash_collections")
1285
+ .set({
1286
+ ...(clearTitle ? { title_field: null } : {}),
1287
+ ...(clearDate ? { date_field: null } : {}),
1288
+ updated_at: new Date().toISOString(),
1289
+ })
1290
+ .where("slug", "=", collectionSlug)
1291
+ .execute();
1292
+ }
1293
+
881
1294
  // If the deleted field was searchable, sync FTS state (removes old triggers)
882
1295
  if (field.searchable) {
883
1296
  await this.syncSearchState(collectionSlug, trx);
884
1297
  }
885
1298
 
1299
+ if (field.indexed) {
1300
+ await this.dropFieldIndex(field.id, trx);
1301
+ }
1302
+
886
1303
  // Drop column from content table — safe now because FTS triggers are gone
887
1304
  await this.dropColumn(collectionSlug, fieldSlug, trx);
888
1305
  });
889
- await markContentMediaUsageCollectionStaleSafely(
890
- this.db,
891
- collectionSlug,
892
- "CONTENT_USAGE_STALE",
893
- );
894
- } catch (error) {
895
- if (schemaMutated) {
1306
+ if (activeCoverageInvalidated) {
1307
+ await invalidateContentMediaUsageSchemaChange(this.db, collectionSlug);
1308
+ } else {
896
1309
  await markContentMediaUsageCollectionStaleSafely(
897
1310
  this.db,
898
1311
  collectionSlug,
899
1312
  "CONTENT_USAGE_STALE",
900
1313
  );
901
1314
  }
1315
+ } catch (error) {
1316
+ if (schemaMutated) {
1317
+ if (activeCoverageInvalidated) {
1318
+ await invalidateContentMediaUsageSchemaChange(this.db, collectionSlug);
1319
+ } else {
1320
+ await markContentMediaUsageCollectionStaleSafely(
1321
+ this.db,
1322
+ collectionSlug,
1323
+ "CONTENT_USAGE_STALE",
1324
+ );
1325
+ }
1326
+ }
902
1327
  throw error;
903
1328
  }
904
1329
  }
@@ -988,6 +1413,7 @@ export class SchemaRegistry {
988
1413
  slug: string,
989
1414
  db?: Kysely<Database>,
990
1415
  fields: readonly CreateFieldInput[] = [],
1416
+ options: { ifNotExists?: boolean } = {},
991
1417
  ): Promise<void> {
992
1418
  const conn = db ?? this.db;
993
1419
  const tableName = this.getTableName(slug);
@@ -1009,6 +1435,7 @@ export class SchemaRegistry {
1009
1435
  .addColumn("draft_revision_id", "text", (col) => col.references("revisions.id"))
1010
1436
  .addColumn("locale", "text", (col) => col.notNull().defaultTo("en"))
1011
1437
  .addColumn("translation_group", "text");
1438
+ if (options.ifNotExists) table = table.ifNotExists();
1012
1439
 
1013
1440
  for (const field of fields) {
1014
1441
  const columnName = this.getColumnName(field.slug);
@@ -1028,40 +1455,42 @@ export class SchemaRegistry {
1028
1455
  .addUniqueConstraint(`${tableName}_slug_locale_unique`, ["slug", "locale"])
1029
1456
  .execute();
1030
1457
 
1458
+ const createIndex = options.ifNotExists ? sql`CREATE INDEX IF NOT EXISTS` : sql`CREATE INDEX`;
1459
+
1031
1460
  // Create standard indexes
1032
1461
  await sql`
1033
- CREATE INDEX ${sql.ref(`idx_${tableName}_slug`)}
1462
+ ${createIndex} ${sql.ref(`idx_${tableName}_slug`)}
1034
1463
  ON ${sql.ref(tableName)} (slug)
1035
1464
  `.execute(conn);
1036
1465
 
1037
1466
  await sql`
1038
- CREATE INDEX ${sql.ref(`idx_${tableName}_scheduled`)}
1467
+ ${createIndex} ${sql.ref(`idx_${tableName}_scheduled`)}
1039
1468
  ON ${sql.ref(tableName)} (scheduled_at)
1040
1469
  WHERE scheduled_at IS NOT NULL
1041
1470
  `.execute(conn);
1042
1471
 
1043
1472
  await sql`
1044
- CREATE INDEX ${sql.ref(`idx_${tableName}_live_revision`)}
1473
+ ${createIndex} ${sql.ref(`idx_${tableName}_live_revision`)}
1045
1474
  ON ${sql.ref(tableName)} (live_revision_id)
1046
1475
  `.execute(conn);
1047
1476
 
1048
1477
  await sql`
1049
- CREATE INDEX ${sql.ref(`idx_${tableName}_draft_revision`)}
1478
+ ${createIndex} ${sql.ref(`idx_${tableName}_draft_revision`)}
1050
1479
  ON ${sql.ref(tableName)} (draft_revision_id)
1051
1480
  `.execute(conn);
1052
1481
 
1053
1482
  await sql`
1054
- CREATE INDEX ${sql.ref(`idx_${tableName}_author`)}
1483
+ ${createIndex} ${sql.ref(`idx_${tableName}_author`)}
1055
1484
  ON ${sql.ref(tableName)} (author_id)
1056
1485
  `.execute(conn);
1057
1486
 
1058
1487
  await sql`
1059
- CREATE INDEX ${sql.ref(`idx_${tableName}_primary_byline`)}
1488
+ ${createIndex} ${sql.ref(`idx_${tableName}_primary_byline`)}
1060
1489
  ON ${sql.ref(tableName)} (primary_byline_id)
1061
1490
  `.execute(conn);
1062
1491
 
1063
1492
  await sql`
1064
- CREATE INDEX ${sql.ref(`idx_${tableName}_locale`)}
1493
+ ${createIndex} ${sql.ref(`idx_${tableName}_locale`)}
1065
1494
  ON ${sql.ref(tableName)} (locale)
1066
1495
  `.execute(conn);
1067
1496
 
@@ -1070,33 +1499,33 @@ export class SchemaRegistry {
1070
1499
  // (menu and reference resolution) need the first; reads that do need the
1071
1500
  // second.
1072
1501
  await sql`
1073
- CREATE INDEX ${sql.ref(`idx_${tableName}_tg_locale`)}
1502
+ ${createIndex} ${sql.ref(`idx_${tableName}_tg_locale`)}
1074
1503
  ON ${sql.ref(tableName)} (translation_group, locale)
1075
1504
  `.execute(conn);
1076
1505
 
1077
1506
  await sql`
1078
- CREATE INDEX ${sql.ref(`idx_${tableName}_del_tg_locale`)}
1507
+ ${createIndex} ${sql.ref(`idx_${tableName}_del_tg_locale`)}
1079
1508
  ON ${sql.ref(tableName)} (deleted_at, translation_group, locale)
1080
1509
  `.execute(conn);
1081
1510
 
1082
1511
  // Composite indexes for optimized query performance (see migration 033)
1083
1512
  await sql`
1084
- CREATE INDEX ${sql.ref(`idx_${tableName}_deleted_updated_id`)}
1513
+ ${createIndex} ${sql.ref(`idx_${tableName}_deleted_updated_id`)}
1085
1514
  ON ${sql.ref(tableName)} (deleted_at, updated_at DESC, id DESC)
1086
1515
  `.execute(conn);
1087
1516
 
1088
1517
  await sql`
1089
- CREATE INDEX ${sql.ref(`idx_${tableName}_deleted_status`)}
1518
+ ${createIndex} ${sql.ref(`idx_${tableName}_deleted_status`)}
1090
1519
  ON ${sql.ref(tableName)} (deleted_at, status)
1091
1520
  `.execute(conn);
1092
1521
 
1093
1522
  await sql`
1094
- CREATE INDEX ${sql.ref(`idx_${tableName}_deleted_created_id`)}
1523
+ ${createIndex} ${sql.ref(`idx_${tableName}_deleted_created_id`)}
1095
1524
  ON ${sql.ref(tableName)} (deleted_at, created_at DESC, id DESC)
1096
1525
  `.execute(conn);
1097
1526
 
1098
1527
  await sql`
1099
- CREATE INDEX ${sql.ref(`idx_${tableName}_deleted_published_id`)}
1528
+ ${createIndex} ${sql.ref(`idx_${tableName}_deleted_published_id`)}
1100
1529
  ON ${sql.ref(tableName)} (deleted_at, published_at DESC, id DESC)
1101
1530
  `.execute(conn);
1102
1531
 
@@ -1105,16 +1534,67 @@ export class SchemaRegistry {
1105
1534
  // inside Postgres's 63-byte identifier limit for long slugs; keep these
1106
1535
  // names identical to migration 041.
1107
1536
  await sql`
1108
- CREATE INDEX ${sql.ref(`idx_${tableName}_loc_upd`)}
1537
+ ${createIndex} ${sql.ref(`idx_${tableName}_loc_upd`)}
1109
1538
  ON ${sql.ref(tableName)} (deleted_at, locale, updated_at DESC, id DESC)
1110
1539
  `.execute(conn);
1111
1540
 
1112
1541
  await sql`
1113
- CREATE INDEX ${sql.ref(`idx_${tableName}_loc_crt`)}
1542
+ ${createIndex} ${sql.ref(`idx_${tableName}_loc_crt`)}
1114
1543
  ON ${sql.ref(tableName)} (deleted_at, locale, created_at DESC, id DESC)
1115
1544
  `.execute(conn);
1116
1545
  }
1117
1546
 
1547
+ private getFieldIndexName(fieldId: string): string {
1548
+ if (!FIELD_ID_PATTERN.test(fieldId)) {
1549
+ throw new SchemaError(`Invalid field id "${fieldId}"`, "INVALID_FIELD_ID");
1550
+ }
1551
+ return `idx_cf_${fieldId.toLowerCase()}`;
1552
+ }
1553
+
1554
+ private getLocaleFieldIndexName(fieldId: string): string {
1555
+ return `${this.getFieldIndexName(fieldId)}_loc`;
1556
+ }
1557
+
1558
+ private async createFieldIndex(
1559
+ collectionSlug: string,
1560
+ fieldId: string,
1561
+ fieldSlug: string,
1562
+ db?: Kysely<Database>,
1563
+ ): Promise<void> {
1564
+ const conn = db ?? this.db;
1565
+ const tableName = this.getTableName(collectionSlug);
1566
+ const columnName = this.getColumnName(fieldSlug);
1567
+ const indexName = this.getFieldIndexName(fieldId);
1568
+ const localeIndexName = this.getLocaleFieldIndexName(fieldId);
1569
+
1570
+ await sql`
1571
+ CREATE INDEX IF NOT EXISTS ${sql.ref(indexName)}
1572
+ ON ${sql.ref(tableName)} (
1573
+ (${sql.ref(columnName)} IS NOT NULL),
1574
+ ${sql.ref(columnName)},
1575
+ id
1576
+ )
1577
+ WHERE deleted_at IS NULL
1578
+ `.execute(conn);
1579
+
1580
+ await sql`
1581
+ CREATE INDEX IF NOT EXISTS ${sql.ref(localeIndexName)}
1582
+ ON ${sql.ref(tableName)} (
1583
+ locale,
1584
+ (${sql.ref(columnName)} IS NOT NULL),
1585
+ ${sql.ref(columnName)},
1586
+ id
1587
+ )
1588
+ WHERE deleted_at IS NULL
1589
+ `.execute(conn);
1590
+ }
1591
+
1592
+ private async dropFieldIndex(fieldId: string, db?: Kysely<Database>): Promise<void> {
1593
+ const conn = db ?? this.db;
1594
+ await sql`DROP INDEX IF EXISTS ${sql.ref(this.getFieldIndexName(fieldId))}`.execute(conn);
1595
+ await sql`DROP INDEX IF EXISTS ${sql.ref(this.getLocaleFieldIndexName(fieldId))}`.execute(conn);
1596
+ }
1597
+
1118
1598
  /**
1119
1599
  * Add a column to a content table
1120
1600
  */
@@ -1314,10 +1794,16 @@ export class SchemaRegistry {
1314
1794
  labelSingular: row.label_singular ?? undefined,
1315
1795
  description: row.description ?? undefined,
1316
1796
  icon: row.icon ?? undefined,
1797
+ admin: parseCollectionAdmin(row.admin_config),
1317
1798
  supports: parseSupports(row.supports),
1318
1799
  source: row.source && isCollectionSource(row.source) ? row.source : undefined,
1319
1800
  hasSeo: row.has_seo === 1,
1801
+ // Raw value; undefined when unset. The admin list resolves the
1802
+ // default (title fallback chain / updatedAt) at the point of use.
1803
+ titleField: row.title_field ?? undefined,
1804
+ dateField: row.date_field ?? undefined,
1320
1805
  urlPattern: row.url_pattern ?? undefined,
1806
+ routable: row.routable !== 0,
1321
1807
  hidden: row.hidden === 1,
1322
1808
  sortOrder: row.sort_order ?? undefined,
1323
1809
  commentsEnabled: row.comments_enabled === 1,
@@ -1351,6 +1837,7 @@ export class SchemaRegistry {
1351
1837
  options: row.options ? JSON.parse(row.options) : undefined,
1352
1838
  sortOrder: row.sort_order,
1353
1839
  searchable: row.searchable === 1,
1840
+ indexed: row.indexed === 1,
1354
1841
  translatable: row.translatable !== 0,
1355
1842
  createdAt: row.created_at,
1356
1843
  };
@@ -1430,6 +1917,9 @@ export class SchemaRegistry {
1430
1917
  ): Promise<Collection> {
1431
1918
  // Verify table exists
1432
1919
  const tableName = this.getTableName(slug);
1920
+ if (await isMediaUsageCollectionSlugDeleting(this.db, slug)) {
1921
+ throw new SchemaError(`Collection "${slug}" is already registered`, "COLLECTION_EXISTS");
1922
+ }
1433
1923
  const exists = await tableExists(this.db, tableName);
1434
1924
 
1435
1925
  if (!exists) {
@@ -1438,32 +1928,64 @@ export class SchemaRegistry {
1438
1928
 
1439
1929
  // Check if already registered
1440
1930
  const existing = await this.getCollection(slug);
1441
- if (existing) {
1931
+ if (await isMediaUsageCollectionSlugDeleting(this.db, slug)) {
1932
+ throw new SchemaError(`Collection "${slug}" is already registered`, "COLLECTION_EXISTS");
1933
+ }
1934
+ if (
1935
+ existing &&
1936
+ !(await canResumeMediaUsageCollectionCapture(this.db, {
1937
+ collectionId: existing.id,
1938
+ collectionSlug: existing.slug,
1939
+ }))
1940
+ ) {
1442
1941
  throw new SchemaError(`Collection "${slug}" is already registered`, "COLLECTION_EXISTS");
1443
1942
  }
1444
1943
 
1445
1944
  // Create collection entry
1446
- const id = ulid();
1945
+ const proposedId = existing?.id ?? ulid();
1447
1946
  const label = options?.label || this.slugToLabel(slug);
1448
1947
 
1449
1948
  let collectionRegistered = false;
1450
1949
  try {
1451
- await this.db
1452
- .insertInto("_emdash_collections")
1453
- .values({
1454
- id,
1455
- slug,
1456
- label,
1457
- label_singular: options?.labelSingular ?? null,
1458
- description: options?.description ?? null,
1459
- icon: null,
1460
- supports: JSON.stringify([]),
1461
- source: "discovered",
1462
- has_seo: 0,
1463
- url_pattern: null,
1464
- })
1465
- .execute();
1466
- collectionRegistered = true;
1950
+ const capture = await prepareMediaUsageCollectionCapture(this.db, {
1951
+ collectionId: proposedId,
1952
+ collectionSlug: slug,
1953
+ registeredCollectionId: existing?.id,
1954
+ });
1955
+ if (capture.captureRequired) {
1956
+ await installPreparedMediaUsageCollectionCapture(this.db, {
1957
+ collectionId: capture.collectionId,
1958
+ collectionSlug: slug,
1959
+ });
1960
+ await markMediaUsageCollectionCaptureReady(this.db, {
1961
+ collectionId: capture.collectionId,
1962
+ collectionSlug: slug,
1963
+ });
1964
+ }
1965
+ if (!capture.registrationExists) {
1966
+ await this.db
1967
+ .insertInto("_emdash_collections")
1968
+ .values({
1969
+ id: capture.collectionId,
1970
+ slug,
1971
+ label,
1972
+ label_singular: options?.labelSingular ?? null,
1973
+ description: options?.description ?? null,
1974
+ icon: null,
1975
+ supports: JSON.stringify([]),
1976
+ source: "discovered",
1977
+ has_seo: 0,
1978
+ url_pattern: null,
1979
+ })
1980
+ .execute();
1981
+ collectionRegistered = true;
1982
+ }
1983
+ if (capture.captureRequired) {
1984
+ await finalizeMediaUsageCollectionCapture(this.db, {
1985
+ collectionId: capture.collectionId,
1986
+ collectionSlug: slug,
1987
+ });
1988
+ }
1467
1989
 
1468
1990
  const collection = await this.getCollection(slug);
1469
1991
  if (!collection) {