emdash 0.36.0 → 0.37.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 (809) hide show
  1. package/dist/{adapters-CzL3H752.d.mts → adapters-BDQwg236.d.mts} +4 -2
  2. package/dist/{adapters-CzL3H752.d.mts.map → adapters-BDQwg236.d.mts.map} +1 -1
  3. package/dist/{allowed-origins-maylGaHv.mjs → allowed-origins-BGglilcs.mjs} +2 -2
  4. package/dist/{allowed-origins-maylGaHv.mjs.map → allowed-origins-BGglilcs.mjs.map} +1 -1
  5. package/dist/api/route-utils.d.mts +3 -3
  6. package/dist/api/route-utils.mjs +17 -17
  7. package/dist/api/schemas/index.d.mts +2 -2
  8. package/dist/api/schemas/index.mjs +5 -5
  9. package/dist/api-Dz6rgoa3.mjs +10040 -0
  10. package/dist/api-Dz6rgoa3.mjs.map +1 -0
  11. package/dist/{api-tokens-9YrrCoE6.mjs → api-tokens-BSsQACNe.mjs} +2 -2
  12. package/dist/{api-tokens-9YrrCoE6.mjs.map → api-tokens-BSsQACNe.mjs.map} +1 -1
  13. package/dist/{apply-B_VMpJ2P.mjs → apply-B4SIOxR1.mjs} +18 -18
  14. package/dist/{apply-B_VMpJ2P.mjs.map → apply-B4SIOxR1.mjs.map} +1 -1
  15. package/dist/astro/image-endpoint.mjs +2 -2
  16. package/dist/astro/image-endpoint.mjs.map +1 -1
  17. package/dist/astro/index.d.mts +12 -12
  18. package/dist/astro/index.d.mts.map +1 -1
  19. package/dist/astro/index.mjs +110 -37
  20. package/dist/astro/index.mjs.map +1 -1
  21. package/dist/astro/middleware/auth.d.mts +10 -10
  22. package/dist/astro/middleware/auth.mjs +6 -6
  23. package/dist/astro/middleware/media-usage-write-fence.mjs +2 -2
  24. package/dist/astro/middleware/redirect.mjs +3 -3
  25. package/dist/astro/middleware/request-context.mjs +2 -2
  26. package/dist/astro/middleware/setup.mjs +1 -1
  27. package/dist/astro/middleware.d.mts +10 -10
  28. package/dist/astro/middleware.d.mts.map +1 -1
  29. package/dist/astro/middleware.mjs +157 -195
  30. package/dist/astro/middleware.mjs.map +1 -1
  31. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +6 -6
  32. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +6 -6
  33. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +3 -3
  34. package/dist/astro/routes/api/admin/api-tokens/index.d.mts.map +1 -1
  35. package/dist/astro/routes/api/admin/api-tokens/index.mjs +5 -5
  36. package/dist/astro/routes/api/admin/api-tokens/index.mjs.map +1 -1
  37. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +3 -3
  38. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +8 -8
  39. package/dist/astro/routes/api/admin/byline-fields/index.mjs +8 -8
  40. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +8 -8
  41. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +14 -14
  42. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +14 -14
  43. package/dist/astro/routes/api/admin/bylines/index.mjs +14 -14
  44. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +12 -12
  45. package/dist/astro/routes/api/admin/comments/_id_.mjs +5 -5
  46. package/dist/astro/routes/api/admin/comments/bulk.mjs +10 -10
  47. package/dist/astro/routes/api/admin/comments/counts.mjs +5 -5
  48. package/dist/astro/routes/api/admin/comments/index.mjs +10 -10
  49. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +3 -3
  50. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +2 -2
  51. package/dist/astro/routes/api/admin/media-usage/activation.mjs +10 -10
  52. package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs +13 -13
  53. package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs +13 -13
  54. package/dist/astro/routes/api/admin/media-usage/progress.mjs +11 -11
  55. package/dist/astro/routes/api/admin/media-usage/repair.mjs +14 -14
  56. package/dist/astro/routes/api/admin/media-usage/work/index.mjs +13 -13
  57. package/dist/astro/routes/api/admin/media-usage/work/retry.mjs +13 -13
  58. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +4 -4
  59. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs.map +1 -1
  60. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +4 -4
  61. package/dist/astro/routes/api/admin/oauth-clients/index.mjs.map +1 -1
  62. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +49 -49
  63. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +49 -49
  64. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +47 -47
  65. package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +4 -4
  66. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +47 -47
  67. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +48 -48
  68. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +48 -48
  69. package/dist/astro/routes/api/admin/plugins/index.mjs +47 -47
  70. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +2 -2
  71. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +47 -47
  72. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +48 -48
  73. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +47 -47
  74. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +48 -48
  75. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.d.mts.map +1 -1
  76. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +54 -50
  77. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
  78. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +17 -16
  79. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
  80. package/dist/astro/routes/api/admin/plugins/registry/install.d.mts.map +1 -1
  81. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +55 -51
  82. package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
  83. package/dist/astro/routes/api/admin/plugins/registry/verify.d.mts +8 -0
  84. package/dist/astro/routes/api/admin/plugins/registry/verify.d.mts.map +1 -0
  85. package/dist/astro/routes/api/admin/plugins/registry/verify.mjs +93 -0
  86. package/dist/astro/routes/api/admin/plugins/registry/verify.mjs.map +1 -0
  87. package/dist/astro/routes/api/admin/plugins/updates.mjs +47 -47
  88. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +47 -47
  89. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +2 -2
  90. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +47 -47
  91. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +1 -1
  92. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +1 -1
  93. package/dist/astro/routes/api/admin/users/_id_/index.mjs +6 -6
  94. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +2 -2
  95. package/dist/astro/routes/api/admin/users/index.mjs +6 -6
  96. package/dist/astro/routes/api/auth/dev-bypass.mjs +3 -3
  97. package/dist/astro/routes/api/auth/invite/accept.mjs +1 -1
  98. package/dist/astro/routes/api/auth/invite/complete.mjs +10 -10
  99. package/dist/astro/routes/api/auth/invite/index.mjs +7 -7
  100. package/dist/astro/routes/api/auth/invite/register-options.mjs +9 -9
  101. package/dist/astro/routes/api/auth/logout.mjs +2 -2
  102. package/dist/astro/routes/api/auth/magic-link/send.mjs +9 -9
  103. package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
  104. package/dist/astro/routes/api/auth/me.mjs +7 -7
  105. package/dist/astro/routes/api/auth/mode.mjs +2 -2
  106. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +3 -3
  107. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
  108. package/dist/astro/routes/api/auth/passkey/_id_.mjs +6 -6
  109. package/dist/astro/routes/api/auth/passkey/index.mjs +1 -1
  110. package/dist/astro/routes/api/auth/passkey/options.mjs +11 -11
  111. package/dist/astro/routes/api/auth/passkey/register/options.mjs +9 -9
  112. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +10 -10
  113. package/dist/astro/routes/api/auth/passkey/verify.mjs +10 -10
  114. package/dist/astro/routes/api/auth/signup/complete.mjs +10 -10
  115. package/dist/astro/routes/api/auth/signup/request.mjs +9 -9
  116. package/dist/astro/routes/api/auth/signup/verify.mjs +1 -1
  117. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +13 -13
  118. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +12 -12
  119. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +2 -2
  120. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.d.mts.map +1 -1
  121. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +14 -5
  122. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs.map +1 -1
  123. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
  124. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +2 -2
  125. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +10 -10
  126. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +13 -10
  127. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
  128. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +15 -15
  129. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs +15 -15
  130. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
  131. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +2 -2
  132. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +8 -8
  133. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +13 -13
  134. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +2 -2
  135. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.d.mts.map +1 -1
  136. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +14 -5
  137. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs.map +1 -1
  138. package/dist/astro/routes/api/content/_collection_/_id_.mjs +8 -8
  139. package/dist/astro/routes/api/content/_collection_/authors.mjs +2 -2
  140. package/dist/astro/routes/api/content/_collection_/index.mjs +8 -8
  141. package/dist/astro/routes/api/content/_collection_/trash.mjs +7 -7
  142. package/dist/astro/routes/api/dashboard.mjs +7 -7
  143. package/dist/astro/routes/api/dev/emails.mjs +2 -2
  144. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  145. package/dist/astro/routes/api/import/probe.mjs +15 -15
  146. package/dist/astro/routes/api/import/wordpress/analyze.mjs +2 -2
  147. package/dist/astro/routes/api/import/wordpress/execute.d.mts +11 -11
  148. package/dist/astro/routes/api/import/wordpress/execute.mjs +11 -11
  149. package/dist/astro/routes/api/import/wordpress/media.d.mts +10 -10
  150. package/dist/astro/routes/api/import/wordpress/media.mjs +9 -9
  151. package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -9
  152. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +11 -11
  153. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +10 -10
  154. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  155. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +15 -15
  156. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +11 -11
  157. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +22 -22
  158. package/dist/astro/routes/api/manifest.mjs +3 -3
  159. package/dist/astro/routes/api/manifest.mjs.map +1 -1
  160. package/dist/astro/routes/api/mcp.mjs +188 -65
  161. package/dist/astro/routes/api/mcp.mjs.map +1 -1
  162. package/dist/astro/routes/api/media/_id_/confirm.mjs +9 -9
  163. package/dist/astro/routes/api/media/_id_/replace.d.mts +8 -0
  164. package/dist/astro/routes/api/media/_id_/replace.d.mts.map +1 -0
  165. package/dist/astro/routes/api/media/_id_/replace.mjs +91 -0
  166. package/dist/astro/routes/api/media/_id_/replace.mjs.map +1 -0
  167. package/dist/astro/routes/api/media/_id_/upload.mjs +5 -5
  168. package/dist/astro/routes/api/media/_id_/usage.mjs +16 -16
  169. package/dist/astro/routes/api/media/_id_.mjs +15 -15
  170. package/dist/astro/routes/api/media/file/_...key_.d.mts.map +1 -1
  171. package/dist/astro/routes/api/media/file/_...key_.mjs +3 -2
  172. package/dist/astro/routes/api/media/file/_...key_.mjs.map +1 -1
  173. package/dist/astro/routes/api/media/folders/_id_.mjs +8 -8
  174. package/dist/astro/routes/api/media/folders/index.mjs +8 -8
  175. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +2 -2
  176. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +2 -2
  177. package/dist/astro/routes/api/media/providers/index.mjs +2 -2
  178. package/dist/astro/routes/api/media/upload-url.mjs +15 -13
  179. package/dist/astro/routes/api/media/upload-url.mjs.map +1 -1
  180. package/dist/astro/routes/api/media.d.mts.map +1 -1
  181. package/dist/astro/routes/api/media.mjs +47 -24
  182. package/dist/astro/routes/api/media.mjs.map +1 -1
  183. package/dist/astro/routes/api/menus/_name_/items/_id_.d.mts.map +1 -1
  184. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +20 -13
  185. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs.map +1 -1
  186. package/dist/astro/routes/api/menus/_name_/items.d.mts.map +1 -1
  187. package/dist/astro/routes/api/menus/_name_/items.mjs +15 -11
  188. package/dist/astro/routes/api/menus/_name_/items.mjs.map +1 -1
  189. package/dist/astro/routes/api/menus/_name_/reorder.d.mts.map +1 -1
  190. package/dist/astro/routes/api/menus/_name_/reorder.mjs +15 -11
  191. package/dist/astro/routes/api/menus/_name_/reorder.mjs.map +1 -1
  192. package/dist/astro/routes/api/menus/_name_/translations.d.mts.map +1 -1
  193. package/dist/astro/routes/api/menus/_name_/translations.mjs +16 -12
  194. package/dist/astro/routes/api/menus/_name_/translations.mjs.map +1 -1
  195. package/dist/astro/routes/api/menus/_name_.d.mts.map +1 -1
  196. package/dist/astro/routes/api/menus/_name_.mjs +21 -14
  197. package/dist/astro/routes/api/menus/_name_.mjs.map +1 -1
  198. package/dist/astro/routes/api/menus/index.d.mts.map +1 -1
  199. package/dist/astro/routes/api/menus/index.mjs +15 -11
  200. package/dist/astro/routes/api/menus/index.mjs.map +1 -1
  201. package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
  202. package/dist/astro/routes/api/oauth/device/authorize.mjs +5 -5
  203. package/dist/astro/routes/api/oauth/device/code.mjs +8 -8
  204. package/dist/astro/routes/api/oauth/device/token.mjs +7 -7
  205. package/dist/astro/routes/api/oauth/register.mjs +2 -2
  206. package/dist/astro/routes/api/oauth/token/refresh.mjs +5 -5
  207. package/dist/astro/routes/api/oauth/token/revoke.mjs +5 -5
  208. package/dist/astro/routes/api/oauth/token.mjs +5 -5
  209. package/dist/astro/routes/api/openapi.json.mjs +104 -38
  210. package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
  211. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +4 -4
  212. package/dist/astro/routes/api/redirects/404s/index.mjs +8 -8
  213. package/dist/astro/routes/api/redirects/404s/summary.mjs +8 -8
  214. package/dist/astro/routes/api/redirects/_id_.mjs +9 -9
  215. package/dist/astro/routes/api/redirects/index.mjs +9 -9
  216. package/dist/astro/routes/api/relations/_id_/index.mjs +15 -15
  217. package/dist/astro/routes/api/relations/_id_/translations.mjs +10 -10
  218. package/dist/astro/routes/api/relations/index.mjs +15 -15
  219. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +2 -2
  220. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
  221. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +47 -47
  222. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +47 -47
  223. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +47 -47
  224. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +47 -47
  225. package/dist/astro/routes/api/schema/collections/index.mjs +47 -47
  226. package/dist/astro/routes/api/schema/collections/reorder.mjs +47 -47
  227. package/dist/astro/routes/api/schema/index.mjs +8 -8
  228. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +47 -47
  229. package/dist/astro/routes/api/schema/orphans/index.mjs +47 -47
  230. package/dist/astro/routes/api/search/enable.mjs +10 -10
  231. package/dist/astro/routes/api/search/index.mjs +9 -9
  232. package/dist/astro/routes/api/search/rebuild.mjs +10 -10
  233. package/dist/astro/routes/api/search/stats.mjs +5 -5
  234. package/dist/astro/routes/api/search/suggest.mjs +9 -9
  235. package/dist/astro/routes/api/sections/_slug_.mjs +9 -9
  236. package/dist/astro/routes/api/sections/index.mjs +9 -9
  237. package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +5 -5
  238. package/dist/astro/routes/api/settings/backups/archives/index.mjs +4 -4
  239. package/dist/astro/routes/api/settings/backups/export.mjs +5 -5
  240. package/dist/astro/routes/api/settings/backups/index.mjs +6 -6
  241. package/dist/astro/routes/api/settings/email.mjs +4 -4
  242. package/dist/astro/routes/api/settings/email.mjs.map +1 -1
  243. package/dist/astro/routes/api/settings.d.mts.map +1 -1
  244. package/dist/astro/routes/api/settings.mjs +18 -14
  245. package/dist/astro/routes/api/settings.mjs.map +1 -1
  246. package/dist/astro/routes/api/setup/admin-verify.mjs +11 -11
  247. package/dist/astro/routes/api/setup/admin.mjs +10 -10
  248. package/dist/astro/routes/api/setup/dev-bypass.mjs +25 -25
  249. package/dist/astro/routes/api/setup/dev-reset.mjs +1 -1
  250. package/dist/astro/routes/api/setup/index.mjs +25 -25
  251. package/dist/astro/routes/api/setup/status.mjs +3 -3
  252. package/dist/astro/routes/api/snapshot.mjs +6 -6
  253. package/dist/astro/routes/api/taxonomies/_name_/reorder.d.mts.map +1 -1
  254. package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs +19 -15
  255. package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs.map +1 -1
  256. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.d.mts.map +1 -1
  257. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +20 -16
  258. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs.map +1 -1
  259. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.d.mts.map +1 -1
  260. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +24 -17
  261. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs.map +1 -1
  262. package/dist/astro/routes/api/taxonomies/_name_/terms/index.d.mts.map +1 -1
  263. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +19 -15
  264. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs.map +1 -1
  265. package/dist/astro/routes/api/taxonomies/_name_/translations.mjs +13 -13
  266. package/dist/astro/routes/api/taxonomies/_name_.d.mts.map +1 -1
  267. package/dist/astro/routes/api/taxonomies/_name_.mjs +25 -18
  268. package/dist/astro/routes/api/taxonomies/_name_.mjs.map +1 -1
  269. package/dist/astro/routes/api/taxonomies/index.d.mts.map +1 -1
  270. package/dist/astro/routes/api/taxonomies/index.mjs +19 -15
  271. package/dist/astro/routes/api/taxonomies/index.mjs.map +1 -1
  272. package/dist/astro/routes/api/themes/preview.mjs +4 -4
  273. package/dist/astro/routes/api/typegen.mjs +4 -4
  274. package/dist/astro/routes/api/well-known/auth.mjs +1 -1
  275. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
  276. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
  277. package/dist/astro/routes/api/widget-areas/_name_/reorder.d.mts.map +1 -1
  278. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +10 -8
  279. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs.map +1 -1
  280. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.d.mts.map +1 -1
  281. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +16 -12
  282. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
  283. package/dist/astro/routes/api/widget-areas/_name_/widgets.d.mts.map +1 -1
  284. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +14 -11
  285. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
  286. package/dist/astro/routes/api/widget-areas/_name_.d.mts.map +1 -1
  287. package/dist/astro/routes/api/widget-areas/_name_.mjs +7 -5
  288. package/dist/astro/routes/api/widget-areas/_name_.mjs.map +1 -1
  289. package/dist/astro/routes/api/widget-areas/index.d.mts.map +1 -1
  290. package/dist/astro/routes/api/widget-areas/index.mjs +14 -11
  291. package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
  292. package/dist/astro/routes/api/widget-components.mjs +2 -2
  293. package/dist/astro/routes/robots.txt.mjs +5 -5
  294. package/dist/astro/routes/sitemap-_collection_.xml.mjs +8 -8
  295. package/dist/astro/routes/sitemap.xml.mjs +6 -6
  296. package/dist/astro/types.d.mts +32 -17
  297. package/dist/astro/types.d.mts.map +1 -1
  298. package/dist/auth/providers/github.d.mts +1 -1
  299. package/dist/auth/providers/google.d.mts +1 -1
  300. package/dist/{authorize-ByYs-Uxs.mjs → authorize-Bkf_UHhD.mjs} +2 -2
  301. package/dist/{authorize-ByYs-Uxs.mjs.map → authorize-Bkf_UHhD.mjs.map} +1 -1
  302. package/dist/{backup-ChyHfIoX.mjs → backup-rwVAl9NI.mjs} +4 -4
  303. package/dist/{backup-ChyHfIoX.mjs.map → backup-rwVAl9NI.mjs.map} +1 -1
  304. package/dist/{bundle-DZFhFurR.mjs → bundle-C8S2Zl6U.mjs} +3 -3
  305. package/dist/{bundle-DZFhFurR.mjs.map → bundle-C8S2Zl6U.mjs.map} +1 -1
  306. package/dist/{byline-Cpnwrje1.mjs → byline-DuhF7NWl.mjs} +25 -12
  307. package/dist/byline-DuhF7NWl.mjs.map +1 -0
  308. package/dist/{byline-fields-CAyA_Umd.mjs → byline-fields-BovY4KYM.mjs} +1 -1
  309. package/dist/{byline-fields-CAyA_Umd.mjs.map → byline-fields-BovY4KYM.mjs.map} +1 -1
  310. package/dist/{bylines-BSo9B9RX.mjs → bylines-CJ5nd6Lw.mjs} +6 -6
  311. package/dist/{bylines-BSo9B9RX.mjs.map → bylines-CJ5nd6Lw.mjs.map} +1 -1
  312. package/dist/{bylines-CHjvehVX.mjs → bylines-RwBAeOF1.mjs} +3 -3
  313. package/dist/{bylines-CHjvehVX.mjs.map → bylines-RwBAeOF1.mjs.map} +1 -1
  314. package/dist/{cache-5VirJ2Cn.mjs → cache-BF5Kvn59.mjs} +2 -2
  315. package/dist/{cache-5VirJ2Cn.mjs.map → cache-BF5Kvn59.mjs.map} +1 -1
  316. package/dist/{challenge-store-D5pSY2S1.mjs → challenge-store-DQN_Yrck.mjs} +5 -3
  317. package/dist/challenge-store-DQN_Yrck.mjs.map +1 -0
  318. package/dist/chrome-tags-D8fiZG94.mjs +24 -0
  319. package/dist/chrome-tags-D8fiZG94.mjs.map +1 -0
  320. package/dist/{chunks-bNiX6_DR.mjs → chunks-C4szrD8o.mjs} +2 -2
  321. package/dist/{chunks-bNiX6_DR.mjs.map → chunks-C4szrD8o.mjs.map} +1 -1
  322. package/dist/cli/index.mjs +26 -26
  323. package/dist/client/cf-access.d.mts +1 -1
  324. package/dist/client/index.d.mts +2 -2
  325. package/dist/client/index.mjs +2 -2
  326. package/dist/{collection-deletion-m_oi2c0N.mjs → collection-deletion-Bj07NHbj.mjs} +2 -2
  327. package/dist/{collection-deletion-m_oi2c0N.mjs.map → collection-deletion-Bj07NHbj.mjs.map} +1 -1
  328. package/dist/{comment-reaction-CRuItEjt.mjs → comment-reaction-BhrvSHml.mjs} +2 -2
  329. package/dist/{comment-reaction-CRuItEjt.mjs.map → comment-reaction-BhrvSHml.mjs.map} +1 -1
  330. package/dist/{comment-DhvLEdhQ.mjs → comment-vxOzWbZN.mjs} +2 -2
  331. package/dist/{comment-DhvLEdhQ.mjs.map → comment-vxOzWbZN.mjs.map} +1 -1
  332. package/dist/{comments-CLj4bi8I.mjs → comments-Ct4ensnf.mjs} +2 -2
  333. package/dist/{comments-CLj4bi8I.mjs.map → comments-Ct4ensnf.mjs.map} +1 -1
  334. package/dist/{components-v4U4DT2y.mjs → components-DJbo1z0S.mjs} +1 -1
  335. package/dist/{components-v4U4DT2y.mjs.map → components-DJbo1z0S.mjs.map} +1 -1
  336. package/dist/{config-DrfghTPe.d.mts → config-B9eZ340f.d.mts} +1 -1
  337. package/dist/{config-DrfghTPe.d.mts.map → config-B9eZ340f.d.mts.map} +1 -1
  338. package/dist/config-it29yDLA.mjs +1163 -0
  339. package/dist/config-it29yDLA.mjs.map +1 -0
  340. package/dist/{content-CP-SeXNa.mjs → content-DjvM0T92.mjs} +84 -44
  341. package/dist/content-DjvM0T92.mjs.map +1 -0
  342. package/dist/{content-list-query-BhejKVqb.d.mts → content-list-query-Btqd4uV2.d.mts} +1 -1
  343. package/dist/{content-list-query-BhejKVqb.d.mts.map → content-list-query-Btqd4uV2.d.mts.map} +1 -1
  344. package/dist/{content-refresh-DLOfIhYc.mjs → content-refresh-Bwp-8yGy.mjs} +48 -21
  345. package/dist/content-refresh-Bwp-8yGy.mjs.map +1 -0
  346. package/dist/{content-repair-BCxzy0r4.mjs → content-repair-Czdp7jz5.mjs} +4 -4
  347. package/dist/{content-repair-BCxzy0r4.mjs.map → content-repair-Czdp7jz5.mjs.map} +1 -1
  348. package/dist/{context-BR5hRWiK.mjs → context-CX6gxOLW.mjs} +52 -17
  349. package/dist/context-CX6gxOLW.mjs.map +1 -0
  350. package/dist/{cron-CWfWrWKG.mjs → cron-C6o7VppJ.mjs} +1 -1
  351. package/dist/{cron-CWfWrWKG.mjs.map → cron-C6o7VppJ.mjs.map} +1 -1
  352. package/dist/{dashboard-CkIV2rnQ.mjs → dashboard-C2kTeeRI.mjs} +4 -4
  353. package/dist/{dashboard-CkIV2rnQ.mjs.map → dashboard-C2kTeeRI.mjs.map} +1 -1
  354. package/dist/db/index.d.mts +3 -3
  355. package/dist/db/index.mjs +1 -1
  356. package/dist/db/index.mjs.map +1 -1
  357. package/dist/db/libsql-migrations.d.mts +1 -1
  358. package/dist/db/libsql-migrations.mjs +4 -4
  359. package/dist/db/libsql.d.mts +1 -1
  360. package/dist/db/postgres-migrations.d.mts +1 -1
  361. package/dist/db/postgres-migrations.mjs +4 -4
  362. package/dist/db/postgres.d.mts +1 -1
  363. package/dist/db/postgres.d.mts.map +1 -1
  364. package/dist/db/postgres.mjs +19 -3
  365. package/dist/db/postgres.mjs.map +1 -1
  366. package/dist/db/sqlite-migrations.d.mts +1 -1
  367. package/dist/db/sqlite-migrations.mjs +4 -4
  368. package/dist/db/sqlite.d.mts +1 -1
  369. package/dist/db/sqlite.mjs +1 -1
  370. package/dist/{default-YMEfcQUx.mjs → default-Clra32mw.mjs} +1 -1
  371. package/dist/{default-YMEfcQUx.mjs.map → default-Clra32mw.mjs.map} +1 -1
  372. package/dist/{deferred-tasks-DfqxH5Xf.d.mts → deferred-tasks-CvDETTJO.d.mts} +1 -1
  373. package/dist/{deferred-tasks-DfqxH5Xf.d.mts.map → deferred-tasks-CvDETTJO.d.mts.map} +1 -1
  374. package/dist/dev-typegen-DzOPfsNj.mjs +29 -0
  375. package/dist/dev-typegen-DzOPfsNj.mjs.map +1 -0
  376. package/dist/{device-flow-CojjSBwb.mjs → device-flow-D2oCWvLJ.mjs} +4 -4
  377. package/dist/{device-flow-CojjSBwb.mjs.map → device-flow-D2oCWvLJ.mjs.map} +1 -1
  378. package/dist/{direct-executor-BWeWiRYB.mjs → direct-executor-Bfa5_ToE.mjs} +3 -3
  379. package/dist/{direct-executor-BWeWiRYB.mjs.map → direct-executor-Bfa5_ToE.mjs.map} +1 -1
  380. package/dist/{email-console-CEJgR_LB.mjs → email-console-Dzb6uGYa.mjs} +1 -1
  381. package/dist/{email-console-CEJgR_LB.mjs.map → email-console-Dzb6uGYa.mjs.map} +1 -1
  382. package/dist/{error-D9w94fjV.mjs → error-DFrQjppn.mjs} +2 -2
  383. package/dist/{error-D9w94fjV.mjs.map → error-DFrQjppn.mjs.map} +1 -1
  384. package/dist/{errors-n2j49uNf.mjs → errors-DsVoDUjJ.mjs} +3 -1
  385. package/dist/errors-DsVoDUjJ.mjs.map +1 -0
  386. package/dist/{escape-DN4Qmjsl.mjs → escape-HmHTC1Is.mjs} +1 -1
  387. package/dist/{escape-DN4Qmjsl.mjs.map → escape-HmHTC1Is.mjs.map} +1 -1
  388. package/dist/{field-defs-cache-Bh4q6oar.mjs → field-defs-cache-BM4SIRqH.mjs} +2 -2
  389. package/dist/{field-defs-cache-Bh4q6oar.mjs.map → field-defs-cache-BM4SIRqH.mjs.map} +1 -1
  390. package/dist/{fts-manager-BhOdW0bi.mjs → fts-manager-Do1BlW2r.mjs} +2 -2
  391. package/dist/{fts-manager-BhOdW0bi.mjs.map → fts-manager-Do1BlW2r.mjs.map} +1 -1
  392. package/dist/{hash-CTcngWvj.mjs → hash-3JJPlHF2.mjs} +1 -1
  393. package/dist/{hash-CTcngWvj.mjs.map → hash-3JJPlHF2.mjs.map} +1 -1
  394. package/dist/hook-result-DCh2WUpe.d.mts +31 -0
  395. package/dist/hook-result-DCh2WUpe.d.mts.map +1 -0
  396. package/dist/{hreflang-7pTHsLZJ.d.mts → hreflang-B6hbAuuO.d.mts} +2 -2
  397. package/dist/{hreflang-7pTHsLZJ.d.mts.map → hreflang-B6hbAuuO.d.mts.map} +1 -1
  398. package/dist/{hreflang-Huc6O5wa.mjs → hreflang-DQbPZliA.mjs} +6 -6
  399. package/dist/{hreflang-Huc6O5wa.mjs.map → hreflang-DQbPZliA.mjs.map} +1 -1
  400. package/dist/{identity-DXmb4aVp.mjs → identity-DksvEazf.mjs} +3 -3
  401. package/dist/{identity-DXmb4aVp.mjs.map → identity-DksvEazf.mjs.map} +1 -1
  402. package/dist/{import-C4_O3KXU.mjs → import-CJpaBBBT.mjs} +5 -5
  403. package/dist/{import-C4_O3KXU.mjs.map → import-CJpaBBBT.mjs.map} +1 -1
  404. package/dist/{index-DWbJrsAg.d.mts → index-BJNMCWmu.d.mts} +158 -49
  405. package/dist/index-BJNMCWmu.d.mts.map +1 -0
  406. package/dist/{index-6bRee8zb.d.mts → index-Du8K7bWa.d.mts} +3 -3
  407. package/dist/{index-6bRee8zb.d.mts.map → index-Du8K7bWa.d.mts.map} +1 -1
  408. package/dist/index.d.mts +22 -21
  409. package/dist/index.mjs +70 -70
  410. package/dist/{load-Bo7fZAbg.mjs → load-CawHjTOH.mjs} +2 -2
  411. package/dist/{load-Bo7fZAbg.mjs.map → load-CawHjTOH.mjs.map} +1 -1
  412. package/dist/{loader-ZsjE4paL.mjs → loader-Z8vl7zTq.mjs} +48 -13
  413. package/dist/loader-Z8vl7zTq.mjs.map +1 -0
  414. package/dist/{manifest-builder-QXuqvVMP.mjs → manifest-builder-COtzuca2.mjs} +2 -2
  415. package/dist/{manifest-builder-QXuqvVMP.mjs.map → manifest-builder-COtzuca2.mjs.map} +1 -1
  416. package/dist/{manifest-schema-CUYPfCEH.mjs → manifest-schema-BNWwR8Vd.mjs} +5 -5
  417. package/dist/manifest-schema-BNWwR8Vd.mjs.map +1 -0
  418. package/dist/media/image-endpoint.d.mts +4 -2
  419. package/dist/media/image-endpoint.d.mts.map +1 -1
  420. package/dist/media/image-endpoint.mjs +6 -4
  421. package/dist/media/image-endpoint.mjs.map +1 -1
  422. package/dist/media/index.d.mts +1 -1
  423. package/dist/media/index.mjs +1 -1
  424. package/dist/media/local-runtime.d.mts +12 -12
  425. package/dist/media/local-runtime.mjs +4 -4
  426. package/dist/{media-D6SeAfBl.mjs → media-BBm8Wg3f.mjs} +30 -2
  427. package/dist/media-BBm8Wg3f.mjs.map +1 -0
  428. package/dist/{media-BjwNuLEu.mjs → media-BHjus86Y.mjs} +35 -5
  429. package/dist/media-BHjus86Y.mjs.map +1 -0
  430. package/dist/{media-allowlist-D35v7bmL.mjs → media-allowlist-CoTBWoDp.mjs} +1 -1
  431. package/dist/{media-allowlist-D35v7bmL.mjs.map → media-allowlist-CoTBWoDp.mjs.map} +1 -1
  432. package/dist/{media-folders-D-0CmRZj.mjs → media-folders-BGkATs8n.mjs} +1 -1
  433. package/dist/{media-folders-D-0CmRZj.mjs.map → media-folders-BGkATs8n.mjs.map} +1 -1
  434. package/dist/{media-upload-C7bLXGcI.mjs → media-upload-BCidR-EY.mjs} +6 -6
  435. package/dist/{media-upload-C7bLXGcI.mjs.map → media-upload-BCidR-EY.mjs.map} +1 -1
  436. package/dist/{media-url-DH7giMx_.mjs → media-url-DHoPr0hq.mjs} +1 -1
  437. package/dist/{media-url-DH7giMx_.mjs.map → media-url-DHoPr0hq.mjs.map} +1 -1
  438. package/dist/{media-usage-BTRJvS6V.mjs → media-usage-CdY5jhSR.mjs} +8 -8
  439. package/dist/{media-usage-BTRJvS6V.mjs.map → media-usage-CdY5jhSR.mjs.map} +1 -1
  440. package/dist/{media-usage-activation-Dx0kPV6b.mjs → media-usage-activation-BgeMtK5C.mjs} +2 -2
  441. package/dist/{media-usage-activation-Dx0kPV6b.mjs.map → media-usage-activation-BgeMtK5C.mjs.map} +1 -1
  442. package/dist/{media-usage-work-DWb2XPMI.mjs → media-usage-work-CnGCdHxx.mjs} +4 -4
  443. package/dist/{media-usage-work-DWb2XPMI.mjs.map → media-usage-work-CnGCdHxx.mjs.map} +1 -1
  444. package/dist/{media-usage-work-C-fhM30c.mjs → media-usage-work-D6D9sMCA.mjs} +1 -1
  445. package/dist/{media-usage-work-C-fhM30c.mjs.map → media-usage-work-D6D9sMCA.mjs.map} +1 -1
  446. package/dist/{media-usage-write-fence-BwoJVRDC.mjs → media-usage-write-fence-hjeD4u7s.mjs} +2 -2
  447. package/dist/{media-usage-write-fence-BwoJVRDC.mjs.map → media-usage-write-fence-hjeD4u7s.mjs.map} +1 -1
  448. package/dist/{menus-CNg0QlDt.mjs → menus-6qSwTNxG.mjs} +346 -94
  449. package/dist/menus-6qSwTNxG.mjs.map +1 -0
  450. package/dist/{menus-CD7EFLwz.mjs → menus-uvPLXMZf.mjs} +3 -3
  451. package/dist/{menus-CD7EFLwz.mjs.map → menus-uvPLXMZf.mjs.map} +1 -1
  452. package/dist/migrations/index.d.mts +1 -1
  453. package/dist/migrations/index.mjs +3 -3
  454. package/dist/{mode-DaUM_MB2.mjs → mode-DN_WneRx.mjs} +1 -1
  455. package/dist/{mode-DaUM_MB2.mjs.map → mode-DN_WneRx.mjs.map} +1 -1
  456. package/dist/{node-sqlite-compat-Udb4Y-rs.mjs → node-sqlite-compat-BuJXIeMz.mjs} +1 -1
  457. package/dist/{node-sqlite-compat-Udb4Y-rs.mjs.map → node-sqlite-compat-BuJXIeMz.mjs.map} +1 -1
  458. package/dist/{normalize-CSxdKBVa.mjs → normalize-C2xNjTSG.mjs} +15 -5
  459. package/dist/normalize-C2xNjTSG.mjs.map +1 -0
  460. package/dist/{oauth-authorization-Dpsb-fMJ.mjs → oauth-authorization-Dj6Kwc21.mjs} +4 -4
  461. package/dist/{oauth-authorization-Dpsb-fMJ.mjs.map → oauth-authorization-Dj6Kwc21.mjs.map} +1 -1
  462. package/dist/{oauth-clients-Bxnoe_67.mjs → oauth-clients-FD7I0J8m.mjs} +1 -1
  463. package/dist/{oauth-clients-Bxnoe_67.mjs.map → oauth-clients-FD7I0J8m.mjs.map} +1 -1
  464. package/dist/{oauth-state-store-0C4s0K6w.mjs → oauth-state-store-DkmDOmHs.mjs} +1 -1
  465. package/dist/{oauth-state-store-0C4s0K6w.mjs.map → oauth-state-store-DkmDOmHs.mjs.map} +1 -1
  466. package/dist/{oauth-user-lookup-wlGYSeqX.mjs → oauth-user-lookup-CvIwGpYd.mjs} +1 -1
  467. package/dist/{oauth-user-lookup-wlGYSeqX.mjs.map → oauth-user-lookup-CvIwGpYd.mjs.map} +1 -1
  468. package/dist/object-cache/memory.d.mts +1 -1
  469. package/dist/{object-cache-71tPfV4A.mjs → object-cache-DVKnyOfy.mjs} +19 -3
  470. package/dist/object-cache-DVKnyOfy.mjs.map +1 -0
  471. package/dist/{options-BhZIeFY1.d.mts → options-CYb4y7hM.d.mts} +24 -11
  472. package/dist/options-CYb4y7hM.d.mts.map +1 -0
  473. package/dist/page/index.d.mts +2 -2
  474. package/dist/{parse-BRJg1kz8.mjs → parse-Ckpg-Qfp.mjs} +2 -2
  475. package/dist/{parse-BRJg1kz8.mjs.map → parse-Ckpg-Qfp.mjs.map} +1 -1
  476. package/dist/{passkey-config-B3P0nHRR.mjs → passkey-config-O_lj3y0k.mjs} +1 -1
  477. package/dist/{passkey-config-B3P0nHRR.mjs.map → passkey-config-O_lj3y0k.mjs.map} +1 -1
  478. package/dist/{placeholder-BR4ZhVJl.d.mts → placeholder-4X9BrRrp.d.mts} +1 -1
  479. package/dist/{placeholder-BR4ZhVJl.d.mts.map → placeholder-4X9BrRrp.d.mts.map} +1 -1
  480. package/dist/plugin-types.d.mts +8 -3
  481. package/dist/plugin-types.d.mts.map +1 -1
  482. package/dist/plugin-utils.d.mts +10 -10
  483. package/dist/plugins/adapt-sandbox-entry.d.mts +10 -10
  484. package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
  485. package/dist/{policy-BVRganJQ.mjs → policy-Dhtwntrt.mjs} +2 -2
  486. package/dist/{policy-BVRganJQ.mjs.map → policy-Dhtwntrt.mjs.map} +1 -1
  487. package/dist/{portable-text-C8Tvp4_g.mjs → portable-text-U85hOafW.mjs} +1 -1
  488. package/dist/{portable-text-C8Tvp4_g.mjs.map → portable-text-U85hOafW.mjs.map} +1 -1
  489. package/dist/{preview-dn2dUyM0.mjs → preview-B_vJLvf8.mjs} +2 -2
  490. package/dist/{preview-dn2dUyM0.mjs.map → preview-B_vJLvf8.mjs.map} +1 -1
  491. package/dist/{protocol-CuBEsqgE.d.mts → protocol-JdD2YhY9.d.mts} +2 -2
  492. package/dist/{protocol-CuBEsqgE.d.mts.map → protocol-JdD2YhY9.d.mts.map} +1 -1
  493. package/dist/{public-url-DRz_AinV.mjs → public-url-CarhZJgf.mjs} +1 -1
  494. package/dist/{public-url-DRz_AinV.mjs.map → public-url-CarhZJgf.mjs.map} +1 -1
  495. package/dist/{query-D18vTdha.mjs → query-BZKZcG5b.mjs} +14 -14
  496. package/dist/{query-D18vTdha.mjs.map → query-BZKZcG5b.mjs.map} +1 -1
  497. package/dist/{query-tZDEdtCN.mjs → query-BndaFxxp.mjs} +4 -4
  498. package/dist/{query-tZDEdtCN.mjs.map → query-BndaFxxp.mjs.map} +1 -1
  499. package/dist/{rate-limit-on7KPo-v.mjs → rate-limit-BZmATC6W.mjs} +2 -2
  500. package/dist/{rate-limit-on7KPo-v.mjs.map → rate-limit-BZmATC6W.mjs.map} +1 -1
  501. package/dist/{redirect-DrbEmU9a.mjs → redirect-CzqnLxK-.mjs} +1 -1
  502. package/dist/{redirect-DrbEmU9a.mjs.map → redirect-CzqnLxK-.mjs.map} +1 -1
  503. package/dist/{redirects-CRIM55KH.mjs → redirects-BLNOWQTn.mjs} +63 -28
  504. package/dist/redirects-BLNOWQTn.mjs.map +1 -0
  505. package/dist/{redirects-BahWiu8Z.mjs → redirects-CqzNM0KM.mjs} +2 -2
  506. package/dist/{redirects-BahWiu8Z.mjs.map → redirects-CqzNM0KM.mjs.map} +1 -1
  507. package/dist/{registry-Cx-jEJOS.mjs → registry-AZAsRsor.mjs} +27 -9
  508. package/dist/registry-AZAsRsor.mjs.map +1 -0
  509. package/dist/{relation-A6wLF_DS.mjs → relation-04DfKRTO.mjs} +2 -2
  510. package/dist/{relation-A6wLF_DS.mjs.map → relation-04DfKRTO.mjs.map} +1 -1
  511. package/dist/{relations-BTPM2saV.mjs → relations-C4htk5sb.mjs} +7 -7
  512. package/dist/relations-C4htk5sb.mjs.map +1 -0
  513. package/dist/{relations-Cj5hZ6Zg.mjs → relations-CcFwDJdJ.mjs} +4 -4
  514. package/dist/{relations-Cj5hZ6Zg.mjs.map → relations-CcFwDJdJ.mjs.map} +1 -1
  515. package/dist/{relations-C7B46bcU.d.mts → relations-DnRK4YIT.d.mts} +175 -41
  516. package/dist/relations-DnRK4YIT.d.mts.map +1 -0
  517. package/dist/request-context.d.mts +1 -1
  518. package/dist/{request-meta-DRp5ENd_.mjs → request-meta-DYxLlnmz.mjs} +2 -2
  519. package/dist/{request-meta-DRp5ENd_.mjs.map → request-meta-DYxLlnmz.mjs.map} +1 -1
  520. package/dist/{resolve-DxUpTMmE.mjs → resolve-prZ3mP7l.mjs} +1 -1
  521. package/dist/{resolve-DxUpTMmE.mjs.map → resolve-prZ3mP7l.mjs.map} +1 -1
  522. package/dist/{runner-77GOZCnH.mjs → runner-BLIDKpmA.mjs} +351 -294
  523. package/dist/runner-BLIDKpmA.mjs.map +1 -0
  524. package/dist/{runner-zgqA_CqP.d.mts → runner-YKfIMnaF.d.mts} +3 -2
  525. package/dist/runner-YKfIMnaF.d.mts.map +1 -0
  526. package/dist/runtime.d.mts +11 -11
  527. package/dist/runtime.mjs +1 -1
  528. package/dist/{schema-BY-oVntF.mjs → schema-CGnBeLiW.mjs} +3 -3
  529. package/dist/{schema-BY-oVntF.mjs.map → schema-CGnBeLiW.mjs.map} +1 -1
  530. package/dist/{schema-Cw9kVK0I.mjs → schema-DYr-Vd-L.mjs} +5 -5
  531. package/dist/{schema-Cw9kVK0I.mjs.map → schema-DYr-Vd-L.mjs.map} +1 -1
  532. package/dist/{scopes-vMR1uIbt.mjs → scopes-BhWvBavj.mjs} +3 -3
  533. package/dist/{scopes-vMR1uIbt.mjs.map → scopes-BhWvBavj.mjs.map} +1 -1
  534. package/dist/{search-CfT3KxO5.mjs → search-C-qDk4kx.mjs} +4 -4
  535. package/dist/{search-CfT3KxO5.mjs.map → search-C-qDk4kx.mjs.map} +1 -1
  536. package/dist/{secrets-BH2Qow1-.mjs → secrets-rEtHt-6z.mjs} +1 -1
  537. package/dist/{secrets-BH2Qow1-.mjs.map → secrets-rEtHt-6z.mjs.map} +1 -1
  538. package/dist/{sections-DTfjyf1z.mjs → sections-B_nYvlwn.mjs} +2 -2
  539. package/dist/{sections-DTfjyf1z.mjs.map → sections-B_nYvlwn.mjs.map} +1 -1
  540. package/dist/seed/index.d.mts +2 -2
  541. package/dist/seed/index.mjs +18 -18
  542. package/dist/seo/index.d.mts +3 -3
  543. package/dist/seo/index.mjs +13 -13
  544. package/dist/{seo-Cm-dtvgk.mjs → seo-926NRIgt.mjs} +3 -3
  545. package/dist/{seo-Cm-dtvgk.mjs.map → seo-926NRIgt.mjs.map} +1 -1
  546. package/dist/{seo-BSiTIXzd.mjs → seo-CVWkdv6x.mjs} +1 -1
  547. package/dist/{seo-BSiTIXzd.mjs.map → seo-CVWkdv6x.mjs.map} +1 -1
  548. package/dist/{service-DYsDHEKe.mjs → service-CJl3ZAYs.mjs} +3 -3
  549. package/dist/{service-DYsDHEKe.mjs.map → service-CJl3ZAYs.mjs.map} +1 -1
  550. package/dist/{session-user-CNAfvsdw.mjs → session-user-BlMgqCHS.mjs} +1 -1
  551. package/dist/{session-user-CNAfvsdw.mjs.map → session-user-BlMgqCHS.mjs.map} +1 -1
  552. package/dist/{settings-BgN-0DT-.mjs → settings--wobrkkC.mjs} +18 -5
  553. package/dist/settings--wobrkkC.mjs.map +1 -0
  554. package/dist/{settings-DjtkUo__.mjs → settings-Cwm6o2jY.mjs} +3 -3
  555. package/dist/{settings-DjtkUo__.mjs.map → settings-Cwm6o2jY.mjs.map} +1 -1
  556. package/dist/{setup-complete-CcpXaPU7.mjs → setup-complete-DQbClIN2.mjs} +1 -1
  557. package/dist/{setup-complete-CcpXaPU7.mjs.map → setup-complete-DQbClIN2.mjs.map} +1 -1
  558. package/dist/{setup-nonce-Reyw30jg.mjs → setup-nonce-DZCFS2sn.mjs} +1 -1
  559. package/dist/{setup-nonce-Reyw30jg.mjs.map → setup-nonce-DZCFS2sn.mjs.map} +1 -1
  560. package/dist/{site-url-Cs_V9n3V.mjs → site-url-BrxA_E9H.mjs} +2 -2
  561. package/dist/{site-url-Cs_V9n3V.mjs.map → site-url-BrxA_E9H.mjs.map} +1 -1
  562. package/dist/{snapshot-DSB8P7Lm.mjs → snapshot--OCDTqdb.mjs} +1 -1
  563. package/dist/{snapshot-DSB8P7Lm.mjs.map → snapshot--OCDTqdb.mjs.map} +1 -1
  564. package/dist/{ssrf-DqebVE34.mjs → ssrf-5sIM5jzl.mjs} +4 -3
  565. package/dist/{ssrf-DqebVE34.mjs.map → ssrf-5sIM5jzl.mjs.map} +1 -1
  566. package/dist/{state-BJfU9Uzw.mjs → state-BpFVsJZB.mjs} +1 -1
  567. package/dist/{state-BJfU9Uzw.mjs.map → state-BpFVsJZB.mjs.map} +1 -1
  568. package/dist/{status-DDu8HIQl.mjs → status-B777-dXz.mjs} +1 -1
  569. package/dist/{status-DDu8HIQl.mjs.map → status-B777-dXz.mjs.map} +1 -1
  570. package/dist/storage/local.d.mts +2 -1
  571. package/dist/storage/local.d.mts.map +1 -1
  572. package/dist/storage/local.mjs +1 -1
  573. package/dist/storage/local.mjs.map +1 -1
  574. package/dist/storage/s3.d.mts +2 -1
  575. package/dist/storage/s3.d.mts.map +1 -1
  576. package/dist/storage/s3.mjs +3 -2
  577. package/dist/storage/s3.mjs.map +1 -1
  578. package/dist/{target-D0o26Azo.mjs → target-BmR0iTwz.mjs} +1 -1
  579. package/dist/{target-D0o26Azo.mjs.map → target-BmR0iTwz.mjs.map} +1 -1
  580. package/dist/{taxonomies-CPGxo-bn.mjs → taxonomies-DkAkMupr.mjs} +16 -13
  581. package/dist/taxonomies-DkAkMupr.mjs.map +1 -0
  582. package/dist/{taxonomies-kmyudNGP.mjs → taxonomies-Wfy_Lg12.mjs} +42 -16
  583. package/dist/taxonomies-Wfy_Lg12.mjs.map +1 -0
  584. package/dist/{taxonomy-8X09dLN5.mjs → taxonomy-tJepplLL.mjs} +4 -4
  585. package/dist/{taxonomy-8X09dLN5.mjs.map → taxonomy-tJepplLL.mjs.map} +1 -1
  586. package/dist/{tokens-gTS-IVUp.mjs → tokens-BCO1oruF.mjs} +1 -1
  587. package/dist/{tokens-gTS-IVUp.mjs.map → tokens-BCO1oruF.mjs.map} +1 -1
  588. package/dist/{transport-fX59xaJz.mjs → transport-Dr3qtwXC.mjs} +1 -1
  589. package/dist/{transport-fX59xaJz.mjs.map → transport-Dr3qtwXC.mjs.map} +1 -1
  590. package/dist/{transport-_6R1ub7t.d.mts → transport-xh6YKW2G.d.mts} +1 -1
  591. package/dist/{transport-_6R1ub7t.d.mts.map → transport-xh6YKW2G.d.mts.map} +1 -1
  592. package/dist/{trusted-proxy-fe6nEgR4.mjs → trusted-proxy-BR5F1Otp.mjs} +1 -1
  593. package/dist/{trusted-proxy-fe6nEgR4.mjs.map → trusted-proxy-BR5F1Otp.mjs.map} +1 -1
  594. package/dist/{types-BXUmsINL.mjs → types-3qNFPlci.mjs} +1 -1
  595. package/dist/{types-BXUmsINL.mjs.map → types-3qNFPlci.mjs.map} +1 -1
  596. package/dist/{types-Vg6f5i3s.d.mts → types-B6GrPic4.d.mts} +9 -3
  597. package/dist/{types-Vg6f5i3s.d.mts.map → types-B6GrPic4.d.mts.map} +1 -1
  598. package/dist/{types-DJQxln7q.d.mts → types-BuHW4xDE.d.mts} +11 -10
  599. package/dist/types-BuHW4xDE.d.mts.map +1 -0
  600. package/dist/{types-BzBd3PeX.d.mts → types-C00oyCZp.d.mts} +1 -1
  601. package/dist/{types-BzBd3PeX.d.mts.map → types-C00oyCZp.d.mts.map} +1 -1
  602. package/dist/{types-Blb-MRC3.d.mts → types-CDtkNSFQ.d.mts} +1 -1
  603. package/dist/{types-Blb-MRC3.d.mts.map → types-CDtkNSFQ.d.mts.map} +1 -1
  604. package/dist/{types-D5VVLr7J.d.mts → types-CPeXo1wF.d.mts} +1 -1
  605. package/dist/{types-D5VVLr7J.d.mts.map → types-CPeXo1wF.d.mts.map} +1 -1
  606. package/dist/types-CQPWhPxE.mjs.map +1 -1
  607. package/dist/{types-D_DWe7k9.d.mts → types-CXFkwEGC.d.mts} +3 -2
  608. package/dist/types-CXFkwEGC.d.mts.map +1 -0
  609. package/dist/{types-CLqvnd05.d.mts → types-DczELMop.d.mts} +1 -1
  610. package/dist/{types-CLqvnd05.d.mts.map → types-DczELMop.d.mts.map} +1 -1
  611. package/dist/types-DfuTR6dL.mjs +3 -0
  612. package/dist/{types-DHxzBjbD.d.mts → types-FV23hEoW.d.mts} +1 -1
  613. package/dist/{types-DHxzBjbD.d.mts.map → types-FV23hEoW.d.mts.map} +1 -1
  614. package/dist/{types-Br-ERaxh.d.mts → types-xDMtUFnO.d.mts} +1 -1
  615. package/dist/{types-Br-ERaxh.d.mts.map → types-xDMtUFnO.d.mts.map} +1 -1
  616. package/dist/{upload-attempts-CNyGSepg.mjs → upload-attempts-BsqoQ1fn.mjs} +1 -1
  617. package/dist/{upload-attempts-CNyGSepg.mjs.map → upload-attempts-BsqoQ1fn.mjs.map} +1 -1
  618. package/dist/{url-pattern-K-KdXcSe.mjs → url-pattern-D4c2czpJ.mjs} +1 -1
  619. package/dist/{url-pattern-K-KdXcSe.mjs.map → url-pattern-D4c2czpJ.mjs.map} +1 -1
  620. package/dist/{user-CtYY0tp2.mjs → user-BTZ40aq6.mjs} +2 -2
  621. package/dist/{user-CtYY0tp2.mjs.map → user-BTZ40aq6.mjs.map} +1 -1
  622. package/dist/{utils-HztC73hq.mjs → utils-BKvNjjhY.mjs} +1 -1
  623. package/dist/{utils-HztC73hq.mjs.map → utils-BKvNjjhY.mjs.map} +1 -1
  624. package/dist/{validate-Dd-IUjnZ.mjs → validate-DE_LRwo9.mjs} +2 -2
  625. package/dist/{validate-Dd-IUjnZ.mjs.map → validate-DE_LRwo9.mjs.map} +1 -1
  626. package/dist/{validate-DlqdOZXB.d.mts → validate-DUP27OYD.d.mts} +5 -5
  627. package/dist/{validate-DlqdOZXB.d.mts.map → validate-DUP27OYD.d.mts.map} +1 -1
  628. package/dist/{validation-D83n7Npi.mjs → validation-BMImFJ11.mjs} +8 -8
  629. package/dist/{validation-D83n7Npi.mjs.map → validation-BMImFJ11.mjs.map} +1 -1
  630. package/dist/version-Bii3suaO.mjs +7 -0
  631. package/dist/{version-ri-sCyAp.mjs.map → version-Bii3suaO.mjs.map} +1 -1
  632. package/dist/{widgets-CpmZd9Q-.mjs → widgets-D3gJ10xU.mjs} +17 -4
  633. package/dist/widgets-D3gJ10xU.mjs.map +1 -0
  634. package/dist/{wxr-taxonomies-BFd0y72_.d.mts → wxr-taxonomies-B2Og_6rU.d.mts} +1 -1
  635. package/dist/{wxr-taxonomies-BFd0y72_.d.mts.map → wxr-taxonomies-B2Og_6rU.d.mts.map} +1 -1
  636. package/dist/{wxr-taxonomies-Df4vxWcU.mjs → wxr-taxonomies-Cxngsjwo.mjs} +4 -4
  637. package/dist/{wxr-taxonomies-Df4vxWcU.mjs.map → wxr-taxonomies-Cxngsjwo.mjs.map} +1 -1
  638. package/dist/{zod-generator-DylzusHh.mjs → zod-generator-BuYSQJB7.mjs} +11 -7
  639. package/dist/zod-generator-BuYSQJB7.mjs.map +1 -0
  640. package/package.json +12 -9
  641. package/src/api/errors.ts +2 -0
  642. package/src/api/handlers/content.ts +49 -11
  643. package/src/api/handlers/index.ts +1 -0
  644. package/src/api/handlers/manifest.ts +161 -12
  645. package/src/api/handlers/media.ts +30 -0
  646. package/src/api/handlers/registry.ts +393 -289
  647. package/src/api/handlers/taxonomies.ts +16 -9
  648. package/src/api/openapi/document.ts +85 -5
  649. package/src/api/parse.ts +1 -1
  650. package/src/api/schemas/auth.ts +4 -4
  651. package/src/api/schemas/bylines.ts +3 -3
  652. package/src/api/schemas/comments.ts +1 -1
  653. package/src/api/schemas/common.ts +3 -2
  654. package/src/api/schemas/content.ts +78 -22
  655. package/src/api/schemas/media.ts +52 -0
  656. package/src/api/schemas/redirects.ts +6 -1
  657. package/src/api/schemas/schema.ts +4 -0
  658. package/src/api/schemas/setup.ts +1 -1
  659. package/src/api/schemas/taxonomies.ts +2 -2
  660. package/src/api/schemas/users.ts +1 -1
  661. package/src/api/types.ts +23 -10
  662. package/src/astro/dev-typegen.ts +35 -0
  663. package/src/astro/image-endpoint.ts +2 -2
  664. package/src/astro/integration/dev-typegen.ts +91 -0
  665. package/src/astro/integration/index.ts +9 -44
  666. package/src/astro/integration/routes.ts +5 -0
  667. package/src/astro/integration/vite-config.ts +21 -2
  668. package/src/astro/middleware.ts +5 -0
  669. package/src/astro/routes/api/admin/api-tokens/index.ts +4 -1
  670. package/src/astro/routes/api/admin/oauth-clients/[id].ts +1 -1
  671. package/src/astro/routes/api/admin/oauth-clients/index.ts +1 -1
  672. package/src/astro/routes/api/admin/plugins/registry/[id]/update.ts +4 -0
  673. package/src/astro/routes/api/admin/plugins/registry/artifact.ts +3 -7
  674. package/src/astro/routes/api/admin/plugins/registry/install.ts +4 -0
  675. package/src/astro/routes/api/admin/plugins/registry/verify.ts +60 -0
  676. package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +8 -2
  677. package/src/astro/routes/api/content/[collection]/[id]/publish.ts +2 -1
  678. package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +6 -2
  679. package/src/astro/routes/api/manifest.ts +1 -1
  680. package/src/astro/routes/api/media/[id]/replace.ts +114 -0
  681. package/src/astro/routes/api/media/file/[...key].ts +4 -1
  682. package/src/astro/routes/api/media/upload-url.ts +8 -4
  683. package/src/astro/routes/api/media.ts +54 -8
  684. package/src/astro/routes/api/menus/[name]/items/[id].ts +7 -2
  685. package/src/astro/routes/api/menus/[name]/items.ts +4 -1
  686. package/src/astro/routes/api/menus/[name]/reorder.ts +4 -1
  687. package/src/astro/routes/api/menus/[name]/translations.ts +4 -1
  688. package/src/astro/routes/api/menus/[name].ts +7 -2
  689. package/src/astro/routes/api/menus/index.ts +4 -1
  690. package/src/astro/routes/api/settings/email.ts +1 -1
  691. package/src/astro/routes/api/settings.ts +4 -1
  692. package/src/astro/routes/api/taxonomies/[name]/reorder.ts +4 -1
  693. package/src/astro/routes/api/taxonomies/[name]/terms/[slug]/translations.ts +4 -1
  694. package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +7 -2
  695. package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +4 -1
  696. package/src/astro/routes/api/taxonomies/[name].ts +7 -2
  697. package/src/astro/routes/api/taxonomies/index.ts +4 -1
  698. package/src/astro/routes/api/widget-areas/[name]/reorder.ts +3 -1
  699. package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +5 -2
  700. package/src/astro/routes/api/widget-areas/[name]/widgets.ts +3 -1
  701. package/src/astro/routes/api/widget-areas/[name].ts +3 -1
  702. package/src/astro/routes/api/widget-areas/index.ts +3 -1
  703. package/src/astro/types.ts +23 -5
  704. package/src/auth/challenge-store.ts +3 -1
  705. package/src/aws-sdk.d.ts +1 -0
  706. package/src/cache/chrome-tags.ts +24 -0
  707. package/src/components/EmDashImage.astro +2 -1
  708. package/src/components/EmDashMedia.astro +2 -1
  709. package/src/components/InlinePortableTextEditor.tsx +6 -1
  710. package/src/components/widgets/Archives.astro +2 -37
  711. package/src/content/converters/index.ts +2 -0
  712. package/src/content/converters/mark-safety.ts +1 -0
  713. package/src/content/converters/portable-text-identity.ts +139 -0
  714. package/src/content/converters/portable-text-to-prosemirror.ts +202 -69
  715. package/src/content/converters/prosemirror-to-portable-text.ts +96 -25
  716. package/src/content/converters/types.ts +1 -0
  717. package/src/database/migrations/017_authorization_codes.ts +14 -4
  718. package/src/database/migrations/074_content_deleted_scheduled_index.ts +55 -0
  719. package/src/database/migrations/runner.ts +3 -1
  720. package/src/database/repositories/byline.ts +32 -16
  721. package/src/database/repositories/content.ts +114 -47
  722. package/src/database/repositories/media-usage.ts +66 -37
  723. package/src/database/repositories/media.ts +60 -1
  724. package/src/database/repositories/plugin-storage.ts +74 -9
  725. package/src/database/repositories/types.ts +8 -7
  726. package/src/db/adapters.ts +8 -1
  727. package/src/db/postgres.ts +23 -0
  728. package/src/emdash-runtime.ts +80 -150
  729. package/src/fields/boolean.ts +1 -1
  730. package/src/fields/datetime.ts +1 -1
  731. package/src/fields/file.ts +1 -1
  732. package/src/fields/integer.ts +1 -1
  733. package/src/fields/json.ts +1 -1
  734. package/src/fields/multiselect.ts +1 -1
  735. package/src/fields/number.ts +1 -1
  736. package/src/fields/portable-text.ts +1 -1
  737. package/src/fields/richtext.ts +1 -1
  738. package/src/fields/select.ts +1 -1
  739. package/src/fields/slug.ts +1 -1
  740. package/src/fields/text.ts +1 -1
  741. package/src/fields/textarea.ts +1 -1
  742. package/src/fields/types.ts +2 -2
  743. package/src/index.ts +25 -3
  744. package/src/loader.ts +63 -10
  745. package/src/mcp/server.ts +228 -24
  746. package/src/media/image-endpoint.ts +5 -2
  747. package/src/media/normalize.ts +15 -4
  748. package/src/media/usage/cleanup.ts +9 -8
  749. package/src/menus/index.ts +16 -0
  750. package/src/object-cache/index.ts +44 -2
  751. package/src/plugin-types.ts +18 -1
  752. package/src/plugins/hooks.ts +4 -1
  753. package/src/plugins/index.ts +6 -0
  754. package/src/plugins/manager.ts +2 -1
  755. package/src/plugins/manifest-schema.ts +1 -1
  756. package/src/plugins/routes.ts +3 -1
  757. package/src/plugins/sandbox/hook-result.ts +52 -0
  758. package/src/plugins/sandbox/index.ts +10 -0
  759. package/src/plugins/types.ts +6 -0
  760. package/src/registry/artifact-checksum.ts +15 -0
  761. package/src/registry/artifact-verification.ts +85 -0
  762. package/src/registry/authoritative-records.ts +180 -0
  763. package/src/registry/record-labels.ts +28 -0
  764. package/src/schema/registry.ts +26 -3
  765. package/src/schema/zod-generator.ts +16 -13
  766. package/src/security/ssrf.ts +5 -2
  767. package/src/settings/index.ts +16 -0
  768. package/src/storage/local.ts +1 -0
  769. package/src/storage/s3.ts +2 -0
  770. package/src/storage/types.ts +2 -0
  771. package/src/taxonomies/index.ts +40 -20
  772. package/src/widgets/archives.ts +67 -0
  773. package/src/widgets/index.ts +16 -0
  774. package/dist/api-B0w3uBiG.mjs +0 -2269
  775. package/dist/api-B0w3uBiG.mjs.map +0 -1
  776. package/dist/byline-Cpnwrje1.mjs.map +0 -1
  777. package/dist/challenge-store-D5pSY2S1.mjs.map +0 -1
  778. package/dist/content-CP-SeXNa.mjs.map +0 -1
  779. package/dist/content-refresh-DLOfIhYc.mjs.map +0 -1
  780. package/dist/context-BR5hRWiK.mjs.map +0 -1
  781. package/dist/errors-n2j49uNf.mjs.map +0 -1
  782. package/dist/index-DWbJrsAg.d.mts.map +0 -1
  783. package/dist/loader-ZsjE4paL.mjs.map +0 -1
  784. package/dist/manifest-schema-CUYPfCEH.mjs.map +0 -1
  785. package/dist/media-BjwNuLEu.mjs.map +0 -1
  786. package/dist/media-D6SeAfBl.mjs.map +0 -1
  787. package/dist/menus-CNg0QlDt.mjs.map +0 -1
  788. package/dist/normalize-CSxdKBVa.mjs.map +0 -1
  789. package/dist/object-cache-71tPfV4A.mjs.map +0 -1
  790. package/dist/options-BhZIeFY1.d.mts.map +0 -1
  791. package/dist/redirects-CRIM55KH.mjs.map +0 -1
  792. package/dist/registry-BiO5WwPq.mjs +0 -2716
  793. package/dist/registry-BiO5WwPq.mjs.map +0 -1
  794. package/dist/registry-Cx-jEJOS.mjs.map +0 -1
  795. package/dist/relations-BTPM2saV.mjs.map +0 -1
  796. package/dist/relations-C7B46bcU.d.mts.map +0 -1
  797. package/dist/runner-77GOZCnH.mjs.map +0 -1
  798. package/dist/runner-zgqA_CqP.d.mts.map +0 -1
  799. package/dist/settings-BgN-0DT-.mjs.map +0 -1
  800. package/dist/taxonomies-CPGxo-bn.mjs.map +0 -1
  801. package/dist/taxonomies-kmyudNGP.mjs.map +0 -1
  802. package/dist/types-D6OcIp2P.mjs +0 -3
  803. package/dist/types-DJQxln7q.d.mts.map +0 -1
  804. package/dist/types-D_DWe7k9.d.mts.map +0 -1
  805. package/dist/version-ri-sCyAp.mjs +0 -7
  806. package/dist/widgets-CpmZd9Q-.mjs.map +0 -1
  807. package/dist/zod-generator-DylzusHh.mjs.map +0 -1
  808. /package/dist/{api-tokens-B5NHLNUN.mjs → api-tokens-BfYts-sq.mjs} +0 -0
  809. /package/dist/{ssrf-BPKzkm6O.mjs → ssrf-Dg3cIT-b.mjs} +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"redirects-BahWiu8Z.mjs","names":[],"sources":["../src/redirects/loops.ts","../src/api/handlers/redirects.ts"],"sourcesContent":["/**\n * Redirect loop and chain detection utilities.\n *\n * Builds a directed graph from redirect rules and detects:\n * - Cycles (loops): /a → /b → /c → /a\n * - Long chains: /a → /b → /c → /d → /e (exceeding a warning threshold)\n *\n * Handles both exact and pattern redirects. When the walker encounters\n * a path with no exact source match, it tests against compiled pattern\n * sources and resolves the destination using captured parameters.\n */\n\nimport {\n\tcompilePattern,\n\tmatchPattern,\n\tinterpolateDestination,\n\ttype CompiledPattern,\n} from \"./patterns.js\";\n\nexport interface RedirectEdge {\n\tid: string;\n\tsource: string;\n\tdestination: string;\n\tenabled: boolean;\n\tisPattern: boolean;\n}\n\ninterface CompiledPatternRedirect {\n\tid: string;\n\tcompiled: CompiledPattern;\n\tdestination: string;\n}\n\n/**\n * Compile all enabled pattern redirects for matching during graph walks.\n */\nfunction compilePatterns(edges: RedirectEdge[]): CompiledPatternRedirect[] {\n\tconst result: CompiledPatternRedirect[] = [];\n\tfor (const edge of edges) {\n\t\tif (edge.enabled && edge.isPattern) {\n\t\t\tresult.push({\n\t\t\t\tid: edge.id,\n\t\t\t\tcompiled: compilePattern(edge.source),\n\t\t\t\tdestination: edge.destination,\n\t\t\t});\n\t\t}\n\t}\n\treturn result;\n}\n\n/** Single-segment dummy value for representative path generation */\nconst DUMMY_SEGMENT = \"__p__\";\n\n/** Splat pattern: [...paramName] */\nconst SPLAT_RE = /\\[\\.\\.\\.(\\w+)\\]/g;\n\n/** Param pattern: [paramName] */\nconst PARAM_RE = /\\[(\\w+)\\]/g;\n\n/**\n * Extract the literal prefix from a pattern source (everything before the\n * first placeholder), stripped of leading segments shared with a base path.\n * e.g., \"/new/docs/[slug]\" → \"docs/__p__\" (the part after \"/new/\")\n */\nfunction extractPatternSuffix(patternSource: string): string {\n\t// Replace placeholders with dummy values\n\tlet result = patternSource.replace(SPLAT_RE, DUMMY_SEGMENT);\n\tSPLAT_RE.lastIndex = 0;\n\tresult = result.replace(PARAM_RE, DUMMY_SEGMENT);\n\t// Strip leading slash and first segment (e.g., \"/new/docs/__p__\" → \"docs/__p__\")\n\tconst parts = result.split(\"/\").filter(Boolean);\n\treturn parts.slice(1).join(\"/\");\n}\n\n/**\n * Generate representative concrete paths from a template string.\n * Replaces [param] with a dummy segment and [...rest] with multiple\n * depth variants. For catch-alls, also generates representatives using\n * literal prefixes from existing pattern sources to catch cross-pattern loops.\n */\nfunction generateRepresentatives(template: string, existingEdges?: RedirectEdge[]): string[] {\n\tconst hasSplat = SPLAT_RE.test(template);\n\tSPLAT_RE.lastIndex = 0;\n\n\tif (hasSplat) {\n\t\t// Extract the static prefix before the catch-all (e.g., \"/old/\" from \"/old/[...path]\")\n\t\tconst splatIndex = template.indexOf(\"[...\");\n\t\tconst prefix = template.slice(0, splatIndex);\n\n\t\tconst reps = [\n\t\t\ttemplate.replace(SPLAT_RE, DUMMY_SEGMENT).replace(PARAM_RE, DUMMY_SEGMENT),\n\t\t\ttemplate\n\t\t\t\t.replace(SPLAT_RE, `${DUMMY_SEGMENT}/${DUMMY_SEGMENT}`)\n\t\t\t\t.replace(PARAM_RE, DUMMY_SEGMENT),\n\t\t\ttemplate\n\t\t\t\t.replace(SPLAT_RE, `${DUMMY_SEGMENT}/${DUMMY_SEGMENT}/${DUMMY_SEGMENT}`)\n\t\t\t\t.replace(PARAM_RE, DUMMY_SEGMENT),\n\t\t];\n\n\t\t// Add representatives derived from existing pattern sources' literal prefixes\n\t\tif (existingEdges) {\n\t\t\tfor (const edge of existingEdges) {\n\t\t\t\tif (edge.enabled && edge.isPattern && edge.source !== template) {\n\t\t\t\t\tconst suffix = extractPatternSuffix(edge.source);\n\t\t\t\t\tif (suffix) {\n\t\t\t\t\t\treps.push(`${prefix}${suffix}`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn reps;\n\t}\n\n\treturn [template.replace(PARAM_RE, DUMMY_SEGMENT)];\n}\n\n/**\n * Resolve the next hop for a given path. Tries exact match first,\n * then pattern matching with parameter interpolation for concrete paths,\n * then representative-based matching for template strings.\n */\nfunction resolveNext(\n\tpath: string,\n\tgraph: Map<string, { destination: string; id: string }>,\n\tpatterns: CompiledPatternRedirect[],\n\tedges?: RedirectEdge[],\n): { destination: string; id: string } | null {\n\t// Exact match (fast) — works for both real paths and template strings\n\tconst exact = graph.get(path);\n\tif (exact) return exact;\n\n\tif (!path.includes(\"[\")) {\n\t\t// Concrete path — try pattern matching directly\n\t\tfor (const pr of patterns) {\n\t\t\tconst params = matchPattern(pr.compiled, path);\n\t\t\tif (params) {\n\t\t\t\tconst resolved = interpolateDestination(pr.destination, params);\n\t\t\t\treturn { destination: resolved, id: pr.id };\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Template string — generate representative paths and test against patterns\n\t\tconst representatives = generateRepresentatives(path, edges);\n\t\tfor (const pr of patterns) {\n\t\t\tfor (const rep of representatives) {\n\t\t\t\tconst params = matchPattern(pr.compiled, rep);\n\t\t\t\tif (params) {\n\t\t\t\t\tconst resolved = interpolateDestination(pr.destination, params);\n\t\t\t\t\treturn { destination: resolved, id: pr.id };\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Build an adjacency map from redirect edges.\n * Includes both exact and pattern redirects — pattern redirects use their\n * template strings as literal graph edges, which works because EmDash\n * patterns pass parameters through without transformation.\n */\nfunction buildGraph(edges: RedirectEdge[]): Map<string, { destination: string; id: string }> {\n\tconst graph = new Map<string, { destination: string; id: string }>();\n\tfor (const edge of edges) {\n\t\tif (edge.enabled) {\n\t\t\tgraph.set(edge.source, { destination: edge.destination, id: edge.id });\n\t\t}\n\t}\n\treturn graph;\n}\n\n/**\n * Detect all redirect IDs that participate in cycles.\n * Walks every node in the graph once, collecting IDs from any cycles found.\n *\n * @returns Array of redirect IDs that are part of a loop\n */\nexport function detectLoops(edges: RedirectEdge[]): string[] {\n\tconst graph = buildGraph(edges);\n\tconst patterns = compilePatterns(edges);\n\tconst visited = new Set<string>();\n\tconst loopRedirectIds = new Set<string>();\n\n\tfor (const [startSource] of graph) {\n\t\tif (visited.has(startSource)) continue;\n\n\t\tconst path: string[] = [];\n\t\tconst pathSet = new Set<string>();\n\t\tconst pathIds: string[] = [];\n\t\tlet current: string | undefined = startSource;\n\n\t\twhile (current) {\n\t\t\tif (pathSet.has(current)) {\n\t\t\t\t// Found a cycle — collect IDs of redirects in the loop\n\t\t\t\tconst loopStart = path.indexOf(current);\n\t\t\t\tfor (const id of pathIds.slice(loopStart)) loopRedirectIds.add(id);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (visited.has(current)) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tconst next = resolveNext(current, graph, patterns, edges);\n\t\t\tif (!next) break;\n\n\t\t\tpath.push(current);\n\t\t\tpathSet.add(current);\n\t\t\tpathIds.push(next.id);\n\t\t\tcurrent = next.destination;\n\t\t}\n\n\t\tfor (const node of path) visited.add(node);\n\t}\n\n\treturn [...loopRedirectIds];\n}\n\n/**\n * Find a compiled pattern redirect whose source matches the given resolved path,\n * returning the source template string for display purposes.\n */\nfunction findMatchingTemplate(\n\tresolvedPath: string,\n\tpatterns: CompiledPatternRedirect[],\n): string | null {\n\tfor (const pr of patterns) {\n\t\tif (matchPattern(pr.compiled, resolvedPath) !== null) {\n\t\t\treturn pr.compiled.source;\n\t\t}\n\t}\n\treturn null;\n}\n\n/**\n * Check if adding or updating a redirect would create a loop.\n *\n * Walks the chain from `destination` through existing redirects.\n * If it reaches `source`, a cycle would form.\n *\n * @returns The loop path if a cycle would be created, or null if safe\n */\nexport function wouldCreateLoop(\n\tsource: string,\n\tdestination: string,\n\texistingEdges: RedirectEdge[],\n\texcludeId?: string,\n): string[] | null {\n\tconst filtered = excludeId ? existingEdges.filter((e) => e.id !== excludeId) : existingEdges;\n\tconst graph = buildGraph(filtered);\n\tconst patterns = compilePatterns(filtered);\n\n\t// If the proposed source is a pattern, compile it so we can check\n\t// whether resolved paths would match it (not just string equality)\n\tconst sourceIsPattern = source.includes(\"[\");\n\tconst compiledSource = sourceIsPattern ? compilePattern(source) : null;\n\n\t// Determine starting points for the walk. If the destination is a\n\t// template, generate representative concrete paths AND find existing\n\t// exact sources in the graph that match the template.\n\tlet startingPoints: string[];\n\tif (destination.includes(\"[\")) {\n\t\tconst reps = generateRepresentatives(destination, filtered);\n\t\t// Also find existing exact graph keys that match this template\n\t\tconst compiled = compilePattern(destination);\n\t\tfor (const [key] of graph) {\n\t\t\tif (!key.includes(\"[\") && matchPattern(compiled, key) !== null) {\n\t\t\t\treps.push(key);\n\t\t\t}\n\t\t}\n\t\t// Always include the destination itself — it may be an exact graph key\n\t\t// (e.g., /a/sub/[...path] exists as a literal source in the graph)\n\t\treps.push(destination);\n\t\tstartingPoints = reps;\n\t} else {\n\t\tstartingPoints = [destination];\n\t}\n\n\tfor (const start of startingPoints) {\n\t\tconst path = [source, destination];\n\t\tlet current = start;\n\t\tconst seen = new Set<string>([source, destination, start]);\n\n\t\t// Walk the chain until it ends or we revisit a node\n\t\t// eslint-disable-next-line no-constant-condition -- terminates via return/break when chain ends or cycle found\n\t\twhile (true) {\n\t\t\tconst next = resolveNext(current, graph, patterns, filtered);\n\t\t\tif (!next) break; // chain ends, try next starting point\n\n\t\t\t// Check if we've looped back — either exact match or pattern match\n\t\t\tconst loopsBack =\n\t\t\t\tseen.has(next.destination) ||\n\t\t\t\t(compiledSource !== null && matchPattern(compiledSource, next.destination) !== null);\n\n\t\t\tif (loopsBack) {\n\t\t\t\t// Show the source template instead of dummy resolved path\n\t\t\t\tconst displayPath =\n\t\t\t\t\t!seen.has(next.destination) && compiledSource !== null ? source : next.destination;\n\t\t\t\tpath.push(displayPath);\n\t\t\t\treturn path; // cycle found\n\t\t\t}\n\n\t\t\t// If the resolved path contains dummy segments, try to find the\n\t\t\t// original pattern template that produced it for cleaner display\n\t\t\tconst cleanDest = next.destination.includes(DUMMY_SEGMENT)\n\t\t\t\t? (findMatchingTemplate(next.destination, patterns) ?? next.destination)\n\t\t\t\t: next.destination;\n\t\t\tpath.push(cleanDest);\n\t\t\tseen.add(next.destination);\n\t\t\tcurrent = next.destination;\n\t\t}\n\t}\n\n\treturn null;\n}\n","/**\n * Redirect CRUD and 404 log handlers\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport { OptionsRepository } from \"../../database/repositories/options.js\";\nimport {\n\tRedirectRepository,\n\ttype Redirect,\n\ttype NotFoundEntry,\n\ttype NotFoundSummary,\n} from \"../../database/repositories/redirect.js\";\nimport { InvalidCursorError } from \"../../database/repositories/types.js\";\nimport type { FindManyResult } from \"../../database/repositories/types.js\";\nimport type { Database } from \"../../database/types.js\";\nimport { wouldCreateLoop, detectLoops, type RedirectEdge } from \"../../redirects/loops.js\";\nimport { validatePattern, validateDestinationParams, isPattern } from \"../../redirects/patterns.js\";\nimport { isTerminalStatus } from \"../../redirects/status.js\";\nimport type { ApiResult } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Redirects\n// ---------------------------------------------------------------------------\n\n/**\n * List redirects with cursor pagination and optional filters\n */\nexport async function handleRedirectList(\n\tdb: Kysely<Database>,\n\tparams: {\n\t\tcursor?: string;\n\t\tlimit?: number;\n\t\tsearch?: string;\n\t\tgroup?: string;\n\t\tenabled?: boolean;\n\t\tauto?: boolean;\n\t},\n): Promise<ApiResult<FindManyResult<Redirect> & { loopRedirectIds?: string[] }>> {\n\ttry {\n\t\tconst repo = new RedirectRepository(db);\n\t\tconst result = await repo.findMany(params);\n\n\t\tconst loopRedirectIds = await getLoopRedirectIds(db);\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\t...result,\n\t\t\t\t...(loopRedirectIds.length > 0 ? { loopRedirectIds } : {}),\n\t\t\t},\n\t\t};\n\t} catch (error) {\n\t\tif (error instanceof InvalidCursorError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_CURSOR\", message: error.message },\n\t\t\t};\n\t\t}\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"REDIRECT_LIST_ERROR\", message: \"Failed to fetch redirects\" },\n\t\t};\n\t}\n}\n\n/**\n * Create a redirect rule\n */\nexport async function handleRedirectCreate(\n\tdb: Kysely<Database>,\n\tinput: {\n\t\tsource: string;\n\t\tdestination?: string;\n\t\ttype?: number;\n\t\tenabled?: boolean;\n\t\tgroupName?: string | null;\n\t},\n): Promise<ApiResult<Redirect>> {\n\ttry {\n\t\tconst repo = new RedirectRepository(db);\n\n\t\tconst type = input.type ?? 301;\n\t\t// Terminal statuses (410 Gone / 451) are served directly and have no\n\t\t// destination — skip the destination/loop checks for them.\n\t\tconst terminal = isTerminalStatus(type);\n\t\tconst destination = terminal ? \"\" : (input.destination ?? \"\");\n\n\t\t// Source and destination must differ\n\t\tif (!terminal && input.source === destination) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage: \"Source and destination must be different\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// If source looks like a pattern, validate it\n\t\tconst sourceIsPattern = isPattern(input.source);\n\t\tif (sourceIsPattern) {\n\t\t\tconst patternError = validatePattern(input.source);\n\t\t\tif (patternError) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: { code: \"VALIDATION_ERROR\", message: `Invalid source pattern: ${patternError}` },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Validate destination params reference valid source params\n\t\t\t// (terminal rules have no destination to interpolate)\n\t\t\tif (!terminal) {\n\t\t\t\tconst destError = validateDestinationParams(input.source, destination);\n\t\t\t\tif (destError) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsuccess: false,\n\t\t\t\t\t\terror: { code: \"VALIDATION_ERROR\", message: destError },\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Check for duplicate source (exact match only for non-patterns)\n\t\tconst existing = await repo.findBySource(input.source);\n\t\tif (existing) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"CONFLICT\",\n\t\t\t\t\tmessage: `A redirect from \"${input.source}\" already exists`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Check for redirect loops (skip if creating as disabled, or terminal —\n\t\t// a Gone rule has no destination, so it can't form a loop)\n\t\tif (!terminal && input.enabled !== false) {\n\t\t\tconst edges = toEdges(await repo.findAllEnabled());\n\t\t\tconst loopPath = wouldCreateLoop(input.source, destination, edges);\n\t\t\tif (loopPath) return loopError(loopPath);\n\t\t}\n\n\t\tconst redirect = await repo.create({\n\t\t\tsource: input.source,\n\t\t\tdestination,\n\t\t\ttype,\n\t\t\tisPattern: sourceIsPattern,\n\t\t\tenabled: input.enabled ?? true,\n\t\t\tgroupName: input.groupName ?? null,\n\t\t});\n\n\t\treturn { success: true, data: redirect };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"REDIRECT_CREATE_ERROR\", message: \"Failed to create redirect\" },\n\t\t};\n\t}\n}\n\n/**\n * Get a redirect by ID\n */\nexport async function handleRedirectGet(\n\tdb: Kysely<Database>,\n\tid: string,\n): Promise<ApiResult<Redirect>> {\n\ttry {\n\t\tconst repo = new RedirectRepository(db);\n\t\tconst redirect = await repo.findById(id);\n\n\t\tif (!redirect) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: `Redirect \"${id}\" not found` },\n\t\t\t};\n\t\t}\n\n\t\treturn { success: true, data: redirect };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"REDIRECT_GET_ERROR\", message: \"Failed to fetch redirect\" },\n\t\t};\n\t}\n}\n\n/**\n * Update a redirect by ID\n */\nexport async function handleRedirectUpdate(\n\tdb: Kysely<Database>,\n\tid: string,\n\tinput: {\n\t\tsource?: string;\n\t\tdestination?: string;\n\t\ttype?: number;\n\t\tenabled?: boolean;\n\t\tgroupName?: string | null;\n\t},\n): Promise<ApiResult<Redirect>> {\n\ttry {\n\t\tconst repo = new RedirectRepository(db);\n\n\t\tconst existing = await repo.findById(id);\n\t\tif (!existing) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: `Redirect \"${id}\" not found` },\n\t\t\t};\n\t\t}\n\n\t\tconst newSource = input.source ?? existing.source;\n\t\tconst newDest = input.destination ?? existing.destination;\n\n\t\t// Source and destination must differ\n\t\tif (newSource === newDest) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage: \"Source and destination must be different\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// If source is changing, validate patterns\n\t\tif (input.source !== undefined) {\n\t\t\tconst sourceIsPattern = isPattern(input.source);\n\t\t\tif (sourceIsPattern) {\n\t\t\t\tconst patternError = validatePattern(input.source);\n\t\t\t\tif (patternError) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsuccess: false,\n\t\t\t\t\t\terror: {\n\t\t\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\t\t\tmessage: `Invalid source pattern: ${patternError}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Check for duplicate source (exclude self)\n\t\t\tconst dup = await repo.findBySource(input.source);\n\t\t\tif (dup && dup.id !== id) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcode: \"CONFLICT\",\n\t\t\t\t\t\tmessage: `A redirect from \"${input.source}\" already exists`,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\t// Validate destination params against the (possibly updated) source\n\t\tconst newSourceIsPattern = isPattern(newSource);\n\t\tif (newSourceIsPattern) {\n\t\t\tconst destError = validateDestinationParams(newSource, newDest);\n\t\t\tif (destError) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: { code: \"VALIDATION_ERROR\", message: destError },\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\t// Check for redirect loops if source or destination changed\n\t\tif (input.source !== undefined || input.destination !== undefined) {\n\t\t\tconst edges = toEdges(await repo.findAllEnabled());\n\t\t\tconst loopPath = wouldCreateLoop(newSource, newDest, edges, id);\n\t\t\tif (loopPath) return loopError(loopPath);\n\t\t}\n\n\t\tconst updated = await repo.update(id, {\n\t\t\tsource: input.source,\n\t\t\tdestination: input.destination,\n\t\t\ttype: input.type,\n\t\t\tenabled: input.enabled,\n\t\t\tgroupName: input.groupName,\n\t\t});\n\n\t\tif (!updated) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"REDIRECT_UPDATE_ERROR\", message: \"Failed to update redirect\" },\n\t\t\t};\n\t\t}\n\n\t\t// Recompute cache — redirect was modified, so re-fetch\n\t\tawait updateLoopCache(db);\n\n\t\treturn { success: true, data: updated };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"REDIRECT_UPDATE_ERROR\", message: \"Failed to update redirect\" },\n\t\t};\n\t}\n}\n\n/**\n * Delete a redirect by ID\n */\nexport async function handleRedirectDelete(\n\tdb: Kysely<Database>,\n\tid: string,\n): Promise<ApiResult<{ deleted: true }>> {\n\ttry {\n\t\tconst repo = new RedirectRepository(db);\n\t\tconst deleted = await repo.delete(id);\n\n\t\tif (!deleted) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: `Redirect \"${id}\" not found` },\n\t\t\t};\n\t\t}\n\n\t\tawait updateLoopCache(db);\n\n\t\treturn { success: true, data: { deleted: true } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"REDIRECT_DELETE_ERROR\", message: \"Failed to delete redirect\" },\n\t\t};\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Loop analysis cache\n// ---------------------------------------------------------------------------\n\nfunction loopError(loopPath: string[]): ApiResult<never> {\n\tconst hops = loopPath\n\t\t.slice(0, -1)\n\t\t.map((p, i) => `${p} \\u2192 ${loopPath[i + 1]}`)\n\t\t.join(\"\\n\");\n\treturn {\n\t\tsuccess: false,\n\t\terror: {\n\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\tmessage: `This redirect would create a loop:\\n${hops}`,\n\t\t},\n\t};\n}\n\nfunction toEdges(redirects: Redirect[]): RedirectEdge[] {\n\treturn redirects.map((r) => ({\n\t\tid: r.id,\n\t\tsource: r.source,\n\t\tdestination: r.destination,\n\t\tenabled: r.enabled,\n\t\tisPattern: r.isPattern,\n\t}));\n}\n\nconst LOOP_CACHE_KEY = \"_redirect_loop_ids\";\n\n/**\n * Recompute loop redirect IDs and store in the options table.\n */\nasync function updateLoopCache(db: Kysely<Database>): Promise<void> {\n\ttry {\n\t\tconst options = new OptionsRepository(db);\n\t\tconst edges = toEdges(await new RedirectRepository(db).findAllEnabled());\n\t\tconst loopRedirectIds = detectLoops(edges);\n\t\tawait options.set(LOOP_CACHE_KEY, loopRedirectIds);\n\t} catch (error) {\n\t\tconsole.error(\"Failed to update redirect loop cache:\", error);\n\t}\n}\n\n/**\n * Get loop redirect IDs from cache, computing lazily on first access.\n */\nasync function getLoopRedirectIds(db: Kysely<Database>): Promise<string[]> {\n\ttry {\n\t\tconst options = new OptionsRepository(db);\n\t\tconst cached = await options.get<string[]>(LOOP_CACHE_KEY);\n\t\tif (cached !== null) return cached;\n\n\t\t// First access after upgrade — compute and cache\n\t\tawait updateLoopCache(db);\n\t\treturn (await options.get<string[]>(LOOP_CACHE_KEY)) ?? [];\n\t} catch {\n\t\treturn [];\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// 404 Log\n// ---------------------------------------------------------------------------\n\n/**\n * List 404 log entries with cursor pagination\n */\nexport async function handleNotFoundList(\n\tdb: Kysely<Database>,\n\tparams: { cursor?: string; limit?: number; search?: string },\n): Promise<ApiResult<FindManyResult<NotFoundEntry>>> {\n\ttry {\n\t\tconst repo = new RedirectRepository(db);\n\t\tconst result = await repo.find404s(params);\n\t\treturn { success: true, data: result };\n\t} catch (error) {\n\t\tif (error instanceof InvalidCursorError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_CURSOR\", message: error.message },\n\t\t\t};\n\t\t}\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"NOT_FOUND_LIST_ERROR\", message: \"Failed to fetch 404 log\" },\n\t\t};\n\t}\n}\n\n/**\n * Get 404 summary (grouped by path, sorted by count)\n */\nexport async function handleNotFoundSummary(\n\tdb: Kysely<Database>,\n\tlimit?: number,\n): Promise<ApiResult<{ items: NotFoundSummary[] }>> {\n\ttry {\n\t\tconst repo = new RedirectRepository(db);\n\t\tconst items = await repo.get404Summary(limit);\n\t\treturn { success: true, data: { items } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"NOT_FOUND_SUMMARY_ERROR\", message: \"Failed to fetch 404 summary\" },\n\t\t};\n\t}\n}\n\n/**\n * Clear all 404 log entries\n */\nexport async function handleNotFoundClear(\n\tdb: Kysely<Database>,\n): Promise<ApiResult<{ deleted: number }>> {\n\ttry {\n\t\tconst repo = new RedirectRepository(db);\n\t\tconst deleted = await repo.clear404s();\n\t\treturn { success: true, data: { deleted } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"NOT_FOUND_CLEAR_ERROR\", message: \"Failed to clear 404 log\" },\n\t\t};\n\t}\n}\n\n/**\n * Prune 404 log entries older than a given date\n */\nexport async function handleNotFoundPrune(\n\tdb: Kysely<Database>,\n\tolderThan: string,\n): Promise<ApiResult<{ deleted: number }>> {\n\ttry {\n\t\tconst repo = new RedirectRepository(db);\n\t\tconst deleted = await repo.prune404s(olderThan);\n\t\treturn { success: true, data: { deleted } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"NOT_FOUND_PRUNE_ERROR\", message: \"Failed to prune 404 log\" },\n\t\t};\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"redirects-CqzNM0KM.mjs","names":[],"sources":["../src/redirects/loops.ts","../src/api/handlers/redirects.ts"],"sourcesContent":["/**\n * Redirect loop and chain detection utilities.\n *\n * Builds a directed graph from redirect rules and detects:\n * - Cycles (loops): /a → /b → /c → /a\n * - Long chains: /a → /b → /c → /d → /e (exceeding a warning threshold)\n *\n * Handles both exact and pattern redirects. When the walker encounters\n * a path with no exact source match, it tests against compiled pattern\n * sources and resolves the destination using captured parameters.\n */\n\nimport {\n\tcompilePattern,\n\tmatchPattern,\n\tinterpolateDestination,\n\ttype CompiledPattern,\n} from \"./patterns.js\";\n\nexport interface RedirectEdge {\n\tid: string;\n\tsource: string;\n\tdestination: string;\n\tenabled: boolean;\n\tisPattern: boolean;\n}\n\ninterface CompiledPatternRedirect {\n\tid: string;\n\tcompiled: CompiledPattern;\n\tdestination: string;\n}\n\n/**\n * Compile all enabled pattern redirects for matching during graph walks.\n */\nfunction compilePatterns(edges: RedirectEdge[]): CompiledPatternRedirect[] {\n\tconst result: CompiledPatternRedirect[] = [];\n\tfor (const edge of edges) {\n\t\tif (edge.enabled && edge.isPattern) {\n\t\t\tresult.push({\n\t\t\t\tid: edge.id,\n\t\t\t\tcompiled: compilePattern(edge.source),\n\t\t\t\tdestination: edge.destination,\n\t\t\t});\n\t\t}\n\t}\n\treturn result;\n}\n\n/** Single-segment dummy value for representative path generation */\nconst DUMMY_SEGMENT = \"__p__\";\n\n/** Splat pattern: [...paramName] */\nconst SPLAT_RE = /\\[\\.\\.\\.(\\w+)\\]/g;\n\n/** Param pattern: [paramName] */\nconst PARAM_RE = /\\[(\\w+)\\]/g;\n\n/**\n * Extract the literal prefix from a pattern source (everything before the\n * first placeholder), stripped of leading segments shared with a base path.\n * e.g., \"/new/docs/[slug]\" → \"docs/__p__\" (the part after \"/new/\")\n */\nfunction extractPatternSuffix(patternSource: string): string {\n\t// Replace placeholders with dummy values\n\tlet result = patternSource.replace(SPLAT_RE, DUMMY_SEGMENT);\n\tSPLAT_RE.lastIndex = 0;\n\tresult = result.replace(PARAM_RE, DUMMY_SEGMENT);\n\t// Strip leading slash and first segment (e.g., \"/new/docs/__p__\" → \"docs/__p__\")\n\tconst parts = result.split(\"/\").filter(Boolean);\n\treturn parts.slice(1).join(\"/\");\n}\n\n/**\n * Generate representative concrete paths from a template string.\n * Replaces [param] with a dummy segment and [...rest] with multiple\n * depth variants. For catch-alls, also generates representatives using\n * literal prefixes from existing pattern sources to catch cross-pattern loops.\n */\nfunction generateRepresentatives(template: string, existingEdges?: RedirectEdge[]): string[] {\n\tconst hasSplat = SPLAT_RE.test(template);\n\tSPLAT_RE.lastIndex = 0;\n\n\tif (hasSplat) {\n\t\t// Extract the static prefix before the catch-all (e.g., \"/old/\" from \"/old/[...path]\")\n\t\tconst splatIndex = template.indexOf(\"[...\");\n\t\tconst prefix = template.slice(0, splatIndex);\n\n\t\tconst reps = [\n\t\t\ttemplate.replace(SPLAT_RE, DUMMY_SEGMENT).replace(PARAM_RE, DUMMY_SEGMENT),\n\t\t\ttemplate\n\t\t\t\t.replace(SPLAT_RE, `${DUMMY_SEGMENT}/${DUMMY_SEGMENT}`)\n\t\t\t\t.replace(PARAM_RE, DUMMY_SEGMENT),\n\t\t\ttemplate\n\t\t\t\t.replace(SPLAT_RE, `${DUMMY_SEGMENT}/${DUMMY_SEGMENT}/${DUMMY_SEGMENT}`)\n\t\t\t\t.replace(PARAM_RE, DUMMY_SEGMENT),\n\t\t];\n\n\t\t// Add representatives derived from existing pattern sources' literal prefixes\n\t\tif (existingEdges) {\n\t\t\tfor (const edge of existingEdges) {\n\t\t\t\tif (edge.enabled && edge.isPattern && edge.source !== template) {\n\t\t\t\t\tconst suffix = extractPatternSuffix(edge.source);\n\t\t\t\t\tif (suffix) {\n\t\t\t\t\t\treps.push(`${prefix}${suffix}`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn reps;\n\t}\n\n\treturn [template.replace(PARAM_RE, DUMMY_SEGMENT)];\n}\n\n/**\n * Resolve the next hop for a given path. Tries exact match first,\n * then pattern matching with parameter interpolation for concrete paths,\n * then representative-based matching for template strings.\n */\nfunction resolveNext(\n\tpath: string,\n\tgraph: Map<string, { destination: string; id: string }>,\n\tpatterns: CompiledPatternRedirect[],\n\tedges?: RedirectEdge[],\n): { destination: string; id: string } | null {\n\t// Exact match (fast) — works for both real paths and template strings\n\tconst exact = graph.get(path);\n\tif (exact) return exact;\n\n\tif (!path.includes(\"[\")) {\n\t\t// Concrete path — try pattern matching directly\n\t\tfor (const pr of patterns) {\n\t\t\tconst params = matchPattern(pr.compiled, path);\n\t\t\tif (params) {\n\t\t\t\tconst resolved = interpolateDestination(pr.destination, params);\n\t\t\t\treturn { destination: resolved, id: pr.id };\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Template string — generate representative paths and test against patterns\n\t\tconst representatives = generateRepresentatives(path, edges);\n\t\tfor (const pr of patterns) {\n\t\t\tfor (const rep of representatives) {\n\t\t\t\tconst params = matchPattern(pr.compiled, rep);\n\t\t\t\tif (params) {\n\t\t\t\t\tconst resolved = interpolateDestination(pr.destination, params);\n\t\t\t\t\treturn { destination: resolved, id: pr.id };\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Build an adjacency map from redirect edges.\n * Includes both exact and pattern redirects — pattern redirects use their\n * template strings as literal graph edges, which works because EmDash\n * patterns pass parameters through without transformation.\n */\nfunction buildGraph(edges: RedirectEdge[]): Map<string, { destination: string; id: string }> {\n\tconst graph = new Map<string, { destination: string; id: string }>();\n\tfor (const edge of edges) {\n\t\tif (edge.enabled) {\n\t\t\tgraph.set(edge.source, { destination: edge.destination, id: edge.id });\n\t\t}\n\t}\n\treturn graph;\n}\n\n/**\n * Detect all redirect IDs that participate in cycles.\n * Walks every node in the graph once, collecting IDs from any cycles found.\n *\n * @returns Array of redirect IDs that are part of a loop\n */\nexport function detectLoops(edges: RedirectEdge[]): string[] {\n\tconst graph = buildGraph(edges);\n\tconst patterns = compilePatterns(edges);\n\tconst visited = new Set<string>();\n\tconst loopRedirectIds = new Set<string>();\n\n\tfor (const [startSource] of graph) {\n\t\tif (visited.has(startSource)) continue;\n\n\t\tconst path: string[] = [];\n\t\tconst pathSet = new Set<string>();\n\t\tconst pathIds: string[] = [];\n\t\tlet current: string | undefined = startSource;\n\n\t\twhile (current) {\n\t\t\tif (pathSet.has(current)) {\n\t\t\t\t// Found a cycle — collect IDs of redirects in the loop\n\t\t\t\tconst loopStart = path.indexOf(current);\n\t\t\t\tfor (const id of pathIds.slice(loopStart)) loopRedirectIds.add(id);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (visited.has(current)) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tconst next = resolveNext(current, graph, patterns, edges);\n\t\t\tif (!next) break;\n\n\t\t\tpath.push(current);\n\t\t\tpathSet.add(current);\n\t\t\tpathIds.push(next.id);\n\t\t\tcurrent = next.destination;\n\t\t}\n\n\t\tfor (const node of path) visited.add(node);\n\t}\n\n\treturn [...loopRedirectIds];\n}\n\n/**\n * Find a compiled pattern redirect whose source matches the given resolved path,\n * returning the source template string for display purposes.\n */\nfunction findMatchingTemplate(\n\tresolvedPath: string,\n\tpatterns: CompiledPatternRedirect[],\n): string | null {\n\tfor (const pr of patterns) {\n\t\tif (matchPattern(pr.compiled, resolvedPath) !== null) {\n\t\t\treturn pr.compiled.source;\n\t\t}\n\t}\n\treturn null;\n}\n\n/**\n * Check if adding or updating a redirect would create a loop.\n *\n * Walks the chain from `destination` through existing redirects.\n * If it reaches `source`, a cycle would form.\n *\n * @returns The loop path if a cycle would be created, or null if safe\n */\nexport function wouldCreateLoop(\n\tsource: string,\n\tdestination: string,\n\texistingEdges: RedirectEdge[],\n\texcludeId?: string,\n): string[] | null {\n\tconst filtered = excludeId ? existingEdges.filter((e) => e.id !== excludeId) : existingEdges;\n\tconst graph = buildGraph(filtered);\n\tconst patterns = compilePatterns(filtered);\n\n\t// If the proposed source is a pattern, compile it so we can check\n\t// whether resolved paths would match it (not just string equality)\n\tconst sourceIsPattern = source.includes(\"[\");\n\tconst compiledSource = sourceIsPattern ? compilePattern(source) : null;\n\n\t// Determine starting points for the walk. If the destination is a\n\t// template, generate representative concrete paths AND find existing\n\t// exact sources in the graph that match the template.\n\tlet startingPoints: string[];\n\tif (destination.includes(\"[\")) {\n\t\tconst reps = generateRepresentatives(destination, filtered);\n\t\t// Also find existing exact graph keys that match this template\n\t\tconst compiled = compilePattern(destination);\n\t\tfor (const [key] of graph) {\n\t\t\tif (!key.includes(\"[\") && matchPattern(compiled, key) !== null) {\n\t\t\t\treps.push(key);\n\t\t\t}\n\t\t}\n\t\t// Always include the destination itself — it may be an exact graph key\n\t\t// (e.g., /a/sub/[...path] exists as a literal source in the graph)\n\t\treps.push(destination);\n\t\tstartingPoints = reps;\n\t} else {\n\t\tstartingPoints = [destination];\n\t}\n\n\tfor (const start of startingPoints) {\n\t\tconst path = [source, destination];\n\t\tlet current = start;\n\t\tconst seen = new Set<string>([source, destination, start]);\n\n\t\t// Walk the chain until it ends or we revisit a node\n\t\t// eslint-disable-next-line no-constant-condition -- terminates via return/break when chain ends or cycle found\n\t\twhile (true) {\n\t\t\tconst next = resolveNext(current, graph, patterns, filtered);\n\t\t\tif (!next) break; // chain ends, try next starting point\n\n\t\t\t// Check if we've looped back — either exact match or pattern match\n\t\t\tconst loopsBack =\n\t\t\t\tseen.has(next.destination) ||\n\t\t\t\t(compiledSource !== null && matchPattern(compiledSource, next.destination) !== null);\n\n\t\t\tif (loopsBack) {\n\t\t\t\t// Show the source template instead of dummy resolved path\n\t\t\t\tconst displayPath =\n\t\t\t\t\t!seen.has(next.destination) && compiledSource !== null ? source : next.destination;\n\t\t\t\tpath.push(displayPath);\n\t\t\t\treturn path; // cycle found\n\t\t\t}\n\n\t\t\t// If the resolved path contains dummy segments, try to find the\n\t\t\t// original pattern template that produced it for cleaner display\n\t\t\tconst cleanDest = next.destination.includes(DUMMY_SEGMENT)\n\t\t\t\t? (findMatchingTemplate(next.destination, patterns) ?? next.destination)\n\t\t\t\t: next.destination;\n\t\t\tpath.push(cleanDest);\n\t\t\tseen.add(next.destination);\n\t\t\tcurrent = next.destination;\n\t\t}\n\t}\n\n\treturn null;\n}\n","/**\n * Redirect CRUD and 404 log handlers\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport { OptionsRepository } from \"../../database/repositories/options.js\";\nimport {\n\tRedirectRepository,\n\ttype Redirect,\n\ttype NotFoundEntry,\n\ttype NotFoundSummary,\n} from \"../../database/repositories/redirect.js\";\nimport { InvalidCursorError } from \"../../database/repositories/types.js\";\nimport type { FindManyResult } from \"../../database/repositories/types.js\";\nimport type { Database } from \"../../database/types.js\";\nimport { wouldCreateLoop, detectLoops, type RedirectEdge } from \"../../redirects/loops.js\";\nimport { validatePattern, validateDestinationParams, isPattern } from \"../../redirects/patterns.js\";\nimport { isTerminalStatus } from \"../../redirects/status.js\";\nimport type { ApiResult } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Redirects\n// ---------------------------------------------------------------------------\n\n/**\n * List redirects with cursor pagination and optional filters\n */\nexport async function handleRedirectList(\n\tdb: Kysely<Database>,\n\tparams: {\n\t\tcursor?: string;\n\t\tlimit?: number;\n\t\tsearch?: string;\n\t\tgroup?: string;\n\t\tenabled?: boolean;\n\t\tauto?: boolean;\n\t},\n): Promise<ApiResult<FindManyResult<Redirect> & { loopRedirectIds?: string[] }>> {\n\ttry {\n\t\tconst repo = new RedirectRepository(db);\n\t\tconst result = await repo.findMany(params);\n\n\t\tconst loopRedirectIds = await getLoopRedirectIds(db);\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\t...result,\n\t\t\t\t...(loopRedirectIds.length > 0 ? { loopRedirectIds } : {}),\n\t\t\t},\n\t\t};\n\t} catch (error) {\n\t\tif (error instanceof InvalidCursorError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_CURSOR\", message: error.message },\n\t\t\t};\n\t\t}\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"REDIRECT_LIST_ERROR\", message: \"Failed to fetch redirects\" },\n\t\t};\n\t}\n}\n\n/**\n * Create a redirect rule\n */\nexport async function handleRedirectCreate(\n\tdb: Kysely<Database>,\n\tinput: {\n\t\tsource: string;\n\t\tdestination?: string;\n\t\ttype?: number;\n\t\tenabled?: boolean;\n\t\tgroupName?: string | null;\n\t},\n): Promise<ApiResult<Redirect>> {\n\ttry {\n\t\tconst repo = new RedirectRepository(db);\n\n\t\tconst type = input.type ?? 301;\n\t\t// Terminal statuses (410 Gone / 451) are served directly and have no\n\t\t// destination — skip the destination/loop checks for them.\n\t\tconst terminal = isTerminalStatus(type);\n\t\tconst destination = terminal ? \"\" : (input.destination ?? \"\");\n\n\t\t// Source and destination must differ\n\t\tif (!terminal && input.source === destination) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage: \"Source and destination must be different\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// If source looks like a pattern, validate it\n\t\tconst sourceIsPattern = isPattern(input.source);\n\t\tif (sourceIsPattern) {\n\t\t\tconst patternError = validatePattern(input.source);\n\t\t\tif (patternError) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: { code: \"VALIDATION_ERROR\", message: `Invalid source pattern: ${patternError}` },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Validate destination params reference valid source params\n\t\t\t// (terminal rules have no destination to interpolate)\n\t\t\tif (!terminal) {\n\t\t\t\tconst destError = validateDestinationParams(input.source, destination);\n\t\t\t\tif (destError) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsuccess: false,\n\t\t\t\t\t\terror: { code: \"VALIDATION_ERROR\", message: destError },\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Check for duplicate source (exact match only for non-patterns)\n\t\tconst existing = await repo.findBySource(input.source);\n\t\tif (existing) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"CONFLICT\",\n\t\t\t\t\tmessage: `A redirect from \"${input.source}\" already exists`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Check for redirect loops (skip if creating as disabled, or terminal —\n\t\t// a Gone rule has no destination, so it can't form a loop)\n\t\tif (!terminal && input.enabled !== false) {\n\t\t\tconst edges = toEdges(await repo.findAllEnabled());\n\t\t\tconst loopPath = wouldCreateLoop(input.source, destination, edges);\n\t\t\tif (loopPath) return loopError(loopPath);\n\t\t}\n\n\t\tconst redirect = await repo.create({\n\t\t\tsource: input.source,\n\t\t\tdestination,\n\t\t\ttype,\n\t\t\tisPattern: sourceIsPattern,\n\t\t\tenabled: input.enabled ?? true,\n\t\t\tgroupName: input.groupName ?? null,\n\t\t});\n\n\t\treturn { success: true, data: redirect };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"REDIRECT_CREATE_ERROR\", message: \"Failed to create redirect\" },\n\t\t};\n\t}\n}\n\n/**\n * Get a redirect by ID\n */\nexport async function handleRedirectGet(\n\tdb: Kysely<Database>,\n\tid: string,\n): Promise<ApiResult<Redirect>> {\n\ttry {\n\t\tconst repo = new RedirectRepository(db);\n\t\tconst redirect = await repo.findById(id);\n\n\t\tif (!redirect) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: `Redirect \"${id}\" not found` },\n\t\t\t};\n\t\t}\n\n\t\treturn { success: true, data: redirect };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"REDIRECT_GET_ERROR\", message: \"Failed to fetch redirect\" },\n\t\t};\n\t}\n}\n\n/**\n * Update a redirect by ID\n */\nexport async function handleRedirectUpdate(\n\tdb: Kysely<Database>,\n\tid: string,\n\tinput: {\n\t\tsource?: string;\n\t\tdestination?: string;\n\t\ttype?: number;\n\t\tenabled?: boolean;\n\t\tgroupName?: string | null;\n\t},\n): Promise<ApiResult<Redirect>> {\n\ttry {\n\t\tconst repo = new RedirectRepository(db);\n\n\t\tconst existing = await repo.findById(id);\n\t\tif (!existing) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: `Redirect \"${id}\" not found` },\n\t\t\t};\n\t\t}\n\n\t\tconst newSource = input.source ?? existing.source;\n\t\tconst newDest = input.destination ?? existing.destination;\n\n\t\t// Source and destination must differ\n\t\tif (newSource === newDest) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage: \"Source and destination must be different\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// If source is changing, validate patterns\n\t\tif (input.source !== undefined) {\n\t\t\tconst sourceIsPattern = isPattern(input.source);\n\t\t\tif (sourceIsPattern) {\n\t\t\t\tconst patternError = validatePattern(input.source);\n\t\t\t\tif (patternError) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsuccess: false,\n\t\t\t\t\t\terror: {\n\t\t\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\t\t\tmessage: `Invalid source pattern: ${patternError}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Check for duplicate source (exclude self)\n\t\t\tconst dup = await repo.findBySource(input.source);\n\t\t\tif (dup && dup.id !== id) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcode: \"CONFLICT\",\n\t\t\t\t\t\tmessage: `A redirect from \"${input.source}\" already exists`,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\t// Validate destination params against the (possibly updated) source\n\t\tconst newSourceIsPattern = isPattern(newSource);\n\t\tif (newSourceIsPattern) {\n\t\t\tconst destError = validateDestinationParams(newSource, newDest);\n\t\t\tif (destError) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: { code: \"VALIDATION_ERROR\", message: destError },\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\t// Check for redirect loops if source or destination changed\n\t\tif (input.source !== undefined || input.destination !== undefined) {\n\t\t\tconst edges = toEdges(await repo.findAllEnabled());\n\t\t\tconst loopPath = wouldCreateLoop(newSource, newDest, edges, id);\n\t\t\tif (loopPath) return loopError(loopPath);\n\t\t}\n\n\t\tconst updated = await repo.update(id, {\n\t\t\tsource: input.source,\n\t\t\tdestination: input.destination,\n\t\t\ttype: input.type,\n\t\t\tenabled: input.enabled,\n\t\t\tgroupName: input.groupName,\n\t\t});\n\n\t\tif (!updated) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"REDIRECT_UPDATE_ERROR\", message: \"Failed to update redirect\" },\n\t\t\t};\n\t\t}\n\n\t\t// Recompute cache — redirect was modified, so re-fetch\n\t\tawait updateLoopCache(db);\n\n\t\treturn { success: true, data: updated };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"REDIRECT_UPDATE_ERROR\", message: \"Failed to update redirect\" },\n\t\t};\n\t}\n}\n\n/**\n * Delete a redirect by ID\n */\nexport async function handleRedirectDelete(\n\tdb: Kysely<Database>,\n\tid: string,\n): Promise<ApiResult<{ deleted: true }>> {\n\ttry {\n\t\tconst repo = new RedirectRepository(db);\n\t\tconst deleted = await repo.delete(id);\n\n\t\tif (!deleted) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: `Redirect \"${id}\" not found` },\n\t\t\t};\n\t\t}\n\n\t\tawait updateLoopCache(db);\n\n\t\treturn { success: true, data: { deleted: true } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"REDIRECT_DELETE_ERROR\", message: \"Failed to delete redirect\" },\n\t\t};\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Loop analysis cache\n// ---------------------------------------------------------------------------\n\nfunction loopError(loopPath: string[]): ApiResult<never> {\n\tconst hops = loopPath\n\t\t.slice(0, -1)\n\t\t.map((p, i) => `${p} \\u2192 ${loopPath[i + 1]}`)\n\t\t.join(\"\\n\");\n\treturn {\n\t\tsuccess: false,\n\t\terror: {\n\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\tmessage: `This redirect would create a loop:\\n${hops}`,\n\t\t},\n\t};\n}\n\nfunction toEdges(redirects: Redirect[]): RedirectEdge[] {\n\treturn redirects.map((r) => ({\n\t\tid: r.id,\n\t\tsource: r.source,\n\t\tdestination: r.destination,\n\t\tenabled: r.enabled,\n\t\tisPattern: r.isPattern,\n\t}));\n}\n\nconst LOOP_CACHE_KEY = \"_redirect_loop_ids\";\n\n/**\n * Recompute loop redirect IDs and store in the options table.\n */\nasync function updateLoopCache(db: Kysely<Database>): Promise<void> {\n\ttry {\n\t\tconst options = new OptionsRepository(db);\n\t\tconst edges = toEdges(await new RedirectRepository(db).findAllEnabled());\n\t\tconst loopRedirectIds = detectLoops(edges);\n\t\tawait options.set(LOOP_CACHE_KEY, loopRedirectIds);\n\t} catch (error) {\n\t\tconsole.error(\"Failed to update redirect loop cache:\", error);\n\t}\n}\n\n/**\n * Get loop redirect IDs from cache, computing lazily on first access.\n */\nasync function getLoopRedirectIds(db: Kysely<Database>): Promise<string[]> {\n\ttry {\n\t\tconst options = new OptionsRepository(db);\n\t\tconst cached = await options.get<string[]>(LOOP_CACHE_KEY);\n\t\tif (cached !== null) return cached;\n\n\t\t// First access after upgrade — compute and cache\n\t\tawait updateLoopCache(db);\n\t\treturn (await options.get<string[]>(LOOP_CACHE_KEY)) ?? [];\n\t} catch {\n\t\treturn [];\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// 404 Log\n// ---------------------------------------------------------------------------\n\n/**\n * List 404 log entries with cursor pagination\n */\nexport async function handleNotFoundList(\n\tdb: Kysely<Database>,\n\tparams: { cursor?: string; limit?: number; search?: string },\n): Promise<ApiResult<FindManyResult<NotFoundEntry>>> {\n\ttry {\n\t\tconst repo = new RedirectRepository(db);\n\t\tconst result = await repo.find404s(params);\n\t\treturn { success: true, data: result };\n\t} catch (error) {\n\t\tif (error instanceof InvalidCursorError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_CURSOR\", message: error.message },\n\t\t\t};\n\t\t}\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"NOT_FOUND_LIST_ERROR\", message: \"Failed to fetch 404 log\" },\n\t\t};\n\t}\n}\n\n/**\n * Get 404 summary (grouped by path, sorted by count)\n */\nexport async function handleNotFoundSummary(\n\tdb: Kysely<Database>,\n\tlimit?: number,\n): Promise<ApiResult<{ items: NotFoundSummary[] }>> {\n\ttry {\n\t\tconst repo = new RedirectRepository(db);\n\t\tconst items = await repo.get404Summary(limit);\n\t\treturn { success: true, data: { items } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"NOT_FOUND_SUMMARY_ERROR\", message: \"Failed to fetch 404 summary\" },\n\t\t};\n\t}\n}\n\n/**\n * Clear all 404 log entries\n */\nexport async function handleNotFoundClear(\n\tdb: Kysely<Database>,\n): Promise<ApiResult<{ deleted: number }>> {\n\ttry {\n\t\tconst repo = new RedirectRepository(db);\n\t\tconst deleted = await repo.clear404s();\n\t\treturn { success: true, data: { deleted } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"NOT_FOUND_CLEAR_ERROR\", message: \"Failed to clear 404 log\" },\n\t\t};\n\t}\n}\n\n/**\n * Prune 404 log entries older than a given date\n */\nexport async function handleNotFoundPrune(\n\tdb: Kysely<Database>,\n\tolderThan: string,\n): Promise<ApiResult<{ deleted: number }>> {\n\ttry {\n\t\tconst repo = new RedirectRepository(db);\n\t\tconst deleted = await repo.prune404s(olderThan);\n\t\treturn { success: true, data: { deleted } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"NOT_FOUND_PRUNE_ERROR\", message: \"Failed to prune 404 log\" },\n\t\t};\n\t}\n}\n"],"mappings":"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"}
@@ -1,13 +1,14 @@
1
- import { c as __exportAll } from "./runner-77GOZCnH.mjs";
1
+ import { c as __exportAll } from "./runner-BLIDKpmA.mjs";
2
2
  import { t as validateIdentifier } from "./validate-BpwoDOgt.mjs";
3
3
  import { a as currentTimestamp, p as tableExists, u as listTablesLike } from "./dialect-helpers-BSbfaO3F.mjs";
4
4
  import { c as isIndexableFieldType, o as RESERVED_COLLECTION_SLUGS, r as FIELD_TYPE_TO_COLUMN, s as RESERVED_FIELD_SLUGS } from "./types-75KIqzL9.mjs";
5
- import { n as chunks, t as SQL_BATCH_SIZE } from "./chunks-bNiX6_DR.mjs";
5
+ import { n as chunks, t as SQL_BATCH_SIZE } from "./chunks-C4szrD8o.mjs";
6
6
  import { t as withTransaction } from "./transaction-0KOpOyMu.mjs";
7
- import { t as FTSManager } from "./fts-manager-BhOdW0bi.mjs";
7
+ import { t as FTSManager } from "./fts-manager-Do1BlW2r.mjs";
8
+ import { t as refreshDevTypes } from "./dev-typegen-DzOPfsNj.mjs";
8
9
  import { c as installPreparedMediaUsageCollectionCapture, i as canResumeMediaUsageCollectionCapture, l as markMediaUsageCollectionCaptureReady, o as finalizeMediaUsageCollectionCapture, u as prepareMediaUsageCollectionCapture } from "./activation-K0kGBWuJ.mjs";
9
- import { a as deleteContentMediaUsageCollection, l as markContentMediaUsageCollectionStaleSafely, s as invalidateContentMediaUsageSchemaChange } from "./content-refresh-DLOfIhYc.mjs";
10
- import { i as isMediaUsageCollectionSlugDeleting, r as deleteActivatedMediaUsageCollection } from "./collection-deletion-m_oi2c0N.mjs";
10
+ import { i as isMediaUsageCollectionSlugDeleting, r as deleteActivatedMediaUsageCollection } from "./collection-deletion-Bj07NHbj.mjs";
11
+ import { a as deleteContentMediaUsageCollection, l as markContentMediaUsageCollectionStaleSafely, s as invalidateContentMediaUsageSchemaChange } from "./content-refresh-Bwp-8yGy.mjs";
11
12
  import { sql } from "kysely";
12
13
  import { ulid } from "ulidx";
13
14
 
@@ -180,6 +181,12 @@ var SchemaRegistry = class {
180
181
  this.db = db;
181
182
  }
182
183
  /**
184
+ * Notify the dev typegen hook that the schema has changed.
185
+ */
186
+ notifyTypegen() {
187
+ refreshDevTypes(this.db);
188
+ }
189
+ /**
183
190
  * List all collections
184
191
  */
185
192
  async listCollections() {
@@ -319,6 +326,7 @@ var SchemaRegistry = class {
319
326
  });
320
327
  const collection = await this.getCollection(input.slug);
321
328
  if (!collection) throw new SchemaError("Failed to create collection", "CREATE_FAILED");
329
+ this.notifyTypegen();
322
330
  return collection;
323
331
  }
324
332
  /**
@@ -444,6 +452,7 @@ var SchemaRegistry = class {
444
452
  if (schemaMutated) await markContentMediaUsageCollectionStaleSafely(this.db, input.slug, "CONTENT_USAGE_STALE");
445
453
  throw error;
446
454
  }
455
+ this.notifyTypegen();
447
456
  }
448
457
  async assertSeedFieldDefinitions(collectionId, fields, db) {
449
458
  const stored = await db.selectFrom("_emdash_fields").selectAll().where("collection_id", "=", collectionId).execute();
@@ -461,7 +470,7 @@ var SchemaRegistry = class {
461
470
  * Update a collection
462
471
  */
463
472
  async updateCollection(slug, input) {
464
- return withTransaction(this.db, async (trx) => {
473
+ const updated = await withTransaction(this.db, async (trx) => {
465
474
  const existingRow = await trx.selectFrom("_emdash_collections").where("slug", "=", slug).selectAll().executeTakeFirst();
466
475
  if (!existingRow) throw new SchemaError(`Collection "${slug}" not found`, "COLLECTION_NOT_FOUND");
467
476
  const existing = this.mapCollectionRow(existingRow);
@@ -497,6 +506,8 @@ var SchemaRegistry = class {
497
506
  }
498
507
  return this.mapCollectionRow(row);
499
508
  });
509
+ this.notifyTypegen();
510
+ return updated;
500
511
  }
501
512
  /**
502
513
  * Delete a collection
@@ -527,6 +538,7 @@ var SchemaRegistry = class {
527
538
  if (contentTableDropped && !await tableExists(this.db, this.getTableName(slug))) await deleteContentMediaUsageCollection(this.db, slug);
528
539
  throw error;
529
540
  }
541
+ this.notifyTypegen();
530
542
  }
531
543
  /**
532
544
  * List fields for a collection
@@ -593,6 +605,7 @@ var SchemaRegistry = class {
593
605
  });
594
606
  if (activeCoverageInvalidated) await invalidateContentMediaUsageSchemaChange(this.db, collectionSlug);
595
607
  else await markContentMediaUsageCollectionStaleSafely(this.db, collectionSlug, "CONTENT_USAGE_STALE");
608
+ this.notifyTypegen();
596
609
  return created;
597
610
  } catch (error) {
598
611
  if (schemaMutated) if (activeCoverageInvalidated) await invalidateContentMediaUsageSchemaChange(this.db, collectionSlug);
@@ -663,6 +676,7 @@ var SchemaRegistry = class {
663
676
  });
664
677
  if (schemaMutated) if (activeCoverageInvalidated) await invalidateContentMediaUsageSchemaChange(this.db, collectionSlug);
665
678
  else await markContentMediaUsageCollectionStaleSafely(this.db, collectionSlug, "CONTENT_USAGE_STALE");
679
+ this.notifyTypegen();
666
680
  return updatedField;
667
681
  } catch (error) {
668
682
  if (schemaMutated) if (activeCoverageInvalidated) await invalidateContentMediaUsageSchemaChange(this.db, collectionSlug);
@@ -727,6 +741,7 @@ var SchemaRegistry = class {
727
741
  else await markContentMediaUsageCollectionStaleSafely(this.db, collectionSlug, "CONTENT_USAGE_STALE");
728
742
  throw error;
729
743
  }
744
+ this.notifyTypegen();
730
745
  }
731
746
  /**
732
747
  * Reorder collections in the admin sidebar.
@@ -755,6 +770,7 @@ var SchemaRegistry = class {
755
770
  updated_at: now
756
771
  }).where("slug", "in", slugChunk).execute();
757
772
  });
773
+ this.notifyTypegen();
758
774
  }
759
775
  /**
760
776
  * Reorder fields
@@ -763,6 +779,7 @@ var SchemaRegistry = class {
763
779
  const collection = await this.getCollection(collectionSlug);
764
780
  if (!collection) throw new SchemaError(`Collection "${collectionSlug}" not found`, "COLLECTION_NOT_FOUND");
765
781
  for (let i = 0; i < fieldSlugs.length; i++) await this.db.updateTable("_emdash_fields").set({ sort_order: i }).where("collection_id", "=", collection.id).where("slug", "=", fieldSlugs[i]).execute();
782
+ this.notifyTypegen();
766
783
  }
767
784
  /**
768
785
  * Create a content table for a collection
@@ -788,8 +805,8 @@ var SchemaRegistry = class {
788
805
  ON ${sql.ref(tableName)} (slug)
789
806
  `.execute(conn);
790
807
  await sql`
791
- ${createIndex} ${sql.ref(`idx_${tableName}_scheduled`)}
792
- ON ${sql.ref(tableName)} (scheduled_at)
808
+ ${createIndex} ${sql.ref(`idx_${tableName}_del_sched`)}
809
+ ON ${sql.ref(tableName)} (deleted_at, scheduled_at)
793
810
  WHERE scheduled_at IS NOT NULL
794
811
  `.execute(conn);
795
812
  await sql`
@@ -1142,6 +1159,7 @@ var SchemaRegistry = class {
1142
1159
  const collection = await this.getCollection(slug);
1143
1160
  if (!collection) throw new SchemaError("Failed to register orphaned table", "REGISTER_FAILED");
1144
1161
  await markContentMediaUsageCollectionStaleSafely(this.db, slug, "CONTENT_USAGE_STALE");
1162
+ this.notifyTypegen();
1145
1163
  return collection;
1146
1164
  } catch (error) {
1147
1165
  if (collectionRegistered) await markContentMediaUsageCollectionStaleSafely(this.db, slug, "CONTENT_USAGE_STALE");
@@ -1158,4 +1176,4 @@ var SchemaRegistry = class {
1158
1176
 
1159
1177
  //#endregion
1160
1178
  export { SchemaRegistry as n, registry_exports as r, SchemaError as t };
1161
- //# sourceMappingURL=registry-Cx-jEJOS.mjs.map
1179
+ //# sourceMappingURL=registry-AZAsRsor.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry-AZAsRsor.mjs","names":[],"sources":["../src/schema/registry.ts"],"sourcesContent":["import type {\n\tColumnDataType,\n\tCreateTableBuilder,\n\tInsertable,\n\tKysely,\n\tSelectable,\n\tUpdateable,\n} from \"kysely\";\nimport { sql } from \"kysely\";\nimport { ulid } from \"ulidx\";\n\nimport { refreshDevTypes } from \"../astro/dev-typegen.js\";\nimport { currentTimestamp, listTablesLike, tableExists } from \"../database/dialect-helpers.js\";\nimport { withTransaction } from \"../database/transaction.js\";\nimport type { CollectionTable, Database, FieldTable } from \"../database/types.js\";\nimport { validateIdentifier } from \"../database/validate.js\";\nimport {\n\tcanResumeMediaUsageCollectionCapture,\n\tfinalizeMediaUsageCollectionCapture,\n\tinstallPreparedMediaUsageCollectionCapture,\n\tmarkMediaUsageCollectionCaptureReady,\n\tprepareMediaUsageCollectionCapture,\n} from \"../media/usage/activation.js\";\nimport {\n\tdeleteActivatedMediaUsageCollection,\n\tisMediaUsageCollectionSlugDeleting,\n} from \"../media/usage/collection-deletion.js\";\nimport {\n\tdeleteContentMediaUsageCollection,\n\tinvalidateContentMediaUsageSchemaChange,\n\tmarkContentMediaUsageCollectionStaleSafely,\n} from \"../media/usage/content-refresh.js\";\nimport { FTSManager } from \"../search/fts-manager.js\";\nimport { chunks, SQL_BATCH_SIZE } from \"../utils/chunks.js\";\nimport {\n\ttype Collection,\n\ttype CollectionAdminConfig,\n\ttype CollectionSource,\n\ttype CollectionSupport,\n\ttype ColumnType,\n\ttype Field,\n\ttype CreateCollectionInput,\n\ttype UpdateCollectionInput,\n\ttype CreateFieldInput,\n\ttype UpdateFieldInput,\n\ttype CollectionWithFields,\n\ttype FieldType,\n\tFIELD_TYPE_TO_COLUMN,\n\tisIndexableFieldType,\n\tRESERVED_FIELD_SLUGS,\n\tRESERVED_COLLECTION_SLUGS,\n} from \"./types.js\";\n\n// Regex patterns for schema registry\nconst SLUG_VALIDATION_PATTERN = /^[a-z][a-z0-9_]*$/;\nconst EC_PREFIX_PATTERN = /^ec_/;\nconst SINGLE_QUOTE_PATTERN = /'/g;\nconst UNDERSCORE_PATTERN = /_/g;\nconst WORD_BOUNDARY_PATTERN = /\\b\\w/g;\nconst FIELD_ID_PATTERN = /^[0-9A-Z]{26}$/;\n\n/** Valid column types for runtime validation */\nconst COLUMN_TYPES: ReadonlySet<string> = new Set([\"TEXT\", \"REAL\", \"INTEGER\", \"JSON\"]);\nconst COLUMN_TYPE_TO_DATA_TYPE = {\n\tTEXT: \"text\",\n\tREAL: \"real\",\n\tINTEGER: \"integer\",\n\tJSON: \"json\",\n} satisfies Record<ColumnType, ColumnDataType>;\nconst TEXT_ALIAS_FIELD_TYPES: ReadonlySet<FieldType> = new Set([\"string\", \"text\", \"slug\"]);\n\n/** Field types usable as a `titleField` — plain text that reads well as a title. */\nconst TITLE_FIELD_TYPES: ReadonlySet<string> = new Set([\"string\", \"text\", \"slug\"]);\n\n/** Valid collection source prefixes/values */\nconst VALID_SOURCES: ReadonlySet<string> = new Set([\"manual\", \"discovered\", \"seed\"]);\n\nfunction isCollectionSource(value: string): value is CollectionSource {\n\treturn VALID_SOURCES.has(value) || value.startsWith(\"template:\") || value.startsWith(\"import:\");\n}\n\nfunction isFieldType(value: string): value is FieldType {\n\treturn value in FIELD_TYPE_TO_COLUMN;\n}\n\nfunction isColumnType(value: string): value is ColumnType {\n\treturn COLUMN_TYPES.has(value);\n}\n\nconst VALID_COLLECTION_SUPPORTS: ReadonlySet<string> = new Set<CollectionSupport>([\n\t\"drafts\",\n\t\"revisions\",\n\t\"preview\",\n\t\"scheduling\",\n\t\"search\",\n\t\"seo\",\n]);\n\n// Each _emdash_fields row uses 16 bound parameters. Six rows keep every\n// multi-row INSERT below D1's 100-parameter statement limit.\nconst SEED_FIELD_INSERT_BATCH_SIZE = 6;\n\n/**\n * Rank given to collections without an explicit `sort_order`. SQLite sorts\n * NULL first on ASC while Postgres sorts it last, so the fallback is\n * materialised with COALESCE rather than left to the dialect.\n */\nconst UNORDERED_COLLECTION_RANK = 2147483647;\n\n/**\n * Collection ordering shared by every list read: explicit `sort_order`\n * first (ascending), then alphabetically by slug.\n */\nconst collectionOrder = sql<number>`coalesce(sort_order, ${sql.lit(UNORDERED_COLLECTION_RANK)})`;\n\nfunction assertIndexableField(type: FieldType, indexed: boolean | undefined, slug: string): void {\n\tif (indexed && !isIndexableFieldType(type)) {\n\t\tthrow new SchemaError(\n\t\t\t`Field \"${slug}\" cannot be indexed because type \"${type}\" is not a scalar query type`,\n\t\t\t\"FIELD_NOT_INDEXABLE\",\n\t\t);\n\t}\n}\n\nfunction isCollectionSupport(value: unknown): value is CollectionSupport {\n\treturn typeof value === \"string\" && VALID_COLLECTION_SUPPORTS.has(value);\n}\n\n/**\n * Parse a collection's `supports` column (stored as a JSON array of\n * CollectionSupport keys). Unknown/invalid entries are filtered out so the\n * runtime value matches the declared `CollectionSupport[]` type.\n *\n * Throws on malformed JSON so corruption surfaces loudly; returns an empty\n * array only for explicitly null/empty values or non-array JSON.\n */\nfunction parseSupports(raw: string | null | undefined): CollectionSupport[] {\n\tif (!raw) return [];\n\tconst parsed: unknown = JSON.parse(raw);\n\tif (!Array.isArray(parsed)) return [];\n\treturn parsed.filter(isCollectionSupport);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction parseCollectionAdmin(raw: string | null | undefined): CollectionAdminConfig | undefined {\n\tif (!raw) return undefined;\n\tconst parsed: unknown = JSON.parse(raw);\n\tif (!isRecord(parsed)) return undefined;\n\tconst listColumns = parsed.listColumns;\n\treturn {\n\t\tlistColumns: Array.isArray(listColumns)\n\t\t\t? listColumns.filter((value): value is string => typeof value === \"string\")\n\t\t\t: undefined,\n\t};\n}\n\nexport async function buildSeedCollectionCaptureFingerprint(\n\tinput: Omit<CreateCollectionInput, \"source\">,\n\tfields: readonly CreateFieldInput[],\n): Promise<string> {\n\tconst supports = input.supports ?? [\"drafts\", \"revisions\"];\n\tconst hasSeo = input.hasSeo ?? supports.includes(\"seo\") ?? false;\n\tlet maxSortOrder = -1;\n\tconst definitions = fields.map((field) => {\n\t\tconst sortOrder = field.sortOrder ?? maxSortOrder + 1;\n\t\tmaxSortOrder = Math.max(maxSortOrder, sortOrder);\n\t\treturn {\n\t\t\tslug: field.slug,\n\t\t\tlabel: field.label,\n\t\t\ttype: field.type,\n\t\t\trequired: field.required ?? false,\n\t\t\tunique: field.unique ?? false,\n\t\t\tdefaultValue: field.defaultValue === undefined ? null : JSON.stringify(field.defaultValue),\n\t\t\tvalidation: field.validation ? JSON.stringify(field.validation) : null,\n\t\t\twidget: field.widget ?? null,\n\t\t\toptions: field.options ? JSON.stringify(field.options) : null,\n\t\t\tsortOrder,\n\t\t\tsearchable: field.searchable ?? false,\n\t\t\ttranslatable: field.translatable ?? true,\n\t\t};\n\t});\n\tconst payload = JSON.stringify(\n\t\tcanonicalizeFingerprintValue({\n\t\t\tversion: 1,\n\t\t\tcollection: {\n\t\t\t\tslug: input.slug,\n\t\t\t\tlabel: input.label,\n\t\t\t\tlabelSingular: input.labelSingular ?? null,\n\t\t\t\tdescription: input.description ?? null,\n\t\t\t\ticon: input.icon ?? null,\n\t\t\t\tadmin: input.admin ?? null,\n\t\t\t\tsupports,\n\t\t\t\thasSeo,\n\t\t\t\thidden: input.hidden ?? false,\n\t\t\t\tsortOrder: input.sortOrder ?? null,\n\t\t\t\tcommentsEnabled: input.commentsEnabled ?? false,\n\t\t\t\turlPattern: input.urlPattern ?? null,\n\t\t\t\troutable: input.routable ?? true,\n\t\t\t},\n\t\t\tfields: definitions,\n\t\t}),\n\t);\n\tconst digest = await crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(payload));\n\tconst hex = Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, \"0\")).join(\n\t\t\"\",\n\t);\n\treturn `media-usage-seed:v1:sha256:${hex}`;\n}\n\nfunction canonicalizeFingerprintValue(value: unknown): unknown {\n\tif (value === undefined) return { __emdashUndefined: true };\n\tif (Array.isArray(value)) return value.map(canonicalizeFingerprintValue);\n\tif (typeof value !== \"object\" || value === null) return value;\n\n\tconst canonical: Record<string, unknown> = {};\n\tfor (const [key, entry] of Object.entries(value).toSorted(([a], [b]) => a.localeCompare(b))) {\n\t\tcanonical[key] = canonicalizeFingerprintValue(entry);\n\t}\n\treturn canonical;\n}\n\n/**\n * Error thrown when a schema operation fails\n */\nexport class SchemaError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic code: string,\n\t\tpublic details?: Record<string, unknown>,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"SchemaError\";\n\t}\n}\n\n/**\n * Schema Registry\n *\n * Manages collection and field definitions stored in D1.\n * Handles runtime DDL operations (CREATE TABLE, ALTER TABLE).\n */\nexport class SchemaRegistry {\n\tconstructor(private db: Kysely<Database>) {}\n\n\t/**\n\t * Notify the dev typegen hook that the schema has changed.\n\t */\n\tprivate notifyTypegen(): void {\n\t\trefreshDevTypes(this.db);\n\t}\n\n\t// ============================================\n\t// Collection Operations\n\t// ============================================\n\n\t/**\n\t * List all collections\n\t */\n\tasync listCollections(): Promise<Collection[]> {\n\t\tconst rows = await this.db\n\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t.selectAll()\n\t\t\t.orderBy(collectionOrder, \"asc\")\n\t\t\t.orderBy(\"slug\", \"asc\")\n\t\t\t.execute();\n\n\t\treturn rows.map(this.mapCollectionRow);\n\t}\n\n\t/**\n\t * Get a collection by slug\n\t */\n\tasync getCollection(slug: string): Promise<Collection | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t.where(\"slug\", \"=\", slug)\n\t\t\t.selectAll()\n\t\t\t.executeTakeFirst();\n\n\t\treturn row ? this.mapCollectionRow(row) : null;\n\t}\n\n\t/**\n\t * Get a collection with all its fields\n\t */\n\tasync getCollectionWithFields(slug: string): Promise<CollectionWithFields | null> {\n\t\tconst collection = await this.getCollection(slug);\n\t\tif (!collection) return null;\n\n\t\tconst fields = await this.listFields(collection.id);\n\n\t\treturn { ...collection, fields };\n\t}\n\n\t/**\n\t * List every collection together with its fields in O(1) query shapes\n\t * — one for collections, then one batched query for the fields of every\n\t * returned collection — instead of the N+1 pattern of `listCollections`\n\t * + per-collection `listFields`. The fields query is chunked at\n\t * `SQL_BATCH_SIZE` to stay under D1's bound-parameter limit, so on\n\t * sites with more than `SQL_BATCH_SIZE` collections the field fetch\n\t * becomes `ceil(collectionCount / SQL_BATCH_SIZE)` queries — still\n\t * a constant factor, not N+1. Typical sites have well under\n\t * `SQL_BATCH_SIZE` collections, so this is two queries in practice.\n\t *\n\t * Used by the manifest build, which previously paid N+1 round-trips on\n\t * every admin request. Each round-trip costs ~80–150ms against the D1\n\t * primary on a busy link, so a 10-collection site spent ~1 s rebuilding\n\t * a manifest that is now built fresh per admin request (no cache).\n\t */\n\tasync listCollectionsWithFields(): Promise<CollectionWithFields[]> {\n\t\tconst collectionRows = await this.db\n\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t.selectAll()\n\t\t\t.orderBy(collectionOrder, \"asc\")\n\t\t\t.orderBy(\"slug\", \"asc\")\n\t\t\t.execute();\n\n\t\tif (collectionRows.length === 0) return [];\n\n\t\tconst fieldsByCollection = new Map<string, Field[]>();\n\t\t// Chunk to stay under D1's bound-parameter limit. Typical sites have\n\t\t// well under SQL_BATCH_SIZE collections, so this is a single query\n\t\t// in practice; on larger sites it becomes a small constant number\n\t\t// of queries, never N+1.\n\t\tfor (const idChunk of chunks(\n\t\t\tcollectionRows.map((c) => c.id),\n\t\t\tSQL_BATCH_SIZE,\n\t\t)) {\n\t\t\tconst fieldRows = await this.db\n\t\t\t\t.selectFrom(\"_emdash_fields\")\n\t\t\t\t.where(\"collection_id\", \"in\", idChunk)\n\t\t\t\t.selectAll()\n\t\t\t\t.orderBy(\"collection_id\", \"asc\")\n\t\t\t\t.orderBy(\"sort_order\", \"asc\")\n\t\t\t\t.orderBy(\"created_at\", \"asc\")\n\t\t\t\t.execute();\n\t\t\tfor (const row of fieldRows) {\n\t\t\t\tconst list = fieldsByCollection.get(row.collection_id) ?? [];\n\t\t\t\tlist.push(this.mapFieldRow(row));\n\t\t\t\tfieldsByCollection.set(row.collection_id, list);\n\t\t\t}\n\t\t}\n\n\t\treturn collectionRows.map((c) => ({\n\t\t\t...this.mapCollectionRow(c),\n\t\t\tfields: fieldsByCollection.get(c.id) ?? [],\n\t\t}));\n\t}\n\n\t/**\n\t * Validate `titleField`/`dateField` against the collection's fields:\n\t * `titleField` must be a text-like field; `dateField` must be a `datetime` field.\n\t * Only truthy values are checked (undefined = unchanged, null/\"\" = cleared).\n\t */\n\tprivate async validateTitleDateFields(\n\t\tcollectionId: string,\n\t\tcollectionSlug: string,\n\t\tinput: { titleField?: string | null; dateField?: string | null },\n\t\tdb: Kysely<Database> = this.db,\n\t): Promise<void> {\n\t\tconst slugs = [input.titleField, input.dateField].filter((slug): slug is string => !!slug);\n\t\tif (slugs.length === 0) return;\n\n\t\tconst rows = await db\n\t\t\t.selectFrom(\"_emdash_fields\")\n\t\t\t.where(\"collection_id\", \"=\", collectionId)\n\t\t\t.where(\"slug\", \"in\", slugs)\n\t\t\t.select([\"slug\", \"type\"])\n\t\t\t.execute();\n\t\tconst typeBySlug = new Map(rows.map((row) => [row.slug, row.type]));\n\n\t\tif (input.titleField) {\n\t\t\tconst type = typeBySlug.get(input.titleField);\n\t\t\tif (type === undefined) {\n\t\t\t\tthrow new SchemaError(\n\t\t\t\t\t`titleField \"${input.titleField}\" is not a field on \"${collectionSlug}\"`,\n\t\t\t\t\t\"INVALID_TITLE_FIELD\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (!TITLE_FIELD_TYPES.has(type)) {\n\t\t\t\tthrow new SchemaError(\n\t\t\t\t\t`titleField \"${input.titleField}\" must be a text field (got \"${type}\")`,\n\t\t\t\t\t\"INVALID_TITLE_FIELD\",\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (input.dateField) {\n\t\t\tconst type = typeBySlug.get(input.dateField);\n\t\t\tif (type === undefined) {\n\t\t\t\tthrow new SchemaError(\n\t\t\t\t\t`dateField \"${input.dateField}\" is not a field on \"${collectionSlug}\"`,\n\t\t\t\t\t\"INVALID_DATE_FIELD\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (type !== \"datetime\") {\n\t\t\t\tthrow new SchemaError(\n\t\t\t\t\t`dateField \"${input.dateField}\" must be a datetime field (got \"${type}\")`,\n\t\t\t\t\t\"INVALID_DATE_FIELD\",\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Create a new collection\n\t */\n\tasync createCollection(input: CreateCollectionInput): Promise<Collection> {\n\t\t// Validate slug\n\t\tthis.validateSlug(input.slug, \"collection\");\n\t\tif (RESERVED_COLLECTION_SLUGS.includes(input.slug)) {\n\t\t\tthrow new SchemaError(`Collection slug \"${input.slug}\" is reserved`, \"RESERVED_SLUG\");\n\t\t}\n\t\tif (await isMediaUsageCollectionSlugDeleting(this.db, input.slug)) {\n\t\t\tthrow new SchemaError(`Collection \"${input.slug}\" already exists`, \"COLLECTION_EXISTS\");\n\t\t}\n\n\t\t// Check if collection already exists\n\t\tconst existing = await this.getCollection(input.slug);\n\t\tif (await isMediaUsageCollectionSlugDeleting(this.db, input.slug)) {\n\t\t\tthrow new SchemaError(`Collection \"${input.slug}\" already exists`, \"COLLECTION_EXISTS\");\n\t\t}\n\t\tif (\n\t\t\texisting &&\n\t\t\t!(await canResumeMediaUsageCollectionCapture(this.db, {\n\t\t\t\tcollectionId: existing.id,\n\t\t\t\tcollectionSlug: existing.slug,\n\t\t\t}))\n\t\t) {\n\t\t\tthrow new SchemaError(`Collection \"${input.slug}\" already exists`, \"COLLECTION_EXISTS\");\n\t\t}\n\n\t\tconst proposedId = existing?.id ?? ulid();\n\n\t\t// Default `supports` to drafts + revisions when the caller didn't\n\t\t// specify it. Explicit empty array (`[]`) is preserved as an opt-out\n\t\t// — only `undefined` triggers the default. This is the canonical\n\t\t// default for new collections; the MCP and admin UI layers used to\n\t\t// duplicate this default but now defer to the registry.\n\t\tconst supports = input.supports ?? [\"drafts\", \"revisions\"];\n\n\t\t// Insert collection record and create content table in a transaction\n\t\t// so a failure in table creation doesn't leave an orphaned row.\n\t\t// Uses withTransaction for D1 compatibility (no transaction support).\n\t\t// Derive hasSeo from supports array if not explicitly set\n\t\tconst hasSeo = input.hasSeo ?? supports.includes(\"seo\") ?? false;\n\n\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\tconst capture = await prepareMediaUsageCollectionCapture(trx, {\n\t\t\t\tcollectionId: proposedId,\n\t\t\t\tcollectionSlug: input.slug,\n\t\t\t\tregisteredCollectionId: existing?.id,\n\t\t\t});\n\t\t\tconst values: Insertable<CollectionTable> = {\n\t\t\t\tid: capture.collectionId,\n\t\t\t\tslug: input.slug,\n\t\t\t\tlabel: input.label,\n\t\t\t\tlabel_singular: input.labelSingular ?? null,\n\t\t\t\tdescription: input.description ?? null,\n\t\t\t\ticon: input.icon ?? null,\n\t\t\t\tadmin_config: input.admin ? JSON.stringify(input.admin) : null,\n\t\t\t\tsupports: JSON.stringify(supports),\n\t\t\t\tsource: input.source ?? \"manual\",\n\t\t\t\thas_seo: hasSeo ? 1 : 0,\n\t\t\t\troutable: input.routable === false ? 0 : 1,\n\t\t\t\thidden: input.hidden ? 1 : 0,\n\t\t\t\tsort_order: input.sortOrder ?? null,\n\t\t\t\tcomments_enabled: input.commentsEnabled ? 1 : 0,\n\t\t\t\turl_pattern: input.urlPattern ?? null,\n\t\t\t};\n\n\t\t\tif (capture.captureRequired) {\n\t\t\t\tawait this.createContentTable(input.slug, trx, [], {\n\t\t\t\t\tifNotExists: capture.resuming,\n\t\t\t\t});\n\t\t\t\tawait installPreparedMediaUsageCollectionCapture(trx, {\n\t\t\t\t\tcollectionId: capture.collectionId,\n\t\t\t\t\tcollectionSlug: input.slug,\n\t\t\t\t});\n\t\t\t\tawait markMediaUsageCollectionCaptureReady(trx, {\n\t\t\t\t\tcollectionId: capture.collectionId,\n\t\t\t\t\tcollectionSlug: input.slug,\n\t\t\t\t});\n\t\t\t\tif (!capture.registrationExists) {\n\t\t\t\t\tawait trx.insertInto(\"_emdash_collections\").values(values).execute();\n\t\t\t\t}\n\t\t\t\tawait finalizeMediaUsageCollectionCapture(trx, {\n\t\t\t\t\tcollectionId: capture.collectionId,\n\t\t\t\t\tcollectionSlug: input.slug,\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tawait trx.insertInto(\"_emdash_collections\").values(values).execute();\n\t\t\tawait this.createContentTable(input.slug, trx);\n\t\t});\n\n\t\tconst collection = await this.getCollection(input.slug);\n\t\tif (!collection) {\n\t\t\tthrow new SchemaError(\"Failed to create collection\", \"CREATE_FAILED\");\n\t\t}\n\n\t\tthis.notifyTypegen();\n\t\treturn collection;\n\t}\n\n\t/**\n\t * Create a seed-owned collection and all of its fields in bulk.\n\t *\n\t * Fresh seeds can define dozens of fields. Creating them through\n\t * `createField` performs multiple reads, one ALTER TABLE, and one media\n\t * usage invalidation per field, which can exhaust D1's per-request query\n\t * budget. This path validates the full schema before mutating it, creates\n\t * the complete content table in one statement, and inserts field metadata\n\t * in parameter-safe batches.\n\t */\n\tasync createSeedCollection(\n\t\tinput: Omit<CreateCollectionInput, \"source\">,\n\t\tfields: readonly CreateFieldInput[],\n\t): Promise<void> {\n\t\tthis.validateSlug(input.slug, \"collection\");\n\t\tif (RESERVED_COLLECTION_SLUGS.includes(input.slug)) {\n\t\t\tthrow new SchemaError(`Collection slug \"${input.slug}\" is reserved`, \"RESERVED_SLUG\");\n\t\t}\n\t\tif (await isMediaUsageCollectionSlugDeleting(this.db, input.slug)) {\n\t\t\tthrow new SchemaError(`Collection \"${input.slug}\" already exists`, \"COLLECTION_EXISTS\");\n\t\t}\n\n\t\tconst fieldSlugs = new Set<string>();\n\t\tfor (const field of fields) {\n\t\t\tthis.validateSlug(field.slug, \"field\");\n\t\t\tassertIndexableField(field.type, field.indexed, field.slug);\n\t\t\tif (RESERVED_FIELD_SLUGS.includes(field.slug)) {\n\t\t\t\tthrow new SchemaError(`Field slug \"${field.slug}\" is reserved`, \"RESERVED_SLUG\");\n\t\t\t}\n\t\t\tif (fieldSlugs.has(field.slug)) {\n\t\t\t\tthrow new SchemaError(\n\t\t\t\t\t`Field \"${field.slug}\" already exists in collection \"${input.slug}\"`,\n\t\t\t\t\t\"FIELD_EXISTS\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tfieldSlugs.add(field.slug);\n\t\t}\n\n\t\tconst supports = input.supports ?? [\"drafts\", \"revisions\"];\n\t\tconst hasSeo = input.hasSeo ?? supports.includes(\"seo\") ?? false;\n\t\tconst creationFingerprint = await buildSeedCollectionCaptureFingerprint(input, fields);\n\t\tconst existing = await this.getCollection(input.slug);\n\t\tif (await isMediaUsageCollectionSlugDeleting(this.db, input.slug)) {\n\t\t\tthrow new SchemaError(`Collection \"${input.slug}\" already exists`, \"COLLECTION_EXISTS\");\n\t\t}\n\t\tif (\n\t\t\texisting &&\n\t\t\t!(await canResumeMediaUsageCollectionCapture(this.db, {\n\t\t\t\tcollectionId: existing.id,\n\t\t\t\tcollectionSlug: existing.slug,\n\t\t\t\tcreationFingerprint,\n\t\t\t}))\n\t\t) {\n\t\t\tthrow new SchemaError(`Collection \"${input.slug}\" already exists`, \"COLLECTION_EXISTS\");\n\t\t}\n\n\t\tconst proposedCollectionId = existing?.id ?? ulid();\n\t\tlet maxSortOrder = -1;\n\t\tconst fieldRows: Insertable<FieldTable>[] = fields.map((field) => {\n\t\t\tconst sortOrder = field.sortOrder ?? maxSortOrder + 1;\n\t\t\tmaxSortOrder = Math.max(maxSortOrder, sortOrder);\n\n\t\t\treturn {\n\t\t\t\tid: ulid(),\n\t\t\t\tcollection_id: proposedCollectionId,\n\t\t\t\tslug: field.slug,\n\t\t\t\tlabel: field.label,\n\t\t\t\ttype: field.type,\n\t\t\t\tcolumn_type: FIELD_TYPE_TO_COLUMN[field.type],\n\t\t\t\trequired: field.required ? 1 : 0,\n\t\t\t\tunique: field.unique ? 1 : 0,\n\t\t\t\tdefault_value: field.defaultValue !== undefined ? JSON.stringify(field.defaultValue) : null,\n\t\t\t\tvalidation: field.validation ? JSON.stringify(field.validation) : null,\n\t\t\t\twidget: field.widget ?? null,\n\t\t\t\toptions: field.options ? JSON.stringify(field.options) : null,\n\t\t\t\tsort_order: sortOrder,\n\t\t\t\tsearchable: field.searchable ? 1 : 0,\n\t\t\t\tindexed: field.indexed ? 1 : 0,\n\t\t\t\ttranslatable: field.translatable === false ? 0 : 1,\n\t\t\t};\n\t\t});\n\n\t\tlet schemaMutated = false;\n\t\ttry {\n\t\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\t\tconst capture = await prepareMediaUsageCollectionCapture(trx, {\n\t\t\t\t\tcollectionId: proposedCollectionId,\n\t\t\t\t\tcollectionSlug: input.slug,\n\t\t\t\t\tcreationFingerprint,\n\t\t\t\t\tregisteredCollectionId: existing?.id,\n\t\t\t\t});\n\t\t\t\tconst collectionValues: Insertable<CollectionTable> = {\n\t\t\t\t\tid: capture.collectionId,\n\t\t\t\t\tslug: input.slug,\n\t\t\t\t\tlabel: input.label,\n\t\t\t\t\tlabel_singular: input.labelSingular ?? null,\n\t\t\t\t\tdescription: input.description ?? null,\n\t\t\t\t\ticon: input.icon ?? null,\n\t\t\t\t\tadmin_config: input.admin ? JSON.stringify(input.admin) : null,\n\t\t\t\t\tsupports: JSON.stringify(supports),\n\t\t\t\t\tsource: \"seed\",\n\t\t\t\t\thas_seo: hasSeo ? 1 : 0,\n\t\t\t\t\troutable: input.routable === false ? 0 : 1,\n\t\t\t\t\thidden: input.hidden ? 1 : 0,\n\t\t\t\t\tsort_order: input.sortOrder ?? null,\n\t\t\t\t\tcomments_enabled: input.commentsEnabled ? 1 : 0,\n\t\t\t\t\turl_pattern: input.urlPattern ?? null,\n\t\t\t\t};\n\t\t\t\tconst rows = fieldRows.map((row) => ({\n\t\t\t\t\t...row,\n\t\t\t\t\tcollection_id: capture.collectionId,\n\t\t\t\t}));\n\n\t\t\t\tif (capture.captureRequired) {\n\t\t\t\t\tawait this.createContentTable(input.slug, trx, fields, {\n\t\t\t\t\t\tifNotExists: capture.resuming,\n\t\t\t\t\t});\n\t\t\t\t\tawait installPreparedMediaUsageCollectionCapture(trx, {\n\t\t\t\t\t\tcollectionId: capture.collectionId,\n\t\t\t\t\t\tcollectionSlug: input.slug,\n\t\t\t\t\t});\n\t\t\t\t\tawait markMediaUsageCollectionCaptureReady(trx, {\n\t\t\t\t\t\tcollectionId: capture.collectionId,\n\t\t\t\t\t\tcollectionSlug: input.slug,\n\t\t\t\t\t});\n\t\t\t\t\tif (!capture.registrationExists) {\n\t\t\t\t\t\tawait trx.insertInto(\"_emdash_collections\").values(collectionValues).execute();\n\t\t\t\t\t\tschemaMutated = true;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tawait trx.insertInto(\"_emdash_collections\").values(collectionValues).execute();\n\t\t\t\t\tschemaMutated = true;\n\t\t\t\t\tawait this.createContentTable(input.slug, trx, fields);\n\t\t\t\t}\n\n\t\t\t\tfor (const fieldBatch of chunks(rows, SEED_FIELD_INSERT_BATCH_SIZE)) {\n\t\t\t\t\tlet insert = trx.insertInto(\"_emdash_fields\").values(fieldBatch);\n\t\t\t\t\tif (capture.resuming) {\n\t\t\t\t\t\tinsert = insert.onConflict((conflict) =>\n\t\t\t\t\t\t\tconflict.columns([\"collection_id\", \"slug\"]).doNothing(),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tawait insert.execute();\n\t\t\t\t}\n\t\t\t\tlet indexedRows: readonly { id: string; slug: string; indexed?: number }[] = rows;\n\t\t\t\tif (capture.resuming) {\n\t\t\t\t\tindexedRows = await this.assertSeedFieldDefinitions(capture.collectionId, fields, trx);\n\t\t\t\t}\n\t\t\t\tfor (const field of indexedRows) {\n\t\t\t\t\tif (field.indexed === 1) {\n\t\t\t\t\t\tawait this.createFieldIndex(input.slug, field.id, field.slug, trx);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (capture.captureRequired) {\n\t\t\t\t\tawait finalizeMediaUsageCollectionCapture(trx, {\n\t\t\t\t\t\tcollectionId: capture.collectionId,\n\t\t\t\t\t\tcollectionSlug: input.slug,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tawait markContentMediaUsageCollectionStaleSafely(this.db, input.slug, \"CONTENT_USAGE_STALE\");\n\t\t} catch (error) {\n\t\t\tif (schemaMutated) {\n\t\t\t\tawait markContentMediaUsageCollectionStaleSafely(\n\t\t\t\t\tthis.db,\n\t\t\t\t\tinput.slug,\n\t\t\t\t\t\"CONTENT_USAGE_STALE\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t\tthis.notifyTypegen();\n\t}\n\n\tprivate async assertSeedFieldDefinitions(\n\t\tcollectionId: string,\n\t\tfields: readonly CreateFieldInput[],\n\t\tdb: Kysely<Database>,\n\t): Promise<Selectable<FieldTable>[]> {\n\t\tconst stored = await db\n\t\t\t.selectFrom(\"_emdash_fields\")\n\t\t\t.selectAll()\n\t\t\t.where(\"collection_id\", \"=\", collectionId)\n\t\t\t.execute();\n\t\tif (stored.length !== fields.length) {\n\t\t\tthrow new SchemaError(\"Interrupted seed collection fields do not match\", \"CREATE_FAILED\");\n\t\t}\n\n\t\tlet maxSortOrder = -1;\n\t\tfor (const field of fields) {\n\t\t\tconst sortOrder = field.sortOrder ?? maxSortOrder + 1;\n\t\t\tmaxSortOrder = Math.max(maxSortOrder, sortOrder);\n\t\t\tconst row = stored.find((candidate) => candidate.slug === field.slug);\n\t\t\tif (\n\t\t\t\t!row ||\n\t\t\t\trow.label !== field.label ||\n\t\t\t\trow.type !== field.type ||\n\t\t\t\trow.column_type !== FIELD_TYPE_TO_COLUMN[field.type] ||\n\t\t\t\trow.required !== (field.required ? 1 : 0) ||\n\t\t\t\trow.unique !== (field.unique ? 1 : 0) ||\n\t\t\t\trow.default_value !==\n\t\t\t\t\t(field.defaultValue !== undefined ? JSON.stringify(field.defaultValue) : null) ||\n\t\t\t\trow.validation !== (field.validation ? JSON.stringify(field.validation) : null) ||\n\t\t\t\trow.widget !== (field.widget ?? null) ||\n\t\t\t\trow.options !== (field.options ? JSON.stringify(field.options) : null) ||\n\t\t\t\trow.sort_order !== sortOrder ||\n\t\t\t\trow.searchable !== (field.searchable ? 1 : 0) ||\n\t\t\t\trow.indexed !== (field.indexed ? 1 : 0) ||\n\t\t\t\trow.translatable !== (field.translatable === false ? 0 : 1)\n\t\t\t) {\n\t\t\t\tthrow new SchemaError(\"Interrupted seed collection fields do not match\", \"CREATE_FAILED\");\n\t\t\t}\n\t\t}\n\t\treturn stored;\n\t}\n\n\t/**\n\t * Update a collection\n\t */\n\tasync updateCollection(slug: string, input: UpdateCollectionInput): Promise<Collection> {\n\t\tconst updated = await withTransaction(this.db, async (trx) => {\n\t\t\tconst existingRow = await trx\n\t\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t\t.where(\"slug\", \"=\", slug)\n\t\t\t\t.selectAll()\n\t\t\t\t.executeTakeFirst();\n\t\t\tif (!existingRow) {\n\t\t\t\tthrow new SchemaError(`Collection \"${slug}\" not found`, \"COLLECTION_NOT_FOUND\");\n\t\t\t}\n\t\t\tconst existing = this.mapCollectionRow(existingRow);\n\t\t\tawait this.validateTitleDateFields(\n\t\t\t\texisting.id,\n\t\t\t\tslug,\n\t\t\t\t{\n\t\t\t\t\ttitleField: input.titleField,\n\t\t\t\t\tdateField: input.dateField,\n\t\t\t\t},\n\t\t\t\ttrx,\n\t\t\t);\n\t\t\tconst updates: Updateable<CollectionTable> = {};\n\n\t\t\tif (input.label !== undefined) updates.label = input.label;\n\t\t\tif (input.labelSingular !== undefined) updates.label_singular = input.labelSingular;\n\t\t\tif (input.description !== undefined) updates.description = input.description;\n\t\t\tif (input.icon !== undefined) updates.icon = input.icon;\n\t\t\tif (input.admin !== undefined) updates.admin_config = JSON.stringify(input.admin);\n\t\t\tif (input.supports !== undefined) updates.supports = JSON.stringify(input.supports);\n\t\t\tif (input.urlPattern !== undefined) updates.url_pattern = input.urlPattern;\n\t\t\tif (input.routable !== undefined) updates.routable = input.routable ? 1 : 0;\n\t\t\tif (input.hasSeo !== undefined) {\n\t\t\t\tupdates.has_seo = input.hasSeo ? 1 : 0;\n\t\t\t} else if (input.supports !== undefined) {\n\t\t\t\tupdates.has_seo = input.supports.includes(\"seo\") ? 1 : 0;\n\t\t\t}\n\t\t\tif (input.hidden !== undefined) updates.hidden = input.hidden ? 1 : 0;\n\t\t\tif (input.sortOrder !== undefined) updates.sort_order = input.sortOrder;\n\t\t\tif (input.titleField !== undefined) updates.title_field = input.titleField || null;\n\t\t\tif (input.dateField !== undefined) updates.date_field = input.dateField || null;\n\t\t\tif (input.commentsEnabled !== undefined) {\n\t\t\t\tupdates.comments_enabled = input.commentsEnabled ? 1 : 0;\n\t\t\t}\n\t\t\tif (input.commentsModeration !== undefined) {\n\t\t\t\tupdates.comments_moderation = input.commentsModeration;\n\t\t\t}\n\t\t\tif (input.commentsClosedAfterDays !== undefined) {\n\t\t\t\tupdates.comments_closed_after_days = input.commentsClosedAfterDays;\n\t\t\t}\n\t\t\tif (input.commentsAutoApproveUsers !== undefined) {\n\t\t\t\tupdates.comments_auto_approve_users = input.commentsAutoApproveUsers ? 1 : 0;\n\t\t\t}\n\n\t\t\tupdates.updated_at = new Date().toISOString();\n\t\t\tawait trx\n\t\t\t\t.updateTable(\"_emdash_collections\")\n\t\t\t\t.set(updates)\n\t\t\t\t.where(\"id\", \"=\", existing.id)\n\t\t\t\t.execute();\n\n\t\t\tconst row = await trx\n\t\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t\t.where(\"slug\", \"=\", slug)\n\t\t\t\t.selectAll()\n\t\t\t\t.executeTakeFirst();\n\n\t\t\tif (!row) {\n\t\t\t\tthrow new SchemaError(\"Failed to update collection\", \"UPDATE_FAILED\");\n\t\t\t}\n\n\t\t\t// Sync FTS state when the supports array changes (e.g. search toggled on/off)\n\t\t\tif (input.supports !== undefined) {\n\t\t\t\tconst hadSearch = existing.supports.includes(\"search\");\n\t\t\t\tconst hasSearch = parseSupports(row.supports).includes(\"search\");\n\t\t\t\tif (hadSearch !== hasSearch) {\n\t\t\t\t\tawait this.syncSearchState(slug, trx);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn this.mapCollectionRow(row);\n\t\t});\n\t\tthis.notifyTypegen();\n\t\treturn updated;\n\t}\n\n\t/**\n\t * Delete a collection\n\t */\n\tasync deleteCollection(slug: string, options?: { force?: boolean }): Promise<void> {\n\t\tconst existing = await this.getCollection(slug);\n\t\tif (existing && !options?.force && (await this.collectionHasContent(slug))) {\n\t\t\tthrow new SchemaError(\n\t\t\t\t`Collection \"${slug}\" has content. Use force: true to delete.`,\n\t\t\t\t\"COLLECTION_HAS_CONTENT\",\n\t\t\t);\n\t\t}\n\t\tconst activated = await deleteActivatedMediaUsageCollection(this.db, {\n\t\t\tcollectionId: existing?.id,\n\t\t\tcollectionSlug: slug,\n\t\t\tforceDelete: options?.force === true,\n\t\t});\n\t\tif (activated === \"has_content\") {\n\t\t\tthrow new SchemaError(\n\t\t\t\t`Collection \"${slug}\" has content. Use force: true to delete.`,\n\t\t\t\t\"COLLECTION_HAS_CONTENT\",\n\t\t\t);\n\t\t}\n\t\tif (activated === \"in_progress\") {\n\t\t\tthrow new SchemaError(`Collection \"${slug}\" deletion is already in progress`, \"CONFLICT\");\n\t\t}\n\t\tif (activated === \"deleted\") return;\n\t\tif (!existing) {\n\t\t\tthrow new SchemaError(`Collection \"${slug}\" not found`, \"COLLECTION_NOT_FOUND\");\n\t\t}\n\n\t\tlet contentTableDropped = false;\n\t\ttry {\n\t\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\t\t// Drop FTS table and triggers before dropping the content table\n\t\t\t\tconst ftsManager = new FTSManager(trx);\n\t\t\t\tawait ftsManager.dropFtsTable(slug);\n\n\t\t\t\t// Drop the content table\n\t\t\t\tconst tableName = this.getTableName(slug);\n\t\t\t\tawait sql`DROP TABLE IF EXISTS ${sql.ref(tableName)}`.execute(trx);\n\t\t\t\tcontentTableDropped = true;\n\n\t\t\t\t// Delete the collection record (fields will cascade)\n\t\t\t\tawait trx.deleteFrom(\"_emdash_collections\").where(\"id\", \"=\", existing.id).execute();\n\t\t\t});\n\t\t\tawait deleteContentMediaUsageCollection(this.db, slug);\n\t\t} catch (error) {\n\t\t\tif (contentTableDropped && !(await tableExists(this.db, this.getTableName(slug)))) {\n\t\t\t\tawait deleteContentMediaUsageCollection(this.db, slug);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t\tthis.notifyTypegen();\n\t}\n\n\t// ============================================\n\t// Field Operations\n\t// ============================================\n\n\t/**\n\t * List fields for a collection\n\t */\n\tasync listFields(collectionId: string): Promise<Field[]> {\n\t\tconst rows = await this.db\n\t\t\t.selectFrom(\"_emdash_fields\")\n\t\t\t.where(\"collection_id\", \"=\", collectionId)\n\t\t\t.selectAll()\n\t\t\t.orderBy(\"sort_order\", \"asc\")\n\t\t\t.orderBy(\"created_at\", \"asc\")\n\t\t\t.execute();\n\n\t\treturn rows.map(this.mapFieldRow);\n\t}\n\n\t/**\n\t * Get a field by slug within a collection\n\t */\n\tasync getField(collectionSlug: string, fieldSlug: string): Promise<Field | null> {\n\t\tconst collection = await this.getCollection(collectionSlug);\n\t\tif (!collection) return null;\n\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"_emdash_fields\")\n\t\t\t.where(\"collection_id\", \"=\", collection.id)\n\t\t\t.where(\"slug\", \"=\", fieldSlug)\n\t\t\t.selectAll()\n\t\t\t.executeTakeFirst();\n\n\t\treturn row ? this.mapFieldRow(row) : null;\n\t}\n\n\t/**\n\t * Create a new field\n\t */\n\tasync createField(collectionSlug: string, input: CreateFieldInput): Promise<Field> {\n\t\tconst collection = await this.getCollection(collectionSlug);\n\t\tif (!collection) {\n\t\t\tthrow new SchemaError(`Collection \"${collectionSlug}\" not found`, \"COLLECTION_NOT_FOUND\");\n\t\t}\n\n\t\t// Validate slug\n\t\tthis.validateSlug(input.slug, \"field\");\n\t\tif (RESERVED_FIELD_SLUGS.includes(input.slug)) {\n\t\t\tthrow new SchemaError(`Field slug \"${input.slug}\" is reserved`, \"RESERVED_SLUG\");\n\t\t}\n\n\t\t// Check if field already exists\n\t\tconst existing = await this.getField(collectionSlug, input.slug);\n\t\tif (existing) {\n\t\t\tthrow new SchemaError(\n\t\t\t\t`Field \"${input.slug}\" already exists in collection \"${collectionSlug}\"`,\n\t\t\t\t\"FIELD_EXISTS\",\n\t\t\t);\n\t\t}\n\n\t\tconst id = ulid();\n\t\tconst columnType = FIELD_TYPE_TO_COLUMN[input.type];\n\t\tassertIndexableField(input.type, input.indexed, input.slug);\n\n\t\t// Get max sort order\n\t\tconst maxSort = await this.db\n\t\t\t.selectFrom(\"_emdash_fields\")\n\t\t\t.where(\"collection_id\", \"=\", collection.id)\n\t\t\t.select((eb) => eb.fn.max<number>(\"sort_order\").as(\"max\"))\n\t\t\t.executeTakeFirst();\n\n\t\tconst sortOrder = input.sortOrder ?? (maxSort?.max ?? -1) + 1;\n\t\tconst activeCoverageInvalidated = await invalidateContentMediaUsageSchemaChange(\n\t\t\tthis.db,\n\t\t\tcollectionSlug,\n\t\t);\n\n\t\tlet schemaMutated = false;\n\t\ttry {\n\t\t\tconst created = await withTransaction(this.db, async (trx) => {\n\t\t\t\t// Insert field record\n\t\t\t\tawait trx\n\t\t\t\t\t.insertInto(\"_emdash_fields\")\n\t\t\t\t\t.values({\n\t\t\t\t\t\tid,\n\t\t\t\t\t\tcollection_id: collection.id,\n\t\t\t\t\t\tslug: input.slug,\n\t\t\t\t\t\tlabel: input.label,\n\t\t\t\t\t\ttype: input.type,\n\t\t\t\t\t\tcolumn_type: columnType,\n\t\t\t\t\t\trequired: input.required ? 1 : 0,\n\t\t\t\t\t\tunique: input.unique ? 1 : 0,\n\t\t\t\t\t\tdefault_value:\n\t\t\t\t\t\t\tinput.defaultValue !== undefined ? JSON.stringify(input.defaultValue) : null,\n\t\t\t\t\t\tvalidation: input.validation ? JSON.stringify(input.validation) : null,\n\t\t\t\t\t\twidget: input.widget ?? null,\n\t\t\t\t\t\toptions: input.options ? JSON.stringify(input.options) : null,\n\t\t\t\t\t\tsort_order: sortOrder,\n\t\t\t\t\t\tsearchable: input.searchable ? 1 : 0,\n\t\t\t\t\t\tindexed: input.indexed ? 1 : 0,\n\t\t\t\t\t\ttranslatable: input.translatable === false ? 0 : 1,\n\t\t\t\t\t})\n\t\t\t\t\t.execute();\n\t\t\t\tschemaMutated = true;\n\n\t\t\t\t// Add column to content table — pass trx to stay on the same connection\n\t\t\t\tawait this.addColumn(\n\t\t\t\t\tcollectionSlug,\n\t\t\t\t\tinput.slug,\n\t\t\t\t\tinput.type,\n\t\t\t\t\t{\n\t\t\t\t\t\trequired: input.required,\n\t\t\t\t\t\tdefaultValue: input.defaultValue,\n\t\t\t\t\t},\n\t\t\t\t\ttrx,\n\t\t\t\t);\n\n\t\t\t\tif (input.indexed) {\n\t\t\t\t\tawait this.createFieldIndex(collectionSlug, id, input.slug, trx);\n\t\t\t\t}\n\n\t\t\t\t// Read the created field via trx (not this.db) to avoid connection mutex deadlock\n\t\t\t\tconst fieldRow = await trx\n\t\t\t\t\t.selectFrom(\"_emdash_fields\")\n\t\t\t\t\t.where(\"collection_id\", \"=\", collection.id)\n\t\t\t\t\t.where(\"slug\", \"=\", input.slug)\n\t\t\t\t\t.selectAll()\n\t\t\t\t\t.executeTakeFirst();\n\n\t\t\t\tif (!fieldRow) {\n\t\t\t\t\tthrow new SchemaError(\"Failed to create field\", \"CREATE_FAILED\");\n\t\t\t\t}\n\n\t\t\t\tconst field = this.mapFieldRow(fieldRow);\n\n\t\t\t\t// Sync search state if this field is searchable; support checks are handled by syncSearchState()\n\t\t\t\tif (input.searchable) {\n\t\t\t\t\tawait this.syncSearchState(collectionSlug, trx);\n\t\t\t\t}\n\n\t\t\t\treturn field;\n\t\t\t});\n\t\t\tif (activeCoverageInvalidated) {\n\t\t\t\tawait invalidateContentMediaUsageSchemaChange(this.db, collectionSlug);\n\t\t\t} else {\n\t\t\t\tawait markContentMediaUsageCollectionStaleSafely(\n\t\t\t\t\tthis.db,\n\t\t\t\t\tcollectionSlug,\n\t\t\t\t\t\"CONTENT_USAGE_STALE\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.notifyTypegen();\n\t\t\treturn created;\n\t\t} catch (error) {\n\t\t\tif (schemaMutated) {\n\t\t\t\tif (activeCoverageInvalidated) {\n\t\t\t\t\tawait invalidateContentMediaUsageSchemaChange(this.db, collectionSlug);\n\t\t\t\t} else {\n\t\t\t\t\tawait markContentMediaUsageCollectionStaleSafely(\n\t\t\t\t\t\tthis.db,\n\t\t\t\t\t\tcollectionSlug,\n\t\t\t\t\t\t\"CONTENT_USAGE_STALE\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t/**\n\t * Update a field\n\t */\n\tasync updateField(\n\t\tcollectionSlug: string,\n\t\tfieldSlug: string,\n\t\tinput: UpdateFieldInput,\n\t): Promise<Field> {\n\t\tlet activeCoverageInvalidated = false;\n\t\tlet schemaMutated = false;\n\t\ttry {\n\t\t\tconst updatedField = await withTransaction(this.db, async (trx) => {\n\t\t\t\tconst collectionRow = await trx\n\t\t\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t\t\t.where(\"slug\", \"=\", collectionSlug)\n\t\t\t\t\t.select([\"id\", \"title_field\", \"date_field\"])\n\t\t\t\t\t.executeTakeFirst();\n\t\t\t\tconst fieldRow = collectionRow\n\t\t\t\t\t? await trx\n\t\t\t\t\t\t\t.selectFrom(\"_emdash_fields\")\n\t\t\t\t\t\t\t.where(\"collection_id\", \"=\", collectionRow.id)\n\t\t\t\t\t\t\t.where(\"slug\", \"=\", fieldSlug)\n\t\t\t\t\t\t\t.selectAll()\n\t\t\t\t\t\t\t.executeTakeFirst()\n\t\t\t\t\t: undefined;\n\t\t\t\tif (!fieldRow) {\n\t\t\t\t\tthrow new SchemaError(\n\t\t\t\t\t\t`Field \"${fieldSlug}\" not found in collection \"${collectionSlug}\"`,\n\t\t\t\t\t\t\"FIELD_NOT_FOUND\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tconst field = this.mapFieldRow(fieldRow);\n\t\t\t\tconst updates: Updateable<FieldTable> = {};\n\t\t\t\tlet nextType = field.type;\n\n\t\t\t\tif (input.type !== undefined && input.type !== field.type) {\n\t\t\t\t\tconst newColumnType = FIELD_TYPE_TO_COLUMN[input.type];\n\t\t\t\t\tif (newColumnType !== field.columnType) {\n\t\t\t\t\t\tthrow new SchemaError(\n\t\t\t\t\t\t\t`Cannot change field \"${fieldSlug}\" in collection \"${collectionSlug}\" from type ` +\n\t\t\t\t\t\t\t\t`\"${field.type}\" to \"${input.type}\": the underlying column type would change from ` +\n\t\t\t\t\t\t\t\t`${field.columnType} to ${newColumnType}, which requires a manual content migration.`,\n\t\t\t\t\t\t\t\"FIELD_TYPE_COLUMN_CHANGE\",\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (!TEXT_ALIAS_FIELD_TYPES.has(field.type) || !TEXT_ALIAS_FIELD_TYPES.has(input.type)) {\n\t\t\t\t\t\tthrow new SchemaError(\n\t\t\t\t\t\t\t`Cannot change field \"${fieldSlug}\" in collection \"${collectionSlug}\" from type ` +\n\t\t\t\t\t\t\t\t`\"${field.type}\" to \"${input.type}\" without a manual content migration.`,\n\t\t\t\t\t\t\t\"FIELD_TYPE_CHANGE_REQUIRES_MIGRATION\",\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (collectionRow?.title_field === fieldSlug && !TITLE_FIELD_TYPES.has(input.type)) {\n\t\t\t\t\t\tthrow new SchemaError(\n\t\t\t\t\t\t\t`titleField \"${fieldSlug}\" must stay a text field, not \"${input.type}\"`,\n\t\t\t\t\t\t\t\"INVALID_TITLE_FIELD\",\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (collectionRow?.date_field === fieldSlug && input.type !== \"datetime\") {\n\t\t\t\t\t\tthrow new SchemaError(\n\t\t\t\t\t\t\t`dateField \"${fieldSlug}\" must stay a datetime field, not \"${input.type}\"`,\n\t\t\t\t\t\t\t\"INVALID_DATE_FIELD\",\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tnextType = input.type;\n\t\t\t\t\tupdates.type = input.type;\n\t\t\t\t\tupdates.column_type = newColumnType;\n\t\t\t\t}\n\n\t\t\t\tif (input.required !== undefined && input.required !== field.required) {\n\t\t\t\t\tthrow new SchemaError(\n\t\t\t\t\t\t`Changing required for field \"${fieldSlug}\" requires a manual content migration.`,\n\t\t\t\t\t\t\"FIELD_UPDATE_REQUIRES_MIGRATION\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (input.unique !== undefined && input.unique !== field.unique) {\n\t\t\t\t\tthrow new SchemaError(\n\t\t\t\t\t\t`Changing unique for field \"${fieldSlug}\" requires a manual content migration.`,\n\t\t\t\t\t\t\"FIELD_UPDATE_REQUIRES_MIGRATION\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (input.translatable === false && field.translatable) {\n\t\t\t\t\tthrow new SchemaError(\n\t\t\t\t\t\t`Changing field \"${fieldSlug}\" to non-translatable requires a manual content migration.`,\n\t\t\t\t\t\t\"FIELD_UPDATE_REQUIRES_MIGRATION\",\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tif (input.label !== undefined) updates.label = input.label;\n\t\t\t\tif (input.required !== undefined) updates.required = input.required ? 1 : 0;\n\t\t\t\tif (input.unique !== undefined) updates.unique = input.unique ? 1 : 0;\n\t\t\t\tif (input.searchable !== undefined) updates.searchable = input.searchable ? 1 : 0;\n\t\t\t\tconst indexedChanged = input.indexed !== undefined && input.indexed !== field.indexed;\n\t\t\t\tif (indexedChanged) updates.indexed = input.indexed ? 1 : 0;\n\t\t\t\tif (input.translatable !== undefined) {\n\t\t\t\t\tupdates.translatable = input.translatable ? 1 : 0;\n\t\t\t\t}\n\t\t\t\tif (input.defaultValue !== undefined) {\n\t\t\t\t\tupdates.default_value = JSON.stringify(input.defaultValue);\n\t\t\t\t}\n\t\t\t\tif (input.validation !== undefined) {\n\t\t\t\t\tupdates.validation = input.validation ? JSON.stringify(input.validation) : null;\n\t\t\t\t}\n\t\t\t\tif (input.widget !== undefined) updates.widget = input.widget;\n\t\t\t\tif (input.options !== undefined) updates.options = JSON.stringify(input.options);\n\t\t\t\tif (input.sortOrder !== undefined) updates.sort_order = input.sortOrder;\n\n\t\t\t\tassertIndexableField(nextType, input.indexed ?? field.indexed, fieldSlug);\n\t\t\t\tif (Object.keys(updates).length === 0) return field;\n\n\t\t\t\tactiveCoverageInvalidated = await invalidateContentMediaUsageSchemaChange(\n\t\t\t\t\ttrx,\n\t\t\t\t\tcollectionSlug,\n\t\t\t\t);\n\n\t\t\t\tif (Object.keys(updates).length > 0) {\n\t\t\t\t\tawait trx.updateTable(\"_emdash_fields\").set(updates).where(\"id\", \"=\", field.id).execute();\n\t\t\t\t\tschemaMutated = true;\n\t\t\t\t}\n\n\t\t\t\tif (indexedChanged) {\n\t\t\t\t\tif (input.indexed) {\n\t\t\t\t\t\tawait this.createFieldIndex(collectionSlug, field.id, fieldSlug, trx);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tawait this.dropFieldIndex(field.id, trx);\n\t\t\t\t\t}\n\t\t\t\t\tschemaMutated = true;\n\t\t\t\t}\n\n\t\t\t\t// Read the updated field via trx (not this.db) to avoid connection mutex deadlock\n\t\t\t\tconst updatedRow = await trx\n\t\t\t\t\t.selectFrom(\"_emdash_fields\")\n\t\t\t\t\t.where(\"collection_id\", \"=\", field.collectionId)\n\t\t\t\t\t.where(\"slug\", \"=\", fieldSlug)\n\t\t\t\t\t.selectAll()\n\t\t\t\t\t.executeTakeFirst();\n\n\t\t\t\tif (!updatedRow) {\n\t\t\t\t\tthrow new SchemaError(\"Failed to update field\", \"UPDATE_FAILED\");\n\t\t\t\t}\n\n\t\t\t\tconst updated = this.mapFieldRow(updatedRow);\n\n\t\t\t\t// If searchable changed, sync FTS state for this collection\n\t\t\t\tconst searchableChanged =\n\t\t\t\t\tschemaMutated && input.searchable !== undefined && input.searchable !== field.searchable;\n\t\t\t\tif (searchableChanged) {\n\t\t\t\t\tawait this.syncSearchState(collectionSlug, trx);\n\t\t\t\t}\n\n\t\t\t\treturn updated;\n\t\t\t});\n\t\t\tif (schemaMutated) {\n\t\t\t\tif (activeCoverageInvalidated) {\n\t\t\t\t\tawait invalidateContentMediaUsageSchemaChange(this.db, collectionSlug);\n\t\t\t\t} else {\n\t\t\t\t\tawait markContentMediaUsageCollectionStaleSafely(\n\t\t\t\t\t\tthis.db,\n\t\t\t\t\t\tcollectionSlug,\n\t\t\t\t\t\t\"CONTENT_USAGE_STALE\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.notifyTypegen();\n\t\t\treturn updatedField;\n\t\t} catch (error) {\n\t\t\tif (schemaMutated) {\n\t\t\t\tif (activeCoverageInvalidated) {\n\t\t\t\t\tawait invalidateContentMediaUsageSchemaChange(this.db, collectionSlug);\n\t\t\t\t} else {\n\t\t\t\t\tawait markContentMediaUsageCollectionStaleSafely(\n\t\t\t\t\t\tthis.db,\n\t\t\t\t\t\tcollectionSlug,\n\t\t\t\t\t\t\"CONTENT_USAGE_STALE\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t/**\n\t * Synchronize an existing FTS index with the collection's current state.\n\t *\n\t * Only rebuilds or disables — never first-time enables. First-time FTS\n\t * enablement is handled by the seed's explicit enableSearch call (which\n\t * is try-caught) or the admin UI toggle.\n\t *\n\t * - FTS active + still has search support and searchable fields → rebuild\n\t * - FTS active + lost search support or no searchable fields → disable\n\t * - FTS not active → no-op\n\t *\n\t * Pass `db` when calling from within a transaction so FTS operations\n\t * participate in the same transaction and are rolled back on failure.\n\t */\n\tprivate async syncSearchState(collectionSlug: string, db?: Kysely<Database>): Promise<void> {\n\t\tconst conn = db ?? this.db;\n\t\tconst ftsManager = new FTSManager(conn);\n\n\t\t// Query via conn (not this.db) to avoid connection mutex deadlock when called inside a transaction\n\t\tconst row = await conn\n\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t.where(\"slug\", \"=\", collectionSlug)\n\t\t\t.select(\"supports\")\n\t\t\t.executeTakeFirst();\n\t\tif (!row) return;\n\n\t\tconst wantsSearch = parseSupports(row.supports).includes(\"search\");\n\t\tconst searchableFields = await ftsManager.getSearchableFields(collectionSlug);\n\t\tconst config = await ftsManager.getSearchConfig(collectionSlug);\n\t\tconst ftsActive = config?.enabled === true;\n\n\t\tif (wantsSearch && searchableFields.length > 0 && ftsActive) {\n\t\t\tawait ftsManager.rebuildIndex(\n\t\t\t\tcollectionSlug,\n\t\t\t\tsearchableFields,\n\t\t\t\tconfig?.weights,\n\t\t\t\tconfig?.tokenize,\n\t\t\t);\n\t\t} else if (ftsActive && (!wantsSearch || searchableFields.length === 0)) {\n\t\t\tawait ftsManager.disableSearch(collectionSlug);\n\t\t}\n\t}\n\n\t/**\n\t * Delete a field\n\t */\n\tasync deleteField(collectionSlug: string, fieldSlug: string): Promise<void> {\n\t\tconst field = await this.getField(collectionSlug, fieldSlug);\n\t\tif (!field) {\n\t\t\tthrow new SchemaError(\n\t\t\t\t`Field \"${fieldSlug}\" not found in collection \"${collectionSlug}\"`,\n\t\t\t\t\"FIELD_NOT_FOUND\",\n\t\t\t);\n\t\t}\n\t\tconst activeCoverageInvalidated = await invalidateContentMediaUsageSchemaChange(\n\t\t\tthis.db,\n\t\t\tcollectionSlug,\n\t\t);\n\n\t\t// If this field powers the collection's titleField/dateField,\n\t\t// clear that reference in the same transaction — otherwise the metadata\n\t\t// would point at a dropped column and later crash the content list sort.\n\t\tconst collection = await this.getCollection(collectionSlug);\n\t\tconst clearTitle = collection?.titleField === fieldSlug;\n\t\tconst clearDate = collection?.dateField === fieldSlug;\n\n\t\tlet schemaMutated = false;\n\t\ttry {\n\t\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\t\t// Delete the field record first so syncSearchState sees the updated field list.\n\t\t\t\t// This ordering matters for searchable fields: SQLite prevents dropping a column\n\t\t\t\t// that is still referenced by a trigger. syncSearchState drops and recreates the\n\t\t\t\t// FTS triggers based on the remaining searchable fields, clearing the dependency\n\t\t\t\t// before we attempt the ALTER TABLE DROP COLUMN below.\n\t\t\t\tawait trx.deleteFrom(\"_emdash_fields\").where(\"id\", \"=\", field.id).execute();\n\t\t\t\tschemaMutated = true;\n\n\t\t\t\tif (clearTitle || clearDate) {\n\t\t\t\t\tawait trx\n\t\t\t\t\t\t.updateTable(\"_emdash_collections\")\n\t\t\t\t\t\t.set({\n\t\t\t\t\t\t\t...(clearTitle ? { title_field: null } : {}),\n\t\t\t\t\t\t\t...(clearDate ? { date_field: null } : {}),\n\t\t\t\t\t\t\tupdated_at: new Date().toISOString(),\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.where(\"slug\", \"=\", collectionSlug)\n\t\t\t\t\t\t.execute();\n\t\t\t\t}\n\n\t\t\t\t// If the deleted field was searchable, sync FTS state (removes old triggers)\n\t\t\t\tif (field.searchable) {\n\t\t\t\t\tawait this.syncSearchState(collectionSlug, trx);\n\t\t\t\t}\n\n\t\t\t\tif (field.indexed) {\n\t\t\t\t\tawait this.dropFieldIndex(field.id, trx);\n\t\t\t\t}\n\n\t\t\t\t// Drop column from content table — safe now because FTS triggers are gone\n\t\t\t\tawait this.dropColumn(collectionSlug, fieldSlug, trx);\n\t\t\t});\n\t\t\tif (activeCoverageInvalidated) {\n\t\t\t\tawait invalidateContentMediaUsageSchemaChange(this.db, collectionSlug);\n\t\t\t} else {\n\t\t\t\tawait markContentMediaUsageCollectionStaleSafely(\n\t\t\t\t\tthis.db,\n\t\t\t\t\tcollectionSlug,\n\t\t\t\t\t\"CONTENT_USAGE_STALE\",\n\t\t\t\t);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tif (schemaMutated) {\n\t\t\t\tif (activeCoverageInvalidated) {\n\t\t\t\t\tawait invalidateContentMediaUsageSchemaChange(this.db, collectionSlug);\n\t\t\t\t} else {\n\t\t\t\t\tawait markContentMediaUsageCollectionStaleSafely(\n\t\t\t\t\t\tthis.db,\n\t\t\t\t\t\tcollectionSlug,\n\t\t\t\t\t\t\"CONTENT_USAGE_STALE\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t\tthis.notifyTypegen();\n\t}\n\n\t/**\n\t * Reorder collections in the admin sidebar.\n\t *\n\t * `slugs` is the full desired order: every listed collection gets its\n\t * index as `sort_order`, and any collection left out has its explicit\n\t * position cleared, dropping it back to the alphabetical tail. Unknown or\n\t * duplicate slugs throw before anything is written.\n\t */\n\tasync reorderCollections(slugs: string[]): Promise<void> {\n\t\tconst known = new Set((await this.listCollections()).map((collection) => collection.slug));\n\n\t\tconst unknown = slugs.filter((slug) => !known.has(slug));\n\t\tif (unknown.length > 0) {\n\t\t\tthrow new SchemaError(\n\t\t\t\t`Unknown collection${unknown.length > 1 ? \"s\" : \"\"}: ${unknown.join(\", \")}`,\n\t\t\t\t\"COLLECTION_NOT_FOUND\",\n\t\t\t\t{ slugs: unknown },\n\t\t\t);\n\t\t}\n\n\t\tconst duplicates = slugs.filter((slug, index) => slugs.indexOf(slug) !== index);\n\t\tif (duplicates.length > 0) {\n\t\t\tthrow new SchemaError(\n\t\t\t\t`Duplicate collection${duplicates.length > 1 ? \"s\" : \"\"}: ${[...new Set(duplicates)].join(\", \")}`,\n\t\t\t\t\"DUPLICATE_SLUG\",\n\t\t\t\t{ slugs: [...new Set(duplicates)] },\n\t\t\t);\n\t\t}\n\n\t\tconst now = new Date().toISOString();\n\t\tconst ordered = new Set(slugs);\n\n\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\tfor (const [index, slug] of slugs.entries()) {\n\t\t\t\tawait trx\n\t\t\t\t\t.updateTable(\"_emdash_collections\")\n\t\t\t\t\t.set({ sort_order: index, updated_at: now })\n\t\t\t\t\t.where(\"slug\", \"=\", slug)\n\t\t\t\t\t.execute();\n\t\t\t}\n\n\t\t\tconst cleared = [...known].filter((slug) => !ordered.has(slug));\n\t\t\t// Chunked to stay under D1's bound-parameter limit; typical sites\n\t\t\t// clear far fewer than one chunk.\n\t\t\tfor (const slugChunk of chunks(cleared, SQL_BATCH_SIZE)) {\n\t\t\t\tawait trx\n\t\t\t\t\t.updateTable(\"_emdash_collections\")\n\t\t\t\t\t.set({ sort_order: null, updated_at: now })\n\t\t\t\t\t.where(\"slug\", \"in\", slugChunk)\n\t\t\t\t\t.execute();\n\t\t\t}\n\t\t});\n\t\tthis.notifyTypegen();\n\t}\n\n\t/**\n\t * Reorder fields\n\t */\n\tasync reorderFields(collectionSlug: string, fieldSlugs: string[]): Promise<void> {\n\t\tconst collection = await this.getCollection(collectionSlug);\n\t\tif (!collection) {\n\t\t\tthrow new SchemaError(`Collection \"${collectionSlug}\" not found`, \"COLLECTION_NOT_FOUND\");\n\t\t}\n\n\t\t// Update sort_order for each field\n\t\tfor (let i = 0; i < fieldSlugs.length; i++) {\n\t\t\tawait this.db\n\t\t\t\t.updateTable(\"_emdash_fields\")\n\t\t\t\t.set({ sort_order: i })\n\t\t\t\t.where(\"collection_id\", \"=\", collection.id)\n\t\t\t\t.where(\"slug\", \"=\", fieldSlugs[i])\n\t\t\t\t.execute();\n\t\t}\n\t\tthis.notifyTypegen();\n\t}\n\n\t// ============================================\n\t// DDL Operations\n\t// ============================================\n\n\t/**\n\t * Create a content table for a collection\n\t */\n\tprivate async createContentTable(\n\t\tslug: string,\n\t\tdb?: Kysely<Database>,\n\t\tfields: readonly CreateFieldInput[] = [],\n\t\toptions: { ifNotExists?: boolean } = {},\n\t): Promise<void> {\n\t\tconst conn = db ?? this.db;\n\t\tconst tableName = this.getTableName(slug);\n\n\t\tlet table: CreateTableBuilder<string, string> = conn.schema\n\t\t\t.createTable(tableName)\n\t\t\t.addColumn(\"id\", \"text\", (col) => col.primaryKey())\n\t\t\t.addColumn(\"slug\", \"text\")\n\t\t\t.addColumn(\"status\", \"text\", (col) => col.defaultTo(\"draft\"))\n\t\t\t.addColumn(\"author_id\", \"text\")\n\t\t\t.addColumn(\"primary_byline_id\", \"text\")\n\t\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(currentTimestamp(conn)))\n\t\t\t.addColumn(\"updated_at\", \"text\", (col) => col.defaultTo(currentTimestamp(conn)))\n\t\t\t.addColumn(\"published_at\", \"text\")\n\t\t\t.addColumn(\"scheduled_at\", \"text\")\n\t\t\t.addColumn(\"deleted_at\", \"text\")\n\t\t\t.addColumn(\"version\", \"integer\", (col) => col.defaultTo(1))\n\t\t\t.addColumn(\"live_revision_id\", \"text\", (col) => col.references(\"revisions.id\"))\n\t\t\t.addColumn(\"draft_revision_id\", \"text\", (col) => col.references(\"revisions.id\"))\n\t\t\t.addColumn(\"locale\", \"text\", (col) => col.notNull().defaultTo(\"en\"))\n\t\t\t.addColumn(\"translation_group\", \"text\");\n\t\tif (options.ifNotExists) table = table.ifNotExists();\n\n\t\tfor (const field of fields) {\n\t\t\tconst columnName = this.getColumnName(field.slug);\n\t\t\tconst columnType = COLUMN_TYPE_TO_DATA_TYPE[FIELD_TYPE_TO_COLUMN[field.type]];\n\t\t\ttable = table.addColumn(columnName, columnType, (column) => {\n\t\t\t\tif (!field.required) return column;\n\n\t\t\t\tconst defaultValue =\n\t\t\t\t\tfield.defaultValue !== undefined\n\t\t\t\t\t\t? this.formatDefaultValue(field.defaultValue, field.type)\n\t\t\t\t\t\t: this.getEmptyDefault(field.type);\n\t\t\t\treturn column.notNull().defaultTo(sql.raw(defaultValue));\n\t\t\t});\n\t\t}\n\n\t\tawait table\n\t\t\t.addUniqueConstraint(`${tableName}_slug_locale_unique`, [\"slug\", \"locale\"])\n\t\t\t.execute();\n\n\t\tconst createIndex = options.ifNotExists ? sql`CREATE INDEX IF NOT EXISTS` : sql`CREATE INDEX`;\n\n\t\t// Create standard indexes\n\t\tawait sql`\n\t\t\t${createIndex} ${sql.ref(`idx_${tableName}_slug`)}\n\t\t\tON ${sql.ref(tableName)} (slug)\n\t\t`.execute(conn);\n\n\t\t// Name must stay identical to migration 074. `deleted_at` leads so the\n\t\t// scheduled-publishing sweep can seek it and walk `scheduled_at` for both\n\t\t// its range and its ORDER BY; a `scheduled_at`-only index leaves a\n\t\t// stats-blind planner reading every live row.\n\t\tawait sql`\n\t\t\t${createIndex} ${sql.ref(`idx_${tableName}_del_sched`)}\n\t\t\tON ${sql.ref(tableName)} (deleted_at, scheduled_at)\n\t\t\tWHERE scheduled_at IS NOT NULL\n\t\t`.execute(conn);\n\n\t\tawait sql`\n\t\t\t${createIndex} ${sql.ref(`idx_${tableName}_live_revision`)}\n\t\t\tON ${sql.ref(tableName)} (live_revision_id)\n\t\t`.execute(conn);\n\n\t\tawait sql`\n\t\t\t${createIndex} ${sql.ref(`idx_${tableName}_draft_revision`)}\n\t\t\tON ${sql.ref(tableName)} (draft_revision_id)\n\t\t`.execute(conn);\n\n\t\tawait sql`\n\t\t\t${createIndex} ${sql.ref(`idx_${tableName}_author`)}\n\t\t\tON ${sql.ref(tableName)} (author_id)\n\t\t`.execute(conn);\n\n\t\tawait sql`\n\t\t\t${createIndex} ${sql.ref(`idx_${tableName}_primary_byline`)}\n\t\t\tON ${sql.ref(tableName)} (primary_byline_id)\n\t\t`.execute(conn);\n\n\t\tawait sql`\n\t\t\t${createIndex} ${sql.ref(`idx_${tableName}_locale`)}\n\t\t\tON ${sql.ref(tableName)} (locale)\n\t\t`.execute(conn);\n\n\t\t// Names must stay identical to migration 055, which creates these indexes\n\t\t// on tables that already exist. Lookups that don't constrain `deleted_at`\n\t\t// (menu and reference resolution) need the first; reads that do need the\n\t\t// second.\n\t\tawait sql`\n\t\t\t${createIndex} ${sql.ref(`idx_${tableName}_tg_locale`)}\n\t\t\tON ${sql.ref(tableName)} (translation_group, locale)\n\t\t`.execute(conn);\n\n\t\tawait sql`\n\t\t\t${createIndex} ${sql.ref(`idx_${tableName}_del_tg_locale`)}\n\t\t\tON ${sql.ref(tableName)} (deleted_at, translation_group, locale)\n\t\t`.execute(conn);\n\n\t\t// Composite indexes for optimized query performance (see migration 033)\n\t\tawait sql`\n\t\t\t${createIndex} ${sql.ref(`idx_${tableName}_deleted_updated_id`)}\n\t\t\tON ${sql.ref(tableName)} (deleted_at, updated_at DESC, id DESC)\n\t\t`.execute(conn);\n\n\t\tawait sql`\n\t\t\t${createIndex} ${sql.ref(`idx_${tableName}_deleted_status`)}\n\t\t\tON ${sql.ref(tableName)} (deleted_at, status)\n\t\t`.execute(conn);\n\n\t\tawait sql`\n\t\t\t${createIndex} ${sql.ref(`idx_${tableName}_deleted_created_id`)}\n\t\t\tON ${sql.ref(tableName)} (deleted_at, created_at DESC, id DESC)\n\t\t`.execute(conn);\n\n\t\tawait sql`\n\t\t\t${createIndex} ${sql.ref(`idx_${tableName}_deleted_published_id`)}\n\t\t\tON ${sql.ref(tableName)} (deleted_at, published_at DESC, id DESC)\n\t\t`.execute(conn);\n\n\t\t// Locale-aware composite indexes for i18n content lists (see migration 041).\n\t\t// Short `loc_upd`/`loc_crt` suffix keeps the updated/created discriminator\n\t\t// inside Postgres's 63-byte identifier limit for long slugs; keep these\n\t\t// names identical to migration 041.\n\t\tawait sql`\n\t\t\t${createIndex} ${sql.ref(`idx_${tableName}_loc_upd`)}\n\t\t\tON ${sql.ref(tableName)} (deleted_at, locale, updated_at DESC, id DESC)\n\t\t`.execute(conn);\n\n\t\tawait sql`\n\t\t\t${createIndex} ${sql.ref(`idx_${tableName}_loc_crt`)}\n\t\t\tON ${sql.ref(tableName)} (deleted_at, locale, created_at DESC, id DESC)\n\t\t`.execute(conn);\n\t}\n\n\tprivate getFieldIndexName(fieldId: string): string {\n\t\tif (!FIELD_ID_PATTERN.test(fieldId)) {\n\t\t\tthrow new SchemaError(`Invalid field id \"${fieldId}\"`, \"INVALID_FIELD_ID\");\n\t\t}\n\t\treturn `idx_cf_${fieldId.toLowerCase()}`;\n\t}\n\n\tprivate getLocaleFieldIndexName(fieldId: string): string {\n\t\treturn `${this.getFieldIndexName(fieldId)}_loc`;\n\t}\n\n\tprivate async createFieldIndex(\n\t\tcollectionSlug: string,\n\t\tfieldId: string,\n\t\tfieldSlug: string,\n\t\tdb?: Kysely<Database>,\n\t): Promise<void> {\n\t\tconst conn = db ?? this.db;\n\t\tconst tableName = this.getTableName(collectionSlug);\n\t\tconst columnName = this.getColumnName(fieldSlug);\n\t\tconst indexName = this.getFieldIndexName(fieldId);\n\t\tconst localeIndexName = this.getLocaleFieldIndexName(fieldId);\n\n\t\tawait sql`\n\t\t\tCREATE INDEX IF NOT EXISTS ${sql.ref(indexName)}\n\t\t\tON ${sql.ref(tableName)} (\n\t\t\t\t(${sql.ref(columnName)} IS NOT NULL),\n\t\t\t\t${sql.ref(columnName)},\n\t\t\t\tid\n\t\t\t)\n\t\t\tWHERE deleted_at IS NULL\n\t\t`.execute(conn);\n\n\t\tawait sql`\n\t\t\tCREATE INDEX IF NOT EXISTS ${sql.ref(localeIndexName)}\n\t\t\tON ${sql.ref(tableName)} (\n\t\t\t\tlocale,\n\t\t\t\t(${sql.ref(columnName)} IS NOT NULL),\n\t\t\t\t${sql.ref(columnName)},\n\t\t\t\tid\n\t\t\t)\n\t\t\tWHERE deleted_at IS NULL\n\t\t`.execute(conn);\n\t}\n\n\tprivate async dropFieldIndex(fieldId: string, db?: Kysely<Database>): Promise<void> {\n\t\tconst conn = db ?? this.db;\n\t\tawait sql`DROP INDEX IF EXISTS ${sql.ref(this.getFieldIndexName(fieldId))}`.execute(conn);\n\t\tawait sql`DROP INDEX IF EXISTS ${sql.ref(this.getLocaleFieldIndexName(fieldId))}`.execute(conn);\n\t}\n\n\t/**\n\t * Add a column to a content table\n\t */\n\tprivate async addColumn(\n\t\tcollectionSlug: string,\n\t\tfieldSlug: string,\n\t\tfieldType: FieldType,\n\t\toptions?: { required?: boolean; defaultValue?: unknown },\n\t\tdb?: Kysely<Database>,\n\t): Promise<void> {\n\t\tconst conn = db ?? this.db;\n\t\tconst tableName = this.getTableName(collectionSlug);\n\t\tconst columnType = FIELD_TYPE_TO_COLUMN[fieldType];\n\t\tconst columnName = this.getColumnName(fieldSlug);\n\n\t\t// Build ALTER TABLE statement\n\t\t// Note: SQLite requires DEFAULT for NOT NULL columns in ALTER TABLE\n\t\tif (options?.required && options?.defaultValue !== undefined) {\n\t\t\tconst defaultVal = this.formatDefaultValue(options.defaultValue, fieldType);\n\t\t\tawait sql`\n\t\t\t\tALTER TABLE ${sql.ref(tableName)}\n\t\t\t\tADD COLUMN ${sql.ref(columnName)} ${sql.raw(columnType)} NOT NULL DEFAULT ${sql.raw(defaultVal)}\n\t\t\t`.execute(conn);\n\t\t} else if (options?.required) {\n\t\t\t// For required fields without default, use empty string/0 as default\n\t\t\tconst defaultVal = this.getEmptyDefault(fieldType);\n\t\t\tawait sql`\n\t\t\t\tALTER TABLE ${sql.ref(tableName)}\n\t\t\t\tADD COLUMN ${sql.ref(columnName)} ${sql.raw(columnType)} NOT NULL DEFAULT ${sql.raw(defaultVal)}\n\t\t\t`.execute(conn);\n\t\t} else {\n\t\t\tawait sql`\n\t\t\t\tALTER TABLE ${sql.ref(tableName)}\n\t\t\t\tADD COLUMN ${sql.ref(columnName)} ${sql.raw(columnType)}\n\t\t\t`.execute(conn);\n\t\t}\n\t}\n\n\t/**\n\t * Drop a column from a content table\n\t */\n\tprivate async dropColumn(\n\t\tcollectionSlug: string,\n\t\tfieldSlug: string,\n\t\tdb?: Kysely<Database>,\n\t): Promise<void> {\n\t\tconst tableName = this.getTableName(collectionSlug);\n\t\tconst columnName = this.getColumnName(fieldSlug);\n\n\t\tawait sql`\n\t\t\tALTER TABLE ${sql.ref(tableName)}\n\t\t\tDROP COLUMN ${sql.ref(columnName)}\n\t\t`.execute(db ?? this.db);\n\t}\n\n\t// ============================================\n\t// Helpers\n\t// ============================================\n\n\t/**\n\t * Check if a collection has any content\n\t */\n\tprivate async collectionHasContent(slug: string): Promise<boolean> {\n\t\tconst tableName = this.getTableName(slug);\n\t\ttry {\n\t\t\tconst result = await sql<{ count: number }>`\n\t\t\t\tSELECT COUNT(*) as count FROM ${sql.ref(tableName)}\n\t\t\t\tWHERE deleted_at IS NULL\n\t\t\t`.execute(this.db);\n\t\t\treturn (result.rows[0]?.count ?? 0) > 0;\n\t\t} catch {\n\t\t\t// Table might not exist\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Get table name for a collection\n\t */\n\tprivate getTableName(slug: string): string {\n\t\tvalidateIdentifier(slug, \"collection slug\");\n\t\treturn `ec_${slug}`;\n\t}\n\n\t/**\n\t * Get column name for a field\n\t */\n\tprivate getColumnName(slug: string): string {\n\t\tvalidateIdentifier(slug, \"field slug\");\n\t\treturn slug;\n\t}\n\n\t/**\n\t * Validate a slug\n\t */\n\tprivate validateSlug(slug: string, type: \"collection\" | \"field\"): void {\n\t\tif (!slug || typeof slug !== \"string\") {\n\t\t\tthrow new SchemaError(`${type} slug is required`, \"INVALID_SLUG\");\n\t\t}\n\n\t\tif (!SLUG_VALIDATION_PATTERN.test(slug)) {\n\t\t\tthrow new SchemaError(\n\t\t\t\t`${type} slug must start with a letter and contain only lowercase letters, numbers, and underscores`,\n\t\t\t\t\"INVALID_SLUG\",\n\t\t\t);\n\t\t}\n\n\t\tif (slug.length > 63) {\n\t\t\tthrow new SchemaError(`${type} slug must be 63 characters or less`, \"INVALID_SLUG\");\n\t\t}\n\t}\n\n\t/**\n\t * Format a default value for SQL.\n\t *\n\t * SQLite `ALTER TABLE ADD COLUMN ... DEFAULT` requires a literal constant\n\t * expression — parameterized values cannot be used here. We manually escape\n\t * single quotes and coerce types to ensure the output is safe.\n\t *\n\t * INTEGER/REAL values are coerced through `Number()` which can only produce\n\t * digits, `.`, `-`, `e`, `Infinity`, or `NaN` — all safe in SQL.\n\t * TEXT/JSON values have single quotes escaped via SQL standard doubling (`''`).\n\t */\n\tprivate formatDefaultValue(value: unknown, fieldType: FieldType): string {\n\t\tif (value === null || value === undefined) {\n\t\t\treturn \"NULL\";\n\t\t}\n\n\t\tconst columnType = FIELD_TYPE_TO_COLUMN[fieldType];\n\n\t\tif (columnType === \"JSON\") {\n\t\t\t// JSON.stringify produces valid JSON; escape single quotes for SQL literal\n\t\t\tconst json = JSON.stringify(value);\n\t\t\treturn `'${json.replace(SINGLE_QUOTE_PATTERN, \"''\")}'`;\n\t\t}\n\n\t\tif (columnType === \"INTEGER\") {\n\t\t\tif (typeof value === \"boolean\") {\n\t\t\t\treturn value ? \"1\" : \"0\";\n\t\t\t}\n\t\t\tconst num = Number(value);\n\t\t\tif (!Number.isFinite(num)) {\n\t\t\t\treturn \"0\";\n\t\t\t}\n\t\t\treturn String(Math.trunc(num));\n\t\t}\n\n\t\tif (columnType === \"REAL\") {\n\t\t\tconst num = Number(value);\n\t\t\tif (!Number.isFinite(num)) {\n\t\t\t\treturn \"0\";\n\t\t\t}\n\t\t\treturn String(num);\n\t\t}\n\n\t\t// TEXT — escape single quotes via SQL standard doubling\n\t\tlet text: string;\n\t\tif (typeof value === \"string\") {\n\t\t\ttext = value;\n\t\t} else if (typeof value === \"number\" || typeof value === \"boolean\") {\n\t\t\ttext = String(value);\n\t\t} else if (typeof value === \"object\" && value !== null) {\n\t\t\ttext = JSON.stringify(value);\n\t\t} else {\n\t\t\ttext = \"\";\n\t\t}\n\t\treturn `'${text.replace(SINGLE_QUOTE_PATTERN, \"''\")}'`;\n\t}\n\n\t/**\n\t * Get empty default for a field type\n\t */\n\tprivate getEmptyDefault(fieldType: FieldType): string {\n\t\tconst columnType = FIELD_TYPE_TO_COLUMN[fieldType];\n\n\t\tswitch (columnType) {\n\t\t\tcase \"INTEGER\":\n\t\t\t\treturn \"0\";\n\t\t\tcase \"REAL\":\n\t\t\t\treturn \"0.0\";\n\t\t\tcase \"JSON\":\n\t\t\t\treturn \"'null'\";\n\t\t\tdefault:\n\t\t\t\treturn \"''\";\n\t\t}\n\t}\n\n\t/**\n\t * Map a collection row to a Collection object\n\t */\n\tprivate mapCollectionRow = (row: Selectable<CollectionTable>): Collection => {\n\t\tconst moderation = row.comments_moderation;\n\t\treturn {\n\t\t\tid: row.id,\n\t\t\tslug: row.slug,\n\t\t\tlabel: row.label,\n\t\t\tlabelSingular: row.label_singular ?? undefined,\n\t\t\tdescription: row.description ?? undefined,\n\t\t\ticon: row.icon ?? undefined,\n\t\t\tadmin: parseCollectionAdmin(row.admin_config),\n\t\t\tsupports: parseSupports(row.supports),\n\t\t\tsource: row.source && isCollectionSource(row.source) ? row.source : undefined,\n\t\t\thasSeo: row.has_seo === 1,\n\t\t\t// Raw value; undefined when unset. The admin list resolves the\n\t\t\t// default (title fallback chain / updatedAt) at the point of use.\n\t\t\ttitleField: row.title_field ?? undefined,\n\t\t\tdateField: row.date_field ?? undefined,\n\t\t\turlPattern: row.url_pattern ?? undefined,\n\t\t\troutable: row.routable !== 0,\n\t\t\thidden: row.hidden === 1,\n\t\t\tsortOrder: row.sort_order ?? undefined,\n\t\t\tcommentsEnabled: row.comments_enabled === 1,\n\t\t\tcommentsModeration:\n\t\t\t\tmoderation === \"all\" || moderation === \"first_time\" || moderation === \"none\"\n\t\t\t\t\t? moderation\n\t\t\t\t\t: \"first_time\",\n\t\t\tcommentsClosedAfterDays: row.comments_closed_after_days ?? 90,\n\t\t\tcommentsAutoApproveUsers: row.comments_auto_approve_users === 1,\n\t\t\tcreatedAt: row.created_at,\n\t\t\tupdatedAt: row.updated_at,\n\t\t};\n\t};\n\n\t/**\n\t * Map a field row to a Field object\n\t */\n\tprivate mapFieldRow = (row: Selectable<FieldTable>): Field => {\n\t\treturn {\n\t\t\tid: row.id,\n\t\t\tcollectionId: row.collection_id,\n\t\t\tslug: row.slug,\n\t\t\tlabel: row.label,\n\t\t\ttype: isFieldType(row.type) ? row.type : \"string\",\n\t\t\tcolumnType: isColumnType(row.column_type) ? row.column_type : \"TEXT\",\n\t\t\trequired: row.required === 1,\n\t\t\tunique: row.unique === 1,\n\t\t\tdefaultValue: row.default_value ? JSON.parse(row.default_value) : undefined,\n\t\t\tvalidation: row.validation ? JSON.parse(row.validation) : undefined,\n\t\t\twidget: row.widget ?? undefined,\n\t\t\toptions: row.options ? JSON.parse(row.options) : undefined,\n\t\t\tsortOrder: row.sort_order,\n\t\t\tsearchable: row.searchable === 1,\n\t\t\tindexed: row.indexed === 1,\n\t\t\ttranslatable: row.translatable !== 0,\n\t\t\tcreatedAt: row.created_at,\n\t\t};\n\t};\n\n\t// ============================================\n\t// Discovery\n\t// ============================================\n\n\t/**\n\t * Discover orphaned content tables\n\t *\n\t * Finds ec_* tables that exist in the database but don't have a\n\t * corresponding entry in _emdash_collections.\n\t */\n\tasync discoverOrphanedTables(): Promise<\n\t\tArray<{ slug: string; tableName: string; rowCount: number }>\n\t> {\n\t\t// Get all ec_* tables\n\t\t// Content tables are ec_* (e.g., ec_posts, ec_pages)\n\t\t// Internal tables are _emdash_* (e.g., _emdash_collections, _emdash_fts_posts)\n\t\tconst allTables = await listTablesLike(this.db, \"ec_%\");\n\n\t\t// Get registered collections\n\t\tconst registered = await this.listCollections();\n\t\tconst registeredSlugs = new Set(registered.map((c) => c.slug));\n\n\t\t// Find orphans\n\t\tconst orphans: Array<{\n\t\t\tslug: string;\n\t\t\ttableName: string;\n\t\t\trowCount: number;\n\t\t}> = [];\n\n\t\tfor (const tableName of allTables) {\n\t\t\tconst slug = tableName.replace(EC_PREFIX_PATTERN, \"\");\n\n\t\t\tif (!registeredSlugs.has(slug)) {\n\t\t\t\t// Count rows in the orphaned table\n\t\t\t\ttry {\n\t\t\t\t\tconst countResult = await sql<{ count: number }>`\n\t\t\t\t\t\tSELECT COUNT(*) as count FROM ${sql.ref(tableName)}\n\t\t\t\t\t\tWHERE deleted_at IS NULL\n\t\t\t\t\t`.execute(this.db);\n\n\t\t\t\t\torphans.push({\n\t\t\t\t\t\tslug,\n\t\t\t\t\t\ttableName,\n\t\t\t\t\t\trowCount: countResult.rows[0]?.count ?? 0,\n\t\t\t\t\t});\n\t\t\t\t} catch {\n\t\t\t\t\t// Table might have unexpected schema, still report it\n\t\t\t\t\torphans.push({\n\t\t\t\t\t\tslug,\n\t\t\t\t\t\ttableName,\n\t\t\t\t\t\trowCount: 0,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn orphans;\n\t}\n\n\t/**\n\t * Register an orphaned table as a collection\n\t *\n\t * Creates a _emdash_collections entry for an existing ec_* table.\n\t */\n\tasync registerOrphanedTable(\n\t\tslug: string,\n\t\toptions?: {\n\t\t\tlabel?: string;\n\t\t\tlabelSingular?: string;\n\t\t\tdescription?: string;\n\t\t},\n\t): Promise<Collection> {\n\t\t// Verify table exists\n\t\tconst tableName = this.getTableName(slug);\n\t\tif (await isMediaUsageCollectionSlugDeleting(this.db, slug)) {\n\t\t\tthrow new SchemaError(`Collection \"${slug}\" is already registered`, \"COLLECTION_EXISTS\");\n\t\t}\n\t\tconst exists = await tableExists(this.db, tableName);\n\n\t\tif (!exists) {\n\t\t\tthrow new SchemaError(`Table \"${tableName}\" does not exist`, \"TABLE_NOT_FOUND\");\n\t\t}\n\n\t\t// Check if already registered\n\t\tconst existing = await this.getCollection(slug);\n\t\tif (await isMediaUsageCollectionSlugDeleting(this.db, slug)) {\n\t\t\tthrow new SchemaError(`Collection \"${slug}\" is already registered`, \"COLLECTION_EXISTS\");\n\t\t}\n\t\tif (\n\t\t\texisting &&\n\t\t\t!(await canResumeMediaUsageCollectionCapture(this.db, {\n\t\t\t\tcollectionId: existing.id,\n\t\t\t\tcollectionSlug: existing.slug,\n\t\t\t}))\n\t\t) {\n\t\t\tthrow new SchemaError(`Collection \"${slug}\" is already registered`, \"COLLECTION_EXISTS\");\n\t\t}\n\n\t\t// Create collection entry\n\t\tconst proposedId = existing?.id ?? ulid();\n\t\tconst label = options?.label || this.slugToLabel(slug);\n\n\t\tlet collectionRegistered = false;\n\t\ttry {\n\t\t\tconst capture = await prepareMediaUsageCollectionCapture(this.db, {\n\t\t\t\tcollectionId: proposedId,\n\t\t\t\tcollectionSlug: slug,\n\t\t\t\tregisteredCollectionId: existing?.id,\n\t\t\t});\n\t\t\tif (capture.captureRequired) {\n\t\t\t\tawait installPreparedMediaUsageCollectionCapture(this.db, {\n\t\t\t\t\tcollectionId: capture.collectionId,\n\t\t\t\t\tcollectionSlug: slug,\n\t\t\t\t});\n\t\t\t\tawait markMediaUsageCollectionCaptureReady(this.db, {\n\t\t\t\t\tcollectionId: capture.collectionId,\n\t\t\t\t\tcollectionSlug: slug,\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (!capture.registrationExists) {\n\t\t\t\tawait this.db\n\t\t\t\t\t.insertInto(\"_emdash_collections\")\n\t\t\t\t\t.values({\n\t\t\t\t\t\tid: capture.collectionId,\n\t\t\t\t\t\tslug,\n\t\t\t\t\t\tlabel,\n\t\t\t\t\t\tlabel_singular: options?.labelSingular ?? null,\n\t\t\t\t\t\tdescription: options?.description ?? null,\n\t\t\t\t\t\ticon: null,\n\t\t\t\t\t\tsupports: JSON.stringify([]),\n\t\t\t\t\t\tsource: \"discovered\",\n\t\t\t\t\t\thas_seo: 0,\n\t\t\t\t\t\turl_pattern: null,\n\t\t\t\t\t})\n\t\t\t\t\t.execute();\n\t\t\t\tcollectionRegistered = true;\n\t\t\t}\n\t\t\tif (capture.captureRequired) {\n\t\t\t\tawait finalizeMediaUsageCollectionCapture(this.db, {\n\t\t\t\t\tcollectionId: capture.collectionId,\n\t\t\t\t\tcollectionSlug: slug,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst collection = await this.getCollection(slug);\n\t\t\tif (!collection) {\n\t\t\t\tthrow new SchemaError(\"Failed to register orphaned table\", \"REGISTER_FAILED\");\n\t\t\t}\n\t\t\tawait markContentMediaUsageCollectionStaleSafely(this.db, slug, \"CONTENT_USAGE_STALE\");\n\t\t\tthis.notifyTypegen();\n\n\t\t\treturn collection;\n\t\t} catch (error) {\n\t\t\tif (collectionRegistered) {\n\t\t\t\tawait markContentMediaUsageCollectionStaleSafely(this.db, slug, \"CONTENT_USAGE_STALE\");\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t/**\n\t * Convert slug to human-readable label\n\t */\n\tprivate slugToLabel(slug: string): string {\n\t\treturn slug\n\t\t\t.replace(UNDERSCORE_PATTERN, \" \")\n\t\t\t.replace(WORD_BOUNDARY_PATTERN, (c) => c.toUpperCase());\n\t}\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { n as chunks, t as SQL_BATCH_SIZE } from "./chunks-bNiX6_DR.mjs";
1
+ import { n as chunks, t as SQL_BATCH_SIZE } from "./chunks-C4szrD8o.mjs";
2
2
  import { i as InvalidCursorError, o as decodeCursor, s as encodeCursor } from "./types-CQPWhPxE.mjs";
3
3
  import { ulid } from "ulidx";
4
4
 
@@ -331,4 +331,4 @@ var RelationRepository = class {
331
331
 
332
332
  //#endregion
333
333
  export { RelationRepository as t };
334
- //# sourceMappingURL=relation-A6wLF_DS.mjs.map
334
+ //# sourceMappingURL=relation-04DfKRTO.mjs.map