@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.
- package/README.md +5 -0
- package/dist/api/LegacyToddleApi.d.ts +34 -0
- package/dist/api/LegacyToddleApi.js +178 -0
- package/dist/api/LegacyToddleApi.js.map +1 -0
- package/dist/api/ToddleApiV2.d.ts +77 -0
- package/dist/api/ToddleApiV2.js +271 -0
- package/dist/api/ToddleApiV2.js.map +1 -0
- package/dist/api/api.d.ts +49 -0
- package/dist/api/api.js +276 -0
- package/dist/api/api.js.map +1 -0
- package/dist/api/apiTypes.d.ts +125 -0
- package/dist/api/apiTypes.js +11 -0
- package/dist/api/apiTypes.js.map +1 -0
- package/dist/api/headers.d.ts +10 -0
- package/dist/api/headers.js +36 -0
- package/dist/api/headers.js.map +1 -0
- package/dist/api/template.d.ts +5 -0
- package/dist/api/template.js +7 -0
- package/dist/api/template.js.map +1 -0
- package/dist/component/ToddleComponent.d.ts +45 -0
- package/dist/component/ToddleComponent.js +373 -0
- package/dist/component/ToddleComponent.js.map +1 -0
- package/dist/component/actionUtils.d.ts +2 -0
- package/dist/component/actionUtils.js +56 -0
- package/dist/component/actionUtils.js.map +1 -0
- package/dist/component/component.types.d.ts +313 -0
- package/dist/component/component.types.js +9 -0
- package/dist/component/component.types.js.map +1 -0
- package/dist/component/isPageComponent.d.ts +2 -0
- package/dist/component/isPageComponent.js +3 -0
- package/dist/component/isPageComponent.js.map +1 -0
- package/dist/formula/ToddleFormula.d.ts +15 -0
- package/dist/formula/ToddleFormula.js +20 -0
- package/dist/formula/ToddleFormula.js.map +1 -0
- package/dist/formula/formula.d.ts +103 -0
- package/dist/formula/formula.js +186 -0
- package/dist/formula/formula.js.map +1 -0
- package/dist/formula/formulaTypes.d.ts +30 -0
- package/dist/formula/formulaTypes.js +2 -0
- package/dist/formula/formulaTypes.js.map +1 -0
- package/dist/formula/formulaUtils.d.ts +18 -0
- package/dist/formula/formulaUtils.js +240 -0
- package/dist/formula/formulaUtils.js.map +1 -0
- package/dist/styling/className.d.ts +2 -0
- package/dist/styling/className.js +52 -0
- package/dist/styling/className.js.map +1 -0
- package/dist/styling/style.css.d.ts +5 -0
- package/dist/styling/style.css.js +242 -0
- package/dist/styling/style.css.js.map +1 -0
- package/dist/styling/theme.const.d.ts +3 -0
- package/dist/styling/theme.const.js +381 -0
- package/dist/styling/theme.const.js.map +1 -0
- package/dist/styling/theme.d.ts +72 -0
- package/dist/styling/theme.js +200 -0
- package/dist/styling/theme.js.map +1 -0
- package/dist/styling/variantSelector.d.ts +123 -0
- package/dist/styling/variantSelector.js +25 -0
- package/dist/styling/variantSelector.js.map +1 -0
- package/dist/types.d.ts +97 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/collections.d.ts +21 -0
- package/dist/utils/collections.js +75 -0
- package/dist/utils/collections.js.map +1 -0
- package/dist/utils/customElements.d.ts +6 -0
- package/dist/utils/customElements.js +15 -0
- package/dist/utils/customElements.js.map +1 -0
- package/dist/utils/hash.d.ts +1 -0
- package/dist/utils/hash.js +17 -0
- package/dist/utils/hash.js.map +1 -0
- package/dist/utils/json.d.ts +5 -0
- package/dist/utils/json.js +16 -0
- package/dist/utils/json.js.map +1 -0
- package/dist/utils/sha1.d.ts +2 -0
- package/dist/utils/sha1.js +13 -0
- package/dist/utils/sha1.js.map +1 -0
- package/dist/utils/url.d.ts +5 -0
- package/dist/utils/url.js +27 -0
- package/dist/utils/url.js.map +1 -0
- package/dist/utils/util.d.ts +3 -0
- package/dist/utils/util.js +4 -0
- package/dist/utils/util.js.map +1 -0
- package/package.json +18 -0
- package/src/api/LegacyToddleApi.ts +205 -0
- package/src/api/ToddleApiV2.ts +331 -0
- package/src/api/api.test.ts +319 -0
- package/src/api/api.ts +414 -0
- package/src/api/apiTypes.ts +145 -0
- package/src/api/headers.ts +41 -0
- package/src/api/template.ts +10 -0
- package/src/component/ToddleComponent.actionReferences.test.ts +75 -0
- package/src/component/ToddleComponent.formulasInComponent.test.ts +234 -0
- package/src/component/ToddleComponent.ts +470 -0
- package/src/component/actionUtils.ts +61 -0
- package/src/component/component.types.ts +362 -0
- package/src/component/isPageComponent.ts +6 -0
- package/src/formula/ToddleFormula.ts +30 -0
- package/src/formula/formula.ts +355 -0
- package/src/formula/formulaTypes.ts +45 -0
- package/src/formula/formulaUtils.ts +287 -0
- package/src/styling/className.test.ts +19 -0
- package/src/styling/className.ts +73 -0
- package/src/styling/style.css.ts +309 -0
- package/src/styling/theme.const.ts +390 -0
- package/src/styling/theme.ts +339 -0
- package/src/styling/variantSelector.ts +168 -0
- package/src/types.ts +158 -0
- package/src/utils/collections.test.ts +57 -0
- package/src/utils/collections.ts +122 -0
- package/src/utils/customElements.test.ts +40 -0
- package/src/utils/customElements.ts +16 -0
- package/src/utils/hash.test.ts +32 -0
- package/src/utils/hash.ts +18 -0
- package/src/utils/json.ts +18 -0
- package/src/utils/sha1.test.ts +50 -0
- package/src/utils/sha1.ts +17 -0
- package/src/utils/url.test.ts +17 -0
- package/src/utils/url.ts +33 -0
- 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
|
+
})
|