emdash 0.22.0 → 0.23.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 (507) 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 +11 -11
  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-BQ_yKRdG.mjs} +12 -12
  10. package/dist/{api-BDuM4bEy.mjs.map → api-BQ_yKRdG.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-CWRyxIoO.mjs} +12 -12
  14. package/dist/{apply-wDqQoxX4.mjs.map → apply-CWRyxIoO.mjs.map} +1 -1
  15. package/dist/astro/index.d.mts +11 -11
  16. package/dist/astro/index.mjs +12 -6
  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 +6 -6
  20. package/dist/astro/middleware/redirect.mjs +3 -3
  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 +349 -128
  28. package/dist/astro/middleware.mjs.map +1 -1
  29. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +5 -5
  30. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +5 -5
  31. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +3 -3
  32. package/dist/astro/routes/api/admin/api-tokens/index.mjs +4 -4
  33. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +3 -3
  34. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +7 -7
  35. package/dist/astro/routes/api/admin/byline-fields/index.mjs +7 -7
  36. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +7 -7
  37. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +12 -11
  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 +12 -11
  40. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs.map +1 -1
  41. package/dist/astro/routes/api/admin/bylines/index.mjs +12 -11
  42. package/dist/astro/routes/api/admin/bylines/index.mjs.map +1 -1
  43. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +9 -9
  44. package/dist/astro/routes/api/admin/comments/_id_.mjs +3 -3
  45. package/dist/astro/routes/api/admin/comments/bulk.mjs +7 -7
  46. package/dist/astro/routes/api/admin/comments/counts.mjs +3 -3
  47. package/dist/astro/routes/api/admin/comments/index.mjs +7 -7
  48. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +3 -3
  49. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +2 -2
  50. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +3 -3
  51. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +3 -3
  52. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +27 -26
  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 +27 -26
  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 +26 -25
  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 +26 -25
  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 +26 -25
  61. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
  62. package/dist/astro/routes/api/admin/plugins/index.mjs +26 -25
  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 +2 -2
  65. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +26 -25
  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 +26 -25
  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 +26 -25
  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 +26 -25
  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 +27 -26
  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 +26 -25
  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 +27 -26
  78. package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
  79. package/dist/astro/routes/api/admin/plugins/updates.mjs +26 -25
  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 +26 -25
  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 +2 -2
  84. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +26 -25
  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 +1 -1
  87. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +1 -1
  88. package/dist/astro/routes/api/admin/users/_id_/index.mjs +5 -5
  89. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +2 -2
  90. package/dist/astro/routes/api/admin/users/index.mjs +5 -5
  91. package/dist/astro/routes/api/auth/dev-bypass.mjs +3 -3
  92. package/dist/astro/routes/api/auth/invite/accept.mjs +1 -1
  93. package/dist/astro/routes/api/auth/invite/complete.mjs +9 -9
  94. package/dist/astro/routes/api/auth/invite/index.mjs +6 -6
  95. package/dist/astro/routes/api/auth/invite/register-options.mjs +8 -8
  96. package/dist/astro/routes/api/auth/logout.mjs +2 -2
  97. package/dist/astro/routes/api/auth/magic-link/send.mjs +8 -8
  98. package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
  99. package/dist/astro/routes/api/auth/me.mjs +5 -5
  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 +5 -5
  104. package/dist/astro/routes/api/auth/passkey/index.mjs +1 -1
  105. package/dist/astro/routes/api/auth/passkey/options.mjs +10 -10
  106. package/dist/astro/routes/api/auth/passkey/register/options.mjs +8 -8
  107. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +9 -9
  108. package/dist/astro/routes/api/auth/passkey/verify.mjs +9 -9
  109. package/dist/astro/routes/api/auth/signup/complete.mjs +9 -9
  110. package/dist/astro/routes/api/auth/signup/request.mjs +8 -8
  111. package/dist/astro/routes/api/auth/signup/verify.mjs +1 -1
  112. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +10 -10
  113. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +9 -9
  114. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +2 -2
  115. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +2 -2
  116. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +2 -2
  117. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +2 -2
  118. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +9 -9
  119. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +6 -6
  120. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +2 -2
  121. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +2 -2
  122. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +6 -6
  123. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +9 -9
  124. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +2 -2
  125. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +2 -2
  126. package/dist/astro/routes/api/content/_collection_/_id_.mjs +6 -6
  127. package/dist/astro/routes/api/content/_collection_/authors.mjs +2 -2
  128. package/dist/astro/routes/api/content/_collection_/index.mjs +6 -6
  129. package/dist/astro/routes/api/content/_collection_/trash.mjs +6 -6
  130. package/dist/astro/routes/api/dashboard.mjs +3 -3
  131. package/dist/astro/routes/api/dev/emails.mjs +2 -2
  132. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  133. package/dist/astro/routes/api/import/probe.mjs +10 -10
  134. package/dist/astro/routes/api/import/wordpress/analyze.mjs +2 -2
  135. package/dist/astro/routes/api/import/wordpress/execute.d.mts +9 -9
  136. package/dist/astro/routes/api/import/wordpress/execute.mjs +8 -7
  137. package/dist/astro/routes/api/import/wordpress/execute.mjs.map +1 -1
  138. package/dist/astro/routes/api/import/wordpress/media.mjs +8 -8
  139. package/dist/astro/routes/api/import/wordpress/prepare.mjs +8 -8
  140. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +8 -8
  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 +10 -10
  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 +12 -11
  145. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
  146. package/dist/astro/routes/api/manifest.mjs +3 -3
  147. package/dist/astro/routes/api/mcp.mjs +29 -29
  148. package/dist/astro/routes/api/media/_id_/confirm.mjs +6 -6
  149. package/dist/astro/routes/api/media/_id_.mjs +6 -6
  150. package/dist/astro/routes/api/media/file/_...key_.mjs +1 -1
  151. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +2 -2
  152. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +2 -2
  153. package/dist/astro/routes/api/media/providers/index.mjs +2 -2
  154. package/dist/astro/routes/api/media/upload-url.mjs +8 -8
  155. package/dist/astro/routes/api/media.mjs +10 -10
  156. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +6 -6
  157. package/dist/astro/routes/api/menus/_name_/items.mjs +6 -6
  158. package/dist/astro/routes/api/menus/_name_/reorder.mjs +6 -6
  159. package/dist/astro/routes/api/menus/_name_/translations.mjs +6 -6
  160. package/dist/astro/routes/api/menus/_name_.mjs +6 -6
  161. package/dist/astro/routes/api/menus/index.mjs +6 -6
  162. package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
  163. package/dist/astro/routes/api/oauth/device/authorize.mjs +5 -5
  164. package/dist/astro/routes/api/oauth/device/code.mjs +8 -8
  165. package/dist/astro/routes/api/oauth/device/token.mjs +7 -7
  166. package/dist/astro/routes/api/oauth/register.mjs +2 -2
  167. package/dist/astro/routes/api/oauth/token/refresh.mjs +5 -5
  168. package/dist/astro/routes/api/oauth/token/revoke.mjs +5 -5
  169. package/dist/astro/routes/api/oauth/token.mjs +5 -5
  170. package/dist/astro/routes/api/openapi.json.mjs +3 -3
  171. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +3 -3
  172. package/dist/astro/routes/api/redirects/404s/index.mjs +7 -7
  173. package/dist/astro/routes/api/redirects/404s/summary.mjs +7 -7
  174. package/dist/astro/routes/api/redirects/_id_.mjs +8 -8
  175. package/dist/astro/routes/api/redirects/index.mjs +8 -8
  176. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +2 -2
  177. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +2 -2
  178. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +26 -25
  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 +26 -25
  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 +26 -25
  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 +26 -25
  185. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
  186. package/dist/astro/routes/api/schema/collections/index.mjs +26 -25
  187. package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
  188. package/dist/astro/routes/api/schema/index.mjs +7 -6
  189. package/dist/astro/routes/api/schema/index.mjs.map +1 -1
  190. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +26 -25
  191. package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
  192. package/dist/astro/routes/api/schema/orphans/index.mjs +26 -25
  193. package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
  194. package/dist/astro/routes/api/search/enable.mjs +9 -9
  195. package/dist/astro/routes/api/search/index.mjs +8 -8
  196. package/dist/astro/routes/api/search/rebuild.mjs +9 -9
  197. package/dist/astro/routes/api/search/stats.mjs +5 -5
  198. package/dist/astro/routes/api/search/suggest.mjs +8 -8
  199. package/dist/astro/routes/api/sections/_slug_.mjs +8 -8
  200. package/dist/astro/routes/api/sections/index.mjs +8 -8
  201. package/dist/astro/routes/api/settings/email.mjs +3 -3
  202. package/dist/astro/routes/api/settings.mjs +9 -9
  203. package/dist/astro/routes/api/setup/admin-verify.mjs +10 -10
  204. package/dist/astro/routes/api/setup/admin.mjs +9 -9
  205. package/dist/astro/routes/api/setup/dev-bypass.mjs +17 -16
  206. package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
  207. package/dist/astro/routes/api/setup/dev-reset.mjs +1 -1
  208. package/dist/astro/routes/api/setup/index.mjs +18 -17
  209. package/dist/astro/routes/api/setup/index.mjs.map +1 -1
  210. package/dist/astro/routes/api/setup/status.mjs +3 -3
  211. package/dist/astro/routes/api/snapshot.mjs +5 -5
  212. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +10 -10
  213. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +10 -10
  214. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +10 -10
  215. package/dist/astro/routes/api/taxonomies/index.mjs +10 -10
  216. package/dist/astro/routes/api/themes/preview.mjs +4 -4
  217. package/dist/astro/routes/api/typegen.mjs +4 -4
  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 +6 -6
  222. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +8 -8
  223. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +8 -8
  224. package/dist/astro/routes/api/widget-areas/_name_.mjs +4 -4
  225. package/dist/astro/routes/api/widget-areas/index.mjs +8 -8
  226. package/dist/astro/routes/api/widget-components.mjs +2 -2
  227. package/dist/astro/routes/robots.txt.mjs +3 -3
  228. package/dist/astro/routes/sitemap-_collection_.xml.mjs +6 -6
  229. package/dist/astro/routes/sitemap.xml.mjs +4 -4
  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-Cse0k7B0.mjs} +2 -2
  234. package/dist/{authorize-C0EYoUeV.mjs.map → authorize-Cse0k7B0.mjs.map} +1 -1
  235. package/dist/{byline-CC0WS4Gu.mjs → byline-DVo-liaP.mjs} +15 -73
  236. package/dist/byline-DVo-liaP.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-f5ppBpD5.mjs} +6 -6
  244. package/dist/{bylines-CCO1CYnH.mjs.map → bylines-f5ppBpD5.mjs.map} +1 -1
  245. package/dist/{bylines-DGekMGGm.mjs → bylines-pIKgc_TL.mjs} +2 -2
  246. package/dist/{bylines-DGekMGGm.mjs.map → bylines-pIKgc_TL.mjs.map} +1 -1
  247. package/dist/{cache-pXTpun6s.mjs → cache-Q6V4U-Xz.mjs} +1 -1
  248. package/dist/{cache-pXTpun6s.mjs.map → cache-Q6V4U-Xz.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/cli/index.mjs +16 -15
  252. package/dist/cli/index.mjs.map +1 -1
  253. package/dist/client/cf-access.d.mts +1 -1
  254. package/dist/client/index.d.mts +1 -1
  255. package/dist/client/index.mjs +2 -2
  256. package/dist/{comment-reaction-B6AdcpXw.mjs → comment-reaction-Dyb-AutY.mjs} +1 -1
  257. package/dist/{comment-reaction-B6AdcpXw.mjs.map → comment-reaction-Dyb-AutY.mjs.map} +1 -1
  258. package/dist/{comments-CtPkT2tp.mjs → comments-Cczx7AvD.mjs} +1 -1
  259. package/dist/{comments-CtPkT2tp.mjs.map → comments-Cczx7AvD.mjs.map} +1 -1
  260. package/dist/{components-D6ZnrzbB.mjs → components-CYt4uVK9.mjs} +1 -1
  261. package/dist/{components-D6ZnrzbB.mjs.map → components-CYt4uVK9.mjs.map} +1 -1
  262. package/dist/{context-f1__jpzF.mjs → context-B1W8soLv.mjs} +58 -20
  263. package/dist/context-B1W8soLv.mjs.map +1 -0
  264. package/dist/{cron-CQPxBjYs.mjs → cron-C5LVoNmP.mjs} +14 -2
  265. package/dist/cron-C5LVoNmP.mjs.map +1 -0
  266. package/dist/{dashboard-BOESNcwE.mjs → dashboard-CYjpQZ6-.mjs} +1 -1
  267. package/dist/{dashboard-BOESNcwE.mjs.map → dashboard-CYjpQZ6-.mjs.map} +1 -1
  268. package/dist/db/index.d.mts +3 -3
  269. package/dist/db/libsql.d.mts +1 -1
  270. package/dist/db/postgres.d.mts +1 -1
  271. package/dist/db/sqlite.d.mts +1 -1
  272. package/dist/{db-errors-CrC5w3Jz.mjs → db-errors-UXB1CK42.mjs} +1 -1
  273. package/dist/{db-errors-CrC5w3Jz.mjs.map → db-errors-UXB1CK42.mjs.map} +1 -1
  274. package/dist/{default-CpBEPn6a.mjs → default-CeRG-Ot4.mjs} +1 -1
  275. package/dist/{default-CpBEPn6a.mjs.map → default-CeRG-Ot4.mjs.map} +1 -1
  276. package/dist/{device-flow-Bn-qiDrw.mjs → device-flow-DENDCQ9F.mjs} +4 -4
  277. package/dist/{device-flow-Bn-qiDrw.mjs.map → device-flow-DENDCQ9F.mjs.map} +1 -1
  278. package/dist/{email-console-B9VS6eN4.mjs → email-console-DJP32ucW.mjs} +1 -1
  279. package/dist/{email-console-B9VS6eN4.mjs.map → email-console-DJP32ucW.mjs.map} +1 -1
  280. package/dist/{error-BQ1pxs0L.mjs → error-Bl6kZgtt.mjs} +1 -1
  281. package/dist/{error-BQ1pxs0L.mjs.map → error-Bl6kZgtt.mjs.map} +1 -1
  282. package/dist/{escape-BtbJWyaW.mjs → escape-Bjio4ZsM.mjs} +1 -1
  283. package/dist/{escape-BtbJWyaW.mjs.map → escape-Bjio4ZsM.mjs.map} +1 -1
  284. package/dist/field-defs-cache-ztrdzUGb.mjs +77 -0
  285. package/dist/field-defs-cache-ztrdzUGb.mjs.map +1 -0
  286. package/dist/{fts-manager-DtiWqwNJ.mjs → fts-manager-CQuHt-vS.mjs} +1 -1
  287. package/dist/{fts-manager-DtiWqwNJ.mjs.map → fts-manager-CQuHt-vS.mjs.map} +1 -1
  288. package/dist/{hash-BowcuPwv.mjs → hash-DrvzIXcz.mjs} +1 -1
  289. package/dist/{hash-BowcuPwv.mjs.map → hash-DrvzIXcz.mjs.map} +1 -1
  290. package/dist/{import-DqSGOlmR.mjs → import-Bb1T9WJS.mjs} +3 -3
  291. package/dist/{import-DqSGOlmR.mjs.map → import-Bb1T9WJS.mjs.map} +1 -1
  292. package/dist/{index-C2dLNT6S.d.mts → index-BaIj5hIq.d.mts} +39 -17
  293. package/dist/index-BaIj5hIq.d.mts.map +1 -0
  294. package/dist/{index-ZDEwR7qW.d.mts → index-qbL02uhs.d.mts} +3 -3
  295. package/dist/{index-ZDEwR7qW.d.mts.map → index-qbL02uhs.d.mts.map} +1 -1
  296. package/dist/index.d.mts +17 -17
  297. package/dist/index.mjs +46 -45
  298. package/dist/{load-Dtoo7KcJ.mjs → load-BNo-ER8C.mjs} +1 -1
  299. package/dist/{load-Dtoo7KcJ.mjs.map → load-BNo-ER8C.mjs.map} +1 -1
  300. package/dist/{loader-C8Z48Uof.mjs → loader-Dn74CG1b.mjs} +68 -9
  301. package/dist/loader-Dn74CG1b.mjs.map +1 -0
  302. package/dist/{manifest-schema-DDSbwkAL.mjs → manifest-schema-BWKnQYQF.mjs} +1 -1
  303. package/dist/{manifest-schema-DDSbwkAL.mjs.map → manifest-schema-BWKnQYQF.mjs.map} +1 -1
  304. package/dist/media/image-endpoint.mjs +1 -1
  305. package/dist/media/index.d.mts +1 -1
  306. package/dist/media/index.mjs +2 -2
  307. package/dist/media/local-runtime.d.mts +19 -11
  308. package/dist/media/local-runtime.d.mts.map +1 -1
  309. package/dist/media/local-runtime.mjs +9 -7
  310. package/dist/media/local-runtime.mjs.map +1 -1
  311. package/dist/{media-allowlist-DunzrKFM.mjs → media-allowlist-B77I5_bw.mjs} +2 -2
  312. package/dist/{media-allowlist-DunzrKFM.mjs.map → media-allowlist-B77I5_bw.mjs.map} +1 -1
  313. package/dist/{media-url-S22B6aPr.mjs → media-url-O4rm9-SQ.mjs} +1 -1
  314. package/dist/{media-url-S22B6aPr.mjs.map → media-url-O4rm9-SQ.mjs.map} +1 -1
  315. package/dist/{menus-DOs1MQJg.mjs → menus-DS8VTZgo.mjs} +56 -13
  316. package/dist/menus-DS8VTZgo.mjs.map +1 -0
  317. package/dist/{mime-DYpsOAny.mjs → mime-BTzvzJ8M.mjs} +1 -1
  318. package/dist/{mime-DYpsOAny.mjs.map → mime-BTzvzJ8M.mjs.map} +1 -1
  319. package/dist/{mode-CFh8Dw8G.mjs → mode-BB0F8xTC.mjs} +1 -1
  320. package/dist/{mode-CFh8Dw8G.mjs.map → mode-BB0F8xTC.mjs.map} +1 -1
  321. package/dist/{normalize-Uam4_Vkr.mjs → normalize-ClbNIenm.mjs} +1 -1
  322. package/dist/{normalize-Uam4_Vkr.mjs.map → normalize-ClbNIenm.mjs.map} +1 -1
  323. package/dist/{oauth-authorization-BN-t83Uy.mjs → oauth-authorization-CsvzIp_F.mjs} +4 -4
  324. package/dist/{oauth-authorization-BN-t83Uy.mjs.map → oauth-authorization-CsvzIp_F.mjs.map} +1 -1
  325. package/dist/{oauth-clients-R6I_V0qz.mjs → oauth-clients-C9SYwEbZ.mjs} +1 -1
  326. package/dist/{oauth-clients-R6I_V0qz.mjs.map → oauth-clients-C9SYwEbZ.mjs.map} +1 -1
  327. package/dist/{oauth-state-store-Csoc6kkR.mjs → oauth-state-store---zrApfB.mjs} +1 -1
  328. package/dist/{oauth-state-store-Csoc6kkR.mjs.map → oauth-state-store---zrApfB.mjs.map} +1 -1
  329. package/dist/{oauth-user-lookup-d7VYjTpx.mjs → oauth-user-lookup-SHsWRlG9.mjs} +1 -1
  330. package/dist/{oauth-user-lookup-d7VYjTpx.mjs.map → oauth-user-lookup-SHsWRlG9.mjs.map} +1 -1
  331. package/dist/object-cache/memory.d.mts +1 -1
  332. package/dist/{options-DFFpvNJU.d.mts → options-CqECG0gM.d.mts} +3 -3
  333. package/dist/{options-DFFpvNJU.d.mts.map → options-CqECG0gM.d.mts.map} +1 -1
  334. package/dist/page/index.d.mts +2 -2
  335. package/dist/{parse-qrHlnzTy.mjs → parse-C9hpjUyY.mjs} +2 -2
  336. package/dist/{parse-qrHlnzTy.mjs.map → parse-C9hpjUyY.mjs.map} +1 -1
  337. package/dist/{passkey-config-BJTbcnI5.mjs → passkey-config-C0YfSBko.mjs} +1 -1
  338. package/dist/{passkey-config-BJTbcnI5.mjs.map → passkey-config-C0YfSBko.mjs.map} +1 -1
  339. package/dist/{placeholder-CIDCpyEY.mjs → placeholder-B0s2mWu5.mjs} +1 -1
  340. package/dist/{placeholder-CIDCpyEY.mjs.map → placeholder-B0s2mWu5.mjs.map} +1 -1
  341. package/dist/{placeholder-CirPauNG.d.mts → placeholder-QoI4gEzP.d.mts} +1 -1
  342. package/dist/{placeholder-CirPauNG.d.mts.map → placeholder-QoI4gEzP.d.mts.map} +1 -1
  343. package/dist/plugin-types.d.mts +1 -1
  344. package/dist/plugin-utils.d.mts +9 -9
  345. package/dist/plugins/adapt-sandbox-entry.d.mts +9 -9
  346. package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
  347. package/dist/{portable-text-BIFhrU6T.mjs → portable-text-BICg8bTk.mjs} +1 -1
  348. package/dist/{portable-text-BIFhrU6T.mjs.map → portable-text-BICg8bTk.mjs.map} +1 -1
  349. package/dist/{preview-IDye9SPQ.mjs → preview-DKGCt2_p.mjs} +2 -2
  350. package/dist/{preview-IDye9SPQ.mjs.map → preview-DKGCt2_p.mjs.map} +1 -1
  351. package/dist/{public-url-8lFyQ8ZF.mjs → public-url-CTVqgMmg.mjs} +1 -1
  352. package/dist/{public-url-8lFyQ8ZF.mjs.map → public-url-CTVqgMmg.mjs.map} +1 -1
  353. package/dist/{query-B1RVFUNM.mjs → query-DgUHZLWB.mjs} +79 -8
  354. package/dist/query-DgUHZLWB.mjs.map +1 -0
  355. package/dist/{rate-limit-DfNAqIWW.mjs → rate-limit-BuQ5d4X_.mjs} +2 -2
  356. package/dist/{rate-limit-DfNAqIWW.mjs.map → rate-limit-BuQ5d4X_.mjs.map} +1 -1
  357. package/dist/{redirect-B4wuX2A5.mjs → redirect-CS-PHtNh.mjs} +1 -1
  358. package/dist/{redirect-B4wuX2A5.mjs.map → redirect-CS-PHtNh.mjs.map} +1 -1
  359. package/dist/{redirects-CT_vb3NV.mjs → redirects-BZ8Mxn8H.mjs} +2 -2
  360. package/dist/{redirects-CT_vb3NV.mjs.map → redirects-BZ8Mxn8H.mjs.map} +1 -1
  361. package/dist/{redirects-DUvXgzCX.mjs → redirects-x9eFPeyi.mjs} +4 -2
  362. package/dist/redirects-x9eFPeyi.mjs.map +1 -0
  363. package/dist/{registry-CjK96fHD.mjs → registry-CK1SS6pi.mjs} +2 -2
  364. package/dist/{registry-CjK96fHD.mjs.map → registry-CK1SS6pi.mjs.map} +1 -1
  365. package/dist/{request-meta-ei3ATilC.mjs → request-meta-CmS1tDFf.mjs} +2 -2
  366. package/dist/{request-meta-ei3ATilC.mjs.map → request-meta-CmS1tDFf.mjs.map} +1 -1
  367. package/dist/{resolve-CpbBglV0.mjs → resolve-C7I0qiR0.mjs} +1 -1
  368. package/dist/{resolve-CpbBglV0.mjs.map → resolve-C7I0qiR0.mjs.map} +1 -1
  369. package/dist/{runner-jdXtFEd3.d.mts → runner-CEtq-6Sm.d.mts} +2 -2
  370. package/dist/{runner-jdXtFEd3.d.mts.map → runner-CEtq-6Sm.d.mts.map} +1 -1
  371. package/dist/runtime.d.mts +10 -10
  372. package/dist/runtime.mjs +1 -1
  373. package/dist/{schema-5z2Jfnbm.mjs → schema-CbFRFoM7.mjs} +3 -3
  374. package/dist/{schema-5z2Jfnbm.mjs.map → schema-CbFRFoM7.mjs.map} +1 -1
  375. package/dist/{search-D9FkoM-k.mjs → search-C-waE_YW.mjs} +3 -3
  376. package/dist/{search-D9FkoM-k.mjs.map → search-C-waE_YW.mjs.map} +1 -1
  377. package/dist/{secrets-BUE5UQys.mjs → secrets-BSf9pRRY.mjs} +2 -2
  378. package/dist/{secrets-BUE5UQys.mjs.map → secrets-BSf9pRRY.mjs.map} +1 -1
  379. package/dist/{sections-DWYZo6HK.mjs → sections-osX9U-YJ.mjs} +2 -2
  380. package/dist/{sections-DWYZo6HK.mjs.map → sections-osX9U-YJ.mjs.map} +1 -1
  381. package/dist/seed/index.d.mts +2 -2
  382. package/dist/seed/index.mjs +12 -11
  383. package/dist/seo/index.d.mts +1 -1
  384. package/dist/seo/index.mjs +1 -1
  385. package/dist/{seo-Cu47j3mJ.mjs → seo-4FovMwNS.mjs} +1 -1
  386. package/dist/{seo-Cu47j3mJ.mjs.map → seo-4FovMwNS.mjs.map} +1 -1
  387. package/dist/{seo-DRnqnstF.mjs → seo-DCEF-AVr.mjs} +1 -1
  388. package/dist/{seo-DRnqnstF.mjs.map → seo-DCEF-AVr.mjs.map} +1 -1
  389. package/dist/{service-DTmtqCnw.mjs → service-jby1RRmU.mjs} +2 -2
  390. package/dist/{service-DTmtqCnw.mjs.map → service-jby1RRmU.mjs.map} +1 -1
  391. package/dist/{session-user-DmtPMNa1.mjs → session-user-B8aLtKAH.mjs} +1 -1
  392. package/dist/{session-user-DmtPMNa1.mjs.map → session-user-B8aLtKAH.mjs.map} +1 -1
  393. package/dist/{settings-DUrVDN0c.mjs → settings-4bRT2NIf.mjs} +2 -2
  394. package/dist/{settings-DUrVDN0c.mjs.map → settings-4bRT2NIf.mjs.map} +1 -1
  395. package/dist/{settings-yaxu9mU-.mjs → settings-t-H2jPuJ.mjs} +3 -3
  396. package/dist/{settings-yaxu9mU-.mjs.map → settings-t-H2jPuJ.mjs.map} +1 -1
  397. package/dist/{setup-complete-BWFHzgvZ.mjs → setup-complete-gEiySUc-.mjs} +1 -1
  398. package/dist/{setup-complete-BWFHzgvZ.mjs.map → setup-complete-gEiySUc-.mjs.map} +1 -1
  399. package/dist/{setup-nonce-DFWG-J5c.mjs → setup-nonce-DzS50zme.mjs} +1 -1
  400. package/dist/{setup-nonce-DFWG-J5c.mjs.map → setup-nonce-DzS50zme.mjs.map} +1 -1
  401. package/dist/{single-flight-cache-B5p5cNOL.mjs → single-flight-cache-Cdfkic3t.mjs} +1 -1
  402. package/dist/{single-flight-cache-B5p5cNOL.mjs.map → single-flight-cache-Cdfkic3t.mjs.map} +1 -1
  403. package/dist/{site-url-BOTPgmeM.mjs → site-url-BLebyON8.mjs} +1 -1
  404. package/dist/{site-url-BOTPgmeM.mjs.map → site-url-BLebyON8.mjs.map} +1 -1
  405. package/dist/{ssrf-B0VeRLrp.mjs → ssrf-BRKb343l.mjs} +1 -1
  406. package/dist/{ssrf-B0VeRLrp.mjs.map → ssrf-BRKb343l.mjs.map} +1 -1
  407. package/dist/{status-CB2basWJ.mjs → status-vUK0SA17.mjs} +1 -1
  408. package/dist/{status-CB2basWJ.mjs.map → status-vUK0SA17.mjs.map} +1 -1
  409. package/dist/storage/local.d.mts +1 -1
  410. package/dist/storage/local.mjs +1 -1
  411. package/dist/storage/s3.d.mts +1 -1
  412. package/dist/storage/s3.mjs +1 -1
  413. package/dist/{taxonomies-CJmkEpdS.mjs → taxonomies-BBB8O-CU.mjs} +2 -2
  414. package/dist/{taxonomies-CJmkEpdS.mjs.map → taxonomies-BBB8O-CU.mjs.map} +1 -1
  415. package/dist/{taxonomies-Be9B1jEj.mjs → taxonomies-Dxrc_szG.mjs} +26 -6
  416. package/dist/{taxonomies-Be9B1jEj.mjs.map → taxonomies-Dxrc_szG.mjs.map} +1 -1
  417. package/dist/{tokens-BvrJvIB6.mjs → tokens-LeuXF9gG.mjs} +1 -1
  418. package/dist/{tokens-BvrJvIB6.mjs.map → tokens-LeuXF9gG.mjs.map} +1 -1
  419. package/dist/{transport-k7YjfmEn.d.mts → transport-BZ-03aFT.d.mts} +1 -1
  420. package/dist/{transport-k7YjfmEn.d.mts.map → transport-BZ-03aFT.d.mts.map} +1 -1
  421. package/dist/{transport-B4rXnSTf.mjs → transport-_2nBz7e9.mjs} +1 -1
  422. package/dist/{transport-B4rXnSTf.mjs.map → transport-_2nBz7e9.mjs.map} +1 -1
  423. package/dist/{trusted-proxy-DY1_UqHr.mjs → trusted-proxy-DZY5WCn2.mjs} +1 -1
  424. package/dist/{trusted-proxy-DY1_UqHr.mjs.map → trusted-proxy-DZY5WCn2.mjs.map} +1 -1
  425. package/dist/{types-Czg1S3gB.d.mts → types-B-gIxFCE.d.mts} +1 -1
  426. package/dist/{types-Czg1S3gB.d.mts.map → types-B-gIxFCE.d.mts.map} +1 -1
  427. package/dist/{types-DXLVzV2w.d.mts → types-BG8_XTLj.d.mts} +1 -1
  428. package/dist/{types-DXLVzV2w.d.mts.map → types-BG8_XTLj.d.mts.map} +1 -1
  429. package/dist/{types-CWGYDYqF.d.mts → types-BWh2MjmA.d.mts} +15 -2
  430. package/dist/types-BWh2MjmA.d.mts.map +1 -0
  431. package/dist/{types-CgvhU-hF.d.mts → types-BWqGVhFn.d.mts} +1 -1
  432. package/dist/{types-CgvhU-hF.d.mts.map → types-BWqGVhFn.d.mts.map} +1 -1
  433. package/dist/{types-DYF2_Vf4.mjs → types-CQAugunJ.mjs} +1 -1
  434. package/dist/{types-DYF2_Vf4.mjs.map → types-CQAugunJ.mjs.map} +1 -1
  435. package/dist/{types-DPCpkzvS.d.mts → types-CTLJYMhN.d.mts} +1 -1
  436. package/dist/{types-DPCpkzvS.d.mts.map → types-CTLJYMhN.d.mts.map} +1 -1
  437. package/dist/{types-CmivgZbp.d.mts → types-CUsXGRVj.d.mts} +1 -1
  438. package/dist/{types-CmivgZbp.d.mts.map → types-CUsXGRVj.d.mts.map} +1 -1
  439. package/dist/types-DXOIbece.mjs.map +1 -1
  440. package/dist/{types-CKNXt63n.d.mts → types-DrYMm9s5.d.mts} +1 -1
  441. package/dist/{types-CKNXt63n.d.mts.map → types-DrYMm9s5.d.mts.map} +1 -1
  442. package/dist/{types-CIKX2HO5.d.mts → types-TTx8uP7V.d.mts} +2 -2
  443. package/dist/{types-CIKX2HO5.d.mts.map → types-TTx8uP7V.d.mts.map} +1 -1
  444. package/dist/{types-DCNNfeCv.d.mts → types-fS7-VTdR.d.mts} +1 -1
  445. package/dist/{types-DCNNfeCv.d.mts.map → types-fS7-VTdR.d.mts.map} +1 -1
  446. package/dist/{utils-LbHcSRTj.mjs → utils-B7A57fm9.mjs} +1 -1
  447. package/dist/{utils-LbHcSRTj.mjs.map → utils-B7A57fm9.mjs.map} +1 -1
  448. package/dist/{validate-Bwl64sxv.mjs → validate-0Utjspbs.mjs} +1 -1
  449. package/dist/{validate-Bwl64sxv.mjs.map → validate-0Utjspbs.mjs.map} +1 -1
  450. package/dist/{validate-CE2M0nvx.d.mts → validate-DOFrIb-c.d.mts} +5 -5
  451. package/dist/{validate-CE2M0nvx.d.mts.map → validate-DOFrIb-c.d.mts.map} +1 -1
  452. package/dist/{validation-C_yOvFaO.mjs → validation-D9EnzH1T.mjs} +5 -5
  453. package/dist/{validation-C_yOvFaO.mjs.map → validation-D9EnzH1T.mjs.map} +1 -1
  454. package/dist/version-BgVM_1MU.mjs +7 -0
  455. package/dist/{version-RnYXYjzD.mjs.map → version-BgVM_1MU.mjs.map} +1 -1
  456. package/dist/{widgets-8GONuRHo.mjs → widgets-BSTzPUYa.mjs} +3 -3
  457. package/dist/{widgets-8GONuRHo.mjs.map → widgets-BSTzPUYa.mjs.map} +1 -1
  458. package/dist/{zod-generator-D481JjzD.mjs → zod-generator-ElVQOav7.mjs} +50 -14
  459. package/dist/zod-generator-ElVQOav7.mjs.map +1 -0
  460. package/package.json +8 -7
  461. package/src/api/schemas/bylines.ts +8 -0
  462. package/src/astro/integration/virtual-modules.ts +8 -0
  463. package/src/astro/integration/vite-config.ts +9 -1
  464. package/src/astro/middleware/request-context.ts +6 -1
  465. package/src/astro/middleware/scoped-db.ts +131 -0
  466. package/src/astro/middleware/stream-end-metrics.ts +4 -8
  467. package/src/astro/middleware.ts +90 -28
  468. package/src/astro/routes/admin.astro +5 -1
  469. package/src/astro/routes/api/schema/index.ts +9 -3
  470. package/src/cli/commands/export-seed.ts +10 -7
  471. package/src/components/Block.astro +71 -0
  472. package/src/components/EmDashMedia.astro +1 -2
  473. package/src/components/Image.astro +36 -2
  474. package/src/components/InlinePortableTextEditor.tsx +10 -2
  475. package/src/components/index.ts +5 -0
  476. package/src/components/portable-text-text-align.ts +38 -0
  477. package/src/content/converters/portable-text-to-prosemirror.ts +5 -1
  478. package/src/content/converters/prosemirror-to-portable-text.ts +8 -0
  479. package/src/content/converters/types.ts +1 -0
  480. package/src/database/repositories/byline.ts +14 -0
  481. package/src/database/repositories/types.ts +13 -0
  482. package/src/emdash-runtime.ts +226 -71
  483. package/src/loader.ts +97 -5
  484. package/src/media/local-runtime.ts +18 -5
  485. package/src/plugins/context.ts +94 -22
  486. package/src/plugins/cron.ts +18 -2
  487. package/src/plugins/routes.ts +40 -1
  488. package/src/query.ts +108 -1
  489. package/src/schema/zod-generator.ts +53 -8
  490. package/src/taxonomies/index.ts +26 -0
  491. package/src/ui.ts +5 -0
  492. package/src/virtual-modules.d.ts +13 -0
  493. package/dist/byline-CC0WS4Gu.mjs.map +0 -1
  494. package/dist/byline-fields-jy07PZJM.d.mts.map +0 -1
  495. package/dist/context-f1__jpzF.mjs.map +0 -1
  496. package/dist/cron-CQPxBjYs.mjs.map +0 -1
  497. package/dist/index-C2dLNT6S.d.mts.map +0 -1
  498. package/dist/loader-C8Z48Uof.mjs.map +0 -1
  499. package/dist/menus-DOs1MQJg.mjs.map +0 -1
  500. package/dist/query-B1RVFUNM.mjs.map +0 -1
  501. package/dist/redirects-DUvXgzCX.mjs.map +0 -1
  502. package/dist/types-CWGYDYqF.d.mts.map +0 -1
  503. package/dist/version-RnYXYjzD.mjs +0 -7
  504. package/dist/zod-generator-D481JjzD.mjs.map +0 -1
  505. /package/dist/{api-tokens-BVH-H8Z9.mjs → api-tokens-B4BQybOp.mjs} +0 -0
  506. /package/dist/{ssrf-Cz1e2QPn.mjs → ssrf-CcX9zvMK.mjs} +0 -0
  507. /package/dist/{types-Bfr2Pj6z.mjs → types-BvbeGEtr.mjs} +0 -0
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;
@@ -785,8 +860,14 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
785
860
  )`
786
861
  : sql``;
787
862
 
863
+ // Fold byline + taxonomy hydration into the list query.
864
+ const { terms: termsSelect, bylines: bylinesSelect } = foldedHydrationSelects(
865
+ db,
866
+ type,
867
+ tableName,
868
+ );
788
869
  result = await sql<Record<string, unknown>>`
789
- SELECT * FROM ${sql.ref(tableName)}
870
+ SELECT *, ${termsSelect}, ${bylinesSelect} FROM ${sql.ref(tableName)}
790
871
  WHERE deleted_at IS NULL
791
872
  AND ${statusCondition}
792
873
  ${localeFilter}
@@ -814,11 +895,13 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
814
895
  rowLocale !== "" &&
815
896
  (rowLocale !== i18nConfig.defaultLocale || i18nConfig.prefixDefaultLocale);
816
897
  const id = shouldPrefix ? `${rowLocale}/${slug}` : slug;
898
+ const data = mapRowToData(row);
899
+ stashFolded(data, row);
817
900
  return {
818
901
  id,
819
902
  slug: rowStr(row, "slug"),
820
903
  status: rowStr(row, "status", "draft"),
821
- data: mapRowToData(row),
904
+ data,
822
905
  cacheHint: {
823
906
  tags: [rowStr(row, "id")],
824
907
  lastModified: row.updated_at ? new Date(rowStr(row, "updated_at")) : undefined,
@@ -922,9 +1005,17 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
922
1005
  ([col, alias]) => sql`${sql.ref(`s.${col}`)} AS ${sql.ref(alias)}`,
923
1006
  ),
924
1007
  );
1008
+ // Fold byline + taxonomy hydration into the content query (see
1009
+ // foldedHydrationSelects), removing the two separate hydration
1010
+ // round trips per fetch.
1011
+ const { terms: termsSelect, bylines: bylinesSelect } = foldedHydrationSelects(
1012
+ db,
1013
+ type,
1014
+ "c",
1015
+ );
925
1016
  const result = locale
926
1017
  ? await sql<Record<string, unknown>>`
927
- SELECT c.*, ${seoSelect}
1018
+ SELECT c.*, ${seoSelect}, ${termsSelect}, ${bylinesSelect}
928
1019
  FROM ${sql.ref(tableName)} AS c
929
1020
  LEFT JOIN ${sql.ref("_emdash_seo")} AS s
930
1021
  ON s.collection = ${type} AND s.content_id = c.id
@@ -933,7 +1024,7 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
933
1024
  LIMIT 1
934
1025
  `.execute(db)
935
1026
  : await sql<Record<string, unknown>>`
936
- SELECT c.*, ${seoSelect}
1027
+ SELECT c.*, ${seoSelect}, ${termsSelect}, ${bylinesSelect}
937
1028
  FROM ${sql.ref(tableName)} AS c
938
1029
  LEFT JOIN ${sql.ref("_emdash_seo")} AS s
939
1030
  ON s.collection = ${type} AND s.content_id = c.id
@@ -1015,6 +1106,7 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
1015
1106
  const entryData = mapRowToData(row);
1016
1107
  const entrySeo = extractSeo(row);
1017
1108
  if (entrySeo) entryData.seo = entrySeo;
1109
+ stashFolded(entryData, row);
1018
1110
  return {
1019
1111
  id: entryId,
1020
1112
  slug: rowStr(row, "slug"),
@@ -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,
@@ -930,6 +937,67 @@ interface CachedEntryValue {
930
937
  async function hydrateEntryBylines<D>(type: string, entries: ContentEntry<D>[]): Promise<void> {
931
938
  if (entries.length === 0) return;
932
939
 
940
+ // Fast path: bylines were folded into the content query. Parse the JSON
941
+ // (no extra round trip) for the common case — explicit credits, no byline
942
+ // custom fields, no author fallback. The query path below handles the rest:
943
+ // - author fallback (entry has authorId but no explicit credit), and
944
+ // - custom byline fields (can't be expressed in the folded subquery).
945
+ if (entries.every((e) => FOLDED_BYLINES in entryData(e))) {
946
+ const parsed = entries.map((entry) => {
947
+ const data = entryData(entry);
948
+ const folded = Reflect.get(data, FOLDED_BYLINES);
949
+ const rows = Array.isArray(folded) ? folded : [];
950
+ const credits = rows
951
+ .map((raw) => {
952
+ const b = raw?.byline ?? {};
953
+ return {
954
+ roleLabel: raw?.roleLabel ?? null,
955
+ sortOrder: Number(raw?.sortOrder ?? 0),
956
+ source: "explicit" as const,
957
+ byline: { ...b, isGuest: Boolean(b.isGuest), customFields: {} },
958
+ };
959
+ })
960
+ .toSorted((a, b) => a.sortOrder - b.sortOrder);
961
+ return { data, credits };
962
+ });
963
+
964
+ // Fall back to the full query path when the fold can't be trusted to be
965
+ // complete: an entry with a byline reference (explicit primary, or an
966
+ // author for the author-fallback) but no folded credits — e.g. a credit
967
+ // in a different locale than the row, which the locale-correlated subquery
968
+ // skips, or the author-fallback path which the fold doesn't express.
969
+ let needsQueryPath = parsed.some(
970
+ (p) =>
971
+ p.credits.length === 0 &&
972
+ (dataStr(p.data, "authorId") !== "" || dataStr(p.data, "primaryBylineId") !== ""),
973
+ );
974
+ let hasCustomFields = false;
975
+ if (!needsQueryPath) {
976
+ try {
977
+ const { getDb } = await import("./loader.js");
978
+ const db = await getDb();
979
+ const { getBylineFieldDefs } = await import("./bylines/field-defs-cache.js");
980
+ hasCustomFields = (await getBylineFieldDefs(db)).length > 0;
981
+ } catch (error) {
982
+ // A missing table is expected pre-migration and means there are no
983
+ // custom fields — the fold's values are complete. Any other error
984
+ // (lock-init failure, dialect error) means the probe can't be
985
+ // trusted, so fall back to the query path rather than risk serving
986
+ // folded bylines with empty customFields.
987
+ if (!isMissingTableError(error)) needsQueryPath = true;
988
+ }
989
+ }
990
+
991
+ if (!needsQueryPath && !hasCustomFields) {
992
+ for (const p of parsed) {
993
+ p.data.bylines = p.credits;
994
+ p.data.byline = p.credits[0]?.byline ?? null;
995
+ }
996
+ return;
997
+ }
998
+ // Fall through to the full query path for fallback / custom-field cases.
999
+ }
1000
+
933
1001
  try {
934
1002
  const { getBylinesForEntries } = await import("./bylines/index.js");
935
1003
 
@@ -1005,6 +1073,45 @@ async function hydrateEntryTerms<D>(
1005
1073
  ): Promise<void> {
1006
1074
  if (entries.length === 0) return;
1007
1075
 
1076
+ // Fast path: terms were folded into the content query. Group the JSON and
1077
+ // skip the separate content_taxonomies query.
1078
+ if (entries.every((e) => FOLDED_TERMS in entryData(e))) {
1079
+ const perEntry: Array<{ entryId: string; byTaxonomy: Record<string, TaxonomyTerm[]> }> = [];
1080
+ for (const entry of entries) {
1081
+ const data = entryData(entry);
1082
+ const folded = Reflect.get(data, FOLDED_TERMS);
1083
+ const rows = Array.isArray(folded) ? folded : [];
1084
+ const grouped: Record<string, TaxonomyTerm[]> = {};
1085
+ for (const r of rows) {
1086
+ const name = String(r?.name);
1087
+ (grouped[name] ??= []).push({
1088
+ id: r?.id,
1089
+ name,
1090
+ slug: r?.slug,
1091
+ label: r?.label,
1092
+ parentId: r?.parent_id ?? undefined,
1093
+ children: [],
1094
+ locale: r?.locale,
1095
+ translationGroup: r?.translation_group,
1096
+ });
1097
+ }
1098
+ // Match getAllTermsForEntries' ORDER BY label (dropped from the
1099
+ // aggregate since SQLite and Postgres order it differently).
1100
+ for (const [name, arr] of Object.entries(grouped)) {
1101
+ grouped[name] = arr.toSorted((a, b) => String(a.label).localeCompare(String(b.label)));
1102
+ }
1103
+ data.terms = grouped;
1104
+ const entryId = dataStr(data, "id");
1105
+ if (entryId) perEntry.push({ entryId, byTaxonomy: grouped });
1106
+ }
1107
+ // Prime the per-entry request cache (wildcard + present taxonomies) so
1108
+ // subsequent getEntryTerms(...) calls in this render hit the cache instead
1109
+ // of issuing an N+1 query. No DB lookup — purely from the folded data.
1110
+ const { primeFoldedEntryTerms } = await import("./taxonomies/index.js");
1111
+ primeFoldedEntryTerms(type, perEntry, { locale });
1112
+ return;
1113
+ }
1114
+
1008
1115
  try {
1009
1116
  const { getAllTermsForEntries } = await import("./taxonomies/index.js");
1010
1117