emdash 0.22.0 → 0.24.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 (538) hide show
  1. package/dist/{adapters-hUy8vOB-.d.mts → adapters-DAAjOID4.d.mts} +1 -1
  2. package/dist/{adapters-hUy8vOB-.d.mts.map → adapters-DAAjOID4.d.mts.map} +1 -1
  3. package/dist/{allowed-origins-DjlhyfNN.mjs → allowed-origins-D5FxMUo8.mjs} +2 -2
  4. package/dist/{allowed-origins-DjlhyfNN.mjs.map → allowed-origins-D5FxMUo8.mjs.map} +1 -1
  5. package/dist/api/route-utils.d.mts +3 -3
  6. package/dist/api/route-utils.mjs +15 -15
  7. package/dist/api/schemas/index.d.mts +1 -1
  8. package/dist/api/schemas/index.mjs +3 -3
  9. package/dist/{api-BDuM4bEy.mjs → api-d_0wi9fW.mjs} +20 -20
  10. package/dist/{api-BDuM4bEy.mjs.map → api-d_0wi9fW.mjs.map} +1 -1
  11. package/dist/{api-tokens-ViYKeIp9.mjs → api-tokens-D6ppjIVi.mjs} +2 -2
  12. package/dist/{api-tokens-ViYKeIp9.mjs.map → api-tokens-D6ppjIVi.mjs.map} +1 -1
  13. package/dist/{apply-wDqQoxX4.mjs → apply-BE9UaWX0.mjs} +17 -17
  14. package/dist/{apply-wDqQoxX4.mjs.map → apply-BE9UaWX0.mjs.map} +1 -1
  15. package/dist/astro/index.d.mts +11 -11
  16. package/dist/astro/index.mjs +36 -11
  17. package/dist/astro/index.mjs.map +1 -1
  18. package/dist/astro/middleware/auth.d.mts +9 -9
  19. package/dist/astro/middleware/auth.mjs +7 -7
  20. package/dist/astro/middleware/redirect.mjs +5 -5
  21. package/dist/astro/middleware/request-context.d.mts.map +1 -1
  22. package/dist/astro/middleware/request-context.mjs +5 -3
  23. package/dist/astro/middleware/request-context.mjs.map +1 -1
  24. package/dist/astro/middleware/setup.mjs +1 -1
  25. package/dist/astro/middleware.d.mts +1 -1
  26. package/dist/astro/middleware.d.mts.map +1 -1
  27. package/dist/astro/middleware.mjs +362 -141
  28. package/dist/astro/middleware.mjs.map +1 -1
  29. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +6 -6
  30. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +6 -6
  31. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +4 -4
  32. package/dist/astro/routes/api/admin/api-tokens/index.mjs +5 -5
  33. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +4 -4
  34. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +8 -8
  35. package/dist/astro/routes/api/admin/byline-fields/index.mjs +8 -8
  36. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +8 -8
  37. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +13 -12
  38. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs.map +1 -1
  39. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +13 -12
  40. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs.map +1 -1
  41. package/dist/astro/routes/api/admin/bylines/index.mjs +13 -12
  42. package/dist/astro/routes/api/admin/bylines/index.mjs.map +1 -1
  43. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +11 -11
  44. package/dist/astro/routes/api/admin/comments/_id_.mjs +5 -5
  45. package/dist/astro/routes/api/admin/comments/bulk.mjs +9 -9
  46. package/dist/astro/routes/api/admin/comments/counts.mjs +5 -5
  47. package/dist/astro/routes/api/admin/comments/index.mjs +9 -9
  48. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +4 -4
  49. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
  50. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +4 -4
  51. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +4 -4
  52. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +35 -34
  53. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
  54. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +35 -34
  55. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
  56. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +34 -33
  57. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
  58. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +34 -33
  59. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
  60. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +34 -33
  61. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
  62. package/dist/astro/routes/api/admin/plugins/index.mjs +34 -33
  63. package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
  64. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
  65. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +34 -33
  66. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
  67. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +34 -33
  68. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
  69. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +34 -33
  70. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
  71. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +34 -33
  72. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
  73. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +35 -34
  74. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
  75. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +34 -33
  76. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
  77. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +35 -34
  78. package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
  79. package/dist/astro/routes/api/admin/plugins/updates.mjs +34 -33
  80. package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
  81. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +34 -33
  82. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
  83. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +3 -3
  84. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +34 -33
  85. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
  86. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +2 -2
  87. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
  88. package/dist/astro/routes/api/admin/users/_id_/index.mjs +6 -6
  89. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +3 -3
  90. package/dist/astro/routes/api/admin/users/index.mjs +6 -6
  91. package/dist/astro/routes/api/auth/dev-bypass.mjs +5 -5
  92. package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
  93. package/dist/astro/routes/api/auth/invite/complete.mjs +10 -10
  94. package/dist/astro/routes/api/auth/invite/index.mjs +7 -7
  95. package/dist/astro/routes/api/auth/invite/register-options.mjs +9 -9
  96. package/dist/astro/routes/api/auth/logout.mjs +3 -3
  97. package/dist/astro/routes/api/auth/magic-link/send.mjs +9 -9
  98. package/dist/astro/routes/api/auth/magic-link/verify.mjs +3 -3
  99. package/dist/astro/routes/api/auth/me.mjs +7 -7
  100. package/dist/astro/routes/api/auth/mode.mjs +1 -1
  101. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +3 -3
  102. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
  103. package/dist/astro/routes/api/auth/passkey/_id_.mjs +6 -6
  104. package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
  105. package/dist/astro/routes/api/auth/passkey/options.mjs +11 -11
  106. package/dist/astro/routes/api/auth/passkey/register/options.mjs +9 -9
  107. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +10 -10
  108. package/dist/astro/routes/api/auth/passkey/verify.mjs +10 -10
  109. package/dist/astro/routes/api/auth/signup/complete.mjs +10 -10
  110. package/dist/astro/routes/api/auth/signup/request.mjs +9 -9
  111. package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
  112. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +12 -12
  113. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +11 -11
  114. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
  115. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +3 -3
  116. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
  117. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
  118. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +10 -10
  119. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +7 -7
  120. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
  121. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
  122. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +7 -7
  123. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +12 -12
  124. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
  125. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +3 -3
  126. package/dist/astro/routes/api/content/_collection_/_id_.mjs +7 -7
  127. package/dist/astro/routes/api/content/_collection_/authors.mjs +3 -3
  128. package/dist/astro/routes/api/content/_collection_/index.mjs +7 -7
  129. package/dist/astro/routes/api/content/_collection_/trash.mjs +7 -7
  130. package/dist/astro/routes/api/dashboard.mjs +7 -7
  131. package/dist/astro/routes/api/dev/emails.mjs +3 -3
  132. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  133. package/dist/astro/routes/api/import/probe.mjs +11 -11
  134. package/dist/astro/routes/api/import/wordpress/analyze.mjs +3 -3
  135. package/dist/astro/routes/api/import/wordpress/execute.d.mts +9 -9
  136. package/dist/astro/routes/api/import/wordpress/execute.mjs +10 -9
  137. package/dist/astro/routes/api/import/wordpress/execute.mjs.map +1 -1
  138. package/dist/astro/routes/api/import/wordpress/media.mjs +9 -9
  139. package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -9
  140. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +9 -9
  141. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  142. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +11 -11
  143. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +1 -1
  144. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +13 -12
  145. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
  146. package/dist/astro/routes/api/manifest.mjs +4 -4
  147. package/dist/astro/routes/api/mcp.mjs +39 -39
  148. package/dist/astro/routes/api/media/_id_/confirm.mjs +7 -7
  149. package/dist/astro/routes/api/media/_id_.mjs +7 -7
  150. package/dist/astro/routes/api/media/file/_...key_.mjs +2 -2
  151. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +3 -3
  152. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +3 -3
  153. package/dist/astro/routes/api/media/providers/index.mjs +3 -3
  154. package/dist/astro/routes/api/media/upload-url.mjs +9 -9
  155. package/dist/astro/routes/api/media.mjs +12 -12
  156. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +8 -8
  157. package/dist/astro/routes/api/menus/_name_/items.mjs +8 -8
  158. package/dist/astro/routes/api/menus/_name_/reorder.mjs +8 -8
  159. package/dist/astro/routes/api/menus/_name_/translations.mjs +8 -8
  160. package/dist/astro/routes/api/menus/_name_.mjs +8 -8
  161. package/dist/astro/routes/api/menus/index.mjs +8 -8
  162. package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
  163. package/dist/astro/routes/api/oauth/device/authorize.mjs +6 -6
  164. package/dist/astro/routes/api/oauth/device/code.mjs +9 -9
  165. package/dist/astro/routes/api/oauth/device/token.mjs +8 -8
  166. package/dist/astro/routes/api/oauth/register.mjs +3 -3
  167. package/dist/astro/routes/api/oauth/token/refresh.mjs +6 -6
  168. package/dist/astro/routes/api/oauth/token/revoke.mjs +6 -6
  169. package/dist/astro/routes/api/oauth/token.mjs +6 -6
  170. package/dist/astro/routes/api/openapi.json.mjs +4 -4
  171. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +4 -4
  172. package/dist/astro/routes/api/redirects/404s/index.mjs +9 -9
  173. package/dist/astro/routes/api/redirects/404s/summary.mjs +9 -9
  174. package/dist/astro/routes/api/redirects/_id_.mjs +10 -10
  175. package/dist/astro/routes/api/redirects/index.mjs +10 -10
  176. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
  177. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
  178. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +34 -33
  179. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
  180. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +34 -33
  181. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs.map +1 -1
  182. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +34 -33
  183. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
  184. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +34 -33
  185. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
  186. package/dist/astro/routes/api/schema/collections/index.mjs +34 -33
  187. package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
  188. package/dist/astro/routes/api/schema/index.mjs +8 -7
  189. package/dist/astro/routes/api/schema/index.mjs.map +1 -1
  190. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +34 -33
  191. package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
  192. package/dist/astro/routes/api/schema/orphans/index.mjs +34 -33
  193. package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
  194. package/dist/astro/routes/api/search/enable.mjs +10 -10
  195. package/dist/astro/routes/api/search/index.mjs +9 -9
  196. package/dist/astro/routes/api/search/rebuild.mjs +10 -10
  197. package/dist/astro/routes/api/search/stats.mjs +6 -6
  198. package/dist/astro/routes/api/search/suggest.mjs +9 -9
  199. package/dist/astro/routes/api/sections/_slug_.mjs +9 -9
  200. package/dist/astro/routes/api/sections/index.mjs +9 -9
  201. package/dist/astro/routes/api/settings/email.mjs +4 -4
  202. package/dist/astro/routes/api/settings.mjs +11 -11
  203. package/dist/astro/routes/api/setup/admin-verify.mjs +11 -11
  204. package/dist/astro/routes/api/setup/admin.mjs +10 -10
  205. package/dist/astro/routes/api/setup/dev-bypass.mjs +23 -22
  206. package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
  207. package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
  208. package/dist/astro/routes/api/setup/index.mjs +24 -23
  209. package/dist/astro/routes/api/setup/index.mjs.map +1 -1
  210. package/dist/astro/routes/api/setup/status.mjs +4 -4
  211. package/dist/astro/routes/api/snapshot.mjs +6 -6
  212. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +12 -12
  213. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +12 -12
  214. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +12 -12
  215. package/dist/astro/routes/api/taxonomies/index.mjs +12 -12
  216. package/dist/astro/routes/api/themes/preview.mjs +5 -5
  217. package/dist/astro/routes/api/typegen.mjs +5 -5
  218. package/dist/astro/routes/api/well-known/auth.mjs +1 -1
  219. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
  220. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
  221. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +7 -7
  222. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +9 -9
  223. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +9 -9
  224. package/dist/astro/routes/api/widget-areas/_name_.mjs +5 -5
  225. package/dist/astro/routes/api/widget-areas/index.mjs +9 -9
  226. package/dist/astro/routes/api/widget-components.mjs +3 -3
  227. package/dist/astro/routes/robots.txt.mjs +5 -5
  228. package/dist/astro/routes/sitemap-_collection_.xml.mjs +8 -8
  229. package/dist/astro/routes/sitemap.xml.mjs +6 -6
  230. package/dist/astro/types.d.mts +12 -12
  231. package/dist/auth/providers/github.d.mts +1 -1
  232. package/dist/auth/providers/google.d.mts +1 -1
  233. package/dist/{authorize-C0EYoUeV.mjs → authorize-B__u-iZJ.mjs} +2 -2
  234. package/dist/{authorize-C0EYoUeV.mjs.map → authorize-B__u-iZJ.mjs.map} +1 -1
  235. package/dist/{byline-CC0WS4Gu.mjs → byline-C-YajfV7.mjs} +18 -76
  236. package/dist/byline-C-YajfV7.mjs.map +1 -0
  237. package/dist/{byline-fields-53i9Et0x.mjs → byline-fields-B-KXkg5R.mjs} +2 -2
  238. package/dist/{byline-fields-53i9Et0x.mjs.map → byline-fields-B-KXkg5R.mjs.map} +1 -1
  239. package/dist/{byline-fields-DNp0HNKc.mjs → byline-fields-DaMKzkhO.mjs} +1 -1
  240. package/dist/{byline-fields-DNp0HNKc.mjs.map → byline-fields-DaMKzkhO.mjs.map} +1 -1
  241. package/dist/{byline-fields-jy07PZJM.d.mts → byline-fields-RFPkDLRw.d.mts} +21 -1
  242. package/dist/byline-fields-RFPkDLRw.d.mts.map +1 -0
  243. package/dist/{bylines-CCO1CYnH.mjs → bylines-BMtJnPnG.mjs} +7 -7
  244. package/dist/{bylines-CCO1CYnH.mjs.map → bylines-BMtJnPnG.mjs.map} +1 -1
  245. package/dist/{bylines-DGekMGGm.mjs → bylines-D4KADDAi.mjs} +4 -4
  246. package/dist/{bylines-DGekMGGm.mjs.map → bylines-D4KADDAi.mjs.map} +1 -1
  247. package/dist/{cache-pXTpun6s.mjs → cache-BUJe2w6Z.mjs} +2 -2
  248. package/dist/{cache-pXTpun6s.mjs.map → cache-BUJe2w6Z.mjs.map} +1 -1
  249. package/dist/{challenge-store-C6E_kWTs.mjs → challenge-store-LhiqMccz.mjs} +1 -1
  250. package/dist/{challenge-store-C6E_kWTs.mjs.map → challenge-store-LhiqMccz.mjs.map} +1 -1
  251. package/dist/{chunks-SyjZaYwV.mjs → chunks-BHgcHh2C.mjs} +2 -2
  252. package/dist/{chunks-SyjZaYwV.mjs.map → chunks-BHgcHh2C.mjs.map} +1 -1
  253. package/dist/cli/index.mjs +25 -24
  254. package/dist/cli/index.mjs.map +1 -1
  255. package/dist/client/cf-access.d.mts +1 -1
  256. package/dist/client/index.d.mts +1 -1
  257. package/dist/client/index.mjs +2 -2
  258. package/dist/{comment-80UyJJUR.mjs → comment-BDx50EX5.mjs} +2 -2
  259. package/dist/{comment-80UyJJUR.mjs.map → comment-BDx50EX5.mjs.map} +1 -1
  260. package/dist/{comment-reaction-B6AdcpXw.mjs → comment-reaction-Gv_pgfI3.mjs} +2 -2
  261. package/dist/{comment-reaction-B6AdcpXw.mjs.map → comment-reaction-Gv_pgfI3.mjs.map} +1 -1
  262. package/dist/{comments-CtPkT2tp.mjs → comments-CXHuQ6jI.mjs} +3 -3
  263. package/dist/{comments-CtPkT2tp.mjs.map → comments-CXHuQ6jI.mjs.map} +1 -1
  264. package/dist/{components-D6ZnrzbB.mjs → components-CYt4uVK9.mjs} +1 -1
  265. package/dist/{components-D6ZnrzbB.mjs.map → components-CYt4uVK9.mjs.map} +1 -1
  266. package/dist/{content-2PTDNnoh.mjs → content-PoVGKjp0.mjs} +3 -3
  267. package/dist/{content-2PTDNnoh.mjs.map → content-PoVGKjp0.mjs.map} +1 -1
  268. package/dist/{context-f1__jpzF.mjs → context-Dvws41_A.mjs} +62 -24
  269. package/dist/context-Dvws41_A.mjs.map +1 -0
  270. package/dist/{cron-CQPxBjYs.mjs → cron-C5LVoNmP.mjs} +14 -2
  271. package/dist/cron-C5LVoNmP.mjs.map +1 -0
  272. package/dist/{dashboard-BOESNcwE.mjs → dashboard-DsYU8JKS.mjs} +4 -4
  273. package/dist/{dashboard-BOESNcwE.mjs.map → dashboard-DsYU8JKS.mjs.map} +1 -1
  274. package/dist/db/index.d.mts +3 -3
  275. package/dist/db/index.mjs +1 -1
  276. package/dist/db/libsql.d.mts +1 -1
  277. package/dist/db/postgres.d.mts +1 -1
  278. package/dist/db/sqlite.d.mts +1 -1
  279. package/dist/{db-errors-CrC5w3Jz.mjs → db-errors-UXB1CK42.mjs} +1 -1
  280. package/dist/{db-errors-CrC5w3Jz.mjs.map → db-errors-UXB1CK42.mjs.map} +1 -1
  281. package/dist/{default-CpBEPn6a.mjs → default-CeRG-Ot4.mjs} +1 -1
  282. package/dist/{default-CpBEPn6a.mjs.map → default-CeRG-Ot4.mjs.map} +1 -1
  283. package/dist/{device-flow-Bn-qiDrw.mjs → device-flow-DENDCQ9F.mjs} +4 -4
  284. package/dist/{device-flow-Bn-qiDrw.mjs.map → device-flow-DENDCQ9F.mjs.map} +1 -1
  285. package/dist/{email-console-B9VS6eN4.mjs → email-console-DJP32ucW.mjs} +1 -1
  286. package/dist/{email-console-B9VS6eN4.mjs.map → email-console-DJP32ucW.mjs.map} +1 -1
  287. package/dist/{error-BQ1pxs0L.mjs → error-lq_fL-RS.mjs} +2 -2
  288. package/dist/{error-BQ1pxs0L.mjs.map → error-lq_fL-RS.mjs.map} +1 -1
  289. package/dist/{escape-BtbJWyaW.mjs → escape-Bjio4ZsM.mjs} +1 -1
  290. package/dist/{escape-BtbJWyaW.mjs.map → escape-Bjio4ZsM.mjs.map} +1 -1
  291. package/dist/field-defs-cache-CYYx8xlG.mjs +77 -0
  292. package/dist/field-defs-cache-CYYx8xlG.mjs.map +1 -0
  293. package/dist/{fts-manager-DtiWqwNJ.mjs → fts-manager-DW_NR-Fs.mjs} +2 -2
  294. package/dist/{fts-manager-DtiWqwNJ.mjs.map → fts-manager-DW_NR-Fs.mjs.map} +1 -1
  295. package/dist/{hash-BowcuPwv.mjs → hash-DrvzIXcz.mjs} +1 -1
  296. package/dist/{hash-BowcuPwv.mjs.map → hash-DrvzIXcz.mjs.map} +1 -1
  297. package/dist/{import-DqSGOlmR.mjs → import-Bb1T9WJS.mjs} +3 -3
  298. package/dist/{import-DqSGOlmR.mjs.map → import-Bb1T9WJS.mjs.map} +1 -1
  299. package/dist/{index-C2dLNT6S.d.mts → index-BroXT-vD.d.mts} +136 -38
  300. package/dist/index-BroXT-vD.d.mts.map +1 -0
  301. package/dist/{index-ZDEwR7qW.d.mts → index-qbL02uhs.d.mts} +3 -3
  302. package/dist/{index-ZDEwR7qW.d.mts.map → index-qbL02uhs.d.mts.map} +1 -1
  303. package/dist/index.d.mts +17 -17
  304. package/dist/index.mjs +55 -54
  305. package/dist/{load-Dtoo7KcJ.mjs → load-DMFi4n1p.mjs} +2 -2
  306. package/dist/{load-Dtoo7KcJ.mjs.map → load-DMFi4n1p.mjs.map} +1 -1
  307. package/dist/{loader-C8Z48Uof.mjs → loader-BR2SgfEc.mjs} +77 -12
  308. package/dist/loader-BR2SgfEc.mjs.map +1 -0
  309. package/dist/{manifest-schema-DDSbwkAL.mjs → manifest-schema-BWKnQYQF.mjs} +1 -1
  310. package/dist/{manifest-schema-DDSbwkAL.mjs.map → manifest-schema-BWKnQYQF.mjs.map} +1 -1
  311. package/dist/media/image-endpoint.mjs +1 -1
  312. package/dist/media/index.d.mts +1 -1
  313. package/dist/media/index.mjs +2 -2
  314. package/dist/media/local-runtime.d.mts +19 -11
  315. package/dist/media/local-runtime.d.mts.map +1 -1
  316. package/dist/media/local-runtime.mjs +11 -9
  317. package/dist/media/local-runtime.mjs.map +1 -1
  318. package/dist/{media-Bw0sA6rb.mjs → media-CTjJoJTh.mjs} +2 -2
  319. package/dist/{media-Bw0sA6rb.mjs.map → media-CTjJoJTh.mjs.map} +1 -1
  320. package/dist/{media-allowlist-DunzrKFM.mjs → media-allowlist-B77I5_bw.mjs} +2 -2
  321. package/dist/{media-allowlist-DunzrKFM.mjs.map → media-allowlist-B77I5_bw.mjs.map} +1 -1
  322. package/dist/{media-url-S22B6aPr.mjs → media-url-O4rm9-SQ.mjs} +1 -1
  323. package/dist/{media-url-S22B6aPr.mjs.map → media-url-O4rm9-SQ.mjs.map} +1 -1
  324. package/dist/{menus-CYs0WZoL.mjs → menus-8O6xNcYW.mjs} +2 -2
  325. package/dist/{menus-CYs0WZoL.mjs.map → menus-8O6xNcYW.mjs.map} +1 -1
  326. package/dist/{menus-DOs1MQJg.mjs → menus-GvRSpgIA.mjs} +57 -14
  327. package/dist/menus-GvRSpgIA.mjs.map +1 -0
  328. package/dist/{mime-DYpsOAny.mjs → mime-BTzvzJ8M.mjs} +1 -1
  329. package/dist/{mime-DYpsOAny.mjs.map → mime-BTzvzJ8M.mjs.map} +1 -1
  330. package/dist/{mode-CFh8Dw8G.mjs → mode-BB0F8xTC.mjs} +1 -1
  331. package/dist/{mode-CFh8Dw8G.mjs.map → mode-BB0F8xTC.mjs.map} +1 -1
  332. package/dist/{normalize-Uam4_Vkr.mjs → normalize-ClbNIenm.mjs} +1 -1
  333. package/dist/{normalize-Uam4_Vkr.mjs.map → normalize-ClbNIenm.mjs.map} +1 -1
  334. package/dist/{oauth-authorization-BN-t83Uy.mjs → oauth-authorization-CsvzIp_F.mjs} +4 -4
  335. package/dist/{oauth-authorization-BN-t83Uy.mjs.map → oauth-authorization-CsvzIp_F.mjs.map} +1 -1
  336. package/dist/{oauth-clients-R6I_V0qz.mjs → oauth-clients-C9SYwEbZ.mjs} +1 -1
  337. package/dist/{oauth-clients-R6I_V0qz.mjs.map → oauth-clients-C9SYwEbZ.mjs.map} +1 -1
  338. package/dist/{oauth-state-store-Csoc6kkR.mjs → oauth-state-store---zrApfB.mjs} +1 -1
  339. package/dist/{oauth-state-store-Csoc6kkR.mjs.map → oauth-state-store---zrApfB.mjs.map} +1 -1
  340. package/dist/{oauth-user-lookup-d7VYjTpx.mjs → oauth-user-lookup-SHsWRlG9.mjs} +1 -1
  341. package/dist/{oauth-user-lookup-d7VYjTpx.mjs.map → oauth-user-lookup-SHsWRlG9.mjs.map} +1 -1
  342. package/dist/object-cache/memory.d.mts +1 -1
  343. package/dist/{options-DFFpvNJU.d.mts → options-CqECG0gM.d.mts} +3 -3
  344. package/dist/{options-DFFpvNJU.d.mts.map → options-CqECG0gM.d.mts.map} +1 -1
  345. package/dist/page/index.d.mts +2 -2
  346. package/dist/{parse-qrHlnzTy.mjs → parse-DHI873qU.mjs} +2 -2
  347. package/dist/{parse-qrHlnzTy.mjs.map → parse-DHI873qU.mjs.map} +1 -1
  348. package/dist/{passkey-config-BJTbcnI5.mjs → passkey-config-C0YfSBko.mjs} +1 -1
  349. package/dist/{passkey-config-BJTbcnI5.mjs.map → passkey-config-C0YfSBko.mjs.map} +1 -1
  350. package/dist/{placeholder-CIDCpyEY.mjs → placeholder-B0s2mWu5.mjs} +1 -1
  351. package/dist/{placeholder-CIDCpyEY.mjs.map → placeholder-B0s2mWu5.mjs.map} +1 -1
  352. package/dist/{placeholder-CirPauNG.d.mts → placeholder-QoI4gEzP.d.mts} +1 -1
  353. package/dist/{placeholder-CirPauNG.d.mts.map → placeholder-QoI4gEzP.d.mts.map} +1 -1
  354. package/dist/plugin-types.d.mts +1 -1
  355. package/dist/plugin-utils.d.mts +9 -9
  356. package/dist/plugins/adapt-sandbox-entry.d.mts +9 -9
  357. package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
  358. package/dist/{portable-text-BIFhrU6T.mjs → portable-text-BICg8bTk.mjs} +1 -1
  359. package/dist/{portable-text-BIFhrU6T.mjs.map → portable-text-BICg8bTk.mjs.map} +1 -1
  360. package/dist/{preview-IDye9SPQ.mjs → preview-DKGCt2_p.mjs} +2 -2
  361. package/dist/{preview-IDye9SPQ.mjs.map → preview-DKGCt2_p.mjs.map} +1 -1
  362. package/dist/{public-url-8lFyQ8ZF.mjs → public-url-CTVqgMmg.mjs} +1 -1
  363. package/dist/{public-url-8lFyQ8ZF.mjs.map → public-url-CTVqgMmg.mjs.map} +1 -1
  364. package/dist/{query-B1RVFUNM.mjs → query-DZdlS6Cu.mjs} +92 -14
  365. package/dist/query-DZdlS6Cu.mjs.map +1 -0
  366. package/dist/{rate-limit-DfNAqIWW.mjs → rate-limit-D1Y6VFVP.mjs} +2 -2
  367. package/dist/{rate-limit-DfNAqIWW.mjs.map → rate-limit-D1Y6VFVP.mjs.map} +1 -1
  368. package/dist/{redirect-Bifv_yHv.mjs → redirect-C1KVGYWe.mjs} +2 -2
  369. package/dist/{redirect-Bifv_yHv.mjs.map → redirect-C1KVGYWe.mjs.map} +1 -1
  370. package/dist/{redirect-B4wuX2A5.mjs → redirect-CS-PHtNh.mjs} +1 -1
  371. package/dist/{redirect-B4wuX2A5.mjs.map → redirect-CS-PHtNh.mjs.map} +1 -1
  372. package/dist/{redirects-CT_vb3NV.mjs → redirects-DgnypVir.mjs} +4 -4
  373. package/dist/{redirects-CT_vb3NV.mjs.map → redirects-DgnypVir.mjs.map} +1 -1
  374. package/dist/{redirects-DUvXgzCX.mjs → redirects-x9eFPeyi.mjs} +4 -2
  375. package/dist/redirects-x9eFPeyi.mjs.map +1 -0
  376. package/dist/{registry-CjK96fHD.mjs → registry-CJkCubOJ.mjs} +4 -4
  377. package/dist/{registry-CjK96fHD.mjs.map → registry-CJkCubOJ.mjs.map} +1 -1
  378. package/dist/{request-meta-ei3ATilC.mjs → request-meta-CmS1tDFf.mjs} +2 -2
  379. package/dist/{request-meta-ei3ATilC.mjs.map → request-meta-CmS1tDFf.mjs.map} +1 -1
  380. package/dist/{resolve-CpbBglV0.mjs → resolve-C7I0qiR0.mjs} +1 -1
  381. package/dist/{resolve-CpbBglV0.mjs.map → resolve-C7I0qiR0.mjs.map} +1 -1
  382. package/dist/{runner-DYYnW530.mjs → runner-BsquExlK.mjs} +246 -174
  383. package/dist/runner-BsquExlK.mjs.map +1 -0
  384. package/dist/{runner-jdXtFEd3.d.mts → runner-CEtq-6Sm.d.mts} +2 -2
  385. package/dist/{runner-jdXtFEd3.d.mts.map → runner-CEtq-6Sm.d.mts.map} +1 -1
  386. package/dist/runtime.d.mts +10 -10
  387. package/dist/runtime.mjs +2 -2
  388. package/dist/{schema-5z2Jfnbm.mjs → schema-DMjXuvNU.mjs} +4 -4
  389. package/dist/{schema-5z2Jfnbm.mjs.map → schema-DMjXuvNU.mjs.map} +1 -1
  390. package/dist/{search-D9FkoM-k.mjs → search-D1wKk3lc.mjs} +4 -4
  391. package/dist/{search-D9FkoM-k.mjs.map → search-D1wKk3lc.mjs.map} +1 -1
  392. package/dist/{secrets-BUE5UQys.mjs → secrets-BSf9pRRY.mjs} +2 -2
  393. package/dist/{secrets-BUE5UQys.mjs.map → secrets-BSf9pRRY.mjs.map} +1 -1
  394. package/dist/{sections-DWYZo6HK.mjs → sections-CCMic8dB.mjs} +3 -3
  395. package/dist/{sections-DWYZo6HK.mjs.map → sections-CCMic8dB.mjs.map} +1 -1
  396. package/dist/seed/index.d.mts +2 -2
  397. package/dist/seed/index.mjs +17 -16
  398. package/dist/seo/index.d.mts +1 -1
  399. package/dist/seo/index.mjs +1 -1
  400. package/dist/{seo-Cu47j3mJ.mjs → seo-3QrZ1EPT.mjs} +2 -2
  401. package/dist/{seo-Cu47j3mJ.mjs.map → seo-3QrZ1EPT.mjs.map} +1 -1
  402. package/dist/{seo-DRnqnstF.mjs → seo-DCEF-AVr.mjs} +1 -1
  403. package/dist/{seo-DRnqnstF.mjs.map → seo-DCEF-AVr.mjs.map} +1 -1
  404. package/dist/{service-DTmtqCnw.mjs → service-CQHK_10l.mjs} +3 -3
  405. package/dist/{service-DTmtqCnw.mjs.map → service-CQHK_10l.mjs.map} +1 -1
  406. package/dist/{session-user-DmtPMNa1.mjs → session-user-B8aLtKAH.mjs} +1 -1
  407. package/dist/{session-user-DmtPMNa1.mjs.map → session-user-B8aLtKAH.mjs.map} +1 -1
  408. package/dist/{settings-DUrVDN0c.mjs → settings-C3Yi5Co3.mjs} +3 -3
  409. package/dist/{settings-DUrVDN0c.mjs.map → settings-C3Yi5Co3.mjs.map} +1 -1
  410. package/dist/{settings-yaxu9mU-.mjs → settings-D9rvaLuY.mjs} +4 -4
  411. package/dist/{settings-yaxu9mU-.mjs.map → settings-D9rvaLuY.mjs.map} +1 -1
  412. package/dist/{setup-complete-BWFHzgvZ.mjs → setup-complete-gEiySUc-.mjs} +1 -1
  413. package/dist/{setup-complete-BWFHzgvZ.mjs.map → setup-complete-gEiySUc-.mjs.map} +1 -1
  414. package/dist/{setup-nonce-DFWG-J5c.mjs → setup-nonce-DzS50zme.mjs} +1 -1
  415. package/dist/{setup-nonce-DFWG-J5c.mjs.map → setup-nonce-DzS50zme.mjs.map} +1 -1
  416. package/dist/{single-flight-cache-B5p5cNOL.mjs → single-flight-cache-Cdfkic3t.mjs} +1 -1
  417. package/dist/{single-flight-cache-B5p5cNOL.mjs.map → single-flight-cache-Cdfkic3t.mjs.map} +1 -1
  418. package/dist/{site-url-BOTPgmeM.mjs → site-url-BLebyON8.mjs} +1 -1
  419. package/dist/{site-url-BOTPgmeM.mjs.map → site-url-BLebyON8.mjs.map} +1 -1
  420. package/dist/{ssrf-B0VeRLrp.mjs → ssrf-BRKb343l.mjs} +1 -1
  421. package/dist/{ssrf-B0VeRLrp.mjs.map → ssrf-BRKb343l.mjs.map} +1 -1
  422. package/dist/{status-CB2basWJ.mjs → status-vUK0SA17.mjs} +1 -1
  423. package/dist/{status-CB2basWJ.mjs.map → status-vUK0SA17.mjs.map} +1 -1
  424. package/dist/storage/local.d.mts +1 -1
  425. package/dist/storage/local.mjs +1 -1
  426. package/dist/storage/s3.d.mts +1 -1
  427. package/dist/storage/s3.mjs +1 -1
  428. package/dist/{taxonomies-Be9B1jEj.mjs → taxonomies-CurpLtNC.mjs} +52 -24
  429. package/dist/taxonomies-CurpLtNC.mjs.map +1 -0
  430. package/dist/{taxonomies-CJmkEpdS.mjs → taxonomies-DhLaNJji.mjs} +34 -27
  431. package/dist/taxonomies-DhLaNJji.mjs.map +1 -0
  432. package/dist/{taxonomy-B2HQuVf5.mjs → taxonomy-BBUlGug5.mjs} +33 -7
  433. package/dist/taxonomy-BBUlGug5.mjs.map +1 -0
  434. package/dist/{tokens-BvrJvIB6.mjs → tokens-LeuXF9gG.mjs} +1 -1
  435. package/dist/{tokens-BvrJvIB6.mjs.map → tokens-LeuXF9gG.mjs.map} +1 -1
  436. package/dist/{transport-k7YjfmEn.d.mts → transport-BZ-03aFT.d.mts} +1 -1
  437. package/dist/{transport-k7YjfmEn.d.mts.map → transport-BZ-03aFT.d.mts.map} +1 -1
  438. package/dist/{transport-B4rXnSTf.mjs → transport-_2nBz7e9.mjs} +1 -1
  439. package/dist/{transport-B4rXnSTf.mjs.map → transport-_2nBz7e9.mjs.map} +1 -1
  440. package/dist/{trusted-proxy-DY1_UqHr.mjs → trusted-proxy-DZY5WCn2.mjs} +1 -1
  441. package/dist/{trusted-proxy-DY1_UqHr.mjs.map → trusted-proxy-DZY5WCn2.mjs.map} +1 -1
  442. package/dist/{types-Czg1S3gB.d.mts → types-B-gIxFCE.d.mts} +1 -1
  443. package/dist/{types-Czg1S3gB.d.mts.map → types-B-gIxFCE.d.mts.map} +1 -1
  444. package/dist/{types-DXLVzV2w.d.mts → types-BG8_XTLj.d.mts} +1 -1
  445. package/dist/{types-DXLVzV2w.d.mts.map → types-BG8_XTLj.d.mts.map} +1 -1
  446. package/dist/{types-CWGYDYqF.d.mts → types-BWh2MjmA.d.mts} +15 -2
  447. package/dist/types-BWh2MjmA.d.mts.map +1 -0
  448. package/dist/{types-CgvhU-hF.d.mts → types-BWqGVhFn.d.mts} +1 -1
  449. package/dist/{types-CgvhU-hF.d.mts.map → types-BWqGVhFn.d.mts.map} +1 -1
  450. package/dist/{types-DYF2_Vf4.mjs → types-CQAugunJ.mjs} +1 -1
  451. package/dist/{types-DYF2_Vf4.mjs.map → types-CQAugunJ.mjs.map} +1 -1
  452. package/dist/{types-DPCpkzvS.d.mts → types-CTLJYMhN.d.mts} +1 -1
  453. package/dist/{types-DPCpkzvS.d.mts.map → types-CTLJYMhN.d.mts.map} +1 -1
  454. package/dist/{types-CmivgZbp.d.mts → types-CUsXGRVj.d.mts} +1 -1
  455. package/dist/{types-CmivgZbp.d.mts.map → types-CUsXGRVj.d.mts.map} +1 -1
  456. package/dist/{types-DXOIbece.mjs → types-CmI1OyQg.mjs} +2 -2
  457. package/dist/types-CmI1OyQg.mjs.map +1 -0
  458. package/dist/{types-CKNXt63n.d.mts → types-DrYMm9s5.d.mts} +1 -1
  459. package/dist/{types-CKNXt63n.d.mts.map → types-DrYMm9s5.d.mts.map} +1 -1
  460. package/dist/{types-CIKX2HO5.d.mts → types-TTx8uP7V.d.mts} +2 -2
  461. package/dist/{types-CIKX2HO5.d.mts.map → types-TTx8uP7V.d.mts.map} +1 -1
  462. package/dist/{types-DCNNfeCv.d.mts → types-fS7-VTdR.d.mts} +1 -1
  463. package/dist/{types-DCNNfeCv.d.mts.map → types-fS7-VTdR.d.mts.map} +1 -1
  464. package/dist/{user-Db-Rti_z.mjs → user-JAuc8VUf.mjs} +3 -3
  465. package/dist/{user-Db-Rti_z.mjs.map → user-JAuc8VUf.mjs.map} +1 -1
  466. package/dist/{utils-LbHcSRTj.mjs → utils-B7A57fm9.mjs} +1 -1
  467. package/dist/{utils-LbHcSRTj.mjs.map → utils-B7A57fm9.mjs.map} +1 -1
  468. package/dist/{validate-Bwl64sxv.mjs → validate-BzXHWnkV.mjs} +2 -2
  469. package/dist/{validate-Bwl64sxv.mjs.map → validate-BzXHWnkV.mjs.map} +1 -1
  470. package/dist/{validate-CE2M0nvx.d.mts → validate-DOFrIb-c.d.mts} +5 -5
  471. package/dist/{validate-CE2M0nvx.d.mts.map → validate-DOFrIb-c.d.mts.map} +1 -1
  472. package/dist/{validation-C_yOvFaO.mjs → validation-CY1gk4Ar.mjs} +6 -6
  473. package/dist/{validation-C_yOvFaO.mjs.map → validation-CY1gk4Ar.mjs.map} +1 -1
  474. package/dist/version-B5nHxI5Y.mjs +7 -0
  475. package/dist/{version-RnYXYjzD.mjs.map → version-B5nHxI5Y.mjs.map} +1 -1
  476. package/dist/{widgets-8GONuRHo.mjs → widgets-Dri0spI4.mjs} +3 -3
  477. package/dist/{widgets-8GONuRHo.mjs.map → widgets-Dri0spI4.mjs.map} +1 -1
  478. package/dist/{zod-generator-D481JjzD.mjs → zod-generator-xEHODMJf.mjs} +51 -15
  479. package/dist/zod-generator-xEHODMJf.mjs.map +1 -0
  480. package/package.json +8 -7
  481. package/src/api/handlers/taxonomies.ts +62 -32
  482. package/src/api/schemas/bylines.ts +8 -0
  483. package/src/astro/integration/shims/use-sync-external-store-with-selector.js +71 -0
  484. package/src/astro/integration/shims/use-sync-external-store.js +1 -0
  485. package/src/astro/integration/virtual-modules.ts +8 -0
  486. package/src/astro/integration/vite-config.ts +56 -9
  487. package/src/astro/middleware/request-context.ts +6 -1
  488. package/src/astro/middleware/scoped-db.ts +131 -0
  489. package/src/astro/middleware/stream-end-metrics.ts +4 -8
  490. package/src/astro/middleware.ts +103 -31
  491. package/src/astro/routes/admin.astro +5 -1
  492. package/src/astro/routes/api/schema/index.ts +9 -3
  493. package/src/cli/commands/export-seed.ts +15 -11
  494. package/src/components/Block.astro +71 -0
  495. package/src/components/EmDashMedia.astro +1 -2
  496. package/src/components/Image.astro +36 -2
  497. package/src/components/InlinePortableTextEditor.tsx +10 -2
  498. package/src/components/index.ts +5 -0
  499. package/src/components/portable-text-text-align.ts +38 -0
  500. package/src/content/converters/portable-text-to-prosemirror.ts +5 -1
  501. package/src/content/converters/prosemirror-to-portable-text.ts +8 -0
  502. package/src/content/converters/types.ts +1 -0
  503. package/src/database/migrations/045_taxonomy_parent_group.ts +68 -0
  504. package/src/database/migrations/runner.ts +2 -0
  505. package/src/database/repositories/byline.ts +14 -0
  506. package/src/database/repositories/taxonomy.ts +50 -7
  507. package/src/database/repositories/types.ts +13 -0
  508. package/src/emdash-runtime.ts +226 -71
  509. package/src/loader.ts +161 -13
  510. package/src/media/local-runtime.ts +18 -5
  511. package/src/plugins/context.ts +94 -22
  512. package/src/plugins/cron.ts +18 -2
  513. package/src/plugins/routes.ts +40 -1
  514. package/src/query.ts +201 -19
  515. package/src/schema/zod-generator.ts +53 -8
  516. package/src/taxonomies/index.ts +46 -8
  517. package/src/ui.ts +5 -0
  518. package/src/virtual-modules.d.ts +13 -0
  519. package/dist/byline-CC0WS4Gu.mjs.map +0 -1
  520. package/dist/byline-fields-jy07PZJM.d.mts.map +0 -1
  521. package/dist/context-f1__jpzF.mjs.map +0 -1
  522. package/dist/cron-CQPxBjYs.mjs.map +0 -1
  523. package/dist/index-C2dLNT6S.d.mts.map +0 -1
  524. package/dist/loader-C8Z48Uof.mjs.map +0 -1
  525. package/dist/menus-DOs1MQJg.mjs.map +0 -1
  526. package/dist/query-B1RVFUNM.mjs.map +0 -1
  527. package/dist/redirects-DUvXgzCX.mjs.map +0 -1
  528. package/dist/runner-DYYnW530.mjs.map +0 -1
  529. package/dist/taxonomies-Be9B1jEj.mjs.map +0 -1
  530. package/dist/taxonomies-CJmkEpdS.mjs.map +0 -1
  531. package/dist/taxonomy-B2HQuVf5.mjs.map +0 -1
  532. package/dist/types-CWGYDYqF.d.mts.map +0 -1
  533. package/dist/types-DXOIbece.mjs.map +0 -1
  534. package/dist/version-RnYXYjzD.mjs +0 -7
  535. package/dist/zod-generator-D481JjzD.mjs.map +0 -1
  536. /package/dist/{api-tokens-BVH-H8Z9.mjs → api-tokens-B4BQybOp.mjs} +0 -0
  537. /package/dist/{ssrf-Cz1e2QPn.mjs → ssrf-CcX9zvMK.mjs} +0 -0
  538. /package/dist/{types-Bfr2Pj6z.mjs → types-BvbeGEtr.mjs} +0 -0
@@ -244,6 +244,15 @@ export interface MediaProviderEntry {
244
244
  */
245
245
  export interface MediaProviderContext {
246
246
  db: Kysely<Database>;
247
+ /**
248
+ * Resolver for the live connection, preferred over `db` by providers that
249
+ * query EmDash's database. Resolves the current request/event-scoped
250
+ * connection from ALS so connection-backed adapters (Postgres over
251
+ * Hyperdrive) don't reuse the per-isolate singleton's socket across events.
252
+ * Providers should resolve per operation rather than capturing `db` once.
253
+ * Omitted-safe: falls back to `db` for stateless adapters (D1, Node SQLite).
254
+ */
255
+ getDb?: () => Kysely<Database>;
247
256
  storage: Storage | null;
248
257
  }
249
258
 
@@ -262,6 +271,16 @@ export interface RuntimeDependencies {
262
271
  plugins: ResolvedPlugin[];
263
272
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
264
273
  createDialect: (config: any) => Dialect;
274
+ /**
275
+ * Factory for a dialect that batches same-turn reads into one round trip
276
+ * ({@link EmDashRuntime.create} uses it for the cold-start read phase).
277
+ * Present only on batching backends (D1, DO); absent backends fall back to
278
+ * the singleton. Returns a fresh connection each call — it must never be the
279
+ * long-lived singleton, whose coalescing buffer would be shared across
280
+ * requests.
281
+ */
282
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
283
+ createCoalescingDialect?: (config: any) => Dialect | null;
265
284
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
266
285
  createStorage: ((config: any) => Storage) | null;
267
286
  sandboxEnabled: boolean;
@@ -323,6 +342,7 @@ export interface EmDashRuntimeParts {
323
342
  allPipelinePlugins: ResolvedPlugin[];
324
343
  pipelineFactoryOptions: {
325
344
  db: Kysely<Database>;
345
+ getDb?: () => Kysely<Database>;
326
346
  storage?: Storage;
327
347
  siteInfo?: { siteName?: string; siteUrl?: string; locale?: string };
328
348
  };
@@ -370,6 +390,28 @@ function getDbHolder(): DbHolder {
370
390
  }
371
391
  return holder;
372
392
  }
393
+
394
+ /**
395
+ * Auto-seed runs at most once per isolate per database. Its lock + "done" set
396
+ * live on globalThis (same bundler-duplication reasoning as the db cache) so a
397
+ * reclaimed-and-rerun `create()` can't seed a second time concurrently. The
398
+ * lock polls rather than sharing a promise, so it is safe to await across a
399
+ * cancelled owner in workerd.
400
+ */
401
+ const SEED_HOLDER_KEY = Symbol.for("emdash:seed-state");
402
+ interface SeedHolder {
403
+ done: Set<string>;
404
+ lock: InitLock;
405
+ }
406
+ function getSeedHolder(): SeedHolder {
407
+ // eslint-disable-next-line typescript/no-unsafe-type-assertion -- globalThis symbol slot, written only below
408
+ let holder = globalSymbolStore[SEED_HOLDER_KEY] as SeedHolder | undefined;
409
+ if (!holder) {
410
+ holder = { done: new Set<string>(), lock: createInitLock() };
411
+ globalSymbolStore[SEED_HOLDER_KEY] = holder;
412
+ }
413
+ return holder;
414
+ }
373
415
  const storageCache = new Map<string, Storage>();
374
416
  const sandboxedPluginCache = new Map<string, SandboxedPluginInstance>();
375
417
  /**
@@ -414,7 +456,20 @@ export class EmDashRuntime {
414
456
  readonly configuredPlugins: ResolvedPlugin[];
415
457
  readonly sandboxedPlugins: Map<string, SandboxedPluginInstance>;
416
458
  readonly sandboxedPluginEntries: SandboxedPluginEntry[];
417
- readonly schemaRegistry: SchemaRegistry;
459
+ /**
460
+ * Schema registry bound to the current request/event-scoped connection.
461
+ * Built per access (SchemaRegistry just wraps a db) against `this.db`, the
462
+ * ALS-aware getter — never a captured snapshot of the singleton. On a
463
+ * connection-backed adapter (Postgres over Hyperdrive) a captured singleton
464
+ * would query a socket opened by an earlier event and trip workerd's
465
+ * cross-request I/O guard; the catch in handlers like handleContentUpdate
466
+ * would then silently treat a revision-enabled collection as non-revisioned
467
+ * and write draft edits to live columns. Same reasoning as the per-call
468
+ * registry in _buildManifest().
469
+ */
470
+ get schemaRegistry(): SchemaRegistry {
471
+ return new SchemaRegistry(this.db);
472
+ }
418
473
  private _hooks!: HookPipeline;
419
474
  readonly config: EmDashConfig;
420
475
  readonly mediaProviders: Map<string, MediaProvider>;
@@ -445,6 +500,7 @@ export class EmDashRuntime {
445
500
  /** Factory options for the hook pipeline context factory */
446
501
  private pipelineFactoryOptions: {
447
502
  db: Kysely<Database>;
503
+ getDb?: () => Kysely<Database>;
448
504
  storage?: Storage;
449
505
  siteInfo?: { siteName?: string; siteUrl?: string; locale?: string };
450
506
  };
@@ -475,7 +531,6 @@ export class EmDashRuntime {
475
531
  this.configuredPlugins = parts.configuredPlugins;
476
532
  this.sandboxedPlugins = parts.sandboxedPlugins;
477
533
  this.sandboxedPluginEntries = parts.sandboxedPluginEntries;
478
- this.schemaRegistry = new SchemaRegistry(parts.db);
479
534
  this._hooks = parts.hooks;
480
535
  this.enabledPlugins = parts.enabledPlugins;
481
536
  this.pluginStates = parts.pluginStates;
@@ -621,7 +676,9 @@ export class EmDashRuntime {
621
676
  // The old pipeline's contextFactoryOptions were built up incrementally
622
677
  // via setContextFactory calls during create(). We replay them here.
623
678
  if (this.email) {
624
- newPipeline.setContextFactory({ db: this.db, emailPipeline: this.email });
679
+ // db/getDb are already wired by createHookPipeline above (they live in
680
+ // pipelineFactoryOptions), so the merge only adds emailPipeline.
681
+ newPipeline.setContextFactory({ emailPipeline: this.email });
625
682
  }
626
683
  if (this.cronScheduler) {
627
684
  const scheduler = this.cronScheduler;
@@ -985,6 +1042,22 @@ export class EmDashRuntime {
985
1042
  // Initialize database (connects, runs migrations if needed)
986
1043
  const db = await phase("rt.db", "DB init + migrations", () => EmDashRuntime.getDatabase(deps));
987
1044
 
1045
+ // Resolver for the live connection, mirroring the `get db()` getter
1046
+ // below (which can't be used here — the runtime instance doesn't exist
1047
+ // yet). Long-lived subsystems built during create() (cron executor,
1048
+ // plugin context factory, media providers) capture this resolver rather
1049
+ // than the `db` snapshot, so a connection-backed adapter (Postgres over
1050
+ // Hyperdrive) serves their queries from the current request/event-scoped
1051
+ // connection in ALS instead of the per-isolate singleton — whose socket
1052
+ // belongs to an earlier request and would trip workerd's cross-request
1053
+ // I/O guard. Stateless adapters (D1, Node SQLite) set no ALS db on most
1054
+ // paths, so this falls back to the singleton: unchanged behavior.
1055
+ const resolveDb = (): Kysely<Database> => {
1056
+ const ctx = getRequestContext();
1057
+ // eslint-disable-next-line typescript/no-unsafe-type-assertion -- ALS db is typed unknown to avoid a circular import; middleware always sets a Kysely<Database>
1058
+ return (ctx?.db as Kysely<Database> | undefined) ?? db;
1059
+ };
1060
+
988
1061
  // Validate EMDASH_ENCRYPTION_KEY once here so a malformed value
989
1062
  // surfaces in startup logs instead of as request-time 500s. The key
990
1063
  // itself is not yet consumed (a follow-up PR adds plugin-secret
@@ -998,47 +1071,156 @@ export class EmDashRuntime {
998
1071
  // Initialize storage (sync)
999
1072
  const storage = EmDashRuntime.getStorage(deps);
1000
1073
 
1001
- // Fetch plugin states and site info concurrently — independent reads
1002
- // against different tables (_plugin_state vs options), so they share
1003
- // one round-trip window instead of paying two sequential ones. Each
1004
- // phase() wrapper still records that phase's own duration, and each
1005
- // body keeps its own non-fatal catch.
1006
1074
  let pluginStates: Map<string, string> = new Map();
1007
1075
  let siteInfo: { siteName?: string; siteUrl?: string; locale?: string } | undefined;
1008
- await Promise.all([
1009
- // Fetch plugin states from database
1076
+ // "Already set up" by default so a read failure (e.g. tables absent on a
1077
+ // pre-migration db) skips seeding rather than seeding a half-built db.
1078
+ let seedGate = { collectionCount: 1, setupDone: true };
1079
+
1080
+ // Seeding must only touch the configured singleton, never a borrowed
1081
+ // per-request db (playground / DO preview) or the loader-fallback db.
1082
+ const reqCtx = getRequestContext();
1083
+ const ownsConfiguredDb = !!deps.config.database && !(reqCtx?.dbIsIsolated && reqCtx.db);
1084
+
1085
+ // Run the init reads on a coalescing connection so the concurrent
1086
+ // same-turn reads flush as one batch() round trip. The singleton can't:
1087
+ // its plain SqliteAdapter reports supportsMultipleConnections=false, so
1088
+ // Kysely's connection mutex serializes concurrent reads into N round
1089
+ // trips. The connection is per-init and discarded — the long-lived
1090
+ // singleton must never coalesce or one request's reads could land in
1091
+ // another's batch. Backends without a coalescing dialect (Node/SQLite)
1092
+ // fall back to the singleton, where serialization costs nothing.
1093
+ let readDb = db;
1094
+ let readDbDisposable: Kysely<Database> | undefined;
1095
+ if (ownsConfiguredDb && deps.createCoalescingDialect && deps.config.database) {
1096
+ try {
1097
+ const dialect = deps.createCoalescingDialect(deps.config.database.config);
1098
+ if (dialect) {
1099
+ readDb = new Kysely<Database>({ dialect, log: kyselyLogOption() });
1100
+ readDbDisposable = readDb;
1101
+ }
1102
+ } catch {
1103
+ readDb = db;
1104
+ }
1105
+ }
1106
+ const optionsRepo = new OptionsRepository(readDb);
1107
+
1108
+ const readSiteInfo = async () => {
1109
+ const siteOpts = await optionsRepo.getMany<string>([
1110
+ "emdash:site_title",
1111
+ "emdash:site_url",
1112
+ "emdash:locale",
1113
+ ]);
1114
+ return {
1115
+ siteName: siteOpts.get("emdash:site_title") ?? undefined,
1116
+ siteUrl: siteOpts.get("emdash:site_url") ?? undefined,
1117
+ locale: siteOpts.get("emdash:locale") ?? undefined,
1118
+ };
1119
+ };
1120
+
1121
+ const coldStartReads: Array<Promise<void>> = [
1010
1122
  phase("rt.plugins", "Plugin states", async () => {
1011
1123
  try {
1012
- const states = await db
1124
+ const states = await readDb
1013
1125
  .selectFrom("_plugin_state")
1014
1126
  .select(["plugin_id", "status"])
1015
1127
  .execute();
1016
1128
  pluginStates = new Map(states.map((s) => [s.plugin_id, s.status]));
1017
1129
  } catch {
1018
- // Plugin state table may not exist yet
1130
+ // _plugin_state may not exist yet on a pre-migration db.
1019
1131
  }
1020
1132
  }),
1021
- // Load site info for plugin context extensions (1 batch query instead of 3)
1022
1133
  phase("rt.site", "Site info options", async () => {
1023
1134
  try {
1024
- const optionsRepo = new OptionsRepository(db);
1025
- const siteOpts = await optionsRepo.getMany<string>([
1026
- "emdash:site_title",
1027
- "emdash:site_url",
1028
- "emdash:locale",
1029
- ]);
1030
- siteInfo = {
1031
- siteName: siteOpts.get("emdash:site_title") ?? undefined,
1032
- siteUrl: siteOpts.get("emdash:site_url") ?? undefined,
1033
- locale: siteOpts.get("emdash:locale") ?? undefined,
1034
- };
1135
+ siteInfo = await readSiteInfo();
1035
1136
  } catch {
1036
- // Options table may not exist yet (pre-setup)
1137
+ // options may not exist yet on a pre-migration db.
1037
1138
  }
1038
1139
  }),
1039
- ]);
1140
+ ];
1141
+
1142
+ if (ownsConfiguredDb) {
1143
+ coldStartReads.push(
1144
+ phase("rt.seedcheck", "Auto-seed gate", async () => {
1145
+ try {
1146
+ const [collectionCount, setupOption] = await Promise.all([
1147
+ readDb
1148
+ .selectFrom("_emdash_collections")
1149
+ .select((eb) => eb.fn.countAll<number>().as("count"))
1150
+ .executeTakeFirstOrThrow(),
1151
+ readDb
1152
+ .selectFrom("options")
1153
+ .select("value")
1154
+ .where("name", "=", "emdash:setup_complete")
1155
+ .executeTakeFirst(),
1156
+ ]);
1157
+ const setupDone = (() => {
1158
+ try {
1159
+ return !!setupOption && JSON.parse(setupOption.value) === true;
1160
+ } catch {
1161
+ return false;
1162
+ }
1163
+ })();
1164
+ seedGate = { collectionCount: collectionCount.count, setupDone };
1165
+ } catch {
1166
+ // Leave the "already set up" default so a read failure never
1167
+ // triggers a seed onto a half-built db.
1168
+ }
1169
+ }),
1170
+ );
1171
+ }
1172
+
1173
+ await Promise.all(coldStartReads);
1174
+
1175
+ // Auto-seed the default schema for a first load that skipped the setup
1176
+ // wizard (the wizard and dev-bypass apply seeds explicitly). Run under a
1177
+ // per-isolate lock keyed by the configured db so a reclaimed-and-rerun
1178
+ // create() can't apply the seed a second time concurrently.
1179
+ if (seedGate.collectionCount === 0 && !seedGate.setupDone) {
1180
+ const seedKey = deps.config.database?.entrypoint ?? "default";
1181
+ const seedHolder = getSeedHolder();
1182
+ try {
1183
+ await initWithLock(
1184
+ seedHolder.lock,
1185
+ () => (seedHolder.done.has(seedKey) ? true : undefined),
1186
+ async () => {
1187
+ const { applySeed } = await import("./seed/apply.js");
1188
+ const { loadSeed } = await import("./seed/load.js");
1189
+ const { validateSeed } = await import("./seed/validate.js");
1190
+
1191
+ const seed = await loadSeed();
1192
+ const validation = validateSeed(seed);
1193
+ if (validation.valid) {
1194
+ await applySeed(db, seed, { onConflict: "skip" });
1195
+ console.log("Auto-seeded default collections");
1196
+ }
1197
+ seedHolder.done.add(seedKey);
1198
+ return true;
1199
+ },
1200
+ { deadlineMs: DB_INIT_DEADLINE_MS, anchor: (promise) => after(() => promise) },
1201
+ );
1202
+ // The site-info read ran before the seed wrote its defaults, so
1203
+ // refresh the snapshot the plugin context sees.
1204
+ try {
1205
+ siteInfo = await readSiteInfo();
1206
+ } catch {
1207
+ // Non-fatal — plugin context falls back to undefined fields.
1208
+ }
1209
+ } catch {
1210
+ // Non-fatal — a failed seed (e.g. missing seed module) leaves the
1211
+ // site un-seeded; a later request retries.
1212
+ }
1213
+ }
1214
+
1215
+ // The read connection is single-use; everything below uses the singleton.
1216
+ if (readDbDisposable) {
1217
+ try {
1218
+ await readDbDisposable.destroy();
1219
+ } catch {
1220
+ // Non-fatal — the underlying binding is shared and needs no teardown.
1221
+ }
1222
+ }
1040
1223
 
1041
- // Build set of enabled plugins
1042
1224
  const enabledPlugins = new Set<string>();
1043
1225
  for (const plugin of deps.plugins) {
1044
1226
  const status = pluginStates.get(plugin.id);
@@ -1154,9 +1336,15 @@ export class EmDashRuntime {
1154
1336
  // Filter to currently enabled plugins for the initial pipeline
1155
1337
  const enabledPluginList = allPipelinePlugins.filter((p) => enabledPlugins.has(p.id));
1156
1338
 
1157
- // Create hook pipeline
1339
+ // Create hook pipeline. getDb travels here (not just via the email
1340
+ // setContextFactory call below) so it survives rebuildHookPipeline(),
1341
+ // which reconstructs the factory from pipelineFactoryOptions. Without it,
1342
+ // toggling a plugin on an email-less deployment would silently revert
1343
+ // plugin contexts to the singleton db — re-breaking connection-backed
1344
+ // adapters. See #1622.
1158
1345
  const pipelineFactoryOptions = {
1159
1346
  db,
1347
+ getDb: resolveDb,
1160
1348
  storage: storage ?? undefined,
1161
1349
  siteInfo,
1162
1350
  };
@@ -1208,7 +1396,7 @@ export class EmDashRuntime {
1208
1396
  // Initialize media providers
1209
1397
  const mediaProviders = new Map<string, MediaProvider>();
1210
1398
  const mediaProviderEntries = deps.mediaProviderEntries ?? [];
1211
- const providerContext: MediaProviderContext = { db, storage };
1399
+ const providerContext: MediaProviderContext = { db, storage, getDb: resolveDb };
1212
1400
 
1213
1401
  for (const entry of mediaProviderEntries) {
1214
1402
  try {
@@ -1249,8 +1437,10 @@ export class EmDashRuntime {
1249
1437
  };
1250
1438
 
1251
1439
  // Wire email pipeline into context factory (independent of cron —
1252
- // must not be inside the cron try/catch or ctx.email breaks when cron fails)
1253
- pipeline.setContextFactory({ db, emailPipeline });
1440
+ // must not be inside the cron try/catch or ctx.email breaks when cron fails).
1441
+ // db/getDb were already set via pipelineFactoryOptions above; merge only
1442
+ // adds emailPipeline.
1443
+ pipeline.setContextFactory({ emailPipeline });
1254
1444
 
1255
1445
  let cronExecutor: CronExecutor | null = null;
1256
1446
  let cronScheduler: CronScheduler | null = null;
@@ -1262,7 +1452,7 @@ export class EmDashRuntime {
1262
1452
 
1263
1453
  await phase("rt.cron", "Cron init (recovery deferred post-response)", async () => {
1264
1454
  try {
1265
- cronExecutor = new CronExecutor(db, invokeCronHook);
1455
+ cronExecutor = new CronExecutor(resolveDb, invokeCronHook);
1266
1456
 
1267
1457
  // Recover stale locks from previous crashes. Pure bookkeeping
1268
1458
  // against the _emdash_cron_tasks table — no request needs the
@@ -1444,45 +1634,9 @@ export class EmDashRuntime {
1444
1634
  // cleanup-flag check costs an extra `options.get()` on every
1445
1635
  // isolate cold boot forever. Cheaper to leave it.
1446
1636
 
1447
- // Auto-seed schema if no collections exist and setup hasn't run.
1448
- // This covers first-load on sites that skip the setup wizard.
1449
- // Dev-bypass and the wizard apply seeds explicitly.
1450
- try {
1451
- const [collectionCount, setupOption] = await Promise.all([
1452
- db
1453
- .selectFrom("_emdash_collections")
1454
- .select((eb) => eb.fn.countAll<number>().as("count"))
1455
- .executeTakeFirstOrThrow(),
1456
- db
1457
- .selectFrom("options")
1458
- .select("value")
1459
- .where("name", "=", "emdash:setup_complete")
1460
- .executeTakeFirst(),
1461
- ]);
1462
-
1463
- const setupDone = (() => {
1464
- try {
1465
- return setupOption && JSON.parse(setupOption.value) === true;
1466
- } catch {
1467
- return false;
1468
- }
1469
- })();
1470
-
1471
- if (collectionCount.count === 0 && !setupDone) {
1472
- const { applySeed } = await import("./seed/apply.js");
1473
- const { loadSeed } = await import("./seed/load.js");
1474
- const { validateSeed } = await import("./seed/validate.js");
1475
-
1476
- const seed = await loadSeed();
1477
- const validation = validateSeed(seed);
1478
- if (validation.valid) {
1479
- await applySeed(db, seed, { onConflict: "skip" });
1480
- console.log("Auto-seeded default collections");
1481
- }
1482
- }
1483
- } catch {
1484
- // Tables may not exist yet. Non-fatal.
1485
- }
1637
+ // This returns a migrated but possibly unseeded db; create() runs
1638
+ // the seed gate and applies the seed, batched with its other init
1639
+ // reads.
1486
1640
 
1487
1641
  // Publish only while still the current owner: a reclaimed slow
1488
1642
  // init must not flip the cached Kysely identity back after the
@@ -2965,6 +3119,7 @@ export class EmDashRuntime {
2965
3119
  if (trustedPlugin && this.enabledPlugins.has(trustedPlugin.id)) {
2966
3120
  const routeRegistry = new PluginRouteRegistry({
2967
3121
  db: this.db,
3122
+ storage: this.storage ?? undefined,
2968
3123
  emailPipeline: this.email ?? undefined,
2969
3124
  trustedProxyHeaders: getTrustedProxyHeaders(this.config),
2970
3125
  });
package/src/loader.ts CHANGED
@@ -12,7 +12,7 @@
12
12
  */
13
13
 
14
14
  import type { LiveLoader } from "astro/loaders";
15
- import { Kysely, sql, type Dialect } from "kysely";
15
+ import { Kysely, type RawBuilder, sql, type Dialect } from "kysely";
16
16
 
17
17
  import { currentTimestampValue, isPostgres } from "./database/dialect-helpers.js";
18
18
  import { kyselyLogOption } from "./database/instrumentation.js";
@@ -72,8 +72,83 @@ const SYSTEM_COLUMNS = new Set([
72
72
  // fields. The aliases are _emdash_-prefixed so they can't shadow a user
73
73
  // field named e.g. `seo_title`.
74
74
  ...SEO_ALIAS_COLUMNS,
75
+ // Folded hydration JSON columns (see foldedHydrationSelects) — surfaced via
76
+ // the FOLDED_* markers, never as flat fields.
77
+ "_emdash_terms",
78
+ "_emdash_bylines",
75
79
  ]);
76
80
 
81
+ /** Markers for byline/taxonomy hydration folded into the content query. */
82
+ export const FOLDED_TERMS = Symbol.for("emdash:foldedTerms");
83
+ export const FOLDED_BYLINES = Symbol.for("emdash:foldedBylines");
84
+
85
+ /**
86
+ * Correlated JSON-array subqueries that fold taxonomy-term and byline hydration
87
+ * into the content query, removing the two separate hydration round trips per
88
+ * fetch. `outer` is the content table's alias/name; each subquery correlates on
89
+ * `<outer>.id`, so the base query stays one row per entry (no join fan-out, no
90
+ * duplicated content payload). Order is NOT applied in the aggregate (it differs
91
+ * across dialects) — the consumer sorts terms by label and credits by sortOrder.
92
+ *
93
+ * Dialect-specific aggregation: SQLite `json_group_array`/`json_object` returns
94
+ * a JSON *string*; Postgres `json_agg`/`json_build_object` (coalesced to `[]`)
95
+ * returns parsed JSON. {@link stashFolded} handles both.
96
+ */
97
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- any Kysely instance
98
+ function foldedHydrationSelects(db: Kysely<any>, type: string, outer: string) {
99
+ const o = sql.ref(outer);
100
+ const pg = isPostgres(db);
101
+ const obj = (pairs: string) =>
102
+ pg ? sql.raw(`json_build_object(${pairs})`) : sql.raw(`json_object(${pairs})`);
103
+ const agg = (inner: RawBuilder<unknown>) =>
104
+ pg ? sql`coalesce(json_agg(${inner}), '[]'::json)` : sql`json_group_array(${inner})`;
105
+
106
+ const termObj = obj(
107
+ "'id', t.id, 'name', t.name, 'slug', t.slug, 'label', t.label, 'parent_id', t.parent_id, 'locale', t.locale, 'translation_group', t.translation_group",
108
+ );
109
+ // Filter terms to the entry's own locale (matches #1441: terms render in the
110
+ // entry's resolved locale, not all locale variants of the attached group).
111
+ const terms = sql`(SELECT ${agg(termObj)} FROM ${sql.ref("content_taxonomies")} AS ct JOIN ${sql.ref("taxonomies")} AS t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ${type} AND ct.entry_id = ${o}.id AND t.locale = ${o}.locale) AS ${sql.ref("_emdash_terms")}`;
112
+
113
+ const bylineInner = obj(
114
+ "'id', b.id, 'slug', b.slug, 'displayName', b.display_name, 'bio', b.bio, 'avatarMediaId', b.avatar_media_id, 'avatarStorageKey', m.storage_key, 'avatarAlt', m.alt, 'avatarBlurhash', m.blurhash, 'avatarDominantColor', m.dominant_color, 'websiteUrl', b.website_url, 'userId', b.user_id, 'isGuest', b.is_guest, 'createdAt', b.created_at, 'updatedAt', b.updated_at, 'locale', b.locale, 'translationGroup', b.translation_group",
115
+ );
116
+ const creditObj = pg
117
+ ? sql.raw(
118
+ "json_build_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', ",
119
+ )
120
+ : sql.raw("json_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', ");
121
+ const credit = sql`${creditObj}${bylineInner})`;
122
+ const bylines = sql`(SELECT ${agg(credit)} FROM ${sql.ref("_emdash_content_bylines")} AS cb JOIN ${sql.ref("_emdash_bylines")} AS b ON b.translation_group = cb.byline_id LEFT JOIN ${sql.ref("media")} AS m ON m.id = b.avatar_media_id WHERE cb.collection_slug = ${type} AND cb.content_id = ${o}.id AND b.locale = ${o}.locale) AS ${sql.ref("_emdash_bylines")}`;
123
+ return { terms, bylines };
124
+ }
125
+
126
+ /**
127
+ * Stash folded hydration JSON (non-enumerable) for the query.ts fast paths.
128
+ * SQLite returns a JSON string (parse it); Postgres returns already-parsed JSON.
129
+ */
130
+ function stashFolded(data: Record<string, unknown>, row: Record<string, unknown>): void {
131
+ for (const [col, sym] of [
132
+ ["_emdash_terms", FOLDED_TERMS],
133
+ ["_emdash_bylines", FOLDED_BYLINES],
134
+ ] as const) {
135
+ const raw = row[col];
136
+ let value: unknown;
137
+ if (typeof raw === "string") {
138
+ try {
139
+ value = JSON.parse(raw);
140
+ } catch {
141
+ continue; // malformed: fall back to the query path
142
+ }
143
+ } else if (Array.isArray(raw)) {
144
+ value = raw; // Postgres json/jsonb already parsed by the driver
145
+ } else {
146
+ continue;
147
+ }
148
+ Object.defineProperty(data, sym, { value, enumerable: false, configurable: true });
149
+ }
150
+ }
151
+
77
152
  /** Resolved SEO shape attached to `entry.data.seo`. Mirrors `ContentSeo`. */
78
153
  interface EntrySeo {
79
154
  title: string | null;
@@ -528,17 +603,17 @@ export interface WhereRange {
528
603
  export type WhereValue = string | string[] | WhereRange;
529
604
 
530
605
  /**
531
- * Filter for loadCollection - type is required
606
+ * Fields shared by every collection filter, independent of pagination mode.
607
+ *
608
+ * Cursor and offset pagination are mutually exclusive, so they live on the
609
+ * `CursorCollectionFilter` / `OffsetCollectionFilter` variants rather than
610
+ * here. Use the {@link CollectionFilter} union for any value that may be
611
+ * either.
532
612
  */
533
- export interface CollectionFilter {
613
+ export interface CollectionFilterBase {
534
614
  type: string;
535
615
  status?: "draft" | "published" | "archived";
536
616
  limit?: number;
537
- /**
538
- * Opaque cursor for keyset pagination.
539
- * Pass the `nextCursor` value from a previous result to fetch the next page.
540
- */
541
- cursor?: string;
542
617
  /**
543
618
  * Filter by field values, taxonomy terms, byline credits, or ranges.
544
619
  *
@@ -566,6 +641,37 @@ export interface CollectionFilter {
566
641
  locale?: string;
567
642
  }
568
643
 
644
+ /** Keyset-paginated collection filter. Cannot also carry an `offset`. */
645
+ export interface CursorCollectionFilter extends CollectionFilterBase {
646
+ /**
647
+ * Opaque cursor for keyset pagination.
648
+ * Pass the `nextCursor` value from a previous result to fetch the next page.
649
+ */
650
+ cursor?: string;
651
+ offset?: never;
652
+ }
653
+
654
+ /** Offset-paginated collection filter. Cannot also carry a `cursor`. */
655
+ export interface OffsetCollectionFilter extends CollectionFilterBase {
656
+ /**
657
+ * Skip this many rows before returning results (offset pagination).
658
+ * Use with `limit` for numbered archive routes (`/page/2`):
659
+ * `offset = (page - 1) * perPage`. Ignored unless it is a positive
660
+ * integer.
661
+ */
662
+ offset?: number;
663
+ cursor?: never;
664
+ }
665
+
666
+ /**
667
+ * Filter for loadCollection - type is required.
668
+ *
669
+ * A union of the cursor and offset pagination variants: supplying both
670
+ * `cursor` and `offset` is a compile-time error, since they are mutually
671
+ * exclusive ways to express "the next page" (cursor wins at runtime).
672
+ */
673
+ export type CollectionFilter = CursorCollectionFilter | OffsetCollectionFilter;
674
+
569
675
  /**
570
676
  * Filter for loadEntry - type and id are required
571
677
  */
@@ -675,6 +781,17 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
675
781
  // Cursor pagination: over-fetch by 1 to detect next page
676
782
  const fetchLimit = limit ? limit + 1 : undefined;
677
783
 
784
+ // Offset pagination (numbered archive routes). Keyset (cursor)
785
+ // and offset are mutually exclusive ways to express "the next
786
+ // page" — when both are supplied, cursor wins and offset is
787
+ // dropped so the two don't stack into a double skip. Only a
788
+ // positive integer applies; 0 / negative / fractional are no-ops.
789
+ const rawOffset = cursor ? undefined : filter?.offset;
790
+ const offset =
791
+ typeof rawOffset === "number" && Number.isInteger(rawOffset) && rawOffset > 0
792
+ ? rawOffset
793
+ : undefined;
794
+
678
795
  // Build cursor condition if cursor is provided
679
796
  const cursorCondition = cursor ? buildCursorCondition(cursor, orderBy) : null;
680
797
 
@@ -785,8 +902,28 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
785
902
  )`
786
903
  : sql``;
787
904
 
905
+ // Fold byline + taxonomy hydration into the list query.
906
+ const { terms: termsSelect, bylines: bylinesSelect } = foldedHydrationSelects(
907
+ db,
908
+ type,
909
+ tableName,
910
+ );
911
+
912
+ // LIMIT/OFFSET clause. SQLite only accepts OFFSET when a
913
+ // LIMIT is present, so a bare offset uses `LIMIT -1`
914
+ // (unbounded); Postgres takes a standalone OFFSET.
915
+ let limitOffsetClause = sql``;
916
+ if (fetchLimit != null && offset != null) {
917
+ limitOffsetClause = sql`LIMIT ${fetchLimit} OFFSET ${offset}`;
918
+ } else if (fetchLimit != null) {
919
+ limitOffsetClause = sql`LIMIT ${fetchLimit}`;
920
+ } else if (offset != null) {
921
+ limitOffsetClause = isPostgres(db)
922
+ ? sql`OFFSET ${offset}`
923
+ : sql`LIMIT -1 OFFSET ${offset}`;
924
+ }
788
925
  result = await sql<Record<string, unknown>>`
789
- SELECT * FROM ${sql.ref(tableName)}
926
+ SELECT *, ${termsSelect}, ${bylinesSelect} FROM ${sql.ref(tableName)}
790
927
  WHERE deleted_at IS NULL
791
928
  AND ${statusCondition}
792
929
  ${localeFilter}
@@ -795,7 +932,7 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
795
932
  ${bylineCond}
796
933
  ${fieldCondsSQL ? sql`AND ${fieldCondsSQL}` : sql``}
797
934
  ${orderByClause}
798
- ${fetchLimit ? sql`LIMIT ${fetchLimit}` : sql``}
935
+ ${limitOffsetClause}
799
936
  `.execute(db);
800
937
  }
801
938
 
@@ -814,11 +951,13 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
814
951
  rowLocale !== "" &&
815
952
  (rowLocale !== i18nConfig.defaultLocale || i18nConfig.prefixDefaultLocale);
816
953
  const id = shouldPrefix ? `${rowLocale}/${slug}` : slug;
954
+ const data = mapRowToData(row);
955
+ stashFolded(data, row);
817
956
  return {
818
957
  id,
819
958
  slug: rowStr(row, "slug"),
820
959
  status: rowStr(row, "status", "draft"),
821
- data: mapRowToData(row),
960
+ data,
822
961
  cacheHint: {
823
962
  tags: [rowStr(row, "id")],
824
963
  lastModified: row.updated_at ? new Date(rowStr(row, "updated_at")) : undefined,
@@ -922,9 +1061,17 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
922
1061
  ([col, alias]) => sql`${sql.ref(`s.${col}`)} AS ${sql.ref(alias)}`,
923
1062
  ),
924
1063
  );
1064
+ // Fold byline + taxonomy hydration into the content query (see
1065
+ // foldedHydrationSelects), removing the two separate hydration
1066
+ // round trips per fetch.
1067
+ const { terms: termsSelect, bylines: bylinesSelect } = foldedHydrationSelects(
1068
+ db,
1069
+ type,
1070
+ "c",
1071
+ );
925
1072
  const result = locale
926
1073
  ? await sql<Record<string, unknown>>`
927
- SELECT c.*, ${seoSelect}
1074
+ SELECT c.*, ${seoSelect}, ${termsSelect}, ${bylinesSelect}
928
1075
  FROM ${sql.ref(tableName)} AS c
929
1076
  LEFT JOIN ${sql.ref("_emdash_seo")} AS s
930
1077
  ON s.collection = ${type} AND s.content_id = c.id
@@ -933,7 +1080,7 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
933
1080
  LIMIT 1
934
1081
  `.execute(db)
935
1082
  : await sql<Record<string, unknown>>`
936
- SELECT c.*, ${seoSelect}
1083
+ SELECT c.*, ${seoSelect}, ${termsSelect}, ${bylinesSelect}
937
1084
  FROM ${sql.ref(tableName)} AS c
938
1085
  LEFT JOIN ${sql.ref("_emdash_seo")} AS s
939
1086
  ON s.collection = ${type} AND s.content_id = c.id
@@ -1015,6 +1162,7 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
1015
1162
  const entryData = mapRowToData(row);
1016
1163
  const entrySeo = extractSeo(row);
1017
1164
  if (entrySeo) entryData.seo = entrySeo;
1165
+ stashFolded(entryData, row);
1018
1166
  return {
1019
1167
  id: entryId,
1020
1168
  slug: rowStr(row, "slug"),