@contentful/experiences-sdk-core 0.12.4 → 0.12.6

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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## 0.12.6 (2026-10-21)
2
+
3
+ This was a version bump only for core to align it with other projects, there were no code changes.
4
+
5
+ ## 0.12.5 (2026-10-18)
6
+
7
+ ### 🚀 Features
8
+
9
+ - attribute Fragments to root Experience variants [EXA-2560] ([#237](https://github.com/contentful/experiences/pull/237))
10
+
1
11
  ## 0.12.4 (2026-10-16)
2
12
 
3
13
  This was a version bump only for core to align it with other projects, there were no code changes.
@@ -78,21 +78,20 @@ function occurrenceKey(row, listPath, runStart) {
78
78
  }
79
79
  function readVariant(ctx, scope) {
80
80
  const refs = scope.layer.record.variants;
81
- if (!Array.isArray(refs) || refs.length === 0) return {};
81
+ if (!Array.isArray(refs) || refs.length === 0) return { variantId: "default" };
82
82
  if (refs.length > 1) {
83
83
  ctx.log.log(`layer "${scope.entityId}" lists ${refs.length} variants; using the first`);
84
84
  }
85
85
  const variant = rowAt(ctx.variants, refs[0]);
86
86
  const optimizationId = asString(variant?.optimizationId);
87
- const variantId = asString(variant?.variantId);
87
+ const variantId = asString(variant?.variantId) ?? "default";
88
88
  const variantIndex = variant?.variantIndex;
89
- const fields = {};
89
+ const fields = { variantId };
90
90
  if (optimizationId !== void 0) fields.optimizationId = optimizationId;
91
- if (variantId !== void 0) fields.variantId = variantId;
92
91
  if (typeof variantIndex === "number") fields.variantIndex = variantIndex;
93
92
  return fields;
94
93
  }
95
- function buildScope(ctx, scope, key, parentExperienceId) {
94
+ function buildScope(ctx, scope, key, parentExperience) {
96
95
  const attribution = {
97
96
  key,
98
97
  entityId: scope.entityId,
@@ -102,8 +101,11 @@ function buildScope(ctx, scope, key, parentExperienceId) {
102
101
  if (scope.entityKindId !== void 0) {
103
102
  attribution.entityKindId = scope.entityKindId;
104
103
  }
105
- if (scope.kind === "Fragment" && parentExperienceId !== void 0) {
106
- attribution.parentExperienceId = parentExperienceId;
104
+ if (scope.kind === "Fragment" && parentExperience !== void 0) {
105
+ attribution.parentExperienceId = parentExperience.entityId;
106
+ if (parentExperience.variantId !== void 0) {
107
+ attribution.parentVariantId = parentExperience.variantId;
108
+ }
107
109
  }
108
110
  return attribution;
109
111
  }
@@ -180,7 +182,7 @@ function attributeNode(ctx, node, parentKeys, previousKeys, listPath, index) {
180
182
  if (scopes.length === 0) return void 0;
181
183
  const keys = /* @__PURE__ */ new Map();
182
184
  const attribution = { scopes: [], roots: [] };
183
- let parentExperienceId;
185
+ let parentExperience;
184
186
  let nearest;
185
187
  const previousScopes = previousKeys.entries();
186
188
  let sameOccurrencePrefix = true;
@@ -191,10 +193,10 @@ function attributeNode(ctx, node, parentKeys, previousKeys, listPath, index) {
191
193
  const key = parentKeys.get(scope.row) ?? (sameOccurrencePrefix ? previous?.[1] : void 0) ?? occurrenceKey(scope.row, listPath, index);
192
194
  sameOccurrencePrefix &&= previous?.[1] === key;
193
195
  keys.set(scope.row, key);
194
- nearest = ctx.scopes[key] ??= buildScope(ctx, scope, key, parentExperienceId);
196
+ nearest = ctx.scopes[key] ??= buildScope(ctx, scope, key, parentExperience);
195
197
  attribution.scopes.push(nearest);
196
198
  if (isRoot) attribution.roots.push(nearest);
197
- if (scope.kind === "Experience") parentExperienceId = scope.entityId;
199
+ if (scope.kind === "Experience" && parentExperience === void 0) parentExperience = nearest;
198
200
  }
199
201
  const entryIds = readNodeEntries(ctx, entry);
200
202
  if (entryIds.length > 0 && nearest !== void 0) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/attribution.ts"],"sourcesContent":["import type { DebugLogger } from './debug-logger.js';\nimport type {\n ExperienceSourceMap,\n NodeAttribution,\n PlanAttribution,\n PortableRenderNode,\n ScopeAttribution,\n} from './types.js';\n\nconst SUPPORTED_VERSION = 1;\n\ntype EntityKind = ScopeAttribution['entityKind'];\ntype VariantFields = Pick<ScopeAttribution, 'optimizationId' | 'variantId' | 'variantIndex'>;\ntype Rec = Record<string, unknown>;\ntype PathSegment = string | number;\n\nconst DEFINING_LAYER_KIND: Record<EntityKind, string> = {\n Experience: 'ExperienceTemplate',\n Fragment: 'Component',\n};\n\nfunction asRecord(value: unknown): Rec | undefined {\n return typeof value === 'object' && value !== null ? (value as Rec) : undefined;\n}\n\nfunction asIndex(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isInteger(value) && value >= 0 ? value : undefined;\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nfunction rowAt(rows: unknown[], ref: unknown): Rec | undefined {\n const index = asIndex(ref);\n return index === undefined ? undefined : asRecord(rows[index]);\n}\n\nfunction isPath(value: unknown): value is PathSegment[] {\n return Array.isArray(value) && value.every((s) => typeof s === 'string' || Number.isInteger(s));\n}\n\ninterface Layer {\n record: Rec;\n kind: string | undefined;\n id: string | undefined;\n}\n\nfunction parseLayer(value: unknown): Layer | undefined {\n const record = asRecord(value);\n if (record === undefined) return undefined;\n return { record, kind: asString(record.kind), id: asString(record.id) };\n}\n\nfunction scopeKind(layer: Layer): EntityKind | undefined {\n if (layer.kind === 'Experience') return 'Experience';\n if (layer.kind === 'ExperienceFragment') return 'Fragment';\n return undefined;\n}\n\ninterface Context {\n nodes: Rec;\n layers: (Layer | undefined)[];\n variants: unknown[];\n entries: unknown[];\n dataAssemblies: unknown[];\n log: DebugLogger;\n scopes: Record<string, ScopeAttribution>;\n scopeEntries: Map<ScopeAttribution, Set<string>>;\n bindingEntries: Map<string, readonly string[]>;\n}\n\nfunction createContext(sourceMap: ExperienceSourceMap, log: DebugLogger): Context | undefined {\n const map = asRecord(sourceMap);\n if (map?.version !== SUPPORTED_VERSION) {\n log.log(`ignoring source map: unsupported version ${String(map?.version)}`);\n return undefined;\n }\n const nodes = asRecord(map.nodes);\n if (!Array.isArray(map.layers) || !Array.isArray(map.variants) || nodes === undefined) {\n log.log('ignoring source map: missing layers, variants or nodes');\n return undefined;\n }\n return {\n nodes,\n layers: map.layers.map(parseLayer),\n variants: map.variants,\n entries: Array.isArray(map.entries) ? map.entries : [],\n dataAssemblies: Array.isArray(map.dataAssemblies) ? map.dataAssemblies : [],\n log,\n scopes: {},\n scopeEntries: new Map(),\n bindingEntries: new Map(),\n };\n}\n\ninterface ChainScope {\n row: number;\n layer: Layer;\n entityId: string;\n kind: EntityKind;\n entityKindId: string | undefined;\n}\n\nfunction reportableScopes(ctx: Context, chain: number[]): ChainScope[] {\n const outerFirst = [...chain].reverse();\n const scopes: ChainScope[] = [];\n\n for (const [position, row] of outerFirst.entries()) {\n const layer = ctx.layers[row];\n const kind = layer === undefined ? undefined : scopeKind(layer);\n if (layer?.id === undefined || kind === undefined) continue;\n if (scopes.some((scope) => scope.row === row)) continue;\n\n const innerRow = outerFirst[position + 1];\n const innerLayer = innerRow === undefined ? undefined : ctx.layers[innerRow];\n const definesThisScope = innerLayer?.kind === DEFINING_LAYER_KIND[kind];\n\n scopes.push({\n row,\n layer,\n entityId: layer.id,\n kind,\n entityKindId: definesThisScope ? innerLayer?.id : undefined,\n });\n }\n return scopes;\n}\n\nfunction occurrenceKey(row: number, listPath: string, runStart: number): string {\n return `s${row}@${listPath}#${runStart}`;\n}\n\nfunction readVariant(ctx: Context, scope: ChainScope): VariantFields {\n const refs = scope.layer.record.variants;\n if (!Array.isArray(refs) || refs.length === 0) return {};\n if (refs.length > 1) {\n ctx.log.log(`layer \"${scope.entityId}\" lists ${refs.length} variants; using the first`);\n }\n\n const variant = rowAt(ctx.variants, refs[0]);\n const optimizationId = asString(variant?.optimizationId);\n const variantId = asString(variant?.variantId);\n const variantIndex = variant?.variantIndex;\n\n const fields: VariantFields = {};\n if (optimizationId !== undefined) fields.optimizationId = optimizationId;\n if (variantId !== undefined) fields.variantId = variantId;\n if (typeof variantIndex === 'number') fields.variantIndex = variantIndex;\n return fields;\n}\n\nfunction buildScope(\n ctx: Context,\n scope: ChainScope,\n key: string,\n parentExperienceId: string | undefined\n): ScopeAttribution {\n const attribution: ScopeAttribution = {\n key,\n entityId: scope.entityId,\n entityKind: scope.kind,\n ...readVariant(ctx, scope),\n };\n if (scope.entityKindId !== undefined) {\n attribution.entityKindId = scope.entityKindId;\n }\n if (scope.kind === 'Fragment' && parentExperienceId !== undefined) {\n attribution.parentExperienceId = parentExperienceId;\n }\n return attribution;\n}\n\nfunction entriesBoundAt(ctx: Context, assemblyRow: number, path: PathSegment[]): readonly string[] {\n const cacheKey = `${assemblyRow}|${JSON.stringify(path)}`;\n const cached = ctx.bindingEntries.get(cacheKey);\n if (cached !== undefined) return cached;\n\n const found = new Set<string>();\n const visited = new Set<string>();\n\n const followAssembly = (row: number, path: PathSegment[]): void => {\n const visitKey = `${row}|${JSON.stringify(path)}`;\n const assembly = asRecord(ctx.dataAssemblies[row]);\n if (assembly === undefined || visited.has(visitKey)) return;\n visited.add(visitKey);\n walk(assembly.return, path);\n };\n\n const walkLeaf = (leaf: Rec, path: PathSegment[]): void => {\n if (leaf.type === 'entry') {\n const entryId = asString(rowAt(ctx.entries, leaf.entry)?.id);\n if (entryId !== undefined) found.add(entryId);\n } else if (leaf.type === 'dataAssembly') {\n const row = asIndex(leaf.dataAssembly);\n const leafPath = isPath(leaf.path) ? leaf.path : [];\n if (row !== undefined) followAssembly(row, [...leafPath, ...path]);\n }\n };\n\n const walk = (value: unknown, path: PathSegment[]): void => {\n const [head, ...rest] = path;\n\n if (Array.isArray(value)) {\n if (head === undefined) value.forEach((child) => walk(child, []));\n else if (typeof head === 'number') walk(value[head], rest);\n return;\n }\n\n const record = asRecord(value);\n if (record === undefined) return;\n if (typeof record.type === 'string') walkLeaf(record, path);\n else if (head === undefined) Object.values(record).forEach((child) => walk(child, []));\n else walk(record[String(head)], rest);\n };\n\n followAssembly(assemblyRow, path);\n const ids = [...found];\n ctx.bindingEntries.set(cacheKey, ids);\n return ids;\n}\n\nfunction readNodeEntries(ctx: Context, entry: Rec): string[] {\n const properties: unknown[] = Array.isArray(entry.contentProperties)\n ? entry.contentProperties\n : [];\n const ids = new Set<string>();\n\n for (const property of properties) {\n const item = asRecord(property);\n const assemblyRow = asIndex(item?.dataAssembly);\n const bindings = asRecord(item?.bindings);\n if (item?.type !== 'dataAssembly' || assemblyRow === undefined || bindings === undefined) {\n continue;\n }\n for (const path of Object.values(bindings).filter(isPath)) {\n for (const id of entriesBoundAt(ctx, assemblyRow, path)) ids.add(id);\n }\n }\n return [...ids];\n}\n\nfunction addScopeEntries(ctx: Context, scope: ScopeAttribution, entryIds: string[]): void {\n const owned = ctx.scopeEntries.get(scope) ?? new Set<string>();\n for (const id of entryIds) owned.add(id);\n ctx.scopeEntries.set(scope, owned);\n}\n\nfunction readChain(entry: Rec): number[] {\n const { layers } = entry;\n return Array.isArray(layers) && layers.every(Number.isInteger) ? (layers as number[]) : [];\n}\n\nfunction attributeNode(\n ctx: Context,\n node: PortableRenderNode,\n parentKeys: Map<number, string>,\n previousKeys: Map<number, string>,\n listPath: string,\n index: number\n): Map<number, string> | undefined {\n const entry = node.nodeId === undefined ? undefined : asRecord(ctx.nodes[node.nodeId]);\n if (entry === undefined) return undefined;\n const scopes = reportableScopes(ctx, readChain(entry));\n if (scopes.length === 0) return undefined;\n\n const keys = new Map<number, string>();\n const attribution: NodeAttribution = { scopes: [], roots: [] };\n let parentExperienceId: string | undefined;\n let nearest: ScopeAttribution | undefined;\n\n // A sibling's key is only reusable while every enclosing scope matches as well.\n const previousScopes = previousKeys.entries();\n let sameOccurrencePrefix = true;\n\n for (const scope of scopes) {\n const previous = previousScopes.next().value;\n sameOccurrencePrefix &&= previous?.[0] === scope.row;\n\n const isRoot = !parentKeys.has(scope.row);\n const key: string =\n parentKeys.get(scope.row) ??\n (sameOccurrencePrefix ? previous?.[1] : undefined) ??\n occurrenceKey(scope.row, listPath, index);\n sameOccurrencePrefix &&= previous?.[1] === key;\n keys.set(scope.row, key);\n\n nearest = ctx.scopes[key] ??= buildScope(ctx, scope, key, parentExperienceId);\n attribution.scopes.push(nearest);\n if (isRoot) attribution.roots.push(nearest);\n if (scope.kind === 'Experience') parentExperienceId = scope.entityId;\n }\n\n const entryIds = readNodeEntries(ctx, entry);\n if (entryIds.length > 0 && nearest !== undefined) {\n attribution.entryIds = entryIds;\n addScopeEntries(ctx, nearest, entryIds);\n }\n node.attribution = attribution;\n return keys;\n}\n\nfunction assignSiblings(\n ctx: Context,\n siblings: PortableRenderNode[],\n parentKeys: Map<number, string>,\n listPath = ''\n): void {\n let previousKeys = new Map<number, string>();\n for (const [index, node] of siblings.entries()) {\n const ownKeys = attributeNode(ctx, node, parentKeys, previousKeys, listPath, index);\n previousKeys = ownKeys ?? new Map();\n\n for (const [slotName, children] of Object.entries(node.slots)) {\n assignSiblings(ctx, children, ownKeys ?? parentKeys, `${listPath}/${index}.${slotName}`);\n }\n }\n}\n\nexport function resolveAttribution(\n sourceMap: ExperienceSourceMap | undefined,\n nodes: PortableRenderNode[],\n log: DebugLogger\n): PlanAttribution | undefined {\n if (sourceMap === undefined) return undefined;\n const ctx = createContext(sourceMap, log);\n if (ctx === undefined) return undefined;\n\n assignSiblings(ctx, nodes, new Map());\n for (const [scope, ids] of ctx.scopeEntries) scope.entryIds = [...ids];\n\n const scopes = Object.values(ctx.scopes);\n for (const scope of scopes) Object.freeze(scope);\n return scopes.length > 0 ? { scopes: ctx.scopes } : undefined;\n}\n"],"mappings":"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","names":["path"]}
1
+ {"version":3,"sources":["../src/attribution.ts"],"sourcesContent":["import type { DebugLogger } from './debug-logger.js';\nimport type {\n ExperienceSourceMap,\n NodeAttribution,\n PlanAttribution,\n PortableRenderNode,\n ScopeAttribution,\n} from './types.js';\n\nconst SUPPORTED_VERSION = 1;\n\ntype EntityKind = ScopeAttribution['entityKind'];\ntype VariantFields = Pick<ScopeAttribution, 'optimizationId' | 'variantId' | 'variantIndex'>;\ntype Rec = Record<string, unknown>;\ntype PathSegment = string | number;\n\nconst DEFINING_LAYER_KIND: Record<EntityKind, string> = {\n Experience: 'ExperienceTemplate',\n Fragment: 'Component',\n};\n\nfunction asRecord(value: unknown): Rec | undefined {\n return typeof value === 'object' && value !== null ? (value as Rec) : undefined;\n}\n\nfunction asIndex(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isInteger(value) && value >= 0 ? value : undefined;\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nfunction rowAt(rows: unknown[], ref: unknown): Rec | undefined {\n const index = asIndex(ref);\n return index === undefined ? undefined : asRecord(rows[index]);\n}\n\nfunction isPath(value: unknown): value is PathSegment[] {\n return Array.isArray(value) && value.every((s) => typeof s === 'string' || Number.isInteger(s));\n}\n\ninterface Layer {\n record: Rec;\n kind: string | undefined;\n id: string | undefined;\n}\n\nfunction parseLayer(value: unknown): Layer | undefined {\n const record = asRecord(value);\n if (record === undefined) return undefined;\n return { record, kind: asString(record.kind), id: asString(record.id) };\n}\n\nfunction scopeKind(layer: Layer): EntityKind | undefined {\n if (layer.kind === 'Experience') return 'Experience';\n if (layer.kind === 'ExperienceFragment') return 'Fragment';\n return undefined;\n}\n\ninterface Context {\n nodes: Rec;\n layers: (Layer | undefined)[];\n variants: unknown[];\n entries: unknown[];\n dataAssemblies: unknown[];\n log: DebugLogger;\n scopes: Record<string, ScopeAttribution>;\n scopeEntries: Map<ScopeAttribution, Set<string>>;\n bindingEntries: Map<string, readonly string[]>;\n}\n\nfunction createContext(sourceMap: ExperienceSourceMap, log: DebugLogger): Context | undefined {\n const map = asRecord(sourceMap);\n if (map?.version !== SUPPORTED_VERSION) {\n log.log(`ignoring source map: unsupported version ${String(map?.version)}`);\n return undefined;\n }\n const nodes = asRecord(map.nodes);\n if (!Array.isArray(map.layers) || !Array.isArray(map.variants) || nodes === undefined) {\n log.log('ignoring source map: missing layers, variants or nodes');\n return undefined;\n }\n return {\n nodes,\n layers: map.layers.map(parseLayer),\n variants: map.variants,\n entries: Array.isArray(map.entries) ? map.entries : [],\n dataAssemblies: Array.isArray(map.dataAssemblies) ? map.dataAssemblies : [],\n log,\n scopes: {},\n scopeEntries: new Map(),\n bindingEntries: new Map(),\n };\n}\n\ninterface ChainScope {\n row: number;\n layer: Layer;\n entityId: string;\n kind: EntityKind;\n entityKindId: string | undefined;\n}\n\nfunction reportableScopes(ctx: Context, chain: number[]): ChainScope[] {\n const outerFirst = [...chain].reverse();\n const scopes: ChainScope[] = [];\n\n for (const [position, row] of outerFirst.entries()) {\n const layer = ctx.layers[row];\n const kind = layer === undefined ? undefined : scopeKind(layer);\n if (layer?.id === undefined || kind === undefined) continue;\n if (scopes.some((scope) => scope.row === row)) continue;\n\n const innerRow = outerFirst[position + 1];\n const innerLayer = innerRow === undefined ? undefined : ctx.layers[innerRow];\n const definesThisScope = innerLayer?.kind === DEFINING_LAYER_KIND[kind];\n\n scopes.push({\n row,\n layer,\n entityId: layer.id,\n kind,\n entityKindId: definesThisScope ? innerLayer?.id : undefined,\n });\n }\n return scopes;\n}\n\nfunction occurrenceKey(row: number, listPath: string, runStart: number): string {\n return `s${row}@${listPath}#${runStart}`;\n}\n\nfunction readVariant(ctx: Context, scope: ChainScope): VariantFields {\n const refs = scope.layer.record.variants;\n if (!Array.isArray(refs) || refs.length === 0) return { variantId: 'default' };\n if (refs.length > 1) {\n ctx.log.log(`layer \"${scope.entityId}\" lists ${refs.length} variants; using the first`);\n }\n\n const variant = rowAt(ctx.variants, refs[0]);\n const optimizationId = asString(variant?.optimizationId);\n const variantId = asString(variant?.variantId) ?? 'default';\n const variantIndex = variant?.variantIndex;\n\n const fields: VariantFields = { variantId };\n if (optimizationId !== undefined) fields.optimizationId = optimizationId;\n if (typeof variantIndex === 'number') fields.variantIndex = variantIndex;\n return fields;\n}\n\nfunction buildScope(\n ctx: Context,\n scope: ChainScope,\n key: string,\n parentExperience: ScopeAttribution | undefined\n): ScopeAttribution {\n const attribution: ScopeAttribution = {\n key,\n entityId: scope.entityId,\n entityKind: scope.kind,\n ...readVariant(ctx, scope),\n };\n if (scope.entityKindId !== undefined) {\n attribution.entityKindId = scope.entityKindId;\n }\n if (scope.kind === 'Fragment' && parentExperience !== undefined) {\n attribution.parentExperienceId = parentExperience.entityId;\n if (parentExperience.variantId !== undefined) {\n attribution.parentVariantId = parentExperience.variantId;\n }\n }\n return attribution;\n}\n\nfunction entriesBoundAt(ctx: Context, assemblyRow: number, path: PathSegment[]): readonly string[] {\n const cacheKey = `${assemblyRow}|${JSON.stringify(path)}`;\n const cached = ctx.bindingEntries.get(cacheKey);\n if (cached !== undefined) return cached;\n\n const found = new Set<string>();\n const visited = new Set<string>();\n\n const followAssembly = (row: number, path: PathSegment[]): void => {\n const visitKey = `${row}|${JSON.stringify(path)}`;\n const assembly = asRecord(ctx.dataAssemblies[row]);\n if (assembly === undefined || visited.has(visitKey)) return;\n visited.add(visitKey);\n walk(assembly.return, path);\n };\n\n const walkLeaf = (leaf: Rec, path: PathSegment[]): void => {\n if (leaf.type === 'entry') {\n const entryId = asString(rowAt(ctx.entries, leaf.entry)?.id);\n if (entryId !== undefined) found.add(entryId);\n } else if (leaf.type === 'dataAssembly') {\n const row = asIndex(leaf.dataAssembly);\n const leafPath = isPath(leaf.path) ? leaf.path : [];\n if (row !== undefined) followAssembly(row, [...leafPath, ...path]);\n }\n };\n\n const walk = (value: unknown, path: PathSegment[]): void => {\n const [head, ...rest] = path;\n\n if (Array.isArray(value)) {\n if (head === undefined) value.forEach((child) => walk(child, []));\n else if (typeof head === 'number') walk(value[head], rest);\n return;\n }\n\n const record = asRecord(value);\n if (record === undefined) return;\n if (typeof record.type === 'string') walkLeaf(record, path);\n else if (head === undefined) Object.values(record).forEach((child) => walk(child, []));\n else walk(record[String(head)], rest);\n };\n\n followAssembly(assemblyRow, path);\n const ids = [...found];\n ctx.bindingEntries.set(cacheKey, ids);\n return ids;\n}\n\nfunction readNodeEntries(ctx: Context, entry: Rec): string[] {\n const properties: unknown[] = Array.isArray(entry.contentProperties)\n ? entry.contentProperties\n : [];\n const ids = new Set<string>();\n\n for (const property of properties) {\n const item = asRecord(property);\n const assemblyRow = asIndex(item?.dataAssembly);\n const bindings = asRecord(item?.bindings);\n if (item?.type !== 'dataAssembly' || assemblyRow === undefined || bindings === undefined) {\n continue;\n }\n for (const path of Object.values(bindings).filter(isPath)) {\n for (const id of entriesBoundAt(ctx, assemblyRow, path)) ids.add(id);\n }\n }\n return [...ids];\n}\n\nfunction addScopeEntries(ctx: Context, scope: ScopeAttribution, entryIds: string[]): void {\n const owned = ctx.scopeEntries.get(scope) ?? new Set<string>();\n for (const id of entryIds) owned.add(id);\n ctx.scopeEntries.set(scope, owned);\n}\n\nfunction readChain(entry: Rec): number[] {\n const { layers } = entry;\n return Array.isArray(layers) && layers.every(Number.isInteger) ? (layers as number[]) : [];\n}\n\nfunction attributeNode(\n ctx: Context,\n node: PortableRenderNode,\n parentKeys: Map<number, string>,\n previousKeys: Map<number, string>,\n listPath: string,\n index: number\n): Map<number, string> | undefined {\n const entry = node.nodeId === undefined ? undefined : asRecord(ctx.nodes[node.nodeId]);\n if (entry === undefined) return undefined;\n const scopes = reportableScopes(ctx, readChain(entry));\n if (scopes.length === 0) return undefined;\n\n const keys = new Map<number, string>();\n const attribution: NodeAttribution = { scopes: [], roots: [] };\n let parentExperience: ScopeAttribution | undefined;\n let nearest: ScopeAttribution | undefined;\n\n // A sibling's key is only reusable while every enclosing scope matches as well.\n const previousScopes = previousKeys.entries();\n let sameOccurrencePrefix = true;\n\n for (const scope of scopes) {\n const previous = previousScopes.next().value;\n sameOccurrencePrefix &&= previous?.[0] === scope.row;\n\n const isRoot = !parentKeys.has(scope.row);\n const key: string =\n parentKeys.get(scope.row) ??\n (sameOccurrencePrefix ? previous?.[1] : undefined) ??\n occurrenceKey(scope.row, listPath, index);\n sameOccurrencePrefix &&= previous?.[1] === key;\n keys.set(scope.row, key);\n\n nearest = ctx.scopes[key] ??= buildScope(ctx, scope, key, parentExperience);\n attribution.scopes.push(nearest);\n if (isRoot) attribution.roots.push(nearest);\n if (scope.kind === 'Experience' && parentExperience === undefined) parentExperience = nearest;\n }\n\n const entryIds = readNodeEntries(ctx, entry);\n if (entryIds.length > 0 && nearest !== undefined) {\n attribution.entryIds = entryIds;\n addScopeEntries(ctx, nearest, entryIds);\n }\n node.attribution = attribution;\n return keys;\n}\n\nfunction assignSiblings(\n ctx: Context,\n siblings: PortableRenderNode[],\n parentKeys: Map<number, string>,\n listPath = ''\n): void {\n let previousKeys = new Map<number, string>();\n for (const [index, node] of siblings.entries()) {\n const ownKeys = attributeNode(ctx, node, parentKeys, previousKeys, listPath, index);\n previousKeys = ownKeys ?? new Map();\n\n for (const [slotName, children] of Object.entries(node.slots)) {\n assignSiblings(ctx, children, ownKeys ?? parentKeys, `${listPath}/${index}.${slotName}`);\n }\n }\n}\n\nexport function resolveAttribution(\n sourceMap: ExperienceSourceMap | undefined,\n nodes: PortableRenderNode[],\n log: DebugLogger\n): PlanAttribution | undefined {\n if (sourceMap === undefined) return undefined;\n const ctx = createContext(sourceMap, log);\n if (ctx === undefined) return undefined;\n\n assignSiblings(ctx, nodes, new Map());\n for (const [scope, ids] of ctx.scopeEntries) scope.entryIds = [...ids];\n\n const scopes = Object.values(ctx.scopes);\n for (const scope of scopes) Object.freeze(scope);\n return scopes.length > 0 ? { scopes: ctx.scopes } : undefined;\n}\n"],"mappings":"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","names":["path"]}
package/dist/types.d.ts CHANGED
@@ -241,9 +241,9 @@ interface PortableRenderNode {
241
241
  /**
242
242
  * One rendered occurrence of an Experience or persisted Fragment. Field names
243
243
  * follow the ExO event shape, so it stays structurally assignable to the
244
- * client's interaction-event builder args. Optional fields are omitted, never
245
- * invented: the baseline variant carries no `variantId`, `variantIndex` or
246
- * `optimizationId`.
244
+ * runtime's interaction-event builder args. Resolved scopes always have a
245
+ * `variantId`: the selected variant, or `default` when no variant ID is available.
246
+ * Other unavailable selection fields are omitted.
247
247
  */
248
248
  interface ScopeAttribution {
249
249
  /** Opaque, plan-local occurrence key. Never parse it. */
@@ -255,6 +255,8 @@ interface ScopeAttribution {
255
255
  variantId?: string;
256
256
  variantIndex?: number;
257
257
  parentExperienceId?: string;
258
+ /** Selected variant of the root Experience. Omitted on the Experience itself. */
259
+ parentVariantId?: string;
258
260
  entryIds?: string[];
259
261
  }
260
262
  /** A node's own view of the scopes it belongs to. Plain data, safe to serialize. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/experiences-sdk-core",
3
- "version": "0.12.4",
3
+ "version": "0.12.6",
4
4
  "description": "Runtime-neutral types + experience resolution for Contentful Experiences",
5
5
  "license": "MIT",
6
6
  "type": "module",