@vizij/node-graph-authoring 0.0.2 → 0.0.5

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.
@@ -0,0 +1,4046 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // src/cli/reportIr.ts
5
+ var import_node_fs = require("fs");
6
+ var import_node_path = require("path");
7
+
8
+ // src/graphBuilder.ts
9
+ var import_utils2 = require("@vizij/utils");
10
+ var import_utils3 = require("@vizij/utils");
11
+
12
+ // src/state.ts
13
+ var import_utils = require("@vizij/utils");
14
+ var VECTOR_ANIMATABLE_TYPES = /* @__PURE__ */ new Set(["vector2", "vector3", "euler", "rgb"]);
15
+ function deriveComponentValueType(component) {
16
+ if (component.component) {
17
+ return "scalar";
18
+ }
19
+ return VECTOR_ANIMATABLE_TYPES.has(component.animatableType) ? "vector" : "scalar";
20
+ }
21
+ function isBindingValueType(value) {
22
+ return value === "scalar" || value === "vector";
23
+ }
24
+ function getTargetValueType(target) {
25
+ return isBindingValueType(target.valueType) ? target.valueType : "scalar";
26
+ }
27
+ function sanitizeSlotValueType(value, targetType) {
28
+ return isBindingValueType(value) ? value : targetType;
29
+ }
30
+ function bindingTargetFromComponent(component) {
31
+ return {
32
+ id: component.id,
33
+ defaultValue: component.defaultValue,
34
+ range: {
35
+ min: component.range.min,
36
+ max: component.range.max
37
+ },
38
+ valueType: deriveComponentValueType(component)
39
+ };
40
+ }
41
+ function bindingTargetFromInput(input) {
42
+ return {
43
+ id: input.id,
44
+ defaultValue: input.defaultValue,
45
+ range: {
46
+ min: input.range.min,
47
+ max: input.range.max
48
+ },
49
+ valueType: "scalar"
50
+ };
51
+ }
52
+ var DEFAULT_INPUT_RANGE = { min: -1, max: 1 };
53
+ var DEFAULT_INPUT_ANCHOR = 0;
54
+ var LEGACY_SLOT_PATTERN = /^slot_(\d+)$/i;
55
+ var PRIMARY_SLOT_ID = "s1";
56
+ var PRIMARY_SLOT_ALIAS = "s1";
57
+ function defaultSlotId(index) {
58
+ return `s${index + 1}`;
59
+ }
60
+ function normalizeSlotId(value, index) {
61
+ if (value && value.length > 0) {
62
+ const match = value.match(LEGACY_SLOT_PATTERN);
63
+ if (match) {
64
+ const suffix = match[1] ?? String(index + 1);
65
+ return `s${suffix}`;
66
+ }
67
+ return value;
68
+ }
69
+ return defaultSlotId(index);
70
+ }
71
+ function normalizeSlotAlias(value, fallback, index) {
72
+ if (value && value.length > 0) {
73
+ const match = value.match(LEGACY_SLOT_PATTERN);
74
+ if (match) {
75
+ const suffix = match[1] ?? String(index + 1);
76
+ return { alias: `s${suffix}`, replaced: value };
77
+ }
78
+ return { alias: value, replaced: null };
79
+ }
80
+ if (fallback && fallback.length > 0) {
81
+ return { alias: fallback, replaced: null };
82
+ }
83
+ return { alias: defaultSlotId(index), replaced: null };
84
+ }
85
+ function rewriteLegacyExpression(expression, replacements) {
86
+ if (expression.trim().length === 0) {
87
+ return expression;
88
+ }
89
+ return expression.replace(/\bslot_(\d+)\b/gi, (match, digits) => {
90
+ const replacement = replacements.get(match);
91
+ if (replacement) {
92
+ return replacement;
93
+ }
94
+ return `s${digits}`;
95
+ });
96
+ }
97
+ function clamp(value, min, max) {
98
+ return Math.min(max, Math.max(min, value));
99
+ }
100
+ function isFiniteNumber(value) {
101
+ return typeof value === "number" && Number.isFinite(value);
102
+ }
103
+ function deriveOutputDefaults(target) {
104
+ const { min, max } = target.range;
105
+ const anchor = clamp(target.defaultValue, min, max);
106
+ return {
107
+ outLow: min,
108
+ outAnchor: anchor,
109
+ outHigh: max
110
+ };
111
+ }
112
+ function deriveInputDefaults() {
113
+ return {
114
+ inLow: DEFAULT_INPUT_RANGE.min,
115
+ inAnchor: DEFAULT_INPUT_ANCHOR,
116
+ inHigh: DEFAULT_INPUT_RANGE.max
117
+ };
118
+ }
119
+ function migrateLegacyRemap(legacy, target) {
120
+ const inputDefaults = deriveInputDefaults();
121
+ const outputDefaults = deriveOutputDefaults(target);
122
+ const defaults = {
123
+ inLow: inputDefaults.inLow,
124
+ inAnchor: inputDefaults.inAnchor,
125
+ inHigh: inputDefaults.inHigh,
126
+ outLow: outputDefaults.outLow,
127
+ outAnchor: outputDefaults.outAnchor,
128
+ outHigh: outputDefaults.outHigh
129
+ };
130
+ if ("inLow" in legacy && "inHigh" in legacy && "outLow" in legacy && "outHigh" in legacy) {
131
+ const inLow2 = isFiniteNumber(legacy.inLow) ? legacy.inLow : defaults.inLow;
132
+ const inAnchor2 = isFiniteNumber(legacy.inAnchor) ? legacy.inAnchor : defaults.inAnchor;
133
+ const inHigh2 = isFiniteNumber(legacy.inHigh) ? legacy.inHigh : defaults.inHigh;
134
+ let outLow = isFiniteNumber(legacy.outLow) ? legacy.outLow : defaults.outLow;
135
+ let outHigh = isFiniteNumber(legacy.outHigh) ? legacy.outHigh : defaults.outHigh;
136
+ if (outLow > outHigh) {
137
+ const low = outHigh;
138
+ const high = outLow;
139
+ outLow = low;
140
+ outHigh = high;
141
+ }
142
+ const outAnchor2 = clamp(
143
+ isFiniteNumber(legacy.outAnchor) ? legacy.outAnchor : defaults.outAnchor,
144
+ outLow,
145
+ outHigh
146
+ );
147
+ return {
148
+ inLow: inLow2,
149
+ inAnchor: inAnchor2,
150
+ inHigh: inHigh2,
151
+ outLow,
152
+ outAnchor: outAnchor2,
153
+ outHigh
154
+ };
155
+ }
156
+ const legacyTyped = legacy;
157
+ const inLow = isFiniteNumber(legacyTyped.inMin) ? legacyTyped.inMin : defaults.inLow;
158
+ const inHigh = isFiniteNumber(legacyTyped.inMax) ? legacyTyped.inMax : defaults.inHigh;
159
+ const inAnchor = (inLow + inHigh) / 2;
160
+ const legacyOutMid = isFiniteNumber(legacyTyped.outMin) && isFiniteNumber(legacyTyped.outMax) ? (legacyTyped.outMin + legacyTyped.outMax) / 2 : defaults.outAnchor;
161
+ const outAnchor = clamp(legacyOutMid, defaults.outLow, defaults.outHigh);
162
+ return {
163
+ inLow,
164
+ inAnchor,
165
+ inHigh,
166
+ outLow: defaults.outLow,
167
+ outAnchor,
168
+ outHigh: defaults.outHigh
169
+ };
170
+ }
171
+ function normalizeRemap(remap, target) {
172
+ if (!remap) {
173
+ return createDefaultRemap(target);
174
+ }
175
+ return migrateLegacyRemap(remap, target);
176
+ }
177
+ function cloneRemap(remap) {
178
+ return (0, import_utils.cloneRemapSettings)(remap);
179
+ }
180
+ function sanitizeRemap(remap, target) {
181
+ const normalized = normalizeRemap(remap, target);
182
+ const outputDefaults = deriveOutputDefaults(target);
183
+ if (!Number.isFinite(normalized.outLow)) {
184
+ normalized.outLow = outputDefaults.outLow;
185
+ }
186
+ if (!Number.isFinite(normalized.outHigh)) {
187
+ normalized.outHigh = outputDefaults.outHigh;
188
+ }
189
+ if (!Number.isFinite(normalized.outAnchor)) {
190
+ normalized.outAnchor = outputDefaults.outAnchor;
191
+ }
192
+ if (normalized.outLow > normalized.outHigh) {
193
+ const low = normalized.outHigh;
194
+ const high = normalized.outLow;
195
+ normalized.outLow = low;
196
+ normalized.outHigh = high;
197
+ }
198
+ normalized.outAnchor = clamp(
199
+ normalized.outAnchor,
200
+ normalized.outLow,
201
+ normalized.outHigh
202
+ );
203
+ return normalized;
204
+ }
205
+ function createDefaultRemap(target) {
206
+ const inputDefaults = deriveInputDefaults();
207
+ const outputDefaults = deriveOutputDefaults(target);
208
+ return {
209
+ inLow: inputDefaults.inLow,
210
+ inAnchor: inputDefaults.inAnchor,
211
+ inHigh: inputDefaults.inHigh,
212
+ outLow: outputDefaults.outLow,
213
+ outAnchor: outputDefaults.outAnchor,
214
+ outHigh: outputDefaults.outHigh
215
+ };
216
+ }
217
+ function ensurePrimarySlot(binding, target) {
218
+ const normalizedBindingRemap = sanitizeRemap(binding.remap, target);
219
+ const targetValueType = getTargetValueType(target);
220
+ const aliasReplacements = /* @__PURE__ */ new Map();
221
+ const sourceSlots = Array.isArray(binding.slots) && binding.slots.length > 0 ? binding.slots : [
222
+ {
223
+ id: PRIMARY_SLOT_ID,
224
+ alias: PRIMARY_SLOT_ALIAS,
225
+ inputId: binding.inputId ?? null,
226
+ remap: cloneRemap(normalizedBindingRemap),
227
+ valueType: targetValueType
228
+ }
229
+ ];
230
+ const normalizedSlots = sourceSlots.map(
231
+ (slot, index) => {
232
+ const normalizedId = normalizeSlotId(slot.id, index);
233
+ const { alias: normalizedAlias, replaced } = normalizeSlotAlias(
234
+ slot.alias,
235
+ normalizedId,
236
+ index
237
+ );
238
+ if (replaced && replaced !== normalizedAlias) {
239
+ aliasReplacements.set(replaced, normalizedAlias);
240
+ }
241
+ const slotRemapSource = slot.remap ?? (index === 0 ? normalizedBindingRemap : createDefaultRemap(target));
242
+ const normalizedSlotRemap = sanitizeRemap(slotRemapSource, target);
243
+ const inputId = slot.inputId !== void 0 && slot.inputId !== null ? slot.inputId : index === 0 ? binding.inputId ?? null : null;
244
+ const slotValueType = sanitizeSlotValueType(
245
+ slot.valueType,
246
+ targetValueType
247
+ );
248
+ return {
249
+ id: normalizedId,
250
+ alias: normalizedAlias,
251
+ inputId,
252
+ remap: cloneRemap(normalizedSlotRemap),
253
+ valueType: slotValueType
254
+ };
255
+ }
256
+ );
257
+ const primary = normalizedSlots[0];
258
+ const primaryRemap = sanitizeRemap(primary.remap, target);
259
+ const primaryInputId = primary.inputId === import_utils.SELF_BINDING_ID ? import_utils.SELF_BINDING_ID : primary.inputId ?? binding.inputId ?? null;
260
+ const primaryAlias = primaryInputId === import_utils.SELF_BINDING_ID ? "self" : primary.alias || PRIMARY_SLOT_ALIAS;
261
+ normalizedSlots[0] = {
262
+ ...primary,
263
+ id: primary.id || PRIMARY_SLOT_ID,
264
+ alias: primaryAlias,
265
+ inputId: primaryInputId,
266
+ remap: cloneRemap(primaryRemap),
267
+ valueType: sanitizeSlotValueType(primary.valueType, targetValueType)
268
+ };
269
+ normalizedSlots.slice(1).forEach((slot, index) => {
270
+ const slotRemap = sanitizeRemap(slot.remap, target);
271
+ normalizedSlots[index + 1] = {
272
+ ...slot,
273
+ id: slot.id || defaultSlotId(index + 1),
274
+ alias: slot.alias || defaultSlotId(index + 1),
275
+ remap: cloneRemap(slotRemap),
276
+ valueType: sanitizeSlotValueType(slot.valueType, targetValueType)
277
+ };
278
+ });
279
+ const rawExpression = typeof binding.expression === "string" ? binding.expression.trim() : "";
280
+ const canonicalExpression = buildCanonicalExpressionFromSlots(normalizedSlots);
281
+ let expression;
282
+ if (expressionMatchesAliasOnly(rawExpression, normalizedSlots)) {
283
+ expression = canonicalExpression;
284
+ } else {
285
+ expression = rewriteLegacyExpression(rawExpression, aliasReplacements);
286
+ }
287
+ const normalizedBinding = {
288
+ ...binding,
289
+ inputId: normalizedSlots[0].inputId ?? null,
290
+ remap: cloneRemap(primaryRemap),
291
+ slots: normalizedSlots,
292
+ expression
293
+ };
294
+ return normalizedBinding;
295
+ }
296
+ function ensureBindingStructure(binding, target) {
297
+ return ensurePrimarySlot(binding, target);
298
+ }
299
+ function sanitizeLiteral(value) {
300
+ if (!Number.isFinite(value)) {
301
+ return 0;
302
+ }
303
+ if (Object.is(value, -0)) {
304
+ return 0;
305
+ }
306
+ return value;
307
+ }
308
+ function formatVectorLiteral(values) {
309
+ return `vec(${values.map((value) => sanitizeLiteral(value)).join(", ")})`;
310
+ }
311
+ function buildPiecewiseRemapExpression(alias, remap) {
312
+ const sanitizedAlias = alias && alias.trim().length > 0 ? alias.trim() : PRIMARY_SLOT_ALIAS;
313
+ const inputBreakpoints = [remap.inLow, remap.inAnchor, remap.inHigh];
314
+ const outputBreakpoints = [remap.outLow, remap.outAnchor, remap.outHigh];
315
+ return `piecewise_remap(${sanitizedAlias}, ${formatVectorLiteral(
316
+ inputBreakpoints
317
+ )}, ${formatVectorLiteral(outputBreakpoints)})`;
318
+ }
319
+ function isSelfAlias(alias) {
320
+ return alias.trim().toLowerCase() === "self";
321
+ }
322
+ function buildDefaultSlotExpression(alias, inputId, remap) {
323
+ const sanitizedAlias = alias && alias.trim().length > 0 ? alias.trim() : PRIMARY_SLOT_ALIAS;
324
+ if (inputId === import_utils.SELF_BINDING_ID || isSelfAlias(sanitizedAlias)) {
325
+ return sanitizedAlias;
326
+ }
327
+ return buildPiecewiseRemapExpression(sanitizedAlias, remap);
328
+ }
329
+ function normalizeSlotAliasForExpression(slot, index) {
330
+ if (slot.alias && slot.alias.trim().length > 0) {
331
+ return slot.alias.trim();
332
+ }
333
+ if (slot.id && slot.id.trim().length > 0) {
334
+ return slot.id.trim();
335
+ }
336
+ return defaultSlotId(index);
337
+ }
338
+ function buildAliasOnlyExpression(slots) {
339
+ if (!slots.length) {
340
+ return PRIMARY_SLOT_ALIAS;
341
+ }
342
+ return slots.map((slot, index) => normalizeSlotAliasForExpression(slot, index)).join(" + ");
343
+ }
344
+ function buildCanonicalExpressionFromSlots(slots) {
345
+ if (!slots.length) {
346
+ return PRIMARY_SLOT_ALIAS;
347
+ }
348
+ return slots.map(
349
+ (slot, index) => buildDefaultSlotExpression(
350
+ normalizeSlotAliasForExpression(slot, index),
351
+ slot.inputId ?? null,
352
+ slot.remap
353
+ )
354
+ ).join(" + ");
355
+ }
356
+ function expressionsEquivalent(left, right) {
357
+ return left.trim() === right.trim();
358
+ }
359
+ function expressionMatchesAliasOnly(expression, slots) {
360
+ if (expression.trim().length === 0) {
361
+ return true;
362
+ }
363
+ const aliasOnly = buildAliasOnlyExpression(slots);
364
+ return expressionsEquivalent(expression, aliasOnly);
365
+ }
366
+
367
+ // src/expression.ts
368
+ var STANDARD_WHITESPACE = /\s/;
369
+ var EXTRA_WHITESPACE_CHARS = /* @__PURE__ */ new Set(["\0", "\u200B", "\uFEFF"]);
370
+ function isWhitespaceCharacter(char) {
371
+ if (!char) {
372
+ return false;
373
+ }
374
+ return STANDARD_WHITESPACE.test(char) || EXTRA_WHITESPACE_CHARS.has(char);
375
+ }
376
+ var IDENT_START = /[A-Za-z_]/;
377
+ var IDENT_PART = /[A-Za-z0-9_]/;
378
+ var DIGIT = /[0-9]/;
379
+ var ControlExpressionParser = class {
380
+ constructor(input) {
381
+ this.input = input;
382
+ this.index = 0;
383
+ this.errors = [];
384
+ }
385
+ parse() {
386
+ this.skipWhitespace();
387
+ const node = this.parseExpression();
388
+ this.skipWhitespace();
389
+ if (!node) {
390
+ if (this.errors.length === 0) {
391
+ this.errors.push({
392
+ index: this.index,
393
+ message: "Empty expression."
394
+ });
395
+ }
396
+ return { node: null, errors: this.errors };
397
+ }
398
+ if (!this.isAtEnd()) {
399
+ this.errors.push({
400
+ index: this.index,
401
+ message: `Unexpected token "${this.peek()}"`
402
+ });
403
+ return { node: null, errors: this.errors };
404
+ }
405
+ return { node, errors: this.errors };
406
+ }
407
+ parseExpression() {
408
+ return this.parseLogicalOr();
409
+ }
410
+ parseLogicalOr() {
411
+ let left = this.parseLogicalAnd();
412
+ if (!left) {
413
+ return null;
414
+ }
415
+ while (true) {
416
+ this.skipWhitespace();
417
+ const operator = this.matchAny(["||"]);
418
+ if (!operator) {
419
+ break;
420
+ }
421
+ const right = this.parseLogicalAnd();
422
+ if (!right) {
423
+ this.errors.push({
424
+ index: this.index,
425
+ message: "Expected expression after operator."
426
+ });
427
+ return null;
428
+ }
429
+ left = {
430
+ type: "Binary",
431
+ operator,
432
+ left,
433
+ right
434
+ };
435
+ }
436
+ return left;
437
+ }
438
+ parseLogicalAnd() {
439
+ let left = this.parseComparison();
440
+ if (!left) {
441
+ return null;
442
+ }
443
+ while (true) {
444
+ this.skipWhitespace();
445
+ const operator = this.matchAny(["&&"]);
446
+ if (!operator) {
447
+ break;
448
+ }
449
+ const right = this.parseComparison();
450
+ if (!right) {
451
+ this.errors.push({
452
+ index: this.index,
453
+ message: "Expected expression after operator."
454
+ });
455
+ return null;
456
+ }
457
+ left = {
458
+ type: "Binary",
459
+ operator,
460
+ left,
461
+ right
462
+ };
463
+ }
464
+ return left;
465
+ }
466
+ parseComparison() {
467
+ let left = this.parseAdditive();
468
+ if (!left) {
469
+ return null;
470
+ }
471
+ while (true) {
472
+ this.skipWhitespace();
473
+ if (this.input.startsWith(">=", this.index) || this.input.startsWith("<=", this.index)) {
474
+ const op = this.input.slice(this.index, this.index + 2);
475
+ this.index += 2;
476
+ this.errors.push({
477
+ index: this.index - 2,
478
+ message: `Operator "${op}" is not supported.`
479
+ });
480
+ return null;
481
+ }
482
+ const operator = this.matchAny(["==", "!=", ">", "<"]);
483
+ if (!operator) {
484
+ break;
485
+ }
486
+ const right = this.parseAdditive();
487
+ if (!right) {
488
+ this.errors.push({
489
+ index: this.index,
490
+ message: "Expected expression after operator."
491
+ });
492
+ return null;
493
+ }
494
+ left = {
495
+ type: "Binary",
496
+ operator,
497
+ left,
498
+ right
499
+ };
500
+ }
501
+ return left;
502
+ }
503
+ parseAdditive() {
504
+ let left = this.parseMultiplicative();
505
+ if (!left) {
506
+ return null;
507
+ }
508
+ while (true) {
509
+ this.skipWhitespace();
510
+ const operator = this.peek();
511
+ if (operator !== "+" && operator !== "-") {
512
+ break;
513
+ }
514
+ this.index += 1;
515
+ const right = this.parseMultiplicative();
516
+ if (!right) {
517
+ this.errors.push({
518
+ index: this.index,
519
+ message: "Expected expression after operator."
520
+ });
521
+ return null;
522
+ }
523
+ left = {
524
+ type: "Binary",
525
+ operator,
526
+ left,
527
+ right
528
+ };
529
+ }
530
+ return left;
531
+ }
532
+ parseMultiplicative() {
533
+ let left = this.parseUnary();
534
+ if (!left) {
535
+ return null;
536
+ }
537
+ while (true) {
538
+ this.skipWhitespace();
539
+ const operator = this.peek();
540
+ if (operator !== "*" && operator !== "/") {
541
+ break;
542
+ }
543
+ this.index += 1;
544
+ const right = this.parseUnary();
545
+ if (!right) {
546
+ this.errors.push({
547
+ index: this.index,
548
+ message: "Expected expression after operator."
549
+ });
550
+ return null;
551
+ }
552
+ left = {
553
+ type: "Binary",
554
+ operator,
555
+ left,
556
+ right
557
+ };
558
+ }
559
+ return left;
560
+ }
561
+ parseUnary() {
562
+ this.skipWhitespace();
563
+ const char = this.peek();
564
+ if (!char) {
565
+ this.errors.push({
566
+ index: this.index,
567
+ message: "Unexpected end of expression."
568
+ });
569
+ return null;
570
+ }
571
+ if (char === "+" || char === "-" || char === "!") {
572
+ this.index += 1;
573
+ const operand = this.parseUnary();
574
+ if (!operand) {
575
+ this.errors.push({
576
+ index: this.index,
577
+ message: `Expected operand after unary "${char}".`
578
+ });
579
+ return null;
580
+ }
581
+ return {
582
+ type: "Unary",
583
+ operator: char,
584
+ operand
585
+ };
586
+ }
587
+ return this.parsePrimary();
588
+ }
589
+ parsePrimary() {
590
+ this.skipWhitespace();
591
+ const char = this.peek();
592
+ if (!char) {
593
+ this.errors.push({
594
+ index: this.index,
595
+ message: "Unexpected end of expression."
596
+ });
597
+ return null;
598
+ }
599
+ if (char === "(") {
600
+ this.index += 1;
601
+ const expression = this.parseExpression();
602
+ this.skipWhitespace();
603
+ if (this.peek() === ")") {
604
+ this.index += 1;
605
+ return expression;
606
+ }
607
+ this.errors.push({
608
+ index: this.index,
609
+ message: "Unmatched parenthesis."
610
+ });
611
+ return null;
612
+ }
613
+ if (char === "[") {
614
+ this.index += 1;
615
+ return this.parseVectorLiteral("]", "vector literal");
616
+ }
617
+ if (IDENT_START.test(char)) {
618
+ return this.parseIdentifierOrFunction();
619
+ }
620
+ if (DIGIT.test(char) || char === ".") {
621
+ return this.parseNumber();
622
+ }
623
+ this.errors.push({
624
+ index: this.index,
625
+ message: `Unexpected character "${char}".`
626
+ });
627
+ return null;
628
+ }
629
+ parseIdentifierOrFunction() {
630
+ const start = this.index;
631
+ while (!this.isAtEnd() && IDENT_PART.test(this.peek())) {
632
+ this.index += 1;
633
+ }
634
+ const name = this.input.slice(start, this.index);
635
+ if (!name) {
636
+ this.errors.push({
637
+ index: start,
638
+ message: "Invalid identifier."
639
+ });
640
+ return null;
641
+ }
642
+ this.skipWhitespace();
643
+ if (this.peek() === "(") {
644
+ this.index += 1;
645
+ if (name.toLowerCase() === "vec") {
646
+ return this.parseVectorLiteral(")", `"${name}" literal`);
647
+ }
648
+ const args = [];
649
+ this.skipWhitespace();
650
+ if (this.peek() === ")") {
651
+ this.index += 1;
652
+ return {
653
+ type: "Function",
654
+ name,
655
+ args
656
+ };
657
+ }
658
+ while (true) {
659
+ const argument = this.parseExpression();
660
+ if (!argument) {
661
+ this.errors.push({
662
+ index: this.index,
663
+ message: `Expected expression for argument ${args.length + 1} of "${name}".`
664
+ });
665
+ return null;
666
+ }
667
+ args.push(argument);
668
+ this.skipWhitespace();
669
+ const next = this.peek();
670
+ if (next === ",") {
671
+ this.index += 1;
672
+ this.skipWhitespace();
673
+ if (this.peek() === ")") {
674
+ this.errors.push({
675
+ index: this.index,
676
+ message: `Expected expression after "," in call to "${name}".`
677
+ });
678
+ return null;
679
+ }
680
+ continue;
681
+ }
682
+ if (next === ")") {
683
+ this.index += 1;
684
+ break;
685
+ }
686
+ if (next === null) {
687
+ this.errors.push({
688
+ index: this.index,
689
+ message: `Unterminated call to "${name}".`
690
+ });
691
+ } else {
692
+ this.errors.push({
693
+ index: this.index,
694
+ message: `Expected "," or ")" in call to "${name}".`
695
+ });
696
+ }
697
+ return null;
698
+ }
699
+ return {
700
+ type: "Function",
701
+ name,
702
+ args
703
+ };
704
+ }
705
+ return {
706
+ type: "Reference",
707
+ name
708
+ };
709
+ }
710
+ parseVectorLiteral(terminator, context) {
711
+ const values = this.parseVectorLiteralValues(terminator, context);
712
+ if (!values) {
713
+ return null;
714
+ }
715
+ return {
716
+ type: "VectorLiteral",
717
+ values
718
+ };
719
+ }
720
+ parseVectorLiteralValues(terminator, context) {
721
+ const values = [];
722
+ this.skipWhitespace();
723
+ if (this.peek() === terminator) {
724
+ this.errors.push({
725
+ index: this.index,
726
+ message: `Expected at least one value in ${context}.`
727
+ });
728
+ return null;
729
+ }
730
+ while (true) {
731
+ this.skipWhitespace();
732
+ const literalValue = this.parseNumericLiteralValue();
733
+ if (literalValue === null) {
734
+ this.errors.push({
735
+ index: this.index,
736
+ message: `Expected numeric literal in ${context}.`
737
+ });
738
+ return null;
739
+ }
740
+ values.push(literalValue);
741
+ this.skipWhitespace();
742
+ const next = this.peek();
743
+ if (next === ",") {
744
+ this.index += 1;
745
+ this.skipWhitespace();
746
+ if (this.peek() === terminator) {
747
+ this.errors.push({
748
+ index: this.index,
749
+ message: `Expected value after "," in ${context}.`
750
+ });
751
+ return null;
752
+ }
753
+ continue;
754
+ }
755
+ if (next === terminator) {
756
+ this.index += 1;
757
+ break;
758
+ }
759
+ if (next === null) {
760
+ this.errors.push({
761
+ index: this.index,
762
+ message: `Unterminated ${context}.`
763
+ });
764
+ } else {
765
+ this.errors.push({
766
+ index: this.index,
767
+ message: `Expected "," or "${terminator}" in ${context}.`
768
+ });
769
+ }
770
+ return null;
771
+ }
772
+ if (values.length === 0) {
773
+ this.errors.push({
774
+ index: this.index,
775
+ message: `Expected at least one value in ${context}.`
776
+ });
777
+ return null;
778
+ }
779
+ return values;
780
+ }
781
+ parseNumericLiteralValue(allowLeadingSign = true) {
782
+ const start = this.index;
783
+ if (allowLeadingSign && (this.peek() === "+" || this.peek() === "-")) {
784
+ this.index += 1;
785
+ }
786
+ let hasDigits = false;
787
+ let seenDecimal = false;
788
+ let seenExponent = false;
789
+ let exponentDigits = false;
790
+ let inExponent = false;
791
+ while (!this.isAtEnd()) {
792
+ const char = this.peek();
793
+ if (DIGIT.test(char)) {
794
+ hasDigits = true;
795
+ if (inExponent) {
796
+ exponentDigits = true;
797
+ }
798
+ this.index += 1;
799
+ continue;
800
+ }
801
+ if (char === "." && !seenDecimal && !seenExponent) {
802
+ seenDecimal = true;
803
+ this.index += 1;
804
+ continue;
805
+ }
806
+ if ((char === "e" || char === "E") && !seenExponent && hasDigits) {
807
+ seenExponent = true;
808
+ inExponent = true;
809
+ exponentDigits = false;
810
+ this.index += 1;
811
+ const sign = this.peek();
812
+ if (sign === "+" || sign === "-") {
813
+ this.index += 1;
814
+ }
815
+ continue;
816
+ }
817
+ break;
818
+ }
819
+ if (!hasDigits || seenExponent && !exponentDigits) {
820
+ this.index = start;
821
+ return null;
822
+ }
823
+ const raw = this.input.slice(start, this.index);
824
+ const value = Number(raw);
825
+ if (!Number.isFinite(value)) {
826
+ this.index = start;
827
+ return null;
828
+ }
829
+ return value;
830
+ }
831
+ parseNumber() {
832
+ const start = this.index;
833
+ const value = this.parseNumericLiteralValue(false);
834
+ if (value === null) {
835
+ this.errors.push({
836
+ index: start,
837
+ message: "Invalid numeric literal."
838
+ });
839
+ return null;
840
+ }
841
+ return {
842
+ type: "Literal",
843
+ value
844
+ };
845
+ }
846
+ matchAny(operators) {
847
+ for (const operator of operators) {
848
+ if (this.input.startsWith(operator, this.index)) {
849
+ this.index += operator.length;
850
+ return operator;
851
+ }
852
+ }
853
+ return null;
854
+ }
855
+ skipWhitespace() {
856
+ while (!this.isAtEnd()) {
857
+ const char = this.peek();
858
+ if (!isWhitespaceCharacter(char)) {
859
+ break;
860
+ }
861
+ this.index += 1;
862
+ }
863
+ }
864
+ peek() {
865
+ if (this.index >= this.input.length) {
866
+ return null;
867
+ }
868
+ return this.input[this.index] ?? null;
869
+ }
870
+ isAtEnd() {
871
+ return this.index >= this.input.length;
872
+ }
873
+ };
874
+ function parseControlExpression(expression) {
875
+ const parser = new ControlExpressionParser(expression);
876
+ return parser.parse();
877
+ }
878
+ function collectExpressionReferences(node, target = /* @__PURE__ */ new Set()) {
879
+ if (!node) {
880
+ return target;
881
+ }
882
+ switch (node.type) {
883
+ case "Reference":
884
+ target.add(node.name);
885
+ break;
886
+ case "Unary":
887
+ collectExpressionReferences(node.operand, target);
888
+ break;
889
+ case "Binary":
890
+ collectExpressionReferences(node.left, target);
891
+ collectExpressionReferences(node.right, target);
892
+ break;
893
+ case "Function":
894
+ node.args.forEach((arg) => collectExpressionReferences(arg, target));
895
+ break;
896
+ default:
897
+ break;
898
+ }
899
+ return target;
900
+ }
901
+
902
+ // src/expressionFunctions.ts
903
+ var import_metadata = require("@vizij/node-graph-wasm/metadata");
904
+ var FUNCTION_OVERRIDES = {
905
+ case: {
906
+ replaceInputs: [
907
+ { id: "selector", optional: false, valueType: "any" },
908
+ { id: "default", optional: false, valueType: "any" }
909
+ ],
910
+ variadic: { id: "operand", min: 1, max: null, valueType: "any" },
911
+ minArgs: 3,
912
+ maxArgs: null,
913
+ params: []
914
+ },
915
+ slew: {
916
+ dropTrailingInputs: 1,
917
+ params: [
918
+ {
919
+ id: "max_rate",
920
+ label: "max_rate",
921
+ optional: false,
922
+ valueType: "scalar"
923
+ }
924
+ ],
925
+ minArgs: 2,
926
+ maxArgs: 2
927
+ }
928
+ };
929
+ var FUNCTION_CONFIGS = [
930
+ {
931
+ typeId: "sin",
932
+ names: ["sin"],
933
+ category: "math",
934
+ description: "Sine of the input value (radians)."
935
+ },
936
+ {
937
+ typeId: "cos",
938
+ names: ["cos"],
939
+ category: "math",
940
+ description: "Cosine of the input value (radians)."
941
+ },
942
+ {
943
+ typeId: "tan",
944
+ names: ["tan"],
945
+ category: "math",
946
+ description: "Tangent of the input value (radians)."
947
+ },
948
+ {
949
+ typeId: "power",
950
+ names: ["power", "pow"],
951
+ category: "math",
952
+ description: "Raise a base value to an exponent."
953
+ },
954
+ {
955
+ typeId: "log",
956
+ names: ["log"],
957
+ category: "math",
958
+ description: "Logarithm of the input value."
959
+ },
960
+ {
961
+ typeId: "clamp",
962
+ names: ["clamp"],
963
+ category: "utility",
964
+ description: "Clamp an input between min/max."
965
+ },
966
+ {
967
+ typeId: "remap",
968
+ names: ["remap"],
969
+ category: "utility",
970
+ description: "Linearly map a value from the input range to the output range."
971
+ },
972
+ {
973
+ typeId: "centered_remap",
974
+ names: ["centeredremap", "centered_remap"],
975
+ category: "utility",
976
+ description: "Remap a value using in/out ranges that include explicit anchors."
977
+ },
978
+ {
979
+ typeId: "piecewise_remap",
980
+ names: ["piecewiseremap", "piecewise_remap"],
981
+ category: "utility",
982
+ description: "Map a value through piecewise-linear breakpoints."
983
+ },
984
+ {
985
+ typeId: "abs",
986
+ names: ["abs"],
987
+ category: "math",
988
+ description: "Absolute value of the input."
989
+ },
990
+ {
991
+ typeId: "add",
992
+ names: ["add"],
993
+ category: "math",
994
+ description: "Sum all operands together."
995
+ },
996
+ {
997
+ typeId: "multiply",
998
+ names: ["multiply"],
999
+ category: "math",
1000
+ description: "Multiply operands together."
1001
+ },
1002
+ {
1003
+ typeId: "subtract",
1004
+ names: ["subtract"],
1005
+ category: "math",
1006
+ description: "Subtract rhs from lhs."
1007
+ },
1008
+ {
1009
+ typeId: "divide",
1010
+ names: ["divide"],
1011
+ category: "math",
1012
+ description: "Divide lhs by rhs."
1013
+ },
1014
+ {
1015
+ typeId: "min",
1016
+ names: ["min"],
1017
+ category: "math",
1018
+ description: "Minimum of all operands."
1019
+ },
1020
+ {
1021
+ typeId: "max",
1022
+ names: ["max"],
1023
+ category: "math",
1024
+ description: "Maximum of all operands."
1025
+ },
1026
+ {
1027
+ typeId: "modulo",
1028
+ names: ["modulo", "mod"],
1029
+ category: "math",
1030
+ description: "Modulo of lhs by rhs."
1031
+ },
1032
+ {
1033
+ typeId: "greaterthan",
1034
+ names: ["greaterthan", "gt"],
1035
+ minArgs: 2,
1036
+ maxArgs: 2,
1037
+ category: "logic",
1038
+ description: "Return true when lhs > rhs."
1039
+ },
1040
+ {
1041
+ typeId: "lessthan",
1042
+ names: ["lessthan", "lt"],
1043
+ minArgs: 2,
1044
+ maxArgs: 2,
1045
+ category: "logic",
1046
+ description: "Return true when lhs < rhs."
1047
+ },
1048
+ {
1049
+ typeId: "equal",
1050
+ names: ["equal", "eq"],
1051
+ minArgs: 2,
1052
+ maxArgs: 2,
1053
+ category: "logic",
1054
+ description: "Return true when operands are equal."
1055
+ },
1056
+ {
1057
+ typeId: "notequal",
1058
+ names: ["notequal", "neq", "ne"],
1059
+ minArgs: 2,
1060
+ maxArgs: 2,
1061
+ category: "logic",
1062
+ description: "Return true when operands differ."
1063
+ },
1064
+ {
1065
+ typeId: "and",
1066
+ names: ["and"],
1067
+ minArgs: 2,
1068
+ maxArgs: 2,
1069
+ category: "logic",
1070
+ description: "Logical AND."
1071
+ },
1072
+ {
1073
+ typeId: "or",
1074
+ names: ["or"],
1075
+ minArgs: 2,
1076
+ maxArgs: 2,
1077
+ category: "logic",
1078
+ description: "Logical OR."
1079
+ },
1080
+ {
1081
+ typeId: "xor",
1082
+ names: ["xor"],
1083
+ minArgs: 2,
1084
+ maxArgs: 2,
1085
+ category: "logic",
1086
+ description: "Logical XOR."
1087
+ },
1088
+ {
1089
+ typeId: "not",
1090
+ names: ["not"],
1091
+ minArgs: 1,
1092
+ maxArgs: 1,
1093
+ category: "logic",
1094
+ description: "Logical NOT of input."
1095
+ },
1096
+ {
1097
+ typeId: "if",
1098
+ names: ["if"],
1099
+ minArgs: 2,
1100
+ maxArgs: 3,
1101
+ category: "logic",
1102
+ description: "Conditional branch with optional fallback."
1103
+ },
1104
+ {
1105
+ typeId: "round",
1106
+ names: ["round"],
1107
+ category: "math",
1108
+ description: "Round input using the configured mode (floor/ceil/trunc)."
1109
+ },
1110
+ {
1111
+ typeId: "case",
1112
+ names: ["case"],
1113
+ minArgs: 3,
1114
+ category: "logic",
1115
+ description: "Route the selector value to matching labeled branches."
1116
+ },
1117
+ {
1118
+ typeId: "time",
1119
+ names: ["time"],
1120
+ minArgs: 0,
1121
+ maxArgs: 0,
1122
+ category: "time",
1123
+ description: "Graph time in seconds."
1124
+ },
1125
+ {
1126
+ typeId: "oscillator",
1127
+ names: ["oscillator"],
1128
+ minArgs: 2,
1129
+ maxArgs: 2,
1130
+ category: "time",
1131
+ description: "Sine/cosine oscillator driven by time."
1132
+ },
1133
+ {
1134
+ typeId: "spring",
1135
+ names: ["spring"],
1136
+ category: "time",
1137
+ description: "Spring toward the target with configurable stiffness and damping."
1138
+ },
1139
+ {
1140
+ typeId: "damp",
1141
+ names: ["damp"],
1142
+ category: "time",
1143
+ description: "Damp input values toward zero at the specified rate."
1144
+ },
1145
+ {
1146
+ typeId: "slew",
1147
+ names: ["slew"],
1148
+ category: "time",
1149
+ description: "Limit the rate of change between inputs over time."
1150
+ },
1151
+ {
1152
+ typeId: "default-blend",
1153
+ names: ["defaultblend", "blend"],
1154
+ category: "utility",
1155
+ description: "Blend operand values using optional baseline/offset and weight inputs."
1156
+ },
1157
+ {
1158
+ typeId: "blendweightedaverage",
1159
+ names: ["blendweightedaverage"],
1160
+ category: "utility",
1161
+ description: "Compute weighted average blends using aggregate sums."
1162
+ },
1163
+ {
1164
+ typeId: "blendadditive",
1165
+ names: ["blendadditive"],
1166
+ category: "utility",
1167
+ description: "Additive blending helper."
1168
+ },
1169
+ {
1170
+ typeId: "blendmultiply",
1171
+ names: ["blendmultiply"],
1172
+ category: "utility",
1173
+ description: "Multiplicative blending helper."
1174
+ },
1175
+ {
1176
+ typeId: "blendweightedoverlay",
1177
+ names: ["blendweightedoverlay"],
1178
+ category: "utility",
1179
+ description: "Overlay blending helper driven by weighted sums."
1180
+ },
1181
+ {
1182
+ typeId: "blendweightedaverageoverlay",
1183
+ names: ["blendweightedaverageoverlay"],
1184
+ category: "utility",
1185
+ description: "Overlay helper that adds averaged deltas to the base value."
1186
+ },
1187
+ {
1188
+ typeId: "blendmax",
1189
+ names: ["blendmax"],
1190
+ category: "utility",
1191
+ description: "Select the operand whose effective weight is largest."
1192
+ },
1193
+ {
1194
+ typeId: "vec3cross",
1195
+ names: ["vec3cross", "cross"],
1196
+ category: "vector",
1197
+ description: "Cross product of vectors A \xD7 B."
1198
+ },
1199
+ {
1200
+ typeId: "vectoradd",
1201
+ names: ["vectoradd", "vadd"],
1202
+ category: "vector",
1203
+ description: "Element-wise sum of vectors."
1204
+ },
1205
+ {
1206
+ typeId: "vectorsubtract",
1207
+ names: ["vectorsubtract", "vsub"],
1208
+ category: "vector",
1209
+ description: "Element-wise subtraction of vectors."
1210
+ },
1211
+ {
1212
+ typeId: "vectormultiply",
1213
+ names: ["vectormultiply", "vmul"],
1214
+ category: "vector",
1215
+ description: "Element-wise multiplication of vectors."
1216
+ },
1217
+ {
1218
+ typeId: "vectorscale",
1219
+ names: ["vectorscale", "vscale"],
1220
+ category: "vector",
1221
+ description: "Scale a vector by a scalar value."
1222
+ },
1223
+ {
1224
+ typeId: "vectornormalize",
1225
+ names: ["vectornormalize", "vnormalize"],
1226
+ category: "vector",
1227
+ description: "Normalize a vector to unit length."
1228
+ },
1229
+ {
1230
+ typeId: "vectordot",
1231
+ names: ["vectordot", "vdot"],
1232
+ category: "vector",
1233
+ description: "Dot product of vectors A \xB7 B."
1234
+ },
1235
+ {
1236
+ typeId: "vectorlength",
1237
+ names: ["vectorlength", "vlength"],
1238
+ category: "vector",
1239
+ description: "Euclidean length of a vector."
1240
+ },
1241
+ {
1242
+ typeId: "vectorindex",
1243
+ names: ["vectorindex", "vindex"],
1244
+ category: "vector",
1245
+ description: "Extract a component from a vector."
1246
+ },
1247
+ {
1248
+ typeId: "vectorconstant",
1249
+ names: ["vectorconstant", "vconst"],
1250
+ category: "vector",
1251
+ description: "Constant vector value."
1252
+ },
1253
+ {
1254
+ typeId: "join",
1255
+ names: ["join"],
1256
+ category: "vector",
1257
+ description: "Join scalar inputs into a vector."
1258
+ },
1259
+ {
1260
+ typeId: "split",
1261
+ names: ["split"],
1262
+ category: "vector",
1263
+ description: "Split a vector into scalar outputs."
1264
+ },
1265
+ {
1266
+ typeId: "vectormin",
1267
+ names: ["vectormin"],
1268
+ category: "vector",
1269
+ description: "Minimum component in a vector."
1270
+ },
1271
+ {
1272
+ typeId: "vectormax",
1273
+ names: ["vectormax"],
1274
+ category: "vector",
1275
+ description: "Maximum component in a vector."
1276
+ },
1277
+ {
1278
+ typeId: "vectormean",
1279
+ names: ["vectormean"],
1280
+ category: "vector",
1281
+ description: "Mean of the provided vector values."
1282
+ },
1283
+ {
1284
+ typeId: "vectormedian",
1285
+ names: ["vectormedian"],
1286
+ category: "vector",
1287
+ description: "Median of the provided vector values."
1288
+ },
1289
+ {
1290
+ typeId: "vectormode",
1291
+ names: ["vectormode"],
1292
+ category: "vector",
1293
+ description: "Mode of the provided vector values."
1294
+ },
1295
+ {
1296
+ typeId: "weightedsumvector",
1297
+ names: ["weightedsumvector", "vectorsum"],
1298
+ category: "vector",
1299
+ description: "Weighted sum across vectors with optional masks."
1300
+ }
1301
+ ];
1302
+ var SCALAR_FUNCTIONS = /* @__PURE__ */ new Map();
1303
+ var SCALAR_FUNCTION_VOCABULARY = [];
1304
+ for (const config of FUNCTION_CONFIGS) {
1305
+ const signature = (0, import_metadata.requireNodeSignature)(config.typeId);
1306
+ const signatureInputs = signature.inputs;
1307
+ let inputs = signatureInputs.map((input) => ({
1308
+ id: input.id,
1309
+ optional: Boolean(input.optional),
1310
+ valueType: inferExpressionValueType(input.ty)
1311
+ }));
1312
+ let variadic = signature.variadic_inputs ? {
1313
+ id: signature.variadic_inputs.id,
1314
+ min: signature.variadic_inputs.min,
1315
+ max: typeof signature.variadic_inputs.max === "number" ? signature.variadic_inputs.max : null,
1316
+ valueType: inferExpressionValueType(signature.variadic_inputs.ty)
1317
+ } : null;
1318
+ const baseRequiredInputs = inputs.filter((input) => !input.optional).length;
1319
+ const variadicMin = variadic?.min ?? 0;
1320
+ const derivedMin = baseRequiredInputs + variadicMin;
1321
+ let derivedMax;
1322
+ if (variadic) {
1323
+ derivedMax = variadic.max === null ? null : inputs.length + (variadic.max ?? 0);
1324
+ } else {
1325
+ derivedMax = inputs.length;
1326
+ }
1327
+ let params = buildParamArgumentSpecs(signature);
1328
+ const override = FUNCTION_OVERRIDES[config.typeId];
1329
+ if (override) {
1330
+ if (override.replaceInputs) {
1331
+ inputs = override.replaceInputs.map((input) => ({ ...input }));
1332
+ } else if (override.dropTrailingInputs) {
1333
+ const dropCount = Math.min(override.dropTrailingInputs, inputs.length);
1334
+ inputs = inputs.slice(0, inputs.length - dropCount);
1335
+ }
1336
+ if ("variadic" in override) {
1337
+ variadic = override.variadic ?? null;
1338
+ }
1339
+ if (override.params) {
1340
+ params = override.params.map((param) => ({ ...param }));
1341
+ }
1342
+ }
1343
+ const requiredParamCount = params.filter((param) => !param.optional).length;
1344
+ const minArgs = override?.minArgs !== void 0 ? override.minArgs : config.minArgs !== void 0 ? config.minArgs : derivedMin + requiredParamCount;
1345
+ const maxArgs = override?.maxArgs !== void 0 ? override.maxArgs : config.maxArgs !== void 0 ? config.maxArgs : derivedMax === null ? null : derivedMax + params.length;
1346
+ const definition = {
1347
+ nodeType: config.typeId,
1348
+ inputs,
1349
+ variadic,
1350
+ params,
1351
+ minArgs,
1352
+ maxArgs,
1353
+ resultValueType: inferExpressionValueType(
1354
+ signature.outputs?.[0]?.ty ?? null
1355
+ )
1356
+ };
1357
+ if (config.typeId === "if" || config.typeId === "case") {
1358
+ const adjustedInputs = definition.inputs.map(
1359
+ (input, index) => {
1360
+ if (config.typeId === "if" && index === 0) {
1361
+ return input;
1362
+ }
1363
+ return {
1364
+ ...input,
1365
+ valueType: "any"
1366
+ };
1367
+ }
1368
+ );
1369
+ definition.inputs = adjustedInputs;
1370
+ if (definition.variadic) {
1371
+ definition.variadic = {
1372
+ ...definition.variadic,
1373
+ valueType: "any"
1374
+ };
1375
+ }
1376
+ definition.resultValueType = "any";
1377
+ }
1378
+ const names = new Set(
1379
+ [config.typeId, ...config.names].map((name) => name.toLowerCase())
1380
+ );
1381
+ names.forEach((name) => {
1382
+ SCALAR_FUNCTIONS.set(name, definition);
1383
+ });
1384
+ const orderedNames = Array.from(names);
1385
+ const displayName = orderedNames[0] ?? config.typeId;
1386
+ SCALAR_FUNCTION_VOCABULARY.push({
1387
+ name: displayName,
1388
+ aliases: orderedNames.slice(1),
1389
+ nodeType: config.typeId,
1390
+ category: config.category ?? "math",
1391
+ description: config.description
1392
+ });
1393
+ }
1394
+ function buildParamArgumentSpecs(signature) {
1395
+ if (!signature || !Array.isArray(signature.params)) {
1396
+ return [];
1397
+ }
1398
+ const params = signature.params;
1399
+ return params.map((param) => ({
1400
+ id: param.id,
1401
+ label: param.label ?? param.id,
1402
+ doc: param.doc ?? void 0,
1403
+ optional: param.default_json !== void 0,
1404
+ valueType: inferExpressionValueType(param.ty),
1405
+ min: typeof param.min === "number" ? param.min : void 0,
1406
+ max: typeof param.max === "number" ? param.max : void 0
1407
+ }));
1408
+ }
1409
+ function inferExpressionValueType(ty) {
1410
+ if (!ty) {
1411
+ return "scalar";
1412
+ }
1413
+ const normalized = ty.toLowerCase();
1414
+ if (normalized === "any") {
1415
+ return "any";
1416
+ }
1417
+ if (normalized === "vector" || normalized === "vec2" || normalized === "vec3" || normalized === "vec4" || normalized === "quat" || normalized === "colorrgba" || normalized === "transform" || normalized === "array" || normalized === "list" || normalized === "record") {
1418
+ return "vector";
1419
+ }
1420
+ if (normalized === "bool" || normalized === "boolean") {
1421
+ return "boolean";
1422
+ }
1423
+ return "scalar";
1424
+ }
1425
+
1426
+ // src/expressionVariables.ts
1427
+ var DefaultExpressionVariableTable = class {
1428
+ constructor() {
1429
+ this.variables = /* @__PURE__ */ new Map();
1430
+ this.order = [];
1431
+ }
1432
+ registerSlotVariable(options) {
1433
+ this.upsert({
1434
+ name: options.name,
1435
+ kind: "slot",
1436
+ nodeId: options.nodeId,
1437
+ metadata: {
1438
+ slotId: options.slotId,
1439
+ slotAlias: options.slotAlias,
1440
+ inputId: options.inputId,
1441
+ targetId: options.targetId,
1442
+ animatableId: options.animatableId,
1443
+ component: options.component,
1444
+ valueType: options.valueType,
1445
+ remap: options.remap,
1446
+ autoRemap: options.autoRemap
1447
+ }
1448
+ });
1449
+ }
1450
+ registerReservedVariable(options) {
1451
+ this.upsert({
1452
+ name: options.name,
1453
+ kind: "reserved",
1454
+ nodeId: options.nodeId,
1455
+ description: options.description,
1456
+ metadata: {
1457
+ targetId: options.targetId,
1458
+ animatableId: options.animatableId,
1459
+ component: options.component
1460
+ }
1461
+ });
1462
+ }
1463
+ resolve(name) {
1464
+ return this.variables.get(name) ?? null;
1465
+ }
1466
+ resolveNodeId(name) {
1467
+ return this.variables.get(name)?.nodeId ?? null;
1468
+ }
1469
+ entries() {
1470
+ return this.order.map((name) => this.variables.get(name)).filter((entry) => Boolean(entry));
1471
+ }
1472
+ firstNodeId() {
1473
+ for (const name of this.order) {
1474
+ const nodeId = this.variables.get(name)?.nodeId;
1475
+ if (nodeId) {
1476
+ return nodeId;
1477
+ }
1478
+ }
1479
+ return null;
1480
+ }
1481
+ missing(names) {
1482
+ const missing = [];
1483
+ for (const name of names) {
1484
+ const entry = this.variables.get(name);
1485
+ if (!entry) {
1486
+ missing.push({ name, reason: "unknown" });
1487
+ continue;
1488
+ }
1489
+ if (!entry.nodeId) {
1490
+ missing.push({
1491
+ name,
1492
+ reason: "unresolved",
1493
+ entry
1494
+ });
1495
+ }
1496
+ }
1497
+ return missing;
1498
+ }
1499
+ upsert(entry) {
1500
+ if (!this.variables.has(entry.name)) {
1501
+ this.order.push(entry.name);
1502
+ }
1503
+ this.variables.set(entry.name, entry);
1504
+ }
1505
+ };
1506
+ function createExpressionVariableTable() {
1507
+ return new DefaultExpressionVariableTable();
1508
+ }
1509
+
1510
+ // src/expressionVocabulary.ts
1511
+ var RESERVED_EXPRESSION_VARIABLES = [
1512
+ {
1513
+ name: "self",
1514
+ description: "Output of the current binding.",
1515
+ scope: "binding",
1516
+ defaultValueType: "scalar",
1517
+ available: true
1518
+ },
1519
+ {
1520
+ name: "time",
1521
+ description: "Elapsed time in seconds since the rig started.",
1522
+ scope: "graph",
1523
+ defaultValueType: "scalar",
1524
+ available: true
1525
+ },
1526
+ {
1527
+ name: "deltaTime",
1528
+ description: "Seconds elapsed since the previous frame update.",
1529
+ scope: "graph",
1530
+ defaultValueType: "scalar",
1531
+ available: true
1532
+ },
1533
+ {
1534
+ name: "frame",
1535
+ description: "Current frame counter.",
1536
+ scope: "graph",
1537
+ defaultValueType: "scalar",
1538
+ available: true
1539
+ }
1540
+ ];
1541
+
1542
+ // src/graphBuilder.ts
1543
+ var import_metadata2 = require("@vizij/node-graph-wasm/metadata");
1544
+
1545
+ // src/ir/builder.ts
1546
+ var graphSequence = 0;
1547
+ function generateGraphId(faceId) {
1548
+ graphSequence += 1;
1549
+ return `ir_${faceId}_${graphSequence}`;
1550
+ }
1551
+ var DefaultIrGraphBuilder = class {
1552
+ constructor(options) {
1553
+ this.nodes = [];
1554
+ this.edges = [];
1555
+ this.constants = [];
1556
+ this.issues = [];
1557
+ this.faceId = options.faceId;
1558
+ this.summary = {
1559
+ faceId: options.faceId,
1560
+ inputs: [],
1561
+ outputs: [],
1562
+ bindings: []
1563
+ };
1564
+ this.metadata = {
1565
+ source: options.source ?? "ir-builder",
1566
+ registryVersion: options.registryVersion,
1567
+ generatedAt: options.generatedAt,
1568
+ annotations: options.annotations
1569
+ };
1570
+ }
1571
+ addNode(node) {
1572
+ this.nodes.push(node);
1573
+ return node;
1574
+ }
1575
+ addEdge(edge) {
1576
+ this.edges.push(edge);
1577
+ return edge;
1578
+ }
1579
+ addConstant(constant) {
1580
+ this.constants.push(constant);
1581
+ return constant;
1582
+ }
1583
+ addIssue(issue) {
1584
+ this.issues.push(issue);
1585
+ return issue;
1586
+ }
1587
+ setSummary(summary) {
1588
+ this.summary = summary;
1589
+ }
1590
+ updateMetadata(metadata) {
1591
+ this.metadata = {
1592
+ ...this.metadata,
1593
+ ...metadata
1594
+ };
1595
+ }
1596
+ build() {
1597
+ return {
1598
+ id: generateGraphId(this.faceId),
1599
+ faceId: this.faceId,
1600
+ nodes: [...this.nodes],
1601
+ edges: [...this.edges],
1602
+ constants: [...this.constants],
1603
+ issues: [...this.issues],
1604
+ summary: { ...this.summary },
1605
+ metadata: { ...this.metadata }
1606
+ };
1607
+ }
1608
+ };
1609
+ function createIrGraphBuilder(options) {
1610
+ return new DefaultIrGraphBuilder(options);
1611
+ }
1612
+ function toIrBindingSummary(summaries) {
1613
+ return summaries.map((summary) => ({
1614
+ ...summary,
1615
+ remap: { ...summary.remap },
1616
+ issues: summary.issues ? [...summary.issues] : void 0
1617
+ }));
1618
+ }
1619
+
1620
+ // src/ir/compiler.ts
1621
+ function compileIrGraph(graph, options = {}) {
1622
+ const preferLegacy = options.preferLegacySpec === true;
1623
+ const legacySpec = graph.legacy?.spec;
1624
+ if (preferLegacy && legacySpec) {
1625
+ return {
1626
+ spec: legacySpec,
1627
+ issues: [...graph.issues]
1628
+ };
1629
+ }
1630
+ const convertedNodes = graph.nodes.map(convertIrNodeToNodeSpec);
1631
+ const existingNodeIds = new Set(convertedNodes.map((node) => node.id));
1632
+ graph.constants.forEach((constant) => {
1633
+ if (existingNodeIds.has(constant.id)) {
1634
+ return;
1635
+ }
1636
+ convertedNodes.push(convertIrConstantToNodeSpec(constant));
1637
+ });
1638
+ const convertedEdges = graph.edges.map(convertIrEdgeToGraphEdge);
1639
+ const spec = {
1640
+ nodes: convertedNodes,
1641
+ edges: convertedEdges.length > 0 ? convertedEdges : void 0
1642
+ };
1643
+ inlineSingleUseConstants(spec);
1644
+ const metadata = extractGraphSpecMetadata(graph);
1645
+ if (metadata !== void 0) {
1646
+ spec.metadata = metadata;
1647
+ }
1648
+ return {
1649
+ spec,
1650
+ issues: [...graph.issues]
1651
+ };
1652
+ }
1653
+ function convertIrNodeToNodeSpec(node) {
1654
+ const spec = {
1655
+ id: node.id,
1656
+ type: node.type
1657
+ };
1658
+ if (node.params) {
1659
+ spec.params = cloneJsonLike(node.params);
1660
+ }
1661
+ if (node.inputDefaults) {
1662
+ spec.input_defaults = cloneJsonLike(node.inputDefaults);
1663
+ }
1664
+ if (node.metadata) {
1665
+ spec.metadata = cloneJsonLike(node.metadata);
1666
+ }
1667
+ return spec;
1668
+ }
1669
+ function convertIrConstantToNodeSpec(constant) {
1670
+ const spec = {
1671
+ id: constant.id,
1672
+ type: "constant",
1673
+ params: {
1674
+ value: constant.value
1675
+ }
1676
+ };
1677
+ if (constant.metadata) {
1678
+ spec.metadata = cloneJsonLike(constant.metadata);
1679
+ }
1680
+ return spec;
1681
+ }
1682
+ function convertIrEdgeToGraphEdge(edge) {
1683
+ return {
1684
+ from: {
1685
+ node_id: edge.from.nodeId,
1686
+ output: edge.from.portId
1687
+ },
1688
+ to: {
1689
+ node_id: edge.to.nodeId,
1690
+ input: edge.to.portId
1691
+ }
1692
+ };
1693
+ }
1694
+ function extractGraphSpecMetadata(graph) {
1695
+ const annotations = graph.metadata.annotations;
1696
+ if (!annotations?.graphSpecMetadata) {
1697
+ return void 0;
1698
+ }
1699
+ return cloneJsonLike(annotations.graphSpecMetadata);
1700
+ }
1701
+ function cloneJsonLike(value) {
1702
+ if (value === void 0 || value === null) {
1703
+ return value;
1704
+ }
1705
+ return JSON.parse(JSON.stringify(value));
1706
+ }
1707
+ function inlineSingleUseConstants(spec) {
1708
+ const nodes = spec.nodes ?? [];
1709
+ const edges = spec.edges ? [...spec.edges] : [];
1710
+ const nodeById = new Map(nodes.map((node) => [node.id, node]));
1711
+ const constantUsage = /* @__PURE__ */ new Map();
1712
+ edges.forEach((edge) => {
1713
+ const source = nodeById.get(edge.from.node_id);
1714
+ if (source?.type === "constant") {
1715
+ constantUsage.set(source.id, (constantUsage.get(source.id) ?? 0) + 1);
1716
+ }
1717
+ });
1718
+ const updatedEdges = [];
1719
+ const constantsToRemove = /* @__PURE__ */ new Set();
1720
+ edges.forEach((edge) => {
1721
+ const source = nodeById.get(edge.from.node_id);
1722
+ if (source?.type === "constant" && constantUsage.get(source.id) === 1 && source.params && Object.prototype.hasOwnProperty.call(source.params, "value")) {
1723
+ const target = nodeById.get(edge.to.node_id);
1724
+ if (target) {
1725
+ const value = source.params.value;
1726
+ if (value !== void 0) {
1727
+ target.input_defaults = {
1728
+ ...target.input_defaults ?? {},
1729
+ [edge.to.input ?? "in"]: value
1730
+ };
1731
+ nodeById.set(target.id, target);
1732
+ constantsToRemove.add(source.id);
1733
+ return;
1734
+ }
1735
+ }
1736
+ }
1737
+ updatedEdges.push(edge);
1738
+ });
1739
+ spec.nodes = nodes.filter((node) => !constantsToRemove.has(node.id));
1740
+ spec.edges = updatedEdges.length > 0 ? updatedEdges : void 0;
1741
+ }
1742
+
1743
+ // src/bindingMetadata.ts
1744
+ function cloneOperand(entry) {
1745
+ return JSON.parse(JSON.stringify(entry));
1746
+ }
1747
+ function describeSlot(entry) {
1748
+ const metadata = entry.metadata;
1749
+ return {
1750
+ kind: "slot",
1751
+ ref: entry.name,
1752
+ alias: metadata?.slotAlias ?? entry.name,
1753
+ slotId: metadata?.slotId,
1754
+ inputId: metadata?.inputId ?? null,
1755
+ valueType: metadata?.valueType
1756
+ };
1757
+ }
1758
+ function describeReserved(entry) {
1759
+ return {
1760
+ kind: "reserved",
1761
+ ref: entry.name,
1762
+ description: entry.description
1763
+ };
1764
+ }
1765
+ function describeReference(node, variables) {
1766
+ const entry = variables.resolve(node.name);
1767
+ if (!entry) {
1768
+ return {
1769
+ kind: "unknown",
1770
+ ref: node.name
1771
+ };
1772
+ }
1773
+ if (entry.kind === "slot") {
1774
+ return describeSlot(entry);
1775
+ }
1776
+ if (entry.kind === "reserved") {
1777
+ return describeReserved(entry);
1778
+ }
1779
+ return {
1780
+ kind: "unknown",
1781
+ ref: entry.name
1782
+ };
1783
+ }
1784
+ function stringifyExpression(node) {
1785
+ switch (node.type) {
1786
+ case "Literal":
1787
+ return Number.isFinite(node.value) ? `${node.value}` : "0";
1788
+ case "VectorLiteral":
1789
+ return `vec(${node.values.join(", ")})`;
1790
+ case "Reference":
1791
+ return node.name;
1792
+ case "Unary":
1793
+ return `${node.operator}${stringifyExpression(node.operand)}`;
1794
+ case "Binary":
1795
+ return `${stringifyExpression(node.left)} ${node.operator} ${stringifyExpression(node.right)}`;
1796
+ case "Function":
1797
+ return `${node.name}(${node.args.map(stringifyExpression).join(", ")})`;
1798
+ default:
1799
+ return "";
1800
+ }
1801
+ }
1802
+ function describeOperand(node, variables) {
1803
+ switch (node.type) {
1804
+ case "Literal":
1805
+ return {
1806
+ kind: "literal",
1807
+ literalValue: node.value
1808
+ };
1809
+ case "Reference":
1810
+ return describeReference(node, variables);
1811
+ case "Unary":
1812
+ case "Binary":
1813
+ case "Function":
1814
+ case "VectorLiteral":
1815
+ return {
1816
+ kind: "expression",
1817
+ expression: stringifyExpression(node)
1818
+ };
1819
+ default:
1820
+ return { kind: "unknown" };
1821
+ }
1822
+ }
1823
+ function describeCaseExpression(node, variables) {
1824
+ if (node.type !== "Function") {
1825
+ return null;
1826
+ }
1827
+ if (node.name.toLowerCase() !== "case") {
1828
+ return null;
1829
+ }
1830
+ if ((node.args?.length ?? 0) < 3) {
1831
+ return null;
1832
+ }
1833
+ const [selector, defaultBranch, ...branches] = node.args;
1834
+ return {
1835
+ kind: "case",
1836
+ selector: describeOperand(selector, variables),
1837
+ defaultBranch: describeOperand(defaultBranch, variables),
1838
+ branches: branches.map((branch) => describeOperand(branch, variables))
1839
+ };
1840
+ }
1841
+ function buildBindingMetadataFromExpression(node, variables) {
1842
+ if (!node) {
1843
+ return void 0;
1844
+ }
1845
+ const caseMetadata = describeCaseExpression(node, variables);
1846
+ if (!caseMetadata) {
1847
+ return void 0;
1848
+ }
1849
+ return {
1850
+ expression: {
1851
+ case: {
1852
+ kind: "case",
1853
+ selector: caseMetadata.selector ? cloneOperand(caseMetadata.selector) : void 0,
1854
+ defaultBranch: caseMetadata.defaultBranch ? cloneOperand(caseMetadata.defaultBranch) : void 0,
1855
+ branches: caseMetadata.branches.map(cloneOperand)
1856
+ }
1857
+ }
1858
+ };
1859
+ }
1860
+
1861
+ // src/graphBuilder.ts
1862
+ function evaluateBinding({
1863
+ binding,
1864
+ target,
1865
+ targetId,
1866
+ animatableId,
1867
+ component,
1868
+ safeId,
1869
+ context,
1870
+ selfNodeId
1871
+ }) {
1872
+ const { nodes, edges, ensureInputNode, bindingIssues, summaryBindings } = context;
1873
+ const exprContext = {
1874
+ componentSafeId: safeId,
1875
+ nodes,
1876
+ edges,
1877
+ constants: /* @__PURE__ */ new Map(),
1878
+ counter: 0,
1879
+ reservedNodes: /* @__PURE__ */ new Map(),
1880
+ nodeValueTypes: /* @__PURE__ */ new Map(),
1881
+ slotRemapNodes: /* @__PURE__ */ new Map(),
1882
+ graphReservedNodes: context.graphReservedNodes,
1883
+ generateReservedNodeId: context.generateReservedNodeId
1884
+ };
1885
+ const targetValueType = target.valueType === "vector" ? "vector" : "scalar";
1886
+ const variableTable = createExpressionVariableTable();
1887
+ const slotSummaries = [];
1888
+ const expressionIssues = [];
1889
+ const rawExpression = typeof binding.expression === "string" ? binding.expression : "";
1890
+ const trimmedExpression = rawExpression.trim();
1891
+ let hasActiveSlot = false;
1892
+ binding.slots.forEach((slot, index) => {
1893
+ const aliasBase = slot.alias?.trim() ?? "";
1894
+ const fallbackAlias = `s${index + 1}`;
1895
+ const alias = aliasBase.length > 0 ? aliasBase : fallbackAlias;
1896
+ const slotId = slot.id && slot.id.length > 0 ? slot.id : alias;
1897
+ const slotValueType = slot.valueType === "vector" ? "vector" : "scalar";
1898
+ let slotOutputId;
1899
+ if (slot.inputId === import_utils3.SELF_BINDING_ID) {
1900
+ if (selfNodeId) {
1901
+ slotOutputId = selfNodeId;
1902
+ hasActiveSlot = true;
1903
+ setNodeValueType(
1904
+ exprContext,
1905
+ slotOutputId,
1906
+ slotValueType === "vector" ? "vector" : "scalar"
1907
+ );
1908
+ } else {
1909
+ expressionIssues.push("Self reference unavailable for this input.");
1910
+ slotOutputId = getConstantNodeId(exprContext, target.defaultValue);
1911
+ }
1912
+ } else if (slot.inputId) {
1913
+ const inputNode = ensureInputNode(slot.inputId);
1914
+ if (inputNode) {
1915
+ slotOutputId = inputNode.nodeId;
1916
+ hasActiveSlot = true;
1917
+ setNodeValueType(
1918
+ exprContext,
1919
+ slotOutputId,
1920
+ slotValueType === "vector" ? "vector" : "scalar"
1921
+ );
1922
+ } else {
1923
+ expressionIssues.push(`Missing standard input "${slot.inputId}".`);
1924
+ slotOutputId = getConstantNodeId(exprContext, 0);
1925
+ }
1926
+ } else {
1927
+ slotOutputId = getConstantNodeId(exprContext, 0);
1928
+ }
1929
+ variableTable.registerSlotVariable({
1930
+ name: alias,
1931
+ nodeId: slotOutputId,
1932
+ slotId,
1933
+ slotAlias: alias,
1934
+ inputId: slot.inputId ?? null,
1935
+ targetId,
1936
+ animatableId,
1937
+ component,
1938
+ valueType: slotValueType,
1939
+ remap: { ...slot.remap },
1940
+ autoRemap: slot.inputId !== import_utils3.SELF_BINDING_ID && slotValueType === "scalar"
1941
+ });
1942
+ setNodeValueType(
1943
+ exprContext,
1944
+ slotOutputId,
1945
+ slotValueType === "vector" ? "vector" : "scalar"
1946
+ );
1947
+ slotSummaries.push({
1948
+ targetId,
1949
+ animatableId,
1950
+ component,
1951
+ slotId,
1952
+ slotAlias: alias,
1953
+ inputId: slot.inputId ?? null,
1954
+ remap: { ...slot.remap },
1955
+ expression: trimmedExpression,
1956
+ valueType: slotValueType,
1957
+ nodeId: slotOutputId,
1958
+ expressionNodeId: slotOutputId
1959
+ });
1960
+ });
1961
+ if (slotSummaries.length === 0) {
1962
+ const alias = PRIMARY_SLOT_ALIAS;
1963
+ const constantId = getConstantNodeId(exprContext, 0);
1964
+ variableTable.registerSlotVariable({
1965
+ name: alias,
1966
+ nodeId: constantId,
1967
+ slotId: PRIMARY_SLOT_ID,
1968
+ slotAlias: alias,
1969
+ inputId: null,
1970
+ targetId,
1971
+ animatableId,
1972
+ component,
1973
+ valueType: targetValueType,
1974
+ remap: createDefaultRemap(target),
1975
+ autoRemap: false
1976
+ });
1977
+ setNodeValueType(
1978
+ exprContext,
1979
+ constantId,
1980
+ targetValueType === "vector" ? "vector" : "scalar"
1981
+ );
1982
+ slotSummaries.push({
1983
+ targetId,
1984
+ animatableId,
1985
+ component,
1986
+ slotId: PRIMARY_SLOT_ID,
1987
+ slotAlias: alias,
1988
+ inputId: null,
1989
+ remap: createDefaultRemap(target),
1990
+ expression: trimmedExpression,
1991
+ valueType: targetValueType,
1992
+ nodeId: constantId,
1993
+ expressionNodeId: constantId
1994
+ });
1995
+ }
1996
+ RESERVED_EXPRESSION_VARIABLES.forEach((reserved) => {
1997
+ if (reserved.available === false) {
1998
+ return;
1999
+ }
2000
+ let nodeId = null;
2001
+ if (reserved.name === "self") {
2002
+ nodeId = selfNodeId ?? null;
2003
+ } else {
2004
+ nodeId = ensureReservedVariableNode(reserved.name, exprContext);
2005
+ }
2006
+ variableTable.registerReservedVariable({
2007
+ name: reserved.name,
2008
+ nodeId,
2009
+ description: reserved.description,
2010
+ targetId: reserved.scope === "binding" ? targetId : void 0,
2011
+ animatableId: reserved.scope === "binding" ? animatableId : void 0,
2012
+ component: reserved.scope === "binding" ? component : void 0
2013
+ });
2014
+ });
2015
+ const defaultAlias = slotSummaries[0]?.slotAlias ?? PRIMARY_SLOT_ALIAS;
2016
+ const expressionText = trimmedExpression.length > 0 ? trimmedExpression : defaultAlias;
2017
+ const parseResult = parseControlExpression(expressionText);
2018
+ let expressionAst = null;
2019
+ if (parseResult.node && parseResult.errors.length === 0) {
2020
+ const references = Array.from(
2021
+ collectExpressionReferences(parseResult.node)
2022
+ );
2023
+ const missing = variableTable.missing(references);
2024
+ if (missing.length === 0) {
2025
+ expressionAst = parseResult.node;
2026
+ } else {
2027
+ validateLiteralParamArguments(parseResult.node, expressionIssues);
2028
+ missing.forEach((missingVar) => {
2029
+ if (missingVar.reason === "unresolved" && missingVar.entry?.kind === "reserved") {
2030
+ expressionIssues.push(
2031
+ `Reserved variable "${missingVar.name}" is unavailable for this binding.`
2032
+ );
2033
+ } else {
2034
+ expressionIssues.push(`Unknown control "${missingVar.name}".`);
2035
+ }
2036
+ });
2037
+ }
2038
+ } else {
2039
+ parseResult.errors.forEach((error) => {
2040
+ expressionIssues.push(error.message);
2041
+ });
2042
+ }
2043
+ const expressionMetadata = buildBindingMetadataFromExpression(
2044
+ expressionAst,
2045
+ variableTable
2046
+ );
2047
+ let valueNodeId = null;
2048
+ if (expressionAst) {
2049
+ valueNodeId = materializeExpression(
2050
+ expressionAst,
2051
+ exprContext,
2052
+ variableTable,
2053
+ expressionIssues
2054
+ );
2055
+ }
2056
+ if (!valueNodeId) {
2057
+ const fallbackNodeId = variableTable.resolveNodeId(defaultAlias) ?? variableTable.firstNodeId();
2058
+ valueNodeId = fallbackNodeId ?? getConstantNodeId(exprContext, 0);
2059
+ }
2060
+ const issuesCopy = expressionIssues.length ? [...new Set(expressionIssues)] : void 0;
2061
+ slotSummaries.forEach((summary) => {
2062
+ summary.expression = expressionText;
2063
+ summary.expressionNodeId = valueNodeId;
2064
+ if (expressionMetadata) {
2065
+ summary.metadata = expressionMetadata;
2066
+ }
2067
+ if (issuesCopy && issuesCopy.length > 0) {
2068
+ summary.issues = issuesCopy;
2069
+ const issueSet = bindingIssues.get(summary.targetId) ?? /* @__PURE__ */ new Set();
2070
+ issuesCopy.forEach((issue) => issueSet.add(issue));
2071
+ bindingIssues.set(summary.targetId, issueSet);
2072
+ }
2073
+ });
2074
+ summaryBindings.push(...slotSummaries);
2075
+ return {
2076
+ valueNodeId,
2077
+ hasActiveSlot
2078
+ };
2079
+ }
2080
+ function sanitizeNodeId(value) {
2081
+ return value.replace(/[^a-zA-Z0-9_]/g, "_");
2082
+ }
2083
+ function buildRigInputPath(faceId, inputPath) {
2084
+ let trimmed = inputPath.startsWith("/") ? inputPath.slice(1) : inputPath;
2085
+ if (!trimmed) {
2086
+ return `rig/${faceId}`;
2087
+ }
2088
+ while (trimmed.startsWith("rig/")) {
2089
+ const segments = trimmed.split("/");
2090
+ if (segments.length >= 3) {
2091
+ const existingFaceId = segments[1];
2092
+ const remainder = segments.slice(2).join("/");
2093
+ if (existingFaceId === faceId) {
2094
+ return trimmed;
2095
+ }
2096
+ trimmed = remainder || "";
2097
+ } else {
2098
+ trimmed = segments.slice(1).join("/");
2099
+ }
2100
+ }
2101
+ const suffix = trimmed ? `/${trimmed}` : "";
2102
+ return `rig/${faceId}${suffix}`;
2103
+ }
2104
+ function getComponentOrder(animatable) {
2105
+ switch (animatable.type) {
2106
+ case "vector2":
2107
+ return ["x", "y"];
2108
+ case "vector3":
2109
+ case "euler":
2110
+ return ["x", "y", "z"];
2111
+ case "rgb":
2112
+ return ["r", "g", "b"];
2113
+ default:
2114
+ return null;
2115
+ }
2116
+ }
2117
+ function isComponentRecord(value) {
2118
+ return typeof value === "object" && value !== null;
2119
+ }
2120
+ function componentIndex(component) {
2121
+ switch (component) {
2122
+ case "x":
2123
+ case "r":
2124
+ return 0;
2125
+ case "y":
2126
+ case "g":
2127
+ return 1;
2128
+ default:
2129
+ return 2;
2130
+ }
2131
+ }
2132
+ function extractComponentDefault(value, component) {
2133
+ if (typeof value === "number") {
2134
+ return value;
2135
+ }
2136
+ if (Array.isArray(value)) {
2137
+ const candidate = value[componentIndex(component)];
2138
+ if (typeof candidate === "number" && Number.isFinite(candidate)) {
2139
+ return candidate;
2140
+ }
2141
+ return 0;
2142
+ }
2143
+ if (value && typeof value === "object") {
2144
+ if (!isComponentRecord(value)) {
2145
+ return 0;
2146
+ }
2147
+ const direct = value[component];
2148
+ if (typeof direct === "number" && Number.isFinite(direct)) {
2149
+ return direct;
2150
+ }
2151
+ const alt = component === "x" ? value.r : component === "y" ? value.g : value.b;
2152
+ if (typeof alt === "number" && Number.isFinite(alt)) {
2153
+ return alt;
2154
+ }
2155
+ }
2156
+ return 0;
2157
+ }
2158
+ function setNodeValueType(context, nodeId, valueType) {
2159
+ context.nodeValueTypes.set(nodeId, valueType);
2160
+ }
2161
+ function getNodeValueType(context, nodeId) {
2162
+ return context.nodeValueTypes.get(nodeId) ?? "any";
2163
+ }
2164
+ function matchesValueType(actual, expected) {
2165
+ if (expected === "any" || actual === "any") {
2166
+ return true;
2167
+ }
2168
+ if (expected === "scalar") {
2169
+ return actual === "scalar" || actual === "boolean";
2170
+ }
2171
+ return actual === expected;
2172
+ }
2173
+ function ensureOperandValueType(context, operandId, expected, functionName, inputId, issues) {
2174
+ if (expected === "any") {
2175
+ return;
2176
+ }
2177
+ const actual = getNodeValueType(context, operandId);
2178
+ if (matchesValueType(actual, expected)) {
2179
+ return;
2180
+ }
2181
+ const expectation = expected === "vector" ? "a vector" : expected === "boolean" ? "a boolean" : "a scalar";
2182
+ issues.push(
2183
+ `Function "${functionName}" expects ${expectation} input for "${inputId}", but the expression produced ${actual}.`
2184
+ );
2185
+ }
2186
+ function resolveSlotReferenceNode(entry, fallbackNodeId, context) {
2187
+ if (entry.kind !== "slot") {
2188
+ return fallbackNodeId;
2189
+ }
2190
+ const metadata = entry.metadata;
2191
+ if (!metadata || metadata.autoRemap === false || !metadata.remap) {
2192
+ return fallbackNodeId;
2193
+ }
2194
+ if (metadata.valueType !== "scalar") {
2195
+ return fallbackNodeId;
2196
+ }
2197
+ if (isIdentityRemap(metadata.remap)) {
2198
+ return fallbackNodeId;
2199
+ }
2200
+ const cacheKey = metadata.slotId ?? entry.name;
2201
+ const existing = context.slotRemapNodes.get(cacheKey);
2202
+ if (existing) {
2203
+ return existing;
2204
+ }
2205
+ const remapNodeId = createSlotRemapNode(
2206
+ context,
2207
+ fallbackNodeId,
2208
+ metadata.remap,
2209
+ metadata.slotAlias ?? cacheKey
2210
+ );
2211
+ context.slotRemapNodes.set(cacheKey, remapNodeId);
2212
+ return remapNodeId;
2213
+ }
2214
+ function createSlotRemapNode(context, sourceNodeId, remap, slotKey) {
2215
+ const safeSlotKey = sanitizeNodeId(slotKey || "slot");
2216
+ const nodeId = `slot_remap_${safeSlotKey}_${context.counter++}`;
2217
+ context.nodes.push({
2218
+ id: nodeId,
2219
+ type: "centered_remap",
2220
+ inputDefaults: {
2221
+ in_low: remap.inLow,
2222
+ in_anchor: remap.inAnchor,
2223
+ in_high: remap.inHigh,
2224
+ out_low: remap.outLow,
2225
+ out_anchor: remap.outAnchor,
2226
+ out_high: remap.outHigh
2227
+ }
2228
+ });
2229
+ context.edges.push({
2230
+ from: { nodeId: sourceNodeId },
2231
+ to: { nodeId, portId: "in" }
2232
+ });
2233
+ setNodeValueType(context, nodeId, "scalar");
2234
+ return nodeId;
2235
+ }
2236
+ function isIdentityRemap(remap) {
2237
+ return nearlyEqual(remap.inLow, remap.outLow) && nearlyEqual(remap.inAnchor, remap.outAnchor) && nearlyEqual(remap.inHigh, remap.outHigh);
2238
+ }
2239
+ function nearlyEqual(a, b, epsilon = 1e-4) {
2240
+ return Math.abs(a - b) <= epsilon;
2241
+ }
2242
+ var REMAP_FUNCTION_NODE_TYPES = /* @__PURE__ */ new Set([
2243
+ "piecewise_remap",
2244
+ "centered_remap",
2245
+ "remap"
2246
+ ]);
2247
+ function shouldSkipAutoRemapForArgument(nodeType, index) {
2248
+ if (!REMAP_FUNCTION_NODE_TYPES.has(nodeType)) {
2249
+ return false;
2250
+ }
2251
+ return index === 0;
2252
+ }
2253
+ function getConstantNodeId(context, value) {
2254
+ const key = Number.isFinite(value) ? value.toString() : "NaN";
2255
+ const existing = context.constants.get(key);
2256
+ if (existing) {
2257
+ return existing;
2258
+ }
2259
+ const nodeId = `const_${context.componentSafeId}_${context.constants.size + 1}`;
2260
+ context.nodes.push({
2261
+ id: nodeId,
2262
+ type: "constant",
2263
+ params: {
2264
+ value: Number.isFinite(value) ? value : 0
2265
+ }
2266
+ });
2267
+ context.constants.set(key, nodeId);
2268
+ setNodeValueType(context, nodeId, "scalar");
2269
+ return nodeId;
2270
+ }
2271
+ function getVectorConstantNodeId(context, values) {
2272
+ const normalized = values.map(
2273
+ (value) => Number.isFinite(value) ? value : 0
2274
+ );
2275
+ const key = `vector:${normalized.join(",")}`;
2276
+ const existing = context.constants.get(key);
2277
+ if (existing) {
2278
+ return existing;
2279
+ }
2280
+ const nodeId = `const_${context.componentSafeId}_${context.constants.size + 1}`;
2281
+ context.nodes.push({
2282
+ id: nodeId,
2283
+ type: "constant",
2284
+ params: {
2285
+ value: {
2286
+ vector: normalized
2287
+ }
2288
+ }
2289
+ });
2290
+ context.constants.set(key, nodeId);
2291
+ setNodeValueType(context, nodeId, "vector");
2292
+ return nodeId;
2293
+ }
2294
+ function createBinaryOperationNode(context, operator, leftId, rightId) {
2295
+ const nodeId = `expr_${context.componentSafeId}_${context.counter++}`;
2296
+ context.nodes.push({
2297
+ id: nodeId,
2298
+ type: operator
2299
+ });
2300
+ setNodeValueType(context, nodeId, "scalar");
2301
+ const leftInput = operator === "subtract" || operator === "divide" ? "lhs" : "operand_1";
2302
+ const rightInput = operator === "subtract" || operator === "divide" ? "rhs" : "operand_2";
2303
+ context.edges.push({
2304
+ from: { nodeId: leftId },
2305
+ to: { nodeId, portId: leftInput }
2306
+ });
2307
+ context.edges.push({
2308
+ from: { nodeId: rightId },
2309
+ to: { nodeId, portId: rightInput }
2310
+ });
2311
+ return nodeId;
2312
+ }
2313
+ function createVariadicOperationNode(context, operator, operandIds) {
2314
+ if (operandIds.length === 0) {
2315
+ return getConstantNodeId(context, operator === "add" ? 0 : 1);
2316
+ }
2317
+ if (operandIds.length === 1) {
2318
+ return operandIds[0];
2319
+ }
2320
+ const nodeId = `expr_${context.componentSafeId}_${context.counter++}`;
2321
+ context.nodes.push({
2322
+ id: nodeId,
2323
+ type: operator
2324
+ });
2325
+ setNodeValueType(context, nodeId, "scalar");
2326
+ operandIds.forEach((operandId, index) => {
2327
+ context.edges.push({
2328
+ from: { nodeId: operandId },
2329
+ to: { nodeId, portId: `operand_${index + 1}` }
2330
+ });
2331
+ });
2332
+ return nodeId;
2333
+ }
2334
+ function createNamedOperationNode(context, operator, inputNames, operandIds, resultType = "scalar", params) {
2335
+ const nodeId = `expr_${context.componentSafeId}_${context.counter++}`;
2336
+ const node = {
2337
+ id: nodeId,
2338
+ type: operator
2339
+ };
2340
+ if (params && Object.keys(params).length > 0) {
2341
+ node.params = params;
2342
+ }
2343
+ context.nodes.push(node);
2344
+ setNodeValueType(context, nodeId, resultType);
2345
+ inputNames.forEach((inputName, index) => {
2346
+ const operandId = operandIds[index];
2347
+ context.edges.push({
2348
+ from: { nodeId: operandId },
2349
+ to: { nodeId, portId: inputName }
2350
+ });
2351
+ });
2352
+ return nodeId;
2353
+ }
2354
+ function ensureReservedVariableNode(name, context) {
2355
+ const existing = context.reservedNodes.get(name);
2356
+ if (existing) {
2357
+ return existing;
2358
+ }
2359
+ if (name === "time" || name === "deltaTime" || name === "frame") {
2360
+ const nodeId = ensureGraphTimeNode(context);
2361
+ context.reservedNodes.set(name, nodeId);
2362
+ return nodeId;
2363
+ }
2364
+ return null;
2365
+ }
2366
+ function ensureGraphTimeNode(context) {
2367
+ const existing = context.graphReservedNodes.get("time");
2368
+ if (existing) {
2369
+ return existing;
2370
+ }
2371
+ const nodeId = context.generateReservedNodeId("time");
2372
+ context.nodes.push({
2373
+ id: nodeId,
2374
+ type: "time",
2375
+ metadata: {
2376
+ reservedVariable: "time"
2377
+ }
2378
+ });
2379
+ setNodeValueType(context, nodeId, "scalar");
2380
+ context.graphReservedNodes.set("time", nodeId);
2381
+ return nodeId;
2382
+ }
2383
+ function emitScalarFunctionNode(definition, operands, argNodes, totalArgCount, context, variables, issues, paramArgOverride) {
2384
+ if (definition.nodeType === "case") {
2385
+ return emitCaseFunctionNode(
2386
+ definition,
2387
+ operands,
2388
+ argNodes,
2389
+ context,
2390
+ variables,
2391
+ issues
2392
+ );
2393
+ }
2394
+ const orderedOperands = operands.slice(0, definition.inputs.length);
2395
+ definition.inputs.forEach((input, index) => {
2396
+ const operandId = orderedOperands[index];
2397
+ if (!operandId) {
2398
+ return;
2399
+ }
2400
+ ensureOperandValueType(
2401
+ context,
2402
+ operandId,
2403
+ input.valueType,
2404
+ definition.nodeType,
2405
+ input.id,
2406
+ issues
2407
+ );
2408
+ });
2409
+ const availableAfterInputs = Math.max(
2410
+ 0,
2411
+ totalArgCount - definition.inputs.length
2412
+ );
2413
+ const paramArgCount = definition.params.length > 0 ? Math.min(definition.params.length, availableAfterInputs) : 0;
2414
+ const variadicArgCount = definition.variadic ? Math.max(0, availableAfterInputs - paramArgCount) : 0;
2415
+ const variadicOperands = definition.variadic ? operands.slice(
2416
+ definition.inputs.length,
2417
+ definition.inputs.length + variadicArgCount
2418
+ ) : [];
2419
+ if (definition.variadic) {
2420
+ variadicOperands.forEach((operandId, _variadicIndex) => {
2421
+ ensureOperandValueType(
2422
+ context,
2423
+ operandId,
2424
+ definition.variadic.valueType,
2425
+ definition.nodeType,
2426
+ definition.variadic.id,
2427
+ issues
2428
+ );
2429
+ });
2430
+ }
2431
+ const paramArgStart = definition.inputs.length + variadicArgCount;
2432
+ const paramArgNodes = paramArgCount > 0 ? paramArgOverride ?? argNodes.slice(paramArgStart, paramArgStart + paramArgCount) : [];
2433
+ const isSlewDebugEnabled = typeof process !== "undefined" && process?.env && process.env.DEBUG_SLEW === "1";
2434
+ if (isSlewDebugEnabled && definition.nodeType === "slew") {
2435
+ console.log("slew-debug", {
2436
+ totalArgs: totalArgCount,
2437
+ inputs: definition.inputs.length,
2438
+ paramCount: definition.params.length,
2439
+ paramArgCount,
2440
+ paramArgNodesTypes: paramArgNodes.map((node) => node?.type ?? null)
2441
+ });
2442
+ }
2443
+ const nodeParams = buildParamAssignments(definition, paramArgNodes, issues);
2444
+ if (definition.variadic && definition.inputs.length === 0) {
2445
+ const nodeId2 = `expr_${context.componentSafeId}_${context.counter++}`;
2446
+ const node = {
2447
+ id: nodeId2,
2448
+ type: definition.nodeType
2449
+ };
2450
+ if (nodeParams) {
2451
+ node.params = nodeParams;
2452
+ }
2453
+ context.nodes.push(node);
2454
+ variadicOperands.forEach((operandId, index) => {
2455
+ context.edges.push({
2456
+ from: { nodeId: operandId },
2457
+ to: {
2458
+ nodeId: nodeId2,
2459
+ portId: `${definition.variadic.id}_${index + 1}`
2460
+ }
2461
+ });
2462
+ });
2463
+ setNodeValueType(context, nodeId2, definition.resultValueType);
2464
+ return nodeId2;
2465
+ }
2466
+ const providedNames = [];
2467
+ const providedOperands = [];
2468
+ definition.inputs.forEach((input, index) => {
2469
+ const operandId = orderedOperands[index];
2470
+ if (!operandId) {
2471
+ return;
2472
+ }
2473
+ providedNames.push(input.id);
2474
+ providedOperands.push(operandId);
2475
+ });
2476
+ const nodeId = createNamedOperationNode(
2477
+ context,
2478
+ definition.nodeType,
2479
+ providedNames,
2480
+ providedOperands,
2481
+ definition.resultValueType,
2482
+ nodeParams ?? void 0
2483
+ );
2484
+ if (definition.variadic) {
2485
+ variadicOperands.forEach((operandId, index) => {
2486
+ context.edges.push({
2487
+ from: { nodeId: operandId },
2488
+ to: {
2489
+ nodeId,
2490
+ portId: `${definition.variadic.id}_${index + 1}`
2491
+ }
2492
+ });
2493
+ });
2494
+ }
2495
+ return nodeId;
2496
+ }
2497
+ function emitCaseFunctionNode(definition, operands, argNodes, context, variables, issues) {
2498
+ const selectorId = operands[0];
2499
+ const defaultId = operands[1];
2500
+ const branchOperands = operands.slice(2);
2501
+ const branchArgs = argNodes.slice(2);
2502
+ if (!selectorId || branchOperands.length === 0) {
2503
+ issues.push(
2504
+ 'Function "case" requires a selector, default, and at least one branch.'
2505
+ );
2506
+ return getConstantNodeId(context, 0);
2507
+ }
2508
+ const nodeId = `expr_${context.componentSafeId}_${context.counter++}`;
2509
+ const caseLabels = branchOperands.map((_, index) => {
2510
+ const extracted = extractCaseLabel(branchArgs[index], variables);
2511
+ if (extracted) {
2512
+ return extracted;
2513
+ }
2514
+ const fallback = `case_${index + 1}`;
2515
+ issues.push(
2516
+ `Case branch ${index + 1} is missing an alias; generated fallback label ${fallback}.`
2517
+ );
2518
+ return fallback;
2519
+ });
2520
+ context.nodes.push({
2521
+ id: nodeId,
2522
+ type: definition.nodeType,
2523
+ params: caseLabels.length > 0 ? { case_labels: caseLabels } : void 0
2524
+ });
2525
+ ensureOperandValueType(
2526
+ context,
2527
+ selectorId,
2528
+ definition.inputs[0]?.valueType ?? "any",
2529
+ definition.nodeType,
2530
+ definition.inputs[0]?.id ?? "selector",
2531
+ issues
2532
+ );
2533
+ context.edges.push({
2534
+ from: { nodeId: selectorId },
2535
+ to: { nodeId, portId: definition.inputs[0]?.id ?? "selector" }
2536
+ });
2537
+ if (defaultId) {
2538
+ ensureOperandValueType(
2539
+ context,
2540
+ defaultId,
2541
+ definition.inputs[1]?.valueType ?? "any",
2542
+ definition.nodeType,
2543
+ "default",
2544
+ issues
2545
+ );
2546
+ context.edges.push({
2547
+ from: { nodeId: defaultId },
2548
+ to: { nodeId, portId: "default" }
2549
+ });
2550
+ }
2551
+ branchOperands.forEach((operandId, index) => {
2552
+ const portId = `${definition.variadic?.id ?? "operand"}_${index + 1}`;
2553
+ ensureOperandValueType(
2554
+ context,
2555
+ operandId,
2556
+ definition.variadic?.valueType ?? "any",
2557
+ definition.nodeType,
2558
+ portId,
2559
+ issues
2560
+ );
2561
+ context.edges.push({
2562
+ from: { nodeId: operandId },
2563
+ to: { nodeId, portId }
2564
+ });
2565
+ });
2566
+ setNodeValueType(context, nodeId, "any");
2567
+ return nodeId;
2568
+ }
2569
+ function buildParamAssignments(definition, paramArgNodes, issues) {
2570
+ if (!definition.params.length || paramArgNodes.length === 0) {
2571
+ return null;
2572
+ }
2573
+ const assignments = {};
2574
+ paramArgNodes.forEach((node, index) => {
2575
+ const spec = definition.params[index];
2576
+ if (!spec) {
2577
+ return;
2578
+ }
2579
+ const literal = extractParamLiteral(
2580
+ node,
2581
+ spec,
2582
+ definition.nodeType,
2583
+ issues
2584
+ );
2585
+ if (literal !== null) {
2586
+ assignments[spec.id] = literal;
2587
+ }
2588
+ });
2589
+ return Object.keys(assignments).length > 0 ? assignments : null;
2590
+ }
2591
+ function extractParamLiteral(node, spec, functionName, issues) {
2592
+ if (spec.valueType === "vector") {
2593
+ if (node.type !== "VectorLiteral") {
2594
+ issues.push(
2595
+ `Function "${functionName}" requires a literal vector for "${spec.id}".`
2596
+ );
2597
+ return null;
2598
+ }
2599
+ if (!Array.isArray(node.values) || node.values.length === 0) {
2600
+ issues.push(
2601
+ `Function "${functionName}" requires at least one value for "${spec.id}".`
2602
+ );
2603
+ return null;
2604
+ }
2605
+ return node.values.map((value) => clampScalarParamValue(value, spec));
2606
+ }
2607
+ if (node.type !== "Literal") {
2608
+ issues.push(
2609
+ `Function "${functionName}" requires a literal ${describeParamExpectation(spec.valueType)} for "${spec.id}".`
2610
+ );
2611
+ return null;
2612
+ }
2613
+ const numeric = Number(node.value);
2614
+ if (!Number.isFinite(numeric)) {
2615
+ issues.push(
2616
+ `Function "${functionName}" requires a finite ${describeParamExpectation(spec.valueType)} for "${spec.id}".`
2617
+ );
2618
+ return null;
2619
+ }
2620
+ const clamped = clampScalarParamValue(numeric, spec);
2621
+ if (spec.valueType === "boolean") {
2622
+ return clamped !== 0;
2623
+ }
2624
+ return clamped;
2625
+ }
2626
+ function clampScalarParamValue(value, spec) {
2627
+ let next = Number.isFinite(value) ? value : 0;
2628
+ if (typeof spec.min === "number" && next < spec.min) {
2629
+ next = spec.min;
2630
+ }
2631
+ if (typeof spec.max === "number" && next > spec.max) {
2632
+ next = spec.max;
2633
+ }
2634
+ return next;
2635
+ }
2636
+ function describeParamExpectation(valueType) {
2637
+ switch (valueType) {
2638
+ case "vector":
2639
+ return "vector";
2640
+ case "boolean":
2641
+ return "boolean";
2642
+ default:
2643
+ return "scalar";
2644
+ }
2645
+ }
2646
+ function extractCaseLabel(node, variables) {
2647
+ if (!node || node.type !== "Reference") {
2648
+ return null;
2649
+ }
2650
+ const entry = variables.resolve(node.name);
2651
+ if (entry && entry.metadata && "slotAlias" in entry.metadata) {
2652
+ const alias = entry.metadata.slotAlias?.trim();
2653
+ if (alias && alias.length > 0) {
2654
+ return alias;
2655
+ }
2656
+ }
2657
+ return node.name;
2658
+ }
2659
+ function collectOperands(node, operator, target) {
2660
+ if (node.type === "Binary" && node.operator === operator) {
2661
+ collectOperands(node.left, operator, target);
2662
+ collectOperands(node.right, operator, target);
2663
+ return;
2664
+ }
2665
+ target.push(node);
2666
+ }
2667
+ function validateLiteralParamArguments(node, issues) {
2668
+ if (node.type === "Function") {
2669
+ const definition = SCALAR_FUNCTIONS.get(node.name.toLowerCase());
2670
+ if (definition && definition.params.length > 0) {
2671
+ const totalArgCount = node.args.length;
2672
+ const availableAfterInputs = Math.max(
2673
+ 0,
2674
+ totalArgCount - definition.inputs.length
2675
+ );
2676
+ const paramArgCount = Math.min(
2677
+ definition.params.length,
2678
+ availableAfterInputs
2679
+ );
2680
+ const variadicArgCount = definition.variadic ? Math.max(0, availableAfterInputs - paramArgCount) : 0;
2681
+ const paramArgStart = definition.inputs.length + variadicArgCount;
2682
+ for (let index = 0; index < paramArgCount; index++) {
2683
+ const spec = definition.params[index];
2684
+ const paramNode = node.args[paramArgStart + index];
2685
+ if (!spec || !paramNode) {
2686
+ continue;
2687
+ }
2688
+ extractParamLiteral(paramNode, spec, node.name, issues);
2689
+ }
2690
+ }
2691
+ node.args.forEach((child) => validateLiteralParamArguments(child, issues));
2692
+ return;
2693
+ }
2694
+ if (node.type === "Unary") {
2695
+ validateLiteralParamArguments(node.operand, issues);
2696
+ return;
2697
+ }
2698
+ if (node.type === "Binary") {
2699
+ validateLiteralParamArguments(node.left, issues);
2700
+ validateLiteralParamArguments(node.right, issues);
2701
+ }
2702
+ }
2703
+ var BINARY_FUNCTION_OPERATOR_MAP = {
2704
+ ">": "greaterthan",
2705
+ "<": "lessthan",
2706
+ "==": "equal",
2707
+ "!=": "notequal",
2708
+ "&&": "and",
2709
+ "||": "or"
2710
+ };
2711
+ function materializeExpression(node, context, variables, issues, options) {
2712
+ const autoRemap = options?.autoRemap !== false;
2713
+ switch (node.type) {
2714
+ case "Literal": {
2715
+ return getConstantNodeId(context, node.value);
2716
+ }
2717
+ case "VectorLiteral": {
2718
+ return getVectorConstantNodeId(context, node.values);
2719
+ }
2720
+ case "Reference": {
2721
+ const entry = variables.resolve(node.name);
2722
+ if (!entry) {
2723
+ issues.push(`Unknown control "${node.name}".`);
2724
+ return getConstantNodeId(context, 0);
2725
+ }
2726
+ const mappedId = entry.nodeId ?? getConstantNodeId(context, 0);
2727
+ if (!autoRemap || entry.kind !== "slot") {
2728
+ return mappedId;
2729
+ }
2730
+ return resolveSlotReferenceNode(entry, mappedId, context);
2731
+ }
2732
+ case "Unary": {
2733
+ const operandId = materializeExpression(
2734
+ node.operand,
2735
+ context,
2736
+ variables,
2737
+ issues
2738
+ );
2739
+ switch (node.operator) {
2740
+ case "+":
2741
+ return operandId;
2742
+ case "-": {
2743
+ const negativeOne = getConstantNodeId(context, -1);
2744
+ return createVariadicOperationNode(context, "multiply", [
2745
+ negativeOne,
2746
+ operandId
2747
+ ]);
2748
+ }
2749
+ case "!": {
2750
+ const definition = SCALAR_FUNCTIONS.get("not");
2751
+ if (!definition) {
2752
+ issues.push('Function "not" is not available in metadata.');
2753
+ return getConstantNodeId(context, 0);
2754
+ }
2755
+ return emitScalarFunctionNode(
2756
+ definition,
2757
+ [operandId],
2758
+ [node.operand],
2759
+ 1,
2760
+ context,
2761
+ variables,
2762
+ issues
2763
+ );
2764
+ }
2765
+ default:
2766
+ issues.push("Unsupported unary operator.");
2767
+ return operandId;
2768
+ }
2769
+ }
2770
+ case "Binary": {
2771
+ const operator = node.operator;
2772
+ if (operator === "+") {
2773
+ const children = [];
2774
+ collectOperands(node, "+", children);
2775
+ const operandIds = children.map(
2776
+ (child) => materializeExpression(child, context, variables, issues)
2777
+ );
2778
+ return createVariadicOperationNode(context, "add", operandIds);
2779
+ }
2780
+ if (operator === "*") {
2781
+ const children = [];
2782
+ collectOperands(node, "*", children);
2783
+ const operandIds = children.map(
2784
+ (child) => materializeExpression(child, context, variables, issues)
2785
+ );
2786
+ return createVariadicOperationNode(context, "multiply", operandIds);
2787
+ }
2788
+ const leftId = materializeExpression(
2789
+ node.left,
2790
+ context,
2791
+ variables,
2792
+ issues
2793
+ );
2794
+ const rightId = materializeExpression(
2795
+ node.right,
2796
+ context,
2797
+ variables,
2798
+ issues
2799
+ );
2800
+ if (operator === "-") {
2801
+ return createBinaryOperationNode(context, "subtract", leftId, rightId);
2802
+ }
2803
+ if (operator === "/") {
2804
+ return createBinaryOperationNode(context, "divide", leftId, rightId);
2805
+ }
2806
+ const mappedFunction = BINARY_FUNCTION_OPERATOR_MAP[operator];
2807
+ if (mappedFunction) {
2808
+ const definition = SCALAR_FUNCTIONS.get(mappedFunction);
2809
+ if (!definition) {
2810
+ issues.push(`Function "${mappedFunction}" is not available.`);
2811
+ return getConstantNodeId(context, 0);
2812
+ }
2813
+ return emitScalarFunctionNode(
2814
+ definition,
2815
+ [leftId, rightId],
2816
+ [node.left, node.right],
2817
+ 2,
2818
+ context,
2819
+ variables,
2820
+ issues
2821
+ );
2822
+ }
2823
+ issues.push(`Unsupported operator "${operator}".`);
2824
+ return getConstantNodeId(context, 0);
2825
+ }
2826
+ case "Function": {
2827
+ const name = node.name;
2828
+ const normalized = name.toLowerCase();
2829
+ const definition = SCALAR_FUNCTIONS.get(normalized);
2830
+ if (!definition) {
2831
+ issues.push(`Unknown function "${name}".`);
2832
+ return getConstantNodeId(context, 0);
2833
+ }
2834
+ const argNodes = node.args;
2835
+ const argCount = argNodes.length;
2836
+ if (argCount < definition.minArgs) {
2837
+ issues.push(
2838
+ `Function "${name}" expects at least ${definition.minArgs} arguments, received ${argCount}.`
2839
+ );
2840
+ return getConstantNodeId(context, 0);
2841
+ }
2842
+ if (definition.maxArgs !== null && argCount > definition.maxArgs) {
2843
+ issues.push(
2844
+ `Function "${name}" expects at most ${definition.maxArgs} arguments, received ${argCount}.`
2845
+ );
2846
+ return getConstantNodeId(context, 0);
2847
+ }
2848
+ if (name === "slew") {
2849
+ const maxRateArg = argNodes[1];
2850
+ if (!maxRateArg || maxRateArg.type !== "Literal") {
2851
+ issues.push(
2852
+ 'Function "slew" requires a literal scalar for "max_rate".'
2853
+ );
2854
+ }
2855
+ }
2856
+ const availableAfterInputs = Math.max(
2857
+ 0,
2858
+ argCount - definition.inputs.length
2859
+ );
2860
+ const paramArgCount = definition.params.length > 0 ? Math.min(definition.params.length, availableAfterInputs) : 0;
2861
+ const variadicArgCount = definition.variadic ? Math.max(0, availableAfterInputs - paramArgCount) : 0;
2862
+ const operandLimit = Math.min(
2863
+ argCount,
2864
+ definition.inputs.length + variadicArgCount
2865
+ );
2866
+ const operandNodes = argNodes.slice(0, operandLimit);
2867
+ const operands = operandNodes.map(
2868
+ (arg, index) => materializeExpression(
2869
+ arg,
2870
+ context,
2871
+ variables,
2872
+ issues,
2873
+ shouldSkipAutoRemapForArgument(definition.nodeType, index) ? { autoRemap: false } : void 0
2874
+ )
2875
+ );
2876
+ const paramArgStart = operandLimit;
2877
+ const paramArgNodes = paramArgCount > 0 ? argNodes.slice(paramArgStart, paramArgStart + paramArgCount) : [];
2878
+ const paramOverride = definition.nodeType === "case" ? void 0 : paramArgNodes;
2879
+ return emitScalarFunctionNode(
2880
+ definition,
2881
+ operands,
2882
+ argNodes,
2883
+ argCount,
2884
+ context,
2885
+ variables,
2886
+ issues,
2887
+ paramOverride
2888
+ );
2889
+ }
2890
+ default: {
2891
+ issues.push("Unsupported expression node.");
2892
+ return getConstantNodeId(context, 0);
2893
+ }
2894
+ }
2895
+ }
2896
+ function buildRigGraphSpec({
2897
+ faceId,
2898
+ animatables,
2899
+ components,
2900
+ bindings,
2901
+ inputsById,
2902
+ inputBindings,
2903
+ inputMetadata
2904
+ }) {
2905
+ const metadataByInputId = inputMetadata ?? /* @__PURE__ */ new Map();
2906
+ const irBuilder = createIrGraphBuilder({
2907
+ faceId,
2908
+ registryVersion: import_metadata2.nodeRegistryVersion,
2909
+ source: "graphBuilder",
2910
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
2911
+ });
2912
+ const nodes = [];
2913
+ const edges = [];
2914
+ const graphReservedNodes = /* @__PURE__ */ new Map();
2915
+ const reservedNodeCounters = /* @__PURE__ */ new Map();
2916
+ const generateReservedNodeId = (kind) => {
2917
+ const nextValue = (reservedNodeCounters.get(kind) ?? 0) + 1;
2918
+ reservedNodeCounters.set(kind, nextValue);
2919
+ return `reserved_${kind}_${nextValue}`;
2920
+ };
2921
+ const inputNodes = /* @__PURE__ */ new Map();
2922
+ const buildingDerived = /* @__PURE__ */ new Set();
2923
+ const computedInputs = /* @__PURE__ */ new Set();
2924
+ const summaryBindings = [];
2925
+ const bindingIssues = /* @__PURE__ */ new Map();
2926
+ const animatableEntries = /* @__PURE__ */ new Map();
2927
+ const outputs = /* @__PURE__ */ new Set();
2928
+ const ensureInputNode = (inputId) => {
2929
+ const existing = inputNodes.get(inputId);
2930
+ if (existing) {
2931
+ return existing;
2932
+ }
2933
+ const input = inputsById.get(inputId);
2934
+ if (!input) {
2935
+ return null;
2936
+ }
2937
+ const defaultValue = Number.isFinite(input.defaultValue) ? input.defaultValue : 0;
2938
+ const inputBindingRaw = inputBindings[inputId];
2939
+ if (inputBindingRaw) {
2940
+ if (buildingDerived.has(inputId)) {
2941
+ const issueSet = bindingIssues.get(inputId) ?? /* @__PURE__ */ new Set();
2942
+ issueSet.add("Derived input cycle detected.");
2943
+ bindingIssues.set(inputId, issueSet);
2944
+ return null;
2945
+ }
2946
+ buildingDerived.add(inputId);
2947
+ try {
2948
+ const target = bindingTargetFromInput(input);
2949
+ const binding = ensureBindingStructure(inputBindingRaw, target);
2950
+ const requiresSelf = binding.inputId === import_utils3.SELF_BINDING_ID || binding.slots.some((slot) => slot.inputId === import_utils3.SELF_BINDING_ID);
2951
+ let selfNodeId;
2952
+ if (requiresSelf) {
2953
+ const sliderNodeId = `input_raw_${sanitizeNodeId(inputId)}`;
2954
+ nodes.push({
2955
+ id: sliderNodeId,
2956
+ type: "input",
2957
+ params: {
2958
+ path: buildRigInputPath(faceId, input.path),
2959
+ value: { float: defaultValue }
2960
+ }
2961
+ });
2962
+ selfNodeId = sliderNodeId;
2963
+ }
2964
+ const { valueNodeId, hasActiveSlot } = evaluateBinding({
2965
+ binding,
2966
+ target,
2967
+ targetId: inputId,
2968
+ animatableId: inputId,
2969
+ component: void 0,
2970
+ safeId: sanitizeNodeId(inputId),
2971
+ context: {
2972
+ nodes,
2973
+ edges,
2974
+ ensureInputNode,
2975
+ bindingIssues,
2976
+ summaryBindings,
2977
+ graphReservedNodes,
2978
+ generateReservedNodeId
2979
+ },
2980
+ selfNodeId
2981
+ });
2982
+ if (!valueNodeId || !hasActiveSlot) {
2983
+ const constNodeId = `derived_default_${sanitizeNodeId(inputId)}`;
2984
+ nodes.push({
2985
+ id: constNodeId,
2986
+ type: "constant",
2987
+ params: {
2988
+ value: input.defaultValue
2989
+ }
2990
+ });
2991
+ const record3 = { nodeId: constNodeId, input };
2992
+ inputNodes.set(inputId, record3);
2993
+ return record3;
2994
+ }
2995
+ computedInputs.add(inputId);
2996
+ const record2 = { nodeId: valueNodeId, input };
2997
+ inputNodes.set(inputId, record2);
2998
+ return record2;
2999
+ } finally {
3000
+ buildingDerived.delete(inputId);
3001
+ }
3002
+ }
3003
+ const nodeId = `input_${sanitizeNodeId(inputId)}`;
3004
+ nodes.push({
3005
+ id: nodeId,
3006
+ type: "input",
3007
+ params: {
3008
+ path: buildRigInputPath(faceId, input.path),
3009
+ value: { float: defaultValue }
3010
+ }
3011
+ });
3012
+ const record = { nodeId, input };
3013
+ inputNodes.set(inputId, record);
3014
+ return record;
3015
+ };
3016
+ const ensureAnimatableEntry = (animatableId) => {
3017
+ const existing = animatableEntries.get(animatableId);
3018
+ if (existing) {
3019
+ return existing;
3020
+ }
3021
+ const animatable = animatables[animatableId];
3022
+ if (!animatable) {
3023
+ return null;
3024
+ }
3025
+ const entry = {
3026
+ animatable,
3027
+ values: /* @__PURE__ */ new Map(),
3028
+ defaults: /* @__PURE__ */ new Map(),
3029
+ isDriven: false
3030
+ };
3031
+ animatableEntries.set(animatableId, entry);
3032
+ return entry;
3033
+ };
3034
+ components.forEach((component) => {
3035
+ const bindingRaw = bindings[component.id];
3036
+ const target = bindingTargetFromComponent(component);
3037
+ const binding = bindingRaw ? ensureBindingStructure(bindingRaw, target) : null;
3038
+ const entry = ensureAnimatableEntry(component.animatableId);
3039
+ if (!entry) {
3040
+ return;
3041
+ }
3042
+ const key = component.component ?? "scalar";
3043
+ let valueNodeId = null;
3044
+ let hasActiveSlot = false;
3045
+ if (binding) {
3046
+ const { valueNodeId: producedNodeId, hasActiveSlot: active } = evaluateBinding({
3047
+ binding,
3048
+ target,
3049
+ targetId: component.id,
3050
+ animatableId: component.animatableId,
3051
+ component: component.component,
3052
+ safeId: component.safeId,
3053
+ context: {
3054
+ nodes,
3055
+ edges,
3056
+ ensureInputNode,
3057
+ bindingIssues,
3058
+ summaryBindings,
3059
+ graphReservedNodes,
3060
+ generateReservedNodeId
3061
+ }
3062
+ });
3063
+ valueNodeId = producedNodeId;
3064
+ hasActiveSlot = active;
3065
+ if (active) {
3066
+ entry.isDriven = true;
3067
+ }
3068
+ } else {
3069
+ summaryBindings.push({
3070
+ targetId: component.id,
3071
+ animatableId: component.animatableId,
3072
+ component: component.component,
3073
+ slotId: PRIMARY_SLOT_ID,
3074
+ slotAlias: PRIMARY_SLOT_ALIAS,
3075
+ inputId: null,
3076
+ remap: createDefaultRemap(target),
3077
+ expression: PRIMARY_SLOT_ALIAS,
3078
+ valueType: target.valueType === "vector" ? "vector" : "scalar",
3079
+ issues: ["Binding not found."],
3080
+ nodeId: PRIMARY_SLOT_ID,
3081
+ expressionNodeId: PRIMARY_SLOT_ID
3082
+ });
3083
+ const fallbackIssues = bindingIssues.get(component.id) ?? /* @__PURE__ */ new Set();
3084
+ fallbackIssues.add("Binding not found.");
3085
+ bindingIssues.set(component.id, fallbackIssues);
3086
+ }
3087
+ if (!valueNodeId || !hasActiveSlot) {
3088
+ entry.defaults.set(key, component.defaultValue);
3089
+ return;
3090
+ }
3091
+ entry.values.set(key, valueNodeId);
3092
+ });
3093
+ inputsById.forEach((_input, inputId) => {
3094
+ ensureInputNode(inputId);
3095
+ });
3096
+ animatableEntries.forEach((entry, animatableId) => {
3097
+ if (!entry.isDriven) {
3098
+ return;
3099
+ }
3100
+ outputs.add(animatableId);
3101
+ const safeId = sanitizeNodeId(animatableId);
3102
+ const order = getComponentOrder(entry.animatable);
3103
+ if (!order) {
3104
+ const valueNodeId = entry.values.get("scalar");
3105
+ if (!valueNodeId) {
3106
+ return;
3107
+ }
3108
+ const outputNodeId2 = `out_${safeId}`;
3109
+ nodes.push({
3110
+ id: outputNodeId2,
3111
+ type: "output",
3112
+ params: {
3113
+ path: animatableId
3114
+ }
3115
+ });
3116
+ edges.push({
3117
+ from: { nodeId: valueNodeId },
3118
+ to: { nodeId: outputNodeId2, portId: "in" }
3119
+ });
3120
+ return;
3121
+ }
3122
+ const joinNodeId = `join_${safeId}`;
3123
+ nodes.push({
3124
+ id: joinNodeId,
3125
+ type: "join"
3126
+ });
3127
+ order.forEach((componentKey, index) => {
3128
+ let sourceId = entry.values.get(componentKey);
3129
+ if (!sourceId) {
3130
+ const componentDefault = entry.defaults.get(componentKey) ?? extractComponentDefault(
3131
+ (0, import_utils2.buildAnimatableValue)(entry.animatable, void 0),
3132
+ componentKey
3133
+ );
3134
+ const constNodeId = `const_${safeId}_${componentKey}`;
3135
+ nodes.push({
3136
+ id: constNodeId,
3137
+ type: "constant",
3138
+ params: {
3139
+ value: componentDefault
3140
+ }
3141
+ });
3142
+ sourceId = constNodeId;
3143
+ }
3144
+ edges.push({
3145
+ from: { nodeId: sourceId },
3146
+ to: { nodeId: joinNodeId, portId: `operand_${index + 1}` }
3147
+ });
3148
+ });
3149
+ const outputNodeId = `out_${safeId}`;
3150
+ nodes.push({
3151
+ id: outputNodeId,
3152
+ type: "output",
3153
+ params: {
3154
+ path: animatableId
3155
+ }
3156
+ });
3157
+ edges.push({
3158
+ from: { nodeId: joinNodeId },
3159
+ to: { nodeId: outputNodeId, portId: "in" }
3160
+ });
3161
+ });
3162
+ const nodeById = /* @__PURE__ */ new Map();
3163
+ nodes.forEach((node) => {
3164
+ nodeById.set(node.id, node);
3165
+ });
3166
+ const constantUsage = /* @__PURE__ */ new Map();
3167
+ edges.forEach((edge) => {
3168
+ const source = nodeById.get(edge.from.nodeId);
3169
+ if (source?.type === "constant") {
3170
+ constantUsage.set(source.id, (constantUsage.get(source.id) ?? 0) + 1);
3171
+ }
3172
+ });
3173
+ const updatedEdges = [];
3174
+ const constantsToRemove = /* @__PURE__ */ new Set();
3175
+ edges.forEach((edge) => {
3176
+ const source = nodeById.get(edge.from.nodeId);
3177
+ if (source?.type === "constant" && constantUsage.get(source.id) === 1 && source.params && Object.prototype.hasOwnProperty.call(source.params, "value")) {
3178
+ const target = nodeById.get(edge.to.nodeId);
3179
+ if (target) {
3180
+ const value = source.params.value;
3181
+ if (value !== void 0) {
3182
+ const targetPort = edge.to.portId ?? "in";
3183
+ target.inputDefaults = {
3184
+ ...target.inputDefaults ?? {},
3185
+ [targetPort]: value
3186
+ };
3187
+ nodeById.set(target.id, target);
3188
+ constantsToRemove.add(source.id);
3189
+ return;
3190
+ }
3191
+ }
3192
+ }
3193
+ updatedEdges.push(edge);
3194
+ });
3195
+ const filteredNodes = nodes.filter((node) => !constantsToRemove.has(node.id)).map((node) => nodeById.get(node.id) ?? node);
3196
+ const remapDefaultIssues = validateRemapDefaults(filteredNodes);
3197
+ const dynamicOutputs = Array.from(outputs);
3198
+ const computedInputList = Array.from(computedInputs);
3199
+ const filteredSummaryBindings = summaryBindings.filter(
3200
+ (binding) => outputs.has(binding.animatableId) || computedInputs.has(binding.animatableId)
3201
+ );
3202
+ const vizijMetadata = {
3203
+ vizij: {
3204
+ faceId,
3205
+ inputs: Array.from(inputsById.values()).map((input) => {
3206
+ const meta = metadataByInputId.get(input.id);
3207
+ const derivedRoot = meta?.root ?? input.group;
3208
+ let derivedSource = meta?.source;
3209
+ if (!derivedSource && input.path.startsWith("/standard/")) {
3210
+ derivedSource = "preset";
3211
+ }
3212
+ const entry = {
3213
+ id: input.id,
3214
+ path: input.path,
3215
+ sourceId: input.sourceId,
3216
+ label: input.label,
3217
+ group: input.group,
3218
+ defaultValue: input.defaultValue,
3219
+ range: {
3220
+ min: input.range.min,
3221
+ max: input.range.max
3222
+ }
3223
+ };
3224
+ if (derivedSource) {
3225
+ entry.source = derivedSource;
3226
+ }
3227
+ if (derivedRoot) {
3228
+ entry.root = derivedRoot;
3229
+ }
3230
+ return entry;
3231
+ }),
3232
+ bindings: filteredSummaryBindings.map((binding) => ({
3233
+ ...binding,
3234
+ remap: { ...binding.remap },
3235
+ expression: binding.expression,
3236
+ valueType: binding.valueType,
3237
+ issues: binding.issues ? [...binding.issues] : void 0,
3238
+ metadata: binding.metadata ? cloneJsonLike2(binding.metadata) : void 0
3239
+ }))
3240
+ }
3241
+ };
3242
+ const issuesByTarget = {};
3243
+ bindingIssues.forEach((issues, targetId) => {
3244
+ if (issues.size === 0) {
3245
+ return;
3246
+ }
3247
+ issuesByTarget[targetId] = Array.from(issues);
3248
+ });
3249
+ const fatalIssues = /* @__PURE__ */ new Set();
3250
+ Object.values(issuesByTarget).forEach((issues) => {
3251
+ issues.forEach((issue) => fatalIssues.add(issue));
3252
+ });
3253
+ remapDefaultIssues.forEach((issue) => fatalIssues.add(issue));
3254
+ const summaryPayload = {
3255
+ faceId,
3256
+ inputs: Array.from(inputNodes.values()).map(
3257
+ ({ input }) => buildRigInputPath(faceId, input.path)
3258
+ ),
3259
+ outputs: [...dynamicOutputs, ...computedInputList],
3260
+ bindings: filteredSummaryBindings
3261
+ };
3262
+ const irSummary = {
3263
+ faceId: summaryPayload.faceId,
3264
+ inputs: [...summaryPayload.inputs],
3265
+ outputs: [...summaryPayload.outputs],
3266
+ bindings: toIrBindingSummary(
3267
+ filteredSummaryBindings.map((binding) => ({
3268
+ ...binding,
3269
+ remap: { ...binding.remap },
3270
+ issues: binding.issues ? [...binding.issues] : void 0
3271
+ }))
3272
+ )
3273
+ };
3274
+ const fatalIssueList = Array.from(fatalIssues);
3275
+ const irIssues = createIrIssuesFromLegacy(issuesByTarget, fatalIssueList);
3276
+ filteredNodes.forEach((node) => {
3277
+ irBuilder.addNode(cloneIrNode(node));
3278
+ const constant = extractIrConstantFromNode(node);
3279
+ if (constant) {
3280
+ irBuilder.addConstant(constant);
3281
+ }
3282
+ });
3283
+ updatedEdges.forEach((edge) => {
3284
+ irBuilder.addEdge(cloneIrEdge(edge));
3285
+ });
3286
+ irBuilder.setSummary(irSummary);
3287
+ irIssues.forEach((issue) => irBuilder.addIssue(issue));
3288
+ irBuilder.updateMetadata({
3289
+ annotations: {
3290
+ graphSpecMetadata: cloneJsonLike2(vizijMetadata)
3291
+ }
3292
+ });
3293
+ const irGraph = irBuilder.build();
3294
+ const compiled = compileIrGraph(irGraph, { preferLegacySpec: false });
3295
+ return {
3296
+ spec: compiled.spec,
3297
+ summary: summaryPayload,
3298
+ issues: {
3299
+ byTarget: issuesByTarget,
3300
+ fatal: fatalIssueList
3301
+ },
3302
+ ir: {
3303
+ graph: irGraph,
3304
+ compile: (options) => compileIrGraph(irGraph, options)
3305
+ }
3306
+ };
3307
+ }
3308
+ function createIrIssuesFromLegacy(issuesByTarget, fatalIssues) {
3309
+ const fatalSet = new Set(fatalIssues);
3310
+ const collected = [];
3311
+ let counter = 0;
3312
+ Object.entries(issuesByTarget).forEach(([targetId, messages]) => {
3313
+ messages.forEach((message) => {
3314
+ counter += 1;
3315
+ collected.push({
3316
+ id: `issue_${counter}`,
3317
+ severity: fatalSet.has(message) ? "error" : "warning",
3318
+ message,
3319
+ targetId,
3320
+ tags: fatalSet.has(message) ? ["fatal"] : void 0
3321
+ });
3322
+ });
3323
+ });
3324
+ fatalSet.forEach((message) => {
3325
+ const alreadyCaptured = collected.some(
3326
+ (issue) => issue.message === message
3327
+ );
3328
+ if (alreadyCaptured) {
3329
+ return;
3330
+ }
3331
+ counter += 1;
3332
+ collected.push({
3333
+ id: `issue_${counter}`,
3334
+ severity: "error",
3335
+ message,
3336
+ tags: ["fatal"]
3337
+ });
3338
+ });
3339
+ return collected;
3340
+ }
3341
+ function cloneIrNode(node) {
3342
+ return {
3343
+ id: node.id,
3344
+ type: node.type,
3345
+ category: node.category,
3346
+ label: node.label,
3347
+ description: node.description,
3348
+ params: cloneJsonLike2(node.params),
3349
+ inputDefaults: cloneJsonLike2(node.inputDefaults),
3350
+ metadata: cloneJsonLike2(node.metadata)
3351
+ };
3352
+ }
3353
+ function cloneIrEdge(edge) {
3354
+ return {
3355
+ id: edge.id,
3356
+ from: {
3357
+ nodeId: edge.from.nodeId,
3358
+ portId: edge.from.portId,
3359
+ component: edge.from.component
3360
+ },
3361
+ to: {
3362
+ nodeId: edge.to.nodeId,
3363
+ portId: edge.to.portId,
3364
+ component: edge.to.component
3365
+ },
3366
+ metadata: cloneJsonLike2(edge.metadata)
3367
+ };
3368
+ }
3369
+ function extractIrConstantFromNode(node) {
3370
+ if (node.type !== "constant") {
3371
+ return null;
3372
+ }
3373
+ const value = node.params?.value;
3374
+ if (typeof value !== "number" || !Number.isFinite(value)) {
3375
+ return null;
3376
+ }
3377
+ return {
3378
+ id: node.id,
3379
+ value,
3380
+ valueType: "scalar",
3381
+ metadata: cloneJsonLike2(node.metadata)
3382
+ };
3383
+ }
3384
+ function cloneJsonLike2(value) {
3385
+ if (value === void 0 || value === null) {
3386
+ return value;
3387
+ }
3388
+ return JSON.parse(JSON.stringify(value));
3389
+ }
3390
+ function validateRemapDefaults(nodes) {
3391
+ const issues = [];
3392
+ nodes.forEach((node) => {
3393
+ if (node.type !== "centered_remap") {
3394
+ return;
3395
+ }
3396
+ const defaults = node.inputDefaults ?? {};
3397
+ [
3398
+ "in_low",
3399
+ "in_anchor",
3400
+ "in_high",
3401
+ "out_low",
3402
+ "out_anchor",
3403
+ "out_high"
3404
+ ].forEach((key) => {
3405
+ const value = defaults[key];
3406
+ if (typeof value !== "number" || !Number.isFinite(value)) {
3407
+ issues.push(`Remap node ${node.id} missing ${key} default.`);
3408
+ }
3409
+ });
3410
+ });
3411
+ return issues;
3412
+ }
3413
+
3414
+ // src/ir/inspection.ts
3415
+ var import_metadata3 = require("@vizij/node-graph-wasm/metadata");
3416
+ var MACHINE_REPORT_VERSION = 1;
3417
+ var REMAP_KEYS = [
3418
+ "inLow",
3419
+ "inAnchor",
3420
+ "inHigh",
3421
+ "outLow",
3422
+ "outAnchor",
3423
+ "outHigh"
3424
+ ];
3425
+ var DEFAULT_DIFF_LIMIT = 50;
3426
+ function buildMachineReport(result) {
3427
+ return {
3428
+ reportVersion: MACHINE_REPORT_VERSION,
3429
+ faceId: result.summary.faceId,
3430
+ summary: normalizeSummary(result.summary),
3431
+ issues: normalizeIssues(result.issues),
3432
+ irGraph: result.ir?.graph ? normalizeIrGraph(result.ir.graph) : void 0
3433
+ };
3434
+ }
3435
+ function diffMachineReports(actual, expected, options) {
3436
+ const limit = normalizeDiffLimit(options?.limit);
3437
+ const ctx = {
3438
+ differences: [],
3439
+ limit,
3440
+ limitReached: false
3441
+ };
3442
+ diffValues(actual, expected, "$", ctx);
3443
+ return {
3444
+ equal: ctx.differences.length === 0,
3445
+ differences: ctx.differences,
3446
+ limitReached: ctx.limitReached
3447
+ };
3448
+ }
3449
+ function normalizeSummary(summary) {
3450
+ return {
3451
+ faceId: summary.faceId,
3452
+ inputs: [...summary.inputs].sort(),
3453
+ outputs: [...summary.outputs].sort(),
3454
+ bindings: normalizeGraphBindingSummaries(summary.bindings)
3455
+ };
3456
+ }
3457
+ function normalizeGraphBindingSummaries(bindings) {
3458
+ const normalized = bindings.map((binding) => ({
3459
+ targetId: binding.targetId,
3460
+ animatableId: binding.animatableId,
3461
+ component: binding.component ?? void 0,
3462
+ slotId: binding.slotId,
3463
+ slotAlias: binding.slotAlias,
3464
+ inputId: binding.inputId ?? null,
3465
+ remap: normalizeRemap2(binding.remap),
3466
+ expression: binding.expression,
3467
+ valueType: binding.valueType,
3468
+ nodeId: binding.nodeId,
3469
+ expressionNodeId: binding.expressionNodeId,
3470
+ issues: normalizeStringArray(binding.issues),
3471
+ metadata: cloneBindingMetadata(binding.metadata)
3472
+ }));
3473
+ normalized.sort((a, b) => bindingSortKey(a).localeCompare(bindingSortKey(b)));
3474
+ return normalized;
3475
+ }
3476
+ function cloneBindingMetadata(metadata) {
3477
+ if (!metadata) {
3478
+ return void 0;
3479
+ }
3480
+ return JSON.parse(JSON.stringify(metadata));
3481
+ }
3482
+ function normalizeIssues(issues) {
3483
+ const byTargetEntries = Object.entries(issues.byTarget ?? {}).map(
3484
+ ([targetId, messages]) => [targetId, [...messages].sort()]
3485
+ );
3486
+ byTargetEntries.sort(([a], [b]) => a.localeCompare(b));
3487
+ const byTarget = {};
3488
+ byTargetEntries.forEach(([targetId, messages]) => {
3489
+ byTarget[targetId] = messages;
3490
+ });
3491
+ return {
3492
+ fatal: [...issues.fatal].sort(),
3493
+ byTarget
3494
+ };
3495
+ }
3496
+ function normalizeIrGraph(graph) {
3497
+ return {
3498
+ id: graph.id,
3499
+ faceId: graph.faceId,
3500
+ nodes: normalizeIrNodes(graph.nodes),
3501
+ edges: normalizeIrEdges(graph.edges),
3502
+ constants: normalizeIrConstants(graph.constants),
3503
+ issues: normalizeIrIssues(graph.issues),
3504
+ summary: normalizeIrGraphSummary(graph.summary),
3505
+ metadata: normalizeIrMetadata(graph.metadata)
3506
+ };
3507
+ }
3508
+ function normalizeIrNodes(nodes) {
3509
+ return [...nodes].map((node) => ({
3510
+ ...node,
3511
+ inputDefaults: node.inputDefaults ? sortPlainObject(node.inputDefaults) : void 0,
3512
+ params: node.params ? sortPlainObject(node.params) : void 0,
3513
+ metadata: node.metadata ? sortPlainObject(node.metadata) : void 0,
3514
+ annotations: buildNodeAnnotations(node)
3515
+ })).sort((a, b) => a.id.localeCompare(b.id));
3516
+ }
3517
+ function normalizeIrEdges(edges) {
3518
+ return [...edges].map((edge) => ({
3519
+ ...edge,
3520
+ from: normalizePortRef(edge.from),
3521
+ to: normalizePortRef(edge.to),
3522
+ metadata: edge.metadata ? sortPlainObject(edge.metadata) : void 0
3523
+ })).sort((a, b) => edgeSortKey(a).localeCompare(edgeSortKey(b)));
3524
+ }
3525
+ function normalizePortRef(edgeRef) {
3526
+ return {
3527
+ nodeId: edgeRef.nodeId,
3528
+ portId: edgeRef.portId ?? void 0,
3529
+ component: edgeRef.component ?? void 0
3530
+ };
3531
+ }
3532
+ function edgeSortKey(edge) {
3533
+ if (edge.id) {
3534
+ return edge.id;
3535
+ }
3536
+ const fromPort = edge.from.portId ?? "";
3537
+ const toPort = edge.to.portId ?? "";
3538
+ return `${edge.from.nodeId}:${fromPort}->${edge.to.nodeId}:${toPort}`;
3539
+ }
3540
+ function normalizeIrConstants(constants) {
3541
+ return [...constants].map((constant) => ({
3542
+ ...constant,
3543
+ metadata: constant.metadata ? sortPlainObject(constant.metadata) : void 0
3544
+ })).sort((a, b) => a.id.localeCompare(b.id));
3545
+ }
3546
+ function normalizeIrIssues(issues) {
3547
+ return [...issues].map((issue) => ({
3548
+ ...issue,
3549
+ tags: normalizeStringArray(issue.tags),
3550
+ details: issue.details ? sortPlainObject(issue.details) : void 0
3551
+ })).sort((a, b) => a.id.localeCompare(b.id));
3552
+ }
3553
+ function normalizeIrGraphSummary(summary) {
3554
+ return {
3555
+ faceId: summary.faceId,
3556
+ inputs: [...summary.inputs].sort(),
3557
+ outputs: [...summary.outputs].sort(),
3558
+ bindings: normalizeIrBindingSummaries(summary.bindings)
3559
+ };
3560
+ }
3561
+ function normalizeIrBindingSummaries(bindings) {
3562
+ const normalized = bindings.map((binding) => ({
3563
+ ...binding,
3564
+ remap: sortPlainObject(binding.remap),
3565
+ issues: normalizeStringArray(binding.issues)
3566
+ }));
3567
+ normalized.sort((a, b) => bindingSortKey(a).localeCompare(bindingSortKey(b)));
3568
+ return normalized;
3569
+ }
3570
+ function normalizeIrMetadata(metadata) {
3571
+ const normalized = {
3572
+ source: metadata.source ?? "unknown"
3573
+ };
3574
+ if (metadata.registryVersion) {
3575
+ normalized.registryVersion = metadata.registryVersion;
3576
+ }
3577
+ if (metadata.annotations) {
3578
+ const sorted = sortPlainObject(metadata.annotations);
3579
+ if (Object.keys(sorted).length > 0) {
3580
+ normalized.annotations = sorted;
3581
+ }
3582
+ }
3583
+ return normalized;
3584
+ }
3585
+ function buildNodeAnnotations(node) {
3586
+ const signature = findRegistrySignature(node.type);
3587
+ if (!signature) {
3588
+ return void 0;
3589
+ }
3590
+ return {
3591
+ registry: normalizeRegistrySignature(signature)
3592
+ };
3593
+ }
3594
+ function findRegistrySignature(typeId) {
3595
+ try {
3596
+ const signature = (0, import_metadata3.findNodeSignature)(typeId);
3597
+ if (!signature) {
3598
+ return void 0;
3599
+ }
3600
+ return signature;
3601
+ } catch {
3602
+ return void 0;
3603
+ }
3604
+ }
3605
+ function normalizeRegistrySignature(signature) {
3606
+ return {
3607
+ typeId: signature.type_id,
3608
+ name: signature.name,
3609
+ category: signature.category,
3610
+ doc: signature.doc,
3611
+ inputs: signature.inputs.map(normalizeRegistryPortSpec),
3612
+ variadicInputs: signature.variadic_inputs ? normalizeRegistryVariadicSpec(signature.variadic_inputs) : void 0,
3613
+ outputs: signature.outputs.map(normalizeRegistryPortSpec),
3614
+ variadicOutputs: signature.variadic_outputs ? normalizeRegistryVariadicSpec(signature.variadic_outputs) : void 0,
3615
+ params: signature.params.map(normalizeRegistryParamSpec)
3616
+ };
3617
+ }
3618
+ function normalizeRegistryPortSpec(port) {
3619
+ const normalized = {
3620
+ id: port.id,
3621
+ label: port.label,
3622
+ type: port.ty ?? "unknown"
3623
+ };
3624
+ if (port.doc) {
3625
+ normalized.doc = port.doc;
3626
+ }
3627
+ if (port.optional) {
3628
+ normalized.optional = true;
3629
+ }
3630
+ return normalized;
3631
+ }
3632
+ function normalizeRegistryVariadicSpec(spec) {
3633
+ const normalized = {
3634
+ id: spec.id,
3635
+ label: spec.label,
3636
+ type: spec.ty ?? "unknown",
3637
+ min: spec.min
3638
+ };
3639
+ if (spec.doc) {
3640
+ normalized.doc = spec.doc;
3641
+ }
3642
+ if (typeof spec.max === "number") {
3643
+ normalized.max = spec.max;
3644
+ }
3645
+ return normalized;
3646
+ }
3647
+ function normalizeRegistryParamSpec(param) {
3648
+ const normalized = {
3649
+ id: param.id,
3650
+ label: param.label,
3651
+ type: param.ty ?? "unknown"
3652
+ };
3653
+ if (param.doc) {
3654
+ normalized.doc = param.doc;
3655
+ }
3656
+ if (param.default_json !== void 0) {
3657
+ normalized.defaultValue = normalizePlainValue(param.default_json);
3658
+ }
3659
+ if (typeof param.min === "number") {
3660
+ normalized.min = param.min;
3661
+ }
3662
+ if (typeof param.max === "number") {
3663
+ normalized.max = param.max;
3664
+ }
3665
+ return normalized;
3666
+ }
3667
+ function normalizeRemap2(remap) {
3668
+ const normalized = {};
3669
+ REMAP_KEYS.forEach((key) => {
3670
+ normalized[key] = remap[key];
3671
+ });
3672
+ return normalized;
3673
+ }
3674
+ function normalizeStringArray(values) {
3675
+ if (!values || values.length === 0) {
3676
+ return void 0;
3677
+ }
3678
+ const unique = Array.from(new Set(values));
3679
+ unique.sort();
3680
+ return unique;
3681
+ }
3682
+ function bindingSortKey(binding) {
3683
+ const component = binding.component ?? "";
3684
+ return `${binding.targetId}::${component}::${binding.slotId}::${binding.slotAlias}::${binding.animatableId}`;
3685
+ }
3686
+ function sortPlainObject(record) {
3687
+ const sorted = {};
3688
+ Object.keys(record).sort().forEach((key) => {
3689
+ sorted[key] = normalizePlainValue(record[key]);
3690
+ });
3691
+ return sorted;
3692
+ }
3693
+ function normalizePlainValue(value) {
3694
+ if (Array.isArray(value)) {
3695
+ return value.map((entry) => normalizePlainValue(entry));
3696
+ }
3697
+ if (isPlainObject(value)) {
3698
+ return sortPlainObject(value);
3699
+ }
3700
+ return value;
3701
+ }
3702
+ function isPlainObject(value) {
3703
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3704
+ }
3705
+ function diffValues(actual, expected, path, ctx) {
3706
+ if (ctx.differences.length >= ctx.limit) {
3707
+ ctx.limitReached = true;
3708
+ return;
3709
+ }
3710
+ if (Object.is(actual, expected)) {
3711
+ return;
3712
+ }
3713
+ if (Array.isArray(actual) && Array.isArray(expected)) {
3714
+ const compareLength = Math.min(actual.length, expected.length);
3715
+ for (let index = 0; index < compareLength; index += 1) {
3716
+ diffValues(actual[index], expected[index], pathIndex(path, index), ctx);
3717
+ if (ctx.differences.length >= ctx.limit) {
3718
+ ctx.limitReached = true;
3719
+ return;
3720
+ }
3721
+ }
3722
+ if (actual.length > expected.length) {
3723
+ for (let index = compareLength; index < actual.length; index += 1) {
3724
+ pushDifference(ctx, {
3725
+ kind: "unexpected",
3726
+ path: pathIndex(path, index),
3727
+ actual: actual[index]
3728
+ });
3729
+ if (ctx.differences.length >= ctx.limit) {
3730
+ ctx.limitReached = true;
3731
+ return;
3732
+ }
3733
+ }
3734
+ } else if (expected.length > actual.length) {
3735
+ for (let index = compareLength; index < expected.length; index += 1) {
3736
+ pushDifference(ctx, {
3737
+ kind: "missing",
3738
+ path: pathIndex(path, index),
3739
+ expected: expected[index]
3740
+ });
3741
+ if (ctx.differences.length >= ctx.limit) {
3742
+ ctx.limitReached = true;
3743
+ return;
3744
+ }
3745
+ }
3746
+ }
3747
+ return;
3748
+ }
3749
+ if (isPlainObject(actual) && isPlainObject(expected)) {
3750
+ const keys = /* @__PURE__ */ new Set([...Object.keys(actual), ...Object.keys(expected)]);
3751
+ const sortedKeys = Array.from(keys).sort();
3752
+ for (const key of sortedKeys) {
3753
+ if (!(key in actual)) {
3754
+ pushDifference(ctx, {
3755
+ kind: "missing",
3756
+ path: pathKey(path, key),
3757
+ expected: expected[key]
3758
+ });
3759
+ } else if (!(key in expected)) {
3760
+ pushDifference(ctx, {
3761
+ kind: "unexpected",
3762
+ path: pathKey(path, key),
3763
+ actual: actual[key]
3764
+ });
3765
+ } else {
3766
+ diffValues(actual[key], expected[key], pathKey(path, key), ctx);
3767
+ }
3768
+ if (ctx.differences.length >= ctx.limit) {
3769
+ ctx.limitReached = true;
3770
+ return;
3771
+ }
3772
+ }
3773
+ return;
3774
+ }
3775
+ pushDifference(ctx, {
3776
+ kind: "mismatch",
3777
+ path,
3778
+ actual,
3779
+ expected
3780
+ });
3781
+ }
3782
+ function pathKey(base, key) {
3783
+ if (base === "") {
3784
+ return key;
3785
+ }
3786
+ if (base.endsWith("]")) {
3787
+ return `${base}.${key}`;
3788
+ }
3789
+ return `${base}.${key}`;
3790
+ }
3791
+ function pathIndex(base, index) {
3792
+ return `${base}[${index}]`;
3793
+ }
3794
+ function pushDifference(ctx, entry) {
3795
+ if (ctx.differences.length < ctx.limit) {
3796
+ ctx.differences.push(entry);
3797
+ if (ctx.differences.length === ctx.limit) {
3798
+ ctx.limitReached = true;
3799
+ }
3800
+ } else {
3801
+ ctx.limitReached = true;
3802
+ }
3803
+ }
3804
+ function normalizeDiffLimit(limit) {
3805
+ if (typeof limit !== "number" || !Number.isFinite(limit) || limit <= 0) {
3806
+ return DEFAULT_DIFF_LIMIT;
3807
+ }
3808
+ return Math.floor(limit);
3809
+ }
3810
+
3811
+ // src/cli/reportIr.ts
3812
+ function usage() {
3813
+ console.error(
3814
+ [
3815
+ "Usage: vizij-ir-report --input <path/to/buildGraphOptions.json> [options]",
3816
+ "",
3817
+ "Options:",
3818
+ " --dump <path|-> Write normalized machine-readable report (use '-' for stdout).",
3819
+ " --diff <path|-> Compare against a dumped report (use '-' or 'stdin').",
3820
+ " --diff-limit <number> Maximum diff entries to emit (default 50).",
3821
+ " -h, --help Show this message."
3822
+ ].join("\n")
3823
+ );
3824
+ process.exit(1);
3825
+ throw new Error("Unreachable");
3826
+ }
3827
+ function parseArgs(argv) {
3828
+ const args = [...argv];
3829
+ let inputPath = "";
3830
+ let dumpPath;
3831
+ let diffPath;
3832
+ let diffLimit;
3833
+ while (args.length > 0) {
3834
+ const token = args.shift();
3835
+ if (!token) {
3836
+ break;
3837
+ }
3838
+ if (token === "--input" || token === "-i") {
3839
+ const value = args.shift();
3840
+ if (!value) {
3841
+ usage();
3842
+ }
3843
+ inputPath = value;
3844
+ } else if (token === "--help" || token === "-h") {
3845
+ usage();
3846
+ } else if (token === "--dump" || token === "--dump-json") {
3847
+ const value = args.shift();
3848
+ if (!value) {
3849
+ usage();
3850
+ }
3851
+ dumpPath = normalizeStreamToken(value);
3852
+ } else if (token === "--diff") {
3853
+ const value = args.shift();
3854
+ if (!value) {
3855
+ usage();
3856
+ }
3857
+ diffPath = normalizeStreamToken(value);
3858
+ } else if (token === "--diff-limit") {
3859
+ const value = args.shift();
3860
+ if (!value) {
3861
+ usage();
3862
+ }
3863
+ diffLimit = parsePositiveInteger(value, "--diff-limit");
3864
+ } else if (!token.startsWith("--") && !inputPath) {
3865
+ inputPath = token;
3866
+ }
3867
+ }
3868
+ if (!inputPath) {
3869
+ usage();
3870
+ }
3871
+ return {
3872
+ inputPath: (0, import_node_path.resolve)(process.cwd(), inputPath),
3873
+ dumpPath: resolveOptionalPath(dumpPath),
3874
+ diffPath: resolveOptionalPath(diffPath),
3875
+ diffLimit
3876
+ };
3877
+ }
3878
+ function loadCliInput(path) {
3879
+ const raw = JSON.parse((0, import_node_fs.readFileSync)(path, "utf-8"));
3880
+ const inputs = raw.inputs ?? [];
3881
+ const inputsById = new Map(inputs.map((input) => [input.id, input]));
3882
+ const inputMetadata = raw.inputMetadata ? new Map(
3883
+ raw.inputMetadata.map((entry) => ({
3884
+ id: entry.id,
3885
+ value: entry
3886
+ })).filter(
3887
+ (entry) => Boolean(entry.id)
3888
+ ).map((entry) => [entry.id, entry.value])
3889
+ ) : void 0;
3890
+ return {
3891
+ faceId: raw.faceId,
3892
+ animatables: raw.animatables,
3893
+ components: raw.components,
3894
+ bindings: raw.bindings,
3895
+ inputsById,
3896
+ inputBindings: raw.inputBindings ?? {},
3897
+ inputMetadata
3898
+ };
3899
+ }
3900
+ function printIssues(result) {
3901
+ console.log(`
3902
+ IR Diagnostics for face "${result.summary.faceId}"`);
3903
+ if (result.issues.fatal.length === 0) {
3904
+ console.log("\u2022 No fatal binding issues detected.");
3905
+ } else {
3906
+ console.log("\u2022 Fatal issues:");
3907
+ result.issues.fatal.forEach((issue) => console.log(` - ${issue}`));
3908
+ }
3909
+ const targets = Object.entries(result.issues.byTarget);
3910
+ if (targets.length > 0) {
3911
+ console.log("\nPer-binding issues:");
3912
+ targets.forEach(([targetId, issues]) => {
3913
+ issues.forEach((issue) => {
3914
+ console.log(` - ${targetId}: ${issue}`);
3915
+ });
3916
+ });
3917
+ }
3918
+ const irIssues = result.ir?.graph.issues ?? [];
3919
+ if (irIssues.length > 0) {
3920
+ console.log("\nIR issues:");
3921
+ irIssues.forEach((issue) => {
3922
+ console.log(
3923
+ ` - (${issue.severity}) ${issue.message}${issue.targetId ? ` [${issue.targetId}]` : ""}`
3924
+ );
3925
+ });
3926
+ }
3927
+ }
3928
+ function printBindingSummary(result, _options) {
3929
+ console.log("\nBinding summary:");
3930
+ result.summary.bindings.forEach((binding) => {
3931
+ console.log(
3932
+ ` - ${binding.targetId} (${binding.slotAlias}): expr="${binding.expression}"`
3933
+ );
3934
+ if (binding.issues && binding.issues.length > 0) {
3935
+ binding.issues.forEach((issue) => {
3936
+ console.log(` \u2022 ${issue}`);
3937
+ });
3938
+ }
3939
+ });
3940
+ }
3941
+ async function main() {
3942
+ const { inputPath, dumpPath, diffPath, diffLimit } = parseArgs(
3943
+ process.argv.slice(2)
3944
+ );
3945
+ const options = loadCliInput(inputPath);
3946
+ const result = buildRigGraphSpec(options);
3947
+ const machineReport = buildMachineReport(result);
3948
+ if (diffPath && dumpPath && diffPath !== "-" && dumpPath !== "-" && diffPath === dumpPath) {
3949
+ throw new Error(
3950
+ "Cannot reuse the same file path for --dump and --diff. Provide separate files."
3951
+ );
3952
+ }
3953
+ if (diffPath) {
3954
+ if (dumpPath === "-") {
3955
+ throw new Error("Cannot dump to stdout while running --diff.");
3956
+ }
3957
+ if (dumpPath) {
3958
+ writeMachineDump(machineReport, dumpPath);
3959
+ }
3960
+ const baseline = loadMachineDump(diffPath);
3961
+ validateReportVersion(baseline.reportVersion);
3962
+ const diffResult = diffMachineReports(machineReport, baseline, {
3963
+ limit: diffLimit
3964
+ });
3965
+ const diffPayload = {
3966
+ mode: "diff",
3967
+ reportVersion: MACHINE_REPORT_VERSION,
3968
+ faceId: machineReport.faceId,
3969
+ inputPath,
3970
+ baselinePath: diffPath,
3971
+ equal: diffResult.equal,
3972
+ differenceCount: diffResult.differences.length,
3973
+ limitReached: diffResult.limitReached,
3974
+ differences: diffResult.differences
3975
+ };
3976
+ console.log(JSON.stringify(diffPayload, null, 2));
3977
+ process.exitCode = diffResult.equal ? 0 : 1;
3978
+ return;
3979
+ }
3980
+ if (dumpPath) {
3981
+ writeMachineDump(machineReport, dumpPath);
3982
+ return;
3983
+ }
3984
+ console.log(
3985
+ `Loaded ${options.components.length} components / ${options.faceId} from ${inputPath}`
3986
+ );
3987
+ printIssues(result);
3988
+ printBindingSummary(result, options);
3989
+ console.log("\nDone.");
3990
+ }
3991
+ main().catch((error) => {
3992
+ console.error("[vizij-ir-report] Failed:", error);
3993
+ process.exit(1);
3994
+ });
3995
+ function resolveOptionalPath(path) {
3996
+ if (!path || path === "-") {
3997
+ return path;
3998
+ }
3999
+ return (0, import_node_path.resolve)(process.cwd(), path);
4000
+ }
4001
+ function normalizeStreamToken(value) {
4002
+ const lower = value.toLowerCase();
4003
+ if (lower === "-" || lower === "stdout" || lower === "stdin") {
4004
+ return "-";
4005
+ }
4006
+ return value;
4007
+ }
4008
+ function parsePositiveInteger(value, flag) {
4009
+ const parsed = Number.parseInt(value, 10);
4010
+ if (!Number.isFinite(parsed) || parsed <= 0) {
4011
+ console.error(`Invalid value for ${flag}: ${value}`);
4012
+ usage();
4013
+ }
4014
+ return parsed;
4015
+ }
4016
+ function writeMachineDump(report, target) {
4017
+ const payload = `${JSON.stringify(report, null, 2)}
4018
+ `;
4019
+ if (target === "-") {
4020
+ process.stdout.write(payload);
4021
+ return;
4022
+ }
4023
+ (0, import_node_fs.writeFileSync)(target, payload, "utf-8");
4024
+ }
4025
+ function loadMachineDump(path) {
4026
+ const content = path === "-" ? (0, import_node_fs.readFileSync)(0, "utf-8") : (0, import_node_fs.readFileSync)(path, "utf-8");
4027
+ try {
4028
+ const parsed = JSON.parse(content);
4029
+ if (typeof parsed !== "object" || parsed === null || typeof parsed.reportVersion !== "number") {
4030
+ throw new Error("Invalid machine report structure.");
4031
+ }
4032
+ return parsed;
4033
+ } catch (error) {
4034
+ if (error instanceof SyntaxError) {
4035
+ throw new Error("Failed to parse machine-readable report JSON.");
4036
+ }
4037
+ throw error;
4038
+ }
4039
+ }
4040
+ function validateReportVersion(version) {
4041
+ if (version !== MACHINE_REPORT_VERSION) {
4042
+ throw new Error(
4043
+ `Machine report version mismatch (expected ${MACHINE_REPORT_VERSION}, received ${version}).`
4044
+ );
4045
+ }
4046
+ }