emdash 0.32.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 (946) hide show
  1. package/dist/{adapters-C0ONzkUP.d.mts → adapters-34EN2eXO.d.mts} +34 -2
  2. package/dist/adapters-34EN2eXO.d.mts.map +1 -0
  3. package/dist/after-CBU2vkoO.mjs +96 -0
  4. package/dist/after-CBU2vkoO.mjs.map +1 -0
  5. package/dist/{allowed-origins-CCEi9bPI.mjs → allowed-origins-DW7sYyqg.mjs} +2 -2
  6. package/dist/{allowed-origins-CCEi9bPI.mjs.map → allowed-origins-DW7sYyqg.mjs.map} +1 -1
  7. package/dist/api/route-utils.d.mts +3 -4
  8. package/dist/api/route-utils.d.mts.map +1 -1
  9. package/dist/api/route-utils.mjs +26 -25
  10. package/dist/api/route-utils.mjs.map +1 -1
  11. package/dist/api/schemas/index.d.mts +2 -2
  12. package/dist/api/schemas/index.mjs +6 -5
  13. package/dist/{api-DgPbMlng.mjs → api-DdAsirBe.mjs} +321 -428
  14. package/dist/api-DdAsirBe.mjs.map +1 -0
  15. package/dist/{api-tokens-p-iMzvXR.mjs → api-tokens-CFXu8F_2.mjs} +2 -2
  16. package/dist/{api-tokens-p-iMzvXR.mjs.map → api-tokens-CFXu8F_2.mjs.map} +1 -1
  17. package/dist/{apply-1_6ra7NP.mjs → apply-D5zfeD09.mjs} +139 -102
  18. package/dist/apply-D5zfeD09.mjs.map +1 -0
  19. package/dist/astro/index.d.mts +12 -12
  20. package/dist/astro/index.d.mts.map +1 -1
  21. package/dist/astro/index.mjs +103 -35
  22. package/dist/astro/index.mjs.map +1 -1
  23. package/dist/astro/middleware/auth.d.mts +10 -10
  24. package/dist/astro/middleware/auth.mjs +9 -9
  25. package/dist/astro/middleware/media-usage-write-fence.d.mts +7 -0
  26. package/dist/astro/middleware/media-usage-write-fence.d.mts.map +1 -0
  27. package/dist/astro/middleware/media-usage-write-fence.mjs +35 -0
  28. package/dist/astro/middleware/media-usage-write-fence.mjs.map +1 -0
  29. package/dist/astro/middleware/redirect.d.mts.map +1 -1
  30. package/dist/astro/middleware/redirect.mjs +13 -10
  31. package/dist/astro/middleware/redirect.mjs.map +1 -1
  32. package/dist/astro/middleware/request-context.mjs +5 -5
  33. package/dist/astro/middleware/setup.mjs +1 -1
  34. package/dist/astro/middleware.d.mts +12 -11
  35. package/dist/astro/middleware.d.mts.map +1 -1
  36. package/dist/astro/middleware.mjs +1915 -223
  37. package/dist/astro/middleware.mjs.map +1 -1
  38. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +9 -8
  39. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs.map +1 -1
  40. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +9 -8
  41. package/dist/astro/routes/api/admin/allowed-domains/index.mjs.map +1 -1
  42. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +5 -5
  43. package/dist/astro/routes/api/admin/api-tokens/index.mjs +6 -6
  44. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +6 -6
  45. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +12 -11
  46. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs.map +1 -1
  47. package/dist/astro/routes/api/admin/byline-fields/index.mjs +12 -11
  48. package/dist/astro/routes/api/admin/byline-fields/index.mjs.map +1 -1
  49. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +12 -11
  50. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs.map +1 -1
  51. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +20 -20
  52. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +20 -20
  53. package/dist/astro/routes/api/admin/bylines/index.mjs +20 -20
  54. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +18 -17
  55. package/dist/astro/routes/api/admin/comments/_id_/status.mjs.map +1 -1
  56. package/dist/astro/routes/api/admin/comments/_id_.mjs +8 -8
  57. package/dist/astro/routes/api/admin/comments/bulk.mjs +14 -13
  58. package/dist/astro/routes/api/admin/comments/bulk.mjs.map +1 -1
  59. package/dist/astro/routes/api/admin/comments/counts.mjs +8 -8
  60. package/dist/astro/routes/api/admin/comments/index.mjs +14 -13
  61. package/dist/astro/routes/api/admin/comments/index.mjs.map +1 -1
  62. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +6 -6
  63. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +5 -5
  64. package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.d.mts +8 -0
  65. package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.d.mts.map +1 -0
  66. package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs +36 -0
  67. package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs.map +1 -0
  68. package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.d.mts +8 -0
  69. package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.d.mts.map +1 -0
  70. package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs +36 -0
  71. package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs.map +1 -0
  72. package/dist/astro/routes/api/admin/media-usage/repair.mjs +15 -14
  73. package/dist/astro/routes/api/admin/media-usage/repair.mjs.map +1 -1
  74. package/dist/astro/routes/api/admin/media-usage/work/index.d.mts +8 -0
  75. package/dist/astro/routes/api/admin/media-usage/work/index.d.mts.map +1 -0
  76. package/dist/astro/routes/api/admin/media-usage/work/index.mjs +37 -0
  77. package/dist/astro/routes/api/admin/media-usage/work/index.mjs.map +1 -0
  78. package/dist/astro/routes/api/admin/media-usage/work/retry.d.mts +8 -0
  79. package/dist/astro/routes/api/admin/media-usage/work/retry.d.mts.map +1 -0
  80. package/dist/astro/routes/api/admin/media-usage/work/retry.mjs +37 -0
  81. package/dist/astro/routes/api/admin/media-usage/work/retry.mjs.map +1 -0
  82. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +5 -5
  83. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +5 -5
  84. package/dist/astro/routes/api/admin/plugins/_id_/disable.d.mts.map +1 -1
  85. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +56 -47
  86. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
  87. package/dist/astro/routes/api/admin/plugins/_id_/enable.d.mts.map +1 -1
  88. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +56 -47
  89. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
  90. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +52 -46
  91. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
  92. package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +6 -6
  93. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +52 -46
  94. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs.map +1 -1
  95. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.d.mts.map +1 -1
  96. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +55 -46
  97. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
  98. package/dist/astro/routes/api/admin/plugins/_id_/update.d.mts.map +1 -1
  99. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +55 -46
  100. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
  101. package/dist/astro/routes/api/admin/plugins/index.mjs +52 -46
  102. package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
  103. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +4 -4
  104. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +52 -46
  105. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
  106. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.d.mts.map +1 -1
  107. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +55 -46
  108. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
  109. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +52 -46
  110. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
  111. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.d.mts.map +1 -1
  112. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +55 -46
  113. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
  114. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.d.mts.map +1 -1
  115. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +56 -47
  116. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
  117. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +52 -46
  118. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
  119. package/dist/astro/routes/api/admin/plugins/registry/install.d.mts.map +1 -1
  120. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +56 -47
  121. package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
  122. package/dist/astro/routes/api/admin/plugins/updates.mjs +52 -46
  123. package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
  124. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +52 -46
  125. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
  126. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +4 -4
  127. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +52 -46
  128. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
  129. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +4 -4
  130. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +3 -3
  131. package/dist/astro/routes/api/admin/users/_id_/index.mjs +10 -9
  132. package/dist/astro/routes/api/admin/users/_id_/index.mjs.map +1 -1
  133. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +6 -6
  134. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs.map +1 -1
  135. package/dist/astro/routes/api/admin/users/index.mjs +9 -8
  136. package/dist/astro/routes/api/admin/users/index.mjs.map +1 -1
  137. package/dist/astro/routes/api/auth/dev-bypass.mjs +8 -8
  138. package/dist/astro/routes/api/auth/invite/accept.mjs +3 -3
  139. package/dist/astro/routes/api/auth/invite/complete.mjs +14 -13
  140. package/dist/astro/routes/api/auth/invite/complete.mjs.map +1 -1
  141. package/dist/astro/routes/api/auth/invite/index.mjs +12 -11
  142. package/dist/astro/routes/api/auth/invite/index.mjs.map +1 -1
  143. package/dist/astro/routes/api/auth/invite/register-options.mjs +13 -12
  144. package/dist/astro/routes/api/auth/invite/register-options.mjs.map +1 -1
  145. package/dist/astro/routes/api/auth/logout.mjs +4 -4
  146. package/dist/astro/routes/api/auth/magic-link/send.mjs +14 -13
  147. package/dist/astro/routes/api/auth/magic-link/send.mjs.map +1 -1
  148. package/dist/astro/routes/api/auth/magic-link/verify.mjs +4 -4
  149. package/dist/astro/routes/api/auth/me.mjs +10 -9
  150. package/dist/astro/routes/api/auth/me.mjs.map +1 -1
  151. package/dist/astro/routes/api/auth/mode.mjs +4 -4
  152. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +4 -4
  153. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
  154. package/dist/astro/routes/api/auth/passkey/_id_.mjs +9 -8
  155. package/dist/astro/routes/api/auth/passkey/_id_.mjs.map +1 -1
  156. package/dist/astro/routes/api/auth/passkey/index.mjs +3 -3
  157. package/dist/astro/routes/api/auth/passkey/options.mjs +15 -14
  158. package/dist/astro/routes/api/auth/passkey/options.mjs.map +1 -1
  159. package/dist/astro/routes/api/auth/passkey/register/options.mjs +13 -12
  160. package/dist/astro/routes/api/auth/passkey/register/options.mjs.map +1 -1
  161. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +14 -13
  162. package/dist/astro/routes/api/auth/passkey/register/verify.mjs.map +1 -1
  163. package/dist/astro/routes/api/auth/passkey/verify.mjs +14 -13
  164. package/dist/astro/routes/api/auth/passkey/verify.mjs.map +1 -1
  165. package/dist/astro/routes/api/auth/signup/complete.mjs +14 -13
  166. package/dist/astro/routes/api/auth/signup/complete.mjs.map +1 -1
  167. package/dist/astro/routes/api/auth/signup/request.mjs +14 -13
  168. package/dist/astro/routes/api/auth/signup/request.mjs.map +1 -1
  169. package/dist/astro/routes/api/auth/signup/verify.mjs +3 -3
  170. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +20 -19
  171. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs.map +1 -1
  172. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +17 -16
  173. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs.map +1 -1
  174. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +4 -4
  175. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +5 -5
  176. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +5 -5
  177. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +4 -4
  178. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +15 -15
  179. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +11 -10
  180. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
  181. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +21 -20
  182. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs.map +1 -1
  183. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs +21 -20
  184. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs.map +1 -1
  185. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +5 -5
  186. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +4 -4
  187. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +11 -10
  188. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs.map +1 -1
  189. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.d.mts.map +1 -1
  190. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +47 -34
  191. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs.map +1 -1
  192. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +4 -4
  193. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +5 -5
  194. package/dist/astro/routes/api/content/_collection_/_id_.mjs +11 -10
  195. package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
  196. package/dist/astro/routes/api/content/_collection_/authors.mjs +4 -4
  197. package/dist/astro/routes/api/content/_collection_/index.mjs +11 -10
  198. package/dist/astro/routes/api/content/_collection_/index.mjs.map +1 -1
  199. package/dist/astro/routes/api/content/_collection_/trash.mjs +10 -9
  200. package/dist/astro/routes/api/content/_collection_/trash.mjs.map +1 -1
  201. package/dist/astro/routes/api/dashboard.mjs +12 -10
  202. package/dist/astro/routes/api/dashboard.mjs.map +1 -1
  203. package/dist/astro/routes/api/dev/emails.mjs +4 -4
  204. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  205. package/dist/astro/routes/api/import/probe.mjs +22 -20
  206. package/dist/astro/routes/api/import/probe.mjs.map +1 -1
  207. package/dist/astro/routes/api/import/wordpress/analyze.mjs +5 -5
  208. package/dist/astro/routes/api/import/wordpress/execute.d.mts +11 -11
  209. package/dist/astro/routes/api/import/wordpress/execute.mjs +18 -18
  210. package/dist/astro/routes/api/import/wordpress/execute.mjs.map +1 -1
  211. package/dist/astro/routes/api/import/wordpress/media.d.mts +10 -10
  212. package/dist/astro/routes/api/import/wordpress/media.mjs +14 -12
  213. package/dist/astro/routes/api/import/wordpress/media.mjs.map +1 -1
  214. package/dist/astro/routes/api/import/wordpress/prepare.mjs +13 -12
  215. package/dist/astro/routes/api/import/wordpress/prepare.mjs.map +1 -1
  216. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +11 -11
  217. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +15 -15
  218. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  219. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +22 -20
  220. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs.map +1 -1
  221. package/dist/astro/routes/api/import/wordpress-plugin/callback.mjs +1 -1
  222. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +11 -11
  223. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +32 -31
  224. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
  225. package/dist/astro/routes/api/manifest.mjs +6 -6
  226. package/dist/astro/routes/api/mcp.d.mts.map +1 -1
  227. package/dist/astro/routes/api/mcp.mjs +174 -72
  228. package/dist/astro/routes/api/mcp.mjs.map +1 -1
  229. package/dist/astro/routes/api/media/_id_/confirm.mjs +14 -12
  230. package/dist/astro/routes/api/media/_id_/confirm.mjs.map +1 -1
  231. package/dist/astro/routes/api/media/_id_/upload.mjs +7 -7
  232. package/dist/astro/routes/api/media/_id_/usage.mjs +17 -16
  233. package/dist/astro/routes/api/media/_id_/usage.mjs.map +1 -1
  234. package/dist/astro/routes/api/media/_id_.mjs +16 -15
  235. package/dist/astro/routes/api/media/_id_.mjs.map +1 -1
  236. package/dist/astro/routes/api/media/file/_...key_.mjs +3 -3
  237. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +4 -4
  238. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +4 -4
  239. package/dist/astro/routes/api/media/providers/index.mjs +4 -4
  240. package/dist/astro/routes/api/media/upload-url.mjs +11 -10
  241. package/dist/astro/routes/api/media/upload-url.mjs.map +1 -1
  242. package/dist/astro/routes/api/media.mjs +19 -16
  243. package/dist/astro/routes/api/media.mjs.map +1 -1
  244. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +13 -13
  245. package/dist/astro/routes/api/menus/_name_/items.mjs +13 -13
  246. package/dist/astro/routes/api/menus/_name_/reorder.mjs +13 -13
  247. package/dist/astro/routes/api/menus/_name_/translations.mjs +13 -13
  248. package/dist/astro/routes/api/menus/_name_.mjs +13 -13
  249. package/dist/astro/routes/api/menus/index.mjs +13 -13
  250. package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
  251. package/dist/astro/routes/api/oauth/device/authorize.mjs +7 -7
  252. package/dist/astro/routes/api/oauth/device/code.mjs +10 -10
  253. package/dist/astro/routes/api/oauth/device/token.mjs +9 -9
  254. package/dist/astro/routes/api/oauth/register.mjs +4 -4
  255. package/dist/astro/routes/api/oauth/token/refresh.mjs +7 -7
  256. package/dist/astro/routes/api/oauth/token/revoke.mjs +7 -7
  257. package/dist/astro/routes/api/oauth/token.mjs +7 -7
  258. package/dist/astro/routes/api/openapi.json.mjs +183 -6
  259. package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
  260. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.d.mts.map +1 -1
  261. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +8 -7
  262. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs.map +1 -1
  263. package/dist/astro/routes/api/redirects/404s/index.mjs +14 -13
  264. package/dist/astro/routes/api/redirects/404s/index.mjs.map +1 -1
  265. package/dist/astro/routes/api/redirects/404s/summary.mjs +14 -13
  266. package/dist/astro/routes/api/redirects/404s/summary.mjs.map +1 -1
  267. package/dist/astro/routes/api/redirects/_id_.mjs +16 -14
  268. package/dist/astro/routes/api/redirects/_id_.mjs.map +1 -1
  269. package/dist/astro/routes/api/redirects/index.mjs +16 -14
  270. package/dist/astro/routes/api/redirects/index.mjs.map +1 -1
  271. package/dist/astro/routes/api/relations/_id_/index.mjs +21 -20
  272. package/dist/astro/routes/api/relations/_id_/index.mjs.map +1 -1
  273. package/dist/astro/routes/api/relations/_id_/translations.mjs +16 -15
  274. package/dist/astro/routes/api/relations/_id_/translations.mjs.map +1 -1
  275. package/dist/astro/routes/api/relations/index.mjs +21 -20
  276. package/dist/astro/routes/api/relations/index.mjs.map +1 -1
  277. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +4 -4
  278. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +5 -5
  279. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +52 -46
  280. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
  281. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +52 -46
  282. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs.map +1 -1
  283. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +52 -46
  284. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
  285. package/dist/astro/routes/api/schema/collections/_slug_/index.d.mts.map +1 -1
  286. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +52 -46
  287. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
  288. package/dist/astro/routes/api/schema/collections/index.mjs +52 -46
  289. package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
  290. package/dist/astro/routes/api/schema/collections/reorder.d.mts +8 -0
  291. package/dist/astro/routes/api/schema/collections/reorder.d.mts.map +1 -0
  292. package/dist/astro/routes/api/schema/collections/reorder.mjs +71 -0
  293. package/dist/astro/routes/api/schema/collections/reorder.mjs.map +1 -0
  294. package/dist/astro/routes/api/schema/index.mjs +14 -11
  295. package/dist/astro/routes/api/schema/index.mjs.map +1 -1
  296. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +52 -46
  297. package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
  298. package/dist/astro/routes/api/schema/orphans/index.mjs +52 -46
  299. package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
  300. package/dist/astro/routes/api/search/enable.d.mts +1 -0
  301. package/dist/astro/routes/api/search/enable.d.mts.map +1 -1
  302. package/dist/astro/routes/api/search/enable.mjs +19 -15
  303. package/dist/astro/routes/api/search/enable.mjs.map +1 -1
  304. package/dist/astro/routes/api/search/index.mjs +13 -13
  305. package/dist/astro/routes/api/search/rebuild.mjs +15 -15
  306. package/dist/astro/routes/api/search/rebuild.mjs.map +1 -1
  307. package/dist/astro/routes/api/search/stats.mjs +9 -9
  308. package/dist/astro/routes/api/search/suggest.mjs +13 -13
  309. package/dist/astro/routes/api/sections/_slug_.mjs +13 -12
  310. package/dist/astro/routes/api/sections/_slug_.mjs.map +1 -1
  311. package/dist/astro/routes/api/sections/index.mjs +13 -12
  312. package/dist/astro/routes/api/sections/index.mjs.map +1 -1
  313. package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +8 -8
  314. package/dist/astro/routes/api/settings/backups/archives/index.mjs +7 -7
  315. package/dist/astro/routes/api/settings/backups/export.mjs +8 -8
  316. package/dist/astro/routes/api/settings/backups/index.mjs +9 -9
  317. package/dist/astro/routes/api/settings/email.mjs +6 -6
  318. package/dist/astro/routes/api/settings.mjs +18 -17
  319. package/dist/astro/routes/api/settings.mjs.map +1 -1
  320. package/dist/astro/routes/api/setup/admin-verify.mjs +15 -14
  321. package/dist/astro/routes/api/setup/admin-verify.mjs.map +1 -1
  322. package/dist/astro/routes/api/setup/admin.mjs +14 -13
  323. package/dist/astro/routes/api/setup/admin.mjs.map +1 -1
  324. package/dist/astro/routes/api/setup/dev-bypass.mjs +34 -32
  325. package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
  326. package/dist/astro/routes/api/setup/dev-reset.mjs +4 -4
  327. package/dist/astro/routes/api/setup/index.mjs +36 -34
  328. package/dist/astro/routes/api/setup/index.mjs.map +1 -1
  329. package/dist/astro/routes/api/setup/status.mjs +5 -5
  330. package/dist/astro/routes/api/snapshot.mjs +10 -10
  331. package/dist/astro/routes/api/taxonomies/_name_/reorder.d.mts +8 -0
  332. package/dist/astro/routes/api/taxonomies/_name_/reorder.d.mts.map +1 -0
  333. package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs +44 -0
  334. package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs.map +1 -0
  335. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +19 -18
  336. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs.map +1 -1
  337. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +19 -18
  338. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs.map +1 -1
  339. package/dist/astro/routes/api/taxonomies/_name_/terms/index.d.mts.map +1 -1
  340. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +21 -19
  341. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs.map +1 -1
  342. package/dist/astro/routes/api/taxonomies/_name_/translations.d.mts +8 -0
  343. package/dist/astro/routes/api/taxonomies/_name_/translations.d.mts.map +1 -0
  344. package/dist/astro/routes/api/taxonomies/_name_/translations.mjs +46 -0
  345. package/dist/astro/routes/api/taxonomies/_name_/translations.mjs.map +1 -0
  346. package/dist/astro/routes/api/taxonomies/_name_.d.mts +19 -0
  347. package/dist/astro/routes/api/taxonomies/_name_.d.mts.map +1 -0
  348. package/dist/astro/routes/api/taxonomies/_name_.mjs +88 -0
  349. package/dist/astro/routes/api/taxonomies/_name_.mjs.map +1 -0
  350. package/dist/astro/routes/api/taxonomies/index.mjs +19 -18
  351. package/dist/astro/routes/api/taxonomies/index.mjs.map +1 -1
  352. package/dist/astro/routes/api/themes/preview.mjs +8 -8
  353. package/dist/astro/routes/api/typegen.mjs +6 -6
  354. package/dist/astro/routes/api/well-known/auth.mjs +2 -2
  355. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
  356. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
  357. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +10 -9
  358. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs.map +1 -1
  359. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +13 -12
  360. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
  361. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +13 -12
  362. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
  363. package/dist/astro/routes/api/widget-areas/_name_.mjs +8 -7
  364. package/dist/astro/routes/api/widget-areas/_name_.mjs.map +1 -1
  365. package/dist/astro/routes/api/widget-areas/index.mjs +13 -12
  366. package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
  367. package/dist/astro/routes/api/widget-components.mjs +4 -4
  368. package/dist/astro/routes/robots.txt.mjs +11 -10
  369. package/dist/astro/routes/robots.txt.mjs.map +1 -1
  370. package/dist/astro/routes/sitemap-_collection_.xml.mjs +14 -14
  371. package/dist/astro/routes/sitemap.xml.mjs +12 -11
  372. package/dist/astro/routes/sitemap.xml.mjs.map +1 -1
  373. package/dist/astro/types.d.mts +41 -17
  374. package/dist/astro/types.d.mts.map +1 -1
  375. package/dist/auth/providers/github.d.mts +1 -1
  376. package/dist/auth/providers/google.d.mts +1 -1
  377. package/dist/{authorize-R5iYYrpa.mjs → authorize-1iFJzPaZ.mjs} +2 -2
  378. package/dist/{authorize-R5iYYrpa.mjs.map → authorize-1iFJzPaZ.mjs.map} +1 -1
  379. package/dist/{backup-LpQ1V_sR.mjs → backup-LHjIAVNP.mjs} +5 -5
  380. package/dist/{backup-LpQ1V_sR.mjs.map → backup-LHjIAVNP.mjs.map} +1 -1
  381. package/dist/{base64-B-PsqheR.mjs → base64-CvLhUKQv.mjs} +1 -1
  382. package/dist/{base64-B-PsqheR.mjs.map → base64-CvLhUKQv.mjs.map} +1 -1
  383. package/dist/bundle-CBEXrBnb.mjs +663 -0
  384. package/dist/bundle-CBEXrBnb.mjs.map +1 -0
  385. package/dist/{byline-fields-T9ZLZMxJ.mjs → byline-fields-C5S8wygO.mjs} +2 -2
  386. package/dist/{byline-fields-T9ZLZMxJ.mjs.map → byline-fields-C5S8wygO.mjs.map} +1 -1
  387. package/dist/{byline-C5TAqs8N.mjs → byline-pEcoDps8.mjs} +9 -9
  388. package/dist/{byline-C5TAqs8N.mjs.map → byline-pEcoDps8.mjs.map} +1 -1
  389. package/dist/{byline-registry-BCuOp4UF.mjs → byline-registry-DDun8sxh.mjs} +4 -4
  390. package/dist/{byline-registry-BCuOp4UF.mjs.map → byline-registry-DDun8sxh.mjs.map} +1 -1
  391. package/dist/{bylines-B8-WGdja.mjs → bylines-BWemdX2k.mjs} +8 -8
  392. package/dist/{bylines-B8-WGdja.mjs.map → bylines-BWemdX2k.mjs.map} +1 -1
  393. package/dist/{bylines-DGuqBLjV.mjs → bylines-DJfSfXCL.mjs} +5 -5
  394. package/dist/{bylines-DGuqBLjV.mjs.map → bylines-DJfSfXCL.mjs.map} +1 -1
  395. package/dist/cache-CBJaDy_E.mjs +108 -0
  396. package/dist/cache-CBJaDy_E.mjs.map +1 -0
  397. package/dist/{challenge-store-BFzgFRog.mjs → challenge-store-CZyFVaad.mjs} +1 -1
  398. package/dist/{challenge-store-BFzgFRog.mjs.map → challenge-store-CZyFVaad.mjs.map} +1 -1
  399. package/dist/{chunks-D5dlPeRb.mjs → chunks-DfPax1I0.mjs} +2 -2
  400. package/dist/{chunks-D5dlPeRb.mjs.map → chunks-DfPax1I0.mjs.map} +1 -1
  401. package/dist/cli/index.mjs +193 -707
  402. package/dist/cli/index.mjs.map +1 -1
  403. package/dist/client/cf-access.d.mts +1 -1
  404. package/dist/client/index.d.mts +74 -4
  405. package/dist/client/index.d.mts.map +1 -1
  406. package/dist/client/index.mjs +25 -2
  407. package/dist/client/index.mjs.map +1 -1
  408. package/dist/collection-deletion-C8BugSGz.mjs +779 -0
  409. package/dist/collection-deletion-C8BugSGz.mjs.map +1 -0
  410. package/dist/{comment-DnTxxVHv.mjs → comment-Dxlep6cE.mjs} +3 -3
  411. package/dist/{comment-DnTxxVHv.mjs.map → comment-Dxlep6cE.mjs.map} +1 -1
  412. package/dist/{comment-reaction-BUlUz2bh.mjs → comment-reaction-D6qfHKBc.mjs} +2 -2
  413. package/dist/{comment-reaction-BUlUz2bh.mjs.map → comment-reaction-D6qfHKBc.mjs.map} +1 -1
  414. package/dist/{comments-QPKgK3nk.mjs → comments-CTOuhTSX.mjs} +3 -3
  415. package/dist/{comments-QPKgK3nk.mjs.map → comments-CTOuhTSX.mjs.map} +1 -1
  416. package/dist/{components-DZkL4Fx0.mjs → components-CQy3lh9x.mjs} +1 -1
  417. package/dist/{components-DZkL4Fx0.mjs.map → components-CQy3lh9x.mjs.map} +1 -1
  418. package/dist/{config-DyF3OKg_.mjs → config-C3EBstHi.mjs} +18 -2
  419. package/dist/config-C3EBstHi.mjs.map +1 -0
  420. package/dist/{content-CpfKV9QE.mjs → content-BNfwh7xX.mjs} +667 -183
  421. package/dist/content-BNfwh7xX.mjs.map +1 -0
  422. package/dist/content-list-query-DhQ84eVV.d.mts +26 -0
  423. package/dist/content-list-query-DhQ84eVV.d.mts.map +1 -0
  424. package/dist/content-refresh-DNERP4-f.mjs +2560 -0
  425. package/dist/content-refresh-DNERP4-f.mjs.map +1 -0
  426. package/dist/{context-kO4YZB03.mjs → context-DRgOvSnW.mjs} +28 -20
  427. package/dist/context-DRgOvSnW.mjs.map +1 -0
  428. package/dist/{cron-BlKIMD_e.mjs → cron-Y9aLtgrA.mjs} +1 -1
  429. package/dist/{cron-BlKIMD_e.mjs.map → cron-Y9aLtgrA.mjs.map} +1 -1
  430. package/dist/{dashboard-DoBwPnON.mjs → dashboard-CB8J89rJ.mjs} +45 -11
  431. package/dist/dashboard-CB8J89rJ.mjs.map +1 -0
  432. package/dist/db/index.d.mts +4 -4
  433. package/dist/db/index.mjs +3 -3
  434. package/dist/db/index.mjs.map +1 -1
  435. package/dist/db/libsql.d.mts +1 -1
  436. package/dist/db/postgres.d.mts +1 -1
  437. package/dist/db/sqlite.d.mts +1 -1
  438. package/dist/{db-errors-CcWLaRiR.mjs → db-errors-Cl-sKYTm.mjs} +1 -1
  439. package/dist/{db-errors-CcWLaRiR.mjs.map → db-errors-Cl-sKYTm.mjs.map} +1 -1
  440. package/dist/{default-C4v9KP56.mjs → default-DdDl3fxv.mjs} +1 -1
  441. package/dist/{default-C4v9KP56.mjs.map → default-DdDl3fxv.mjs.map} +1 -1
  442. package/dist/deferred-tasks-BMsa9F64.d.mts +9 -0
  443. package/dist/deferred-tasks-BMsa9F64.d.mts.map +1 -0
  444. package/dist/{device-flow-DhUsyJU6.mjs → device-flow-CaioYGK3.mjs} +5 -5
  445. package/dist/{device-flow-DhUsyJU6.mjs.map → device-flow-CaioYGK3.mjs.map} +1 -1
  446. package/dist/{dialect-helpers-DFwyWjTM.mjs → dialect-helpers-BSbfaO3F.mjs} +24 -16
  447. package/dist/dialect-helpers-BSbfaO3F.mjs.map +1 -0
  448. package/dist/dist-D_6CKR8z.mjs +810 -0
  449. package/dist/dist-D_6CKR8z.mjs.map +1 -0
  450. package/dist/{email-console-C-9Ng8DM.mjs → email-console-BHNjIwiB.mjs} +1 -1
  451. package/dist/{email-console-C-9Ng8DM.mjs.map → email-console-BHNjIwiB.mjs.map} +1 -1
  452. package/dist/{enrich-CFJJgxs_.mjs → enrich-_ySMEvEE.mjs} +2 -2
  453. package/dist/{enrich-CFJJgxs_.mjs.map → enrich-_ySMEvEE.mjs.map} +1 -1
  454. package/dist/{error-DmmN74gW.mjs → error-DupyJZE2.mjs} +3 -3
  455. package/dist/{error-DmmN74gW.mjs.map → error-DupyJZE2.mjs.map} +1 -1
  456. package/dist/{errors-DtEXIQQV.mjs → errors-CLUPm7aD.mjs} +15 -2
  457. package/dist/errors-CLUPm7aD.mjs.map +1 -0
  458. package/dist/{escape-CdW31WsG.mjs → escape-C_aLKP3H.mjs} +1 -1
  459. package/dist/{escape-CdW31WsG.mjs.map → escape-C_aLKP3H.mjs.map} +1 -1
  460. package/dist/{field-defs-cache-QMVnzTH6.mjs → field-defs-cache-hg6QkjV9.mjs} +5 -5
  461. package/dist/{field-defs-cache-QMVnzTH6.mjs.map → field-defs-cache-hg6QkjV9.mjs.map} +1 -1
  462. package/dist/{fts-manager-vncsZIJm.mjs → fts-manager-CZ-5dVwJ.mjs} +111 -61
  463. package/dist/fts-manager-CZ-5dVwJ.mjs.map +1 -0
  464. package/dist/{hash-Cb7U1b5M.mjs → hash-Cg0TXJPV.mjs} +1 -1
  465. package/dist/{hash-Cb7U1b5M.mjs.map → hash-Cg0TXJPV.mjs.map} +1 -1
  466. package/dist/{hreflang-CZbSx6Bg.d.mts → hreflang-D3Uehfv2.d.mts} +2 -2
  467. package/dist/{hreflang-CZbSx6Bg.d.mts.map → hreflang-D3Uehfv2.d.mts.map} +1 -1
  468. package/dist/{hreflang-Bg-zMpkr.mjs → hreflang-DNswoYsi.mjs} +7 -7
  469. package/dist/{hreflang-Bg-zMpkr.mjs.map → hreflang-DNswoYsi.mjs.map} +1 -1
  470. package/dist/{import-PKhLeXvn.mjs → import-B41u8Rot.mjs} +7 -7
  471. package/dist/{import-PKhLeXvn.mjs.map → import-B41u8Rot.mjs.map} +1 -1
  472. package/dist/{index-j9rTh3cp.d.mts → index-C3Y-rsyN.d.mts} +286 -93
  473. package/dist/index-C3Y-rsyN.d.mts.map +1 -0
  474. package/dist/{index-BeNto_Vs.d.mts → index-Cof4-Fab.d.mts} +5 -10
  475. package/dist/index-Cof4-Fab.d.mts.map +1 -0
  476. package/dist/index.d.mts +20 -19
  477. package/dist/index.mjs +79 -71
  478. package/dist/{init-lock-DJkX6Hto.mjs → init-lock-6b309ZrF.mjs} +1 -1
  479. package/dist/{init-lock-DJkX6Hto.mjs.map → init-lock-6b309ZrF.mjs.map} +1 -1
  480. package/dist/{load-BwTdWE8B.mjs → load-DmvJrtxS.mjs} +2 -2
  481. package/dist/{load-BwTdWE8B.mjs.map → load-DmvJrtxS.mjs.map} +1 -1
  482. package/dist/{loader-C1XOLV5b.mjs → loader-DLBqRiGP.mjs} +120 -64
  483. package/dist/loader-DLBqRiGP.mjs.map +1 -0
  484. package/dist/{manifest-schema-bCq54i7F.mjs → manifest-schema-BXbUAHod.mjs} +1 -1
  485. package/dist/{manifest-schema-bCq54i7F.mjs.map → manifest-schema-BXbUAHod.mjs.map} +1 -1
  486. package/dist/media/image-endpoint.mjs +1 -1
  487. package/dist/media/index.d.mts +1 -1
  488. package/dist/media/index.mjs +3 -2
  489. package/dist/media/index.mjs.map +1 -1
  490. package/dist/media/local-runtime.d.mts +12 -12
  491. package/dist/media/local-runtime.mjs +10 -9
  492. package/dist/media/local-runtime.mjs.map +1 -1
  493. package/dist/{media-DWIzoVFL.mjs → media-BWWWb8kg.mjs} +92 -3
  494. package/dist/media-BWWWb8kg.mjs.map +1 -0
  495. package/dist/{media-tQDZEdu7.mjs → media-CzhAn2--.mjs} +2 -2
  496. package/dist/{media-tQDZEdu7.mjs.map → media-CzhAn2--.mjs.map} +1 -1
  497. package/dist/{media-allowlist-B7TPD6aq.mjs → media-allowlist-BD6HhgXA.mjs} +14 -3
  498. package/dist/media-allowlist-BD6HhgXA.mjs.map +1 -0
  499. package/dist/{media-upload-Bc9Nc5Zf.mjs → media-upload-9yas9zGH.mjs} +11 -9
  500. package/dist/{media-upload-Bc9Nc5Zf.mjs.map → media-upload-9yas9zGH.mjs.map} +1 -1
  501. package/dist/{media-url-BCm5vBn6.mjs → media-url-BvoX88FA.mjs} +1 -1
  502. package/dist/{media-url-BCm5vBn6.mjs.map → media-url-BvoX88FA.mjs.map} +1 -1
  503. package/dist/{media-usage-DPJcNaAq.mjs → media-usage-De3qvwjc.mjs} +138 -61
  504. package/dist/media-usage-De3qvwjc.mjs.map +1 -0
  505. package/dist/media-usage-work-B9Bksh_b.mjs +152 -0
  506. package/dist/media-usage-work-B9Bksh_b.mjs.map +1 -0
  507. package/dist/media-usage-work-DrCbqBcl.mjs +424 -0
  508. package/dist/media-usage-work-DrCbqBcl.mjs.map +1 -0
  509. package/dist/media-usage-write-fence-aHEgN1zB.mjs +42 -0
  510. package/dist/media-usage-write-fence-aHEgN1zB.mjs.map +1 -0
  511. package/dist/{menus-C6qhqP4X.mjs → menus-CC-yn1F9.mjs} +554 -121
  512. package/dist/menus-CC-yn1F9.mjs.map +1 -0
  513. package/dist/{menus-BxPLFfIc.mjs → menus-fxvEDYE6.mjs} +5 -5
  514. package/dist/{menus-BxPLFfIc.mjs.map → menus-fxvEDYE6.mjs.map} +1 -1
  515. package/dist/{mode-fiXRMfeA.mjs → mode-Bpe-tNNy.mjs} +1 -1
  516. package/dist/{mode-fiXRMfeA.mjs.map → mode-Bpe-tNNy.mjs.map} +1 -1
  517. package/dist/{normalize-C-SHXmra.mjs → normalize-Bc-s_r-a.mjs} +1 -1
  518. package/dist/{normalize-C-SHXmra.mjs.map → normalize-Bc-s_r-a.mjs.map} +1 -1
  519. package/dist/{oauth-authorization-C9tFinly.mjs → oauth-authorization-BXO1MKej.mjs} +5 -5
  520. package/dist/{oauth-authorization-C9tFinly.mjs.map → oauth-authorization-BXO1MKej.mjs.map} +1 -1
  521. package/dist/{oauth-clients-BwTxE1aE.mjs → oauth-clients-C_DNqtRu.mjs} +1 -1
  522. package/dist/{oauth-clients-BwTxE1aE.mjs.map → oauth-clients-C_DNqtRu.mjs.map} +1 -1
  523. package/dist/{oauth-state-store-DaRUwPWO.mjs → oauth-state-store-DwAxMtk4.mjs} +1 -1
  524. package/dist/{oauth-state-store-DaRUwPWO.mjs.map → oauth-state-store-DwAxMtk4.mjs.map} +1 -1
  525. package/dist/{oauth-user-lookup-CzuzOSlF.mjs → oauth-user-lookup-BxfZizdk.mjs} +1 -1
  526. package/dist/{oauth-user-lookup-CzuzOSlF.mjs.map → oauth-user-lookup-BxfZizdk.mjs.map} +1 -1
  527. package/dist/object-cache/memory.d.mts +1 -1
  528. package/dist/{object-cache-Bok5j2ae.mjs → object-cache-71tPfV4A.mjs} +106 -5
  529. package/dist/object-cache-71tPfV4A.mjs.map +1 -0
  530. package/dist/{options-BlmBHTvX.mjs → options-CBXIHUoT.mjs} +1 -1
  531. package/dist/{options-BlmBHTvX.mjs.map → options-CBXIHUoT.mjs.map} +1 -1
  532. package/dist/{options-aeIs8WFu.d.mts → options-DRe1pIBL.d.mts} +4 -3
  533. package/dist/options-DRe1pIBL.d.mts.map +1 -0
  534. package/dist/page/index.d.mts +2 -2
  535. package/dist/{parse-BL49yb9D.mjs → parse-DFbuc-Fh.mjs} +2 -2
  536. package/dist/{parse-BL49yb9D.mjs.map → parse-DFbuc-Fh.mjs.map} +1 -1
  537. package/dist/{passkey-config-KMhgFjdQ.mjs → passkey-config-_2GOwwK8.mjs} +1 -1
  538. package/dist/{passkey-config-KMhgFjdQ.mjs.map → passkey-config-_2GOwwK8.mjs.map} +1 -1
  539. package/dist/{placeholder-Dxo5Zt58.d.mts → placeholder-CzW7Lshs.d.mts} +1 -1
  540. package/dist/{placeholder-Dxo5Zt58.d.mts.map → placeholder-CzW7Lshs.d.mts.map} +1 -1
  541. package/dist/plugin-types.d.mts +8 -1
  542. package/dist/plugin-types.d.mts.map +1 -1
  543. package/dist/plugin-utils.d.mts +10 -10
  544. package/dist/plugins/adapt-sandbox-entry.d.mts +10 -10
  545. package/dist/plugins/adapt-sandbox-entry.mjs +7 -4
  546. package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -1
  547. package/dist/{portable-text-CNahFfj6.mjs → portable-text-DhKbDAGr.mjs} +1 -1
  548. package/dist/{portable-text-CNahFfj6.mjs.map → portable-text-DhKbDAGr.mjs.map} +1 -1
  549. package/dist/{preview-D4Jnbfx7.mjs → preview-PW-nOnq4.mjs} +2 -2
  550. package/dist/{preview-D4Jnbfx7.mjs.map → preview-PW-nOnq4.mjs.map} +1 -1
  551. package/dist/{public-url-DSGTnJFw.mjs → public-url-CWoFhQT8.mjs} +14 -3
  552. package/dist/public-url-CWoFhQT8.mjs.map +1 -0
  553. package/dist/{query-DCiXI7OZ.mjs → query-BgjfiLUQ.mjs} +38 -77
  554. package/dist/query-BgjfiLUQ.mjs.map +1 -0
  555. package/dist/{query-1xOcEy18.mjs → query-vgZNwIhh.mjs} +4 -4
  556. package/dist/{query-1xOcEy18.mjs.map → query-vgZNwIhh.mjs.map} +1 -1
  557. package/dist/{rate-limit-Vkp9gQ1Y.mjs → rate-limit-L5anykgo.mjs} +2 -2
  558. package/dist/{rate-limit-Vkp9gQ1Y.mjs.map → rate-limit-L5anykgo.mjs.map} +1 -1
  559. package/dist/{redirect-BJ6d1yHh.mjs → redirect-COsS6SWJ.mjs} +3 -3
  560. package/dist/{redirect-BJ6d1yHh.mjs.map → redirect-COsS6SWJ.mjs.map} +1 -1
  561. package/dist/{redirect-CDgfv9vh.mjs → redirect-KBNE3UEs.mjs} +1 -1
  562. package/dist/{redirect-CDgfv9vh.mjs.map → redirect-KBNE3UEs.mjs.map} +1 -1
  563. package/dist/{redirects-Cwkq2H-G.mjs → redirects-CufRQt0Q.mjs} +5 -5
  564. package/dist/{redirects-Cwkq2H-G.mjs.map → redirects-CufRQt0Q.mjs.map} +1 -1
  565. package/dist/{redirects-BOn2DIUs.mjs → redirects-cV9g7V-0.mjs} +149 -16
  566. package/dist/redirects-cV9g7V-0.mjs.map +1 -0
  567. package/dist/registry-C581V4TP.mjs +1275 -0
  568. package/dist/registry-C581V4TP.mjs.map +1 -0
  569. package/dist/{relation-DJ5DZh7B.mjs → relation-DtmAQH7s.mjs} +3 -3
  570. package/dist/{relation-DJ5DZh7B.mjs.map → relation-DtmAQH7s.mjs.map} +1 -1
  571. package/dist/{relations-C4duJnwI.mjs → relations-BBApI4e2.mjs} +3 -3
  572. package/dist/{relations-C4duJnwI.mjs.map → relations-BBApI4e2.mjs.map} +1 -1
  573. package/dist/{relations-DpTjftCl.mjs → relations-DICSyfsh.mjs} +6 -6
  574. package/dist/{relations-DpTjftCl.mjs.map → relations-DICSyfsh.mjs.map} +1 -1
  575. package/dist/{relations-DHFQGX0h.d.mts → relations-DItI3MN9.d.mts} +401 -40
  576. package/dist/relations-DItI3MN9.d.mts.map +1 -0
  577. package/dist/request-context.d.mts +3 -0
  578. package/dist/request-context.d.mts.map +1 -1
  579. package/dist/request-context.mjs.map +1 -1
  580. package/dist/{request-meta-DzXYYI-n.mjs → request-meta-DgZJh3zG.mjs} +2 -2
  581. package/dist/{request-meta-DzXYYI-n.mjs.map → request-meta-DgZJh3zG.mjs.map} +1 -1
  582. package/dist/{resolve-BUvFE0Lr.mjs → resolve-DOhVEvty.mjs} +2 -2
  583. package/dist/{resolve-BUvFE0Lr.mjs.map → resolve-DOhVEvty.mjs.map} +1 -1
  584. package/dist/{runner-BsI18UgP.mjs → runner-BiDvq5dv.mjs} +1404 -235
  585. package/dist/runner-BiDvq5dv.mjs.map +1 -0
  586. package/dist/{runner-CHdkERbt.d.mts → runner-CgQbELYC.d.mts} +2 -2
  587. package/dist/{runner-CHdkERbt.d.mts.map → runner-CgQbELYC.d.mts.map} +1 -1
  588. package/dist/runtime.d.mts +11 -11
  589. package/dist/runtime.mjs +5 -4
  590. package/dist/runtime.mjs.map +1 -1
  591. package/dist/{schema-eqZhkR0S.mjs → schema-BFyTg38T.mjs} +3 -3
  592. package/dist/{schema-eqZhkR0S.mjs.map → schema-BFyTg38T.mjs.map} +1 -1
  593. package/dist/schema-CeQSNwB3.mjs +390 -0
  594. package/dist/schema-CeQSNwB3.mjs.map +1 -0
  595. package/dist/{scopes-Be2d8bSb.mjs → scopes-BVeuhGBE.mjs} +3 -3
  596. package/dist/{scopes-Be2d8bSb.mjs.map → scopes-BVeuhGBE.mjs.map} +1 -1
  597. package/dist/{search-CeO678cp.mjs → search-BpuPKZb0.mjs} +26 -16
  598. package/dist/search-BpuPKZb0.mjs.map +1 -0
  599. package/dist/{secrets-870d-7yA.mjs → secrets-dtYs2hDo.mjs} +5 -5
  600. package/dist/{secrets-870d-7yA.mjs.map → secrets-dtYs2hDo.mjs.map} +1 -1
  601. package/dist/{sections-DSuEfirn.mjs → sections-COctk6hf.mjs} +3 -3
  602. package/dist/{sections-DSuEfirn.mjs.map → sections-COctk6hf.mjs.map} +1 -1
  603. package/dist/seed/index.d.mts +2 -2
  604. package/dist/seed/index.mjs +28 -26
  605. package/dist/seo/index.d.mts +3 -3
  606. package/dist/seo/index.mjs +20 -19
  607. package/dist/seo/index.mjs.map +1 -1
  608. package/dist/{seo-C3GDfT0V.mjs → seo-AFpVvTqr.mjs} +6 -4
  609. package/dist/seo-AFpVvTqr.mjs.map +1 -0
  610. package/dist/{seo-BAPKiED8.mjs → seo-CvDxfvFb.mjs} +3 -3
  611. package/dist/{seo-BAPKiED8.mjs.map → seo-CvDxfvFb.mjs.map} +1 -1
  612. package/dist/{service-DyFT-k-R.mjs → service-xMiMSTsR.mjs} +4 -4
  613. package/dist/{service-DyFT-k-R.mjs.map → service-xMiMSTsR.mjs.map} +1 -1
  614. package/dist/{session-user-BrK2zz7S.mjs → session-user-BzVemLkw.mjs} +2 -2
  615. package/dist/{session-user-BrK2zz7S.mjs.map → session-user-BzVemLkw.mjs.map} +1 -1
  616. package/dist/{settings-DgKouY2S.mjs → settings-B372MV0p.mjs} +7 -7
  617. package/dist/{settings-DgKouY2S.mjs.map → settings-B372MV0p.mjs.map} +1 -1
  618. package/dist/{settings-CREKyj8I.mjs → settings-BE9aqn_9.mjs} +3 -3
  619. package/dist/{settings-CREKyj8I.mjs.map → settings-BE9aqn_9.mjs.map} +1 -1
  620. package/dist/{setup-complete-L7bMK2VI.mjs → setup-complete-DF7TAfD5.mjs} +2 -2
  621. package/dist/{setup-complete-L7bMK2VI.mjs.map → setup-complete-DF7TAfD5.mjs.map} +1 -1
  622. package/dist/{setup-nonce-Ct5tjWcq.mjs → setup-nonce-Do-U_cg9.mjs} +1 -1
  623. package/dist/{setup-nonce-Ct5tjWcq.mjs.map → setup-nonce-Do-U_cg9.mjs.map} +1 -1
  624. package/dist/{single-flight-cache-C2exrGAi.mjs → single-flight-cache-B0eLKS6K.mjs} +2 -2
  625. package/dist/{single-flight-cache-C2exrGAi.mjs.map → single-flight-cache-B0eLKS6K.mjs.map} +1 -1
  626. package/dist/site-url-Bowsi9T-.mjs +16 -0
  627. package/dist/site-url-Bowsi9T-.mjs.map +1 -0
  628. package/dist/slugify-B994zkXC.mjs +17 -0
  629. package/dist/slugify-B994zkXC.mjs.map +1 -0
  630. package/dist/{snapshot-BdpUJKD-.mjs → snapshot-B5h6aPMh.mjs} +2 -2
  631. package/dist/{snapshot-BdpUJKD-.mjs.map → snapshot-B5h6aPMh.mjs.map} +1 -1
  632. package/dist/{ssrf-CviKqWmq.mjs → ssrf-Cpvg_n3A.mjs} +1 -1
  633. package/dist/{ssrf-CviKqWmq.mjs.map → ssrf-Cpvg_n3A.mjs.map} +1 -1
  634. package/dist/{state-xxv6ZTMv.mjs → state-DYwPue40.mjs} +1 -1
  635. package/dist/{state-xxv6ZTMv.mjs.map → state-DYwPue40.mjs.map} +1 -1
  636. package/dist/{status-COfycGIU.mjs → status-CWVYpbD8.mjs} +1 -1
  637. package/dist/{status-COfycGIU.mjs.map → status-CWVYpbD8.mjs.map} +1 -1
  638. package/dist/storage/local.d.mts +1 -1
  639. package/dist/storage/local.mjs +1 -1
  640. package/dist/storage/s3.d.mts +1 -1
  641. package/dist/storage/s3.mjs +1 -1
  642. package/dist/{taxonomies-B61CRSha.mjs → taxonomies-B0o9ExNV.mjs} +85 -75
  643. package/dist/taxonomies-B0o9ExNV.mjs.map +1 -0
  644. package/dist/{taxonomies-Dbdki3Un.mjs → taxonomies-Dk3MAklL.mjs} +360 -56
  645. package/dist/taxonomies-Dk3MAklL.mjs.map +1 -0
  646. package/dist/taxonomy-CPeM8d2o.mjs +484 -0
  647. package/dist/taxonomy-CPeM8d2o.mjs.map +1 -0
  648. package/dist/{tokens-DVltpO2D.mjs → tokens-C39klX8R.mjs} +2 -2
  649. package/dist/{tokens-DVltpO2D.mjs.map → tokens-C39klX8R.mjs.map} +1 -1
  650. package/dist/{transaction-D0FOsb3X.mjs → transaction-NQj4VJ7Z.mjs} +1 -1
  651. package/dist/{transaction-D0FOsb3X.mjs.map → transaction-NQj4VJ7Z.mjs.map} +1 -1
  652. package/dist/{transport-BWQo0Foy.mjs → transport--CfOflrp.mjs} +1 -1
  653. package/dist/{transport-BWQo0Foy.mjs.map → transport--CfOflrp.mjs.map} +1 -1
  654. package/dist/{transport-DokJOScS.d.mts → transport-B_rfeU-a.d.mts} +1 -1
  655. package/dist/{transport-DokJOScS.d.mts.map → transport-B_rfeU-a.d.mts.map} +1 -1
  656. package/dist/{trusted-proxy-CwjQj0YG.mjs → trusted-proxy-CEVekpby.mjs} +1 -1
  657. package/dist/{trusted-proxy-CwjQj0YG.mjs.map → trusted-proxy-CEVekpby.mjs.map} +1 -1
  658. package/dist/{types-o7xo7VgH.mjs → types-75KIqzL9.mjs} +20 -3
  659. package/dist/types-75KIqzL9.mjs.map +1 -0
  660. package/dist/{types-C9P3ysxq.d.mts → types-BPuloofb.d.mts} +1 -1
  661. package/dist/{types-C9P3ysxq.d.mts.map → types-BPuloofb.d.mts.map} +1 -1
  662. package/dist/{types-BD40g7zz.mjs → types-BW1syrlw.mjs} +1 -1
  663. package/dist/{types-BD40g7zz.mjs.map → types-BW1syrlw.mjs.map} +1 -1
  664. package/dist/{types-jISEalTX.d.mts → types-C3_AbkDP.d.mts} +53 -10
  665. package/dist/types-C3_AbkDP.d.mts.map +1 -0
  666. package/dist/{types-84Bque3-.d.mts → types-CAGdePf3.d.mts} +133 -1
  667. package/dist/types-CAGdePf3.d.mts.map +1 -0
  668. package/dist/{types-CcSZCcF1.d.mts → types-CFUfyt1s.d.mts} +1 -1
  669. package/dist/{types-CcSZCcF1.d.mts.map → types-CFUfyt1s.d.mts.map} +1 -1
  670. package/dist/{types-DQLZ58GV.d.mts → types-CPx2lIGo.d.mts} +1 -1
  671. package/dist/{types-DQLZ58GV.d.mts.map → types-CPx2lIGo.d.mts.map} +1 -1
  672. package/dist/{types-XrQQ-Aex.mjs → types-CQPWhPxE.mjs} +15 -11
  673. package/dist/types-CQPWhPxE.mjs.map +1 -0
  674. package/dist/{types-BQY7VYw6.d.mts → types-Cs-TFLNf.d.mts} +28 -8
  675. package/dist/types-Cs-TFLNf.d.mts.map +1 -0
  676. package/dist/types-CtzytVXn.mjs +18 -0
  677. package/dist/types-CtzytVXn.mjs.map +1 -0
  678. package/dist/{types-DqZVPILm.d.mts → types-D4_1Ga4L.d.mts} +44 -5
  679. package/dist/types-D4_1Ga4L.d.mts.map +1 -0
  680. package/dist/{types-Dr2GLlwu.d.mts → types-DefV3OiO.d.mts} +1 -1
  681. package/dist/{types-Dr2GLlwu.d.mts.map → types-DefV3OiO.d.mts.map} +1 -1
  682. package/dist/{types-DsAMyD_o.d.mts → types-X0MThr7x.d.mts} +1 -1
  683. package/dist/{types-DsAMyD_o.d.mts.map → types-X0MThr7x.d.mts.map} +1 -1
  684. package/dist/{upload-attempts-C5yd6Gae.mjs → upload-attempts-hlne3iA1.mjs} +1 -1
  685. package/dist/{upload-attempts-C5yd6Gae.mjs.map → upload-attempts-hlne3iA1.mjs.map} +1 -1
  686. package/dist/url-pattern-BHS0V5Ng.mjs +33 -0
  687. package/dist/url-pattern-BHS0V5Ng.mjs.map +1 -0
  688. package/dist/{user-BAumEmpA.mjs → user-sHgJ62SP.mjs} +3 -3
  689. package/dist/{user-BAumEmpA.mjs.map → user-sHgJ62SP.mjs.map} +1 -1
  690. package/dist/{utils-BbUgmeZG.mjs → utils-D4vD_Y98.mjs} +3 -3
  691. package/dist/{utils-BbUgmeZG.mjs.map → utils-D4vD_Y98.mjs.map} +1 -1
  692. package/dist/{validate-Bs_wT2ul.mjs → validate-BkqIG_Uj.mjs} +24 -5
  693. package/dist/validate-BkqIG_Uj.mjs.map +1 -0
  694. package/dist/{validate-VPnKoIzW.mjs → validate-BpwoDOgt.mjs} +25 -2
  695. package/dist/validate-BpwoDOgt.mjs.map +1 -0
  696. package/dist/{validate-CdYcY-Ln.d.mts → validate-DeGtBN84.d.mts} +25 -7
  697. package/dist/validate-DeGtBN84.d.mts.map +1 -0
  698. package/dist/{validation-BngR0nit.mjs → validation-CiJb_X5o.mjs} +14 -13
  699. package/dist/{validation-BngR0nit.mjs.map → validation-CiJb_X5o.mjs.map} +1 -1
  700. package/dist/version-x0NjTScG.mjs +7 -0
  701. package/dist/{version-Bdfb34kk.mjs.map → version-x0NjTScG.mjs.map} +1 -1
  702. package/dist/{widgets-L-VndTKD.mjs → widgets-Cwzvww0W.mjs} +3 -3
  703. package/dist/{widgets-L-VndTKD.mjs.map → widgets-Cwzvww0W.mjs.map} +1 -1
  704. package/dist/{wxr-taxonomies-DueaLFd9.d.mts → wxr-taxonomies-CmC0ZsDr.d.mts} +1 -1
  705. package/dist/{wxr-taxonomies-DueaLFd9.d.mts.map → wxr-taxonomies-CmC0ZsDr.d.mts.map} +1 -1
  706. package/dist/{wxr-taxonomies-BexjtNid.mjs → wxr-taxonomies-QZjWxjyk.mjs} +13 -32
  707. package/dist/{wxr-taxonomies-BexjtNid.mjs.map → wxr-taxonomies-QZjWxjyk.mjs.map} +1 -1
  708. package/dist/{zod-generator-DMzfga3g.mjs → zod-generator-D11MhxG4.mjs} +53 -6
  709. package/dist/zod-generator-D11MhxG4.mjs.map +1 -0
  710. package/package.json +18 -7
  711. package/src/after.ts +6 -1
  712. package/src/api/errors.ts +13 -0
  713. package/src/api/handlers/content.ts +172 -33
  714. package/src/api/handlers/dashboard.ts +12 -2
  715. package/src/api/handlers/index.ts +16 -0
  716. package/src/api/handlers/manifest.ts +2 -0
  717. package/src/api/handlers/marketplace.ts +14 -0
  718. package/src/api/handlers/media-allowlist.ts +13 -2
  719. package/src/api/handlers/media-usage-work.ts +188 -0
  720. package/src/api/handlers/media-usage.ts +5 -3
  721. package/src/api/handlers/registry.ts +14 -0
  722. package/src/api/handlers/revision.ts +19 -5
  723. package/src/api/handlers/schema.ts +46 -3
  724. package/src/api/handlers/seo.ts +5 -2
  725. package/src/api/handlers/snapshot.ts +1 -1
  726. package/src/api/handlers/taxonomies.ts +446 -83
  727. package/src/api/media-usage-write-fence.ts +64 -0
  728. package/src/api/openapi/document.ts +247 -0
  729. package/src/api/public-url.ts +14 -2
  730. package/src/api/schemas/common.ts +3 -1
  731. package/src/api/schemas/content.ts +122 -1
  732. package/src/api/schemas/media-usage.ts +133 -0
  733. package/src/api/schemas/schema.ts +91 -3
  734. package/src/api/schemas/search.ts +2 -0
  735. package/src/api/schemas/taxonomies.ts +58 -8
  736. package/src/api/site-url.ts +16 -6
  737. package/src/api/types.ts +1 -0
  738. package/src/astro/integration/index.ts +55 -42
  739. package/src/astro/integration/routes.ts +48 -4
  740. package/src/astro/integration/runtime.ts +28 -0
  741. package/src/astro/integration/virtual-modules.ts +27 -1
  742. package/src/astro/integration/vite-config.ts +16 -1
  743. package/src/astro/middleware/media-usage-write-fence.ts +30 -0
  744. package/src/astro/middleware/redirect.ts +14 -12
  745. package/src/astro/middleware/scoped-db.ts +35 -4
  746. package/src/astro/middleware.ts +131 -30
  747. package/src/astro/routes/api/admin/comments/[id]/status.ts +1 -1
  748. package/src/astro/routes/api/admin/media-usage/collection-deletions/index.ts +22 -0
  749. package/src/astro/routes/api/admin/media-usage/collection-deletions/retry.ts +22 -0
  750. package/src/astro/routes/api/admin/media-usage/work/index.ts +26 -0
  751. package/src/astro/routes/api/admin/media-usage/work/retry.ts +26 -0
  752. package/src/astro/routes/api/admin/plugins/[id]/disable.ts +4 -0
  753. package/src/astro/routes/api/admin/plugins/[id]/enable.ts +4 -0
  754. package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +4 -0
  755. package/src/astro/routes/api/admin/plugins/[id]/update.ts +4 -0
  756. package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +4 -0
  757. package/src/astro/routes/api/admin/plugins/registry/[id]/uninstall.ts +4 -0
  758. package/src/astro/routes/api/admin/plugins/registry/[id]/update.ts +4 -0
  759. package/src/astro/routes/api/admin/plugins/registry/install.ts +4 -0
  760. package/src/astro/routes/api/admin/users/[id]/send-recovery.ts +2 -2
  761. package/src/astro/routes/api/auth/invite/index.ts +2 -2
  762. package/src/astro/routes/api/auth/magic-link/send.ts +2 -2
  763. package/src/astro/routes/api/auth/signup/request.ts +2 -2
  764. package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +1 -1
  765. package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +67 -31
  766. package/src/astro/routes/api/import/wordpress/execute.ts +3 -10
  767. package/src/astro/routes/api/mcp.ts +1 -0
  768. package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +7 -1
  769. package/src/astro/routes/api/schema/collections/[slug]/index.ts +1 -7
  770. package/src/astro/routes/api/schema/collections/reorder.ts +31 -0
  771. package/src/astro/routes/api/schema/index.ts +2 -0
  772. package/src/astro/routes/api/search/enable.ts +5 -1
  773. package/src/astro/routes/api/search/rebuild.ts +6 -1
  774. package/src/astro/routes/api/taxonomies/[name]/reorder.ts +41 -0
  775. package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +1 -0
  776. package/src/astro/routes/api/taxonomies/[name]/translations.ts +48 -0
  777. package/src/astro/routes/api/taxonomies/[name].ts +102 -0
  778. package/src/astro/types.ts +31 -4
  779. package/src/cleanup.ts +25 -26
  780. package/src/cli/commands/dev.ts +13 -3
  781. package/src/cli/commands/doctor.ts +193 -6
  782. package/src/cli/commands/export-seed.ts +6 -1
  783. package/src/cli/index.ts +1 -1
  784. package/src/client/index.ts +119 -0
  785. package/src/components/Block.astro +12 -0
  786. package/src/components/CommentForm.astro +1 -1
  787. package/src/components/Comments.astro +1 -1
  788. package/src/components/InlinePortableTextEditor.tsx +260 -32
  789. package/src/components/OrderedList.astro +14 -0
  790. package/src/components/PortableText.astro +26 -4
  791. package/src/components/index.ts +12 -1
  792. package/src/components/ordered-list.ts +549 -0
  793. package/src/content/converters/mark-safety.ts +120 -0
  794. package/src/content/converters/numbered-list.ts +148 -0
  795. package/src/content/converters/portable-text-to-prosemirror.ts +84 -25
  796. package/src/content/converters/prosemirror-to-portable-text.ts +35 -14
  797. package/src/content/converters/types.ts +2 -0
  798. package/src/content/portable-text-lists.ts +205 -0
  799. package/src/content-list-query.ts +29 -0
  800. package/src/database/dialect-helpers.ts +3 -22
  801. package/src/database/migrations/055_content_translation_group_locale_index.ts +67 -0
  802. package/src/database/migrations/056_taxonomy_term_sort_order.ts +189 -0
  803. package/src/database/migrations/057_collection_hidden.ts +24 -0
  804. package/src/database/migrations/058_collection_sort_order.ts +20 -0
  805. package/src/database/migrations/059_revision_prune_queue.ts +36 -0
  806. package/src/database/migrations/060_collection_admin_config.ts +16 -0
  807. package/src/database/migrations/061_media_usage_cleanup.ts +77 -0
  808. package/src/database/migrations/062_media_usage_cleanup_fence.ts +295 -0
  809. package/src/database/migrations/063_media_usage_incremental_work.ts +397 -0
  810. package/src/database/migrations/064_fts_plain_text.ts +253 -0
  811. package/src/database/migrations/065_media_usage_collection_deletion.ts +85 -0
  812. package/src/database/migrations/066_media_usage_reconciliation.ts +115 -0
  813. package/src/database/migrations/067_indexed_content_fields.ts +39 -0
  814. package/src/database/migrations/068_content_taxonomy_entry_groups.ts +61 -0
  815. package/src/database/migrations/069_collection_title_date_fields.ts +45 -0
  816. package/src/database/migrations/070_collection_routable.ts +18 -0
  817. package/src/database/migrations/runner.ts +32 -0
  818. package/src/database/repositories/content.ts +1051 -273
  819. package/src/database/repositories/media-usage-work.ts +848 -0
  820. package/src/database/repositories/media-usage.ts +1502 -138
  821. package/src/database/repositories/revision.ts +98 -22
  822. package/src/database/repositories/taxonomy.ts +429 -103
  823. package/src/database/repositories/types.ts +58 -2
  824. package/src/database/types.ts +147 -17
  825. package/src/database/validate.ts +39 -0
  826. package/src/db/adapters.ts +36 -0
  827. package/src/db/index.ts +3 -0
  828. package/src/deferred-tasks.ts +71 -0
  829. package/src/emdash-runtime.ts +493 -110
  830. package/src/fields/file.ts +7 -4
  831. package/src/fields/image.ts +8 -1
  832. package/src/fields/types.ts +18 -5
  833. package/src/i18n/config.ts +32 -0
  834. package/src/i18n/repair-locale-casing.ts +0 -14
  835. package/src/i18n/taxonomy-locale-diagnostic.ts +47 -0
  836. package/src/import/wxr-taxonomies.ts +7 -47
  837. package/src/index.ts +27 -2
  838. package/src/loader.ts +165 -89
  839. package/src/mcp/server.ts +242 -34
  840. package/src/media/usage/activation.ts +626 -0
  841. package/src/media/usage/capture-triggers.ts +543 -0
  842. package/src/media/usage/cleanup.ts +305 -0
  843. package/src/media/usage/collection-deletion-processor.ts +356 -0
  844. package/src/media/usage/collection-deletion.ts +836 -0
  845. package/src/media/usage/content-fields.ts +40 -3
  846. package/src/media/usage/content-refresh.ts +356 -47
  847. package/src/media/usage/content-repair.ts +180 -61
  848. package/src/media/usage/content-snapshots.ts +85 -105
  849. package/src/media/usage/gc.ts +50 -0
  850. package/src/media/usage/projection-fingerprint.ts +129 -0
  851. package/src/media/usage/reconciliation-processor.ts +372 -0
  852. package/src/media/usage/reconciliation.ts +1117 -0
  853. package/src/media/usage/source-key.ts +4 -0
  854. package/src/media/usage/types.ts +2 -0
  855. package/src/media/usage/work-processor.ts +248 -0
  856. package/src/menus/index.ts +246 -141
  857. package/src/object-cache/index.ts +160 -3
  858. package/src/plugin-types.ts +8 -0
  859. package/src/plugins/adapt-sandbox-entry.ts +24 -5
  860. package/src/plugins/context.ts +24 -7
  861. package/src/plugins/index.ts +9 -0
  862. package/src/plugins/routes.ts +77 -1
  863. package/src/plugins/sandbox/index.ts +10 -1
  864. package/src/plugins/sandbox/types.ts +78 -1
  865. package/src/plugins/scheduler/node.ts +13 -1
  866. package/src/plugins/scheduler/types.ts +2 -0
  867. package/src/plugins/storage-indexes.ts +50 -11
  868. package/src/plugins/types.ts +39 -2
  869. package/src/query.ts +50 -79
  870. package/src/redirects/cache.ts +79 -19
  871. package/src/request-context.ts +3 -0
  872. package/src/scheduled-publish.ts +6 -1
  873. package/src/scheduler-health.ts +46 -0
  874. package/src/schema/registry.ts +863 -249
  875. package/src/schema/types.ts +74 -8
  876. package/src/schema/url-pattern.ts +33 -0
  877. package/src/schema/zod-generator.ts +77 -5
  878. package/src/search/fts-manager.ts +167 -86
  879. package/src/search/index.ts +2 -0
  880. package/src/search/query.ts +32 -11
  881. package/src/search/types.ts +8 -0
  882. package/src/seed/apply.ts +89 -16
  883. package/src/seed/types.ts +21 -3
  884. package/src/seed/validate.ts +52 -2
  885. package/src/taxonomies/index.ts +88 -82
  886. package/src/taxonomies/term-counts.ts +23 -12
  887. package/src/ui-comments.ts +19 -0
  888. package/src/ui.ts +11 -3
  889. package/src/utils/slugify.ts +1 -29
  890. package/src/virtual-modules.d.ts +24 -0
  891. package/dist/adapters-C0ONzkUP.d.mts.map +0 -1
  892. package/dist/after-B1IIdH3Y.mjs +0 -29
  893. package/dist/after-B1IIdH3Y.mjs.map +0 -1
  894. package/dist/api-DgPbMlng.mjs.map +0 -1
  895. package/dist/apply-1_6ra7NP.mjs.map +0 -1
  896. package/dist/cache-B26cufFd.mjs +0 -65
  897. package/dist/cache-B26cufFd.mjs.map +0 -1
  898. package/dist/config-DyF3OKg_.mjs.map +0 -1
  899. package/dist/content-CpfKV9QE.mjs.map +0 -1
  900. package/dist/content-refresh-DgqUBeTv.mjs +0 -1629
  901. package/dist/content-refresh-DgqUBeTv.mjs.map +0 -1
  902. package/dist/context-kO4YZB03.mjs.map +0 -1
  903. package/dist/dashboard-DoBwPnON.mjs.map +0 -1
  904. package/dist/dialect-helpers-DFwyWjTM.mjs.map +0 -1
  905. package/dist/errors-DtEXIQQV.mjs.map +0 -1
  906. package/dist/fts-manager-vncsZIJm.mjs.map +0 -1
  907. package/dist/index-BeNto_Vs.d.mts.map +0 -1
  908. package/dist/index-j9rTh3cp.d.mts.map +0 -1
  909. package/dist/loader-C1XOLV5b.mjs.map +0 -1
  910. package/dist/media-DWIzoVFL.mjs.map +0 -1
  911. package/dist/media-allowlist-B7TPD6aq.mjs.map +0 -1
  912. package/dist/media-usage-DPJcNaAq.mjs.map +0 -1
  913. package/dist/menus-C6qhqP4X.mjs.map +0 -1
  914. package/dist/object-cache-Bok5j2ae.mjs.map +0 -1
  915. package/dist/options-aeIs8WFu.d.mts.map +0 -1
  916. package/dist/public-url-DSGTnJFw.mjs.map +0 -1
  917. package/dist/query-DCiXI7OZ.mjs.map +0 -1
  918. package/dist/redirects-BOn2DIUs.mjs.map +0 -1
  919. package/dist/registry-BP1JK2xh.mjs +0 -795
  920. package/dist/registry-BP1JK2xh.mjs.map +0 -1
  921. package/dist/relations-DHFQGX0h.d.mts.map +0 -1
  922. package/dist/runner-BsI18UgP.mjs.map +0 -1
  923. package/dist/search-CeO678cp.mjs.map +0 -1
  924. package/dist/seo-C3GDfT0V.mjs.map +0 -1
  925. package/dist/site-url-8N3kSyVZ.mjs +0 -13
  926. package/dist/site-url-8N3kSyVZ.mjs.map +0 -1
  927. package/dist/slugify-C_tqlU4G.mjs +0 -30
  928. package/dist/slugify-C_tqlU4G.mjs.map +0 -1
  929. package/dist/taxonomies-B61CRSha.mjs.map +0 -1
  930. package/dist/taxonomies-Dbdki3Un.mjs.map +0 -1
  931. package/dist/taxonomy-DvwWAPvA.mjs +0 -304
  932. package/dist/taxonomy-DvwWAPvA.mjs.map +0 -1
  933. package/dist/types-84Bque3-.d.mts.map +0 -1
  934. package/dist/types-BQY7VYw6.d.mts.map +0 -1
  935. package/dist/types-DqZVPILm.d.mts.map +0 -1
  936. package/dist/types-XrQQ-Aex.mjs.map +0 -1
  937. package/dist/types-jISEalTX.d.mts.map +0 -1
  938. package/dist/types-o7xo7VgH.mjs.map +0 -1
  939. package/dist/validate-Bs_wT2ul.mjs.map +0 -1
  940. package/dist/validate-CdYcY-Ln.d.mts.map +0 -1
  941. package/dist/validate-VPnKoIzW.mjs.map +0 -1
  942. package/dist/version-Bdfb34kk.mjs +0 -7
  943. package/dist/zod-generator-DMzfga3g.mjs.map +0 -1
  944. /package/dist/{api-tokens-CEsW_jCg.mjs → api-tokens-CyTsLszZ.mjs} +0 -0
  945. /package/dist/{ssrf-BB__xfNM.mjs → ssrf-DpXgKsna.mjs} +0 -0
  946. /package/dist/{types-CK0LxPUQ.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,10 +95,32 @@ 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
 
102
+ /**
103
+ * Rank given to collections without an explicit `sort_order`. SQLite sorts
104
+ * NULL first on ASC while Postgres sorts it last, so the fallback is
105
+ * materialised with COALESCE rather than left to the dialect.
106
+ */
107
+ const UNORDERED_COLLECTION_RANK = 2147483647;
108
+
109
+ /**
110
+ * Collection ordering shared by every list read: explicit `sort_order`
111
+ * first (ascending), then alphabetically by slug.
112
+ */
113
+ const collectionOrder = sql<number>`coalesce(sort_order, ${sql.lit(UNORDERED_COLLECTION_RANK)})`;
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
+
76
124
  function isCollectionSupport(value: unknown): value is CollectionSupport {
77
125
  return typeof value === "string" && VALID_COLLECTION_SUPPORTS.has(value);
78
126
  }
@@ -92,6 +140,87 @@ function parseSupports(raw: string | null | undefined): CollectionSupport[] {
92
140
  return parsed.filter(isCollectionSupport);
93
141
  }
94
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
+
95
224
  /**
96
225
  * Error thrown when a schema operation fails
97
226
  */
@@ -126,6 +255,7 @@ export class SchemaRegistry {
126
255
  const rows = await this.db
127
256
  .selectFrom("_emdash_collections")
128
257
  .selectAll()
258
+ .orderBy(collectionOrder, "asc")
129
259
  .orderBy("slug", "asc")
130
260
  .execute();
131
261
 
@@ -177,6 +307,7 @@ export class SchemaRegistry {
177
307
  const collectionRows = await this.db
178
308
  .selectFrom("_emdash_collections")
179
309
  .selectAll()
310
+ .orderBy(collectionOrder, "asc")
180
311
  .orderBy("slug", "asc")
181
312
  .execute();
182
313
 
@@ -212,6 +343,61 @@ export class SchemaRegistry {
212
343
  }));
213
344
  }
214
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
+
215
401
  /**
216
402
  * Create a new collection
217
403
  */
@@ -221,14 +407,26 @@ export class SchemaRegistry {
221
407
  if (RESERVED_COLLECTION_SLUGS.includes(input.slug)) {
222
408
  throw new SchemaError(`Collection slug "${input.slug}" is reserved`, "RESERVED_SLUG");
223
409
  }
410
+ if (await isMediaUsageCollectionSlugDeleting(this.db, input.slug)) {
411
+ throw new SchemaError(`Collection "${input.slug}" already exists`, "COLLECTION_EXISTS");
412
+ }
224
413
 
225
414
  // Check if collection already exists
226
415
  const existing = await this.getCollection(input.slug);
227
- 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
+ ) {
228
426
  throw new SchemaError(`Collection "${input.slug}" already exists`, "COLLECTION_EXISTS");
229
427
  }
230
428
 
231
- const id = ulid();
429
+ const proposedId = existing?.id ?? ulid();
232
430
 
233
431
  // Default `supports` to drafts + revisions when the caller didn't
234
432
  // specify it. Explicit empty array (`[]`) is preserved as an opt-out
@@ -244,24 +442,52 @@ export class SchemaRegistry {
244
442
  const hasSeo = input.hasSeo ?? supports.includes("seo") ?? false;
245
443
 
246
444
  await withTransaction(this.db, async (trx) => {
247
- await trx
248
- .insertInto("_emdash_collections")
249
- .values({
250
- id,
251
- slug: input.slug,
252
- label: input.label,
253
- label_singular: input.labelSingular ?? null,
254
- description: input.description ?? null,
255
- icon: input.icon ?? null,
256
- supports: JSON.stringify(supports),
257
- source: input.source ?? "manual",
258
- has_seo: hasSeo ? 1 : 0,
259
- comments_enabled: input.commentsEnabled ? 1 : 0,
260
- url_pattern: input.urlPattern ?? null,
261
- })
262
- .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
+ };
467
+
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
+ }
263
489
 
264
- // Create the content table for this collection
490
+ await trx.insertInto("_emdash_collections").values(values).execute();
265
491
  await this.createContentTable(input.slug, trx);
266
492
  });
267
493
 
@@ -291,15 +517,14 @@ export class SchemaRegistry {
291
517
  if (RESERVED_COLLECTION_SLUGS.includes(input.slug)) {
292
518
  throw new SchemaError(`Collection slug "${input.slug}" is reserved`, "RESERVED_SLUG");
293
519
  }
294
-
295
- const existing = await this.getCollection(input.slug);
296
- if (existing) {
520
+ if (await isMediaUsageCollectionSlugDeleting(this.db, input.slug)) {
297
521
  throw new SchemaError(`Collection "${input.slug}" already exists`, "COLLECTION_EXISTS");
298
522
  }
299
523
 
300
524
  const fieldSlugs = new Set<string>();
301
525
  for (const field of fields) {
302
526
  this.validateSlug(field.slug, "field");
527
+ assertIndexableField(field.type, field.indexed, field.slug);
303
528
  if (RESERVED_FIELD_SLUGS.includes(field.slug)) {
304
529
  throw new SchemaError(`Field slug "${field.slug}" is reserved`, "RESERVED_SLUG");
305
530
  }
@@ -312,9 +537,25 @@ export class SchemaRegistry {
312
537
  fieldSlugs.add(field.slug);
313
538
  }
314
539
 
315
- const collectionId = ulid();
316
540
  const supports = input.supports ?? ["drafts", "revisions"];
317
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();
318
559
  let maxSortOrder = -1;
319
560
  const fieldRows: Insertable<FieldTable>[] = fields.map((field) => {
320
561
  const sortOrder = field.sortOrder ?? maxSortOrder + 1;
@@ -322,7 +563,7 @@ export class SchemaRegistry {
322
563
 
323
564
  return {
324
565
  id: ulid(),
325
- collection_id: collectionId,
566
+ collection_id: proposedCollectionId,
326
567
  slug: field.slug,
327
568
  label: field.label,
328
569
  type: field.type,
@@ -335,6 +576,7 @@ export class SchemaRegistry {
335
576
  options: field.options ? JSON.stringify(field.options) : null,
336
577
  sort_order: sortOrder,
337
578
  searchable: field.searchable ? 1 : 0,
579
+ indexed: field.indexed ? 1 : 0,
338
580
  translatable: field.translatable === false ? 0 : 1,
339
581
  };
340
582
  });
@@ -342,28 +584,79 @@ export class SchemaRegistry {
342
584
  let schemaMutated = false;
343
585
  try {
344
586
  await withTransaction(this.db, async (trx) => {
345
- await trx
346
- .insertInto("_emdash_collections")
347
- .values({
348
- id: collectionId,
349
- slug: input.slug,
350
- label: input.label,
351
- label_singular: input.labelSingular ?? null,
352
- description: input.description ?? null,
353
- icon: input.icon ?? null,
354
- supports: JSON.stringify(supports),
355
- source: "seed",
356
- has_seo: hasSeo ? 1 : 0,
357
- comments_enabled: input.commentsEnabled ? 1 : 0,
358
- url_pattern: input.urlPattern ?? null,
359
- })
360
- .execute();
361
- schemaMutated = true;
362
-
363
- 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
+ }
364
636
 
365
- for (const fieldBatch of chunks(fieldRows, SEED_FIELD_INSERT_BATCH_SIZE)) {
366
- 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
+ });
367
660
  }
368
661
  });
369
662
 
@@ -380,66 +673,108 @@ export class SchemaRegistry {
380
673
  }
381
674
  }
382
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
+
383
718
  /**
384
719
  * Update a collection
385
720
  */
386
721
  async updateCollection(slug: string, input: UpdateCollectionInput): Promise<Collection> {
387
- const existing = await this.getCollection(slug);
388
- if (!existing) {
389
- throw new SchemaError(`Collection "${slug}" not found`, "COLLECTION_NOT_FOUND");
390
- }
391
-
392
- const now = new Date().toISOString();
393
-
394
- // Derive hasSeo from supports array if supports is being updated and hasSeo not explicitly set
395
- const supportsArray = input.supports ?? existing.supports;
396
- const hasSeo =
397
- input.hasSeo !== undefined
398
- ? input.hasSeo
399
- : input.supports !== undefined
400
- ? supportsArray.includes("seo")
401
- : existing.hasSeo;
402
-
403
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();
404
774
  await trx
405
775
  .updateTable("_emdash_collections")
406
- .set({
407
- label: input.label ?? existing.label,
408
- label_singular: input.labelSingular ?? existing.labelSingular ?? null,
409
- description: input.description ?? existing.description ?? null,
410
- icon: input.icon ?? existing.icon ?? null,
411
- supports: input.supports
412
- ? JSON.stringify(input.supports)
413
- : JSON.stringify(existing.supports),
414
- url_pattern:
415
- input.urlPattern !== undefined
416
- ? (input.urlPattern ?? null)
417
- : (existing.urlPattern ?? null),
418
- has_seo: hasSeo ? 1 : 0,
419
- comments_enabled:
420
- input.commentsEnabled !== undefined
421
- ? input.commentsEnabled
422
- ? 1
423
- : 0
424
- : existing.commentsEnabled
425
- ? 1
426
- : 0,
427
- comments_moderation: input.commentsModeration ?? existing.commentsModeration,
428
- comments_closed_after_days:
429
- input.commentsClosedAfterDays !== undefined
430
- ? input.commentsClosedAfterDays
431
- : existing.commentsClosedAfterDays,
432
- comments_auto_approve_users:
433
- input.commentsAutoApproveUsers !== undefined
434
- ? input.commentsAutoApproveUsers
435
- ? 1
436
- : 0
437
- : existing.commentsAutoApproveUsers
438
- ? 1
439
- : 0,
440
- updated_at: now,
441
- })
442
- .where("slug", "=", slug)
776
+ .set(updates)
777
+ .where("id", "=", existing.id)
443
778
  .execute();
444
779
 
445
780
  const row = await trx
@@ -470,21 +805,31 @@ export class SchemaRegistry {
470
805
  */
471
806
  async deleteCollection(slug: string, options?: { force?: boolean }): Promise<void> {
472
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;
473
829
  if (!existing) {
474
830
  throw new SchemaError(`Collection "${slug}" not found`, "COLLECTION_NOT_FOUND");
475
831
  }
476
832
 
477
- // Check if collection has content
478
- if (!options?.force) {
479
- const hasContent = await this.collectionHasContent(slug);
480
- if (hasContent) {
481
- throw new SchemaError(
482
- `Collection "${slug}" has content. Use force: true to delete.`,
483
- "COLLECTION_HAS_CONTENT",
484
- );
485
- }
486
- }
487
-
488
833
  let contentTableDropped = false;
489
834
  try {
490
835
  await withTransaction(this.db, async (trx) => {
@@ -571,6 +916,7 @@ export class SchemaRegistry {
571
916
 
572
917
  const id = ulid();
573
918
  const columnType = FIELD_TYPE_TO_COLUMN[input.type];
919
+ assertIndexableField(input.type, input.indexed, input.slug);
574
920
 
575
921
  // Get max sort order
576
922
  const maxSort = await this.db
@@ -580,6 +926,10 @@ export class SchemaRegistry {
580
926
  .executeTakeFirst();
581
927
 
582
928
  const sortOrder = input.sortOrder ?? (maxSort?.max ?? -1) + 1;
929
+ const activeCoverageInvalidated = await invalidateContentMediaUsageSchemaChange(
930
+ this.db,
931
+ collectionSlug,
932
+ );
583
933
 
584
934
  let schemaMutated = false;
585
935
  try {
@@ -603,6 +953,7 @@ export class SchemaRegistry {
603
953
  options: input.options ? JSON.stringify(input.options) : null,
604
954
  sort_order: sortOrder,
605
955
  searchable: input.searchable ? 1 : 0,
956
+ indexed: input.indexed ? 1 : 0,
606
957
  translatable: input.translatable === false ? 0 : 1,
607
958
  })
608
959
  .execute();
@@ -620,6 +971,10 @@ export class SchemaRegistry {
620
971
  trx,
621
972
  );
622
973
 
974
+ if (input.indexed) {
975
+ await this.createFieldIndex(collectionSlug, id, input.slug, trx);
976
+ }
977
+
623
978
  // Read the created field via trx (not this.db) to avoid connection mutex deadlock
624
979
  const fieldRow = await trx
625
980
  .selectFrom("_emdash_fields")
@@ -641,20 +996,28 @@ export class SchemaRegistry {
641
996
 
642
997
  return field;
643
998
  });
644
- await markContentMediaUsageCollectionStaleSafely(
645
- this.db,
646
- collectionSlug,
647
- "CONTENT_USAGE_STALE",
648
- );
649
- return created;
650
- } catch (error) {
651
- if (schemaMutated) {
999
+ if (activeCoverageInvalidated) {
1000
+ await invalidateContentMediaUsageSchemaChange(this.db, collectionSlug);
1001
+ } else {
652
1002
  await markContentMediaUsageCollectionStaleSafely(
653
1003
  this.db,
654
1004
  collectionSlug,
655
1005
  "CONTENT_USAGE_STALE",
656
1006
  );
657
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
+ }
658
1021
  throw error;
659
1022
  }
660
1023
  }
@@ -667,87 +1030,126 @@ export class SchemaRegistry {
667
1030
  fieldSlug: string,
668
1031
  input: UpdateFieldInput,
669
1032
  ): Promise<Field> {
670
- const field = await this.getField(collectionSlug, fieldSlug);
671
- if (!field) {
672
- throw new SchemaError(
673
- `Field "${fieldSlug}" not found in collection "${collectionSlug}"`,
674
- "FIELD_NOT_FOUND",
675
- );
676
- }
677
-
678
- // `input.validation === undefined` means "no change" (keep existing);
679
- // an explicit `null` clears the column.
680
- const nextValidation = input.validation === undefined ? field.validation : input.validation;
681
-
682
- // A field-type change is only safe when the underlying column type stays
683
- // the same. There is no in-place column migration (only addColumn/
684
- // dropColumn), so a change that would alter the SQLite column affinity
685
- // (e.g. `text` TEXT -> `portableText` JSON) is rejected rather than
686
- // silently rewriting only the metadata — which would leave `column_type`
687
- // pointing at a column type the real `ec_*` column doesn't have (#1397).
688
- let nextType = field.type;
689
- let nextColumnType = field.columnType;
690
- if (input.type !== undefined && input.type !== field.type) {
691
- const newColumnType = FIELD_TYPE_TO_COLUMN[input.type];
692
- if (newColumnType !== field.columnType) {
693
- throw new SchemaError(
694
- `Cannot change field "${fieldSlug}" in collection "${collectionSlug}" from type ` +
695
- `"${field.type}" to "${input.type}": the underlying column type would change from ` +
696
- `${field.columnType} to ${newColumnType}, which requires a manual content migration. ` +
697
- `Drop and re-create the field, or migrate the column data, before changing its type.`,
698
- "FIELD_TYPE_COLUMN_CHANGE",
699
- );
700
- }
701
- nextType = input.type;
702
- nextColumnType = newColumnType;
703
- }
704
-
1033
+ let activeCoverageInvalidated = false;
705
1034
  let schemaMutated = false;
706
1035
  try {
707
1036
  const updatedField = await withTransaction(this.db, async (trx) => {
708
- await trx
709
- .updateTable("_emdash_fields")
710
- .set({
711
- type: nextType,
712
- column_type: nextColumnType,
713
- label: input.label ?? field.label,
714
- required:
715
- input.required !== undefined ? (input.required ? 1 : 0) : field.required ? 1 : 0,
716
- unique: input.unique !== undefined ? (input.unique ? 1 : 0) : field.unique ? 1 : 0,
717
- searchable:
718
- input.searchable !== undefined
719
- ? input.searchable
720
- ? 1
721
- : 0
722
- : field.searchable
723
- ? 1
724
- : 0,
725
- translatable:
726
- input.translatable !== undefined
727
- ? input.translatable
728
- ? 1
729
- : 0
730
- : field.translatable
731
- ? 1
732
- : 0,
733
- default_value:
734
- input.defaultValue !== undefined
735
- ? JSON.stringify(input.defaultValue)
736
- : field.defaultValue !== undefined
737
- ? JSON.stringify(field.defaultValue)
738
- : null,
739
- validation: nextValidation ? JSON.stringify(nextValidation) : null,
740
- widget: input.widget ?? field.widget ?? null,
741
- options: input.options
742
- ? JSON.stringify(input.options)
743
- : field.options
744
- ? JSON.stringify(field.options)
745
- : null,
746
- sort_order: input.sortOrder ?? field.sortOrder,
747
- })
748
- .where("id", "=", field.id)
749
- .execute();
750
- 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
+ }
751
1153
 
752
1154
  // Read the updated field via trx (not this.db) to avoid connection mutex deadlock
753
1155
  const updatedRow = await trx
@@ -765,26 +1167,36 @@ export class SchemaRegistry {
765
1167
 
766
1168
  // If searchable changed, sync FTS state for this collection
767
1169
  const searchableChanged =
768
- input.searchable !== undefined && input.searchable !== field.searchable;
1170
+ schemaMutated && input.searchable !== undefined && input.searchable !== field.searchable;
769
1171
  if (searchableChanged) {
770
1172
  await this.syncSearchState(collectionSlug, trx);
771
1173
  }
772
1174
 
773
1175
  return updated;
774
1176
  });
775
- await markContentMediaUsageCollectionStaleSafely(
776
- this.db,
777
- collectionSlug,
778
- "CONTENT_USAGE_STALE",
779
- );
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
+ }
780
1188
  return updatedField;
781
1189
  } catch (error) {
782
1190
  if (schemaMutated) {
783
- await markContentMediaUsageCollectionStaleSafely(
784
- this.db,
785
- collectionSlug,
786
- "CONTENT_USAGE_STALE",
787
- );
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
+ }
788
1200
  }
789
1201
  throw error;
790
1202
  }
@@ -822,7 +1234,12 @@ export class SchemaRegistry {
822
1234
  const ftsActive = config?.enabled === true;
823
1235
 
824
1236
  if (wantsSearch && searchableFields.length > 0 && ftsActive) {
825
- await ftsManager.rebuildIndex(collectionSlug, searchableFields, config?.weights);
1237
+ await ftsManager.rebuildIndex(
1238
+ collectionSlug,
1239
+ searchableFields,
1240
+ config?.weights,
1241
+ config?.tokenize,
1242
+ );
826
1243
  } else if (ftsActive && (!wantsSearch || searchableFields.length === 0)) {
827
1244
  await ftsManager.disableSearch(collectionSlug);
828
1245
  }
@@ -839,6 +1256,17 @@ export class SchemaRegistry {
839
1256
  "FIELD_NOT_FOUND",
840
1257
  );
841
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;
842
1270
 
843
1271
  let schemaMutated = false;
844
1272
  try {
@@ -851,31 +1279,109 @@ export class SchemaRegistry {
851
1279
  await trx.deleteFrom("_emdash_fields").where("id", "=", field.id).execute();
852
1280
  schemaMutated = true;
853
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
+
854
1294
  // If the deleted field was searchable, sync FTS state (removes old triggers)
855
1295
  if (field.searchable) {
856
1296
  await this.syncSearchState(collectionSlug, trx);
857
1297
  }
858
1298
 
1299
+ if (field.indexed) {
1300
+ await this.dropFieldIndex(field.id, trx);
1301
+ }
1302
+
859
1303
  // Drop column from content table — safe now because FTS triggers are gone
860
1304
  await this.dropColumn(collectionSlug, fieldSlug, trx);
861
1305
  });
862
- await markContentMediaUsageCollectionStaleSafely(
863
- this.db,
864
- collectionSlug,
865
- "CONTENT_USAGE_STALE",
866
- );
867
- } catch (error) {
868
- if (schemaMutated) {
1306
+ if (activeCoverageInvalidated) {
1307
+ await invalidateContentMediaUsageSchemaChange(this.db, collectionSlug);
1308
+ } else {
869
1309
  await markContentMediaUsageCollectionStaleSafely(
870
1310
  this.db,
871
1311
  collectionSlug,
872
1312
  "CONTENT_USAGE_STALE",
873
1313
  );
874
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
+ }
875
1327
  throw error;
876
1328
  }
877
1329
  }
878
1330
 
1331
+ /**
1332
+ * Reorder collections in the admin sidebar.
1333
+ *
1334
+ * `slugs` is the full desired order: every listed collection gets its
1335
+ * index as `sort_order`, and any collection left out has its explicit
1336
+ * position cleared, dropping it back to the alphabetical tail. Unknown or
1337
+ * duplicate slugs throw before anything is written.
1338
+ */
1339
+ async reorderCollections(slugs: string[]): Promise<void> {
1340
+ const known = new Set((await this.listCollections()).map((collection) => collection.slug));
1341
+
1342
+ const unknown = slugs.filter((slug) => !known.has(slug));
1343
+ if (unknown.length > 0) {
1344
+ throw new SchemaError(
1345
+ `Unknown collection${unknown.length > 1 ? "s" : ""}: ${unknown.join(", ")}`,
1346
+ "COLLECTION_NOT_FOUND",
1347
+ { slugs: unknown },
1348
+ );
1349
+ }
1350
+
1351
+ const duplicates = slugs.filter((slug, index) => slugs.indexOf(slug) !== index);
1352
+ if (duplicates.length > 0) {
1353
+ throw new SchemaError(
1354
+ `Duplicate collection${duplicates.length > 1 ? "s" : ""}: ${[...new Set(duplicates)].join(", ")}`,
1355
+ "DUPLICATE_SLUG",
1356
+ { slugs: [...new Set(duplicates)] },
1357
+ );
1358
+ }
1359
+
1360
+ const now = new Date().toISOString();
1361
+ const ordered = new Set(slugs);
1362
+
1363
+ await withTransaction(this.db, async (trx) => {
1364
+ for (const [index, slug] of slugs.entries()) {
1365
+ await trx
1366
+ .updateTable("_emdash_collections")
1367
+ .set({ sort_order: index, updated_at: now })
1368
+ .where("slug", "=", slug)
1369
+ .execute();
1370
+ }
1371
+
1372
+ const cleared = [...known].filter((slug) => !ordered.has(slug));
1373
+ // Chunked to stay under D1's bound-parameter limit; typical sites
1374
+ // clear far fewer than one chunk.
1375
+ for (const slugChunk of chunks(cleared, SQL_BATCH_SIZE)) {
1376
+ await trx
1377
+ .updateTable("_emdash_collections")
1378
+ .set({ sort_order: null, updated_at: now })
1379
+ .where("slug", "in", slugChunk)
1380
+ .execute();
1381
+ }
1382
+ });
1383
+ }
1384
+
879
1385
  /**
880
1386
  * Reorder fields
881
1387
  */
@@ -907,6 +1413,7 @@ export class SchemaRegistry {
907
1413
  slug: string,
908
1414
  db?: Kysely<Database>,
909
1415
  fields: readonly CreateFieldInput[] = [],
1416
+ options: { ifNotExists?: boolean } = {},
910
1417
  ): Promise<void> {
911
1418
  const conn = db ?? this.db;
912
1419
  const tableName = this.getTableName(slug);
@@ -928,6 +1435,7 @@ export class SchemaRegistry {
928
1435
  .addColumn("draft_revision_id", "text", (col) => col.references("revisions.id"))
929
1436
  .addColumn("locale", "text", (col) => col.notNull().defaultTo("en"))
930
1437
  .addColumn("translation_group", "text");
1438
+ if (options.ifNotExists) table = table.ifNotExists();
931
1439
 
932
1440
  for (const field of fields) {
933
1441
  const columnName = this.getColumnName(field.slug);
@@ -947,66 +1455,77 @@ export class SchemaRegistry {
947
1455
  .addUniqueConstraint(`${tableName}_slug_locale_unique`, ["slug", "locale"])
948
1456
  .execute();
949
1457
 
1458
+ const createIndex = options.ifNotExists ? sql`CREATE INDEX IF NOT EXISTS` : sql`CREATE INDEX`;
1459
+
950
1460
  // Create standard indexes
951
1461
  await sql`
952
- CREATE INDEX ${sql.ref(`idx_${tableName}_slug`)}
1462
+ ${createIndex} ${sql.ref(`idx_${tableName}_slug`)}
953
1463
  ON ${sql.ref(tableName)} (slug)
954
1464
  `.execute(conn);
955
1465
 
956
1466
  await sql`
957
- CREATE INDEX ${sql.ref(`idx_${tableName}_scheduled`)}
1467
+ ${createIndex} ${sql.ref(`idx_${tableName}_scheduled`)}
958
1468
  ON ${sql.ref(tableName)} (scheduled_at)
959
1469
  WHERE scheduled_at IS NOT NULL
960
1470
  `.execute(conn);
961
1471
 
962
1472
  await sql`
963
- CREATE INDEX ${sql.ref(`idx_${tableName}_live_revision`)}
1473
+ ${createIndex} ${sql.ref(`idx_${tableName}_live_revision`)}
964
1474
  ON ${sql.ref(tableName)} (live_revision_id)
965
1475
  `.execute(conn);
966
1476
 
967
1477
  await sql`
968
- CREATE INDEX ${sql.ref(`idx_${tableName}_draft_revision`)}
1478
+ ${createIndex} ${sql.ref(`idx_${tableName}_draft_revision`)}
969
1479
  ON ${sql.ref(tableName)} (draft_revision_id)
970
1480
  `.execute(conn);
971
1481
 
972
1482
  await sql`
973
- CREATE INDEX ${sql.ref(`idx_${tableName}_author`)}
1483
+ ${createIndex} ${sql.ref(`idx_${tableName}_author`)}
974
1484
  ON ${sql.ref(tableName)} (author_id)
975
1485
  `.execute(conn);
976
1486
 
977
1487
  await sql`
978
- CREATE INDEX ${sql.ref(`idx_${tableName}_primary_byline`)}
1488
+ ${createIndex} ${sql.ref(`idx_${tableName}_primary_byline`)}
979
1489
  ON ${sql.ref(tableName)} (primary_byline_id)
980
1490
  `.execute(conn);
981
1491
 
982
1492
  await sql`
983
- CREATE INDEX ${sql.ref(`idx_${tableName}_locale`)}
1493
+ ${createIndex} ${sql.ref(`idx_${tableName}_locale`)}
984
1494
  ON ${sql.ref(tableName)} (locale)
985
1495
  `.execute(conn);
986
1496
 
1497
+ // Names must stay identical to migration 055, which creates these indexes
1498
+ // on tables that already exist. Lookups that don't constrain `deleted_at`
1499
+ // (menu and reference resolution) need the first; reads that do need the
1500
+ // second.
987
1501
  await sql`
988
- CREATE INDEX ${sql.ref(`idx_${tableName}_translation_group`)}
989
- ON ${sql.ref(tableName)} (translation_group)
1502
+ ${createIndex} ${sql.ref(`idx_${tableName}_tg_locale`)}
1503
+ ON ${sql.ref(tableName)} (translation_group, locale)
1504
+ `.execute(conn);
1505
+
1506
+ await sql`
1507
+ ${createIndex} ${sql.ref(`idx_${tableName}_del_tg_locale`)}
1508
+ ON ${sql.ref(tableName)} (deleted_at, translation_group, locale)
990
1509
  `.execute(conn);
991
1510
 
992
1511
  // Composite indexes for optimized query performance (see migration 033)
993
1512
  await sql`
994
- CREATE INDEX ${sql.ref(`idx_${tableName}_deleted_updated_id`)}
1513
+ ${createIndex} ${sql.ref(`idx_${tableName}_deleted_updated_id`)}
995
1514
  ON ${sql.ref(tableName)} (deleted_at, updated_at DESC, id DESC)
996
1515
  `.execute(conn);
997
1516
 
998
1517
  await sql`
999
- CREATE INDEX ${sql.ref(`idx_${tableName}_deleted_status`)}
1518
+ ${createIndex} ${sql.ref(`idx_${tableName}_deleted_status`)}
1000
1519
  ON ${sql.ref(tableName)} (deleted_at, status)
1001
1520
  `.execute(conn);
1002
1521
 
1003
1522
  await sql`
1004
- CREATE INDEX ${sql.ref(`idx_${tableName}_deleted_created_id`)}
1523
+ ${createIndex} ${sql.ref(`idx_${tableName}_deleted_created_id`)}
1005
1524
  ON ${sql.ref(tableName)} (deleted_at, created_at DESC, id DESC)
1006
1525
  `.execute(conn);
1007
1526
 
1008
1527
  await sql`
1009
- CREATE INDEX ${sql.ref(`idx_${tableName}_deleted_published_id`)}
1528
+ ${createIndex} ${sql.ref(`idx_${tableName}_deleted_published_id`)}
1010
1529
  ON ${sql.ref(tableName)} (deleted_at, published_at DESC, id DESC)
1011
1530
  `.execute(conn);
1012
1531
 
@@ -1015,16 +1534,67 @@ export class SchemaRegistry {
1015
1534
  // inside Postgres's 63-byte identifier limit for long slugs; keep these
1016
1535
  // names identical to migration 041.
1017
1536
  await sql`
1018
- CREATE INDEX ${sql.ref(`idx_${tableName}_loc_upd`)}
1537
+ ${createIndex} ${sql.ref(`idx_${tableName}_loc_upd`)}
1019
1538
  ON ${sql.ref(tableName)} (deleted_at, locale, updated_at DESC, id DESC)
1020
1539
  `.execute(conn);
1021
1540
 
1022
1541
  await sql`
1023
- CREATE INDEX ${sql.ref(`idx_${tableName}_loc_crt`)}
1542
+ ${createIndex} ${sql.ref(`idx_${tableName}_loc_crt`)}
1024
1543
  ON ${sql.ref(tableName)} (deleted_at, locale, created_at DESC, id DESC)
1025
1544
  `.execute(conn);
1026
1545
  }
1027
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
+
1028
1598
  /**
1029
1599
  * Add a column to a content table
1030
1600
  */
@@ -1224,10 +1794,18 @@ export class SchemaRegistry {
1224
1794
  labelSingular: row.label_singular ?? undefined,
1225
1795
  description: row.description ?? undefined,
1226
1796
  icon: row.icon ?? undefined,
1797
+ admin: parseCollectionAdmin(row.admin_config),
1227
1798
  supports: parseSupports(row.supports),
1228
1799
  source: row.source && isCollectionSource(row.source) ? row.source : undefined,
1229
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,
1230
1805
  urlPattern: row.url_pattern ?? undefined,
1806
+ routable: row.routable !== 0,
1807
+ hidden: row.hidden === 1,
1808
+ sortOrder: row.sort_order ?? undefined,
1231
1809
  commentsEnabled: row.comments_enabled === 1,
1232
1810
  commentsModeration:
1233
1811
  moderation === "all" || moderation === "first_time" || moderation === "none"
@@ -1259,6 +1837,7 @@ export class SchemaRegistry {
1259
1837
  options: row.options ? JSON.parse(row.options) : undefined,
1260
1838
  sortOrder: row.sort_order,
1261
1839
  searchable: row.searchable === 1,
1840
+ indexed: row.indexed === 1,
1262
1841
  translatable: row.translatable !== 0,
1263
1842
  createdAt: row.created_at,
1264
1843
  };
@@ -1338,6 +1917,9 @@ export class SchemaRegistry {
1338
1917
  ): Promise<Collection> {
1339
1918
  // Verify table exists
1340
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
+ }
1341
1923
  const exists = await tableExists(this.db, tableName);
1342
1924
 
1343
1925
  if (!exists) {
@@ -1346,32 +1928,64 @@ export class SchemaRegistry {
1346
1928
 
1347
1929
  // Check if already registered
1348
1930
  const existing = await this.getCollection(slug);
1349
- 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
+ ) {
1350
1941
  throw new SchemaError(`Collection "${slug}" is already registered`, "COLLECTION_EXISTS");
1351
1942
  }
1352
1943
 
1353
1944
  // Create collection entry
1354
- const id = ulid();
1945
+ const proposedId = existing?.id ?? ulid();
1355
1946
  const label = options?.label || this.slugToLabel(slug);
1356
1947
 
1357
1948
  let collectionRegistered = false;
1358
1949
  try {
1359
- await this.db
1360
- .insertInto("_emdash_collections")
1361
- .values({
1362
- id,
1363
- slug,
1364
- label,
1365
- label_singular: options?.labelSingular ?? null,
1366
- description: options?.description ?? null,
1367
- icon: null,
1368
- supports: JSON.stringify([]),
1369
- source: "discovered",
1370
- has_seo: 0,
1371
- url_pattern: null,
1372
- })
1373
- .execute();
1374
- 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
+ }
1375
1989
 
1376
1990
  const collection = await this.getCollection(slug);
1377
1991
  if (!collection) {