emdash 0.15.0 → 0.16.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 (351) 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-CLwG_3dh.mjs → api-BNKqxyFX.mjs} +54 -14
  4. package/dist/{api-CLwG_3dh.mjs.map → api-BNKqxyFX.mjs.map} +1 -1
  5. package/dist/{apply-wJhM_bwU.mjs → apply-BOPaD-s9.mjs} +16 -16
  6. package/dist/{apply-wJhM_bwU.mjs.map → apply-BOPaD-s9.mjs.map} +1 -1
  7. package/dist/astro/index.d.mts +3 -3
  8. package/dist/astro/index.d.mts.map +1 -1
  9. package/dist/astro/index.mjs +33 -1
  10. package/dist/astro/index.mjs.map +1 -1
  11. package/dist/astro/middleware/auth.d.mts +3 -3
  12. package/dist/astro/middleware/auth.mjs +2 -2
  13. package/dist/astro/middleware/redirect.mjs +4 -4
  14. package/dist/astro/middleware/request-context.mjs +1 -1
  15. package/dist/astro/middleware.d.mts.map +1 -1
  16. package/dist/astro/middleware.mjs +66 -46
  17. package/dist/astro/middleware.mjs.map +1 -1
  18. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +3 -3
  19. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +3 -3
  20. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +2 -2
  21. package/dist/astro/routes/api/admin/api-tokens/index.mjs +3 -3
  22. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +8 -8
  23. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +9 -9
  24. package/dist/astro/routes/api/admin/bylines/index.mjs +9 -9
  25. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +7 -7
  26. package/dist/astro/routes/api/admin/comments/_id_.mjs +5 -5
  27. package/dist/astro/routes/api/admin/comments/bulk.mjs +6 -6
  28. package/dist/astro/routes/api/admin/comments/counts.mjs +5 -5
  29. package/dist/astro/routes/api/admin/comments/index.mjs +6 -6
  30. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +4 -4
  31. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
  32. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +3 -3
  33. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +3 -3
  34. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +27 -27
  35. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +27 -27
  36. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +27 -27
  37. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +27 -27
  38. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +27 -27
  39. package/dist/astro/routes/api/admin/plugins/index.mjs +27 -27
  40. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
  41. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +27 -27
  42. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +27 -27
  43. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +27 -27
  44. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +27 -27
  45. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.d.mts.map +1 -1
  46. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +41 -28
  47. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
  48. package/dist/astro/routes/api/admin/plugins/registry/artifact.d.mts +8 -0
  49. package/dist/astro/routes/api/admin/plugins/registry/artifact.d.mts.map +1 -0
  50. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +301 -0
  51. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -0
  52. package/dist/astro/routes/api/admin/plugins/registry/install.d.mts.map +1 -1
  53. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +46 -28
  54. package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
  55. package/dist/astro/routes/api/admin/plugins/updates.mjs +27 -27
  56. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +27 -27
  57. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +3 -3
  58. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +27 -27
  59. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +2 -2
  60. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
  61. package/dist/astro/routes/api/admin/users/_id_/index.mjs +3 -3
  62. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +2 -2
  63. package/dist/astro/routes/api/admin/users/index.mjs +3 -3
  64. package/dist/astro/routes/api/auth/dev-bypass.mjs +3 -3
  65. package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
  66. package/dist/astro/routes/api/auth/invite/complete.mjs +3 -3
  67. package/dist/astro/routes/api/auth/invite/index.mjs +3 -3
  68. package/dist/astro/routes/api/auth/invite/register-options.mjs +3 -3
  69. package/dist/astro/routes/api/auth/logout.mjs +2 -2
  70. package/dist/astro/routes/api/auth/magic-link/send.mjs +4 -4
  71. package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
  72. package/dist/astro/routes/api/auth/me.mjs +3 -3
  73. package/dist/astro/routes/api/auth/passkey/_id_.mjs +3 -3
  74. package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
  75. package/dist/astro/routes/api/auth/passkey/options.mjs +4 -4
  76. package/dist/astro/routes/api/auth/passkey/register/options.mjs +3 -3
  77. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +3 -3
  78. package/dist/astro/routes/api/auth/passkey/verify.mjs +3 -3
  79. package/dist/astro/routes/api/auth/signup/complete.mjs +3 -3
  80. package/dist/astro/routes/api/auth/signup/request.mjs +4 -4
  81. package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
  82. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +6 -6
  83. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
  84. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +3 -3
  85. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
  86. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
  87. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +4 -4
  88. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +4 -4
  89. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
  90. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
  91. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +4 -4
  92. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +8 -8
  93. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
  94. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +3 -3
  95. package/dist/astro/routes/api/content/_collection_/_id_.mjs +4 -4
  96. package/dist/astro/routes/api/content/_collection_/index.mjs +4 -4
  97. package/dist/astro/routes/api/content/_collection_/trash.mjs +4 -4
  98. package/dist/astro/routes/api/dashboard.mjs +7 -7
  99. package/dist/astro/routes/api/dev/emails.mjs +2 -2
  100. package/dist/astro/routes/api/import/probe.mjs +4 -4
  101. package/dist/astro/routes/api/import/wordpress/analyze.mjs +3 -3
  102. package/dist/astro/routes/api/import/wordpress/execute.d.mts +3 -3
  103. package/dist/astro/routes/api/import/wordpress/execute.mjs +8 -8
  104. package/dist/astro/routes/api/import/wordpress/media.mjs +4 -4
  105. package/dist/astro/routes/api/import/wordpress/prepare.mjs +6 -6
  106. package/dist/astro/routes/api/import/wordpress/rewrite-url-helpers.d.mts +11 -1
  107. package/dist/astro/routes/api/import/wordpress/rewrite-url-helpers.d.mts.map +1 -1
  108. package/dist/astro/routes/api/import/wordpress/rewrite-url-helpers.mjs +17 -1
  109. package/dist/astro/routes/api/import/wordpress/rewrite-url-helpers.mjs.map +1 -1
  110. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts.map +1 -1
  111. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +7 -7
  112. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs.map +1 -1
  113. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +4 -4
  114. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +5 -5
  115. package/dist/astro/routes/api/manifest.mjs +3 -3
  116. package/dist/astro/routes/api/mcp.mjs +26 -26
  117. package/dist/astro/routes/api/media/_id_/confirm.mjs +4 -4
  118. package/dist/astro/routes/api/media/_id_.mjs +4 -4
  119. package/dist/astro/routes/api/media/file/_...key_.mjs +2 -2
  120. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +3 -3
  121. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +3 -3
  122. package/dist/astro/routes/api/media/providers/index.mjs +3 -3
  123. package/dist/astro/routes/api/media/upload-url.mjs +4 -4
  124. package/dist/astro/routes/api/media.mjs +5 -5
  125. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +5 -5
  126. package/dist/astro/routes/api/menus/_name_/items.mjs +5 -5
  127. package/dist/astro/routes/api/menus/_name_/reorder.mjs +5 -5
  128. package/dist/astro/routes/api/menus/_name_/translations.mjs +5 -5
  129. package/dist/astro/routes/api/menus/_name_.mjs +5 -5
  130. package/dist/astro/routes/api/menus/index.mjs +5 -5
  131. package/dist/astro/routes/api/oauth/device/authorize.mjs +3 -3
  132. package/dist/astro/routes/api/oauth/device/code.mjs +4 -4
  133. package/dist/astro/routes/api/oauth/device/token.mjs +4 -4
  134. package/dist/astro/routes/api/oauth/register.mjs +2 -2
  135. package/dist/astro/routes/api/oauth/token/refresh.mjs +3 -3
  136. package/dist/astro/routes/api/oauth/token/revoke.mjs +3 -3
  137. package/dist/astro/routes/api/oauth/token.mjs +2 -2
  138. package/dist/astro/routes/api/openapi.json.mjs +2 -2
  139. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +3 -3
  140. package/dist/astro/routes/api/redirects/404s/index.mjs +6 -6
  141. package/dist/astro/routes/api/redirects/404s/summary.mjs +6 -6
  142. package/dist/astro/routes/api/redirects/_id_.mjs +7 -7
  143. package/dist/astro/routes/api/redirects/index.mjs +7 -7
  144. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
  145. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
  146. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +27 -27
  147. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +27 -27
  148. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +27 -27
  149. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +27 -27
  150. package/dist/astro/routes/api/schema/collections/index.mjs +27 -27
  151. package/dist/astro/routes/api/schema/index.mjs +6 -6
  152. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +27 -27
  153. package/dist/astro/routes/api/schema/orphans/index.mjs +27 -27
  154. package/dist/astro/routes/api/search/enable.mjs +7 -7
  155. package/dist/astro/routes/api/search/index.mjs +6 -6
  156. package/dist/astro/routes/api/search/rebuild.mjs +7 -7
  157. package/dist/astro/routes/api/search/stats.mjs +6 -6
  158. package/dist/astro/routes/api/search/suggest.mjs +6 -6
  159. package/dist/astro/routes/api/sections/_slug_.mjs +6 -6
  160. package/dist/astro/routes/api/sections/index.mjs +6 -6
  161. package/dist/astro/routes/api/settings/email.mjs +4 -4
  162. package/dist/astro/routes/api/settings.mjs +8 -8
  163. package/dist/astro/routes/api/setup/admin-verify.mjs +3 -3
  164. package/dist/astro/routes/api/setup/admin.mjs +3 -3
  165. package/dist/astro/routes/api/setup/dev-bypass.mjs +15 -15
  166. package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
  167. package/dist/astro/routes/api/setup/index.mjs +16 -16
  168. package/dist/astro/routes/api/setup/status.mjs +3 -3
  169. package/dist/astro/routes/api/snapshot.mjs +4 -4
  170. package/dist/astro/routes/api/snapshot.mjs.map +1 -1
  171. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +9 -9
  172. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +9 -9
  173. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +9 -9
  174. package/dist/astro/routes/api/taxonomies/index.mjs +9 -9
  175. package/dist/astro/routes/api/themes/preview.mjs +3 -3
  176. package/dist/astro/routes/api/typegen.mjs +5 -5
  177. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +4 -4
  178. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +6 -6
  179. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +6 -6
  180. package/dist/astro/routes/api/widget-areas/_name_.mjs +5 -5
  181. package/dist/astro/routes/api/widget-areas/index.mjs +6 -6
  182. package/dist/astro/routes/api/widget-components.mjs +2 -2
  183. package/dist/astro/routes/robots.txt.mjs +4 -4
  184. package/dist/astro/routes/sitemap-_collection_.xml.d.mts.map +1 -1
  185. package/dist/astro/routes/sitemap-_collection_.xml.mjs +58 -13
  186. package/dist/astro/routes/sitemap-_collection_.xml.mjs.map +1 -1
  187. package/dist/astro/routes/sitemap.xml.mjs +5 -5
  188. package/dist/astro/types.d.mts +10 -3
  189. package/dist/astro/types.d.mts.map +1 -1
  190. package/dist/{authorize-Bkwe8kuL.mjs → authorize-Bn4S4DUT.mjs} +2 -2
  191. package/dist/{authorize-Bkwe8kuL.mjs.map → authorize-Bn4S4DUT.mjs.map} +1 -1
  192. package/dist/{byline-CTaWkMh5.mjs → byline-BDylH_m4.mjs} +3 -3
  193. package/dist/{byline-CTaWkMh5.mjs.map → byline-BDylH_m4.mjs.map} +1 -1
  194. package/dist/{bylines-H0Xh5TMy.mjs → bylines-B7TFEvFf.mjs} +2 -2
  195. package/dist/{bylines-H0Xh5TMy.mjs.map → bylines-B7TFEvFf.mjs.map} +1 -1
  196. package/dist/{bylines-DtDRNF1n.d.mts → bylines-DWLnr6-k.d.mts} +17 -17
  197. package/dist/{bylines-DtDRNF1n.d.mts.map → bylines-DWLnr6-k.d.mts.map} +1 -1
  198. package/dist/{bylines-BYHWU3T7.mjs → bylines-n6nykUyI.mjs} +6 -6
  199. package/dist/{bylines-BYHWU3T7.mjs.map → bylines-n6nykUyI.mjs.map} +1 -1
  200. package/dist/{cache-CNk1jIxp.mjs → cache-BcI1yUjR.mjs} +2 -2
  201. package/dist/{cache-CNk1jIxp.mjs.map → cache-BcI1yUjR.mjs.map} +1 -1
  202. package/dist/{chunks-BkfVdD-3.mjs → chunks-cYG4SnIP.mjs} +2 -2
  203. package/dist/{chunks-BkfVdD-3.mjs.map → chunks-cYG4SnIP.mjs.map} +1 -1
  204. package/dist/cli/index.mjs +61 -15
  205. package/dist/cli/index.mjs.map +1 -1
  206. package/dist/client/cf-access.d.mts +1 -1
  207. package/dist/client/index.d.mts +1 -1
  208. package/dist/{comment-_yzlBYPx.mjs → comment-C76G-9tz.mjs} +2 -2
  209. package/dist/{comment-_yzlBYPx.mjs.map → comment-C76G-9tz.mjs.map} +1 -1
  210. package/dist/{comments-DxID-rsd.mjs → comments-CCxFFGY1.mjs} +3 -3
  211. package/dist/{comments-DxID-rsd.mjs.map → comments-CCxFFGY1.mjs.map} +1 -1
  212. package/dist/{content-C0ooIs-f.mjs → content-8voQNTXX.mjs} +3 -3
  213. package/dist/{content-C0ooIs-f.mjs.map → content-8voQNTXX.mjs.map} +1 -1
  214. package/dist/{context-sAnCaUIR.mjs → context-B7qiYrz2.mjs} +7 -7
  215. package/dist/{context-sAnCaUIR.mjs.map → context-B7qiYrz2.mjs.map} +1 -1
  216. package/dist/{dashboard-Cqw3ay2X.mjs → dashboard-BeaFSPpx.mjs} +4 -4
  217. package/dist/{dashboard-Cqw3ay2X.mjs.map → dashboard-BeaFSPpx.mjs.map} +1 -1
  218. package/dist/db/index.d.mts +1 -1
  219. package/dist/db/index.mjs +1 -1
  220. package/dist/db/sqlite.mjs +1 -1
  221. package/dist/{db-errors-CGN9kJfo.mjs → db-errors-BiYqoX-n.mjs} +14 -2
  222. package/dist/db-errors-BiYqoX-n.mjs.map +1 -0
  223. package/dist/{error-CPh_8eLq.mjs → error-ChfADBuu.mjs} +5 -3
  224. package/dist/error-ChfADBuu.mjs.map +1 -0
  225. package/dist/errors-9P_FDrJ_.mjs +17 -0
  226. package/dist/errors-9P_FDrJ_.mjs.map +1 -0
  227. package/dist/{fts-manager-Mnrtn-r2.mjs → fts-manager-C_b-4x8u.mjs} +2 -2
  228. package/dist/{fts-manager-Mnrtn-r2.mjs.map → fts-manager-C_b-4x8u.mjs.map} +1 -1
  229. package/dist/{index-Bv1Wf1zB.d.mts → index-D_p_jIP1.d.mts} +153 -109
  230. package/dist/index-D_p_jIP1.d.mts.map +1 -0
  231. package/dist/index.d.mts +4 -4
  232. package/dist/index.mjs +38 -38
  233. package/dist/{load-DmXNVhst.mjs → load-CLFRjk9r.mjs} +2 -2
  234. package/dist/{load-DmXNVhst.mjs.map → load-CLFRjk9r.mjs.map} +1 -1
  235. package/dist/{loader-Chm5h7Gr.mjs → loader-D-vIJjfY.mjs} +86 -46
  236. package/dist/loader-D-vIJjfY.mjs.map +1 -0
  237. package/dist/media/local-runtime.d.mts +3 -3
  238. package/dist/media/local-runtime.mjs +4 -4
  239. package/dist/{media-oqRcNiQf.mjs → media-CKQd8AYU.mjs} +2 -2
  240. package/dist/{media-oqRcNiQf.mjs.map → media-CKQd8AYU.mjs.map} +1 -1
  241. package/dist/{menus-C75SSmRy.mjs → menus-C-nWT5Tu.mjs} +17 -11
  242. package/dist/menus-C-nWT5Tu.mjs.map +1 -0
  243. package/dist/{menus-Bjf5R1Qq.mjs → menus-arUNspyU.mjs} +2 -2
  244. package/dist/{menus-Bjf5R1Qq.mjs.map → menus-arUNspyU.mjs.map} +1 -1
  245. package/dist/{parse-3-caTKgt.mjs → parse-DHbXfvxO.mjs} +2 -2
  246. package/dist/{parse-3-caTKgt.mjs.map → parse-DHbXfvxO.mjs.map} +1 -1
  247. package/dist/plugin-utils.d.mts +25 -10
  248. package/dist/plugin-utils.d.mts.map +1 -1
  249. package/dist/plugin-utils.mjs +11 -10
  250. package/dist/plugin-utils.mjs.map +1 -1
  251. package/dist/plugins/adapt-sandbox-entry.d.mts +3 -3
  252. package/dist/{query-BJn8TOPk.mjs → query-7m6-l0f_.mjs} +21 -14
  253. package/dist/query-7m6-l0f_.mjs.map +1 -0
  254. package/dist/{rate-limit-D_-gAeJ0.mjs → rate-limit-D8RAXN8b.mjs} +2 -2
  255. package/dist/{rate-limit-D_-gAeJ0.mjs.map → rate-limit-D8RAXN8b.mjs.map} +1 -1
  256. package/dist/{redirect-CNv4mHX2.mjs → redirect-CjfDGrTd.mjs} +2 -2
  257. package/dist/{redirect-CNv4mHX2.mjs.map → redirect-CjfDGrTd.mjs.map} +1 -1
  258. package/dist/{redirects-B-CUZ1Xh.mjs → redirects-CowoEHdE.mjs} +3 -3
  259. package/dist/{redirects-B-CUZ1Xh.mjs.map → redirects-CowoEHdE.mjs.map} +1 -1
  260. package/dist/{registry-DqrAQDXH.mjs → registry-Cyp-dx6J.mjs} +4 -4
  261. package/dist/{registry-DqrAQDXH.mjs.map → registry-Cyp-dx6J.mjs.map} +1 -1
  262. package/dist/resolve-D6sM-SgF.mjs +143 -0
  263. package/dist/resolve-D6sM-SgF.mjs.map +1 -0
  264. package/dist/{runner-CNHRo1mT.d.mts → runner-DSQBurMS.d.mts} +7 -4
  265. package/dist/runner-DSQBurMS.d.mts.map +1 -0
  266. package/dist/{runner-CGlojznK.mjs → runner-Drnvs96u.mjs} +20 -24
  267. package/dist/{runner-CGlojznK.mjs.map → runner-Drnvs96u.mjs.map} +1 -1
  268. package/dist/runtime.d.mts +3 -3
  269. package/dist/runtime.mjs +2 -2
  270. package/dist/{schema-Djdlfi5G.mjs → schema-CI9mYPX3.mjs} +4 -4
  271. package/dist/{schema-Djdlfi5G.mjs.map → schema-CI9mYPX3.mjs.map} +1 -1
  272. package/dist/{search-By-NN3da.mjs → search-DKz_mGBP.mjs} +4 -4
  273. package/dist/{search-By-NN3da.mjs.map → search-DKz_mGBP.mjs.map} +1 -1
  274. package/dist/{sections-DcBIlOq1.mjs → sections-DBbCDIAT.mjs} +3 -3
  275. package/dist/{sections-DcBIlOq1.mjs.map → sections-DBbCDIAT.mjs.map} +1 -1
  276. package/dist/seed/index.mjs +13 -13
  277. package/dist/{seo-bjDoq9Eg.mjs → seo-BGCyDlkb.mjs} +2 -2
  278. package/dist/{seo-bjDoq9Eg.mjs.map → seo-BGCyDlkb.mjs.map} +1 -1
  279. package/dist/{seo-BoR4wCUh.mjs → seo-Dq707mNQ.mjs} +5 -3
  280. package/dist/seo-Dq707mNQ.mjs.map +1 -0
  281. package/dist/{service-BuuTdGAT.mjs → service-B0H7U1Y9.mjs} +2 -2
  282. package/dist/{service-BuuTdGAT.mjs.map → service-B0H7U1Y9.mjs.map} +1 -1
  283. package/dist/{settings-hcubRfkr.mjs → settings-BSXRtTzk.mjs} +3 -3
  284. package/dist/{settings-hcubRfkr.mjs.map → settings-BSXRtTzk.mjs.map} +1 -1
  285. package/dist/{settings-CJnKiWuR.mjs → settings-DfwNyQkf.mjs} +3 -3
  286. package/dist/{settings-CJnKiWuR.mjs.map → settings-DfwNyQkf.mjs.map} +1 -1
  287. package/dist/{taxonomies-CLs9HPE2.mjs → taxonomies-4vx0nmMr.mjs} +4 -4
  288. package/dist/{taxonomies-CLs9HPE2.mjs.map → taxonomies-4vx0nmMr.mjs.map} +1 -1
  289. package/dist/{taxonomies-WamPVA2x.mjs → taxonomies-CcvrMLbR.mjs} +7 -7
  290. package/dist/{taxonomies-WamPVA2x.mjs.map → taxonomies-CcvrMLbR.mjs.map} +1 -1
  291. package/dist/{taxonomy-D4Uc2LsZ.mjs → taxonomy-zqGQUqgu.mjs} +3 -3
  292. package/dist/{taxonomy-D4Uc2LsZ.mjs.map → taxonomy-zqGQUqgu.mjs.map} +1 -1
  293. package/dist/{transport-DOxLfUir.d.mts → transport-C2MGqtL6.d.mts} +1 -1
  294. package/dist/{transport-DOxLfUir.d.mts.map → transport-C2MGqtL6.d.mts.map} +1 -1
  295. package/dist/{types-ByV5sgsv.mjs → types-B0bmgwMG.mjs} +2 -2
  296. package/dist/{types-ByV5sgsv.mjs.map → types-B0bmgwMG.mjs.map} +1 -1
  297. package/dist/{user-D3BD5zdT.mjs → user-hUSOaIJy.mjs} +2 -2
  298. package/dist/{user-D3BD5zdT.mjs.map → user-hUSOaIJy.mjs.map} +1 -1
  299. package/dist/{validate-mz87i8_1.mjs → validate-IGltez8n.mjs} +2 -2
  300. package/dist/{validate-mz87i8_1.mjs.map → validate-IGltez8n.mjs.map} +1 -1
  301. package/dist/{validation-DKHhXjPr.mjs → validation-Bmymau7y.mjs} +6 -6
  302. package/dist/{validation-DKHhXjPr.mjs.map → validation-Bmymau7y.mjs.map} +1 -1
  303. package/dist/version-ITD3PlQd.mjs +7 -0
  304. package/dist/{version-Ct7C6RSo.mjs.map → version-ITD3PlQd.mjs.map} +1 -1
  305. package/dist/{widgets-lShIQXU5.mjs → widgets-yHQa4c6c.mjs} +2 -2
  306. package/dist/{widgets-lShIQXU5.mjs.map → widgets-yHQa4c6c.mjs.map} +1 -1
  307. package/dist/{zod-generator-dvxgmd1M.mjs → zod-generator-B80aap1J.mjs} +2 -2
  308. package/dist/{zod-generator-dvxgmd1M.mjs.map → zod-generator-B80aap1J.mjs.map} +1 -1
  309. package/package.json +7 -7
  310. package/src/api/errors.ts +2 -0
  311. package/src/api/handlers/index.ts +2 -0
  312. package/src/api/handlers/registry.ts +69 -1
  313. package/src/api/handlers/seo.ts +16 -1
  314. package/src/api/handlers/snapshot.ts +1 -1
  315. package/src/astro/integration/index.ts +26 -0
  316. package/src/astro/integration/routes.ts +5 -0
  317. package/src/astro/integration/runtime.ts +8 -0
  318. package/src/astro/middleware.ts +4 -0
  319. package/src/astro/public-plugin-api-routes.ts +41 -0
  320. package/src/astro/routes/api/admin/plugins/registry/[id]/update.ts +4 -0
  321. package/src/astro/routes/api/admin/plugins/registry/artifact.ts +388 -0
  322. package/src/astro/routes/api/admin/plugins/registry/install.ts +7 -1
  323. package/src/astro/routes/api/import/wordpress/rewrite-url-helpers.ts +22 -0
  324. package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +5 -2
  325. package/src/astro/routes/sitemap-[collection].xml.ts +114 -14
  326. package/src/astro/types.ts +14 -0
  327. package/src/content/converters/portable-text-to-prosemirror.ts +35 -11
  328. package/src/database/connection.ts +3 -10
  329. package/src/database/errors.ts +14 -0
  330. package/src/database/index.ts +3 -1
  331. package/src/database/migrations/runner.ts +29 -21
  332. package/src/emdash-runtime.ts +1 -0
  333. package/src/i18n/resolve.ts +152 -0
  334. package/src/index.ts +2 -0
  335. package/src/loader.ts +133 -59
  336. package/src/plugin-utils.ts +23 -0
  337. package/src/query.ts +24 -5
  338. package/src/utils/db-errors.ts +24 -0
  339. package/dist/connection-2igzM-AT.mjs +0 -57
  340. package/dist/connection-2igzM-AT.mjs.map +0 -1
  341. package/dist/db-errors-CGN9kJfo.mjs.map +0 -1
  342. package/dist/error-CPh_8eLq.mjs.map +0 -1
  343. package/dist/index-Bv1Wf1zB.d.mts.map +0 -1
  344. package/dist/loader-Chm5h7Gr.mjs.map +0 -1
  345. package/dist/menus-C75SSmRy.mjs.map +0 -1
  346. package/dist/query-BJn8TOPk.mjs.map +0 -1
  347. package/dist/resolve-Cj98DuqN.mjs +0 -39
  348. package/dist/resolve-Cj98DuqN.mjs.map +0 -1
  349. package/dist/runner-CNHRo1mT.d.mts.map +0 -1
  350. package/dist/seo-BoR4wCUh.mjs.map +0 -1
  351. package/dist/version-Ct7C6RSo.mjs +0 -7
@@ -35,13 +35,23 @@ export function portableTextToProsemirror(blocks: PortableTextBlock[]): ProseMir
35
35
 
36
36
  // Check for list items
37
37
  if (isTextBlock(block) && block.listItem) {
38
- // Collect consecutive list items
38
+ // Collect a list "run": the level=1 anchor plus everything that
39
+ // nests under it (level > 1, regardless of listItem type — a number
40
+ // child under a bullet parent is still part of the same tree). A
41
+ // level=1 block with a different listItem ends the run. Without the
42
+ // `level > 1` carve-out the run breaks on the first nested type
43
+ // switch and the descendant subtree leaks out as its own top-level
44
+ // list (e.g. `[bullet L1, number L2, bullet L1]` would render as
45
+ // three sibling lists instead of one bullet list with a numbered
46
+ // child).
39
47
  const listBlocks: PortableTextTextBlock[] = [];
40
48
  const listType = block.listItem;
41
49
 
42
50
  while (i < blocks.length) {
43
51
  const current = blocks[i];
44
- if (isTextBlock(current) && current.listItem === listType) {
52
+ if (!isTextBlock(current) || !current.listItem) break;
53
+ const level = current.level || 1;
54
+ if (level > 1 || current.listItem === listType) {
45
55
  listBlocks.push(current);
46
56
  i++;
47
57
  } else {
@@ -230,20 +240,34 @@ function convertListItem(
230
240
 
231
241
  // Handle nested items
232
242
  if (nestedItems.length > 0) {
233
- // Group nested items by their list type
234
- let j = 0;
243
+ // The shallowest level in `nestedItems` is the effective root of this
244
+ // item's nested subtree. A new sub-list only starts when we hit
245
+ // another block at that root level with a different `listItem` type;
246
+ // deeper blocks (level > minLevel) belong to the current group as
247
+ // descendants regardless of their own `listItem`. The previous
248
+ // grouping broke on any type change at any depth, so a deep mixed
249
+ // tree like `bullet L1 → number L2 → bullet L3 → number L2` would
250
+ // emit C(L3) as a sibling list under A(L1) instead of nesting it
251
+ // under B(L2), then degrade C to L2 on round-trip.
252
+ let minLevel = Infinity;
253
+ for (const ni of nestedItems) {
254
+ const level = ni.level || 2;
255
+ if (level < minLevel) minLevel = level;
256
+ }
235
257
 
258
+ let j = 0;
236
259
  while (j < nestedItems.length) {
237
- const nestedListType = nestedItems[j].listItem || parentListType;
260
+ const anchorType: "bullet" | "number" = nestedItems[j].listItem || parentListType;
238
261
  const nestedGroup: PortableTextTextBlock[] = [];
239
262
 
240
- while (
241
- j < nestedItems.length &&
242
- (nestedItems[j].listItem || parentListType) === nestedListType
243
- ) {
263
+ do {
244
264
  nestedGroup.push(nestedItems[j]);
245
265
  j++;
246
- }
266
+ } while (
267
+ j < nestedItems.length &&
268
+ ((nestedItems[j].level || 2) > minLevel ||
269
+ (nestedItems[j].listItem || parentListType) === anchorType)
270
+ );
247
271
 
248
272
  if (nestedGroup.length > 0) {
249
273
  // Decrease level for nested conversion
@@ -251,7 +275,7 @@ function convertListItem(
251
275
  ...ni,
252
276
  level: (ni.level || 2) - 1,
253
277
  }));
254
- content.push(convertList(adjustedGroup, nestedListType));
278
+ content.push(convertList(adjustedGroup, anchorType));
255
279
  }
256
280
  }
257
281
  }
@@ -1,24 +1,17 @@
1
1
  import BetterSqlite3 from "better-sqlite3";
2
2
  import { Kysely, SqliteDialect } from "kysely";
3
3
 
4
+ import { EmDashDatabaseError } from "./errors.js";
4
5
  import { kyselyLogOption } from "./instrumentation.js";
5
6
  import type { Database } from "./types.js";
6
7
 
8
+ export { EmDashDatabaseError };
9
+
7
10
  export interface DatabaseConfig {
8
11
  url: string;
9
12
  authToken?: string;
10
13
  }
11
14
 
12
- export class EmDashDatabaseError extends Error {
13
- constructor(
14
- message: string,
15
- public override cause?: unknown,
16
- ) {
17
- super(message);
18
- this.name = "EmDashDatabaseError";
19
- }
20
- }
21
-
22
15
  /**
23
16
  * Returns a helpful, actionable message when better-sqlite3's native binary
24
17
  * was compiled against a different Node.js version than the one running. This
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Database error types. Kept in their own module (no driver imports) so the
3
+ * public package barrel can re-export them without dragging native database
4
+ * drivers into the module graph of consumers that picked a different dialect.
5
+ */
6
+ export class EmDashDatabaseError extends Error {
7
+ constructor(
8
+ message: string,
9
+ public override cause?: unknown,
10
+ ) {
11
+ super(message);
12
+ this.name = "EmDashDatabaseError";
13
+ }
14
+ }
@@ -1,4 +1,6 @@
1
- export { createDatabase, EmDashDatabaseError } from "./connection.js";
1
+ // `createDatabase` is intentionally not re-exported here: it lives in
2
+ // `connection.ts`, which statically imports `better-sqlite3`. See #947.
3
+ export { EmDashDatabaseError } from "./errors.js";
2
4
  export type { DatabaseConfig } from "./connection.js";
3
5
  export { runMigrations, getMigrationStatus, rollbackMigration } from "./migrations/runner.js";
4
6
  export type { MigrationStatus } from "./migrations/runner.js";
@@ -107,16 +107,28 @@ export interface MigrationStatus {
107
107
  const MIGRATION_TABLE = "_emdash_migrations";
108
108
  const MIGRATION_LOCK_TABLE = "_emdash_migrations_lock";
109
109
 
110
- /**
111
- * Get migration status
112
- */
113
- export async function getMigrationStatus(db: Kysely<Database>): Promise<MigrationStatus> {
114
- const migrator = new Migrator({
110
+ export interface MigrationOptions {
111
+ migrationTableSchema?: string;
112
+ }
113
+
114
+ function createMigrator(db: Kysely<Database>, options?: MigrationOptions): Migrator {
115
+ return new Migrator({
115
116
  db,
116
117
  provider: new StaticMigrationProvider(),
117
118
  migrationTableName: MIGRATION_TABLE,
118
119
  migrationLockTableName: MIGRATION_LOCK_TABLE,
120
+ migrationTableSchema: options?.migrationTableSchema,
119
121
  });
122
+ }
123
+
124
+ /**
125
+ * Get migration status
126
+ */
127
+ export async function getMigrationStatus(
128
+ db: Kysely<Database>,
129
+ options?: MigrationOptions,
130
+ ): Promise<MigrationStatus> {
131
+ const migrator = createMigrator(db, options);
120
132
 
121
133
  const migrations = await migrator.getMigrations();
122
134
 
@@ -274,24 +286,24 @@ function deepErrorMessage(error: unknown): string {
274
286
  * success. This matches the user-observable expectation that running
275
287
  * migrations twice in a row is a no-op.
276
288
  */
277
- export async function runMigrations(db: Kysely<Database>): Promise<{ applied: string[] }> {
289
+ export async function runMigrations(
290
+ db: Kysely<Database>,
291
+ options?: MigrationOptions,
292
+ ): Promise<{ applied: string[] }> {
278
293
  // Fast path: check if all migrations are already applied.
279
294
  // A single cheap query vs the Migrator's full schema introspection.
280
295
  // We use `>=` rather than `===` so a database with extra rows from a
281
296
  // newer build (e.g. mid-deploy old isolate, or downgrade) still skips
282
297
  // the migrator instead of falling through to the race-recovery path
283
298
  // unnecessarily.
284
- const initialCount = await getAppliedMigrationCount(db);
285
- if (initialCount !== null && initialCount >= MIGRATION_COUNT) {
286
- return { applied: [] };
299
+ if (!options?.migrationTableSchema) {
300
+ const initialCount = await getAppliedMigrationCount(db);
301
+ if (initialCount !== null && initialCount >= MIGRATION_COUNT) {
302
+ return { applied: [] };
303
+ }
287
304
  }
288
305
 
289
- const migrator = new Migrator({
290
- db,
291
- provider: new StaticMigrationProvider(),
292
- migrationTableName: MIGRATION_TABLE,
293
- migrationLockTableName: MIGRATION_LOCK_TABLE,
294
- });
306
+ const migrator = createMigrator(db, options);
295
307
 
296
308
  const { error, results } = await migrator.migrateToLatest();
297
309
 
@@ -325,13 +337,9 @@ export async function runMigrations(db: Kysely<Database>): Promise<{ applied: st
325
337
  */
326
338
  export async function rollbackMigration(
327
339
  db: Kysely<Database>,
340
+ options?: MigrationOptions,
328
341
  ): Promise<{ rolledBack: string | null }> {
329
- const migrator = new Migrator({
330
- db,
331
- provider: new StaticMigrationProvider(),
332
- migrationTableName: MIGRATION_TABLE,
333
- migrationLockTableName: MIGRATION_LOCK_TABLE,
334
- });
342
+ const migrator = createMigrator(db, options);
335
343
 
336
344
  const { error, results } = await migrator.migrateDown();
337
345
 
@@ -2048,6 +2048,7 @@ export class EmDashRuntime {
2048
2048
  return {
2049
2049
  version: VERSION,
2050
2050
  commit: COMMIT,
2051
+ astroVersion: this.config.astroVersion,
2051
2052
  hash: manifestHash,
2052
2053
  collections: manifestCollections,
2053
2054
  plugins: manifestPlugins,
@@ -35,3 +35,155 @@ export function resolveLocaleChain(explicit?: string): string[] {
35
35
  if (!isI18nEnabled()) return [locale];
36
36
  return getFallbackChain(locale);
37
37
  }
38
+
39
+ const REPEATED_SLASHES = /\/{2,}/g;
40
+
41
+ /**
42
+ * Interpolate a collection `url_pattern` with a row's slug and id.
43
+ *
44
+ * Falls back to `/{collection}/{slug}` when no pattern is configured.
45
+ * Does NOT apply any locale prefix — pass the result through
46
+ * Astro's `getRelativeLocaleUrl` / `getAbsoluteLocaleUrl` (or the
47
+ * `localizePath` helper below) to add the locale segment.
48
+ */
49
+ export function interpolateUrlPattern(options: {
50
+ pattern: string | null;
51
+ collection: string;
52
+ slug: string;
53
+ id: string;
54
+ }): string {
55
+ const { pattern, collection, slug, id } = options;
56
+ const basePattern = pattern ?? `/${encodeURIComponent(collection)}/{slug}`;
57
+ let path = basePattern
58
+ .replace("{slug}", encodeURIComponent(slug))
59
+ .replace("{id}", encodeURIComponent(id));
60
+ path = path.replace(REPEATED_SLASHES, "/");
61
+ if (path.length > 1 && path.endsWith("/")) path = path.slice(0, -1);
62
+ if (!path.startsWith("/")) path = `/${path}`;
63
+ return path;
64
+ }
65
+
66
+ /**
67
+ * Apply a locale prefix to a path, honouring the user's Astro `i18n`
68
+ * routing config (`prefixDefaultLocale`, custom `path`/`codes` mappings).
69
+ *
70
+ * Reads the resolved config from `astro:config/server`, which is always
71
+ * available regardless of whether i18n is enabled -- so this function
72
+ * works in both i18n and non-i18n builds without tripping Astro's
73
+ * `i18nNotEnabled` resolver (the case with importing `astro:i18n`).
74
+ *
75
+ * Returns:
76
+ * - The original `path` when i18n is not configured.
77
+ * - The original `path` for the default locale when
78
+ * `prefixDefaultLocale` is false.
79
+ * - `/{segment}{path}` for any other configured locale, where
80
+ * `{segment}` is the locale's custom `path` if one is set,
81
+ * otherwise the locale code.
82
+ * - `null` when the row's locale isn't in the configured list.
83
+ * Callers should drop the entry: a sitemap link to a route the
84
+ * site can't serve is worse than no link at all (search engines
85
+ * get a 404 / soft-404 and downrank the page).
86
+ *
87
+ * Falls back to `getI18nConfig()` (EmDash's mirror of the same config,
88
+ * populated at runtime startup) when `astro:config/server` is
89
+ * unavailable -- e.g. running outside an Astro build context, such as
90
+ * in vitest.
91
+ */
92
+ export async function localizePath(path: string, locale: string): Promise<string | null> {
93
+ const segment = await resolveLocaleSegment(locale);
94
+ if (segment === undefined) return null;
95
+ if (segment === null || segment === "") return normalizePath(path);
96
+ return normalizePath(`/${segment}${path}`);
97
+ }
98
+
99
+ /**
100
+ * Resolve the URL segment to use for a locale.
101
+ *
102
+ * Returns:
103
+ * - `null` when i18n isn't configured (caller should not prefix).
104
+ * - `""` when the locale is the default locale and
105
+ * `prefixDefaultLocale` is false (caller should not prefix).
106
+ * - The locale's custom `path` value, or the locale string itself.
107
+ * - `undefined` when the locale isn't in the configured list --
108
+ * the row points at a route the site can't serve.
109
+ */
110
+ async function resolveLocaleSegment(locale: string): Promise<string | null | undefined> {
111
+ const i18n = await readAstroI18nConfig();
112
+ if (!i18n || !i18n.locales || i18n.locales.length <= 1) return null;
113
+
114
+ const isDefault = locale === i18n.defaultLocale;
115
+ if (isDefault && !i18n.prefixDefaultLocale) return "";
116
+
117
+ // When the locale has a custom `path`/`codes` mapping, use the path
118
+ // for the URL segment. Otherwise use the locale code directly.
119
+ for (const entry of i18n.locales) {
120
+ if (typeof entry === "string") {
121
+ if (entry === locale) return entry;
122
+ } else if (entry.codes.includes(locale)) {
123
+ return entry.path;
124
+ }
125
+ }
126
+
127
+ return undefined;
128
+ }
129
+
130
+ interface AstroI18nConfig {
131
+ defaultLocale: string;
132
+ locales: Array<string | { codes: readonly string[]; path: string }>;
133
+ prefixDefaultLocale?: boolean;
134
+ }
135
+
136
+ let astroI18nCache: AstroI18nConfig | null | undefined;
137
+
138
+ async function readAstroI18nConfig(): Promise<AstroI18nConfig | null> {
139
+ if (astroI18nCache !== undefined) return astroI18nCache;
140
+
141
+ try {
142
+ const mod = (await import("astro:config/server")) as {
143
+ i18n?: {
144
+ defaultLocale: string;
145
+ locales: Array<string | { codes: readonly string[]; path: string }>;
146
+ routing?: { prefixDefaultLocale?: boolean } | string;
147
+ };
148
+ };
149
+ if (!mod.i18n) {
150
+ astroI18nCache = null;
151
+ return null;
152
+ }
153
+ const routing = mod.i18n.routing;
154
+ astroI18nCache = {
155
+ defaultLocale: mod.i18n.defaultLocale,
156
+ locales: mod.i18n.locales,
157
+ prefixDefaultLocale:
158
+ typeof routing === "object" ? (routing.prefixDefaultLocale ?? false) : false,
159
+ };
160
+ return astroI18nCache;
161
+ } catch {
162
+ // `astro:config/server` isn't resolvable (e.g. running under vitest
163
+ // outside an Astro build). Fall back to EmDash's runtime config,
164
+ // which is populated at startup via the same astroConfig object.
165
+ const cfg = getI18nConfig();
166
+ if (!cfg || !isI18nEnabled()) {
167
+ astroI18nCache = null;
168
+ return null;
169
+ }
170
+ astroI18nCache = {
171
+ defaultLocale: cfg.defaultLocale,
172
+ locales: cfg.locales,
173
+ prefixDefaultLocale: cfg.prefixDefaultLocale,
174
+ };
175
+ return astroI18nCache;
176
+ }
177
+ }
178
+
179
+ /** @internal -- exposed for tests to reset the module-level cache. */
180
+ export function _resetAstroI18nCacheForTests(): void {
181
+ astroI18nCache = undefined;
182
+ }
183
+
184
+ function normalizePath(path: string): string {
185
+ let p = path.replace(REPEATED_SLASHES, "/");
186
+ if (p.length > 1 && p.endsWith("/")) p = p.slice(0, -1);
187
+ if (!p.startsWith("/")) p = `/${p}`;
188
+ return p;
189
+ }
package/src/index.ts CHANGED
@@ -128,6 +128,8 @@ export type {
128
128
  ResolvePathResult,
129
129
  TranslationSummary,
130
130
  TranslationsResult,
131
+ WhereRange,
132
+ WhereValue,
131
133
  } from "./query.js";
132
134
 
133
135
  // Request context (ALS-based ambient state for query functions)
package/src/loader.ts CHANGED
@@ -20,7 +20,7 @@ import { decodeCursor, encodeCursor } from "./database/repositories/types.js";
20
20
  import { validateIdentifier } from "./database/validate.js";
21
21
  import type { Database } from "./index.js";
22
22
  import { getRequestContext } from "./request-context.js";
23
- import { isMissingTableError } from "./utils/db-errors.js";
23
+ import { isMissingColumnError, isMissingTableError } from "./utils/db-errors.js";
24
24
 
25
25
  const FIELD_NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
26
26
 
@@ -311,10 +311,12 @@ function buildStatusCondition(
311
311
  const scheduledAtExpr = isPostgres(db)
312
312
  ? sql`${sql.ref(scheduledAtField)}::timestamptz`
313
313
  : sql.ref(scheduledAtField);
314
- return sql`(${sql.ref(statusField)} = 'published' OR (${sql.ref(statusField)} = 'scheduled' AND ${scheduledAtExpr} <= ${currentTimestampValue(db)}))`;
314
+ const nowExpr = isPostgres(db)
315
+ ? currentTimestampValue(db)
316
+ : sql`strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;
317
+ return sql`(${sql.ref(statusField)} = 'published' OR (${sql.ref(statusField)} = 'scheduled' AND ${scheduledAtExpr} <= ${nowExpr}))`;
315
318
  }
316
319
 
317
- // For other statuses (draft, archived), just match exactly
318
320
  return sql`${sql.ref(statusField)} = ${status}`;
319
321
  }
320
322
 
@@ -408,6 +410,65 @@ function buildCursorCondition(
408
410
  return sql`(${sql.ref(primary.field)} > ${orderValue} OR (${sql.ref(primary.field)} = ${orderValue} AND ${sql.ref(idField)} > ${cursorId}))`;
409
411
  }
410
412
 
413
+ /** Type guard: is the where value a range object (not a string or array)? */
414
+ function isWhereRange(value: WhereValue): value is WhereRange {
415
+ return value !== null && typeof value === "object" && !Array.isArray(value);
416
+ }
417
+
418
+ /**
419
+ * Build AND conditions for non-taxonomy field filters.
420
+ * Returns an array of sql fragments; empty if no field filters apply.
421
+ * Field names are validated against FIELD_NAME_PATTERN to prevent injection.
422
+ */
423
+ function buildFieldConditions(
424
+ fields: Record<string, WhereValue>,
425
+ tablePrefix?: string,
426
+ ): ReturnType<typeof sql>[] {
427
+ const conditions: ReturnType<typeof sql>[] = [];
428
+
429
+ for (const [key, value] of Object.entries(fields)) {
430
+ if (!FIELD_NAME_PATTERN.test(key)) {
431
+ console.warn(`[emdash] where filter: invalid field name "${key}" ignored`);
432
+ continue;
433
+ }
434
+ if (value == null) continue;
435
+ const ref = tablePrefix ? sql.ref(`${tablePrefix}.${key}`) : sql.ref(key);
436
+
437
+ if (isWhereRange(value)) {
438
+ if (value.gt !== undefined) conditions.push(sql`${ref} > ${value.gt}`);
439
+ if (value.gte !== undefined) conditions.push(sql`${ref} >= ${value.gte}`);
440
+ if (value.lt !== undefined) conditions.push(sql`${ref} < ${value.lt}`);
441
+ if (value.lte !== undefined) conditions.push(sql`${ref} <= ${value.lte}`);
442
+ } else if (Array.isArray(value)) {
443
+ if (value.length > 0) {
444
+ conditions.push(sql`${ref} IN (${sql.join(value.map((v) => sql`${v}`))})`);
445
+ }
446
+ } else {
447
+ conditions.push(sql`${ref} = ${value}`);
448
+ }
449
+ }
450
+
451
+ return conditions;
452
+ }
453
+
454
+ /**
455
+ * Range filter for comparison operators on field values.
456
+ * Values are compared as strings in the database. This works correctly for
457
+ * ISO 8601 dates (e.g. "2024-01-01T00:00:00Z") because lexicographic ordering
458
+ * matches chronological ordering. Ensure date values use a consistent format.
459
+ */
460
+ export interface WhereRange {
461
+ gt?: string;
462
+ gte?: string;
463
+ lt?: string;
464
+ lte?: string;
465
+ }
466
+
467
+ /**
468
+ * A where clause value: exact match, multi-value match, or range comparison.
469
+ */
470
+ export type WhereValue = string | string[] | WhereRange;
471
+
411
472
  /**
412
473
  * Filter for loadCollection - type is required
413
474
  */
@@ -421,9 +482,16 @@ export interface CollectionFilter {
421
482
  */
422
483
  cursor?: string;
423
484
  /**
424
- * Filter by field values or taxonomy terms
485
+ * Filter by field values, taxonomy terms, or ranges.
486
+ *
487
+ * Taxonomy names are detected automatically and filtered via JOIN.
488
+ * Other keys are treated as column filters on the content table.
489
+ *
490
+ * @example { category: 'news' } - taxonomy term
491
+ * @example { series: 'main' } - exact match on a content field
492
+ * @example { published_at: { gte: '2024-01-01', lt: '2025-01-01' } } - date range
425
493
  */
426
- where?: Record<string, string | string[]>;
494
+ where?: Record<string, WhereValue>;
427
495
  /**
428
496
  * Order results by field(s)
429
497
  * @default { created_at: "desc" }
@@ -551,79 +619,81 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
551
619
  ? buildCursorCondition(cursor, orderBy, tableName)
552
620
  : null;
553
621
 
554
- // Check if we need taxonomy filtering
622
+ // Separate taxonomy filters from field filters
555
623
  let result: { rows: Record<string, unknown>[] };
624
+ let taxonomyFilter: { name: string; slugs: string[] } | null = null;
625
+ const fieldFilters: Record<string, WhereValue> = {};
556
626
 
557
627
  if (where && Object.keys(where).length > 0) {
558
- // Get taxonomy names to detect taxonomy filters
559
628
  const taxNames = await getTaxonomyNames(db);
560
- const taxonomyFilters: Record<string, string | string[]> = {};
561
629
 
562
630
  for (const [key, value] of Object.entries(where)) {
631
+ if (value == null) continue;
563
632
  if (taxNames.has(key)) {
564
- taxonomyFilters[key] = value;
633
+ if (isWhereRange(value)) {
634
+ console.warn(
635
+ `[emdash] where filter: range operators are not supported on taxonomy "${key}", ignored`,
636
+ );
637
+ continue;
638
+ }
639
+ if (taxonomyFilter) {
640
+ console.warn(
641
+ `[emdash] where filter: only one taxonomy is supported per query, "${key}" ignored`,
642
+ );
643
+ continue;
644
+ }
645
+ const slugs = Array.isArray(value) ? value : [value];
646
+ taxonomyFilter = { name: key, slugs };
647
+ } else {
648
+ fieldFilters[key] = value;
565
649
  }
566
650
  }
651
+ }
567
652
 
568
- // If we have taxonomy filters, use JOIN
569
- if (Object.keys(taxonomyFilters).length > 0) {
570
- // Build query with taxonomy JOIN
571
- // For now, support single taxonomy filter (can extend later for multiple)
572
- const [taxName, termSlugs] = Object.entries(taxonomyFilters)[0];
573
- const slugs = Array.isArray(termSlugs) ? termSlugs : [termSlugs];
574
- const orderByClause = buildOrderByClause(orderBy, tableName);
575
-
576
- const statusCondition = buildStatusCondition(db, status, tableName);
577
- const localeCondition = locale
578
- ? sql`AND ${sql.ref(tableName)}.locale = ${locale}`
579
- : sql``;
580
- const cursorCond = cursorConditionPrefixed
581
- ? sql`AND ${cursorConditionPrefixed}`
582
- : sql``;
583
- result = await sql<Record<string, unknown>>`
584
- SELECT DISTINCT ${sql.ref(tableName)}.* FROM ${sql.ref(tableName)}
585
- INNER JOIN content_taxonomies ct
586
- ON ct.collection = ${type}
587
- AND ct.entry_id = ${sql.ref(tableName)}.id
588
- INNER JOIN taxonomies t
589
- ON t.id = ct.taxonomy_id
590
- WHERE ${sql.ref(tableName)}.deleted_at IS NULL
591
- AND ${statusCondition}
592
- ${localeCondition}
593
- ${cursorCond}
594
- AND t.name = ${taxName}
595
- AND t.slug IN (${sql.join(slugs.map((s) => sql`${s}`))})
596
- ${orderByClause}
597
- ${fetchLimit ? sql`LIMIT ${fetchLimit}` : sql``}
598
- `.execute(db);
599
- } else {
600
- // No taxonomy filters, use simple query
601
- const orderByClause = buildOrderByClause(orderBy);
602
- const statusCondition = buildStatusCondition(db, status);
603
- const localeFilter = locale ? sql`AND locale = ${locale}` : sql``;
604
- const cursorCond = cursorCondition ? sql`AND ${cursorCondition}` : sql``;
605
- result = await sql<Record<string, unknown>>`
606
- SELECT * FROM ${sql.ref(tableName)}
607
- WHERE deleted_at IS NULL
653
+ if (taxonomyFilter) {
654
+ const orderByClause = buildOrderByClause(orderBy, tableName);
655
+ const statusCondition = buildStatusCondition(db, status, tableName);
656
+ const localeCondition = locale
657
+ ? sql`AND ${sql.ref(tableName)}.locale = ${locale}`
658
+ : sql``;
659
+ const cursorCond = cursorConditionPrefixed ? sql`AND ${cursorConditionPrefixed}` : sql``;
660
+ const fieldConds = buildFieldConditions(fieldFilters, tableName);
661
+ const fieldCondsSQL =
662
+ fieldConds.length > 0 ? sql`${sql.join(fieldConds, sql` AND `)}` : null;
663
+
664
+ result = await sql<Record<string, unknown>>`
665
+ SELECT DISTINCT ${sql.ref(tableName)}.* FROM ${sql.ref(tableName)}
666
+ INNER JOIN content_taxonomies ct
667
+ ON ct.collection = ${type}
668
+ AND ct.entry_id = ${sql.ref(tableName)}.id
669
+ INNER JOIN taxonomies t
670
+ ON t.id = ct.taxonomy_id
671
+ WHERE ${sql.ref(tableName)}.deleted_at IS NULL
608
672
  AND ${statusCondition}
609
- ${localeFilter}
673
+ ${localeCondition}
610
674
  ${cursorCond}
611
- ${orderByClause}
612
- ${fetchLimit ? sql`LIMIT ${fetchLimit}` : sql``}
613
- `.execute(db);
614
- }
675
+ AND t.name = ${taxonomyFilter.name}
676
+ AND t.slug IN (${sql.join(taxonomyFilter.slugs.map((s) => sql`${s}`))})
677
+ ${fieldCondsSQL ? sql`AND ${fieldCondsSQL}` : sql``}
678
+ ${orderByClause}
679
+ ${fetchLimit ? sql`LIMIT ${fetchLimit}` : sql``}
680
+ `.execute(db);
615
681
  } else {
616
- // No where clause, use simple query
617
682
  const orderByClause = buildOrderByClause(orderBy);
618
683
  const statusCondition = buildStatusCondition(db, status);
619
684
  const localeFilter = locale ? sql`AND locale = ${locale}` : sql``;
620
685
  const cursorCond = cursorCondition ? sql`AND ${cursorCondition}` : sql``;
686
+ const fieldConds = buildFieldConditions(fieldFilters);
687
+ const fieldCondsSQL =
688
+ fieldConds.length > 0 ? sql`${sql.join(fieldConds, sql` AND `)}` : null;
689
+
621
690
  result = await sql<Record<string, unknown>>`
622
691
  SELECT * FROM ${sql.ref(tableName)}
623
692
  WHERE deleted_at IS NULL
624
693
  AND ${statusCondition}
625
694
  ${localeFilter}
626
695
  ${cursorCond}
696
+ ${fieldCondsSQL ? sql`AND ${fieldCondsSQL}` : sql``}
627
697
  ${orderByClause}
628
698
  ${fetchLimit ? sql`LIMIT ${fetchLimit}` : sql``}
629
699
  `.execute(db);
@@ -693,13 +763,17 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
693
763
  },
694
764
  };
695
765
  } catch (error) {
696
- // Handle missing table gracefully - return empty collection.
697
- // This happens before migrations have run.
698
- if (isMissingTableError(error)) {
766
+ // Handle missing table/column gracefully - return empty collection.
767
+ // Missing table happens before migrations have run.
768
+ // Missing column happens when a where filter references a non-existent field.
769
+ const message = error instanceof Error ? error.message : String(error);
770
+ if (isMissingTableError(error) || isMissingColumnError(error)) {
771
+ if (isMissingColumnError(error)) {
772
+ console.warn(`[emdash] where filter: ${message}`);
773
+ }
699
774
  return { entries: [] };
700
775
  }
701
776
 
702
- const message = error instanceof Error ? error.message : String(error);
703
777
  return {
704
778
  error: new Error(`Failed to load collection: ${message}`),
705
779
  };