emdash 0.28.1 → 0.29.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 (680) hide show
  1. package/dist/{adapters-hUy8vOB-.d.mts → adapters-DPAkL6QF.d.mts} +1 -1
  2. package/dist/{adapters-hUy8vOB-.d.mts.map → adapters-DPAkL6QF.d.mts.map} +1 -1
  3. package/dist/{allowed-origins-CRsJ6BPM.mjs → allowed-origins-FXB-FplB.mjs} +2 -2
  4. package/dist/{allowed-origins-CRsJ6BPM.mjs.map → allowed-origins-FXB-FplB.mjs.map} +1 -1
  5. package/dist/api/route-utils.d.mts +3 -3
  6. package/dist/api/route-utils.mjs +22 -21
  7. package/dist/api/route-utils.mjs.map +1 -1
  8. package/dist/api/schemas/index.d.mts +2 -2
  9. package/dist/api/schemas/index.mjs +4 -4
  10. package/dist/{api-DxFSorgk.mjs → api-DVbHNApo.mjs} +75 -23
  11. package/dist/api-DVbHNApo.mjs.map +1 -0
  12. package/dist/{api-tokens-DvqeZmVq.mjs → api-tokens-UYBkl17d.mjs} +2 -2
  13. package/dist/{api-tokens-DvqeZmVq.mjs.map → api-tokens-UYBkl17d.mjs.map} +1 -1
  14. package/dist/{apply-CDJqIjPU.mjs → apply-D23iUV4q.mjs} +20 -20
  15. package/dist/{apply-CDJqIjPU.mjs.map → apply-D23iUV4q.mjs.map} +1 -1
  16. package/dist/astro/index.d.mts +13 -12
  17. package/dist/astro/index.d.mts.map +1 -1
  18. package/dist/astro/index.mjs +10 -5
  19. package/dist/astro/index.mjs.map +1 -1
  20. package/dist/astro/middleware/auth.d.mts +11 -10
  21. package/dist/astro/middleware/auth.d.mts.map +1 -1
  22. package/dist/astro/middleware/auth.mjs +8 -8
  23. package/dist/astro/middleware/redirect.mjs +7 -7
  24. package/dist/astro/middleware/request-context.d.mts +3 -0
  25. package/dist/astro/middleware/request-context.d.mts.map +1 -1
  26. package/dist/astro/middleware/request-context.mjs +257 -30
  27. package/dist/astro/middleware/request-context.mjs.map +1 -1
  28. package/dist/astro/middleware/setup.mjs +1 -1
  29. package/dist/astro/middleware.d.mts +2 -2
  30. package/dist/astro/middleware.d.mts.map +1 -1
  31. package/dist/astro/middleware.mjs +125 -79
  32. package/dist/astro/middleware.mjs.map +1 -1
  33. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +7 -7
  34. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +7 -7
  35. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +5 -5
  36. package/dist/astro/routes/api/admin/api-tokens/index.mjs +6 -6
  37. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +6 -6
  38. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +10 -10
  39. package/dist/astro/routes/api/admin/byline-fields/index.mjs +10 -10
  40. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +10 -10
  41. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +17 -17
  42. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +17 -17
  43. package/dist/astro/routes/api/admin/bylines/index.mjs +17 -17
  44. package/dist/astro/routes/api/admin/comments/_id_/status.d.mts.map +1 -1
  45. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +13 -13
  46. package/dist/astro/routes/api/admin/comments/_id_/status.mjs.map +1 -1
  47. package/dist/astro/routes/api/admin/comments/_id_.mjs +6 -6
  48. package/dist/astro/routes/api/admin/comments/bulk.mjs +10 -10
  49. package/dist/astro/routes/api/admin/comments/counts.mjs +6 -6
  50. package/dist/astro/routes/api/admin/comments/index.d.mts.map +1 -1
  51. package/dist/astro/routes/api/admin/comments/index.mjs +10 -10
  52. package/dist/astro/routes/api/admin/comments/index.mjs.map +1 -1
  53. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +6 -6
  54. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +5 -5
  55. package/dist/astro/routes/api/admin/media-usage/repair.d.mts +8 -0
  56. package/dist/astro/routes/api/admin/media-usage/repair.d.mts.map +1 -0
  57. package/dist/astro/routes/api/admin/media-usage/repair.mjs +28 -0
  58. package/dist/astro/routes/api/admin/media-usage/repair.mjs.map +1 -0
  59. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +5 -5
  60. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +5 -5
  61. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +43 -41
  62. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
  63. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +43 -41
  64. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
  65. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +42 -40
  66. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
  67. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +42 -40
  68. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
  69. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +42 -40
  70. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
  71. package/dist/astro/routes/api/admin/plugins/index.mjs +42 -40
  72. package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
  73. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +4 -4
  74. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +42 -40
  75. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
  76. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +42 -40
  77. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
  78. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +42 -40
  79. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
  80. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +42 -40
  81. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
  82. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +43 -41
  83. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
  84. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +42 -40
  85. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
  86. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +43 -41
  87. package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
  88. package/dist/astro/routes/api/admin/plugins/updates.mjs +42 -40
  89. package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
  90. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +42 -40
  91. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
  92. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +4 -4
  93. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +42 -40
  94. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
  95. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +4 -4
  96. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +3 -3
  97. package/dist/astro/routes/api/admin/users/_id_/index.mjs +8 -8
  98. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +5 -5
  99. package/dist/astro/routes/api/admin/users/index.mjs +7 -7
  100. package/dist/astro/routes/api/auth/dev-bypass.mjs +7 -7
  101. package/dist/astro/routes/api/auth/invite/accept.mjs +3 -3
  102. package/dist/astro/routes/api/auth/invite/complete.mjs +12 -12
  103. package/dist/astro/routes/api/auth/invite/index.mjs +9 -9
  104. package/dist/astro/routes/api/auth/invite/register-options.mjs +11 -11
  105. package/dist/astro/routes/api/auth/logout.mjs +4 -4
  106. package/dist/astro/routes/api/auth/magic-link/send.mjs +11 -11
  107. package/dist/astro/routes/api/auth/magic-link/verify.mjs +4 -4
  108. package/dist/astro/routes/api/auth/me.mjs +8 -8
  109. package/dist/astro/routes/api/auth/mode.mjs +1 -1
  110. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +4 -4
  111. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs.map +1 -1
  112. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
  113. package/dist/astro/routes/api/auth/oauth/_provider_.mjs.map +1 -1
  114. package/dist/astro/routes/api/auth/passkey/_id_.mjs +7 -7
  115. package/dist/astro/routes/api/auth/passkey/index.mjs +3 -3
  116. package/dist/astro/routes/api/auth/passkey/options.mjs +13 -13
  117. package/dist/astro/routes/api/auth/passkey/register/options.mjs +11 -11
  118. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +12 -12
  119. package/dist/astro/routes/api/auth/passkey/verify.mjs +12 -12
  120. package/dist/astro/routes/api/auth/signup/complete.mjs +12 -12
  121. package/dist/astro/routes/api/auth/signup/request.mjs +11 -11
  122. package/dist/astro/routes/api/auth/signup/verify.mjs +3 -3
  123. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.d.mts.map +1 -1
  124. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +69 -14
  125. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs.map +1 -1
  126. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +13 -13
  127. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +4 -4
  128. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +4 -4
  129. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +4 -4
  130. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +4 -4
  131. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +12 -12
  132. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +8 -8
  133. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.d.mts +9 -0
  134. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.d.mts.map +1 -0
  135. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +71 -0
  136. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs.map +1 -0
  137. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.d.mts +8 -0
  138. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.d.mts.map +1 -0
  139. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs +50 -0
  140. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs.map +1 -0
  141. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +4 -4
  142. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +4 -4
  143. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +8 -8
  144. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +15 -15
  145. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +4 -4
  146. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +4 -4
  147. package/dist/astro/routes/api/content/_collection_/_id_.mjs +8 -8
  148. package/dist/astro/routes/api/content/_collection_/authors.mjs +4 -4
  149. package/dist/astro/routes/api/content/_collection_/index.mjs +8 -8
  150. package/dist/astro/routes/api/content/_collection_/trash.mjs +8 -8
  151. package/dist/astro/routes/api/dashboard.mjs +8 -8
  152. package/dist/astro/routes/api/dev/emails.mjs +4 -4
  153. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  154. package/dist/astro/routes/api/import/probe.mjs +18 -18
  155. package/dist/astro/routes/api/import/wordpress/analyze.mjs +5 -5
  156. package/dist/astro/routes/api/import/wordpress/execute.d.mts +12 -11
  157. package/dist/astro/routes/api/import/wordpress/execute.d.mts.map +1 -1
  158. package/dist/astro/routes/api/import/wordpress/execute.mjs +16 -16
  159. package/dist/astro/routes/api/import/wordpress/media.d.mts +11 -10
  160. package/dist/astro/routes/api/import/wordpress/media.d.mts.map +1 -1
  161. package/dist/astro/routes/api/import/wordpress/media.mjs +11 -11
  162. package/dist/astro/routes/api/import/wordpress/prepare.mjs +11 -11
  163. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +12 -16
  164. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts.map +1 -1
  165. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +11 -11
  166. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  167. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +18 -18
  168. package/dist/astro/routes/api/import/wordpress-plugin/callback.mjs +1 -1
  169. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +12 -11
  170. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts.map +1 -1
  171. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +28 -28
  172. package/dist/astro/routes/api/manifest.mjs +6 -6
  173. package/dist/astro/routes/api/mcp.mjs +56 -47
  174. package/dist/astro/routes/api/mcp.mjs.map +1 -1
  175. package/dist/astro/routes/api/media/_id_/confirm.mjs +9 -9
  176. package/dist/astro/routes/api/media/_id_.mjs +8 -8
  177. package/dist/astro/routes/api/media/file/_...key_.mjs +3 -3
  178. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +4 -4
  179. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +4 -4
  180. package/dist/astro/routes/api/media/providers/index.mjs +4 -4
  181. package/dist/astro/routes/api/media/upload-url.mjs +10 -10
  182. package/dist/astro/routes/api/media.mjs +13 -13
  183. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +9 -9
  184. package/dist/astro/routes/api/menus/_name_/items.mjs +9 -9
  185. package/dist/astro/routes/api/menus/_name_/reorder.mjs +9 -9
  186. package/dist/astro/routes/api/menus/_name_/translations.mjs +9 -9
  187. package/dist/astro/routes/api/menus/_name_.mjs +9 -9
  188. package/dist/astro/routes/api/menus/index.mjs +9 -9
  189. package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
  190. package/dist/astro/routes/api/oauth/device/authorize.mjs +7 -7
  191. package/dist/astro/routes/api/oauth/device/code.mjs +10 -10
  192. package/dist/astro/routes/api/oauth/device/token.mjs +9 -9
  193. package/dist/astro/routes/api/oauth/register.mjs +4 -4
  194. package/dist/astro/routes/api/oauth/token/refresh.mjs +7 -7
  195. package/dist/astro/routes/api/oauth/token/revoke.mjs +7 -7
  196. package/dist/astro/routes/api/oauth/token.mjs +7 -7
  197. package/dist/astro/routes/api/openapi.json.mjs +24 -5
  198. package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
  199. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +5 -5
  200. package/dist/astro/routes/api/redirects/404s/index.mjs +12 -12
  201. package/dist/astro/routes/api/redirects/404s/summary.mjs +12 -12
  202. package/dist/astro/routes/api/redirects/_id_.mjs +13 -13
  203. package/dist/astro/routes/api/redirects/index.mjs +13 -13
  204. package/dist/astro/routes/api/relations/_id_/index.d.mts +10 -0
  205. package/dist/astro/routes/api/relations/_id_/index.d.mts.map +1 -0
  206. package/dist/astro/routes/api/relations/_id_/index.mjs +69 -0
  207. package/dist/astro/routes/api/relations/_id_/index.mjs.map +1 -0
  208. package/dist/astro/routes/api/relations/_id_/translations.d.mts +8 -0
  209. package/dist/astro/routes/api/relations/_id_/translations.d.mts.map +1 -0
  210. package/dist/astro/routes/api/relations/_id_/translations.mjs +34 -0
  211. package/dist/astro/routes/api/relations/_id_/translations.mjs.map +1 -0
  212. package/dist/astro/routes/api/relations/index.d.mts +9 -0
  213. package/dist/astro/routes/api/relations/index.d.mts.map +1 -0
  214. package/dist/astro/routes/api/relations/index.mjs +53 -0
  215. package/dist/astro/routes/api/relations/index.mjs.map +1 -0
  216. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +4 -4
  217. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +4 -4
  218. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.d.mts.map +1 -1
  219. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +42 -40
  220. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
  221. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.d.mts.map +1 -1
  222. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +42 -40
  223. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs.map +1 -1
  224. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +42 -40
  225. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
  226. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +42 -40
  227. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
  228. package/dist/astro/routes/api/schema/collections/index.mjs +42 -40
  229. package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
  230. package/dist/astro/routes/api/schema/index.mjs +9 -9
  231. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +42 -40
  232. package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
  233. package/dist/astro/routes/api/schema/orphans/index.mjs +42 -40
  234. package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
  235. package/dist/astro/routes/api/search/enable.mjs +12 -12
  236. package/dist/astro/routes/api/search/index.mjs +13 -12
  237. package/dist/astro/routes/api/search/index.mjs.map +1 -1
  238. package/dist/astro/routes/api/search/rebuild.mjs +12 -12
  239. package/dist/astro/routes/api/search/stats.mjs +8 -8
  240. package/dist/astro/routes/api/search/suggest.mjs +11 -11
  241. package/dist/astro/routes/api/sections/_slug_.mjs +11 -11
  242. package/dist/astro/routes/api/sections/index.mjs +11 -11
  243. package/dist/astro/routes/api/settings/email.mjs +6 -6
  244. package/dist/astro/routes/api/settings.mjs +15 -15
  245. package/dist/astro/routes/api/setup/admin-verify.mjs +13 -13
  246. package/dist/astro/routes/api/setup/admin.mjs +12 -12
  247. package/dist/astro/routes/api/setup/dev-bypass.mjs +29 -29
  248. package/dist/astro/routes/api/setup/dev-reset.mjs +4 -4
  249. package/dist/astro/routes/api/setup/index.mjs +30 -30
  250. package/dist/astro/routes/api/setup/status.mjs +5 -5
  251. package/dist/astro/routes/api/snapshot.mjs +8 -8
  252. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +15 -15
  253. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +15 -15
  254. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +15 -15
  255. package/dist/astro/routes/api/taxonomies/index.mjs +15 -15
  256. package/dist/astro/routes/api/themes/preview.mjs +7 -7
  257. package/dist/astro/routes/api/typegen.mjs +6 -6
  258. package/dist/astro/routes/api/well-known/auth.mjs +2 -2
  259. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
  260. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
  261. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +8 -8
  262. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +12 -12
  263. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +12 -12
  264. package/dist/astro/routes/api/widget-areas/_name_.mjs +8 -8
  265. package/dist/astro/routes/api/widget-areas/index.mjs +12 -12
  266. package/dist/astro/routes/api/widget-components.mjs +4 -4
  267. package/dist/astro/routes/robots.txt.mjs +9 -9
  268. package/dist/astro/routes/sitemap-_collection_.xml.mjs +12 -12
  269. package/dist/astro/routes/sitemap.xml.mjs +10 -10
  270. package/dist/astro/types.d.mts +16 -13
  271. package/dist/astro/types.d.mts.map +1 -1
  272. package/dist/auth/providers/github.d.mts +1 -1
  273. package/dist/auth/providers/google.d.mts +1 -1
  274. package/dist/{authorize-Dz88F_Fw.mjs → authorize-BmCwQCib.mjs} +2 -2
  275. package/dist/{authorize-Dz88F_Fw.mjs.map → authorize-BmCwQCib.mjs.map} +1 -1
  276. package/dist/{base64-CmWvODNW.mjs → base64-C74Msx8h.mjs} +1 -1
  277. package/dist/{base64-CmWvODNW.mjs.map → base64-C74Msx8h.mjs.map} +1 -1
  278. package/dist/{byline-Bm8ZgUP9.mjs → byline-BWb5H-65.mjs} +8 -8
  279. package/dist/{byline-Bm8ZgUP9.mjs.map → byline-BWb5H-65.mjs.map} +1 -1
  280. package/dist/{byline-fields-CIx_GuN4.mjs → byline-fields-DO6bAYK8.mjs} +2 -2
  281. package/dist/{byline-fields-CIx_GuN4.mjs.map → byline-fields-DO6bAYK8.mjs.map} +1 -1
  282. package/dist/{byline-registry-BOjqDOim.mjs → byline-registry-BCuOp4UF.mjs} +3 -3
  283. package/dist/{byline-registry-BOjqDOim.mjs.map → byline-registry-BCuOp4UF.mjs.map} +1 -1
  284. package/dist/{bylines-6tm9mgQb.mjs → bylines-BGuKFaBy.mjs} +4 -4
  285. package/dist/{bylines-6tm9mgQb.mjs.map → bylines-BGuKFaBy.mjs.map} +1 -1
  286. package/dist/{bylines-CAZxGSrm.mjs → bylines-CHojDMpj.mjs} +8 -8
  287. package/dist/{bylines-CAZxGSrm.mjs.map → bylines-CHojDMpj.mjs.map} +1 -1
  288. package/dist/{cache-CEBAZjir.mjs → cache-BeR7RfN8.mjs} +3 -3
  289. package/dist/{cache-CEBAZjir.mjs.map → cache-BeR7RfN8.mjs.map} +1 -1
  290. package/dist/{challenge-store-anhwVER0.mjs → challenge-store-B426wQ6O.mjs} +1 -1
  291. package/dist/{challenge-store-anhwVER0.mjs.map → challenge-store-B426wQ6O.mjs.map} +1 -1
  292. package/dist/{chunks-B2isWQhr.mjs → chunks-BU5_nrAC.mjs} +2 -2
  293. package/dist/{chunks-B2isWQhr.mjs.map → chunks-BU5_nrAC.mjs.map} +1 -1
  294. package/dist/cli/index.mjs +28 -28
  295. package/dist/cli/index.mjs.map +1 -1
  296. package/dist/client/cf-access.d.mts +1 -1
  297. package/dist/client/index.d.mts +34 -2
  298. package/dist/client/index.d.mts.map +1 -1
  299. package/dist/client/index.mjs +6 -2
  300. package/dist/client/index.mjs.map +1 -1
  301. package/dist/{comment-CzeatwBV.mjs → comment-SWh31kS0.mjs} +2 -2
  302. package/dist/{comment-CzeatwBV.mjs.map → comment-SWh31kS0.mjs.map} +1 -1
  303. package/dist/{comment-reaction-Cj9QJY7I.mjs → comment-reaction-DgcpziXI.mjs} +2 -2
  304. package/dist/{comment-reaction-Cj9QJY7I.mjs.map → comment-reaction-DgcpziXI.mjs.map} +1 -1
  305. package/dist/{comments-BLE-jv5k.mjs → comments-kZYGppMS.mjs} +3 -3
  306. package/dist/{comments-BLE-jv5k.mjs.map → comments-kZYGppMS.mjs.map} +1 -1
  307. package/dist/{components-B_sATNUc.mjs → components-CliOkZAs.mjs} +1 -1
  308. package/dist/{components-B_sATNUc.mjs.map → components-CliOkZAs.mjs.map} +1 -1
  309. package/dist/{content-DhgEth4k.mjs → content-BO61Sx4j.mjs} +179 -20
  310. package/dist/content-BO61Sx4j.mjs.map +1 -0
  311. package/dist/{content-refresh-Br4gMzh8.mjs → content-refresh-BkkApzeS.mjs} +8 -8
  312. package/dist/{content-refresh-Br4gMzh8.mjs.map → content-refresh-BkkApzeS.mjs.map} +1 -1
  313. package/dist/{context-BRIpHARC.mjs → context-DtSFYFuS.mjs} +71 -14
  314. package/dist/context-DtSFYFuS.mjs.map +1 -0
  315. package/dist/{cron-v4FTEFR_.mjs → cron-YFJ7uNVA.mjs} +1 -1
  316. package/dist/{cron-v4FTEFR_.mjs.map → cron-YFJ7uNVA.mjs.map} +1 -1
  317. package/dist/{dashboard-EvEKF5en.mjs → dashboard-xo_gPs_D.mjs} +6 -5
  318. package/dist/dashboard-xo_gPs_D.mjs.map +1 -0
  319. package/dist/database/pg-migration-lock.d.mts +21 -0
  320. package/dist/database/pg-migration-lock.d.mts.map +1 -0
  321. package/dist/database/pg-migration-lock.mjs +45 -0
  322. package/dist/database/pg-migration-lock.mjs.map +1 -0
  323. package/dist/db/index.d.mts +3 -3
  324. package/dist/db/index.mjs +2 -2
  325. package/dist/db/libsql.d.mts +1 -1
  326. package/dist/db/postgres.d.mts +1 -1
  327. package/dist/db/postgres.d.mts.map +1 -1
  328. package/dist/db/postgres.mjs +2 -8
  329. package/dist/db/postgres.mjs.map +1 -1
  330. package/dist/db/sqlite.d.mts +1 -1
  331. package/dist/{db-errors-CK46D-ly.mjs → db-errors-BKuXh_fi.mjs} +1 -1
  332. package/dist/{db-errors-CK46D-ly.mjs.map → db-errors-BKuXh_fi.mjs.map} +1 -1
  333. package/dist/{default-CM_D83Mg.mjs → default-DY5ovGUG.mjs} +1 -1
  334. package/dist/{default-CM_D83Mg.mjs.map → default-DY5ovGUG.mjs.map} +1 -1
  335. package/dist/{device-flow-CfGP18Uw.mjs → device-flow-CPXQ_QfW.mjs} +5 -5
  336. package/dist/{device-flow-CfGP18Uw.mjs.map → device-flow-CPXQ_QfW.mjs.map} +1 -1
  337. package/dist/{dialect-helpers-DRI5pyY3.mjs → dialect-helpers-Cue7QQOR.mjs} +21 -2
  338. package/dist/dialect-helpers-Cue7QQOR.mjs.map +1 -0
  339. package/dist/{email-console-CIf4Lfvv.mjs → email-console-CjkQc7oY.mjs} +1 -1
  340. package/dist/{email-console-CIf4Lfvv.mjs.map → email-console-CjkQc7oY.mjs.map} +1 -1
  341. package/dist/{enrich-BQ0mxJRs.mjs → enrich-CFJJgxs_.mjs} +2 -2
  342. package/dist/{enrich-BQ0mxJRs.mjs.map → enrich-CFJJgxs_.mjs.map} +1 -1
  343. package/dist/{error-CkAseSqZ.mjs → error-CfJMLSkd.mjs} +12 -3
  344. package/dist/error-CfJMLSkd.mjs.map +1 -0
  345. package/dist/{escape-DqD0aCKu.mjs → escape-r8_GbEU2.mjs} +1 -1
  346. package/dist/{escape-DqD0aCKu.mjs.map → escape-r8_GbEU2.mjs.map} +1 -1
  347. package/dist/{field-defs-cache-WUI88LX5.mjs → field-defs-cache-BVrNCgSj.mjs} +5 -5
  348. package/dist/{field-defs-cache-WUI88LX5.mjs.map → field-defs-cache-BVrNCgSj.mjs.map} +1 -1
  349. package/dist/{fts-manager-BbFaF_V8.mjs → fts-manager-BD9s77qh.mjs} +27 -3
  350. package/dist/{fts-manager-BbFaF_V8.mjs.map → fts-manager-BD9s77qh.mjs.map} +1 -1
  351. package/dist/{hash-DrvzIXcz.mjs → hash-DFFrkivP.mjs} +1 -1
  352. package/dist/{hash-DrvzIXcz.mjs.map → hash-DFFrkivP.mjs.map} +1 -1
  353. package/dist/hreflang-CgIxhH4k.d.mts +48 -0
  354. package/dist/hreflang-CgIxhH4k.d.mts.map +1 -0
  355. package/dist/hreflang-pAWUiPd_.mjs +102 -0
  356. package/dist/hreflang-pAWUiPd_.mjs.map +1 -0
  357. package/dist/{import-fYLf14Co.mjs → import-2jHtLzsO.mjs} +6 -6
  358. package/dist/{import-fYLf14Co.mjs.map → import-2jHtLzsO.mjs.map} +1 -1
  359. package/dist/{index-B9sIxwe4.d.mts → index-BXOCyLj0.d.mts} +115 -21
  360. package/dist/index-BXOCyLj0.d.mts.map +1 -0
  361. package/dist/{index-Bm-Gy8Mh.d.mts → index-DxjWLKGT.d.mts} +3 -3
  362. package/dist/{index-Bm-Gy8Mh.d.mts.map → index-DxjWLKGT.d.mts.map} +1 -1
  363. package/dist/index.d.mts +20 -19
  364. package/dist/index.mjs +68 -64
  365. package/dist/{init-lock-6b309ZrF.mjs → init-lock-DJkX6Hto.mjs} +1 -1
  366. package/dist/{init-lock-6b309ZrF.mjs.map → init-lock-DJkX6Hto.mjs.map} +1 -1
  367. package/dist/{load-Djn3HWQS.mjs → load-DgoWMdRX.mjs} +2 -2
  368. package/dist/{load-Djn3HWQS.mjs.map → load-DgoWMdRX.mjs.map} +1 -1
  369. package/dist/{loader-CB6YHeul.mjs → loader-Rb8QlGZp.mjs} +5 -20
  370. package/dist/loader-Rb8QlGZp.mjs.map +1 -0
  371. package/dist/{manifest-schema-BHlgZM1W.mjs → manifest-schema-DeGNma2O.mjs} +3 -1
  372. package/dist/manifest-schema-DeGNma2O.mjs.map +1 -0
  373. package/dist/media/image-endpoint.mjs +1 -1
  374. package/dist/media/index.d.mts +1 -1
  375. package/dist/media/index.mjs +2 -2
  376. package/dist/media/local-runtime.d.mts +13 -12
  377. package/dist/media/local-runtime.d.mts.map +1 -1
  378. package/dist/media/local-runtime.mjs +8 -8
  379. package/dist/{media-BrXIaMFN.mjs → media-D2ZFowpn.mjs} +2 -2
  380. package/dist/media-D2ZFowpn.mjs.map +1 -0
  381. package/dist/{media-allowlist-CRHEeJ_i.mjs → media-allowlist-Dc05vKtt.mjs} +2 -2
  382. package/dist/{media-allowlist-CRHEeJ_i.mjs.map → media-allowlist-Dc05vKtt.mjs.map} +1 -1
  383. package/dist/{media-url-CKi5XSw4.mjs → media-url-DgIEYZr4.mjs} +1 -1
  384. package/dist/{media-url-CKi5XSw4.mjs.map → media-url-DgIEYZr4.mjs.map} +1 -1
  385. package/dist/media-usage-B__v9NMm.mjs +421 -0
  386. package/dist/media-usage-B__v9NMm.mjs.map +1 -0
  387. package/dist/{menus-Cfivc2Ad.mjs → menus-BpfevlJD.mjs} +3 -3
  388. package/dist/{menus-Cfivc2Ad.mjs.map → menus-BpfevlJD.mjs.map} +1 -1
  389. package/dist/{menus-b2T4clbO.mjs → menus-BtvH7Z7t.mjs} +31 -12
  390. package/dist/menus-BtvH7Z7t.mjs.map +1 -0
  391. package/dist/{mime-CCEzze7W.mjs → mime-DfIVjNkr.mjs} +1 -1
  392. package/dist/{mime-CCEzze7W.mjs.map → mime-DfIVjNkr.mjs.map} +1 -1
  393. package/dist/{mode-BM9QgEmt.mjs → mode-BVnhGZ2o.mjs} +1 -1
  394. package/dist/{mode-BM9QgEmt.mjs.map → mode-BVnhGZ2o.mjs.map} +1 -1
  395. package/dist/{normalize-CeglrBT9.mjs → normalize-C-SHXmra.mjs} +1 -1
  396. package/dist/{normalize-CeglrBT9.mjs.map → normalize-C-SHXmra.mjs.map} +1 -1
  397. package/dist/{oauth-authorization-CXWkIGT4.mjs → oauth-authorization-BWqWTHBK.mjs} +5 -5
  398. package/dist/{oauth-authorization-CXWkIGT4.mjs.map → oauth-authorization-BWqWTHBK.mjs.map} +1 -1
  399. package/dist/{oauth-clients-BmTI5rsl.mjs → oauth-clients-43_XADoa.mjs} +1 -1
  400. package/dist/{oauth-clients-BmTI5rsl.mjs.map → oauth-clients-43_XADoa.mjs.map} +1 -1
  401. package/dist/{oauth-state-store-YLFLcH5F.mjs → oauth-state-store-BRSUEkmx.mjs} +1 -1
  402. package/dist/{oauth-state-store-YLFLcH5F.mjs.map → oauth-state-store-BRSUEkmx.mjs.map} +1 -1
  403. package/dist/{oauth-user-lookup-Bsc_GsX-.mjs → oauth-user-lookup-Ctgemb0k.mjs} +1 -1
  404. package/dist/{oauth-user-lookup-Bsc_GsX-.mjs.map → oauth-user-lookup-Ctgemb0k.mjs.map} +1 -1
  405. package/dist/object-cache/memory.d.mts +1 -1
  406. package/dist/{options-DTTML-Tx.mjs → options-BlmBHTvX.mjs} +1 -1
  407. package/dist/{options-DTTML-Tx.mjs.map → options-BlmBHTvX.mjs.map} +1 -1
  408. package/dist/{options-iMpRRxIe.d.mts → options-COXGTtqd.d.mts} +3 -3
  409. package/dist/{options-iMpRRxIe.d.mts.map → options-COXGTtqd.d.mts.map} +1 -1
  410. package/dist/page/index.d.mts +2 -2
  411. package/dist/{parse-GmjecGct.mjs → parse-GLksnn-B.mjs} +2 -2
  412. package/dist/{parse-GmjecGct.mjs.map → parse-GLksnn-B.mjs.map} +1 -1
  413. package/dist/{passkey-config-De0ahnRg.mjs → passkey-config-CjA1UbDj.mjs} +1 -1
  414. package/dist/{passkey-config-De0ahnRg.mjs.map → passkey-config-CjA1UbDj.mjs.map} +1 -1
  415. package/dist/{patterns-BKmjvM7K.mjs → patterns-CiyXeDgr.mjs} +1 -1
  416. package/dist/{patterns-BKmjvM7K.mjs.map → patterns-CiyXeDgr.mjs.map} +1 -1
  417. package/dist/{placeholder-BSKwzRe4.d.mts → placeholder-DR-eeZr-.d.mts} +1 -1
  418. package/dist/{placeholder-BSKwzRe4.d.mts.map → placeholder-DR-eeZr-.d.mts.map} +1 -1
  419. package/dist/plugin-types.d.mts +1 -1
  420. package/dist/plugin-utils.d.mts +11 -10
  421. package/dist/plugin-utils.d.mts.map +1 -1
  422. package/dist/plugins/adapt-sandbox-entry.d.mts +11 -10
  423. package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -1
  424. package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
  425. package/dist/{portable-text-cCa0cGV6.mjs → portable-text-BAkgEKcv.mjs} +1 -1
  426. package/dist/{portable-text-cCa0cGV6.mjs.map → portable-text-BAkgEKcv.mjs.map} +1 -1
  427. package/dist/{preview-SSXNVPAC.mjs → preview-Rloa09Wp.mjs} +2 -2
  428. package/dist/{preview-SSXNVPAC.mjs.map → preview-Rloa09Wp.mjs.map} +1 -1
  429. package/dist/{public-url-Danv47cS.mjs → public-url-C0cEvrRu.mjs} +1 -1
  430. package/dist/{public-url-Danv47cS.mjs.map → public-url-C0cEvrRu.mjs.map} +1 -1
  431. package/dist/{schema-CX9cfNkZ.mjs → query-BxT95opY.mjs} +5 -10
  432. package/dist/query-BxT95opY.mjs.map +1 -0
  433. package/dist/{query-1Cp6U9oo.mjs → query-ZzvXyxWq.mjs} +15 -15
  434. package/dist/query-ZzvXyxWq.mjs.map +1 -0
  435. package/dist/{rate-limit-C_Lf63Dd.mjs → rate-limit-Cg4Rvc_-.mjs} +2 -2
  436. package/dist/{rate-limit-C_Lf63Dd.mjs.map → rate-limit-Cg4Rvc_-.mjs.map} +1 -1
  437. package/dist/{redirect-CoW3G-0U.mjs → redirect-Ce_6Yb6z.mjs} +1 -1
  438. package/dist/{redirect-CoW3G-0U.mjs.map → redirect-Ce_6Yb6z.mjs.map} +1 -1
  439. package/dist/{redirect-IyfNirgS.mjs → redirect-Ch3E98qZ.mjs} +4 -4
  440. package/dist/{redirect-IyfNirgS.mjs.map → redirect-Ch3E98qZ.mjs.map} +1 -1
  441. package/dist/{redirects-Bs1g42Ri.mjs → redirects-CXmVGRNZ.mjs} +6 -6
  442. package/dist/{redirects-Bs1g42Ri.mjs.map → redirects-CXmVGRNZ.mjs.map} +1 -1
  443. package/dist/{redirects-DXEXBAII.mjs → redirects-CeD7K7EX.mjs} +43 -5
  444. package/dist/redirects-CeD7K7EX.mjs.map +1 -0
  445. package/dist/{registry-B57vp_X9.mjs → registry-CyIXPi4E.mjs} +8 -8
  446. package/dist/{registry-B57vp_X9.mjs.map → registry-CyIXPi4E.mjs.map} +1 -1
  447. package/dist/relation-Ba68Tm4Z.mjs +334 -0
  448. package/dist/relation-Ba68Tm4Z.mjs.map +1 -0
  449. package/dist/{byline-fields-2Lp5Hbgf.mjs → relations-BmBmX4Eb.mjs} +60 -4
  450. package/dist/relations-BmBmX4Eb.mjs.map +1 -0
  451. package/dist/relations-C8hVyEy0.mjs +397 -0
  452. package/dist/relations-C8hVyEy0.mjs.map +1 -0
  453. package/dist/{byline-fields-CHE8UStY.d.mts → relations-CbBpoOys.d.mts} +182 -29
  454. package/dist/relations-CbBpoOys.d.mts.map +1 -0
  455. package/dist/{request-cache-KCNHp_RJ.mjs → request-cache-BSUptuJR.mjs} +1 -1
  456. package/dist/{request-cache-KCNHp_RJ.mjs.map → request-cache-BSUptuJR.mjs.map} +1 -1
  457. package/dist/{request-meta-DbvXaAvU.mjs → request-meta-DCwktC5P.mjs} +2 -2
  458. package/dist/{request-meta-DbvXaAvU.mjs.map → request-meta-DCwktC5P.mjs.map} +1 -1
  459. package/dist/{resolve-DR3wqMTy.mjs → resolve-Cd9dzclN.mjs} +1 -1
  460. package/dist/{resolve-DR3wqMTy.mjs.map → resolve-Cd9dzclN.mjs.map} +1 -1
  461. package/dist/{runner-DvFWoz8-.d.mts → runner-CiNdIk3y.d.mts} +8 -2
  462. package/dist/runner-CiNdIk3y.d.mts.map +1 -0
  463. package/dist/{runner-K8XK101B.mjs → runner-jt65bF7F.mjs} +24 -7
  464. package/dist/{runner-K8XK101B.mjs.map → runner-jt65bF7F.mjs.map} +1 -1
  465. package/dist/runtime.d.mts +12 -11
  466. package/dist/runtime.d.mts.map +1 -1
  467. package/dist/runtime.mjs +4 -4
  468. package/dist/{scheduled-publish-Drffrf2a.d.mts → scheduled-publish-DlJHGcuL.d.mts} +1 -1
  469. package/dist/scheduled-publish-DlJHGcuL.d.mts.map +1 -0
  470. package/dist/schema-DwjufZ1S.mjs +9 -0
  471. package/dist/schema-DwjufZ1S.mjs.map +1 -0
  472. package/dist/{search-DZN2HZ-V.mjs → search-Cl875CVE.mjs} +62 -12
  473. package/dist/search-Cl875CVE.mjs.map +1 -0
  474. package/dist/{secrets-kp760LlD.mjs → secrets-CO_bEmII.mjs} +4 -4
  475. package/dist/{secrets-kp760LlD.mjs.map → secrets-CO_bEmII.mjs.map} +1 -1
  476. package/dist/{sections-CpF5SaGr.mjs → sections-DTKzgoiS.mjs} +3 -3
  477. package/dist/{sections-CpF5SaGr.mjs.map → sections-DTKzgoiS.mjs.map} +1 -1
  478. package/dist/seed/index.d.mts +2 -2
  479. package/dist/seed/index.mjs +23 -23
  480. package/dist/seo/index.d.mts +4 -2
  481. package/dist/seo/index.d.mts.map +1 -1
  482. package/dist/seo/index.mjs +19 -2
  483. package/dist/seo/index.mjs.map +1 -1
  484. package/dist/{seo-DXu2UQnC.mjs → seo-BLm6SXzS.mjs} +2 -2
  485. package/dist/{seo-DXu2UQnC.mjs.map → seo-BLm6SXzS.mjs.map} +1 -1
  486. package/dist/{seo-89-53DDs.mjs → seo-BjrbXAd_.mjs} +1 -1
  487. package/dist/{seo-89-53DDs.mjs.map → seo-BjrbXAd_.mjs.map} +1 -1
  488. package/dist/{service-DQmiBMzt.mjs → service-D6zDmKXy.mjs} +3 -3
  489. package/dist/{service-DQmiBMzt.mjs.map → service-D6zDmKXy.mjs.map} +1 -1
  490. package/dist/{session-user-Cascbax7.mjs → session-user-DoSs6YM8.mjs} +1 -1
  491. package/dist/{session-user-Cascbax7.mjs.map → session-user-DoSs6YM8.mjs.map} +1 -1
  492. package/dist/{settings-zhmdKyJQ.mjs → settings-BuQYvLfy.mjs} +6 -6
  493. package/dist/{settings-zhmdKyJQ.mjs.map → settings-BuQYvLfy.mjs.map} +1 -1
  494. package/dist/{settings-rZyHEcNT.mjs → settings-CmS3TwXf.mjs} +3 -3
  495. package/dist/{settings-rZyHEcNT.mjs.map → settings-CmS3TwXf.mjs.map} +1 -1
  496. package/dist/{setup-complete-Cb3LRyzy.mjs → setup-complete-BPNgLHlU.mjs} +2 -2
  497. package/dist/{setup-complete-Cb3LRyzy.mjs.map → setup-complete-BPNgLHlU.mjs.map} +1 -1
  498. package/dist/{setup-nonce-Z8x0tHf8.mjs → setup-nonce-DEPZX96T.mjs} +1 -1
  499. package/dist/{setup-nonce-Z8x0tHf8.mjs.map → setup-nonce-DEPZX96T.mjs.map} +1 -1
  500. package/dist/{single-flight-cache-BG3B_GhN.mjs → single-flight-cache-DEtW-OEG.mjs} +2 -2
  501. package/dist/{single-flight-cache-BG3B_GhN.mjs.map → single-flight-cache-DEtW-OEG.mjs.map} +1 -1
  502. package/dist/{site-url-BBfDTgZB.mjs → site-url-CcLy5D5s.mjs} +2 -2
  503. package/dist/{site-url-BBfDTgZB.mjs.map → site-url-CcLy5D5s.mjs.map} +1 -1
  504. package/dist/{slugify-Cce3dTdg.mjs → slugify-C_tqlU4G.mjs} +1 -1
  505. package/dist/{slugify-Cce3dTdg.mjs.map → slugify-C_tqlU4G.mjs.map} +1 -1
  506. package/dist/{ssrf-1g7usGBg.mjs → ssrf-TrKYJAy0.mjs} +1 -1
  507. package/dist/{ssrf-1g7usGBg.mjs.map → ssrf-TrKYJAy0.mjs.map} +1 -1
  508. package/dist/{status-CqJQX1QN.mjs → status-D3Gy1Zc6.mjs} +1 -1
  509. package/dist/{status-CqJQX1QN.mjs.map → status-D3Gy1Zc6.mjs.map} +1 -1
  510. package/dist/storage/local.d.mts +1 -1
  511. package/dist/storage/local.mjs +1 -1
  512. package/dist/storage/s3.d.mts +1 -1
  513. package/dist/storage/s3.mjs +1 -1
  514. package/dist/{taxonomies-Bs13SN-F.mjs → taxonomies-ClS4RKaZ.mjs} +13 -9
  515. package/dist/taxonomies-ClS4RKaZ.mjs.map +1 -0
  516. package/dist/{taxonomies-kH9ABDUF.mjs → taxonomies-pUkuD6nj.mjs} +115 -36
  517. package/dist/taxonomies-pUkuD6nj.mjs.map +1 -0
  518. package/dist/{taxonomy-BxGxZZdu.mjs → taxonomy-BkxbUMSw.mjs} +12 -3
  519. package/dist/{taxonomy-BxGxZZdu.mjs.map → taxonomy-BkxbUMSw.mjs.map} +1 -1
  520. package/dist/{tokens-BGT0TUUo.mjs → tokens-BV6fgl5A.mjs} +2 -2
  521. package/dist/{tokens-BGT0TUUo.mjs.map → tokens-BV6fgl5A.mjs.map} +1 -1
  522. package/dist/{transaction-qfqpPVpu.mjs → transaction-D0FOsb3X.mjs} +1 -1
  523. package/dist/{transaction-qfqpPVpu.mjs.map → transaction-D0FOsb3X.mjs.map} +1 -1
  524. package/dist/{transport-DPefPMcz.mjs → transport-DTFhhPOg.mjs} +1 -1
  525. package/dist/{transport-DPefPMcz.mjs.map → transport-DTFhhPOg.mjs.map} +1 -1
  526. package/dist/{transport-BMDWHSgZ.d.mts → transport-DmlmzD2U.d.mts} +1 -1
  527. package/dist/{transport-BMDWHSgZ.d.mts.map → transport-DmlmzD2U.d.mts.map} +1 -1
  528. package/dist/{trusted-proxy-VTPRiX2s.mjs → trusted-proxy-CSgZVESw.mjs} +1 -1
  529. package/dist/{trusted-proxy-VTPRiX2s.mjs.map → trusted-proxy-CSgZVESw.mjs.map} +1 -1
  530. package/dist/{types-CgvhU-hF.d.mts → types-B6-N9cBB.d.mts} +1 -1
  531. package/dist/{types-CgvhU-hF.d.mts.map → types-B6-N9cBB.d.mts.map} +1 -1
  532. package/dist/{types-g_aFIdW0.d.mts → types-BCVuUPrL.d.mts} +1 -1
  533. package/dist/{types-g_aFIdW0.d.mts.map → types-BCVuUPrL.d.mts.map} +1 -1
  534. package/dist/{types-Jo3UYd6b.mjs → types-BNZsPL9z.mjs} +1 -1
  535. package/dist/{types-Jo3UYd6b.mjs.map → types-BNZsPL9z.mjs.map} +1 -1
  536. package/dist/{types-vgEWF18A.d.mts → types-B_pmP7Yq.d.mts} +1 -1
  537. package/dist/{types-vgEWF18A.d.mts.map → types-B_pmP7Yq.d.mts.map} +1 -1
  538. package/dist/{types-CuW0zWDA.mjs → types-Ch8pFMEm.mjs} +3 -3
  539. package/dist/{types-CuW0zWDA.mjs.map → types-Ch8pFMEm.mjs.map} +1 -1
  540. package/dist/{types-ea9sAPck.d.mts → types-Cm4Cz-8M.d.mts} +1 -1
  541. package/dist/{types-ea9sAPck.d.mts.map → types-Cm4Cz-8M.d.mts.map} +1 -1
  542. package/dist/{types-CGbYowGQ.d.mts → types-CtDVagM4.d.mts} +55 -3
  543. package/dist/types-CtDVagM4.d.mts.map +1 -0
  544. package/dist/{types-BcLUUbbR.d.mts → types-DLIrNtv5.d.mts} +11 -3
  545. package/dist/{types-BcLUUbbR.d.mts.map → types-DLIrNtv5.d.mts.map} +1 -1
  546. package/dist/{types-DmHk7vAt.d.mts → types-QnlQIyPE.d.mts} +1 -1
  547. package/dist/{types-DmHk7vAt.d.mts.map → types-QnlQIyPE.d.mts.map} +1 -1
  548. package/dist/{types-_HFLRzTn.d.mts → types-momGysZk.d.mts} +1 -1
  549. package/dist/{types-_HFLRzTn.d.mts.map → types-momGysZk.d.mts.map} +1 -1
  550. package/dist/{types-tM44hEcf.mjs → types-o7xo7VgH.mjs} +1 -1
  551. package/dist/{types-tM44hEcf.mjs.map → types-o7xo7VgH.mjs.map} +1 -1
  552. package/dist/{types-ClUkdbWb.d.mts → types-qljd1hsL.d.mts} +1 -1
  553. package/dist/{types-ClUkdbWb.d.mts.map → types-qljd1hsL.d.mts.map} +1 -1
  554. package/dist/{user-BjAOvU3Z.mjs → user-DTxX_Fha.mjs} +3 -3
  555. package/dist/{user-BjAOvU3Z.mjs.map → user-DTxX_Fha.mjs.map} +1 -1
  556. package/dist/{utils-BJXElxdw.mjs → utils-Cz8PVrl4.mjs} +3 -3
  557. package/dist/{utils-BJXElxdw.mjs.map → utils-Cz8PVrl4.mjs.map} +1 -1
  558. package/dist/{validate-Z1b86_3B.d.mts → validate-9jLuE301.d.mts} +5 -5
  559. package/dist/{validate-Z1b86_3B.d.mts.map → validate-9jLuE301.d.mts.map} +1 -1
  560. package/dist/{validate-CWSnKVIJ.mjs → validate-C3CpWlNt.mjs} +3 -3
  561. package/dist/{validate-CWSnKVIJ.mjs.map → validate-C3CpWlNt.mjs.map} +1 -1
  562. package/dist/{validation-0l8-RYj4.mjs → validation-DM2K_NJC.mjs} +10 -10
  563. package/dist/{validation-0l8-RYj4.mjs.map → validation-DM2K_NJC.mjs.map} +1 -1
  564. package/dist/version-BlIJvNAZ.mjs +7 -0
  565. package/dist/{version-qmeO1XZY.mjs.map → version-BlIJvNAZ.mjs.map} +1 -1
  566. package/dist/{widgets-hWcIicX8.mjs → widgets-B_X6_UXJ.mjs} +4 -4
  567. package/dist/{widgets-hWcIicX8.mjs.map → widgets-B_X6_UXJ.mjs.map} +1 -1
  568. package/dist/{wxr-taxonomies-CNFcsNCr.mjs → wxr-taxonomies-oNJB2S81.mjs} +4 -4
  569. package/dist/{wxr-taxonomies-CNFcsNCr.mjs.map → wxr-taxonomies-oNJB2S81.mjs.map} +1 -1
  570. package/dist/{wxr-taxonomies-0_uxSrmf.d.mts → wxr-taxonomies-tADW4KTa.d.mts} +1 -1
  571. package/dist/{wxr-taxonomies-0_uxSrmf.d.mts.map → wxr-taxonomies-tADW4KTa.d.mts.map} +1 -1
  572. package/dist/{zod-generator-CacrR-i8.mjs → zod-generator-CrfV79De.mjs} +3 -3
  573. package/dist/{zod-generator-CacrR-i8.mjs.map → zod-generator-CrfV79De.mjs.map} +1 -1
  574. package/package.json +11 -7
  575. package/src/api/errors.ts +11 -0
  576. package/src/api/handlers/content.ts +111 -1
  577. package/src/api/handlers/dashboard.ts +2 -0
  578. package/src/api/handlers/index.ts +6 -0
  579. package/src/api/handlers/media-usage.ts +80 -0
  580. package/src/api/handlers/relations.ts +474 -0
  581. package/src/api/handlers/taxonomies.ts +26 -4
  582. package/src/api/handlers/validate-media-fields.ts +1 -1
  583. package/src/api/openapi/document.ts +25 -0
  584. package/src/api/parse.ts +1 -1
  585. package/src/api/schemas/comments.ts +6 -0
  586. package/src/api/schemas/content.ts +8 -0
  587. package/src/api/schemas/index.ts +2 -0
  588. package/src/api/schemas/media-usage.ts +48 -0
  589. package/src/api/schemas/relations.ts +109 -0
  590. package/src/api/schemas/search.ts +1 -0
  591. package/src/astro/integration/index.ts +1 -0
  592. package/src/astro/integration/routes.ts +5 -0
  593. package/src/astro/integration/runtime.ts +24 -0
  594. package/src/astro/middleware/request-context.ts +162 -20
  595. package/src/astro/middleware.ts +22 -9
  596. package/src/astro/routes/api/admin/comments/[id]/status.ts +1 -2
  597. package/src/astro/routes/api/admin/comments/index.ts +1 -2
  598. package/src/astro/routes/api/admin/media-usage/repair.ts +24 -0
  599. package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +1 -2
  600. package/src/astro/routes/api/auth/oauth/[provider].ts +1 -2
  601. package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +13 -0
  602. package/src/astro/routes/api/content/[collection]/[id]/references/[relation]/children.ts +97 -0
  603. package/src/astro/routes/api/content/[collection]/[id]/references/[relation]/parents.ts +48 -0
  604. package/src/astro/routes/api/relations/[id]/index.ts +84 -0
  605. package/src/astro/routes/api/relations/[id]/translations.ts +33 -0
  606. package/src/astro/routes/api/relations/index.ts +56 -0
  607. package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +1 -7
  608. package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +1 -2
  609. package/src/astro/routes/api/search/index.ts +2 -0
  610. package/src/astro/types.ts +2 -0
  611. package/src/cli/commands/export-seed.ts +1 -10
  612. package/src/client/index.ts +39 -0
  613. package/src/comments/service.ts +1 -1
  614. package/src/comments/turnstile.ts +64 -0
  615. package/src/components/BlockquoteGroup.astro +34 -0
  616. package/src/components/CommentForm.astro +12 -5
  617. package/src/components/EmDashHead.astro +27 -1
  618. package/src/components/InlinePortableTextEditor.tsx +34 -0
  619. package/src/components/PortableText.astro +7 -1
  620. package/src/components/index.ts +2 -0
  621. package/src/components/portable-text-blockquote-group.ts +68 -0
  622. package/src/content/converters/portable-text-to-prosemirror.ts +32 -1
  623. package/src/database/dialect-helpers.ts +34 -0
  624. package/src/database/migrations/039_fix_fts5_triggers.ts +1 -1
  625. package/src/database/migrations/runner.ts +46 -6
  626. package/src/database/pg-migration-lock.ts +72 -0
  627. package/src/database/repositories/comment.ts +1 -1
  628. package/src/database/repositories/content.ts +172 -21
  629. package/src/database/repositories/media.ts +1 -1
  630. package/src/database/repositories/relation.ts +130 -5
  631. package/src/database/repositories/taxonomy.ts +9 -0
  632. package/src/database/repositories/types.ts +8 -0
  633. package/src/db/postgres.ts +5 -2
  634. package/src/emdash-runtime.ts +72 -4
  635. package/src/index.ts +6 -2
  636. package/src/loader.ts +1 -31
  637. package/src/mcp/server.ts +29 -3
  638. package/src/plugins/context.ts +85 -0
  639. package/src/plugins/define-plugin.ts +1 -0
  640. package/src/plugins/index.ts +4 -0
  641. package/src/plugins/manifest-schema.ts +2 -0
  642. package/src/plugins/types.ts +59 -0
  643. package/src/schema/byline-registry.ts +1 -1
  644. package/src/search/fts-manager.ts +34 -0
  645. package/src/search/match.ts +50 -0
  646. package/src/search/query.ts +94 -9
  647. package/src/seo/hreflang.ts +192 -0
  648. package/src/seo/index.ts +3 -0
  649. package/src/taxonomies/index.ts +62 -45
  650. package/src/taxonomies/term-counts.ts +117 -0
  651. package/src/virtual-modules.d.ts +1 -0
  652. package/src/visual-editing/editable.ts +41 -35
  653. package/src/visual-editing/toolbar-bootstrap.ts +120 -0
  654. package/src/visual-editing/toolbar.ts +46 -0
  655. package/dist/api-DxFSorgk.mjs.map +0 -1
  656. package/dist/byline-fields-2Lp5Hbgf.mjs.map +0 -1
  657. package/dist/byline-fields-CHE8UStY.d.mts.map +0 -1
  658. package/dist/content-DhgEth4k.mjs.map +0 -1
  659. package/dist/context-BRIpHARC.mjs.map +0 -1
  660. package/dist/dashboard-EvEKF5en.mjs.map +0 -1
  661. package/dist/dialect-helpers-DRI5pyY3.mjs.map +0 -1
  662. package/dist/error-CkAseSqZ.mjs.map +0 -1
  663. package/dist/index-B9sIxwe4.d.mts.map +0 -1
  664. package/dist/loader-CB6YHeul.mjs.map +0 -1
  665. package/dist/manifest-schema-BHlgZM1W.mjs.map +0 -1
  666. package/dist/media-BrXIaMFN.mjs.map +0 -1
  667. package/dist/menus-b2T4clbO.mjs.map +0 -1
  668. package/dist/query-1Cp6U9oo.mjs.map +0 -1
  669. package/dist/redirects-DXEXBAII.mjs.map +0 -1
  670. package/dist/runner-DvFWoz8-.d.mts.map +0 -1
  671. package/dist/scheduled-publish-Drffrf2a.d.mts.map +0 -1
  672. package/dist/schema-CX9cfNkZ.mjs.map +0 -1
  673. package/dist/search-DZN2HZ-V.mjs.map +0 -1
  674. package/dist/taxonomies-Bs13SN-F.mjs.map +0 -1
  675. package/dist/taxonomies-kH9ABDUF.mjs.map +0 -1
  676. package/dist/types-CGbYowGQ.d.mts.map +0 -1
  677. package/dist/version-qmeO1XZY.mjs +0 -7
  678. /package/dist/{api-tokens-DsvvCtdh.mjs → api-tokens-Dk2GrJMY.mjs} +0 -0
  679. /package/dist/{ssrf-CmuGgCq3.mjs → ssrf-B5DOv5E5.mjs} +0 -0
  680. /package/dist/{types-DROWRxYV.mjs → types-DeCZBZxn.mjs} +0 -0
@@ -2,6 +2,9 @@ import { sql, type Kysely } from "kysely";
2
2
  import { ulid } from "ulidx";
3
3
 
4
4
  import { invalidateCollectionCache } from "../../object-cache/index.js";
5
+ import { buildFtsPrefixMatch, buildSlugGlobPrefix } from "../../search/match.js";
6
+ import { chunks, SQL_BATCH_SIZE } from "../../utils/chunks.js";
7
+ import { isMissingTableError } from "../../utils/db-errors.js";
5
8
  import { slugify } from "../../utils/slugify.js";
6
9
  import type { Database } from "../types.js";
7
10
  import { validateIdentifier } from "../validate.js";
@@ -402,16 +405,26 @@ export class ContentRepository {
402
405
  ? (t: string, s: string) => this.findBySlugIncludingTrashed(t, s, locale)
403
406
  : (t: string, s: string) => this.findBySlug(t, s, locale);
404
407
 
405
- if (looksLikeUlid) {
406
- // Try ID first, fall back to slug
407
- const byId = await findById(type, identifier);
408
- if (byId) return byId;
409
- return findBySlug(type, identifier);
408
+ try {
409
+ if (looksLikeUlid) {
410
+ // Try ID first, fall back to slug
411
+ const byId = await findById(type, identifier);
412
+ if (byId) return byId;
413
+ return await findBySlug(type, identifier);
414
+ }
415
+ // Try slug first, fall back to ID
416
+ const bySlug = await findBySlug(type, identifier);
417
+ if (bySlug) return bySlug;
418
+ return await findById(type, identifier);
419
+ } catch (error) {
420
+ // A collection dropped out from under a still-referencing caller (e.g. a
421
+ // relation whose collection was deleted without cascading) leaves the
422
+ // ec_* table missing. Treat that as "not found", matching
423
+ // findManyByIdOrSlug and findTranslationsForGroups, so callers surface a
424
+ // structured NOT_FOUND instead of a 500.
425
+ if (isMissingTableError(error)) return null;
426
+ throw error;
410
427
  }
411
- // Try slug first, fall back to ID
412
- const bySlug = await findBySlug(type, identifier);
413
- if (bySlug) return bySlug;
414
- return findById(type, identifier);
415
428
  }
416
429
 
417
430
  /**
@@ -513,7 +526,7 @@ export class ContentRepository {
513
526
  query = query.where("locale" as any, "=", options.where.locale);
514
527
  }
515
528
 
516
- query = this.applySearchFilter(query, options.where);
529
+ query = this.applySearchFilter(query, options.where, type);
517
530
  query = this.applyDateFilter(query, options.where);
518
531
 
519
532
  // Handle cursor pagination — decodeCursor throws InvalidCursorError
@@ -792,19 +805,46 @@ export class ContentRepository {
792
805
  }
793
806
 
794
807
  /**
795
- * Apply the optional case-insensitive `q` substring filter across the
796
- * handler-resolved `searchColumns` (OR'd). User input is treated literally
797
- * (LIKE wildcards escaped) and `lower()` is applied on both sides for
798
- * SQLite/Postgres case-insensitive parity.
808
+ * Apply the optional `q` filter.
809
+ *
810
+ * When the handler sets `useFts` (collection has a healthy FTS5 index
811
+ * covering the display columns; SQLite only), the filter is served from
812
+ * the index: a token-prefix MATCH against `_emdash_fts_<slug>` OR'd with
813
+ * an index-served `slug GLOB 'term*'` prefix (the slug is not in the FTS
814
+ * index). Both sides are index-backed, so SQLite's OR optimization avoids
815
+ * the full-table scan the LIKE fallback needs (#1517). The trade-off is
816
+ * search semantics: token-prefix matching instead of arbitrary substring.
817
+ *
818
+ * Fallback (Postgres, search disabled, or no usable terms): case-
819
+ * insensitive substring LIKE across the handler-resolved `searchColumns`
820
+ * (OR'd). User input is treated literally (LIKE wildcards escaped) and
821
+ * `lower()` is applied on both sides for SQLite/Postgres parity.
799
822
  */
800
823
  private applySearchFilter<QB extends { where: (cb: (eb: any) => unknown) => QB }>(
801
824
  query: QB,
802
- where?: { q?: string; searchColumns?: string[] },
825
+ where: { q?: string; searchColumns?: string[]; useFts?: boolean } | undefined,
826
+ type: string,
803
827
  ): QB {
804
828
  const term = where?.q?.trim();
805
829
  const columns = where?.searchColumns;
806
830
  if (!term || !columns || columns.length === 0) return query;
807
831
 
832
+ if (where.useFts) {
833
+ const match = buildFtsPrefixMatch(term);
834
+ if (match) {
835
+ validateIdentifier(type, "collection slug");
836
+ const ftsTable = `_emdash_fts_${type}`;
837
+ const slugPrefix = buildSlugGlobPrefix(term);
838
+ return query.where((eb) =>
839
+ eb.or([
840
+ sql<boolean>`id IN (SELECT id FROM ${sql.ref(ftsTable)} WHERE ${sql.ref(ftsTable)} MATCH ${match})`,
841
+ sql<boolean>`slug GLOB ${slugPrefix}`,
842
+ ]),
843
+ );
844
+ }
845
+ // No usable terms (e.g. quotes only) — fall through to LIKE.
846
+ }
847
+
808
848
  const escaped = term.replace(LIKE_WILDCARD_RE, (c) => `\\${c}`);
809
849
  const pattern = `%${escaped}%`;
810
850
 
@@ -866,7 +906,7 @@ export class ContentRepository {
866
906
  query = query.where("locale" as any, "=", where.locale);
867
907
  }
868
908
 
869
- query = this.applySearchFilter(query, where);
909
+ query = this.applySearchFilter(query, where, type);
870
910
  query = this.applyDateFilter(query, where);
871
911
 
872
912
  const result = await query.executeTakeFirst();
@@ -890,13 +930,13 @@ export class ContentRepository {
890
930
  .where("author_id" as never, "is not", null)
891
931
  .execute();
892
932
 
893
- return rows
894
- .map((row) => (row as { author_id: string | null }).author_id)
895
- .filter((id): id is string => id !== null);
933
+ return rows.map((row) => row.author_id).filter((id): id is string => id !== null);
896
934
  }
897
935
 
898
- // get overall statistics (total, published, draft) for a content type in a single query
899
- async getStats(type: string): Promise<{ total: number; published: number; draft: number }> {
936
+ // get overall statistics for a content type in a single query
937
+ async getStats(
938
+ type: string,
939
+ ): Promise<{ total: number; published: number; draft: number; scheduled: number }> {
900
940
  const tableName = getTableName(type);
901
941
 
902
942
  const result = await this.db
@@ -905,6 +945,7 @@ export class ContentRepository {
905
945
  eb.fn.count("id").as("total"),
906
946
  eb.fn.sum(eb.case().when("status", "=", "published").then(1).else(0).end()).as("published"),
907
947
  eb.fn.sum(eb.case().when("status", "=", "draft").then(1).else(0).end()).as("draft"),
948
+ sql<number>`SUM(CASE WHEN scheduled_at IS NOT NULL THEN 1 ELSE 0 END)`.as("scheduled"),
908
949
  ])
909
950
  .where("deleted_at" as never, "is", null)
910
951
  .executeTakeFirst();
@@ -913,6 +954,7 @@ export class ContentRepository {
913
954
  total: Number(result?.total || 0),
914
955
  published: Number(result?.published || 0),
915
956
  draft: Number(result?.draft || 0),
957
+ scheduled: Number(result?.scheduled || 0),
916
958
  };
917
959
  }
918
960
 
@@ -1054,6 +1096,115 @@ export class ContentRepository {
1054
1096
  return result.rows.map((row) => this.mapRow(type, row));
1055
1097
  }
1056
1098
 
1099
+ /**
1100
+ * Batch variant of {@link findTranslations}: every (non-deleted) locale
1101
+ * variant for any of `translationGroups`, in one `WHERE translation_group IN
1102
+ * (...)` query chunked at `SQL_BATCH_SIZE` for D1's bind-parameter limit.
1103
+ * Lets callers resolve many edge groups without an N+1 per group. The caller
1104
+ * groups the flat result by `translationGroup` itself.
1105
+ *
1106
+ * `publishedOnly` restricts the result to `status = 'published'` — reference
1107
+ * reads pass this for callers without `content:read_drafts` so draft/scheduled
1108
+ * entries never leak through an edge traversal.
1109
+ *
1110
+ * A reference edge stores only a collection slug (no SQL FK), so the table may
1111
+ * have been dropped since the edge was written. That is a tolerated dangling
1112
+ * state, not an error: a missing table resolves to no rows, mirroring how the
1113
+ * content read handlers treat `isMissingTableError`.
1114
+ */
1115
+ async findTranslationsForGroups(
1116
+ type: string,
1117
+ translationGroups: string[],
1118
+ options: { publishedOnly?: boolean } = {},
1119
+ ): Promise<ContentItem[]> {
1120
+ if (translationGroups.length === 0) return [];
1121
+ const tableName = getTableName(type);
1122
+ const publishedFilter = options.publishedOnly ? sql`AND status = 'published'` : sql``;
1123
+
1124
+ const items: ContentItem[] = [];
1125
+ try {
1126
+ for (const chunk of chunks(translationGroups, SQL_BATCH_SIZE)) {
1127
+ const result = await sql<Record<string, unknown>>`
1128
+ SELECT * FROM ${sql.ref(tableName)}
1129
+ WHERE translation_group IN (${sql.join(chunk)})
1130
+ AND deleted_at IS NULL
1131
+ ${publishedFilter}
1132
+ ORDER BY locale ASC
1133
+ `.execute(this.db);
1134
+ for (const row of result.rows) items.push(this.mapRow(type, row));
1135
+ }
1136
+ } catch (error) {
1137
+ if (isMissingTableError(error)) return [];
1138
+ throw error;
1139
+ }
1140
+ return items;
1141
+ }
1142
+
1143
+ /**
1144
+ * Batch variant of {@link findByIdOrSlug}: resolve many identifiers (each an
1145
+ * id OR a slug) within `type` in a constant number of queries — one `WHERE id
1146
+ * IN (...)` and one `WHERE slug IN (...)`, each chunked at `SQL_BATCH_SIZE`.
1147
+ * Returns a map from the input identifier to its resolved item; identifiers
1148
+ * that match nothing are absent. Used on write paths that accept a list of
1149
+ * references, so a single request doesn't fan out to an N+1 of point lookups.
1150
+ *
1151
+ * Resolution mirrors {@link findByIdOrSlug}: a ULID-shaped identifier prefers
1152
+ * the id match and falls back to slug; anything else prefers the slug match
1153
+ * and falls back to id. Slug matches collapse to the lowest-locale variant
1154
+ * (`ORDER BY locale ASC`), matching the slug-without-locale lookup.
1155
+ */
1156
+ async findManyByIdOrSlug(type: string, identifiers: string[]): Promise<Map<string, ContentItem>> {
1157
+ const resolved = new Map<string, ContentItem>();
1158
+ const unique = [...new Set(identifiers)];
1159
+ if (unique.length === 0) return resolved;
1160
+
1161
+ const tableName = getTableName(type);
1162
+ const byId = new Map<string, ContentItem>();
1163
+ const bySlug = new Map<string, ContentItem>();
1164
+
1165
+ try {
1166
+ for (const chunk of chunks(unique, SQL_BATCH_SIZE)) {
1167
+ const idRows = await sql<Record<string, unknown>>`
1168
+ SELECT * FROM ${sql.ref(tableName)}
1169
+ WHERE id IN (${sql.join(chunk)})
1170
+ AND deleted_at IS NULL
1171
+ `.execute(this.db);
1172
+ for (const row of idRows.rows) {
1173
+ const item = this.mapRow(type, row);
1174
+ byId.set(item.id, item);
1175
+ }
1176
+
1177
+ const slugRows = await sql<Record<string, unknown>>`
1178
+ SELECT * FROM ${sql.ref(tableName)}
1179
+ WHERE slug IN (${sql.join(chunk)})
1180
+ AND deleted_at IS NULL
1181
+ ORDER BY locale ASC
1182
+ `.execute(this.db);
1183
+ for (const row of slugRows.rows) {
1184
+ const item = this.mapRow(type, row);
1185
+ // First write wins → lowest locale, matching findBySlug without a locale.
1186
+ if (item.slug != null && !bySlug.has(item.slug)) bySlug.set(item.slug, item);
1187
+ }
1188
+ }
1189
+ } catch (error) {
1190
+ // A collection dropped after a relation was created leaves the relation
1191
+ // pointing at a missing table. Treat it like an empty collection (no
1192
+ // matches) so callers surface a structured NOT_FOUND, not a 500 —
1193
+ // mirroring findTranslationsForGroups.
1194
+ if (isMissingTableError(error)) return resolved;
1195
+ throw error;
1196
+ }
1197
+
1198
+ for (const identifier of unique) {
1199
+ const looksLikeUlid = ULID_PATTERN.test(identifier);
1200
+ const item = looksLikeUlid
1201
+ ? (byId.get(identifier) ?? bySlug.get(identifier))
1202
+ : (bySlug.get(identifier) ?? byId.get(identifier));
1203
+ if (item) resolved.set(identifier, item);
1204
+ }
1205
+ return resolved;
1206
+ }
1207
+
1057
1208
  /**
1058
1209
  * Publish the current draft
1059
1210
  *
@@ -118,7 +118,7 @@ export class MediaRepository {
118
118
 
119
119
  await this.db.insertInto("media").values(row).execute();
120
120
 
121
- return this.rowToItem(row as MediaRow);
121
+ return this.rowToItem(row);
122
122
  }
123
123
 
124
124
  /**
@@ -3,6 +3,7 @@ import { ulid } from "ulidx";
3
3
 
4
4
  import { chunks, SQL_BATCH_SIZE } from "../../utils/chunks.js";
5
5
  import type { Database, RelationTable, ContentReferenceTable } from "../types.js";
6
+ import { decodeCursor, encodeCursor, InvalidCursorError, type FindManyResult } from "./types.js";
6
7
 
7
8
  export interface Relation {
8
9
  id: string;
@@ -17,8 +18,12 @@ export interface Relation {
17
18
 
18
19
  export interface CreateRelationInput {
19
20
  name: string;
20
- parentCollection: string;
21
- childCollection: string;
21
+ /** Required for a base relation; ignored (inherited from the source) when
22
+ * `translationOf` is set. */
23
+ parentCollection?: string;
24
+ /** Required for a base relation; ignored (inherited from the source) when
25
+ * `translationOf` is set. */
26
+ childCollection?: string;
22
27
  parentLabel: string;
23
28
  childLabel: string;
24
29
  /** Omit to let the DB default (current value: 'en') apply. Higher layers
@@ -73,9 +78,9 @@ export class RelationRepository {
73
78
  const now = new Date().toISOString();
74
79
 
75
80
  let translationGroup = id;
76
- let name = input.name;
77
- let parentCollection = input.parentCollection;
78
- let childCollection = input.childCollection;
81
+ let name: string;
82
+ let parentCollection: string;
83
+ let childCollection: string;
79
84
 
80
85
  if (input.translationOf) {
81
86
  const source = await this.findById(input.translationOf);
@@ -87,6 +92,18 @@ export class RelationRepository {
87
92
  name = source.name;
88
93
  parentCollection = source.parentCollection;
89
94
  childCollection = source.childCollection;
95
+ } else {
96
+ // A base relation carries its own structural fields. The API layer's Zod
97
+ // schema enforces this; guard here too since the repo trusts its inputs
98
+ // and the columns are NOT NULL.
99
+ if (input.parentCollection === undefined || input.childCollection === undefined) {
100
+ throw new Error(
101
+ "parentCollection and childCollection are required unless translationOf is set",
102
+ );
103
+ }
104
+ name = input.name;
105
+ parentCollection = input.parentCollection;
106
+ childCollection = input.childCollection;
90
107
  }
91
108
 
92
109
  await this.db
@@ -319,6 +336,61 @@ export class RelationRepository {
319
336
  return rows.map((row) => this.rowToReference(row));
320
337
  }
321
338
 
339
+ /**
340
+ * Forward traversal, paginated: one page of a parent's children for a
341
+ * relation, ordered by `(sort_order, id)`. Use this on request paths — a
342
+ * parent's children are capped but still up to 1000, and an unbounded read
343
+ * scales poorly. Returns `{ items, nextCursor? }`; the cursor's order value is
344
+ * the row's `sort_order`. Default limit 50, max 100.
345
+ */
346
+ async getChildrenPage(
347
+ relation: string,
348
+ parentGroup: string,
349
+ options: { limit?: number; cursor?: string } = {},
350
+ ): Promise<FindManyResult<ContentReference>> {
351
+ const relationGroup = await this.resolveRelationGroup(relation);
352
+ if (!relationGroup) return { items: [] };
353
+
354
+ const limit = Math.min(options.limit || 50, 100);
355
+
356
+ let query = this.db
357
+ .selectFrom("_emdash_content_references")
358
+ .selectAll()
359
+ .where("relation_group", "=", relationGroup)
360
+ .where("parent_group", "=", parentGroup);
361
+
362
+ if (options.cursor) {
363
+ const decoded = decodeCursor(options.cursor);
364
+ const sortOrder = Number(decoded.orderValue);
365
+ // `decodeCursor` only guarantees `orderValue` is a string; a hand-crafted
366
+ // cursor with a non-numeric order value would coerce to NaN and blow up at
367
+ // the driver bind as a 500. A bad cursor is a client error — surface it as
368
+ // INVALID_CURSOR (400). Server-issued cursors are always numeric here.
369
+ if (!Number.isFinite(sortOrder)) throw new InvalidCursorError(options.cursor);
370
+ query = query.where((eb) =>
371
+ eb.or([
372
+ eb("sort_order", ">", sortOrder),
373
+ eb.and([eb("sort_order", "=", sortOrder), eb("id", ">", decoded.id)]),
374
+ ]),
375
+ );
376
+ }
377
+
378
+ const rows = await query
379
+ .orderBy("sort_order", "asc")
380
+ .orderBy("id", "asc")
381
+ .limit(limit + 1)
382
+ .execute();
383
+
384
+ const hasMore = rows.length > limit;
385
+ const items = rows.slice(0, limit).map((row) => this.rowToReference(row));
386
+ const result: FindManyResult<ContentReference> = { items };
387
+ const last = items.at(-1);
388
+ if (hasMore && last) {
389
+ result.nextCursor = encodeCursor(String(last.sortOrder), last.id);
390
+ }
391
+ return result;
392
+ }
393
+
322
394
  /** Backlink traversal: the parents that reference a child for a relation. */
323
395
  async getParents(relation: string, childGroup: string): Promise<ContentReference[]> {
324
396
  const relationGroup = await this.resolveRelationGroup(relation);
@@ -346,6 +418,15 @@ export class RelationRepository {
346
418
  * relying on the insert's onConflict to silently drop them. Not wrapped in a
347
419
  * transaction: a crash between the delete and insert leaves the parent with
348
420
  * no children — acceptable for a replace-all, since a retry restores state.
421
+ *
422
+ * Concurrency: two simultaneous replace-all calls for the same (relation,
423
+ * parent) can interleave their deletes and inserts and merge into the union of
424
+ * both sets (a lost update — neither "replace" wins). This is non-corrupting —
425
+ * keyset pagination stays totally ordered via the `(sort_order, id)` tiebreak
426
+ * even with duplicate sort_orders — and a single client editing one parent's
427
+ * children serially never hits it. A D1-portable fix isn't available (no
428
+ * multi-statement transactions), so concurrent replace-all on one parent is
429
+ * unsupported by design rather than guarded here.
349
430
  */
350
431
  async setChildren(relation: string, parentGroup: string, childGroups: string[]): Promise<void> {
351
432
  const relationGroup = await this.resolveRelationGroup(relation);
@@ -381,6 +462,50 @@ export class RelationRepository {
381
462
  .execute();
382
463
  }
383
464
 
465
+ /**
466
+ * Backlink traversal, paginated: one page of the parents that reference a
467
+ * child for a relation, ordered by `id`. Unlike a parent's children, a
468
+ * child's backlinks are *unbounded* — one popular entry can be referenced by
469
+ * arbitrarily many parents — so this read must paginate. Returns
470
+ * `{ items, nextCursor? }`; the cursor's order value is the row `id`. Default
471
+ * limit 50, max 100.
472
+ */
473
+ async getParentsPage(
474
+ relation: string,
475
+ childGroup: string,
476
+ options: { limit?: number; cursor?: string } = {},
477
+ ): Promise<FindManyResult<ContentReference>> {
478
+ const relationGroup = await this.resolveRelationGroup(relation);
479
+ if (!relationGroup) return { items: [] };
480
+
481
+ const limit = Math.min(options.limit || 50, 100);
482
+
483
+ let query = this.db
484
+ .selectFrom("_emdash_content_references")
485
+ .selectAll()
486
+ .where("relation_group", "=", relationGroup)
487
+ .where("child_group", "=", childGroup);
488
+
489
+ if (options.cursor) {
490
+ const decoded = decodeCursor(options.cursor);
491
+ query = query.where("id", ">", decoded.id);
492
+ }
493
+
494
+ const rows = await query
495
+ .orderBy("id", "asc")
496
+ .limit(limit + 1)
497
+ .execute();
498
+
499
+ const hasMore = rows.length > limit;
500
+ const items = rows.slice(0, limit).map((row) => this.rowToReference(row));
501
+ const result: FindManyResult<ContentReference> = { items };
502
+ const last = items.at(-1);
503
+ if (hasMore && last) {
504
+ result.nextCursor = encodeCursor(last.id, last.id);
505
+ }
506
+ return result;
507
+ }
508
+
384
509
  /**
385
510
  * Remove every edge where `group` is the parent OR the child — i.e. ensure no
386
511
  * orphaned reference edges survive when a content entry is deleted. The
@@ -404,6 +404,12 @@ export class TaxonomyRepository {
404
404
  /**
405
405
  * Count content entries that use any translation of this term. Accepts
406
406
  * either a term id or a translation_group — we normalise to the group.
407
+ *
408
+ * Counts raw pivot rows regardless of the entry's status or deletion —
409
+ * drafts and trashed entries are included. User-facing counts (admin term
410
+ * list/get, public widget and term pages) use `fetchVisibleTermCounts`
411
+ * from `taxonomies/term-counts.ts` instead, which counts only publicly
412
+ * visible entries.
407
413
  */
408
414
  async countEntriesWithTerm(termIdOrGroup: string): Promise<number> {
409
415
  const group = await this.resolveTranslationGroup(termIdOrGroup);
@@ -453,6 +459,9 @@ export class TaxonomyRepository {
453
459
  *
454
460
  * Pass translation_groups (not term ids) — `content_taxonomies.taxonomy_id`
455
461
  * stores the translation_group so a single assignment spans every locale.
462
+ *
463
+ * Like `countEntriesWithTerm`, this counts raw pivot rows regardless of
464
+ * status/deletion; user-facing counts go through `fetchVisibleTermCounts`.
456
465
  */
457
466
  async countEntriesForTerms(translationGroups: string[]): Promise<Map<string, number>> {
458
467
  if (translationGroups.length === 0) return new Map();
@@ -158,6 +158,14 @@ export interface FindManyOptions {
158
158
  * repository stays generic. Each name is validated as a SQL identifier.
159
159
  */
160
160
  searchColumns?: string[];
161
+ /**
162
+ * Serve `q` from the collection's FTS5 index (`_emdash_fts_<slug>`)
163
+ * instead of an unindexable substring LIKE. Set by the handler only
164
+ * when the collection has search enabled and the index exists
165
+ * (SQLite only). The repository combines a token-prefix MATCH with
166
+ * an index-served slug prefix so slug lookups keep working.
167
+ */
168
+ useFts?: boolean;
161
169
  /** Inclusive date range over a whitelisted timestamp column. */
162
170
  dateFilter?: ContentDateFilter;
163
171
  };
@@ -5,9 +5,10 @@
5
5
  * Loaded at runtime via virtual module.
6
6
  */
7
7
 
8
- import { PostgresDialect } from "kysely";
8
+ import type { PostgresDialect } from "kysely";
9
9
  import { Pool } from "pg";
10
10
 
11
+ import { FailFastPostgresDialect } from "../database/pg-migration-lock.js";
11
12
  import type { PostgresConfig } from "./adapters.js";
12
13
 
13
14
  /**
@@ -26,5 +27,7 @@ export function createDialect(config: PostgresConfig): PostgresDialect {
26
27
  max: config.pool?.max ?? 10,
27
28
  });
28
29
 
29
- return new PostgresDialect({ pool });
30
+ // Fail-fast migration locking instead of Kysely's blocking advisory
31
+ // lock — see pg-migration-lock.ts (#1744).
32
+ return new FailFastPostgresDialect({ pool });
30
33
  }
@@ -22,7 +22,11 @@ import { getAuthMode } from "./auth/mode.js";
22
22
  import { getTrustedProxyHeaders } from "./auth/trusted-proxy.js";
23
23
  import { isSqlite } from "./database/dialect-helpers.js";
24
24
  import { kyselyLogOption } from "./database/instrumentation.js";
25
- import { MIGRATION_RACE_WAIT_MS, runMigrations } from "./database/migrations/runner.js";
25
+ import {
26
+ ConcurrentMigrationTimeoutError,
27
+ MIGRATION_RACE_WAIT_MS,
28
+ runMigrations,
29
+ } from "./database/migrations/runner.js";
26
30
  import { RevisionRepository } from "./database/repositories/revision.js";
27
31
  import type {
28
32
  ContentItem as ContentItemInternal,
@@ -385,18 +389,42 @@ const DB_HOLDER_KEY = Symbol.for("emdash:db-cache");
385
389
  interface DbHolder {
386
390
  cache: Map<string, Kysely<Database>>;
387
391
  lock: InitLock;
392
+ /**
393
+ * Recent migration failures, keyed like `cache`. A failed migration is
394
+ * near-certain to fail again immediately (schema conflict, broken
395
+ * migration), and without this every request in a warm isolate would
396
+ * re-attempt it — on Workers with Postgres that stampedes the database
397
+ * through the migration advisory lock (#1744). Entries expire after
398
+ * DB_INIT_FAILURE_BACKOFF_MS; a successful init clears them.
399
+ */
400
+ failures: Map<string, { at: number; message: string }>;
388
401
  }
389
402
  const globalSymbolStore = globalThis as Record<symbol, unknown>;
390
403
  function getDbHolder(): DbHolder {
391
404
  // eslint-disable-next-line typescript/no-unsafe-type-assertion -- globalThis symbol slot, written only below
392
405
  let holder = globalSymbolStore[DB_HOLDER_KEY] as DbHolder | undefined;
393
406
  if (!holder) {
394
- holder = { cache: new Map<string, Kysely<Database>>(), lock: createInitLock() };
407
+ holder = {
408
+ cache: new Map<string, Kysely<Database>>(),
409
+ lock: createInitLock(),
410
+ failures: new Map(),
411
+ };
395
412
  globalSymbolStore[DB_HOLDER_KEY] = holder;
396
413
  }
414
+ // A holder created by an older copy of this module (dev-server HMR keeps
415
+ // globalThis across reloads) may predate the failures map.
416
+ holder.failures ??= new Map();
397
417
  return holder;
398
418
  }
399
419
 
420
+ /**
421
+ * After a database init fails (migrations threw), skip re-attempting for
422
+ * this long. Cold isolates still get one attempt each, so a transient
423
+ * failure heals on its own; a persistently failing migration is retried at
424
+ * most once per backoff window per isolate instead of on every request.
425
+ */
426
+ const DB_INIT_FAILURE_BACKOFF_MS = 30_000;
427
+
400
428
  /**
401
429
  * Auto-seed runs at most once per isolate per database. Its lock + "done" set
402
430
  * live on globalThis (same bundler-duplication reasoning as the db cache) so a
@@ -1623,14 +1651,52 @@ export class EmDashRuntime {
1623
1651
  // owner's init still completes and populates the cache. Net: a stale
1624
1652
  // lock is reclaimed after a deadline.
1625
1653
  const holder = getDbHolder();
1654
+
1655
+ const throwIfBackingOff = () => {
1656
+ const failure = holder.failures.get(cacheKey);
1657
+ if (!failure) return;
1658
+ if (Date.now() - failure.at < DB_INIT_FAILURE_BACKOFF_MS) {
1659
+ throw new Error(
1660
+ `Database initialization is backing off after a recent migration failure: ${failure.message}`,
1661
+ );
1662
+ }
1663
+ // Expired — drop it so the map only ever holds active backoffs.
1664
+ holder.failures.delete(cacheKey);
1665
+ };
1666
+ throwIfBackingOff();
1667
+
1626
1668
  return initWithLock(
1627
1669
  holder.lock,
1628
1670
  () => holder.cache.get(cacheKey),
1629
1671
  async (isCurrentClaim) => {
1672
+ // Re-check under the lock: a request that entered the wait loop
1673
+ // before the failure was recorded must not immediately re-run
1674
+ // the failing migration when the lock frees up.
1675
+ throwIfBackingOff();
1676
+
1630
1677
  const dialect = deps.createDialect(dbConfig.config);
1631
1678
  const db = new Kysely<Database>({ dialect, log: kyselyLogOption() });
1632
1679
 
1633
- await runMigrations(db);
1680
+ try {
1681
+ await runMigrations(db);
1682
+ } catch (error) {
1683
+ // Timing out behind another instance's in-flight migrations
1684
+ // is not a failure of OUR migration — the holder may just be
1685
+ // slow. Don't back off for it: the next request waits again
1686
+ // and init recovers the moment the holder finishes.
1687
+ if (!(error instanceof ConcurrentMigrationTimeoutError)) {
1688
+ holder.failures.set(cacheKey, {
1689
+ at: Date.now(),
1690
+ message: error instanceof Error ? error.message : String(error),
1691
+ });
1692
+ }
1693
+ // Every attempt builds a fresh dialect/pool; close it or each
1694
+ // failed init leaks its connection(s) (#1744 observed these
1695
+ // piling up as idle Postgres connections).
1696
+ await db.destroy().catch(() => {});
1697
+ throw error;
1698
+ }
1699
+ holder.failures.delete(cacheKey);
1634
1700
 
1635
1701
  // Note: legacy installs may carry a stray `emdash:manifest_cache`
1636
1702
  // row in the options table from versions that persisted a JSON
@@ -2520,7 +2586,7 @@ export class EmDashRuntime {
2520
2586
  ...r.data,
2521
2587
  item: { ...item, data: mergedData, liveData },
2522
2588
  },
2523
- } as T;
2589
+ };
2524
2590
  } catch (error) {
2525
2591
  // Non-fatal — fall back to the unhydrated response. Log so the
2526
2592
  // failure isn't completely silent (the response will look stale
@@ -2540,6 +2606,7 @@ export class EmDashRuntime {
2540
2606
  bylines?: Array<{ bylineId: string; roleLabel?: string | null }>;
2541
2607
  locale?: string;
2542
2608
  translationOf?: string;
2609
+ taxonomies?: Record<string, string[]>;
2543
2610
  },
2544
2611
  ) {
2545
2612
  // Run beforeSave hooks (trusted plugins)
@@ -2604,6 +2671,7 @@ export class EmDashRuntime {
2604
2671
  canonical?: string | null;
2605
2672
  noIndex?: boolean;
2606
2673
  };
2674
+ taxonomies?: Record<string, string[]>;
2607
2675
  publishedAt?: string | null;
2608
2676
  locale?: string;
2609
2677
  /** Skip revision creation (used by autosave) */
package/src/index.ts CHANGED
@@ -247,6 +247,10 @@ export type {
247
247
  MediaAccess,
248
248
  HttpAccess,
249
249
  LogAccess,
250
+ TaxonomyAccess,
251
+ TaxonomyDefInfo,
252
+ TaxonomyTermInfo,
253
+ TaxonomyReadOptions,
250
254
  PluginHooks,
251
255
  HookConfig,
252
256
  HookName,
@@ -404,8 +408,8 @@ export type {
404
408
  } from "./settings/types.js";
405
409
 
406
410
  // SEO
407
- export { getSeoMeta, getContentSeo } from "./seo/index.js";
408
- export type { SeoMeta, SeoMetaOptions } from "./seo/index.js";
411
+ export { getSeoMeta, getContentSeo, getHreflangAlternates } from "./seo/index.js";
412
+ export type { SeoMeta, SeoMetaOptions, HreflangAlternate, HreflangOptions } from "./seo/index.js";
409
413
 
410
414
  // Public page contribution types
411
415
  export type {