@world-engines/tmw 0.1.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +46 -0
- package/dist/activation.d.ts +50 -0
- package/dist/activation.js +113 -0
- package/dist/audio-presentation-projection.d.ts +23 -0
- package/dist/audio-presentation-projection.js +39 -0
- package/dist/author-scenario-source-v3.d.ts +34 -0
- package/dist/author-scenario-source-v3.js +1 -0
- package/dist/authoring-runtime.d.ts +76 -0
- package/dist/authoring-runtime.js +282 -0
- package/dist/builder-diagnostics.d.ts +3 -0
- package/dist/builder-diagnostics.js +8 -0
- package/dist/builder-node.d.ts +178 -0
- package/dist/builder-node.js +212 -0
- package/dist/builder.d.ts +32 -0
- package/dist/builder.js +127 -0
- package/dist/capability-registry.d.ts +32 -0
- package/dist/capability-registry.js +33 -0
- package/dist/compile-target-profile.d.ts +16 -0
- package/dist/compile-target-profile.js +70 -0
- package/dist/compiled-plan.d.ts +44 -0
- package/dist/compiled-plan.js +1 -0
- package/dist/compiler-compatibility-gate.d.ts +96 -0
- package/dist/compiler-compatibility-gate.js +609 -0
- package/dist/compiler-release-authority.d.ts +212 -0
- package/dist/compiler-release-authority.js +676 -0
- package/dist/compiler-stage-cache.d.ts +42 -0
- package/dist/compiler-stage-cache.js +100 -0
- package/dist/compiler-stage-registry.d.ts +48 -0
- package/dist/compiler-stage-registry.js +126 -0
- package/dist/compiler.d.ts +7 -0
- package/dist/compiler.js +68 -0
- package/dist/condition-graph.d.ts +46 -0
- package/dist/condition-graph.js +135 -0
- package/dist/diagnostics.d.ts +20 -0
- package/dist/diagnostics.js +20 -0
- package/dist/durable-intent.d.ts +29 -0
- package/dist/durable-intent.js +60 -0
- package/dist/effect-flow.d.ts +69 -0
- package/dist/effect-flow.js +98 -0
- package/dist/event-fabric.d.ts +81 -0
- package/dist/event-fabric.js +227 -0
- package/dist/event-lifecycle.d.ts +59 -0
- package/dist/event-lifecycle.js +161 -0
- package/dist/evidence.d.ts +37 -0
- package/dist/evidence.js +92 -0
- package/dist/gameplay-state.d.ts +127 -0
- package/dist/gameplay-state.js +214 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.js +52 -0
- package/dist/isolated-replay.d.ts +81 -0
- package/dist/isolated-replay.js +316 -0
- package/dist/music-candidate-set.d.ts +38 -0
- package/dist/music-candidate-set.js +102 -0
- package/dist/natural-language-chunking.d.ts +10 -0
- package/dist/natural-language-chunking.js +62 -0
- package/dist/natural-language-ingestion.d.ts +14 -0
- package/dist/natural-language-ingestion.js +128 -0
- package/dist/natural-language-structured-decode.d.ts +2 -0
- package/dist/natural-language-structured-decode.js +69 -0
- package/dist/natural-language-world-types.d.ts +142 -0
- package/dist/natural-language-world-types.js +2 -0
- package/dist/neutral-source-compiler.d.ts +135 -0
- package/dist/neutral-source-compiler.js +608 -0
- package/dist/presentation-intent.d.ts +42 -0
- package/dist/presentation-intent.js +137 -0
- package/dist/recall-query-envelope.d.ts +32 -0
- package/dist/recall-query-envelope.js +51 -0
- package/dist/rewrite-compensation.d.ts +28 -0
- package/dist/rewrite-compensation.js +85 -0
- package/dist/runtime-contract.d.ts +56 -0
- package/dist/runtime-contract.js +96 -0
- package/dist/runtime-domain-capabilities.d.ts +12 -0
- package/dist/runtime-domain-capabilities.js +20 -0
- package/dist/runtime-sav.d.ts +117 -0
- package/dist/runtime-sav.js +356 -0
- package/dist/runtime-trace.d.ts +44 -0
- package/dist/runtime-trace.js +119 -0
- package/dist/runtime.d.ts +238 -0
- package/dist/runtime.js +1887 -0
- package/dist/scene-author-source.d.ts +13 -0
- package/dist/scene-author-source.js +25 -0
- package/dist/scene-state.d.ts +275 -0
- package/dist/scene-state.js +334 -0
- package/dist/scheduler.d.ts +55 -0
- package/dist/scheduler.js +235 -0
- package/dist/selector-ast.d.ts +24 -0
- package/dist/selector-ast.js +18 -0
- package/dist/semantic-verdict.d.ts +48 -0
- package/dist/semantic-verdict.js +101 -0
- package/dist/simulator.d.ts +45 -0
- package/dist/simulator.js +58 -0
- package/dist/source-canonical.d.ts +4 -0
- package/dist/source-canonical.js +51 -0
- package/dist/source-crypto.d.ts +20 -0
- package/dist/source-crypto.js +17 -0
- package/dist/source-revision.d.ts +22 -0
- package/dist/source-revision.js +43 -0
- package/dist/spatial-impact-receipt.d.ts +38 -0
- package/dist/spatial-impact-receipt.js +67 -0
- package/dist/temporal-rewrite.d.ts +59 -0
- package/dist/temporal-rewrite.js +214 -0
- package/dist/time-domains.d.ts +37 -0
- package/dist/time-domains.js +142 -0
- package/dist/timeline-revision.d.ts +28 -0
- package/dist/timeline-revision.js +59 -0
- package/dist/trigger-document-v3.d.ts +99 -0
- package/dist/trigger-document-v3.js +334 -0
- package/dist/turn-commit.d.ts +56 -0
- package/dist/turn-commit.js +190 -0
- package/dist/validator.d.ts +7 -0
- package/dist/validator.js +142 -0
- package/dist/value-graph.d.ts +98 -0
- package/dist/value-graph.js +318 -0
- package/dist/view-event-delivery.d.ts +47 -0
- package/dist/view-event-delivery.js +197 -0
- package/dist/view-interaction-manifest-v3.d.ts +60 -0
- package/dist/view-interaction-manifest-v3.js +198 -0
- package/dist/view-payload-schema.d.ts +17 -0
- package/dist/view-payload-schema.js +84 -0
- package/package.json +62 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { canonicalTmwJson } from "./runtime-contract.js";
|
|
2
|
+
export const TMW_VALUE_GRAPH_SCHEMA_VERSION = 1;
|
|
3
|
+
export const TMW_VALUE_TYPES = ["number", "string", "boolean", "int32", "world_time", "real_timestamp", "duration", "spatial_ref", "string_list", "object_ref"];
|
|
4
|
+
/** 变量身份包含 scope,避免执行局部值遮蔽存档变量。 */
|
|
5
|
+
export function tmwValueContextFromSavV1(sav) {
|
|
6
|
+
const variables = Object.create(null);
|
|
7
|
+
for (const variable of sav.gameplayState?.variables ?? []) {
|
|
8
|
+
if (type(variable.valueType))
|
|
9
|
+
variables[`${variable.scope}:${variable.name}`] = { value_type: variable.valueType, value: variable.value };
|
|
10
|
+
}
|
|
11
|
+
return Object.freeze({ scene: sav.scene, variables: Object.freeze(variables), ...(sav.gameplayState?.entity_state_stacks === undefined ? {} : { entity_states: sav.gameplayState.entity_state_stacks }) });
|
|
12
|
+
}
|
|
13
|
+
export function resolveTmwEffectValueArgumentsV1(graph, targetId, args, context, capabilityId) {
|
|
14
|
+
const resolved = resolveTmwValueGraphInputsV1(graph, targetId, context);
|
|
15
|
+
const output = { ...args };
|
|
16
|
+
for (const [port, value] of Object.entries(resolved)) {
|
|
17
|
+
if (value === undefined)
|
|
18
|
+
throw new Error("tmw_value_effect_input_unavailable");
|
|
19
|
+
if (capabilityId === "variable.set" && port === "value") {
|
|
20
|
+
const variable = context.variables?.[`${args.scope}:${args.name}`];
|
|
21
|
+
if (!variable || variable.value_type !== value.value_type)
|
|
22
|
+
throw new Error("tmw_value_variable_type_mismatch");
|
|
23
|
+
}
|
|
24
|
+
if (Object.hasOwn(output, port))
|
|
25
|
+
throw new Error("tmw_value_input_inline_conflict");
|
|
26
|
+
output[port] = value.value;
|
|
27
|
+
}
|
|
28
|
+
return Object.freeze(output);
|
|
29
|
+
}
|
|
30
|
+
export function tmwEffectValuePortTypeV1(capabilityId, port, args, schema) {
|
|
31
|
+
if (capabilityId.startsWith("scene.") && port === "worldTime")
|
|
32
|
+
return "world_time";
|
|
33
|
+
if (capabilityId.startsWith("scene.") && port === "spatialRef")
|
|
34
|
+
return "spatial_ref";
|
|
35
|
+
if (port === "value" && type(args.valueType))
|
|
36
|
+
return args.valueType;
|
|
37
|
+
const properties = obj(schema) && obj(schema.properties) ? schema.properties : {};
|
|
38
|
+
const property = properties[port];
|
|
39
|
+
if (!obj(property))
|
|
40
|
+
return undefined;
|
|
41
|
+
if (type(property.tmw_value_type))
|
|
42
|
+
return property.tmw_value_type;
|
|
43
|
+
if (property.type === "string" || property.type === "number" || property.type === "boolean")
|
|
44
|
+
return property.type;
|
|
45
|
+
if (property.type === "integer")
|
|
46
|
+
return "int32";
|
|
47
|
+
if (property.type === "array" && obj(property.items) && property.items.type === "string")
|
|
48
|
+
return "string_list";
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
/** 绑定输入占据声明参数槽;其余 inline 参数仍须通过原 capability schema。 */
|
|
52
|
+
export function validateTmwBoundEffectArgumentsV1(args, boundPorts, schema) {
|
|
53
|
+
if (!obj(schema) || !obj(schema.properties))
|
|
54
|
+
throw new Error("tmw_value_effect_schema_required");
|
|
55
|
+
const names = [...Object.keys(args), ...boundPorts];
|
|
56
|
+
if (schema.additionalProperties === false && names.some((name) => !Object.hasOwn(schema.properties, name)))
|
|
57
|
+
throw new Error("tmw_value_effect_argument_unknown");
|
|
58
|
+
if (Array.isArray(schema.required) && schema.required.some((name) => !names.includes(String(name))))
|
|
59
|
+
throw new Error("tmw_value_effect_argument_missing");
|
|
60
|
+
const matches = (value, rule) => {
|
|
61
|
+
if (!obj(rule))
|
|
62
|
+
return false;
|
|
63
|
+
if (Array.isArray(rule.anyOf))
|
|
64
|
+
return rule.anyOf.some((r) => matches(value, r));
|
|
65
|
+
if (Array.isArray(rule.enum) && !rule.enum.some((v) => canonicalTmwJson(v) === canonicalTmwJson(value)))
|
|
66
|
+
return false;
|
|
67
|
+
const acceptsType = (t) => t === "null" ? value === null : t === "integer" ? Number.isSafeInteger(value) : t === "array" ? Array.isArray(value) : t === "object" ? obj(value) : typeof value === t;
|
|
68
|
+
if (rule.type !== undefined && !(Array.isArray(rule.type) ? rule.type.some(acceptsType) : acceptsType(rule.type)))
|
|
69
|
+
return false;
|
|
70
|
+
if (typeof value === "number" && (!Number.isFinite(value) || typeof rule.minimum === "number" && value < rule.minimum || typeof rule.maximum === "number" && value > rule.maximum))
|
|
71
|
+
return false;
|
|
72
|
+
if (typeof value === "string" && (typeof rule.minLength === "number" && value.length < rule.minLength || typeof rule.maxLength === "number" && value.length > rule.maxLength))
|
|
73
|
+
return false;
|
|
74
|
+
if (Array.isArray(value)) {
|
|
75
|
+
if (typeof rule.maxItems === "number" && value.length > rule.maxItems || typeof rule.minItems === "number" && value.length < rule.minItems)
|
|
76
|
+
return false;
|
|
77
|
+
if (rule.uniqueItems === true && new Set(value.map((v) => canonicalTmwJson(v))).size !== value.length)
|
|
78
|
+
return false;
|
|
79
|
+
if (rule.items !== undefined && !value.every((v) => matches(v, rule.items)))
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
if (obj(value)) {
|
|
83
|
+
if (typeof rule.maxProperties === "number" && Object.keys(value).length > rule.maxProperties)
|
|
84
|
+
return false;
|
|
85
|
+
const properties = obj(rule.properties) ? rule.properties : {};
|
|
86
|
+
if (rule.additionalProperties === false && Object.keys(value).some((key) => !Object.hasOwn(properties, key)))
|
|
87
|
+
return false;
|
|
88
|
+
if (Array.isArray(rule.required) && rule.required.some((key) => !Object.hasOwn(value, String(key))))
|
|
89
|
+
return false;
|
|
90
|
+
if (Object.entries(value).some(([key, v]) => Object.hasOwn(properties, key) && !matches(v, properties[key])))
|
|
91
|
+
return false;
|
|
92
|
+
if (obj(rule.additionalProperties) && Object.entries(value).some(([key, v]) => !Object.hasOwn(properties, key) && !matches(v, rule.additionalProperties)))
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
return true;
|
|
96
|
+
};
|
|
97
|
+
for (const [name, value] of Object.entries(args))
|
|
98
|
+
if (!matches(value, schema.properties[name]))
|
|
99
|
+
throw new Error("tmw_value_effect_argument_invalid");
|
|
100
|
+
}
|
|
101
|
+
const sceneTypes = Object.freeze({ world_time: "world_time", location: "spatial_ref", scene_id: "string", present_entity_ids: "string_list" });
|
|
102
|
+
const id = (v) => typeof v === "string" && /^[a-z0-9][a-z0-9._:-]{0,127}$/u.test(v);
|
|
103
|
+
const objectRefId = (v) => typeof v === "string" && /^[^\0\r\n]{1,512}$/u.test(v);
|
|
104
|
+
const obj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
105
|
+
const keys = (v, names) => Object.keys(v).sort().join(",") === names.sort().join(",");
|
|
106
|
+
const type = (v) => TMW_VALUE_TYPES.includes(v);
|
|
107
|
+
export function isTmwTypedValueV1(valueType, value) {
|
|
108
|
+
switch (valueType) {
|
|
109
|
+
case "boolean": return typeof value === "boolean";
|
|
110
|
+
case "string": return typeof value === "string";
|
|
111
|
+
case "int32": return typeof value === "number" && Number.isInteger(value) && value >= -2147483648 && value <= 2147483647;
|
|
112
|
+
case "number":
|
|
113
|
+
case "world_time": return typeof value === "number" && Number.isFinite(value);
|
|
114
|
+
case "real_timestamp": return typeof value === "number" && Number.isSafeInteger(value);
|
|
115
|
+
case "duration": return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
116
|
+
case "string_list": return Array.isArray(value) && value.every((v) => typeof v === "string");
|
|
117
|
+
case "spatial_ref": return obj(value) && keys(value, ["worldId", "sourceRevision", "spatialId"]) && typeof value.worldId === "string" && value.worldId.length > 0 && typeof value.spatialId === "string" && value.spatialId.length > 0 && Number.isSafeInteger(value.sourceRevision) && value.sourceRevision >= 0;
|
|
118
|
+
case "object_ref": return isTmwObjectRefV1(value);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
export function isTmwObjectRefV1(value) {
|
|
122
|
+
return obj(value) && keys(value, ["id", "kind"])
|
|
123
|
+
&& (value.kind === "node" || value.kind === "edge")
|
|
124
|
+
&& objectRefId(value.id);
|
|
125
|
+
}
|
|
126
|
+
export function tmwValueNodePortsV1(node) {
|
|
127
|
+
const inputs = {};
|
|
128
|
+
if (node.op === "compare") {
|
|
129
|
+
inputs.left = node.value_type;
|
|
130
|
+
inputs.right = node.value_type;
|
|
131
|
+
}
|
|
132
|
+
if (node.op === "bitwise") {
|
|
133
|
+
inputs.left = "int32";
|
|
134
|
+
if (node.operator !== "not")
|
|
135
|
+
inputs.right = "int32";
|
|
136
|
+
}
|
|
137
|
+
const output = node.op === "compare" || node.op === "state_read" ? "boolean" : node.op === "bitwise" ? "int32" : node.op === "scene_read" ? sceneTypes[node.field] : node.value_type;
|
|
138
|
+
return Object.freeze({ inputs: Object.freeze(inputs), outputs: Object.freeze({ value: output }) });
|
|
139
|
+
}
|
|
140
|
+
export function parseTmwValueGraphV1(value) {
|
|
141
|
+
if (!obj(value) || !keys(value, ["schema_version", "nodes", "edges"]) || value.schema_version !== 1 || !Array.isArray(value.nodes) || !Array.isArray(value.edges) || value.nodes.length > 4096 || value.edges.length > 16384)
|
|
142
|
+
throw new Error("tmw_value_graph_invalid");
|
|
143
|
+
const nodes = value.nodes.map((n) => {
|
|
144
|
+
if (!obj(n) || n.kind !== "value" || !id(n.stable_id))
|
|
145
|
+
throw new Error("tmw_value_node_invalid");
|
|
146
|
+
const base = ["kind", "stable_id", "op"];
|
|
147
|
+
if (n.op === "constant") {
|
|
148
|
+
if (!keys(n, [...base, "value_type", "value"]) || !type(n.value_type) || !isTmwTypedValueV1(n.value_type, n.value))
|
|
149
|
+
throw new Error("tmw_value_constant_invalid");
|
|
150
|
+
}
|
|
151
|
+
else if (n.op === "variable_read") {
|
|
152
|
+
if (!keys(n, [...base, "variable_id", "value_type"]) || !id(n.variable_id) || !type(n.value_type))
|
|
153
|
+
throw new Error("tmw_value_variable_invalid");
|
|
154
|
+
}
|
|
155
|
+
else if (n.op === "state_read") {
|
|
156
|
+
if (!keys(n, [...base, "target", "entity_id", "field"]) || !["node", "edge"].includes(String(n.target)) || !id(n.entity_id) || !["unlocked", "frozen"].includes(String(n.field)))
|
|
157
|
+
throw new Error("tmw_value_state_read_invalid");
|
|
158
|
+
}
|
|
159
|
+
else if (n.op === "scene_read") {
|
|
160
|
+
if (!keys(n, [...base, "field"]) || typeof n.field !== "string" || !Object.hasOwn(sceneTypes, n.field))
|
|
161
|
+
throw new Error("tmw_value_scene_field_invalid");
|
|
162
|
+
}
|
|
163
|
+
else if (n.op === "compare") {
|
|
164
|
+
if (!keys(n, [...base, "value_type", "comparison"]) || !type(n.value_type) || !["eq", "neq", "gt", "gte", "lt", "lte"].includes(String(n.comparison)) || (!["eq", "neq"].includes(String(n.comparison)) && ["boolean", "spatial_ref", "string_list", "object_ref"].includes(n.value_type)))
|
|
165
|
+
throw new Error("tmw_value_compare_invalid");
|
|
166
|
+
}
|
|
167
|
+
else if (n.op === "bitwise") {
|
|
168
|
+
if (!keys(n, [...base, "operator"]) || !["and", "or", "xor", "not", "shl", "shr", "ushr"].includes(String(n.operator)))
|
|
169
|
+
throw new Error("tmw_value_bitwise_invalid");
|
|
170
|
+
}
|
|
171
|
+
else
|
|
172
|
+
throw new Error("tmw_value_node_op_invalid");
|
|
173
|
+
return Object.freeze(JSON.parse(canonicalTmwJson(n)));
|
|
174
|
+
});
|
|
175
|
+
const edges = value.edges.map((e) => {
|
|
176
|
+
if (!obj(e) || !keys(e, ["from_node", "from_port", "to_node", "to_port", "value_type"]) || !id(e.from_node) || !id(e.to_node) || typeof e.to_port !== "string" || !/^[a-z][a-zA-Z0-9_]{0,127}$/u.test(e.to_port) || e.from_port !== "value" || !type(e.value_type))
|
|
177
|
+
throw new Error("tmw_value_edge_invalid");
|
|
178
|
+
return Object.freeze({ ...e });
|
|
179
|
+
});
|
|
180
|
+
return Object.freeze({ schema_version: 1, nodes: Object.freeze(nodes), edges: Object.freeze(edges) });
|
|
181
|
+
}
|
|
182
|
+
export function validateTmwValueGraphV1(graph, consumers = []) {
|
|
183
|
+
const nodes = new Map(graph.nodes.map((n) => [n.stable_id, n]));
|
|
184
|
+
if (nodes.size !== graph.nodes.length)
|
|
185
|
+
throw new Error("tmw_value_node_duplicate");
|
|
186
|
+
const ports = new Map(consumers.map((n) => [n.stable_id, n.inputs]));
|
|
187
|
+
for (const n of graph.nodes) {
|
|
188
|
+
if (ports.has(n.stable_id))
|
|
189
|
+
throw new Error("tmw_value_node_identity_collision");
|
|
190
|
+
ports.set(n.stable_id, tmwValueNodePortsV1(n).inputs);
|
|
191
|
+
}
|
|
192
|
+
const inputs = new Set();
|
|
193
|
+
const parents = new Map();
|
|
194
|
+
for (const edge of graph.edges) {
|
|
195
|
+
const source = nodes.get(edge.from_node);
|
|
196
|
+
if (!source || tmwValueNodePortsV1(source).outputs[edge.from_port] !== edge.value_type || ports.get(edge.to_node)?.[edge.to_port] !== edge.value_type)
|
|
197
|
+
throw new Error("tmw_value_edge_type_mismatch");
|
|
198
|
+
const input = `${edge.to_node}\0${edge.to_port}`;
|
|
199
|
+
if (inputs.has(input))
|
|
200
|
+
throw new Error("tmw_value_input_multiple_sources");
|
|
201
|
+
inputs.add(input);
|
|
202
|
+
parents.set(edge.to_node, [...(parents.get(edge.to_node) ?? []), edge.from_node]);
|
|
203
|
+
}
|
|
204
|
+
for (const [node, p] of ports)
|
|
205
|
+
for (const port of Object.keys(p))
|
|
206
|
+
if (!inputs.has(`${node}\0${port}`))
|
|
207
|
+
throw new Error("tmw_value_input_missing");
|
|
208
|
+
const visited = new Set(), active = new Set();
|
|
209
|
+
const visit = (n, depth) => {
|
|
210
|
+
if (depth > 128)
|
|
211
|
+
throw new Error("tmw_value_graph_depth_exceeded");
|
|
212
|
+
if (active.has(n))
|
|
213
|
+
throw new Error("tmw_value_graph_cycle");
|
|
214
|
+
if (visited.has(n))
|
|
215
|
+
return;
|
|
216
|
+
active.add(n);
|
|
217
|
+
for (const p of parents.get(n) ?? [])
|
|
218
|
+
visit(p, depth + 1);
|
|
219
|
+
active.delete(n);
|
|
220
|
+
visited.add(n);
|
|
221
|
+
};
|
|
222
|
+
for (const n of nodes.keys())
|
|
223
|
+
visit(n, 0);
|
|
224
|
+
}
|
|
225
|
+
export function readTmwSceneValueV1(scene, field) {
|
|
226
|
+
if (!scene)
|
|
227
|
+
return undefined;
|
|
228
|
+
const value = field === "world_time" ? scene.worldTime : field === "location" ? scene.spatialRef : field === "scene_id" ? scene.sceneId : scene.presentEntityNodeIds;
|
|
229
|
+
if (value === null)
|
|
230
|
+
return undefined;
|
|
231
|
+
return Object.freeze({ value_type: sceneTypes[field], value: value });
|
|
232
|
+
}
|
|
233
|
+
export function compareTmwValuesV1(left, right, comparison) {
|
|
234
|
+
if (comparison === "eq" || comparison === "neq")
|
|
235
|
+
return (canonicalTmwJson(left) === canonicalTmwJson(right)) === (comparison === "eq");
|
|
236
|
+
if (comparison === "contains")
|
|
237
|
+
return typeof left === "string" && typeof right === "string" ? left.includes(right) : Array.isArray(left) ? left.some((v) => canonicalTmwJson(v) === canonicalTmwJson(right)) : undefined;
|
|
238
|
+
if (!(typeof left === "number" && typeof right === "number") && !(typeof left === "string" && typeof right === "string"))
|
|
239
|
+
return undefined;
|
|
240
|
+
if (comparison === "gt")
|
|
241
|
+
return left > right;
|
|
242
|
+
if (comparison === "gte")
|
|
243
|
+
return left >= right;
|
|
244
|
+
if (comparison === "lt")
|
|
245
|
+
return left < right;
|
|
246
|
+
return left <= right;
|
|
247
|
+
}
|
|
248
|
+
export function evaluateTmwValueGraphV1(graph, context = {}, roots = graph.nodes.map((n) => n.stable_id)) {
|
|
249
|
+
const nodes = new Map(graph.nodes.map((n) => [n.stable_id, n]));
|
|
250
|
+
const result = new Map(), active = new Set();
|
|
251
|
+
const visit = (nodeId) => {
|
|
252
|
+
if (result.has(nodeId))
|
|
253
|
+
return result.get(nodeId);
|
|
254
|
+
if (active.has(nodeId) || active.size > 128)
|
|
255
|
+
throw new Error("tmw_value_graph_cycle");
|
|
256
|
+
const node = nodes.get(nodeId);
|
|
257
|
+
if (!node)
|
|
258
|
+
throw new Error("tmw_value_node_missing");
|
|
259
|
+
active.add(nodeId);
|
|
260
|
+
const input = (port) => {
|
|
261
|
+
const edge = graph.edges.find((e) => e.to_node === nodeId && e.to_port === port);
|
|
262
|
+
return edge ? visit(edge.from_node) : undefined;
|
|
263
|
+
};
|
|
264
|
+
let out;
|
|
265
|
+
if (node.op === "constant")
|
|
266
|
+
out = { value_type: node.value_type, value: node.value };
|
|
267
|
+
else if (node.op === "variable_read") {
|
|
268
|
+
out = context.variables?.[node.variable_id];
|
|
269
|
+
if (out && out.value_type !== node.value_type)
|
|
270
|
+
throw new Error("tmw_value_variable_type_mismatch");
|
|
271
|
+
}
|
|
272
|
+
else if (node.op === "scene_read")
|
|
273
|
+
out = readTmwSceneValueV1(context.scene, node.field);
|
|
274
|
+
else if (node.op === "state_read") {
|
|
275
|
+
if (context.entity_states !== undefined) {
|
|
276
|
+
const state = context.entity_states[`${node.target}:${node.entity_id}`];
|
|
277
|
+
const kind = state?.kind ?? "available";
|
|
278
|
+
out = { value_type: "boolean", value: node.field === "frozen" ? kind === "frozen" : (kind === "frozen" ? state?.frozen_stack[0]?.prev_kind : kind) === "available" };
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
const left = input("left"), right = input("right");
|
|
283
|
+
const expectedType = node.op === "compare" ? node.value_type : "int32";
|
|
284
|
+
if (left && left.value_type !== expectedType || right && right.value_type !== expectedType)
|
|
285
|
+
throw new Error("tmw_value_runtime_type_mismatch");
|
|
286
|
+
if (left && (right || (node.op === "bitwise" && node.operator === "not"))) {
|
|
287
|
+
if (node.op === "compare") {
|
|
288
|
+
const value = compareTmwValuesV1(left.value, right.value, node.comparison);
|
|
289
|
+
if (value !== undefined)
|
|
290
|
+
out = { value_type: "boolean", value };
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
const a = left.value, b = right?.value;
|
|
294
|
+
if (["shl", "shr", "ushr"].includes(node.operator) && (!Number.isInteger(b) || b < 0 || b > 31))
|
|
295
|
+
throw new Error("tmw_value_shift_count_invalid");
|
|
296
|
+
// 位运算采用 32 位二补数;无符号右移的 bit pattern 仍以 int32 输出。
|
|
297
|
+
const value = node.operator === "and" ? a & b : node.operator === "or" ? a | b : node.operator === "xor" ? a ^ b : node.operator === "not" ? ~a : node.operator === "shl" ? a << b : node.operator === "shr" ? a >> b : (a >>> b) | 0;
|
|
298
|
+
out = { value_type: "int32", value };
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (out && !isTmwTypedValueV1(out.value_type, out.value))
|
|
303
|
+
throw new Error("tmw_value_runtime_type_mismatch");
|
|
304
|
+
active.delete(nodeId);
|
|
305
|
+
result.set(nodeId, out && Object.freeze(out));
|
|
306
|
+
return result.get(nodeId);
|
|
307
|
+
};
|
|
308
|
+
for (const nodeId of roots)
|
|
309
|
+
visit(nodeId);
|
|
310
|
+
return result;
|
|
311
|
+
}
|
|
312
|
+
export function resolveTmwValueGraphInputsV1(graph, targetId, context = {}) {
|
|
313
|
+
if (!graph)
|
|
314
|
+
return Object.freeze({});
|
|
315
|
+
const edges = graph.edges.filter((e) => e.to_node === targetId);
|
|
316
|
+
const values = evaluateTmwValueGraphV1(graph, context, edges.map((e) => e.from_node));
|
|
317
|
+
return Object.freeze(Object.fromEntries(edges.map((e) => [e.to_port, values.get(e.from_node)])));
|
|
318
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { TmwCommittedEventExecutionReceipt } from "./runtime.js";
|
|
2
|
+
import { type TmwJsonPrimitive } from "./runtime-contract.js";
|
|
3
|
+
import { type ViewPayloadSchemaV3 } from "./view-payload-schema.js";
|
|
4
|
+
export interface ViewEventDeliveryPayloadPathV1 {
|
|
5
|
+
readonly from: "viewVisiblePayload";
|
|
6
|
+
readonly path: ReadonlyArray<string>;
|
|
7
|
+
}
|
|
8
|
+
export interface ViewEventDeliveryLiteralV1 {
|
|
9
|
+
readonly literal: TmwJsonPrimitive;
|
|
10
|
+
}
|
|
11
|
+
export type ViewEventDeliveryFieldSourceV1 = ViewEventDeliveryPayloadPathV1 | ViewEventDeliveryLiteralV1;
|
|
12
|
+
export interface ViewEventDeliveryDeclarationV1 {
|
|
13
|
+
readonly deliveryId: string;
|
|
14
|
+
readonly eventType: string;
|
|
15
|
+
readonly payloadSchema: ViewPayloadSchemaV3;
|
|
16
|
+
readonly fields: Readonly<Record<string, ViewEventDeliveryFieldSourceV1>>;
|
|
17
|
+
}
|
|
18
|
+
export interface ViewEventDeliveryMessageV1 {
|
|
19
|
+
readonly deliveryId: string;
|
|
20
|
+
readonly eventType: string;
|
|
21
|
+
readonly payload: Readonly<Record<string, TmwJsonPrimitive>>;
|
|
22
|
+
/** 仅在此交付合同内稳定;UI 只能将其作为不透明去重键。 */
|
|
23
|
+
readonly opaqueId: string;
|
|
24
|
+
}
|
|
25
|
+
export type ViewEventDeliveryDeclarationParseResult = {
|
|
26
|
+
readonly ok: true;
|
|
27
|
+
readonly declarations: ReadonlyArray<ViewEventDeliveryDeclarationV1>;
|
|
28
|
+
} | {
|
|
29
|
+
readonly ok: false;
|
|
30
|
+
readonly code: string;
|
|
31
|
+
};
|
|
32
|
+
export type ViewEventDeliveryProjectionResult = {
|
|
33
|
+
readonly ok: true;
|
|
34
|
+
readonly deliveries: ReadonlyArray<ViewEventDeliveryMessageV1>;
|
|
35
|
+
} | {
|
|
36
|
+
readonly ok: false;
|
|
37
|
+
readonly code: string;
|
|
38
|
+
};
|
|
39
|
+
/** 在读取 Runtime authorViewEvents 前 fail-closed 地解析声明。 */
|
|
40
|
+
export declare const parseViewEventDeliveryDeclarations: (value: unknown) => ViewEventDeliveryDeclarationParseResult;
|
|
41
|
+
/** Runtime 成功 activation receipt 的可信下行项;本模块不自行伪造它。 */
|
|
42
|
+
export type TmwAuthorViewEventV1 = TmwCommittedEventExecutionReceipt["authorViewEvents"][number];
|
|
43
|
+
/**
|
|
44
|
+
* 仅将 Runtime 成功 activation receipt 内的 verified authorViewEvent 投影为
|
|
45
|
+
* 显式声明的 SDK 消息。绝不读取 committed event 或其 generic payload。
|
|
46
|
+
*/
|
|
47
|
+
export declare const projectAuthorViewEventToViewDeliveries: (authorViewEvent: TmwAuthorViewEventV1, declarationInput: unknown) => Promise<ViewEventDeliveryProjectionResult>;
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { canonicalTmwJson, tmwSha256Hex } from "./runtime-contract.js";
|
|
2
|
+
import { parseViewPayloadSchemaV3, viewPayloadMatchesSchemaV3, } from "./view-payload-schema.js";
|
|
3
|
+
/**
|
|
4
|
+
* Trigger committed event 是私有运行时事实。本模块只暴露显式声明的、
|
|
5
|
+
* 更小的 View SDK 下行面。
|
|
6
|
+
*/
|
|
7
|
+
const DELIVERY_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
8
|
+
const DELIVERY_EVENT_TYPE = /^[a-z][a-z0-9]*(?:[._:-][a-z0-9]+)*$/;
|
|
9
|
+
const MAX_DECLARATIONS = 128;
|
|
10
|
+
const MAX_MAPPINGS = 128;
|
|
11
|
+
const MAX_DELIVERY_BYTES = 32 * 1024;
|
|
12
|
+
const isRecord = (value) => {
|
|
13
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
14
|
+
return false;
|
|
15
|
+
const prototype = Object.getPrototypeOf(value);
|
|
16
|
+
return prototype === Object.prototype || prototype === null;
|
|
17
|
+
};
|
|
18
|
+
const ownData = (record, key) => {
|
|
19
|
+
const descriptor = Object.getOwnPropertyDescriptor(record, key);
|
|
20
|
+
return descriptor !== undefined && descriptor.enumerable && "value" in descriptor
|
|
21
|
+
? descriptor.value
|
|
22
|
+
: undefined;
|
|
23
|
+
};
|
|
24
|
+
const hasExactKeys = (record, keys) => {
|
|
25
|
+
const ownKeys = Reflect.ownKeys(record);
|
|
26
|
+
return ownKeys.length === keys.length
|
|
27
|
+
&& ownKeys.every((key) => typeof key === "string" && keys.includes(key))
|
|
28
|
+
&& keys.every((key) => ownData(record, key) !== undefined || Object.hasOwn(record, key));
|
|
29
|
+
};
|
|
30
|
+
const isPrimitive = (value) => value === null
|
|
31
|
+
|| typeof value === "string"
|
|
32
|
+
|| typeof value === "boolean"
|
|
33
|
+
|| (typeof value === "number" && Number.isFinite(value));
|
|
34
|
+
const isDeliveryIdentifier = (value) => typeof value === "string" && DELIVERY_ID.test(value);
|
|
35
|
+
const parseFieldSource = (value) => {
|
|
36
|
+
if (!isRecord(value))
|
|
37
|
+
return null;
|
|
38
|
+
if (hasExactKeys(value, ["literal"])) {
|
|
39
|
+
const literal = ownData(value, "literal");
|
|
40
|
+
return isPrimitive(literal) ? Object.freeze({ literal }) : null;
|
|
41
|
+
}
|
|
42
|
+
if (!hasExactKeys(value, ["from", "path"]))
|
|
43
|
+
return null;
|
|
44
|
+
const from = ownData(value, "from");
|
|
45
|
+
const path = ownData(value, "path");
|
|
46
|
+
if (from !== "viewVisiblePayload"
|
|
47
|
+
|| !Array.isArray(path)
|
|
48
|
+
|| path.length !== 1
|
|
49
|
+
|| !path.every(isDeliveryIdentifier))
|
|
50
|
+
return null;
|
|
51
|
+
return Object.freeze({ from, path: Object.freeze([...path]) });
|
|
52
|
+
};
|
|
53
|
+
const parseDeclaration = (value) => {
|
|
54
|
+
if (!isRecord(value) || !hasExactKeys(value, ["deliveryId", "eventType", "payloadSchema", "fields"])) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const deliveryId = ownData(value, "deliveryId");
|
|
58
|
+
const eventType = ownData(value, "eventType");
|
|
59
|
+
const payloadSchema = ownData(value, "payloadSchema");
|
|
60
|
+
const fields = ownData(value, "fields");
|
|
61
|
+
if (!isDeliveryIdentifier(deliveryId) || typeof eventType !== "string" || !DELIVERY_EVENT_TYPE.test(eventType)) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const schema = parseViewPayloadSchemaV3(payloadSchema);
|
|
65
|
+
if (schema === null || !isRecord(fields))
|
|
66
|
+
return null;
|
|
67
|
+
const schemaKeys = Object.keys(schema.properties).sort();
|
|
68
|
+
const fieldKeys = Object.keys(fields).sort();
|
|
69
|
+
if (fieldKeys.length > MAX_MAPPINGS || fieldKeys.some((key) => !Object.hasOwn(schema.properties, key))) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
const parsedFields = Object.create(null);
|
|
73
|
+
for (const key of fieldKeys) {
|
|
74
|
+
const field = parseFieldSource(ownData(fields, key));
|
|
75
|
+
if (field === null)
|
|
76
|
+
return null;
|
|
77
|
+
parsedFields[key] = field;
|
|
78
|
+
}
|
|
79
|
+
if (schemaKeys.some((key) => schema.properties[key].required && !Object.hasOwn(parsedFields, key))) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
return Object.freeze({
|
|
83
|
+
deliveryId,
|
|
84
|
+
eventType,
|
|
85
|
+
payloadSchema: schema,
|
|
86
|
+
fields: Object.freeze(parsedFields),
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
/** 在读取 Runtime authorViewEvents 前 fail-closed 地解析声明。 */
|
|
90
|
+
export const parseViewEventDeliveryDeclarations = (value) => {
|
|
91
|
+
if (!Array.isArray(value) || value.length > MAX_DECLARATIONS) {
|
|
92
|
+
return { ok: false, code: "view_delivery_declarations_invalid" };
|
|
93
|
+
}
|
|
94
|
+
const declarations = [];
|
|
95
|
+
const deliveryIds = new Set();
|
|
96
|
+
for (const item of value) {
|
|
97
|
+
const parsed = parseDeclaration(item);
|
|
98
|
+
if (parsed === null || deliveryIds.has(parsed.deliveryId)) {
|
|
99
|
+
return { ok: false, code: "view_delivery_declaration_invalid" };
|
|
100
|
+
}
|
|
101
|
+
deliveryIds.add(parsed.deliveryId);
|
|
102
|
+
declarations.push(parsed);
|
|
103
|
+
}
|
|
104
|
+
return { ok: true, declarations: Object.freeze(declarations) };
|
|
105
|
+
};
|
|
106
|
+
const sourceAuthorViewEvent = (value) => {
|
|
107
|
+
if (!isRecord(value))
|
|
108
|
+
return null;
|
|
109
|
+
const eventId = ownData(value, "eventId");
|
|
110
|
+
const eventType = ownData(value, "eventType");
|
|
111
|
+
const viewVisiblePayload = ownData(value, "viewVisiblePayload");
|
|
112
|
+
if (!isDeliveryIdentifier(eventId)
|
|
113
|
+
|| typeof eventType !== "string"
|
|
114
|
+
|| !DELIVERY_EVENT_TYPE.test(eventType)
|
|
115
|
+
|| !isRecord(viewVisiblePayload)
|
|
116
|
+
|| !Object.values(viewVisiblePayload).every(isPrimitive)) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const primitivePayload = Object.create(null);
|
|
120
|
+
for (const [key, item] of Object.entries(viewVisiblePayload)) {
|
|
121
|
+
if (!isPrimitive(item))
|
|
122
|
+
return null;
|
|
123
|
+
primitivePayload[key] = item;
|
|
124
|
+
}
|
|
125
|
+
return Object.freeze({
|
|
126
|
+
eventId,
|
|
127
|
+
eventType,
|
|
128
|
+
viewVisiblePayload: Object.freeze(primitivePayload),
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
const valueAtPath = (payload, path) => {
|
|
132
|
+
let cursor = payload;
|
|
133
|
+
for (const segment of path) {
|
|
134
|
+
if (!isRecord(cursor))
|
|
135
|
+
return undefined;
|
|
136
|
+
const value = ownData(cursor, segment);
|
|
137
|
+
if (value === undefined && !Object.hasOwn(cursor, segment))
|
|
138
|
+
return undefined;
|
|
139
|
+
cursor = value;
|
|
140
|
+
}
|
|
141
|
+
return isPrimitive(cursor) ? cursor : undefined;
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* 去重身份覆盖完整声明(包括字段来源),但不进入下行消息本体。字段按
|
|
145
|
+
* canonicalTmwJson 的键排序稳定编码,声明调整必然得到新的不透明键空间。
|
|
146
|
+
*/
|
|
147
|
+
const canonicalDeclarationIdentity = (declaration) => canonicalTmwJson({
|
|
148
|
+
deliveryId: declaration.deliveryId,
|
|
149
|
+
eventType: declaration.eventType,
|
|
150
|
+
payloadSchema: declaration.payloadSchema,
|
|
151
|
+
fields: declaration.fields,
|
|
152
|
+
});
|
|
153
|
+
const opaqueDeliveryId = async (declaration, eventId) => await tmwSha256Hex(new TextEncoder().encode(canonicalTmwJson({
|
|
154
|
+
declaration: canonicalDeclarationIdentity(declaration),
|
|
155
|
+
eventId,
|
|
156
|
+
})));
|
|
157
|
+
/**
|
|
158
|
+
* 仅将 Runtime 成功 activation receipt 内的 verified authorViewEvent 投影为
|
|
159
|
+
* 显式声明的 SDK 消息。绝不读取 committed event 或其 generic payload。
|
|
160
|
+
*/
|
|
161
|
+
export const projectAuthorViewEventToViewDeliveries = async (authorViewEvent, declarationInput) => {
|
|
162
|
+
const parsed = parseViewEventDeliveryDeclarations(declarationInput);
|
|
163
|
+
if (!parsed.ok)
|
|
164
|
+
return parsed;
|
|
165
|
+
const source = sourceAuthorViewEvent(authorViewEvent);
|
|
166
|
+
if (source === null)
|
|
167
|
+
return { ok: false, code: "view_delivery_event_identity_invalid" };
|
|
168
|
+
const deliveries = [];
|
|
169
|
+
for (const declaration of parsed.declarations) {
|
|
170
|
+
if (declaration.eventType !== source.eventType)
|
|
171
|
+
continue;
|
|
172
|
+
const payload = Object.create(null);
|
|
173
|
+
for (const key of Object.keys(declaration.fields).sort()) {
|
|
174
|
+
const sourceField = declaration.fields[key];
|
|
175
|
+
const value = "literal" in sourceField
|
|
176
|
+
? sourceField.literal
|
|
177
|
+
: valueAtPath(source.viewVisiblePayload, sourceField.path);
|
|
178
|
+
if (value === undefined)
|
|
179
|
+
return { ok: false, code: "view_delivery_source_value_invalid" };
|
|
180
|
+
payload[key] = value;
|
|
181
|
+
}
|
|
182
|
+
const frozenPayload = Object.freeze(payload);
|
|
183
|
+
if (!viewPayloadMatchesSchemaV3(frozenPayload, declaration.payloadSchema)) {
|
|
184
|
+
return { ok: false, code: "view_delivery_payload_invalid" };
|
|
185
|
+
}
|
|
186
|
+
if (new TextEncoder().encode(canonicalTmwJson(frozenPayload)).byteLength > MAX_DELIVERY_BYTES) {
|
|
187
|
+
return { ok: false, code: "view_delivery_payload_too_large" };
|
|
188
|
+
}
|
|
189
|
+
deliveries.push(Object.freeze({
|
|
190
|
+
deliveryId: declaration.deliveryId,
|
|
191
|
+
eventType: declaration.eventType,
|
|
192
|
+
payload: frozenPayload,
|
|
193
|
+
opaqueId: await opaqueDeliveryId(declaration, source.eventId),
|
|
194
|
+
}));
|
|
195
|
+
}
|
|
196
|
+
return { ok: true, deliveries: Object.freeze(deliveries) };
|
|
197
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { TMW_RUNTIME_GENERATION } from "./runtime-contract.js";
|
|
2
|
+
import { type TmwJsonValue } from "./runtime-contract.js";
|
|
3
|
+
import { type ViewPayloadSchemaV3 } from "./view-payload-schema.js";
|
|
4
|
+
import { type ViewEventDeliveryDeclarationV1 } from "./view-event-delivery.js";
|
|
5
|
+
export { parseViewPayloadSchemaV3, viewPayloadMatchesSchemaV3, type ViewPayloadPrimitiveKind, type ViewPayloadPropertyV3, type ViewPayloadSchemaV3, } from "./view-payload-schema.js";
|
|
6
|
+
export interface ViewInteractionEventDeclarationV3 {
|
|
7
|
+
readonly event_id: string;
|
|
8
|
+
readonly event_type: string;
|
|
9
|
+
readonly capability: string;
|
|
10
|
+
readonly payload_schema: ViewPayloadSchemaV3;
|
|
11
|
+
}
|
|
12
|
+
export interface ViewInteractionManifestV3 {
|
|
13
|
+
readonly schema: "view-interaction-manifest.v3";
|
|
14
|
+
readonly generation: typeof TMW_RUNTIME_GENERATION;
|
|
15
|
+
readonly epoch: number;
|
|
16
|
+
readonly artifact_digest: string;
|
|
17
|
+
readonly runtime_contract_hash: string;
|
|
18
|
+
readonly bootstrap_url: string;
|
|
19
|
+
readonly capabilities: ReadonlyArray<string>;
|
|
20
|
+
readonly events: ReadonlyArray<ViewInteractionEventDeclarationV3>;
|
|
21
|
+
/** 可选的 committed-event 到 View SDK 下行投影;缺席时绝不下行。 */
|
|
22
|
+
readonly view_deliveries?: ReadonlyArray<ViewEventDeliveryDeclarationV1>;
|
|
23
|
+
}
|
|
24
|
+
/** ArtifactV5 编译器消费的唯一 view interaction source。 */
|
|
25
|
+
export interface ViewInteractionSourceV3 {
|
|
26
|
+
readonly schema: "view-interaction-source.v3";
|
|
27
|
+
readonly generation: typeof TMW_RUNTIME_GENERATION;
|
|
28
|
+
readonly manifest: ViewInteractionManifestV3;
|
|
29
|
+
readonly manifest_digest: string;
|
|
30
|
+
}
|
|
31
|
+
export interface ViewInteractionEventV3 {
|
|
32
|
+
readonly schema: "view-interaction-event.v3";
|
|
33
|
+
readonly generation: typeof TMW_RUNTIME_GENERATION;
|
|
34
|
+
readonly event_id: string;
|
|
35
|
+
readonly event_type: string;
|
|
36
|
+
readonly payload: Readonly<Record<string, TmwJsonValue>>;
|
|
37
|
+
readonly interaction_id: string;
|
|
38
|
+
readonly manifest_digest: string;
|
|
39
|
+
readonly epoch: number;
|
|
40
|
+
readonly artifact_digest: string;
|
|
41
|
+
readonly nonce: string;
|
|
42
|
+
}
|
|
43
|
+
export type ViewInteractionParseResult = {
|
|
44
|
+
readonly ok: true;
|
|
45
|
+
readonly event: ViewInteractionEventV3;
|
|
46
|
+
} | {
|
|
47
|
+
readonly ok: false;
|
|
48
|
+
readonly code: string;
|
|
49
|
+
};
|
|
50
|
+
export interface ViewInteractionEventGateV3 {
|
|
51
|
+
parse(value: unknown): ViewInteractionParseResult;
|
|
52
|
+
clear(): void;
|
|
53
|
+
}
|
|
54
|
+
export declare const parseViewInteractionManifestV3: (value: unknown) => ViewInteractionManifestV3 | null;
|
|
55
|
+
export declare const parseViewInteractionSourceV3: (value: unknown) => ViewInteractionSourceV3 | null;
|
|
56
|
+
export declare const canonicalViewInteractionManifestV3: (manifest: ViewInteractionManifestV3) => string;
|
|
57
|
+
export declare const digestViewInteractionManifestV3: (manifest: ViewInteractionManifestV3) => Promise<string>;
|
|
58
|
+
export declare const parseViewInteractionEventV3: (value: unknown, manifest: ViewInteractionManifestV3, manifestDigest: string) => ViewInteractionParseResult;
|
|
59
|
+
/** Pure stateful replay gate; the Player/preview host remains the sole listener. */
|
|
60
|
+
export declare const createViewInteractionEventGateV3: (manifest: ViewInteractionManifestV3, manifestDigest: string, maxReplayNonces?: number) => ViewInteractionEventGateV3;
|