@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,122 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test'
|
|
2
|
+
import { applyFormula, type SwitchOperation } from './formula'
|
|
3
|
+
import { valueFormula } from './formulaUtils'
|
|
4
|
+
import {
|
|
5
|
+
applyEvaluateAllSwitchFormula,
|
|
6
|
+
applySwitchFormula,
|
|
7
|
+
} from './switchFormula'
|
|
8
|
+
import {
|
|
9
|
+
createTestFormulaContext,
|
|
10
|
+
createTestFormulaContextForAllPaths,
|
|
11
|
+
} from './testUtils.test'
|
|
12
|
+
|
|
13
|
+
describe('applySwitchFormula', () => {
|
|
14
|
+
it('returns the first matching case value', () => {
|
|
15
|
+
const formula: SwitchOperation = {
|
|
16
|
+
type: 'switch',
|
|
17
|
+
cases: [
|
|
18
|
+
{ condition: valueFormula(false), formula: valueFormula('no') },
|
|
19
|
+
{ condition: valueFormula(true), formula: valueFormula('yes') },
|
|
20
|
+
{
|
|
21
|
+
condition: valueFormula(true),
|
|
22
|
+
formula: valueFormula('should not match'),
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
default: valueFormula('default'),
|
|
26
|
+
}
|
|
27
|
+
const ctx = createTestFormulaContext()
|
|
28
|
+
expect(applySwitchFormula(formula, ctx)).toBe('yes')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('returns the default value if no case matches', () => {
|
|
32
|
+
const formula: SwitchOperation = {
|
|
33
|
+
type: 'switch',
|
|
34
|
+
cases: [
|
|
35
|
+
{ condition: valueFormula(false), formula: valueFormula('no') },
|
|
36
|
+
{ condition: valueFormula(false), formula: valueFormula('nope') },
|
|
37
|
+
],
|
|
38
|
+
default: valueFormula('default'),
|
|
39
|
+
}
|
|
40
|
+
const ctx = createTestFormulaContext()
|
|
41
|
+
expect(applySwitchFormula(formula, ctx)).toBe('default')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('evaluates conditions using context data', () => {
|
|
45
|
+
const formula: SwitchOperation = {
|
|
46
|
+
type: 'switch',
|
|
47
|
+
cases: [
|
|
48
|
+
{
|
|
49
|
+
condition: { type: 'path', path: ['foo'] },
|
|
50
|
+
formula: valueFormula('foo matched'),
|
|
51
|
+
},
|
|
52
|
+
{ condition: valueFormula(true), formula: valueFormula('fallback') },
|
|
53
|
+
],
|
|
54
|
+
default: valueFormula('default'),
|
|
55
|
+
}
|
|
56
|
+
const ctx = createTestFormulaContext({ foo: true })
|
|
57
|
+
expect(applySwitchFormula(formula, ctx)).toBe('foo matched')
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
describe('applyEvaluateAllSwitchFormula', () => {
|
|
62
|
+
it('returns the first matching case value, but evaluates all cases and default', () => {
|
|
63
|
+
const formula: SwitchOperation = {
|
|
64
|
+
type: 'switch',
|
|
65
|
+
cases: [
|
|
66
|
+
{ condition: valueFormula(false), formula: valueFormula('no') },
|
|
67
|
+
{ condition: valueFormula(true), formula: valueFormula('yes') },
|
|
68
|
+
{
|
|
69
|
+
condition: valueFormula(true),
|
|
70
|
+
formula: valueFormula('should not match'),
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
default: valueFormula('default'),
|
|
74
|
+
}
|
|
75
|
+
const results: Record<string, any> = {}
|
|
76
|
+
const ctx = createTestFormulaContextForAllPaths(
|
|
77
|
+
{},
|
|
78
|
+
(path, result) => (results[path.join('/')] = result),
|
|
79
|
+
)
|
|
80
|
+
// We use applyFormula directly since it will gather results in EVALUATE ALL PATHS mode
|
|
81
|
+
const result = applyFormula(formula, ctx, [])
|
|
82
|
+
expect(result).toBe('yes')
|
|
83
|
+
expect(results).toMatchObject({
|
|
84
|
+
'cases/0/condition': false,
|
|
85
|
+
'cases/0/formula': 'no',
|
|
86
|
+
'cases/1/condition': true,
|
|
87
|
+
'cases/1/formula': 'yes',
|
|
88
|
+
'cases/2/condition': true,
|
|
89
|
+
'cases/2/formula': 'should not match',
|
|
90
|
+
default: 'default',
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('returns the default value if no case matches', () => {
|
|
95
|
+
const formula: SwitchOperation = {
|
|
96
|
+
type: 'switch',
|
|
97
|
+
cases: [
|
|
98
|
+
{ condition: valueFormula(false), formula: valueFormula('no') },
|
|
99
|
+
{ condition: valueFormula(false), formula: valueFormula('nope') },
|
|
100
|
+
],
|
|
101
|
+
default: valueFormula('default'),
|
|
102
|
+
}
|
|
103
|
+
const ctx = createTestFormulaContext()
|
|
104
|
+
expect(applyEvaluateAllSwitchFormula(formula, ctx)).toBe('default')
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('evaluates conditions using context data', () => {
|
|
108
|
+
const formula: SwitchOperation = {
|
|
109
|
+
type: 'switch',
|
|
110
|
+
cases: [
|
|
111
|
+
{
|
|
112
|
+
condition: { type: 'path', path: ['foo'] },
|
|
113
|
+
formula: valueFormula('foo matched'),
|
|
114
|
+
},
|
|
115
|
+
{ condition: valueFormula(true), formula: valueFormula('fallback') },
|
|
116
|
+
],
|
|
117
|
+
default: valueFormula('default'),
|
|
118
|
+
}
|
|
119
|
+
const ctx = createTestFormulaContext({ foo: true })
|
|
120
|
+
expect(applyEvaluateAllSwitchFormula(formula, ctx)).toBe('foo matched')
|
|
121
|
+
})
|
|
122
|
+
})
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { toBoolean } from '../utils/util'
|
|
2
|
+
import {
|
|
3
|
+
applyFormula,
|
|
4
|
+
type FormulaContext,
|
|
5
|
+
type SwitchOperation,
|
|
6
|
+
} from './formula'
|
|
7
|
+
|
|
8
|
+
export const applySwitchFormula = (
|
|
9
|
+
formula: SwitchOperation,
|
|
10
|
+
ctx: FormulaContext,
|
|
11
|
+
) => {
|
|
12
|
+
// Evaluates cases until one matches
|
|
13
|
+
for (let i = 0; i < (formula.cases ?? []).length; i++) {
|
|
14
|
+
const switchCase = (formula.cases ?? [])[i]
|
|
15
|
+
if (
|
|
16
|
+
toBoolean(
|
|
17
|
+
applyFormula(switchCase?.condition, ctx, ['cases', i, 'condition']),
|
|
18
|
+
)
|
|
19
|
+
) {
|
|
20
|
+
return applyFormula(switchCase?.formula, ctx, ['cases', i, 'formula'])
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return applyFormula(formula.default, ctx, ['default'])
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const applyEvaluateAllSwitchFormula = (
|
|
27
|
+
formula: SwitchOperation,
|
|
28
|
+
ctx: FormulaContext,
|
|
29
|
+
) => {
|
|
30
|
+
// Evaluate all cases and the default, but only returns the first matching case or the default
|
|
31
|
+
let switchResult: { match: true; value: any } | null = null
|
|
32
|
+
for (let i = 0; i < (formula.cases ?? []).length; i++) {
|
|
33
|
+
const switchCase = (formula.cases ?? [])[i]
|
|
34
|
+
const conditionValue = applyFormula(switchCase?.condition, ctx, [
|
|
35
|
+
'cases',
|
|
36
|
+
i,
|
|
37
|
+
'condition',
|
|
38
|
+
])
|
|
39
|
+
const formulaValue = applyFormula(switchCase?.formula, ctx, [
|
|
40
|
+
'cases',
|
|
41
|
+
i,
|
|
42
|
+
'formula',
|
|
43
|
+
])
|
|
44
|
+
if (toBoolean(conditionValue) && switchResult === null) {
|
|
45
|
+
switchResult = { match: true, value: formulaValue }
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const defaultValue = applyFormula(formula.default, ctx, ['default'])
|
|
49
|
+
if (switchResult !== null) {
|
|
50
|
+
return switchResult.value
|
|
51
|
+
} else {
|
|
52
|
+
return defaultValue
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { FormulaContext } from './formula'
|
|
2
|
+
import type { FormulaEvaluationReporter } from './formulaTypes'
|
|
3
|
+
|
|
4
|
+
// Register all core formulas globally for tests
|
|
5
|
+
if (!(globalThis as any).__CORE_FORMULAS__) {
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
7
|
+
const libFormulas = require('../../../lib/dist/formulas')
|
|
8
|
+
;(globalThis as any).__CORE_FORMULAS__ = Object.fromEntries(
|
|
9
|
+
Object.entries(libFormulas).map(([name, module]) => [
|
|
10
|
+
'@toddle/' + name,
|
|
11
|
+
((module as any).default ?? module) as any,
|
|
12
|
+
]),
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Helper to create a minimal context
|
|
17
|
+
export const createTestFormulaContext = (data: any = {}): FormulaContext => {
|
|
18
|
+
const coreFormulas = (globalThis as any).__CORE_FORMULAS__ as
|
|
19
|
+
| Record<string, any>
|
|
20
|
+
| undefined
|
|
21
|
+
return {
|
|
22
|
+
component: undefined,
|
|
23
|
+
formulaCache: {},
|
|
24
|
+
data,
|
|
25
|
+
root: {} as Document,
|
|
26
|
+
package: undefined,
|
|
27
|
+
toddle: {
|
|
28
|
+
getFormula: (name: string) => coreFormulas?.[name],
|
|
29
|
+
getCustomFormula: () => undefined,
|
|
30
|
+
errors: [],
|
|
31
|
+
},
|
|
32
|
+
env: {
|
|
33
|
+
logErrors: true,
|
|
34
|
+
runtime: 'page',
|
|
35
|
+
branchName: 'main',
|
|
36
|
+
isServer: false,
|
|
37
|
+
request: undefined,
|
|
38
|
+
},
|
|
39
|
+
reportFormulaEvaluation: undefined,
|
|
40
|
+
jsonPath: [],
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Helper to create a minimal context when evaluating all paths
|
|
45
|
+
export const createTestFormulaContextForAllPaths = (
|
|
46
|
+
data: any = {},
|
|
47
|
+
formulaEvaluationReporter: FormulaEvaluationReporter,
|
|
48
|
+
): FormulaContext => ({
|
|
49
|
+
component: undefined,
|
|
50
|
+
formulaCache: {},
|
|
51
|
+
data,
|
|
52
|
+
root: undefined,
|
|
53
|
+
package: undefined,
|
|
54
|
+
toddle: {
|
|
55
|
+
getFormula: (name: string) => coreFormulas[name],
|
|
56
|
+
getCustomFormula: () => undefined,
|
|
57
|
+
errors: [],
|
|
58
|
+
},
|
|
59
|
+
env: {
|
|
60
|
+
logErrors: true,
|
|
61
|
+
runtime: 'page',
|
|
62
|
+
branchName: 'main',
|
|
63
|
+
isServer: false,
|
|
64
|
+
request: undefined,
|
|
65
|
+
},
|
|
66
|
+
jsonPath: [],
|
|
67
|
+
reportFormulaEvaluation: formulaEvaluationReporter,
|
|
68
|
+
})
|
package/src/types.ts
CHANGED
|
@@ -85,7 +85,7 @@ export type ArgumentInputDataFunction = (
|
|
|
85
85
|
|
|
86
86
|
export type CustomFormulaHandler = (
|
|
87
87
|
name: string,
|
|
88
|
-
packageName: string | undefined,
|
|
88
|
+
packageName: string | null | undefined,
|
|
89
89
|
) => PluginFormula<FormulaHandlerV2> | undefined
|
|
90
90
|
|
|
91
91
|
export type FormulaLookup = (name: string) => FormulaHandler | undefined
|
package/src/utils/measure.ts
CHANGED
|
@@ -31,7 +31,13 @@ export const measure = (key: string, details: Record<string, unknown>) => {
|
|
|
31
31
|
const start = performance.now()
|
|
32
32
|
STACK.push(key)
|
|
33
33
|
|
|
34
|
+
let _stopped = false
|
|
34
35
|
return (extraDetails?: Record<string, unknown>) => {
|
|
36
|
+
if (_stopped) {
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
_stopped = true
|
|
35
41
|
const end = performance.now()
|
|
36
42
|
const mergedDetails = extraDetails
|
|
37
43
|
? { ...details, ...extraDetails }
|