@nordcraft/runtime 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 (74) hide show
  1. package/dist/api/createAPI.js +29 -11
  2. package/dist/api/createAPI.js.map +1 -1
  3. package/dist/api/createAPIv2.js +49 -11
  4. package/dist/api/createAPIv2.js.map +1 -1
  5. package/dist/components/createComponent.js +19 -10
  6. package/dist/components/createComponent.js.map +1 -1
  7. package/dist/components/createElement.js +48 -16
  8. package/dist/components/createElement.js.map +1 -1
  9. package/dist/components/createNode.js +22 -11
  10. package/dist/components/createNode.js.map +1 -1
  11. package/dist/components/createNode.test.js +3 -3
  12. package/dist/components/createNode.test.js.map +1 -1
  13. package/dist/components/createSlot.js +2 -1
  14. package/dist/components/createSlot.js.map +1 -1
  15. package/dist/components/createText.js +7 -2
  16. package/dist/components/createText.js.map +1 -1
  17. package/dist/components/renderComponent.d.ts +4 -1
  18. package/dist/components/renderComponent.js +4 -2
  19. package/dist/components/renderComponent.js.map +1 -1
  20. package/dist/context/subscribeToContext.js +12 -2
  21. package/dist/context/subscribeToContext.js.map +1 -1
  22. package/dist/custom-element/ToddleComponent.js +12 -4
  23. package/dist/custom-element/ToddleComponent.js.map +1 -1
  24. package/dist/custom-element.main.esm.js +29 -29
  25. package/dist/custom-element.main.esm.js.map +4 -4
  26. package/dist/editor/editorUtils.d.ts +2 -0
  27. package/dist/editor/editorUtils.js +26 -0
  28. package/dist/editor/editorUtils.js.map +1 -0
  29. package/dist/editor/types.d.ts +4 -0
  30. package/dist/editor/types.js.map +1 -1
  31. package/dist/editor-preview.main.js +82 -18
  32. package/dist/editor-preview.main.js.map +1 -1
  33. package/dist/events/handleAction.js +74 -42
  34. package/dist/events/handleAction.js.map +1 -1
  35. package/dist/page.main.esm.js +3 -3
  36. package/dist/page.main.esm.js.map +4 -4
  37. package/dist/page.main.js +39 -8
  38. package/dist/page.main.js.map +1 -1
  39. package/dist/utils/createFormulaCache.js.map +1 -1
  40. package/dist/utils/nodes.d.ts +1 -0
  41. package/dist/utils/nodes.js +9 -0
  42. package/dist/utils/nodes.js.map +1 -1
  43. package/dist/utils/nodes.test.d.ts +1 -0
  44. package/dist/utils/nodes.test.js +192 -0
  45. package/dist/utils/nodes.test.js.map +1 -0
  46. package/dist/utils/subscribeCustomProperty.d.ts +1 -1
  47. package/dist/utils/subscribeCustomProperty.js +8 -4
  48. package/dist/utils/subscribeCustomProperty.js.map +1 -1
  49. package/dist/utils/subscribeCustomProperty.test.d.ts +1 -0
  50. package/dist/utils/subscribeCustomProperty.test.js +63 -0
  51. package/dist/utils/subscribeCustomProperty.test.js.map +1 -0
  52. package/package.json +3 -3
  53. package/src/api/createAPI.ts +90 -46
  54. package/src/api/createAPIv2.ts +79 -13
  55. package/src/components/createComponent.ts +123 -85
  56. package/src/components/createElement.ts +63 -27
  57. package/src/components/createNode.test.ts +3 -3
  58. package/src/components/createNode.ts +55 -31
  59. package/src/components/createSlot.ts +2 -1
  60. package/src/components/createText.ts +35 -18
  61. package/src/components/renderComponent.ts +8 -1
  62. package/src/context/subscribeToContext.ts +12 -2
  63. package/src/custom-element/ToddleComponent.ts +37 -22
  64. package/src/editor/editorUtils.ts +28 -0
  65. package/src/editor/types.ts +5 -0
  66. package/src/editor-preview.main.ts +137 -46
  67. package/src/events/handleAction.ts +190 -113
  68. package/src/page.main.ts +64 -26
  69. package/src/types.d.ts +3 -0
  70. package/src/utils/createFormulaCache.ts +2 -2
  71. package/src/utils/nodes.test.ts +246 -0
  72. package/src/utils/nodes.ts +11 -0
  73. package/src/utils/subscribeCustomProperty.test.ts +78 -0
  74. package/src/utils/subscribeCustomProperty.ts +21 -22
@@ -2,9 +2,13 @@
2
2
  import type { LegacyComponentAPI } from '@nordcraft/core/dist/api/apiTypes'
3
3
  import { mapHeadersToObject } from '@nordcraft/core/dist/api/headers'
4
4
  import type { ComponentData } from '@nordcraft/core/dist/component/component.types'
5
- import { applyFormula, isFormula } from '@nordcraft/core/dist/formula/formula'
5
+ import {
6
+ applyFormula,
7
+ isFormula,
8
+ type FormulaContext,
9
+ } from '@nordcraft/core/dist/formula/formula'
6
10
  import type { Nullable } from '@nordcraft/core/dist/types'
7
- import { mapValues } from '@nordcraft/core/dist/utils/collections'
11
+ import { mapObject } from '@nordcraft/core/dist/utils/collections'
8
12
  import { parseJSONWithDate } from '@nordcraft/core/dist/utils/json'
9
13
  import { handleAction } from '../events/handleAction'
10
14
  import type { Signal } from '../signal/signal'
@@ -35,7 +39,7 @@ export function createLegacyAPI(
35
39
  api: LegacyComponentAPI,
36
40
  data: ComponentData,
37
41
  ): ApiRequest {
38
- const formulaContext = {
42
+ const formulaContext: FormulaContext = {
39
43
  data,
40
44
  component: ctx.component,
41
45
  formulaCache: ctx.formulaCache,
@@ -43,14 +47,20 @@ export function createLegacyAPI(
43
47
  package: ctx.package,
44
48
  toddle: ctx.toddle,
45
49
  env: ctx.env,
50
+ jsonPath: ctx.jsonPath,
51
+ reportFormulaEvaluation: ctx.reportFormulaEvaluation,
46
52
  }
47
53
 
48
54
  // construct the url
49
- const baseUrl = applyFormula(api.url, formulaContext) ?? ''
55
+ const baseUrl = applyFormula(api.url, formulaContext, ['url']) ?? ''
50
56
  const urlPath =
51
57
  api.path && api.path.length > 0
52
58
  ? '/' +
53
- api.path.map((p) => applyFormula(p.formula, formulaContext)).join('/')
59
+ api.path
60
+ .map((p, i) =>
61
+ applyFormula(p.formula, formulaContext, ['path', i, 'formula']),
62
+ )
63
+ .join('/')
54
64
  : ''
55
65
 
56
66
  // build querystring
@@ -60,25 +70,21 @@ export function createLegacyAPI(
60
70
  ? '?' +
61
71
  queryParams
62
72
  .map(
63
- (param) =>
73
+ (param, i) =>
64
74
  `${param.name}=${encodeURIComponent(
65
- applyFormula(param.formula, formulaContext),
75
+ applyFormula(param.formula, formulaContext, [
76
+ 'queryParams',
77
+ i,
78
+ 'formula',
79
+ ]),
66
80
  )}`,
67
81
  )
68
82
  .join('&')
69
83
  : ''
70
84
  const headers = isFormula(api.headers) // this is supporting a few legacy cases where the whole header object was set as a formula. This is no longer possible
71
- ? applyFormula(api.headers, {
72
- data,
73
- component: ctx.component,
74
- formulaCache: ctx.formulaCache,
75
- root: ctx.root,
76
- package: ctx.package,
77
- toddle: ctx.toddle,
78
- env: ctx.env,
79
- })
80
- : mapValues(api.headers ?? {}, (value) =>
81
- applyFormula(value, {
85
+ ? applyFormula(
86
+ api.headers,
87
+ {
82
88
  data,
83
89
  component: ctx.component,
84
90
  formulaCache: ctx.formulaCache,
@@ -86,7 +92,27 @@ export function createLegacyAPI(
86
92
  package: ctx.package,
87
93
  toddle: ctx.toddle,
88
94
  env: ctx.env,
89
- }),
95
+ jsonPath: ctx.jsonPath,
96
+ reportFormulaEvaluation: ctx.reportFormulaEvaluation,
97
+ },
98
+ ['headers'],
99
+ )
100
+ : mapObject(api.headers ?? {}, ([key, value]) =>
101
+ applyFormula(
102
+ value,
103
+ {
104
+ data,
105
+ component: ctx.component,
106
+ formulaCache: ctx.formulaCache,
107
+ root: ctx.root,
108
+ package: ctx.package,
109
+ toddle: ctx.toddle,
110
+ env: ctx.env,
111
+ jsonPath: ctx.jsonPath,
112
+ reportFormulaEvaluation: ctx.reportFormulaEvaluation,
113
+ },
114
+ ['headers', key],
115
+ ),
90
116
  )
91
117
  const contentType = String(
92
118
  Object.entries(headers).find(
@@ -97,15 +123,21 @@ export function createLegacyAPI(
97
123
  const body =
98
124
  api.body && ['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)
99
125
  ? encodeBody(
100
- applyFormula(api.body, {
101
- data,
102
- component: ctx.component,
103
- formulaCache: ctx.formulaCache,
104
- root: ctx.root,
105
- package: ctx.package,
106
- toddle: ctx.toddle,
107
- env: ctx.env,
108
- }),
126
+ applyFormula(
127
+ api.body,
128
+ {
129
+ data,
130
+ component: ctx.component,
131
+ formulaCache: ctx.formulaCache,
132
+ root: ctx.root,
133
+ package: ctx.package,
134
+ toddle: ctx.toddle,
135
+ env: ctx.env,
136
+ jsonPath: ctx.jsonPath,
137
+ reportFormulaEvaluation: ctx.reportFormulaEvaluation,
138
+ },
139
+ ['body'],
140
+ ),
109
141
  contentType,
110
142
  )
111
143
  : undefined
@@ -286,15 +318,21 @@ export function createLegacyAPI(
286
318
  data: null,
287
319
  isLoading:
288
320
  api.autoFetch &&
289
- applyFormula(api.autoFetch, {
290
- data: ctx.dataSignal.get(),
291
- component: ctx.component,
292
- formulaCache: ctx.formulaCache,
293
- root: ctx.root,
294
- package: ctx.package,
295
- toddle: ctx.toddle,
296
- env: ctx.env,
297
- })
321
+ applyFormula(
322
+ api.autoFetch,
323
+ {
324
+ data: ctx.dataSignal.get(),
325
+ component: ctx.component,
326
+ formulaCache: ctx.formulaCache,
327
+ root: ctx.root,
328
+ package: ctx.package,
329
+ toddle: ctx.toddle,
330
+ env: ctx.env,
331
+ jsonPath: ctx.jsonPath,
332
+ reportFormulaEvaluation: ctx.reportFormulaEvaluation,
333
+ },
334
+ ['autoFetch'],
335
+ )
298
336
  ? true
299
337
  : false,
300
338
  error: null,
@@ -308,15 +346,21 @@ export function createLegacyAPI(
308
346
  payloadSignal.subscribe((body) => {
309
347
  if (
310
348
  api.autoFetch &&
311
- applyFormula(api.autoFetch, {
312
- data: ctx.dataSignal.get(),
313
- component: ctx.component,
314
- formulaCache: ctx.formulaCache,
315
- root: ctx.root,
316
- package: ctx.package,
317
- toddle: ctx.toddle,
318
- env: ctx.env,
319
- })
349
+ applyFormula(
350
+ api.autoFetch,
351
+ {
352
+ data: ctx.dataSignal.get(),
353
+ component: ctx.component,
354
+ formulaCache: ctx.formulaCache,
355
+ root: ctx.root,
356
+ package: ctx.package,
357
+ toddle: ctx.toddle,
358
+ env: ctx.env,
359
+ jsonPath: ctx.jsonPath,
360
+ reportFormulaEvaluation: ctx.reportFormulaEvaluation,
361
+ },
362
+ ['autoFetch'],
363
+ )
320
364
  ) {
321
365
  // We should only lookup cached data for pages since
322
366
  // we don't fetch data for component APIs during SSR
@@ -97,7 +97,7 @@ export function createAPI({
97
97
  componentData: ComponentData | undefined,
98
98
  ): FormulaContext {
99
99
  // Use the general formula context to evaluate the arguments of the api
100
- const formulaContext = {
100
+ const formulaContext: FormulaContext = {
101
101
  data: ctx.dataSignal.get(),
102
102
  component: ctx.component,
103
103
  formulaCache: ctx.formulaCache,
@@ -105,13 +105,15 @@ export function createAPI({
105
105
  package: ctx.package,
106
106
  toddle: ctx.toddle,
107
107
  env: ctx.env,
108
+ jsonPath: ctx.jsonPath,
109
+ reportFormulaEvaluation: ctx.reportFormulaEvaluation,
108
110
  }
109
111
 
110
112
  // Make sure inputs are also available in the formula context
111
113
  const evaluatedInputs = Object.entries(api.inputs).reduce<
112
114
  Record<string, unknown>
113
115
  >((acc, [key, value]) => {
114
- acc[key] = applyFormula(value.formula, formulaContext)
116
+ acc[key] = applyFormula(value.formula, formulaContext, ['inputs', key])
115
117
  return acc
116
118
  }, {})
117
119
 
@@ -131,6 +133,8 @@ export function createAPI({
131
133
  data,
132
134
  toddle: ctx.toddle,
133
135
  env: ctx.env,
136
+ jsonPath: ctx.jsonPath,
137
+ reportFormulaEvaluation: ctx.reportFormulaEvaluation,
134
138
  }
135
139
  }
136
140
 
@@ -140,15 +144,19 @@ export function createAPI({
140
144
  ([_, rule]) => rule.index,
141
145
  )) {
142
146
  const formulaContext = getFormulaContext(api, componentData)
143
- const location = applyFormula(rule.formula, {
144
- ...formulaContext,
145
- data: {
146
- ...formulaContext.data,
147
- Apis: {
148
- [api.name]: ctx.dataSignal.get().Apis?.[api.name] as ApiStatus,
147
+ const location = applyFormula(
148
+ rule.formula,
149
+ {
150
+ ...formulaContext,
151
+ data: {
152
+ ...formulaContext.data,
153
+ Apis: {
154
+ [api.name]: ctx.dataSignal.get().Apis?.[api.name] as ApiStatus,
155
+ },
149
156
  },
150
157
  },
151
- })
158
+ ['redirectRules', ruleName],
159
+ )
152
160
  if (typeof location === 'string') {
153
161
  const url = validateUrl({
154
162
  path: location,
@@ -282,6 +290,22 @@ export function createAPI({
282
290
  },
283
291
  },
284
292
  })
293
+
294
+ ctx.reportFormulaEvaluation?.(
295
+ ['apis', api.name],
296
+ {
297
+ isLoading: false,
298
+ data: data.body,
299
+ error: null,
300
+ response: {
301
+ status: data.status,
302
+ headers: data.headers,
303
+ performance,
304
+ },
305
+ },
306
+ ctx,
307
+ )
308
+
285
309
  const appliedRedirectRule = handleRedirectRules(api, componentData)
286
310
  if (appliedRedirectRule) {
287
311
  ctx.dataSignal.set({
@@ -345,6 +369,22 @@ export function createAPI({
345
369
  },
346
370
  },
347
371
  })
372
+
373
+ ctx.reportFormulaEvaluation?.(
374
+ ['apis', api.name],
375
+ {
376
+ isLoading: false,
377
+ data: null,
378
+ error: data.body,
379
+ response: {
380
+ status: data.status,
381
+ headers: data.headers,
382
+ performance,
383
+ },
384
+ },
385
+ ctx,
386
+ )
387
+
348
388
  const appliedRedirectRule = handleRedirectRules(api, componentData)
349
389
  if (appliedRedirectRule) {
350
390
  ctx.dataSignal.set({
@@ -400,6 +440,17 @@ export function createAPI({
400
440
  },
401
441
  },
402
442
  })
443
+
444
+ ctx.reportFormulaEvaluation?.(
445
+ ['apis', api.name],
446
+ {
447
+ isLoading: true,
448
+ data: ctx.dataSignal.get().Apis?.[api.name]?.data ?? null,
449
+ error: null,
450
+ },
451
+ ctx,
452
+ )
453
+
403
454
  let response
404
455
 
405
456
  try {
@@ -407,6 +458,7 @@ export function createAPI({
407
458
  ? (applyFormula(
408
459
  api.server.proxy.enabled.formula,
409
460
  getFormulaContext(api, componentData),
461
+ ['server', 'proxy', 'enabled'],
410
462
  ) ?? false)
411
463
  : false
412
464
 
@@ -429,6 +481,7 @@ export function createAPI({
429
481
  applyFormula(
430
482
  api.server?.proxy?.useTemplatesInBody?.formula,
431
483
  getFormulaContext(api, componentData),
484
+ ['server', 'proxy', 'useTemplatesInBody'],
432
485
  ),
433
486
  )
434
487
  if (allowBodyTemplateValues) {
@@ -490,6 +543,7 @@ export function createAPI({
490
543
  applyFormula(
491
544
  api.client?.debounce?.formula,
492
545
  getFormulaContext(api, componentData),
546
+ ['client', 'debounce'],
493
547
  ),
494
548
  )
495
549
  })
@@ -984,15 +1038,24 @@ export function createAPI({
984
1038
  payloadSignal = ctx.dataSignal.map((data) => {
985
1039
  const payloadContext = getFormulaContext(api, data)
986
1040
  const request = constructRequest(api, data)
1041
+
1042
+ if (ctx.reportFormulaEvaluation) {
1043
+ const apiStatus = data.Apis?.[api.name]
1044
+ if (apiStatus) {
1045
+ ctx.reportFormulaEvaluation(['apis', api.name], apiStatus, ctx)
1046
+ }
1047
+ }
987
1048
  return {
988
1049
  request,
989
1050
  api: getApiForComparison(api),
990
1051
  // Serialize the Headers object to be able to compare changes
991
1052
  headers: Array.from(request.requestSettings.headers.entries()),
992
1053
  autoFetch: api.autoFetch
993
- ? applyFormula(api.autoFetch, payloadContext)
1054
+ ? applyFormula(api.autoFetch, payloadContext, ['autoFetch'])
994
1055
  : false,
995
- proxy: applyFormula(api.server?.proxy?.enabled.formula, payloadContext),
1056
+ proxy: applyFormula(api.server?.proxy?.enabled.formula, payloadContext, [
1057
+ 'proxy',
1058
+ ]),
996
1059
  }
997
1060
  })
998
1061
  payloadSignal.subscribe(async (apiData) => {
@@ -1050,6 +1113,7 @@ export function createAPI({
1050
1113
  applyFormula(
1051
1114
  api.autoFetch,
1052
1115
  getFormulaContext(api, initialComponentData),
1116
+ ['autoFetch'],
1053
1117
  )
1054
1118
  ) {
1055
1119
  // Execute will set the initial status of the api in the dataSignal
@@ -1155,7 +1219,8 @@ export function createAPI({
1155
1219
  api = newApi
1156
1220
  const updateContext = getFormulaContext(api, componentData)
1157
1221
  const autoFetch =
1158
- api.autoFetch && applyFormula(api.autoFetch, updateContext)
1222
+ api.autoFetch &&
1223
+ applyFormula(api.autoFetch, updateContext, ['autoFetch'])
1159
1224
  if (autoFetch) {
1160
1225
  const request = constructRequest(newApi, componentData)
1161
1226
  payloadSignal?.set({
@@ -1165,6 +1230,7 @@ export function createAPI({
1165
1230
  proxy: applyFormula(
1166
1231
  newApi.server?.proxy?.enabled.formula,
1167
1232
  updateContext,
1233
+ ['proxy'],
1168
1234
  ),
1169
1235
  // Serialize the Headers object to be able to compare changes
1170
1236
  headers: Array.from(request.requestSettings.headers.entries()),
@@ -1174,7 +1240,7 @@ export function createAPI({
1174
1240
  triggerActions: (componentData) => {
1175
1241
  const apiData = ctx.dataSignal.get().Apis?.[api.name]
1176
1242
  if (
1177
- apiData === undefined ||
1243
+ !isDefined(apiData) ||
1178
1244
  (apiData.data === null && apiData.error === null)
1179
1245
  ) {
1180
1246
  return
@@ -6,9 +6,10 @@ import type {
6
6
  } from '@nordcraft/core/dist/component/component.types'
7
7
  import { applyFormula } from '@nordcraft/core/dist/formula/formula'
8
8
  import { appendUnit } from '@nordcraft/core/dist/styling/customProperty'
9
- import { mapObject } from '@nordcraft/core/dist/utils/collections'
9
+ import { filterObject, mapObject } from '@nordcraft/core/dist/utils/collections'
10
10
  import { getNodeSelector } from '@nordcraft/core/dist/utils/getNodeSelector'
11
11
  import { isDefined } from '@nordcraft/core/dist/utils/util'
12
+ import type { ComponentAPI } from '@nordcraft/core/src/api/apiTypes'
12
13
  import { isContextApiV2 } from '../api/apiUtils'
13
14
  import { createLegacyAPI } from '../api/createAPI'
14
15
  import { createAPI } from '../api/createAPIv2'
@@ -68,15 +69,20 @@ export function createComponent({
68
69
  package: ctx.package,
69
70
  toddle: ctx.toddle,
70
71
  env: ctx.env,
72
+ reportFormulaEvaluation: ctx.reportFormulaEvaluation,
71
73
  }
72
74
  const attributesSignal = dataSignal.map((data) => {
73
75
  return mapObject(node.attrs, ([attr, value]) => [
74
76
  attr,
75
77
  value?.type !== 'value'
76
- ? applyFormula(value, {
77
- ...formulaCtx,
78
- data,
79
- })
78
+ ? applyFormula(
79
+ value,
80
+ {
81
+ ...formulaCtx,
82
+ data,
83
+ },
84
+ ['attrs', attr],
85
+ )
80
86
  : value?.value,
81
87
  ])
82
88
  })
@@ -84,22 +90,29 @@ export function createComponent({
84
90
  const componentDataSignal = signal<ComponentData>({
85
91
  Location: dataSignal.get().Location,
86
92
  Attributes: attributesSignal.get(),
87
- Apis: mapObject(component.apis ?? {}, ([name, api]) => [
88
- name,
89
- {
90
- data: null,
91
- isLoading:
92
- api.autoFetch &&
93
- applyFormula(api.autoFetch, {
94
- ...formulaCtx,
95
- component,
96
- data: dataSignal.get(),
97
- })
98
- ? true
99
- : false,
100
- error: null,
101
- },
102
- ]),
93
+ Apis: mapObject(
94
+ filterObject(component.apis ?? {}, ([_, api]) => isDefined(api)),
95
+ ([name, api]) => [
96
+ name,
97
+ {
98
+ data: null,
99
+ isLoading:
100
+ api!.autoFetch &&
101
+ applyFormula(
102
+ api!.autoFetch,
103
+ {
104
+ ...formulaCtx,
105
+ component,
106
+ data: dataSignal.get(),
107
+ },
108
+ ['apis', name, 'autoFetch'],
109
+ )
110
+ ? true
111
+ : false,
112
+ error: null,
113
+ },
114
+ ],
115
+ ),
103
116
  })
104
117
 
105
118
  // Subscribe to global stores (currently only theme)
@@ -120,30 +133,67 @@ export function createComponent({
120
133
  ...data,
121
134
  Variables: mapObject(component.variables ?? {}, ([name, variable]) => [
122
135
  name,
123
- applyFormula(variable.initialValue, {
124
- // Initial value
125
- ...formulaCtx,
126
- component,
127
- data: componentDataSignal.get(),
128
- }),
136
+ applyFormula(
137
+ variable.initialValue,
138
+ {
139
+ // Initial value
140
+ ...formulaCtx,
141
+ component,
142
+ data: componentDataSignal.get(),
143
+ },
144
+ ['variables', name],
145
+ ),
129
146
  ]),
130
147
  }))
131
148
  registerComponentToLogState(component, componentDataSignal)
132
149
 
133
150
  // Call the abort signal if the component's datasignal is destroyed (component unmounted) to cancel any pending requests
134
151
  const abortController = new AbortController()
135
- componentDataSignal.subscribe(() => {}, {
136
- destroy: () =>
137
- abortController.abort(`Component ${component.name} unmounted`),
138
- })
152
+ componentDataSignal.subscribe(
153
+ (data) => {
154
+ Object.entries(data.Variables ?? {}).forEach(([name, value]) => {
155
+ ctx.reportFormulaEvaluation?.(['variables', name], value, ctx)
156
+ })
157
+ },
158
+ {
159
+ destroy: () =>
160
+ abortController.abort(`Component ${component.name} unmounted`),
161
+ },
162
+ )
139
163
  const formulaCache = createFormulaCache(component)
140
164
 
141
165
  // Note: this function must run procedurally to ensure apis (which are in correct order) can reference each other
142
166
  const apis: Record<string, ContextApi> = {}
143
- sortApiObjects(Object.entries(component.apis ?? {})).forEach(
144
- ([name, api]) => {
145
- if (isLegacyApi(api)) {
146
- apis[name] = createLegacyAPI(api, {
167
+ sortApiObjects(
168
+ Object.entries(component.apis ?? {}).filter(
169
+ (entry): entry is [string, ComponentAPI] => isDefined(entry[1]),
170
+ ),
171
+ ).forEach(([name, api]) => {
172
+ if (isLegacyApi(api)) {
173
+ apis[name] = createLegacyAPI(api, {
174
+ ...ctx,
175
+ apis,
176
+ component,
177
+ dataSignal: componentDataSignal,
178
+ abortSignal: abortController.signal,
179
+ isRootComponent: false,
180
+ formulaCache,
181
+ package: node.package ?? ctx.package,
182
+ triggerEvent: (eventTrigger, data) => {
183
+ const eventHandler = Object.values(node.events).find(
184
+ (e) => e.trigger === eventTrigger,
185
+ )
186
+ if (eventHandler) {
187
+ eventHandler.actions?.forEach((action) =>
188
+ handleAction(action, { ...dataSignal.get(), Event: data }, ctx),
189
+ )
190
+ }
191
+ },
192
+ })
193
+ } else {
194
+ apis[name] = createAPI({
195
+ apiRequest: api,
196
+ ctx: {
147
197
  ...ctx,
148
198
  apis,
149
199
  component,
@@ -162,39 +212,11 @@ export function createComponent({
162
212
  )
163
213
  }
164
214
  },
165
- })
166
- } else {
167
- apis[name] = createAPI({
168
- apiRequest: api,
169
- ctx: {
170
- ...ctx,
171
- apis,
172
- component,
173
- dataSignal: componentDataSignal,
174
- abortSignal: abortController.signal,
175
- isRootComponent: false,
176
- formulaCache,
177
- package: node.package ?? ctx.package,
178
- triggerEvent: (eventTrigger, data) => {
179
- const eventHandler = Object.values(node.events).find(
180
- (e) => e.trigger === eventTrigger,
181
- )
182
- if (eventHandler) {
183
- eventHandler.actions?.forEach((action) =>
184
- handleAction(
185
- action,
186
- { ...dataSignal.get(), Event: data },
187
- ctx,
188
- ),
189
- )
190
- }
191
- },
192
- },
193
- componentData: componentDataSignal.get(),
194
- })
195
- }
196
- },
197
- )
215
+ },
216
+ componentData: componentDataSignal.get(),
217
+ })
218
+ }
219
+ })
198
220
  Object.values(apis)
199
221
  .filter(isContextApiV2)
200
222
  .forEach((api) => {
@@ -221,15 +243,21 @@ export function createComponent({
221
243
  .map(([name, formula]) => [
222
244
  name,
223
245
  componentDataSignal.map((data) =>
224
- applyFormula(formula.formula, {
225
- data,
226
- component,
227
- formulaCache: ctx.formulaCache,
228
- root: ctx.root,
229
- package: ctx.package,
230
- toddle: ctx.toddle,
231
- env: ctx.env,
232
- }),
246
+ applyFormula(
247
+ formula.formula,
248
+ {
249
+ data,
250
+ component,
251
+ formulaCache: ctx.formulaCache,
252
+ root: ctx.root,
253
+ package: ctx.package,
254
+ toddle: ctx.toddle,
255
+ env: ctx.env,
256
+ jsonPath: ctx.jsonPath,
257
+ reportFormulaEvaluation: ctx.reportFormulaEvaluation,
258
+ },
259
+ ['formulas', name],
260
+ ),
233
261
  ),
234
262
  ]),
235
263
  )
@@ -301,6 +329,8 @@ export function createComponent({
301
329
  node.id === 'root'
302
330
  ? { ...instance, [ctx.component.name]: 'root' }
303
331
  : { [ctx.component.name]: node.id ?? '' },
332
+ jsonPath: ctx.jsonPath,
333
+ reportFormulaEvaluation: ctx.reportFormulaEvaluation,
304
334
  })
305
335
 
306
336
  // Custom properties instance overrides are added after the child tree is rendered to ensure correct order
@@ -314,10 +344,14 @@ export function createComponent({
314
344
  }),
315
345
  signal: dataSignal.map((data) =>
316
346
  appendUnit(
317
- applyFormula(customProperty.formula, {
318
- ...formulaCtx,
319
- data,
320
- }),
347
+ applyFormula(
348
+ customProperty.formula,
349
+ {
350
+ ...formulaCtx,
351
+ data,
352
+ },
353
+ ['customProperties', customPropertyName, 'formula'],
354
+ ),
321
355
  customProperty.unit,
322
356
  ),
323
357
  ),
@@ -337,10 +371,14 @@ export function createComponent({
337
371
  }),
338
372
  signal: dataSignal.map((data) =>
339
373
  appendUnit(
340
- applyFormula(customProperty.formula, {
341
- ...formulaCtx,
342
- data,
343
- }),
374
+ applyFormula(
375
+ customProperty.formula,
376
+ {
377
+ ...formulaCtx,
378
+ data,
379
+ },
380
+ ['customProperties', customPropertyName, 'formula'],
381
+ ),
344
382
  customProperty.unit,
345
383
  ),
346
384
  ),