@likec4/generators 1.52.0 → 1.53.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/dist/_chunks/chunk.mjs +11 -0
- package/dist/index.d.mts +18 -1
- package/dist/index.mjs +189 -47
- package/dist/likec4/index.d.mts +277169 -0
- package/dist/likec4/index.mjs +1799 -0
- package/likec4/package.json +4 -0
- package/package.json +22 -8
- package/src/drawio/generate-drawio.ts +73 -8
- package/src/drawio/index.ts +1 -0
- package/src/drawio/parse-drawio.ts +172 -18
- package/src/index.ts +2 -0
- package/src/likec4/generate-likec4.ts +72 -0
- package/src/likec4/index.ts +12 -0
- package/src/likec4/operators/base.ts +938 -0
- package/src/likec4/operators/deployment.ts +263 -0
- package/src/likec4/operators/expressions.ts +422 -0
- package/src/likec4/operators/index.ts +13 -0
- package/src/likec4/operators/likec4data.ts +33 -0
- package/src/likec4/operators/model.ts +222 -0
- package/src/likec4/operators/properties.ts +244 -0
- package/src/likec4/operators/specification.ts +119 -0
- package/src/likec4/operators/views.ts +390 -0
- package/src/likec4/schemas/common.ts +123 -0
- package/src/likec4/schemas/deployment.ts +113 -0
- package/src/likec4/schemas/expression.ts +218 -0
- package/src/likec4/schemas/index.ts +83 -0
- package/src/likec4/schemas/likec4data.ts +76 -0
- package/src/likec4/schemas/model.ts +127 -0
- package/src/likec4/schemas/specification.ts +83 -0
- package/src/likec4/schemas/views.ts +321 -0
- package/src/model/generate-likec4.ts +0 -5
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import {
|
|
2
|
+
hasProp,
|
|
3
|
+
invariant,
|
|
4
|
+
nonexhaustive,
|
|
5
|
+
} from '@likec4/core'
|
|
6
|
+
import { CompositeGeneratorNode, NL } from 'langium/generate'
|
|
7
|
+
import { hasAtLeast, values } from 'remeda'
|
|
8
|
+
import { schemas } from '../schemas'
|
|
9
|
+
import {
|
|
10
|
+
type Op,
|
|
11
|
+
body,
|
|
12
|
+
foreach,
|
|
13
|
+
foreachNewLine,
|
|
14
|
+
guard,
|
|
15
|
+
indent,
|
|
16
|
+
inlineText,
|
|
17
|
+
lines,
|
|
18
|
+
merge,
|
|
19
|
+
print,
|
|
20
|
+
printProperty,
|
|
21
|
+
property,
|
|
22
|
+
select,
|
|
23
|
+
separateComma,
|
|
24
|
+
space,
|
|
25
|
+
spaceBetween,
|
|
26
|
+
withctx,
|
|
27
|
+
zodOp,
|
|
28
|
+
} from './base'
|
|
29
|
+
import { expression } from './expressions'
|
|
30
|
+
import {
|
|
31
|
+
colorProperty,
|
|
32
|
+
descriptionProperty,
|
|
33
|
+
linksProperty,
|
|
34
|
+
notationProperty,
|
|
35
|
+
notesProperty,
|
|
36
|
+
styleProperties,
|
|
37
|
+
tagsProperty,
|
|
38
|
+
technologyProperty,
|
|
39
|
+
titleProperty,
|
|
40
|
+
} from './properties'
|
|
41
|
+
|
|
42
|
+
const viewTitleProperty = <A extends { title?: string | null | undefined }>(): Op<A> =>
|
|
43
|
+
property(
|
|
44
|
+
'title',
|
|
45
|
+
spaceBetween(
|
|
46
|
+
print('title'),
|
|
47
|
+
inlineText(),
|
|
48
|
+
),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
export const viewRulePredicate = zodOp(schemas.views.viewRulePredicate)(({ ctx, exec }) => {
|
|
52
|
+
let exprs
|
|
53
|
+
let type
|
|
54
|
+
if ('include' in ctx) {
|
|
55
|
+
exprs = ctx.include
|
|
56
|
+
type = 'include'
|
|
57
|
+
} else if ('exclude' in ctx) {
|
|
58
|
+
exprs = ctx.exclude
|
|
59
|
+
type = 'exclude'
|
|
60
|
+
}
|
|
61
|
+
invariant(exprs && type, 'Invalid view rule predicate')
|
|
62
|
+
|
|
63
|
+
if (!hasAtLeast(exprs, 1)) {
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const isMultiple = hasAtLeast(exprs, 2)
|
|
68
|
+
|
|
69
|
+
const exprOp = withctx(exprs)(
|
|
70
|
+
foreach(
|
|
71
|
+
expression(),
|
|
72
|
+
separateComma(isMultiple),
|
|
73
|
+
),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
exec(
|
|
77
|
+
ctx,
|
|
78
|
+
merge(
|
|
79
|
+
print(type),
|
|
80
|
+
...(isMultiple ? [indent(exprOp)] : [space(), exprOp]),
|
|
81
|
+
),
|
|
82
|
+
)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
export const viewRuleStyle = zodOp(schemas.views.viewRuleStyle)(
|
|
86
|
+
spaceBetween(
|
|
87
|
+
print('style'),
|
|
88
|
+
property(
|
|
89
|
+
'targets',
|
|
90
|
+
foreach(
|
|
91
|
+
expression(),
|
|
92
|
+
separateComma(),
|
|
93
|
+
),
|
|
94
|
+
),
|
|
95
|
+
body('{', '}')(
|
|
96
|
+
notationProperty(),
|
|
97
|
+
property('style', styleProperties()),
|
|
98
|
+
),
|
|
99
|
+
),
|
|
100
|
+
)
|
|
101
|
+
export const viewRuleGroup = zodOp(schemas.views.viewRuleGroup)(({ ctx, exec }) => {
|
|
102
|
+
throw new Error('not implemented')
|
|
103
|
+
})
|
|
104
|
+
export const viewRuleGlobalStyle = zodOp(schemas.views.viewRuleGlobalStyle)(({ ctx, exec }) => {
|
|
105
|
+
throw new Error('not implemented')
|
|
106
|
+
})
|
|
107
|
+
export const viewRuleGlobalPredicate = zodOp(schemas.views.viewRuleGlobalPredicate)(({ ctx, exec }) => {
|
|
108
|
+
throw new Error('not implemented')
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
const mapping = {
|
|
112
|
+
'TB': 'TopBottom',
|
|
113
|
+
'BT': 'BottomTop',
|
|
114
|
+
'LR': 'LeftRight',
|
|
115
|
+
'RL': 'RightLeft',
|
|
116
|
+
} as const
|
|
117
|
+
export const viewRuleAutoLayout = zodOp(schemas.views.viewRuleAutoLayout)(
|
|
118
|
+
spaceBetween(
|
|
119
|
+
print('autoLayout'),
|
|
120
|
+
property(
|
|
121
|
+
'direction',
|
|
122
|
+
print(v => mapping[v]),
|
|
123
|
+
),
|
|
124
|
+
guard(
|
|
125
|
+
hasProp('rankSep'),
|
|
126
|
+
spaceBetween(
|
|
127
|
+
printProperty('rankSep'),
|
|
128
|
+
printProperty('nodeSep'),
|
|
129
|
+
),
|
|
130
|
+
),
|
|
131
|
+
),
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
export const viewRuleRank = zodOp(schemas.views.viewRuleRank)(({ ctx, exec }) => {
|
|
135
|
+
throw new Error('not implemented')
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
export const elementViewRule = zodOp(schemas.views.elementViewRule)(
|
|
139
|
+
({ ctx, exec }) => {
|
|
140
|
+
if ('include' in ctx || 'exclude' in ctx) {
|
|
141
|
+
return exec(ctx, viewRulePredicate())
|
|
142
|
+
}
|
|
143
|
+
if ('groupRules' in ctx) {
|
|
144
|
+
return exec(ctx, viewRuleGroup())
|
|
145
|
+
}
|
|
146
|
+
if ('rank' in ctx) {
|
|
147
|
+
return exec(ctx, viewRuleRank())
|
|
148
|
+
}
|
|
149
|
+
if ('direction' in ctx) {
|
|
150
|
+
return exec(ctx, viewRuleAutoLayout())
|
|
151
|
+
}
|
|
152
|
+
if ('styleId' in ctx) {
|
|
153
|
+
return exec(ctx, viewRuleGlobalStyle())
|
|
154
|
+
}
|
|
155
|
+
if ('predicateId' in ctx) {
|
|
156
|
+
return exec(ctx, viewRuleGlobalPredicate())
|
|
157
|
+
}
|
|
158
|
+
if ('targets' in ctx && 'style' in ctx) {
|
|
159
|
+
return exec(ctx, viewRuleStyle())
|
|
160
|
+
}
|
|
161
|
+
nonexhaustive(ctx)
|
|
162
|
+
},
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
export const elementView = zodOp(schemas.views.elementView.partial({ _type: true }))(
|
|
166
|
+
spaceBetween(
|
|
167
|
+
print('view'),
|
|
168
|
+
print(v => v.id),
|
|
169
|
+
property(
|
|
170
|
+
'viewOf',
|
|
171
|
+
spaceBetween(
|
|
172
|
+
print('of'),
|
|
173
|
+
print(),
|
|
174
|
+
),
|
|
175
|
+
),
|
|
176
|
+
body(
|
|
177
|
+
lines(2)(
|
|
178
|
+
// Properties on each line
|
|
179
|
+
lines(
|
|
180
|
+
tagsProperty(),
|
|
181
|
+
viewTitleProperty(),
|
|
182
|
+
descriptionProperty(),
|
|
183
|
+
linksProperty(),
|
|
184
|
+
),
|
|
185
|
+
property(
|
|
186
|
+
'rules',
|
|
187
|
+
foreachNewLine(
|
|
188
|
+
elementViewRule(),
|
|
189
|
+
),
|
|
190
|
+
),
|
|
191
|
+
),
|
|
192
|
+
),
|
|
193
|
+
),
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
// --- Deployment View ---
|
|
197
|
+
|
|
198
|
+
export const deploymentViewRule = zodOp(schemas.views.deploymentViewRule)(
|
|
199
|
+
({ ctx, exec }) => {
|
|
200
|
+
if ('include' in ctx || 'exclude' in ctx) {
|
|
201
|
+
return exec(ctx, viewRulePredicate())
|
|
202
|
+
}
|
|
203
|
+
if ('direction' in ctx) {
|
|
204
|
+
return exec(ctx, viewRuleAutoLayout())
|
|
205
|
+
}
|
|
206
|
+
if ('styleId' in ctx) {
|
|
207
|
+
return exec(ctx, viewRuleGlobalStyle())
|
|
208
|
+
}
|
|
209
|
+
if ('predicateId' in ctx) {
|
|
210
|
+
return exec(ctx, viewRuleGlobalPredicate())
|
|
211
|
+
}
|
|
212
|
+
if ('targets' in ctx && 'style' in ctx) {
|
|
213
|
+
return exec(ctx, viewRuleStyle())
|
|
214
|
+
}
|
|
215
|
+
nonexhaustive(ctx)
|
|
216
|
+
},
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
export const deploymentView = zodOp(schemas.views.deploymentView.partial({ _type: true }))(
|
|
220
|
+
spaceBetween(
|
|
221
|
+
print('deployment view'),
|
|
222
|
+
print(v => v.id),
|
|
223
|
+
body(
|
|
224
|
+
lines(2)(
|
|
225
|
+
// Properties on each line
|
|
226
|
+
lines(
|
|
227
|
+
tagsProperty(),
|
|
228
|
+
viewTitleProperty(),
|
|
229
|
+
descriptionProperty(),
|
|
230
|
+
linksProperty(),
|
|
231
|
+
),
|
|
232
|
+
property(
|
|
233
|
+
'rules',
|
|
234
|
+
foreachNewLine(
|
|
235
|
+
deploymentViewRule(),
|
|
236
|
+
),
|
|
237
|
+
),
|
|
238
|
+
),
|
|
239
|
+
),
|
|
240
|
+
),
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
// --- Dynamic View ---
|
|
244
|
+
|
|
245
|
+
export const dynamicStep = zodOp(schemas.views.dynamicStep)(
|
|
246
|
+
spaceBetween(
|
|
247
|
+
print(v => v.source),
|
|
248
|
+
print(v => v.isBackward ? '<-' : '->'),
|
|
249
|
+
print(v => v.target),
|
|
250
|
+
body(
|
|
251
|
+
lines(
|
|
252
|
+
titleProperty(),
|
|
253
|
+
technologyProperty(),
|
|
254
|
+
descriptionProperty(),
|
|
255
|
+
notesProperty(),
|
|
256
|
+
property('navigateTo'),
|
|
257
|
+
notationProperty(),
|
|
258
|
+
colorProperty(),
|
|
259
|
+
property('line'),
|
|
260
|
+
property('head'),
|
|
261
|
+
property('tail'),
|
|
262
|
+
),
|
|
263
|
+
),
|
|
264
|
+
),
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
export const dynamicStepsSeries = zodOp(schemas.views.dynamicStepsSeries)(({ ctx, exec }) => {
|
|
268
|
+
throw new Error('Not implemented')
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
export const dynamicStepsParallel = zodOp(schemas.views.dynamicStepsParallel)(({ ctx, exec }) => {
|
|
272
|
+
throw new Error('Not implemented')
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
export const dynamicViewStep = zodOp(schemas.views.dynamicViewStep)(({ ctx, exec }) => {
|
|
276
|
+
if ('__series' in ctx) {
|
|
277
|
+
return exec(ctx, dynamicStepsSeries())
|
|
278
|
+
}
|
|
279
|
+
if ('__parallel' in ctx) {
|
|
280
|
+
return exec(ctx, dynamicStepsParallel())
|
|
281
|
+
}
|
|
282
|
+
return exec(ctx, dynamicStep())
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
export const dynamicViewIncludeRule = zodOp(schemas.views.dynamicViewIncludeRule)(({ ctx, exec }) => {
|
|
286
|
+
if (!hasAtLeast(ctx.include, 1)) {
|
|
287
|
+
return
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const isMultiple = hasAtLeast(ctx.include, 2)
|
|
291
|
+
|
|
292
|
+
const exprOp = withctx(ctx.include)(
|
|
293
|
+
foreach(
|
|
294
|
+
expression(),
|
|
295
|
+
separateComma(isMultiple),
|
|
296
|
+
),
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
exec(
|
|
300
|
+
ctx,
|
|
301
|
+
merge(
|
|
302
|
+
print('include'),
|
|
303
|
+
...(isMultiple ? [indent(exprOp)] : [space(), exprOp]),
|
|
304
|
+
),
|
|
305
|
+
)
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
export const dynamicViewRule = zodOp(schemas.views.dynamicViewRule)(
|
|
309
|
+
({ ctx, exec }) => {
|
|
310
|
+
if ('include' in ctx) {
|
|
311
|
+
return exec(ctx, dynamicViewIncludeRule())
|
|
312
|
+
}
|
|
313
|
+
if ('predicateId' in ctx) {
|
|
314
|
+
return exec(ctx, viewRuleGlobalPredicate())
|
|
315
|
+
}
|
|
316
|
+
if ('targets' in ctx && 'style' in ctx) {
|
|
317
|
+
return exec(ctx, viewRuleStyle())
|
|
318
|
+
}
|
|
319
|
+
if ('styleId' in ctx) {
|
|
320
|
+
return exec(ctx, viewRuleGlobalStyle())
|
|
321
|
+
}
|
|
322
|
+
if ('direction' in ctx) {
|
|
323
|
+
return exec(ctx, viewRuleAutoLayout())
|
|
324
|
+
}
|
|
325
|
+
nonexhaustive(ctx)
|
|
326
|
+
},
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
export const dynamicView = zodOp(schemas.views.dynamicView.partial({ _type: true }))(
|
|
330
|
+
spaceBetween(
|
|
331
|
+
print('dynamic view'),
|
|
332
|
+
print(v => v.id),
|
|
333
|
+
body(
|
|
334
|
+
lines(2)(
|
|
335
|
+
lines(
|
|
336
|
+
tagsProperty(),
|
|
337
|
+
viewTitleProperty(),
|
|
338
|
+
descriptionProperty(),
|
|
339
|
+
linksProperty(),
|
|
340
|
+
property('variant'),
|
|
341
|
+
),
|
|
342
|
+
property(
|
|
343
|
+
'steps',
|
|
344
|
+
foreachNewLine(
|
|
345
|
+
dynamicViewStep(),
|
|
346
|
+
),
|
|
347
|
+
),
|
|
348
|
+
property(
|
|
349
|
+
'rules',
|
|
350
|
+
foreachNewLine(
|
|
351
|
+
dynamicViewRule(),
|
|
352
|
+
),
|
|
353
|
+
),
|
|
354
|
+
),
|
|
355
|
+
),
|
|
356
|
+
),
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
// --- Parsed View ---
|
|
360
|
+
export const anyView = zodOp(schemas.views.anyView)(({ ctx, exec }) => {
|
|
361
|
+
if ('_type' in ctx) {
|
|
362
|
+
if (ctx._type == 'element') {
|
|
363
|
+
return exec(ctx, elementView())
|
|
364
|
+
}
|
|
365
|
+
if (ctx._type === 'deployment') {
|
|
366
|
+
return exec(ctx, deploymentView())
|
|
367
|
+
}
|
|
368
|
+
if (ctx._type === 'dynamic') {
|
|
369
|
+
return exec(ctx, dynamicView())
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
nonexhaustive(ctx)
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
export const views = zodOp(schemas.views.views)(
|
|
376
|
+
body('views')(
|
|
377
|
+
select(
|
|
378
|
+
ctx => values(ctx),
|
|
379
|
+
foreach(
|
|
380
|
+
anyView(),
|
|
381
|
+
{
|
|
382
|
+
// Extra line between
|
|
383
|
+
separator: new CompositeGeneratorNode().appendNewLine().appendNewLine(),
|
|
384
|
+
// Add empty line before first view (if not the last one)
|
|
385
|
+
prefix: (_, index, isLast) => index === 0 && !isLast ? NL : undefined,
|
|
386
|
+
},
|
|
387
|
+
),
|
|
388
|
+
),
|
|
389
|
+
),
|
|
390
|
+
)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BorderStyles,
|
|
3
|
+
ElementShapes,
|
|
4
|
+
IconPositions,
|
|
5
|
+
RelationshipArrowTypes,
|
|
6
|
+
Sizes,
|
|
7
|
+
ThemeColors,
|
|
8
|
+
} from '@likec4/core/styles'
|
|
9
|
+
import {
|
|
10
|
+
type CustomColor,
|
|
11
|
+
type Link,
|
|
12
|
+
type scalar,
|
|
13
|
+
exact,
|
|
14
|
+
} from '@likec4/core/types'
|
|
15
|
+
import * as z from 'zod/v4'
|
|
16
|
+
|
|
17
|
+
export const id = z
|
|
18
|
+
.string()
|
|
19
|
+
.regex(/^[a-zA-Z0-9_.-]+$/, 'id must consist of alphanumeric characters, underscores or hyphens')
|
|
20
|
+
|
|
21
|
+
export const viewId = id.transform(value => value as unknown as scalar.ViewId)
|
|
22
|
+
|
|
23
|
+
export const fqn = z
|
|
24
|
+
.string()
|
|
25
|
+
.regex(/^[a-zA-Z0-9_.-]+$/, 'FQN must consist of alphanumeric characters, dots, underscores or hyphens')
|
|
26
|
+
.transform(value => value as unknown as scalar.Fqn)
|
|
27
|
+
|
|
28
|
+
export const kind = z
|
|
29
|
+
.string()
|
|
30
|
+
.regex(/^[a-zA-Z0-9_-]+$/, 'Kind must consist of alphanumeric characters, underscores or hyphens')
|
|
31
|
+
.transform(value => value as unknown as scalar.ElementKind)
|
|
32
|
+
|
|
33
|
+
export const opacity = z
|
|
34
|
+
.int()
|
|
35
|
+
.min(0, 'Opacity must be between 0 and 100')
|
|
36
|
+
.max(100, 'Opacity must be between 0 and 100')
|
|
37
|
+
|
|
38
|
+
export const shape = z.literal(ElementShapes)
|
|
39
|
+
|
|
40
|
+
export const icon = z.string().nonempty('Icon cannot be empty').transform(value => value as unknown as scalar.Icon)
|
|
41
|
+
|
|
42
|
+
export const border = z
|
|
43
|
+
.literal(BorderStyles)
|
|
44
|
+
|
|
45
|
+
export const size = z.literal(Sizes)
|
|
46
|
+
|
|
47
|
+
export const iconPosition = z
|
|
48
|
+
.literal(IconPositions)
|
|
49
|
+
|
|
50
|
+
export const arrow = z
|
|
51
|
+
.literal(RelationshipArrowTypes)
|
|
52
|
+
|
|
53
|
+
export const line = z
|
|
54
|
+
.literal(['dashed', 'solid', 'dotted'])
|
|
55
|
+
|
|
56
|
+
export const link = z.union([
|
|
57
|
+
z.string(),
|
|
58
|
+
z.object({
|
|
59
|
+
title: z.string().optional(),
|
|
60
|
+
url: z.string(),
|
|
61
|
+
}),
|
|
62
|
+
]).transform(
|
|
63
|
+
value => exact(typeof value === 'string' ? { url: value } : value) as Link,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
export const links = z.array(link).readonly()
|
|
67
|
+
|
|
68
|
+
export const themeColor = z.literal(ThemeColors)
|
|
69
|
+
|
|
70
|
+
export const customColor = z
|
|
71
|
+
.custom<string & Record<never, never>>()
|
|
72
|
+
.refine(v => typeof v === 'string', 'Custom color name must be a string')
|
|
73
|
+
.transform(value => value as unknown as CustomColor)
|
|
74
|
+
|
|
75
|
+
export const tag = z
|
|
76
|
+
.string()
|
|
77
|
+
.nonempty('Tag cannot be empty')
|
|
78
|
+
.transform(tag => (tag.startsWith('#') ? tag.slice(1) : tag) as unknown as scalar.Tag)
|
|
79
|
+
|
|
80
|
+
export const tags = z.array(tag).readonly()
|
|
81
|
+
|
|
82
|
+
export const markdownOrString = z.union([
|
|
83
|
+
z.string(),
|
|
84
|
+
z.strictObject({ md: z.string() }),
|
|
85
|
+
z.strictObject({ txt: z.string() }),
|
|
86
|
+
]).transform(v => (typeof v === 'string' ? { txt: v } : v) as scalar.MarkdownOrString)
|
|
87
|
+
|
|
88
|
+
export const color = themeColor.or(customColor)
|
|
89
|
+
|
|
90
|
+
export const style = z
|
|
91
|
+
.object({
|
|
92
|
+
shape: shape,
|
|
93
|
+
icon: icon,
|
|
94
|
+
iconColor: color,
|
|
95
|
+
iconSize: size,
|
|
96
|
+
iconPosition: iconPosition,
|
|
97
|
+
color: color,
|
|
98
|
+
border: border,
|
|
99
|
+
opacity: opacity,
|
|
100
|
+
size: size,
|
|
101
|
+
padding: size,
|
|
102
|
+
textSize: size,
|
|
103
|
+
multiple: z.boolean(),
|
|
104
|
+
})
|
|
105
|
+
.partial()
|
|
106
|
+
|
|
107
|
+
export const metadataValue = z.union([z.string(), z.boolean(), z.number()])
|
|
108
|
+
|
|
109
|
+
export const metadata = z.record(z.string(), metadataValue.or(z.array(metadataValue)))
|
|
110
|
+
|
|
111
|
+
export const props = z
|
|
112
|
+
.object({
|
|
113
|
+
tags: tags.nullable(),
|
|
114
|
+
title: z.string(),
|
|
115
|
+
summary: markdownOrString.nullable(),
|
|
116
|
+
description: markdownOrString.nullable(),
|
|
117
|
+
notation: z.string().nullable(),
|
|
118
|
+
technology: z.string().nullable(),
|
|
119
|
+
links: links.nullable(),
|
|
120
|
+
metadata: metadata,
|
|
121
|
+
})
|
|
122
|
+
// all properties are optional, as the generator should be able to handle missing fields and provide defaults
|
|
123
|
+
.partial()
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RelationId,
|
|
3
|
+
} from '@likec4/core/types'
|
|
4
|
+
import { produce } from 'immer'
|
|
5
|
+
import { indexBy, isArray, isNonNullish, pickBy, prop, randomString } from 'remeda'
|
|
6
|
+
import * as z from 'zod/v4'
|
|
7
|
+
import * as common from './common'
|
|
8
|
+
import * as expression from './expression'
|
|
9
|
+
|
|
10
|
+
export const node = common.props
|
|
11
|
+
.extend({
|
|
12
|
+
id: common.fqn,
|
|
13
|
+
kind: common.kind,
|
|
14
|
+
style: common.style.optional(),
|
|
15
|
+
/**
|
|
16
|
+
* Allowing shape, color and icon to be defined at the element level for convenience,
|
|
17
|
+
* they will be moved to the style property during parsing
|
|
18
|
+
* (and will override properties)
|
|
19
|
+
*/
|
|
20
|
+
shape: common.shape.optional(),
|
|
21
|
+
color: common.color.optional(),
|
|
22
|
+
icon: common.icon.optional(),
|
|
23
|
+
})
|
|
24
|
+
.readonly()
|
|
25
|
+
.transform(value => {
|
|
26
|
+
const { shape, color, icon, ...rest } = value
|
|
27
|
+
if (shape || color || icon) {
|
|
28
|
+
return produce(rest, draft => {
|
|
29
|
+
draft.style = rest.style || {}
|
|
30
|
+
draft.style.shape = shape ?? rest.style?.shape
|
|
31
|
+
draft.style.color = color ?? rest.style?.color
|
|
32
|
+
draft.style.icon = icon ?? rest.style?.icon
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
return rest
|
|
36
|
+
})
|
|
37
|
+
.transform(pickBy(isNonNullish))
|
|
38
|
+
|
|
39
|
+
export const instance = common.props
|
|
40
|
+
.extend({
|
|
41
|
+
id: common.fqn,
|
|
42
|
+
element: common.fqn,
|
|
43
|
+
style: common.style.optional(),
|
|
44
|
+
/**
|
|
45
|
+
* Allowing shape, color and icon to be defined at the element level for convenience,
|
|
46
|
+
* they will be moved to the style property during parsing
|
|
47
|
+
* (and will override properties)
|
|
48
|
+
*/
|
|
49
|
+
shape: common.shape.optional(),
|
|
50
|
+
color: common.color.optional(),
|
|
51
|
+
icon: common.icon.optional(),
|
|
52
|
+
})
|
|
53
|
+
.readonly()
|
|
54
|
+
.transform(value => {
|
|
55
|
+
const { shape, color, icon, ...rest } = value
|
|
56
|
+
if (shape || color || icon) {
|
|
57
|
+
return produce(rest, draft => {
|
|
58
|
+
draft.style = rest.style || {}
|
|
59
|
+
draft.style.shape = shape ?? rest.style?.shape
|
|
60
|
+
draft.style.color = color ?? rest.style?.color
|
|
61
|
+
draft.style.icon = icon ?? rest.style?.icon
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
return rest
|
|
65
|
+
})
|
|
66
|
+
.transform(pickBy(isNonNullish))
|
|
67
|
+
|
|
68
|
+
const relationshipEndpoint = expression.refDeployment
|
|
69
|
+
|
|
70
|
+
const relationshipId = common.id.transform(value => value as unknown as RelationId)
|
|
71
|
+
|
|
72
|
+
export const relationship = common.props
|
|
73
|
+
.extend({
|
|
74
|
+
id: relationshipId.optional(),
|
|
75
|
+
title: z.string().nullish(),
|
|
76
|
+
source: relationshipEndpoint,
|
|
77
|
+
target: relationshipEndpoint,
|
|
78
|
+
navigateTo: common.viewId.nullish(),
|
|
79
|
+
color: common.color.nullish(),
|
|
80
|
+
kind: common.kind.nullish(),
|
|
81
|
+
line: common.line.nullish(),
|
|
82
|
+
head: common.arrow.nullish(),
|
|
83
|
+
tail: common.arrow.nullish(),
|
|
84
|
+
})
|
|
85
|
+
.readonly()
|
|
86
|
+
.transform(pickBy(isNonNullish))
|
|
87
|
+
|
|
88
|
+
// ============ Top-Level Schema ============
|
|
89
|
+
|
|
90
|
+
export const element = z.union([node, instance])
|
|
91
|
+
|
|
92
|
+
const elements = z.record(common.fqn, element)
|
|
93
|
+
const relationships = z.record(relationshipId, relationship)
|
|
94
|
+
|
|
95
|
+
const genRelationshipId = (r: z.output<typeof relationship>): RelationId => r.id ?? randomString(8) as RelationId
|
|
96
|
+
|
|
97
|
+
export const schema = z
|
|
98
|
+
.object({
|
|
99
|
+
elements: z
|
|
100
|
+
.union([
|
|
101
|
+
elements,
|
|
102
|
+
z.array(element),
|
|
103
|
+
])
|
|
104
|
+
.transform(v => isArray(v) ? indexBy(v, prop('id')) as unknown as z.output<typeof elements> : v)
|
|
105
|
+
.optional(),
|
|
106
|
+
relations: z
|
|
107
|
+
.union([
|
|
108
|
+
relationships,
|
|
109
|
+
z.array(relationship),
|
|
110
|
+
])
|
|
111
|
+
.transform(v => isArray(v) ? indexBy(v, genRelationshipId) as unknown as z.output<typeof relationships> : v)
|
|
112
|
+
.optional(),
|
|
113
|
+
})
|