@babylonjs/loaders 9.26.2 → 9.27.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/glTF/2.0/Extensions/EXT_texture_avif.pure.js +1 -1
  2. package/glTF/2.0/Extensions/EXT_texture_avif.pure.js.map +1 -1
  3. package/glTF/2.0/Extensions/EXT_texture_webp.pure.js +1 -1
  4. package/glTF/2.0/Extensions/EXT_texture_webp.pure.js.map +1 -1
  5. package/glTF/2.0/Extensions/KHR_interactivity/declarationMapper.d.ts +33 -0
  6. package/glTF/2.0/Extensions/KHR_interactivity/declarationMapper.js +166 -37
  7. package/glTF/2.0/Extensions/KHR_interactivity/declarationMapper.js.map +1 -1
  8. package/glTF/2.0/Extensions/KHR_interactivity/index.d.ts +1 -0
  9. package/glTF/2.0/Extensions/KHR_interactivity/index.js +1 -0
  10. package/glTF/2.0/Extensions/KHR_interactivity/index.js.map +1 -1
  11. package/glTF/2.0/Extensions/KHR_interactivity/interactivityGraphExporter.d.ts +243 -0
  12. package/glTF/2.0/Extensions/KHR_interactivity/interactivityGraphExporter.js +2335 -0
  13. package/glTF/2.0/Extensions/KHR_interactivity/interactivityGraphExporter.js.map +1 -0
  14. package/glTF/2.0/Extensions/KHR_interactivity/interactivityGraphModel.d.ts +82 -1
  15. package/glTF/2.0/Extensions/KHR_interactivity/interactivityGraphModel.js +180 -34
  16. package/glTF/2.0/Extensions/KHR_interactivity/interactivityGraphModel.js.map +1 -1
  17. package/glTF/2.0/Extensions/KHR_interactivity/interactivityGraphParser.d.ts +14 -2
  18. package/glTF/2.0/Extensions/KHR_interactivity/interactivityGraphParser.js +220 -34
  19. package/glTF/2.0/Extensions/KHR_interactivity/interactivityGraphParser.js.map +1 -1
  20. package/glTF/2.0/Extensions/KHR_interactivity/pure.d.ts +1 -0
  21. package/glTF/2.0/Extensions/KHR_interactivity/pure.js +1 -0
  22. package/glTF/2.0/Extensions/KHR_interactivity/pure.js.map +1 -1
  23. package/glTF/2.0/Extensions/KHR_interactivity.pure.d.ts +4 -0
  24. package/glTF/2.0/Extensions/KHR_interactivity.pure.js +6 -2
  25. package/glTF/2.0/Extensions/KHR_interactivity.pure.js.map +1 -1
  26. package/glTF/2.0/Extensions/KHR_texture_basisu.pure.js +1 -1
  27. package/glTF/2.0/Extensions/KHR_texture_basisu.pure.js.map +1 -1
  28. package/glTF/2.0/glTFLoader.pure.d.ts +2 -1
  29. package/glTF/2.0/glTFLoader.pure.js +41 -4
  30. package/glTF/2.0/glTFLoader.pure.js.map +1 -1
  31. package/glTF/2.0/glTFLoaderInterfaces.d.ts +9 -0
  32. package/glTF/2.0/glTFLoaderInterfaces.js.map +1 -1
  33. package/package.json +3 -3
@@ -1,4 +1,4 @@
1
- import { type IKHRInteractivity, type IKHRInteractivity_Declaration, type IKHRInteractivity_Graph } from "babylonjs-gltf2interface";
1
+ import { type IKHRInteractivity, type IKHRInteractivity_Declaration, type IKHRInteractivity_Graph, type IKHRInteractivity_OutputSocketReference, type IKHRInteractivity_Variable } from "babylonjs-gltf2interface";
2
2
  import { FlowGraphTypes } from "@babylonjs/core/FlowGraph/flowGraphRichTypes.js";
3
3
  /**
4
4
  * Ratified KHR_interactivity specification baseline used by this importer.
@@ -29,6 +29,87 @@ export interface IKHRInteractivityDiagnostic {
29
29
  /** Diagnostic severity. */
30
30
  severity: "error" | "warning";
31
31
  }
32
+ /**
33
+ * Stable source identity retained on every FlowGraph block lowered from a KHR_interactivity node.
34
+ */
35
+ export interface IKHRInteractivityBlockProvenance {
36
+ /** Source graph index. */
37
+ graphIndex: number;
38
+ /** Source node index. */
39
+ nodeIndex: number;
40
+ /** Source declaration index. */
41
+ declarationIndex: number;
42
+ /** Full operation name. */
43
+ operation: string;
44
+ /** Role of this block in a one-to-many mapping. Negative roles identify generated helper blocks. */
45
+ role: number;
46
+ /** Stable JSON pointer to the source node. */
47
+ sourcePath: string;
48
+ /** Original and lowered configuration values used for safe inverse transforms. */
49
+ configuration?: Record<string, IKHRInteractivityConfigurationProvenance>;
50
+ /** Importer-generated primitive configuration that must remain unchanged for inverse export. */
51
+ generatedConfiguration?: Record<string, unknown>;
52
+ /** Runtime fingerprints for generated configuration that changes representation while parsing. */
53
+ generatedConfigurationRuntime?: Record<string, IKHRInteractivityInputDefaultProvenance>;
54
+ /** Importer-generated unconnected input defaults that have no authored KHR socket representation. */
55
+ generatedInputDefaults?: Record<string, IKHRInteractivityInputDefaultProvenance>;
56
+ }
57
+ /**
58
+ * Import-time value retained for an unconnected FlowGraph input that is not represented by an authored KHR socket.
59
+ */
60
+ export interface IKHRInteractivityInputDefaultProvenance {
61
+ /** Type-tagged JSON-stable fingerprint of the normalized runtime input value. */
62
+ runtimeValueFingerprint?: string;
63
+ /** Whether the import-time value could not be fingerprinted safely. */
64
+ unrepresentable?: true;
65
+ }
66
+ /**
67
+ * Provenance for a transformed KHR configuration value.
68
+ */
69
+ export interface IKHRInteractivityConfigurationProvenance {
70
+ /** Exact source configuration array. */
71
+ sourceValue?: unknown[];
72
+ /** Lowered FlowGraph configuration value immediately after import. */
73
+ runtimeValue?: unknown;
74
+ }
75
+ /** @internal */
76
+ export declare function _NormalizeKHRInteractivityRuntimeValue(value: unknown): unknown[] | undefined;
77
+ /** @internal */
78
+ export declare function _CreateKHRInteractivityRuntimeValueSnapshot(value: unknown): IKHRInteractivityInputDefaultProvenance;
79
+ /**
80
+ * Stable source identity retained on a FlowGraph connection lowered from a KHR_interactivity socket.
81
+ */
82
+ export interface IKHRInteractivitySocketProvenance extends IKHRInteractivityBlockProvenance {
83
+ /** Socket category. */
84
+ kind: "value" | "flow";
85
+ /** Whether the socket is consumed by or produced from the logical KHR node. */
86
+ direction: "input" | "output";
87
+ /** Exact source KHR socket identifier. */
88
+ socket: string;
89
+ /** Exact canonical source value, when this is an input value socket. */
90
+ sourceValue?: IKHRInteractivity_Variable | IKHRInteractivity_OutputSocketReference;
91
+ /** Lowered inline runtime value immediately after import. */
92
+ runtimeValue?: unknown[];
93
+ /** Type-tagged runtime fingerprint used to distinguish parsing precision changes from edits. */
94
+ runtimeValueSnapshot?: IKHRInteractivityInputDefaultProvenance;
95
+ }
96
+ /**
97
+ * Canonical source retained on a FlowGraph lowered from KHR_interactivity.
98
+ */
99
+ export interface IKHRInteractivityGraphProvenance {
100
+ /** Source graph index. */
101
+ graphIndex: number;
102
+ /** Ratified specification revision used by the importer. */
103
+ specificationCommit: string;
104
+ /** Detached canonical source graph. */
105
+ source: IKHRInteractivity_Graph;
106
+ /** Explicit editor-authored variable defaults keyed by canonical variable index. */
107
+ authoredVariableValues?: Record<number, unknown[]>;
108
+ /** Explicit editor-authored FlowGraph variable types keyed by canonical variable index. */
109
+ authoredVariableTypes?: Record<number, string>;
110
+ /** True when variables were explicitly added, renamed, or deleted in the editor. */
111
+ authoredVariableStructureChanged?: boolean;
112
+ }
32
113
  /**
33
114
  * Canonical declaration information retained during import.
34
115
  */
@@ -20,6 +20,95 @@ export const gltfTypeToBabylonType = {
20
20
  ref: { length: 1, flowGraphType: "string" /* FlowGraphTypes.String */, elementType: "string" },
21
21
  custom: { length: 0, flowGraphType: "any" /* FlowGraphTypes.Any */, elementType: "any" },
22
22
  };
23
+ /** @internal */
24
+ export function _NormalizeKHRInteractivityRuntimeValue(value) {
25
+ if (value === undefined) {
26
+ return undefined;
27
+ }
28
+ if (value !== null && typeof value === "object") {
29
+ const asArray = value.asArray;
30
+ if (typeof asArray === "function") {
31
+ return Array.from(asArray.call(value));
32
+ }
33
+ if ("value" in value && typeof value.value !== "object") {
34
+ return [value.value];
35
+ }
36
+ }
37
+ return Array.isArray(value) ? value.slice() : [value];
38
+ }
39
+ function _EncodeRuntimeValueSnapshot(value, ancestors) {
40
+ if (value === undefined) {
41
+ return ["undefined"];
42
+ }
43
+ if (value === null) {
44
+ return ["null"];
45
+ }
46
+ switch (typeof value) {
47
+ case "boolean":
48
+ return ["boolean", value];
49
+ case "number":
50
+ return ["number", Number.isNaN(value) ? "NaN" : value === Infinity ? "Infinity" : value === -Infinity ? "-Infinity" : Object.is(value, -0) ? "-0" : value];
51
+ case "string":
52
+ return ["string", value];
53
+ case "object": {
54
+ if (Object.getOwnPropertySymbols(value).length > 0) {
55
+ return undefined;
56
+ }
57
+ if (Array.isArray(value)) {
58
+ const hasNonIndexProperty = Object.getOwnPropertyNames(value).some((key) => key !== "length" && (!/^(0|[1-9]\d*)$/.test(key) || Number(key) >= value.length || !("value" in Object.getOwnPropertyDescriptor(value, key))));
59
+ if (hasNonIndexProperty) {
60
+ return undefined;
61
+ }
62
+ }
63
+ else {
64
+ const prototype = Object.getPrototypeOf(value);
65
+ if (prototype !== Object.prototype && prototype !== null) {
66
+ return undefined;
67
+ }
68
+ const hasNonJsonProperty = Object.getOwnPropertyNames(value).some((key) => !Object.prototype.propertyIsEnumerable.call(value, key) || !("value" in Object.getOwnPropertyDescriptor(value, key)));
69
+ if (hasNonJsonProperty) {
70
+ return undefined;
71
+ }
72
+ }
73
+ if (ancestors.has(value)) {
74
+ return undefined;
75
+ }
76
+ ancestors.add(value);
77
+ let encoded;
78
+ if (Array.isArray(value)) {
79
+ const entries = value.map((entry) => _EncodeRuntimeValueSnapshot(entry, ancestors));
80
+ encoded = entries.some((entry) => entry === undefined) ? undefined : ["array", entries];
81
+ }
82
+ else {
83
+ const entries = [];
84
+ let unrepresentable = false;
85
+ for (const key of Object.keys(value).sort()) {
86
+ const entry = _EncodeRuntimeValueSnapshot(value[key], ancestors);
87
+ if (entry === undefined) {
88
+ unrepresentable = true;
89
+ break;
90
+ }
91
+ entries.push([key, entry]);
92
+ }
93
+ encoded = unrepresentable ? undefined : ["object", entries];
94
+ }
95
+ ancestors.delete(value);
96
+ return encoded;
97
+ }
98
+ default:
99
+ return undefined;
100
+ }
101
+ }
102
+ /** @internal */
103
+ export function _CreateKHRInteractivityRuntimeValueSnapshot(value) {
104
+ try {
105
+ const encoded = _EncodeRuntimeValueSnapshot(Array.isArray(value) ? value : _NormalizeKHRInteractivityRuntimeValue(value), new Set());
106
+ return encoded === undefined ? { unrepresentable: true } : { runtimeValueFingerprint: JSON.stringify(encoded) };
107
+ }
108
+ catch {
109
+ return { unrepresentable: true };
110
+ }
111
+ }
23
112
  function _cloneJson(value) {
24
113
  if (Array.isArray(value)) {
25
114
  return value.map((entry) => _cloneJson(entry));
@@ -49,6 +138,9 @@ export function CloneKHRInteractivityGraph(graph) {
49
138
  function _isValidIndex(value, length) {
50
139
  return typeof value === "number" && Number.isInteger(value) && value >= 0 && value < length;
51
140
  }
141
+ function _isRecord(value) {
142
+ return value !== null && typeof value === "object" && !Array.isArray(value);
143
+ }
52
144
  function _addError(diagnostics, path, message) {
53
145
  diagnostics.push({ path, message, severity: "error" });
54
146
  }
@@ -88,6 +180,10 @@ function _validateValue(value, graph, diagnostics, path) {
88
180
  if (value.value === undefined) {
89
181
  return;
90
182
  }
183
+ if (!Array.isArray(value.value)) {
184
+ _addError(diagnostics, `${path}/value`, "Value must be an array when present.");
185
+ return;
186
+ }
91
187
  const type = graph.types[value.type];
92
188
  const runtimeType = gltfTypeToBabylonType[type.signature];
93
189
  if (runtimeType && type.signature !== "custom" && value.value.length !== runtimeType.length) {
@@ -144,12 +240,12 @@ function _isCompatibleExtensionDeclaration(declaration, graph, schema) {
144
240
  if (actualKeys.length !== expectedKeys.length || actualKeys.some((key, index) => key !== expectedKeys[index])) {
145
241
  return false;
146
242
  }
147
- return actualKeys.every((key) => graph.types?.[actual[key].type]?.signature === expected[key]);
243
+ return actualKeys.every((key) => _isRecord(actual[key]) && graph.types?.[actual[key].type]?.signature === expected[key]);
148
244
  };
149
245
  return matches(declaration.inputValueSockets, schema.inputValueSockets) && matches(declaration.outputValueSockets, schema.outputValueSockets);
150
246
  }
151
247
  function _validateConfigurationValue(value, type) {
152
- if (!value) {
248
+ if (!Array.isArray(value)) {
153
249
  return false;
154
250
  }
155
251
  const isInt = (entry) => typeof entry === "number" && Number.isInteger(entry) && entry >= -2147483648 && entry <= 2147483647;
@@ -208,30 +304,14 @@ function _isEffectiveConfigurationValue(configuration, property, graph, assetNod
208
304
  return false;
209
305
  }
210
306
  }
307
+ if (property.allowedValues && configuration.value.some((value) => !property.allowedValues.includes(value))) {
308
+ return false;
309
+ }
211
310
  if (property.debugLogTemplate && !ParseDebugLogTemplate(configuration.value[0]).valid) {
212
311
  return false;
213
312
  }
214
313
  return true;
215
314
  }
216
- function _getAllowedDynamicFlowOutputSockets(node, mapping, graph, assetNodeCount) {
217
- for (const [key, property] of Object.entries(mapping.configuration ?? {})) {
218
- if (!property.generatesOutputFlowSockets) {
219
- continue;
220
- }
221
- const configured = node.configuration?.[key];
222
- const values = _isEffectiveConfigurationValue(configured, property, graph, assetNodeCount) ? configured.value : property.defaultValue;
223
- return new Set(Array.isArray(values) ? values.map(String) : []);
224
- }
225
- return undefined;
226
- }
227
- function _matchesFlowOutputSocket(mapping, socket, allowedDynamicSockets) {
228
- const flowMappings = mapping.outputs?.flows;
229
- if (flowMappings && Object.prototype.hasOwnProperty.call(flowMappings, socket)) {
230
- return true;
231
- }
232
- const hasWildcard = Object.keys(flowMappings ?? {}).some((name) => name.startsWith("[") && name.endsWith("]"));
233
- return !hasWildcard || !allowedDynamicSockets || allowedDynamicSockets.has(socket);
234
- }
235
315
  /**
236
316
  * Creates the executable graph view with all ratified configuration defaults and fallback rules
237
317
  * applied without modifying the lossless source graph.
@@ -313,7 +393,11 @@ function _resolveOutputTypeIndex(nodeIndex, socket, graph, declarations, visited
313
393
  if (declaredType !== undefined) {
314
394
  return declaredType;
315
395
  }
316
- const booleanType = () => graph.types?.findIndex((type) => type.signature === "bool");
396
+ const findTypeIndex = (signature) => {
397
+ const index = graph.types?.findIndex((type) => type.signature === signature) ?? -1;
398
+ return index >= 0 ? index : undefined;
399
+ };
400
+ const booleanType = () => findTypeIndex("bool");
317
401
  if (socket === "isValid") {
318
402
  return booleanType();
319
403
  }
@@ -335,14 +419,14 @@ function _resolveOutputTypeIndex(nodeIndex, socket, graph, declarations, visited
335
419
  }
336
420
  const fixedSignature = _getFixedOutputSignature(declaration.operation, socket);
337
421
  if (fixedSignature) {
338
- return graph.types?.findIndex((type) => type.signature === fixedSignature);
422
+ return findTypeIndex(String(fixedSignature));
339
423
  }
340
424
  const mapping = getMappingForDeclaration(declaration.source, false);
341
425
  const outputMapping = mapping?.outputs?.values?.[socket];
342
426
  const mappedType = outputMapping?.gltfType;
343
427
  const signature = mappedType === "number" ? "float" : mappedType === "boolean" ? "bool" : mappedType;
344
428
  if (signature && signature in gltfTypeToBabylonType) {
345
- return graph.types?.findIndex((type) => type.signature === signature);
429
+ return findTypeIndex(signature);
346
430
  }
347
431
  const typeSource = outputMapping?.typeSourceInput ? sourceNode.values?.[outputMapping.typeSourceInput] : undefined;
348
432
  return typeSource ? _resolveValueTypeIndex(typeSource, graph, declarations, visited) : undefined;
@@ -414,7 +498,14 @@ function _validateNode(node, nodeIndex, graph, declarations, diagnostics, graphP
414
498
  }
415
499
  };
416
500
  for (const [key, configuration] of Object.entries(node.configuration ?? {})) {
417
- if (configuration.value && configuration.value.length === 0) {
501
+ if (!_isRecord(configuration)) {
502
+ _addError(diagnostics, `${path}/configuration/${key}`, `Configuration "${key}" must be an object.`);
503
+ continue;
504
+ }
505
+ if (configuration.value !== undefined && !Array.isArray(configuration.value)) {
506
+ _addError(diagnostics, `${path}/configuration/${key}/value`, "Configuration value must be an array when present.");
507
+ }
508
+ else if (configuration.value && configuration.value.length === 0) {
418
509
  _addError(diagnostics, `${path}/configuration/${key}/value`, "Configuration value arrays must contain at least one item when present.");
419
510
  }
420
511
  }
@@ -445,6 +536,9 @@ function _validateNode(node, nodeIndex, graph, declarations, diagnostics, graphP
445
536
  reportConfigurationIssue(property, `${path}/configuration/${key}/value/0`, `Type "${String(signature)}" is not supported by this operation.`);
446
537
  }
447
538
  }
539
+ if (isConfigurationValid && configuration.value && property.allowedValues && configuration.value.some((value) => !property.allowedValues.includes(value))) {
540
+ reportConfigurationIssue(property, `${path}/configuration/${key}/value`, `Configuration "${key}" contains an unsupported value.`);
541
+ }
448
542
  if (isConfigurationValid && configuration.value && property.debugLogTemplate && !ParseDebugLogTemplate(configuration.value[0]).valid) {
449
543
  reportConfigurationIssue(property, `${path}/configuration/${key}/value/0`, `Configuration "${key}" is not a valid debug message template.`);
450
544
  }
@@ -549,6 +643,10 @@ function _validateNode(node, nodeIndex, graph, declarations, diagnostics, graphP
549
643
  }
550
644
  }
551
645
  for (const [key, value] of Object.entries(node.values ?? {})) {
646
+ if (!_isRecord(value)) {
647
+ _addError(diagnostics, `${path}/values/${key}`, `Input value socket "${key}" must be an object.`);
648
+ continue;
649
+ }
552
650
  if (declarationModel.support === "unsupported-extension" && !declarationModel.source.inputValueSockets?.[key]) {
553
651
  _addError(diagnostics, `${path}/values/${key}`, `Input value socket "${key}" is not defined by operation "${declarationModel.operation}".`);
554
652
  }
@@ -579,8 +677,28 @@ function _validateNode(node, nodeIndex, graph, declarations, diagnostics, graphP
579
677
  }
580
678
  const declaredOutputType = sourceDeclaration.source.outputValueSockets?.[sourceSocket]?.type;
581
679
  const effectiveOutputType = declaredOutputType ?? _resolveOutputTypeIndex(value.node, sourceSocket, graph, declarations);
582
- if (value.type !== undefined && effectiveOutputType !== undefined && value.type !== effectiveOutputType) {
583
- _addError(diagnostics, `${path}/values/${key}/type`, `Type assertion does not match output socket "${sourceSocket}".`);
680
+ if (value.type !== undefined) {
681
+ const mappedOutputs = sourceMapping?.outputs?.values;
682
+ const mappedOutput = mappedOutputs && Object.prototype.hasOwnProperty.call(mappedOutputs, sourceSocket) ? mappedOutputs[sourceSocket] : undefined;
683
+ const mappedType = mappedOutput?.gltfType;
684
+ const mappedOutputSignature = mappedType === "number"
685
+ ? "float"
686
+ : mappedType === "boolean"
687
+ ? "bool"
688
+ : mappedType === "vector2"
689
+ ? "float2"
690
+ : mappedType === "vector3"
691
+ ? "float3"
692
+ : mappedType === "vector4"
693
+ ? "float4"
694
+ : mappedType;
695
+ const fixedOutputSignature = _getFixedOutputSignature(sourceDeclaration.operation, sourceSocket) ??
696
+ (mappedOutputSignature && Object.prototype.hasOwnProperty.call(gltfTypeToBabylonType, mappedOutputSignature) ? mappedOutputSignature : undefined);
697
+ const assertedSignature = graph.types?.[value.type]?.signature;
698
+ if ((fixedOutputSignature !== undefined && assertedSignature !== fixedOutputSignature) ||
699
+ (fixedOutputSignature === undefined && effectiveOutputType !== undefined && value.type !== effectiveOutputType)) {
700
+ _addError(diagnostics, `${path}/values/${key}/type`, `Type assertion does not match output socket "${sourceSocket}".`);
701
+ }
584
702
  }
585
703
  }
586
704
  }
@@ -589,9 +707,9 @@ function _validateNode(node, nodeIndex, graph, declarations, diagnostics, graphP
589
707
  }
590
708
  }
591
709
  for (const [key, flow] of Object.entries(node.flows ?? {})) {
592
- const allowedDynamicSockets = _getAllowedDynamicFlowOutputSockets(node, mapping, graph, assetNodeCount);
593
- if (!_matchesFlowOutputSocket(mapping, key, allowedDynamicSockets)) {
594
- _addError(diagnostics, `${path}/flows/${key}`, `Output flow socket "${key}" is not defined by operation "${declarationModel.operation}".`);
710
+ if (!_isRecord(flow)) {
711
+ _addError(diagnostics, `${path}/flows/${key}`, `Output flow socket "${key}" must be an object.`);
712
+ continue;
595
713
  }
596
714
  if (!_isValidIndex(flow.node, graph.nodes?.length ?? 0)) {
597
715
  _addError(diagnostics, `${path}/flows/${key}/node`, `Node index ${String(flow.node)} is out of range.`);
@@ -647,14 +765,23 @@ export function CreateKHRInteractivityGraphModel(graph, index = 0, supportedExte
647
765
  }
648
766
  }
649
767
  for (let eventIndex = 0; eventIndex < (graph.events?.length ?? 0); eventIndex++) {
650
- if (graph.events[eventIndex].values && Object.keys(graph.events[eventIndex].values).length === 0) {
768
+ const event = graph.events[eventIndex];
769
+ if (event.values !== undefined && !_isRecord(event.values)) {
770
+ _addError(diagnostics, `${path}/events/${eventIndex}/values`, '"values" must be an object when present.');
771
+ event.values = undefined;
772
+ }
773
+ else if (event.values && Object.keys(event.values).length === 0) {
651
774
  _addError(diagnostics, `${path}/events/${eventIndex}/values`, '"values" must contain at least one property when present.');
652
775
  }
653
776
  }
654
777
  for (let declarationIndex = 0; declarationIndex < (graph.declarations?.length ?? 0); declarationIndex++) {
655
778
  const declaration = graph.declarations[declarationIndex];
656
779
  for (const key of ["inputValueSockets", "outputValueSockets"]) {
657
- if (declaration[key] && Object.keys(declaration[key]).length === 0) {
780
+ if (declaration[key] !== undefined && !_isRecord(declaration[key])) {
781
+ _addError(diagnostics, `${path}/declarations/${declarationIndex}/${key}`, `"${key}" must be an object when present.`);
782
+ declaration[key] = undefined;
783
+ }
784
+ else if (declaration[key] && Object.keys(declaration[key]).length === 0) {
658
785
  _addError(diagnostics, `${path}/declarations/${declarationIndex}/${key}`, `"${key}" must contain at least one property when present.`);
659
786
  }
660
787
  }
@@ -662,7 +789,11 @@ export function CreateKHRInteractivityGraphModel(graph, index = 0, supportedExte
662
789
  for (let nodeIndex = 0; nodeIndex < (graph.nodes?.length ?? 0); nodeIndex++) {
663
790
  const node = graph.nodes[nodeIndex];
664
791
  for (const key of ["configuration", "values", "flows"]) {
665
- if (node[key] && Object.keys(node[key]).length === 0) {
792
+ if (node[key] !== undefined && !_isRecord(node[key])) {
793
+ _addError(diagnostics, `${path}/nodes/${nodeIndex}/${key}`, `"${key}" must be an object when present.`);
794
+ node[key] = undefined;
795
+ }
796
+ else if (node[key] && Object.keys(node[key]).length === 0) {
666
797
  _addError(diagnostics, `${path}/nodes/${nodeIndex}/${key}`, `"${key}" must contain at least one property when present.`);
667
798
  }
668
799
  }
@@ -671,7 +802,10 @@ export function CreateKHRInteractivityGraphModel(graph, index = 0, supportedExte
671
802
  const declarations = (graph.declarations ?? []).map((declaration, declarationIndex) => {
672
803
  const inputSockets = Object.keys(declaration.inputValueSockets ?? {})
673
804
  .sort()
674
- .map((key) => `${JSON.stringify(key)}:${declaration.inputValueSockets[key].type}`)
805
+ .map((key) => {
806
+ const definition = declaration.inputValueSockets[key];
807
+ return `${JSON.stringify(key)}:${_isRecord(definition) ? String(definition.type) : "invalid"}`;
808
+ })
675
809
  .join(",");
676
810
  const declarationKey = `${JSON.stringify(declaration.op)}:${JSON.stringify(declaration.extension ?? "")}:${inputSockets}`;
677
811
  if (declarationKeys.has(declarationKey)) {
@@ -686,9 +820,17 @@ export function CreateKHRInteractivityGraphModel(graph, index = 0, supportedExte
686
820
  _addError(diagnostics, `${path}/declarations/${declarationIndex}`, "Core operation declarations cannot define dynamic value sockets.");
687
821
  }
688
822
  for (const [socket, definition] of Object.entries(declaration.inputValueSockets ?? {})) {
823
+ if (!_isRecord(definition)) {
824
+ _addError(diagnostics, `${path}/declarations/${declarationIndex}/inputValueSockets/${socket}`, `Input value socket "${socket}" must be an object.`);
825
+ continue;
826
+ }
689
827
  _validateTypeIndex(definition.type, graph, diagnostics, `${path}/declarations/${declarationIndex}/inputValueSockets/${socket}/type`);
690
828
  }
691
829
  for (const [socket, definition] of Object.entries(declaration.outputValueSockets ?? {})) {
830
+ if (!_isRecord(definition)) {
831
+ _addError(diagnostics, `${path}/declarations/${declarationIndex}/outputValueSockets/${socket}`, `Output value socket "${socket}" must be an object.`);
832
+ continue;
833
+ }
692
834
  _validateTypeIndex(definition.type, graph, diagnostics, `${path}/declarations/${declarationIndex}/outputValueSockets/${socket}/type`);
693
835
  }
694
836
  return {
@@ -721,6 +863,10 @@ export function CreateKHRInteractivityGraphModel(graph, index = 0, supportedExte
721
863
  _addError(diagnostics, `${path}/events/${eventIndex}/values/event`, 'Custom event value socket id "event" is reserved.');
722
864
  }
723
865
  for (const [socket, value] of Object.entries(graph.events[eventIndex].values ?? {})) {
866
+ if (!_isRecord(value)) {
867
+ _addError(diagnostics, `${path}/events/${eventIndex}/values/${socket}`, `Event value socket "${socket}" must be an object.`);
868
+ continue;
869
+ }
724
870
  _validateValue(value, graph, diagnostics, `${path}/events/${eventIndex}/values/${socket}`);
725
871
  }
726
872
  }