emdash 0.25.1 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (418) hide show
  1. package/dist/{adapters-DAAjOID4.d.mts → adapters-u037EnTR.d.mts} +1 -1
  2. package/dist/{adapters-DAAjOID4.d.mts.map → adapters-u037EnTR.d.mts.map} +1 -1
  3. package/dist/api/route-utils.d.mts +3 -3
  4. package/dist/api/route-utils.mjs +11 -11
  5. package/dist/api/schemas/index.d.mts +1 -1
  6. package/dist/{api-D2_sbavn.mjs → api-80kycR9o.mjs} +86 -30
  7. package/dist/api-80kycR9o.mjs.map +1 -0
  8. package/dist/{apply-Bk2mAuLn.mjs → apply-DSypk6Iu.mjs} +20 -18
  9. package/dist/apply-DSypk6Iu.mjs.map +1 -0
  10. package/dist/astro/index.d.mts +11 -10
  11. package/dist/astro/index.d.mts.map +1 -1
  12. package/dist/astro/index.mjs +12 -3
  13. package/dist/astro/index.mjs.map +1 -1
  14. package/dist/astro/middleware/auth.d.mts +10 -9
  15. package/dist/astro/middleware/auth.d.mts.map +1 -1
  16. package/dist/astro/middleware/auth.mjs +4 -3
  17. package/dist/astro/middleware/auth.mjs.map +1 -1
  18. package/dist/astro/middleware/redirect.mjs +4 -4
  19. package/dist/astro/middleware.d.mts +2 -9
  20. package/dist/astro/middleware.d.mts.map +1 -1
  21. package/dist/astro/middleware.mjs +86 -85
  22. package/dist/astro/middleware.mjs.map +1 -1
  23. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +3 -3
  24. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +3 -3
  25. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +2 -2
  26. package/dist/astro/routes/api/admin/api-tokens/index.mjs +3 -3
  27. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +3 -3
  28. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +4 -4
  29. package/dist/astro/routes/api/admin/byline-fields/index.mjs +4 -4
  30. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +4 -4
  31. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +10 -10
  32. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +10 -10
  33. package/dist/astro/routes/api/admin/bylines/index.mjs +10 -10
  34. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +8 -8
  35. package/dist/astro/routes/api/admin/comments/_id_.mjs +6 -6
  36. package/dist/astro/routes/api/admin/comments/bulk.mjs +7 -7
  37. package/dist/astro/routes/api/admin/comments/counts.mjs +6 -6
  38. package/dist/astro/routes/api/admin/comments/index.mjs +7 -7
  39. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +4 -4
  40. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
  41. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +3 -3
  42. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +3 -3
  43. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +29 -29
  44. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
  45. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +29 -29
  46. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
  47. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +29 -29
  48. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
  49. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +28 -28
  50. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +28 -28
  51. package/dist/astro/routes/api/admin/plugins/index.mjs +29 -29
  52. package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
  53. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
  54. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +28 -28
  55. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +28 -28
  56. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +28 -28
  57. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +28 -28
  58. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +29 -29
  59. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +28 -28
  60. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +29 -29
  61. package/dist/astro/routes/api/admin/plugins/updates.mjs +28 -28
  62. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +28 -28
  63. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +3 -3
  64. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +28 -28
  65. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +2 -2
  66. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
  67. package/dist/astro/routes/api/admin/users/_id_/index.mjs +3 -3
  68. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +2 -2
  69. package/dist/astro/routes/api/admin/users/index.mjs +3 -3
  70. package/dist/astro/routes/api/auth/dev-bypass.mjs +3 -3
  71. package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
  72. package/dist/astro/routes/api/auth/invite/complete.mjs +3 -3
  73. package/dist/astro/routes/api/auth/invite/index.mjs +3 -3
  74. package/dist/astro/routes/api/auth/invite/register-options.mjs +3 -3
  75. package/dist/astro/routes/api/auth/logout.mjs +2 -2
  76. package/dist/astro/routes/api/auth/magic-link/send.mjs +4 -4
  77. package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
  78. package/dist/astro/routes/api/auth/me.mjs +4 -4
  79. package/dist/astro/routes/api/auth/passkey/_id_.mjs +3 -3
  80. package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
  81. package/dist/astro/routes/api/auth/passkey/options.mjs +4 -4
  82. package/dist/astro/routes/api/auth/passkey/register/options.mjs +3 -3
  83. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +3 -3
  84. package/dist/astro/routes/api/auth/passkey/verify.mjs +3 -3
  85. package/dist/astro/routes/api/auth/signup/complete.mjs +3 -3
  86. package/dist/astro/routes/api/auth/signup/request.mjs +4 -4
  87. package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
  88. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +7 -7
  89. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +7 -7
  90. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
  91. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +3 -3
  92. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
  93. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
  94. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +4 -4
  95. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +4 -4
  96. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
  97. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
  98. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +4 -4
  99. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +9 -9
  100. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
  101. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +3 -3
  102. package/dist/astro/routes/api/content/_collection_/_id_.mjs +4 -4
  103. package/dist/astro/routes/api/content/_collection_/authors.mjs +3 -3
  104. package/dist/astro/routes/api/content/_collection_/index.mjs +4 -4
  105. package/dist/astro/routes/api/content/_collection_/trash.mjs +4 -4
  106. package/dist/astro/routes/api/dashboard.mjs +8 -8
  107. package/dist/astro/routes/api/dev/emails.mjs +2 -2
  108. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  109. package/dist/astro/routes/api/import/probe.mjs +4 -4
  110. package/dist/astro/routes/api/import/wordpress/analyze.mjs +3 -3
  111. package/dist/astro/routes/api/import/wordpress/execute.d.mts +10 -9
  112. package/dist/astro/routes/api/import/wordpress/execute.d.mts.map +1 -1
  113. package/dist/astro/routes/api/import/wordpress/execute.mjs +9 -9
  114. package/dist/astro/routes/api/import/wordpress/media.d.mts +10 -9
  115. package/dist/astro/routes/api/import/wordpress/media.d.mts.map +1 -1
  116. package/dist/astro/routes/api/import/wordpress/media.mjs +4 -4
  117. package/dist/astro/routes/api/import/wordpress/prepare.mjs +6 -6
  118. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +5 -5
  119. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  120. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +4 -4
  121. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +1 -1
  122. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +7 -7
  123. package/dist/astro/routes/api/manifest.d.mts.map +1 -1
  124. package/dist/astro/routes/api/manifest.mjs +13 -4
  125. package/dist/astro/routes/api/manifest.mjs.map +1 -1
  126. package/dist/astro/routes/api/mcp.mjs +34 -34
  127. package/dist/astro/routes/api/media/_id_/confirm.mjs +4 -4
  128. package/dist/astro/routes/api/media/_id_.mjs +4 -4
  129. package/dist/astro/routes/api/media/file/_...key_.mjs +2 -2
  130. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +3 -3
  131. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +3 -3
  132. package/dist/astro/routes/api/media/providers/index.mjs +3 -3
  133. package/dist/astro/routes/api/media/upload-url.mjs +4 -4
  134. package/dist/astro/routes/api/media.mjs +5 -5
  135. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +6 -6
  136. package/dist/astro/routes/api/menus/_name_/items.mjs +6 -6
  137. package/dist/astro/routes/api/menus/_name_/reorder.mjs +6 -6
  138. package/dist/astro/routes/api/menus/_name_/translations.mjs +6 -6
  139. package/dist/astro/routes/api/menus/_name_.mjs +6 -6
  140. package/dist/astro/routes/api/menus/index.mjs +6 -6
  141. package/dist/astro/routes/api/oauth/device/authorize.mjs +3 -3
  142. package/dist/astro/routes/api/oauth/device/code.mjs +4 -4
  143. package/dist/astro/routes/api/oauth/device/token.mjs +4 -4
  144. package/dist/astro/routes/api/oauth/register.mjs +2 -2
  145. package/dist/astro/routes/api/oauth/token/refresh.mjs +3 -3
  146. package/dist/astro/routes/api/oauth/token/revoke.mjs +3 -3
  147. package/dist/astro/routes/api/oauth/token.mjs +2 -2
  148. package/dist/astro/routes/api/openapi.json.mjs +2 -2
  149. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +3 -3
  150. package/dist/astro/routes/api/redirects/404s/index.mjs +6 -6
  151. package/dist/astro/routes/api/redirects/404s/summary.mjs +6 -6
  152. package/dist/astro/routes/api/redirects/_id_.mjs +7 -7
  153. package/dist/astro/routes/api/redirects/index.mjs +7 -7
  154. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
  155. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
  156. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +28 -28
  157. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +28 -28
  158. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +28 -28
  159. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +28 -28
  160. package/dist/astro/routes/api/schema/collections/index.mjs +28 -28
  161. package/dist/astro/routes/api/schema/index.mjs +6 -6
  162. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +28 -28
  163. package/dist/astro/routes/api/schema/orphans/index.mjs +28 -28
  164. package/dist/astro/routes/api/search/enable.mjs +7 -7
  165. package/dist/astro/routes/api/search/index.mjs +6 -6
  166. package/dist/astro/routes/api/search/rebuild.mjs +7 -7
  167. package/dist/astro/routes/api/search/stats.mjs +6 -6
  168. package/dist/astro/routes/api/search/suggest.mjs +6 -6
  169. package/dist/astro/routes/api/sections/_slug_.mjs +6 -6
  170. package/dist/astro/routes/api/sections/index.mjs +6 -6
  171. package/dist/astro/routes/api/settings/email.mjs +4 -4
  172. package/dist/astro/routes/api/settings.mjs +9 -9
  173. package/dist/astro/routes/api/setup/admin-verify.mjs +3 -3
  174. package/dist/astro/routes/api/setup/admin.mjs +3 -3
  175. package/dist/astro/routes/api/setup/dev-bypass.d.mts.map +1 -1
  176. package/dist/astro/routes/api/setup/dev-bypass.mjs +20 -18
  177. package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
  178. package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
  179. package/dist/astro/routes/api/setup/index.mjs +18 -18
  180. package/dist/astro/routes/api/setup/status.mjs +3 -3
  181. package/dist/astro/routes/api/snapshot.mjs +3 -3
  182. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +9 -9
  183. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +9 -9
  184. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +9 -9
  185. package/dist/astro/routes/api/taxonomies/index.mjs +9 -9
  186. package/dist/astro/routes/api/themes/preview.mjs +3 -3
  187. package/dist/astro/routes/api/typegen.mjs +5 -5
  188. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +4 -4
  189. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +6 -6
  190. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +6 -6
  191. package/dist/astro/routes/api/widget-areas/_name_.mjs +5 -5
  192. package/dist/astro/routes/api/widget-areas/index.mjs +6 -6
  193. package/dist/astro/routes/api/widget-components.mjs +2 -2
  194. package/dist/astro/routes/robots.txt.mjs +5 -5
  195. package/dist/astro/routes/sitemap-_collection_.xml.mjs +10 -9
  196. package/dist/astro/routes/sitemap-_collection_.xml.mjs.map +1 -1
  197. package/dist/astro/routes/sitemap.xml.mjs +5 -5
  198. package/dist/astro/types.d.mts +12 -10
  199. package/dist/astro/types.d.mts.map +1 -1
  200. package/dist/auth/providers/github.d.mts +1 -1
  201. package/dist/auth/providers/google.d.mts +1 -1
  202. package/dist/{authorize-CuxZ0i2t.mjs → authorize-C3a_zGsM.mjs} +2 -2
  203. package/dist/{authorize-CuxZ0i2t.mjs.map → authorize-C3a_zGsM.mjs.map} +1 -1
  204. package/dist/{byline-C-YajfV7.mjs → byline-DEKSVPTy.mjs} +6 -6
  205. package/dist/{byline-C-YajfV7.mjs.map → byline-DEKSVPTy.mjs.map} +1 -1
  206. package/dist/{byline-fields-KD3JFRuH.d.mts → byline-fields-CtQ6blXd.d.mts} +32 -32
  207. package/dist/{byline-fields-KD3JFRuH.d.mts.map → byline-fields-CtQ6blXd.d.mts.map} +1 -1
  208. package/dist/{bylines-CPc_q2Vd.mjs → bylines-CiqgTHWF.mjs} +5 -5
  209. package/dist/{bylines-CPc_q2Vd.mjs.map → bylines-CiqgTHWF.mjs.map} +1 -1
  210. package/dist/{bylines-D4KADDAi.mjs → bylines-Ds4-otXl.mjs} +4 -4
  211. package/dist/{bylines-D4KADDAi.mjs.map → bylines-Ds4-otXl.mjs.map} +1 -1
  212. package/dist/{cache-C35VWD5D.mjs → cache-Ciu3e3AF.mjs} +2 -2
  213. package/dist/{cache-C35VWD5D.mjs.map → cache-Ciu3e3AF.mjs.map} +1 -1
  214. package/dist/{chunks-BHgcHh2C.mjs → chunks-CZFiauig.mjs} +2 -2
  215. package/dist/{chunks-BHgcHh2C.mjs.map → chunks-CZFiauig.mjs.map} +1 -1
  216. package/dist/cli/index.mjs +17 -17
  217. package/dist/cli/index.mjs.map +1 -1
  218. package/dist/client/cf-access.d.mts +1 -1
  219. package/dist/client/index.d.mts +1 -1
  220. package/dist/{comment-BDx50EX5.mjs → comment-CH1IrFFs.mjs} +3 -3
  221. package/dist/{comment-BDx50EX5.mjs.map → comment-CH1IrFFs.mjs.map} +1 -1
  222. package/dist/{comment-reaction-Gv_pgfI3.mjs → comment-reaction-kruNY-kP.mjs} +2 -2
  223. package/dist/{comment-reaction-Gv_pgfI3.mjs.map → comment-reaction-kruNY-kP.mjs.map} +1 -1
  224. package/dist/{comments-CXHuQ6jI.mjs → comments-BUJyXbgq.mjs} +3 -3
  225. package/dist/{comments-CXHuQ6jI.mjs.map → comments-BUJyXbgq.mjs.map} +1 -1
  226. package/dist/{content-B43-YrIq.mjs → content-RmfHoWqK.mjs} +4 -4
  227. package/dist/{content-B43-YrIq.mjs.map → content-RmfHoWqK.mjs.map} +1 -1
  228. package/dist/{context-BW3N7wyt.mjs → context-CzUU_WzL.mjs} +7 -7
  229. package/dist/{context-BW3N7wyt.mjs.map → context-CzUU_WzL.mjs.map} +1 -1
  230. package/dist/{dashboard-FIe0S512.mjs → dashboard-CVyGhift.mjs} +4 -4
  231. package/dist/{dashboard-FIe0S512.mjs.map → dashboard-CVyGhift.mjs.map} +1 -1
  232. package/dist/db/index.d.mts +3 -3
  233. package/dist/db/index.mjs +1 -1
  234. package/dist/db/libsql.d.mts +1 -1
  235. package/dist/db/postgres.d.mts +1 -1
  236. package/dist/db/sqlite.d.mts +1 -1
  237. package/dist/{error-DXnK2wF4.mjs → error-BHkLZk-w.mjs} +2 -2
  238. package/dist/{error-DXnK2wF4.mjs.map → error-BHkLZk-w.mjs.map} +1 -1
  239. package/dist/{field-defs-cache-CYYx8xlG.mjs → field-defs-cache-BA-8ZPiT.mjs} +2 -2
  240. package/dist/{field-defs-cache-CYYx8xlG.mjs.map → field-defs-cache-BA-8ZPiT.mjs.map} +1 -1
  241. package/dist/{fts-manager-DW_NR-Fs.mjs → fts-manager-Db9jGavf.mjs} +2 -2
  242. package/dist/{fts-manager-DW_NR-Fs.mjs.map → fts-manager-Db9jGavf.mjs.map} +1 -1
  243. package/dist/{index-Cz6_JBL3.d.mts → index-CDZZjrLT.d.mts} +2430 -2363
  244. package/dist/index-CDZZjrLT.d.mts.map +1 -0
  245. package/dist/{index-qbL02uhs.d.mts → index-DGIjmUXQ.d.mts} +3 -3
  246. package/dist/{index-qbL02uhs.d.mts.map → index-DGIjmUXQ.d.mts.map} +1 -1
  247. package/dist/index.d.mts +16 -15
  248. package/dist/index.mjs +38 -38
  249. package/dist/{load-DMFi4n1p.mjs → load-DpHICVPh.mjs} +2 -2
  250. package/dist/{load-DMFi4n1p.mjs.map → load-DpHICVPh.mjs.map} +1 -1
  251. package/dist/{loader-DrpofRon.mjs → loader-4K37qA-y.mjs} +81 -18
  252. package/dist/loader-4K37qA-y.mjs.map +1 -0
  253. package/dist/{manifest-schema-BOfPXse0.mjs → manifest-schema-kPGX7VS-.mjs} +3 -1
  254. package/dist/manifest-schema-kPGX7VS-.mjs.map +1 -0
  255. package/dist/media/local-runtime.d.mts +11 -10
  256. package/dist/media/local-runtime.d.mts.map +1 -1
  257. package/dist/media/local-runtime.mjs +5 -5
  258. package/dist/{media-BjKxjzk8.mjs → media-C0nRRfs1.mjs} +2 -2
  259. package/dist/{media-BjKxjzk8.mjs.map → media-C0nRRfs1.mjs.map} +1 -1
  260. package/dist/{menus-CxqJ-z-N.mjs → menus-DzRyC1ID.mjs} +50 -68
  261. package/dist/menus-DzRyC1ID.mjs.map +1 -0
  262. package/dist/{menus-8O6xNcYW.mjs → menus-SL2K0xq2.mjs} +3 -3
  263. package/dist/{menus-8O6xNcYW.mjs.map → menus-SL2K0xq2.mjs.map} +1 -1
  264. package/dist/object-cache/memory.d.mts +1 -1
  265. package/dist/{object-cache-SEb2IM4Z.mjs → object-cache-CHbHv83-.mjs} +8 -4
  266. package/dist/object-cache-CHbHv83-.mjs.map +1 -0
  267. package/dist/{options-BvClpP5Q.d.mts → options-41nCWqi9.d.mts} +2 -2
  268. package/dist/{options-BvClpP5Q.d.mts.map → options-41nCWqi9.d.mts.map} +1 -1
  269. package/dist/page/index.d.mts +6 -3
  270. package/dist/page/index.d.mts.map +1 -1
  271. package/dist/page/index.mjs +3 -2
  272. package/dist/page/index.mjs.map +1 -1
  273. package/dist/{parse-DqMWHQ8x.mjs → parse-CqyZj3Hp.mjs} +2 -2
  274. package/dist/{parse-DqMWHQ8x.mjs.map → parse-CqyZj3Hp.mjs.map} +1 -1
  275. package/dist/plugin-types.d.mts +4 -2
  276. package/dist/plugin-types.d.mts.map +1 -1
  277. package/dist/plugin-utils.d.mts +10 -9
  278. package/dist/plugin-utils.d.mts.map +1 -1
  279. package/dist/plugins/adapt-sandbox-entry.d.mts +10 -9
  280. package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -1
  281. package/dist/plugins/adapt-sandbox-entry.mjs +1 -1
  282. package/dist/{query-34pfm02Q.mjs → query-B0M-9LYR.mjs} +14 -14
  283. package/dist/{query-34pfm02Q.mjs.map → query-B0M-9LYR.mjs.map} +1 -1
  284. package/dist/{rate-limit-CBQK4ygJ.mjs → rate-limit-BwoUdIpC.mjs} +2 -2
  285. package/dist/{rate-limit-CBQK4ygJ.mjs.map → rate-limit-BwoUdIpC.mjs.map} +1 -1
  286. package/dist/{redirect-C1KVGYWe.mjs → redirect-lMOzG04S.mjs} +2 -2
  287. package/dist/{redirect-C1KVGYWe.mjs.map → redirect-lMOzG04S.mjs.map} +1 -1
  288. package/dist/{redirects-DgnypVir.mjs → redirects-9218MmYl.mjs} +3 -3
  289. package/dist/{redirects-DgnypVir.mjs.map → redirects-9218MmYl.mjs.map} +1 -1
  290. package/dist/{registry-3aOAS4My.mjs → registry-DP-iEtdR.mjs} +4 -4
  291. package/dist/{registry-3aOAS4My.mjs.map → registry-DP-iEtdR.mjs.map} +1 -1
  292. package/dist/{runner-CEtq-6Sm.d.mts → runner-BbR3DfrL.d.mts} +2 -2
  293. package/dist/{runner-CEtq-6Sm.d.mts.map → runner-BbR3DfrL.d.mts.map} +1 -1
  294. package/dist/{runner-BsquExlK.mjs → runner-CFngB4WZ.mjs} +296 -191
  295. package/dist/runner-CFngB4WZ.mjs.map +1 -0
  296. package/dist/runtime.d.mts +10 -9
  297. package/dist/runtime.d.mts.map +1 -1
  298. package/dist/runtime.mjs +2 -2
  299. package/dist/scheduled-publish-BMljtY5G.d.mts +11 -0
  300. package/dist/scheduled-publish-BMljtY5G.d.mts.map +1 -0
  301. package/dist/{schema-BGwx8AQY.mjs → schema-BfJ7bIXH.mjs} +5 -5
  302. package/dist/{schema-BGwx8AQY.mjs.map → schema-BfJ7bIXH.mjs.map} +1 -1
  303. package/dist/{search-Gl747zMP.mjs → search-C7t3PH1J.mjs} +6 -4
  304. package/dist/search-C7t3PH1J.mjs.map +1 -0
  305. package/dist/{sections-C-umuuLj.mjs → sections-Dm0CVX23.mjs} +3 -3
  306. package/dist/{sections-C-umuuLj.mjs.map → sections-Dm0CVX23.mjs.map} +1 -1
  307. package/dist/seed/index.d.mts +2 -2
  308. package/dist/seed/index.mjs +15 -15
  309. package/dist/{seo-BNzkiK_W.mjs → seo-Dd3tbY_k.mjs} +3 -3
  310. package/dist/{seo-BNzkiK_W.mjs.map → seo-Dd3tbY_k.mjs.map} +1 -1
  311. package/dist/{service-CQHK_10l.mjs → service-CTrqp_Jy.mjs} +2 -2
  312. package/dist/{service-CQHK_10l.mjs.map → service-CTrqp_Jy.mjs.map} +1 -1
  313. package/dist/{settings-C4tXRYiG.mjs → settings-BX8cWBus.mjs} +4 -4
  314. package/dist/{settings-C4tXRYiG.mjs.map → settings-BX8cWBus.mjs.map} +1 -1
  315. package/dist/{settings-fPSR6dJa.mjs → settings-CZ7ZP9cW.mjs} +3 -3
  316. package/dist/{settings-fPSR6dJa.mjs.map → settings-CZ7ZP9cW.mjs.map} +1 -1
  317. package/dist/storage/local.d.mts +1 -1
  318. package/dist/storage/s3.d.mts +1 -1
  319. package/dist/{taxonomies-DbAtL6m4.mjs → taxonomies-DChKcVxf.mjs} +5 -4
  320. package/dist/taxonomies-DChKcVxf.mjs.map +1 -0
  321. package/dist/{taxonomies-n8w72lLQ.mjs → taxonomies-ckPjz-6d.mjs} +75 -20
  322. package/dist/taxonomies-ckPjz-6d.mjs.map +1 -0
  323. package/dist/{taxonomy-BBUlGug5.mjs → taxonomy-Cru-he6s.mjs} +4 -4
  324. package/dist/{taxonomy-BBUlGug5.mjs.map → taxonomy-Cru-he6s.mjs.map} +1 -1
  325. package/dist/{transport-BZ-03aFT.d.mts → transport-Blrl2k_o.d.mts} +1 -1
  326. package/dist/{transport-BZ-03aFT.d.mts.map → transport-Blrl2k_o.d.mts.map} +1 -1
  327. package/dist/{types-h2Cwbzi2.d.mts → types-BYnDBVwe.d.mts} +24 -5
  328. package/dist/{types-h2Cwbzi2.d.mts.map → types-BYnDBVwe.d.mts.map} +1 -1
  329. package/dist/{types-BG8_XTLj.d.mts → types-BkZ8DUEI.d.mts} +1 -1
  330. package/dist/{types-BG8_XTLj.d.mts.map → types-BkZ8DUEI.d.mts.map} +1 -1
  331. package/dist/{types-DrYMm9s5.d.mts → types-ByChcBgE.d.mts} +1 -1
  332. package/dist/{types-DrYMm9s5.d.mts.map → types-ByChcBgE.d.mts.map} +1 -1
  333. package/dist/{types-BWqGVhFn.d.mts → types-CvuKO5Pn.d.mts} +1 -1
  334. package/dist/{types-BWqGVhFn.d.mts.map → types-CvuKO5Pn.d.mts.map} +1 -1
  335. package/dist/{types-CmI1OyQg.mjs → types-DU4TSFS5.mjs} +2 -2
  336. package/dist/{types-CmI1OyQg.mjs.map → types-DU4TSFS5.mjs.map} +1 -1
  337. package/dist/{types-CUsXGRVj.d.mts → types-Dbqff978.d.mts} +1 -1
  338. package/dist/{types-CUsXGRVj.d.mts.map → types-Dbqff978.d.mts.map} +1 -1
  339. package/dist/{types-fS7-VTdR.d.mts → types-Y09-wtyU.d.mts} +1 -1
  340. package/dist/{types-fS7-VTdR.d.mts.map → types-Y09-wtyU.d.mts.map} +1 -1
  341. package/dist/{types-B-gIxFCE.d.mts → types-xIfVRNLp.d.mts} +38 -1
  342. package/dist/types-xIfVRNLp.d.mts.map +1 -0
  343. package/dist/{user-JAuc8VUf.mjs → user-DgGbKL9s.mjs} +3 -3
  344. package/dist/{user-JAuc8VUf.mjs.map → user-DgGbKL9s.mjs.map} +1 -1
  345. package/dist/{validate-Va_An3qb.d.mts → validate-9ECmtEpJ.d.mts} +10 -5
  346. package/dist/{validate-Va_An3qb.d.mts.map → validate-9ECmtEpJ.d.mts.map} +1 -1
  347. package/dist/{validate-BzXHWnkV.mjs → validate-CRkcYZAR.mjs} +2 -2
  348. package/dist/{validate-BzXHWnkV.mjs.map → validate-CRkcYZAR.mjs.map} +1 -1
  349. package/dist/{validation-B6DKPTtU.mjs → validation-sR26SOwD.mjs} +5 -5
  350. package/dist/{validation-B6DKPTtU.mjs.map → validation-sR26SOwD.mjs.map} +1 -1
  351. package/dist/version-CrqhKGWn.mjs +7 -0
  352. package/dist/{version-DHdCA24u.mjs.map → version-CrqhKGWn.mjs.map} +1 -1
  353. package/dist/{widgets-C2RbFN0G.mjs → widgets-DRgtn7Cl.mjs} +2 -2
  354. package/dist/{widgets-C2RbFN0G.mjs.map → widgets-DRgtn7Cl.mjs.map} +1 -1
  355. package/dist/{zod-generator-xEHODMJf.mjs → zod-generator-C5JaV--i.mjs} +6 -3
  356. package/dist/zod-generator-C5JaV--i.mjs.map +1 -0
  357. package/package.json +8 -8
  358. package/src/api/handlers/content.ts +59 -16
  359. package/src/api/handlers/plugins.ts +77 -10
  360. package/src/api/handlers/taxonomies.ts +5 -1
  361. package/src/astro/integration/virtual-modules.ts +15 -0
  362. package/src/astro/integration/vite-config.ts +19 -1
  363. package/src/astro/middleware/auth.ts +1 -0
  364. package/src/astro/middleware.ts +1 -0
  365. package/src/astro/routes/api/admin/plugins/[id]/disable.ts +6 -1
  366. package/src/astro/routes/api/admin/plugins/[id]/enable.ts +6 -1
  367. package/src/astro/routes/api/admin/plugins/[id]/index.ts +1 -0
  368. package/src/astro/routes/api/admin/plugins/index.ts +1 -0
  369. package/src/astro/routes/api/manifest.ts +20 -1
  370. package/src/astro/routes/api/setup/dev-bypass.ts +7 -2
  371. package/src/astro/routes/sitemap-[collection].xml.ts +5 -4
  372. package/src/astro/types.ts +4 -0
  373. package/src/cli/commands/seed.ts +1 -1
  374. package/src/components/EmDashHead.astro +23 -3
  375. package/src/components/JsonLdScript.astro +9 -0
  376. package/src/components/LiveSearch.astro +40 -4
  377. package/src/components/live-search-routing.ts +30 -0
  378. package/src/database/migrations/036_i18n_menus_and_taxonomies.ts +39 -18
  379. package/src/database/migrations/046_media_usage_index.ts +115 -0
  380. package/src/database/migrations/047_restore_taxonomy_parent_index.ts +29 -0
  381. package/src/database/migrations/048_restore_content_taxonomies_term_index.ts +32 -0
  382. package/src/database/migrations/runner.ts +6 -0
  383. package/src/database/repositories/media-usage.ts +444 -0
  384. package/src/database/types.ts +39 -0
  385. package/src/emdash-runtime.ts +61 -52
  386. package/src/index.ts +3 -0
  387. package/src/loader.ts +110 -31
  388. package/src/media/usage/extractor.ts +302 -0
  389. package/src/media/usage/types.ts +46 -0
  390. package/src/object-cache/index.ts +8 -2
  391. package/src/page/metadata.ts +23 -3
  392. package/src/plugin-types.ts +10 -0
  393. package/src/plugins/define-plugin.ts +14 -5
  394. package/src/plugins/hooks.ts +53 -65
  395. package/src/plugins/index.ts +5 -0
  396. package/src/plugins/manager.ts +22 -0
  397. package/src/plugins/manifest-schema.ts +2 -0
  398. package/src/plugins/types.ts +35 -3
  399. package/src/schema/zod-generator.ts +9 -1
  400. package/src/search/query.ts +3 -0
  401. package/src/search/types.ts +2 -0
  402. package/src/seed/apply.ts +8 -2
  403. package/src/seed/types.ts +6 -1
  404. package/src/taxonomies/index.ts +121 -18
  405. package/dist/api-D2_sbavn.mjs.map +0 -1
  406. package/dist/apply-Bk2mAuLn.mjs.map +0 -1
  407. package/dist/index-Cz6_JBL3.d.mts.map +0 -1
  408. package/dist/loader-DrpofRon.mjs.map +0 -1
  409. package/dist/manifest-schema-BOfPXse0.mjs.map +0 -1
  410. package/dist/menus-CxqJ-z-N.mjs.map +0 -1
  411. package/dist/object-cache-SEb2IM4Z.mjs.map +0 -1
  412. package/dist/runner-BsquExlK.mjs.map +0 -1
  413. package/dist/search-Gl747zMP.mjs.map +0 -1
  414. package/dist/taxonomies-DbAtL6m4.mjs.map +0 -1
  415. package/dist/taxonomies-n8w72lLQ.mjs.map +0 -1
  416. package/dist/types-B-gIxFCE.d.mts.map +0 -1
  417. package/dist/version-DHdCA24u.mjs +0 -7
  418. package/dist/zod-generator-xEHODMJf.mjs.map +0 -1
package/src/loader.ts CHANGED
@@ -25,15 +25,23 @@ import { isMissingColumnError, isMissingTableError } from "./utils/db-errors.js"
25
25
  const FIELD_NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
26
26
 
27
27
  /**
28
- * SEO columns joined in from `_emdash_seo` on the single-entry path, mapped to
29
- * aliased result keys. SEO lives in a side table, so a LEFT JOIN folds it into
30
- * the entry load at zero extra query cost; the result is surfaced as a nested
31
- * `data.seo` object (see extractSeo) rather than flat fields.
28
+ * SEO columns folded into the single-entry query as a single JSON column
29
+ * (`_emdash_seo` in the result set), then expanded onto the row under these
30
+ * aliases for `extractSeo()`. Surfacing SEO as one aggregated column keeps the
31
+ * result-set width bounded regardless of how many fields the collection has,
32
+ * which matters for D1: a flat `LEFT JOIN _emdash_seo` adds 5 alias columns to
33
+ * every row and pushes wide collections (common after WordPress / ACF imports)
34
+ * past D1's per-result-set column limit, surfacing as a silent null entry.
35
+ * One JSON column is one column, so the join stays safe at any schema width.
36
+ *
37
+ * The aliases mirror the strategy used by `foldedHydrationSelects` for byline
38
+ * and taxonomy hydration: aggregate in SQL, expand in JS. SEO is 1:1 with
39
+ * content, so the subquery uses `json_object` (not the array aggregator).
32
40
  *
33
41
  * The `_emdash_` prefix on the aliases guarantees they can never collide with
34
42
  * a content field. Field slugs must match `/^[a-z][a-z0-9_]*$/`, so a user can
35
- * legitimately define a `seo_title` field; selecting the joined column under
36
- * its bare name would shadow that field in the result set and drop the user's
43
+ * legitimately define a `seo_title` field; surfacing the SEO column under its
44
+ * bare name would shadow that field in the result set and drop the user's
37
45
  * value. The prefix (illegal as a leading slug char) sidesteps this entirely.
38
46
  */
39
47
  const SEO_COLUMN_ALIASES: Record<string, string> = {
@@ -47,6 +55,9 @@ const SEO_COLUMN_ALIASES: Record<string, string> = {
47
55
  /** Aliased SEO result keys — excluded from generic field mapping. */
48
56
  const SEO_ALIAS_COLUMNS = Object.values(SEO_COLUMN_ALIASES);
49
57
 
58
+ /** Folded SEO JSON column name in the result set (expanded onto aliases in JS). */
59
+ const SEO_FOLDED_COLUMN = "_emdash_seo";
60
+
50
61
  /**
51
62
  * System columns excluded from entry.data
52
63
  * Note: slug is intentionally NOT excluded - it's useful as data.slug in templates
@@ -67,15 +78,17 @@ const SYSTEM_COLUMNS = new Set([
67
78
  "draft_revision_id",
68
79
  "locale",
69
80
  "translation_group",
70
- // Aliased SEO columns joined from _emdash_seo on the single-entry path.
71
- // Surfaced as a nested data.seo object (see extractSeo), never as flat
72
- // fields. The aliases are _emdash_-prefixed so they can't shadow a user
73
- // field named e.g. `seo_title`.
81
+ // Aliased SEO columns expanded from the folded _emdash_seo JSON column on
82
+ // the single-entry path. Surfaced as a nested data.seo object (see
83
+ // extractSeo), never as flat fields. The aliases are _emdash_-prefixed so
84
+ // they can't shadow a user field named e.g. `seo_title`.
74
85
  ...SEO_ALIAS_COLUMNS,
75
- // Folded hydration JSON columns (see foldedHydrationSelects) — surfaced via
76
- // the FOLDED_* markers, never as flat fields.
86
+ // Folded hydration JSON columns (see foldedHydrationSelects and
87
+ // foldedSeoSelect) — surfaced via the FOLDED_* markers or expanded onto
88
+ // SEO_ALIAS_COLUMNS, never as flat fields.
77
89
  "_emdash_terms",
78
90
  "_emdash_bylines",
91
+ SEO_FOLDED_COLUMN,
79
92
  ]);
80
93
 
81
94
  /** Markers for byline/taxonomy hydration folded into the content query. */
@@ -123,6 +136,62 @@ function foldedHydrationSelects(db: Kysely<any>, type: string, outer: string) {
123
136
  return { terms, bylines };
124
137
  }
125
138
 
139
+ /**
140
+ * Correlated JSON-object subquery that folds per-entry SEO into the content
141
+ * query without widening the result set: 1 row of `_emdash_seo` becomes 1 JSON
142
+ * column rather than 5 flat columns. The JSON column is expanded onto the row
143
+ * via {@link expandFoldedSeo} after the query runs, preserving the alias keys
144
+ * that {@link extractSeo} reads. Missing SEO row (no entry in `_emdash_seo`)
145
+ * yields NULL, which {@link expandFoldedSeo} treats as "no SEO" - identical to
146
+ * the prior LEFT JOIN miss behavior.
147
+ *
148
+ * Dialect-specific aggregation mirrors {@link foldedHydrationSelects}: SQLite
149
+ * `json_object` returns a JSON *string*, Postgres `json_build_object` returns
150
+ * parsed JSON; both branches are handled in expansion.
151
+ */
152
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- any Kysely instance
153
+ function foldedSeoSelect(db: Kysely<any>, type: string, outer: string) {
154
+ const o = sql.ref(outer);
155
+ const pg = isPostgres(db);
156
+ // Use raw column names (not aliases) as JSON keys: the JSON is expanded back
157
+ // onto SEO_COLUMN_ALIASES in JS, and keeping the keys matched to the
158
+ // underlying columns makes the SQL readable and the expansion 1-to-1.
159
+ const pairs =
160
+ "'seo_title', s.seo_title, 'seo_description', s.seo_description, 'seo_image', s.seo_image, 'seo_canonical', s.seo_canonical, 'seo_no_index', s.seo_no_index";
161
+ const obj = pg ? sql.raw(`json_build_object(${pairs})`) : sql.raw(`json_object(${pairs})`);
162
+ return sql`(SELECT ${obj} FROM ${sql.ref("_emdash_seo")} AS s WHERE s.collection = ${type} AND s.content_id = ${o}.id LIMIT 1) AS ${sql.ref(SEO_FOLDED_COLUMN)}`;
163
+ }
164
+
165
+ /**
166
+ * Expand the folded `_emdash_seo` JSON column onto the row using SEO_COLUMN_ALIASES,
167
+ * so {@link extractSeo} reads it transparently. SQLite returns a JSON string
168
+ * (parse it); Postgres returns already-parsed JSON. Missing/malformed/null is
169
+ * a no-op: {@link extractSeo} returns null when the aliases are absent.
170
+ */
171
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
172
+ return !!value && typeof value === "object" && !Array.isArray(value);
173
+ }
174
+
175
+ function expandFoldedSeo(row: Record<string, unknown>): void {
176
+ const raw = row[SEO_FOLDED_COLUMN];
177
+ delete row[SEO_FOLDED_COLUMN];
178
+ let parsed: Record<string, unknown> | null = null;
179
+ if (typeof raw === "string") {
180
+ try {
181
+ const candidate: unknown = JSON.parse(raw);
182
+ if (isPlainObject(candidate)) parsed = candidate;
183
+ } catch {
184
+ return; // malformed JSON: leave the row without SEO aliases (extractSeo returns null)
185
+ }
186
+ } else if (isPlainObject(raw)) {
187
+ parsed = raw;
188
+ }
189
+ if (!parsed) return;
190
+ for (const [col, alias] of Object.entries(SEO_COLUMN_ALIASES)) {
191
+ row[alias] = parsed[col] ?? null;
192
+ }
193
+ }
194
+
126
195
  /**
127
196
  * Stash folded hydration JSON (non-enumerable) for the query.ts fast paths.
128
197
  * SQLite returns a JSON string (parse it); Postgres returns already-parsed JSON.
@@ -227,6 +296,18 @@ async function getTaxonomyNames(db: Kysely<Database>): Promise<Set<string>> {
227
296
  }
228
297
  }
229
298
 
299
+ /**
300
+ * Reset the module-scoped taxonomy-names cache.
301
+ *
302
+ * Called from `invalidateTaxonomyDefsCache()` so that creating or seeding a
303
+ * taxonomy definition is reflected within the current isolate instead of
304
+ * waiting for the isolate to recycle. Keeps this cache consistent with the
305
+ * isolate-wide taxonomy-defs cache in `taxonomies/index.ts`.
306
+ */
307
+ export function resetTaxonomyNamesCache(): void {
308
+ taxonomyNames = null;
309
+ }
310
+
230
311
  /**
231
312
  * System columns to include in data (mapped to camelCase where needed)
232
313
  */
@@ -879,11 +960,12 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
879
960
  taxonomyFilters.map(
880
961
  (f) => sql`AND EXISTS (
881
962
  SELECT 1 FROM content_taxonomies ct
882
- INNER JOIN taxonomies t ON t.id = ct.taxonomy_id
963
+ INNER JOIN taxonomies t ON t.translation_group = ct.taxonomy_id
883
964
  WHERE ct.collection = ${type}
884
965
  AND ct.entry_id = ${sql.ref(tableName)}.id
885
966
  AND t.name = ${f.name}
886
967
  AND t.slug IN (${sql.join(f.slugs.map((s) => sql`${s}`))})
968
+ ${locale ? sql`AND t.locale = ${locale}` : sql``}
887
969
  )`,
888
970
  ),
889
971
  sql` `,
@@ -1050,31 +1132,25 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
1050
1132
  // When locale is specified, prefer locale-scoped slug match,
1051
1133
  // but IDs are globally unique so always check id without locale scope.
1052
1134
  //
1053
- // LEFT JOIN _emdash_seo folds per-entry SEO (canonical, noindex,
1054
- // etc.) into this single query at zero extra round-trip cost. The
1055
- // joined columns are surfaced as a nested data.seo object via
1056
- // extractSeo() and excluded from the generic field mapping. SEO is
1057
- // 1:1 with content (PK on collection+content_id), so the join never
1058
- // multiplies rows.
1059
- const seoSelect = sql.join(
1060
- Object.entries(SEO_COLUMN_ALIASES).map(
1061
- ([col, alias]) => sql`${sql.ref(`s.${col}`)} AS ${sql.ref(alias)}`,
1062
- ),
1063
- );
1064
- // Fold byline + taxonomy hydration into the content query (see
1065
- // foldedHydrationSelects), removing the two separate hydration
1066
- // round trips per fetch.
1135
+ // Byline + taxonomy hydration (foldedHydrationSelects) and per-entry
1136
+ // SEO (foldedSeoSelect) are each surfaced as a single aggregated JSON
1137
+ // column rather than flat columns. This keeps the result-set width
1138
+ // bounded at any collection schema width: a flat `LEFT JOIN _emdash_seo`
1139
+ // adds 5 alias columns to every row and pushes wide flat-schema
1140
+ // collections (common after WordPress / ACF imports) past D1's
1141
+ // per-result-set column limit, surfacing as a silent null entry. One
1142
+ // JSON column is one column, so the join stays safe at any width and
1143
+ // we keep the single round trip.
1067
1144
  const { terms: termsSelect, bylines: bylinesSelect } = foldedHydrationSelects(
1068
1145
  db,
1069
1146
  type,
1070
1147
  "c",
1071
1148
  );
1149
+ const seoSelect = foldedSeoSelect(db, type, "c");
1072
1150
  const result = locale
1073
1151
  ? await sql<Record<string, unknown>>`
1074
1152
  SELECT c.*, ${seoSelect}, ${termsSelect}, ${bylinesSelect}
1075
1153
  FROM ${sql.ref(tableName)} AS c
1076
- LEFT JOIN ${sql.ref("_emdash_seo")} AS s
1077
- ON s.collection = ${type} AND s.content_id = c.id
1078
1154
  WHERE c.deleted_at IS NULL
1079
1155
  AND ((c.slug = ${id} AND c.locale = ${locale}) OR c.id = ${id})
1080
1156
  LIMIT 1
@@ -1082,8 +1158,6 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
1082
1158
  : await sql<Record<string, unknown>>`
1083
1159
  SELECT c.*, ${seoSelect}, ${termsSelect}, ${bylinesSelect}
1084
1160
  FROM ${sql.ref(tableName)} AS c
1085
- LEFT JOIN ${sql.ref("_emdash_seo")} AS s
1086
- ON s.collection = ${type} AND s.content_id = c.id
1087
1161
  WHERE c.deleted_at IS NULL
1088
1162
  AND (c.slug = ${id} OR c.id = ${id})
1089
1163
  LIMIT 1
@@ -1094,6 +1168,11 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
1094
1168
  return undefined;
1095
1169
  }
1096
1170
 
1171
+ // Expand the folded SEO JSON column onto SEO_COLUMN_ALIASES so
1172
+ // extractSeo() reads it transparently. Missing/null SEO is a
1173
+ // no-op: extractSeo() returns null when the aliases are absent.
1174
+ expandFoldedSeo(row);
1175
+
1097
1176
  const i18nConfig = virtualConfig?.i18n;
1098
1177
  const i18nEnabled = i18nConfig && i18nConfig.locales.length > 1;
1099
1178
  const entrySlug = rowStr(row, "slug") || rowStr(row, "id");
@@ -0,0 +1,302 @@
1
+ import { normalizeMime } from "../mime.js";
2
+ import type {
3
+ ExtractedMediaUsageOccurrence,
4
+ ExtractMediaUsageOccurrencesInput,
5
+ MediaKind,
6
+ MediaUsageExtractionSubField,
7
+ MediaUsageReferenceType,
8
+ } from "./types.js";
9
+
10
+ const INTERNAL_MEDIA_PREFIX = "/_emdash/api/media/file/";
11
+ const URL_LIKE_RE = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
12
+
13
+ interface MediaRef {
14
+ mediaId: string | null;
15
+ provider: string;
16
+ providerAssetId: string;
17
+ mediaKind: MediaKind | null;
18
+ mimeType: string | null;
19
+ }
20
+
21
+ interface AddOccurrenceInput {
22
+ fieldSlug: string;
23
+ fieldPath: string;
24
+ referenceType: MediaUsageReferenceType;
25
+ value: unknown;
26
+ fallbackKind: MediaKind | null;
27
+ }
28
+
29
+ export function extractMediaUsageOccurrences({
30
+ fields,
31
+ data,
32
+ }: ExtractMediaUsageOccurrencesInput): ExtractedMediaUsageOccurrence[] {
33
+ const occurrences: ExtractedMediaUsageOccurrence[] = [];
34
+ const seen = new Set<string>();
35
+
36
+ for (const field of fields) {
37
+ const value = data[field.slug];
38
+
39
+ if (field.type === "image") {
40
+ addOccurrence(occurrences, seen, {
41
+ fieldSlug: field.slug,
42
+ fieldPath: field.slug,
43
+ referenceType: "image_field",
44
+ value,
45
+ fallbackKind: "image",
46
+ });
47
+ continue;
48
+ }
49
+
50
+ if (field.type === "file") {
51
+ addOccurrence(occurrences, seen, {
52
+ fieldSlug: field.slug,
53
+ fieldPath: field.slug,
54
+ referenceType: "file_field",
55
+ value,
56
+ fallbackKind: null,
57
+ });
58
+ continue;
59
+ }
60
+
61
+ if (field.type === "repeater") {
62
+ extractRepeaterOccurrences(occurrences, seen, field.slug, value, field.validation?.subFields);
63
+ continue;
64
+ }
65
+
66
+ if (field.type === "portableText") {
67
+ extractPortableTextOccurrences(occurrences, seen, field.slug, value);
68
+ }
69
+ }
70
+
71
+ return occurrences;
72
+ }
73
+
74
+ function extractRepeaterOccurrences(
75
+ occurrences: ExtractedMediaUsageOccurrence[],
76
+ seen: Set<string>,
77
+ fieldSlug: string,
78
+ value: unknown,
79
+ subFields: readonly MediaUsageExtractionSubField[] | undefined,
80
+ ): void {
81
+ if (!Array.isArray(value) || !Array.isArray(subFields)) return;
82
+
83
+ for (const [itemIndex, item] of value.entries()) {
84
+ if (!isRecord(item)) continue;
85
+
86
+ for (const subField of subFields) {
87
+ if (subField.type !== "image" && subField.type !== "file") continue;
88
+
89
+ addOccurrence(occurrences, seen, {
90
+ fieldSlug,
91
+ fieldPath: `${fieldSlug}[${itemIndex}].${subField.slug}`,
92
+ referenceType: subField.type === "image" ? "image_field" : "file_field",
93
+ value: item[subField.slug],
94
+ fallbackKind: subField.type === "image" ? "image" : null,
95
+ });
96
+ }
97
+ }
98
+ }
99
+
100
+ function extractPortableTextOccurrences(
101
+ occurrences: ExtractedMediaUsageOccurrence[],
102
+ seen: Set<string>,
103
+ fieldSlug: string,
104
+ value: unknown,
105
+ ): void {
106
+ if (!Array.isArray(value)) return;
107
+
108
+ for (const [blockIndex, block] of value.entries()) {
109
+ if (!isRecord(block) || block._type !== "image" || !isRecord(block.asset)) continue;
110
+
111
+ const provider = normalizeProvider(block.asset.provider);
112
+ const ref = readPortableTextAssetRef(block.asset, provider);
113
+ if (!ref) continue;
114
+
115
+ addRefOccurrence(occurrences, seen, {
116
+ fieldSlug,
117
+ fieldPath: `${fieldSlug}[${blockIndex}].asset.${ref.key}`,
118
+ referenceType: "portable_text_image",
119
+ ref: buildMediaRef({
120
+ id: ref.id,
121
+ provider,
122
+ mimeType: normalizeMimeValue(block.asset.mimeType),
123
+ fallbackKind: "image",
124
+ }),
125
+ });
126
+ }
127
+ }
128
+
129
+ function addOccurrence(
130
+ occurrences: ExtractedMediaUsageOccurrence[],
131
+ seen: Set<string>,
132
+ input: AddOccurrenceInput,
133
+ ): void {
134
+ const ref = readMediaRef(input.value, input.fallbackKind);
135
+ if (!ref) return;
136
+
137
+ addRefOccurrence(occurrences, seen, {
138
+ fieldSlug: input.fieldSlug,
139
+ fieldPath: input.fieldPath,
140
+ referenceType: input.referenceType,
141
+ ref,
142
+ });
143
+ }
144
+
145
+ function addRefOccurrence(
146
+ occurrences: ExtractedMediaUsageOccurrence[],
147
+ seen: Set<string>,
148
+ input: {
149
+ fieldSlug: string;
150
+ fieldPath: string;
151
+ referenceType: MediaUsageReferenceType;
152
+ ref: MediaRef | null;
153
+ },
154
+ ): void {
155
+ if (!input.ref) return;
156
+
157
+ const occurrence: ExtractedMediaUsageOccurrence = {
158
+ fieldSlug: input.fieldSlug,
159
+ fieldPath: input.fieldPath,
160
+ occurrenceIndex: 0,
161
+ referenceType: input.referenceType,
162
+ mediaId: input.ref.mediaId,
163
+ provider: input.ref.provider,
164
+ providerAssetId: input.ref.providerAssetId,
165
+ mediaKind: input.ref.mediaKind,
166
+ mimeType: input.ref.mimeType,
167
+ };
168
+
169
+ const key = [
170
+ occurrence.fieldSlug,
171
+ occurrence.fieldPath,
172
+ occurrence.occurrenceIndex,
173
+ occurrence.referenceType,
174
+ occurrence.provider,
175
+ occurrence.providerAssetId,
176
+ occurrence.mediaId ?? "",
177
+ ].join("\0");
178
+
179
+ if (seen.has(key)) return;
180
+ seen.add(key);
181
+ occurrences.push(occurrence);
182
+ }
183
+
184
+ function readMediaRef(value: unknown, fallbackKind: MediaKind | null): MediaRef | null {
185
+ if (typeof value === "string") {
186
+ const id = normalizeLocalMediaId(value);
187
+ return id ? buildMediaRef({ id, provider: "local", mimeType: null, fallbackKind }) : null;
188
+ }
189
+
190
+ if (!isRecord(value)) return null;
191
+
192
+ const provider = normalizeProvider(value.provider);
193
+ const id = provider === "local" ? normalizeLocalMediaId(value.id) : normalizeStableId(value.id);
194
+ if (!id) return null;
195
+
196
+ return buildMediaRef({
197
+ id,
198
+ provider,
199
+ mimeType: normalizeMimeValue(value.mimeType),
200
+ fallbackKind,
201
+ });
202
+ }
203
+
204
+ function buildMediaRef(input: {
205
+ id: string;
206
+ provider: string;
207
+ mimeType: string | null;
208
+ fallbackKind: MediaKind | null;
209
+ }): MediaRef | null {
210
+ const provider = normalizeProvider(input.provider);
211
+ if (provider === "external") return null;
212
+
213
+ return {
214
+ mediaId: provider === "local" ? input.id : null,
215
+ provider,
216
+ providerAssetId: input.id,
217
+ mediaKind: mediaKindFromMime(input.mimeType) ?? input.fallbackKind,
218
+ mimeType: input.mimeType,
219
+ };
220
+ }
221
+
222
+ function readPortableTextAssetRef(
223
+ asset: Record<string, unknown>,
224
+ provider: string,
225
+ ): { key: "_ref" | "id"; id: string } | null {
226
+ const normalizeId = provider === "local" ? normalizeLocalMediaId : normalizeStableId;
227
+ const ref = normalizeId(asset._ref);
228
+ if (ref) return { key: "_ref", id: ref };
229
+
230
+ const id = normalizeId(asset.id);
231
+ if (id) return { key: "id", id };
232
+
233
+ return null;
234
+ }
235
+
236
+ function normalizeProvider(value: unknown): string {
237
+ const provider = readString(value)?.trim();
238
+ return provider || "local";
239
+ }
240
+
241
+ function normalizeLocalMediaId(value: unknown): string | null {
242
+ const id = normalizeStableId(value);
243
+ if (!id) return null;
244
+ return id.includes("/") ? null : id;
245
+ }
246
+
247
+ function normalizeStableId(value: unknown): string | null {
248
+ if (typeof value !== "string") return null;
249
+ const trimmed = value.trim();
250
+ if (!trimmed) return null;
251
+ if (URL_LIKE_RE.test(trimmed)) return null;
252
+ if (trimmed.startsWith(INTERNAL_MEDIA_PREFIX)) return null;
253
+ return trimmed;
254
+ }
255
+
256
+ function normalizeMimeValue(value: unknown): string | null {
257
+ if (typeof value !== "string") return null;
258
+ const normalized = normalizeMime(value);
259
+ return normalized.includes("/") ? normalized : null;
260
+ }
261
+
262
+ function mediaKindFromMime(mimeType: string | null): MediaKind | null {
263
+ if (!mimeType) return null;
264
+ if (mimeType.startsWith("image/")) return "image";
265
+ if (mimeType.startsWith("video/")) return "video";
266
+ if (mimeType.startsWith("audio/")) return "audio";
267
+ if (mimeType.startsWith("font/") || mimeType.startsWith("application/font-")) return "font";
268
+ if (mimeType.startsWith("text/")) return "text";
269
+ if (isDocumentMime(mimeType)) return "document";
270
+ if (isArchiveMime(mimeType)) return "archive";
271
+ return "other";
272
+ }
273
+
274
+ function isDocumentMime(mimeType: string): boolean {
275
+ return (
276
+ mimeType === "application/pdf" ||
277
+ mimeType === "application/msword" ||
278
+ mimeType === "application/rtf" ||
279
+ mimeType === "application/vnd.ms-excel" ||
280
+ mimeType === "application/vnd.ms-powerpoint" ||
281
+ mimeType.startsWith("application/vnd.openxmlformats-officedocument.")
282
+ );
283
+ }
284
+
285
+ function isArchiveMime(mimeType: string): boolean {
286
+ return (
287
+ mimeType === "application/zip" ||
288
+ mimeType === "application/gzip" ||
289
+ mimeType === "application/x-tar" ||
290
+ mimeType === "application/x-7z-compressed" ||
291
+ mimeType === "application/x-rar-compressed" ||
292
+ mimeType === "application/vnd.rar"
293
+ );
294
+ }
295
+
296
+ function readString(value: unknown): string | null {
297
+ return typeof value === "string" ? value : null;
298
+ }
299
+
300
+ function isRecord(value: unknown): value is Record<string, unknown> {
301
+ return typeof value === "object" && value !== null && !Array.isArray(value);
302
+ }
@@ -0,0 +1,46 @@
1
+ import type { FieldType } from "../../schema/types.js";
2
+
3
+ export type MediaKind =
4
+ | "image"
5
+ | "video"
6
+ | "audio"
7
+ | "document"
8
+ | "archive"
9
+ | "font"
10
+ | "text"
11
+ | "other";
12
+
13
+ export type MediaUsageReferenceType = "image_field" | "file_field" | "portable_text_image";
14
+
15
+ export interface MediaUsageExtractionSubField {
16
+ slug: string;
17
+ type: FieldType;
18
+ label?: string;
19
+ }
20
+
21
+ export interface MediaUsageExtractionValidation {
22
+ subFields?: readonly MediaUsageExtractionSubField[];
23
+ }
24
+
25
+ export interface MediaUsageExtractionField {
26
+ slug: string;
27
+ type: FieldType;
28
+ validation?: MediaUsageExtractionValidation | null;
29
+ }
30
+
31
+ export interface ExtractMediaUsageOccurrencesInput {
32
+ fields: readonly MediaUsageExtractionField[];
33
+ data: Record<string, unknown>;
34
+ }
35
+
36
+ export interface ExtractedMediaUsageOccurrence {
37
+ fieldSlug: string;
38
+ fieldPath: string;
39
+ occurrenceIndex: number;
40
+ referenceType: MediaUsageReferenceType;
41
+ mediaId: string | null;
42
+ provider: string;
43
+ providerAssetId: string;
44
+ mediaKind: MediaKind | null;
45
+ mimeType: string | null;
46
+ }
@@ -167,7 +167,7 @@ async function getBackend(): Promise<ObjectCacheBackend | null> {
167
167
  } catch (error) {
168
168
  // Importing the virtual module fails outside an Astro/Vite context
169
169
  // (e.g. unit tests, CLI). Treat as "no cache configured".
170
- if (import.meta.env.DEV) {
170
+ if (import.meta.env?.DEV) {
171
171
  console.warn("[object-cache] backend unavailable:", error);
172
172
  }
173
173
  holder.backend = null;
@@ -378,7 +378,7 @@ export async function cachedQuery<T>(options: CachedQueryOptions<T>): Promise<T>
378
378
  const encoded = encode({ e: currentEpochs, v: value } satisfies CacheEnvelope<T>);
379
379
  await backend.set(fullKey, encoded, ttl);
380
380
  } catch (error) {
381
- if (import.meta.env.DEV) {
381
+ if (import.meta.env?.DEV) {
382
382
  console.warn("[object-cache] set failed:", error);
383
383
  }
384
384
  }
@@ -388,6 +388,12 @@ export async function cachedQuery<T>(options: CachedQueryOptions<T>): Promise<T>
388
388
  return value;
389
389
  }
390
390
 
391
+ /** Whether object-cache reads are active for the current request. */
392
+ export async function isObjectCacheActive(): Promise<boolean> {
393
+ const backend = await getBackend();
394
+ return backend !== null && !shouldBypass();
395
+ }
396
+
391
397
  /**
392
398
  * Invalidate every cached value in `namespace` by bumping its epoch.
393
399
  *
@@ -20,6 +20,10 @@ export interface ResolvedPageMetadata {
20
20
  jsonld: Array<{ id?: string; json: string }>;
21
21
  }
22
22
 
23
+ interface RenderPageMetadataOptions {
24
+ includeJsonLd?: boolean;
25
+ }
26
+
23
27
  // ── Validation ──────────────────────────────────────────────────
24
28
 
25
29
  /** Schemes safe for use in link href attributes */
@@ -66,6 +70,16 @@ export function safeJsonLdSerialize(value: unknown): string {
66
70
  .replace(JSONLD_U2029_RE, "\\u2029");
67
71
  }
68
72
 
73
+ export async function createSha256CspHash(value: string): Promise<string> {
74
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
75
+ const bytes = new Uint8Array(digest);
76
+ let binary = "";
77
+ for (const byte of bytes) {
78
+ binary += String.fromCharCode(byte);
79
+ }
80
+ return `sha256-${btoa(binary)}`;
81
+ }
82
+
69
83
  // ── Merge / dedupe ──────────────────────────────────────────────
70
84
 
71
85
  /**
@@ -155,8 +169,12 @@ export function resolvePageMetadata(
155
169
  // ── HTML rendering ──────────────────────────────────────────────
156
170
 
157
171
  /** Render resolved metadata to an HTML string for embedding in <head> */
158
- export function renderPageMetadata(metadata: ResolvedPageMetadata): string {
172
+ export function renderPageMetadata(
173
+ metadata: ResolvedPageMetadata,
174
+ options: RenderPageMetadataOptions = {},
175
+ ): string {
159
176
  const parts: string[] = [];
177
+ const includeJsonLd = options.includeJsonLd ?? true;
160
178
 
161
179
  for (const m of metadata.meta) {
162
180
  parts.push(`<meta name="${escapeHtmlAttr(m.name)}" content="${escapeHtmlAttr(m.content)}">`);
@@ -177,8 +195,10 @@ export function renderPageMetadata(metadata: ResolvedPageMetadata): string {
177
195
  parts.push(tag);
178
196
  }
179
197
 
180
- for (const j of metadata.jsonld) {
181
- parts.push(`<script type="application/ld+json">${j.json}</script>`);
198
+ if (includeJsonLd) {
199
+ for (const j of metadata.jsonld) {
200
+ parts.push(`<script type="application/ld+json">${j.json}</script>`);
201
+ }
182
202
  }
183
203
 
184
204
  return parts.join("\n");
@@ -51,12 +51,17 @@ import type {
51
51
  ContentAfterPublishHandler,
52
52
  ContentAfterRestoreHandler,
53
53
  ContentAfterSaveHandler,
54
+ ContentAfterScheduleHandler,
54
55
  ContentAfterUnpublishHandler,
56
+ ContentAfterUnscheduleHandler,
55
57
  ContentBeforeDeleteHandler,
56
58
  ContentBeforeSaveHandler,
57
59
  ContentDeleteEvent,
58
60
  ContentHookEvent,
59
61
  ContentPublishStateChangeEvent,
62
+ ContentRestoreStateChangeEvent,
63
+ ContentScheduleStateChangeEvent,
64
+ ContentStateChangeEvent,
60
65
  CronEvent,
61
66
  CronHandler,
62
67
  EmailAfterSendEvent,
@@ -99,6 +104,8 @@ export interface HookHandlers {
99
104
  "content:afterPublish": ContentAfterPublishHandler;
100
105
  "content:afterUnpublish": ContentAfterUnpublishHandler;
101
106
  "content:afterRestore": ContentAfterRestoreHandler;
107
+ "content:afterSchedule": ContentAfterScheduleHandler;
108
+ "content:afterUnschedule": ContentAfterUnscheduleHandler;
102
109
  "media:beforeUpload": MediaBeforeUploadHandler;
103
110
  "media:afterUpload": MediaAfterUploadHandler;
104
111
  cron: CronHandler;
@@ -228,6 +235,9 @@ export type {
228
235
  ContentDeleteEvent,
229
236
  ContentHookEvent,
230
237
  ContentPublishStateChangeEvent,
238
+ ContentRestoreStateChangeEvent,
239
+ ContentScheduleStateChangeEvent,
240
+ ContentStateChangeEvent,
231
241
  CronEvent,
232
242
  EmailAfterSendEvent,
233
243
  EmailBeforeSendEvent,