@elytracms/next 0.0.82 → 0.0.83

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
@@ -715,7 +715,8 @@ function canvasSitemapEntries(source, options) {
715
715
  }
716
716
  return client;
717
717
  };
718
- const byPath = /* @__PURE__ */ new Map();
718
+ const byUrl = /* @__PURE__ */ new Map();
719
+ const byGroup = /* @__PURE__ */ new Map();
719
720
  for (const route of routes) {
720
721
  const paramNames = paramNamesOf(route.pattern);
721
722
  const mount = hierarchyMount(route);
@@ -776,9 +777,12 @@ function canvasSitemapEntries(source, options) {
776
777
  );
777
778
  continue;
778
779
  }
779
- const perLocale = byPath.get(path) ?? /* @__PURE__ */ new Map();
780
- perLocale.set(locale, originOf(options.baseUrl) + urlPath);
781
- byPath.set(path, perLocale);
780
+ const absolute = originOf(options.baseUrl) + urlPath;
781
+ const group = result.document ? `${route.id}\0doc\0${result.document.collection}\0${result.document.id}` : `${route.id}\0path\0${path}`;
782
+ if (!byUrl.has(absolute)) byUrl.set(absolute, { group, locale });
783
+ const perLocale = byGroup.get(group) ?? /* @__PURE__ */ new Map();
784
+ if (!perLocale.has(locale)) perLocale.set(locale, absolute);
785
+ byGroup.set(group, perLocale);
782
786
  }
783
787
  if (mount && paths.length > 0 && unresolved === paths.length) {
784
788
  skip(
@@ -789,14 +793,10 @@ function canvasSitemapEntries(source, options) {
789
793
  }
790
794
  }
791
795
  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
- }
796
+ for (const [url, { group }] of byUrl) {
797
+ const perLocale = byGroup.get(group);
798
+ const languages = perLocale && perLocale.size > 1 ? Object.fromEntries(perLocale) : void 0;
799
+ entries.push({ url, ...languages ? { alternates: { languages } } : {} });
800
800
  }
801
801
  entries.sort((a, b) => a.url < b.url ? -1 : a.url > b.url ? 1 : 0);
802
802
  return { entries, skipped };
@@ -1266,12 +1266,23 @@ function createElytraHost(options) {
1266
1266
  const env = resolveHostEnv(options.env ?? process.env);
1267
1267
  const routes = materializeRoutes(options.routes, options.collections);
1268
1268
  const live = env.convexUrl !== void 0 && env.projectId !== void 0;
1269
+ if (!live && (env.convexUrl !== void 0 || env.projectId !== void 0)) {
1270
+ 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];
1271
+ throw new Error(
1272
+ `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.`
1273
+ );
1274
+ }
1275
+ const contentMode = live ? "live" : options.fallbackContent ? "fallback" : "empty";
1269
1276
  const contentScope = live ? env.projectId : options.slug;
1270
1277
  const components = defineHostComponents([
1271
1278
  // Batteries included (EC-368 image defaults): graph `base.Image` nodes render
1272
1279
  // through next/image — resized variants via the host's own optimizer. The
1273
1280
  // canonical manifest stays (first registration wins in defineHostComponents).
1274
1281
  ...options.image === false ? [] : [nextImagePrimitive(options.image ?? {})],
1282
+ // EC-539: the option is the CONFIG's entry shape (`implementation: unknown`)
1283
+ // so the recommended `{ ...site }` spread compiles. Narrowing here is the
1284
+ // one place that widening is paid for; see the option's doc comment and
1285
+ // EC-541 for the runtime check that should back it.
1275
1286
  ...options.components ?? []
1276
1287
  ]);
1277
1288
  const emptySource = createStaticContentSource({
@@ -1341,6 +1352,7 @@ function createElytraHost(options) {
1341
1352
  };
1342
1353
  return {
1343
1354
  contentScope,
1355
+ contentMode,
1344
1356
  env,
1345
1357
  routes,
1346
1358
  components,