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