@elytracms/next 0.0.86 → 0.0.87

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.js CHANGED
@@ -1,48 +1,21 @@
1
1
  import { ComponentRegistry } from '@elytracms/core/component-registry';
2
2
  export { defineComponent } from '@elytracms/core/component-registry';
3
- import { basePrimitives, getImplementation, renderDocumentBodyItems, documentBodyItems, ImageManifest, serializeListingQuery, RenderErrorBoundary, documentBodyRichTextValues, documentBodyCompositionNodes, richTextEmbedsInValue, collectReferencedAssetIds, collectRichTextRefsInValue, collectReferencedRelations, collectReferencedListings, collectReferencedRichTextLinks, normalizeImageSource } from '@elytracms/core/runtime-renderer';
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
4
  import { isValidElement } from 'react';
5
5
  import { isBlockSequenceValueShape } from '@elytracms/core/rich-text';
6
6
  import { jsx, Fragment } from 'react/jsx-runtime';
7
- import { cacheTags, isSectionSourceId, parseCmsSourceQueryConfig, listQueryOfCmsConfig, isDocumentHistory, resolvedAsset, createContentClient, createContentContext } from '@elytracms/core/content';
7
+ import { cacheTags, isDocumentHistory, resolvedAsset, createContentClient, createContentContext } from '@elytracms/core/content';
8
8
  import { documentKey, splitLocalePath, hierarchyMount, composePath, localeUrlPath, mergeRouteRecords, fallbackHomeRoutes, presentationBodyFields, splitPath, documentSchema, redirectRecordSchema } from '@elytracms/core/cms-core';
9
9
  export { documentSchema, fallbackHomeRoutes, localeConfigSchema, localeUrlPath, mergeRouteRecords, redirectRecordSchema, resolveLocale, routeRecordSchema, splitLocalePath, textDirection } from '@elytracms/core/cms-core';
10
10
  import { revalidateTag, unstable_cache } from 'next/cache';
11
11
  import { draftMode } from 'next/headers';
12
12
  import { redirect, notFound, permanentRedirect } from 'next/navigation';
13
+ import { COMPOSITE_KEY_SEPARATOR } from '@elytracms/core/project-graph';
13
14
  import { createHmac, timingSafeEqual } from 'crypto';
14
15
  import { z } from 'zod';
15
16
  export { z } from 'zod';
16
17
  import NextImage from 'next/image';
17
18
 
18
- // src/components.ts
19
-
20
- // src/is-component.ts
21
- var EXOTIC_COMPONENT_TYPES = /* @__PURE__ */ new Set([
22
- /* @__PURE__ */ Symbol.for("react.memo"),
23
- /* @__PURE__ */ Symbol.for("react.forward_ref"),
24
- /* @__PURE__ */ Symbol.for("react.lazy")
25
- ]);
26
- function isComponentImplementation(value) {
27
- if (typeof value === "function") return true;
28
- if (typeof value !== "object" || value === null) return false;
29
- const marker = value.$$typeof;
30
- return typeof marker === "symbol" && EXOTIC_COMPONENT_TYPES.has(marker);
31
- }
32
- function describeImplementation(value) {
33
- if (value === null) return "null";
34
- if (value === void 0) return "undefined";
35
- if (typeof value === "string") {
36
- return `the string ${JSON.stringify(value)} (which React would render silently as an HTML tag)`;
37
- }
38
- if (Array.isArray(value)) return "an array";
39
- if (typeof value === "object") {
40
- const marker = value.$$typeof;
41
- return typeof marker === "symbol" ? `an object tagged ${String(marker)}` : "a plain object";
42
- }
43
- return `a ${typeof value}`;
44
- }
45
-
46
19
  // src/components.ts
47
20
  function defineHostComponents(components = [], options = {}) {
48
21
  const includeBase = options.includeBasePrimitives !== false;
@@ -53,74 +26,9 @@ function defineHostComponents(components = [], options = {}) {
53
26
  const implementation = getImplementation(base.implementations, manifest.id);
54
27
  if (implementation) implementations[manifest.id] = implementation;
55
28
  }
56
- const implementationIssues = [];
57
- for (const component of components) {
58
- const id = component.manifest.id;
59
- if (isComponentImplementation(component.implementation)) {
60
- implementations[id] = component.implementation;
61
- continue;
62
- }
63
- implementationIssues.push({
64
- code: "invalid-implementation",
65
- componentId: id,
66
- message: `Component "${id}": implementation is not a React component \u2014 received ${describeImplementation(component.implementation)}; expected a function or class component, or a React.memo / forwardRef / lazy wrapper. The entry is registered without an implementation, so its nodes render the missing-component fallback.`
67
- });
68
- }
69
- return { registry, implementations, issues: [...registry.issues, ...implementationIssues] };
70
- }
71
-
72
- // src/payloads.ts
73
- var SOURCE_PAYLOAD_ERROR_KEY = "__elytraSourcePayloadError";
74
- function sourcePayloadError(sourceId, messages) {
75
- return { [SOURCE_PAYLOAD_ERROR_KEY]: true, sourceId, messages: [...messages] };
76
- }
77
- function isSourcePayloadError(value) {
78
- return typeof value === "object" && value !== null && value[SOURCE_PAYLOAD_ERROR_KEY] === true && typeof value.sourceId === "string" && Array.isArray(value.messages);
79
- }
80
- function unescapeRefToken(token) {
81
- return token.replace(/~1/g, "/").replace(/~0/g, "~");
82
- }
83
- function pathFromToken(token) {
84
- if (token === "" || token === "/") return [];
85
- if (!token.startsWith("/")) return void 0;
86
- return token.slice(1).split("/").map(unescapeRefToken);
87
- }
88
- var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
89
- function resolvePayloadToken(payload, token) {
90
- const path = pathFromToken(token);
91
- if (path === void 0) return void 0;
92
- let current = payload;
93
- for (const segment of path) {
94
- if (Array.isArray(current)) {
95
- if (!/^(0|[1-9][0-9]*)$/.test(segment)) return void 0;
96
- const index = Number(segment);
97
- if (index >= current.length) return void 0;
98
- current = current[index];
99
- continue;
100
- }
101
- if (isPlainObject(current)) {
102
- if (!Object.prototype.hasOwnProperty.call(current, segment)) return void 0;
103
- current = current[segment];
104
- continue;
105
- }
106
- return void 0;
107
- }
108
- return current;
109
- }
110
- function createBindingResolver(payloads) {
111
- return (ref, item) => {
112
- if (ref.mode === "repeaterItem") {
113
- return item === void 0 ? void 0 : resolvePayloadToken(item.item, ref.token);
114
- }
115
- if (!Object.prototype.hasOwnProperty.call(payloads, ref.sourceId)) return void 0;
116
- const payload = payloads[ref.sourceId];
117
- if (isSourcePayloadError(payload)) {
118
- throw new Error(
119
- `Source "${payload.sourceId}" query was rejected: ${payload.messages.join("; ")}`
120
- );
121
- }
122
- return resolvePayloadToken(payload, ref.token);
123
- };
29
+ const host = registerImplementations(components);
30
+ Object.assign(implementations, host.implementations);
31
+ return { registry, implementations, issues: [...registry.issues, ...host.issues] };
124
32
  }
125
33
  function relationResolver(relations) {
126
34
  return (ref) => relations[`${ref.collection}:${ref.id}`] ?? null;
@@ -159,19 +67,14 @@ function stripServerErrorBoundary(_node, rendered) {
159
67
  }
160
68
  return rendered;
161
69
  }
162
- function defaultBindingPayloads(result) {
163
- return { document: result.document, params: result.dynamicParams };
164
- }
165
70
  async function CanvasRenderer(props) {
166
71
  const { result, components } = props;
167
72
  if (result.status !== "ok") {
168
73
  return /* @__PURE__ */ jsx(UnresolvedPageFallback, { status: result.status });
169
74
  }
170
- const resolveBinding = props.resolveBinding ?? createBindingResolver(props.payloads ?? defaultBindingPayloads(result));
171
75
  const ctx = {
172
76
  registry: components.registry,
173
77
  implementations: components.implementations,
174
- resolveBinding,
175
78
  resolveAsset: assetResolver(props.assets ?? []),
176
79
  // EC-254: wire relation population only when the host baked the populated
177
80
  // targets, so a host that did not opt in keeps the raw-ref passthrough.
@@ -230,42 +133,6 @@ function mergeCacheTags(first, ...rest) {
230
133
  locale: first.locale
231
134
  });
232
135
  }
233
- function collectBindingSourceIds(node, into = /* @__PURE__ */ new Set()) {
234
- for (const value of Object.values(node.props ?? {})) {
235
- if (value.kind === "binding") into.add(value.binding.sourceId);
236
- }
237
- if (node.condition) into.add(node.condition.source.sourceId);
238
- for (const children of Object.values(node.slots ?? {})) {
239
- for (const child of children) collectBindingSourceIds(child, into);
240
- }
241
- return into;
242
- }
243
- function materializeSourcePayloads(input) {
244
- const { client, result, sources } = input;
245
- const payloads = {};
246
- const tags = [];
247
- if (result.status !== "ok") return { payloads, tags };
248
- const referenced = /* @__PURE__ */ new Set();
249
- const byId = new Map(sources.map((source) => [source.id, source]));
250
- for (const sourceId of [...referenced].sort()) {
251
- if (!isSectionSourceId(sourceId)) continue;
252
- const definition = byId.get(sourceId);
253
- if (!definition || definition.kind !== "cms") continue;
254
- const config = parseCmsSourceQueryConfig(definition.config);
255
- if (!config || config.documentId !== void 0) continue;
256
- const list = client.listDocuments(config.collectionId, listQueryOfCmsConfig(config));
257
- tags.push(list.tags);
258
- if (list.ok) {
259
- payloads[sourceId] = list.documents;
260
- } else {
261
- payloads[sourceId] = sourcePayloadError(
262
- sourceId,
263
- list.errors.map((error) => error.message)
264
- );
265
- }
266
- }
267
- return { payloads, tags };
268
- }
269
136
  function createStaticContentSource(data) {
270
137
  const locales = data.locales;
271
138
  const routes = data.routes ?? [];
@@ -311,7 +178,6 @@ function createStaticContentSource(data) {
311
178
  // dimensions. Baked into the (serializable) render outcome by the route
312
179
  // helper, so a cached page keeps its images during a backend outage.
313
180
  assets: () => [...assets.values()].map((record2) => resolvedAsset(record2, record2.url ?? assetUrl(record2))),
314
- ...data.sources ? { sources: data.sources } : {},
315
181
  // EC-499: the schema the route helper reads a collection's declared
316
182
  // `presentation` from (which field is the delivery body, which component
317
183
  // wraps it). Already prefetched for the lookup above — exposing it costs
@@ -351,7 +217,6 @@ async function createContentSnapshot(adapter, projectId, options) {
351
217
  locales: options.locales,
352
218
  ...options.routes ? { routes: options.routes } : {},
353
219
  redirects: options.redirects ? [...options.redirects] : redirects,
354
- ...options.sources ? { sources: options.sources } : {},
355
220
  ...schema ? { collections: schema.collections } : {},
356
221
  documents,
357
222
  assets: assetRecords,
@@ -474,26 +339,6 @@ async function resolveCanvasPage(source, url, perspective, options = {}) {
474
339
  if (result.status !== "ok") {
475
340
  return { kind: "not-found", tags: result.tags };
476
341
  }
477
- const extraTags = [];
478
- let payloads = defaultBindingPayloads(result);
479
- const materialized = materializeSourcePayloads({
480
- client,
481
- result,
482
- sources: source.sources ?? []
483
- });
484
- payloads = { ...payloads, ...materialized.payloads };
485
- extraTags.push(...materialized.tags);
486
- if (options.payloads) {
487
- const custom = await options.payloads({
488
- client,
489
- result,
490
- perspective,
491
- path,
492
- params: result.dynamicParams,
493
- addTags: (tags) => extraTags.push(tags)
494
- });
495
- payloads = { ...payloads, ...custom };
496
- }
497
342
  const presentation = presentationFor(source, result);
498
343
  const body = pageBodyOf(result, presentation);
499
344
  const allAssets = source.assets?.() ?? [];
@@ -524,7 +369,6 @@ async function resolveCanvasPage(source, url, perspective, options = {}) {
524
369
  return {
525
370
  kind: "render",
526
371
  result,
527
- payloads,
528
372
  assets: bakedAssets,
529
373
  relations,
530
374
  listings,
@@ -532,7 +376,6 @@ async function resolveCanvasPage(source, url, perspective, options = {}) {
532
376
  ...presentation ? { presentation } : {},
533
377
  tags: mergeCacheTags(
534
378
  result.tags,
535
- ...extraTags,
536
379
  ...listingTags,
537
380
  assetTags,
538
381
  relationTags,
@@ -665,7 +508,6 @@ function createCanvasRoute(options) {
665
508
  const resolveFresh = async () => {
666
509
  const source = await options.loadContent({ perspective });
667
510
  return resolveCanvasPage(source, url, perspective, {
668
- ...options.payloads ? { payloads: options.payloads } : {},
669
511
  // EC-205: page-scope the baked assets to what the page actually uses.
670
512
  registry: options.components.registry
671
513
  });
@@ -701,7 +543,6 @@ function createCanvasRoute(options) {
701
543
  {
702
544
  result: outcome.result,
703
545
  components: options.components,
704
- payloads: outcome.payloads,
705
546
  assets: outcome.assets,
706
547
  relations: outcome.relations,
707
548
  listings: outcome.listings,
@@ -830,7 +671,7 @@ function canvasSitemapEntries(source, options) {
830
671
  continue;
831
672
  }
832
673
  const absolute = originOf(options.baseUrl) + urlPath;
833
- const group = result.document ? `${route.id}\0doc\0${result.document.collection}\0${result.document.id}` : `${route.id}\0path\0${path}`;
674
+ 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}`;
834
675
  if (!byUrl.has(absolute)) byUrl.set(absolute, { group, locale });
835
676
  const perLocale = byGroup.get(group) ?? /* @__PURE__ */ new Map();
836
677
  if (!perLocale.has(locale)) perLocale.set(locale, absolute);
@@ -1080,21 +921,6 @@ function parseRedirects(rows) {
1080
921
  }
1081
922
  return redirects;
1082
923
  }
1083
- var canvasDataSourceSchema = z.object({
1084
- kind: z.string().min(1),
1085
- id: z.string().min(1),
1086
- label: z.string().optional(),
1087
- config: z.unknown().optional()
1088
- });
1089
- function parseSources(rows) {
1090
- if (!Array.isArray(rows)) return [];
1091
- const sources = [];
1092
- for (const row of rows) {
1093
- const parsed = canvasDataSourceSchema.safeParse(row);
1094
- if (parsed.success) sources.push(parsed.data);
1095
- }
1096
- return sources;
1097
- }
1098
924
  function assembleLiveSource(payload, options, perspective) {
1099
925
  const locales = options.locales;
1100
926
  const documents = parseDocuments(payload.documents);
@@ -1110,10 +936,7 @@ function assembleLiveSource(payload, options, perspective) {
1110
936
  redirects: parseRedirects(payload.redirects ?? []),
1111
937
  collections: [...options.collections],
1112
938
  documents: documentSources,
1113
- assets: payload.assets,
1114
- // EC-166 LIVE mode: the builder's persisted section/template sources, so
1115
- // the route helper materializes their payloads.
1116
- sources: parseSources(payload.sources)
939
+ assets: payload.assets
1117
940
  });
1118
941
  }
1119
942
  async function loadLiveContent(options, perspective) {
@@ -1252,12 +1075,30 @@ function resolveGlobals(source, options = {}) {
1252
1075
  }
1253
1076
 
1254
1077
  // src/preview-core.ts
1078
+ var RESOLUTION_ORIGIN = "https://redirect-target.invalid";
1079
+ function resolveOnOrigin(value) {
1080
+ let resolved;
1081
+ try {
1082
+ resolved = new URL(value, RESOLUTION_ORIGIN);
1083
+ } catch {
1084
+ return null;
1085
+ }
1086
+ return resolved.origin === RESOLUTION_ORIGIN ? resolved : null;
1087
+ }
1088
+ function hasControlCharacter(value) {
1089
+ for (let i = 0; i < value.length; i++) {
1090
+ const code = value.charCodeAt(i);
1091
+ if (code <= 31 || code === 127) return true;
1092
+ }
1093
+ return false;
1094
+ }
1255
1095
  function safeRedirectTarget(value, fallback = "/") {
1256
1096
  if (value === null || value === void 0 || value === "") return fallback;
1257
- if (!value.startsWith("/")) return null;
1258
- if (value.includes("\\")) return null;
1259
- if (value.startsWith("//")) return null;
1260
- return value;
1097
+ if (!value.startsWith("/") || hasControlCharacter(value)) return null;
1098
+ const resolved = resolveOnOrigin(value);
1099
+ if (resolved === null) return null;
1100
+ const target = resolved.pathname + resolved.search + resolved.hash;
1101
+ return resolveOnOrigin(target) === null ? null : target;
1261
1102
  }
1262
1103
  function evaluatePreviewRequest(requestUrl, expectedToken) {
1263
1104
  if (expectedToken === void 0 || expectedToken.length === 0) {
@@ -1279,6 +1120,15 @@ function evaluatePreviewRequest(requestUrl, expectedToken) {
1279
1120
  }
1280
1121
  return { ok: true, redirectTo };
1281
1122
  }
1123
+ function previewDisableTarget(requestUrl) {
1124
+ let url;
1125
+ try {
1126
+ url = typeof requestUrl === "string" ? new URL(requestUrl, "http://localhost") : requestUrl;
1127
+ } catch {
1128
+ return "/";
1129
+ }
1130
+ return safeRedirectTarget(url.searchParams.get("redirect")) ?? "/";
1131
+ }
1282
1132
 
1283
1133
  // src/preview.ts
1284
1134
  function createPreviewRoute(options) {
@@ -1299,13 +1149,7 @@ function createPreviewDisableRoute() {
1299
1149
  async GET(request) {
1300
1150
  const draft = await draftMode();
1301
1151
  draft.disable();
1302
- let target;
1303
- try {
1304
- target = safeRedirectTarget(new URL(request.url).searchParams.get("redirect"));
1305
- } catch {
1306
- target = "/";
1307
- }
1308
- redirect(target ?? "/");
1152
+ redirect(previewDisableTarget(request.url));
1309
1153
  }
1310
1154
  };
1311
1155
  }
@@ -1345,8 +1189,7 @@ function createElytraHost(options) {
1345
1189
  redirects: [],
1346
1190
  collections: [...options.collections],
1347
1191
  documents: [],
1348
- assets: [],
1349
- sources: []
1192
+ assets: []
1350
1193
  });
1351
1194
  const loadContent = (request) => {
1352
1195
  if (live) {
@@ -1422,6 +1265,6 @@ function createElytraHost(options) {
1422
1265
  }
1423
1266
  var PACKAGE = "@elytracms/next";
1424
1267
 
1425
- export { CanvasRenderer, ELYTRA_HOST_ENV_KEYS, ElytraImage, PACKAGE, REVALIDATE_SIGNATURE_HEADER, 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 };
1268
+ export { CanvasRenderer, ELYTRA_HOST_ENV_KEYS, ElytraImage, PACKAGE, REVALIDATE_SIGNATURE_HEADER, assembleLiveSource, bodyItemsOf, canvasPageMetadata, canvasSitemapEntries, collectionCacheTag, createAssetImageLoader, createCanvasRoute, createCanvasSitemap, createContentSnapshot, createElytraHost, createPreviewDisableRoute, createPreviewRoute, createRevalidateRoute, createStaticContentSource, defineHostComponents, docCacheTag, elytraHostImage, evaluatePreviewRequest, evaluateRevalidateRequest, firstBlockFullBleed, loadCachedEntry, loadLiveContent, mergeCacheTags, nextCacheTags, nextImagePrimitive, pathFromSlug, projectSweepTag, resolveCanvasPage, resolveGlobals, resolveHostEnv, revalidatePayloadSchema, routeCacheTag, safeRedirectTarget, scopeCacheTag, signRevalidateBody };
1426
1269
  //# sourceMappingURL=index.js.map
1427
1270
  //# sourceMappingURL=index.js.map