@nordcraft/core 1.0.94 → 1.0.96
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/LegacyToddleApi.js +2 -2
- package/dist/api/LegacyToddleApi.js.map +1 -1
- package/dist/api/ToddleApiV2.js +2 -2
- package/dist/api/ToddleApiV2.js.map +1 -1
- 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/component.types.d.ts +3 -3
- 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 +19 -17
- package/dist/formula/formula.js +45 -172
- package/dist/formula/formula.js.map +1 -1
- package/dist/formula/formulaTypes.d.ts +1 -0
- package/dist/formula/formulaUtils.d.ts +1 -1
- package/dist/formula/formulaUtils.js +2 -2
- 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/LegacyToddleApi.ts +2 -2
- package/src/api/ToddleApiV2.ts +2 -2
- package/src/api/api.test.ts +22 -22
- package/src/api/api.ts +36 -10
- package/src/component/ToddleComponent.ts +9 -7
- package/src/component/component.types.ts +5 -3
- 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 +79 -214
- package/src/formula/formulaTypes.ts +5 -0
- package/src/formula/formulaUtils.ts +3 -3
- 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,31 @@
|
|
|
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'
|
|
15
28
|
|
|
16
29
|
// 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
30
|
type ShadowRoot = DocumentFragment
|
|
19
31
|
|
|
20
32
|
interface BaseOperation extends NordcraftMetadata {
|
|
@@ -23,7 +35,7 @@ interface BaseOperation extends NordcraftMetadata {
|
|
|
23
35
|
|
|
24
36
|
export interface PathOperation extends BaseOperation {
|
|
25
37
|
type: 'path'
|
|
26
|
-
path: string
|
|
38
|
+
path: Array<string | number>
|
|
27
39
|
}
|
|
28
40
|
|
|
29
41
|
export interface FunctionArgument {
|
|
@@ -39,13 +51,13 @@ export interface FunctionOperation extends BaseOperation {
|
|
|
39
51
|
name: string
|
|
40
52
|
display_name?: Nullable<string>
|
|
41
53
|
package?: Nullable<string>
|
|
42
|
-
arguments
|
|
54
|
+
arguments?: Nullable<FunctionArgument[]>
|
|
43
55
|
variableArguments?: Nullable<boolean>
|
|
44
56
|
}
|
|
45
57
|
|
|
46
58
|
export interface RecordOperation extends BaseOperation {
|
|
47
59
|
type: 'record'
|
|
48
|
-
entries
|
|
60
|
+
entries?: Nullable<FunctionArgument[]>
|
|
49
61
|
}
|
|
50
62
|
|
|
51
63
|
export interface ObjectOperation extends BaseOperation {
|
|
@@ -55,23 +67,23 @@ export interface ObjectOperation extends BaseOperation {
|
|
|
55
67
|
|
|
56
68
|
export interface ArrayOperation extends BaseOperation {
|
|
57
69
|
type: 'array'
|
|
58
|
-
arguments
|
|
70
|
+
arguments?: Nullable<Array<{ formula: Formula }>>
|
|
59
71
|
}
|
|
60
72
|
|
|
61
73
|
export interface OrOperation extends BaseOperation {
|
|
62
74
|
type: 'or'
|
|
63
|
-
arguments
|
|
75
|
+
arguments?: Nullable<Array<{ formula: Formula }>>
|
|
64
76
|
}
|
|
65
77
|
|
|
66
78
|
export interface AndOperation extends BaseOperation {
|
|
67
79
|
type: 'and'
|
|
68
|
-
arguments
|
|
80
|
+
arguments?: Nullable<Array<{ formula: Formula }>>
|
|
69
81
|
}
|
|
70
82
|
|
|
71
83
|
export interface ApplyOperation extends BaseOperation {
|
|
72
84
|
type: 'apply'
|
|
73
85
|
name: string
|
|
74
|
-
arguments
|
|
86
|
+
arguments?: Nullable<FunctionArgument[]>
|
|
75
87
|
}
|
|
76
88
|
|
|
77
89
|
export interface ValueOperation extends BaseOperation {
|
|
@@ -79,14 +91,22 @@ export interface ValueOperation extends BaseOperation {
|
|
|
79
91
|
value: ValueOperationValue
|
|
80
92
|
}
|
|
81
93
|
|
|
82
|
-
export type ValueOperationValue =
|
|
94
|
+
export type ValueOperationValue =
|
|
95
|
+
| string
|
|
96
|
+
| number
|
|
97
|
+
| boolean
|
|
98
|
+
| null
|
|
99
|
+
| object
|
|
100
|
+
| undefined
|
|
83
101
|
|
|
84
102
|
export interface SwitchOperation extends BaseOperation {
|
|
85
103
|
type: 'switch'
|
|
86
|
-
cases
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
104
|
+
cases?: Nullable<
|
|
105
|
+
Array<{
|
|
106
|
+
condition: Formula
|
|
107
|
+
formula: Formula
|
|
108
|
+
}>
|
|
109
|
+
>
|
|
90
110
|
default: Formula
|
|
91
111
|
}
|
|
92
112
|
|
|
@@ -102,7 +122,7 @@ export type Formula =
|
|
|
102
122
|
| ValueOperation
|
|
103
123
|
| ApplyOperation
|
|
104
124
|
|
|
105
|
-
export
|
|
125
|
+
export interface FormulaContext {
|
|
106
126
|
component: Component | undefined
|
|
107
127
|
formulaCache?: Nullable<
|
|
108
128
|
Record<
|
|
@@ -121,6 +141,8 @@ export type FormulaContext = {
|
|
|
121
141
|
getCustomFormula: CustomFormulaHandler
|
|
122
142
|
errors: Error[]
|
|
123
143
|
}
|
|
144
|
+
jsonPath?: Array<string | number> | undefined
|
|
145
|
+
reportFormulaEvaluation?: FormulaEvaluationReporter | undefined
|
|
124
146
|
env: ToddleEnv | undefined
|
|
125
147
|
}
|
|
126
148
|
|
|
@@ -183,220 +205,61 @@ export const isToddleFormula = <Handler>(
|
|
|
183
205
|
|
|
184
206
|
export function applyFormula(
|
|
185
207
|
formula: Formula | string | number | undefined | null | boolean,
|
|
186
|
-
|
|
208
|
+
_ctx: FormulaContext,
|
|
209
|
+
extendedPath?: Array<string | number> | undefined,
|
|
187
210
|
): any {
|
|
211
|
+
const path = [...(_ctx?.jsonPath ?? []), ...(extendedPath ?? [])]
|
|
212
|
+
const ctx = { ..._ctx, jsonPath: path }
|
|
213
|
+
const report = (value: any, p: Array<string | number> = path) => {
|
|
214
|
+
ctx?.reportFormulaEvaluation?.(p, value)
|
|
215
|
+
return value
|
|
216
|
+
}
|
|
217
|
+
|
|
188
218
|
if (!isFormula(formula)) {
|
|
189
|
-
return formula
|
|
219
|
+
return report(formula)
|
|
190
220
|
}
|
|
191
221
|
try {
|
|
192
222
|
switch (formula.type) {
|
|
193
|
-
case 'value':
|
|
194
|
-
return formula.value
|
|
223
|
+
case 'value': {
|
|
224
|
+
return report(formula.value)
|
|
225
|
+
}
|
|
195
226
|
case 'path': {
|
|
196
|
-
|
|
197
|
-
for (const key of formula.path) {
|
|
198
|
-
if (input && typeof input === 'object') {
|
|
199
|
-
input = input[key]
|
|
200
|
-
} else {
|
|
201
|
-
return null
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return input
|
|
227
|
+
return report(applyPathFormula(formula, ctx.data))
|
|
206
228
|
}
|
|
207
229
|
case 'switch': {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
return applyFormula(branch.formula, ctx)
|
|
211
|
-
}
|
|
230
|
+
if (ctx.reportFormulaEvaluation) {
|
|
231
|
+
return report(applyEvaluateAllSwitchFormula(formula, ctx))
|
|
212
232
|
}
|
|
213
|
-
return
|
|
233
|
+
return applySwitchFormula(formula, ctx)
|
|
214
234
|
}
|
|
215
235
|
case 'or': {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
return true
|
|
219
|
-
}
|
|
236
|
+
if (ctx.reportFormulaEvaluation) {
|
|
237
|
+
return report(applyEvaluateAllOrFormula(formula, ctx))
|
|
220
238
|
}
|
|
221
|
-
return
|
|
239
|
+
return applyOrFormula(formula, ctx)
|
|
222
240
|
}
|
|
223
241
|
case 'and': {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
return false
|
|
227
|
-
}
|
|
242
|
+
if (ctx.reportFormulaEvaluation) {
|
|
243
|
+
return report(applyEvaluateAllAndFormula(formula, ctx))
|
|
228
244
|
}
|
|
229
|
-
return
|
|
245
|
+
return applyAndFormula(formula, ctx)
|
|
246
|
+
}
|
|
247
|
+
case 'object': {
|
|
248
|
+
return report(applyObjectFormula(formula, ctx))
|
|
249
|
+
}
|
|
250
|
+
case 'record': {
|
|
251
|
+
// object used to be called record, there are still examples in the wild.
|
|
252
|
+
return report(applyRecordFormula(formula, ctx))
|
|
253
|
+
}
|
|
254
|
+
case 'array': {
|
|
255
|
+
return report(applyArrayFormula(formula, ctx))
|
|
230
256
|
}
|
|
231
257
|
case 'function': {
|
|
232
|
-
|
|
233
|
-
formula,
|
|
234
|
-
component: ctx.component?.name,
|
|
235
|
-
})
|
|
236
|
-
const packageName = formula.package ?? ctx.package ?? undefined
|
|
237
|
-
const newFunc = (
|
|
238
|
-
ctx.toddle ??
|
|
239
|
-
((globalThis as any).toddle as Toddle<unknown, unknown> | undefined)
|
|
240
|
-
)?.getCustomFormula(formula.name, packageName)
|
|
241
|
-
if (isDefined(newFunc)) {
|
|
242
|
-
ctx.package = packageName
|
|
243
|
-
const args = formula.arguments.reduce<Record<string, unknown>>(
|
|
244
|
-
(args, arg, i) => ({
|
|
245
|
-
...args,
|
|
246
|
-
[arg.name ?? `${i}`]: arg.isFunction
|
|
247
|
-
? (Args: any) =>
|
|
248
|
-
applyFormula(arg.formula, {
|
|
249
|
-
...ctx,
|
|
250
|
-
data: {
|
|
251
|
-
...ctx.data,
|
|
252
|
-
Args: ctx.data.Args
|
|
253
|
-
? { ...Args, '@toddle.parent': ctx.data.Args }
|
|
254
|
-
: Args,
|
|
255
|
-
},
|
|
256
|
-
})
|
|
257
|
-
: applyFormula(arg.formula, ctx),
|
|
258
|
-
}),
|
|
259
|
-
{},
|
|
260
|
-
)
|
|
261
|
-
try {
|
|
262
|
-
if (isToddleFormula(newFunc)) {
|
|
263
|
-
return applyFormula(newFunc.formula, {
|
|
264
|
-
...ctx,
|
|
265
|
-
data: { ...ctx.data, Args: args },
|
|
266
|
-
})
|
|
267
|
-
} else {
|
|
268
|
-
return newFunc.handler(args, {
|
|
269
|
-
root: ctx.root ?? document,
|
|
270
|
-
env: ctx.env,
|
|
271
|
-
} as any)
|
|
272
|
-
}
|
|
273
|
-
} catch (e) {
|
|
274
|
-
ctx.toddle.errors.push(e as Error)
|
|
275
|
-
if (ctx.env?.logErrors) {
|
|
276
|
-
console.error(e)
|
|
277
|
-
}
|
|
278
|
-
return null
|
|
279
|
-
} finally {
|
|
280
|
-
stopMeasure()
|
|
281
|
-
}
|
|
282
|
-
} else {
|
|
283
|
-
// Lookup legacy formula
|
|
284
|
-
const legacyFunc: FormulaHandler | undefined = (
|
|
285
|
-
ctx.toddle ??
|
|
286
|
-
((globalThis as any).toddle as Toddle<unknown, unknown>)
|
|
287
|
-
).getFormula(formula.name)
|
|
288
|
-
if (typeof legacyFunc === 'function') {
|
|
289
|
-
const args = (formula.arguments ?? []).map((arg) =>
|
|
290
|
-
arg.isFunction
|
|
291
|
-
? (Args: any) =>
|
|
292
|
-
applyFormula(arg.formula, {
|
|
293
|
-
...ctx,
|
|
294
|
-
data: {
|
|
295
|
-
...ctx.data,
|
|
296
|
-
Args: ctx.data.Args
|
|
297
|
-
? { ...Args, '@toddle.parent': ctx.data.Args }
|
|
298
|
-
: Args,
|
|
299
|
-
},
|
|
300
|
-
})
|
|
301
|
-
: applyFormula(arg.formula, ctx),
|
|
302
|
-
)
|
|
303
|
-
try {
|
|
304
|
-
return legacyFunc(args, ctx as any)
|
|
305
|
-
} catch (e) {
|
|
306
|
-
ctx.toddle.errors.push(e as Error)
|
|
307
|
-
if (ctx.env?.logErrors) {
|
|
308
|
-
console.error(e)
|
|
309
|
-
}
|
|
310
|
-
return null
|
|
311
|
-
} finally {
|
|
312
|
-
stopMeasure()
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
if (ctx.env?.logErrors) {
|
|
317
|
-
console.error(
|
|
318
|
-
`Could not find formula ${formula.name} in package ${
|
|
319
|
-
packageName ?? ''
|
|
320
|
-
}`,
|
|
321
|
-
formula,
|
|
322
|
-
)
|
|
323
|
-
}
|
|
324
|
-
return null
|
|
258
|
+
return report(applyFunctionFormula(formula, ctx))
|
|
325
259
|
}
|
|
326
|
-
case 'object':
|
|
327
|
-
return Object.fromEntries(
|
|
328
|
-
formula.arguments?.map((entry) => [
|
|
329
|
-
entry.name,
|
|
330
|
-
applyFormula(entry.formula, ctx),
|
|
331
|
-
]) ?? [],
|
|
332
|
-
)
|
|
333
|
-
case 'record': // object used to be called record, there are still examples in the wild.
|
|
334
|
-
return Object.fromEntries(
|
|
335
|
-
formula.entries.map((entry) => [
|
|
336
|
-
entry.name,
|
|
337
|
-
applyFormula(entry.formula, ctx),
|
|
338
|
-
]),
|
|
339
|
-
)
|
|
340
|
-
case 'array':
|
|
341
|
-
return formula.arguments.map((entry) =>
|
|
342
|
-
applyFormula(entry.formula, ctx),
|
|
343
|
-
)
|
|
344
260
|
case 'apply': {
|
|
345
|
-
|
|
346
|
-
if (!componentFormula) {
|
|
347
|
-
if (ctx.env?.logErrors) {
|
|
348
|
-
console.log(
|
|
349
|
-
'Component does not have a formula with the name ',
|
|
350
|
-
formula.name,
|
|
351
|
-
)
|
|
352
|
-
}
|
|
353
|
-
return null
|
|
354
|
-
}
|
|
355
|
-
const stopMeasure = measure(`Formula: ${componentFormula.name}`, {
|
|
356
|
-
formula,
|
|
357
|
-
component: ctx.component?.name,
|
|
358
|
-
})
|
|
359
|
-
const Input = Object.fromEntries(
|
|
360
|
-
formula.arguments.map((arg) =>
|
|
361
|
-
arg.isFunction
|
|
362
|
-
? [
|
|
363
|
-
arg.name,
|
|
364
|
-
(Args: any) =>
|
|
365
|
-
applyFormula(arg.formula, {
|
|
366
|
-
...ctx,
|
|
367
|
-
data: {
|
|
368
|
-
...ctx.data,
|
|
369
|
-
Args: ctx.data.Args
|
|
370
|
-
? { ...Args, '@toddle.parent': ctx.data.Args }
|
|
371
|
-
: Args,
|
|
372
|
-
},
|
|
373
|
-
}),
|
|
374
|
-
]
|
|
375
|
-
: [arg.name, applyFormula(arg.formula, ctx)],
|
|
376
|
-
),
|
|
377
|
-
)
|
|
378
|
-
const data = {
|
|
379
|
-
...ctx.data,
|
|
380
|
-
Args: ctx.data.Args
|
|
381
|
-
? { ...Input, '@toddle.parent': ctx.data.Args }
|
|
382
|
-
: Input,
|
|
383
|
-
}
|
|
384
|
-
const cache = ctx.formulaCache?.[formula.name]?.get(data)
|
|
385
|
-
|
|
386
|
-
if (cache?.hit) {
|
|
387
|
-
stopMeasure({ cache: 'hit' })
|
|
388
|
-
return cache.data
|
|
389
|
-
} else {
|
|
390
|
-
const result = applyFormula(componentFormula.formula, {
|
|
391
|
-
...ctx,
|
|
392
|
-
data,
|
|
393
|
-
})
|
|
394
|
-
ctx.formulaCache?.[formula.name]?.set(data, result)
|
|
395
|
-
stopMeasure({ cache: 'miss' })
|
|
396
|
-
return result
|
|
397
|
-
}
|
|
261
|
+
return report(applyApplyFormula(formula, ctx))
|
|
398
262
|
}
|
|
399
|
-
|
|
400
263
|
default:
|
|
401
264
|
if (ctx.env?.logErrors) {
|
|
402
265
|
console.error('Could not recognize formula', formula)
|
|
@@ -406,6 +269,8 @@ export function applyFormula(
|
|
|
406
269
|
if (ctx.env?.logErrors) {
|
|
407
270
|
console.error(e)
|
|
408
271
|
}
|
|
409
|
-
return null
|
|
272
|
+
return report(null)
|
|
410
273
|
}
|
|
274
|
+
|
|
275
|
+
return report(undefined)
|
|
411
276
|
}
|