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
@@ -29,6 +29,14 @@ export interface LocalMediaRuntimeConfig {
29
29
  enabled?: boolean;
30
30
  // These are injected by the runtime, not from user config
31
31
  db?: Kysely<Database>;
32
+ /**
33
+ * Resolver for the live connection, preferred over `db`. The runtime
34
+ * injects it so a connection-backed adapter (Postgres over Hyperdrive)
35
+ * serves provider queries from the current request-scoped connection in ALS
36
+ * rather than a snapshot of the per-isolate singleton. Omitted for stateless
37
+ * adapters (D1, Node SQLite), where `db` is used directly.
38
+ */
39
+ getDb?: () => Kysely<Database>;
32
40
  storage?: Storage;
33
41
  }
34
42
 
@@ -42,11 +50,15 @@ export const createMediaProvider: CreateMediaProviderFn<LocalMediaRuntimeConfig>
42
50
  throw new Error("Local media provider requires database connection");
43
51
  }
44
52
 
45
- const repo = new MediaRepository(db);
53
+ // Resolve the connection per operation (not captured once) so a
54
+ // connection-backed adapter uses the current event-scoped connection; falls
55
+ // back to the injected `db` for stateless adapters.
56
+ const resolveDb = config.getDb ?? (() => db);
57
+ const repo = () => new MediaRepository(resolveDb());
46
58
 
47
59
  const provider: MediaProvider = {
48
60
  async list(options: MediaListOptions) {
49
- const result = await repo.findMany({
61
+ const result = await repo().findMany({
50
62
  cursor: options.cursor,
51
63
  limit: options.limit,
52
64
  mimeType: options.mimeType,
@@ -75,7 +87,7 @@ export const createMediaProvider: CreateMediaProviderFn<LocalMediaRuntimeConfig>
75
87
  },
76
88
 
77
89
  async get(id: string) {
78
- const item = await repo.findById(id);
90
+ const item = await repo().findById(id);
79
91
  if (!item) return null;
80
92
 
81
93
  return {
@@ -108,7 +120,8 @@ export const createMediaProvider: CreateMediaProviderFn<LocalMediaRuntimeConfig>
108
120
  },
109
121
 
110
122
  async delete(id: string) {
111
- const item = await repo.findById(id);
123
+ const repoInstance = repo();
124
+ const item = await repoInstance.findById(id);
112
125
  if (!item) return;
113
126
 
114
127
  // Delete from storage if available
@@ -120,7 +133,7 @@ export const createMediaProvider: CreateMediaProviderFn<LocalMediaRuntimeConfig>
120
133
  }
121
134
  }
122
135
 
123
- await repo.delete(id);
136
+ await repoInstance.delete(id);
124
137
 
125
138
  // If this row was referenced by `logo`, `favicon`, or
126
139
  // `seo.defaultOgImage`, the worker-scoped settings cache now
@@ -432,23 +432,55 @@ export function createMediaAccess(db: Kysely<Database>): MediaAccess {
432
432
 
433
433
  /**
434
434
  * Create full media access with write operations.
435
- * If storage is not provided, upload() will throw at call time.
435
+ *
436
+ * `getUploadUrlFn` is optional: when omitted, `getUploadUrl()` is derived from
437
+ * `storage` (create a pending record + a signed PUT URL), mirroring the REST
438
+ * `/_emdash/api/media/upload-url` endpoint. `upload()` only needs `storage`.
439
+ * If storage is not provided, both throw at call time.
436
440
  */
437
441
  export function createMediaAccessWithWrite(
438
442
  db: Kysely<Database>,
439
- getUploadUrlFn: (
440
- filename: string,
441
- contentType: string,
442
- ) => Promise<{ uploadUrl: string; mediaId: string }>,
443
+ getUploadUrlFn:
444
+ | ((filename: string, contentType: string) => Promise<{ uploadUrl: string; mediaId: string }>)
445
+ | undefined,
443
446
  storage?: Storage,
444
447
  ): MediaAccessWithWrite {
445
448
  const mediaRepo = new MediaRepository(db);
446
449
  const readAccess = createMediaAccess(db);
447
450
 
451
+ const getUploadUrl =
452
+ getUploadUrlFn ??
453
+ (async (filename: string, contentType: string) => {
454
+ if (!storage) {
455
+ throw new Error(
456
+ "Media getUploadUrl() requires a storage backend. Configure storage in PluginContextFactoryOptions.",
457
+ );
458
+ }
459
+
460
+ const basename = filename.split("/").pop() ?? filename;
461
+ const dotIdx = basename.lastIndexOf(".");
462
+ const ext = dotIdx > 0 ? basename.slice(dotIdx).toLowerCase() : "";
463
+ const storageKey = `${ulid()}${ext}`;
464
+
465
+ const media = await mediaRepo.createPending({
466
+ filename: basename,
467
+ mimeType: contentType,
468
+ storageKey,
469
+ });
470
+
471
+ const signed = await storage.getSignedUploadUrl({
472
+ key: storageKey,
473
+ contentType,
474
+ expiresIn: 3600,
475
+ });
476
+
477
+ return { uploadUrl: signed.url, mediaId: media.id };
478
+ });
479
+
448
480
  return {
449
481
  ...readAccess,
450
482
 
451
- getUploadUrl: getUploadUrlFn,
483
+ getUploadUrl,
452
484
 
453
485
  async upload(
454
486
  filename: string,
@@ -836,14 +868,26 @@ export function createUserAccess(db: Kysely<Database>): UserAccess {
836
868
 
837
869
  export interface PluginContextFactoryOptions {
838
870
  db: Kysely<Database>;
871
+ /**
872
+ * Resolver for the database connection, preferred over `db` when present.
873
+ * Called per `createContext()` so connection-backed adapters (e.g. Postgres
874
+ * over Hyperdrive) get the current request/event-scoped connection from ALS
875
+ * rather than a snapshot of the per-isolate singleton — reusing the
876
+ * singleton's socket from a later event trips workerd's cross-request I/O
877
+ * guard. When omitted, `db` is used directly (correct for stateless
878
+ * adapters like D1 and Node SQLite). `db` remains required as the fallback.
879
+ */
880
+ getDb?: () => Kysely<Database>;
839
881
  /**
840
882
  * Storage backend for direct media uploads.
841
883
  * If not provided, upload() will throw.
842
884
  */
843
885
  storage?: Storage;
844
886
  /**
845
- * Function to generate upload URLs for media.
846
- * If not provided, media write operations will throw.
887
+ * Explicit provider for `ctx.media.getUploadUrl()`. Optional: when omitted
888
+ * but `storage` is configured, the factory derives a working `getUploadUrl()`
889
+ * (and `upload()`) from storage. Only when neither `getUploadUrl` nor
890
+ * `storage` is present do media write operations become unavailable.
847
891
  */
848
892
  getUploadUrl?: (
849
893
  filename: string,
@@ -877,8 +921,7 @@ export interface PluginContextFactoryOptions {
877
921
  * Factory for creating plugin contexts
878
922
  */
879
923
  export class PluginContextFactory {
880
- private optionsRepo: OptionsRepository;
881
- private db: Kysely<Database>;
924
+ private resolveDb: () => Kysely<Database>;
882
925
  private storage?: Storage;
883
926
  private getUploadUrl?: (
884
927
  filename: string,
@@ -888,10 +931,16 @@ export class PluginContextFactory {
888
931
  private urlHelper: (path: string) => string;
889
932
  private cronReschedule?: () => void;
890
933
  private emailPipeline?: EmailPipeline;
934
+ /**
935
+ * Plugin IDs already warned about a missing media-write backend, so the
936
+ * warning fires once per factory instead of on every hook/route context
937
+ * creation (which would spam logs for hook-participating plugins).
938
+ */
939
+ private warnedMissingMediaBackend = new Set<string>();
891
940
 
892
941
  constructor(options: PluginContextFactoryOptions) {
893
- this.db = options.db;
894
- this.optionsRepo = new OptionsRepository(options.db);
942
+ const fixedDb = options.db;
943
+ this.resolveDb = options.getDb ?? (() => fixedDb);
895
944
  this.storage = options.storage;
896
945
  this.getUploadUrl = options.getUploadUrl;
897
946
  this.site = createSiteInfo(options.siteInfo ?? {});
@@ -906,10 +955,17 @@ export class PluginContextFactory {
906
955
  createContext(plugin: ResolvedPlugin): PluginContext {
907
956
  const capabilities = new Set(plugin.capabilities);
908
957
 
958
+ // Resolve the connection once per context. For stateless adapters this
959
+ // is the singleton; for connection-backed adapters it's the current
960
+ // request/event-scoped connection from ALS. All repos below are built
961
+ // from this local `db` so a hook never queries a stale singleton socket.
962
+ const db = this.resolveDb();
963
+ const optionsRepo = new OptionsRepository(db);
964
+
909
965
  // Always available
910
- const kv = createKVAccess(this.optionsRepo, plugin.id);
966
+ const kv = createKVAccess(optionsRepo, plugin.id);
911
967
  const log = createLogAccess(plugin.id);
912
- const storage = createStorageAccess(this.db, plugin.id, plugin.storage);
968
+ const storage = createStorageAccess(db, plugin.id, plugin.storage);
913
969
 
914
970
  // Capability-gated: content
915
971
  // Note: capabilities reach this point already normalized to the
@@ -917,17 +973,33 @@ export class PluginContextFactory {
917
973
  // names ("read:content", "write:content") never appear here.
918
974
  let content: ContentAccess | ContentAccessWithWrite | undefined;
919
975
  if (capabilities.has("content:write")) {
920
- content = createContentAccessWithWrite(this.db);
976
+ content = createContentAccessWithWrite(db);
921
977
  } else if (capabilities.has("content:read")) {
922
- content = createContentAccess(this.db);
978
+ content = createContentAccess(db);
923
979
  }
924
980
 
925
981
  // Capability-gated: media
982
+ // `upload()` only needs `storage`; `getUploadUrl()` is derived from
983
+ // storage when no explicit provider is wired. Granting write access on
984
+ // either avoids silently degrading media:write to read-only — the bug
985
+ // where the runtime threads `storage` but not `getUploadUrl`.
926
986
  let media: MediaAccess | MediaAccessWithWrite | undefined;
927
- if (capabilities.has("media:write") && this.getUploadUrl) {
928
- media = createMediaAccessWithWrite(this.db, this.getUploadUrl, this.storage);
987
+ if (capabilities.has("media:write")) {
988
+ if (this.getUploadUrl || this.storage) {
989
+ media = createMediaAccessWithWrite(db, this.getUploadUrl, this.storage);
990
+ } else {
991
+ if (!this.warnedMissingMediaBackend.has(plugin.id)) {
992
+ this.warnedMissingMediaBackend.add(plugin.id);
993
+ log.warn(
994
+ "declares the media:write capability but no storage backend is configured; upload() is unavailable.",
995
+ );
996
+ }
997
+ if (capabilities.has("media:read")) {
998
+ media = createMediaAccess(db);
999
+ }
1000
+ }
929
1001
  } else if (capabilities.has("media:read")) {
930
- media = createMediaAccess(this.db);
1002
+ media = createMediaAccess(db);
931
1003
  }
932
1004
 
933
1005
  // Capability-gated: http
@@ -941,14 +1013,14 @@ export class PluginContextFactory {
941
1013
  // Capability-gated: users
942
1014
  let users: UserAccess | undefined;
943
1015
  if (capabilities.has("users:read")) {
944
- users = createUserAccess(this.db);
1016
+ users = createUserAccess(db);
945
1017
  }
946
1018
 
947
- // Cron access ��� always available (scoped to plugin), but only if
1019
+ // Cron access always available (scoped to plugin), but only if
948
1020
  // the runtime provided a reschedule callback (i.e. cron is wired up).
949
1021
  let cron: CronAccess | undefined;
950
1022
  if (this.cronReschedule) {
951
- cron = new CronAccessImpl(this.db, plugin.id, this.cronReschedule);
1023
+ cron = new CronAccessImpl(db, plugin.id, this.cronReschedule);
952
1024
  }
953
1025
 
954
1026
  // Email access — requires email:send capability AND a configured provider
@@ -39,10 +39,26 @@ export type RescheduleFn = () => void;
39
39
  * Stateless — all state lives in the database.
40
40
  */
41
41
  export class CronExecutor {
42
+ /**
43
+ * Resolves the database connection to use for this tick. A resolver (not a
44
+ * captured instance) so connection-backed adapters work across events: on
45
+ * Cloudflare the `scheduled()` handler installs an event-scoped connection
46
+ * in ALS, and this resolves to it instead of the per-isolate singleton
47
+ * whose socket belongs to an earlier request. Accepts a plain `Kysely` too
48
+ * (wrapped in a constant resolver) for callers/tests that don't need ALS.
49
+ */
50
+ private readonly resolveDb: () => Kysely<Database>;
51
+
42
52
  constructor(
43
- private db: Kysely<Database>,
53
+ db: Kysely<Database> | (() => Kysely<Database>),
44
54
  private invokeCronHook: InvokeCronHookFn,
45
- ) {}
55
+ ) {
56
+ this.resolveDb = typeof db === "function" ? db : () => db;
57
+ }
58
+
59
+ private get db(): Kysely<Database> {
60
+ return this.resolveDb();
61
+ }
46
62
 
47
63
  /**
48
64
  * Process all overdue tasks.
@@ -12,6 +12,42 @@ import { PluginContextFactory, type PluginContextFactoryOptions } from "./contex
12
12
  import { extractRequestMeta } from "./request-meta.js";
13
13
  import type { ResolvedPlugin, RouteContext, PluginRoute } from "./types.js";
14
14
 
15
+ /**
16
+ * Body-reading methods on `Request`. EmDash parses the request body once before
17
+ * the handler runs and exposes the result as `ctx.input`, leaving the underlying
18
+ * stream consumed. Calling any of these on `ctx.request` would re-read a spent
19
+ * stream and throw an opaque platform error ("Body is unusable: Body has already
20
+ * been read") with no hint about `ctx.input` — so the guard replaces them with an
21
+ * actionable message instead (#1293).
22
+ */
23
+ const CONSUMED_BODY_METHODS = new Set(["json", "text", "arrayBuffer", "blob", "formData", "bytes"]);
24
+
25
+ /**
26
+ * Wrap the request handed to a plugin route handler so an accidental
27
+ * `ctx.request.json()` (or `.text()`, `.formData()`, …) fails with a message
28
+ * pointing at `ctx.input` rather than the runtime's cryptic "body already read"
29
+ * error. Every non-body member passes through unchanged; function members are
30
+ * bound to the underlying request so methods like `clone()` don't throw an
31
+ * "Illegal invocation" when called on the proxy.
32
+ */
33
+ function guardConsumedRequestBody(request: Request): Request {
34
+ return new Proxy(request, {
35
+ get(target, prop) {
36
+ if (typeof prop === "string" && CONSUMED_BODY_METHODS.has(prop)) {
37
+ return () => {
38
+ throw new Error(
39
+ `[emdash] ctx.request.${prop}() is not available inside a plugin route handler: ` +
40
+ `EmDash has already parsed the request body and exposes it as ctx.input. ` +
41
+ `Read ctx.input instead of ctx.request.${prop}().`,
42
+ );
43
+ };
44
+ }
45
+ const value = Reflect.get(target, prop, target);
46
+ return typeof value === "function" ? value.bind(target) : value;
47
+ },
48
+ });
49
+ }
50
+
15
51
  /**
16
52
  * Route metadata (public flag) without the handler.
17
53
  * Used by the catch-all route to decide auth before dispatch.
@@ -100,7 +136,10 @@ export class PluginRouteHandler {
100
136
  const routeContext: RouteContext = {
101
137
  ...baseContext,
102
138
  input: validatedInput,
103
- request: options.request,
139
+ // The body is already parsed into `input`; guard `ctx.request`'s
140
+ // body-reading methods so a re-read fails with an actionable message
141
+ // (#1293). Metadata extraction uses the original request (headers only).
142
+ request: guardConsumedRequestBody(options.request),
104
143
  requestMeta: extractRequestMeta(options.request, this.trustedProxyHeaders),
105
144
  };
106
145
 
package/src/query.ts CHANGED
@@ -26,7 +26,13 @@
26
26
 
27
27
  import { encodeCursor } from "./database/repositories/types.js";
28
28
  import { getFallbackChain, getI18nConfig, isI18nEnabled } from "./i18n/config.js";
29
- import { CURSOR_RAW_VALUES, type WhereRange, type WhereValue } from "./loader.js";
29
+ import {
30
+ CURSOR_RAW_VALUES,
31
+ FOLDED_BYLINES,
32
+ FOLDED_TERMS,
33
+ type WhereRange,
34
+ type WhereValue,
35
+ } from "./loader.js";
30
36
  import {
31
37
  cachedQuery,
32
38
  contentNamespaces,
@@ -34,6 +40,7 @@ import {
34
40
  } from "./object-cache/index.js";
35
41
  import { requestCached } from "./request-cache.js";
36
42
  import { getRequestContext } from "./request-context.js";
43
+ import type { TaxonomyTerm } from "./taxonomies/types.js";
37
44
  import { isMissingTableError } from "./utils/db-errors.js";
38
45
  import {
39
46
  createEditable,
@@ -89,22 +96,17 @@ export type OrderBySpec = Record<string, SortDirection>;
89
96
 
90
97
  export type { WhereRange, WhereValue };
91
98
 
92
- export interface CollectionFilter {
99
+ /**
100
+ * Fields shared by every collection query, independent of pagination mode.
101
+ *
102
+ * Cursor and offset pagination are mutually exclusive, so they live on the
103
+ * `CursorCollectionFilter` / `OffsetCollectionFilter` variants rather than
104
+ * here. Use the {@link CollectionFilter} union for any value that may be
105
+ * either.
106
+ */
107
+ export interface CollectionFilterBase {
93
108
  status?: "draft" | "published" | "archived";
94
109
  limit?: number;
95
- /**
96
- * Opaque cursor for keyset pagination.
97
- * Pass the `nextCursor` value from a previous result to fetch the next page.
98
- * @example
99
- * ```ts
100
- * const cursor = Astro.url.searchParams.get("cursor") ?? undefined;
101
- * const { entries, nextCursor } = await getEmDashCollection("posts", {
102
- * limit: 10,
103
- * cursor,
104
- * });
105
- * ```
106
- */
107
- cursor?: string;
108
110
  /**
109
111
  * Filter by field values, taxonomy terms, byline credits, or ranges.
110
112
  *
@@ -140,6 +142,56 @@ export interface CollectionFilter {
140
142
  locale?: string;
141
143
  }
142
144
 
145
+ /** Keyset-paginated query filter. Cannot also carry an `offset`. */
146
+ export interface CursorCollectionFilter extends CollectionFilterBase {
147
+ /**
148
+ * Opaque cursor for keyset pagination.
149
+ * Pass the `nextCursor` value from a previous result to fetch the next page.
150
+ * @example
151
+ * ```ts
152
+ * const cursor = Astro.url.searchParams.get("cursor") ?? undefined;
153
+ * const { entries, nextCursor } = await getEmDashCollection("posts", {
154
+ * limit: 10,
155
+ * cursor,
156
+ * });
157
+ * ```
158
+ */
159
+ cursor?: string;
160
+ offset?: never;
161
+ }
162
+
163
+ /** Offset-paginated query filter. Cannot also carry a `cursor`. */
164
+ export interface OffsetCollectionFilter extends CollectionFilterBase {
165
+ /**
166
+ * Skip this many entries before returning results (offset pagination).
167
+ *
168
+ * Use with `limit` to render numbered archive routes like `/page/2`
169
+ * without walking cursors or over-fetching from the start:
170
+ *
171
+ * ```ts
172
+ * const perPage = 20;
173
+ * const { entries, hasMore } = await getEmDashCollection("posts", {
174
+ * limit: perPage,
175
+ * offset: (page - 1) * perPage,
176
+ * orderBy: { published_at: "desc" },
177
+ * });
178
+ * ```
179
+ *
180
+ * Only a positive integer applies.
181
+ */
182
+ offset?: number;
183
+ cursor?: never;
184
+ }
185
+
186
+ /**
187
+ * Filter for `getEmDashCollection`.
188
+ *
189
+ * A union of the cursor and offset pagination variants: supplying both
190
+ * `cursor` and `offset` is a compile-time error, since they are mutually
191
+ * exclusive ways to express "the next page" (cursor wins at runtime).
192
+ */
193
+ export type CollectionFilter = CursorCollectionFilter | OffsetCollectionFilter;
194
+
143
195
  export interface ContentEntry<T = Record<string, unknown>> {
144
196
  id: string;
145
197
  data: T;
@@ -169,6 +221,12 @@ export interface CollectionResult<T> {
169
221
  * Pass this as `cursor` in the next query to get the next page.
170
222
  */
171
223
  nextCursor?: string;
224
+ /**
225
+ * Whether more entries exist beyond this page. Set whenever `limit` is
226
+ * provided (cursor or offset pagination), so numbered archive routes can
227
+ * render a "next page" link without computing a total count.
228
+ */
229
+ hasMore?: boolean;
172
230
  }
173
231
 
174
232
  /**
@@ -343,6 +401,7 @@ export async function getEmDashCollection<T extends string, D = InferCollectionD
343
401
  interface CachedCollectionValue {
344
402
  entries: unknown[];
345
403
  nextCursor?: string;
404
+ hasMore?: boolean;
346
405
  cacheHint: CacheHint;
347
406
  }
348
407
 
@@ -370,6 +429,7 @@ async function loadCollectionCached<T extends string, D = InferCollectionData<T>
370
429
  value: {
371
430
  entries: result.entries.map(entrySnapshot),
372
431
  nextCursor: result.nextCursor,
432
+ hasMore: result.hasMore,
373
433
  cacheHint: result.cacheHint,
374
434
  },
375
435
  };
@@ -383,6 +443,7 @@ async function loadCollectionCached<T extends string, D = InferCollectionData<T>
383
443
  return {
384
444
  entries: snapshot.value.entries.map((entry) => reviveEntry<D>(entry)),
385
445
  nextCursor: snapshot.value.nextCursor,
446
+ hasMore: snapshot.value.hasMore,
386
447
  cacheHint: snapshot.value.cacheHint,
387
448
  };
388
449
  }
@@ -410,7 +471,10 @@ export function bucketFilter(filter: CollectionFilter | undefined): BucketedFilt
410
471
  limit === undefined ||
411
472
  limit >= BUCKET_LIMIT_THRESHOLD ||
412
473
  limit <= 0 ||
413
- filter?.cursor !== undefined
474
+ filter?.cursor !== undefined ||
475
+ // Offset paginates a deliberate page window; its limit is part of the
476
+ // pagination contract, so don't round it up the way "recent N" widgets get.
477
+ filter?.offset !== undefined
414
478
  ) {
415
479
  return { fetchFilter: filter, requestedLimit: undefined };
416
480
  }
@@ -442,7 +506,8 @@ export function sliceCollectionResult<D>(
442
506
  // buildCursorCondition in loader.ts — it filters strictly past this row.
443
507
  const lastEntry = sliced.at(-1);
444
508
  const nextCursor = lastEntry ? encodeEntryCursor(lastEntry, orderBy) : undefined;
445
- return { ...cached, entries: sliced, nextCursor };
509
+ // Truncating to the requested limit means at least one more entry existed.
510
+ return { ...cached, entries: sliced, nextCursor, hasMore: true };
446
511
  }
447
512
 
448
513
  /** Map of database column names to camelCase keys present on entry.data. */
@@ -526,6 +591,7 @@ function collectionCacheKey(type: string, filter?: CollectionFilter): string {
526
591
  filter.status ?? "",
527
592
  filter.limit ?? "",
528
593
  filter.cursor ?? "",
594
+ filter.offset ?? "",
529
595
  filter.where ? stableStringify(filter.where) : "",
530
596
  filter.orderBy ? JSON.stringify(filter.orderBy) : "",
531
597
  filter.locale ?? "",
@@ -626,11 +692,19 @@ async function getEmDashCollectionUncached<T extends string, D = InferCollection
626
692
  filter?.locale ?? ctx?.locale ?? (isI18nEnabled() ? i18nConfig!.defaultLocale : undefined);
627
693
 
628
694
  const requestedLimit = filter?.limit;
695
+ // cursor and offset are mutually exclusive on the loader filter union, so
696
+ // spread only the one in play (cursor wins) rather than emitting both keys.
697
+ const pageParam =
698
+ filter?.cursor !== undefined
699
+ ? { cursor: filter.cursor }
700
+ : filter?.offset !== undefined
701
+ ? { offset: filter.offset }
702
+ : {};
629
703
  const result = await getLiveCollection(COLLECTION_NAME, {
630
704
  type,
631
705
  status: filter?.status,
632
706
  limit: requestedLimit && requestedLimit > 0 ? requestedLimit + 1 : filter?.limit,
633
- cursor: filter?.cursor,
707
+ ...pageParam,
634
708
  where: filter?.where,
635
709
  orderBy: filter?.orderBy,
636
710
  locale: resolvedLocale,
@@ -645,6 +719,9 @@ async function getEmDashCollectionUncached<T extends string, D = InferCollection
645
719
  const hasMore = requestedLimit != null && requestedLimit > 0 && entries.length > requestedLimit;
646
720
  const pageEntries = hasMore ? entries.slice(0, requestedLimit) : entries;
647
721
  const nextCursor = hasMore ? encodeEntryCursor(pageEntries.at(-1), filter?.orderBy) : undefined;
722
+ // `hasMore` is only meaningful when a limit bounds the page; otherwise the
723
+ // query returned everything and there is no "next page" to report.
724
+ const hasMoreResult = requestedLimit != null && requestedLimit > 0 ? hasMore : undefined;
648
725
 
649
726
  const isEditMode = ctx?.editMode ?? false;
650
727
  const entriesWithEdit = pageEntries.map((entry: ContentEntry<D>) => {
@@ -668,7 +745,12 @@ async function getEmDashCollectionUncached<T extends string, D = InferCollection
668
745
  hydrateEntryTerms(type, entriesWithEdit, resolvedLocale),
669
746
  ]);
670
747
 
671
- return { entries: entriesWithEdit, nextCursor, cacheHint: cacheHint ?? {} };
748
+ return {
749
+ entries: entriesWithEdit,
750
+ nextCursor,
751
+ hasMore: hasMoreResult,
752
+ cacheHint: cacheHint ?? {},
753
+ };
672
754
  }
673
755
 
674
756
  /**
@@ -930,6 +1012,67 @@ interface CachedEntryValue {
930
1012
  async function hydrateEntryBylines<D>(type: string, entries: ContentEntry<D>[]): Promise<void> {
931
1013
  if (entries.length === 0) return;
932
1014
 
1015
+ // Fast path: bylines were folded into the content query. Parse the JSON
1016
+ // (no extra round trip) for the common case — explicit credits, no byline
1017
+ // custom fields, no author fallback. The query path below handles the rest:
1018
+ // - author fallback (entry has authorId but no explicit credit), and
1019
+ // - custom byline fields (can't be expressed in the folded subquery).
1020
+ if (entries.every((e) => FOLDED_BYLINES in entryData(e))) {
1021
+ const parsed = entries.map((entry) => {
1022
+ const data = entryData(entry);
1023
+ const folded = Reflect.get(data, FOLDED_BYLINES);
1024
+ const rows = Array.isArray(folded) ? folded : [];
1025
+ const credits = rows
1026
+ .map((raw) => {
1027
+ const b = raw?.byline ?? {};
1028
+ return {
1029
+ roleLabel: raw?.roleLabel ?? null,
1030
+ sortOrder: Number(raw?.sortOrder ?? 0),
1031
+ source: "explicit" as const,
1032
+ byline: { ...b, isGuest: Boolean(b.isGuest), customFields: {} },
1033
+ };
1034
+ })
1035
+ .toSorted((a, b) => a.sortOrder - b.sortOrder);
1036
+ return { data, credits };
1037
+ });
1038
+
1039
+ // Fall back to the full query path when the fold can't be trusted to be
1040
+ // complete: an entry with a byline reference (explicit primary, or an
1041
+ // author for the author-fallback) but no folded credits — e.g. a credit
1042
+ // in a different locale than the row, which the locale-correlated subquery
1043
+ // skips, or the author-fallback path which the fold doesn't express.
1044
+ let needsQueryPath = parsed.some(
1045
+ (p) =>
1046
+ p.credits.length === 0 &&
1047
+ (dataStr(p.data, "authorId") !== "" || dataStr(p.data, "primaryBylineId") !== ""),
1048
+ );
1049
+ let hasCustomFields = false;
1050
+ if (!needsQueryPath) {
1051
+ try {
1052
+ const { getDb } = await import("./loader.js");
1053
+ const db = await getDb();
1054
+ const { getBylineFieldDefs } = await import("./bylines/field-defs-cache.js");
1055
+ hasCustomFields = (await getBylineFieldDefs(db)).length > 0;
1056
+ } catch (error) {
1057
+ // A missing table is expected pre-migration and means there are no
1058
+ // custom fields — the fold's values are complete. Any other error
1059
+ // (lock-init failure, dialect error) means the probe can't be
1060
+ // trusted, so fall back to the query path rather than risk serving
1061
+ // folded bylines with empty customFields.
1062
+ if (!isMissingTableError(error)) needsQueryPath = true;
1063
+ }
1064
+ }
1065
+
1066
+ if (!needsQueryPath && !hasCustomFields) {
1067
+ for (const p of parsed) {
1068
+ p.data.bylines = p.credits;
1069
+ p.data.byline = p.credits[0]?.byline ?? null;
1070
+ }
1071
+ return;
1072
+ }
1073
+ // Fall through to the full query path for fallback / custom-field cases.
1074
+ }
1075
+
933
1076
  try {
934
1077
  const { getBylinesForEntries } = await import("./bylines/index.js");
935
1078
 
@@ -1005,6 +1148,45 @@ async function hydrateEntryTerms<D>(
1005
1148
  ): Promise<void> {
1006
1149
  if (entries.length === 0) return;
1007
1150
 
1151
+ // Fast path: terms were folded into the content query. Group the JSON and
1152
+ // skip the separate content_taxonomies query.
1153
+ if (entries.every((e) => FOLDED_TERMS in entryData(e))) {
1154
+ const perEntry: Array<{ entryId: string; byTaxonomy: Record<string, TaxonomyTerm[]> }> = [];
1155
+ for (const entry of entries) {
1156
+ const data = entryData(entry);
1157
+ const folded = Reflect.get(data, FOLDED_TERMS);
1158
+ const rows = Array.isArray(folded) ? folded : [];
1159
+ const grouped: Record<string, TaxonomyTerm[]> = {};
1160
+ for (const r of rows) {
1161
+ const name = String(r?.name);
1162
+ (grouped[name] ??= []).push({
1163
+ id: r?.id,
1164
+ name,
1165
+ slug: r?.slug,
1166
+ label: r?.label,
1167
+ parentId: r?.parent_id ?? undefined,
1168
+ children: [],
1169
+ locale: r?.locale,
1170
+ translationGroup: r?.translation_group,
1171
+ });
1172
+ }
1173
+ // Match getAllTermsForEntries' ORDER BY label (dropped from the
1174
+ // aggregate since SQLite and Postgres order it differently).
1175
+ for (const [name, arr] of Object.entries(grouped)) {
1176
+ grouped[name] = arr.toSorted((a, b) => String(a.label).localeCompare(String(b.label)));
1177
+ }
1178
+ data.terms = grouped;
1179
+ const entryId = dataStr(data, "id");
1180
+ if (entryId) perEntry.push({ entryId, byTaxonomy: grouped });
1181
+ }
1182
+ // Prime the per-entry request cache (wildcard + present taxonomies) so
1183
+ // subsequent getEntryTerms(...) calls in this render hit the cache instead
1184
+ // of issuing an N+1 query. No DB lookup — purely from the folded data.
1185
+ const { primeFoldedEntryTerms } = await import("./taxonomies/index.js");
1186
+ primeFoldedEntryTerms(type, perEntry, { locale });
1187
+ return;
1188
+ }
1189
+
1008
1190
  try {
1009
1191
  const { getAllTermsForEntries } = await import("./taxonomies/index.js");
1010
1192