form0-core 0.3.0 → 0.4.0
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 +63 -0
- package/package.json +5 -2
- package/src/builtins/builtin-definitions.js +320 -0
- package/src/builtins/choice/choicelabel.js +2 -17
- package/src/builtins/choice/choicelabels.js +2 -17
- package/src/builtins/choice/choicevalue.js +2 -20
- package/src/builtins/choice/choicevalues.js +2 -17
- package/src/builtins/choice/hasother.js +2 -20
- package/src/builtins/choice/other.js +2 -20
- package/src/builtins/control/eval.js +78 -31
- package/src/builtins/control/setresult.js +29 -17
- package/src/builtins/date/dateadd.js +24 -0
- package/src/builtins/date/days.js +22 -0
- package/src/builtins/event/control/off.js +3 -27
- package/src/builtins/event/control/on.js +3 -19
- package/src/builtins/event/event-operations-collector.js +33 -9
- package/src/builtins/event/field/setvalue.js +3 -32
- package/src/builtins/event/ui/alert.js +3 -11
- package/src/builtins/logical/and.js +2 -17
- package/src/builtins/logical/array.js +2 -35
- package/src/builtins/logical/count.js +2 -20
- package/src/builtins/logical/counta.js +2 -20
- package/src/builtins/logical/countblank.js +2 -20
- package/src/builtins/logical/if.js +2 -20
- package/src/builtins/logical/or.js +2 -17
- package/src/builtins/math/abs.js +2 -20
- package/src/builtins/math/ceiling.js +2 -20
- package/src/builtins/math/cos.js +2 -20
- package/src/builtins/math/round.js +2 -23
- package/src/builtins/math/sin.js +2 -20
- package/src/builtins/registry.js +23 -40
- package/src/builtins/schema/datanames.js +9 -20
- package/src/builtins/schema/form.js +2 -20
- package/src/builtins/string/upper.js +2 -20
- package/src/engine/calculation-diagnostics.js +107 -0
- package/src/engine/calculation.js +65 -32
- package/src/engine/conditions.js +69 -3
- package/src/engine/evaluator.js +56 -18
- package/src/engine/event-operation-scope.js +61 -0
- package/src/engine/event-source.js +164 -0
- package/src/engine/events.js +111 -23
- package/src/engine/form-engine.js +61 -16
- package/src/engine/warning-system.js +7 -5
- package/src/schema/field-specs.js +2 -2
- package/src/security/validation.js +10 -8
- package/src/utilities/calculation-editor.js +42 -6
- package/src/utilities/date-utils.js +72 -0
- package/src/builtins/docs/choice/choicelabel.mdx +0 -110
- package/src/builtins/docs/choice/choicelabels.mdx +0 -230
- package/src/builtins/docs/choice/choicevalue.mdx +0 -109
- package/src/builtins/docs/choice/choicevalues.mdx +0 -196
- package/src/builtins/docs/choice/hasother.mdx +0 -133
- package/src/builtins/docs/choice/other.mdx +0 -150
- package/src/builtins/docs/control/setresult.mdx +0 -203
- package/src/builtins/docs/event/field/setvalue.mdx +0 -239
- package/src/builtins/docs/index.mdx +0 -234
- package/src/builtins/docs/logical/and.mdx +0 -141
- package/src/builtins/docs/logical/if.mdx +0 -94
- package/src/builtins/docs/logical/or.mdx +0 -191
- package/src/builtins/docs/overview.mdx +0 -255
|
@@ -1,40 +1,33 @@
|
|
|
1
1
|
import { validateExpression } from '../../security/validation.js';
|
|
2
2
|
import { SAFE_SECURITY_CONFIG } from '../../security/config.js';
|
|
3
|
-
|
|
3
|
+
export { EVAL_METADATA } from '../builtin-definitions.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @builtin EVAL
|
|
7
|
-
*
|
|
7
|
+
* Name, signature, description, examples, and contexts: see builtin-definitions.js.
|
|
8
8
|
* @param {string} expression - The dynamic expression to evaluate
|
|
9
9
|
* @returns {*} The result of the evaluated expression
|
|
10
|
-
* @example
|
|
11
|
-
* // Dynamic field access
|
|
12
|
-
* EVAL('$' + dynamicFieldName)
|
|
13
|
-
* @example
|
|
14
|
-
* // Dynamic builtin calls
|
|
15
|
-
* EVAL('CHOICEVALUE($' + fieldVar + ')')
|
|
16
|
-
* @example
|
|
17
|
-
* // Computed field references
|
|
18
|
-
* EVAL('$city' + '_suffix')
|
|
19
10
|
*/
|
|
20
|
-
export const EVAL_METADATA = defineBuiltinMetadata({
|
|
21
|
-
name: 'EVAL',
|
|
22
|
-
category: 'control',
|
|
23
|
-
signature: 'EVAL(expression)',
|
|
24
|
-
description: 'Evaluate a dynamically built expression string in a restricted context.',
|
|
25
|
-
examples: ['EVAL("$" + dynamicFieldName)'],
|
|
26
|
-
contexts: [BUILTIN_CONTEXTS.CALCULATION, BUILTIN_CONTEXTS.EVENT],
|
|
27
|
-
});
|
|
28
|
-
|
|
29
11
|
// Global context for EVAL() - set during expression evaluation
|
|
30
12
|
let _evalContext = null;
|
|
13
|
+
let _evalReporting = null;
|
|
31
14
|
|
|
32
15
|
/**
|
|
33
16
|
* Set the context for EVAL() during expression evaluation
|
|
34
17
|
* Called internally by the expression evaluator
|
|
35
18
|
*/
|
|
36
|
-
export function __setEvalContext(context) {
|
|
19
|
+
export function __setEvalContext(context, reporting = null) {
|
|
20
|
+
const previousContext = _evalContext;
|
|
21
|
+
const previousReporting = _evalReporting;
|
|
37
22
|
_evalContext = context;
|
|
23
|
+
_evalReporting = reporting;
|
|
24
|
+
let restored = false;
|
|
25
|
+
return () => {
|
|
26
|
+
if (restored) return;
|
|
27
|
+
restored = true;
|
|
28
|
+
_evalContext = previousContext;
|
|
29
|
+
_evalReporting = previousReporting;
|
|
30
|
+
};
|
|
38
31
|
}
|
|
39
32
|
|
|
40
33
|
/**
|
|
@@ -43,24 +36,54 @@ export function __setEvalContext(context) {
|
|
|
43
36
|
*/
|
|
44
37
|
export function __clearEvalContext() {
|
|
45
38
|
_evalContext = null;
|
|
39
|
+
_evalReporting = null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function reportFailure(code, phase, message, consoleArgs, context = {}) {
|
|
43
|
+
_evalReporting?.onDiagnostic?.({
|
|
44
|
+
code,
|
|
45
|
+
phase,
|
|
46
|
+
message,
|
|
47
|
+
context: { source: 'EVAL', ...context },
|
|
48
|
+
});
|
|
49
|
+
if (!_evalReporting?.suppressConsole) console.warn(...consoleArgs);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function logDebug(...args) {
|
|
53
|
+
if (!_evalReporting?.suppressConsole) console.log(...args);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function referenceContext(reference) {
|
|
57
|
+
// Legacy EVAL also treats strings starting with '$' as reference lookups.
|
|
58
|
+
// Do not copy a whole expression into diagnostic metadata in that case.
|
|
59
|
+
return /^\$[a-zA-Z_][a-zA-Z0-9_]*$/.test(reference)
|
|
60
|
+
? { referencedFieldName: reference.slice(1) }
|
|
61
|
+
: {};
|
|
46
62
|
}
|
|
47
63
|
|
|
48
64
|
export const EVAL = (expression) => {
|
|
49
65
|
// Step 1: Basic validation
|
|
50
66
|
if (typeof expression !== 'string') {
|
|
51
|
-
|
|
67
|
+
reportFailure('eval_invalid_argument', 'validation', 'EVAL() requires a string expression', [
|
|
68
|
+
'[form0] EVAL() requires a string expression',
|
|
69
|
+
]);
|
|
52
70
|
return null;
|
|
53
71
|
}
|
|
54
72
|
|
|
55
73
|
if (!expression || expression.trim() === '') {
|
|
56
|
-
|
|
74
|
+
reportFailure('eval_invalid_argument', 'validation', 'EVAL() requires a non-empty expression', [
|
|
75
|
+
'[form0] EVAL() requires a non-empty expression',
|
|
76
|
+
]);
|
|
57
77
|
return null;
|
|
58
78
|
}
|
|
59
79
|
|
|
60
80
|
// Step 2: Security validation for EVAL() expressions
|
|
61
81
|
const securityValidation = validateEvalExpression(expression);
|
|
62
82
|
if (!securityValidation.valid) {
|
|
63
|
-
|
|
83
|
+
reportFailure('expression_validation_failed', 'validation', securityValidation.reason, [
|
|
84
|
+
'[form0] EVAL() security validation failed:',
|
|
85
|
+
securityValidation.reason,
|
|
86
|
+
]);
|
|
64
87
|
return null;
|
|
65
88
|
}
|
|
66
89
|
|
|
@@ -147,12 +170,13 @@ function validateEvalExpression(expression) {
|
|
|
147
170
|
* @returns {*} The evaluation result
|
|
148
171
|
*/
|
|
149
172
|
function evaluateHybridExpression(expression, formContext) {
|
|
173
|
+
let phase = 'runtime';
|
|
150
174
|
try {
|
|
151
175
|
// Step 3a: Handle simple string literals and field references directly
|
|
152
176
|
if (isSimpleStringLiteral(expression)) {
|
|
153
177
|
// Simple string literal - return as is
|
|
154
178
|
const result = expression;
|
|
155
|
-
|
|
179
|
+
logDebug('[form0] EVAL() simple string:', expression, '→', result);
|
|
156
180
|
return result;
|
|
157
181
|
}
|
|
158
182
|
|
|
@@ -160,10 +184,16 @@ function evaluateHybridExpression(expression, formContext) {
|
|
|
160
184
|
// Field reference - resolve from context
|
|
161
185
|
const fieldValue = formContext?.[expression];
|
|
162
186
|
if (fieldValue !== undefined) {
|
|
163
|
-
|
|
187
|
+
logDebug('[form0] EVAL() resolved field reference:', expression, '→', fieldValue);
|
|
164
188
|
return fieldValue;
|
|
165
189
|
} else {
|
|
166
|
-
|
|
190
|
+
reportFailure(
|
|
191
|
+
'eval_reference_unavailable',
|
|
192
|
+
'scope',
|
|
193
|
+
'EVAL() field reference is unavailable.',
|
|
194
|
+
['[form0] EVAL() field reference not found:', expression],
|
|
195
|
+
referenceContext(expression)
|
|
196
|
+
);
|
|
167
197
|
return null;
|
|
168
198
|
}
|
|
169
199
|
}
|
|
@@ -183,7 +213,9 @@ function evaluateHybridExpression(expression, formContext) {
|
|
|
183
213
|
const keys = Object.keys(basicContext);
|
|
184
214
|
const values = Object.values(basicContext);
|
|
185
215
|
|
|
216
|
+
phase = 'syntax';
|
|
186
217
|
const fn = new Function(...keys, `return (${expression});`);
|
|
218
|
+
phase = 'runtime';
|
|
187
219
|
const result = fn(...values);
|
|
188
220
|
|
|
189
221
|
// Step 3e: Check if result should be resolved as field reference
|
|
@@ -191,7 +223,7 @@ function evaluateHybridExpression(expression, formContext) {
|
|
|
191
223
|
// This is a field reference - resolve it from context
|
|
192
224
|
const fieldValue = formContext?.[result];
|
|
193
225
|
if (fieldValue !== undefined) {
|
|
194
|
-
|
|
226
|
+
logDebug(
|
|
195
227
|
'[form0] EVAL() resolved field reference:',
|
|
196
228
|
expression,
|
|
197
229
|
'→',
|
|
@@ -201,16 +233,31 @@ function evaluateHybridExpression(expression, formContext) {
|
|
|
201
233
|
);
|
|
202
234
|
return fieldValue;
|
|
203
235
|
} else {
|
|
204
|
-
|
|
236
|
+
reportFailure(
|
|
237
|
+
'eval_reference_unavailable',
|
|
238
|
+
'scope',
|
|
239
|
+
'EVAL() field reference is unavailable.',
|
|
240
|
+
['[form0] EVAL() field reference not found:', result],
|
|
241
|
+
referenceContext(result)
|
|
242
|
+
);
|
|
205
243
|
return null;
|
|
206
244
|
}
|
|
207
245
|
}
|
|
208
246
|
|
|
209
247
|
// Step 3f: Return string result for dataname references
|
|
210
|
-
|
|
248
|
+
logDebug('[form0] EVAL() built string:', expression, '→', result);
|
|
211
249
|
return result;
|
|
212
250
|
} catch (error) {
|
|
213
|
-
|
|
251
|
+
reportFailure(
|
|
252
|
+
phase === 'syntax'
|
|
253
|
+
? error instanceof SyntaxError
|
|
254
|
+
? 'invalid_syntax'
|
|
255
|
+
: 'compilation_error'
|
|
256
|
+
: 'runtime_exception',
|
|
257
|
+
phase,
|
|
258
|
+
error instanceof Error && error.message ? error.message : 'Unknown EVAL() runtime error.',
|
|
259
|
+
['[form0] EVAL() execution failed:', error.message]
|
|
260
|
+
);
|
|
214
261
|
return null;
|
|
215
262
|
}
|
|
216
263
|
}
|
|
@@ -1,30 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
export { SETRESULT_METADATA } from '../builtin-definitions.js';
|
|
2
2
|
|
|
3
3
|
// Global state for result management
|
|
4
4
|
let _resultSet = false;
|
|
5
5
|
let _resultValue;
|
|
6
|
+
let _resultScopeDepth = 0;
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* @builtin SETRESULT
|
|
9
|
-
*
|
|
10
|
+
* Name, signature, description, examples, and contexts: see builtin-definitions.js.
|
|
10
11
|
* @param {*} value - The value to set as the result
|
|
11
12
|
* @returns {*} The same value that was passed in
|
|
12
|
-
* @example
|
|
13
|
-
* // Set a calculation result
|
|
14
|
-
* SETRESULT(price * quantity * (1 + taxRate))
|
|
15
|
-
* @example
|
|
16
|
-
* // Set a conditional result
|
|
17
|
-
* SETRESULT(IF(isEligible, discount, 0))
|
|
18
13
|
*/
|
|
19
|
-
export const SETRESULT_METADATA = defineBuiltinMetadata({
|
|
20
|
-
name: 'SETRESULT',
|
|
21
|
-
category: 'control',
|
|
22
|
-
signature: 'SETRESULT(value)',
|
|
23
|
-
description: 'Set the final return value for multiline calculations.',
|
|
24
|
-
examples: ['SETRESULT($price * $quantity)'],
|
|
25
|
-
contexts: [BUILTIN_CONTEXTS.CALCULATION],
|
|
26
|
-
});
|
|
27
|
-
|
|
28
14
|
export const SETRESULT = (value) => {
|
|
29
15
|
_resultSet = true;
|
|
30
16
|
_resultValue = value;
|
|
@@ -43,6 +29,32 @@ export function __consumeResult() {
|
|
|
43
29
|
return { called, value };
|
|
44
30
|
}
|
|
45
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Start an isolated synchronous result scope and return its restoration function.
|
|
34
|
+
* Nested scopes restore the enclosing SETRESULT state; top-level scopes retain the
|
|
35
|
+
* legacy behavior of clearing stale state at both evaluation boundaries.
|
|
36
|
+
* @returns {() => void}
|
|
37
|
+
*/
|
|
38
|
+
export function __beginResultScope() {
|
|
39
|
+
const previous = { called: _resultSet, value: _resultValue };
|
|
40
|
+
const restorePrevious = _resultScopeDepth > 0;
|
|
41
|
+
_resultScopeDepth += 1;
|
|
42
|
+
__resetResult();
|
|
43
|
+
|
|
44
|
+
let restored = false;
|
|
45
|
+
return () => {
|
|
46
|
+
if (restored) return;
|
|
47
|
+
restored = true;
|
|
48
|
+
_resultScopeDepth = Math.max(0, _resultScopeDepth - 1);
|
|
49
|
+
if (restorePrevious) {
|
|
50
|
+
_resultSet = previous.called;
|
|
51
|
+
_resultValue = previous.value;
|
|
52
|
+
} else {
|
|
53
|
+
__resetResult();
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
46
58
|
/** Clear calculation result state without consuming it. */
|
|
47
59
|
export function __resetResult() {
|
|
48
60
|
_resultSet = false;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { formatIsoDate, isBlankDateArgument, parseIsoDate } from '../../utilities/date-utils.js';
|
|
2
|
+
|
|
3
|
+
export { DATEADD_METADATA } from '../builtin-definitions.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @builtin DATEADD
|
|
7
|
+
* Name, signature, description, examples, and contexts: see builtin-definitions.js.
|
|
8
|
+
* @param {string | null} date - A YYYY-MM-DD date
|
|
9
|
+
* @param {number | null} days - Whole number of days to add; negative values subtract
|
|
10
|
+
* @returns {string | null} The resulting YYYY-MM-DD date, or null when an argument is blank
|
|
11
|
+
*/
|
|
12
|
+
export const DATEADD = (date, days) => {
|
|
13
|
+
if (isBlankDateArgument(date) || isBlankDateArgument(days)) return null;
|
|
14
|
+
|
|
15
|
+
const dayNumber = parseIsoDate(date);
|
|
16
|
+
if (dayNumber === null) {
|
|
17
|
+
throw new Error('DATEADD requires a date in YYYY-MM-DD format');
|
|
18
|
+
}
|
|
19
|
+
if (!Number.isInteger(days)) {
|
|
20
|
+
throw new Error('DATEADD requires a whole number of days');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return formatIsoDate(dayNumber + days);
|
|
24
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { isBlankDateArgument, parseIsoDate } from '../../utilities/date-utils.js';
|
|
2
|
+
|
|
3
|
+
export { DAYS_METADATA } from '../builtin-definitions.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @builtin DAYS
|
|
7
|
+
* Name, signature, description, examples, and contexts: see builtin-definitions.js.
|
|
8
|
+
* @param {string | null} endDate - A YYYY-MM-DD date
|
|
9
|
+
* @param {string | null} startDate - A YYYY-MM-DD date
|
|
10
|
+
* @returns {number | null} Days from startDate to endDate, or null when an argument is blank
|
|
11
|
+
*/
|
|
12
|
+
export const DAYS = (endDate, startDate) => {
|
|
13
|
+
if (isBlankDateArgument(endDate) || isBlankDateArgument(startDate)) return null;
|
|
14
|
+
|
|
15
|
+
const end = parseIsoDate(endDate);
|
|
16
|
+
const start = parseIsoDate(startDate);
|
|
17
|
+
if (end === null || start === null) {
|
|
18
|
+
throw new Error('DAYS requires dates in YYYY-MM-DD format');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return end - start;
|
|
22
|
+
};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { __collectEventOperation } from '../event-operations-collector.js';
|
|
2
|
-
|
|
2
|
+
export { OFF_METADATA } from '../../builtin-definitions.js';
|
|
3
3
|
import { isValidEventType } from '../../../engine/event-registry.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* @builtin OFF
|
|
7
|
+
* Name, signature, description, examples, and contexts: see builtin-definitions.js.
|
|
7
8
|
* Removes event handlers within event context
|
|
8
9
|
* Automatically collects operation for platform execution
|
|
9
10
|
*
|
|
@@ -11,32 +12,7 @@ import { isValidEventType } from '../../../engine/event-registry.js';
|
|
|
11
12
|
* @param {string|Function} [fieldKeyOrCallback] - Field key for field events, or callback for global events
|
|
12
13
|
* @param {Function} [callback] - Callback function for field events (when fieldKeyOrCallback is a field key)
|
|
13
14
|
* @returns {Object} Operation descriptor for platform execution (for backward compatibility)
|
|
14
|
-
*
|
|
15
|
-
* @example
|
|
16
|
-
* // Remove specific global event handler
|
|
17
|
-
* OFF('load-record', specificCallback);
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* // Remove all global event handlers
|
|
21
|
-
* OFF('load-record');
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* // Remove specific field event handler
|
|
25
|
-
* OFF('change', 'city', specificCallback);
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* // Remove all field event handlers
|
|
29
|
-
* OFF('change', 'city');
|
|
30
15
|
*/
|
|
31
|
-
export const OFF_METADATA = defineBuiltinMetadata({
|
|
32
|
-
name: 'OFF',
|
|
33
|
-
category: 'event',
|
|
34
|
-
signature: 'OFF(eventType, fieldKeyOrCallback, callback)',
|
|
35
|
-
description: 'Remove event handlers within form event code.',
|
|
36
|
-
examples: ["OFF('change', 'city', specificCallback)"],
|
|
37
|
-
contexts: [BUILTIN_CONTEXTS.EVENT],
|
|
38
|
-
});
|
|
39
|
-
|
|
40
16
|
export function OFF(eventType, fieldKeyOrCallback, callback) {
|
|
41
17
|
if (typeof eventType !== 'string' || eventType.trim().length === 0) {
|
|
42
18
|
console.warn('[form0] OFF() requires a non-empty event type string');
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { __collectEventOperation } from '../event-operations-collector.js';
|
|
2
|
-
|
|
2
|
+
export { ON_METADATA } from '../../builtin-definitions.js';
|
|
3
3
|
import { isValidEventType } from '../../../engine/event-registry.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* @builtin ON
|
|
7
|
+
* Name, signature, description, examples, and contexts: see builtin-definitions.js.
|
|
7
8
|
* Registers an event handler within event context
|
|
8
9
|
* Automatically collects operation for platform execution
|
|
9
10
|
*
|
|
@@ -11,24 +12,7 @@ import { isValidEventType } from '../../../engine/event-registry.js';
|
|
|
11
12
|
* @param {string|Function} fieldKeyOrCallback - Field key for field events, or callback for global events
|
|
12
13
|
* @param {Function} [callback] - Callback function for field events
|
|
13
14
|
* @returns {Object} Operation descriptor for platform execution (for backward compatibility)
|
|
14
|
-
*
|
|
15
|
-
* @example
|
|
16
|
-
* // Register global event handler
|
|
17
|
-
* ON('load-record', function() { ALERT('Record loaded!'); });
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* // Register field-specific event handler
|
|
21
|
-
* ON('change', 'city', function(event) { ALERT('City changed!'); });
|
|
22
15
|
*/
|
|
23
|
-
export const ON_METADATA = defineBuiltinMetadata({
|
|
24
|
-
name: 'ON',
|
|
25
|
-
category: 'event',
|
|
26
|
-
signature: 'ON(eventType, fieldKeyOrCallback, callback)',
|
|
27
|
-
description: 'Register an event handler within form event code.',
|
|
28
|
-
examples: ["ON('change', 'city', function(event) { ALERT('City changed!'); })"],
|
|
29
|
-
contexts: [BUILTIN_CONTEXTS.EVENT],
|
|
30
|
-
});
|
|
31
|
-
|
|
32
16
|
export function ON(eventType, fieldKeyOrCallback, callback) {
|
|
33
17
|
if (typeof eventType !== 'string' || eventType.trim().length === 0) {
|
|
34
18
|
console.warn('[form0] ON() requires a non-empty event type string');
|
|
@@ -1,20 +1,44 @@
|
|
|
1
|
-
|
|
2
|
-
let
|
|
1
|
+
let activeScope = null;
|
|
2
|
+
let boundScope = null;
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Internal function to collect event operations
|
|
6
6
|
* @param {Object} operation - The operation descriptor to collect
|
|
7
7
|
*/
|
|
8
8
|
export function __collectEventOperation(operation) {
|
|
9
|
-
|
|
9
|
+
const scope = boundScope || activeScope;
|
|
10
|
+
scope?.collect(operation);
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
|
-
*
|
|
14
|
-
* @
|
|
14
|
+
* Run synchronous event work with a dispatch-owned operation scope.
|
|
15
|
+
* @param {Object} scope - The scope receiving operations
|
|
16
|
+
* @param {Function} work - Synchronous work to execute
|
|
17
|
+
* @returns {*} The work result
|
|
15
18
|
*/
|
|
16
|
-
export function
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
export function __runWithEventOperationScope(scope, work) {
|
|
20
|
+
const enclosingScope = activeScope;
|
|
21
|
+
activeScope = scope;
|
|
22
|
+
try {
|
|
23
|
+
return work();
|
|
24
|
+
} finally {
|
|
25
|
+
activeScope = enclosingScope;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Invoke a canonical builtin with the scope it was bound to.
|
|
31
|
+
* @param {Object} scope - The builtin's originating scope
|
|
32
|
+
* @param {Function} builtin - Canonical event builtin
|
|
33
|
+
* @param {Array} args - Builtin arguments
|
|
34
|
+
* @returns {*} The builtin result
|
|
35
|
+
*/
|
|
36
|
+
export function __invokeBoundEventBuiltin(scope, builtin, args) {
|
|
37
|
+
const enclosingScope = boundScope;
|
|
38
|
+
boundScope = scope;
|
|
39
|
+
try {
|
|
40
|
+
return builtin(...args);
|
|
41
|
+
} finally {
|
|
42
|
+
boundScope = enclosingScope;
|
|
43
|
+
}
|
|
20
44
|
}
|
|
@@ -1,45 +1,16 @@
|
|
|
1
1
|
import { __collectEventOperation } from '../event-operations-collector.js';
|
|
2
|
-
|
|
2
|
+
export { SETVALUE_METADATA } from '../../builtin-definitions.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* @builtin SETVALUE
|
|
6
|
+
* Name, signature, description, examples, and contexts: see builtin-definitions.js.
|
|
6
7
|
* Sets a field value within event context
|
|
7
8
|
* Automatically collects operation for platform execution
|
|
8
|
-
* Supports multiple field types:
|
|
9
|
-
* - TextField: string values
|
|
10
|
-
* - NumericField: numeric values
|
|
11
|
-
* - SingleChoiceField: string choice values
|
|
12
|
-
* - MultiChoiceField: array of string choice values
|
|
13
9
|
*
|
|
14
10
|
* @param {string} fieldDataName - The data name of the field to set
|
|
15
11
|
* @param {any} valueToSet - The value to set (string, number, array, etc.)
|
|
16
12
|
* @returns {Object} Operation descriptor for platform execution (for backward compatibility)
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
19
|
-
* // Set TextField value
|
|
20
|
-
* SETVALUE('field_dataname', 'value_to_set')
|
|
21
|
-
*
|
|
22
|
-
* @example
|
|
23
|
-
* // Set NumericField value
|
|
24
|
-
* SETVALUE('field_dataname', 12)
|
|
25
|
-
*
|
|
26
|
-
* @example
|
|
27
|
-
* // Set SingleChoiceField value
|
|
28
|
-
* SETVALUE('field_dataname', 'choicefield_value_to_set')
|
|
29
|
-
*
|
|
30
|
-
* @example
|
|
31
|
-
* // Set MultiChoiceField value
|
|
32
|
-
* SETVALUE('field_dataname', ['value1', 'value2', 'value3'])
|
|
33
13
|
*/
|
|
34
|
-
export const SETVALUE_METADATA = defineBuiltinMetadata({
|
|
35
|
-
name: 'SETVALUE',
|
|
36
|
-
category: 'event',
|
|
37
|
-
signature: 'SETVALUE(fieldDataName, valueToSet)',
|
|
38
|
-
description: 'Set a field value from a form event handler.',
|
|
39
|
-
examples: ["SETVALUE('field_dataname', 'value_to_set')"],
|
|
40
|
-
contexts: [BUILTIN_CONTEXTS.EVENT],
|
|
41
|
-
});
|
|
42
|
-
|
|
43
14
|
export function SETVALUE(fieldDataName, valueToSet) {
|
|
44
15
|
// Create operation descriptor
|
|
45
16
|
const operation = {
|
|
@@ -1,22 +1,14 @@
|
|
|
1
1
|
import { __collectEventOperation } from '../event-operations-collector.js';
|
|
2
|
-
|
|
2
|
+
export { ALERT_METADATA } from '../../builtin-definitions.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* @builtin ALERT
|
|
6
|
+
* Name, signature, description, examples, and contexts: see builtin-definitions.js.
|
|
6
7
|
* Automatically collects operation for platform execution
|
|
7
8
|
* @param {string} title - The title to display in the alert
|
|
8
9
|
* @param {string} message - The message to display in the alert (optional)
|
|
9
10
|
* @returns {Object} Operation descriptor for platform execution (for backward compatibility)
|
|
10
11
|
*/
|
|
11
|
-
export const ALERT_METADATA = defineBuiltinMetadata({
|
|
12
|
-
name: 'ALERT',
|
|
13
|
-
category: 'event',
|
|
14
|
-
signature: "ALERT(title, message = '')",
|
|
15
|
-
description: 'Display an alert from a form event handler.',
|
|
16
|
-
examples: ["ALERT('Saved', 'The record was saved successfully.')"],
|
|
17
|
-
contexts: [BUILTIN_CONTEXTS.EVENT],
|
|
18
|
-
});
|
|
19
|
-
|
|
20
12
|
export function ALERT(title, message = '') {
|
|
21
13
|
// Create operation descriptor
|
|
22
14
|
const operation = {
|
|
@@ -1,24 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
export { AND_METADATA } from '../builtin-definitions.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @builtin AND
|
|
5
|
-
*
|
|
5
|
+
* Name, signature, description, examples, and contexts: see builtin-definitions.js.
|
|
6
6
|
* @param {...*} args - Any number of arguments to evaluate
|
|
7
7
|
* @returns {boolean} True if all arguments are truthy, false otherwise
|
|
8
|
-
* @example
|
|
9
|
-
* // Check if user is adult and has premium account
|
|
10
|
-
* AND(user.age >= 18, user.isPremium)
|
|
11
|
-
* @example
|
|
12
|
-
* // Multiple conditions
|
|
13
|
-
* AND(name.length > 0, email.includes("@"), age >= 13)
|
|
14
8
|
*/
|
|
15
|
-
export const AND_METADATA = defineBuiltinMetadata({
|
|
16
|
-
name: 'AND',
|
|
17
|
-
category: 'logical',
|
|
18
|
-
signature: 'AND(...conditions)',
|
|
19
|
-
description: 'Return true only when all arguments are truthy.',
|
|
20
|
-
examples: ['AND($age >= 18, $country === "it")'],
|
|
21
|
-
contexts: [BUILTIN_CONTEXTS.CALCULATION, BUILTIN_CONTEXTS.EVENT],
|
|
22
|
-
});
|
|
23
|
-
|
|
24
9
|
export const AND = (...args) => args.every(Boolean);
|
|
@@ -1,44 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
export { ARRAY_METADATA } from '../builtin-definitions.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @builtin ARRAY
|
|
5
|
-
*
|
|
5
|
+
* Name, signature, description, examples, and contexts: see builtin-definitions.js.
|
|
6
6
|
* @param {...*} args - Values to create an array from. Accepts multiple arguments or a single string to parse
|
|
7
7
|
* @returns {Array} An array containing the arguments
|
|
8
|
-
* @example
|
|
9
|
-
* // Create array from multiple arguments
|
|
10
|
-
* ARRAY(1, 2, 3) // returns [1, 2, 3]
|
|
11
|
-
* @example
|
|
12
|
-
* // Pass an existing array (no flattening)
|
|
13
|
-
* ARRAY([1, 2, 3]) // returns [1, 2, 3]
|
|
14
|
-
* @example
|
|
15
|
-
* // Parse JSON array string (double quotes)
|
|
16
|
-
* ARRAY("[1, 2, 3]") // returns [1, 2, 3]
|
|
17
|
-
* @example
|
|
18
|
-
* // Parse array string with single quotes
|
|
19
|
-
* ARRAY("['a', 'b', 'c']") // returns ['a', 'b', 'c']
|
|
20
|
-
* @example
|
|
21
|
-
* // Parse simple comma-separated string (becomes array of strings)
|
|
22
|
-
* ARRAY("one, two, three") // returns ["one", "two", "three"]
|
|
23
|
-
* @example
|
|
24
|
-
* // Nested arrays are preserved (no flattening)
|
|
25
|
-
* ARRAY([1, [2, 3]]) // returns [1, [2, 3]]
|
|
26
|
-
* @example
|
|
27
|
-
* // Empty arguments
|
|
28
|
-
* ARRAY() // returns []
|
|
29
|
-
* @example
|
|
30
|
-
* // For complex parsing, JSON.parse() can be used:
|
|
31
|
-
* // EVAL('JSON.parse("[1,2,3]")')
|
|
32
8
|
*/
|
|
33
|
-
export const ARRAY_METADATA = defineBuiltinMetadata({
|
|
34
|
-
name: 'ARRAY',
|
|
35
|
-
category: 'logical',
|
|
36
|
-
signature: 'ARRAY(...values)',
|
|
37
|
-
description: 'Build an array from individual arguments.',
|
|
38
|
-
examples: ['ARRAY($city, $country)'],
|
|
39
|
-
contexts: [BUILTIN_CONTEXTS.CALCULATION, BUILTIN_CONTEXTS.EVENT],
|
|
40
|
-
});
|
|
41
|
-
|
|
42
9
|
export const ARRAY = (...args) => {
|
|
43
10
|
// Handle empty arguments
|
|
44
11
|
if (args.length === 0) {
|
|
@@ -1,29 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
export { COUNT_METADATA } from '../builtin-definitions.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @builtin COUNT
|
|
5
|
-
*
|
|
5
|
+
* Name, signature, description, examples, and contexts: see builtin-definitions.js.
|
|
6
6
|
* @param {Array} values - An array of values to count
|
|
7
7
|
* @returns {number} The count of numeric values in the array
|
|
8
|
-
* @example
|
|
9
|
-
* // Returns 5
|
|
10
|
-
* COUNT([11, 22, 33, 44, 55])
|
|
11
|
-
* @example
|
|
12
|
-
* // Returns 0 (only counts numeric values)
|
|
13
|
-
* COUNT(['a', 'b', 'c', 'd', 'e'])
|
|
14
|
-
* @example
|
|
15
|
-
* // Returns 3 (mixed array)
|
|
16
|
-
* COUNT([1, 'a', 2, null, 3])
|
|
17
8
|
*/
|
|
18
|
-
export const COUNT_METADATA = defineBuiltinMetadata({
|
|
19
|
-
name: 'COUNT',
|
|
20
|
-
category: 'logical',
|
|
21
|
-
signature: 'COUNT(values)',
|
|
22
|
-
description: 'Count numeric values.',
|
|
23
|
-
examples: ['COUNT(ARRAY($score_1, $score_2, $score_3))'],
|
|
24
|
-
contexts: [BUILTIN_CONTEXTS.CALCULATION, BUILTIN_CONTEXTS.EVENT],
|
|
25
|
-
});
|
|
26
|
-
|
|
27
9
|
export const COUNT = (values) => {
|
|
28
10
|
// Handle null/undefined input
|
|
29
11
|
if (!Array.isArray(values)) {
|
|
@@ -1,29 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
export { COUNTA_METADATA } from '../builtin-definitions.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @builtin COUNTA
|
|
5
|
-
*
|
|
5
|
+
* Name, signature, description, examples, and contexts: see builtin-definitions.js.
|
|
6
6
|
* @param {Array} values - An array of values to count
|
|
7
7
|
* @returns {number} The total count of items in the array
|
|
8
|
-
* @example
|
|
9
|
-
* // Returns 5
|
|
10
|
-
* COUNTA([11, 22, 33, 44, 55])
|
|
11
|
-
* @example
|
|
12
|
-
* // Returns 5 (counts all values regardless of type)
|
|
13
|
-
* COUNTA(['a', 'b', 'c', 'd', 'e'])
|
|
14
|
-
* @example
|
|
15
|
-
* // Returns 4 (counts all items including null)
|
|
16
|
-
* COUNTA([1, 'a', null, true])
|
|
17
8
|
*/
|
|
18
|
-
export const COUNTA_METADATA = defineBuiltinMetadata({
|
|
19
|
-
name: 'COUNTA',
|
|
20
|
-
category: 'logical',
|
|
21
|
-
signature: 'COUNTA(values)',
|
|
22
|
-
description: 'Count non-empty values.',
|
|
23
|
-
examples: ['COUNTA(ARRAY($first_name, $last_name, $email))'],
|
|
24
|
-
contexts: [BUILTIN_CONTEXTS.CALCULATION, BUILTIN_CONTEXTS.EVENT],
|
|
25
|
-
});
|
|
26
|
-
|
|
27
9
|
export const COUNTA = (values) => {
|
|
28
10
|
// Handle null/undefined input
|
|
29
11
|
if (!Array.isArray(values)) {
|