@sitecore-content-sdk/nextjs 2.1.1 → 2.2.0-canary.20260527110239

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.
Files changed (43) hide show
  1. package/dist/cjs/cache/sitecore-cache-tags.js +130 -0
  2. package/dist/cjs/cache/sitecore-edge-webhook-revalidation.js +67 -0
  3. package/dist/cjs/cache/sitecore-page-cache-tags.js +83 -0
  4. package/dist/cjs/debug.js +1 -1
  5. package/dist/cjs/editing/utils.js +6 -10
  6. package/dist/cjs/index.js +9 -1
  7. package/dist/cjs/proxy/redirects-proxy.js +55 -23
  8. package/dist/cjs/route-handler/index.js +3 -1
  9. package/dist/cjs/route-handler/sitecore-revalidate-route-handler.js +124 -0
  10. package/dist/cjs/tools/generate-map.js +120 -115
  11. package/dist/cjs/tools/templating/utils.js +22 -10
  12. package/dist/esm/cache/sitecore-cache-tags.js +119 -0
  13. package/dist/esm/cache/sitecore-edge-webhook-revalidation.js +63 -0
  14. package/dist/esm/cache/sitecore-page-cache-tags.js +80 -0
  15. package/dist/esm/debug.js +2 -2
  16. package/dist/esm/editing/utils.js +6 -10
  17. package/dist/esm/index.js +6 -0
  18. package/dist/esm/proxy/redirects-proxy.js +55 -23
  19. package/dist/esm/route-handler/index.js +1 -0
  20. package/dist/esm/route-handler/sitecore-revalidate-route-handler.js +118 -0
  21. package/dist/esm/tools/generate-map.js +119 -115
  22. package/dist/esm/tools/templating/utils.js +21 -10
  23. package/package.json +10 -10
  24. package/types/cache/sitecore-cache-tags.d.ts +108 -0
  25. package/types/cache/sitecore-cache-tags.d.ts.map +1 -0
  26. package/types/cache/sitecore-edge-webhook-revalidation.d.ts +54 -0
  27. package/types/cache/sitecore-edge-webhook-revalidation.d.ts.map +1 -0
  28. package/types/cache/sitecore-page-cache-tags.d.ts +42 -0
  29. package/types/cache/sitecore-page-cache-tags.d.ts.map +1 -0
  30. package/types/debug.d.ts.map +1 -1
  31. package/types/editing/utils.d.ts.map +1 -1
  32. package/types/index.d.ts +2 -0
  33. package/types/index.d.ts.map +1 -1
  34. package/types/proxy/redirects-proxy.d.ts +18 -0
  35. package/types/proxy/redirects-proxy.d.ts.map +1 -1
  36. package/types/route-handler/index.d.ts +1 -0
  37. package/types/route-handler/index.d.ts.map +1 -1
  38. package/types/route-handler/sitecore-revalidate-route-handler.d.ts +68 -0
  39. package/types/route-handler/sitecore-revalidate-route-handler.d.ts.map +1 -0
  40. package/types/tools/generate-map.d.ts +11 -6
  41. package/types/tools/generate-map.d.ts.map +1 -1
  42. package/types/tools/templating/utils.d.ts +11 -0
  43. package/types/tools/templating/utils.d.ts.map +1 -1
@@ -33,11 +33,39 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.generateMap = exports.defaultClientMapTemplate = void 0;
36
+ exports.generateMap = exports.defaultClientMapTemplate = exports.defaultServerMapTemplate = void 0;
37
37
  const tools_1 = require("@sitecore-content-sdk/content/tools");
38
38
  const path = __importStar(require("path"));
39
39
  const fs = __importStar(require("fs"));
40
40
  const utils_1 = require("./templating/utils");
41
+ const DEFAULT_HEADER_COMMENT = "Below are built-in components that are available in the app, it's recommended to keep them as is";
42
+ const APP_ROUTER_BUILTIN_IMPORTS = `
43
+ import { BYOCServerWrapper, NextjsContentSdkComponent, FEaaSServerWrapper } from '@sitecore-content-sdk/nextjs';
44
+ import { Form } from '@sitecore-content-sdk/nextjs';
45
+ `;
46
+ const APP_ROUTER_BUILTIN_ENTRIES = [
47
+ `['BYOCWrapper', BYOCServerWrapper]`,
48
+ `['FEaaSWrapper', FEaaSServerWrapper]`,
49
+ `['Form', { ...Form, componentType: 'client' }]`,
50
+ ];
51
+ const PAGES_ROUTER_BUILTIN_IMPORTS = `
52
+ import { BYOCWrapper, NextjsContentSdkComponent, FEaaSWrapper } from '@sitecore-content-sdk/nextjs';
53
+ import { Form } from '@sitecore-content-sdk/nextjs';
54
+ `;
55
+ const PAGES_ROUTER_BUILTIN_ENTRIES = [
56
+ `['BYOCWrapper', BYOCWrapper]`,
57
+ `['FEaaSWrapper', FEaaSWrapper]`,
58
+ `['Form', Form]`,
59
+ ];
60
+ const CLIENT_MAP_BUILTIN_IMPORTS = `
61
+ import { BYOCClientWrapper, NextjsContentSdkComponent, FEaaSClientWrapper } from '@sitecore-content-sdk/nextjs';
62
+ import { Form } from '@sitecore-content-sdk/nextjs';
63
+ `;
64
+ const CLIENT_MAP_BUILTIN_ENTRIES = [
65
+ `['BYOCWrapper', BYOCClientWrapper]`,
66
+ `['FEaaSWrapper', FEaaSClientWrapper]`,
67
+ `['Form', Form]`,
68
+ ];
41
69
  // Common builder for Next.js component map content
42
70
  const prepareComponentsForMap = (components, opts) => {
43
71
  const groups = new Map();
@@ -113,21 +141,16 @@ const prepareComponentsForMap = (components, opts) => {
113
141
  }
114
142
  return entries;
115
143
  };
116
- const buildNextjsMapContent = (entries, componentImports, options = {}) => {
117
- const isAppRouter = (0, utils_1.detectRouterType)() === 'app';
118
- const { headerComment = "Below are built-in components that are available in the app, it's recommended to keep them as is", isClientMap = false, } = options;
144
+ /**
145
+ * Distinguishes the simple 2-arity ComponentMapTemplate from the 3-arity EnhancedComponentMapTemplate.
146
+ * @param {ComponentMapTemplate | EnhancedComponentMapTemplate} fn The template function to check.
147
+ * @internal
148
+ */
149
+ const isComponentMapTemplate = (fn) => fn.length === 2;
150
+ const buildNextjsMapContent = (entries, componentImports, options) => {
151
+ const { headerComment = DEFAULT_HEADER_COMMENT, isClientMap = false, builtInImports, builtInMapEntries, } = options;
119
152
  const wildcardImports = [];
120
153
  const namedImports = [];
121
- const builtInImports = options.builtInImports ||
122
- `
123
- import { BYOCWrapper, NextjsContentSdkComponent, FEaaSWrapper } from '@sitecore-content-sdk/nextjs';
124
- import { Form } from '@sitecore-content-sdk/nextjs';
125
- `;
126
- const builtInMapEntries = options.builtInMapEntries || [
127
- `['BYOCWrapper', BYOCWrapper]`,
128
- `['FEaaSWrapper', FEaaSWrapper]`,
129
- `['Form', ${isAppRouter ? '{ ...Form, componentType: \'client\' }' : 'Form'}]`,
130
- ];
131
154
  // Add per-entry imports
132
155
  entries.forEach((e) => wildcardImports.push(...e.imports));
133
156
  // Handle package imports
@@ -145,8 +168,8 @@ import { Form } from '@sitecore-content-sdk/nextjs';
145
168
  ...namedImports,
146
169
  ].filter(Boolean);
147
170
  const importsSection = importLines.length ? `\n${importLines.join('\n')}` : '';
148
- // Build entry lines (package named imports are appended below)
149
- const componentMapEntries = builtInMapEntries;
171
+ // Clone to avoid mutating the caller's array
172
+ const componentMapEntries = structuredClone(builtInMapEntries);
150
173
  for (const e of entries) {
151
174
  const value = !isClientMap && e.annotateClient
152
175
  ? `{ ${e.valueExpr}, componentType: 'client' }`
@@ -179,26 +202,31 @@ ${componentMapEntries
179
202
  export default componentMap;
180
203
  `;
181
204
  };
182
- // default client template
205
+ // Default App Router (server) component map template
206
+ const defaultServerMapTemplate = (components, componentImports, ctx) => {
207
+ var _a, _b;
208
+ const entries = (_a = ctx === null || ctx === void 0 ? void 0 : ctx.entries) !== null && _a !== void 0 ? _a : prepareComponentsForMap(components, {
209
+ includeVariants: (_b = ctx === null || ctx === void 0 ? void 0 : ctx.includeVariants) !== null && _b !== void 0 ? _b : true,
210
+ });
211
+ return buildNextjsMapContent(entries, componentImports, {
212
+ headerComment: DEFAULT_HEADER_COMMENT,
213
+ isClientMap: false,
214
+ builtInImports: APP_ROUTER_BUILTIN_IMPORTS,
215
+ builtInMapEntries: APP_ROUTER_BUILTIN_ENTRIES,
216
+ });
217
+ };
218
+ exports.defaultServerMapTemplate = defaultServerMapTemplate;
219
+ // Default client-safe component map template for App Router
183
220
  const defaultClientMapTemplate = (components, componentImports, ctx) => {
184
221
  var _a, _b;
185
- const builtInImports = `
186
- import { BYOCClientWrapper, NextjsContentSdkComponent, FEaaSClientWrapper } from '@sitecore-content-sdk/nextjs';
187
- import { Form } from '@sitecore-content-sdk/nextjs';
188
- `;
189
- const builtInMapEntries = [
190
- `['BYOCWrapper', BYOCClientWrapper]`,
191
- `['FEaaSWrapper', FEaaSClientWrapper]`,
192
- `['Form', Form]`,
193
- ];
194
222
  const entries = (_a = ctx === null || ctx === void 0 ? void 0 : ctx.entries) !== null && _a !== void 0 ? _a : prepareComponentsForMap(components, {
195
223
  includeVariants: (_b = ctx === null || ctx === void 0 ? void 0 : ctx.includeVariants) !== null && _b !== void 0 ? _b : true,
196
224
  });
197
225
  return buildNextjsMapContent(entries, componentImports, {
198
226
  headerComment: 'Client-safe component map for App Router',
199
227
  isClientMap: true,
200
- builtInImports,
201
- builtInMapEntries,
228
+ builtInImports: CLIENT_MAP_BUILTIN_IMPORTS,
229
+ builtInMapEntries: CLIENT_MAP_BUILTIN_ENTRIES,
202
230
  });
203
231
  };
204
232
  exports.defaultClientMapTemplate = defaultClientMapTemplate;
@@ -219,116 +247,93 @@ const collectComponents = (opts) => {
219
247
  /**
220
248
  * Generate and write componentMap.ts files based on provided params.
221
249
  *
222
- * When clientComponentMap is true, generates:
250
+ * Pages Router:
251
+ * - component-map.ts : Single component map with Pages Router wrappers
252
+ *
253
+ * App Router (clientComponentMap=true or undefined):
223
254
  * - component-map.ts : Full component map with all components (server, client, universal)
224
- * - component-map.client.ts : Client-safe map with only client + universal components
255
+ * - component-map.client.ts : Client-safe map with client + universal components
225
256
  *
226
- * When clientComponentMap is false, generates:
227
- * - component-map.ts : Single component map (traditional behavior)
257
+ * App Router (clientComponentMap=false):
258
+ * - component-map.ts : Full component map with all components (server, client, universal)
259
+ * - component-map.client.ts : Client-safe map with built-in components only (no user components)
228
260
  *
229
- * When includeVariants is true (in either mode):
261
+ * When includeVariants is true:
230
262
  * - Includes component **variants** in the generated map(s) alongside base components
231
263
  * - Preserves the same client/server filtering rules (variants obey clientComponentMap filtering)
232
264
  * - Variant entries are emitted using the same naming/keys convention as their base components
233
265
  *
234
266
  * Template Customization:
235
267
  * - mapTemplate: Custom template for main component map (works for both single and dual map modes)
236
- * - clientMapTemplate: Custom template for client component map (only used when clientComponentMap is true)
268
+ * - clientMapTemplate: Custom template for client component map (App Router only)
237
269
  * @param {GenerateMapArgs} params - The parameters for the generateMap function.
238
270
  * @public
239
271
  */
240
272
  const generateMap = ({ paths, destination = '.sitecore', exclude, componentImports, mapTemplate, clientMapTemplate, clientComponentMap, includeVariants = true, }) => {
241
- const isAppRouter = (0, utils_1.detectRouterType)() === 'app';
242
- const shouldGenerateClientMap = clientComponentMap !== null && clientComponentMap !== void 0 ? clientComponentMap : isAppRouter;
243
- if (shouldGenerateClientMap) {
244
- // App Router case, main map
245
- const getComponents = collectComponents({ paths, exclude, includeVariants, filter: 'all' });
246
- let mainContent;
247
- if (mapTemplate) {
248
- mainContent = mapTemplate(getComponents.raw, componentImports, {
249
- entries: getComponents.entries,
273
+ const routerType = (0, utils_1.detectRouterType)();
274
+ const allComponents = collectComponents({ paths, exclude, includeVariants, filter: 'all' });
275
+ if (routerType === utils_1.ROUTER_TYPE.PAGES) {
276
+ const content = mapTemplate
277
+ ? mapTemplate(allComponents.raw, componentImports, {
278
+ entries: allComponents.entries,
250
279
  includeVariants,
251
280
  isClientMap: false,
252
- });
253
- }
254
- else {
255
- // default app router server map
256
- const builtInImports = `
257
- import { BYOCServerWrapper, NextjsContentSdkComponent, FEaaSServerWrapper } from '@sitecore-content-sdk/nextjs';
258
- import { Form } from '@sitecore-content-sdk/nextjs';
259
- `;
260
- const builtInMapEntries = [
261
- `['BYOCWrapper', BYOCServerWrapper]`,
262
- `['FEaaSWrapper', FEaaSServerWrapper]`,
263
- `['Form', { ...Form, componentType: 'client' }]`,
264
- ];
265
- mainContent = buildNextjsMapContent(getComponents.entries, componentImports, {
266
- headerComment: "Below are built-in components that are available in the app, it's recommended to keep them as is",
281
+ })
282
+ : buildNextjsMapContent(allComponents.entries, componentImports, {
267
283
  isClientMap: false,
268
- builtInImports,
269
- builtInMapEntries,
284
+ builtInImports: PAGES_ROUTER_BUILTIN_IMPORTS,
285
+ builtInMapEntries: PAGES_ROUTER_BUILTIN_ENTRIES,
270
286
  });
287
+ try {
288
+ fs.writeFileSync(path.join(process.cwd(), destination, 'component-map.ts'), content, 'utf8');
271
289
  }
272
- fs.writeFileSync(path.join(process.cwd(), destination, 'component-map.ts'), mainContent, 'utf8');
273
- // App Router, client map
274
- const clientComponents = collectComponents({
275
- paths,
276
- exclude,
277
- includeVariants,
278
- filter: 'client',
279
- });
280
- const clientTemplate = clientMapTemplate || exports.defaultClientMapTemplate;
281
- let clientContent;
282
- if (clientTemplate.length >= 2) {
283
- clientContent = clientTemplate(clientComponents.raw, componentImports);
284
- }
285
- else {
286
- clientContent = clientTemplate(clientComponents.raw, componentImports, {
287
- entries: clientComponents.entries,
288
- includeVariants,
289
- isClientMap: true,
290
- });
290
+ catch (error) {
291
+ console.error(`Component Map generation failed. Error writing to file ${destination}:`, error);
292
+ throw error;
291
293
  }
292
- fs.writeFileSync(path.join(process.cwd(), destination, 'component-map.client.ts'), clientContent, 'utf8');
294
+ return;
293
295
  }
294
- else {
295
- // Either in pages/app router or clientComponentMap = false
296
- const components = collectComponents({
297
- paths,
298
- exclude,
296
+ const mainContent = mapTemplate
297
+ ? mapTemplate(allComponents.raw, componentImports, {
298
+ entries: allComponents.entries,
299
+ includeVariants,
300
+ isClientMap: false,
301
+ })
302
+ : (0, exports.defaultServerMapTemplate)(allComponents.raw, componentImports, {
303
+ entries: allComponents.entries,
299
304
  includeVariants,
300
- filter: 'all',
301
- }).entries;
302
- const content = buildNextjsMapContent(components, componentImports, {
303
- headerComment: "Below are built-in components that are available in the app, it's recommended to keep them as is",
304
305
  isClientMap: false,
305
306
  });
306
- fs.writeFileSync(path.join(process.cwd(), destination, 'component-map.ts'), content, 'utf8');
307
- // For App Router compatibility, always generate client map file even when clientComponentMap is false
308
- // When clientComponentMap is false, only include built-in components (no custom client components)
309
- if (shouldGenerateClientMap || isAppRouter) {
310
- const clientMapTemplateToUse = clientMapTemplate || exports.defaultClientMapTemplate;
311
- const components = collectComponents({ paths: [], includeVariants, filter: 'all' });
312
- let clientMapContent;
313
- if (clientMapTemplateToUse.length >= 2) {
314
- clientMapContent = clientMapTemplateToUse([], componentImports);
315
- }
316
- else {
317
- clientMapContent = clientMapTemplateToUse([], componentImports, {
318
- entries: components.entries,
319
- includeVariants,
320
- isClientMap: true,
321
- });
322
- }
323
- const clientMapFile = path.join(process.cwd(), destination, 'component-map.client.ts');
324
- try {
325
- fs.writeFileSync(clientMapFile, clientMapContent, { encoding: 'utf8' });
326
- }
327
- catch (error) {
328
- console.error(`Client Component Map generation failed. Error writing to file ${destination}:`, error);
329
- throw error;
330
- }
331
- }
307
+ try {
308
+ fs.writeFileSync(path.join(process.cwd(), destination, 'component-map.ts'), mainContent, 'utf8');
309
+ }
310
+ catch (error) {
311
+ console.error(`Main Component Map generation failed. Error writing to file ${destination}:`, error);
312
+ throw error;
313
+ }
314
+ // clientComponentMap=true -> include user client+universal components
315
+ // clientComponentMap=undefined -> include user client+universal components
316
+ // clientComponentMap=false -> built-ins only
317
+ const shouldGenerateClientMap = clientComponentMap !== null && clientComponentMap !== void 0 ? clientComponentMap : true;
318
+ const clientComponents = shouldGenerateClientMap
319
+ ? collectComponents({ paths, exclude, includeVariants, filter: 'client' })
320
+ : { raw: [], entries: [] };
321
+ const clientTemplate = clientMapTemplate || exports.defaultClientMapTemplate;
322
+ let clientContent;
323
+ if (isComponentMapTemplate(clientTemplate))
324
+ clientContent = clientTemplate(clientComponents.raw, componentImports);
325
+ else
326
+ clientContent = clientTemplate(clientComponents.raw, componentImports, {
327
+ entries: clientComponents.entries,
328
+ includeVariants,
329
+ isClientMap: true,
330
+ });
331
+ try {
332
+ fs.writeFileSync(path.join(process.cwd(), destination, 'component-map.client.ts'), clientContent, 'utf8');
333
+ }
334
+ catch (error) {
335
+ console.error(`Client Component Map generation failed. Error writing to file ${destination}:`, error);
336
+ throw error;
332
337
  }
333
338
  };
334
339
  exports.generateMap = generateMap;
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ROUTER_TYPE = void 0;
6
7
  exports.detectRouterType = detectRouterType;
7
8
  exports.detectComponentType = detectComponentType;
8
9
  exports.getComponentListWithTypes = getComponentListWithTypes;
@@ -12,6 +13,17 @@ exports.nextjsDefaultMapTemplate = nextjsDefaultMapTemplate;
12
13
  const node_tools_1 = require("@sitecore-content-sdk/content/node-tools");
13
14
  const typescript_1 = __importDefault(require("typescript"));
14
15
  const fs_1 = __importDefault(require("fs"));
16
+ /**
17
+ * Constants for Next.js router types. Used for consistent detection and comparison throughout the codebase.
18
+ * Values are based on Next.js conventions:
19
+ * - 'app' for App Router (src/app or app directory)
20
+ * - 'pages' for Pages Router (src/pages or pages directory)
21
+ * @internal
22
+ */
23
+ exports.ROUTER_TYPE = {
24
+ APP: 'app',
25
+ PAGES: 'pages',
26
+ };
15
27
  /**
16
28
  * Detects the Next.js router type (App Router or Pages Router) based on directory structure.
17
29
  * @param {string} projectRoot - The project root directory. Defaults to current working directory.
@@ -19,15 +31,15 @@ const fs_1 = __importDefault(require("fs"));
19
31
  * @internal
20
32
  */
21
33
  function detectRouterType(projectRoot = process.cwd()) {
22
- const appDirExists = fs_1.default.existsSync(`${projectRoot}/src/app`) || fs_1.default.existsSync(`${projectRoot}/app`);
23
- const pagesDirExists = fs_1.default.existsSync(`${projectRoot}/src/pages`) || fs_1.default.existsSync(`${projectRoot}/pages`);
24
- if (appDirExists) {
25
- return 'app';
26
- }
27
- if (pagesDirExists) {
28
- return 'pages';
29
- }
30
- return 'pages';
34
+ const appDirExists = fs_1.default.existsSync(`${projectRoot}/src/${exports.ROUTER_TYPE.APP}`) ||
35
+ fs_1.default.existsSync(`${projectRoot}/${exports.ROUTER_TYPE.APP}`);
36
+ const pagesDirExists = fs_1.default.existsSync(`${projectRoot}/src/${exports.ROUTER_TYPE.PAGES}`) ||
37
+ fs_1.default.existsSync(`${projectRoot}/${exports.ROUTER_TYPE.PAGES}`);
38
+ if (appDirExists)
39
+ return exports.ROUTER_TYPE.APP;
40
+ if (pagesDirExists)
41
+ return exports.ROUTER_TYPE.PAGES;
42
+ return exports.ROUTER_TYPE.PAGES;
31
43
  }
32
44
  /**
33
45
  * Detects the component type based on directives, imports, and router context.
@@ -155,7 +167,7 @@ function detectComponentType(filePath, routerType) {
155
167
  // Router-aware defaults:
156
168
  // - App Router: defaults to server (RSC by default)
157
169
  // - Pages Router: defaults to universal (isomorphic by default)
158
- if (detectedRouterType === 'app') {
170
+ if (detectedRouterType === exports.ROUTER_TYPE.APP) {
159
171
  return 'server';
160
172
  }
161
173
  else {
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Stable cache tag strings for Sitecore content (Next.js `cacheTag`, `unstable_cache` tags, `revalidateTag`).
3
+ * Tags are deterministic for the same logical inputs so app code and invalidation webhooks stay aligned.
4
+ * @internal
5
+ */
6
+ export const SITECORE_CONTENT_CACHE_TAG_PREFIX = 'sc';
7
+ /**
8
+ * Sanitizes a single segment for use inside Sitecore cache tags.
9
+ * Colons are reserved as delimiters; slashes and whitespace are normalized for stable keys.
10
+ * @param {string} value - Raw segment (site name, locale, path segment, etc.).
11
+ * @internal
12
+ */
13
+ export function sanitizeSitecoreCacheTagSegment(value) {
14
+ return value.trim().toLowerCase().replace(/[/:\s]+/g, '_');
15
+ }
16
+ /**
17
+ * Normalizes a Sitecore item GUID for use in cache tags (lowercase, no braces).
18
+ * @param {string} itemId - Sitecore item id or GUID string.
19
+ * @internal
20
+ */
21
+ export function normalizeSitecoreItemIdForCacheTag(itemId) {
22
+ return itemId.trim().toLowerCase().replace(/[{}]/g, '');
23
+ }
24
+ /**
25
+ * Tag for a resolved route (site + language + logical path). Use for URL-level invalidation.
26
+ * @param {BuildSitecoreRouteCacheTagParams} params - Site, locale, and optional path segments.
27
+ * @internal
28
+ */
29
+ export function buildSitecoreRouteCacheTag(params) {
30
+ var _a;
31
+ const site = sanitizeSitecoreCacheTagSegment(params.site);
32
+ const locale = sanitizeSitecoreCacheTagSegment(params.locale);
33
+ const segments = ((_a = params.pathSegments) !== null && _a !== void 0 ? _a : []).map((s) => sanitizeSitecoreCacheTagSegment(s));
34
+ const pathKey = segments.length > 0 ? segments.join('/') : '_';
35
+ return `${SITECORE_CONTENT_CACHE_TAG_PREFIX}:route:${site}:${locale}:${pathKey}`;
36
+ }
37
+ /**
38
+ * Tag for a layout/route item (and anything else keyed the same way). Use for item-level invalidation.
39
+ * @param {BuildSitecoreItemCacheTagParams} params - Item id, locale, and optional published version.
40
+ * @internal
41
+ */
42
+ export function buildSitecoreItemCacheTag(params) {
43
+ const id = normalizeSitecoreItemIdForCacheTag(params.itemId);
44
+ const locale = sanitizeSitecoreCacheTagSegment(params.locale);
45
+ const ver = params.version !== undefined && Number.isFinite(params.version)
46
+ ? `v${Math.trunc(params.version)}`
47
+ : 'latest';
48
+ return `${SITECORE_CONTENT_CACHE_TAG_PREFIX}:item:${id}:${locale}:${ver}`;
49
+ }
50
+ /**
51
+ * Tag for dictionary data scoped to site + locale.
52
+ * @param {BuildSitecoreDictionaryCacheTagParams} params - Site and locale for the dictionary fetch.
53
+ * @public
54
+ */
55
+ export function buildSitecoreDictionaryCacheTag(params) {
56
+ const site = sanitizeSitecoreCacheTagSegment(params.site);
57
+ const locale = sanitizeSitecoreCacheTagSegment(params.locale);
58
+ return `${SITECORE_CONTENT_CACHE_TAG_PREFIX}:dict:${site}:${locale}`;
59
+ }
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
+ /**
82
+ * Builds an item cache tag from Sitecore layout route data when `itemId` is present.
83
+ * Prefers `itemLanguage` from Sitecore when set; otherwise uses `fallbackLocale`.
84
+ * Accepts the same `RouteData` shape returned by the layout service (e.g. `page.layout.sitecore.route`,
85
+ * which is `RouteData | null`) or `undefined` when the page did not resolve.
86
+ * @param {RouteData | null | undefined} route - Route node from layout (item id, language, version).
87
+ * @param {string} fallbackLocale - Locale used when `route.itemLanguage` is not set.
88
+ * @returns `null` when `route` is missing or `route.itemId` is not set.
89
+ * @internal
90
+ */
91
+ export function buildSitecoreItemCacheTagFromRouteData(route, fallbackLocale) {
92
+ if (!(route === null || route === void 0 ? void 0 : route.itemId)) {
93
+ return null;
94
+ }
95
+ const locale = route.itemLanguage
96
+ ? sanitizeSitecoreCacheTagSegment(route.itemLanguage)
97
+ : sanitizeSitecoreCacheTagSegment(fallbackLocale);
98
+ const id = normalizeSitecoreItemIdForCacheTag(route.itemId);
99
+ const ver = route.itemVersion !== undefined && Number.isFinite(route.itemVersion)
100
+ ? `v${Math.trunc(route.itemVersion)}`
101
+ : 'latest';
102
+ return `${SITECORE_CONTENT_CACHE_TAG_PREFIX}:item:${id}:${locale}:${ver}`;
103
+ }
104
+ /**
105
+ * Deduplicates tag strings while preserving first-seen order.
106
+ * @param {string[]} tags - Tag strings possibly containing duplicates.
107
+ * @internal
108
+ */
109
+ export function dedupeSitecoreCacheTags(tags) {
110
+ const seen = new Set();
111
+ const out = [];
112
+ for (const t of tags) {
113
+ if (!seen.has(t)) {
114
+ seen.add(t);
115
+ out.push(t);
116
+ }
117
+ }
118
+ return out;
119
+ }
@@ -0,0 +1,63 @@
1
+ import { SITECORE_CONTENT_CACHE_TAG_PREFIX, buildSitecoreItemCacheTag, dedupeSitecoreCacheTags, } from './sitecore-cache-tags';
2
+ /**
3
+ * Strips Experience Edge style suffixes from an `identifier` so the value can be used as an item id in cache tags.
4
+ * Handles `{GUID}`, `{GUID}-media`, `{GUID}-layout` style strings.
5
+ * @param {string} identifier - Raw identifier from a webhook update row.
6
+ * @internal
7
+ */
8
+ export function extractSitecoreEdgeContentId(identifier) {
9
+ if (!identifier || typeof identifier !== 'string') {
10
+ return '';
11
+ }
12
+ const trimmed = identifier.trim();
13
+ return trimmed.replace(/-(?:media|layout)$/i, '');
14
+ }
15
+ const FULL_TAG_PREFIX = `${SITECORE_CONTENT_CACHE_TAG_PREFIX}:`;
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.
19
+ */
20
+ function isFullSitecoreContentCacheTag(value) {
21
+ return value.startsWith(FULL_TAG_PREFIX);
22
+ }
23
+ /**
24
+ * Maps an Experience Edge webhook JSON body to Content SDK cache tag strings used by
25
+ * {@link collectSitecorePageCacheTags} / {@link buildSitecoreItemCacheTag} (`sc:item:...`), so
26
+ * `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.
30
+ * @internal
31
+ */
32
+ export function collectSitecoreTagsFromEdgeRevalidateRequestBody(body, options) {
33
+ var _a, _b, _c, _d;
34
+ const { defaultLocale } = options;
35
+ 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 }));
51
+ }
52
+ }
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 : '');
56
+ if (!id) {
57
+ continue;
58
+ }
59
+ const locale = ((_d = u === null || u === void 0 ? void 0 : u.entity_culture) === null || _d === void 0 ? void 0 : _d.trim()) || defaultLocale;
60
+ out.push(buildSitecoreItemCacheTag({ itemId: id, locale }));
61
+ }
62
+ return dedupeSitecoreCacheTags(out).filter(Boolean);
63
+ }
@@ -0,0 +1,80 @@
1
+ import { normalizePersonalizedRewrite } from '@sitecore-content-sdk/content/personalize';
2
+ import { buildSitecoreItemCacheTagFromRouteData, buildSitecoreRouteCacheTag, dedupeSitecoreCacheTags, } from './sitecore-cache-tags';
3
+ /** @param {string} pathname - Raw pathname (may omit leading slash). */
4
+ function normalizePathname(pathname) {
5
+ const trimmed = pathname.trim() || '/';
6
+ return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
7
+ }
8
+ /**
9
+ * Trims leading and trailing `/` from a single path segment without regex (linear time; avoids ReDoS flags on path-derived input).
10
+ * @param {string} part - One App Router catch-all path segment.
11
+ */
12
+ function trimSlashes(part) {
13
+ let start = 0;
14
+ let end = part.length;
15
+ while (start < end && part[start] === '/') {
16
+ start++;
17
+ }
18
+ while (end > start && part[end - 1] === '/') {
19
+ end--;
20
+ }
21
+ return part.slice(start, end);
22
+ }
23
+ /**
24
+ * Normalizes App Router catch-all `path` segments the same way as `SitecoreClient.parsePath` for a
25
+ * string array (leading slash, trim segments, drop empty `/` parts).
26
+ * @param {string[]} path - App Router catch-all segments.
27
+ */
28
+ function personalizedPathnameFromPathSegments(path) {
29
+ if (path.length === 0) {
30
+ return '/';
31
+ }
32
+ return `/${path
33
+ .filter((part) => part !== '/')
34
+ .map((part) => trimSlashes(part))
35
+ .join('/')}`;
36
+ }
37
+ /**
38
+ * Route segments after removing personalization rewrite markers, for stable route-level tags.
39
+ * @param {string} personalizedPathname - Pathname that may include personalization rewrite segments.
40
+ */
41
+ function routeSegmentsFromPersonalizedPathname(personalizedPathname) {
42
+ const pathname = normalizePathname(personalizedPathname);
43
+ const n = normalizePersonalizedRewrite(pathname);
44
+ if (!n || n === '/') {
45
+ return [];
46
+ }
47
+ const noLead = n.startsWith('/') ? n.slice(1) : n;
48
+ return noLead.split('/').filter(Boolean);
49
+ }
50
+ /**
51
+ * Builds cache tags for a Sitecore page read (`getPage`): the route tag and the route's item tag.
52
+ * Dictionary data is not part of `getPage`; tag dictionary fetches separately (for example with
53
+ * `buildSitecoreDictionaryCacheTag` on a dedicated `use cache` helper).
54
+ *
55
+ * Registers **`sc:route:…`** and **`sc:item:…`** (when layout has `itemId`). Edge-style webhooks emit
56
+ * item ids, which the Sitecore revalidate route handler maps to **`sc:item:…`**; route tags are only
57
+ * invalidated when callers send the full `sc:route:…` strings in the `tags[]` array of the same revalidate request.
58
+ *
59
+ * Personalization variants are isolated naturally by URL path (each variant rewrite yields a distinct
60
+ * Cache Components key) so no `sc:pvv:…` tag is added here. If a personalize-specific webhook is wired
61
+ * up later, build that tag in the dedicated helper and add it on top of these.
62
+ * @param {CollectSitecorePageCacheTagsParams} params - Site, locale, path or personalized pathname, and route metadata.
63
+ * @public
64
+ */
65
+ export function collectSitecorePageCacheTags(params) {
66
+ var _a, _b;
67
+ const pathnameInput = params.personalizedPathname !== undefined
68
+ ? params.personalizedPathname
69
+ : personalizedPathnameFromPathSegments((_a = params.path) !== null && _a !== void 0 ? _a : []);
70
+ const pathname = normalizePathname(pathnameInput);
71
+ const pathSegments = routeSegmentsFromPersonalizedPathname(pathname);
72
+ return dedupeSitecoreCacheTags([
73
+ buildSitecoreRouteCacheTag({
74
+ site: params.site,
75
+ locale: params.locale,
76
+ pathSegments,
77
+ }),
78
+ (_b = buildSitecoreItemCacheTagFromRouteData(params.route, params.locale)) !== null && _b !== void 0 ? _b : '',
79
+ ]).filter(Boolean);
80
+ }
package/dist/esm/debug.js CHANGED
@@ -1,9 +1,9 @@
1
- import { debug as coreDebug } from '@sitecore-content-sdk/core';
1
+ import { debug as coreDebug, debugModule, debugNamespace, } from '@sitecore-content-sdk/core';
2
2
  import { debug as contentDebug } from '@sitecore-content-sdk/content';
3
3
  import { debug as searchDebug } from '@sitecore-content-sdk/react/search';
4
4
  /**
5
5
  * Unified debug object containing all debug namespaces from referenced content-sdk packages.
6
6
  * @public
7
7
  */
8
- const debug = Object.assign(Object.assign(Object.assign({}, coreDebug), contentDebug), { search: searchDebug });
8
+ const debug = Object.assign(Object.assign(Object.assign({}, coreDebug), contentDebug), { search: searchDebug, revalidate: debugModule(`${debugNamespace}:revalidate`) });
9
9
  export default debug;