@nordcraft/core 1.0.0

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 (119) hide show
  1. package/README.md +5 -0
  2. package/dist/api/LegacyToddleApi.d.ts +34 -0
  3. package/dist/api/LegacyToddleApi.js +178 -0
  4. package/dist/api/LegacyToddleApi.js.map +1 -0
  5. package/dist/api/ToddleApiV2.d.ts +77 -0
  6. package/dist/api/ToddleApiV2.js +271 -0
  7. package/dist/api/ToddleApiV2.js.map +1 -0
  8. package/dist/api/api.d.ts +49 -0
  9. package/dist/api/api.js +276 -0
  10. package/dist/api/api.js.map +1 -0
  11. package/dist/api/apiTypes.d.ts +125 -0
  12. package/dist/api/apiTypes.js +11 -0
  13. package/dist/api/apiTypes.js.map +1 -0
  14. package/dist/api/headers.d.ts +10 -0
  15. package/dist/api/headers.js +36 -0
  16. package/dist/api/headers.js.map +1 -0
  17. package/dist/api/template.d.ts +5 -0
  18. package/dist/api/template.js +7 -0
  19. package/dist/api/template.js.map +1 -0
  20. package/dist/component/ToddleComponent.d.ts +45 -0
  21. package/dist/component/ToddleComponent.js +373 -0
  22. package/dist/component/ToddleComponent.js.map +1 -0
  23. package/dist/component/actionUtils.d.ts +2 -0
  24. package/dist/component/actionUtils.js +56 -0
  25. package/dist/component/actionUtils.js.map +1 -0
  26. package/dist/component/component.types.d.ts +313 -0
  27. package/dist/component/component.types.js +9 -0
  28. package/dist/component/component.types.js.map +1 -0
  29. package/dist/component/isPageComponent.d.ts +2 -0
  30. package/dist/component/isPageComponent.js +3 -0
  31. package/dist/component/isPageComponent.js.map +1 -0
  32. package/dist/formula/ToddleFormula.d.ts +15 -0
  33. package/dist/formula/ToddleFormula.js +20 -0
  34. package/dist/formula/ToddleFormula.js.map +1 -0
  35. package/dist/formula/formula.d.ts +103 -0
  36. package/dist/formula/formula.js +186 -0
  37. package/dist/formula/formula.js.map +1 -0
  38. package/dist/formula/formulaTypes.d.ts +30 -0
  39. package/dist/formula/formulaTypes.js +2 -0
  40. package/dist/formula/formulaTypes.js.map +1 -0
  41. package/dist/formula/formulaUtils.d.ts +18 -0
  42. package/dist/formula/formulaUtils.js +240 -0
  43. package/dist/formula/formulaUtils.js.map +1 -0
  44. package/dist/styling/className.d.ts +2 -0
  45. package/dist/styling/className.js +52 -0
  46. package/dist/styling/className.js.map +1 -0
  47. package/dist/styling/style.css.d.ts +5 -0
  48. package/dist/styling/style.css.js +242 -0
  49. package/dist/styling/style.css.js.map +1 -0
  50. package/dist/styling/theme.const.d.ts +3 -0
  51. package/dist/styling/theme.const.js +381 -0
  52. package/dist/styling/theme.const.js.map +1 -0
  53. package/dist/styling/theme.d.ts +72 -0
  54. package/dist/styling/theme.js +200 -0
  55. package/dist/styling/theme.js.map +1 -0
  56. package/dist/styling/variantSelector.d.ts +123 -0
  57. package/dist/styling/variantSelector.js +25 -0
  58. package/dist/styling/variantSelector.js.map +1 -0
  59. package/dist/types.d.ts +97 -0
  60. package/dist/types.js +2 -0
  61. package/dist/types.js.map +1 -0
  62. package/dist/utils/collections.d.ts +21 -0
  63. package/dist/utils/collections.js +75 -0
  64. package/dist/utils/collections.js.map +1 -0
  65. package/dist/utils/customElements.d.ts +6 -0
  66. package/dist/utils/customElements.js +15 -0
  67. package/dist/utils/customElements.js.map +1 -0
  68. package/dist/utils/hash.d.ts +1 -0
  69. package/dist/utils/hash.js +17 -0
  70. package/dist/utils/hash.js.map +1 -0
  71. package/dist/utils/json.d.ts +5 -0
  72. package/dist/utils/json.js +16 -0
  73. package/dist/utils/json.js.map +1 -0
  74. package/dist/utils/sha1.d.ts +2 -0
  75. package/dist/utils/sha1.js +13 -0
  76. package/dist/utils/sha1.js.map +1 -0
  77. package/dist/utils/url.d.ts +5 -0
  78. package/dist/utils/url.js +27 -0
  79. package/dist/utils/url.js.map +1 -0
  80. package/dist/utils/util.d.ts +3 -0
  81. package/dist/utils/util.js +4 -0
  82. package/dist/utils/util.js.map +1 -0
  83. package/package.json +18 -0
  84. package/src/api/LegacyToddleApi.ts +205 -0
  85. package/src/api/ToddleApiV2.ts +331 -0
  86. package/src/api/api.test.ts +319 -0
  87. package/src/api/api.ts +414 -0
  88. package/src/api/apiTypes.ts +145 -0
  89. package/src/api/headers.ts +41 -0
  90. package/src/api/template.ts +10 -0
  91. package/src/component/ToddleComponent.actionReferences.test.ts +75 -0
  92. package/src/component/ToddleComponent.formulasInComponent.test.ts +234 -0
  93. package/src/component/ToddleComponent.ts +470 -0
  94. package/src/component/actionUtils.ts +61 -0
  95. package/src/component/component.types.ts +362 -0
  96. package/src/component/isPageComponent.ts +6 -0
  97. package/src/formula/ToddleFormula.ts +30 -0
  98. package/src/formula/formula.ts +355 -0
  99. package/src/formula/formulaTypes.ts +45 -0
  100. package/src/formula/formulaUtils.ts +287 -0
  101. package/src/styling/className.test.ts +19 -0
  102. package/src/styling/className.ts +73 -0
  103. package/src/styling/style.css.ts +309 -0
  104. package/src/styling/theme.const.ts +390 -0
  105. package/src/styling/theme.ts +339 -0
  106. package/src/styling/variantSelector.ts +168 -0
  107. package/src/types.ts +158 -0
  108. package/src/utils/collections.test.ts +57 -0
  109. package/src/utils/collections.ts +122 -0
  110. package/src/utils/customElements.test.ts +40 -0
  111. package/src/utils/customElements.ts +16 -0
  112. package/src/utils/hash.test.ts +32 -0
  113. package/src/utils/hash.ts +18 -0
  114. package/src/utils/json.ts +18 -0
  115. package/src/utils/sha1.test.ts +50 -0
  116. package/src/utils/sha1.ts +17 -0
  117. package/src/utils/url.test.ts +17 -0
  118. package/src/utils/url.ts +33 -0
  119. package/src/utils/util.ts +8 -0
@@ -0,0 +1,355 @@
1
+ /* eslint-disable no-console */
2
+ import type { Component, ComponentData } from '../component/component.types'
3
+ import type {
4
+ CustomFormulaHandler,
5
+ FormulaHandler,
6
+ FormulaLookup,
7
+ Toddle,
8
+ ToddleMetadata,
9
+ } from '../types'
10
+ import { isDefined, toBoolean } from '../utils/util'
11
+ import { isToddleFormula } from './formulaTypes'
12
+
13
+ // Define the some objects types as union of ServerSide and ClientSide runtime types as applyFormula is used in both
14
+ declare const document: Document | undefined
15
+ type ShadowRoot = DocumentFragment
16
+
17
+ export interface PathOperation extends ToddleMetadata {
18
+ type: 'path'
19
+ path: string[]
20
+ }
21
+
22
+ type FunctionArgument = {
23
+ name?: string
24
+ isFunction?: boolean
25
+ formula: Formula
26
+ }
27
+
28
+ export interface FunctionOperation extends ToddleMetadata {
29
+ type: 'function'
30
+ name: string
31
+ display_name?: string | null
32
+ package?: string
33
+ arguments: FunctionArgument[]
34
+ variableArguments?: boolean
35
+ }
36
+
37
+ export interface RecordOperation extends ToddleMetadata {
38
+ type: 'record'
39
+ entries: FunctionArgument[]
40
+ }
41
+
42
+ export interface ObjectOperation extends ToddleMetadata {
43
+ type: 'object'
44
+ arguments?: FunctionArgument[]
45
+ }
46
+
47
+ export interface ArrayOperation extends ToddleMetadata {
48
+ type: 'array'
49
+ arguments: Array<{ formula: Formula }>
50
+ }
51
+
52
+ export interface OrOperation extends ToddleMetadata {
53
+ type: 'or'
54
+ arguments: Array<{ formula: Formula }>
55
+ }
56
+
57
+ export interface AndOperation extends ToddleMetadata {
58
+ type: 'and'
59
+ arguments: Array<{ formula: Formula }>
60
+ }
61
+
62
+ export interface ApplyOperation extends ToddleMetadata {
63
+ type: 'apply'
64
+ name: string
65
+ arguments: FunctionArgument[]
66
+ }
67
+
68
+ export interface ValueOperation extends ToddleMetadata {
69
+ type: 'value'
70
+ value: ValueOperationValue
71
+ }
72
+
73
+ export type ValueOperationValue = string | number | boolean | null | object
74
+
75
+ export interface SwitchOperation extends ToddleMetadata {
76
+ type: 'switch'
77
+ cases: Array<{
78
+ condition: Formula
79
+ formula: Formula
80
+ }>
81
+ default: Formula
82
+ }
83
+
84
+ export type Formula =
85
+ | FunctionOperation
86
+ | RecordOperation
87
+ | ObjectOperation
88
+ | ArrayOperation
89
+ | PathOperation
90
+ | SwitchOperation
91
+ | OrOperation
92
+ | AndOperation
93
+ | ValueOperation
94
+ | ApplyOperation
95
+
96
+ export type FormulaContext = {
97
+ component: Component
98
+ formulaCache?: Record<
99
+ string,
100
+ {
101
+ get: (data: ComponentData) => any
102
+ set: (data: ComponentData, result: any) => void
103
+ }
104
+ >
105
+ data: ComponentData
106
+ root?: Document | ShadowRoot | null
107
+ package: string | undefined
108
+ toddle: {
109
+ getFormula: FormulaLookup
110
+ getCustomFormula: CustomFormulaHandler
111
+ errors: Error[]
112
+ }
113
+ env: ToddleEnv | undefined
114
+ }
115
+
116
+ export type ToddleServerEnv = {
117
+ branchName: string
118
+ // isServer will be true for SSR + proxied requests
119
+ isServer: true
120
+ request: {
121
+ headers: Record<string, string>
122
+ cookies: Record<string, string>
123
+ url: string
124
+ }
125
+ runtime: never
126
+ logErrors: boolean
127
+ }
128
+
129
+ export type ToddleEnv =
130
+ | ToddleServerEnv
131
+ | {
132
+ branchName: string
133
+ // isServer will be false for client-side
134
+ isServer: false
135
+ request: undefined
136
+ runtime: 'page' | 'custom-element' | 'preview'
137
+ logErrors: boolean
138
+ }
139
+
140
+ export function isFormula(f: any): f is Formula {
141
+ return f && typeof f === 'object' && typeof f.type === 'string'
142
+ }
143
+ export function isFormulaApplyOperation(
144
+ formula: Formula,
145
+ ): formula is ApplyOperation {
146
+ return formula.type === 'apply'
147
+ }
148
+
149
+ export function applyFormula(
150
+ formula: Formula | string | number | undefined | null | boolean,
151
+ ctx: FormulaContext,
152
+ ): any {
153
+ if (!isFormula(formula)) {
154
+ return formula
155
+ }
156
+ try {
157
+ switch (formula.type) {
158
+ case 'value':
159
+ return formula.value
160
+ case 'path': {
161
+ let input: any = ctx.data
162
+ for (const key of formula.path) {
163
+ if (input && typeof input === 'object') {
164
+ input = input[key]
165
+ } else {
166
+ return null
167
+ }
168
+ }
169
+
170
+ return input
171
+ }
172
+ case 'switch': {
173
+ for (const branch of formula.cases) {
174
+ if (toBoolean(applyFormula(branch.condition, ctx))) {
175
+ return applyFormula(branch.formula, ctx)
176
+ }
177
+ }
178
+ return applyFormula(formula.default, ctx)
179
+ }
180
+ case 'or': {
181
+ for (const entry of formula.arguments) {
182
+ if (toBoolean(applyFormula(entry.formula, ctx))) {
183
+ return true
184
+ }
185
+ }
186
+ return false
187
+ }
188
+ case 'and': {
189
+ for (const entry of formula.arguments) {
190
+ if (!toBoolean(applyFormula(entry.formula, ctx))) {
191
+ return false
192
+ }
193
+ }
194
+ return true
195
+ }
196
+ case 'function': {
197
+ const packageName = formula.package ?? ctx.package
198
+ const newFunc = (
199
+ ctx.toddle ??
200
+ ((globalThis as any).toddle as Toddle<unknown, unknown> | undefined)
201
+ )?.getCustomFormula(formula.name, packageName)
202
+ const legacyFunc: FormulaHandler | undefined = (
203
+ ctx.toddle ?? ((globalThis as any).toddle as Toddle<unknown, unknown>)
204
+ ).getFormula(formula.name)
205
+ if (isDefined(newFunc)) {
206
+ const args = formula.arguments.reduce<Record<string, unknown>>(
207
+ (args, arg, i) => ({
208
+ ...args,
209
+ [arg.name ?? `${i}`]: arg.isFunction
210
+ ? (Args: any) =>
211
+ applyFormula(arg.formula, {
212
+ ...ctx,
213
+ data: {
214
+ ...ctx.data,
215
+ Args: ctx.data.Args
216
+ ? { ...Args, '@toddle.parent': ctx.data.Args }
217
+ : Args,
218
+ },
219
+ })
220
+ : applyFormula(arg.formula, ctx),
221
+ }),
222
+ {},
223
+ )
224
+ try {
225
+ return isToddleFormula(newFunc)
226
+ ? applyFormula(newFunc.formula, {
227
+ ...ctx,
228
+ data: { ...ctx.data, Args: args },
229
+ })
230
+ : newFunc.handler(args, {
231
+ root: ctx.root ?? document,
232
+ env: ctx.env,
233
+ } as any)
234
+ } catch (e) {
235
+ ctx.toddle.errors.push(e as Error)
236
+ if (ctx.env?.logErrors) {
237
+ console.error(e)
238
+ }
239
+ return null
240
+ }
241
+ } else if (typeof legacyFunc === 'function') {
242
+ const args = (formula.arguments ?? []).map((arg) =>
243
+ arg.isFunction
244
+ ? (Args: any) =>
245
+ applyFormula(arg.formula, {
246
+ ...ctx,
247
+ data: {
248
+ ...ctx.data,
249
+ Args: ctx.data.Args
250
+ ? { ...Args, '@toddle.parent': ctx.data.Args }
251
+ : Args,
252
+ },
253
+ })
254
+ : applyFormula(arg.formula, ctx),
255
+ )
256
+ try {
257
+ return legacyFunc(args, ctx as any)
258
+ } catch (e) {
259
+ ctx.toddle.errors.push(e as Error)
260
+ if (ctx.env?.logErrors) {
261
+ console.error(e)
262
+ }
263
+ return null
264
+ }
265
+ }
266
+ if (ctx.env?.logErrors) {
267
+ console.error(
268
+ `Could not find formula ${formula.name} in package ${
269
+ packageName ?? ''
270
+ }`,
271
+ formula,
272
+ )
273
+ }
274
+ return null
275
+ }
276
+ case 'object':
277
+ return Object.fromEntries(
278
+ formula.arguments?.map((entry) => [
279
+ entry.name,
280
+ applyFormula(entry.formula, ctx),
281
+ ]) ?? [],
282
+ )
283
+ case 'record': // object used to be called record, there are still examples in the wild.
284
+ return Object.fromEntries(
285
+ formula.entries.map((entry) => [
286
+ entry.name,
287
+ applyFormula(entry.formula, ctx),
288
+ ]),
289
+ )
290
+ case 'array':
291
+ return formula.arguments.map((entry) =>
292
+ applyFormula(entry.formula, ctx),
293
+ )
294
+ case 'apply': {
295
+ const componentFormula = ctx.component.formulas?.[formula.name]
296
+ if (!componentFormula) {
297
+ if (ctx.env?.logErrors) {
298
+ console.log(
299
+ 'Component does not have a formula with the name ',
300
+ formula.name,
301
+ )
302
+ }
303
+ return null
304
+ }
305
+ const Input = Object.fromEntries(
306
+ formula.arguments.map((arg) =>
307
+ arg.isFunction
308
+ ? [
309
+ arg.name,
310
+ (Args: any) =>
311
+ applyFormula(arg.formula, {
312
+ ...ctx,
313
+ data: {
314
+ ...ctx.data,
315
+ Args: ctx.data.Args
316
+ ? { ...Args, '@toddle.parent': ctx.data.Args }
317
+ : Args,
318
+ },
319
+ }),
320
+ ]
321
+ : [arg.name, applyFormula(arg.formula, ctx)],
322
+ ),
323
+ )
324
+ const data = {
325
+ ...ctx.data,
326
+ Args: ctx.data.Args
327
+ ? { ...Input, '@toddle.parent': ctx.data.Args }
328
+ : Input,
329
+ }
330
+ const cache = ctx.formulaCache?.[formula.name]?.get(data)
331
+
332
+ if (cache?.hit) {
333
+ return cache.data
334
+ } else {
335
+ const result = applyFormula(componentFormula.formula, {
336
+ ...ctx,
337
+ data,
338
+ })
339
+ ctx.formulaCache?.[formula.name]?.set(data, result)
340
+ return result
341
+ }
342
+ }
343
+
344
+ default:
345
+ if (ctx.env?.logErrors) {
346
+ console.error('Could not recognize formula', formula)
347
+ }
348
+ }
349
+ } catch (e) {
350
+ if (ctx.env?.logErrors) {
351
+ console.error(e)
352
+ }
353
+ return null
354
+ }
355
+ }
@@ -0,0 +1,45 @@
1
+ import type { Formula } from './formula'
2
+
3
+ export interface BaseFormula {
4
+ name: string
5
+ description?: string
6
+ arguments: Array<{
7
+ name: string
8
+ formula?: Formula | null
9
+ testValue?: unknown
10
+ }>
11
+ // exported indicates that a formula is exported in a package
12
+ exported?: boolean
13
+ variableArguments?: boolean | null
14
+ }
15
+
16
+ export interface ToddleFormula extends BaseFormula {
17
+ formula: Formula
18
+ }
19
+
20
+ /**
21
+ * The Handler generic is a string server side, but a function client side
22
+ */
23
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
24
+ export interface CodeFormula<Handler = string | Function> extends BaseFormula {
25
+ version?: 2 | never
26
+ handler: Handler
27
+ }
28
+
29
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
30
+ export type PluginFormula<Handler = string | Function> =
31
+ | ToddleFormula
32
+ | CodeFormula<Handler>
33
+
34
+ export const isToddleFormula = <Handler>(
35
+ formula: PluginFormula<Handler>,
36
+ ): formula is ToddleFormula => Object.hasOwn(formula, 'formula')
37
+
38
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
39
+ export interface GlobalFormulas<Handler = string | Function> {
40
+ formulas?: Record<string, PluginFormula<Handler>>
41
+ packages?: Record<
42
+ string,
43
+ { formulas?: Record<string, PluginFormula<Handler>> }
44
+ >
45
+ }
@@ -0,0 +1,287 @@
1
+ import type { ActionModel } from '../component/component.types'
2
+ import { isDefined } from '../utils/util'
3
+ import type {
4
+ Formula,
5
+ FunctionOperation,
6
+ PathOperation,
7
+ ValueOperation,
8
+ } from './formula'
9
+ import { isFormula } from './formula'
10
+ import type { GlobalFormulas } from './formulaTypes'
11
+ import { isToddleFormula } from './formulaTypes'
12
+
13
+ export const valueFormula = (
14
+ value: string | number | boolean | null | object,
15
+ ): ValueOperation => ({
16
+ type: 'value',
17
+ value,
18
+ })
19
+
20
+ export const pathFormula = (path: string[]): PathOperation => ({
21
+ type: 'path',
22
+ path,
23
+ })
24
+
25
+ export const functionFormula = (
26
+ name: string,
27
+ formula?: Omit<Partial<FunctionOperation>, 'type' | 'name'>,
28
+ ): FunctionOperation => ({
29
+ type: 'function',
30
+ name,
31
+ package: formula?.package,
32
+ arguments: formula?.arguments ?? [],
33
+ variableArguments: formula?.variableArguments,
34
+ })
35
+
36
+ export function* getFormulasInFormula<Handler>({
37
+ formula,
38
+ globalFormulas,
39
+ path = [],
40
+ visitedFormulas = new Set<string>(),
41
+ }: {
42
+ formula: Formula | undefined | null
43
+ globalFormulas: GlobalFormulas<Handler>
44
+ path?: (string | number)[]
45
+ visitedFormulas?: Set<string>
46
+ }): Generator<[(string | number)[], Formula]> {
47
+ if (!isDefined(formula)) {
48
+ return
49
+ }
50
+
51
+ yield [path, formula]
52
+ switch (formula.type) {
53
+ case 'path':
54
+ case 'value':
55
+ break
56
+ case 'record':
57
+ for (const [key, entry] of formula.entries.entries()) {
58
+ yield* getFormulasInFormula({
59
+ formula: entry.formula,
60
+ globalFormulas,
61
+ path: [...path, 'entries', key, 'formula'],
62
+ visitedFormulas,
63
+ })
64
+ }
65
+ break
66
+ case 'function': {
67
+ const formulaKey = [formula.package, formula.name]
68
+ .filter(isDefined)
69
+ .join('/')
70
+ const shouldVisitFormula = !visitedFormulas.has(formulaKey)
71
+ visitedFormulas.add(formulaKey)
72
+ const globalFormula = formula.package
73
+ ? globalFormulas.packages?.[formula.package]?.formulas?.[formula.name]
74
+ : globalFormulas.formulas?.[formula.name]
75
+ for (const [key, arg] of (
76
+ (formula.arguments as typeof formula.arguments | undefined) ?? []
77
+ ).entries()) {
78
+ yield* getFormulasInFormula({
79
+ formula: arg.formula,
80
+ globalFormulas,
81
+ path: [...path, 'arguments', key, 'formula'],
82
+ visitedFormulas,
83
+ })
84
+ }
85
+ // Lookup the actual function and traverse its potential formula references
86
+ // if this formula wasn't already visited
87
+ if (
88
+ globalFormula &&
89
+ isToddleFormula(globalFormula) &&
90
+ shouldVisitFormula
91
+ ) {
92
+ yield* getFormulasInFormula({
93
+ formula: globalFormula.formula,
94
+ globalFormulas,
95
+ path: [...path, 'formula'],
96
+ visitedFormulas,
97
+ })
98
+ }
99
+ break
100
+ }
101
+ case 'array':
102
+ case 'or':
103
+ case 'and':
104
+ case 'object':
105
+ for (const [key, arg] of (
106
+ (formula.arguments as typeof formula.arguments | undefined) ?? []
107
+ ).entries()) {
108
+ yield* getFormulasInFormula({
109
+ formula: arg.formula,
110
+ globalFormulas,
111
+ path: [...path, 'arguments', key, 'formula'],
112
+ visitedFormulas,
113
+ })
114
+ }
115
+ break
116
+ case 'apply':
117
+ for (const [key, arg] of (
118
+ (formula.arguments as typeof formula.arguments | undefined) ?? []
119
+ ).entries()) {
120
+ yield* getFormulasInFormula({
121
+ formula: arg.formula,
122
+ globalFormulas,
123
+ path: [...path, 'arguments', key, 'formula'],
124
+ visitedFormulas,
125
+ })
126
+ }
127
+ break
128
+ case 'switch':
129
+ for (const [key, c] of formula.cases.entries()) {
130
+ yield* getFormulasInFormula({
131
+ formula: c.condition,
132
+ globalFormulas,
133
+ path: [...path, 'cases', key, 'condition'],
134
+ visitedFormulas,
135
+ })
136
+ yield* getFormulasInFormula({
137
+ formula: c.formula,
138
+ globalFormulas,
139
+ path: [...path, 'cases', key, 'formula'],
140
+ visitedFormulas,
141
+ })
142
+ }
143
+ yield* getFormulasInFormula({
144
+ formula: formula.default,
145
+ globalFormulas,
146
+ path: [...path, 'default'],
147
+ visitedFormulas,
148
+ })
149
+ break
150
+ }
151
+ }
152
+ export function* getFormulasInAction<Handler>({
153
+ action,
154
+ globalFormulas,
155
+ path = [],
156
+ visitedFormulas = new Set<string>(),
157
+ }: {
158
+ action: ActionModel | null
159
+ globalFormulas: GlobalFormulas<Handler>
160
+ path?: (string | number)[]
161
+ visitedFormulas?: Set<string>
162
+ }): Generator<[(string | number)[], Formula]> {
163
+ if (!isDefined(action)) {
164
+ return
165
+ }
166
+
167
+ switch (action.type) {
168
+ case 'Fetch':
169
+ for (const [inputKey, input] of Object.entries(action.inputs ?? {})) {
170
+ yield* getFormulasInFormula({
171
+ formula: input.formula,
172
+ globalFormulas,
173
+ path: [...path, 'input', inputKey, 'formula'],
174
+ visitedFormulas,
175
+ })
176
+ }
177
+ for (const [key, a] of Object.entries(action.onSuccess?.actions ?? {})) {
178
+ yield* getFormulasInAction({
179
+ action: a,
180
+ globalFormulas,
181
+ path: [...path, 'onSuccess', 'actions', key],
182
+ visitedFormulas,
183
+ })
184
+ }
185
+ for (const [key, a] of Object.entries(action.onError?.actions ?? {})) {
186
+ yield* getFormulasInAction({
187
+ action: a,
188
+ globalFormulas,
189
+ path: [...path, 'onError', 'actions', key],
190
+ visitedFormulas,
191
+ })
192
+ }
193
+ for (const [key, a] of Object.entries(action.onMessage?.actions ?? {})) {
194
+ yield* getFormulasInAction({
195
+ action: a,
196
+ globalFormulas,
197
+ path: [...path, 'onMessage', 'actions', key],
198
+ visitedFormulas,
199
+ })
200
+ }
201
+ break
202
+ case 'Custom':
203
+ case undefined:
204
+ if (isFormula(action.data)) {
205
+ yield* getFormulasInFormula({
206
+ formula: action.data,
207
+ globalFormulas,
208
+ path: [...path, 'data'],
209
+ visitedFormulas,
210
+ })
211
+ }
212
+ for (const [key, a] of Object.entries(action.arguments ?? {})) {
213
+ yield* getFormulasInFormula({
214
+ formula: a.formula,
215
+ globalFormulas,
216
+ path: [...path, 'arguments', key, 'formula'],
217
+ visitedFormulas,
218
+ })
219
+ }
220
+
221
+ for (const [eventKey, event] of Object.entries(action.events ?? {})) {
222
+ for (const [key, a] of Object.entries(event.actions ?? {})) {
223
+ yield* getFormulasInAction({
224
+ action: a,
225
+ globalFormulas,
226
+ path: [...path, 'events', eventKey, 'actions', key],
227
+ visitedFormulas,
228
+ })
229
+ }
230
+ }
231
+ break
232
+ case 'SetVariable':
233
+ case 'SetURLParameter':
234
+ case 'TriggerEvent':
235
+ yield* getFormulasInFormula({
236
+ formula: action.data,
237
+ globalFormulas,
238
+ path: [...path, 'data'],
239
+ visitedFormulas,
240
+ })
241
+ break
242
+ case 'TriggerWorkflow':
243
+ for (const [key, a] of Object.entries(action.parameters ?? {})) {
244
+ yield* getFormulasInFormula({
245
+ formula: a.formula,
246
+ globalFormulas,
247
+ path: [...path, 'parameters', key, 'formula'],
248
+ visitedFormulas,
249
+ })
250
+ }
251
+ break
252
+ case 'Switch':
253
+ if (isDefined(action.data) && isFormula(action.data)) {
254
+ yield* getFormulasInFormula({
255
+ formula: action.data,
256
+ globalFormulas,
257
+ path: [...path, 'data'],
258
+ visitedFormulas,
259
+ })
260
+ }
261
+ for (const [key, c] of action.cases.entries()) {
262
+ yield* getFormulasInFormula({
263
+ formula: c.condition,
264
+ globalFormulas,
265
+ path: [...path, 'cases', key, 'condition'],
266
+ visitedFormulas,
267
+ })
268
+ for (const [actionKey, a] of Object.entries(c.actions)) {
269
+ yield* getFormulasInAction({
270
+ action: a,
271
+ globalFormulas,
272
+ path: [...path, 'cases', key, 'actions', actionKey],
273
+ visitedFormulas,
274
+ })
275
+ }
276
+ }
277
+ for (const [actionKey, a] of Object.entries(action.default.actions)) {
278
+ yield* getFormulasInAction({
279
+ action: a,
280
+ globalFormulas,
281
+ path: [...path, 'default', 'actions', actionKey],
282
+ visitedFormulas,
283
+ })
284
+ }
285
+ break
286
+ }
287
+ }
@@ -0,0 +1,19 @@
1
+ import { toValidClassName } from './className'
2
+
3
+ describe('toValidClassName()', () => {
4
+ test('it trims leading and trailing whitespace and replace the remaining whitespace with hyphens', () => {
5
+ expect(toValidClassName(' my class ')).toBe('my-class')
6
+ })
7
+
8
+ test('it escapes invalid characters by prefixing them with a backslash, if flag is set', () => {
9
+ expect(toValidClassName('my.class', true)).toBe('my\\.class')
10
+ })
11
+
12
+ test('it does not escape invalid characters by default', () => {
13
+ expect(toValidClassName('my.class')).toBe('my.class')
14
+ })
15
+
16
+ test('it ensures the class name does not start with a number or special character', () => {
17
+ expect(toValidClassName('1class')).toBe('_1class')
18
+ })
19
+ })