@likec4/core 1.19.1 → 1.19.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/builder/index.d.mts +3 -3
  2. package/dist/builder/index.d.ts +3 -3
  3. package/dist/builder/index.js +2 -2
  4. package/dist/compute-view/index.d.mts +2 -2
  5. package/dist/compute-view/index.d.ts +2 -2
  6. package/dist/compute-view/index.js +3 -3
  7. package/dist/index.d.mts +2 -2
  8. package/dist/index.d.ts +2 -2
  9. package/dist/index.js +2 -2
  10. package/dist/model/index.d.mts +8 -3
  11. package/dist/model/index.d.ts +8 -3
  12. package/dist/model/index.js +1 -1
  13. package/dist/shared/{core.BZ9Msq7w.d.mts → core.2I-l5UtM.d.mts} +5 -1
  14. package/dist/shared/{core.CmYMqgha.d.mts → core.BL1oTk7-.d.mts} +13 -3
  15. package/dist/shared/{core.BIfgabEw.d.ts → core.CqXs9Frh.d.ts} +5 -1
  16. package/dist/shared/{core.C05RDLhi.d.ts → core.DyJ41fBO.d.ts} +13 -3
  17. package/dist/shared/{index.DafVOuVd.js → index.KOeaSlL8.js} +332 -208
  18. package/dist/shared/{view.CyZrG8BJ.js → view.DrmaOfjr.js} +3 -0
  19. package/dist/types/index.d.mts +2 -2
  20. package/dist/types/index.d.ts +2 -2
  21. package/dist/types/index.js +1 -1
  22. package/package.json +3 -3
  23. package/src/builder/Builder.view-common.ts +1 -0
  24. package/src/compute-view/deployment-view/__test__/fixture.ts +31 -3
  25. package/src/compute-view/deployment-view/_types.ts +2 -1
  26. package/src/compute-view/deployment-view/compute.ts +4 -36
  27. package/src/compute-view/deployment-view/predicates/index.ts +8 -0
  28. package/src/compute-view/deployment-view/predicates/relation-direct.ts +117 -88
  29. package/src/compute-view/deployment-view/predicates/relation-in-out.ts +16 -8
  30. package/src/compute-view/deployment-view/predicates/relation-incoming.ts +18 -12
  31. package/src/compute-view/deployment-view/predicates/relation-outgoing.ts +16 -9
  32. package/src/compute-view/deployment-view/predicates/relation-where.ts +18 -0
  33. package/src/compute-view/deployment-view/predicates/utils.ts +183 -0
  34. package/src/model/DeploymentElementModel.ts +14 -1
  35. package/src/model/RelationModel.ts +4 -0
  36. package/src/model/connection/deployment/DeploymentConnectionModel.ts +11 -1
  37. package/src/types/deployments.ts +1 -0
  38. package/src/types/expression-v2.ts +12 -0
  39. package/src/types/index.ts +1 -0
  40. package/src/types/operators.ts +5 -5
@@ -1,18 +1,18 @@
1
- import { anyPass } from 'remeda'
1
+ import { anyPass, filter, pipe } from 'remeda'
2
2
  import { invariant } from '../../../errors'
3
+ import type { AnyAux, RelationshipModel } from '../../../model'
3
4
  import { findConnectionsBetween } from '../../../model/connection/deployment'
4
5
  import type { LikeC4DeploymentModel } from '../../../model/DeploymentModel'
5
- import type { RelationshipModel } from '../../../model/RelationModel'
6
- import type { AnyAux } from '../../../model/types'
7
- import { FqnExpr, type RelationExpr } from '../../../types'
8
- import { isAncestor } from '../../../utils/fqn'
6
+ import { type RelationExpr, FqnExpr } from '../../../types'
7
+ import { isAncestor } from '../../../utils'
9
8
  import type { Connection, Elem, PredicateExecutor } from '../_types'
10
9
  import { resolveElements, resolveModelElements } from '../utils'
11
- import { excludeModelRelations, resolveAscendingSiblings } from './relation-direct'
10
+ import { resolveAscendingSiblings } from './relation-direct'
11
+ import { applyPredicate, excludeModelRelations, matchConnection, matchConnections } from './utils'
12
12
 
13
13
  // from visible element incoming to this
14
14
  export const IncomingRelationPredicate: PredicateExecutor<RelationExpr.Incoming> = {
15
- include: ({ expr, model, memory, stage }) => {
15
+ include: ({ expr, model, memory, stage, where }) => {
16
16
  const sources = [...memory.elements]
17
17
 
18
18
  if (FqnExpr.isWildcard(expr.incoming)) {
@@ -21,7 +21,8 @@ export const IncomingRelationPredicate: PredicateExecutor<RelationExpr.Incoming>
21
21
  continue
22
22
  }
23
23
  const targets = [...resolveAscendingSiblings(source)]
24
- stage.addConnections(findConnectionsBetween(source, targets, 'directed'))
24
+ const toInclude = applyPredicate(findConnectionsBetween(source, targets, 'directed'), where)
25
+ stage.addConnections(toInclude)
25
26
  }
26
27
  return stage
27
28
  }
@@ -29,16 +30,17 @@ export const IncomingRelationPredicate: PredicateExecutor<RelationExpr.Incoming>
29
30
 
30
31
  const targets = resolveElements(model, expr.incoming)
31
32
  for (const source of sources) {
32
- stage.addConnections(findConnectionsBetween(source, targets, 'directed'))
33
+ const toInclude = applyPredicate(findConnectionsBetween(source, targets, 'directed'), where)
34
+ stage.addConnections(toInclude)
33
35
  }
34
36
 
35
37
  return stage
36
38
  },
37
- exclude: ({ expr, model, memory, stage }) => {
39
+ exclude: ({ expr, model, memory, stage, where }) => {
38
40
  // Exclude all connections that have model relationshps with the elements
39
41
  if (FqnExpr.isModelRef(expr.incoming)) {
40
42
  const excludedRelations = resolveAllImcomingRelations(model, expr.incoming)
41
- return excludeModelRelations(excludedRelations, { stage, memory })
43
+ return excludeModelRelations(excludedRelations, { stage, memory }, where)
42
44
  }
43
45
  if (FqnExpr.isWildcard(expr.incoming)) {
44
46
  // non-sense
@@ -46,7 +48,11 @@ export const IncomingRelationPredicate: PredicateExecutor<RelationExpr.Incoming>
46
48
  }
47
49
 
48
50
  const isIncoming = filterIncomingConnections(resolveElements(model, expr.incoming))
49
- const toExclude = memory.connections.filter(isIncoming)
51
+ const toExclude = pipe(
52
+ memory.connections,
53
+ filter(isIncoming),
54
+ applyPredicate(where),
55
+ )
50
56
  stage.excludeConnections(toExclude)
51
57
  return stage
52
58
  },
@@ -1,16 +1,17 @@
1
- import { anyPass } from 'remeda'
1
+ import { anyPass, filter, pipe } from 'remeda'
2
2
  import { invariant } from '../../../errors'
3
3
  import type { LikeC4DeploymentModel } from '../../../model'
4
4
  import { findConnection, findConnectionsBetween } from '../../../model/connection/deployment'
5
5
  import type { RelationshipModel } from '../../../model/RelationModel'
6
- import { FqnExpr, type RelationExpr } from '../../../types'
6
+ import { type RelationExpr, FqnExpr } from '../../../types'
7
7
  import { isAncestor } from '../../../utils'
8
8
  import type { Connection, Elem, PredicateExecutor } from '../_types'
9
9
  import { resolveElements, resolveModelElements } from '../utils'
10
- import { excludeModelRelations, resolveAscendingSiblings } from './relation-direct'
10
+ import { resolveAscendingSiblings } from './relation-direct'
11
+ import { applyPredicate, excludeModelRelations } from './utils'
11
12
 
12
13
  export const OutgoingRelationPredicate: PredicateExecutor<RelationExpr.Outgoing> = {
13
- include: ({ expr, model, memory, stage }) => {
14
+ include: ({ expr, model, memory, stage, where }) => {
14
15
  const targets = [...memory.elements]
15
16
 
16
17
  // * -> (to visible elements)
@@ -21,7 +22,8 @@ export const OutgoingRelationPredicate: PredicateExecutor<RelationExpr.Outgoing>
21
22
  continue
22
23
  }
23
24
  for (const source of resolveAscendingSiblings(target)) {
24
- stage.addConnections(findConnection(source, target, 'directed'))
25
+ const toInclude = applyPredicate(findConnection(source, target, 'directed'), where)
26
+ stage.addConnections(toInclude)
25
27
  }
26
28
  }
27
29
  return stage
@@ -30,16 +32,17 @@ export const OutgoingRelationPredicate: PredicateExecutor<RelationExpr.Outgoing>
30
32
 
31
33
  const sources = resolveElements(model, expr.outgoing)
32
34
  for (const source of sources) {
33
- stage.addConnections(findConnectionsBetween(source, targets, 'directed'))
35
+ const toInclude = applyPredicate(findConnectionsBetween(source, targets, 'directed'), where)
36
+ stage.addConnections(toInclude)
34
37
  }
35
38
 
36
39
  return stage
37
40
  },
38
- exclude: ({ expr, model, memory, stage }) => {
41
+ exclude: ({ expr, model, memory, stage, where }) => {
39
42
  // Exclude all connections that have model relationshps with the elements
40
43
  if (FqnExpr.isModelRef(expr.outgoing)) {
41
44
  const excludedRelations = resolveAllOutgoingRelations(model, expr.outgoing)
42
- return excludeModelRelations(excludedRelations, { stage, memory })
45
+ return excludeModelRelations(excludedRelations, { stage, memory }, where)
43
46
  }
44
47
  if (FqnExpr.isWildcard(expr.outgoing)) {
45
48
  // non-sense
@@ -47,7 +50,11 @@ export const OutgoingRelationPredicate: PredicateExecutor<RelationExpr.Outgoing>
47
50
  }
48
51
 
49
52
  const isOutgoing = filterOutgoingConnections(resolveElements(model, expr.outgoing))
50
- const toExclude = memory.connections.filter(isOutgoing)
53
+ const toExclude = pipe(
54
+ memory.connections,
55
+ filter(isOutgoing),
56
+ applyPredicate(where),
57
+ )
51
58
  stage.excludeConnections(toExclude)
52
59
  return stage
53
60
  },
@@ -0,0 +1,18 @@
1
+ import { whereOperatorAsPredicate, type RelationExpr } from '../../../types'
2
+ import type { PredicateExecutor } from '../_types'
3
+ import type { StageExclude, StageInclude } from '../memory'
4
+ import { predicateToPatch } from './utils'
5
+
6
+ // relation matches the condition
7
+ export const WhereRelationPredicate: PredicateExecutor<RelationExpr.Where> = {
8
+ include: ({ expr, model, memory, stage }) => {
9
+ const where = whereOperatorAsPredicate(expr.where.condition)
10
+
11
+ return predicateToPatch('include', { expr: expr.where.expr, model, stage, memory, where }) as StageInclude
12
+ },
13
+ exclude: ({ expr, model, memory, stage }) => {
14
+ const where = whereOperatorAsPredicate(expr.where.condition)
15
+
16
+ return predicateToPatch('exclude', { expr: expr.where.expr, model, stage, memory, where }) as StageExclude
17
+ }
18
+ }
@@ -0,0 +1,183 @@
1
+ import { filter, isArray, map, pick, pipe } from 'remeda'
2
+ import { nonexhaustive } from '../../../errors'
3
+ import type { DeploymentConnectionModel } from '../../../model'
4
+ import type { DeploymentElementModel, DeploymentRelationModel } from '../../../model/DeploymentElementModel'
5
+ import type { RelationshipModel } from '../../../model/RelationModel'
6
+ import type { AnyAux } from '../../../model/types'
7
+ import { type Filterable, type OperatorPredicate, FqnExpr, RelationExpr } from '../../../types'
8
+ import { hasIntersection, intersection } from '../../../utils/set'
9
+ import type { ExcludePredicateCtx, PredicateCtx } from '../_types'
10
+ import type { StageExclude, StageInclude } from '../memory'
11
+ import { DeploymentRefPredicate } from './elements'
12
+ import { DirectRelationPredicate } from './relation-direct'
13
+ import { InOutRelationPredicate } from './relation-in-out'
14
+ import { IncomingRelationPredicate } from './relation-incoming'
15
+ import { OutgoingRelationPredicate } from './relation-outgoing'
16
+ import { WhereRelationPredicate } from './relation-where'
17
+ import { WildcardPredicate } from './wildcard'
18
+
19
+ /**
20
+ * Builds a patch object from an expression
21
+ */
22
+ export function predicateToPatch(
23
+ op: 'include' | 'exclude',
24
+ { expr, where, ...ctx }: PredicateCtx,
25
+ ): StageExclude | StageInclude | undefined {
26
+ switch (true) {
27
+ case FqnExpr.isModelRef(expr):
28
+ // Ignore model refs in deployment view
29
+ return undefined
30
+ case FqnExpr.isDeploymentRef(expr):
31
+ return DeploymentRefPredicate[op]({ ...ctx, expr, where } as any)
32
+ case FqnExpr.isWildcard(expr):
33
+ return WildcardPredicate[op]({ ...ctx, expr, where } as any)
34
+ case RelationExpr.isDirect(expr):
35
+ return DirectRelationPredicate[op]({ ...ctx, expr, where } as any)
36
+ case RelationExpr.isInOut(expr):
37
+ return InOutRelationPredicate[op]({ ...ctx, expr, where } as any)
38
+ case RelationExpr.isOutgoing(expr):
39
+ return OutgoingRelationPredicate[op]({ ...ctx, expr, where } as any)
40
+ case RelationExpr.isIncoming(expr):
41
+ return IncomingRelationPredicate[op]({ ...ctx, expr, where } as any)
42
+ case RelationExpr.isWhere(expr):
43
+ return WhereRelationPredicate[op]({ ...ctx, expr, where } as any)
44
+ default:
45
+ nonexhaustive(expr)
46
+ }
47
+ }
48
+
49
+ export function excludeModelRelations(
50
+ relationsToExclude: ReadonlySet<RelationshipModel<AnyAux>>,
51
+ { stage, memory }: Pick<ExcludePredicateCtx, 'stage' | 'memory'>,
52
+ where: OperatorPredicate<Filterable> | null,
53
+ // Optional filter to scope the connections to exclude
54
+ filterConnections: (c: DeploymentConnectionModel) => boolean = () => true,
55
+ ): StageExclude {
56
+ if (relationsToExclude.size === 0) {
57
+ return stage
58
+ }
59
+ const toExclude = pipe(
60
+ memory.connections,
61
+ filter(c => filterConnections(c)),
62
+ // Find connections that have at least one relation in common with the excluded relations
63
+ filter(c => hasIntersection(c.relations.model, relationsToExclude)),
64
+ map(c =>
65
+ c.update({
66
+ deployment: null,
67
+ model: intersection(c.relations.model, relationsToExclude),
68
+ })
69
+ ),
70
+ applyPredicate(where),
71
+ filter(c => c.nonEmpty()),
72
+ )
73
+ if (toExclude.length === 0) {
74
+ return stage
75
+ }
76
+ return stage.excludeConnections(toExclude)
77
+ }
78
+
79
+ export function matchConnection<M extends AnyAux>(
80
+ c: DeploymentConnectionModel<M>,
81
+ where: OperatorPredicate<Filterable> | null,
82
+ ): boolean {
83
+ return applyPredicate(c, where).nonEmpty()
84
+ }
85
+
86
+ /**
87
+ * Filters relations of the provided connections using the provided predicate.
88
+ * And copy of the connection with the filtered relations will be created.
89
+ * Connections left without relations are removed from resulting collection.
90
+ *
91
+ * @param c The connection to apply the predicate to
92
+ * @param where The predicate
93
+ * @returns A copy of the connection with the filtered relations
94
+ */
95
+ export function applyPredicate<M extends AnyAux>(
96
+ c: readonly DeploymentConnectionModel<M>[],
97
+ where: OperatorPredicate<Filterable> | null,
98
+ ): readonly DeploymentConnectionModel<M>[]
99
+ /**
100
+ * Filters relations of the connection using the provided predicate.
101
+ *
102
+ * @param c The connection to apply the predicate to
103
+ * @param where The predicate
104
+ * @returns A deep copy of the original collection with the filtered relations
105
+ */
106
+ export function applyPredicate<M extends AnyAux>(
107
+ c: DeploymentConnectionModel<M>,
108
+ where: OperatorPredicate<Filterable> | null,
109
+ ): DeploymentConnectionModel<M>
110
+ /**
111
+ * Creates a function that filters relations of the provided connections using the provided predicate.
112
+ * Connections left without relations are removed from resulting collection.
113
+ *
114
+ * @param c The connection to apply the predicate to
115
+ * @param where The predicate
116
+ * @returns A function to create a filtered copy of connections
117
+ */
118
+ export function applyPredicate<M extends AnyAux>(
119
+ where: OperatorPredicate<Filterable> | null,
120
+ ): (data: readonly DeploymentConnectionModel<M>[]) => readonly DeploymentConnectionModel<M>[]
121
+ export function applyPredicate<M extends AnyAux>(
122
+ ...args:
123
+ | [readonly DeploymentConnectionModel<M>[], OperatorPredicate<Filterable> | null]
124
+ | [OperatorPredicate<Filterable> | null]
125
+ | [c: DeploymentConnectionModel<M>, OperatorPredicate<Filterable> | null]
126
+ ):
127
+ | DeploymentConnectionModel<M>
128
+ | readonly DeploymentConnectionModel<M>[]
129
+ | ((data: readonly DeploymentConnectionModel<M>[]) => readonly DeploymentConnectionModel<M>[])
130
+ {
131
+ if (args.length === 1) {
132
+ return x => applyPredicate(x, args[0])
133
+ }
134
+
135
+ const [c, where] = args
136
+ if (where === null) {
137
+ return c
138
+ }
139
+
140
+ if (isArray(c)) {
141
+ return c
142
+ .map(x => applyPredicate(x, where))
143
+ .filter(x => x.nonEmpty())
144
+ }
145
+
146
+ const map = toFilterableRelation(c.source, c.target)
147
+ return c.update({
148
+ model: new Set([...c.relations.model.values()].filter(r => where(map(r)))),
149
+ deployment: new Set([...c.relations.deployment.values()].filter(r => where(map(r)))),
150
+ })
151
+ }
152
+
153
+ export function matchConnections<M extends AnyAux>(
154
+ connections: readonly DeploymentConnectionModel<M>[],
155
+ where: OperatorPredicate<Filterable> | null,
156
+ ): readonly DeploymentConnectionModel[] {
157
+ if (!where) {
158
+ return connections
159
+ }
160
+
161
+ return pipe(
162
+ connections,
163
+ filter(c => matchConnection(c, where)),
164
+ )
165
+ }
166
+
167
+ function elementToFilterable<M extends AnyAux>(element: DeploymentElementModel<M>) {
168
+ return pick(element, ['tags', 'kind'])
169
+ }
170
+
171
+ function toFilterableRelation<M extends AnyAux>(
172
+ source: DeploymentElementModel<M>,
173
+ target: DeploymentElementModel<M>,
174
+ ) {
175
+ return (
176
+ relation: RelationshipModel<M> | DeploymentRelationModel<M>,
177
+ ) => ({
178
+ tags: relation.tags,
179
+ kind: relation.kind,
180
+ source: elementToFilterable(source),
181
+ target: elementToFilterable(target),
182
+ })
183
+ }
@@ -11,6 +11,7 @@ import {
11
11
  type ElementShape as C4ElementShape,
12
12
  type IteratorLike,
13
13
  type Link,
14
+ type RelationshipKind,
14
15
  type Tag,
15
16
  type Tag as C4Tag,
16
17
  type ThemeColor,
@@ -62,6 +63,10 @@ abstract class AbstractDeploymentElementModel<M extends AnyAux = AnyAux> {
62
63
  return this.$node.tags ?? []
63
64
  }
64
65
 
66
+ get kind(): DeploymentNodeKind | string | null {
67
+ return this.$node.kind ?? null
68
+ }
69
+
65
70
  get description(): string | null {
66
71
  return this.$node.description ?? null
67
72
  }
@@ -223,7 +228,7 @@ export class DeploymentNodeModel<M extends AnyAux = AnyAux> extends AbstractDepl
223
228
  return this.$model.parent(this)
224
229
  }
225
230
 
226
- get kind(): DeploymentNodeKind {
231
+ override get kind(): DeploymentNodeKind {
227
232
  return this.$node.kind
228
233
  }
229
234
 
@@ -367,6 +372,10 @@ export class DeployedInstanceModel<M extends AnyAux = AnyAux> extends AbstractDe
367
372
  return this.$instance.tags ?? []
368
373
  }
369
374
 
375
+ override get kind(): string | null {
376
+ return this.$instance.kind ?? null
377
+ }
378
+
370
379
  override get description(): string | null {
371
380
  return this.$instance.description ?? this.element.description
372
381
  }
@@ -514,6 +523,10 @@ export class DeploymentRelationModel<M extends AnyAux = AnyAux> {
514
523
  return this.$relationship.tags ?? []
515
524
  }
516
525
 
526
+ get kind(): RelationshipKind | null {
527
+ return this.$relationship.kind ?? null
528
+ }
529
+
517
530
  get navigateTo(): LikeC4ViewModel<M> | null {
518
531
  return this.$relationship.navigateTo ? this.$model.$model.view(this.$relationship.navigateTo) : null
519
532
  }
@@ -67,6 +67,10 @@ export class RelationshipModel<M extends AnyAux = AnyAux> {
67
67
  return this.$relationship.tags ?? []
68
68
  }
69
69
 
70
+ get kind(): string | null {
71
+ return this.$relationship.kind ?? null
72
+ }
73
+
70
74
  get links(): ReadonlyArray<Link> {
71
75
  return this.$relationship.links ?? []
72
76
  }
@@ -110,11 +110,21 @@ export class DeploymentConnectionModel<M extends AnyAux = AnyAux>
110
110
  yield* this.relations.deployment
111
111
  }
112
112
 
113
+ /**
114
+ * Merge with another connections, if it has the same source and target.
115
+ * Returns new connection with union of relationships.
116
+ */
117
+ public mergeWith(others: DeploymentConnectionModel<M>[]): DeploymentConnectionModel<M>
113
118
  /**
114
119
  * Merge with another connection, if it has the same source and target.
115
120
  * Returns new connection with union of relationships.
116
121
  */
117
- public mergeWith(other: DeploymentConnectionModel<M>) {
122
+ public mergeWith(other: DeploymentConnectionModel<M>): DeploymentConnectionModel<M>
123
+ public mergeWith(other: DeploymentConnectionModel<M> | DeploymentConnectionModel<M>[]): DeploymentConnectionModel<M> {
124
+ if (Array.isArray(other)) {
125
+ return other.reduce((acc, o) => acc.mergeWith(o), this)
126
+ }
127
+
118
128
  invariant(this.source.id === other.source.id, 'Cannot merge connections with different sources')
119
129
  invariant(this.target.id === other.target.id, 'Cannot merge connections with different targets')
120
130
  return new DeploymentConnectionModel(
@@ -42,6 +42,7 @@ export interface DeployedInstance {
42
42
  readonly title?: string
43
43
  readonly description?: string | null
44
44
  readonly technology?: string | null
45
+ readonly kind?: string | null // TODO: ensure the property is set
45
46
  readonly tags?: NonEmptyArray<Tag> | null
46
47
  readonly links?: NonEmptyArray<Link> | null
47
48
  readonly style?: DeploymentElementStyle
@@ -2,6 +2,7 @@ import { invariant } from '../errors'
2
2
  import type { ExclusiveUnion } from './_common'
3
3
  import type { DeploymentRef as DeploymentModelRef, PredicateSelector } from './deployments'
4
4
  import type { Fqn } from './element'
5
+ import type { WhereOperator } from './operators'
5
6
 
6
7
  export namespace FqnRef {
7
8
  /**
@@ -131,6 +132,15 @@ export namespace RelationExpr {
131
132
  export const isInOut = (expr: ExpressionV2): expr is InOut => {
132
133
  return 'inout' in expr
133
134
  }
135
+ export type Where<D = Fqn, M = Fqn> = {
136
+ where: {
137
+ expr: ExpressionV2<D, M>
138
+ condition: WhereOperator<string, string>
139
+ }
140
+ }
141
+ export const isWhere = (expr: ExpressionV2): expr is Where => {
142
+ return 'where' in expr
143
+ }
134
144
  }
135
145
 
136
146
  export type RelationExpr<D = Fqn, M = Fqn> = ExclusiveUnion<{
@@ -138,6 +148,7 @@ export type RelationExpr<D = Fqn, M = Fqn> = ExclusiveUnion<{
138
148
  Incoming: RelationExpr.Incoming<D, M>
139
149
  Outgoing: RelationExpr.Outgoing<D, M>
140
150
  InOut: RelationExpr.InOut<D, M>
151
+ Where: RelationExpr.Where<D, M>
141
152
  }>
142
153
 
143
154
  /**
@@ -154,6 +165,7 @@ export type ExpressionV2<D = Fqn, M = Fqn> = ExclusiveUnion<{
154
165
  Incoming: RelationExpr.Incoming<D, M>
155
166
  Outgoing: RelationExpr.Outgoing<D, M>
156
167
  InOut: RelationExpr.InOut<D, M>
168
+ RelationPredicateOrWhere: RelationExpr.Where<D, M>
157
169
  }>
158
170
 
159
171
  export namespace ExpressionV2 {
@@ -121,6 +121,7 @@ export type {
121
121
  Filterable,
122
122
  KindEqual,
123
123
  NotOperator,
124
+ OperatorPredicate,
124
125
  OrOperator,
125
126
  TagEqual,
126
127
  WhereOperator,
@@ -61,19 +61,19 @@ export type WhereOperator<Tag, Kind> =
61
61
  | OrOperator<Tag, Kind>
62
62
 
63
63
  export type Filterable<
64
- FTag extends string = string,
65
- FKind extends string = string
64
+ FTag extends string | null = string | null,
65
+ FKind extends string | null = string | null,
66
66
  > = {
67
67
  tags?: readonly FTag[] | null
68
68
  kind?: FKind
69
69
  }
70
70
 
71
- type OperatorPredicate<V extends Filterable> = (value: V) => boolean
71
+ export type OperatorPredicate<V extends Filterable> = (value: V) => boolean
72
72
 
73
73
  export function whereOperatorAsPredicate<
74
74
  FTag extends string = string,
75
- FKind extends string = string
76
- >(operator: WhereOperator<FTag, FKind>): OperatorPredicate<Filterable<FTag, FKind>> {
75
+ FKind extends string = string,
76
+ >(operator: WhereOperator<FTag, FKind>): OperatorPredicate<Filterable> {
77
77
  switch (true) {
78
78
  case isTagEqual(operator): {
79
79
  if ('eq' in operator.tag) {