@kaskad/core 0.0.6 → 0.0.8

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.
@@ -1,13 +1,13 @@
1
1
  export * from '@kaskad/types';
2
2
  import { DefinitionStore } from '@kaskad/definition';
3
3
  export * from '@kaskad/definition';
4
- import { isObject, loadTemplates, templateRegistry, unfoldNodeSchema, unfoldComponentDefinitions } from '@kaskad/schema';
4
+ import { unfoldComponentDefinitions, isObject, loadTemplates, templateRegistry, unfoldNodeSchema } from '@kaskad/schema';
5
5
  export { loadTemplates, templateRegistry, unfoldComponentDefinitions, unfoldNodeSchema } from '@kaskad/schema';
6
- import { evaluateNode, FunctionExecutionStateBuilder, FunctionRegistry, wrapImperativeAsReactive } from '@kaskad/eval-tree';
6
+ import { FunctionExecutionStateBuilder, FunctionRegistry, evaluateNode, wrapImperativeAsReactive } from '@kaskad/eval-tree';
7
7
  export { FunctionRegistry } from '@kaskad/eval-tree';
8
8
  import { ComponentTreeApi, ComponentStore, getComponent, componentTreeConfig } from '@kaskad/component-tree';
9
9
  export { ComponentStore, ComponentTreeApi, createRootNode } from '@kaskad/component-tree';
10
- import { reaction, comparer, runInAction, when, computed, isComputed } from 'mobx';
10
+ import { computed, reaction, comparer, runInAction, when, isComputed } from 'mobx';
11
11
  import { log, Delimiters } from '@kaskad/config';
12
12
  import { parseFormula } from '@kaskad/formula-parser';
13
13
  import { Observable, firstValueFrom } from 'rxjs';
@@ -46,1663 +46,2046 @@ class TemplateLoadingTracker {
46
46
  // Export a singleton instance for tracking template loading operations
47
47
  const templateLoadingTracker = new TemplateLoadingTracker();
48
48
 
49
- const lowerValue = (astNode) => ({
50
- type: 'value',
51
- value: astNode.value,
52
- });
53
- const lowerReference = (astNode) => ({
54
- type: 'function',
55
- name: 'get',
56
- args: [
57
- {
58
- type: 'value',
59
- value: astNode.name,
60
- },
61
- ],
62
- });
63
- const lowerIdentifier = (astNode) => ({
64
- type: 'value',
65
- value: astNode.name,
66
- });
67
- const lowerArray = (astNode) => ({
68
- type: 'array',
69
- items: astNode.items.map(lowerFormulaAstNode),
70
- });
71
- const lowerObject = (astNode) => ({
72
- type: 'object',
73
- properties: astNode.properties.map(({ key, value }) => ({
74
- key: lowerFormulaAstNode(key),
75
- value: lowerFormulaAstNode(value),
76
- })),
77
- });
78
- const lowerFunction = (astNode) => ({
79
- type: 'function',
80
- name: astNode.name,
81
- args: astNode.args.map(lowerFormulaAstNode),
49
+ function execute$U(_ctx, ms) {
50
+ return new Promise((resolve) => setTimeout(() => resolve(), ms));
51
+ }
52
+
53
+ var delay$1 = /*#__PURE__*/Object.freeze({
54
+ __proto__: null,
55
+ execute: execute$U
82
56
  });
83
- const lowerNot = (astNode) => {
84
- if (!astNode.operand) {
85
- throw new Error('Invert node must have an item');
86
- }
87
- return {
88
- type: 'function',
89
- name: 'not',
90
- args: [lowerFormulaAstNode(astNode.operand)],
91
- };
92
- };
93
- const lowerGroup = (astNode) => lowerFormulaAstNode(astNode.item);
94
- const lowerTernary = (astNode) => {
95
- if (!astNode.thenBranch || !astNode.elseBranch) {
96
- throw new Error('Ternary expression must have both then and else branches');
97
- }
98
- return {
99
- type: 'function',
100
- name: 'if',
101
- args: [
102
- lowerFormulaAstNode(astNode.condition),
103
- lowerFormulaAstNode(astNode.thenBranch),
104
- lowerFormulaAstNode(astNode.elseBranch),
105
- ],
106
- };
57
+
58
+ const commands$1 = {
59
+ delay: delay$1,
107
60
  };
108
- const lowerBinaryOperation = (astNode) => {
109
- if (!astNode.right) {
110
- throw new Error(`Binary operation "${astNode.type}" must have a right operand`);
111
- }
112
- return {
113
- type: 'function',
114
- name: astNode.type,
115
- args: [lowerFormulaAstNode(astNode.left), lowerFormulaAstNode(astNode.right)],
116
- };
61
+
62
+ /**
63
+ * Variadic AND function with short-circuit evaluation.
64
+ * Stops at the first falsy value.
65
+ */
66
+ const and = {
67
+ kind: 'reactive',
68
+ execute: (args) => {
69
+ if (args.length === 0) {
70
+ throw new Error('and requires at least 1 argument');
71
+ }
72
+ return computed(() => {
73
+ const state = new FunctionExecutionStateBuilder();
74
+ for (const argComputed of args) {
75
+ const argState = argComputed.get();
76
+ const earlyReturn = state.checkReady(argState);
77
+ if (earlyReturn)
78
+ return earlyReturn;
79
+ if (!argState.value) {
80
+ return state.success(false);
81
+ }
82
+ }
83
+ return state.success(true);
84
+ });
85
+ },
86
+ signature: '(...unknown) => boolean',
117
87
  };
118
- const astNodeLowerers = {
119
- value: (astNode) => lowerValue(astNode),
120
- reference: (astNode) => lowerReference(astNode),
121
- identifier: (astNode) => lowerIdentifier(astNode),
122
- array: (astNode) => lowerArray(astNode),
123
- object: (astNode) => lowerObject(astNode),
124
- function: (astNode) => lowerFunction(astNode),
125
- not: (astNode) => lowerNot(astNode),
126
- multiply: (astNode) => lowerBinaryOperation(astNode),
127
- divide: (astNode) => lowerBinaryOperation(astNode),
128
- plus: (astNode) => lowerBinaryOperation(astNode),
129
- minus: (astNode) => lowerBinaryOperation(astNode),
130
- eq: (astNode) => lowerBinaryOperation(astNode),
131
- neq: (astNode) => lowerBinaryOperation(astNode),
132
- gt: (astNode) => lowerBinaryOperation(astNode),
133
- lt: (astNode) => lowerBinaryOperation(astNode),
134
- gte: (astNode) => lowerBinaryOperation(astNode),
135
- lte: (astNode) => lowerBinaryOperation(astNode),
136
- group: (astNode) => lowerGroup(astNode),
137
- ternary: (astNode) => lowerTernary(astNode),
88
+
89
+ function execute$T(source, path) {
90
+ const takeDeep = (source, path) => path.reduce((current, step) => {
91
+ return current?.[step];
92
+ }, source);
93
+ return source.map((element) => takeDeep(element, path));
94
+ }
95
+ const arrayPluck = {
96
+ kind: 'imperative',
97
+ execute: execute$T,
98
+ signature: '(unknown{}[], string[]) => unknown',
138
99
  };
139
- function lowerFormulaAstNode(astNode) {
140
- const lowerer = astNodeLowerers[astNode.type];
141
- if (lowerer) {
142
- return lowerer(astNode);
143
- }
144
- throw new Error(`Unsupported Formula AST node type: ${astNode.type}`);
100
+
101
+ function execute$S(value, index) {
102
+ return value[index];
145
103
  }
104
+ const at = {
105
+ kind: 'imperative',
106
+ execute: execute$S,
107
+ signature: '(unknown[], number) => unknown',
108
+ };
146
109
 
147
- function lowerFormula(formula) {
148
- let formulaAst;
149
- try {
150
- formulaAst = parseFormula(formula);
151
- }
152
- catch (e) {
153
- log.trace(e);
154
- return { type: 'value', value: null };
155
- }
156
- return lowerFormulaAstNode(formulaAst);
110
+ function execute$R(value) {
111
+ return Boolean(value);
157
112
  }
113
+ const bool = {
114
+ kind: 'imperative',
115
+ execute: execute$R,
116
+ signature: '(unknown) => boolean',
117
+ };
158
118
 
159
- function bindArray(node, component, cfg) {
160
- const { componentSelector, valueProp } = cfg;
161
- const evalNode = lowerFormula(`propArray('${componentSelector}', '${valueProp}')`);
162
- if (!node.position.componentId) {
163
- throw new Error('Node does not have a component context');
164
- }
165
- const { computed, disposers } = evaluateNode(evalNode, {
166
- componentId: node.position.componentId,
167
- });
168
- node.disposers.push(...disposers);
169
- const disposer = reaction(() => [
170
- node.extractedValue,
171
- ComponentTreeApi.findComponentsIds(component.id, componentSelector),
172
- computed.get(),
173
- ], ([value, componentsIds, evalState], prev) => {
174
- let newValue;
175
- if (!prev) {
176
- // first run
177
- if (value && value.length > 0) {
178
- newValue = [...value];
179
- }
180
- else {
181
- newValue = evalState.value;
182
- }
183
- }
184
- else {
185
- if (!comparer.structural(value, prev[0])) {
186
- // console.log('100', JSON.stringify(value), JSON.stringify(prev[0]));
187
- newValue = value ? [...value] : [];
188
- }
189
- else {
190
- // console.log('200');
191
- newValue = [...evalState.value];
192
- if (value && evalState.value.length > prev[2].value.length) {
193
- newValue = [...value];
194
- }
195
- }
196
- }
197
- // console.log(JSON.stringify(newValue));
198
- const components = componentsIds.map((id) => ComponentStore.getInstance().getComponentOrThrow(id));
199
- runInAction(() => {
200
- const store = ComponentStore.getInstance();
201
- store.setNodeRawSchema(node, newValue);
202
- for (let i = 0; i < components.length; i++) {
203
- const c = components[i];
204
- const value = newValue[i];
205
- const valueNode = c.getNode(valueProp);
206
- store.setNodeRawSchema(valueNode, value);
207
- }
208
- });
209
- }, { fireImmediately: true, equals: comparer.structural });
210
- node.disposers.push(disposer);
119
+ function execute$Q(value) {
120
+ return Math.ceil(value);
211
121
  }
122
+ const ceil = {
123
+ kind: 'imperative',
124
+ execute: execute$Q,
125
+ signature: '(number) => number',
126
+ };
212
127
 
213
- function bindMirror(component, node, cfg) {
214
- const store = ComponentStore.getInstance();
215
- let targetNode;
216
- try {
217
- // For Facade components, start lookup from parent. This handles inline bindings
218
- // on template contract variables, where the Facade has a variable with the same
219
- // name - we need to find the parent's variable, not self.
220
- const lookupFromId = component.componentType === 'sys.Facade' ? component.position.componentId : component.id;
221
- if (!lookupFromId) {
222
- log.error(`Cannot activate mirror binding: no parent component for selector "${cfg.selector}"`);
223
- return;
224
- }
225
- targetNode = store.getNode(lookupFromId, cfg.selector);
226
- }
227
- catch (error) {
228
- log.error(`Cannot activate mirror binding: ${error}`);
229
- return;
230
- }
231
- const disposer = reaction(() => [targetNode.extractedValue, node.extractedValue], (curr, prev) => {
232
- const [targetValue, sourceValue = null] = curr;
233
- const [prevTargetValue, prevSourceValue] = prev || [null, null];
234
- if (comparer.structural(targetValue, sourceValue)) {
235
- return;
236
- }
237
- if (!prev && targetValue !== null) {
238
- store.setNodeRawSchema(node, targetValue);
239
- return;
240
- }
241
- if (targetValue !== prevTargetValue) {
242
- store.setNodeRawSchema(node, targetValue);
243
- return;
244
- }
245
- if (sourceValue !== prevSourceValue) {
246
- store.setNodeRawSchema(targetNode, sourceValue);
247
- }
248
- }, { fireImmediately: true });
249
- node.disposers.push(disposer);
128
+ function execute$P(strings, separator = '') {
129
+ return strings.join(separator);
250
130
  }
131
+ const concat = {
132
+ kind: 'imperative',
133
+ execute: execute$P,
134
+ signature: '(unknown[], string) => string',
135
+ };
251
136
 
252
- function bindObject(node, component, cfg) {
253
- const { componentSelector, keyProp, valueProp } = cfg;
254
- const evalNode = lowerFormula(`propObject('${componentSelector}', '${keyProp}', '${valueProp}')`);
255
- if (!node.position.componentId) {
256
- throw new Error('Node does not have a component context');
257
- }
258
- const { computed, disposers } = evaluateNode(evalNode, {
259
- componentId: node.position.componentId,
260
- });
261
- node.disposers.push(...disposers);
262
- let preservedNodeValue = {};
263
- const disposer = reaction(() => [
264
- node.extractedValue,
265
- ComponentTreeApi.findComponentsIds(component.id, componentSelector),
266
- computed.get(),
267
- ], ([nodeValue, componentsIds, evalState], prev) => {
268
- const currentValue = nodeValue || {};
269
- if (comparer.structural(evalState.value, currentValue)) {
270
- return;
271
- }
272
- const firstRun = !prev;
273
- const nodeChanged = !firstRun && !comparer.structural(nodeValue, prev[0]);
274
- if (firstRun || nodeChanged) {
275
- preservedNodeValue = currentValue;
276
- }
277
- else {
278
- preservedNodeValue = shallowMerge(preservedNodeValue, evalState.value);
279
- }
280
- let componentValues;
281
- if (firstRun) {
282
- const hasInitialValue = Object.keys(currentValue).length > 0;
283
- componentValues = hasInitialValue
284
- ? syncNodeValueAndComponents$1(currentValue, evalState)
285
- : { ...evalState.value };
286
- }
287
- else if (nodeChanged) {
288
- componentValues = syncNodeValueAndComponents$1(currentValue, evalState);
289
- }
290
- else {
291
- componentValues = handleComponentsChanged(evalState, preservedNodeValue);
292
- }
293
- runInAction(() => {
294
- const store = ComponentStore.getInstance();
295
- store.setNodeRawSchema(node, componentValues);
296
- updateComponentsValues$1(componentsIds, componentValues, keyProp, valueProp);
297
- });
298
- }, { fireImmediately: true, equals: comparer.structural });
299
- node.disposers.push(disposer);
137
+ const execute$O = (value) => {
138
+ return new Promise((resolve) => setTimeout(() => resolve(value), 500));
139
+ };
140
+ const delay = {
141
+ kind: 'imperative',
142
+ execute: execute$O,
143
+ signature: '(unknown) => unknown',
144
+ };
145
+
146
+ function execute$N(num1, num2) {
147
+ return num1 / num2;
300
148
  }
301
- function handleComponentsChanged(evalState, preservedNodeValue) {
302
- const componentValues = {};
303
- for (const [inputKey, currentValue] of Object.entries(evalState.value)) {
304
- const preservedValue = preservedNodeValue[inputKey];
305
- if (isObject(currentValue) && isObject(preservedValue)) {
306
- componentValues[inputKey] = mergePreservedWithChildGroup(currentValue, preservedValue);
307
- continue;
308
- }
309
- componentValues[inputKey] = currentValue ?? preservedValue;
310
- }
311
- return componentValues;
149
+ const divide = {
150
+ kind: 'imperative',
151
+ execute: execute$N,
152
+ signature: '(number, number) => number',
153
+ };
154
+
155
+ function execute$M(items) {
156
+ return items.length === 0;
312
157
  }
313
- function mergePreservedWithChildGroup(currentValue, preservedValue) {
314
- const merged = { ...currentValue };
315
- for (const [key, value] of Object.entries(currentValue)) {
316
- if (value !== null) {
317
- continue;
318
- }
319
- if (preservedValue[key] === undefined || preservedValue[key] === null) {
320
- continue;
321
- }
322
- merged[key] = preservedValue[key];
323
- }
324
- return merged;
158
+ const empty = {
159
+ kind: 'imperative',
160
+ execute: execute$M,
161
+ signature: '(unknown[]) => boolean',
162
+ };
163
+
164
+ function execute$L(object) {
165
+ return Object.keys(object).length === 0;
325
166
  }
326
- function syncNodeValueAndComponents$1(nodeValue, evalState) {
327
- const componentValues = {};
328
- for (const fieldKey of Object.keys(evalState.value)) {
329
- if (!(fieldKey in nodeValue)) {
330
- componentValues[fieldKey] = null;
331
- continue;
332
- }
333
- componentValues[fieldKey] = nodeValue[fieldKey];
334
- }
335
- return componentValues;
167
+ const emptyObject = {
168
+ kind: 'imperative',
169
+ execute: execute$L,
170
+ signature: '(unknown{}) => boolean',
171
+ };
172
+
173
+ const execute$K = (a, b) => a === b;
174
+ const eq = {
175
+ kind: 'imperative',
176
+ execute: execute$K,
177
+ signature: '(unknown, unknown) => boolean',
178
+ };
179
+
180
+ function execute$J(items) {
181
+ return items.every((item) => !!item);
336
182
  }
337
- function updateComponentsValues$1(componentsIds, componentValues, keyProp, valueProp) {
338
- const store = ComponentStore.getInstance();
339
- const components = componentsIds.map((id) => store.getComponentOrThrow(id));
340
- for (const cmp of components) {
341
- const key = cmp.getNodeValue(keyProp);
342
- if (!key)
343
- continue;
344
- const value = componentValues[key];
345
- const valueNode = cmp.getNode(valueProp);
346
- store.setNodeRawSchema(valueNode, value);
183
+ const every = {
184
+ kind: 'imperative',
185
+ execute: execute$J,
186
+ signature: '(unknown[]) => boolean',
187
+ };
188
+
189
+ function execute$I(array, allowedValues) {
190
+ if (!allowedValues) {
191
+ return [];
192
+ }
193
+ if (allowedValues instanceof Set) {
194
+ return array.filter((item) => allowedValues.has(item));
347
195
  }
196
+ return array.filter((item) => allowedValues.includes(item));
348
197
  }
349
- function shallowMerge(target, source) {
350
- const merged = { ...target };
351
- for (const [key, value] of Object.entries(source)) {
352
- if (value === null || value === undefined) {
353
- continue;
354
- }
355
- if (isObject(value) && isObject(merged[key])) {
356
- merged[key] = { ...merged[key], ...value };
357
- continue;
198
+ const filterIn = {
199
+ kind: 'imperative',
200
+ execute: execute$I,
201
+ signature: '(unknown[], unknown[]) => unknown[]',
202
+ };
203
+
204
+ function execute$H(collection, key, values) {
205
+ return values
206
+ .map((value) => collection.find((element) => element[key] === value))
207
+ .filter((item) => item !== undefined);
208
+ }
209
+ const findAllBy = {
210
+ kind: 'imperative',
211
+ execute: execute$H,
212
+ signature: '(unknown{}[], string, unknown[]) => unknown{}[]',
213
+ };
214
+
215
+ function execute$G(collection, key, value) {
216
+ const found = collection.find((element) => element[key] === value);
217
+ return found || null;
218
+ }
219
+ const findBy = {
220
+ kind: 'imperative',
221
+ execute: execute$G,
222
+ signature: '(unknown{}[], string, unknown) => unknown',
223
+ };
224
+
225
+ function execute$F(array) {
226
+ return array.flat();
227
+ }
228
+ const flatten = {
229
+ kind: 'imperative',
230
+ execute: execute$F,
231
+ signature: '(unknown[][]) => unknown[]',
232
+ };
233
+
234
+ function execute$E(value) {
235
+ return Math.floor(value);
236
+ }
237
+ const floor = {
238
+ kind: 'imperative',
239
+ execute: execute$E,
240
+ signature: '(number) => number',
241
+ };
242
+
243
+ const execute$D = (a, b) => a > b;
244
+ const gt = {
245
+ kind: 'imperative',
246
+ execute: execute$D,
247
+ signature: '(number, number) => boolean',
248
+ };
249
+
250
+ const execute$C = (a, b) => a >= b;
251
+ const gte = {
252
+ kind: 'imperative',
253
+ execute: execute$C,
254
+ signature: '(number, number) => boolean',
255
+ };
256
+
257
+ /**
258
+ * Conditional branching with lazy evaluation.
259
+ * Only evaluates the selected branch.
260
+ */
261
+ const ifFn = {
262
+ kind: 'reactive',
263
+ execute: (args) => {
264
+ if (args.length !== 3) {
265
+ throw new Error('if requires exactly 3 arguments: condition, trueValue, falseValue');
358
266
  }
359
- merged[key] = value;
267
+ const [conditionComputed, trueComputed, falseComputed] = args;
268
+ return computed(() => {
269
+ const state = new FunctionExecutionStateBuilder();
270
+ const conditionState = conditionComputed.get();
271
+ const earlyReturn = state.checkReady(conditionState);
272
+ if (earlyReturn)
273
+ return earlyReturn;
274
+ const branchState = conditionState.value ? trueComputed.get() : falseComputed.get();
275
+ const branchEarlyReturn = state.checkReady(branchState);
276
+ if (branchEarlyReturn)
277
+ return branchEarlyReturn;
278
+ return state.success(branchState.value);
279
+ });
280
+ },
281
+ signature: '(unknown, unknown, unknown) => unknown',
282
+ };
283
+
284
+ function execute$B(collection, value) {
285
+ if (collection instanceof Set) {
286
+ return collection.has(value);
360
287
  }
361
- return merged;
288
+ return collection.includes(value);
362
289
  }
290
+ const inFn = {
291
+ kind: 'imperative',
292
+ execute: execute$B,
293
+ signature: '(unknown[], unknown) => boolean',
294
+ };
363
295
 
364
- function bindSelection(node, component, cfg) {
365
- const { componentSelector, keyProp, valueProp } = cfg;
366
- const evalNode = lowerFormula(`propObject('${componentSelector}', '${keyProp}', '${valueProp}')`);
367
- if (!node.position.componentId) {
368
- throw new Error('Node does not have a component context');
296
+ function replacer(_key, value) {
297
+ if (value instanceof Set) {
298
+ return Array.from(value);
369
299
  }
370
- const { computed, disposers } = evaluateNode(evalNode, {
371
- componentId: node.position.componentId,
372
- });
373
- node.disposers.push(...disposers);
374
- let preservedNodeValue = null;
375
- const disposer = reaction(() => [
376
- node.extractedValue,
377
- ComponentTreeApi.findComponentsIds(component.id, componentSelector),
378
- computed.get(),
379
- ], ([nodeValue, componentsIds, evalState], prev) => {
380
- const currentValue = toObject(nodeValue);
381
- const firstRun = !prev;
382
- const nodeChanged = !firstRun && !comparer.structural(nodeValue, prev[0]);
383
- if (firstRun || nodeChanged) {
384
- preservedNodeValue = currentValue;
385
- }
386
- if (comparer.structural(evalState.value, currentValue)) {
387
- return;
388
- }
389
- let componentValues;
390
- if (firstRun) {
391
- const initialNodeValue = Object.keys(currentValue).length > 0;
392
- componentValues = initialNodeValue
393
- ? syncNodeValueAndComponents(currentValue, evalState, preservedNodeValue)
394
- : { ...evalState.value };
300
+ return value;
301
+ }
302
+ function execute$A(arg) {
303
+ return JSON.stringify(arg, replacer);
304
+ }
305
+ const json = {
306
+ kind: 'imperative',
307
+ execute: execute$A,
308
+ signature: '(unknown) => string',
309
+ };
310
+
311
+ function execute$z(object) {
312
+ return Object.keys(object);
313
+ }
314
+ const keys = {
315
+ kind: 'imperative',
316
+ execute: execute$z,
317
+ signature: '(unknown{}) => string[]',
318
+ };
319
+
320
+ function execute$y(value) {
321
+ return value.length;
322
+ }
323
+ const length = {
324
+ kind: 'imperative',
325
+ execute: execute$y,
326
+ signature: '(unknown[]) => number',
327
+ };
328
+
329
+ const execute$x = (a, b) => a < b;
330
+ const lt = {
331
+ kind: 'imperative',
332
+ execute: execute$x,
333
+ signature: '(number, number) => boolean',
334
+ };
335
+
336
+ const execute$w = (a, b) => a <= b;
337
+ const lte = {
338
+ kind: 'imperative',
339
+ execute: execute$w,
340
+ signature: '(number, number) => boolean',
341
+ };
342
+
343
+ function execute$v(num, delta) {
344
+ return num - delta;
345
+ }
346
+ const minus = {
347
+ kind: 'imperative',
348
+ execute: execute$v,
349
+ signature: '(number, number) => number',
350
+ };
351
+
352
+ function execute$u(a, b) {
353
+ return a % b;
354
+ }
355
+ const modulo = {
356
+ kind: 'imperative',
357
+ execute: execute$u,
358
+ signature: '(number, number) => number',
359
+ };
360
+
361
+ function execute$t(num1, num2) {
362
+ return num1 * num2;
363
+ }
364
+ const multiply = {
365
+ kind: 'imperative',
366
+ execute: execute$t,
367
+ signature: '(number, number) => number',
368
+ };
369
+
370
+ function execute$s(num) {
371
+ return -num;
372
+ }
373
+ const negate = {
374
+ kind: 'imperative',
375
+ execute: execute$s,
376
+ signature: '(number) => number',
377
+ };
378
+
379
+ const execute$r = (a, b) => a !== b;
380
+ const neq = {
381
+ kind: 'imperative',
382
+ execute: execute$r,
383
+ signature: '(unknown, unknown) => boolean',
384
+ };
385
+
386
+ function execute$q(value) {
387
+ return !value;
388
+ }
389
+ const not = {
390
+ kind: 'imperative',
391
+ execute: execute$q,
392
+ signature: '(unknown) => boolean',
393
+ };
394
+
395
+ /**
396
+ * Null coalescing with lazy evaluation.
397
+ * Returns left if non-null, otherwise evaluates and returns right.
398
+ */
399
+ const nullCoalesce = {
400
+ kind: 'reactive',
401
+ execute: (args) => {
402
+ if (args.length !== 2) {
403
+ throw new Error('nullCoalesce requires exactly 2 arguments');
395
404
  }
396
- else if (nodeChanged) {
397
- componentValues = syncNodeValueAndComponents(currentValue, evalState, preservedNodeValue);
405
+ const [leftComputed, rightComputed] = args;
406
+ return computed(() => {
407
+ const state = new FunctionExecutionStateBuilder();
408
+ const leftState = leftComputed.get();
409
+ const earlyReturn = state.checkReady(leftState);
410
+ if (earlyReturn)
411
+ return earlyReturn;
412
+ if (leftState.value != null) {
413
+ return state.success(leftState.value);
414
+ }
415
+ const rightState = rightComputed.get();
416
+ const rightEarlyReturn = state.checkReady(rightState);
417
+ if (rightEarlyReturn)
418
+ return rightEarlyReturn;
419
+ return state.success(rightState.value);
420
+ });
421
+ },
422
+ signature: '(unknown, unknown) => unknown',
423
+ };
424
+
425
+ function execute$p(value) {
426
+ const number = Number(value);
427
+ if (Number.isNaN(number)) {
428
+ throw new Error(`Cannot convert value ${value} to number`);
429
+ }
430
+ return number;
431
+ }
432
+ const number = {
433
+ kind: 'imperative',
434
+ execute: execute$p,
435
+ signature: '(unknown) => number',
436
+ };
437
+
438
+ function execute$o(parts) {
439
+ const result = {};
440
+ for (let i = 0; i < parts.length; i += 2) {
441
+ const key = parts[i];
442
+ if (typeof key !== 'string') {
443
+ throw new Error(`Invalid key type: ${typeof key}. Expected string`);
398
444
  }
399
- else {
400
- componentValues = syncComponentsAndPreserved(evalState, preservedNodeValue, currentValue);
445
+ result[key] = parts[i + 1];
446
+ }
447
+ return result;
448
+ }
449
+ const objectFn = {
450
+ kind: 'imperative',
451
+ execute: execute$o,
452
+ signature: '(unknown[]) => unknown{}',
453
+ };
454
+
455
+ /**
456
+ * Variadic OR function with short-circuit evaluation.
457
+ * Stops at the first truthy value.
458
+ */
459
+ const or = {
460
+ kind: 'reactive',
461
+ execute: (args) => {
462
+ if (args.length === 0) {
463
+ throw new Error('or requires at least 1 argument');
401
464
  }
402
- const selectionValue = toSelectionSet(componentValues);
403
- runInAction(() => {
404
- const store = ComponentStore.getInstance();
405
- store.setNodeRawSchema(node, selectionValue);
406
- updateComponentsValues(componentsIds, componentValues, keyProp, valueProp);
465
+ return computed(() => {
466
+ const state = new FunctionExecutionStateBuilder();
467
+ for (const argComputed of args) {
468
+ const argState = argComputed.get();
469
+ const earlyReturn = state.checkReady(argState);
470
+ if (earlyReturn)
471
+ return earlyReturn;
472
+ if (argState.value) {
473
+ return state.success(true);
474
+ }
475
+ }
476
+ return state.success(false);
407
477
  });
408
- }, { fireImmediately: true, equals: comparer.structural });
409
- node.disposers.push(disposer);
478
+ },
479
+ signature: '(...unknown) => boolean',
480
+ };
481
+
482
+ function execute$n(source, path) {
483
+ return path.reduce((current, step) => {
484
+ return current?.[step];
485
+ }, source);
410
486
  }
411
- function syncNodeValueAndComponents(nodeValue, evalState, preservedNodeValue) {
412
- const componentValues = { ...nodeValue };
413
- // Keep keys in preservedNodeValue to handle async component loading
414
- for (const key of Object.keys(componentValues)) {
415
- if (!(key in evalState.value) && (!preservedNodeValue || !(key in preservedNodeValue))) {
416
- delete componentValues[key];
487
+ const pluck = {
488
+ kind: 'imperative',
489
+ execute: execute$n,
490
+ signature: '(unknown{}, string[]) => unknown',
491
+ };
492
+
493
+ function execute$m(a, b) {
494
+ return a + b;
495
+ }
496
+ const plus = {
497
+ kind: 'imperative',
498
+ execute: execute$m,
499
+ signature: '(unknown, unknown) => unknown',
500
+ };
501
+
502
+ function execute$l(value, precision = 0) {
503
+ const multiplier = Math.pow(10, precision);
504
+ return Math.round(value * multiplier) / multiplier;
505
+ }
506
+ const round = {
507
+ kind: 'imperative',
508
+ execute: execute$l,
509
+ signature: '(number, number) => number',
510
+ };
511
+
512
+ function execute$k(value, start, end) {
513
+ return value.slice(start, end);
514
+ }
515
+ const slice = {
516
+ kind: 'imperative',
517
+ execute: execute$k,
518
+ signature: '(unknown[], number, number) => unknown[]',
519
+ };
520
+
521
+ function execute$j(items) {
522
+ return items.some((item) => !!item);
523
+ }
524
+ const some = {
525
+ kind: 'imperative',
526
+ execute: execute$j,
527
+ signature: '(unknown[]) => boolean',
528
+ };
529
+
530
+ function execute$i(value) {
531
+ return String(value);
532
+ }
533
+ const string = {
534
+ kind: 'imperative',
535
+ execute: execute$i,
536
+ signature: '(unknown) => string',
537
+ };
538
+
539
+ /**
540
+ * Switch statement with lazy case evaluation.
541
+ * Supports array mode (legacy) and variadic mode.
542
+ */
543
+ const switchFn = {
544
+ kind: 'reactive',
545
+ execute: (args) => {
546
+ if (args.length < 2) {
547
+ throw new Error('switch requires at least 2 arguments: value and default');
417
548
  }
549
+ const valueComputed = args[0];
550
+ return computed(() => {
551
+ const state = new FunctionExecutionStateBuilder();
552
+ const valueState = valueComputed.get();
553
+ const earlyReturn = state.checkReady(valueState);
554
+ if (earlyReturn)
555
+ return earlyReturn;
556
+ const value = valueState.value;
557
+ if (args.length === 3) {
558
+ const casesState = args[1].get();
559
+ const defaultState = args[2].get();
560
+ const casesEarlyReturn = state.checkReady(casesState);
561
+ if (casesEarlyReturn)
562
+ return casesEarlyReturn;
563
+ const defaultEarlyReturn = state.checkReady(defaultState);
564
+ if (defaultEarlyReturn)
565
+ return defaultEarlyReturn;
566
+ const cases = casesState.value;
567
+ if (Array.isArray(cases)) {
568
+ if (cases.length % 2 !== 0) {
569
+ throw new Error('Cases array should have even number of elements');
570
+ }
571
+ const matchIndex = cases.findIndex((caseValue, index) => index % 2 === 0 && caseValue === value);
572
+ const returnValue = matchIndex >= 0 ? cases[matchIndex + 1] : defaultState.value;
573
+ return state.success(returnValue);
574
+ }
575
+ }
576
+ const defaultComputed = args[args.length - 1];
577
+ const caseArgs = args.slice(1, -1);
578
+ if (caseArgs.length % 2 !== 0) {
579
+ throw new Error('switch requires an even number of case arguments (key-value pairs)');
580
+ }
581
+ for (let i = 0; i < caseArgs.length; i += 2) {
582
+ const caseKeyComputed = caseArgs[i];
583
+ const caseValueComputed = caseArgs[i + 1];
584
+ const caseKeyState = caseKeyComputed.get();
585
+ const keyEarlyReturn = state.checkReady(caseKeyState);
586
+ if (keyEarlyReturn)
587
+ return keyEarlyReturn;
588
+ const caseKey = caseKeyState.value;
589
+ if (caseKey === value) {
590
+ const caseValueState = caseValueComputed.get();
591
+ const valueEarlyReturn = state.checkReady(caseValueState);
592
+ if (valueEarlyReturn)
593
+ return valueEarlyReturn;
594
+ return state.success(caseValueState.value);
595
+ }
596
+ }
597
+ const defaultState = defaultComputed.get();
598
+ const defaultEarlyReturn = state.checkReady(defaultState);
599
+ if (defaultEarlyReturn)
600
+ return defaultEarlyReturn;
601
+ return state.success(defaultState.value);
602
+ });
603
+ },
604
+ signature: '(...unknown) => unknown',
605
+ };
606
+
607
+ function execute$h(value) {
608
+ return value.toLowerCase();
609
+ }
610
+ const toLowerCase = {
611
+ kind: 'imperative',
612
+ execute: execute$h,
613
+ signature: '(string) => string',
614
+ };
615
+
616
+ function execute$g(value) {
617
+ return value.toUpperCase();
618
+ }
619
+ const toUpperCase = {
620
+ kind: 'imperative',
621
+ execute: execute$g,
622
+ signature: '(string) => string',
623
+ };
624
+
625
+ /**
626
+ * Shared utility formula functions.
627
+ *
628
+ * Control flow functions use reactive implementations with lazy evaluation:
629
+ * - `if`: Conditional evaluation (3 args: condition, trueValue, falseValue)
630
+ * - `and`, `or`: Variadic logical operators with short-circuit evaluation
631
+ * - `switch`: Variadic pattern matching with lazy case evaluation
632
+ * - `some`, `every`: Array element checkers (imperative, not lazy)
633
+ */
634
+ const formulaFunctions$2 = {
635
+ // Collection functions
636
+ findBy,
637
+ findAllBy,
638
+ at,
639
+ not,
640
+ // Array element checkers (imperative)
641
+ some,
642
+ every,
643
+ // Control flow (reactive with lazy evaluation)
644
+ if: ifFn,
645
+ and,
646
+ or,
647
+ nullCoalesce,
648
+ switch: switchFn,
649
+ // Utilities
650
+ in: inFn,
651
+ object: objectFn,
652
+ bool,
653
+ length,
654
+ empty,
655
+ concat,
656
+ pluck,
657
+ flatten,
658
+ json,
659
+ minus,
660
+ modulo,
661
+ negate,
662
+ plus,
663
+ divide,
664
+ multiply,
665
+ emptyObject,
666
+ arrayPluck,
667
+ string,
668
+ number,
669
+ delay,
670
+ keys,
671
+ toUpperCase,
672
+ toLowerCase,
673
+ ceil,
674
+ floor,
675
+ round,
676
+ slice,
677
+ filterIn,
678
+ eq,
679
+ neq,
680
+ gt,
681
+ lt,
682
+ gte,
683
+ lte,
684
+ };
685
+
686
+ const common = {
687
+ commands: commands$1,
688
+ functions: formulaFunctions$2,
689
+ };
690
+
691
+ function registerPlugin(plugin) {
692
+ const defStore = DefinitionStore.getInstance();
693
+ // setup() may register shapes/variant shapes needed by definitions
694
+ if (plugin.setup) {
695
+ plugin.setup();
418
696
  }
419
- for (const key of Object.keys(evalState.value)) {
420
- if (!(key in componentValues)) {
421
- componentValues[key] = false;
697
+ // Shapes must be registered before definitions (definitions reference shape types)
698
+ if (plugin.shapes) {
699
+ for (const [shapeType, properties] of Object.entries(plugin.shapes)) {
700
+ defStore.setShape(shapeType, properties);
422
701
  }
423
702
  }
424
- return componentValues;
425
- }
426
- function syncComponentsAndPreserved(evalState, preservedNodeValue, currentValue) {
427
- const componentValues = { ...evalState.value };
428
- if (preservedNodeValue) {
429
- for (const key of Object.keys(evalState.value)) {
430
- if (evalState.value[key] === null) {
431
- componentValues[key] = preservedNodeValue[key] ?? false;
432
- }
433
- }
434
- // Preserve selection for components being recreated
435
- for (const key of Object.keys(preservedNodeValue)) {
436
- if (!(key in componentValues)) {
437
- componentValues[key] = preservedNodeValue[key];
438
- }
439
- }
703
+ if (plugin.definitions) {
704
+ defStore.setComponents(unfoldComponentDefinitions(plugin.definitions));
440
705
  }
441
- // Include user changes not yet in preservedNodeValue
442
- for (const key of Object.keys(currentValue)) {
443
- if (!(key in componentValues)) {
444
- componentValues[key] = currentValue[key];
445
- }
706
+ if (plugin.commands) {
707
+ defStore.setCommands(plugin.commands);
446
708
  }
447
- return componentValues;
448
- }
449
- function updateComponentsValues(componentsIds, componentValues, keyProp, valueProp) {
450
- const store = ComponentStore.getInstance();
451
- const components = componentsIds.map((id) => store.getComponentOrThrow(id));
452
- for (const cmp of components) {
453
- const key = cmp.getNodeValue(keyProp);
454
- if (!key)
455
- continue;
456
- const value = componentValues[key];
457
- const valueNode = cmp.getNode(valueProp);
458
- store.setNodeRawSchema(valueNode, value);
709
+ if (plugin.functions) {
710
+ FunctionRegistry.getInstance().setFunctions(plugin.functions);
459
711
  }
460
712
  }
461
- function toSelectionSet(componentValues) {
462
- return new Set(Object.entries(componentValues)
463
- .filter(([, fieldValue]) => !!fieldValue)
464
- .map(([key]) => key));
465
- }
466
- function toObject(selectionSet) {
467
- return Object.fromEntries(Array.from(selectionSet || []).map((key) => [key, true]));
468
- }
469
-
470
- async function addBinding(node, extra = false) {
471
- await when(() => !node.evaluating);
472
- const store = ComponentStore.getInstance();
473
- if (!node.position.componentId) {
474
- throw new Error('Cannot add binding without component context');
475
- }
476
- const component = store.getComponent(node.position.componentId);
477
- if (!component) {
478
- return;
479
- }
480
- const bindingSchema = extra ? node.extraBinding : node.bindingSchema;
481
- if (!bindingSchema) {
482
- return;
483
- }
484
- const valueType = node.valueType;
485
- switch (bindingSchema.bindingType) {
486
- case 'object':
487
- bindObject(node, component, {
488
- componentSelector: bindingSchema.componentSelector,
489
- keyProp: bindingSchema.mapping.key,
490
- valueProp: bindingSchema.mapping.value,
491
- valueType,
492
- });
493
- break;
494
- case 'selection':
495
- bindSelection(node, component, {
496
- componentSelector: bindingSchema.componentSelector,
497
- keyProp: bindingSchema.mapping.key,
498
- valueProp: bindingSchema.mapping.selected,
499
- valueType,
500
- });
501
- break;
502
- case 'array':
503
- bindArray(node, component, {
504
- componentSelector: bindingSchema.componentSelector,
505
- valueProp: bindingSchema.mapping.value,
506
- valueType,
507
- });
508
- break;
509
- case 'mirror':
510
- bindMirror(component, node, bindingSchema);
511
- break;
713
+ function registerPlugins(...plugins) {
714
+ for (const plugin of plugins) {
715
+ registerPlugin(plugin);
512
716
  }
513
717
  }
514
718
 
515
- const debugName = (position, thing) => {
516
- return [position.componentId, position.path.join(Delimiters.NodePath), thing].join('|');
517
- };
518
- const componentInstanceMap = new Map();
519
-
520
- const lowerSimpleValue = (schema) => ({
719
+ const lowerValue = (astNode) => ({
521
720
  type: 'value',
522
- value: schema.value,
721
+ value: astNode.value,
523
722
  });
524
- const lowerObjectOrMap = (schema) => {
525
- if (!schema.value) {
526
- return { type: 'value', value: null };
527
- }
528
- const entries = Object.entries(schema.value);
529
- return {
530
- type: 'object',
531
- properties: entries.map(([key, value]) => ({
532
- key: { type: 'value', value: key },
533
- value: lowerSchema(value),
534
- })),
535
- };
536
- };
537
- const lowerArrayOrSet = (schema) => {
538
- if (!schema.value) {
539
- throw new Error(`${schema.valueType.type} schema without value`);
540
- }
541
- return {
542
- type: 'array',
543
- items: schema.value.map((item) => lowerSchema(item)),
544
- };
545
- };
546
- const lowerShape = () => {
547
- throw new Error('Shape lowering not implemented');
548
- };
549
- const lowerVariantShape = (schema) => {
550
- if (!schema.value) {
551
- return { type: 'value', value: null };
723
+ const lowerReference = (astNode) => ({
724
+ type: 'function',
725
+ name: 'get',
726
+ args: [
727
+ {
728
+ type: 'value',
729
+ value: astNode.name,
730
+ },
731
+ ],
732
+ });
733
+ const lowerIdentifier = (astNode) => ({
734
+ type: 'value',
735
+ value: astNode.name,
736
+ });
737
+ const lowerArray = (astNode) => ({
738
+ type: 'array',
739
+ items: astNode.items.map(lowerFormulaAstNode),
740
+ });
741
+ const lowerObject = (astNode) => ({
742
+ type: 'object',
743
+ properties: astNode.properties.map(({ key, value }) => ({
744
+ key: lowerFormulaAstNode(key),
745
+ value: lowerFormulaAstNode(value),
746
+ })),
747
+ });
748
+ const lowerFunction = (astNode) => ({
749
+ type: 'function',
750
+ name: astNode.name,
751
+ args: astNode.args.map(lowerFormulaAstNode),
752
+ });
753
+ const lowerNot = (astNode) => {
754
+ if (!astNode.operand) {
755
+ throw new Error('Invert node must have an item');
552
756
  }
553
- const variantShapeValue = schema.value;
554
- const variantField = `${schema.valueType.shapeType}Type`;
555
- const entries = Object.entries(variantShapeValue);
556
- const properties = entries
557
- .filter(([key]) => key !== 'kind')
558
- .map(([key, value]) => ({
559
- key: { type: 'value', value: key },
560
- value: typeof value === 'string' ? { type: 'value', value } : lowerSchema(value),
561
- }));
562
- properties.push({
563
- key: { type: 'value', value: variantField },
564
- value: { type: 'value', value: variantShapeValue['kind'] },
565
- });
566
757
  return {
567
- type: 'object',
568
- properties,
758
+ type: 'function',
759
+ name: 'not',
760
+ args: [lowerFormulaAstNode(astNode.operand)],
569
761
  };
570
762
  };
571
- const schemaLowerers = {
572
- string: lowerSimpleValue,
573
- number: lowerSimpleValue,
574
- boolean: lowerSimpleValue,
575
- unknown: lowerSimpleValue,
576
- component: lowerSimpleValue,
577
- command: lowerSimpleValue,
578
- componentSchema: lowerSimpleValue,
579
- object: lowerObjectOrMap,
580
- map: lowerObjectOrMap,
581
- array: lowerArrayOrSet,
582
- set: lowerArrayOrSet,
583
- shape: lowerShape,
584
- variantShape: lowerVariantShape,
585
- };
586
- function lowerSchema(schema) {
587
- // If schema has a computation, lower it
588
- if (schema.computation) {
589
- return lowerComputationSchema(schema.computation);
590
- }
591
- // Otherwise lower the value based on valueType
592
- const lowerer = schemaLowerers[schema.valueType.type];
593
- if (lowerer) {
594
- return lowerer(schema);
763
+ const lowerNegate = (astNode) => {
764
+ if (!astNode.operand) {
765
+ throw new Error('Negate node must have an operand');
595
766
  }
596
- throw new Error(`Unsupported value type: ${schema.valueType.type}`);
597
- }
598
-
599
- function lowerForLoop(forComputation) {
600
- const dimensionsNode = {
601
- type: 'array',
602
- items: forComputation.dimensions.map((dimension) => {
603
- // Extract trackBy expression to be evaluated in RefSpace context where $item is available
604
- let trackByExpression = null;
605
- if (dimension.trackBy.computation && dimension.trackBy.computation.computationType === 'formula') {
606
- trackByExpression = `=${dimension.trackBy.computation.formula}`;
607
- }
608
- else if (dimension.trackBy.value !== null) {
609
- trackByExpression = dimension.trackBy.value;
610
- }
611
- return {
612
- type: 'object',
613
- properties: [
614
- { key: { type: 'value', value: 'items' }, value: lowerSchema(dimension.items) },
615
- { key: { type: 'value', value: 'itemsType' }, value: { type: 'value', value: dimension.itemsType } },
616
- { key: { type: 'value', value: 'item' }, value: lowerSchema(dimension.item) },
617
- { key: { type: 'value', value: 'index' }, value: lowerSchema(dimension.index) },
618
- { key: { type: 'value', value: 'first' }, value: lowerSchema(dimension.first) },
619
- { key: { type: 'value', value: 'last' }, value: lowerSchema(dimension.last) },
620
- { key: { type: 'value', value: 'trackBy' }, value: { type: 'value', value: trackByExpression } },
621
- ],
622
- };
623
- }),
624
- };
625
- const yieldNode = lowerSchema(forComputation.yield);
626
- return {
627
- type: 'function',
628
- name: 'repeat',
629
- args: [dimensionsNode, yieldNode],
630
- };
631
- }
632
- function lowerTemplate(templateComputation) {
633
767
  return {
634
- type: 'value',
635
- value: templateComputation,
768
+ type: 'function',
769
+ name: 'negate',
770
+ args: [lowerFormulaAstNode(astNode.operand)],
636
771
  };
637
- }
638
- function lowerIfStatement(ifNode) {
772
+ };
773
+ const lowerGroup = (astNode) => lowerFormulaAstNode(astNode.item);
774
+ const lowerTernary = (astNode) => {
775
+ if (!astNode.thenBranch || !astNode.elseBranch) {
776
+ throw new Error('Ternary expression must have both then and else branches');
777
+ }
639
778
  return {
640
779
  type: 'function',
641
780
  name: 'if',
642
- args: [lowerSchema(ifNode.if), lowerSchema(ifNode.then), lowerSchema(ifNode.else)],
781
+ args: [
782
+ lowerFormulaAstNode(astNode.condition),
783
+ lowerFormulaAstNode(astNode.thenBranch),
784
+ lowerFormulaAstNode(astNode.elseBranch),
785
+ ],
643
786
  };
644
- }
645
- function lowerSwitch(switchComputation) {
646
- // Lower the source
647
- const sourceEvalNode = lowerSchema(switchComputation.source);
648
- // Build cases array as flat array: [value1, result1, value2, result2, ...]
649
- // Extract .value from ComponentSchemaNodeSchema to get the actual component schema
650
- const casesArrayItems = [];
651
- for (const caseItem of switchComputation.cases) {
652
- casesArrayItems.push({ type: 'value', value: caseItem.equals });
653
- // caseItem.then is a ComponentSchemaNodeSchema, extract its .value property
654
- casesArrayItems.push({ type: 'value', value: caseItem.then.value });
787
+ };
788
+ const lowerBinaryOperation = (astNode) => {
789
+ if (!astNode.right) {
790
+ throw new Error(`Binary operation "${astNode.type}" must have a right operand`);
655
791
  }
656
- const casesArrayNode = {
657
- type: 'array',
658
- items: casesArrayItems,
659
- };
660
- // Lower the default value - extract .value from ComponentSchemaNodeSchema
661
- const defaultNode = { type: 'value', value: switchComputation.default.value };
662
792
  return {
663
793
  type: 'function',
664
- name: 'switch',
665
- args: [sourceEvalNode, casesArrayNode, defaultNode],
794
+ name: astNode.type,
795
+ args: [lowerFormulaAstNode(astNode.left), lowerFormulaAstNode(astNode.right)],
666
796
  };
667
- }
668
- const computationLowerers = {
669
- formula: (schema) => lowerFormula(schema.formula),
670
- for: (schema) => lowerForLoop(schema),
671
- template: (schema) => lowerTemplate(schema),
672
- if: (schema) => lowerIfStatement(schema),
673
- switch: (schema) => lowerSwitch(schema),
674
797
  };
675
- function lowerComputationSchema(computationSchema) {
676
- const lowerer = computationLowerers[computationSchema.computationType];
798
+ const astNodeLowerers = {
799
+ value: (astNode) => lowerValue(astNode),
800
+ reference: (astNode) => lowerReference(astNode),
801
+ identifier: (astNode) => lowerIdentifier(astNode),
802
+ array: (astNode) => lowerArray(astNode),
803
+ object: (astNode) => lowerObject(astNode),
804
+ function: (astNode) => lowerFunction(astNode),
805
+ not: (astNode) => lowerNot(astNode),
806
+ negate: (astNode) => lowerNegate(astNode),
807
+ multiply: (astNode) => lowerBinaryOperation(astNode),
808
+ divide: (astNode) => lowerBinaryOperation(astNode),
809
+ modulo: (astNode) => lowerBinaryOperation(astNode),
810
+ plus: (astNode) => lowerBinaryOperation(astNode),
811
+ minus: (astNode) => lowerBinaryOperation(astNode),
812
+ eq: (astNode) => lowerBinaryOperation(astNode),
813
+ neq: (astNode) => lowerBinaryOperation(astNode),
814
+ gt: (astNode) => lowerBinaryOperation(astNode),
815
+ lt: (astNode) => lowerBinaryOperation(astNode),
816
+ gte: (astNode) => lowerBinaryOperation(astNode),
817
+ lte: (astNode) => lowerBinaryOperation(astNode),
818
+ and: (astNode) => lowerBinaryOperation(astNode),
819
+ or: (astNode) => lowerBinaryOperation(astNode),
820
+ nullCoalesce: (astNode) => lowerBinaryOperation(astNode),
821
+ group: (astNode) => lowerGroup(astNode),
822
+ ternary: (astNode) => lowerTernary(astNode),
823
+ };
824
+ function lowerFormulaAstNode(astNode) {
825
+ const lowerer = astNodeLowerers[astNode.type];
677
826
  if (lowerer) {
678
- return lowerer(computationSchema);
827
+ return lowerer(astNode);
679
828
  }
680
- throw new Error(`Unsupported computation type: ${computationSchema.computationType}`);
829
+ throw new Error(`Unsupported Formula AST node type: ${astNode.type}`);
681
830
  }
682
831
 
683
- function activateForComputation(node) {
684
- const store = ComponentStore.getInstance();
685
- const currentFrame = node.computationStack.top;
686
- if (!currentFrame) {
687
- throw new Error('Cannot activate for computation: no current frame');
832
+ function lowerFormula(formula) {
833
+ let formulaAst;
834
+ try {
835
+ formulaAst = parseFormula(formula);
688
836
  }
689
- const evalNode = lowerComputationSchema(currentFrame.schema);
837
+ catch (e) {
838
+ log.trace(e);
839
+ return { type: 'value', value: null };
840
+ }
841
+ return lowerFormulaAstNode(formulaAst);
842
+ }
843
+
844
+ function bindArray(node, component, cfg) {
845
+ const { componentSelector, valueProp } = cfg;
846
+ const evalNode = lowerFormula(`propArray('${componentSelector}', '${valueProp}')`);
690
847
  if (!node.position.componentId) {
691
848
  throw new Error('Node does not have a component context');
692
849
  }
693
850
  const { computed, disposers } = evaluateNode(evalNode, {
694
851
  componentId: node.position.componentId,
695
852
  });
696
- currentFrame.disposers.push(...disposers);
697
- const computationDisposer = reaction(() => computed.get(), ({ value: newItems }) => {
698
- const toAdd = store.reconcileForLoopChildren(node, newItems);
699
- // Activate only newly added children
700
- const structure = node.structure || [];
701
- for (const index of toAdd) {
702
- const child = structure[index];
703
- if (child) {
704
- activateNode(child);
853
+ node.disposers.push(...disposers);
854
+ const disposer = reaction(() => [
855
+ node.extractedValue,
856
+ ComponentTreeApi.findComponentsIds(component.id, componentSelector),
857
+ computed.get(),
858
+ ], ([value, componentsIds, evalState], prev) => {
859
+ let newValue;
860
+ if (!prev) {
861
+ // first run
862
+ if (value && value.length > 0) {
863
+ newValue = [...value];
864
+ }
865
+ else {
866
+ newValue = evalState.value;
705
867
  }
706
868
  }
707
- }, { fireImmediately: true, name: debugName(node.position, 'repeatable-reaction') });
708
- currentFrame.disposers.push(computationDisposer);
709
- currentFrame.markActivated();
710
- }
711
-
712
- function activateArrayStructure(node) {
713
- const structure = node.structure;
714
- if (structure === null) {
715
- return;
716
- }
717
- structure.forEach((item) => {
718
- activateNode(item);
719
- });
720
- }
721
-
722
- /**
723
- * Activates node change handlers for a component by setting up MobX reactions.
724
- * Each handler watches a node selector and executes its command when the node value changes.
725
- */
726
- function activateNodeChangeHandlers(component) {
727
- for (const handler of component.onNodeChange) {
728
- const disposer = reaction(
729
- // Track the node value using the selector
730
- () => component.getNodeValue(handler.selector),
731
- // Execute the command when node value changes
732
- async (nodeValue) => {
733
- const src = component.sourceUrl ? `[${component.sourceUrl}] ` : '';
734
- log.trace(`${src}onNodeChange "${handler.selector}" triggered on ${component.id}`);
735
- try {
736
- await handler.command.viewModel.execute(nodeValue);
869
+ else {
870
+ if (!comparer.structural(value, prev[0])) {
871
+ // console.log('100', JSON.stringify(value), JSON.stringify(prev[0]));
872
+ newValue = value ? [...value] : [];
737
873
  }
738
- catch (error) {
739
- const src = component.sourceUrl ? `[${component.sourceUrl}] ` : '';
740
- log.error(`${src}onNodeChange handler failed for component ${component.id}, selector "${handler.selector}":`, error);
741
- // Re-throw to maintain existing error behavior
742
- throw error;
874
+ else {
875
+ // console.log('200');
876
+ newValue = [...evalState.value];
877
+ if (value && evalState.value.length > prev[2].value.length) {
878
+ newValue = [...value];
879
+ }
880
+ }
881
+ }
882
+ // console.log(JSON.stringify(newValue));
883
+ const components = componentsIds.map((id) => ComponentStore.getInstance().getComponentOrThrow(id));
884
+ runInAction(() => {
885
+ const store = ComponentStore.getInstance();
886
+ store.setNodeRawSchema(node, newValue);
887
+ for (let i = 0; i < components.length; i++) {
888
+ const c = components[i];
889
+ const value = newValue[i];
890
+ const valueNode = c.getNode(valueProp);
891
+ store.setNodeRawSchema(valueNode, value);
743
892
  }
744
- }, {
745
- // Fire immediately on setup to handle initial state
746
- fireImmediately: true,
747
893
  });
748
- component.handlerDisposers.push(disposer);
749
- }
750
- }
751
-
752
- function activateComponentStructure(node) {
753
- const structure = node.structure;
754
- if (structure === null) {
755
- return;
756
- }
757
- const component = ComponentStore.getInstance().getComponentOrThrow(structure);
758
- activateComponent(component);
759
- }
760
- function activateComponent(component) {
761
- ComponentStore.getInstance().componentsToPreload.add(component.componentType);
762
- for (const node of component.props.values()) {
763
- activateNode(node);
764
- }
765
- for (const node of component.variables.values()) {
766
- activateNode(node);
767
- }
768
- for (const handler of component.onNodeChange) {
769
- activateNode(handler.command);
770
- }
771
- activateNodeChangeHandlers(component);
772
- }
773
-
774
- function activateMapStructure(node) {
775
- const structure = node.structure;
776
- if (structure === null) {
777
- return;
778
- }
779
- for (const item of Object.values(structure)) {
780
- activateNode(item);
781
- }
782
- }
783
-
784
- function activateObjectStructure(node) {
785
- const structure = node.structure;
786
- if (structure === null) {
787
- return;
788
- }
789
- for (const node of Object.values(structure)) {
790
- activateNode(node);
791
- }
792
- }
793
-
794
- function activateShapeStructure(node) {
795
- const { valueType, structure } = node;
796
- if (structure === null) {
797
- return;
798
- }
799
- const objectValueType = DefinitionStore.getInstance().getShape(valueType.shapeType);
800
- for (const key of Object.keys(objectValueType.fields)) {
801
- activateNode(structure[key]);
802
- }
803
- }
804
-
805
- function activateVariantShapeStructure(node) {
806
- const { valueType, structure } = node;
807
- if (structure === null) {
808
- return;
809
- }
810
- const objectValueType = DefinitionStore.getInstance().getVariantShape(valueType.shapeType, structure.kind);
811
- for (const key of Object.keys(objectValueType.fields)) {
812
- activateNode(structure.fields[key]);
813
- }
894
+ }, { fireImmediately: true, equals: comparer.structural });
895
+ node.disposers.push(disposer);
814
896
  }
815
897
 
816
- const structureActivators = {
817
- shape: (node) => activateShapeStructure(node),
818
- variantShape: (node) => activateVariantShapeStructure(node),
819
- array: (node) => activateArrayStructure(node),
820
- object: (node) => activateObjectStructure(node),
821
- map: (node) => activateMapStructure(node),
822
- component: (node) => activateComponentStructure(node),
823
- };
824
- function activateStructure(node) {
825
- const value = node.structure;
826
- if (value === null) {
827
- return;
898
+ function bindMirror(component, node, cfg) {
899
+ const store = ComponentStore.getInstance();
900
+ let targetNode;
901
+ try {
902
+ // For Facade components, start lookup from parent. This handles inline bindings
903
+ // on template contract variables, where the Facade has a variable with the same
904
+ // name - we need to find the parent's variable, not self.
905
+ const lookupFromId = component.componentType === 'sys.Facade' ? component.position.componentId : component.id;
906
+ if (!lookupFromId) {
907
+ log.error(`Cannot activate mirror binding: no parent component for selector "${cfg.selector}"`);
908
+ return;
909
+ }
910
+ targetNode = store.getNode(lookupFromId, cfg.selector);
828
911
  }
829
- const activator = structureActivators[node.nodeType];
830
- if (activator) {
831
- activator(node);
912
+ catch (error) {
913
+ log.error(`Cannot activate mirror binding: ${error}`);
914
+ return;
832
915
  }
916
+ const disposer = reaction(() => [targetNode.extractedValue, node.extractedValue], (curr, prev) => {
917
+ const [targetValue, sourceValue = null] = curr;
918
+ const [prevTargetValue, prevSourceValue] = prev || [null, null];
919
+ if (comparer.structural(targetValue, sourceValue)) {
920
+ return;
921
+ }
922
+ if (!prev && targetValue !== null) {
923
+ store.setNodeRawSchema(node, targetValue);
924
+ return;
925
+ }
926
+ if (targetValue !== prevTargetValue) {
927
+ store.setNodeRawSchema(node, targetValue);
928
+ return;
929
+ }
930
+ if (sourceValue !== prevSourceValue) {
931
+ store.setNodeRawSchema(targetNode, sourceValue);
932
+ }
933
+ }, { fireImmediately: true });
934
+ node.disposers.push(disposer);
833
935
  }
834
936
 
835
- function activateFormulaComputation(node) {
836
- const store = ComponentStore.getInstance();
837
- const currentFrame = node.computationStack.top;
838
- if (!currentFrame) {
839
- throw new Error('Cannot activate formula computation: no current frame');
840
- }
841
- const thisFrameDepth = node.computationStack.depth;
842
- const evalNode = lowerComputationSchema(currentFrame.schema);
937
+ function bindObject(node, component, cfg) {
938
+ const { componentSelector, keyProp, valueProp } = cfg;
939
+ const evalNode = lowerFormula(`propObject('${componentSelector}', '${keyProp}', '${valueProp}')`);
843
940
  if (!node.position.componentId) {
844
941
  throw new Error('Node does not have a component context');
845
942
  }
846
- const component = getComponent(node);
847
943
  const { computed, disposers } = evaluateNode(evalNode, {
848
944
  componentId: node.position.componentId,
849
- sourceUrl: component.sourceUrl ?? undefined,
850
945
  });
851
- currentFrame.disposers.push(...disposers);
852
- const computationDisposer = reaction(() => computed.get(), (computationState) => {
853
- const { evaluating, value: rawSchema, failed } = computationState;
854
- node.evaluating = evaluating;
855
- if (evaluating) {
946
+ node.disposers.push(...disposers);
947
+ let preservedNodeValue = {};
948
+ const disposer = reaction(() => [
949
+ node.extractedValue,
950
+ ComponentTreeApi.findComponentsIds(component.id, componentSelector),
951
+ computed.get(),
952
+ ], ([nodeValue, componentsIds, evalState], prev) => {
953
+ const currentValue = nodeValue || {};
954
+ if (comparer.structural(evalState.value, currentValue)) {
856
955
  return;
857
956
  }
858
- node.failed = failed;
859
- // Clear result frames from this depth onward (not all results!)
860
- // Example: if this is depth 2, clear frames 2, 3, 4... but keep 0, 1
861
- node.computationStack.popFrom(thisFrameDepth);
862
- store.setNodeRawSchema(node, rawSchema);
863
- if (node.computationStack.depth > thisFrameDepth) {
864
- // If a nested computation was pushed onto the stack, activate it
865
- activateComputation(node);
957
+ const firstRun = !prev;
958
+ const nodeChanged = !firstRun && !comparer.structural(nodeValue, prev[0]);
959
+ if (firstRun || nodeChanged) {
960
+ preservedNodeValue = currentValue;
866
961
  }
867
962
  else {
868
- activateStructure(node);
963
+ preservedNodeValue = shallowMerge(preservedNodeValue, evalState.value);
869
964
  }
870
- }, {
871
- fireImmediately: true,
872
- name: debugName(node.position, 'formula-reaction'),
873
- });
874
- currentFrame.disposers.push(computationDisposer);
875
- currentFrame.markActivated();
965
+ let componentValues;
966
+ if (firstRun) {
967
+ const hasInitialValue = Object.keys(currentValue).length > 0;
968
+ componentValues = hasInitialValue
969
+ ? syncNodeValueAndComponents$1(currentValue, evalState)
970
+ : { ...evalState.value };
971
+ }
972
+ else if (nodeChanged) {
973
+ componentValues = syncNodeValueAndComponents$1(currentValue, evalState);
974
+ }
975
+ else {
976
+ componentValues = handleComponentsChanged(evalState, preservedNodeValue);
977
+ }
978
+ runInAction(() => {
979
+ const store = ComponentStore.getInstance();
980
+ store.setNodeRawSchema(node, componentValues);
981
+ updateComponentsValues$1(componentsIds, componentValues, keyProp, valueProp);
982
+ });
983
+ }, { fireImmediately: true, equals: comparer.structural });
984
+ node.disposers.push(disposer);
876
985
  }
877
-
878
- function activateTemplateComputation(node) {
879
- const store = ComponentStore.getInstance();
880
- const nodePath = node.position.path;
881
- const component = getComponent(node);
882
- const currentFrame = node.computationStack.top;
883
- if (!currentFrame) {
884
- throw new Error('Cannot activate template computation: no current frame');
986
+ function handleComponentsChanged(evalState, preservedNodeValue) {
987
+ const componentValues = {};
988
+ for (const [inputKey, currentValue] of Object.entries(evalState.value)) {
989
+ const preservedValue = preservedNodeValue[inputKey];
990
+ if (isObject(currentValue) && isObject(preservedValue)) {
991
+ componentValues[inputKey] = mergePreservedWithChildGroup(currentValue, preservedValue);
992
+ continue;
993
+ }
994
+ componentValues[inputKey] = currentValue ?? preservedValue;
885
995
  }
886
- const { defer, path, contractVariables, ref } = currentFrame.schema;
887
- const taskKey = `template-${component.id}-${nodePath}`;
888
- if (defer) {
889
- templateLoadingTracker.startLoading(taskKey);
890
- store.setNodeSchema(node, defer.placeholder);
891
- // Fire-and-forget: load template asynchronously
892
- loadTemplates({ templateUrl: path })
893
- .then(() => {
894
- const cacheEntry = templateRegistry.get(path);
895
- if (!cacheEntry) {
896
- // Loading failed - show error schema
897
- store.setNodeSchema(node, defer.error);
898
- templateLoadingTracker.finishLoading(taskKey);
899
- return;
900
- }
901
- // Loading succeeded - create and set the template schema
902
- setTemplateOnNode(node, cacheEntry, path, contractVariables, ref);
903
- templateLoadingTracker.finishLoading(taskKey);
904
- })
905
- .catch((error) => {
906
- // Handle any unhandled errors during template creation
907
- log.error(`Failed to activate template "${path}":`, error);
908
- store.setNodeSchema(node, defer.error);
909
- templateLoadingTracker.finishLoading(taskKey);
910
- });
911
- // Mark as activated immediately (don't wait for loading)
912
- currentFrame.markActivated();
913
- return;
996
+ return componentValues;
997
+ }
998
+ function mergePreservedWithChildGroup(currentValue, preservedValue) {
999
+ const merged = { ...currentValue };
1000
+ for (const [key, value] of Object.entries(currentValue)) {
1001
+ if (value !== null) {
1002
+ continue;
1003
+ }
1004
+ if (preservedValue[key] === undefined || preservedValue[key] === null) {
1005
+ continue;
1006
+ }
1007
+ merged[key] = preservedValue[key];
914
1008
  }
915
- const cacheEntry = templateRegistry.get(path);
916
- if (!cacheEntry) {
917
- const src = component.sourceUrl ? `[${component.sourceUrl}] ` : '';
918
- throw new Error(`${src}Template not found in cache: ${path}`);
1009
+ return merged;
1010
+ }
1011
+ function syncNodeValueAndComponents$1(nodeValue, evalState) {
1012
+ const componentValues = {};
1013
+ for (const fieldKey of Object.keys(evalState.value)) {
1014
+ if (!(fieldKey in nodeValue)) {
1015
+ componentValues[fieldKey] = null;
1016
+ continue;
1017
+ }
1018
+ componentValues[fieldKey] = nodeValue[fieldKey];
919
1019
  }
920
- // Non-defer path: template is already loaded, create schema synchronously
921
- setTemplateOnNode(node, cacheEntry, path, contractVariables, ref);
922
- currentFrame.markActivated();
1020
+ return componentValues;
923
1021
  }
924
- function setTemplateOnNode(node, cacheEntry, path, contractVariables, ref) {
1022
+ function updateComponentsValues$1(componentsIds, componentValues, keyProp, valueProp) {
925
1023
  const store = ComponentStore.getInstance();
926
- const rawFacadeSchema = createRawFacadeSchema(cacheEntry, path, contractVariables, ref);
927
- store.setNodeRawSchema(node, rawFacadeSchema);
928
- // Activate the facade component after it's created (only for regular component nodes)
929
- if (node.nodeType !== 'componentSchema') {
930
- const facadeId = node.structure;
931
- const facade = store.getComponentOrThrow(facadeId);
932
- facade.sourceUrl = path;
933
- activateComponent(facade);
1024
+ const components = componentsIds.map((id) => store.getComponentOrThrow(id));
1025
+ for (const cmp of components) {
1026
+ const key = cmp.getNodeValue(keyProp);
1027
+ if (!key)
1028
+ continue;
1029
+ const value = componentValues[key];
1030
+ const valueNode = cmp.getNode(valueProp);
1031
+ store.setNodeRawSchema(valueNode, value);
934
1032
  }
935
1033
  }
936
- function createRawFacadeSchema(cacheEntry, path, contractVariables, ref) {
937
- if (!cacheEntry) {
938
- throw new Error(`Template cache entry is undefined for path: ${path}`);
1034
+ function shallowMerge(target, source) {
1035
+ const merged = { ...target };
1036
+ for (const [key, value] of Object.entries(source)) {
1037
+ if (value === null || value === undefined) {
1038
+ continue;
1039
+ }
1040
+ if (isObject(value) && isObject(merged[key])) {
1041
+ merged[key] = { ...merged[key], ...value };
1042
+ continue;
1043
+ }
1044
+ merged[key] = value;
939
1045
  }
940
- const { recipe } = cacheEntry;
941
- const { types: _types, importTypes: _importTypes, contract = {}, defaultSlot, ...componentRecipe } = recipe;
942
- const rootSchema = unfoldNodeSchema(componentRecipe, { type: 'component' });
943
- const rootValue = rootSchema.value;
944
- if (!rootValue) {
945
- const recipeKeys = Object.keys(componentRecipe).join(', ');
946
- throw new Error(`Template "${path}": Root schema has no value. ` +
947
- `The recipe root must define a component (implicit or explicit componentType). ` +
948
- `Found keys: [${recipeKeys}]`);
1046
+ return merged;
1047
+ }
1048
+
1049
+ function bindSelection(node, component, cfg) {
1050
+ const { componentSelector, keyProp, valueProp } = cfg;
1051
+ const evalNode = lowerFormula(`propObject('${componentSelector}', '${keyProp}', '${valueProp}')`);
1052
+ if (!node.position.componentId) {
1053
+ throw new Error('Node does not have a component context');
1054
+ }
1055
+ const { computed, disposers } = evaluateNode(evalNode, {
1056
+ componentId: node.position.componentId,
1057
+ });
1058
+ node.disposers.push(...disposers);
1059
+ let preservedNodeValue = null;
1060
+ const disposer = reaction(() => [
1061
+ node.extractedValue,
1062
+ ComponentTreeApi.findComponentsIds(component.id, componentSelector),
1063
+ computed.get(),
1064
+ ], ([nodeValue, componentsIds, evalState], prev) => {
1065
+ const currentValue = toObject(nodeValue);
1066
+ const firstRun = !prev;
1067
+ const nodeChanged = !firstRun && !comparer.structural(nodeValue, prev[0]);
1068
+ if (firstRun || nodeChanged) {
1069
+ preservedNodeValue = currentValue;
1070
+ }
1071
+ if (comparer.structural(evalState.value, currentValue)) {
1072
+ return;
1073
+ }
1074
+ let componentValues;
1075
+ if (firstRun) {
1076
+ const initialNodeValue = Object.keys(currentValue).length > 0;
1077
+ componentValues = initialNodeValue
1078
+ ? syncNodeValueAndComponents(currentValue, evalState, preservedNodeValue)
1079
+ : { ...evalState.value };
1080
+ }
1081
+ else if (nodeChanged) {
1082
+ componentValues = syncNodeValueAndComponents(currentValue, evalState, preservedNodeValue);
1083
+ }
1084
+ else {
1085
+ componentValues = syncComponentsAndPreserved(evalState, preservedNodeValue, currentValue);
1086
+ }
1087
+ const selectionValue = toSelectionSet(componentValues);
1088
+ runInAction(() => {
1089
+ const store = ComponentStore.getInstance();
1090
+ store.setNodeRawSchema(node, selectionValue);
1091
+ updateComponentsValues(componentsIds, componentValues, keyProp, valueProp);
1092
+ });
1093
+ }, { fireImmediately: true, equals: comparer.structural });
1094
+ node.disposers.push(disposer);
1095
+ }
1096
+ function syncNodeValueAndComponents(nodeValue, evalState, preservedNodeValue) {
1097
+ const componentValues = { ...nodeValue };
1098
+ // Keep keys in preservedNodeValue to handle async component loading
1099
+ for (const key of Object.keys(componentValues)) {
1100
+ if (!(key in evalState.value) && (!preservedNodeValue || !(key in preservedNodeValue))) {
1101
+ delete componentValues[key];
1102
+ }
949
1103
  }
950
- // Collect value types from the root schema for each mapped variable
951
- const valueTypes = {};
952
- for (const [facadeKey, rootKey] of Object.entries(contract)) {
953
- const sourceNodeSchema = getSourceNodeOrThrow(rootValue, rootKey, path);
954
- valueTypes[facadeKey] = sourceNodeSchema.valueType;
1104
+ for (const key of Object.keys(evalState.value)) {
1105
+ if (!(key in componentValues)) {
1106
+ componentValues[key] = false;
1107
+ }
955
1108
  }
956
- const prefixedVariables = createFacadeVariables(path, contractVariables, contract, valueTypes, defaultSlot);
957
- const contentWithBindings = addSynchronizeBindings(contract, rootSchema);
958
- // Return the raw facade schema object
959
- return {
960
- componentType: 'sys.Facade',
961
- ref,
962
- content: contentWithBindings,
963
- ...prefixedVariables,
964
- };
1109
+ return componentValues;
965
1110
  }
966
- function createFacadeVariables(templatePath, contractVariables, contract, valueTypes, flatWrapperSlot) {
967
- const contractKeys = Object.keys(contractVariables);
968
- const contractPropKeys = Object.keys(contract);
969
- const unmappedVariables = contractKeys.filter((key) => key !== 'slot' && !contractPropKeys.includes(key));
970
- if (unmappedVariables.length > 0) {
971
- // Detect if unmapped keys look like variable declarations (have $ prefix or @ type annotation)
972
- const variableDeclarations = unmappedVariables.filter((key) => key.startsWith('$') || key.includes('@'));
973
- const regularProps = unmappedVariables.filter((key) => !key.startsWith('$') && !key.includes('@'));
974
- if (variableDeclarations.length > 0) {
975
- throw new Error(`Template "${templatePath}": Variable declarations cannot be siblings of templateUrl. ` +
976
- `Found: ${variableDeclarations.join(', ')}. ` +
977
- `Pass values directly or wrap templateUrl in a component.`);
1111
+ function syncComponentsAndPreserved(evalState, preservedNodeValue, currentValue) {
1112
+ const componentValues = { ...evalState.value };
1113
+ if (preservedNodeValue) {
1114
+ for (const key of Object.keys(evalState.value)) {
1115
+ if (evalState.value[key] === null) {
1116
+ componentValues[key] = preservedNodeValue[key] ?? false;
1117
+ }
978
1118
  }
979
- else if (regularProps.length > 0) {
980
- log.warn(`Template "${templatePath}": Unknown properties: ${regularProps.join(', ')}. ` +
981
- `Expected: ${contractPropKeys.join(', ')}`);
1119
+ // Preserve selection for components being recreated
1120
+ for (const key of Object.keys(preservedNodeValue)) {
1121
+ if (!(key in componentValues)) {
1122
+ componentValues[key] = preservedNodeValue[key];
1123
+ }
982
1124
  }
983
1125
  }
984
- const prefixedVariables = {};
985
- for (const facadeKey of contractPropKeys) {
986
- const prefixedKey = `${Delimiters.Variable}${facadeKey}`;
987
- prefixedVariables[prefixedKey] = unfoldNodeSchema(contractVariables[facadeKey], valueTypes[facadeKey]);
988
- }
989
- if ('slot' in contractVariables) {
990
- const slotComponent = contractVariables['slot'];
991
- let targetSlot = flatWrapperSlot;
992
- // Find default component slot if not explicitly specified
993
- if (!targetSlot) {
994
- const componentSlots = Object.keys(valueTypes).filter((key) => valueTypes[key].type === 'component');
995
- if (componentSlots.length !== 1) {
996
- throw new Error(`Cannot find default slot for template computation. Found ${componentSlots.length} slots: ${componentSlots.join(', ')}`);
997
- }
998
- targetSlot = componentSlots[0];
1126
+ // Include user changes not yet in preservedNodeValue
1127
+ for (const key of Object.keys(currentValue)) {
1128
+ if (!(key in componentValues)) {
1129
+ componentValues[key] = currentValue[key];
999
1130
  }
1000
- const prefixedKey = `${Delimiters.Variable}${targetSlot}`;
1001
- prefixedVariables[prefixedKey] = unfoldNodeSchema(slotComponent, valueTypes[targetSlot]);
1002
1131
  }
1003
- return prefixedVariables;
1132
+ return componentValues;
1004
1133
  }
1005
- function addSynchronizeBindings(contract, rootSchema) {
1006
- const rootValue = rootSchema.value;
1007
- for (const [facadeKey, templateKey] of Object.entries(contract)) {
1008
- // This will always succeed because mapping was already validated in createRawFacadeSchema
1009
- const sourceNodeSchema = getSourceNodeOrThrow(rootValue, templateKey, '');
1010
- sourceNodeSchema.extraBinding = {
1011
- bindingType: 'mirror',
1012
- selector: `^sys.Facade->$${facadeKey}`,
1013
- };
1134
+ function updateComponentsValues(componentsIds, componentValues, keyProp, valueProp) {
1135
+ const store = ComponentStore.getInstance();
1136
+ const components = componentsIds.map((id) => store.getComponentOrThrow(id));
1137
+ for (const cmp of components) {
1138
+ const key = cmp.getNodeValue(keyProp);
1139
+ if (!key)
1140
+ continue;
1141
+ const value = componentValues[key];
1142
+ const valueNode = cmp.getNode(valueProp);
1143
+ store.setNodeRawSchema(valueNode, value);
1014
1144
  }
1015
- return rootSchema;
1016
1145
  }
1017
- function getSourceNodeOrThrow(rootValue, key, templatePath) {
1018
- const isVariable = key.startsWith('$');
1019
- const source = isVariable ? rootValue.variables : rootValue.props;
1020
- if (!source) {
1021
- const kind = isVariable ? 'variables' : 'props';
1022
- throw new Error(`Template "${templatePath}": Cannot resolve contract key "${key}" — ` +
1023
- `root component "${rootValue.componentType}" has no ${kind}. ` +
1024
- `Ensure the template recipe declares "${key}" as a ${isVariable ? 'variable' : 'property'}.`);
1025
- }
1026
- const node = source.get(isVariable ? key.slice(1) : key);
1027
- if (!node) {
1028
- const kind = isVariable ? 'variable' : 'property';
1029
- const lookupKey = isVariable ? key.slice(1) : key;
1030
- const available = [...source.keys()].map((k) => (isVariable ? `$${k}` : k));
1031
- throw new Error(`Template "${templatePath}": Contract key "${key}" not found as ${kind} "${lookupKey}" ` +
1032
- `in root component "${rootValue.componentType}". ` +
1033
- `Available ${kind}s: [${available.join(', ')}]`);
1034
- }
1035
- return node;
1146
+ function toSelectionSet(componentValues) {
1147
+ return new Set(Object.entries(componentValues)
1148
+ .filter(([, fieldValue]) => !!fieldValue)
1149
+ .map(([key]) => key));
1150
+ }
1151
+ function toObject(selectionSet) {
1152
+ return Object.fromEntries(Array.from(selectionSet || []).map((key) => [key, true]));
1036
1153
  }
1037
1154
 
1038
- const computationActivators = {
1039
- if: (node) => activateFormulaComputation(node),
1040
- formula: (node) => activateFormulaComputation(node),
1041
- switch: (node) => activateFormulaComputation(node),
1042
- for: (node) => activateForComputation(node),
1043
- template: (node) => activateTemplateComputation(node),
1044
- };
1045
- function activateComputation(node) {
1046
- const frame = node.computationStack.top;
1047
- if (!frame) {
1155
+ async function addBinding(node, extra = false) {
1156
+ await when(() => !node.evaluating);
1157
+ const store = ComponentStore.getInstance();
1158
+ if (!node.position.componentId) {
1159
+ throw new Error('Cannot add binding without component context');
1160
+ }
1161
+ const component = store.getComponent(node.position.componentId);
1162
+ if (!component) {
1048
1163
  return;
1049
1164
  }
1050
- // Idempotency: don't re-activate an already activated frame
1051
- if (frame.activated) {
1165
+ const bindingSchema = extra ? node.extraBinding : node.bindingSchema;
1166
+ if (!bindingSchema) {
1052
1167
  return;
1053
1168
  }
1054
- const activator = computationActivators[frame.schema.computationType];
1055
- if (activator) {
1056
- activator(node);
1169
+ const valueType = node.valueType;
1170
+ switch (bindingSchema.bindingType) {
1171
+ case 'object':
1172
+ bindObject(node, component, {
1173
+ componentSelector: bindingSchema.componentSelector,
1174
+ keyProp: bindingSchema.mapping.key,
1175
+ valueProp: bindingSchema.mapping.value,
1176
+ valueType,
1177
+ });
1178
+ break;
1179
+ case 'selection':
1180
+ bindSelection(node, component, {
1181
+ componentSelector: bindingSchema.componentSelector,
1182
+ keyProp: bindingSchema.mapping.key,
1183
+ valueProp: bindingSchema.mapping.selected,
1184
+ valueType,
1185
+ });
1186
+ break;
1187
+ case 'array':
1188
+ bindArray(node, component, {
1189
+ componentSelector: bindingSchema.componentSelector,
1190
+ valueProp: bindingSchema.mapping.value,
1191
+ valueType,
1192
+ });
1193
+ break;
1194
+ case 'mirror':
1195
+ bindMirror(component, node, bindingSchema);
1196
+ break;
1057
1197
  }
1058
1198
  }
1059
1199
 
1060
- function activateNode(node) {
1061
- if (node.fullyActivated) {
1062
- return;
1200
+ const debugName = (position, thing) => {
1201
+ return [position.componentId, position.path.join(Delimiters.NodePath), thing].join('|');
1202
+ };
1203
+ const componentInstanceMap = new Map();
1204
+
1205
+ const lowerSimpleValue = (schema) => ({
1206
+ type: 'value',
1207
+ value: schema.value,
1208
+ });
1209
+ const lowerObjectOrMap = (schema) => {
1210
+ if (!schema.value) {
1211
+ return { type: 'value', value: null };
1063
1212
  }
1064
- activateStructure(node);
1065
- if (!node.computationStack.isEmpty) {
1066
- activateComputation(node);
1213
+ const entries = Object.entries(schema.value);
1214
+ return {
1215
+ type: 'object',
1216
+ properties: entries.map(([key, value]) => ({
1217
+ key: { type: 'value', value: key },
1218
+ value: lowerSchema(value),
1219
+ })),
1220
+ };
1221
+ };
1222
+ const lowerArrayOrSet = (schema) => {
1223
+ if (!schema.value) {
1224
+ throw new Error(`${schema.valueType.type} schema without value`);
1067
1225
  }
1068
- if (node.bindingSchema) {
1069
- void addBinding(node);
1226
+ return {
1227
+ type: 'array',
1228
+ items: schema.value.map((item) => lowerSchema(item)),
1229
+ };
1230
+ };
1231
+ const lowerShape = () => {
1232
+ throw new Error('Shape lowering not implemented');
1233
+ };
1234
+ const lowerVariantShape = (schema) => {
1235
+ if (!schema.value) {
1236
+ return { type: 'value', value: null };
1070
1237
  }
1071
- if (node.extraBinding) {
1072
- void addBinding(node, true);
1238
+ const variantShapeValue = schema.value;
1239
+ const variantField = `${schema.valueType.shapeType}Type`;
1240
+ const entries = Object.entries(variantShapeValue);
1241
+ const properties = entries
1242
+ .filter(([key]) => key !== 'kind')
1243
+ .map(([key, value]) => ({
1244
+ key: { type: 'value', value: key },
1245
+ value: typeof value === 'string' ? { type: 'value', value } : lowerSchema(value),
1246
+ }));
1247
+ properties.push({
1248
+ key: { type: 'value', value: variantField },
1249
+ value: { type: 'value', value: variantShapeValue['kind'] },
1250
+ });
1251
+ return {
1252
+ type: 'object',
1253
+ properties,
1254
+ };
1255
+ };
1256
+ const schemaLowerers = {
1257
+ string: lowerSimpleValue,
1258
+ number: lowerSimpleValue,
1259
+ boolean: lowerSimpleValue,
1260
+ unknown: lowerSimpleValue,
1261
+ component: lowerSimpleValue,
1262
+ command: lowerSimpleValue,
1263
+ componentSchema: lowerSimpleValue,
1264
+ object: lowerObjectOrMap,
1265
+ map: lowerObjectOrMap,
1266
+ array: lowerArrayOrSet,
1267
+ set: lowerArrayOrSet,
1268
+ shape: lowerShape,
1269
+ variantShape: lowerVariantShape,
1270
+ };
1271
+ function lowerSchema(schema) {
1272
+ // If schema has a computation, lower it
1273
+ if (schema.computation) {
1274
+ return lowerComputationSchema(schema.computation);
1073
1275
  }
1074
- node.markActivated();
1075
- }
1076
-
1077
- function execute$T(ctx, selector, item) {
1078
- ComponentTreeApi.appendArrayItem(ctx.componentId, selector, item);
1079
- const arrayNode = ComponentTreeApi.findNode(ctx.componentId, selector);
1080
- if (arrayNode) {
1081
- // Activate the last item in the array (the one we just appended)
1082
- const lastIndex = (arrayNode.structure?.length ?? 1) - 1;
1083
- const newItem = arrayNode.structure?.[lastIndex];
1084
- if (newItem) {
1085
- activateNode(newItem);
1086
- }
1276
+ // Otherwise lower the value based on valueType
1277
+ const lowerer = schemaLowerers[schema.valueType.type];
1278
+ if (lowerer) {
1279
+ return lowerer(schema);
1087
1280
  }
1281
+ throw new Error(`Unsupported value type: ${schema.valueType.type}`);
1088
1282
  }
1089
1283
 
1090
- var appendArrayItem = /*#__PURE__*/Object.freeze({
1091
- __proto__: null,
1092
- execute: execute$T
1093
- });
1094
-
1095
- function execute$S(ctx, selector, path) {
1096
- return ComponentTreeApi.collectValuesArray(ctx.componentId, selector, path);
1097
- }
1098
-
1099
- var collectValuesArray$1 = /*#__PURE__*/Object.freeze({
1100
- __proto__: null,
1101
- execute: execute$S
1102
- });
1103
-
1104
- function execute$R(ctx, selector, objectKey, objectValue) {
1105
- return ComponentTreeApi.collectValuesMap(ctx.componentId, selector, objectKey, objectValue);
1284
+ function lowerForLoop(forComputation) {
1285
+ const dimensionsNode = {
1286
+ type: 'array',
1287
+ items: forComputation.dimensions.map((dimension) => {
1288
+ // Extract trackBy expression to be evaluated in RefSpace context where $item is available
1289
+ let trackByExpression = null;
1290
+ if (dimension.trackBy.computation && dimension.trackBy.computation.computationType === 'formula') {
1291
+ trackByExpression = `=${dimension.trackBy.computation.formula}`;
1292
+ }
1293
+ else if (dimension.trackBy.value !== null) {
1294
+ trackByExpression = dimension.trackBy.value;
1295
+ }
1296
+ return {
1297
+ type: 'object',
1298
+ properties: [
1299
+ { key: { type: 'value', value: 'items' }, value: lowerSchema(dimension.items) },
1300
+ { key: { type: 'value', value: 'itemsType' }, value: { type: 'value', value: dimension.itemsType } },
1301
+ { key: { type: 'value', value: 'item' }, value: lowerSchema(dimension.item) },
1302
+ { key: { type: 'value', value: 'index' }, value: lowerSchema(dimension.index) },
1303
+ { key: { type: 'value', value: 'first' }, value: lowerSchema(dimension.first) },
1304
+ { key: { type: 'value', value: 'last' }, value: lowerSchema(dimension.last) },
1305
+ { key: { type: 'value', value: 'trackBy' }, value: { type: 'value', value: trackByExpression } },
1306
+ ],
1307
+ };
1308
+ }),
1309
+ };
1310
+ const yieldNode = lowerSchema(forComputation.yield);
1311
+ return {
1312
+ type: 'function',
1313
+ name: 'repeat',
1314
+ args: [dimensionsNode, yieldNode],
1315
+ };
1106
1316
  }
1107
-
1108
- var collectValuesMap$1 = /*#__PURE__*/Object.freeze({
1109
- __proto__: null,
1110
- execute: execute$R
1111
- });
1112
-
1113
- function execute$Q(_ctx, ms) {
1114
- return new Promise((resolve) => setTimeout(() => resolve(), ms));
1317
+ function lowerTemplate(templateComputation) {
1318
+ return {
1319
+ type: 'value',
1320
+ value: templateComputation,
1321
+ };
1115
1322
  }
1116
-
1117
- var delay$1 = /*#__PURE__*/Object.freeze({
1118
- __proto__: null,
1119
- execute: execute$Q
1120
- });
1121
-
1122
- function execute$P(ctx, selector) {
1123
- return ComponentTreeApi.findNode(ctx.componentId, selector)?.extractedValue ?? null;
1323
+ function lowerIfStatement(ifNode) {
1324
+ return {
1325
+ type: 'function',
1326
+ name: 'if',
1327
+ args: [lowerSchema(ifNode.if), lowerSchema(ifNode.then), lowerSchema(ifNode.else)],
1328
+ };
1124
1329
  }
1125
-
1126
- var get = /*#__PURE__*/Object.freeze({
1127
- __proto__: null,
1128
- execute: execute$P
1129
- });
1130
-
1131
- function execute$O(ctx, selector, index) {
1132
- ComponentTreeApi.removeArrayItem(ctx.componentId, selector, index);
1330
+ function lowerSwitch(switchComputation) {
1331
+ // Lower the source
1332
+ const sourceEvalNode = lowerSchema(switchComputation.source);
1333
+ // Build cases array as flat array: [value1, result1, value2, result2, ...]
1334
+ // Extract .value from ComponentSchemaNodeSchema to get the actual component schema
1335
+ const casesArrayItems = [];
1336
+ for (const caseItem of switchComputation.cases) {
1337
+ casesArrayItems.push({ type: 'value', value: caseItem.equals });
1338
+ // caseItem.then is a ComponentSchemaNodeSchema, extract its .value property
1339
+ casesArrayItems.push({ type: 'value', value: caseItem.then.value });
1340
+ }
1341
+ const casesArrayNode = {
1342
+ type: 'array',
1343
+ items: casesArrayItems,
1344
+ };
1345
+ // Lower the default value - extract .value from ComponentSchemaNodeSchema
1346
+ const defaultNode = { type: 'value', value: switchComputation.default.value };
1347
+ return {
1348
+ type: 'function',
1349
+ name: 'switch',
1350
+ args: [sourceEvalNode, casesArrayNode, defaultNode],
1351
+ };
1133
1352
  }
1134
-
1135
- var removeArrayItem = /*#__PURE__*/Object.freeze({
1136
- __proto__: null,
1137
- execute: execute$O
1138
- });
1139
-
1140
- function execute$N(ctx, selector, ...args) {
1141
- return ComponentTreeApi.runCommandNode(ctx.componentId, selector, ...args);
1353
+ const computationLowerers = {
1354
+ formula: (schema) => lowerFormula(schema.formula),
1355
+ for: (schema) => lowerForLoop(schema),
1356
+ template: (schema) => lowerTemplate(schema),
1357
+ if: (schema) => lowerIfStatement(schema),
1358
+ switch: (schema) => lowerSwitch(schema),
1359
+ };
1360
+ function lowerComputationSchema(computationSchema) {
1361
+ const lowerer = computationLowerers[computationSchema.computationType];
1362
+ if (lowerer) {
1363
+ return lowerer(computationSchema);
1364
+ }
1365
+ throw new Error(`Unsupported computation type: ${computationSchema.computationType}`);
1142
1366
  }
1143
1367
 
1144
- var run = /*#__PURE__*/Object.freeze({
1145
- __proto__: null,
1146
- execute: execute$N
1147
- });
1148
-
1149
- function execute$M(ctx, componentSelector, methodName, ...args) {
1150
- const component = ComponentTreeApi.getComponent(ctx.componentId, componentSelector);
1151
- const instance = componentInstanceMap.get(component.id);
1152
- if (!instance) {
1153
- throw new Error(`Cannot find component instance for component with id "${component.id}"`);
1368
+ function activateForComputation(node) {
1369
+ const store = ComponentStore.getInstance();
1370
+ const currentFrame = node.computationStack.top;
1371
+ if (!currentFrame) {
1372
+ throw new Error('Cannot activate for computation: no current frame');
1154
1373
  }
1155
- const method = instance[methodName];
1156
- if (!method || typeof method !== 'function') {
1157
- throw new Error(`Cannot find method "${methodName}" in component of type "${component.componentType}"`);
1374
+ const evalNode = lowerComputationSchema(currentFrame.schema);
1375
+ if (!node.position.componentId) {
1376
+ throw new Error('Node does not have a component context');
1158
1377
  }
1159
- return method.call(instance, ...args);
1378
+ const { computed, disposers } = evaluateNode(evalNode, {
1379
+ componentId: node.position.componentId,
1380
+ });
1381
+ currentFrame.disposers.push(...disposers);
1382
+ const computationDisposer = reaction(() => computed.get(), ({ value: newItems }) => {
1383
+ const toAdd = store.reconcileForLoopChildren(node, newItems);
1384
+ // Activate only newly added children
1385
+ const structure = node.structure || [];
1386
+ for (const index of toAdd) {
1387
+ const child = structure[index];
1388
+ if (child) {
1389
+ activateNode(child);
1390
+ }
1391
+ }
1392
+ }, { fireImmediately: true, name: debugName(node.position, 'repeatable-reaction') });
1393
+ currentFrame.disposers.push(computationDisposer);
1394
+ currentFrame.markActivated();
1160
1395
  }
1161
1396
 
1162
- var runComponentMethod = /*#__PURE__*/Object.freeze({
1163
- __proto__: null,
1164
- execute: execute$M
1165
- });
1166
-
1167
- function execute$L(ctx, selector, value) {
1168
- ComponentTreeApi.setNodeRawSchema(ctx.componentId, selector, value);
1169
- const node = ComponentTreeApi.findNode(ctx.componentId, selector);
1170
- if (node) {
1171
- activateStructure(node);
1397
+ function activateArrayStructure(node) {
1398
+ const structure = node.structure;
1399
+ if (structure === null) {
1400
+ return;
1172
1401
  }
1402
+ structure.forEach((item) => {
1403
+ activateNode(item);
1404
+ });
1173
1405
  }
1174
1406
 
1175
- var set = /*#__PURE__*/Object.freeze({
1176
- __proto__: null,
1177
- execute: execute$L
1178
- });
1179
-
1180
- const commands = {
1181
- delay: delay$1,
1182
- // New names
1183
- collectValuesMap: collectValuesMap$1,
1184
- collectValuesArray: collectValuesArray$1,
1185
- // Legacy aliases for backwards compatibility
1186
- propObject: collectValuesMap$1,
1187
- propArray: collectValuesArray$1,
1188
- appendArrayItem,
1189
- removeArrayItem,
1190
- runComponentMethod,
1191
- get,
1192
- set,
1193
- run,
1194
- };
1195
-
1196
1407
  /**
1197
- * Variadic AND function with short-circuit evaluation.
1198
- * Stops at the first falsy value.
1408
+ * Activates node change handlers for a component by setting up MobX reactions.
1409
+ * Each handler watches a node selector and executes its command when the node value changes.
1199
1410
  */
1200
- const and = {
1201
- kind: 'reactive',
1202
- execute: (args) => {
1203
- if (args.length === 0) {
1204
- throw new Error('and requires at least 1 argument');
1205
- }
1206
- return computed(() => {
1207
- const state = new FunctionExecutionStateBuilder();
1208
- for (const argComputed of args) {
1209
- const argState = argComputed.get();
1210
- const earlyReturn = state.checkReady(argState);
1211
- if (earlyReturn)
1212
- return earlyReturn;
1213
- if (!argState.value) {
1214
- return state.success(false);
1215
- }
1216
- }
1217
- return state.success(true);
1411
+ function activateNodeChangeHandlers(component) {
1412
+ for (const handler of component.onNodeChange) {
1413
+ const disposer = reaction(() => component.getNodeValue(handler.selector), (nodeValue) => {
1414
+ const src = component.sourceUrl ? `[${component.sourceUrl}] ` : '';
1415
+ log.trace(`${src}onNodeChange "${handler.selector}" triggered on ${component.id}`);
1416
+ handler.command.viewModel.execute(nodeValue);
1417
+ }, {
1418
+ fireImmediately: true,
1218
1419
  });
1219
- },
1220
- signature: '(...unknown) => boolean',
1221
- };
1222
-
1223
- function execute$K(source, path) {
1224
- const takeDeep = (source, path) => path.reduce((current, step) => {
1225
- return current?.[step];
1226
- }, source);
1227
- return source.map((element) => takeDeep(element, path));
1228
- }
1229
- const arrayPluck = {
1230
- kind: 'imperative',
1231
- execute: execute$K,
1232
- signature: '(unknown{}[], string[]) => unknown',
1233
- };
1234
-
1235
- function execute$J(value, index) {
1236
- return value[index];
1237
- }
1238
- const at = {
1239
- kind: 'imperative',
1240
- execute: execute$J,
1241
- signature: '(unknown[], number) => unknown',
1242
- };
1420
+ component.handlerDisposers.push(disposer);
1421
+ }
1422
+ }
1243
1423
 
1244
- function execute$I(value) {
1245
- return Boolean(value);
1424
+ function activateComponentStructure(node) {
1425
+ const structure = node.structure;
1426
+ if (structure === null) {
1427
+ return;
1428
+ }
1429
+ const component = ComponentStore.getInstance().getComponentOrThrow(structure);
1430
+ activateComponent(component);
1431
+ }
1432
+ function activateComponent(component) {
1433
+ ComponentStore.getInstance().componentsToPreload.add(component.componentType);
1434
+ for (const node of component.props.values()) {
1435
+ activateNode(node);
1436
+ }
1437
+ for (const node of component.variables.values()) {
1438
+ activateNode(node);
1439
+ }
1440
+ for (const handler of component.onNodeChange) {
1441
+ activateNode(handler.command);
1442
+ }
1443
+ activateNodeChangeHandlers(component);
1246
1444
  }
1247
- const bool = {
1248
- kind: 'imperative',
1249
- execute: execute$I,
1250
- signature: '(unknown) => boolean',
1251
- };
1252
1445
 
1253
- function execute$H(value) {
1254
- return Math.ceil(value);
1446
+ function activateMapStructure(node) {
1447
+ const structure = node.structure;
1448
+ if (structure === null) {
1449
+ return;
1450
+ }
1451
+ for (const item of Object.values(structure)) {
1452
+ activateNode(item);
1453
+ }
1255
1454
  }
1256
- const ceil = {
1257
- kind: 'imperative',
1258
- execute: execute$H,
1259
- signature: '(number) => number',
1260
- };
1261
1455
 
1262
- function execute$G(ctx, selector, path) {
1263
- return ComponentTreeApi.collectValuesArray(ctx.componentId, selector, path);
1456
+ function activateObjectStructure(node) {
1457
+ const structure = node.structure;
1458
+ if (structure === null) {
1459
+ return;
1460
+ }
1461
+ for (const node of Object.values(structure)) {
1462
+ activateNode(node);
1463
+ }
1264
1464
  }
1265
- const collectValuesArray = {
1266
- kind: 'imperative',
1267
- contextual: true,
1268
- execute: execute$G,
1269
- signature: '(string, string) => unknown[]',
1270
- };
1271
1465
 
1272
- function execute$F(ctx, selector, objectKey, objectValue) {
1273
- return ComponentTreeApi.collectValuesMap(ctx.componentId, selector, objectKey, objectValue);
1466
+ function activateShapeStructure(node) {
1467
+ const { valueType, structure } = node;
1468
+ if (structure === null) {
1469
+ return;
1470
+ }
1471
+ const objectValueType = DefinitionStore.getInstance().getShape(valueType.shapeType);
1472
+ for (const key of Object.keys(objectValueType.fields)) {
1473
+ activateNode(structure[key]);
1474
+ }
1274
1475
  }
1275
- const collectValuesMap = {
1276
- kind: 'imperative',
1277
- contextual: true,
1278
- execute: execute$F,
1279
- signature: '(string, string, string) => unknown{}',
1280
- };
1281
1476
 
1282
- function execute$E(strings, separator = '') {
1283
- return strings.join(separator);
1477
+ function activateVariantShapeStructure(node) {
1478
+ const { valueType, structure } = node;
1479
+ if (structure === null) {
1480
+ return;
1481
+ }
1482
+ const objectValueType = DefinitionStore.getInstance().getVariantShape(valueType.shapeType, structure.kind);
1483
+ for (const key of Object.keys(objectValueType.fields)) {
1484
+ activateNode(structure.fields[key]);
1485
+ }
1284
1486
  }
1285
- const concat = {
1286
- kind: 'imperative',
1287
- execute: execute$E,
1288
- signature: '(unknown[], string) => string',
1289
- };
1290
1487
 
1291
- const execute$D = (value) => {
1292
- return new Promise((resolve) => setTimeout(() => resolve(value), 500));
1293
- };
1294
- const delay = {
1295
- kind: 'imperative',
1296
- execute: execute$D,
1297
- signature: '(unknown) => unknown',
1488
+ const structureActivators = {
1489
+ shape: (node) => activateShapeStructure(node),
1490
+ variantShape: (node) => activateVariantShapeStructure(node),
1491
+ array: (node) => activateArrayStructure(node),
1492
+ object: (node) => activateObjectStructure(node),
1493
+ map: (node) => activateMapStructure(node),
1494
+ component: (node) => activateComponentStructure(node),
1298
1495
  };
1299
-
1300
- function execute$C(num1, num2) {
1301
- return num1 / num2;
1496
+ function activateStructure(node) {
1497
+ const value = node.structure;
1498
+ if (value === null) {
1499
+ return;
1500
+ }
1501
+ const activator = structureActivators[node.nodeType];
1502
+ if (activator) {
1503
+ activator(node);
1504
+ }
1302
1505
  }
1303
- const divide = {
1304
- kind: 'imperative',
1305
- execute: execute$C,
1306
- signature: '(number, number) => number',
1307
- };
1308
1506
 
1309
- function execute$B(items) {
1310
- return items.length === 0;
1507
+ function activateFormulaComputation(node) {
1508
+ const store = ComponentStore.getInstance();
1509
+ const currentFrame = node.computationStack.top;
1510
+ if (!currentFrame) {
1511
+ throw new Error('Cannot activate formula computation: no current frame');
1512
+ }
1513
+ const thisFrameDepth = node.computationStack.depth;
1514
+ const evalNode = lowerComputationSchema(currentFrame.schema);
1515
+ if (!node.position.componentId) {
1516
+ throw new Error('Node does not have a component context');
1517
+ }
1518
+ const component = getComponent(node);
1519
+ const { computed, disposers } = evaluateNode(evalNode, {
1520
+ componentId: node.position.componentId,
1521
+ sourceUrl: component.sourceUrl ?? undefined,
1522
+ });
1523
+ currentFrame.disposers.push(...disposers);
1524
+ const computationDisposer = reaction(() => computed.get(), (computationState) => {
1525
+ const { evaluating, value: rawSchema, failed } = computationState;
1526
+ node.evaluating = evaluating;
1527
+ if (evaluating) {
1528
+ return;
1529
+ }
1530
+ node.failed = failed;
1531
+ // Clear result frames from this depth onward (not all results!)
1532
+ // Example: if this is depth 2, clear frames 2, 3, 4... but keep 0, 1
1533
+ node.computationStack.popFrom(thisFrameDepth);
1534
+ store.setNodeRawSchema(node, rawSchema);
1535
+ if (node.computationStack.depth > thisFrameDepth) {
1536
+ // If a nested computation was pushed onto the stack, activate it
1537
+ activateComputation(node);
1538
+ }
1539
+ else {
1540
+ activateStructure(node);
1541
+ }
1542
+ }, {
1543
+ fireImmediately: true,
1544
+ name: debugName(node.position, 'formula-reaction'),
1545
+ });
1546
+ currentFrame.disposers.push(computationDisposer);
1547
+ currentFrame.markActivated();
1311
1548
  }
1312
- const empty = {
1313
- kind: 'imperative',
1314
- execute: execute$B,
1315
- signature: '(unknown[]) => boolean',
1316
- };
1317
1549
 
1318
- function execute$A(object) {
1319
- return Object.keys(object).length === 0;
1550
+ function activateTemplateComputation(node) {
1551
+ const store = ComponentStore.getInstance();
1552
+ const nodePath = node.position.path;
1553
+ const component = getComponent(node);
1554
+ const currentFrame = node.computationStack.top;
1555
+ if (!currentFrame) {
1556
+ throw new Error('Cannot activate template computation: no current frame');
1557
+ }
1558
+ const { defer, path, contractVariables, ref } = currentFrame.schema;
1559
+ const taskKey = `template-${component.id}-${nodePath}`;
1560
+ if (defer) {
1561
+ templateLoadingTracker.startLoading(taskKey);
1562
+ store.setNodeSchema(node, defer.placeholder);
1563
+ // Fire-and-forget: load template asynchronously
1564
+ loadTemplates({ templateUrl: path })
1565
+ .then(() => {
1566
+ const cacheEntry = templateRegistry.get(path);
1567
+ if (!cacheEntry) {
1568
+ // Loading failed - show error schema
1569
+ store.setNodeSchema(node, defer.error);
1570
+ templateLoadingTracker.finishLoading(taskKey);
1571
+ return;
1572
+ }
1573
+ // Loading succeeded - create and set the template schema
1574
+ setTemplateOnNode(node, cacheEntry, path, contractVariables, ref);
1575
+ templateLoadingTracker.finishLoading(taskKey);
1576
+ })
1577
+ .catch((error) => {
1578
+ // Handle any unhandled errors during template creation
1579
+ log.error(`Failed to activate template "${path}":`, error);
1580
+ store.setNodeSchema(node, defer.error);
1581
+ templateLoadingTracker.finishLoading(taskKey);
1582
+ });
1583
+ // Mark as activated immediately (don't wait for loading)
1584
+ currentFrame.markActivated();
1585
+ return;
1586
+ }
1587
+ const cacheEntry = templateRegistry.get(path);
1588
+ if (!cacheEntry) {
1589
+ const src = component.sourceUrl ? `[${component.sourceUrl}] ` : '';
1590
+ throw new Error(`${src}Template not found in cache: ${path}`);
1591
+ }
1592
+ // Non-defer path: template is already loaded, create schema synchronously
1593
+ setTemplateOnNode(node, cacheEntry, path, contractVariables, ref);
1594
+ currentFrame.markActivated();
1320
1595
  }
1321
- const emptyObject = {
1322
- kind: 'imperative',
1323
- execute: execute$A,
1324
- signature: '(unknown{}) => boolean',
1325
- };
1326
-
1327
- const execute$z = (a, b) => a === b;
1328
- const eq = {
1329
- kind: 'imperative',
1330
- execute: execute$z,
1331
- signature: '(unknown, unknown) => boolean',
1332
- };
1333
-
1334
- function execute$y(value1, value2) {
1335
- return value1 === value2;
1596
+ function setTemplateOnNode(node, cacheEntry, path, contractVariables, ref) {
1597
+ const store = ComponentStore.getInstance();
1598
+ const rawFacadeSchema = createRawFacadeSchema(cacheEntry, path, contractVariables, ref);
1599
+ store.setNodeRawSchema(node, rawFacadeSchema);
1600
+ // Activate the facade component after it's created (only for regular component nodes)
1601
+ if (node.nodeType !== 'componentSchema') {
1602
+ const facadeId = node.structure;
1603
+ const facade = store.getComponentOrThrow(facadeId);
1604
+ facade.sourceUrl = path;
1605
+ activateComponent(facade);
1606
+ }
1336
1607
  }
1337
- const equal = {
1338
- kind: 'imperative',
1339
- execute: execute$y,
1340
- signature: '(unknown, unknown) => boolean',
1341
- };
1342
-
1343
- function execute$x(ctx, selector) {
1344
- return ComponentTreeApi.getNode(ctx.componentId, selector).evaluating;
1608
+ function createRawFacadeSchema(cacheEntry, path, contractVariables, ref) {
1609
+ if (!cacheEntry) {
1610
+ throw new Error(`Template cache entry is undefined for path: ${path}`);
1611
+ }
1612
+ const { recipe } = cacheEntry;
1613
+ const { types: _types, importTypes: _importTypes, contract = {}, defaultSlot, ...componentRecipe } = recipe;
1614
+ const rootSchema = unfoldNodeSchema(componentRecipe, { type: 'component' });
1615
+ const rootValue = rootSchema.value;
1616
+ if (!rootValue) {
1617
+ const recipeKeys = Object.keys(componentRecipe).join(', ');
1618
+ throw new Error(`Template "${path}": Root schema has no value. ` +
1619
+ `The recipe root must define a component (implicit or explicit componentType). ` +
1620
+ `Found keys: [${recipeKeys}]`);
1621
+ }
1622
+ // Collect value types from the root schema for each mapped variable
1623
+ const valueTypes = {};
1624
+ for (const [facadeKey, rootKey] of Object.entries(contract)) {
1625
+ const sourceNodeSchema = getSourceNodeOrThrow(rootValue, rootKey, path);
1626
+ valueTypes[facadeKey] = sourceNodeSchema.valueType;
1627
+ }
1628
+ const prefixedVariables = createFacadeVariables(path, contractVariables, contract, valueTypes, defaultSlot);
1629
+ const contentWithBindings = addSynchronizeBindings(contract, rootSchema);
1630
+ // Return the raw facade schema object
1631
+ return {
1632
+ componentType: 'sys.Facade',
1633
+ ref,
1634
+ content: contentWithBindings,
1635
+ ...prefixedVariables,
1636
+ };
1345
1637
  }
1346
- const evaluating = {
1347
- kind: 'imperative',
1348
- contextual: true,
1349
- execute: execute$x,
1350
- signature: '(string) => boolean',
1351
- };
1352
-
1353
- function execute$w(items) {
1354
- return items.every((item) => !!item);
1638
+ function createFacadeVariables(templatePath, contractVariables, contract, valueTypes, flatWrapperSlot) {
1639
+ const contractKeys = Object.keys(contractVariables);
1640
+ const contractPropKeys = Object.keys(contract);
1641
+ const unmappedVariables = contractKeys.filter((key) => key !== 'slot' && !contractPropKeys.includes(key));
1642
+ if (unmappedVariables.length > 0) {
1643
+ // Detect if unmapped keys look like variable declarations (have $ prefix or @ type annotation)
1644
+ const variableDeclarations = unmappedVariables.filter((key) => key.startsWith('$') || key.includes('@'));
1645
+ const regularProps = unmappedVariables.filter((key) => !key.startsWith('$') && !key.includes('@'));
1646
+ if (variableDeclarations.length > 0) {
1647
+ throw new Error(`Template "${templatePath}": Variable declarations cannot be siblings of templateUrl. ` +
1648
+ `Found: ${variableDeclarations.join(', ')}. ` +
1649
+ `Pass values directly or wrap templateUrl in a component.`);
1650
+ }
1651
+ else if (regularProps.length > 0) {
1652
+ log.warn(`Template "${templatePath}": Unknown properties: ${regularProps.join(', ')}. ` +
1653
+ `Expected: ${contractPropKeys.join(', ')}`);
1654
+ }
1655
+ }
1656
+ const prefixedVariables = {};
1657
+ for (const facadeKey of contractPropKeys) {
1658
+ const prefixedKey = `${Delimiters.Variable}${facadeKey}`;
1659
+ prefixedVariables[prefixedKey] = unfoldNodeSchema(contractVariables[facadeKey], valueTypes[facadeKey]);
1660
+ }
1661
+ if ('slot' in contractVariables) {
1662
+ const slotComponent = contractVariables['slot'];
1663
+ let targetSlot = flatWrapperSlot;
1664
+ // Find default component slot if not explicitly specified
1665
+ if (!targetSlot) {
1666
+ const componentSlots = Object.keys(valueTypes).filter((key) => valueTypes[key].type === 'component');
1667
+ if (componentSlots.length !== 1) {
1668
+ throw new Error(`Cannot find default slot for template computation. Found ${componentSlots.length} slots: ${componentSlots.join(', ')}`);
1669
+ }
1670
+ targetSlot = componentSlots[0];
1671
+ }
1672
+ const prefixedKey = `${Delimiters.Variable}${targetSlot}`;
1673
+ prefixedVariables[prefixedKey] = unfoldNodeSchema(slotComponent, valueTypes[targetSlot]);
1674
+ }
1675
+ return prefixedVariables;
1355
1676
  }
1356
- const every = {
1357
- kind: 'imperative',
1358
- execute: execute$w,
1359
- signature: '(unknown[]) => boolean',
1360
- };
1361
-
1362
- function execute$v(ctx, selector) {
1363
- return ComponentTreeApi.getNode(ctx.componentId, selector).failed;
1677
+ function addSynchronizeBindings(contract, rootSchema) {
1678
+ const rootValue = rootSchema.value;
1679
+ for (const [facadeKey, templateKey] of Object.entries(contract)) {
1680
+ // This will always succeed because mapping was already validated in createRawFacadeSchema
1681
+ const sourceNodeSchema = getSourceNodeOrThrow(rootValue, templateKey, '');
1682
+ sourceNodeSchema.extraBinding = {
1683
+ bindingType: 'mirror',
1684
+ selector: `^sys.Facade->$${facadeKey}`,
1685
+ };
1686
+ }
1687
+ return rootSchema;
1364
1688
  }
1365
- const failed = {
1366
- kind: 'imperative',
1367
- contextual: true,
1368
- execute: execute$v,
1369
- signature: '(string) => boolean',
1370
- };
1371
-
1372
- function execute$u(array, allowedValues) {
1373
- if (!allowedValues) {
1374
- return [];
1689
+ function getSourceNodeOrThrow(rootValue, key, templatePath) {
1690
+ const isVariable = key.startsWith('$');
1691
+ const source = isVariable ? rootValue.variables : rootValue.props;
1692
+ if (!source) {
1693
+ const kind = isVariable ? 'variables' : 'props';
1694
+ throw new Error(`Template "${templatePath}": Cannot resolve contract key "${key}" — ` +
1695
+ `root component "${rootValue.componentType}" has no ${kind}. ` +
1696
+ `Ensure the template recipe declares "${key}" as a ${isVariable ? 'variable' : 'property'}.`);
1375
1697
  }
1376
- if (allowedValues instanceof Set) {
1377
- return array.filter((item) => allowedValues.has(item));
1698
+ const node = source.get(isVariable ? key.slice(1) : key);
1699
+ if (!node) {
1700
+ const kind = isVariable ? 'variable' : 'property';
1701
+ const lookupKey = isVariable ? key.slice(1) : key;
1702
+ const available = [...source.keys()].map((k) => (isVariable ? `$${k}` : k));
1703
+ throw new Error(`Template "${templatePath}": Contract key "${key}" not found as ${kind} "${lookupKey}" ` +
1704
+ `in root component "${rootValue.componentType}". ` +
1705
+ `Available ${kind}s: [${available.join(', ')}]`);
1378
1706
  }
1379
- return array.filter((item) => allowedValues.includes(item));
1380
- }
1381
- const filterIn = {
1382
- kind: 'imperative',
1383
- execute: execute$u,
1384
- signature: '(unknown[], unknown[]) => unknown[]',
1385
- };
1386
-
1387
- function execute$t(collection, key, values) {
1388
- return values
1389
- .map((value) => collection.find((element) => element[key] === value))
1390
- .filter((item) => item !== undefined);
1707
+ return node;
1391
1708
  }
1392
- const findAllBy = {
1393
- kind: 'imperative',
1394
- execute: execute$t,
1395
- signature: '(unknown{}[], string, unknown[]) => unknown{}[]',
1396
- };
1397
1709
 
1398
- function execute$s(collection, key, value) {
1399
- const found = collection.find((element) => element[key] === value);
1400
- return found || null;
1401
- }
1402
- const findBy = {
1403
- kind: 'imperative',
1404
- execute: execute$s,
1405
- signature: '(unknown{}[], string, unknown) => unknown',
1710
+ const computationActivators = {
1711
+ if: (node) => activateFormulaComputation(node),
1712
+ formula: (node) => activateFormulaComputation(node),
1713
+ switch: (node) => activateFormulaComputation(node),
1714
+ for: (node) => activateForComputation(node),
1715
+ template: (node) => activateTemplateComputation(node),
1406
1716
  };
1407
-
1408
- function execute$r(array) {
1409
- return array.flat();
1717
+ function activateComputation(node) {
1718
+ const frame = node.computationStack.top;
1719
+ if (!frame) {
1720
+ return;
1721
+ }
1722
+ // Idempotency: don't re-activate an already activated frame
1723
+ if (frame.activated) {
1724
+ return;
1725
+ }
1726
+ const activator = computationActivators[frame.schema.computationType];
1727
+ if (activator) {
1728
+ activator(node);
1729
+ }
1410
1730
  }
1411
- const flatten = {
1412
- kind: 'imperative',
1413
- execute: execute$r,
1414
- signature: '(unknown[][]) => unknown[]',
1415
- };
1416
1731
 
1417
- function execute$q(value) {
1418
- return Math.floor(value);
1732
+ function activateNode(node) {
1733
+ if (node.fullyActivated) {
1734
+ return;
1735
+ }
1736
+ activateStructure(node);
1737
+ if (!node.computationStack.isEmpty) {
1738
+ activateComputation(node);
1739
+ }
1740
+ if (node.bindingSchema) {
1741
+ void addBinding(node);
1742
+ }
1743
+ if (node.extraBinding) {
1744
+ void addBinding(node, true);
1745
+ }
1746
+ node.markActivated();
1419
1747
  }
1420
- const floor = {
1421
- kind: 'imperative',
1422
- execute: execute$q,
1423
- signature: '(number) => number',
1424
- };
1425
1748
 
1426
- function createFailedAsyncValue(error) {
1427
- return {
1428
- kind: 'promise',
1429
- current: null,
1430
- error: error instanceof Error ? error : new Error('Node lookup failed'),
1431
- pending: false,
1432
- };
1433
- }
1434
- function warnNodeNotFound(selector) {
1435
- const baseName = selector.split('.')[0];
1436
- const isVariable = baseName.startsWith('$');
1437
- const name = isVariable ? baseName.slice(1) : baseName;
1438
- const type = isVariable ? 'Variable' : 'Property';
1439
- log.trace(`${type} "${name}" not found`);
1749
+ const compiledCache = new WeakMap();
1750
+ function getOrCompile(structure, key) {
1751
+ let cached = compiledCache.get(structure);
1752
+ if (!cached) {
1753
+ cached = {};
1754
+ compiledCache.set(structure, cached);
1755
+ }
1756
+ if (!cached[key]) {
1757
+ cached[key] = createJsFunction(structure[key]);
1758
+ }
1759
+ return cached[key];
1440
1760
  }
1441
- const getFn = {
1442
- kind: 'reactive',
1443
- contextual: true,
1444
- execute: (ctx, args) => {
1445
- return computed(() => {
1446
- const state = new FunctionExecutionStateBuilder();
1447
- // Check if selector argument is ready
1448
- const selectorState = args[0].get();
1449
- const earlyReturn = state.checkReady(selectorState);
1450
- if (earlyReturn)
1451
- return earlyReturn;
1452
- const selector = selectorState.value;
1453
- const isOptional = selector.includes('?->') || selector.includes('?.');
1454
- // Try to find the target node
1455
- let targetNode;
1456
- try {
1457
- targetNode = ComponentTreeApi.findNode(ctx.componentId, selector);
1458
- }
1459
- catch (error) {
1460
- // Warn about non-existing variables/properties (only for required selectors)
1461
- if (!isOptional && error instanceof Error && error.message.includes('not found')) {
1462
- warnNodeNotFound(selector);
1463
- }
1464
- // Optional selectors return null, required selectors fail with AsyncValue
1465
- return state.success(isOptional ? null : createFailedAsyncValue(error));
1761
+ async function runCommand(node, ...args) {
1762
+ const structure = node.structure;
1763
+ if (!structure) {
1764
+ const comp = node.position.componentId
1765
+ ? ComponentStore.getInstance().getComponent(node.position.componentId)
1766
+ : null;
1767
+ const src = comp?.sourceUrl ? `[${comp.sourceUrl}] ` : '';
1768
+ log.trace(`${src}Command is empty — no handler was bound to it.`);
1769
+ return null;
1770
+ }
1771
+ const component = ComponentStore.getInstance().getComponentOrThrow(structure.owner);
1772
+ const src = component.sourceUrl ? `[${component.sourceUrl}] ` : '';
1773
+ const functionsProxy = proxifySubCommands(component);
1774
+ try {
1775
+ if (structure.init) {
1776
+ log.trace(`${src}Running init hook`);
1777
+ const initFunc = getOrCompile(structure, 'init');
1778
+ await initFunc(functionsProxy, args);
1779
+ }
1780
+ if (structure.runnerType === 'inline') {
1781
+ log.trace(`${src}Executing inline callback (${args.length} args)`);
1782
+ return await structure.target(...args);
1783
+ }
1784
+ const preview = structure.target.slice(0, 80);
1785
+ log.trace(`${src}Executing command: ${preview}`);
1786
+ const targetFunc = getOrCompile(structure, 'target');
1787
+ return await targetFunc(functionsProxy, args);
1788
+ }
1789
+ catch (e) {
1790
+ throw new Error(`${src}Error executing ${structure.runnerType === 'inline' ? 'inline callback' : 'command'}`, {
1791
+ cause: e,
1792
+ });
1793
+ }
1794
+ finally {
1795
+ if (structure.finalize) {
1796
+ try {
1797
+ log.trace(`${src}Running finalize hook`);
1798
+ const finalizeFunc = getOrCompile(structure, 'finalize');
1799
+ await finalizeFunc(functionsProxy, args);
1466
1800
  }
1467
- // Handle case where findNode returns null (e.g., optional component selector, or traversing missing path)
1468
- if (!targetNode) {
1469
- return state.success(isOptional ? null : createFailedAsyncValue(new Error(`Node not found: ${selector}`)));
1801
+ catch (finalizeError) {
1802
+ log.error(`${src}Error in finalize hook`, finalizeError);
1470
1803
  }
1471
- // Wrap node value in AsyncValue if needed to propagate evaluating/failed state
1472
- if (targetNode.evaluating || targetNode.failed) {
1473
- const asyncValue = {
1474
- kind: 'promise',
1475
- current: targetNode.extractedValue,
1476
- error: targetNode.failed ? new Error('Target node failed') : null,
1477
- pending: targetNode.evaluating,
1804
+ }
1805
+ }
1806
+ }
1807
+ function proxifySubCommands(component) {
1808
+ return new Proxy({}, {
1809
+ get(target, functionName) {
1810
+ return (...functionArgs) => {
1811
+ let execute;
1812
+ if (functionName in target) {
1813
+ execute = target[functionName];
1814
+ }
1815
+ else {
1816
+ const def = DefinitionStore.getInstance().getCommand(functionName);
1817
+ execute = target[functionName] = def.execute;
1818
+ }
1819
+ const commandCtx = {
1820
+ componentId: component.id,
1478
1821
  };
1479
- return state.success(asyncValue);
1480
- }
1481
- return state.success(targetNode.extractedValue);
1482
- });
1483
- },
1484
- signature: '(string) => unknown',
1485
- };
1486
-
1487
- const execute$p = (a, b) => a > b;
1488
- const gt = {
1489
- kind: 'imperative',
1490
- execute: execute$p,
1491
- signature: '(number, number) => boolean',
1492
- };
1493
-
1494
- const execute$o = (a, b) => a >= b;
1495
- const gte = {
1496
- kind: 'imperative',
1497
- execute: execute$o,
1498
- signature: '(number, number) => boolean',
1499
- };
1500
-
1501
- function execute$n(ctx, componentSelector) {
1502
- return ComponentTreeApi.findComponent(ctx.componentId, componentSelector)?.id ?? null;
1822
+ const result = execute(commandCtx, ...functionArgs);
1823
+ if (result instanceof Observable) {
1824
+ return firstValueFrom(result);
1825
+ }
1826
+ if (result instanceof Promise) {
1827
+ return result;
1828
+ }
1829
+ if (isComputed(result)) {
1830
+ return result.get();
1831
+ }
1832
+ return result;
1833
+ };
1834
+ },
1835
+ });
1503
1836
  }
1504
- const idFn = {
1505
- kind: 'imperative',
1506
- contextual: true,
1507
- execute: execute$n,
1508
- signature: '(string) => string',
1509
- };
1510
-
1511
1837
  /**
1512
- * Conditional branching with lazy evaluation.
1513
- * Only evaluates the selected branch.
1838
+ * Creates a JavaScript function from a string of code.
1839
+ *
1840
+ * SECURITY WARNING: This function uses `new Function()` to execute dynamic code.
1841
+ * Only use with schemas from trusted sources. Malicious schemas could execute
1842
+ * arbitrary code in the browser context, leading to XSS or data theft.
1843
+ *
1844
+ * The code has access to:
1845
+ * - All registered commands via destructured parameters
1846
+ * - Function arguments via the `args` parameter
1514
1847
  */
1515
- const ifFn = {
1516
- kind: 'reactive',
1517
- execute: (args) => {
1518
- if (args.length !== 3) {
1519
- throw new Error('if requires exactly 3 arguments: condition, trueValue, falseValue');
1520
- }
1521
- const [conditionComputed, trueComputed, falseComputed] = args;
1522
- return computed(() => {
1523
- const state = new FunctionExecutionStateBuilder();
1524
- const conditionState = conditionComputed.get();
1525
- const earlyReturn = state.checkReady(conditionState);
1526
- if (earlyReturn)
1527
- return earlyReturn;
1528
- const branchState = conditionState.value ? trueComputed.get() : falseComputed.get();
1529
- const branchEarlyReturn = state.checkReady(branchState);
1530
- if (branchEarlyReturn)
1531
- return branchEarlyReturn;
1532
- return state.success(branchState.value);
1533
- });
1534
- },
1535
- signature: '(unknown, unknown, unknown) => unknown',
1536
- };
1848
+ function createJsFunction(func) {
1849
+ const commandNames = DefinitionStore.getInstance().getCommandNames();
1850
+ const commandsStr = commandNames.length ? `{ ${commandNames.join(', ')} }` : 'commands';
1851
+ return new Function(commandsStr, 'args', `return (async () => { ${func} })();`);
1852
+ }
1537
1853
 
1538
- function execute$m(collection, value) {
1539
- if (collection instanceof Set) {
1540
- return collection.has(value);
1854
+ function execute$f(ctx, selector, item) {
1855
+ ComponentTreeApi.appendArrayItem(ctx.componentId, selector, item);
1856
+ const arrayNode = ComponentTreeApi.findNode(ctx.componentId, selector);
1857
+ if (arrayNode) {
1858
+ // Activate the last item in the array (the one we just appended)
1859
+ const lastIndex = (arrayNode.structure?.length ?? 1) - 1;
1860
+ const newItem = arrayNode.structure?.[lastIndex];
1861
+ if (newItem) {
1862
+ activateNode(newItem);
1863
+ }
1541
1864
  }
1542
- return collection.includes(value);
1543
1865
  }
1544
- const inFn = {
1545
- kind: 'imperative',
1546
- execute: execute$m,
1547
- signature: '(unknown[], unknown) => boolean',
1548
- };
1549
1866
 
1550
- function replacer(_key, value) {
1551
- if (value instanceof Set) {
1552
- return Array.from(value);
1553
- }
1554
- return value;
1867
+ var appendArrayItem = /*#__PURE__*/Object.freeze({
1868
+ __proto__: null,
1869
+ execute: execute$f
1870
+ });
1871
+
1872
+ function execute$e(ctx, selector) {
1873
+ return ComponentTreeApi.findNode(ctx.componentId, selector)?.extractedValue ?? null;
1555
1874
  }
1556
- function execute$l(arg) {
1557
- return JSON.stringify(arg, replacer);
1875
+
1876
+ var get = /*#__PURE__*/Object.freeze({
1877
+ __proto__: null,
1878
+ execute: execute$e
1879
+ });
1880
+
1881
+ function execute$d(ctx, selector, path) {
1882
+ return ComponentTreeApi.collectValuesArray(ctx.componentId, selector, path);
1558
1883
  }
1559
- const json = {
1560
- kind: 'imperative',
1561
- execute: execute$l,
1562
- signature: '(unknown) => string',
1563
- };
1564
1884
 
1565
- function execute$k(object) {
1566
- return Object.keys(object);
1885
+ var propArray$1 = /*#__PURE__*/Object.freeze({
1886
+ __proto__: null,
1887
+ execute: execute$d
1888
+ });
1889
+
1890
+ function execute$c(ctx, selector, objectKey, objectValue) {
1891
+ return ComponentTreeApi.collectValuesMap(ctx.componentId, selector, objectKey, objectValue);
1567
1892
  }
1568
- const keys = {
1569
- kind: 'imperative',
1570
- execute: execute$k,
1571
- signature: '(unknown{}) => string[]',
1572
- };
1573
1893
 
1574
- function execute$j(value) {
1575
- return value.length;
1894
+ var propObject$1 = /*#__PURE__*/Object.freeze({
1895
+ __proto__: null,
1896
+ execute: execute$c
1897
+ });
1898
+
1899
+ function execute$b(ctx, selector, index) {
1900
+ ComponentTreeApi.removeArrayItem(ctx.componentId, selector, index);
1576
1901
  }
1577
- const length = {
1578
- kind: 'imperative',
1579
- execute: execute$j,
1580
- signature: '(unknown[]) => number',
1581
- };
1582
1902
 
1583
- const execute$i = (a, b) => a < b;
1584
- const lt = {
1585
- kind: 'imperative',
1586
- execute: execute$i,
1587
- signature: '(number, number) => boolean',
1588
- };
1903
+ var removeArrayItem = /*#__PURE__*/Object.freeze({
1904
+ __proto__: null,
1905
+ execute: execute$b
1906
+ });
1589
1907
 
1590
- const execute$h = (a, b) => a <= b;
1591
- const lte = {
1592
- kind: 'imperative',
1593
- execute: execute$h,
1594
- signature: '(number, number) => boolean',
1595
- };
1908
+ function execute$a(ctx, selector, ...args) {
1909
+ return ComponentTreeApi.runCommandNode(ctx.componentId, selector, ...args);
1910
+ }
1596
1911
 
1597
- function execute$g(num, delta) {
1598
- return num - delta;
1912
+ var run = /*#__PURE__*/Object.freeze({
1913
+ __proto__: null,
1914
+ execute: execute$a
1915
+ });
1916
+
1917
+ function execute$9(ctx, componentSelector, methodName, ...args) {
1918
+ const component = ComponentTreeApi.getComponent(ctx.componentId, componentSelector);
1919
+ const instance = componentInstanceMap.get(component.id);
1920
+ if (!instance) {
1921
+ throw new Error(`Cannot find component instance for component with id "${component.id}"`);
1922
+ }
1923
+ const method = instance[methodName];
1924
+ if (!method || typeof method !== 'function') {
1925
+ throw new Error(`Cannot find method "${methodName}" in component of type "${component.componentType}"`);
1926
+ }
1927
+ return method.call(instance, ...args);
1599
1928
  }
1600
- const minus = {
1601
- kind: 'imperative',
1602
- execute: execute$g,
1603
- signature: '(number, number) => number',
1604
- };
1605
1929
 
1606
- function execute$f(num1, num2) {
1607
- return num1 * num2;
1930
+ var runComponentMethod = /*#__PURE__*/Object.freeze({
1931
+ __proto__: null,
1932
+ execute: execute$9
1933
+ });
1934
+
1935
+ function execute$8(ctx, selector, value) {
1936
+ ComponentTreeApi.setNodeRawSchema(ctx.componentId, selector, value);
1937
+ const node = ComponentTreeApi.findNode(ctx.componentId, selector);
1938
+ if (node) {
1939
+ activateStructure(node);
1940
+ }
1608
1941
  }
1609
- const multiply = {
1610
- kind: 'imperative',
1611
- execute: execute$f,
1612
- signature: '(number, number) => number',
1942
+
1943
+ var set = /*#__PURE__*/Object.freeze({
1944
+ __proto__: null,
1945
+ execute: execute$8
1946
+ });
1947
+
1948
+ const commands = {
1949
+ propObject: propObject$1,
1950
+ propArray: propArray$1,
1951
+ appendArrayItem,
1952
+ removeArrayItem,
1953
+ runComponentMethod,
1954
+ get,
1955
+ set,
1956
+ run,
1613
1957
  };
1614
1958
 
1615
- const execute$e = (a, b) => a !== b;
1616
- const neq = {
1617
- kind: 'imperative',
1618
- execute: execute$e,
1619
- signature: '(unknown, unknown) => boolean',
1959
+ const componentsDefinitions$2 = {
1960
+ 'sys.Box': {
1961
+ properties: {
1962
+ 'content@component': null,
1963
+ },
1964
+ },
1965
+ 'sys.Group': {
1966
+ properties: {
1967
+ 'children@component[]': [],
1968
+ },
1969
+ },
1970
+ 'sys.RefSpace': {
1971
+ traits: ['sys.Box'],
1972
+ properties: {},
1973
+ },
1974
+ 'sys.Facade': {
1975
+ traits: ['sys.Box'],
1976
+ properties: {},
1977
+ },
1620
1978
  };
1621
1979
 
1622
- function execute$d(value) {
1623
- return !value;
1980
+ function execute$7(ctx, selector) {
1981
+ return ComponentTreeApi.getNode(ctx.componentId, selector).evaluating;
1624
1982
  }
1625
- const not = {
1983
+ const evaluating = {
1626
1984
  kind: 'imperative',
1627
- execute: execute$d,
1628
- signature: '(unknown) => boolean',
1985
+ contextual: true,
1986
+ execute: execute$7,
1987
+ signature: '(string) => boolean',
1629
1988
  };
1630
1989
 
1631
- function execute$c(value) {
1632
- const number = Number(value);
1633
- if (Number.isNaN(number)) {
1634
- throw new Error(`Cannot convert value ${value} to number`);
1635
- }
1636
- return number;
1990
+ function execute$6(ctx, selector) {
1991
+ return ComponentTreeApi.getNode(ctx.componentId, selector).failed;
1637
1992
  }
1638
- const number = {
1993
+ const failed = {
1639
1994
  kind: 'imperative',
1640
- execute: execute$c,
1641
- signature: '(unknown) => number',
1995
+ contextual: true,
1996
+ execute: execute$6,
1997
+ signature: '(string) => boolean',
1642
1998
  };
1643
1999
 
1644
- function execute$b(parts) {
1645
- const result = {};
1646
- for (let i = 0; i < parts.length; i += 2) {
1647
- const key = parts[i];
1648
- if (typeof key !== 'string') {
1649
- throw new Error(`Invalid key type: ${typeof key}. Expected string`);
1650
- }
1651
- result[key] = parts[i + 1];
1652
- }
1653
- return result;
2000
+ function createFailedAsyncValue(error) {
2001
+ return {
2002
+ kind: 'promise',
2003
+ current: null,
2004
+ error: error instanceof Error ? error : new Error('Node lookup failed'),
2005
+ pending: false,
2006
+ };
1654
2007
  }
1655
- const objectFn = {
1656
- kind: 'imperative',
1657
- execute: execute$b,
1658
- signature: '(unknown[]) => unknown{}',
1659
- };
1660
-
1661
- /**
1662
- * Variadic OR function with short-circuit evaluation.
1663
- * Stops at the first truthy value.
1664
- */
1665
- const or = {
2008
+ function warnNodeNotFound(selector) {
2009
+ const baseName = selector.split('.')[0];
2010
+ const isVariable = baseName.startsWith('$');
2011
+ const name = isVariable ? baseName.slice(1) : baseName;
2012
+ const type = isVariable ? 'Variable' : 'Property';
2013
+ log.trace(`${type} "${name}" not found`);
2014
+ }
2015
+ const getFn = {
1666
2016
  kind: 'reactive',
1667
- execute: (args) => {
1668
- if (args.length === 0) {
1669
- throw new Error('or requires at least 1 argument');
1670
- }
2017
+ contextual: true,
2018
+ execute: (ctx, args) => {
1671
2019
  return computed(() => {
1672
2020
  const state = new FunctionExecutionStateBuilder();
1673
- for (const argComputed of args) {
1674
- const argState = argComputed.get();
1675
- const earlyReturn = state.checkReady(argState);
1676
- if (earlyReturn)
1677
- return earlyReturn;
1678
- if (argState.value) {
1679
- return state.success(true);
2021
+ // Check if selector argument is ready
2022
+ const selectorState = args[0].get();
2023
+ const earlyReturn = state.checkReady(selectorState);
2024
+ if (earlyReturn)
2025
+ return earlyReturn;
2026
+ const selector = selectorState.value;
2027
+ const isOptional = selector.includes('?->') || selector.includes('?.');
2028
+ // Try to find the target node
2029
+ let targetNode;
2030
+ try {
2031
+ targetNode = ComponentTreeApi.findNode(ctx.componentId, selector);
2032
+ }
2033
+ catch (error) {
2034
+ // Warn about non-existing variables/properties (only for required selectors)
2035
+ if (!isOptional && error instanceof Error && error.message.includes('not found')) {
2036
+ warnNodeNotFound(selector);
1680
2037
  }
2038
+ // Optional selectors return null, required selectors fail with AsyncValue
2039
+ return state.success(isOptional ? null : createFailedAsyncValue(error));
1681
2040
  }
1682
- return state.success(false);
2041
+ // Handle case where findNode returns null (e.g., optional component selector, or traversing missing path)
2042
+ if (!targetNode) {
2043
+ return state.success(isOptional ? null : createFailedAsyncValue(new Error(`Node not found: ${selector}`)));
2044
+ }
2045
+ // Wrap node value in AsyncValue if needed to propagate evaluating/failed state
2046
+ if (targetNode.evaluating || targetNode.failed) {
2047
+ const asyncValue = {
2048
+ kind: 'promise',
2049
+ current: targetNode.extractedValue,
2050
+ error: targetNode.failed ? new Error('Target node failed') : null,
2051
+ pending: targetNode.evaluating,
2052
+ };
2053
+ return state.success(asyncValue);
2054
+ }
2055
+ return state.success(targetNode.extractedValue);
1683
2056
  });
1684
2057
  },
1685
- signature: '(...unknown) => boolean',
2058
+ signature: '(string) => unknown',
1686
2059
  };
1687
2060
 
1688
- function execute$a(source, path) {
1689
- return path.reduce((current, step) => {
1690
- return current?.[step];
1691
- }, source);
2061
+ function execute$5(ctx, componentSelector) {
2062
+ return ComponentTreeApi.findComponent(ctx.componentId, componentSelector)?.id ?? null;
1692
2063
  }
1693
- const pluck = {
2064
+ const idFn = {
1694
2065
  kind: 'imperative',
1695
- execute: execute$a,
1696
- signature: '(unknown{}, string[]) => unknown',
2066
+ contextual: true,
2067
+ execute: execute$5,
2068
+ signature: '(string) => string',
1697
2069
  };
1698
2070
 
1699
- function execute$9(a, b) {
1700
- return a + b;
2071
+ function execute$4(ctx, selector, path) {
2072
+ return ComponentTreeApi.collectValuesArray(ctx.componentId, selector, path);
1701
2073
  }
1702
- const plus = {
2074
+ const propArray = {
1703
2075
  kind: 'imperative',
1704
- execute: execute$9,
1705
- signature: '(unknown, unknown) => unknown',
2076
+ contextual: true,
2077
+ execute: execute$4,
2078
+ signature: '(string, string) => unknown[]',
2079
+ };
2080
+
2081
+ function execute$3(ctx, selector, objectKey, objectValue) {
2082
+ return ComponentTreeApi.collectValuesMap(ctx.componentId, selector, objectKey, objectValue);
2083
+ }
2084
+ const propObject = {
2085
+ kind: 'imperative',
2086
+ contextual: true,
2087
+ execute: execute$3,
2088
+ signature: '(string, string, string) => unknown{}',
1706
2089
  };
1707
2090
 
1708
2091
  /**
@@ -1723,7 +2106,7 @@ function getNestedProperty(obj, path) {
1723
2106
  }
1724
2107
  return current;
1725
2108
  }
1726
- function execute$8(dimensions, content) {
2109
+ function execute$2(dimensions, content) {
1727
2110
  return generateCombinations(dimensions).map(({ variables }) => ({
1728
2111
  componentType: 'sys.RefSpace',
1729
2112
  content,
@@ -1788,366 +2171,54 @@ function generateCombinations(dimensions) {
1788
2171
  else {
1789
2172
  keyPart = index;
1790
2173
  }
1791
- const prevKey = result.key;
1792
- const key = dimensionIndex === 0 ? keyPart : `${prevKey}:${keyPart}`;
1793
- newVariables[`${Delimiters.Variable}trackBy`] = {
1794
- _valueType: { type: 'string' },
1795
- _value: String(key),
1796
- _computation: null,
1797
- _binding: null,
1798
- };
1799
- tempResults.push({ variables: newVariables, key });
1800
- });
1801
- });
1802
- results = tempResults;
1803
- });
1804
- return results;
1805
- }
1806
- const repeat = {
1807
- kind: 'imperative',
1808
- execute: execute$8,
1809
- signature: '({items: unknown[], itemsType: unknown, item: string, index: string, first: unknown, last: unknown, trackBy: unknown}[], componentSchema) => componentSchema[]',
1810
- };
1811
-
1812
- function execute$7(value, precision = 0) {
1813
- const multiplier = Math.pow(10, precision);
1814
- return Math.round(value * multiplier) / multiplier;
1815
- }
1816
- const round = {
1817
- kind: 'imperative',
1818
- execute: execute$7,
1819
- signature: '(number, number) => number',
1820
- };
1821
-
1822
- function execute$6(ctx, selector) {
1823
- return ComponentTreeApi.isCommandRunning(ctx.componentId, selector);
1824
- }
1825
- const running = {
1826
- kind: 'imperative',
1827
- contextual: true,
1828
- execute: execute$6,
1829
- signature: '(string) => boolean',
1830
- };
1831
-
1832
- function execute$5(value, start, end) {
1833
- return value.slice(start, end);
1834
- }
1835
- const slice = {
1836
- kind: 'imperative',
1837
- execute: execute$5,
1838
- signature: '(unknown[], number, number) => unknown[]',
1839
- };
1840
-
1841
- function execute$4(items) {
1842
- return items.some((item) => !!item);
1843
- }
1844
- const some = {
1845
- kind: 'imperative',
1846
- execute: execute$4,
1847
- signature: '(unknown[]) => boolean',
1848
- };
1849
-
1850
- function execute$3(value) {
1851
- return String(value);
1852
- }
1853
- const string = {
1854
- kind: 'imperative',
1855
- execute: execute$3,
1856
- signature: '(unknown) => string',
1857
- };
1858
-
1859
- /**
1860
- * Switch statement with lazy case evaluation.
1861
- * Supports array mode (legacy) and variadic mode.
1862
- */
1863
- const switchFn = {
1864
- kind: 'reactive',
1865
- execute: (args) => {
1866
- if (args.length < 2) {
1867
- throw new Error('switch requires at least 2 arguments: value and default');
1868
- }
1869
- const valueComputed = args[0];
1870
- return computed(() => {
1871
- const state = new FunctionExecutionStateBuilder();
1872
- const valueState = valueComputed.get();
1873
- const earlyReturn = state.checkReady(valueState);
1874
- if (earlyReturn)
1875
- return earlyReturn;
1876
- const value = valueState.value;
1877
- if (args.length === 3) {
1878
- const casesState = args[1].get();
1879
- const defaultState = args[2].get();
1880
- const casesEarlyReturn = state.checkReady(casesState);
1881
- if (casesEarlyReturn)
1882
- return casesEarlyReturn;
1883
- const defaultEarlyReturn = state.checkReady(defaultState);
1884
- if (defaultEarlyReturn)
1885
- return defaultEarlyReturn;
1886
- const cases = casesState.value;
1887
- if (Array.isArray(cases)) {
1888
- if (cases.length % 2 !== 0) {
1889
- throw new Error('Cases array should have even number of elements');
1890
- }
1891
- const matchIndex = cases.findIndex((caseValue, index) => index % 2 === 0 && caseValue === value);
1892
- const returnValue = matchIndex >= 0 ? cases[matchIndex + 1] : defaultState.value;
1893
- return state.success(returnValue);
1894
- }
1895
- }
1896
- const defaultComputed = args[args.length - 1];
1897
- const caseArgs = args.slice(1, -1);
1898
- if (caseArgs.length % 2 !== 0) {
1899
- throw new Error('switch requires an even number of case arguments (key-value pairs)');
1900
- }
1901
- for (let i = 0; i < caseArgs.length; i += 2) {
1902
- const caseKeyComputed = caseArgs[i];
1903
- const caseValueComputed = caseArgs[i + 1];
1904
- const caseKeyState = caseKeyComputed.get();
1905
- const keyEarlyReturn = state.checkReady(caseKeyState);
1906
- if (keyEarlyReturn)
1907
- return keyEarlyReturn;
1908
- const caseKey = caseKeyState.value;
1909
- if (caseKey === value) {
1910
- const caseValueState = caseValueComputed.get();
1911
- const valueEarlyReturn = state.checkReady(caseValueState);
1912
- if (valueEarlyReturn)
1913
- return valueEarlyReturn;
1914
- return state.success(caseValueState.value);
1915
- }
1916
- }
1917
- const defaultState = defaultComputed.get();
1918
- const defaultEarlyReturn = state.checkReady(defaultState);
1919
- if (defaultEarlyReturn)
1920
- return defaultEarlyReturn;
1921
- return state.success(defaultState.value);
2174
+ const prevKey = result.key;
2175
+ const key = dimensionIndex === 0 ? keyPart : `${prevKey}:${keyPart}`;
2176
+ newVariables[`${Delimiters.Variable}trackBy`] = {
2177
+ _valueType: { type: 'string' },
2178
+ _value: String(key),
2179
+ _computation: null,
2180
+ _binding: null,
2181
+ };
2182
+ tempResults.push({ variables: newVariables, key });
2183
+ });
1922
2184
  });
1923
- },
1924
- signature: '(...unknown) => unknown',
1925
- };
1926
-
1927
- function execute$2(value) {
1928
- return value.toLowerCase();
2185
+ results = tempResults;
2186
+ });
2187
+ return results;
1929
2188
  }
1930
- const toLowerCase = {
2189
+ const repeat = {
1931
2190
  kind: 'imperative',
1932
2191
  execute: execute$2,
1933
- signature: '(string) => string',
2192
+ signature: '({items: unknown[], itemsType: unknown, item: string, index: string, first: unknown, last: unknown, trackBy: unknown}[], componentSchema) => componentSchema[]',
1934
2193
  };
1935
2194
 
1936
- function execute$1(value) {
1937
- return value.toUpperCase();
2195
+ function execute$1(ctx, selector) {
2196
+ return ComponentTreeApi.isCommandRunning(ctx.componentId, selector);
1938
2197
  }
1939
- const toUpperCase = {
2198
+ const running = {
1940
2199
  kind: 'imperative',
2200
+ contextual: true,
1941
2201
  execute: execute$1,
1942
- signature: '(string) => string',
2202
+ signature: '(string) => boolean',
1943
2203
  };
1944
2204
 
1945
- /**
1946
- * All formula functions available in the system.
1947
- *
1948
- * Control flow functions use reactive implementations with lazy evaluation:
1949
- * - `if`: Conditional evaluation (3 args: condition, trueValue, falseValue)
1950
- * - `and`, `or`: Variadic logical operators with short-circuit evaluation
1951
- * - `switch`: Variadic pattern matching with lazy case evaluation
1952
- * - `some`, `every`: Array element checkers (imperative, not lazy)
1953
- */
1954
2205
  const formulaFunctions$1 = {
1955
- // Collection functions
1956
- collectValuesMap,
1957
- collectValuesArray,
1958
- propObject: collectValuesMap,
1959
- propArray: collectValuesArray,
1960
- findBy,
1961
- findAllBy,
1962
- at,
1963
- not,
1964
- equal,
1965
- // Array element checkers (imperative)
1966
- some,
1967
- every,
1968
- // Control flow (reactive with lazy evaluation)
1969
- if: ifFn,
1970
- and,
1971
- or,
1972
- switch: switchFn,
1973
- // Utilities
1974
- in: inFn,
1975
- object: objectFn,
1976
- bool,
1977
- length,
1978
- empty,
1979
- concat,
1980
- pluck,
1981
- flatten,
1982
- json,
1983
- minus,
1984
- plus,
1985
- divide,
1986
- multiply,
1987
- running,
2206
+ propObject,
2207
+ propArray,
1988
2208
  get: getFn,
1989
2209
  id: idFn,
1990
- repeat,
1991
- emptyObject,
1992
- arrayPluck,
1993
- string,
1994
- number,
1995
- delay,
1996
2210
  evaluating,
1997
2211
  failed,
1998
- keys,
1999
- toUpperCase,
2000
- toLowerCase,
2001
- ceil,
2002
- floor,
2003
- round,
2004
- slice,
2005
- filterIn,
2006
- eq,
2007
- neq,
2008
- gt,
2009
- lt,
2010
- gte,
2011
- lte,
2212
+ running,
2213
+ repeat,
2012
2214
  };
2013
2215
 
2014
- function registerCommonDefinitions() {
2015
- const defStore = DefinitionStore.getInstance();
2016
- defStore.setCommands(commands);
2017
- // Register all functions (FunctionRegistry wraps imperative functions automatically)
2018
- FunctionRegistry.getInstance().setFunctions(formulaFunctions$1);
2019
- }
2020
-
2021
- const compiledCache = new WeakMap();
2022
- function getOrCompile(structure, key) {
2023
- let cached = compiledCache.get(structure);
2024
- if (!cached) {
2025
- cached = {};
2026
- compiledCache.set(structure, cached);
2027
- }
2028
- if (!cached[key]) {
2029
- cached[key] = createJsFunction(structure[key]);
2030
- }
2031
- return cached[key];
2032
- }
2033
- async function runCommand(node, ...args) {
2034
- const structure = node.structure;
2035
- if (!structure) {
2036
- const comp = node.position.componentId
2037
- ? ComponentStore.getInstance().getComponent(node.position.componentId)
2038
- : null;
2039
- const src = comp?.sourceUrl ? `[${comp.sourceUrl}] ` : '';
2040
- log.trace(`${src}Command is empty — no handler was bound to it.`);
2041
- return null;
2042
- }
2043
- const component = ComponentStore.getInstance().getComponentOrThrow(structure.owner);
2044
- const src = component.sourceUrl ? `[${component.sourceUrl}] ` : '';
2045
- const functionsProxy = proxifySubCommands(component);
2046
- try {
2047
- if (structure.init) {
2048
- log.trace(`${src}Running init hook`);
2049
- const initFunc = getOrCompile(structure, 'init');
2050
- await initFunc(functionsProxy, args);
2051
- }
2052
- if (structure.runnerType === 'inline') {
2053
- log.trace(`${src}Executing inline callback (${args.length} args)`);
2054
- return await structure.target(...args);
2055
- }
2056
- const preview = structure.target.slice(0, 80);
2057
- log.trace(`${src}Executing command: ${preview}`);
2058
- const targetFunc = getOrCompile(structure, 'target');
2059
- return await targetFunc(functionsProxy, args);
2060
- }
2061
- catch (e) {
2062
- throw new Error(`${src}Error executing ${structure.runnerType === 'inline' ? 'inline callback' : 'command'}`, {
2063
- cause: e,
2064
- });
2065
- }
2066
- finally {
2067
- if (structure.finalize) {
2068
- try {
2069
- log.trace(`${src}Running finalize hook`);
2070
- const finalizeFunc = getOrCompile(structure, 'finalize');
2071
- await finalizeFunc(functionsProxy, args);
2072
- }
2073
- catch (finalizeError) {
2074
- log.error(`${src}Error in finalize hook`, finalizeError);
2075
- }
2076
- }
2077
- }
2078
- }
2079
- function proxifySubCommands(component) {
2080
- return new Proxy({}, {
2081
- get(target, functionName) {
2082
- return (...functionArgs) => {
2083
- let execute;
2084
- if (functionName in target) {
2085
- execute = target[functionName];
2086
- }
2087
- else {
2088
- const def = DefinitionStore.getInstance().getCommand(functionName);
2089
- execute = target[functionName] = def.execute;
2090
- }
2091
- const commandCtx = {
2092
- componentId: component.id,
2093
- };
2094
- const result = execute(commandCtx, ...functionArgs);
2095
- if (result instanceof Observable) {
2096
- return firstValueFrom(result);
2097
- }
2098
- if (result instanceof Promise) {
2099
- return result;
2100
- }
2101
- if (isComputed(result)) {
2102
- return result.get();
2103
- }
2104
- return result;
2105
- };
2106
- },
2107
- });
2108
- }
2109
- /**
2110
- * Creates a JavaScript function from a string of code.
2111
- *
2112
- * SECURITY WARNING: This function uses `new Function()` to execute dynamic code.
2113
- * Only use with schemas from trusted sources. Malicious schemas could execute
2114
- * arbitrary code in the browser context, leading to XSS or data theft.
2115
- *
2116
- * The code has access to:
2117
- * - All registered commands via destructured parameters
2118
- * - Function arguments via the `args` parameter
2119
- */
2120
- function createJsFunction(func) {
2121
- const commandNames = DefinitionStore.getInstance().getCommandNames();
2122
- const commandsStr = commandNames.length ? `{ ${commandNames.join(', ')} }` : 'commands';
2123
- return new Function(commandsStr, 'args', `return (async () => { ${func} })();`);
2124
- }
2125
-
2126
- const componentsDefinitions$2 = {
2127
- 'sys.Box': {
2128
- properties: {
2129
- 'content@component': null,
2130
- },
2131
- },
2132
- 'sys.Group': {
2133
- properties: {
2134
- 'children@component[]': [],
2135
- },
2136
- },
2137
- 'sys.RefSpace': {
2138
- traits: ['sys.Box'],
2139
- properties: {},
2140
- },
2141
- 'sys.Facade': {
2142
- traits: ['sys.Box'],
2143
- properties: {},
2144
- },
2216
+ const sys = {
2217
+ definitions: componentsDefinitions$2,
2218
+ commands,
2219
+ functions: formulaFunctions$1,
2145
2220
  };
2146
2221
 
2147
- function autoRegisterCommonDefinitions() {
2148
- DefinitionStore.getInstance().setComponents(unfoldComponentDefinitions(componentsDefinitions$2));
2149
- }
2150
-
2151
2222
  const componentsDefinitions$1 = {
2152
2223
  'browser.Styled': {
2153
2224
  properties: {
@@ -2166,26 +2237,92 @@ const componentsDefinitions$1 = {
2166
2237
  properties: {
2167
2238
  'click@()': null,
2168
2239
  'stopPropagation@boolean': null,
2240
+ 'contextMenu@()': null,
2169
2241
  },
2170
2242
  },
2171
2243
  'browser.Focusable': {
2172
2244
  traits: ['sys.Box'],
2173
2245
  properties: {
2174
2246
  'tabIndex@number': null,
2175
- 'blur@command': null,
2247
+ 'blur@()': null,
2176
2248
  },
2177
2249
  },
2178
2250
  'browser.Dialog': {
2179
2251
  traits: ['sys.Box', 'browser.Styled'],
2180
2252
  properties: {
2181
- 'close@(unknown)': null,
2253
+ 'close@()': null,
2254
+ },
2255
+ },
2256
+ 'browser.Popover': {
2257
+ traits: ['sys.Box', 'browser.Styled'],
2258
+ properties: {
2259
+ 'close@()': null,
2260
+ 'mode@string': 'auto',
2261
+ 'anchor@string': null,
2262
+ 'positionArea@string': null,
2263
+ },
2264
+ },
2265
+ 'browser.Link': {
2266
+ traits: ['sys.Group', 'browser.Styled'],
2267
+ properties: {
2268
+ 'href@string': null,
2269
+ 'target@string': null,
2270
+ 'rel@string': null,
2271
+ },
2272
+ },
2273
+ 'browser.Image': {
2274
+ traits: ['browser.Styled'],
2275
+ properties: {
2276
+ 'src@string': null,
2277
+ 'alt@string': null,
2278
+ 'loading@string': null,
2279
+ },
2280
+ },
2281
+ 'browser.Hoverable': {
2282
+ traits: ['sys.Box'],
2283
+ properties: {
2284
+ 'mouseEnter@()': null,
2285
+ 'mouseLeave@()': null,
2286
+ },
2287
+ },
2288
+ 'browser.KeyListener': {
2289
+ traits: ['sys.Box'],
2290
+ properties: {
2291
+ 'keyDown@(string)': null,
2292
+ 'keyUp@(string)': null,
2293
+ },
2294
+ },
2295
+ 'browser.Scrollable': {
2296
+ traits: ['sys.Box'],
2297
+ properties: {
2298
+ 'scroll@()': null,
2299
+ },
2300
+ },
2301
+ 'browser.Animated': {
2302
+ traits: ['sys.Box'],
2303
+ properties: {
2304
+ 'animationEnd@()': null,
2305
+ 'transitionEnd@()': null,
2306
+ },
2307
+ },
2308
+ 'browser.Visible': {
2309
+ traits: ['sys.Box'],
2310
+ properties: {
2311
+ 'visible@()': null,
2312
+ 'invisible@()': null,
2313
+ },
2314
+ },
2315
+ 'browser.Resizable': {
2316
+ traits: ['sys.Box'],
2317
+ properties: {
2318
+ 'resize@()': null,
2182
2319
  },
2183
2320
  },
2184
2321
  };
2185
2322
 
2186
- function autoRegisterBrowserDefinitions() {
2187
- DefinitionStore.getInstance().setComponents(unfoldComponentDefinitions(componentsDefinitions$1));
2188
- }
2323
+ const browser = {
2324
+ definitions: componentsDefinitions$1,
2325
+ };
2189
2326
 
2190
2327
  const componentsDefinitions = {
2191
2328
  'forms.Input': {
@@ -2483,12 +2620,14 @@ function registerShapes() {
2483
2620
  },
2484
2621
  });
2485
2622
  }
2486
- function autoRegisterFormsDefinitions() {
2487
- registerShapes();
2488
- registerValidators(validators);
2489
- FunctionRegistry.getInstance().setFunctions(formulaFunctions);
2490
- DefinitionStore.getInstance().setComponents(unfoldComponentDefinitions(componentsDefinitions));
2491
- }
2623
+ const forms = {
2624
+ definitions: componentsDefinitions,
2625
+ functions: formulaFunctions,
2626
+ setup() {
2627
+ registerShapes();
2628
+ registerValidators(validators);
2629
+ },
2630
+ };
2492
2631
 
2493
2632
  function initRuntime() {
2494
2633
  componentTreeConfig.runCommand = runCommand;
@@ -2498,5 +2637,5 @@ function initRuntime() {
2498
2637
  * Generated bundle index. Do not edit.
2499
2638
  */
2500
2639
 
2501
- export { activateNode, activateStructure, autoRegisterBrowserDefinitions, autoRegisterCommonDefinitions, autoRegisterFormsDefinitions, componentInstanceMap, debugName, initRuntime, registerCommonDefinitions, registerValidator, registerValidators, runCommand, templateLoadingTracker };
2640
+ export { activateNode, activateStructure, browser, common, componentInstanceMap, debugName, forms, initRuntime, registerPlugin, registerPlugins, registerValidator, registerValidators, runCommand, sys, templateLoadingTracker };
2502
2641
  //# sourceMappingURL=kaskad-core.mjs.map