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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Kysely } from "kysely";
|
|
1
|
+
import type { Kysely, Selectable } from "kysely";
|
|
2
2
|
import { ulid } from "ulidx";
|
|
3
3
|
|
|
4
4
|
import type { Database, TaxonomyTable, ContentTaxonomyTable } from "../types.js";
|
|
@@ -10,6 +10,8 @@ export interface Taxonomy {
|
|
|
10
10
|
label: string;
|
|
11
11
|
parentId: string | null;
|
|
12
12
|
data: Record<string, unknown> | null;
|
|
13
|
+
locale: string;
|
|
14
|
+
translationGroup: string | null;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
export interface CreateTaxonomyInput {
|
|
@@ -18,6 +20,11 @@ export interface CreateTaxonomyInput {
|
|
|
18
20
|
label: string;
|
|
19
21
|
parentId?: string;
|
|
20
22
|
data?: Record<string, unknown>;
|
|
23
|
+
/** Omit to let the DB default (current value: 'en') apply. Higher layers
|
|
24
|
+
* resolve the locale from the request context / i18n config. */
|
|
25
|
+
locale?: string;
|
|
26
|
+
/** When set, links the new term into the source term's translation_group. */
|
|
27
|
+
translationOf?: string;
|
|
21
28
|
}
|
|
22
29
|
|
|
23
30
|
export interface UpdateTaxonomyInput {
|
|
@@ -27,16 +34,29 @@ export interface UpdateTaxonomyInput {
|
|
|
27
34
|
data?: Record<string, unknown>;
|
|
28
35
|
}
|
|
29
36
|
|
|
37
|
+
export interface FindOptions {
|
|
38
|
+
parentId?: string | null;
|
|
39
|
+
locale?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
30
42
|
/**
|
|
31
|
-
* Taxonomy repository for categories, tags, and other classification
|
|
43
|
+
* Taxonomy repository for categories, tags, and other classification.
|
|
32
44
|
*
|
|
33
|
-
*
|
|
45
|
+
* Terms are per-locale. Translations of the same term share a `translation_group`
|
|
46
|
+
* ULID. `content_taxonomies.taxonomy_id` stores the translation_group so a single
|
|
47
|
+
* association spans every locale of a post.
|
|
48
|
+
*
|
|
49
|
+
* The repository does not resolve locale fallbacks on its own — callers supply
|
|
50
|
+
* the locale they want. Runtime helpers and handlers use `getFallbackChain()`
|
|
51
|
+
* from `i18n/config` when they need fallback behaviour.
|
|
34
52
|
*/
|
|
35
53
|
export class TaxonomyRepository {
|
|
36
54
|
constructor(private db: Kysely<Database>) {}
|
|
37
55
|
|
|
38
56
|
/**
|
|
39
|
-
* Create a new taxonomy term
|
|
57
|
+
* Create a new taxonomy term. When `translationOf` is set the new row joins
|
|
58
|
+
* the source term's translation_group; otherwise a fresh group is minted
|
|
59
|
+
* (matching the migration backfill pattern `translation_group = id`).
|
|
40
60
|
*/
|
|
41
61
|
async create(input: CreateTaxonomyInput): Promise<Taxonomy> {
|
|
42
62
|
const id = ulid();
|
|
@@ -44,58 +64,68 @@ export class TaxonomyRepository {
|
|
|
44
64
|
// Empty-string parentId is coerced to null defensively. Higher layers
|
|
45
65
|
// also normalize this — see handleTermCreate / handleTermUpdate.
|
|
46
66
|
const parentId = input.parentId === undefined || input.parentId === "" ? null : input.parentId;
|
|
47
|
-
const row: TaxonomyTable = {
|
|
48
|
-
id,
|
|
49
|
-
name: input.name,
|
|
50
|
-
slug: input.slug,
|
|
51
|
-
label: input.label,
|
|
52
|
-
parent_id: parentId,
|
|
53
|
-
data: input.data ? JSON.stringify(input.data) : null,
|
|
54
|
-
};
|
|
55
67
|
|
|
56
|
-
|
|
68
|
+
let translationGroup = id;
|
|
69
|
+
if (input.translationOf) {
|
|
70
|
+
const source = await this.findById(input.translationOf);
|
|
71
|
+
if (source?.translationGroup) translationGroup = source.translationGroup;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
await this.db
|
|
75
|
+
.insertInto("taxonomies")
|
|
76
|
+
.values({
|
|
77
|
+
id,
|
|
78
|
+
name: input.name,
|
|
79
|
+
slug: input.slug,
|
|
80
|
+
label: input.label,
|
|
81
|
+
parent_id: parentId,
|
|
82
|
+
data: input.data ? JSON.stringify(input.data) : null,
|
|
83
|
+
// When omitted, the DB DEFAULT 'en' is used — keeps behaviour
|
|
84
|
+
// consistent with ContentRepository and lets higher layers
|
|
85
|
+
// supply an explicit locale from request context.
|
|
86
|
+
...(input.locale !== undefined ? { locale: input.locale } : {}),
|
|
87
|
+
translation_group: translationGroup,
|
|
88
|
+
})
|
|
89
|
+
.execute();
|
|
57
90
|
|
|
58
91
|
const taxonomy = await this.findById(id);
|
|
59
|
-
if (!taxonomy)
|
|
60
|
-
throw new Error("Failed to create taxonomy");
|
|
61
|
-
}
|
|
92
|
+
if (!taxonomy) throw new Error("Failed to create taxonomy");
|
|
62
93
|
return taxonomy;
|
|
63
94
|
}
|
|
64
95
|
|
|
65
|
-
/**
|
|
66
|
-
* Find taxonomy by ID
|
|
67
|
-
*/
|
|
68
96
|
async findById(id: string): Promise<Taxonomy | null> {
|
|
69
97
|
const row = await this.db
|
|
70
98
|
.selectFrom("taxonomies")
|
|
71
99
|
.selectAll()
|
|
72
100
|
.where("id", "=", id)
|
|
73
101
|
.executeTakeFirst();
|
|
74
|
-
|
|
75
102
|
return row ? this.rowToTaxonomy(row) : null;
|
|
76
103
|
}
|
|
77
104
|
|
|
78
105
|
/**
|
|
79
|
-
* Find
|
|
106
|
+
* Find a term by (name, slug). When `locale` is provided, filter by it.
|
|
107
|
+
* When omitted, returns the lowest-locale-code match (deterministic across
|
|
108
|
+
* calls). Mirrors `ContentRepository.findBySlug`.
|
|
80
109
|
*/
|
|
81
|
-
async findBySlug(name: string, slug: string): Promise<Taxonomy | null> {
|
|
82
|
-
|
|
110
|
+
async findBySlug(name: string, slug: string, locale?: string): Promise<Taxonomy | null> {
|
|
111
|
+
let query = this.db
|
|
83
112
|
.selectFrom("taxonomies")
|
|
84
113
|
.selectAll()
|
|
85
114
|
.where("name", "=", name)
|
|
86
|
-
.where("slug", "=", slug)
|
|
87
|
-
|
|
88
|
-
|
|
115
|
+
.where("slug", "=", slug);
|
|
116
|
+
if (locale !== undefined) query = query.where("locale", "=", locale);
|
|
117
|
+
const row = await query.orderBy("locale", "asc").executeTakeFirst();
|
|
89
118
|
return row ? this.rowToTaxonomy(row) : null;
|
|
90
119
|
}
|
|
91
120
|
|
|
92
121
|
/**
|
|
93
|
-
* Get all terms for a taxonomy (e.g., all categories)
|
|
122
|
+
* Get all terms for a taxonomy (e.g., all categories).
|
|
123
|
+
*
|
|
124
|
+
* `id asc` is a stable tiebreaker for terms that share a label. Without it
|
|
125
|
+
* the SQL ordering is implementation-defined when labels match, which
|
|
126
|
+
* breaks keyset pagination over `(label, id)`.
|
|
94
127
|
*/
|
|
95
|
-
async findByName(name: string, options:
|
|
96
|
-
// `id asc` is a stable tiebreaker for terms that share a label.
|
|
97
|
-
// Without it the SQL ordering is implementation-defined when labels
|
|
98
|
-
// match, which breaks keyset pagination over `(label, id)`.
|
|
128
|
+
async findByName(name: string, options: FindOptions = {}): Promise<Taxonomy[]> {
|
|
99
129
|
let query = this.db
|
|
100
130
|
.selectFrom("taxonomies")
|
|
101
131
|
.selectAll()
|
|
@@ -103,6 +133,8 @@ export class TaxonomyRepository {
|
|
|
103
133
|
.orderBy("label", "asc")
|
|
104
134
|
.orderBy("id", "asc");
|
|
105
135
|
|
|
136
|
+
if (options.locale !== undefined) query = query.where("locale", "=", options.locale);
|
|
137
|
+
|
|
106
138
|
if (options.parentId !== undefined) {
|
|
107
139
|
if (options.parentId === null) {
|
|
108
140
|
query = query.where("parent_id", "is", null);
|
|
@@ -115,9 +147,6 @@ export class TaxonomyRepository {
|
|
|
115
147
|
return rows.map((row) => this.rowToTaxonomy(row));
|
|
116
148
|
}
|
|
117
149
|
|
|
118
|
-
/**
|
|
119
|
-
* Get children of a taxonomy term
|
|
120
|
-
*/
|
|
121
150
|
async findChildren(parentId: string): Promise<Taxonomy[]> {
|
|
122
151
|
const rows = await this.db
|
|
123
152
|
.selectFrom("taxonomies")
|
|
@@ -126,18 +155,28 @@ export class TaxonomyRepository {
|
|
|
126
155
|
.orderBy("label", "asc")
|
|
127
156
|
.orderBy("id", "asc")
|
|
128
157
|
.execute();
|
|
129
|
-
|
|
130
158
|
return rows.map((row) => this.rowToTaxonomy(row));
|
|
131
159
|
}
|
|
132
160
|
|
|
133
161
|
/**
|
|
134
|
-
*
|
|
162
|
+
* Every translation sibling of a term (including itself), identified by
|
|
163
|
+
* their shared `translation_group`.
|
|
135
164
|
*/
|
|
165
|
+
async findTranslations(translationGroup: string): Promise<Taxonomy[]> {
|
|
166
|
+
const rows = await this.db
|
|
167
|
+
.selectFrom("taxonomies")
|
|
168
|
+
.selectAll()
|
|
169
|
+
.where("translation_group", "=", translationGroup)
|
|
170
|
+
.orderBy("locale", "asc")
|
|
171
|
+
.execute();
|
|
172
|
+
return rows.map((row) => this.rowToTaxonomy(row));
|
|
173
|
+
}
|
|
174
|
+
|
|
136
175
|
async update(id: string, input: UpdateTaxonomyInput): Promise<Taxonomy | null> {
|
|
137
176
|
const existing = await this.findById(id);
|
|
138
177
|
if (!existing) return null;
|
|
139
178
|
|
|
140
|
-
const updates:
|
|
179
|
+
const updates: Record<string, unknown> = {};
|
|
141
180
|
if (input.slug !== undefined) updates.slug = input.slug;
|
|
142
181
|
if (input.label !== undefined) updates.label = input.label;
|
|
143
182
|
if (input.parentId !== undefined) {
|
|
@@ -153,31 +192,42 @@ export class TaxonomyRepository {
|
|
|
153
192
|
return this.findById(id);
|
|
154
193
|
}
|
|
155
194
|
|
|
156
|
-
/**
|
|
157
|
-
* Delete a taxonomy term
|
|
158
|
-
*/
|
|
159
195
|
async delete(id: string): Promise<boolean> {
|
|
160
|
-
|
|
161
|
-
|
|
196
|
+
const term = await this.findById(id);
|
|
197
|
+
if (!term) return false;
|
|
198
|
+
|
|
199
|
+
// When deleting the last translation of a group the pivot rows that
|
|
200
|
+
// reference that translation_group become orphaned — purge them.
|
|
201
|
+
if (term.translationGroup) {
|
|
202
|
+
const siblings = await this.db
|
|
203
|
+
.selectFrom("taxonomies")
|
|
204
|
+
.select("id")
|
|
205
|
+
.where("translation_group", "=", term.translationGroup)
|
|
206
|
+
.where("id", "!=", id)
|
|
207
|
+
.execute();
|
|
208
|
+
if (siblings.length === 0) {
|
|
209
|
+
await this.db
|
|
210
|
+
.deleteFrom("content_taxonomies")
|
|
211
|
+
.where("taxonomy_id", "=", term.translationGroup)
|
|
212
|
+
.execute();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
162
215
|
|
|
163
216
|
const result = await this.db.deleteFrom("taxonomies").where("id", "=", id).executeTakeFirst();
|
|
164
|
-
|
|
165
|
-
return (result.numDeletedRows ?? 0) > 0;
|
|
217
|
+
return (result.numDeletedRows ?? 0n) > 0n;
|
|
166
218
|
}
|
|
167
219
|
|
|
168
|
-
// --- Content-Taxonomy Junction ---
|
|
220
|
+
// --- Content-Taxonomy Junction (taxonomy_id stores the translation_group) ---
|
|
169
221
|
|
|
170
|
-
/**
|
|
171
|
-
* Attach a taxonomy term to a content entry
|
|
172
|
-
*/
|
|
173
222
|
async attachToEntry(collection: string, entryId: string, taxonomyId: string): Promise<void> {
|
|
223
|
+
const group = await this.resolveTranslationGroup(taxonomyId);
|
|
224
|
+
if (!group) return;
|
|
225
|
+
|
|
174
226
|
const row: ContentTaxonomyTable = {
|
|
175
227
|
collection,
|
|
176
228
|
entry_id: entryId,
|
|
177
|
-
taxonomy_id:
|
|
229
|
+
taxonomy_id: group,
|
|
178
230
|
};
|
|
179
|
-
|
|
180
|
-
// Use INSERT OR IGNORE pattern for idempotency
|
|
181
231
|
await this.db
|
|
182
232
|
.insertInto("content_taxonomies")
|
|
183
233
|
.values(row)
|
|
@@ -185,58 +235,72 @@ export class TaxonomyRepository {
|
|
|
185
235
|
.execute();
|
|
186
236
|
}
|
|
187
237
|
|
|
188
|
-
/**
|
|
189
|
-
* Detach a taxonomy term from a content entry
|
|
190
|
-
*/
|
|
191
238
|
async detachFromEntry(collection: string, entryId: string, taxonomyId: string): Promise<void> {
|
|
239
|
+
const group = await this.resolveTranslationGroup(taxonomyId);
|
|
240
|
+
if (!group) return;
|
|
241
|
+
|
|
192
242
|
await this.db
|
|
193
243
|
.deleteFrom("content_taxonomies")
|
|
194
244
|
.where("collection", "=", collection)
|
|
195
245
|
.where("entry_id", "=", entryId)
|
|
196
|
-
.where("taxonomy_id", "=",
|
|
246
|
+
.where("taxonomy_id", "=", group)
|
|
197
247
|
.execute();
|
|
198
248
|
}
|
|
199
249
|
|
|
200
250
|
/**
|
|
201
|
-
*
|
|
251
|
+
* Taxonomy terms assigned to a content entry, resolved into a specific locale.
|
|
252
|
+
* Terms whose translation_group lacks a row in the requested locale are
|
|
253
|
+
* omitted — callers wanting fallback behaviour apply it themselves.
|
|
202
254
|
*/
|
|
203
255
|
async getTermsForEntry(
|
|
204
256
|
collection: string,
|
|
205
257
|
entryId: string,
|
|
206
258
|
taxonomyName?: string,
|
|
259
|
+
locale?: string,
|
|
207
260
|
): Promise<Taxonomy[]> {
|
|
208
261
|
let query = this.db
|
|
209
262
|
.selectFrom("content_taxonomies")
|
|
210
|
-
.innerJoin("taxonomies", "taxonomies.
|
|
263
|
+
.innerJoin("taxonomies", "taxonomies.translation_group", "content_taxonomies.taxonomy_id")
|
|
211
264
|
.selectAll("taxonomies")
|
|
212
265
|
.where("content_taxonomies.collection", "=", collection)
|
|
213
266
|
.where("content_taxonomies.entry_id", "=", entryId);
|
|
214
267
|
|
|
215
|
-
if (taxonomyName)
|
|
216
|
-
|
|
217
|
-
}
|
|
268
|
+
if (taxonomyName) query = query.where("taxonomies.name", "=", taxonomyName);
|
|
269
|
+
if (locale !== undefined) query = query.where("taxonomies.locale", "=", locale);
|
|
218
270
|
|
|
219
|
-
const rows = await query.execute();
|
|
271
|
+
const rows = await query.orderBy("taxonomies.locale", "asc").execute();
|
|
220
272
|
return rows.map((row) => this.rowToTaxonomy(row));
|
|
221
273
|
}
|
|
222
274
|
|
|
223
275
|
/**
|
|
224
|
-
*
|
|
225
|
-
*
|
|
276
|
+
* Replace all assignments of a given taxonomy for one content entry.
|
|
277
|
+
* Term ids OR translation_groups are accepted and normalised to groups.
|
|
226
278
|
*/
|
|
227
279
|
async setTermsForEntry(
|
|
228
280
|
collection: string,
|
|
229
281
|
entryId: string,
|
|
230
282
|
taxonomyName: string,
|
|
231
|
-
|
|
283
|
+
termIds: string[],
|
|
232
284
|
): Promise<void> {
|
|
233
|
-
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
285
|
+
const groups: string[] = [];
|
|
286
|
+
for (const id of termIds) {
|
|
287
|
+
const group = await this.resolveTranslationGroup(id);
|
|
288
|
+
if (group) groups.push(group);
|
|
289
|
+
}
|
|
290
|
+
const newGroups = new Set(groups);
|
|
237
291
|
|
|
238
|
-
|
|
239
|
-
|
|
292
|
+
const current = await this.db
|
|
293
|
+
.selectFrom("content_taxonomies")
|
|
294
|
+
.innerJoin("taxonomies", "taxonomies.translation_group", "content_taxonomies.taxonomy_id")
|
|
295
|
+
.select(["content_taxonomies.taxonomy_id as group"])
|
|
296
|
+
.distinct()
|
|
297
|
+
.where("content_taxonomies.collection", "=", collection)
|
|
298
|
+
.where("content_taxonomies.entry_id", "=", entryId)
|
|
299
|
+
.where("taxonomies.name", "=", taxonomyName)
|
|
300
|
+
.execute();
|
|
301
|
+
const currentGroups = new Set(current.map((r) => r.group));
|
|
302
|
+
|
|
303
|
+
const toRemove = [...currentGroups].filter((g) => !newGroups.has(g));
|
|
240
304
|
if (toRemove.length > 0) {
|
|
241
305
|
await this.db
|
|
242
306
|
.deleteFrom("content_taxonomies")
|
|
@@ -246,8 +310,7 @@ export class TaxonomyRepository {
|
|
|
246
310
|
.execute();
|
|
247
311
|
}
|
|
248
312
|
|
|
249
|
-
|
|
250
|
-
const toAdd = taxonomyIds.filter((id) => !currentIds.has(id));
|
|
313
|
+
const toAdd = [...newGroups].filter((g) => !currentGroups.has(g));
|
|
251
314
|
if (toAdd.length > 0) {
|
|
252
315
|
await this.db
|
|
253
316
|
.insertInto("content_taxonomies")
|
|
@@ -263,36 +326,101 @@ export class TaxonomyRepository {
|
|
|
263
326
|
}
|
|
264
327
|
}
|
|
265
328
|
|
|
266
|
-
/**
|
|
267
|
-
* Remove all taxonomy associations for an entry (use when entry is deleted)
|
|
268
|
-
*/
|
|
269
329
|
async clearEntryTerms(collection: string, entryId: string): Promise<number> {
|
|
270
330
|
const result = await this.db
|
|
271
331
|
.deleteFrom("content_taxonomies")
|
|
272
332
|
.where("collection", "=", collection)
|
|
273
333
|
.where("entry_id", "=", entryId)
|
|
274
334
|
.executeTakeFirst();
|
|
275
|
-
|
|
276
335
|
return Number(result.numDeletedRows ?? 0);
|
|
277
336
|
}
|
|
278
337
|
|
|
279
338
|
/**
|
|
280
|
-
*
|
|
339
|
+
* Copy every term assignment from one content entry to another. Used when
|
|
340
|
+
* creating a translation of a post so the new translation inherits the
|
|
341
|
+
* source's term assignments. Safe to call when the source has no terms.
|
|
342
|
+
*/
|
|
343
|
+
async copyEntryTerms(
|
|
344
|
+
collection: string,
|
|
345
|
+
sourceEntryId: string,
|
|
346
|
+
targetEntryId: string,
|
|
347
|
+
): Promise<void> {
|
|
348
|
+
const rows = await this.db
|
|
349
|
+
.selectFrom("content_taxonomies")
|
|
350
|
+
.select(["taxonomy_id"])
|
|
351
|
+
.where("collection", "=", collection)
|
|
352
|
+
.where("entry_id", "=", sourceEntryId)
|
|
353
|
+
.execute();
|
|
354
|
+
if (rows.length === 0) return;
|
|
355
|
+
|
|
356
|
+
await this.db
|
|
357
|
+
.insertInto("content_taxonomies")
|
|
358
|
+
.values(
|
|
359
|
+
rows.map((r) => ({
|
|
360
|
+
collection,
|
|
361
|
+
entry_id: targetEntryId,
|
|
362
|
+
taxonomy_id: r.taxonomy_id,
|
|
363
|
+
})),
|
|
364
|
+
)
|
|
365
|
+
.onConflict((oc) => oc.doNothing())
|
|
366
|
+
.execute();
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Count content entries that use any translation of this term. Accepts
|
|
371
|
+
* either a term id or a translation_group — we normalise to the group.
|
|
281
372
|
*/
|
|
282
|
-
async countEntriesWithTerm(
|
|
373
|
+
async countEntriesWithTerm(termIdOrGroup: string): Promise<number> {
|
|
374
|
+
const group = await this.resolveTranslationGroup(termIdOrGroup);
|
|
375
|
+
if (!group) return 0;
|
|
376
|
+
|
|
283
377
|
const result = await this.db
|
|
284
378
|
.selectFrom("content_taxonomies")
|
|
285
379
|
.select((eb) => eb.fn.count("entry_id").as("count"))
|
|
286
|
-
.where("taxonomy_id", "=",
|
|
380
|
+
.where("taxonomy_id", "=", group)
|
|
287
381
|
.executeTakeFirst();
|
|
382
|
+
return Number(result?.count ?? 0);
|
|
383
|
+
}
|
|
288
384
|
|
|
289
|
-
|
|
385
|
+
private async resolveTranslationGroup(idOrGroup: string): Promise<string | null> {
|
|
386
|
+
const row = await this.db
|
|
387
|
+
.selectFrom("taxonomies")
|
|
388
|
+
.select(["translation_group"])
|
|
389
|
+
.where((eb) => eb.or([eb("id", "=", idOrGroup), eb("translation_group", "=", idOrGroup)]))
|
|
390
|
+
.executeTakeFirst();
|
|
391
|
+
return row?.translation_group ?? null;
|
|
290
392
|
}
|
|
291
393
|
|
|
292
394
|
/**
|
|
293
|
-
*
|
|
395
|
+
* Batch count entries for multiple taxonomy translation_groups.
|
|
396
|
+
* Chunks the query at SQL_BATCH_SIZE to stay below D1's bind-parameter limit.
|
|
397
|
+
* Returns a Map from translation_group to count.
|
|
398
|
+
*
|
|
399
|
+
* Pass translation_groups (not term ids) — `content_taxonomies.taxonomy_id`
|
|
400
|
+
* stores the translation_group so a single assignment spans every locale.
|
|
294
401
|
*/
|
|
295
|
-
|
|
402
|
+
async countEntriesForTerms(translationGroups: string[]): Promise<Map<string, number>> {
|
|
403
|
+
if (translationGroups.length === 0) return new Map();
|
|
404
|
+
|
|
405
|
+
const { chunks, SQL_BATCH_SIZE } = await import("../../utils/chunks.js");
|
|
406
|
+
|
|
407
|
+
const counts = new Map<string, number>();
|
|
408
|
+
for (const chunk of chunks(translationGroups, SQL_BATCH_SIZE)) {
|
|
409
|
+
const rows = await this.db
|
|
410
|
+
.selectFrom("content_taxonomies")
|
|
411
|
+
.select(["taxonomy_id", (eb) => eb.fn.count("entry_id").as("count")])
|
|
412
|
+
.where("taxonomy_id", "in", chunk)
|
|
413
|
+
.groupBy("taxonomy_id")
|
|
414
|
+
.execute();
|
|
415
|
+
|
|
416
|
+
for (const row of rows) {
|
|
417
|
+
counts.set(row.taxonomy_id, Number(row.count || 0));
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return counts;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
private rowToTaxonomy(row: Selectable<TaxonomyTable>): Taxonomy {
|
|
296
424
|
return {
|
|
297
425
|
id: row.id,
|
|
298
426
|
name: row.name,
|
|
@@ -300,6 +428,8 @@ export class TaxonomyRepository {
|
|
|
300
428
|
label: row.label,
|
|
301
429
|
parentId: row.parent_id,
|
|
302
430
|
data: row.data ? JSON.parse(row.data) : null,
|
|
431
|
+
locale: row.locale,
|
|
432
|
+
translationGroup: row.translation_group,
|
|
303
433
|
};
|
|
304
434
|
}
|
|
305
435
|
}
|
package/src/database/types.ts
CHANGED
|
@@ -20,12 +20,14 @@ export interface TaxonomyTable {
|
|
|
20
20
|
label: string;
|
|
21
21
|
parent_id: string | null;
|
|
22
22
|
data: string | null; // JSON
|
|
23
|
+
locale: Generated<string>; // e.g. 'en', 'es', 'fr'
|
|
24
|
+
translation_group: string | null; // shared across translations of the same term
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
export interface ContentTaxonomyTable {
|
|
26
28
|
collection: string; // e.g., 'posts'
|
|
27
29
|
entry_id: string; // ID in the ec_* table
|
|
28
|
-
taxonomy_id: string;
|
|
30
|
+
taxonomy_id: string; // stores taxonomies.translation_group (locale-agnostic)
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
export interface TaxonomyDefTable {
|
|
@@ -36,6 +38,8 @@ export interface TaxonomyDefTable {
|
|
|
36
38
|
hierarchical: number; // 0 or 1 (SQLite boolean)
|
|
37
39
|
collections: string | null; // JSON array
|
|
38
40
|
created_at: Generated<string>;
|
|
41
|
+
locale: Generated<string>;
|
|
42
|
+
translation_group: string | null;
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
export interface MediaTable {
|
|
@@ -292,6 +296,8 @@ export interface MenuTable {
|
|
|
292
296
|
label: string;
|
|
293
297
|
created_at: Generated<string>;
|
|
294
298
|
updated_at: Generated<string>;
|
|
299
|
+
locale: Generated<string>;
|
|
300
|
+
translation_group: string | null;
|
|
295
301
|
}
|
|
296
302
|
|
|
297
303
|
export interface MenuItemTable {
|
|
@@ -301,13 +307,15 @@ export interface MenuItemTable {
|
|
|
301
307
|
sort_order: number;
|
|
302
308
|
type: string;
|
|
303
309
|
reference_collection: string | null;
|
|
304
|
-
reference_id: string | null;
|
|
310
|
+
reference_id: string | null; // stores translation_group of referenced content/term
|
|
305
311
|
custom_url: string | null;
|
|
306
312
|
label: string;
|
|
307
313
|
title_attr: string | null;
|
|
308
314
|
target: string | null;
|
|
309
315
|
css_classes: string | null;
|
|
310
316
|
created_at: Generated<string>;
|
|
317
|
+
locale: Generated<string>;
|
|
318
|
+
translation_group: string | null;
|
|
311
319
|
}
|
|
312
320
|
|
|
313
321
|
// Widget Areas
|
package/src/db/libsql.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Loaded at runtime via virtual module.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { LibsqlDialect } from "@libsql/kysely-libsql";
|
|
8
9
|
import type { Dialect } from "kysely";
|
|
9
10
|
|
|
10
11
|
import type { LibsqlConfig } from "./adapters.js";
|
|
@@ -13,9 +14,6 @@ import type { LibsqlConfig } from "./adapters.js";
|
|
|
13
14
|
* Create a libSQL dialect from config
|
|
14
15
|
*/
|
|
15
16
|
export function createDialect(config: LibsqlConfig): Dialect {
|
|
16
|
-
// Dynamic import to avoid loading @libsql/kysely-libsql at config time
|
|
17
|
-
const { LibsqlDialect } = require("@libsql/kysely-libsql");
|
|
18
|
-
|
|
19
17
|
return new LibsqlDialect({
|
|
20
18
|
url: config.url,
|
|
21
19
|
authToken: config.authToken,
|
package/src/db/sqlite.ts
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* Loaded at runtime via virtual module.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
import BetterSqlite3 from "better-sqlite3";
|
|
9
|
+
import { type Dialect, SqliteDialect } from "kysely";
|
|
9
10
|
|
|
10
11
|
import type { SqliteConfig } from "./adapters.js";
|
|
11
12
|
|
|
@@ -13,10 +14,6 @@ import type { SqliteConfig } from "./adapters.js";
|
|
|
13
14
|
* Create a SQLite dialect from config
|
|
14
15
|
*/
|
|
15
16
|
export function createDialect(config: SqliteConfig): Dialect {
|
|
16
|
-
// Dynamic import to avoid loading better-sqlite3 at config time
|
|
17
|
-
const BetterSqlite3 = require("better-sqlite3");
|
|
18
|
-
const { SqliteDialect } = require("kysely");
|
|
19
|
-
|
|
20
17
|
// Parse URL to get file path
|
|
21
18
|
const url = config.url;
|
|
22
19
|
const filePath = url.startsWith("file:") ? url.slice(5) : url;
|