@elytracms/next 0.0.93 → 0.0.95

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,15 +1,17 @@
1
1
  import { registerComponents, createRenderContext, renderDocumentBodyItems, documentBodyItems, serializeListingQuery, documentBodyRichTextValues, documentBodyCompositionNodes, richTextEmbedsInValue, collectReferencedAssetIds, collectRichTextRefsInValue, collectReferencedRelations, collectReferencedListings, collectReferencedRichTextLinks, listingRequestOf } from '@elytracms/core/runtime-renderer';
2
2
  import { isBlockSequenceValueShape } from '@elytracms/core/rich-text';
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';
3
+ import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
4
+ import { cacheTags, isDocumentHistory, assetWithoutUrlIssue, resolvedAsset, parseDeliveredDocuments, parseDeliveredRedirects, createContentClient, createContentContext } from '@elytracms/core/content';
5
+ import { DEFAULT_SEO_FIELD, parseLocaleConfig, documentKey, readLiveRedirects, SEO_SUBFIELDS, splitLocalePath, DRAFT_PERSPECTIVE, splitPath, localeUrlPath, variantOf, DRAFT_VARIANT, createRouter, presentationBodyFields } from '@elytracms/core/cms-core';
6
6
  export { DRAFT_PERSPECTIVE, documentSchema, localeConfigSchema, localeUrlPath, parseLocaleConfig, redirectRecordSchema, resolveLocale, routeRecordSchema, splitLocalePath, textDirection, variantOf } from '@elytracms/core/cms-core';
7
+ import { assetReadLocale, redirectRecordKind } from '@elytracms/core/persistence';
7
8
  import { revalidateTag, cacheTag } from 'next/cache';
8
9
  import { draftMode } from 'next/headers';
9
10
  import { redirect, notFound, permanentRedirect } from 'next/navigation';
10
11
  import { COMPOSITE_KEY_SEPARATOR } from '@elytracms/core/project-graph';
11
12
  import { verifyRevalidationSignature, readRevalidationBody, REVALIDATE_SIGNATURE_HEADER } from '@elytracms/core/revalidation';
12
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';
13
15
  import NextImage from 'next/image';
14
16
  import { normalizeImageSource } from '@elytracms/components';
15
17
  import { projectFrontendUrl, resolveProject } from '@elytracms/core/studio-core';
@@ -120,7 +122,7 @@ function createStaticContentSource(data) {
120
122
  const locales = parseLocaleConfig(data.locales);
121
123
  const routes = data.routes ?? [];
122
124
  const redirects = data.redirects ?? [];
123
- const assetUrl = data.assetUrl ?? ((asset) => `asset://unresolved/${asset.storageKey}`);
125
+ const assetUrl = (asset) => asset.url ?? data.assetUrl?.(asset);
124
126
  const collections = /* @__PURE__ */ new Map();
125
127
  for (const collection of data.collections ?? []) collections.set(collection.id, collection);
126
128
  const documents = /* @__PURE__ */ new Map();
@@ -134,12 +136,16 @@ function createStaticContentSource(data) {
134
136
  }
135
137
  const assets = /* @__PURE__ */ new Map();
136
138
  for (const record2 of data.assets ?? []) assets.set(record2.id, record2);
139
+ const sourceIssues = [
140
+ ...data.sourceIssues ?? [],
141
+ ...[...assets.values()].filter((record2) => !assetUrl(record2)).map((record2) => assetWithoutUrlIssue(record2.id))
142
+ ];
137
143
  const lookupFor = (_perspective) => ({
138
144
  collection: (id) => collections.get(id),
139
145
  document: (ref) => documents.get(documentKey(ref)),
140
146
  documents: (collectionId) => byCollection.get(collectionId) ?? [],
141
147
  asset: (id) => assets.get(id),
142
- assetUrl: (asset) => asset.url ?? assetUrl(asset)
148
+ assetUrl
143
149
  });
144
150
  const client = (init) => createContentClient({
145
151
  lookup: lookupFor(init.perspective),
@@ -150,7 +156,7 @@ function createStaticContentSource(data) {
150
156
  }),
151
157
  routes,
152
158
  redirects,
153
- ...data.sourceIssues ? { sourceIssues: data.sourceIssues } : {}
159
+ ...sourceIssues.length > 0 ? { sourceIssues } : {}
154
160
  });
155
161
  return {
156
162
  projectId: data.projectId,
@@ -161,7 +167,14 @@ function createStaticContentSource(data) {
161
167
  // `asset`-typed Image prop (an asset id) to a usable url + intrinsic
162
168
  // dimensions. Baked into the (serializable) render outcome by the route
163
169
  // helper, so a cached page keeps its images during a backend outage.
164
- assets: () => [...assets.values()].map((record2) => resolvedAsset(record2, record2.url ?? assetUrl(record2))),
170
+ // EC-632: in the requested locale (the route helper passes the request's), so a
171
+ // localized alt is baked in the locale the page renders in.
172
+ // EC-633: a record with no URL is no resolved asset — not baked, so the page renders
173
+ // the empty state the canvas renders for it (and `sourceIssues` names it).
174
+ assets: (locale) => [...assets.values()].flatMap((record2) => {
175
+ const asset = resolvedAsset(record2, assetUrl(record2), assetReadLocale(locales, locale));
176
+ return asset ? [asset] : [];
177
+ }),
165
178
  // EC-499: the schema the route helper reads a collection's declared
166
179
  // `presentation` from (which field is the delivery body, which component
167
180
  // wraps it). Already prefetched for the lookup above — exposing it costs
@@ -173,8 +186,16 @@ async function createContentSnapshot(adapter, projectId, options) {
173
186
  const documentRecords = await adapter.cms.listDocuments(projectId);
174
187
  const versionRecords = await adapter.cms.listAllDocumentVersions(projectId);
175
188
  const assetRecords = await adapter.assets.listAssets(projectId);
176
- const redirectRecords = await adapter.redirects.listRedirects(projectId);
177
- const redirects = liveRedirectRecords(redirectRecords);
189
+ const blobs = adapter.blobs;
190
+ const servedUrls = /* @__PURE__ */ new Map();
191
+ await Promise.all(
192
+ assetRecords.filter((record2) => !record2.url).map(async (record2) => {
193
+ const url = blobs.resolveUrl ? await blobs.resolveUrl(record2.storageKey).catch(() => void 0) : blobs.url(record2.storageKey);
194
+ servedUrls.set(record2.id, url || void 0);
195
+ })
196
+ );
197
+ const redirectRecords = await adapter.records(redirectRecordKind).list(projectId);
198
+ const { redirects, issues: redirectIssues } = readLiveRedirects(redirectRecords);
178
199
  const versionsByKey = /* @__PURE__ */ new Map();
179
200
  for (const record2 of versionRecords) {
180
201
  const key = `${record2.collection}:${record2.docId}`;
@@ -192,13 +213,15 @@ async function createContentSnapshot(adapter, projectId, options) {
192
213
  });
193
214
  return createStaticContentSource({
194
215
  projectId,
216
+ // EC-628: only for the stored redirects this source serves (an explicit override replaces them).
217
+ ...options.redirects === void 0 && redirectIssues.length > 0 ? { sourceIssues: redirectIssues } : {},
195
218
  locales: options.locales,
196
219
  ...options.routes ? { routes: options.routes } : {},
197
220
  redirects: options.redirects ? [...options.redirects] : redirects,
198
221
  collections: options.collections,
199
222
  documents,
200
223
  assets: assetRecords,
201
- assetUrl: (asset) => adapter.blobs.url(asset.storageKey)
224
+ assetUrl: (asset) => servedUrls.get(asset.id)
202
225
  });
203
226
  }
204
227
  function seoContextOf(result, assets = []) {
@@ -283,6 +306,15 @@ function canvasWrapContext(result, info) {
283
306
  result
284
307
  };
285
308
  }
309
+ function canvasBeaconProps(result, info) {
310
+ const document = result.document;
311
+ return {
312
+ path: info.url,
313
+ status: 200,
314
+ locale: result.tags.locale,
315
+ ...document ? { collection: document.collection, documentId: document.id } : {}
316
+ };
317
+ }
286
318
  function pathFromSlug(slug) {
287
319
  return "/" + (slug ?? []).join("/");
288
320
  }
@@ -399,7 +431,7 @@ async function resolveCanvasPage(source, url, perspective, options = {}) {
399
431
  }
400
432
  const presentation = presentationFor(source, result);
401
433
  const body = pageBodyOf(result, presentation);
402
- const allAssets = source.assets?.() ?? [];
434
+ const allAssets = source.assets?.(locale) ?? [];
403
435
  const bakedAssets = options.registry ? scopeAssetsToPage(
404
436
  allAssets,
405
437
  options.registry,
@@ -590,8 +622,13 @@ function createCanvasRoute(options) {
590
622
  presentation: outcome.presentation
591
623
  }
592
624
  );
593
- if (!options.wrap) return rendered;
594
- return options.wrap(rendered, canvasWrapContext(outcome.result, { url, perspective }));
625
+ const page = options.wrap ? options.wrap(rendered, canvasWrapContext(outcome.result, { url, perspective })) : rendered;
626
+ const Beacon = options.analytics;
627
+ if (!Beacon || perspective !== "published") return page;
628
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
629
+ page,
630
+ /* @__PURE__ */ jsx(Beacon, { ...canvasBeaconProps(outcome.result, { url}) })
631
+ ] });
595
632
  }
596
633
  }
597
634
  };
@@ -758,6 +795,177 @@ function createRevalidateRoute(options) {
758
795
  }
759
796
  };
760
797
  }
798
+ function createFixedWindowLimiter(options) {
799
+ const windowMs = options.windowMs ?? 6e4;
800
+ const now = options.now ?? Date.now;
801
+ const windows = /* @__PURE__ */ new Map();
802
+ return {
803
+ hit(key) {
804
+ const at = now();
805
+ for (const [held, window2] of windows) if (at - window2.windowStart >= windowMs) windows.delete(held);
806
+ const window = windows.get(key);
807
+ if (!window) {
808
+ windows.set(key, { windowStart: at, count: 1 });
809
+ return { allowed: true };
810
+ }
811
+ if (window.count >= options.limit) {
812
+ return { allowed: false, retryAfterSeconds: Math.max(1, Math.ceil((window.windowStart + windowMs - at) / 1e3)) };
813
+ }
814
+ window.count += 1;
815
+ return { allowed: true };
816
+ },
817
+ size: () => windows.size
818
+ };
819
+ }
820
+ function defaultIngestClientKey(request) {
821
+ const forwarded = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim();
822
+ if (forwarded) return forwarded;
823
+ const real = request.headers.get("x-real-ip")?.trim();
824
+ return real || "unknown";
825
+ }
826
+ function isCrossOrigin(request) {
827
+ const site = request.headers.get("sec-fetch-site");
828
+ if (site && site !== "same-origin" && site !== "none") return true;
829
+ const origin = request.headers.get("origin");
830
+ if (!origin) return false;
831
+ try {
832
+ return new URL(origin).origin !== new URL(request.url).origin;
833
+ } catch {
834
+ return true;
835
+ }
836
+ }
837
+ async function evaluateIngestRequest(input) {
838
+ const { request } = input;
839
+ if (request.method !== "POST") {
840
+ return { ok: false, status: 405, body: { error: "Only POST is accepted." }, headers: { allow: "POST" } };
841
+ }
842
+ if (!input.configured) {
843
+ return { ok: false, status: 503, body: { error: "Ingest is not configured on this site." } };
844
+ }
845
+ if (isCrossOrigin(request)) {
846
+ return { ok: false, status: 403, body: { error: "Cross-origin requests are not accepted." } };
847
+ }
848
+ const limited = input.limiter.hit(input.clientKey(request));
849
+ if (!limited.allowed) {
850
+ return {
851
+ ok: false,
852
+ status: 429,
853
+ body: { error: "Too many requests \u2014 try again shortly." },
854
+ headers: { "retry-after": String(limited.retryAfterSeconds) }
855
+ };
856
+ }
857
+ if (input.rawBody === void 0) {
858
+ return { ok: false, status: 413, body: { error: "The payload is too large." } };
859
+ }
860
+ const read = readIngestPayload(input.rawBody);
861
+ if (!read.ok) {
862
+ return { ok: false, status: 400, body: { error: read.message, ...read.issues ? { issues: read.issues } : {} } };
863
+ }
864
+ if (input.botGate) {
865
+ const verdict = await input.botGate({ request, payload: read.value });
866
+ if (!verdict.allow) {
867
+ return { ok: false, status: 403, body: { error: "Refused by the bot gate.", gate: "bot", reason: verdict.reason } };
868
+ }
869
+ }
870
+ if (!input.enrich) return { ok: true, payload: read.value };
871
+ return enrichPayload(input.enrich, request, read.value);
872
+ }
873
+ async function enrichPayload(enrich, request, payload) {
874
+ let enriched;
875
+ try {
876
+ enriched = await enrich({ request, payload });
877
+ } catch (error) {
878
+ const message = error instanceof Error ? error.message : String(error);
879
+ return { ok: false, status: 500, body: { error: `The site's ingest enrichment failed: ${message}` } };
880
+ }
881
+ const body = JSON.stringify(enriched ?? null);
882
+ const reread = readIngestPayload(body);
883
+ if (!reread.ok) {
884
+ return {
885
+ ok: false,
886
+ status: 500,
887
+ body: { error: "The site's ingest enrichment returned an invalid payload.", ...reread.issues ? { issues: reread.issues } : {} }
888
+ };
889
+ }
890
+ if (new TextEncoder().encode(body).byteLength > INGEST_MAX_BODY_BYTES) {
891
+ return { ok: false, status: 500, body: { error: "The site's ingest enrichment grew the payload past the receiver's cap." } };
892
+ }
893
+ return { ok: true, payload: reread.value };
894
+ }
895
+ async function readCappedBody(request, maxBytes = INGEST_MAX_BODY_BYTES) {
896
+ const declared = Number(request.headers.get("content-length"));
897
+ if (Number.isFinite(declared) && declared > maxBytes) return void 0;
898
+ if (!request.body) return "";
899
+ const reader = request.body.getReader();
900
+ const chunks = [];
901
+ let total = 0;
902
+ for (; ; ) {
903
+ const { done, value } = await reader.read();
904
+ if (done) break;
905
+ total += value.byteLength;
906
+ if (total > maxBytes) {
907
+ await reader.cancel().catch(() => void 0);
908
+ return void 0;
909
+ }
910
+ chunks.push(value);
911
+ }
912
+ const bytes = new Uint8Array(total);
913
+ let offset = 0;
914
+ for (const chunk of chunks) {
915
+ bytes.set(chunk, offset);
916
+ offset += chunk.byteLength;
917
+ }
918
+ return new TextDecoder().decode(bytes);
919
+ }
920
+ var NO_STORE = { "cache-control": "no-store" };
921
+ function createIngestRoute(options) {
922
+ const maxBodyBytes = options.maxBodyBytes ?? INGEST_MAX_BODY_BYTES;
923
+ if (maxBodyBytes > INGEST_MAX_BODY_BYTES || maxBodyBytes <= 0) {
924
+ throw new Error(`createIngestRoute: maxBodyBytes must be between 1 and ${INGEST_MAX_BODY_BYTES} (the receiver's cap).`);
925
+ }
926
+ const limiter = createFixedWindowLimiter({
927
+ limit: options.maxRequestsPerMinute ?? 30,
928
+ ...options.now ? { now: options.now } : {}
929
+ });
930
+ const clientKey = options.clientKey ?? defaultIngestClientKey;
931
+ const receiver = options.siteUrl ? `${options.siteUrl.replace(/\/+$/, "")}${INGEST_RECEIVER_PATH}` : void 0;
932
+ const fetchImpl = options.fetchImpl ?? fetch;
933
+ return {
934
+ async POST(request) {
935
+ const rawBody = request.method === "POST" ? await readCappedBody(request, maxBodyBytes) : "";
936
+ const evaluation = await evaluateIngestRequest({
937
+ request,
938
+ rawBody,
939
+ configured: options.secret !== void 0 && options.secret.length > 0 && receiver !== void 0,
940
+ limiter,
941
+ clientKey,
942
+ ...options.botGate ? { botGate: options.botGate } : {},
943
+ ...options.enrich ? { enrich: options.enrich } : {}
944
+ });
945
+ if (!evaluation.ok) {
946
+ return Response.json(evaluation.body, { status: evaluation.status, headers: { ...NO_STORE, ...evaluation.headers } });
947
+ }
948
+ const signed = await signedIngestRequest(evaluation.payload, options.secret);
949
+ let answer;
950
+ try {
951
+ answer = await fetchImpl(receiver, { method: "POST", headers: signed.headers, body: signed.body });
952
+ } catch {
953
+ return Response.json({ error: "The ingest receiver could not be reached." }, { status: 502, headers: NO_STORE });
954
+ }
955
+ if (answer.status === 401) {
956
+ return Response.json(
957
+ { error: "The ingest receiver refused this site's signature \u2014 INGEST_SECRET must equal the deployment's ELYTRA_INGEST_SECRET." },
958
+ { status: 502, headers: NO_STORE }
959
+ );
960
+ }
961
+ const text = await answer.text();
962
+ return new Response(text, {
963
+ status: answer.status,
964
+ headers: { ...NO_STORE, "content-type": answer.headers.get("content-type") ?? "application/json" }
965
+ });
966
+ }
967
+ };
968
+ }
761
969
  var LQIP_MAX_EDGE = 24;
762
970
  function fitLqipLongEdge(width, height) {
763
971
  const scale = Math.min(1, LQIP_MAX_EDGE / Math.max(width, height));
@@ -866,7 +1074,8 @@ var ELYTRA_HOST_ENV_KEYS = {
866
1074
  convexUrl: "ELYTRA_CONVEX_URL",
867
1075
  projectId: "ELYTRA_PROJECT_ID",
868
1076
  draftToken: "DRAFT_PREVIEW_TOKEN",
869
- revalidateSecret: "REVALIDATE_SECRET"
1077
+ revalidateSecret: "REVALIDATE_SECRET",
1078
+ ingestSecret: "INGEST_SECRET"
870
1079
  };
871
1080
  function cleaned(value) {
872
1081
  const trimmed = value?.trim();
@@ -895,17 +1104,11 @@ async function runConvexQuery(fetchImpl, convexUrl, path, args) {
895
1104
  }
896
1105
  return body.value;
897
1106
  }
898
- function parseRedirects(rows) {
899
- const redirects = [];
900
- for (const row of rows) {
901
- const parsed = redirectRecordSchema.safeParse(row);
902
- if (parsed.success) redirects.push(parsed.data);
903
- }
904
- return redirects;
905
- }
906
1107
  function assembleLiveSource(payload, options, perspective) {
907
1108
  const locales = options.locales;
908
- const { documents, issues: sourceIssues } = parseDeliveredDocuments(payload.documents);
1109
+ const { documents, issues: documentIssues } = parseDeliveredDocuments(payload.documents);
1110
+ const delivered = parseDeliveredRedirects(payload.redirects ?? [], payload.redirectIssues ?? []);
1111
+ const sourceIssues = [...documentIssues, ...delivered.issues];
909
1112
  const routes = [...options.routes];
910
1113
  const documentSources = perspective === "published" ? documents.map((doc) => ({ draft: doc, published: doc, versions: [] })) : documents;
911
1114
  return createStaticContentSource({
@@ -915,7 +1118,7 @@ function assembleLiveSource(payload, options, perspective) {
915
1118
  // Redirects are editorial DATA (EC-303): the ENABLED ones ship in the
916
1119
  // snapshot, so they change dynamically (no redeploy). Native next.config
917
1120
  // redirects run before this.
918
- redirects: parseRedirects(payload.redirects ?? []),
1121
+ redirects: delivered.redirects,
919
1122
  collections: [...options.collections],
920
1123
  documents: documentSources,
921
1124
  assets: payload.assets,
@@ -971,7 +1174,7 @@ function resolveGlobals(source, options = {}) {
971
1174
  const navigationCollection = conventions.navigationCollection ?? "navigation";
972
1175
  const itemsFields = conventions.navigationItemsFields ?? DEFAULT_ITEMS_FIELDS;
973
1176
  const client = source.client({ perspective, ...options.locale ? { locale: options.locale } : {} });
974
- const assets = source.assets?.() ?? [];
1177
+ const assets = source.assets?.(options.locale) ?? [];
975
1178
  const assetById = new Map(assets.map((asset) => [asset.id, asset]));
976
1179
  const listDocs = (collection) => {
977
1180
  const result = client.listDocuments(collection);
@@ -1136,8 +1339,6 @@ function createPreviewDisableRoute() {
1136
1339
  }
1137
1340
  };
1138
1341
  }
1139
-
1140
- // src/host-kit.ts
1141
1342
  function resolveHostProject(entry) {
1142
1343
  const result = resolveProject(entry);
1143
1344
  if (result.ok) return result.project;
@@ -1238,6 +1439,11 @@ function createElytraHost(options) {
1238
1439
  });
1239
1440
  const previewRoute = () => createPreviewRoute({ token: env.draftToken });
1240
1441
  const previewDisableRoute = () => createPreviewDisableRoute();
1442
+ const ingestRoute = (overrides = {}) => createIngestRoute({
1443
+ ...overrides,
1444
+ secret: env.ingestSecret,
1445
+ siteUrl: "siteUrl" in overrides ? overrides.siteUrl : convexSiteUrl(env.convexUrl)
1446
+ });
1241
1447
  const pageOpensFullBleed = async (slug) => {
1242
1448
  const perspective = await requestPerspective();
1243
1449
  const source = await loadContent({ perspective });
@@ -1263,11 +1469,12 @@ function createElytraHost(options) {
1263
1469
  revalidateRoute,
1264
1470
  previewRoute,
1265
1471
  previewDisableRoute,
1472
+ ingestRoute,
1266
1473
  pageOpensFullBleed
1267
1474
  };
1268
1475
  }
1269
1476
  var PACKAGE = "@elytracms/next";
1270
1477
 
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 };
1478
+ 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 };
1272
1479
  //# sourceMappingURL=index.js.map
1273
1480
  //# sourceMappingURL=index.js.map