emdash 0.23.0 → 0.24.1

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 (302) hide show
  1. package/dist/api/route-utils.mjs +10 -10
  2. package/dist/api/schemas/index.d.mts +1 -1
  3. package/dist/{api-BQ_yKRdG.mjs → api-d_0wi9fW.mjs} +13 -13
  4. package/dist/{api-BQ_yKRdG.mjs.map → api-d_0wi9fW.mjs.map} +1 -1
  5. package/dist/{apply-CWRyxIoO.mjs → apply-BE9UaWX0.mjs} +16 -16
  6. package/dist/{apply-CWRyxIoO.mjs.map → apply-BE9UaWX0.mjs.map} +1 -1
  7. package/dist/astro/index.d.mts +2 -2
  8. package/dist/astro/index.mjs +25 -6
  9. package/dist/astro/index.mjs.map +1 -1
  10. package/dist/astro/middleware/auth.d.mts +2 -2
  11. package/dist/astro/middleware/auth.mjs +2 -2
  12. package/dist/astro/middleware/redirect.mjs +4 -4
  13. package/dist/astro/middleware.d.mts.map +1 -1
  14. package/dist/astro/middleware.mjs +46 -46
  15. package/dist/astro/middleware.mjs.map +1 -1
  16. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +3 -3
  17. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +3 -3
  18. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +2 -2
  19. package/dist/astro/routes/api/admin/api-tokens/index.mjs +3 -3
  20. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +3 -3
  21. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +4 -4
  22. package/dist/astro/routes/api/admin/byline-fields/index.mjs +4 -4
  23. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +4 -4
  24. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +9 -9
  25. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +9 -9
  26. package/dist/astro/routes/api/admin/bylines/index.mjs +9 -9
  27. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +7 -7
  28. package/dist/astro/routes/api/admin/comments/_id_.mjs +5 -5
  29. package/dist/astro/routes/api/admin/comments/bulk.mjs +6 -6
  30. package/dist/astro/routes/api/admin/comments/counts.mjs +5 -5
  31. package/dist/astro/routes/api/admin/comments/index.mjs +6 -6
  32. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +4 -4
  33. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
  34. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +3 -3
  35. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +3 -3
  36. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +26 -26
  37. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +26 -26
  38. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +26 -26
  39. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +26 -26
  40. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +26 -26
  41. package/dist/astro/routes/api/admin/plugins/index.mjs +26 -26
  42. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
  43. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +26 -26
  44. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +26 -26
  45. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +26 -26
  46. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +26 -26
  47. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +27 -27
  48. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +26 -26
  49. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +27 -27
  50. package/dist/astro/routes/api/admin/plugins/updates.mjs +26 -26
  51. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +26 -26
  52. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +3 -3
  53. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +26 -26
  54. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +2 -2
  55. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
  56. package/dist/astro/routes/api/admin/users/_id_/index.mjs +3 -3
  57. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +2 -2
  58. package/dist/astro/routes/api/admin/users/index.mjs +3 -3
  59. package/dist/astro/routes/api/auth/dev-bypass.mjs +3 -3
  60. package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
  61. package/dist/astro/routes/api/auth/invite/complete.mjs +3 -3
  62. package/dist/astro/routes/api/auth/invite/index.mjs +3 -3
  63. package/dist/astro/routes/api/auth/invite/register-options.mjs +3 -3
  64. package/dist/astro/routes/api/auth/logout.mjs +2 -2
  65. package/dist/astro/routes/api/auth/magic-link/send.mjs +4 -4
  66. package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
  67. package/dist/astro/routes/api/auth/me.mjs +4 -4
  68. package/dist/astro/routes/api/auth/passkey/_id_.mjs +3 -3
  69. package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
  70. package/dist/astro/routes/api/auth/passkey/options.mjs +4 -4
  71. package/dist/astro/routes/api/auth/passkey/register/options.mjs +3 -3
  72. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +3 -3
  73. package/dist/astro/routes/api/auth/passkey/verify.mjs +3 -3
  74. package/dist/astro/routes/api/auth/signup/complete.mjs +3 -3
  75. package/dist/astro/routes/api/auth/signup/request.mjs +4 -4
  76. package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
  77. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +6 -6
  78. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +6 -6
  79. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
  80. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +3 -3
  81. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
  82. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
  83. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +4 -4
  84. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +4 -4
  85. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
  86. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
  87. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +4 -4
  88. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +8 -8
  89. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
  90. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +3 -3
  91. package/dist/astro/routes/api/content/_collection_/_id_.mjs +4 -4
  92. package/dist/astro/routes/api/content/_collection_/authors.mjs +3 -3
  93. package/dist/astro/routes/api/content/_collection_/index.mjs +4 -4
  94. package/dist/astro/routes/api/content/_collection_/trash.mjs +4 -4
  95. package/dist/astro/routes/api/dashboard.mjs +7 -7
  96. package/dist/astro/routes/api/dev/emails.mjs +2 -2
  97. package/dist/astro/routes/api/import/probe.mjs +4 -4
  98. package/dist/astro/routes/api/import/wordpress/analyze.mjs +3 -3
  99. package/dist/astro/routes/api/import/wordpress/execute.d.mts +2 -2
  100. package/dist/astro/routes/api/import/wordpress/execute.mjs +8 -8
  101. package/dist/astro/routes/api/import/wordpress/media.mjs +4 -4
  102. package/dist/astro/routes/api/import/wordpress/prepare.mjs +6 -6
  103. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +5 -5
  104. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +4 -4
  105. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +6 -6
  106. package/dist/astro/routes/api/manifest.mjs +3 -3
  107. package/dist/astro/routes/api/mcp.mjs +34 -34
  108. package/dist/astro/routes/api/media/_id_/confirm.mjs +4 -4
  109. package/dist/astro/routes/api/media/_id_.mjs +4 -4
  110. package/dist/astro/routes/api/media/file/_...key_.mjs +2 -2
  111. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +3 -3
  112. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +3 -3
  113. package/dist/astro/routes/api/media/providers/index.mjs +3 -3
  114. package/dist/astro/routes/api/media/upload-url.mjs +4 -4
  115. package/dist/astro/routes/api/media.mjs +5 -5
  116. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +5 -5
  117. package/dist/astro/routes/api/menus/_name_/items.mjs +5 -5
  118. package/dist/astro/routes/api/menus/_name_/reorder.mjs +5 -5
  119. package/dist/astro/routes/api/menus/_name_/translations.mjs +5 -5
  120. package/dist/astro/routes/api/menus/_name_.mjs +5 -5
  121. package/dist/astro/routes/api/menus/index.mjs +5 -5
  122. package/dist/astro/routes/api/oauth/device/authorize.mjs +3 -3
  123. package/dist/astro/routes/api/oauth/device/code.mjs +4 -4
  124. package/dist/astro/routes/api/oauth/device/token.mjs +4 -4
  125. package/dist/astro/routes/api/oauth/register.mjs +2 -2
  126. package/dist/astro/routes/api/oauth/token/refresh.mjs +3 -3
  127. package/dist/astro/routes/api/oauth/token/revoke.mjs +3 -3
  128. package/dist/astro/routes/api/oauth/token.mjs +2 -2
  129. package/dist/astro/routes/api/openapi.json.mjs +2 -2
  130. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +3 -3
  131. package/dist/astro/routes/api/redirects/404s/index.mjs +6 -6
  132. package/dist/astro/routes/api/redirects/404s/summary.mjs +6 -6
  133. package/dist/astro/routes/api/redirects/_id_.mjs +7 -7
  134. package/dist/astro/routes/api/redirects/index.mjs +7 -7
  135. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
  136. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
  137. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +26 -26
  138. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +26 -26
  139. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +26 -26
  140. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +26 -26
  141. package/dist/astro/routes/api/schema/collections/index.mjs +26 -26
  142. package/dist/astro/routes/api/schema/index.mjs +6 -6
  143. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +26 -26
  144. package/dist/astro/routes/api/schema/orphans/index.mjs +26 -26
  145. package/dist/astro/routes/api/search/enable.mjs +7 -7
  146. package/dist/astro/routes/api/search/index.mjs +6 -6
  147. package/dist/astro/routes/api/search/rebuild.mjs +7 -7
  148. package/dist/astro/routes/api/search/stats.mjs +6 -6
  149. package/dist/astro/routes/api/search/suggest.mjs +6 -6
  150. package/dist/astro/routes/api/sections/_slug_.mjs +6 -6
  151. package/dist/astro/routes/api/sections/index.mjs +6 -6
  152. package/dist/astro/routes/api/settings/email.mjs +4 -4
  153. package/dist/astro/routes/api/settings.mjs +8 -8
  154. package/dist/astro/routes/api/setup/admin-verify.mjs +3 -3
  155. package/dist/astro/routes/api/setup/admin.mjs +3 -3
  156. package/dist/astro/routes/api/setup/dev-bypass.mjs +16 -16
  157. package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
  158. package/dist/astro/routes/api/setup/index.mjs +17 -17
  159. package/dist/astro/routes/api/setup/status.mjs +3 -3
  160. package/dist/astro/routes/api/snapshot.mjs +3 -3
  161. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +8 -8
  162. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +8 -8
  163. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +8 -8
  164. package/dist/astro/routes/api/taxonomies/index.mjs +8 -8
  165. package/dist/astro/routes/api/themes/preview.mjs +3 -3
  166. package/dist/astro/routes/api/typegen.mjs +5 -5
  167. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +4 -4
  168. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +6 -6
  169. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +6 -6
  170. package/dist/astro/routes/api/widget-areas/_name_.mjs +5 -5
  171. package/dist/astro/routes/api/widget-areas/index.mjs +6 -6
  172. package/dist/astro/routes/api/widget-components.mjs +2 -2
  173. package/dist/astro/routes/robots.txt.mjs +4 -4
  174. package/dist/astro/routes/sitemap-_collection_.xml.mjs +4 -4
  175. package/dist/astro/routes/sitemap.xml.mjs +4 -4
  176. package/dist/astro/types.d.mts +2 -2
  177. package/dist/{authorize-Cse0k7B0.mjs → authorize-B__u-iZJ.mjs} +2 -2
  178. package/dist/{authorize-Cse0k7B0.mjs.map → authorize-B__u-iZJ.mjs.map} +1 -1
  179. package/dist/{byline-DVo-liaP.mjs → byline-C-YajfV7.mjs} +5 -5
  180. package/dist/{byline-DVo-liaP.mjs.map → byline-C-YajfV7.mjs.map} +1 -1
  181. package/dist/{byline-fields-RFPkDLRw.d.mts → byline-fields-Ca56gaT6.d.mts} +6 -6
  182. package/dist/{byline-fields-RFPkDLRw.d.mts.map → byline-fields-Ca56gaT6.d.mts.map} +1 -1
  183. package/dist/{bylines-f5ppBpD5.mjs → bylines-BMtJnPnG.mjs} +5 -5
  184. package/dist/{bylines-f5ppBpD5.mjs.map → bylines-BMtJnPnG.mjs.map} +1 -1
  185. package/dist/{bylines-pIKgc_TL.mjs → bylines-D4KADDAi.mjs} +4 -4
  186. package/dist/{bylines-pIKgc_TL.mjs.map → bylines-D4KADDAi.mjs.map} +1 -1
  187. package/dist/{cache-Q6V4U-Xz.mjs → cache-BUJe2w6Z.mjs} +2 -2
  188. package/dist/{cache-Q6V4U-Xz.mjs.map → cache-BUJe2w6Z.mjs.map} +1 -1
  189. package/dist/{chunks-SyjZaYwV.mjs → chunks-BHgcHh2C.mjs} +2 -2
  190. package/dist/{chunks-SyjZaYwV.mjs.map → chunks-BHgcHh2C.mjs.map} +1 -1
  191. package/dist/cli/index.mjs +17 -17
  192. package/dist/cli/index.mjs.map +1 -1
  193. package/dist/{comment-80UyJJUR.mjs → comment-BDx50EX5.mjs} +2 -2
  194. package/dist/{comment-80UyJJUR.mjs.map → comment-BDx50EX5.mjs.map} +1 -1
  195. package/dist/{comment-reaction-Dyb-AutY.mjs → comment-reaction-Gv_pgfI3.mjs} +2 -2
  196. package/dist/{comment-reaction-Dyb-AutY.mjs.map → comment-reaction-Gv_pgfI3.mjs.map} +1 -1
  197. package/dist/{comments-Cczx7AvD.mjs → comments-CXHuQ6jI.mjs} +3 -3
  198. package/dist/{comments-Cczx7AvD.mjs.map → comments-CXHuQ6jI.mjs.map} +1 -1
  199. package/dist/{content-2PTDNnoh.mjs → content-PoVGKjp0.mjs} +3 -3
  200. package/dist/{content-2PTDNnoh.mjs.map → content-PoVGKjp0.mjs.map} +1 -1
  201. package/dist/{context-B1W8soLv.mjs → context-Dvws41_A.mjs} +7 -7
  202. package/dist/{context-B1W8soLv.mjs.map → context-Dvws41_A.mjs.map} +1 -1
  203. package/dist/{dashboard-CYjpQZ6-.mjs → dashboard-DsYU8JKS.mjs} +4 -4
  204. package/dist/{dashboard-CYjpQZ6-.mjs.map → dashboard-DsYU8JKS.mjs.map} +1 -1
  205. package/dist/db/index.mjs +1 -1
  206. package/dist/{error-Bl6kZgtt.mjs → error-lq_fL-RS.mjs} +2 -2
  207. package/dist/{error-Bl6kZgtt.mjs.map → error-lq_fL-RS.mjs.map} +1 -1
  208. package/dist/{field-defs-cache-ztrdzUGb.mjs → field-defs-cache-CYYx8xlG.mjs} +2 -2
  209. package/dist/{field-defs-cache-ztrdzUGb.mjs.map → field-defs-cache-CYYx8xlG.mjs.map} +1 -1
  210. package/dist/{fts-manager-CQuHt-vS.mjs → fts-manager-DW_NR-Fs.mjs} +2 -2
  211. package/dist/{fts-manager-CQuHt-vS.mjs.map → fts-manager-DW_NR-Fs.mjs.map} +1 -1
  212. package/dist/{index-BaIj5hIq.d.mts → index-BroXT-vD.d.mts} +98 -22
  213. package/dist/index-BroXT-vD.d.mts.map +1 -0
  214. package/dist/index.d.mts +2 -2
  215. package/dist/index.mjs +36 -36
  216. package/dist/{load-BNo-ER8C.mjs → load-DMFi4n1p.mjs} +2 -2
  217. package/dist/{load-BNo-ER8C.mjs.map → load-DMFi4n1p.mjs.map} +1 -1
  218. package/dist/{loader-Dn74CG1b.mjs → loader-BR2SgfEc.mjs} +10 -4
  219. package/dist/loader-BR2SgfEc.mjs.map +1 -0
  220. package/dist/media/local-runtime.d.mts +2 -2
  221. package/dist/media/local-runtime.mjs +4 -4
  222. package/dist/{media-Bw0sA6rb.mjs → media-CTjJoJTh.mjs} +2 -2
  223. package/dist/{media-Bw0sA6rb.mjs.map → media-CTjJoJTh.mjs.map} +1 -1
  224. package/dist/{menus-CYs0WZoL.mjs → menus-8O6xNcYW.mjs} +2 -2
  225. package/dist/{menus-CYs0WZoL.mjs.map → menus-8O6xNcYW.mjs.map} +1 -1
  226. package/dist/{menus-DS8VTZgo.mjs → menus-GvRSpgIA.mjs} +5 -5
  227. package/dist/{menus-DS8VTZgo.mjs.map → menus-GvRSpgIA.mjs.map} +1 -1
  228. package/dist/{parse-C9hpjUyY.mjs → parse-DHI873qU.mjs} +2 -2
  229. package/dist/{parse-C9hpjUyY.mjs.map → parse-DHI873qU.mjs.map} +1 -1
  230. package/dist/plugin-utils.d.mts +2 -2
  231. package/dist/plugins/adapt-sandbox-entry.d.mts +2 -2
  232. package/dist/{query-DgUHZLWB.mjs → query-DZdlS6Cu.mjs} +23 -16
  233. package/dist/query-DZdlS6Cu.mjs.map +1 -0
  234. package/dist/{rate-limit-BuQ5d4X_.mjs → rate-limit-D1Y6VFVP.mjs} +2 -2
  235. package/dist/{rate-limit-BuQ5d4X_.mjs.map → rate-limit-D1Y6VFVP.mjs.map} +1 -1
  236. package/dist/{redirect-Bifv_yHv.mjs → redirect-C1KVGYWe.mjs} +2 -2
  237. package/dist/{redirect-Bifv_yHv.mjs.map → redirect-C1KVGYWe.mjs.map} +1 -1
  238. package/dist/{redirects-BZ8Mxn8H.mjs → redirects-DgnypVir.mjs} +3 -3
  239. package/dist/{redirects-BZ8Mxn8H.mjs.map → redirects-DgnypVir.mjs.map} +1 -1
  240. package/dist/{registry-CK1SS6pi.mjs → registry-CJkCubOJ.mjs} +4 -4
  241. package/dist/{registry-CK1SS6pi.mjs.map → registry-CJkCubOJ.mjs.map} +1 -1
  242. package/dist/{runner-DYYnW530.mjs → runner-BsquExlK.mjs} +246 -174
  243. package/dist/runner-BsquExlK.mjs.map +1 -0
  244. package/dist/runner-CEtq-6Sm.d.mts.map +1 -1
  245. package/dist/runtime.d.mts +2 -2
  246. package/dist/runtime.mjs +2 -2
  247. package/dist/{schema-CbFRFoM7.mjs → schema-DMjXuvNU.mjs} +4 -4
  248. package/dist/{schema-CbFRFoM7.mjs.map → schema-DMjXuvNU.mjs.map} +1 -1
  249. package/dist/{search-C-waE_YW.mjs → search-D1wKk3lc.mjs} +4 -4
  250. package/dist/{search-C-waE_YW.mjs.map → search-D1wKk3lc.mjs.map} +1 -1
  251. package/dist/{sections-osX9U-YJ.mjs → sections-CCMic8dB.mjs} +3 -3
  252. package/dist/{sections-osX9U-YJ.mjs.map → sections-CCMic8dB.mjs.map} +1 -1
  253. package/dist/seed/index.mjs +14 -14
  254. package/dist/{seo-4FovMwNS.mjs → seo-3QrZ1EPT.mjs} +2 -2
  255. package/dist/{seo-4FovMwNS.mjs.map → seo-3QrZ1EPT.mjs.map} +1 -1
  256. package/dist/{service-jby1RRmU.mjs → service-CQHK_10l.mjs} +2 -2
  257. package/dist/{service-jby1RRmU.mjs.map → service-CQHK_10l.mjs.map} +1 -1
  258. package/dist/{settings-4bRT2NIf.mjs → settings-C3Yi5Co3.mjs} +3 -3
  259. package/dist/{settings-4bRT2NIf.mjs.map → settings-C3Yi5Co3.mjs.map} +1 -1
  260. package/dist/{settings-t-H2jPuJ.mjs → settings-D9rvaLuY.mjs} +3 -3
  261. package/dist/{settings-t-H2jPuJ.mjs.map → settings-D9rvaLuY.mjs.map} +1 -1
  262. package/dist/{taxonomies-Dxrc_szG.mjs → taxonomies-CurpLtNC.mjs} +29 -21
  263. package/dist/taxonomies-CurpLtNC.mjs.map +1 -0
  264. package/dist/{taxonomies-BBB8O-CU.mjs → taxonomies-DhLaNJji.mjs} +34 -27
  265. package/dist/taxonomies-DhLaNJji.mjs.map +1 -0
  266. package/dist/{taxonomy-B2HQuVf5.mjs → taxonomy-BBUlGug5.mjs} +33 -7
  267. package/dist/taxonomy-BBUlGug5.mjs.map +1 -0
  268. package/dist/{types-DXOIbece.mjs → types-CmI1OyQg.mjs} +2 -2
  269. package/dist/{types-DXOIbece.mjs.map → types-CmI1OyQg.mjs.map} +1 -1
  270. package/dist/{user-Db-Rti_z.mjs → user-JAuc8VUf.mjs} +3 -3
  271. package/dist/{user-Db-Rti_z.mjs.map → user-JAuc8VUf.mjs.map} +1 -1
  272. package/dist/{validate-0Utjspbs.mjs → validate-BzXHWnkV.mjs} +2 -2
  273. package/dist/{validate-0Utjspbs.mjs.map → validate-BzXHWnkV.mjs.map} +1 -1
  274. package/dist/{validation-D9EnzH1T.mjs → validation-CY1gk4Ar.mjs} +5 -5
  275. package/dist/{validation-D9EnzH1T.mjs.map → validation-CY1gk4Ar.mjs.map} +1 -1
  276. package/dist/version-C0JPFUG8.mjs +7 -0
  277. package/dist/{version-BgVM_1MU.mjs.map → version-C0JPFUG8.mjs.map} +1 -1
  278. package/dist/{widgets-BSTzPUYa.mjs → widgets-Dri0spI4.mjs} +2 -2
  279. package/dist/{widgets-BSTzPUYa.mjs.map → widgets-Dri0spI4.mjs.map} +1 -1
  280. package/dist/{zod-generator-ElVQOav7.mjs → zod-generator-xEHODMJf.mjs} +2 -2
  281. package/dist/{zod-generator-ElVQOav7.mjs.map → zod-generator-xEHODMJf.mjs.map} +1 -1
  282. package/package.json +6 -6
  283. package/src/api/handlers/taxonomies.ts +62 -32
  284. package/src/astro/integration/shims/use-sync-external-store-with-selector.js +71 -0
  285. package/src/astro/integration/shims/use-sync-external-store.js +1 -0
  286. package/src/astro/integration/vite-config.ts +47 -8
  287. package/src/astro/middleware.ts +13 -3
  288. package/src/cli/commands/export-seed.ts +5 -4
  289. package/src/database/migrations/045_taxonomy_parent_group.ts +68 -0
  290. package/src/database/migrations/runner.ts +2 -0
  291. package/src/database/repositories/taxonomy.ts +50 -7
  292. package/src/loader.ts +64 -8
  293. package/src/query.ts +93 -18
  294. package/src/taxonomies/index.ts +20 -8
  295. package/dist/index-BaIj5hIq.d.mts.map +0 -1
  296. package/dist/loader-Dn74CG1b.mjs.map +0 -1
  297. package/dist/query-DgUHZLWB.mjs.map +0 -1
  298. package/dist/runner-DYYnW530.mjs.map +0 -1
  299. package/dist/taxonomies-BBB8O-CU.mjs.map +0 -1
  300. package/dist/taxonomies-Dxrc_szG.mjs.map +0 -1
  301. package/dist/taxonomy-B2HQuVf5.mjs.map +0 -1
  302. package/dist/version-BgVM_1MU.mjs +0 -7
@@ -1 +0,0 @@
1
- {"version":3,"file":"loader-Dn74CG1b.mjs","names":[],"sources":["../src/loader.ts"],"sourcesContent":["/**\n * Astro Live Collections loader for EmDash\n *\n * This loader implements the Astro LiveLoader interface to fetch content\n * at runtime from the database, enabling live editing without rebuilds.\n *\n * Architecture:\n * - Single `_emdash` Astro collection handles all content types\n * - Dialect comes from virtual module (configured in astro.config.mjs)\n * - Each content type maps to its own database table: ec_posts, ec_products, etc.\n * - `getEmDashCollection()` / `getEmDashEntry()` wrap Astro's live collection API\n */\n\nimport type { LiveLoader } from \"astro/loaders\";\nimport { Kysely, type RawBuilder, sql, type Dialect } from \"kysely\";\n\nimport { currentTimestampValue, isPostgres } from \"./database/dialect-helpers.js\";\nimport { kyselyLogOption } from \"./database/instrumentation.js\";\nimport { decodeCursor, encodeCursor } from \"./database/repositories/types.js\";\nimport { validateIdentifier } from \"./database/validate.js\";\nimport type { Database } from \"./index.js\";\nimport { getRequestContext } from \"./request-context.js\";\nimport { isMissingColumnError, isMissingTableError } from \"./utils/db-errors.js\";\n\nconst FIELD_NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\n\n/**\n * SEO columns joined in from `_emdash_seo` on the single-entry path, mapped to\n * aliased result keys. SEO lives in a side table, so a LEFT JOIN folds it into\n * the entry load at zero extra query cost; the result is surfaced as a nested\n * `data.seo` object (see extractSeo) rather than flat fields.\n *\n * The `_emdash_` prefix on the aliases guarantees they can never collide with\n * a content field. Field slugs must match `/^[a-z][a-z0-9_]*$/`, so a user can\n * legitimately define a `seo_title` field; selecting the joined column under\n * its bare name would shadow that field in the result set and drop the user's\n * value. The prefix (illegal as a leading slug char) sidesteps this entirely.\n */\nconst SEO_COLUMN_ALIASES: Record<string, string> = {\n\tseo_title: \"_emdash_seo_title\",\n\tseo_description: \"_emdash_seo_description\",\n\tseo_image: \"_emdash_seo_image\",\n\tseo_canonical: \"_emdash_seo_canonical\",\n\tseo_no_index: \"_emdash_seo_no_index\",\n};\n\n/** Aliased SEO result keys — excluded from generic field mapping. */\nconst SEO_ALIAS_COLUMNS = Object.values(SEO_COLUMN_ALIASES);\n\n/**\n * System columns excluded from entry.data\n * Note: slug is intentionally NOT excluded - it's useful as data.slug in templates\n */\nconst SYSTEM_COLUMNS = new Set([\n\t\"id\",\n\t// \"slug\" - kept in data for template access\n\t\"status\",\n\t\"author_id\",\n\t\"primary_byline_id\",\n\t\"created_at\",\n\t\"updated_at\",\n\t\"published_at\",\n\t\"scheduled_at\",\n\t\"deleted_at\",\n\t\"version\",\n\t\"live_revision_id\",\n\t\"draft_revision_id\",\n\t\"locale\",\n\t\"translation_group\",\n\t// Aliased SEO columns joined from _emdash_seo on the single-entry path.\n\t// Surfaced as a nested data.seo object (see extractSeo), never as flat\n\t// fields. The aliases are _emdash_-prefixed so they can't shadow a user\n\t// field named e.g. `seo_title`.\n\t...SEO_ALIAS_COLUMNS,\n\t// Folded hydration JSON columns (see foldedHydrationSelects) — surfaced via\n\t// the FOLDED_* markers, never as flat fields.\n\t\"_emdash_terms\",\n\t\"_emdash_bylines\",\n]);\n\n/** Markers for byline/taxonomy hydration folded into the content query. */\nexport const FOLDED_TERMS = Symbol.for(\"emdash:foldedTerms\");\nexport const FOLDED_BYLINES = Symbol.for(\"emdash:foldedBylines\");\n\n/**\n * Correlated JSON-array subqueries that fold taxonomy-term and byline hydration\n * into the content query, removing the two separate hydration round trips per\n * fetch. `outer` is the content table's alias/name; each subquery correlates on\n * `<outer>.id`, so the base query stays one row per entry (no join fan-out, no\n * duplicated content payload). Order is NOT applied in the aggregate (it differs\n * across dialects) — the consumer sorts terms by label and credits by sortOrder.\n *\n * Dialect-specific aggregation: SQLite `json_group_array`/`json_object` returns\n * a JSON *string*; Postgres `json_agg`/`json_build_object` (coalesced to `[]`)\n * returns parsed JSON. {@link stashFolded} handles both.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- any Kysely instance\nfunction foldedHydrationSelects(db: Kysely<any>, type: string, outer: string) {\n\tconst o = sql.ref(outer);\n\tconst pg = isPostgres(db);\n\tconst obj = (pairs: string) =>\n\t\tpg ? sql.raw(`json_build_object(${pairs})`) : sql.raw(`json_object(${pairs})`);\n\tconst agg = (inner: RawBuilder<unknown>) =>\n\t\tpg ? sql`coalesce(json_agg(${inner}), '[]'::json)` : sql`json_group_array(${inner})`;\n\n\tconst termObj = obj(\n\t\t\"'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\",\n\t);\n\t// Filter terms to the entry's own locale (matches #1441: terms render in the\n\t// entry's resolved locale, not all locale variants of the attached group).\n\tconst 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\")}`;\n\n\tconst bylineInner = obj(\n\t\t\"'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\",\n\t);\n\tconst creditObj = pg\n\t\t? sql.raw(\n\t\t\t\t\"json_build_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', \",\n\t\t\t)\n\t\t: sql.raw(\"json_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', \");\n\tconst credit = sql`${creditObj}${bylineInner})`;\n\tconst 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\")}`;\n\treturn { terms, bylines };\n}\n\n/**\n * Stash folded hydration JSON (non-enumerable) for the query.ts fast paths.\n * SQLite returns a JSON string (parse it); Postgres returns already-parsed JSON.\n */\nfunction stashFolded(data: Record<string, unknown>, row: Record<string, unknown>): void {\n\tfor (const [col, sym] of [\n\t\t[\"_emdash_terms\", FOLDED_TERMS],\n\t\t[\"_emdash_bylines\", FOLDED_BYLINES],\n\t] as const) {\n\t\tconst raw = row[col];\n\t\tlet value: unknown;\n\t\tif (typeof raw === \"string\") {\n\t\t\ttry {\n\t\t\t\tvalue = JSON.parse(raw);\n\t\t\t} catch {\n\t\t\t\tcontinue; // malformed: fall back to the query path\n\t\t\t}\n\t\t} else if (Array.isArray(raw)) {\n\t\t\tvalue = raw; // Postgres json/jsonb already parsed by the driver\n\t\t} else {\n\t\t\tcontinue;\n\t\t}\n\t\tObject.defineProperty(data, sym, { value, enumerable: false, configurable: true });\n\t}\n}\n\n/** Resolved SEO shape attached to `entry.data.seo`. Mirrors `ContentSeo`. */\ninterface EntrySeo {\n\ttitle: string | null;\n\tdescription: string | null;\n\timage: string | null;\n\tcanonical: string | null;\n\tnoIndex: boolean;\n}\n\n/**\n * Build a `data.seo` object from the joined `_emdash_seo` columns on a row.\n *\n * Returns `null` when no SEO row exists (LEFT JOIN miss → `seo_no_index` is\n * NULL, since the column is `NOT NULL DEFAULT 0` whenever a row is present).\n * Returning null keeps the `seo` key off entries that have none, so\n * `getSeoMeta()` falls back to its defaults exactly as before.\n */\nfunction extractSeo(row: Record<string, unknown>): EntrySeo | null {\n\tconst noIndex = row[SEO_COLUMN_ALIASES.seo_no_index];\n\tif (noIndex === null || noIndex === undefined) return null;\n\tconst title = row[SEO_COLUMN_ALIASES.seo_title];\n\tconst description = row[SEO_COLUMN_ALIASES.seo_description];\n\tconst image = row[SEO_COLUMN_ALIASES.seo_image];\n\tconst canonical = row[SEO_COLUMN_ALIASES.seo_canonical];\n\treturn {\n\t\ttitle: typeof title === \"string\" ? title : null,\n\t\tdescription: typeof description === \"string\" ? description : null,\n\t\timage: typeof image === \"string\" ? image : null,\n\t\tcanonical: typeof canonical === \"string\" ? canonical : null,\n\t\tnoIndex: noIndex === 1,\n\t};\n}\n\n/**\n * Get the table name for a collection type\n */\nfunction getTableName(type: string): string {\n\tvalidateIdentifier(type, \"collection type\");\n\treturn `ec_${type}`;\n}\n\n/**\n * Cache for taxonomy names (only used for the primary database).\n * Skipped when a per-request DB override is active (e.g. preview mode)\n * because the override DB may have different taxonomies.\n */\nlet taxonomyNames: Set<string> | null = null;\n\n/**\n * Get all taxonomy names (cached for the primary DB, bypassed only when\n * the per-request DB is an isolated instance — playground / DO preview).\n * Plain D1 Sessions routing shares schema with the singleton, so the\n * module-scoped cache stays valid.\n */\nasync function getTaxonomyNames(db: Kysely<Database>): Promise<Set<string>> {\n\tconst hasIsolatedDb = getRequestContext()?.dbIsIsolated === true;\n\n\tif (!hasIsolatedDb && taxonomyNames) {\n\t\treturn taxonomyNames;\n\t}\n\n\ttry {\n\t\tconst defs = await db.selectFrom(\"_emdash_taxonomy_defs\").select(\"name\").execute();\n\t\tconst names = new Set(defs.map((d) => d.name));\n\t\tif (!hasIsolatedDb) {\n\t\t\ttaxonomyNames = names;\n\t\t}\n\t\treturn names;\n\t} catch {\n\t\t// Table doesn't exist yet, return empty set\n\t\tconst empty = new Set<string>();\n\t\tif (!hasIsolatedDb) {\n\t\t\ttaxonomyNames = empty;\n\t\t}\n\t\treturn empty;\n\t}\n}\n\n/**\n * System columns to include in data (mapped to camelCase where needed)\n */\nconst INCLUDE_IN_DATA: Record<string, string> = {\n\tid: \"id\",\n\tstatus: \"status\",\n\tauthor_id: \"authorId\",\n\tprimary_byline_id: \"primaryBylineId\",\n\tcreated_at: \"createdAt\",\n\tupdated_at: \"updatedAt\",\n\tpublished_at: \"publishedAt\",\n\tscheduled_at: \"scheduledAt\",\n\tdraft_revision_id: \"draftRevisionId\",\n\tlive_revision_id: \"liveRevisionId\",\n\tlocale: \"locale\",\n\ttranslation_group: \"translationGroup\",\n};\n\n/** System date columns that should be converted to Date objects */\nconst DATE_COLUMNS = new Set([\"created_at\", \"updated_at\", \"published_at\", \"scheduled_at\"]);\n\n/**\n * Hidden, symbol-keyed property on each mapped data record carrying the raw\n * DB string for every date column. Lets cursor encoders downstream reproduce\n * the loader's exact `nextCursor` format without round-tripping through\n * `new Date()`, which loses precision for stored values that aren't already\n * ISO-with-milliseconds (e.g. `2026-01-01T00:00:00Z` becomes\n * `2026-01-01T00:00:00.000Z`).\n */\nexport const CURSOR_RAW_VALUES: unique symbol = Symbol(\"emdash:cursorRawValues\");\n\nconst LOCAL_MEDIA_FILE_PREFIX = \"/_emdash/api/media/file/\";\nconst URL_SCHEME_PATTERN = /^[a-zA-Z][a-zA-Z\\d+\\-.]*:/;\n\n/** Safely extract a string value from a record, returning fallback if not a string */\nfunction rowStr(row: Record<string, unknown>, key: string, fallback = \"\"): string {\n\tconst val = row[key];\n\treturn typeof val === \"string\" ? val : fallback;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isBareMediaKey(src: string): boolean {\n\treturn !src.startsWith(\"/\") && !URL_SCHEME_PATTERN.test(src);\n}\n\nfunction normalizeLocalMediaValue(value: unknown): unknown {\n\tif (Array.isArray(value)) {\n\t\treturn value.map(normalizeLocalMediaValue);\n\t}\n\n\tif (!isRecord(value)) {\n\t\treturn value;\n\t}\n\n\tconst normalized: Record<string, unknown> = {};\n\tfor (const [key, child] of Object.entries(value)) {\n\t\tnormalized[key] = normalizeLocalMediaValue(child);\n\t}\n\n\tif (\n\t\tnormalized.provider === \"local\" &&\n\t\ttypeof normalized.src === \"string\" &&\n\t\tnormalized.src.length > 0\n\t) {\n\t\tconst src = normalized.src;\n\t\tif (src.startsWith(LOCAL_MEDIA_FILE_PREFIX)) {\n\t\t\tconst id = src.slice(LOCAL_MEDIA_FILE_PREFIX.length);\n\t\t\tif (!normalized.id && id) {\n\t\t\t\tnormalized.id = id;\n\t\t\t}\n\t\t} else if (isBareMediaKey(src)) {\n\t\t\tif (!normalized.id) {\n\t\t\t\tnormalized.id = src;\n\t\t\t}\n\t\t\tnormalized.src = `${LOCAL_MEDIA_FILE_PREFIX}${src}`;\n\t\t}\n\t}\n\n\treturn normalized;\n}\n\n/**\n * Map a database row to entry data\n * Extracts content fields (non-system columns) and parses JSON where needed.\n * System columns needed for templates (id, status, dates) are included with camelCase names.\n */\nfunction mapRowToData(row: Record<string, unknown>): Record<string, unknown> {\n\tconst data: Record<string, unknown> = {};\n\tconst rawDateValues: Record<string, string> = {};\n\n\tfor (const [key, value] of Object.entries(row)) {\n\t\t// Include certain system columns (mapped to camelCase where needed)\n\t\tif (key in INCLUDE_IN_DATA) {\n\t\t\t// Convert date columns from ISO strings to Date objects\n\t\t\tif (DATE_COLUMNS.has(key)) {\n\t\t\t\tif (typeof value === \"string\") {\n\t\t\t\t\trawDateValues[key] = value;\n\t\t\t\t\tdata[INCLUDE_IN_DATA[key]] = new Date(value);\n\t\t\t\t} else {\n\t\t\t\t\tdata[INCLUDE_IN_DATA[key]] = null;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdata[INCLUDE_IN_DATA[key]] = value;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (SYSTEM_COLUMNS.has(key)) continue;\n\n\t\t// Try to parse JSON strings (for portableText, json fields, etc.)\n\t\tif (typeof value === \"string\") {\n\t\t\ttry {\n\t\t\t\t// Only parse if it looks like JSON (starts with { or [)\n\t\t\t\tif (value.startsWith(\"{\") || value.startsWith(\"[\")) {\n\t\t\t\t\tdata[key] = normalizeLocalMediaValue(JSON.parse(value));\n\t\t\t\t} else {\n\t\t\t\t\tdata[key] = value;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\tdata[key] = value;\n\t\t\t}\n\t\t} else {\n\t\t\tdata[key] = value;\n\t\t}\n\t}\n\n\tObject.defineProperty(data, CURSOR_RAW_VALUES, {\n\t\tvalue: rawDateValues,\n\t\tenumerable: false,\n\t\tconfigurable: false,\n\t\twritable: false,\n\t});\n\n\treturn data;\n}\n\n/**\n * Map revision data (already-parsed JSON object) to entry data.\n * Strips _-prefixed metadata keys (e.g. _slug) used internally by revisions.\n */\nfunction mapRevisionData(data: Record<string, unknown>): Record<string, unknown> {\n\tconst result: Record<string, unknown> = {};\n\tfor (const [key, value] of Object.entries(data)) {\n\t\tif (key.startsWith(\"_\")) continue; // revision metadata\n\t\tresult[key] = normalizeLocalMediaValue(value);\n\t}\n\treturn result;\n}\n\n// Virtual module imports are lazy-loaded to avoid errors when importing\n// emdash outside of Astro/Vite context (e.g., in astro.config.mjs)\nlet virtualConfig:\n\t| {\n\t\t\tdatabase?: { config: unknown };\n\t\t\ti18n?: { defaultLocale: string; locales: string[]; prefixDefaultLocale?: boolean } | null;\n\t }\n\t| undefined;\nlet virtualCreateDialect: ((config: unknown) => Dialect) | undefined;\n\nasync function loadVirtualModules() {\n\tif (virtualConfig === undefined) {\n\t\t// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n\t\t// @ts-ignore - virtual module\n\t\tconst configModule = await import(\"virtual:emdash/config\");\n\t\tvirtualConfig = configModule.default;\n\t}\n\tif (virtualCreateDialect === undefined) {\n\t\t// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n\t\t// @ts-ignore - virtual module\n\t\tconst dialectModule = await import(\"virtual:emdash/dialect\");\n\t\tvirtualCreateDialect = dialectModule.createDialect;\n\t\t// dialectType is no longer needed here — dialect detection is\n\t\t// done via the db adapter instance in dialect-helpers.ts\n\t}\n}\n\n/**\n * Entry data type - generic object\n */\nexport type EntryData = Record<string, unknown>;\n\n/**\n * Sort direction\n */\nexport type SortDirection = \"asc\" | \"desc\";\n\n/**\n * Order by specification - field name to direction\n * @example { created_at: \"desc\" } - Sort by created_at descending\n * @example { title: \"asc\" } - Sort by title ascending\n */\nexport type OrderBySpec = Record<string, SortDirection>;\n\n/**\n * Build WHERE clause for status filtering.\n * When filtering for 'published' status, also include scheduled content\n * whose scheduled_at time has passed (treating it as effectively published).\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nfunction buildStatusCondition(\n\tdb: Kysely<any>,\n\tstatus: string,\n\ttablePrefix?: string,\n): ReturnType<typeof sql> {\n\tconst statusField = tablePrefix ? `${tablePrefix}.status` : \"status\";\n\tconst scheduledAtField = tablePrefix ? `${tablePrefix}.scheduled_at` : \"scheduled_at\";\n\n\tif (status === \"published\") {\n\t\t// Include both published content AND scheduled content past its publish time.\n\t\t// scheduled_at is stored as text (ISO 8601). On Postgres, we must cast it\n\t\t// to timestamptz for the comparison with CURRENT_TIMESTAMP to work.\n\t\tconst scheduledAtExpr = isPostgres(db)\n\t\t\t? sql`${sql.ref(scheduledAtField)}::timestamptz`\n\t\t\t: sql.ref(scheduledAtField);\n\t\tconst nowExpr = isPostgres(db)\n\t\t\t? currentTimestampValue(db)\n\t\t\t: sql`strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;\n\t\treturn sql`(${sql.ref(statusField)} = 'published' OR (${sql.ref(statusField)} = 'scheduled' AND ${scheduledAtExpr} <= ${nowExpr}))`;\n\t}\n\n\treturn sql`${sql.ref(statusField)} = ${status}`;\n}\n\n/**\n * Resolved primary sort field and direction (used for cursor pagination).\n */\ninterface PrimarySort {\n\tfield: string;\n\tdirection: SortDirection;\n}\n\n/**\n * Get the primary sort field from an orderBy spec (first valid field, or default).\n */\nfunction getPrimarySort(orderBy: OrderBySpec | undefined, tablePrefix?: string): PrimarySort {\n\tif (orderBy) {\n\t\tfor (const [field, direction] of Object.entries(orderBy)) {\n\t\t\tif (FIELD_NAME_PATTERN.test(field)) {\n\t\t\t\tconst fullField = tablePrefix ? `${tablePrefix}.${field}` : field;\n\t\t\t\treturn { field: fullField, direction };\n\t\t\t}\n\t\t}\n\t}\n\tconst defaultField = tablePrefix ? `${tablePrefix}.created_at` : \"created_at\";\n\treturn { field: defaultField, direction: \"desc\" };\n}\n\n/**\n * Build ORDER BY clause from orderBy spec\n * Validates field names to prevent SQL injection (alphanumeric + underscore only)\n * Supports multiple sort fields in object key order\n */\nfunction buildOrderByClause(\n\torderBy: OrderBySpec | undefined,\n\ttablePrefix?: string,\n): ReturnType<typeof sql> {\n\t// Default to created_at DESC\n\tif (!orderBy || Object.keys(orderBy).length === 0) {\n\t\tconst field = tablePrefix ? `${tablePrefix}.created_at` : \"created_at\";\n\t\treturn sql`ORDER BY ${sql.ref(field)} DESC, ${sql.ref(tablePrefix ? `${tablePrefix}.id` : \"id\")} DESC`;\n\t}\n\n\tconst sortParts: ReturnType<typeof sql>[] = [];\n\n\tfor (const [field, direction] of Object.entries(orderBy)) {\n\t\t// Validate field name (alphanumeric + underscore only)\n\t\tif (!FIELD_NAME_PATTERN.test(field)) {\n\t\t\tcontinue; // Skip invalid field names\n\t\t}\n\n\t\tconst fullField = tablePrefix ? `${tablePrefix}.${field}` : field;\n\t\tconst dir = direction === \"asc\" ? sql`ASC` : sql`DESC`;\n\t\tsortParts.push(sql`${sql.ref(fullField)} ${dir}`);\n\t}\n\n\t// If no valid sort fields, fall back to default\n\tif (sortParts.length === 0) {\n\t\tconst defaultField = tablePrefix ? `${tablePrefix}.created_at` : \"created_at\";\n\t\treturn sql`ORDER BY ${sql.ref(defaultField)} DESC, ${sql.ref(tablePrefix ? `${tablePrefix}.id` : \"id\")} DESC`;\n\t}\n\n\t// Add id as tiebreaker to ensure stable cursor ordering\n\tconst primary = getPrimarySort(orderBy, tablePrefix);\n\tconst idField = tablePrefix ? `${tablePrefix}.id` : \"id\";\n\tconst idDir = primary.direction === \"asc\" ? sql`ASC` : sql`DESC`;\n\tsortParts.push(sql`${sql.ref(idField)} ${idDir}`);\n\n\treturn sql`ORDER BY ${sql.join(sortParts, sql`, `)}`;\n}\n\n/**\n * Build a cursor WHERE condition for keyset pagination.\n * Uses the primary sort field + id as tiebreaker for stable ordering.\n *\n * Throws `InvalidCursorError` if the cursor is malformed; callers should\n * let this propagate so users see a real error rather than silently\n * falling back to the first page.\n */\nfunction buildCursorCondition(\n\tcursor: string,\n\torderBy: OrderBySpec | undefined,\n\ttablePrefix?: string,\n): ReturnType<typeof sql> {\n\tconst { orderValue, id: cursorId } = decodeCursor(cursor);\n\tconst primary = getPrimarySort(orderBy, tablePrefix);\n\tconst idField = tablePrefix ? `${tablePrefix}.id` : \"id\";\n\n\tif (primary.direction === \"desc\") {\n\t\treturn sql`(${sql.ref(primary.field)} < ${orderValue} OR (${sql.ref(primary.field)} = ${orderValue} AND ${sql.ref(idField)} < ${cursorId}))`;\n\t}\n\treturn sql`(${sql.ref(primary.field)} > ${orderValue} OR (${sql.ref(primary.field)} = ${orderValue} AND ${sql.ref(idField)} > ${cursorId}))`;\n}\n\n/** Type guard: is the where value a range object (not a string or array)? */\nfunction isWhereRange(value: WhereValue): value is WhereRange {\n\treturn value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\n/**\n * Build AND conditions for non-taxonomy field filters.\n * Returns an array of sql fragments; empty if no field filters apply.\n * Field names are validated against FIELD_NAME_PATTERN to prevent injection.\n */\nfunction buildFieldConditions(\n\tfields: Record<string, WhereValue>,\n\ttablePrefix?: string,\n): ReturnType<typeof sql>[] {\n\tconst conditions: ReturnType<typeof sql>[] = [];\n\n\tfor (const [key, value] of Object.entries(fields)) {\n\t\tif (!FIELD_NAME_PATTERN.test(key)) {\n\t\t\tconsole.warn(`[emdash] where filter: invalid field name \"${key}\" ignored`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (value == null) continue;\n\t\tconst ref = tablePrefix ? sql.ref(`${tablePrefix}.${key}`) : sql.ref(key);\n\n\t\tif (isWhereRange(value)) {\n\t\t\tif (value.gt !== undefined) conditions.push(sql`${ref} > ${value.gt}`);\n\t\t\tif (value.gte !== undefined) conditions.push(sql`${ref} >= ${value.gte}`);\n\t\t\tif (value.lt !== undefined) conditions.push(sql`${ref} < ${value.lt}`);\n\t\t\tif (value.lte !== undefined) conditions.push(sql`${ref} <= ${value.lte}`);\n\t\t} else if (Array.isArray(value)) {\n\t\t\tif (value.length > 0) {\n\t\t\t\tconditions.push(sql`${ref} IN (${sql.join(value.map((v) => sql`${v}`))})`);\n\t\t\t}\n\t\t} else {\n\t\t\tconditions.push(sql`${ref} = ${value}`);\n\t\t}\n\t}\n\n\treturn conditions;\n}\n\n/**\n * Range filter for comparison operators on field values.\n * Values are compared as strings in the database. This works correctly for\n * ISO 8601 dates (e.g. \"2024-01-01T00:00:00Z\") because lexicographic ordering\n * matches chronological ordering. Ensure date values use a consistent format.\n */\nexport interface WhereRange {\n\tgt?: string;\n\tgte?: string;\n\tlt?: string;\n\tlte?: string;\n}\n\n/**\n * A where clause value: exact match, multi-value match, or range comparison.\n */\nexport type WhereValue = string | string[] | WhereRange;\n\n/**\n * Filter for loadCollection - type is required\n */\nexport interface CollectionFilter {\n\ttype: string;\n\tstatus?: \"draft\" | \"published\" | \"archived\";\n\tlimit?: number;\n\t/**\n\t * Opaque cursor for keyset pagination.\n\t * Pass the `nextCursor` value from a previous result to fetch the next page.\n\t */\n\tcursor?: string;\n\t/**\n\t * Filter by field values, taxonomy terms, byline credits, or ranges.\n\t *\n\t * Taxonomy names are detected automatically and filtered via JOIN.\n\t * The reserved `byline` key filters by byline credit (any credit, not\n\t * just the primary one) via the `_emdash_content_bylines` junction\n\t * table; its value is one or more byline translation groups.\n\t * Other keys are treated as column filters on the content table.\n\t *\n\t * @example { category: 'news' } - taxonomy term\n\t * @example { byline: '01HXYZ...' } - entries credited to a byline (any position)\n\t * @example { series: 'main' } - exact match on a content field\n\t * @example { published_at: { gte: '2024-01-01', lt: '2025-01-01' } } - date range\n\t */\n\twhere?: Record<string, WhereValue>;\n\t/**\n\t * Order results by field(s)\n\t * @default { created_at: \"desc\" }\n\t */\n\torderBy?: OrderBySpec;\n\t/**\n\t * Filter by locale (e.g. 'en', 'fr').\n\t * When set, only returns content in this locale.\n\t */\n\tlocale?: string;\n}\n\n/**\n * Filter for loadEntry - type and id are required\n */\nexport interface EntryFilter {\n\ttype: string;\n\tid: string;\n\t/**\n\t * When set, fetch content data from this revision instead of the content table.\n\t * Used by preview mode to serve draft revision data.\n\t */\n\trevisionId?: string;\n\t/**\n\t * Locale to scope slug lookup. Only affects slug resolution;\n\t * IDs are globally unique and always resolve regardless of locale.\n\t */\n\tlocale?: string;\n}\n\n// Cached database instance (shared across calls)\nlet dbInstance: Kysely<Database> | null = null;\n\n/**\n * Get the database instance. Used by query wrapper functions and middleware.\n *\n * Checks the ALS request context first — if a per-request DB override is set\n * (e.g. by DO preview middleware), it takes precedence over the module-level\n * cached instance. This allows preview mode to route queries to an isolated\n * Durable Object database without modifying any calling code.\n *\n * Initializes the default database on first call using config from virtual module.\n */\nexport async function getDb(): Promise<Kysely<Database>> {\n\t// Per-request DB override via ALS (normal mode)\n\tconst ctx = getRequestContext();\n\tif (ctx?.db) {\n\t\treturn ctx.db as Kysely<Database>; // eslint-disable-line typescript/no-unsafe-type-assertion -- db is typed as unknown in RequestContext to avoid circular deps\n\t}\n\n\tif (!dbInstance) {\n\t\tawait loadVirtualModules();\n\t\tif (!virtualConfig?.database || typeof virtualCreateDialect !== \"function\") {\n\t\t\tthrow new Error(\n\t\t\t\t\"EmDash database not configured. Add database config to emdash() in astro.config.mjs\",\n\t\t\t);\n\t\t}\n\t\tconst dialect = virtualCreateDialect(virtualConfig.database.config);\n\t\tdbInstance = new Kysely<Database>({ dialect, log: kyselyLogOption() });\n\t}\n\treturn dbInstance;\n}\n\n/**\n * Create an EmDash Live Collections loader\n *\n * This loader handles ALL content types in a single Astro collection.\n * Use `getEmDashCollection()` and `getEmDashEntry()` to query\n * specific content types.\n *\n * Database is configured in astro.config.mjs via the emdash() integration.\n *\n * @example\n * ```ts\n * // src/live.config.ts\n * import { defineLiveCollection } from \"astro:content\";\n * import { emdashLoader } from \"emdash\";\n *\n * export const collections = {\n * emdash: defineLiveCollection({\n * loader: emdashLoader(),\n * }),\n * };\n * ```\n */\nexport function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFilter> {\n\treturn {\n\t\tname: \"emdash\",\n\n\t\t/**\n\t\t * Load all entries for a content type\n\t\t */\n\t\tasync loadCollection({ filter }) {\n\t\t\ttry {\n\t\t\t\t// Get DB instance (initializes on first use)\n\t\t\t\tconst db = await getDb();\n\n\t\t\t\t// Type filter is required\n\t\t\t\tconst type = filter?.type;\n\t\t\t\tif (!type) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: new Error(\n\t\t\t\t\t\t\t\"type filter is required. Use getEmDashCollection() instead of getLiveCollection() directly.\",\n\t\t\t\t\t\t),\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\t// Query the per-collection table (ec_posts, ec_products, etc.)\n\t\t\t\tconst tableName = getTableName(type);\n\n\t\t\t\t// Build query with dynamic table name\n\t\t\t\tconst status = filter?.status || \"published\";\n\t\t\t\tconst limit = filter?.limit;\n\t\t\t\tconst cursor = filter?.cursor;\n\t\t\t\tconst where = filter?.where;\n\t\t\t\tconst orderBy = filter?.orderBy;\n\t\t\t\tconst locale = filter?.locale;\n\n\t\t\t\t// Cursor pagination: over-fetch by 1 to detect next page\n\t\t\t\tconst fetchLimit = limit ? limit + 1 : undefined;\n\n\t\t\t\t// Build cursor condition if cursor is provided\n\t\t\t\tconst cursorCondition = cursor ? buildCursorCondition(cursor, orderBy) : null;\n\n\t\t\t\t// Separate taxonomy / byline filters from field filters\n\t\t\t\tlet result: { rows: Record<string, unknown>[] };\n\t\t\t\t// Taxonomy filters AND together: each entry constrains the base\n\t\t\t\t// row to match at least one of its slugs *within that taxonomy*.\n\t\t\t\t// Term slugs are unique only within a taxonomy, so every filter\n\t\t\t\t// keeps its own `name` and emits its own `EXISTS` clause rather\n\t\t\t\t// than pooling slugs into one `IN`.\n\t\t\t\tconst taxonomyFilters: { name: string; slugs: string[] }[] = [];\n\t\t\t\t// A byline filter matches entries credited to any of the given\n\t\t\t\t// byline translation groups via the `_emdash_content_bylines`\n\t\t\t\t// junction table. `null` means no byline filter; an empty\n\t\t\t\t// `groups` array means the filter was requested but matches\n\t\t\t\t// nothing (short-circuited to an empty result below).\n\t\t\t\tlet bylineFilter: { groups: string[] } | null = null;\n\t\t\t\tconst fieldFilters: Record<string, WhereValue> = {};\n\n\t\t\t\tif (where && Object.keys(where).length > 0) {\n\t\t\t\t\tconst taxNames = await getTaxonomyNames(db);\n\n\t\t\t\t\tfor (const [key, value] of Object.entries(where)) {\n\t\t\t\t\t\tif (value == null) continue;\n\t\t\t\t\t\tif (key === \"byline\") {\n\t\t\t\t\t\t\tif (isWhereRange(value)) {\n\t\t\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t\t\t`[emdash] where filter: range operators are not supported on \"byline\", ignored`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst groups = Array.isArray(value) ? value : [value];\n\t\t\t\t\t\t\tbylineFilter = { groups };\n\t\t\t\t\t\t} else if (taxNames.has(key)) {\n\t\t\t\t\t\t\tif (isWhereRange(value)) {\n\t\t\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t\t\t`[emdash] where filter: range operators are not supported on taxonomy \"${key}\", ignored`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst slugs = Array.isArray(value) ? value : [value];\n\t\t\t\t\t\t\ttaxonomyFilters.push({ name: key, slugs });\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfieldFilters[key] = value;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// A byline or taxonomy filter with no values matches nothing —\n\t\t\t\t// short-circuit before building SQL (an empty `IN ()` is invalid\n\t\t\t\t// SQL on both dialects).\n\t\t\t\tif (\n\t\t\t\t\t(bylineFilter && bylineFilter.groups.length === 0) ||\n\t\t\t\t\ttaxonomyFilters.some((f) => f.slugs.length === 0)\n\t\t\t\t) {\n\t\t\t\t\treturn { entries: [], cacheHint: { tags: [type] } };\n\t\t\t\t}\n\n\t\t\t\t{\n\t\t\t\t\t// Taxonomy and byline filters are applied as correlated\n\t\t\t\t\t// `EXISTS` semi-joins rather than `INNER JOIN ... DISTINCT`.\n\t\t\t\t\t// A join fan-out would force `SELECT DISTINCT table.*`, and\n\t\t\t\t\t// Postgres cannot apply DISTINCT to a row containing a `json`\n\t\t\t\t\t// column (no equality operator), so the join approach throws\n\t\t\t\t\t// there. EXISTS matches \"credited/tagged at least once\"\n\t\t\t\t\t// without duplicating rows, needs no DISTINCT, and works on\n\t\t\t\t\t// both SQLite and Postgres. The base query stays a single-\n\t\t\t\t\t// table `SELECT *`, so all field/status/locale/cursor/order\n\t\t\t\t\t// conditions reference unprefixed columns as before.\n\t\t\t\t\tconst orderByClause = buildOrderByClause(orderBy);\n\t\t\t\t\tconst statusCondition = buildStatusCondition(db, status);\n\t\t\t\t\tconst localeFilter = locale ? sql`AND locale = ${locale}` : sql``;\n\t\t\t\t\tconst cursorCond = cursorCondition ? sql`AND ${cursorCondition}` : sql``;\n\t\t\t\t\tconst fieldConds = buildFieldConditions(fieldFilters);\n\t\t\t\t\tconst fieldCondsSQL =\n\t\t\t\t\t\tfieldConds.length > 0 ? sql`${sql.join(fieldConds, sql` AND `)}` : null;\n\n\t\t\t\t\t// One `EXISTS` per taxonomy, AND'd together: an entry must be\n\t\t\t\t\t// tagged with a matching term in *every* requested taxonomy.\n\t\t\t\t\t// Each clause pins its own `t.name`, so slugs never pool\n\t\t\t\t\t// across taxonomies (they're only unique within one).\n\t\t\t\t\tconst taxonomyCond =\n\t\t\t\t\t\ttaxonomyFilters.length > 0\n\t\t\t\t\t\t\t? sql`${sql.join(\n\t\t\t\t\t\t\t\t\ttaxonomyFilters.map(\n\t\t\t\t\t\t\t\t\t\t(f) => sql`AND EXISTS (\n\t\t\t\t\t\t\tSELECT 1 FROM content_taxonomies ct\n\t\t\t\t\t\t\tINNER JOIN taxonomies t ON t.id = ct.taxonomy_id\n\t\t\t\t\t\t\tWHERE ct.collection = ${type}\n\t\t\t\t\t\t\t\tAND ct.entry_id = ${sql.ref(tableName)}.id\n\t\t\t\t\t\t\t\tAND t.name = ${f.name}\n\t\t\t\t\t\t\t\tAND t.slug IN (${sql.join(f.slugs.map((s) => sql`${s}`))})\n\t\t\t\t\t\t)`,\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\tsql` `,\n\t\t\t\t\t\t\t\t)}`\n\t\t\t\t\t\t\t: sql``;\n\n\t\t\t\t\t// `_emdash_content_bylines.byline_id` stores the byline's\n\t\t\t\t\t// translation_group (migration 040), so a credit spans every\n\t\t\t\t\t// locale variant of the byline and we match the group directly.\n\t\t\t\t\tconst bylineCond = bylineFilter\n\t\t\t\t\t\t? sql`AND EXISTS (\n\t\t\t\t\t\t\tSELECT 1 FROM _emdash_content_bylines cb\n\t\t\t\t\t\t\tWHERE cb.collection_slug = ${type}\n\t\t\t\t\t\t\t\tAND cb.content_id = ${sql.ref(tableName)}.id\n\t\t\t\t\t\t\t\tAND cb.byline_id IN (${sql.join(bylineFilter.groups.map((g) => sql`${g}`))})\n\t\t\t\t\t\t)`\n\t\t\t\t\t\t: sql``;\n\n\t\t\t\t\t// Fold byline + taxonomy hydration into the list query.\n\t\t\t\t\tconst { terms: termsSelect, bylines: bylinesSelect } = foldedHydrationSelects(\n\t\t\t\t\t\tdb,\n\t\t\t\t\t\ttype,\n\t\t\t\t\t\ttableName,\n\t\t\t\t\t);\n\t\t\t\t\tresult = await sql<Record<string, unknown>>`\n\t\t\t\t\t\tSELECT *, ${termsSelect}, ${bylinesSelect} FROM ${sql.ref(tableName)}\n\t\t\t\t\t\tWHERE deleted_at IS NULL\n\t\t\t\t\t\tAND ${statusCondition}\n\t\t\t\t\t\t${localeFilter}\n\t\t\t\t\t\t${cursorCond}\n\t\t\t\t\t\t${taxonomyCond}\n\t\t\t\t\t\t${bylineCond}\n\t\t\t\t\t\t${fieldCondsSQL ? sql`AND ${fieldCondsSQL}` : sql``}\n\t\t\t\t\t\t${orderByClause}\n\t\t\t\t\t\t${fetchLimit ? sql`LIMIT ${fetchLimit}` : sql``}\n\t\t\t\t\t`.execute(db);\n\t\t\t\t}\n\n\t\t\t\t// Detect whether there are more results (over-fetched by 1)\n\t\t\t\tconst hasMore = limit ? result.rows.length > limit : false;\n\t\t\t\tconst rows = hasMore ? result.rows.slice(0, limit) : result.rows;\n\n\t\t\t\t// Map rows to entries\n\t\t\t\tconst i18nConfig = virtualConfig?.i18n;\n\t\t\t\tconst i18nEnabled = i18nConfig && i18nConfig.locales.length > 1;\n\t\t\t\tconst entries = rows.map((row) => {\n\t\t\t\t\tconst slug = rowStr(row, \"slug\") || rowStr(row, \"id\");\n\t\t\t\t\tconst rowLocale = rowStr(row, \"locale\");\n\t\t\t\t\tconst shouldPrefix =\n\t\t\t\t\t\ti18nEnabled &&\n\t\t\t\t\t\trowLocale !== \"\" &&\n\t\t\t\t\t\t(rowLocale !== i18nConfig.defaultLocale || i18nConfig.prefixDefaultLocale);\n\t\t\t\t\tconst id = shouldPrefix ? `${rowLocale}/${slug}` : slug;\n\t\t\t\t\tconst data = mapRowToData(row);\n\t\t\t\t\tstashFolded(data, row);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tid,\n\t\t\t\t\t\tslug: rowStr(row, \"slug\"),\n\t\t\t\t\t\tstatus: rowStr(row, \"status\", \"draft\"),\n\t\t\t\t\t\tdata,\n\t\t\t\t\t\tcacheHint: {\n\t\t\t\t\t\t\ttags: [rowStr(row, \"id\")],\n\t\t\t\t\t\t\tlastModified: row.updated_at ? new Date(rowStr(row, \"updated_at\")) : undefined,\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t});\n\n\t\t\t\t// Encode nextCursor from the last row if there are more results\n\t\t\t\tlet nextCursor: string | undefined;\n\t\t\t\tif (hasMore && rows.length > 0) {\n\t\t\t\t\tconst lastRow = rows.at(-1)!;\n\t\t\t\t\tconst primary = getPrimarySort(orderBy);\n\t\t\t\t\t// Strip table prefix from field name for row lookup\n\t\t\t\t\tconst fieldName = primary.field.includes(\".\")\n\t\t\t\t\t\t? primary.field.split(\".\").pop()!\n\t\t\t\t\t\t: primary.field;\n\t\t\t\t\tconst lastOrderValue = lastRow[fieldName];\n\t\t\t\t\tconst orderStr =\n\t\t\t\t\t\ttypeof lastOrderValue === \"string\" || typeof lastOrderValue === \"number\"\n\t\t\t\t\t\t\t? String(lastOrderValue)\n\t\t\t\t\t\t\t: \"\";\n\t\t\t\t\tnextCursor = encodeCursor(orderStr, String(lastRow.id));\n\t\t\t\t}\n\n\t\t\t\t// Collection-level cache hint uses the most recent updated_at\n\t\t\t\tlet collectionLastModified: Date | undefined;\n\t\t\t\tfor (const row of rows) {\n\t\t\t\t\tif (row.updated_at) {\n\t\t\t\t\t\tconst d = new Date(rowStr(row, \"updated_at\"));\n\t\t\t\t\t\tif (!collectionLastModified || d > collectionLastModified) {\n\t\t\t\t\t\t\tcollectionLastModified = d;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tentries,\n\t\t\t\t\tnextCursor,\n\t\t\t\t\tcacheHint: {\n\t\t\t\t\t\ttags: [type],\n\t\t\t\t\t\tlastModified: collectionLastModified,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\t// Handle missing table/column gracefully - return empty collection.\n\t\t\t\t// Missing table happens before migrations have run.\n\t\t\t\t// Missing column happens when a where filter references a non-existent field.\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tif (isMissingTableError(error) || isMissingColumnError(error)) {\n\t\t\t\t\tif (isMissingColumnError(error)) {\n\t\t\t\t\t\tconsole.warn(`[emdash] where filter: ${message}`);\n\t\t\t\t\t}\n\t\t\t\t\treturn { entries: [] };\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\terror: new Error(`Failed to load collection: ${message}`),\n\t\t\t\t};\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Load a single entry by type and ID/slug\n\t\t *\n\t\t * When filter.revisionId is set (preview mode), the entry's data\n\t\t * comes from the revisions table instead of the content table columns.\n\t\t */\n\t\tasync loadEntry({ filter }) {\n\t\t\ttry {\n\t\t\t\t// Get DB instance\n\t\t\t\tconst db = await getDb();\n\n\t\t\t\t// Both type and id are required\n\t\t\t\tconst type = filter?.type;\n\t\t\t\tconst id = filter?.id;\n\n\t\t\t\tif (!type || !id) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: new Error(\n\t\t\t\t\t\t\t\"type and id filters are required. Use getEmDashEntry() instead of getLiveEntry() directly.\",\n\t\t\t\t\t\t),\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\t// Query the per-collection table\n\t\t\t\tconst tableName = getTableName(type);\n\t\t\t\tconst locale = filter?.locale;\n\n\t\t\t\t// Use raw SQL for dynamic table name, match by slug or id\n\t\t\t\t// When locale is specified, prefer locale-scoped slug match,\n\t\t\t\t// but IDs are globally unique so always check id without locale scope.\n\t\t\t\t//\n\t\t\t\t// LEFT JOIN _emdash_seo folds per-entry SEO (canonical, noindex,\n\t\t\t\t// etc.) into this single query at zero extra round-trip cost. The\n\t\t\t\t// joined columns are surfaced as a nested data.seo object via\n\t\t\t\t// extractSeo() and excluded from the generic field mapping. SEO is\n\t\t\t\t// 1:1 with content (PK on collection+content_id), so the join never\n\t\t\t\t// multiplies rows.\n\t\t\t\tconst seoSelect = sql.join(\n\t\t\t\t\tObject.entries(SEO_COLUMN_ALIASES).map(\n\t\t\t\t\t\t([col, alias]) => sql`${sql.ref(`s.${col}`)} AS ${sql.ref(alias)}`,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\t// Fold byline + taxonomy hydration into the content query (see\n\t\t\t\t// foldedHydrationSelects), removing the two separate hydration\n\t\t\t\t// round trips per fetch.\n\t\t\t\tconst { terms: termsSelect, bylines: bylinesSelect } = foldedHydrationSelects(\n\t\t\t\t\tdb,\n\t\t\t\t\ttype,\n\t\t\t\t\t\"c\",\n\t\t\t\t);\n\t\t\t\tconst result = locale\n\t\t\t\t\t? await sql<Record<string, unknown>>`\n\t\t\t\t\t\t\tSELECT c.*, ${seoSelect}, ${termsSelect}, ${bylinesSelect}\n\t\t\t\t\t\t\tFROM ${sql.ref(tableName)} AS c\n\t\t\t\t\t\t\tLEFT JOIN ${sql.ref(\"_emdash_seo\")} AS s\n\t\t\t\t\t\t\t\tON s.collection = ${type} AND s.content_id = c.id\n\t\t\t\t\t\t\tWHERE c.deleted_at IS NULL\n\t\t\t\t\t\t\tAND ((c.slug = ${id} AND c.locale = ${locale}) OR c.id = ${id})\n\t\t\t\t\t\t\tLIMIT 1\n\t\t\t\t\t\t`.execute(db)\n\t\t\t\t\t: await sql<Record<string, unknown>>`\n\t\t\t\t\t\t\tSELECT c.*, ${seoSelect}, ${termsSelect}, ${bylinesSelect}\n\t\t\t\t\t\t\tFROM ${sql.ref(tableName)} AS c\n\t\t\t\t\t\t\tLEFT JOIN ${sql.ref(\"_emdash_seo\")} AS s\n\t\t\t\t\t\t\t\tON s.collection = ${type} AND s.content_id = c.id\n\t\t\t\t\t\t\tWHERE c.deleted_at IS NULL\n\t\t\t\t\t\t\tAND (c.slug = ${id} OR c.id = ${id})\n\t\t\t\t\t\t\tLIMIT 1\n\t\t\t\t\t\t`.execute(db);\n\n\t\t\t\tconst row = result.rows[0];\n\t\t\t\tif (!row) {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\n\t\t\t\tconst i18nConfig = virtualConfig?.i18n;\n\t\t\t\tconst i18nEnabled = i18nConfig && i18nConfig.locales.length > 1;\n\t\t\t\tconst entrySlug = rowStr(row, \"slug\") || rowStr(row, \"id\");\n\t\t\t\tconst entryLocale = rowStr(row, \"locale\");\n\t\t\t\tconst shouldPrefixEntry =\n\t\t\t\t\ti18nEnabled &&\n\t\t\t\t\tentryLocale !== \"\" &&\n\t\t\t\t\t(entryLocale !== i18nConfig.defaultLocale || i18nConfig.prefixDefaultLocale);\n\t\t\t\tconst entryId = shouldPrefixEntry ? `${entryLocale}/${entrySlug}` : entrySlug;\n\n\t\t\t\t// Preview mode: override content fields with revision data,\n\t\t\t\t// keeping system metadata from the content table row.\n\t\t\t\tconst revisionId = filter?.revisionId;\n\t\t\t\tif (revisionId) {\n\t\t\t\t\tconst revRow = await sql<{ data: string }>`\n\t\t\t\t\t\tSELECT data FROM revisions\n\t\t\t\t\t\tWHERE id = ${revisionId}\n\t\t\t\t\t\tLIMIT 1\n\t\t\t\t\t`.execute(db);\n\n\t\t\t\t\tconst revData = revRow.rows[0];\n\t\t\t\t\tif (revData) {\n\t\t\t\t\t\tconst parsed: Record<string, unknown> = JSON.parse(revData.data);\n\t\t\t\t\t\t// System metadata from content table + content fields from revision\n\t\t\t\t\t\tconst systemData: Record<string, unknown> = {};\n\t\t\t\t\t\tfor (const [key, mappedKey] of Object.entries(INCLUDE_IN_DATA)) {\n\t\t\t\t\t\t\tif (key in row) {\n\t\t\t\t\t\t\t\tif (DATE_COLUMNS.has(key)) {\n\t\t\t\t\t\t\t\t\tsystemData[mappedKey] = typeof row[key] === \"string\" ? new Date(row[key]) : null;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tsystemData[mappedKey] = row[key];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Use slug from revision metadata if present, else from content table\n\t\t\t\t\t\tconst slug = typeof parsed._slug === \"string\" ? parsed._slug : rowStr(row, \"slug\");\n\t\t\t\t\t\tconst revSlug = slug || rowStr(row, \"id\");\n\t\t\t\t\t\tconst revLocale = rowStr(row, \"locale\");\n\t\t\t\t\t\tconst shouldPrefixRev =\n\t\t\t\t\t\t\ti18nEnabled &&\n\t\t\t\t\t\t\trevLocale !== \"\" &&\n\t\t\t\t\t\t\t(revLocale !== i18nConfig.defaultLocale || i18nConfig.prefixDefaultLocale);\n\t\t\t\t\t\tconst revId = shouldPrefixRev ? `${revLocale}/${revSlug}` : revSlug;\n\t\t\t\t\t\t// SEO is not revisioned — it comes from the content row's\n\t\t\t\t\t\t// joined _emdash_seo columns, not the revision snapshot.\n\t\t\t\t\t\tconst revEntryData: Record<string, unknown> = {\n\t\t\t\t\t\t\t...systemData,\n\t\t\t\t\t\t\tslug,\n\t\t\t\t\t\t\t...mapRevisionData(parsed),\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst revSeo = extractSeo(row);\n\t\t\t\t\t\tif (revSeo) revEntryData.seo = revSeo;\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tid: revId,\n\t\t\t\t\t\t\tslug,\n\t\t\t\t\t\t\tstatus: rowStr(row, \"status\", \"draft\"),\n\t\t\t\t\t\t\tdata: revEntryData,\n\t\t\t\t\t\t\tcacheHint: {\n\t\t\t\t\t\t\t\ttags: [rowStr(row, \"id\")],\n\t\t\t\t\t\t\t\tlastModified: row.updated_at ? new Date(rowStr(row, \"updated_at\")) : undefined,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst entryData = mapRowToData(row);\n\t\t\t\tconst entrySeo = extractSeo(row);\n\t\t\t\tif (entrySeo) entryData.seo = entrySeo;\n\t\t\t\tstashFolded(entryData, row);\n\t\t\t\treturn {\n\t\t\t\t\tid: entryId,\n\t\t\t\t\tslug: rowStr(row, \"slug\"),\n\t\t\t\t\tstatus: rowStr(row, \"status\", \"draft\"),\n\t\t\t\t\tdata: entryData,\n\t\t\t\t\tcacheHint: {\n\t\t\t\t\t\ttags: [rowStr(row, \"id\")],\n\t\t\t\t\t\tlastModified: row.updated_at ? new Date(rowStr(row, \"updated_at\")) : undefined,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\t// Handle missing table gracefully - return undefined (not found).\n\t\t\t\t// This happens before migrations have run.\n\t\t\t\tif (isMissingTableError(error)) {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\treturn {\n\t\t\t\t\terror: new Error(`Failed to load entry: ${message}`),\n\t\t\t\t};\n\t\t\t}\n\t\t},\n\t};\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"query-DgUHZLWB.mjs","names":[],"sources":["../src/visual-editing/editable.ts","../src/query.ts"],"sourcesContent":["/**\n * Visual editing annotation system\n *\n * Creates Proxy objects that emit data-emdash-ref attributes when spread onto elements.\n */\n\nexport interface CMSAnnotation {\n\tcollection: string;\n\tid: string;\n\tfield?: string;\n\t/** Entry status — only present on entry-level annotations (not field-level) */\n\tstatus?: string;\n\t/** Whether the entry has unpublished draft changes */\n\thasDraft?: boolean;\n}\n\n/** The shape returned when spreading an edit annotation onto an element */\nexport interface FieldAnnotation {\n\t\"data-emdash-ref\": string;\n}\n\nexport interface EditableOptions {\n\t/** Entry status: \"draft\", \"published\", \"scheduled\" */\n\tstatus?: string;\n\t/** true when draftRevisionId exists and differs from liveRevisionId */\n\thasDraft?: boolean;\n}\n\n/**\n * Create an editable proxy for an entry.\n *\n * Usage:\n * - `{...entry.edit}` - entry-level annotation (includes status/hasDraft)\n * - `{...entry.edit.title}` - field-level annotation\n * - `{...entry.edit['nested.field']}` - nested field (bracket notation)\n */\nexport function createEditable(\n\tcollection: string,\n\tid: string,\n\toptions?: EditableOptions,\n): EditProxy {\n\tconst base: CMSAnnotation = {\n\t\tcollection,\n\t\tid,\n\t\t...(options?.status && { status: options.status }),\n\t\t...(options?.hasDraft && { hasDraft: true }),\n\t};\n\n\treturn new Proxy({} as EditProxy, {\n\t\tget(_, prop) {\n\t\t\tif (prop === \"toJSON\") return () => ({ \"data-emdash-ref\": JSON.stringify(base) });\n\t\t\tif (typeof prop === \"symbol\") return undefined;\n\n\t\t\t// data-emdash-ref access returns the entry-level string\n\t\t\tif (prop === \"data-emdash-ref\") return JSON.stringify(base);\n\n\t\t\t// Field-level: return a FieldAnnotation for the specific field\n\t\t\treturn {\n\t\t\t\t\"data-emdash-ref\": JSON.stringify({ ...base, field: String(prop) }),\n\t\t\t} satisfies FieldAnnotation;\n\t\t},\n\t\townKeys() {\n\t\t\treturn [\"data-emdash-ref\"];\n\t\t},\n\t\tgetOwnPropertyDescriptor(_, prop) {\n\t\t\tif (prop === \"data-emdash-ref\") {\n\t\t\t\treturn {\n\t\t\t\t\tconfigurable: true,\n\t\t\t\t\tenumerable: true,\n\t\t\t\t\tvalue: JSON.stringify(base),\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn undefined;\n\t\t},\n\t});\n}\n\n/**\n * Create a noop proxy for production mode.\n * Spreading this produces no attributes.\n */\nexport function createNoop(): EditProxy {\n\treturn new Proxy({} as EditProxy, {\n\t\tget(_, prop) {\n\t\t\tif (typeof prop === \"symbol\") return undefined;\n\t\t\t// All property access returns undefined in noop mode\n\t\t\treturn undefined;\n\t\t},\n\t\townKeys() {\n\t\t\treturn [];\n\t\t},\n\t\tgetOwnPropertyDescriptor() {\n\t\t\treturn undefined;\n\t\t},\n\t});\n}\n\n/**\n * Visual editing proxy type.\n *\n * Spread directly onto elements for entry-level annotations: `{...entry.edit}`\n * Access a field for field-level annotations: `{...entry.edit.title}`\n *\n * In production, spreading produces no attributes (noop).\n */\nexport type EditProxy = {\n\treadonly [field: string]: Partial<FieldAnnotation>;\n};\n","/// <reference types=\"astro/client\" />\n/**\n * Query functions for EmDash content\n *\n * These wrap Astro's getLiveCollection/getLiveEntry with type filtering.\n * Use these instead of calling Astro's functions directly.\n *\n * Error handling follows Astro's pattern - returns { entries/entry, error }\n * so callers can gracefully handle errors (including 404s).\n *\n * Preview mode is handled implicitly via ALS request context —\n * no parameters needed. The middleware verifies the preview token\n * and sets the context; query functions read it automatically.\n *\n * The triple-slash directive above pulls in the ambient declaration for\n * `astro:content` (used by the dynamic imports below) so this source\n * file typechecks even when reached transitively by a sibling package\n * whose tsconfig doesn't list `astro/client` in `compilerOptions.types`.\n *\n * Note: the directive is stripped from the compiled output (`dist/*`)\n * by tsdown, so it does not propagate to downstream consumers of the\n * published package. Consumers are Astro sites and already provide their\n * own `astro/client` ambient surface anyway, so the runtime dynamic\n * import resolves there at typecheck time without our help.\n */\n\nimport { encodeCursor } from \"./database/repositories/types.js\";\nimport { getFallbackChain, getI18nConfig, isI18nEnabled } from \"./i18n/config.js\";\nimport {\n\tCURSOR_RAW_VALUES,\n\tFOLDED_BYLINES,\n\tFOLDED_TERMS,\n\ttype WhereRange,\n\ttype WhereValue,\n} from \"./loader.js\";\nimport {\n\tcachedQuery,\n\tcontentNamespaces,\n\tinvalidateSchemaObjectCache,\n} from \"./object-cache/index.js\";\nimport { requestCached } from \"./request-cache.js\";\nimport { getRequestContext } from \"./request-context.js\";\nimport type { TaxonomyTerm } from \"./taxonomies/types.js\";\nimport { isMissingTableError } from \"./utils/db-errors.js\";\nimport {\n\tcreateEditable,\n\tcreateNoop,\n\ttype EditProxy,\n\ttype EditableOptions,\n} from \"./visual-editing/editable.js\";\n\n/**\n * Collection type registry for type-safe queries.\n *\n * This interface is extended by the generated emdash-env.d.ts file\n * to provide type inference for collection names and their data shapes.\n *\n * @example\n * ```ts\n * // In emdash-env.d.ts (generated):\n * declare module \"emdash\" {\n * interface EmDashCollections {\n * posts: { title: string; content: PortableTextBlock[]; };\n * pages: { title: string; body: PortableTextBlock[]; };\n * }\n * }\n *\n * // Then in your code:\n * const { entries } = await getEmDashCollection(\"posts\");\n * // entries[0].data.title is typed as string\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface EmDashCollections {}\n\n/**\n * Helper type to infer the data type for a collection.\n * Returns the registered type if known, otherwise falls back to Record<string, unknown>.\n */\nexport type InferCollectionData<T extends string> = T extends keyof EmDashCollections\n\t? EmDashCollections[T]\n\t: Record<string, unknown>;\n\n/**\n * Sort direction\n */\nexport type SortDirection = \"asc\" | \"desc\";\n\n/**\n * Order by specification - field name to direction\n * @example { created_at: \"desc\" } - Sort by created_at descending\n * @example { title: \"asc\" } - Sort by title ascending\n * @example { published_at: \"desc\", title: \"asc\" } - Multi-field sort\n */\nexport type OrderBySpec = Record<string, SortDirection>;\n\nexport type { WhereRange, WhereValue };\n\nexport interface CollectionFilter {\n\tstatus?: \"draft\" | \"published\" | \"archived\";\n\tlimit?: number;\n\t/**\n\t * Opaque cursor for keyset pagination.\n\t * Pass the `nextCursor` value from a previous result to fetch the next page.\n\t * @example\n\t * ```ts\n\t * const cursor = Astro.url.searchParams.get(\"cursor\") ?? undefined;\n\t * const { entries, nextCursor } = await getEmDashCollection(\"posts\", {\n\t * limit: 10,\n\t * cursor,\n\t * });\n\t * ```\n\t */\n\tcursor?: string;\n\t/**\n\t * Filter by field values, taxonomy terms, byline credits, or ranges.\n\t *\n\t * Taxonomy names are detected automatically and filtered via JOIN.\n\t * The reserved `byline` key filters by byline credit (any credit, not\n\t * just the primary one) via the `_emdash_content_bylines` junction\n\t * table; its value is one or more byline translation groups. This\n\t * matches co-authored entries, which `primary_byline_id` alone misses.\n\t * Other keys are treated as column filters on the content table.\n\t *\n\t * @example { category: 'news' } - Filter by taxonomy term\n\t * @example { category: ['news', 'featured'] } - Filter by multiple terms (OR)\n\t * @example { byline: '01HXYZ...' } - Entries credited to a byline (any position)\n\t * @example { byline: ['01HXYZ...', '01HABC...'] } - Credited to any of these bylines (OR)\n\t * @example { series: 'main' } - Exact match on a content field\n\t * @example { published_at: { gte: '2024-01-01', lt: '2025-01-01' } } - Date range\n\t */\n\twhere?: Record<string, WhereValue>;\n\t/**\n\t * Order results by field(s)\n\t * @default { created_at: \"desc\" }\n\t * @example { created_at: \"desc\" } - Sort by created_at descending (default)\n\t * @example { title: \"asc\" } - Sort by title ascending\n\t * @example { published_at: \"desc\", title: \"asc\" } - Multi-field sort\n\t */\n\torderBy?: OrderBySpec;\n\t/**\n\t * Filter by locale. When set, only returns entries in this locale.\n\t * Only relevant when i18n is configured.\n\t * @example \"en\" — English entries only\n\t * @example \"fr\" — French entries only\n\t */\n\tlocale?: string;\n}\n\nexport interface ContentEntry<T = Record<string, unknown>> {\n\tid: string;\n\tdata: T;\n\t/** Visual editing annotations. Spread onto elements: {...entry.edit.title} */\n\tedit: EditProxy;\n}\n\n/** Cache hint returned by the content loader for route caching */\nexport interface CacheHint {\n\ttags?: string[];\n\tlastModified?: Date;\n}\n\n/**\n * Result from getEmDashCollection\n */\nexport interface CollectionResult<T> {\n\t/** The entries (empty array if error or none found) */\n\tentries: ContentEntry<T>[];\n\t/** Error if the query failed */\n\terror?: Error;\n\t/** Cache hint for route caching (pass to Astro.cache.set()) */\n\tcacheHint: CacheHint;\n\t/**\n\t * Opaque cursor for the next page.\n\t * Undefined when there are no more results.\n\t * Pass this as `cursor` in the next query to get the next page.\n\t */\n\tnextCursor?: string;\n}\n\n/**\n * Result from getEmDashEntry\n */\nexport interface EntryResult<T> {\n\t/** The entry, or null if not found */\n\tentry: ContentEntry<T> | null;\n\t/** Error if the query failed (not set for \"not found\", only for actual errors) */\n\terror?: Error;\n\t/** Whether we're in preview mode (valid token was provided) */\n\tisPreview: boolean;\n\t/** Set when a fallback locale was used instead of the requested locale */\n\tfallbackLocale?: string;\n\t/** Cache hint for route caching (pass to Astro.cache.set()) */\n\tcacheHint: CacheHint;\n}\n\nconst COLLECTION_NAME = \"_emdash\";\n\n/** Symbol key for edit metadata on PT arrays — avoids collision with user data */\nconst EMDASH_EDIT = Symbol.for(\"__emdash\");\n\n/** Edit metadata attached to PT arrays in edit mode */\nexport interface EditFieldMeta {\n\tcollection: string;\n\tid: string;\n\tfield: string;\n}\n\n/** Type guard for EditFieldMeta */\nfunction isEditFieldMeta(value: unknown): value is EditFieldMeta {\n\tif (typeof value !== \"object\" || value === null) return false;\n\tif (!(\"collection\" in value) || !(\"id\" in value) || !(\"field\" in value)) return false;\n\t// After `in` checks, TS narrows to Record<\"collection\" | \"id\" | \"field\", unknown>\n\tconst { collection, id, field } = value;\n\treturn typeof collection === \"string\" && typeof id === \"string\" && typeof field === \"string\";\n}\n\n/**\n * Read edit metadata from a value (returns undefined if not tagged).\n * Uses Object.getOwnPropertyDescriptor to access Symbol-keyed property\n * without an unsafe type assertion.\n */\nexport function getEditMeta(value: unknown): EditFieldMeta | undefined {\n\tif (value && typeof value === \"object\") {\n\t\tconst desc = Object.getOwnPropertyDescriptor(value, EMDASH_EDIT);\n\t\tconst meta: unknown = desc?.value;\n\t\tif (isEditFieldMeta(meta)) {\n\t\t\treturn meta;\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/**\n * Tag PT-like arrays in entry data with edit metadata (non-enumerable).\n * A PT array is identified by: is an array, first element has _type property.\n */\nfunction tagEditableFields(data: Record<string, unknown>, collection: string, id: string): void {\n\tfor (const [field, value] of Object.entries(data)) {\n\t\tif (\n\t\t\tArray.isArray(value) &&\n\t\t\tvalue.length > 0 &&\n\t\t\tvalue[0] &&\n\t\t\ttypeof value[0] === \"object\" &&\n\t\t\t\"_type\" in value[0]\n\t\t) {\n\t\t\tObject.defineProperty(value, EMDASH_EDIT, {\n\t\t\t\tvalue: { collection, id, field } satisfies EditFieldMeta,\n\t\t\t\tenumerable: false,\n\t\t\t\tconfigurable: true,\n\t\t\t});\n\t\t}\n\t}\n}\n\n/** Safely read a string field from a Record, with optional fallback */\nfunction dataStr(data: Record<string, unknown>, key: string, fallback = \"\"): string {\n\tconst val = data[key];\n\treturn typeof val === \"string\" ? val : fallback;\n}\n\n/** Safely read a date-like field from a Record */\nfunction dataDate(data: Record<string, unknown>, key: string): Date | undefined {\n\tconst val = data[key];\n\tif (val instanceof Date) {\n\t\treturn Number.isNaN(val.getTime()) ? undefined : val;\n\t}\n\tif (typeof val !== \"string\" && typeof val !== \"number\") return undefined;\n\tconst date = new Date(val);\n\treturn Number.isNaN(date.getTime()) ? undefined : date;\n}\n\n/** Type guard for Record<string, unknown> */\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** Extract data as Record from an Astro entry (which is any-typed) */\nfunction entryData(entry: { data?: unknown }): Record<string, unknown> {\n\treturn isRecord(entry.data) ? entry.data : {};\n}\n\n/** Extract the database ID from entry data (data.id is the ULID, entry.id is the slug) */\nfunction entryDatabaseId(entry: { id: string; data?: unknown }): string {\n\tconst d = entryData(entry);\n\treturn dataStr(d, \"id\") || entry.id;\n}\n\n/** Extract edit options from entry data for the proxy */\nfunction entryEditOptions(entry: { data?: unknown }): EditableOptions {\n\tconst data = entryData(entry);\n\tconst status = dataStr(data, \"status\", \"draft\");\n\tconst draftRevisionId = dataStr(data, \"draftRevisionId\") || undefined;\n\tconst liveRevisionId = dataStr(data, \"liveRevisionId\") || undefined;\n\tconst hasDraft = !!draftRevisionId && draftRevisionId !== liveRevisionId;\n\treturn { status, hasDraft };\n}\n\n/**\n * Get all entries of a content type\n *\n * Returns { entries, error } for graceful error handling.\n *\n * When emdash-env.d.ts is generated, the collection name will be\n * type-checked and the return type will be inferred automatically.\n *\n * @example\n * ```ts\n * import { getEmDashCollection } from \"emdash\";\n *\n * const { entries: posts, error } = await getEmDashCollection(\"posts\");\n * if (error) {\n * console.error(\"Failed to load posts:\", error);\n * return;\n * }\n * // posts[0].data.title is typed (if emdash-env.d.ts exists)\n *\n * // With filters\n * const { entries: drafts } = await getEmDashCollection(\"posts\", { status: \"draft\" });\n * ```\n */\nexport async function getEmDashCollection<T extends string, D = InferCollectionData<T>>(\n\ttype: T,\n\tfilter?: CollectionFilter,\n): Promise<CollectionResult<D>> {\n\t// Cache per (type, filter) within a single request. Edit mode and\n\t// preview are request-scoped and stable, so they don't need to be\n\t// part of the key. Widgets and layouts frequently request the same\n\t// collection shape as the page itself (e.g. a \"recent posts\" list\n\t// appears on the home page AND in the sidebar) — caching collapses\n\t// those duplicate queries, along with the bylines and taxonomy-term\n\t// hydration each call would otherwise re-do.\n\t//\n\t// Bucket small limits to a shared minimum so a page with several\n\t// \"recent N posts\" widgets at slightly different limits (e.g. a\n\t// post-detail page asking for 4 in the body and 5 in the sidebar)\n\t// shares one fetch + hydration round-trip rather than running two.\n\t// Cursor-paginated calls are exempt: their limit is part of the\n\t// pagination contract.\n\tconst bucketed = bucketFilter(filter);\n\tconst cached = await requestCached(collectionCacheKey(type, bucketed.fetchFilter), () =>\n\t\tloadCollectionCached<T, D>(type, bucketed.fetchFilter),\n\t);\n\treturn bucketed.requestedLimit === undefined\n\t\t? cached\n\t\t: sliceCollectionResult(cached, bucketed.requestedLimit, filter?.orderBy);\n}\n\n/** Shape of a cached collection snapshot (entries reduced to JSON-safe form). */\ninterface CachedCollectionValue {\n\tentries: unknown[];\n\tnextCursor?: string;\n\tcacheHint: CacheHint;\n}\n\n/**\n * Distributed (L2) read-through around {@link getEmDashCollectionUncached}.\n *\n * Caches a JSON-safe snapshot keyed by collection + filter + effective locale,\n * folding the shared `bylines`/`taxonomies` epochs into the key so renaming an\n * author or term invalidates affected lists. Errors are never cached.\n */\nasync function loadCollectionCached<T extends string, D = InferCollectionData<T>>(\n\ttype: T,\n\tfilter?: CollectionFilter,\n): Promise<CollectionResult<D>> {\n\tconst snapshot = await cachedQuery<ContentSnapshot<CachedCollectionValue>>({\n\t\tnamespace: contentNamespaces(type),\n\t\tkey: `collection:${collectionCacheKey(type, filter)}|loc=${effectiveLocaleKey(filter)}`,\n\t\tload: async () => {\n\t\t\tconst result = await getEmDashCollectionUncached<T, D>(type, filter);\n\t\t\tif (result.error) {\n\t\t\t\treturn { ok: false, error: result.error, cacheHint: result.cacheHint };\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tok: true,\n\t\t\t\tvalue: {\n\t\t\t\t\tentries: result.entries.map(entrySnapshot),\n\t\t\t\t\tnextCursor: result.nextCursor,\n\t\t\t\t\tcacheHint: result.cacheHint,\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t\tcacheable: (snap) => snap.ok,\n\t});\n\n\tif (!snapshot.ok) {\n\t\treturn { entries: [], error: snapshot.error, cacheHint: snapshot.cacheHint };\n\t}\n\treturn {\n\t\tentries: snapshot.value.entries.map((entry) => reviveEntry<D>(entry)),\n\t\tnextCursor: snapshot.value.nextCursor,\n\t\tcacheHint: snapshot.value.cacheHint,\n\t};\n}\n\n/**\n * Threshold for limit bucketing. Page templates routinely render small\n * \"recent posts\" widgets at limits 3-8; rounding those up to a single\n * shared bucket lets one fetch satisfy several widgets within a request.\n * Above this, the requested limit is honoured exactly — bucketing limit:50\n * to limit:64 would waste hydration work for callers fetching real pages.\n */\nconst BUCKET_LIMIT_THRESHOLD = 10;\n\ninterface BucketedFilter {\n\t/** Filter to pass to the loader (with limit possibly raised). */\n\tfetchFilter: CollectionFilter | undefined;\n\t/** Original limit; defined only when bucketing was applied. */\n\trequestedLimit: number | undefined;\n}\n\n/** @internal exported for unit tests; not part of the public API. */\nexport function bucketFilter(filter: CollectionFilter | undefined): BucketedFilter {\n\tconst limit = filter?.limit;\n\tif (\n\t\tlimit === undefined ||\n\t\tlimit >= BUCKET_LIMIT_THRESHOLD ||\n\t\tlimit <= 0 ||\n\t\tfilter?.cursor !== undefined\n\t) {\n\t\treturn { fetchFilter: filter, requestedLimit: undefined };\n\t}\n\treturn {\n\t\tfetchFilter: { ...filter, limit: BUCKET_LIMIT_THRESHOLD },\n\t\trequestedLimit: limit,\n\t};\n}\n\n/**\n * Slice a cached bucketed result down to the originally-requested limit\n * and recompute `nextCursor` from the row that would have been the\n * over-fetch detector for that limit. When truncation is needed, returns\n * a shallow-copied result with a new `entries` array; otherwise returns\n * the cached result unchanged (including error results and results\n * already within the requested limit).\n */\n/** @internal exported for unit tests; not part of the public API. */\nexport function sliceCollectionResult<D>(\n\tcached: CollectionResult<D>,\n\tlimit: number,\n\torderBy: OrderBySpec | undefined,\n): CollectionResult<D> {\n\tif (cached.error) return cached;\n\tif (cached.entries.length <= limit) return cached;\n\tconst sliced = cached.entries.slice(0, limit);\n\t// Mirror the loader's encoding: cursor points at the last returned row,\n\t// so \"next page\" picks up at the row immediately after it. See\n\t// buildCursorCondition in loader.ts — it filters strictly past this row.\n\tconst lastEntry = sliced.at(-1);\n\tconst nextCursor = lastEntry ? encodeEntryCursor(lastEntry, orderBy) : undefined;\n\treturn { ...cached, entries: sliced, nextCursor };\n}\n\n/** Map of database column names to camelCase keys present on entry.data. */\nconst ENTRY_DATA_KEY_MAP: Record<string, string> = {\n\tcreated_at: \"createdAt\",\n\tupdated_at: \"updatedAt\",\n\tpublished_at: \"publishedAt\",\n\tscheduled_at: \"scheduledAt\",\n\tauthor_id: \"authorId\",\n\tprimary_byline_id: \"primaryBylineId\",\n};\n\n// Mirror loader.ts FIELD_NAME_PATTERN. Kept in sync intentionally — diverging\n// would let the encoder accept a field name the loader's getPrimarySort then\n// rejected, producing a cursor that paginates against a different column.\nconst FIELD_NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\n\n/**\n * Encode a `nextCursor` from a content entry, mirroring the loader's\n * encoding scheme: `(orderValue, id)` where `orderValue` is the primary\n * sort field's stringified value. For date columns, reads the raw DB\n * string the loader stashed via CURSOR_RAW_VALUES — round-tripping the\n * parsed Date through `toISOString()` would lose precision for stored\n * values that aren't already ISO-with-milliseconds.\n */\nfunction encodeEntryCursor<D>(\n\tentry: ContentEntry<D>,\n\torderBy: OrderBySpec | undefined,\n): string | undefined {\n\tconst data = entryData(entry);\n\tconst id = dataStr(data, \"id\");\n\tif (!id) return undefined;\n\n\t// Match loader.ts getPrimarySort: take the first valid field, default to created_at.\n\tlet dbField = \"created_at\";\n\tif (orderBy) {\n\t\tfor (const field of Object.keys(orderBy)) {\n\t\t\tif (FIELD_NAME_PATTERN.test(field)) {\n\t\t\t\tdbField = field;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Date columns: prefer the raw stored string captured by the loader so\n\t// the cursor matches what a direct loader fetch would emit, regardless\n\t// of how the DB stored the timestamp.\n\tconst rawDateValuesRaw = Reflect.get(data, CURSOR_RAW_VALUES);\n\tif (rawDateValuesRaw !== null && typeof rawDateValuesRaw === \"object\") {\n\t\tconst raw = Reflect.get(rawDateValuesRaw, dbField);\n\t\tif (typeof raw === \"string\") return encodeCursor(raw, id);\n\t}\n\n\tconst dataKey = ENTRY_DATA_KEY_MAP[dbField] ?? dbField;\n\tconst value = data[dataKey];\n\tlet orderValue: string;\n\tif (value instanceof Date) {\n\t\torderValue = value.toISOString();\n\t} else if (typeof value === \"string\" || typeof value === \"number\") {\n\t\torderValue = String(value);\n\t} else {\n\t\t// Match the loader's empty-string fallback for null/undefined order\n\t\t// values so cursor decoding stays valid even at the boundary.\n\t\torderValue = \"\";\n\t}\n\treturn encodeCursor(orderValue, id);\n}\n\n/**\n * Build a canonical cache key for `getEmDashCollection`.\n *\n * `JSON.stringify` is insertion-order-sensitive, so two callers passing\n * semantically identical filters with different key orders would miss\n * the cache. We fix the top-level field order and sort `where` keys\n * (order there is irrelevant), while preserving `orderBy` key order\n * because that's the sort priority.\n */\nfunction collectionCacheKey(type: string, filter?: CollectionFilter): string {\n\tif (!filter) return `collection:${type}:`;\n\tconst parts = [\n\t\tfilter.status ?? \"\",\n\t\tfilter.limit ?? \"\",\n\t\tfilter.cursor ?? \"\",\n\t\tfilter.where ? stableStringify(filter.where) : \"\",\n\t\tfilter.orderBy ? JSON.stringify(filter.orderBy) : \"\",\n\t\tfilter.locale ?? \"\",\n\t];\n\treturn `collection:${type}:${parts.join(\"|\")}`;\n}\n\nfunction stableStringify(value: Record<string, unknown>): string {\n\treturn JSON.stringify(stableOrder(value));\n}\n\nfunction stableOrder(value: Record<string, unknown>): Record<string, unknown> {\n\tconst keys = Object.keys(value).toSorted();\n\tconst ordered: Record<string, unknown> = {};\n\tfor (const k of keys) {\n\t\tconst v = value[k];\n\t\tif (isRecord(v)) {\n\t\t\tordered[k] = stableOrder(v);\n\t\t} else {\n\t\t\tordered[k] = v;\n\t\t}\n\t}\n\treturn ordered;\n}\n\n// ── Object-cache (L2) serialization for content reads ───────────────────────\n//\n// Content entries can't be stored verbatim: each carries a non-serializable\n// `edit` proxy (a function) and a non-enumerable `CURSOR_RAW_VALUES` symbol on\n// `data` (raw date strings used to reproduce the loader's pagination cursor).\n// We reduce each entry to a JSON-safe snapshot before caching — copying the\n// cursor-raw values into an enumerable field and dropping `edit` — then rebuild\n// the symbol and re-attach a no-op `edit` on the way out. The object cache's\n// codec preserves `Date` instances, so timestamps survive the round-trip.\n//\n// L2 is only consulted for anonymous, non-preview, non-edit requests (see\n// `shouldBypass` in object-cache), where `edit` is always the no-op variant —\n// so dropping and recreating it is lossless.\n\n/** Enumerable field carrying the {@link CURSOR_RAW_VALUES} payload in snapshots. */\nconst CURSOR_RAW_FIELD = \"__emdashCursorRaw\";\n\n/** Result wrapper distinguishing a cached error from a cacheable success. */\ntype ContentSnapshot<S> =\n\t| { ok: true; value: S }\n\t| { ok: false; error?: Error; cacheHint: CacheHint };\n\nfunction entrySnapshot<D>(entry: ContentEntry<D>): Record<string, unknown> {\n\tconst data = entryData(entry);\n\tconst rawCursor = Reflect.get(data, CURSOR_RAW_VALUES);\n\t// Drop the `edit` function; copy enumerable data + the cursor-raw values.\n\tconst { edit: _edit, ...rest } = entry as ContentEntry<D> & { edit?: unknown };\n\treturn {\n\t\t...rest,\n\t\tdata: { ...data, [CURSOR_RAW_FIELD]: rawCursor ?? {} },\n\t};\n}\n\nfunction reviveEntry<D>(raw: unknown): ContentEntry<D> {\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- snapshot shape produced by entrySnapshot\n\tconst entry = raw as Record<string, unknown>;\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- snapshot `data` is always a record\n\tconst data: Record<string, unknown> = { ...(entry.data as Record<string, unknown>) };\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- snapshot field written by entrySnapshot\n\tconst rawCursor = (data[CURSOR_RAW_FIELD] as Record<string, string> | undefined) ?? {};\n\tdelete data[CURSOR_RAW_FIELD];\n\tObject.defineProperty(data, CURSOR_RAW_VALUES, {\n\t\tvalue: rawCursor,\n\t\tenumerable: false,\n\t\tconfigurable: false,\n\t\twritable: false,\n\t});\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- rebuilt to the ContentEntry shape with a no-op edit proxy\n\treturn { ...entry, data, edit: createNoop() } as ContentEntry<D>;\n}\n\n/** Resolve the effective locale used by content reads, for the L2 cache key. */\nfunction effectiveLocaleKey(filter?: { locale?: string }): string {\n\tconst ctx = getRequestContext();\n\tconst i18nConfig = getI18nConfig();\n\treturn (\n\t\tfilter?.locale ?? ctx?.locale ?? (isI18nEnabled() ? i18nConfig!.defaultLocale : undefined) ?? \"\"\n\t);\n}\n\nasync function getEmDashCollectionUncached<T extends string, D = InferCollectionData<T>>(\n\ttype: T,\n\tfilter?: CollectionFilter,\n): Promise<CollectionResult<D>> {\n\t// Dynamic import to avoid build-time issues\n\tconst { getLiveCollection } = await import(\"astro:content\");\n\n\t// Resolve locale: explicit filter > ALS context > defaultLocale (when i18n enabled)\n\t// Without this, queries return all locale rows, producing broken IDs\n\tconst ctx = getRequestContext();\n\tconst i18nConfig = getI18nConfig();\n\tconst resolvedLocale =\n\t\tfilter?.locale ?? ctx?.locale ?? (isI18nEnabled() ? i18nConfig!.defaultLocale : undefined);\n\n\tconst requestedLimit = filter?.limit;\n\tconst result = await getLiveCollection(COLLECTION_NAME, {\n\t\ttype,\n\t\tstatus: filter?.status,\n\t\tlimit: requestedLimit && requestedLimit > 0 ? requestedLimit + 1 : filter?.limit,\n\t\tcursor: filter?.cursor,\n\t\twhere: filter?.where,\n\t\torderBy: filter?.orderBy,\n\t\tlocale: resolvedLocale,\n\t});\n\n\tconst { entries, error, cacheHint } = result;\n\n\tif (error) {\n\t\treturn { entries: [], error, cacheHint: {} };\n\t}\n\n\tconst hasMore = requestedLimit != null && requestedLimit > 0 && entries.length > requestedLimit;\n\tconst pageEntries = hasMore ? entries.slice(0, requestedLimit) : entries;\n\tconst nextCursor = hasMore ? encodeEntryCursor(pageEntries.at(-1), filter?.orderBy) : undefined;\n\n\tconst isEditMode = ctx?.editMode ?? false;\n\tconst entriesWithEdit = pageEntries.map((entry: ContentEntry<D>) => {\n\t\tconst dbId = entryDatabaseId(entry);\n\t\tif (isEditMode) {\n\t\t\ttagEditableFields(entryData(entry), type, dbId);\n\t\t}\n\t\treturn {\n\t\t\t...entry,\n\t\t\tedit: isEditMode ? createEditable(type, dbId, entryEditOptions(entry)) : createNoop(),\n\t\t};\n\t});\n\n\t// Eagerly hydrate bylines and taxonomy terms for all entries in parallel.\n\t// Both are independent queries, so running them concurrently halves the\n\t// round-trip cost on remote databases (D1 replicas, etc.).\n\tawait Promise.all([\n\t\thydrateEntryBylines(type, entriesWithEdit),\n\t\t// Hydrate terms in the same locale the content rows were resolved to,\n\t\t// otherwise localized entries get default-locale taxonomy terms (#1441).\n\t\thydrateEntryTerms(type, entriesWithEdit, resolvedLocale),\n\t]);\n\n\treturn { entries: entriesWithEdit, nextCursor, cacheHint: cacheHint ?? {} };\n}\n\n/**\n * Get a single entry by type and ID/slug\n *\n * Returns { entry, error, isPreview } for graceful error handling.\n * - entry is null if not found (not an error)\n * - error is set only for actual errors (db issues, etc.)\n *\n * Preview mode is detected automatically from request context (ALS).\n * When the URL has a valid `_preview` token, the middleware sets preview\n * context and this function serves draft revision data if available.\n *\n * @example\n * ```ts\n * import { getEmDashEntry } from \"emdash\";\n *\n * // Simple usage — preview just works via middleware\n * const { entry: post, isPreview, error } = await getEmDashEntry(\"posts\", \"my-slug\");\n * if (!post) return Astro.redirect(\"/404\");\n * ```\n */\nexport async function getEmDashEntry<T extends string, D = InferCollectionData<T>>(\n\ttype: T,\n\tid: string,\n\toptions?: { locale?: string },\n): Promise<EntryResult<D>> {\n\t// Dynamic import to avoid build-time issues\n\tconst { getLiveEntry } = await import(\"astro:content\");\n\n\t// Check ALS for preview and edit mode context\n\tconst ctx = getRequestContext();\n\tconst preview = ctx?.preview;\n\tconst isEditMode = ctx?.editMode ?? false;\n\tconst isPreviewMode = !!preview && preview.collection === type;\n\t// Edit mode implies preview — editors should see draft content\n\tconst serveDrafts = isPreviewMode || isEditMode;\n\n\t// Resolve locale: explicit option > ALS context > undefined (no filter)\n\tconst requestedLocale = options?.locale ?? ctx?.locale;\n\n\t/** Wrap a raw Astro entry with edit proxy, tagging editable fields if needed */\n\tfunction wrapEntry(raw: ContentEntry<D>): ContentEntry<D> {\n\t\tconst dbId = entryDatabaseId(raw);\n\t\tif (isEditMode) {\n\t\t\ttagEditableFields(entryData(raw), type, dbId);\n\t\t}\n\t\treturn {\n\t\t\t...raw,\n\t\t\tedit: isEditMode ? createEditable(type, dbId, entryEditOptions(raw)) : createNoop(),\n\t\t};\n\t}\n\n\t/** Check if an entry is publicly visible (published or scheduled past its time) */\n\tfunction isVisible(entry: ContentEntry<D>): boolean {\n\t\tconst data = entryData(entry);\n\t\tconst status = dataStr(data, \"status\");\n\t\tconst scheduledAt = dataDate(data, \"scheduledAt\");\n\t\tconst isPublished = status === \"published\";\n\t\tconst isScheduledAndReady =\n\t\t\tstatus === \"scheduled\" && scheduledAt !== undefined && scheduledAt.getTime() <= Date.now();\n\t\treturn isPublished || !!isScheduledAndReady;\n\t}\n\n\t/** True when an entry is scheduled to become visible at a future time. */\n\tfunction isPendingScheduled(entry: ContentEntry<D>): boolean {\n\t\tconst data = entryData(entry);\n\t\tif (dataStr(data, \"status\") !== \"scheduled\") return false;\n\t\tconst scheduledAt = dataDate(data, \"scheduledAt\");\n\t\treturn scheduledAt !== undefined && scheduledAt.getTime() > Date.now();\n\t}\n\n\t// Build the fallback chain: [requestedLocale, fallback1, ..., defaultLocale]\n\t// When i18n is disabled or no locale requested, just use a single-element chain\n\tconst localeChain =\n\t\trequestedLocale && isI18nEnabled() ? getFallbackChain(requestedLocale) : [requestedLocale];\n\n\t/** Return a successful EntryResult with bylines and taxonomy terms hydrated */\n\tasync function successResult(\n\t\twrapped: ContentEntry<D>,\n\t\topts: { isPreview: boolean; fallbackLocale?: string; cacheHint: CacheHint },\n\t): Promise<EntryResult<D>> {\n\t\t// Hydrate terms in the entry's resolved locale (fallback-aware) so a\n\t\t// localized entry never picks up default-locale taxonomy terms (#1441).\n\t\t// When i18n is disabled we leave the locale unset to preserve the\n\t\t// legacy \"do not filter by locale\" behaviour.\n\t\tconst termLocale = isI18nEnabled()\n\t\t\t? dataStr(entryData(wrapped), \"locale\") || undefined\n\t\t\t: undefined;\n\t\tawait Promise.all([\n\t\t\thydrateEntryBylines(type, [wrapped]),\n\t\t\thydrateEntryTerms(type, [wrapped], termLocale),\n\t\t]);\n\t\treturn {\n\t\t\tentry: wrapped,\n\t\t\tisPreview: opts.isPreview,\n\t\t\tfallbackLocale: opts.fallbackLocale,\n\t\t\tcacheHint: opts.cacheHint,\n\t\t};\n\t}\n\n\tif (serveDrafts) {\n\t\t// Draft mode: try each locale in the fallback chain\n\t\tfor (let i = 0; i < localeChain.length; i++) {\n\t\t\tconst locale = localeChain[i];\n\t\t\tconst fallbackLocale = i > 0 ? locale : undefined;\n\n\t\t\tconst {\n\t\t\t\tentry: baseEntry,\n\t\t\t\terror: baseError,\n\t\t\t\tcacheHint,\n\t\t\t} = await getLiveEntry(COLLECTION_NAME, {\n\t\t\t\ttype,\n\t\t\t\tid,\n\t\t\t\tlocale,\n\t\t\t});\n\n\t\t\tif (baseError) {\n\t\t\t\treturn { entry: null, error: baseError, isPreview: serveDrafts, cacheHint: {} };\n\t\t\t}\n\n\t\t\tif (!baseEntry) continue; // Try next locale in chain\n\n\t\t\t// Preview tokens are item-scoped: verify the resolved entry matches.\n\t\t\t// Edit mode (authenticated editors) has collection-wide draft access.\n\t\t\tif (isPreviewMode && !isEditMode) {\n\t\t\t\tconst dbId = entryDatabaseId(baseEntry);\n\t\t\t\tif (preview.id !== dbId && preview.id !== id) {\n\t\t\t\t\t// Token doesn't match — serve only if publicly visible, without draft access\n\t\t\t\t\tif (isVisible(baseEntry)) {\n\t\t\t\t\t\treturn successResult(wrapEntry(baseEntry), {\n\t\t\t\t\t\t\tisPreview: false,\n\t\t\t\t\t\t\tfallbackLocale,\n\t\t\t\t\t\t\tcacheHint: cacheHint ?? {},\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\t// Not visible — try next locale in fallback chain\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Check if entry has a draft revision — if so, re-fetch with revision data\n\t\t\tconst baseData = entryData(baseEntry);\n\t\t\tconst draftRevisionId = dataStr(baseData, \"draftRevisionId\") || undefined;\n\n\t\t\tif (draftRevisionId) {\n\t\t\t\tconst { entry: draftEntry, error: draftError } = await getLiveEntry(COLLECTION_NAME, {\n\t\t\t\t\ttype,\n\t\t\t\t\tid,\n\t\t\t\t\trevisionId: draftRevisionId,\n\t\t\t\t\tlocale,\n\t\t\t\t});\n\n\t\t\t\tif (!draftError && draftEntry) {\n\t\t\t\t\treturn successResult(wrapEntry(draftEntry), {\n\t\t\t\t\t\tisPreview: serveDrafts,\n\t\t\t\t\t\tfallbackLocale,\n\t\t\t\t\t\tcacheHint: cacheHint ?? {},\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn successResult(wrapEntry(baseEntry), {\n\t\t\t\tisPreview: serveDrafts,\n\t\t\t\tfallbackLocale,\n\t\t\t\tcacheHint: cacheHint ?? {},\n\t\t\t});\n\t\t}\n\n\t\t// No entry found in any locale\n\t\treturn { entry: null, isPreview: serveDrafts, cacheHint: {} };\n\t}\n\n\t// Normal mode: try each locale in the fallback chain, only return published\n\t// content. The full resolution (fallback chain + visibility + byline/term\n\t// hydration) is wrapped in the distributed L2 cache, keyed by the requested\n\t// locale. Preview/edit requests took the `serveDrafts` branch above and\n\t// never reach here; the object cache additionally bypasses them.\n\t// A scheduled entry becomes visible on a future clock tick, not on a write,\n\t// so an L2 snapshot taken before its time would keep it hidden past go-live\n\t// (until the publish sweep bumps the epoch or the TTL lapses). Mark such a\n\t// resolution time-sensitive and skip caching it.\n\tlet timeSensitive = false;\n\n\tconst resolveNormal = async (): Promise<EntryResult<D>> => {\n\t\tfor (let i = 0; i < localeChain.length; i++) {\n\t\t\tconst locale = localeChain[i];\n\t\t\tconst fallbackLocale = i > 0 ? locale : undefined;\n\n\t\t\tconst { entry, error, cacheHint } = await getLiveEntry(COLLECTION_NAME, { type, id, locale });\n\t\t\tif (error) {\n\t\t\t\treturn { entry: null, error, isPreview: false, cacheHint: {} };\n\t\t\t}\n\n\t\t\tif (entry && isVisible(entry)) {\n\t\t\t\treturn successResult(wrapEntry(entry), {\n\t\t\t\t\tisPreview: false,\n\t\t\t\t\tfallbackLocale,\n\t\t\t\t\tcacheHint: cacheHint ?? {},\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (entry && isPendingScheduled(entry)) {\n\t\t\t\ttimeSensitive = true;\n\t\t\t}\n\t\t\t// Entry not found or not visible in this locale — try next\n\t\t}\n\t\treturn { entry: null, isPreview: false, cacheHint: {} };\n\t};\n\n\tconst snapshot = await cachedQuery<ContentSnapshot<CachedEntryValue>>({\n\t\tnamespace: contentNamespaces(type),\n\t\tkey: `entry:${id}|loc=${requestedLocale ?? \"\"}`,\n\t\tload: async () => {\n\t\t\tconst result = await resolveNormal();\n\t\t\tif (result.error) {\n\t\t\t\treturn { ok: false, error: result.error, cacheHint: result.cacheHint };\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tok: true,\n\t\t\t\tvalue: {\n\t\t\t\t\tentry: result.entry ? entrySnapshot(result.entry) : null,\n\t\t\t\t\tisPreview: result.isPreview,\n\t\t\t\t\tfallbackLocale: result.fallbackLocale,\n\t\t\t\t\tcacheHint: result.cacheHint,\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t\tcacheable: (snap) => snap.ok && !timeSensitive,\n\t});\n\n\tif (!snapshot.ok) {\n\t\treturn { entry: null, error: snapshot.error, isPreview: false, cacheHint: snapshot.cacheHint };\n\t}\n\treturn {\n\t\tentry: snapshot.value.entry ? reviveEntry<D>(snapshot.value.entry) : null,\n\t\tisPreview: snapshot.value.isPreview,\n\t\tfallbackLocale: snapshot.value.fallbackLocale,\n\t\tcacheHint: snapshot.value.cacheHint,\n\t};\n}\n\n/** Shape of a cached single-entry snapshot. */\ninterface CachedEntryValue {\n\tentry: Record<string, unknown> | null;\n\tisPreview: boolean;\n\tfallbackLocale?: string;\n\tcacheHint: CacheHint;\n}\n\n/**\n * Eagerly hydrate byline data onto entry.data for one or more entries.\n *\n * Attaches `bylines` (array of ContentBylineCredit) and `byline`\n * (primary BylineSummary or null) to each entry's data object.\n * Uses batch queries to avoid N+1.\n *\n * Fails silently if the byline tables don't exist yet (pre-migration).\n */\nasync function hydrateEntryBylines<D>(type: string, entries: ContentEntry<D>[]): Promise<void> {\n\tif (entries.length === 0) return;\n\n\t// Fast path: bylines were folded into the content query. Parse the JSON\n\t// (no extra round trip) for the common case — explicit credits, no byline\n\t// custom fields, no author fallback. The query path below handles the rest:\n\t// - author fallback (entry has authorId but no explicit credit), and\n\t// - custom byline fields (can't be expressed in the folded subquery).\n\tif (entries.every((e) => FOLDED_BYLINES in entryData(e))) {\n\t\tconst parsed = entries.map((entry) => {\n\t\t\tconst data = entryData(entry);\n\t\t\tconst folded = Reflect.get(data, FOLDED_BYLINES);\n\t\t\tconst rows = Array.isArray(folded) ? folded : [];\n\t\t\tconst credits = rows\n\t\t\t\t.map((raw) => {\n\t\t\t\t\tconst b = raw?.byline ?? {};\n\t\t\t\t\treturn {\n\t\t\t\t\t\troleLabel: raw?.roleLabel ?? null,\n\t\t\t\t\t\tsortOrder: Number(raw?.sortOrder ?? 0),\n\t\t\t\t\t\tsource: \"explicit\" as const,\n\t\t\t\t\t\tbyline: { ...b, isGuest: Boolean(b.isGuest), customFields: {} },\n\t\t\t\t\t};\n\t\t\t\t})\n\t\t\t\t.toSorted((a, b) => a.sortOrder - b.sortOrder);\n\t\t\treturn { data, credits };\n\t\t});\n\n\t\t// Fall back to the full query path when the fold can't be trusted to be\n\t\t// complete: an entry with a byline reference (explicit primary, or an\n\t\t// author for the author-fallback) but no folded credits — e.g. a credit\n\t\t// in a different locale than the row, which the locale-correlated subquery\n\t\t// skips, or the author-fallback path which the fold doesn't express.\n\t\tlet needsQueryPath = parsed.some(\n\t\t\t(p) =>\n\t\t\t\tp.credits.length === 0 &&\n\t\t\t\t(dataStr(p.data, \"authorId\") !== \"\" || dataStr(p.data, \"primaryBylineId\") !== \"\"),\n\t\t);\n\t\tlet hasCustomFields = false;\n\t\tif (!needsQueryPath) {\n\t\t\ttry {\n\t\t\t\tconst { getDb } = await import(\"./loader.js\");\n\t\t\t\tconst db = await getDb();\n\t\t\t\tconst { getBylineFieldDefs } = await import(\"./bylines/field-defs-cache.js\");\n\t\t\t\thasCustomFields = (await getBylineFieldDefs(db)).length > 0;\n\t\t\t} catch (error) {\n\t\t\t\t// A missing table is expected pre-migration and means there are no\n\t\t\t\t// custom fields — the fold's values are complete. Any other error\n\t\t\t\t// (lock-init failure, dialect error) means the probe can't be\n\t\t\t\t// trusted, so fall back to the query path rather than risk serving\n\t\t\t\t// folded bylines with empty customFields.\n\t\t\t\tif (!isMissingTableError(error)) needsQueryPath = true;\n\t\t\t}\n\t\t}\n\n\t\tif (!needsQueryPath && !hasCustomFields) {\n\t\t\tfor (const p of parsed) {\n\t\t\t\tp.data.bylines = p.credits;\n\t\t\t\tp.data.byline = p.credits[0]?.byline ?? null;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\t// Fall through to the full query path for fallback / custom-field cases.\n\t}\n\n\ttry {\n\t\tconst { getBylinesForEntries } = await import(\"./bylines/index.js\");\n\n\t\tconst refs = entries\n\t\t\t.map((e) => {\n\t\t\t\tconst data = entryData(e);\n\t\t\t\tconst id = dataStr(data, \"id\");\n\t\t\t\tif (!id) return null;\n\t\t\t\treturn {\n\t\t\t\t\tid,\n\t\t\t\t\tauthorId: dataStr(data, \"authorId\") || null,\n\t\t\t\t\tprimaryBylineId: dataStr(data, \"primaryBylineId\") || null,\n\t\t\t\t\tlocale: dataStr(data, \"locale\") || null,\n\t\t\t\t};\n\t\t\t})\n\t\t\t.filter(\n\t\t\t\t(\n\t\t\t\t\tr,\n\t\t\t\t): r is {\n\t\t\t\t\tid: string;\n\t\t\t\t\tauthorId: string | null;\n\t\t\t\t\tprimaryBylineId: string | null;\n\t\t\t\t\tlocale: string | null;\n\t\t\t\t} => r !== null,\n\t\t\t);\n\t\tif (refs.length === 0) return;\n\n\t\tconst bylinesMap = await getBylinesForEntries(type, refs);\n\n\t\tfor (const entry of entries) {\n\t\t\tconst data = entryData(entry);\n\t\t\tconst dbId = dataStr(data, \"id\");\n\t\t\tif (!dbId) continue;\n\n\t\t\tconst credits = bylinesMap.get(dbId) ?? [];\n\t\t\tdata.bylines = credits;\n\t\t\tdata.byline = credits[0]?.byline ?? null;\n\t\t}\n\t} catch (err) {\n\t\t// Only swallow \"table not found\" errors from pre-migration databases.\n\t\t// Matches SQLite/D1 (\"no such table\") and PostgreSQL (\"relation/table\n\t\t// ... does not exist\") via the shared helper.\n\t\tif (!isMissingTableError(err)) {\n\t\t\tconst msg = err instanceof Error ? err.message : String(err);\n\t\t\tconsole.warn(\"[emdash] Failed to hydrate bylines:\", msg);\n\t\t}\n\t}\n}\n\n/**\n * Eagerly hydrate taxonomy term data onto entry.data for one or more entries.\n *\n * Attaches `terms` (Record keyed by taxonomy name with an array of TaxonomyTerm\n * values) to each entry's data object. Uses a single batched JOIN query across\n * all taxonomies so the cost is O(1) regardless of the number of entries or\n * taxonomies on the site.\n *\n * This eliminates the common N+1 pattern where templates loop over list\n * results and call getEntryTerms() per entry. With hydration, the list page\n * stays at a single round-trip for term data.\n *\n * `locale` must be the locale the entries were resolved to. It is forwarded to\n * `getAllTermsForEntries` so terms are returned in the entry's locale rather\n * than falling back to the request-context / default locale (#1441). Pass\n * `undefined` to keep the legacy \"do not filter by locale\" behaviour.\n *\n * Fails silently if the taxonomy tables don't exist yet (pre-migration).\n */\nasync function hydrateEntryTerms<D>(\n\ttype: string,\n\tentries: ContentEntry<D>[],\n\tlocale?: string,\n): Promise<void> {\n\tif (entries.length === 0) return;\n\n\t// Fast path: terms were folded into the content query. Group the JSON and\n\t// skip the separate content_taxonomies query.\n\tif (entries.every((e) => FOLDED_TERMS in entryData(e))) {\n\t\tconst perEntry: Array<{ entryId: string; byTaxonomy: Record<string, TaxonomyTerm[]> }> = [];\n\t\tfor (const entry of entries) {\n\t\t\tconst data = entryData(entry);\n\t\t\tconst folded = Reflect.get(data, FOLDED_TERMS);\n\t\t\tconst rows = Array.isArray(folded) ? folded : [];\n\t\t\tconst grouped: Record<string, TaxonomyTerm[]> = {};\n\t\t\tfor (const r of rows) {\n\t\t\t\tconst name = String(r?.name);\n\t\t\t\t(grouped[name] ??= []).push({\n\t\t\t\t\tid: r?.id,\n\t\t\t\t\tname,\n\t\t\t\t\tslug: r?.slug,\n\t\t\t\t\tlabel: r?.label,\n\t\t\t\t\tparentId: r?.parent_id ?? undefined,\n\t\t\t\t\tchildren: [],\n\t\t\t\t\tlocale: r?.locale,\n\t\t\t\t\ttranslationGroup: r?.translation_group,\n\t\t\t\t});\n\t\t\t}\n\t\t\t// Match getAllTermsForEntries' ORDER BY label (dropped from the\n\t\t\t// aggregate since SQLite and Postgres order it differently).\n\t\t\tfor (const [name, arr] of Object.entries(grouped)) {\n\t\t\t\tgrouped[name] = arr.toSorted((a, b) => String(a.label).localeCompare(String(b.label)));\n\t\t\t}\n\t\t\tdata.terms = grouped;\n\t\t\tconst entryId = dataStr(data, \"id\");\n\t\t\tif (entryId) perEntry.push({ entryId, byTaxonomy: grouped });\n\t\t}\n\t\t// Prime the per-entry request cache (wildcard + present taxonomies) so\n\t\t// subsequent getEntryTerms(...) calls in this render hit the cache instead\n\t\t// of issuing an N+1 query. No DB lookup — purely from the folded data.\n\t\tconst { primeFoldedEntryTerms } = await import(\"./taxonomies/index.js\");\n\t\tprimeFoldedEntryTerms(type, perEntry, { locale });\n\t\treturn;\n\t}\n\n\ttry {\n\t\tconst { getAllTermsForEntries } = await import(\"./taxonomies/index.js\");\n\n\t\tconst ids = entries.map((e) => dataStr(entryData(e), \"id\")).filter(Boolean);\n\t\tif (ids.length === 0) return;\n\n\t\tconst termsMap = await getAllTermsForEntries(type, ids, { locale });\n\n\t\tfor (const entry of entries) {\n\t\t\tconst data = entryData(entry);\n\t\t\tconst dbId = dataStr(data, \"id\");\n\t\t\tif (!dbId) continue;\n\n\t\t\tdata.terms = termsMap.get(dbId) ?? {};\n\t\t}\n\t} catch (err) {\n\t\t// Only swallow \"table not found\" errors from pre-migration databases.\n\t\t// Matches SQLite/D1 (\"no such table\") and PostgreSQL (\"relation/table\n\t\t// ... does not exist\") via the shared helper.\n\t\tif (!isMissingTableError(err)) {\n\t\t\tconst msg = err instanceof Error ? err.message : String(err);\n\t\t\tconsole.warn(\"[emdash] Failed to hydrate terms:\", msg);\n\t\t}\n\t}\n}\n\n/**\n * Translation summary for a single locale variant\n */\nexport interface TranslationSummary {\n\t/** Content item ID */\n\tid: string;\n\t/** Locale code (e.g. \"en\", \"fr\") */\n\tlocale: string;\n\t/** URL slug */\n\tslug: string | null;\n\t/** Current status */\n\tstatus: string;\n}\n\n/**\n * Result from getTranslations\n */\nexport interface TranslationsResult {\n\t/** The translation group ID (shared across locales) */\n\ttranslationGroup: string;\n\t/** All locale variants in this group */\n\ttranslations: TranslationSummary[];\n\t/** Error if the query failed */\n\terror?: Error;\n}\n\n/**\n * Get all translations of a content item.\n *\n * Given a content entry, returns all locale variants that share the same\n * translation group. This is useful for building language switcher UI.\n *\n * @example\n * ```ts\n * import { getEmDashEntry, getTranslations } from \"emdash\";\n *\n * const { entry: post } = await getEmDashEntry(\"posts\", \"hello-world\", { locale: \"en\" });\n * const { translations } = await getTranslations(\"posts\", post.data.id);\n * // translations = [{ id: \"...\", locale: \"en\", slug: \"hello-world\", status: \"published\" }, ...]\n * ```\n */\nexport async function getTranslations(type: string, id: string): Promise<TranslationsResult> {\n\ttry {\n\t\tconst db = (await import(\"./loader.js\")).getDb;\n\t\tconst dbInstance = await db();\n\t\tconst { ContentRepository } = await import(\"./database/repositories/content.js\");\n\t\tconst repo = new ContentRepository(dbInstance);\n\n\t\t// Find the item to get its translation group\n\t\tconst item = await repo.findByIdOrSlug(type, id);\n\t\tif (!item) {\n\t\t\treturn {\n\t\t\t\ttranslationGroup: \"\",\n\t\t\t\ttranslations: [],\n\t\t\t\terror: new Error(`Content item not found: ${id}`),\n\t\t\t};\n\t\t}\n\n\t\tconst group = item.translationGroup || item.id;\n\t\tconst translations = await repo.findTranslations(type, group);\n\n\t\treturn {\n\t\t\ttranslationGroup: group,\n\t\t\ttranslations: translations.map((t) => ({\n\t\t\t\tid: t.id,\n\t\t\t\tlocale: t.locale || \"en\",\n\t\t\t\tslug: t.slug,\n\t\t\t\tstatus: t.status,\n\t\t\t})),\n\t\t};\n\t} catch (error) {\n\t\treturn {\n\t\t\ttranslationGroup: \"\",\n\t\t\ttranslations: [],\n\t\t\terror: error instanceof Error ? error : new Error(String(error)),\n\t\t};\n\t}\n}\n\n/**\n * Result from resolveEmDashPath\n */\nexport interface ResolvePathResult<T = Record<string, unknown>> {\n\t/** The matched entry */\n\tentry: ContentEntry<T>;\n\t/** The collection slug that matched */\n\tcollection: string;\n\t/** Extracted parameters from the URL pattern (e.g. { slug: \"my-post\" }) */\n\tparams: Record<string, string>;\n}\n\n/** Matches `{paramName}` placeholders in URL patterns */\nconst URL_PARAM_PATTERN = /\\{(\\w+)\\}/g;\n\n/** Convert a URL pattern like \"/blog/{slug}\" to a regex and param name list */\nfunction patternToRegex(pattern: string): { regex: RegExp; paramNames: string[] } {\n\tconst paramNames: string[] = [];\n\tconst regexStr = pattern.replace(URL_PARAM_PATTERN, (_match, name: string) => {\n\t\tparamNames.push(name);\n\t\treturn \"([^/]+)\";\n\t});\n\treturn { regex: new RegExp(`^${regexStr}$`), paramNames };\n}\n\n/** Cached compiled URL patterns for resolveEmDashPath */\ninterface CachedPattern {\n\tslug: string;\n\tregex: RegExp;\n\tparamNames: string[];\n}\nlet cachedUrlPatterns: CachedPattern[] | null = null;\n\n/**\n * Invalidate the cached URL patterns used by resolveEmDashPath.\n * Call when collection URL patterns change (schema updates).\n *\n * Also busts the distributed schema cache (collection metadata such as\n * `commentsEnabled`, `supports`, fields read by `getCollectionInfo`), since\n * every schema-mutation path already routes through here.\n */\nexport function invalidateUrlPatternCache(): void {\n\tcachedUrlPatterns = null;\n\tinvalidateSchemaObjectCache();\n}\n\n/**\n * Resolve a URL path to a content entry by matching against collection URL patterns.\n *\n * Loads all collections with a `urlPattern` set, converts each pattern to a regex,\n * and tests the given path. On match, extracts the slug and fetches the entry.\n *\n * @example\n * ```ts\n * import { resolveEmDashPath } from \"emdash\";\n *\n * // Given pages with urlPattern \"/{slug}\" and posts with \"/blog/{slug}\":\n * const result = await resolveEmDashPath(\"/blog/hello-world\");\n * if (result) {\n * console.log(result.collection); // \"posts\"\n * console.log(result.params.slug); // \"hello-world\"\n * console.log(result.entry.data); // post data\n * }\n * ```\n */\nexport async function resolveEmDashPath<T = Record<string, unknown>>(\n\tpath: string,\n): Promise<ResolvePathResult<T> | null> {\n\t// Build and cache compiled patterns on first call\n\tif (!cachedUrlPatterns) {\n\t\tconst { getDb } = await import(\"./loader.js\");\n\t\tconst { SchemaRegistry } = await import(\"./schema/registry.js\");\n\t\tconst db = await getDb();\n\t\tconst registry = new SchemaRegistry(db);\n\t\tconst collections = await registry.listCollections();\n\n\t\tcachedUrlPatterns = [];\n\t\tfor (const collection of collections) {\n\t\t\tif (!collection.urlPattern) continue;\n\t\t\tconst { regex, paramNames } = patternToRegex(collection.urlPattern);\n\t\t\tcachedUrlPatterns.push({ slug: collection.slug, regex, paramNames });\n\t\t}\n\t}\n\n\tfor (const pattern of cachedUrlPatterns) {\n\t\tconst match = path.match(pattern.regex);\n\t\tif (!match) continue;\n\n\t\t// Extract params\n\t\tconst params: Record<string, string> = {};\n\t\tfor (let i = 0; i < pattern.paramNames.length; i++) {\n\t\t\tparams[pattern.paramNames[i]] = match[i + 1];\n\t\t}\n\n\t\t// Look up entry by slug (most common pattern)\n\t\tconst slug = params.slug;\n\t\tif (!slug) continue;\n\n\t\tconst { entry } = await getEmDashEntry<string, T>(pattern.slug, slug);\n\t\tif (entry) {\n\t\t\treturn { entry, collection: pattern.slug, params };\n\t\t}\n\t}\n\n\treturn null;\n}\n"],"mappings":"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"}