emdash 0.17.2 → 0.18.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/api/route-utils.mjs +10 -10
- package/dist/api/schemas/index.d.mts +1 -1
- package/dist/{api-B7GATEYo.mjs → api-Cs7DAACP.mjs} +12 -12
- package/dist/{api-B7GATEYo.mjs.map → api-Cs7DAACP.mjs.map} +1 -1
- package/dist/{apply-BrVqULFe.mjs → apply-BWMV4Zmw.mjs} +16 -16
- package/dist/{apply-BrVqULFe.mjs.map → apply-BWMV4Zmw.mjs.map} +1 -1
- package/dist/astro/index.d.mts +2 -2
- package/dist/astro/index.mjs +1 -1
- package/dist/astro/middleware/auth.d.mts +2 -2
- package/dist/astro/middleware/auth.mjs +2 -2
- package/dist/astro/middleware/redirect.mjs +4 -4
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +250 -83
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +3 -3
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs +3 -3
- package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +2 -2
- package/dist/astro/routes/api/admin/api-tokens/index.mjs +3 -3
- package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +3 -3
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +4 -4
- package/dist/astro/routes/api/admin/byline-fields/index.mjs +4 -4
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +4 -4
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +8 -8
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +8 -8
- package/dist/astro/routes/api/admin/bylines/index.mjs +8 -8
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs +7 -7
- package/dist/astro/routes/api/admin/comments/_id_.mjs +5 -5
- package/dist/astro/routes/api/admin/comments/bulk.mjs +6 -6
- package/dist/astro/routes/api/admin/comments/counts.mjs +5 -5
- package/dist/astro/routes/api/admin/comments/index.mjs +6 -6
- package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +4 -4
- package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
- package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +3 -3
- package/dist/astro/routes/api/admin/oauth-clients/index.mjs +3 -3
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +25 -25
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +25 -25
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +25 -25
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +25 -25
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +25 -25
- package/dist/astro/routes/api/admin/plugins/index.mjs +25 -25
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +25 -25
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +25 -25
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +25 -25
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +25 -25
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +26 -26
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +25 -25
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs +26 -26
- package/dist/astro/routes/api/admin/plugins/updates.mjs +25 -25
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +25 -25
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +3 -3
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +25 -25
- package/dist/astro/routes/api/admin/users/_id_/disable.mjs +2 -2
- package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
- package/dist/astro/routes/api/admin/users/_id_/index.mjs +3 -3
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +2 -2
- package/dist/astro/routes/api/admin/users/index.mjs +3 -3
- package/dist/astro/routes/api/auth/dev-bypass.mjs +3 -3
- package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
- package/dist/astro/routes/api/auth/invite/complete.mjs +3 -3
- package/dist/astro/routes/api/auth/invite/index.mjs +3 -3
- package/dist/astro/routes/api/auth/invite/register-options.mjs +3 -3
- package/dist/astro/routes/api/auth/logout.mjs +2 -2
- package/dist/astro/routes/api/auth/magic-link/send.mjs +4 -4
- package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
- package/dist/astro/routes/api/auth/me.mjs +4 -4
- package/dist/astro/routes/api/auth/passkey/_id_.mjs +3 -3
- package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
- package/dist/astro/routes/api/auth/passkey/options.mjs +4 -4
- package/dist/astro/routes/api/auth/passkey/register/options.mjs +3 -3
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs +3 -3
- package/dist/astro/routes/api/auth/passkey/verify.mjs +3 -3
- package/dist/astro/routes/api/auth/signup/complete.mjs +3 -3
- package/dist/astro/routes/api/auth/signup/request.mjs +4 -4
- package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +6 -6
- package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +3 -3
- 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 +4 -4
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +4 -4
- 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.mjs +4 -4
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +8 -8
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_.mjs +4 -4
- package/dist/astro/routes/api/content/_collection_/index.mjs +4 -4
- package/dist/astro/routes/api/content/_collection_/trash.mjs +4 -4
- 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.mjs +4 -4
- package/dist/astro/routes/api/import/wordpress/analyze.mjs +3 -3
- package/dist/astro/routes/api/import/wordpress/execute.d.mts +2 -2
- package/dist/astro/routes/api/import/wordpress/execute.mjs +7 -7
- package/dist/astro/routes/api/import/wordpress/media.mjs +4 -4
- package/dist/astro/routes/api/import/wordpress/prepare.mjs +6 -6
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +5 -5
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +4 -4
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +5 -5
- package/dist/astro/routes/api/manifest.mjs +3 -3
- package/dist/astro/routes/api/mcp.mjs +26 -26
- package/dist/astro/routes/api/media/_id_/confirm.mjs +4 -4
- package/dist/astro/routes/api/media/_id_.mjs +4 -4
- 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 +4 -4
- package/dist/astro/routes/api/media.mjs +5 -5
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +5 -5
- package/dist/astro/routes/api/menus/_name_/items.mjs +5 -5
- package/dist/astro/routes/api/menus/_name_/reorder.mjs +5 -5
- package/dist/astro/routes/api/menus/_name_/translations.mjs +5 -5
- package/dist/astro/routes/api/menus/_name_.mjs +5 -5
- package/dist/astro/routes/api/menus/index.mjs +5 -5
- package/dist/astro/routes/api/oauth/device/authorize.mjs +3 -3
- package/dist/astro/routes/api/oauth/device/code.mjs +4 -4
- package/dist/astro/routes/api/oauth/device/token.mjs +4 -4
- package/dist/astro/routes/api/oauth/register.mjs +2 -2
- package/dist/astro/routes/api/oauth/token/refresh.mjs +3 -3
- package/dist/astro/routes/api/oauth/token/revoke.mjs +3 -3
- package/dist/astro/routes/api/oauth/token.mjs +2 -2
- package/dist/astro/routes/api/openapi.json.mjs +2 -2
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +3 -3
- package/dist/astro/routes/api/redirects/404s/index.mjs +6 -6
- package/dist/astro/routes/api/redirects/404s/summary.mjs +6 -6
- package/dist/astro/routes/api/redirects/_id_.mjs +7 -7
- package/dist/astro/routes/api/redirects/index.mjs +7 -7
- 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 +25 -25
- package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +25 -25
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +25 -25
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +25 -25
- package/dist/astro/routes/api/schema/collections/index.mjs +25 -25
- package/dist/astro/routes/api/schema/index.mjs +6 -6
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +25 -25
- package/dist/astro/routes/api/schema/orphans/index.mjs +25 -25
- package/dist/astro/routes/api/search/enable.mjs +7 -7
- package/dist/astro/routes/api/search/index.mjs +6 -6
- package/dist/astro/routes/api/search/rebuild.mjs +7 -7
- package/dist/astro/routes/api/search/stats.mjs +6 -6
- package/dist/astro/routes/api/search/suggest.mjs +6 -6
- package/dist/astro/routes/api/sections/_slug_.mjs +6 -6
- package/dist/astro/routes/api/sections/index.mjs +6 -6
- package/dist/astro/routes/api/settings/email.mjs +4 -4
- package/dist/astro/routes/api/settings.mjs +8 -8
- package/dist/astro/routes/api/setup/admin-verify.mjs +3 -3
- package/dist/astro/routes/api/setup/admin.mjs +3 -3
- package/dist/astro/routes/api/setup/dev-bypass.mjs +15 -15
- package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
- package/dist/astro/routes/api/setup/index.mjs +16 -16
- package/dist/astro/routes/api/setup/status.mjs +3 -3
- package/dist/astro/routes/api/snapshot.mjs +3 -3
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +8 -8
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +8 -8
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +8 -8
- package/dist/astro/routes/api/taxonomies/index.mjs +8 -8
- package/dist/astro/routes/api/themes/preview.mjs +3 -3
- package/dist/astro/routes/api/typegen.mjs +5 -5
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +4 -4
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +6 -6
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +6 -6
- package/dist/astro/routes/api/widget-areas/_name_.mjs +5 -5
- package/dist/astro/routes/api/widget-areas/index.mjs +6 -6
- package/dist/astro/routes/api/widget-components.mjs +2 -2
- package/dist/astro/routes/robots.txt.mjs +4 -4
- package/dist/astro/routes/sitemap-_collection_.xml.mjs +4 -4
- package/dist/astro/routes/sitemap.xml.mjs +4 -4
- package/dist/astro/types.d.mts +2 -2
- package/dist/{authorize-CLTmOUyx.mjs → authorize-CotM4Yiu.mjs} +2 -2
- package/dist/{authorize-CLTmOUyx.mjs.map → authorize-CotM4Yiu.mjs.map} +1 -1
- package/dist/{byline-CAhk4FrG.mjs → byline-CWQ9aSoz.mjs} +3 -3
- package/dist/{byline-CAhk4FrG.mjs.map → byline-CWQ9aSoz.mjs.map} +1 -1
- package/dist/{byline-fields-CR5hGLMw.d.mts → byline-fields-BNy7Ng1U.d.mts} +28 -28
- package/dist/{byline-fields-CR5hGLMw.d.mts.map → byline-fields-BNy7Ng1U.d.mts.map} +1 -1
- package/dist/{bylines-DCczH3AV.mjs → bylines-BJSva1Un.mjs} +4 -4
- package/dist/{bylines-DCczH3AV.mjs.map → bylines-BJSva1Un.mjs.map} +1 -1
- package/dist/{bylines-CbrD7STW.mjs → bylines-LJMgENMI.mjs} +3 -3
- package/dist/{bylines-CbrD7STW.mjs.map → bylines-LJMgENMI.mjs.map} +1 -1
- package/dist/{cache-DIHHyPkt.mjs → cache-lZL7SgVb.mjs} +2 -2
- package/dist/{cache-DIHHyPkt.mjs.map → cache-lZL7SgVb.mjs.map} +1 -1
- package/dist/{chunks-DnnHlRG3.mjs → chunks-BU-vP9Dh.mjs} +2 -2
- package/dist/{chunks-DnnHlRG3.mjs.map → chunks-BU-vP9Dh.mjs.map} +1 -1
- package/dist/cli/index.mjs +13 -13
- package/dist/{comment-DkAfGX9E.mjs → comment-C4jVbCM8.mjs} +2 -2
- package/dist/{comment-DkAfGX9E.mjs.map → comment-C4jVbCM8.mjs.map} +1 -1
- package/dist/{comments-DLFnXs7J.mjs → comments-BTAbC0Ek.mjs} +3 -3
- package/dist/{comments-DLFnXs7J.mjs.map → comments-BTAbC0Ek.mjs.map} +1 -1
- package/dist/{content-C7aJ7keg.mjs → content-CyqOmOzm.mjs} +3 -3
- package/dist/{content-C7aJ7keg.mjs.map → content-CyqOmOzm.mjs.map} +1 -1
- package/dist/{context-Ca0HkaIh.mjs → context-DZ7bEh5-.mjs} +7 -7
- package/dist/{context-Ca0HkaIh.mjs.map → context-DZ7bEh5-.mjs.map} +1 -1
- package/dist/{dashboard-BrfLIsX1.mjs → dashboard-B5WQpNTP.mjs} +4 -4
- package/dist/{dashboard-BrfLIsX1.mjs.map → dashboard-B5WQpNTP.mjs.map} +1 -1
- package/dist/db/index.mjs +1 -1
- package/dist/{error-Bk9s3Ism.mjs → error-DJOsMVSt.mjs} +2 -2
- package/dist/{error-Bk9s3Ism.mjs.map → error-DJOsMVSt.mjs.map} +1 -1
- package/dist/{fts-manager-XpDfbIKo.mjs → fts-manager-DR1ERA0c.mjs} +2 -2
- package/dist/{fts-manager-XpDfbIKo.mjs.map → fts-manager-DR1ERA0c.mjs.map} +1 -1
- package/dist/{index-C8ciqSMJ.d.mts → index-CjKdMZ3U.d.mts} +4 -4
- package/dist/{index-C8ciqSMJ.d.mts.map → index-CjKdMZ3U.d.mts.map} +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +34 -34
- package/dist/{load-CF5oETkh.mjs → load-6ZrRhepW.mjs} +2 -2
- package/dist/{load-CF5oETkh.mjs.map → load-6ZrRhepW.mjs.map} +1 -1
- package/dist/{loader-BxyvbrZP.mjs → loader-Dyx8dhFV.mjs} +3 -3
- package/dist/{loader-BxyvbrZP.mjs.map → loader-Dyx8dhFV.mjs.map} +1 -1
- package/dist/media/local-runtime.d.mts +2 -2
- package/dist/media/local-runtime.mjs +4 -4
- package/dist/{media-Cyz5BhSN.mjs → media-C-oovGCG.mjs} +2 -2
- package/dist/{media-Cyz5BhSN.mjs.map → media-C-oovGCG.mjs.map} +1 -1
- package/dist/{menus-CIdZ_Q6U.mjs → menus-BKkxXCmd.mjs} +30 -11
- package/dist/menus-BKkxXCmd.mjs.map +1 -0
- package/dist/{menus-PFp8FDuO.mjs → menus-DugoYwTX.mjs} +2 -2
- package/dist/{menus-PFp8FDuO.mjs.map → menus-DugoYwTX.mjs.map} +1 -1
- package/dist/{parse-B-K21lvm.mjs → parse-BBkFmLVr.mjs} +2 -2
- package/dist/{parse-B-K21lvm.mjs.map → parse-BBkFmLVr.mjs.map} +1 -1
- package/dist/plugin-utils.d.mts +2 -2
- package/dist/plugins/adapt-sandbox-entry.d.mts +2 -2
- package/dist/{query-Cc649nDl.mjs → query-Ctlq1aOk.mjs} +10 -10
- package/dist/{query-Cc649nDl.mjs.map → query-Ctlq1aOk.mjs.map} +1 -1
- package/dist/{rate-limit-BI1OdpQH.mjs → rate-limit-CH6W6ikK.mjs} +2 -2
- package/dist/{rate-limit-BI1OdpQH.mjs.map → rate-limit-CH6W6ikK.mjs.map} +1 -1
- package/dist/{redirect-C-FeA4j9.mjs → redirect-C6tJA7tk.mjs} +2 -2
- package/dist/{redirect-C-FeA4j9.mjs.map → redirect-C6tJA7tk.mjs.map} +1 -1
- package/dist/{redirects-C1UgU9E0.mjs → redirects-CacE9eQa.mjs} +3 -3
- package/dist/{redirects-C1UgU9E0.mjs.map → redirects-CacE9eQa.mjs.map} +1 -1
- package/dist/{registry-C-T_PWgp.mjs → registry-CIDxZbhh.mjs} +4 -4
- package/dist/{registry-C-T_PWgp.mjs.map → registry-CIDxZbhh.mjs.map} +1 -1
- package/dist/runner-DM1yR5qd.d.mts.map +1 -1
- package/dist/{runner-BiuUfx-V.mjs → runner-pt6Wl-l-.mjs} +8 -3
- package/dist/{runner-BiuUfx-V.mjs.map → runner-pt6Wl-l-.mjs.map} +1 -1
- package/dist/runtime.d.mts +2 -2
- package/dist/runtime.mjs +2 -2
- package/dist/{schema-BpCJh2lU.mjs → schema-B4tk0HAG.mjs} +4 -4
- package/dist/{schema-BpCJh2lU.mjs.map → schema-B4tk0HAG.mjs.map} +1 -1
- package/dist/{search-BrF7k0Ho.mjs → search-f-fNfwab.mjs} +4 -4
- package/dist/{search-BrF7k0Ho.mjs.map → search-f-fNfwab.mjs.map} +1 -1
- package/dist/{sections-8DEa-dWt.mjs → sections-biElLfT9.mjs} +3 -3
- package/dist/{sections-8DEa-dWt.mjs.map → sections-biElLfT9.mjs.map} +1 -1
- package/dist/seed/index.mjs +13 -13
- package/dist/{seo-CKr7pLfA.mjs → seo-BR39kvTF.mjs} +2 -2
- package/dist/{seo-CKr7pLfA.mjs.map → seo-BR39kvTF.mjs.map} +1 -1
- package/dist/{service-9P2cdyR_.mjs → service-BhR2acnc.mjs} +2 -2
- package/dist/{service-9P2cdyR_.mjs.map → service-BhR2acnc.mjs.map} +1 -1
- package/dist/{settings-DYVzINdn.mjs → settings-D_NJvjgN.mjs} +3 -3
- package/dist/{settings-DYVzINdn.mjs.map → settings-D_NJvjgN.mjs.map} +1 -1
- package/dist/{settings-Jro4YcUb.mjs → settings-b5zW1R1T.mjs} +3 -3
- package/dist/{settings-Jro4YcUb.mjs.map → settings-b5zW1R1T.mjs.map} +1 -1
- package/dist/{taxonomies-CGD6y79Q.mjs → taxonomies-Crtzy4MT.mjs} +8 -7
- package/dist/taxonomies-Crtzy4MT.mjs.map +1 -0
- package/dist/{taxonomies-C0bVme_m.mjs → taxonomies-Mhn9rjTQ.mjs} +4 -4
- package/dist/{taxonomies-C0bVme_m.mjs.map → taxonomies-Mhn9rjTQ.mjs.map} +1 -1
- package/dist/{taxonomy-Db5xwphL.mjs → taxonomy-DTZrIQpi.mjs} +3 -3
- package/dist/{taxonomy-Db5xwphL.mjs.map → taxonomy-DTZrIQpi.mjs.map} +1 -1
- package/dist/{types-CfyYQ7eY.mjs → types-K3MDsxpy.mjs} +2 -2
- package/dist/{types-CfyYQ7eY.mjs.map → types-K3MDsxpy.mjs.map} +1 -1
- package/dist/{user-tLdHUEXV.mjs → user-DzEUl5zA.mjs} +2 -2
- package/dist/{user-tLdHUEXV.mjs.map → user-DzEUl5zA.mjs.map} +1 -1
- package/dist/{validate-DWmnRg6E.mjs → validate-JCXcsqiY.mjs} +2 -2
- package/dist/{validate-DWmnRg6E.mjs.map → validate-JCXcsqiY.mjs.map} +1 -1
- package/dist/{validation-BQ_TP-On.mjs → validation-Bq-VyKJg.mjs} +5 -5
- package/dist/{validation-BQ_TP-On.mjs.map → validation-Bq-VyKJg.mjs.map} +1 -1
- package/dist/version-CnS-Cr8A.mjs +7 -0
- package/dist/{version-CgcnMvqS.mjs.map → version-CnS-Cr8A.mjs.map} +1 -1
- package/dist/{widgets-DzlINGI6.mjs → widgets-Bap1eS1X.mjs} +2 -2
- package/dist/{widgets-DzlINGI6.mjs.map → widgets-Bap1eS1X.mjs.map} +1 -1
- package/dist/{zod-generator-MMm56Prt.mjs → zod-generator-BSDpkqSH.mjs} +4 -3
- package/dist/zod-generator-BSDpkqSH.mjs.map +1 -0
- package/package.json +7 -7
- package/src/astro/middleware/stream-end-metrics.ts +96 -0
- package/src/astro/middleware.ts +80 -32
- package/src/components/EmDashImage.astro +1 -0
- package/src/database/migrations/runner.ts +7 -2
- package/src/emdash-runtime.ts +177 -126
- package/src/menus/index.ts +27 -9
- package/src/plugins/hooks.ts +35 -6
- package/src/plugins/manager.ts +1 -0
- package/src/schema/zod-generator.ts +6 -2
- package/src/taxonomies/index.ts +12 -8
- package/src/utils/init-lock.ts +143 -0
- package/dist/menus-CIdZ_Q6U.mjs.map +0 -1
- package/dist/taxonomies-CGD6y79Q.mjs.map +0 -1
- package/dist/version-CgcnMvqS.mjs +0 -7
- package/dist/zod-generator-MMm56Prt.mjs.map +0 -1
package/src/astro/middleware.ts
CHANGED
|
@@ -27,12 +27,14 @@ import { sandboxedPlugins as virtualSandboxedPlugins } from "virtual:emdash/sand
|
|
|
27
27
|
// @ts-ignore - virtual module
|
|
28
28
|
import { createStorage as virtualCreateStorage } from "virtual:emdash/storage";
|
|
29
29
|
|
|
30
|
+
import { after } from "../after.js";
|
|
30
31
|
import {
|
|
31
32
|
createRecorder,
|
|
32
33
|
flushRecorder,
|
|
33
34
|
isInstrumentationEnabled,
|
|
34
35
|
} from "../database/instrumentation.js";
|
|
35
36
|
import {
|
|
37
|
+
DB_INIT_DEADLINE_MS,
|
|
36
38
|
EmDashRuntime,
|
|
37
39
|
type RuntimeDependencies,
|
|
38
40
|
type SandboxedPluginEntry,
|
|
@@ -51,17 +53,20 @@ import {
|
|
|
51
53
|
runWithContext,
|
|
52
54
|
} from "../request-context.js";
|
|
53
55
|
import { isMissingTableError } from "../utils/db-errors.js";
|
|
56
|
+
import { createInitLock, type InitLock, initWithLock } from "../utils/init-lock.js";
|
|
54
57
|
import type { EmDashConfig } from "./integration/runtime.js";
|
|
58
|
+
import { wrapBodyForStreamMetrics } from "./middleware/stream-end-metrics.js";
|
|
55
59
|
import { createPublicPluginApiRouteHandler } from "./public-plugin-api-routes.js";
|
|
56
60
|
import type { EmDashHandlers } from "./types.js";
|
|
57
61
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
let
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Runtime init lock reclaim deadline. Must be strictly larger than the db
|
|
64
|
+
* init deadline: this lock wraps EmDashRuntime.create() → getDatabase() →
|
|
65
|
+
* the db init lock, and equal deadlines would let this outer lock reclaim
|
|
66
|
+
* (spawning a second cron scheduler and sandbox runner) while the inner db
|
|
67
|
+
* init is legitimately still working through a contended migration.
|
|
68
|
+
*/
|
|
69
|
+
const RUNTIME_INIT_DEADLINE_MS = DB_INIT_DEADLINE_MS + 15_000;
|
|
65
70
|
|
|
66
71
|
/**
|
|
67
72
|
* Whether we've verified the database has been set up.
|
|
@@ -89,6 +94,32 @@ function markSetupVerified(): void {
|
|
|
89
94
|
setupFlagStore[SETUP_VERIFIED_KEY] = true;
|
|
90
95
|
}
|
|
91
96
|
|
|
97
|
+
/**
|
|
98
|
+
* The runtime singleton and its init lock live on globalThis behind a
|
|
99
|
+
* Symbol — same reasoning as SETUP_VERIFIED_KEY above: the bundler can
|
|
100
|
+
* duplicate this module across SSR chunks, and a duplicated instance/lock
|
|
101
|
+
* would mean multiple runtimes (each with its own cron scheduler) per
|
|
102
|
+
* isolate, initializing and reclaiming independently.
|
|
103
|
+
*/
|
|
104
|
+
const RUNTIME_HOLDER_KEY = Symbol.for("emdash:runtime-holder");
|
|
105
|
+
interface RuntimeHolder {
|
|
106
|
+
instance: EmDashRuntime | null;
|
|
107
|
+
lock: InitLock;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function getRuntimeHolder(): RuntimeHolder {
|
|
111
|
+
// eslint-disable-next-line typescript/no-unsafe-type-assertion -- globalThis symbol slot, written only below
|
|
112
|
+
let holder = setupFlagStore[RUNTIME_HOLDER_KEY] as RuntimeHolder | undefined;
|
|
113
|
+
if (!holder) {
|
|
114
|
+
holder = { instance: null, lock: createInitLock() };
|
|
115
|
+
setupFlagStore[RUNTIME_HOLDER_KEY] = holder;
|
|
116
|
+
}
|
|
117
|
+
return holder;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Whether i18n config has been initialized from the virtual module */
|
|
121
|
+
let i18nInitialized = false;
|
|
122
|
+
|
|
92
123
|
/**
|
|
93
124
|
* Get EmDash configuration from virtual module
|
|
94
125
|
*/
|
|
@@ -176,29 +207,40 @@ async function getRuntime(
|
|
|
176
207
|
config: EmDashConfig,
|
|
177
208
|
initTimings?: Array<{ name: string; dur: number; desc?: string }>,
|
|
178
209
|
): Promise<EmDashRuntime> {
|
|
179
|
-
//
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
210
|
+
// Waiters poll rather than awaiting the initializing request's promise —
|
|
211
|
+
// workerd flags cross-request promise resolution (warnings + potential
|
|
212
|
+
// hangs). If the initializing request is cancelled mid-create (client
|
|
213
|
+
// disconnect tears down its continuation, skipping any `finally`), the
|
|
214
|
+
// anchored init keeps running under waitUntil and populates the cache;
|
|
215
|
+
// failing that, the stale lock is reclaimed after a deadline instead of
|
|
216
|
+
// hanging every subsequent request in the isolate until eviction.
|
|
217
|
+
const holder = getRuntimeHolder();
|
|
218
|
+
return initWithLock(
|
|
219
|
+
holder.lock,
|
|
220
|
+
() => holder.instance,
|
|
221
|
+
async (isCurrentClaim) => {
|
|
222
|
+
const deps = buildDependencies(config);
|
|
223
|
+
const runtime = await EmDashRuntime.create(deps, initTimings);
|
|
224
|
+
if (isCurrentClaim()) {
|
|
225
|
+
holder.instance = runtime;
|
|
226
|
+
} else {
|
|
227
|
+
// This init was reclaimed mid-flight (it ran past the deadline
|
|
228
|
+
// and a waiter started its own). Don't overwrite the
|
|
229
|
+
// reclaimer's published runtime, and stop this one's cron
|
|
230
|
+
// scheduler so it doesn't keep firing unreferenced. The
|
|
231
|
+
// runtime is still returned — it's fully functional for the
|
|
232
|
+
// request that built it.
|
|
233
|
+
runtime.stopCron().catch((error: unknown) => {
|
|
234
|
+
console.error("[emdash] failed to stop superseded runtime's cron:", error);
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
return runtime;
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
deadlineMs: RUNTIME_INIT_DEADLINE_MS,
|
|
241
|
+
anchor: (promise) => after(() => promise),
|
|
242
|
+
},
|
|
243
|
+
);
|
|
202
244
|
}
|
|
203
245
|
|
|
204
246
|
/**
|
|
@@ -429,7 +471,10 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
|
|
429
471
|
timings.push({ name: "render", dur: performance.now() - t0, desc: "Page render" });
|
|
430
472
|
timings.push({ name: "mw", dur: performance.now() - mwStart, desc: "Total middleware" });
|
|
431
473
|
pushMetricsTimings(timings, metrics);
|
|
432
|
-
|
|
474
|
+
// Server-Timing only sees pre-stream queries; the stream-end
|
|
475
|
+
// wrapper (instrumentation-gated, no-op otherwise) emits the
|
|
476
|
+
// final counters once the body finishes streaming.
|
|
477
|
+
return wrapBodyForStreamMetrics(finalizeResponse(response, timings));
|
|
433
478
|
};
|
|
434
479
|
if (anonScoped) {
|
|
435
480
|
const parent = getRequestContext();
|
|
@@ -596,7 +641,10 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
|
|
596
641
|
timings.push({ name: "render", dur: performance.now() - t0, desc: "Page render" });
|
|
597
642
|
timings.push({ name: "mw", dur: performance.now() - mwStart, desc: "Total middleware" });
|
|
598
643
|
pushMetricsTimings(timings, metrics);
|
|
599
|
-
|
|
644
|
+
// Server-Timing only sees pre-stream queries; the stream-end
|
|
645
|
+
// wrapper (instrumentation-gated, no-op otherwise) emits the
|
|
646
|
+
// final counters once the body finishes streaming.
|
|
647
|
+
return wrapBodyForStreamMetrics(finalizeResponse(response, timings));
|
|
600
648
|
};
|
|
601
649
|
|
|
602
650
|
if (scoped) {
|
|
@@ -170,6 +170,7 @@ const imgProps: Record<string, unknown> = {
|
|
|
170
170
|
height: finalHeight,
|
|
171
171
|
alt: finalAlt,
|
|
172
172
|
loading: priority ? "eager" : "lazy",
|
|
173
|
+
fetchpriority: priority ? "high" : undefined,
|
|
173
174
|
decoding: "async",
|
|
174
175
|
style: placeholderStyle ? `${baseStyle} ${placeholderStyle}` : baseStyle,
|
|
175
176
|
...attrs,
|
|
@@ -178,8 +178,13 @@ const MIGRATION_RACE_PATTERN = new RegExp(
|
|
|
178
178
|
"i",
|
|
179
179
|
);
|
|
180
180
|
|
|
181
|
-
/**
|
|
182
|
-
|
|
181
|
+
/**
|
|
182
|
+
* How long to wait for a concurrent migrator to finish before giving up.
|
|
183
|
+
* Exported because the db init lock's reclaim deadline must comfortably
|
|
184
|
+
* exceed it (see DB_INIT_DEADLINE_MS in emdash-runtime.ts) — a healthy
|
|
185
|
+
* init can legitimately block this long inside waitForConcurrentMigrator.
|
|
186
|
+
*/
|
|
187
|
+
export const MIGRATION_RACE_WAIT_MS = 10_000;
|
|
183
188
|
/** Polling interval while waiting for a concurrent migrator. */
|
|
184
189
|
const MIGRATION_RACE_POLL_MS = 100;
|
|
185
190
|
|
package/src/emdash-runtime.ts
CHANGED
|
@@ -22,7 +22,7 @@ import { getAuthMode } from "./auth/mode.js";
|
|
|
22
22
|
import { getTrustedProxyHeaders } from "./auth/trusted-proxy.js";
|
|
23
23
|
import { isSqlite } from "./database/dialect-helpers.js";
|
|
24
24
|
import { kyselyLogOption } from "./database/instrumentation.js";
|
|
25
|
-
import { runMigrations } from "./database/migrations/runner.js";
|
|
25
|
+
import { MIGRATION_RACE_WAIT_MS, runMigrations } from "./database/migrations/runner.js";
|
|
26
26
|
import { RevisionRepository } from "./database/repositories/revision.js";
|
|
27
27
|
import type { ContentItem as ContentItemInternal } from "./database/repositories/types.js";
|
|
28
28
|
import { validateIdentifier } from "./database/validate.js";
|
|
@@ -41,6 +41,7 @@ import type {
|
|
|
41
41
|
} from "./plugins/types.js";
|
|
42
42
|
import type { FieldType } from "./schema/types.js";
|
|
43
43
|
import { hashString } from "./utils/hash.js";
|
|
44
|
+
import { createInitLock, type InitLock, initWithLock } from "./utils/init-lock.js";
|
|
44
45
|
import { COMMIT, VERSION } from "./version.js";
|
|
45
46
|
|
|
46
47
|
const LEADING_SLASH_PATTERN = /^\//;
|
|
@@ -310,9 +311,38 @@ function contentItemToRecord(item: ContentItemInternal): Record<string, unknown>
|
|
|
310
311
|
return { ...item };
|
|
311
312
|
}
|
|
312
313
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
314
|
+
/**
|
|
315
|
+
* Db init lock reclaim deadline. Derived from the migration race wait so
|
|
316
|
+
* they can't drift apart: a healthy init can legitimately block for the
|
|
317
|
+
* full MIGRATION_RACE_WAIT_MS inside waitForConcurrentMigrator, plus cold
|
|
318
|
+
* connect and migrator work, before it should be presumed dead. The outer
|
|
319
|
+
* runtime init lock (middleware.ts) must use a strictly larger deadline —
|
|
320
|
+
* it wraps create() → getDatabase() → this lock, and equal deadlines would
|
|
321
|
+
* let the outer reclaim while the inner is legitimately still working.
|
|
322
|
+
*/
|
|
323
|
+
export const DB_INIT_DEADLINE_MS = MIGRATION_RACE_WAIT_MS + 20_000;
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Db cache + its init lock live on globalThis behind a Symbol: the bundler
|
|
327
|
+
* can duplicate this module across SSR chunks (same reasoning as
|
|
328
|
+
* request-cache.ts), and a duplicated cache/lock would mean concurrent
|
|
329
|
+
* independent db inits — and duplicate migrators — per isolate.
|
|
330
|
+
*/
|
|
331
|
+
const DB_HOLDER_KEY = Symbol.for("emdash:db-cache");
|
|
332
|
+
interface DbHolder {
|
|
333
|
+
cache: Map<string, Kysely<Database>>;
|
|
334
|
+
lock: InitLock;
|
|
335
|
+
}
|
|
336
|
+
const globalSymbolStore = globalThis as Record<symbol, unknown>;
|
|
337
|
+
function getDbHolder(): DbHolder {
|
|
338
|
+
// eslint-disable-next-line typescript/no-unsafe-type-assertion -- globalThis symbol slot, written only below
|
|
339
|
+
let holder = globalSymbolStore[DB_HOLDER_KEY] as DbHolder | undefined;
|
|
340
|
+
if (!holder) {
|
|
341
|
+
holder = { cache: new Map<string, Kysely<Database>>(), lock: createInitLock() };
|
|
342
|
+
globalSymbolStore[DB_HOLDER_KEY] = holder;
|
|
343
|
+
}
|
|
344
|
+
return holder;
|
|
345
|
+
}
|
|
316
346
|
const storageCache = new Map<string, Storage>();
|
|
317
347
|
const sandboxedPluginCache = new Map<string, SandboxedPluginInstance>();
|
|
318
348
|
/**
|
|
@@ -887,19 +917,45 @@ export class EmDashRuntime {
|
|
|
887
917
|
// Initialize storage (sync)
|
|
888
918
|
const storage = EmDashRuntime.getStorage(deps);
|
|
889
919
|
|
|
890
|
-
// Fetch plugin states
|
|
920
|
+
// Fetch plugin states and site info concurrently — independent reads
|
|
921
|
+
// against different tables (_plugin_state vs options), so they share
|
|
922
|
+
// one round-trip window instead of paying two sequential ones. Each
|
|
923
|
+
// phase() wrapper still records that phase's own duration, and each
|
|
924
|
+
// body keeps its own non-fatal catch.
|
|
891
925
|
let pluginStates: Map<string, string> = new Map();
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
926
|
+
let siteInfo: { siteName?: string; siteUrl?: string; locale?: string } | undefined;
|
|
927
|
+
await Promise.all([
|
|
928
|
+
// Fetch plugin states from database
|
|
929
|
+
phase("rt.plugins", "Plugin states", async () => {
|
|
930
|
+
try {
|
|
931
|
+
const states = await db
|
|
932
|
+
.selectFrom("_plugin_state")
|
|
933
|
+
.select(["plugin_id", "status"])
|
|
934
|
+
.execute();
|
|
935
|
+
pluginStates = new Map(states.map((s) => [s.plugin_id, s.status]));
|
|
936
|
+
} catch {
|
|
937
|
+
// Plugin state table may not exist yet
|
|
938
|
+
}
|
|
939
|
+
}),
|
|
940
|
+
// Load site info for plugin context extensions (1 batch query instead of 3)
|
|
941
|
+
phase("rt.site", "Site info options", async () => {
|
|
942
|
+
try {
|
|
943
|
+
const optionsRepo = new OptionsRepository(db);
|
|
944
|
+
const siteOpts = await optionsRepo.getMany<string>([
|
|
945
|
+
"emdash:site_title",
|
|
946
|
+
"emdash:site_url",
|
|
947
|
+
"emdash:locale",
|
|
948
|
+
]);
|
|
949
|
+
siteInfo = {
|
|
950
|
+
siteName: siteOpts.get("emdash:site_title") ?? undefined,
|
|
951
|
+
siteUrl: siteOpts.get("emdash:site_url") ?? undefined,
|
|
952
|
+
locale: siteOpts.get("emdash:locale") ?? undefined,
|
|
953
|
+
};
|
|
954
|
+
} catch {
|
|
955
|
+
// Options table may not exist yet (pre-setup)
|
|
956
|
+
}
|
|
957
|
+
}),
|
|
958
|
+
]);
|
|
903
959
|
|
|
904
960
|
// Build set of enabled plugins
|
|
905
961
|
const enabledPlugins = new Set<string>();
|
|
@@ -910,26 +966,6 @@ export class EmDashRuntime {
|
|
|
910
966
|
}
|
|
911
967
|
}
|
|
912
968
|
|
|
913
|
-
// Load site info for plugin context extensions (1 batch query instead of 3)
|
|
914
|
-
let siteInfo: { siteName?: string; siteUrl?: string; locale?: string } | undefined;
|
|
915
|
-
await phase("rt.site", "Site info options", async () => {
|
|
916
|
-
try {
|
|
917
|
-
const optionsRepo = new OptionsRepository(db);
|
|
918
|
-
const siteOpts = await optionsRepo.getMany<string>([
|
|
919
|
-
"emdash:site_title",
|
|
920
|
-
"emdash:site_url",
|
|
921
|
-
"emdash:locale",
|
|
922
|
-
]);
|
|
923
|
-
siteInfo = {
|
|
924
|
-
siteName: siteOpts.get("emdash:site_title") ?? undefined,
|
|
925
|
-
siteUrl: siteOpts.get("emdash:site_url") ?? undefined,
|
|
926
|
-
locale: siteOpts.get("emdash:locale") ?? undefined,
|
|
927
|
-
};
|
|
928
|
-
} catch {
|
|
929
|
-
// Options table may not exist yet (pre-setup)
|
|
930
|
-
}
|
|
931
|
-
});
|
|
932
|
-
|
|
933
969
|
// Build the full list of pipeline-eligible plugins: all configured
|
|
934
970
|
// plugins (regardless of current enabled status) plus built-in plugins.
|
|
935
971
|
// rebuildHookPipeline() filters this to only enabled plugins.
|
|
@@ -1050,32 +1086,43 @@ export class EmDashRuntime {
|
|
|
1050
1086
|
EmDashRuntime.loadSandboxedPlugins(deps, db, storage),
|
|
1051
1087
|
);
|
|
1052
1088
|
|
|
1053
|
-
// Cold-start: load marketplace-installed plugins from
|
|
1054
|
-
// the sandbox runner.
|
|
1089
|
+
// Cold-start: load marketplace- and registry-installed plugins from
|
|
1090
|
+
// site R2 via the sandbox runner. The two tiers only depend on the
|
|
1091
|
+
// sandbox phase above, not on each other, so when both are enabled
|
|
1092
|
+
// they run concurrently instead of paying two sequential loads.
|
|
1093
|
+
// In bypass mode marketplace plugins were already handled above.
|
|
1094
|
+
const installedTierPhases: Promise<void>[] = [];
|
|
1055
1095
|
if (deps.config.marketplace && storage && !deps.sandboxBypassed) {
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1096
|
+
installedTierPhases.push(
|
|
1097
|
+
phase("rt.market", "Marketplace plugins", () =>
|
|
1098
|
+
EmDashRuntime.loadInstalledSandboxedPlugins(
|
|
1099
|
+
"marketplace",
|
|
1100
|
+
db,
|
|
1101
|
+
storage,
|
|
1102
|
+
deps,
|
|
1103
|
+
sandboxedPlugins,
|
|
1104
|
+
),
|
|
1063
1105
|
),
|
|
1064
1106
|
);
|
|
1065
1107
|
}
|
|
1066
1108
|
|
|
1067
1109
|
// Cold-start: load registry-installed plugins from site R2
|
|
1068
1110
|
if (deps.config.experimental?.registry && storage) {
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1111
|
+
installedTierPhases.push(
|
|
1112
|
+
phase("rt.registry", "Registry plugins", () =>
|
|
1113
|
+
EmDashRuntime.loadInstalledSandboxedPlugins(
|
|
1114
|
+
"registry",
|
|
1115
|
+
db,
|
|
1116
|
+
storage,
|
|
1117
|
+
deps,
|
|
1118
|
+
sandboxedPlugins,
|
|
1119
|
+
),
|
|
1076
1120
|
),
|
|
1077
1121
|
);
|
|
1078
1122
|
}
|
|
1123
|
+
if (installedTierPhases.length > 0) {
|
|
1124
|
+
await Promise.all(installedTierPhases);
|
|
1125
|
+
}
|
|
1079
1126
|
|
|
1080
1127
|
// Initialize media providers
|
|
1081
1128
|
const mediaProviders = new Map<string, MediaProvider>();
|
|
@@ -1270,83 +1317,86 @@ export class EmDashRuntime {
|
|
|
1270
1317
|
|
|
1271
1318
|
const cacheKey = dbConfig.entrypoint;
|
|
1272
1319
|
|
|
1273
|
-
//
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
//
|
|
1280
|
-
//
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
console.log("Auto-seeded default collections");
|
|
1320
|
+
// Waiters poll the cache rather than sharing the initializing request's
|
|
1321
|
+
// promise: if the request that owns the init is cancelled mid-await
|
|
1322
|
+
// (e.g. client disconnect during cold migrations), a shared promise
|
|
1323
|
+
// never settles — and the owner's `finally` that would clear it never
|
|
1324
|
+
// runs — deadlocking every later request in the isolate. Prevention:
|
|
1325
|
+
// the in-flight init is anchored via after()/waitUntil so a cancelled
|
|
1326
|
+
// owner's init still completes and populates the cache. Net: a stale
|
|
1327
|
+
// lock is reclaimed after a deadline.
|
|
1328
|
+
const holder = getDbHolder();
|
|
1329
|
+
return initWithLock(
|
|
1330
|
+
holder.lock,
|
|
1331
|
+
() => holder.cache.get(cacheKey),
|
|
1332
|
+
async (isCurrentClaim) => {
|
|
1333
|
+
const dialect = deps.createDialect(dbConfig.config);
|
|
1334
|
+
const db = new Kysely<Database>({ dialect, log: kyselyLogOption() });
|
|
1335
|
+
|
|
1336
|
+
await runMigrations(db);
|
|
1337
|
+
|
|
1338
|
+
// Note: legacy installs may carry a stray `emdash:manifest_cache`
|
|
1339
|
+
// row in the options table from versions that persisted a JSON
|
|
1340
|
+
// manifest. The runtime no longer reads or writes it. We do not
|
|
1341
|
+
// proactively delete it: the row is a few hundred bytes of dead
|
|
1342
|
+
// weight and is never on the read path, whereas a one-shot
|
|
1343
|
+
// cleanup-flag check costs an extra `options.get()` on every
|
|
1344
|
+
// isolate cold boot forever. Cheaper to leave it.
|
|
1345
|
+
|
|
1346
|
+
// Auto-seed schema if no collections exist and setup hasn't run.
|
|
1347
|
+
// This covers first-load on sites that skip the setup wizard.
|
|
1348
|
+
// Dev-bypass and the wizard apply seeds explicitly.
|
|
1349
|
+
try {
|
|
1350
|
+
const [collectionCount, setupOption] = await Promise.all([
|
|
1351
|
+
db
|
|
1352
|
+
.selectFrom("_emdash_collections")
|
|
1353
|
+
.select((eb) => eb.fn.countAll<number>().as("count"))
|
|
1354
|
+
.executeTakeFirstOrThrow(),
|
|
1355
|
+
db
|
|
1356
|
+
.selectFrom("options")
|
|
1357
|
+
.select("value")
|
|
1358
|
+
.where("name", "=", "emdash:setup_complete")
|
|
1359
|
+
.executeTakeFirst(),
|
|
1360
|
+
]);
|
|
1361
|
+
|
|
1362
|
+
const setupDone = (() => {
|
|
1363
|
+
try {
|
|
1364
|
+
return setupOption && JSON.parse(setupOption.value) === true;
|
|
1365
|
+
} catch {
|
|
1366
|
+
return false;
|
|
1367
|
+
}
|
|
1368
|
+
})();
|
|
1369
|
+
|
|
1370
|
+
if (collectionCount.count === 0 && !setupDone) {
|
|
1371
|
+
const { applySeed } = await import("./seed/apply.js");
|
|
1372
|
+
const { loadSeed } = await import("./seed/load.js");
|
|
1373
|
+
const { validateSeed } = await import("./seed/validate.js");
|
|
1374
|
+
|
|
1375
|
+
const seed = await loadSeed();
|
|
1376
|
+
const validation = validateSeed(seed);
|
|
1377
|
+
if (validation.valid) {
|
|
1378
|
+
await applySeed(db, seed, { onConflict: "skip" });
|
|
1379
|
+
console.log("Auto-seeded default collections");
|
|
1380
|
+
}
|
|
1335
1381
|
}
|
|
1382
|
+
} catch {
|
|
1383
|
+
// Tables may not exist yet. Non-fatal.
|
|
1336
1384
|
}
|
|
1337
|
-
} catch {
|
|
1338
|
-
// Tables may not exist yet. Non-fatal.
|
|
1339
|
-
}
|
|
1340
1385
|
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1386
|
+
// Publish only while still the current owner: a reclaimed slow
|
|
1387
|
+
// init must not flip the cached Kysely identity back after the
|
|
1388
|
+
// reclaimer has published its own. The unpublished instance is
|
|
1389
|
+
// still returned and fully valid for the request that built it.
|
|
1390
|
+
if (isCurrentClaim()) {
|
|
1391
|
+
holder.cache.set(cacheKey, db);
|
|
1392
|
+
}
|
|
1393
|
+
return db;
|
|
1394
|
+
},
|
|
1395
|
+
{
|
|
1396
|
+
deadlineMs: DB_INIT_DEADLINE_MS,
|
|
1397
|
+
anchor: (promise) => after(() => promise),
|
|
1398
|
+
},
|
|
1399
|
+
);
|
|
1350
1400
|
}
|
|
1351
1401
|
|
|
1352
1402
|
/**
|
|
@@ -1778,6 +1828,7 @@ export class EmDashRuntime {
|
|
|
1778
1828
|
pipeline,
|
|
1779
1829
|
isActive: () => true,
|
|
1780
1830
|
getOption: (key) => optionsRepo.get<string>(key),
|
|
1831
|
+
getOptions: (keys) => optionsRepo.getMany<string>(keys),
|
|
1781
1832
|
setOption: (key, value) => optionsRepo.set(key, value),
|
|
1782
1833
|
deleteOption: async (key) => {
|
|
1783
1834
|
await optionsRepo.delete(key);
|
package/src/menus/index.ts
CHANGED
|
@@ -136,15 +136,10 @@ async function buildMenuTree(
|
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
const urlPatterns =
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
.select(["slug", "url_pattern"])
|
|
144
|
-
.where("slug", "in", [...collectionSlugs])
|
|
145
|
-
.execute();
|
|
146
|
-
for (const row of rows) urlPatterns.set(row.slug, row.url_pattern);
|
|
147
|
-
}
|
|
139
|
+
const urlPatterns =
|
|
140
|
+
collectionSlugs.size > 0
|
|
141
|
+
? await getCollectionUrlPatterns(db, collectionSlugs)
|
|
142
|
+
: new Map<string, string | null>();
|
|
148
143
|
|
|
149
144
|
const resolvedItems = await Promise.all(
|
|
150
145
|
items.map((item) => resolveMenuItem(item, db, urlPatterns, locale)),
|
|
@@ -173,6 +168,29 @@ async function buildMenuTree(
|
|
|
173
168
|
return rootItems;
|
|
174
169
|
}
|
|
175
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Look up the `url_pattern` for a set of collection slugs, request-cached so
|
|
173
|
+
* a page rendering several menus (header, footer, ...) only pays for the
|
|
174
|
+
* lookup once per distinct slug set. Callers must treat the returned map as
|
|
175
|
+
* read-only — it is shared across cache hits within the request.
|
|
176
|
+
*/
|
|
177
|
+
function getCollectionUrlPatterns(
|
|
178
|
+
db: Kysely<Database>,
|
|
179
|
+
collectionSlugs: Set<string>,
|
|
180
|
+
): Promise<Map<string, string | null>> {
|
|
181
|
+
const key = `menu-collection-patterns:${[...collectionSlugs].toSorted().join(",")}`;
|
|
182
|
+
return requestCached(key, async () => {
|
|
183
|
+
const rows = await db
|
|
184
|
+
.selectFrom("_emdash_collections")
|
|
185
|
+
.select(["slug", "url_pattern"])
|
|
186
|
+
.where("slug", "in", [...collectionSlugs])
|
|
187
|
+
.execute();
|
|
188
|
+
const urlPatterns = new Map<string, string | null>();
|
|
189
|
+
for (const row of rows) urlPatterns.set(row.slug, row.url_pattern);
|
|
190
|
+
return urlPatterns;
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
176
194
|
/**
|
|
177
195
|
* Resolve a single menu item's URL. `reference_id` is a translation_group
|
|
178
196
|
* (migration 036 remapped all existing references); we join it against
|