emdash 0.19.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 +5 -5
- package/dist/api/schemas/index.d.mts +1 -1
- package/dist/api/schemas/index.mjs +2 -2
- package/dist/{api-BZ6bhjYs.mjs → api-DStv36ik.mjs} +36 -5
- 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-hQkKKBCf.mjs → apply-Dr7snAMT.mjs} +7 -7
- package/dist/{apply-hQkKKBCf.mjs.map → apply-Dr7snAMT.mjs.map} +1 -1
- package/dist/astro/index.d.mts +10 -10
- package/dist/astro/index.mjs +3 -3
- package/dist/astro/middleware/auth.d.mts +9 -9
- package/dist/astro/middleware/auth.mjs +4 -4
- package/dist/astro/middleware/redirect.mjs +1 -1
- package/dist/astro/middleware/request-context.mjs +1 -1
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.d.mts +1 -1
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +63 -112
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +2 -2
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs +2 -2
- package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +2 -2
- package/dist/astro/routes/api/admin/api-tokens/index.mjs +2 -2
- package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +2 -2
- 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 +6 -6
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +6 -6
- package/dist/astro/routes/api/admin/bylines/index.mjs +6 -6
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs +6 -6
- package/dist/astro/routes/api/admin/comments/_id_.mjs +2 -2
- package/dist/astro/routes/api/admin/comments/bulk.mjs +4 -4
- package/dist/astro/routes/api/admin/comments/counts.mjs +2 -2
- package/dist/astro/routes/api/admin/comments/index.mjs +4 -4
- package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +1 -1
- package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +1 -1
- package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +1 -1
- package/dist/astro/routes/api/admin/oauth-clients/index.mjs +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +14 -14
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +14 -14
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +14 -14
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +14 -14
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +14 -14
- package/dist/astro/routes/api/admin/plugins/index.mjs +14 -14
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +14 -14
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +14 -14
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +14 -14
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +14 -14
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +15 -15
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +14 -14
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs +15 -15
- package/dist/astro/routes/api/admin/plugins/updates.mjs +14 -14
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +14 -14
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +1 -1
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +14 -14
- package/dist/astro/routes/api/admin/users/_id_/index.mjs +2 -2
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +1 -1
- package/dist/astro/routes/api/admin/users/index.mjs +2 -2
- package/dist/astro/routes/api/auth/dev-bypass.mjs +2 -2
- package/dist/astro/routes/api/auth/invite/complete.mjs +6 -6
- package/dist/astro/routes/api/auth/invite/index.mjs +3 -3
- package/dist/astro/routes/api/auth/invite/register-options.mjs +5 -5
- package/dist/astro/routes/api/auth/logout.mjs +1 -1
- package/dist/astro/routes/api/auth/magic-link/send.mjs +4 -4
- package/dist/astro/routes/api/auth/magic-link/verify.mjs +1 -1
- package/dist/astro/routes/api/auth/me.mjs +2 -2
- package/dist/astro/routes/api/auth/mode.mjs +1 -1
- package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +3 -3
- package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
- package/dist/astro/routes/api/auth/passkey/_id_.mjs +2 -2
- package/dist/astro/routes/api/auth/passkey/options.mjs +6 -6
- package/dist/astro/routes/api/auth/passkey/register/options.mjs +5 -5
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs +6 -6
- package/dist/astro/routes/api/auth/passkey/verify.mjs +6 -6
- package/dist/astro/routes/api/auth/signup/complete.mjs +6 -6
- package/dist/astro/routes/api/auth/signup/request.mjs +4 -4
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +6 -6
- package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +4 -4
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +5 -5
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/authors.mjs +1 -1
- package/dist/astro/routes/api/content/_collection_/index.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/trash.mjs +3 -3
- package/dist/astro/routes/api/dashboard.mjs +1 -1
- package/dist/astro/routes/api/import/probe.d.mts +3 -3
- package/dist/astro/routes/api/import/probe.mjs +3 -3
- package/dist/astro/routes/api/import/wordpress/analyze.mjs +1 -1
- package/dist/astro/routes/api/import/wordpress/execute.d.mts +9 -9
- package/dist/astro/routes/api/import/wordpress/execute.mjs +3 -3
- package/dist/astro/routes/api/import/wordpress/media.mjs +3 -3
- package/dist/astro/routes/api/import/wordpress/prepare.mjs +3 -3
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +3 -3
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +3 -3
- package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +3 -3
- package/dist/astro/routes/api/manifest.mjs +2 -2
- package/dist/astro/routes/api/mcp.mjs +18 -18
- package/dist/astro/routes/api/media/_id_/confirm.mjs +3 -3
- package/dist/astro/routes/api/media/_id_.mjs +3 -3
- package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +1 -1
- package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +1 -1
- package/dist/astro/routes/api/media/providers/index.mjs +1 -1
- package/dist/astro/routes/api/media/upload-url.mjs +4 -4
- package/dist/astro/routes/api/media.mjs +4 -4
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +3 -3
- package/dist/astro/routes/api/menus/_name_/items.mjs +3 -3
- package/dist/astro/routes/api/menus/_name_/reorder.mjs +3 -3
- package/dist/astro/routes/api/menus/_name_/translations.mjs +3 -3
- package/dist/astro/routes/api/menus/_name_.mjs +3 -3
- package/dist/astro/routes/api/menus/index.mjs +3 -3
- package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
- package/dist/astro/routes/api/oauth/device/authorize.mjs +3 -3
- package/dist/astro/routes/api/oauth/device/code.mjs +5 -5
- package/dist/astro/routes/api/oauth/device/token.mjs +4 -4
- package/dist/astro/routes/api/oauth/register.mjs +1 -1
- 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 +4 -4
- package/dist/astro/routes/api/openapi.json.mjs +1 -1
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +2 -2
- package/dist/astro/routes/api/redirects/404s/index.mjs +4 -4
- package/dist/astro/routes/api/redirects/404s/summary.mjs +4 -4
- package/dist/astro/routes/api/redirects/_id_.mjs +4 -4
- package/dist/astro/routes/api/redirects/index.mjs +4 -4
- package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +1 -1
- package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +14 -14
- package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +14 -14
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +14 -14
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +14 -14
- package/dist/astro/routes/api/schema/collections/index.mjs +14 -14
- package/dist/astro/routes/api/schema/index.mjs +5 -10
- package/dist/astro/routes/api/schema/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +14 -14
- package/dist/astro/routes/api/schema/orphans/index.mjs +14 -14
- package/dist/astro/routes/api/search/enable.mjs +5 -5
- package/dist/astro/routes/api/search/index.mjs +4 -4
- package/dist/astro/routes/api/search/rebuild.mjs +5 -5
- package/dist/astro/routes/api/search/stats.mjs +3 -3
- package/dist/astro/routes/api/search/suggest.mjs +4 -4
- package/dist/astro/routes/api/sections/_slug_.mjs +5 -5
- package/dist/astro/routes/api/sections/index.mjs +5 -5
- package/dist/astro/routes/api/settings/email.mjs +1 -1
- package/dist/astro/routes/api/settings.mjs +6 -6
- package/dist/astro/routes/api/setup/admin-verify.mjs +7 -7
- package/dist/astro/routes/api/setup/admin.mjs +6 -6
- package/dist/astro/routes/api/setup/dev-bypass.mjs +10 -10
- package/dist/astro/routes/api/setup/index.mjs +9 -9
- package/dist/astro/routes/api/setup/status.mjs +2 -2
- package/dist/astro/routes/api/snapshot.mjs +3 -3
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +6 -6
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +6 -6
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +6 -6
- package/dist/astro/routes/api/taxonomies/index.mjs +6 -6
- package/dist/astro/routes/api/themes/preview.mjs +3 -3
- package/dist/astro/routes/api/well-known/auth.mjs +1 -1
- 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 +3 -3
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +6 -5
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +6 -5
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_.mjs +4 -3
- package/dist/astro/routes/api/widget-areas/_name_.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/index.mjs +6 -5
- package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
- package/dist/astro/routes/api/widget-components.mjs +1 -1
- package/dist/astro/routes/robots.txt.mjs +3 -3
- package/dist/astro/routes/sitemap-_collection_.xml.d.mts.map +1 -1
- package/dist/astro/routes/sitemap-_collection_.xml.mjs +12 -5
- package/dist/astro/routes/sitemap-_collection_.xml.mjs.map +1 -1
- package/dist/astro/routes/sitemap.xml.mjs +4 -4
- package/dist/astro/types.d.mts +12 -12
- package/dist/auth/providers/github.d.mts +1 -1
- package/dist/auth/providers/google.d.mts +1 -1
- package/dist/{authorize-C_8t2KGa.mjs → authorize-DsMSVSaY.mjs} +1 -1
- package/dist/{authorize-C_8t2KGa.mjs.map → authorize-DsMSVSaY.mjs.map} +1 -1
- package/dist/{byline-fields-C_OsR-KF.mjs → byline-fields--WxSNS79.mjs} +1 -1
- package/dist/{byline-fields-C_OsR-KF.mjs.map → byline-fields--WxSNS79.mjs.map} +1 -1
- package/dist/{byline-fields-51kg6Vuv.mjs → byline-fields-8TMtkBnH.mjs} +2 -2
- package/dist/{byline-fields-51kg6Vuv.mjs.map → byline-fields-8TMtkBnH.mjs.map} +1 -1
- package/dist/{byline-fields-DYXKDuNX.d.mts → byline-fields-DbibsvTl.d.mts} +5 -1
- package/dist/byline-fields-DbibsvTl.d.mts.map +1 -0
- package/dist/{bylines-Cx5n-WqP.mjs → bylines-BdxWCnPL.mjs} +1 -1
- package/dist/{bylines-Cx5n-WqP.mjs.map → bylines-BdxWCnPL.mjs.map} +1 -1
- package/dist/{bylines-wurS258E.mjs → bylines-s8c2DXbH.mjs} +3 -3
- package/dist/{bylines-wurS258E.mjs.map → bylines-s8c2DXbH.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/cli/index.mjs +11 -10
- 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/{comments-CJ0RZsYR.mjs → comments-Vkivawyl.mjs} +1 -1
- package/dist/{comments-CJ0RZsYR.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/{context-GG52SPgh.mjs → context-Y7BRkWes.mjs} +2 -2
- package/dist/{context-GG52SPgh.mjs.map → context-Y7BRkWes.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/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-s6_q3T7A.mjs → device-flow-R23SIbQ2.mjs} +4 -4
- package/dist/{device-flow-s6_q3T7A.mjs.map → device-flow-R23SIbQ2.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/{index-FfiTQJq2.d.mts → index-B1keaX5Y.d.mts} +43 -12
- package/dist/{index-FfiTQJq2.d.mts.map → index-B1keaX5Y.d.mts.map} +1 -1
- package/dist/{index-BpYeJO1E.d.mts → index-DR56od45.d.mts} +3 -3
- package/dist/{index-BpYeJO1E.d.mts.map → index-DR56od45.d.mts.map} +1 -1
- package/dist/index.d.mts +16 -16
- package/dist/index.mjs +22 -22
- package/dist/{load-B84ohfBk.mjs → load-BBetCvLC.mjs} +1 -1
- package/dist/{load-B84ohfBk.mjs.map → load-BBetCvLC.mjs.map} +1 -1
- package/dist/{loader-CpZKpFz0.mjs → loader-ZN1ll-d-.mjs} +11 -14
- 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/local-runtime.d.mts +11 -11
- package/dist/media/local-runtime.mjs +2 -2
- 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-Dp9xporj.mjs → menus-DrQLusqj.mjs} +6 -33
- 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/{oauth-authorization-1aPAYjiC.mjs → oauth-authorization-Bw4NdF_S.mjs} +4 -4
- package/dist/{oauth-authorization-1aPAYjiC.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-D4MnavW_.d.mts → options-DyYIYpPd.d.mts} +3 -3
- package/dist/{options-D4MnavW_.d.mts.map → options-DyYIYpPd.d.mts.map} +1 -1
- package/dist/page/index.d.mts +2 -2
- 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/{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-BFQ029Ts.mjs → query-CbUcI4Xk.mjs} +18 -8
- package/dist/query-CbUcI4Xk.mjs.map +1 -0
- package/dist/{rate-limit-ClFFUga6.mjs → rate-limit-C7hjdkS5.mjs} +1 -1
- package/dist/{rate-limit-ClFFUga6.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/{redirects-DEygMrRO.mjs → redirects-CCbCqCCd.mjs} +4 -2
- package/dist/redirects-CCbCqCCd.mjs.map +1 -0
- package/dist/{redirects-OIu6vQ2i.mjs → redirects-DxVoR7PI.mjs} +1 -1
- package/dist/{redirects-OIu6vQ2i.mjs.map → redirects-DxVoR7PI.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-BcRuXq_h.d.mts → runner-DTdhuI9i.d.mts} +2 -2
- package/dist/{runner-BcRuXq_h.d.mts.map → runner-DTdhuI9i.d.mts.map} +1 -1
- package/dist/runtime.d.mts +10 -10
- package/dist/runtime.mjs +1 -1
- package/dist/{schema-CS7Eg5gh.mjs → schema-C1E70ug_.mjs} +2 -2
- package/dist/{schema-CS7Eg5gh.mjs.map → schema-C1E70ug_.mjs.map} +1 -1
- package/dist/{search-o-aQzHI1.mjs → search-B3SGZw91.mjs} +2 -2
- package/dist/{search-o-aQzHI1.mjs.map → search-B3SGZw91.mjs.map} +1 -1
- package/dist/{secrets-C_ZtRos3.mjs → secrets-ChPTmy9x.mjs} +1 -1
- package/dist/{secrets-C_ZtRos3.mjs.map → secrets-ChPTmy9x.mjs.map} +1 -1
- package/dist/{sections-DhsZ0ns9.mjs → sections-D_lVzwRZ.mjs} +2 -2
- package/dist/{sections-DhsZ0ns9.mjs.map → sections-D_lVzwRZ.mjs.map} +1 -1
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +6 -6
- 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-DfjLvu8i.mjs → seo-D_LPkOtu.mjs} +4 -3
- package/dist/seo-D_LPkOtu.mjs.map +1 -0
- package/dist/{service-DAxg8RPR.mjs → service-ChDcsTBs.mjs} +2 -2
- package/dist/{service-DAxg8RPR.mjs.map → service-ChDcsTBs.mjs.map} +1 -1
- package/dist/{settings-DIsbHTRE.mjs → settings-Cv47v9u8.mjs} +2 -2
- package/dist/{settings-DIsbHTRE.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-Yuv78yua.mjs → setup-complete-yvPE4OsP.mjs} +1 -1
- package/dist/{setup-complete-Yuv78yua.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-mEVmwIFi.mjs → site-url-CnHlmAs9.mjs} +1 -1
- package/dist/{site-url-mEVmwIFi.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-UusDXv3C.mjs → taxonomies-BILwiyGk.mjs} +2 -2
- package/dist/{taxonomies-UusDXv3C.mjs.map → taxonomies-BILwiyGk.mjs.map} +1 -1
- package/dist/{taxonomies-BEW7S5AI.mjs → taxonomies-BdAmbOwx.mjs} +46 -9
- package/dist/taxonomies-BdAmbOwx.mjs.map +1 -0
- package/dist/{transport-BwQeeY2p.d.mts → transport-B7PPP2CC.d.mts} +1 -1
- package/dist/{transport-BwQeeY2p.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-OT_Es5mp.d.mts → types-BPzXTV9x.d.mts} +1 -1
- package/dist/{types-OT_Es5mp.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-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-WVmpZBJV.d.mts → types-DTniiNto.d.mts} +2 -2
- package/dist/{types-WVmpZBJV.d.mts.map → types-DTniiNto.d.mts.map} +1 -1
- package/dist/types-DZk_y-MU.mjs.map +1 -1
- 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/{validate-ZP9Dvg0P.mjs → validate-Bz4vqcX1.mjs} +1 -1
- package/dist/{validate-ZP9Dvg0P.mjs.map → validate-Bz4vqcX1.mjs.map} +1 -1
- package/dist/{validate-BPAHUSge.d.mts → validate-CNwkPWzz.d.mts} +5 -5
- package/dist/{validate-BPAHUSge.d.mts.map → validate-CNwkPWzz.d.mts.map} +1 -1
- package/dist/{validation-CE5i4q0c.mjs → validation-DgGTJm3u.mjs} +1 -1
- package/dist/{validation-CE5i4q0c.mjs.map → validation-DgGTJm3u.mjs.map} +1 -1
- package/dist/version-D-5txk2m.mjs +7 -0
- package/dist/{version-Dw0JXu45.mjs.map → version-D-5txk2m.mjs.map} +1 -1
- package/dist/{widgets-ClEnYQCH.mjs → widgets-DZfmAbE4.mjs} +47 -44
- package/dist/widgets-DZfmAbE4.mjs.map +1 -0
- package/package.json +10 -10
- 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/schemas/schema.ts +13 -1
- package/src/astro/middleware.ts +20 -6
- package/src/astro/routes/api/schema/index.ts +7 -15
- package/src/astro/routes/sitemap-[collection].xml.ts +13 -2
- package/src/cli/commands/bundle-utils.ts +2 -0
- package/src/cli/commands/secrets.ts +2 -2
- package/src/database/instrumentation.ts +13 -0
- package/src/emdash-runtime.ts +31 -25
- package/src/loader.ts +24 -15
- package/src/plugins/manifest-schema.ts +75 -0
- package/src/plugins/marketplace.ts +2 -5
- package/src/plugins/types.ts +12 -0
- package/src/query.ts +13 -2
- package/src/request-context.ts +8 -0
- package/src/schema/types.ts +11 -1
- 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 +78 -12
- package/src/utils/isolate-cache.ts +189 -0
- package/src/widgets/index.ts +57 -54
- package/dist/api-BZ6bhjYs.mjs.map +0 -1
- package/dist/byline-fields-DYXKDuNX.d.mts.map +0 -1
- package/dist/loader-CpZKpFz0.mjs.map +0 -1
- package/dist/manifest-schema-Cj-YrzrF.mjs.map +0 -1
- package/dist/menus-Dp9xporj.mjs.map +0 -1
- package/dist/query-BFQ029Ts.mjs.map +0 -1
- package/dist/redirects-DEygMrRO.mjs.map +0 -1
- package/dist/seo-DfjLvu8i.mjs.map +0 -1
- package/dist/settings-B1p-gPUK.mjs +0 -235
- package/dist/settings-B1p-gPUK.mjs.map +0 -1
- package/dist/taxonomies-BEW7S5AI.mjs.map +0 -1
- package/dist/types-Cj2S6FuC.mjs +0 -3
- package/dist/types-DbCWhHet.d.mts.map +0 -1
- package/dist/version-Dw0JXu45.mjs +0 -7
- package/dist/widgets-ClEnYQCH.mjs.map +0 -1
- /package/dist/{api-tokens-B6VgoE6M.mjs → api-tokens-Oq39ba-Z.mjs} +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { r as
|
|
2
|
-
import {
|
|
1
|
+
import { r as requestCached } from "./request-cache-D32LpnmI.mjs";
|
|
2
|
+
import { r as getDb } from "./loader-ZN1ll-d-.mjs";
|
|
3
|
+
import { t as getWidgetComponents$1 } from "./components-CK0cuUoH.mjs";
|
|
3
4
|
|
|
4
5
|
//#region src/widgets/index.ts
|
|
5
6
|
/**
|
|
@@ -10,48 +11,50 @@ import { t as getWidgetComponents$1 } from "./components-CTfpu3PZ.mjs";
|
|
|
10
11
|
* row with null widget columns, which we skip when mapping.
|
|
11
12
|
*/
|
|
12
13
|
async function getWidgetArea(name) {
|
|
13
|
-
|
|
14
|
-
"a.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
14
|
+
return requestCached(`widget-area:${name}`, async () => {
|
|
15
|
+
const rows = await (await getDb()).selectFrom("_emdash_widget_areas as a").leftJoin("_emdash_widgets as w", "w.area_id", "a.id").select([
|
|
16
|
+
"a.id as a_id",
|
|
17
|
+
"a.name as a_name",
|
|
18
|
+
"a.label as a_label",
|
|
19
|
+
"a.description as a_description",
|
|
20
|
+
"w.id as w_id",
|
|
21
|
+
"w.type as w_type",
|
|
22
|
+
"w.title as w_title",
|
|
23
|
+
"w.content as w_content",
|
|
24
|
+
"w.menu_name as w_menu_name",
|
|
25
|
+
"w.component_id as w_component_id",
|
|
26
|
+
"w.component_props as w_component_props",
|
|
27
|
+
"w.area_id as w_area_id",
|
|
28
|
+
"w.sort_order as w_sort_order",
|
|
29
|
+
"w.created_at as w_created_at"
|
|
30
|
+
]).where("a.name", "=", name).orderBy("w.sort_order", "asc").execute();
|
|
31
|
+
const first = rows[0];
|
|
32
|
+
if (!first) return null;
|
|
33
|
+
const widgets = [];
|
|
34
|
+
for (const row of rows) {
|
|
35
|
+
if (row.w_id === null) continue;
|
|
36
|
+
const widgetRow = {
|
|
37
|
+
id: row.w_id,
|
|
38
|
+
type: row.w_type,
|
|
39
|
+
title: row.w_title,
|
|
40
|
+
content: row.w_content,
|
|
41
|
+
menu_name: row.w_menu_name,
|
|
42
|
+
component_id: row.w_component_id,
|
|
43
|
+
component_props: row.w_component_props,
|
|
44
|
+
area_id: row.w_area_id,
|
|
45
|
+
sort_order: row.w_sort_order,
|
|
46
|
+
created_at: row.w_created_at
|
|
47
|
+
};
|
|
48
|
+
widgets.push(rowToWidget(widgetRow));
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
id: first.a_id,
|
|
52
|
+
name: first.a_name,
|
|
53
|
+
label: first.a_label,
|
|
54
|
+
description: first.a_description ?? void 0,
|
|
55
|
+
widgets
|
|
45
56
|
};
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
return {
|
|
49
|
-
id: first.a_id,
|
|
50
|
-
name: first.a_name,
|
|
51
|
-
label: first.a_label,
|
|
52
|
-
description: first.a_description ?? void 0,
|
|
53
|
-
widgets
|
|
54
|
-
};
|
|
57
|
+
});
|
|
55
58
|
}
|
|
56
59
|
/**
|
|
57
60
|
* Get all widget areas with their widgets
|
|
@@ -103,4 +106,4 @@ function rowToWidget(row) {
|
|
|
103
106
|
|
|
104
107
|
//#endregion
|
|
105
108
|
export { rowToWidget as i, getWidgetAreas as n, getWidgetComponents as r, getWidgetArea as t };
|
|
106
|
-
//# sourceMappingURL=widgets-
|
|
109
|
+
//# sourceMappingURL=widgets-DZfmAbE4.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"widgets-DZfmAbE4.mjs","names":["getComponentRegistry"],"sources":["../src/widgets/index.ts"],"sourcesContent":["import { getDb } from \"../loader.js\";\nimport { requestCached } from \"../request-cache.js\";\nimport { getWidgetComponents as getComponentRegistry } from \"./components.js\";\nimport type { Widget, WidgetArea, WidgetRow, WidgetComponentDef } from \"./types.js\";\n\nexport type {\n\tWidget,\n\tWidgetArea,\n\tWidgetType,\n\tWidgetComponentDef,\n\tPropDef,\n\tCreateWidgetAreaInput,\n\tCreateWidgetInput,\n\tUpdateWidgetInput,\n\tReorderWidgetsInput,\n} from \"./types.js\";\n\n/**\n * Get a widget area by name, with all its widgets.\n *\n * Single query with a left join rather than area-then-widgets so the\n * common case costs one round-trip. An area with no widgets yields one\n * row with null widget columns, which we skip when mapping.\n */\nexport async function getWidgetArea(name: string): Promise<WidgetArea | null> {\n\treturn requestCached(`widget-area:${name}`, async () => {\n\t\tconst db = await getDb();\n\t\tconst rows = await db\n\t\t\t.selectFrom(\"_emdash_widget_areas as a\")\n\t\t\t.leftJoin(\"_emdash_widgets as w\", \"w.area_id\", \"a.id\")\n\t\t\t.select([\n\t\t\t\t\"a.id as a_id\",\n\t\t\t\t\"a.name as a_name\",\n\t\t\t\t\"a.label as a_label\",\n\t\t\t\t\"a.description as a_description\",\n\t\t\t\t\"w.id as w_id\",\n\t\t\t\t\"w.type as w_type\",\n\t\t\t\t\"w.title as w_title\",\n\t\t\t\t\"w.content as w_content\",\n\t\t\t\t\"w.menu_name as w_menu_name\",\n\t\t\t\t\"w.component_id as w_component_id\",\n\t\t\t\t\"w.component_props as w_component_props\",\n\t\t\t\t\"w.area_id as w_area_id\",\n\t\t\t\t\"w.sort_order as w_sort_order\",\n\t\t\t\t\"w.created_at as w_created_at\",\n\t\t\t])\n\t\t\t.where(\"a.name\", \"=\", name)\n\t\t\t.orderBy(\"w.sort_order\", \"asc\")\n\t\t\t.execute();\n\n\t\tconst first = rows[0];\n\t\tif (!first) return null;\n\t\tconst widgets: Widget[] = [];\n\t\tfor (const row of rows) {\n\t\t\tif (row.w_id === null) continue; // area has no widgets (left-join null row)\n\t\t\t// Left-join makes every w_* column nullable in the type; at runtime\n\t\t\t// they're all non-null once w_id is (we match on widgets.area_id, so\n\t\t\t// a widget row always has the not-null columns filled). Cast is the\n\t\t\t// price of that structural fact.\n\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- left-join row is non-null when w_id is set; see above\n\t\t\tconst widgetRow = {\n\t\t\t\tid: row.w_id,\n\t\t\t\ttype: row.w_type,\n\t\t\t\ttitle: row.w_title,\n\t\t\t\tcontent: row.w_content,\n\t\t\t\tmenu_name: row.w_menu_name,\n\t\t\t\tcomponent_id: row.w_component_id,\n\t\t\t\tcomponent_props: row.w_component_props,\n\t\t\t\tarea_id: row.w_area_id,\n\t\t\t\tsort_order: row.w_sort_order,\n\t\t\t\tcreated_at: row.w_created_at,\n\t\t\t} as WidgetRow;\n\t\t\twidgets.push(rowToWidget(widgetRow));\n\t\t}\n\n\t\treturn {\n\t\t\tid: first.a_id,\n\t\t\tname: first.a_name,\n\t\t\tlabel: first.a_label,\n\t\t\tdescription: first.a_description ?? undefined,\n\t\t\twidgets,\n\t\t};\n\t});\n}\n\n/**\n * Get all widget areas with their widgets\n */\nexport async function getWidgetAreas(): Promise<WidgetArea[]> {\n\tconst db = await getDb();\n\t// Get all areas\n\tconst areaRows = await db.selectFrom(\"_emdash_widget_areas\").selectAll().execute();\n\n\t// Get all widgets\n\tconst widgetRows = await db\n\t\t.selectFrom(\"_emdash_widgets\")\n\t\t.selectAll()\n\t\t.$castTo<WidgetRow>()\n\t\t.orderBy(\"sort_order\", \"asc\")\n\t\t.execute();\n\n\t// Group widgets by area\n\tconst widgetsByArea = new Map<string, Widget[]>();\n\tfor (const row of widgetRows) {\n\t\tif (!widgetsByArea.has(row.area_id)) {\n\t\t\twidgetsByArea.set(row.area_id, []);\n\t\t}\n\t\twidgetsByArea.get(row.area_id)!.push(rowToWidget(row));\n\t}\n\n\t// Combine\n\treturn areaRows.map((areaRow) => ({\n\t\tid: areaRow.id,\n\t\tname: areaRow.name,\n\t\tlabel: areaRow.label,\n\t\tdescription: areaRow.description ?? undefined,\n\t\twidgets: widgetsByArea.get(areaRow.id) || [],\n\t}));\n}\n\n/**\n * Get available widget components (for admin UI)\n */\nexport function getWidgetComponents(): WidgetComponentDef[] {\n\treturn getComponentRegistry();\n}\n\n/**\n * Convert a widget row to the API type\n */\nexport function rowToWidget(row: WidgetRow): Widget {\n\tconst widget: Widget = {\n\t\tid: row.id,\n\t\ttype: row.type,\n\t\ttitle: row.title ?? undefined,\n\t};\n\n\t// Type-specific fields\n\tif (row.type === \"content\" && row.content) {\n\t\ttry {\n\t\t\twidget.content = JSON.parse(row.content);\n\t\t} catch {\n\t\t\t// Invalid JSON, ignore\n\t\t}\n\t}\n\n\tif (row.type === \"menu\" && row.menu_name) {\n\t\twidget.menuName = row.menu_name;\n\t}\n\n\tif (row.type === \"component\" && row.component_id) {\n\t\twidget.componentId = row.component_id;\n\t\tif (row.component_props) {\n\t\t\ttry {\n\t\t\t\twidget.componentProps = JSON.parse(row.component_props);\n\t\t\t} catch {\n\t\t\t\t// Invalid JSON, ignore\n\t\t\t}\n\t\t}\n\t}\n\n\treturn widget;\n}\n"],"mappings":";;;;;;;;;;;;AAwBA,eAAsB,cAAc,MAA0C;AAC7E,QAAO,cAAc,eAAe,QAAQ,YAAY;EAEvD,MAAM,OAAO,OADF,MAAM,OAAO,EAEtB,WAAW,4BAA4B,CACvC,SAAS,wBAAwB,aAAa,OAAO,CACrD,OAAO;GACP;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,CAAC,CACD,MAAM,UAAU,KAAK,KAAK,CAC1B,QAAQ,gBAAgB,MAAM,CAC9B,SAAS;EAEX,MAAM,QAAQ,KAAK;AACnB,MAAI,CAAC,MAAO,QAAO;EACnB,MAAM,UAAoB,EAAE;AAC5B,OAAK,MAAM,OAAO,MAAM;AACvB,OAAI,IAAI,SAAS,KAAM;GAMvB,MAAM,YAAY;IACjB,IAAI,IAAI;IACR,MAAM,IAAI;IACV,OAAO,IAAI;IACX,SAAS,IAAI;IACb,WAAW,IAAI;IACf,cAAc,IAAI;IAClB,iBAAiB,IAAI;IACrB,SAAS,IAAI;IACb,YAAY,IAAI;IAChB,YAAY,IAAI;IAChB;AACD,WAAQ,KAAK,YAAY,UAAU,CAAC;;AAGrC,SAAO;GACN,IAAI,MAAM;GACV,MAAM,MAAM;GACZ,OAAO,MAAM;GACb,aAAa,MAAM,iBAAiB;GACpC;GACA;GACA;;;;;AAMH,eAAsB,iBAAwC;CAC7D,MAAM,KAAK,MAAM,OAAO;CAExB,MAAM,WAAW,MAAM,GAAG,WAAW,uBAAuB,CAAC,WAAW,CAAC,SAAS;CAGlF,MAAM,aAAa,MAAM,GACvB,WAAW,kBAAkB,CAC7B,WAAW,CACX,SAAoB,CACpB,QAAQ,cAAc,MAAM,CAC5B,SAAS;CAGX,MAAM,gCAAgB,IAAI,KAAuB;AACjD,MAAK,MAAM,OAAO,YAAY;AAC7B,MAAI,CAAC,cAAc,IAAI,IAAI,QAAQ,CAClC,eAAc,IAAI,IAAI,SAAS,EAAE,CAAC;AAEnC,gBAAc,IAAI,IAAI,QAAQ,CAAE,KAAK,YAAY,IAAI,CAAC;;AAIvD,QAAO,SAAS,KAAK,aAAa;EACjC,IAAI,QAAQ;EACZ,MAAM,QAAQ;EACd,OAAO,QAAQ;EACf,aAAa,QAAQ,eAAe;EACpC,SAAS,cAAc,IAAI,QAAQ,GAAG,IAAI,EAAE;EAC5C,EAAE;;;;;AAMJ,SAAgB,sBAA4C;AAC3D,QAAOA,uBAAsB;;;;;AAM9B,SAAgB,YAAY,KAAwB;CACnD,MAAM,SAAiB;EACtB,IAAI,IAAI;EACR,MAAM,IAAI;EACV,OAAO,IAAI,SAAS;EACpB;AAGD,KAAI,IAAI,SAAS,aAAa,IAAI,QACjC,KAAI;AACH,SAAO,UAAU,KAAK,MAAM,IAAI,QAAQ;SACjC;AAKT,KAAI,IAAI,SAAS,UAAU,IAAI,UAC9B,QAAO,WAAW,IAAI;AAGvB,KAAI,IAAI,SAAS,eAAe,IAAI,cAAc;AACjD,SAAO,cAAc,IAAI;AACzB,MAAI,IAAI,gBACP,KAAI;AACH,UAAO,iBAAiB,KAAK,MAAM,IAAI,gBAAgB;UAChD;;AAMV,QAAO"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "emdash",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "Astro-native CMS with WordPress migration support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.mjs",
|
|
@@ -178,7 +178,7 @@
|
|
|
178
178
|
"#types": "./src/astro/types.js"
|
|
179
179
|
},
|
|
180
180
|
"dependencies": {
|
|
181
|
-
"@atcute/lexicons": "^
|
|
181
|
+
"@atcute/lexicons": "^2.0.0",
|
|
182
182
|
"@atcute/multibase": "^1.2.0",
|
|
183
183
|
"@floating-ui/react": "^0.27.16",
|
|
184
184
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
@@ -217,12 +217,12 @@
|
|
|
217
217
|
"ulidx": "^2.4.1",
|
|
218
218
|
"upng-js": "^2.1.0",
|
|
219
219
|
"zod": "^4.4.1",
|
|
220
|
-
"@atcute/client": "^
|
|
221
|
-
"@emdash-cms/admin": "0.
|
|
222
|
-
"@emdash-cms/auth": "0.
|
|
223
|
-
"@emdash-cms/gutenberg-to-portable-text": "0.
|
|
224
|
-
"@emdash-cms/
|
|
225
|
-
"@emdash-cms/
|
|
220
|
+
"@atcute/client": "^5.0.0",
|
|
221
|
+
"@emdash-cms/admin": "0.20.0",
|
|
222
|
+
"@emdash-cms/auth": "0.20.0",
|
|
223
|
+
"@emdash-cms/gutenberg-to-portable-text": "0.20.0",
|
|
224
|
+
"@emdash-cms/plugin-types": "0.1.0",
|
|
225
|
+
"@emdash-cms/registry-client": "0.3.2"
|
|
226
226
|
},
|
|
227
227
|
"optionalDependencies": {
|
|
228
228
|
"@libsql/kysely-libsql": "^0.4.0",
|
|
@@ -248,14 +248,14 @@
|
|
|
248
248
|
"@types/react": "19.2.14",
|
|
249
249
|
"@types/sanitize-html": "^2.16.0",
|
|
250
250
|
"@types/sax": "^1.2.7",
|
|
251
|
-
"@vitest/ui": "^4.1.
|
|
251
|
+
"@vitest/ui": "^4.1.8",
|
|
252
252
|
"publint": "0.3.17",
|
|
253
253
|
"tsdown": "0.20.3",
|
|
254
254
|
"typescript": "^6.0.3",
|
|
255
255
|
"vite": "^6.0.0",
|
|
256
256
|
"vitest": "^4.1.5",
|
|
257
257
|
"zod-openapi": "^5.4.6",
|
|
258
|
-
"@emdash-cms/blocks": "0.
|
|
258
|
+
"@emdash-cms/blocks": "0.20.0"
|
|
259
259
|
},
|
|
260
260
|
"repository": {
|
|
261
261
|
"type": "git",
|
|
@@ -9,7 +9,7 @@ import type { Kysely } from "kysely";
|
|
|
9
9
|
|
|
10
10
|
import type { Database } from "../../database/types.js";
|
|
11
11
|
import { validatePluginIdentifier } from "../../database/validate.js";
|
|
12
|
-
import { pluginManifestSchema } from "../../plugins/manifest-schema.js";
|
|
12
|
+
import { pluginManifestSchema, reconcileManifestAccess } from "../../plugins/manifest-schema.js";
|
|
13
13
|
import { normalizeManifestRoute } from "../../plugins/manifest-schema.js";
|
|
14
14
|
import {
|
|
15
15
|
createMarketplaceClient,
|
|
@@ -271,10 +271,7 @@ export async function loadBundleFromR2(
|
|
|
271
271
|
const parsed: unknown = JSON.parse(manifestText);
|
|
272
272
|
const result = pluginManifestSchema.safeParse(parsed);
|
|
273
273
|
if (!result.success) return null;
|
|
274
|
-
|
|
275
|
-
// for the Element[] type (Block Kit validation happens at render time).
|
|
276
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Zod types elements as unknown[]; Element type validated at render time
|
|
277
|
-
const manifest = result.data as unknown as PluginManifest;
|
|
274
|
+
const manifest = reconcileManifestAccess(result.data);
|
|
278
275
|
|
|
279
276
|
// Try to load admin code (optional)
|
|
280
277
|
let adminCode: string | undefined;
|
|
@@ -49,6 +49,8 @@ import { extractBundle } from "../../plugins/marketplace.js";
|
|
|
49
49
|
import type { PluginBundle } from "../../plugins/marketplace.js";
|
|
50
50
|
import type { SandboxRunner } from "../../plugins/sandbox/types.js";
|
|
51
51
|
import { PluginStateRepository } from "../../plugins/state.js";
|
|
52
|
+
import { declaredAccessToCapabilities } from "../../plugins/types.js";
|
|
53
|
+
import type { DeclaredAccess } from "../../plugins/types.js";
|
|
52
54
|
import {
|
|
53
55
|
canonicalCapabilitiesForDriftCheck,
|
|
54
56
|
coerceRegistryConfig,
|
|
@@ -70,6 +72,26 @@ import {
|
|
|
70
72
|
storeBundleInR2,
|
|
71
73
|
} from "./marketplace.js";
|
|
72
74
|
|
|
75
|
+
const RELEASE_EXTENSION_NSID = "com.emdashcms.experimental.package.releaseExtension";
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Whether two `declaredAccess` blocks grant exactly the same enforced access --
|
|
79
|
+
* the same capabilities AND the same host allow-list. Both are lowered through
|
|
80
|
+
* the canonical converter so that constraint content (`allowedHosts`), not just
|
|
81
|
+
* the capability set, is part of the comparison. The capability-set consent
|
|
82
|
+
* gate is blind to host scope; this is what keeps a bundle from being installed
|
|
83
|
+
* with a wider (or simply different) host allow-list than its published record
|
|
84
|
+
* advertised and the user consented to.
|
|
85
|
+
*/
|
|
86
|
+
export function enforcedAccessEqual(a: DeclaredAccess, b: DeclaredAccess): boolean {
|
|
87
|
+
const aa = declaredAccessToCapabilities(a);
|
|
88
|
+
const bb = declaredAccessToCapabilities(b);
|
|
89
|
+
return (
|
|
90
|
+
JSON.stringify(aa.capabilities.toSorted()) === JSON.stringify(bb.capabilities.toSorted()) &&
|
|
91
|
+
JSON.stringify(aa.allowedHosts.toSorted()) === JSON.stringify(bb.allowedHosts.toSorted())
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
73
95
|
// ── Types ──────────────────────────────────────────────────────────
|
|
74
96
|
|
|
75
97
|
export interface RegistryInstallInput {
|
|
@@ -999,6 +1021,31 @@ export async function handleRegistryInstall(
|
|
|
999
1021
|
// marketplace plugins that happen to share the publisher's slug.
|
|
1000
1022
|
bundle.manifest = { ...bundle.manifest, id: pluginId };
|
|
1001
1023
|
|
|
1024
|
+
// Integrity: the bundle that will run MUST declare exactly the access
|
|
1025
|
+
// the signed release record advertises. The consent dialog is driven
|
|
1026
|
+
// from the record's `declaredAccess`, so a bundle enforcing something
|
|
1027
|
+
// different -- a wider host allow-list, an extra capability -- would run
|
|
1028
|
+
// outside what the user reviewed. The capability-set consent gate below
|
|
1029
|
+
// is blind to constraint content (host scope), so compare the full
|
|
1030
|
+
// enforced access of record vs bundle here and refuse on any difference.
|
|
1031
|
+
const recordExt =
|
|
1032
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- extensions is the lexicon's open `unknown` map; narrow to read our own extension
|
|
1033
|
+
(release?.extensions as Record<string, { declaredAccess?: DeclaredAccess }> | undefined)?.[
|
|
1034
|
+
RELEASE_EXTENSION_NSID
|
|
1035
|
+
];
|
|
1036
|
+
if (
|
|
1037
|
+
!enforcedAccessEqual(recordExt?.declaredAccess ?? {}, bundle.manifest.declaredAccess ?? {})
|
|
1038
|
+
) {
|
|
1039
|
+
return {
|
|
1040
|
+
success: false,
|
|
1041
|
+
error: {
|
|
1042
|
+
code: "DECLARED_ACCESS_DRIFT",
|
|
1043
|
+
message:
|
|
1044
|
+
"The plugin bundle declares different permissions than its published record. Installation refused.",
|
|
1045
|
+
},
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1002
1049
|
// Capability consent gate: the admin MUST acknowledge the
|
|
1003
1050
|
// capabilities the bundle's manifest actually declares before we
|
|
1004
1051
|
// install it. The bundle manifest is the only source of truth
|
|
@@ -1488,6 +1535,29 @@ export async function handleRegistryUpdate(
|
|
|
1488
1535
|
// and R2 layout stay in sync across install and update.
|
|
1489
1536
|
bundle.manifest = { ...bundle.manifest, id: pluginId };
|
|
1490
1537
|
|
|
1538
|
+
// Integrity: same gate as install. The new bundle must declare exactly
|
|
1539
|
+
// the access its signed release record advertises. Without it, an update
|
|
1540
|
+
// that changes only the host scope (e.g. api.good.com -> evil.com) keeps
|
|
1541
|
+
// the capability set identical, sails through the escalation diff below,
|
|
1542
|
+
// and installs a bundle enforcing a scope the record never showed.
|
|
1543
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- extensions is the lexicon's open `unknown` map; narrow to read our own extension
|
|
1544
|
+
const updateRecordExtensions = signedRelease?.extensions as
|
|
1545
|
+
| Record<string, { declaredAccess?: DeclaredAccess }>
|
|
1546
|
+
| undefined;
|
|
1547
|
+
const recordExt = updateRecordExtensions?.[RELEASE_EXTENSION_NSID];
|
|
1548
|
+
if (
|
|
1549
|
+
!enforcedAccessEqual(recordExt?.declaredAccess ?? {}, bundle.manifest.declaredAccess ?? {})
|
|
1550
|
+
) {
|
|
1551
|
+
return {
|
|
1552
|
+
success: false,
|
|
1553
|
+
error: {
|
|
1554
|
+
code: "DECLARED_ACCESS_DRIFT",
|
|
1555
|
+
message:
|
|
1556
|
+
"The plugin bundle declares different permissions than its published record. Update refused.",
|
|
1557
|
+
},
|
|
1558
|
+
};
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1491
1561
|
// Diff capabilities + route visibility against the currently
|
|
1492
1562
|
// installed bundle. Loading from R2 keeps us honest: the diff is
|
|
1493
1563
|
// against the bytes the sandbox is actually running, not whatever
|
package/src/api/handlers/seo.ts
CHANGED
|
@@ -29,6 +29,12 @@ export interface SitemapContentEntry {
|
|
|
29
29
|
* alternates between siblings.
|
|
30
30
|
*/
|
|
31
31
|
translationGroup: string | null;
|
|
32
|
+
/**
|
|
33
|
+
* Stored SEO image reference (`_emdash_seo.seo_image`), or null when
|
|
34
|
+
* the entry has no SEO image. The route resolves it to an absolute
|
|
35
|
+
* URL and emits it as an `<image:image>` sitemap entry.
|
|
36
|
+
*/
|
|
37
|
+
image: string | null;
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
/** Per-collection sitemap data with entries and URL pattern */
|
|
@@ -106,8 +112,9 @@ export async function handleSitemapData(
|
|
|
106
112
|
updated_at: string;
|
|
107
113
|
locale: string;
|
|
108
114
|
translation_group: string | null;
|
|
115
|
+
seo_image: string | null;
|
|
109
116
|
}>`
|
|
110
|
-
SELECT c.slug, c.id, c.updated_at, c.locale, c.translation_group
|
|
117
|
+
SELECT c.slug, c.id, c.updated_at, c.locale, c.translation_group, s.seo_image
|
|
111
118
|
FROM ${sql.ref(tableName)} c
|
|
112
119
|
LEFT JOIN _emdash_seo s
|
|
113
120
|
ON s.collection = ${col.slug}
|
|
@@ -129,6 +136,7 @@ export async function handleSitemapData(
|
|
|
129
136
|
updatedAt: row.updated_at,
|
|
130
137
|
locale: row.locale,
|
|
131
138
|
translationGroup: row.translation_group,
|
|
139
|
+
image: row.seo_image ?? null,
|
|
132
140
|
});
|
|
133
141
|
}
|
|
134
142
|
|
|
@@ -31,7 +31,19 @@ const fieldTypeValues = z.enum([
|
|
|
31
31
|
|
|
32
32
|
const repeaterSubFieldSchema = z.object({
|
|
33
33
|
slug: z.string().min(1).max(63).regex(slugPattern, "Invalid slug format"),
|
|
34
|
-
|
|
34
|
+
// Keep in sync with REPEATER_SUB_FIELD_TYPES in schema/types.ts.
|
|
35
|
+
// ("url" was already a documented sub-field type but missing here.)
|
|
36
|
+
type: z.enum([
|
|
37
|
+
"string",
|
|
38
|
+
"text",
|
|
39
|
+
"url",
|
|
40
|
+
"number",
|
|
41
|
+
"integer",
|
|
42
|
+
"boolean",
|
|
43
|
+
"datetime",
|
|
44
|
+
"select",
|
|
45
|
+
"image",
|
|
46
|
+
]),
|
|
35
47
|
label: z.string().min(1),
|
|
36
48
|
required: z.boolean().optional(),
|
|
37
49
|
options: z.array(z.string()).optional(),
|
package/src/astro/middleware.ts
CHANGED
|
@@ -333,6 +333,9 @@ function pushMetricsTimings(
|
|
|
333
333
|
timings.push({ name: "db.last", dur: metrics.dbLastOffset, desc: "Last query at" });
|
|
334
334
|
}
|
|
335
335
|
}
|
|
336
|
+
if (metrics.rpcCount > 0) {
|
|
337
|
+
timings.push({ name: "rpc.count", dur: metrics.rpcCount, desc: "DB round trips" });
|
|
338
|
+
}
|
|
336
339
|
if (metrics.cacheHits + metrics.cacheMisses > 0) {
|
|
337
340
|
timings.push({ name: "cache.hit", dur: metrics.cacheHits, desc: "Cache hits" });
|
|
338
341
|
timings.push({ name: "cache.miss", dur: metrics.cacheMisses, desc: "Cache misses" });
|
|
@@ -510,9 +513,15 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
|
|
510
513
|
? { ...parent, db: anonScoped.db }
|
|
511
514
|
: { editMode: false, db: anonScoped.db, metrics };
|
|
512
515
|
return runWithContext(ctx, async () => {
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
+
// commit() in finally: the write reached the primary independently
|
|
517
|
+
// of render, so the bookmark cookie must be persisted even if
|
|
518
|
+
// render throws -- otherwise a write-then-failed-render leaves the
|
|
519
|
+
// next request able to read pre-write state off a lagging replica.
|
|
520
|
+
try {
|
|
521
|
+
return await runAnon();
|
|
522
|
+
} finally {
|
|
523
|
+
anonScoped.commit();
|
|
524
|
+
}
|
|
516
525
|
});
|
|
517
526
|
}
|
|
518
527
|
return runAnon();
|
|
@@ -681,9 +690,14 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
|
|
681
690
|
? { ...parent, db: scoped.db }
|
|
682
691
|
: { editMode: false, db: scoped.db, metrics };
|
|
683
692
|
return runWithContext(ctx, async () => {
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
693
|
+
// commit() in finally: persist the bookmark cookie even if render
|
|
694
|
+
// throws -- the write already reached the primary, so a failed
|
|
695
|
+
// render must not strand the next request on a stale replica read.
|
|
696
|
+
try {
|
|
697
|
+
return await renderAndFinalize();
|
|
698
|
+
} finally {
|
|
699
|
+
scoped.commit();
|
|
700
|
+
}
|
|
687
701
|
});
|
|
688
702
|
}
|
|
689
703
|
|
|
@@ -13,7 +13,7 @@ import type { APIRoute } from "astro";
|
|
|
13
13
|
import { hashString } from "emdash";
|
|
14
14
|
|
|
15
15
|
import { requirePerm } from "#api/authorize.js";
|
|
16
|
-
import { handleError, requireDb } from "#api/error.js";
|
|
16
|
+
import { apiSuccess, handleError, requireDb } from "#api/error.js";
|
|
17
17
|
import { SchemaRegistry } from "#schema/registry.js";
|
|
18
18
|
import { generateTypeScript } from "#schema/zod-generator.js";
|
|
19
19
|
|
|
@@ -89,20 +89,12 @@ import type { PortableTextBlock } from "emdash";
|
|
|
89
89
|
|
|
90
90
|
const version = await hashString(JSON.stringify(schemaExport));
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
status: 200,
|
|
99
|
-
headers: {
|
|
100
|
-
"Content-Type": "application/json",
|
|
101
|
-
"Cache-Control": "private, no-store",
|
|
102
|
-
"X-Schema-Version": version,
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
);
|
|
92
|
+
const response = apiSuccess({
|
|
93
|
+
...schemaExport,
|
|
94
|
+
version,
|
|
95
|
+
});
|
|
96
|
+
response.headers.set("X-Schema-Version", version);
|
|
97
|
+
return response;
|
|
106
98
|
} catch (error) {
|
|
107
99
|
return handleError(error, "Schema export failed", "SCHEMA_EXPORT_ERROR");
|
|
108
100
|
}
|
|
@@ -23,6 +23,7 @@ import { getSiteSettingsWithDb } from "#settings/index.js";
|
|
|
23
23
|
|
|
24
24
|
import { getI18nConfig, isI18nEnabled } from "../../i18n/config.js";
|
|
25
25
|
import { interpolateUrlPattern, localizePath } from "../../i18n/resolve.js";
|
|
26
|
+
import { buildSeoImageUrl } from "../../seo/media-url.js";
|
|
26
27
|
|
|
27
28
|
export const prerender = false;
|
|
28
29
|
|
|
@@ -112,8 +113,8 @@ export const GET: APIRoute = async ({ params, locals, url }) => {
|
|
|
112
113
|
const lines: string[] = ['<?xml version="1.0" encoding="UTF-8"?>'];
|
|
113
114
|
lines.push(
|
|
114
115
|
useXhtml
|
|
115
|
-
? '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">'
|
|
116
|
-
: '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
|
|
116
|
+
? '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">'
|
|
117
|
+
: '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">',
|
|
117
118
|
);
|
|
118
119
|
|
|
119
120
|
const writeUrl = async (entry: Entry, siblings: Entry[] | null) => {
|
|
@@ -127,6 +128,16 @@ export const GET: APIRoute = async ({ params, locals, url }) => {
|
|
|
127
128
|
lines.push(` <loc>${escapeXml(loc)}</loc>`);
|
|
128
129
|
lines.push(` <lastmod>${escapeXml(entry.updatedAt)}</lastmod>`);
|
|
129
130
|
|
|
131
|
+
// Google image sitemap extension: advertise the entry's SEO
|
|
132
|
+
// image (the same "preferred image" used for og:image) so it
|
|
133
|
+
// can be discovered and indexed for Google Images.
|
|
134
|
+
if (entry.image) {
|
|
135
|
+
const imageLoc = buildSeoImageUrl(entry.image, siteUrl);
|
|
136
|
+
lines.push(" <image:image>");
|
|
137
|
+
lines.push(` <image:loc>${escapeXml(imageLoc)}</image:loc>`);
|
|
138
|
+
lines.push(" </image:image>");
|
|
139
|
+
}
|
|
140
|
+
|
|
130
141
|
if (useXhtml && siblings && siblings.length > 1) {
|
|
131
142
|
// Emit one xhtml:link per sibling (including self -- Google
|
|
132
143
|
// recommends including the page's own hreflang annotation).
|
|
@@ -13,6 +13,7 @@ import { pipeline } from "node:stream/promises";
|
|
|
13
13
|
import { imageSize } from "image-size";
|
|
14
14
|
import { packTar } from "modern-tar/fs";
|
|
15
15
|
|
|
16
|
+
import { capabilitiesToDeclaredAccess } from "../../plugins/types.js";
|
|
16
17
|
import type {
|
|
17
18
|
PluginManifest,
|
|
18
19
|
ResolvedPlugin,
|
|
@@ -151,6 +152,7 @@ export function extractManifest(plugin: ResolvedPlugin): PluginManifest {
|
|
|
151
152
|
return {
|
|
152
153
|
id: plugin.id,
|
|
153
154
|
version: plugin.version,
|
|
155
|
+
declaredAccess: capabilitiesToDeclaredAccess(plugin.capabilities, plugin.allowedHosts),
|
|
154
156
|
capabilities: plugin.capabilities,
|
|
155
157
|
allowedHosts: plugin.allowedHosts,
|
|
156
158
|
storage: plugin.storage,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Pure (no-DB) commands for working with EmDash secrets:
|
|
5
5
|
*
|
|
6
6
|
* - `emdash secrets generate` — emits a fresh `EMDASH_ENCRYPTION_KEY`.
|
|
7
|
-
* Optionally writes it to
|
|
7
|
+
* Optionally writes it to a local-secrets file (`.env`).
|
|
8
8
|
* - `emdash secrets fingerprint <key>` — prints the kid for a key,
|
|
9
9
|
* useful in CI for verifying what's been deployed without exposing
|
|
10
10
|
* the raw value.
|
|
@@ -87,7 +87,7 @@ const generateCommand = defineCommand({
|
|
|
87
87
|
write: {
|
|
88
88
|
type: "string",
|
|
89
89
|
description:
|
|
90
|
-
"Optional path to write the key to (e.g. .
|
|
90
|
+
"Optional path to write the key to (e.g. .env). " +
|
|
91
91
|
"Won't overwrite an existing entry without --force.",
|
|
92
92
|
},
|
|
93
93
|
force: {
|
|
@@ -110,3 +110,16 @@ function kyselyLog(event: LogEvent): void {
|
|
|
110
110
|
export function kyselyLogOption(): Logger {
|
|
111
111
|
return kyselyLog;
|
|
112
112
|
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Record physical database round trips for the current request.
|
|
116
|
+
*
|
|
117
|
+
* Called by backends that batch (the DO SQL driver coalesces same-turn SELECTs
|
|
118
|
+
* into one RPC), so we can see round-trip count separately from logical query
|
|
119
|
+
* count (`dbCount`, bumped by the Kysely log hook). No-op outside a request or
|
|
120
|
+
* when metrics aren't attached (e.g. migrations on the singleton).
|
|
121
|
+
*/
|
|
122
|
+
export function recordRpc(count = 1): void {
|
|
123
|
+
const ctx = getRequestContext();
|
|
124
|
+
if (ctx?.metrics) ctx.metrics.rpcCount += count;
|
|
125
|
+
}
|
package/src/emdash-runtime.ts
CHANGED
|
@@ -45,6 +45,7 @@ import type {
|
|
|
45
45
|
import type { FieldType } from "./schema/types.js";
|
|
46
46
|
import { hashString } from "./utils/hash.js";
|
|
47
47
|
import { createInitLock, type InitLock, initWithLock } from "./utils/init-lock.js";
|
|
48
|
+
import { createIsolateCache, isolateCachedAsync } from "./utils/isolate-cache.js";
|
|
48
49
|
import { COMMIT, VERSION } from "./version.js";
|
|
49
50
|
|
|
50
51
|
const LEADING_SLASH_PATTERN = /^\//;
|
|
@@ -417,12 +418,12 @@ export class EmDashRuntime {
|
|
|
417
418
|
private pluginStates: Map<string, string>;
|
|
418
419
|
|
|
419
420
|
/**
|
|
420
|
-
*
|
|
421
|
-
*
|
|
422
|
-
*
|
|
421
|
+
* Isolate-lifetime guard so FTS indexes are verified at most once per
|
|
422
|
+
* worker rather than on every admin request. See ensureSearchHealthy().
|
|
423
|
+
* Uses the poison-immune isolate cache (never a shared awaitable promise)
|
|
424
|
+
* so a cancelled first caller can't wedge later ones.
|
|
423
425
|
*/
|
|
424
|
-
private
|
|
425
|
-
private _searchHealthPromise: Promise<void> | null = null;
|
|
426
|
+
private readonly _searchHealthCache = createIsolateCache<void>();
|
|
426
427
|
|
|
427
428
|
/** Current hook pipeline. Use the `hooks` getter for external access. */
|
|
428
429
|
get hooks(): HookPipeline {
|
|
@@ -2228,27 +2229,32 @@ export class EmDashRuntime {
|
|
|
2228
2229
|
* defend against FTS not existing yet (pre-setup).
|
|
2229
2230
|
*/
|
|
2230
2231
|
async ensureSearchHealthy(): Promise<void> {
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
if (!isSqlite(this._db))
|
|
2234
|
-
|
|
2235
|
-
|
|
2232
|
+
// Non-SQLite has no FTS to verify; the check is a cheap synchronous
|
|
2233
|
+
// branch, no need to cache it.
|
|
2234
|
+
if (!isSqlite(this._db)) return;
|
|
2235
|
+
try {
|
|
2236
|
+
await isolateCachedAsync(
|
|
2237
|
+
this._searchHealthCache,
|
|
2238
|
+
async () => {
|
|
2239
|
+
try {
|
|
2240
|
+
const ftsManager = new FTSManager(this._db);
|
|
2241
|
+
const repaired = await ftsManager.verifyAndRepairAll();
|
|
2242
|
+
if (repaired > 0) {
|
|
2243
|
+
console.log(`Repaired ${repaired} corrupted FTS index(es)`);
|
|
2244
|
+
}
|
|
2245
|
+
} catch {
|
|
2246
|
+
// FTS tables may not exist yet (pre-setup). Non-fatal — cache
|
|
2247
|
+
// the "checked" state regardless so we don't re-scan.
|
|
2248
|
+
}
|
|
2249
|
+
},
|
|
2250
|
+
{ anchor: (promise) => after(() => promise), ownerTimeoutMs: 30_000 },
|
|
2251
|
+
);
|
|
2252
|
+
} catch {
|
|
2253
|
+
// This check is best-effort and must never fail the calling request.
|
|
2254
|
+
// The inner body already swallows verify errors; this guards the
|
|
2255
|
+
// outer failure modes (owner timeout, waiter give-up) so a slow FTS
|
|
2256
|
+
// scan degrades to "unverified", not a 500 on admin/search routes.
|
|
2236
2257
|
}
|
|
2237
|
-
this._searchHealthPromise = (async () => {
|
|
2238
|
-
try {
|
|
2239
|
-
const ftsManager = new FTSManager(this._db);
|
|
2240
|
-
const repaired = await ftsManager.verifyAndRepairAll();
|
|
2241
|
-
if (repaired > 0) {
|
|
2242
|
-
console.log(`Repaired ${repaired} corrupted FTS index(es)`);
|
|
2243
|
-
}
|
|
2244
|
-
} catch {
|
|
2245
|
-
// FTS tables may not exist yet (pre-setup). Non-fatal.
|
|
2246
|
-
} finally {
|
|
2247
|
-
this._searchHealthChecked = true;
|
|
2248
|
-
this._searchHealthPromise = null;
|
|
2249
|
-
}
|
|
2250
|
-
})();
|
|
2251
|
-
return this._searchHealthPromise;
|
|
2252
2258
|
}
|
|
2253
2259
|
|
|
2254
2260
|
// =========================================================================
|