@nordcraft/core 1.0.95 → 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/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 +8 -6
- 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.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.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 +67 -206
- package/src/formula/formulaTypes.ts +5 -0
- package/src/formula/formulaUtils.ts +1 -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,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 {
|
|
@@ -79,7 +91,13 @@ 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'
|
|
@@ -104,7 +122,7 @@ export type Formula =
|
|
|
104
122
|
| ValueOperation
|
|
105
123
|
| ApplyOperation
|
|
106
124
|
|
|
107
|
-
export
|
|
125
|
+
export interface FormulaContext {
|
|
108
126
|
component: Component | undefined
|
|
109
127
|
formulaCache?: Nullable<
|
|
110
128
|
Record<
|
|
@@ -123,6 +141,8 @@ export type FormulaContext = {
|
|
|
123
141
|
getCustomFormula: CustomFormulaHandler
|
|
124
142
|
errors: Error[]
|
|
125
143
|
}
|
|
144
|
+
jsonPath?: Array<string | number> | undefined
|
|
145
|
+
reportFormulaEvaluation?: FormulaEvaluationReporter | undefined
|
|
126
146
|
env: ToddleEnv | undefined
|
|
127
147
|
}
|
|
128
148
|
|
|
@@ -185,222 +205,61 @@ export const isToddleFormula = <Handler>(
|
|
|
185
205
|
|
|
186
206
|
export function applyFormula(
|
|
187
207
|
formula: Formula | string | number | undefined | null | boolean,
|
|
188
|
-
|
|
208
|
+
_ctx: FormulaContext,
|
|
209
|
+
extendedPath?: Array<string | number> | undefined,
|
|
189
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
|
+
|
|
190
218
|
if (!isFormula(formula)) {
|
|
191
|
-
return formula
|
|
219
|
+
return report(formula)
|
|
192
220
|
}
|
|
193
221
|
try {
|
|
194
222
|
switch (formula.type) {
|
|
195
|
-
case 'value':
|
|
196
|
-
return formula.value
|
|
223
|
+
case 'value': {
|
|
224
|
+
return report(formula.value)
|
|
225
|
+
}
|
|
197
226
|
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
|
|
227
|
+
return report(applyPathFormula(formula, ctx.data))
|
|
208
228
|
}
|
|
209
229
|
case 'switch': {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
return applyFormula(branch.formula, ctx)
|
|
213
|
-
}
|
|
230
|
+
if (ctx.reportFormulaEvaluation) {
|
|
231
|
+
return report(applyEvaluateAllSwitchFormula(formula, ctx))
|
|
214
232
|
}
|
|
215
|
-
return
|
|
233
|
+
return applySwitchFormula(formula, ctx)
|
|
216
234
|
}
|
|
217
235
|
case 'or': {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
return true
|
|
221
|
-
}
|
|
236
|
+
if (ctx.reportFormulaEvaluation) {
|
|
237
|
+
return report(applyEvaluateAllOrFormula(formula, ctx))
|
|
222
238
|
}
|
|
223
|
-
return
|
|
239
|
+
return applyOrFormula(formula, ctx)
|
|
224
240
|
}
|
|
225
241
|
case 'and': {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
return false
|
|
229
|
-
}
|
|
242
|
+
if (ctx.reportFormulaEvaluation) {
|
|
243
|
+
return report(applyEvaluateAllAndFormula(formula, ctx))
|
|
230
244
|
}
|
|
231
|
-
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))
|
|
232
256
|
}
|
|
233
257
|
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
|
|
258
|
+
return report(applyFunctionFormula(formula, ctx))
|
|
329
259
|
}
|
|
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
260
|
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
|
-
}
|
|
261
|
+
return report(applyApplyFormula(formula, ctx))
|
|
402
262
|
}
|
|
403
|
-
|
|
404
263
|
default:
|
|
405
264
|
if (ctx.env?.logErrors) {
|
|
406
265
|
console.error('Could not recognize formula', formula)
|
|
@@ -410,6 +269,8 @@ export function applyFormula(
|
|
|
410
269
|
if (ctx.env?.logErrors) {
|
|
411
270
|
console.error(e)
|
|
412
271
|
}
|
|
413
|
-
return null
|
|
272
|
+
return report(null)
|
|
414
273
|
}
|
|
274
|
+
|
|
275
|
+
return report(undefined)
|
|
415
276
|
}
|
|
@@ -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,
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test'
|
|
2
|
+
import type { FormulaHandlerV2 } from '../types'
|
|
3
|
+
import type { FunctionOperation } from './formula'
|
|
4
|
+
import type { PluginFormula } from './formulaTypes'
|
|
5
|
+
import { valueFormula } from './formulaUtils'
|
|
6
|
+
import { applyFunctionFormula } from './functionFormula'
|
|
7
|
+
import { createTestFormulaContext } from './testUtils.test'
|
|
8
|
+
|
|
9
|
+
const contextWithCustomFormulas = (
|
|
10
|
+
formulas: Record<string, PluginFormula<FormulaHandlerV2>>,
|
|
11
|
+
) => {
|
|
12
|
+
return {
|
|
13
|
+
...createTestFormulaContext(),
|
|
14
|
+
toddle: {
|
|
15
|
+
...createTestFormulaContext().toddle,
|
|
16
|
+
getCustomFormula: (name: string) => formulas[name],
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('applyFunctionFormula', () => {
|
|
22
|
+
it('returns the value of a simple function formula', () => {
|
|
23
|
+
const ctx = contextWithCustomFormulas({
|
|
24
|
+
identity: {
|
|
25
|
+
version: 2,
|
|
26
|
+
name: 'identity',
|
|
27
|
+
handler: () => 42,
|
|
28
|
+
arguments: [],
|
|
29
|
+
},
|
|
30
|
+
})
|
|
31
|
+
expect(
|
|
32
|
+
applyFunctionFormula(
|
|
33
|
+
{ type: 'function', name: 'identity', arguments: [] },
|
|
34
|
+
ctx,
|
|
35
|
+
),
|
|
36
|
+
).toBe(42)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('returns null if the function is not found', () => {
|
|
40
|
+
const formula: FunctionOperation = {
|
|
41
|
+
type: 'function',
|
|
42
|
+
name: 'missing',
|
|
43
|
+
arguments: [{ name: 'x', formula: valueFormula(1) }],
|
|
44
|
+
}
|
|
45
|
+
const ctx = contextWithCustomFormulas({})
|
|
46
|
+
expect(applyFunctionFormula(formula, ctx)).toBeNull()
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('calls isFunction arguments as functions', () => {
|
|
50
|
+
const formula: FunctionOperation = {
|
|
51
|
+
type: 'function',
|
|
52
|
+
name: 'fn',
|
|
53
|
+
arguments: [
|
|
54
|
+
{
|
|
55
|
+
name: 'fn',
|
|
56
|
+
isFunction: true,
|
|
57
|
+
formula: valueFormula('called'),
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
}
|
|
61
|
+
const ctx = contextWithCustomFormulas({
|
|
62
|
+
fn: {
|
|
63
|
+
version: 2,
|
|
64
|
+
name: 'fn',
|
|
65
|
+
handler: ({ fn }) => {
|
|
66
|
+
return (fn as any)()
|
|
67
|
+
},
|
|
68
|
+
arguments: [{ name: 'fn' }],
|
|
69
|
+
},
|
|
70
|
+
})
|
|
71
|
+
expect(applyFunctionFormula(formula, ctx)).toBe('called')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('returns the result of a ToddleFormula', () => {
|
|
75
|
+
const formula: FunctionOperation = {
|
|
76
|
+
type: 'function',
|
|
77
|
+
name: 'projectFormula',
|
|
78
|
+
arguments: [],
|
|
79
|
+
}
|
|
80
|
+
const ctx = contextWithCustomFormulas({
|
|
81
|
+
projectFormula: {
|
|
82
|
+
name: 'projectFormula',
|
|
83
|
+
arguments: [],
|
|
84
|
+
formula: valueFormula([1, 2, 3]),
|
|
85
|
+
},
|
|
86
|
+
})
|
|
87
|
+
expect(applyFunctionFormula(formula, ctx)).toEqual([1, 2, 3])
|
|
88
|
+
})
|
|
89
|
+
})
|