@nordcraft/core 1.0.95 → 1.0.96

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) 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/component.types.d.ts +3 -3
  6. package/dist/component/component.types.js.map +1 -1
  7. package/dist/formula/andFormula.d.ts +3 -0
  8. package/dist/formula/andFormula.js +25 -0
  9. package/dist/formula/andFormula.js.map +1 -0
  10. package/dist/formula/applyFormula.d.ts +2 -0
  11. package/dist/formula/applyFormula.js +49 -0
  12. package/dist/formula/applyFormula.js.map +1 -0
  13. package/dist/formula/arrayFormula.d.ts +2 -0
  14. package/dist/formula/arrayFormula.js +5 -0
  15. package/dist/formula/arrayFormula.js.map +1 -0
  16. package/dist/formula/formula.d.ts +8 -6
  17. package/dist/formula/formula.js +45 -172
  18. package/dist/formula/formula.js.map +1 -1
  19. package/dist/formula/formulaTypes.d.ts +1 -0
  20. package/dist/formula/formulaUtils.d.ts +1 -1
  21. package/dist/formula/formulaUtils.js.map +1 -1
  22. package/dist/formula/functionFormula.d.ts +2 -0
  23. package/dist/formula/functionFormula.js +88 -0
  24. package/dist/formula/functionFormula.js.map +1 -0
  25. package/dist/formula/objectFormula.d.ts +2 -0
  26. package/dist/formula/objectFormula.js +8 -0
  27. package/dist/formula/objectFormula.js.map +1 -0
  28. package/dist/formula/orFormula.d.ts +3 -0
  29. package/dist/formula/orFormula.js +27 -0
  30. package/dist/formula/orFormula.js.map +1 -0
  31. package/dist/formula/pathFormula.d.ts +2 -0
  32. package/dist/formula/pathFormula.js +13 -0
  33. package/dist/formula/pathFormula.js.map +1 -0
  34. package/dist/formula/recordFormula.d.ts +2 -0
  35. package/dist/formula/recordFormula.js +8 -0
  36. package/dist/formula/recordFormula.js.map +1 -0
  37. package/dist/formula/switchFormula.d.ts +3 -0
  38. package/dist/formula/switchFormula.js +40 -0
  39. package/dist/formula/switchFormula.js.map +1 -0
  40. package/dist/types.d.ts +1 -1
  41. package/dist/utils/measure.js +5 -0
  42. package/dist/utils/measure.js.map +1 -1
  43. package/package.json +1 -1
  44. package/src/api/api.test.ts +22 -22
  45. package/src/api/api.ts +36 -10
  46. package/src/component/ToddleComponent.ts +9 -7
  47. package/src/component/component.types.ts +5 -3
  48. package/src/formula/andFormula.test.ts +112 -0
  49. package/src/formula/andFormula.ts +33 -0
  50. package/src/formula/applyFormula.test.ts +151 -0
  51. package/src/formula/applyFormula.ts +72 -0
  52. package/src/formula/arrayFormula.test.ts +52 -0
  53. package/src/formula/arrayFormula.ts +14 -0
  54. package/src/formula/formula.ts +67 -206
  55. package/src/formula/formulaTypes.ts +5 -0
  56. package/src/formula/formulaUtils.ts +1 -1
  57. package/src/formula/functionFormula.test.ts +89 -0
  58. package/src/formula/functionFormula.ts +118 -0
  59. package/src/formula/objectFormula.test.ts +56 -0
  60. package/src/formula/objectFormula.ts +17 -0
  61. package/src/formula/orFormula.test.ts +113 -0
  62. package/src/formula/orFormula.ts +33 -0
  63. package/src/formula/pathFormula.test.ts +35 -0
  64. package/src/formula/pathFormula.ts +17 -0
  65. package/src/formula/recordFormula.test.ts +56 -0
  66. package/src/formula/recordFormula.ts +17 -0
  67. package/src/formula/switchFormula.test.ts +122 -0
  68. package/src/formula/switchFormula.ts +54 -0
  69. package/src/formula/testUtils.test.ts +68 -0
  70. package/src/types.ts +1 -1
  71. package/src/utils/measure.ts +6 -0
@@ -1 +1 @@
1
- {"version":3,"file":"measure.js","sourceRoot":"","sources":["../../src/utils/measure.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,GAAQ,OAAO,UAAU,KAAK,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAA;AAChF,WAAW,CAAC,sBAAsB,GAAG,EAAE,CAAA;AACvC,WAAW,CAAC,oBAAoB;IAC9B,OAAO,cAAc,KAAK,WAAW;QACrC,cAAc,CAAC,OAAO,CAAC,cAAc,CAAC,KAAK,MAAM,CAAA;AAEnD,WAAW,CAAC,kBAAkB,GAAG,CAAC,OAAO,GAAG,IAAI,EAAE,QAAQ,GAAG,EAAE,EAAE,EAAE;IACjE,IAAI,OAAO,EAAE,CAAC;QACZ,cAAc,CAAC,OAAO,CAAC,cAAc,EAAE,MAAM,CAAC,CAAA;IAChD,CAAC;SAAM,CAAC;QACN,cAAc,CAAC,UAAU,CAAC,cAAc,CAAC,CAAA;IAC3C,CAAC;IACD,WAAW,CAAC,oBAAoB,GAAG,OAAO,CAAA;IAC1C,WAAW,CAAC,sBAAsB,GAAG,QAAQ,CAAA;AAC/C,CAAC,CAAA;AAED,IAAI,YAAY,GAAG,CAAC,CAAA;AACpB,MAAM,KAAK,GAAa,EAAE,CAAA;AAC1B,MAAM,IAAI,GAAG,GAAG,EAAE,GAAE,CAAC,CAAA;AAErB,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,GAAW,EAAE,OAAgC,EAAE,EAAE;IACvE,IAAI,CAAC,WAAW,CAAC,oBAAoB,EAAE,CAAC;QACtC,OAAO,IAAI,CAAA;IACb,CAAC;IAED,MAAM,SAAS,GAAG,YAAY,EAAE,CAAA;IAChC,IAAI,KAAK,CAAC,MAAM,IAAI,WAAW,CAAC,sBAAsB,EAAE,CAAC;QACvD,OAAO,IAAI,CAAA;IACb,CAAC;IAED,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAA;IAC/B,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAEf,OAAO,CAAC,YAAsC,EAAE,EAAE;QAChD,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,EAAE,CAAA;QAC7B,MAAM,aAAa,GAAG,YAAY;YAChC,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,GAAG,YAAY,EAAE;YACjC,CAAC,CAAC,OAAO,CAAA;QAEX,WAAW,CAAC,OAAO,CAAC,GAAG,EAAE;YACvB,KAAK;YACL,GAAG;YACH,MAAM,EAAE;gBACN,QAAQ,EAAE;oBACR,QAAQ,EAAE,aAAa;oBACvB,KAAK,EAAE,oBAAoB;oBAC3B,UAAU,EAAE;wBACV,GAAG,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;wBAChE,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;wBAC5B,CAAC,eAAe,EAAE,SAAS,CAAC;wBAC5B,CAAC,cAAc,EAAE,YAAY,GAAG,SAAS,CAAC;qBAC3C;oBACD,WAAW,EAAE,GAAG,SAAS,KAAK,GAAG,EAAE;iBACpC;aACF;SACF,CAAC,CAAA;QACF,KAAK,CAAC,GAAG,EAAE,CAAA;IACb,CAAC,CAAA;AACH,CAAC,CAAA"}
1
+ {"version":3,"file":"measure.js","sourceRoot":"","sources":["../../src/utils/measure.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,GAAQ,OAAO,UAAU,KAAK,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAA;AAChF,WAAW,CAAC,sBAAsB,GAAG,EAAE,CAAA;AACvC,WAAW,CAAC,oBAAoB;IAC9B,OAAO,cAAc,KAAK,WAAW;QACrC,cAAc,CAAC,OAAO,CAAC,cAAc,CAAC,KAAK,MAAM,CAAA;AAEnD,WAAW,CAAC,kBAAkB,GAAG,CAAC,OAAO,GAAG,IAAI,EAAE,QAAQ,GAAG,EAAE,EAAE,EAAE;IACjE,IAAI,OAAO,EAAE,CAAC;QACZ,cAAc,CAAC,OAAO,CAAC,cAAc,EAAE,MAAM,CAAC,CAAA;IAChD,CAAC;SAAM,CAAC;QACN,cAAc,CAAC,UAAU,CAAC,cAAc,CAAC,CAAA;IAC3C,CAAC;IACD,WAAW,CAAC,oBAAoB,GAAG,OAAO,CAAA;IAC1C,WAAW,CAAC,sBAAsB,GAAG,QAAQ,CAAA;AAC/C,CAAC,CAAA;AAED,IAAI,YAAY,GAAG,CAAC,CAAA;AACpB,MAAM,KAAK,GAAa,EAAE,CAAA;AAC1B,MAAM,IAAI,GAAG,GAAG,EAAE,GAAE,CAAC,CAAA;AAErB,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,GAAW,EAAE,OAAgC,EAAE,EAAE;IACvE,IAAI,CAAC,WAAW,CAAC,oBAAoB,EAAE,CAAC;QACtC,OAAO,IAAI,CAAA;IACb,CAAC;IAED,MAAM,SAAS,GAAG,YAAY,EAAE,CAAA;IAChC,IAAI,KAAK,CAAC,MAAM,IAAI,WAAW,CAAC,sBAAsB,EAAE,CAAC;QACvD,OAAO,IAAI,CAAA;IACb,CAAC;IAED,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAA;IAC/B,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAEf,IAAI,QAAQ,GAAG,KAAK,CAAA;IACpB,OAAO,CAAC,YAAsC,EAAE,EAAE;QAChD,IAAI,QAAQ,EAAE,CAAC;YACb,OAAM;QACR,CAAC;QAED,QAAQ,GAAG,IAAI,CAAA;QACf,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,EAAE,CAAA;QAC7B,MAAM,aAAa,GAAG,YAAY;YAChC,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,GAAG,YAAY,EAAE;YACjC,CAAC,CAAC,OAAO,CAAA;QAEX,WAAW,CAAC,OAAO,CAAC,GAAG,EAAE;YACvB,KAAK;YACL,GAAG;YACH,MAAM,EAAE;gBACN,QAAQ,EAAE;oBACR,QAAQ,EAAE,aAAa;oBACvB,KAAK,EAAE,oBAAoB;oBAC3B,UAAU,EAAE;wBACV,GAAG,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;wBAChE,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;wBAC5B,CAAC,eAAe,EAAE,SAAS,CAAC;wBAC5B,CAAC,cAAc,EAAE,YAAY,GAAG,SAAS,CAAC;qBAC3C;oBACD,WAAW,EAAE,GAAG,SAAS,KAAK,GAAG,EAAE;iBACpC;aACF;SACF,CAAC,CAAA;QACF,KAAK,CAAC,GAAG,EAAE,CAAA;IACb,CAAC,CAAA;AACH,CAAC,CAAA"}
package/package.json CHANGED
@@ -19,5 +19,5 @@
19
19
  "zod": "4.2.1"
20
20
  },
21
21
  "main": "dist/index.js",
22
- "version": "1.0.95"
22
+ "version": "1.0.96"
23
23
  }
@@ -13,21 +13,21 @@ import { ApiMethod } from './apiTypes'
13
13
 
14
14
  describe('getApiPath()', () => {
15
15
  test('it returns a valid url path string', () => {
16
- expect(getRequestPath({}, undefined as any)).toBe('')
16
+ expect(getRequestPath({}, {} as any)).toBe('')
17
17
  expect(
18
18
  getRequestPath(
19
19
  {
20
20
  first: { formula: valueFormula('hello'), index: 0 },
21
21
  second: { formula: valueFormula('world'), index: 1 },
22
22
  },
23
- undefined as any,
23
+ {} as any,
24
24
  ),
25
25
  ).toBe('hello/world')
26
26
  })
27
27
  })
28
28
  describe('getQueryParams()', () => {
29
29
  test('it returns a valid url path string', () => {
30
- const emptyParams = getRequestQueryParams({}, undefined as any)
30
+ const emptyParams = getRequestQueryParams({}, {} as any)
31
31
  expect(emptyParams.size).toBe(0)
32
32
  const params = getRequestQueryParams(
33
33
  {
@@ -44,7 +44,7 @@ describe('getQueryParams()', () => {
44
44
  enabled: valueFormula(false),
45
45
  },
46
46
  },
47
- undefined as any,
47
+ {} as any,
48
48
  )
49
49
  expect(params.get('q')).toBe('hello')
50
50
  expect(params.get('filter')).toBe('world')
@@ -59,7 +59,7 @@ describe('getQueryParams()', () => {
59
59
  enabled: valueFormula(true),
60
60
  },
61
61
  },
62
- undefined as any,
62
+ {} as any,
63
63
  )
64
64
  expect(params.getAll('q')).toEqual(['hello', 'world'])
65
65
  expect(params.size).toBe(2)
@@ -72,7 +72,7 @@ describe('getQueryParams()', () => {
72
72
  enabled: valueFormula(true),
73
73
  },
74
74
  },
75
- undefined as any,
75
+ {} as any,
76
76
  )
77
77
  expect(params.get('q[a]')).toEqual('hello')
78
78
  expect(params.get('q[b][c]')).toEqual('world')
@@ -95,7 +95,7 @@ describe('getUrl()', () => {
95
95
  },
96
96
  },
97
97
  },
98
- undefined as any,
98
+ {} as any,
99
99
  'https://example.com',
100
100
  )
101
101
  expect(url.href).toBe('https://example.com/hello/world?q=test')
@@ -114,7 +114,7 @@ describe('getUrl()', () => {
114
114
  },
115
115
  },
116
116
  },
117
- undefined as any,
117
+ {} as any,
118
118
  'https://example.com',
119
119
  )
120
120
  expect(url.href).toBe('https://example.com/test/path/hello/world?q=test')
@@ -128,7 +128,7 @@ describe('getUrl()', () => {
128
128
  b: { formula: valueFormula('world'), index: 1 },
129
129
  },
130
130
  },
131
- undefined as any,
131
+ {} as any,
132
132
  'https://example.com',
133
133
  )
134
134
  expect(url.href).toBe('https://example.com/test/path/hello/world')
@@ -142,7 +142,7 @@ describe('getUrl()', () => {
142
142
  b: { formula: valueFormula('world'), index: 1 },
143
143
  },
144
144
  },
145
- undefined as any,
145
+ {} as any,
146
146
  'https://example.com',
147
147
  )
148
148
  expect(url.href).toBe('https://example.com/88/hello/world')
@@ -156,7 +156,7 @@ describe('getUrl()', () => {
156
156
  b: { formula: valueFormula('world'), index: 1 },
157
157
  },
158
158
  },
159
- undefined as any,
159
+ {} as any,
160
160
  'https://mysite.com',
161
161
  )
162
162
  expect(url.href).toBe('https://mysite.com/test/path/hello/world')
@@ -175,7 +175,7 @@ describe('getUrl()', () => {
175
175
  },
176
176
  },
177
177
  },
178
- undefined as any,
178
+ {} as any,
179
179
  'https://mysite.com',
180
180
  )
181
181
  expect(url.href).toBe(
@@ -197,7 +197,7 @@ describe('getUrl()', () => {
197
197
  },
198
198
  hash: { formula: valueFormula('my-hash') },
199
199
  },
200
- undefined as any,
200
+ {} as any,
201
201
  'https://mysite.com',
202
202
  )
203
203
  expect(url.hash).toBe('#my-hash')
@@ -210,7 +210,7 @@ describe('getApiHeaders()', () => {
210
210
  test('it returns valid headers', () => {
211
211
  const emptyParams = getRequestHeaders({
212
212
  apiHeaders: {},
213
- formulaContext: undefined as any,
213
+ formulaContext: {} as any,
214
214
  defaultHeaders: undefined,
215
215
  })
216
216
  expect(emptyParams.entries.length).toBe(0)
@@ -219,7 +219,7 @@ describe('getApiHeaders()', () => {
219
219
  q: { formula: valueFormula('hello') },
220
220
  filter: { formula: valueFormula('world') },
221
221
  },
222
- formulaContext: undefined as any,
222
+ formulaContext: {} as any,
223
223
  defaultHeaders: undefined,
224
224
  })
225
225
  expect(headers.get('q')).toBe('hello')
@@ -230,7 +230,7 @@ describe('getApiHeaders()', () => {
230
230
  q: { formula: valueFormula('hello') },
231
231
  filter: { formula: valueFormula('world') },
232
232
  },
233
- formulaContext: undefined as any,
233
+ formulaContext: {} as any,
234
234
  defaultHeaders: new Headers([['accept-encoding', 'gzip']]),
235
235
  })
236
236
  expect(headersWithDefaults.get('q')).toBe('hello')
@@ -248,7 +248,7 @@ describe('getApiHeaders()', () => {
248
248
  },
249
249
  filter: { formula: valueFormula('world') },
250
250
  },
251
- formulaContext: undefined as any,
251
+ formulaContext: {} as any,
252
252
  defaultHeaders: undefined,
253
253
  })
254
254
  expect(headers.get('q')).toBe('hello')
@@ -282,7 +282,7 @@ describe('createApiRequest', () => {
282
282
 
283
283
  const { url, requestSettings } = createApiRequest({
284
284
  api: apiRequest,
285
- formulaContext: undefined as any,
285
+ formulaContext: {} as any,
286
286
  baseUrl,
287
287
  defaultHeaders: undefined,
288
288
  })
@@ -327,7 +327,7 @@ describe('createApiRequest', () => {
327
327
 
328
328
  const { url, requestSettings } = createApiRequest({
329
329
  api: apiRequest,
330
- formulaContext: undefined as any,
330
+ formulaContext: {} as any,
331
331
  baseUrl,
332
332
  defaultHeaders: undefined,
333
333
  })
@@ -353,7 +353,7 @@ describe('getRequestBody', () => {
353
353
  }
354
354
  const body = getRequestBody({
355
355
  api: apiRequest,
356
- formulaContext: undefined as any,
356
+ formulaContext: {} as any,
357
357
  headers: new Headers(),
358
358
  method: ApiMethod.POST,
359
359
  })
@@ -372,7 +372,7 @@ describe('getRequestBody', () => {
372
372
  }
373
373
  const body = getRequestBody({
374
374
  api: apiRequest,
375
- formulaContext: undefined as any,
375
+ formulaContext: {} as any,
376
376
  headers: new Headers([
377
377
  ['Content-Type', 'application/x-www-form-urlencoded'],
378
378
  ]),
@@ -393,7 +393,7 @@ describe('getRequestBody', () => {
393
393
  }
394
394
  const body = getRequestBody({
395
395
  api: apiRequest,
396
- formulaContext: undefined as any,
396
+ formulaContext: {} as any,
397
397
  headers: new Headers([['Content-Type', 'multipart/form-data']]),
398
398
  method: ApiMethod.POST,
399
399
  })
package/src/api/api.ts CHANGED
@@ -35,7 +35,11 @@ export const createApiRequest = <Handler>({
35
35
  baseUrl?: Nullable<string>
36
36
  defaultHeaders: Headers | undefined
37
37
  }) => {
38
- const url = getUrl(api, formulaContext, baseUrl)
38
+ const url = getUrl(
39
+ api,
40
+ { ...formulaContext, jsonPath: ['apis', api.name] },
41
+ baseUrl,
42
+ )
39
43
  const requestSettings = getRequestSettings({
40
44
  api,
41
45
  formulaContext,
@@ -53,7 +57,7 @@ export const getUrl = (
53
57
  let urlPathname = ''
54
58
  let urlQueryParams = new URLSearchParams()
55
59
  let parsedUrl: URL | undefined
56
- const url = applyFormula(api.url, formulaContext)
60
+ const url = applyFormula(api.url, formulaContext, ['url'])
57
61
  if (['string', 'number'].includes(typeof url)) {
58
62
  const urlInput = typeof url === 'number' ? String(url) : url
59
63
  try {
@@ -74,7 +78,10 @@ export const getUrl = (
74
78
  ])
75
79
  const queryString =
76
80
  [...queryParams.entries()].length > 0 ? `?${queryParams.toString()}` : ''
77
- const hash = applyFormula(api.hash?.formula, formulaContext)
81
+ const hash = applyFormula(api.hash?.formula, formulaContext, [
82
+ 'hash',
83
+ 'formula',
84
+ ])
78
85
  const hashString =
79
86
  typeof hash === 'string' && hash.length > 0 ? `#${hash}` : ''
80
87
  if (parsedUrl) {
@@ -102,7 +109,10 @@ export const applyAbortSignal = (
102
109
  formulaContext: FormulaContext,
103
110
  ) => {
104
111
  if (api.timeout) {
105
- const timeout = applyFormula(api.timeout.formula, formulaContext)
112
+ const timeout = applyFormula(api.timeout.formula, formulaContext, [
113
+ 'timeout',
114
+ 'formula',
115
+ ])
106
116
  if (typeof timeout === 'number' && !Number.isNaN(timeout) && timeout > 0) {
107
117
  requestSettings.signal = AbortSignal.timeout(timeout)
108
118
  }
@@ -147,7 +157,9 @@ export const getRequestPath = (
147
157
  formulaContext: FormulaContext,
148
158
  ): string =>
149
159
  sortObjectEntries(path ?? {}, ([_, p]) => p.index)
150
- .map(([_, p]) => applyFormula(p.formula, formulaContext))
160
+ .map(([parameterName, p]) =>
161
+ applyFormula(p.formula, formulaContext, ['path', parameterName]),
162
+ )
151
163
  .join('/')
152
164
 
153
165
  export const getRequestQueryParams = (
@@ -157,13 +169,21 @@ export const getRequestQueryParams = (
157
169
  const queryParams = new URLSearchParams()
158
170
  Object.entries(params ?? {}).forEach(([key, param]) => {
159
171
  const enabled = isDefined(param.enabled)
160
- ? applyFormula(param.enabled, formulaContext)
172
+ ? applyFormula(param.enabled, formulaContext, [
173
+ 'queryParams',
174
+ key,
175
+ 'enabled',
176
+ ])
161
177
  : true
162
178
  if (!enabled) {
163
179
  return
164
180
  }
165
181
 
166
- const value = applyFormula(param.formula, formulaContext)
182
+ const value = applyFormula(param.formula, formulaContext, [
183
+ 'queryParams',
184
+ key,
185
+ 'formula',
186
+ ])
167
187
  if (!isDefined(value)) {
168
188
  // Ignore null/undefined values
169
189
  return
@@ -202,10 +222,14 @@ export const getRequestHeaders = ({
202
222
  const headers = new Headers(defaultHeaders)
203
223
  Object.entries(apiHeaders ?? {}).forEach(([key, param]) => {
204
224
  const enabled = isDefined(param.enabled)
205
- ? applyFormula(param.enabled, formulaContext)
225
+ ? applyFormula(param.enabled, formulaContext, ['headers', key, 'enabled'])
206
226
  : true
207
227
  if (enabled) {
208
- const value = applyFormula(param.formula, formulaContext)
228
+ const value = applyFormula(param.formula, formulaContext, [
229
+ 'headers',
230
+ key,
231
+ 'formula',
232
+ ])
209
233
  if (isDefined(value)) {
210
234
  try {
211
235
  headers.set(
@@ -286,6 +310,8 @@ export const isApiError = ({
286
310
  },
287
311
  },
288
312
  env: formulaContext.env,
313
+ jsonPath: ['apis', apiName, 'isError', 'formula'],
314
+ reportFormulaEvaluation: formulaContext.reportFormulaEvaluation,
289
315
  })
290
316
  : null
291
317
 
@@ -310,7 +336,7 @@ export const getRequestBody = ({
310
336
  return
311
337
  }
312
338
 
313
- const body = applyFormula(api.body, formulaContext)
339
+ const body = applyFormula(api.body, formulaContext, ['body'])
314
340
  if (!body) {
315
341
  return
316
342
  }
@@ -1,4 +1,5 @@
1
1
  import { isLegacyApi } from '../api/api'
2
+ import type { ComponentAPI } from '../api/apiTypes'
2
3
  import { LegacyToddleApi } from '../api/LegacyToddleApi'
3
4
  import { ToddleApiV2 } from '../api/ToddleApiV2'
4
5
  import type { Formula, FunctionOperation } from '../formula/formula'
@@ -561,13 +562,14 @@ export class ToddleComponent<Handler> {
561
562
 
562
563
  get apis() {
563
564
  return Object.fromEntries(
564
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
565
- Object.entries(this.component.apis ?? {}).map(([key, api]) => [
566
- key,
567
- isLegacyApi(api)
568
- ? new LegacyToddleApi(api, key, this.globalFormulas)
569
- : new ToddleApiV2(api, key, this.globalFormulas),
570
- ]),
565
+ Object.entries(this.component.apis ?? {})
566
+ .filter((entry): entry is [string, ComponentAPI] => isDefined(entry[1]))
567
+ .map(([key, api]) => [
568
+ key,
569
+ isLegacyApi(api)
570
+ ? new LegacyToddleApi(api, key, this.globalFormulas)
571
+ : new ToddleApiV2(api, key, this.globalFormulas),
572
+ ]),
571
573
  )
572
574
  }
573
575
 
@@ -32,8 +32,10 @@ export interface ComponentData {
32
32
  Apis?: Nullable<
33
33
  Record<
34
34
  string,
35
- | LegacyApiStatus
36
- | (ApiStatus & { inputs?: Nullable<Record<string, unknown>> })
35
+ Nullable<
36
+ | LegacyApiStatus
37
+ | (ApiStatus & { inputs?: Nullable<Record<string, unknown>> })
38
+ >
37
39
  >
38
40
  >
39
41
  Args?: Nullable<unknown>
@@ -188,7 +190,7 @@ export interface Component {
188
190
  >
189
191
  >
190
192
  workflows?: Nullable<Record<string, ComponentWorkflow>>
191
- apis?: Nullable<Record<string, ComponentAPI>>
193
+ apis?: Nullable<Record<string, Nullable<ComponentAPI>>>
192
194
  nodes?: Nullable<Record<string, NodeModel>>
193
195
  events?: Nullable<ComponentEvent[]>
194
196
  onLoad?: Nullable<EventModel>
@@ -0,0 +1,112 @@
1
+ import { describe, expect, it } from 'bun:test'
2
+ import { applyAndFormula, applyEvaluateAllAndFormula } from './andFormula'
3
+ import { applyFormula, type AndOperation } from './formula'
4
+ import { valueFormula } from './formulaUtils'
5
+ import {
6
+ createTestFormulaContext,
7
+ createTestFormulaContextForAllPaths,
8
+ } from './testUtils.test'
9
+
10
+ describe('applyAndFormula', () => {
11
+ it('returns false if any argument is falsy', () => {
12
+ const formula: AndOperation = {
13
+ type: 'and',
14
+ arguments: [
15
+ { formula: valueFormula(true) },
16
+ { formula: valueFormula(false) },
17
+ { formula: valueFormula(true) },
18
+ ],
19
+ }
20
+ const ctx = createTestFormulaContext()
21
+ expect(applyAndFormula(formula, ctx)).toBe(false)
22
+ })
23
+
24
+ it('returns true if all arguments are truthy', () => {
25
+ const formula: AndOperation = {
26
+ type: 'and',
27
+ arguments: [
28
+ { formula: valueFormula(1) },
29
+ { formula: valueFormula('ok') },
30
+ { formula: valueFormula(true) },
31
+ ],
32
+ }
33
+ const ctx = createTestFormulaContext()
34
+ expect(applyAndFormula(formula, ctx)).toBe(true)
35
+ })
36
+
37
+ it('returns false for first falsy argument', () => {
38
+ const formula: AndOperation = {
39
+ type: 'and',
40
+ arguments: [
41
+ { formula: valueFormula(false) },
42
+ { formula: valueFormula(true) },
43
+ { formula: valueFormula(true) },
44
+ ],
45
+ }
46
+ const ctx = createTestFormulaContext()
47
+ expect(applyAndFormula(formula, ctx)).toBe(false)
48
+ })
49
+ })
50
+
51
+ describe('applyEvaluateAllAndFormula', () => {
52
+ it('visits all formulas in "report" mode even if the first argument is falsy', () => {
53
+ const formula: AndOperation = {
54
+ type: 'and',
55
+ arguments: [
56
+ { formula: valueFormula(false) },
57
+ { formula: valueFormula('hello') },
58
+ { formula: valueFormula(true) },
59
+ ],
60
+ }
61
+ const results: Record<string, any> = {}
62
+ const ctx = createTestFormulaContextForAllPaths(
63
+ {},
64
+ (path, result) => (results[path.join('/')] = result),
65
+ )
66
+ expect(applyFormula(formula, ctx, [])).toBe(false)
67
+ expect(results).toMatchObject({
68
+ 'arguments/0/formula': false,
69
+ 'arguments/1/formula': 'hello',
70
+ 'arguments/2/formula': true,
71
+ })
72
+ })
73
+
74
+ it('returns true if all arguments are truthy', () => {
75
+ const formula: AndOperation = {
76
+ type: 'and',
77
+ arguments: [
78
+ { formula: valueFormula(1) },
79
+ { formula: valueFormula('ok') },
80
+ { formula: valueFormula(true) },
81
+ ],
82
+ }
83
+ const ctx = createTestFormulaContext()
84
+ expect(applyEvaluateAllAndFormula(formula, ctx)).toBe(true)
85
+ })
86
+
87
+ it('returns false if any argument is falsy', () => {
88
+ const formula: AndOperation = {
89
+ type: 'and',
90
+ arguments: [
91
+ { formula: valueFormula(true) },
92
+ { formula: valueFormula(false) },
93
+ { formula: valueFormula(true) },
94
+ ],
95
+ }
96
+ const ctx = createTestFormulaContext()
97
+ expect(applyEvaluateAllAndFormula(formula, ctx)).toBe(false)
98
+ })
99
+
100
+ it('returns false if last argument is falsy', () => {
101
+ const formula: AndOperation = {
102
+ type: 'and',
103
+ arguments: [
104
+ { formula: valueFormula(true) },
105
+ { formula: valueFormula(true) },
106
+ { formula: valueFormula(false) },
107
+ ],
108
+ }
109
+ const ctx = createTestFormulaContext()
110
+ expect(applyEvaluateAllAndFormula(formula, ctx)).toBe(false)
111
+ })
112
+ })
@@ -0,0 +1,33 @@
1
+ import { toBoolean } from '../utils/util'
2
+ import { applyFormula, type AndOperation, type FormulaContext } from './formula'
3
+
4
+ export const applyAndFormula = (formula: AndOperation, 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 false
11
+ }
12
+ }
13
+ return true
14
+ }
15
+
16
+ export const applyEvaluateAllAndFormula = (
17
+ formula: AndOperation,
18
+ ctx: FormulaContext,
19
+ ) => {
20
+ let andResult = true
21
+ if (!formula.arguments || formula.arguments.length === 0) {
22
+ return andResult
23
+ }
24
+ for (let i = 0; i < (formula.arguments ?? []).length; i++) {
25
+ const arg = (formula.arguments ?? [])[i]
26
+ if (
27
+ !toBoolean(applyFormula(arg?.formula, ctx, ['arguments', i, 'formula']))
28
+ ) {
29
+ andResult = false
30
+ }
31
+ }
32
+ return andResult
33
+ }
@@ -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
+ })