@nordcraft/core 1.0.95 → 1.0.97
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/dist/api/api.js +30 -10
- package/dist/api/api.js.map +1 -1
- package/dist/component/ToddleComponent.js +3 -3
- package/dist/component/ToddleComponent.js.map +1 -1
- package/dist/component/actionUtils.js +17 -1
- package/dist/component/actionUtils.js.map +1 -1
- package/dist/component/component.types.d.ts +6 -6
- package/dist/component/component.types.js.map +1 -1
- package/dist/formula/andFormula.d.ts +3 -0
- package/dist/formula/andFormula.js +25 -0
- package/dist/formula/andFormula.js.map +1 -0
- package/dist/formula/applyFormula.d.ts +2 -0
- package/dist/formula/applyFormula.js +49 -0
- package/dist/formula/applyFormula.js.map +1 -0
- package/dist/formula/arrayFormula.d.ts +2 -0
- package/dist/formula/arrayFormula.js +5 -0
- package/dist/formula/arrayFormula.js.map +1 -0
- package/dist/formula/formula.d.ts +8 -6
- package/dist/formula/formula.js +51 -172
- package/dist/formula/formula.js.map +1 -1
- package/dist/formula/formulaTypes.d.ts +2 -1
- package/dist/formula/formulaUtils.d.ts +1 -1
- package/dist/formula/formulaUtils.js +15 -0
- package/dist/formula/formulaUtils.js.map +1 -1
- package/dist/formula/functionFormula.d.ts +2 -0
- package/dist/formula/functionFormula.js +88 -0
- package/dist/formula/functionFormula.js.map +1 -0
- package/dist/formula/objectFormula.d.ts +2 -0
- package/dist/formula/objectFormula.js +8 -0
- package/dist/formula/objectFormula.js.map +1 -0
- package/dist/formula/orFormula.d.ts +3 -0
- package/dist/formula/orFormula.js +27 -0
- package/dist/formula/orFormula.js.map +1 -0
- package/dist/formula/pathFormula.d.ts +2 -0
- package/dist/formula/pathFormula.js +13 -0
- package/dist/formula/pathFormula.js.map +1 -0
- package/dist/formula/recordFormula.d.ts +2 -0
- package/dist/formula/recordFormula.js +8 -0
- package/dist/formula/recordFormula.js.map +1 -0
- package/dist/formula/switchFormula.d.ts +3 -0
- package/dist/formula/switchFormula.js +40 -0
- package/dist/formula/switchFormula.js.map +1 -0
- package/dist/types.d.ts +1 -1
- package/dist/utils/measure.js +5 -0
- package/dist/utils/measure.js.map +1 -1
- package/package.json +1 -1
- package/src/api/api.test.ts +22 -22
- package/src/api/api.ts +36 -10
- package/src/component/ToddleComponent.actionReferences.test.ts +37 -0
- package/src/component/ToddleComponent.ts +9 -7
- package/src/component/actionUtils.ts +17 -1
- package/src/component/component.types.ts +8 -4
- package/src/formula/andFormula.test.ts +112 -0
- package/src/formula/andFormula.ts +33 -0
- package/src/formula/applyFormula.test.ts +151 -0
- package/src/formula/applyFormula.ts +72 -0
- package/src/formula/arrayFormula.test.ts +52 -0
- package/src/formula/arrayFormula.ts +14 -0
- package/src/formula/formula.ts +80 -206
- package/src/formula/formulaTypes.ts +7 -1
- package/src/formula/formulaUtils.ts +18 -1
- package/src/formula/functionFormula.test.ts +89 -0
- package/src/formula/functionFormula.ts +118 -0
- package/src/formula/objectFormula.test.ts +56 -0
- package/src/formula/objectFormula.ts +17 -0
- package/src/formula/orFormula.test.ts +113 -0
- package/src/formula/orFormula.ts +33 -0
- package/src/formula/pathFormula.test.ts +35 -0
- package/src/formula/pathFormula.ts +17 -0
- package/src/formula/recordFormula.test.ts +56 -0
- package/src/formula/recordFormula.ts +17 -0
- package/src/formula/switchFormula.test.ts +122 -0
- package/src/formula/switchFormula.ts +54 -0
- package/src/formula/testUtils.test.ts +68 -0
- package/src/types.ts +1 -1
- package/src/utils/measure.ts +6 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test'
|
|
2
|
+
import type { ComponentFormula } from '../component/component.types'
|
|
3
|
+
import { applyApplyFormula } from './applyFormula'
|
|
4
|
+
import {
|
|
5
|
+
applyFormula,
|
|
6
|
+
type ApplyOperation,
|
|
7
|
+
type FormulaContext,
|
|
8
|
+
} from './formula'
|
|
9
|
+
import { valueFormula } from './formulaUtils'
|
|
10
|
+
import { createTestFormulaContext } from './testUtils.test'
|
|
11
|
+
|
|
12
|
+
describe('applyApplyFormula', () => {
|
|
13
|
+
it('applies a simple value formula from the component', () => {
|
|
14
|
+
const componentFormula: ComponentFormula = {
|
|
15
|
+
name: 'isSimple',
|
|
16
|
+
formula: valueFormula(true),
|
|
17
|
+
}
|
|
18
|
+
const formula: ApplyOperation = {
|
|
19
|
+
type: 'apply',
|
|
20
|
+
name: 'isSimple',
|
|
21
|
+
arguments: [],
|
|
22
|
+
}
|
|
23
|
+
const ctx: FormulaContext = {
|
|
24
|
+
...createTestFormulaContext(),
|
|
25
|
+
component: {
|
|
26
|
+
formulas: { isSimple: componentFormula },
|
|
27
|
+
name: 'TestComponent',
|
|
28
|
+
attributes: {},
|
|
29
|
+
variables: {},
|
|
30
|
+
apis: {},
|
|
31
|
+
nodes: {},
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
// The result should be the result of the inner formula (here, just the value of b)
|
|
35
|
+
expect(applyFormula(formula, ctx, [])).toEqual(true)
|
|
36
|
+
})
|
|
37
|
+
it('applies a formula from the component with arguments', () => {
|
|
38
|
+
const componentFormula: ComponentFormula = {
|
|
39
|
+
name: 'sum',
|
|
40
|
+
formula: {
|
|
41
|
+
type: 'function',
|
|
42
|
+
name: '@toddle/sum',
|
|
43
|
+
arguments: [
|
|
44
|
+
{
|
|
45
|
+
name: 'Array',
|
|
46
|
+
formula: {
|
|
47
|
+
type: 'array',
|
|
48
|
+
arguments: [
|
|
49
|
+
{
|
|
50
|
+
formula: { type: 'path', path: ['Args', 'a'] },
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
formula: { type: 'path', path: ['Args', 'b'] },
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
type: { type: 'Array' },
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
arguments: [
|
|
62
|
+
{ name: 'a', testValue: 2 },
|
|
63
|
+
{ name: 'b', testValue: 3 },
|
|
64
|
+
],
|
|
65
|
+
}
|
|
66
|
+
const formula: ApplyOperation = {
|
|
67
|
+
type: 'apply',
|
|
68
|
+
name: 'sum',
|
|
69
|
+
arguments: [
|
|
70
|
+
{ name: 'a', formula: valueFormula(2) },
|
|
71
|
+
{ name: 'b', formula: valueFormula(3) },
|
|
72
|
+
],
|
|
73
|
+
}
|
|
74
|
+
const ctx: FormulaContext = {
|
|
75
|
+
...createTestFormulaContext(),
|
|
76
|
+
component: {
|
|
77
|
+
formulas: { sum: componentFormula },
|
|
78
|
+
name: 'TestComponent',
|
|
79
|
+
attributes: {},
|
|
80
|
+
variables: {},
|
|
81
|
+
apis: {},
|
|
82
|
+
nodes: {},
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
// The result should be the result of the inner formula (here, just the value of b)
|
|
86
|
+
expect(applyFormula(formula, ctx, [])).toEqual(5)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('returns null if the formula does not exist in the component', () => {
|
|
90
|
+
const formula: ApplyOperation = {
|
|
91
|
+
type: 'apply',
|
|
92
|
+
name: 'missing',
|
|
93
|
+
arguments: [],
|
|
94
|
+
}
|
|
95
|
+
const ctx: FormulaContext = {
|
|
96
|
+
...createTestFormulaContext(),
|
|
97
|
+
component: { formulas: {} } as any,
|
|
98
|
+
env: { logErrors: true } as any,
|
|
99
|
+
}
|
|
100
|
+
expect(applyApplyFormula(formula, ctx)).toBeNull()
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('returns cached result if available', () => {
|
|
104
|
+
const componentFormula = {
|
|
105
|
+
name: 'cached',
|
|
106
|
+
formula: valueFormula('cached-result'),
|
|
107
|
+
}
|
|
108
|
+
const formula: ApplyOperation = {
|
|
109
|
+
type: 'apply',
|
|
110
|
+
name: 'cached',
|
|
111
|
+
arguments: [],
|
|
112
|
+
}
|
|
113
|
+
const cache = new Map()
|
|
114
|
+
cache.set({}, { hit: true, data: 'from-cache' })
|
|
115
|
+
const ctx: FormulaContext = {
|
|
116
|
+
...createTestFormulaContext(),
|
|
117
|
+
component: {
|
|
118
|
+
formulas: { cached: componentFormula },
|
|
119
|
+
} as any,
|
|
120
|
+
formulaCache: {
|
|
121
|
+
cached: {
|
|
122
|
+
get: () => ({ hit: true, data: 'from-cache' }),
|
|
123
|
+
set: () => {},
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
}
|
|
127
|
+
expect(applyApplyFormula(formula, ctx)).toBe('from-cache')
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('can use built in formulas', () => {
|
|
131
|
+
const formula = {
|
|
132
|
+
type: 'function',
|
|
133
|
+
name: '@toddle/string',
|
|
134
|
+
arguments: [
|
|
135
|
+
{
|
|
136
|
+
name: 'Input',
|
|
137
|
+
formula: { type: 'value', value: 'test' },
|
|
138
|
+
type: { type: 'Any' },
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
display_name: 'String',
|
|
142
|
+
}
|
|
143
|
+
const ctx: FormulaContext = {
|
|
144
|
+
...createTestFormulaContext(),
|
|
145
|
+
component: {
|
|
146
|
+
formulas: {},
|
|
147
|
+
} as any,
|
|
148
|
+
}
|
|
149
|
+
expect(applyFormula(formula as any, ctx)).toBe('test')
|
|
150
|
+
})
|
|
151
|
+
})
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import { measure } from '../utils/measure'
|
|
3
|
+
import {
|
|
4
|
+
applyFormula,
|
|
5
|
+
type ApplyOperation,
|
|
6
|
+
type FormulaContext,
|
|
7
|
+
} from './formula'
|
|
8
|
+
|
|
9
|
+
export const applyApplyFormula = (
|
|
10
|
+
formula: ApplyOperation,
|
|
11
|
+
ctx: FormulaContext,
|
|
12
|
+
) => {
|
|
13
|
+
const componentFormula = ctx.component?.formulas?.[formula.name]
|
|
14
|
+
if (!componentFormula) {
|
|
15
|
+
if (ctx.env?.logErrors) {
|
|
16
|
+
console.log(
|
|
17
|
+
'Component does not have a formula with the name ',
|
|
18
|
+
formula.name,
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
const stopMeasure = measure(`Formula: ${componentFormula.name}`, {
|
|
24
|
+
formula,
|
|
25
|
+
component: ctx.component?.name,
|
|
26
|
+
})
|
|
27
|
+
const Input = Object.fromEntries(
|
|
28
|
+
(formula.arguments ?? []).map((arg, i) =>
|
|
29
|
+
arg.isFunction
|
|
30
|
+
? [
|
|
31
|
+
arg.name,
|
|
32
|
+
(Args: any) =>
|
|
33
|
+
applyFormula(
|
|
34
|
+
arg.formula,
|
|
35
|
+
{
|
|
36
|
+
...ctx,
|
|
37
|
+
data: {
|
|
38
|
+
...ctx.data,
|
|
39
|
+
Args: ctx.data.Args
|
|
40
|
+
? { ...Args, '@toddle.parent': ctx.data.Args }
|
|
41
|
+
: Args,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
['arguments', i],
|
|
45
|
+
),
|
|
46
|
+
]
|
|
47
|
+
: [arg.name, applyFormula(arg.formula, ctx, ['arguments', i])],
|
|
48
|
+
),
|
|
49
|
+
)
|
|
50
|
+
const data = {
|
|
51
|
+
...ctx.data,
|
|
52
|
+
Args: ctx.data.Args ? { ...Input, '@toddle.parent': ctx.data.Args } : Input,
|
|
53
|
+
}
|
|
54
|
+
const cache = ctx.formulaCache?.[formula.name]?.get(data)
|
|
55
|
+
|
|
56
|
+
if (cache?.hit) {
|
|
57
|
+
stopMeasure({ cache: 'hit' })
|
|
58
|
+
return cache.data
|
|
59
|
+
} else {
|
|
60
|
+
const result = applyFormula(
|
|
61
|
+
componentFormula.formula,
|
|
62
|
+
{
|
|
63
|
+
...ctx,
|
|
64
|
+
data,
|
|
65
|
+
},
|
|
66
|
+
['formula'],
|
|
67
|
+
)
|
|
68
|
+
ctx.formulaCache?.[formula.name]?.set(data, result)
|
|
69
|
+
stopMeasure({ cache: 'miss' })
|
|
70
|
+
return result
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test'
|
|
2
|
+
import { applyArrayFormula } from './arrayFormula'
|
|
3
|
+
import type { ArrayOperation } from './formula'
|
|
4
|
+
import { valueFormula } from './formulaUtils'
|
|
5
|
+
import {
|
|
6
|
+
createTestFormulaContext,
|
|
7
|
+
createTestFormulaContextForAllPaths,
|
|
8
|
+
} from './testUtils.test'
|
|
9
|
+
|
|
10
|
+
describe('applyArrayFormula', () => {
|
|
11
|
+
it('returns an array with evaluated values', () => {
|
|
12
|
+
const formula: ArrayOperation = {
|
|
13
|
+
type: 'array',
|
|
14
|
+
arguments: [
|
|
15
|
+
{ formula: valueFormula(1) },
|
|
16
|
+
{ formula: valueFormula('hello') },
|
|
17
|
+
{ formula: valueFormula(true) },
|
|
18
|
+
],
|
|
19
|
+
}
|
|
20
|
+
const ctx = createTestFormulaContext()
|
|
21
|
+
expect(applyArrayFormula(formula, ctx)).toEqual([1, 'hello', true])
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('returns an empty array if arguments is empty', () => {
|
|
25
|
+
const formula: ArrayOperation = {
|
|
26
|
+
type: 'array',
|
|
27
|
+
arguments: [],
|
|
28
|
+
}
|
|
29
|
+
const ctx = createTestFormulaContext()
|
|
30
|
+
expect(applyArrayFormula(formula, ctx)).toEqual([])
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('evaluates all formulas and reports results in "report" mode', () => {
|
|
34
|
+
const formula: ArrayOperation = {
|
|
35
|
+
type: 'array',
|
|
36
|
+
arguments: [
|
|
37
|
+
{ formula: valueFormula('foo') },
|
|
38
|
+
{ formula: valueFormula('bar') },
|
|
39
|
+
],
|
|
40
|
+
}
|
|
41
|
+
const results: Record<string, any> = {}
|
|
42
|
+
const ctx = createTestFormulaContextForAllPaths(
|
|
43
|
+
{},
|
|
44
|
+
(path, result) => (results[path.join('/')] = result),
|
|
45
|
+
)
|
|
46
|
+
expect(applyArrayFormula(formula, ctx)).toEqual(['foo', 'bar'])
|
|
47
|
+
expect(results).toMatchObject({
|
|
48
|
+
'arguments/0/formula': 'foo',
|
|
49
|
+
'arguments/1/formula': 'bar',
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
applyFormula,
|
|
3
|
+
type ArrayOperation,
|
|
4
|
+
type FormulaContext,
|
|
5
|
+
} from './formula'
|
|
6
|
+
|
|
7
|
+
export const applyArrayFormula = (
|
|
8
|
+
formula: ArrayOperation,
|
|
9
|
+
ctx: FormulaContext,
|
|
10
|
+
) => {
|
|
11
|
+
return (formula.arguments ?? []).map((entry, i) =>
|
|
12
|
+
applyFormula(entry.formula, ctx, ['arguments', i, 'formula']),
|
|
13
|
+
)
|
|
14
|
+
}
|
package/src/formula/formula.ts
CHANGED
|
@@ -2,19 +2,35 @@
|
|
|
2
2
|
import type { Component, ComponentData } from '../component/component.types'
|
|
3
3
|
import type {
|
|
4
4
|
CustomFormulaHandler,
|
|
5
|
-
FormulaHandler,
|
|
6
5
|
FormulaLookup,
|
|
7
6
|
NordcraftMetadata,
|
|
8
7
|
Nullable,
|
|
9
8
|
Runtime,
|
|
10
|
-
Toddle,
|
|
11
9
|
} from '../types'
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
10
|
+
import { isDefined } from '../utils/util'
|
|
11
|
+
import { applyAndFormula, applyEvaluateAllAndFormula } from './andFormula'
|
|
12
|
+
import { applyApplyFormula } from './applyFormula'
|
|
13
|
+
import { applyArrayFormula } from './arrayFormula'
|
|
14
|
+
import {
|
|
15
|
+
type FormulaEvaluationReporter,
|
|
16
|
+
type PluginFormula,
|
|
17
|
+
type ToddleFormula,
|
|
18
|
+
} from './formulaTypes'
|
|
19
|
+
import { applyFunctionFormula } from './functionFormula'
|
|
20
|
+
import { applyObjectFormula } from './objectFormula'
|
|
21
|
+
import { applyEvaluateAllOrFormula, applyOrFormula } from './orFormula'
|
|
22
|
+
import { applyPathFormula } from './pathFormula'
|
|
23
|
+
import { applyRecordFormula } from './recordFormula'
|
|
24
|
+
import {
|
|
25
|
+
applyEvaluateAllSwitchFormula,
|
|
26
|
+
applySwitchFormula,
|
|
27
|
+
} from './switchFormula'
|
|
28
|
+
|
|
29
|
+
// As we are evaluating all branches of "if", "or" & "and" formulas when reportFormulaEvaluation is provided,
|
|
30
|
+
// we need to limit the depth to infinite loops as exit conditions are no longer used in recursive formulas.
|
|
31
|
+
const MAX_REPORT_DEPTH = 64
|
|
15
32
|
|
|
16
33
|
// Define the some objects types as union of ServerSide and ClientSide runtime types as applyFormula is used in both
|
|
17
|
-
declare const document: Document | undefined
|
|
18
34
|
type ShadowRoot = DocumentFragment
|
|
19
35
|
|
|
20
36
|
interface BaseOperation extends NordcraftMetadata {
|
|
@@ -23,7 +39,7 @@ interface BaseOperation extends NordcraftMetadata {
|
|
|
23
39
|
|
|
24
40
|
export interface PathOperation extends BaseOperation {
|
|
25
41
|
type: 'path'
|
|
26
|
-
path: string
|
|
42
|
+
path: Array<string | number>
|
|
27
43
|
}
|
|
28
44
|
|
|
29
45
|
export interface FunctionArgument {
|
|
@@ -79,7 +95,13 @@ export interface ValueOperation extends BaseOperation {
|
|
|
79
95
|
value: ValueOperationValue
|
|
80
96
|
}
|
|
81
97
|
|
|
82
|
-
export type ValueOperationValue =
|
|
98
|
+
export type ValueOperationValue =
|
|
99
|
+
| string
|
|
100
|
+
| number
|
|
101
|
+
| boolean
|
|
102
|
+
| null
|
|
103
|
+
| object
|
|
104
|
+
| undefined
|
|
83
105
|
|
|
84
106
|
export interface SwitchOperation extends BaseOperation {
|
|
85
107
|
type: 'switch'
|
|
@@ -104,7 +126,7 @@ export type Formula =
|
|
|
104
126
|
| ValueOperation
|
|
105
127
|
| ApplyOperation
|
|
106
128
|
|
|
107
|
-
export
|
|
129
|
+
export interface FormulaContext {
|
|
108
130
|
component: Component | undefined
|
|
109
131
|
formulaCache?: Nullable<
|
|
110
132
|
Record<
|
|
@@ -123,6 +145,8 @@ export type FormulaContext = {
|
|
|
123
145
|
getCustomFormula: CustomFormulaHandler
|
|
124
146
|
errors: Error[]
|
|
125
147
|
}
|
|
148
|
+
jsonPath?: Array<string | number> | undefined
|
|
149
|
+
reportFormulaEvaluation?: FormulaEvaluationReporter | undefined
|
|
126
150
|
env: ToddleEnv | undefined
|
|
127
151
|
}
|
|
128
152
|
|
|
@@ -185,222 +209,70 @@ export const isToddleFormula = <Handler>(
|
|
|
185
209
|
|
|
186
210
|
export function applyFormula(
|
|
187
211
|
formula: Formula | string | number | undefined | null | boolean,
|
|
188
|
-
|
|
212
|
+
_ctx: FormulaContext,
|
|
213
|
+
extendedPath?: Array<string | number> | undefined,
|
|
189
214
|
): any {
|
|
215
|
+
const path = [...(_ctx?.jsonPath ?? []), ...(extendedPath ?? [])]
|
|
216
|
+
const ctx = { ..._ctx, jsonPath: path }
|
|
217
|
+
const report = (value: any, p: Array<string | number> = path) => {
|
|
218
|
+
ctx?.reportFormulaEvaluation?.(p, value, ctx)
|
|
219
|
+
return value
|
|
220
|
+
}
|
|
221
|
+
|
|
190
222
|
if (!isFormula(formula)) {
|
|
191
|
-
return formula
|
|
223
|
+
return report(formula)
|
|
192
224
|
}
|
|
193
225
|
try {
|
|
194
226
|
switch (formula.type) {
|
|
195
|
-
case 'value':
|
|
196
|
-
return formula.value
|
|
227
|
+
case 'value': {
|
|
228
|
+
return report(formula.value)
|
|
229
|
+
}
|
|
197
230
|
case 'path': {
|
|
198
|
-
|
|
199
|
-
for (const key of formula.path) {
|
|
200
|
-
if (input && typeof input === 'object') {
|
|
201
|
-
input = input[key]
|
|
202
|
-
} else {
|
|
203
|
-
return null
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
return input
|
|
231
|
+
return report(applyPathFormula(formula, ctx.data))
|
|
208
232
|
}
|
|
209
233
|
case 'switch': {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
234
|
+
if (
|
|
235
|
+
ctx.reportFormulaEvaluation &&
|
|
236
|
+
ctx.jsonPath.length < MAX_REPORT_DEPTH
|
|
237
|
+
) {
|
|
238
|
+
return report(applyEvaluateAllSwitchFormula(formula, ctx))
|
|
214
239
|
}
|
|
215
|
-
return
|
|
240
|
+
return applySwitchFormula(formula, ctx)
|
|
216
241
|
}
|
|
217
242
|
case 'or': {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
243
|
+
if (
|
|
244
|
+
ctx.reportFormulaEvaluation &&
|
|
245
|
+
ctx.jsonPath.length < MAX_REPORT_DEPTH
|
|
246
|
+
) {
|
|
247
|
+
return report(applyEvaluateAllOrFormula(formula, ctx))
|
|
222
248
|
}
|
|
223
|
-
return
|
|
249
|
+
return applyOrFormula(formula, ctx)
|
|
224
250
|
}
|
|
225
251
|
case 'and': {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
252
|
+
if (
|
|
253
|
+
ctx.reportFormulaEvaluation &&
|
|
254
|
+
ctx.jsonPath.length < MAX_REPORT_DEPTH
|
|
255
|
+
) {
|
|
256
|
+
return report(applyEvaluateAllAndFormula(formula, ctx))
|
|
230
257
|
}
|
|
231
|
-
return
|
|
258
|
+
return applyAndFormula(formula, ctx)
|
|
259
|
+
}
|
|
260
|
+
case 'object': {
|
|
261
|
+
return report(applyObjectFormula(formula, ctx))
|
|
262
|
+
}
|
|
263
|
+
case 'record': {
|
|
264
|
+
// object used to be called record, there are still examples in the wild.
|
|
265
|
+
return report(applyRecordFormula(formula, ctx))
|
|
266
|
+
}
|
|
267
|
+
case 'array': {
|
|
268
|
+
return report(applyArrayFormula(formula, ctx))
|
|
232
269
|
}
|
|
233
270
|
case 'function': {
|
|
234
|
-
|
|
235
|
-
formula,
|
|
236
|
-
component: ctx.component?.name,
|
|
237
|
-
})
|
|
238
|
-
const packageName = formula.package ?? ctx.package ?? undefined
|
|
239
|
-
const newFunc = (
|
|
240
|
-
ctx.toddle ??
|
|
241
|
-
((globalThis as any).toddle as Toddle<unknown, unknown> | undefined)
|
|
242
|
-
)?.getCustomFormula(formula.name, packageName)
|
|
243
|
-
if (isDefined(newFunc)) {
|
|
244
|
-
ctx.package = packageName
|
|
245
|
-
const args = (formula.arguments ?? []).reduce<
|
|
246
|
-
Record<string, unknown>
|
|
247
|
-
>(
|
|
248
|
-
(args, arg, i) => ({
|
|
249
|
-
...args,
|
|
250
|
-
[arg.name ?? `${i}`]: arg.isFunction
|
|
251
|
-
? (Args: any) =>
|
|
252
|
-
applyFormula(arg.formula, {
|
|
253
|
-
...ctx,
|
|
254
|
-
data: {
|
|
255
|
-
...ctx.data,
|
|
256
|
-
Args: ctx.data.Args
|
|
257
|
-
? { ...Args, '@toddle.parent': ctx.data.Args }
|
|
258
|
-
: Args,
|
|
259
|
-
},
|
|
260
|
-
})
|
|
261
|
-
: applyFormula(arg.formula, ctx),
|
|
262
|
-
}),
|
|
263
|
-
{},
|
|
264
|
-
)
|
|
265
|
-
try {
|
|
266
|
-
if (isToddleFormula(newFunc)) {
|
|
267
|
-
return applyFormula(newFunc.formula, {
|
|
268
|
-
...ctx,
|
|
269
|
-
data: { ...ctx.data, Args: args },
|
|
270
|
-
})
|
|
271
|
-
} else {
|
|
272
|
-
return newFunc.handler(args, {
|
|
273
|
-
root: ctx.root ?? document,
|
|
274
|
-
env: ctx.env,
|
|
275
|
-
} as any)
|
|
276
|
-
}
|
|
277
|
-
} catch (e) {
|
|
278
|
-
ctx.toddle.errors.push(e as Error)
|
|
279
|
-
if (ctx.env?.logErrors) {
|
|
280
|
-
console.error(e)
|
|
281
|
-
}
|
|
282
|
-
return null
|
|
283
|
-
} finally {
|
|
284
|
-
stopMeasure()
|
|
285
|
-
}
|
|
286
|
-
} else {
|
|
287
|
-
// Lookup legacy formula
|
|
288
|
-
const legacyFunc: FormulaHandler | undefined = (
|
|
289
|
-
ctx.toddle ??
|
|
290
|
-
((globalThis as any).toddle as Toddle<unknown, unknown>)
|
|
291
|
-
).getFormula(formula.name)
|
|
292
|
-
if (typeof legacyFunc === 'function') {
|
|
293
|
-
const args = (formula.arguments ?? []).map((arg) =>
|
|
294
|
-
arg.isFunction
|
|
295
|
-
? (Args: any) =>
|
|
296
|
-
applyFormula(arg.formula, {
|
|
297
|
-
...ctx,
|
|
298
|
-
data: {
|
|
299
|
-
...ctx.data,
|
|
300
|
-
Args: ctx.data.Args
|
|
301
|
-
? { ...Args, '@toddle.parent': ctx.data.Args }
|
|
302
|
-
: Args,
|
|
303
|
-
},
|
|
304
|
-
})
|
|
305
|
-
: applyFormula(arg.formula, ctx),
|
|
306
|
-
)
|
|
307
|
-
try {
|
|
308
|
-
return legacyFunc(args, ctx as any)
|
|
309
|
-
} catch (e) {
|
|
310
|
-
ctx.toddle.errors.push(e as Error)
|
|
311
|
-
if (ctx.env?.logErrors) {
|
|
312
|
-
console.error(e)
|
|
313
|
-
}
|
|
314
|
-
return null
|
|
315
|
-
} finally {
|
|
316
|
-
stopMeasure()
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
if (ctx.env?.logErrors) {
|
|
321
|
-
console.error(
|
|
322
|
-
`Could not find formula ${formula.name} in package ${
|
|
323
|
-
packageName ?? ''
|
|
324
|
-
}`,
|
|
325
|
-
formula,
|
|
326
|
-
)
|
|
327
|
-
}
|
|
328
|
-
return null
|
|
271
|
+
return report(applyFunctionFormula(formula, ctx))
|
|
329
272
|
}
|
|
330
|
-
case 'object':
|
|
331
|
-
return Object.fromEntries(
|
|
332
|
-
formula.arguments?.map((entry) => [
|
|
333
|
-
entry.name,
|
|
334
|
-
applyFormula(entry.formula, ctx),
|
|
335
|
-
]) ?? [],
|
|
336
|
-
)
|
|
337
|
-
case 'record': // object used to be called record, there are still examples in the wild.
|
|
338
|
-
return Object.fromEntries(
|
|
339
|
-
(formula.entries ?? []).map((entry) => [
|
|
340
|
-
entry.name,
|
|
341
|
-
applyFormula(entry.formula, ctx),
|
|
342
|
-
]),
|
|
343
|
-
)
|
|
344
|
-
case 'array':
|
|
345
|
-
return (formula.arguments ?? []).map((entry) =>
|
|
346
|
-
applyFormula(entry.formula, ctx),
|
|
347
|
-
)
|
|
348
273
|
case 'apply': {
|
|
349
|
-
|
|
350
|
-
if (!componentFormula) {
|
|
351
|
-
if (ctx.env?.logErrors) {
|
|
352
|
-
console.log(
|
|
353
|
-
'Component does not have a formula with the name ',
|
|
354
|
-
formula.name,
|
|
355
|
-
)
|
|
356
|
-
}
|
|
357
|
-
return null
|
|
358
|
-
}
|
|
359
|
-
const stopMeasure = measure(`Formula: ${componentFormula.name}`, {
|
|
360
|
-
formula,
|
|
361
|
-
component: ctx.component?.name,
|
|
362
|
-
})
|
|
363
|
-
const Input = Object.fromEntries(
|
|
364
|
-
(formula.arguments ?? []).map((arg) =>
|
|
365
|
-
arg.isFunction
|
|
366
|
-
? [
|
|
367
|
-
arg.name,
|
|
368
|
-
(Args: any) =>
|
|
369
|
-
applyFormula(arg.formula, {
|
|
370
|
-
...ctx,
|
|
371
|
-
data: {
|
|
372
|
-
...ctx.data,
|
|
373
|
-
Args: ctx.data.Args
|
|
374
|
-
? { ...Args, '@toddle.parent': ctx.data.Args }
|
|
375
|
-
: Args,
|
|
376
|
-
},
|
|
377
|
-
}),
|
|
378
|
-
]
|
|
379
|
-
: [arg.name, applyFormula(arg.formula, ctx)],
|
|
380
|
-
),
|
|
381
|
-
)
|
|
382
|
-
const data = {
|
|
383
|
-
...ctx.data,
|
|
384
|
-
Args: ctx.data.Args
|
|
385
|
-
? { ...Input, '@toddle.parent': ctx.data.Args }
|
|
386
|
-
: Input,
|
|
387
|
-
}
|
|
388
|
-
const cache = ctx.formulaCache?.[formula.name]?.get(data)
|
|
389
|
-
|
|
390
|
-
if (cache?.hit) {
|
|
391
|
-
stopMeasure({ cache: 'hit' })
|
|
392
|
-
return cache.data
|
|
393
|
-
} else {
|
|
394
|
-
const result = applyFormula(componentFormula.formula, {
|
|
395
|
-
...ctx,
|
|
396
|
-
data,
|
|
397
|
-
})
|
|
398
|
-
ctx.formulaCache?.[formula.name]?.set(data, result)
|
|
399
|
-
stopMeasure({ cache: 'miss' })
|
|
400
|
-
return result
|
|
401
|
-
}
|
|
274
|
+
return report(applyApplyFormula(formula, ctx))
|
|
402
275
|
}
|
|
403
|
-
|
|
404
276
|
default:
|
|
405
277
|
if (ctx.env?.logErrors) {
|
|
406
278
|
console.error('Could not recognize formula', formula)
|
|
@@ -410,6 +282,8 @@ export function applyFormula(
|
|
|
410
282
|
if (ctx.env?.logErrors) {
|
|
411
283
|
console.error(e)
|
|
412
284
|
}
|
|
413
|
-
return null
|
|
285
|
+
return report(null)
|
|
414
286
|
}
|
|
287
|
+
|
|
288
|
+
return report(undefined)
|
|
415
289
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Nullable } from '../types'
|
|
2
|
-
import type { Formula } from './formula'
|
|
2
|
+
import type { Formula, FormulaContext } from './formula'
|
|
3
3
|
|
|
4
4
|
export interface BaseFormula {
|
|
5
5
|
name: string
|
|
@@ -38,3 +38,9 @@ export interface GlobalFormulas<Handler = string | Function> {
|
|
|
38
38
|
>
|
|
39
39
|
>
|
|
40
40
|
}
|
|
41
|
+
|
|
42
|
+
export type FormulaEvaluationReporter = (
|
|
43
|
+
path: Array<string | number>,
|
|
44
|
+
result: any,
|
|
45
|
+
ctx: Pick<FormulaContext, 'component'>,
|
|
46
|
+
) => void
|
|
@@ -11,7 +11,7 @@ import { isFormula, isToddleFormula } from './formula'
|
|
|
11
11
|
import type { GlobalFormulas } from './formulaTypes'
|
|
12
12
|
|
|
13
13
|
export const valueFormula = (
|
|
14
|
-
value: string | number | boolean | null | object,
|
|
14
|
+
value: string | number | boolean | null | object | undefined,
|
|
15
15
|
): ValueOperation => ({
|
|
16
16
|
type: 'value',
|
|
17
17
|
value,
|
|
@@ -319,6 +319,23 @@ export function* getFormulasInAction<Handler>({
|
|
|
319
319
|
})
|
|
320
320
|
}
|
|
321
321
|
}
|
|
322
|
+
for (const [callbackKey, callback] of Object.entries(
|
|
323
|
+
action.callbacks ?? {},
|
|
324
|
+
)) {
|
|
325
|
+
if (isDefined(callback?.actions)) {
|
|
326
|
+
for (const [key, a] of Object.entries(callback.actions)) {
|
|
327
|
+
if (isDefined(a)) {
|
|
328
|
+
yield* getFormulasInAction({
|
|
329
|
+
action: a,
|
|
330
|
+
globalFormulas,
|
|
331
|
+
path: [...path, 'callbacks', callbackKey, 'actions', key],
|
|
332
|
+
visitedFormulas,
|
|
333
|
+
packageName,
|
|
334
|
+
})
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
322
339
|
break
|
|
323
340
|
case 'Switch':
|
|
324
341
|
if (isDefined(action.data) && isFormula(action.data)) {
|