@elytracms/next 0.0.80 → 0.0.82
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 +260 -59
- package/dist/index.js +106 -49
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { ComponentType, ReactNode } from 'react';
|
|
2
2
|
import { ComponentRegistry, ComponentIssue, ComponentManifest } from '@elytracms/core/component-registry';
|
|
3
3
|
export { ComponentManifest, PropField, SlotSpec, defineComponent } from '@elytracms/core/component-registry';
|
|
4
|
-
import { ComponentImplementations, ResolveBinding, RelationTarget } from '@elytracms/core/runtime-renderer';
|
|
4
|
+
import { ComponentImplementations, ResolveBinding, RelationTarget, DocumentBodyItem } from '@elytracms/core/runtime-renderer';
|
|
5
5
|
export { ComponentImplementations, RenderAsset } from '@elytracms/core/runtime-renderer';
|
|
6
6
|
import { ResolvePageResult, ResolvedAsset, Perspective, ContentClient, CacheTags, ContentDocumentSource, ResolvedDocument } from '@elytracms/core/content';
|
|
7
7
|
export { Perspective } from '@elytracms/core/content';
|
|
8
|
-
import { LocaleConfig, Locale, RouteRecord,
|
|
9
|
-
export { CmsDocument, CollectionDef, FieldDef, Locale, LocaleConfig, RedirectRecord, RouteRecord, documentSchema, fallbackHomeRoutes, localeConfigSchema, mergeRouteRecords, redirectRecordSchema, routeRecordSchema } from '@elytracms/core/cms-core';
|
|
8
|
+
import { LocaleConfig, Locale, RouteRecord, CollectionDef, RedirectRecord } from '@elytracms/core/cms-core';
|
|
9
|
+
export { CmsDocument, CollectionDef, FieldDef, Locale, LocaleConfig, LocaleUrlPath, RedirectRecord, RouteRecord, documentSchema, fallbackHomeRoutes, localeConfigSchema, localeUrlPath, mergeRouteRecords, redirectRecordSchema, routeRecordSchema, splitLocalePath } from '@elytracms/core/cms-core';
|
|
10
10
|
import { ComponentNode } from '@elytracms/core/project-graph';
|
|
11
11
|
export { ComponentNode } from '@elytracms/core/project-graph';
|
|
12
12
|
import { AssetRecord, PersistenceAdapter } from '@elytracms/core/persistence';
|
|
@@ -169,6 +169,29 @@ interface CanvasRendererProps {
|
|
|
169
169
|
* issue (EC-290), never a silent `#`.
|
|
170
170
|
*/
|
|
171
171
|
links?: Record<string, string>;
|
|
172
|
+
/**
|
|
173
|
+
* The resolved document's collection-declared delivery presentation (EC-499,
|
|
174
|
+
* from the render outcome): which field(s) hold the body. Absent → the EC-458
|
|
175
|
+
* shape picker selects the body by VALUE, exactly as before.
|
|
176
|
+
*/
|
|
177
|
+
presentation?: DeliveryPresentation;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* A collection's `presentation` declaration (EC-499), resolved for delivery: the
|
|
181
|
+
* body field list normalized from `string | string[]`. Plain data — it rides the
|
|
182
|
+
* (serializable) render outcome and the RSC boundary like the baked
|
|
183
|
+
* assets/relations/links do.
|
|
184
|
+
*
|
|
185
|
+
* EC-524: this renderer applies no CHROME. It carried a `wrapper` component id
|
|
186
|
+
* briefly (never released); a detail wrapper is a page template, templates are
|
|
187
|
+
* code, and the host now supplies one from its own route file via
|
|
188
|
+
* `createCanvasRoute`'s `wrap` option. What stays here is content selection —
|
|
189
|
+
* which is exactly what makes a canvas body and a rich-text body render
|
|
190
|
+
* agnostically through the one entry point.
|
|
191
|
+
*/
|
|
192
|
+
interface DeliveryPresentation {
|
|
193
|
+
/** Field names whose values form the body, in render order. */
|
|
194
|
+
body?: readonly string[];
|
|
172
195
|
}
|
|
173
196
|
/** Default payloads: the route-bound document and the dynamic route params. */
|
|
174
197
|
declare function defaultBindingPayloads(result: ResolvePageResult): BindingPayloads;
|
|
@@ -178,6 +201,15 @@ declare function defaultBindingPayloads(result: ResolvePageResult): BindingPaylo
|
|
|
178
201
|
* the `ContentSource`).
|
|
179
202
|
*/
|
|
180
203
|
declare function CanvasRenderer(props: CanvasRendererProps): Promise<ReactNode>;
|
|
204
|
+
/**
|
|
205
|
+
* The ordered body items of a resolved document (EC-499): the values of the
|
|
206
|
+
* collection's declared `presentation.body` fields concatenated in declaration
|
|
207
|
+
* order, or — when the collection declares nothing — the EC-458 value-shape
|
|
208
|
+
* pick, unchanged. A declared field the document has no value for contributes
|
|
209
|
+
* nothing (an empty body is legitimate); a declared field holding something that
|
|
210
|
+
* is neither composition nor rich text contributes a visible fallback.
|
|
211
|
+
*/
|
|
212
|
+
declare function bodyItemsOf(document: Record<string, unknown> | null | undefined, presentation?: DeliveryPresentation): DocumentBodyItem[];
|
|
181
213
|
|
|
182
214
|
/**
|
|
183
215
|
* The content seam of the embedded runtime (EC-144, vision AD-1/AD-4): the
|
|
@@ -224,6 +256,16 @@ interface ContentSource {
|
|
|
224
256
|
* explicit routes.
|
|
225
257
|
*/
|
|
226
258
|
readonly routes?: readonly RouteRecord[];
|
|
259
|
+
/**
|
|
260
|
+
* The project's collection definitions (EC-499, additive) — config-owned
|
|
261
|
+
* (AD-11), already prefetched by every source that reads a schema. The route
|
|
262
|
+
* helper reads the resolved document's collection from here to honour its
|
|
263
|
+
* declared `presentation` (which field(s) hold the delivery body, and the
|
|
264
|
+
* optional detail wrapper). Optional so existing custom sources stay valid;
|
|
265
|
+
* without it every collection is treated as undeclared and the EC-458 shape
|
|
266
|
+
* picker decides, exactly as before.
|
|
267
|
+
*/
|
|
268
|
+
readonly collections?: readonly CollectionDef[];
|
|
227
269
|
/**
|
|
228
270
|
* Data-source definitions the graph's bindings reference (EC-166 delivery,
|
|
229
271
|
* additive). When present, the route helper materializes per-section list
|
|
@@ -306,13 +348,36 @@ interface MaterializedSourcePayloads {
|
|
|
306
348
|
*/
|
|
307
349
|
declare function materializeSourcePayloads(input: MaterializeSourcePayloadsInput): MaterializedSourcePayloads;
|
|
308
350
|
|
|
351
|
+
/**
|
|
352
|
+
* Bridge from a `PersistenceAdapter` to the synchronous `ContentLookup` the
|
|
353
|
+
* accessors (EC-143) read through: prefetch the project's collections,
|
|
354
|
+
* documents, assets, and graphs ONCE (async), then serve every accessor call
|
|
355
|
+
* synchronously from memory. This is how `apps/example-site` reads the seeded
|
|
356
|
+
* in-memory fixture project; a Convex-backed `ContentSource` (EC-145/146)
|
|
357
|
+
* replaces this bridge without touching the route helper.
|
|
358
|
+
*
|
|
359
|
+
* The assembly half is exposed separately as {@link createStaticContentSource}
|
|
360
|
+
* (EC-156): a host that already holds the prefetched data as plain values —
|
|
361
|
+
* e.g. a delivery-snapshot payload fetched over Convex's public HTTP query
|
|
362
|
+
* API — assembles the exact same `ContentSource` without faking an adapter.
|
|
363
|
+
*/
|
|
309
364
|
interface ContentSnapshotOptions {
|
|
310
365
|
/** Route records served by `resolvePage` (EC-019). */
|
|
311
366
|
routes?: readonly RouteRecord[];
|
|
312
367
|
/** Redirect records served by `resolvePage` (EC-019/020). */
|
|
313
368
|
redirects?: readonly RedirectRecord[];
|
|
314
|
-
/**
|
|
315
|
-
|
|
369
|
+
/**
|
|
370
|
+
* Locale configuration — **required**, never defaulted (EC-530). The default
|
|
371
|
+
* locale is the storage key every localized value is written under, so a
|
|
372
|
+
* fabricated `en` is a data migration to undo, not a config edit: content
|
|
373
|
+
* authored under one locale and served under another, silently (EC-512
|
|
374
|
+
* measured exactly that). EC-490 made a config declaring no locales an error
|
|
375
|
+
* and EC-519 collapsed the two declarations into one; this was the last place
|
|
376
|
+
* in the chain still inventing the value, and it sits in the package that
|
|
377
|
+
* resolves the URLs. Hand it the project's `LocaleConfig` — the one the
|
|
378
|
+
* config declares.
|
|
379
|
+
*/
|
|
380
|
+
locales: LocaleConfig;
|
|
316
381
|
/**
|
|
317
382
|
* Data-source definitions the graph's bindings reference (EC-166): section
|
|
318
383
|
* sources (`cms.section.<nodeId>`) carry their stored where/sort/limit, and
|
|
@@ -332,8 +397,8 @@ interface ContentSnapshot extends ContentSource {
|
|
|
332
397
|
*/
|
|
333
398
|
interface StaticContentData {
|
|
334
399
|
projectId: string;
|
|
335
|
-
/** Locale configuration
|
|
336
|
-
locales
|
|
400
|
+
/** Locale configuration — **required**, never defaulted (see EC-530 above). */
|
|
401
|
+
locales: LocaleConfig;
|
|
337
402
|
/** Route records served by `resolvePage` (EC-019). */
|
|
338
403
|
routes?: readonly RouteRecord[];
|
|
339
404
|
/** Redirect records served by `resolvePage` (EC-019/020). */
|
|
@@ -374,7 +439,7 @@ declare function createStaticContentSource(data: StaticContentData): ContentSnap
|
|
|
374
439
|
* request (or memoize across requests for fixture-backed sites) — accessors
|
|
375
440
|
* never reach back to the adapter.
|
|
376
441
|
*/
|
|
377
|
-
declare function createContentSnapshot(adapter: PersistenceAdapter, projectId: string, options
|
|
442
|
+
declare function createContentSnapshot(adapter: PersistenceAdapter, projectId: string, options: ContentSnapshotOptions): Promise<ContentSnapshot>;
|
|
378
443
|
|
|
379
444
|
/**
|
|
380
445
|
* The pure half of the catch-all route helper (EC-144): URL → outcome, with
|
|
@@ -383,6 +448,43 @@ declare function createContentSnapshot(adapter: PersistenceAdapter, projectId: s
|
|
|
383
448
|
* each outcome onto `redirect()` / `permanentRedirect()` / `notFound()` /
|
|
384
449
|
* `<CanvasRenderer />`.
|
|
385
450
|
*/
|
|
451
|
+
/** What the route helper tells a host callback about the request it is serving. */
|
|
452
|
+
interface CanvasRequestInfo {
|
|
453
|
+
/** The raw request path (including any locale prefix). */
|
|
454
|
+
url: string;
|
|
455
|
+
perspective: Perspective;
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* What a host's `wrap` function is told about the request it is wrapping
|
|
459
|
+
* (EC-524 — see `CanvasRouteOptions.wrap`).
|
|
460
|
+
*
|
|
461
|
+
* Every field is something the route already had in hand at the wrap point:
|
|
462
|
+
* nothing is gathered for this seam, and nothing was invented for it.
|
|
463
|
+
*/
|
|
464
|
+
interface CanvasWrapContext extends CanvasRequestInfo {
|
|
465
|
+
/**
|
|
466
|
+
* The collection of the document this URL resolved to — **the branch key**.
|
|
467
|
+
* `undefined` when the resolution carried no document, so a host branching on
|
|
468
|
+
* a collection id naturally wraps nothing there (which is what the removed
|
|
469
|
+
* `presentation.wrapper` declaration did too: it only applied with a document).
|
|
470
|
+
*/
|
|
471
|
+
collection?: string;
|
|
472
|
+
/**
|
|
473
|
+
* The resolved document. Verbatim the `document` prop the removed wrapper
|
|
474
|
+
* component received, so a wrapper ported to this seam needs no other source
|
|
475
|
+
* for its field-level chrome (the title, a byline, a date).
|
|
476
|
+
*/
|
|
477
|
+
document: ResolvedDocument | null;
|
|
478
|
+
/**
|
|
479
|
+
* The whole resolution — the same value the `metadata` callback already
|
|
480
|
+
* receives, so this is the established host-callback argument on this route
|
|
481
|
+
* rather than a new surface. It carries `dynamicParams`, `route`,
|
|
482
|
+
* `localeFallback` and `issues` for a wrapper that needs more than the
|
|
483
|
+
* document (EC-499 recorded wanting exactly that and could not have it across
|
|
484
|
+
* a serializable declaration).
|
|
485
|
+
*/
|
|
486
|
+
result: ResolvePageResult;
|
|
487
|
+
}
|
|
386
488
|
/** What the host's payload factory sees for one request. */
|
|
387
489
|
interface CanvasPayloadContext {
|
|
388
490
|
/** Accessor client bound to this request's perspective + locale. */
|
|
@@ -454,19 +556,20 @@ type CanvasPageOutcome = {
|
|
|
454
556
|
* embedding page's href.
|
|
455
557
|
*/
|
|
456
558
|
links: Record<string, string>;
|
|
559
|
+
/**
|
|
560
|
+
* The resolved document's collection-declared delivery presentation
|
|
561
|
+
* (EC-499): which field(s) are the render body. Plain, serializable data —
|
|
562
|
+
* handed to `<CanvasRenderer />` so ONE catch-all renders every
|
|
563
|
+
* collection's body, whatever its shape. Absent when the collection
|
|
564
|
+
* declares nothing (or the source exposes no collections), and the EC-458
|
|
565
|
+
* value-shape picker then decides exactly as before.
|
|
566
|
+
*/
|
|
567
|
+
presentation?: DeliveryPresentation;
|
|
457
568
|
tags: CacheTags;
|
|
458
569
|
};
|
|
459
570
|
/** Build the request path from a Next catch-all `slug` param. */
|
|
460
571
|
declare function pathFromSlug(slug: readonly string[] | undefined): string;
|
|
461
|
-
|
|
462
|
-
* Derive the request locale from a leading URL segment (`/de/about` → locale
|
|
463
|
-
* `de`, path `/about`) and return the remaining path. No prefix (or the
|
|
464
|
-
* default locale's own content at root) falls back to the default locale.
|
|
465
|
-
*/
|
|
466
|
-
declare function splitLocalePath(url: string, locales: LocaleConfig): {
|
|
467
|
-
locale: Locale;
|
|
468
|
-
path: string;
|
|
469
|
-
};
|
|
572
|
+
|
|
470
573
|
interface ResolveCanvasPageOptions {
|
|
471
574
|
payloads?: CanvasPayloadsFactory;
|
|
472
575
|
/**
|
|
@@ -487,28 +590,6 @@ interface ResolveCanvasPageOptions {
|
|
|
487
590
|
*/
|
|
488
591
|
declare function resolveCanvasPage(source: ContentSource, url: string, perspective: Perspective, options?: ResolveCanvasPageOptions): Promise<CanvasPageOutcome>;
|
|
489
592
|
|
|
490
|
-
/**
|
|
491
|
-
* Catch-all route helper (EC-144): the factory a host app calls once in
|
|
492
|
-
* `app/[[...slug]]/page.tsx` to wire `resolvePage` → `<CanvasRenderer />`,
|
|
493
|
-
* with redirects answered by Next's `redirect()`/`permanentRedirect()` (per
|
|
494
|
-
* the route's `permanent` flag), unknown URLs answered by `notFound()` (the
|
|
495
|
-
* host's 404 renders), and the perspective chosen by Next `draftMode()` —
|
|
496
|
-
* draft when enabled (via the token-gated preview route), published
|
|
497
|
-
* otherwise.
|
|
498
|
-
*
|
|
499
|
-
* With `cache` configured (EC-146), the published-perspective resolve+render
|
|
500
|
-
* data work runs inside `unstable_cache`, keyed by URL and tagged with the
|
|
501
|
-
* fetch-time tag set of what the response actually contained (see
|
|
502
|
-
* `cache.ts`). Publishing in the studio then invalidates exactly the affected
|
|
503
|
-
* entries via the signed `/api/revalidate` webhook — and while the content
|
|
504
|
-
* backend is unreachable, cached entries keep serving because a cache hit
|
|
505
|
-
* never invokes `loadContent`.
|
|
506
|
-
*/
|
|
507
|
-
interface CanvasRequestInfo {
|
|
508
|
-
/** The raw request path (including any locale prefix). */
|
|
509
|
-
url: string;
|
|
510
|
-
perspective: Perspective;
|
|
511
|
-
}
|
|
512
593
|
interface CanvasCacheOptions {
|
|
513
594
|
/**
|
|
514
595
|
* Project scope prefix for every tag (`p:<scope>:…`) — must be the project
|
|
@@ -562,6 +643,38 @@ interface CanvasRouteOptions {
|
|
|
562
643
|
onTags?: (tags: CacheTags, info: CanvasRequestInfo) => void;
|
|
563
644
|
/** Override the generated `<head>` metadata for a resolved page. */
|
|
564
645
|
metadata?: (result: ResolvePageResult, info: CanvasRequestInfo) => Metadata;
|
|
646
|
+
/**
|
|
647
|
+
* Wrap the rendered page in the host's own chrome (EC-524) — a detail
|
|
648
|
+
* template, a per-collection layout, a draft ribbon. Receives the rendered
|
|
649
|
+
* canvas as `children` and returns whatever should stand in its place:
|
|
650
|
+
*
|
|
651
|
+
* ```tsx
|
|
652
|
+
* elytra.canvasRoute({
|
|
653
|
+
* wrap: (children, ctx) =>
|
|
654
|
+
* ctx.collection === 'post' ? <PostDetail document={ctx.document}>{children}</PostDetail> : children,
|
|
655
|
+
* })
|
|
656
|
+
* ```
|
|
657
|
+
*
|
|
658
|
+
* **This is deliberately host code, not config** (owner ruling 2026-08-01):
|
|
659
|
+
* *"this sounds very much like page-templates … templates belong in code, so
|
|
660
|
+
* the wrapper is something the developer has to care about … the page-content
|
|
661
|
+
* (Canvas or Rich-Text) should render agnostically."* A collection's
|
|
662
|
+
* `presentation.body` still names WHICH field is the content — that is content
|
|
663
|
+
* selection, and it is what lets one catch-all render a canvas page and a
|
|
664
|
+
* rich-text post through the same call. What surrounds that content is the
|
|
665
|
+
* developer's implementation decision, branched here in their own file.
|
|
666
|
+
*
|
|
667
|
+
* It replaced a `presentation.wrapper` component-id STRING resolved from the
|
|
668
|
+
* canvas registry, which forced a delivery-only component to register as a
|
|
669
|
+
* pseudo-block (EC-514). A function beats that on every axis that matters: it
|
|
670
|
+
* cannot dangle, the compiler checks it, and it can branch on anything in
|
|
671
|
+
* {@link CanvasWrapContext} rather than on collection identity alone.
|
|
672
|
+
*
|
|
673
|
+
* Applied to the `render` outcome only — a redirect or a 404 never reaches it.
|
|
674
|
+
* Returning `children` unchanged is the no-op, and omitting `wrap` entirely is
|
|
675
|
+
* the default.
|
|
676
|
+
*/
|
|
677
|
+
wrap?: (children: ReactNode, ctx: CanvasWrapContext) => ReactNode;
|
|
565
678
|
}
|
|
566
679
|
interface CanvasRouteProps {
|
|
567
680
|
params: Promise<{
|
|
@@ -601,6 +714,16 @@ declare function canvasPageMetadata(result: ResolvePageResult, assets?: readonly
|
|
|
601
714
|
* documents (composing each page's nested path from its parent chain) — no
|
|
602
715
|
* params provider entry needed.
|
|
603
716
|
*
|
|
717
|
+
* **Enumeration is a candidate list, not the output (EC-486).** Every branch
|
|
718
|
+
* above — static, provider-fed, and mount — only proposes paths; each one is
|
|
719
|
+
* then put back through `resolvePage`, and a path the resolver refuses is
|
|
720
|
+
* dropped. So the sitemap cannot claim a URL the delivery host would 404, even
|
|
721
|
+
* though the mount branch enumerates its collection independently of route
|
|
722
|
+
* precedence. Do not read the enumeration in isolation: the gate below it is
|
|
723
|
+
* what makes the two agree. What a mount enumerated and the resolver then
|
|
724
|
+
* refused is reported in {@link CanvasSitemapResult.skipped}, never dropped in
|
|
725
|
+
* silence.
|
|
726
|
+
*
|
|
604
727
|
* Host usage (`app/sitemap.ts`):
|
|
605
728
|
*
|
|
606
729
|
* ```ts
|
|
@@ -702,14 +825,14 @@ declare function createCanvasSitemap(options: CreateCanvasSitemapOptions): () =>
|
|
|
702
825
|
* keeps multi-project hosts (one Next app serving several builder projects)
|
|
703
826
|
* from cross-invalidating, so the prefix is **on whenever the host knows its
|
|
704
827
|
* project id** — decision: include it. The emitter (the studio's webhook
|
|
705
|
-
* dispatcher, `
|
|
828
|
+
* dispatcher, `packages/studio/src/lib/webhooks`) always scopes with the
|
|
706
829
|
* operation's `projectId`; a host that omits `cache.scope` therefore will not
|
|
707
830
|
* match studio-emitted tags. Configure `cache.scope` with the same project id
|
|
708
831
|
* the studio publishes under.
|
|
709
832
|
*
|
|
710
833
|
* The studio-side emitter cannot depend on this package (it would drag the
|
|
711
|
-
* `next` peer dependency into the
|
|
712
|
-
* `
|
|
834
|
+
* `next` peer dependency into the studio bundle), so
|
|
835
|
+
* `packages/studio/src/lib/webhooks/tags.ts` mirrors these formatters; tests on
|
|
713
836
|
* both sides pin the exact literal strings to keep them in lock-step.
|
|
714
837
|
*/
|
|
715
838
|
/** Apply the optional multi-project scope prefix to one tag. */
|
|
@@ -784,14 +907,67 @@ interface CachedEntry<T> {
|
|
|
784
907
|
* entry and the next request repeats the cycle with fresh data.
|
|
785
908
|
*
|
|
786
909
|
* The loader runs **once per revalidation cycle** (per key), never once per
|
|
787
|
-
* request. Time-based revalidation is intentionally not exposed
|
|
788
|
-
* stale-while-revalidate background
|
|
789
|
-
*
|
|
790
|
-
* `revalidate: false`
|
|
910
|
+
* request. Time-based revalidation is intentionally not exposed, and the reason
|
|
911
|
+
* is a real hazard rather than a preference: a stale-while-revalidate background
|
|
912
|
+
* refresh re-runs the probe callback, whose bail aborts the refresh, so the
|
|
913
|
+
* entry is never rewritten. `revalidate: false` keeps *time* from ever putting
|
|
914
|
+
* an entry into that state — which is the AD-5 model.
|
|
915
|
+
*
|
|
916
|
+
* **What `revalidate: false` does NOT buy (EC-515, measured on Next 16.2.10).**
|
|
917
|
+
* The earlier version of this comment claimed `revalidate: false` made the SWR
|
|
918
|
+
* hazard unreachable. It does not: a **tag** can put an entry into the stale
|
|
919
|
+
* state too, and then exactly the aborted-refresh loop above happens. Whether it
|
|
920
|
+
* does is decided by how the invalidator stamps the tag —
|
|
921
|
+
* `revalidateTag(tag, 'max')` marks entries stale (365-day expiry) and triggers
|
|
922
|
+
* the loop; `revalidateTag(tag, { expire: 0 })` drops them outright and does
|
|
923
|
+
* not. `revalidate.ts` therefore sends `{ expire: 0 }`, and the reasoning lives
|
|
924
|
+
* at that call site. Keep the two in step: this design only survives an
|
|
925
|
+
* invalidation that **drops** the entry, never one that merely staleness-marks
|
|
926
|
+
* it.
|
|
791
927
|
*
|
|
792
928
|
* Concurrency: wrappers and the captured closure are per-request, so there is
|
|
793
929
|
* no cross-request mutation; two racing first requests both resolve and the
|
|
794
930
|
* last write wins (deterministic content ⇒ identical entries).
|
|
931
|
+
*
|
|
932
|
+
* ## ⚠️ Self-hosted hosts: only the BASELINE tags invalidate (EC-515)
|
|
933
|
+
*
|
|
934
|
+
* **Measured on a self-hosted `next start` (Next 16.2.10, default
|
|
935
|
+
* `FileSystemCache`, `.next/cache` on disk); NOT measured on Vercel.**
|
|
936
|
+
*
|
|
937
|
+
* Fetch-time tag discovery writes the entry with the tags the response actually
|
|
938
|
+
* read — but the default self-hosted cache handler decides staleness and expiry
|
|
939
|
+
* from the tags the **reading** wrapper names (`ctx.tags`, see
|
|
940
|
+
* `IncrementalCache.get` / `FileSystemCache.get` for `kind: FETCH`), not from
|
|
941
|
+
* the tags the entry was **stored** with. The stored tag set is only ever used
|
|
942
|
+
* to decide whether to re-write the entry with a superset.
|
|
943
|
+
*
|
|
944
|
+
* Every read in {@link loadCachedEntry} goes through the **probe** wrapper,
|
|
945
|
+
* whose tags are `baselineTags`. So on such a host:
|
|
946
|
+
*
|
|
947
|
+
* - revalidating a tag that is in `baselineTags` works (the route helper passes
|
|
948
|
+
* the project sweep tag, so `project` sweeps do invalidate);
|
|
949
|
+
* - revalidating a *discovered* `doc:` / `collection:` / `route:` / `asset:` tag
|
|
950
|
+
* invalidates **nothing** — silently. No error, no log line, no loader run.
|
|
951
|
+
*
|
|
952
|
+
* Measured 2×2 that isolates it (same host, same run): a probe handed the full
|
|
953
|
+
* discovered tag set up front DOES honour a `doc:` tag, and the same probe
|
|
954
|
+
* handed only the baseline does not. So the tags, their formatting and the
|
|
955
|
+
* webhook are all correct — the read simply never consults them.
|
|
956
|
+
*
|
|
957
|
+
* Consequence for a self-hosted deployment: invalidation granularity collapses
|
|
958
|
+
* to whatever is in `baselineTags`. The studio's Convex write chokepoint emits
|
|
959
|
+
* the project sweep on every publish, so instant publish still *works* there,
|
|
960
|
+
* coarsely; the browser dispatcher's targeted `doc:`/`collection:` tag set
|
|
961
|
+
* (`packages/studio/src/lib/webhooks/tags.ts`) does not reach a self-hosted host
|
|
962
|
+
* at all. Vercel-hosted deployments are unaffected in practice — both consumer
|
|
963
|
+
* sites were measured healthy on 2026-08-01 — which is consistent with Vercel's
|
|
964
|
+
* cache handler purging by the entry's stored tags, though that has **not** been
|
|
965
|
+
* measured here.
|
|
966
|
+
*
|
|
967
|
+
* Fixing this properly means the read must be able to name the entry's own tags,
|
|
968
|
+
* which no `unstable_cache` API allows without restructuring the probe/bail
|
|
969
|
+
* seam. That is a deliberate open decision — see EC-515, do not "fix" it here
|
|
970
|
+
* incidentally.
|
|
795
971
|
*/
|
|
796
972
|
declare function loadCachedEntry<T>(cache: CacheWrapper, keyParts: string[], baselineTags: readonly string[], load: () => Promise<CachedEntry<T>>): Promise<CachedEntry<T>>;
|
|
797
973
|
|
|
@@ -1250,16 +1426,13 @@ declare function createPreviewDisableRoute(): PreviewRouteHandlers;
|
|
|
1250
1426
|
* ```ts
|
|
1251
1427
|
* // frontend/lib/elytra.ts — the ONE seam file
|
|
1252
1428
|
* import { createElytraHost } from '@elytracms/next'
|
|
1253
|
-
*
|
|
1254
|
-
*
|
|
1255
|
-
*
|
|
1256
|
-
*
|
|
1257
|
-
*
|
|
1258
|
-
*
|
|
1259
|
-
*
|
|
1260
|
-
* routes,
|
|
1261
|
-
* components: siteComponents,
|
|
1262
|
-
* })
|
|
1429
|
+
* // Read the project out of the repo's own config — `elytra.config.ts` is the
|
|
1430
|
+
* // single contract surface, and importing it costs a delivery bundle nothing
|
|
1431
|
+
* // (EC-532). Any other module layout works just as well; this is an example,
|
|
1432
|
+
* // not a required path.
|
|
1433
|
+
* import { site } from '../../elytra.config'
|
|
1434
|
+
*
|
|
1435
|
+
* export const elytra = createElytraHost({ ...site })
|
|
1263
1436
|
* ```
|
|
1264
1437
|
*
|
|
1265
1438
|
* ```ts
|
|
@@ -1291,7 +1464,23 @@ interface ElytraHostOptions {
|
|
|
1291
1464
|
* Used as the content scope when no live backend is configured.
|
|
1292
1465
|
*/
|
|
1293
1466
|
slug: string;
|
|
1294
|
-
/**
|
|
1467
|
+
/**
|
|
1468
|
+
* Locale config — config-owned (AD-11), never read from the snapshot.
|
|
1469
|
+
*
|
|
1470
|
+
* **Import it, do not restate it** (EC-519). This is the *same*
|
|
1471
|
+
* {@link LocaleConfig} `elytra.config.ts` takes, so a project declares its
|
|
1472
|
+
* locales once and hands that one binding to both mounts. It stays a host
|
|
1473
|
+
* option because the engine cannot know where a repo keeps the value — but it
|
|
1474
|
+
* is no longer an option you have to *retype*: since EC-532 the delivery
|
|
1475
|
+
* bundle can import `elytra.config.ts` itself for zero engine modules
|
|
1476
|
+
* (measured), so `createElytraHost({ ...site })` over a project exported from
|
|
1477
|
+
* the config is the shortest correct answer.
|
|
1478
|
+
*
|
|
1479
|
+
* This used to be `{ default, locales }` against a config spelling the same
|
|
1480
|
+
* fact `locales` + `defaultLocale`, and in our own demo the two drifted — the
|
|
1481
|
+
* host said `['en']`, the config said `['en','de']`, and every `/de` URL 404'd
|
|
1482
|
+
* silently for weeks (EC-512).
|
|
1483
|
+
*/
|
|
1295
1484
|
locales: LocaleConfig;
|
|
1296
1485
|
/** CMS collection schema — config-owned (AD-11). */
|
|
1297
1486
|
collections: readonly CollectionDef[];
|
|
@@ -1342,6 +1531,12 @@ interface HostCanvasRouteOptions {
|
|
|
1342
1531
|
onTags?: (tags: CacheTags, info: CanvasRequestInfo) => void;
|
|
1343
1532
|
/** Metadata override (see `CanvasRouteOptions.metadata`). */
|
|
1344
1533
|
metadata?: (result: ResolvePageResult, info: CanvasRequestInfo) => Metadata;
|
|
1534
|
+
/**
|
|
1535
|
+
* Host-owned chrome around the rendered page (EC-524) — the seam a per-
|
|
1536
|
+
* collection detail template lives on. Branch on `ctx.collection` in your own
|
|
1537
|
+
* route file (see `CanvasRouteOptions.wrap`).
|
|
1538
|
+
*/
|
|
1539
|
+
wrap?: (children: ReactNode, ctx: CanvasWrapContext) => ReactNode;
|
|
1345
1540
|
/**
|
|
1346
1541
|
* Tagged data cache (instant publish). Default: enabled, scoped to the
|
|
1347
1542
|
* kit's content scope so the studio's webhook tags match. `false` disables
|
|
@@ -1485,6 +1680,12 @@ declare function loadLiveContent(options: LiveContentOptions, perspective: Persp
|
|
|
1485
1680
|
* document, empty composition, unknown component, or an undeclared hint all
|
|
1486
1681
|
* answer `false` — the safe default is clearance.
|
|
1487
1682
|
*
|
|
1683
|
+
* EC-499: pass the collection's resolved `presentation` (from the render
|
|
1684
|
+
* outcome) so this reads the SAME body the renderer will render when the
|
|
1685
|
+
* collection declares one — otherwise the value-shape pick could name a
|
|
1686
|
+
* different field than the declaration does. A body whose first item is prose
|
|
1687
|
+
* rather than a block answers `false`, which is right: rich text opens no hero.
|
|
1688
|
+
*
|
|
1488
1689
|
* ```tsx
|
|
1489
1690
|
* const outcome = client.resolvePage(path)
|
|
1490
1691
|
* const fullBleed = firstBlockFullBleed(outcome.document, components.registry)
|
|
@@ -1493,7 +1694,7 @@ declare function loadLiveContent(options: LiveContentOptions, perspective: Persp
|
|
|
1493
1694
|
*
|
|
1494
1695
|
* The host kit wires the resolution half for you: `elytra.pageOpensFullBleed(slug)`.
|
|
1495
1696
|
*/
|
|
1496
|
-
declare function firstBlockFullBleed(document: Record<string, unknown> | null | undefined, registry: ComponentRegistry): boolean;
|
|
1697
|
+
declare function firstBlockFullBleed(document: Record<string, unknown> | null | undefined, registry: ComponentRegistry, presentation?: DeliveryPresentation): boolean;
|
|
1497
1698
|
|
|
1498
1699
|
/**
|
|
1499
1700
|
* Pure token/target logic of the draft-preview routes (EC-144), kept free of
|
|
@@ -1541,4 +1742,4 @@ declare function evaluatePreviewRequest(requestUrl: string | URL, expectedToken:
|
|
|
1541
1742
|
*/
|
|
1542
1743
|
declare const PACKAGE = "@elytracms/next";
|
|
1543
1744
|
|
|
1544
|
-
export { type AssetImageLoaderOptions, type BindingPayloads, type CacheWrapper, type CacheWrapperOptions, type CachedEntry, type CanvasCacheOptions, type CanvasContentRequest, type CanvasDataSource, type CanvasPageOutcome, type CanvasPayloadContext, type CanvasPayloadsFactory, CanvasRenderer, type CanvasRendererProps, type CanvasRequestInfo, type CanvasRoute, type CanvasRouteOptions, type CanvasRouteProps, type CanvasSitemapOptions, type CanvasSitemapResult, type ContentSnapshot, type ContentSnapshotOptions, type ContentSource, type CreateCanvasSitemapOptions, type DefineHostComponentsOptions, ELYTRA_HOST_ENV_KEYS, 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 MaterializeSourcePayloadsInput, type MaterializedSourcePayloads, 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 SourcePayloadError, type StaticContentData, assembleLiveSource, canvasPageMetadata, canvasSitemapEntries, collectBindingSourceIds, collectionCacheTag, createAssetImageLoader, createBindingResolver, createCanvasRoute, createCanvasSitemap, createContentSnapshot, createElytraHost, createPreviewDisableRoute, createPreviewRoute, createRevalidateRoute, createStaticContentSource, defaultBindingPayloads, defineHostComponents, docCacheTag, elytraHostImage, evaluatePreviewRequest, evaluateRevalidateRequest, firstBlockFullBleed, isSourcePayloadError, loadCachedEntry, loadLiveContent, materializeSourcePayloads, mergeCacheTags, nextCacheTags, nextImagePrimitive, pathFromSlug, projectSweepTag, resolveCanvasPage, resolveGlobals, resolveHostEnv, resolvePayloadToken, revalidatePayloadSchema, routeCacheTag, safeRedirectTarget, scopeCacheTag, signRevalidateBody, sourcePayloadError
|
|
1745
|
+
export { type AssetImageLoaderOptions, type BindingPayloads, type CacheWrapper, type CacheWrapperOptions, type CachedEntry, type CanvasCacheOptions, type CanvasContentRequest, type CanvasDataSource, type CanvasPageOutcome, type CanvasPayloadContext, type CanvasPayloadsFactory, 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 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 MaterializeSourcePayloadsInput, type MaterializedSourcePayloads, 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 SourcePayloadError, type StaticContentData, assembleLiveSource, bodyItemsOf, canvasPageMetadata, canvasSitemapEntries, collectBindingSourceIds, collectionCacheTag, createAssetImageLoader, createBindingResolver, createCanvasRoute, createCanvasSitemap, createContentSnapshot, createElytraHost, createPreviewDisableRoute, createPreviewRoute, createRevalidateRoute, createStaticContentSource, defaultBindingPayloads, defineHostComponents, docCacheTag, elytraHostImage, evaluatePreviewRequest, evaluateRevalidateRequest, firstBlockFullBleed, isSourcePayloadError, loadCachedEntry, loadLiveContent, materializeSourcePayloads, mergeCacheTags, nextCacheTags, nextImagePrimitive, pathFromSlug, projectSweepTag, resolveCanvasPage, resolveGlobals, resolveHostEnv, resolvePayloadToken, revalidatePayloadSchema, routeCacheTag, safeRedirectTarget, scopeCacheTag, signRevalidateBody, sourcePayloadError };
|