@salesforce/storefront-next-runtime 0.4.1 → 1.0.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 (59) hide show
  1. package/README.md +9 -3
  2. package/dist/config.d.ts +33 -221
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +34 -116
  5. package/dist/config.js.map +1 -1
  6. package/dist/data-store.d.ts +185 -15
  7. package/dist/data-store.d.ts.map +1 -1
  8. package/dist/data-store.js +412 -10
  9. package/dist/data-store.js.map +1 -1
  10. package/dist/design-data.d.ts +266 -62
  11. package/dist/design-data.d.ts.map +1 -1
  12. package/dist/design-data.js +399 -14
  13. package/dist/design-data.js.map +1 -1
  14. package/dist/design-mode.d.ts +3 -2
  15. package/dist/design-mode.d.ts.map +1 -1
  16. package/dist/events.d.ts +32 -6
  17. package/dist/events.d.ts.map +1 -1
  18. package/dist/i18n-client.d.ts.map +1 -1
  19. package/dist/i18n-client.js.map +1 -1
  20. package/dist/i18n.d.ts +1 -2
  21. package/dist/i18n.d.ts.map +1 -1
  22. package/dist/modeDetection.js +0 -18
  23. package/dist/modeDetection.js.map +1 -1
  24. package/dist/scapi.d.ts +2185 -466
  25. package/dist/scapi.d.ts.map +1 -1
  26. package/dist/scapi.js +1 -1
  27. package/dist/scapi.js.map +1 -1
  28. package/dist/schema.d.ts +17 -15
  29. package/dist/schema.d.ts.map +1 -1
  30. package/dist/site-context.d.ts +43 -27
  31. package/dist/site-context.d.ts.map +1 -1
  32. package/dist/site-context.js +2 -2
  33. package/dist/site-context2.js +41 -31
  34. package/dist/site-context2.js.map +1 -1
  35. package/dist/types.d.ts +19 -3
  36. package/dist/types.d.ts.map +1 -1
  37. package/dist/types2.d.ts +89 -63
  38. package/dist/types2.d.ts.map +1 -1
  39. package/package.json +2 -20
  40. package/dist/custom-global-preferences.d.ts +0 -20
  41. package/dist/custom-global-preferences.d.ts.map +0 -1
  42. package/dist/custom-global-preferences.js +0 -31
  43. package/dist/custom-global-preferences.js.map +0 -1
  44. package/dist/custom-site-preferences.d.ts +0 -20
  45. package/dist/custom-site-preferences.d.ts.map +0 -1
  46. package/dist/custom-site-preferences.js +0 -31
  47. package/dist/custom-site-preferences.js.map +0 -1
  48. package/dist/data-store-custom-global-preferences.d.ts +0 -2
  49. package/dist/data-store-custom-global-preferences.js +0 -6
  50. package/dist/data-store-custom-site-preferences.d.ts +0 -2
  51. package/dist/data-store-custom-site-preferences.js +0 -6
  52. package/dist/data-store-gcp-preferences.d.ts +0 -2
  53. package/dist/data-store-gcp-preferences.js +0 -6
  54. package/dist/gcp-preferences.d.ts +0 -52
  55. package/dist/gcp-preferences.d.ts.map +0 -1
  56. package/dist/gcp-preferences.js +0 -64
  57. package/dist/gcp-preferences.js.map +0 -1
  58. package/dist/utils.js +0 -90
  59. package/dist/utils.js.map +0 -1
@@ -349,6 +349,24 @@ function transformRegion(region, visitor) {
349
349
  */
350
350
  const BARE_EXPRESSION_PATTERN = /^(\w+)\.(\w+)$/;
351
351
  /**
352
+ * Coerces a string value returned by the data binding API into a boolean or
353
+ * number when the contents represent one. The data provider returns every
354
+ * field as a string, so callers expecting typed values would otherwise receive
355
+ * `"true"` instead of `true` or `"2026"` instead of `2026`.
356
+ *
357
+ * Non-string inputs are returned as-is. Strings that are neither booleans nor
358
+ * finite numbers are returned unchanged.
359
+ */
360
+ function parseFieldValue(value) {
361
+ if (typeof value !== "string") return value;
362
+ if (value === "true") return true;
363
+ if (value === "false") return false;
364
+ if (value.trim() === "") return value;
365
+ const num = Number(value);
366
+ if (Number.isFinite(num)) return num;
367
+ return value;
368
+ }
369
+ /**
352
370
  * Parses a binding expression string into its provider type and field name.
353
371
  * Supports the bare `type.field` format.
354
372
  *
@@ -389,7 +407,7 @@ function resolveExpression(expression, contexts, dataBindings) {
389
407
  if (!context) return "";
390
408
  const record = dataBindings[context.type]?.[context.id];
391
409
  if (!record) return "";
392
- return record[parsed.field] ?? "";
410
+ return parseFieldValue(record[parsed.field] ?? "");
393
411
  }
394
412
  /**
395
413
  * Resolves data binding expressions for a single component. Replaces attribute
@@ -452,6 +470,286 @@ function resolveComponentDataBindings(component, binding, dataBindings) {
452
470
  };
453
471
  }
454
472
 
473
+ //#endregion
474
+ //#region src/design/data/page/markup-url-rewriter.ts
475
+ const STATICLINK_PATTERN = /\?\$staticlink\$/gi;
476
+ const STATICLINK_DELIMITERS_SINGLE = "\":='(>";
477
+ const STATICLINK_DELIMITERS_DOUBLE = [
478
+ "\"[",
479
+ "=[",
480
+ ",[",
481
+ " [",
482
+ " ,",
483
+ ", "
484
+ ];
485
+ let warnedStaticlink = false;
486
+ function rewriteImages(source, ctx) {
487
+ const domain = ctx.pageLibraryDomain;
488
+ if (!domain) {
489
+ if (!warnedStaticlink) {
490
+ warnedStaticlink = true;
491
+ ctx.onWarn?.({
492
+ kind: "staticlink-rewrite-skipped",
493
+ message: "?$staticlink$ rewrite skipped: ctx.pageLibraryDomain is not set",
494
+ typeId: "",
495
+ attrId: "",
496
+ attrType: "markup"
497
+ });
498
+ }
499
+ return source;
500
+ }
501
+ const resolveStaticUrl = ctx.staticLinkFor ?? ctx.resolveMediaUrl;
502
+ let result = "";
503
+ let lastPos = -1;
504
+ STATICLINK_PATTERN.lastIndex = 0;
505
+ let match = STATICLINK_PATTERN.exec(source);
506
+ if (!match) return source;
507
+ while (match) {
508
+ const pos = match.index;
509
+ const newPos = STATICLINK_PATTERN.lastIndex;
510
+ let startPos = pos - 1;
511
+ while (true) {
512
+ if (startPos <= lastPos) break;
513
+ const ch = source.charAt(startPos);
514
+ if (STATICLINK_DELIMITERS_SINGLE.indexOf(ch) !== -1) {
515
+ if (!(ch === "=" && startPos + 1 < source.length && source.charAt(startPos + 1) === ".")) break;
516
+ }
517
+ if (startPos > 0) {
518
+ const doubleChar = source.substring(startPos - 1, startPos + 1);
519
+ if (STATICLINK_DELIMITERS_DOUBLE.includes(doubleChar)) break;
520
+ }
521
+ startPos--;
522
+ }
523
+ const leftStart = lastPos === -1 ? 0 : lastPos;
524
+ result += source.substring(leftStart, startPos + 1);
525
+ const path = source.substring(startPos + 1, pos);
526
+ if (path.trim().length !== 0) {
527
+ let url = resolveStaticUrl({
528
+ libraryDomain: domain,
529
+ path: path.trim(),
530
+ locale: ctx.locale
531
+ });
532
+ if (path.startsWith(" ")) url = ` ${url}`;
533
+ if (path.endsWith(" ")) url += " ";
534
+ result += url;
535
+ }
536
+ lastPos = newPos;
537
+ match = STATICLINK_PATTERN.exec(source);
538
+ }
539
+ const tailStart = lastPos === -1 ? 0 : lastPos;
540
+ result += source.substring(tailStart);
541
+ return result;
542
+ }
543
+ /**
544
+ * Rewrites `?$staticlink$` placeholders in markup to fully-qualified
545
+ * static-content URLs. Pipeline-action placeholders pass through unchanged.
546
+ */
547
+ function rewriteMarkup(source, ctx) {
548
+ if (!source) return "";
549
+ return rewriteImages(source, ctx);
550
+ }
551
+
552
+ //#endregion
553
+ //#region src/design/data/page/attribute-resolution.ts
554
+ /**
555
+ * Module-scoped dedup set for unknown-type / malformed-envelope warnings.
556
+ * Keyed by `${kind}|${typeId}|${attrId}|${attrType}` so two different
557
+ * issues on the same attribute (e.g. malformed-image then later
558
+ * unknown-type) both fire once.
559
+ */
560
+ const warnedKeys = /* @__PURE__ */ new Set();
561
+ /**
562
+ * Routes a structured warning to the consumer's `onWarn` handler at most
563
+ * once per `(kind, typeId, attrId, attrType)` triple. When no handler is
564
+ * configured the runtime stays silent — production callers are expected to
565
+ * supply a handler.
566
+ */
567
+ function warnOnce(ctx, kind, typeId, attrId, attrType, message) {
568
+ if (!ctx.onWarn) return;
569
+ const key = `${kind}|${typeId}|${attrId}|${attrType}`;
570
+ if (warnedKeys.has(key)) return;
571
+ warnedKeys.add(key);
572
+ ctx.onWarn({
573
+ kind,
574
+ message,
575
+ typeId,
576
+ attrId,
577
+ attrType
578
+ });
579
+ }
580
+ /**
581
+ * Returns true when `value` is shaped like an {@link ImageEnvelope}. Used
582
+ * during structural dispatch (when `componentTypes` is unavailable) to
583
+ * recognize image attributes without `attrDef.type`.
584
+ */
585
+ function isImageEnvelope(value) {
586
+ if (!value || typeof value !== "object") return false;
587
+ const media = value.media;
588
+ return media != null && typeof media === "object" && typeof media.libraryDomain === "string" && typeof media.path === "string";
589
+ }
590
+ /**
591
+ * Converts an {@link ImageEnvelope} to the resolved SCAPI shape by stamping
592
+ * the URL. Returns the original value untouched if the envelope is
593
+ * malformed (missing `media.libraryDomain` or `media.path`); a warning is
594
+ * logged once per `(typeId, attrId, attrType)` triple so production logs
595
+ * don't drown.
596
+ */
597
+ function resolveImageAttribute(value, typeId, attrId, attrType, ctx) {
598
+ if (!isImageEnvelope(value)) {
599
+ warnOnce(ctx, "malformed-image", typeId, attrId, attrType, "malformed image envelope, passing through unchanged");
600
+ return value;
601
+ }
602
+ const out = { url: ctx.resolveMediaUrl({
603
+ libraryDomain: value.media.libraryDomain,
604
+ path: value.media.path,
605
+ locale: ctx.locale
606
+ }) };
607
+ if (value.focalPoint) out.focalPoint = value.focalPoint;
608
+ if (value.metaData) out.metaData = value.metaData;
609
+ return out;
610
+ }
611
+ function isFileEnvelope(value) {
612
+ if (!value || typeof value !== "object") return false;
613
+ const candidate = value;
614
+ const media = candidate.media;
615
+ return media != null && typeof media === "object" && typeof media.libraryDomain === "string" && typeof media.path === "string" && !("focalPoint" in candidate || "metaData" in candidate);
616
+ }
617
+ /**
618
+ * Resolves a file envelope to a URL string. Matches SCAPI's
619
+ * `mediaFile.getAbsURL().toString()` — file attributes emit a plain URL
620
+ * string, not an object envelope.
621
+ */
622
+ function resolveFileAttribute(value, typeId, attrId, ctx) {
623
+ if (!isFileEnvelope(value)) {
624
+ warnOnce(ctx, "malformed-file", typeId, attrId, "file", "malformed file envelope, passing through unchanged");
625
+ return value;
626
+ }
627
+ return ctx.resolveMediaUrl({
628
+ libraryDomain: value.media.libraryDomain,
629
+ path: value.media.path,
630
+ locale: ctx.locale
631
+ });
632
+ }
633
+ const MAX_CMS_RECORD_DEPTH = 10;
634
+ function isCmsRecordEnvelope(value) {
635
+ if (!value || typeof value !== "object") return false;
636
+ const candidate = value;
637
+ if (typeof candidate.id !== "string") return false;
638
+ const type = candidate.type;
639
+ if (!type || typeof type !== "object" || typeof type.id !== "string") return false;
640
+ if (!Array.isArray(type.attributeDefinitions)) return false;
641
+ return candidate.attributes != null && typeof candidate.attributes === "object";
642
+ }
643
+ function resolveCmsRecordAttribute(value, typeId, attrId, ctx, depth) {
644
+ if (value == null) return value;
645
+ if (!isCmsRecordEnvelope(value)) {
646
+ warnOnce(ctx, "malformed-cms-record", typeId, attrId, "cms_record", "malformed cms_record envelope, passing through unchanged");
647
+ return value;
648
+ }
649
+ if (depth >= MAX_CMS_RECORD_DEPTH) {
650
+ warnOnce(ctx, "cms-record-depth-exceeded", typeId, attrId, "cms_record", `cms_record nesting depth exceeded (max ${MAX_CMS_RECORD_DEPTH}), passing through unchanged`);
651
+ return value;
652
+ }
653
+ const innerDefs = value.type.attributeDefinitions;
654
+ const resolvedAttrs = resolveCmsRecordInnerAttributes(value.attributes, typeId, innerDefs, ctx, depth + 1);
655
+ return {
656
+ id: value.id,
657
+ type: value.type,
658
+ attributes: resolvedAttrs
659
+ };
660
+ }
661
+ function resolveCmsRecordInnerAttributes(data, typeId, defs, ctx, depth) {
662
+ const out = {};
663
+ const defsById = /* @__PURE__ */ new Map();
664
+ for (const def of defs) defsById.set(def.id, def);
665
+ for (const [attrId, value] of Object.entries(data)) {
666
+ const def = defsById.get(attrId);
667
+ if (!def) {
668
+ out[attrId] = value;
669
+ continue;
670
+ }
671
+ out[attrId] = dispatchCmsRecordInner(value, typeId, attrId, def, ctx, depth);
672
+ }
673
+ return out;
674
+ }
675
+ function dispatchCmsRecordInner(value, typeId, attrId, attrDef, ctx, depth) {
676
+ if (attrDef.type === "cms_record") return resolveCmsRecordAttribute(value, typeId, attrId, ctx, depth);
677
+ return dispatchByType(value, typeId, attrId, attrDef, ctx);
678
+ }
679
+ /**
680
+ * Resolves every attribute on a component's `data` map to the wire shape
681
+ * SCAPI `getPage` would have returned.
682
+ *
683
+ * Dispatch is type-driven when {@code typeAttributeDefinitions} is supplied.
684
+ * Otherwise the resolver inspects each value structurally — it recognizes
685
+ * the image envelope by the presence of `media.libraryDomain` and
686
+ * `media.path` and passes everything else through unchanged.
687
+ *
688
+ * Forward-compatibility (Q9): unknown attribute types pass through. Each
689
+ * `(typeId, attrId, attrType)` triple is logged once per process via a
690
+ * module-scoped dedup set.
691
+ *
692
+ * @param data attribute map to resolve, already
693
+ * locale-merged + data-binding-resolved by
694
+ * {@link processPage}.
695
+ * @param typeId component type identifier, used as part
696
+ * of the dedup key for warnings. Empty
697
+ * string is acceptable for anonymous
698
+ * callers (page-level data).
699
+ * @param typeAttributeDefinitions attribute definitions for {@code typeId}
700
+ * from `manifest.componentTypes`. When
701
+ * omitted, falls back to structural
702
+ * detection of the image envelope.
703
+ * @param ctx per-request resolution surface.
704
+ * @returns a new map with each attribute's value replaced by the resolved
705
+ * wire shape; pass-through for any attribute type the resolver
706
+ * doesn't yet recognize.
707
+ */
708
+ function resolveAttributeValues(data, typeId, typeAttributeDefinitions, ctx) {
709
+ if (!data) return {};
710
+ const out = {};
711
+ if (typeAttributeDefinitions && Object.keys(typeAttributeDefinitions).length > 0) {
712
+ for (const [attrId, value] of Object.entries(data)) {
713
+ const def = typeAttributeDefinitions[attrId];
714
+ if (!def) {
715
+ out[attrId] = value;
716
+ continue;
717
+ }
718
+ out[attrId] = dispatchByType(value, typeId, attrId, def, ctx);
719
+ }
720
+ return out;
721
+ }
722
+ for (const [attrId, value] of Object.entries(data)) if (isImageEnvelope(value)) out[attrId] = resolveImageAttribute(value, typeId, attrId, "image", ctx);
723
+ else out[attrId] = value;
724
+ return out;
725
+ }
726
+ /**
727
+ * Type-driven dispatch. Unknown types fall through with a deduped warning
728
+ * (Q9) — the principle is that a runtime older than ECOM should still
729
+ * produce *something* rather than dropping the value.
730
+ */
731
+ function dispatchByType(value, typeId, attrId, attrDef, ctx) {
732
+ switch (attrDef.type) {
733
+ case "image": return resolveImageAttribute(value, typeId, attrId, attrDef.type, ctx);
734
+ case "markup": return typeof value === "string" ? rewriteMarkup(value, ctx) : value;
735
+ case "file": return resolveFileAttribute(value, typeId, attrId, ctx);
736
+ case "cms_record": return resolveCmsRecordAttribute(value, typeId, attrId, ctx, 0);
737
+ case "string":
738
+ case "text":
739
+ case "url":
740
+ case "boolean":
741
+ case "integer":
742
+ case "enum":
743
+ case "custom":
744
+ case "product":
745
+ case "category":
746
+ case "page": return value;
747
+ default:
748
+ warnOnce(ctx, "unknown-attribute-type", typeId, attrId, attrDef.type, "unknown attribute type, passing through unchanged");
749
+ return value;
750
+ }
751
+ }
752
+
455
753
  //#endregion
456
754
  //#region src/design/data/validate-rule.ts
457
755
  /**
@@ -498,7 +796,7 @@ function resolveComponentDataBindings(component, binding, dataBindings) {
498
796
  * ```
499
797
  */
500
798
  function validateRule(rule, locale, context) {
501
- if (rule.campaignQualifiers) {
799
+ if (rule.campaignQualifiers?.length) {
502
800
  for (const campaignQualifier of rule.campaignQualifiers) if (!context?.campaignQualifiers?.[campaignQualifier.campaignId]?.[campaignQualifier.promotionId]) return false;
503
801
  } else {
504
802
  if (rule.activeLocales && !rule.activeLocales.includes(locale)) return false;
@@ -573,9 +871,49 @@ function validateRule(rule, locale, context) {
573
871
  * // "loyalty-offer" was removed because the shopper isn't a loyalty member
574
872
  * ```
575
873
  */
874
+ /**
875
+ * Builds a component's `data` map by walking each attribute definition and
876
+ * picking the first non-undefined value in priority order:
877
+ *
878
+ * active-locale content → fallback-locale content → attrDef.defaultValue
879
+ *
880
+ * If none of those have a value the attribute is omitted from the result.
881
+ *
882
+ * When no `typeDefs` are supplied, we fall back to the legacy behavior:
883
+ * `{ ...nodeData, ...defaultContent, ...localeContent }`. This keeps
884
+ * already-deployed manifests rendering until the manifest builder starts
885
+ * emitting `componentTypes`.
886
+ */
887
+ function composeComponentData({ nodeData, defaultContent, localeContent, typeDefs }) {
888
+ if (!typeDefs || Object.keys(typeDefs).length === 0) return {
889
+ ...nodeData ?? {},
890
+ ...defaultContent,
891
+ ...localeContent
892
+ };
893
+ const result = {};
894
+ for (const attrId of Object.keys(typeDefs)) {
895
+ const def = typeDefs[attrId];
896
+ if (Object.prototype.hasOwnProperty.call(localeContent, attrId)) result[attrId] = localeContent[attrId];
897
+ else if (Object.prototype.hasOwnProperty.call(defaultContent, attrId)) result[attrId] = defaultContent[attrId];
898
+ else if (def.defaultValue !== void 0) result[attrId] = def.defaultValue;
899
+ }
900
+ return result;
901
+ }
576
902
  function processPage(page, processorContext) {
577
903
  const { pruneInvisible = true } = processorContext;
578
904
  return transformPage(page, {
905
+ visitPage(ctx) {
906
+ const pageNode = ctx.node;
907
+ const result = {
908
+ ...pageNode,
909
+ regions: ctx.visitRegions(pageNode.regions)
910
+ };
911
+ if (pageNode.data !== void 0) {
912
+ const typeDefs = processorContext.componentTypes?.[pageNode.typeId]?.attributeDefinitions;
913
+ result.data = resolveAttributeValues(pageNode.data, pageNode.typeId, typeDefs, processorContext.attrCtx);
914
+ }
915
+ return result;
916
+ },
579
917
  visitRegion(ctx) {
580
918
  let regionInfo;
581
919
  if (ctx.parent?.type === "page") regionInfo = processorContext.pageInfo.regions[ctx.node.id];
@@ -610,23 +948,32 @@ function processPage(page, processorContext) {
610
948
  isVisible = false;
611
949
  }
612
950
  }
613
- const defaultContent = componentInfo?.content?.default ?? {};
951
+ const defaultContent = componentInfo?.content?.[processorContext.defaultLocale] ?? {};
614
952
  const localeContent = componentInfo?.content?.[processorContext.locale] ?? {};
615
- const content = {
616
- ...defaultContent,
617
- ...localeContent
618
- };
619
953
  const isLocalized = Boolean(componentInfo?.content?.[processorContext.locale]);
954
+ const typeDefs = processorContext.componentTypes?.[ctx.node.typeId]?.attributeDefinitions;
955
+ const composedData = composeComponentData({
956
+ nodeData: ctx.node.data,
957
+ defaultContent,
958
+ localeContent,
959
+ typeDefs
960
+ });
961
+ const name = componentInfo?.name ?? ctx.node.name;
962
+ const fragment = componentInfo?.fragment ?? ctx.node.fragment ?? false;
620
963
  let node = {
621
964
  ...ctx.node,
965
+ name,
966
+ fragment,
622
967
  localized: isLocalized,
623
968
  visible: isVisible,
624
- data: {
625
- ...ctx.node.data,
626
- ...content
627
- }
969
+ data: composedData
628
970
  };
629
971
  node = resolveComponentDataBindings(node, componentInfo?.dataBinding, processorContext.qualifiers?.dataBindings);
972
+ const resolvedData = resolveAttributeValues(node.data, node.typeId, typeDefs, processorContext.attrCtx);
973
+ node = {
974
+ ...node,
975
+ data: resolvedData
976
+ };
630
977
  return {
631
978
  ...node,
632
979
  regions: ctx.visitRegions(ctx.node.regions)
@@ -878,6 +1225,36 @@ async function getPageFromManifest(manifest, { contextResolver, locale }) {
878
1225
  //#endregion
879
1226
  //#region src/design/data/page/resolve-page.ts
880
1227
  /**
1228
+ * Page metadata fields the manifest builder may locale-overlay. Used by
1229
+ * {@link applyPageMetadataOverlay} to know which keys to copy from the
1230
+ * overlay onto the resolved page; structural fields like `id`, `typeId`,
1231
+ * and `regions` are intentionally excluded.
1232
+ */
1233
+ const PAGE_METADATA_OVERLAY_KEYS = [
1234
+ "name",
1235
+ "aspectTypeId",
1236
+ "description",
1237
+ "pageTitle",
1238
+ "pageDescription",
1239
+ "pageKeywords"
1240
+ ];
1241
+ /**
1242
+ * Applies a per-locale page metadata overlay to the variation's default-locale
1243
+ * page. The overlay is a **full replacement** for the listed metadata fields
1244
+ * — when a key is present in the overlay it wins; when absent we fall through
1245
+ * to the default-locale value (Q6 of the design plan).
1246
+ *
1247
+ * Returns a shallow copy of the page with overlaid fields applied. Structural
1248
+ * fields (`id`, `typeId`, `regions`, `data`) are never touched.
1249
+ */
1250
+ function applyPageMetadataOverlay(variation, locale) {
1251
+ const overlay = variation.pageContent?.[locale];
1252
+ if (!overlay) return variation.page;
1253
+ const out = { ...variation.page };
1254
+ for (const key of PAGE_METADATA_OVERLAY_KEYS) if (overlay[key] !== void 0) out[key] = overlay[key];
1255
+ return out;
1256
+ }
1257
+ /**
881
1258
  * Main entry point for the page resolution pipeline. Orchestrates the full flow:
882
1259
  *
883
1260
  * 1. **Resolve dynamic page ID** — For product/category identifiers, looks up
@@ -934,7 +1311,7 @@ async function getPageFromManifest(manifest, { contextResolver, locale }) {
934
1311
  * }
935
1312
  * ```
936
1313
  */
937
- async function resolvePage({ id, identifierType, aspectType, locale, manifestStorage, contextResolver, pruneInvisible = true }) {
1314
+ async function resolvePage({ id, identifierType, aspectType, locale, defaultLocale, manifestStorage, contextResolver, attrCtx, pruneInvisible = true }) {
938
1315
  let resolvedId = null;
939
1316
  if (ContentAssignmentResolvers.has(identifierType)) {
940
1317
  const siteManifest = await manifestStorage.getSiteManifest();
@@ -956,15 +1333,23 @@ async function resolvePage({ id, identifierType, aspectType, locale, manifestSto
956
1333
  if (!pageResults) return null;
957
1334
  let context = null;
958
1335
  if (pageResults.entry.pageRequiresContext) context = pageResults.context ?? await contextResolver?.(pageManifest.context) ?? null;
959
- return processPage(pageResults.entry.page, {
1336
+ const localizedPage = applyPageMetadataOverlay(pageResults.entry, locale);
1337
+ const resolvedAttrCtx = pageManifest.pageLibraryDomain && !attrCtx.pageLibraryDomain ? {
1338
+ ...attrCtx,
1339
+ pageLibraryDomain: pageManifest.pageLibraryDomain
1340
+ } : attrCtx;
1341
+ return processPage(localizedPage, {
960
1342
  qualifiers: context,
961
1343
  componentInfo: pageManifest.componentInfo,
962
1344
  pageInfo: { regions: pageResults.entry.regions },
963
1345
  locale,
1346
+ defaultLocale,
1347
+ attrCtx: resolvedAttrCtx,
1348
+ componentTypes: pageManifest.componentTypes,
964
1349
  pruneInvisible
965
1350
  });
966
1351
  }
967
1352
 
968
1353
  //#endregion
969
- export { ContentAssignmentResolvers, RequiredError, getPageFromManifest, parseExpression, processPage, resolveComponentDataBindings, resolveDynamicPageId, resolveExpression, resolvePage, transformComponent, transformPage, transformRegion, validateRule };
1354
+ export { ContentAssignmentResolvers, RequiredError, getPageFromManifest, parseExpression, processPage, resolveAttributeValues, resolveComponentDataBindings, resolveDynamicPageId, resolveExpression, resolvePage, rewriteMarkup, transformComponent, transformPage, transformRegion, validateRule };
970
1355
  //# sourceMappingURL=design-data.js.map