@nordcraft/core 2.0.7 → 2.0.9
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/dist/api/LegacyToddleApi.d.ts +1 -0
- package/dist/api/LegacyToddleApi.js +3 -0
- package/dist/api/LegacyToddleApi.js.map +1 -1
- package/dist/api/ToddleApiV2.d.ts +2 -1
- package/dist/api/ToddleApiV2.js +3 -0
- package/dist/api/ToddleApiV2.js.map +1 -1
- package/dist/api/api.d.ts +2 -4
- package/dist/api/api.js +1 -42
- package/dist/api/api.js.map +1 -1
- package/dist/api/apiTypes.d.ts +2 -0
- package/dist/api/apiTypes.js.map +1 -1
- package/dist/component/ToddleComponent.d.ts +0 -2
- package/dist/component/ToddleComponent.js +7 -21
- package/dist/component/ToddleComponent.js.map +1 -1
- package/dist/component/component.types.d.ts +11 -2
- package/dist/component/component.types.js.map +1 -1
- package/dist/component/schemas/api-schema.js +5 -0
- package/dist/component/schemas/api-schema.js.map +1 -1
- package/dist/styling/className.d.ts +4 -1
- package/dist/styling/className.js +4 -1
- package/dist/styling/className.js.map +1 -1
- package/dist/styling/style.css.js.map +1 -1
- package/package.json +1 -1
- package/src/api/LegacyToddleApi.ts +3 -0
- package/src/api/ToddleApiV2.ts +5 -1
- package/src/api/api.test.ts +47 -0
- package/src/api/api.ts +3 -63
- package/src/api/apiTypes.ts +2 -0
- package/src/component/ToddleComponent.subComponents.test.ts +219 -0
- package/src/component/ToddleComponent.ts +10 -54
- package/src/component/component.types.ts +17 -2
- package/src/component/schemas/api-schema.ts +5 -0
- package/src/styling/className.test.ts +16 -1
- package/src/styling/className.ts +15 -2
- package/src/styling/style.css.ts +2 -3
- package/src/component/ToddleComponent.actionReferences.test.ts +0 -112
package/src/api/ToddleApiV2.ts
CHANGED
|
@@ -27,7 +27,7 @@ export class ToddleApiV2<Handler> implements ApiRequest {
|
|
|
27
27
|
this.globalFormulas = globalFormulas
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
get apiReferences(): Set<string> {
|
|
30
|
+
private get apiReferences(): Set<string> {
|
|
31
31
|
if (this._apiReferences) {
|
|
32
32
|
// Only compute apiReferences once
|
|
33
33
|
return this._apiReferences
|
|
@@ -174,6 +174,10 @@ export class ToddleApiV2<Handler> implements ApiRequest {
|
|
|
174
174
|
return this.api['@nordcraft/metadata']
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
get dependsOn() {
|
|
178
|
+
return Array.from(this.apiReferences)
|
|
179
|
+
}
|
|
180
|
+
|
|
177
181
|
*formulasInApi(): Generator<{
|
|
178
182
|
path: (string | number)[]
|
|
179
183
|
formula: Formula
|
package/src/api/api.test.ts
CHANGED
|
@@ -7,10 +7,13 @@ import {
|
|
|
7
7
|
getRequestPath,
|
|
8
8
|
getRequestQueryParams,
|
|
9
9
|
getUrl,
|
|
10
|
+
isLegacyApi,
|
|
10
11
|
toFormData,
|
|
11
12
|
} from './api'
|
|
12
13
|
import type { ApiRequest } from './apiTypes'
|
|
13
14
|
import { ApiMethod } from './apiTypes'
|
|
15
|
+
import { LegacyToddleApi } from './LegacyToddleApi'
|
|
16
|
+
import { ToddleApiV2 } from './ToddleApiV2'
|
|
14
17
|
|
|
15
18
|
describe('getApiPath()', () => {
|
|
16
19
|
test('it returns a valid url path string', () => {
|
|
@@ -486,3 +489,47 @@ describe('toFormData()', () => {
|
|
|
486
489
|
expect(formData.get('valid')).toBe('working')
|
|
487
490
|
})
|
|
488
491
|
})
|
|
492
|
+
describe('isLegacyApi()', () => {
|
|
493
|
+
test('it checks if an API is legacy', () => {
|
|
494
|
+
expect(
|
|
495
|
+
isLegacyApi({
|
|
496
|
+
name: 'Legacy API',
|
|
497
|
+
type: 'REST',
|
|
498
|
+
}),
|
|
499
|
+
).toBe(true)
|
|
500
|
+
expect(
|
|
501
|
+
isLegacyApi({
|
|
502
|
+
name: 'New API',
|
|
503
|
+
type: 'http',
|
|
504
|
+
version: 2,
|
|
505
|
+
inputs: {},
|
|
506
|
+
}),
|
|
507
|
+
).toBe(false)
|
|
508
|
+
expect(
|
|
509
|
+
isLegacyApi(
|
|
510
|
+
new LegacyToddleApi(
|
|
511
|
+
{
|
|
512
|
+
name: 'Legacy API',
|
|
513
|
+
type: 'REST',
|
|
514
|
+
},
|
|
515
|
+
'myLegacyApi',
|
|
516
|
+
{},
|
|
517
|
+
),
|
|
518
|
+
),
|
|
519
|
+
).toBe(true)
|
|
520
|
+
expect(
|
|
521
|
+
isLegacyApi(
|
|
522
|
+
new ToddleApiV2(
|
|
523
|
+
{
|
|
524
|
+
name: 'New API',
|
|
525
|
+
type: 'http',
|
|
526
|
+
version: 2,
|
|
527
|
+
inputs: {},
|
|
528
|
+
},
|
|
529
|
+
'myNewApi',
|
|
530
|
+
{},
|
|
531
|
+
),
|
|
532
|
+
),
|
|
533
|
+
).toBe(false)
|
|
534
|
+
})
|
|
535
|
+
})
|
package/src/api/api.ts
CHANGED
|
@@ -14,15 +14,14 @@ import type {
|
|
|
14
14
|
} from './apiTypes'
|
|
15
15
|
import { ApiMethod } from './apiTypes'
|
|
16
16
|
import { isJsonHeader } from './headers'
|
|
17
|
-
import { LegacyToddleApi } from './LegacyToddleApi'
|
|
18
|
-
import { ToddleApiV2 } from './ToddleApiV2'
|
|
17
|
+
import type { LegacyToddleApi } from './LegacyToddleApi'
|
|
18
|
+
import type { ToddleApiV2 } from './ToddleApiV2'
|
|
19
19
|
|
|
20
20
|
export const NON_BODY_RESPONSE_CODES = [101, 204, 205, 304]
|
|
21
21
|
|
|
22
22
|
export const isLegacyApi = <Handler>(
|
|
23
23
|
api: ComponentAPI | LegacyToddleApi<Handler> | ToddleApiV2<Handler>,
|
|
24
|
-
): api is LegacyComponentAPI | LegacyToddleApi<Handler> =>
|
|
25
|
-
api instanceof LegacyToddleApi ? true : !('version' in api)
|
|
24
|
+
): api is LegacyComponentAPI | LegacyToddleApi<Handler> => !('version' in api)
|
|
26
25
|
|
|
27
26
|
export const createApiRequest = <Handler>({
|
|
28
27
|
api,
|
|
@@ -420,62 +419,3 @@ export const createApiEvent = (
|
|
|
420
419
|
detail,
|
|
421
420
|
})
|
|
422
421
|
}
|
|
423
|
-
|
|
424
|
-
const compareApiDependencies = <Handler>(
|
|
425
|
-
a: LegacyToddleApi<Handler> | ToddleApiV2<Handler>,
|
|
426
|
-
b: LegacyToddleApi<Handler> | ToddleApiV2<Handler>,
|
|
427
|
-
) => {
|
|
428
|
-
const isADependentOnB = a.apiReferences.has(b.name)
|
|
429
|
-
const isBDependentOnA = b.apiReferences.has(a.name)
|
|
430
|
-
if (isADependentOnB === isBDependentOnA) {
|
|
431
|
-
return 0
|
|
432
|
-
}
|
|
433
|
-
// 1 means A goes last - hence B is evaluated before A
|
|
434
|
-
return isADependentOnB ? 1 : -1
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
export const sortApiObjects = <Handler>(
|
|
438
|
-
apis: Array<[string, ComponentAPI]>,
|
|
439
|
-
) => {
|
|
440
|
-
const apiMap = new Map<
|
|
441
|
-
string,
|
|
442
|
-
LegacyToddleApi<Handler> | ToddleApiV2<Handler>
|
|
443
|
-
>()
|
|
444
|
-
const getApi = (apiObj: ComponentAPI, key: string) => {
|
|
445
|
-
let api = apiMap.get(key)
|
|
446
|
-
if (!api) {
|
|
447
|
-
api =
|
|
448
|
-
apiObj.version === 2
|
|
449
|
-
? new ToddleApiV2<Handler>(
|
|
450
|
-
apiObj,
|
|
451
|
-
key,
|
|
452
|
-
// global formulas are not required for sorting
|
|
453
|
-
{
|
|
454
|
-
formulas: {},
|
|
455
|
-
packages: {},
|
|
456
|
-
},
|
|
457
|
-
)
|
|
458
|
-
: new LegacyToddleApi<Handler>(
|
|
459
|
-
apiObj,
|
|
460
|
-
key,
|
|
461
|
-
// global formulas are not required for sorting
|
|
462
|
-
{
|
|
463
|
-
formulas: {},
|
|
464
|
-
packages: {},
|
|
465
|
-
},
|
|
466
|
-
)
|
|
467
|
-
apiMap.set(key, api)
|
|
468
|
-
}
|
|
469
|
-
return api
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
return [...apis].sort(([aKey, aObj], [bKey, bObj]) => {
|
|
473
|
-
const a = getApi(aObj, aKey)
|
|
474
|
-
const b = getApi(bObj, bKey)
|
|
475
|
-
return compareApiDependencies(a, b)
|
|
476
|
-
})
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
export const sortApiEntries = <Handler>(
|
|
480
|
-
apis: Array<[string, LegacyToddleApi<Handler> | ToddleApiV2<Handler>]>,
|
|
481
|
-
) => [...apis].sort(([_, a], [__, b]) => compareApiDependencies(a, b))
|
package/src/api/apiTypes.ts
CHANGED
|
@@ -23,6 +23,7 @@ export interface LegacyComponentAPI {
|
|
|
23
23
|
onCompleted?: Nullable<EventModel>
|
|
24
24
|
onFailed?: Nullable<EventModel>
|
|
25
25
|
version?: never
|
|
26
|
+
dependsOn?: string[]
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
export interface LegacyApiStatus {
|
|
@@ -137,6 +138,7 @@ export interface ApiRequest extends ApiBase {
|
|
|
137
138
|
isError?: Nullable<{ formula: Formula }>
|
|
138
139
|
// Formula for determining when the request should time out
|
|
139
140
|
timeout?: Nullable<{ formula: Formula }>
|
|
141
|
+
dependsOn?: string[]
|
|
140
142
|
}
|
|
141
143
|
|
|
142
144
|
export interface ApiStatus {
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { ToddleComponent } from './ToddleComponent'
|
|
3
|
+
import type { Component } from './component.types'
|
|
4
|
+
|
|
5
|
+
describe('ToddleComponent.uniqueSubComponents', () => {
|
|
6
|
+
test('it should pass down the package name to sub-components', () => {
|
|
7
|
+
const packagedComp: Component = {
|
|
8
|
+
name: 'PackagedComp',
|
|
9
|
+
nodes: {
|
|
10
|
+
node1: {
|
|
11
|
+
type: 'component',
|
|
12
|
+
name: 'InternalComp',
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
apis: {},
|
|
16
|
+
attributes: {},
|
|
17
|
+
variables: {},
|
|
18
|
+
workflows: {},
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const internalComp: Component = {
|
|
22
|
+
name: 'InternalComp',
|
|
23
|
+
nodes: {},
|
|
24
|
+
apis: {},
|
|
25
|
+
attributes: {},
|
|
26
|
+
variables: {},
|
|
27
|
+
workflows: {},
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const root: Component = {
|
|
31
|
+
name: 'root',
|
|
32
|
+
nodes: {
|
|
33
|
+
node1: {
|
|
34
|
+
type: 'component',
|
|
35
|
+
name: 'PackagedComp',
|
|
36
|
+
package: 'my-package',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
apis: {},
|
|
40
|
+
attributes: {},
|
|
41
|
+
variables: {},
|
|
42
|
+
workflows: {},
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const componentsMap: Record<string, Component> = {
|
|
46
|
+
'my-package/PackagedComp': packagedComp,
|
|
47
|
+
'my-package/InternalComp': internalComp,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const getComponent = (name: string, packageName?: string) => {
|
|
51
|
+
const key = packageName ? `${packageName}/${name}` : name
|
|
52
|
+
return componentsMap[key]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const demo = new ToddleComponent({
|
|
56
|
+
component: root,
|
|
57
|
+
getComponent,
|
|
58
|
+
packageName: undefined,
|
|
59
|
+
globalFormulas: { formulas: {}, packages: {} },
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const subComponents = demo.uniqueSubComponents
|
|
63
|
+
expect(subComponents).toHaveLength(2)
|
|
64
|
+
expect(subComponents[0].name).toBe('PackagedComp')
|
|
65
|
+
expect(subComponents[0].packageName).toBe('my-package')
|
|
66
|
+
expect(subComponents[1].name).toBe('InternalComp')
|
|
67
|
+
expect(subComponents[1].packageName).toBe('my-package')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('it should handle nested components and avoid duplicates', () => {
|
|
71
|
+
const compA: Component = {
|
|
72
|
+
name: 'CompA',
|
|
73
|
+
nodes: {
|
|
74
|
+
node1: {
|
|
75
|
+
type: 'component',
|
|
76
|
+
name: 'CompB',
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
apis: {},
|
|
80
|
+
attributes: {},
|
|
81
|
+
variables: {},
|
|
82
|
+
workflows: {},
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const compB: Component = {
|
|
86
|
+
name: 'CompB',
|
|
87
|
+
nodes: {},
|
|
88
|
+
apis: {},
|
|
89
|
+
attributes: {},
|
|
90
|
+
variables: {},
|
|
91
|
+
workflows: {},
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const root: Component = {
|
|
95
|
+
name: 'root',
|
|
96
|
+
nodes: {
|
|
97
|
+
node1: {
|
|
98
|
+
type: 'component',
|
|
99
|
+
name: 'CompA',
|
|
100
|
+
},
|
|
101
|
+
node2: {
|
|
102
|
+
type: 'component',
|
|
103
|
+
name: 'CompB',
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
apis: {},
|
|
107
|
+
attributes: {},
|
|
108
|
+
variables: {},
|
|
109
|
+
workflows: {},
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const componentsMap: Record<string, Component> = {
|
|
113
|
+
CompA: compA,
|
|
114
|
+
CompB: compB,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const getComponent = (name: string, packageName?: string) => {
|
|
118
|
+
const key = packageName ? `${packageName}/${name}` : name
|
|
119
|
+
return componentsMap[key]
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const demo = new ToddleComponent({
|
|
123
|
+
component: root,
|
|
124
|
+
getComponent,
|
|
125
|
+
packageName: undefined,
|
|
126
|
+
globalFormulas: { formulas: {}, packages: {} },
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
const subComponents = demo.uniqueSubComponents
|
|
130
|
+
expect(subComponents).toHaveLength(2)
|
|
131
|
+
const names = subComponents.map((c) => c.name).sort()
|
|
132
|
+
expect(names).toEqual(['CompA', 'CompB'])
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test('it should allow sub-components to override the package name', () => {
|
|
136
|
+
const pkgAComp: Component = {
|
|
137
|
+
name: 'PkgAComp',
|
|
138
|
+
nodes: {
|
|
139
|
+
node1: {
|
|
140
|
+
type: 'component',
|
|
141
|
+
name: 'PkgBComp',
|
|
142
|
+
package: 'package-b',
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
apis: {},
|
|
146
|
+
attributes: {},
|
|
147
|
+
variables: {},
|
|
148
|
+
workflows: {},
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const pkgBComp: Component = {
|
|
152
|
+
name: 'PkgBComp',
|
|
153
|
+
nodes: {
|
|
154
|
+
node1: {
|
|
155
|
+
type: 'component',
|
|
156
|
+
name: 'InternalComp',
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
apis: {},
|
|
160
|
+
attributes: {},
|
|
161
|
+
variables: {},
|
|
162
|
+
workflows: {},
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const internalComp: Component = {
|
|
166
|
+
name: 'InternalComp',
|
|
167
|
+
nodes: {},
|
|
168
|
+
apis: {},
|
|
169
|
+
attributes: {},
|
|
170
|
+
variables: {},
|
|
171
|
+
workflows: {},
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const root: Component = {
|
|
175
|
+
name: 'root',
|
|
176
|
+
nodes: {
|
|
177
|
+
node1: {
|
|
178
|
+
type: 'component',
|
|
179
|
+
name: 'PkgAComp',
|
|
180
|
+
package: 'package-a',
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
apis: {},
|
|
184
|
+
attributes: {},
|
|
185
|
+
variables: {},
|
|
186
|
+
workflows: {},
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const componentsMap: Record<string, Component> = {
|
|
190
|
+
'package-a/PkgAComp': pkgAComp,
|
|
191
|
+
'package-b/PkgBComp': pkgBComp,
|
|
192
|
+
'package-b/InternalComp': internalComp,
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const getComponent = (name: string, packageName?: string) => {
|
|
196
|
+
const key = packageName ? `${packageName}/${name}` : name
|
|
197
|
+
return componentsMap[key]
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const demo = new ToddleComponent({
|
|
201
|
+
component: root,
|
|
202
|
+
getComponent,
|
|
203
|
+
packageName: undefined,
|
|
204
|
+
globalFormulas: { formulas: {}, packages: {} },
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
const subComponents = demo.uniqueSubComponents
|
|
208
|
+
expect(subComponents).toHaveLength(3)
|
|
209
|
+
|
|
210
|
+
const pA = subComponents.find((c) => c.name === 'PkgAComp')
|
|
211
|
+
expect(pA?.packageName).toBe('package-a')
|
|
212
|
+
|
|
213
|
+
const pB = subComponents.find((c) => c.name === 'PkgBComp')
|
|
214
|
+
expect(pB?.packageName).toBe('package-b')
|
|
215
|
+
|
|
216
|
+
const internal = subComponents.find((c) => c.name === 'InternalComp')
|
|
217
|
+
expect(internal?.packageName).toBe('package-b')
|
|
218
|
+
})
|
|
219
|
+
})
|
|
@@ -2,7 +2,7 @@ import { isLegacyApi } from '../api/api'
|
|
|
2
2
|
import type { ComponentAPI } from '../api/apiTypes'
|
|
3
3
|
import { LegacyToddleApi } from '../api/LegacyToddleApi'
|
|
4
4
|
import { ToddleApiV2 } from '../api/ToddleApiV2'
|
|
5
|
-
import type { Formula
|
|
5
|
+
import type { Formula } from '../formula/formula'
|
|
6
6
|
import type { GlobalFormulas } from '../formula/formulaTypes'
|
|
7
7
|
import {
|
|
8
8
|
getFormulasInAction,
|
|
@@ -10,12 +10,7 @@ import {
|
|
|
10
10
|
} from '../formula/formulaUtils'
|
|
11
11
|
import { isDefined } from '../utils/util'
|
|
12
12
|
import { getActionsInAction } from './actionUtils'
|
|
13
|
-
import type {
|
|
14
|
-
ActionModel,
|
|
15
|
-
Component,
|
|
16
|
-
CustomActionModel,
|
|
17
|
-
NodeModel,
|
|
18
|
-
} from './component.types'
|
|
13
|
+
import type { ActionModel, Component, NodeModel } from './component.types'
|
|
19
14
|
import { isPageComponent } from './isPageComponent'
|
|
20
15
|
|
|
21
16
|
export class ToddleComponent<Handler> {
|
|
@@ -54,10 +49,8 @@ export class ToddleComponent<Handler> {
|
|
|
54
49
|
if (components.has(node.name)) {
|
|
55
50
|
return
|
|
56
51
|
}
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
node.package ?? packageName,
|
|
60
|
-
)
|
|
52
|
+
const componentPackageName = node.package ?? packageName
|
|
53
|
+
const component = this.getComponent(node.name, componentPackageName)
|
|
61
54
|
if (!component) {
|
|
62
55
|
return
|
|
63
56
|
}
|
|
@@ -66,13 +59,17 @@ export class ToddleComponent<Handler> {
|
|
|
66
59
|
new ToddleComponent({
|
|
67
60
|
component,
|
|
68
61
|
getComponent: this.getComponent,
|
|
69
|
-
packageName:
|
|
62
|
+
packageName: componentPackageName,
|
|
70
63
|
globalFormulas: this.globalFormulas,
|
|
71
64
|
}),
|
|
72
65
|
)
|
|
73
66
|
Object.values(component.nodes ?? {}).forEach((node) => {
|
|
74
67
|
if (isDefined(node)) {
|
|
75
|
-
|
|
68
|
+
const nodePackageName =
|
|
69
|
+
(node.type === 'component' ? node.package : undefined) ??
|
|
70
|
+
componentPackageName ??
|
|
71
|
+
packageName
|
|
72
|
+
visitNode(nodePackageName)(node)
|
|
76
73
|
}
|
|
77
74
|
})
|
|
78
75
|
}
|
|
@@ -84,47 +81,6 @@ export class ToddleComponent<Handler> {
|
|
|
84
81
|
return [...components.values()]
|
|
85
82
|
}
|
|
86
83
|
|
|
87
|
-
get formulaReferences() {
|
|
88
|
-
return new Set(
|
|
89
|
-
Array.from(this.formulasInComponent())
|
|
90
|
-
.filter(
|
|
91
|
-
(
|
|
92
|
-
entry,
|
|
93
|
-
): entry is {
|
|
94
|
-
path: (string | number)[]
|
|
95
|
-
formula: FunctionOperation
|
|
96
|
-
packageName?: string
|
|
97
|
-
} => entry.formula.type === 'function',
|
|
98
|
-
)
|
|
99
|
-
.flatMap((entry) => {
|
|
100
|
-
const refs = [entry.formula.name]
|
|
101
|
-
const packageName =
|
|
102
|
-
entry.formula.package ?? entry.packageName ?? this.packageName
|
|
103
|
-
if (
|
|
104
|
-
packageName &&
|
|
105
|
-
this.globalFormulas.packages?.[packageName]?.formulas?.[
|
|
106
|
-
entry.formula.name
|
|
107
|
-
]
|
|
108
|
-
) {
|
|
109
|
-
refs.push([packageName, entry.formula.name].join('/'))
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return refs
|
|
113
|
-
}),
|
|
114
|
-
)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
get actionReferences(): Set<string> {
|
|
118
|
-
return new Set(
|
|
119
|
-
Array.from(this.actionModelsInComponent())
|
|
120
|
-
.filter(
|
|
121
|
-
(entry): entry is [(string | number)[], CustomActionModel] =>
|
|
122
|
-
entry[1].type === 'Custom' || entry[1].type === undefined,
|
|
123
|
-
)
|
|
124
|
-
.map(([, a]) => [a.package, a.name].filter(isDefined).join('/')),
|
|
125
|
-
)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
84
|
/**
|
|
129
85
|
* Traverse all formulas in the component.
|
|
130
86
|
* @returns An iterable that yields the path and formula.
|
|
@@ -206,10 +206,12 @@ export interface Component {
|
|
|
206
206
|
// Later, we will add information about allowed origins here
|
|
207
207
|
enabled?: Nullable<Formula>
|
|
208
208
|
}>
|
|
209
|
+
// Used to indicate if a page or any of its child components use custom actions/formulas
|
|
210
|
+
customCode?: Nullable<boolean>
|
|
209
211
|
}
|
|
210
212
|
|
|
211
213
|
export interface ComponentFormula extends NordcraftMetadata {
|
|
212
|
-
name
|
|
214
|
+
name?: string
|
|
213
215
|
arguments?: Nullable<Array<{ name: string; testValue: any }>>
|
|
214
216
|
memoize?: Nullable<boolean>
|
|
215
217
|
exposeInContext?: Nullable<boolean>
|
|
@@ -217,7 +219,7 @@ export interface ComponentFormula extends NordcraftMetadata {
|
|
|
217
219
|
}
|
|
218
220
|
|
|
219
221
|
export interface ComponentWorkflow extends NordcraftMetadata {
|
|
220
|
-
name
|
|
222
|
+
name?: string
|
|
221
223
|
parameters: Array<{ name: string; testValue: any }>
|
|
222
224
|
callbacks?: Nullable<Array<{ name: string; testValue: any }>>
|
|
223
225
|
actions: ActionModel[]
|
|
@@ -239,7 +241,17 @@ export interface RouteDeclaration {
|
|
|
239
241
|
query: Record<string, { name: string; testValue: any }>
|
|
240
242
|
}
|
|
241
243
|
|
|
244
|
+
export interface ResponseHeaders {
|
|
245
|
+
[key: string]: Nullable<Formula | string>
|
|
246
|
+
}
|
|
247
|
+
|
|
242
248
|
export interface PageRoute extends RouteDeclaration {
|
|
249
|
+
response?: Nullable<{
|
|
250
|
+
// Allow overriding response headers for a page render. For example to set a custom cache-control header for a page
|
|
251
|
+
// or specify a Location along with a 3xx status code to redirect the user to another page
|
|
252
|
+
headers?: Nullable<ResponseHeaders>
|
|
253
|
+
status?: Nullable<Formula>
|
|
254
|
+
}>
|
|
243
255
|
// Information for the <head> element
|
|
244
256
|
// only relevant for pages - not for regular
|
|
245
257
|
// components
|
|
@@ -418,6 +430,9 @@ export interface ComponentEvent extends NordcraftMetadata {
|
|
|
418
430
|
|
|
419
431
|
export interface ComponentVariable extends NordcraftMetadata {
|
|
420
432
|
initialValue: Formula
|
|
433
|
+
// @deprecated - variables should be referenced by their key in component.variables
|
|
434
|
+
// name is only here to better reflect how variables are stored in legacy components
|
|
435
|
+
name?: never
|
|
421
436
|
}
|
|
422
437
|
|
|
423
438
|
export interface ComponentAttribute extends NordcraftMetadata {
|
|
@@ -210,6 +210,10 @@ const ApiRequestSchema: z.ZodType<ApiRequest> = z
|
|
|
210
210
|
.describe(
|
|
211
211
|
'Rules for redirecting based on response data. The key is a unique identifier for the rule.',
|
|
212
212
|
),
|
|
213
|
+
dependsOn: z
|
|
214
|
+
.array(z.string())
|
|
215
|
+
.optional()
|
|
216
|
+
.describe('List of APIs that this API depends on.'),
|
|
213
217
|
})
|
|
214
218
|
.describe('Schema defining an API request from a component or a page.')
|
|
215
219
|
|
|
@@ -244,6 +248,7 @@ const LegacyComponentAPISchema: z.ZodType<LegacyComponentAPI> = z
|
|
|
244
248
|
type: z.enum(['Bearer id_token', 'Bearer access_token']),
|
|
245
249
|
})
|
|
246
250
|
.nullish(),
|
|
251
|
+
dependsOn: z.array(z.string()).optional(),
|
|
247
252
|
})
|
|
248
253
|
.describe(
|
|
249
254
|
'Legacy API schema for backward compatibility. Never use this for new APIs.',
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { getClassName, toValidClassName } from './className'
|
|
2
3
|
|
|
3
4
|
describe('toValidClassName()', () => {
|
|
4
5
|
test('it trims leading and trailing whitespace and replace the remaining whitespace with hyphens', () => {
|
|
@@ -17,3 +18,17 @@ describe('toValidClassName()', () => {
|
|
|
17
18
|
expect(toValidClassName('1class')).toBe('_1class')
|
|
18
19
|
})
|
|
19
20
|
})
|
|
21
|
+
|
|
22
|
+
describe('getClassName()', () => {
|
|
23
|
+
test('it produces the same classname whether a nullish entry is provided or not', () => {
|
|
24
|
+
expect(getClassName([null, undefined, { color: 'red' }])).toBe(
|
|
25
|
+
getClassName([{ color: 'red' }]),
|
|
26
|
+
)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('it produces the same classname whether an empty object is provided or not', () => {
|
|
30
|
+
expect(getClassName([{}, { color: 'red' }])).toBe(
|
|
31
|
+
getClassName([{ color: 'red' }]),
|
|
32
|
+
)
|
|
33
|
+
})
|
|
34
|
+
})
|
package/src/styling/className.ts
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
|
+
import type { NodeStyleModel } from '../component/component.types'
|
|
2
|
+
import type { Nullable } from '../types'
|
|
1
3
|
import { generateAlphabeticName, hash } from './hash'
|
|
4
|
+
import type { StyleVariant } from './variantSelector'
|
|
2
5
|
|
|
3
6
|
// Classnames are reused a lot, and JS hashing is expensive, so there is benefit in caching them in a native hashmap.
|
|
4
7
|
const CLASSNAME_LOOKUP = new Map<string, string>()
|
|
5
|
-
export const getClassName = (
|
|
6
|
-
|
|
8
|
+
export const getClassName = (
|
|
9
|
+
object: [Nullable<NodeStyleModel>, Nullable<StyleVariant[]>],
|
|
10
|
+
) => {
|
|
11
|
+
const stringified = JSON.stringify(
|
|
12
|
+
object.filter(
|
|
13
|
+
(item) =>
|
|
14
|
+
item !== null && // Skip nullish values
|
|
15
|
+
item !== undefined &&
|
|
16
|
+
(Array.isArray(item) ? item.length > 0 : true) && // Skip empty arrays/objects
|
|
17
|
+
(typeof item === 'object' ? Object.keys(item).length > 0 : true),
|
|
18
|
+
),
|
|
19
|
+
)
|
|
7
20
|
if (CLASSNAME_LOOKUP.has(stringified)) {
|
|
8
21
|
return CLASSNAME_LOOKUP.get(stringified)!
|
|
9
22
|
}
|
package/src/styling/style.css.ts
CHANGED
|
@@ -284,11 +284,10 @@ export const createStylesheet = (
|
|
|
284
284
|
if (childComponent) {
|
|
285
285
|
insertComponentStyles(childComponent, node.package ?? package_name)
|
|
286
286
|
stylesheet += getNodeStyles(
|
|
287
|
-
node
|
|
287
|
+
node,
|
|
288
288
|
toValidClassName(`${component.name}:${id}`, true),
|
|
289
289
|
animationHashes,
|
|
290
290
|
)
|
|
291
|
-
|
|
292
291
|
return
|
|
293
292
|
}
|
|
294
293
|
}
|
|
@@ -300,7 +299,7 @@ export const createStylesheet = (
|
|
|
300
299
|
return ''
|
|
301
300
|
}
|
|
302
301
|
hashes.add(classHash)
|
|
303
|
-
stylesheet += getNodeStyles(node
|
|
302
|
+
stylesheet += getNodeStyles(node, classHash, animationHashes)
|
|
304
303
|
})
|
|
305
304
|
}
|
|
306
305
|
insertComponentStyles(root)
|