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.
- package/dist/{adapters-C5AWLJSD.d.mts → adapters-BzIHV3sw.d.mts} +1 -1
- package/dist/{adapters-C5AWLJSD.d.mts.map → adapters-BzIHV3sw.d.mts.map} +1 -1
- package/dist/{allowed-origins-CyYLEJkp.mjs → allowed-origins-B1u7Qnvg.mjs} +2 -2
- package/dist/{allowed-origins-CyYLEJkp.mjs.map → allowed-origins-B1u7Qnvg.mjs.map} +1 -1
- package/dist/api/route-utils.d.mts +3 -3
- package/dist/api/route-utils.mjs +15 -15
- package/dist/api/schemas/index.d.mts +2 -2
- package/dist/api/schemas/index.mjs +3 -3
- package/dist/{api-Cs7DAACP.mjs → api-DStv36ik.mjs} +123 -20
- package/dist/api-DStv36ik.mjs.map +1 -0
- package/dist/{api-tokens-VrXNiNvV.mjs → api-tokens-DPfhPu5V.mjs} +2 -2
- package/dist/{api-tokens-VrXNiNvV.mjs.map → api-tokens-DPfhPu5V.mjs.map} +1 -1
- package/dist/{apply-BWMV4Zmw.mjs → apply-Dr7snAMT.mjs} +23 -23
- package/dist/apply-Dr7snAMT.mjs.map +1 -0
- package/dist/astro/index.d.mts +10 -10
- package/dist/astro/index.d.mts.map +1 -1
- package/dist/astro/index.mjs +115 -25
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +9 -9
- package/dist/astro/middleware/auth.mjs +6 -6
- package/dist/astro/middleware/redirect.mjs +4 -4
- package/dist/astro/middleware/request-context.mjs +2 -2
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.d.mts +26 -4
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +242 -259
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +5 -5
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs +5 -5
- package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +4 -4
- package/dist/astro/routes/api/admin/api-tokens/index.mjs +5 -5
- package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +5 -5
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +8 -8
- package/dist/astro/routes/api/admin/byline-fields/index.mjs +8 -8
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +8 -8
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +12 -12
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +12 -12
- package/dist/astro/routes/api/admin/bylines/index.mjs +12 -12
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs +11 -11
- package/dist/astro/routes/api/admin/comments/_id_.mjs +5 -5
- package/dist/astro/routes/api/admin/comments/bulk.mjs +8 -8
- package/dist/astro/routes/api/admin/comments/counts.mjs +5 -5
- package/dist/astro/routes/api/admin/comments/index.mjs +8 -8
- package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +5 -5
- package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +4 -4
- package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +4 -4
- package/dist/astro/routes/api/admin/oauth-clients/index.mjs +4 -4
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +34 -34
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +34 -34
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +33 -33
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +33 -33
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +33 -33
- package/dist/astro/routes/api/admin/plugins/index.mjs +33 -33
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +33 -33
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +33 -33
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +33 -33
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +33 -33
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +34 -34
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +33 -33
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs +34 -34
- package/dist/astro/routes/api/admin/plugins/updates.mjs +33 -33
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +33 -33
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +3 -3
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +33 -33
- package/dist/astro/routes/api/admin/users/_id_/disable.mjs +3 -3
- package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
- package/dist/astro/routes/api/admin/users/_id_/index.mjs +6 -6
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +4 -4
- package/dist/astro/routes/api/admin/users/index.mjs +5 -5
- package/dist/astro/routes/api/auth/dev-bypass.mjs +5 -5
- package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
- package/dist/astro/routes/api/auth/invite/complete.mjs +10 -10
- package/dist/astro/routes/api/auth/invite/index.mjs +7 -7
- package/dist/astro/routes/api/auth/invite/register-options.mjs +9 -9
- package/dist/astro/routes/api/auth/logout.mjs +3 -3
- package/dist/astro/routes/api/auth/magic-link/send.mjs +8 -8
- package/dist/astro/routes/api/auth/magic-link/verify.mjs +3 -3
- package/dist/astro/routes/api/auth/me.mjs +6 -6
- package/dist/astro/routes/api/auth/mode.mjs +1 -1
- package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +4 -4
- package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
- package/dist/astro/routes/api/auth/passkey/_id_.mjs +5 -5
- package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
- package/dist/astro/routes/api/auth/passkey/options.mjs +10 -10
- package/dist/astro/routes/api/auth/passkey/register/options.mjs +9 -9
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs +10 -10
- package/dist/astro/routes/api/auth/passkey/verify.mjs +10 -10
- package/dist/astro/routes/api/auth/signup/complete.mjs +10 -10
- package/dist/astro/routes/api/auth/signup/request.mjs +8 -8
- package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +11 -11
- package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +8 -8
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +9 -8
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.d.mts.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +12 -10
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +11 -11
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_.mjs +9 -8
- package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/authors.d.mts +8 -0
- package/dist/astro/routes/api/content/_collection_/authors.d.mts.map +1 -0
- package/dist/astro/routes/api/content/_collection_/authors.mjs +19 -0
- package/dist/astro/routes/api/content/_collection_/authors.mjs.map +1 -0
- package/dist/astro/routes/api/content/_collection_/index.mjs +6 -6
- package/dist/astro/routes/api/content/_collection_/trash.mjs +6 -6
- package/dist/astro/routes/api/dashboard.mjs +7 -7
- package/dist/astro/routes/api/dev/emails.mjs +2 -2
- package/dist/astro/routes/api/import/probe.d.mts +3 -3
- package/dist/astro/routes/api/import/probe.mjs +6 -6
- package/dist/astro/routes/api/import/wordpress/analyze.mjs +4 -4
- package/dist/astro/routes/api/import/wordpress/execute.d.mts +9 -9
- package/dist/astro/routes/api/import/wordpress/execute.mjs +9 -9
- package/dist/astro/routes/api/import/wordpress/media.mjs +6 -6
- package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -9
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +8 -8
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +6 -6
- package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +9 -9
- package/dist/astro/routes/api/manifest.mjs +4 -4
- package/dist/astro/routes/api/mcp.mjs +29 -29
- package/dist/astro/routes/api/media/_id_/confirm.mjs +6 -6
- package/dist/astro/routes/api/media/_id_.mjs +6 -6
- package/dist/astro/routes/api/media/file/_...key_.mjs +2 -2
- package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +3 -3
- package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +3 -3
- package/dist/astro/routes/api/media/providers/index.mjs +3 -3
- package/dist/astro/routes/api/media/upload-url.mjs +7 -7
- package/dist/astro/routes/api/media.mjs +8 -8
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +7 -7
- package/dist/astro/routes/api/menus/_name_/items.mjs +7 -7
- package/dist/astro/routes/api/menus/_name_/reorder.mjs +7 -7
- package/dist/astro/routes/api/menus/_name_/translations.mjs +7 -7
- package/dist/astro/routes/api/menus/_name_.mjs +7 -7
- package/dist/astro/routes/api/menus/index.mjs +7 -7
- package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
- package/dist/astro/routes/api/oauth/device/authorize.mjs +6 -6
- package/dist/astro/routes/api/oauth/device/code.mjs +8 -8
- package/dist/astro/routes/api/oauth/device/token.mjs +7 -7
- package/dist/astro/routes/api/oauth/register.mjs +3 -3
- package/dist/astro/routes/api/oauth/token/refresh.mjs +6 -6
- package/dist/astro/routes/api/oauth/token/revoke.mjs +6 -6
- package/dist/astro/routes/api/oauth/token.mjs +6 -6
- package/dist/astro/routes/api/openapi.json.mjs +17 -3
- package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +4 -4
- package/dist/astro/routes/api/redirects/404s/index.mjs +9 -9
- package/dist/astro/routes/api/redirects/404s/summary.mjs +9 -9
- package/dist/astro/routes/api/redirects/_id_.mjs +10 -10
- package/dist/astro/routes/api/redirects/index.mjs +10 -10
- package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
- package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +33 -33
- package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +33 -33
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +33 -33
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +33 -33
- package/dist/astro/routes/api/schema/collections/index.mjs +33 -33
- package/dist/astro/routes/api/schema/index.mjs +9 -14
- package/dist/astro/routes/api/schema/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +33 -33
- package/dist/astro/routes/api/schema/orphans/index.mjs +33 -33
- package/dist/astro/routes/api/search/enable.mjs +9 -9
- package/dist/astro/routes/api/search/index.mjs +8 -8
- package/dist/astro/routes/api/search/rebuild.mjs +9 -9
- package/dist/astro/routes/api/search/stats.mjs +6 -6
- package/dist/astro/routes/api/search/suggest.mjs +8 -8
- package/dist/astro/routes/api/sections/_slug_.mjs +8 -8
- package/dist/astro/routes/api/sections/index.mjs +8 -8
- package/dist/astro/routes/api/settings/email.mjs +5 -5
- package/dist/astro/routes/api/settings.mjs +12 -12
- package/dist/astro/routes/api/setup/admin-verify.mjs +11 -11
- package/dist/astro/routes/api/setup/admin.mjs +10 -10
- package/dist/astro/routes/api/setup/dev-bypass.mjs +23 -23
- package/dist/astro/routes/api/setup/dev-reset.mjs +3 -3
- package/dist/astro/routes/api/setup/index.mjs +23 -23
- package/dist/astro/routes/api/setup/status.mjs +4 -4
- package/dist/astro/routes/api/snapshot.mjs +6 -6
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +11 -11
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +11 -11
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +11 -11
- package/dist/astro/routes/api/taxonomies/index.mjs +11 -11
- package/dist/astro/routes/api/themes/preview.mjs +6 -6
- package/dist/astro/routes/api/typegen.mjs +5 -5
- package/dist/astro/routes/api/well-known/auth.mjs +2 -2
- package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
- package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +6 -6
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +9 -8
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +9 -8
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_.mjs +6 -5
- package/dist/astro/routes/api/widget-areas/_name_.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/index.mjs +9 -8
- package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
- package/dist/astro/routes/api/widget-components.mjs +3 -3
- package/dist/astro/routes/robots.txt.mjs +7 -7
- package/dist/astro/routes/sitemap-_collection_.xml.d.mts.map +1 -1
- package/dist/astro/routes/sitemap-_collection_.xml.mjs +16 -9
- package/dist/astro/routes/sitemap-_collection_.xml.mjs.map +1 -1
- package/dist/astro/routes/sitemap.xml.mjs +8 -8
- package/dist/astro/types.d.mts +19 -12
- package/dist/astro/types.d.mts.map +1 -1
- package/dist/auth/providers/github.d.mts +1 -1
- package/dist/auth/providers/google.d.mts +1 -1
- package/dist/{authorize-CotM4Yiu.mjs → authorize-DsMSVSaY.mjs} +2 -2
- package/dist/{authorize-CotM4Yiu.mjs.map → authorize-DsMSVSaY.mjs.map} +1 -1
- package/dist/{byline-CWQ9aSoz.mjs → byline-DUx48sJp.mjs} +6 -6
- package/dist/{byline-CWQ9aSoz.mjs.map → byline-DUx48sJp.mjs.map} +1 -1
- package/dist/{byline-fields-DC3Wkk-U.mjs → byline-fields--WxSNS79.mjs} +2 -2
- package/dist/{byline-fields-DC3Wkk-U.mjs.map → byline-fields--WxSNS79.mjs.map} +1 -1
- package/dist/{byline-fields-Dr-xcb6S.mjs → byline-fields-8TMtkBnH.mjs} +3 -3
- package/dist/{byline-fields-Dr-xcb6S.mjs.map → byline-fields-8TMtkBnH.mjs.map} +1 -1
- package/dist/{byline-fields-BNy7Ng1U.d.mts → byline-fields-DbibsvTl.d.mts} +30 -2
- package/dist/byline-fields-DbibsvTl.d.mts.map +1 -0
- package/dist/{byline-registry-CxK5g559.mjs → byline-registry-CWP7I71B.mjs} +3 -3
- package/dist/{byline-registry-CxK5g559.mjs.map → byline-registry-CWP7I71B.mjs.map} +1 -1
- package/dist/{bylines-LJMgENMI.mjs → bylines-BdxWCnPL.mjs} +3 -3
- package/dist/{bylines-LJMgENMI.mjs.map → bylines-BdxWCnPL.mjs.map} +1 -1
- package/dist/{bylines-BJSva1Un.mjs → bylines-s8c2DXbH.mjs} +50 -6
- package/dist/{bylines-BJSva1Un.mjs.map → bylines-s8c2DXbH.mjs.map} +1 -1
- package/dist/{cache-lZL7SgVb.mjs → cache-B_HzASVT.mjs} +3 -3
- package/dist/{cache-lZL7SgVb.mjs.map → cache-B_HzASVT.mjs.map} +1 -1
- package/dist/{challenge-store-DGwuCc4R.mjs → challenge-store-DXX3rfdI.mjs} +1 -1
- package/dist/{challenge-store-DGwuCc4R.mjs.map → challenge-store-DXX3rfdI.mjs.map} +1 -1
- package/dist/{chunks-BU-vP9Dh.mjs → chunks-BerYVuve.mjs} +2 -2
- package/dist/{chunks-BU-vP9Dh.mjs.map → chunks-BerYVuve.mjs.map} +1 -1
- package/dist/cli/index.mjs +46 -32
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/cf-access.d.mts +1 -1
- package/dist/client/index.d.mts +1 -1
- package/dist/client/index.mjs +1 -1
- package/dist/{comment-C4jVbCM8.mjs → comment-sqQxNpN3.mjs} +2 -2
- package/dist/{comment-C4jVbCM8.mjs.map → comment-sqQxNpN3.mjs.map} +1 -1
- package/dist/{comments-BTAbC0Ek.mjs → comments-Vkivawyl.mjs} +3 -3
- package/dist/{comments-BTAbC0Ek.mjs.map → comments-Vkivawyl.mjs.map} +1 -1
- package/dist/{components-CTfpu3PZ.mjs → components-CK0cuUoH.mjs} +1 -1
- package/dist/{components-CTfpu3PZ.mjs.map → components-CK0cuUoH.mjs.map} +1 -1
- package/dist/{content-CyqOmOzm.mjs → content-BIlVx-RX.mjs} +132 -43
- package/dist/content-BIlVx-RX.mjs.map +1 -0
- package/dist/{context-DZ7bEh5-.mjs → context-Y7BRkWes.mjs} +10 -10
- package/dist/{context-DZ7bEh5-.mjs.map → context-Y7BRkWes.mjs.map} +1 -1
- package/dist/{cron-DZovZUnC.mjs → cron-BJ2ClIlj.mjs} +4 -3
- package/dist/cron-BJ2ClIlj.mjs.map +1 -0
- package/dist/{dashboard-B5WQpNTP.mjs → dashboard-2JgAMWxK.mjs} +4 -4
- package/dist/{dashboard-B5WQpNTP.mjs.map → dashboard-2JgAMWxK.mjs.map} +1 -1
- package/dist/database/instrumentation.d.mts +10 -1
- package/dist/database/instrumentation.d.mts.map +1 -1
- package/dist/database/instrumentation.mjs +13 -1
- package/dist/database/instrumentation.mjs.map +1 -1
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs +1 -1
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/{default-xLFNSsZ9.mjs → default-IlBaTFxM.mjs} +1 -1
- package/dist/{default-xLFNSsZ9.mjs.map → default-IlBaTFxM.mjs.map} +1 -1
- package/dist/{device-flow-ptLrVINd.mjs → device-flow-R23SIbQ2.mjs} +5 -5
- package/dist/{device-flow-ptLrVINd.mjs.map → device-flow-R23SIbQ2.mjs.map} +1 -1
- package/dist/{error-DJOsMVSt.mjs → error-RwM4dD35.mjs} +2 -2
- package/dist/{error-DJOsMVSt.mjs.map → error-RwM4dD35.mjs.map} +1 -1
- package/dist/{escape-bIyGoW5W.mjs → escape-Ds07EEyu.mjs} +1 -1
- package/dist/{escape-bIyGoW5W.mjs.map → escape-Ds07EEyu.mjs.map} +1 -1
- package/dist/{fts-manager-DR1ERA0c.mjs → fts-manager-1RgHmopc.mjs} +2 -2
- package/dist/{fts-manager-DR1ERA0c.mjs.map → fts-manager-1RgHmopc.mjs.map} +1 -1
- package/dist/{index-CjKdMZ3U.d.mts → index-B1keaX5Y.d.mts} +237 -24
- package/dist/index-B1keaX5Y.d.mts.map +1 -0
- package/dist/{index-D60_SzHG.d.mts → index-DR56od45.d.mts} +3 -3
- package/dist/{index-D60_SzHG.d.mts.map → index-DR56od45.d.mts.map} +1 -1
- package/dist/index.d.mts +17 -17
- package/dist/index.mjs +46 -46
- package/dist/{load-6ZrRhepW.mjs → load-BBetCvLC.mjs} +2 -2
- package/dist/{load-6ZrRhepW.mjs.map → load-BBetCvLC.mjs.map} +1 -1
- package/dist/{loader-Dyx8dhFV.mjs → loader-ZN1ll-d-.mjs} +36 -37
- package/dist/loader-ZN1ll-d-.mjs.map +1 -0
- package/dist/{manifest-schema-Cj-YrzrF.mjs → manifest-schema-BtwbL_vj.mjs} +55 -2
- package/dist/manifest-schema-BtwbL_vj.mjs.map +1 -0
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +1 -1
- package/dist/media/local-runtime.d.mts +11 -11
- package/dist/media/local-runtime.mjs +6 -6
- package/dist/{media-C-oovGCG.mjs → media-JOf3pNkw.mjs} +2 -2
- package/dist/{media-C-oovGCG.mjs.map → media-JOf3pNkw.mjs.map} +1 -1
- package/dist/{media-allowlist-CMcoYIjQ.mjs → media-allowlist-Dknq-OFY.mjs} +1 -1
- package/dist/{media-allowlist-CMcoYIjQ.mjs.map → media-allowlist-Dknq-OFY.mjs.map} +1 -1
- package/dist/media-url-VClf8glU.mjs +26 -0
- package/dist/media-url-VClf8glU.mjs.map +1 -0
- package/dist/{menus-DugoYwTX.mjs → menus-DX4_E01q.mjs} +3 -3
- package/dist/{menus-DugoYwTX.mjs.map → menus-DX4_E01q.mjs.map} +1 -1
- package/dist/{menus-BKkxXCmd.mjs → menus-DrQLusqj.mjs} +87 -37
- package/dist/menus-DrQLusqj.mjs.map +1 -0
- package/dist/{mode-BjlXswIw.mjs → mode-CO2vQHfq.mjs} +1 -1
- package/dist/{mode-BjlXswIw.mjs.map → mode-CO2vQHfq.mjs.map} +1 -1
- package/dist/{normalize-DVV8nbrL.mjs → normalize-CK5o04zr.mjs} +2 -2
- package/dist/{normalize-DVV8nbrL.mjs.map → normalize-CK5o04zr.mjs.map} +1 -1
- package/dist/{oauth-authorization-DvBAL75d.mjs → oauth-authorization-Bw4NdF_S.mjs} +5 -5
- package/dist/{oauth-authorization-DvBAL75d.mjs.map → oauth-authorization-Bw4NdF_S.mjs.map} +1 -1
- package/dist/{oauth-clients-8mPDStMv.mjs → oauth-clients-BGGFp57s.mjs} +1 -1
- package/dist/{oauth-clients-8mPDStMv.mjs.map → oauth-clients-BGGFp57s.mjs.map} +1 -1
- package/dist/{oauth-state-store-BJ7YtrfD.mjs → oauth-state-store-97x0xtN2.mjs} +1 -1
- package/dist/{oauth-state-store-BJ7YtrfD.mjs.map → oauth-state-store-97x0xtN2.mjs.map} +1 -1
- package/dist/{oauth-user-lookup-BdDSDvjF.mjs → oauth-user-lookup-B_vnZHKO.mjs} +1 -1
- package/dist/{oauth-user-lookup-BdDSDvjF.mjs.map → oauth-user-lookup-B_vnZHKO.mjs.map} +1 -1
- package/dist/{options-BL4X94qY.mjs → options-BPCVnesz.mjs} +1 -1
- package/dist/{options-BL4X94qY.mjs.map → options-BPCVnesz.mjs.map} +1 -1
- package/dist/{options-tb7DJROi.d.mts → options-DyYIYpPd.d.mts} +3 -3
- package/dist/{options-tb7DJROi.d.mts.map → options-DyYIYpPd.d.mts.map} +1 -1
- package/dist/page/index.d.mts +2 -2
- package/dist/{parse-BBkFmLVr.mjs → parse-CrGndy1A.mjs} +2 -2
- package/dist/{parse-BBkFmLVr.mjs.map → parse-CrGndy1A.mjs.map} +1 -1
- package/dist/{passkey-config-BDVM86Tj.mjs → passkey-config-C3QgnQnU.mjs} +1 -1
- package/dist/{passkey-config-BDVM86Tj.mjs.map → passkey-config-C3QgnQnU.mjs.map} +1 -1
- package/dist/{patterns-CqG5Ya3i.mjs → patterns-p-RBdTbM.mjs} +1 -1
- package/dist/{patterns-CqG5Ya3i.mjs.map → patterns-p-RBdTbM.mjs.map} +1 -1
- package/dist/{placeholder-B9lUUEmj.d.mts → placeholder-CVBv5z8k.d.mts} +1 -1
- package/dist/{placeholder-B9lUUEmj.d.mts.map → placeholder-CVBv5z8k.d.mts.map} +1 -1
- package/dist/plugin-types.d.mts +1 -1
- package/dist/plugin-utils.d.mts +9 -9
- package/dist/plugins/adapt-sandbox-entry.d.mts +9 -9
- package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
- package/dist/{public-url-egRHCy1m.mjs → public-url-BFVC2OTJ.mjs} +1 -1
- package/dist/{public-url-egRHCy1m.mjs.map → public-url-BFVC2OTJ.mjs.map} +1 -1
- package/dist/{query-Ctlq1aOk.mjs → query-CbUcI4Xk.mjs} +33 -17
- package/dist/query-CbUcI4Xk.mjs.map +1 -0
- package/dist/{rate-limit-CH6W6ikK.mjs → rate-limit-C7hjdkS5.mjs} +2 -2
- package/dist/{rate-limit-CH6W6ikK.mjs.map → rate-limit-C7hjdkS5.mjs.map} +1 -1
- package/dist/{redirect-Cw3JTlmj.mjs → redirect-B_q19j4v.mjs} +1 -1
- package/dist/{redirect-Cw3JTlmj.mjs.map → redirect-B_q19j4v.mjs.map} +1 -1
- package/dist/{redirect-C6tJA7tk.mjs → redirect-CRWIt8Zj.mjs} +3 -3
- package/dist/{redirect-C6tJA7tk.mjs.map → redirect-CRWIt8Zj.mjs.map} +1 -1
- package/dist/{redirects-C0L9JUk4.mjs → redirects-CCbCqCCd.mjs} +28 -4
- package/dist/redirects-CCbCqCCd.mjs.map +1 -0
- package/dist/{redirects-CacE9eQa.mjs → redirects-DxVoR7PI.mjs} +5 -5
- package/dist/{redirects-CacE9eQa.mjs.map → redirects-DxVoR7PI.mjs.map} +1 -1
- package/dist/{registry-CIDxZbhh.mjs → registry-brYh-rAT.mjs} +6 -6
- package/dist/{registry-CIDxZbhh.mjs.map → registry-brYh-rAT.mjs.map} +1 -1
- package/dist/{request-cache-BYMs-BGX.mjs → request-cache-D32LpnmI.mjs} +1 -1
- package/dist/{request-cache-BYMs-BGX.mjs.map → request-cache-D32LpnmI.mjs.map} +1 -1
- package/dist/request-context.d.mts +7 -0
- package/dist/request-context.d.mts.map +1 -1
- package/dist/request-context.mjs +2 -1
- package/dist/request-context.mjs.map +1 -1
- package/dist/{runner-pt6Wl-l-.mjs → runner--4wMWwKM.mjs} +217 -166
- package/dist/runner--4wMWwKM.mjs.map +1 -0
- package/dist/{runner-DM1yR5qd.d.mts → runner-DTdhuI9i.d.mts} +2 -2
- package/dist/{runner-DM1yR5qd.d.mts.map → runner-DTdhuI9i.d.mts.map} +1 -1
- package/dist/runtime.d.mts +10 -10
- package/dist/runtime.mjs +2 -2
- package/dist/{schema-B4tk0HAG.mjs → schema-C1E70ug_.mjs} +5 -5
- package/dist/{schema-B4tk0HAG.mjs.map → schema-C1E70ug_.mjs.map} +1 -1
- package/dist/{search-f-fNfwab.mjs → search-B3SGZw91.mjs} +4 -4
- package/dist/{search-f-fNfwab.mjs.map → search-B3SGZw91.mjs.map} +1 -1
- package/dist/{secrets-YYbTgB1w.mjs → secrets-ChPTmy9x.mjs} +2 -2
- package/dist/{secrets-YYbTgB1w.mjs.map → secrets-ChPTmy9x.mjs.map} +1 -1
- package/dist/{sections-biElLfT9.mjs → sections-D_lVzwRZ.mjs} +3 -3
- package/dist/{sections-biElLfT9.mjs.map → sections-D_lVzwRZ.mjs.map} +1 -1
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +17 -17
- package/dist/seo/index.d.mts +1 -1
- package/dist/seo/index.d.mts.map +1 -1
- package/dist/seo/index.mjs +3 -12
- package/dist/seo/index.mjs.map +1 -1
- package/dist/{seo-BR39kvTF.mjs → seo-B5e6y9Wk.mjs} +2 -2
- package/dist/{seo-BR39kvTF.mjs.map → seo-B5e6y9Wk.mjs.map} +1 -1
- package/dist/{seo-DfjLvu8i.mjs → seo-D_LPkOtu.mjs} +4 -3
- package/dist/seo-D_LPkOtu.mjs.map +1 -0
- package/dist/{service-BhR2acnc.mjs → service-ChDcsTBs.mjs} +3 -3
- package/dist/{service-BhR2acnc.mjs.map → service-ChDcsTBs.mjs.map} +1 -1
- package/dist/{settings-D_NJvjgN.mjs → settings-Cv47v9u8.mjs} +3 -3
- package/dist/{settings-D_NJvjgN.mjs.map → settings-Cv47v9u8.mjs.map} +1 -1
- package/dist/settings-DfxiWY_s.mjs +411 -0
- package/dist/settings-DfxiWY_s.mjs.map +1 -0
- package/dist/{setup-complete-VoEZfasi.mjs → setup-complete-yvPE4OsP.mjs} +2 -2
- package/dist/{setup-complete-VoEZfasi.mjs.map → setup-complete-yvPE4OsP.mjs.map} +1 -1
- package/dist/{setup-nonce-Bm0uKqmf.mjs → setup-nonce-C9aFzb94.mjs} +1 -1
- package/dist/{setup-nonce-Bm0uKqmf.mjs.map → setup-nonce-C9aFzb94.mjs.map} +1 -1
- package/dist/{site-url-Cm8-sJy7.mjs → site-url-CnHlmAs9.mjs} +2 -2
- package/dist/{site-url-Cm8-sJy7.mjs.map → site-url-CnHlmAs9.mjs.map} +1 -1
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/s3.d.mts +1 -1
- package/dist/{taxonomies-Mhn9rjTQ.mjs → taxonomies-BILwiyGk.mjs} +4 -4
- package/dist/{taxonomies-Mhn9rjTQ.mjs.map → taxonomies-BILwiyGk.mjs.map} +1 -1
- package/dist/{taxonomies-Crtzy4MT.mjs → taxonomies-BdAmbOwx.mjs} +50 -12
- package/dist/taxonomies-BdAmbOwx.mjs.map +1 -0
- package/dist/{taxonomy-DTZrIQpi.mjs → taxonomy-CdllE4oq.mjs} +3 -3
- package/dist/{taxonomy-DTZrIQpi.mjs.map → taxonomy-CdllE4oq.mjs.map} +1 -1
- package/dist/{transaction-NQj4VJ7Z.mjs → transaction-x2tJQ-A1.mjs} +1 -1
- package/dist/{transaction-NQj4VJ7Z.mjs.map → transaction-x2tJQ-A1.mjs.map} +1 -1
- package/dist/{transport-OnMNbsIA.d.mts → transport-B7PPP2CC.d.mts} +1 -1
- package/dist/{transport-OnMNbsIA.d.mts.map → transport-B7PPP2CC.d.mts.map} +1 -1
- package/dist/{transport--Ck3RBin.mjs → transport-CmpLD7W3.mjs} +1 -1
- package/dist/{transport--Ck3RBin.mjs.map → transport-CmpLD7W3.mjs.map} +1 -1
- package/dist/{types-DWnN7weG.d.mts → types-BFgrqwSk.d.mts} +1 -1
- package/dist/{types-DWnN7weG.d.mts.map → types-BFgrqwSk.d.mts.map} +1 -1
- package/dist/{types-Qa7-HJJC.d.mts → types-BH8-30hc.d.mts} +1 -1
- package/dist/{types-Qa7-HJJC.d.mts.map → types-BH8-30hc.d.mts.map} +1 -1
- package/dist/{types-DawhLFwy.d.mts → types-BPzXTV9x.d.mts} +26 -1
- package/dist/{types-DawhLFwy.d.mts.map → types-BPzXTV9x.d.mts.map} +1 -1
- package/dist/{types-DbCWhHet.d.mts → types-BUUVn1zr.d.mts} +2 -2
- package/dist/types-BUUVn1zr.d.mts.map +1 -0
- package/dist/{types-K3MDsxpy.mjs → types-BXSUSAjt.mjs} +16 -3
- package/dist/{types-K3MDsxpy.mjs.map → types-BXSUSAjt.mjs.map} +1 -1
- package/dist/{types-DMwSpvcw.d.mts → types-CPAPl93j.d.mts} +9 -3
- package/dist/{types-DMwSpvcw.d.mts.map → types-CPAPl93j.d.mts.map} +1 -1
- package/dist/types-CZI4E3qG.mjs +3 -0
- package/dist/{types-kwqCOUxj.d.mts → types-D4kUqbHh.d.mts} +1 -1
- package/dist/{types-kwqCOUxj.d.mts.map → types-D4kUqbHh.d.mts.map} +1 -1
- package/dist/{types-i8_uzhMD.d.mts → types-DTniiNto.d.mts} +19 -4
- package/dist/types-DTniiNto.d.mts.map +1 -0
- package/dist/{types-D8bhH891.mjs → types-DZk_y-MU.mjs} +1 -1
- package/dist/types-DZk_y-MU.mjs.map +1 -0
- package/dist/{types-DX6v9KzJ.d.mts → types-S15DXXNi.d.mts} +1 -1
- package/dist/{types-DX6v9KzJ.d.mts.map → types-S15DXXNi.d.mts.map} +1 -1
- package/dist/{user-DzEUl5zA.mjs → user-C0um7wrg.mjs} +18 -2
- package/dist/user-C0um7wrg.mjs.map +1 -0
- package/dist/{validate-JCXcsqiY.mjs → validate-Bz4vqcX1.mjs} +6 -3
- package/dist/validate-Bz4vqcX1.mjs.map +1 -0
- package/dist/{validate-Dy6nkNls.d.mts → validate-CNwkPWzz.d.mts} +13 -5
- package/dist/validate-CNwkPWzz.d.mts.map +1 -0
- package/dist/{validation-Bq-VyKJg.mjs → validation-DgGTJm3u.mjs} +5 -5
- package/dist/{validation-Bq-VyKJg.mjs.map → validation-DgGTJm3u.mjs.map} +1 -1
- package/dist/version-D-5txk2m.mjs +7 -0
- package/dist/{version-CnS-Cr8A.mjs.map → version-D-5txk2m.mjs.map} +1 -1
- package/dist/{widgets-Bap1eS1X.mjs → widgets-DZfmAbE4.mjs} +47 -44
- package/dist/widgets-DZfmAbE4.mjs.map +1 -0
- package/dist/{zod-generator-BSDpkqSH.mjs → zod-generator-Djo_VHCt.mjs} +2 -2
- package/dist/{zod-generator-BSDpkqSH.mjs.map → zod-generator-Djo_VHCt.mjs.map} +1 -1
- package/package.json +10 -10
- package/src/api/handlers/content.ts +107 -8
- package/src/api/handlers/index.ts +2 -0
- package/src/api/handlers/marketplace.ts +2 -5
- package/src/api/handlers/registry.ts +70 -0
- package/src/api/handlers/seo.ts +9 -1
- package/src/api/openapi/document.ts +25 -0
- package/src/api/schemas/content.ts +33 -0
- package/src/api/schemas/schema.ts +13 -1
- package/src/astro/integration/index.ts +98 -0
- package/src/astro/integration/routes.ts +6 -0
- package/src/astro/integration/virtual-modules.ts +39 -0
- package/src/astro/integration/vite-config.ts +12 -0
- package/src/astro/middleware.ts +48 -6
- package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +4 -2
- package/src/astro/routes/api/content/[collection]/[id]/publish.ts +4 -2
- package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +8 -4
- package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +4 -2
- package/src/astro/routes/api/content/[collection]/[id].ts +4 -2
- package/src/astro/routes/api/content/[collection]/authors.ts +34 -0
- package/src/astro/routes/api/schema/index.ts +7 -15
- package/src/astro/routes/sitemap-[collection].xml.ts +13 -2
- package/src/astro/types.ts +8 -1
- package/src/bylines/index.ts +57 -0
- package/src/cli/commands/bundle-utils.ts +2 -0
- package/src/cli/commands/export-seed.ts +28 -12
- package/src/cli/commands/secrets.ts +2 -2
- package/src/components/EmDashImage.astro +22 -4
- package/src/components/Image.astro +20 -3
- package/src/database/instrumentation.ts +13 -0
- package/src/database/migrations/043_content_references.ts +121 -0
- package/src/database/migrations/runner.ts +2 -0
- package/src/database/repositories/content.ts +225 -67
- package/src/database/repositories/index.ts +7 -0
- package/src/database/repositories/relation.ts +467 -0
- package/src/database/repositories/types.ts +31 -0
- package/src/database/repositories/user.ts +18 -0
- package/src/database/types.ts +34 -0
- package/src/emdash-runtime.ts +172 -67
- package/src/index.ts +8 -1
- package/src/loader.ts +81 -39
- package/src/media/responsive.ts +125 -0
- package/src/plugins/cron.ts +3 -2
- package/src/plugins/index.ts +5 -0
- package/src/plugins/manifest-schema.ts +75 -0
- package/src/plugins/marketplace.ts +2 -5
- package/src/plugins/scheduler/node.ts +9 -2
- package/src/plugins/types.ts +12 -0
- package/src/query.ts +45 -7
- package/src/request-context.ts +8 -0
- package/src/scheduled-publish.ts +153 -0
- package/src/schema/types.ts +11 -1
- package/src/seed/apply.ts +16 -6
- package/src/seed/types.ts +9 -0
- package/src/seed/validate.ts +15 -0
- package/src/seo/index.ts +2 -28
- package/src/seo/media-url.ts +32 -0
- package/src/settings/index.ts +32 -40
- package/src/taxonomies/index.ts +79 -12
- package/src/utils/isolate-cache.ts +189 -0
- package/src/virtual-modules.d.ts +11 -0
- package/src/widgets/index.ts +57 -54
- package/dist/api-Cs7DAACP.mjs.map +0 -1
- package/dist/apply-BWMV4Zmw.mjs.map +0 -1
- package/dist/byline-fields-BNy7Ng1U.d.mts.map +0 -1
- package/dist/content-CyqOmOzm.mjs.map +0 -1
- package/dist/cron-DZovZUnC.mjs.map +0 -1
- package/dist/index-CjKdMZ3U.d.mts.map +0 -1
- package/dist/loader-Dyx8dhFV.mjs.map +0 -1
- package/dist/manifest-schema-Cj-YrzrF.mjs.map +0 -1
- package/dist/menus-BKkxXCmd.mjs.map +0 -1
- package/dist/query-Ctlq1aOk.mjs.map +0 -1
- package/dist/redirects-C0L9JUk4.mjs.map +0 -1
- package/dist/runner-pt6Wl-l-.mjs.map +0 -1
- package/dist/seo-DfjLvu8i.mjs.map +0 -1
- package/dist/settings-b5zW1R1T.mjs +0 -235
- package/dist/settings-b5zW1R1T.mjs.map +0 -1
- package/dist/taxonomies-Crtzy4MT.mjs.map +0 -1
- package/dist/types-Cj2S6FuC.mjs +0 -3
- package/dist/types-D8bhH891.mjs.map +0 -1
- package/dist/types-DbCWhHet.d.mts.map +0 -1
- package/dist/types-i8_uzhMD.d.mts.map +0 -1
- package/dist/user-DzEUl5zA.mjs.map +0 -1
- package/dist/validate-Dy6nkNls.d.mts.map +0 -1
- package/dist/validate-JCXcsqiY.mjs.map +0 -1
- package/dist/version-CnS-Cr8A.mjs +0 -7
- package/dist/widgets-Bap1eS1X.mjs.map +0 -1
- package/src/plugins/scheduler/piggyback.ts +0 -71
- /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 ??
|
|
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,
|
|
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:
|
|
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 ??
|
|
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;
|
package/src/seed/validate.ts
CHANGED
|
@@ -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 ?
|
|
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
|
+
}
|
package/src/settings/index.ts
CHANGED
|
@@ -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
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
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
|
|
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
|
|
48
|
+
(g[SITE_SETTINGS_CACHE_KEY] as IsolateCache<Partial<SiteSettings>> | undefined) ??
|
|
49
49
|
(() => {
|
|
50
|
-
const
|
|
51
|
-
g[SITE_SETTINGS_CACHE_KEY] =
|
|
52
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
/**
|
package/src/taxonomies/index.ts
CHANGED
|
@@ -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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/src/virtual-modules.d.ts
CHANGED
|
@@ -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.
|