@salesforce/storefront-next-runtime 0.4.2 → 1.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +9 -3
  2. package/dist/ComponentContext.js +199 -4
  3. package/dist/ComponentContext.js.map +1 -1
  4. package/dist/DesignComponent.js +2 -2
  5. package/dist/DesignRegion.js +2 -2
  6. package/dist/RegionContext.js +9 -0
  7. package/dist/RegionContext.js.map +1 -0
  8. package/dist/component.types.d.ts +1 -1
  9. package/dist/config.d.ts +34 -221
  10. package/dist/config.d.ts.map +1 -1
  11. package/dist/config.js +35 -116
  12. package/dist/config.js.map +1 -1
  13. package/dist/data-store.d.ts +185 -15
  14. package/dist/data-store.d.ts.map +1 -1
  15. package/dist/data-store.js +412 -10
  16. package/dist/data-store.js.map +1 -1
  17. package/dist/defaults.d.ts +106 -0
  18. package/dist/defaults.d.ts.map +1 -0
  19. package/dist/defaults.js +67 -0
  20. package/dist/defaults.js.map +1 -0
  21. package/dist/design-data.d.ts +238 -356
  22. package/dist/design-data.d.ts.map +1 -1
  23. package/dist/design-data.js +459 -30
  24. package/dist/design-data.js.map +1 -1
  25. package/dist/design-mode.d.ts +3 -2
  26. package/dist/design-mode.d.ts.map +1 -1
  27. package/dist/design-react-core.d.ts +5 -15
  28. package/dist/design-react-core.d.ts.map +1 -1
  29. package/dist/design-react-core.js +2 -2
  30. package/dist/design-react.d.ts +2 -2
  31. package/dist/design.d.ts +2 -2
  32. package/dist/events.d.ts +32 -6
  33. package/dist/events.d.ts.map +1 -1
  34. package/dist/i18n-client.d.ts.map +1 -1
  35. package/dist/i18n-client.js.map +1 -1
  36. package/dist/i18n.d.ts +1 -2
  37. package/dist/i18n.d.ts.map +1 -1
  38. package/dist/modeDetection.js +0 -18
  39. package/dist/modeDetection.js.map +1 -1
  40. package/dist/scapi.d.ts +2185 -466
  41. package/dist/scapi.d.ts.map +1 -1
  42. package/dist/scapi.js +1 -1
  43. package/dist/scapi.js.map +1 -1
  44. package/dist/schema.d.ts +17 -15
  45. package/dist/schema.d.ts.map +1 -1
  46. package/dist/security-react.d.ts +34 -0
  47. package/dist/security-react.d.ts.map +1 -0
  48. package/dist/security-react.js +21 -0
  49. package/dist/security-react.js.map +1 -0
  50. package/dist/security.d.ts +61 -0
  51. package/dist/security.d.ts.map +1 -0
  52. package/dist/security.js +304 -0
  53. package/dist/security.js.map +1 -0
  54. package/dist/site-context.d.ts +43 -27
  55. package/dist/site-context.d.ts.map +1 -1
  56. package/dist/site-context.js +2 -2
  57. package/dist/site-context2.js +41 -31
  58. package/dist/site-context2.js.map +1 -1
  59. package/dist/types.d.ts +19 -3
  60. package/dist/types.d.ts.map +1 -1
  61. package/dist/types2.d.ts +89 -63
  62. package/dist/types2.d.ts.map +1 -1
  63. package/dist/types3.d.ts +1 -35
  64. package/dist/types3.d.ts.map +1 -1
  65. package/package.json +15 -20
  66. package/dist/DesignFrame.js +0 -204
  67. package/dist/DesignFrame.js.map +0 -1
  68. package/dist/custom-global-preferences.d.ts +0 -20
  69. package/dist/custom-global-preferences.d.ts.map +0 -1
  70. package/dist/custom-global-preferences.js +0 -31
  71. package/dist/custom-global-preferences.js.map +0 -1
  72. package/dist/custom-site-preferences.d.ts +0 -20
  73. package/dist/custom-site-preferences.d.ts.map +0 -1
  74. package/dist/custom-site-preferences.js +0 -31
  75. package/dist/custom-site-preferences.js.map +0 -1
  76. package/dist/data-store-custom-global-preferences.d.ts +0 -2
  77. package/dist/data-store-custom-global-preferences.js +0 -6
  78. package/dist/data-store-custom-site-preferences.d.ts +0 -2
  79. package/dist/data-store-custom-site-preferences.js +0 -6
  80. package/dist/data-store-gcp-preferences.d.ts +0 -2
  81. package/dist/data-store-gcp-preferences.js +0 -6
  82. package/dist/gcp-preferences.d.ts +0 -52
  83. package/dist/gcp-preferences.d.ts.map +0 -1
  84. package/dist/gcp-preferences.js +0 -64
  85. package/dist/gcp-preferences.js.map +0 -1
  86. package/dist/utils.js +0 -90
  87. package/dist/utils.js.map +0 -1
@@ -1,5 +1,133 @@
1
1
  import { r as ShopperExperience } from "./types2.js";
2
2
 
3
+ //#region src/design/data/page/attribute-resolution.d.ts
4
+
5
+ /**
6
+ * Per-request resolution surface. Storefront-next builds one of these from
7
+ * the request URL + site config; Page Designer preview builds one against
8
+ * the BM origin. Both surfaces inject URL-building utilities so this module
9
+ * stays platform-neutral.
10
+ */
11
+ interface AttributeResolutionContext {
12
+ /**
13
+ * Storefront origin used to absolutize URLs, e.g.
14
+ * `"https://www.shop.example"`. Page Designer preview supplies the BM
15
+ * origin instead.
16
+ */
17
+ host: string;
18
+ /**
19
+ * Builds a static-content URL for a media-file path inside a library.
20
+ * Mirrors ECOM's `MediaFile.getAbsURL()` chain, parameterized by the
21
+ * storefront request rather than a JVM `Request`.
22
+ *
23
+ * The {@code locale} hint is optional — when omitted, the resolver
24
+ * substitutes `"default"` so URLs still resolve.
25
+ */
26
+ resolveMediaUrl: (ref: {
27
+ libraryDomain: string;
28
+ path: string;
29
+ locale?: string;
30
+ }) => string;
31
+ /**
32
+ * Resolves a library-relative path inside markup (`?$staticlink$`).
33
+ * When omitted, falls back to {@link resolveMediaUrl}.
34
+ */
35
+ staticLinkFor?: (ref: {
36
+ libraryDomain: string;
37
+ path: string;
38
+ locale?: string;
39
+ }) => string;
40
+ /**
41
+ * Default library media domain used when rewriting `?$staticlink$`
42
+ * references inside markup attributes. Sourced from
43
+ * {@code manifest.pageLibraryDomain} and threaded through by the
44
+ * caller. Optional — when omitted, `?$staticlink$` placeholders inside
45
+ * markup are left untouched and a one-time warning fires.
46
+ */
47
+ pageLibraryDomain?: string;
48
+ /**
49
+ * Locale hint forwarded to {@link resolveMediaUrl}. Page Designer
50
+ * preview may omit this when the editor session has no locale; the
51
+ * resolver substitutes `"default"` in that case.
52
+ */
53
+ locale?: string;
54
+ /**
55
+ * Optional handler invoked when the resolver encounters a recoverable
56
+ * problem — malformed envelopes, unknown attribute types, depth limits
57
+ * exceeded. Lets the consumer route these into its own logger / metric
58
+ * pipeline instead of the SDK calling `console.warn` directly.
59
+ *
60
+ * The runtime dedupes calls to {@code onWarn} per
61
+ * `(typeId, attrId, attrType)` triple so a misshapen value processed
62
+ * many times only fires the handler once per process.
63
+ *
64
+ * When omitted the runtime stays silent — fits unit tests and Page
65
+ * Designer preview where stderr noise is undesirable. Production
66
+ * callers should supply a handler that forwards to their structured
67
+ * logger.
68
+ */
69
+ onWarn?: (warning: AttributeResolutionWarning) => void;
70
+ }
71
+ /**
72
+ * Payload passed to {@link AttributeResolutionContext.onWarn}. Keep this
73
+ * shape stable — consumers may pattern-match on `kind` to decide log
74
+ * level, attach extra metadata, etc.
75
+ */
76
+ interface AttributeResolutionWarning {
77
+ /**
78
+ * Identifier for the kind of issue, useful for routing or grouping in
79
+ * downstream logging:
80
+ *
81
+ * - `malformed-image` / `malformed-file` / `malformed-cms-record` —
82
+ * the manifest envelope didn't match the expected shape and the
83
+ * value is being passed through unchanged.
84
+ * - `unknown-attribute-type` — the runtime saw an attribute type it
85
+ * doesn't recognize (forward-compat from a newer ECOM).
86
+ * - `cms-record-depth-exceeded` — recursive cms_record nesting hit
87
+ * the resolver's safety limit.
88
+ * - `staticlink-rewrite-skipped` — markup contains `?$staticlink$`
89
+ * placeholders but `ctx.pageLibraryDomain` was not configured, so
90
+ * the placeholder is left in the source. Fires once per process
91
+ * regardless of how many markup attributes hit it (tracked via the
92
+ * {@code typeId}/{@code attrId} fields, both empty strings, in the
93
+ * {@code warnOnce} dedup key).
94
+ */
95
+ kind: 'malformed-image' | 'malformed-file' | 'malformed-cms-record' | 'cms-record-depth-exceeded' | 'unknown-attribute-type' | 'staticlink-rewrite-skipped';
96
+ /** Human-readable message — safe to log directly. */
97
+ message: string;
98
+ /** Component type id the offending attribute belongs to. */
99
+ typeId: string;
100
+ /** Attribute id within the component. */
101
+ attrId: string;
102
+ /** The attribute's declared type, when known. Empty for inner cms_record entries that don't carry a type. */
103
+ attrType: string;
104
+ }
105
+ /**
106
+ * Slim attribute definition used by the resolver to dispatch by type.
107
+ * Mirrors the fields {@code AttributeDefinition} ships in SCAPI's
108
+ * `componentTypes` map. Defined here so the resolver doesn't take a
109
+ * dependency on the larger SCAPI generated types.
110
+ */
111
+ interface AttributeDefinition {
112
+ /** Attribute identifier as authored by the merchant (e.g. `"hero"`). */
113
+ id: string;
114
+ /**
115
+ * Lower-case attribute type identifier matching ECOM's
116
+ * {@code AttributeDefinition.Type#getID}. Examples:
117
+ * `"string"`, `"text"`, `"image"`, `"markup"`, `"file"`, `"cms_record"`.
118
+ */
119
+ type: string;
120
+ /**
121
+ * Default value declared on the attribute definition. Used by component
122
+ * data composition as a fallback when neither the active locale nor the
123
+ * fallback locale has a value for this attribute (see
124
+ * {@code processPage}'s `visitComponent`). The shape is whatever the
125
+ * attribute's `type` would normally hold — a string for `string`/`text`,
126
+ * an envelope for `image`/`file`, etc.
127
+ */
128
+ defaultValue?: unknown;
129
+ }
130
+ //#endregion
3
131
  //#region src/design/data/types.d.ts
4
132
 
5
133
  /**
@@ -19,6 +147,22 @@ interface PageManifest {
19
147
  variations: Record<string, VariationEntry>;
20
148
  /** The variation ID to use when no other variation's rule matches. */
21
149
  defaultVariation: string;
150
+ /**
151
+ * Per-component-type attribute definitions hoisted from the page layout
152
+ * by the manifest builder, deduped by `typeId`. Used by the MRT
153
+ * attribute resolver to dispatch by attribute type without round-tripping
154
+ * to ECOM. Optional — older manifests may not include this field.
155
+ */
156
+ componentTypes?: Record<string, {
157
+ attributeDefinitions: Record<string, AttributeDefinition>;
158
+ }>;
159
+ /**
160
+ * Media-file domain name of the page's owning library. Used by the markup
161
+ * URL rewriter to resolve `?$staticlink$` placeholders at request time.
162
+ * Set by the manifest builder from `library.getMediaFileDomain().getDomainName()`.
163
+ * Optional — older manifests may not include this field.
164
+ */
165
+ pageLibraryDomain?: string;
22
166
  /**
23
167
  * Component visibility rule definitions extracted from the page layout.
24
168
  * Maps each component ID to its array of rule objects and a flag indicating
@@ -27,7 +171,7 @@ interface PageManifest {
27
171
  componentInfo: {
28
172
  [componentId: string]: {
29
173
  /** The visibility rules for this component. */
30
- visibilityRules: VisibilityRuleDef[];
174
+ visibilityRules?: VisibilityRuleDef[];
31
175
  /**
32
176
  * Locale-specific content attributes for this component. Keyed by locale
33
177
  * (e.g. `"en_US"`), each entry contains attribute values that are merged
@@ -36,10 +180,16 @@ interface PageManifest {
36
180
  content?: {
37
181
  [locale: string]: Record<string, unknown>;
38
182
  };
39
- /** Data binding metadata for this component, or `null` if not bound. */
40
- dataBinding?: ComponentDataBinding | null;
183
+ /** Data binding metadata for this component. Omitted when the component has no bindings. */
184
+ dataBinding?: ComponentDataBinding;
185
+ /** Whether this component is a fragment (a reusable, externally-managed content asset). */
186
+ fragment?: boolean;
187
+ /** Custom component data produced by the type's serialize script. Omitted when the component has no custom data. */
188
+ custom?: Record<string, unknown>;
189
+ /** Display name of the component. Omitted when the component has no name. */
190
+ name?: string;
41
191
  /** Region-level configuration (e.g. maxComponents limits), keyed by region ID. */
42
- regions: {
192
+ regions?: {
43
193
  [regionId: string]: RegionInfo;
44
194
  };
45
195
  };
@@ -48,13 +198,13 @@ interface PageManifest {
48
198
  /** Region-level configuration extracted from the page manifest, including type filters and component limits. */
49
199
  interface RegionInfo {
50
200
  /** The name of the region. */
51
- name: string;
201
+ name?: string;
52
202
  /** The component type exclusions for the region. */
53
- componentTypeExclusions: string[] | null;
203
+ componentTypeExclusions?: string[];
54
204
  /** The component type inclusions for the region. */
55
- componentTypeInclusions: string[] | null;
56
- /** Maximum number of visible components to render in this region, or `null` for no limit. */
57
- maxComponents: number | null;
205
+ componentTypeInclusions?: string[];
206
+ /** Maximum number of visible components to render in this region. Omitted when there is no limit. */
207
+ maxComponents?: number;
58
208
  }
59
209
  /**
60
210
  * Site-wide manifest containing content assignments that map product and category
@@ -152,13 +302,52 @@ interface VariationEntry {
152
302
  ruleRequiresContext: boolean;
153
303
  /** The visibility rule that must pass for this variation to be selected. Undefined for the default variation. */
154
304
  visibilityRule?: VisibilityRuleDef;
155
- /** The full page data for this variation. */
305
+ /**
306
+ * The full page data for this variation. Includes the SCAPI-shape page
307
+ * metadata fields (`name`, `aspectTypeId`, `description`, `pageTitle`,
308
+ * `pageDescription`, `pageKeywords`) populated from the default-locale
309
+ * `Page` by the manifest builder. Non-default-locale overrides for
310
+ * these fields live in {@link pageContent}.
311
+ */
156
312
  page: ShopperExperience.schemas['Page'];
313
+ /**
314
+ * Per-locale overlay for the variation's page metadata. When the request
315
+ * locale is not the default and the page metadata differs, the manifest
316
+ * builder writes the **full set** of locale-specific page metadata fields
317
+ * here (full replacement, not diff — see Q6 of the design plan).
318
+ *
319
+ * Each entry is a partial `Page` carrying only the metadata fields that
320
+ * may be locale-overridden (`name`, `aspectTypeId`, `description`,
321
+ * `pageTitle`, `pageDescription`, `pageKeywords`); structural fields
322
+ * (`id`, `typeId`, `regions`) live on {@link page} and are never
323
+ * locale-overlaid.
324
+ *
325
+ * Absent or missing entries fall back to the default-locale page
326
+ * metadata. Optional — older manifests may not include this field.
327
+ */
328
+ pageContent?: {
329
+ [locale: string]: PageMetadataOverlay;
330
+ };
157
331
  /** Page-level region configuration for this variation, keyed by region ID. These are top-level regions owned by the page itself, not nested under a component. */
158
332
  regions: {
159
333
  [regionId: string]: RegionInfo;
160
334
  };
161
335
  }
336
+ /**
337
+ * Subset of {@link ShopperExperience.schemas#Page} fields that the manifest
338
+ * builder may locale-overlay. Stored on
339
+ * {@link VariationEntry.pageContent} keyed by locale ID. Structural fields
340
+ * (`id`, `typeId`, `regions`) are intentionally excluded — they are not
341
+ * locale-scoped.
342
+ */
343
+ interface PageMetadataOverlay {
344
+ name?: string;
345
+ aspectTypeId?: string;
346
+ description?: string;
347
+ pageTitle?: string;
348
+ pageDescription?: string;
349
+ pageKeywords?: string;
350
+ }
162
351
  /**
163
352
  * A visibility rule definition that controls when a page variation or component
164
353
  * is shown. All conditions within a rule use AND logic — every specified
@@ -185,12 +374,6 @@ interface VisibilityRuleDef {
185
374
  * is typically resolved lazily — only fetched when a rule actually needs it.
186
375
  */
187
376
  type QualifierContext = ShopperExperience.schemas['QualifierResolveResponse'];
188
- /**
189
- * A resolved data binding object containing the fields returned by the data
190
- * provider for a specific record. For example, a resolved `content_asset`
191
- * might contain `{ title: "Winter Sale", body: "<div>…</div>" }`.
192
- */
193
- type ResolvedDataBinding = ShopperExperience.schemas['ResolvedDataBinding'];
194
377
  /**
195
378
  * The type of identifier used to look up a page. Determines how the ID is
196
379
  * resolved to a page manifest:
@@ -231,6 +414,24 @@ interface PageProcessorContext {
231
414
  };
232
415
  /** The locale to use when resolving locale-specific component content (e.g. `"en_US"`). */
233
416
  locale: string;
417
+ /** The site's default locale, used as a fallback when the current locale has no content entry (e.g. `"en_US"`). */
418
+ defaultLocale: string;
419
+ /**
420
+ * Per-request resolution surface used by {@link resolveAttributeValues} to
421
+ * convert manifest envelopes into the wire shape SCAPI `getPage` would have
422
+ * returned. The storefront-next middleware builds it once per request and
423
+ * Page Designer preview supplies an editor-mode equivalent.
424
+ */
425
+ attrCtx: AttributeResolutionContext;
426
+ /**
427
+ * Per-component-type attribute definitions hoisted by the manifest builder.
428
+ * Keyed by `typeId`. Optional — when omitted, the resolver falls back to
429
+ * structural detection for the image envelope and passes everything else
430
+ * through.
431
+ */
432
+ componentTypes?: Record<string, {
433
+ attributeDefinitions: Record<string, AttributeDefinition>;
434
+ }>;
234
435
  /**
235
436
  * When `true` (default), invisible components are removed from the tree and
236
437
  * regions are truncated to their `maxComponents` limit. When `false`, invisible
@@ -239,57 +440,6 @@ interface PageProcessorContext {
239
440
  */
240
441
  pruneInvisible?: boolean;
241
442
  }
242
- /**
243
- * Filters a page's components based on their visibility rules and resolves
244
- * data binding expressions in a single traversal. Traverses the page tree
245
- * using the visitor pattern and:
246
- *
247
- * 1. Removes any component whose visibility rules do not pass against the
248
- * shopper's qualifier context.
249
- * 2. Resolves data binding expressions in each surviving component's `data`
250
- * attributes using the resolved data bindings from context resolution.
251
- *
252
- * A component is visible if **any** of its visibility rules pass (OR logic).
253
- * If a component has rules and none of them pass, it is removed. Components
254
- * without rules are always included.
255
- *
256
- * @param page - The page to process.
257
- * @param context - The processing context with qualifier data, visibility rules, and resolved data bindings.
258
- * @returns A new page with invisible components filtered out and data binding expressions resolved.
259
- *
260
- * @example
261
- * ```ts
262
- * import { processPage } from '@salesforce/storefront-next-runtime/design/data';
263
- *
264
- * const page = {
265
- * id: 'homepage',
266
- * typeId: 'storePage',
267
- * regions: [{
268
- * id: 'main',
269
- * components: [
270
- * { id: 'public-banner', typeId: 'commerce_assets.heroBanner', regions: [] },
271
- * { id: 'loyalty-offer', typeId: 'commerce_assets.promoTile', regions: [] },
272
- * ],
273
- * }],
274
- * };
275
- *
276
- * // The "loyalty-offer" component requires the shopper to be in "loyalty-members"
277
- * const componentInfo = {
278
- * 'public-banner': { visibilityRules: [] },
279
- * 'loyalty-offer': {
280
- * visibilityRules: [{ customerGroups: ['loyalty-members'] }],
281
- * },
282
- * };
283
- *
284
- * // Guest shopper — not in any customer group
285
- * const filtered = processPage(page, {
286
- * qualifiers: { customerGroups: {}, campaignQualifiers: {} },
287
- * componentInfo,
288
- * });
289
- * // filtered.regions[0].components has only "public-banner"
290
- * // "loyalty-offer" was removed because the shopper isn't a loyalty member
291
- * ```
292
- */
293
443
  declare function processPage(page: ShopperExperience.schemas['Page'], processorContext: PageProcessorContext): ShopperExperience.schemas['Page'];
294
444
  //#endregion
295
445
  //#region src/design/data/page/transform.d.ts
@@ -576,88 +726,6 @@ declare class RequiredError extends Error {
576
726
  static assert<TValue>(value: TValue, message: string, isEmpty?: (value: TValue) => boolean): asserts value is NonNullable<TValue>;
577
727
  }
578
728
  //#endregion
579
- //#region src/design/data/page/resolve-data-bindings.d.ts
580
- /**
581
- * Parses a binding expression string into its provider type and field name.
582
- * Supports the bare `type.field` format.
583
- *
584
- * @param expression - The expression string to parse.
585
- * @returns The parsed type and field, or `null` if the expression is invalid.
586
- *
587
- * @example
588
- * ```ts
589
- * parseExpression('content_asset.title'); // { type: 'content_asset', field: 'title' }
590
- * parseExpression('invalid'); // null
591
- * ```
592
- */
593
- declare function parseExpression(expression: string): {
594
- type: string;
595
- field: string;
596
- } | null;
597
- /**
598
- * Resolves a single binding expression against the component's data contexts
599
- * and the resolved data bindings from context resolution.
600
- *
601
- * Returns the resolved field value, or an empty string if the expression is
602
- * invalid, the matching context or record is not found, or the field does not
603
- * exist on the resolved record.
604
- *
605
- * @param expression - The expression string (e.g. `"content_asset.body"`).
606
- * @param contexts - The component's data binding contexts.
607
- * @param dataBindings - The resolved data bindings from {@link QualifierContext}.
608
- * @returns The resolved value, or `''` if resolution fails.
609
- */
610
- declare function resolveExpression(expression: string, contexts: DataBindingRequirement[], dataBindings: NonNullable<QualifierContext['dataBindings']>): unknown;
611
- /**
612
- * Resolves data binding expressions for a single component. Replaces attribute
613
- * values in the component's `data` with the resolved values from context
614
- * resolution. Attributes without a matching expression are preserved as-is.
615
- * When an expression cannot be resolved, the attribute value is set to an
616
- * empty string.
617
- *
618
- * Returns the component unchanged if it has no data binding metadata or if
619
- * `dataBindings` is `undefined`.
620
- *
621
- * @param component - The component to resolve data bindings for.
622
- * @param binding - The component's data binding metadata from the page manifest's `componentInfo`, or `null`/`undefined` if not bound.
623
- * @param dataBindings - The resolved data bindings from {@link QualifierContext}, or `undefined` if no bindings were resolved.
624
- * @returns The component with resolved attribute values, or the original component if no bindings apply.
625
- *
626
- * @example
627
- * ```ts
628
- * import { resolveComponentDataBindings } from '@salesforce/storefront-next-runtime/design/data';
629
- *
630
- * const component = {
631
- * id: 'banner',
632
- * typeId: 'commerce_assets.contentBanner',
633
- * data: { heading: 'Fallback Title', body: 'Fallback Body' },
634
- * regions: [],
635
- * };
636
- *
637
- * const binding = {
638
- * expressions: {
639
- * heading: 'content_asset.title',
640
- * body: 'content_asset.body',
641
- * },
642
- * contexts: [{ type: 'content_asset', id: 'winter-sale-uuid' }],
643
- * };
644
- *
645
- * const dataBindings = {
646
- * content_asset: {
647
- * 'winter-sale-uuid': {
648
- * title: 'Winter Sale',
649
- * body: '<div>Free Shipping on all orders!</div>',
650
- * },
651
- * },
652
- * };
653
- *
654
- * const resolved = resolveComponentDataBindings(component, binding, dataBindings);
655
- * // resolved.data.heading === 'Winter Sale'
656
- * // resolved.data.body === '<div>Free Shipping on all orders!</div>'
657
- * ```
658
- */
659
- declare function resolveComponentDataBindings(component: ShopperExperience.schemas['Component'], binding: ComponentDataBinding | null | undefined, dataBindings: QualifierContext['dataBindings']): ShopperExperience.schemas['Component'];
660
- //#endregion
661
729
  //#region src/design/data/page/resolve-page.d.ts
662
730
  /**
663
731
  * Main entry point for the page resolution pipeline. Orchestrates the full flow:
@@ -679,6 +747,7 @@ declare function resolveComponentDataBindings(component: ShopperExperience.schem
679
747
  * @param options.manifestStorage - Storage implementation for fetching manifests.
680
748
  * @param options.contextResolver - Optional async function that returns the shopper's qualifier context. Only called if a visibility rule needs it.
681
749
  * @param options.aspectType - The aspect type to resolve the page for when the identifier type is `'product'` or `'category'`.
750
+ * @param options.categoryId - Optional fallback category ID (or a Promise resolving to one) used only when `identifierType` is `'product'` and the product has no content assignment for the requested aspect type. The promise is awaited lazily — the happy path never pays for it.
682
751
  * @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.
683
752
  * @returns The fully resolved and filtered page, or `null`.
684
753
  *
@@ -720,226 +789,39 @@ declare function resolvePage({
720
789
  id,
721
790
  identifierType,
722
791
  aspectType,
792
+ categoryId,
723
793
  locale,
794
+ defaultLocale,
724
795
  manifestStorage,
725
796
  contextResolver,
797
+ attrCtx,
726
798
  pruneInvisible
727
799
  }: {
728
800
  id: string;
729
801
  identifierType: IdentifierType;
730
802
  aspectType?: string;
803
+ /**
804
+ * Fallback category ID (or a Promise resolving to one) consulted only
805
+ * when `identifierType === 'product'` and the product has no content
806
+ * assignment for the requested aspect type. Awaited lazily — the happy
807
+ * path skips it.
808
+ */
809
+ categoryId?: string | Promise<string | null | undefined> | null;
731
810
  locale: string;
811
+ defaultLocale: string;
732
812
  manifestStorage: ManifestStorage;
733
813
  contextResolver?: ContextResolver;
814
+ /**
815
+ * Per-request resolution surface for attribute envelope rewriting. Built
816
+ * once per request by the storefront-next middleware (or Page Designer
817
+ * preview). The `componentTypes` map travels on the
818
+ * {@link PageManifest} itself and is read off the manifest below before
819
+ * being threaded into {@link processPage}.
820
+ */
821
+ attrCtx: AttributeResolutionContext;
734
822
  pruneInvisible?: boolean;
735
823
  }): Promise<ShopperExperience.schemas['Page'] | null>;
736
824
  //#endregion
737
- //#region src/design/data/manifest/resolve-dynamic-page-id.d.ts
738
- /**
739
- * Converts a product or category identifier into a page ID by looking up
740
- * content assignments in the site manifest. For categories, the lookup
741
- * traverses the category hierarchy from the given category up to the root,
742
- * returning the first matching assignment.
743
- *
744
- * Returns `null` if no content assignment is found for the identifier or if
745
- * the identifier type has no registered resolver.
746
- *
747
- * @param options - The resolution options.
748
- * @param options.id - The identifier to resolve (product ID, category ID, or page ID).
749
- * @param options.identifierType - The type of identifier: `'product'`, `'category'`, or `'page'`.
750
- * @param options.siteManifest - The site manifest containing content assignments and category hierarchy.
751
- * @returns The resolved page ID, or `null` if no assignment was found.
752
- *
753
- * @example
754
- * ```ts
755
- * import { resolveDynamicPageId } from '@salesforce/storefront-next-runtime/design/data';
756
- *
757
- * const siteManifest = {
758
- * contentObjectAssignments: {
759
- * plp: {
760
- * category: {
761
- * 'mens-shoes': {
762
- * lookupMode: 'category-explicit',
763
- * contentId: 'page-mens-shoes-plp',
764
- * },
765
- * },
766
- * },
767
- * },
768
- * categories: {
769
- * 'mens-running-shoes': { name: 'Running Shoes', parentCategory: 'mens-shoes' },
770
- * 'mens-shoes': { name: "Men's Shoes" },
771
- * },
772
- * };
773
- *
774
- * // Direct match
775
- * resolveDynamicPageId({ id: 'mens-shoes', identifierType: 'category', siteManifest });
776
- * // => 'page-mens-shoes-plp'
777
- *
778
- * // Inherited from parent category
779
- * resolveDynamicPageId({ id: 'mens-running-shoes', identifierType: 'category', siteManifest });
780
- * // => 'page-mens-shoes-plp' (found via parent traversal)
781
- *
782
- * // No assignment found
783
- * resolveDynamicPageId({ id: 'womens-shoes', identifierType: 'category', siteManifest });
784
- * // => null
785
- * ```
786
- */
787
- declare function resolveDynamicPageId<TIdentifier extends IdentifierType = IdentifierType>({
788
- id,
789
- identifierType,
790
- siteManifest,
791
- aspectType
792
- }: {
793
- id: string;
794
- identifierType: TIdentifier;
795
- aspectType: string;
796
- siteManifest?: SiteManifest | null;
797
- }): string | null;
798
- //#endregion
799
- //#region src/design/data/manifest/get-page.d.ts
800
- /**
801
- * Selects the appropriate page variation from a manifest by evaluating each
802
- * variation's visibility rule in order. Returns the first variation whose rule
803
- * passes, or falls back to the manifest's default variation.
804
- *
805
- * The qualifier context is resolved lazily — the `contextResolver` is only
806
- * called when a variation's `ruleRequiresContext` flag is `true`, and only
807
- * once (the result is cached for subsequent variations).
808
- *
809
- * @param manifest - The page manifest containing all variations.
810
- * @param options - Resolution options.
811
- * @param options.contextResolver - Optional async function that returns the shopper's qualifier context. Only called if a variation's rule needs it.
812
- * @param options.locale - The current locale (e.g. `"en_US"`). Used to evaluate locale-based visibility rules.
813
- * @returns The selected variation entry and resolved context, or `null` if no variation (including default) exists.
814
- *
815
- * @example
816
- * ```ts
817
- * import { getPageFromManifest } from '@salesforce/storefront-next-runtime/design/data';
818
- *
819
- * const manifest = {
820
- * pageId: 'homepage',
821
- * context: { campaignQualifiers: [], customerGroups: ['vip-customers'], dataBindings: [] },
822
- * variationOrder: ['vip-homepage', 'holiday-homepage'],
823
- * variations: {
824
- * 'vip-homepage': {
825
- * ruleRequiresContext: true,
826
- * pageRequiresContext: false,
827
- * visibilityRule: { activeLocales: ['en-US'], customerGroups: ['vip-customers'] },
828
- * page: { id: 'homepage', typeId: 'storePage', regions: [] },
829
- * regions: {},
830
- * },
831
- * 'holiday-homepage': {
832
- * ruleRequiresContext: false,
833
- * pageRequiresContext: false,
834
- * visibilityRule: {
835
- * activeLocales: ['en-US'],
836
- * schedule: {
837
- * start: new Date('2026-12-01').toISOString(),
838
- * end: new Date('2026-12-31').toISOString(),
839
- * },
840
- * },
841
- * page: { id: 'homepage', typeId: 'storePage', regions: [] },
842
- * regions: {},
843
- * },
844
- * 'default-homepage': {
845
- * ruleRequiresContext: false,
846
- * pageRequiresContext: false,
847
- * page: { id: 'homepage', typeId: 'storePage', regions: [] },
848
- * regions: {},
849
- * },
850
- * },
851
- * defaultVariation: 'default-homepage',
852
- * componentInfo: {},
853
- * };
854
- *
855
- * // VIP shopper — matches first variation
856
- * const result = await getPageFromManifest(manifest, {
857
- * locale: 'en-US',
858
- * contextResolver: async () => ({
859
- * customerGroups: { 'vip-customers': true },
860
- * campaignQualifiers: {},
861
- * }),
862
- * });
863
- * // result.entry === manifest.variations['vip-homepage']
864
- *
865
- * // Non-VIP shopper outside holiday window — falls back to default
866
- * const fallback = await getPageFromManifest(manifest, {
867
- * locale: 'en-US',
868
- * contextResolver: async () => ({
869
- * customerGroups: {},
870
- * campaignQualifiers: {},
871
- * }),
872
- * });
873
- * // fallback.entry === manifest.variations['default-homepage']
874
- * ```
875
- */
876
- declare function getPageFromManifest(manifest: PageManifest, {
877
- contextResolver,
878
- locale
879
- }: {
880
- contextResolver?: ContextResolver;
881
- locale: string;
882
- }): Promise<{
883
- entry: VariationEntry;
884
- context: QualifierContext | null;
885
- } | null>;
886
- //#endregion
887
- //#region src/design/data/manifest/content-assignment-resolvers.d.ts
888
- /**
889
- * The result of resolving an identifier through a content assignment resolver.
890
- * Contains the object type, aspect type, and ordered list of keys to search
891
- * in the site manifest's content assignments.
892
- */
893
- interface ResolvedContentAssignmentLookup {
894
- /** The type of commerce object (e.g. `'product'`, `'category'`). */
895
- objectType: string;
896
- /** Ordered list of object IDs to search in the site manifest's content assignments. */
897
- keys: string[];
898
- }
899
- /**
900
- * A function that converts an identifier key (e.g., a product or category ID)
901
- * into a {@link ResolvedContentAssignmentLookup} describing where to search
902
- * in the site manifest for the assigned page ID.
903
- */
904
- type ContentAssignmentResolver = (key: string, manifest?: SiteManifest | null) => ResolvedContentAssignmentLookup;
905
- /**
906
- * Registry of content assignment resolvers keyed by {@link IdentifierType}.
907
- * Each resolver knows how to convert its identifier type into a set of lookup
908
- * keys for the site manifest.
909
- *
910
- * Built-in resolvers:
911
- * - **`'product'`** — Maps a product ID to a single PDP lookup key.
912
- * - **`'category'`** — Maps a category ID to an ordered list of keys that
913
- * traverses the category hierarchy from child to root, enabling inherited
914
- * page assignments.
915
- *
916
- * The `'page'` identifier type has no resolver — page IDs are used directly.
917
- *
918
- * @example
919
- * ```ts
920
- * import { ContentAssignmentResolvers } from '@salesforce/storefront-next-runtime/design/data';
921
- *
922
- * // Resolve a product identifier for PDP lookup
923
- * const productResolver = ContentAssignmentResolvers.get('product');
924
- * productResolver('nike-air-max-90');
925
- * // => { objectType: 'product', aspectType: 'pdp', keys: ['nike-air-max-90'] }
926
- *
927
- * // Resolve a category identifier — traverses hierarchy to find inherited assignments
928
- * const categoryResolver = ContentAssignmentResolvers.get('category');
929
- * const siteManifest = {
930
- * categories: {
931
- * 'mens-running-shoes': { name: 'Running Shoes', parentCategory: 'mens-shoes' },
932
- * 'mens-shoes': { name: "Men's Shoes", parentCategory: 'mens' },
933
- * 'mens': { name: 'Men' },
934
- * },
935
- * contentObjectAssignments: {},
936
- * };
937
- * categoryResolver('mens-running-shoes', siteManifest);
938
- * // => { objectType: 'category', aspectType: 'plp', keys: ['mens-running-shoes', 'mens-shoes', 'mens'] }
939
- * ```
940
- */
941
- declare const ContentAssignmentResolvers: Map<string, ContentAssignmentResolver>;
942
- //#endregion
943
825
  //#region src/design/data/validate-rule.d.ts
944
826
  /**
945
827
  * Evaluates a visibility rule against a shopper's qualifier context.
@@ -986,5 +868,5 @@ declare const ContentAssignmentResolvers: Map<string, ContentAssignmentResolver>
986
868
  */
987
869
  declare function validateRule(rule: VisibilityRuleDef, locale: string, context?: QualifierContext | null): boolean;
988
870
  //#endregion
989
- export { CampaignQualifier, ComponentDataBinding, ContentAssignmentResolvers, ContextResolver, DataBindingRequirement, IdentifierType, InferNodeFromType, ManifestStorage, PageManifest, PageManifestContext, type PageProcessorContext, type PageVisitor, QualifierContext, RegionInfo, RequiredError, ResolvedDataBinding, SiteManifest, VariationEntry, VisibilityRuleDef, type VisitorContext, VisitorContextType, getPageFromManifest, parseExpression, processPage, resolveComponentDataBindings, resolveDynamicPageId, resolveExpression, resolvePage, transformComponent, transformPage, transformRegion, validateRule };
871
+ export { type AttributeResolutionContext, type AttributeResolutionWarning, type ContextResolver, type IdentifierType, type InferNodeFromType, type ManifestStorage, type PageManifest, type PageProcessorContext, type PageVisitor, type QualifierContext, RequiredError, type SiteManifest, type VisibilityRuleDef, type VisitorContext, type VisitorContextType, processPage, resolvePage, transformComponent, transformPage, transformRegion, validateRule };
990
872
  //# sourceMappingURL=design-data.d.ts.map