@sitecore-content-sdk/nextjs 2.4.1-canary.20260910100657 → 2.4.1

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.
@@ -6,7 +6,6 @@ exports.normalizeSitecoreItemIdForCacheTag = normalizeSitecoreItemIdForCacheTag;
6
6
  exports.buildSitecoreRouteCacheTag = buildSitecoreRouteCacheTag;
7
7
  exports.buildSitecoreItemCacheTag = buildSitecoreItemCacheTag;
8
8
  exports.buildSitecoreDictionaryCacheTag = buildSitecoreDictionaryCacheTag;
9
- exports.buildSitecoreDictionaryCacheTagsFromSites = buildSitecoreDictionaryCacheTagsFromSites;
10
9
  exports.buildSitecoreItemCacheTagFromRouteData = buildSitecoreItemCacheTagFromRouteData;
11
10
  exports.dedupeSitecoreCacheTags = dedupeSitecoreCacheTags;
12
11
  /**
@@ -28,12 +27,20 @@ function sanitizeSitecoreCacheTagSegment(value) {
28
27
  .replace(/[/:\s]+/g, '_');
29
28
  }
30
29
  /**
31
- * Normalizes a Sitecore item GUID for use in cache tags (lowercase, no braces).
30
+ * Normalizes a Sitecore item GUID for use in cache tags.
31
+ * Lowercases, strips braces, and canonicalizes 32-character hex GUIDs to hyphenated form
32
+ * so layout-service ids (`{guid}`) and Experience Edge identifiers (`GUID` without hyphens)
33
+ * produce the same `sc:item:` tag.
32
34
  * @param {string} itemId - Sitecore item id or GUID string.
33
35
  * @internal
34
36
  */
35
37
  function normalizeSitecoreItemIdForCacheTag(itemId) {
36
- return itemId.trim().toLowerCase().replace(/[{}]/g, '');
38
+ const cleaned = itemId.trim().toLowerCase().replace(/[{}]/g, '');
39
+ const hex = cleaned.replace(/-/g, '');
40
+ if (/^[0-9a-f]{32}$/.test(hex)) {
41
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
42
+ }
43
+ return cleaned;
37
44
  }
38
45
  /**
39
46
  * Tag for a resolved route (site + language + logical path). Use for URL-level invalidation.
@@ -68,27 +75,6 @@ function buildSitecoreDictionaryCacheTag(params) {
68
75
  const locale = sanitizeSitecoreCacheTagSegment(params.locale);
69
76
  return `${exports.SITECORE_CONTENT_CACHE_TAG_PREFIX}:dict:${site}:${locale}`;
70
77
  }
71
- /**
72
- * Builds deduplicated dictionary cache tags from a sites list.
73
- * @param {BuildSitecoreDictionaryCacheTagsFromSitesParams} params - Sites list and fallback locale.
74
- * @internal
75
- */
76
- function buildSitecoreDictionaryCacheTagsFromSites(params) {
77
- var _a;
78
- const seen = new Set();
79
- const out = [];
80
- const push = (tag) => {
81
- if (!seen.has(tag)) {
82
- seen.add(tag);
83
- out.push(tag);
84
- }
85
- };
86
- for (const site of params.sites) {
87
- const locale = ((_a = site.language) === null || _a === void 0 ? void 0 : _a.trim()) ? site.language : params.baseLocale;
88
- push(buildSitecoreDictionaryCacheTag({ site: site.name, locale }));
89
- }
90
- return out;
91
- }
92
78
  /**
93
79
  * Builds an item cache tag from Sitecore layout route data when `itemId` is present.
94
80
  * Prefers `itemLanguage` from Sitecore when set; otherwise uses `fallbackLocale`.
@@ -1,8 +1,14 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.extractSitecoreEdgeContentId = extractSitecoreEdgeContentId;
7
+ exports.isSitecoreDictionaryEntryUpdate = isSitecoreDictionaryEntryUpdate;
8
+ exports.resolveSitecoreDictionarySiteNameFromIdentifier = resolveSitecoreDictionarySiteNameFromIdentifier;
4
9
  exports.collectSitecoreTagsFromEdgeRevalidateRequestBody = collectSitecoreTagsFromEdgeRevalidateRequestBody;
5
10
  const sitecore_cache_tags_1 = require("./sitecore-cache-tags");
11
+ const debug_1 = __importDefault(require("../debug"));
6
12
  /**
7
13
  * Strips Experience Edge style suffixes from an `identifier` so the value can be used as an item id in cache tags.
8
14
  * Handles `{GUID}`, `{GUID}-media`, `{GUID}-layout` style strings.
@@ -16,51 +22,99 @@ function extractSitecoreEdgeContentId(identifier) {
16
22
  const trimmed = identifier.trim();
17
23
  return trimmed.replace(/-(?:media|layout)$/i, '');
18
24
  }
19
- const FULL_TAG_PREFIX = `${sitecore_cache_tags_1.SITECORE_CONTENT_CACHE_TAG_PREFIX}:`;
20
25
  /**
21
- * @param {string} value - Candidate tag string from a webhook body.
22
- * @returns True when `value` is already a full `sc:` content cache tag.
26
+ * `entity_definition` value Experience Edge sends for Dictionary entry updates. This is the literal value Experience Edge sends.
27
+ * @internal
28
+ */
29
+ const DICTIONARY_ENTRY_ENTITY_DEFINITION = 'DictionaryEntry';
30
+ /**
31
+ * Whether a webhook update row is a Dictionary entry change (`entity_definition: "DictionaryEntry"`).
32
+ * @param {string} [entityDefinition] - `entity_definition` from a webhook update row.
33
+ * @internal
34
+ */
35
+ function isSitecoreDictionaryEntryUpdate(entityDefinition) {
36
+ return entityDefinition === DICTIONARY_ENTRY_ENTITY_DEFINITION;
37
+ }
38
+ /**
39
+ * Resolves which configured site a Dictionary entry webhook `identifier` belongs to.
40
+ *
41
+ * Dictionary identifiers follow `<site name>-<dictionary folder id>-<value>-<language>`, but the
42
+ * folder id, value, and language segments can themselves contain hyphens (and the value can contain
43
+ * spaces), so splitting the identifier on `-` is not reliable. Instead this matches the identifier
44
+ * against the known configured site names, preferring the longest name so a more specific name wins
45
+ * over a shorter overlapping one (e.g. `my-site-2` over `my-site`).
46
+ * @param {string} identifier - Raw `identifier` from a Dictionary entry update row.
47
+ * @param {string[]} siteNames - Configured site names (e.g. from `.sitecore/sites.json`).
48
+ * @returns The matched site name, or `undefined` when no configured site name prefixes `identifier`.
49
+ * @internal
50
+ */
51
+ function resolveSitecoreDictionarySiteNameFromIdentifier(identifier, siteNames) {
52
+ const normalized = identifier.trim().toLowerCase();
53
+ if (!normalized) {
54
+ return undefined;
55
+ }
56
+ return matchSitecoreDictionarySiteName(normalized, sortSiteNamesByLengthDesc(siteNames));
57
+ }
58
+ /**
59
+ * Sorts site names longest-first, for {@link matchSitecoreDictionarySiteName}.
60
+ * @param {string[]} siteNames - Configured site names.
61
+ * @internal
23
62
  */
24
- function isFullSitecoreContentCacheTag(value) {
25
- return value.startsWith(FULL_TAG_PREFIX);
63
+ function sortSiteNamesByLengthDesc(siteNames) {
64
+ return [...siteNames].sort((a, b) => b.length - a.length);
65
+ }
66
+ /**
67
+ * Matches a normalized (trimmed, lowercased) identifier against site names already sorted
68
+ * longest-first. Split out of {@link resolveSitecoreDictionarySiteNameFromIdentifier} so a request
69
+ * with many Dictionary entry updates in one payload sorts `siteNames` once instead of per update.
70
+ * @param {string} normalizedIdentifier - Identifier, already trimmed and lowercased.
71
+ * @param {string[]} siteNamesByLengthDesc - Site names sorted longest-first.
72
+ * @internal
73
+ */
74
+ function matchSitecoreDictionarySiteName(normalizedIdentifier, siteNamesByLengthDesc) {
75
+ for (const siteName of siteNamesByLengthDesc) {
76
+ const prefix = `${siteName.trim().toLowerCase()}-`;
77
+ if (prefix.length > 1 && normalizedIdentifier.startsWith(prefix)) {
78
+ return siteName;
79
+ }
80
+ }
81
+ return undefined;
26
82
  }
27
83
  /**
28
84
  * Maps an Experience Edge webhook JSON body to Content SDK cache tag strings used by
29
85
  * {@link collectSitecorePageCacheTags} / {@link buildSitecoreItemCacheTag} (`sc:item:...`), so
30
86
  * `revalidateTag` matches tags registered during cached reads.
31
- * **`updates`** rows resolve to **`sc:item:…`** (locale from `entity_culture` or `defaultLocale`). **`tags`**: full `sc:` strings pass through; bare ids become **`sc:item:…`** with `defaultLocale`. Route/variant tags are not inferred.
32
- * @param {SitecoreEdgeRevalidateRequestBody | null | undefined} body - Webhook JSON body (tags and/or updates).
33
- * @param {CollectSitecoreTagsFromEdgeBodyOptions} options - Default locale when culture is missing on an update or bare tag.
87
+ * **`updates`** rows resolve to **`sc:item:…`** (locale from `entity_culture` or `defaultLocale`), except
88
+ * rows where `entity_definition` is `"DictionaryEntry"`, which resolve to **`sc:dict:…`** scoped to the
89
+ * site resolved from the identifier (see {@link resolveSitecoreDictionarySiteNameFromIdentifier}) and the
90
+ * same locale. Route/variant tags are not inferred.
91
+ * @param {SitecoreEdgeRevalidateRequestBody | null | undefined} body - Webhook JSON body (updates).
92
+ * @param {CollectSitecoreTagsFromEdgeBodyOptions} options - Default locale, and site names for Dictionary entry updates.
34
93
  * @internal
35
94
  */
36
95
  function collectSitecoreTagsFromEdgeRevalidateRequestBody(body, options) {
37
96
  var _a, _b, _c, _d;
38
- const { defaultLocale } = options;
97
+ const { defaultLocale, siteNames = [] } = options;
98
+ const siteNamesByLengthDesc = sortSiteNamesByLengthDesc(siteNames);
39
99
  const out = [];
40
- for (const raw of (_a = body === null || body === void 0 ? void 0 : body.tags) !== null && _a !== void 0 ? _a : []) {
41
- if (typeof raw !== 'string') {
42
- continue;
43
- }
44
- const s = raw.trim();
45
- if (!s) {
46
- continue;
47
- }
48
- if (isFullSitecoreContentCacheTag(s)) {
49
- out.push(s);
50
- }
51
- else {
52
- const id = extractSitecoreEdgeContentId(s);
53
- if (id) {
54
- out.push((0, sitecore_cache_tags_1.buildSitecoreItemCacheTag)({ itemId: id, locale: defaultLocale }));
100
+ for (const u of (_a = body === null || body === void 0 ? void 0 : body.updates) !== null && _a !== void 0 ? _a : []) {
101
+ const locale = ((_b = u === null || u === void 0 ? void 0 : u.entity_culture) === null || _b === void 0 ? void 0 : _b.trim()) || defaultLocale;
102
+ if (isSitecoreDictionaryEntryUpdate(u === null || u === void 0 ? void 0 : u.entity_definition)) {
103
+ const identifier = ((_c = u === null || u === void 0 ? void 0 : u.identifier) !== null && _c !== void 0 ? _c : '').trim().toLowerCase();
104
+ const site = identifier
105
+ ? matchSitecoreDictionarySiteName(identifier, siteNamesByLengthDesc)
106
+ : undefined;
107
+ if (!site) {
108
+ debug_1.default.revalidate('could not resolve site for Dictionary entry identifier %o, skipping', identifier);
109
+ continue;
55
110
  }
111
+ out.push((0, sitecore_cache_tags_1.buildSitecoreDictionaryCacheTag)({ site, locale }));
112
+ continue;
56
113
  }
57
- }
58
- for (const u of (_b = body === null || body === void 0 ? void 0 : body.updates) !== null && _b !== void 0 ? _b : []) {
59
- const id = extractSitecoreEdgeContentId((_c = u === null || u === void 0 ? void 0 : u.identifier) !== null && _c !== void 0 ? _c : '');
114
+ const id = extractSitecoreEdgeContentId((_d = u === null || u === void 0 ? void 0 : u.identifier) !== null && _d !== void 0 ? _d : '');
60
115
  if (!id) {
61
116
  continue;
62
117
  }
63
- const locale = ((_d = u === null || u === void 0 ? void 0 : u.entity_culture) === null || _d === void 0 ? void 0 : _d.trim()) || defaultLocale;
64
118
  out.push((0, sitecore_cache_tags_1.buildSitecoreItemCacheTag)({ itemId: id, locale }));
65
119
  }
66
120
  return (0, sitecore_cache_tags_1.dedupeSitecoreCacheTags)(out).filter(Boolean);
@@ -27,8 +27,8 @@ function routeSegmentsFromPathname(pathname) {
27
27
  * `buildSitecoreDictionaryCacheTag` on a dedicated `use cache` helper).
28
28
  *
29
29
  * Registers **`sc:route:…`** and **`sc:item:…`** (when layout has `itemId`). Edge-style webhooks emit
30
- * item ids, which the Sitecore revalidate route handler maps to **`sc:item:…`**; route tags are only
31
- * invalidated when callers send the full `sc:route:…` strings in the `tags[]` array of the same revalidate request.
30
+ * item ids, which the Sitecore revalidate route handler maps to **`sc:item:…`**; route tags are not
31
+ * invalidated by that webhook flow.
32
32
  *
33
33
  * Personalization variants are isolated naturally by URL path (each variant rewrite yields a distinct
34
34
  * Cache Components key) so no `sc:pvv:…` tag is added here. If a personalize-specific webhook is wired
@@ -29,27 +29,21 @@ function resolveConfiguredRevalidateSecret(secretOption, envValue) {
29
29
  *
30
30
  * - **`updates[]`** — Sitecore publish-event rows. Each row's `identifier` (with `-media` / `-layout`
31
31
  * suffix stripped) maps to an `sc:item:<id>:<locale>` tag, using `entity_culture` for locale
32
- * (falling back to the handler's `defaultLocale`).
33
- * - **`tags[]`** — pass-through and convenience array:
34
- * - Strings already starting with `sc:` are used verbatim (e.g. `sc:route:...`, `sc:item:...`, `sc:dict:...`).
35
- * - Bare values are treated as Sitecore item ids and mapped to `sc:item:<id>:<defaultLocale>`.
36
- *
37
- * When **`sites`** is configured, the handler also appends one `sc:dict:<site>:<locale>` tag per
38
- * site so dictionary updates flow through the same call.
32
+ * (falling back to the handler's `defaultLocale`) — except rows where `entity_definition` is
33
+ * `"DictionaryEntry"`, which map to `sc:dict:<site>:<locale>` for the site resolved from the
34
+ * identifier via the configured **`sites`** option (skipped, with a debug log, when no configured
35
+ * site matches). Only updates that are actually Dictionary changes revalidate dictionary tags — a
36
+ * webhook for an unrelated item never touches them.
39
37
  *
40
38
  * Auth (optional): when `SITECORE_REVALIDATE_SECRET` (or the `secret` option) is non-empty, callers must
41
39
  * send the same value in the **`x-revalidate-secret`** header. When unset or blank, no header is required.
42
- * @param {SitecoreRevalidateRouteHandlerOptions} [options] - Optional inline `secret`, `cacheProfile`, locale, sites, and dictionary options.
40
+ * @param {SitecoreRevalidateRouteHandlerOptions} [options] - Optional inline `secret`, `cacheProfile`, locale, and sites options.
43
41
  * @public
44
42
  */
45
43
  function createSitecoreRevalidateRouteHandler(options = {}) {
44
+ var _a;
46
45
  const { defaultLocale = 'en', sites, secret, cacheProfile = 'max' } = options;
47
- const dictionaryTags = sites !== undefined
48
- ? (0, sitecore_cache_tags_1.buildSitecoreDictionaryCacheTagsFromSites)({
49
- sites,
50
- baseLocale: defaultLocale,
51
- })
52
- : [];
46
+ const siteNames = (_a = sites === null || sites === void 0 ? void 0 : sites.map((site) => site.name)) !== null && _a !== void 0 ? _a : [];
53
47
  const POST = async (req) => {
54
48
  var _a, _b, _c, _d, _e, _f, _g, _h;
55
49
  const startTimestamp = Date.now();
@@ -82,18 +76,13 @@ function createSitecoreRevalidateRouteHandler(options = {}) {
82
76
  invocation_id: (_a = webhookBody.invocation_id) !== null && _a !== void 0 ? _a : null,
83
77
  continues: (_b = webhookBody.continues) !== null && _b !== void 0 ? _b : false,
84
78
  updatesCount: (_d = (_c = webhookBody.updates) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0,
85
- tagsCount: Array.isArray(webhookBody.tags) ? webhookBody.tags.length : 0,
86
- dictionaryTagsCount: dictionaryTags.length,
87
79
  defaultLocale,
88
80
  });
89
- const tags = (0, sitecore_cache_tags_1.dedupeSitecoreCacheTags)([
90
- ...(0, sitecore_edge_webhook_revalidation_1.collectSitecoreTagsFromEdgeRevalidateRequestBody)(webhookBody, { defaultLocale }),
91
- ...dictionaryTags,
92
- ]);
81
+ const tags = (0, sitecore_cache_tags_1.dedupeSitecoreCacheTags)((0, sitecore_edge_webhook_revalidation_1.collectSitecoreTagsFromEdgeRevalidateRequestBody)(webhookBody, { defaultLocale, siteNames }));
93
82
  if (tags.length === 0) {
94
83
  debug_1.default.revalidate('sitecore revalidate: no tags resolved in %dms', Date.now() - startTimestamp);
95
84
  return server_1.NextResponse.json({
96
- error: 'Provide non-empty `updates` (with identifiers) and/or `tags` that resolve to at least one cache tag.',
85
+ error: 'Provide non-empty `updates` (with identifiers) that resolve to at least one cache tag.',
97
86
  }, { status: 400 });
98
87
  }
99
88
  for (const tag of tags) {
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.generateMap = exports.defaultClientMapTemplate = exports.defaultServerMapTemplate = void 0;
37
37
  const tools_1 = require("@sitecore-content-sdk/content/tools");
38
+ const node_tools_1 = require("@sitecore-content-sdk/content/node-tools");
38
39
  const path = __importStar(require("path"));
39
40
  const fs = __importStar(require("fs"));
40
41
  const utils_1 = require("./templating/utils");
@@ -74,10 +75,10 @@ const isComponentMapTemplate = (fn) => fn.length === 2;
74
75
  // Default App Router (server) component map template
75
76
  const defaultServerMapTemplate = (components, componentImports, ctx) => {
76
77
  var _a, _b;
77
- const entries = (_a = ctx === null || ctx === void 0 ? void 0 : ctx.entries) !== null && _a !== void 0 ? _a : (0, tools_1.prepareComponentsForMap)(components, {
78
+ const entries = (_a = ctx === null || ctx === void 0 ? void 0 : ctx.entries) !== null && _a !== void 0 ? _a : (0, node_tools_1.prepareComponentsForMap)(components, {
78
79
  includeVariants: (_b = ctx === null || ctx === void 0 ? void 0 : ctx.includeVariants) !== null && _b !== void 0 ? _b : true,
79
80
  });
80
- return (0, tools_1.buildComponentMapContent)(entries, componentImports, {
81
+ return (0, node_tools_1.buildComponentMapContent)(entries, componentImports, {
81
82
  framework: 'nextjs',
82
83
  isClientMap: false,
83
84
  builtInImports: APP_ROUTER_BUILTIN_IMPORTS,
@@ -88,11 +89,11 @@ exports.defaultServerMapTemplate = defaultServerMapTemplate;
88
89
  // Default client-safe component map template for App Router
89
90
  const defaultClientMapTemplate = (components, componentImports, ctx) => {
90
91
  var _a, _b;
91
- const entries = (_a = ctx === null || ctx === void 0 ? void 0 : ctx.entries) !== null && _a !== void 0 ? _a : (0, tools_1.prepareComponentsForMap)(components, {
92
+ const entries = (_a = ctx === null || ctx === void 0 ? void 0 : ctx.entries) !== null && _a !== void 0 ? _a : (0, node_tools_1.prepareComponentsForMap)(components, {
92
93
  includeVariants: (_b = ctx === null || ctx === void 0 ? void 0 : ctx.includeVariants) !== null && _b !== void 0 ? _b : true,
93
94
  shouldAnnotateClient: true,
94
95
  });
95
- return (0, tools_1.buildComponentMapContent)(entries, componentImports, {
96
+ return (0, node_tools_1.buildComponentMapContent)(entries, componentImports, {
96
97
  framework: 'nextjs',
97
98
  headerComment: 'Client-safe component map for App Router',
98
99
  isClientMap: true,
@@ -112,7 +113,7 @@ const collectComponents = (opts) => {
112
113
  }
113
114
  return {
114
115
  raw: filtered,
115
- entries: (0, tools_1.prepareComponentsForMap)(filtered, {
116
+ entries: (0, node_tools_1.prepareComponentsForMap)(filtered, {
116
117
  includeVariants: opts.includeVariants,
117
118
  shouldAnnotateClient: true,
118
119
  }),
@@ -153,7 +154,7 @@ const generateMap = ({ paths, destination = '.sitecore', exclude, componentImpor
153
154
  includeVariants,
154
155
  isClientMap: false,
155
156
  })
156
- : (0, tools_1.buildComponentMapContent)(allComponents.entries, componentImports, {
157
+ : (0, node_tools_1.buildComponentMapContent)(allComponents.entries, componentImports, {
157
158
  framework: 'nextjs',
158
159
  isClientMap: false,
159
160
  builtInImports: PAGES_ROUTER_BUILTIN_IMPORTS,
@@ -17,12 +17,20 @@ export function sanitizeSitecoreCacheTagSegment(value) {
17
17
  .replace(/[/:\s]+/g, '_');
18
18
  }
19
19
  /**
20
- * Normalizes a Sitecore item GUID for use in cache tags (lowercase, no braces).
20
+ * Normalizes a Sitecore item GUID for use in cache tags.
21
+ * Lowercases, strips braces, and canonicalizes 32-character hex GUIDs to hyphenated form
22
+ * so layout-service ids (`{guid}`) and Experience Edge identifiers (`GUID` without hyphens)
23
+ * produce the same `sc:item:` tag.
21
24
  * @param {string} itemId - Sitecore item id or GUID string.
22
25
  * @internal
23
26
  */
24
27
  export function normalizeSitecoreItemIdForCacheTag(itemId) {
25
- return itemId.trim().toLowerCase().replace(/[{}]/g, '');
28
+ const cleaned = itemId.trim().toLowerCase().replace(/[{}]/g, '');
29
+ const hex = cleaned.replace(/-/g, '');
30
+ if (/^[0-9a-f]{32}$/.test(hex)) {
31
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
32
+ }
33
+ return cleaned;
26
34
  }
27
35
  /**
28
36
  * Tag for a resolved route (site + language + logical path). Use for URL-level invalidation.
@@ -57,27 +65,6 @@ export function buildSitecoreDictionaryCacheTag(params) {
57
65
  const locale = sanitizeSitecoreCacheTagSegment(params.locale);
58
66
  return `${SITECORE_CONTENT_CACHE_TAG_PREFIX}:dict:${site}:${locale}`;
59
67
  }
60
- /**
61
- * Builds deduplicated dictionary cache tags from a sites list.
62
- * @param {BuildSitecoreDictionaryCacheTagsFromSitesParams} params - Sites list and fallback locale.
63
- * @internal
64
- */
65
- export function buildSitecoreDictionaryCacheTagsFromSites(params) {
66
- var _a;
67
- const seen = new Set();
68
- const out = [];
69
- const push = (tag) => {
70
- if (!seen.has(tag)) {
71
- seen.add(tag);
72
- out.push(tag);
73
- }
74
- };
75
- for (const site of params.sites) {
76
- const locale = ((_a = site.language) === null || _a === void 0 ? void 0 : _a.trim()) ? site.language : params.baseLocale;
77
- push(buildSitecoreDictionaryCacheTag({ site: site.name, locale }));
78
- }
79
- return out;
80
- }
81
68
  /**
82
69
  * Builds an item cache tag from Sitecore layout route data when `itemId` is present.
83
70
  * Prefers `itemLanguage` from Sitecore when set; otherwise uses `fallbackLocale`.
@@ -1,4 +1,5 @@
1
- import { SITECORE_CONTENT_CACHE_TAG_PREFIX, buildSitecoreItemCacheTag, dedupeSitecoreCacheTags, } from './sitecore-cache-tags';
1
+ import { buildSitecoreDictionaryCacheTag, buildSitecoreItemCacheTag, dedupeSitecoreCacheTags, } from './sitecore-cache-tags';
2
+ import debug from '../debug';
2
3
  /**
3
4
  * Strips Experience Edge style suffixes from an `identifier` so the value can be used as an item id in cache tags.
4
5
  * Handles `{GUID}`, `{GUID}-media`, `{GUID}-layout` style strings.
@@ -12,51 +13,99 @@ export function extractSitecoreEdgeContentId(identifier) {
12
13
  const trimmed = identifier.trim();
13
14
  return trimmed.replace(/-(?:media|layout)$/i, '');
14
15
  }
15
- const FULL_TAG_PREFIX = `${SITECORE_CONTENT_CACHE_TAG_PREFIX}:`;
16
16
  /**
17
- * @param {string} value - Candidate tag string from a webhook body.
18
- * @returns True when `value` is already a full `sc:` content cache tag.
17
+ * `entity_definition` value Experience Edge sends for Dictionary entry updates. This is the literal value Experience Edge sends.
18
+ * @internal
19
+ */
20
+ const DICTIONARY_ENTRY_ENTITY_DEFINITION = 'DictionaryEntry';
21
+ /**
22
+ * Whether a webhook update row is a Dictionary entry change (`entity_definition: "DictionaryEntry"`).
23
+ * @param {string} [entityDefinition] - `entity_definition` from a webhook update row.
24
+ * @internal
25
+ */
26
+ export function isSitecoreDictionaryEntryUpdate(entityDefinition) {
27
+ return entityDefinition === DICTIONARY_ENTRY_ENTITY_DEFINITION;
28
+ }
29
+ /**
30
+ * Resolves which configured site a Dictionary entry webhook `identifier` belongs to.
31
+ *
32
+ * Dictionary identifiers follow `<site name>-<dictionary folder id>-<value>-<language>`, but the
33
+ * folder id, value, and language segments can themselves contain hyphens (and the value can contain
34
+ * spaces), so splitting the identifier on `-` is not reliable. Instead this matches the identifier
35
+ * against the known configured site names, preferring the longest name so a more specific name wins
36
+ * over a shorter overlapping one (e.g. `my-site-2` over `my-site`).
37
+ * @param {string} identifier - Raw `identifier` from a Dictionary entry update row.
38
+ * @param {string[]} siteNames - Configured site names (e.g. from `.sitecore/sites.json`).
39
+ * @returns The matched site name, or `undefined` when no configured site name prefixes `identifier`.
40
+ * @internal
41
+ */
42
+ export function resolveSitecoreDictionarySiteNameFromIdentifier(identifier, siteNames) {
43
+ const normalized = identifier.trim().toLowerCase();
44
+ if (!normalized) {
45
+ return undefined;
46
+ }
47
+ return matchSitecoreDictionarySiteName(normalized, sortSiteNamesByLengthDesc(siteNames));
48
+ }
49
+ /**
50
+ * Sorts site names longest-first, for {@link matchSitecoreDictionarySiteName}.
51
+ * @param {string[]} siteNames - Configured site names.
52
+ * @internal
19
53
  */
20
- function isFullSitecoreContentCacheTag(value) {
21
- return value.startsWith(FULL_TAG_PREFIX);
54
+ function sortSiteNamesByLengthDesc(siteNames) {
55
+ return [...siteNames].sort((a, b) => b.length - a.length);
56
+ }
57
+ /**
58
+ * Matches a normalized (trimmed, lowercased) identifier against site names already sorted
59
+ * longest-first. Split out of {@link resolveSitecoreDictionarySiteNameFromIdentifier} so a request
60
+ * with many Dictionary entry updates in one payload sorts `siteNames` once instead of per update.
61
+ * @param {string} normalizedIdentifier - Identifier, already trimmed and lowercased.
62
+ * @param {string[]} siteNamesByLengthDesc - Site names sorted longest-first.
63
+ * @internal
64
+ */
65
+ function matchSitecoreDictionarySiteName(normalizedIdentifier, siteNamesByLengthDesc) {
66
+ for (const siteName of siteNamesByLengthDesc) {
67
+ const prefix = `${siteName.trim().toLowerCase()}-`;
68
+ if (prefix.length > 1 && normalizedIdentifier.startsWith(prefix)) {
69
+ return siteName;
70
+ }
71
+ }
72
+ return undefined;
22
73
  }
23
74
  /**
24
75
  * Maps an Experience Edge webhook JSON body to Content SDK cache tag strings used by
25
76
  * {@link collectSitecorePageCacheTags} / {@link buildSitecoreItemCacheTag} (`sc:item:...`), so
26
77
  * `revalidateTag` matches tags registered during cached reads.
27
- * **`updates`** rows resolve to **`sc:item:…`** (locale from `entity_culture` or `defaultLocale`). **`tags`**: full `sc:` strings pass through; bare ids become **`sc:item:…`** with `defaultLocale`. Route/variant tags are not inferred.
28
- * @param {SitecoreEdgeRevalidateRequestBody | null | undefined} body - Webhook JSON body (tags and/or updates).
29
- * @param {CollectSitecoreTagsFromEdgeBodyOptions} options - Default locale when culture is missing on an update or bare tag.
78
+ * **`updates`** rows resolve to **`sc:item:…`** (locale from `entity_culture` or `defaultLocale`), except
79
+ * rows where `entity_definition` is `"DictionaryEntry"`, which resolve to **`sc:dict:…`** scoped to the
80
+ * site resolved from the identifier (see {@link resolveSitecoreDictionarySiteNameFromIdentifier}) and the
81
+ * same locale. Route/variant tags are not inferred.
82
+ * @param {SitecoreEdgeRevalidateRequestBody | null | undefined} body - Webhook JSON body (updates).
83
+ * @param {CollectSitecoreTagsFromEdgeBodyOptions} options - Default locale, and site names for Dictionary entry updates.
30
84
  * @internal
31
85
  */
32
86
  export function collectSitecoreTagsFromEdgeRevalidateRequestBody(body, options) {
33
87
  var _a, _b, _c, _d;
34
- const { defaultLocale } = options;
88
+ const { defaultLocale, siteNames = [] } = options;
89
+ const siteNamesByLengthDesc = sortSiteNamesByLengthDesc(siteNames);
35
90
  const out = [];
36
- for (const raw of (_a = body === null || body === void 0 ? void 0 : body.tags) !== null && _a !== void 0 ? _a : []) {
37
- if (typeof raw !== 'string') {
38
- continue;
39
- }
40
- const s = raw.trim();
41
- if (!s) {
42
- continue;
43
- }
44
- if (isFullSitecoreContentCacheTag(s)) {
45
- out.push(s);
46
- }
47
- else {
48
- const id = extractSitecoreEdgeContentId(s);
49
- if (id) {
50
- out.push(buildSitecoreItemCacheTag({ itemId: id, locale: defaultLocale }));
91
+ for (const u of (_a = body === null || body === void 0 ? void 0 : body.updates) !== null && _a !== void 0 ? _a : []) {
92
+ const locale = ((_b = u === null || u === void 0 ? void 0 : u.entity_culture) === null || _b === void 0 ? void 0 : _b.trim()) || defaultLocale;
93
+ if (isSitecoreDictionaryEntryUpdate(u === null || u === void 0 ? void 0 : u.entity_definition)) {
94
+ const identifier = ((_c = u === null || u === void 0 ? void 0 : u.identifier) !== null && _c !== void 0 ? _c : '').trim().toLowerCase();
95
+ const site = identifier
96
+ ? matchSitecoreDictionarySiteName(identifier, siteNamesByLengthDesc)
97
+ : undefined;
98
+ if (!site) {
99
+ debug.revalidate('could not resolve site for Dictionary entry identifier %o, skipping', identifier);
100
+ continue;
51
101
  }
102
+ out.push(buildSitecoreDictionaryCacheTag({ site, locale }));
103
+ continue;
52
104
  }
53
- }
54
- for (const u of (_b = body === null || body === void 0 ? void 0 : body.updates) !== null && _b !== void 0 ? _b : []) {
55
- const id = extractSitecoreEdgeContentId((_c = u === null || u === void 0 ? void 0 : u.identifier) !== null && _c !== void 0 ? _c : '');
105
+ const id = extractSitecoreEdgeContentId((_d = u === null || u === void 0 ? void 0 : u.identifier) !== null && _d !== void 0 ? _d : '');
56
106
  if (!id) {
57
107
  continue;
58
108
  }
59
- const locale = ((_d = u === null || u === void 0 ? void 0 : u.entity_culture) === null || _d === void 0 ? void 0 : _d.trim()) || defaultLocale;
60
109
  out.push(buildSitecoreItemCacheTag({ itemId: id, locale }));
61
110
  }
62
111
  return dedupeSitecoreCacheTags(out).filter(Boolean);
@@ -24,8 +24,8 @@ function routeSegmentsFromPathname(pathname) {
24
24
  * `buildSitecoreDictionaryCacheTag` on a dedicated `use cache` helper).
25
25
  *
26
26
  * Registers **`sc:route:…`** and **`sc:item:…`** (when layout has `itemId`). Edge-style webhooks emit
27
- * item ids, which the Sitecore revalidate route handler maps to **`sc:item:…`**; route tags are only
28
- * invalidated when callers send the full `sc:route:…` strings in the `tags[]` array of the same revalidate request.
27
+ * item ids, which the Sitecore revalidate route handler maps to **`sc:item:…`**; route tags are not
28
+ * invalidated by that webhook flow.
29
29
  *
30
30
  * Personalization variants are isolated naturally by URL path (each variant rewrite yields a distinct
31
31
  * Cache Components key) so no `sc:pvv:…` tag is added here. If a personalize-specific webhook is wired
@@ -1,5 +1,5 @@
1
1
  import { collectSitecoreTagsFromEdgeRevalidateRequestBody, } from '../cache/sitecore-edge-webhook-revalidation';
2
- import { buildSitecoreDictionaryCacheTagsFromSites, dedupeSitecoreCacheTags } from '../cache/sitecore-cache-tags';
2
+ import { dedupeSitecoreCacheTags } from '../cache/sitecore-cache-tags';
3
3
  import debug from '../debug';
4
4
  import { revalidateTag } from 'next/cache';
5
5
  import { NextResponse } from 'next/server';
@@ -23,27 +23,21 @@ function resolveConfiguredRevalidateSecret(secretOption, envValue) {
23
23
  *
24
24
  * - **`updates[]`** — Sitecore publish-event rows. Each row's `identifier` (with `-media` / `-layout`
25
25
  * suffix stripped) maps to an `sc:item:<id>:<locale>` tag, using `entity_culture` for locale
26
- * (falling back to the handler's `defaultLocale`).
27
- * - **`tags[]`** — pass-through and convenience array:
28
- * - Strings already starting with `sc:` are used verbatim (e.g. `sc:route:...`, `sc:item:...`, `sc:dict:...`).
29
- * - Bare values are treated as Sitecore item ids and mapped to `sc:item:<id>:<defaultLocale>`.
30
- *
31
- * When **`sites`** is configured, the handler also appends one `sc:dict:<site>:<locale>` tag per
32
- * site so dictionary updates flow through the same call.
26
+ * (falling back to the handler's `defaultLocale`) — except rows where `entity_definition` is
27
+ * `"DictionaryEntry"`, which map to `sc:dict:<site>:<locale>` for the site resolved from the
28
+ * identifier via the configured **`sites`** option (skipped, with a debug log, when no configured
29
+ * site matches). Only updates that are actually Dictionary changes revalidate dictionary tags — a
30
+ * webhook for an unrelated item never touches them.
33
31
  *
34
32
  * Auth (optional): when `SITECORE_REVALIDATE_SECRET` (or the `secret` option) is non-empty, callers must
35
33
  * send the same value in the **`x-revalidate-secret`** header. When unset or blank, no header is required.
36
- * @param {SitecoreRevalidateRouteHandlerOptions} [options] - Optional inline `secret`, `cacheProfile`, locale, sites, and dictionary options.
34
+ * @param {SitecoreRevalidateRouteHandlerOptions} [options] - Optional inline `secret`, `cacheProfile`, locale, and sites options.
37
35
  * @public
38
36
  */
39
37
  export function createSitecoreRevalidateRouteHandler(options = {}) {
38
+ var _a;
40
39
  const { defaultLocale = 'en', sites, secret, cacheProfile = 'max' } = options;
41
- const dictionaryTags = sites !== undefined
42
- ? buildSitecoreDictionaryCacheTagsFromSites({
43
- sites,
44
- baseLocale: defaultLocale,
45
- })
46
- : [];
40
+ const siteNames = (_a = sites === null || sites === void 0 ? void 0 : sites.map((site) => site.name)) !== null && _a !== void 0 ? _a : [];
47
41
  const POST = async (req) => {
48
42
  var _a, _b, _c, _d, _e, _f, _g, _h;
49
43
  const startTimestamp = Date.now();
@@ -76,18 +70,13 @@ export function createSitecoreRevalidateRouteHandler(options = {}) {
76
70
  invocation_id: (_a = webhookBody.invocation_id) !== null && _a !== void 0 ? _a : null,
77
71
  continues: (_b = webhookBody.continues) !== null && _b !== void 0 ? _b : false,
78
72
  updatesCount: (_d = (_c = webhookBody.updates) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0,
79
- tagsCount: Array.isArray(webhookBody.tags) ? webhookBody.tags.length : 0,
80
- dictionaryTagsCount: dictionaryTags.length,
81
73
  defaultLocale,
82
74
  });
83
- const tags = dedupeSitecoreCacheTags([
84
- ...collectSitecoreTagsFromEdgeRevalidateRequestBody(webhookBody, { defaultLocale }),
85
- ...dictionaryTags,
86
- ]);
75
+ const tags = dedupeSitecoreCacheTags(collectSitecoreTagsFromEdgeRevalidateRequestBody(webhookBody, { defaultLocale, siteNames }));
87
76
  if (tags.length === 0) {
88
77
  debug.revalidate('sitecore revalidate: no tags resolved in %dms', Date.now() - startTimestamp);
89
78
  return NextResponse.json({
90
- error: 'Provide non-empty `updates` (with identifiers) and/or `tags` that resolve to at least one cache tag.',
79
+ error: 'Provide non-empty `updates` (with identifiers) that resolve to at least one cache tag.',
91
80
  }, { status: 400 });
92
81
  }
93
82
  for (const tag of tags) {
@@ -1,4 +1,5 @@
1
- import { filterComponentsByType, prepareComponentsForMap, buildComponentMapContent, } from '@sitecore-content-sdk/content/tools';
1
+ import { filterComponentsByType, } from '@sitecore-content-sdk/content/tools';
2
+ import { prepareComponentsForMap, buildComponentMapContent, } from '@sitecore-content-sdk/content/node-tools';
2
3
  import * as path from 'path';
3
4
  import * as fs from 'fs';
4
5
  import { detectRouterType, getComponentListWithTypes, ROUTER_TYPE } from './templating/utils';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sitecore-content-sdk/nextjs",
3
- "version": "2.4.1-canary.20260910100657",
3
+ "version": "2.4.1",
4
4
  "main": "dist/cjs/index.js",
5
5
  "module": "dist/esm/index.js",
6
6
  "sideEffects": false,
@@ -90,10 +90,10 @@
90
90
  },
91
91
  "dependencies": {
92
92
  "@babel/parser": "^7.27.2",
93
- "@sitecore-content-sdk/content": "2.3.1-canary.20260910100657",
93
+ "@sitecore-content-sdk/content": "^2.3.1",
94
94
  "@sitecore-content-sdk/core": "^2.1.4",
95
95
  "@sitecore-content-sdk/events": "^2.1.2",
96
- "@sitecore-content-sdk/react": "2.4.1-canary.20260910100657",
96
+ "@sitecore-content-sdk/react": "^2.4.0",
97
97
  "recast": "^0.23.11",
98
98
  "regex-parser": "^2.3.1"
99
99
  },
@@ -13,7 +13,10 @@ export declare const SITECORE_CONTENT_CACHE_TAG_PREFIX = "sc";
13
13
  */
14
14
  export declare function sanitizeSitecoreCacheTagSegment(value: string): string;
15
15
  /**
16
- * Normalizes a Sitecore item GUID for use in cache tags (lowercase, no braces).
16
+ * Normalizes a Sitecore item GUID for use in cache tags.
17
+ * Lowercases, strips braces, and canonicalizes 32-character hex GUIDs to hyphenated form
18
+ * so layout-service ids (`{guid}`) and Experience Edge identifiers (`GUID` without hyphens)
19
+ * produce the same `sc:item:` tag.
17
20
  * @param {string} itemId - Sitecore item id or GUID string.
18
21
  * @internal
19
22
  */
@@ -65,25 +68,6 @@ export type BuildSitecoreDictionaryCacheTagParams = {
65
68
  * @public
66
69
  */
67
70
  export declare function buildSitecoreDictionaryCacheTag(params: BuildSitecoreDictionaryCacheTagParams): string;
68
- /**
69
- * Parameters for buildSitecoreDictionaryCacheTagsFromSites.
70
- * @internal
71
- */
72
- export type BuildSitecoreDictionaryCacheTagsFromSitesParams = {
73
- /** Sites list (e.g. from generated multisite JSON). */
74
- sites: readonly {
75
- name: string;
76
- language?: string;
77
- }[];
78
- /** Locale used when a site has no `language` value. */
79
- baseLocale: string;
80
- };
81
- /**
82
- * Builds deduplicated dictionary cache tags from a sites list.
83
- * @param {BuildSitecoreDictionaryCacheTagsFromSitesParams} params - Sites list and fallback locale.
84
- * @internal
85
- */
86
- export declare function buildSitecoreDictionaryCacheTagsFromSites(params: BuildSitecoreDictionaryCacheTagsFromSitesParams): string[];
87
71
  /**
88
72
  * Builds an item cache tag from Sitecore layout route data when `itemId` is present.
89
73
  * Prefers `itemLanguage` from Sitecore when set; otherwise uses `fallbackLocale`.
@@ -1 +1 @@
1
- {"version":3,"file":"sitecore-cache-tags.d.ts","sourceRoot":"","sources":["../../src/cache/sitecore-cache-tags.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sCAAsC,CAAC;AAEtE;;;;GAIG;AACH,eAAO,MAAM,iCAAiC,OAAO,CAAC;AAEtD;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKrE;AAED;;;;GAIG;AACH,wBAAgB,kCAAkC,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEzE;AAED;;;GAGG;AACH,MAAM,MAAM,gCAAgC,GAAG;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,gCAAgC,GAAG,MAAM,CAM3F;AAED;;;GAGG;AACH,MAAM,MAAM,+BAA+B,GAAG;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,+BAA+B,GAAG,MAAM,CAIzF;AAED;;;GAGG;AACH,MAAM,MAAM,qCAAqC,GAAG;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,qCAAqC,GAC5C,MAAM,CAIR;AAED;;;GAGG;AACH,MAAM,MAAM,+CAA+C,GAAG;IAC5D,uDAAuD;IACvD,KAAK,EAAE,SAAS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACtD,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,yCAAyC,CACvD,MAAM,EAAE,+CAA+C,GACtD,MAAM,EAAE,CAcV;AAED;;;;;;;;;GASG;AACH,wBAAgB,sCAAsC,CACpD,KAAK,EAAE,SAAS,GAAG,IAAI,GAAG,SAAS,EACnC,cAAc,EAAE,MAAM,GACrB,MAAM,GAAG,IAAI,CASf;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAUhE"}
1
+ {"version":3,"file":"sitecore-cache-tags.d.ts","sourceRoot":"","sources":["../../src/cache/sitecore-cache-tags.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sCAAsC,CAAC;AAEtE;;;;GAIG;AACH,eAAO,MAAM,iCAAiC,OAAO,CAAC;AAEtD;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKrE;AAED;;;;;;;GAOG;AACH,wBAAgB,kCAAkC,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAOzE;AAED;;;GAGG;AACH,MAAM,MAAM,gCAAgC,GAAG;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,gCAAgC,GAAG,MAAM,CAM3F;AAED;;;GAGG;AACH,MAAM,MAAM,+BAA+B,GAAG;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,+BAA+B,GAAG,MAAM,CAIzF;AAED;;;GAGG;AACH,MAAM,MAAM,qCAAqC,GAAG;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,qCAAqC,GAC5C,MAAM,CAIR;AAED;;;;;;;;;GASG;AACH,wBAAgB,sCAAsC,CACpD,KAAK,EAAE,SAAS,GAAG,IAAI,GAAG,SAAS,EACnC,cAAc,EAAE,MAAM,GACrB,MAAM,GAAG,IAAI,CASf;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAUhE"}
@@ -17,12 +17,6 @@ export type SitecoreEdgeRevalidateRequestBody = {
17
17
  invocation_id?: string;
18
18
  updates?: SitecoreEdgeRevalidateUpdate[];
19
19
  continues?: boolean;
20
- /**
21
- * Extra tag strings. Values starting with the Sitecore cache prefix (`sc:`) are used as-is.
22
- * Bare values are treated as Sitecore item ids (with optional `-media` / `-layout` suffix stripped)
23
- * and mapped to {@link buildSitecoreItemCacheTag} using the handler default locale when culture is missing.
24
- */
25
- tags?: string[];
26
20
  };
27
21
  /**
28
22
  * Strips Experience Edge style suffixes from an `identifier` so the value can be used as an item id in cache tags.
@@ -31,23 +25,52 @@ export type SitecoreEdgeRevalidateRequestBody = {
31
25
  * @internal
32
26
  */
33
27
  export declare function extractSitecoreEdgeContentId(identifier: string): string;
28
+ /**
29
+ * Whether a webhook update row is a Dictionary entry change (`entity_definition: "DictionaryEntry"`).
30
+ * @param {string} [entityDefinition] - `entity_definition` from a webhook update row.
31
+ * @internal
32
+ */
33
+ export declare function isSitecoreDictionaryEntryUpdate(entityDefinition?: string): boolean;
34
+ /**
35
+ * Resolves which configured site a Dictionary entry webhook `identifier` belongs to.
36
+ *
37
+ * Dictionary identifiers follow `<site name>-<dictionary folder id>-<value>-<language>`, but the
38
+ * folder id, value, and language segments can themselves contain hyphens (and the value can contain
39
+ * spaces), so splitting the identifier on `-` is not reliable. Instead this matches the identifier
40
+ * against the known configured site names, preferring the longest name so a more specific name wins
41
+ * over a shorter overlapping one (e.g. `my-site-2` over `my-site`).
42
+ * @param {string} identifier - Raw `identifier` from a Dictionary entry update row.
43
+ * @param {string[]} siteNames - Configured site names (e.g. from `.sitecore/sites.json`).
44
+ * @returns The matched site name, or `undefined` when no configured site name prefixes `identifier`.
45
+ * @internal
46
+ */
47
+ export declare function resolveSitecoreDictionarySiteNameFromIdentifier(identifier: string, siteNames: readonly string[]): string | undefined;
34
48
  /**
35
49
  * Options for {@link collectSitecoreTagsFromEdgeRevalidateRequestBody}.
36
50
  * @internal
37
51
  */
38
52
  export type CollectSitecoreTagsFromEdgeBodyOptions = {
39
53
  /**
40
- * Used when an update omits `entity_culture`, and when mapping bare item ids in `tags`.
54
+ * Used when an update omits `entity_culture`.
41
55
  */
42
56
  defaultLocale: string;
57
+ /**
58
+ * Configured site names (e.g. from `.sitecore/sites.json`), used only to resolve which site a
59
+ * Dictionary entry update (`entity_definition: "DictionaryEntry"`) belongs to. When omitted (or when
60
+ * an identifier matches none of them), that update's Dictionary tag is skipped rather than guessed.
61
+ */
62
+ siteNames?: readonly string[];
43
63
  };
44
64
  /**
45
65
  * Maps an Experience Edge webhook JSON body to Content SDK cache tag strings used by
46
66
  * {@link collectSitecorePageCacheTags} / {@link buildSitecoreItemCacheTag} (`sc:item:...`), so
47
67
  * `revalidateTag` matches tags registered during cached reads.
48
- * **`updates`** rows resolve to **`sc:item:…`** (locale from `entity_culture` or `defaultLocale`). **`tags`**: full `sc:` strings pass through; bare ids become **`sc:item:…`** with `defaultLocale`. Route/variant tags are not inferred.
49
- * @param {SitecoreEdgeRevalidateRequestBody | null | undefined} body - Webhook JSON body (tags and/or updates).
50
- * @param {CollectSitecoreTagsFromEdgeBodyOptions} options - Default locale when culture is missing on an update or bare tag.
68
+ * **`updates`** rows resolve to **`sc:item:…`** (locale from `entity_culture` or `defaultLocale`), except
69
+ * rows where `entity_definition` is `"DictionaryEntry"`, which resolve to **`sc:dict:…`** scoped to the
70
+ * site resolved from the identifier (see {@link resolveSitecoreDictionarySiteNameFromIdentifier}) and the
71
+ * same locale. Route/variant tags are not inferred.
72
+ * @param {SitecoreEdgeRevalidateRequestBody | null | undefined} body - Webhook JSON body (updates).
73
+ * @param {CollectSitecoreTagsFromEdgeBodyOptions} options - Default locale, and site names for Dictionary entry updates.
51
74
  * @internal
52
75
  */
53
76
  export declare function collectSitecoreTagsFromEdgeRevalidateRequestBody(body: SitecoreEdgeRevalidateRequestBody | null | undefined, options: CollectSitecoreTagsFromEdgeBodyOptions): string[];
@@ -1 +1 @@
1
- {"version":3,"file":"sitecore-edge-webhook-revalidation.d.ts","sourceRoot":"","sources":["../../src/cache/sitecore-edge-webhook-revalidation.ts"],"names":[],"mappings":"AAMA;;;;GAIG;AACH,MAAM,MAAM,4BAA4B,GAAG;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,iCAAiC,GAAG;IAC9C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,4BAA4B,EAAE,CAAC;IACzC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAMvE;AAYD;;;GAGG;AACH,MAAM,MAAM,sCAAsC,GAAG;IACnD;;OAEG;IACH,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAgB,gDAAgD,CAC9D,IAAI,EAAE,iCAAiC,GAAG,IAAI,GAAG,SAAS,EAC1D,OAAO,EAAE,sCAAsC,GAC9C,MAAM,EAAE,CAgCV"}
1
+ {"version":3,"file":"sitecore-edge-webhook-revalidation.d.ts","sourceRoot":"","sources":["../../src/cache/sitecore-edge-webhook-revalidation.ts"],"names":[],"mappings":"AAOA;;;;GAIG;AACH,MAAM,MAAM,4BAA4B,GAAG;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,iCAAiC,GAAG;IAC9C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,4BAA4B,EAAE,CAAC;IACzC,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAMvE;AAQD;;;;GAIG;AACH,wBAAgB,+BAA+B,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,OAAO,CAElF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,+CAA+C,CAC7D,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,SAAS,MAAM,EAAE,GAC3B,MAAM,GAAG,SAAS,CAMpB;AAgCD;;;GAGG;AACH,MAAM,MAAM,sCAAsC,GAAG;IACnD;;OAEG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC/B,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,gDAAgD,CAC9D,IAAI,EAAE,iCAAiC,GAAG,IAAI,GAAG,SAAS,EAC1D,OAAO,EAAE,sCAAsC,GAC9C,MAAM,EAAE,CAoCV"}
@@ -23,8 +23,8 @@ export type CollectSitecorePageCacheTagsParams = {
23
23
  * `buildSitecoreDictionaryCacheTag` on a dedicated `use cache` helper).
24
24
  *
25
25
  * Registers **`sc:route:…`** and **`sc:item:…`** (when layout has `itemId`). Edge-style webhooks emit
26
- * item ids, which the Sitecore revalidate route handler maps to **`sc:item:…`**; route tags are only
27
- * invalidated when callers send the full `sc:route:…` strings in the `tags[]` array of the same revalidate request.
26
+ * item ids, which the Sitecore revalidate route handler maps to **`sc:item:…`**; route tags are not
27
+ * invalidated by that webhook flow.
28
28
  *
29
29
  * Personalization variants are isolated naturally by URL path (each variant rewrite yields a distinct
30
30
  * Cache Components key) so no `sc:pvv:…` tag is added here. If a personalize-specific webhook is wired
@@ -24,13 +24,15 @@ export type SitecoreRevalidateRouteHandlerOptions = {
24
24
  */
25
25
  cacheProfile?: RevalidateTagCacheProfile;
26
26
  /**
27
- * Locale for item tags when culture is missing, and for dictionary tags when a site has no language.
27
+ * Locale for item and dictionary tags when an update omits `entity_culture`.
28
28
  * Defaults to `'en'` when omitted.
29
29
  */
30
30
  defaultLocale?: string;
31
31
  /**
32
- * Sites list (e.g. from `.sitecore/sites.json`). Adds one `sc:dict:<site>:<locale>` tag per
33
- * site on every revalidation call. `generateSites` always includes the configured default site.
32
+ * Sites list (e.g. from `.sitecore/sites.json`), used to resolve which site a Dictionary entry
33
+ * update (`entity_definition: "DictionaryEntry"`) belongs to, so only that site's
34
+ * `sc:dict:<site>:<locale>` tag is revalidated instead of every configured site's. `generateSites`
35
+ * always includes the configured default site.
34
36
  */
35
37
  sites?: SiteInfo[];
36
38
  };
@@ -42,17 +44,15 @@ export type SitecoreRevalidateRouteHandlerOptions = {
42
44
  *
43
45
  * - **`updates[]`** — Sitecore publish-event rows. Each row's `identifier` (with `-media` / `-layout`
44
46
  * suffix stripped) maps to an `sc:item:<id>:<locale>` tag, using `entity_culture` for locale
45
- * (falling back to the handler's `defaultLocale`).
46
- * - **`tags[]`** — pass-through and convenience array:
47
- * - Strings already starting with `sc:` are used verbatim (e.g. `sc:route:...`, `sc:item:...`, `sc:dict:...`).
48
- * - Bare values are treated as Sitecore item ids and mapped to `sc:item:<id>:<defaultLocale>`.
49
- *
50
- * When **`sites`** is configured, the handler also appends one `sc:dict:<site>:<locale>` tag per
51
- * site so dictionary updates flow through the same call.
47
+ * (falling back to the handler's `defaultLocale`) — except rows where `entity_definition` is
48
+ * `"DictionaryEntry"`, which map to `sc:dict:<site>:<locale>` for the site resolved from the
49
+ * identifier via the configured **`sites`** option (skipped, with a debug log, when no configured
50
+ * site matches). Only updates that are actually Dictionary changes revalidate dictionary tags — a
51
+ * webhook for an unrelated item never touches them.
52
52
  *
53
53
  * Auth (optional): when `SITECORE_REVALIDATE_SECRET` (or the `secret` option) is non-empty, callers must
54
54
  * send the same value in the **`x-revalidate-secret`** header. When unset or blank, no header is required.
55
- * @param {SitecoreRevalidateRouteHandlerOptions} [options] - Optional inline `secret`, `cacheProfile`, locale, sites, and dictionary options.
55
+ * @param {SitecoreRevalidateRouteHandlerOptions} [options] - Optional inline `secret`, `cacheProfile`, locale, and sites options.
56
56
  * @public
57
57
  */
58
58
  export declare function createSitecoreRevalidateRouteHandler(options?: SitecoreRevalidateRouteHandlerOptions): {
@@ -1 +1 @@
1
- {"version":3,"file":"sitecore-revalidate-route-handler.d.ts","sourceRoot":"","sources":["../../src/route-handler/sitecore-revalidate-route-handler.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAC;AAmBnE;;;;GAIG;AACH,MAAM,MAAM,yBAAyB,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;AAE5E;;;GAGG;AACH,MAAM,MAAM,qCAAqC,GAAG;IAClD;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,YAAY,CAAC,EAAE,yBAAyB,CAAC;IACzC;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;CACpB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,oCAAoC,CAClD,OAAO,GAAE,qCAA0C;gBAY1B,WAAW;;;;;;;;EA0FrC"}
1
+ {"version":3,"file":"sitecore-revalidate-route-handler.d.ts","sourceRoot":"","sources":["../../src/route-handler/sitecore-revalidate-route-handler.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAC;AAmBnE;;;;GAIG;AACH,MAAM,MAAM,yBAAyB,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;AAE5E;;;GAGG;AACH,MAAM,MAAM,qCAAqC,GAAG;IAClD;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,YAAY,CAAC,EAAE,yBAAyB,CAAC;IACzC;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;CACpB,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,oCAAoC,CAClD,OAAO,GAAE,qCAA0C;gBAK1B,WAAW;;;;;;;;EAsFrC"}
@@ -1 +1 @@
1
- {"version":3,"file":"generate-map.d.ts","sourceRoot":"","sources":["../../src/tools/generate-map.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,mBAAmB,EAGnB,4BAA4B,EAK7B,MAAM,qCAAqC,CAAC;AAgD7C,eAAO,MAAM,wBAAwB,EAAE,4BAiBtC,CAAC;AAGF,eAAO,MAAM,wBAAwB,EAAE,4BAmBtC,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAC;AAgCtE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,WAAW,EAAE,mBAkGzB,CAAC"}
1
+ {"version":3,"file":"generate-map.d.ts","sourceRoot":"","sources":["../../src/tools/generate-map.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,mBAAmB,EAGnB,4BAA4B,EAG7B,MAAM,qCAAqC,CAAC;AAoD7C,eAAO,MAAM,wBAAwB,EAAE,4BAiBtC,CAAC;AAGF,eAAO,MAAM,wBAAwB,EAAE,4BAmBtC,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAC;AAgCtE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,WAAW,EAAE,mBAkGzB,CAAC"}