@salesforce/storefront-next-runtime 0.3.1 → 0.4.0-alpha.0

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 (67) hide show
  1. package/README.md +82 -0
  2. package/dist/DesignComponent.js +37 -12
  3. package/dist/DesignComponent.js.map +1 -1
  4. package/dist/DesignContext.js +47 -2
  5. package/dist/DesignContext.js.map +1 -1
  6. package/dist/DesignFrame.js +1 -1
  7. package/dist/DesignRegion.js +1 -1
  8. package/dist/config.d.ts +4 -4
  9. package/dist/custom-global-preferences.d.ts +20 -0
  10. package/dist/custom-global-preferences.d.ts.map +1 -0
  11. package/dist/custom-global-preferences.js +28 -0
  12. package/dist/custom-global-preferences.js.map +1 -0
  13. package/dist/custom-site-preferences.d.ts +20 -0
  14. package/dist/custom-site-preferences.d.ts.map +1 -0
  15. package/dist/custom-site-preferences.js +28 -0
  16. package/dist/custom-site-preferences.js.map +1 -0
  17. package/dist/data-store-custom-global-preferences.d.ts +2 -0
  18. package/dist/data-store-custom-global-preferences.js +6 -0
  19. package/dist/data-store-custom-site-preferences.d.ts +2 -0
  20. package/dist/data-store-custom-site-preferences.js +6 -0
  21. package/dist/data-store-gcp-preferences.d.ts +2 -0
  22. package/dist/data-store-gcp-preferences.js +6 -0
  23. package/dist/data-store.d.ts +97 -0
  24. package/dist/data-store.d.ts.map +1 -0
  25. package/dist/data-store.js +42 -0
  26. package/dist/data-store.js.map +1 -0
  27. package/dist/design-data.d.ts +82 -88
  28. package/dist/design-data.d.ts.map +1 -1
  29. package/dist/design-data.js +95 -57
  30. package/dist/design-data.js.map +1 -1
  31. package/dist/design-messaging.d.ts +2 -2
  32. package/dist/design-react-core.d.ts +2 -2
  33. package/dist/events.d.ts +34 -6
  34. package/dist/events.d.ts.map +1 -1
  35. package/dist/events.js +6 -6
  36. package/dist/events.js.map +1 -1
  37. package/dist/gcp-preferences.d.ts +52 -0
  38. package/dist/gcp-preferences.d.ts.map +1 -0
  39. package/dist/gcp-preferences.js +61 -0
  40. package/dist/gcp-preferences.js.map +1 -0
  41. package/dist/i18n-client.d.ts +38 -0
  42. package/dist/i18n-client.d.ts.map +1 -0
  43. package/dist/i18n-client.js +72 -0
  44. package/dist/i18n-client.js.map +1 -0
  45. package/dist/i18n.d.ts +63 -0
  46. package/dist/i18n.d.ts.map +1 -0
  47. package/dist/i18n.js +98 -0
  48. package/dist/i18n.js.map +1 -0
  49. package/dist/index.d.ts +60 -1
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/messaging-api.js +3 -1
  52. package/dist/messaging-api.js.map +1 -1
  53. package/dist/scapi.d.ts +247 -2
  54. package/dist/scapi.d.ts.map +1 -1
  55. package/dist/scapi.js +1 -1
  56. package/dist/scapi.js.map +1 -1
  57. package/dist/site-context.d.ts +93 -17
  58. package/dist/site-context.d.ts.map +1 -1
  59. package/dist/site-context.js +2 -417
  60. package/dist/site-context2.js +513 -0
  61. package/dist/site-context2.js.map +1 -0
  62. package/dist/types2.d.ts +210 -0
  63. package/dist/types2.d.ts.map +1 -1
  64. package/dist/utils.js +179 -0
  65. package/dist/utils.js.map +1 -0
  66. package/package.json +63 -4
  67. package/dist/site-context.js.map +0 -1
@@ -42,6 +42,12 @@ var VisitorContext = class VisitorContext {
42
42
  return this.context.page;
43
43
  }
44
44
  /**
45
+ * The parent visitor context, providing access to the node that contains the current one in the page tree.
46
+ */
47
+ get parent() {
48
+ return this.context.parent;
49
+ }
50
+ /**
45
51
  * The parent region of the current node, if traversing within a region.
46
52
  */
47
53
  get parentRegion() {
@@ -158,6 +164,7 @@ var VisitorContext = class VisitorContext {
158
164
  page,
159
165
  parentComponent: void 0,
160
166
  parentRegion: void 0,
167
+ parent: void 0,
161
168
  node: page
162
169
  });
163
170
  if (this.context.visitor.visitPage) return this.context.visitor.visitPage(pageContext);
@@ -169,11 +176,13 @@ var VisitorContext = class VisitorContext {
169
176
  }
170
177
  toChildContext(type, node) {
171
178
  VisitorContextError.assert(this.context.type, type);
179
+ const parent = this;
172
180
  if (type === "region") return new VisitorContext({
173
181
  type: "region",
174
182
  visitor: this.context.visitor,
175
183
  page: this.page,
176
184
  node,
185
+ parent,
177
186
  parentComponent: this.node,
178
187
  parentRegion: this.parentRegion
179
188
  });
@@ -182,6 +191,7 @@ var VisitorContext = class VisitorContext {
182
191
  visitor: this.context.visitor,
183
192
  page: this.page,
184
193
  node,
194
+ parent,
185
195
  parentComponent: this.parentComponent,
186
196
  parentRegion: this.node
187
197
  });
@@ -382,14 +392,6 @@ function resolveExpression(expression, contexts, dataBindings) {
382
392
  return record[parsed.field] ?? "";
383
393
  }
384
394
  /**
385
- * Extracts the {@link ComponentDataBinding} metadata from a component's
386
- * `custom` field. Returns `undefined` if the component has no data binding
387
- * configuration.
388
- */
389
- function getDataBinding(component) {
390
- return component.custom?.dataBinding;
391
- }
392
- /**
393
395
  * Resolves data binding expressions for a single component. Replaces attribute
394
396
  * values in the component's `data` with the resolved values from context
395
397
  * resolution. Attributes without a matching expression are preserved as-is.
@@ -400,6 +402,7 @@ function getDataBinding(component) {
400
402
  * `dataBindings` is `undefined`.
401
403
  *
402
404
  * @param component - The component to resolve data bindings for.
405
+ * @param binding - The component's data binding metadata from the page manifest's `componentInfo`, or `null`/`undefined` if not bound.
403
406
  * @param dataBindings - The resolved data bindings from {@link QualifierContext}, or `undefined` if no bindings were resolved.
404
407
  * @returns The component with resolved attribute values, or the original component if no bindings apply.
405
408
  *
@@ -411,18 +414,17 @@ function getDataBinding(component) {
411
414
  * id: 'banner',
412
415
  * typeId: 'commerce_assets.contentBanner',
413
416
  * data: { heading: 'Fallback Title', body: 'Fallback Body' },
414
- * custom: {
415
- * dataBinding: {
416
- * expressions: {
417
- * heading: 'content_asset.title',
418
- * body: 'content_asset.body',
419
- * },
420
- * contexts: [{ type: 'content_asset', id: 'winter-sale-uuid' }],
421
- * },
422
- * },
423
417
  * regions: [],
424
418
  * };
425
419
  *
420
+ * const binding = {
421
+ * expressions: {
422
+ * heading: 'content_asset.title',
423
+ * body: 'content_asset.body',
424
+ * },
425
+ * contexts: [{ type: 'content_asset', id: 'winter-sale-uuid' }],
426
+ * };
427
+ *
426
428
  * const dataBindings = {
427
429
  * content_asset: {
428
430
  * 'winter-sale-uuid': {
@@ -432,14 +434,13 @@ function getDataBinding(component) {
432
434
  * },
433
435
  * };
434
436
  *
435
- * const resolved = resolveComponentDataBindings(component, dataBindings);
437
+ * const resolved = resolveComponentDataBindings(component, binding, dataBindings);
436
438
  * // resolved.data.heading === 'Winter Sale'
437
439
  * // resolved.data.body === '<div>Free Shipping on all orders!</div>'
438
440
  * ```
439
441
  */
440
- function resolveComponentDataBindings(component, dataBindings) {
442
+ function resolveComponentDataBindings(component, binding, dataBindings) {
441
443
  if (!dataBindings) return component;
442
- const binding = getDataBinding(component);
443
444
  if (!binding?.contexts?.length) return component;
444
445
  const expressionEntries = Object.entries(binding.expressions ?? {});
445
446
  if (expressionEntries.length === 0) return component;
@@ -498,7 +499,7 @@ function resolveComponentDataBindings(component, dataBindings) {
498
499
  */
499
500
  function validateRule(rule, locale, context) {
500
501
  if (rule.campaignQualifiers) {
501
- for (const campaignQualifier of rule.campaignQualifiers) if (!context?.campaignQualifiers[campaignQualifier.campaignId]?.[campaignQualifier.promotionId]) return false;
502
+ for (const campaignQualifier of rule.campaignQualifiers) if (!context?.campaignQualifiers?.[campaignQualifier.campaignId]?.[campaignQualifier.promotionId]) return false;
502
503
  } else {
503
504
  if (rule.activeLocales && !rule.activeLocales.includes(locale)) return false;
504
505
  if (rule.schedule) {
@@ -513,7 +514,7 @@ function validateRule(rule, locale, context) {
513
514
  }
514
515
  }
515
516
  if (rule.customerGroups) {
516
- for (const customerGroup of rule.customerGroups) if (!context?.customerGroups[customerGroup]) return false;
517
+ for (const customerGroup of rule.customerGroups) if (!context?.customerGroups?.[customerGroup]) return false;
517
518
  }
518
519
  }
519
520
  return true;
@@ -573,34 +574,65 @@ function validateRule(rule, locale, context) {
573
574
  * ```
574
575
  */
575
576
  function processPage(page, processorContext) {
576
- return transformPage(page, { visitComponent(ctx) {
577
- const componentInfo = processorContext.componentInfo[ctx.node.id];
578
- const visibilityRules = componentInfo?.visibilityRules ?? [];
579
- if (visibilityRules.length > 0) {
580
- if (!visibilityRules.some((rule) => validateRule(rule, processorContext.locale, processorContext.qualifiers))) return null;
581
- }
582
- const defaultContent = componentInfo?.content?.default ?? {};
583
- const localeContent = componentInfo?.content?.[processorContext.locale] ?? {};
584
- const content = {
585
- ...defaultContent,
586
- ...localeContent
587
- };
588
- const isLocalized = Boolean(componentInfo?.content?.[processorContext.locale]);
589
- let node = {
590
- ...ctx.node,
591
- localized: isLocalized,
592
- visible: true,
593
- data: {
594
- ...ctx.node.data,
595
- ...content
577
+ const { pruneInvisible = true } = processorContext;
578
+ return transformPage(page, {
579
+ visitRegion(ctx) {
580
+ let regionInfo;
581
+ if (ctx.parent?.type === "page") regionInfo = processorContext.pageInfo.regions[ctx.node.id];
582
+ else if (ctx.parent?.type === "component") regionInfo = processorContext.componentInfo[ctx.parent.node.id]?.regions?.[ctx.node.id];
583
+ let components = ctx.visitComponents(ctx.node.components);
584
+ if (regionInfo?.maxComponents != null) if (pruneInvisible) components = components.slice(0, regionInfo.maxComponents);
585
+ else {
586
+ const result = [];
587
+ let visibleCount = 0;
588
+ for (const comp of components) {
589
+ if (comp.visible) visibleCount++;
590
+ if (visibleCount > regionInfo.maxComponents) result.push({
591
+ ...comp,
592
+ visible: false
593
+ });
594
+ else result.push(comp);
595
+ }
596
+ components = result;
596
597
  }
597
- };
598
- node = resolveComponentDataBindings(node, processorContext.qualifiers?.dataBindings);
599
- return {
600
- ...node,
601
- regions: ctx.visitRegions(ctx.node.regions)
602
- };
603
- } });
598
+ return {
599
+ ...ctx.node,
600
+ components
601
+ };
602
+ },
603
+ visitComponent(ctx) {
604
+ const componentInfo = processorContext.componentInfo[ctx.node.id];
605
+ const visibilityRules = componentInfo?.visibilityRules ?? [];
606
+ let isVisible = true;
607
+ if (visibilityRules.length > 0) {
608
+ if (!visibilityRules.some((rule) => validateRule(rule, processorContext.locale, processorContext.qualifiers))) {
609
+ if (pruneInvisible) return null;
610
+ isVisible = false;
611
+ }
612
+ }
613
+ const defaultContent = componentInfo?.content?.default ?? {};
614
+ const localeContent = componentInfo?.content?.[processorContext.locale] ?? {};
615
+ const content = {
616
+ ...defaultContent,
617
+ ...localeContent
618
+ };
619
+ const isLocalized = Boolean(componentInfo?.content?.[processorContext.locale]);
620
+ let node = {
621
+ ...ctx.node,
622
+ localized: isLocalized,
623
+ visible: isVisible,
624
+ data: {
625
+ ...ctx.node.data,
626
+ ...content
627
+ }
628
+ };
629
+ node = resolveComponentDataBindings(node, componentInfo?.dataBinding, processorContext.qualifiers?.dataBindings);
630
+ return {
631
+ ...node,
632
+ regions: ctx.visitRegions(ctx.node.regions)
633
+ };
634
+ }
635
+ });
604
636
  }
605
637
 
606
638
  //#endregion
@@ -777,6 +809,7 @@ function resolveDynamicPageId({ id, identifierType, siteManifest, aspectType })
777
809
  * pageRequiresContext: false,
778
810
  * visibilityRule: { activeLocales: ['en-US'], customerGroups: ['vip-customers'] },
779
811
  * page: { id: 'homepage', typeId: 'storePage', regions: [] },
812
+ * regions: {},
780
813
  * },
781
814
  * 'holiday-homepage': {
782
815
  * ruleRequiresContext: false,
@@ -789,11 +822,13 @@ function resolveDynamicPageId({ id, identifierType, siteManifest, aspectType })
789
822
  * },
790
823
  * },
791
824
  * page: { id: 'homepage', typeId: 'storePage', regions: [] },
825
+ * regions: {},
792
826
  * },
793
827
  * 'default-homepage': {
794
828
  * ruleRequiresContext: false,
795
829
  * pageRequiresContext: false,
796
830
  * page: { id: 'homepage', typeId: 'storePage', regions: [] },
831
+ * regions: {},
797
832
  * },
798
833
  * },
799
834
  * defaultVariation: 'default-homepage',
@@ -862,6 +897,7 @@ async function getPageFromManifest(manifest, { contextResolver, locale }) {
862
897
  * @param options.manifestStorage - Storage implementation for fetching manifests.
863
898
  * @param options.contextResolver - Optional async function that returns the shopper's qualifier context. Only called if a visibility rule needs it.
864
899
  * @param options.aspectType - The aspect type to resolve the page for when the identifier type is `'product'` or `'category'`.
900
+ * @param options.pruneInvisible - When `true` (default), invisible and overflow components are removed. When `false`, they are kept but marked `visible: false` for design/preview mode.
865
901
  * @returns The fully resolved and filtered page, or `null`.
866
902
  *
867
903
  * @example
@@ -875,12 +911,12 @@ async function getPageFromManifest(manifest, { contextResolver, locale }) {
875
911
  * aspectType: 'pdp',
876
912
  * locale: 'en-US',
877
913
  * manifestStorage: {
878
- * async getPageManifest(id, locale) {
914
+ * async getPageManifest(id) {
879
915
  * // Fetch from CDN, filesystem, or database
880
- * return fetchManifest(`/manifests/${locale}/${id}.json`);
916
+ * return fetchManifest(`/manifests/${id}.json`);
881
917
  * },
882
- * async getSiteManifest(locale) {
883
- * return fetchManifest(`/manifests/${locale}/site.json`);
918
+ * async getSiteManifest() {
919
+ * return fetchManifest('/manifests/site.json');
884
920
  * },
885
921
  * },
886
922
  * contextResolver: async () => ({
@@ -898,10 +934,10 @@ async function getPageFromManifest(manifest, { contextResolver, locale }) {
898
934
  * }
899
935
  * ```
900
936
  */
901
- async function resolvePage({ id, identifierType, aspectType, locale, manifestStorage, contextResolver }) {
937
+ async function resolvePage({ id, identifierType, aspectType, locale, manifestStorage, contextResolver, pruneInvisible = true }) {
902
938
  let resolvedId = null;
903
939
  if (ContentAssignmentResolvers.has(identifierType)) {
904
- const siteManifest = await manifestStorage.getSiteManifest(locale);
940
+ const siteManifest = await manifestStorage.getSiteManifest();
905
941
  RequiredError.assert(aspectType, `Aspect type is required for identifier type ${identifierType}`, (v) => !v);
906
942
  resolvedId = resolveDynamicPageId({
907
943
  id,
@@ -911,7 +947,7 @@ async function resolvePage({ id, identifierType, aspectType, locale, manifestSto
911
947
  });
912
948
  } else resolvedId = id;
913
949
  if (!resolvedId) return null;
914
- const pageManifest = await manifestStorage.getPageManifest(resolvedId, locale);
950
+ const pageManifest = await manifestStorage.getPageManifest(resolvedId);
915
951
  if (!pageManifest) return null;
916
952
  const pageResults = await getPageFromManifest(pageManifest, {
917
953
  contextResolver,
@@ -923,7 +959,9 @@ async function resolvePage({ id, identifierType, aspectType, locale, manifestSto
923
959
  return processPage(pageResults.entry.page, {
924
960
  qualifiers: context,
925
961
  componentInfo: pageManifest.componentInfo,
926
- locale
962
+ pageInfo: { regions: pageResults.entry.regions },
963
+ locale,
964
+ pruneInvisible
927
965
  });
928
966
  }
929
967
 
@@ -1 +1 @@
1
- {"version":3,"file":"design-data.js","names":["context: {\n /** The current node being visited. */\n node: TNode;\n /** The node type */\n type: VisitorContextType;\n /** The visitor being used to transform the page tree. */\n visitor: PageVisitor;\n /** The root page being traversed. */\n page?: ShopperExperience.schemas['Page'];\n /** The parent region of the current node, if traversing within a region. */\n parentRegion?: ShopperExperience.schemas['Region'];\n /** The parent component of the current node, if traversing within a component's nested regions. */\n parentComponent?: ShopperExperience.schemas['Component'];\n }","record: ResolvedDataBinding | undefined","resolvedData: Record<string, unknown>","node: ShopperExperience.schemas['Component']","currentCategoryId: string | undefined","context: QualifierContext | null","resolvedVariation: VariationEntry | null","resolvedId: string | null","context: QualifierContext | null"],"sources":["../src/design/data/errors/visitor-context-error.ts","../src/design/data/page/transform.ts","../src/design/data/page/resolve-data-bindings.ts","../src/design/data/validate-rule.ts","../src/design/data/page/process-page.ts","../src/design/data/errors/required.ts","../src/design/data/manifest/content-assignment-resolvers.ts","../src/design/data/manifest/resolve-dynamic-page-id.ts","../src/design/data/manifest/get-page.ts","../src/design/data/page/resolve-page.ts"],"sourcesContent":["/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { VisitorContextType } from '../types';\n\nexport class VisitorContextError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'VisitorContextError';\n }\n\n static assert(parentType: VisitorContextType, childType: VisitorContextType) {\n if (\n (parentType === 'component' && childType !== 'region') ||\n (parentType === 'page' && childType !== 'region') ||\n (parentType === 'region' && childType !== 'component')\n ) {\n throw new VisitorContextError(\n `Invalid child context type ${childType} for parent context type ${parentType}`\n );\n }\n }\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { ShopperExperience } from '@/scapi-client/types';\nimport { VisitorContextError } from '../errors/visitor-context-error';\nimport type { InferNodeFromType, VisitorContextType } from '../types';\n\n/**\n * Context object passed to {@link PageVisitor} handler methods during page tree\n * traversal. Provides access to the current node via {@link node}, the tree\n * position via {@link page}, {@link parentRegion}, and {@link parentComponent},\n * and traversal methods ({@link visitRegions}, {@link visitComponents}) for\n * continuing into child nodes.\n *\n * When a visitor handler is defined, the handler is responsible for traversing\n * into children by calling the appropriate context method. If the handler does\n * not call these methods, children will not be visited.\n */\nexport class VisitorContext<TNode> {\n constructor(\n private readonly context: {\n /** The current node being visited. */\n node: TNode;\n /** The node type */\n type: VisitorContextType;\n /** The visitor being used to transform the page tree. */\n visitor: PageVisitor;\n /** The root page being traversed. */\n page?: ShopperExperience.schemas['Page'];\n /** The parent region of the current node, if traversing within a region. */\n parentRegion?: ShopperExperience.schemas['Region'];\n /** The parent component of the current node, if traversing within a component's nested regions. */\n parentComponent?: ShopperExperience.schemas['Component'];\n }\n ) {}\n\n get type(): VisitorContextType {\n return this.context.type;\n }\n\n /**\n * The current node being visited.\n */\n get node(): TNode {\n return this.context.node;\n }\n\n /**\n * The root page being traversed.\n */\n get page(): ShopperExperience.schemas['Page'] | undefined {\n return this.context.page;\n }\n\n /**\n * The parent region of the current node, if traversing within a region.\n */\n get parentRegion(): ShopperExperience.schemas['Region'] | undefined {\n return this.context.parentRegion;\n }\n\n /**\n * The parent component of the current node, if traversing within a component's nested regions.\n */\n get parentComponent(): ShopperExperience.schemas['Component'] | undefined {\n return this.context.parentComponent;\n }\n\n /**\n * Traverses an array of regions, invoking the visitor's `visitRegion` handler\n * on each one. Regions for which the handler returns `null` are excluded from\n * the result. Call this from within a `visitPage` or `visitComponent` handler\n * to continue traversal into child regions.\n *\n * @param regions - The regions to traverse.\n * @returns The filtered array of transformed regions.\n *\n * @example\n * ```ts\n * transformPage(page, {\n * visitPage(context) {\n * // Traverse into regions explicitly\n * const regions = context.visitRegions(context.node.regions);\n * return { ...context.node, regions };\n * },\n * });\n * ```\n */\n visitRegions(regions: ShopperExperience.schemas['Region'][] = []): ShopperExperience.schemas['Region'][] {\n const newRegions = [];\n\n for (const region of regions) {\n const newRegion = this.visitRegion(region);\n\n if (newRegion) {\n newRegions.push(newRegion);\n }\n }\n\n return newRegions;\n }\n\n /**\n * Traverses a single region. If the visitor has a `visitRegion` handler, the\n * handler is called with a new {@link VisitorContext} for the region. Otherwise,\n * the region's child components are traversed automatically.\n *\n * @param region - The region to visit.\n * @returns The transformed region, or `null` to exclude it.\n */\n visitRegion(region: ShopperExperience.schemas['Region']): ShopperExperience.schemas['Region'] | null {\n const regionContext = this.toChildContext('region', region);\n\n if (this.context.visitor.visitRegion) {\n return this.context.visitor.visitRegion(regionContext);\n } else if (region.components) {\n return {\n ...region,\n components: regionContext.visitComponents(region.components),\n };\n }\n\n return region;\n }\n\n /**\n * Traverses an array of components, invoking the visitor's `visitComponent`\n * handler on each one. Components for which the handler returns `null` are\n * excluded from the result. Call this from within a `visitRegion` handler to\n * continue traversal into child components.\n *\n * @param components - The components to traverse.\n * @returns The filtered array of transformed components.\n *\n * @example\n * ```ts\n * transformPage(page, {\n * visitRegion(context) {\n * // Traverse into components explicitly\n * const components = context.visitComponents(context.node.components);\n * return { ...context.node, components };\n * },\n * });\n * ```\n */\n visitComponents(\n components: ShopperExperience.schemas['Component'][] = []\n ): ShopperExperience.schemas['Component'][] {\n const newComponents = [];\n\n for (const component of components) {\n const newComponent = this.visitComponent(component);\n\n if (newComponent) {\n newComponents.push(newComponent);\n }\n }\n\n return newComponents;\n }\n\n /**\n * Traverses a single component. If the visitor has a `visitComponent` handler,\n * the handler is called with a new {@link VisitorContext} for the component.\n * Otherwise, the component's nested regions are traversed automatically.\n *\n * @param component - The component to visit.\n * @returns The transformed component, or `null` to exclude it.\n */\n visitComponent(component: ShopperExperience.schemas['Component']): ShopperExperience.schemas['Component'] | null {\n const componentContext = this.toChildContext('component', component);\n\n if (this.context.visitor.visitComponent) {\n return this.context.visitor.visitComponent(componentContext);\n } else if (component.regions) {\n return {\n ...component,\n regions: componentContext.visitRegions(component.regions),\n };\n }\n\n return component;\n }\n\n /**\n * Traverses a single page. If the visitor has a `visitPage` handler, the\n * handler is called with a new {@link VisitorContext} for the page. Otherwise,\n * the page's regions are traversed automatically.\n *\n * @param page - The page to visit.\n * @returns The transformed page, or `null` to exclude it.\n */\n visitPage(page: ShopperExperience.schemas['Page']): ShopperExperience.schemas['Page'] | null {\n const pageContext = new VisitorContext({\n type: 'page',\n visitor: this.context.visitor,\n page,\n parentComponent: undefined,\n parentRegion: undefined,\n node: page,\n });\n\n if (this.context.visitor.visitPage) {\n return this.context.visitor.visitPage(pageContext);\n } else if (page.regions) {\n const newPage = {\n ...page,\n regions: pageContext.visitRegions(page.regions),\n };\n\n return newPage;\n }\n\n return page;\n }\n\n private toChildContext<TType extends VisitorContextType>(\n type: TType,\n node: InferNodeFromType<TType>\n ): VisitorContext<InferNodeFromType<TType>> {\n VisitorContextError.assert(this.context.type, type);\n\n if (type === 'region') {\n return new VisitorContext({\n type: 'region',\n visitor: this.context.visitor,\n page: this.page,\n node,\n parentComponent: this.node as ShopperExperience.schemas['Component'],\n parentRegion: this.parentRegion,\n });\n }\n\n return new VisitorContext({\n type: 'component',\n visitor: this.context.visitor,\n page: this.page,\n node,\n parentComponent: this.parentComponent,\n parentRegion: this.node as ShopperExperience.schemas['Region'],\n });\n }\n}\n\nclass RootVisitorContext extends VisitorContext<null> {\n constructor(visitor: PageVisitor) {\n super({\n node: null,\n type: 'root',\n visitor,\n });\n }\n}\n\n/**\n * Visitor interface for traversing and transforming a Page Designer page tree.\n * Implement any combination of visit methods to intercept pages, regions, or\n * components during traversal. Return `null` from `visitRegion` or\n * `visitComponent` to remove that element from the tree.\n */\nexport interface PageVisitor {\n visitPage?(context: VisitorContext<ShopperExperience.schemas['Page']>): ShopperExperience.schemas['Page'];\n visitRegion?(\n context: VisitorContext<ShopperExperience.schemas['Region']>\n ): ShopperExperience.schemas['Region'] | null;\n visitComponent?(\n component: VisitorContext<ShopperExperience.schemas['Component']>\n ): ShopperExperience.schemas['Component'] | null;\n}\n\n/**\n * Traverses a page tree using the visitor pattern, applying the visitor's\n * callbacks to the page, its regions, and their nested components. This is\n * the top-level entry point for page tree transformation.\n *\n * When a visitor handler is defined, it receives a {@link VisitorContext} and\n * is responsible for traversing into children using the context's traversal\n * methods (`visitRegions`, `visitComponents`). If the handler does not call\n * these methods, children will not be visited. When no handler is defined for\n * a node type, children are traversed automatically.\n *\n * Returning `null` from a `visitRegion` or `visitComponent` callback removes\n * that element and its children from the resulting tree.\n *\n * @param page - The page to traverse.\n * @param visitor - The visitor with callbacks to apply at each tree node.\n * @returns A new page with visitor transformations applied, or `null`.\n *\n * @example\n * ```ts\n * import { transformPage } from '@salesforce/storefront-next-runtime/design/data';\n *\n * const page = { id: 'homepage', typeId: 'storePage', regions: [\n * { id: 'header', components: [\n * { id: 'hero-banner', typeId: 'commerce_assets.heroBanner', regions: [] },\n * { id: 'promo-tile', typeId: 'commerce_assets.promoTile', regions: [] },\n * ]},\n * ]};\n *\n * // When only visitComponent is defined, regions are traversed automatically.\n * // The handler receives a VisitorContext — use context.node to access the component.\n * transformPage(page, {\n * visitComponent(context) {\n * console.log(`Component: ${context.node.typeId} in region ${context.parentRegion?.id}`);\n * return context.node;\n * },\n * });\n *\n * // When visitRegion is defined, the handler must traverse into children explicitly.\n * // Without calling context.visitComponents(), components inside the region are skipped.\n * transformPage(page, {\n * visitRegion(context) {\n * console.log(`Entering region: ${context.node.id}`);\n * const components = context.visitComponents(context.node.components);\n * return { ...context.node, components };\n * },\n * visitComponent(context) {\n * console.log(` Component: ${context.node.typeId}`);\n * return context.node;\n * },\n * });\n * ```\n */\nexport function transformPage(\n page: ShopperExperience.schemas['Page'],\n visitor: PageVisitor\n): ShopperExperience.schemas['Page'] | null {\n return new RootVisitorContext(visitor).visitPage(page);\n}\n\n/**\n * Applies the visitor to a single component. If the visitor's `visitComponent`\n * handler is defined, it receives a {@link VisitorContext} and is responsible\n * for traversing into the component's nested regions using `context.visitRegions()`.\n * If no `visitComponent` handler is defined, nested regions are traversed\n * automatically. Returns `null` to exclude the component from the result.\n *\n * @param component - The component to transform.\n * @param visitor - The visitor with callbacks.\n * @returns The transformed component, or `null` to exclude it.\n *\n * @example\n * ```ts\n * import { transformComponent } from '@salesforce/storefront-next-runtime/design/data';\n *\n * // Replace the image URL in a hero banner component and traverse its nested regions\n * const heroBanner = {\n * id: 'hero-1',\n * typeId: 'commerce_assets.heroBanner',\n * data: { imageUrl: '/images/summer-sale.jpg' },\n * regions: [{ id: 'banner-content', components: [] }],\n * };\n *\n * const result = transformComponent(heroBanner, {\n * visitComponent(context) {\n * // Traverse into nested regions using the context API\n * const regions = context.visitRegions(context.node.regions);\n *\n * if (context.node.typeId === 'commerce_assets.heroBanner') {\n * return { ...context.node, regions, data: { ...context.node.data, imageUrl: '/images/winter-sale.jpg' } };\n * }\n * return { ...context.node, regions };\n * },\n * });\n * ```\n */\nexport function transformComponent(\n component: ShopperExperience.schemas['Component'],\n visitor: PageVisitor\n): ShopperExperience.schemas['Component'] | null {\n return new RootVisitorContext(visitor).visitComponent(component);\n}\n\n/**\n * Applies the visitor to a single region. If the visitor's `visitRegion`\n * handler is defined, it receives a {@link VisitorContext} and is responsible\n * for traversing into the region's child components using `context.visitComponents()`.\n * If no `visitRegion` handler is defined, child components are traversed\n * automatically. Returns `null` to exclude the region and all its children\n * from the result.\n *\n * @param region - The region to transform.\n * @param visitor - The visitor with callbacks.\n * @returns The transformed region, or `null` to exclude it.\n *\n * @example\n * ```ts\n * import { transformRegion } from '@salesforce/storefront-next-runtime/design/data';\n *\n * // Filter empty regions and traverse into non-empty ones\n * const emptyRegion = { id: 'sidebar', components: [] };\n * const populatedRegion = { id: 'main', components: [\n * { id: 'product-grid', typeId: 'commerce_assets.productGrid', regions: [] },\n * ]};\n *\n * const visitor = {\n * visitRegion(context) {\n * if (!context.node.components?.length) {\n * return null; // Remove empty regions\n * }\n * // Traverse into child components using the context API\n * const components = context.visitComponents(context.node.components);\n * return { ...context.node, components };\n * },\n * };\n *\n * transformRegion(emptyRegion, visitor); // => null (removed)\n * transformRegion(populatedRegion, visitor); // => { id: 'main', components: [...] }\n * ```\n */\nexport function transformRegion(\n region: ShopperExperience.schemas['Region'],\n visitor: PageVisitor\n): ShopperExperience.schemas['Region'] | null {\n return new RootVisitorContext(visitor).visitRegion(region);\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { QualifierContext, ResolvedDataBinding } from '../types';\nimport type { ShopperExperience } from '@/scapi-client/types';\n\n/**\n * Data binding metadata attached to a component instance. Stored in the\n * component's `custom.dataBinding` field by ECOM when the author binds a\n * data source to the component in Page Designer.\n */\nexport interface ComponentDataBinding {\n /** Maps attribute names to expression strings (e.g. `\"content_asset.body\"`). */\n expressions: Record<string, string>;\n /** The data contexts bound to this component, identifying the records to resolve against. */\n contexts: DataBindingContext[];\n}\n\n/**\n * A data context reference on a component instance, identifying a specific\n * record from a data provider.\n */\nexport interface DataBindingContext {\n /** The data provider type (e.g. `\"content_asset\"`). */\n type: string;\n /** The record identifier (e.g. a content asset UUID). */\n id: string;\n}\n\n/**\n * Pattern matching bare expressions: `type.field`.\n */\nconst BARE_EXPRESSION_PATTERN = /^(\\w+)\\.(\\w+)$/;\n\n/**\n * Parses a binding expression string into its provider type and field name.\n * Supports the bare `type.field` format.\n *\n * @param expression - The expression string to parse.\n * @returns The parsed type and field, or `null` if the expression is invalid.\n *\n * @example\n * ```ts\n * parseExpression('content_asset.title'); // { type: 'content_asset', field: 'title' }\n * parseExpression('invalid'); // null\n * ```\n */\nexport function parseExpression(expression: string): { type: string; field: string } | null {\n const match = expression.trim().match(BARE_EXPRESSION_PATTERN);\n if (match) {\n return { type: match[1], field: match[2] };\n }\n\n return null;\n}\n\n/**\n * Resolves a single binding expression against the component's data contexts\n * and the resolved data bindings from context resolution.\n *\n * Returns the resolved field value, or an empty string if the expression is\n * invalid, the matching context or record is not found, or the field does not\n * exist on the resolved record.\n *\n * @param expression - The expression string (e.g. `\"content_asset.body\"`).\n * @param contexts - The component's data binding contexts.\n * @param dataBindings - The resolved data bindings from {@link QualifierContext}.\n * @returns The resolved value, or `''` if resolution fails.\n */\nexport function resolveExpression(\n expression: string,\n contexts: DataBindingContext[],\n dataBindings: NonNullable<QualifierContext['dataBindings']>\n): unknown {\n const parsed = parseExpression(expression);\n if (!parsed) return '';\n\n const context = contexts.find((c) => c.type === parsed.type);\n if (!context) return '';\n\n const record: ResolvedDataBinding | undefined = dataBindings[context.type]?.[context.id];\n if (!record) return '';\n\n return record[parsed.field] ?? '';\n}\n\n/**\n * Extracts the {@link ComponentDataBinding} metadata from a component's\n * `custom` field. Returns `undefined` if the component has no data binding\n * configuration.\n */\nfunction getDataBinding(component: ShopperExperience.schemas['Component']): ComponentDataBinding | undefined {\n const custom = component.custom as Record<string, unknown> | undefined;\n return custom?.dataBinding as ComponentDataBinding | undefined;\n}\n\n/**\n * Resolves data binding expressions for a single component. Replaces attribute\n * values in the component's `data` with the resolved values from context\n * resolution. Attributes without a matching expression are preserved as-is.\n * When an expression cannot be resolved, the attribute value is set to an\n * empty string.\n *\n * Returns the component unchanged if it has no data binding metadata or if\n * `dataBindings` is `undefined`.\n *\n * @param component - The component to resolve data bindings for.\n * @param dataBindings - The resolved data bindings from {@link QualifierContext}, or `undefined` if no bindings were resolved.\n * @returns The component with resolved attribute values, or the original component if no bindings apply.\n *\n * @example\n * ```ts\n * import { resolveComponentDataBindings } from '@salesforce/storefront-next-runtime/design/data';\n *\n * const component = {\n * id: 'banner',\n * typeId: 'commerce_assets.contentBanner',\n * data: { heading: 'Fallback Title', body: 'Fallback Body' },\n * custom: {\n * dataBinding: {\n * expressions: {\n * heading: 'content_asset.title',\n * body: 'content_asset.body',\n * },\n * contexts: [{ type: 'content_asset', id: 'winter-sale-uuid' }],\n * },\n * },\n * regions: [],\n * };\n *\n * const dataBindings = {\n * content_asset: {\n * 'winter-sale-uuid': {\n * title: 'Winter Sale',\n * body: '<div>Free Shipping on all orders!</div>',\n * },\n * },\n * };\n *\n * const resolved = resolveComponentDataBindings(component, dataBindings);\n * // resolved.data.heading === 'Winter Sale'\n * // resolved.data.body === '<div>Free Shipping on all orders!</div>'\n * ```\n */\nexport function resolveComponentDataBindings(\n component: ShopperExperience.schemas['Component'],\n dataBindings: QualifierContext['dataBindings']\n): ShopperExperience.schemas['Component'] {\n if (!dataBindings) {\n return component;\n }\n\n const binding = getDataBinding(component);\n if (!binding?.contexts?.length) return component;\n\n const expressionEntries = Object.entries(binding.expressions ?? {});\n if (expressionEntries.length === 0) return component;\n\n const resolvedData: Record<string, unknown> = {\n ...(component.data as Record<string, unknown> | undefined),\n };\n\n for (const [attrName, expression] of expressionEntries) {\n resolvedData[attrName] = resolveExpression(expression, binding.contexts, dataBindings);\n }\n\n return {\n ...component,\n data: resolvedData as typeof component.data,\n };\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { QualifierContext, VisibilityRuleDef } from './types';\n\n/**\n * Evaluates a visibility rule against a shopper's qualifier context.\n *\n * Campaign-based and non-campaign rules are **mutually exclusive** paths,\n * matching the server's `VisibilityDefinition.isVisible()` logic:\n *\n * - **Campaign-based rule** (has `campaignQualifiers`): only the campaign\n * qualifiers are checked. Schedule, locale, and customer-group fields are\n * ignored because the campaign qualification already incorporates those\n * checks server-side.\n * - **Non-campaign rule**: locale, schedule, AND customer groups are checked.\n * All specified conditions must pass.\n *\n * When no context is provided and the rule requires campaign or customer group\n * checks, those checks will fail (returning `false`). Schedule checks do not\n * require context and are evaluated against `Date.now()`.\n *\n * @param rule - The visibility rule to evaluate.\n * @param locale - The current locale (e.g. `\"en_US\"`). Used to check whether the rule applies to this locale.\n * @param context - The shopper's active qualifiers, or `null`/`undefined` if not yet resolved.\n * @returns `true` if the rule's conditions pass, `false` otherwise.\n *\n * @example\n * ```ts\n * import { validateRule } from '@salesforce/storefront-next-runtime/design/data';\n *\n * // Campaign-based rule — only campaign qualifiers are evaluated\n * const campaignRule = {\n * activeLocales: ['en_US'],\n * campaignQualifiers: [{ campaignId: 'holiday-sale-2026', promotionId: 'free-shipping' }],\n * };\n *\n * // Non-campaign rule — locale, schedule AND customer groups are evaluated\n * const segmentRule = {\n * activeLocales: ['en_US', 'fr_FR'],\n * customerGroups: ['vip-customers'],\n * schedule: {\n * start: new Date('2026-12-01').toISOString(),\n * end: new Date('2026-12-31').toISOString(),\n * },\n * };\n * ```\n */\nexport function validateRule(rule: VisibilityRuleDef, locale: string, context?: QualifierContext | null): boolean {\n // Campaign-based rules and non-campaign rules are mutually exclusive\n // paths, mirroring the server's if/else-if branching.\n if (rule.campaignQualifiers) {\n for (const campaignQualifier of rule.campaignQualifiers) {\n if (!context?.campaignQualifiers[campaignQualifier.campaignId]?.[campaignQualifier.promotionId]) {\n return false;\n }\n }\n } else {\n if (rule.activeLocales && !rule.activeLocales.includes(locale)) {\n return false;\n }\n\n // Rule schedule times are in ISO 8601 format, so we need to convert them to milliseconds\n if (rule.schedule) {\n const now = Date.now();\n\n if (rule.schedule.start) {\n const startTimeInMillis = new Date(rule.schedule.start).getTime();\n\n // If the start time is invalid, the rule fails\n if (Number.isNaN(startTimeInMillis) || startTimeInMillis >= now) {\n return false;\n }\n }\n\n if (rule.schedule.end) {\n const endTimeInMillis = new Date(rule.schedule.end).getTime();\n\n // If the end time is invalid, the rule fails\n if (Number.isNaN(endTimeInMillis) || endTimeInMillis <= now) {\n return false;\n }\n }\n }\n\n if (rule.customerGroups) {\n for (const customerGroup of rule.customerGroups) {\n if (!context?.customerGroups[customerGroup]) {\n return false;\n }\n }\n }\n }\n\n return true;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { transformPage } from './transform';\nimport { resolveComponentDataBindings } from './resolve-data-bindings';\nimport { validateRule } from '../validate-rule';\nimport type { QualifierContext, PageManifest } from '../types';\nimport type { ShopperExperience } from '@/scapi-client/types';\n\n/**\n * Context required for page processing. Contains the shopper's runtime\n * qualifiers, the component-level visibility rules, and the locale used\n * to resolve locale-specific component content from the page manifest.\n */\nexport interface PageProcessorContext {\n /** The shopper's active qualifiers (campaigns, customer groups), or `null` if not resolved. */\n qualifiers: QualifierContext | null;\n /** Component visibility rule definitions extracted from the page layout. */\n componentInfo: PageManifest['componentInfo'];\n /** The locale to use when resolving locale-specific component content (e.g. `\"en_US\"`). */\n locale: string;\n}\n\n/**\n * Filters a page's components based on their visibility rules and resolves\n * data binding expressions in a single traversal. Traverses the page tree\n * using the visitor pattern and:\n *\n * 1. Removes any component whose visibility rules do not pass against the\n * shopper's qualifier context.\n * 2. Resolves data binding expressions in each surviving component's `data`\n * attributes using the resolved data bindings from context resolution.\n *\n * A component is visible if **any** of its visibility rules pass (OR logic).\n * If a component has rules and none of them pass, it is removed. Components\n * without rules are always included.\n *\n * @param page - The page to process.\n * @param context - The processing context with qualifier data, visibility rules, and resolved data bindings.\n * @returns A new page with invisible components filtered out and data binding expressions resolved.\n *\n * @example\n * ```ts\n * import { processPage } from '@salesforce/storefront-next-runtime/design/data';\n *\n * const page = {\n * id: 'homepage',\n * typeId: 'storePage',\n * regions: [{\n * id: 'main',\n * components: [\n * { id: 'public-banner', typeId: 'commerce_assets.heroBanner', regions: [] },\n * { id: 'loyalty-offer', typeId: 'commerce_assets.promoTile', regions: [] },\n * ],\n * }],\n * };\n *\n * // The \"loyalty-offer\" component requires the shopper to be in \"loyalty-members\"\n * const componentInfo = {\n * 'public-banner': { visibilityRules: [] },\n * 'loyalty-offer': {\n * visibilityRules: [{ customerGroups: ['loyalty-members'] }],\n * },\n * };\n *\n * // Guest shopper — not in any customer group\n * const filtered = processPage(page, {\n * qualifiers: { customerGroups: {}, campaignQualifiers: {} },\n * componentInfo,\n * });\n * // filtered.regions[0].components has only \"public-banner\"\n * // \"loyalty-offer\" was removed because the shopper isn't a loyalty member\n * ```\n */\nexport function processPage(\n page: ShopperExperience.schemas['Page'],\n processorContext: PageProcessorContext\n): ShopperExperience.schemas['Page'] {\n return transformPage(page, {\n visitComponent(ctx) {\n const componentInfo = processorContext.componentInfo[ctx.node.id];\n const visibilityRules = componentInfo?.visibilityRules ?? [];\n\n // Visibility rules use OR logic: the component is visible\n // if ANY rule passes. Only remove it when it has its own\n // rules and none of them pass.\n if (visibilityRules.length > 0) {\n const anyRulePassed = visibilityRules.some((rule) =>\n validateRule(rule, processorContext.locale, processorContext.qualifiers)\n );\n\n if (!anyRulePassed) {\n return null;\n }\n }\n\n // Apply locale-specific content from the manifest to the component's data.\n // The \"default\" locale provides base values; the current locale overrides them.\n const defaultContent = componentInfo?.content?.default ?? {};\n const localeContent = componentInfo?.content?.[processorContext.locale] ?? {};\n const content = { ...defaultContent, ...localeContent };\n const isLocalized = Boolean(componentInfo?.content?.[processorContext.locale]);\n\n let node: ShopperExperience.schemas['Component'] = {\n ...ctx.node,\n // @ts-expect-error - This isn't updated in the schema yet.\n localized: isLocalized,\n // Always true here — this processing logic only runs in live/published mode\n // where invisible components are already filtered out above. The `visible`\n // flag is only false in design/preview mode, which returns all components\n // unfiltered and bypasses this code path entirely.\n visible: true,\n data: {\n ...(ctx.node.data as Record<string, unknown>),\n ...content,\n } as typeof ctx.node.data,\n };\n\n // Resolve data binding expressions (overrides content for bound attributes).\n node = resolveComponentDataBindings(node, processorContext.qualifiers?.dataBindings);\n\n return {\n ...node,\n regions: ctx.visitRegions(ctx.node.regions),\n };\n },\n }) as ShopperExperience.schemas['Page'];\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nexport class RequiredError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'RequiredError';\n }\n\n static assert<TValue>(\n value: TValue,\n message: string,\n isEmpty: (value: TValue) => boolean = (v) => v == null\n ): asserts value is NonNullable<TValue> {\n if (isEmpty(value)) {\n throw new RequiredError(message);\n }\n }\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { SiteManifest } from '../types';\n\n/**\n * The result of resolving an identifier through a content assignment resolver.\n * Contains the object type, aspect type, and ordered list of keys to search\n * in the site manifest's content assignments.\n */\nexport interface ResolvedContentAssignmentLookup {\n /** The type of commerce object (e.g. `'product'`, `'category'`). */\n objectType: string;\n /** Ordered list of object IDs to search in the site manifest's content assignments. */\n keys: string[];\n}\n\n/**\n * A function that converts an identifier key (e.g., a product or category ID)\n * into a {@link ResolvedContentAssignmentLookup} describing where to search\n * in the site manifest for the assigned page ID.\n */\nexport type ContentAssignmentResolver = (key: string, manifest?: SiteManifest) => ResolvedContentAssignmentLookup;\n\n/**\n * Registry of content assignment resolvers keyed by {@link IdentifierType}.\n * Each resolver knows how to convert its identifier type into a set of lookup\n * keys for the site manifest.\n *\n * Built-in resolvers:\n * - **`'product'`** — Maps a product ID to a single PDP lookup key.\n * - **`'category'`** — Maps a category ID to an ordered list of keys that\n * traverses the category hierarchy from child to root, enabling inherited\n * page assignments.\n *\n * The `'page'` identifier type has no resolver — page IDs are used directly.\n *\n * @example\n * ```ts\n * import { ContentAssignmentResolvers } from '@salesforce/storefront-next-runtime/design/data';\n *\n * // Resolve a product identifier for PDP lookup\n * const productResolver = ContentAssignmentResolvers.get('product');\n * productResolver('nike-air-max-90');\n * // => { objectType: 'product', aspectType: 'pdp', keys: ['nike-air-max-90'] }\n *\n * // Resolve a category identifier — traverses hierarchy to find inherited assignments\n * const categoryResolver = ContentAssignmentResolvers.get('category');\n * const siteManifest = {\n * categories: {\n * 'mens-running-shoes': { name: 'Running Shoes', parentCategory: 'mens-shoes' },\n * 'mens-shoes': { name: \"Men's Shoes\", parentCategory: 'mens' },\n * 'mens': { name: 'Men' },\n * },\n * contentObjectAssignments: {},\n * };\n * categoryResolver('mens-running-shoes', siteManifest);\n * // => { objectType: 'category', aspectType: 'plp', keys: ['mens-running-shoes', 'mens-shoes', 'mens'] }\n * ```\n */\nexport const ContentAssignmentResolvers = new Map<string, ContentAssignmentResolver>([\n [\n 'product',\n (key) => ({\n objectType: 'product',\n keys: [key],\n }),\n ],\n [\n 'category',\n (key, manifest) => {\n const keys = [];\n const visited = new Set<string>();\n\n let currentCategoryId: string | undefined = key;\n\n while (currentCategoryId && !visited.has(currentCategoryId)) {\n visited.add(currentCategoryId);\n keys.push(currentCategoryId);\n currentCategoryId = manifest?.categories[currentCategoryId]?.parentCategory;\n }\n\n return {\n objectType: 'category',\n keys,\n };\n },\n ],\n]);\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { SiteManifest, IdentifierType } from '../types';\nimport { ContentAssignmentResolvers } from './content-assignment-resolvers';\n\n/**\n * Converts a product or category identifier into a page ID by looking up\n * content assignments in the site manifest. For categories, the lookup\n * traverses the category hierarchy from the given category up to the root,\n * returning the first matching assignment.\n *\n * Returns `null` if no content assignment is found for the identifier or if\n * the identifier type has no registered resolver.\n *\n * @param options - The resolution options.\n * @param options.id - The identifier to resolve (product ID, category ID, or page ID).\n * @param options.identifierType - The type of identifier: `'product'`, `'category'`, or `'page'`.\n * @param options.siteManifest - The site manifest containing content assignments and category hierarchy.\n * @returns The resolved page ID, or `null` if no assignment was found.\n *\n * @example\n * ```ts\n * import { resolveDynamicPageId } from '@salesforce/storefront-next-runtime/design/data';\n *\n * const siteManifest = {\n * contentObjectAssignments: {\n * plp: {\n * category: {\n * 'mens-shoes': {\n * lookupMode: 'category-explicit',\n * contentId: 'page-mens-shoes-plp',\n * },\n * },\n * },\n * },\n * categories: {\n * 'mens-running-shoes': { name: 'Running Shoes', parentCategory: 'mens-shoes' },\n * 'mens-shoes': { name: \"Men's Shoes\" },\n * },\n * };\n *\n * // Direct match\n * resolveDynamicPageId({ id: 'mens-shoes', identifierType: 'category', siteManifest });\n * // => 'page-mens-shoes-plp'\n *\n * // Inherited from parent category\n * resolveDynamicPageId({ id: 'mens-running-shoes', identifierType: 'category', siteManifest });\n * // => 'page-mens-shoes-plp' (found via parent traversal)\n *\n * // No assignment found\n * resolveDynamicPageId({ id: 'womens-shoes', identifierType: 'category', siteManifest });\n * // => null\n * ```\n */\nexport function resolveDynamicPageId<TIdentifier extends IdentifierType = IdentifierType>({\n id,\n identifierType,\n siteManifest,\n aspectType,\n}: {\n id: string;\n identifierType: TIdentifier;\n aspectType: string;\n siteManifest?: SiteManifest;\n}): string | null {\n const resolvedContentAssignmentLookup = ContentAssignmentResolvers.get(identifierType)?.(id, siteManifest);\n\n if (resolvedContentAssignmentLookup) {\n for (const key of resolvedContentAssignmentLookup.keys) {\n const contentAssignment =\n siteManifest?.contentObjectAssignments?.[aspectType]?.[resolvedContentAssignmentLookup.objectType]?.[\n key\n ];\n\n if (contentAssignment) {\n return contentAssignment.contentId;\n }\n }\n }\n\n return null;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { ContextResolver, PageManifest, QualifierContext, VariationEntry } from '../types';\nimport { validateRule } from '../validate-rule';\n\n/**\n * Selects the appropriate page variation from a manifest by evaluating each\n * variation's visibility rule in order. Returns the first variation whose rule\n * passes, or falls back to the manifest's default variation.\n *\n * The qualifier context is resolved lazily — the `contextResolver` is only\n * called when a variation's `ruleRequiresContext` flag is `true`, and only\n * once (the result is cached for subsequent variations).\n *\n * @param manifest - The page manifest containing all variations.\n * @param options - Resolution options.\n * @param options.contextResolver - Optional async function that returns the shopper's qualifier context. Only called if a variation's rule needs it.\n * @param options.locale - The current locale (e.g. `\"en_US\"`). Used to evaluate locale-based visibility rules.\n * @returns The selected variation entry and resolved context, or `null` if no variation (including default) exists.\n *\n * @example\n * ```ts\n * import { getPageFromManifest } from '@salesforce/storefront-next-runtime/design/data';\n *\n * const manifest = {\n * pageId: 'homepage',\n * context: { campaignQualifiers: [], customerGroups: ['vip-customers'], dataBindings: [] },\n * variationOrder: ['vip-homepage', 'holiday-homepage'],\n * variations: {\n * 'vip-homepage': {\n * ruleRequiresContext: true,\n * pageRequiresContext: false,\n * visibilityRule: { activeLocales: ['en-US'], customerGroups: ['vip-customers'] },\n * page: { id: 'homepage', typeId: 'storePage', regions: [] },\n * },\n * 'holiday-homepage': {\n * ruleRequiresContext: false,\n * pageRequiresContext: false,\n * visibilityRule: {\n * activeLocales: ['en-US'],\n * schedule: {\n * start: new Date('2026-12-01').toISOString(),\n * end: new Date('2026-12-31').toISOString(),\n * },\n * },\n * page: { id: 'homepage', typeId: 'storePage', regions: [] },\n * },\n * 'default-homepage': {\n * ruleRequiresContext: false,\n * pageRequiresContext: false,\n * page: { id: 'homepage', typeId: 'storePage', regions: [] },\n * },\n * },\n * defaultVariation: 'default-homepage',\n * componentInfo: {},\n * };\n *\n * // VIP shopper — matches first variation\n * const result = await getPageFromManifest(manifest, {\n * locale: 'en-US',\n * contextResolver: async () => ({\n * customerGroups: { 'vip-customers': true },\n * campaignQualifiers: {},\n * }),\n * });\n * // result.entry === manifest.variations['vip-homepage']\n *\n * // Non-VIP shopper outside holiday window — falls back to default\n * const fallback = await getPageFromManifest(manifest, {\n * locale: 'en-US',\n * contextResolver: async () => ({\n * customerGroups: {},\n * campaignQualifiers: {},\n * }),\n * });\n * // fallback.entry === manifest.variations['default-homepage']\n * ```\n */\nexport async function getPageFromManifest(\n manifest: PageManifest,\n {\n contextResolver,\n locale,\n }: {\n contextResolver?: ContextResolver;\n locale: string;\n }\n): Promise<{\n entry: VariationEntry;\n context: QualifierContext | null;\n} | null> {\n let context: QualifierContext | null = null;\n let resolvedVariation: VariationEntry | null = null;\n\n for (const variationId of manifest.variationOrder) {\n const variation = manifest.variations[variationId];\n\n if (variation?.ruleRequiresContext && !context) {\n context = (await contextResolver?.(manifest.context)) ?? null;\n }\n\n if (!variation?.visibilityRule || validateRule(variation.visibilityRule, locale, context)) {\n resolvedVariation = variation;\n break;\n }\n }\n\n if (!resolvedVariation) {\n resolvedVariation = manifest.variations[manifest.defaultVariation];\n }\n\n if (!resolvedVariation) {\n return null;\n }\n\n return {\n entry: resolvedVariation,\n context,\n };\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { IdentifierType, ManifestStorage, ContextResolver, QualifierContext } from '../types';\nimport type { ShopperExperience } from '@/scapi-client/types';\nimport { ContentAssignmentResolvers } from '../manifest/content-assignment-resolvers';\nimport { resolveDynamicPageId } from '../manifest/resolve-dynamic-page-id';\nimport { getPageFromManifest } from '../manifest/get-page';\nimport { processPage } from './process-page';\nimport { RequiredError } from '../errors/required';\n\n/**\n * Main entry point for the page resolution pipeline. Orchestrates the full flow:\n *\n * 1. **Resolve dynamic page ID** — For product/category identifiers, looks up\n * the assigned page ID via content assignments in the site manifest.\n * 2. **Fetch page manifest** — Loads all variations for the resolved page.\n * 3. **Select variation** — Evaluates visibility rules to pick the right variation.\n * 4. **Load qualifier context** — Lazily fetches the shopper's context only if needed.\n * 5. **Process page** — Filters out components that fail visibility rules.\n *\n * Returns `null` if the page ID cannot be resolved, the manifest doesn't exist,\n * or no variation is available.\n *\n * @param options - The resolution options.\n * @param options.id - The identifier to resolve (product ID, category ID, or page ID).\n * @param options.identifierType - The type of identifier: `'product'`, `'category'`, or `'page'`.\n * @param options.locale - The locale to resolve the page for (e.g. `\"en-US\"`).\n * @param options.manifestStorage - Storage implementation for fetching manifests.\n * @param options.contextResolver - Optional async function that returns the shopper's qualifier context. Only called if a visibility rule needs it.\n * @param options.aspectType - The aspect type to resolve the page for when the identifier type is `'product'` or `'category'`.\n * @returns The fully resolved and filtered page, or `null`.\n *\n * @example\n * ```ts\n * import { resolvePage } from '@salesforce/storefront-next-runtime/design/data';\n *\n * // Resolve the PDP page for a specific product with an active holiday campaign\n * const page = await resolvePage({\n * id: 'nike-air-max-90',\n * identifierType: 'product',\n * aspectType: 'pdp',\n * locale: 'en-US',\n * manifestStorage: {\n * async getPageManifest(id, locale) {\n * // Fetch from CDN, filesystem, or database\n * return fetchManifest(`/manifests/${locale}/${id}.json`);\n * },\n * async getSiteManifest(locale) {\n * return fetchManifest(`/manifests/${locale}/site.json`);\n * },\n * },\n * contextResolver: async () => ({\n * customerGroups: { 'vip-customers': true },\n * campaignQualifiers: {\n * 'holiday-sale-2026': { 'free-shipping': true },\n * },\n * }),\n * });\n *\n * if (page) {\n * // page.regions contains only components visible to this VIP shopper\n * // during the holiday sale campaign\n * renderPage(page);\n * }\n * ```\n */\nexport async function resolvePage({\n id,\n identifierType,\n aspectType,\n locale,\n manifestStorage,\n contextResolver,\n}: {\n id: string;\n identifierType: IdentifierType;\n aspectType?: string;\n locale: string;\n manifestStorage: ManifestStorage;\n contextResolver?: ContextResolver;\n}): Promise<ShopperExperience.schemas['Page'] | null> {\n let resolvedId: string | null = null;\n\n if (ContentAssignmentResolvers.has(identifierType)) {\n const siteManifest = await manifestStorage.getSiteManifest(locale);\n\n RequiredError.assert(aspectType, `Aspect type is required for identifier type ${identifierType}`, (v) => !v);\n\n resolvedId = resolveDynamicPageId({ id, identifierType, aspectType, siteManifest });\n } else {\n resolvedId = id;\n }\n\n if (!resolvedId) {\n return null;\n }\n\n const pageManifest = await manifestStorage.getPageManifest(resolvedId, locale);\n\n if (!pageManifest) {\n return null;\n }\n\n const pageResults = await getPageFromManifest(pageManifest, {\n contextResolver,\n locale,\n });\n\n if (!pageResults) {\n return null;\n }\n\n let context: QualifierContext | null = null;\n\n if (pageResults.entry.pageRequiresContext) {\n context = pageResults.context ?? (await contextResolver?.(pageManifest.context)) ?? null;\n }\n\n return processPage(pageResults.entry.page, {\n qualifiers: context,\n componentInfo: pageManifest.componentInfo,\n locale,\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"design-data.js","names":["context: {\n /** The current node being visited. */\n node: TNode;\n /** The node type */\n type: VisitorContextType;\n /** The visitor being used to transform the page tree. */\n visitor: PageVisitor;\n /** The root page being traversed. */\n page?: ShopperExperience.schemas['Page'];\n /** The parent visitor context, providing access to the node that contains the current one in the page tree. */\n parent?: VisitorContext<\n | ShopperExperience.schemas['Page']\n | ShopperExperience.schemas['Region']\n | ShopperExperience.schemas['Component']\n >;\n /** The parent region of the current node, if traversing within a region. */\n parentRegion?: ShopperExperience.schemas['Region'];\n /** The parent component of the current node, if traversing within a component's nested regions. */\n parentComponent?: ShopperExperience.schemas['Component'];\n }","record: ResolvedDataBinding | undefined","resolvedData: Record<string, unknown>","regionInfo: RegionInfo | undefined","result: ShopperExperience.schemas['Component'][]","node: ShopperExperience.schemas['Component']","currentCategoryId: string | undefined","context: QualifierContext | null","resolvedVariation: VariationEntry | null","resolvedId: string | null","context: QualifierContext | null"],"sources":["../src/design/data/errors/visitor-context-error.ts","../src/design/data/page/transform.ts","../src/design/data/page/resolve-data-bindings.ts","../src/design/data/validate-rule.ts","../src/design/data/page/process-page.ts","../src/design/data/errors/required.ts","../src/design/data/manifest/content-assignment-resolvers.ts","../src/design/data/manifest/resolve-dynamic-page-id.ts","../src/design/data/manifest/get-page.ts","../src/design/data/page/resolve-page.ts"],"sourcesContent":["/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { VisitorContextType } from '../types';\n\nexport class VisitorContextError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'VisitorContextError';\n }\n\n static assert(parentType: VisitorContextType, childType: VisitorContextType) {\n if (\n (parentType === 'component' && childType !== 'region') ||\n (parentType === 'page' && childType !== 'region') ||\n (parentType === 'region' && childType !== 'component')\n ) {\n throw new VisitorContextError(\n `Invalid child context type ${childType} for parent context type ${parentType}`\n );\n }\n }\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { ShopperExperience } from '@/scapi-client/types';\nimport { VisitorContextError } from '../errors/visitor-context-error';\nimport type { InferNodeFromType, VisitorContextType } from '../types';\n\n/**\n * Context object passed to {@link PageVisitor} handler methods during page tree\n * traversal. Provides access to the current node via {@link node}, the tree\n * position via {@link page}, {@link parentRegion}, and {@link parentComponent},\n * and traversal methods ({@link visitRegions}, {@link visitComponents}) for\n * continuing into child nodes.\n *\n * When a visitor handler is defined, the handler is responsible for traversing\n * into children by calling the appropriate context method. If the handler does\n * not call these methods, children will not be visited.\n */\nexport class VisitorContext<TNode> {\n constructor(\n private readonly context: {\n /** The current node being visited. */\n node: TNode;\n /** The node type */\n type: VisitorContextType;\n /** The visitor being used to transform the page tree. */\n visitor: PageVisitor;\n /** The root page being traversed. */\n page?: ShopperExperience.schemas['Page'];\n /** The parent visitor context, providing access to the node that contains the current one in the page tree. */\n parent?: VisitorContext<\n | ShopperExperience.schemas['Page']\n | ShopperExperience.schemas['Region']\n | ShopperExperience.schemas['Component']\n >;\n /** The parent region of the current node, if traversing within a region. */\n parentRegion?: ShopperExperience.schemas['Region'];\n /** The parent component of the current node, if traversing within a component's nested regions. */\n parentComponent?: ShopperExperience.schemas['Component'];\n }\n ) {}\n\n get type(): VisitorContextType {\n return this.context.type;\n }\n\n /**\n * The current node being visited.\n */\n get node(): TNode {\n return this.context.node;\n }\n\n /**\n * The root page being traversed.\n */\n get page(): ShopperExperience.schemas['Page'] | undefined {\n return this.context.page;\n }\n\n /**\n * The parent visitor context, providing access to the node that contains the current one in the page tree.\n */\n get parent():\n | VisitorContext<\n | ShopperExperience.schemas['Page']\n | ShopperExperience.schemas['Region']\n | ShopperExperience.schemas['Component']\n >\n | undefined {\n return this.context.parent;\n }\n\n /**\n * The parent region of the current node, if traversing within a region.\n */\n get parentRegion(): ShopperExperience.schemas['Region'] | undefined {\n return this.context.parentRegion;\n }\n\n /**\n * The parent component of the current node, if traversing within a component's nested regions.\n */\n get parentComponent(): ShopperExperience.schemas['Component'] | undefined {\n return this.context.parentComponent;\n }\n\n /**\n * Traverses an array of regions, invoking the visitor's `visitRegion` handler\n * on each one. Regions for which the handler returns `null` are excluded from\n * the result. Call this from within a `visitPage` or `visitComponent` handler\n * to continue traversal into child regions.\n *\n * @param regions - The regions to traverse.\n * @returns The filtered array of transformed regions.\n *\n * @example\n * ```ts\n * transformPage(page, {\n * visitPage(context) {\n * // Traverse into regions explicitly\n * const regions = context.visitRegions(context.node.regions);\n * return { ...context.node, regions };\n * },\n * });\n * ```\n */\n visitRegions(regions: ShopperExperience.schemas['Region'][] = []): ShopperExperience.schemas['Region'][] {\n const newRegions = [];\n\n for (const region of regions) {\n const newRegion = this.visitRegion(region);\n\n if (newRegion) {\n newRegions.push(newRegion);\n }\n }\n\n return newRegions;\n }\n\n /**\n * Traverses a single region. If the visitor has a `visitRegion` handler, the\n * handler is called with a new {@link VisitorContext} for the region. Otherwise,\n * the region's child components are traversed automatically.\n *\n * @param region - The region to visit.\n * @returns The transformed region, or `null` to exclude it.\n */\n visitRegion(region: ShopperExperience.schemas['Region']): ShopperExperience.schemas['Region'] | null {\n const regionContext = this.toChildContext('region', region);\n\n if (this.context.visitor.visitRegion) {\n return this.context.visitor.visitRegion(regionContext);\n } else if (region.components) {\n return {\n ...region,\n components: regionContext.visitComponents(region.components),\n };\n }\n\n return region;\n }\n\n /**\n * Traverses an array of components, invoking the visitor's `visitComponent`\n * handler on each one. Components for which the handler returns `null` are\n * excluded from the result. Call this from within a `visitRegion` handler to\n * continue traversal into child components.\n *\n * @param components - The components to traverse.\n * @returns The filtered array of transformed components.\n *\n * @example\n * ```ts\n * transformPage(page, {\n * visitRegion(context) {\n * // Traverse into components explicitly\n * const components = context.visitComponents(context.node.components);\n * return { ...context.node, components };\n * },\n * });\n * ```\n */\n visitComponents(\n components: ShopperExperience.schemas['Component'][] = []\n ): ShopperExperience.schemas['Component'][] {\n const newComponents = [];\n\n for (const component of components) {\n const newComponent = this.visitComponent(component);\n\n if (newComponent) {\n newComponents.push(newComponent);\n }\n }\n\n return newComponents;\n }\n\n /**\n * Traverses a single component. If the visitor has a `visitComponent` handler,\n * the handler is called with a new {@link VisitorContext} for the component.\n * Otherwise, the component's nested regions are traversed automatically.\n *\n * @param component - The component to visit.\n * @returns The transformed component, or `null` to exclude it.\n */\n visitComponent(component: ShopperExperience.schemas['Component']): ShopperExperience.schemas['Component'] | null {\n const componentContext = this.toChildContext('component', component);\n\n if (this.context.visitor.visitComponent) {\n return this.context.visitor.visitComponent(componentContext);\n } else if (component.regions) {\n return {\n ...component,\n regions: componentContext.visitRegions(component.regions),\n };\n }\n\n return component;\n }\n\n /**\n * Traverses a single page. If the visitor has a `visitPage` handler, the\n * handler is called with a new {@link VisitorContext} for the page. Otherwise,\n * the page's regions are traversed automatically.\n *\n * @param page - The page to visit.\n * @returns The transformed page, or `null` to exclude it.\n */\n visitPage(page: ShopperExperience.schemas['Page']): ShopperExperience.schemas['Page'] | null {\n const pageContext = new VisitorContext({\n type: 'page',\n visitor: this.context.visitor,\n page,\n parentComponent: undefined,\n parentRegion: undefined,\n parent: undefined,\n node: page,\n });\n\n if (this.context.visitor.visitPage) {\n return this.context.visitor.visitPage(pageContext);\n } else if (page.regions) {\n const newPage = {\n ...page,\n regions: pageContext.visitRegions(page.regions),\n };\n\n return newPage;\n }\n\n return page;\n }\n\n private toChildContext<TType extends VisitorContextType>(\n type: TType,\n node: InferNodeFromType<TType>\n ): VisitorContext<InferNodeFromType<TType>> {\n VisitorContextError.assert(this.context.type, type);\n\n const parent = this as VisitorContext<\n | ShopperExperience.schemas['Region']\n | ShopperExperience.schemas['Component']\n | ShopperExperience.schemas['Page']\n >;\n\n if (type === 'region') {\n return new VisitorContext({\n type: 'region',\n visitor: this.context.visitor,\n page: this.page,\n node,\n parent,\n parentComponent: this.node as ShopperExperience.schemas['Component'],\n parentRegion: this.parentRegion,\n });\n }\n\n return new VisitorContext({\n type: 'component',\n visitor: this.context.visitor,\n page: this.page,\n node,\n parent,\n parentComponent: this.parentComponent,\n parentRegion: this.node as ShopperExperience.schemas['Region'],\n });\n }\n}\n\nclass RootVisitorContext extends VisitorContext<null> {\n constructor(visitor: PageVisitor) {\n super({\n node: null,\n type: 'root',\n visitor,\n });\n }\n}\n\n/**\n * Visitor interface for traversing and transforming a Page Designer page tree.\n * Implement any combination of visit methods to intercept pages, regions, or\n * components during traversal. Return `null` from `visitRegion` or\n * `visitComponent` to remove that element from the tree.\n */\nexport interface PageVisitor {\n visitPage?(context: VisitorContext<ShopperExperience.schemas['Page']>): ShopperExperience.schemas['Page'];\n visitRegion?(\n context: VisitorContext<ShopperExperience.schemas['Region']>\n ): ShopperExperience.schemas['Region'] | null;\n visitComponent?(\n component: VisitorContext<ShopperExperience.schemas['Component']>\n ): ShopperExperience.schemas['Component'] | null;\n}\n\n/**\n * Traverses a page tree using the visitor pattern, applying the visitor's\n * callbacks to the page, its regions, and their nested components. This is\n * the top-level entry point for page tree transformation.\n *\n * When a visitor handler is defined, it receives a {@link VisitorContext} and\n * is responsible for traversing into children using the context's traversal\n * methods (`visitRegions`, `visitComponents`). If the handler does not call\n * these methods, children will not be visited. When no handler is defined for\n * a node type, children are traversed automatically.\n *\n * Returning `null` from a `visitRegion` or `visitComponent` callback removes\n * that element and its children from the resulting tree.\n *\n * @param page - The page to traverse.\n * @param visitor - The visitor with callbacks to apply at each tree node.\n * @returns A new page with visitor transformations applied, or `null`.\n *\n * @example\n * ```ts\n * import { transformPage } from '@salesforce/storefront-next-runtime/design/data';\n *\n * const page = { id: 'homepage', typeId: 'storePage', regions: [\n * { id: 'header', components: [\n * { id: 'hero-banner', typeId: 'commerce_assets.heroBanner', regions: [] },\n * { id: 'promo-tile', typeId: 'commerce_assets.promoTile', regions: [] },\n * ]},\n * ]};\n *\n * // When only visitComponent is defined, regions are traversed automatically.\n * // The handler receives a VisitorContext — use context.node to access the component.\n * transformPage(page, {\n * visitComponent(context) {\n * console.log(`Component: ${context.node.typeId} in region ${context.parentRegion?.id}`);\n * return context.node;\n * },\n * });\n *\n * // When visitRegion is defined, the handler must traverse into children explicitly.\n * // Without calling context.visitComponents(), components inside the region are skipped.\n * transformPage(page, {\n * visitRegion(context) {\n * console.log(`Entering region: ${context.node.id}`);\n * const components = context.visitComponents(context.node.components);\n * return { ...context.node, components };\n * },\n * visitComponent(context) {\n * console.log(` Component: ${context.node.typeId}`);\n * return context.node;\n * },\n * });\n * ```\n */\nexport function transformPage(\n page: ShopperExperience.schemas['Page'],\n visitor: PageVisitor\n): ShopperExperience.schemas['Page'] | null {\n return new RootVisitorContext(visitor).visitPage(page);\n}\n\n/**\n * Applies the visitor to a single component. If the visitor's `visitComponent`\n * handler is defined, it receives a {@link VisitorContext} and is responsible\n * for traversing into the component's nested regions using `context.visitRegions()`.\n * If no `visitComponent` handler is defined, nested regions are traversed\n * automatically. Returns `null` to exclude the component from the result.\n *\n * @param component - The component to transform.\n * @param visitor - The visitor with callbacks.\n * @returns The transformed component, or `null` to exclude it.\n *\n * @example\n * ```ts\n * import { transformComponent } from '@salesforce/storefront-next-runtime/design/data';\n *\n * // Replace the image URL in a hero banner component and traverse its nested regions\n * const heroBanner = {\n * id: 'hero-1',\n * typeId: 'commerce_assets.heroBanner',\n * data: { imageUrl: '/images/summer-sale.jpg' },\n * regions: [{ id: 'banner-content', components: [] }],\n * };\n *\n * const result = transformComponent(heroBanner, {\n * visitComponent(context) {\n * // Traverse into nested regions using the context API\n * const regions = context.visitRegions(context.node.regions);\n *\n * if (context.node.typeId === 'commerce_assets.heroBanner') {\n * return { ...context.node, regions, data: { ...context.node.data, imageUrl: '/images/winter-sale.jpg' } };\n * }\n * return { ...context.node, regions };\n * },\n * });\n * ```\n */\nexport function transformComponent(\n component: ShopperExperience.schemas['Component'],\n visitor: PageVisitor\n): ShopperExperience.schemas['Component'] | null {\n return new RootVisitorContext(visitor).visitComponent(component);\n}\n\n/**\n * Applies the visitor to a single region. If the visitor's `visitRegion`\n * handler is defined, it receives a {@link VisitorContext} and is responsible\n * for traversing into the region's child components using `context.visitComponents()`.\n * If no `visitRegion` handler is defined, child components are traversed\n * automatically. Returns `null` to exclude the region and all its children\n * from the result.\n *\n * @param region - The region to transform.\n * @param visitor - The visitor with callbacks.\n * @returns The transformed region, or `null` to exclude it.\n *\n * @example\n * ```ts\n * import { transformRegion } from '@salesforce/storefront-next-runtime/design/data';\n *\n * // Filter empty regions and traverse into non-empty ones\n * const emptyRegion = { id: 'sidebar', components: [] };\n * const populatedRegion = { id: 'main', components: [\n * { id: 'product-grid', typeId: 'commerce_assets.productGrid', regions: [] },\n * ]};\n *\n * const visitor = {\n * visitRegion(context) {\n * if (!context.node.components?.length) {\n * return null; // Remove empty regions\n * }\n * // Traverse into child components using the context API\n * const components = context.visitComponents(context.node.components);\n * return { ...context.node, components };\n * },\n * };\n *\n * transformRegion(emptyRegion, visitor); // => null (removed)\n * transformRegion(populatedRegion, visitor); // => { id: 'main', components: [...] }\n * ```\n */\nexport function transformRegion(\n region: ShopperExperience.schemas['Region'],\n visitor: PageVisitor\n): ShopperExperience.schemas['Region'] | null {\n return new RootVisitorContext(visitor).visitRegion(region);\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { ComponentDataBinding, DataBindingRequirement, QualifierContext, ResolvedDataBinding } from '../types';\nimport type { ShopperExperience } from '@/scapi-client/types';\n\n/**\n * Pattern matching bare expressions: `type.field`.\n */\nconst BARE_EXPRESSION_PATTERN = /^(\\w+)\\.(\\w+)$/;\n\n/**\n * Parses a binding expression string into its provider type and field name.\n * Supports the bare `type.field` format.\n *\n * @param expression - The expression string to parse.\n * @returns The parsed type and field, or `null` if the expression is invalid.\n *\n * @example\n * ```ts\n * parseExpression('content_asset.title'); // { type: 'content_asset', field: 'title' }\n * parseExpression('invalid'); // null\n * ```\n */\nexport function parseExpression(expression: string): { type: string; field: string } | null {\n const match = expression.trim().match(BARE_EXPRESSION_PATTERN);\n if (match) {\n return { type: match[1], field: match[2] };\n }\n\n return null;\n}\n\n/**\n * Resolves a single binding expression against the component's data contexts\n * and the resolved data bindings from context resolution.\n *\n * Returns the resolved field value, or an empty string if the expression is\n * invalid, the matching context or record is not found, or the field does not\n * exist on the resolved record.\n *\n * @param expression - The expression string (e.g. `\"content_asset.body\"`).\n * @param contexts - The component's data binding contexts.\n * @param dataBindings - The resolved data bindings from {@link QualifierContext}.\n * @returns The resolved value, or `''` if resolution fails.\n */\nexport function resolveExpression(\n expression: string,\n contexts: DataBindingRequirement[],\n dataBindings: NonNullable<QualifierContext['dataBindings']>\n): unknown {\n const parsed = parseExpression(expression);\n if (!parsed) return '';\n\n const context = contexts.find((c) => c.type === parsed.type);\n if (!context) return '';\n\n const record: ResolvedDataBinding | undefined = dataBindings[context.type]?.[context.id];\n if (!record) return '';\n\n return record[parsed.field] ?? '';\n}\n\n/**\n * Resolves data binding expressions for a single component. Replaces attribute\n * values in the component's `data` with the resolved values from context\n * resolution. Attributes without a matching expression are preserved as-is.\n * When an expression cannot be resolved, the attribute value is set to an\n * empty string.\n *\n * Returns the component unchanged if it has no data binding metadata or if\n * `dataBindings` is `undefined`.\n *\n * @param component - The component to resolve data bindings for.\n * @param binding - The component's data binding metadata from the page manifest's `componentInfo`, or `null`/`undefined` if not bound.\n * @param dataBindings - The resolved data bindings from {@link QualifierContext}, or `undefined` if no bindings were resolved.\n * @returns The component with resolved attribute values, or the original component if no bindings apply.\n *\n * @example\n * ```ts\n * import { resolveComponentDataBindings } from '@salesforce/storefront-next-runtime/design/data';\n *\n * const component = {\n * id: 'banner',\n * typeId: 'commerce_assets.contentBanner',\n * data: { heading: 'Fallback Title', body: 'Fallback Body' },\n * regions: [],\n * };\n *\n * const binding = {\n * expressions: {\n * heading: 'content_asset.title',\n * body: 'content_asset.body',\n * },\n * contexts: [{ type: 'content_asset', id: 'winter-sale-uuid' }],\n * };\n *\n * const dataBindings = {\n * content_asset: {\n * 'winter-sale-uuid': {\n * title: 'Winter Sale',\n * body: '<div>Free Shipping on all orders!</div>',\n * },\n * },\n * };\n *\n * const resolved = resolveComponentDataBindings(component, binding, dataBindings);\n * // resolved.data.heading === 'Winter Sale'\n * // resolved.data.body === '<div>Free Shipping on all orders!</div>'\n * ```\n */\nexport function resolveComponentDataBindings(\n component: ShopperExperience.schemas['Component'],\n binding: ComponentDataBinding | null | undefined,\n dataBindings: QualifierContext['dataBindings']\n): ShopperExperience.schemas['Component'] {\n if (!dataBindings) {\n return component;\n }\n\n if (!binding?.contexts?.length) return component;\n\n const expressionEntries = Object.entries(binding.expressions ?? {});\n if (expressionEntries.length === 0) return component;\n\n const resolvedData: Record<string, unknown> = {\n ...(component.data as Record<string, unknown> | undefined),\n };\n\n for (const [attrName, expression] of expressionEntries) {\n resolvedData[attrName] = resolveExpression(expression, binding.contexts, dataBindings);\n }\n\n return {\n ...component,\n data: resolvedData as typeof component.data,\n };\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { QualifierContext, VisibilityRuleDef } from './types';\n\n/**\n * Evaluates a visibility rule against a shopper's qualifier context.\n *\n * Campaign-based and non-campaign rules are **mutually exclusive** paths,\n * matching the server's `VisibilityDefinition.isVisible()` logic:\n *\n * - **Campaign-based rule** (has `campaignQualifiers`): only the campaign\n * qualifiers are checked. Schedule, locale, and customer-group fields are\n * ignored because the campaign qualification already incorporates those\n * checks server-side.\n * - **Non-campaign rule**: locale, schedule, AND customer groups are checked.\n * All specified conditions must pass.\n *\n * When no context is provided and the rule requires campaign or customer group\n * checks, those checks will fail (returning `false`). Schedule checks do not\n * require context and are evaluated against `Date.now()`.\n *\n * @param rule - The visibility rule to evaluate.\n * @param locale - The current locale (e.g. `\"en_US\"`). Used to check whether the rule applies to this locale.\n * @param context - The shopper's active qualifiers, or `null`/`undefined` if not yet resolved.\n * @returns `true` if the rule's conditions pass, `false` otherwise.\n *\n * @example\n * ```ts\n * import { validateRule } from '@salesforce/storefront-next-runtime/design/data';\n *\n * // Campaign-based rule — only campaign qualifiers are evaluated\n * const campaignRule = {\n * activeLocales: ['en_US'],\n * campaignQualifiers: [{ campaignId: 'holiday-sale-2026', promotionId: 'free-shipping' }],\n * };\n *\n * // Non-campaign rule — locale, schedule AND customer groups are evaluated\n * const segmentRule = {\n * activeLocales: ['en_US', 'fr_FR'],\n * customerGroups: ['vip-customers'],\n * schedule: {\n * start: new Date('2026-12-01').toISOString(),\n * end: new Date('2026-12-31').toISOString(),\n * },\n * };\n * ```\n */\nexport function validateRule(rule: VisibilityRuleDef, locale: string, context?: QualifierContext | null): boolean {\n // Campaign-based rules and non-campaign rules are mutually exclusive\n // paths, mirroring the server's if/else-if branching.\n if (rule.campaignQualifiers) {\n for (const campaignQualifier of rule.campaignQualifiers) {\n if (!context?.campaignQualifiers?.[campaignQualifier.campaignId]?.[campaignQualifier.promotionId]) {\n return false;\n }\n }\n } else {\n if (rule.activeLocales && !rule.activeLocales.includes(locale)) {\n return false;\n }\n\n // Rule schedule times are in ISO 8601 format, so we need to convert them to milliseconds\n if (rule.schedule) {\n const now = Date.now();\n\n if (rule.schedule.start) {\n const startTimeInMillis = new Date(rule.schedule.start).getTime();\n\n // If the start time is invalid, the rule fails\n if (Number.isNaN(startTimeInMillis) || startTimeInMillis >= now) {\n return false;\n }\n }\n\n if (rule.schedule.end) {\n const endTimeInMillis = new Date(rule.schedule.end).getTime();\n\n // If the end time is invalid, the rule fails\n if (Number.isNaN(endTimeInMillis) || endTimeInMillis <= now) {\n return false;\n }\n }\n }\n\n if (rule.customerGroups) {\n for (const customerGroup of rule.customerGroups) {\n if (!context?.customerGroups?.[customerGroup]) {\n return false;\n }\n }\n }\n }\n\n return true;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { transformPage } from './transform';\nimport { resolveComponentDataBindings } from './resolve-data-bindings';\nimport { validateRule } from '../validate-rule';\nimport type { QualifierContext, PageManifest, VariationEntry, RegionInfo } from '../types';\nimport type { ShopperExperience } from '@/scapi-client/types';\n\n/**\n * Context required for page processing. Contains the shopper's runtime\n * qualifiers, the component-level visibility rules, and the locale used\n * to resolve locale-specific component content from the page manifest.\n */\nexport interface PageProcessorContext {\n /** The shopper's active qualifiers (campaigns, customer groups), or `null` if not resolved. */\n qualifiers: QualifierContext | null;\n /** Component visibility rule definitions extracted from the page layout. */\n componentInfo: PageManifest['componentInfo'];\n /** Page-level region configuration (e.g. maxComponents limits) for top-level regions not nested under a component. */\n pageInfo: {\n regions: VariationEntry['regions'];\n };\n /** The locale to use when resolving locale-specific component content (e.g. `\"en_US\"`). */\n locale: string;\n /**\n * When `true` (default), invisible components are removed from the tree and\n * regions are truncated to their `maxComponents` limit. When `false`, invisible\n * components and overflow components are kept in the tree but marked with\n * `visible: false` — used in design/preview mode so the editor can display them.\n */\n pruneInvisible?: boolean;\n}\n\n/**\n * Filters a page's components based on their visibility rules and resolves\n * data binding expressions in a single traversal. Traverses the page tree\n * using the visitor pattern and:\n *\n * 1. Removes any component whose visibility rules do not pass against the\n * shopper's qualifier context.\n * 2. Resolves data binding expressions in each surviving component's `data`\n * attributes using the resolved data bindings from context resolution.\n *\n * A component is visible if **any** of its visibility rules pass (OR logic).\n * If a component has rules and none of them pass, it is removed. Components\n * without rules are always included.\n *\n * @param page - The page to process.\n * @param context - The processing context with qualifier data, visibility rules, and resolved data bindings.\n * @returns A new page with invisible components filtered out and data binding expressions resolved.\n *\n * @example\n * ```ts\n * import { processPage } from '@salesforce/storefront-next-runtime/design/data';\n *\n * const page = {\n * id: 'homepage',\n * typeId: 'storePage',\n * regions: [{\n * id: 'main',\n * components: [\n * { id: 'public-banner', typeId: 'commerce_assets.heroBanner', regions: [] },\n * { id: 'loyalty-offer', typeId: 'commerce_assets.promoTile', regions: [] },\n * ],\n * }],\n * };\n *\n * // The \"loyalty-offer\" component requires the shopper to be in \"loyalty-members\"\n * const componentInfo = {\n * 'public-banner': { visibilityRules: [] },\n * 'loyalty-offer': {\n * visibilityRules: [{ customerGroups: ['loyalty-members'] }],\n * },\n * };\n *\n * // Guest shopper — not in any customer group\n * const filtered = processPage(page, {\n * qualifiers: { customerGroups: {}, campaignQualifiers: {} },\n * componentInfo,\n * });\n * // filtered.regions[0].components has only \"public-banner\"\n * // \"loyalty-offer\" was removed because the shopper isn't a loyalty member\n * ```\n */\nexport function processPage(\n page: ShopperExperience.schemas['Page'],\n processorContext: PageProcessorContext\n): ShopperExperience.schemas['Page'] {\n const { pruneInvisible = true } = processorContext;\n\n return transformPage(page, {\n visitRegion(ctx) {\n let regionInfo: RegionInfo | undefined;\n\n if (ctx.parent?.type === 'page') {\n regionInfo = processorContext.pageInfo.regions[ctx.node.id];\n } else if (ctx.parent?.type === 'component') {\n regionInfo = processorContext.componentInfo[ctx.parent.node.id]?.regions?.[ctx.node.id];\n }\n\n // Visit each component first — this runs visitComponent which\n // filters out components that fail their visibility rules.\n let components = ctx.visitComponents(ctx.node.components);\n\n if (regionInfo?.maxComponents != null) {\n if (pruneInvisible) {\n components = components.slice(0, regionInfo.maxComponents);\n } else {\n const result: ShopperExperience.schemas['Component'][] = [];\n let visibleCount = 0;\n\n for (const comp of components) {\n if (comp.visible) {\n visibleCount++;\n }\n\n if (visibleCount > regionInfo.maxComponents) {\n result.push({ ...comp, visible: false });\n } else {\n result.push(comp);\n }\n }\n\n components = result;\n }\n }\n\n return {\n ...ctx.node,\n // After visibility filtering, enforce the region's max component\n // limit by keeping only the first N visible components.\n components,\n };\n },\n visitComponent(ctx) {\n const componentInfo = processorContext.componentInfo[ctx.node.id];\n const visibilityRules = componentInfo?.visibilityRules ?? [];\n let isVisible = true;\n\n // Visibility rules use OR logic: the component is visible\n // if ANY rule passes. Only remove it when it has its own\n // rules and none of them pass.\n if (visibilityRules.length > 0) {\n const anyRulePassed = visibilityRules.some((rule) =>\n validateRule(rule, processorContext.locale, processorContext.qualifiers)\n );\n\n if (!anyRulePassed) {\n if (pruneInvisible) {\n return null;\n }\n\n isVisible = false;\n }\n }\n\n // Apply locale-specific content from the manifest to the component's data.\n // The \"default\" locale provides base values; the current locale overrides them.\n const defaultContent = componentInfo?.content?.default ?? {};\n const localeContent = componentInfo?.content?.[processorContext.locale] ?? {};\n const content = { ...defaultContent, ...localeContent };\n const isLocalized = Boolean(componentInfo?.content?.[processorContext.locale]);\n\n let node: ShopperExperience.schemas['Component'] = {\n ...ctx.node,\n localized: isLocalized,\n visible: isVisible,\n data: {\n ...(ctx.node.data as Record<string, unknown>),\n ...content,\n } as typeof ctx.node.data,\n };\n\n // Resolve data binding expressions (overrides content for bound attributes).\n node = resolveComponentDataBindings(\n node,\n componentInfo?.dataBinding,\n processorContext.qualifiers?.dataBindings\n );\n\n return {\n ...node,\n regions: ctx.visitRegions(ctx.node.regions),\n };\n },\n }) as ShopperExperience.schemas['Page'];\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nexport class RequiredError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'RequiredError';\n }\n\n static assert<TValue>(\n value: TValue,\n message: string,\n isEmpty: (value: TValue) => boolean = (v) => v == null\n ): asserts value is NonNullable<TValue> {\n if (isEmpty(value)) {\n throw new RequiredError(message);\n }\n }\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { SiteManifest } from '../types';\n\n/**\n * The result of resolving an identifier through a content assignment resolver.\n * Contains the object type, aspect type, and ordered list of keys to search\n * in the site manifest's content assignments.\n */\nexport interface ResolvedContentAssignmentLookup {\n /** The type of commerce object (e.g. `'product'`, `'category'`). */\n objectType: string;\n /** Ordered list of object IDs to search in the site manifest's content assignments. */\n keys: string[];\n}\n\n/**\n * A function that converts an identifier key (e.g., a product or category ID)\n * into a {@link ResolvedContentAssignmentLookup} describing where to search\n * in the site manifest for the assigned page ID.\n */\nexport type ContentAssignmentResolver = (\n key: string,\n manifest?: SiteManifest | null\n) => ResolvedContentAssignmentLookup;\n\n/**\n * Registry of content assignment resolvers keyed by {@link IdentifierType}.\n * Each resolver knows how to convert its identifier type into a set of lookup\n * keys for the site manifest.\n *\n * Built-in resolvers:\n * - **`'product'`** — Maps a product ID to a single PDP lookup key.\n * - **`'category'`** — Maps a category ID to an ordered list of keys that\n * traverses the category hierarchy from child to root, enabling inherited\n * page assignments.\n *\n * The `'page'` identifier type has no resolver — page IDs are used directly.\n *\n * @example\n * ```ts\n * import { ContentAssignmentResolvers } from '@salesforce/storefront-next-runtime/design/data';\n *\n * // Resolve a product identifier for PDP lookup\n * const productResolver = ContentAssignmentResolvers.get('product');\n * productResolver('nike-air-max-90');\n * // => { objectType: 'product', aspectType: 'pdp', keys: ['nike-air-max-90'] }\n *\n * // Resolve a category identifier — traverses hierarchy to find inherited assignments\n * const categoryResolver = ContentAssignmentResolvers.get('category');\n * const siteManifest = {\n * categories: {\n * 'mens-running-shoes': { name: 'Running Shoes', parentCategory: 'mens-shoes' },\n * 'mens-shoes': { name: \"Men's Shoes\", parentCategory: 'mens' },\n * 'mens': { name: 'Men' },\n * },\n * contentObjectAssignments: {},\n * };\n * categoryResolver('mens-running-shoes', siteManifest);\n * // => { objectType: 'category', aspectType: 'plp', keys: ['mens-running-shoes', 'mens-shoes', 'mens'] }\n * ```\n */\nexport const ContentAssignmentResolvers = new Map<string, ContentAssignmentResolver>([\n [\n 'product',\n (key) => ({\n objectType: 'product',\n keys: [key],\n }),\n ],\n [\n 'category',\n (key, manifest) => {\n const keys = [];\n const visited = new Set<string>();\n\n let currentCategoryId: string | undefined = key;\n\n while (currentCategoryId && !visited.has(currentCategoryId)) {\n visited.add(currentCategoryId);\n keys.push(currentCategoryId);\n currentCategoryId = manifest?.categories[currentCategoryId]?.parentCategory;\n }\n\n return {\n objectType: 'category',\n keys,\n };\n },\n ],\n]);\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { SiteManifest, IdentifierType } from '../types';\nimport { ContentAssignmentResolvers } from './content-assignment-resolvers';\n\n/**\n * Converts a product or category identifier into a page ID by looking up\n * content assignments in the site manifest. For categories, the lookup\n * traverses the category hierarchy from the given category up to the root,\n * returning the first matching assignment.\n *\n * Returns `null` if no content assignment is found for the identifier or if\n * the identifier type has no registered resolver.\n *\n * @param options - The resolution options.\n * @param options.id - The identifier to resolve (product ID, category ID, or page ID).\n * @param options.identifierType - The type of identifier: `'product'`, `'category'`, or `'page'`.\n * @param options.siteManifest - The site manifest containing content assignments and category hierarchy.\n * @returns The resolved page ID, or `null` if no assignment was found.\n *\n * @example\n * ```ts\n * import { resolveDynamicPageId } from '@salesforce/storefront-next-runtime/design/data';\n *\n * const siteManifest = {\n * contentObjectAssignments: {\n * plp: {\n * category: {\n * 'mens-shoes': {\n * lookupMode: 'category-explicit',\n * contentId: 'page-mens-shoes-plp',\n * },\n * },\n * },\n * },\n * categories: {\n * 'mens-running-shoes': { name: 'Running Shoes', parentCategory: 'mens-shoes' },\n * 'mens-shoes': { name: \"Men's Shoes\" },\n * },\n * };\n *\n * // Direct match\n * resolveDynamicPageId({ id: 'mens-shoes', identifierType: 'category', siteManifest });\n * // => 'page-mens-shoes-plp'\n *\n * // Inherited from parent category\n * resolveDynamicPageId({ id: 'mens-running-shoes', identifierType: 'category', siteManifest });\n * // => 'page-mens-shoes-plp' (found via parent traversal)\n *\n * // No assignment found\n * resolveDynamicPageId({ id: 'womens-shoes', identifierType: 'category', siteManifest });\n * // => null\n * ```\n */\nexport function resolveDynamicPageId<TIdentifier extends IdentifierType = IdentifierType>({\n id,\n identifierType,\n siteManifest,\n aspectType,\n}: {\n id: string;\n identifierType: TIdentifier;\n aspectType: string;\n siteManifest?: SiteManifest | null;\n}): string | null {\n const resolvedContentAssignmentLookup = ContentAssignmentResolvers.get(identifierType)?.(id, siteManifest);\n\n if (resolvedContentAssignmentLookup) {\n for (const key of resolvedContentAssignmentLookup.keys) {\n const contentAssignment =\n siteManifest?.contentObjectAssignments?.[aspectType]?.[resolvedContentAssignmentLookup.objectType]?.[\n key\n ];\n\n if (contentAssignment) {\n return contentAssignment.contentId;\n }\n }\n }\n\n return null;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { ContextResolver, PageManifest, QualifierContext, VariationEntry } from '../types';\nimport { validateRule } from '../validate-rule';\n\n/**\n * Selects the appropriate page variation from a manifest by evaluating each\n * variation's visibility rule in order. Returns the first variation whose rule\n * passes, or falls back to the manifest's default variation.\n *\n * The qualifier context is resolved lazily — the `contextResolver` is only\n * called when a variation's `ruleRequiresContext` flag is `true`, and only\n * once (the result is cached for subsequent variations).\n *\n * @param manifest - The page manifest containing all variations.\n * @param options - Resolution options.\n * @param options.contextResolver - Optional async function that returns the shopper's qualifier context. Only called if a variation's rule needs it.\n * @param options.locale - The current locale (e.g. `\"en_US\"`). Used to evaluate locale-based visibility rules.\n * @returns The selected variation entry and resolved context, or `null` if no variation (including default) exists.\n *\n * @example\n * ```ts\n * import { getPageFromManifest } from '@salesforce/storefront-next-runtime/design/data';\n *\n * const manifest = {\n * pageId: 'homepage',\n * context: { campaignQualifiers: [], customerGroups: ['vip-customers'], dataBindings: [] },\n * variationOrder: ['vip-homepage', 'holiday-homepage'],\n * variations: {\n * 'vip-homepage': {\n * ruleRequiresContext: true,\n * pageRequiresContext: false,\n * visibilityRule: { activeLocales: ['en-US'], customerGroups: ['vip-customers'] },\n * page: { id: 'homepage', typeId: 'storePage', regions: [] },\n * regions: {},\n * },\n * 'holiday-homepage': {\n * ruleRequiresContext: false,\n * pageRequiresContext: false,\n * visibilityRule: {\n * activeLocales: ['en-US'],\n * schedule: {\n * start: new Date('2026-12-01').toISOString(),\n * end: new Date('2026-12-31').toISOString(),\n * },\n * },\n * page: { id: 'homepage', typeId: 'storePage', regions: [] },\n * regions: {},\n * },\n * 'default-homepage': {\n * ruleRequiresContext: false,\n * pageRequiresContext: false,\n * page: { id: 'homepage', typeId: 'storePage', regions: [] },\n * regions: {},\n * },\n * },\n * defaultVariation: 'default-homepage',\n * componentInfo: {},\n * };\n *\n * // VIP shopper — matches first variation\n * const result = await getPageFromManifest(manifest, {\n * locale: 'en-US',\n * contextResolver: async () => ({\n * customerGroups: { 'vip-customers': true },\n * campaignQualifiers: {},\n * }),\n * });\n * // result.entry === manifest.variations['vip-homepage']\n *\n * // Non-VIP shopper outside holiday window — falls back to default\n * const fallback = await getPageFromManifest(manifest, {\n * locale: 'en-US',\n * contextResolver: async () => ({\n * customerGroups: {},\n * campaignQualifiers: {},\n * }),\n * });\n * // fallback.entry === manifest.variations['default-homepage']\n * ```\n */\nexport async function getPageFromManifest(\n manifest: PageManifest,\n {\n contextResolver,\n locale,\n }: {\n contextResolver?: ContextResolver;\n locale: string;\n }\n): Promise<{\n entry: VariationEntry;\n context: QualifierContext | null;\n} | null> {\n let context: QualifierContext | null = null;\n let resolvedVariation: VariationEntry | null = null;\n\n for (const variationId of manifest.variationOrder) {\n const variation = manifest.variations[variationId];\n\n if (variation?.ruleRequiresContext && !context) {\n context = (await contextResolver?.(manifest.context)) ?? null;\n }\n\n if (!variation?.visibilityRule || validateRule(variation.visibilityRule, locale, context)) {\n resolvedVariation = variation;\n break;\n }\n }\n\n if (!resolvedVariation) {\n resolvedVariation = manifest.variations[manifest.defaultVariation];\n }\n\n if (!resolvedVariation) {\n return null;\n }\n\n return {\n entry: resolvedVariation,\n context,\n };\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { IdentifierType, ManifestStorage, ContextResolver, QualifierContext } from '../types';\nimport type { ShopperExperience } from '@/scapi-client/types';\nimport { ContentAssignmentResolvers } from '../manifest/content-assignment-resolvers';\nimport { resolveDynamicPageId } from '../manifest/resolve-dynamic-page-id';\nimport { getPageFromManifest } from '../manifest/get-page';\nimport { processPage } from './process-page';\nimport { RequiredError } from '../errors/required';\n\n/**\n * Main entry point for the page resolution pipeline. Orchestrates the full flow:\n *\n * 1. **Resolve dynamic page ID** — For product/category identifiers, looks up\n * the assigned page ID via content assignments in the site manifest.\n * 2. **Fetch page manifest** — Loads all variations for the resolved page.\n * 3. **Select variation** — Evaluates visibility rules to pick the right variation.\n * 4. **Load qualifier context** — Lazily fetches the shopper's context only if needed.\n * 5. **Process page** — Filters out components that fail visibility rules.\n *\n * Returns `null` if the page ID cannot be resolved, the manifest doesn't exist,\n * or no variation is available.\n *\n * @param options - The resolution options.\n * @param options.id - The identifier to resolve (product ID, category ID, or page ID).\n * @param options.identifierType - The type of identifier: `'product'`, `'category'`, or `'page'`.\n * @param options.locale - The locale to resolve the page for (e.g. `\"en-US\"`).\n * @param options.manifestStorage - Storage implementation for fetching manifests.\n * @param options.contextResolver - Optional async function that returns the shopper's qualifier context. Only called if a visibility rule needs it.\n * @param options.aspectType - The aspect type to resolve the page for when the identifier type is `'product'` or `'category'`.\n * @param options.pruneInvisible - When `true` (default), invisible and overflow components are removed. When `false`, they are kept but marked `visible: false` for design/preview mode.\n * @returns The fully resolved and filtered page, or `null`.\n *\n * @example\n * ```ts\n * import { resolvePage } from '@salesforce/storefront-next-runtime/design/data';\n *\n * // Resolve the PDP page for a specific product with an active holiday campaign\n * const page = await resolvePage({\n * id: 'nike-air-max-90',\n * identifierType: 'product',\n * aspectType: 'pdp',\n * locale: 'en-US',\n * manifestStorage: {\n * async getPageManifest(id) {\n * // Fetch from CDN, filesystem, or database\n * return fetchManifest(`/manifests/${id}.json`);\n * },\n * async getSiteManifest() {\n * return fetchManifest('/manifests/site.json');\n * },\n * },\n * contextResolver: async () => ({\n * customerGroups: { 'vip-customers': true },\n * campaignQualifiers: {\n * 'holiday-sale-2026': { 'free-shipping': true },\n * },\n * }),\n * });\n *\n * if (page) {\n * // page.regions contains only components visible to this VIP shopper\n * // during the holiday sale campaign\n * renderPage(page);\n * }\n * ```\n */\nexport async function resolvePage({\n id,\n identifierType,\n aspectType,\n locale,\n manifestStorage,\n contextResolver,\n pruneInvisible = true,\n}: {\n id: string;\n identifierType: IdentifierType;\n aspectType?: string;\n locale: string;\n manifestStorage: ManifestStorage;\n contextResolver?: ContextResolver;\n pruneInvisible?: boolean;\n}): Promise<ShopperExperience.schemas['Page'] | null> {\n let resolvedId: string | null = null;\n\n if (ContentAssignmentResolvers.has(identifierType)) {\n const siteManifest = await manifestStorage.getSiteManifest();\n\n RequiredError.assert(aspectType, `Aspect type is required for identifier type ${identifierType}`, (v) => !v);\n\n resolvedId = resolveDynamicPageId({ id, identifierType, aspectType, siteManifest });\n } else {\n resolvedId = id;\n }\n\n if (!resolvedId) {\n return null;\n }\n\n const pageManifest = await manifestStorage.getPageManifest(resolvedId);\n\n if (!pageManifest) {\n return null;\n }\n\n const pageResults = await getPageFromManifest(pageManifest, {\n contextResolver,\n locale,\n });\n\n if (!pageResults) {\n return null;\n }\n\n let context: QualifierContext | null = null;\n\n if (pageResults.entry.pageRequiresContext) {\n context = pageResults.context ?? (await contextResolver?.(pageManifest.context)) ?? null;\n }\n\n return processPage(pageResults.entry.page, {\n qualifiers: context,\n componentInfo: pageManifest.componentInfo,\n pageInfo: {\n regions: pageResults.entry.regions,\n },\n locale,\n pruneInvisible,\n });\n}\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
1
  import "./types2.js";
2
- import { A as ClientWindowDragExitedEvent, B as ComponentMovedToRegionEvent, C as ClientConfigurationChangedEvent, D as ClientReady, E as ClientPageChangedEvent, F as ComponentDragStartedEvent, G as ErrorEvent, H as ComponentSelectedEvent, I as ComponentFocusedEvent, J as HostToClientConfiguration, K as HostDisconnected, L as ComponentHoveredInEvent, M as ComponentAddedToRegionEvent, N as ComponentDeletedEvent, O as ClientWindowDragDroppedEvent, P as ComponentDeselectedEvent, R as ComponentHoveredOutEvent, S as ClientAcknowledgedEvent, T as ClientInitializedEvent, U as ComponentType, V as ComponentPropertiesChangedEvent, W as DefaultForwardedKeys, X as PageSettingsChangedEvent, Y as MediaChangedEvent, Z as WindowScrollChangedEvent, _ as IsomorphicEventNameMapping, a as ClientEventNameMapping, b as WithEventType, c as EventHandler, d as HostApi, f as HostConfiguration, g as IsomorphicConfiguration, h as IsomorphicApi, i as ClientConfiguration, j as ClientWindowDragMovedEvent, k as ClientWindowDragEnteredEvent, l as EventPayload, m as HostMessage, n as createClientApi, o as ClientMessage, p as HostEventNameMapping, q as HostKeyPressedEvent, r as ClientApi, s as ConfigFactory, t as createHostApi, u as EventTypeName, v as MessageEmitter, w as ClientDisconnectedEvent, x as WithMeta, y as Source, z as ComponentInfo } from "./index.js";
3
- export { ClientAcknowledgedEvent, ClientApi, ClientConfiguration, ClientConfigurationChangedEvent, ClientDisconnectedEvent, ClientEventNameMapping, ClientInitializedEvent, ClientMessage, ClientPageChangedEvent, ClientReady, ClientWindowDragDroppedEvent, ClientWindowDragEnteredEvent, ClientWindowDragExitedEvent, ClientWindowDragMovedEvent, ComponentAddedToRegionEvent, ComponentDeletedEvent, ComponentDeselectedEvent, ComponentDragStartedEvent, ComponentFocusedEvent, ComponentHoveredInEvent, ComponentHoveredOutEvent, ComponentInfo, ComponentMovedToRegionEvent, ComponentPropertiesChangedEvent, ComponentSelectedEvent, ComponentType, ConfigFactory, DefaultForwardedKeys, ErrorEvent, EventHandler, EventPayload, EventTypeName, HostApi, HostConfiguration, HostDisconnected, HostEventNameMapping, HostKeyPressedEvent, HostMessage, HostToClientConfiguration, IsomorphicApi, IsomorphicConfiguration, IsomorphicEventNameMapping, MediaChangedEvent, MessageEmitter, PageSettingsChangedEvent, Source, WindowScrollChangedEvent, WithEventType, WithMeta, createClientApi, createHostApi };
2
+ import { $ as WindowScrollChangedEvent, A as ClientWindowDragExitedEvent, B as ComponentMovedToRegionEvent, C as ClientConfigurationChangedEvent, D as ClientReady, E as ClientPageChangedEvent, F as ComponentDragStartedEvent, G as DefaultForwardedKeys, H as ComponentSelectedEvent, I as ComponentFocusedEvent, J as HostKeyPressedEvent, K as ErrorEvent, L as ComponentHoveredInEvent, M as ComponentAddedToRegionEvent, N as ComponentDeletedEvent, O as ClientWindowDragDroppedEvent, P as ComponentDeselectedEvent, Q as RegionInfo, R as ComponentHoveredOutEvent, S as ClientAcknowledgedEvent, T as ClientInitializedEvent, U as ComponentType, V as ComponentPropertiesChangedEvent, W as ComponentUpdatedEvent, X as MediaChangedEvent, Y as HostToClientConfiguration, Z as PageSettingsChangedEvent, _ as IsomorphicEventNameMapping, a as ClientEventNameMapping, b as WithEventType, c as EventHandler, d as HostApi, f as HostConfiguration, g as IsomorphicConfiguration, h as IsomorphicApi, i as ClientConfiguration, j as ClientWindowDragMovedEvent, k as ClientWindowDragEnteredEvent, l as EventPayload, m as HostMessage, n as createClientApi, o as ClientMessage, p as HostEventNameMapping, q as HostDisconnected, r as ClientApi, s as ConfigFactory, t as createHostApi, u as EventTypeName, v as MessageEmitter, w as ClientDisconnectedEvent, x as WithMeta, y as Source, z as ComponentInfo } from "./index.js";
3
+ export { ClientAcknowledgedEvent, ClientApi, ClientConfiguration, ClientConfigurationChangedEvent, ClientDisconnectedEvent, ClientEventNameMapping, ClientInitializedEvent, ClientMessage, ClientPageChangedEvent, ClientReady, ClientWindowDragDroppedEvent, ClientWindowDragEnteredEvent, ClientWindowDragExitedEvent, ClientWindowDragMovedEvent, ComponentAddedToRegionEvent, ComponentDeletedEvent, ComponentDeselectedEvent, ComponentDragStartedEvent, ComponentFocusedEvent, ComponentHoveredInEvent, ComponentHoveredOutEvent, ComponentInfo, ComponentMovedToRegionEvent, ComponentPropertiesChangedEvent, ComponentSelectedEvent, ComponentType, ComponentUpdatedEvent, ConfigFactory, DefaultForwardedKeys, ErrorEvent, EventHandler, EventPayload, EventTypeName, HostApi, HostConfiguration, HostDisconnected, HostEventNameMapping, HostKeyPressedEvent, HostMessage, HostToClientConfiguration, IsomorphicApi, IsomorphicConfiguration, IsomorphicEventNameMapping, MediaChangedEvent, MessageEmitter, PageSettingsChangedEvent, RegionInfo, Source, WindowScrollChangedEvent, WithEventType, WithMeta, createClientApi, createHostApi };