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
package/src/loader.ts
CHANGED
|
@@ -680,7 +680,12 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
|
|
|
680
680
|
|
|
681
681
|
// Separate taxonomy / byline filters from field filters
|
|
682
682
|
let result: { rows: Record<string, unknown>[] };
|
|
683
|
-
|
|
683
|
+
// Taxonomy filters AND together: each entry constrains the base
|
|
684
|
+
// row to match at least one of its slugs *within that taxonomy*.
|
|
685
|
+
// Term slugs are unique only within a taxonomy, so every filter
|
|
686
|
+
// keeps its own `name` and emits its own `EXISTS` clause rather
|
|
687
|
+
// than pooling slugs into one `IN`.
|
|
688
|
+
const taxonomyFilters: { name: string; slugs: string[] }[] = [];
|
|
684
689
|
// A byline filter matches entries credited to any of the given
|
|
685
690
|
// byline translation groups via the `_emdash_content_bylines`
|
|
686
691
|
// junction table. `null` means no byline filter; an empty
|
|
@@ -710,14 +715,8 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
|
|
|
710
715
|
);
|
|
711
716
|
continue;
|
|
712
717
|
}
|
|
713
|
-
if (taxonomyFilter) {
|
|
714
|
-
console.warn(
|
|
715
|
-
`[emdash] where filter: only one taxonomy is supported per query, "${key}" ignored`,
|
|
716
|
-
);
|
|
717
|
-
continue;
|
|
718
|
-
}
|
|
719
718
|
const slugs = Array.isArray(value) ? value : [value];
|
|
720
|
-
|
|
719
|
+
taxonomyFilters.push({ name: key, slugs });
|
|
721
720
|
} else {
|
|
722
721
|
fieldFilters[key] = value;
|
|
723
722
|
}
|
|
@@ -729,7 +728,7 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
|
|
|
729
728
|
// SQL on both dialects).
|
|
730
729
|
if (
|
|
731
730
|
(bylineFilter && bylineFilter.groups.length === 0) ||
|
|
732
|
-
(
|
|
731
|
+
taxonomyFilters.some((f) => f.slugs.length === 0)
|
|
733
732
|
) {
|
|
734
733
|
return { entries: [], cacheHint: { tags: [type] } };
|
|
735
734
|
}
|
|
@@ -753,16 +752,26 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
|
|
|
753
752
|
const fieldCondsSQL =
|
|
754
753
|
fieldConds.length > 0 ? sql`${sql.join(fieldConds, sql` AND `)}` : null;
|
|
755
754
|
|
|
756
|
-
|
|
757
|
-
|
|
755
|
+
// One `EXISTS` per taxonomy, AND'd together: an entry must be
|
|
756
|
+
// tagged with a matching term in *every* requested taxonomy.
|
|
757
|
+
// Each clause pins its own `t.name`, so slugs never pool
|
|
758
|
+
// across taxonomies (they're only unique within one).
|
|
759
|
+
const taxonomyCond =
|
|
760
|
+
taxonomyFilters.length > 0
|
|
761
|
+
? sql`${sql.join(
|
|
762
|
+
taxonomyFilters.map(
|
|
763
|
+
(f) => sql`AND EXISTS (
|
|
758
764
|
SELECT 1 FROM content_taxonomies ct
|
|
759
765
|
INNER JOIN taxonomies t ON t.id = ct.taxonomy_id
|
|
760
766
|
WHERE ct.collection = ${type}
|
|
761
767
|
AND ct.entry_id = ${sql.ref(tableName)}.id
|
|
762
|
-
AND t.name = ${
|
|
763
|
-
AND t.slug IN (${sql.join(
|
|
764
|
-
)
|
|
765
|
-
|
|
768
|
+
AND t.name = ${f.name}
|
|
769
|
+
AND t.slug IN (${sql.join(f.slugs.map((s) => sql`${s}`))})
|
|
770
|
+
)`,
|
|
771
|
+
),
|
|
772
|
+
sql` `,
|
|
773
|
+
)}`
|
|
774
|
+
: sql``;
|
|
766
775
|
|
|
767
776
|
// `_emdash_content_bylines.byline_id` stores the byline's
|
|
768
777
|
// translation_group (migration 040), so a credit spans every
|
|
@@ -8,8 +8,14 @@
|
|
|
8
8
|
* - Marketplace ingest extends this with publishing-specific fields
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import {
|
|
12
|
+
capabilitiesToDeclaredAccess,
|
|
13
|
+
declaredAccessToCapabilities,
|
|
14
|
+
} from "@emdash-cms/plugin-types";
|
|
11
15
|
import { z } from "zod";
|
|
12
16
|
|
|
17
|
+
import type { PluginManifest } from "./types.js";
|
|
18
|
+
|
|
13
19
|
// ── Enum values (must stay in sync with types.ts) ───────────────
|
|
14
20
|
|
|
15
21
|
/**
|
|
@@ -219,16 +225,63 @@ const pluginAdminConfigSchema = z.object({
|
|
|
219
225
|
.optional(),
|
|
220
226
|
});
|
|
221
227
|
|
|
228
|
+
// ── declaredAccess ──────────────────────────────────────────────
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* An operation's constraint object. Open vocabulary: keys the runtime
|
|
232
|
+
* recognises are enforced, others are advisory. The bundler emits `{}` for a
|
|
233
|
+
* granted operation; presence (not value) signals the grant.
|
|
234
|
+
*/
|
|
235
|
+
const accessConstraints = z.record(z.string(), z.unknown());
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Structured trust contract embedded in the bundle manifest. Mirrors
|
|
239
|
+
* `DeclaredAccess` in `@emdash-cms/plugin-types`. Categories are host
|
|
240
|
+
* subsystems; operations are modes of participation.
|
|
241
|
+
*/
|
|
242
|
+
const declaredAccessSchema = z.object({
|
|
243
|
+
content: z
|
|
244
|
+
.object({ read: accessConstraints.optional(), write: accessConstraints.optional() })
|
|
245
|
+
.optional(),
|
|
246
|
+
media: z
|
|
247
|
+
.object({ read: accessConstraints.optional(), write: accessConstraints.optional() })
|
|
248
|
+
.optional(),
|
|
249
|
+
network: z
|
|
250
|
+
.object({
|
|
251
|
+
// allowedHosts: absent = unrestricted; present = host-restricted. Reject
|
|
252
|
+
// an empty array (which the decoder would otherwise have to treat as
|
|
253
|
+
// deny-all) to match the record lexicon's `minLength: 1` and keep the
|
|
254
|
+
// "absent vs empty" distinction from ever reaching enforcement ambiguous.
|
|
255
|
+
request: z.object({ allowedHosts: z.array(z.string()).min(1).optional() }).optional(),
|
|
256
|
+
})
|
|
257
|
+
.optional(),
|
|
258
|
+
email: z
|
|
259
|
+
.object({
|
|
260
|
+
send: accessConstraints.optional(),
|
|
261
|
+
events: accessConstraints.optional(),
|
|
262
|
+
transport: accessConstraints.optional(),
|
|
263
|
+
})
|
|
264
|
+
.optional(),
|
|
265
|
+
page: z.object({ fragments: accessConstraints.optional() }).optional(),
|
|
266
|
+
users: z.object({ read: accessConstraints.optional() }).optional(),
|
|
267
|
+
});
|
|
268
|
+
|
|
222
269
|
// ── Main schema ─────────────────────────────────────────────────
|
|
223
270
|
|
|
224
271
|
/**
|
|
225
272
|
* Zod schema matching the PluginManifest interface from types.ts.
|
|
226
273
|
*
|
|
227
274
|
* Every JSON.parse of a manifest.json should validate through this.
|
|
275
|
+
*
|
|
276
|
+
* `declaredAccess` is the trust contract; `capabilities`/`allowedHosts` are the
|
|
277
|
+
* runtime's enforcement currency. Apply `reconcileManifestAccess` after parsing
|
|
278
|
+
* to make them consistent (declaredAccess authoritative when present). Kept a
|
|
279
|
+
* plain object (no `.transform`) because callers `.pick()`/`.extend()` it.
|
|
228
280
|
*/
|
|
229
281
|
export const pluginManifestSchema = z.object({
|
|
230
282
|
id: z.string().min(1),
|
|
231
283
|
version: z.string().min(1),
|
|
284
|
+
declaredAccess: declaredAccessSchema.optional(),
|
|
232
285
|
capabilities: z.array(z.enum(PLUGIN_CAPABILITIES)),
|
|
233
286
|
allowedHosts: z.array(z.string()),
|
|
234
287
|
storage: z.record(z.string(), storageCollectionSchema),
|
|
@@ -254,6 +307,28 @@ export const pluginManifestSchema = z.object({
|
|
|
254
307
|
|
|
255
308
|
export type ValidatedPluginManifest = z.infer<typeof pluginManifestSchema>;
|
|
256
309
|
|
|
310
|
+
/**
|
|
311
|
+
* Reconcile a parsed manifest's trust contract with its enforcement currency.
|
|
312
|
+
* `declaredAccess` is authoritative: when present, `capabilities`/`allowedHosts`
|
|
313
|
+
* are re-derived from it so what the runtime enforces always matches what was
|
|
314
|
+
* recorded and consented to. A pre-migration bundle without `declaredAccess`
|
|
315
|
+
* has it derived from the legacy capability list instead. The result always
|
|
316
|
+
* carries both, mutually consistent. Apply this at every bundle-parse site.
|
|
317
|
+
*/
|
|
318
|
+
export function reconcileManifestAccess(manifest: ValidatedPluginManifest): PluginManifest {
|
|
319
|
+
const reconciled: ValidatedPluginManifest = manifest.declaredAccess
|
|
320
|
+
? { ...manifest, ...declaredAccessToCapabilities(manifest.declaredAccess) }
|
|
321
|
+
: {
|
|
322
|
+
...manifest,
|
|
323
|
+
declaredAccess: capabilitiesToDeclaredAccess(manifest.capabilities, manifest.allowedHosts),
|
|
324
|
+
};
|
|
325
|
+
// Block Kit admin elements are typed as `unknown` by the Zod schema (their
|
|
326
|
+
// Element shape is validated at render time), so the validated manifest
|
|
327
|
+
// needs a structural cast up to the runtime PluginManifest.
|
|
328
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- admin elements are unknown[] in Zod; Element type checked at render time
|
|
329
|
+
return reconciled as unknown as PluginManifest;
|
|
330
|
+
}
|
|
331
|
+
|
|
257
332
|
/**
|
|
258
333
|
* Normalize a manifest hook entry — plain strings become `{ name }` objects.
|
|
259
334
|
*/
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { createGzipDecoder, unpackTar } from "modern-tar";
|
|
10
10
|
|
|
11
|
-
import { pluginManifestSchema } from "./manifest-schema.js";
|
|
11
|
+
import { pluginManifestSchema, reconcileManifestAccess } from "./manifest-schema.js";
|
|
12
12
|
import type { PluginManifest } from "./types.js";
|
|
13
13
|
|
|
14
14
|
// ── Module-level regex patterns ───────────────────────────────────
|
|
@@ -495,10 +495,7 @@ export async function extractBundle(tarballBytes: Uint8Array): Promise<PluginBun
|
|
|
495
495
|
"INVALID_BUNDLE",
|
|
496
496
|
);
|
|
497
497
|
}
|
|
498
|
-
|
|
499
|
-
// for the Element[] type (Block Kit validation happens at render time).
|
|
500
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Zod types elements as unknown[]; Element type validated at render time
|
|
501
|
-
manifest = result.data as unknown as PluginManifest;
|
|
498
|
+
manifest = reconcileManifestAccess(result.data);
|
|
502
499
|
} catch (err) {
|
|
503
500
|
if (err instanceof MarketplaceError) throw err;
|
|
504
501
|
throw new MarketplaceError(
|
package/src/plugins/types.ts
CHANGED
|
@@ -19,10 +19,13 @@ import type { Element } from "@emdash-cms/blocks";
|
|
|
19
19
|
// (e.g. `import { PluginCapability } from "../plugins/types.js"`).
|
|
20
20
|
import {
|
|
21
21
|
CAPABILITY_RENAMES,
|
|
22
|
+
capabilitiesToDeclaredAccess,
|
|
23
|
+
declaredAccessToCapabilities,
|
|
22
24
|
isDeprecatedCapability,
|
|
23
25
|
normalizeCapabilities,
|
|
24
26
|
normalizeCapability,
|
|
25
27
|
type CurrentPluginCapability,
|
|
28
|
+
type DeclaredAccess,
|
|
26
29
|
type DeprecatedPluginCapability,
|
|
27
30
|
type ManifestHookEntry,
|
|
28
31
|
type ManifestRouteEntry,
|
|
@@ -40,10 +43,13 @@ import type { FieldType } from "../schema/types.js";
|
|
|
40
43
|
|
|
41
44
|
export {
|
|
42
45
|
CAPABILITY_RENAMES,
|
|
46
|
+
capabilitiesToDeclaredAccess,
|
|
47
|
+
declaredAccessToCapabilities,
|
|
43
48
|
isDeprecatedCapability,
|
|
44
49
|
normalizeCapabilities,
|
|
45
50
|
normalizeCapability,
|
|
46
51
|
type CurrentPluginCapability,
|
|
52
|
+
type DeclaredAccess,
|
|
47
53
|
type DeprecatedPluginCapability,
|
|
48
54
|
type ManifestHookEntry,
|
|
49
55
|
type ManifestRouteEntry,
|
|
@@ -1336,6 +1342,12 @@ export interface PluginAdminExports {
|
|
|
1336
1342
|
export interface PluginManifest {
|
|
1337
1343
|
id: string;
|
|
1338
1344
|
version: string;
|
|
1345
|
+
/**
|
|
1346
|
+
* The trust contract (see `@emdash-cms/plugin-types`). Authoritative;
|
|
1347
|
+
* `capabilities`/`allowedHosts` are derived from it at the parse boundary
|
|
1348
|
+
* via `reconcileManifestAccess`. Optional during the wire-format migration.
|
|
1349
|
+
*/
|
|
1350
|
+
declaredAccess?: DeclaredAccess;
|
|
1339
1351
|
capabilities: PluginCapability[];
|
|
1340
1352
|
allowedHosts: string[];
|
|
1341
1353
|
storage: PluginStorageConfig;
|
package/src/query.ts
CHANGED
|
@@ -247,6 +247,17 @@ function dataStr(data: Record<string, unknown>, key: string, fallback = ""): str
|
|
|
247
247
|
return typeof val === "string" ? val : fallback;
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
+
/** Safely read a date-like field from a Record */
|
|
251
|
+
function dataDate(data: Record<string, unknown>, key: string): Date | undefined {
|
|
252
|
+
const val = data[key];
|
|
253
|
+
if (val instanceof Date) {
|
|
254
|
+
return Number.isNaN(val.getTime()) ? undefined : val;
|
|
255
|
+
}
|
|
256
|
+
if (typeof val !== "string" && typeof val !== "number") return undefined;
|
|
257
|
+
const date = new Date(val);
|
|
258
|
+
return Number.isNaN(date.getTime()) ? undefined : date;
|
|
259
|
+
}
|
|
260
|
+
|
|
250
261
|
/** Type guard for Record<string, unknown> */
|
|
251
262
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
252
263
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -602,10 +613,10 @@ export async function getEmDashEntry<T extends string, D = InferCollectionData<T
|
|
|
602
613
|
function isVisible(entry: ContentEntry<D>): boolean {
|
|
603
614
|
const data = entryData(entry);
|
|
604
615
|
const status = dataStr(data, "status");
|
|
605
|
-
const scheduledAt =
|
|
616
|
+
const scheduledAt = dataDate(data, "scheduledAt");
|
|
606
617
|
const isPublished = status === "published";
|
|
607
618
|
const isScheduledAndReady =
|
|
608
|
-
status === "scheduled" && scheduledAt &&
|
|
619
|
+
status === "scheduled" && scheduledAt !== undefined && scheduledAt.getTime() <= Date.now();
|
|
609
620
|
return isPublished || !!isScheduledAndReady;
|
|
610
621
|
}
|
|
611
622
|
|
package/src/request-context.ts
CHANGED
|
@@ -39,6 +39,13 @@ export interface RequestMetrics {
|
|
|
39
39
|
dbLastOffset: number | null;
|
|
40
40
|
cacheHits: number;
|
|
41
41
|
cacheMisses: number;
|
|
42
|
+
/**
|
|
43
|
+
* Physical database round trips. Differs from `dbCount` (logical queries)
|
|
44
|
+
* when a backend batches: the DO SQL driver coalesces same-turn SELECTs into
|
|
45
|
+
* one RPC, so `rpcCount` can be far lower than `dbCount`. Bumped by the
|
|
46
|
+
* adapter, not the Kysely log hook.
|
|
47
|
+
*/
|
|
48
|
+
rpcCount: number;
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
export function createRequestMetrics(start: number): RequestMetrics {
|
|
@@ -50,6 +57,7 @@ export function createRequestMetrics(start: number): RequestMetrics {
|
|
|
50
57
|
dbLastOffset: null,
|
|
51
58
|
cacheHits: 0,
|
|
52
59
|
cacheMisses: 0,
|
|
60
|
+
rpcCount: 0,
|
|
53
61
|
};
|
|
54
62
|
}
|
|
55
63
|
|
package/src/schema/types.ts
CHANGED
|
@@ -102,7 +102,16 @@ export type CollectionSource =
|
|
|
102
102
|
/** Sub-field definition for repeater fields */
|
|
103
103
|
export interface RepeaterSubField {
|
|
104
104
|
slug: string;
|
|
105
|
-
type:
|
|
105
|
+
type:
|
|
106
|
+
| "string"
|
|
107
|
+
| "text"
|
|
108
|
+
| "url"
|
|
109
|
+
| "number"
|
|
110
|
+
| "integer"
|
|
111
|
+
| "boolean"
|
|
112
|
+
| "datetime"
|
|
113
|
+
| "select"
|
|
114
|
+
| "image";
|
|
106
115
|
label: string;
|
|
107
116
|
required?: boolean;
|
|
108
117
|
options?: string[]; // For select sub-fields
|
|
@@ -118,6 +127,7 @@ export const REPEATER_SUB_FIELD_TYPES = [
|
|
|
118
127
|
"boolean",
|
|
119
128
|
"datetime",
|
|
120
129
|
"select",
|
|
130
|
+
"image",
|
|
121
131
|
] as const;
|
|
122
132
|
|
|
123
133
|
export interface FieldValidation {
|
package/src/seo/index.ts
CHANGED
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
32
|
import type { ContentSeo } from "../database/repositories/types.js";
|
|
33
|
+
import { buildSeoImageUrl } from "./media-url.js";
|
|
33
34
|
|
|
34
35
|
const TRAILING_SLASH_RE = /\/$/;
|
|
35
36
|
const ABSOLUTE_URL_RE = /^https?:\/\//i;
|
|
@@ -117,7 +118,7 @@ export function getSeoMeta<T>(content: SeoContentInput<T>, options: SeoMetaOptio
|
|
|
117
118
|
null;
|
|
118
119
|
|
|
119
120
|
// OG image: SEO image > default
|
|
120
|
-
const ogImage = seo.image ?
|
|
121
|
+
const ogImage = seo.image ? buildSeoImageUrl(seo.image, siteUrl) : (defaultOgImage ?? null);
|
|
121
122
|
|
|
122
123
|
// Canonical: explicit > path-based > null
|
|
123
124
|
let canonical: string | null = null;
|
|
@@ -159,30 +160,3 @@ export function getSeoMeta<T>(content: SeoContentInput<T>, options: SeoMetaOptio
|
|
|
159
160
|
export function getContentSeo<T>(content: SeoContentInput<T>): ContentSeo | undefined {
|
|
160
161
|
return content.seo ?? content.data.seo;
|
|
161
162
|
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Build a media URL from a media reference ID.
|
|
165
|
-
* If it's already an absolute URL, return as-is.
|
|
166
|
-
*/
|
|
167
|
-
function buildMediaUrl(imageRef: string, siteUrl?: string): string {
|
|
168
|
-
// If already an absolute URL, return as-is
|
|
169
|
-
if (ABSOLUTE_URL_RE.test(imageRef)) {
|
|
170
|
-
return imageRef;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Root-relative path — the CMS SEO panel stores seo_image as
|
|
174
|
-
// "/_emdash/api/media/file/01KS....svg" (already includes the API
|
|
175
|
-
// prefix). Without this branch we'd re-prefix and produce
|
|
176
|
-
// "${siteUrl}/_emdash/api/media/file//_emdash/api/media/file/<id>"
|
|
177
|
-
// which 404s and breaks <meta property="og:image">.
|
|
178
|
-
if (imageRef.startsWith("/")) {
|
|
179
|
-
return siteUrl ? `${siteUrl.replace(TRAILING_SLASH_RE, "")}${imageRef}` : imageRef;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Bare media_id — build the full media API path
|
|
183
|
-
const mediaPath = `/_emdash/api/media/file/${imageRef}`;
|
|
184
|
-
if (siteUrl) {
|
|
185
|
-
return `${siteUrl.replace(TRAILING_SLASH_RE, "")}${mediaPath}`;
|
|
186
|
-
}
|
|
187
|
-
return mediaPath;
|
|
188
|
-
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a stored SEO image reference to a URL.
|
|
3
|
+
*
|
|
4
|
+
* The CMS SEO panel stores `seo_image` in one of these shapes:
|
|
5
|
+
* - an absolute URL (`https://...`) — returned as-is;
|
|
6
|
+
* - a root-relative path that already includes the media API prefix
|
|
7
|
+
* (`/_emdash/api/media/file/01KS....webp`) — prefixed with `siteUrl`;
|
|
8
|
+
* - a bare media id (`01KS...`) — expanded to the media API path, then
|
|
9
|
+
* prefixed with `siteUrl`.
|
|
10
|
+
*
|
|
11
|
+
* Shared by the SEO meta builder (`og:image`) and the sitemap route
|
|
12
|
+
* (`<image:image>`) so both resolve image references identically.
|
|
13
|
+
*/
|
|
14
|
+
const TRAILING_SLASH_RE = /\/$/;
|
|
15
|
+
const ABSOLUTE_URL_RE = /^https?:\/\//i;
|
|
16
|
+
|
|
17
|
+
export function buildSeoImageUrl(imageRef: string, siteUrl?: string): string {
|
|
18
|
+
// Already absolute — use as-is.
|
|
19
|
+
if (ABSOLUTE_URL_RE.test(imageRef)) {
|
|
20
|
+
return imageRef;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Root-relative path (already includes the media API prefix). Without
|
|
24
|
+
// this branch we'd re-prefix and produce a doubled path that 404s.
|
|
25
|
+
if (imageRef.startsWith("/")) {
|
|
26
|
+
return siteUrl ? `${siteUrl.replace(TRAILING_SLASH_RE, "")}${imageRef}` : imageRef;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Bare media id — build the full media API path.
|
|
30
|
+
const mediaPath = `/_emdash/api/media/file/${imageRef}`;
|
|
31
|
+
return siteUrl ? `${siteUrl.replace(TRAILING_SLASH_RE, "")}${mediaPath}` : mediaPath;
|
|
32
|
+
}
|
package/src/settings/index.ts
CHANGED
|
@@ -7,12 +7,19 @@
|
|
|
7
7
|
|
|
8
8
|
import type { Kysely } from "kysely";
|
|
9
9
|
|
|
10
|
+
import { after } from "../after.js";
|
|
10
11
|
import { MediaRepository } from "../database/repositories/media.js";
|
|
11
12
|
import { OptionsRepository } from "../database/repositories/options.js";
|
|
12
13
|
import type { Database } from "../database/types.js";
|
|
13
14
|
import { getDb } from "../loader.js";
|
|
14
15
|
import { peekRequestCache, requestCached } from "../request-cache.js";
|
|
15
16
|
import type { Storage } from "../storage/types.js";
|
|
17
|
+
import {
|
|
18
|
+
createIsolateCache,
|
|
19
|
+
type IsolateCache,
|
|
20
|
+
invalidateIsolateCache,
|
|
21
|
+
isolateCachedAsync,
|
|
22
|
+
} from "../utils/isolate-cache.js";
|
|
16
23
|
import type { SiteSettings, SiteSettingKey, MediaReference, SeoSettings } from "./types.js";
|
|
17
24
|
|
|
18
25
|
/** Prefix for site settings in the options table */
|
|
@@ -27,29 +34,22 @@ const SETTINGS_PREFIX = "site:";
|
|
|
27
34
|
* once-per-isolate. Cross-isolate staleness is bounded by isolate lifetime
|
|
28
35
|
* (workerd typically recycles within minutes); acceptable for chrome.
|
|
29
36
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* cold-isolate readers share the in-flight query.
|
|
37
|
+
* Backed by isolate-cache.ts: concurrent cold-isolate reads coalesce onto one
|
|
38
|
+
* query via a reclaimable single-flight lock and the resolved *value* is
|
|
39
|
+
* cached — never a shared in-flight promise, so a cancelled request can't
|
|
40
|
+
* poison the isolate (see that file's header). Stored on globalThis with a
|
|
41
|
+
* Symbol.for key so Vite SSR chunk duplication doesn't produce two
|
|
42
|
+
* independent caches (same pattern as request-context.ts).
|
|
37
43
|
*/
|
|
38
|
-
interface SiteSettingsHolder {
|
|
39
|
-
version: number;
|
|
40
|
-
cached: Promise<Partial<SiteSettings>> | null;
|
|
41
|
-
cachedVersion: number;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
44
|
const SITE_SETTINGS_CACHE_KEY = Symbol.for("emdash:site-settings");
|
|
45
45
|
const g = globalThis as Record<symbol, unknown>;
|
|
46
|
-
const
|
|
46
|
+
const settingsCache: IsolateCache<Partial<SiteSettings>> =
|
|
47
47
|
// eslint-disable-next-line typescript/no-unsafe-type-assertion -- globalThis singleton pattern (see request-context.ts)
|
|
48
|
-
(g[SITE_SETTINGS_CACHE_KEY] as
|
|
48
|
+
(g[SITE_SETTINGS_CACHE_KEY] as IsolateCache<Partial<SiteSettings>> | undefined) ??
|
|
49
49
|
(() => {
|
|
50
|
-
const
|
|
51
|
-
g[SITE_SETTINGS_CACHE_KEY] =
|
|
52
|
-
return
|
|
50
|
+
const c = createIsolateCache<Partial<SiteSettings>>();
|
|
51
|
+
g[SITE_SETTINGS_CACHE_KEY] = c;
|
|
52
|
+
return c;
|
|
53
53
|
})();
|
|
54
54
|
|
|
55
55
|
/**
|
|
@@ -60,9 +60,7 @@ const holder: SiteSettingsHolder =
|
|
|
60
60
|
* own cached copy until they expire — staleness bounded by isolate lifetime.
|
|
61
61
|
*/
|
|
62
62
|
export function invalidateSiteSettingsCache(): void {
|
|
63
|
-
|
|
64
|
-
holder.cached = null;
|
|
65
|
-
holder.cachedVersion = -1;
|
|
63
|
+
invalidateIsolateCache(settingsCache);
|
|
66
64
|
}
|
|
67
65
|
|
|
68
66
|
/**
|
|
@@ -210,25 +208,19 @@ export async function getSiteSettingWithDb<K extends SiteSettingKey>(
|
|
|
210
208
|
* ```
|
|
211
209
|
*/
|
|
212
210
|
export function getSiteSettings(): Promise<Partial<SiteSettings>> {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
throw error;
|
|
227
|
-
});
|
|
228
|
-
holder.cached = fetchPromise;
|
|
229
|
-
holder.cachedVersion = versionAtCall;
|
|
230
|
-
return fetchPromise;
|
|
231
|
-
});
|
|
211
|
+
// requestCached dedupes within a single request; isolateCachedAsync
|
|
212
|
+
// coalesces across requests and caches the resolved value for the
|
|
213
|
+
// isolate's lifetime without ever sharing an awaitable promise.
|
|
214
|
+
return requestCached("siteSettings", () =>
|
|
215
|
+
isolateCachedAsync(
|
|
216
|
+
settingsCache,
|
|
217
|
+
async () => {
|
|
218
|
+
const db = await getDb();
|
|
219
|
+
return getSiteSettingsWithDb(db);
|
|
220
|
+
},
|
|
221
|
+
{ anchor: (promise) => after(() => promise), ownerTimeoutMs: 30_000 },
|
|
222
|
+
),
|
|
223
|
+
);
|
|
232
224
|
}
|
|
233
225
|
|
|
234
226
|
/**
|
package/src/taxonomies/index.ts
CHANGED
|
@@ -120,15 +120,10 @@ export async function getTaxonomyTerms(
|
|
|
120
120
|
if (locale !== undefined) termsQuery = termsQuery.where("locale", "=", locale);
|
|
121
121
|
const rows = await termsQuery.execute();
|
|
122
122
|
|
|
123
|
-
// Counts are keyed by translation_group (what the pivot stores)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
.select((eb) => eb.fn.count<number>("entry_id").as("count"))
|
|
128
|
-
.groupBy("taxonomy_id")
|
|
129
|
-
.execute();
|
|
130
|
-
const counts = new Map<string, number>();
|
|
131
|
-
for (const row of countsResult) counts.set(row.taxonomy_id, row.count);
|
|
123
|
+
// Counts are keyed by translation_group (what the pivot stores) and are
|
|
124
|
+
// locale-independent, so the aggregate is shared across every taxonomy
|
|
125
|
+
// rendered in this request (Categories + Tags widgets, etc.).
|
|
126
|
+
const counts = await getTaxonomyTermCounts();
|
|
132
127
|
|
|
133
128
|
const flatTerms: TaxonomyTermRow[] = rows.map((row) => ({
|
|
134
129
|
id: row.id,
|
|
@@ -157,6 +152,27 @@ export async function getTaxonomyTerms(
|
|
|
157
152
|
});
|
|
158
153
|
}
|
|
159
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Per-translation-group usage counts across all taxonomies, in one aggregate
|
|
157
|
+
* scan of `content_taxonomies`. Counts are locale-independent (the pivot stores
|
|
158
|
+
* translation_group), so a single request-cached entry serves every taxonomy
|
|
159
|
+
* that renders during the request.
|
|
160
|
+
*/
|
|
161
|
+
function getTaxonomyTermCounts(): Promise<Map<string, number>> {
|
|
162
|
+
return requestCached("taxonomy-term-counts", async () => {
|
|
163
|
+
const db = await getDb();
|
|
164
|
+
const countsResult = await db
|
|
165
|
+
.selectFrom("content_taxonomies")
|
|
166
|
+
.select(["taxonomy_id"])
|
|
167
|
+
.select((eb) => eb.fn.count<number>("entry_id").as("count"))
|
|
168
|
+
.groupBy("taxonomy_id")
|
|
169
|
+
.execute();
|
|
170
|
+
const counts = new Map<string, number>();
|
|
171
|
+
for (const row of countsResult) counts.set(row.taxonomy_id, row.count);
|
|
172
|
+
return counts;
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
160
176
|
/**
|
|
161
177
|
* Get a single term by (taxonomy, slug). Honours the fallback chain — if the
|
|
162
178
|
* slug exists in a fallback locale, we return that row (useful for deep-linking
|
|
@@ -290,10 +306,57 @@ export async function getTermsForEntries(
|
|
|
290
306
|
for (const id of uniqueIds) result.set(id, []);
|
|
291
307
|
if (uniqueIds.length === 0) return result;
|
|
292
308
|
|
|
293
|
-
const db = await getDb();
|
|
294
309
|
const locale = resolveLocale(options.locale);
|
|
310
|
+
const localeKey = locale ?? "*";
|
|
295
311
|
|
|
296
|
-
|
|
312
|
+
// Entry-term hydration (getAllTermsForEntries -> primeEntryTermsCache)
|
|
313
|
+
// seeds the per-entry cache under the same key getEntryTerms uses:
|
|
314
|
+
// `terms:${collection}:${entryId}:${taxonomyName}:${localeKey}`, storing a
|
|
315
|
+
// TaxonomyTerm[] (including `[]` for entries with no terms). Satisfy those
|
|
316
|
+
// from cache and run the batched query only for the ids that missed.
|
|
317
|
+
const missedIds: string[] = [];
|
|
318
|
+
type CacheRead = { id: string; terms: TaxonomyTerm[] } | { id: string; miss: true };
|
|
319
|
+
const cacheReads: Array<Promise<CacheRead>> = [];
|
|
320
|
+
for (const id of uniqueIds) {
|
|
321
|
+
const cached = peekRequestCache<TaxonomyTerm[]>(
|
|
322
|
+
`terms:${collection}:${id}:${taxonomyName}:${localeKey}`,
|
|
323
|
+
);
|
|
324
|
+
if (cached) {
|
|
325
|
+
// A peeked promise can reject (e.g. a sibling getEntryTerms hit a
|
|
326
|
+
// missing table). Treat a rejection as a cache miss so the batched
|
|
327
|
+
// query path -- and its isMissingTableError guard below -- still runs,
|
|
328
|
+
// rather than propagating an uncaught error.
|
|
329
|
+
cacheReads.push(
|
|
330
|
+
cached.then(
|
|
331
|
+
(terms): CacheRead => ({ id, terms }),
|
|
332
|
+
(): CacheRead => ({ id, miss: true }),
|
|
333
|
+
),
|
|
334
|
+
);
|
|
335
|
+
} else {
|
|
336
|
+
missedIds.push(id);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
for (const read of await Promise.all(cacheReads)) {
|
|
340
|
+
if ("miss" in read) {
|
|
341
|
+
missedIds.push(read.id);
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
// Return a private copy. The cached array and its term objects are shared
|
|
345
|
+
// with getEntryTerms/getAllTermsForEntries (primeEntryTermsCache stores
|
|
346
|
+
// the same references), so a caller that mutates the result -- sorting in
|
|
347
|
+
// place, pushing into `children` -- must not poison the cache. The
|
|
348
|
+
// pre-cache implementation always returned freshly built arrays.
|
|
349
|
+
result.set(
|
|
350
|
+
read.id,
|
|
351
|
+
read.terms.map((t) => ({ ...t, children: [...t.children] })),
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (missedIds.length === 0) return result;
|
|
356
|
+
|
|
357
|
+
const db = await getDb();
|
|
358
|
+
|
|
359
|
+
for (const chunk of chunks(missedIds, SQL_BATCH_SIZE)) {
|
|
297
360
|
let rows;
|
|
298
361
|
try {
|
|
299
362
|
let query = db
|
|
@@ -311,7 +374,10 @@ export async function getTermsForEntries(
|
|
|
311
374
|
])
|
|
312
375
|
.where("content_taxonomies.collection", "=", collection)
|
|
313
376
|
.where("content_taxonomies.entry_id", "in", chunk)
|
|
314
|
-
.where("taxonomies.name", "=", taxonomyName)
|
|
377
|
+
.where("taxonomies.name", "=", taxonomyName)
|
|
378
|
+
// Match the order getAllTermsForEntries (the cache primer) uses, so
|
|
379
|
+
// cache-hit and DB-miss entries in one result are ordered consistently.
|
|
380
|
+
.orderBy("taxonomies.label", "asc");
|
|
315
381
|
if (locale !== undefined) query = query.where("taxonomies.locale", "=", locale);
|
|
316
382
|
rows = await query.execute();
|
|
317
383
|
} catch (error) {
|