emdash 0.7.0 → 1.0.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-Di31kZ28.d.mts → adapters-BKSf3T9R.d.mts} +1 -1
- package/dist/{adapters-Di31kZ28.d.mts.map → adapters-BKSf3T9R.d.mts.map} +1 -1
- package/dist/{apply-5uslYdUu.mjs → apply-x0eMK1lX.mjs} +18 -17
- package/dist/apply-x0eMK1lX.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 +86 -15
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +5 -5
- package/dist/astro/middleware/auth.d.mts.map +1 -1
- package/dist/astro/middleware/auth.mjs +22 -2
- package/dist/astro/middleware/auth.mjs.map +1 -1
- package/dist/astro/middleware/redirect.mjs +2 -2
- package/dist/astro/middleware/request-context.mjs +1 -1
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +259 -71
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/types.d.mts +16 -8
- package/dist/astro/types.d.mts.map +1 -1
- package/dist/{byline-C4OVd8b3.mjs → byline-Chbr2GoP.mjs} +3 -3
- package/dist/byline-Chbr2GoP.mjs.map +1 -0
- package/dist/{bylines-hPTW79hw.mjs → bylines-CRNsVG88.mjs} +4 -4
- package/dist/{bylines-hPTW79hw.mjs.map → bylines-CRNsVG88.mjs.map} +1 -1
- package/dist/cli/index.mjs +16 -12
- 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/{content-D7J5y73J.mjs → content-BcQPYxdV.mjs} +13 -15
- package/dist/content-BcQPYxdV.mjs.map +1 -0
- 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/{db-errors-D0UT85nC.mjs → db-errors-l1Qh2RPR.mjs} +1 -1
- package/dist/{db-errors-D0UT85nC.mjs.map → db-errors-l1Qh2RPR.mjs.map} +1 -1
- package/dist/{default-CME5YdZ3.mjs → default-DCVqE5ib.mjs} +1 -1
- package/dist/{default-CME5YdZ3.mjs.map → default-DCVqE5ib.mjs.map} +1 -1
- package/dist/{error-CiYn9yDu.mjs → error-zG5T1UGA.mjs} +1 -1
- package/dist/error-zG5T1UGA.mjs.map +1 -0
- package/dist/{index-De6_Xv3v.d.mts → index-DIb-CzNx.d.mts} +157 -14
- package/dist/index-DIb-CzNx.d.mts.map +1 -0
- package/dist/index.d.mts +11 -11
- package/dist/index.mjs +22 -20
- package/dist/{load-CBcmDIot.mjs → load-CyEoextb.mjs} +1 -1
- package/dist/{load-CBcmDIot.mjs.map → load-CyEoextb.mjs.map} +1 -1
- package/dist/{loader-DeiBJEMe.mjs → loader-CndGj8kM.mjs} +8 -6
- package/dist/loader-CndGj8kM.mjs.map +1 -0
- package/dist/{manifest-schema-V30qsMft.mjs → manifest-schema-DH9xhc6t.mjs} +13 -1
- package/dist/manifest-schema-DH9xhc6t.mjs.map +1 -0
- package/dist/media/index.d.mts +1 -1
- package/dist/media/local-runtime.d.mts +7 -7
- package/dist/media/local-runtime.mjs +2 -2
- package/dist/{media-DqHVh136.mjs → media-D8FbNsl0.mjs} +4 -7
- package/dist/media-D8FbNsl0.mjs.map +1 -0
- package/dist/{mode-CpNnGkPz.mjs → mode-BnAOqItE.mjs} +1 -1
- package/dist/mode-BnAOqItE.mjs.map +1 -0
- package/dist/page/index.d.mts +2 -2
- package/dist/placeholder-C-fk5hYI.mjs.map +1 -1
- package/dist/{placeholder-tzpqGWII.d.mts → placeholder-D29tWZ7o.d.mts} +1 -1
- package/dist/{placeholder-tzpqGWII.d.mts.map → placeholder-D29tWZ7o.d.mts.map} +1 -1
- package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
- package/dist/plugins/adapt-sandbox-entry.mjs +1 -1
- package/dist/{query-g4Ug-9j9.mjs → query-fqEdLFms.mjs} +9 -9
- package/dist/{query-g4Ug-9j9.mjs.map → query-fqEdLFms.mjs.map} +1 -1
- package/dist/{redirect-CN0Rt9Ob.mjs → redirect-D_pshWdf.mjs} +4 -4
- package/dist/redirect-D_pshWdf.mjs.map +1 -0
- package/dist/{registry-Ci3WxVAr.mjs → registry-C3Mr0ODu.mjs} +33 -9
- package/dist/registry-C3Mr0ODu.mjs.map +1 -0
- package/dist/{request-cache-DiR961CV.mjs → request-cache-Ci7f5pBb.mjs} +1 -1
- package/dist/request-cache-Ci7f5pBb.mjs.map +1 -0
- package/dist/{runner-BR2xKwhn.d.mts → runner-OURCaApa.d.mts} +2 -2
- package/dist/{runner-BR2xKwhn.d.mts.map → runner-OURCaApa.d.mts.map} +1 -1
- package/dist/runtime.d.mts +6 -6
- package/dist/runtime.mjs +2 -2
- package/dist/{search-B0effn3j.mjs → search-BoZYFuUk.mjs} +227 -84
- package/dist/search-BoZYFuUk.mjs.map +1 -0
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +12 -12
- 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.d.mts.map +1 -1
- package/dist/storage/s3.mjs +4 -4
- package/dist/storage/s3.mjs.map +1 -1
- package/dist/{taxonomies-K2z0Uhnj.mjs → taxonomies-B4IAshV8.mjs} +5 -5
- package/dist/{taxonomies-K2z0Uhnj.mjs.map → taxonomies-B4IAshV8.mjs.map} +1 -1
- package/dist/{tokens-BFPFx3CA.mjs → tokens-D9vnZqYS.mjs} +1 -1
- package/dist/{tokens-BFPFx3CA.mjs.map → tokens-D9vnZqYS.mjs.map} +1 -1
- package/dist/{transport-BykRfpyy.mjs → transport-C9ugt2Nr.mjs} +1 -1
- package/dist/{transport-BykRfpyy.mjs.map → transport-C9ugt2Nr.mjs.map} +1 -1
- package/dist/{transport-H4Iwx7tC.d.mts → transport-CUnEL3Vs.d.mts} +1 -1
- package/dist/{transport-H4Iwx7tC.d.mts.map → transport-CUnEL3Vs.d.mts.map} +1 -1
- package/dist/types-BIgulNsW.mjs +68 -0
- package/dist/types-BIgulNsW.mjs.map +1 -0
- package/dist/{types-DDS4MxsT.mjs → types-Bm1dn-q3.mjs} +1 -1
- package/dist/{types-DDS4MxsT.mjs.map → types-Bm1dn-q3.mjs.map} +1 -1
- package/dist/{types-CnZYHyLW.d.mts → types-BmPPSUEx.d.mts} +1 -1
- package/dist/{types-CnZYHyLW.d.mts.map → types-BmPPSUEx.d.mts.map} +1 -1
- package/dist/{types-6CUZRrZP.d.mts → types-BrA0xf5I.d.mts} +24 -2
- package/dist/{types-6CUZRrZP.d.mts.map → types-BrA0xf5I.d.mts.map} +1 -1
- package/dist/{types-C2v0c34j.d.mts → types-CS8FIX7L.d.mts} +1 -1
- package/dist/{types-C2v0c34j.d.mts.map → types-CS8FIX7L.d.mts.map} +1 -1
- package/dist/{types-BH2L167P.mjs → types-CgqmmMJB.mjs} +1 -1
- package/dist/{types-BH2L167P.mjs.map → types-CgqmmMJB.mjs.map} +1 -1
- package/dist/{types-CFWjXmus.d.mts → types-DIMwPFub.d.mts} +1 -1
- package/dist/{types-CFWjXmus.d.mts.map → types-DIMwPFub.d.mts.map} +1 -1
- package/dist/{types-DgrIP0tF.d.mts → types-i36XcA_X.d.mts} +49 -6
- package/dist/types-i36XcA_X.d.mts.map +1 -0
- package/dist/{validate-CqsNItbt.mjs → validate-CxVsLehf.mjs} +2 -2
- package/dist/{validate-CqsNItbt.mjs.map → validate-CxVsLehf.mjs.map} +1 -1
- package/dist/{validate-kM8Pjuf7.d.mts → validate-DHxmpFJt.d.mts} +4 -4
- package/dist/{validate-kM8Pjuf7.d.mts.map → validate-DHxmpFJt.d.mts.map} +1 -1
- package/dist/validation-C-ZpN2GI.mjs +144 -0
- package/dist/validation-C-ZpN2GI.mjs.map +1 -0
- package/dist/version-DJrV1K0M.mjs +7 -0
- package/dist/{version-BnTKdfam.mjs.map → version-DJrV1K0M.mjs.map} +1 -1
- package/dist/zod-generator-CpwccCIv.mjs +132 -0
- package/dist/zod-generator-CpwccCIv.mjs.map +1 -0
- package/package.json +19 -6
- package/src/api/auth-storage.ts +37 -0
- package/src/api/error.ts +6 -0
- package/src/api/errors.ts +8 -0
- package/src/api/handlers/comments.ts +13 -0
- package/src/api/handlers/content.ts +122 -3
- package/src/api/handlers/index.ts +2 -0
- package/src/api/handlers/media.ts +8 -1
- package/src/api/handlers/menus.ts +160 -21
- package/src/api/handlers/redirects.ts +16 -3
- package/src/api/handlers/sections.ts +8 -1
- package/src/api/handlers/taxonomies.ts +128 -16
- package/src/api/handlers/validation.ts +212 -0
- package/src/api/openapi/document.ts +4 -1
- package/src/api/public-url.ts +6 -3
- package/src/api/route-utils.ts +14 -0
- package/src/api/schemas/common.ts +1 -1
- package/src/api/schemas/setup.ts +8 -0
- package/src/api/schemas/widgets.ts +12 -10
- package/src/api/setup-complete.ts +40 -0
- package/src/astro/integration/index.ts +13 -2
- package/src/astro/integration/routes.ts +28 -0
- package/src/astro/integration/runtime.ts +19 -1
- package/src/astro/integration/virtual-modules.ts +41 -0
- package/src/astro/integration/vite-config.ts +43 -12
- package/src/astro/middleware/auth.ts +21 -0
- package/src/astro/middleware.ts +18 -1
- package/src/astro/routes/PluginRegistry.tsx +10 -1
- package/src/astro/routes/api/auth/mode.ts +57 -0
- package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +23 -3
- package/src/astro/routes/api/auth/oauth/[provider].ts +10 -4
- package/src/astro/routes/api/content/[collection]/[id]/translations.ts +1 -1
- package/src/astro/routes/api/content/[collection]/index.ts +1 -9
- package/src/astro/routes/api/import/wordpress/media.ts +2 -7
- package/src/astro/routes/api/import/wordpress/prepare.ts +10 -0
- package/src/astro/routes/api/settings/email.ts +4 -9
- package/src/astro/routes/api/setup/admin.ts +8 -2
- package/src/astro/routes/api/setup/index.ts +2 -2
- package/src/astro/routes/api/setup/status.ts +3 -1
- package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +4 -1
- package/src/astro/routes/api/widget-areas/[name]/widgets.ts +4 -1
- package/src/astro/routes/api/widget-areas/[name].ts +4 -1
- package/src/astro/routes/api/widget-areas/index.ts +4 -1
- package/src/astro/types.ts +9 -0
- package/src/auth/mode.ts +15 -3
- package/src/auth/providers/github-admin.tsx +29 -0
- package/src/auth/providers/github.ts +31 -0
- package/src/auth/providers/google-admin.tsx +44 -0
- package/src/auth/providers/google.ts +31 -0
- package/src/auth/types.ts +114 -4
- package/src/cli/commands/bundle.ts +3 -1
- package/src/components/EmDashImage.astro +7 -6
- package/src/components/Gallery.astro +5 -3
- package/src/components/Image.astro +8 -3
- package/src/components/InlinePortableTextEditor.tsx +2 -1
- package/src/components/LiveSearch.astro +5 -14
- package/src/database/repositories/audit.ts +6 -8
- package/src/database/repositories/byline.ts +6 -8
- package/src/database/repositories/comment.ts +12 -16
- package/src/database/repositories/content.ts +40 -40
- package/src/database/repositories/index.ts +1 -1
- package/src/database/repositories/media.ts +10 -13
- package/src/database/repositories/plugin-storage.ts +4 -6
- package/src/database/repositories/redirect.ts +12 -16
- package/src/database/repositories/taxonomy.ts +14 -3
- package/src/database/repositories/types.ts +57 -8
- package/src/database/repositories/user.ts +6 -8
- package/src/emdash-runtime.ts +306 -90
- package/src/index.ts +5 -1
- package/src/loader.ts +6 -5
- package/src/mcp/server.ts +678 -105
- package/src/media/normalize.ts +1 -1
- package/src/media/url.ts +78 -0
- package/src/plugins/email-console.ts +10 -3
- package/src/plugins/hooks.ts +11 -0
- package/src/plugins/manifest-schema.ts +12 -0
- package/src/plugins/types.ts +23 -2
- package/src/query.ts +1 -1
- package/src/request-cache.ts +3 -0
- package/src/schema/registry.ts +41 -5
- package/src/search/fts-manager.ts +0 -2
- package/src/search/query.ts +111 -26
- package/src/search/types.ts +8 -1
- package/src/sections/index.ts +7 -9
- package/src/storage/s3.ts +12 -6
- package/src/virtual-modules.d.ts +21 -1
- package/src/widgets/index.ts +1 -1
- package/dist/apply-5uslYdUu.mjs.map +0 -1
- package/dist/byline-C4OVd8b3.mjs.map +0 -1
- package/dist/content-D7J5y73J.mjs.map +0 -1
- package/dist/error-CiYn9yDu.mjs.map +0 -1
- package/dist/index-De6_Xv3v.d.mts.map +0 -1
- package/dist/loader-DeiBJEMe.mjs.map +0 -1
- package/dist/manifest-schema-V30qsMft.mjs.map +0 -1
- package/dist/media-DqHVh136.mjs.map +0 -1
- package/dist/mode-CpNnGkPz.mjs.map +0 -1
- package/dist/redirect-CN0Rt9Ob.mjs.map +0 -1
- package/dist/registry-Ci3WxVAr.mjs.map +0 -1
- package/dist/request-cache-DiR961CV.mjs.map +0 -1
- package/dist/search-B0effn3j.mjs.map +0 -1
- package/dist/types-CMMN0pNg.mjs +0 -31
- package/dist/types-CMMN0pNg.mjs.map +0 -1
- package/dist/types-DgrIP0tF.d.mts.map +0 -1
- package/dist/version-BnTKdfam.mjs +0 -7
package/dist/client/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as tokenInterceptor, i as devBypassInterceptor, n as createTransport, r as csrfInterceptor, t as Interceptor } from "../transport-
|
|
1
|
+
import { a as tokenInterceptor, i as devBypassInterceptor, n as createTransport, r as csrfInterceptor, t as Interceptor } from "../transport-CUnEL3Vs.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/client/portable-text.d.ts
|
|
4
4
|
/**
|
package/dist/client/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as tokenInterceptor, c as markdownToPortableText, i as refreshInterceptor, l as portableTextToMarkdown, n as csrfInterceptor, o as convertDataForRead, r as devBypassInterceptor, s as convertDataForWrite, t as createTransport } from "../transport-
|
|
1
|
+
import { a as tokenInterceptor, c as markdownToPortableText, i as refreshInterceptor, l as portableTextToMarkdown, n as csrfInterceptor, o as convertDataForRead, r as devBypassInterceptor, s as convertDataForWrite, t as createTransport } from "../transport-C9ugt2Nr.mjs";
|
|
2
2
|
import mime from "mime/lite";
|
|
3
3
|
|
|
4
4
|
//#region src/client/index.ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-ClPoSABd.mjs";
|
|
2
2
|
import { t as validateIdentifier } from "./validate-VPnKoIzW.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { i as encodeCursor, r as decodeCursor, t as EmDashValidationError } from "./types-BIgulNsW.mjs";
|
|
4
4
|
import { sql } from "kysely";
|
|
5
5
|
import { monotonicFactory, ulid } from "ulidx";
|
|
6
6
|
|
|
@@ -434,12 +434,9 @@ var ContentRepository = class {
|
|
|
434
434
|
if (options.where?.authorId) query = query.where("author_id", "=", options.where.authorId);
|
|
435
435
|
if (options.where?.locale) query = query.where("locale", "=", options.where.locale);
|
|
436
436
|
if (options.cursor) {
|
|
437
|
-
const
|
|
438
|
-
if (
|
|
439
|
-
|
|
440
|
-
if (safeOrderDirection === "DESC") query = query.where((eb) => eb.or([eb(dbField, "<", orderValue), eb.and([eb(dbField, "=", orderValue), eb("id", "<", cursorId)])]));
|
|
441
|
-
else query = query.where((eb) => eb.or([eb(dbField, ">", orderValue), eb.and([eb(dbField, "=", orderValue), eb("id", ">", cursorId)])]));
|
|
442
|
-
}
|
|
437
|
+
const { orderValue, id: cursorId } = decodeCursor(options.cursor);
|
|
438
|
+
if (safeOrderDirection === "DESC") query = query.where((eb) => eb.or([eb(dbField, "<", orderValue), eb.and([eb(dbField, "=", orderValue), eb("id", "<", cursorId)])]));
|
|
439
|
+
else query = query.where((eb) => eb.or([eb(dbField, ">", orderValue), eb.and([eb(dbField, "=", orderValue), eb("id", ">", cursorId)])]));
|
|
443
440
|
}
|
|
444
441
|
query = query.orderBy(dbField, safeOrderDirection === "ASC" ? "asc" : "desc").orderBy("id", safeOrderDirection === "ASC" ? "asc" : "desc").limit(limit + 1);
|
|
445
442
|
const rows = await query.execute();
|
|
@@ -526,12 +523,9 @@ var ContentRepository = class {
|
|
|
526
523
|
const safeOrderDirection = orderDirection.toLowerCase() === "asc" ? "ASC" : "DESC";
|
|
527
524
|
let query = this.db.selectFrom(tableName).selectAll().where("deleted_at", "is not", null);
|
|
528
525
|
if (options.cursor) {
|
|
529
|
-
const
|
|
530
|
-
if (
|
|
531
|
-
|
|
532
|
-
if (safeOrderDirection === "DESC") query = query.where((eb) => eb.or([eb(dbField, "<", orderValue), eb.and([eb(dbField, "=", orderValue), eb("id", "<", cursorId)])]));
|
|
533
|
-
else query = query.where((eb) => eb.or([eb(dbField, ">", orderValue), eb.and([eb(dbField, "=", orderValue), eb("id", ">", cursorId)])]));
|
|
534
|
-
}
|
|
526
|
+
const { orderValue, id: cursorId } = decodeCursor(options.cursor);
|
|
527
|
+
if (safeOrderDirection === "DESC") query = query.where((eb) => eb.or([eb(dbField, "<", orderValue), eb.and([eb(dbField, "=", orderValue), eb("id", "<", cursorId)])]));
|
|
528
|
+
else query = query.where((eb) => eb.or([eb(dbField, ">", orderValue), eb.and([eb(dbField, "=", orderValue), eb("id", ">", cursorId)])]));
|
|
535
529
|
}
|
|
536
530
|
query = query.orderBy(dbField, safeOrderDirection === "ASC" ? "asc" : "desc").orderBy("id", safeOrderDirection === "ASC" ? "asc" : "desc").limit(limit + 1);
|
|
537
531
|
const rows = await query.execute();
|
|
@@ -741,6 +735,7 @@ var ContentRepository = class {
|
|
|
741
735
|
UPDATE ${sql.ref(tableName)}
|
|
742
736
|
SET live_revision_id = NULL,
|
|
743
737
|
status = 'draft',
|
|
738
|
+
published_at = NULL,
|
|
744
739
|
updated_at = ${now}
|
|
745
740
|
WHERE id = ${id}
|
|
746
741
|
AND deleted_at IS NULL
|
|
@@ -868,7 +863,10 @@ var ContentRepository = class {
|
|
|
868
863
|
scheduledAt: "scheduled_at",
|
|
869
864
|
deletedAt: "deleted_at",
|
|
870
865
|
title: "title",
|
|
871
|
-
|
|
866
|
+
name: "name",
|
|
867
|
+
slug: "slug",
|
|
868
|
+
status: "status",
|
|
869
|
+
locale: "locale"
|
|
872
870
|
}[field];
|
|
873
871
|
if (!mapped) throw new EmDashValidationError(`Invalid order field: ${field}`);
|
|
874
872
|
return mapped;
|
|
@@ -877,4 +875,4 @@ var ContentRepository = class {
|
|
|
877
875
|
|
|
878
876
|
//#endregion
|
|
879
877
|
export { slugify as a, decodeSlug as i, content_exports as n, RevisionRepository as r, ContentRepository as t };
|
|
880
|
-
//# sourceMappingURL=content-
|
|
878
|
+
//# sourceMappingURL=content-BcQPYxdV.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-BcQPYxdV.mjs","names":[],"sources":["../src/utils/slugify.ts","../src/database/repositories/revision.ts","../src/database/repositories/content.ts"],"sourcesContent":["// Regex patterns for slug normalization\nconst DIACRITICS_PATTERN = /[\\u0300-\\u036f]/g;\nconst WHITESPACE_UNDERSCORE_PATTERN = /[\\s_]+/g;\nconst NON_ALPHANUMERIC_HYPHEN_PATTERN = /[^a-z0-9-]/g;\nconst MULTIPLE_HYPHENS_PATTERN = /-+/g;\nconst LEADING_TRAILING_HYPHEN_PATTERN = /^-|-$/g;\nconst TRAILING_HYPHEN_PATTERN = /-$/;\n\n/**\n * Convert a string to a URL-friendly slug.\n *\n * Handles unicode by normalizing to NFD and stripping diacritics,\n * so \"café\" becomes \"cafe\", \"naïve\" becomes \"naive\", etc.\n */\n/**\n * Decode a URI-encoded slug parameter.\n *\n * Browsers percent-encode non-ASCII characters in URLs, so a slug like\n * \"మేష-రాసి\" arrives as \"%e0%b0%ae%e0%b1%87%e0%b0%b7-%e0%b0%b0%e0%b0%be%e0%b0%b8%e0%b0%bf\".\n * Call this on `Astro.params.slug` before using it in database lookups.\n */\nexport function decodeSlug(raw: string | undefined): string | undefined {\n\treturn raw ? decodeURIComponent(raw) : undefined;\n}\n\nexport function slugify(text: string, maxLength: number = 80): string {\n\treturn (\n\t\ttext\n\t\t\t.toLowerCase()\n\t\t\t.normalize(\"NFD\")\n\t\t\t.replace(DIACRITICS_PATTERN, \"\")\n\t\t\t.replace(WHITESPACE_UNDERSCORE_PATTERN, \"-\")\n\t\t\t.replace(NON_ALPHANUMERIC_HYPHEN_PATTERN, \"\")\n\t\t\t.replace(MULTIPLE_HYPHENS_PATTERN, \"-\")\n\t\t\t.replace(LEADING_TRAILING_HYPHEN_PATTERN, \"\")\n\t\t\t.slice(0, maxLength)\n\t\t\t// Clean trailing hyphen from truncation\n\t\t\t.replace(TRAILING_HYPHEN_PATTERN, \"\")\n\t);\n}\n","import type { Kysely } from \"kysely\";\nimport { monotonicFactory } from \"ulidx\";\n\nimport type { Database, RevisionTable } from \"../types.js\";\n\nconst monotonic = monotonicFactory();\n\nexport interface Revision {\n\tid: string;\n\tcollection: string;\n\tentryId: string;\n\tdata: Record<string, unknown>;\n\tauthorId: string | null;\n\tcreatedAt: string;\n}\n\nexport interface CreateRevisionInput {\n\tcollection: string;\n\tentryId: string;\n\tdata: Record<string, unknown>;\n\tauthorId?: string;\n}\n\n/**\n * Revision repository for version history\n *\n * Each revision stores a JSON snapshot of the content at a point in time.\n * Used when collection has `supports: [\"revisions\"]` enabled.\n */\nexport class RevisionRepository {\n\tconstructor(private db: Kysely<Database>) {}\n\n\t/**\n\t * Create a new revision\n\t */\n\tasync create(input: CreateRevisionInput): Promise<Revision> {\n\t\tconst id = monotonic();\n\n\t\tconst row: Omit<RevisionTable, \"created_at\"> = {\n\t\t\tid,\n\t\t\tcollection: input.collection,\n\t\t\tentry_id: input.entryId,\n\t\t\tdata: JSON.stringify(input.data),\n\t\t\tauthor_id: input.authorId ?? null,\n\t\t};\n\n\t\tawait this.db.insertInto(\"revisions\").values(row).execute();\n\n\t\tconst revision = await this.findById(id);\n\t\tif (!revision) {\n\t\t\tthrow new Error(\"Failed to create revision\");\n\t\t}\n\t\treturn revision;\n\t}\n\n\t/**\n\t * Find revision by ID\n\t */\n\tasync findById(id: string): Promise<Revision | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"revisions\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirst();\n\n\t\treturn row ? this.rowToRevision(row) : null;\n\t}\n\n\t/**\n\t * Get all revisions for an entry (newest first)\n\t *\n\t * Orders by monotonic ULID (descending). The monotonic factory\n\t * guarantees strictly increasing IDs even within the same millisecond.\n\t */\n\tasync findByEntry(\n\t\tcollection: string,\n\t\tentryId: string,\n\t\toptions: { limit?: number } = {},\n\t): Promise<Revision[]> {\n\t\tlet query = this.db\n\t\t\t.selectFrom(\"revisions\")\n\t\t\t.selectAll()\n\t\t\t.where(\"collection\", \"=\", collection)\n\t\t\t.where(\"entry_id\", \"=\", entryId)\n\t\t\t.orderBy(\"id\", \"desc\");\n\n\t\tif (options.limit) {\n\t\t\tquery = query.limit(options.limit);\n\t\t}\n\n\t\tconst rows = await query.execute();\n\t\treturn rows.map((row) => this.rowToRevision(row));\n\t}\n\n\t/**\n\t * Get the most recent revision for an entry\n\t */\n\tasync findLatest(collection: string, entryId: string): Promise<Revision | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"revisions\")\n\t\t\t.selectAll()\n\t\t\t.where(\"collection\", \"=\", collection)\n\t\t\t.where(\"entry_id\", \"=\", entryId)\n\t\t\t.orderBy(\"id\", \"desc\")\n\t\t\t.limit(1)\n\t\t\t.executeTakeFirst();\n\n\t\treturn row ? this.rowToRevision(row) : null;\n\t}\n\n\t/**\n\t * Count revisions for an entry\n\t */\n\tasync countByEntry(collection: string, entryId: string): Promise<number> {\n\t\tconst result = await this.db\n\t\t\t.selectFrom(\"revisions\")\n\t\t\t.select((eb) => eb.fn.count(\"id\").as(\"count\"))\n\t\t\t.where(\"collection\", \"=\", collection)\n\t\t\t.where(\"entry_id\", \"=\", entryId)\n\t\t\t.executeTakeFirst();\n\n\t\treturn Number(result?.count || 0);\n\t}\n\n\t/**\n\t * Delete all revisions for an entry (use when entry is deleted)\n\t */\n\tasync deleteByEntry(collection: string, entryId: string): Promise<number> {\n\t\tconst result = await this.db\n\t\t\t.deleteFrom(\"revisions\")\n\t\t\t.where(\"collection\", \"=\", collection)\n\t\t\t.where(\"entry_id\", \"=\", entryId)\n\t\t\t.executeTakeFirst();\n\n\t\treturn Number(result.numDeletedRows ?? 0);\n\t}\n\n\t/**\n\t * Delete old revisions, keeping the most recent N\n\t */\n\tasync pruneOldRevisions(collection: string, entryId: string, keepCount: number): Promise<number> {\n\t\t// Get IDs of revisions to keep\n\t\tconst keep = await this.db\n\t\t\t.selectFrom(\"revisions\")\n\t\t\t.select(\"id\")\n\t\t\t.where(\"collection\", \"=\", collection)\n\t\t\t.where(\"entry_id\", \"=\", entryId)\n\t\t\t.orderBy(\"created_at\", \"desc\")\n\t\t\t.orderBy(\"id\", \"desc\") // ULID tiebreaker\n\t\t\t.limit(keepCount)\n\t\t\t.execute();\n\n\t\tconst keepIds = keep.map((r) => r.id);\n\n\t\tif (keepIds.length === 0) return 0;\n\n\t\t// Delete everything else for this entry\n\t\tconst result = await this.db\n\t\t\t.deleteFrom(\"revisions\")\n\t\t\t.where(\"collection\", \"=\", collection)\n\t\t\t.where(\"entry_id\", \"=\", entryId)\n\t\t\t.where(\"id\", \"not in\", keepIds)\n\t\t\t.executeTakeFirst();\n\n\t\treturn Number(result.numDeletedRows ?? 0);\n\t}\n\n\t/**\n\t * Update revision data in place\n\t * Used for autosave to avoid creating many small revisions.\n\t */\n\tasync updateData(id: string, data: Record<string, unknown>): Promise<void> {\n\t\tawait this.db\n\t\t\t.updateTable(\"revisions\")\n\t\t\t.set({ data: JSON.stringify(data) })\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.execute();\n\t}\n\n\t/**\n\t * Convert database row to Revision object\n\t */\n\tprivate rowToRevision(row: {\n\t\tid: string;\n\t\tcollection: string;\n\t\tentry_id: string;\n\t\tdata: string;\n\t\tauthor_id: string | null;\n\t\tcreated_at: string;\n\t}): Revision {\n\t\treturn {\n\t\t\tid: row.id,\n\t\t\tcollection: row.collection,\n\t\t\tentryId: row.entry_id,\n\t\t\tdata: JSON.parse(row.data),\n\t\t\tauthorId: row.author_id,\n\t\t\tcreatedAt: row.created_at,\n\t\t};\n\t}\n}\n","import { sql, type Kysely } from \"kysely\";\nimport { ulid } from \"ulidx\";\n\nimport { slugify } from \"../../utils/slugify.js\";\nimport type { Database } from \"../types.js\";\nimport { validateIdentifier } from \"../validate.js\";\nimport { RevisionRepository } from \"./revision.js\";\nimport type {\n\tCreateContentInput,\n\tUpdateContentInput,\n\tFindManyOptions,\n\tFindManyResult,\n\tContentItem,\n} from \"./types.js\";\nimport { EmDashValidationError, encodeCursor, decodeCursor } from \"./types.js\";\n\n// Regex pattern for ULID validation\nconst ULID_PATTERN = /^[0-9A-Z]{26}$/;\n\n/**\n * System columns that exist in every ec_* table\n */\nconst SYSTEM_COLUMNS = new Set([\n\t\"id\",\n\t\"slug\",\n\t\"status\",\n\t\"author_id\",\n\t\"primary_byline_id\",\n\t\"created_at\",\n\t\"updated_at\",\n\t\"published_at\",\n\t\"scheduled_at\",\n\t\"deleted_at\",\n\t\"version\",\n\t\"live_revision_id\",\n\t\"draft_revision_id\",\n\t\"locale\",\n\t\"translation_group\",\n]);\n\n/**\n * Get the table name for a collection type\n */\nfunction getTableName(type: string): string {\n\tvalidateIdentifier(type, \"collection type\");\n\treturn `ec_${type}`;\n}\n\n/**\n * Serialize a value for database storage\n * Objects/arrays are JSON-stringified\n * Booleans are converted to 0/1 for SQLite\n */\nfunction serializeValue(value: unknown): unknown {\n\tif (value === null || value === undefined) {\n\t\treturn null;\n\t}\n\tif (typeof value === \"boolean\") {\n\t\treturn value ? 1 : 0;\n\t}\n\tif (typeof value === \"object\") {\n\t\treturn JSON.stringify(value);\n\t}\n\treturn value;\n}\n\n/**\n * Deserialize a value from database storage\n * Attempts to parse JSON strings that look like objects/arrays\n */\nfunction deserializeValue(value: unknown): unknown {\n\tif (typeof value === \"string\") {\n\t\t// Try to parse if it looks like JSON\n\t\tif (value.startsWith(\"{\") || value.startsWith(\"[\")) {\n\t\t\ttry {\n\t\t\t\treturn JSON.parse(value);\n\t\t\t} catch {\n\t\t\t\treturn value;\n\t\t\t}\n\t\t}\n\t}\n\treturn value;\n}\n\n/** Pattern for escaping special regex characters */\nconst REGEX_ESCAPE_PATTERN = /[.*+?^${}()|[\\]\\\\]/g;\n\n/**\n * Escape special regex characters in a string for use in `new RegExp()`\n */\nfunction escapeRegExp(s: string): string {\n\treturn s.replace(REGEX_ESCAPE_PATTERN, \"\\\\$&\");\n}\n\n/**\n * Repository for content CRUD operations\n *\n * Content is stored in per-collection tables (ec_posts, ec_pages, etc.)\n * Each field becomes a real column in the table.\n */\nexport class ContentRepository {\n\tconstructor(private db: Kysely<Database>) {}\n\n\t/**\n\t * Create a new content item\n\t */\n\tasync create(input: CreateContentInput): Promise<ContentItem> {\n\t\tconst id = ulid();\n\t\tconst now = new Date().toISOString();\n\n\t\tconst {\n\t\t\ttype,\n\t\t\tslug,\n\t\t\tdata,\n\t\t\tstatus = \"draft\",\n\t\t\tauthorId,\n\t\t\tprimaryBylineId,\n\t\t\tlocale,\n\t\t\ttranslationOf,\n\t\t\tpublishedAt,\n\t\t\tcreatedAt,\n\t\t} = input;\n\n\t\t// Validate required fields\n\t\tif (!type) {\n\t\t\tthrow new EmDashValidationError(\"Content type is required\");\n\t\t}\n\n\t\tconst tableName = getTableName(type);\n\n\t\t// Resolve translation_group: if translationOf is set, look up the source item's group\n\t\tlet translationGroup: string = id; // default: self-reference\n\t\tif (translationOf) {\n\t\t\tconst source = await this.findById(type, translationOf);\n\t\t\tif (!source) {\n\t\t\t\tthrow new EmDashValidationError(\"Translation source content not found\");\n\t\t\t}\n\t\t\ttranslationGroup = source.translationGroup || source.id;\n\t\t}\n\n\t\t// Build column names and values\n\t\tconst columns: string[] = [\n\t\t\t\"id\",\n\t\t\t\"slug\",\n\t\t\t\"status\",\n\t\t\t\"author_id\",\n\t\t\t\"primary_byline_id\",\n\t\t\t\"created_at\",\n\t\t\t\"updated_at\",\n\t\t\t\"published_at\",\n\t\t\t\"version\",\n\t\t\t\"locale\",\n\t\t\t\"translation_group\",\n\t\t];\n\t\tconst values: unknown[] = [\n\t\t\tid,\n\t\t\tslug || null,\n\t\t\tstatus,\n\t\t\tauthorId || null,\n\t\t\tprimaryBylineId ?? null,\n\t\t\tcreatedAt || now,\n\t\t\tnow,\n\t\t\tpublishedAt || null,\n\t\t\t1,\n\t\t\tlocale || \"en\",\n\t\t\ttranslationGroup,\n\t\t];\n\n\t\t// Add data fields as columns (skip system columns to prevent injection via data)\n\t\tif (data && typeof data === \"object\") {\n\t\t\tfor (const [key, value] of Object.entries(data)) {\n\t\t\t\tif (!SYSTEM_COLUMNS.has(key)) {\n\t\t\t\t\tvalidateIdentifier(key, \"content field name\");\n\t\t\t\t\tcolumns.push(key);\n\t\t\t\t\tvalues.push(serializeValue(value));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Build dynamic INSERT using raw SQL\n\t\tconst columnRefs = columns.map((c) => sql.ref(c));\n\t\tconst valuePlaceholders = values.map((v) => (v === null ? sql`NULL` : sql`${v}`));\n\n\t\tawait sql`\n\t\t\tINSERT INTO ${sql.ref(tableName)} (${sql.join(columnRefs, sql`, `)})\n\t\t\tVALUES (${sql.join(valuePlaceholders, sql`, `)})\n\t\t`.execute(this.db);\n\n\t\t// Fetch and return the created item\n\t\tconst item = await this.findById(type, id);\n\t\tif (!item) {\n\t\t\tthrow new Error(\"Failed to create content\");\n\t\t}\n\t\treturn item;\n\t}\n\n\t/**\n\t * Generate a unique slug for a content item within a collection.\n\t *\n\t * Checks the collection table for existing slugs that match `baseSlug`\n\t * (optionally scoped to a locale) and appends a numeric suffix (`-1`,\n\t * `-2`, etc.) on collision to guarantee uniqueness.\n\t *\n\t * Returns `null` if `baseSlug` is empty after slugification.\n\t */\n\tasync generateUniqueSlug(type: string, text: string, locale?: string): Promise<string | null> {\n\t\tconst baseSlug = slugify(text);\n\t\tif (!baseSlug) return null;\n\n\t\tconst tableName = getTableName(type);\n\n\t\t// Check if the base slug is available\n\t\tconst existing = locale\n\t\t\t? await sql<{ slug: string }>`\n\t\t\t\t\tSELECT slug FROM ${sql.ref(tableName)}\n\t\t\t\t\tWHERE slug = ${baseSlug}\n\t\t\t\t\tAND locale = ${locale}\n\t\t\t\t\tLIMIT 1\n\t\t\t\t`.execute(this.db)\n\t\t\t: await sql<{ slug: string }>`\n\t\t\t\t\tSELECT slug FROM ${sql.ref(tableName)}\n\t\t\t\t\tWHERE slug = ${baseSlug}\n\t\t\t\t\tLIMIT 1\n\t\t\t\t`.execute(this.db);\n\n\t\tif (existing.rows.length === 0) {\n\t\t\treturn baseSlug;\n\t\t}\n\n\t\t// Find all slugs matching the pattern `baseSlug` or `baseSlug-N`\n\t\tconst pattern = `${baseSlug}-%`;\n\t\tconst candidates = locale\n\t\t\t? await sql<{ slug: string }>`\n\t\t\t\t\tSELECT slug FROM ${sql.ref(tableName)}\n\t\t\t\t\tWHERE (slug = ${baseSlug} OR slug LIKE ${pattern})\n\t\t\t\t\tAND locale = ${locale}\n\t\t\t\t`.execute(this.db)\n\t\t\t: await sql<{ slug: string }>`\n\t\t\t\t\tSELECT slug FROM ${sql.ref(tableName)}\n\t\t\t\t\tWHERE slug = ${baseSlug} OR slug LIKE ${pattern}\n\t\t\t\t`.execute(this.db);\n\n\t\t// Find the highest numeric suffix in use\n\t\tlet maxSuffix = 0;\n\t\tconst suffixPattern = new RegExp(`^${escapeRegExp(baseSlug)}-(\\\\d+)$`);\n\t\tfor (const row of candidates.rows) {\n\t\t\tconst match = suffixPattern.exec(row.slug);\n\t\t\tif (match) {\n\t\t\t\tconst n = parseInt(match[1], 10);\n\t\t\t\tif (n > maxSuffix) maxSuffix = n;\n\t\t\t}\n\t\t}\n\n\t\treturn `${baseSlug}-${maxSuffix + 1}`;\n\t}\n\n\t/**\n\t * Duplicate a content item\n\t * Creates a new draft copy with \"(Copy)\" appended to the title.\n\t * A slug is auto-generated from the new title by the handler layer.\n\t */\n\tasync duplicate(type: string, id: string, authorId?: string): Promise<ContentItem> {\n\t\t// Fetch the original item\n\t\tconst original = await this.findById(type, id);\n\t\tif (!original) {\n\t\t\tthrow new EmDashValidationError(\"Content item not found\");\n\t\t}\n\n\t\t// Prepare the new data\n\t\tconst newData = { ...original.data };\n\n\t\t// Append \"(Copy)\" to title if present\n\t\tif (typeof newData.title === \"string\") {\n\t\t\tnewData.title = `${newData.title} (Copy)`;\n\t\t} else if (typeof newData.name === \"string\") {\n\t\t\tnewData.name = `${newData.name} (Copy)`;\n\t\t}\n\n\t\t// Auto-generate a unique slug from the new title/name\n\t\tconst slugSource =\n\t\t\ttypeof newData.title === \"string\"\n\t\t\t\t? newData.title\n\t\t\t\t: typeof newData.name === \"string\"\n\t\t\t\t\t? newData.name\n\t\t\t\t\t: null;\n\n\t\tconst slug = slugSource\n\t\t\t? await this.generateUniqueSlug(type, slugSource, original.locale ?? undefined)\n\t\t\t: null;\n\n\t\t// Create the duplicate as a draft — use override authorId if provided (caller owns the copy)\n\t\treturn this.create({\n\t\t\ttype,\n\t\t\tslug,\n\t\t\tdata: newData,\n\t\t\tstatus: \"draft\",\n\t\t\tauthorId: authorId || original.authorId || undefined,\n\t\t});\n\t}\n\n\t/**\n\t * Find content by ID\n\t */\n\tasync findById(type: string, id: string): Promise<ContentItem | null> {\n\t\tconst tableName = getTableName(type);\n\n\t\tconst result = await sql<Record<string, unknown>>`\n\t\t\tSELECT * FROM ${sql.ref(tableName)}\n\t\t\tWHERE id = ${id}\n\t\t\tAND deleted_at IS NULL\n\t\t`.execute(this.db);\n\n\t\tconst row = result.rows[0];\n\t\tif (!row) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn this.mapRow(type, row);\n\t}\n\n\t/**\n\t * Find content by id, including trashed (soft-deleted) items.\n\t * Used by restore endpoint for ownership checks.\n\t */\n\tasync findByIdIncludingTrashed(type: string, id: string): Promise<ContentItem | null> {\n\t\tconst tableName = getTableName(type);\n\n\t\tconst result = await sql<Record<string, unknown>>`\n\t\t\tSELECT * FROM ${sql.ref(tableName)}\n\t\t\tWHERE id = ${id}\n\t\t`.execute(this.db);\n\n\t\tconst row = result.rows[0];\n\t\tif (!row) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn this.mapRow(type, row);\n\t}\n\n\t/**\n\t * Find content by ID or slug. Tries ID first if it looks like a ULID,\n\t * otherwise tries slug. Falls back to the other if the first lookup misses.\n\t */\n\tasync findByIdOrSlug(\n\t\ttype: string,\n\t\tidentifier: string,\n\t\tlocale?: string,\n\t): Promise<ContentItem | null> {\n\t\treturn this._findByIdOrSlug(type, identifier, false, locale);\n\t}\n\n\t/**\n\t * Find content by ID or slug, including trashed (soft-deleted) items.\n\t * Used by restore/permanent-delete endpoints.\n\t */\n\tasync findByIdOrSlugIncludingTrashed(\n\t\ttype: string,\n\t\tidentifier: string,\n\t\tlocale?: string,\n\t): Promise<ContentItem | null> {\n\t\treturn this._findByIdOrSlug(type, identifier, true, locale);\n\t}\n\n\tprivate async _findByIdOrSlug(\n\t\ttype: string,\n\t\tidentifier: string,\n\t\tincludeTrashed: boolean,\n\t\tlocale?: string,\n\t): Promise<ContentItem | null> {\n\t\t// ULIDs are 26 uppercase alphanumeric chars\n\t\tconst looksLikeUlid = ULID_PATTERN.test(identifier);\n\n\t\tconst findById = includeTrashed\n\t\t\t? (t: string, id: string) => this.findByIdIncludingTrashed(t, id)\n\t\t\t: (t: string, id: string) => this.findById(t, id);\n\t\tconst findBySlug = includeTrashed\n\t\t\t? (t: string, s: string) => this.findBySlugIncludingTrashed(t, s, locale)\n\t\t\t: (t: string, s: string) => this.findBySlug(t, s, locale);\n\n\t\tif (looksLikeUlid) {\n\t\t\t// Try ID first, fall back to slug\n\t\t\tconst byId = await findById(type, identifier);\n\t\t\tif (byId) return byId;\n\t\t\treturn findBySlug(type, identifier);\n\t\t}\n\t\t// Try slug first, fall back to ID\n\t\tconst bySlug = await findBySlug(type, identifier);\n\t\tif (bySlug) return bySlug;\n\t\treturn findById(type, identifier);\n\t}\n\n\t/**\n\t * Find content by slug\n\t */\n\tasync findBySlug(type: string, slug: string, locale?: string): Promise<ContentItem | null> {\n\t\tconst tableName = getTableName(type);\n\n\t\tconst result = locale\n\t\t\t? await sql<Record<string, unknown>>`\n\t\t\t\t\tSELECT * FROM ${sql.ref(tableName)}\n\t\t\t\t\tWHERE slug = ${slug}\n\t\t\t\t\tAND locale = ${locale}\n\t\t\t\t\tAND deleted_at IS NULL\n\t\t\t\t`.execute(this.db)\n\t\t\t: await sql<Record<string, unknown>>`\n\t\t\t\t\tSELECT * FROM ${sql.ref(tableName)}\n\t\t\t\t\tWHERE slug = ${slug}\n\t\t\t\t\tAND deleted_at IS NULL\n\t\t\t\t\tORDER BY locale ASC\n\t\t\t\t\tLIMIT 1\n\t\t\t\t`.execute(this.db);\n\n\t\tconst row = result.rows[0];\n\t\tif (!row) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn this.mapRow(type, row);\n\t}\n\n\t/**\n\t * Find content by slug, including trashed (soft-deleted) items.\n\t * Used by restore/permanent-delete endpoints.\n\t */\n\tasync findBySlugIncludingTrashed(\n\t\ttype: string,\n\t\tslug: string,\n\t\tlocale?: string,\n\t): Promise<ContentItem | null> {\n\t\tconst tableName = getTableName(type);\n\n\t\tconst result = locale\n\t\t\t? await sql<Record<string, unknown>>`\n\t\t\t\t\tSELECT * FROM ${sql.ref(tableName)}\n\t\t\t\t\tWHERE slug = ${slug}\n\t\t\t\t\tAND locale = ${locale}\n\t\t\t\t`.execute(this.db)\n\t\t\t: await sql<Record<string, unknown>>`\n\t\t\t\t\tSELECT * FROM ${sql.ref(tableName)}\n\t\t\t\t\tWHERE slug = ${slug}\n\t\t\t\t\tORDER BY locale ASC\n\t\t\t\t\tLIMIT 1\n\t\t\t\t`.execute(this.db);\n\n\t\tconst row = result.rows[0];\n\t\tif (!row) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn this.mapRow(type, row);\n\t}\n\n\t/**\n\t * Find many content items with filtering and pagination\n\t */\n\tasync findMany(\n\t\ttype: string,\n\t\toptions: FindManyOptions = {},\n\t): Promise<FindManyResult<ContentItem>> {\n\t\tconst tableName = getTableName(type);\n\t\tconst limit = Math.min(options.limit || 50, 100);\n\n\t\t// Determine ordering\n\t\tconst orderField = options.orderBy?.field || \"createdAt\";\n\t\tconst orderDirection = options.orderBy?.direction || \"desc\";\n\t\tconst dbField = this.mapOrderField(orderField);\n\n\t\t// Validate order direction to prevent injection\n\t\tconst safeOrderDirection = orderDirection.toLowerCase() === \"asc\" ? \"ASC\" : \"DESC\";\n\n\t\t// Build query with parameterized values (no string interpolation)\n\t\t// Note: Dynamic content tables have deleted_at column, cast needed for Kysely\n\t\tlet query = this.db\n\t\t\t.selectFrom(tableName as keyof Database)\n\t\t\t.selectAll()\n\t\t\t.where(\"deleted_at\" as never, \"is\", null);\n\n\t\t// Apply filters with parameterized queries\n\t\tif (options.where?.status) {\n\t\t\tquery = query.where(\"status\", \"=\", options.where.status);\n\t\t}\n\n\t\tif (options.where?.authorId) {\n\t\t\tquery = query.where(\"author_id\", \"=\", options.where.authorId);\n\t\t}\n\n\t\tif (options.where?.locale) {\n\t\t\tquery = query.where(\"locale\" as any, \"=\", options.where.locale);\n\t\t}\n\n\t\t// Handle cursor pagination — decodeCursor throws InvalidCursorError\n\t\t// on malformed input; let it propagate so handlers surface a\n\t\t// structured INVALID_CURSOR rather than silently returning page 1.\n\t\tif (options.cursor) {\n\t\t\tconst { orderValue, id: cursorId } = decodeCursor(options.cursor);\n\n\t\t\tif (safeOrderDirection === \"DESC\") {\n\t\t\t\tquery = query.where((eb) =>\n\t\t\t\t\teb.or([\n\t\t\t\t\t\teb(dbField as any, \"<\", orderValue),\n\t\t\t\t\t\teb.and([eb(dbField as any, \"=\", orderValue), eb(\"id\", \"<\", cursorId)]),\n\t\t\t\t\t]),\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tquery = query.where((eb) =>\n\t\t\t\t\teb.or([\n\t\t\t\t\t\teb(dbField as any, \">\", orderValue),\n\t\t\t\t\t\teb.and([eb(dbField as any, \"=\", orderValue), eb(\"id\", \">\", cursorId)]),\n\t\t\t\t\t]),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Apply ordering and limit\n\t\tquery = query\n\t\t\t.orderBy(dbField as any, safeOrderDirection === \"ASC\" ? \"asc\" : \"desc\")\n\t\t\t.orderBy(\"id\", safeOrderDirection === \"ASC\" ? \"asc\" : \"desc\")\n\t\t\t.limit(limit + 1);\n\n\t\tconst rows = await query.execute();\n\t\tconst hasMore = rows.length > limit;\n\t\tconst items = rows.slice(0, limit);\n\n\t\tconst mappedResult: FindManyResult<ContentItem> = {\n\t\t\titems: items.map((row) => this.mapRow(type, row as Record<string, unknown>)),\n\t\t};\n\n\t\tif (hasMore && items.length > 0) {\n\t\t\tconst lastRow = items.at(-1) as Record<string, unknown>;\n\t\t\tconst lastOrderValue = lastRow[dbField];\n\t\t\tconst orderStr =\n\t\t\t\ttypeof lastOrderValue === \"string\" || typeof lastOrderValue === \"number\"\n\t\t\t\t\t? String(lastOrderValue)\n\t\t\t\t\t: \"\";\n\t\t\tmappedResult.nextCursor = encodeCursor(orderStr, String(lastRow.id));\n\t\t}\n\n\t\treturn mappedResult;\n\t}\n\n\t/**\n\t * Update content\n\t */\n\tasync update(type: string, id: string, input: UpdateContentInput): Promise<ContentItem> {\n\t\tconst tableName = getTableName(type);\n\t\tconst now = new Date().toISOString();\n\n\t\t// Build update object with parameterized values\n\t\tconst updates: Record<string, unknown> = {\n\t\t\tupdated_at: now,\n\t\t\tversion: sql`version + 1`,\n\t\t};\n\n\t\tif (input.status !== undefined) {\n\t\t\tupdates.status = input.status;\n\t\t}\n\n\t\tif (input.slug !== undefined) {\n\t\t\tupdates.slug = input.slug;\n\t\t}\n\n\t\tif (input.publishedAt !== undefined) {\n\t\t\tupdates.published_at = input.publishedAt;\n\t\t}\n\n\t\tif (input.scheduledAt !== undefined) {\n\t\t\tupdates.scheduled_at = input.scheduledAt;\n\t\t}\n\n\t\tif (input.authorId !== undefined) {\n\t\t\tupdates.author_id = input.authorId;\n\t\t}\n\n\t\tif (input.primaryBylineId !== undefined) {\n\t\t\tupdates.primary_byline_id = input.primaryBylineId;\n\t\t}\n\n\t\t// Update data fields (skip system columns to prevent injection via data)\n\t\tif (input.data !== undefined && typeof input.data === \"object\") {\n\t\t\tfor (const [key, value] of Object.entries(input.data)) {\n\t\t\t\tif (!SYSTEM_COLUMNS.has(key)) {\n\t\t\t\t\tvalidateIdentifier(key, \"content field name\");\n\t\t\t\t\tupdates[key] = serializeValue(value);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tawait this.db\n\t\t\t.updateTable(tableName as keyof Database)\n\t\t\t.set(updates)\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.where(\"deleted_at\" as never, \"is\", null)\n\t\t\t.execute();\n\n\t\tconst updated = await this.findById(type, id);\n\t\tif (!updated) {\n\t\t\tthrow new Error(\"Content not found\");\n\t\t}\n\n\t\treturn updated;\n\t}\n\n\t/**\n\t * Delete content (soft delete - moves to trash)\n\t */\n\tasync delete(type: string, id: string): Promise<boolean> {\n\t\tconst tableName = getTableName(type);\n\t\tconst now = new Date().toISOString();\n\n\t\tconst result = await sql`\n\t\t\tUPDATE ${sql.ref(tableName)}\n\t\t\tSET deleted_at = ${now}\n\t\t\tWHERE id = ${id}\n\t\t\tAND deleted_at IS NULL\n\t\t`.execute(this.db);\n\n\t\treturn (result.numAffectedRows ?? 0n) > 0n;\n\t}\n\n\t/**\n\t * Restore content from trash\n\t */\n\tasync restore(type: string, id: string): Promise<boolean> {\n\t\tconst tableName = getTableName(type);\n\n\t\tconst result = await sql`\n\t\t\tUPDATE ${sql.ref(tableName)}\n\t\t\tSET deleted_at = NULL\n\t\t\tWHERE id = ${id}\n\t\t\tAND deleted_at IS NOT NULL\n\t\t`.execute(this.db);\n\n\t\treturn (result.numAffectedRows ?? 0n) > 0n;\n\t}\n\n\t/**\n\t * Permanently delete content (cannot be undone)\n\t */\n\tasync permanentDelete(type: string, id: string): Promise<boolean> {\n\t\tconst tableName = getTableName(type);\n\n\t\tconst result = await sql`\n\t\t\tDELETE FROM ${sql.ref(tableName)}\n\t\t\tWHERE id = ${id}\n\t\t`.execute(this.db);\n\n\t\treturn (result.numAffectedRows ?? 0n) > 0n;\n\t}\n\n\t/**\n\t * Find trashed content items\n\t */\n\tasync findTrashed(\n\t\ttype: string,\n\t\toptions: Omit<FindManyOptions, \"where\"> = {},\n\t): Promise<FindManyResult<ContentItem & { deletedAt: string }>> {\n\t\tconst tableName = getTableName(type);\n\t\tconst limit = Math.min(options.limit || 50, 100);\n\n\t\t// Determine ordering - default to most recently deleted\n\t\tconst orderField = options.orderBy?.field || \"deletedAt\";\n\t\tconst orderDirection = options.orderBy?.direction || \"desc\";\n\t\tconst dbField = this.mapOrderField(orderField);\n\n\t\tconst safeOrderDirection = orderDirection.toLowerCase() === \"asc\" ? \"ASC\" : \"DESC\";\n\n\t\tlet query = this.db\n\t\t\t.selectFrom(tableName as keyof Database)\n\t\t\t.selectAll()\n\t\t\t.where(\"deleted_at\" as never, \"is not\", null);\n\n\t\t// Handle cursor pagination — decodeCursor throws on invalid input.\n\t\tif (options.cursor) {\n\t\t\tconst { orderValue, id: cursorId } = decodeCursor(options.cursor);\n\n\t\t\tif (safeOrderDirection === \"DESC\") {\n\t\t\t\tquery = query.where((eb) =>\n\t\t\t\t\teb.or([\n\t\t\t\t\t\teb(dbField as any, \"<\", orderValue),\n\t\t\t\t\t\teb.and([eb(dbField as any, \"=\", orderValue), eb(\"id\", \"<\", cursorId)]),\n\t\t\t\t\t]),\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tquery = query.where((eb) =>\n\t\t\t\t\teb.or([\n\t\t\t\t\t\teb(dbField as any, \">\", orderValue),\n\t\t\t\t\t\teb.and([eb(dbField as any, \"=\", orderValue), eb(\"id\", \">\", cursorId)]),\n\t\t\t\t\t]),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tquery = query\n\t\t\t.orderBy(dbField as any, safeOrderDirection === \"ASC\" ? \"asc\" : \"desc\")\n\t\t\t.orderBy(\"id\", safeOrderDirection === \"ASC\" ? \"asc\" : \"desc\")\n\t\t\t.limit(limit + 1);\n\n\t\tconst rows = await query.execute();\n\t\tconst hasMore = rows.length > limit;\n\t\tconst items = rows.slice(0, limit);\n\n\t\tconst mappedResult: FindManyResult<ContentItem & { deletedAt: string }> = {\n\t\t\titems: items.map((row) => {\n\t\t\t\tconst record = row as Record<string, unknown>;\n\t\t\t\treturn {\n\t\t\t\t\t...this.mapRow(type, record),\n\t\t\t\t\tdeletedAt: typeof record.deleted_at === \"string\" ? record.deleted_at : \"\",\n\t\t\t\t};\n\t\t\t}),\n\t\t};\n\n\t\tif (hasMore && items.length > 0) {\n\t\t\tconst lastRow = items.at(-1) as Record<string, unknown>;\n\t\t\tconst lastOrderValue = lastRow[dbField];\n\t\t\tconst orderStr =\n\t\t\t\ttypeof lastOrderValue === \"string\" || typeof lastOrderValue === \"number\"\n\t\t\t\t\t? String(lastOrderValue)\n\t\t\t\t\t: \"\";\n\t\t\tmappedResult.nextCursor = encodeCursor(orderStr, String(lastRow.id));\n\t\t}\n\n\t\treturn mappedResult;\n\t}\n\n\t/**\n\t * Count trashed content items\n\t */\n\tasync countTrashed(type: string): Promise<number> {\n\t\tconst tableName = getTableName(type);\n\n\t\tconst result = await this.db\n\t\t\t.selectFrom(tableName as keyof Database)\n\t\t\t.select((eb) => eb.fn.count(\"id\").as(\"count\"))\n\t\t\t.where(\"deleted_at\" as never, \"is not\", null)\n\t\t\t.executeTakeFirst();\n\n\t\treturn Number(result?.count || 0);\n\t}\n\n\t/**\n\t * Count content items\n\t */\n\tasync count(\n\t\ttype: string,\n\t\twhere?: { status?: string; authorId?: string; locale?: string },\n\t): Promise<number> {\n\t\tconst tableName = getTableName(type);\n\n\t\tlet query = this.db\n\t\t\t.selectFrom(tableName as keyof Database)\n\t\t\t.select((eb) => eb.fn.count(\"id\").as(\"count\"))\n\t\t\t.where(\"deleted_at\" as never, \"is\", null);\n\n\t\tif (where?.status) {\n\t\t\tquery = query.where(\"status\", \"=\", where.status);\n\t\t}\n\n\t\tif (where?.authorId) {\n\t\t\tquery = query.where(\"author_id\", \"=\", where.authorId);\n\t\t}\n\n\t\tif (where?.locale) {\n\t\t\tquery = query.where(\"locale\" as any, \"=\", where.locale);\n\t\t}\n\n\t\tconst result = await query.executeTakeFirst();\n\t\treturn Number(result?.count || 0);\n\t}\n\n\t// get overall statistics (total, published, draft) for a content type in a single query\n\tasync getStats(type: string): Promise<{ total: number; published: number; draft: number }> {\n\t\tconst tableName = getTableName(type);\n\n\t\tconst result = await this.db\n\t\t\t.selectFrom(tableName as keyof Database)\n\t\t\t.select((eb) => [\n\t\t\t\teb.fn.count(\"id\").as(\"total\"),\n\t\t\t\teb.fn.sum(eb.case().when(\"status\", \"=\", \"published\").then(1).else(0).end()).as(\"published\"),\n\t\t\t\teb.fn.sum(eb.case().when(\"status\", \"=\", \"draft\").then(1).else(0).end()).as(\"draft\"),\n\t\t\t])\n\t\t\t.where(\"deleted_at\" as never, \"is\", null)\n\t\t\t.executeTakeFirst();\n\n\t\treturn {\n\t\t\ttotal: Number(result?.total || 0),\n\t\t\tpublished: Number(result?.published || 0),\n\t\t\tdraft: Number(result?.draft || 0),\n\t\t};\n\t}\n\n\t/**\n\t * Schedule content for future publishing\n\t *\n\t * Sets status to 'scheduled' and stores the scheduled publish time.\n\t * The content will be auto-published when the scheduled time is reached.\n\t */\n\tasync schedule(type: string, id: string, scheduledAt: string): Promise<ContentItem> {\n\t\tconst tableName = getTableName(type);\n\t\tconst now = new Date().toISOString();\n\n\t\t// Validate scheduledAt is in the future\n\t\tconst scheduledDate = new Date(scheduledAt);\n\t\tif (isNaN(scheduledDate.getTime())) {\n\t\t\tthrow new EmDashValidationError(\"Invalid scheduled date\");\n\t\t}\n\t\tif (scheduledDate <= new Date()) {\n\t\t\tthrow new EmDashValidationError(\"Scheduled date must be in the future\");\n\t\t}\n\n\t\tconst existing = await this.findById(type, id);\n\t\tif (!existing) {\n\t\t\tthrow new EmDashValidationError(\"Content item not found\");\n\t\t}\n\n\t\t// Published posts keep their status — the schedule applies to the\n\t\t// pending draft, not the currently-live revision. Unpublished posts\n\t\t// transition to 'scheduled' so they aren't visible before the time.\n\t\tconst newStatus = existing.status === \"published\" ? \"published\" : \"scheduled\";\n\n\t\tawait sql`\n\t\t\tUPDATE ${sql.ref(tableName)}\n\t\t\tSET status = ${newStatus},\n\t\t\t\tscheduled_at = ${scheduledAt},\n\t\t\t\tupdated_at = ${now}\n\t\t\tWHERE id = ${id}\n\t\t\tAND deleted_at IS NULL\n\t\t`.execute(this.db);\n\n\t\tconst updated = await this.findById(type, id);\n\t\tif (!updated) {\n\t\t\tthrow new Error(\"Content not found\");\n\t\t}\n\n\t\treturn updated;\n\t}\n\n\t/**\n\t * Unschedule content\n\t *\n\t * Clears the scheduled time. Published posts stay published;\n\t * draft/scheduled posts revert to 'draft'.\n\t */\n\tasync unschedule(type: string, id: string): Promise<ContentItem> {\n\t\tconst tableName = getTableName(type);\n\t\tconst now = new Date().toISOString();\n\n\t\tconst existing = await this.findById(type, id);\n\t\tif (!existing) {\n\t\t\tthrow new EmDashValidationError(\"Content item not found\");\n\t\t}\n\n\t\t// Published posts keep their status — just clear the pending schedule.\n\t\t// Draft/scheduled posts revert to 'draft'.\n\t\tconst newStatus = existing.status === \"published\" ? \"published\" : \"draft\";\n\n\t\tawait sql`\n\t\t\tUPDATE ${sql.ref(tableName)}\n\t\t\tSET status = ${newStatus},\n\t\t\t\tscheduled_at = NULL,\n\t\t\t\tupdated_at = ${now}\n\t\t\tWHERE id = ${id}\n\t\t\tAND scheduled_at IS NOT NULL\n\t\t\tAND deleted_at IS NULL\n\t\t`.execute(this.db);\n\n\t\tconst updated = await this.findById(type, id);\n\t\tif (!updated) {\n\t\t\tthrow new Error(\"Content not found\");\n\t\t}\n\n\t\treturn updated;\n\t}\n\n\t/**\n\t * Find content that is ready to be published\n\t *\n\t * Returns all content where scheduled_at <= now, regardless of status.\n\t * This covers both draft-scheduled posts (status='scheduled') and\n\t * published posts with scheduled draft changes (status='published').\n\t */\n\tasync findReadyToPublish(type: string): Promise<ContentItem[]> {\n\t\tconst tableName = getTableName(type);\n\t\tconst now = new Date().toISOString();\n\n\t\tconst result = await sql<Record<string, unknown>>`\n\t\t\tSELECT * FROM ${sql.ref(tableName)}\n\t\t\tWHERE scheduled_at IS NOT NULL\n\t\t\tAND scheduled_at <= ${now}\n\t\t\tAND deleted_at IS NULL\n\t\t\tORDER BY scheduled_at ASC\n\t\t`.execute(this.db);\n\n\t\treturn result.rows.map((row) => this.mapRow(type, row));\n\t}\n\n\t/**\n\t * Find all translations in a translation group\n\t */\n\tasync findTranslations(type: string, translationGroup: string): Promise<ContentItem[]> {\n\t\tconst tableName = getTableName(type);\n\n\t\tconst result = await sql<Record<string, unknown>>`\n\t\t\tSELECT * FROM ${sql.ref(tableName)}\n\t\t\tWHERE translation_group = ${translationGroup}\n\t\t\tAND deleted_at IS NULL\n\t\t\tORDER BY locale ASC\n\t\t`.execute(this.db);\n\n\t\treturn result.rows.map((row) => this.mapRow(type, row));\n\t}\n\n\t/**\n\t * Publish the current draft\n\t *\n\t * Promotes draft_revision_id to live_revision_id and clears draft pointer.\n\t * Syncs the draft revision's data into the content table columns so the\n\t * content table always reflects the published version.\n\t * If no draft revision exists, creates one from current data and publishes it.\n\t */\n\tasync publish(type: string, id: string): Promise<ContentItem> {\n\t\tconst tableName = getTableName(type);\n\t\tconst now = new Date().toISOString();\n\n\t\tconst existing = await this.findById(type, id);\n\t\tif (!existing) {\n\t\t\tthrow new EmDashValidationError(\"Content item not found\");\n\t\t}\n\n\t\tconst revisionRepo = new RevisionRepository(this.db);\n\t\tlet revisionToPublish = existing.draftRevisionId || existing.liveRevisionId;\n\n\t\tif (!revisionToPublish) {\n\t\t\t// No revision exists - create one from current data\n\t\t\tconst revision = await revisionRepo.create({\n\t\t\t\tcollection: type,\n\t\t\t\tentryId: id,\n\t\t\t\tdata: existing.data,\n\t\t\t});\n\t\t\trevisionToPublish = revision.id;\n\t\t}\n\n\t\t// Sync the revision's data into the content table columns\n\t\t// so the content table always holds the published version\n\t\tconst revision = await revisionRepo.findById(revisionToPublish);\n\t\tif (revision) {\n\t\t\tawait this.syncDataColumns(type, id, revision.data);\n\n\t\t\t// Sync slug from revision if stored there\n\t\t\tif (typeof revision.data._slug === \"string\") {\n\t\t\t\tawait sql`\n\t\t\t\t\tUPDATE ${sql.ref(tableName)}\n\t\t\t\t\tSET slug = ${revision.data._slug}\n\t\t\t\t\tWHERE id = ${id}\n\t\t\t\t`.execute(this.db);\n\t\t\t}\n\t\t}\n\n\t\tawait sql`\n\t\t\tUPDATE ${sql.ref(tableName)}\n\t\t\tSET live_revision_id = ${revisionToPublish},\n\t\t\t\tdraft_revision_id = NULL,\n\t\t\t\tstatus = 'published',\n\t\t\t\tscheduled_at = NULL,\n\t\t\t\tpublished_at = COALESCE(published_at, ${now}),\n\t\t\t\tupdated_at = ${now}\n\t\t\tWHERE id = ${id}\n\t\t\tAND deleted_at IS NULL\n\t\t`.execute(this.db);\n\n\t\tconst updated = await this.findById(type, id);\n\t\tif (!updated) {\n\t\t\tthrow new Error(\"Content not found\");\n\t\t}\n\n\t\treturn updated;\n\t}\n\n\t/**\n\t * Unpublish content\n\t *\n\t * Removes live pointer but preserves draft. If no draft exists,\n\t * creates one from the live version so the content isn't lost.\n\t */\n\tasync unpublish(type: string, id: string): Promise<ContentItem> {\n\t\tconst tableName = getTableName(type);\n\t\tconst now = new Date().toISOString();\n\n\t\tconst existing = await this.findById(type, id);\n\t\tif (!existing) {\n\t\t\tthrow new EmDashValidationError(\"Content item not found\");\n\t\t}\n\n\t\t// If no draft exists, create one from the live version\n\t\tif (!existing.draftRevisionId && existing.liveRevisionId) {\n\t\t\tconst revisionRepo = new RevisionRepository(this.db);\n\t\t\tconst liveRevision = await revisionRepo.findById(existing.liveRevisionId);\n\t\t\tif (liveRevision) {\n\t\t\t\tconst draft = await revisionRepo.create({\n\t\t\t\t\tcollection: type,\n\t\t\t\t\tentryId: id,\n\t\t\t\t\tdata: liveRevision.data,\n\t\t\t\t});\n\n\t\t\t\tawait sql`\n\t\t\t\t\tUPDATE ${sql.ref(tableName)}\n\t\t\t\t\tSET draft_revision_id = ${draft.id}\n\t\t\t\t\tWHERE id = ${id}\n\t\t\t\t`.execute(this.db);\n\t\t\t}\n\t\t}\n\n\t\tawait sql`\n\t\t\tUPDATE ${sql.ref(tableName)}\n\t\t\tSET live_revision_id = NULL,\n\t\t\t\tstatus = 'draft',\n\t\t\t\tpublished_at = NULL,\n\t\t\t\tupdated_at = ${now}\n\t\t\tWHERE id = ${id}\n\t\t\tAND deleted_at IS NULL\n\t\t`.execute(this.db);\n\n\t\tconst updated = await this.findById(type, id);\n\t\tif (!updated) {\n\t\t\tthrow new Error(\"Content not found\");\n\t\t}\n\n\t\treturn updated;\n\t}\n\n\t/**\n\t * Set the draft revision pointer for a content item.\n\t *\n\t * Used by seed/import paths that stage a new revision's data before\n\t * promoting it to live via `publish()`.\n\t *\n\t * Validates that the content item exists and is not soft-deleted, that\n\t * the revision exists, and that the revision belongs to the same\n\t * collection and entry. Without these checks, a caller could leave the\n\t * content row pointing at a missing or unrelated revision.\n\t */\n\tasync setDraftRevision(type: string, id: string, revisionId: string): Promise<void> {\n\t\tconst tableName = getTableName(type);\n\t\tconst now = new Date().toISOString();\n\n\t\tconst existing = await this.findById(type, id);\n\t\tif (!existing) {\n\t\t\tthrow new EmDashValidationError(\"Content item not found\");\n\t\t}\n\n\t\tconst revisionRepo = new RevisionRepository(this.db);\n\t\tconst revision = await revisionRepo.findById(revisionId);\n\t\tif (!revision) {\n\t\t\tthrow new EmDashValidationError(\"Revision not found\");\n\t\t}\n\n\t\tif (revision.collection !== type || revision.entryId !== id) {\n\t\t\tthrow new EmDashValidationError(\"Revision does not belong to the specified content item\");\n\t\t}\n\n\t\tawait sql`\n\t\t\tUPDATE ${sql.ref(tableName)}\n\t\t\tSET draft_revision_id = ${revisionId},\n\t\t\t\tupdated_at = ${now}\n\t\t\tWHERE id = ${id}\n\t\t\tAND deleted_at IS NULL\n\t\t`.execute(this.db);\n\t}\n\n\t/**\n\t * Discard pending draft changes\n\t *\n\t * Clears draft_revision_id. The content table columns already hold the\n\t * published version, so no data sync is needed.\n\t */\n\tasync discardDraft(type: string, id: string): Promise<ContentItem> {\n\t\tconst tableName = getTableName(type);\n\t\tconst now = new Date().toISOString();\n\n\t\tconst existing = await this.findById(type, id);\n\t\tif (!existing) {\n\t\t\tthrow new EmDashValidationError(\"Content item not found\");\n\t\t}\n\n\t\tif (!existing.draftRevisionId) {\n\t\t\t// No draft to discard\n\t\t\treturn existing;\n\t\t}\n\n\t\tawait sql`\n\t\t\tUPDATE ${sql.ref(tableName)}\n\t\t\tSET draft_revision_id = NULL,\n\t\t\t\tupdated_at = ${now}\n\t\t\tWHERE id = ${id}\n\t\t\tAND deleted_at IS NULL\n\t\t`.execute(this.db);\n\n\t\tconst updated = await this.findById(type, id);\n\t\tif (!updated) {\n\t\t\tthrow new Error(\"Content not found\");\n\t\t}\n\n\t\treturn updated;\n\t}\n\n\t/**\n\t * Sync data columns in the content table from a data object.\n\t * Used to promote revision data into the content table on publish.\n\t * Keys starting with _ are revision metadata (e.g. _slug) and are skipped.\n\t */\n\tprivate async syncDataColumns(\n\t\ttype: string,\n\t\tid: string,\n\t\tdata: Record<string, unknown>,\n\t): Promise<void> {\n\t\tconst tableName = getTableName(type);\n\t\tconst updates: Record<string, unknown> = {};\n\n\t\tfor (const [key, value] of Object.entries(data)) {\n\t\t\tif (SYSTEM_COLUMNS.has(key)) continue;\n\t\t\tif (key.startsWith(\"_\")) continue; // revision metadata\n\t\t\tvalidateIdentifier(key, \"content field name\");\n\t\t\tupdates[key] = serializeValue(value);\n\t\t}\n\n\t\tif (Object.keys(updates).length === 0) return;\n\n\t\tawait this.db\n\t\t\t.updateTable(tableName as keyof Database)\n\t\t\t.set(updates)\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.execute();\n\t}\n\n\t/**\n\t * Count content items with a pending schedule.\n\t * Includes both draft-scheduled (status='scheduled') and published\n\t * posts with scheduled draft changes (status='published', scheduled_at set).\n\t */\n\tasync countScheduled(type: string): Promise<number> {\n\t\tconst tableName = getTableName(type);\n\n\t\tconst result = await sql<{ count: number }>`\n\t\t\tSELECT COUNT(id) as count FROM ${sql.ref(tableName)}\n\t\t\tWHERE scheduled_at IS NOT NULL\n\t\t\tAND deleted_at IS NULL\n\t\t`.execute(this.db);\n\n\t\treturn Number(result.rows[0]?.count || 0);\n\t}\n\n\t/**\n\t * Map database row to ContentItem\n\t * Extracts system columns and puts content fields in data\n\t * Excludes null values from data to match input semantics\n\t */\n\tprivate mapRow(type: string, row: Record<string, unknown>): ContentItem {\n\t\tconst data: Record<string, unknown> = {};\n\n\t\tfor (const [key, value] of Object.entries(row)) {\n\t\t\tif (!SYSTEM_COLUMNS.has(key) && value !== null) {\n\t\t\t\tdata[key] = deserializeValue(value);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tid: row.id as string,\n\t\t\ttype,\n\t\t\tslug: row.slug as string | null,\n\t\t\tstatus: row.status as string,\n\t\t\tdata,\n\t\t\tauthorId: row.author_id as string | null,\n\t\t\tprimaryBylineId: (row.primary_byline_id as string | null) ?? null,\n\t\t\tcreatedAt: row.created_at as string,\n\t\t\tupdatedAt: row.updated_at as string,\n\t\t\tpublishedAt: row.published_at as string | null,\n\t\t\tscheduledAt: row.scheduled_at as string | null,\n\t\t\tliveRevisionId: (row.live_revision_id as string | null) ?? null,\n\t\t\tdraftRevisionId: (row.draft_revision_id as string | null) ?? null,\n\t\t\tversion: typeof row.version === \"number\" ? row.version : 1,\n\t\t\tlocale: (row.locale as string) ?? null,\n\t\t\ttranslationGroup: (row.translation_group as string) ?? null,\n\t\t};\n\t}\n\n\t/**\n\t * Map order field names to database columns.\n\t * Only allows known fields to prevent column enumeration via crafted orderBy values.\n\t */\n\tprivate mapOrderField(field: string): string {\n\t\tconst mapping: Record<string, string> = {\n\t\t\tcreatedAt: \"created_at\",\n\t\t\tupdatedAt: \"updated_at\",\n\t\t\tpublishedAt: \"published_at\",\n\t\t\tscheduledAt: \"scheduled_at\",\n\t\t\tdeletedAt: \"deleted_at\",\n\t\t\ttitle: \"title\",\n\t\t\tname: \"name\",\n\t\t\tslug: \"slug\",\n\t\t\tstatus: \"status\",\n\t\t\tlocale: \"locale\",\n\t\t};\n\n\t\tconst mapped = mapping[field];\n\t\tif (!mapped) {\n\t\t\tthrow new EmDashValidationError(`Invalid order field: ${field}`);\n\t\t}\n\t\treturn mapped;\n\t}\n}\n"],"mappings":"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"}
|
package/dist/db/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import "../types-
|
|
2
|
-
import { i as runMigrations, n as getMigrationStatus, r as rollbackMigration, t as MigrationStatus } from "../runner-
|
|
3
|
-
import { a as SqliteConfig, c as sqlite, i as PostgresConfig, n as DatabaseDialectType, o as libsql, r as LibsqlConfig, s as postgres, t as DatabaseDescriptor } from "../adapters-
|
|
1
|
+
import "../types-CS8FIX7L.mjs";
|
|
2
|
+
import { i as runMigrations, n as getMigrationStatus, r as rollbackMigration, t as MigrationStatus } from "../runner-OURCaApa.mjs";
|
|
3
|
+
import { a as SqliteConfig, c as sqlite, i as PostgresConfig, n as DatabaseDialectType, o as libsql, r as LibsqlConfig, s as postgres, t as DatabaseDescriptor } from "../adapters-BKSf3T9R.mjs";
|
|
4
4
|
export { type DatabaseDescriptor, type DatabaseDialectType, type LibsqlConfig, type MigrationStatus, type PostgresConfig, type SqliteConfig, getMigrationStatus, libsql, postgres, rollbackMigration, runMigrations, sqlite };
|
package/dist/db/libsql.d.mts
CHANGED
package/dist/db/postgres.d.mts
CHANGED
package/dist/db/sqlite.d.mts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db-errors-
|
|
1
|
+
{"version":3,"file":"db-errors-l1Qh2RPR.mjs","names":[],"sources":["../src/utils/db-errors.ts"],"sourcesContent":["/**\n * Shared detection helpers for database-layer error messages.\n *\n * Different SQL dialects phrase \"table or relation does not exist\" differently:\n *\n * - SQLite / D1: \"no such table: foo\"\n * - PostgreSQL: 'relation \"foo\" does not exist'\n * 'table \"foo\" does not exist'\n * - MySQL (future): \"Table 'db.foo' doesn't exist\"\n *\n * Runtime code paths that short-circuit on missing tables (pre-migration\n * probes, optional feature tables, etc.) should use these helpers rather\n * than hand-rolling string matches per call-site.\n */\n\n/**\n * Extract a lowercase error message from any unknown value, safely.\n */\nfunction messageOf(error: unknown): string {\n\tif (error instanceof Error) return error.message.toLowerCase();\n\tif (typeof error === \"string\") return error.toLowerCase();\n\treturn \"\";\n}\n\n/**\n * Returns true when `error` is a \"table does not exist\" error across the\n * dialects EmDash supports (D1/SQLite and PostgreSQL). Used by runtime\n * probes to treat pre-migration databases as empty without logging a scary\n * warning, while still propagating unrelated errors (permissions, connection\n * loss, syntax issues) to callers.\n */\nexport function isMissingTableError(error: unknown): boolean {\n\tconst message = messageOf(error);\n\tif (!message) return false;\n\n\t// SQLite / D1\n\tif (message.includes(\"no such table\")) return true;\n\n\t// PostgreSQL (and some MySQL variants): \"relation ... does not exist\" /\n\t// \"table ... does not exist\" / \"doesn't exist\".\n\tif (message.includes(\"does not exist\") || message.includes(\"doesn't exist\")) {\n\t\treturn message.includes(\"relation\") || message.includes(\"table\");\n\t}\n\n\treturn false;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,SAAS,UAAU,OAAwB;AAC1C,KAAI,iBAAiB,MAAO,QAAO,MAAM,QAAQ,aAAa;AAC9D,KAAI,OAAO,UAAU,SAAU,QAAO,MAAM,aAAa;AACzD,QAAO;;;;;;;;;AAUR,SAAgB,oBAAoB,OAAyB;CAC5D,MAAM,UAAU,UAAU,MAAM;AAChC,KAAI,CAAC,QAAS,QAAO;AAGrB,KAAI,QAAQ,SAAS,gBAAgB,CAAE,QAAO;AAI9C,KAAI,QAAQ,SAAS,iBAAiB,IAAI,QAAQ,SAAS,gBAAgB,CAC1E,QAAO,QAAQ,SAAS,WAAW,IAAI,QAAQ,SAAS,QAAQ;AAGjE,QAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"default-
|
|
1
|
+
{"version":3,"file":"default-DCVqE5ib.mjs","names":[],"sources":["../src/seed/default.ts"],"sourcesContent":["/**\n * Default seed applied when no user seed file exists.\n *\n * Provides the baseline schema every EmDash site needs:\n * posts, pages, categories, and tags.\n */\n\nimport type { SeedFile } from \"./types.js\";\n\nexport const defaultSeed: SeedFile = {\n\tversion: \"1\",\n\tmeta: {\n\t\tname: \"Default\",\n\t\tdescription: \"Posts and pages with categories and tags\",\n\t},\n\tcollections: [\n\t\t{\n\t\t\tslug: \"posts\",\n\t\t\tlabel: \"Posts\",\n\t\t\tlabelSingular: \"Post\",\n\t\t\tsupports: [\"drafts\", \"revisions\", \"search\"],\n\t\t\tfields: [\n\t\t\t\t{\n\t\t\t\t\tslug: \"title\",\n\t\t\t\t\tlabel: \"Title\",\n\t\t\t\t\ttype: \"string\",\n\t\t\t\t\trequired: true,\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"featured_image\",\n\t\t\t\t\tlabel: \"Featured Image\",\n\t\t\t\t\ttype: \"image\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"content\",\n\t\t\t\t\tlabel: \"Content\",\n\t\t\t\t\ttype: \"portableText\",\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"excerpt\",\n\t\t\t\t\tlabel: \"Excerpt\",\n\t\t\t\t\ttype: \"text\",\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\t{\n\t\t\tslug: \"pages\",\n\t\t\tlabel: \"Pages\",\n\t\t\tlabelSingular: \"Page\",\n\t\t\tsupports: [\"drafts\", \"revisions\", \"search\"],\n\t\t\tfields: [\n\t\t\t\t{\n\t\t\t\t\tslug: \"title\",\n\t\t\t\t\tlabel: \"Title\",\n\t\t\t\t\ttype: \"string\",\n\t\t\t\t\trequired: true,\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"content\",\n\t\t\t\t\tlabel: \"Content\",\n\t\t\t\t\ttype: \"portableText\",\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t],\n\ttaxonomies: [\n\t\t{\n\t\t\tname: \"category\",\n\t\t\tlabel: \"Categories\",\n\t\t\tlabelSingular: \"Category\",\n\t\t\thierarchical: true,\n\t\t\tcollections: [\"posts\"],\n\t\t},\n\t\t{\n\t\t\tname: \"tag\",\n\t\t\tlabel: \"Tags\",\n\t\t\tlabelSingular: \"Tag\",\n\t\t\thierarchical: false,\n\t\t\tcollections: [\"posts\"],\n\t\t},\n\t],\n};\n"],"mappings":";AASA,MAAa,cAAwB;CACpC,SAAS;CACT,MAAM;EACL,MAAM;EACN,aAAa;EACb;CACD,aAAa,CACZ;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,UAAU;GAAC;GAAU;GAAa;GAAS;EAC3C,QAAQ;GACP;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN,UAAU;IACV,YAAY;IACZ;GACD;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN;GACD;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN,YAAY;IACZ;GACD;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN;GACD;EACD,EACD;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,UAAU;GAAC;GAAU;GAAa;GAAS;EAC3C,QAAQ,CACP;GACC,MAAM;GACN,OAAO;GACP,MAAM;GACN,UAAU;GACV,YAAY;GACZ,EACD;GACC,MAAM;GACN,OAAO;GACP,MAAM;GACN,YAAY;GACZ,CACD;EACD,CACD;CACD,YAAY,CACX;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,cAAc;EACd,aAAa,CAAC,QAAQ;EACtB,EACD;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,cAAc;EACd,aAAa,CAAC,QAAQ;EACtB,CACD;CACD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-zG5T1UGA.mjs","names":[],"sources":["../src/api/error.ts"],"sourcesContent":["/**\n * Standardized API error responses.\n *\n * All API routes should use these utilities instead of inline\n * `new Response(JSON.stringify({ error: ... }), ...)` patterns.\n */\n\nimport { InvalidCursorError } from \"../database/repositories/types.js\";\nimport { mapErrorStatus } from \"./errors.js\";\nimport type { ApiResult } from \"./types.js\";\n\n// Re-export everything from errors.ts so existing `import { mapErrorStatus } from \"./error.js\"` still works\nexport * from \"./errors.js\";\n\n/**\n * Standard cache headers for all API responses.\n *\n * Cache-Control: private, no-store -- prevents CDN/proxy caching of authenticated data.\n * no-store already tells caches not to store the response, so Vary is unnecessary.\n */\nconst API_CACHE_HEADERS: HeadersInit = {\n\t\"Cache-Control\": \"private, no-store\",\n};\n\n/**\n * Create a standardized error response.\n *\n * Always returns `{ error: { code, message } }` with correct Content-Type.\n * Use this for all error responses in API routes.\n */\nexport function apiError(code: string, message: string, status: number): Response {\n\treturn Response.json({ error: { code, message } }, { status, headers: API_CACHE_HEADERS });\n}\n\n/**\n * Create a standardized success response.\n *\n * Always returns `{ data: T }` with correct status code.\n * Use this for all success responses in API routes.\n */\nexport function apiSuccess<T>(data: T, status = 200): Response {\n\treturn Response.json({ data }, { status, headers: API_CACHE_HEADERS });\n}\n\n/**\n * Handle an unknown error in a catch block.\n *\n * - Logs the full error server-side\n * - Returns a generic message to the client (never leaks error.message)\n * - Use `fallbackMessage` for the public-facing message\n * - Use `fallbackCode` for the error code\n */\nexport function handleError(\n\terror: unknown,\n\tfallbackMessage: string,\n\tfallbackCode: string,\n): Response {\n\t// Bubble malformed-cursor errors as a structured 400 instead of a\n\t// generic 500.\n\tif (error instanceof InvalidCursorError) {\n\t\treturn apiError(\"INVALID_CURSOR\", error.message, 400);\n\t}\n\tconsole.error(`[${fallbackCode}]`, error);\n\treturn apiError(fallbackCode, fallbackMessage, 500);\n}\n\n/**\n * Standard initialization check.\n *\n * Returns an error response if EmDash is not initialized, or null if OK.\n * Usage: `const err = requireInit(emdash); if (err) return err;`\n */\nexport function requireInit(emdash: unknown): Response | null {\n\tif (!emdash || typeof emdash !== \"object\") {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\treturn null;\n}\n\n/**\n * Standard database check.\n *\n * Returns an error response if the database is not available, or null if OK.\n * Usage: `const err = requireDb(emdash?.db); if (err) return err;`\n */\nexport function requireDb(db: unknown): Response | null {\n\tif (!db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\treturn null;\n}\n\n/**\n * Convert an ApiResult into an HTTP Response.\n *\n * Collapses the handler-to-response boilerplate:\n * - Success: returns `apiSuccess(result.data, successStatus)`\n * - Error: returns `apiError(code, message, mapErrorStatus(code))`\n */\nexport function unwrapResult<T>(result: ApiResult<T>, successStatus = 200): Response {\n\tif (!result.success) {\n\t\treturn apiError(result.error.code, result.error.message, mapErrorStatus(result.error.code));\n\t}\n\treturn apiSuccess(result.data, successStatus);\n}\n"],"mappings":";;;;;;;AAoBA,MAAM,oBAAiC,EACtC,iBAAiB,qBACjB;;;;;;;AAQD,SAAgB,SAAS,MAAc,SAAiB,QAA0B;AACjF,QAAO,SAAS,KAAK,EAAE,OAAO;EAAE;EAAM;EAAS,EAAE,EAAE;EAAE;EAAQ,SAAS;EAAmB,CAAC"}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { _ as MediaValue, m as MediaProviderDescriptor } from "./placeholder-
|
|
2
|
-
import { t as Database } from "./types-
|
|
3
|
-
import { a as ContentSeoInput, c as FindManyOptions, l as FindManyResult, o as CreateContentInput, r as ContentItem, t as BylineSummary
|
|
4
|
-
import { A as PageMetadataContribution, B as PluginDefinition, D as PageFragmentContribution, H as PluginManifest, J as RequestMeta, O as PageFragmentEvent, W as PluginStorageConfig, X as ResolvedPlugin, a as CommentAfterModerateEvent, g as EmailMessage, h as CronEvent, j as PageMetadataEvent, r as CommentAfterCreateEvent, s as CommentBeforeCreateEvent, tt as StandardPluginDefinition, w as MediaItem$1 } from "./types-
|
|
5
|
-
import { a as Collection, b as UpdateFieldInput, c as CollectionWithFields, d as CreateFieldInput, i as SiteSettings, p as Field, r as SiteSettingKey, u as CreateCollectionInput, y as UpdateCollectionInput } from "./types-
|
|
6
|
-
import { d as Storage } from "./types-
|
|
7
|
-
import { t as DatabaseDescriptor } from "./adapters-
|
|
1
|
+
import { _ as MediaValue, m as MediaProviderDescriptor } from "./placeholder-D29tWZ7o.mjs";
|
|
2
|
+
import { t as Database } from "./types-CS8FIX7L.mjs";
|
|
3
|
+
import { a as ContentSeoInput, c as FindManyOptions, d as UpdateContentInput, l as FindManyResult, o as CreateContentInput, r as ContentItem, t as BylineSummary } from "./types-BrA0xf5I.mjs";
|
|
4
|
+
import { A as PageMetadataContribution, B as PluginDefinition, D as PageFragmentContribution, H as PluginManifest, J as RequestMeta, O as PageFragmentEvent, W as PluginStorageConfig, X as ResolvedPlugin, a as CommentAfterModerateEvent, g as EmailMessage, h as CronEvent, j as PageMetadataEvent, r as CommentAfterCreateEvent, s as CommentBeforeCreateEvent, tt as StandardPluginDefinition, w as MediaItem$1 } from "./types-i36XcA_X.mjs";
|
|
5
|
+
import { a as Collection, b as UpdateFieldInput, c as CollectionWithFields, d as CreateFieldInput, i as SiteSettings, p as Field, r as SiteSettingKey, u as CreateCollectionInput, y as UpdateCollectionInput } from "./types-BmPPSUEx.mjs";
|
|
6
|
+
import { d as Storage } from "./types-DIMwPFub.mjs";
|
|
7
|
+
import { t as DatabaseDescriptor } from "./adapters-BKSf3T9R.mjs";
|
|
8
8
|
import { Kysely } from "kysely";
|
|
9
9
|
import { z } from "astro/zod";
|
|
10
10
|
import { z as z$1 } from "zod";
|
|
11
11
|
import { PortableTextBlock } from "@emdash-cms/gutenberg-to-portable-text";
|
|
12
12
|
import { Readable } from "node:stream";
|
|
13
13
|
import { LiveLoader } from "astro/loaders";
|
|
14
|
+
import * as react from "react";
|
|
14
15
|
|
|
15
16
|
//#region src/database/connection.d.ts
|
|
16
17
|
interface DatabaseConfig {
|
|
@@ -2042,7 +2043,13 @@ declare function definePlugin(definition: StandardPluginDefinition): StandardPlu
|
|
|
2042
2043
|
* Auth Provider Types
|
|
2043
2044
|
*
|
|
2044
2045
|
* Defines the interfaces for pluggable authentication providers.
|
|
2045
|
-
*
|
|
2046
|
+
*
|
|
2047
|
+
* Two systems coexist:
|
|
2048
|
+
* - `AuthDescriptor` — transparent auth (Cloudflare Access) that authenticates
|
|
2049
|
+
* every request via headers/cookies. No login UI needed.
|
|
2050
|
+
* - `AuthProviderDescriptor` — pluggable login methods (GitHub, Google,
|
|
2051
|
+
* AT Protocol, etc.) that appear as options on the login page and setup
|
|
2052
|
+
* wizard. Passkey is built-in; providers are additive.
|
|
2046
2053
|
*/
|
|
2047
2054
|
/**
|
|
2048
2055
|
* Result of authenticating a request via an external auth provider
|
|
@@ -2060,10 +2067,10 @@ interface AuthResult {
|
|
|
2060
2067
|
metadata?: Record<string, unknown>;
|
|
2061
2068
|
}
|
|
2062
2069
|
/**
|
|
2063
|
-
* Auth descriptor
|
|
2070
|
+
* Auth descriptor — transparent auth providers (e.g., Cloudflare Access).
|
|
2064
2071
|
*
|
|
2065
|
-
*
|
|
2066
|
-
*
|
|
2072
|
+
* These authenticate every request via headers/cookies. No login UI needed.
|
|
2073
|
+
* The module's `authenticate()` function is called by middleware on each request.
|
|
2067
2074
|
*/
|
|
2068
2075
|
interface AuthDescriptor {
|
|
2069
2076
|
/**
|
|
@@ -2098,6 +2105,97 @@ interface AuthProviderModule {
|
|
|
2098
2105
|
*/
|
|
2099
2106
|
authenticate(request: Request, config: unknown): Promise<AuthResult>;
|
|
2100
2107
|
}
|
|
2108
|
+
/**
|
|
2109
|
+
* Descriptor for a pluggable auth provider.
|
|
2110
|
+
*
|
|
2111
|
+
* Auth providers appear as login options on the login page and setup wizard.
|
|
2112
|
+
* They coexist with passkey (which is built-in) and with each other.
|
|
2113
|
+
* Any provider can be used to create the initial admin account.
|
|
2114
|
+
*
|
|
2115
|
+
* @example
|
|
2116
|
+
* ```ts
|
|
2117
|
+
* // astro.config.ts
|
|
2118
|
+
* import { atproto } from "@emdash-cms/auth-atproto";
|
|
2119
|
+
*
|
|
2120
|
+
* emdash({
|
|
2121
|
+
* authProviders: [atproto(), github(), google()],
|
|
2122
|
+
* })
|
|
2123
|
+
* ```
|
|
2124
|
+
*/
|
|
2125
|
+
interface AuthProviderDescriptor {
|
|
2126
|
+
/** Unique provider ID (e.g., "github", "atproto") */
|
|
2127
|
+
id: string;
|
|
2128
|
+
/** Human-readable label for UI (e.g., "GitHub", "AT Protocol") */
|
|
2129
|
+
label: string;
|
|
2130
|
+
/** Provider-specific config (JSON-serializable) */
|
|
2131
|
+
config?: unknown;
|
|
2132
|
+
/**
|
|
2133
|
+
* Module exporting React components for the admin UI.
|
|
2134
|
+
* Statically imported at build time via virtual module.
|
|
2135
|
+
*
|
|
2136
|
+
* The module should export components matching `AuthProviderAdminExports`.
|
|
2137
|
+
*/
|
|
2138
|
+
adminEntry?: string;
|
|
2139
|
+
/**
|
|
2140
|
+
* Astro route handlers this provider needs injected at build time.
|
|
2141
|
+
* Used for login initiation, OAuth callbacks, well-known endpoints, etc.
|
|
2142
|
+
*/
|
|
2143
|
+
routes?: AuthRouteDescriptor[];
|
|
2144
|
+
/**
|
|
2145
|
+
* URL prefixes/paths that should bypass auth middleware.
|
|
2146
|
+
* Added to the public routes set so login/callback endpoints work
|
|
2147
|
+
* for unauthenticated users.
|
|
2148
|
+
*/
|
|
2149
|
+
publicRoutes?: string[];
|
|
2150
|
+
/**
|
|
2151
|
+
* Storage collections for persistent auth state (e.g., OAuth sessions).
|
|
2152
|
+
* Same format as plugin storage — collections are stored in the shared
|
|
2153
|
+
* `_plugin_storage` table namespaced under `auth:<providerId>`.
|
|
2154
|
+
*
|
|
2155
|
+
* Access via `getAuthProviderStorage()` from `emdash/api/route-utils`.
|
|
2156
|
+
*/
|
|
2157
|
+
storage?: Record<string, {
|
|
2158
|
+
indexes?: Array<string | string[]>;
|
|
2159
|
+
uniqueIndexes?: Array<string | string[]>;
|
|
2160
|
+
}>;
|
|
2161
|
+
}
|
|
2162
|
+
/**
|
|
2163
|
+
* A route that an auth provider needs injected into the Astro app.
|
|
2164
|
+
*/
|
|
2165
|
+
interface AuthRouteDescriptor {
|
|
2166
|
+
/** URL pattern (e.g., "/_emdash/api/auth/atproto/login") */
|
|
2167
|
+
pattern: string;
|
|
2168
|
+
/** Module specifier for the Astro route handler */
|
|
2169
|
+
entrypoint: string;
|
|
2170
|
+
}
|
|
2171
|
+
/**
|
|
2172
|
+
* Expected exports from an auth provider's `adminEntry` module.
|
|
2173
|
+
*
|
|
2174
|
+
* All exports are optional. Providers export whichever components
|
|
2175
|
+
* make sense for their auth flow.
|
|
2176
|
+
*/
|
|
2177
|
+
interface AuthProviderAdminExports {
|
|
2178
|
+
/**
|
|
2179
|
+
* Compact button for the login page (icon + label).
|
|
2180
|
+
* Used for providers with a simple redirect flow (GitHub, Google).
|
|
2181
|
+
* Rendered in the "Or continue with" section.
|
|
2182
|
+
*/
|
|
2183
|
+
LoginButton?: react.ComponentType;
|
|
2184
|
+
/**
|
|
2185
|
+
* Full login form for providers that need custom input.
|
|
2186
|
+
* Used for providers like AT Protocol that need a handle field.
|
|
2187
|
+
* Rendered as an expandable section on the login page.
|
|
2188
|
+
*/
|
|
2189
|
+
LoginForm?: react.ComponentType;
|
|
2190
|
+
/**
|
|
2191
|
+
* Setup wizard step for creating the admin account via this provider.
|
|
2192
|
+
* When present, this provider appears as an option in the setup wizard's
|
|
2193
|
+
* "Create admin account" step.
|
|
2194
|
+
*/
|
|
2195
|
+
SetupStep?: react.ComponentType<{
|
|
2196
|
+
onComplete: () => void;
|
|
2197
|
+
}>;
|
|
2198
|
+
}
|
|
2101
2199
|
/**
|
|
2102
2200
|
* Configuration options common to external auth providers
|
|
2103
2201
|
*/
|
|
@@ -2375,6 +2473,23 @@ interface EmDashConfig {
|
|
|
2375
2473
|
* ```
|
|
2376
2474
|
*/
|
|
2377
2475
|
auth?: AuthDescriptor;
|
|
2476
|
+
/**
|
|
2477
|
+
* Pluggable auth providers (login methods on the login page).
|
|
2478
|
+
*
|
|
2479
|
+
* Auth providers appear as options alongside passkey on the login page
|
|
2480
|
+
* and setup wizard. Any provider can be used to create the initial
|
|
2481
|
+
* admin account. Passkey is built-in; providers listed here are additive.
|
|
2482
|
+
*
|
|
2483
|
+
* @example
|
|
2484
|
+
* ```ts
|
|
2485
|
+
* import { atproto } from "@emdash-cms/auth-atproto";
|
|
2486
|
+
*
|
|
2487
|
+
* emdash({
|
|
2488
|
+
* authProviders: [atproto()],
|
|
2489
|
+
* })
|
|
2490
|
+
* ```
|
|
2491
|
+
*/
|
|
2492
|
+
authProviders?: AuthProviderDescriptor[];
|
|
2378
2493
|
/**
|
|
2379
2494
|
* MCP (Model Context Protocol) server endpoint.
|
|
2380
2495
|
*
|
|
@@ -2704,6 +2819,18 @@ declare const pluginManifestSchema: z$1.ZodObject<{
|
|
|
2704
2819
|
type: z$1.ZodLiteral<"secret">;
|
|
2705
2820
|
label: z$1.ZodString;
|
|
2706
2821
|
description: z$1.ZodOptional<z$1.ZodString>;
|
|
2822
|
+
}, z$1.core.$strip>, z$1.ZodObject<{
|
|
2823
|
+
type: z$1.ZodLiteral<"url">;
|
|
2824
|
+
default: z$1.ZodOptional<z$1.ZodString>;
|
|
2825
|
+
placeholder: z$1.ZodOptional<z$1.ZodString>;
|
|
2826
|
+
label: z$1.ZodString;
|
|
2827
|
+
description: z$1.ZodOptional<z$1.ZodString>;
|
|
2828
|
+
}, z$1.core.$strip>, z$1.ZodObject<{
|
|
2829
|
+
type: z$1.ZodLiteral<"email">;
|
|
2830
|
+
default: z$1.ZodOptional<z$1.ZodString>;
|
|
2831
|
+
placeholder: z$1.ZodOptional<z$1.ZodString>;
|
|
2832
|
+
label: z$1.ZodString;
|
|
2833
|
+
description: z$1.ZodOptional<z$1.ZodString>;
|
|
2707
2834
|
}, z$1.core.$strip>], "type">>>;
|
|
2708
2835
|
pages: z$1.ZodOptional<z$1.ZodArray<z$1.ZodObject<{
|
|
2709
2836
|
path: z$1.ZodString;
|
|
@@ -3010,6 +3137,15 @@ declare class HookPipeline {
|
|
|
3010
3137
|
getExclusiveHookProviders(hookName: string): Array<{
|
|
3011
3138
|
pluginId: string;
|
|
3012
3139
|
}>;
|
|
3140
|
+
/**
|
|
3141
|
+
* Get all plugins that registered a non-exclusive handler for a given
|
|
3142
|
+
* hook (e.g. `email:beforeSend`, `email:afterSend`), preserving priority
|
|
3143
|
+
* order. Partitions with `getExclusiveHookProviders()`, which returns
|
|
3144
|
+
* plugins whose registration is marked `exclusive: true`.
|
|
3145
|
+
*/
|
|
3146
|
+
getHookProviders(hookName: string): Array<{
|
|
3147
|
+
pluginId: string;
|
|
3148
|
+
}>;
|
|
3013
3149
|
/**
|
|
3014
3150
|
* Invoke an exclusive hook — dispatch only to the selected provider.
|
|
3015
3151
|
* Returns null if no provider is selected or if the selected hook
|
|
@@ -4547,7 +4683,14 @@ interface SearchResult {
|
|
|
4547
4683
|
locale: string;
|
|
4548
4684
|
/** Entry title (if available) */
|
|
4549
4685
|
title?: string;
|
|
4550
|
-
/**
|
|
4686
|
+
/**
|
|
4687
|
+
* Highlighted snippet showing match context.
|
|
4688
|
+
*
|
|
4689
|
+
* Sanitized server-side to be safe for `set:html` / `innerHTML`:
|
|
4690
|
+
* all HTML metacharacters in the source text are escaped, and
|
|
4691
|
+
* matched terms are wrapped in literal `<mark>...</mark>` tags
|
|
4692
|
+
* (the only HTML the snippet is allowed to contain).
|
|
4693
|
+
*/
|
|
4551
4694
|
snippet?: string;
|
|
4552
4695
|
/** Relevance score (higher = more relevant) */
|
|
4553
4696
|
score: number;
|
|
@@ -4808,5 +4951,5 @@ declare function extractPlainText(blocks: PortableTextBlock$1[] | string | null
|
|
|
4808
4951
|
*/
|
|
4809
4952
|
declare function extractSearchableFields(entry: Record<string, unknown>, fields: string[]): Record<string, string>;
|
|
4810
4953
|
//#endregion
|
|
4811
|
-
export { UpdateMenuInput as $,
|
|
4812
|
-
//# sourceMappingURL=index-
|
|
4954
|
+
export { UpdateMenuInput as $, EntryResult as $n, handleMediaList as $r, SuggestedAction as $t, getEntriesByTerm as A, ListResponse as Ai, WxrData as An, ProseMirrorNode as Ar, probeUrl as At, TaxonomyTerm as B, CreateMediaInput as Bi, getDb as Bn, CreateSectionInput as Br, ImportContext as Bt, ReorderWidgetsInput as C, handleContentUnpublish as Ci, AuthResult as Cn, PortableTextLinkMark as Cr, parseWxrDate as Ct, WidgetComponentDef as D, ContentListResponse as Di, WxrAttachment as Dn, PortableTextUnknownBlock as Dr, getFileSources as Dt, WidgetArea as E, ApiContext as Ei, definePlugin as En, PortableTextTextBlock as Er, getAllSources as Et, getTerm as F, FieldDefinition as Fi, parseWxrString as Fn, SandboxRunnerFactory as Fr, FetchOptions as Ft, getMenu as G, EmDashDatabaseError as Gi, WaitUntilFn as Gn, getCollectionInfo as Gr, OAuthInput as Gt, UpdateTermInput as H, MediaRepository as Hi, getFallbackChain as Hn, Section as Hr, ImportResult as Ht, getTermsForEntries as I, FieldUIHints as Ii, CollectionFilter as In, SandboxedPlugin as Ir, FieldCompatibility as It, CreateMenuItemInput as J, CollectionFilter$1 as Jn, MediaListResponse as Jr, ProbeResult as Jt, getMenus as K, after as Kn, SchemaError as Kr, PostTypeAnalysis as Kt, invalidateTermCache as L, FileValue as Li, EntryData as Ln, SerializedRequest as Lr, FileInput as Lt, getTaxonomyDef as M, portableText as Mi, WxrSite as Mn, SandboxEmailSendCallback as Mr, importReusableBlocksAsSections as Mt, getTaxonomyDefs as N, reference as Ni, WxrTag as Nn, SandboxOptions as Nr, AttachmentInfo as Nt, WidgetType as O, ContentResponse as Oi, WxrAuthor as On, ProseMirrorDocument as Or, getSource as Ot, getTaxonomyTerms as P, image as Pi, parseWxr as Pn, SandboxRunner as Pr, CollectionSchemaStatus as Pt, ReorderMenuItemsInput as Q, EmDashCollections as Qn, handleMediaGet as Qr, SourceProbeResult as Qt, CreateTermInput as R, ImageValue as Ri, EntryFilter as Rn, getSection as Rr, ImportAnalysis as Rt, PropDef as S, handleContentTranslations as Si, AuthProviderModule as Sn, PortableTextImageBlock as Sr, wordpressRestSource as St, Widget as T, handleContentUpdate as Ti, ExternalAuthConfig as Tn, PortableTextSpan as Tr, clearSources as Tt, getByline as U, ContentRepository as Ui, getI18nConfig as Un, SectionSource as Ur, ImportSource as Ut, TaxonomyTermRow as V, MediaItem as Vi, I18nConfig as Vn, GetSectionsOptions as Vr, ImportFieldDef as Vt, getBylineBySlug as W, DatabaseConfig as Wi, isI18nEnabled as Wn, UpdateSectionInput as Wr, NormalizedItem as Wt, MenuItem as X, ContentEntry as Xn, handleMediaCreate as Xr, SourceCapabilities as Xt, Menu as Y, CollectionResult as Yn, MediaResponse as Yr, SourceAuth as Yt, MenuItemType as Z, EditFieldMeta as Zn, handleMediaDelete as Zr, SourceInput as Zt, getWidgetArea as _, handleContentListTrashed as _i, S3StorageConfig as _n, computeContentHash as _r, VerifyPreviewTokenOptions as _t, search as a, handleRevisionRestore as ai, createPluginManager as an, getEmDashCollection as ar, getPluginSetting as at, CreateWidgetAreaInput as b, handleContentRestore as bi, AuthProviderAdminExports as bn, prosemirrorToPortableText as br, parseContentId as bt, FTSManager as c, handleContentCountScheduled as ci, HookPipeline as cn, resolveEmDashPath as cr, getSiteSettings as ct, SearchOptions as d, handleContentDelete as di, ValidatedPluginManifest as dn, FieldAnnotation as dr, isPreviewRequest as dt, handleMediaUpdate as ei, UrlInput as en, InferCollectionData as er, UpdateMenuItemInput as et, SearchResponse as f, handleContentDiscardDraft as fi, pluginManifestSchema as fn, createEditable as fr, GetPreviewUrlOptions as ft, Suggestion as g, handleContentList as gi, LocalStorageConfig as gn, sanitizeHref as gr, PreviewTokenPayload as gt, SuggestOptions as h, handleContentGetIncludingTrashed as hi, getStoredConfig as hn, isSafeHref as hr, GeneratePreviewTokenOptions as ht, getSuggestions as i, handleRevisionList as ii, PluginManager as in, getEditMeta as ir, getComments as it, getEntryTerms as j, ManifestResponse as ji, WxrPost as jn, SandboxEmailMessage as jr, registerSource as jt, getAllTermsForEntries as k, FieldDescriptor as ki, WxrCategory as kn, ProseMirrorMark as kr, getUrlSources as kt, CollectionSearchOptions as l, handleContentCountTrashed as li, HookResult as ln, CMSAnnotation as lr, setSiteSettings as lt, SearchStats as m, handleContentGet as mi, PluginDescriptor as mn, decodeSlug as mr, getPreviewUrl as mt, extractSearchableFields as n, RevisionResponse as ni, SandboxNotAvailableError as nn, TranslationSummary as nr, GetCommentsResult as nt, searchCollection as o, generateManifest as oi, PluginRouteError as on, getEmDashEntry as or, getPluginSettings as ot, SearchResult as p, handleContentDuplicate as pi, EmDashConfig as pn, createNoop as pr, buildPreviewUrl as pt, CreateMenuInput as q, CacheHint as qn, SchemaRegistry as qr, PostTypeMapping as qt, getSearchStats as r, handleRevisionGet as ri, createNoopSandboxRunner as rn, TranslationsResult as rr, getCommentCount as rt, searchWithDb as s, handleContentCompare as si, EmailPipeline as sn, getTranslations as sr, getSiteSetting as st, extractPlainText as t, RevisionListResponse as ti, NoopSandboxRunner as tn, ResolvePathResult as tr, GetCommentsOptions as tt, SearchConfig as u, handleContentCreate as ui, createHookPipeline as un, EditProxy as ur, getPreviewToken as ut, getWidgetAreas as v, handleContentPermanentDelete as vi, StorageDescriptor as vn, hashString as vr, VerifyPreviewTokenResult as vt, UpdateWidgetInput as w, handleContentUnschedule as wi, AuthRouteDescriptor as wn, PortableTextMarkDef as wr, wxrSource as wt, CreateWidgetInput as x, handleContentSchedule as xi, AuthProviderDescriptor as xn, PortableTextCodeBlock as xr, verifyPreviewToken as xt, getWidgetComponents as y, handleContentPublish as yi, AuthDescriptor as yn, portableTextToProsemirror as yr, generatePreviewToken as yt, TaxonomyDef as z, PortableTextBlock$2 as zi, emdashLoader as zn, getSections as zr, ImportConfig as zt };
|
|
4955
|
+
//# sourceMappingURL=index-DIb-CzNx.d.mts.map
|