@elytracms/next 0.0.82 → 0.0.84

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -16,6 +16,7 @@ import { z } from 'zod';
16
16
  export { z } from 'zod';
17
17
  import { ImageLoader } from 'next/image';
18
18
  import { HostImageRenderer } from './host-image.js';
19
+ import { HostComponentEntry } from '@elytracms/core/studio-core';
19
20
 
20
21
  /**
21
22
  * One host-repo component: the manifest the builder edits against plus the
@@ -1491,8 +1492,26 @@ interface ElytraHostOptions {
1491
1492
  * SAME modules the studio composes with). Base primitives are always
1492
1493
  * included; `base.Image` is swapped for `next/image` delivery by default
1493
1494
  * (see `image`).
1495
+ *
1496
+ * Typed as the CONFIG's `HostComponentEntry` (`implementation: unknown`), not
1497
+ * the stricter {@link HostComponent}, so that `createElytraHost({ ...site })`
1498
+ * — the shape EC-532 makes the contract — compiles however the consumer typed
1499
+ * their project entry. It previously demanded `ComponentType`, which meant the
1500
+ * recommended spread worked only when the entry's implementation types
1501
+ * survived inference: `apps/demo/elytra.config.ts` gets away with it via
1502
+ * `satisfies`, and anyone using the engine's own published `HostComponentEntry`
1503
+ * / `WorkspaceProjectEntry` annotation did not. Both production consumers hit
1504
+ * exactly that and each carried one hand-restated `components` line (EC-539).
1505
+ *
1506
+ * What this costs, stated plainly rather than glossed: a list passed HERE no
1507
+ * longer gets a compile-time check that each `implementation` is really a
1508
+ * component, and nothing yet checks it at runtime either — a garbage value
1509
+ * would reach the renderer. `defineHostComponents` keeps the strict
1510
+ * {@link HostComponent} signature, so a hand-built list passed there directly
1511
+ * is still checked. Closing the gap properly needs a registration issue code
1512
+ * in the core registry; booked as EC-541 rather than smuggled in here.
1494
1513
  */
1495
- components?: readonly HostComponent[];
1514
+ components?: readonly HostComponentEntry[];
1496
1515
  /**
1497
1516
  * `base.Image` delivery options (loader/sizes/quality — the non-Vercel CDN
1498
1517
  * seam), or `false` to keep the plain-`<img>` primitive. Default: Next's own
@@ -1548,6 +1567,25 @@ interface LoadGlobalsOptions {
1548
1567
  /** Perspective of the chrome read. Default `'published'` (live is published-only). */
1549
1568
  perspective?: Perspective;
1550
1569
  }
1570
+ /**
1571
+ * Which world {@link ElytraHost.loadContent} reads from.
1572
+ *
1573
+ * - `live` — the configured Convex deployment (published; token-gated draft).
1574
+ * - `fallback` — the host's own `fallbackContent` world (the scaffold's seeded
1575
+ * starter, the demo's in-memory fixture). A legitimate, supported mode.
1576
+ * - `empty` — no backend and no fallback: this project's structure with no
1577
+ * content, so every URL is a clean 404. Correct for a CI build; almost never
1578
+ * what a deployed site intends.
1579
+ *
1580
+ * The mode was decided from env long before EC-527 and readable by **nobody**,
1581
+ * so a site serving the fallback looked identical from the outside to one
1582
+ * reading the real deployment. Measured on the demo: `/` returned `200` while
1583
+ * all four of EC-512's published fixtures 404'd — an agent following AGENTS.md
1584
+ * §5 gets no signal that the thing under test was never exercised. Declared
1585
+ * once, read here, so a host can *say* which world it is in instead of
1586
+ * re-deriving it from `env` and drifting.
1587
+ */
1588
+ type ElytraContentMode = 'live' | 'fallback' | 'empty';
1551
1589
  /** The delivery host handle {@link createElytraHost} yields. */
1552
1590
  interface ElytraHost {
1553
1591
  /**
@@ -1555,6 +1593,13 @@ interface ElytraHost {
1555
1593
  * studio's webhook tags), else the config slug.
1556
1594
  */
1557
1595
  contentScope: string;
1596
+ /**
1597
+ * Which world {@link loadContent} reads from — see {@link ElytraContentMode}.
1598
+ * Read it to announce the mode (a boot line, a dev-only banner) rather than
1599
+ * re-deriving it from {@link env}, which is the drift this field exists to
1600
+ * prevent.
1601
+ */
1602
+ contentMode: ElytraContentMode;
1558
1603
  /** The resolved env bag (documented names, trimmed, empty = unset). */
1559
1604
  env: ElytraHostEnv;
1560
1605
  /** Materialized route records (a derive is run once, at factory time). */
@@ -1742,4 +1787,4 @@ declare function evaluatePreviewRequest(requestUrl: string | URL, expectedToken:
1742
1787
  */
1743
1788
  declare const PACKAGE = "@elytracms/next";
1744
1789
 
1745
- export { type AssetImageLoaderOptions, type BindingPayloads, type CacheWrapper, type CacheWrapperOptions, type CachedEntry, type CanvasCacheOptions, type CanvasContentRequest, type CanvasDataSource, type CanvasPageOutcome, type CanvasPayloadContext, type CanvasPayloadsFactory, CanvasRenderer, type CanvasRendererProps, type CanvasRequestInfo, type CanvasRoute, type CanvasRouteOptions, type CanvasRouteProps, type CanvasSitemapOptions, type CanvasSitemapResult, type CanvasWrapContext, type ContentSnapshot, type ContentSnapshotOptions, type ContentSource, type CreateCanvasSitemapOptions, type DefineHostComponentsOptions, type DeliveryPresentation, ELYTRA_HOST_ENV_KEYS, type ElytraHost, type ElytraHostEnv, type ElytraHostImageOptions, type ElytraHostOptions, ElytraImage, type ElytraImageAsset, type ElytraImageProps, type GlobalsConventions, type HostCanvasRouteOptions, type HostComponent, type HostComponents, type LiveContentOptions, type LoadGlobalsOptions, type MaterializeSourcePayloadsInput, type MaterializedSourcePayloads, type NextImagePrimitiveOptions, PACKAGE, type PreviewEvaluation, type PreviewRouteHandlers, type PreviewRouteOptions, REVALIDATE_SIGNATURE_HEADER, type ResolveCanvasPageOptions, type ResolveGlobalsOptions, type ResolvedLink, type ResolvedNavEntry, type ResolvedNavLink, type RevalidateEvaluation, type RevalidatePayload, type RevalidateRouteHandlers, type RevalidateRouteOptions, type SiteGlobals, type SitemapParamsProvider, type SitemapRouteContext, type SitemapSkippedRoute, type SourcePayloadError, type StaticContentData, assembleLiveSource, bodyItemsOf, canvasPageMetadata, canvasSitemapEntries, collectBindingSourceIds, collectionCacheTag, createAssetImageLoader, createBindingResolver, createCanvasRoute, createCanvasSitemap, createContentSnapshot, createElytraHost, createPreviewDisableRoute, createPreviewRoute, createRevalidateRoute, createStaticContentSource, defaultBindingPayloads, defineHostComponents, docCacheTag, elytraHostImage, evaluatePreviewRequest, evaluateRevalidateRequest, firstBlockFullBleed, isSourcePayloadError, loadCachedEntry, loadLiveContent, materializeSourcePayloads, mergeCacheTags, nextCacheTags, nextImagePrimitive, pathFromSlug, projectSweepTag, resolveCanvasPage, resolveGlobals, resolveHostEnv, resolvePayloadToken, revalidatePayloadSchema, routeCacheTag, safeRedirectTarget, scopeCacheTag, signRevalidateBody, sourcePayloadError };
1790
+ export { type AssetImageLoaderOptions, type BindingPayloads, type CacheWrapper, type CacheWrapperOptions, type CachedEntry, type CanvasCacheOptions, type CanvasContentRequest, type CanvasDataSource, type CanvasPageOutcome, type CanvasPayloadContext, type CanvasPayloadsFactory, CanvasRenderer, type CanvasRendererProps, type CanvasRequestInfo, type CanvasRoute, type CanvasRouteOptions, type CanvasRouteProps, type CanvasSitemapOptions, type CanvasSitemapResult, type CanvasWrapContext, type ContentSnapshot, type ContentSnapshotOptions, type ContentSource, type CreateCanvasSitemapOptions, type DefineHostComponentsOptions, type DeliveryPresentation, ELYTRA_HOST_ENV_KEYS, type ElytraContentMode, type ElytraHost, type ElytraHostEnv, type ElytraHostImageOptions, type ElytraHostOptions, ElytraImage, type ElytraImageAsset, type ElytraImageProps, type GlobalsConventions, type HostCanvasRouteOptions, type HostComponent, type HostComponents, type LiveContentOptions, type LoadGlobalsOptions, type MaterializeSourcePayloadsInput, type MaterializedSourcePayloads, type NextImagePrimitiveOptions, PACKAGE, type PreviewEvaluation, type PreviewRouteHandlers, type PreviewRouteOptions, REVALIDATE_SIGNATURE_HEADER, type ResolveCanvasPageOptions, type ResolveGlobalsOptions, type ResolvedLink, type ResolvedNavEntry, type ResolvedNavLink, type RevalidateEvaluation, type RevalidatePayload, type RevalidateRouteHandlers, type RevalidateRouteOptions, type SiteGlobals, type SitemapParamsProvider, type SitemapRouteContext, type SitemapSkippedRoute, type SourcePayloadError, type StaticContentData, assembleLiveSource, bodyItemsOf, canvasPageMetadata, canvasSitemapEntries, collectBindingSourceIds, collectionCacheTag, createAssetImageLoader, createBindingResolver, createCanvasRoute, createCanvasSitemap, createContentSnapshot, createElytraHost, createPreviewDisableRoute, createPreviewRoute, createRevalidateRoute, createStaticContentSource, defaultBindingPayloads, defineHostComponents, docCacheTag, elytraHostImage, evaluatePreviewRequest, evaluateRevalidateRequest, firstBlockFullBleed, isSourcePayloadError, loadCachedEntry, loadLiveContent, materializeSourcePayloads, mergeCacheTags, nextCacheTags, nextImagePrimitive, pathFromSlug, projectSweepTag, resolveCanvasPage, resolveGlobals, resolveHostEnv, resolvePayloadToken, revalidatePayloadSchema, routeCacheTag, safeRedirectTarget, scopeCacheTag, signRevalidateBody, sourcePayloadError };
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@ import { ComponentRegistry } from '@elytracms/core/component-registry';
2
2
  export { defineComponent } from '@elytracms/core/component-registry';
3
3
  import { basePrimitives, getImplementation, 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
+ import { isBlockSequenceValueShape } from '@elytracms/core/rich-text';
5
6
  import { jsx, Fragment } from 'react/jsx-runtime';
6
7
  import { cacheTags, isSectionSourceId, parseCmsSourceQueryConfig, listQueryOfCmsConfig, isDocumentHistory, resolvedAsset, createContentClient, createContentContext } from '@elytracms/core/content';
7
8
  import { documentKey, splitLocalePath, hierarchyMount, composePath, localeUrlPath, mergeRouteRecords, fallbackHomeRoutes, presentationBodyFields, splitPath, documentSchema, redirectRecordSchema } from '@elytracms/core/cms-core';
@@ -160,10 +161,11 @@ function bodyItemsOf(document, presentation) {
160
161
  }
161
162
  function compositionOf(document) {
162
163
  const body = document["body"];
163
- if (Array.isArray(body) || isComponentNodeValue(body)) return body;
164
+ if (isCompositionishSequence(body) || Array.isArray(body) || isComponentNodeValue(body)) return body;
164
165
  const content = document["content"];
165
- if (Array.isArray(content) || isComponentNodeValue(content)) return content;
166
+ if (isCompositionishSequence(content) || Array.isArray(content) || isComponentNodeValue(content)) return content;
166
167
  for (const value of Object.values(document)) {
168
+ if (isCompositionishSequence(value)) return value;
167
169
  if (isComponentNodeValue(value)) return value;
168
170
  if (Array.isArray(value) && value.length > 0 && value.every(isComponentNodeValue)) return value;
169
171
  }
@@ -172,6 +174,11 @@ function compositionOf(document) {
172
174
  function compositionNodesOf(document, presentation) {
173
175
  return documentBodyCompositionNodes(bodyItemsOf(document, presentation));
174
176
  }
177
+ function isCompositionishSequence(value) {
178
+ return isBlockSequenceValueShape(value) && value.content.some(
179
+ (node) => typeof node === "object" && node !== null && node.type === "elytra/component"
180
+ );
181
+ }
175
182
  function isComponentNodeValue(value) {
176
183
  return typeof value === "object" && value !== null && typeof value.id === "string" && typeof value.componentId === "string";
177
184
  }
@@ -715,7 +722,8 @@ function canvasSitemapEntries(source, options) {
715
722
  }
716
723
  return client;
717
724
  };
718
- const byPath = /* @__PURE__ */ new Map();
725
+ const byUrl = /* @__PURE__ */ new Map();
726
+ const byGroup = /* @__PURE__ */ new Map();
719
727
  for (const route of routes) {
720
728
  const paramNames = paramNamesOf(route.pattern);
721
729
  const mount = hierarchyMount(route);
@@ -776,9 +784,12 @@ function canvasSitemapEntries(source, options) {
776
784
  );
777
785
  continue;
778
786
  }
779
- const perLocale = byPath.get(path) ?? /* @__PURE__ */ new Map();
780
- perLocale.set(locale, originOf(options.baseUrl) + urlPath);
781
- byPath.set(path, perLocale);
787
+ const absolute = originOf(options.baseUrl) + urlPath;
788
+ const group = result.document ? `${route.id}\0doc\0${result.document.collection}\0${result.document.id}` : `${route.id}\0path\0${path}`;
789
+ if (!byUrl.has(absolute)) byUrl.set(absolute, { group, locale });
790
+ const perLocale = byGroup.get(group) ?? /* @__PURE__ */ new Map();
791
+ if (!perLocale.has(locale)) perLocale.set(locale, absolute);
792
+ byGroup.set(group, perLocale);
782
793
  }
783
794
  if (mount && paths.length > 0 && unresolved === paths.length) {
784
795
  skip(
@@ -789,14 +800,10 @@ function canvasSitemapEntries(source, options) {
789
800
  }
790
801
  }
791
802
  const entries = [];
792
- for (const perLocale of byPath.values()) {
793
- const languages = Object.fromEntries(perLocale);
794
- for (const url of perLocale.values()) {
795
- entries.push({
796
- url,
797
- ...perLocale.size > 1 ? { alternates: { languages } } : {}
798
- });
799
- }
803
+ for (const [url, { group }] of byUrl) {
804
+ const perLocale = byGroup.get(group);
805
+ const languages = perLocale && perLocale.size > 1 ? Object.fromEntries(perLocale) : void 0;
806
+ entries.push({ url, ...languages ? { alternates: { languages } } : {} });
800
807
  }
801
808
  entries.sort((a, b) => a.url < b.url ? -1 : a.url > b.url ? 1 : 0);
802
809
  return { entries, skipped };
@@ -1266,12 +1273,23 @@ function createElytraHost(options) {
1266
1273
  const env = resolveHostEnv(options.env ?? process.env);
1267
1274
  const routes = materializeRoutes(options.routes, options.collections);
1268
1275
  const live = env.convexUrl !== void 0 && env.projectId !== void 0;
1276
+ if (!live && (env.convexUrl !== void 0 || env.projectId !== void 0)) {
1277
+ 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];
1278
+ throw new Error(
1279
+ `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.`
1280
+ );
1281
+ }
1282
+ const contentMode = live ? "live" : options.fallbackContent ? "fallback" : "empty";
1269
1283
  const contentScope = live ? env.projectId : options.slug;
1270
1284
  const components = defineHostComponents([
1271
1285
  // Batteries included (EC-368 image defaults): graph `base.Image` nodes render
1272
1286
  // through next/image — resized variants via the host's own optimizer. The
1273
1287
  // canonical manifest stays (first registration wins in defineHostComponents).
1274
1288
  ...options.image === false ? [] : [nextImagePrimitive(options.image ?? {})],
1289
+ // EC-539: the option is the CONFIG's entry shape (`implementation: unknown`)
1290
+ // so the recommended `{ ...site }` spread compiles. Narrowing here is the
1291
+ // one place that widening is paid for; see the option's doc comment and
1292
+ // EC-541 for the runtime check that should back it.
1275
1293
  ...options.components ?? []
1276
1294
  ]);
1277
1295
  const emptySource = createStaticContentSource({
@@ -1341,6 +1359,7 @@ function createElytraHost(options) {
1341
1359
  };
1342
1360
  return {
1343
1361
  contentScope,
1362
+ contentMode,
1344
1363
  env,
1345
1364
  routes,
1346
1365
  components,