@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.
@@ -0,0 +1,218 @@
1
+ import * as z from 'zod/v4'
2
+ import * as common from './common'
3
+
4
+ export const refModel = z.strictObject({
5
+ model: common.fqn,
6
+ project: z.string().optional(),
7
+ })
8
+
9
+ export const refDeployment = z.strictObject({
10
+ deployment: common.fqn,
11
+ element: common.fqn.nullish(),
12
+ })
13
+
14
+ export const fqnRef = z.union([
15
+ refModel,
16
+ refDeployment,
17
+ ])
18
+
19
+ export const selector = z.literal(['children', 'expanded', 'descendants'])
20
+
21
+ /**
22
+ * Accepts a value directly (shorthand for equality), or an explicit {eq: value} / {neq: value}.
23
+ * Replicates the EqualOperator from the core.
24
+ */
25
+ function equalOp<T extends z.ZodType>(schema: T) {
26
+ return z.union([
27
+ schema.transform((value) => ({ eq: value })),
28
+ z.object({ eq: schema }),
29
+ z.object({ neq: schema }),
30
+ ])
31
+ }
32
+
33
+ export const whereTag = z.object({ tag: equalOp(common.tag) })
34
+ export const whereKind = z.object({ kind: equalOp(common.kind) })
35
+ export const whereMetadata = z.object({
36
+ metadata: z.object({
37
+ key: z.string(),
38
+ value: equalOp(z.string()).optional(),
39
+ }),
40
+ })
41
+
42
+ export const whereParticipant = z.object({
43
+ participant: z.literal(['source', 'target']),
44
+ operator: z.union([
45
+ whereTag,
46
+ whereKind,
47
+ whereMetadata,
48
+ ]),
49
+ })
50
+
51
+ export const whereAnd = z.object({
52
+ get and(): z.ZodArray<typeof whereOperator> {
53
+ return z.array(whereOperator)
54
+ },
55
+ })
56
+
57
+ export const whereNot = z.object({
58
+ get not() {
59
+ return whereOperator
60
+ },
61
+ })
62
+
63
+ export const whereOr = z.object({
64
+ get or(): z.ZodArray<typeof whereOperator> {
65
+ return z.array(whereOperator)
66
+ },
67
+ })
68
+
69
+ export const whereOperator = z.union([
70
+ whereTag,
71
+ whereKind,
72
+ whereMetadata,
73
+ whereParticipant,
74
+ whereAnd,
75
+ whereOr,
76
+ whereNot,
77
+ ])
78
+
79
+ // ---- ModelFqnExpr base variants ----
80
+ export const wildcardExpr = z.object({ wildcard: z.literal(true) })
81
+ export const refExpr = z.object({
82
+ ref: fqnRef,
83
+ selector: selector.optional(),
84
+ })
85
+ export const elementKindExpr = z.object({
86
+ elementKind: common.kind,
87
+ isEqual: z.boolean(),
88
+ })
89
+ export const elementTagExpr = z.object({
90
+ elementTag: common.tag,
91
+ isEqual: z.boolean(),
92
+ })
93
+
94
+ export const fqnExpr = z.union([
95
+ wildcardExpr,
96
+ refExpr,
97
+ elementKindExpr,
98
+ elementTagExpr,
99
+ ])
100
+
101
+ const fqnExprWhere = z.strictObject({
102
+ where: z.strictObject({
103
+ expr: fqnExpr,
104
+ condition: whereOperator,
105
+ }),
106
+ })
107
+
108
+ export const fqnExprOrWhere = z.union([
109
+ fqnExpr,
110
+ fqnExprWhere,
111
+ ])
112
+
113
+ /**
114
+ * Common custom properties that apply to both elements and relations
115
+ */
116
+ const commonCustomProperties = z.object({
117
+ title: z.string(),
118
+ description: common.markdownOrString,
119
+ technology: z.string(),
120
+ notation: z.string(),
121
+ notes: common.markdownOrString,
122
+ navigateTo: common.viewId,
123
+ color: common.color,
124
+ })
125
+
126
+ const customElementProperties = z.object({
127
+ ...commonCustomProperties.shape,
128
+ shape: common.shape,
129
+ icon: common.icon,
130
+ iconColor: common.color,
131
+ iconSize: common.size,
132
+ iconPosition: common.iconPosition,
133
+ border: common.border,
134
+ opacity: common.opacity,
135
+ multiple: z.boolean(),
136
+ size: common.size,
137
+ padding: common.size,
138
+ textSize: common.size,
139
+ }).partial()
140
+
141
+ const customRelationProperties = z.object({
142
+ ...commonCustomProperties.shape,
143
+ line: common.line,
144
+ head: common.arrow,
145
+ tail: common.arrow,
146
+ }).partial()
147
+
148
+ export const fqnExprCustom = z.strictObject({
149
+ custom: customElementProperties.extend({
150
+ expr: fqnExprOrWhere,
151
+ }),
152
+ })
153
+
154
+ export const fqnExprAny = z.union([
155
+ fqnExprOrWhere,
156
+ fqnExprCustom,
157
+ ])
158
+
159
+ // ---- ModelRelationExpr base variants ----
160
+ export const directRelationExpr = z.object({
161
+ source: fqnExpr,
162
+ target: fqnExpr,
163
+ isBidirectional: z.boolean().optional(),
164
+ })
165
+ export const incomingRelationExpr = z.object({ incoming: fqnExpr })
166
+ export const outgoingRelationExpr = z.object({ outgoing: fqnExpr })
167
+ export const inoutRelationExpr = z.object({ inout: fqnExpr })
168
+
169
+ export const relationExpr = z.union([
170
+ directRelationExpr,
171
+ incomingRelationExpr,
172
+ outgoingRelationExpr,
173
+ inoutRelationExpr,
174
+ ])
175
+
176
+ export const relationExprOrWhere = z.union([
177
+ relationExpr,
178
+ z.strictObject({
179
+ where: z.strictObject({
180
+ expr: relationExpr,
181
+ condition: whereOperator,
182
+ }),
183
+ }),
184
+ ])
185
+
186
+ export const relationExprCustom = z.strictObject({
187
+ customRelation: customRelationProperties.extend({
188
+ expr: relationExprOrWhere,
189
+ }),
190
+ })
191
+
192
+ export const relationExprAny = z.union([
193
+ relationExprOrWhere,
194
+ relationExprCustom,
195
+ ])
196
+
197
+ // Where expression - wraps either a fqn or relation base expression with a condition
198
+ export const whereExpr = z.object({
199
+ where: z.object({
200
+ expr: z.union([
201
+ fqnExpr,
202
+ relationExpr,
203
+ ]),
204
+ condition: whereOperator,
205
+ }),
206
+ })
207
+
208
+ /**
209
+ * Full model expression, union of all fqn and relation expression variants.
210
+ * Replicates ModelExpression from the core.
211
+ */
212
+ export const expression = z.union([
213
+ fqnExpr,
214
+ fqnExprCustom,
215
+ relationExpr,
216
+ relationExprCustom,
217
+ whereExpr,
218
+ ])
@@ -0,0 +1,83 @@
1
+ import type z from 'zod/v4'
2
+ import * as common from './common'
3
+ import * as deployment from './deployment'
4
+ import * as expr from './expression'
5
+ import { likec4data } from './likec4data'
6
+ import * as model from './model'
7
+ import * as specification from './specification'
8
+ import * as views from './views'
9
+
10
+ export const schemas = {
11
+ common,
12
+ expr,
13
+ specification,
14
+ model,
15
+ deployment,
16
+ views,
17
+ likec4data,
18
+ }
19
+
20
+ export namespace schemas {
21
+ export namespace common {
22
+ }
23
+
24
+ export namespace expr {
25
+ export type Input = z.input<typeof schemas.expr.expression>
26
+ export type Data = z.output<typeof schemas.expr.expression>
27
+ }
28
+
29
+ export namespace specification {
30
+ export type Input = z.input<typeof schemas.specification.schema>
31
+ }
32
+
33
+ export namespace model {
34
+ export type Input = z.input<typeof schemas.model.schema>
35
+
36
+ export namespace element {
37
+ export type Input = z.input<typeof schemas.model.element>
38
+ export type Data = z.output<typeof schemas.model.element>
39
+ }
40
+
41
+ export namespace relationship {
42
+ export type Input = z.input<typeof schemas.model.relationship>
43
+ export type Data = z.output<typeof schemas.model.relationship>
44
+ }
45
+ }
46
+
47
+ export namespace deployment {
48
+ export type Input = z.input<typeof schemas.deployment.schema>
49
+ export type Data = z.output<typeof schemas.deployment.schema>
50
+
51
+ export namespace node {
52
+ export type Input = z.input<typeof schemas.deployment.node>
53
+ export type Data = z.output<typeof schemas.deployment.node>
54
+ }
55
+
56
+ export namespace instance {
57
+ export type Input = z.input<typeof schemas.deployment.instance>
58
+ export type Data = z.output<typeof schemas.deployment.instance>
59
+ }
60
+
61
+ export namespace element {
62
+ export type Input = z.input<typeof schemas.deployment.element>
63
+ export type Data = z.output<typeof schemas.deployment.element>
64
+ }
65
+
66
+ export namespace relationship {
67
+ export type Input = z.input<typeof schemas.deployment.relationship>
68
+ export type Data = z.output<typeof schemas.deployment.relationship>
69
+ }
70
+ }
71
+
72
+ export namespace views {
73
+ export namespace elementView {
74
+ export type Input = z.input<typeof schemas.views.elementView>
75
+ export type Data = z.output<typeof schemas.views.elementView>
76
+ }
77
+ }
78
+
79
+ export namespace likec4data {
80
+ export type Input = z.input<typeof schemas.likec4data>
81
+ export type Data = z.output<typeof schemas.likec4data>
82
+ }
83
+ }
@@ -0,0 +1,76 @@
1
+ import { LikeC4StylesConfigSchema } from '@likec4/config'
2
+ import { produce } from 'immer'
3
+ import { entries, isDeepEqual, isEmptyish, mapValues } from 'remeda'
4
+ import * as z from 'zod/v4'
5
+ import * as deployment from './deployment'
6
+ import * as model from './model'
7
+ import * as specification from './specification'
8
+ import { views } from './views'
9
+
10
+ const likec4dataPreTransform = z
11
+ .object({
12
+ ...model.schema.shape,
13
+ views: views,
14
+ project: z.object({
15
+ id: z.string(),
16
+ styles: LikeC4StylesConfigSchema.nullish(),
17
+ }),
18
+ deployment: deployment.schema,
19
+ deployments: deployment.schema,
20
+ specification: specification.schema,
21
+ })
22
+ .partial()
23
+ .readonly()
24
+
25
+ export const likec4data = likec4dataPreTransform.transform(normalizeStyles)
26
+
27
+ type LikeC4Data = z.output<typeof likec4dataPreTransform>
28
+ type ElementData = z.output<typeof model.element>
29
+ type ElementSpecificationData = z.output<typeof specification.element>
30
+
31
+ function normalizeStyles(data: LikeC4Data): LikeC4Data {
32
+ const elementSpecs = data.specification?.elements
33
+ if (!isEmptyish(elementSpecs) && !isEmptyish(data.elements)) {
34
+ data = {
35
+ ...data,
36
+ elements: mapValues(data.elements, element => normalizeElementStyles(element, elementSpecs)),
37
+ }
38
+ }
39
+
40
+ return data
41
+ }
42
+
43
+ function normalizeElementStyles(element: ElementData, specs: Record<string, ElementSpecificationData>): ElementData {
44
+ const spec = specs[element.kind]
45
+ if (!spec) {
46
+ return element
47
+ }
48
+ const specStyle = spec.style ?? {}
49
+ return produce(element, draft => {
50
+ for (
51
+ // Remove properties that are the same as the specification
52
+ const key of [
53
+ 'description',
54
+ 'technology',
55
+ 'title',
56
+ 'tags',
57
+ 'summary',
58
+ ] satisfies (keyof ElementSpecificationData)[]
59
+ ) {
60
+ const specValue = spec[key]
61
+ const elementValue = element[key]
62
+ if (isDeepEqual(specValue, elementValue)) {
63
+ delete draft[key]
64
+ }
65
+ }
66
+ if (!element.style) {
67
+ return
68
+ }
69
+ // Remove style properties that are the same as the specification
70
+ for (const [key, value] of entries(specStyle)) {
71
+ if (isDeepEqual(element.style?.[key], value)) {
72
+ delete draft.style![key]
73
+ }
74
+ }
75
+ })
76
+ }
@@ -0,0 +1,127 @@
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
+
9
+ // export const elementTree = z.object({
10
+ // id: common.fqn.optional(),
11
+ // name: common.id.optional(),
12
+ // kind: common.kind,
13
+ // style: common.style.optional(),
14
+ // /**
15
+ // * Allowing shape, color and icon to be defined at the element level for convenience,
16
+ // * they will be moved to the style property during parsing
17
+ // * (and will override properties)
18
+ // */
19
+ // shape: common.shape.optional(),
20
+ // color: common.color.optional(),
21
+ // icon: common.icon.optional(),
22
+ // get children(): z.ZodOptional<z.ZodArray<typeof elementTree>> {
23
+ // return z.array(elementTree).optional()
24
+ // },
25
+ // })
26
+ // .readonly()
27
+
28
+ /**
29
+ * Replicates the {@link Element} from the core,
30
+ * less strict, as the generator should be able to handle missing fields and provide defaults.
31
+ */
32
+ export const element = z
33
+ .object({
34
+ ...common.props.shape,
35
+ id: common.fqn,
36
+ kind: common.kind,
37
+ style: common.style.optional(),
38
+ /**
39
+ * Allowing shape, color and icon to be defined at the element level for convenience,
40
+ * they will be moved to the style property during parsing
41
+ * (and will override properties)
42
+ */
43
+ shape: common.shape.optional(),
44
+ color: common.color.optional(),
45
+ icon: common.icon.optional(),
46
+ })
47
+ .transform(value => {
48
+ let { shape, color, icon, ...rest } = value
49
+ if (shape || color || icon) {
50
+ rest = produce(rest, draft => {
51
+ draft.style = rest.style || {}
52
+ draft.style.shape = shape ?? rest.style?.shape
53
+ draft.style.color = color ?? rest.style?.color
54
+ draft.style.icon = icon ?? rest.style?.icon
55
+ })
56
+ }
57
+ return pickBy(rest, isNonNullish)
58
+ })
59
+ .readonly()
60
+
61
+ const relationshipEndpoint = z.union([
62
+ common.fqn,
63
+ z.strictObject({
64
+ model: common.fqn,
65
+ }),
66
+ ]).transform(v => (typeof v === 'string' ? { model: v } : v))
67
+
68
+ const relationshipId = common.id.transform(value => value as unknown as RelationId)
69
+
70
+ export const relationship = z.object({
71
+ ...common.props.shape,
72
+ id: relationshipId.optional(),
73
+ title: z.string().nullish(),
74
+ source: relationshipEndpoint,
75
+ target: relationshipEndpoint,
76
+ navigateTo: common.viewId.nullish(),
77
+ color: common.color.nullish(),
78
+ kind: common.kind.nullish(),
79
+ line: common.line.nullish(),
80
+ head: common.arrow.nullish(),
81
+ tail: common.arrow.nullish(),
82
+ })
83
+ .transform(pickBy(isNonNullish))
84
+ .readonly()
85
+
86
+ // ============ Top-Level Schema ============
87
+
88
+ const elements = z.record(common.fqn, element)
89
+ const relationships = z.record(relationshipId, relationship)
90
+
91
+ const genRelationshipId = (r: z.output<typeof relationship>): RelationId => r.id ?? randomString(8) as RelationId
92
+
93
+ export const schema = z
94
+ .object({
95
+ elements: z
96
+ .union([
97
+ elements,
98
+ z.array(element),
99
+ ])
100
+ .transform(v => isArray(v) ? indexBy(v, prop('id')) as unknown as z.output<typeof elements> : v)
101
+ .optional(),
102
+ relations: z
103
+ .union([
104
+ relationships,
105
+ z.array(relationship),
106
+ ])
107
+ .transform(v => isArray(v) ? indexBy(v, genRelationshipId) as unknown as z.output<typeof relationships> : v)
108
+ .optional(),
109
+ // isArray(v) ?
110
+ // indexBy(
111
+ // v,
112
+ // (r, idx) => r.id as string ?? stringHash(`${r.source.model}, ${r.target.model}, ${r.kind ?? ''}, ${idx}`),
113
+ // ) :
114
+ // v
115
+ // )
116
+ // .optional(),
117
+ // views: z.union([
118
+ // z.record(viewId, ElementViewSchema),
119
+ // z.array(ElementViewSchema)
120
+ // .transform(indexBy(v => v.id as string)),
121
+ // ]),
122
+ // project: z.object({
123
+ // id: z.string(),
124
+ // styles: LikeC4StylesConfigSchema.nullish(),
125
+ // }),
126
+ // specification: SpecificationSchema,
127
+ })
@@ -0,0 +1,83 @@
1
+ import type {
2
+ ElementSpecification,
3
+ RelationshipSpecification,
4
+ } from '@likec4/core/types'
5
+ import { isNonNullish, mapToObj, pickBy } from 'remeda'
6
+ import * as z from 'zod/v4'
7
+ import * as common from './common'
8
+
9
+ /**
10
+ * Replicates the {@link ElementSpecification} from the core,
11
+ * less strict, as the generator should be able to handle missing fields and provide defaults.
12
+ */
13
+ export const element = z
14
+ .object({
15
+ tags: common.tags.nullable(),
16
+ title: z.string().nullable(),
17
+ summary: common.markdownOrString.nullable(),
18
+ description: common.markdownOrString.nullable(),
19
+ technology: z.string().nullable(),
20
+ notation: z.string().nullable(),
21
+ links: common.links.nullable(),
22
+ style: common.style.nullable(),
23
+ })
24
+ .partial()
25
+ .transform(pickBy(isNonNullish))
26
+
27
+ /**
28
+ * Replicates the {@link RelationshipSpecification} from the core,
29
+ * less strict, as the generator should be able to handle missing fields and provide defaults.
30
+ */
31
+ export const relationship = z
32
+ .object({
33
+ technology: z.string().nullable(),
34
+ notation: z.string().nullable(),
35
+ color: common.color.nullable(),
36
+ line: common.line.nullable(),
37
+ head: common.arrow.nullable(),
38
+ tail: common.arrow.nullable(),
39
+ })
40
+ .partial()
41
+ .transform(pickBy(isNonNullish))
42
+
43
+ export const tagSpec = z
44
+ .object({
45
+ color: z
46
+ .string()
47
+ .regex(/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/)
48
+ .optional()
49
+ .catch(undefined),
50
+ })
51
+ .partial()
52
+ .transform(pickBy(isNonNullish))
53
+
54
+ export const schema = z
55
+ .object({
56
+ /**
57
+ * Element kinds specifications, where key is the kind name
58
+ */
59
+ elements: z.record(common.kind, element),
60
+
61
+ /**
62
+ * Element kinds specifications, where key is the kind name
63
+ */
64
+ deployments: z.record(common.kind, element),
65
+
66
+ /**
67
+ * Relationship kinds specifications, where key is the kind name
68
+ */
69
+ relationships: z.record(common.kind, relationship),
70
+
71
+ /**
72
+ * Tag specifications, where key is the tag name
73
+ * Or an array of tags, if no additional properties are needed for tags (like color)
74
+ */
75
+ tags: z.union([
76
+ z.record(
77
+ common.tag,
78
+ tagSpec,
79
+ ),
80
+ z.array(common.tag).transform(tags => mapToObj(tags, t => [t, {} as Record<string, any>] as const)),
81
+ ]),
82
+ })
83
+ .partial()