emdash 0.17.2 → 0.19.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 (415) hide show
  1. package/dist/api/route-utils.d.mts +2 -2
  2. package/dist/api/route-utils.mjs +14 -14
  3. package/dist/api/schemas/index.d.mts +2 -2
  4. package/dist/api/schemas/index.mjs +3 -3
  5. package/dist/{api-B7GATEYo.mjs → api-BZ6bhjYs.mjs} +88 -16
  6. package/dist/api-BZ6bhjYs.mjs.map +1 -0
  7. package/dist/{apply-BrVqULFe.mjs → apply-hQkKKBCf.mjs} +23 -23
  8. package/dist/apply-hQkKKBCf.mjs.map +1 -0
  9. package/dist/astro/index.d.mts +8 -8
  10. package/dist/astro/index.d.mts.map +1 -1
  11. package/dist/astro/index.mjs +113 -23
  12. package/dist/astro/index.mjs.map +1 -1
  13. package/dist/astro/middleware/auth.d.mts +7 -7
  14. package/dist/astro/middleware/auth.mjs +2 -2
  15. package/dist/astro/middleware/redirect.mjs +4 -4
  16. package/dist/astro/middleware/request-context.mjs +2 -2
  17. package/dist/astro/middleware.d.mts +26 -4
  18. package/dist/astro/middleware.d.mts.map +1 -1
  19. package/dist/astro/middleware.mjs +414 -215
  20. package/dist/astro/middleware.mjs.map +1 -1
  21. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +5 -5
  22. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +5 -5
  23. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +2 -2
  24. package/dist/astro/routes/api/admin/api-tokens/index.mjs +3 -3
  25. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +5 -5
  26. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +8 -8
  27. package/dist/astro/routes/api/admin/byline-fields/index.mjs +8 -8
  28. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +8 -8
  29. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +12 -12
  30. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +12 -12
  31. package/dist/astro/routes/api/admin/bylines/index.mjs +12 -12
  32. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +11 -11
  33. package/dist/astro/routes/api/admin/comments/_id_.mjs +5 -5
  34. package/dist/astro/routes/api/admin/comments/bulk.mjs +8 -8
  35. package/dist/astro/routes/api/admin/comments/counts.mjs +5 -5
  36. package/dist/astro/routes/api/admin/comments/index.mjs +8 -8
  37. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +5 -5
  38. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +4 -4
  39. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +3 -3
  40. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +3 -3
  41. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +31 -31
  42. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +31 -31
  43. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +30 -30
  44. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +30 -30
  45. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +30 -30
  46. package/dist/astro/routes/api/admin/plugins/index.mjs +30 -30
  47. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
  48. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +30 -30
  49. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +30 -30
  50. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +30 -30
  51. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +30 -30
  52. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +31 -31
  53. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +30 -30
  54. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +31 -31
  55. package/dist/astro/routes/api/admin/plugins/updates.mjs +30 -30
  56. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +30 -30
  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 +30 -30
  59. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +3 -3
  60. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
  61. package/dist/astro/routes/api/admin/users/_id_/index.mjs +6 -6
  62. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +4 -4
  63. package/dist/astro/routes/api/admin/users/index.mjs +5 -5
  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 +6 -6
  67. package/dist/astro/routes/api/auth/invite/index.mjs +7 -7
  68. package/dist/astro/routes/api/auth/invite/register-options.mjs +6 -6
  69. package/dist/astro/routes/api/auth/logout.mjs +2 -2
  70. package/dist/astro/routes/api/auth/magic-link/send.mjs +8 -8
  71. package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
  72. package/dist/astro/routes/api/auth/me.mjs +6 -6
  73. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +2 -2
  74. package/dist/astro/routes/api/auth/passkey/_id_.mjs +5 -5
  75. package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
  76. package/dist/astro/routes/api/auth/passkey/options.mjs +7 -7
  77. package/dist/astro/routes/api/auth/passkey/register/options.mjs +6 -6
  78. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +6 -6
  79. package/dist/astro/routes/api/auth/passkey/verify.mjs +6 -6
  80. package/dist/astro/routes/api/auth/signup/complete.mjs +6 -6
  81. package/dist/astro/routes/api/auth/signup/request.mjs +8 -8
  82. package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
  83. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +11 -11
  84. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
  85. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +6 -5
  86. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs.map +1 -1
  87. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
  88. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
  89. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +8 -8
  90. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +9 -8
  91. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
  92. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
  93. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
  94. package/dist/astro/routes/api/content/_collection_/_id_/schedule.d.mts.map +1 -1
  95. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +12 -10
  96. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs.map +1 -1
  97. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +11 -11
  98. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
  99. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +6 -5
  100. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs.map +1 -1
  101. package/dist/astro/routes/api/content/_collection_/_id_.mjs +9 -8
  102. package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
  103. package/dist/astro/routes/api/content/_collection_/authors.d.mts +8 -0
  104. package/dist/astro/routes/api/content/_collection_/authors.d.mts.map +1 -0
  105. package/dist/astro/routes/api/content/_collection_/authors.mjs +19 -0
  106. package/dist/astro/routes/api/content/_collection_/authors.mjs.map +1 -0
  107. package/dist/astro/routes/api/content/_collection_/index.mjs +6 -6
  108. package/dist/astro/routes/api/content/_collection_/trash.mjs +6 -6
  109. package/dist/astro/routes/api/dashboard.mjs +7 -7
  110. package/dist/astro/routes/api/dev/emails.mjs +2 -2
  111. package/dist/astro/routes/api/import/probe.d.mts +2 -2
  112. package/dist/astro/routes/api/import/probe.mjs +6 -6
  113. package/dist/astro/routes/api/import/wordpress/analyze.mjs +4 -4
  114. package/dist/astro/routes/api/import/wordpress/execute.d.mts +7 -7
  115. package/dist/astro/routes/api/import/wordpress/execute.mjs +9 -9
  116. package/dist/astro/routes/api/import/wordpress/media.mjs +6 -6
  117. package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -9
  118. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +8 -8
  119. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +6 -6
  120. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +9 -9
  121. package/dist/astro/routes/api/manifest.mjs +3 -3
  122. package/dist/astro/routes/api/mcp.mjs +28 -28
  123. package/dist/astro/routes/api/media/_id_/confirm.mjs +6 -6
  124. package/dist/astro/routes/api/media/_id_.mjs +6 -6
  125. package/dist/astro/routes/api/media/file/_...key_.mjs +2 -2
  126. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +3 -3
  127. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +3 -3
  128. package/dist/astro/routes/api/media/providers/index.mjs +3 -3
  129. package/dist/astro/routes/api/media/upload-url.mjs +6 -6
  130. package/dist/astro/routes/api/media.mjs +7 -7
  131. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +7 -7
  132. package/dist/astro/routes/api/menus/_name_/items.mjs +7 -7
  133. package/dist/astro/routes/api/menus/_name_/reorder.mjs +7 -7
  134. package/dist/astro/routes/api/menus/_name_/translations.mjs +7 -7
  135. package/dist/astro/routes/api/menus/_name_.mjs +7 -7
  136. package/dist/astro/routes/api/menus/index.mjs +7 -7
  137. package/dist/astro/routes/api/oauth/authorize.mjs +1 -1
  138. package/dist/astro/routes/api/oauth/device/authorize.mjs +4 -4
  139. package/dist/astro/routes/api/oauth/device/code.mjs +5 -5
  140. package/dist/astro/routes/api/oauth/device/token.mjs +5 -5
  141. package/dist/astro/routes/api/oauth/register.mjs +2 -2
  142. package/dist/astro/routes/api/oauth/token/refresh.mjs +4 -4
  143. package/dist/astro/routes/api/oauth/token/revoke.mjs +4 -4
  144. package/dist/astro/routes/api/oauth/token.mjs +4 -4
  145. package/dist/astro/routes/api/openapi.json.mjs +17 -3
  146. package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
  147. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +3 -3
  148. package/dist/astro/routes/api/redirects/404s/index.mjs +9 -9
  149. package/dist/astro/routes/api/redirects/404s/summary.mjs +9 -9
  150. package/dist/astro/routes/api/redirects/_id_.mjs +10 -10
  151. package/dist/astro/routes/api/redirects/index.mjs +10 -10
  152. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
  153. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
  154. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +30 -30
  155. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +30 -30
  156. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +30 -30
  157. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +30 -30
  158. package/dist/astro/routes/api/schema/collections/index.mjs +30 -30
  159. package/dist/astro/routes/api/schema/index.mjs +6 -6
  160. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +30 -30
  161. package/dist/astro/routes/api/schema/orphans/index.mjs +30 -30
  162. package/dist/astro/routes/api/search/enable.mjs +9 -9
  163. package/dist/astro/routes/api/search/index.mjs +8 -8
  164. package/dist/astro/routes/api/search/rebuild.mjs +9 -9
  165. package/dist/astro/routes/api/search/stats.mjs +6 -6
  166. package/dist/astro/routes/api/search/suggest.mjs +8 -8
  167. package/dist/astro/routes/api/sections/_slug_.mjs +8 -8
  168. package/dist/astro/routes/api/sections/index.mjs +8 -8
  169. package/dist/astro/routes/api/settings/email.mjs +5 -5
  170. package/dist/astro/routes/api/settings.mjs +12 -12
  171. package/dist/astro/routes/api/setup/admin-verify.mjs +6 -6
  172. package/dist/astro/routes/api/setup/admin.mjs +6 -6
  173. package/dist/astro/routes/api/setup/dev-bypass.mjs +18 -18
  174. package/dist/astro/routes/api/setup/dev-reset.mjs +3 -3
  175. package/dist/astro/routes/api/setup/index.mjs +21 -21
  176. package/dist/astro/routes/api/setup/status.mjs +3 -3
  177. package/dist/astro/routes/api/snapshot.mjs +5 -5
  178. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +11 -11
  179. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +11 -11
  180. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +11 -11
  181. package/dist/astro/routes/api/taxonomies/index.mjs +11 -11
  182. package/dist/astro/routes/api/themes/preview.mjs +5 -5
  183. package/dist/astro/routes/api/typegen.mjs +5 -5
  184. package/dist/astro/routes/api/well-known/auth.mjs +1 -1
  185. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +6 -6
  186. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +8 -8
  187. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +8 -8
  188. package/dist/astro/routes/api/widget-areas/_name_.mjs +5 -5
  189. package/dist/astro/routes/api/widget-areas/index.mjs +8 -8
  190. package/dist/astro/routes/api/widget-components.mjs +2 -2
  191. package/dist/astro/routes/robots.txt.mjs +6 -6
  192. package/dist/astro/routes/sitemap-_collection_.xml.mjs +6 -6
  193. package/dist/astro/routes/sitemap.xml.mjs +6 -6
  194. package/dist/astro/types.d.mts +15 -8
  195. package/dist/astro/types.d.mts.map +1 -1
  196. package/dist/{authorize-CLTmOUyx.mjs → authorize-C_8t2KGa.mjs} +2 -2
  197. package/dist/{authorize-CLTmOUyx.mjs.map → authorize-C_8t2KGa.mjs.map} +1 -1
  198. package/dist/{byline-CAhk4FrG.mjs → byline-DUx48sJp.mjs} +6 -6
  199. package/dist/{byline-CAhk4FrG.mjs.map → byline-DUx48sJp.mjs.map} +1 -1
  200. package/dist/{byline-fields-Dr-xcb6S.mjs → byline-fields-51kg6Vuv.mjs} +3 -3
  201. package/dist/{byline-fields-Dr-xcb6S.mjs.map → byline-fields-51kg6Vuv.mjs.map} +1 -1
  202. package/dist/{byline-fields-DC3Wkk-U.mjs → byline-fields-C_OsR-KF.mjs} +2 -2
  203. package/dist/{byline-fields-DC3Wkk-U.mjs.map → byline-fields-C_OsR-KF.mjs.map} +1 -1
  204. package/dist/{byline-fields-CR5hGLMw.d.mts → byline-fields-DYXKDuNX.d.mts} +53 -29
  205. package/dist/byline-fields-DYXKDuNX.d.mts.map +1 -0
  206. package/dist/{byline-registry-CxK5g559.mjs → byline-registry-CWP7I71B.mjs} +3 -3
  207. package/dist/{byline-registry-CxK5g559.mjs.map → byline-registry-CWP7I71B.mjs.map} +1 -1
  208. package/dist/{bylines-CbrD7STW.mjs → bylines-Cx5n-WqP.mjs} +3 -3
  209. package/dist/{bylines-CbrD7STW.mjs.map → bylines-Cx5n-WqP.mjs.map} +1 -1
  210. package/dist/{bylines-DCczH3AV.mjs → bylines-wurS258E.mjs} +50 -6
  211. package/dist/{bylines-DCczH3AV.mjs.map → bylines-wurS258E.mjs.map} +1 -1
  212. package/dist/{cache-DIHHyPkt.mjs → cache-B_HzASVT.mjs} +3 -3
  213. package/dist/{cache-DIHHyPkt.mjs.map → cache-B_HzASVT.mjs.map} +1 -1
  214. package/dist/{chunks-DnnHlRG3.mjs → chunks-BerYVuve.mjs} +2 -2
  215. package/dist/{chunks-DnnHlRG3.mjs.map → chunks-BerYVuve.mjs.map} +1 -1
  216. package/dist/cli/index.mjs +40 -27
  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-DkAfGX9E.mjs → comment-sqQxNpN3.mjs} +2 -2
  221. package/dist/{comment-DkAfGX9E.mjs.map → comment-sqQxNpN3.mjs.map} +1 -1
  222. package/dist/{comments-DLFnXs7J.mjs → comments-CJ0RZsYR.mjs} +3 -3
  223. package/dist/{comments-DLFnXs7J.mjs.map → comments-CJ0RZsYR.mjs.map} +1 -1
  224. package/dist/{content-C7aJ7keg.mjs → content-BIlVx-RX.mjs} +132 -43
  225. package/dist/content-BIlVx-RX.mjs.map +1 -0
  226. package/dist/{context-Ca0HkaIh.mjs → context-GG52SPgh.mjs} +10 -10
  227. package/dist/{context-Ca0HkaIh.mjs.map → context-GG52SPgh.mjs.map} +1 -1
  228. package/dist/{cron-DZovZUnC.mjs → cron-BJ2ClIlj.mjs} +4 -3
  229. package/dist/cron-BJ2ClIlj.mjs.map +1 -0
  230. package/dist/{dashboard-BrfLIsX1.mjs → dashboard-2JgAMWxK.mjs} +4 -4
  231. package/dist/{dashboard-BrfLIsX1.mjs.map → dashboard-2JgAMWxK.mjs.map} +1 -1
  232. package/dist/db/index.d.mts +2 -2
  233. package/dist/db/index.mjs +1 -1
  234. package/dist/{device-flow-ptLrVINd.mjs → device-flow-s6_q3T7A.mjs} +2 -2
  235. package/dist/{device-flow-ptLrVINd.mjs.map → device-flow-s6_q3T7A.mjs.map} +1 -1
  236. package/dist/{error-Bk9s3Ism.mjs → error-RwM4dD35.mjs} +2 -2
  237. package/dist/{error-Bk9s3Ism.mjs.map → error-RwM4dD35.mjs.map} +1 -1
  238. package/dist/{fts-manager-XpDfbIKo.mjs → fts-manager-1RgHmopc.mjs} +2 -2
  239. package/dist/{fts-manager-XpDfbIKo.mjs.map → fts-manager-1RgHmopc.mjs.map} +1 -1
  240. package/dist/{index-D60_SzHG.d.mts → index-BpYeJO1E.d.mts} +2 -2
  241. package/dist/{index-D60_SzHG.d.mts.map → index-BpYeJO1E.d.mts.map} +1 -1
  242. package/dist/{index-C8ciqSMJ.d.mts → index-FfiTQJq2.d.mts} +202 -20
  243. package/dist/index-FfiTQJq2.d.mts.map +1 -0
  244. package/dist/index.d.mts +9 -9
  245. package/dist/index.mjs +43 -43
  246. package/dist/{load-CF5oETkh.mjs → load-B84ohfBk.mjs} +2 -2
  247. package/dist/{load-CF5oETkh.mjs.map → load-B84ohfBk.mjs.map} +1 -1
  248. package/dist/{loader-BxyvbrZP.mjs → loader-CpZKpFz0.mjs} +32 -30
  249. package/dist/loader-CpZKpFz0.mjs.map +1 -0
  250. package/dist/media/index.mjs +1 -1
  251. package/dist/media/local-runtime.d.mts +7 -7
  252. package/dist/media/local-runtime.mjs +6 -6
  253. package/dist/{media-Cyz5BhSN.mjs → media-JOf3pNkw.mjs} +2 -2
  254. package/dist/{media-Cyz5BhSN.mjs.map → media-JOf3pNkw.mjs.map} +1 -1
  255. package/dist/{menus-PFp8FDuO.mjs → menus-DX4_E01q.mjs} +3 -3
  256. package/dist/{menus-PFp8FDuO.mjs.map → menus-DX4_E01q.mjs.map} +1 -1
  257. package/dist/{menus-CIdZ_Q6U.mjs → menus-Dp9xporj.mjs} +112 -16
  258. package/dist/menus-Dp9xporj.mjs.map +1 -0
  259. package/dist/{normalize-DVV8nbrL.mjs → normalize-CK5o04zr.mjs} +2 -2
  260. package/dist/{normalize-DVV8nbrL.mjs.map → normalize-CK5o04zr.mjs.map} +1 -1
  261. package/dist/{oauth-authorization-DvBAL75d.mjs → oauth-authorization-1aPAYjiC.mjs} +2 -2
  262. package/dist/{oauth-authorization-DvBAL75d.mjs.map → oauth-authorization-1aPAYjiC.mjs.map} +1 -1
  263. package/dist/{options-BL4X94qY.mjs → options-BPCVnesz.mjs} +1 -1
  264. package/dist/{options-BL4X94qY.mjs.map → options-BPCVnesz.mjs.map} +1 -1
  265. package/dist/{options-tb7DJROi.d.mts → options-D4MnavW_.d.mts} +3 -3
  266. package/dist/{options-tb7DJROi.d.mts.map → options-D4MnavW_.d.mts.map} +1 -1
  267. package/dist/{parse-B-K21lvm.mjs → parse-CrGndy1A.mjs} +2 -2
  268. package/dist/{parse-B-K21lvm.mjs.map → parse-CrGndy1A.mjs.map} +1 -1
  269. package/dist/{patterns-CqG5Ya3i.mjs → patterns-p-RBdTbM.mjs} +1 -1
  270. package/dist/{patterns-CqG5Ya3i.mjs.map → patterns-p-RBdTbM.mjs.map} +1 -1
  271. package/dist/plugin-utils.d.mts +7 -7
  272. package/dist/plugins/adapt-sandbox-entry.d.mts +7 -7
  273. package/dist/{query-Cc649nDl.mjs → query-BFQ029Ts.mjs} +21 -15
  274. package/dist/query-BFQ029Ts.mjs.map +1 -0
  275. package/dist/{rate-limit-BI1OdpQH.mjs → rate-limit-ClFFUga6.mjs} +2 -2
  276. package/dist/{rate-limit-BI1OdpQH.mjs.map → rate-limit-ClFFUga6.mjs.map} +1 -1
  277. package/dist/{redirect-C-FeA4j9.mjs → redirect-CRWIt8Zj.mjs} +3 -3
  278. package/dist/{redirect-C-FeA4j9.mjs.map → redirect-CRWIt8Zj.mjs.map} +1 -1
  279. package/dist/{redirects-C0L9JUk4.mjs → redirects-DEygMrRO.mjs} +25 -3
  280. package/dist/redirects-DEygMrRO.mjs.map +1 -0
  281. package/dist/{redirects-C1UgU9E0.mjs → redirects-OIu6vQ2i.mjs} +5 -5
  282. package/dist/{redirects-C1UgU9E0.mjs.map → redirects-OIu6vQ2i.mjs.map} +1 -1
  283. package/dist/{registry-C-T_PWgp.mjs → registry-brYh-rAT.mjs} +6 -6
  284. package/dist/{registry-C-T_PWgp.mjs.map → registry-brYh-rAT.mjs.map} +1 -1
  285. package/dist/{request-cache-BYMs-BGX.mjs → request-cache-D32LpnmI.mjs} +1 -1
  286. package/dist/{request-cache-BYMs-BGX.mjs.map → request-cache-D32LpnmI.mjs.map} +1 -1
  287. package/dist/{runner-BiuUfx-V.mjs → runner--4wMWwKM.mjs} +224 -168
  288. package/dist/runner--4wMWwKM.mjs.map +1 -0
  289. package/dist/{runner-DM1yR5qd.d.mts → runner-BcRuXq_h.d.mts} +2 -2
  290. package/dist/{runner-DM1yR5qd.d.mts.map → runner-BcRuXq_h.d.mts.map} +1 -1
  291. package/dist/runtime.d.mts +7 -7
  292. package/dist/runtime.mjs +2 -2
  293. package/dist/{schema-BpCJh2lU.mjs → schema-CS7Eg5gh.mjs} +5 -5
  294. package/dist/{schema-BpCJh2lU.mjs.map → schema-CS7Eg5gh.mjs.map} +1 -1
  295. package/dist/{search-BrF7k0Ho.mjs → search-o-aQzHI1.mjs} +4 -4
  296. package/dist/{search-BrF7k0Ho.mjs.map → search-o-aQzHI1.mjs.map} +1 -1
  297. package/dist/{secrets-YYbTgB1w.mjs → secrets-C_ZtRos3.mjs} +2 -2
  298. package/dist/{secrets-YYbTgB1w.mjs.map → secrets-C_ZtRos3.mjs.map} +1 -1
  299. package/dist/{sections-8DEa-dWt.mjs → sections-DhsZ0ns9.mjs} +3 -3
  300. package/dist/{sections-8DEa-dWt.mjs.map → sections-DhsZ0ns9.mjs.map} +1 -1
  301. package/dist/seed/index.d.mts +2 -2
  302. package/dist/seed/index.mjs +16 -16
  303. package/dist/seo/index.d.mts +1 -1
  304. package/dist/{seo-CKr7pLfA.mjs → seo-B5e6y9Wk.mjs} +2 -2
  305. package/dist/{seo-CKr7pLfA.mjs.map → seo-B5e6y9Wk.mjs.map} +1 -1
  306. package/dist/{service-9P2cdyR_.mjs → service-DAxg8RPR.mjs} +2 -2
  307. package/dist/{service-9P2cdyR_.mjs.map → service-DAxg8RPR.mjs.map} +1 -1
  308. package/dist/{settings-Jro4YcUb.mjs → settings-B1p-gPUK.mjs} +5 -5
  309. package/dist/{settings-Jro4YcUb.mjs.map → settings-B1p-gPUK.mjs.map} +1 -1
  310. package/dist/{settings-DYVzINdn.mjs → settings-DIsbHTRE.mjs} +3 -3
  311. package/dist/{settings-DYVzINdn.mjs.map → settings-DIsbHTRE.mjs.map} +1 -1
  312. package/dist/{setup-complete-VoEZfasi.mjs → setup-complete-Yuv78yua.mjs} +2 -2
  313. package/dist/{setup-complete-VoEZfasi.mjs.map → setup-complete-Yuv78yua.mjs.map} +1 -1
  314. package/dist/{site-url-Cm8-sJy7.mjs → site-url-mEVmwIFi.mjs} +2 -2
  315. package/dist/{site-url-Cm8-sJy7.mjs.map → site-url-mEVmwIFi.mjs.map} +1 -1
  316. package/dist/{taxonomies-CGD6y79Q.mjs → taxonomies-BEW7S5AI.mjs} +10 -8
  317. package/dist/taxonomies-BEW7S5AI.mjs.map +1 -0
  318. package/dist/{taxonomies-C0bVme_m.mjs → taxonomies-UusDXv3C.mjs} +4 -4
  319. package/dist/{taxonomies-C0bVme_m.mjs.map → taxonomies-UusDXv3C.mjs.map} +1 -1
  320. package/dist/{taxonomy-Db5xwphL.mjs → taxonomy-CdllE4oq.mjs} +3 -3
  321. package/dist/{taxonomy-Db5xwphL.mjs.map → taxonomy-CdllE4oq.mjs.map} +1 -1
  322. package/dist/{transaction-NQj4VJ7Z.mjs → transaction-x2tJQ-A1.mjs} +1 -1
  323. package/dist/{transaction-NQj4VJ7Z.mjs.map → transaction-x2tJQ-A1.mjs.map} +1 -1
  324. package/dist/{transport-OnMNbsIA.d.mts → transport-BwQeeY2p.d.mts} +1 -1
  325. package/dist/{transport-OnMNbsIA.d.mts.map → transport-BwQeeY2p.d.mts.map} +1 -1
  326. package/dist/{types-CfyYQ7eY.mjs → types-BXSUSAjt.mjs} +16 -3
  327. package/dist/{types-CfyYQ7eY.mjs.map → types-BXSUSAjt.mjs.map} +1 -1
  328. package/dist/{types-D8bhH891.mjs → types-DZk_y-MU.mjs} +1 -1
  329. package/dist/{types-D8bhH891.mjs.map → types-DZk_y-MU.mjs.map} +1 -1
  330. package/dist/{types-DawhLFwy.d.mts → types-OT_Es5mp.d.mts} +26 -1
  331. package/dist/{types-DawhLFwy.d.mts.map → types-OT_Es5mp.d.mts.map} +1 -1
  332. package/dist/{types-i8_uzhMD.d.mts → types-WVmpZBJV.d.mts} +18 -3
  333. package/dist/types-WVmpZBJV.d.mts.map +1 -0
  334. package/dist/{user-tLdHUEXV.mjs → user-C0um7wrg.mjs} +18 -2
  335. package/dist/user-C0um7wrg.mjs.map +1 -0
  336. package/dist/{validate-Dy6nkNls.d.mts → validate-BPAHUSge.d.mts} +10 -2
  337. package/dist/validate-BPAHUSge.d.mts.map +1 -0
  338. package/dist/{validate-DWmnRg6E.mjs → validate-ZP9Dvg0P.mjs} +6 -3
  339. package/dist/validate-ZP9Dvg0P.mjs.map +1 -0
  340. package/dist/{validation-BQ_TP-On.mjs → validation-CE5i4q0c.mjs} +5 -5
  341. package/dist/{validation-BQ_TP-On.mjs.map → validation-CE5i4q0c.mjs.map} +1 -1
  342. package/dist/version-Dw0JXu45.mjs +7 -0
  343. package/dist/{version-CgcnMvqS.mjs.map → version-Dw0JXu45.mjs.map} +1 -1
  344. package/dist/{widgets-DzlINGI6.mjs → widgets-ClEnYQCH.mjs} +2 -2
  345. package/dist/{widgets-DzlINGI6.mjs.map → widgets-ClEnYQCH.mjs.map} +1 -1
  346. package/dist/{zod-generator-MMm56Prt.mjs → zod-generator-Djo_VHCt.mjs} +4 -3
  347. package/dist/zod-generator-Djo_VHCt.mjs.map +1 -0
  348. package/package.json +7 -7
  349. package/src/api/handlers/content.ts +107 -8
  350. package/src/api/handlers/index.ts +2 -0
  351. package/src/api/openapi/document.ts +25 -0
  352. package/src/api/schemas/content.ts +33 -0
  353. package/src/astro/integration/index.ts +98 -0
  354. package/src/astro/integration/routes.ts +6 -0
  355. package/src/astro/integration/virtual-modules.ts +39 -0
  356. package/src/astro/integration/vite-config.ts +12 -0
  357. package/src/astro/middleware/stream-end-metrics.ts +96 -0
  358. package/src/astro/middleware.ts +107 -31
  359. package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +4 -2
  360. package/src/astro/routes/api/content/[collection]/[id]/publish.ts +4 -2
  361. package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +8 -4
  362. package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +4 -2
  363. package/src/astro/routes/api/content/[collection]/[id].ts +4 -2
  364. package/src/astro/routes/api/content/[collection]/authors.ts +34 -0
  365. package/src/astro/types.ts +8 -1
  366. package/src/bylines/index.ts +57 -0
  367. package/src/cli/commands/export-seed.ts +28 -12
  368. package/src/components/EmDashImage.astro +23 -4
  369. package/src/components/Image.astro +20 -3
  370. package/src/database/migrations/043_content_references.ts +121 -0
  371. package/src/database/migrations/runner.ts +9 -2
  372. package/src/database/repositories/content.ts +225 -67
  373. package/src/database/repositories/index.ts +7 -0
  374. package/src/database/repositories/relation.ts +467 -0
  375. package/src/database/repositories/types.ts +31 -0
  376. package/src/database/repositories/user.ts +18 -0
  377. package/src/database/types.ts +34 -0
  378. package/src/emdash-runtime.ts +318 -168
  379. package/src/index.ts +8 -1
  380. package/src/loader.ts +67 -34
  381. package/src/media/responsive.ts +125 -0
  382. package/src/menus/index.ts +27 -9
  383. package/src/plugins/cron.ts +3 -2
  384. package/src/plugins/hooks.ts +35 -6
  385. package/src/plugins/index.ts +5 -0
  386. package/src/plugins/manager.ts +1 -0
  387. package/src/plugins/scheduler/node.ts +9 -2
  388. package/src/query.ts +32 -5
  389. package/src/scheduled-publish.ts +153 -0
  390. package/src/schema/zod-generator.ts +6 -2
  391. package/src/seed/apply.ts +16 -6
  392. package/src/seed/types.ts +9 -0
  393. package/src/seed/validate.ts +15 -0
  394. package/src/taxonomies/index.ts +13 -8
  395. package/src/utils/init-lock.ts +143 -0
  396. package/src/virtual-modules.d.ts +11 -0
  397. package/dist/api-B7GATEYo.mjs.map +0 -1
  398. package/dist/apply-BrVqULFe.mjs.map +0 -1
  399. package/dist/byline-fields-CR5hGLMw.d.mts.map +0 -1
  400. package/dist/content-C7aJ7keg.mjs.map +0 -1
  401. package/dist/cron-DZovZUnC.mjs.map +0 -1
  402. package/dist/index-C8ciqSMJ.d.mts.map +0 -1
  403. package/dist/loader-BxyvbrZP.mjs.map +0 -1
  404. package/dist/menus-CIdZ_Q6U.mjs.map +0 -1
  405. package/dist/query-Cc649nDl.mjs.map +0 -1
  406. package/dist/redirects-C0L9JUk4.mjs.map +0 -1
  407. package/dist/runner-BiuUfx-V.mjs.map +0 -1
  408. package/dist/taxonomies-CGD6y79Q.mjs.map +0 -1
  409. package/dist/types-i8_uzhMD.d.mts.map +0 -1
  410. package/dist/user-tLdHUEXV.mjs.map +0 -1
  411. package/dist/validate-DWmnRg6E.mjs.map +0 -1
  412. package/dist/validate-Dy6nkNls.d.mts.map +0 -1
  413. package/dist/version-CgcnMvqS.mjs +0 -7
  414. package/dist/zod-generator-MMm56Prt.mjs.map +0 -1
  415. package/src/plugins/scheduler/piggyback.ts +0 -71
@@ -25,18 +25,23 @@ import * as virtualSandboxRunnerModule from "virtual:emdash/sandbox-runner";
25
25
  // @ts-ignore - virtual module
26
26
  import { sandboxedPlugins as virtualSandboxedPlugins } from "virtual:emdash/sandboxed-plugins";
27
27
  // @ts-ignore - virtual module
28
+ import { createScheduler as virtualCreateScheduler } from "virtual:emdash/scheduler";
29
+ // @ts-ignore - virtual module
28
30
  import { createStorage as virtualCreateStorage } from "virtual:emdash/storage";
29
31
 
32
+ import { after } from "../after.js";
30
33
  import {
31
34
  createRecorder,
32
35
  flushRecorder,
33
36
  isInstrumentationEnabled,
34
37
  } from "../database/instrumentation.js";
35
38
  import {
39
+ DB_INIT_DEADLINE_MS,
36
40
  EmDashRuntime,
37
41
  type RuntimeDependencies,
38
42
  type SandboxedPluginEntry,
39
43
  type MediaProviderEntry,
44
+ type CreateSchedulerFn,
40
45
  } from "../emdash-runtime.js";
41
46
  import { setI18nConfig } from "../i18n/config.js";
42
47
  import type { Database, Storage } from "../index.js";
@@ -50,18 +55,22 @@ import {
50
55
  type RequestMetrics,
51
56
  runWithContext,
52
57
  } from "../request-context.js";
58
+ import type { PublishedRef } from "../scheduled-publish.js";
53
59
  import { isMissingTableError } from "../utils/db-errors.js";
60
+ import { createInitLock, type InitLock, initWithLock } from "../utils/init-lock.js";
54
61
  import type { EmDashConfig } from "./integration/runtime.js";
62
+ import { wrapBodyForStreamMetrics } from "./middleware/stream-end-metrics.js";
55
63
  import { createPublicPluginApiRouteHandler } from "./public-plugin-api-routes.js";
56
64
  import type { EmDashHandlers } from "./types.js";
57
65
 
58
- // Cached runtime instance (persists across requests within worker)
59
- let runtimeInstance: EmDashRuntime | null = null;
60
- // Whether initialization is in progress (prevents concurrent init attempts)
61
- let runtimeInitializing = false;
62
-
63
- /** Whether i18n config has been initialized from the virtual module */
64
- let i18nInitialized = false;
66
+ /**
67
+ * Runtime init lock reclaim deadline. Must be strictly larger than the db
68
+ * init deadline: this lock wraps EmDashRuntime.create() getDatabase()
69
+ * the db init lock, and equal deadlines would let this outer lock reclaim
70
+ * (spawning a second cron scheduler and sandbox runner) while the inner db
71
+ * init is legitimately still working through a contended migration.
72
+ */
73
+ const RUNTIME_INIT_DEADLINE_MS = DB_INIT_DEADLINE_MS + 15_000;
65
74
 
66
75
  /**
67
76
  * Whether we've verified the database has been set up.
@@ -89,6 +98,32 @@ function markSetupVerified(): void {
89
98
  setupFlagStore[SETUP_VERIFIED_KEY] = true;
90
99
  }
91
100
 
101
+ /**
102
+ * The runtime singleton and its init lock live on globalThis behind a
103
+ * Symbol — same reasoning as SETUP_VERIFIED_KEY above: the bundler can
104
+ * duplicate this module across SSR chunks, and a duplicated instance/lock
105
+ * would mean multiple runtimes (each with its own cron scheduler) per
106
+ * isolate, initializing and reclaiming independently.
107
+ */
108
+ const RUNTIME_HOLDER_KEY = Symbol.for("emdash:runtime-holder");
109
+ interface RuntimeHolder {
110
+ instance: EmDashRuntime | null;
111
+ lock: InitLock;
112
+ }
113
+
114
+ function getRuntimeHolder(): RuntimeHolder {
115
+ // eslint-disable-next-line typescript/no-unsafe-type-assertion -- globalThis symbol slot, written only below
116
+ let holder = setupFlagStore[RUNTIME_HOLDER_KEY] as RuntimeHolder | undefined;
117
+ if (!holder) {
118
+ holder = { instance: null, lock: createInitLock() };
119
+ setupFlagStore[RUNTIME_HOLDER_KEY] = holder;
120
+ }
121
+ return holder;
122
+ }
123
+
124
+ /** Whether i18n config has been initialized from the virtual module */
125
+ let i18nInitialized = false;
126
+
92
127
  /**
93
128
  * Get EmDash configuration from virtual module
94
129
  */
@@ -143,6 +178,7 @@ function buildDependencies(config: EmDashConfig): RuntimeDependencies {
143
178
  plugins: getPlugins(),
144
179
  createDialect: virtualCreateDialect as (config: Record<string, unknown>) => unknown,
145
180
  createStorage: virtualCreateStorage as ((config: Record<string, unknown>) => Storage) | null,
181
+ createScheduler: virtualCreateScheduler as CreateSchedulerFn | null,
146
182
  sandboxEnabled: sandboxModule.sandboxEnabled as boolean,
147
183
  sandboxBypassed: (sandboxModule.sandboxBypassed as boolean) ?? false,
148
184
  sandboxedPluginEntries: (virtualSandboxedPlugins as SandboxedPluginEntry[]) || [],
@@ -176,29 +212,63 @@ async function getRuntime(
176
212
  config: EmDashConfig,
177
213
  initTimings?: Array<{ name: string; dur: number; desc?: string }>,
178
214
  ): Promise<EmDashRuntime> {
179
- // Return cached instance if available
180
- if (runtimeInstance) {
181
- return runtimeInstance;
182
- }
183
-
184
- // If another request is already initializing, wait and retry.
185
- // We don't share the promise across requests because workerd flags
186
- // cross-request promise resolution (causes warnings + potential hangs).
187
- if (runtimeInitializing) {
188
- // Poll until the initializing request finishes
189
- await new Promise((resolve) => setTimeout(resolve, 50));
190
- return getRuntime(config, initTimings);
191
- }
215
+ // Waiters poll rather than awaiting the initializing request's promise —
216
+ // workerd flags cross-request promise resolution (warnings + potential
217
+ // hangs). If the initializing request is cancelled mid-create (client
218
+ // disconnect tears down its continuation, skipping any `finally`), the
219
+ // anchored init keeps running under waitUntil and populates the cache;
220
+ // failing that, the stale lock is reclaimed after a deadline instead of
221
+ // hanging every subsequent request in the isolate until eviction.
222
+ const holder = getRuntimeHolder();
223
+ return initWithLock(
224
+ holder.lock,
225
+ () => holder.instance,
226
+ async (isCurrentClaim) => {
227
+ const deps = buildDependencies(config);
228
+ const runtime = await EmDashRuntime.create(deps, initTimings);
229
+ if (isCurrentClaim()) {
230
+ holder.instance = runtime;
231
+ } else {
232
+ // This init was reclaimed mid-flight (it ran past the deadline
233
+ // and a waiter started its own). Don't overwrite the
234
+ // reclaimer's published runtime, and stop this one's cron
235
+ // scheduler so it doesn't keep firing unreferenced. The
236
+ // runtime is still returned — it's fully functional for the
237
+ // request that built it.
238
+ runtime.stopCron().catch((error: unknown) => {
239
+ console.error("[emdash] failed to stop superseded runtime's cron:", error);
240
+ });
241
+ }
242
+ return runtime;
243
+ },
244
+ {
245
+ deadlineMs: RUNTIME_INIT_DEADLINE_MS,
246
+ anchor: (promise) => after(() => promise),
247
+ },
248
+ );
249
+ }
192
250
 
193
- runtimeInitializing = true;
194
- try {
195
- const deps = buildDependencies(config);
196
- const runtime = await EmDashRuntime.create(deps, initTimings);
197
- runtimeInstance = runtime;
198
- return runtime;
199
- } finally {
200
- runtimeInitializing = false;
201
- }
251
+ /**
252
+ * Run scheduled maintenance (cron tasks, scheduled publishing, system cleanup)
253
+ * outside any request. Resolves the runtime from the build-time virtual config
254
+ * and the cached singleton — the same instance request handlers use.
255
+ *
256
+ * Wired into a platform heartbeat that is not a request: the Cloudflare Worker's
257
+ * `scheduled()` handler (Cron Trigger) calls this. On Node the runtime's own
258
+ * timer-based scheduler already drives the same work, so this isn't needed there.
259
+ *
260
+ * Returns the content promoted by the publishing sweep so the caller can purge
261
+ * edge-cache tags for it. `onPublished` (optional) is awaited after each
262
+ * collection's batch so the caller can invalidate edge-cache tags incrementally
263
+ * rather than only after the whole sweep.
264
+ */
265
+ export async function runScheduledTasks(
266
+ options: { onPublished?: (refs: PublishedRef[]) => Promise<void> } = {},
267
+ ): Promise<{ published: PublishedRef[] }> {
268
+ const config = getConfig();
269
+ if (!config) return { published: [] };
270
+ const runtime = await getRuntime(config);
271
+ return runtime.runScheduledTasks(options);
202
272
  }
203
273
 
204
274
  /**
@@ -429,7 +499,10 @@ export const onRequest = defineMiddleware(async (context, next) => {
429
499
  timings.push({ name: "render", dur: performance.now() - t0, desc: "Page render" });
430
500
  timings.push({ name: "mw", dur: performance.now() - mwStart, desc: "Total middleware" });
431
501
  pushMetricsTimings(timings, metrics);
432
- return finalizeResponse(response, timings);
502
+ // Server-Timing only sees pre-stream queries; the stream-end
503
+ // wrapper (instrumentation-gated, no-op otherwise) emits the
504
+ // final counters once the body finishes streaming.
505
+ return wrapBodyForStreamMetrics(finalizeResponse(response, timings));
433
506
  };
434
507
  if (anonScoped) {
435
508
  const parent = getRequestContext();
@@ -596,7 +669,10 @@ export const onRequest = defineMiddleware(async (context, next) => {
596
669
  timings.push({ name: "render", dur: performance.now() - t0, desc: "Page render" });
597
670
  timings.push({ name: "mw", dur: performance.now() - mwStart, desc: "Total middleware" });
598
671
  pushMetricsTimings(timings, metrics);
599
- return finalizeResponse(response, timings);
672
+ // Server-Timing only sees pre-stream queries; the stream-end
673
+ // wrapper (instrumentation-gated, no-op otherwise) emits the
674
+ // final counters once the body finishes streaming.
675
+ return wrapBodyForStreamMetrics(finalizeResponse(response, timings));
600
676
  };
601
677
 
602
678
  if (scoped) {
@@ -11,7 +11,7 @@ import { apiError, mapErrorStatus, unwrapResult } from "#api/error.js";
11
11
 
12
12
  export const prerender = false;
13
13
 
14
- export const POST: APIRoute = async ({ params, locals, cache }) => {
14
+ export const POST: APIRoute = async ({ params, locals, url, cache }) => {
15
15
  const { emdash, user } = locals;
16
16
  const collection = params.collection!;
17
17
  const id = params.id!;
@@ -20,8 +20,10 @@ export const POST: APIRoute = async ({ params, locals, cache }) => {
20
20
  return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
21
21
  }
22
22
 
23
+ const locale = url.searchParams.get("locale") || undefined;
24
+
23
25
  // Fetch item to check ownership
24
- const existing = await emdash.handleContentGet(collection, id);
26
+ const existing = await emdash.handleContentGet(collection, id, locale);
25
27
  if (!existing.success) {
26
28
  return apiError(
27
29
  existing.error?.code ?? "UNKNOWN_ERROR",
@@ -20,7 +20,7 @@ import { contentPublishBody } from "#api/schemas.js";
20
20
 
21
21
  export const prerender = false;
22
22
 
23
- export const POST: APIRoute = async ({ params, request, locals, cache }) => {
23
+ export const POST: APIRoute = async ({ params, request, locals, url, cache }) => {
24
24
  const { emdash, user } = locals;
25
25
  const collection = params.collection!;
26
26
  const id = params.id!;
@@ -34,8 +34,10 @@ export const POST: APIRoute = async ({ params, request, locals, cache }) => {
34
34
  const body = await parseOptionalBody(request, contentPublishBody, {});
35
35
  if (isParseError(body)) return body;
36
36
 
37
+ const locale = url.searchParams.get("locale") || undefined;
38
+
37
39
  // Fetch item to check ownership
38
- const existing = await emdash.handleContentGet(collection, id);
40
+ const existing = await emdash.handleContentGet(collection, id, locale);
39
41
  if (!existing.success) {
40
42
  return apiError(
41
43
  existing.error?.code ?? "UNKNOWN_ERROR",
@@ -34,7 +34,7 @@ function extractOwnership(data: unknown): { authorId: string; resolvedId: string
34
34
  };
35
35
  }
36
36
 
37
- export const POST: APIRoute = async ({ params, request, locals, cache }) => {
37
+ export const POST: APIRoute = async ({ params, request, locals, url, cache }) => {
38
38
  const { emdash, user } = locals;
39
39
  const collection = params.collection!;
40
40
  const id = params.id!;
@@ -45,8 +45,10 @@ export const POST: APIRoute = async ({ params, request, locals, cache }) => {
45
45
  return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
46
46
  }
47
47
 
48
+ const locale = url.searchParams.get("locale") || undefined;
49
+
48
50
  // Fetch item to check ownership
49
- const existing = await emdash.handleContentGet(collection, id);
51
+ const existing = await emdash.handleContentGet(collection, id, locale);
50
52
  if (!existing.success) {
51
53
  return apiError(
52
54
  existing.error?.code ?? "UNKNOWN_ERROR",
@@ -68,7 +70,7 @@ export const POST: APIRoute = async ({ params, request, locals, cache }) => {
68
70
  return unwrapResult(result);
69
71
  };
70
72
 
71
- export const DELETE: APIRoute = async ({ params, locals, cache }) => {
73
+ export const DELETE: APIRoute = async ({ params, locals, url, cache }) => {
72
74
  const { emdash, user } = locals;
73
75
  const collection = params.collection!;
74
76
  const id = params.id!;
@@ -77,8 +79,10 @@ export const DELETE: APIRoute = async ({ params, locals, cache }) => {
77
79
  return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
78
80
  }
79
81
 
82
+ const locale = url.searchParams.get("locale") || undefined;
83
+
80
84
  // Fetch item to check ownership
81
- const existing = await emdash.handleContentGet(collection, id);
85
+ const existing = await emdash.handleContentGet(collection, id, locale);
82
86
  if (!existing.success) {
83
87
  return apiError(
84
88
  existing.error?.code ?? "UNKNOWN_ERROR",
@@ -11,7 +11,7 @@ import { apiError, mapErrorStatus, unwrapResult } from "#api/error.js";
11
11
 
12
12
  export const prerender = false;
13
13
 
14
- export const POST: APIRoute = async ({ params, locals, cache }) => {
14
+ export const POST: APIRoute = async ({ params, locals, url, cache }) => {
15
15
  const { emdash, user } = locals;
16
16
  const collection = params.collection!;
17
17
  const id = params.id!;
@@ -20,8 +20,10 @@ export const POST: APIRoute = async ({ params, locals, cache }) => {
20
20
  return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
21
21
  }
22
22
 
23
+ const locale = url.searchParams.get("locale") || undefined;
24
+
23
25
  // Fetch item to check ownership
24
- const existing = await emdash.handleContentGet(collection, id);
26
+ const existing = await emdash.handleContentGet(collection, id, locale);
25
27
  if (!existing.success) {
26
28
  return apiError(
27
29
  existing.error?.code ?? "UNKNOWN_ERROR",
@@ -132,7 +132,7 @@ export const PUT: APIRoute = async ({ params, request, locals, cache }) => {
132
132
  return unwrapResult(result);
133
133
  };
134
134
 
135
- export const DELETE: APIRoute = async ({ params, locals, cache }) => {
135
+ export const DELETE: APIRoute = async ({ params, locals, url, cache }) => {
136
136
  const { emdash, user } = locals;
137
137
  const collection = params.collection!;
138
138
  const id = params.id!;
@@ -141,8 +141,10 @@ export const DELETE: APIRoute = async ({ params, locals, cache }) => {
141
141
  return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
142
142
  }
143
143
 
144
+ const locale = url.searchParams.get("locale") || undefined;
145
+
144
146
  // Fetch item to check ownership
145
- const existing = await emdash.handleContentGet(collection, id);
147
+ const existing = await emdash.handleContentGet(collection, id, locale);
146
148
  if (!existing.success) {
147
149
  return apiError(
148
150
  existing.error?.code ?? "UNKNOWN_ERROR",
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Content authors endpoint - injected by EmDash integration
3
+ *
4
+ * GET /_emdash/api/content/{collection}/authors - List the distinct authors
5
+ * of a collection's live content, for the admin author filter.
6
+ */
7
+
8
+ import type { APIRoute } from "astro";
9
+
10
+ import { requirePerm } from "#api/authorize.js";
11
+ import { apiError, unwrapResult } from "#api/error.js";
12
+
13
+ export const prerender = false;
14
+
15
+ export const GET: APIRoute = async ({ params, locals }) => {
16
+ const { emdash, user } = locals;
17
+ const collection = params.collection!;
18
+
19
+ // Editorial capability, not plain read. This response carries author
20
+ // emails (PII) and reveals the authors of unpublished entries, so it must
21
+ // not be reachable by subscribers (content:read). content:read_drafts is
22
+ // the same tier the list route requires before it stops forcing
23
+ // status=published, so the visibility surfaces line up.
24
+ const denied = requirePerm(user, "content:read_drafts");
25
+ if (denied) return denied;
26
+
27
+ if (!emdash?.handleContentAuthors) {
28
+ return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
29
+ }
30
+
31
+ const result = await emdash.handleContentAuthors(collection);
32
+
33
+ return unwrapResult(result);
34
+ };
@@ -233,9 +233,16 @@ export interface EmDashHandlers {
233
233
  orderBy?: string;
234
234
  order?: "asc" | "desc";
235
235
  locale?: string;
236
+ q?: string;
237
+ authorId?: string;
238
+ dateField?: "createdAt" | "updatedAt" | "publishedAt";
239
+ dateFrom?: string;
240
+ dateTo?: string;
236
241
  },
237
242
  ) => Promise<HandlerResponse>;
238
243
 
244
+ handleContentAuthors: (collection: string) => Promise<HandlerResponse>;
245
+
239
246
  handleContentGet: (
240
247
  collection: string,
241
248
  id: string,
@@ -313,7 +320,7 @@ export interface EmDashHandlers {
313
320
  handleContentPublish: (
314
321
  collection: string,
315
322
  id: string,
316
- options?: { publishedAt?: string },
323
+ options?: { publishedAt?: string; requireScheduledDue?: boolean },
317
324
  ) => Promise<HandlerResponse>;
318
325
 
319
326
  handleContentUnpublish: (collection: string, id: string) => Promise<HandlerResponse>;
@@ -320,6 +320,63 @@ export async function getBylinesForEntries(
320
320
  return result;
321
321
  }
322
322
 
323
+ /**
324
+ * Get content entries credited to a byline, in any credit position.
325
+ *
326
+ * Unlike filtering on the content table's `primary_byline_id` column (which
327
+ * only finds entries where the byline is the first/primary credit), this
328
+ * matches every explicit credit recorded in `_emdash_content_bylines`, so
329
+ * co-authored entries where the byline is a secondary credit are included.
330
+ *
331
+ * `byline` is matched against the byline's `translation_group` (the value
332
+ * stored on credits since migration 040), so a single credit spans every
333
+ * locale variant of the byline. Pass `byline.translationGroup ?? byline.id`
334
+ * from `getByline` / `getBylineBySlug`. An array matches any of the given
335
+ * bylines (OR).
336
+ *
337
+ * The result respects the active locale, status, ordering, and eager
338
+ * hydration of `getEmDashCollection`.
339
+ *
340
+ * @example
341
+ * ```ts
342
+ * import { getBylineBySlug, getEntriesByByline } from "emdash";
343
+ *
344
+ * const byline = await getBylineBySlug("jane-doe");
345
+ * if (byline) {
346
+ * const posts = await getEntriesByByline("posts", byline.translationGroup ?? byline.id, {
347
+ * orderBy: { published_at: "desc" },
348
+ * });
349
+ * }
350
+ * ```
351
+ *
352
+ * @param collection - The collection slug (e.g. "posts")
353
+ * @param byline - A byline translation group, or an array of them (OR)
354
+ * @param options - Optional locale, ordering, status, and limit
355
+ */
356
+ export async function getEntriesByByline(
357
+ collection: string,
358
+ byline: string | string[],
359
+ options: {
360
+ locale?: string;
361
+ orderBy?: Record<string, "asc" | "desc">;
362
+ status?: "draft" | "published" | "archived";
363
+ limit?: number;
364
+ } = {},
365
+ ): Promise<Array<{ id: string; data: Record<string, unknown> }>> {
366
+ const { getEmDashCollection } = await import("../query.js");
367
+
368
+ const queryOptions: Record<string, unknown> = {
369
+ where: { byline },
370
+ };
371
+ if (options.locale !== undefined) queryOptions.locale = options.locale;
372
+ if (options.orderBy !== undefined) queryOptions.orderBy = options.orderBy;
373
+ if (options.status !== undefined) queryOptions.status = options.status;
374
+ if (options.limit !== undefined) queryOptions.limit = options.limit;
375
+
376
+ const { entries } = await getEmDashCollection(collection, queryOptions);
377
+ return entries;
378
+ }
379
+
323
380
  /** Reads `author_id` + `primary_byline_id` for one entry in a single query. */
324
381
  async function getEntryContext(
325
382
  db: Awaited<ReturnType<typeof getDb>>,
@@ -20,7 +20,7 @@ import { OptionsRepository } from "../../database/repositories/options.js";
20
20
  import { TaxonomyRepository } from "../../database/repositories/taxonomy.js";
21
21
  import type { Database } from "../../database/types.js";
22
22
  import { validateIdentifier } from "../../database/validate.js";
23
- import { isI18nEnabled } from "../../i18n/config.js";
23
+ import { getI18nConfig, isI18nEnabled } from "../../i18n/config.js";
24
24
  import { SchemaRegistry } from "../../schema/registry.js";
25
25
  import type { FieldType } from "../../schema/types.js";
26
26
  import type {
@@ -128,7 +128,12 @@ export async function exportSeed(db: Kysely<Database>, withContent?: string): Pr
128
128
  // middleware, but the CLI never does, so `isI18nEnabled()` is always false
129
129
  // under `emdash export-seed` (#1330). Detecting multiple locales in the data
130
130
  // keeps the export locale-aware without the runtime flag.
131
- const i18nEnabled = await detectI18nEnabled(db, seed.collections);
131
+ const { i18nEnabled, defaultLocale } = await detectLocaleInfo(db, seed.collections);
132
+
133
+ // Self-describe the default locale so a non-`en` single-locale project
134
+ // survives the round-trip: `emdash seed` runs outside the runtime and would
135
+ // otherwise backfill omitted locales as `en` (#1421).
136
+ if (defaultLocale) seed.defaultLocale = defaultLocale;
132
137
 
133
138
  // 3. Export taxonomy definitions and terms
134
139
  seed.taxonomies = await exportTaxonomies(db, i18nEnabled);
@@ -216,7 +221,7 @@ async function exportBylines(
216
221
  }
217
222
 
218
223
  /**
219
- * Determine whether the export should emit locale-suffixed seed ids.
224
+ * Determine locale-awareness and the data's default locale for the export.
220
225
  *
221
226
  * The runtime initializes the i18n config in middleware, but the CLI never does,
222
227
  * so `isI18nEnabled()` is always false under `emdash export-seed` (#1330). When
@@ -227,39 +232,50 @@ async function exportBylines(
227
232
  * genuinely single-locale project from a multi-locale one. This keeps
228
233
  * single-locale exports on bare ids and gives multi-locale exports the
229
234
  * per-locale suffix they need to avoid duplicate seed ids.
235
+ *
236
+ * `defaultLocale` self-describes the single-locale case so a non-`en` default
237
+ * survives the round-trip (#1421). When more than one locale is present every
238
+ * row already carries its own `locale`, so no fallback is needed and we leave it
239
+ * undefined rather than guess which locale is the "default" without the runtime
240
+ * config.
230
241
  */
231
- async function detectI18nEnabled(
242
+ async function detectLocaleInfo(
232
243
  db: Kysely<Database>,
233
244
  collections: SeedCollection[],
234
- ): Promise<boolean> {
235
- if (isI18nEnabled()) return true;
245
+ ): Promise<{ i18nEnabled: boolean; defaultLocale: string | undefined }> {
246
+ const config = getI18nConfig();
247
+ if (isI18nEnabled() && config) {
248
+ return { i18nEnabled: true, defaultLocale: config.defaultLocale };
249
+ }
236
250
 
237
251
  const locales = new Set<string>();
238
- const collectDistinctLocales = async (tableRef: ReturnType<typeof sql.ref>): Promise<boolean> => {
252
+ const collectDistinctLocales = async (tableRef: ReturnType<typeof sql.ref>): Promise<void> => {
239
253
  const result = await sql<{ locale: string | null }>`
240
254
  SELECT DISTINCT locale FROM ${tableRef}
241
255
  `.execute(db);
242
256
  for (const row of result.rows) {
243
257
  if (row.locale) locales.add(row.locale);
244
258
  }
245
- return locales.size > 1;
246
259
  };
247
260
 
248
- if (await collectDistinctLocales(sql.ref("_emdash_taxonomy_defs"))) return true;
249
- if (await collectDistinctLocales(sql.ref("_emdash_menus"))) return true;
261
+ await collectDistinctLocales(sql.ref("_emdash_taxonomy_defs"));
262
+ await collectDistinctLocales(sql.ref("_emdash_menus"));
250
263
 
251
264
  for (const collection of collections) {
252
265
  validateIdentifier(collection.slug, "collection slug");
253
266
  // On D1, deleteCollection is non-atomic, so a collection row can outlive
254
267
  // its ec_* table. Skip missing tables rather than crashing the export.
255
268
  try {
256
- if (await collectDistinctLocales(sql.ref(`ec_${collection.slug}`))) return true;
269
+ await collectDistinctLocales(sql.ref(`ec_${collection.slug}`));
257
270
  } catch (error) {
258
271
  if (!isMissingTableError(error)) throw error;
259
272
  }
260
273
  }
261
274
 
262
- return false;
275
+ return {
276
+ i18nEnabled: locales.size > 1,
277
+ defaultLocale: locales.size === 1 ? [...locales][0] : undefined,
278
+ };
263
279
  }
264
280
 
265
281
  /**
@@ -18,11 +18,16 @@
18
18
  */
19
19
  import type { MediaValue } from "../fields/types.js";
20
20
  import type { HTMLAttributes } from "astro/types";
21
+ import { getImage } from "astro:assets";
21
22
  import type { ImageEmbed } from "../media/types.js";
22
23
  import { getMediaProvider } from "../media/provider-loader.js";
23
24
  import { buildRenderMediaUrl } from "../media/url.js";
24
- // Standard responsive breakpoints
25
- const BREAKPOINTS = [640, 750, 828, 960, 1080, 1280, 1600, 1920];
25
+ import {
26
+ buildResponsiveImage,
27
+ toAbsoluteMediaUrl,
28
+ RESPONSIVE_BREAKPOINTS,
29
+ } from "../media/responsive.js";
30
+ import { getPublicOrigin } from "../api/public-url.js";
26
31
 
27
32
  interface Props extends Omit<
28
33
  HTMLAttributes<"img">,
@@ -72,7 +77,7 @@ function generateSrcset(
72
77
  maxWidth: number,
73
78
  aspectRatio?: number
74
79
  ): string {
75
- return BREAKPOINTS.filter((w) => w <= maxWidth * 2) // Include up to 2x for retina
80
+ return RESPONSIVE_BREAKPOINTS.filter((w) => w <= maxWidth * 2) // Include up to 2x for retina
76
81
  .map((w) => {
77
82
  const h = aspectRatio ? Math.round(w / aspectRatio) : undefined;
78
83
  return `${getSrc({ width: w, height: h })} ${w}w`;
@@ -98,8 +103,21 @@ if (img) {
98
103
  const providerId = img.provider ?? "local";
99
104
 
100
105
  if (providerId === "local" || img.src) {
101
- // Local provider or direct src URL
106
+ // Local provider or direct src URL. Route through Astro's image service
107
+ // (`astro:assets`) to generate a responsive srcset; on Cloudflare this is
108
+ // the Images binding, on Node it is sharp. Falls back to a plain <img>
109
+ // when the service is unavailable or the source can't be optimized.
102
110
  src = img.src || buildLocalImageUrl(img);
111
+ const optimized = await buildResponsiveImage(getImage, {
112
+ src: toAbsoluteMediaUrl(src, getPublicOrigin(Astro.url, Astro.locals.emdash?.config)),
113
+ width: finalWidth,
114
+ height: finalHeight,
115
+ });
116
+ if (optimized) {
117
+ src = optimized.src;
118
+ srcset = optimized.srcset;
119
+ sizes = optimized.sizes;
120
+ }
103
121
  } else {
104
122
  // External provider
105
123
  try {
@@ -170,6 +188,7 @@ const imgProps: Record<string, unknown> = {
170
188
  height: finalHeight,
171
189
  alt: finalAlt,
172
190
  loading: priority ? "eager" : "lazy",
191
+ fetchpriority: priority ? "high" : undefined,
173
192
  decoding: "async",
174
193
  style: placeholderStyle ? `${baseStyle} ${placeholderStyle}` : baseStyle,
175
194
  ...attrs,
@@ -5,11 +5,16 @@
5
5
  * Renders image blocks from WordPress imports and EmDash media.
6
6
  * Uses the provider's getSrc function for responsive srcset generation.
7
7
  */
8
+ import { getImage } from "astro:assets";
8
9
  import type { ImageEmbed } from "../media/types.js";
9
10
  import { getMediaProvider } from "../media/provider-loader.js";
10
11
  import { buildRenderMediaUrl } from "../media/url.js";
11
- // Standard responsive breakpoints
12
- const BREAKPOINTS = [640, 750, 828, 960, 1080, 1280, 1600, 1920];
12
+ import {
13
+ buildResponsiveImage,
14
+ toAbsoluteMediaUrl,
15
+ RESPONSIVE_BREAKPOINTS,
16
+ } from "../media/responsive.js";
17
+ import { getPublicOrigin } from "../api/public-url.js";
13
18
 
14
19
  export interface Props {
15
20
  node: {
@@ -44,7 +49,7 @@ function generateSrcset(
44
49
  maxWidth: number,
45
50
  aspectRatio?: number,
46
51
  ): string {
47
- return BREAKPOINTS.filter((w) => w <= maxWidth * 2)
52
+ return RESPONSIVE_BREAKPOINTS.filter((w) => w <= maxWidth * 2)
48
53
  .map((w) => {
49
54
  const h = aspectRatio ? Math.round(w / aspectRatio) : undefined;
50
55
  return `${getSrc({ width: w, height: h })} ${w}w`;
@@ -131,6 +136,18 @@ if (!src) {
131
136
  url: asset.url,
132
137
  id: asset._ref,
133
138
  });
139
+ // Generate a responsive srcset via Astro's image service for local/R2 media.
140
+ // Falls back to the plain URL when optimization isn't possible.
141
+ const optimized = await buildResponsiveImage(getImage, {
142
+ src: toAbsoluteMediaUrl(src, getPublicOrigin(Astro.url, Astro.locals.emdash?.config)),
143
+ width: renderWidth,
144
+ height: renderHeight,
145
+ });
146
+ if (optimized) {
147
+ src = optimized.src;
148
+ srcset = optimized.srcset;
149
+ sizes = optimized.sizes;
150
+ }
134
151
  }
135
152
 
136
153
  // Build placeholder background style