@elytracms/next 0.0.88 → 0.0.91
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 +314 -304
- package/dist/index.js +180 -173
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,34 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import { basePrimitives, getImplementation, registerImplementations, renderDocumentBodyItems, documentBodyItems, ImageManifest, serializeListingQuery, RenderErrorBoundary, documentBodyRichTextValues, documentBodyCompositionNodes, richTextEmbedsInValue, collectReferencedAssetIds, collectRichTextRefsInValue, collectReferencedRelations, collectReferencedListings, collectReferencedRichTextLinks, normalizeImageSource } from '@elytracms/core/runtime-renderer';
|
|
4
|
-
import { isValidElement } from 'react';
|
|
1
|
+
import { registerComponents, renderDocumentBodyItems, documentBodyItems, serializeListingQuery, documentBodyRichTextValues, documentBodyCompositionNodes, richTextEmbedsInValue, collectReferencedAssetIds, collectRichTextRefsInValue, collectReferencedRelations, collectReferencedListings, collectReferencedRichTextLinks, RenderErrorBoundary, nodeListingOf, listingRequestOf } from '@elytracms/core/runtime-renderer';
|
|
2
|
+
import { Fragment as Fragment$1, isValidElement } from 'react';
|
|
5
3
|
import { isBlockSequenceValueShape } from '@elytracms/core/rich-text';
|
|
6
|
-
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
4
|
+
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
7
5
|
import { cacheTags, isDocumentHistory, resolvedAsset, createContentClient, createContentContext } from '@elytracms/core/content';
|
|
8
|
-
import { documentKey, splitLocalePath, hierarchyMount, composePath, localeUrlPath, mergeRouteRecords, fallbackHomeRoutes, presentationBodyFields, splitPath, documentSchema, redirectRecordSchema } from '@elytracms/core/cms-core';
|
|
9
|
-
export { documentSchema, fallbackHomeRoutes, localeConfigSchema, localeUrlPath, mergeRouteRecords, redirectRecordSchema, resolveLocale, routeRecordSchema, splitLocalePath, textDirection } from '@elytracms/core/cms-core';
|
|
10
|
-
import { revalidateTag,
|
|
6
|
+
import { parseLocaleConfig, documentKey, splitLocalePath, hierarchyMount, composePath, localeUrlPath, mergeRouteRecords, fallbackHomeRoutes, presentationBodyFields, splitPath, documentSchema, redirectRecordSchema } from '@elytracms/core/cms-core';
|
|
7
|
+
export { documentSchema, fallbackHomeRoutes, localeConfigSchema, localeUrlPath, mergeRouteRecords, parseLocaleConfig, redirectRecordSchema, resolveLocale, routeRecordSchema, splitLocalePath, textDirection } from '@elytracms/core/cms-core';
|
|
8
|
+
import { revalidateTag, cacheLife, cacheTag } from 'next/cache';
|
|
11
9
|
import { draftMode } from 'next/headers';
|
|
12
10
|
import { redirect, notFound, permanentRedirect } from 'next/navigation';
|
|
13
11
|
import { COMPOSITE_KEY_SEPARATOR } from '@elytracms/core/project-graph';
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
export { z } from 'zod';
|
|
12
|
+
import { verifyRevalidationSignature, readRevalidationBody, REVALIDATE_SIGNATURE_HEADER } from '@elytracms/core/revalidation';
|
|
13
|
+
export { REVALIDATE_SIGNATURE_HEADER, revalidationEventSchema, signRevalidationBody, signedRevalidationRequest } from '@elytracms/core/revalidation';
|
|
17
14
|
import NextImage from 'next/image';
|
|
15
|
+
import { normalizeImageSource } from '@elytracms/components';
|
|
16
|
+
export { defineComponent } from '@elytracms/core/component-registry';
|
|
17
|
+
export { z } from 'zod';
|
|
18
18
|
|
|
19
19
|
// src/components.ts
|
|
20
|
-
function defineHostComponents(components = []
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
const registry = new ComponentRegistry([...base.manifests, ...components.map((c) => c.manifest)]);
|
|
24
|
-
const implementations = {};
|
|
25
|
-
for (const manifest of base.manifests) {
|
|
26
|
-
const implementation = getImplementation(base.implementations, manifest.id);
|
|
27
|
-
if (implementation) implementations[manifest.id] = implementation;
|
|
28
|
-
}
|
|
29
|
-
const host = registerImplementations(components);
|
|
30
|
-
Object.assign(implementations, host.implementations);
|
|
31
|
-
return { registry, implementations, issues: [...registry.issues, ...host.issues] };
|
|
20
|
+
function defineHostComponents(components = []) {
|
|
21
|
+
const { registry, implementations } = registerComponents(components);
|
|
22
|
+
return { registry, implementations };
|
|
32
23
|
}
|
|
33
24
|
function relationResolver(relations) {
|
|
34
25
|
return (ref) => relations[`${ref.collection}:${ref.id}`] ?? null;
|
|
@@ -37,7 +28,22 @@ function linkResolver(links) {
|
|
|
37
28
|
return (ref) => links[`${ref.collection}:${ref.id}`] ?? null;
|
|
38
29
|
}
|
|
39
30
|
function listingResolver(listings) {
|
|
40
|
-
return (query) =>
|
|
31
|
+
return (query) => {
|
|
32
|
+
const baked = listings[serializeListingQuery(query)];
|
|
33
|
+
return Array.isArray(baked) ? baked : null;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function listingFailureOf(node, components, listings) {
|
|
37
|
+
const manifest = components.registry.getManifest(node.componentId);
|
|
38
|
+
if (!manifest) return null;
|
|
39
|
+
const listing = nodeListingOf(node, manifest, components.registry);
|
|
40
|
+
if (!listing || listing.state === "awaiting") return null;
|
|
41
|
+
if (listing.state === "invalid") return listing.messages;
|
|
42
|
+
const baked = listings?.[serializeListingQuery(listing.query)];
|
|
43
|
+
return baked && !Array.isArray(baked) ? baked.errors.map((error) => error.message) : null;
|
|
44
|
+
}
|
|
45
|
+
function InvalidListingNotice(props) {
|
|
46
|
+
return /* @__PURE__ */ jsx("div", { "data-ec-fallback": "invalid-listing", "data-ec-node-id": props.nodeId, role: "note", children: `This listing can't run: ${props.messages.join(" ")}` });
|
|
41
47
|
}
|
|
42
48
|
function assetResolver(assets) {
|
|
43
49
|
const byId = new Map(assets.map((asset) => [asset.id, asset]));
|
|
@@ -49,7 +55,7 @@ function assetResolver(assets) {
|
|
|
49
55
|
height: asset.height,
|
|
50
56
|
alt: asset.alt,
|
|
51
57
|
focalPoint: asset.focalPoint,
|
|
52
|
-
// EC-420: carry the LQIP so a placed
|
|
58
|
+
// EC-420: carry the LQIP so a placed Image blurs up on delivery.
|
|
53
59
|
lqip: asset.lqip,
|
|
54
60
|
meta: asset.meta
|
|
55
61
|
} : null;
|
|
@@ -86,7 +92,15 @@ async function CanvasRenderer(props) {
|
|
|
86
92
|
// so a host that did not opt in degrades a reference link to the visible
|
|
87
93
|
// `unresolved-ref` issue (EC-290) rather than a wrong href.
|
|
88
94
|
...props.links ? { resolveLink: linkResolver(props.links) } : {},
|
|
89
|
-
decorateNode:
|
|
95
|
+
decorateNode: (node, rendered) => {
|
|
96
|
+
const stripped = stripServerErrorBoundary(node, rendered);
|
|
97
|
+
const failure = listingFailureOf(node, components, props.listings);
|
|
98
|
+
if (!failure) return stripped;
|
|
99
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
100
|
+
/* @__PURE__ */ jsx(InvalidListingNotice, { nodeId: node.id, messages: failure }),
|
|
101
|
+
stripped
|
|
102
|
+
] }, node.id);
|
|
103
|
+
}
|
|
90
104
|
};
|
|
91
105
|
const document = result.document;
|
|
92
106
|
if (document) {
|
|
@@ -134,7 +148,7 @@ function mergeCacheTags(first, ...rest) {
|
|
|
134
148
|
});
|
|
135
149
|
}
|
|
136
150
|
function createStaticContentSource(data) {
|
|
137
|
-
const locales = data.locales;
|
|
151
|
+
const locales = parseLocaleConfig(data.locales);
|
|
138
152
|
const routes = data.routes ?? [];
|
|
139
153
|
const redirects = data.redirects ?? [];
|
|
140
154
|
const assetUrl = data.assetUrl ?? ((asset) => `asset://unresolved/${asset.storageKey}`);
|
|
@@ -235,6 +249,7 @@ function canvasWrapContext(result, info) {
|
|
|
235
249
|
function pathFromSlug(slug) {
|
|
236
250
|
return "/" + (slug ?? []).join("/");
|
|
237
251
|
}
|
|
252
|
+
var systemClock = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
238
253
|
function scopeAssetsToPage(allAssets, registry, result, body, resolveRelation) {
|
|
239
254
|
if (allAssets.length === 0) return allAssets;
|
|
240
255
|
const ids = /* @__PURE__ */ new Set();
|
|
@@ -281,27 +296,33 @@ function populatePageRelations(body, client, registry) {
|
|
|
281
296
|
);
|
|
282
297
|
return { relations, docKeys: [...refKeys] };
|
|
283
298
|
}
|
|
284
|
-
function populatePageListings(body, client, registry) {
|
|
285
|
-
if (!registry) return { listings: {}, tags: [] };
|
|
299
|
+
function populatePageListings(body, client, registry, clock) {
|
|
300
|
+
if (!registry) return { listings: {}, tags: [], readsNow: false };
|
|
286
301
|
const tags = [];
|
|
302
|
+
let instant;
|
|
303
|
+
const now = () => instant ??= clock();
|
|
287
304
|
const listings = collectReferencedListings(
|
|
288
305
|
body.nodes,
|
|
289
306
|
registry,
|
|
290
307
|
(query) => {
|
|
291
|
-
const listed = client.listDocuments(query.collection,
|
|
292
|
-
where: query.where,
|
|
293
|
-
...query.sort ? { sort: query.sort } : {},
|
|
294
|
-
...query.limit !== void 0 ? { limit: query.limit } : {}
|
|
295
|
-
});
|
|
308
|
+
const listed = client.listDocuments(query.collection, listDocumentsQueryOf(query, now));
|
|
296
309
|
tags.push(listed.tags);
|
|
297
|
-
return listed.ok ? listed.documents :
|
|
310
|
+
return listed.ok ? listed.documents : { errors: listed.errors };
|
|
298
311
|
},
|
|
299
312
|
// EC-255: reach listings nested inside transcluded reusable content (the same
|
|
300
313
|
// depth-1 populate the relation bake uses), so a grid inside a reusable bakes —
|
|
301
314
|
// and its membership tag is folded in — instead of rendering empty on the host.
|
|
302
315
|
(ref) => client.resolveRelation(ref)
|
|
303
316
|
);
|
|
304
|
-
return { listings, tags };
|
|
317
|
+
return { listings, tags, readsNow: instant !== void 0 };
|
|
318
|
+
}
|
|
319
|
+
function listDocumentsQueryOf(query, now) {
|
|
320
|
+
const request = listingRequestOf(query, now);
|
|
321
|
+
return {
|
|
322
|
+
where: request.where,
|
|
323
|
+
...request.sort ? { sort: request.sort } : {},
|
|
324
|
+
...request.limit !== void 0 ? { limit: request.limit } : {}
|
|
325
|
+
};
|
|
305
326
|
}
|
|
306
327
|
function populatePageLinks(body, client, registry) {
|
|
307
328
|
if (!registry) return { links: {}, docKeys: [] };
|
|
@@ -359,11 +380,11 @@ async function resolveCanvasPage(source, url, perspective, options = {}) {
|
|
|
359
380
|
options.registry
|
|
360
381
|
);
|
|
361
382
|
const relationTags = cacheTags({ docs: relationDocKeys, locale: result.tags.locale });
|
|
362
|
-
const {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
);
|
|
383
|
+
const {
|
|
384
|
+
listings,
|
|
385
|
+
tags: listingTags,
|
|
386
|
+
readsNow
|
|
387
|
+
} = populatePageListings(body, client, options.registry, options.clock ?? systemClock);
|
|
367
388
|
const { links, docKeys: linkDocKeys } = populatePageLinks(body, client, options.registry);
|
|
368
389
|
const linkTags = cacheTags({ docs: linkDocKeys, locale: result.tags.locale });
|
|
369
390
|
return {
|
|
@@ -372,6 +393,7 @@ async function resolveCanvasPage(source, url, perspective, options = {}) {
|
|
|
372
393
|
assets: bakedAssets,
|
|
373
394
|
relations,
|
|
374
395
|
listings,
|
|
396
|
+
readsNow,
|
|
375
397
|
links,
|
|
376
398
|
...presentation ? { presentation } : {},
|
|
377
399
|
tags: mergeCacheTags(
|
|
@@ -414,40 +436,61 @@ function nextCacheTags(tags, scope) {
|
|
|
414
436
|
for (const assetId of tags.assets) all.add(assetCacheTag(assetId, scope));
|
|
415
437
|
return [...all].sort();
|
|
416
438
|
}
|
|
417
|
-
function
|
|
418
|
-
|
|
439
|
+
function revalidationTags(event, project) {
|
|
440
|
+
const { scope } = project;
|
|
441
|
+
const tags = /* @__PURE__ */ new Set();
|
|
442
|
+
switch (event.kind) {
|
|
443
|
+
case "publish":
|
|
444
|
+
case "unpublish":
|
|
445
|
+
case "delete":
|
|
446
|
+
tags.add(docCacheTag(`${event.collection}:${event.id}`, scope));
|
|
447
|
+
for (const locale of project.locales) tags.add(collectionCacheTag(event.collection, locale, scope));
|
|
448
|
+
break;
|
|
449
|
+
case "redirects":
|
|
450
|
+
tags.add(projectSweepTag(scope));
|
|
451
|
+
break;
|
|
452
|
+
case "asset":
|
|
453
|
+
tags.add(assetCacheTag(event.id, scope));
|
|
454
|
+
tags.add(projectSweepTag(scope));
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
return [...tags].sort();
|
|
419
458
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
459
|
+
function canvasCacheKey({ scope, mode, url }) {
|
|
460
|
+
return [scope ?? "", mode ?? "", url];
|
|
461
|
+
}
|
|
462
|
+
var CANVAS_CACHE_LIFE = { revalidate: Infinity, expire: Infinity };
|
|
463
|
+
var CANVAS_DEV_CACHE_LIFE = { revalidate: 1, expire: 300 };
|
|
464
|
+
var CANVAS_CLOCK_CACHE_LIFE = { revalidate: 60, expire: Infinity };
|
|
465
|
+
function canvasEntryLife(nodeEnv, readsClock = false) {
|
|
466
|
+
if (nodeEnv === "development") return CANVAS_DEV_CACHE_LIFE;
|
|
467
|
+
return readsClock ? CANVAS_CLOCK_CACHE_LIFE : CANVAS_CACHE_LIFE;
|
|
468
|
+
}
|
|
469
|
+
var NEXT_CACHE_TAG_CALL_LIMIT = 128;
|
|
470
|
+
function entryTagCalls(tags, scope) {
|
|
471
|
+
const sweep = projectSweepTag(scope);
|
|
472
|
+
const rest = nextCacheTags(tags, scope).filter((tag) => tag !== sweep);
|
|
473
|
+
const calls = [[sweep]];
|
|
474
|
+
for (let start = 0; start < rest.length; start += NEXT_CACHE_TAG_CALL_LIMIT) {
|
|
475
|
+
calls.push(rest.slice(start, start + NEXT_CACHE_TAG_CALL_LIMIT));
|
|
424
476
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
return await probe();
|
|
441
|
-
} catch (error) {
|
|
442
|
-
if (!(error instanceof CacheDiscoveryBail)) throw error;
|
|
477
|
+
return calls;
|
|
478
|
+
}
|
|
479
|
+
var canvasResolvers = /* @__PURE__ */ new Map();
|
|
480
|
+
function resolverKey(scope, mode) {
|
|
481
|
+
return JSON.stringify([scope, mode]);
|
|
482
|
+
}
|
|
483
|
+
function registerCanvasResolver(scope, mode, resolve) {
|
|
484
|
+
canvasResolvers.set(resolverKey(scope, mode), resolve);
|
|
485
|
+
}
|
|
486
|
+
function canvasResolverFor(scope, mode) {
|
|
487
|
+
const resolve = canvasResolvers.get(resolverKey(scope, mode));
|
|
488
|
+
if (!resolve) {
|
|
489
|
+
throw new Error(
|
|
490
|
+
`Elytra delivery cache: no canvas route registered for scope "${scope}" and content mode "${mode}" in this server process \u2014 the cached route function has no loader to run. This is a wiring defect: create the route with createCanvasRoute / elytra.canvasRoute() before its Page renders.`
|
|
491
|
+
);
|
|
443
492
|
}
|
|
444
|
-
|
|
445
|
-
const write = cache(
|
|
446
|
-
passthrough(async () => entry),
|
|
447
|
-
keyParts,
|
|
448
|
-
{ tags: entry.tags, revalidate: false }
|
|
449
|
-
);
|
|
450
|
-
return write();
|
|
493
|
+
return resolve;
|
|
451
494
|
}
|
|
452
495
|
|
|
453
496
|
// src/metadata.ts
|
|
@@ -510,6 +553,8 @@ function mergeMetadataOverParent(parent, page) {
|
|
|
510
553
|
function isPlainObject(value) {
|
|
511
554
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
512
555
|
}
|
|
556
|
+
var CANVAS_VALIDATION_SLUG = ["_elytra"];
|
|
557
|
+
var CANVAS_VALIDATION_PATH = pathFromSlug([...CANVAS_VALIDATION_SLUG]);
|
|
513
558
|
function defaultMetadata(result, assets) {
|
|
514
559
|
return canvasPageMetadata(result, assets);
|
|
515
560
|
}
|
|
@@ -517,33 +562,35 @@ async function requestPerspective() {
|
|
|
517
562
|
const { isEnabled } = await draftMode();
|
|
518
563
|
return isEnabled ? "draft" : "published";
|
|
519
564
|
}
|
|
520
|
-
|
|
565
|
+
async function cachedCanvasOutcome(scope, mode, url) {
|
|
566
|
+
"use cache";
|
|
567
|
+
const outcome = await canvasResolverFor(scope, mode)(url);
|
|
568
|
+
cacheLife(canvasEntryLife(process.env.NODE_ENV, outcome.kind === "render" && outcome.readsNow));
|
|
569
|
+
for (const tags of entryTagCalls(outcome.tags, scope)) cacheTag(...tags);
|
|
570
|
+
return outcome;
|
|
571
|
+
}
|
|
521
572
|
function createCanvasRoute(options) {
|
|
573
|
+
const resolveFresh = async (url, perspective) => {
|
|
574
|
+
const source = await options.loadContent({ perspective });
|
|
575
|
+
return resolveCanvasPage(source, url, perspective, {
|
|
576
|
+
// EC-205: page-scope the baked assets to what the page actually uses.
|
|
577
|
+
registry: options.components.registry
|
|
578
|
+
});
|
|
579
|
+
};
|
|
580
|
+
const cache = options.cache;
|
|
581
|
+
const entryKey = (url) => canvasCacheKey({ scope: cache?.scope, mode: cache?.mode, url });
|
|
582
|
+
if (cache) {
|
|
583
|
+
const [scope, mode] = entryKey("");
|
|
584
|
+
registerCanvasResolver(scope, mode, (url) => resolveFresh(url, "published"));
|
|
585
|
+
}
|
|
522
586
|
const loadOutcome = async (url, perspective) => {
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
return resolveCanvasPage(source, url, perspective, {
|
|
526
|
-
// EC-205: page-scope the baked assets to what the page actually uses.
|
|
527
|
-
registry: options.components.registry
|
|
528
|
-
});
|
|
529
|
-
};
|
|
530
|
-
const devBypass = process.env.NODE_ENV === "development";
|
|
531
|
-
if (!options.cache || perspective !== "published" || devBypass) return resolveFresh();
|
|
532
|
-
const scope = options.cache.scope;
|
|
533
|
-
const entry = await loadCachedEntry(
|
|
534
|
-
nextCacheWrapper,
|
|
535
|
-
canvasCacheKey({ scope, mode: options.cache.mode, url }),
|
|
536
|
-
[projectSweepTag(scope)],
|
|
537
|
-
async () => {
|
|
538
|
-
const outcome = await resolveFresh();
|
|
539
|
-
return { value: outcome, tags: nextCacheTags(outcome.tags, scope) };
|
|
540
|
-
}
|
|
541
|
-
);
|
|
542
|
-
return entry.value;
|
|
587
|
+
if (!cache || perspective !== "published") return resolveFresh(url, perspective);
|
|
588
|
+
return cachedCanvasOutcome(...entryKey(url));
|
|
543
589
|
};
|
|
544
590
|
const Page = async (props) => {
|
|
545
591
|
const { slug } = await props.params;
|
|
546
592
|
const url = pathFromSlug(slug);
|
|
593
|
+
if (url === CANVAS_VALIDATION_PATH) return notFound();
|
|
547
594
|
const perspective = await requestPerspective();
|
|
548
595
|
const outcome = await loadOutcome(url, perspective);
|
|
549
596
|
options.onTags?.(outcome.tags, { url, perspective });
|
|
@@ -573,8 +620,9 @@ function createCanvasRoute(options) {
|
|
|
573
620
|
const pageMetadata = async (props) => {
|
|
574
621
|
const { slug } = await props.params;
|
|
575
622
|
const url = pathFromSlug(slug);
|
|
623
|
+
if (url === CANVAS_VALIDATION_PATH) return {};
|
|
576
624
|
const perspective = await requestPerspective();
|
|
577
|
-
if (
|
|
625
|
+
if (cache && perspective === "published") {
|
|
578
626
|
const outcome2 = await loadOutcome(url, perspective);
|
|
579
627
|
if (outcome2.kind !== "render") return {};
|
|
580
628
|
return options.metadata ? options.metadata(outcome2.result, { url, perspective }) : defaultMetadata(outcome2.result, outcome2.assets);
|
|
@@ -585,10 +633,13 @@ function createCanvasRoute(options) {
|
|
|
585
633
|
return options.metadata ? options.metadata(outcome.result, { url, perspective }) : defaultMetadata(outcome.result, outcome.assets);
|
|
586
634
|
};
|
|
587
635
|
const generateMetadata = async (props, parent) => {
|
|
588
|
-
const [page, resolvedParent] = await Promise.all([pageMetadata(props), parent
|
|
636
|
+
const [page, resolvedParent] = await Promise.all([pageMetadata(props), parent]);
|
|
589
637
|
return mergeMetadataOverParent(resolvedParent, page);
|
|
590
638
|
};
|
|
591
|
-
|
|
639
|
+
const generateStaticParams = async () => [
|
|
640
|
+
{ slug: [...CANVAS_VALIDATION_SLUG] }
|
|
641
|
+
];
|
|
642
|
+
return { Page, generateMetadata, generateStaticParams };
|
|
592
643
|
}
|
|
593
644
|
function paramNamesOf(pattern) {
|
|
594
645
|
return splitPath(pattern).filter((segment) => segment.startsWith(":")).map((segment) => segment.slice(1));
|
|
@@ -719,71 +770,33 @@ function createCanvasSitemap(options) {
|
|
|
719
770
|
return canvasSitemapEntries(source, options).entries;
|
|
720
771
|
};
|
|
721
772
|
}
|
|
722
|
-
|
|
723
|
-
var revalidatePayloadSchema = z.object({
|
|
724
|
-
projectId: z.string().min(1),
|
|
725
|
-
/** The operation that fired, e.g. `documents.setState`, `publishing.publish`. */
|
|
726
|
-
event: z.string().min(1),
|
|
727
|
-
/** Fully-formatted Next tag strings (already scoped) — revalidated verbatim. */
|
|
728
|
-
tags: z.array(z.string().min(1)),
|
|
729
|
-
/** ISO timestamp of emission. Informational in v1 (no replay window). */
|
|
730
|
-
timestamp: z.string().min(1)
|
|
731
|
-
});
|
|
732
|
-
function signRevalidateBody(rawBody, secret) {
|
|
733
|
-
return `sha256=${createHmac("sha256", secret).update(rawBody, "utf8").digest("hex")}`;
|
|
734
|
-
}
|
|
735
|
-
var SIGNATURE_PREFIX = "sha256=";
|
|
736
|
-
var HEX_PATTERN = /^[0-9a-f]+$/;
|
|
737
|
-
function evaluateRevalidateRequest(rawBody, signature, secret) {
|
|
773
|
+
async function evaluateRevalidateRequest(rawBody, signature, secret) {
|
|
738
774
|
if (secret === void 0 || secret.length === 0) {
|
|
739
775
|
return { ok: false, status: 401, message: "Revalidation webhook is not configured." };
|
|
740
776
|
}
|
|
741
|
-
if (signature
|
|
777
|
+
if (typeof signature !== "string" || signature.length === 0) {
|
|
742
778
|
return { ok: false, status: 401, message: "Missing or malformed webhook signature." };
|
|
743
779
|
}
|
|
744
|
-
|
|
745
|
-
const expectedHex = createHmac("sha256", secret).update(rawBody, "utf8").digest("hex");
|
|
746
|
-
if (providedHex.length !== expectedHex.length || !HEX_PATTERN.test(providedHex) || !timingSafeEqual(Buffer.from(providedHex, "hex"), Buffer.from(expectedHex, "hex"))) {
|
|
780
|
+
if (!await verifyRevalidationSignature(rawBody, signature, secret)) {
|
|
747
781
|
return { ok: false, status: 401, message: "Invalid webhook signature." };
|
|
748
782
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
} catch {
|
|
753
|
-
return { ok: false, status: 400, message: "Webhook body is not valid JSON." };
|
|
754
|
-
}
|
|
755
|
-
const payload = revalidatePayloadSchema.safeParse(parsed);
|
|
756
|
-
if (!payload.success) {
|
|
757
|
-
return { ok: false, status: 400, message: "Webhook payload has an invalid shape." };
|
|
758
|
-
}
|
|
759
|
-
return { ok: true, payload: payload.data };
|
|
760
|
-
}
|
|
761
|
-
function corsHeaders(allowOrigin) {
|
|
762
|
-
if (allowOrigin === void 0 || allowOrigin.length === 0) return {};
|
|
763
|
-
return {
|
|
764
|
-
"access-control-allow-origin": allowOrigin,
|
|
765
|
-
"access-control-allow-methods": "POST, OPTIONS",
|
|
766
|
-
"access-control-allow-headers": `content-type, ${REVALIDATE_SIGNATURE_HEADER}`
|
|
767
|
-
};
|
|
783
|
+
const read = readRevalidationBody(rawBody);
|
|
784
|
+
if (!read.ok) return { ok: false, status: 400, message: read.message };
|
|
785
|
+
return { ok: true, event: read.event };
|
|
768
786
|
}
|
|
769
787
|
function createRevalidateRoute(options) {
|
|
770
|
-
const
|
|
788
|
+
const project = { scope: options.scope, locales: [...options.locales] };
|
|
771
789
|
return {
|
|
772
790
|
async POST(request) {
|
|
773
791
|
const rawBody = await request.text();
|
|
774
792
|
const signature = request.headers.get(REVALIDATE_SIGNATURE_HEADER);
|
|
775
|
-
const evaluation = evaluateRevalidateRequest(rawBody, signature, options.secret);
|
|
793
|
+
const evaluation = await evaluateRevalidateRequest(rawBody, signature, options.secret);
|
|
776
794
|
if (!evaluation.ok) {
|
|
777
|
-
return Response.json(
|
|
778
|
-
{ error: evaluation.message },
|
|
779
|
-
{ status: evaluation.status, headers: cors }
|
|
780
|
-
);
|
|
795
|
+
return Response.json({ error: evaluation.message }, { status: evaluation.status });
|
|
781
796
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
async OPTIONS() {
|
|
786
|
-
return new Response(null, { status: 204, headers: cors });
|
|
797
|
+
const tags = revalidationTags(evaluation.event, project);
|
|
798
|
+
for (const tag of tags) revalidateTag(tag, { expire: 0 });
|
|
799
|
+
return Response.json({ revalidated: tags });
|
|
787
800
|
}
|
|
788
801
|
};
|
|
789
802
|
}
|
|
@@ -845,8 +858,8 @@ function createAssetImageLoader(options = {}) {
|
|
|
845
858
|
return `${src}${separator}${widthParam}=${width}&${qualityParam}=${quality ?? defaultQuality}`;
|
|
846
859
|
};
|
|
847
860
|
}
|
|
848
|
-
function
|
|
849
|
-
return function
|
|
861
|
+
function createNextImage(options) {
|
|
862
|
+
return function ElytraNextImage(props) {
|
|
850
863
|
const { url, width, height, alt, focalPoint, lqip } = normalizeImageSource(props);
|
|
851
864
|
if (url.length === 0) return null;
|
|
852
865
|
return /* @__PURE__ */ jsx(
|
|
@@ -861,11 +874,12 @@ function createNextImagePrimitive(options) {
|
|
|
861
874
|
);
|
|
862
875
|
};
|
|
863
876
|
}
|
|
864
|
-
function
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
877
|
+
function withNextImage(components, options = {}) {
|
|
878
|
+
const list = [...components ?? []];
|
|
879
|
+
const image = registerComponents(list).registry.roleComponent("image");
|
|
880
|
+
if (!image) return list;
|
|
881
|
+
const implementation = createNextImage(options);
|
|
882
|
+
return list.map((entry) => entry.manifest === image ? { manifest: entry.manifest, implementation } : entry);
|
|
869
883
|
}
|
|
870
884
|
function elytraHostImage(options = {}) {
|
|
871
885
|
return ({ asset, alt, className, priority, sizes, loading }) => /* @__PURE__ */ jsx(
|
|
@@ -894,8 +908,7 @@ var ELYTRA_HOST_ENV_KEYS = {
|
|
|
894
908
|
convexUrl: "ELYTRA_CONVEX_URL",
|
|
895
909
|
projectId: "ELYTRA_PROJECT_ID",
|
|
896
910
|
draftToken: "DRAFT_PREVIEW_TOKEN",
|
|
897
|
-
revalidateSecret: "REVALIDATE_SECRET"
|
|
898
|
-
studioOrigin: "STUDIO_ORIGIN"
|
|
911
|
+
revalidateSecret: "REVALIDATE_SECRET"
|
|
899
912
|
};
|
|
900
913
|
function cleaned(value) {
|
|
901
914
|
const trimmed = value?.trim();
|
|
@@ -1189,17 +1202,7 @@ function createElytraHost(options) {
|
|
|
1189
1202
|
}
|
|
1190
1203
|
const contentMode = live ? "live" : options.fallbackContent ? "fallback" : "empty";
|
|
1191
1204
|
const contentScope = live ? env.projectId : options.slug;
|
|
1192
|
-
const components = defineHostComponents([
|
|
1193
|
-
// Batteries included (EC-368 image defaults): graph `base.Image` nodes render
|
|
1194
|
-
// through next/image — resized variants via the host's own optimizer. The
|
|
1195
|
-
// canonical manifest stays (first registration wins in defineHostComponents).
|
|
1196
|
-
...options.image === false ? [] : [nextImagePrimitive(options.image ?? {})],
|
|
1197
|
-
// EC-539: the option is the CONFIG's entry shape (`implementation: unknown`)
|
|
1198
|
-
// so the recommended `{ ...site }` spread compiles; EC-541: registration
|
|
1199
|
-
// takes that shape too and checks each implementation at runtime (see the
|
|
1200
|
-
// option's doc comment) — no narrowing cast here.
|
|
1201
|
-
...options.components ?? []
|
|
1202
|
-
]);
|
|
1205
|
+
const components = defineHostComponents(options.components ?? []);
|
|
1203
1206
|
const emptySource = createStaticContentSource({
|
|
1204
1207
|
projectId: contentScope,
|
|
1205
1208
|
locales: options.locales,
|
|
@@ -1241,11 +1244,10 @@ function createElytraHost(options) {
|
|
|
1241
1244
|
return createCanvasRoute({
|
|
1242
1245
|
loadContent,
|
|
1243
1246
|
components,
|
|
1244
|
-
// Tag scope =
|
|
1245
|
-
// tags
|
|
1246
|
-
// EC-543: the mode is in the entry key so no world ever reads another's
|
|
1247
|
-
|
|
1248
|
-
...cache === false ? {} : { cache: { mode: contentMode, ...cache ?? { scope: contentScope } } },
|
|
1247
|
+
// Tag scope = `contentScope`, the SAME value `revalidateRoute` derives its
|
|
1248
|
+
// invalidation tags with (EC-579) — one scope, so they cannot disagree.
|
|
1249
|
+
// EC-543: the mode is in the entry key so no world ever reads another's entries.
|
|
1250
|
+
...cache === false ? {} : { cache: { mode: contentMode, scope: contentScope } },
|
|
1249
1251
|
...rest
|
|
1250
1252
|
});
|
|
1251
1253
|
};
|
|
@@ -1253,7 +1255,11 @@ function createElytraHost(options) {
|
|
|
1253
1255
|
// An unset secret keeps the endpoint CLOSED (401) — the kit never ships a
|
|
1254
1256
|
// default secret (an auth-shaped no-op is worse than an explicit gap).
|
|
1255
1257
|
secret: "secret" in overrides ? overrides.secret : env.revalidateSecret,
|
|
1256
|
-
|
|
1258
|
+
// EC-579: the receiver's OWN project — the scope its cache entries are written
|
|
1259
|
+
// under (`canvasRoute` above) and every locale it serves. The emitter sends only
|
|
1260
|
+
// the event, so this is the one place the scope and locales are read for it.
|
|
1261
|
+
scope: contentScope,
|
|
1262
|
+
locales: options.locales.locales
|
|
1257
1263
|
});
|
|
1258
1264
|
const previewRoute = () => createPreviewRoute({ token: env.draftToken });
|
|
1259
1265
|
const previewDisableRoute = () => createPreviewDisableRoute();
|
|
@@ -1267,6 +1273,7 @@ function createElytraHost(options) {
|
|
|
1267
1273
|
return firstBlockFullBleed(resolved.document, components.registry);
|
|
1268
1274
|
};
|
|
1269
1275
|
return {
|
|
1276
|
+
collections: options.collections,
|
|
1270
1277
|
contentScope,
|
|
1271
1278
|
contentMode,
|
|
1272
1279
|
env,
|
|
@@ -1284,6 +1291,6 @@ function createElytraHost(options) {
|
|
|
1284
1291
|
}
|
|
1285
1292
|
var PACKAGE = "@elytracms/next";
|
|
1286
1293
|
|
|
1287
|
-
export { CanvasRenderer, ELYTRA_HOST_ENV_KEYS, ElytraImage, PACKAGE,
|
|
1294
|
+
export { CANVAS_VALIDATION_SLUG, CanvasRenderer, ELYTRA_HOST_ENV_KEYS, ElytraImage, PACKAGE, assembleLiveSource, bodyItemsOf, canvasPageMetadata, canvasSitemapEntries, collectionCacheTag, createAssetImageLoader, createCanvasRoute, createCanvasSitemap, createContentSnapshot, createElytraHost, createPreviewDisableRoute, createPreviewRoute, createRevalidateRoute, createStaticContentSource, defineHostComponents, docCacheTag, elytraHostImage, evaluatePreviewRequest, evaluateRevalidateRequest, firstBlockFullBleed, loadLiveContent, mergeCacheTags, mergeMetadataOverParent, nextCacheTags, openGraphLocale, pathFromSlug, projectSweepTag, resolveCanvasPage, resolveGlobals, resolveHostEnv, revalidationTags, routeCacheTag, safeRedirectTarget, scopeCacheTag, withNextImage };
|
|
1288
1295
|
//# sourceMappingURL=index.js.map
|
|
1289
1296
|
//# sourceMappingURL=index.js.map
|