@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
@@ -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,
@@ -67,7 +67,7 @@ export function* getFormulasInFormula<Handler>({
67
67
  case 'value':
68
68
  break
69
69
  case 'record':
70
- for (const [key, entry] of formula.entries.entries()) {
70
+ for (const [key, entry] of (formula.entries ?? []).entries()) {
71
71
  yield* getFormulasInFormula({
72
72
  formula: entry.formula,
73
73
  globalFormulas,
@@ -148,7 +148,7 @@ export function* getFormulasInFormula<Handler>({
148
148
  }
149
149
  break
150
150
  case 'switch':
151
- for (const [key, c] of formula.cases.entries()) {
151
+ for (const [key, c] of formula.cases?.entries() ?? []) {
152
152
  yield* getFormulasInFormula({
153
153
  formula: c.condition,
154
154
  globalFormulas,
@@ -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
+ })
@@ -0,0 +1,118 @@
1
+ /* eslint-disable no-console */
2
+ import type { FormulaHandler, Toddle } from '../types'
3
+ import { measure } from '../utils/measure'
4
+ import { isDefined } from '../utils/util'
5
+ import {
6
+ applyFormula,
7
+ isToddleFormula,
8
+ type FormulaContext,
9
+ type FunctionOperation,
10
+ } from './formula'
11
+
12
+ export const applyFunctionFormula = (
13
+ formula: FunctionOperation,
14
+ ctx: FormulaContext,
15
+ ) => {
16
+ const stopMeasure = measure(`Formula: ${formula.name}`, {
17
+ formula,
18
+ component: ctx.component?.name,
19
+ })
20
+ const packageName = formula.package ?? ctx.package ?? undefined
21
+ const newFunc = (
22
+ ctx.toddle ??
23
+ ((globalThis as any).toddle as Toddle<unknown, unknown> | undefined)
24
+ )?.getCustomFormula(formula.name, packageName)
25
+ if (isDefined(newFunc)) {
26
+ ctx.package = packageName
27
+ const args = (formula.arguments ?? []).reduce<Record<string, unknown>>(
28
+ (args, arg, i) => ({
29
+ ...args,
30
+ [arg.name ?? `${i}`]: arg.isFunction
31
+ ? (Args: any) =>
32
+ applyFormula(
33
+ arg.formula,
34
+ {
35
+ ...ctx,
36
+ data: {
37
+ ...ctx.data,
38
+ Args: ctx.data.Args
39
+ ? { ...Args, '@toddle.parent': ctx.data.Args }
40
+ : Args,
41
+ },
42
+ },
43
+ ['arguments', i],
44
+ )
45
+ : applyFormula(arg.formula, ctx, ['arguments', i]),
46
+ }),
47
+ {},
48
+ )
49
+ try {
50
+ if (isToddleFormula(newFunc)) {
51
+ return applyFormula(
52
+ newFunc.formula,
53
+ {
54
+ ...ctx,
55
+ data: { ...ctx.data, Args: args },
56
+ },
57
+ ['formula'],
58
+ )
59
+ } else {
60
+ return newFunc.handler(args, {
61
+ root: ctx.root ?? document,
62
+ env: ctx.env,
63
+ } as any)
64
+ }
65
+ } catch (e) {
66
+ ctx.toddle.errors.push(e as Error)
67
+ if (ctx.env?.logErrors) {
68
+ console.error(e)
69
+ }
70
+ return null
71
+ } finally {
72
+ stopMeasure()
73
+ }
74
+ } else {
75
+ // Lookup legacy formula
76
+ const legacyFunc: FormulaHandler | undefined = (
77
+ ctx.toddle ?? ((globalThis as any).toddle as Toddle<unknown, unknown>)
78
+ ).getFormula(formula.name)
79
+ if (typeof legacyFunc === 'function') {
80
+ const args = (formula.arguments ?? []).map((arg, i) =>
81
+ arg.isFunction
82
+ ? (Args: any) =>
83
+ applyFormula(
84
+ arg.formula,
85
+ {
86
+ ...ctx,
87
+ data: {
88
+ ...ctx.data,
89
+ Args: ctx.data.Args
90
+ ? { ...Args, '@toddle.parent': ctx.data.Args }
91
+ : Args,
92
+ },
93
+ },
94
+ ['arguments', i],
95
+ )
96
+ : applyFormula(arg.formula, ctx, ['arguments', i]),
97
+ )
98
+ try {
99
+ return legacyFunc(args, ctx as any)
100
+ } catch (e) {
101
+ ctx.toddle.errors.push(e as Error)
102
+ if (ctx.env?.logErrors) {
103
+ console.error(e)
104
+ }
105
+ return null
106
+ } finally {
107
+ stopMeasure()
108
+ }
109
+ }
110
+ }
111
+ if (ctx.env?.logErrors) {
112
+ console.error(
113
+ `Could not find formula ${formula.name} in package ${packageName ?? ''}`,
114
+ formula,
115
+ )
116
+ }
117
+ return null
118
+ }
@@ -0,0 +1,56 @@
1
+ import { describe, expect, it } from 'bun:test'
2
+ import type { ObjectOperation } from './formula'
3
+ import { valueFormula } from './formulaUtils'
4
+ import { applyObjectFormula } from './objectFormula'
5
+ import {
6
+ createTestFormulaContext,
7
+ createTestFormulaContextForAllPaths,
8
+ } from './testUtils.test'
9
+
10
+ describe('applyObjectFormula', () => {
11
+ it('returns an object with evaluated values', () => {
12
+ const formula: ObjectOperation = {
13
+ type: 'object',
14
+ arguments: [
15
+ { name: 'a', formula: valueFormula(1) },
16
+ { name: 'b', formula: valueFormula('hello') },
17
+ { name: 'c', formula: valueFormula(true) },
18
+ ],
19
+ }
20
+ const ctx = createTestFormulaContext()
21
+ expect(applyObjectFormula(formula, ctx)).toEqual({
22
+ a: 1,
23
+ b: 'hello',
24
+ c: true,
25
+ })
26
+ })
27
+
28
+ it('returns an empty object if arguments is undefined', () => {
29
+ const formula: ObjectOperation = {
30
+ type: 'object',
31
+ arguments: undefined,
32
+ }
33
+ const ctx = createTestFormulaContext()
34
+ expect(applyObjectFormula(formula, ctx)).toEqual({})
35
+ })
36
+
37
+ it('evaluates all formulas and reports results in "report" mode', () => {
38
+ const formula: ObjectOperation = {
39
+ type: 'object',
40
+ arguments: [
41
+ { name: 'x', formula: valueFormula('foo') },
42
+ { name: 'y', formula: valueFormula('bar') },
43
+ ],
44
+ }
45
+ const results: Record<string, any> = {}
46
+ const ctx = createTestFormulaContextForAllPaths(
47
+ {},
48
+ (path, result) => (results[path.join('/')] = result),
49
+ )
50
+ expect(applyObjectFormula(formula, ctx)).toEqual({ x: 'foo', y: 'bar' })
51
+ expect(results).toMatchObject({
52
+ 'arguments/0/formula': 'foo',
53
+ 'arguments/1/formula': 'bar',
54
+ })
55
+ })
56
+ })
@@ -0,0 +1,17 @@
1
+ import {
2
+ applyFormula,
3
+ type FormulaContext,
4
+ type ObjectOperation,
5
+ } from './formula'
6
+
7
+ export const applyObjectFormula = (
8
+ formula: ObjectOperation,
9
+ ctx: FormulaContext,
10
+ ) => {
11
+ return Object.fromEntries(
12
+ formula.arguments?.map((entry, i) => [
13
+ entry.name,
14
+ applyFormula(entry.formula, ctx, ['arguments', i, 'formula']),
15
+ ]) ?? [],
16
+ )
17
+ }
@@ -0,0 +1,113 @@
1
+ import { describe, expect, it } from 'bun:test'
2
+ import { applyFormula, type OrOperation } from './formula'
3
+ import { valueFormula } from './formulaUtils'
4
+ import { applyEvaluateAllOrFormula, applyOrFormula } from './orFormula'
5
+ import {
6
+ createTestFormulaContext,
7
+ createTestFormulaContextForAllPaths,
8
+ } from './testUtils.test'
9
+
10
+ describe('applyOrFormula', () => {
11
+ it('returns true if any argument is truthy', () => {
12
+ const formula: OrOperation = {
13
+ type: 'or',
14
+ arguments: [
15
+ { formula: valueFormula(false) },
16
+ { formula: valueFormula(0) },
17
+ { formula: valueFormula('yes') },
18
+ ],
19
+ }
20
+ const ctx = createTestFormulaContext()
21
+ expect(applyOrFormula(formula, ctx)).toBe(true)
22
+ })
23
+
24
+ it('returns false if all arguments are falsy', () => {
25
+ const formula: OrOperation = {
26
+ type: 'or',
27
+ arguments: [
28
+ { formula: valueFormula(false) },
29
+ { formula: valueFormula(null) },
30
+ { formula: valueFormula(undefined) },
31
+ ],
32
+ }
33
+ const ctx = createTestFormulaContext()
34
+ expect(applyOrFormula(formula, ctx)).toBe(false)
35
+ })
36
+
37
+ it('returns true for first truthy argument', () => {
38
+ const formula: OrOperation = {
39
+ type: 'or',
40
+ arguments: [
41
+ { formula: valueFormula(false) },
42
+ { formula: valueFormula(true) },
43
+ { formula: valueFormula(false) },
44
+ ],
45
+ }
46
+ const ctx = createTestFormulaContext()
47
+ expect(applyOrFormula(formula, ctx)).toBe(true)
48
+ })
49
+ })
50
+
51
+ describe('applyEvaluateAllOrFormula', () => {
52
+ it('visits all formulas in "report" mode even if the first argument is truthy', () => {
53
+ const formula: OrOperation = {
54
+ type: 'or',
55
+ arguments: [
56
+ { formula: valueFormula(true) },
57
+ { formula: valueFormula('hello') },
58
+ { formula: valueFormula(false) },
59
+ ],
60
+ }
61
+ const results: Record<string, any> = {}
62
+ const ctx = createTestFormulaContextForAllPaths(
63
+ {},
64
+ (path, result) => (results[path.join('/')] = result),
65
+ )
66
+ // Call applyFormula directly since it will report the results
67
+ expect(applyFormula(formula, ctx, [])).toBe(true)
68
+ expect(results).toMatchObject({
69
+ 'arguments/0/formula': true,
70
+ 'arguments/1/formula': 'hello',
71
+ 'arguments/2/formula': false,
72
+ })
73
+ })
74
+
75
+ it('returns true if any argument is truthy', () => {
76
+ const formula: OrOperation = {
77
+ type: 'or',
78
+ arguments: [
79
+ { formula: valueFormula(false) },
80
+ { formula: valueFormula(1) },
81
+ { formula: valueFormula(false) },
82
+ ],
83
+ }
84
+ const ctx = createTestFormulaContext()
85
+ expect(applyEvaluateAllOrFormula(formula, ctx)).toBe(true)
86
+ })
87
+
88
+ it('returns false if all arguments are falsy', () => {
89
+ const formula: OrOperation = {
90
+ type: 'or',
91
+ arguments: [
92
+ { formula: valueFormula(false) },
93
+ { formula: valueFormula(null) },
94
+ { formula: valueFormula(undefined) },
95
+ ],
96
+ }
97
+ const ctx = createTestFormulaContext()
98
+ expect(applyEvaluateAllOrFormula(formula, ctx)).toBe(false)
99
+ })
100
+
101
+ it('returns true if last argument is truthy', () => {
102
+ const formula: OrOperation = {
103
+ type: 'or',
104
+ arguments: [
105
+ { formula: valueFormula(false) },
106
+ { formula: valueFormula(false) },
107
+ { formula: valueFormula('ok') },
108
+ ],
109
+ }
110
+ const ctx = createTestFormulaContext()
111
+ expect(applyEvaluateAllOrFormula(formula, ctx)).toBe(true)
112
+ })
113
+ })
@@ -0,0 +1,33 @@
1
+ import { toBoolean } from '../utils/util'
2
+ import { applyFormula, type FormulaContext, type OrOperation } from './formula'
3
+
4
+ export const applyOrFormula = (formula: OrOperation, ctx: FormulaContext) => {
5
+ for (let i = 0; i < (formula.arguments ?? []).length; i++) {
6
+ const arg = (formula.arguments ?? [])[i]
7
+ if (
8
+ toBoolean(applyFormula(arg?.formula, ctx, ['arguments', i, 'formula']))
9
+ ) {
10
+ return true
11
+ }
12
+ }
13
+ return false
14
+ }
15
+
16
+ export const applyEvaluateAllOrFormula = (
17
+ formula: OrOperation,
18
+ ctx: FormulaContext,
19
+ ) => {
20
+ let orResult = false
21
+ for (let i = 0; i < (formula.arguments ?? []).length; i++) {
22
+ const arg = (formula.arguments ?? [])[i]
23
+ const argResult = applyFormula(arg?.formula, ctx, [
24
+ 'arguments',
25
+ i,
26
+ 'formula',
27
+ ])
28
+ if (toBoolean(argResult)) {
29
+ orResult = true
30
+ }
31
+ }
32
+ return orResult
33
+ }
@@ -0,0 +1,35 @@
1
+ import { describe, expect, it } from 'bun:test'
2
+ import type { PathOperation } from './formula'
3
+ import { applyPathFormula } from './pathFormula'
4
+
5
+ describe('applyPathFormula', () => {
6
+ it('returns the value at the given path', () => {
7
+ const formula: PathOperation = { type: 'path', path: ['foo', 'bar'] }
8
+ const data: any = { foo: { bar: 42 } }
9
+ expect(applyPathFormula(formula, data)).toBe(42)
10
+ })
11
+
12
+ it('returns undefined if the path does not exist', () => {
13
+ const formula: PathOperation = { type: 'path', path: ['foo', 'baz'] }
14
+ const data: any = { foo: { bar: 42 } }
15
+ expect(applyPathFormula(formula, data)).toBeUndefined()
16
+ })
17
+
18
+ it('returns null if intermediate value is not an object', () => {
19
+ const formula: PathOperation = { type: 'path', path: ['foo', 'bar', 'baz'] }
20
+ const data: any = { foo: { bar: 42 } }
21
+ expect(applyPathFormula(formula, data)).toBeNull()
22
+ })
23
+
24
+ it('returns the root value if path is empty', () => {
25
+ const formula: PathOperation = { type: 'path', path: [] }
26
+ const data: any = { foo: 1 }
27
+ expect(applyPathFormula(formula, data)).toEqual(data)
28
+ })
29
+
30
+ it('works with numeric keys', () => {
31
+ const formula: PathOperation = { type: 'path', path: ['arr', 1] }
32
+ const data: any = { arr: [10, 20, 30] }
33
+ expect(applyPathFormula(formula, data)).toBe(20)
34
+ })
35
+ })
@@ -0,0 +1,17 @@
1
+ import type { FormulaContext, PathOperation } from './formula'
2
+
3
+ export const applyPathFormula = (
4
+ formula: PathOperation,
5
+ data: FormulaContext['data'],
6
+ ) => {
7
+ let input: any = data
8
+ for (const key of formula.path) {
9
+ if (input && typeof input === 'object') {
10
+ input = input[key]
11
+ } else {
12
+ return null
13
+ }
14
+ }
15
+
16
+ return input
17
+ }
@@ -0,0 +1,56 @@
1
+ import { describe, expect, it } from 'bun:test'
2
+ import type { RecordOperation } from './formula'
3
+ import { valueFormula } from './formulaUtils'
4
+ import { applyRecordFormula } from './recordFormula'
5
+ import {
6
+ createTestFormulaContext,
7
+ createTestFormulaContextForAllPaths,
8
+ } from './testUtils.test'
9
+
10
+ describe('applyRecordFormula', () => {
11
+ it('returns an object with evaluated entries', () => {
12
+ const formula: RecordOperation = {
13
+ type: 'record',
14
+ entries: [
15
+ { name: 'a', formula: valueFormula(1) },
16
+ { name: 'b', formula: valueFormula('hello') },
17
+ { name: 'c', formula: valueFormula(true) },
18
+ ],
19
+ }
20
+ const ctx = createTestFormulaContext()
21
+ expect(applyRecordFormula(formula, ctx)).toEqual({
22
+ a: 1,
23
+ b: 'hello',
24
+ c: true,
25
+ })
26
+ })
27
+
28
+ it('returns an empty object if entries is empty', () => {
29
+ const formula: RecordOperation = {
30
+ type: 'record',
31
+ entries: [],
32
+ }
33
+ const ctx = createTestFormulaContext()
34
+ expect(applyRecordFormula(formula, ctx)).toEqual({})
35
+ })
36
+
37
+ it('evaluates all formulas and reports results in "report" mode', () => {
38
+ const formula: RecordOperation = {
39
+ type: 'record',
40
+ entries: [
41
+ { name: 'x', formula: valueFormula('foo') },
42
+ { name: 'y', formula: valueFormula('bar') },
43
+ ],
44
+ }
45
+ const results: Record<string, any> = {}
46
+ const ctx = createTestFormulaContextForAllPaths(
47
+ {},
48
+ (path, result) => (results[path.join('/')] = result),
49
+ )
50
+ expect(applyRecordFormula(formula, ctx)).toEqual({ x: 'foo', y: 'bar' })
51
+ expect(results).toMatchObject({
52
+ 'entries/0/formula': 'foo',
53
+ 'entries/1/formula': 'bar',
54
+ })
55
+ })
56
+ })
@@ -0,0 +1,17 @@
1
+ import {
2
+ applyFormula,
3
+ type FormulaContext,
4
+ type RecordOperation,
5
+ } from './formula'
6
+
7
+ export const applyRecordFormula = (
8
+ formula: RecordOperation,
9
+ ctx: FormulaContext,
10
+ ) => {
11
+ return Object.fromEntries(
12
+ (formula.entries ?? []).map((entry, i) => [
13
+ entry.name,
14
+ applyFormula(entry.formula, ctx, ['entries', i, 'formula']),
15
+ ]),
16
+ )
17
+ }