@elytracms/next 0.0.88 → 0.0.90

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/index.d.ts CHANGED
@@ -6,15 +6,16 @@ export { ComponentImplementations, RenderAsset } from '@elytracms/core/runtime-r
6
6
  import { HostComponentEntry } from '@elytracms/core/studio-core';
7
7
  import { ResolvePageResult, ResolvedAsset, Perspective, ContentClient, CacheTags, ContentDocumentSource, ResolvedDocument } from '@elytracms/core/content';
8
8
  export { Perspective } from '@elytracms/core/content';
9
- import { LocaleConfig, Locale, RouteRecord, CollectionDef, RedirectRecord } from '@elytracms/core/cms-core';
10
- export { CmsDocument, CollectionDef, FieldDef, Locale, LocaleConfig, LocaleRequest, LocaleStrategy, LocaleUrlPath, RedirectRecord, RouteRecord, documentSchema, fallbackHomeRoutes, localeConfigSchema, localeUrlPath, mergeRouteRecords, redirectRecordSchema, resolveLocale, routeRecordSchema, splitLocalePath, textDirection } from '@elytracms/core/cms-core';
9
+ import { LocaleConfig, Locale, RouteRecord, CollectionDef, RedirectRecord, LocaleConfigInput } from '@elytracms/core/cms-core';
10
+ export { CmsDocument, CollectionDef, FieldDef, Locale, LocaleConfig, LocaleConfigInput, LocaleRequest, LocaleStrategy, LocaleUrlPath, RedirectRecord, RouteRecord, documentSchema, fallbackHomeRoutes, localeConfigSchema, localeUrlPath, mergeRouteRecords, parseLocaleConfig, redirectRecordSchema, resolveLocale, routeRecordSchema, splitLocalePath, textDirection } from '@elytracms/core/cms-core';
11
11
  import { AssetRecord, PersistenceAdapter } from '@elytracms/core/persistence';
12
12
  export { AssetRecord } from '@elytracms/core/persistence';
13
13
  import { ResolvingMetadata, Metadata, ResolvedMetadata, MetadataRoute } from 'next';
14
- import { z } from 'zod';
15
- export { z } from 'zod';
14
+ import { RevalidationEvent } from '@elytracms/core/revalidation';
15
+ export { REVALIDATE_SIGNATURE_HEADER, RevalidationEvent, RevalidationEventKind, revalidationEventSchema, signRevalidationBody, signedRevalidationRequest } from '@elytracms/core/revalidation';
16
16
  import { ImageLoader } from 'next/image';
17
17
  import { HostImageRenderer } from './host-image.js';
18
+ export { z } from 'zod';
18
19
  export { ComponentNode } from '@elytracms/core/project-graph';
19
20
 
20
21
  /**
@@ -259,9 +260,9 @@ interface ContentSnapshotOptions {
259
260
  * and EC-519 collapsed the two declarations into one; this was the last place
260
261
  * in the chain still inventing the value, and it sits in the package that
261
262
  * resolves the URLs. Hand it the project's `LocaleConfig` — the one the
262
- * config declares.
263
+ * config declares. Parsed here (EC-580): a `default` outside `locales` throws.
263
264
  */
264
- locales: LocaleConfig;
265
+ locales: LocaleConfigInput;
265
266
  }
266
267
  interface ContentSnapshot extends ContentSource {
267
268
  readonly projectId: string;
@@ -275,8 +276,8 @@ interface ContentSnapshot extends ContentSource {
275
276
  */
276
277
  interface StaticContentData {
277
278
  projectId: string;
278
- /** Locale configuration — **required**, never defaulted (see EC-530 above). */
279
- locales: LocaleConfig;
279
+ /** Locale configuration — **required**, never defaulted (see EC-530 above); parsed (EC-580). */
280
+ locales: LocaleConfigInput;
280
281
  /** Route records served by `resolvePage` (EC-019). */
281
282
  routes?: readonly RouteRecord[];
282
283
  /** Redirect records served by `resolvePage` (EC-019/020). */
@@ -443,9 +444,9 @@ declare function resolveCanvasPage(source: ContentSource, url: string, perspecti
443
444
 
444
445
  interface CanvasCacheOptions {
445
446
  /**
446
- * Project scope prefix for every tag (`p:<scope>:…`) — must be the project
447
- * id the studio publishes under, so the webhook dispatcher's tags match.
448
- * Omit only when the emitter also emits unscoped tags (see `cache.ts`).
447
+ * Project scope prefix for every tag (`p:<scope>:…`) — must be the SAME scope
448
+ * the revalidate receiver derives its tags with (`createRevalidateRoute`'s
449
+ * `scope`; the delivery kit hands its one `contentScope` to both, EC-579).
449
450
  */
450
451
  scope?: string;
451
452
  /**
@@ -543,8 +544,14 @@ interface CanvasRoute {
543
544
  * Next passes the parent segment's resolving metadata as the second argument;
544
545
  * the route merges the page's tags over it (EC-610). A caller that invokes this
545
546
  * by hand (the scaffold's `app/page.tsx`) forwards its own `parent`.
547
+ *
548
+ * `parent` is REQUIRED, not optional (EC-614): Next's webpack build validates a
549
+ * page's `generateMetadata` with `SecondArg<…> extends ResolvingMetadata`, and an
550
+ * optional parameter reads as `ResolvingMetadata | undefined`, which fails that
551
+ * constraint — a consumer re-exporting this hook could not `next build --webpack`
552
+ * on 0.0.88 (the Turbopack validator is lenient, which is how the gate missed it).
546
553
  */
547
- generateMetadata: (props: CanvasRouteProps, parent?: ResolvingMetadata) => Promise<Metadata>;
554
+ generateMetadata: (props: CanvasRouteProps, parent: ResolvingMetadata) => Promise<Metadata>;
548
555
  }
549
556
  /** Create the `{ Page, generateMetadata }` pair for `app/[[...slug]]/page.tsx`. */
550
557
  declare function createCanvasRoute(options: CanvasRouteOptions): CanvasRoute;
@@ -688,10 +695,17 @@ declare function createCanvasSitemap(options: CreateCanvasSitemapOptions): () =>
688
695
  * Cache-tag pipeline (EC-146, vision AD-5) — the pure half.
689
696
  *
690
697
  * This module defines (a) the **canonical Next.js tag string format** mapping
691
- * the accessor-level `CacheTags` (EC-143) onto `revalidateTag` keys, and (b)
692
- * the **tag-discovery caching logic** the catch-all route helper runs inside
693
- * `unstable_cache`. No Next.js imports — everything here is unit-testable;
694
- * `route.tsx` injects the real `unstable_cache` via {@link CacheWrapper}.
698
+ * the accessor-level `CacheTags` (EC-143) onto `revalidateTag` keys, (b) the
699
+ * **tag derivation for a revalidation event** ({@link revalidationTags}, EC-579 —
700
+ * the receiver's half of the one emitter), and (c) the **tag-discovery caching
701
+ * logic** the catch-all route helper runs inside `unstable_cache`. No Next.js
702
+ * imports — everything here is unit-testable; `route.tsx` injects the real
703
+ * `unstable_cache` via {@link CacheWrapper}.
704
+ *
705
+ * This is the ONE tag module: the entries are tagged here and the invalidations
706
+ * are derived here, from the same formatters and the same scope. Before EC-579 the
707
+ * format was mirrored twice more (the studio's browser dispatcher and an inline
708
+ * literal in the Convex emitter), and each emitter brought its own scope.
695
709
  *
696
710
  * ## Tag format
697
711
  *
@@ -709,8 +723,8 @@ declare function createCanvasSitemap(options: CreateCanvasSitemapOptions): () =>
709
723
  * (alt/dimensions/re-upload) drops exactly the pages that baked it. Not
710
724
  * locale-scoped — an asset record is locale-agnostic.
711
725
  * - `project` — the whole-project sweep tag attached to **every** cached
712
- * entry; graph publish/unpublish emits it (a layout or route change can
713
- * affect any page, so the honest v1 fallback is a full project sweep).
726
+ * entry; every revalidation event that changes live output invalidates it
727
+ * (see {@link revalidationTags} for why, until EC-515).
714
728
  *
715
729
  * ## Project scope
716
730
  *
@@ -718,16 +732,11 @@ declare function createCanvasSitemap(options: CreateCanvasSitemapOptions): () =>
718
732
  * `p:prj_aurora:doc:post:post-1`). The cost is a few bytes per tag and it
719
733
  * keeps multi-project hosts (one Next app serving several builder projects)
720
734
  * from cross-invalidating, so the prefix is **on whenever the host knows its
721
- * project id** — decision: include it. The emitter (the studio's webhook
722
- * dispatcher, `packages/studio/src/lib/webhooks`) always scopes with the
723
- * operation's `projectId`; a host that omits `cache.scope` therefore will not
724
- * match studio-emitted tags. Configure `cache.scope` with the same project id
725
- * the studio publishes under.
726
- *
727
- * The studio-side emitter cannot depend on this package (it would drag the
728
- * `next` peer dependency into the studio bundle), so
729
- * `packages/studio/src/lib/webhooks/tags.ts` mirrors these formatters; tests on
730
- * both sides pin the exact literal strings to keep them in lock-step.
735
+ * project id** — decision: include it. Since EC-579 the scope has ONE owner: the
736
+ * host. The webhook carries an event, not tags, and the receiver derives the
737
+ * tags with the same scope its cache entries are written under (the delivery
738
+ * kit hands both the one `contentScope`), so emitter and host can no longer
739
+ * disagree about it.
731
740
  */
732
741
  /** Apply the optional multi-project scope prefix to one tag. */
733
742
  declare function scopeCacheTag(tag: string, scope?: string): string;
@@ -741,9 +750,8 @@ declare function collectionCacheTag(collection: string, locale: string, scope?:
741
750
  /** Tag for one resolved route path (or redirect-chain hop) in one locale. */
742
751
  declare function routeCacheTag(pattern: string, locale: string, scope?: string): string;
743
752
  /**
744
- * The whole-project sweep tag. Attached to every cached page entry; emitted
745
- * on graph publish/unpublish (and as the emitter's honest fallback when a
746
- * change's precise tag set is unknowable).
753
+ * The whole-project sweep tag. Attached to every cached page entry; derived for
754
+ * every revalidation event that changes live output ({@link revalidationTags}).
747
755
  */
748
756
  declare function projectSweepTag(scope?: string): string;
749
757
  /**
@@ -752,6 +760,38 @@ declare function projectSweepTag(scope?: string): string;
752
760
  * cache entry — including the project sweep tag.
753
761
  */
754
762
  declare function nextCacheTags(tags: CacheTags, scope?: string): string[];
763
+ /** The receiver's own project — what its cached entries are tagged with. */
764
+ interface RevalidationProject {
765
+ /** The tag scope of this host's cache entries (`p:<scope>:…`), if any. */
766
+ scope?: string | undefined;
767
+ /** Every locale this host serves — one `collection:` tag per locale. */
768
+ locales: readonly string[];
769
+ }
770
+ /**
771
+ * The tags one revalidation event invalidates on this host — sorted, deduplicated,
772
+ * scoped with the receiver's OWN scope, so the emitter never needs to know it.
773
+ *
774
+ * - `publish` / `unpublish` / `delete` → `doc:<collection>:<id>` (its detail page and
775
+ * every cached list that contained it) + `collection:<collection>:<locale>` for
776
+ * EVERY locale this host serves (list membership may have changed) + the sweep;
777
+ * - `redirects` → the sweep (routing can change any URL; see the `redirects` trigger
778
+ * in the studio's `convex/triggers.ts` for why a targeted `route:` tag is wrong);
779
+ * - `asset` → `asset:<id>` + the sweep (a page reading the asset through a document
780
+ * asset FIELD carries no `asset:` tag — EC-566 D16, EC-589);
781
+ * - `probe` → nothing (the doctor's no-op ping).
782
+ *
783
+ * **Why every live-output event also carries the project sweep — a deliberate,
784
+ * measured deviation from the EC-579 ruling's text, which lists the sweep only for
785
+ * redirects and asset removes.** A self-hosted Next host honours ONLY the baseline
786
+ * tags of a cache read (measured in EC-515 on Next 16.2.10 — see
787
+ * {@link loadCachedEntry}): a discovered `doc:` / `collection:` / `asset:` tag
788
+ * invalidates nothing there, silently, and Vercel is unmeasured. Until EC-515's
789
+ * option D makes the precise tags honoured, the precise tags alone would turn
790
+ * "revalidates coarsely" into "never revalidates" on every self-hosted host — so the
791
+ * sweep stays on every event, exactly as the Convex path sent it before. Dropping it
792
+ * from a publish is EC-515's job, together with the read that makes it safe.
793
+ */
794
+ declare function revalidationTags(event: RevalidationEvent, project: RevalidationProject): string[];
755
795
  /**
756
796
  * The slice of `unstable_cache` the discovery logic needs, injectable so the
757
797
  * logic is testable against a faithful in-memory model (and so the test model
@@ -849,11 +889,11 @@ interface CachedEntry<T> {
849
889
  * webhook are all correct — the read simply never consults them.
850
890
  *
851
891
  * Consequence for a self-hosted deployment: invalidation granularity collapses
852
- * to whatever is in `baselineTags`. The studio's Convex write chokepoint emits
853
- * the project sweep on every publish, so instant publish still *works* there,
854
- * coarsely; the browser dispatcher's targeted `doc:`/`collection:` tag set
855
- * (`packages/studio/src/lib/webhooks/tags.ts`) does not reach a self-hosted host
856
- * at all. Vercel-hosted deployments are unaffected in practice — both consumer
892
+ * to whatever is in `baselineTags`. {@link revalidationTags} therefore adds the
893
+ * project sweep to every event that changes live output (EC-579), so instant
894
+ * publish still *works* there, coarsely; its targeted `doc:`/`collection:`/`asset:`
895
+ * tags do not reach a self-hosted host's entries at all. Vercel-hosted deployments
896
+ * are unaffected in practice — both consumer
857
897
  * sites were measured healthy on 2026-08-01 — which is consistent with Vercel's
858
898
  * cache handler purging by the entry's stored tags, though that has **not** been
859
899
  * measured here.
@@ -866,90 +906,80 @@ interface CachedEntry<T> {
866
906
  declare function loadCachedEntry<T>(cache: CacheWrapper, keyParts: string[], baselineTags: readonly string[], load: () => Promise<CachedEntry<T>>): Promise<CachedEntry<T>>;
867
907
 
868
908
  /**
869
- * Pure signature/payload logic of the revalidation webhook receiver (EC-146),
870
- * kept free of Next.js imports so it is unit-testable. `revalidate.ts` wires
871
- * it into a route handler with `revalidateTag`.
872
- *
873
- * Scheme: HMAC-SHA256 over the **raw request body** with a shared secret,
874
- * carried as `x-elytra-signature: sha256=<hex>`. The emitter (the studio's
875
- * webhook dispatcher) signs the exact JSON string it sends; the receiver
876
- * verifies over the raw text *before* parsing, with a timing-safe compare.
909
+ * Pure request logic of the revalidation webhook receiver (EC-146), kept free of
910
+ * Next.js imports so it is unit-testable. `revalidate.ts` wires it into a route
911
+ * handler with `revalidateTag`.
912
+ *
913
+ * The wire — event schema, signer, header — is NOT declared here: it is the one
914
+ * module `@elytracms/core/revalidation`, shared with the emitter (the studio's
915
+ * Convex write chokepoint) and the doctor probe (EC-579). This file only decides
916
+ * the HTTP answer: auth before parsing, then the event.
877
917
  */
878
- /** Header carrying the HMAC signature. */
879
- declare const REVALIDATE_SIGNATURE_HEADER = "x-elytra-signature";
880
- /** The webhook body: which tags to revalidate, and why. */
881
- declare const revalidatePayloadSchema: z.ZodObject<{
882
- projectId: z.ZodString;
883
- event: z.ZodString;
884
- tags: z.ZodArray<z.ZodString>;
885
- timestamp: z.ZodString;
886
- }, z.core.$strip>;
887
- type RevalidatePayload = z.infer<typeof revalidatePayloadSchema>;
888
- /** Compute the signature header value for a raw body (emitter/test side). */
889
- declare function signRevalidateBody(rawBody: string, secret: string): string;
890
918
  type RevalidateEvaluation = {
891
919
  ok: true;
892
- payload: RevalidatePayload;
920
+ event: RevalidationEvent;
893
921
  } | {
894
922
  ok: false;
895
923
  status: 400 | 401;
896
924
  message: string;
897
925
  };
898
926
  /**
899
- * Evaluate one webhook request: verify the HMAC first (auth before parsing),
900
- * then validate the payload shape.
901
- *
902
- * - unconfigured (empty) secret → 401 for every request: the endpoint stays
903
- * closed until a secret is deliberately configured (mirrors EC-144 preview);
904
- * - missing/malformed/mismatched signature → 401 (timing-safe compare);
905
- * - unparseable or schema-invalid body → 400 with no tag revalidated.
927
+ * Evaluate one webhook request: verify the HMAC first (auth before parsing), then
928
+ * read the event.
929
+ *
930
+ * - unconfigured (empty) secret → 401 for every request: the endpoint stays closed
931
+ * until a secret is deliberately configured (mirrors EC-144 preview);
932
+ * - missing/malformed/mismatched signature → 401 (constant-time verify);
933
+ * - unparseable or schema-invalid body → 400, nothing revalidated; a body in the
934
+ * pre-EC-579 tag-list shape gets its own named 400 (an older Elytra emitter —
935
+ * deploy the matching studio functions), because a version skew between the
936
+ * Convex deployment and this host is the one way a correctly signed body can
937
+ * arrive in the wrong shape.
906
938
  */
907
- declare function evaluateRevalidateRequest(rawBody: string, signature: string | null | undefined, secret: string | undefined): RevalidateEvaluation;
939
+ declare function evaluateRevalidateRequest(rawBody: string, signature: string | null | undefined, secret: string | undefined): Promise<RevalidateEvaluation>;
908
940
 
909
941
  /**
910
942
  * Revalidation webhook route handler (EC-146): the receiving end of the
911
- * publish → live-in-seconds pipeline. The studio's dispatcher POSTs a signed
912
- * `{projectId, event, tags, timestamp}` payload; this handler verifies the
913
- * HMAC over the raw body and calls `revalidateTag` for each tag, dropping
914
- * exactly the cached entries whose fetch-time tag sets contained them.
915
- *
916
- * Server-only by construction — consumed from an App Router route handler
917
- * file, never from client bundles.
943
+ * publish → live-in-seconds pipeline. The studio's Convex write chokepoint — the ONE
944
+ * emitter (EC-579) — POSTs a signed EVENT (`{ kind, collection?, id? }`, the wire in
945
+ * `@elytracms/core/revalidation`); this handler verifies the HMAC over the raw body,
946
+ * derives the tags from the event with this host's OWN scope and locales
947
+ * (`revalidationTags`, `cache.ts`) and calls `revalidateTag` for each, dropping the
948
+ * cached entries whose fetch-time tag sets contained them.
949
+ *
950
+ * Server-to-server only: the emitter runs in Convex, so there is no CORS and no
951
+ * `OPTIONS` preflight (the studio's browser dispatcher that needed them is deleted).
952
+ * Server-only by construction — consumed from an App Router route handler file,
953
+ * never from client bundles.
918
954
  */
919
- interface RevalidateRouteOptions {
955
+ interface RevalidateRouteOptions extends RevalidationProject {
920
956
  /**
921
957
  * Shared HMAC secret. An empty/undefined secret rejects every request with
922
958
  * 401 — the endpoint stays closed until deliberately configured.
923
959
  */
924
960
  secret: string | undefined;
925
- /**
926
- * CORS origin allowed to call this endpoint (e.g. the studio's origin).
927
- * The v1 dispatcher is a browser `fetch` from the studio, and the custom
928
- * signature header always triggers a CORS preflight — so cross-origin
929
- * studio → host dispatch only works when this is set. Server-side emitters
930
- * (the Convex action variant, CLIs, CI) need no CORS and may leave it
931
- * unset. Never use `*` with a real secret-bearing deployment unless you
932
- * accept that any origin may *attempt* deliveries (they still need the
933
- * secret to have any effect).
934
- */
935
- allowOrigin?: string;
936
961
  }
937
962
  interface RevalidateRouteHandlers {
938
963
  POST(request: Request): Promise<Response>;
939
- OPTIONS(): Promise<Response>;
940
964
  }
941
965
  /**
942
- * Create the webhook route handlers for `app/api/revalidate/route.ts`:
966
+ * Create the webhook route handler for `app/api/revalidate/route.ts`. Through the
967
+ * delivery kit it is `export const { POST } = elytra.revalidateRoute()`, which hands
968
+ * it the kit's own scope and locales — the ones its cached entries carry. Wiring it
969
+ * by hand, pass the SAME scope your canvas route caches under:
943
970
  *
944
971
  * ```ts
945
- * export const { POST, OPTIONS } = createRevalidateRoute({
972
+ * export const { POST } = createRevalidateRoute({
946
973
  * secret: process.env.REVALIDATE_SECRET,
974
+ * scope: process.env.ELYTRA_PROJECT_ID,
975
+ * locales: ['en', 'de'],
947
976
  * })
948
977
  * ```
949
978
  *
950
- * Unsigned/invalid requests are rejected with 401 (timing-safe HMAC check,
951
- * see `evaluateRevalidateRequest`); valid requests revalidate every carried
952
- * tag and answer `{ revalidated: tags }`.
979
+ * Unsigned/invalid requests are rejected with 401 (constant-time HMAC verify, see
980
+ * `evaluateRevalidateRequest`), signed-but-unreadable ones with 400; a valid event
981
+ * revalidates its derived tags and answers `{ revalidated: tags }` (`[]` for the
982
+ * doctor's `probe`).
953
983
  */
954
984
  declare function createRevalidateRoute(options: RevalidateRouteOptions): RevalidateRouteHandlers;
955
985
 
@@ -1152,7 +1182,9 @@ declare function elytraHostImage(options?: ElytraHostImageOptions): HostImageRen
1152
1182
  * | | equal the Convex deployment's own env var) |
1153
1183
  * | `REVALIDATE_SECRET` | Instant-publish webhook HMAC secret (must equal the |
1154
1184
  * | | deployment's `CANVAS_REVALIDATE_SECRET`) |
1155
- * | `STUDIO_ORIGIN` | CORS origin for the studio's browser-dispatch path |
1185
+ *
1186
+ * EC-579 retired `STUDIO_ORIGIN` (the CORS origin of the studio's deleted browser
1187
+ * dispatcher): the one emitter is the Convex deployment, server to server.
1156
1188
  */
1157
1189
  /** The kit's documented env names (single source of truth, also used in docs/tests). */
1158
1190
  declare const ELYTRA_HOST_ENV_KEYS: {
@@ -1160,14 +1192,12 @@ declare const ELYTRA_HOST_ENV_KEYS: {
1160
1192
  readonly projectId: "ELYTRA_PROJECT_ID";
1161
1193
  readonly draftToken: "DRAFT_PREVIEW_TOKEN";
1162
1194
  readonly revalidateSecret: "REVALIDATE_SECRET";
1163
- readonly studioOrigin: "STUDIO_ORIGIN";
1164
1195
  };
1165
1196
  interface ElytraHostEnv {
1166
1197
  convexUrl?: string;
1167
1198
  projectId?: string;
1168
1199
  draftToken?: string;
1169
1200
  revalidateSecret?: string;
1170
- studioOrigin?: string;
1171
1201
  }
1172
1202
  /**
1173
1203
  * Resolve the kit's env bag from a raw env record (`process.env` by default at
@@ -1338,7 +1368,7 @@ declare function createPreviewDisableRoute(): PreviewRouteHandlers;
1338
1368
  * export const generateMetadata = route.generateMetadata
1339
1369
  *
1340
1370
  * // app/api/revalidate/route.ts
1341
- * export const { POST, OPTIONS } = elytra.revalidateRoute()
1371
+ * export const { POST } = elytra.revalidateRoute()
1342
1372
  *
1343
1373
  * // app/api/draft/route.ts — GET /api/draft?token=…&redirect=/path
1344
1374
  * export const { GET } = elytra.previewRoute()
@@ -1364,7 +1394,7 @@ interface ElytraHostOptions {
1364
1394
  * Locale config — config-owned (AD-11), never read from the snapshot.
1365
1395
  *
1366
1396
  * **Import it, do not restate it** (EC-519). This is the *same*
1367
- * {@link LocaleConfig} `elytra.config.ts` takes, so a project declares its
1397
+ * {@link LocaleConfigInput} `elytra.config.ts` takes, so a project declares its
1368
1398
  * locales once and hands that one binding to both mounts. It stays a host
1369
1399
  * option because the engine cannot know where a repo keeps the value — but it
1370
1400
  * is no longer an option you have to *retype*: since EC-532 the delivery
@@ -1376,8 +1406,14 @@ interface ElytraHostOptions {
1376
1406
  * fact `locales` + `defaultLocale`, and in our own demo the two drifted — the
1377
1407
  * host said `['en']`, the config said `['en','de']`, and every `/de` URL 404'd
1378
1408
  * silently for weeks (EC-512).
1409
+ *
1410
+ * The DECLARED shape (EC-580): the kit parses it into the engine's `LocaleConfig`
1411
+ * where every content source is assembled (`createStaticContentSource` — the
1412
+ * empty source is built at boot), so a `default` outside `locales` stops the host
1413
+ * with a located message instead of serving — the same rule the studio's
1414
+ * workspace gate applies to the same value.
1379
1415
  */
1380
- locales: LocaleConfig;
1416
+ locales: LocaleConfigInput;
1381
1417
  /** CMS collection schema — config-owned (AD-11). */
1382
1418
  collections: readonly CollectionDef[];
1383
1419
  /** Routes as code (AD-3/AD-11): records, or the `(collections) => records` derive. */
@@ -1424,7 +1460,7 @@ interface ElytraHostOptions {
1424
1460
  /**
1425
1461
  * Env bag; defaults to `process.env`. Documented names (see `host-env.ts`):
1426
1462
  * `ELYTRA_CONVEX_URL`, `ELYTRA_PROJECT_ID`, `DRAFT_PREVIEW_TOKEN`,
1427
- * `REVALIDATE_SECRET`, `STUDIO_ORIGIN`.
1463
+ * `REVALIDATE_SECRET`.
1428
1464
  */
1429
1465
  env?: Record<string, string | undefined>;
1430
1466
  /**
@@ -1455,13 +1491,17 @@ interface HostCanvasRouteOptions {
1455
1491
  */
1456
1492
  wrap?: (children: ReactNode, ctx: CanvasWrapContext) => ReactNode;
1457
1493
  /**
1458
- * Tagged data cache (instant publish). Default: enabled, scoped to the
1459
- * kit's content scope so the studio's webhook tags match. `false` disables
1460
- * caching entirely; an explicit `CanvasCacheOptions` overrides the scope.
1461
- * The kit's {@link ElytraHost.contentMode} is always part of the entry key
1462
- * (EC-543) — an override can replace the scope, never drop the mode.
1494
+ * Tagged data cache (instant publish). Always on and always scoped to the kit's
1495
+ * {@link ElytraHost.contentScope}, with the kit's
1496
+ * {@link ElytraHost.contentMode} in the entry key (EC-543). `false` disables
1497
+ * caching entirely.
1498
+ *
1499
+ * There is no scope override (EC-579): the kit's revalidate receiver derives its
1500
+ * tags with that same `contentScope`, so one value scopes both the entries and
1501
+ * their invalidation. An override here was the one way left for the two to
1502
+ * disagree — and a disagreement drops nothing, silently.
1463
1503
  */
1464
- cache?: CanvasCacheOptions | false;
1504
+ cache?: false;
1465
1505
  }
1466
1506
  interface LoadGlobalsOptions {
1467
1507
  /** Perspective of the chrome read. Default `'published'` (live is published-only). */
@@ -1498,8 +1538,9 @@ type ElytraContentMode = 'live' | 'fallback' | 'empty';
1498
1538
  /** The delivery host handle {@link createElytraHost} yields. */
1499
1539
  interface ElytraHost {
1500
1540
  /**
1501
- * The cache-tag scope: `ELYTRA_PROJECT_ID` in live mode (matching the
1502
- * studio's webhook tags), else the config slug.
1541
+ * The cache-tag scope: `ELYTRA_PROJECT_ID` in live mode, else the config slug.
1542
+ * The ONE scope of this host (EC-579): its cache entries are written under it and
1543
+ * its revalidate receiver derives every invalidation tag with it.
1503
1544
  */
1504
1545
  contentScope: string;
1505
1546
  /**
@@ -1532,11 +1573,13 @@ interface ElytraHost {
1532
1573
  /** The catch-all canvas route pair for `app/[[...slug]]/page.tsx`. */
1533
1574
  canvasRoute(options?: HostCanvasRouteOptions): CanvasRoute;
1534
1575
  /**
1535
- * The instant-publish webhook handlers for `app/api/revalidate/route.ts`,
1536
- * wired to `REVALIDATE_SECRET` (+ `STUDIO_ORIGIN` CORS). Unset secret =
1537
- * endpoint closed (401), never a default secret.
1576
+ * The instant-publish webhook handler for `app/api/revalidate/route.ts`, wired to
1577
+ * `REVALIDATE_SECRET` and to this host's own project — {@link contentScope} and the
1578
+ * config locales — which it derives the tags of every event with (EC-579). Unset
1579
+ * secret = endpoint closed (401), never a default secret. Only the secret can be
1580
+ * overridden: the scope and locales are the ones this kit's cache entries carry.
1538
1581
  */
1539
- revalidateRoute(overrides?: Partial<RevalidateRouteOptions>): RevalidateRouteHandlers;
1582
+ revalidateRoute(overrides?: Partial<Pick<RevalidateRouteOptions, 'secret'>>): RevalidateRouteHandlers;
1540
1583
  /**
1541
1584
  * The draft-mode ENABLE route for e.g. `app/api/draft/route.ts` —
1542
1585
  * `GET /api/draft?token=…&redirect=/path`, gated by the ONE
@@ -1582,8 +1625,11 @@ interface LiveContentOptions {
1582
1625
  /** Convex deployment URL, e.g. `https://your-deployment-123.convex.cloud`. */
1583
1626
  convexUrl: string;
1584
1627
  projectId: string;
1585
- /** Project locale config — config-owned (AD-11), never read from the snapshot. */
1586
- locales: LocaleConfig;
1628
+ /**
1629
+ * Project locale config — config-owned (AD-11), never read from the snapshot.
1630
+ * The declared shape; parsed where the source is assembled (EC-580).
1631
+ */
1632
+ locales: LocaleConfigInput;
1587
1633
  /** CMS collection schema — config-owned (AD-11); drives depth-1 relation population. */
1588
1634
  collections: readonly CollectionDef[];
1589
1635
  /** Route records — config-owned (AD-3/AD-11); merged with the home fallback. */
@@ -1708,4 +1754,4 @@ declare function evaluatePreviewRequest(requestUrl: string | URL, expectedToken:
1708
1754
  */
1709
1755
  declare const PACKAGE = "@elytracms/next";
1710
1756
 
1711
- export { type AssetImageLoaderOptions, type CacheWrapper, type CacheWrapperOptions, type CachedEntry, type CanvasCacheOptions, type CanvasContentRequest, type CanvasPageOutcome, CanvasRenderer, type CanvasRendererProps, type CanvasRequestInfo, type CanvasRoute, type CanvasRouteOptions, type CanvasRouteProps, type CanvasSitemapOptions, type CanvasSitemapResult, type CanvasWrapContext, type ContentSnapshot, type ContentSnapshotOptions, type ContentSource, type CreateCanvasSitemapOptions, type DefineHostComponentsOptions, type DeliveryPresentation, ELYTRA_HOST_ENV_KEYS, type ElytraContentMode, type ElytraHost, type ElytraHostEnv, type ElytraHostImageOptions, type ElytraHostOptions, ElytraImage, type ElytraImageAsset, type ElytraImageProps, type GlobalsConventions, type HostCanvasRouteOptions, type HostComponent, type HostComponents, type LiveContentOptions, type LoadGlobalsOptions, type NextImagePrimitiveOptions, PACKAGE, type PreviewEvaluation, type PreviewRouteHandlers, type PreviewRouteOptions, REVALIDATE_SIGNATURE_HEADER, type ResolveCanvasPageOptions, type ResolveGlobalsOptions, type ResolvedLink, type ResolvedNavEntry, type ResolvedNavLink, type RevalidateEvaluation, type RevalidatePayload, type RevalidateRouteHandlers, type RevalidateRouteOptions, type SiteGlobals, type SitemapParamsProvider, type SitemapRouteContext, type SitemapSkippedRoute, type StaticContentData, assembleLiveSource, bodyItemsOf, canvasPageMetadata, canvasSitemapEntries, collectionCacheTag, createAssetImageLoader, createCanvasRoute, createCanvasSitemap, createContentSnapshot, createElytraHost, createPreviewDisableRoute, createPreviewRoute, createRevalidateRoute, createStaticContentSource, defineHostComponents, docCacheTag, elytraHostImage, evaluatePreviewRequest, evaluateRevalidateRequest, firstBlockFullBleed, loadCachedEntry, loadLiveContent, mergeCacheTags, mergeMetadataOverParent, nextCacheTags, nextImagePrimitive, openGraphLocale, pathFromSlug, projectSweepTag, resolveCanvasPage, resolveGlobals, resolveHostEnv, revalidatePayloadSchema, routeCacheTag, safeRedirectTarget, scopeCacheTag, signRevalidateBody };
1757
+ export { type AssetImageLoaderOptions, type CacheWrapper, type CacheWrapperOptions, type CachedEntry, type CanvasCacheOptions, type CanvasContentRequest, type CanvasPageOutcome, CanvasRenderer, type CanvasRendererProps, type CanvasRequestInfo, type CanvasRoute, type CanvasRouteOptions, type CanvasRouteProps, type CanvasSitemapOptions, type CanvasSitemapResult, type CanvasWrapContext, type ContentSnapshot, type ContentSnapshotOptions, type ContentSource, type CreateCanvasSitemapOptions, type DefineHostComponentsOptions, type DeliveryPresentation, ELYTRA_HOST_ENV_KEYS, type ElytraContentMode, type ElytraHost, type ElytraHostEnv, type ElytraHostImageOptions, type ElytraHostOptions, ElytraImage, type ElytraImageAsset, type ElytraImageProps, type GlobalsConventions, type HostCanvasRouteOptions, type HostComponent, type HostComponents, type LiveContentOptions, type LoadGlobalsOptions, type NextImagePrimitiveOptions, PACKAGE, type PreviewEvaluation, type PreviewRouteHandlers, type PreviewRouteOptions, type ResolveCanvasPageOptions, type ResolveGlobalsOptions, type ResolvedLink, type ResolvedNavEntry, type ResolvedNavLink, type RevalidateEvaluation, type RevalidateRouteHandlers, type RevalidateRouteOptions, type RevalidationProject, type SiteGlobals, type SitemapParamsProvider, type SitemapRouteContext, type SitemapSkippedRoute, type StaticContentData, assembleLiveSource, bodyItemsOf, canvasPageMetadata, canvasSitemapEntries, collectionCacheTag, createAssetImageLoader, createCanvasRoute, createCanvasSitemap, createContentSnapshot, createElytraHost, createPreviewDisableRoute, createPreviewRoute, createRevalidateRoute, createStaticContentSource, defineHostComponents, docCacheTag, elytraHostImage, evaluatePreviewRequest, evaluateRevalidateRequest, firstBlockFullBleed, loadCachedEntry, loadLiveContent, mergeCacheTags, mergeMetadataOverParent, nextCacheTags, nextImagePrimitive, openGraphLocale, pathFromSlug, projectSweepTag, resolveCanvasPage, resolveGlobals, resolveHostEnv, revalidationTags, routeCacheTag, safeRedirectTarget, scopeCacheTag };