@fabricorg/experience-runtime 0.4.8 → 0.4.10
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 +33 -0
- package/dist/client.cjs +120 -137
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +19 -20
- package/dist/client.d.ts +19 -20
- package/dist/client.js +117 -110
- package/dist/client.js.map +1 -1
- package/dist/contracts.cjs +0 -19
- package/dist/contracts.d.cts +71 -71
- package/dist/contracts.d.ts +71 -71
- package/dist/contracts.js +1 -1
- package/dist/offline.cjs +307 -270
- package/dist/offline.cjs.map +1 -1
- package/dist/offline.d.cts +87 -88
- package/dist/offline.d.ts +87 -88
- package/dist/offline.js +305 -244
- package/dist/offline.js.map +1 -1
- package/dist/server.cjs +230 -225
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +88 -89
- package/dist/server.d.ts +88 -89
- package/dist/server.js +226 -200
- package/dist/server.js.map +1 -1
- package/package.json +8 -7
- package/dist/contracts.cjs.map +0 -1
- package/dist/contracts.js.map +0 -1
package/dist/server.js
CHANGED
|
@@ -1,219 +1,245 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
activateReleaseChannelPointer,
|
|
4
|
-
verifySignedExperienceRelease
|
|
5
|
-
} from "@fabricorg/sdui-release/contracts";
|
|
1
|
+
import { activateReleaseChannelPointer, verifySignedExperienceRelease } from "@fabricorg/sdui-release/contracts";
|
|
2
|
+
//#region server.ts
|
|
6
3
|
function experienceServerChecks() {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
];
|
|
4
|
+
return [
|
|
5
|
+
{
|
|
6
|
+
id: "fabric.experience.plan-opaque.v1",
|
|
7
|
+
async run(subject) {
|
|
8
|
+
const plan = await subject.runtime.issuePlan(subject.issue);
|
|
9
|
+
if (JSON.stringify(plan).includes("capability://")) throw new Error("Public render plan exposes a capability reference.");
|
|
10
|
+
return [plan.planId];
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
id: "fabric.experience.scope-isolation.v1",
|
|
15
|
+
async run(subject) {
|
|
16
|
+
const plan = await subject.runtime.issuePlan(subject.issue);
|
|
17
|
+
try {
|
|
18
|
+
await subject.runtime.resolveIntent({
|
|
19
|
+
planId: plan.planId,
|
|
20
|
+
...subject.intent,
|
|
21
|
+
scope: subject.wrongScope
|
|
22
|
+
});
|
|
23
|
+
throw new Error("Runtime accepted an intent from the wrong trusted scope.");
|
|
24
|
+
} catch (error) {
|
|
25
|
+
if (error instanceof Error && error.message === "Runtime accepted an intent from the wrong trusted scope.") throw error;
|
|
26
|
+
}
|
|
27
|
+
return ["wrong scope rejected"];
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: "fabric.experience.intent-resolution.v1",
|
|
32
|
+
async run(subject) {
|
|
33
|
+
const plan = await subject.runtime.issuePlan(subject.issue);
|
|
34
|
+
const resolved = await subject.runtime.resolveIntent({
|
|
35
|
+
planId: plan.planId,
|
|
36
|
+
...subject.intent,
|
|
37
|
+
scope: subject.issue.scope
|
|
38
|
+
});
|
|
39
|
+
return [`${resolved.actionId}@${resolved.actionVersion}`];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
];
|
|
47
43
|
}
|
|
48
44
|
async function runExperienceServerChecks(subject, checks = experienceServerChecks()) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
45
|
+
const results = [];
|
|
46
|
+
for (const check of checks) try {
|
|
47
|
+
results.push({
|
|
48
|
+
id: check.id,
|
|
49
|
+
status: "passed",
|
|
50
|
+
evidence: await check.run(subject)
|
|
51
|
+
});
|
|
52
|
+
} catch (error) {
|
|
53
|
+
results.push({
|
|
54
|
+
id: check.id,
|
|
55
|
+
status: "failed",
|
|
56
|
+
evidence: [],
|
|
57
|
+
error: error instanceof Error ? error.message : String(error)
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
passed: results.every((result) => result.status === "passed"),
|
|
62
|
+
checks: results
|
|
63
|
+
};
|
|
58
64
|
}
|
|
59
65
|
function createExperienceServerRuntime(options) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
66
|
+
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
67
|
+
return {
|
|
68
|
+
async issuePlan(input) {
|
|
69
|
+
const admitted = await options.resolveChannel({
|
|
70
|
+
application: input.application,
|
|
71
|
+
channel: input.channel
|
|
72
|
+
});
|
|
73
|
+
const pointer = await activateReleaseChannelPointer({
|
|
74
|
+
compactJws: admitted.pointerJws,
|
|
75
|
+
crypto: options.crypto,
|
|
76
|
+
trust: options.trust,
|
|
77
|
+
generations: options.generations,
|
|
78
|
+
expected: {
|
|
79
|
+
issuer: options.issuer,
|
|
80
|
+
application: input.application,
|
|
81
|
+
channel: input.channel
|
|
82
|
+
},
|
|
83
|
+
now: now()
|
|
84
|
+
});
|
|
85
|
+
const release = await verifySignedExperienceRelease({
|
|
86
|
+
compactJws: admitted.releaseJws,
|
|
87
|
+
crypto: options.crypto,
|
|
88
|
+
trust: options.trust,
|
|
89
|
+
expected: {
|
|
90
|
+
issuer: options.issuer,
|
|
91
|
+
application: input.application,
|
|
92
|
+
assemblyDigest: pointer.assemblyDigest
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
if (release.releaseDigest !== pointer.activeReleaseDigest) throw new Error("Selected experience release does not match the active release-channel pointer.");
|
|
96
|
+
const selected = selectTree(release, input.variantId);
|
|
97
|
+
const planId = options.nextOpaqueId();
|
|
98
|
+
if (!planId || /capability:\/\//.test(planId)) throw new Error("Experience plan id must be opaque.");
|
|
99
|
+
const intents = /* @__PURE__ */ new Map();
|
|
100
|
+
const views = /* @__PURE__ */ new Map();
|
|
101
|
+
const root = toRenderPlanFragment(selected.root, release, selected.treeId, intents, views);
|
|
102
|
+
const publicPlan = Object.freeze({
|
|
103
|
+
formatVersion: 1,
|
|
104
|
+
planId,
|
|
105
|
+
application: input.application,
|
|
106
|
+
channel: input.channel,
|
|
107
|
+
releaseDigest: release.releaseDigest,
|
|
108
|
+
assemblyDigest: release.assemblyDigest,
|
|
109
|
+
treeId: selected.treeId,
|
|
110
|
+
root,
|
|
111
|
+
tokenSet: {
|
|
112
|
+
name: release.tokenSet.name,
|
|
113
|
+
version: release.tokenSet.version
|
|
114
|
+
},
|
|
115
|
+
expiresAt: new Date(now().getTime() + (input.ttlMs ?? 3e5)).toISOString()
|
|
116
|
+
});
|
|
117
|
+
if (JSON.stringify(publicPlan).includes("capability://")) throw new Error("Public render plan exposes a capability reference.");
|
|
118
|
+
await options.store.save({
|
|
119
|
+
publicPlan,
|
|
120
|
+
scope: { ...input.scope },
|
|
121
|
+
intents,
|
|
122
|
+
views
|
|
123
|
+
});
|
|
124
|
+
return publicPlan;
|
|
125
|
+
},
|
|
126
|
+
async resolveIntent(input) {
|
|
127
|
+
const state = await options.store.load(input.planId);
|
|
128
|
+
if (!state) throw new Error("Experience render plan was not found.");
|
|
129
|
+
if (Date.parse(state.publicPlan.expiresAt) <= now().getTime()) throw new Error("Experience render plan has expired.");
|
|
130
|
+
if (!sameScope(state.scope, input.scope)) throw new Error("Experience render plan scope does not match trusted request scope.");
|
|
131
|
+
const intent = state.intents.get(`${input.fragmentId}\u0000${input.eventName}`);
|
|
132
|
+
if (!intent) throw new Error(`Fragment event "${input.fragmentId}.${input.eventName}" is not granted by the render plan.`);
|
|
133
|
+
return {
|
|
134
|
+
actionId: intent.route.actionId,
|
|
135
|
+
actionVersion: intent.route.version,
|
|
136
|
+
execution: intent.route.execution,
|
|
137
|
+
initiatingReleaseDigest: state.publicPlan.releaseDigest,
|
|
138
|
+
assemblyDigest: state.publicPlan.assemblyDigest
|
|
139
|
+
};
|
|
140
|
+
},
|
|
141
|
+
async resolveView(input) {
|
|
142
|
+
const state = await loadPlan(options.store, input.planId, input.scope, now());
|
|
143
|
+
const view = state.views.get(`${input.fragmentId}\u0000${input.bindingName}`);
|
|
144
|
+
if (!view) throw new Error(`Fragment binding "${input.fragmentId}.${input.bindingName}" is not granted by the render plan.`);
|
|
145
|
+
return {
|
|
146
|
+
name: view.route.name,
|
|
147
|
+
version: view.route.version,
|
|
148
|
+
initiatingReleaseDigest: state.publicPlan.releaseDigest,
|
|
149
|
+
assemblyDigest: state.publicPlan.assemblyDigest
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
};
|
|
136
153
|
}
|
|
137
154
|
function selectTree(release, variantId) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
155
|
+
if (variantId === void 0) return {
|
|
156
|
+
treeId: "root",
|
|
157
|
+
root: release.document.root
|
|
158
|
+
};
|
|
159
|
+
const variant = release.document.variants?.find((candidate) => candidate.id === variantId);
|
|
160
|
+
if (!variant) throw new Error(`Variant "${variantId}" is not enumerated by the promoted release.`);
|
|
161
|
+
return {
|
|
162
|
+
treeId: variant.id,
|
|
163
|
+
root: variant.root
|
|
164
|
+
};
|
|
142
165
|
}
|
|
143
166
|
function toRenderPlanFragment(fragment, release, treeId, intents, views) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
167
|
+
const effective = release.effectiveGrants.find((grant) => grant.treeId === treeId && grant.fragmentId === fragment.id);
|
|
168
|
+
if (!effective) throw new Error(`Promoted release has no effective grant for "${treeId}:${fragment.id}".`);
|
|
169
|
+
const events = [];
|
|
170
|
+
const offlineEvents = [];
|
|
171
|
+
const bindings = [];
|
|
172
|
+
if (fragment.dataRef !== void 0) {
|
|
173
|
+
if (!effective.views.includes(fragment.dataRef)) throw new Error(`Fragment data binding "${fragment.id}" exceeds its effective grants.`);
|
|
174
|
+
const route = release.routes.views.find((candidate) => candidate.reference === fragment.dataRef);
|
|
175
|
+
if (!route) throw new Error(`Fragment data binding "${fragment.id}" has no compiled view route.`);
|
|
176
|
+
views.set(`${fragment.id}\u0000$data`, {
|
|
177
|
+
fragmentId: fragment.id,
|
|
178
|
+
bindingName: "$data",
|
|
179
|
+
route
|
|
180
|
+
});
|
|
181
|
+
bindings.push("$data");
|
|
182
|
+
}
|
|
183
|
+
for (const [eventName, action] of Object.entries(fragment.actions ?? {})) {
|
|
184
|
+
if (!effective.intents.includes(action.intent)) throw new Error(`Fragment event "${fragment.id}.${eventName}" exceeds its effective grants.`);
|
|
185
|
+
const route = release.routes.intents.find((candidate) => candidate.reference === action.intent);
|
|
186
|
+
if (!route) throw new Error(`Fragment event "${fragment.id}.${eventName}" has no compiled intent route.`);
|
|
187
|
+
intents.set(`${fragment.id}\u0000${eventName}`, {
|
|
188
|
+
fragmentId: fragment.id,
|
|
189
|
+
eventName,
|
|
190
|
+
route
|
|
191
|
+
});
|
|
192
|
+
events.push(eventName);
|
|
193
|
+
if (route.execution?.connectivity === "offline-capturable") offlineEvents.push(eventName);
|
|
194
|
+
}
|
|
195
|
+
return Object.freeze({
|
|
196
|
+
id: fragment.id,
|
|
197
|
+
...fragment.component === void 0 ? {} : { component: fragment.component },
|
|
198
|
+
...fragment.props === void 0 ? {} : { props: stripCapabilityReferences(fragment.props) },
|
|
199
|
+
...fragment.slot === void 0 ? {} : { slot: fragment.slot },
|
|
200
|
+
...events.length === 0 ? {} : { events: Object.freeze(events.sort()) },
|
|
201
|
+
...offlineEvents.length === 0 ? {} : { offlineEvents: Object.freeze(offlineEvents.sort()) },
|
|
202
|
+
...bindings.length === 0 ? {} : { bindings: Object.freeze(bindings) },
|
|
203
|
+
...fragment.children === void 0 ? {} : { children: Object.freeze(fragment.children.map((child) => toRenderPlanFragment(child, release, treeId, intents, views))) }
|
|
204
|
+
});
|
|
176
205
|
}
|
|
177
206
|
async function loadPlan(store, planId, scope, now) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
207
|
+
const state = await store.load(planId);
|
|
208
|
+
if (!state) throw new Error("Experience render plan was not found.");
|
|
209
|
+
if (Date.parse(state.publicPlan.expiresAt) <= now.getTime()) throw new Error("Experience render plan has expired.");
|
|
210
|
+
if (!sameScope(state.scope, scope)) throw new Error("Experience render plan scope does not match trusted request scope.");
|
|
211
|
+
return state;
|
|
183
212
|
}
|
|
184
213
|
function stripCapabilityReferences(value) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
214
|
+
const strip = (candidate) => {
|
|
215
|
+
if (Array.isArray(candidate)) return candidate.map(strip);
|
|
216
|
+
if (candidate && typeof candidate === "object") {
|
|
217
|
+
if ("$ref" in candidate) return void 0;
|
|
218
|
+
return Object.fromEntries(Object.entries(candidate).flatMap(([key, entry]) => {
|
|
219
|
+
const stripped = strip(entry);
|
|
220
|
+
return stripped === void 0 ? [] : [[key, stripped]];
|
|
221
|
+
}));
|
|
222
|
+
}
|
|
223
|
+
return candidate;
|
|
224
|
+
};
|
|
225
|
+
return strip(value);
|
|
197
226
|
}
|
|
198
227
|
function sameScope(left, right) {
|
|
199
|
-
|
|
228
|
+
return left.tenantId === right.tenantId && left.spaceId === right.spaceId && left.actorId === right.actorId && left.actorType === right.actorType;
|
|
200
229
|
}
|
|
201
230
|
function createMemoryExperiencePlanStore() {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
231
|
+
const plans = /* @__PURE__ */ new Map();
|
|
232
|
+
return {
|
|
233
|
+
async save(state) {
|
|
234
|
+
if (plans.has(state.publicPlan.planId)) throw new Error(`Experience render plan "${state.publicPlan.planId}" already exists.`);
|
|
235
|
+
plans.set(state.publicPlan.planId, state);
|
|
236
|
+
},
|
|
237
|
+
async load(planId) {
|
|
238
|
+
return plans.get(planId) ?? null;
|
|
239
|
+
}
|
|
240
|
+
};
|
|
212
241
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
experienceServerChecks,
|
|
217
|
-
runExperienceServerChecks
|
|
218
|
-
};
|
|
242
|
+
//#endregion
|
|
243
|
+
export { createExperienceServerRuntime, createMemoryExperiencePlanStore, experienceServerChecks, runExperienceServerChecks };
|
|
244
|
+
|
|
219
245
|
//# sourceMappingURL=server.js.map
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../server.ts"],"sourcesContent":["import type {\n\tReleaseCryptoPort,\n\tReleaseGenerationStore,\n\tReleaseTrustStore,\n\tExperienceIntentRoute,\n\tExperienceViewRoute,\n\tSduiFragment,\n\tSduiFragmentValue,\n\tSduiReleaseV3,\n} from \"@fabricorg/sdui-release/contracts\";\nimport {\n\tactivateReleaseChannelPointer,\n\tverifySignedExperienceRelease,\n} from \"@fabricorg/sdui-release/contracts\";\nimport type {\n\tExperienceScope,\n\tRenderPlanFragment,\n\tScopedRenderPlan,\n} from \"./contracts\";\n\nexport type {\n\tExperienceScope,\n\tRenderPlanFragment,\n\tScopedRenderPlan,\n} from \"./contracts\";\n\nexport interface ExperiencePlanState {\n\tpublicPlan: ScopedRenderPlan;\n\tscope: ExperienceScope;\n\tintents: ReadonlyMap<string, {\n\t\tfragmentId: string;\n\t\teventName: string;\n\t\troute: ExperienceIntentRoute;\n\t}>;\n\tviews: ReadonlyMap<string, {\n\t\tfragmentId: string;\n\t\tbindingName: string;\n\t\troute: ExperienceViewRoute;\n\t}>;\n}\n\nexport interface ExperiencePlanStore {\n\tsave(state: ExperiencePlanState): Promise<void>;\n\tload(planId: string): Promise<ExperiencePlanState | null>;\n}\n\nexport interface IssueRenderPlanInput {\n\tapplication: string;\n\tchannel: string;\n\tscope: ExperienceScope;\n\tvariantId?: string;\n\tttlMs?: number;\n}\n\nexport interface ResolveIntentInput {\n\tplanId: string;\n\tfragmentId: string;\n\teventName: string;\n\tscope: ExperienceScope;\n}\n\nexport interface ResolvedExperienceIntent {\n\tactionId: `${string}.${string}`;\n\tactionVersion: number;\n\texecution: ExperienceIntentRoute[\"execution\"];\n\tinitiatingReleaseDigest: string;\n\tassemblyDigest: string;\n}\n\nexport interface ResolvedExperienceView {\n\tname: `${string}/${string}`;\n\tversion: string;\n\tinitiatingReleaseDigest: string;\n\tassemblyDigest: string;\n}\n\nexport interface ExperienceServerRuntime {\n\tissuePlan(input: IssueRenderPlanInput): Promise<ScopedRenderPlan>;\n\tresolveIntent(input: ResolveIntentInput): Promise<ResolvedExperienceIntent>;\n\tresolveView(input: {\n\t\tplanId: string;\n\t\tfragmentId: string;\n\t\tbindingName: string;\n\t\tscope: ExperienceScope;\n\t}): Promise<ResolvedExperienceView>;\n}\n\nexport interface ExperienceServerConformanceSubject {\n\truntime: ExperienceServerRuntime;\n\tissue: IssueRenderPlanInput;\n\tintent: { fragmentId: string; eventName: string };\n\twrongScope: ExperienceScope;\n}\n\nexport interface ExperienceServerCheck {\n\tid: \"fabric.experience.plan-opaque.v1\" | \"fabric.experience.scope-isolation.v1\" | \"fabric.experience.intent-resolution.v1\";\n\trun(subject: ExperienceServerConformanceSubject): Promise<string[]>;\n}\n\nexport function experienceServerChecks(): readonly ExperienceServerCheck[] {\n\treturn [\n\t\t{\n\t\t\tid: \"fabric.experience.plan-opaque.v1\",\n\t\t\tasync run(subject) {\n\t\t\t\tconst plan = await subject.runtime.issuePlan(subject.issue);\n\t\t\t\tconst serialized = JSON.stringify(plan);\n\t\t\t\tif (serialized.includes(\"capability://\")) throw new Error(\"Public render plan exposes a capability reference.\");\n\t\t\t\treturn [plan.planId];\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: \"fabric.experience.scope-isolation.v1\",\n\t\t\tasync run(subject) {\n\t\t\t\tconst plan = await subject.runtime.issuePlan(subject.issue);\n\t\t\t\ttry {\n\t\t\t\t\tawait subject.runtime.resolveIntent({\n\t\t\t\t\t\tplanId: plan.planId,\n\t\t\t\t\t\t...subject.intent,\n\t\t\t\t\t\tscope: subject.wrongScope,\n\t\t\t\t\t});\n\t\t\t\t\tthrow new Error(\"Runtime accepted an intent from the wrong trusted scope.\");\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error instanceof Error && error.message === \"Runtime accepted an intent from the wrong trusted scope.\") throw error;\n\t\t\t\t}\n\t\t\t\treturn [\"wrong scope rejected\"];\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: \"fabric.experience.intent-resolution.v1\",\n\t\t\tasync run(subject) {\n\t\t\t\tconst plan = await subject.runtime.issuePlan(subject.issue);\n\t\t\t\tconst resolved = await subject.runtime.resolveIntent({\n\t\t\t\t\tplanId: plan.planId,\n\t\t\t\t\t...subject.intent,\n\t\t\t\t\tscope: subject.issue.scope,\n\t\t\t\t});\n\t\t\t\treturn [`${resolved.actionId}@${resolved.actionVersion}`];\n\t\t\t},\n\t\t},\n\t];\n}\n\nexport async function runExperienceServerChecks(\n\tsubject: ExperienceServerConformanceSubject,\n\tchecks: readonly ExperienceServerCheck[] = experienceServerChecks(),\n): Promise<{\n\tpassed: boolean;\n\tchecks: Array<{ id: ExperienceServerCheck[\"id\"]; status: \"passed\" | \"failed\"; evidence: string[]; error?: string }>;\n}> {\n\tconst results: Array<{ id: ExperienceServerCheck[\"id\"]; status: \"passed\" | \"failed\"; evidence: string[]; error?: string }> = [];\n\tfor (const check of checks) {\n\t\ttry {\n\t\t\tresults.push({ id: check.id, status: \"passed\", evidence: await check.run(subject) });\n\t\t} catch (error) {\n\t\t\tresults.push({ id: check.id, status: \"failed\", evidence: [], error: error instanceof Error ? error.message : String(error) });\n\t\t}\n\t}\n\treturn { passed: results.every((result) => result.status === \"passed\"), checks: results };\n}\n\nexport function createExperienceServerRuntime(options: {\n\tstore: ExperiencePlanStore;\n\tresolveChannel(input: { application: string; channel: string }): Promise<{\n\t\tpointerJws: string;\n\t\treleaseJws: string;\n\t}>;\n\tissuer: string;\n\tcrypto: ReleaseCryptoPort;\n\ttrust: ReleaseTrustStore;\n\tgenerations: ReleaseGenerationStore;\n\tnextOpaqueId(): string;\n\tnow?: () => Date;\n}): ExperienceServerRuntime {\n\tconst now = options.now ?? (() => new Date());\n\treturn {\n\t\tasync issuePlan(input) {\n\t\t\tconst admitted = await options.resolveChannel({ application: input.application, channel: input.channel });\n\t\t\tconst pointer = await activateReleaseChannelPointer({\n\t\t\t\tcompactJws: admitted.pointerJws,\n\t\t\t\tcrypto: options.crypto,\n\t\t\t\ttrust: options.trust,\n\t\t\t\tgenerations: options.generations,\n\t\t\t\texpected: { issuer: options.issuer, application: input.application, channel: input.channel },\n\t\t\t\tnow: now(),\n\t\t\t});\n\t\t\tconst release = await verifySignedExperienceRelease({\n\t\t\t\tcompactJws: admitted.releaseJws,\n\t\t\t\tcrypto: options.crypto,\n\t\t\t\ttrust: options.trust,\n\t\t\t\texpected: {\n\t\t\t\t\tissuer: options.issuer,\n\t\t\t\t\tapplication: input.application,\n\t\t\t\t\tassemblyDigest: pointer.assemblyDigest,\n\t\t\t\t},\n\t\t\t});\n\t\t\tif (release.releaseDigest !== pointer.activeReleaseDigest) {\n\t\t\t\tthrow new Error(\"Selected experience release does not match the active release-channel pointer.\");\n\t\t\t}\n\t\t\tconst selected = selectTree(release, input.variantId);\n\t\t\tconst planId = options.nextOpaqueId();\n\t\t\tif (!planId || /capability:\\/\\//.test(planId)) throw new Error(\"Experience plan id must be opaque.\");\n\t\t\tconst intents = new Map<string, ExperiencePlanState[\"intents\"] extends ReadonlyMap<string, infer Value> ? Value : never>();\n\t\t\tconst views = new Map<string, ExperiencePlanState[\"views\"] extends ReadonlyMap<string, infer Value> ? Value : never>();\n\t\t\tconst root = toRenderPlanFragment(selected.root, release, selected.treeId, intents, views);\n\t\t\tconst publicPlan: ScopedRenderPlan = Object.freeze({\n\t\t\t\tformatVersion: 1,\n\t\t\t\tplanId,\n\t\t\t\tapplication: input.application,\n\t\t\t\tchannel: input.channel,\n\t\t\t\treleaseDigest: release.releaseDigest,\n\t\t\t\tassemblyDigest: release.assemblyDigest,\n\t\t\t\ttreeId: selected.treeId,\n\t\t\t\troot,\n\t\t\t\ttokenSet: { name: release.tokenSet.name, version: release.tokenSet.version },\n\t\t\t\texpiresAt: new Date(now().getTime() + (input.ttlMs ?? 300_000)).toISOString(),\n\t\t\t});\n\t\t\tif (JSON.stringify(publicPlan).includes(\"capability://\")) {\n\t\t\t\tthrow new Error(\"Public render plan exposes a capability reference.\");\n\t\t\t}\n\t\t\tawait options.store.save({ publicPlan, scope: { ...input.scope }, intents, views });\n\t\t\treturn publicPlan;\n\t\t},\n\t\tasync resolveIntent(input) {\n\t\t\tconst state = await options.store.load(input.planId);\n\t\t\tif (!state) throw new Error(\"Experience render plan was not found.\");\n\t\t\tif (Date.parse(state.publicPlan.expiresAt) <= now().getTime()) throw new Error(\"Experience render plan has expired.\");\n\t\t\tif (!sameScope(state.scope, input.scope)) throw new Error(\"Experience render plan scope does not match trusted request scope.\");\n\t\t\tconst intent = state.intents.get(`${input.fragmentId}\\u0000${input.eventName}`);\n\t\t\tif (!intent) throw new Error(`Fragment event \"${input.fragmentId}.${input.eventName}\" is not granted by the render plan.`);\n\t\t\treturn {\n\t\t\t\tactionId: intent.route.actionId,\n\t\t\t\tactionVersion: intent.route.version,\n\t\t\t\texecution: intent.route.execution,\n\t\t\t\tinitiatingReleaseDigest: state.publicPlan.releaseDigest,\n\t\t\t\tassemblyDigest: state.publicPlan.assemblyDigest,\n\t\t\t};\n\t\t},\n\t\tasync resolveView(input) {\n\t\t\tconst state = await loadPlan(options.store, input.planId, input.scope, now());\n\t\t\tconst view = state.views.get(`${input.fragmentId}\\u0000${input.bindingName}`);\n\t\t\tif (!view) throw new Error(`Fragment binding \"${input.fragmentId}.${input.bindingName}\" is not granted by the render plan.`);\n\t\t\treturn {\n\t\t\t\tname: view.route.name,\n\t\t\t\tversion: view.route.version,\n\t\t\t\tinitiatingReleaseDigest: state.publicPlan.releaseDigest,\n\t\t\t\tassemblyDigest: state.publicPlan.assemblyDigest,\n\t\t\t};\n\t\t},\n\t};\n}\n\nfunction selectTree(release: SduiReleaseV3, variantId?: string): { treeId: string; root: SduiFragment } {\n\tif (variantId === undefined) return { treeId: \"root\", root: release.document.root };\n\tconst variant = release.document.variants?.find((candidate) => candidate.id === variantId);\n\tif (!variant) throw new Error(`Variant \"${variantId}\" is not enumerated by the promoted release.`);\n\treturn { treeId: variant.id, root: variant.root };\n}\n\nfunction toRenderPlanFragment(\n\tfragment: SduiFragment,\n\trelease: SduiReleaseV3,\n\ttreeId: string,\n\tintents: Map<string, { fragmentId: string; eventName: string; route: ExperienceIntentRoute }>,\n\tviews: Map<string, { fragmentId: string; bindingName: string; route: ExperienceViewRoute }>,\n): RenderPlanFragment {\n\tconst effective = release.effectiveGrants.find((grant) => grant.treeId === treeId && grant.fragmentId === fragment.id);\n\tif (!effective) throw new Error(`Promoted release has no effective grant for \"${treeId}:${fragment.id}\".`);\n\tconst events: string[] = [];\n\tconst offlineEvents: string[] = [];\n\tconst bindings: string[] = [];\n\tif (fragment.dataRef !== undefined) {\n\t\tif (!effective.views.includes(fragment.dataRef)) throw new Error(`Fragment data binding \"${fragment.id}\" exceeds its effective grants.`);\n\t\tconst route = release.routes.views.find((candidate) => candidate.reference === fragment.dataRef);\n\t\tif (!route) throw new Error(`Fragment data binding \"${fragment.id}\" has no compiled view route.`);\n\t\tviews.set(`${fragment.id}\\u0000$data`, { fragmentId: fragment.id, bindingName: \"$data\", route });\n\t\tbindings.push(\"$data\");\n\t}\n\tfor (const [eventName, action] of Object.entries(fragment.actions ?? {})) {\n\t\tif (!effective.intents.includes(action.intent)) throw new Error(`Fragment event \"${fragment.id}.${eventName}\" exceeds its effective grants.`);\n\t\tconst route = release.routes.intents.find((candidate) => candidate.reference === action.intent);\n\t\tif (!route) throw new Error(`Fragment event \"${fragment.id}.${eventName}\" has no compiled intent route.`);\n\t\tintents.set(`${fragment.id}\\u0000${eventName}`, { fragmentId: fragment.id, eventName, route });\n\t\tevents.push(eventName);\n\t\tif (route.execution?.connectivity === \"offline-capturable\") offlineEvents.push(eventName);\n\t}\n\treturn Object.freeze({\n\t\tid: fragment.id,\n\t\t...(fragment.component === undefined ? {} : { component: fragment.component }),\n\t\t...(fragment.props === undefined ? {} : { props: stripCapabilityReferences(fragment.props) }),\n\t\t...(fragment.slot === undefined ? {} : { slot: fragment.slot }),\n\t\t...(events.length === 0 ? {} : { events: Object.freeze(events.sort()) }),\n\t\t...(offlineEvents.length === 0 ? {} : { offlineEvents: Object.freeze(offlineEvents.sort()) }),\n\t\t...(bindings.length === 0 ? {} : { bindings: Object.freeze(bindings) }),\n\t\t...(fragment.children === undefined ? {} : {\n\t\t\tchildren: Object.freeze(fragment.children.map((child) => toRenderPlanFragment(child, release, treeId, intents, views))),\n\t\t}),\n\t});\n}\n\nasync function loadPlan(\n\tstore: ExperiencePlanStore,\n\tplanId: string,\n\tscope: ExperienceScope,\n\tnow: Date,\n): Promise<ExperiencePlanState> {\n\tconst state = await store.load(planId);\n\tif (!state) throw new Error(\"Experience render plan was not found.\");\n\tif (Date.parse(state.publicPlan.expiresAt) <= now.getTime()) throw new Error(\"Experience render plan has expired.\");\n\tif (!sameScope(state.scope, scope)) throw new Error(\"Experience render plan scope does not match trusted request scope.\");\n\treturn state;\n}\n\nfunction stripCapabilityReferences(value: Record<string, SduiFragmentValue>): Record<string, unknown> {\n\tconst strip = (candidate: SduiFragmentValue): unknown => {\n\t\tif (Array.isArray(candidate)) return candidate.map(strip);\n\t\tif (candidate && typeof candidate === \"object\") {\n\t\t\tif (\"$ref\" in candidate) return undefined;\n\t\t\treturn Object.fromEntries(Object.entries(candidate).flatMap(([key, entry]) => {\n\t\t\t\tconst stripped = strip(entry);\n\t\t\t\treturn stripped === undefined ? [] : [[key, stripped]];\n\t\t\t}));\n\t\t}\n\t\treturn candidate;\n\t};\n\treturn strip(value) as Record<string, unknown>;\n}\n\nfunction sameScope(left: ExperienceScope, right: ExperienceScope): boolean {\n\treturn left.tenantId === right.tenantId\n\t\t&& left.spaceId === right.spaceId\n\t\t&& left.actorId === right.actorId\n\t\t&& left.actorType === right.actorType;\n}\n\nexport function createMemoryExperiencePlanStore(): ExperiencePlanStore {\n\tconst plans = new Map<string, ExperiencePlanState>();\n\treturn {\n\t\tasync save(state) {\n\t\t\tif (plans.has(state.publicPlan.planId)) throw new Error(`Experience render plan \"${state.publicPlan.planId}\" already exists.`);\n\t\t\tplans.set(state.publicPlan.planId, state);\n\t\t},\n\t\tasync load(planId) {\n\t\t\treturn plans.get(planId) ?? null;\n\t\t},\n\t};\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"file":"server.js","names":[],"sources":["../server.ts"],"sourcesContent":["import type {\n\tReleaseCryptoPort,\n\tReleaseGenerationStore,\n\tReleaseTrustStore,\n\tExperienceIntentRoute,\n\tExperienceViewRoute,\n\tSduiFragment,\n\tSduiFragmentValue,\n\tSduiReleaseV3,\n} from \"@fabricorg/sdui-release/contracts\";\nimport {\n\tactivateReleaseChannelPointer,\n\tverifySignedExperienceRelease,\n} from \"@fabricorg/sdui-release/contracts\";\nimport type {\n\tExperienceScope,\n\tRenderPlanFragment,\n\tScopedRenderPlan,\n} from \"./contracts\";\n\nexport type {\n\tExperienceScope,\n\tRenderPlanFragment,\n\tScopedRenderPlan,\n} from \"./contracts\";\n\nexport interface ExperiencePlanState {\n\tpublicPlan: ScopedRenderPlan;\n\tscope: ExperienceScope;\n\tintents: ReadonlyMap<string, {\n\t\tfragmentId: string;\n\t\teventName: string;\n\t\troute: ExperienceIntentRoute;\n\t}>;\n\tviews: ReadonlyMap<string, {\n\t\tfragmentId: string;\n\t\tbindingName: string;\n\t\troute: ExperienceViewRoute;\n\t}>;\n}\n\nexport interface ExperiencePlanStore {\n\tsave(state: ExperiencePlanState): Promise<void>;\n\tload(planId: string): Promise<ExperiencePlanState | null>;\n}\n\nexport interface IssueRenderPlanInput {\n\tapplication: string;\n\tchannel: string;\n\tscope: ExperienceScope;\n\tvariantId?: string;\n\tttlMs?: number;\n}\n\nexport interface ResolveIntentInput {\n\tplanId: string;\n\tfragmentId: string;\n\teventName: string;\n\tscope: ExperienceScope;\n}\n\nexport interface ResolvedExperienceIntent {\n\tactionId: `${string}.${string}`;\n\tactionVersion: number;\n\texecution: ExperienceIntentRoute[\"execution\"];\n\tinitiatingReleaseDigest: string;\n\tassemblyDigest: string;\n}\n\nexport interface ResolvedExperienceView {\n\tname: `${string}/${string}`;\n\tversion: string;\n\tinitiatingReleaseDigest: string;\n\tassemblyDigest: string;\n}\n\nexport interface ExperienceServerRuntime {\n\tissuePlan(input: IssueRenderPlanInput): Promise<ScopedRenderPlan>;\n\tresolveIntent(input: ResolveIntentInput): Promise<ResolvedExperienceIntent>;\n\tresolveView(input: {\n\t\tplanId: string;\n\t\tfragmentId: string;\n\t\tbindingName: string;\n\t\tscope: ExperienceScope;\n\t}): Promise<ResolvedExperienceView>;\n}\n\nexport interface ExperienceServerConformanceSubject {\n\truntime: ExperienceServerRuntime;\n\tissue: IssueRenderPlanInput;\n\tintent: { fragmentId: string; eventName: string };\n\twrongScope: ExperienceScope;\n}\n\nexport interface ExperienceServerCheck {\n\tid: \"fabric.experience.plan-opaque.v1\" | \"fabric.experience.scope-isolation.v1\" | \"fabric.experience.intent-resolution.v1\";\n\trun(subject: ExperienceServerConformanceSubject): Promise<string[]>;\n}\n\nexport function experienceServerChecks(): readonly ExperienceServerCheck[] {\n\treturn [\n\t\t{\n\t\t\tid: \"fabric.experience.plan-opaque.v1\",\n\t\t\tasync run(subject) {\n\t\t\t\tconst plan = await subject.runtime.issuePlan(subject.issue);\n\t\t\t\tconst serialized = JSON.stringify(plan);\n\t\t\t\tif (serialized.includes(\"capability://\")) throw new Error(\"Public render plan exposes a capability reference.\");\n\t\t\t\treturn [plan.planId];\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: \"fabric.experience.scope-isolation.v1\",\n\t\t\tasync run(subject) {\n\t\t\t\tconst plan = await subject.runtime.issuePlan(subject.issue);\n\t\t\t\ttry {\n\t\t\t\t\tawait subject.runtime.resolveIntent({\n\t\t\t\t\t\tplanId: plan.planId,\n\t\t\t\t\t\t...subject.intent,\n\t\t\t\t\t\tscope: subject.wrongScope,\n\t\t\t\t\t});\n\t\t\t\t\tthrow new Error(\"Runtime accepted an intent from the wrong trusted scope.\");\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error instanceof Error && error.message === \"Runtime accepted an intent from the wrong trusted scope.\") throw error;\n\t\t\t\t}\n\t\t\t\treturn [\"wrong scope rejected\"];\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: \"fabric.experience.intent-resolution.v1\",\n\t\t\tasync run(subject) {\n\t\t\t\tconst plan = await subject.runtime.issuePlan(subject.issue);\n\t\t\t\tconst resolved = await subject.runtime.resolveIntent({\n\t\t\t\t\tplanId: plan.planId,\n\t\t\t\t\t...subject.intent,\n\t\t\t\t\tscope: subject.issue.scope,\n\t\t\t\t});\n\t\t\t\treturn [`${resolved.actionId}@${resolved.actionVersion}`];\n\t\t\t},\n\t\t},\n\t];\n}\n\nexport async function runExperienceServerChecks(\n\tsubject: ExperienceServerConformanceSubject,\n\tchecks: readonly ExperienceServerCheck[] = experienceServerChecks(),\n): Promise<{\n\tpassed: boolean;\n\tchecks: Array<{ id: ExperienceServerCheck[\"id\"]; status: \"passed\" | \"failed\"; evidence: string[]; error?: string }>;\n}> {\n\tconst results: Array<{ id: ExperienceServerCheck[\"id\"]; status: \"passed\" | \"failed\"; evidence: string[]; error?: string }> = [];\n\tfor (const check of checks) {\n\t\ttry {\n\t\t\tresults.push({ id: check.id, status: \"passed\", evidence: await check.run(subject) });\n\t\t} catch (error) {\n\t\t\tresults.push({ id: check.id, status: \"failed\", evidence: [], error: error instanceof Error ? error.message : String(error) });\n\t\t}\n\t}\n\treturn { passed: results.every((result) => result.status === \"passed\"), checks: results };\n}\n\nexport function createExperienceServerRuntime(options: {\n\tstore: ExperiencePlanStore;\n\tresolveChannel(input: { application: string; channel: string }): Promise<{\n\t\tpointerJws: string;\n\t\treleaseJws: string;\n\t}>;\n\tissuer: string;\n\tcrypto: ReleaseCryptoPort;\n\ttrust: ReleaseTrustStore;\n\tgenerations: ReleaseGenerationStore;\n\tnextOpaqueId(): string;\n\tnow?: () => Date;\n}): ExperienceServerRuntime {\n\tconst now = options.now ?? (() => new Date());\n\treturn {\n\t\tasync issuePlan(input) {\n\t\t\tconst admitted = await options.resolveChannel({ application: input.application, channel: input.channel });\n\t\t\tconst pointer = await activateReleaseChannelPointer({\n\t\t\t\tcompactJws: admitted.pointerJws,\n\t\t\t\tcrypto: options.crypto,\n\t\t\t\ttrust: options.trust,\n\t\t\t\tgenerations: options.generations,\n\t\t\t\texpected: { issuer: options.issuer, application: input.application, channel: input.channel },\n\t\t\t\tnow: now(),\n\t\t\t});\n\t\t\tconst release = await verifySignedExperienceRelease({\n\t\t\t\tcompactJws: admitted.releaseJws,\n\t\t\t\tcrypto: options.crypto,\n\t\t\t\ttrust: options.trust,\n\t\t\t\texpected: {\n\t\t\t\t\tissuer: options.issuer,\n\t\t\t\t\tapplication: input.application,\n\t\t\t\t\tassemblyDigest: pointer.assemblyDigest,\n\t\t\t\t},\n\t\t\t});\n\t\t\tif (release.releaseDigest !== pointer.activeReleaseDigest) {\n\t\t\t\tthrow new Error(\"Selected experience release does not match the active release-channel pointer.\");\n\t\t\t}\n\t\t\tconst selected = selectTree(release, input.variantId);\n\t\t\tconst planId = options.nextOpaqueId();\n\t\t\tif (!planId || /capability:\\/\\//.test(planId)) throw new Error(\"Experience plan id must be opaque.\");\n\t\t\tconst intents = new Map<string, ExperiencePlanState[\"intents\"] extends ReadonlyMap<string, infer Value> ? Value : never>();\n\t\t\tconst views = new Map<string, ExperiencePlanState[\"views\"] extends ReadonlyMap<string, infer Value> ? Value : never>();\n\t\t\tconst root = toRenderPlanFragment(selected.root, release, selected.treeId, intents, views);\n\t\t\tconst publicPlan: ScopedRenderPlan = Object.freeze({\n\t\t\t\tformatVersion: 1,\n\t\t\t\tplanId,\n\t\t\t\tapplication: input.application,\n\t\t\t\tchannel: input.channel,\n\t\t\t\treleaseDigest: release.releaseDigest,\n\t\t\t\tassemblyDigest: release.assemblyDigest,\n\t\t\t\ttreeId: selected.treeId,\n\t\t\t\troot,\n\t\t\t\ttokenSet: { name: release.tokenSet.name, version: release.tokenSet.version },\n\t\t\t\texpiresAt: new Date(now().getTime() + (input.ttlMs ?? 300_000)).toISOString(),\n\t\t\t});\n\t\t\tif (JSON.stringify(publicPlan).includes(\"capability://\")) {\n\t\t\t\tthrow new Error(\"Public render plan exposes a capability reference.\");\n\t\t\t}\n\t\t\tawait options.store.save({ publicPlan, scope: { ...input.scope }, intents, views });\n\t\t\treturn publicPlan;\n\t\t},\n\t\tasync resolveIntent(input) {\n\t\t\tconst state = await options.store.load(input.planId);\n\t\t\tif (!state) throw new Error(\"Experience render plan was not found.\");\n\t\t\tif (Date.parse(state.publicPlan.expiresAt) <= now().getTime()) throw new Error(\"Experience render plan has expired.\");\n\t\t\tif (!sameScope(state.scope, input.scope)) throw new Error(\"Experience render plan scope does not match trusted request scope.\");\n\t\t\tconst intent = state.intents.get(`${input.fragmentId}\\u0000${input.eventName}`);\n\t\t\tif (!intent) throw new Error(`Fragment event \"${input.fragmentId}.${input.eventName}\" is not granted by the render plan.`);\n\t\t\treturn {\n\t\t\t\tactionId: intent.route.actionId,\n\t\t\t\tactionVersion: intent.route.version,\n\t\t\t\texecution: intent.route.execution,\n\t\t\t\tinitiatingReleaseDigest: state.publicPlan.releaseDigest,\n\t\t\t\tassemblyDigest: state.publicPlan.assemblyDigest,\n\t\t\t};\n\t\t},\n\t\tasync resolveView(input) {\n\t\t\tconst state = await loadPlan(options.store, input.planId, input.scope, now());\n\t\t\tconst view = state.views.get(`${input.fragmentId}\\u0000${input.bindingName}`);\n\t\t\tif (!view) throw new Error(`Fragment binding \"${input.fragmentId}.${input.bindingName}\" is not granted by the render plan.`);\n\t\t\treturn {\n\t\t\t\tname: view.route.name,\n\t\t\t\tversion: view.route.version,\n\t\t\t\tinitiatingReleaseDigest: state.publicPlan.releaseDigest,\n\t\t\t\tassemblyDigest: state.publicPlan.assemblyDigest,\n\t\t\t};\n\t\t},\n\t};\n}\n\nfunction selectTree(release: SduiReleaseV3, variantId?: string): { treeId: string; root: SduiFragment } {\n\tif (variantId === undefined) return { treeId: \"root\", root: release.document.root };\n\tconst variant = release.document.variants?.find((candidate) => candidate.id === variantId);\n\tif (!variant) throw new Error(`Variant \"${variantId}\" is not enumerated by the promoted release.`);\n\treturn { treeId: variant.id, root: variant.root };\n}\n\nfunction toRenderPlanFragment(\n\tfragment: SduiFragment,\n\trelease: SduiReleaseV3,\n\ttreeId: string,\n\tintents: Map<string, { fragmentId: string; eventName: string; route: ExperienceIntentRoute }>,\n\tviews: Map<string, { fragmentId: string; bindingName: string; route: ExperienceViewRoute }>,\n): RenderPlanFragment {\n\tconst effective = release.effectiveGrants.find((grant) => grant.treeId === treeId && grant.fragmentId === fragment.id);\n\tif (!effective) throw new Error(`Promoted release has no effective grant for \"${treeId}:${fragment.id}\".`);\n\tconst events: string[] = [];\n\tconst offlineEvents: string[] = [];\n\tconst bindings: string[] = [];\n\tif (fragment.dataRef !== undefined) {\n\t\tif (!effective.views.includes(fragment.dataRef)) throw new Error(`Fragment data binding \"${fragment.id}\" exceeds its effective grants.`);\n\t\tconst route = release.routes.views.find((candidate) => candidate.reference === fragment.dataRef);\n\t\tif (!route) throw new Error(`Fragment data binding \"${fragment.id}\" has no compiled view route.`);\n\t\tviews.set(`${fragment.id}\\u0000$data`, { fragmentId: fragment.id, bindingName: \"$data\", route });\n\t\tbindings.push(\"$data\");\n\t}\n\tfor (const [eventName, action] of Object.entries(fragment.actions ?? {})) {\n\t\tif (!effective.intents.includes(action.intent)) throw new Error(`Fragment event \"${fragment.id}.${eventName}\" exceeds its effective grants.`);\n\t\tconst route = release.routes.intents.find((candidate) => candidate.reference === action.intent);\n\t\tif (!route) throw new Error(`Fragment event \"${fragment.id}.${eventName}\" has no compiled intent route.`);\n\t\tintents.set(`${fragment.id}\\u0000${eventName}`, { fragmentId: fragment.id, eventName, route });\n\t\tevents.push(eventName);\n\t\tif (route.execution?.connectivity === \"offline-capturable\") offlineEvents.push(eventName);\n\t}\n\treturn Object.freeze({\n\t\tid: fragment.id,\n\t\t...(fragment.component === undefined ? {} : { component: fragment.component }),\n\t\t...(fragment.props === undefined ? {} : { props: stripCapabilityReferences(fragment.props) }),\n\t\t...(fragment.slot === undefined ? {} : { slot: fragment.slot }),\n\t\t...(events.length === 0 ? {} : { events: Object.freeze(events.sort()) }),\n\t\t...(offlineEvents.length === 0 ? {} : { offlineEvents: Object.freeze(offlineEvents.sort()) }),\n\t\t...(bindings.length === 0 ? {} : { bindings: Object.freeze(bindings) }),\n\t\t...(fragment.children === undefined ? {} : {\n\t\t\tchildren: Object.freeze(fragment.children.map((child) => toRenderPlanFragment(child, release, treeId, intents, views))),\n\t\t}),\n\t});\n}\n\nasync function loadPlan(\n\tstore: ExperiencePlanStore,\n\tplanId: string,\n\tscope: ExperienceScope,\n\tnow: Date,\n): Promise<ExperiencePlanState> {\n\tconst state = await store.load(planId);\n\tif (!state) throw new Error(\"Experience render plan was not found.\");\n\tif (Date.parse(state.publicPlan.expiresAt) <= now.getTime()) throw new Error(\"Experience render plan has expired.\");\n\tif (!sameScope(state.scope, scope)) throw new Error(\"Experience render plan scope does not match trusted request scope.\");\n\treturn state;\n}\n\nfunction stripCapabilityReferences(value: Record<string, SduiFragmentValue>): Record<string, unknown> {\n\tconst strip = (candidate: SduiFragmentValue): unknown => {\n\t\tif (Array.isArray(candidate)) return candidate.map(strip);\n\t\tif (candidate && typeof candidate === \"object\") {\n\t\t\tif (\"$ref\" in candidate) return undefined;\n\t\t\treturn Object.fromEntries(Object.entries(candidate).flatMap(([key, entry]) => {\n\t\t\t\tconst stripped = strip(entry);\n\t\t\t\treturn stripped === undefined ? [] : [[key, stripped]];\n\t\t\t}));\n\t\t}\n\t\treturn candidate;\n\t};\n\treturn strip(value) as Record<string, unknown>;\n}\n\nfunction sameScope(left: ExperienceScope, right: ExperienceScope): boolean {\n\treturn left.tenantId === right.tenantId\n\t\t&& left.spaceId === right.spaceId\n\t\t&& left.actorId === right.actorId\n\t\t&& left.actorType === right.actorType;\n}\n\nexport function createMemoryExperiencePlanStore(): ExperiencePlanStore {\n\tconst plans = new Map<string, ExperiencePlanState>();\n\treturn {\n\t\tasync save(state) {\n\t\t\tif (plans.has(state.publicPlan.planId)) throw new Error(`Experience render plan \"${state.publicPlan.planId}\" already exists.`);\n\t\t\tplans.set(state.publicPlan.planId, state);\n\t\t},\n\t\tasync load(planId) {\n\t\t\treturn plans.get(planId) ?? null;\n\t\t},\n\t};\n}\n"],"mappings":";;AAmGA,SAAgB,yBAA2D;CAC1E,OAAO;EACN;GACC,IAAI;GACJ,MAAM,IAAI,SAAS;IAClB,MAAM,OAAO,MAAM,QAAQ,QAAQ,UAAU,QAAQ,KAAK;IAE1D,IADmB,KAAK,UAAU,IACrB,CAAC,CAAC,SAAS,eAAe,GAAG,MAAM,IAAI,MAAM,oDAAoD;IAC9G,OAAO,CAAC,KAAK,MAAM;GACpB;EACD;EACA;GACC,IAAI;GACJ,MAAM,IAAI,SAAS;IAClB,MAAM,OAAO,MAAM,QAAQ,QAAQ,UAAU,QAAQ,KAAK;IAC1D,IAAI;KACH,MAAM,QAAQ,QAAQ,cAAc;MACnC,QAAQ,KAAK;MACb,GAAG,QAAQ;MACX,OAAO,QAAQ;KAChB,CAAC;KACD,MAAM,IAAI,MAAM,0DAA0D;IAC3E,SAAS,OAAO;KACf,IAAI,iBAAiB,SAAS,MAAM,YAAY,4DAA4D,MAAM;IACnH;IACA,OAAO,CAAC,sBAAsB;GAC/B;EACD;EACA;GACC,IAAI;GACJ,MAAM,IAAI,SAAS;IAClB,MAAM,OAAO,MAAM,QAAQ,QAAQ,UAAU,QAAQ,KAAK;IAC1D,MAAM,WAAW,MAAM,QAAQ,QAAQ,cAAc;KACpD,QAAQ,KAAK;KACb,GAAG,QAAQ;KACX,OAAO,QAAQ,MAAM;IACtB,CAAC;IACD,OAAO,CAAC,GAAG,SAAS,SAAS,GAAG,SAAS,eAAe;GACzD;EACD;CACD;AACD;AAEA,eAAsB,0BACrB,SACA,SAA2C,uBAAuB,GAIhE;CACF,MAAM,UAAuH,CAAC;CAC9H,KAAK,MAAM,SAAS,QACnB,IAAI;EACH,QAAQ,KAAK;GAAE,IAAI,MAAM;GAAI,QAAQ;GAAU,UAAU,MAAM,MAAM,IAAI,OAAO;EAAE,CAAC;CACpF,SAAS,OAAO;EACf,QAAQ,KAAK;GAAE,IAAI,MAAM;GAAI,QAAQ;GAAU,UAAU,CAAC;GAAG,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;EAAE,CAAC;CAC7H;CAED,OAAO;EAAE,QAAQ,QAAQ,OAAO,WAAW,OAAO,WAAW,QAAQ;EAAG,QAAQ;CAAQ;AACzF;AAEA,SAAgB,8BAA8B,SAYlB;CAC3B,MAAM,MAAM,QAAQ,8BAAc,IAAI,KAAK;CAC3C,OAAO;EACN,MAAM,UAAU,OAAO;GACtB,MAAM,WAAW,MAAM,QAAQ,eAAe;IAAE,aAAa,MAAM;IAAa,SAAS,MAAM;GAAQ,CAAC;GACxG,MAAM,UAAU,MAAM,8BAA8B;IACnD,YAAY,SAAS;IACrB,QAAQ,QAAQ;IAChB,OAAO,QAAQ;IACf,aAAa,QAAQ;IACrB,UAAU;KAAE,QAAQ,QAAQ;KAAQ,aAAa,MAAM;KAAa,SAAS,MAAM;IAAQ;IAC3F,KAAK,IAAI;GACV,CAAC;GACD,MAAM,UAAU,MAAM,8BAA8B;IACnD,YAAY,SAAS;IACrB,QAAQ,QAAQ;IAChB,OAAO,QAAQ;IACf,UAAU;KACT,QAAQ,QAAQ;KAChB,aAAa,MAAM;KACnB,gBAAgB,QAAQ;IACzB;GACD,CAAC;GACD,IAAI,QAAQ,kBAAkB,QAAQ,qBACrC,MAAM,IAAI,MAAM,gFAAgF;GAEjG,MAAM,WAAW,WAAW,SAAS,MAAM,SAAS;GACpD,MAAM,SAAS,QAAQ,aAAa;GACpC,IAAI,CAAC,UAAU,kBAAkB,KAAK,MAAM,GAAG,MAAM,IAAI,MAAM,oCAAoC;GACnG,MAAM,0BAAU,IAAI,IAAqG;GACzH,MAAM,wBAAQ,IAAI,IAAmG;GACrH,MAAM,OAAO,qBAAqB,SAAS,MAAM,SAAS,SAAS,QAAQ,SAAS,KAAK;GACzF,MAAM,aAA+B,OAAO,OAAO;IAClD,eAAe;IACf;IACA,aAAa,MAAM;IACnB,SAAS,MAAM;IACf,eAAe,QAAQ;IACvB,gBAAgB,QAAQ;IACxB,QAAQ,SAAS;IACjB;IACA,UAAU;KAAE,MAAM,QAAQ,SAAS;KAAM,SAAS,QAAQ,SAAS;IAAQ;IAC3E,WAAW,IAAI,KAAK,IAAI,CAAC,CAAC,QAAQ,KAAK,MAAM,SAAS,IAAQ,CAAC,CAAC,YAAY;GAC7E,CAAC;GACD,IAAI,KAAK,UAAU,UAAU,CAAC,CAAC,SAAS,eAAe,GACtD,MAAM,IAAI,MAAM,oDAAoD;GAErE,MAAM,QAAQ,MAAM,KAAK;IAAE;IAAY,OAAO,EAAE,GAAG,MAAM,MAAM;IAAG;IAAS;GAAM,CAAC;GAClF,OAAO;EACR;EACA,MAAM,cAAc,OAAO;GAC1B,MAAM,QAAQ,MAAM,QAAQ,MAAM,KAAK,MAAM,MAAM;GACnD,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,uCAAuC;GACnE,IAAI,KAAK,MAAM,MAAM,WAAW,SAAS,KAAK,IAAI,CAAC,CAAC,QAAQ,GAAG,MAAM,IAAI,MAAM,qCAAqC;GACpH,IAAI,CAAC,UAAU,MAAM,OAAO,MAAM,KAAK,GAAG,MAAM,IAAI,MAAM,oEAAoE;GAC9H,MAAM,SAAS,MAAM,QAAQ,IAAI,GAAG,MAAM,WAAW,QAAQ,MAAM,WAAW;GAC9E,IAAI,CAAC,QAAQ,MAAM,IAAI,MAAM,mBAAmB,MAAM,WAAW,GAAG,MAAM,UAAU,qCAAqC;GACzH,OAAO;IACN,UAAU,OAAO,MAAM;IACvB,eAAe,OAAO,MAAM;IAC5B,WAAW,OAAO,MAAM;IACxB,yBAAyB,MAAM,WAAW;IAC1C,gBAAgB,MAAM,WAAW;GAClC;EACD;EACA,MAAM,YAAY,OAAO;GACxB,MAAM,QAAQ,MAAM,SAAS,QAAQ,OAAO,MAAM,QAAQ,MAAM,OAAO,IAAI,CAAC;GAC5E,MAAM,OAAO,MAAM,MAAM,IAAI,GAAG,MAAM,WAAW,QAAQ,MAAM,aAAa;GAC5E,IAAI,CAAC,MAAM,MAAM,IAAI,MAAM,qBAAqB,MAAM,WAAW,GAAG,MAAM,YAAY,qCAAqC;GAC3H,OAAO;IACN,MAAM,KAAK,MAAM;IACjB,SAAS,KAAK,MAAM;IACpB,yBAAyB,MAAM,WAAW;IAC1C,gBAAgB,MAAM,WAAW;GAClC;EACD;CACD;AACD;AAEA,SAAS,WAAW,SAAwB,WAA4D;CACvG,IAAI,cAAc,KAAA,GAAW,OAAO;EAAE,QAAQ;EAAQ,MAAM,QAAQ,SAAS;CAAK;CAClF,MAAM,UAAU,QAAQ,SAAS,UAAU,MAAM,cAAc,UAAU,OAAO,SAAS;CACzF,IAAI,CAAC,SAAS,MAAM,IAAI,MAAM,YAAY,UAAU,6CAA6C;CACjG,OAAO;EAAE,QAAQ,QAAQ;EAAI,MAAM,QAAQ;CAAK;AACjD;AAEA,SAAS,qBACR,UACA,SACA,QACA,SACA,OACqB;CACrB,MAAM,YAAY,QAAQ,gBAAgB,MAAM,UAAU,MAAM,WAAW,UAAU,MAAM,eAAe,SAAS,EAAE;CACrH,IAAI,CAAC,WAAW,MAAM,IAAI,MAAM,gDAAgD,OAAO,GAAG,SAAS,GAAG,GAAG;CACzG,MAAM,SAAmB,CAAC;CAC1B,MAAM,gBAA0B,CAAC;CACjC,MAAM,WAAqB,CAAC;CAC5B,IAAI,SAAS,YAAY,KAAA,GAAW;EACnC,IAAI,CAAC,UAAU,MAAM,SAAS,SAAS,OAAO,GAAG,MAAM,IAAI,MAAM,0BAA0B,SAAS,GAAG,gCAAgC;EACvI,MAAM,QAAQ,QAAQ,OAAO,MAAM,MAAM,cAAc,UAAU,cAAc,SAAS,OAAO;EAC/F,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,0BAA0B,SAAS,GAAG,8BAA8B;EAChG,MAAM,IAAI,GAAG,SAAS,GAAG,cAAc;GAAE,YAAY,SAAS;GAAI,aAAa;GAAS;EAAM,CAAC;EAC/F,SAAS,KAAK,OAAO;CACtB;CACA,KAAK,MAAM,CAAC,WAAW,WAAW,OAAO,QAAQ,SAAS,WAAW,CAAC,CAAC,GAAG;EACzE,IAAI,CAAC,UAAU,QAAQ,SAAS,OAAO,MAAM,GAAG,MAAM,IAAI,MAAM,mBAAmB,SAAS,GAAG,GAAG,UAAU,gCAAgC;EAC5I,MAAM,QAAQ,QAAQ,OAAO,QAAQ,MAAM,cAAc,UAAU,cAAc,OAAO,MAAM;EAC9F,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,mBAAmB,SAAS,GAAG,GAAG,UAAU,gCAAgC;EACxG,QAAQ,IAAI,GAAG,SAAS,GAAG,QAAQ,aAAa;GAAE,YAAY,SAAS;GAAI;GAAW;EAAM,CAAC;EAC7F,OAAO,KAAK,SAAS;EACrB,IAAI,MAAM,WAAW,iBAAiB,sBAAsB,cAAc,KAAK,SAAS;CACzF;CACA,OAAO,OAAO,OAAO;EACpB,IAAI,SAAS;EACb,GAAI,SAAS,cAAc,KAAA,IAAY,CAAC,IAAI,EAAE,WAAW,SAAS,UAAU;EAC5E,GAAI,SAAS,UAAU,KAAA,IAAY,CAAC,IAAI,EAAE,OAAO,0BAA0B,SAAS,KAAK,EAAE;EAC3F,GAAI,SAAS,SAAS,KAAA,IAAY,CAAC,IAAI,EAAE,MAAM,SAAS,KAAK;EAC7D,GAAI,OAAO,WAAW,IAAI,CAAC,IAAI,EAAE,QAAQ,OAAO,OAAO,OAAO,KAAK,CAAC,EAAE;EACtE,GAAI,cAAc,WAAW,IAAI,CAAC,IAAI,EAAE,eAAe,OAAO,OAAO,cAAc,KAAK,CAAC,EAAE;EAC3F,GAAI,SAAS,WAAW,IAAI,CAAC,IAAI,EAAE,UAAU,OAAO,OAAO,QAAQ,EAAE;EACrE,GAAI,SAAS,aAAa,KAAA,IAAY,CAAC,IAAI,EAC1C,UAAU,OAAO,OAAO,SAAS,SAAS,KAAK,UAAU,qBAAqB,OAAO,SAAS,QAAQ,SAAS,KAAK,CAAC,CAAC,EACvH;CACD,CAAC;AACF;AAEA,eAAe,SACd,OACA,QACA,OACA,KAC+B;CAC/B,MAAM,QAAQ,MAAM,MAAM,KAAK,MAAM;CACrC,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,uCAAuC;CACnE,IAAI,KAAK,MAAM,MAAM,WAAW,SAAS,KAAK,IAAI,QAAQ,GAAG,MAAM,IAAI,MAAM,qCAAqC;CAClH,IAAI,CAAC,UAAU,MAAM,OAAO,KAAK,GAAG,MAAM,IAAI,MAAM,oEAAoE;CACxH,OAAO;AACR;AAEA,SAAS,0BAA0B,OAAmE;CACrG,MAAM,SAAS,cAA0C;EACxD,IAAI,MAAM,QAAQ,SAAS,GAAG,OAAO,UAAU,IAAI,KAAK;EACxD,IAAI,aAAa,OAAO,cAAc,UAAU;GAC/C,IAAI,UAAU,WAAW,OAAO,KAAA;GAChC,OAAO,OAAO,YAAY,OAAO,QAAQ,SAAS,CAAC,CAAC,SAAS,CAAC,KAAK,WAAW;IAC7E,MAAM,WAAW,MAAM,KAAK;IAC5B,OAAO,aAAa,KAAA,IAAY,CAAC,IAAI,CAAC,CAAC,KAAK,QAAQ,CAAC;GACtD,CAAC,CAAC;EACH;EACA,OAAO;CACR;CACA,OAAO,MAAM,KAAK;AACnB;AAEA,SAAS,UAAU,MAAuB,OAAiC;CAC1E,OAAO,KAAK,aAAa,MAAM,YAC3B,KAAK,YAAY,MAAM,WACvB,KAAK,YAAY,MAAM,WACvB,KAAK,cAAc,MAAM;AAC9B;AAEA,SAAgB,kCAAuD;CACtE,MAAM,wBAAQ,IAAI,IAAiC;CACnD,OAAO;EACN,MAAM,KAAK,OAAO;GACjB,IAAI,MAAM,IAAI,MAAM,WAAW,MAAM,GAAG,MAAM,IAAI,MAAM,2BAA2B,MAAM,WAAW,OAAO,kBAAkB;GAC7H,MAAM,IAAI,MAAM,WAAW,QAAQ,KAAK;EACzC;EACA,MAAM,KAAK,QAAQ;GAClB,OAAO,MAAM,IAAI,MAAM,KAAK;EAC7B;CACD;AACD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fabricorg/experience-runtime",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.10",
|
|
4
4
|
"description": "Server-issued scoped render plans and browser-safe experience sessions.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -76,16 +76,17 @@
|
|
|
76
76
|
],
|
|
77
77
|
"devDependencies": {
|
|
78
78
|
"@types/node": "^22.10.0",
|
|
79
|
-
"
|
|
79
|
+
"tsdown": "^0.23.1",
|
|
80
80
|
"typescript": "^5.7.3",
|
|
81
81
|
"vitest": "^4.1.5",
|
|
82
|
-
"@fabricorg/platform": "^1.10.
|
|
82
|
+
"@fabricorg/platform": "^1.10.2",
|
|
83
|
+
"@fabric-platform/bundling": "0.0.0"
|
|
83
84
|
},
|
|
84
85
|
"dependencies": {
|
|
85
|
-
"@fabricorg/sdui-release": "^0.7.
|
|
86
|
+
"@fabricorg/sdui-release": "^0.7.10"
|
|
86
87
|
},
|
|
87
88
|
"peerDependencies": {
|
|
88
|
-
"@fabricorg/platform": "^1.10.
|
|
89
|
+
"@fabricorg/platform": "^1.10.2"
|
|
89
90
|
},
|
|
90
91
|
"engines": {
|
|
91
92
|
"node": ">=20"
|
|
@@ -95,8 +96,8 @@
|
|
|
95
96
|
},
|
|
96
97
|
"scripts": {
|
|
97
98
|
"clean": "rm -rf dist tsconfig.tsbuildinfo",
|
|
98
|
-
"build": "
|
|
99
|
-
"type-check": "
|
|
99
|
+
"build": "tsdown",
|
|
100
|
+
"type-check": "tsgo --noEmit",
|
|
100
101
|
"test": "vitest run"
|
|
101
102
|
}
|
|
102
103
|
}
|
package/dist/contracts.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../contracts.ts"],"sourcesContent":["export interface ExperienceScope {\n\ttenantId: string;\n\tspaceId: string;\n\tactorId: string;\n\tactorType: string;\n}\n\nexport interface RenderPlanFragment {\n\tid: string;\n\tcomponent?: string;\n\tprops?: Record<string, unknown>;\n\tslot?: string;\n\tevents?: readonly string[];\n\tofflineEvents?: readonly string[];\n\tbindings?: readonly string[];\n\tchildren?: readonly RenderPlanFragment[];\n}\n\nexport interface ScopedRenderPlan {\n\tformatVersion: 1;\n\tplanId: string;\n\tapplication: string;\n\tchannel: string;\n\treleaseDigest: string;\n\tassemblyDigest: string;\n\ttreeId: string;\n\troot: RenderPlanFragment;\n\ttokenSet: { name: string; version: string };\n\texpiresAt: string;\n}\n\nexport interface ExperienceIntentRequest {\n\tplanId: string;\n\tfragmentId: string;\n\teventName: string;\n\tparameters: Record<string, unknown>;\n\tidempotencyKey?: string;\n}\n\n/**\n * A replay of an action captured while disconnected.\n *\n * The marker and the capture moment travel with the request rather than by\n * adapter convention. An adapter that forwarded a replay as an ordinary intent\n * would skip the Host's offline eligibility and reconciliation rules, and\n * capture-authority evidence stamped at receipt cannot truthfully attest when\n * the operator actually acted.\n */\nexport interface ExperienceReplayRequest extends ExperienceIntentRequest {\n\texecutionReason: \"offline_replay\";\n\t/** RFC 3339 instant the action was captured on the device. */\n\tcapturedAt: string;\n\tidempotencyKey: string;\n}\n\nexport interface ExperienceReadRequest {\n\tplanId: string;\n\tfragmentId: string;\n\tbindingName: string;\n\tparameters?: Record<string, unknown>;\n}\n\nexport interface ExperienceGatewayClient {\n\tinvoke(request: ExperienceIntentRequest): Promise<unknown>;\n\treplay?(request: ExperienceReplayRequest): Promise<ExperienceInvocationOutcome>;\n\tread(request: ExperienceReadRequest): Promise<{ data: unknown; stale?: boolean }>;\n}\n\n/**\n * What a governed submission comes back as.\n *\n * The Host reports a refusal by *returning* `reconciliation_required` with the\n * reason, not by throwing: a lapsed authority is a business outcome rather than\n * an error. Anything reading only the exception path will count a refusal as a\n * success.\n */\nexport interface ExperienceInvocationOutcome {\n\tstatus?: string;\n\t/** `executionReason` names the attempt that produced this; `\"recovery\"` means effects may already have landed. */\n\treconciliation?: { kind?: string; message?: string; executionReason?: string };\n\t/** Present when an adapter's own outcome could not be determined. */\n\tadapterReconciliation?: unknown;\n\t[key: string]: unknown;\n}\n\nexport interface ExperienceSession {\n\treadonly plan: ScopedRenderPlan;\n\tinvoke(fragmentId: string, eventName: string, parameters: Record<string, unknown>, idempotencyKey?: string): Promise<unknown>;\n\tread(fragmentId: string, bindingName: string, parameters?: Record<string, unknown>): Promise<{ data: unknown; stale?: boolean }>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
|
package/dist/contracts.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|