@elytracms/next 0.0.88 → 0.0.90
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 +158 -112
- package/dist/index.js +51 -66
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -5,16 +5,16 @@ import { isValidElement } from 'react';
|
|
|
5
5
|
import { isBlockSequenceValueShape } from '@elytracms/core/rich-text';
|
|
6
6
|
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
7
7
|
import { cacheTags, isDocumentHistory, resolvedAsset, createContentClient, createContentContext } from '@elytracms/core/content';
|
|
8
|
-
import { documentKey, splitLocalePath, hierarchyMount, composePath, localeUrlPath, mergeRouteRecords, fallbackHomeRoutes, presentationBodyFields, splitPath, documentSchema, redirectRecordSchema } from '@elytracms/core/cms-core';
|
|
9
|
-
export { documentSchema, fallbackHomeRoutes, localeConfigSchema, localeUrlPath, mergeRouteRecords, redirectRecordSchema, resolveLocale, routeRecordSchema, splitLocalePath, textDirection } from '@elytracms/core/cms-core';
|
|
8
|
+
import { parseLocaleConfig, documentKey, splitLocalePath, hierarchyMount, composePath, localeUrlPath, mergeRouteRecords, fallbackHomeRoutes, presentationBodyFields, splitPath, documentSchema, redirectRecordSchema } from '@elytracms/core/cms-core';
|
|
9
|
+
export { documentSchema, fallbackHomeRoutes, localeConfigSchema, localeUrlPath, mergeRouteRecords, parseLocaleConfig, 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
13
|
import { COMPOSITE_KEY_SEPARATOR } from '@elytracms/core/project-graph';
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
export { z } from 'zod';
|
|
14
|
+
import { verifyRevalidationSignature, readRevalidationBody, REVALIDATE_SIGNATURE_HEADER } from '@elytracms/core/revalidation';
|
|
15
|
+
export { REVALIDATE_SIGNATURE_HEADER, revalidationEventSchema, signRevalidationBody, signedRevalidationRequest } from '@elytracms/core/revalidation';
|
|
17
16
|
import NextImage from 'next/image';
|
|
17
|
+
export { z } from 'zod';
|
|
18
18
|
|
|
19
19
|
// src/components.ts
|
|
20
20
|
function defineHostComponents(components = [], options = {}) {
|
|
@@ -134,7 +134,7 @@ function mergeCacheTags(first, ...rest) {
|
|
|
134
134
|
});
|
|
135
135
|
}
|
|
136
136
|
function createStaticContentSource(data) {
|
|
137
|
-
const locales = data.locales;
|
|
137
|
+
const locales = parseLocaleConfig(data.locales);
|
|
138
138
|
const routes = data.routes ?? [];
|
|
139
139
|
const redirects = data.redirects ?? [];
|
|
140
140
|
const assetUrl = data.assetUrl ?? ((asset) => `asset://unresolved/${asset.storageKey}`);
|
|
@@ -414,6 +414,27 @@ function nextCacheTags(tags, scope) {
|
|
|
414
414
|
for (const assetId of tags.assets) all.add(assetCacheTag(assetId, scope));
|
|
415
415
|
return [...all].sort();
|
|
416
416
|
}
|
|
417
|
+
function revalidationTags(event, project) {
|
|
418
|
+
const { scope } = project;
|
|
419
|
+
const tags = /* @__PURE__ */ new Set();
|
|
420
|
+
switch (event.kind) {
|
|
421
|
+
case "publish":
|
|
422
|
+
case "unpublish":
|
|
423
|
+
case "delete":
|
|
424
|
+
tags.add(docCacheTag(`${event.collection}:${event.id}`, scope));
|
|
425
|
+
for (const locale of project.locales) tags.add(collectionCacheTag(event.collection, locale, scope));
|
|
426
|
+
tags.add(projectSweepTag(scope));
|
|
427
|
+
break;
|
|
428
|
+
case "redirects":
|
|
429
|
+
tags.add(projectSweepTag(scope));
|
|
430
|
+
break;
|
|
431
|
+
case "asset":
|
|
432
|
+
tags.add(assetCacheTag(event.id, scope));
|
|
433
|
+
tags.add(projectSweepTag(scope));
|
|
434
|
+
break;
|
|
435
|
+
}
|
|
436
|
+
return [...tags].sort();
|
|
437
|
+
}
|
|
417
438
|
function canvasCacheKey({ scope, mode, url }) {
|
|
418
439
|
return ["elytra-route", scope ?? "", mode ?? "", url];
|
|
419
440
|
}
|
|
@@ -585,7 +606,7 @@ function createCanvasRoute(options) {
|
|
|
585
606
|
return options.metadata ? options.metadata(outcome.result, { url, perspective }) : defaultMetadata(outcome.result, outcome.assets);
|
|
586
607
|
};
|
|
587
608
|
const generateMetadata = async (props, parent) => {
|
|
588
|
-
const [page, resolvedParent] = await Promise.all([pageMetadata(props), parent
|
|
609
|
+
const [page, resolvedParent] = await Promise.all([pageMetadata(props), parent]);
|
|
589
610
|
return mergeMetadataOverParent(resolvedParent, page);
|
|
590
611
|
};
|
|
591
612
|
return { Page, generateMetadata };
|
|
@@ -719,71 +740,33 @@ function createCanvasSitemap(options) {
|
|
|
719
740
|
return canvasSitemapEntries(source, options).entries;
|
|
720
741
|
};
|
|
721
742
|
}
|
|
722
|
-
|
|
723
|
-
var revalidatePayloadSchema = z.object({
|
|
724
|
-
projectId: z.string().min(1),
|
|
725
|
-
/** The operation that fired, e.g. `documents.setState`, `publishing.publish`. */
|
|
726
|
-
event: z.string().min(1),
|
|
727
|
-
/** Fully-formatted Next tag strings (already scoped) — revalidated verbatim. */
|
|
728
|
-
tags: z.array(z.string().min(1)),
|
|
729
|
-
/** ISO timestamp of emission. Informational in v1 (no replay window). */
|
|
730
|
-
timestamp: z.string().min(1)
|
|
731
|
-
});
|
|
732
|
-
function signRevalidateBody(rawBody, secret) {
|
|
733
|
-
return `sha256=${createHmac("sha256", secret).update(rawBody, "utf8").digest("hex")}`;
|
|
734
|
-
}
|
|
735
|
-
var SIGNATURE_PREFIX = "sha256=";
|
|
736
|
-
var HEX_PATTERN = /^[0-9a-f]+$/;
|
|
737
|
-
function evaluateRevalidateRequest(rawBody, signature, secret) {
|
|
743
|
+
async function evaluateRevalidateRequest(rawBody, signature, secret) {
|
|
738
744
|
if (secret === void 0 || secret.length === 0) {
|
|
739
745
|
return { ok: false, status: 401, message: "Revalidation webhook is not configured." };
|
|
740
746
|
}
|
|
741
|
-
if (signature
|
|
747
|
+
if (typeof signature !== "string" || signature.length === 0) {
|
|
742
748
|
return { ok: false, status: 401, message: "Missing or malformed webhook signature." };
|
|
743
749
|
}
|
|
744
|
-
|
|
745
|
-
const expectedHex = createHmac("sha256", secret).update(rawBody, "utf8").digest("hex");
|
|
746
|
-
if (providedHex.length !== expectedHex.length || !HEX_PATTERN.test(providedHex) || !timingSafeEqual(Buffer.from(providedHex, "hex"), Buffer.from(expectedHex, "hex"))) {
|
|
750
|
+
if (!await verifyRevalidationSignature(rawBody, signature, secret)) {
|
|
747
751
|
return { ok: false, status: 401, message: "Invalid webhook signature." };
|
|
748
752
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
} catch {
|
|
753
|
-
return { ok: false, status: 400, message: "Webhook body is not valid JSON." };
|
|
754
|
-
}
|
|
755
|
-
const payload = revalidatePayloadSchema.safeParse(parsed);
|
|
756
|
-
if (!payload.success) {
|
|
757
|
-
return { ok: false, status: 400, message: "Webhook payload has an invalid shape." };
|
|
758
|
-
}
|
|
759
|
-
return { ok: true, payload: payload.data };
|
|
760
|
-
}
|
|
761
|
-
function corsHeaders(allowOrigin) {
|
|
762
|
-
if (allowOrigin === void 0 || allowOrigin.length === 0) return {};
|
|
763
|
-
return {
|
|
764
|
-
"access-control-allow-origin": allowOrigin,
|
|
765
|
-
"access-control-allow-methods": "POST, OPTIONS",
|
|
766
|
-
"access-control-allow-headers": `content-type, ${REVALIDATE_SIGNATURE_HEADER}`
|
|
767
|
-
};
|
|
753
|
+
const read = readRevalidationBody(rawBody);
|
|
754
|
+
if (!read.ok) return { ok: false, status: 400, message: read.message };
|
|
755
|
+
return { ok: true, event: read.event };
|
|
768
756
|
}
|
|
769
757
|
function createRevalidateRoute(options) {
|
|
770
|
-
const
|
|
758
|
+
const project = { scope: options.scope, locales: [...options.locales] };
|
|
771
759
|
return {
|
|
772
760
|
async POST(request) {
|
|
773
761
|
const rawBody = await request.text();
|
|
774
762
|
const signature = request.headers.get(REVALIDATE_SIGNATURE_HEADER);
|
|
775
|
-
const evaluation = evaluateRevalidateRequest(rawBody, signature, options.secret);
|
|
763
|
+
const evaluation = await evaluateRevalidateRequest(rawBody, signature, options.secret);
|
|
776
764
|
if (!evaluation.ok) {
|
|
777
|
-
return Response.json(
|
|
778
|
-
{ error: evaluation.message },
|
|
779
|
-
{ status: evaluation.status, headers: cors }
|
|
780
|
-
);
|
|
765
|
+
return Response.json({ error: evaluation.message }, { status: evaluation.status });
|
|
781
766
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
async OPTIONS() {
|
|
786
|
-
return new Response(null, { status: 204, headers: cors });
|
|
767
|
+
const tags = revalidationTags(evaluation.event, project);
|
|
768
|
+
for (const tag of tags) revalidateTag(tag, { expire: 0 });
|
|
769
|
+
return Response.json({ revalidated: tags });
|
|
787
770
|
}
|
|
788
771
|
};
|
|
789
772
|
}
|
|
@@ -894,8 +877,7 @@ var ELYTRA_HOST_ENV_KEYS = {
|
|
|
894
877
|
convexUrl: "ELYTRA_CONVEX_URL",
|
|
895
878
|
projectId: "ELYTRA_PROJECT_ID",
|
|
896
879
|
draftToken: "DRAFT_PREVIEW_TOKEN",
|
|
897
|
-
revalidateSecret: "REVALIDATE_SECRET"
|
|
898
|
-
studioOrigin: "STUDIO_ORIGIN"
|
|
880
|
+
revalidateSecret: "REVALIDATE_SECRET"
|
|
899
881
|
};
|
|
900
882
|
function cleaned(value) {
|
|
901
883
|
const trimmed = value?.trim();
|
|
@@ -1241,11 +1223,10 @@ function createElytraHost(options) {
|
|
|
1241
1223
|
return createCanvasRoute({
|
|
1242
1224
|
loadContent,
|
|
1243
1225
|
components,
|
|
1244
|
-
// Tag scope =
|
|
1245
|
-
// tags
|
|
1246
|
-
// EC-543: the mode is in the entry key so no world ever reads another's
|
|
1247
|
-
|
|
1248
|
-
...cache === false ? {} : { cache: { mode: contentMode, ...cache ?? { scope: contentScope } } },
|
|
1226
|
+
// Tag scope = `contentScope`, the SAME value `revalidateRoute` derives its
|
|
1227
|
+
// invalidation tags with (EC-579) — one scope, so they cannot disagree.
|
|
1228
|
+
// EC-543: the mode is in the entry key so no world ever reads another's entries.
|
|
1229
|
+
...cache === false ? {} : { cache: { mode: contentMode, scope: contentScope } },
|
|
1249
1230
|
...rest
|
|
1250
1231
|
});
|
|
1251
1232
|
};
|
|
@@ -1253,7 +1234,11 @@ function createElytraHost(options) {
|
|
|
1253
1234
|
// An unset secret keeps the endpoint CLOSED (401) — the kit never ships a
|
|
1254
1235
|
// default secret (an auth-shaped no-op is worse than an explicit gap).
|
|
1255
1236
|
secret: "secret" in overrides ? overrides.secret : env.revalidateSecret,
|
|
1256
|
-
|
|
1237
|
+
// EC-579: the receiver's OWN project — the scope its cache entries are written
|
|
1238
|
+
// under (`canvasRoute` above) and every locale it serves. The emitter sends only
|
|
1239
|
+
// the event, so this is the one place the scope and locales are read for it.
|
|
1240
|
+
scope: contentScope,
|
|
1241
|
+
locales: options.locales.locales
|
|
1257
1242
|
});
|
|
1258
1243
|
const previewRoute = () => createPreviewRoute({ token: env.draftToken });
|
|
1259
1244
|
const previewDisableRoute = () => createPreviewDisableRoute();
|
|
@@ -1284,6 +1269,6 @@ function createElytraHost(options) {
|
|
|
1284
1269
|
}
|
|
1285
1270
|
var PACKAGE = "@elytracms/next";
|
|
1286
1271
|
|
|
1287
|
-
export { CanvasRenderer, ELYTRA_HOST_ENV_KEYS, ElytraImage, PACKAGE,
|
|
1272
|
+
export { CanvasRenderer, ELYTRA_HOST_ENV_KEYS, ElytraImage, PACKAGE, assembleLiveSource, bodyItemsOf, canvasPageMetadata, canvasSitemapEntries, collectionCacheTag, createAssetImageLoader, createCanvasRoute, createCanvasSitemap, createContentSnapshot, createElytraHost, createPreviewDisableRoute, createPreviewRoute, createRevalidateRoute, createStaticContentSource, defineHostComponents, docCacheTag, elytraHostImage, evaluatePreviewRequest, evaluateRevalidateRequest, firstBlockFullBleed, loadCachedEntry, loadLiveContent, mergeCacheTags, mergeMetadataOverParent, nextCacheTags, nextImagePrimitive, openGraphLocale, pathFromSlug, projectSweepTag, resolveCanvasPage, resolveGlobals, resolveHostEnv, revalidationTags, routeCacheTag, safeRedirectTarget, scopeCacheTag };
|
|
1288
1273
|
//# sourceMappingURL=index.js.map
|
|
1289
1274
|
//# sourceMappingURL=index.js.map
|