@kaskad/core 0.0.5 → 0.0.7

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