@contentful/experiences-sdk-core 0.12.6 → 0.13.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.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,448 @@
1
- export { ComponentNode, ComponentRef, DesignPropValue, DesignToken, ExperienceContext, ExperienceExtensions, ExperienceNode, ExperiencePayload, ExperienceSourceMap, ExperienceSys, ExperienceTemplateNode, ExperienceTemplateRef, ManualDesignValue, NodeAttribution, PlanAttribution, PortableRegistration, PortableRenderNode, PortableRenderPlan, RenderContext, ResolveContext, ResolveToken, ScopeAttribution } from './types.js';
2
- export { ResolveExperienceOptions, ResolverConfig, resolveExperience } from './resolve-experience.js';
3
- export { DebugLogger, createDebugLogger } from './debug-logger.js';
4
- export { applyTokenResolver, resolveDesignProperties } from './design-properties.js';
5
- export { TRACKING_CLICKABLE_ATTRIBUTE, TRACKING_SCOPES_ATTRIBUTE, TrackingAttributes, getTrackingAttributes } from './tracking-attributes.js';
1
+ /**
2
+ * Per-render runtime context. Read through the adapter's accessor
3
+ * (`useExperience()` / `getExperience()` / `injectExperience()`), and passed to
4
+ * every `resolveData` hook as `ctx.experience`. Never spread onto component
5
+ * props — components receive only the props they declare.
6
+ *
7
+ * `debug` is the single observability switch. When on it: emits verbose logs
8
+ * from `resolveExperience` and `fetchExperience`; renders the visible
9
+ * missing-component box (see the adapters' `MissingComponent`); and turns the
10
+ * default `renderUnknown` fallback into the richer debug component. One boolean
11
+ * threads through both fetch and render, so a customer can't enable one half
12
+ * and be confused by the other.
13
+ */
14
+ interface ExperienceContext {
15
+ debug: boolean;
16
+ /**
17
+ * Free-form pass-through for application data. The SDK never writes a key
18
+ * here. Untyped on purpose — narrow at the read site.
19
+ *
20
+ * Must be plain serializable data: it rides on `PortableRenderPlan`, which
21
+ * crosses server/client boundaries.
22
+ */
23
+ metadata: Record<string, unknown>;
24
+ }
25
+ /** Render-time context is the same plain data supplied to `resolveData`. */
26
+ type RenderContext = ExperienceContext;
27
+ /**
28
+ * Discriminated design-property value as it arrives from XDA. v1 accepts:
29
+ * - ManualDesignValue: an explicit scalar.
30
+ * - DesignToken: a token reference resolved while building the render plan.
31
+ */
32
+ type DesignPropValue = ManualDesignValue | DesignToken;
33
+ interface ManualDesignValue {
34
+ type: 'ManualDesignValue';
35
+ value: string | number | boolean;
36
+ }
37
+ interface DesignToken {
38
+ type: 'DesignToken';
39
+ value: string;
40
+ }
41
+ /**
42
+ * Turns a `DesignToken` into a runtime value. `ref.value` is the
43
+ * customer-defined token id; returning `undefined` retains the raw token and
44
+ * records a diagnostic. Sync only — it runs while building the render plan.
45
+ */
46
+ type ResolveToken = (ref: DesignToken) => unknown;
47
+ /**
48
+ * Resource-link reference to a registered Component. The `urn` carries
49
+ * the component id; the build-plan extracts the id by taking the segment after
50
+ * the last slash.
51
+ *
52
+ * Mirrors `ComponentLink` from `@contentful/experience-delivery`.
53
+ */
54
+ interface ComponentRef {
55
+ sys: {
56
+ type: 'ResourceLink';
57
+ linkType: 'Contentful:Component';
58
+ urn: string;
59
+ };
60
+ }
61
+ /**
62
+ * Resource-link reference to an Experience Template. The `urn` carries the
63
+ * template id, extracted the same way as a component id (segment after the
64
+ * last slash).
65
+ *
66
+ * Mirrors `ExperienceTemplateLink` from `@contentful/experience-delivery`.
67
+ */
68
+ interface ExperienceTemplateRef {
69
+ sys: {
70
+ type: 'ResourceLink';
71
+ linkType: 'Contentful:ExperienceTemplate';
72
+ urn: string;
73
+ };
74
+ }
75
+ /**
76
+ * One node from `HydratedExperienceView.nodes` (or any `slots[name]`).
77
+ * Discriminated by which of `component` / `experienceTemplate` is present.
78
+ *
79
+ * Mirrors `RenamedHydratedTreeNode` from `@contentful/experience-delivery`.
80
+ */
81
+ type ExperienceNode = ComponentNode | ExperienceTemplateNode;
82
+ /** Mirrors `RenamedComponentTreeNode` from `@contentful/experience-delivery`. */
83
+ interface ComponentNode {
84
+ component: ComponentRef;
85
+ id?: string;
86
+ contentProperties?: Record<string, unknown>;
87
+ designProperties?: Record<string, DesignPropValue>;
88
+ slots?: Record<string, ExperienceNode[]>;
89
+ contentBindings?: string;
90
+ }
91
+ /** Mirrors `RenamedTemplateTreeNode` from `@contentful/experience-delivery`. */
92
+ interface ExperienceTemplateNode {
93
+ experienceTemplate: ExperienceTemplateRef;
94
+ id?: string;
95
+ contentProperties?: Record<string, unknown>;
96
+ designProperties?: Record<string, DesignPropValue>;
97
+ slots?: Record<string, ExperienceNode[]>;
98
+ contentBindings?: string;
99
+ }
100
+ /**
101
+ * Top-level `sys` block on an Experience payload. The bits the SDK actually
102
+ * reads are typed; everything else is left loose because the upstream
103
+ * type carries dozens of editor/audit fields the renderer doesn't care about.
104
+ *
105
+ * Mirrors the parts of `RenamedDeliveryExperienceSys` the renderer reads.
106
+ */
107
+ interface ExperienceSys {
108
+ /**
109
+ * Editorial link to the Experience Template this Experience was authored
110
+ * from. The renderer does NOT read this — it is present on every Experience
111
+ * (both coded and composite templates), so it carries no signal about
112
+ * whether a template should wrap anything. Rendering is driven entirely by
113
+ * `nodes`: an `ExperienceTemplateNode` there means "render this coded
114
+ * template"; its absence means the template was composite and the nodes are
115
+ * plain components. Typed here only so payloads round-trip.
116
+ */
117
+ experienceTemplate?: ExperienceTemplateRef;
118
+ [key: string]: unknown;
119
+ }
120
+ /**
121
+ * Content source map, returned under `extensions.sourceMap` when the request
122
+ * opts in. The SDK passes it through without interpreting it, so collections
123
+ * stay `unknown[]` — `core` takes no dependency on the delivery client. Narrow
124
+ * with `ContentfulViewDelivery.HydratedExperienceViewExtensionsSourceMap`.
125
+ *
126
+ * Mirrors `HydratedExperienceViewExtensionsSourceMap`.
127
+ */
128
+ interface ExperienceSourceMap {
129
+ version: number;
130
+ variants: unknown[];
131
+ spaces: string[];
132
+ environments: string[];
133
+ locales: string[];
134
+ entries: unknown[];
135
+ assets: unknown[];
136
+ layers: unknown[];
137
+ dataAssemblies: unknown[];
138
+ nodes: Record<string, unknown>;
139
+ }
140
+ /**
141
+ * XDA response extensions consumed by the SDK. Kept as a structural subset of
142
+ * `HydratedExperienceViewExtensions` so Core remains dependency-free.
143
+ */
144
+ interface ExperienceExtensions {
145
+ personalization?: {
146
+ /** Optional here so hand-authored and partial payloads remain accepted. */
147
+ profile?: {
148
+ id?: string;
149
+ };
150
+ experiences?: unknown[];
151
+ changes?: unknown[];
152
+ };
153
+ sourceMap?: ExperienceSourceMap;
154
+ }
155
+ /**
156
+ * Top-level Experience payload as returned by the Experience Delivery API
157
+ * (`HydratedExperienceView` from `@contentful/experience-delivery`).
158
+ *
159
+ * Structurally compatible with the upstream type — no normalization step
160
+ * required when consuming a delivery-client response. The delivery API returns
161
+ * this shape when the request carries the
162
+ * `x-contentful-enable-alpha-feature: new-exo-entity-types` header, which
163
+ * `@contentful/experience-delivery` sends on every request.
164
+ */
165
+ interface ExperiencePayload {
166
+ nodes: ExperienceNode[];
167
+ errors?: unknown[];
168
+ extensions?: ExperienceExtensions;
169
+ sys?: ExperienceSys;
170
+ }
171
+ /**
172
+ * Per-node context handed to a component's `resolveData` resolver. Carries
173
+ * the raw content + design props from the payload.
174
+ */
175
+ interface ResolveContext {
176
+ content: Record<string, unknown>;
177
+ design: Record<string, DesignPropValue>;
178
+ experience: ExperienceContext;
179
+ }
180
+ /**
181
+ * Registration metadata for a single instance — the SDK's interpreted
182
+ * pointer to the customer's implementation. Carries the resolved id plus the
183
+ * registry that id belongs to; capabilities (state requirements, supported
184
+ * events, lifecycle hints, fallback ids) land here when needed.
185
+ *
186
+ * `kind` tells the adapter which registry to look `id` up in:
187
+ * `'component'` → `Config.components`, `'experienceTemplate'` →
188
+ * `Config.experienceTemplates`. Everything else about a node is identical
189
+ * across the two kinds — a coded Experience Template is just a node whose
190
+ * implementation lives in the other registry.
191
+ */
192
+ interface PortableRegistration {
193
+ kind: 'component' | 'experienceTemplate';
194
+ id: string;
195
+ }
196
+ /**
197
+ * The IR — one node per component or Experience Template instance. The seam
198
+ * that lets non-React adapters (Angular, SwiftUI, Compose) consume the same
199
+ * interpretation.
200
+ *
201
+ * Design props preserve the discriminated value shape as they arrived. Token
202
+ * resolution happens when the plan is built, before any framework renderer
203
+ * reads the values.
204
+ *
205
+ * `props.resolved` is populated by `resolveExperience` from any
206
+ * customer-supplied `resolveData` resolver and merged into the final props
207
+ * after content + design but before slot props.
208
+ *
209
+ * `props.design` contains values ready for the renderer; `props.designRaw`
210
+ * preserves the source values for `resolveData` and payload context helpers.
211
+ */
212
+ interface PortableRenderNode {
213
+ /**
214
+ * Reportable scopes this node belongs to. Present only when the plan carries
215
+ * a usable source map and the node's id is in it.
216
+ */
217
+ attribution?: NodeAttribution;
218
+ /**
219
+ * Optional. Passed through from the XDA payload's `id` field when the
220
+ * editor supplies one. The SDK does NOT auto-generate ids; adapters fall
221
+ * back to the array index for React keys / debug labels when absent.
222
+ */
223
+ nodeId?: string;
224
+ registration: PortableRegistration;
225
+ props: {
226
+ content: Record<string, unknown>;
227
+ /** Token-resolved design values. */
228
+ design: Record<string, unknown>;
229
+ resolved?: Record<string, unknown>;
230
+ /** Design-property envelopes as delivered by XDA. */
231
+ designRaw: Record<string, DesignPropValue>;
232
+ };
233
+ /**
234
+ * Slot children keyed by slot name, pre-built in payload order. Adapters
235
+ * pass each entry to the customer's implementation as a prop of the same
236
+ * name — a slot named `content` becomes a `content` prop. `children` is not
237
+ * special; it is simply the conventional default slot name.
238
+ */
239
+ slots: Record<string, PortableRenderNode[]>;
240
+ }
241
+ /**
242
+ * One rendered occurrence of an Experience or persisted Fragment. Field names
243
+ * follow the ExO event shape, so it stays structurally assignable to the
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
+ */
248
+ interface ScopeAttribution {
249
+ /** Opaque, plan-local occurrence key. Never parse it. */
250
+ key: string;
251
+ entityId: string;
252
+ entityKind: 'Experience' | 'ExperienceFragment';
253
+ entityKindId?: string;
254
+ optimizationId?: string;
255
+ variantId?: string;
256
+ variantIndex?: number;
257
+ parentExperienceId?: string;
258
+ /** Selected variant of the root Experience. Omitted on the Experience itself. */
259
+ parentVariantId?: string;
260
+ entryIds?: string[];
261
+ }
262
+ /** A node's own view of the scopes it belongs to. Plain data, safe to serialize. */
263
+ interface NodeAttribution {
264
+ /** Every reportable scope the node belongs to, outer to inner. Shared with `plan.attribution.scopes` and frozen. */
265
+ scopes: ScopeAttribution[];
266
+ /** The scopes this node is a top-level node of, i.e. its parent is not in them. */
267
+ roots: ScopeAttribution[];
268
+ /** Entries this node's own content properties are bound to. */
269
+ entryIds?: string[];
270
+ }
271
+ /** Occurrence-keyed lookup of every reportable scope in a plan. */
272
+ interface PlanAttribution {
273
+ scopes: Record<string, ScopeAttribution>;
274
+ }
275
+ /**
276
+ * The interpreted experience tree.
277
+ *
278
+ * Top-level is `nodes: PortableRenderNode[]` (array, not single root) to
279
+ * match the actual XDA payload shape. Renderers iterate top-level nodes and
280
+ * recurse into `node.slots`. A coded Experience Template shows up as a
281
+ * top-level node with `registration.kind === 'experienceTemplate'`, so there
282
+ * is no plan-level template concept — see `PortableRegistration`.
283
+ */
284
+ interface PortableRenderPlan {
285
+ nodes: PortableRenderNode[];
286
+ /**
287
+ * The `metadata` the resolve step ran with, so the renderer does not need it
288
+ * passed again. The renderer's `metadata` prop merges over this. `{}` when
289
+ * the caller passed none.
290
+ */
291
+ metadata: Record<string, unknown>;
292
+ /** The `debug` flag the resolve step ran with. The renderer's prop overrides it. */
293
+ debug: boolean;
294
+ /** Present only when the fetch requested the `sourceMap` extension. */
295
+ sourceMap?: ExperienceSourceMap;
296
+ /** Reportable scopes resolved from `sourceMap`. Absent when there is no usable map. */
297
+ attribution?: PlanAttribution;
298
+ /** Present when XDA returned personalization state for this Experience. */
299
+ personalization?: {
300
+ /** Profile id to use for subsequent personalized requests. */
301
+ profileId: string;
302
+ };
303
+ /**
304
+ * Resolve-time diagnostics collected while building this plan — malformed
305
+ * payload/slot shapes, an unidentifiable node, a failing `resolveData`, an
306
+ * unresolved design token. Plain `Error`s: the message names the node/
307
+ * component involved, and `resolve-data-failed`'s entry sets `.cause` to
308
+ * the original thrown/rejected error so the real stack trace stays
309
+ * reachable. Render-time diagnostics (unregistered id,
310
+ * component-render-error) are NOT here — each adapter collects those per
311
+ * render and merges both lists for `<DebugExperience>`.
312
+ *
313
+ * Note these are `Error` instances, so unlike the rest of the plan they do
314
+ * not survive a server/client serialization boundary intact — see the
315
+ * `metadata` note above. Adapters read them during the render that produced
316
+ * them.
317
+ */
318
+ diagnostics: Error[];
319
+ }
320
+
321
+ /**
322
+ * Structural type the resolver walker depends on. Matches the React /
323
+ * Svelte adapter `Config` shape but doesn't require importing them —
324
+ * render-core stays decoupled from any framework.
325
+ *
326
+ * Registry values are typed as `unknown` because each adapter accepts
327
+ * either a bare framework component (function / Svelte class / etc.) OR
328
+ * a config-object shape with `{ component, defaults?, resolveData? }`.
329
+ * The resolver only cares about `resolveData`; it duck-types each entry
330
+ * at runtime and ignores anything without it.
331
+ */
332
+ interface ResolverConfig {
333
+ components: Record<string, unknown>;
334
+ experienceTemplates?: Record<string, unknown>;
335
+ /**
336
+ * Resolves `DesignToken` design properties to runtime values. Mirrors the
337
+ * adapter `Config.resolveToken`, so server and client agree without the
338
+ * caller re-supplying it. Used during server-side pre-resolution.
339
+ */
340
+ resolveToken?: ResolveToken;
341
+ }
342
+ interface ResolveExperienceOptions {
343
+ /**
344
+ * Arbitrary per-render metadata exposed to every resolver as
345
+ * `ctx.experience.metadata`. Defaults to `{}`.
346
+ */
347
+ metadata?: Record<string, unknown>;
348
+ /**
349
+ * Observability switch. When on, `resolveExperience` logs the resolution
350
+ * steps and per-node `resolveData` fan-out timings. Threads through to the
351
+ * resolver context as `ctx.experience.debug`. Defaults to `false`.
352
+ */
353
+ debug?: boolean;
354
+ /**
355
+ * Source map carried onto the plan. An explicit map wins over
356
+ * `payload.extensions.sourceMap`. Omit to use the payload's map, if it
357
+ * carries one.
358
+ */
359
+ sourceMap?: ExperienceSourceMap;
360
+ }
361
+ /**
362
+ * Turns an Experience payload (XDA response shape) into a PortableRenderPlan
363
+ * ready to hand to a renderer. Walks the tree, classifies props, captures
364
+ * slots, and runs any component-declared `resolveData` hooks (sync or async)
365
+ * in parallel.
366
+ *
367
+ * Implementation note: the function is always async — even when no component
368
+ * declares a resolver, the cost is one microtask. Customers get a single
369
+ * uniform call site.
370
+ */
371
+ declare function resolveExperience(payload: ExperiencePayload, config: ResolverConfig, options?: ResolveExperienceOptions): Promise<PortableRenderPlan>;
372
+
373
+ /**
374
+ * A scoped debug logger. `scope` names the emitting subsystem (e.g. `core`,
375
+ * `client`, `react`) so interleaved logs stay legible.
376
+ */
377
+ interface DebugLogger {
378
+ /** Log a message with optional structured data. */
379
+ log(message: string, data?: unknown): void;
380
+ /**
381
+ * Log a message whose payload is expensive to build. The thunk only runs
382
+ * when debug is enabled — keep large payload serialization out of the hot
383
+ * path when debug is off.
384
+ */
385
+ lazy(message: string, getData: () => unknown): void;
386
+ /** Time an async span and log its duration when it settles. */
387
+ time<T>(label: string, fn: () => Promise<T>): Promise<T>;
388
+ /** True when this logger is actually emitting. */
389
+ readonly enabled: boolean;
390
+ }
391
+ /**
392
+ * Build a debug logger. Returns a shared no-op when `debug` is falsy so
393
+ * callers can hold onto the result unconditionally and pay nothing when off.
394
+ */
395
+ declare function createDebugLogger(debug: boolean | undefined, scope: string): DebugLogger;
396
+
397
+ /**
398
+ * Resolve every design property on a node into a record keyed by name.
399
+ */
400
+ declare function resolveDesignProperties(designProperties: Record<string, DesignPropValue> | undefined): Record<string, string | number | boolean | DesignToken>;
401
+ /**
402
+ * Resolve `DesignToken` values via `resolveToken`; scalars pass through.
403
+ * Keys that don't resolve — no `resolveToken` configured, or the resolver
404
+ * returns `undefined` for that particular token — pass through as the raw
405
+ * `DesignToken` and their id is collected in `unresolved` for the caller to
406
+ * warn/diagnose (see `warnUnresolvedTokens` in `resolve-experience.ts`, which
407
+ * already dedupes per `resolveExperience()` call, so this function carries
408
+ * no warn-once state of its own).
409
+ */
410
+ declare function applyTokenResolver(props: Record<string, string | number | boolean | DesignToken>, resolveToken?: ResolveToken): {
411
+ props: Record<string, unknown>;
412
+ unresolved: string[];
413
+ };
414
+
415
+ /**
416
+ * Lists the occurrence keys of the scopes an element roots, space-separated.
417
+ * Put it on the outermost element a node renders. The keys are opaque: they
418
+ * resolve through `plan.attribution.scopes`, so no attribution lives in the DOM.
419
+ */
420
+ declare const TRACKING_SCOPES_ATTRIBUTE = "data-ctfl-scopes";
421
+ /**
422
+ * Opts an element into click tracking when it is not otherwise interactive.
423
+ * Links, buttons, form controls, `[role="button"]`, `[role="link"]`, and
424
+ * elements with an `onclick` handler count already. Set it to `"true"`.
425
+ */
426
+ declare const TRACKING_CLICKABLE_ATTRIBUTE = "data-ctfl-clickable";
427
+ interface TrackingAttributes {
428
+ [TRACKING_SCOPES_ATTRIBUTE]?: string;
429
+ }
430
+ /**
431
+ * Attributes to spread onto a tracked node's outermost element, built from the
432
+ * `attribution` a component context accessor returns. Returns `{}` when the
433
+ * node roots no scope: nothing is rendered for it, so a node in the middle of a
434
+ * scope, or one without attribution, is left alone.
435
+ *
436
+ * @example
437
+ * ```tsx
438
+ * const contentful = useContentfulComponent();
439
+ * <section {...getTrackingAttributes(contentful?.attribution)}>…</section>
440
+ * ```
441
+ */
442
+ declare function getTrackingAttributes(attribution: {
443
+ roots: ReadonlyArray<{
444
+ key: string;
445
+ }>;
446
+ } | undefined): TrackingAttributes;
447
+
448
+ export { type ComponentNode, type ComponentRef, type DebugLogger, type DesignPropValue, type DesignToken, type ExperienceContext, type ExperienceExtensions, type ExperienceNode, type ExperiencePayload, type ExperienceSourceMap, type ExperienceSys, type ExperienceTemplateNode, type ExperienceTemplateRef, type ManualDesignValue, type NodeAttribution, type PlanAttribution, type PortableRegistration, type PortableRenderNode, type PortableRenderPlan, type RenderContext, type ResolveContext, type ResolveExperienceOptions, type ResolveToken, type ResolverConfig, type ScopeAttribution, TRACKING_CLICKABLE_ATTRIBUTE, TRACKING_SCOPES_ATTRIBUTE, type TrackingAttributes, applyTokenResolver, createDebugLogger, getTrackingAttributes, resolveDesignProperties, resolveExperience };