@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.
Files changed (76) hide show
  1. package/dist/api/api.js +30 -10
  2. package/dist/api/api.js.map +1 -1
  3. package/dist/component/ToddleComponent.js +3 -3
  4. package/dist/component/ToddleComponent.js.map +1 -1
  5. package/dist/component/actionUtils.js +17 -1
  6. package/dist/component/actionUtils.js.map +1 -1
  7. package/dist/component/component.types.d.ts +6 -6
  8. package/dist/component/component.types.js.map +1 -1
  9. package/dist/formula/andFormula.d.ts +3 -0
  10. package/dist/formula/andFormula.js +25 -0
  11. package/dist/formula/andFormula.js.map +1 -0
  12. package/dist/formula/applyFormula.d.ts +2 -0
  13. package/dist/formula/applyFormula.js +49 -0
  14. package/dist/formula/applyFormula.js.map +1 -0
  15. package/dist/formula/arrayFormula.d.ts +2 -0
  16. package/dist/formula/arrayFormula.js +5 -0
  17. package/dist/formula/arrayFormula.js.map +1 -0
  18. package/dist/formula/formula.d.ts +8 -6
  19. package/dist/formula/formula.js +51 -172
  20. package/dist/formula/formula.js.map +1 -1
  21. package/dist/formula/formulaTypes.d.ts +2 -1
  22. package/dist/formula/formulaUtils.d.ts +1 -1
  23. package/dist/formula/formulaUtils.js +15 -0
  24. package/dist/formula/formulaUtils.js.map +1 -1
  25. package/dist/formula/functionFormula.d.ts +2 -0
  26. package/dist/formula/functionFormula.js +88 -0
  27. package/dist/formula/functionFormula.js.map +1 -0
  28. package/dist/formula/objectFormula.d.ts +2 -0
  29. package/dist/formula/objectFormula.js +8 -0
  30. package/dist/formula/objectFormula.js.map +1 -0
  31. package/dist/formula/orFormula.d.ts +3 -0
  32. package/dist/formula/orFormula.js +27 -0
  33. package/dist/formula/orFormula.js.map +1 -0
  34. package/dist/formula/pathFormula.d.ts +2 -0
  35. package/dist/formula/pathFormula.js +13 -0
  36. package/dist/formula/pathFormula.js.map +1 -0
  37. package/dist/formula/recordFormula.d.ts +2 -0
  38. package/dist/formula/recordFormula.js +8 -0
  39. package/dist/formula/recordFormula.js.map +1 -0
  40. package/dist/formula/switchFormula.d.ts +3 -0
  41. package/dist/formula/switchFormula.js +40 -0
  42. package/dist/formula/switchFormula.js.map +1 -0
  43. package/dist/types.d.ts +1 -1
  44. package/dist/utils/measure.js +5 -0
  45. package/dist/utils/measure.js.map +1 -1
  46. package/package.json +1 -1
  47. package/src/api/api.test.ts +22 -22
  48. package/src/api/api.ts +36 -10
  49. package/src/component/ToddleComponent.actionReferences.test.ts +37 -0
  50. package/src/component/ToddleComponent.ts +9 -7
  51. package/src/component/actionUtils.ts +17 -1
  52. package/src/component/component.types.ts +8 -4
  53. package/src/formula/andFormula.test.ts +112 -0
  54. package/src/formula/andFormula.ts +33 -0
  55. package/src/formula/applyFormula.test.ts +151 -0
  56. package/src/formula/applyFormula.ts +72 -0
  57. package/src/formula/arrayFormula.test.ts +52 -0
  58. package/src/formula/arrayFormula.ts +14 -0
  59. package/src/formula/formula.ts +80 -206
  60. package/src/formula/formulaTypes.ts +7 -1
  61. package/src/formula/formulaUtils.ts +18 -1
  62. package/src/formula/functionFormula.test.ts +89 -0
  63. package/src/formula/functionFormula.ts +118 -0
  64. package/src/formula/objectFormula.test.ts +56 -0
  65. package/src/formula/objectFormula.ts +17 -0
  66. package/src/formula/orFormula.test.ts +113 -0
  67. package/src/formula/orFormula.ts +33 -0
  68. package/src/formula/pathFormula.test.ts +35 -0
  69. package/src/formula/pathFormula.ts +17 -0
  70. package/src/formula/recordFormula.test.ts +56 -0
  71. package/src/formula/recordFormula.ts +17 -0
  72. package/src/formula/switchFormula.test.ts +122 -0
  73. package/src/formula/switchFormula.ts +54 -0
  74. package/src/formula/testUtils.test.ts +68 -0
  75. package/src/types.ts +1 -1
  76. 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
+ }
@@ -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 { measure } from '../utils/measure'
13
- import { isDefined, toBoolean } from '../utils/util'
14
- import { type PluginFormula, type ToddleFormula } from './formulaTypes'
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 = string | number | boolean | null | object
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 type FormulaContext = {
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
- ctx: FormulaContext,
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
- let input: any = ctx.data
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
- for (const branch of formula.cases ?? []) {
211
- if (toBoolean(applyFormula(branch.condition, ctx))) {
212
- return applyFormula(branch.formula, ctx)
213
- }
234
+ if (
235
+ ctx.reportFormulaEvaluation &&
236
+ ctx.jsonPath.length < MAX_REPORT_DEPTH
237
+ ) {
238
+ return report(applyEvaluateAllSwitchFormula(formula, ctx))
214
239
  }
215
- return applyFormula(formula.default, ctx)
240
+ return applySwitchFormula(formula, ctx)
216
241
  }
217
242
  case 'or': {
218
- for (const entry of formula.arguments ?? []) {
219
- if (toBoolean(applyFormula(entry.formula, ctx))) {
220
- return true
221
- }
243
+ if (
244
+ ctx.reportFormulaEvaluation &&
245
+ ctx.jsonPath.length < MAX_REPORT_DEPTH
246
+ ) {
247
+ return report(applyEvaluateAllOrFormula(formula, ctx))
222
248
  }
223
- return false
249
+ return applyOrFormula(formula, ctx)
224
250
  }
225
251
  case 'and': {
226
- for (const entry of formula.arguments ?? []) {
227
- if (!toBoolean(applyFormula(entry.formula, ctx))) {
228
- return false
229
- }
252
+ if (
253
+ ctx.reportFormulaEvaluation &&
254
+ ctx.jsonPath.length < MAX_REPORT_DEPTH
255
+ ) {
256
+ return report(applyEvaluateAllAndFormula(formula, ctx))
230
257
  }
231
- return true
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
- const stopMeasure = measure(`Formula: ${formula.name}`, {
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
- const componentFormula = ctx.component?.formulas?.[formula.name]
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)) {