@elytracms/next 0.0.91 → 0.0.93
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/host-image.d.ts +14 -18
- package/dist/host-image.js.map +1 -1
- package/dist/index.d.ts +385 -233
- package/dist/index.js +291 -314
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { registerComponents, renderDocumentBodyItems, documentBodyItems, serializeListingQuery, documentBodyRichTextValues, documentBodyCompositionNodes, richTextEmbedsInValue, collectReferencedAssetIds, collectRichTextRefsInValue, collectReferencedRelations, collectReferencedListings, collectReferencedRichTextLinks,
|
|
2
|
-
import { Fragment as Fragment$1, isValidElement } from 'react';
|
|
1
|
+
import { registerComponents, createRenderContext, renderDocumentBodyItems, documentBodyItems, serializeListingQuery, documentBodyRichTextValues, documentBodyCompositionNodes, richTextEmbedsInValue, collectReferencedAssetIds, collectRichTextRefsInValue, collectReferencedRelations, collectReferencedListings, collectReferencedRichTextLinks, listingRequestOf } from '@elytracms/core/runtime-renderer';
|
|
3
2
|
import { isBlockSequenceValueShape } from '@elytracms/core/rich-text';
|
|
4
|
-
import { jsx, Fragment
|
|
5
|
-
import { cacheTags, isDocumentHistory, resolvedAsset, createContentClient, createContentContext } from '@elytracms/core/content';
|
|
6
|
-
import { parseLocaleConfig, documentKey, splitLocalePath,
|
|
7
|
-
export {
|
|
8
|
-
import { revalidateTag,
|
|
3
|
+
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
4
|
+
import { cacheTags, isDocumentHistory, resolvedAsset, parseDeliveredDocuments, createContentClient, createContentContext } from '@elytracms/core/content';
|
|
5
|
+
import { DEFAULT_SEO_FIELD, parseLocaleConfig, documentKey, liveRedirectRecords, SEO_SUBFIELDS, splitLocalePath, DRAFT_PERSPECTIVE, splitPath, localeUrlPath, variantOf, DRAFT_VARIANT, createRouter, presentationBodyFields, redirectRecordSchema } from '@elytracms/core/cms-core';
|
|
6
|
+
export { DRAFT_PERSPECTIVE, documentSchema, localeConfigSchema, localeUrlPath, parseLocaleConfig, redirectRecordSchema, resolveLocale, routeRecordSchema, splitLocalePath, textDirection, variantOf } from '@elytracms/core/cms-core';
|
|
7
|
+
import { revalidateTag, cacheTag } from 'next/cache';
|
|
9
8
|
import { draftMode } from 'next/headers';
|
|
10
9
|
import { redirect, notFound, permanentRedirect } from 'next/navigation';
|
|
11
10
|
import { COMPOSITE_KEY_SEPARATOR } from '@elytracms/core/project-graph';
|
|
@@ -13,6 +12,7 @@ import { verifyRevalidationSignature, readRevalidationBody, REVALIDATE_SIGNATURE
|
|
|
13
12
|
export { REVALIDATE_SIGNATURE_HEADER, revalidationEventSchema, signRevalidationBody, signedRevalidationRequest } from '@elytracms/core/revalidation';
|
|
14
13
|
import NextImage from 'next/image';
|
|
15
14
|
import { normalizeImageSource } from '@elytracms/components';
|
|
15
|
+
import { projectFrontendUrl, resolveProject } from '@elytracms/core/studio-core';
|
|
16
16
|
export { defineComponent } from '@elytracms/core/component-registry';
|
|
17
17
|
export { z } from 'zod';
|
|
18
18
|
|
|
@@ -33,33 +33,15 @@ function listingResolver(listings) {
|
|
|
33
33
|
return Array.isArray(baked) ? baked : null;
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
|
-
function
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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(" ")}` });
|
|
36
|
+
function bakedListingFailure(listings) {
|
|
37
|
+
return (query) => {
|
|
38
|
+
const baked = listings[serializeListingQuery(query)];
|
|
39
|
+
return baked && !Array.isArray(baked) ? baked.errors.map((error) => error.message) : null;
|
|
40
|
+
};
|
|
47
41
|
}
|
|
48
42
|
function assetResolver(assets) {
|
|
49
43
|
const byId = new Map(assets.map((asset) => [asset.id, asset]));
|
|
50
|
-
return (id) =>
|
|
51
|
-
const asset = byId.get(id);
|
|
52
|
-
return asset ? {
|
|
53
|
-
url: asset.url,
|
|
54
|
-
width: asset.width,
|
|
55
|
-
height: asset.height,
|
|
56
|
-
alt: asset.alt,
|
|
57
|
-
focalPoint: asset.focalPoint,
|
|
58
|
-
// EC-420: carry the LQIP so a placed Image blurs up on delivery.
|
|
59
|
-
lqip: asset.lqip,
|
|
60
|
-
meta: asset.meta
|
|
61
|
-
} : null;
|
|
62
|
-
};
|
|
44
|
+
return (id) => byId.get(id) ?? null;
|
|
63
45
|
}
|
|
64
46
|
function UnresolvedPageFallback(props) {
|
|
65
47
|
return /* @__PURE__ */ jsx("div", { "data-ec-fallback": "unresolved-page", "data-ec-status": props.status, children: `This URL did not resolve to a page (status: ${props.status}).` });
|
|
@@ -67,41 +49,28 @@ function UnresolvedPageFallback(props) {
|
|
|
67
49
|
function MissingDocumentFallback() {
|
|
68
50
|
return /* @__PURE__ */ jsx("div", { "data-ec-fallback": "missing-document", children: `Route resolved but carried no page document to render.` });
|
|
69
51
|
}
|
|
70
|
-
function stripServerErrorBoundary(_node, rendered) {
|
|
71
|
-
if (isValidElement(rendered) && rendered.type === RenderErrorBoundary) {
|
|
72
|
-
return rendered.props.children ?? null;
|
|
73
|
-
}
|
|
74
|
-
return rendered;
|
|
75
|
-
}
|
|
76
52
|
async function CanvasRenderer(props) {
|
|
77
53
|
const { result, components } = props;
|
|
78
54
|
if (result.status !== "ok") {
|
|
79
55
|
return /* @__PURE__ */ jsx(UnresolvedPageFallback, { status: result.status });
|
|
80
56
|
}
|
|
81
|
-
const ctx = {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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);
|
|
57
|
+
const ctx = createRenderContext({
|
|
58
|
+
components,
|
|
59
|
+
mode: "delivery",
|
|
60
|
+
source: {
|
|
61
|
+
resolveAsset: assetResolver(props.assets ?? []),
|
|
62
|
+
// EC-254: wire relation population only when the host baked the populated
|
|
63
|
+
// targets, so a host that did not opt in keeps the raw-ref passthrough.
|
|
64
|
+
...props.relations ? { resolveRelation: relationResolver(props.relations) } : {},
|
|
65
|
+
// EC-255: wire the listing resolver only when the host baked the query results,
|
|
66
|
+
// so a host that did not opt in keeps listing props empty (the block's own state).
|
|
67
|
+
...props.listings ? { resolveListing: listingResolver(props.listings), listingFailure: bakedListingFailure(props.listings) } : {},
|
|
68
|
+
// EC-291: wire the rich-text link resolver only when the host baked the link map,
|
|
69
|
+
// so a host that did not opt in degrades a reference link to the visible
|
|
70
|
+
// `unresolved-ref` issue (EC-290) rather than a wrong href.
|
|
71
|
+
...props.links ? { resolveLink: linkResolver(props.links) } : {}
|
|
103
72
|
}
|
|
104
|
-
};
|
|
73
|
+
});
|
|
105
74
|
const document = result.document;
|
|
106
75
|
if (document) {
|
|
107
76
|
return /* @__PURE__ */ jsx(Fragment, { children: renderDocumentBodyItems(bodyItemsOf(document, props.presentation), ctx) });
|
|
@@ -180,7 +149,8 @@ function createStaticContentSource(data) {
|
|
|
180
149
|
locales
|
|
181
150
|
}),
|
|
182
151
|
routes,
|
|
183
|
-
redirects
|
|
152
|
+
redirects,
|
|
153
|
+
...data.sourceIssues ? { sourceIssues: data.sourceIssues } : {}
|
|
184
154
|
});
|
|
185
155
|
return {
|
|
186
156
|
projectId: data.projectId,
|
|
@@ -200,17 +170,11 @@ function createStaticContentSource(data) {
|
|
|
200
170
|
};
|
|
201
171
|
}
|
|
202
172
|
async function createContentSnapshot(adapter, projectId, options) {
|
|
203
|
-
const schema = await adapter.cms.findSchema(projectId);
|
|
204
173
|
const documentRecords = await adapter.cms.listDocuments(projectId);
|
|
205
174
|
const versionRecords = await adapter.cms.listAllDocumentVersions(projectId);
|
|
206
175
|
const assetRecords = await adapter.assets.listAssets(projectId);
|
|
207
176
|
const redirectRecords = await adapter.redirects.listRedirects(projectId);
|
|
208
|
-
const redirects = redirectRecords
|
|
209
|
-
id: record2.id,
|
|
210
|
-
from: record2.from,
|
|
211
|
-
to: record2.to,
|
|
212
|
-
permanent: record2.permanent
|
|
213
|
-
}));
|
|
177
|
+
const redirects = liveRedirectRecords(redirectRecords);
|
|
214
178
|
const versionsByKey = /* @__PURE__ */ new Map();
|
|
215
179
|
for (const record2 of versionRecords) {
|
|
216
180
|
const key = `${record2.collection}:${record2.docId}`;
|
|
@@ -231,12 +195,85 @@ async function createContentSnapshot(adapter, projectId, options) {
|
|
|
231
195
|
locales: options.locales,
|
|
232
196
|
...options.routes ? { routes: options.routes } : {},
|
|
233
197
|
redirects: options.redirects ? [...options.redirects] : redirects,
|
|
234
|
-
|
|
198
|
+
collections: options.collections,
|
|
235
199
|
documents,
|
|
236
200
|
assets: assetRecords,
|
|
237
201
|
assetUrl: (asset) => adapter.blobs.url(asset.storageKey)
|
|
238
202
|
});
|
|
239
203
|
}
|
|
204
|
+
function seoContextOf(result, assets = []) {
|
|
205
|
+
return {
|
|
206
|
+
seoField: result.seoField ?? null,
|
|
207
|
+
title: result.title ?? null,
|
|
208
|
+
locale: result.tags.locale,
|
|
209
|
+
assets
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function str(value) {
|
|
213
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
214
|
+
}
|
|
215
|
+
function resolveSeoImage(value, assets) {
|
|
216
|
+
if (value && typeof value === "object") {
|
|
217
|
+
const url = value.url;
|
|
218
|
+
return typeof url === "string" && url.length > 0 ? url : null;
|
|
219
|
+
}
|
|
220
|
+
if (typeof value !== "string" || value.length === 0) return null;
|
|
221
|
+
return assets.find((asset) => asset.id === value)?.url ?? null;
|
|
222
|
+
}
|
|
223
|
+
function openGraphLocale(locale) {
|
|
224
|
+
return locale.includes("-") ? locale.replace(/-/g, "_") : null;
|
|
225
|
+
}
|
|
226
|
+
function seoObjectOf(doc, seoField = DEFAULT_SEO_FIELD) {
|
|
227
|
+
if (!doc || seoField === null) return {};
|
|
228
|
+
const value = doc[seoField ?? DEFAULT_SEO_FIELD];
|
|
229
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
230
|
+
}
|
|
231
|
+
function seoMetadata(doc, ctx = {}) {
|
|
232
|
+
const seo = seoObjectOf(doc, ctx.seoField);
|
|
233
|
+
const assets = ctx.assets ?? [];
|
|
234
|
+
const seoTitle = str(seo[SEO_SUBFIELDS.title]);
|
|
235
|
+
const description = str(seo[SEO_SUBFIELDS.description]);
|
|
236
|
+
const ogTitle = str(seo[SEO_SUBFIELDS.ogTitle]);
|
|
237
|
+
const ogDescription = str(seo[SEO_SUBFIELDS.ogDescription]);
|
|
238
|
+
const image = resolveSeoImage(seo[SEO_SUBFIELDS.image], assets);
|
|
239
|
+
const noindex = seo[SEO_SUBFIELDS.noindex] === true;
|
|
240
|
+
const canonical = str(seo[SEO_SUBFIELDS.canonical]);
|
|
241
|
+
const docTitle = ctx.title !== void 0 ? str(ctx.title) : str(doc?.["title"]) ?? str(doc?.["name"]);
|
|
242
|
+
const title = seoTitle ?? docTitle;
|
|
243
|
+
const metadata = {};
|
|
244
|
+
if (title !== null) metadata.title = title;
|
|
245
|
+
if (description) metadata.description = description;
|
|
246
|
+
if (doc) {
|
|
247
|
+
const effectiveOgTitle = ogTitle ?? title;
|
|
248
|
+
const effectiveOgDescription = ogDescription ?? description;
|
|
249
|
+
const ogLocale = ctx.locale !== void 0 ? openGraphLocale(ctx.locale) : null;
|
|
250
|
+
metadata.openGraph = {
|
|
251
|
+
...effectiveOgTitle !== null ? { title: effectiveOgTitle } : {},
|
|
252
|
+
...effectiveOgDescription ? { description: effectiveOgDescription } : {},
|
|
253
|
+
...image ? { images: [{ url: image }] } : {},
|
|
254
|
+
...ogLocale !== null ? { locale: ogLocale } : {}
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
if (noindex) metadata.robots = { index: false };
|
|
258
|
+
if (canonical) metadata.alternates = { canonical };
|
|
259
|
+
return metadata;
|
|
260
|
+
}
|
|
261
|
+
function mergeMetadataOverParent(parent, page) {
|
|
262
|
+
if (!parent) return page;
|
|
263
|
+
const merged = { ...page };
|
|
264
|
+
for (const [key, pageValue] of Object.entries(page)) {
|
|
265
|
+
const parentValue = parent[key];
|
|
266
|
+
if (isPlainObject(pageValue) && isPlainObject(parentValue)) {
|
|
267
|
+
merged[key] = { ...parentValue, ...pageValue };
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return merged;
|
|
271
|
+
}
|
|
272
|
+
function isPlainObject(value) {
|
|
273
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// src/route-core.ts
|
|
240
277
|
function canvasWrapContext(result, info) {
|
|
241
278
|
const collection = result.document?.collection;
|
|
242
279
|
return {
|
|
@@ -260,7 +297,7 @@ function scopeAssetsToPage(allAssets, registry, result, body, resolveRelation) {
|
|
|
260
297
|
for (const value of body.richTexts) {
|
|
261
298
|
collectRichTextRefsInValue(value, { onAsset: (id) => ids.add(id) });
|
|
262
299
|
}
|
|
263
|
-
const ogImage = result.document
|
|
300
|
+
const ogImage = seoObjectOf(result.document, result.seoField)[SEO_SUBFIELDS.image];
|
|
264
301
|
const ogImageId = typeof ogImage === "string" ? ogImage : ogImage && typeof ogImage === "object" && typeof ogImage.id === "string" ? ogImage.id : null;
|
|
265
302
|
if (ogImageId) ids.add(ogImageId);
|
|
266
303
|
return ids.size === 0 ? [] : allAssets.filter((asset) => ids.has(asset.id));
|
|
@@ -457,14 +494,20 @@ function revalidationTags(event, project) {
|
|
|
457
494
|
return [...tags].sort();
|
|
458
495
|
}
|
|
459
496
|
function canvasCacheKey({ scope, mode, url }) {
|
|
460
|
-
return [scope ?? "", mode ?? "", url];
|
|
497
|
+
return JSON.stringify([scope ?? "", mode ?? "", url]);
|
|
461
498
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
499
|
+
function parseCanvasCacheKey(key) {
|
|
500
|
+
let parsed;
|
|
501
|
+
try {
|
|
502
|
+
parsed = JSON.parse(key);
|
|
503
|
+
} catch {
|
|
504
|
+
return void 0;
|
|
505
|
+
}
|
|
506
|
+
if (!Array.isArray(parsed) || parsed.length !== 3 || !parsed.every((part) => typeof part === "string")) {
|
|
507
|
+
return void 0;
|
|
508
|
+
}
|
|
509
|
+
const [scope, mode, url] = parsed;
|
|
510
|
+
return { scope, mode, url };
|
|
468
511
|
}
|
|
469
512
|
var NEXT_CACHE_TAG_CALL_LIMIT = 128;
|
|
470
513
|
function entryTagCalls(tags, scope) {
|
|
@@ -476,100 +519,21 @@ function entryTagCalls(tags, scope) {
|
|
|
476
519
|
}
|
|
477
520
|
return calls;
|
|
478
521
|
}
|
|
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
|
-
);
|
|
492
|
-
}
|
|
493
|
-
return resolve;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// src/metadata.ts
|
|
497
|
-
function str(value) {
|
|
498
|
-
return typeof value === "string" && value.length > 0 ? value : null;
|
|
499
|
-
}
|
|
500
|
-
function looksLikeUrl(value) {
|
|
501
|
-
return /^(https?:)?\/\//.test(value) || value.startsWith("/");
|
|
502
|
-
}
|
|
503
|
-
function resolveOgImage(value, assets) {
|
|
504
|
-
if (value && typeof value === "object") {
|
|
505
|
-
const url = value.url;
|
|
506
|
-
return typeof url === "string" && url.length > 0 ? url : null;
|
|
507
|
-
}
|
|
508
|
-
if (typeof value !== "string" || value.length === 0) return null;
|
|
509
|
-
if (looksLikeUrl(value)) return value;
|
|
510
|
-
return assets.find((asset) => asset.id === value)?.url ?? null;
|
|
511
|
-
}
|
|
512
|
-
function openGraphLocale(locale) {
|
|
513
|
-
return locale.includes("-") ? locale.replace(/-/g, "_") : null;
|
|
514
|
-
}
|
|
515
|
-
function canvasPageMetadata(result, assets = []) {
|
|
516
|
-
const doc = result.document;
|
|
517
|
-
const seoTitle = str(doc?.["seoTitle"]);
|
|
518
|
-
const seoDescription = str(doc?.["seoDescription"]);
|
|
519
|
-
const seoOgTitle = str(doc?.["seoOgTitle"]);
|
|
520
|
-
const seoOgImage = resolveOgImage(doc?.["seoOgImage"], assets);
|
|
521
|
-
const seoNoindex = doc?.["seoNoindex"] === true;
|
|
522
|
-
const seoCanonical = str(doc?.["seoCanonical"]);
|
|
523
|
-
const docTitle = str(doc?.["name"]) ?? str(doc?.["title"]);
|
|
524
|
-
const title = seoTitle ?? docTitle;
|
|
525
|
-
const metadata = {};
|
|
526
|
-
if (title !== null) metadata.title = title;
|
|
527
|
-
if (seoDescription) metadata.description = seoDescription;
|
|
528
|
-
const ogTitle = seoOgTitle ?? title;
|
|
529
|
-
if (doc) {
|
|
530
|
-
const ogLocale = openGraphLocale(result.tags.locale);
|
|
531
|
-
metadata.openGraph = {
|
|
532
|
-
...ogTitle !== null ? { title: ogTitle } : {},
|
|
533
|
-
...seoDescription ? { description: seoDescription } : {},
|
|
534
|
-
...seoOgImage ? { images: [{ url: seoOgImage }] } : {},
|
|
535
|
-
...ogLocale !== null ? { locale: ogLocale } : {}
|
|
536
|
-
};
|
|
537
|
-
}
|
|
538
|
-
if (seoNoindex) metadata.robots = { index: false };
|
|
539
|
-
if (seoCanonical) metadata.alternates = { canonical: seoCanonical };
|
|
540
|
-
return metadata;
|
|
541
|
-
}
|
|
542
|
-
function mergeMetadataOverParent(parent, page) {
|
|
543
|
-
if (!parent) return page;
|
|
544
|
-
const merged = { ...page };
|
|
545
|
-
for (const [key, pageValue] of Object.entries(page)) {
|
|
546
|
-
const parentValue = parent[key];
|
|
547
|
-
if (isPlainObject(pageValue) && isPlainObject(parentValue)) {
|
|
548
|
-
merged[key] = { ...parentValue, ...pageValue };
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
return merged;
|
|
552
|
-
}
|
|
553
|
-
function isPlainObject(value) {
|
|
554
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
555
|
-
}
|
|
556
522
|
var CANVAS_VALIDATION_SLUG = ["_elytra"];
|
|
557
523
|
var CANVAS_VALIDATION_PATH = pathFromSlug([...CANVAS_VALIDATION_SLUG]);
|
|
558
524
|
function defaultMetadata(result, assets) {
|
|
559
|
-
return
|
|
525
|
+
return seoMetadata(result.document, seoContextOf(result, assets));
|
|
560
526
|
}
|
|
561
527
|
async function requestPerspective() {
|
|
562
528
|
const { isEnabled } = await draftMode();
|
|
563
|
-
return isEnabled ?
|
|
564
|
-
}
|
|
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;
|
|
529
|
+
return isEnabled ? DRAFT_PERSPECTIVE : "published";
|
|
571
530
|
}
|
|
572
531
|
function createCanvasRoute(options) {
|
|
532
|
+
if (Object.hasOwn(options, "published") && options.published === void 0) {
|
|
533
|
+
throw new Error(
|
|
534
|
+
"Elytra canvas route: `published` was passed but is undefined. If it names your cache function (the one carrying the cache directive), declare that function ABOVE the canvasRoute() call \u2014 Next compiles a cached function into a variable assigned where it is written, so it is not hoisted like a plain function. To render without a cache, omit `published` entirely."
|
|
535
|
+
);
|
|
536
|
+
}
|
|
573
537
|
const resolveFresh = async (url, perspective) => {
|
|
574
538
|
const source = await options.loadContent({ perspective });
|
|
575
539
|
return resolveCanvasPage(source, url, perspective, {
|
|
@@ -577,15 +541,29 @@ function createCanvasRoute(options) {
|
|
|
577
541
|
registry: options.components.registry
|
|
578
542
|
});
|
|
579
543
|
};
|
|
580
|
-
const
|
|
581
|
-
const
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
544
|
+
const scope = options.scope ?? "";
|
|
545
|
+
const mode = options.mode ?? "";
|
|
546
|
+
const keyFor = (url) => canvasCacheKey({ scope, mode, url });
|
|
547
|
+
const resolve = async (key) => {
|
|
548
|
+
const parts = parseCanvasCacheKey(key);
|
|
549
|
+
if (!parts) {
|
|
550
|
+
throw new Error(
|
|
551
|
+
`Elytra canvas route: resolve() was handed ${JSON.stringify(key)}, which is not a key this route produced. Pass your cache function's argument through unchanged \u2014 it is the entry's key.`
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
if (parts.scope !== scope || parts.mode !== mode) {
|
|
555
|
+
throw new Error(
|
|
556
|
+
`Elytra canvas route: resolve() was handed a cache key of scope "${parts.scope}" and content mode "${parts.mode}", but this route resolves in scope "${scope}" and mode "${mode}". The entry would be keyed under one world and filled from another (EC-543). Pass each route's \`published\` function the key that route hands it, and call that same route's resolve().`
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
return resolveFresh(parts.url, "published");
|
|
560
|
+
};
|
|
561
|
+
const applyCacheTags = (result) => {
|
|
562
|
+
for (const tags of entryTagCalls(result.tags, scope)) cacheTag(...tags);
|
|
563
|
+
};
|
|
586
564
|
const loadOutcome = async (url, perspective) => {
|
|
587
|
-
if (
|
|
588
|
-
return
|
|
565
|
+
if (perspective !== "published" || !options.published) return resolveFresh(url, perspective);
|
|
566
|
+
return options.published(keyFor(url));
|
|
589
567
|
};
|
|
590
568
|
const Page = async (props) => {
|
|
591
569
|
const { slug } = await props.params;
|
|
@@ -622,7 +600,7 @@ function createCanvasRoute(options) {
|
|
|
622
600
|
const url = pathFromSlug(slug);
|
|
623
601
|
if (url === CANVAS_VALIDATION_PATH) return {};
|
|
624
602
|
const perspective = await requestPerspective();
|
|
625
|
-
if (
|
|
603
|
+
if (options.published && perspective === "published") {
|
|
626
604
|
const outcome2 = await loadOutcome(url, perspective);
|
|
627
605
|
if (outcome2.kind !== "render") return {};
|
|
628
606
|
return options.metadata ? options.metadata(outcome2.result, { url, perspective }) : defaultMetadata(outcome2.result, outcome2.assets);
|
|
@@ -639,29 +617,13 @@ function createCanvasRoute(options) {
|
|
|
639
617
|
const generateStaticParams = async () => [
|
|
640
618
|
{ slug: [...CANVAS_VALIDATION_SLUG] }
|
|
641
619
|
];
|
|
642
|
-
return { Page, generateMetadata, generateStaticParams };
|
|
643
|
-
}
|
|
644
|
-
function paramNamesOf(pattern) {
|
|
645
|
-
return splitPath(pattern).filter((segment) => segment.startsWith(":")).map((segment) => segment.slice(1));
|
|
646
|
-
}
|
|
647
|
-
function fillPattern(pattern, params) {
|
|
648
|
-
const segments = [];
|
|
649
|
-
for (const segment of splitPath(pattern)) {
|
|
650
|
-
if (!segment.startsWith(":")) {
|
|
651
|
-
segments.push(segment);
|
|
652
|
-
continue;
|
|
653
|
-
}
|
|
654
|
-
const value = params[segment.slice(1)];
|
|
655
|
-
if (value === void 0 || value.length === 0) return null;
|
|
656
|
-
segments.push(encodeURIComponent(value));
|
|
657
|
-
}
|
|
658
|
-
return "/" + segments.join("/");
|
|
620
|
+
return { Page, generateMetadata, resolve, applyCacheTags, generateStaticParams };
|
|
659
621
|
}
|
|
660
622
|
function originOf(baseUrl) {
|
|
661
623
|
return baseUrl.replace(/\/+$/, "");
|
|
662
624
|
}
|
|
663
625
|
function canvasSitemapEntries(source, options) {
|
|
664
|
-
const routes =
|
|
626
|
+
const routes = source.routes ?? [];
|
|
665
627
|
const locales = source.locales;
|
|
666
628
|
const skipped = [];
|
|
667
629
|
const skip = (route, reason) => {
|
|
@@ -678,83 +640,79 @@ function canvasSitemapEntries(source, options) {
|
|
|
678
640
|
}
|
|
679
641
|
return client;
|
|
680
642
|
};
|
|
681
|
-
const
|
|
682
|
-
const
|
|
643
|
+
const candidates = [];
|
|
644
|
+
const collectionLocales = /* @__PURE__ */ new Map();
|
|
683
645
|
for (const route of routes) {
|
|
684
|
-
|
|
685
|
-
const
|
|
686
|
-
const
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
paths = [];
|
|
703
|
-
for (const row of rows) {
|
|
704
|
-
if (typeof row.values["slug"] !== "string" || row.values["slug"] === "") continue;
|
|
705
|
-
const composed = composePath(row, (id) => byId.get(id));
|
|
706
|
-
if (!composed.ok) continue;
|
|
707
|
-
paths.push("/" + [...mount.prefix, ...composed.segments].join("/"));
|
|
708
|
-
}
|
|
709
|
-
} else if (paramNames.length === 0) {
|
|
710
|
-
paths = [route.pattern];
|
|
711
|
-
} else {
|
|
712
|
-
const paramSets = options.params?.({ route, locale, client });
|
|
713
|
-
if (paramSets == null) {
|
|
714
|
-
skip(route, "dynamic route not enumerated (no params provider entry)");
|
|
715
|
-
continue;
|
|
716
|
-
}
|
|
717
|
-
paths = [];
|
|
718
|
-
for (const paramSet of paramSets) {
|
|
719
|
-
const path = fillPattern(route.pattern, paramSet);
|
|
720
|
-
if (path === null) {
|
|
721
|
-
skip(route, `params entry missing one of :${paramNames.join(", :")}`);
|
|
722
|
-
continue;
|
|
723
|
-
}
|
|
724
|
-
paths.push(path);
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
let unresolved = 0;
|
|
728
|
-
for (const path of paths) {
|
|
729
|
-
const result = client.resolvePage(path, locale);
|
|
730
|
-
if (result.status !== "ok") {
|
|
731
|
-
if (result.status !== "redirect") unresolved += 1;
|
|
732
|
-
continue;
|
|
733
|
-
}
|
|
734
|
-
if (result.document?.["seoNoindex"] === true) continue;
|
|
735
|
-
const { urlPath, addressable } = localeUrlPath(path, locale, locales);
|
|
736
|
-
if (!addressable) {
|
|
737
|
-
skip(
|
|
738
|
-
route,
|
|
739
|
-
`"${path}" (locale "${locale}") has no addressable URL \u2014 its first segment is read as a locale prefix`
|
|
740
|
-
);
|
|
741
|
-
continue;
|
|
742
|
-
}
|
|
743
|
-
const absolute = originOf(options.baseUrl) + urlPath;
|
|
744
|
-
const group = result.document ? `${route.id}${COMPOSITE_KEY_SEPARATOR}doc${COMPOSITE_KEY_SEPARATOR}${result.document.collection}${COMPOSITE_KEY_SEPARATOR}${result.document.id}` : `${route.id}${COMPOSITE_KEY_SEPARATOR}path${COMPOSITE_KEY_SEPARATOR}${path}`;
|
|
745
|
-
if (!byUrl.has(absolute)) byUrl.set(absolute, { group, locale });
|
|
746
|
-
const perLocale = byGroup.get(group) ?? /* @__PURE__ */ new Map();
|
|
747
|
-
if (!perLocale.has(locale)) perLocale.set(locale, absolute);
|
|
748
|
-
byGroup.set(group, perLocale);
|
|
749
|
-
}
|
|
750
|
-
if (mount && paths.length > 0 && unresolved === paths.length) {
|
|
751
|
-
skip(
|
|
752
|
-
route,
|
|
753
|
-
`hierarchy mount over "${mount.collection}" composed ${paths.length} path(s) in locale "${locale}", none of which the resolver serves`
|
|
754
|
-
);
|
|
646
|
+
if (!route.document) continue;
|
|
647
|
+
const set = collectionLocales.get(route.document.collection) ?? /* @__PURE__ */ new Set();
|
|
648
|
+
for (const locale of route.locale ? [route.locale] : locales.locales) set.add(locale);
|
|
649
|
+
collectionLocales.set(route.document.collection, set);
|
|
650
|
+
}
|
|
651
|
+
for (const [collection, routeLocales] of collectionLocales) {
|
|
652
|
+
for (const locale of locales.locales) {
|
|
653
|
+
if (!routeLocales.has(locale)) continue;
|
|
654
|
+
for (const entry of clientFor(locale).urlsFor(collection)) {
|
|
655
|
+
candidates.push({
|
|
656
|
+
route: entry.route,
|
|
657
|
+
locale,
|
|
658
|
+
routePath: entry.routePath,
|
|
659
|
+
path: entry.path,
|
|
660
|
+
addressable: entry.addressable,
|
|
661
|
+
// EC-517: group by the DOCUMENT — the locale-invariant fact — scoped by route.
|
|
662
|
+
group: `${entry.route.id}${COMPOSITE_KEY_SEPARATOR}doc${COMPOSITE_KEY_SEPARATOR}${collection}${COMPOSITE_KEY_SEPARATOR}${entry.id}`
|
|
663
|
+
});
|
|
755
664
|
}
|
|
756
665
|
}
|
|
757
666
|
}
|
|
667
|
+
for (const route of routes) {
|
|
668
|
+
if (route.document || splitPath(route.pattern).some((segment) => segment.startsWith(":"))) continue;
|
|
669
|
+
for (const locale of route.locale ? [route.locale] : locales.locales) {
|
|
670
|
+
const { urlPath, addressable } = localeUrlPath(route.pattern, locale, locales);
|
|
671
|
+
candidates.push({
|
|
672
|
+
route,
|
|
673
|
+
locale,
|
|
674
|
+
routePath: route.pattern,
|
|
675
|
+
path: urlPath,
|
|
676
|
+
addressable,
|
|
677
|
+
group: `${route.id}${COMPOSITE_KEY_SEPARATOR}path${COMPOSITE_KEY_SEPARATOR}${route.pattern}`
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
const byUrl = /* @__PURE__ */ new Map();
|
|
682
|
+
const byGroup = /* @__PURE__ */ new Map();
|
|
683
|
+
const tally = /* @__PURE__ */ new Map();
|
|
684
|
+
for (const candidate of candidates) {
|
|
685
|
+
const key = `${candidate.route.id}${COMPOSITE_KEY_SEPARATOR}${candidate.locale}`;
|
|
686
|
+
const count = tally.get(key) ?? { route: candidate.route, locale: candidate.locale, total: 0, unresolved: 0 };
|
|
687
|
+
count.total += 1;
|
|
688
|
+
tally.set(key, count);
|
|
689
|
+
const result = clientFor(candidate.locale).resolvePage(candidate.routePath, candidate.locale);
|
|
690
|
+
if (result.status !== "ok") {
|
|
691
|
+
if (result.status !== "redirect") count.unresolved += 1;
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
if (seoObjectOf(result.document, result.seoField)[SEO_SUBFIELDS.noindex] === true) continue;
|
|
695
|
+
if (!candidate.addressable) {
|
|
696
|
+
skip(
|
|
697
|
+
candidate.route,
|
|
698
|
+
`"${candidate.routePath}" (locale "${candidate.locale}") has no addressable URL \u2014 its first segment is read as a locale prefix`
|
|
699
|
+
);
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
const absolute = originOf(options.baseUrl) + candidate.path;
|
|
703
|
+
if (!byUrl.has(absolute)) byUrl.set(absolute, { group: candidate.group, locale: candidate.locale });
|
|
704
|
+
const perLocale = byGroup.get(candidate.group) ?? /* @__PURE__ */ new Map();
|
|
705
|
+
if (!perLocale.has(candidate.locale)) perLocale.set(candidate.locale, absolute);
|
|
706
|
+
byGroup.set(candidate.group, perLocale);
|
|
707
|
+
}
|
|
708
|
+
for (const { route, locale, total, unresolved } of tally.values()) {
|
|
709
|
+
if (total > 0 && unresolved === total && route.document && splitPath(route.pattern).at(-1)?.endsWith("*")) {
|
|
710
|
+
skip(
|
|
711
|
+
route,
|
|
712
|
+
`hierarchy mount over "${route.document.collection}" composed ${total} path(s) in locale "${locale}", none of which the resolver serves`
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
758
716
|
const entries = [];
|
|
759
717
|
for (const [url, { group }] of byUrl) {
|
|
760
718
|
const perLocale = byGroup.get(group);
|
|
@@ -885,7 +843,7 @@ function elytraHostImage(options = {}) {
|
|
|
885
843
|
return ({ asset, alt, className, priority, sizes, loading }) => /* @__PURE__ */ jsx(
|
|
886
844
|
ElytraImage,
|
|
887
845
|
{
|
|
888
|
-
asset: { ...asset, alt },
|
|
846
|
+
asset: { ...asset, alt: alt ?? asset.alt },
|
|
889
847
|
...className !== void 0 ? { className } : {},
|
|
890
848
|
...priority ? { priority: true } : {},
|
|
891
849
|
...sizes !== void 0 ? { sizes } : {},
|
|
@@ -937,14 +895,6 @@ async function runConvexQuery(fetchImpl, convexUrl, path, args) {
|
|
|
937
895
|
}
|
|
938
896
|
return body.value;
|
|
939
897
|
}
|
|
940
|
-
function parseDocuments(rows) {
|
|
941
|
-
const documents = [];
|
|
942
|
-
for (const row of rows) {
|
|
943
|
-
const parsed = documentSchema.safeParse(row);
|
|
944
|
-
if (parsed.success) documents.push(parsed.data);
|
|
945
|
-
}
|
|
946
|
-
return documents;
|
|
947
|
-
}
|
|
948
898
|
function parseRedirects(rows) {
|
|
949
899
|
const redirects = [];
|
|
950
900
|
for (const row of rows) {
|
|
@@ -955,8 +905,8 @@ function parseRedirects(rows) {
|
|
|
955
905
|
}
|
|
956
906
|
function assembleLiveSource(payload, options, perspective) {
|
|
957
907
|
const locales = options.locales;
|
|
958
|
-
const documents =
|
|
959
|
-
const routes =
|
|
908
|
+
const { documents, issues: sourceIssues } = parseDeliveredDocuments(payload.documents);
|
|
909
|
+
const routes = [...options.routes];
|
|
960
910
|
const documentSources = perspective === "published" ? documents.map((doc) => ({ draft: doc, published: doc, versions: [] })) : documents;
|
|
961
911
|
return createStaticContentSource({
|
|
962
912
|
projectId: payload.projectId,
|
|
@@ -968,14 +918,21 @@ function assembleLiveSource(payload, options, perspective) {
|
|
|
968
918
|
redirects: parseRedirects(payload.redirects ?? []),
|
|
969
919
|
collections: [...options.collections],
|
|
970
920
|
documents: documentSources,
|
|
971
|
-
assets: payload.assets
|
|
921
|
+
assets: payload.assets,
|
|
922
|
+
...sourceIssues.length > 0 ? { sourceIssues } : {}
|
|
972
923
|
});
|
|
973
924
|
}
|
|
974
925
|
async function loadLiveContent(options, perspective) {
|
|
975
926
|
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
976
927
|
const args = { projectId: options.projectId };
|
|
977
928
|
let payload;
|
|
978
|
-
|
|
929
|
+
const variant = variantOf(perspective);
|
|
930
|
+
if (variant !== void 0) {
|
|
931
|
+
if (variant !== DRAFT_VARIANT) {
|
|
932
|
+
throw new Error(
|
|
933
|
+
`Elytra live content: no delivery snapshot serves the "${variant}" variant \u2014 the deployment serves the published snapshot and the "${DRAFT_VARIANT}" variant's only.`
|
|
934
|
+
);
|
|
935
|
+
}
|
|
979
936
|
if (!options.draftToken) {
|
|
980
937
|
throw new Error(
|
|
981
938
|
"Draft preview requested but DRAFT_PREVIEW_TOKEN is not configured on the host."
|
|
@@ -1024,11 +981,7 @@ function resolveGlobals(source, options = {}) {
|
|
|
1024
981
|
const navDocs = listDocs(navigationCollection);
|
|
1025
982
|
const navigation = (slot) => navDocs.find((doc) => doc.alias === slot) ?? null;
|
|
1026
983
|
const assetUrl = (value) => {
|
|
1027
|
-
if (typeof value === "string")
|
|
1028
|
-
const found = assetById.get(value);
|
|
1029
|
-
if (found) return found.url;
|
|
1030
|
-
return value.startsWith("http") || value.startsWith("/") ? value : void 0;
|
|
1031
|
-
}
|
|
984
|
+
if (typeof value === "string") return assetById.get(value)?.url;
|
|
1032
985
|
const url = record(value).url;
|
|
1033
986
|
return typeof url === "string" && url.length > 0 ? url : void 0;
|
|
1034
987
|
};
|
|
@@ -1059,8 +1012,6 @@ function resolveGlobals(source, options = {}) {
|
|
|
1059
1012
|
if (typeof shaped.path === "string" && shaped.path.startsWith("/")) {
|
|
1060
1013
|
return { href: shaped.path, openInNewTab };
|
|
1061
1014
|
}
|
|
1062
|
-
const slug = str2(shaped.slug);
|
|
1063
|
-
if (slug) return { href: `/${slug}`, openInNewTab };
|
|
1064
1015
|
return null;
|
|
1065
1016
|
}
|
|
1066
1017
|
return null;
|
|
@@ -1187,12 +1138,35 @@ function createPreviewDisableRoute() {
|
|
|
1187
1138
|
}
|
|
1188
1139
|
|
|
1189
1140
|
// src/host-kit.ts
|
|
1190
|
-
function
|
|
1191
|
-
|
|
1141
|
+
function resolveHostProject(entry) {
|
|
1142
|
+
const result = resolveProject(entry);
|
|
1143
|
+
if (result.ok) return result.project;
|
|
1144
|
+
const lines = result.issues.filter((issue) => issue.severity === "error").map((issue) => {
|
|
1145
|
+
const at = issue.path.length > 0 ? ` at ${issue.path.join(".")}` : "";
|
|
1146
|
+
return ` \u2022 ${issue.code}${at}: ${issue.message}`;
|
|
1147
|
+
});
|
|
1148
|
+
throw new Error(
|
|
1149
|
+
`Elytra delivery host: the project config "${String(entry["slug"])}" is invalid \u2014 the studio refuses it too:
|
|
1150
|
+
${lines.join("\n")}`
|
|
1151
|
+
);
|
|
1152
|
+
}
|
|
1153
|
+
function surfaceRouteIssues(slug, issues) {
|
|
1154
|
+
if (issues.length === 0) return;
|
|
1155
|
+
const building = process.env["NEXT_PHASE"] === "phase-production-build";
|
|
1156
|
+
if (!building && process.env["NODE_ENV"] === "production") return;
|
|
1157
|
+
const lines = issues.map((issue) => ` \u2022 ${issue.severity} ${issue.code}: ${issue.message}`);
|
|
1158
|
+
const report = `[elytra] project "${slug}": ${issues.length} route issue(s) \u2014
|
|
1159
|
+
${lines.join("\n")}`;
|
|
1160
|
+
if (issues.some((issue) => issue.severity === "error")) console.error(report);
|
|
1161
|
+
else console.warn(report);
|
|
1192
1162
|
}
|
|
1193
1163
|
function createElytraHost(options) {
|
|
1194
|
-
const env =
|
|
1195
|
-
const
|
|
1164
|
+
const { env: envOption, fallbackContent, globals, fetch: fetchOption, ...entry } = options;
|
|
1165
|
+
const project = resolveHostProject(entry);
|
|
1166
|
+
const env = resolveHostEnv(envOption ?? process.env);
|
|
1167
|
+
const routes = project.routes;
|
|
1168
|
+
const routeIssues = createRouter(project).issues;
|
|
1169
|
+
surfaceRouteIssues(project.slug, routeIssues);
|
|
1196
1170
|
const live = env.convexUrl !== void 0 && env.projectId !== void 0;
|
|
1197
1171
|
if (!live && (env.convexUrl !== void 0 || env.projectId !== void 0)) {
|
|
1198
1172
|
const [present, missing] = env.convexUrl !== void 0 ? [ELYTRA_HOST_ENV_KEYS.convexUrl, ELYTRA_HOST_ENV_KEYS.projectId] : [ELYTRA_HOST_ENV_KEYS.projectId, ELYTRA_HOST_ENV_KEYS.convexUrl];
|
|
@@ -1200,16 +1174,16 @@ function createElytraHost(options) {
|
|
|
1200
1174
|
`Elytra delivery host: ${present} is set but ${missing} is not \u2014 live content needs both. Left as it is, this site would serve fallback content and look healthy. Set ${missing} to go live, or unset ${present} to run the no-backend fallback on purpose.`
|
|
1201
1175
|
);
|
|
1202
1176
|
}
|
|
1203
|
-
const contentMode = live ? "live" :
|
|
1204
|
-
const contentScope = live ? env.projectId :
|
|
1205
|
-
const components = defineHostComponents(
|
|
1177
|
+
const contentMode = live ? "live" : fallbackContent ? "fallback" : "empty";
|
|
1178
|
+
const contentScope = live ? env.projectId : project.slug;
|
|
1179
|
+
const components = defineHostComponents(project.components ?? []);
|
|
1206
1180
|
const emptySource = createStaticContentSource({
|
|
1207
1181
|
projectId: contentScope,
|
|
1208
|
-
locales:
|
|
1182
|
+
locales: project.locales,
|
|
1209
1183
|
routes,
|
|
1210
1184
|
// Redirects are editorial DATA (EC-303) — the no-backend build has none.
|
|
1211
1185
|
redirects: [],
|
|
1212
|
-
collections:
|
|
1186
|
+
collections: project.collections,
|
|
1213
1187
|
documents: [],
|
|
1214
1188
|
assets: []
|
|
1215
1189
|
});
|
|
@@ -1219,16 +1193,16 @@ function createElytraHost(options) {
|
|
|
1219
1193
|
{
|
|
1220
1194
|
convexUrl: env.convexUrl,
|
|
1221
1195
|
projectId: env.projectId,
|
|
1222
|
-
locales:
|
|
1223
|
-
collections:
|
|
1196
|
+
locales: project.locales,
|
|
1197
|
+
collections: project.collections,
|
|
1224
1198
|
routes,
|
|
1225
1199
|
...env.draftToken ? { draftToken: env.draftToken } : {},
|
|
1226
|
-
...
|
|
1200
|
+
...fetchOption ? { fetch: fetchOption } : {}
|
|
1227
1201
|
},
|
|
1228
1202
|
request.perspective
|
|
1229
1203
|
);
|
|
1230
1204
|
}
|
|
1231
|
-
if (
|
|
1205
|
+
if (fallbackContent) return fallbackContent(request);
|
|
1232
1206
|
return Promise.resolve(emptySource);
|
|
1233
1207
|
};
|
|
1234
1208
|
const loadGlobals = async (globalsOptions = {}) => {
|
|
@@ -1236,21 +1210,22 @@ function createElytraHost(options) {
|
|
|
1236
1210
|
const source = await loadContent({ perspective });
|
|
1237
1211
|
return resolveGlobals(source, {
|
|
1238
1212
|
perspective,
|
|
1239
|
-
...
|
|
1240
|
-
});
|
|
1241
|
-
};
|
|
1242
|
-
const canvasRoute = (routeOptions = {}) => {
|
|
1243
|
-
const { cache, ...rest } = routeOptions;
|
|
1244
|
-
return createCanvasRoute({
|
|
1245
|
-
loadContent,
|
|
1246
|
-
components,
|
|
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 } },
|
|
1251
|
-
...rest
|
|
1213
|
+
...globals ? { conventions: globals } : {}
|
|
1252
1214
|
});
|
|
1253
1215
|
};
|
|
1216
|
+
const canvasRoute = (routeOptions = {}) => createCanvasRoute({
|
|
1217
|
+
...routeOptions,
|
|
1218
|
+
loadContent,
|
|
1219
|
+
components,
|
|
1220
|
+
// EC-620: the route caches nothing itself; these reach the host's own cache
|
|
1221
|
+
// function. Tag scope = `contentScope`, the SAME value `revalidateRoute` derives
|
|
1222
|
+
// its invalidation tags with (EC-579) — so `route.applyCacheTags` writes exactly
|
|
1223
|
+
// the strings the receiver revalidates. EC-543: the mode rides in every cache key
|
|
1224
|
+
// the route hands out, so no world ever reads another's entries. Spread last:
|
|
1225
|
+
// neither can be overridden.
|
|
1226
|
+
scope: contentScope,
|
|
1227
|
+
mode: contentMode
|
|
1228
|
+
});
|
|
1254
1229
|
const revalidateRoute = (overrides = {}) => createRevalidateRoute({
|
|
1255
1230
|
// An unset secret keeps the endpoint CLOSED (401) — the kit never ships a
|
|
1256
1231
|
// default secret (an auth-shaped no-op is worse than an explicit gap).
|
|
@@ -1259,27 +1234,29 @@ function createElytraHost(options) {
|
|
|
1259
1234
|
// under (`canvasRoute` above) and every locale it serves. The emitter sends only
|
|
1260
1235
|
// the event, so this is the one place the scope and locales are read for it.
|
|
1261
1236
|
scope: contentScope,
|
|
1262
|
-
locales:
|
|
1237
|
+
locales: project.locales.locales
|
|
1263
1238
|
});
|
|
1264
1239
|
const previewRoute = () => createPreviewRoute({ token: env.draftToken });
|
|
1265
1240
|
const previewDisableRoute = () => createPreviewDisableRoute();
|
|
1266
1241
|
const pageOpensFullBleed = async (slug) => {
|
|
1267
|
-
const
|
|
1268
|
-
const perspective = isEnabled ? "draft" : "published";
|
|
1242
|
+
const perspective = await requestPerspective();
|
|
1269
1243
|
const source = await loadContent({ perspective });
|
|
1270
1244
|
const { locale, path } = splitLocalePath(pathFromSlug(slug), source.locales);
|
|
1271
1245
|
const client = source.client({ perspective, locale });
|
|
1272
1246
|
const resolved = client.resolvePage(path);
|
|
1273
1247
|
return firstBlockFullBleed(resolved.document, components.registry);
|
|
1274
1248
|
};
|
|
1249
|
+
const frontendUrl = projectFrontendUrl(project);
|
|
1275
1250
|
return {
|
|
1251
|
+
routeIssues,
|
|
1276
1252
|
collections: options.collections,
|
|
1277
1253
|
contentScope,
|
|
1278
1254
|
contentMode,
|
|
1279
1255
|
env,
|
|
1256
|
+
project,
|
|
1280
1257
|
routes,
|
|
1281
1258
|
components,
|
|
1282
|
-
...
|
|
1259
|
+
...frontendUrl !== void 0 ? { frontendUrl } : {},
|
|
1283
1260
|
loadContent,
|
|
1284
1261
|
loadGlobals,
|
|
1285
1262
|
canvasRoute,
|
|
@@ -1291,6 +1268,6 @@ function createElytraHost(options) {
|
|
|
1291
1268
|
}
|
|
1292
1269
|
var PACKAGE = "@elytracms/next";
|
|
1293
1270
|
|
|
1294
|
-
export { CANVAS_VALIDATION_SLUG, CanvasRenderer, ELYTRA_HOST_ENV_KEYS, ElytraImage, PACKAGE, assembleLiveSource, bodyItemsOf,
|
|
1271
|
+
export { CANVAS_VALIDATION_SLUG, CanvasRenderer, ELYTRA_HOST_ENV_KEYS, ElytraImage, PACKAGE, assembleLiveSource, bodyItemsOf, canvasSitemapEntries, collectionCacheTag, createAssetImageLoader, createCanvasRoute, createCanvasSitemap, createContentSnapshot, createElytraHost, createPreviewDisableRoute, createPreviewRoute, createRevalidateRoute, createStaticContentSource, defineHostComponents, docCacheTag, elytraHostImage, evaluatePreviewRequest, evaluateRevalidateRequest, firstBlockFullBleed, loadLiveContent, mergeCacheTags, mergeMetadataOverParent, nextCacheTags, openGraphLocale, pathFromSlug, projectSweepTag, requestPerspective, resolveCanvasPage, resolveGlobals, resolveHostEnv, revalidationTags, routeCacheTag, safeRedirectTarget, scopeCacheTag, seoContextOf, seoMetadata, seoObjectOf, withNextImage };
|
|
1295
1272
|
//# sourceMappingURL=index.js.map
|
|
1296
1273
|
//# sourceMappingURL=index.js.map
|