@kaskad/core 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/fesm2022/kaskad-core.mjs +2502 -0
- package/fesm2022/kaskad-core.mjs.map +1 -0
- package/package.json +32 -0
- package/types/kaskad-core.d.ts +64 -0
|
@@ -0,0 +1,2502 @@
|
|
|
1
|
+
export * from '@kaskad/types';
|
|
2
|
+
import { DefinitionStore } from '@kaskad/definition';
|
|
3
|
+
export * from '@kaskad/definition';
|
|
4
|
+
import { isObject, loadTemplates, templateRegistry, unfoldNodeSchema, unfoldComponentDefinitions } from '@kaskad/schema';
|
|
5
|
+
export { loadTemplates, templateRegistry, unfoldComponentDefinitions, unfoldNodeSchema } from '@kaskad/schema';
|
|
6
|
+
import { evaluateNode, FunctionExecutionStateBuilder, FunctionRegistry, wrapImperativeAsReactive } from '@kaskad/eval-tree';
|
|
7
|
+
export { FunctionRegistry } from '@kaskad/eval-tree';
|
|
8
|
+
import { ComponentTreeApi, ComponentStore, getComponent, componentTreeConfig } from '@kaskad/component-tree';
|
|
9
|
+
export { ComponentStore, ComponentTreeApi, createRootNode } from '@kaskad/component-tree';
|
|
10
|
+
import { reaction, comparer, runInAction, when, computed, isComputed } from 'mobx';
|
|
11
|
+
import { log, Delimiters } from '@kaskad/config';
|
|
12
|
+
import { parseFormula } from '@kaskad/formula-parser';
|
|
13
|
+
import { Observable, firstValueFrom } from 'rxjs';
|
|
14
|
+
|
|
15
|
+
class TemplateLoadingTracker {
|
|
16
|
+
pendingLoads = new Set();
|
|
17
|
+
completionPromise = null;
|
|
18
|
+
resolveCompletion = null;
|
|
19
|
+
get allLoadsComplete() {
|
|
20
|
+
if (!this.completionPromise) {
|
|
21
|
+
return Promise.resolve();
|
|
22
|
+
}
|
|
23
|
+
return this.completionPromise;
|
|
24
|
+
}
|
|
25
|
+
startLoading(templateKey) {
|
|
26
|
+
this.pendingLoads.add(templateKey);
|
|
27
|
+
if (!this.completionPromise) {
|
|
28
|
+
this.completionPromise = new Promise((resolve) => {
|
|
29
|
+
this.resolveCompletion = resolve;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
finishLoading(templateKey) {
|
|
34
|
+
// Only process if this load was actually being tracked
|
|
35
|
+
if (!this.pendingLoads.has(templateKey)) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
this.pendingLoads.delete(templateKey);
|
|
39
|
+
if (this.pendingLoads.size === 0) {
|
|
40
|
+
this.resolveCompletion?.();
|
|
41
|
+
this.completionPromise = null;
|
|
42
|
+
this.resolveCompletion = null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Export a singleton instance for tracking template loading operations
|
|
47
|
+
const templateLoadingTracker = new TemplateLoadingTracker();
|
|
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),
|
|
82
|
+
});
|
|
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
|
+
};
|
|
107
|
+
};
|
|
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
|
+
};
|
|
117
|
+
};
|
|
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),
|
|
138
|
+
};
|
|
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}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
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);
|
|
157
|
+
}
|
|
158
|
+
|
|
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);
|
|
211
|
+
}
|
|
212
|
+
|
|
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);
|
|
250
|
+
}
|
|
251
|
+
|
|
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);
|
|
300
|
+
}
|
|
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;
|
|
312
|
+
}
|
|
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;
|
|
325
|
+
}
|
|
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;
|
|
336
|
+
}
|
|
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);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
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;
|
|
358
|
+
}
|
|
359
|
+
merged[key] = value;
|
|
360
|
+
}
|
|
361
|
+
return merged;
|
|
362
|
+
}
|
|
363
|
+
|
|
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');
|
|
369
|
+
}
|
|
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 };
|
|
395
|
+
}
|
|
396
|
+
else if (nodeChanged) {
|
|
397
|
+
componentValues = syncNodeValueAndComponents(currentValue, evalState, preservedNodeValue);
|
|
398
|
+
}
|
|
399
|
+
else {
|
|
400
|
+
componentValues = syncComponentsAndPreserved(evalState, preservedNodeValue, currentValue);
|
|
401
|
+
}
|
|
402
|
+
const selectionValue = toSelectionSet(componentValues);
|
|
403
|
+
runInAction(() => {
|
|
404
|
+
const store = ComponentStore.getInstance();
|
|
405
|
+
store.setNodeRawSchema(node, selectionValue);
|
|
406
|
+
updateComponentsValues(componentsIds, componentValues, keyProp, valueProp);
|
|
407
|
+
});
|
|
408
|
+
}, { fireImmediately: true, equals: comparer.structural });
|
|
409
|
+
node.disposers.push(disposer);
|
|
410
|
+
}
|
|
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];
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
for (const key of Object.keys(evalState.value)) {
|
|
420
|
+
if (!(key in componentValues)) {
|
|
421
|
+
componentValues[key] = false;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
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
|
+
}
|
|
440
|
+
}
|
|
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
|
+
}
|
|
446
|
+
}
|
|
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);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
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;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
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) => ({
|
|
521
|
+
type: 'value',
|
|
522
|
+
value: schema.value,
|
|
523
|
+
});
|
|
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 };
|
|
552
|
+
}
|
|
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
|
+
return {
|
|
567
|
+
type: 'object',
|
|
568
|
+
properties,
|
|
569
|
+
};
|
|
570
|
+
};
|
|
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);
|
|
595
|
+
}
|
|
596
|
+
throw new Error(`Unsupported value type: ${schema.valueType.type}`);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function lowerForLoop(forComputation) {
|
|
600
|
+
const dimensionsNode = {
|
|
601
|
+
type: 'array',
|
|
602
|
+
items: forComputation.dimensions.map((dimension) => {
|
|
603
|
+
// Extract trackBy expression to be evaluated in RefSpace context where $item is available
|
|
604
|
+
let trackByExpression = null;
|
|
605
|
+
if (dimension.trackBy.computation && dimension.trackBy.computation.computationType === 'formula') {
|
|
606
|
+
trackByExpression = `=${dimension.trackBy.computation.formula}`;
|
|
607
|
+
}
|
|
608
|
+
else if (dimension.trackBy.value !== null) {
|
|
609
|
+
trackByExpression = dimension.trackBy.value;
|
|
610
|
+
}
|
|
611
|
+
return {
|
|
612
|
+
type: 'object',
|
|
613
|
+
properties: [
|
|
614
|
+
{ key: { type: 'value', value: 'items' }, value: lowerSchema(dimension.items) },
|
|
615
|
+
{ key: { type: 'value', value: 'itemsType' }, value: { type: 'value', value: dimension.itemsType } },
|
|
616
|
+
{ key: { type: 'value', value: 'item' }, value: lowerSchema(dimension.item) },
|
|
617
|
+
{ key: { type: 'value', value: 'index' }, value: lowerSchema(dimension.index) },
|
|
618
|
+
{ key: { type: 'value', value: 'first' }, value: lowerSchema(dimension.first) },
|
|
619
|
+
{ key: { type: 'value', value: 'last' }, value: lowerSchema(dimension.last) },
|
|
620
|
+
{ key: { type: 'value', value: 'trackBy' }, value: { type: 'value', value: trackByExpression } },
|
|
621
|
+
],
|
|
622
|
+
};
|
|
623
|
+
}),
|
|
624
|
+
};
|
|
625
|
+
const yieldNode = lowerSchema(forComputation.yield);
|
|
626
|
+
return {
|
|
627
|
+
type: 'function',
|
|
628
|
+
name: 'repeat',
|
|
629
|
+
args: [dimensionsNode, yieldNode],
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
function lowerTemplate(templateComputation) {
|
|
633
|
+
return {
|
|
634
|
+
type: 'value',
|
|
635
|
+
value: templateComputation,
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
function lowerIfStatement(ifNode) {
|
|
639
|
+
return {
|
|
640
|
+
type: 'function',
|
|
641
|
+
name: 'if',
|
|
642
|
+
args: [lowerSchema(ifNode.if), lowerSchema(ifNode.then), lowerSchema(ifNode.else)],
|
|
643
|
+
};
|
|
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 });
|
|
655
|
+
}
|
|
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
|
+
return {
|
|
663
|
+
type: 'function',
|
|
664
|
+
name: 'switch',
|
|
665
|
+
args: [sourceEvalNode, casesArrayNode, defaultNode],
|
|
666
|
+
};
|
|
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
|
+
};
|
|
675
|
+
function lowerComputationSchema(computationSchema) {
|
|
676
|
+
const lowerer = computationLowerers[computationSchema.computationType];
|
|
677
|
+
if (lowerer) {
|
|
678
|
+
return lowerer(computationSchema);
|
|
679
|
+
}
|
|
680
|
+
throw new Error(`Unsupported computation type: ${computationSchema.computationType}`);
|
|
681
|
+
}
|
|
682
|
+
|
|
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');
|
|
688
|
+
}
|
|
689
|
+
const evalNode = lowerComputationSchema(currentFrame.schema);
|
|
690
|
+
if (!node.position.componentId) {
|
|
691
|
+
throw new Error('Node does not have a component context');
|
|
692
|
+
}
|
|
693
|
+
const { computed, disposers } = evaluateNode(evalNode, {
|
|
694
|
+
componentId: node.position.componentId,
|
|
695
|
+
});
|
|
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);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
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);
|
|
737
|
+
}
|
|
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;
|
|
743
|
+
}
|
|
744
|
+
}, {
|
|
745
|
+
// Fire immediately on setup to handle initial state
|
|
746
|
+
fireImmediately: true,
|
|
747
|
+
});
|
|
748
|
+
component.handlerDisposers.push(disposer);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
function activateComponentStructure(node) {
|
|
753
|
+
const structure = node.structure;
|
|
754
|
+
if (structure === null) {
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
const component = ComponentStore.getInstance().getComponentOrThrow(structure);
|
|
758
|
+
activateComponent(component);
|
|
759
|
+
}
|
|
760
|
+
function activateComponent(component) {
|
|
761
|
+
ComponentStore.getInstance().componentsToPreload.add(component.componentType);
|
|
762
|
+
for (const node of component.props.values()) {
|
|
763
|
+
activateNode(node);
|
|
764
|
+
}
|
|
765
|
+
for (const node of component.variables.values()) {
|
|
766
|
+
activateNode(node);
|
|
767
|
+
}
|
|
768
|
+
for (const handler of component.onNodeChange) {
|
|
769
|
+
activateNode(handler.command);
|
|
770
|
+
}
|
|
771
|
+
activateNodeChangeHandlers(component);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function activateMapStructure(node) {
|
|
775
|
+
const structure = node.structure;
|
|
776
|
+
if (structure === null) {
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
for (const item of Object.values(structure)) {
|
|
780
|
+
activateNode(item);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function activateObjectStructure(node) {
|
|
785
|
+
const structure = node.structure;
|
|
786
|
+
if (structure === null) {
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
for (const node of Object.values(structure)) {
|
|
790
|
+
activateNode(node);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function activateShapeStructure(node) {
|
|
795
|
+
const { valueType, structure } = node;
|
|
796
|
+
if (structure === null) {
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
const objectValueType = DefinitionStore.getInstance().getShape(valueType.shapeType);
|
|
800
|
+
for (const key of Object.keys(objectValueType.fields)) {
|
|
801
|
+
activateNode(structure[key]);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
function activateVariantShapeStructure(node) {
|
|
806
|
+
const { valueType, structure } = node;
|
|
807
|
+
if (structure === null) {
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
const objectValueType = DefinitionStore.getInstance().getVariantShape(valueType.shapeType, structure.kind);
|
|
811
|
+
for (const key of Object.keys(objectValueType.fields)) {
|
|
812
|
+
activateNode(structure.fields[key]);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
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);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
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);
|
|
843
|
+
if (!node.position.componentId) {
|
|
844
|
+
throw new Error('Node does not have a component context');
|
|
845
|
+
}
|
|
846
|
+
const component = getComponent(node);
|
|
847
|
+
const { computed, disposers } = evaluateNode(evalNode, {
|
|
848
|
+
componentId: node.position.componentId,
|
|
849
|
+
sourceUrl: component.sourceUrl ?? undefined,
|
|
850
|
+
});
|
|
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) {
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
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);
|
|
866
|
+
}
|
|
867
|
+
else {
|
|
868
|
+
activateStructure(node);
|
|
869
|
+
}
|
|
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);
|
|
910
|
+
});
|
|
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
|
+
}
|
|
935
|
+
}
|
|
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}]`);
|
|
949
|
+
}
|
|
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;
|
|
955
|
+
}
|
|
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
|
+
};
|
|
965
|
+
}
|
|
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.`);
|
|
978
|
+
}
|
|
979
|
+
else if (regularProps.length > 0) {
|
|
980
|
+
log.warn(`Template "${templatePath}": Unknown properties: ${regularProps.join(', ')}. ` +
|
|
981
|
+
`Expected: ${contractPropKeys.join(', ')}`);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
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];
|
|
999
|
+
}
|
|
1000
|
+
const prefixedKey = `${Delimiters.Variable}${targetSlot}`;
|
|
1001
|
+
prefixedVariables[prefixedKey] = unfoldNodeSchema(slotComponent, valueTypes[targetSlot]);
|
|
1002
|
+
}
|
|
1003
|
+
return prefixedVariables;
|
|
1004
|
+
}
|
|
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
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
return rootSchema;
|
|
1016
|
+
}
|
|
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;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
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) {
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
// Idempotency: don't re-activate an already activated frame
|
|
1051
|
+
if (frame.activated) {
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
const activator = computationActivators[frame.schema.computationType];
|
|
1055
|
+
if (activator) {
|
|
1056
|
+
activator(node);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
function activateNode(node) {
|
|
1061
|
+
if (node.fullyActivated) {
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
activateStructure(node);
|
|
1065
|
+
if (!node.computationStack.isEmpty) {
|
|
1066
|
+
activateComputation(node);
|
|
1067
|
+
}
|
|
1068
|
+
if (node.bindingSchema) {
|
|
1069
|
+
void addBinding(node);
|
|
1070
|
+
}
|
|
1071
|
+
if (node.extraBinding) {
|
|
1072
|
+
void addBinding(node, true);
|
|
1073
|
+
}
|
|
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
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
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);
|
|
1106
|
+
}
|
|
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));
|
|
1115
|
+
}
|
|
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;
|
|
1124
|
+
}
|
|
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);
|
|
1133
|
+
}
|
|
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);
|
|
1142
|
+
}
|
|
1143
|
+
|
|
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}"`);
|
|
1154
|
+
}
|
|
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}"`);
|
|
1158
|
+
}
|
|
1159
|
+
return method.call(instance, ...args);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
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);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
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
|
+
/**
|
|
1197
|
+
* Variadic AND function with short-circuit evaluation.
|
|
1198
|
+
* Stops at the first falsy value.
|
|
1199
|
+
*/
|
|
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);
|
|
1218
|
+
});
|
|
1219
|
+
},
|
|
1220
|
+
signature: '(...unknown) => boolean',
|
|
1221
|
+
};
|
|
1222
|
+
|
|
1223
|
+
function execute$K(source, path) {
|
|
1224
|
+
const takeDeep = (source, path) => path.reduce((current, step) => {
|
|
1225
|
+
return current?.[step];
|
|
1226
|
+
}, source);
|
|
1227
|
+
return source.map((element) => takeDeep(element, path));
|
|
1228
|
+
}
|
|
1229
|
+
const arrayPluck = {
|
|
1230
|
+
kind: 'imperative',
|
|
1231
|
+
execute: execute$K,
|
|
1232
|
+
signature: '(unknown{}[], string[]) => unknown',
|
|
1233
|
+
};
|
|
1234
|
+
|
|
1235
|
+
function execute$J(value, index) {
|
|
1236
|
+
return value[index];
|
|
1237
|
+
}
|
|
1238
|
+
const at = {
|
|
1239
|
+
kind: 'imperative',
|
|
1240
|
+
execute: execute$J,
|
|
1241
|
+
signature: '(unknown[], number) => unknown',
|
|
1242
|
+
};
|
|
1243
|
+
|
|
1244
|
+
function execute$I(value) {
|
|
1245
|
+
return Boolean(value);
|
|
1246
|
+
}
|
|
1247
|
+
const bool = {
|
|
1248
|
+
kind: 'imperative',
|
|
1249
|
+
execute: execute$I,
|
|
1250
|
+
signature: '(unknown) => boolean',
|
|
1251
|
+
};
|
|
1252
|
+
|
|
1253
|
+
function execute$H(value) {
|
|
1254
|
+
return Math.ceil(value);
|
|
1255
|
+
}
|
|
1256
|
+
const ceil = {
|
|
1257
|
+
kind: 'imperative',
|
|
1258
|
+
execute: execute$H,
|
|
1259
|
+
signature: '(number) => number',
|
|
1260
|
+
};
|
|
1261
|
+
|
|
1262
|
+
function execute$G(ctx, selector, path) {
|
|
1263
|
+
return ComponentTreeApi.collectValuesArray(ctx.componentId, selector, path);
|
|
1264
|
+
}
|
|
1265
|
+
const collectValuesArray = {
|
|
1266
|
+
kind: 'imperative',
|
|
1267
|
+
contextual: true,
|
|
1268
|
+
execute: execute$G,
|
|
1269
|
+
signature: '(string, string) => unknown[]',
|
|
1270
|
+
};
|
|
1271
|
+
|
|
1272
|
+
function execute$F(ctx, selector, objectKey, objectValue) {
|
|
1273
|
+
return ComponentTreeApi.collectValuesMap(ctx.componentId, selector, objectKey, objectValue);
|
|
1274
|
+
}
|
|
1275
|
+
const collectValuesMap = {
|
|
1276
|
+
kind: 'imperative',
|
|
1277
|
+
contextual: true,
|
|
1278
|
+
execute: execute$F,
|
|
1279
|
+
signature: '(string, string, string) => unknown{}',
|
|
1280
|
+
};
|
|
1281
|
+
|
|
1282
|
+
function execute$E(strings, separator = '') {
|
|
1283
|
+
return strings.join(separator);
|
|
1284
|
+
}
|
|
1285
|
+
const concat = {
|
|
1286
|
+
kind: 'imperative',
|
|
1287
|
+
execute: execute$E,
|
|
1288
|
+
signature: '(unknown[], string) => string',
|
|
1289
|
+
};
|
|
1290
|
+
|
|
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',
|
|
1298
|
+
};
|
|
1299
|
+
|
|
1300
|
+
function execute$C(num1, num2) {
|
|
1301
|
+
return num1 / num2;
|
|
1302
|
+
}
|
|
1303
|
+
const divide = {
|
|
1304
|
+
kind: 'imperative',
|
|
1305
|
+
execute: execute$C,
|
|
1306
|
+
signature: '(number, number) => number',
|
|
1307
|
+
};
|
|
1308
|
+
|
|
1309
|
+
function execute$B(items) {
|
|
1310
|
+
return items.length === 0;
|
|
1311
|
+
}
|
|
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;
|
|
1320
|
+
}
|
|
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;
|
|
1336
|
+
}
|
|
1337
|
+
const equal = {
|
|
1338
|
+
kind: 'imperative',
|
|
1339
|
+
execute: execute$y,
|
|
1340
|
+
signature: '(unknown, unknown) => boolean',
|
|
1341
|
+
};
|
|
1342
|
+
|
|
1343
|
+
function execute$x(ctx, selector) {
|
|
1344
|
+
return ComponentTreeApi.getNode(ctx.componentId, selector).evaluating;
|
|
1345
|
+
}
|
|
1346
|
+
const evaluating = {
|
|
1347
|
+
kind: 'imperative',
|
|
1348
|
+
contextual: true,
|
|
1349
|
+
execute: execute$x,
|
|
1350
|
+
signature: '(string) => boolean',
|
|
1351
|
+
};
|
|
1352
|
+
|
|
1353
|
+
function execute$w(items) {
|
|
1354
|
+
return items.every((item) => !!item);
|
|
1355
|
+
}
|
|
1356
|
+
const every = {
|
|
1357
|
+
kind: 'imperative',
|
|
1358
|
+
execute: execute$w,
|
|
1359
|
+
signature: '(unknown[]) => boolean',
|
|
1360
|
+
};
|
|
1361
|
+
|
|
1362
|
+
function execute$v(ctx, selector) {
|
|
1363
|
+
return ComponentTreeApi.getNode(ctx.componentId, selector).failed;
|
|
1364
|
+
}
|
|
1365
|
+
const failed = {
|
|
1366
|
+
kind: 'imperative',
|
|
1367
|
+
contextual: true,
|
|
1368
|
+
execute: execute$v,
|
|
1369
|
+
signature: '(string) => boolean',
|
|
1370
|
+
};
|
|
1371
|
+
|
|
1372
|
+
function execute$u(array, allowedValues) {
|
|
1373
|
+
if (!allowedValues) {
|
|
1374
|
+
return [];
|
|
1375
|
+
}
|
|
1376
|
+
if (allowedValues instanceof Set) {
|
|
1377
|
+
return array.filter((item) => allowedValues.has(item));
|
|
1378
|
+
}
|
|
1379
|
+
return array.filter((item) => allowedValues.includes(item));
|
|
1380
|
+
}
|
|
1381
|
+
const filterIn = {
|
|
1382
|
+
kind: 'imperative',
|
|
1383
|
+
execute: execute$u,
|
|
1384
|
+
signature: '(unknown[], unknown[]) => unknown[]',
|
|
1385
|
+
};
|
|
1386
|
+
|
|
1387
|
+
function execute$t(collection, key, values) {
|
|
1388
|
+
return values
|
|
1389
|
+
.map((value) => collection.find((element) => element[key] === value))
|
|
1390
|
+
.filter((item) => item !== undefined);
|
|
1391
|
+
}
|
|
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;
|
|
1401
|
+
}
|
|
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();
|
|
1410
|
+
}
|
|
1411
|
+
const flatten = {
|
|
1412
|
+
kind: 'imperative',
|
|
1413
|
+
execute: execute$r,
|
|
1414
|
+
signature: '(unknown[][]) => unknown[]',
|
|
1415
|
+
};
|
|
1416
|
+
|
|
1417
|
+
function execute$q(value) {
|
|
1418
|
+
return Math.floor(value);
|
|
1419
|
+
}
|
|
1420
|
+
const floor = {
|
|
1421
|
+
kind: 'imperative',
|
|
1422
|
+
execute: execute$q,
|
|
1423
|
+
signature: '(number) => number',
|
|
1424
|
+
};
|
|
1425
|
+
|
|
1426
|
+
function createFailedAsyncValue(error) {
|
|
1427
|
+
return {
|
|
1428
|
+
kind: 'promise',
|
|
1429
|
+
current: null,
|
|
1430
|
+
error: error instanceof Error ? error : new Error('Node lookup failed'),
|
|
1431
|
+
pending: false,
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
function warnNodeNotFound(selector) {
|
|
1435
|
+
const baseName = selector.split('.')[0];
|
|
1436
|
+
const isVariable = baseName.startsWith('$');
|
|
1437
|
+
const name = isVariable ? baseName.slice(1) : baseName;
|
|
1438
|
+
const type = isVariable ? 'Variable' : 'Property';
|
|
1439
|
+
log.trace(`${type} "${name}" not found`);
|
|
1440
|
+
}
|
|
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
|
+
|
|
1487
|
+
const execute$p = (a, b) => a > b;
|
|
1488
|
+
const gt = {
|
|
1489
|
+
kind: 'imperative',
|
|
1490
|
+
execute: execute$p,
|
|
1491
|
+
signature: '(number, number) => boolean',
|
|
1492
|
+
};
|
|
1493
|
+
|
|
1494
|
+
const execute$o = (a, b) => a >= b;
|
|
1495
|
+
const gte = {
|
|
1496
|
+
kind: 'imperative',
|
|
1497
|
+
execute: execute$o,
|
|
1498
|
+
signature: '(number, number) => boolean',
|
|
1499
|
+
};
|
|
1500
|
+
|
|
1501
|
+
function execute$n(ctx, componentSelector) {
|
|
1502
|
+
return ComponentTreeApi.findComponent(ctx.componentId, componentSelector)?.id ?? null;
|
|
1503
|
+
}
|
|
1504
|
+
const idFn = {
|
|
1505
|
+
kind: 'imperative',
|
|
1506
|
+
contextual: true,
|
|
1507
|
+
execute: execute$n,
|
|
1508
|
+
signature: '(string) => string',
|
|
1509
|
+
};
|
|
1510
|
+
|
|
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
|
+
};
|
|
1537
|
+
|
|
1538
|
+
function execute$m(collection, value) {
|
|
1539
|
+
if (collection instanceof Set) {
|
|
1540
|
+
return collection.has(value);
|
|
1541
|
+
}
|
|
1542
|
+
return collection.includes(value);
|
|
1543
|
+
}
|
|
1544
|
+
const inFn = {
|
|
1545
|
+
kind: 'imperative',
|
|
1546
|
+
execute: execute$m,
|
|
1547
|
+
signature: '(unknown[], unknown) => boolean',
|
|
1548
|
+
};
|
|
1549
|
+
|
|
1550
|
+
function replacer(_key, value) {
|
|
1551
|
+
if (value instanceof Set) {
|
|
1552
|
+
return Array.from(value);
|
|
1553
|
+
}
|
|
1554
|
+
return value;
|
|
1555
|
+
}
|
|
1556
|
+
function execute$l(arg) {
|
|
1557
|
+
return JSON.stringify(arg, replacer);
|
|
1558
|
+
}
|
|
1559
|
+
const json = {
|
|
1560
|
+
kind: 'imperative',
|
|
1561
|
+
execute: execute$l,
|
|
1562
|
+
signature: '(unknown) => string',
|
|
1563
|
+
};
|
|
1564
|
+
|
|
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
|
+
};
|
|
1573
|
+
|
|
1574
|
+
function execute$j(value) {
|
|
1575
|
+
return value.length;
|
|
1576
|
+
}
|
|
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
|
+
|
|
1590
|
+
const execute$h = (a, b) => a <= b;
|
|
1591
|
+
const lte = {
|
|
1592
|
+
kind: 'imperative',
|
|
1593
|
+
execute: execute$h,
|
|
1594
|
+
signature: '(number, number) => boolean',
|
|
1595
|
+
};
|
|
1596
|
+
|
|
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',
|
|
1604
|
+
};
|
|
1605
|
+
|
|
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',
|
|
1613
|
+
};
|
|
1614
|
+
|
|
1615
|
+
const execute$e = (a, b) => a !== b;
|
|
1616
|
+
const neq = {
|
|
1617
|
+
kind: 'imperative',
|
|
1618
|
+
execute: execute$e,
|
|
1619
|
+
signature: '(unknown, unknown) => boolean',
|
|
1620
|
+
};
|
|
1621
|
+
|
|
1622
|
+
function execute$d(value) {
|
|
1623
|
+
return !value;
|
|
1624
|
+
}
|
|
1625
|
+
const not = {
|
|
1626
|
+
kind: 'imperative',
|
|
1627
|
+
execute: execute$d,
|
|
1628
|
+
signature: '(unknown) => boolean',
|
|
1629
|
+
};
|
|
1630
|
+
|
|
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;
|
|
1637
|
+
}
|
|
1638
|
+
const number = {
|
|
1639
|
+
kind: 'imperative',
|
|
1640
|
+
execute: execute$c,
|
|
1641
|
+
signature: '(unknown) => number',
|
|
1642
|
+
};
|
|
1643
|
+
|
|
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;
|
|
1654
|
+
}
|
|
1655
|
+
const objectFn = {
|
|
1656
|
+
kind: 'imperative',
|
|
1657
|
+
execute: execute$b,
|
|
1658
|
+
signature: '(unknown[]) => unknown{}',
|
|
1659
|
+
};
|
|
1660
|
+
|
|
1661
|
+
/**
|
|
1662
|
+
* Variadic OR function with short-circuit evaluation.
|
|
1663
|
+
* Stops at the first truthy value.
|
|
1664
|
+
*/
|
|
1665
|
+
const or = {
|
|
1666
|
+
kind: 'reactive',
|
|
1667
|
+
execute: (args) => {
|
|
1668
|
+
if (args.length === 0) {
|
|
1669
|
+
throw new Error('or requires at least 1 argument');
|
|
1670
|
+
}
|
|
1671
|
+
return computed(() => {
|
|
1672
|
+
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);
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
return state.success(false);
|
|
1683
|
+
});
|
|
1684
|
+
},
|
|
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',
|
|
1697
|
+
};
|
|
1698
|
+
|
|
1699
|
+
function execute$9(a, b) {
|
|
1700
|
+
return a + b;
|
|
1701
|
+
}
|
|
1702
|
+
const plus = {
|
|
1703
|
+
kind: 'imperative',
|
|
1704
|
+
execute: execute$9,
|
|
1705
|
+
signature: '(unknown, unknown) => unknown',
|
|
1706
|
+
};
|
|
1707
|
+
|
|
1708
|
+
/**
|
|
1709
|
+
* Get a nested property value from an object using dot notation
|
|
1710
|
+
* e.g., getNestedProperty({ user: { id: 123 } }, "user.id") => 123
|
|
1711
|
+
*/
|
|
1712
|
+
function getNestedProperty(obj, path) {
|
|
1713
|
+
if (obj === null || obj === undefined) {
|
|
1714
|
+
return undefined;
|
|
1715
|
+
}
|
|
1716
|
+
const parts = path.split('.');
|
|
1717
|
+
let current = obj;
|
|
1718
|
+
for (const part of parts) {
|
|
1719
|
+
if (current === null || current === undefined || typeof current !== 'object') {
|
|
1720
|
+
return undefined;
|
|
1721
|
+
}
|
|
1722
|
+
current = current[part];
|
|
1723
|
+
}
|
|
1724
|
+
return current;
|
|
1725
|
+
}
|
|
1726
|
+
function execute$8(dimensions, content) {
|
|
1727
|
+
return generateCombinations(dimensions).map(({ variables }) => ({
|
|
1728
|
+
componentType: 'sys.RefSpace',
|
|
1729
|
+
content,
|
|
1730
|
+
...variables,
|
|
1731
|
+
}));
|
|
1732
|
+
}
|
|
1733
|
+
function generateCombinations(dimensions) {
|
|
1734
|
+
let results = [{ variables: {}, key: 0 }];
|
|
1735
|
+
dimensions.forEach((dimension, dimensionIndex) => {
|
|
1736
|
+
const tempResults = [];
|
|
1737
|
+
results.forEach((result) => {
|
|
1738
|
+
dimension.items.forEach((item, index) => {
|
|
1739
|
+
const newVariables = {
|
|
1740
|
+
...result.variables,
|
|
1741
|
+
[`${Delimiters.Variable}${dimension.index}`]: {
|
|
1742
|
+
_valueType: { type: 'number' },
|
|
1743
|
+
_value: index,
|
|
1744
|
+
_computation: null,
|
|
1745
|
+
_binding: null,
|
|
1746
|
+
},
|
|
1747
|
+
[`${Delimiters.Variable}${dimension.item}`]: {
|
|
1748
|
+
_valueType: dimension.itemsType.item,
|
|
1749
|
+
_value: item,
|
|
1750
|
+
_computation: null,
|
|
1751
|
+
_binding: null,
|
|
1752
|
+
},
|
|
1753
|
+
};
|
|
1754
|
+
if (dimension.first !== null) {
|
|
1755
|
+
newVariables[`${Delimiters.Variable}first`] = {
|
|
1756
|
+
_valueType: { type: 'boolean' },
|
|
1757
|
+
_value: index === 0,
|
|
1758
|
+
_computation: null,
|
|
1759
|
+
_binding: null,
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1762
|
+
if (dimension.last !== null) {
|
|
1763
|
+
newVariables[`${Delimiters.Variable}last`] = {
|
|
1764
|
+
_valueType: { type: 'boolean' },
|
|
1765
|
+
_value: dimension.items.length - 1 === index,
|
|
1766
|
+
_computation: null,
|
|
1767
|
+
_binding: null,
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
// Compute key for stable identity tracking
|
|
1771
|
+
// dimension.trackBy: null = use index, "" = use item value, "prop" = use property
|
|
1772
|
+
let keyPart;
|
|
1773
|
+
if (dimension.trackBy !== null) {
|
|
1774
|
+
let trackByValue;
|
|
1775
|
+
if (dimension.trackBy === '') {
|
|
1776
|
+
trackByValue = item;
|
|
1777
|
+
}
|
|
1778
|
+
else {
|
|
1779
|
+
trackByValue = getNestedProperty(item, dimension.trackBy);
|
|
1780
|
+
}
|
|
1781
|
+
if (trackByValue === null || trackByValue === undefined || trackByValue === '') {
|
|
1782
|
+
keyPart = index;
|
|
1783
|
+
}
|
|
1784
|
+
else {
|
|
1785
|
+
keyPart = trackByValue;
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
else {
|
|
1789
|
+
keyPart = index;
|
|
1790
|
+
}
|
|
1791
|
+
const prevKey = result.key;
|
|
1792
|
+
const key = dimensionIndex === 0 ? keyPart : `${prevKey}:${keyPart}`;
|
|
1793
|
+
newVariables[`${Delimiters.Variable}trackBy`] = {
|
|
1794
|
+
_valueType: { type: 'string' },
|
|
1795
|
+
_value: String(key),
|
|
1796
|
+
_computation: null,
|
|
1797
|
+
_binding: null,
|
|
1798
|
+
};
|
|
1799
|
+
tempResults.push({ variables: newVariables, key });
|
|
1800
|
+
});
|
|
1801
|
+
});
|
|
1802
|
+
results = tempResults;
|
|
1803
|
+
});
|
|
1804
|
+
return results;
|
|
1805
|
+
}
|
|
1806
|
+
const repeat = {
|
|
1807
|
+
kind: 'imperative',
|
|
1808
|
+
execute: execute$8,
|
|
1809
|
+
signature: '({items: unknown[], itemsType: unknown, item: string, index: string, first: unknown, last: unknown, trackBy: unknown}[], componentSchema) => componentSchema[]',
|
|
1810
|
+
};
|
|
1811
|
+
|
|
1812
|
+
function execute$7(value, precision = 0) {
|
|
1813
|
+
const multiplier = Math.pow(10, precision);
|
|
1814
|
+
return Math.round(value * multiplier) / multiplier;
|
|
1815
|
+
}
|
|
1816
|
+
const round = {
|
|
1817
|
+
kind: 'imperative',
|
|
1818
|
+
execute: execute$7,
|
|
1819
|
+
signature: '(number, number) => number',
|
|
1820
|
+
};
|
|
1821
|
+
|
|
1822
|
+
function execute$6(ctx, selector) {
|
|
1823
|
+
return ComponentTreeApi.isCommandRunning(ctx.componentId, selector);
|
|
1824
|
+
}
|
|
1825
|
+
const running = {
|
|
1826
|
+
kind: 'imperative',
|
|
1827
|
+
contextual: true,
|
|
1828
|
+
execute: execute$6,
|
|
1829
|
+
signature: '(string) => boolean',
|
|
1830
|
+
};
|
|
1831
|
+
|
|
1832
|
+
function execute$5(value, start, end) {
|
|
1833
|
+
return value.slice(start, end);
|
|
1834
|
+
}
|
|
1835
|
+
const slice = {
|
|
1836
|
+
kind: 'imperative',
|
|
1837
|
+
execute: execute$5,
|
|
1838
|
+
signature: '(unknown[], number, number) => unknown[]',
|
|
1839
|
+
};
|
|
1840
|
+
|
|
1841
|
+
function execute$4(items) {
|
|
1842
|
+
return items.some((item) => !!item);
|
|
1843
|
+
}
|
|
1844
|
+
const some = {
|
|
1845
|
+
kind: 'imperative',
|
|
1846
|
+
execute: execute$4,
|
|
1847
|
+
signature: '(unknown[]) => boolean',
|
|
1848
|
+
};
|
|
1849
|
+
|
|
1850
|
+
function execute$3(value) {
|
|
1851
|
+
return String(value);
|
|
1852
|
+
}
|
|
1853
|
+
const string = {
|
|
1854
|
+
kind: 'imperative',
|
|
1855
|
+
execute: execute$3,
|
|
1856
|
+
signature: '(unknown) => string',
|
|
1857
|
+
};
|
|
1858
|
+
|
|
1859
|
+
/**
|
|
1860
|
+
* Switch statement with lazy case evaluation.
|
|
1861
|
+
* Supports array mode (legacy) and variadic mode.
|
|
1862
|
+
*/
|
|
1863
|
+
const switchFn = {
|
|
1864
|
+
kind: 'reactive',
|
|
1865
|
+
execute: (args) => {
|
|
1866
|
+
if (args.length < 2) {
|
|
1867
|
+
throw new Error('switch requires at least 2 arguments: value and default');
|
|
1868
|
+
}
|
|
1869
|
+
const valueComputed = args[0];
|
|
1870
|
+
return computed(() => {
|
|
1871
|
+
const state = new FunctionExecutionStateBuilder();
|
|
1872
|
+
const valueState = valueComputed.get();
|
|
1873
|
+
const earlyReturn = state.checkReady(valueState);
|
|
1874
|
+
if (earlyReturn)
|
|
1875
|
+
return earlyReturn;
|
|
1876
|
+
const value = valueState.value;
|
|
1877
|
+
if (args.length === 3) {
|
|
1878
|
+
const casesState = args[1].get();
|
|
1879
|
+
const defaultState = args[2].get();
|
|
1880
|
+
const casesEarlyReturn = state.checkReady(casesState);
|
|
1881
|
+
if (casesEarlyReturn)
|
|
1882
|
+
return casesEarlyReturn;
|
|
1883
|
+
const defaultEarlyReturn = state.checkReady(defaultState);
|
|
1884
|
+
if (defaultEarlyReturn)
|
|
1885
|
+
return defaultEarlyReturn;
|
|
1886
|
+
const cases = casesState.value;
|
|
1887
|
+
if (Array.isArray(cases)) {
|
|
1888
|
+
if (cases.length % 2 !== 0) {
|
|
1889
|
+
throw new Error('Cases array should have even number of elements');
|
|
1890
|
+
}
|
|
1891
|
+
const matchIndex = cases.findIndex((caseValue, index) => index % 2 === 0 && caseValue === value);
|
|
1892
|
+
const returnValue = matchIndex >= 0 ? cases[matchIndex + 1] : defaultState.value;
|
|
1893
|
+
return state.success(returnValue);
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
const defaultComputed = args[args.length - 1];
|
|
1897
|
+
const caseArgs = args.slice(1, -1);
|
|
1898
|
+
if (caseArgs.length % 2 !== 0) {
|
|
1899
|
+
throw new Error('switch requires an even number of case arguments (key-value pairs)');
|
|
1900
|
+
}
|
|
1901
|
+
for (let i = 0; i < caseArgs.length; i += 2) {
|
|
1902
|
+
const caseKeyComputed = caseArgs[i];
|
|
1903
|
+
const caseValueComputed = caseArgs[i + 1];
|
|
1904
|
+
const caseKeyState = caseKeyComputed.get();
|
|
1905
|
+
const keyEarlyReturn = state.checkReady(caseKeyState);
|
|
1906
|
+
if (keyEarlyReturn)
|
|
1907
|
+
return keyEarlyReturn;
|
|
1908
|
+
const caseKey = caseKeyState.value;
|
|
1909
|
+
if (caseKey === value) {
|
|
1910
|
+
const caseValueState = caseValueComputed.get();
|
|
1911
|
+
const valueEarlyReturn = state.checkReady(caseValueState);
|
|
1912
|
+
if (valueEarlyReturn)
|
|
1913
|
+
return valueEarlyReturn;
|
|
1914
|
+
return state.success(caseValueState.value);
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
const defaultState = defaultComputed.get();
|
|
1918
|
+
const defaultEarlyReturn = state.checkReady(defaultState);
|
|
1919
|
+
if (defaultEarlyReturn)
|
|
1920
|
+
return defaultEarlyReturn;
|
|
1921
|
+
return state.success(defaultState.value);
|
|
1922
|
+
});
|
|
1923
|
+
},
|
|
1924
|
+
signature: '(...unknown) => unknown',
|
|
1925
|
+
};
|
|
1926
|
+
|
|
1927
|
+
function execute$2(value) {
|
|
1928
|
+
return value.toLowerCase();
|
|
1929
|
+
}
|
|
1930
|
+
const toLowerCase = {
|
|
1931
|
+
kind: 'imperative',
|
|
1932
|
+
execute: execute$2,
|
|
1933
|
+
signature: '(string) => string',
|
|
1934
|
+
};
|
|
1935
|
+
|
|
1936
|
+
function execute$1(value) {
|
|
1937
|
+
return value.toUpperCase();
|
|
1938
|
+
}
|
|
1939
|
+
const toUpperCase = {
|
|
1940
|
+
kind: 'imperative',
|
|
1941
|
+
execute: execute$1,
|
|
1942
|
+
signature: '(string) => string',
|
|
1943
|
+
};
|
|
1944
|
+
|
|
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
|
+
const formulaFunctions$1 = {
|
|
1955
|
+
// Collection functions
|
|
1956
|
+
collectValuesMap,
|
|
1957
|
+
collectValuesArray,
|
|
1958
|
+
propObject: collectValuesMap,
|
|
1959
|
+
propArray: collectValuesArray,
|
|
1960
|
+
findBy,
|
|
1961
|
+
findAllBy,
|
|
1962
|
+
at,
|
|
1963
|
+
not,
|
|
1964
|
+
equal,
|
|
1965
|
+
// Array element checkers (imperative)
|
|
1966
|
+
some,
|
|
1967
|
+
every,
|
|
1968
|
+
// Control flow (reactive with lazy evaluation)
|
|
1969
|
+
if: ifFn,
|
|
1970
|
+
and,
|
|
1971
|
+
or,
|
|
1972
|
+
switch: switchFn,
|
|
1973
|
+
// Utilities
|
|
1974
|
+
in: inFn,
|
|
1975
|
+
object: objectFn,
|
|
1976
|
+
bool,
|
|
1977
|
+
length,
|
|
1978
|
+
empty,
|
|
1979
|
+
concat,
|
|
1980
|
+
pluck,
|
|
1981
|
+
flatten,
|
|
1982
|
+
json,
|
|
1983
|
+
minus,
|
|
1984
|
+
plus,
|
|
1985
|
+
divide,
|
|
1986
|
+
multiply,
|
|
1987
|
+
running,
|
|
1988
|
+
get: getFn,
|
|
1989
|
+
id: idFn,
|
|
1990
|
+
repeat,
|
|
1991
|
+
emptyObject,
|
|
1992
|
+
arrayPluck,
|
|
1993
|
+
string,
|
|
1994
|
+
number,
|
|
1995
|
+
delay,
|
|
1996
|
+
evaluating,
|
|
1997
|
+
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,
|
|
2012
|
+
};
|
|
2013
|
+
|
|
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
|
+
},
|
|
2145
|
+
};
|
|
2146
|
+
|
|
2147
|
+
function autoRegisterCommonDefinitions() {
|
|
2148
|
+
DefinitionStore.getInstance().setComponents(unfoldComponentDefinitions(componentsDefinitions$2));
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
const componentsDefinitions$1 = {
|
|
2152
|
+
'browser.Styled': {
|
|
2153
|
+
properties: {
|
|
2154
|
+
'styleClass@string': null,
|
|
2155
|
+
},
|
|
2156
|
+
},
|
|
2157
|
+
'browser.Element': {
|
|
2158
|
+
traits: ['sys.Group', 'browser.Styled'],
|
|
2159
|
+
properties: {
|
|
2160
|
+
'tag@string': null,
|
|
2161
|
+
'innerHTML@string': null,
|
|
2162
|
+
},
|
|
2163
|
+
},
|
|
2164
|
+
'browser.Clickable': {
|
|
2165
|
+
traits: ['sys.Box'],
|
|
2166
|
+
properties: {
|
|
2167
|
+
'click@()': null,
|
|
2168
|
+
'stopPropagation@boolean': null,
|
|
2169
|
+
},
|
|
2170
|
+
},
|
|
2171
|
+
'browser.Focusable': {
|
|
2172
|
+
traits: ['sys.Box'],
|
|
2173
|
+
properties: {
|
|
2174
|
+
'tabIndex@number': null,
|
|
2175
|
+
'blur@command': null,
|
|
2176
|
+
},
|
|
2177
|
+
},
|
|
2178
|
+
'browser.Dialog': {
|
|
2179
|
+
traits: ['sys.Box', 'browser.Styled'],
|
|
2180
|
+
properties: {
|
|
2181
|
+
'close@(unknown)': null,
|
|
2182
|
+
},
|
|
2183
|
+
},
|
|
2184
|
+
};
|
|
2185
|
+
|
|
2186
|
+
function autoRegisterBrowserDefinitions() {
|
|
2187
|
+
DefinitionStore.getInstance().setComponents(unfoldComponentDefinitions(componentsDefinitions$1));
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
const componentsDefinitions = {
|
|
2191
|
+
'forms.Input': {
|
|
2192
|
+
properties: {
|
|
2193
|
+
'inputKey@string': null,
|
|
2194
|
+
'value@unknown': null,
|
|
2195
|
+
'setValue@(unknown)': `set('value', args[0])`,
|
|
2196
|
+
'disabled@boolean': false,
|
|
2197
|
+
'dirty@boolean': false,
|
|
2198
|
+
'touched@boolean': false,
|
|
2199
|
+
'touch@()': `set('touched', true)`,
|
|
2200
|
+
'validators@validator[]': [],
|
|
2201
|
+
'validity@forms.Validity': '=> validate(value, validators)',
|
|
2202
|
+
},
|
|
2203
|
+
},
|
|
2204
|
+
'forms.BooleanInput': {
|
|
2205
|
+
traits: ['forms.Input'],
|
|
2206
|
+
properties: {
|
|
2207
|
+
'value@boolean': null,
|
|
2208
|
+
'setValue@(boolean)': `set('value', args[0])`,
|
|
2209
|
+
},
|
|
2210
|
+
},
|
|
2211
|
+
'forms.StringInput': {
|
|
2212
|
+
traits: ['forms.Input'],
|
|
2213
|
+
properties: {
|
|
2214
|
+
'value@string': null,
|
|
2215
|
+
'setValue@(string)': `set('value', args[0])`,
|
|
2216
|
+
},
|
|
2217
|
+
},
|
|
2218
|
+
'forms.NumberInput': {
|
|
2219
|
+
traits: ['forms.Input'],
|
|
2220
|
+
properties: {
|
|
2221
|
+
'value@number': null,
|
|
2222
|
+
'setValue@(number)': `set('value', args[0])`,
|
|
2223
|
+
},
|
|
2224
|
+
},
|
|
2225
|
+
'forms.CustomInput': {
|
|
2226
|
+
traits: ['sys.Box', 'forms.Input'],
|
|
2227
|
+
properties: {},
|
|
2228
|
+
},
|
|
2229
|
+
'forms.InputComposite': {
|
|
2230
|
+
traits: ['forms.Input'],
|
|
2231
|
+
properties: {
|
|
2232
|
+
'touched@boolean': `=> some(propArray(':forms.Input', 'touched'))`,
|
|
2233
|
+
'dirty@boolean': `=> some(propArray(':forms.Input', 'dirty'))`,
|
|
2234
|
+
'subInputsValid@boolean': `=> every(propArray(':forms.Input', 'validity.valid'))`,
|
|
2235
|
+
'selfValidity@forms.Validity': '=> validate(value, validators)',
|
|
2236
|
+
'validity@forms.Validity': `=> { valid: every([selfValidity.valid, subInputsValid]), errors: selfValidity.errors }`,
|
|
2237
|
+
},
|
|
2238
|
+
},
|
|
2239
|
+
'forms.InputGroup': {
|
|
2240
|
+
traits: ['sys.Box', 'forms.InputComposite'],
|
|
2241
|
+
properties: {
|
|
2242
|
+
'value@unknown{}': {
|
|
2243
|
+
bindingType: 'object',
|
|
2244
|
+
componentSelector: ':forms.Input',
|
|
2245
|
+
mapping: {
|
|
2246
|
+
key: 'inputKey',
|
|
2247
|
+
value: 'value',
|
|
2248
|
+
},
|
|
2249
|
+
},
|
|
2250
|
+
},
|
|
2251
|
+
},
|
|
2252
|
+
'forms.BooleanInputGroup': {
|
|
2253
|
+
traits: ['sys.Box', 'forms.InputComposite'],
|
|
2254
|
+
properties: {
|
|
2255
|
+
'value@unknown#': {
|
|
2256
|
+
bindingType: 'selection',
|
|
2257
|
+
componentSelector: ':forms.Input',
|
|
2258
|
+
mapping: {
|
|
2259
|
+
key: 'inputKey',
|
|
2260
|
+
selected: 'value',
|
|
2261
|
+
},
|
|
2262
|
+
},
|
|
2263
|
+
},
|
|
2264
|
+
},
|
|
2265
|
+
'forms.InputArray': {
|
|
2266
|
+
traits: ['sys.Box', 'forms.InputComposite'],
|
|
2267
|
+
properties: {
|
|
2268
|
+
'value@unknown[]': {
|
|
2269
|
+
bindingType: 'array',
|
|
2270
|
+
componentSelector: ':forms.Input',
|
|
2271
|
+
mapping: {
|
|
2272
|
+
value: 'value',
|
|
2273
|
+
},
|
|
2274
|
+
},
|
|
2275
|
+
},
|
|
2276
|
+
},
|
|
2277
|
+
};
|
|
2278
|
+
|
|
2279
|
+
class ValidatorRegistry {
|
|
2280
|
+
static instance;
|
|
2281
|
+
validators = {};
|
|
2282
|
+
static getInstance() {
|
|
2283
|
+
if (!ValidatorRegistry.instance) {
|
|
2284
|
+
ValidatorRegistry.instance = new ValidatorRegistry();
|
|
2285
|
+
}
|
|
2286
|
+
return ValidatorRegistry.instance;
|
|
2287
|
+
}
|
|
2288
|
+
register(validatorType, validator) {
|
|
2289
|
+
this.validators[validatorType] = validator;
|
|
2290
|
+
}
|
|
2291
|
+
get(validatorType) {
|
|
2292
|
+
return this.validators[validatorType];
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2296
|
+
async function execute(value, validators) {
|
|
2297
|
+
const errors = { valid: true, errors: [] };
|
|
2298
|
+
for (const validator of validators) {
|
|
2299
|
+
try {
|
|
2300
|
+
const result = await runValidator(value, validator);
|
|
2301
|
+
if (result) {
|
|
2302
|
+
errors.valid = false;
|
|
2303
|
+
errors.errors.push(result);
|
|
2304
|
+
break;
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
catch (e) {
|
|
2308
|
+
log.error(`Error running validator "${validator.validatorType}":`, e);
|
|
2309
|
+
errors.valid = false;
|
|
2310
|
+
break;
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
return errors;
|
|
2314
|
+
}
|
|
2315
|
+
async function runValidator(value, validator) {
|
|
2316
|
+
const validatorDef = ValidatorRegistry.getInstance().get(validator.validatorType);
|
|
2317
|
+
if (!validatorDef) {
|
|
2318
|
+
throw new Error(`Validator "${validator.validatorType}" not registered`);
|
|
2319
|
+
}
|
|
2320
|
+
if (validator.disabled) {
|
|
2321
|
+
return null;
|
|
2322
|
+
}
|
|
2323
|
+
if (validatorDef.createMessage) {
|
|
2324
|
+
validator.message = validatorDef.createMessage(validator.params);
|
|
2325
|
+
}
|
|
2326
|
+
validator.message = replacePlaceholders(validator.message || validatorDef.defaultMessage, validator.params);
|
|
2327
|
+
const validationResult = await validatorDef.validate(value, validator);
|
|
2328
|
+
if (validationResult) {
|
|
2329
|
+
return { validator: validator.validatorType, message: validationResult.message };
|
|
2330
|
+
}
|
|
2331
|
+
return null;
|
|
2332
|
+
}
|
|
2333
|
+
function replacePlaceholders(template, params) {
|
|
2334
|
+
return template.replace(/\[\[(\w+)]]/g, (match, key) => {
|
|
2335
|
+
if (key in params) {
|
|
2336
|
+
return String(params[key]);
|
|
2337
|
+
}
|
|
2338
|
+
return match;
|
|
2339
|
+
});
|
|
2340
|
+
}
|
|
2341
|
+
const validate = {
|
|
2342
|
+
kind: 'imperative',
|
|
2343
|
+
execute,
|
|
2344
|
+
signature: '(unknown, unknown[]) => {valid: boolean, errors: {validator: string, message: string}[]}',
|
|
2345
|
+
};
|
|
2346
|
+
|
|
2347
|
+
const formulaFunctions = {
|
|
2348
|
+
validate: wrapImperativeAsReactive(validate),
|
|
2349
|
+
};
|
|
2350
|
+
|
|
2351
|
+
const pattern = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
|
2352
|
+
const email = {
|
|
2353
|
+
params: {},
|
|
2354
|
+
defaultMessage: 'The field must be a valid email address',
|
|
2355
|
+
validate(value, validator) {
|
|
2356
|
+
if (!value) {
|
|
2357
|
+
return null;
|
|
2358
|
+
}
|
|
2359
|
+
const invalid = typeof value !== 'string' || !value.toLowerCase().match(pattern);
|
|
2360
|
+
if (invalid) {
|
|
2361
|
+
return { message: validator.message };
|
|
2362
|
+
}
|
|
2363
|
+
return null;
|
|
2364
|
+
},
|
|
2365
|
+
};
|
|
2366
|
+
|
|
2367
|
+
const equals = {
|
|
2368
|
+
params: {
|
|
2369
|
+
value: { type: 'unknown' },
|
|
2370
|
+
},
|
|
2371
|
+
defaultMessage: 'The value must be equal to [[value]]',
|
|
2372
|
+
validate(value, validator) {
|
|
2373
|
+
if (!value) {
|
|
2374
|
+
return null;
|
|
2375
|
+
}
|
|
2376
|
+
const invalid = value !== validator.params.value;
|
|
2377
|
+
if (invalid) {
|
|
2378
|
+
return { message: validator.message };
|
|
2379
|
+
}
|
|
2380
|
+
return null;
|
|
2381
|
+
},
|
|
2382
|
+
};
|
|
2383
|
+
|
|
2384
|
+
const maxLength = {
|
|
2385
|
+
params: {
|
|
2386
|
+
maxLength: { type: 'number' },
|
|
2387
|
+
},
|
|
2388
|
+
defaultMessage: 'The field must have at most [[maxLength]] characters',
|
|
2389
|
+
validate(value, validator) {
|
|
2390
|
+
if (!value) {
|
|
2391
|
+
return null;
|
|
2392
|
+
}
|
|
2393
|
+
const invalid = typeof value !== 'string' || value.length > validator.params.maxLength;
|
|
2394
|
+
if (invalid) {
|
|
2395
|
+
return { message: validator.message };
|
|
2396
|
+
}
|
|
2397
|
+
return null;
|
|
2398
|
+
},
|
|
2399
|
+
};
|
|
2400
|
+
|
|
2401
|
+
const min = {
|
|
2402
|
+
params: {
|
|
2403
|
+
min: { type: 'number' },
|
|
2404
|
+
},
|
|
2405
|
+
defaultMessage: 'The value must be [[min]] or higher',
|
|
2406
|
+
validate(value, validator) {
|
|
2407
|
+
if (!value) {
|
|
2408
|
+
return null;
|
|
2409
|
+
}
|
|
2410
|
+
const invalid = typeof value !== 'number' || value < validator.params.min;
|
|
2411
|
+
if (invalid) {
|
|
2412
|
+
return { message: validator.message };
|
|
2413
|
+
}
|
|
2414
|
+
return null;
|
|
2415
|
+
},
|
|
2416
|
+
};
|
|
2417
|
+
|
|
2418
|
+
const minLength = {
|
|
2419
|
+
params: {
|
|
2420
|
+
minLength: { type: 'number' },
|
|
2421
|
+
},
|
|
2422
|
+
defaultMessage: `The field must have at least [[minLength]] characters`,
|
|
2423
|
+
validate(value, validator) {
|
|
2424
|
+
if (!value) {
|
|
2425
|
+
return null;
|
|
2426
|
+
}
|
|
2427
|
+
const invalid = typeof value !== 'string' || value.length < validator.params.minLength;
|
|
2428
|
+
if (invalid) {
|
|
2429
|
+
return { message: validator.message };
|
|
2430
|
+
}
|
|
2431
|
+
return null;
|
|
2432
|
+
},
|
|
2433
|
+
};
|
|
2434
|
+
|
|
2435
|
+
const required = {
|
|
2436
|
+
params: {},
|
|
2437
|
+
defaultMessage: 'The field is required',
|
|
2438
|
+
validate(value, validator) {
|
|
2439
|
+
const invalid = value === undefined || value === null || value === '' || Number.isNaN(value);
|
|
2440
|
+
if (invalid) {
|
|
2441
|
+
return { message: validator.message };
|
|
2442
|
+
}
|
|
2443
|
+
return null;
|
|
2444
|
+
},
|
|
2445
|
+
};
|
|
2446
|
+
|
|
2447
|
+
const validators = {
|
|
2448
|
+
required,
|
|
2449
|
+
email,
|
|
2450
|
+
min,
|
|
2451
|
+
maxLength,
|
|
2452
|
+
minLength,
|
|
2453
|
+
equals,
|
|
2454
|
+
};
|
|
2455
|
+
|
|
2456
|
+
function registerValidator(validatorType, def) {
|
|
2457
|
+
DefinitionStore.getInstance().setVariantShapeKind('validator', validatorType, {
|
|
2458
|
+
params: { type: 'object', fields: def.params },
|
|
2459
|
+
});
|
|
2460
|
+
ValidatorRegistry.getInstance().register(validatorType, def);
|
|
2461
|
+
}
|
|
2462
|
+
function registerValidators(validators) {
|
|
2463
|
+
for (const [validatorType, def] of Object.entries(validators)) {
|
|
2464
|
+
registerValidator(validatorType, def);
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
function registerShapes() {
|
|
2468
|
+
DefinitionStore.getInstance().setVariantShape('validator', {
|
|
2469
|
+
message: { type: 'string' },
|
|
2470
|
+
disabled: { type: 'boolean' },
|
|
2471
|
+
});
|
|
2472
|
+
DefinitionStore.getInstance().setShape('forms.Validity', {
|
|
2473
|
+
valid: { type: 'boolean' },
|
|
2474
|
+
errors: {
|
|
2475
|
+
type: 'array',
|
|
2476
|
+
item: {
|
|
2477
|
+
type: 'object',
|
|
2478
|
+
fields: {
|
|
2479
|
+
validator: { type: 'string' },
|
|
2480
|
+
message: { type: 'string' },
|
|
2481
|
+
},
|
|
2482
|
+
},
|
|
2483
|
+
},
|
|
2484
|
+
});
|
|
2485
|
+
}
|
|
2486
|
+
function autoRegisterFormsDefinitions() {
|
|
2487
|
+
registerShapes();
|
|
2488
|
+
registerValidators(validators);
|
|
2489
|
+
FunctionRegistry.getInstance().setFunctions(formulaFunctions);
|
|
2490
|
+
DefinitionStore.getInstance().setComponents(unfoldComponentDefinitions(componentsDefinitions));
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
function initRuntime() {
|
|
2494
|
+
componentTreeConfig.runCommand = runCommand;
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
/**
|
|
2498
|
+
* Generated bundle index. Do not edit.
|
|
2499
|
+
*/
|
|
2500
|
+
|
|
2501
|
+
export { activateNode, activateStructure, autoRegisterBrowserDefinitions, autoRegisterCommonDefinitions, autoRegisterFormsDefinitions, componentInstanceMap, debugName, initRuntime, registerCommonDefinitions, registerValidator, registerValidators, runCommand, templateLoadingTracker };
|
|
2502
|
+
//# sourceMappingURL=kaskad-core.mjs.map
|