@contentful/experiences-sdk-core 0.5.2

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 ADDED
@@ -0,0 +1,37 @@
1
+ ## 0.5.2 (2026-07-20)
2
+
3
+ ### 🩹 Fixes
4
+
5
+ - rename experiences-core to experiences-sdk-core [AIS-305] ([#76](https://github.com/contentful/experiences/pull/76))
6
+
7
+ ## 0.5.1 (2026-07-17)
8
+
9
+ ### 🚀 Features
10
+
11
+ - design-token resolution via resolveToken + useDesignValues [AIS-149] ([#53](https://github.com/contentful/experiences/pull/53))
12
+
13
+ ## 0.5.0 (2026-07-17)
14
+
15
+ This was a version bump only for core to align it with other projects, there were no code changes.
16
+
17
+ ## 0.4.0 (2026-07-08)
18
+
19
+ ### 🚀 Features
20
+
21
+ - idiomatic adapters — bare components + context hooks ([0365bfb](https://github.com/contentful/experiences/commit/0365bfb))
22
+
23
+ ## 0.3.0 (2026-06-24)
24
+
25
+ ### 🚀 Features
26
+
27
+ - more-robust examples + simple/advanced README split + contentful prop ([#18](https://github.com/contentful/experiences/pull/18))
28
+
29
+ ## 0.2.0 (2026-06-24)
30
+
31
+ This was a version bump only for core to align it with other projects, there were no code changes.
32
+
33
+ ## 0.1.0 (2026-06-23)
34
+
35
+ ### 🚀 Features
36
+
37
+ - initial Experiences SDK monorepo with React adapter [] ([#16](https://github.com/contentful/experiences/pull/16))
package/README.md ADDED
@@ -0,0 +1,20 @@
1
+ # @contentful/experiences-sdk-core
2
+
3
+ > ⚠️ **Internal package.** Customers do not install this directly. The framework adapter (e.g. [`@contentful/experiences-react`](../adapter-react/)) re-exports everything customers need.
4
+
5
+ Runtime-neutral primitives shared across all framework adapters.
6
+
7
+ ## What lives here
8
+
9
+ - **Types** — `PortableRenderPlan`, `PortableRenderNode`, `PortableTemplate`, `ExperiencePayload`, `ExperienceNode`, the discriminated `DesignPropValue` union (`ManualDesignValue` / `DesignToken` / `ValuesByViewport`), `ViewportDef`, `ExperienceContext`, `ResolveContext`.
10
+ - **`resolveExperience(payload, config, opts)`** — single async entry that walks an XDA payload, classifies content vs. design properties, captures slots, runs any component-declared `resolveData` hooks in parallel, and emits a runtime-neutral `PortableRenderPlan` ready for any framework adapter to render.
11
+
12
+ ## Why a separate package?
13
+
14
+ Future Angular, Svelte, Vue, SwiftUI, and Compose adapters consume the same plan. Sharing core means each adapter has zero plan-building or prop-classification logic to duplicate — the seam is the `PortableRenderPlan` contract.
15
+
16
+ See [`../../AGENTS.md`](../../AGENTS.md) for the full architecture and design decisions.
17
+
18
+ ## License
19
+
20
+ MIT. See the repository [`LICENSE`](../../LICENSE) and [`NOTICE`](../../NOTICE) for full attribution.
@@ -0,0 +1,2 @@
1
+ export { ComponentTypeNode, ComponentTypeRef, DesignPropValue, DesignToken, ExperienceContext, ExperienceNode, ExperiencePayload, ExperienceSys, ManualDesignValue, PortableRegistration, PortableRenderNode, PortableRenderPlan, PortableTemplate, ResolveContext, ResolveToken, TemplateNode, TemplateRef, ValuesByViewport, ViewportDef } from './types.js';
2
+ export { ResolveExperienceOptions, ResolverConfig, resolveExperience } from './resolve-experience.js';
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export * from "./types";
2
+ import { resolveExperience } from "./resolve-experience";
3
+ export {
4
+ resolveExperience
5
+ };
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from './types';\nexport { resolveExperience } from './resolve-experience';\nexport type { ResolverConfig, ResolveExperienceOptions } from './resolve-experience';\n"],"mappings":"AAAA,cAAc;AACd,SAAS,yBAAyB;","names":[]}
@@ -0,0 +1,37 @@
1
+ import { ExperienceContext, ExperiencePayload, PortableRenderPlan } from './types.js';
2
+
3
+ /**
4
+ * Structural type the resolver walker depends on. Matches the React /
5
+ * Svelte adapter `Config` shape but doesn't require importing them —
6
+ * render-core stays decoupled from any framework.
7
+ *
8
+ * Registry values are typed as `unknown` because each adapter accepts
9
+ * either a bare framework component (function / Svelte class / etc.) OR
10
+ * a config-object shape with `{ component, defaults?, resolveData? }`.
11
+ * The resolver only cares about `resolveData`; it duck-types each entry
12
+ * at runtime and ignores anything without it.
13
+ */
14
+ interface ResolverConfig {
15
+ components: Record<string, unknown>;
16
+ templates?: Record<string, unknown>;
17
+ }
18
+ interface ResolveExperienceOptions {
19
+ /**
20
+ * Per-render runtime context exposed to every resolver as `ctx.experience`.
21
+ * Defaults to `{ isPreview: false, metadata: {} }`.
22
+ */
23
+ experience?: Partial<ExperienceContext>;
24
+ }
25
+ /**
26
+ * Turns an Experience payload (XDA response shape) into a PortableRenderPlan
27
+ * ready to hand to a renderer. Walks the tree, classifies props, captures
28
+ * slots, and runs any component-declared `resolveData` hooks (sync or async)
29
+ * in parallel.
30
+ *
31
+ * Implementation note: the function is always async — even when no component
32
+ * declares a resolver, the cost is one microtask. Customers get a single
33
+ * uniform call site.
34
+ */
35
+ declare function resolveExperience(payload: ExperiencePayload, config: ResolverConfig, options?: ResolveExperienceOptions): Promise<PortableRenderPlan>;
36
+
37
+ export { type ResolveExperienceOptions, type ResolverConfig, resolveExperience };
@@ -0,0 +1,124 @@
1
+ function getResolver(entry) {
2
+ if (typeof entry !== "object" || entry === null) return void 0;
3
+ const candidate = entry.resolveData;
4
+ return typeof candidate === "function" ? candidate : void 0;
5
+ }
6
+ const DEFAULT_EXPERIENCE = {
7
+ isPreview: false,
8
+ metadata: {},
9
+ viewports: []
10
+ };
11
+ function isComponentTypeNode(node) {
12
+ return "componentType" in node;
13
+ }
14
+ function extractIdFromUrn(urn) {
15
+ const segments = urn.split("/").filter((s) => s.length > 0);
16
+ return segments[segments.length - 1] ?? urn;
17
+ }
18
+ function buildNode(node, config, nodeRefs) {
19
+ if (!isComponentTypeNode(node)) {
20
+ if (typeof console !== "undefined") {
21
+ console.warn(
22
+ "[@contentful/experiences-sdk-core] Skipping Template-variant node \u2014 Templates are not supported in v1."
23
+ );
24
+ }
25
+ return null;
26
+ }
27
+ const componentTypeId = extractIdFromUrn(node.componentType.sys.urn);
28
+ const slots = {};
29
+ if (node.slots) {
30
+ for (const [slotName, children] of Object.entries(node.slots)) {
31
+ if (!Array.isArray(children)) {
32
+ throw new TypeError(
33
+ `Slot "${slotName}" on component "${componentTypeId}" must be an array of nodes.`
34
+ );
35
+ }
36
+ const built2 = [];
37
+ for (const child of children) {
38
+ const childNode = buildNode(child, config, nodeRefs);
39
+ if (childNode === null) continue;
40
+ built2.push(childNode);
41
+ }
42
+ slots[slotName] = built2;
43
+ }
44
+ }
45
+ const built = {
46
+ registration: { componentTypeId },
47
+ props: {
48
+ content: { ...node.contentProperties ?? {} },
49
+ design: { ...node.designProperties ?? {} }
50
+ },
51
+ slots
52
+ };
53
+ if (node.id) built.nodeId = node.id;
54
+ if (getResolver(config.components[componentTypeId])) {
55
+ nodeRefs.push(built);
56
+ }
57
+ return built;
58
+ }
59
+ async function resolveExperience(payload, config, options = {}) {
60
+ const nodeRefs = [];
61
+ const nodes = [];
62
+ for (const node of payload.nodes) {
63
+ const built = buildNode(node, config, nodeRefs);
64
+ if (built !== null) nodes.push(built);
65
+ }
66
+ const templateUrn = payload.sys?.template?.sys.urn;
67
+ let template;
68
+ if (typeof templateUrn === "string" && templateUrn.length > 0) {
69
+ template = {
70
+ templateId: extractIdFromUrn(templateUrn),
71
+ props: { content: {}, design: {} }
72
+ };
73
+ }
74
+ const experience = {
75
+ ...DEFAULT_EXPERIENCE,
76
+ ...options.experience,
77
+ metadata: {
78
+ ...DEFAULT_EXPERIENCE.metadata,
79
+ ...options.experience?.metadata ?? {}
80
+ },
81
+ viewports: payload.viewports
82
+ };
83
+ const tasks = [];
84
+ for (const node of nodeRefs) {
85
+ const resolver = getResolver(config.components[node.registration.componentTypeId]);
86
+ if (!resolver) continue;
87
+ const ctx = {
88
+ content: node.props.content,
89
+ design: node.props.design,
90
+ experience
91
+ };
92
+ tasks.push(
93
+ Promise.resolve(resolver(ctx)).then((resolved) => {
94
+ node.props.resolved = resolved;
95
+ })
96
+ );
97
+ }
98
+ if (template) {
99
+ const tplResolver = getResolver(config.templates?.[template.templateId]);
100
+ if (tplResolver) {
101
+ const ctx = {
102
+ content: template.props.content,
103
+ design: template.props.design,
104
+ experience
105
+ };
106
+ const tpl = template;
107
+ tasks.push(
108
+ Promise.resolve(tplResolver(ctx)).then((resolved) => {
109
+ tpl.props.resolved = resolved;
110
+ })
111
+ );
112
+ }
113
+ }
114
+ if (tasks.length > 0) await Promise.all(tasks);
115
+ return {
116
+ viewports: payload.viewports,
117
+ nodes,
118
+ ...template ? { template } : {}
119
+ };
120
+ }
121
+ export {
122
+ resolveExperience
123
+ };
124
+ //# sourceMappingURL=resolve-experience.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/resolve-experience.ts"],"sourcesContent":["/*\n * Single async entry that turns an XDA Experience payload into a\n * runtime-neutral PortableRenderPlan ready to render.\n *\n * v1 behavior:\n * - Walk the payload's nodes recursively. Each ComponentType node becomes\n * a PortableRenderNode with `registration.componentTypeId` extracted\n * from `componentType.sys.urn` (last slash-segment).\n * - Split content + design properties onto `node.props.{content,design}`.\n * Design-prop envelopes (DesignToken / ManualDesignValue / ValuesByViewport)\n * are preserved on the IR; the design package unwraps them at render time.\n * - Template-variant nodes are skipped with a console.warn — out of v1 scope.\n * - For every component whose registration declares `resolveData`, run the\n * resolver (sync or async) in parallel with peers, and attach the result\n * to `node.props.resolved`.\n * - Unknown component-type-id is a render-time concern (handled by the\n * framework adapter via `renderUnknown`); the IR still emits the node.\n */\n\nimport type {\n ComponentTypeNode,\n DesignPropValue,\n ExperienceContext,\n ExperienceNode,\n ExperiencePayload,\n PortableRenderNode,\n PortableRenderPlan,\n PortableTemplate,\n ResolveContext,\n} from './types';\n\n/**\n * Structural type the resolver walker depends on. Matches the React /\n * Svelte adapter `Config` shape but doesn't require importing them —\n * render-core stays decoupled from any framework.\n *\n * Registry values are typed as `unknown` because each adapter accepts\n * either a bare framework component (function / Svelte class / etc.) OR\n * a config-object shape with `{ component, defaults?, resolveData? }`.\n * The resolver only cares about `resolveData`; it duck-types each entry\n * at runtime and ignores anything without it.\n */\nexport interface ResolverConfig {\n components: Record<string, unknown>;\n templates?: Record<string, unknown>;\n}\n\nfunction getResolver(\n entry: unknown\n):\n | ((ctx: ResolveContext) => Record<string, unknown> | Promise<Record<string, unknown>>)\n | undefined {\n if (typeof entry !== 'object' || entry === null) return undefined;\n const candidate = (entry as { resolveData?: unknown }).resolveData;\n return typeof candidate === 'function'\n ? (candidate as (\n ctx: ResolveContext\n ) => Record<string, unknown> | Promise<Record<string, unknown>>)\n : undefined;\n}\n\nexport interface ResolveExperienceOptions {\n /**\n * Per-render runtime context exposed to every resolver as `ctx.experience`.\n * Defaults to `{ isPreview: false, metadata: {} }`.\n */\n experience?: Partial<ExperienceContext>;\n}\n\nconst DEFAULT_EXPERIENCE: ExperienceContext = {\n isPreview: false,\n metadata: {},\n viewports: [],\n};\n\nfunction isComponentTypeNode(node: ExperienceNode): node is ComponentTypeNode {\n return 'componentType' in node;\n}\n\n/**\n * Extract the flat id (componentType or template) from its `ResourceLink`\n * URN. Real URN shapes:\n * crn:contentful:::experience:spaces/$self/environments/$self/componentTypes/<id>\n * crn:contentful:::experience:spaces/$self/environments/$self/templates/<id>\n *\n * The id is the final path segment. We split on `/` and take the last\n * non-empty piece so this also tolerates trailing slashes or alternative\n * prefix shapes.\n */\nfunction extractIdFromUrn(urn: string): string {\n const segments = urn.split('/').filter((s) => s.length > 0);\n return segments[segments.length - 1] ?? urn;\n}\n\n/**\n * Recursively turn a payload node into an IR node. The collected `nodeRefs`\n * array is for the resolver pass — every built node with a registered\n * resolver gets a reference appended so we can run them in parallel without\n * walking the tree twice.\n */\nfunction buildNode(\n node: ExperienceNode,\n config: ResolverConfig,\n nodeRefs: PortableRenderNode[]\n): PortableRenderNode | null {\n if (!isComponentTypeNode(node)) {\n if (typeof console !== 'undefined') {\n console.warn(\n '[@contentful/experiences-sdk-core] Skipping Template-variant node — Templates are not supported in v1.'\n );\n }\n return null;\n }\n\n const componentTypeId = extractIdFromUrn(node.componentType.sys.urn);\n\n const slots: Record<string, PortableRenderNode[]> = {};\n if (node.slots) {\n for (const [slotName, children] of Object.entries(node.slots)) {\n if (!Array.isArray(children)) {\n throw new TypeError(\n `Slot \"${slotName}\" on component \"${componentTypeId}\" must be an array of nodes.`\n );\n }\n const built: PortableRenderNode[] = [];\n for (const child of children) {\n const childNode = buildNode(child, config, nodeRefs);\n if (childNode === null) continue;\n built.push(childNode);\n }\n slots[slotName] = built;\n }\n }\n\n const built: PortableRenderNode = {\n registration: { componentTypeId },\n props: {\n content: { ...(node.contentProperties ?? {}) },\n design: { ...(node.designProperties ?? {}) } as Record<string, DesignPropValue>,\n },\n slots,\n };\n if (node.id) built.nodeId = node.id;\n if (getResolver(config.components[componentTypeId])) {\n nodeRefs.push(built);\n }\n return built;\n}\n\n/**\n * Turns an Experience payload (XDA response shape) into a PortableRenderPlan\n * ready to hand to a renderer. Walks the tree, classifies props, captures\n * slots, and runs any component-declared `resolveData` hooks (sync or async)\n * in parallel.\n *\n * Implementation note: the function is always async — even when no component\n * declares a resolver, the cost is one microtask. Customers get a single\n * uniform call site.\n */\nexport async function resolveExperience(\n payload: ExperiencePayload,\n config: ResolverConfig,\n options: ResolveExperienceOptions = {}\n): Promise<PortableRenderPlan> {\n // Pass 1: walk the payload into the IR. Collect refs to nodes that need\n // resolveData so pass 2 can run them in parallel without re-walking.\n const nodeRefs: PortableRenderNode[] = [];\n const nodes: PortableRenderNode[] = [];\n for (const node of payload.nodes) {\n const built = buildNode(node, config, nodeRefs);\n if (built !== null) nodes.push(built);\n }\n\n // Build the page-level template stub if the payload carries one. XDA\n // payloads don't yet emit template-level content/design properties, so\n // the IR carries empty bags.\n const templateUrn = payload.sys?.template?.sys.urn;\n let template: PortableTemplate | undefined;\n if (typeof templateUrn === 'string' && templateUrn.length > 0) {\n template = {\n templateId: extractIdFromUrn(templateUrn),\n props: { content: {}, design: {} },\n };\n }\n\n // Pass 2: run resolveData hooks for components AND the template in parallel.\n // `viewports` is always sourced from the payload — caller-supplied\n // options.experience.viewports is ignored (the list is fact, not opinion).\n const experience: ExperienceContext = {\n ...DEFAULT_EXPERIENCE,\n ...options.experience,\n metadata: {\n ...DEFAULT_EXPERIENCE.metadata,\n ...(options.experience?.metadata ?? {}),\n },\n viewports: payload.viewports,\n };\n\n const tasks: Array<Promise<void>> = [];\n\n for (const node of nodeRefs) {\n const resolver = getResolver(config.components[node.registration.componentTypeId]);\n if (!resolver) continue;\n const ctx: ResolveContext = {\n content: node.props.content,\n design: node.props.design,\n experience,\n };\n tasks.push(\n Promise.resolve(resolver(ctx)).then((resolved) => {\n node.props.resolved = resolved;\n })\n );\n }\n\n if (template) {\n const tplResolver = getResolver(config.templates?.[template.templateId]);\n if (tplResolver) {\n const ctx: ResolveContext = {\n content: template.props.content,\n design: template.props.design,\n experience,\n };\n const tpl = template;\n tasks.push(\n Promise.resolve(tplResolver(ctx)).then((resolved) => {\n tpl.props.resolved = resolved;\n })\n );\n }\n }\n\n if (tasks.length > 0) await Promise.all(tasks);\n\n return {\n viewports: payload.viewports,\n nodes,\n ...(template ? { template } : {}),\n };\n}\n"],"mappings":"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","names":["built"]}
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Per-render runtime context attached to every customer component as the
3
+ * `experience` prop, and passed to every `resolveData` hook as `ctx.experience`.
4
+ * The conventional, single injection point — kept small at v1.
5
+ *
6
+ * `viewports` (the *list*) is here so customer resolvers can inspect what
7
+ * viewports an Experience declares (e.g. "is there a mobile viewport?").
8
+ * The *active* viewport is render-time only and lives on the framework
9
+ * adapter's RenderContext — exposing it here would mean async resolvers
10
+ * re-fire on every viewport change, which would be a footgun.
11
+ */
12
+ interface ExperienceContext {
13
+ isPreview: boolean;
14
+ metadata: Record<string, unknown>;
15
+ viewports: ViewportDef[];
16
+ }
17
+ /**
18
+ * One viewport definition from a delivered Experience. The `query` is the
19
+ * Contentful media-query DSL ("*" | "<992px" | ">1200px"), not raw CSS.
20
+ *
21
+ * The first viewport in the list is conventionally the wildcard ("*") that
22
+ * always matches. The viewport order encodes the cascade direction —
23
+ * desktop-first (descending) or mobile-first (ascending).
24
+ */
25
+ interface ViewportDef {
26
+ id: string;
27
+ query: string;
28
+ displayName: string;
29
+ previewSize: string;
30
+ }
31
+ /**
32
+ * Discriminated design-property value as it arrives from XDA. v1 accepts:
33
+ * - ManualDesignValue: an explicit scalar (no viewport involved).
34
+ * - ValuesByViewport: a viewport-keyed bag where each entry is itself a
35
+ * ManualDesignValue or DesignToken.
36
+ * - DesignToken: a token reference, passed through to customer components
37
+ * as-is for v1. Resolution lands in the future tokens package.
38
+ */
39
+ type DesignPropValue = ManualDesignValue | DesignToken | ValuesByViewport;
40
+ interface ManualDesignValue {
41
+ type: 'ManualDesignValue';
42
+ value: string | number | boolean;
43
+ }
44
+ interface DesignToken {
45
+ type: 'DesignToken';
46
+ value: string;
47
+ }
48
+ /**
49
+ * Turns a `DesignToken` envelope into a runtime value. `ref.value` is the
50
+ * customer-defined token id; returning `undefined` means "not resolvable" and
51
+ * the adapter drops the key (with a warning). Sync only — it runs at render time.
52
+ */
53
+ type ResolveToken = (ref: DesignToken) => unknown;
54
+ interface ValuesByViewport {
55
+ type: 'ValuesByViewport';
56
+ values: Record<string, ManualDesignValue | DesignToken>;
57
+ }
58
+ /**
59
+ * Resource-link reference to a registered Component Type. The `urn` carries
60
+ * the type id; the build-plan extracts the id by taking the segment after
61
+ * the last slash.
62
+ */
63
+ interface ComponentTypeRef {
64
+ sys: {
65
+ type: 'ResourceLink';
66
+ linkType: 'Contentful:ComponentType';
67
+ urn: string;
68
+ };
69
+ }
70
+ /**
71
+ * Resource-link reference to a Template. Templates are out of v1 scope and
72
+ * are skipped at plan-build time with a diagnostic.
73
+ */
74
+ interface TemplateRef {
75
+ sys: {
76
+ type: 'ResourceLink';
77
+ linkType: 'Contentful:Template';
78
+ urn: string;
79
+ };
80
+ }
81
+ /**
82
+ * One node from `GetExperienceViewResponse.nodes` (or any `slots[name]`).
83
+ * Discriminated by which of `componentType` / `template` is present.
84
+ */
85
+ type ExperienceNode = ComponentTypeNode | TemplateNode;
86
+ interface ComponentTypeNode {
87
+ componentType: ComponentTypeRef;
88
+ id?: string;
89
+ contentProperties?: Record<string, unknown>;
90
+ designProperties?: Record<string, DesignPropValue>;
91
+ slots?: Record<string, ExperienceNode[]>;
92
+ contentBindings?: string;
93
+ }
94
+ interface TemplateNode {
95
+ template: TemplateRef;
96
+ id?: string;
97
+ contentProperties?: Record<string, unknown>;
98
+ designProperties?: Record<string, DesignPropValue>;
99
+ slots?: Record<string, ExperienceNode[]>;
100
+ contentBindings?: string;
101
+ }
102
+ /**
103
+ * Top-level `sys` block on an Experience payload. The bits the SDK actually
104
+ * reads are typed; everything else is left loose because the upstream
105
+ * type carries dozens of editor/audit fields the renderer doesn't care about.
106
+ */
107
+ interface ExperienceSys {
108
+ /**
109
+ * Optional page-level template reference. When present, the renderer wraps
110
+ * the experience nodes with the matching template registered in the
111
+ * customer's Config. When absent, nodes render at the top level.
112
+ */
113
+ template?: TemplateRef;
114
+ [key: string]: unknown;
115
+ }
116
+ /**
117
+ * Top-level Experience payload as returned by the Experience Delivery API
118
+ * (`GetExperienceViewResponse` from `@contentful/experience-delivery`).
119
+ *
120
+ * Structurally compatible with the upstream type — no normalization step
121
+ * required when consuming a delivery-client response.
122
+ */
123
+ interface ExperiencePayload {
124
+ viewports: ViewportDef[];
125
+ nodes: ExperienceNode[];
126
+ errors?: unknown[];
127
+ extensions?: unknown;
128
+ sys?: ExperienceSys;
129
+ }
130
+ /**
131
+ * Per-node context handed to a component's `resolveData` resolver. Carries
132
+ * the raw content + design props from the payload (design envelopes are NOT
133
+ * pre-resolved against a viewport — viewport resolution stays a render-time
134
+ * concern so client viewport changes don't re-trigger async resolvers).
135
+ */
136
+ interface ResolveContext {
137
+ content: Record<string, unknown>;
138
+ design: Record<string, DesignPropValue>;
139
+ experience: ExperienceContext;
140
+ }
141
+ /**
142
+ * Registration metadata for a single instance — the SDK's interpreted
143
+ * pointer to the customer's component implementation. Today carries only
144
+ * the resolved component-type id; capabilities (state requirements,
145
+ * supported events, lifecycle hints, fallback ids) land here when needed.
146
+ */
147
+ interface PortableRegistration {
148
+ componentTypeId: string;
149
+ }
150
+ /**
151
+ * The IR — one node per component instance. The seam that lets non-React
152
+ * adapters (Angular, SwiftUI, Compose) consume the same interpretation.
153
+ *
154
+ * Design props preserve the discriminated envelope as they arrived. Adapters
155
+ * unwrap to plain scalars at render time, given an active viewport.
156
+ * (DesignToken envelopes pass through unwrapped — customer components decide
157
+ * how to resolve them in v1.)
158
+ *
159
+ * `props.resolved` is populated by `resolveExperience` from any
160
+ * customer-supplied `resolveData` resolver and merged into the final prop bag
161
+ * after content + design but before slot props.
162
+ */
163
+ interface PortableRenderNode {
164
+ /**
165
+ * Optional. Passed through from the XDA payload's `id` field when the
166
+ * editor supplies one. The SDK does NOT auto-generate ids; adapters fall
167
+ * back to the array index for React keys / debug labels when absent.
168
+ */
169
+ nodeId?: string;
170
+ registration: PortableRegistration;
171
+ props: {
172
+ content: Record<string, unknown>;
173
+ design: Record<string, DesignPropValue>;
174
+ resolved?: Record<string, unknown>;
175
+ };
176
+ slots: Record<string, PortableRenderNode[]>;
177
+ }
178
+ /**
179
+ * Interpreted page-level template — the optional wrapper around the
180
+ * experience tree. `templateId` is extracted from
181
+ * `payload.sys.template.sys.urn` (last slash-segment).
182
+ *
183
+ * Templates carry the same prop-resolution shape as components: content +
184
+ * design envelopes plus an optional `resolved` bag from a `resolveData` hook.
185
+ * v1 payloads from XDA don't carry template-level content/design properties
186
+ * yet, but the IR makes room for them so the API doesn't need to break later.
187
+ */
188
+ interface PortableTemplate {
189
+ templateId: string;
190
+ props: {
191
+ content: Record<string, unknown>;
192
+ design: Record<string, DesignPropValue>;
193
+ resolved?: Record<string, unknown>;
194
+ };
195
+ }
196
+ /**
197
+ * The interpreted experience tree.
198
+ *
199
+ * Top-level is `nodes: PortableRenderNode[]` (array, not single root) to
200
+ * match the actual XDA payload shape. Renderers iterate top-level nodes
201
+ * and recurse into `node.slots`. When `template` is present, the renderer
202
+ * wraps the nodes with the matching template config; otherwise nodes
203
+ * render at the top level.
204
+ */
205
+ interface PortableRenderPlan {
206
+ viewports: ViewportDef[];
207
+ nodes: PortableRenderNode[];
208
+ template?: PortableTemplate;
209
+ }
210
+
211
+ export type { ComponentTypeNode, ComponentTypeRef, DesignPropValue, DesignToken, ExperienceContext, ExperienceNode, ExperiencePayload, ExperienceSys, ManualDesignValue, PortableRegistration, PortableRenderNode, PortableRenderPlan, PortableTemplate, ResolveContext, ResolveToken, TemplateNode, TemplateRef, ValuesByViewport, ViewportDef };
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@contentful/experiences-sdk-core",
3
+ "version": "0.5.2",
4
+ "description": "Runtime-neutral types + experience resolution for Contentful Experiences",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "sideEffects": false,
10
+ "exports": {
11
+ ".": {
12
+ "import": {
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.js"
15
+ }
16
+ },
17
+ "./package.json": "./package.json"
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "README.md",
22
+ "CHANGELOG.md"
23
+ ],
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/contentful/experiences.git",
30
+ "directory": "packages/core"
31
+ }
32
+ }