emdash 0.18.0 → 0.20.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 (528) hide show
  1. package/dist/{adapters-C5AWLJSD.d.mts → adapters-BzIHV3sw.d.mts} +1 -1
  2. package/dist/{adapters-C5AWLJSD.d.mts.map → adapters-BzIHV3sw.d.mts.map} +1 -1
  3. package/dist/{allowed-origins-CyYLEJkp.mjs → allowed-origins-B1u7Qnvg.mjs} +2 -2
  4. package/dist/{allowed-origins-CyYLEJkp.mjs.map → allowed-origins-B1u7Qnvg.mjs.map} +1 -1
  5. package/dist/api/route-utils.d.mts +3 -3
  6. package/dist/api/route-utils.mjs +15 -15
  7. package/dist/api/schemas/index.d.mts +2 -2
  8. package/dist/api/schemas/index.mjs +3 -3
  9. package/dist/{api-Cs7DAACP.mjs → api-DStv36ik.mjs} +123 -20
  10. package/dist/api-DStv36ik.mjs.map +1 -0
  11. package/dist/{api-tokens-VrXNiNvV.mjs → api-tokens-DPfhPu5V.mjs} +2 -2
  12. package/dist/{api-tokens-VrXNiNvV.mjs.map → api-tokens-DPfhPu5V.mjs.map} +1 -1
  13. package/dist/{apply-BWMV4Zmw.mjs → apply-Dr7snAMT.mjs} +23 -23
  14. package/dist/apply-Dr7snAMT.mjs.map +1 -0
  15. package/dist/astro/index.d.mts +10 -10
  16. package/dist/astro/index.d.mts.map +1 -1
  17. package/dist/astro/index.mjs +115 -25
  18. package/dist/astro/index.mjs.map +1 -1
  19. package/dist/astro/middleware/auth.d.mts +9 -9
  20. package/dist/astro/middleware/auth.mjs +6 -6
  21. package/dist/astro/middleware/redirect.mjs +4 -4
  22. package/dist/astro/middleware/request-context.mjs +2 -2
  23. package/dist/astro/middleware/setup.mjs +1 -1
  24. package/dist/astro/middleware.d.mts +26 -4
  25. package/dist/astro/middleware.d.mts.map +1 -1
  26. package/dist/astro/middleware.mjs +242 -259
  27. package/dist/astro/middleware.mjs.map +1 -1
  28. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +5 -5
  29. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +5 -5
  30. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +4 -4
  31. package/dist/astro/routes/api/admin/api-tokens/index.mjs +5 -5
  32. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +5 -5
  33. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +8 -8
  34. package/dist/astro/routes/api/admin/byline-fields/index.mjs +8 -8
  35. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +8 -8
  36. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +12 -12
  37. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +12 -12
  38. package/dist/astro/routes/api/admin/bylines/index.mjs +12 -12
  39. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +11 -11
  40. package/dist/astro/routes/api/admin/comments/_id_.mjs +5 -5
  41. package/dist/astro/routes/api/admin/comments/bulk.mjs +8 -8
  42. package/dist/astro/routes/api/admin/comments/counts.mjs +5 -5
  43. package/dist/astro/routes/api/admin/comments/index.mjs +8 -8
  44. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +5 -5
  45. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +4 -4
  46. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +4 -4
  47. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +4 -4
  48. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +34 -34
  49. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +34 -34
  50. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +33 -33
  51. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +33 -33
  52. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +33 -33
  53. package/dist/astro/routes/api/admin/plugins/index.mjs +33 -33
  54. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
  55. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +33 -33
  56. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +33 -33
  57. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +33 -33
  58. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +33 -33
  59. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +34 -34
  60. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +33 -33
  61. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +34 -34
  62. package/dist/astro/routes/api/admin/plugins/updates.mjs +33 -33
  63. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +33 -33
  64. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +3 -3
  65. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +33 -33
  66. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +3 -3
  67. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
  68. package/dist/astro/routes/api/admin/users/_id_/index.mjs +6 -6
  69. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +4 -4
  70. package/dist/astro/routes/api/admin/users/index.mjs +5 -5
  71. package/dist/astro/routes/api/auth/dev-bypass.mjs +5 -5
  72. package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
  73. package/dist/astro/routes/api/auth/invite/complete.mjs +10 -10
  74. package/dist/astro/routes/api/auth/invite/index.mjs +7 -7
  75. package/dist/astro/routes/api/auth/invite/register-options.mjs +9 -9
  76. package/dist/astro/routes/api/auth/logout.mjs +3 -3
  77. package/dist/astro/routes/api/auth/magic-link/send.mjs +8 -8
  78. package/dist/astro/routes/api/auth/magic-link/verify.mjs +3 -3
  79. package/dist/astro/routes/api/auth/me.mjs +6 -6
  80. package/dist/astro/routes/api/auth/mode.mjs +1 -1
  81. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +4 -4
  82. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
  83. package/dist/astro/routes/api/auth/passkey/_id_.mjs +5 -5
  84. package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
  85. package/dist/astro/routes/api/auth/passkey/options.mjs +10 -10
  86. package/dist/astro/routes/api/auth/passkey/register/options.mjs +9 -9
  87. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +10 -10
  88. package/dist/astro/routes/api/auth/passkey/verify.mjs +10 -10
  89. package/dist/astro/routes/api/auth/signup/complete.mjs +10 -10
  90. package/dist/astro/routes/api/auth/signup/request.mjs +8 -8
  91. package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
  92. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +11 -11
  93. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
  94. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +6 -5
  95. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs.map +1 -1
  96. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
  97. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
  98. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +8 -8
  99. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +9 -8
  100. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
  101. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
  102. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
  103. package/dist/astro/routes/api/content/_collection_/_id_/schedule.d.mts.map +1 -1
  104. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +12 -10
  105. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs.map +1 -1
  106. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +11 -11
  107. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
  108. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +6 -5
  109. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs.map +1 -1
  110. package/dist/astro/routes/api/content/_collection_/_id_.mjs +9 -8
  111. package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
  112. package/dist/astro/routes/api/content/_collection_/authors.d.mts +8 -0
  113. package/dist/astro/routes/api/content/_collection_/authors.d.mts.map +1 -0
  114. package/dist/astro/routes/api/content/_collection_/authors.mjs +19 -0
  115. package/dist/astro/routes/api/content/_collection_/authors.mjs.map +1 -0
  116. package/dist/astro/routes/api/content/_collection_/index.mjs +6 -6
  117. package/dist/astro/routes/api/content/_collection_/trash.mjs +6 -6
  118. package/dist/astro/routes/api/dashboard.mjs +7 -7
  119. package/dist/astro/routes/api/dev/emails.mjs +2 -2
  120. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  121. package/dist/astro/routes/api/import/probe.mjs +6 -6
  122. package/dist/astro/routes/api/import/wordpress/analyze.mjs +4 -4
  123. package/dist/astro/routes/api/import/wordpress/execute.d.mts +9 -9
  124. package/dist/astro/routes/api/import/wordpress/execute.mjs +9 -9
  125. package/dist/astro/routes/api/import/wordpress/media.mjs +6 -6
  126. package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -9
  127. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +8 -8
  128. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  129. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +6 -6
  130. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +1 -1
  131. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +9 -9
  132. package/dist/astro/routes/api/manifest.mjs +4 -4
  133. package/dist/astro/routes/api/mcp.mjs +29 -29
  134. package/dist/astro/routes/api/media/_id_/confirm.mjs +6 -6
  135. package/dist/astro/routes/api/media/_id_.mjs +6 -6
  136. package/dist/astro/routes/api/media/file/_...key_.mjs +2 -2
  137. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +3 -3
  138. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +3 -3
  139. package/dist/astro/routes/api/media/providers/index.mjs +3 -3
  140. package/dist/astro/routes/api/media/upload-url.mjs +7 -7
  141. package/dist/astro/routes/api/media.mjs +8 -8
  142. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +7 -7
  143. package/dist/astro/routes/api/menus/_name_/items.mjs +7 -7
  144. package/dist/astro/routes/api/menus/_name_/reorder.mjs +7 -7
  145. package/dist/astro/routes/api/menus/_name_/translations.mjs +7 -7
  146. package/dist/astro/routes/api/menus/_name_.mjs +7 -7
  147. package/dist/astro/routes/api/menus/index.mjs +7 -7
  148. package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
  149. package/dist/astro/routes/api/oauth/device/authorize.mjs +6 -6
  150. package/dist/astro/routes/api/oauth/device/code.mjs +8 -8
  151. package/dist/astro/routes/api/oauth/device/token.mjs +7 -7
  152. package/dist/astro/routes/api/oauth/register.mjs +3 -3
  153. package/dist/astro/routes/api/oauth/token/refresh.mjs +6 -6
  154. package/dist/astro/routes/api/oauth/token/revoke.mjs +6 -6
  155. package/dist/astro/routes/api/oauth/token.mjs +6 -6
  156. package/dist/astro/routes/api/openapi.json.mjs +17 -3
  157. package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
  158. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +4 -4
  159. package/dist/astro/routes/api/redirects/404s/index.mjs +9 -9
  160. package/dist/astro/routes/api/redirects/404s/summary.mjs +9 -9
  161. package/dist/astro/routes/api/redirects/_id_.mjs +10 -10
  162. package/dist/astro/routes/api/redirects/index.mjs +10 -10
  163. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
  164. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
  165. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +33 -33
  166. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +33 -33
  167. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +33 -33
  168. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +33 -33
  169. package/dist/astro/routes/api/schema/collections/index.mjs +33 -33
  170. package/dist/astro/routes/api/schema/index.mjs +9 -14
  171. package/dist/astro/routes/api/schema/index.mjs.map +1 -1
  172. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +33 -33
  173. package/dist/astro/routes/api/schema/orphans/index.mjs +33 -33
  174. package/dist/astro/routes/api/search/enable.mjs +9 -9
  175. package/dist/astro/routes/api/search/index.mjs +8 -8
  176. package/dist/astro/routes/api/search/rebuild.mjs +9 -9
  177. package/dist/astro/routes/api/search/stats.mjs +6 -6
  178. package/dist/astro/routes/api/search/suggest.mjs +8 -8
  179. package/dist/astro/routes/api/sections/_slug_.mjs +8 -8
  180. package/dist/astro/routes/api/sections/index.mjs +8 -8
  181. package/dist/astro/routes/api/settings/email.mjs +5 -5
  182. package/dist/astro/routes/api/settings.mjs +12 -12
  183. package/dist/astro/routes/api/setup/admin-verify.mjs +11 -11
  184. package/dist/astro/routes/api/setup/admin.mjs +10 -10
  185. package/dist/astro/routes/api/setup/dev-bypass.mjs +23 -23
  186. package/dist/astro/routes/api/setup/dev-reset.mjs +3 -3
  187. package/dist/astro/routes/api/setup/index.mjs +23 -23
  188. package/dist/astro/routes/api/setup/status.mjs +4 -4
  189. package/dist/astro/routes/api/snapshot.mjs +6 -6
  190. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +11 -11
  191. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +11 -11
  192. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +11 -11
  193. package/dist/astro/routes/api/taxonomies/index.mjs +11 -11
  194. package/dist/astro/routes/api/themes/preview.mjs +6 -6
  195. package/dist/astro/routes/api/typegen.mjs +5 -5
  196. package/dist/astro/routes/api/well-known/auth.mjs +2 -2
  197. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
  198. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
  199. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +6 -6
  200. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +9 -8
  201. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
  202. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +9 -8
  203. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
  204. package/dist/astro/routes/api/widget-areas/_name_.mjs +6 -5
  205. package/dist/astro/routes/api/widget-areas/_name_.mjs.map +1 -1
  206. package/dist/astro/routes/api/widget-areas/index.mjs +9 -8
  207. package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
  208. package/dist/astro/routes/api/widget-components.mjs +3 -3
  209. package/dist/astro/routes/robots.txt.mjs +7 -7
  210. package/dist/astro/routes/sitemap-_collection_.xml.d.mts.map +1 -1
  211. package/dist/astro/routes/sitemap-_collection_.xml.mjs +16 -9
  212. package/dist/astro/routes/sitemap-_collection_.xml.mjs.map +1 -1
  213. package/dist/astro/routes/sitemap.xml.mjs +8 -8
  214. package/dist/astro/types.d.mts +19 -12
  215. package/dist/astro/types.d.mts.map +1 -1
  216. package/dist/auth/providers/github.d.mts +1 -1
  217. package/dist/auth/providers/google.d.mts +1 -1
  218. package/dist/{authorize-CotM4Yiu.mjs → authorize-DsMSVSaY.mjs} +2 -2
  219. package/dist/{authorize-CotM4Yiu.mjs.map → authorize-DsMSVSaY.mjs.map} +1 -1
  220. package/dist/{byline-CWQ9aSoz.mjs → byline-DUx48sJp.mjs} +6 -6
  221. package/dist/{byline-CWQ9aSoz.mjs.map → byline-DUx48sJp.mjs.map} +1 -1
  222. package/dist/{byline-fields-DC3Wkk-U.mjs → byline-fields--WxSNS79.mjs} +2 -2
  223. package/dist/{byline-fields-DC3Wkk-U.mjs.map → byline-fields--WxSNS79.mjs.map} +1 -1
  224. package/dist/{byline-fields-Dr-xcb6S.mjs → byline-fields-8TMtkBnH.mjs} +3 -3
  225. package/dist/{byline-fields-Dr-xcb6S.mjs.map → byline-fields-8TMtkBnH.mjs.map} +1 -1
  226. package/dist/{byline-fields-BNy7Ng1U.d.mts → byline-fields-DbibsvTl.d.mts} +30 -2
  227. package/dist/byline-fields-DbibsvTl.d.mts.map +1 -0
  228. package/dist/{byline-registry-CxK5g559.mjs → byline-registry-CWP7I71B.mjs} +3 -3
  229. package/dist/{byline-registry-CxK5g559.mjs.map → byline-registry-CWP7I71B.mjs.map} +1 -1
  230. package/dist/{bylines-LJMgENMI.mjs → bylines-BdxWCnPL.mjs} +3 -3
  231. package/dist/{bylines-LJMgENMI.mjs.map → bylines-BdxWCnPL.mjs.map} +1 -1
  232. package/dist/{bylines-BJSva1Un.mjs → bylines-s8c2DXbH.mjs} +50 -6
  233. package/dist/{bylines-BJSva1Un.mjs.map → bylines-s8c2DXbH.mjs.map} +1 -1
  234. package/dist/{cache-lZL7SgVb.mjs → cache-B_HzASVT.mjs} +3 -3
  235. package/dist/{cache-lZL7SgVb.mjs.map → cache-B_HzASVT.mjs.map} +1 -1
  236. package/dist/{challenge-store-DGwuCc4R.mjs → challenge-store-DXX3rfdI.mjs} +1 -1
  237. package/dist/{challenge-store-DGwuCc4R.mjs.map → challenge-store-DXX3rfdI.mjs.map} +1 -1
  238. package/dist/{chunks-BU-vP9Dh.mjs → chunks-BerYVuve.mjs} +2 -2
  239. package/dist/{chunks-BU-vP9Dh.mjs.map → chunks-BerYVuve.mjs.map} +1 -1
  240. package/dist/cli/index.mjs +46 -32
  241. package/dist/cli/index.mjs.map +1 -1
  242. package/dist/client/cf-access.d.mts +1 -1
  243. package/dist/client/index.d.mts +1 -1
  244. package/dist/client/index.mjs +1 -1
  245. package/dist/{comment-C4jVbCM8.mjs → comment-sqQxNpN3.mjs} +2 -2
  246. package/dist/{comment-C4jVbCM8.mjs.map → comment-sqQxNpN3.mjs.map} +1 -1
  247. package/dist/{comments-BTAbC0Ek.mjs → comments-Vkivawyl.mjs} +3 -3
  248. package/dist/{comments-BTAbC0Ek.mjs.map → comments-Vkivawyl.mjs.map} +1 -1
  249. package/dist/{components-CTfpu3PZ.mjs → components-CK0cuUoH.mjs} +1 -1
  250. package/dist/{components-CTfpu3PZ.mjs.map → components-CK0cuUoH.mjs.map} +1 -1
  251. package/dist/{content-CyqOmOzm.mjs → content-BIlVx-RX.mjs} +132 -43
  252. package/dist/content-BIlVx-RX.mjs.map +1 -0
  253. package/dist/{context-DZ7bEh5-.mjs → context-Y7BRkWes.mjs} +10 -10
  254. package/dist/{context-DZ7bEh5-.mjs.map → context-Y7BRkWes.mjs.map} +1 -1
  255. package/dist/{cron-DZovZUnC.mjs → cron-BJ2ClIlj.mjs} +4 -3
  256. package/dist/cron-BJ2ClIlj.mjs.map +1 -0
  257. package/dist/{dashboard-B5WQpNTP.mjs → dashboard-2JgAMWxK.mjs} +4 -4
  258. package/dist/{dashboard-B5WQpNTP.mjs.map → dashboard-2JgAMWxK.mjs.map} +1 -1
  259. package/dist/database/instrumentation.d.mts +10 -1
  260. package/dist/database/instrumentation.d.mts.map +1 -1
  261. package/dist/database/instrumentation.mjs +13 -1
  262. package/dist/database/instrumentation.mjs.map +1 -1
  263. package/dist/db/index.d.mts +3 -3
  264. package/dist/db/index.mjs +1 -1
  265. package/dist/db/libsql.d.mts +1 -1
  266. package/dist/db/postgres.d.mts +1 -1
  267. package/dist/db/sqlite.d.mts +1 -1
  268. package/dist/{default-xLFNSsZ9.mjs → default-IlBaTFxM.mjs} +1 -1
  269. package/dist/{default-xLFNSsZ9.mjs.map → default-IlBaTFxM.mjs.map} +1 -1
  270. package/dist/{device-flow-ptLrVINd.mjs → device-flow-R23SIbQ2.mjs} +5 -5
  271. package/dist/{device-flow-ptLrVINd.mjs.map → device-flow-R23SIbQ2.mjs.map} +1 -1
  272. package/dist/{error-DJOsMVSt.mjs → error-RwM4dD35.mjs} +2 -2
  273. package/dist/{error-DJOsMVSt.mjs.map → error-RwM4dD35.mjs.map} +1 -1
  274. package/dist/{escape-bIyGoW5W.mjs → escape-Ds07EEyu.mjs} +1 -1
  275. package/dist/{escape-bIyGoW5W.mjs.map → escape-Ds07EEyu.mjs.map} +1 -1
  276. package/dist/{fts-manager-DR1ERA0c.mjs → fts-manager-1RgHmopc.mjs} +2 -2
  277. package/dist/{fts-manager-DR1ERA0c.mjs.map → fts-manager-1RgHmopc.mjs.map} +1 -1
  278. package/dist/{index-CjKdMZ3U.d.mts → index-B1keaX5Y.d.mts} +237 -24
  279. package/dist/index-B1keaX5Y.d.mts.map +1 -0
  280. package/dist/{index-D60_SzHG.d.mts → index-DR56od45.d.mts} +3 -3
  281. package/dist/{index-D60_SzHG.d.mts.map → index-DR56od45.d.mts.map} +1 -1
  282. package/dist/index.d.mts +17 -17
  283. package/dist/index.mjs +46 -46
  284. package/dist/{load-6ZrRhepW.mjs → load-BBetCvLC.mjs} +2 -2
  285. package/dist/{load-6ZrRhepW.mjs.map → load-BBetCvLC.mjs.map} +1 -1
  286. package/dist/{loader-Dyx8dhFV.mjs → loader-ZN1ll-d-.mjs} +36 -37
  287. package/dist/loader-ZN1ll-d-.mjs.map +1 -0
  288. package/dist/{manifest-schema-Cj-YrzrF.mjs → manifest-schema-BtwbL_vj.mjs} +55 -2
  289. package/dist/manifest-schema-BtwbL_vj.mjs.map +1 -0
  290. package/dist/media/index.d.mts +1 -1
  291. package/dist/media/index.mjs +1 -1
  292. package/dist/media/local-runtime.d.mts +11 -11
  293. package/dist/media/local-runtime.mjs +6 -6
  294. package/dist/{media-C-oovGCG.mjs → media-JOf3pNkw.mjs} +2 -2
  295. package/dist/{media-C-oovGCG.mjs.map → media-JOf3pNkw.mjs.map} +1 -1
  296. package/dist/{media-allowlist-CMcoYIjQ.mjs → media-allowlist-Dknq-OFY.mjs} +1 -1
  297. package/dist/{media-allowlist-CMcoYIjQ.mjs.map → media-allowlist-Dknq-OFY.mjs.map} +1 -1
  298. package/dist/media-url-VClf8glU.mjs +26 -0
  299. package/dist/media-url-VClf8glU.mjs.map +1 -0
  300. package/dist/{menus-DugoYwTX.mjs → menus-DX4_E01q.mjs} +3 -3
  301. package/dist/{menus-DugoYwTX.mjs.map → menus-DX4_E01q.mjs.map} +1 -1
  302. package/dist/{menus-BKkxXCmd.mjs → menus-DrQLusqj.mjs} +87 -37
  303. package/dist/menus-DrQLusqj.mjs.map +1 -0
  304. package/dist/{mode-BjlXswIw.mjs → mode-CO2vQHfq.mjs} +1 -1
  305. package/dist/{mode-BjlXswIw.mjs.map → mode-CO2vQHfq.mjs.map} +1 -1
  306. package/dist/{normalize-DVV8nbrL.mjs → normalize-CK5o04zr.mjs} +2 -2
  307. package/dist/{normalize-DVV8nbrL.mjs.map → normalize-CK5o04zr.mjs.map} +1 -1
  308. package/dist/{oauth-authorization-DvBAL75d.mjs → oauth-authorization-Bw4NdF_S.mjs} +5 -5
  309. package/dist/{oauth-authorization-DvBAL75d.mjs.map → oauth-authorization-Bw4NdF_S.mjs.map} +1 -1
  310. package/dist/{oauth-clients-8mPDStMv.mjs → oauth-clients-BGGFp57s.mjs} +1 -1
  311. package/dist/{oauth-clients-8mPDStMv.mjs.map → oauth-clients-BGGFp57s.mjs.map} +1 -1
  312. package/dist/{oauth-state-store-BJ7YtrfD.mjs → oauth-state-store-97x0xtN2.mjs} +1 -1
  313. package/dist/{oauth-state-store-BJ7YtrfD.mjs.map → oauth-state-store-97x0xtN2.mjs.map} +1 -1
  314. package/dist/{oauth-user-lookup-BdDSDvjF.mjs → oauth-user-lookup-B_vnZHKO.mjs} +1 -1
  315. package/dist/{oauth-user-lookup-BdDSDvjF.mjs.map → oauth-user-lookup-B_vnZHKO.mjs.map} +1 -1
  316. package/dist/{options-BL4X94qY.mjs → options-BPCVnesz.mjs} +1 -1
  317. package/dist/{options-BL4X94qY.mjs.map → options-BPCVnesz.mjs.map} +1 -1
  318. package/dist/{options-tb7DJROi.d.mts → options-DyYIYpPd.d.mts} +3 -3
  319. package/dist/{options-tb7DJROi.d.mts.map → options-DyYIYpPd.d.mts.map} +1 -1
  320. package/dist/page/index.d.mts +2 -2
  321. package/dist/{parse-BBkFmLVr.mjs → parse-CrGndy1A.mjs} +2 -2
  322. package/dist/{parse-BBkFmLVr.mjs.map → parse-CrGndy1A.mjs.map} +1 -1
  323. package/dist/{passkey-config-BDVM86Tj.mjs → passkey-config-C3QgnQnU.mjs} +1 -1
  324. package/dist/{passkey-config-BDVM86Tj.mjs.map → passkey-config-C3QgnQnU.mjs.map} +1 -1
  325. package/dist/{patterns-CqG5Ya3i.mjs → patterns-p-RBdTbM.mjs} +1 -1
  326. package/dist/{patterns-CqG5Ya3i.mjs.map → patterns-p-RBdTbM.mjs.map} +1 -1
  327. package/dist/{placeholder-B9lUUEmj.d.mts → placeholder-CVBv5z8k.d.mts} +1 -1
  328. package/dist/{placeholder-B9lUUEmj.d.mts.map → placeholder-CVBv5z8k.d.mts.map} +1 -1
  329. package/dist/plugin-types.d.mts +1 -1
  330. package/dist/plugin-utils.d.mts +9 -9
  331. package/dist/plugins/adapt-sandbox-entry.d.mts +9 -9
  332. package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
  333. package/dist/{public-url-egRHCy1m.mjs → public-url-BFVC2OTJ.mjs} +1 -1
  334. package/dist/{public-url-egRHCy1m.mjs.map → public-url-BFVC2OTJ.mjs.map} +1 -1
  335. package/dist/{query-Ctlq1aOk.mjs → query-CbUcI4Xk.mjs} +33 -17
  336. package/dist/query-CbUcI4Xk.mjs.map +1 -0
  337. package/dist/{rate-limit-CH6W6ikK.mjs → rate-limit-C7hjdkS5.mjs} +2 -2
  338. package/dist/{rate-limit-CH6W6ikK.mjs.map → rate-limit-C7hjdkS5.mjs.map} +1 -1
  339. package/dist/{redirect-Cw3JTlmj.mjs → redirect-B_q19j4v.mjs} +1 -1
  340. package/dist/{redirect-Cw3JTlmj.mjs.map → redirect-B_q19j4v.mjs.map} +1 -1
  341. package/dist/{redirect-C6tJA7tk.mjs → redirect-CRWIt8Zj.mjs} +3 -3
  342. package/dist/{redirect-C6tJA7tk.mjs.map → redirect-CRWIt8Zj.mjs.map} +1 -1
  343. package/dist/{redirects-C0L9JUk4.mjs → redirects-CCbCqCCd.mjs} +28 -4
  344. package/dist/redirects-CCbCqCCd.mjs.map +1 -0
  345. package/dist/{redirects-CacE9eQa.mjs → redirects-DxVoR7PI.mjs} +5 -5
  346. package/dist/{redirects-CacE9eQa.mjs.map → redirects-DxVoR7PI.mjs.map} +1 -1
  347. package/dist/{registry-CIDxZbhh.mjs → registry-brYh-rAT.mjs} +6 -6
  348. package/dist/{registry-CIDxZbhh.mjs.map → registry-brYh-rAT.mjs.map} +1 -1
  349. package/dist/{request-cache-BYMs-BGX.mjs → request-cache-D32LpnmI.mjs} +1 -1
  350. package/dist/{request-cache-BYMs-BGX.mjs.map → request-cache-D32LpnmI.mjs.map} +1 -1
  351. package/dist/request-context.d.mts +7 -0
  352. package/dist/request-context.d.mts.map +1 -1
  353. package/dist/request-context.mjs +2 -1
  354. package/dist/request-context.mjs.map +1 -1
  355. package/dist/{runner-pt6Wl-l-.mjs → runner--4wMWwKM.mjs} +217 -166
  356. package/dist/runner--4wMWwKM.mjs.map +1 -0
  357. package/dist/{runner-DM1yR5qd.d.mts → runner-DTdhuI9i.d.mts} +2 -2
  358. package/dist/{runner-DM1yR5qd.d.mts.map → runner-DTdhuI9i.d.mts.map} +1 -1
  359. package/dist/runtime.d.mts +10 -10
  360. package/dist/runtime.mjs +2 -2
  361. package/dist/{schema-B4tk0HAG.mjs → schema-C1E70ug_.mjs} +5 -5
  362. package/dist/{schema-B4tk0HAG.mjs.map → schema-C1E70ug_.mjs.map} +1 -1
  363. package/dist/{search-f-fNfwab.mjs → search-B3SGZw91.mjs} +4 -4
  364. package/dist/{search-f-fNfwab.mjs.map → search-B3SGZw91.mjs.map} +1 -1
  365. package/dist/{secrets-YYbTgB1w.mjs → secrets-ChPTmy9x.mjs} +2 -2
  366. package/dist/{secrets-YYbTgB1w.mjs.map → secrets-ChPTmy9x.mjs.map} +1 -1
  367. package/dist/{sections-biElLfT9.mjs → sections-D_lVzwRZ.mjs} +3 -3
  368. package/dist/{sections-biElLfT9.mjs.map → sections-D_lVzwRZ.mjs.map} +1 -1
  369. package/dist/seed/index.d.mts +2 -2
  370. package/dist/seed/index.mjs +17 -17
  371. package/dist/seo/index.d.mts +1 -1
  372. package/dist/seo/index.d.mts.map +1 -1
  373. package/dist/seo/index.mjs +3 -12
  374. package/dist/seo/index.mjs.map +1 -1
  375. package/dist/{seo-BR39kvTF.mjs → seo-B5e6y9Wk.mjs} +2 -2
  376. package/dist/{seo-BR39kvTF.mjs.map → seo-B5e6y9Wk.mjs.map} +1 -1
  377. package/dist/{seo-DfjLvu8i.mjs → seo-D_LPkOtu.mjs} +4 -3
  378. package/dist/seo-D_LPkOtu.mjs.map +1 -0
  379. package/dist/{service-BhR2acnc.mjs → service-ChDcsTBs.mjs} +3 -3
  380. package/dist/{service-BhR2acnc.mjs.map → service-ChDcsTBs.mjs.map} +1 -1
  381. package/dist/{settings-D_NJvjgN.mjs → settings-Cv47v9u8.mjs} +3 -3
  382. package/dist/{settings-D_NJvjgN.mjs.map → settings-Cv47v9u8.mjs.map} +1 -1
  383. package/dist/settings-DfxiWY_s.mjs +411 -0
  384. package/dist/settings-DfxiWY_s.mjs.map +1 -0
  385. package/dist/{setup-complete-VoEZfasi.mjs → setup-complete-yvPE4OsP.mjs} +2 -2
  386. package/dist/{setup-complete-VoEZfasi.mjs.map → setup-complete-yvPE4OsP.mjs.map} +1 -1
  387. package/dist/{setup-nonce-Bm0uKqmf.mjs → setup-nonce-C9aFzb94.mjs} +1 -1
  388. package/dist/{setup-nonce-Bm0uKqmf.mjs.map → setup-nonce-C9aFzb94.mjs.map} +1 -1
  389. package/dist/{site-url-Cm8-sJy7.mjs → site-url-CnHlmAs9.mjs} +2 -2
  390. package/dist/{site-url-Cm8-sJy7.mjs.map → site-url-CnHlmAs9.mjs.map} +1 -1
  391. package/dist/storage/local.d.mts +1 -1
  392. package/dist/storage/s3.d.mts +1 -1
  393. package/dist/{taxonomies-Mhn9rjTQ.mjs → taxonomies-BILwiyGk.mjs} +4 -4
  394. package/dist/{taxonomies-Mhn9rjTQ.mjs.map → taxonomies-BILwiyGk.mjs.map} +1 -1
  395. package/dist/{taxonomies-Crtzy4MT.mjs → taxonomies-BdAmbOwx.mjs} +50 -12
  396. package/dist/taxonomies-BdAmbOwx.mjs.map +1 -0
  397. package/dist/{taxonomy-DTZrIQpi.mjs → taxonomy-CdllE4oq.mjs} +3 -3
  398. package/dist/{taxonomy-DTZrIQpi.mjs.map → taxonomy-CdllE4oq.mjs.map} +1 -1
  399. package/dist/{transaction-NQj4VJ7Z.mjs → transaction-x2tJQ-A1.mjs} +1 -1
  400. package/dist/{transaction-NQj4VJ7Z.mjs.map → transaction-x2tJQ-A1.mjs.map} +1 -1
  401. package/dist/{transport-OnMNbsIA.d.mts → transport-B7PPP2CC.d.mts} +1 -1
  402. package/dist/{transport-OnMNbsIA.d.mts.map → transport-B7PPP2CC.d.mts.map} +1 -1
  403. package/dist/{transport--Ck3RBin.mjs → transport-CmpLD7W3.mjs} +1 -1
  404. package/dist/{transport--Ck3RBin.mjs.map → transport-CmpLD7W3.mjs.map} +1 -1
  405. package/dist/{types-DWnN7weG.d.mts → types-BFgrqwSk.d.mts} +1 -1
  406. package/dist/{types-DWnN7weG.d.mts.map → types-BFgrqwSk.d.mts.map} +1 -1
  407. package/dist/{types-Qa7-HJJC.d.mts → types-BH8-30hc.d.mts} +1 -1
  408. package/dist/{types-Qa7-HJJC.d.mts.map → types-BH8-30hc.d.mts.map} +1 -1
  409. package/dist/{types-DawhLFwy.d.mts → types-BPzXTV9x.d.mts} +26 -1
  410. package/dist/{types-DawhLFwy.d.mts.map → types-BPzXTV9x.d.mts.map} +1 -1
  411. package/dist/{types-DbCWhHet.d.mts → types-BUUVn1zr.d.mts} +2 -2
  412. package/dist/types-BUUVn1zr.d.mts.map +1 -0
  413. package/dist/{types-K3MDsxpy.mjs → types-BXSUSAjt.mjs} +16 -3
  414. package/dist/{types-K3MDsxpy.mjs.map → types-BXSUSAjt.mjs.map} +1 -1
  415. package/dist/{types-DMwSpvcw.d.mts → types-CPAPl93j.d.mts} +9 -3
  416. package/dist/{types-DMwSpvcw.d.mts.map → types-CPAPl93j.d.mts.map} +1 -1
  417. package/dist/types-CZI4E3qG.mjs +3 -0
  418. package/dist/{types-kwqCOUxj.d.mts → types-D4kUqbHh.d.mts} +1 -1
  419. package/dist/{types-kwqCOUxj.d.mts.map → types-D4kUqbHh.d.mts.map} +1 -1
  420. package/dist/{types-i8_uzhMD.d.mts → types-DTniiNto.d.mts} +19 -4
  421. package/dist/types-DTniiNto.d.mts.map +1 -0
  422. package/dist/{types-D8bhH891.mjs → types-DZk_y-MU.mjs} +1 -1
  423. package/dist/types-DZk_y-MU.mjs.map +1 -0
  424. package/dist/{types-DX6v9KzJ.d.mts → types-S15DXXNi.d.mts} +1 -1
  425. package/dist/{types-DX6v9KzJ.d.mts.map → types-S15DXXNi.d.mts.map} +1 -1
  426. package/dist/{user-DzEUl5zA.mjs → user-C0um7wrg.mjs} +18 -2
  427. package/dist/user-C0um7wrg.mjs.map +1 -0
  428. package/dist/{validate-JCXcsqiY.mjs → validate-Bz4vqcX1.mjs} +6 -3
  429. package/dist/validate-Bz4vqcX1.mjs.map +1 -0
  430. package/dist/{validate-Dy6nkNls.d.mts → validate-CNwkPWzz.d.mts} +13 -5
  431. package/dist/validate-CNwkPWzz.d.mts.map +1 -0
  432. package/dist/{validation-Bq-VyKJg.mjs → validation-DgGTJm3u.mjs} +5 -5
  433. package/dist/{validation-Bq-VyKJg.mjs.map → validation-DgGTJm3u.mjs.map} +1 -1
  434. package/dist/version-D-5txk2m.mjs +7 -0
  435. package/dist/{version-CnS-Cr8A.mjs.map → version-D-5txk2m.mjs.map} +1 -1
  436. package/dist/{widgets-Bap1eS1X.mjs → widgets-DZfmAbE4.mjs} +47 -44
  437. package/dist/widgets-DZfmAbE4.mjs.map +1 -0
  438. package/dist/{zod-generator-BSDpkqSH.mjs → zod-generator-Djo_VHCt.mjs} +2 -2
  439. package/dist/{zod-generator-BSDpkqSH.mjs.map → zod-generator-Djo_VHCt.mjs.map} +1 -1
  440. package/package.json +10 -10
  441. package/src/api/handlers/content.ts +107 -8
  442. package/src/api/handlers/index.ts +2 -0
  443. package/src/api/handlers/marketplace.ts +2 -5
  444. package/src/api/handlers/registry.ts +70 -0
  445. package/src/api/handlers/seo.ts +9 -1
  446. package/src/api/openapi/document.ts +25 -0
  447. package/src/api/schemas/content.ts +33 -0
  448. package/src/api/schemas/schema.ts +13 -1
  449. package/src/astro/integration/index.ts +98 -0
  450. package/src/astro/integration/routes.ts +6 -0
  451. package/src/astro/integration/virtual-modules.ts +39 -0
  452. package/src/astro/integration/vite-config.ts +12 -0
  453. package/src/astro/middleware.ts +48 -6
  454. package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +4 -2
  455. package/src/astro/routes/api/content/[collection]/[id]/publish.ts +4 -2
  456. package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +8 -4
  457. package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +4 -2
  458. package/src/astro/routes/api/content/[collection]/[id].ts +4 -2
  459. package/src/astro/routes/api/content/[collection]/authors.ts +34 -0
  460. package/src/astro/routes/api/schema/index.ts +7 -15
  461. package/src/astro/routes/sitemap-[collection].xml.ts +13 -2
  462. package/src/astro/types.ts +8 -1
  463. package/src/bylines/index.ts +57 -0
  464. package/src/cli/commands/bundle-utils.ts +2 -0
  465. package/src/cli/commands/export-seed.ts +28 -12
  466. package/src/cli/commands/secrets.ts +2 -2
  467. package/src/components/EmDashImage.astro +22 -4
  468. package/src/components/Image.astro +20 -3
  469. package/src/database/instrumentation.ts +13 -0
  470. package/src/database/migrations/043_content_references.ts +121 -0
  471. package/src/database/migrations/runner.ts +2 -0
  472. package/src/database/repositories/content.ts +225 -67
  473. package/src/database/repositories/index.ts +7 -0
  474. package/src/database/repositories/relation.ts +467 -0
  475. package/src/database/repositories/types.ts +31 -0
  476. package/src/database/repositories/user.ts +18 -0
  477. package/src/database/types.ts +34 -0
  478. package/src/emdash-runtime.ts +172 -67
  479. package/src/index.ts +8 -1
  480. package/src/loader.ts +81 -39
  481. package/src/media/responsive.ts +125 -0
  482. package/src/plugins/cron.ts +3 -2
  483. package/src/plugins/index.ts +5 -0
  484. package/src/plugins/manifest-schema.ts +75 -0
  485. package/src/plugins/marketplace.ts +2 -5
  486. package/src/plugins/scheduler/node.ts +9 -2
  487. package/src/plugins/types.ts +12 -0
  488. package/src/query.ts +45 -7
  489. package/src/request-context.ts +8 -0
  490. package/src/scheduled-publish.ts +153 -0
  491. package/src/schema/types.ts +11 -1
  492. package/src/seed/apply.ts +16 -6
  493. package/src/seed/types.ts +9 -0
  494. package/src/seed/validate.ts +15 -0
  495. package/src/seo/index.ts +2 -28
  496. package/src/seo/media-url.ts +32 -0
  497. package/src/settings/index.ts +32 -40
  498. package/src/taxonomies/index.ts +79 -12
  499. package/src/utils/isolate-cache.ts +189 -0
  500. package/src/virtual-modules.d.ts +11 -0
  501. package/src/widgets/index.ts +57 -54
  502. package/dist/api-Cs7DAACP.mjs.map +0 -1
  503. package/dist/apply-BWMV4Zmw.mjs.map +0 -1
  504. package/dist/byline-fields-BNy7Ng1U.d.mts.map +0 -1
  505. package/dist/content-CyqOmOzm.mjs.map +0 -1
  506. package/dist/cron-DZovZUnC.mjs.map +0 -1
  507. package/dist/index-CjKdMZ3U.d.mts.map +0 -1
  508. package/dist/loader-Dyx8dhFV.mjs.map +0 -1
  509. package/dist/manifest-schema-Cj-YrzrF.mjs.map +0 -1
  510. package/dist/menus-BKkxXCmd.mjs.map +0 -1
  511. package/dist/query-Ctlq1aOk.mjs.map +0 -1
  512. package/dist/redirects-C0L9JUk4.mjs.map +0 -1
  513. package/dist/runner-pt6Wl-l-.mjs.map +0 -1
  514. package/dist/seo-DfjLvu8i.mjs.map +0 -1
  515. package/dist/settings-b5zW1R1T.mjs +0 -235
  516. package/dist/settings-b5zW1R1T.mjs.map +0 -1
  517. package/dist/taxonomies-Crtzy4MT.mjs.map +0 -1
  518. package/dist/types-Cj2S6FuC.mjs +0 -3
  519. package/dist/types-D8bhH891.mjs.map +0 -1
  520. package/dist/types-DbCWhHet.d.mts.map +0 -1
  521. package/dist/types-i8_uzhMD.d.mts.map +0 -1
  522. package/dist/user-DzEUl5zA.mjs.map +0 -1
  523. package/dist/validate-Dy6nkNls.d.mts.map +0 -1
  524. package/dist/validate-JCXcsqiY.mjs.map +0 -1
  525. package/dist/version-CnS-Cr8A.mjs +0 -7
  526. package/dist/widgets-Bap1eS1X.mjs.map +0 -1
  527. package/src/plugins/scheduler/piggyback.ts +0 -71
  528. /package/dist/{api-tokens-B6VgoE6M.mjs → api-tokens-Oq39ba-Z.mjs} +0 -0
package/src/seed/apply.ts CHANGED
@@ -115,6 +115,13 @@ export async function applySeed(
115
115
  const seedIdMap = new Map<string, string>(); // seed id -> real entry id
116
116
  const seedBylineIdMap = new Map<string, string>(); // seed byline id -> real byline id
117
117
 
118
+ // Fallback locale for rows that omit an explicit `locale`. Prefer the runtime
119
+ // config (runtime-driven seeds), then the seed's self-described `defaultLocale`
120
+ // (CLI exports run outside the runtime), and only then `en`. Without the
121
+ // seed-carried default, a non-`en` single-locale project would be rewritten to
122
+ // `en` on apply (#1421).
123
+ const defaultLocale = getI18nConfig()?.defaultLocale ?? seed.defaultLocale ?? "en";
124
+
118
125
  // 1. Site settings
119
126
  if (seed.settings) {
120
127
  await setSiteSettings(seed.settings, db);
@@ -224,10 +231,9 @@ export async function applySeed(
224
231
  // seed-local id -> resolved info, used to wire `translationOf` refs.
225
232
  const defSeedIdMap = new Map<string, { id: string; translationGroup: string }>();
226
233
  const termSeedIdMap = new Map<string, string>();
227
- const fallbackLocale = getI18nConfig()?.defaultLocale ?? "en";
228
234
 
229
235
  for (const taxonomy of seed.taxonomies) {
230
- const defLocale = taxonomy.locale ?? fallbackLocale;
236
+ const defLocale = taxonomy.locale ?? defaultLocale;
231
237
 
232
238
  // (name, locale) is the UNIQUE key after migration 036.
233
239
  const existingDef = await db
@@ -418,8 +424,13 @@ export async function applySeed(
418
424
  // Create content entries
419
425
  for (const [collectionSlug, entries] of Object.entries(seed.content)) {
420
426
  for (const entry of entries) {
427
+ // Resolve the entry's locale up front so a non-`en` single-locale
428
+ // export (which omits `locale`) is filed under the project default
429
+ // rather than `en` (#1421).
430
+ const entryLocale = entry.locale ?? defaultLocale;
431
+
421
432
  // Check if entry exists (by slug + locale for locale-aware lookup)
422
- const existing = await contentRepo.findBySlug(collectionSlug, entry.slug, entry.locale);
433
+ const existing = await contentRepo.findBySlug(collectionSlug, entry.slug, entryLocale);
423
434
 
424
435
  if (existing) {
425
436
  if (onConflict === "error") {
@@ -515,7 +526,7 @@ export async function applySeed(
515
526
  slug: entry.slug,
516
527
  status,
517
528
  data: resolvedData,
518
- locale: entry.locale,
529
+ locale: entryLocale,
519
530
  translationOf,
520
531
  publishedAt: status === "published" ? new Date().toISOString() : null,
521
532
  });
@@ -545,10 +556,9 @@ export async function applySeed(
545
556
  const menuSeedIdMap = new Map<string, { id: string; translationGroup: string }>();
546
557
  // Shared across menus: translated items reference anchor items in sibling menus.
547
558
  const itemSeedIdMap = new Map<string, { id: string; translationGroup: string }>();
548
- const fallbackLocale = getI18nConfig()?.defaultLocale ?? "en";
549
559
 
550
560
  for (const menu of seed.menus) {
551
- const locale = menu.locale ?? fallbackLocale;
561
+ const locale = menu.locale ?? defaultLocale;
552
562
  let lookup = db
553
563
  .selectFrom("_emdash_menus")
554
564
  .selectAll()
package/src/seed/types.ts CHANGED
@@ -19,6 +19,15 @@ export interface SeedFile {
19
19
  /** Seed format version */
20
20
  version: "1";
21
21
 
22
+ /**
23
+ * Default locale for locale-bearing rows (menus, taxonomies, content) that
24
+ * omit an explicit `locale`. Lets a non-`en` single-locale project survive an
25
+ * `export-seed` → `seed` round-trip: `apply` runs outside the Astro runtime
26
+ * (no i18n config), so without this it would backfill the omitted locale as
27
+ * `en`. See #1421.
28
+ */
29
+ defaultLocale?: string;
30
+
22
31
  /** Metadata about the seed */
23
32
  meta?: {
24
33
  name?: string;
@@ -57,6 +57,21 @@ export function validateSeed(data: unknown): ValidationResult {
57
57
  errors.push(`Unsupported seed version: ${String(seed.version)}`);
58
58
  }
59
59
 
60
+ // defaultLocale backfills the locale of rows that omit one, so a blank or
61
+ // whitespace-padded value would silently write empty/invalid locales to the DB.
62
+ // Exported seeds never hit this, but it's part of the public schema now.
63
+ if (seed.defaultLocale !== undefined) {
64
+ if (
65
+ typeof seed.defaultLocale !== "string" ||
66
+ seed.defaultLocale.length === 0 ||
67
+ seed.defaultLocale !== seed.defaultLocale.trim()
68
+ ) {
69
+ errors.push(
70
+ "defaultLocale: must be a non-empty string with no leading or trailing whitespace",
71
+ );
72
+ }
73
+ }
74
+
60
75
  // Validate collections
61
76
  if (seed.collections) {
62
77
  if (!Array.isArray(seed.collections)) {
package/src/seo/index.ts CHANGED
@@ -30,6 +30,7 @@
30
30
  */
31
31
 
32
32
  import type { ContentSeo } from "../database/repositories/types.js";
33
+ import { buildSeoImageUrl } from "./media-url.js";
33
34
 
34
35
  const TRAILING_SLASH_RE = /\/$/;
35
36
  const ABSOLUTE_URL_RE = /^https?:\/\//i;
@@ -117,7 +118,7 @@ export function getSeoMeta<T>(content: SeoContentInput<T>, options: SeoMetaOptio
117
118
  null;
118
119
 
119
120
  // OG image: SEO image > default
120
- const ogImage = seo.image ? buildMediaUrl(seo.image, siteUrl) : (defaultOgImage ?? null);
121
+ const ogImage = seo.image ? buildSeoImageUrl(seo.image, siteUrl) : (defaultOgImage ?? null);
121
122
 
122
123
  // Canonical: explicit > path-based > null
123
124
  let canonical: string | null = null;
@@ -159,30 +160,3 @@ export function getSeoMeta<T>(content: SeoContentInput<T>, options: SeoMetaOptio
159
160
  export function getContentSeo<T>(content: SeoContentInput<T>): ContentSeo | undefined {
160
161
  return content.seo ?? content.data.seo;
161
162
  }
162
-
163
- /**
164
- * Build a media URL from a media reference ID.
165
- * If it's already an absolute URL, return as-is.
166
- */
167
- function buildMediaUrl(imageRef: string, siteUrl?: string): string {
168
- // If already an absolute URL, return as-is
169
- if (ABSOLUTE_URL_RE.test(imageRef)) {
170
- return imageRef;
171
- }
172
-
173
- // Root-relative path — the CMS SEO panel stores seo_image as
174
- // "/_emdash/api/media/file/01KS....svg" (already includes the API
175
- // prefix). Without this branch we'd re-prefix and produce
176
- // "${siteUrl}/_emdash/api/media/file//_emdash/api/media/file/<id>"
177
- // which 404s and breaks <meta property="og:image">.
178
- if (imageRef.startsWith("/")) {
179
- return siteUrl ? `${siteUrl.replace(TRAILING_SLASH_RE, "")}${imageRef}` : imageRef;
180
- }
181
-
182
- // Bare media_id — build the full media API path
183
- const mediaPath = `/_emdash/api/media/file/${imageRef}`;
184
- if (siteUrl) {
185
- return `${siteUrl.replace(TRAILING_SLASH_RE, "")}${mediaPath}`;
186
- }
187
- return mediaPath;
188
- }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Resolve a stored SEO image reference to a URL.
3
+ *
4
+ * The CMS SEO panel stores `seo_image` in one of these shapes:
5
+ * - an absolute URL (`https://...`) — returned as-is;
6
+ * - a root-relative path that already includes the media API prefix
7
+ * (`/_emdash/api/media/file/01KS....webp`) — prefixed with `siteUrl`;
8
+ * - a bare media id (`01KS...`) — expanded to the media API path, then
9
+ * prefixed with `siteUrl`.
10
+ *
11
+ * Shared by the SEO meta builder (`og:image`) and the sitemap route
12
+ * (`<image:image>`) so both resolve image references identically.
13
+ */
14
+ const TRAILING_SLASH_RE = /\/$/;
15
+ const ABSOLUTE_URL_RE = /^https?:\/\//i;
16
+
17
+ export function buildSeoImageUrl(imageRef: string, siteUrl?: string): string {
18
+ // Already absolute — use as-is.
19
+ if (ABSOLUTE_URL_RE.test(imageRef)) {
20
+ return imageRef;
21
+ }
22
+
23
+ // Root-relative path (already includes the media API prefix). Without
24
+ // this branch we'd re-prefix and produce a doubled path that 404s.
25
+ if (imageRef.startsWith("/")) {
26
+ return siteUrl ? `${siteUrl.replace(TRAILING_SLASH_RE, "")}${imageRef}` : imageRef;
27
+ }
28
+
29
+ // Bare media id — build the full media API path.
30
+ const mediaPath = `/_emdash/api/media/file/${imageRef}`;
31
+ return siteUrl ? `${siteUrl.replace(TRAILING_SLASH_RE, "")}${mediaPath}` : mediaPath;
32
+ }
@@ -7,12 +7,19 @@
7
7
 
8
8
  import type { Kysely } from "kysely";
9
9
 
10
+ import { after } from "../after.js";
10
11
  import { MediaRepository } from "../database/repositories/media.js";
11
12
  import { OptionsRepository } from "../database/repositories/options.js";
12
13
  import type { Database } from "../database/types.js";
13
14
  import { getDb } from "../loader.js";
14
15
  import { peekRequestCache, requestCached } from "../request-cache.js";
15
16
  import type { Storage } from "../storage/types.js";
17
+ import {
18
+ createIsolateCache,
19
+ type IsolateCache,
20
+ invalidateIsolateCache,
21
+ isolateCachedAsync,
22
+ } from "../utils/isolate-cache.js";
16
23
  import type { SiteSettings, SiteSettingKey, MediaReference, SeoSettings } from "./types.js";
17
24
 
18
25
  /** Prefix for site settings in the options table */
@@ -27,29 +34,22 @@ const SETTINGS_PREFIX = "site:";
27
34
  * once-per-isolate. Cross-isolate staleness is bounded by isolate lifetime
28
35
  * (workerd typically recycles within minutes); acceptable for chrome.
29
36
  *
30
- * Stored on globalThis with a Symbol.for key so Vite SSR chunk duplication
31
- * doesn't produce two independent caches (same pattern as request-context.ts).
32
- *
33
- * Invalidation: every `site:*` write bumps `version`. Reads compare the
34
- * cached promise's version against the current version and refetch on
35
- * mismatch. Caching the promise (not the resolved value) lets concurrent
36
- * cold-isolate readers share the in-flight query.
37
+ * Backed by isolate-cache.ts: concurrent cold-isolate reads coalesce onto one
38
+ * query via a reclaimable single-flight lock and the resolved *value* is
39
+ * cached — never a shared in-flight promise, so a cancelled request can't
40
+ * poison the isolate (see that file's header). Stored on globalThis with a
41
+ * Symbol.for key so Vite SSR chunk duplication doesn't produce two
42
+ * independent caches (same pattern as request-context.ts).
37
43
  */
38
- interface SiteSettingsHolder {
39
- version: number;
40
- cached: Promise<Partial<SiteSettings>> | null;
41
- cachedVersion: number;
42
- }
43
-
44
44
  const SITE_SETTINGS_CACHE_KEY = Symbol.for("emdash:site-settings");
45
45
  const g = globalThis as Record<symbol, unknown>;
46
- const holder: SiteSettingsHolder =
46
+ const settingsCache: IsolateCache<Partial<SiteSettings>> =
47
47
  // eslint-disable-next-line typescript/no-unsafe-type-assertion -- globalThis singleton pattern (see request-context.ts)
48
- (g[SITE_SETTINGS_CACHE_KEY] as SiteSettingsHolder | undefined) ??
48
+ (g[SITE_SETTINGS_CACHE_KEY] as IsolateCache<Partial<SiteSettings>> | undefined) ??
49
49
  (() => {
50
- const h: SiteSettingsHolder = { version: 0, cached: null, cachedVersion: -1 };
51
- g[SITE_SETTINGS_CACHE_KEY] = h;
52
- return h;
50
+ const c = createIsolateCache<Partial<SiteSettings>>();
51
+ g[SITE_SETTINGS_CACHE_KEY] = c;
52
+ return c;
53
53
  })();
54
54
 
55
55
  /**
@@ -60,9 +60,7 @@ const holder: SiteSettingsHolder =
60
60
  * own cached copy until they expire — staleness bounded by isolate lifetime.
61
61
  */
62
62
  export function invalidateSiteSettingsCache(): void {
63
- holder.version++;
64
- holder.cached = null;
65
- holder.cachedVersion = -1;
63
+ invalidateIsolateCache(settingsCache);
66
64
  }
67
65
 
68
66
  /**
@@ -210,25 +208,19 @@ export async function getSiteSettingWithDb<K extends SiteSettingKey>(
210
208
  * ```
211
209
  */
212
210
  export function getSiteSettings(): Promise<Partial<SiteSettings>> {
213
- return requestCached("siteSettings", () => {
214
- const versionAtCall = holder.version;
215
- if (holder.cached && holder.cachedVersion === versionAtCall) {
216
- return holder.cached;
217
- }
218
- const fetchPromise = (async () => {
219
- const db = await getDb();
220
- return getSiteSettingsWithDb(db);
221
- })().catch((error) => {
222
- if (holder.cached === fetchPromise) {
223
- holder.cached = null;
224
- holder.cachedVersion = -1;
225
- }
226
- throw error;
227
- });
228
- holder.cached = fetchPromise;
229
- holder.cachedVersion = versionAtCall;
230
- return fetchPromise;
231
- });
211
+ // requestCached dedupes within a single request; isolateCachedAsync
212
+ // coalesces across requests and caches the resolved value for the
213
+ // isolate's lifetime without ever sharing an awaitable promise.
214
+ return requestCached("siteSettings", () =>
215
+ isolateCachedAsync(
216
+ settingsCache,
217
+ async () => {
218
+ const db = await getDb();
219
+ return getSiteSettingsWithDb(db);
220
+ },
221
+ { anchor: (promise) => after(() => promise), ownerTimeoutMs: 30_000 },
222
+ ),
223
+ );
232
224
  }
233
225
 
234
226
  /**
@@ -120,15 +120,10 @@ export async function getTaxonomyTerms(
120
120
  if (locale !== undefined) termsQuery = termsQuery.where("locale", "=", locale);
121
121
  const rows = await termsQuery.execute();
122
122
 
123
- // Counts are keyed by translation_group (what the pivot stores).
124
- const countsResult = await db
125
- .selectFrom("content_taxonomies")
126
- .select(["taxonomy_id"])
127
- .select((eb) => eb.fn.count<number>("entry_id").as("count"))
128
- .groupBy("taxonomy_id")
129
- .execute();
130
- const counts = new Map<string, number>();
131
- for (const row of countsResult) counts.set(row.taxonomy_id, row.count);
123
+ // Counts are keyed by translation_group (what the pivot stores) and are
124
+ // locale-independent, so the aggregate is shared across every taxonomy
125
+ // rendered in this request (Categories + Tags widgets, etc.).
126
+ const counts = await getTaxonomyTermCounts();
132
127
 
133
128
  const flatTerms: TaxonomyTermRow[] = rows.map((row) => ({
134
129
  id: row.id,
@@ -148,6 +143,7 @@ export async function getTaxonomyTerms(
148
143
  name: term.name,
149
144
  slug: term.slug,
150
145
  label: term.label,
146
+ description: term.data ? JSON.parse(term.data).description : undefined,
151
147
  children: [],
152
148
  count: counts.get(term.translation_group ?? term.id) ?? 0,
153
149
  locale: term.locale,
@@ -156,6 +152,27 @@ export async function getTaxonomyTerms(
156
152
  });
157
153
  }
158
154
 
155
+ /**
156
+ * Per-translation-group usage counts across all taxonomies, in one aggregate
157
+ * scan of `content_taxonomies`. Counts are locale-independent (the pivot stores
158
+ * translation_group), so a single request-cached entry serves every taxonomy
159
+ * that renders during the request.
160
+ */
161
+ function getTaxonomyTermCounts(): Promise<Map<string, number>> {
162
+ return requestCached("taxonomy-term-counts", async () => {
163
+ const db = await getDb();
164
+ const countsResult = await db
165
+ .selectFrom("content_taxonomies")
166
+ .select(["taxonomy_id"])
167
+ .select((eb) => eb.fn.count<number>("entry_id").as("count"))
168
+ .groupBy("taxonomy_id")
169
+ .execute();
170
+ const counts = new Map<string, number>();
171
+ for (const row of countsResult) counts.set(row.taxonomy_id, row.count);
172
+ return counts;
173
+ });
174
+ }
175
+
159
176
  /**
160
177
  * Get a single term by (taxonomy, slug). Honours the fallback chain — if the
161
178
  * slug exists in a fallback locale, we return that row (useful for deep-linking
@@ -289,10 +306,57 @@ export async function getTermsForEntries(
289
306
  for (const id of uniqueIds) result.set(id, []);
290
307
  if (uniqueIds.length === 0) return result;
291
308
 
292
- const db = await getDb();
293
309
  const locale = resolveLocale(options.locale);
310
+ const localeKey = locale ?? "*";
294
311
 
295
- for (const chunk of chunks(uniqueIds, SQL_BATCH_SIZE)) {
312
+ // Entry-term hydration (getAllTermsForEntries -> primeEntryTermsCache)
313
+ // seeds the per-entry cache under the same key getEntryTerms uses:
314
+ // `terms:${collection}:${entryId}:${taxonomyName}:${localeKey}`, storing a
315
+ // TaxonomyTerm[] (including `[]` for entries with no terms). Satisfy those
316
+ // from cache and run the batched query only for the ids that missed.
317
+ const missedIds: string[] = [];
318
+ type CacheRead = { id: string; terms: TaxonomyTerm[] } | { id: string; miss: true };
319
+ const cacheReads: Array<Promise<CacheRead>> = [];
320
+ for (const id of uniqueIds) {
321
+ const cached = peekRequestCache<TaxonomyTerm[]>(
322
+ `terms:${collection}:${id}:${taxonomyName}:${localeKey}`,
323
+ );
324
+ if (cached) {
325
+ // A peeked promise can reject (e.g. a sibling getEntryTerms hit a
326
+ // missing table). Treat a rejection as a cache miss so the batched
327
+ // query path -- and its isMissingTableError guard below -- still runs,
328
+ // rather than propagating an uncaught error.
329
+ cacheReads.push(
330
+ cached.then(
331
+ (terms): CacheRead => ({ id, terms }),
332
+ (): CacheRead => ({ id, miss: true }),
333
+ ),
334
+ );
335
+ } else {
336
+ missedIds.push(id);
337
+ }
338
+ }
339
+ for (const read of await Promise.all(cacheReads)) {
340
+ if ("miss" in read) {
341
+ missedIds.push(read.id);
342
+ continue;
343
+ }
344
+ // Return a private copy. The cached array and its term objects are shared
345
+ // with getEntryTerms/getAllTermsForEntries (primeEntryTermsCache stores
346
+ // the same references), so a caller that mutates the result -- sorting in
347
+ // place, pushing into `children` -- must not poison the cache. The
348
+ // pre-cache implementation always returned freshly built arrays.
349
+ result.set(
350
+ read.id,
351
+ read.terms.map((t) => ({ ...t, children: [...t.children] })),
352
+ );
353
+ }
354
+
355
+ if (missedIds.length === 0) return result;
356
+
357
+ const db = await getDb();
358
+
359
+ for (const chunk of chunks(missedIds, SQL_BATCH_SIZE)) {
296
360
  let rows;
297
361
  try {
298
362
  let query = db
@@ -310,7 +374,10 @@ export async function getTermsForEntries(
310
374
  ])
311
375
  .where("content_taxonomies.collection", "=", collection)
312
376
  .where("content_taxonomies.entry_id", "in", chunk)
313
- .where("taxonomies.name", "=", taxonomyName);
377
+ .where("taxonomies.name", "=", taxonomyName)
378
+ // Match the order getAllTermsForEntries (the cache primer) uses, so
379
+ // cache-hit and DB-miss entries in one result are ordered consistently.
380
+ .orderBy("taxonomies.label", "asc");
314
381
  if (locale !== undefined) query = query.where("taxonomies.locale", "=", locale);
315
382
  rows = await query.execute();
316
383
  } catch (error) {
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Isolate-lifetime async value cache with single-flight and poison-immunity.
3
+ *
4
+ * Built for the "compute once per isolate, read on every request" caches
5
+ * (site settings, search-health verification, ...). These must coalesce
6
+ * concurrent cold-isolate reads into one query — but the obvious way to do
7
+ * that, caching the in-flight *promise* on an isolate-global and awaiting it
8
+ * from later requests, is unsafe on workerd: if the request that created the
9
+ * promise is cancelled mid-await (client disconnect, context teardown), its
10
+ * continuation never runs, so the promise neither resolves nor rejects. Every
11
+ * later request that awaits that shared promise then hangs until the isolate
12
+ * is evicted (observed as 524s at the 100s wall, near-zero CPU). A `.catch`
13
+ * that clears the cache doesn't help — a cancelled request doesn't reject.
14
+ *
15
+ * This cache stores the resolved *value* (not a promise) and coalesces via
16
+ * `initWithLock`: one request becomes the owner and runs `fetch`, everyone
17
+ * else polls for the published value and never awaits the owner's promise.
18
+ * A cancelled owner can therefore never strand a waiter — the worst case is
19
+ * the lock looks held until `deadlineMs`, then the next caller reclaims. The
20
+ * owner's `fetch` is also anchored (waitUntil) so a cancelled originator's
21
+ * query still completes and populates the cache, and bounded by
22
+ * `ownerTimeoutMs` so a genuinely stuck fetch reclaims instead of hanging.
23
+ *
24
+ * Invalidation bumps `version`; reads compare against the version captured at
25
+ * call time and refetch on mismatch.
26
+ */
27
+
28
+ import { createInitLock, type InitLock, initWithLock } from "./init-lock.js";
29
+
30
+ export interface IsolateCache<T> {
31
+ /** Last resolved value, valid only when `hasValue` is true. */
32
+ value: T | null;
33
+ /**
34
+ * Presence flag, separate from `value` so that falsy/`undefined`/`void`
35
+ * results cache correctly (a plain null check can't distinguish "cached
36
+ * undefined" from "never fetched").
37
+ */
38
+ hasValue: boolean;
39
+ /** Invalidation counter; bumped by `invalidateIsolateCache`. */
40
+ version: number;
41
+ /** The `version` the cached value was fetched at. */
42
+ valueVersion: number;
43
+ /** Reclaimable single-flight lock (see init-lock.ts). */
44
+ lock: InitLock;
45
+ }
46
+
47
+ export function createIsolateCache<T>(): IsolateCache<T> {
48
+ return { value: null, hasValue: false, version: 0, valueVersion: -1, lock: createInitLock() };
49
+ }
50
+
51
+ /**
52
+ * Force the next `isolateCachedAsync` call to refetch. An in-flight owner
53
+ * fetched at the old version will not publish into the new version, so its
54
+ * result is ignored by subsequent reads.
55
+ */
56
+ export function invalidateIsolateCache(cache: IsolateCache<unknown>): void {
57
+ cache.version++;
58
+ cache.hasValue = false;
59
+ cache.value = null;
60
+ cache.valueVersion = -1;
61
+ // Free the single-flight lock so a reader at the new version starts the
62
+ // refetch immediately instead of waiting out a stale owner's deadline. A
63
+ // still-running old-version owner can neither publish into the new version
64
+ // (version gate) nor clobber a new owner (claim gate), so releasing here
65
+ // is safe; the worst case is one brief duplicate fetch.
66
+ cache.lock.ownerStartedAt = null;
67
+ }
68
+
69
+ /**
70
+ * Headroom between the owner's own timeout and the waiter reclaim deadline.
71
+ * The reclaim deadline must sit *above* `ownerTimeoutMs` so a slow-but-live
72
+ * owner times out (and releases the lock) before a waiter would reclaim it —
73
+ * otherwise a fetch slower than the deadline is superseded before it can
74
+ * publish, and steady traffic turns that into a self-sustaining stampede.
75
+ */
76
+ const RECLAIM_HEADROOM_MS = 5_000;
77
+
78
+ export interface IsolateCachedOptions {
79
+ /**
80
+ * Hand the in-flight fetch to the host's lifetime extender (waitUntil via
81
+ * `after()`), so a cancelled originating request still drives it to
82
+ * completion and populates the cache.
83
+ */
84
+ anchor?: (promise: Promise<void>) => void;
85
+ /** Reclaim the single-flight lock if the owner holds it past this. */
86
+ deadlineMs?: number;
87
+ /** Waiter poll interval. */
88
+ pollMs?: number;
89
+ /** Waiter gives up and throws after this long rather than hanging. */
90
+ maxWaitMs?: number;
91
+ /**
92
+ * Bound the owner's own `fetch`: if it doesn't settle within this, the
93
+ * owner rejects (and releases the lock) instead of waiting indefinitely.
94
+ * The anchored copy keeps running, so a slow-but-live fetch can still
95
+ * publish for a later caller. Omit to leave the owner unbounded.
96
+ */
97
+ ownerTimeoutMs?: number;
98
+ }
99
+
100
+ /** Boxed cache hit so a `void`/falsy value is still distinguishable from a miss. */
101
+ interface Box<T> {
102
+ v: T;
103
+ }
104
+
105
+ function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
106
+ return new Promise<T>((resolve, reject) => {
107
+ const timer = setTimeout(() => {
108
+ reject(new Error(`isolateCachedAsync: owner fetch exceeded ${ms}ms`));
109
+ }, ms);
110
+ // Settle from the underlying promise (whichever wins the race with the
111
+ // timer), and always clear the timer so a resolved fetch doesn't leave
112
+ // a pending timeout holding the isolate alive.
113
+ promise.then(resolve, reject).finally(() => {
114
+ clearTimeout(timer);
115
+ });
116
+ });
117
+ }
118
+
119
+ /**
120
+ * Return the cached value for `cache`, computing it via `fetch` under a
121
+ * single-flight lock on a miss. Concurrent callers coalesce onto one fetch;
122
+ * a cancelled owner cannot poison later callers (see file header).
123
+ */
124
+ export function isolateCachedAsync<T>(
125
+ cache: IsolateCache<T>,
126
+ fetch: () => Promise<T>,
127
+ options: IsolateCachedOptions = {},
128
+ ): Promise<T> {
129
+ // Capture the version once: a value published at this version satisfies
130
+ // this call; an invalidation that lands mid-fetch makes the published
131
+ // value stale for *later* calls (which captured the newer version) but
132
+ // still valid for this one.
133
+ const versionAtCall = cache.version;
134
+
135
+ // Ignore a non-positive / non-finite owner timeout rather than letting it
136
+ // degenerate into an instant-reject (setTimeout coerces NaN/0 to ~0ms).
137
+ const ownerTimeoutMs =
138
+ options.ownerTimeoutMs !== undefined &&
139
+ Number.isFinite(options.ownerTimeoutMs) &&
140
+ options.ownerTimeoutMs > 0
141
+ ? options.ownerTimeoutMs
142
+ : undefined;
143
+
144
+ // Keep the reclaim deadline above the owner timeout (see RECLAIM_HEADROOM_MS):
145
+ // the owner's own timeout, not a waiter reclaim, is the primary release.
146
+ const deadlineMs =
147
+ ownerTimeoutMs === undefined
148
+ ? options.deadlineMs
149
+ : Math.max(options.deadlineMs ?? 0, ownerTimeoutMs + RECLAIM_HEADROOM_MS);
150
+
151
+ return initWithLock<Box<T>>(
152
+ cache.lock,
153
+ () =>
154
+ cache.hasValue && cache.valueVersion === versionAtCall
155
+ ? // eslint-disable-next-line typescript/no-unsafe-type-assertion -- hasValue gates that `value` holds a real T
156
+ ({ v: cache.value as T } satisfies Box<T>)
157
+ : null,
158
+ (isCurrentClaim) => {
159
+ // The real work, anchored independently so a cancelled owner's
160
+ // fetch still settles and publishes. Publication is gated on the
161
+ // claim so a reclaimed slow owner can't clobber the reclaimer's
162
+ // value (same contract as initWithLock's own callers).
163
+ const real = (async (): Promise<Box<T>> => {
164
+ const value = await fetch();
165
+ if (isCurrentClaim()) {
166
+ cache.value = value;
167
+ cache.hasValue = true;
168
+ cache.valueVersion = versionAtCall;
169
+ }
170
+ return { v: value };
171
+ })();
172
+ // Anchor the real fetch (not the timeout race): this is what must
173
+ // survive a cancelled owner and run to publication. initWithLock is
174
+ // left to manage only the lock; we don't double-anchor.
175
+ options.anchor?.(
176
+ real.then(
177
+ () => undefined,
178
+ () => undefined,
179
+ ),
180
+ );
181
+ return ownerTimeoutMs === undefined ? real : withTimeout(real, ownerTimeoutMs);
182
+ },
183
+ {
184
+ deadlineMs,
185
+ pollMs: options.pollMs,
186
+ maxWaitMs: options.maxWaitMs,
187
+ },
188
+ ).then((box) => box.v);
189
+ }
@@ -132,6 +132,17 @@ declare module "virtual:emdash/wait-until" {
132
132
  export const waitUntil: ((promise: Promise<unknown>) => void) | undefined;
133
133
  }
134
134
 
135
+ declare module "virtual:emdash/scheduler" {
136
+ import type { CreateSchedulerFn } from "./emdash-runtime.js";
137
+ /**
138
+ * Factory for the timer-based cron/maintenance heartbeat. A
139
+ * `NodeCronScheduler` factory on long-lived runtimes (Node/Bun); `null`
140
+ * under serverless adapters (e.g. Cloudflare) where an external Cron
141
+ * Trigger drives scheduled work instead.
142
+ */
143
+ export const createScheduler: CreateSchedulerFn | null;
144
+ }
145
+
135
146
  declare module "virtual:emdash/admin-registry" {
136
147
  /**
137
148
  * Plugin admin module registry.