@kaskad/eval-tree 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,787 @@
1
+ import { computed, untracked, observable, runInAction, isComputed, toJS, comparer } from 'mobx';
2
+ import { parseValueType, isUnfoldedNodeSchema, unfoldNodeSchema } from '@kaskad/schema';
3
+ import { isObservable } from 'rxjs';
4
+ import { log } from '@kaskad/config';
5
+
6
+ const debugName = (context, thing) => {
7
+ return [context.componentId, thing].join('|');
8
+ };
9
+
10
+ function evaluateArray(evalNode, context) {
11
+ const itemResults = evalNode.items.map((item) => evaluateNode(item, context));
12
+ const itemComputeds = itemResults.map((result) => result.computed);
13
+ const disposers = itemResults.flatMap((result) => result.disposers);
14
+ return {
15
+ computed: computed(() => {
16
+ const evaluating = itemComputeds.some((ro) => ro.get().evaluating);
17
+ const failed = itemComputeds.some((ro) => ro.get().failed);
18
+ return itemComputeds.reduce((acc, ro) => {
19
+ acc.value.push(ro.get().value);
20
+ return acc;
21
+ }, { value: [], evaluating, settled: !evaluating, failed });
22
+ }, {
23
+ name: debugName(context, 'array-literal'),
24
+ }),
25
+ disposers,
26
+ };
27
+ }
28
+
29
+ /**
30
+ * Parses a function signature string into structured parameter and return types.
31
+ *
32
+ * Format: (param1Type, param2Type, ...) => returnType
33
+ * Variadic: (...type) => returnType
34
+ *
35
+ * Examples:
36
+ * - '() => void'
37
+ * - '(string, number) => boolean'
38
+ * - '(unknown[], number) => unknown'
39
+ * - '(...unknown) => boolean'
40
+ * - '({name: string}, number[]) => void'
41
+ */
42
+ function parseSignature(signature) {
43
+ const trimmed = signature.trim();
44
+ // Split by => to get params and return parts
45
+ const arrowIndex = trimmed.indexOf('=>');
46
+ if (arrowIndex === -1) {
47
+ throw new Error(`Invalid signature: missing '=>'. Expected format: '(params) => returnType'`);
48
+ }
49
+ const paramsStr = trimmed.slice(0, arrowIndex).trim();
50
+ const returnStr = trimmed.slice(arrowIndex + 2).trim();
51
+ if (!returnStr) {
52
+ throw new Error(`Invalid signature: missing return type after '=>'`);
53
+ }
54
+ // Parse return type
55
+ const returns = parseValueType(returnStr);
56
+ // Parse params
57
+ if (!paramsStr.startsWith('(') || !paramsStr.endsWith(')')) {
58
+ throw new Error(`Invalid signature: params must be wrapped in parentheses. Got: ${paramsStr}`);
59
+ }
60
+ const paramsContent = paramsStr.slice(1, -1).trim();
61
+ // Empty params
62
+ if (!paramsContent) {
63
+ return {
64
+ params: [],
65
+ variadic: false,
66
+ returns,
67
+ };
68
+ }
69
+ // Check for variadic
70
+ if (paramsContent.startsWith('...')) {
71
+ const variadicType = paramsContent.slice(3).trim();
72
+ if (!variadicType) {
73
+ throw new Error(`Invalid signature: variadic parameter must have a type. Got: '...${variadicType}'`);
74
+ }
75
+ return {
76
+ params: [parseValueType(variadicType)],
77
+ variadic: true,
78
+ returns,
79
+ };
80
+ }
81
+ // Split by comma, respecting nesting
82
+ const paramTypes = splitByComma(paramsContent);
83
+ return {
84
+ params: paramTypes.map((typeStr) => parseValueType(typeStr.trim())),
85
+ variadic: false,
86
+ returns,
87
+ };
88
+ }
89
+ /**
90
+ * Splits a string by commas while respecting nested braces and brackets.
91
+ * Used to split parameter lists that may contain complex types like {a: string, b: number}.
92
+ */
93
+ function splitByComma(str) {
94
+ const parts = [];
95
+ let current = '';
96
+ let depth = 0;
97
+ for (let i = 0; i < str.length; i++) {
98
+ const char = str[i];
99
+ if (char === '{' || char === '[' || char === '(') {
100
+ depth++;
101
+ current += char;
102
+ }
103
+ else if (char === '}' || char === ']' || char === ')') {
104
+ depth--;
105
+ current += char;
106
+ }
107
+ else if (char === ',' && depth === 0) {
108
+ parts.push(current.trim());
109
+ current = '';
110
+ }
111
+ else {
112
+ current += char;
113
+ }
114
+ }
115
+ if (current.trim()) {
116
+ parts.push(current.trim());
117
+ }
118
+ return parts;
119
+ }
120
+
121
+ /**
122
+ * Recursively extracts primitive values from nested NodeSchema structures.
123
+ *
124
+ * Traverses the value tree and unwraps NodeSchema objects to their primitive values.
125
+ * Preserves collection types (Array, Set, Map) and component inline values.
126
+ *
127
+ * @param value - Value that may contain nested NodeSchema objects
128
+ * @returns Primitive value with all NodeSchema wrappers removed
129
+ */
130
+ function extractPrimitiveValue(value) {
131
+ if (value === null || value === undefined) {
132
+ return value;
133
+ }
134
+ if (isUnfoldedNodeSchema(value)) {
135
+ return extractPrimitiveValue(value.value);
136
+ }
137
+ if (Array.isArray(value)) {
138
+ return value.map(extractPrimitiveValue);
139
+ }
140
+ if (value instanceof Set) {
141
+ const extracted = new Set();
142
+ value.forEach((item) => extracted.add(extractPrimitiveValue(item)));
143
+ return extracted;
144
+ }
145
+ if (value instanceof Map) {
146
+ const extracted = new Map();
147
+ value.forEach((val, key) => extracted.set(key, extractPrimitiveValue(val)));
148
+ return extracted;
149
+ }
150
+ // ComponentNodeInlineValue has Maps that must be preserved
151
+ if (typeof value === 'object' && 'componentType' in value && value.componentType) {
152
+ return value;
153
+ }
154
+ if (typeof value === 'object') {
155
+ const extracted = {};
156
+ for (const [key, val] of Object.entries(value)) {
157
+ extracted[key] = extractPrimitiveValue(val);
158
+ }
159
+ return extracted;
160
+ }
161
+ return value;
162
+ }
163
+ /**
164
+ * Normalizes raw values by detecting formulas and extracting primitives.
165
+ *
166
+ * Performs schema unfolding to handle formula detection (e.g., "={{...}}" or "=..."),
167
+ * then extracts primitive values from the resulting NodeSchema structure.
168
+ *
169
+ * @param rawValue - Raw value that may be a formula string or contain nested schemas
170
+ * @param valueType - Expected type of the value after normalization
171
+ * @returns ComputationSchema if value is a formula, otherwise primitive value
172
+ */
173
+ function normalizeValue(rawValue, valueType) {
174
+ // Handle runtime Set values directly - they don't need unfolding.
175
+ // This occurs when Set-typed variables are passed to functions expecting arrays.
176
+ // The function implementations (e.g., filterIn) handle both arrays and Sets.
177
+ if (rawValue instanceof Set) {
178
+ return rawValue;
179
+ }
180
+ const schema = unfoldNodeSchema(rawValue, valueType);
181
+ if (schema.computation) {
182
+ return schema.computation;
183
+ }
184
+ return extractPrimitiveValue(schema.value);
185
+ }
186
+
187
+ /**
188
+ * Sentinel value for arguments that have not yet been evaluated.
189
+ * Distinguishes unevaluated arguments from arguments that evaluated to null.
190
+ */
191
+ const NOT_EVALUATED = Symbol('NOT_EVALUATED');
192
+ /**
193
+ * Evaluates function arguments and aggregates evaluation states.
194
+ * Returns values array with NOT_EVALUATED symbol for unevaluated arguments.
195
+ */
196
+ function evaluateArguments(ctx) {
197
+ const { computedArgs, argsTypes } = ctx;
198
+ const values = [];
199
+ let allEvaluated = true;
200
+ let anyEvaluating = false;
201
+ let anyFailed = false;
202
+ for (let i = 0; i < computedArgs.length; i++) {
203
+ const { value: rawValue, settled, evaluating, failed } = computedArgs[i].get();
204
+ if (evaluating) {
205
+ anyEvaluating = true;
206
+ }
207
+ if (failed) {
208
+ anyFailed = true;
209
+ }
210
+ if (!settled) {
211
+ allEvaluated = false;
212
+ values.push(NOT_EVALUATED);
213
+ }
214
+ else {
215
+ if (!argsTypes[i]) {
216
+ throw new Error(`Argument type definition missing for argument ${i}. ` +
217
+ `Expected ${argsTypes.length} arguments but got ${computedArgs.length}.`);
218
+ }
219
+ values.push(normalizeValue(rawValue, argsTypes[i].valueType));
220
+ }
221
+ }
222
+ return { values, allEvaluated, anyEvaluating, anyFailed };
223
+ }
224
+
225
+ /**
226
+ * Wraps async values (Promises, RxJS Observables) in MobX-tracked structures.
227
+ *
228
+ * Returns PromiseValue for Promises, ObservableValue for Observables, or the value unchanged.
229
+ */
230
+ function toObservable(result) {
231
+ if (result instanceof Promise) {
232
+ return untracked(() => {
233
+ const asyncValue = observable.object({
234
+ kind: 'promise',
235
+ current: null,
236
+ error: null,
237
+ pending: true,
238
+ });
239
+ result.then((value) => {
240
+ runInAction(() => {
241
+ asyncValue.current = value;
242
+ asyncValue.pending = false;
243
+ asyncValue.error = null;
244
+ });
245
+ }, (err) => {
246
+ runInAction(() => {
247
+ asyncValue.error = err;
248
+ asyncValue.pending = false;
249
+ asyncValue.current = null;
250
+ });
251
+ });
252
+ return asyncValue;
253
+ });
254
+ }
255
+ if (isObservable(result)) {
256
+ return untracked(() => {
257
+ const asyncValue = observable.object({
258
+ kind: 'observable',
259
+ current: null,
260
+ error: null,
261
+ pending: true,
262
+ subscription: undefined,
263
+ });
264
+ try {
265
+ const subscription = result.subscribe({
266
+ next: (value) => {
267
+ runInAction(() => {
268
+ asyncValue.current = value;
269
+ asyncValue.pending = false;
270
+ asyncValue.error = null;
271
+ });
272
+ },
273
+ error: (err) => {
274
+ runInAction(() => {
275
+ asyncValue.error = err;
276
+ asyncValue.pending = false;
277
+ });
278
+ },
279
+ complete: () => {
280
+ runInAction(() => {
281
+ asyncValue.pending = false;
282
+ });
283
+ },
284
+ });
285
+ runInAction(() => {
286
+ asyncValue.subscription = subscription;
287
+ });
288
+ }
289
+ catch (error) {
290
+ runInAction(() => {
291
+ asyncValue.error = error;
292
+ asyncValue.pending = false;
293
+ asyncValue.current = null;
294
+ });
295
+ }
296
+ return asyncValue;
297
+ });
298
+ }
299
+ return result;
300
+ }
301
+
302
+ /**
303
+ * Wraps an imperative function to work in the reactive pipeline.
304
+ * Unwraps computed arguments to plain values and propagates argument states.
305
+ */
306
+ function wrapImperativeAsReactive(imperativeDef) {
307
+ const { params } = parseSignature(imperativeDef.signature);
308
+ const argsTypes = params.map((p) => ({ valueType: p }));
309
+ if (imperativeDef.contextual) {
310
+ // Contextual imperative -> contextual reactive
311
+ return {
312
+ kind: 'reactive',
313
+ contextual: true,
314
+ execute: (ctx, computedArgs) => {
315
+ return computed(() => {
316
+ const { values, allEvaluated, anyEvaluating, anyFailed } = evaluateArguments({
317
+ computedArgs,
318
+ argsTypes,
319
+ });
320
+ if (!allEvaluated || anyFailed) {
321
+ return {
322
+ returnValue: null,
323
+ argsEvaluated: false,
324
+ argsEvaluating: anyEvaluating,
325
+ argsFailed: anyFailed,
326
+ };
327
+ }
328
+ try {
329
+ const result = imperativeDef.execute(ctx, ...values);
330
+ return {
331
+ returnValue: toObservable(result),
332
+ argsEvaluated: true,
333
+ argsEvaluating: false,
334
+ argsFailed: false,
335
+ };
336
+ }
337
+ catch (error) {
338
+ return {
339
+ returnValue: toObservable(Promise.reject(error)),
340
+ argsEvaluated: true,
341
+ argsEvaluating: false,
342
+ argsFailed: false,
343
+ };
344
+ }
345
+ });
346
+ },
347
+ signature: imperativeDef.signature,
348
+ };
349
+ }
350
+ else {
351
+ // Pure imperative -> pure reactive
352
+ return {
353
+ kind: 'reactive',
354
+ execute: (computedArgs) => {
355
+ return computed(() => {
356
+ const { values, allEvaluated, anyEvaluating, anyFailed } = evaluateArguments({
357
+ computedArgs,
358
+ argsTypes,
359
+ });
360
+ if (!allEvaluated || anyFailed) {
361
+ return {
362
+ returnValue: null,
363
+ argsEvaluated: false,
364
+ argsEvaluating: anyEvaluating,
365
+ argsFailed: anyFailed,
366
+ };
367
+ }
368
+ try {
369
+ const result = imperativeDef.execute(...values);
370
+ return {
371
+ returnValue: toObservable(result),
372
+ argsEvaluated: true,
373
+ argsEvaluating: false,
374
+ argsFailed: false,
375
+ };
376
+ }
377
+ catch (error) {
378
+ return {
379
+ returnValue: toObservable(Promise.reject(error)),
380
+ argsEvaluated: true,
381
+ argsEvaluating: false,
382
+ argsFailed: false,
383
+ };
384
+ }
385
+ });
386
+ },
387
+ signature: imperativeDef.signature,
388
+ };
389
+ }
390
+ }
391
+
392
+ /**
393
+ * Ensures a function definition is reactive.
394
+ *
395
+ * This utility function provides a consistent way to convert any function definition
396
+ * into a reactive version that works with MobX computed values. Reactive functions
397
+ * are required for the evaluation pipeline to support lazy evaluation and proper
398
+ * dependency tracking.
399
+ *
400
+ * @param definition - Either an imperative or reactive function definition
401
+ * @returns A reactive function definition. If the input was already reactive,
402
+ * returns it unchanged. If imperative, wraps it using wrapImperativeAsReactive.
403
+ *
404
+ * @example
405
+ * ```typescript
406
+ * // Register a function, ensuring it's reactive
407
+ * const reactiveFn = ensureReactive(myImperativeFunction);
408
+ * defStore.setFunction('myFn', reactiveFn);
409
+ * ```
410
+ */
411
+ function ensureReactive(definition) {
412
+ if (definition.kind === 'reactive') {
413
+ return definition;
414
+ }
415
+ else {
416
+ return wrapImperativeAsReactive(definition);
417
+ }
418
+ }
419
+
420
+ /**
421
+ * Validates that a function definition's signature string matches its execute function.
422
+ *
423
+ * Performs basic parameter count validation to catch common mistakes.
424
+ * Runs at function registration time (once per function), not on every invocation.
425
+ *
426
+ * @param name - Function identifier for error messages
427
+ * @param definition - Function definition to validate
428
+ * @throws {Error} When signature is malformed or parameter count exceeds declaration
429
+ */
430
+ function validateFunctionSignature(name, definition) {
431
+ try {
432
+ const { params, variadic } = parseSignature(definition.signature);
433
+ if (definition.kind === 'imperative') {
434
+ const ctxOffset = definition.contextual ? 1 : 0; // +1 for ctx parameter if contextual
435
+ const expectedLength = params.length + ctxOffset;
436
+ const actualLength = definition.execute.length;
437
+ // Validate only when execute has MORE parameters than declared in signature.
438
+ // We allow fewer because function.length excludes default parameters.
439
+ // Example: execute(ctx, a, b = 5) has length 2, even with signature '(a, b) => result'
440
+ if (actualLength > 0 && actualLength > expectedLength && !variadic) {
441
+ throw new Error(`Function "${name}": execute function has more parameters (${actualLength - ctxOffset}) than signature declares (${params.length}). ` +
442
+ `Signature: ${definition.signature}`);
443
+ }
444
+ }
445
+ else {
446
+ // Reactive functions: contextual receives (ctx, args), pure receives just (args)
447
+ const actualLength = definition.execute.length;
448
+ const expectedLength = definition.contextual ? 2 : 1;
449
+ if (actualLength !== expectedLength) {
450
+ log.warn(`Function "${name}": reactive execute should take exactly ${expectedLength} parameter(s) ${definition.contextual ? '(ctx, args)' : '(args)'}. Got ${actualLength}. ` +
451
+ `This is unusual but may be intentional.`);
452
+ }
453
+ }
454
+ }
455
+ catch (error) {
456
+ if (error instanceof Error) {
457
+ throw new Error(`Function "${name}": Invalid signature - ${error.message}`);
458
+ }
459
+ throw error;
460
+ }
461
+ }
462
+ /**
463
+ * Validates all functions in a registry.
464
+ *
465
+ * Useful for bulk validation in test setup or during application initialization.
466
+ * Throws on the first invalid function encountered.
467
+ *
468
+ * @param functions - Map of function names to their definitions
469
+ * @throws {Error} When any function has an invalid signature
470
+ */
471
+ function validateFunctionRegistry(functions) {
472
+ for (const [name, definition] of Object.entries(functions)) {
473
+ validateFunctionSignature(name, definition);
474
+ }
475
+ }
476
+
477
+ /**
478
+ * Registry for formula functions.
479
+ * Singleton pattern for global access throughout the application.
480
+ */
481
+ class FunctionRegistry {
482
+ static instance;
483
+ // All functions are stored as reactive (imperative ones are wrapped automatically)
484
+ reactiveFunctions = {};
485
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
486
+ constructor() { }
487
+ static getInstance() {
488
+ if (!FunctionRegistry.instance) {
489
+ FunctionRegistry.instance = new FunctionRegistry();
490
+ }
491
+ return FunctionRegistry.instance;
492
+ }
493
+ static reset() {
494
+ FunctionRegistry.instance = new FunctionRegistry();
495
+ return FunctionRegistry.instance;
496
+ }
497
+ /**
498
+ * Register multiple formula functions at once.
499
+ * Automatically wraps imperative functions as reactive.
500
+ */
501
+ setFunctions(definitions) {
502
+ for (const [functionType, definition] of Object.entries(definitions)) {
503
+ this.setFunction(functionType, definition);
504
+ }
505
+ }
506
+ /**
507
+ * Register a formula function.
508
+ *
509
+ * Accepts both reactive and imperative definitions. Imperative definitions are
510
+ * automatically wrapped as reactive. The registry stores all functions as ReactiveFunctionDefinition.
511
+ *
512
+ * In development mode, validates that the signature matches the execute function.
513
+ *
514
+ * @param functionType - Unique identifier for the function
515
+ * @param definition - Function definition (imperative or reactive)
516
+ */
517
+ setFunction(functionType, definition) {
518
+ // Validate signature in development
519
+ validateFunctionSignature(functionType, definition);
520
+ this.reactiveFunctions[functionType] = ensureReactive(definition);
521
+ }
522
+ /**
523
+ * Get a registered formula function.
524
+ * @throws Error if function is not found
525
+ */
526
+ getFunction(functionType) {
527
+ const definition = this.reactiveFunctions[functionType];
528
+ if (!definition) {
529
+ throw new Error(`Function "${functionType}" not found.`);
530
+ }
531
+ return definition;
532
+ }
533
+ /**
534
+ * Get all registered function names.
535
+ */
536
+ getFunctionNames() {
537
+ return Object.keys(this.reactiveFunctions);
538
+ }
539
+ /**
540
+ * Check if a function is registered.
541
+ */
542
+ hasFunction(functionType) {
543
+ return !!this.reactiveFunctions[functionType];
544
+ }
545
+ }
546
+
547
+ /** Type guard for AsyncValue. */
548
+ function isAsyncValue(obj) {
549
+ return (typeof obj === 'object' &&
550
+ obj !== null &&
551
+ 'kind' in obj &&
552
+ (obj.kind === 'promise' || obj.kind === 'observable') &&
553
+ 'current' in obj &&
554
+ 'error' in obj &&
555
+ 'pending' in obj);
556
+ }
557
+ /** Type guard for PromiseValue. */
558
+ function isPromiseValue(obj) {
559
+ return isAsyncValue(obj) && obj.kind === 'promise';
560
+ }
561
+ /** Type guard for ObservableValue. */
562
+ function isObservableValue(obj) {
563
+ return isAsyncValue(obj) && obj.kind === 'observable';
564
+ }
565
+
566
+ /**
567
+ * Manages RxJS subscription lifecycle across function re-evaluations.
568
+ */
569
+ const createSubscriptionTracker = () => {
570
+ let currentSubscription = null;
571
+ const updateSubscription = (result) => {
572
+ const newSubscription = isObservableValue(result.returnValue) ? result.returnValue.subscription : null;
573
+ if (currentSubscription !== null && currentSubscription !== newSubscription) {
574
+ if (typeof currentSubscription.unsubscribe === 'function') {
575
+ currentSubscription.unsubscribe();
576
+ }
577
+ }
578
+ currentSubscription = newSubscription;
579
+ };
580
+ const cleanupDisposer = (() => {
581
+ if (currentSubscription && typeof currentSubscription.unsubscribe === 'function') {
582
+ currentSubscription.unsubscribe();
583
+ }
584
+ currentSubscription = null;
585
+ });
586
+ return [updateSubscription, cleanupDisposer];
587
+ };
588
+ /**
589
+ * Executes a function and returns its reactive state with cleanup handlers.
590
+ */
591
+ const executeFunction = (evalNode, context) => {
592
+ const fnRegistry = FunctionRegistry.getInstance();
593
+ const argResults = evalNode.args.map((arg) => evaluateNode(arg, context));
594
+ const computedArgs = argResults.map((result) => result.computed);
595
+ const argDisposers = argResults.flatMap((result) => result.disposers);
596
+ const fnDefinition = fnRegistry.getFunction(evalNode.name);
597
+ try {
598
+ const resultComputed = fnDefinition.contextual
599
+ ? fnDefinition.execute(context, computedArgs)
600
+ : fnDefinition.execute(computedArgs);
601
+ const [updateSubscription, cleanupDisposer] = createSubscriptionTracker();
602
+ const executionResultComputed = computed(() => {
603
+ const result = resultComputed.get();
604
+ updateSubscription(result);
605
+ return result;
606
+ }, {
607
+ name: debugName(context, evalNode.name),
608
+ });
609
+ return [executionResultComputed, cleanupDisposer, argDisposers];
610
+ }
611
+ catch (error) {
612
+ const src = context.sourceUrl ? `[${context.sourceUrl}] ` : '';
613
+ if (error instanceof Error) {
614
+ const message = [
615
+ `${src}Function "${evalNode.name}" failed`,
616
+ error.message,
617
+ `\nContext:\n Component: ${context.componentId}`,
618
+ ].join('\n');
619
+ throw new Error(message, { cause: error });
620
+ }
621
+ else {
622
+ throw new Error(`${src}Function "${evalNode.name}" failed: ${String(error)}`, { cause: error });
623
+ }
624
+ }
625
+ };
626
+
627
+ function isComputedValue(value) {
628
+ return isComputed(value);
629
+ }
630
+
631
+ /**
632
+ * Extracts evaluation state from return values, unwrapping AsyncValue wrappers.
633
+ */
634
+ function getReturnValueState(source) {
635
+ let rawValue;
636
+ let evaluating = false;
637
+ let failed = false;
638
+ if (isAsyncValue(source)) {
639
+ rawValue = toJS(source.current);
640
+ evaluating = source.pending;
641
+ failed = !!source.error;
642
+ }
643
+ else {
644
+ try {
645
+ rawValue = isComputedValue(source) ? source.get() : source;
646
+ }
647
+ catch {
648
+ rawValue = null;
649
+ failed = true;
650
+ }
651
+ }
652
+ return {
653
+ value: rawValue,
654
+ evaluating,
655
+ failed,
656
+ };
657
+ }
658
+
659
+ /**
660
+ * Evaluates a function call with reactive state tracking.
661
+ * Supports synchronous values, Promises, and RxJS Observables.
662
+ */
663
+ function evaluateFunction(evalNode, context) {
664
+ const [executionResult, functionDisposer, argDisposers] = executeFunction(evalNode, context);
665
+ return {
666
+ computed: computed(() => {
667
+ const { returnValue, argsEvaluated, argsEvaluating, argsFailed } = executionResult.get();
668
+ const returnValueState = getReturnValueState(returnValue);
669
+ return {
670
+ value: returnValueState.value,
671
+ settled: argsEvaluated && !returnValueState.evaluating,
672
+ evaluating: argsEvaluating || returnValueState.evaluating,
673
+ failed: argsFailed || returnValueState.failed,
674
+ };
675
+ }, {
676
+ equals: comparer.structural,
677
+ name: debugName(context, evalNode.name),
678
+ }),
679
+ disposers: [functionDisposer, ...argDisposers],
680
+ };
681
+ }
682
+
683
+ function evaluateObject(evalNode, context) {
684
+ const propertyResults = evalNode.properties.map((property) => {
685
+ const keyResult = evaluateNode(property.key, context);
686
+ const valueResult = evaluateNode(property.value, context);
687
+ return { keyResult, valueResult };
688
+ });
689
+ const entriesComputeds = propertyResults.map(({ keyResult, valueResult }) => [keyResult.computed, valueResult.computed]);
690
+ const disposers = propertyResults.flatMap(({ keyResult, valueResult }) => [
691
+ ...keyResult.disposers,
692
+ ...valueResult.disposers,
693
+ ]);
694
+ return {
695
+ computed: computed(() => {
696
+ const evaluating = entriesComputeds.some(([keyComputed, valueComputed]) => keyComputed.get().evaluating || valueComputed.get().evaluating);
697
+ const failed = entriesComputeds.some(([keyComputed, valueComputed]) => keyComputed.get().failed || valueComputed.get().failed);
698
+ const entries = entriesComputeds.map(([keyComputed, valueComputed]) => [
699
+ keyComputed.get().value,
700
+ valueComputed.get().value,
701
+ ]);
702
+ const value = Object.fromEntries(entries);
703
+ return { value, evaluating, settled: !evaluating, failed };
704
+ }, {
705
+ equals: comparer.structural,
706
+ name: debugName(context, 'object-literal'),
707
+ }),
708
+ disposers,
709
+ };
710
+ }
711
+
712
+ function evaluateValue(node) {
713
+ return {
714
+ computed: computed(() => ({
715
+ value: node.value,
716
+ evaluating: false,
717
+ settled: true,
718
+ failed: false,
719
+ })),
720
+ disposers: [],
721
+ };
722
+ }
723
+
724
+ const delegates = {
725
+ function: (evalNode, context) => evaluateFunction(evalNode, context),
726
+ value: (evalNode) => evaluateValue(evalNode),
727
+ array: (evalNode, context) => evaluateArray(evalNode, context),
728
+ object: (evalNode, context) => evaluateObject(evalNode, context),
729
+ };
730
+ function evaluateNode(evalNode, context) {
731
+ const delegate = delegates[evalNode.type];
732
+ if (!delegate) {
733
+ throw new Error(`Cannot found computer. Node: ${JSON.stringify(evalNode)}`);
734
+ }
735
+ return delegate(evalNode, context);
736
+ }
737
+
738
+ /**
739
+ * Helper for building FunctionExecutionState in reactive functions.
740
+ * Tracks argument evaluation states and provides early-return helpers.
741
+ */
742
+ class FunctionExecutionStateBuilder {
743
+ argsEvaluating = false;
744
+ argsFailed = false;
745
+ /** Track state from an argument. */
746
+ trackArg(argState) {
747
+ if (argState.evaluating)
748
+ this.argsEvaluating = true;
749
+ if (argState.failed)
750
+ this.argsFailed = true;
751
+ }
752
+ /** Check if argument is ready. Returns state to return early if not ready, null otherwise. */
753
+ checkReady(argState) {
754
+ this.trackArg(argState);
755
+ if (!argState.settled) {
756
+ return this.notReady();
757
+ }
758
+ return null;
759
+ }
760
+ /** Return state when arguments are not ready. */
761
+ notReady() {
762
+ return {
763
+ returnValue: null,
764
+ argsEvaluated: false,
765
+ argsEvaluating: this.argsEvaluating,
766
+ argsFailed: this.argsFailed,
767
+ };
768
+ }
769
+ /** Return successful state with a value. */
770
+ success(value) {
771
+ return {
772
+ returnValue: value,
773
+ argsEvaluated: true,
774
+ argsEvaluating: this.argsEvaluating,
775
+ argsFailed: this.argsFailed,
776
+ };
777
+ }
778
+ }
779
+
780
+ // Main evaluation function
781
+
782
+ /**
783
+ * Generated bundle index. Do not edit.
784
+ */
785
+
786
+ export { FunctionExecutionStateBuilder, FunctionRegistry, evaluateNode, wrapImperativeAsReactive };
787
+ //# sourceMappingURL=kaskad-eval-tree.mjs.map