emdash 0.8.0 → 0.10.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-BKSf3T9R.d.mts → adapters-BktHA7EO.d.mts} +1 -1
- package/dist/{adapters-BKSf3T9R.d.mts.map → adapters-BktHA7EO.d.mts.map} +1 -1
- package/dist/{apply-x0eMK1lX.mjs → apply-UsrFuO7l.mjs} +207 -355
- package/dist/apply-UsrFuO7l.mjs.map +1 -0
- package/dist/astro/index.d.mts +6 -6
- package/dist/astro/index.d.mts.map +1 -1
- package/dist/astro/index.mjs +118 -4
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +6 -7
- package/dist/astro/middleware/auth.d.mts.map +1 -1
- package/dist/astro/middleware/auth.mjs +14 -57
- package/dist/astro/middleware/auth.mjs.map +1 -1
- package/dist/astro/middleware/redirect.d.mts.map +1 -1
- package/dist/astro/middleware/redirect.mjs +15 -10
- package/dist/astro/middleware/redirect.mjs.map +1 -1
- package/dist/astro/middleware/request-context.d.mts.map +1 -1
- package/dist/astro/middleware/request-context.mjs +8 -5
- package/dist/astro/middleware/request-context.mjs.map +1 -1
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +70 -121
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/types.d.mts +25 -10
- package/dist/astro/types.d.mts.map +1 -1
- package/dist/{byline-Chbr2GoP.mjs → byline-C3vnhIpU.mjs} +4 -4
- package/dist/{byline-Chbr2GoP.mjs.map → byline-C3vnhIpU.mjs.map} +1 -1
- package/dist/bylines-esI7ioa9.mjs +113 -0
- package/dist/bylines-esI7ioa9.mjs.map +1 -0
- package/dist/cache-fTzxgMFJ.mjs +65 -0
- package/dist/cache-fTzxgMFJ.mjs.map +1 -0
- package/dist/{chunks-HGz06Soa.mjs → chunks-Da2-b-oA.mjs} +8 -2
- package/dist/{chunks-HGz06Soa.mjs.map → chunks-Da2-b-oA.mjs.map} +1 -1
- package/dist/cli/index.mjs +456 -90
- 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 +3 -3
- package/dist/client/index.mjs.map +1 -1
- package/dist/{config-BXwuX8Bx.mjs → config-CVssduLe.mjs} +1 -1
- package/dist/{config-BXwuX8Bx.mjs.map → config-CVssduLe.mjs.map} +1 -1
- package/dist/{content-BcQPYxdV.mjs → content-C7G4QXkK.mjs} +42 -14
- package/dist/content-C7G4QXkK.mjs.map +1 -0
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs +2 -2
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/libsql.d.mts.map +1 -1
- package/dist/db/libsql.mjs +7 -2
- package/dist/db/libsql.mjs.map +1 -1
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/db/sqlite.d.mts.map +1 -1
- package/dist/db/sqlite.mjs +8 -3
- package/dist/db/sqlite.mjs.map +1 -1
- package/dist/{db-errors-l1Qh2RPR.mjs → db-errors-B7P2pSCn.mjs} +1 -1
- package/dist/{db-errors-l1Qh2RPR.mjs.map → db-errors-B7P2pSCn.mjs.map} +1 -1
- package/dist/{default-DCVqE5ib.mjs → default-pHuz9WF6.mjs} +1 -1
- package/dist/{default-DCVqE5ib.mjs.map → default-pHuz9WF6.mjs.map} +1 -1
- package/dist/{dialect-helpers-DhTzaUxP.mjs → dialect-helpers-BKCvISIQ.mjs} +19 -2
- package/dist/dialect-helpers-BKCvISIQ.mjs.map +1 -0
- package/dist/{error-zG5T1UGA.mjs → error-DqnRMM5z.mjs} +1 -1
- package/dist/{error-zG5T1UGA.mjs.map → error-DqnRMM5z.mjs.map} +1 -1
- package/dist/{index-DIb-CzNx.d.mts → index-DjPMOfO0.d.mts} +162 -87
- package/dist/index-DjPMOfO0.d.mts.map +1 -0
- package/dist/index.d.mts +11 -11
- package/dist/index.mjs +27 -24
- package/dist/{load-CyEoextb.mjs → load-sXRuM7Us.mjs} +2 -2
- package/dist/{load-CyEoextb.mjs.map → load-sXRuM7Us.mjs.map} +1 -1
- package/dist/{loader-CndGj8kM.mjs → loader-Bx2_9-5e.mjs} +53 -8
- package/dist/loader-Bx2_9-5e.mjs.map +1 -0
- package/dist/{manifest-schema-DH9xhc6t.mjs → manifest-schema-CXAbd1vH.mjs} +33 -3
- package/dist/manifest-schema-CXAbd1vH.mjs.map +1 -0
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +1 -1
- package/dist/media/local-runtime.d.mts +7 -7
- package/dist/{mode-BnAOqItE.mjs → mode-YhqNVef_.mjs} +1 -1
- package/dist/{mode-BnAOqItE.mjs.map → mode-YhqNVef_.mjs.map} +1 -1
- package/dist/options-nPxWnrya.mjs +117 -0
- package/dist/options-nPxWnrya.mjs.map +1 -0
- package/dist/page/index.d.mts +2 -2
- package/dist/{patterns-CrCYkMBb.mjs → patterns-DsUZ4uxI.mjs} +1 -1
- package/dist/{patterns-CrCYkMBb.mjs.map → patterns-DsUZ4uxI.mjs.map} +1 -1
- package/dist/{placeholder-D29tWZ7o.d.mts → placeholder-CDPtkelt.d.mts} +1 -1
- package/dist/{placeholder-D29tWZ7o.d.mts.map → placeholder-CDPtkelt.d.mts.map} +1 -1
- package/dist/{placeholder-C-fk5hYI.mjs → placeholder-Ci0RLeCk.mjs} +1 -1
- package/dist/{placeholder-C-fk5hYI.mjs.map → placeholder-Ci0RLeCk.mjs.map} +1 -1
- package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
- package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -1
- package/dist/plugins/adapt-sandbox-entry.mjs +6 -5
- package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -1
- package/dist/public-url-B1AxbbbQ.mjs +51 -0
- package/dist/public-url-B1AxbbbQ.mjs.map +1 -0
- package/dist/{query-fqEdLFms.mjs → query-Bo-msrmu.mjs} +114 -16
- package/dist/query-Bo-msrmu.mjs.map +1 -0
- package/dist/{redirect-D_pshWdf.mjs → redirect-C5H7VGIX.mjs} +11 -6
- package/dist/redirect-C5H7VGIX.mjs.map +1 -0
- package/dist/{registry-C3Mr0ODu.mjs → registry-Beb7wxFc.mjs} +39 -5
- package/dist/registry-Beb7wxFc.mjs.map +1 -0
- package/dist/{request-cache-Ci7f5pBb.mjs → request-cache-C-tIpYIw.mjs} +1 -1
- package/dist/{request-cache-Ci7f5pBb.mjs.map → request-cache-C-tIpYIw.mjs.map} +1 -1
- package/dist/runner-Clwe4Mme.d.mts +44 -0
- package/dist/runner-Clwe4Mme.d.mts.map +1 -0
- package/dist/{runner-tQ7BJ4T7.mjs → runner-DMnlIkh4.mjs} +616 -191
- package/dist/runner-DMnlIkh4.mjs.map +1 -0
- package/dist/runtime.d.mts +6 -6
- package/dist/runtime.mjs +2 -2
- package/dist/{search-BoZYFuUk.mjs → search-DkN-BqsS.mjs} +270 -152
- package/dist/search-DkN-BqsS.mjs.map +1 -0
- package/dist/secrets-CZ8rxLX3.mjs +314 -0
- package/dist/secrets-CZ8rxLX3.mjs.map +1 -0
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +13 -11
- package/dist/seo/index.d.mts +1 -1
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/local.mjs +1 -1
- package/dist/storage/s3.d.mts +1 -1
- package/dist/storage/s3.mjs +1 -1
- package/dist/taxonomies-CTtewrSQ.mjs +407 -0
- package/dist/taxonomies-CTtewrSQ.mjs.map +1 -0
- package/dist/taxonomy-DSxx2K2L.mjs +218 -0
- package/dist/taxonomy-DSxx2K2L.mjs.map +1 -0
- package/dist/{tokens-D9vnZqYS.mjs → tokens-CyRDPVW2.mjs} +1 -1
- package/dist/{tokens-D9vnZqYS.mjs.map → tokens-CyRDPVW2.mjs.map} +1 -1
- package/dist/{transaction-Cn2rjY78.mjs → transaction-D44LBXvU.mjs} +1 -1
- package/dist/{transaction-Cn2rjY78.mjs.map → transaction-D44LBXvU.mjs.map} +1 -1
- package/dist/{transport-CUnEL3Vs.d.mts → transport-DX_5rpsq.d.mts} +1 -1
- package/dist/{transport-CUnEL3Vs.d.mts.map → transport-DX_5rpsq.d.mts.map} +1 -1
- package/dist/{transport-C9ugt2Nr.mjs → transport-xpzIjCIB.mjs} +6 -5
- package/dist/{transport-C9ugt2Nr.mjs.map → transport-xpzIjCIB.mjs.map} +1 -1
- package/dist/{types-BrA0xf5I.d.mts → types-B_CXXnzh.d.mts} +1 -1
- package/dist/{types-BrA0xf5I.d.mts.map → types-B_CXXnzh.d.mts.map} +1 -1
- package/dist/{types-DIMwPFub.d.mts → types-C-aFbqmA.d.mts} +1 -1
- package/dist/{types-DIMwPFub.d.mts.map → types-C-aFbqmA.d.mts.map} +1 -1
- package/dist/types-CoO6mpV3.mjs +68 -0
- package/dist/types-CoO6mpV3.mjs.map +1 -0
- package/dist/{types-i36XcA_X.d.mts → types-D19uBYWn.d.mts} +83 -7
- package/dist/types-D19uBYWn.d.mts.map +1 -0
- package/dist/{types-BmPPSUEx.d.mts → types-Dl1fgFjn.d.mts} +24 -2
- package/dist/{types-BmPPSUEx.d.mts.map → types-Dl1fgFjn.d.mts.map} +1 -1
- package/dist/{types-CS8FIX7L.d.mts → types-Dtx1mSMX.d.mts} +9 -1
- package/dist/types-Dtx1mSMX.d.mts.map +1 -0
- package/dist/{types-Bm1dn-q3.mjs → types-Eg829jj9.mjs} +1 -1
- package/dist/{types-Bm1dn-q3.mjs.map → types-Eg829jj9.mjs.map} +1 -1
- package/dist/{types-CgqmmMJB.mjs → types-K-EkEQCI.mjs} +1 -1
- package/dist/{types-CgqmmMJB.mjs.map → types-K-EkEQCI.mjs.map} +1 -1
- package/dist/{validate-CxVsLehf.mjs → validate-CBIbxM3L.mjs} +14 -10
- package/dist/validate-CBIbxM3L.mjs.map +1 -0
- package/dist/{validate-DHxmpFJt.d.mts → validate-DHGwADqO.d.mts} +18 -5
- package/dist/validate-DHGwADqO.d.mts.map +1 -0
- package/dist/{validation-C-ZpN2GI.mjs → validation-B1NYiEos.mjs} +6 -6
- package/dist/{validation-C-ZpN2GI.mjs.map → validation-B1NYiEos.mjs.map} +1 -1
- package/dist/version-CMD42IRC.mjs +7 -0
- package/dist/{version-Bbq8TCrz.mjs.map → version-CMD42IRC.mjs.map} +1 -1
- package/dist/{zod-generator-CpwccCIv.mjs → zod-generator-BNJDQBSZ.mjs} +11 -6
- package/dist/{zod-generator-CpwccCIv.mjs.map → zod-generator-BNJDQBSZ.mjs.map} +1 -1
- package/locals.d.ts +1 -6
- package/package.json +9 -8
- package/src/api/handlers/comments.ts +6 -4
- package/src/api/handlers/content.ts +40 -1
- package/src/api/handlers/dashboard.ts +29 -36
- package/src/api/handlers/device-flow.ts +5 -0
- package/src/api/handlers/marketplace.ts +11 -4
- package/src/api/handlers/menus.ts +256 -75
- package/src/api/handlers/oauth-authorization.ts +72 -33
- package/src/api/handlers/revision.ts +23 -14
- package/src/api/handlers/taxonomies.ts +273 -100
- package/src/api/public-url.ts +48 -2
- package/src/api/schemas/comments.ts +2 -2
- package/src/api/schemas/common.ts +7 -0
- package/src/api/schemas/content.ts +17 -0
- package/src/api/schemas/menus.ts +23 -0
- package/src/api/schemas/sections.ts +3 -3
- package/src/api/schemas/taxonomies.ts +39 -0
- package/src/api/schemas/users.ts +1 -1
- package/src/api/types.ts +5 -1
- package/src/astro/integration/index.ts +17 -0
- package/src/astro/integration/routes.ts +10 -0
- package/src/astro/integration/runtime.ts +30 -0
- package/src/astro/integration/virtual-modules.ts +32 -2
- package/src/astro/integration/vite-config.ts +6 -1
- package/src/astro/middleware/auth.ts +13 -6
- package/src/astro/middleware/redirect.ts +29 -16
- package/src/astro/middleware/request-context.ts +15 -5
- package/src/astro/middleware.ts +23 -9
- package/src/astro/routes/api/auth/invite/complete.ts +6 -1
- package/src/astro/routes/api/auth/passkey/register/verify.ts +6 -1
- package/src/astro/routes/api/auth/passkey/verify.ts +6 -1
- package/src/astro/routes/api/auth/signup/complete.ts +6 -1
- package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +2 -2
- package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +4 -2
- package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +1 -1
- package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +34 -12
- package/src/astro/routes/api/content/[collection]/[id]/publish.ts +32 -2
- package/src/astro/routes/api/content/[collection]/[id]/restore.ts +4 -2
- package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +3 -2
- package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +8 -4
- package/src/astro/routes/api/content/[collection]/[id].ts +12 -0
- package/src/astro/routes/api/import/wordpress/execute.ts +3 -1
- package/src/astro/routes/api/import/wordpress/prepare.ts +7 -8
- package/src/astro/routes/api/import/wordpress/rewrite-url-helpers.ts +196 -0
- package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +9 -177
- package/src/astro/routes/api/import/wordpress-plugin/execute.ts +3 -1
- package/src/astro/routes/api/manifest.ts +62 -45
- package/src/astro/routes/api/media/[id]/confirm.ts +10 -1
- package/src/astro/routes/api/media/providers/[providerId]/index.ts +12 -3
- package/src/astro/routes/api/menus/[name]/items.ts +16 -6
- package/src/astro/routes/api/menus/[name]/reorder.ts +8 -3
- package/src/astro/routes/api/menus/[name]/translations.ts +82 -0
- package/src/astro/routes/api/menus/[name].ts +19 -10
- package/src/astro/routes/api/menus/index.ts +9 -6
- package/src/astro/routes/api/openapi.json.ts +27 -10
- package/src/astro/routes/api/redirects/404s/index.ts +10 -4
- package/src/astro/routes/api/redirects/404s/summary.ts +4 -2
- package/src/astro/routes/api/redirects/[id].ts +10 -4
- package/src/astro/routes/api/redirects/index.ts +7 -3
- package/src/astro/routes/api/revisions/[revisionId]/index.ts +1 -1
- package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +0 -2
- package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +0 -1
- package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +0 -1
- package/src/astro/routes/api/schema/collections/[slug]/index.ts +2 -2
- package/src/astro/routes/api/schema/collections/index.ts +1 -1
- package/src/astro/routes/api/search/index.ts +10 -2
- package/src/astro/routes/api/sections/[slug].ts +10 -4
- package/src/astro/routes/api/sections/index.ts +7 -3
- package/src/astro/routes/api/setup/admin-verify.ts +6 -1
- package/src/astro/routes/api/snapshot.ts +44 -18
- package/src/astro/routes/api/taxonomies/[name]/terms/[slug]/translations.ts +89 -0
- package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +22 -22
- package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +11 -14
- package/src/astro/routes/api/taxonomies/index.ts +9 -7
- package/src/astro/routes/api/themes/preview.ts +11 -5
- package/src/astro/types.ts +23 -3
- package/src/auth/allowed-origins.ts +168 -0
- package/src/auth/passkey-config.ts +35 -13
- package/src/bylines/index.ts +37 -88
- package/src/cli/commands/auth.ts +28 -6
- package/src/cli/commands/bundle-utils.ts +11 -2
- package/src/cli/commands/bundle.ts +28 -8
- package/src/cli/commands/content.ts +13 -0
- package/src/cli/commands/export-seed.ts +82 -21
- package/src/cli/commands/login.ts +8 -1
- package/src/cli/commands/plugin-init.ts +216 -90
- package/src/cli/commands/publish.ts +24 -0
- package/src/cli/commands/secrets.ts +183 -0
- package/src/cli/credentials.ts +1 -1
- package/src/cli/index.ts +5 -1
- package/src/client/index.ts +4 -4
- package/src/client/transport.ts +17 -7
- package/src/components/Break.astro +2 -2
- package/src/components/EmDashHead.astro +18 -13
- package/src/components/Embed.astro +1 -1
- package/src/components/Gallery.astro +1 -1
- package/src/components/Image.astro +1 -1
- package/src/components/InlinePortableTextEditor.tsx +104 -18
- package/src/config/secrets.ts +528 -0
- package/src/database/dialect-helpers.ts +50 -0
- package/src/database/migrations/034_published_at_index.ts +1 -1
- package/src/database/migrations/035_bounded_404_log.ts +56 -39
- package/src/database/migrations/036_i18n_menus_and_taxonomies.ts +477 -0
- package/src/database/migrations/runner.ts +158 -23
- package/src/database/repositories/content.ts +47 -12
- package/src/database/repositories/redirect.ts +14 -3
- package/src/database/repositories/taxonomy.ts +212 -82
- package/src/database/types.ts +10 -2
- package/src/db/libsql.ts +1 -3
- package/src/db/sqlite.ts +2 -5
- package/src/emdash-runtime.ts +84 -159
- package/src/i18n/resolve.ts +37 -0
- package/src/index.ts +9 -0
- package/src/loader.ts +73 -3
- package/src/mcp/server.ts +180 -54
- package/src/menus/index.ts +143 -124
- package/src/menus/types.ts +15 -1
- package/src/page/site-identity.ts +58 -0
- package/src/plugins/adapt-sandbox-entry.ts +22 -10
- package/src/plugins/context.ts +13 -10
- package/src/plugins/define-plugin.ts +40 -12
- package/src/plugins/hooks.ts +23 -19
- package/src/plugins/index.ts +9 -0
- package/src/plugins/manifest-schema.ts +37 -2
- package/src/plugins/types.ts +151 -11
- package/src/preview/urls.ts +23 -3
- package/src/query.ts +148 -5
- package/src/redirects/cache.ts +38 -18
- package/src/schema/registry.ts +56 -0
- package/src/schema/zod-generator.ts +39 -7
- package/src/seed/apply.ts +142 -54
- package/src/seed/types.ts +14 -1
- package/src/seed/validate.ts +27 -13
- package/src/settings/index.ts +80 -6
- package/src/settings/types.ts +23 -1
- package/src/taxonomies/index.ts +237 -210
- package/src/taxonomies/types.ts +10 -0
- package/dist/apply-x0eMK1lX.mjs.map +0 -1
- package/dist/bylines-CRNsVG88.mjs +0 -157
- package/dist/bylines-CRNsVG88.mjs.map +0 -1
- package/dist/cache-BkKBuIvS.mjs +0 -56
- package/dist/cache-BkKBuIvS.mjs.map +0 -1
- package/dist/chunk-ClPoSABd.mjs +0 -21
- package/dist/content-BcQPYxdV.mjs.map +0 -1
- package/dist/dialect-helpers-DhTzaUxP.mjs.map +0 -1
- package/dist/index-DIb-CzNx.d.mts.map +0 -1
- package/dist/loader-CndGj8kM.mjs.map +0 -1
- package/dist/manifest-schema-DH9xhc6t.mjs.map +0 -1
- package/dist/query-fqEdLFms.mjs.map +0 -1
- package/dist/redirect-D_pshWdf.mjs.map +0 -1
- package/dist/registry-C3Mr0ODu.mjs.map +0 -1
- package/dist/runner-OURCaApa.d.mts +0 -34
- package/dist/runner-OURCaApa.d.mts.map +0 -1
- package/dist/runner-tQ7BJ4T7.mjs.map +0 -1
- package/dist/search-BoZYFuUk.mjs.map +0 -1
- package/dist/taxonomies-B4IAshV8.mjs +0 -308
- package/dist/taxonomies-B4IAshV8.mjs.map +0 -1
- package/dist/types-CS8FIX7L.d.mts.map +0 -1
- package/dist/types-i36XcA_X.d.mts.map +0 -1
- package/dist/validate-CxVsLehf.mjs.map +0 -1
- package/dist/validate-DHxmpFJt.d.mts.map +0 -1
- package/dist/version-Bbq8TCrz.mjs +0 -7
package/src/seed/validate.ts
CHANGED
|
@@ -147,11 +147,16 @@ export function validateSeed(data: unknown): ValidationResult {
|
|
|
147
147
|
if (!taxonomy.name) {
|
|
148
148
|
errors.push(`${prefix}: name is required`);
|
|
149
149
|
} else {
|
|
150
|
-
//
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
// Uniqueness is per (name, locale).
|
|
151
|
+
const key = `${taxonomy.name}::${taxonomy.locale ?? ""}`;
|
|
152
|
+
if (taxonomyNames.has(key)) {
|
|
153
|
+
errors.push(
|
|
154
|
+
taxonomy.locale
|
|
155
|
+
? `${prefix}.name: duplicate taxonomy "${taxonomy.name}" in locale "${taxonomy.locale}"`
|
|
156
|
+
: `${prefix}.name: duplicate taxonomy name "${taxonomy.name}"`,
|
|
157
|
+
);
|
|
153
158
|
}
|
|
154
|
-
taxonomyNames.add(
|
|
159
|
+
taxonomyNames.add(key);
|
|
155
160
|
}
|
|
156
161
|
|
|
157
162
|
if (!taxonomy.label) {
|
|
@@ -184,13 +189,15 @@ export function validateSeed(data: unknown): ValidationResult {
|
|
|
184
189
|
if (!term.slug) {
|
|
185
190
|
errors.push(`${termPrefix}: slug is required`);
|
|
186
191
|
} else {
|
|
187
|
-
//
|
|
188
|
-
|
|
192
|
+
// Uniqueness is per (slug, locale) so the same slug can repeat
|
|
193
|
+
// across locale variants of the def.
|
|
194
|
+
const key = `${term.slug}::${term.locale ?? taxonomy.locale ?? ""}`;
|
|
195
|
+
if (termSlugs.has(key)) {
|
|
189
196
|
errors.push(
|
|
190
197
|
`${termPrefix}.slug: duplicate term slug "${term.slug}" in taxonomy "${taxonomy.name}"`,
|
|
191
198
|
);
|
|
192
199
|
}
|
|
193
|
-
termSlugs.add(
|
|
200
|
+
termSlugs.add(key);
|
|
194
201
|
}
|
|
195
202
|
|
|
196
203
|
if (!term.label) {
|
|
@@ -207,11 +214,12 @@ export function validateSeed(data: unknown): ValidationResult {
|
|
|
207
214
|
}
|
|
208
215
|
}
|
|
209
216
|
|
|
210
|
-
// Second pass: validate parent references
|
|
217
|
+
// Second pass: validate parent references (within the same locale).
|
|
211
218
|
if (taxonomy.hierarchical && taxonomy.terms) {
|
|
212
219
|
for (let j = 0; j < taxonomy.terms.length; j++) {
|
|
213
220
|
const term = taxonomy.terms[j];
|
|
214
|
-
|
|
221
|
+
const termLocale = term.locale ?? taxonomy.locale ?? "";
|
|
222
|
+
if (term.parent && !termSlugs.has(`${term.parent}::${termLocale}`)) {
|
|
215
223
|
errors.push(
|
|
216
224
|
`${prefix}.terms[${j}].parent: parent term "${term.parent}" not found in taxonomy`,
|
|
217
225
|
);
|
|
@@ -243,11 +251,17 @@ export function validateSeed(data: unknown): ValidationResult {
|
|
|
243
251
|
if (!menu.name) {
|
|
244
252
|
errors.push(`${prefix}: name is required`);
|
|
245
253
|
} else {
|
|
246
|
-
//
|
|
247
|
-
|
|
248
|
-
|
|
254
|
+
// Uniqueness is per (name, locale) — siblings of a translation
|
|
255
|
+
// group share name but differ in locale.
|
|
256
|
+
const key = `${menu.name}::${menu.locale ?? ""}`;
|
|
257
|
+
if (menuNames.has(key)) {
|
|
258
|
+
errors.push(
|
|
259
|
+
menu.locale
|
|
260
|
+
? `${prefix}.name: duplicate menu "${menu.name}" in locale "${menu.locale}"`
|
|
261
|
+
: `${prefix}.name: duplicate menu name "${menu.name}"`,
|
|
262
|
+
);
|
|
249
263
|
}
|
|
250
|
-
menuNames.add(
|
|
264
|
+
menuNames.add(key);
|
|
251
265
|
}
|
|
252
266
|
|
|
253
267
|
if (!menu.label) {
|
package/src/settings/index.ts
CHANGED
|
@@ -18,6 +18,53 @@ import type { SiteSettings, SiteSettingKey, MediaReference } from "./types.js";
|
|
|
18
18
|
/** Prefix for site settings in the options table */
|
|
19
19
|
const SETTINGS_PREFIX = "site:";
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Worker-isolate cache for the resolved `site:*` settings.
|
|
23
|
+
*
|
|
24
|
+
* Site settings (title, logo, SEO defaults) change rarely but are read on
|
|
25
|
+
* every public request. Caching across the isolate's lifetime drops the
|
|
26
|
+
* `options WHERE name LIKE 'site:%'` prefix scan from once-per-request to
|
|
27
|
+
* once-per-isolate. Cross-isolate staleness is bounded by isolate lifetime
|
|
28
|
+
* (workerd typically recycles within minutes); acceptable for chrome.
|
|
29
|
+
*
|
|
30
|
+
* Stored on globalThis with a Symbol.for key so Vite SSR chunk duplication
|
|
31
|
+
* doesn't produce two independent caches (same pattern as request-context.ts).
|
|
32
|
+
*
|
|
33
|
+
* Invalidation: every `site:*` write bumps `version`. Reads compare the
|
|
34
|
+
* cached promise's version against the current version and refetch on
|
|
35
|
+
* mismatch. Caching the promise (not the resolved value) lets concurrent
|
|
36
|
+
* cold-isolate readers share the in-flight query.
|
|
37
|
+
*/
|
|
38
|
+
interface SiteSettingsHolder {
|
|
39
|
+
version: number;
|
|
40
|
+
cached: Promise<Partial<SiteSettings>> | null;
|
|
41
|
+
cachedVersion: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const SITE_SETTINGS_CACHE_KEY = Symbol.for("emdash:site-settings");
|
|
45
|
+
const g = globalThis as Record<symbol, unknown>;
|
|
46
|
+
const holder: SiteSettingsHolder =
|
|
47
|
+
// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- globalThis singleton pattern (see request-context.ts)
|
|
48
|
+
(g[SITE_SETTINGS_CACHE_KEY] as SiteSettingsHolder | undefined) ??
|
|
49
|
+
(() => {
|
|
50
|
+
const h: SiteSettingsHolder = { version: 0, cached: null, cachedVersion: -1 };
|
|
51
|
+
g[SITE_SETTINGS_CACHE_KEY] = h;
|
|
52
|
+
return h;
|
|
53
|
+
})();
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Bump the isolate-wide site-settings cache version, forcing the next
|
|
57
|
+
* `getSiteSettings()` to re-query the database.
|
|
58
|
+
*
|
|
59
|
+
* Called from every `site:*` write path. Other isolates still serve their
|
|
60
|
+
* own cached copy until they expire — staleness bounded by isolate lifetime.
|
|
61
|
+
*/
|
|
62
|
+
export function invalidateSiteSettingsCache(): void {
|
|
63
|
+
holder.version++;
|
|
64
|
+
holder.cached = null;
|
|
65
|
+
holder.cachedVersion = -1;
|
|
66
|
+
}
|
|
67
|
+
|
|
21
68
|
/**
|
|
22
69
|
* Type guard for MediaReference values
|
|
23
70
|
*/
|
|
@@ -26,13 +73,18 @@ function isMediaReference(value: unknown): value is MediaReference {
|
|
|
26
73
|
}
|
|
27
74
|
|
|
28
75
|
/**
|
|
29
|
-
* Resolve a media reference to include the full URL
|
|
76
|
+
* Resolve a media reference to include the full URL plus content metadata.
|
|
77
|
+
*
|
|
78
|
+
* Pulls `mimeType` and intrinsic dimensions from the media row so callers
|
|
79
|
+
* can emit correct head tags (e.g. `<link rel="icon" type="image/svg+xml">`,
|
|
80
|
+
* which Chromium requires when the URL has no `.svg` extension) without
|
|
81
|
+
* a second round-trip to the media table.
|
|
30
82
|
*/
|
|
31
83
|
async function resolveMediaReference(
|
|
32
84
|
mediaRef: MediaReference | undefined,
|
|
33
85
|
db: Kysely<Database>,
|
|
34
86
|
_storage: Storage | null,
|
|
35
|
-
): Promise<
|
|
87
|
+
): Promise<MediaReference | undefined> {
|
|
36
88
|
if (!mediaRef?.mediaId) {
|
|
37
89
|
return mediaRef;
|
|
38
90
|
}
|
|
@@ -46,6 +98,9 @@ async function resolveMediaReference(
|
|
|
46
98
|
return {
|
|
47
99
|
...mediaRef,
|
|
48
100
|
url: `/_emdash/api/media/file/${media.storageKey}`,
|
|
101
|
+
contentType: media.mimeType,
|
|
102
|
+
...(media.width !== null ? { width: media.width } : {}),
|
|
103
|
+
...(media.height !== null ? { height: media.height } : {}),
|
|
49
104
|
};
|
|
50
105
|
}
|
|
51
106
|
} catch {
|
|
@@ -142,9 +197,24 @@ export async function getSiteSettingWithDb<K extends SiteSettingKey>(
|
|
|
142
197
|
* ```
|
|
143
198
|
*/
|
|
144
199
|
export function getSiteSettings(): Promise<Partial<SiteSettings>> {
|
|
145
|
-
return requestCached("siteSettings",
|
|
146
|
-
const
|
|
147
|
-
|
|
200
|
+
return requestCached("siteSettings", () => {
|
|
201
|
+
const versionAtCall = holder.version;
|
|
202
|
+
if (holder.cached && holder.cachedVersion === versionAtCall) {
|
|
203
|
+
return holder.cached;
|
|
204
|
+
}
|
|
205
|
+
const fetchPromise = (async () => {
|
|
206
|
+
const db = await getDb();
|
|
207
|
+
return getSiteSettingsWithDb(db);
|
|
208
|
+
})().catch((error) => {
|
|
209
|
+
if (holder.cached === fetchPromise) {
|
|
210
|
+
holder.cached = null;
|
|
211
|
+
holder.cachedVersion = -1;
|
|
212
|
+
}
|
|
213
|
+
throw error;
|
|
214
|
+
});
|
|
215
|
+
holder.cached = fetchPromise;
|
|
216
|
+
holder.cachedVersion = versionAtCall;
|
|
217
|
+
return fetchPromise;
|
|
148
218
|
});
|
|
149
219
|
}
|
|
150
220
|
|
|
@@ -218,7 +288,11 @@ export async function setSiteSettings(
|
|
|
218
288
|
}
|
|
219
289
|
}
|
|
220
290
|
|
|
221
|
-
|
|
291
|
+
try {
|
|
292
|
+
await options.setMany(updates);
|
|
293
|
+
} finally {
|
|
294
|
+
invalidateSiteSettingsCache();
|
|
295
|
+
}
|
|
222
296
|
}
|
|
223
297
|
|
|
224
298
|
/**
|
package/src/settings/types.ts
CHANGED
|
@@ -4,10 +4,32 @@
|
|
|
4
4
|
* Global configuration for the site (title, logo, social links, etc.)
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Media reference for logo/favicon.
|
|
9
|
+
*
|
|
10
|
+
* Stored shape is just `{ mediaId, alt? }`. The remaining fields are
|
|
11
|
+
* populated by `resolveMediaReference` on read so templates can emit
|
|
12
|
+
* correct head tags without a second round-trip to the media table.
|
|
13
|
+
*
|
|
14
|
+
* The Zod schemas at the REST/MCP boundary (`mediaReference`) define
|
|
15
|
+
* only `mediaId` and `alt` and rely on default strip-mode parsing to
|
|
16
|
+
* discard the resolved fields if a client posts them back. If you
|
|
17
|
+
* ever switch those schemas to `passthrough`, you must also strip the
|
|
18
|
+
* resolved fields explicitly in `setSiteSettings`, or stored options
|
|
19
|
+
* will accumulate stale `url` / `contentType` / `width` / `height`
|
|
20
|
+
* snapshots.
|
|
21
|
+
*/
|
|
8
22
|
export interface MediaReference {
|
|
9
23
|
mediaId: string;
|
|
10
24
|
alt?: string;
|
|
25
|
+
/** Resolved URL. Populated by `resolveMediaReference`; absent on raw stored values. */
|
|
26
|
+
url?: string;
|
|
27
|
+
/** Stored MIME type (e.g. `image/svg+xml`). Populated alongside `url`. */
|
|
28
|
+
contentType?: string;
|
|
29
|
+
/** Pixel width if known. Populated alongside `url`. */
|
|
30
|
+
width?: number;
|
|
31
|
+
/** Pixel height if known. Populated alongside `url`. */
|
|
32
|
+
height?: number;
|
|
11
33
|
}
|
|
12
34
|
|
|
13
35
|
/** Site-level SEO settings */
|