@elytracms/next 0.0.92 → 0.0.94

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,16 +1,17 @@
1
- import { registerComponents, renderDocumentBodyItems, documentBodyItems, serializeListingQuery, documentBodyRichTextValues, documentBodyCompositionNodes, richTextEmbedsInValue, collectReferencedAssetIds, collectRichTextRefsInValue, collectReferencedRelations, collectReferencedListings, collectReferencedRichTextLinks, RenderErrorBoundary, nodeListingOf, listingRequestOf } from '@elytracms/core/runtime-renderer';
2
- import { Fragment as Fragment$1, isValidElement } from 'react';
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
3
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
5
- import { cacheTags, isDocumentHistory, resolvedAsset, createContentClient, createContentContext } from '@elytracms/core/content';
6
- import { DEFAULT_SEO_FIELD, parseLocaleConfig, documentKey, liveRedirectRecords, SEO_SUBFIELDS, splitLocalePath, DRAFT_PERSPECTIVE, splitPath, localeUrlPath, variantOf, DRAFT_VARIANT, createRouter, presentationBodyFields, documentSchema, redirectRecordSchema } from '@elytracms/core/cms-core';
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';
7
6
  export { DRAFT_PERSPECTIVE, documentSchema, localeConfigSchema, localeUrlPath, parseLocaleConfig, redirectRecordSchema, resolveLocale, routeRecordSchema, splitLocalePath, textDirection, variantOf } from '@elytracms/core/cms-core';
7
+ import { redirectRecordKind } from '@elytracms/core/persistence';
8
8
  import { revalidateTag, cacheTag } from 'next/cache';
9
9
  import { draftMode } from 'next/headers';
10
10
  import { redirect, notFound, permanentRedirect } from 'next/navigation';
11
11
  import { COMPOSITE_KEY_SEPARATOR } from '@elytracms/core/project-graph';
12
12
  import { verifyRevalidationSignature, readRevalidationBody, REVALIDATE_SIGNATURE_HEADER } from '@elytracms/core/revalidation';
13
13
  export { REVALIDATE_SIGNATURE_HEADER, revalidationEventSchema, signRevalidationBody, signedRevalidationRequest } from '@elytracms/core/revalidation';
14
+ import { INGEST_MAX_BODY_BYTES, readIngestPayload, INGEST_RECEIVER_PATH, signedIngestRequest, convexSiteUrl } from '@elytracms/core/ingest';
14
15
  import NextImage from 'next/image';
15
16
  import { normalizeImageSource } from '@elytracms/components';
16
17
  import { projectFrontendUrl, resolveProject } from '@elytracms/core/studio-core';
@@ -34,33 +35,15 @@ function listingResolver(listings) {
34
35
  return Array.isArray(baked) ? baked : null;
35
36
  };
36
37
  }
37
- function listingFailureOf(node, components, listings) {
38
- const manifest = components.registry.getManifest(node.componentId);
39
- if (!manifest) return null;
40
- const listing = nodeListingOf(node, manifest, components.registry);
41
- if (!listing || listing.state === "awaiting") return null;
42
- if (listing.state === "invalid") return listing.messages;
43
- const baked = listings?.[serializeListingQuery(listing.query)];
44
- return baked && !Array.isArray(baked) ? baked.errors.map((error) => error.message) : null;
45
- }
46
- function InvalidListingNotice(props) {
47
- 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(" ")}` });
38
+ function bakedListingFailure(listings) {
39
+ return (query) => {
40
+ const baked = listings[serializeListingQuery(query)];
41
+ return baked && !Array.isArray(baked) ? baked.errors.map((error) => error.message) : null;
42
+ };
48
43
  }
49
44
  function assetResolver(assets) {
50
45
  const byId = new Map(assets.map((asset) => [asset.id, asset]));
51
- return (id) => {
52
- const asset = byId.get(id);
53
- return asset ? {
54
- url: asset.url,
55
- width: asset.width,
56
- height: asset.height,
57
- alt: asset.alt,
58
- focalPoint: asset.focalPoint,
59
- // EC-420: carry the LQIP so a placed Image blurs up on delivery.
60
- lqip: asset.lqip,
61
- meta: asset.meta
62
- } : null;
63
- };
46
+ return (id) => byId.get(id) ?? null;
64
47
  }
65
48
  function UnresolvedPageFallback(props) {
66
49
  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}).` });
@@ -68,41 +51,28 @@ function UnresolvedPageFallback(props) {
68
51
  function MissingDocumentFallback() {
69
52
  return /* @__PURE__ */ jsx("div", { "data-ec-fallback": "missing-document", children: `Route resolved but carried no page document to render.` });
70
53
  }
71
- function stripServerErrorBoundary(_node, rendered) {
72
- if (isValidElement(rendered) && rendered.type === RenderErrorBoundary) {
73
- return rendered.props.children ?? null;
74
- }
75
- return rendered;
76
- }
77
54
  async function CanvasRenderer(props) {
78
55
  const { result, components } = props;
79
56
  if (result.status !== "ok") {
80
57
  return /* @__PURE__ */ jsx(UnresolvedPageFallback, { status: result.status });
81
58
  }
82
- const ctx = {
83
- registry: components.registry,
84
- implementations: components.implementations,
85
- resolveAsset: assetResolver(props.assets ?? []),
86
- // EC-254: wire relation population only when the host baked the populated
87
- // targets, so a host that did not opt in keeps the raw-ref passthrough.
88
- ...props.relations ? { resolveRelation: relationResolver(props.relations) } : {},
89
- // EC-255: wire the listing resolver only when the host baked the query results,
90
- // so a host that did not opt in keeps listing props empty (the block's own state).
91
- ...props.listings ? { resolveListing: listingResolver(props.listings) } : {},
92
- // EC-291: wire the rich-text link resolver only when the host baked the link map,
93
- // so a host that did not opt in degrades a reference link to the visible
94
- // `unresolved-ref` issue (EC-290) rather than a wrong href.
95
- ...props.links ? { resolveLink: linkResolver(props.links) } : {},
96
- decorateNode: (node, rendered) => {
97
- const stripped = stripServerErrorBoundary(node, rendered);
98
- const failure = listingFailureOf(node, components, props.listings);
99
- if (!failure) return stripped;
100
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [
101
- /* @__PURE__ */ jsx(InvalidListingNotice, { nodeId: node.id, messages: failure }),
102
- stripped
103
- ] }, node.id);
59
+ const ctx = createRenderContext({
60
+ components,
61
+ mode: "delivery",
62
+ source: {
63
+ resolveAsset: assetResolver(props.assets ?? []),
64
+ // EC-254: wire relation population only when the host baked the populated
65
+ // targets, so a host that did not opt in keeps the raw-ref passthrough.
66
+ ...props.relations ? { resolveRelation: relationResolver(props.relations) } : {},
67
+ // EC-255: wire the listing resolver only when the host baked the query results,
68
+ // so a host that did not opt in keeps listing props empty (the block's own state).
69
+ ...props.listings ? { resolveListing: listingResolver(props.listings), listingFailure: bakedListingFailure(props.listings) } : {},
70
+ // EC-291: wire the rich-text link resolver only when the host baked the link map,
71
+ // so a host that did not opt in degrades a reference link to the visible
72
+ // `unresolved-ref` issue (EC-290) rather than a wrong href.
73
+ ...props.links ? { resolveLink: linkResolver(props.links) } : {}
104
74
  }
105
- };
75
+ });
106
76
  const document = result.document;
107
77
  if (document) {
108
78
  return /* @__PURE__ */ jsx(Fragment, { children: renderDocumentBodyItems(bodyItemsOf(document, props.presentation), ctx) });
@@ -181,7 +151,8 @@ function createStaticContentSource(data) {
181
151
  locales
182
152
  }),
183
153
  routes,
184
- redirects
154
+ redirects,
155
+ ...data.sourceIssues ? { sourceIssues: data.sourceIssues } : {}
185
156
  });
186
157
  return {
187
158
  projectId: data.projectId,
@@ -204,7 +175,7 @@ async function createContentSnapshot(adapter, projectId, options) {
204
175
  const documentRecords = await adapter.cms.listDocuments(projectId);
205
176
  const versionRecords = await adapter.cms.listAllDocumentVersions(projectId);
206
177
  const assetRecords = await adapter.assets.listAssets(projectId);
207
- const redirectRecords = await adapter.redirects.listRedirects(projectId);
178
+ const redirectRecords = await adapter.records(redirectRecordKind).list(projectId);
208
179
  const redirects = liveRedirectRecords(redirectRecords);
209
180
  const versionsByKey = /* @__PURE__ */ new Map();
210
181
  for (const record2 of versionRecords) {
@@ -243,16 +214,12 @@ function seoContextOf(result, assets = []) {
243
214
  function str(value) {
244
215
  return typeof value === "string" && value.length > 0 ? value : null;
245
216
  }
246
- function looksLikeUrl(value) {
247
- return /^(https?:)?\/\//.test(value) || value.startsWith("/");
248
- }
249
217
  function resolveSeoImage(value, assets) {
250
218
  if (value && typeof value === "object") {
251
219
  const url = value.url;
252
220
  return typeof url === "string" && url.length > 0 ? url : null;
253
221
  }
254
222
  if (typeof value !== "string" || value.length === 0) return null;
255
- if (looksLikeUrl(value)) return value;
256
223
  return assets.find((asset) => asset.id === value)?.url ?? null;
257
224
  }
258
225
  function openGraphLocale(locale) {
@@ -318,6 +285,15 @@ function canvasWrapContext(result, info) {
318
285
  result
319
286
  };
320
287
  }
288
+ function canvasBeaconProps(result, info) {
289
+ const document = result.document;
290
+ return {
291
+ path: info.url,
292
+ status: 200,
293
+ locale: result.tags.locale,
294
+ ...document ? { collection: document.collection, documentId: document.id } : {}
295
+ };
296
+ }
321
297
  function pathFromSlug(slug) {
322
298
  return "/" + (slug ?? []).join("/");
323
299
  }
@@ -625,8 +601,13 @@ function createCanvasRoute(options) {
625
601
  presentation: outcome.presentation
626
602
  }
627
603
  );
628
- if (!options.wrap) return rendered;
629
- return options.wrap(rendered, canvasWrapContext(outcome.result, { url, perspective }));
604
+ const page = options.wrap ? options.wrap(rendered, canvasWrapContext(outcome.result, { url, perspective })) : rendered;
605
+ const Beacon = options.analytics;
606
+ if (!Beacon || perspective !== "published") return page;
607
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
608
+ page,
609
+ /* @__PURE__ */ jsx(Beacon, { ...canvasBeaconProps(outcome.result, { url}) })
610
+ ] });
630
611
  }
631
612
  }
632
613
  };
@@ -793,6 +774,177 @@ function createRevalidateRoute(options) {
793
774
  }
794
775
  };
795
776
  }
777
+ function createFixedWindowLimiter(options) {
778
+ const windowMs = options.windowMs ?? 6e4;
779
+ const now = options.now ?? Date.now;
780
+ const windows = /* @__PURE__ */ new Map();
781
+ return {
782
+ hit(key) {
783
+ const at = now();
784
+ for (const [held, window2] of windows) if (at - window2.windowStart >= windowMs) windows.delete(held);
785
+ const window = windows.get(key);
786
+ if (!window) {
787
+ windows.set(key, { windowStart: at, count: 1 });
788
+ return { allowed: true };
789
+ }
790
+ if (window.count >= options.limit) {
791
+ return { allowed: false, retryAfterSeconds: Math.max(1, Math.ceil((window.windowStart + windowMs - at) / 1e3)) };
792
+ }
793
+ window.count += 1;
794
+ return { allowed: true };
795
+ },
796
+ size: () => windows.size
797
+ };
798
+ }
799
+ function defaultIngestClientKey(request) {
800
+ const forwarded = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim();
801
+ if (forwarded) return forwarded;
802
+ const real = request.headers.get("x-real-ip")?.trim();
803
+ return real || "unknown";
804
+ }
805
+ function isCrossOrigin(request) {
806
+ const site = request.headers.get("sec-fetch-site");
807
+ if (site && site !== "same-origin" && site !== "none") return true;
808
+ const origin = request.headers.get("origin");
809
+ if (!origin) return false;
810
+ try {
811
+ return new URL(origin).origin !== new URL(request.url).origin;
812
+ } catch {
813
+ return true;
814
+ }
815
+ }
816
+ async function evaluateIngestRequest(input) {
817
+ const { request } = input;
818
+ if (request.method !== "POST") {
819
+ return { ok: false, status: 405, body: { error: "Only POST is accepted." }, headers: { allow: "POST" } };
820
+ }
821
+ if (!input.configured) {
822
+ return { ok: false, status: 503, body: { error: "Ingest is not configured on this site." } };
823
+ }
824
+ if (isCrossOrigin(request)) {
825
+ return { ok: false, status: 403, body: { error: "Cross-origin requests are not accepted." } };
826
+ }
827
+ const limited = input.limiter.hit(input.clientKey(request));
828
+ if (!limited.allowed) {
829
+ return {
830
+ ok: false,
831
+ status: 429,
832
+ body: { error: "Too many requests \u2014 try again shortly." },
833
+ headers: { "retry-after": String(limited.retryAfterSeconds) }
834
+ };
835
+ }
836
+ if (input.rawBody === void 0) {
837
+ return { ok: false, status: 413, body: { error: "The payload is too large." } };
838
+ }
839
+ const read = readIngestPayload(input.rawBody);
840
+ if (!read.ok) {
841
+ return { ok: false, status: 400, body: { error: read.message, ...read.issues ? { issues: read.issues } : {} } };
842
+ }
843
+ if (input.botGate) {
844
+ const verdict = await input.botGate({ request, payload: read.value });
845
+ if (!verdict.allow) {
846
+ return { ok: false, status: 403, body: { error: "Refused by the bot gate.", gate: "bot", reason: verdict.reason } };
847
+ }
848
+ }
849
+ if (!input.enrich) return { ok: true, payload: read.value };
850
+ return enrichPayload(input.enrich, request, read.value);
851
+ }
852
+ async function enrichPayload(enrich, request, payload) {
853
+ let enriched;
854
+ try {
855
+ enriched = await enrich({ request, payload });
856
+ } catch (error) {
857
+ const message = error instanceof Error ? error.message : String(error);
858
+ return { ok: false, status: 500, body: { error: `The site's ingest enrichment failed: ${message}` } };
859
+ }
860
+ const body = JSON.stringify(enriched ?? null);
861
+ const reread = readIngestPayload(body);
862
+ if (!reread.ok) {
863
+ return {
864
+ ok: false,
865
+ status: 500,
866
+ body: { error: "The site's ingest enrichment returned an invalid payload.", ...reread.issues ? { issues: reread.issues } : {} }
867
+ };
868
+ }
869
+ if (new TextEncoder().encode(body).byteLength > INGEST_MAX_BODY_BYTES) {
870
+ return { ok: false, status: 500, body: { error: "The site's ingest enrichment grew the payload past the receiver's cap." } };
871
+ }
872
+ return { ok: true, payload: reread.value };
873
+ }
874
+ async function readCappedBody(request, maxBytes = INGEST_MAX_BODY_BYTES) {
875
+ const declared = Number(request.headers.get("content-length"));
876
+ if (Number.isFinite(declared) && declared > maxBytes) return void 0;
877
+ if (!request.body) return "";
878
+ const reader = request.body.getReader();
879
+ const chunks = [];
880
+ let total = 0;
881
+ for (; ; ) {
882
+ const { done, value } = await reader.read();
883
+ if (done) break;
884
+ total += value.byteLength;
885
+ if (total > maxBytes) {
886
+ await reader.cancel().catch(() => void 0);
887
+ return void 0;
888
+ }
889
+ chunks.push(value);
890
+ }
891
+ const bytes = new Uint8Array(total);
892
+ let offset = 0;
893
+ for (const chunk of chunks) {
894
+ bytes.set(chunk, offset);
895
+ offset += chunk.byteLength;
896
+ }
897
+ return new TextDecoder().decode(bytes);
898
+ }
899
+ var NO_STORE = { "cache-control": "no-store" };
900
+ function createIngestRoute(options) {
901
+ const maxBodyBytes = options.maxBodyBytes ?? INGEST_MAX_BODY_BYTES;
902
+ if (maxBodyBytes > INGEST_MAX_BODY_BYTES || maxBodyBytes <= 0) {
903
+ throw new Error(`createIngestRoute: maxBodyBytes must be between 1 and ${INGEST_MAX_BODY_BYTES} (the receiver's cap).`);
904
+ }
905
+ const limiter = createFixedWindowLimiter({
906
+ limit: options.maxRequestsPerMinute ?? 30,
907
+ ...options.now ? { now: options.now } : {}
908
+ });
909
+ const clientKey = options.clientKey ?? defaultIngestClientKey;
910
+ const receiver = options.siteUrl ? `${options.siteUrl.replace(/\/+$/, "")}${INGEST_RECEIVER_PATH}` : void 0;
911
+ const fetchImpl = options.fetchImpl ?? fetch;
912
+ return {
913
+ async POST(request) {
914
+ const rawBody = request.method === "POST" ? await readCappedBody(request, maxBodyBytes) : "";
915
+ const evaluation = await evaluateIngestRequest({
916
+ request,
917
+ rawBody,
918
+ configured: options.secret !== void 0 && options.secret.length > 0 && receiver !== void 0,
919
+ limiter,
920
+ clientKey,
921
+ ...options.botGate ? { botGate: options.botGate } : {},
922
+ ...options.enrich ? { enrich: options.enrich } : {}
923
+ });
924
+ if (!evaluation.ok) {
925
+ return Response.json(evaluation.body, { status: evaluation.status, headers: { ...NO_STORE, ...evaluation.headers } });
926
+ }
927
+ const signed = await signedIngestRequest(evaluation.payload, options.secret);
928
+ let answer;
929
+ try {
930
+ answer = await fetchImpl(receiver, { method: "POST", headers: signed.headers, body: signed.body });
931
+ } catch {
932
+ return Response.json({ error: "The ingest receiver could not be reached." }, { status: 502, headers: NO_STORE });
933
+ }
934
+ if (answer.status === 401) {
935
+ return Response.json(
936
+ { error: "The ingest receiver refused this site's signature \u2014 INGEST_SECRET must equal the deployment's ELYTRA_INGEST_SECRET." },
937
+ { status: 502, headers: NO_STORE }
938
+ );
939
+ }
940
+ const text = await answer.text();
941
+ return new Response(text, {
942
+ status: answer.status,
943
+ headers: { ...NO_STORE, "content-type": answer.headers.get("content-type") ?? "application/json" }
944
+ });
945
+ }
946
+ };
947
+ }
796
948
  var LQIP_MAX_EDGE = 24;
797
949
  function fitLqipLongEdge(width, height) {
798
950
  const scale = Math.min(1, LQIP_MAX_EDGE / Math.max(width, height));
@@ -878,7 +1030,7 @@ function elytraHostImage(options = {}) {
878
1030
  return ({ asset, alt, className, priority, sizes, loading }) => /* @__PURE__ */ jsx(
879
1031
  ElytraImage,
880
1032
  {
881
- asset: { ...asset, alt },
1033
+ asset: { ...asset, alt: alt ?? asset.alt },
882
1034
  ...className !== void 0 ? { className } : {},
883
1035
  ...priority ? { priority: true } : {},
884
1036
  ...sizes !== void 0 ? { sizes } : {},
@@ -901,7 +1053,8 @@ var ELYTRA_HOST_ENV_KEYS = {
901
1053
  convexUrl: "ELYTRA_CONVEX_URL",
902
1054
  projectId: "ELYTRA_PROJECT_ID",
903
1055
  draftToken: "DRAFT_PREVIEW_TOKEN",
904
- revalidateSecret: "REVALIDATE_SECRET"
1056
+ revalidateSecret: "REVALIDATE_SECRET",
1057
+ ingestSecret: "INGEST_SECRET"
905
1058
  };
906
1059
  function cleaned(value) {
907
1060
  const trimmed = value?.trim();
@@ -930,14 +1083,6 @@ async function runConvexQuery(fetchImpl, convexUrl, path, args) {
930
1083
  }
931
1084
  return body.value;
932
1085
  }
933
- function parseDocuments(rows) {
934
- const documents = [];
935
- for (const row of rows) {
936
- const parsed = documentSchema.safeParse(row);
937
- if (parsed.success) documents.push(parsed.data);
938
- }
939
- return documents;
940
- }
941
1086
  function parseRedirects(rows) {
942
1087
  const redirects = [];
943
1088
  for (const row of rows) {
@@ -948,7 +1093,7 @@ function parseRedirects(rows) {
948
1093
  }
949
1094
  function assembleLiveSource(payload, options, perspective) {
950
1095
  const locales = options.locales;
951
- const documents = parseDocuments(payload.documents);
1096
+ const { documents, issues: sourceIssues } = parseDeliveredDocuments(payload.documents);
952
1097
  const routes = [...options.routes];
953
1098
  const documentSources = perspective === "published" ? documents.map((doc) => ({ draft: doc, published: doc, versions: [] })) : documents;
954
1099
  return createStaticContentSource({
@@ -961,7 +1106,8 @@ function assembleLiveSource(payload, options, perspective) {
961
1106
  redirects: parseRedirects(payload.redirects ?? []),
962
1107
  collections: [...options.collections],
963
1108
  documents: documentSources,
964
- assets: payload.assets
1109
+ assets: payload.assets,
1110
+ ...sourceIssues.length > 0 ? { sourceIssues } : {}
965
1111
  });
966
1112
  }
967
1113
  async function loadLiveContent(options, perspective) {
@@ -1023,11 +1169,7 @@ function resolveGlobals(source, options = {}) {
1023
1169
  const navDocs = listDocs(navigationCollection);
1024
1170
  const navigation = (slot) => navDocs.find((doc) => doc.alias === slot) ?? null;
1025
1171
  const assetUrl = (value) => {
1026
- if (typeof value === "string") {
1027
- const found = assetById.get(value);
1028
- if (found) return found.url;
1029
- return value.startsWith("http") || value.startsWith("/") ? value : void 0;
1030
- }
1172
+ if (typeof value === "string") return assetById.get(value)?.url;
1031
1173
  const url = record(value).url;
1032
1174
  return typeof url === "string" && url.length > 0 ? url : void 0;
1033
1175
  };
@@ -1182,8 +1324,6 @@ function createPreviewDisableRoute() {
1182
1324
  }
1183
1325
  };
1184
1326
  }
1185
-
1186
- // src/host-kit.ts
1187
1327
  function resolveHostProject(entry) {
1188
1328
  const result = resolveProject(entry);
1189
1329
  if (result.ok) return result.project;
@@ -1284,6 +1424,11 @@ function createElytraHost(options) {
1284
1424
  });
1285
1425
  const previewRoute = () => createPreviewRoute({ token: env.draftToken });
1286
1426
  const previewDisableRoute = () => createPreviewDisableRoute();
1427
+ const ingestRoute = (overrides = {}) => createIngestRoute({
1428
+ ...overrides,
1429
+ secret: env.ingestSecret,
1430
+ siteUrl: "siteUrl" in overrides ? overrides.siteUrl : convexSiteUrl(env.convexUrl)
1431
+ });
1287
1432
  const pageOpensFullBleed = async (slug) => {
1288
1433
  const perspective = await requestPerspective();
1289
1434
  const source = await loadContent({ perspective });
@@ -1309,11 +1454,12 @@ function createElytraHost(options) {
1309
1454
  revalidateRoute,
1310
1455
  previewRoute,
1311
1456
  previewDisableRoute,
1457
+ ingestRoute,
1312
1458
  pageOpensFullBleed
1313
1459
  };
1314
1460
  }
1315
1461
  var PACKAGE = "@elytracms/next";
1316
1462
 
1317
- 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 };
1463
+ export { CANVAS_VALIDATION_SLUG, CanvasRenderer, ELYTRA_HOST_ENV_KEYS, ElytraImage, PACKAGE, assembleLiveSource, bodyItemsOf, canvasBeaconProps, canvasSitemapEntries, collectionCacheTag, createAssetImageLoader, createCanvasRoute, createCanvasSitemap, createContentSnapshot, createElytraHost, createFixedWindowLimiter, createIngestRoute, createPreviewDisableRoute, createPreviewRoute, createRevalidateRoute, createStaticContentSource, defaultIngestClientKey, defineHostComponents, docCacheTag, elytraHostImage, evaluateIngestRequest, evaluatePreviewRequest, evaluateRevalidateRequest, firstBlockFullBleed, isCrossOrigin, loadLiveContent, mergeCacheTags, mergeMetadataOverParent, nextCacheTags, openGraphLocale, pathFromSlug, projectSweepTag, readCappedBody, requestPerspective, resolveCanvasPage, resolveGlobals, resolveHostEnv, revalidationTags, routeCacheTag, safeRedirectTarget, scopeCacheTag, seoContextOf, seoMetadata, seoObjectOf, withNextImage };
1318
1464
  //# sourceMappingURL=index.js.map
1319
1465
  //# sourceMappingURL=index.js.map