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
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import { t as MediaRepository } from "./media-JOf3pNkw.mjs";
|
|
2
|
+
import { t as OptionsRepository } from "./options-BPCVnesz.mjs";
|
|
3
|
+
import { n as peekRequestCache, r as requestCached } from "./request-cache-D32LpnmI.mjs";
|
|
4
|
+
import { r as getDb } from "./loader-ZN1ll-d-.mjs";
|
|
5
|
+
|
|
6
|
+
//#region src/after.ts
|
|
7
|
+
const waitUntilReady = (async () => {
|
|
8
|
+
try {
|
|
9
|
+
return (await import("virtual:emdash/wait-until")).waitUntil ?? null;
|
|
10
|
+
} catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
})();
|
|
14
|
+
waitUntilReady.catch(() => {});
|
|
15
|
+
/**
|
|
16
|
+
* Schedule `fn` to run without blocking the response.
|
|
17
|
+
*
|
|
18
|
+
* Errors are caught and logged — a deferred task should never surface
|
|
19
|
+
* as an unhandled rejection because the response is long gone. Callers
|
|
20
|
+
* that care about errors should handle them inside `fn`.
|
|
21
|
+
*/
|
|
22
|
+
function after(fn) {
|
|
23
|
+
const promise = Promise.resolve().then(fn).catch((error) => {
|
|
24
|
+
console.error("[emdash] deferred task failed:", error);
|
|
25
|
+
});
|
|
26
|
+
waitUntilReady.then((waitUntil) => {
|
|
27
|
+
if (waitUntil) waitUntil(promise);
|
|
28
|
+
return null;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/utils/init-lock.ts
|
|
34
|
+
function createInitLock() {
|
|
35
|
+
return {
|
|
36
|
+
ownerStartedAt: null,
|
|
37
|
+
generation: 0
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const DEFAULT_DEADLINE_MS = 15e3;
|
|
41
|
+
const DEFAULT_POLL_MS = 50;
|
|
42
|
+
const MAX_WAIT_HEADROOM_MS = 15e3;
|
|
43
|
+
function sleep(ms) {
|
|
44
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Return the cached value if present, otherwise initialize it under the
|
|
48
|
+
* lock. `init` is responsible for storing the value so that `getCached`
|
|
49
|
+
* returns it on subsequent calls — waiters re-check `getCached` after the
|
|
50
|
+
* owner finishes rather than sharing the owner's promise.
|
|
51
|
+
*
|
|
52
|
+
* `init` receives an `isCurrentClaim` predicate and must gate its cache
|
|
53
|
+
* publication on it: a slow init that was reclaimed past the deadline
|
|
54
|
+
* must not overwrite the value published by the reclaimer (for the
|
|
55
|
+
* runtime singleton that would orphan the reclaimer's active cron
|
|
56
|
+
* scheduler). A losing init should also tear down any side resources it
|
|
57
|
+
* started, since its result will never be published.
|
|
58
|
+
*/
|
|
59
|
+
async function initWithLock(lock, getCached, init, options) {
|
|
60
|
+
const deadlineMs = options?.deadlineMs ?? DEFAULT_DEADLINE_MS;
|
|
61
|
+
const pollMs = options?.pollMs ?? DEFAULT_POLL_MS;
|
|
62
|
+
const maxWaitMs = options?.maxWaitMs ?? deadlineMs + MAX_WAIT_HEADROOM_MS;
|
|
63
|
+
const waitStart = Date.now();
|
|
64
|
+
for (;;) {
|
|
65
|
+
const cached = getCached();
|
|
66
|
+
if (cached !== null && cached !== void 0) return cached;
|
|
67
|
+
const ownerStartedAt = lock.ownerStartedAt;
|
|
68
|
+
if (ownerStartedAt === null || Date.now() - ownerStartedAt > deadlineMs) {
|
|
69
|
+
lock.generation += 1;
|
|
70
|
+
const claim = lock.generation;
|
|
71
|
+
lock.ownerStartedAt = Date.now();
|
|
72
|
+
try {
|
|
73
|
+
const isCurrentClaim = () => lock.generation === claim;
|
|
74
|
+
const initPromise = Promise.resolve().then(() => init(isCurrentClaim));
|
|
75
|
+
options?.anchor?.(initPromise.then(() => void 0, () => void 0));
|
|
76
|
+
return await initPromise;
|
|
77
|
+
} finally {
|
|
78
|
+
if (lock.generation === claim) lock.ownerStartedAt = null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (Date.now() - waitStart > maxWaitMs) throw new Error(`initWithLock: timed out after ${maxWaitMs}ms waiting for initialization`);
|
|
82
|
+
await sleep(pollMs);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region src/utils/isolate-cache.ts
|
|
88
|
+
/**
|
|
89
|
+
* Isolate-lifetime async value cache with single-flight and poison-immunity.
|
|
90
|
+
*
|
|
91
|
+
* Built for the "compute once per isolate, read on every request" caches
|
|
92
|
+
* (site settings, search-health verification, ...). These must coalesce
|
|
93
|
+
* concurrent cold-isolate reads into one query — but the obvious way to do
|
|
94
|
+
* that, caching the in-flight *promise* on an isolate-global and awaiting it
|
|
95
|
+
* from later requests, is unsafe on workerd: if the request that created the
|
|
96
|
+
* promise is cancelled mid-await (client disconnect, context teardown), its
|
|
97
|
+
* continuation never runs, so the promise neither resolves nor rejects. Every
|
|
98
|
+
* later request that awaits that shared promise then hangs until the isolate
|
|
99
|
+
* is evicted (observed as 524s at the 100s wall, near-zero CPU). A `.catch`
|
|
100
|
+
* that clears the cache doesn't help — a cancelled request doesn't reject.
|
|
101
|
+
*
|
|
102
|
+
* This cache stores the resolved *value* (not a promise) and coalesces via
|
|
103
|
+
* `initWithLock`: one request becomes the owner and runs `fetch`, everyone
|
|
104
|
+
* else polls for the published value and never awaits the owner's promise.
|
|
105
|
+
* A cancelled owner can therefore never strand a waiter — the worst case is
|
|
106
|
+
* the lock looks held until `deadlineMs`, then the next caller reclaims. The
|
|
107
|
+
* owner's `fetch` is also anchored (waitUntil) so a cancelled originator's
|
|
108
|
+
* query still completes and populates the cache, and bounded by
|
|
109
|
+
* `ownerTimeoutMs` so a genuinely stuck fetch reclaims instead of hanging.
|
|
110
|
+
*
|
|
111
|
+
* Invalidation bumps `version`; reads compare against the version captured at
|
|
112
|
+
* call time and refetch on mismatch.
|
|
113
|
+
*/
|
|
114
|
+
function createIsolateCache() {
|
|
115
|
+
return {
|
|
116
|
+
value: null,
|
|
117
|
+
hasValue: false,
|
|
118
|
+
version: 0,
|
|
119
|
+
valueVersion: -1,
|
|
120
|
+
lock: createInitLock()
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Force the next `isolateCachedAsync` call to refetch. An in-flight owner
|
|
125
|
+
* fetched at the old version will not publish into the new version, so its
|
|
126
|
+
* result is ignored by subsequent reads.
|
|
127
|
+
*/
|
|
128
|
+
function invalidateIsolateCache(cache) {
|
|
129
|
+
cache.version++;
|
|
130
|
+
cache.hasValue = false;
|
|
131
|
+
cache.value = null;
|
|
132
|
+
cache.valueVersion = -1;
|
|
133
|
+
cache.lock.ownerStartedAt = null;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Headroom between the owner's own timeout and the waiter reclaim deadline.
|
|
137
|
+
* The reclaim deadline must sit *above* `ownerTimeoutMs` so a slow-but-live
|
|
138
|
+
* owner times out (and releases the lock) before a waiter would reclaim it —
|
|
139
|
+
* otherwise a fetch slower than the deadline is superseded before it can
|
|
140
|
+
* publish, and steady traffic turns that into a self-sustaining stampede.
|
|
141
|
+
*/
|
|
142
|
+
const RECLAIM_HEADROOM_MS = 5e3;
|
|
143
|
+
function withTimeout(promise, ms) {
|
|
144
|
+
return new Promise((resolve, reject) => {
|
|
145
|
+
const timer = setTimeout(() => {
|
|
146
|
+
reject(/* @__PURE__ */ new Error(`isolateCachedAsync: owner fetch exceeded ${ms}ms`));
|
|
147
|
+
}, ms);
|
|
148
|
+
promise.then(resolve, reject).finally(() => {
|
|
149
|
+
clearTimeout(timer);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Return the cached value for `cache`, computing it via `fetch` under a
|
|
155
|
+
* single-flight lock on a miss. Concurrent callers coalesce onto one fetch;
|
|
156
|
+
* a cancelled owner cannot poison later callers (see file header).
|
|
157
|
+
*/
|
|
158
|
+
function isolateCachedAsync(cache, fetch, options = {}) {
|
|
159
|
+
const versionAtCall = cache.version;
|
|
160
|
+
const ownerTimeoutMs = options.ownerTimeoutMs !== void 0 && Number.isFinite(options.ownerTimeoutMs) && options.ownerTimeoutMs > 0 ? options.ownerTimeoutMs : void 0;
|
|
161
|
+
const deadlineMs = ownerTimeoutMs === void 0 ? options.deadlineMs : Math.max(options.deadlineMs ?? 0, ownerTimeoutMs + RECLAIM_HEADROOM_MS);
|
|
162
|
+
return initWithLock(cache.lock, () => cache.hasValue && cache.valueVersion === versionAtCall ? { v: cache.value } : null, (isCurrentClaim) => {
|
|
163
|
+
const real = (async () => {
|
|
164
|
+
const value = await fetch();
|
|
165
|
+
if (isCurrentClaim()) {
|
|
166
|
+
cache.value = value;
|
|
167
|
+
cache.hasValue = true;
|
|
168
|
+
cache.valueVersion = versionAtCall;
|
|
169
|
+
}
|
|
170
|
+
return { v: value };
|
|
171
|
+
})();
|
|
172
|
+
options.anchor?.(real.then(() => void 0, () => void 0));
|
|
173
|
+
return ownerTimeoutMs === void 0 ? real : withTimeout(real, ownerTimeoutMs);
|
|
174
|
+
}, {
|
|
175
|
+
deadlineMs,
|
|
176
|
+
pollMs: options.pollMs,
|
|
177
|
+
maxWaitMs: options.maxWaitMs
|
|
178
|
+
}).then((box) => box.v);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
//#endregion
|
|
182
|
+
//#region src/settings/index.ts
|
|
183
|
+
/** Prefix for site settings in the options table */
|
|
184
|
+
const SETTINGS_PREFIX = "site:";
|
|
185
|
+
/**
|
|
186
|
+
* Worker-isolate cache for the resolved `site:*` settings.
|
|
187
|
+
*
|
|
188
|
+
* Site settings (title, logo, SEO defaults) change rarely but are read on
|
|
189
|
+
* every public request. Caching across the isolate's lifetime drops the
|
|
190
|
+
* `options WHERE name LIKE 'site:%'` prefix scan from once-per-request to
|
|
191
|
+
* once-per-isolate. Cross-isolate staleness is bounded by isolate lifetime
|
|
192
|
+
* (workerd typically recycles within minutes); acceptable for chrome.
|
|
193
|
+
*
|
|
194
|
+
* Backed by isolate-cache.ts: concurrent cold-isolate reads coalesce onto one
|
|
195
|
+
* query via a reclaimable single-flight lock and the resolved *value* is
|
|
196
|
+
* cached — never a shared in-flight promise, so a cancelled request can't
|
|
197
|
+
* poison the isolate (see that file's header). Stored on globalThis with a
|
|
198
|
+
* Symbol.for key so Vite SSR chunk duplication doesn't produce two
|
|
199
|
+
* independent caches (same pattern as request-context.ts).
|
|
200
|
+
*/
|
|
201
|
+
const SITE_SETTINGS_CACHE_KEY = Symbol.for("emdash:site-settings");
|
|
202
|
+
const g = globalThis;
|
|
203
|
+
const settingsCache = g[SITE_SETTINGS_CACHE_KEY] ?? (() => {
|
|
204
|
+
const c = createIsolateCache();
|
|
205
|
+
g[SITE_SETTINGS_CACHE_KEY] = c;
|
|
206
|
+
return c;
|
|
207
|
+
})();
|
|
208
|
+
/**
|
|
209
|
+
* Bump the isolate-wide site-settings cache version, forcing the next
|
|
210
|
+
* `getSiteSettings()` to re-query the database.
|
|
211
|
+
*
|
|
212
|
+
* Called from every `site:*` write path. Other isolates still serve their
|
|
213
|
+
* own cached copy until they expire — staleness bounded by isolate lifetime.
|
|
214
|
+
*/
|
|
215
|
+
function invalidateSiteSettingsCache() {
|
|
216
|
+
invalidateIsolateCache(settingsCache);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Type guard for MediaReference values
|
|
220
|
+
*/
|
|
221
|
+
function isMediaReference(value) {
|
|
222
|
+
return typeof value === "object" && value !== null && "mediaId" in value;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Resolve a media reference to include the full URL plus content metadata.
|
|
226
|
+
*
|
|
227
|
+
* Pulls `mimeType` and intrinsic dimensions from the media row so callers
|
|
228
|
+
* can emit correct head tags (e.g. `<link rel="icon" type="image/svg+xml">`,
|
|
229
|
+
* which Chromium requires when the URL has no `.svg` extension) without
|
|
230
|
+
* a second round-trip to the media table.
|
|
231
|
+
*/
|
|
232
|
+
async function resolveMediaReference(mediaRef, db, _storage) {
|
|
233
|
+
if (!mediaRef?.mediaId) return mediaRef;
|
|
234
|
+
try {
|
|
235
|
+
const media = await new MediaRepository(db).findById(mediaRef.mediaId);
|
|
236
|
+
if (media) return {
|
|
237
|
+
...mediaRef,
|
|
238
|
+
url: `/_emdash/api/media/file/${media.storageKey}`,
|
|
239
|
+
contentType: media.mimeType,
|
|
240
|
+
...media.width !== null ? { width: media.width } : {},
|
|
241
|
+
...media.height !== null ? { height: media.height } : {}
|
|
242
|
+
};
|
|
243
|
+
} catch {}
|
|
244
|
+
return mediaRef;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Get a single site setting by key
|
|
248
|
+
*
|
|
249
|
+
* Returns `undefined` if the setting has not been configured.
|
|
250
|
+
* For media settings (logo, favicon), the URL is resolved automatically.
|
|
251
|
+
*
|
|
252
|
+
* @param key - The setting key (e.g., "title", "logo", "social")
|
|
253
|
+
* @returns The setting value, or undefined if not set
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* ```ts
|
|
257
|
+
* import { getSiteSetting } from "emdash";
|
|
258
|
+
*
|
|
259
|
+
* const title = await getSiteSetting("title");
|
|
260
|
+
* const logo = await getSiteSetting("logo");
|
|
261
|
+
* console.log(logo?.url); // Resolved URL
|
|
262
|
+
* ```
|
|
263
|
+
*/
|
|
264
|
+
async function getSiteSetting(key) {
|
|
265
|
+
const primed = peekRequestCache("siteSettings");
|
|
266
|
+
if (primed) return (await primed)[key];
|
|
267
|
+
return requestCached(`siteSetting:${key}`, async () => {
|
|
268
|
+
return getSiteSettingWithDb(key, await getDb());
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Get a single site setting by key (with explicit db)
|
|
273
|
+
*
|
|
274
|
+
* @internal Use `getSiteSetting()` in templates. This variant is for admin routes
|
|
275
|
+
* that already have a database handle.
|
|
276
|
+
*/
|
|
277
|
+
async function getSiteSettingWithDb(key, db, storage = null) {
|
|
278
|
+
const value = await new OptionsRepository(db).get(`${SETTINGS_PREFIX}${key}`);
|
|
279
|
+
if (!value) return;
|
|
280
|
+
if ((key === "logo" || key === "favicon") && isMediaReference(value)) return await resolveMediaReference(value, db, storage);
|
|
281
|
+
if (key === "seo" && value && typeof value === "object") {
|
|
282
|
+
const seo = value;
|
|
283
|
+
if (seo.defaultOgImage) return {
|
|
284
|
+
...seo,
|
|
285
|
+
defaultOgImage: await resolveMediaReference(seo.defaultOgImage, db, storage)
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
return value;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Get all site settings
|
|
292
|
+
*
|
|
293
|
+
* Returns all configured settings. Unset values are undefined.
|
|
294
|
+
* Media references (logo/favicon) are resolved to include URLs.
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* ```ts
|
|
298
|
+
* import { getSiteSettings } from "emdash";
|
|
299
|
+
*
|
|
300
|
+
* const settings = await getSiteSettings();
|
|
301
|
+
* console.log(settings.title); // "My Site"
|
|
302
|
+
* console.log(settings.logo?.url); // "/_emdash/api/media/file/abc123"
|
|
303
|
+
* ```
|
|
304
|
+
*/
|
|
305
|
+
function getSiteSettings() {
|
|
306
|
+
return requestCached("siteSettings", () => isolateCachedAsync(settingsCache, async () => {
|
|
307
|
+
return getSiteSettingsWithDb(await getDb());
|
|
308
|
+
}, {
|
|
309
|
+
anchor: (promise) => after(() => promise),
|
|
310
|
+
ownerTimeoutMs: 3e4
|
|
311
|
+
}));
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Get all site settings (with explicit db)
|
|
315
|
+
*
|
|
316
|
+
* @internal Use `getSiteSettings()` in templates. This variant is for admin routes
|
|
317
|
+
* that already have a database handle.
|
|
318
|
+
*/
|
|
319
|
+
async function getSiteSettingsWithDb(db, storage = null) {
|
|
320
|
+
const allOptions = await new OptionsRepository(db).getByPrefix(SETTINGS_PREFIX);
|
|
321
|
+
const settings = {};
|
|
322
|
+
for (const [key, value] of allOptions) {
|
|
323
|
+
const settingKey = key.replace(SETTINGS_PREFIX, "");
|
|
324
|
+
settings[settingKey] = value;
|
|
325
|
+
}
|
|
326
|
+
const typedSettings = settings;
|
|
327
|
+
if (typedSettings.logo) typedSettings.logo = await resolveMediaReference(typedSettings.logo, db, storage);
|
|
328
|
+
if (typedSettings.favicon) typedSettings.favicon = await resolveMediaReference(typedSettings.favicon, db, storage);
|
|
329
|
+
if (typedSettings.seo?.defaultOgImage) typedSettings.seo = {
|
|
330
|
+
...typedSettings.seo,
|
|
331
|
+
defaultOgImage: await resolveMediaReference(typedSettings.seo.defaultOgImage, db, storage)
|
|
332
|
+
};
|
|
333
|
+
return typedSettings;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Set site settings (internal function used by admin API)
|
|
337
|
+
*
|
|
338
|
+
* Merges provided settings with existing ones. Only provided fields are updated.
|
|
339
|
+
* Media references should include just the mediaId; URLs are resolved on read.
|
|
340
|
+
*
|
|
341
|
+
* @param settings - Partial settings object with values to update
|
|
342
|
+
* @param db - Kysely database instance
|
|
343
|
+
* @returns Promise that resolves when settings are saved
|
|
344
|
+
*
|
|
345
|
+
* @internal
|
|
346
|
+
*
|
|
347
|
+
* @example
|
|
348
|
+
* ```ts
|
|
349
|
+
* // Update multiple settings at once
|
|
350
|
+
* await setSiteSettings({
|
|
351
|
+
* title: "My Site",
|
|
352
|
+
* tagline: "Welcome",
|
|
353
|
+
* logo: { mediaId: "med_123", alt: "Logo" }
|
|
354
|
+
* }, db);
|
|
355
|
+
* ```
|
|
356
|
+
*/
|
|
357
|
+
async function setSiteSettings(settings, db) {
|
|
358
|
+
const options = new OptionsRepository(db);
|
|
359
|
+
const updates = {};
|
|
360
|
+
for (const [key, value] of Object.entries(settings)) if (value !== void 0) updates[`${SETTINGS_PREFIX}${key}`] = value;
|
|
361
|
+
try {
|
|
362
|
+
await options.setMany(updates);
|
|
363
|
+
} finally {
|
|
364
|
+
invalidateSiteSettingsCache();
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Get a single plugin setting by key.
|
|
369
|
+
*
|
|
370
|
+
* Plugin settings are stored in the options table under
|
|
371
|
+
* `plugin:<pluginId>:settings:<key>`.
|
|
372
|
+
*/
|
|
373
|
+
async function getPluginSetting(pluginId, key) {
|
|
374
|
+
return getPluginSettingWithDb(pluginId, key, await getDb());
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Get a single plugin setting by key (with explicit db).
|
|
378
|
+
*
|
|
379
|
+
* @internal Use `getPluginSetting()` in templates and plugin rendering code.
|
|
380
|
+
*/
|
|
381
|
+
async function getPluginSettingWithDb(pluginId, key, db) {
|
|
382
|
+
return await new OptionsRepository(db).get(`plugin:${pluginId}:settings:${key}`) ?? void 0;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Get all persisted plugin settings for a plugin.
|
|
386
|
+
*
|
|
387
|
+
* Defaults declared in `admin.settingsSchema` are not materialized
|
|
388
|
+
* automatically; callers should apply their own fallback defaults.
|
|
389
|
+
*/
|
|
390
|
+
async function getPluginSettings(pluginId) {
|
|
391
|
+
return getPluginSettingsWithDb(pluginId, await getDb());
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Get all persisted plugin settings for a plugin (with explicit db).
|
|
395
|
+
*
|
|
396
|
+
* @internal Use `getPluginSettings()` in templates and plugin rendering code.
|
|
397
|
+
*/
|
|
398
|
+
async function getPluginSettingsWithDb(pluginId, db) {
|
|
399
|
+
const prefix = `plugin:${pluginId}:settings:`;
|
|
400
|
+
const allOptions = await new OptionsRepository(db).getByPrefix(prefix);
|
|
401
|
+
const settings = {};
|
|
402
|
+
for (const [key, value] of allOptions) {
|
|
403
|
+
if (!key.startsWith(prefix)) continue;
|
|
404
|
+
settings[key.slice(prefix.length)] = value;
|
|
405
|
+
}
|
|
406
|
+
return settings;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
//#endregion
|
|
410
|
+
export { getSiteSettingsWithDb as a, createIsolateCache as c, initWithLock as d, after as f, getSiteSettings as i, isolateCachedAsync as l, getPluginSettings as n, invalidateSiteSettingsCache as o, getSiteSetting as r, setSiteSettings as s, getPluginSetting as t, createInitLock as u };
|
|
411
|
+
//# sourceMappingURL=settings-DfxiWY_s.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings-DfxiWY_s.mjs","names":[],"sources":["../src/after.ts","../src/utils/init-lock.ts","../src/utils/isolate-cache.ts","../src/settings/index.ts"],"sourcesContent":["/**\n * Defer work past the HTTP response.\n *\n * Use for bookkeeping that doesn't need to complete before the client\n * gets bytes — writes that record state, maintenance queries, cache\n * refreshes. `after()` hands the promise to the host's lifetime\n * extender when one is available (Cloudflare's `waitUntil` under\n * workerd), or fires-and-forgets on Node (the process lives for the\n * next request anyway).\n *\n * Host binding is resolved lazily via a dynamic import of the\n * `virtual:emdash/wait-until` virtual module. Lazy — rather than a\n * static top-level import — so tools that walk the dist in a plain\n * Node loader (`astro check`, Vitest, etc.) don't trip over the\n * `virtual:` scheme: they'd only fail if they actually called\n * `after()`, which they don't during type-checking.\n */\n\nexport type WaitUntilFn = (promise: Promise<unknown>) => void;\n\n// Resolves to the host's waitUntil if the adapter provided one, or\n// null otherwise. Kicked off once at module load; subsequent `after()`\n// calls see the cached result without re-importing.\nconst waitUntilReady: Promise<WaitUntilFn | null> = (async () => {\n\ttry {\n\t\t// @ts-ignore - virtual module, generated by the Astro integration\n\t\tconst mod = (await import(\"virtual:emdash/wait-until\")) as {\n\t\t\twaitUntil?: WaitUntilFn;\n\t\t};\n\t\treturn mod.waitUntil ?? null;\n\t} catch {\n\t\t// No virtual module available (Node-side tooling, tests without the\n\t\t// integration in scope). Fire-and-forget is the safe fallback.\n\t\treturn null;\n\t}\n})();\n// Surface rejections without making the module-load fail.\nwaitUntilReady.catch(() => {});\n\n/**\n * Schedule `fn` to run without blocking the response.\n *\n * Errors are caught and logged — a deferred task should never surface\n * as an unhandled rejection because the response is long gone. Callers\n * that care about errors should handle them inside `fn`.\n */\nexport function after(fn: () => void | Promise<void>): void {\n\tconst promise = Promise.resolve()\n\t\t.then(fn)\n\t\t.catch((error) => {\n\t\t\tconsole.error(\"[emdash] deferred task failed:\", error);\n\t\t});\n\n\t// Defer the lifetime-extender handoff to the microtask that resolves\n\t// waitUntilReady. On workerd this is effectively instant (the virtual\n\t// module is already loaded in the bundle); on Node the promise\n\t// resolves to null, so this is just one extra microtask and no-op.\n\tvoid waitUntilReady.then((waitUntil) => {\n\t\tif (waitUntil) waitUntil(promise);\n\t\treturn null;\n\t});\n}\n","/**\n * Reclaimable initialization lock for isolate-lifetime singletons.\n *\n * Guards \"first request initializes, everyone else waits\" sections\n * (runtime creation, database init) against a workerd failure mode: if the\n * request that owns the initialization is cancelled mid-await (client\n * disconnect, context teardown), its continuation — including any `finally`\n * that would release the lock — never runs. A plain boolean or shared\n * promise then stays stuck forever and every subsequent request in the\n * isolate hangs until the platform kills it (observed as 524s at the\n * 100-second wall limit, with the isolate poisoned until eviction).\n *\n * This lock instead records *when* the owner started. Waiters poll — we\n * deliberately never await a promise created by another request, which\n * workerd flags — and if the owner has held the lock past `deadlineMs`,\n * the next waiter assumes the owner is dead, reclaims the lock, and runs\n * the initialization itself. Waiters also give up after `maxWaitMs` so a\n * request degrades to an error response rather than hanging.\n */\n\nexport interface InitLock {\n\t/** Epoch ms when the current owner claimed the lock, or null when free. */\n\townerStartedAt: number | null;\n\t/**\n\t * Monotonic claim counter identifying the current owner. Release is\n\t * gated on it: a slow owner that finishes after a waiter has reclaimed\n\t * the lock must not clear the reclaimer's claim — that would let yet\n\t * another caller claim the lock and start a third concurrent init.\n\t */\n\tgeneration: number;\n}\n\nexport function createInitLock(): InitLock {\n\treturn { ownerStartedAt: null, generation: 0 };\n}\n\nexport interface InitLockOptions {\n\t/**\n\t * Reclaim the lock if the owner has held it longer than this. Must be\n\t * comfortably above the slowest legitimate init (cold migrations on a\n\t * contended D1, including the concurrent-migrator wait) — a too-short\n\t * deadline risks two concurrent inits, a too-long one delays recovery\n\t * of a poisoned isolate. Nested locks must compose: an outer lock's\n\t * deadline must exceed the deadline of any lock its init acquires.\n\t */\n\tdeadlineMs?: number;\n\t/** Waiter poll interval. */\n\tpollMs?: number;\n\t/**\n\t * Give up waiting after this long and throw instead of hanging.\n\t * Defaults to `deadlineMs` plus headroom so a waiter always survives\n\t * long enough to reclaim a dead owner before giving up.\n\t */\n\tmaxWaitMs?: number;\n\t/**\n\t * Called with the in-flight init promise (errors pre-swallowed) so the\n\t * caller can hand it to the host's lifetime extender (waitUntil via\n\t * `after()`). If the owning request is cancelled mid-init, the anchored\n\t * promise keeps the context alive: init completes, populates the cache,\n\t * and the `finally` below releases the lock — preventing the poisoning\n\t * instead of merely recovering from it via reclaim.\n\t */\n\tanchor?: (promise: Promise<void>) => void;\n}\n\nconst DEFAULT_DEADLINE_MS = 15_000;\nconst DEFAULT_POLL_MS = 50;\nconst MAX_WAIT_HEADROOM_MS = 15_000;\n\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Return the cached value if present, otherwise initialize it under the\n * lock. `init` is responsible for storing the value so that `getCached`\n * returns it on subsequent calls — waiters re-check `getCached` after the\n * owner finishes rather than sharing the owner's promise.\n *\n * `init` receives an `isCurrentClaim` predicate and must gate its cache\n * publication on it: a slow init that was reclaimed past the deadline\n * must not overwrite the value published by the reclaimer (for the\n * runtime singleton that would orphan the reclaimer's active cron\n * scheduler). A losing init should also tear down any side resources it\n * started, since its result will never be published.\n */\nexport async function initWithLock<T>(\n\tlock: InitLock,\n\tgetCached: () => T | null | undefined,\n\tinit: (isCurrentClaim: () => boolean) => Promise<T>,\n\toptions?: InitLockOptions,\n): Promise<T> {\n\tconst deadlineMs = options?.deadlineMs ?? DEFAULT_DEADLINE_MS;\n\tconst pollMs = options?.pollMs ?? DEFAULT_POLL_MS;\n\tconst maxWaitMs = options?.maxWaitMs ?? deadlineMs + MAX_WAIT_HEADROOM_MS;\n\t// Date.now() is deliberate and only works because every loop iteration\n\t// awaits: in workerd the clock only advances across I/O, so a sync spin\n\t// would never observe the deadline. Don't \"optimize\" away the sleep.\n\tconst waitStart = Date.now();\n\n\tfor (;;) {\n\t\tconst cached = getCached();\n\t\tif (cached !== null && cached !== undefined) {\n\t\t\treturn cached;\n\t\t}\n\n\t\tconst ownerStartedAt = lock.ownerStartedAt;\n\t\tif (ownerStartedAt === null || Date.now() - ownerStartedAt > deadlineMs) {\n\t\t\t// Free, or the owner has been gone past the deadline — claim it.\n\t\t\t// Synchronous between awaits, so two waiters can't both claim.\n\t\t\tlock.generation += 1;\n\t\t\tconst claim = lock.generation;\n\t\t\tlock.ownerStartedAt = Date.now();\n\t\t\ttry {\n\t\t\t\t// Promise.resolve().then(...) so a synchronous throw from\n\t\t\t\t// init still becomes a rejection after the anchor attaches.\n\t\t\t\tconst isCurrentClaim = () => lock.generation === claim;\n\t\t\t\tconst initPromise = Promise.resolve().then(() => init(isCurrentClaim));\n\t\t\t\toptions?.anchor?.(\n\t\t\t\t\tinitPromise.then(\n\t\t\t\t\t\t() => undefined,\n\t\t\t\t\t\t() => undefined,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\treturn await initPromise;\n\t\t\t} finally {\n\t\t\t\t// If this request dies mid-init unanchored this never runs;\n\t\t\t\t// the next waiter reclaims after deadlineMs instead. Release\n\t\t\t\t// only while still the current owner: a reclaimer may have\n\t\t\t\t// taken the lock while this (slow) init was running, and\n\t\t\t\t// clearing its claim would admit a third concurrent init.\n\t\t\t\tif (lock.generation === claim) {\n\t\t\t\t\tlock.ownerStartedAt = null;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (Date.now() - waitStart > maxWaitMs) {\n\t\t\tthrow new Error(`initWithLock: timed out after ${maxWaitMs}ms waiting for initialization`);\n\t\t}\n\t\tawait sleep(pollMs);\n\t}\n}\n","/**\n * Isolate-lifetime async value cache with single-flight and poison-immunity.\n *\n * Built for the \"compute once per isolate, read on every request\" caches\n * (site settings, search-health verification, ...). These must coalesce\n * concurrent cold-isolate reads into one query — but the obvious way to do\n * that, caching the in-flight *promise* on an isolate-global and awaiting it\n * from later requests, is unsafe on workerd: if the request that created the\n * promise is cancelled mid-await (client disconnect, context teardown), its\n * continuation never runs, so the promise neither resolves nor rejects. Every\n * later request that awaits that shared promise then hangs until the isolate\n * is evicted (observed as 524s at the 100s wall, near-zero CPU). A `.catch`\n * that clears the cache doesn't help — a cancelled request doesn't reject.\n *\n * This cache stores the resolved *value* (not a promise) and coalesces via\n * `initWithLock`: one request becomes the owner and runs `fetch`, everyone\n * else polls for the published value and never awaits the owner's promise.\n * A cancelled owner can therefore never strand a waiter — the worst case is\n * the lock looks held until `deadlineMs`, then the next caller reclaims. The\n * owner's `fetch` is also anchored (waitUntil) so a cancelled originator's\n * query still completes and populates the cache, and bounded by\n * `ownerTimeoutMs` so a genuinely stuck fetch reclaims instead of hanging.\n *\n * Invalidation bumps `version`; reads compare against the version captured at\n * call time and refetch on mismatch.\n */\n\nimport { createInitLock, type InitLock, initWithLock } from \"./init-lock.js\";\n\nexport interface IsolateCache<T> {\n\t/** Last resolved value, valid only when `hasValue` is true. */\n\tvalue: T | null;\n\t/**\n\t * Presence flag, separate from `value` so that falsy/`undefined`/`void`\n\t * results cache correctly (a plain null check can't distinguish \"cached\n\t * undefined\" from \"never fetched\").\n\t */\n\thasValue: boolean;\n\t/** Invalidation counter; bumped by `invalidateIsolateCache`. */\n\tversion: number;\n\t/** The `version` the cached value was fetched at. */\n\tvalueVersion: number;\n\t/** Reclaimable single-flight lock (see init-lock.ts). */\n\tlock: InitLock;\n}\n\nexport function createIsolateCache<T>(): IsolateCache<T> {\n\treturn { value: null, hasValue: false, version: 0, valueVersion: -1, lock: createInitLock() };\n}\n\n/**\n * Force the next `isolateCachedAsync` call to refetch. An in-flight owner\n * fetched at the old version will not publish into the new version, so its\n * result is ignored by subsequent reads.\n */\nexport function invalidateIsolateCache(cache: IsolateCache<unknown>): void {\n\tcache.version++;\n\tcache.hasValue = false;\n\tcache.value = null;\n\tcache.valueVersion = -1;\n\t// Free the single-flight lock so a reader at the new version starts the\n\t// refetch immediately instead of waiting out a stale owner's deadline. A\n\t// still-running old-version owner can neither publish into the new version\n\t// (version gate) nor clobber a new owner (claim gate), so releasing here\n\t// is safe; the worst case is one brief duplicate fetch.\n\tcache.lock.ownerStartedAt = null;\n}\n\n/**\n * Headroom between the owner's own timeout and the waiter reclaim deadline.\n * The reclaim deadline must sit *above* `ownerTimeoutMs` so a slow-but-live\n * owner times out (and releases the lock) before a waiter would reclaim it —\n * otherwise a fetch slower than the deadline is superseded before it can\n * publish, and steady traffic turns that into a self-sustaining stampede.\n */\nconst RECLAIM_HEADROOM_MS = 5_000;\n\nexport interface IsolateCachedOptions {\n\t/**\n\t * Hand the in-flight fetch to the host's lifetime extender (waitUntil via\n\t * `after()`), so a cancelled originating request still drives it to\n\t * completion and populates the cache.\n\t */\n\tanchor?: (promise: Promise<void>) => void;\n\t/** Reclaim the single-flight lock if the owner holds it past this. */\n\tdeadlineMs?: number;\n\t/** Waiter poll interval. */\n\tpollMs?: number;\n\t/** Waiter gives up and throws after this long rather than hanging. */\n\tmaxWaitMs?: number;\n\t/**\n\t * Bound the owner's own `fetch`: if it doesn't settle within this, the\n\t * owner rejects (and releases the lock) instead of waiting indefinitely.\n\t * The anchored copy keeps running, so a slow-but-live fetch can still\n\t * publish for a later caller. Omit to leave the owner unbounded.\n\t */\n\townerTimeoutMs?: number;\n}\n\n/** Boxed cache hit so a `void`/falsy value is still distinguishable from a miss. */\ninterface Box<T> {\n\tv: T;\n}\n\nfunction withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {\n\treturn new Promise<T>((resolve, reject) => {\n\t\tconst timer = setTimeout(() => {\n\t\t\treject(new Error(`isolateCachedAsync: owner fetch exceeded ${ms}ms`));\n\t\t}, ms);\n\t\t// Settle from the underlying promise (whichever wins the race with the\n\t\t// timer), and always clear the timer so a resolved fetch doesn't leave\n\t\t// a pending timeout holding the isolate alive.\n\t\tpromise.then(resolve, reject).finally(() => {\n\t\t\tclearTimeout(timer);\n\t\t});\n\t});\n}\n\n/**\n * Return the cached value for `cache`, computing it via `fetch` under a\n * single-flight lock on a miss. Concurrent callers coalesce onto one fetch;\n * a cancelled owner cannot poison later callers (see file header).\n */\nexport function isolateCachedAsync<T>(\n\tcache: IsolateCache<T>,\n\tfetch: () => Promise<T>,\n\toptions: IsolateCachedOptions = {},\n): Promise<T> {\n\t// Capture the version once: a value published at this version satisfies\n\t// this call; an invalidation that lands mid-fetch makes the published\n\t// value stale for *later* calls (which captured the newer version) but\n\t// still valid for this one.\n\tconst versionAtCall = cache.version;\n\n\t// Ignore a non-positive / non-finite owner timeout rather than letting it\n\t// degenerate into an instant-reject (setTimeout coerces NaN/0 to ~0ms).\n\tconst ownerTimeoutMs =\n\t\toptions.ownerTimeoutMs !== undefined &&\n\t\tNumber.isFinite(options.ownerTimeoutMs) &&\n\t\toptions.ownerTimeoutMs > 0\n\t\t\t? options.ownerTimeoutMs\n\t\t\t: undefined;\n\n\t// Keep the reclaim deadline above the owner timeout (see RECLAIM_HEADROOM_MS):\n\t// the owner's own timeout, not a waiter reclaim, is the primary release.\n\tconst deadlineMs =\n\t\townerTimeoutMs === undefined\n\t\t\t? options.deadlineMs\n\t\t\t: Math.max(options.deadlineMs ?? 0, ownerTimeoutMs + RECLAIM_HEADROOM_MS);\n\n\treturn initWithLock<Box<T>>(\n\t\tcache.lock,\n\t\t() =>\n\t\t\tcache.hasValue && cache.valueVersion === versionAtCall\n\t\t\t\t? // eslint-disable-next-line typescript/no-unsafe-type-assertion -- hasValue gates that `value` holds a real T\n\t\t\t\t\t({ v: cache.value as T } satisfies Box<T>)\n\t\t\t\t: null,\n\t\t(isCurrentClaim) => {\n\t\t\t// The real work, anchored independently so a cancelled owner's\n\t\t\t// fetch still settles and publishes. Publication is gated on the\n\t\t\t// claim so a reclaimed slow owner can't clobber the reclaimer's\n\t\t\t// value (same contract as initWithLock's own callers).\n\t\t\tconst real = (async (): Promise<Box<T>> => {\n\t\t\t\tconst value = await fetch();\n\t\t\t\tif (isCurrentClaim()) {\n\t\t\t\t\tcache.value = value;\n\t\t\t\t\tcache.hasValue = true;\n\t\t\t\t\tcache.valueVersion = versionAtCall;\n\t\t\t\t}\n\t\t\t\treturn { v: value };\n\t\t\t})();\n\t\t\t// Anchor the real fetch (not the timeout race): this is what must\n\t\t\t// survive a cancelled owner and run to publication. initWithLock is\n\t\t\t// left to manage only the lock; we don't double-anchor.\n\t\t\toptions.anchor?.(\n\t\t\t\treal.then(\n\t\t\t\t\t() => undefined,\n\t\t\t\t\t() => undefined,\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn ownerTimeoutMs === undefined ? real : withTimeout(real, ownerTimeoutMs);\n\t\t},\n\t\t{\n\t\t\tdeadlineMs,\n\t\t\tpollMs: options.pollMs,\n\t\t\tmaxWaitMs: options.maxWaitMs,\n\t\t},\n\t).then((box) => box.v);\n}\n","/**\n * Site Settings API\n *\n * Functions for getting and setting global site configuration.\n * Settings are stored in the options table with 'site:' prefix.\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport { after } from \"../after.js\";\nimport { MediaRepository } from \"../database/repositories/media.js\";\nimport { OptionsRepository } from \"../database/repositories/options.js\";\nimport type { Database } from \"../database/types.js\";\nimport { getDb } from \"../loader.js\";\nimport { peekRequestCache, requestCached } from \"../request-cache.js\";\nimport type { Storage } from \"../storage/types.js\";\nimport {\n\tcreateIsolateCache,\n\ttype IsolateCache,\n\tinvalidateIsolateCache,\n\tisolateCachedAsync,\n} from \"../utils/isolate-cache.js\";\nimport type { SiteSettings, SiteSettingKey, MediaReference, SeoSettings } from \"./types.js\";\n\n/** Prefix for site settings in the options table */\nconst SETTINGS_PREFIX = \"site:\";\n\n/**\n * Worker-isolate cache for the resolved `site:*` settings.\n *\n * Site settings (title, logo, SEO defaults) change rarely but are read on\n * every public request. Caching across the isolate's lifetime drops the\n * `options WHERE name LIKE 'site:%'` prefix scan from once-per-request to\n * once-per-isolate. Cross-isolate staleness is bounded by isolate lifetime\n * (workerd typically recycles within minutes); acceptable for chrome.\n *\n * Backed by isolate-cache.ts: concurrent cold-isolate reads coalesce onto one\n * query via a reclaimable single-flight lock and the resolved *value* is\n * cached — never a shared in-flight promise, so a cancelled request can't\n * poison the isolate (see that file's header). Stored on globalThis with a\n * Symbol.for key so Vite SSR chunk duplication doesn't produce two\n * independent caches (same pattern as request-context.ts).\n */\nconst SITE_SETTINGS_CACHE_KEY = Symbol.for(\"emdash:site-settings\");\nconst g = globalThis as Record<symbol, unknown>;\nconst settingsCache: IsolateCache<Partial<SiteSettings>> =\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- globalThis singleton pattern (see request-context.ts)\n\t(g[SITE_SETTINGS_CACHE_KEY] as IsolateCache<Partial<SiteSettings>> | undefined) ??\n\t(() => {\n\t\tconst c = createIsolateCache<Partial<SiteSettings>>();\n\t\tg[SITE_SETTINGS_CACHE_KEY] = c;\n\t\treturn c;\n\t})();\n\n/**\n * Bump the isolate-wide site-settings cache version, forcing the next\n * `getSiteSettings()` to re-query the database.\n *\n * Called from every `site:*` write path. Other isolates still serve their\n * own cached copy until they expire — staleness bounded by isolate lifetime.\n */\nexport function invalidateSiteSettingsCache(): void {\n\tinvalidateIsolateCache(settingsCache);\n}\n\n/**\n * Type guard for MediaReference values\n */\nfunction isMediaReference(value: unknown): value is MediaReference {\n\treturn typeof value === \"object\" && value !== null && \"mediaId\" in value;\n}\n\n/**\n * Resolve a media reference to include the full URL plus content metadata.\n *\n * Pulls `mimeType` and intrinsic dimensions from the media row so callers\n * can emit correct head tags (e.g. `<link rel=\"icon\" type=\"image/svg+xml\">`,\n * which Chromium requires when the URL has no `.svg` extension) without\n * a second round-trip to the media table.\n */\nasync function resolveMediaReference(\n\tmediaRef: MediaReference | undefined,\n\tdb: Kysely<Database>,\n\t_storage: Storage | null,\n): Promise<MediaReference | undefined> {\n\tif (!mediaRef?.mediaId) {\n\t\treturn mediaRef;\n\t}\n\n\ttry {\n\t\tconst mediaRepo = new MediaRepository(db);\n\t\tconst media = await mediaRepo.findById(mediaRef.mediaId);\n\n\t\tif (media) {\n\t\t\t// Construct URL using the same pattern as API handlers\n\t\t\treturn {\n\t\t\t\t...mediaRef,\n\t\t\t\turl: `/_emdash/api/media/file/${media.storageKey}`,\n\t\t\t\tcontentType: media.mimeType,\n\t\t\t\t...(media.width !== null ? { width: media.width } : {}),\n\t\t\t\t...(media.height !== null ? { height: media.height } : {}),\n\t\t\t};\n\t\t}\n\t} catch {\n\t\t// If media not found or error, return the reference as-is\n\t}\n\n\treturn mediaRef;\n}\n\n/**\n * Get a single site setting by key\n *\n * Returns `undefined` if the setting has not been configured.\n * For media settings (logo, favicon), the URL is resolved automatically.\n *\n * @param key - The setting key (e.g., \"title\", \"logo\", \"social\")\n * @returns The setting value, or undefined if not set\n *\n * @example\n * ```ts\n * import { getSiteSetting } from \"emdash\";\n *\n * const title = await getSiteSetting(\"title\");\n * const logo = await getSiteSetting(\"logo\");\n * console.log(logo?.url); // Resolved URL\n * ```\n */\nexport async function getSiteSetting<K extends SiteSettingKey>(\n\tkey: K,\n): Promise<SiteSettings[K] | undefined> {\n\t// If `getSiteSettings()` has already been called in this request,\n\t// read from that (request-cached) batch rather than firing a second\n\t// options-table query. Common layout: a Base template pulls the\n\t// whole settings object up-front, then `EmDashHead` or a plugin\n\t// asks for one key — no reason the singular call should round-trip\n\t// again.\n\tconst primed = peekRequestCache<Partial<SiteSettings>>(\"siteSettings\");\n\tif (primed) {\n\t\tconst settings = await primed;\n\t\treturn settings[key];\n\t}\n\n\t// Otherwise cache per-key. Templates that pull several settings\n\t// independently still share the in-flight query for each one.\n\treturn requestCached(`siteSetting:${key}`, async () => {\n\t\tconst db = await getDb();\n\t\treturn getSiteSettingWithDb(key, db);\n\t});\n}\n\n/**\n * Get a single site setting by key (with explicit db)\n *\n * @internal Use `getSiteSetting()` in templates. This variant is for admin routes\n * that already have a database handle.\n */\nexport async function getSiteSettingWithDb<K extends SiteSettingKey>(\n\tkey: K,\n\tdb: Kysely<Database>,\n\tstorage: Storage | null = null,\n): Promise<SiteSettings[K] | undefined> {\n\tconst options = new OptionsRepository(db);\n\tconst value = await options.get<SiteSettings[K]>(`${SETTINGS_PREFIX}${key}`);\n\n\tif (!value) {\n\t\treturn undefined;\n\t}\n\n\t// Resolve media references if needed.\n\t// TS cannot narrow generic K from key equality checks — this is a known limitation.\n\t// We use the non-generic getSiteSettingsWithDb for media resolution instead.\n\tif ((key === \"logo\" || key === \"favicon\") && isMediaReference(value)) {\n\t\tconst resolved = await resolveMediaReference(value, db, storage);\n\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- TS can't narrow generic K from key equality; resolved type is correct\n\t\treturn resolved as SiteSettings[K] | undefined;\n\t}\n\n\tif (key === \"seo\" && value && typeof value === \"object\") {\n\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- TS can't narrow generic K from key equality\n\t\tconst seo = value as SeoSettings;\n\t\tif (seo.defaultOgImage) {\n\t\t\tconst resolved = {\n\t\t\t\t...seo,\n\t\t\t\tdefaultOgImage: await resolveMediaReference(seo.defaultOgImage, db, storage),\n\t\t\t};\n\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- TS can't narrow generic K from key equality\n\t\t\treturn resolved as SiteSettings[K] | undefined;\n\t\t}\n\t}\n\n\treturn value;\n}\n\n/**\n * Get all site settings\n *\n * Returns all configured settings. Unset values are undefined.\n * Media references (logo/favicon) are resolved to include URLs.\n *\n * @example\n * ```ts\n * import { getSiteSettings } from \"emdash\";\n *\n * const settings = await getSiteSettings();\n * console.log(settings.title); // \"My Site\"\n * console.log(settings.logo?.url); // \"/_emdash/api/media/file/abc123\"\n * ```\n */\nexport function getSiteSettings(): Promise<Partial<SiteSettings>> {\n\t// requestCached dedupes within a single request; isolateCachedAsync\n\t// coalesces across requests and caches the resolved value for the\n\t// isolate's lifetime without ever sharing an awaitable promise.\n\treturn requestCached(\"siteSettings\", () =>\n\t\tisolateCachedAsync(\n\t\t\tsettingsCache,\n\t\t\tasync () => {\n\t\t\t\tconst db = await getDb();\n\t\t\t\treturn getSiteSettingsWithDb(db);\n\t\t\t},\n\t\t\t{ anchor: (promise) => after(() => promise), ownerTimeoutMs: 30_000 },\n\t\t),\n\t);\n}\n\n/**\n * Get all site settings (with explicit db)\n *\n * @internal Use `getSiteSettings()` in templates. This variant is for admin routes\n * that already have a database handle.\n */\nexport async function getSiteSettingsWithDb(\n\tdb: Kysely<Database>,\n\tstorage: Storage | null = null,\n): Promise<Partial<SiteSettings>> {\n\tconst options = new OptionsRepository(db);\n\tconst allOptions = await options.getByPrefix(SETTINGS_PREFIX);\n\n\tconst settings: Record<string, unknown> = {};\n\n\t// Convert Map to settings object, removing the prefix\n\tfor (const [key, value] of allOptions) {\n\t\tconst settingKey = key.replace(SETTINGS_PREFIX, \"\");\n\t\tsettings[settingKey] = value;\n\t}\n\n\tconst typedSettings = settings as Partial<SiteSettings>;\n\n\t// Resolve media references\n\tif (typedSettings.logo) {\n\t\ttypedSettings.logo = await resolveMediaReference(typedSettings.logo, db, storage);\n\t}\n\tif (typedSettings.favicon) {\n\t\ttypedSettings.favicon = await resolveMediaReference(typedSettings.favicon, db, storage);\n\t}\n\tif (typedSettings.seo?.defaultOgImage) {\n\t\ttypedSettings.seo = {\n\t\t\t...typedSettings.seo,\n\t\t\tdefaultOgImage: await resolveMediaReference(typedSettings.seo.defaultOgImage, db, storage),\n\t\t};\n\t}\n\n\treturn typedSettings;\n}\n\n/**\n * Set site settings (internal function used by admin API)\n *\n * Merges provided settings with existing ones. Only provided fields are updated.\n * Media references should include just the mediaId; URLs are resolved on read.\n *\n * @param settings - Partial settings object with values to update\n * @param db - Kysely database instance\n * @returns Promise that resolves when settings are saved\n *\n * @internal\n *\n * @example\n * ```ts\n * // Update multiple settings at once\n * await setSiteSettings({\n * title: \"My Site\",\n * tagline: \"Welcome\",\n * logo: { mediaId: \"med_123\", alt: \"Logo\" }\n * }, db);\n * ```\n */\nexport async function setSiteSettings(\n\tsettings: Partial<SiteSettings>,\n\tdb: Kysely<Database>,\n): Promise<void> {\n\tconst options = new OptionsRepository(db);\n\n\t// Convert settings to options format\n\tconst updates: Record<string, unknown> = {};\n\tfor (const [key, value] of Object.entries(settings)) {\n\t\tif (value !== undefined) {\n\t\t\tupdates[`${SETTINGS_PREFIX}${key}`] = value;\n\t\t}\n\t}\n\n\ttry {\n\t\tawait options.setMany(updates);\n\t} finally {\n\t\tinvalidateSiteSettingsCache();\n\t}\n}\n\n/**\n * Get a single plugin setting by key.\n *\n * Plugin settings are stored in the options table under\n * `plugin:<pluginId>:settings:<key>`.\n */\nexport async function getPluginSetting<T = unknown>(\n\tpluginId: string,\n\tkey: string,\n): Promise<T | undefined> {\n\tconst db = await getDb();\n\treturn getPluginSettingWithDb<T>(pluginId, key, db);\n}\n\n/**\n * Get a single plugin setting by key (with explicit db).\n *\n * @internal Use `getPluginSetting()` in templates and plugin rendering code.\n */\nexport async function getPluginSettingWithDb<T = unknown>(\n\tpluginId: string,\n\tkey: string,\n\tdb: Kysely<Database>,\n): Promise<T | undefined> {\n\tconst options = new OptionsRepository(db);\n\tconst value = await options.get<T>(`plugin:${pluginId}:settings:${key}`);\n\treturn value ?? undefined;\n}\n\n/**\n * Get all persisted plugin settings for a plugin.\n *\n * Defaults declared in `admin.settingsSchema` are not materialized\n * automatically; callers should apply their own fallback defaults.\n */\nexport async function getPluginSettings(pluginId: string): Promise<Record<string, unknown>> {\n\tconst db = await getDb();\n\treturn getPluginSettingsWithDb(pluginId, db);\n}\n\n/**\n * Get all persisted plugin settings for a plugin (with explicit db).\n *\n * @internal Use `getPluginSettings()` in templates and plugin rendering code.\n */\nexport async function getPluginSettingsWithDb(\n\tpluginId: string,\n\tdb: Kysely<Database>,\n): Promise<Record<string, unknown>> {\n\tconst prefix = `plugin:${pluginId}:settings:`;\n\tconst options = new OptionsRepository(db);\n\tconst allOptions = await options.getByPrefix(prefix);\n\n\tconst settings: Record<string, unknown> = {};\n\tfor (const [key, value] of allOptions) {\n\t\tif (!key.startsWith(prefix)) {\n\t\t\tcontinue;\n\t\t}\n\t\tsettings[key.slice(prefix.length)] = value;\n\t}\n\n\treturn settings;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup-complete-
|
|
1
|
+
{"version":3,"file":"setup-complete-yvPE4OsP.mjs","names":[],"sources":["../src/api/setup-complete.ts"],"sourcesContent":["/**\n * Shared setup completion logic.\n *\n * Called by OAuth callbacks and the passkey verify step when the first user\n * is created during setup. Persists site title/tagline from setup state\n * and marks setup as complete.\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport { OptionsRepository } from \"../database/repositories/options.js\";\nimport type { Database } from \"../database/types.js\";\n\n/**\n * Finalize setup after the first admin user is created.\n *\n * Reads the setup_state option (written by the setup wizard's step 1),\n * persists site_title and site_tagline, then marks setup complete.\n *\n * Safe to call multiple times — checks setup_complete first and no-ops\n * if already done.\n */\nexport async function finalizeSetup(db: Kysely<Database>): Promise<void> {\n\tconst options = new OptionsRepository(db);\n\n\tconst setupComplete = await options.get(\"emdash:setup_complete\");\n\tif (setupComplete === true || setupComplete === \"true\") return;\n\n\t// Persist site title/tagline from setup state (stored in step 1)\n\tconst setupState = await options.get<Record<string, unknown>>(\"emdash:setup_state\");\n\tif (setupState?.title && typeof setupState.title === \"string\") {\n\t\tawait options.set(\"emdash:site_title\", setupState.title);\n\t}\n\tif (setupState?.tagline && typeof setupState.tagline === \"string\") {\n\t\tawait options.set(\"emdash:site_tagline\", setupState.tagline);\n\t}\n\n\tawait options.set(\"emdash:setup_complete\", true);\n\tawait options.delete(\"emdash:setup_state\");\n}\n"],"mappings":";;;;;;;;;;;;AAsBA,eAAsB,cAAc,IAAqC;CACxE,MAAM,UAAU,IAAI,kBAAkB,GAAG;CAEzC,MAAM,gBAAgB,MAAM,QAAQ,IAAI,wBAAwB;AAChE,KAAI,kBAAkB,QAAQ,kBAAkB,OAAQ;CAGxD,MAAM,aAAa,MAAM,QAAQ,IAA6B,qBAAqB;AACnF,KAAI,YAAY,SAAS,OAAO,WAAW,UAAU,SACpD,OAAM,QAAQ,IAAI,qBAAqB,WAAW,MAAM;AAEzD,KAAI,YAAY,WAAW,OAAO,WAAW,YAAY,SACxD,OAAM,QAAQ,IAAI,uBAAuB,WAAW,QAAQ;AAG7D,OAAM,QAAQ,IAAI,yBAAyB,KAAK;AAChD,OAAM,QAAQ,OAAO,qBAAqB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup-nonce-
|
|
1
|
+
{"version":3,"file":"setup-nonce-C9aFzb94.mjs","names":[],"sources":["../src/auth/setup-nonce.ts"],"sourcesContent":["/**\n * Session binding for the first-setup admin-creation flow.\n *\n * Shared constants for the nonce cookie that ties /_emdash/api/setup/admin\n * and /_emdash/api/setup/admin/verify to the same browser. Without this\n * binding, any unauthenticated caller could POST /setup/admin during the\n * setup window and substitute their own email into the stored setup state\n * before the legitimate admin completes passkey verification.\n *\n * Implementation lives in the two route handlers; this module is just\n * the name / lifetime so both ends agree.\n */\n\n/** Cookie name carrying the setup-admin session nonce. */\nexport const SETUP_NONCE_COOKIE = \"emdash_setup_nonce\";\n\n/**\n * Cookie max-age in seconds. One hour is plenty of time to complete\n * a passkey registration; if the user lingers longer the admin step\n * can simply be retried.\n */\nexport const SETUP_NONCE_MAX_AGE_SECONDS = 60 * 60;\n"],"mappings":";;;;;;;;;;;;;;AAcA,MAAa,qBAAqB;;;;;;AAOlC,MAAa,8BAA8B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"site-url-
|
|
1
|
+
{"version":3,"file":"site-url-CnHlmAs9.mjs","names":[],"sources":["../src/api/site-url.ts"],"sourcesContent":["/**\n * Resolve the canonical site base URL for use in outbound links (emails, etc.).\n *\n * Uses the stored `emdash:site_url` (set during setup on the real domain)\n * so that Host header spoofing in later requests cannot redirect users to\n * attacker-controlled domains.\n *\n * Falls back to the request URL only if no stored value exists (pre-setup).\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport { OptionsRepository } from \"../database/repositories/options.js\";\nimport type { Database } from \"../database/types.js\";\n\nexport async function getSiteBaseUrl(db: Kysely<Database>, request: Request): Promise<string> {\n\tconst options = new OptionsRepository(db);\n\tconst storedUrl = await options.get<string>(\"emdash:site_url\");\n\tif (storedUrl) {\n\t\treturn `${storedUrl}/_emdash`;\n\t}\n\t// Fallback: derive from request (only reached before setup completes)\n\tconst url = new URL(request.url);\n\treturn `${url.protocol}//${url.host}/_emdash`;\n}\n"],"mappings":";;;AAeA,eAAsB,eAAe,IAAsB,SAAmC;CAE7F,MAAM,YAAY,MADF,IAAI,kBAAkB,GAAG,CACT,IAAY,kBAAkB;AAC9D,KAAI,UACH,QAAO,GAAG,UAAU;CAGrB,MAAM,MAAM,IAAI,IAAI,QAAQ,IAAI;AAChC,QAAO,GAAG,IAAI,SAAS,IAAI,IAAI,KAAK"}
|
package/dist/storage/local.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as ListOptions, d as Storage, l as SignedUploadOptions, n as DownloadResult, o as ListResult, p as UploadResult, s as LocalStorageConfig, u as SignedUploadUrl } from "../types-
|
|
1
|
+
import { a as ListOptions, d as Storage, l as SignedUploadOptions, n as DownloadResult, o as ListResult, p as UploadResult, s as LocalStorageConfig, u as SignedUploadUrl } from "../types-D4kUqbHh.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/storage/local.d.ts
|
|
4
4
|
/**
|
package/dist/storage/s3.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as ListOptions, c as S3StorageConfig, d as Storage, l as SignedUploadOptions, n as DownloadResult, o as ListResult, p as UploadResult, u as SignedUploadUrl } from "../types-
|
|
1
|
+
import { a as ListOptions, c as S3StorageConfig, d as Storage, l as SignedUploadOptions, n as DownloadResult, o as ListResult, p as UploadResult, u as SignedUploadUrl } from "../types-D4kUqbHh.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/storage/s3.d.ts
|
|
4
4
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as __exportAll } from "./runner--4wMWwKM.mjs";
|
|
2
2
|
import { t as TaxonomyRepository } from "./taxonomy-CdllE4oq.mjs";
|
|
3
|
-
import { l as invalidateTermCache } from "./taxonomies-
|
|
3
|
+
import { l as invalidateTermCache } from "./taxonomies-BdAmbOwx.mjs";
|
|
4
4
|
import { ulid } from "ulidx";
|
|
5
5
|
|
|
6
6
|
//#region src/api/handlers/taxonomies.ts
|
|
@@ -505,4 +505,4 @@ async function handleTermDelete(db, taxonomyName, termSlug, options = {}) {
|
|
|
505
505
|
|
|
506
506
|
//#endregion
|
|
507
507
|
export { handleTermGet as a, handleTermUpdate as c, handleTermDelete as i, taxonomies_exports as l, handleTaxonomyList as n, handleTermList as o, handleTermCreate as r, handleTermTranslations as s, handleTaxonomyCreate as t };
|
|
508
|
-
//# sourceMappingURL=taxonomies-
|
|
508
|
+
//# sourceMappingURL=taxonomies-BILwiyGk.mjs.map
|