@likec4/core 1.1.1 → 1.2.1
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/colors/element.d.ts +1 -0
- package/dist/colors/index.d.ts +1 -0
- package/dist/colors/relationships.d.ts +1 -0
- package/dist/errors/errors.spec.d.ts +2 -0
- package/dist/errors/errors.spec.js +69 -0
- package/dist/errors/index.d.ts +1 -0
- package/dist/errors/index.js +7 -1
- package/dist/index.d.ts +1 -0
- package/dist/types/_common.d.ts +1 -0
- package/dist/types/element.d.ts +1 -0
- package/dist/types/expression.d.ts +1 -0
- package/dist/types/index.d.ts +1 -2
- package/dist/types/index.js +2 -3
- package/dist/types/model.d.ts +3 -3
- package/dist/types/opaque.d.ts +1 -0
- package/dist/types/relation.d.ts +1 -0
- package/dist/types/theme.d.ts +1 -0
- package/dist/types/view.d.ts +133 -8
- package/dist/types/view.js +31 -3
- package/dist/utils/fqn.d.ts +1 -0
- package/dist/utils/fqn.spec.d.ts +2 -0
- package/dist/utils/fqn.spec.js +82 -0
- package/dist/utils/guards.d.ts +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/promises.d.ts +1 -0
- package/dist/utils/relations.d.ts +1 -0
- package/dist/utils/relations.spec.d.ts +2 -0
- package/dist/utils/relations.spec.js +210 -0
- package/package.json +6 -6
- package/src/colors/element.ts +80 -0
- package/src/colors/index.ts +12 -0
- package/src/colors/relationships.ts +54 -0
- package/src/errors/errors.spec.ts +88 -0
- package/src/errors/index.ts +120 -0
- package/src/index.ts +9 -0
- package/src/reset.d.ts +2 -0
- package/src/types/_common.ts +5 -0
- package/src/types/element.ts +56 -0
- package/src/types/expression.ts +140 -0
- package/src/types/index.ts +10 -0
- package/src/types/model.ts +15 -0
- package/src/types/opaque.ts +108 -0
- package/src/types/relation.ts +38 -0
- package/src/types/theme.ts +46 -0
- package/src/types/view.ts +263 -0
- package/src/utils/fqn.spec.ts +105 -0
- package/src/utils/fqn.ts +112 -0
- package/src/utils/guards.ts +10 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/promises.ts +9 -0
- package/src/utils/relations.spec.ts +268 -0
- package/src/utils/relations.ts +104 -0
- package/dist/types/computed-view.d.ts +0 -52
- package/dist/types/computed-view.js +0 -1
- package/dist/types/diagram.d.ts +0 -40
- package/dist/types/diagram.js +0 -1
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { IconUrl } from './_common'
|
|
2
|
+
import type { BorderStyle, ElementKind, ElementShape, Fqn, Tag } from './element'
|
|
3
|
+
import type { ThemeColor } from './theme'
|
|
4
|
+
import type { ViewID } from './view'
|
|
5
|
+
|
|
6
|
+
interface BaseExpr {
|
|
7
|
+
element?: never
|
|
8
|
+
custom?: never
|
|
9
|
+
expanded?: never
|
|
10
|
+
elementKind?: never
|
|
11
|
+
elementTag?: never
|
|
12
|
+
isEqual?: never
|
|
13
|
+
isDescedants?: never
|
|
14
|
+
wildcard?: never
|
|
15
|
+
source?: never
|
|
16
|
+
target?: never
|
|
17
|
+
inout?: never
|
|
18
|
+
incoming?: never
|
|
19
|
+
outgoing?: never
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ElementRefExpr extends Omit<BaseExpr, 'element' | 'isDescedants'> {
|
|
23
|
+
element: Fqn
|
|
24
|
+
isDescedants?: boolean
|
|
25
|
+
}
|
|
26
|
+
export function isElementRef(expr: Expression): expr is ElementRefExpr {
|
|
27
|
+
return 'element' in expr
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ExpandedElementExpr extends Omit<BaseExpr, 'expanded'> {
|
|
31
|
+
expanded: Fqn
|
|
32
|
+
}
|
|
33
|
+
export function isExpandedElementExpr(expr: Expression): expr is ExpandedElementExpr {
|
|
34
|
+
return 'expanded' in expr
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface CustomElementExpr extends Omit<BaseExpr, 'custom'> {
|
|
38
|
+
custom: {
|
|
39
|
+
element: Fqn
|
|
40
|
+
title?: string
|
|
41
|
+
description?: string
|
|
42
|
+
technology?: string
|
|
43
|
+
shape?: ElementShape
|
|
44
|
+
color?: ThemeColor
|
|
45
|
+
icon?: IconUrl
|
|
46
|
+
border?: BorderStyle
|
|
47
|
+
opacity?: number
|
|
48
|
+
navigateTo?: ViewID
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function isCustomElement(expr: Expression): expr is CustomElementExpr {
|
|
53
|
+
return 'custom' in expr
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface WildcardExpr extends Omit<BaseExpr, 'wildcard'> {
|
|
57
|
+
wildcard: true
|
|
58
|
+
}
|
|
59
|
+
export function isWildcard(expr: Expression): expr is WildcardExpr {
|
|
60
|
+
return 'wildcard' in expr
|
|
61
|
+
}
|
|
62
|
+
export interface ElementKindExpr extends Omit<BaseExpr, 'elementKind' | 'isEqual'> {
|
|
63
|
+
elementKind: ElementKind
|
|
64
|
+
isEqual: boolean
|
|
65
|
+
}
|
|
66
|
+
export function isElementKindExpr(expr: Expression): expr is ElementKindExpr {
|
|
67
|
+
return 'elementKind' in expr && 'isEqual' in expr
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface ElementTagExpr extends Omit<BaseExpr, 'elementTag' | 'isEqual'> {
|
|
71
|
+
elementTag: Tag
|
|
72
|
+
isEqual: boolean
|
|
73
|
+
}
|
|
74
|
+
export function isElementTagExpr(expr: Expression): expr is ElementTagExpr {
|
|
75
|
+
return 'elementTag' in expr && 'isEqual' in expr
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type ElementExpression =
|
|
79
|
+
| ElementRefExpr
|
|
80
|
+
| WildcardExpr
|
|
81
|
+
| ElementKindExpr
|
|
82
|
+
| ElementTagExpr
|
|
83
|
+
| ExpandedElementExpr
|
|
84
|
+
export function isElement(expr: Expression): expr is ElementExpression {
|
|
85
|
+
return isElementRef(expr)
|
|
86
|
+
|| isWildcard(expr)
|
|
87
|
+
|| isElementKindExpr(expr)
|
|
88
|
+
|| isElementTagExpr(expr)
|
|
89
|
+
|| isExpandedElementExpr(expr)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface RelationExpr extends Omit<BaseExpr, 'source' | 'target'> {
|
|
93
|
+
source: ElementExpression
|
|
94
|
+
target: ElementExpression
|
|
95
|
+
isBidirectional?: boolean
|
|
96
|
+
}
|
|
97
|
+
export function isRelation(expr: Expression): expr is RelationExpr {
|
|
98
|
+
return 'source' in expr && 'target' in expr
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface InOutExpr extends Omit<BaseExpr, 'inout'> {
|
|
102
|
+
inout: ElementExpression
|
|
103
|
+
}
|
|
104
|
+
export function isInOut(expr: Expression): expr is InOutExpr {
|
|
105
|
+
return 'inout' in expr
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface IncomingExpr extends Omit<BaseExpr, 'incoming'> {
|
|
109
|
+
incoming: ElementExpression
|
|
110
|
+
}
|
|
111
|
+
export function isIncoming(expr: Expression): expr is IncomingExpr {
|
|
112
|
+
return 'incoming' in expr
|
|
113
|
+
}
|
|
114
|
+
export interface OutgoingExpr extends Omit<BaseExpr, 'outgoing'> {
|
|
115
|
+
outgoing: ElementExpression
|
|
116
|
+
}
|
|
117
|
+
export function isOutgoing(expr: Expression): expr is OutgoingExpr {
|
|
118
|
+
return 'outgoing' in expr
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export type AnyRelationExpression = RelationExpr | InOutExpr | IncomingExpr | OutgoingExpr
|
|
122
|
+
|
|
123
|
+
export function isAnyRelation(expr: Expression): expr is AnyRelationExpression {
|
|
124
|
+
return isRelation(expr) || isInOut(expr) || isIncoming(expr) || isOutgoing(expr)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export type Expression = ElementExpression | CustomElementExpr | AnyRelationExpression
|
|
128
|
+
|
|
129
|
+
// export const Expr = {
|
|
130
|
+
// isElementRef,
|
|
131
|
+
// isWildcard,
|
|
132
|
+
// isElementKindExpr,
|
|
133
|
+
// isElementTagExpr,
|
|
134
|
+
// isElement,
|
|
135
|
+
// isRelation,
|
|
136
|
+
// isInOut,
|
|
137
|
+
// isIncoming,
|
|
138
|
+
// isOutgoing,
|
|
139
|
+
// isAnyRelation,
|
|
140
|
+
// }
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Element, Fqn } from './element'
|
|
2
|
+
import type { Relation, RelationID } from './relation'
|
|
3
|
+
import type { ComputedView, View, ViewID } from './view'
|
|
4
|
+
|
|
5
|
+
export interface LikeC4Model {
|
|
6
|
+
elements: Record<Fqn, Element>
|
|
7
|
+
relations: Record<RelationID, Relation>
|
|
8
|
+
views: Record<ViewID, View>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface LikeC4ComputedModel {
|
|
12
|
+
elements: Record<Fqn, Element>
|
|
13
|
+
relations: Record<RelationID, Relation>
|
|
14
|
+
views: Record<ViewID, ComputedView>
|
|
15
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
declare const tag: unique symbol
|
|
2
|
+
|
|
3
|
+
type Tagged<Token> = {
|
|
4
|
+
readonly [tag]: Token
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
Create an opaque type, which hides its internal details from the public, and can only be created by being used explicitly.
|
|
9
|
+
|
|
10
|
+
The generic type parameter can be anything. It doesn't have to be an object.
|
|
11
|
+
|
|
12
|
+
[Read more about opaque types.](https://codemix.com/opaque-types-in-javascript/)
|
|
13
|
+
|
|
14
|
+
There have been several discussions about adding this feature to TypeScript via the `opaque type` operator, similar to how Flow does it. Unfortunately, nothing has (yet) moved forward:
|
|
15
|
+
- [Microsoft/TypeScript#202](https://github.com/microsoft/TypeScript/issues/202)
|
|
16
|
+
- [Microsoft/TypeScript#15408](https://github.com/Microsoft/TypeScript/issues/15408)
|
|
17
|
+
- [Microsoft/TypeScript#15807](https://github.com/Microsoft/TypeScript/issues/15807)
|
|
18
|
+
|
|
19
|
+
@example
|
|
20
|
+
```
|
|
21
|
+
import type {Opaque} from 'type-fest';
|
|
22
|
+
|
|
23
|
+
type AccountNumber = Opaque<number, 'AccountNumber'>;
|
|
24
|
+
type AccountBalance = Opaque<number, 'AccountBalance'>;
|
|
25
|
+
|
|
26
|
+
// The `Token` parameter allows the compiler to differentiate between types, whereas "unknown" will not. For example, consider the following structures:
|
|
27
|
+
type ThingOne = Opaque<string>;
|
|
28
|
+
type ThingTwo = Opaque<string>;
|
|
29
|
+
|
|
30
|
+
// To the compiler, these types are allowed to be cast to each other as they have the same underlying type. They are both `string & { __opaque__: unknown }`.
|
|
31
|
+
// To avoid this behaviour, you would instead pass the "Token" parameter, like so.
|
|
32
|
+
type NewThingOne = Opaque<string, 'ThingOne'>;
|
|
33
|
+
type NewThingTwo = Opaque<string, 'ThingTwo'>;
|
|
34
|
+
|
|
35
|
+
// Now they're completely separate types, so the following will fail to compile.
|
|
36
|
+
function createNewThingOne (): NewThingOne {
|
|
37
|
+
// As you can see, casting from a string is still allowed. However, you may not cast NewThingOne to NewThingTwo, and vice versa.
|
|
38
|
+
return 'new thing one' as NewThingOne;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// This will fail to compile, as they are fundamentally different types.
|
|
42
|
+
const thingTwo = createNewThingOne() as NewThingTwo;
|
|
43
|
+
|
|
44
|
+
// Here's another example of opaque typing.
|
|
45
|
+
function createAccountNumber(): AccountNumber {
|
|
46
|
+
return 2 as AccountNumber;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getMoneyForAccount(accountNumber: AccountNumber): AccountBalance {
|
|
50
|
+
return 4 as AccountBalance;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// This will compile successfully.
|
|
54
|
+
getMoneyForAccount(createAccountNumber());
|
|
55
|
+
|
|
56
|
+
// But this won't, because it has to be explicitly passed as an `AccountNumber` type.
|
|
57
|
+
getMoneyForAccount(2);
|
|
58
|
+
|
|
59
|
+
// You can use opaque values like they aren't opaque too.
|
|
60
|
+
const accountNumber = createAccountNumber();
|
|
61
|
+
|
|
62
|
+
// This will not compile successfully.
|
|
63
|
+
const newAccountNumber = accountNumber + 2;
|
|
64
|
+
|
|
65
|
+
// As a side note, you can (and should) use recursive types for your opaque types to make them stronger and hopefully easier to type.
|
|
66
|
+
type Person = {
|
|
67
|
+
id: Opaque<number, Person>;
|
|
68
|
+
name: string;
|
|
69
|
+
};
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
@category Type
|
|
73
|
+
*/
|
|
74
|
+
export type Opaque<Type, Token = unknown> = Type & Tagged<Token>
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
Revert an opaque type back to its original type by removing the readonly `[tag]`.
|
|
78
|
+
|
|
79
|
+
Why is this necessary?
|
|
80
|
+
|
|
81
|
+
1. Use an `Opaque` type as object keys
|
|
82
|
+
2. Prevent TS4058 error: "Return type of exported function has or is using name X from external module Y but cannot be named"
|
|
83
|
+
|
|
84
|
+
@example
|
|
85
|
+
```
|
|
86
|
+
import type {Opaque, UnwrapOpaque} from 'type-fest';
|
|
87
|
+
|
|
88
|
+
type AccountType = Opaque<'SAVINGS' | 'CHECKING', 'AccountType'>;
|
|
89
|
+
|
|
90
|
+
const moneyByAccountType: Record<UnwrapOpaque<AccountType>, number> = {
|
|
91
|
+
SAVINGS: 99,
|
|
92
|
+
CHECKING: 0.1
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// Without UnwrapOpaque, the following expression would throw a type error.
|
|
96
|
+
const money = moneyByAccountType.SAVINGS; // TS error: Property 'SAVINGS' does not exist
|
|
97
|
+
|
|
98
|
+
// Attempting to pass an non-Opaque type to UnwrapOpaque will raise a type error.
|
|
99
|
+
type WontWork = UnwrapOpaque<string>;
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
@category Type
|
|
103
|
+
*/
|
|
104
|
+
export type UnwrapOpaque<OpaqueType extends Tagged<unknown>> = OpaqueType extends Opaque<
|
|
105
|
+
infer Type,
|
|
106
|
+
OpaqueType[typeof tag]
|
|
107
|
+
> ? Type
|
|
108
|
+
: OpaqueType
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { NonEmptyArray } from './_common'
|
|
2
|
+
import type { Fqn, Tag } from './element'
|
|
3
|
+
import type { Opaque } from './opaque'
|
|
4
|
+
import type { ThemeColor } from './theme'
|
|
5
|
+
|
|
6
|
+
export type RelationID = Opaque<string, 'RelationID'>
|
|
7
|
+
export type RelationshipKind = Opaque<string, 'RelationshipKind'>
|
|
8
|
+
|
|
9
|
+
export type RelationshipLineType = 'dashed' | 'solid' | 'dotted'
|
|
10
|
+
|
|
11
|
+
// reference: https://graphviz.org/docs/attr-types/arrowType/
|
|
12
|
+
export type RelationshipArrowType =
|
|
13
|
+
| 'none'
|
|
14
|
+
| 'normal'
|
|
15
|
+
| 'onormal'
|
|
16
|
+
| 'diamond'
|
|
17
|
+
| 'odiamond'
|
|
18
|
+
| 'crow'
|
|
19
|
+
| 'open'
|
|
20
|
+
| 'vee'
|
|
21
|
+
|
|
22
|
+
export const DefaultLineStyle = 'dashed' satisfies RelationshipLineType
|
|
23
|
+
export const DefaultArrowType = 'normal' satisfies RelationshipArrowType
|
|
24
|
+
export const DefaultRelationshipColor = 'gray' satisfies ThemeColor
|
|
25
|
+
|
|
26
|
+
export interface Relation {
|
|
27
|
+
readonly id: RelationID
|
|
28
|
+
readonly source: Fqn
|
|
29
|
+
readonly target: Fqn
|
|
30
|
+
readonly title: string
|
|
31
|
+
readonly tags?: NonEmptyArray<Tag>
|
|
32
|
+
readonly kind?: RelationshipKind
|
|
33
|
+
readonly color?: ThemeColor
|
|
34
|
+
readonly line?: RelationshipLineType
|
|
35
|
+
readonly head?: RelationshipArrowType
|
|
36
|
+
readonly tail?: RelationshipArrowType
|
|
37
|
+
readonly links?: NonEmptyArray<string>
|
|
38
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export type ThemeColor =
|
|
2
|
+
| 'amber'
|
|
3
|
+
| 'blue'
|
|
4
|
+
| 'gray'
|
|
5
|
+
| 'slate'
|
|
6
|
+
| 'green'
|
|
7
|
+
| 'indigo'
|
|
8
|
+
| 'muted'
|
|
9
|
+
| 'primary'
|
|
10
|
+
| 'red'
|
|
11
|
+
| 'secondary'
|
|
12
|
+
| 'sky'
|
|
13
|
+
|
|
14
|
+
export type HexColorLiteral = `#${string}`
|
|
15
|
+
|
|
16
|
+
export type ColorLiteral = HexColorLiteral
|
|
17
|
+
|
|
18
|
+
export interface ElementThemeColorValues {
|
|
19
|
+
fill: ColorLiteral
|
|
20
|
+
stroke: ColorLiteral
|
|
21
|
+
// Main text (title, etc.)
|
|
22
|
+
hiContrast: ColorLiteral
|
|
23
|
+
// Secondary text (description, etc.)
|
|
24
|
+
loContrast: ColorLiteral
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type ElementThemeColors = {
|
|
28
|
+
[key in ThemeColor]: ElementThemeColorValues
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface RelationshipThemeColorValues {
|
|
32
|
+
lineColor: ColorLiteral
|
|
33
|
+
labelBgColor: ColorLiteral
|
|
34
|
+
labelColor: ColorLiteral
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type RelationshipThemeColors = {
|
|
38
|
+
[key in ThemeColor]: RelationshipThemeColorValues
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface LikeC4Theme {
|
|
42
|
+
font: 'Helvetica' // for now only support Helvetica
|
|
43
|
+
shadow: ColorLiteral
|
|
44
|
+
relationships: RelationshipThemeColors
|
|
45
|
+
elements: ElementThemeColors
|
|
46
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { isNullish } from 'remeda'
|
|
2
|
+
import type { IconUrl, NonEmptyArray } from './_common'
|
|
3
|
+
import type { ElementKind, ElementShape, ElementStyle, Fqn, Tag } from './element'
|
|
4
|
+
import type { CustomElementExpr, ElementExpression, Expression } from './expression'
|
|
5
|
+
import type { Opaque } from './opaque'
|
|
6
|
+
import type { RelationID, RelationshipArrowType, RelationshipLineType } from './relation'
|
|
7
|
+
import type { ColorLiteral, ThemeColor } from './theme'
|
|
8
|
+
|
|
9
|
+
// Full-qualified-name
|
|
10
|
+
export type ViewID = Opaque<string, 'ViewID'>
|
|
11
|
+
|
|
12
|
+
export type ViewRuleExpression =
|
|
13
|
+
| {
|
|
14
|
+
include: Expression[]
|
|
15
|
+
exclude?: never
|
|
16
|
+
}
|
|
17
|
+
| {
|
|
18
|
+
include?: never
|
|
19
|
+
exclude: Expression[]
|
|
20
|
+
}
|
|
21
|
+
export function isViewRuleExpression(rule: ViewRule): rule is ViewRuleExpression {
|
|
22
|
+
return (
|
|
23
|
+
('include' in rule && Array.isArray(rule.include))
|
|
24
|
+
|| ('exclude' in rule && Array.isArray(rule.exclude))
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ViewRuleStyle {
|
|
29
|
+
targets: ElementExpression[]
|
|
30
|
+
style: ElementStyle & {
|
|
31
|
+
color?: ThemeColor
|
|
32
|
+
shape?: ElementShape
|
|
33
|
+
icon?: IconUrl
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function isViewRuleStyle(rule: ViewRule): rule is ViewRuleStyle {
|
|
37
|
+
return 'style' in rule && 'targets' in rule
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type AutoLayoutDirection = 'TB' | 'BT' | 'LR' | 'RL'
|
|
41
|
+
export interface ViewRuleAutoLayout {
|
|
42
|
+
autoLayout: AutoLayoutDirection
|
|
43
|
+
}
|
|
44
|
+
export function isViewRuleAutoLayout(rule: ViewRule): rule is ViewRuleAutoLayout {
|
|
45
|
+
return 'autoLayout' in rule
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type ViewRule = ViewRuleExpression | ViewRuleStyle | ViewRuleAutoLayout
|
|
49
|
+
|
|
50
|
+
export interface BasicView<ViewType extends 'element' | 'dynamic' = 'element' | 'dynamic'> {
|
|
51
|
+
readonly __?: ViewType
|
|
52
|
+
readonly id: ViewID
|
|
53
|
+
readonly title: string | null
|
|
54
|
+
readonly description: string | null
|
|
55
|
+
readonly tags: NonEmptyArray<Tag> | null
|
|
56
|
+
readonly links: NonEmptyArray<string> | null
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* URI to the source file of this view.
|
|
60
|
+
* Undefined if the view is auto-generated.
|
|
61
|
+
*/
|
|
62
|
+
readonly docUri?: string
|
|
63
|
+
/**
|
|
64
|
+
* For all views we find common ancestor path.
|
|
65
|
+
* This is used to generate relative paths, i.e.:
|
|
66
|
+
* - "" for views in the common ancestor directory (or root)
|
|
67
|
+
* - "subdir" for views in "<root>/subdir"
|
|
68
|
+
* - "subdir/subdir1" for views in "<root>/subdir/subdir1"
|
|
69
|
+
*
|
|
70
|
+
* Undefined if the view is auto-generated.
|
|
71
|
+
*/
|
|
72
|
+
readonly relativePath?: string
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface BasicElementView extends BasicView<'element'> {
|
|
76
|
+
readonly viewOf?: Fqn
|
|
77
|
+
readonly rules: ViewRule[]
|
|
78
|
+
}
|
|
79
|
+
export interface StrictElementView extends BasicElementView {
|
|
80
|
+
readonly viewOf: Fqn
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface ExtendsElementView extends BasicElementView {
|
|
84
|
+
readonly extends: ViewID
|
|
85
|
+
}
|
|
86
|
+
export type ElementView = StrictElementView | ExtendsElementView | BasicElementView
|
|
87
|
+
|
|
88
|
+
export interface DynamicViewStep {
|
|
89
|
+
readonly source: Fqn
|
|
90
|
+
readonly target: Fqn
|
|
91
|
+
readonly title: string | null
|
|
92
|
+
readonly isBackward?: boolean
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export type DynamicViewIncludeRule = {
|
|
96
|
+
include: (ElementExpression | CustomElementExpr)[]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function isDynamicViewIncludeRule(rule: DynamicViewRule): rule is DynamicViewIncludeRule {
|
|
100
|
+
return 'include' in rule && Array.isArray(rule.include)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export type DynamicViewRule = DynamicViewIncludeRule | ViewRuleStyle | ViewRuleAutoLayout
|
|
104
|
+
export interface DynamicView extends BasicView<'dynamic'> {
|
|
105
|
+
readonly __: 'dynamic'
|
|
106
|
+
|
|
107
|
+
readonly steps: DynamicViewStep[]
|
|
108
|
+
|
|
109
|
+
readonly rules: DynamicViewRule[]
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export type View = ElementView | DynamicView
|
|
113
|
+
|
|
114
|
+
export function isDynamicView(view: View): view is DynamicView {
|
|
115
|
+
return view.__ === 'dynamic'
|
|
116
|
+
}
|
|
117
|
+
export function isElementView(view: View): view is ElementView {
|
|
118
|
+
return isNullish(view.__) || view.__ === 'element'
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function isExtendsElementView(view: View): view is ExtendsElementView {
|
|
122
|
+
return isElementView(view) && 'extends' in view
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function isStrictElementView(view: View): view is StrictElementView {
|
|
126
|
+
return isElementView(view) && 'viewOf' in view
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export type NodeId = Fqn
|
|
130
|
+
|
|
131
|
+
export type EdgeId = Opaque<string, 'EdgeId'>
|
|
132
|
+
|
|
133
|
+
export type StepEdgeIdLiteral = `step-${number}`
|
|
134
|
+
export type StepEdgeId = Opaque<StepEdgeIdLiteral, 'EdgeId'>
|
|
135
|
+
export function StepEdgeId(step: number): StepEdgeId {
|
|
136
|
+
return `step-${String(step).padStart(3, '0')}` as StepEdgeId
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function isStepEdgeId(id: string): id is StepEdgeId {
|
|
140
|
+
return id.startsWith('step-')
|
|
141
|
+
}
|
|
142
|
+
export function extractStep(id: EdgeId): number {
|
|
143
|
+
if (!isStepEdgeId(id)) {
|
|
144
|
+
throw new Error(`Invalid step edge id: ${id}`)
|
|
145
|
+
}
|
|
146
|
+
return Number(id.slice('step-'.length))
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface ComputedNode {
|
|
150
|
+
id: NodeId
|
|
151
|
+
kind: ElementKind
|
|
152
|
+
parent: NodeId | null
|
|
153
|
+
title: string
|
|
154
|
+
description: string | null
|
|
155
|
+
technology: string | null
|
|
156
|
+
tags: NonEmptyArray<Tag> | null
|
|
157
|
+
links: NonEmptyArray<string> | null
|
|
158
|
+
children: NodeId[]
|
|
159
|
+
inEdges: EdgeId[]
|
|
160
|
+
outEdges: EdgeId[]
|
|
161
|
+
shape: ElementShape
|
|
162
|
+
/**
|
|
163
|
+
* @deprecated Use `style` instead
|
|
164
|
+
*/
|
|
165
|
+
color: ThemeColor
|
|
166
|
+
/**
|
|
167
|
+
* @deprecated Use `style` instead
|
|
168
|
+
*/
|
|
169
|
+
icon?: IconUrl
|
|
170
|
+
style: ElementStyle
|
|
171
|
+
navigateTo?: ViewID
|
|
172
|
+
level: number
|
|
173
|
+
// For compound nodes, the max depth of nested nodes
|
|
174
|
+
depth?: number
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export interface ComputedEdge {
|
|
178
|
+
id: EdgeId
|
|
179
|
+
parent: NodeId | null
|
|
180
|
+
source: NodeId
|
|
181
|
+
target: NodeId
|
|
182
|
+
label: string | null
|
|
183
|
+
relations: RelationID[]
|
|
184
|
+
color?: ThemeColor
|
|
185
|
+
line?: RelationshipLineType
|
|
186
|
+
head?: RelationshipArrowType
|
|
187
|
+
tail?: RelationshipArrowType
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* For layouting purposes
|
|
191
|
+
* @default 'forward'
|
|
192
|
+
*/
|
|
193
|
+
dir?: 'forward' | 'back'
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface ComputedElementView extends Omit<ElementView, 'rules'> {
|
|
197
|
+
readonly extends?: ViewID
|
|
198
|
+
readonly autoLayout: ViewRuleAutoLayout['autoLayout']
|
|
199
|
+
readonly nodes: ComputedNode[]
|
|
200
|
+
readonly edges: ComputedEdge[]
|
|
201
|
+
}
|
|
202
|
+
export interface ComputedDynamicView extends Omit<DynamicView, 'rules' | 'steps'> {
|
|
203
|
+
readonly autoLayout: ViewRuleAutoLayout['autoLayout']
|
|
204
|
+
readonly nodes: ComputedNode[]
|
|
205
|
+
readonly edges: ComputedEdge[]
|
|
206
|
+
}
|
|
207
|
+
export function isComputedDynamicView(view: ComputedView): view is ComputedDynamicView {
|
|
208
|
+
return view.__ === 'dynamic'
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export type ComputedView = ComputedElementView | ComputedDynamicView
|
|
212
|
+
|
|
213
|
+
export function isComputedElementView(view: ComputedView): view is ComputedElementView {
|
|
214
|
+
return isNullish(view.__) || view.__ === 'element'
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export type Point = readonly [x: number, y: number]
|
|
218
|
+
|
|
219
|
+
// Bounding box
|
|
220
|
+
export type BBox = {
|
|
221
|
+
x: number
|
|
222
|
+
y: number
|
|
223
|
+
width: number
|
|
224
|
+
height: number
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export interface DiagramLabel {
|
|
228
|
+
align: 'left' | 'right' | 'center'
|
|
229
|
+
fontStyle?: 'bold' | 'normal'
|
|
230
|
+
color?: ColorLiteral
|
|
231
|
+
fontSize: number
|
|
232
|
+
pt: Point
|
|
233
|
+
width: number
|
|
234
|
+
text: string
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export interface DiagramNode extends ComputedNode {
|
|
238
|
+
width: number
|
|
239
|
+
height: number
|
|
240
|
+
position: Point // Absolute position, top left
|
|
241
|
+
// relative: Point // Top left, relative to parent
|
|
242
|
+
labels: DiagramLabel[]
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export interface DiagramEdge extends ComputedEdge {
|
|
246
|
+
points: NonEmptyArray<Point>
|
|
247
|
+
// Polygons are used to draw arrows
|
|
248
|
+
headArrow?: NonEmptyArray<Point>
|
|
249
|
+
// Draw arrow from the last point of the edge to this point
|
|
250
|
+
headArrowPoint?: Point
|
|
251
|
+
tailArrow?: NonEmptyArray<Point>
|
|
252
|
+
// Draw arrow from the first point of the edge to this point
|
|
253
|
+
tailArrowPoint?: Point
|
|
254
|
+
labels?: NonEmptyArray<DiagramLabel>
|
|
255
|
+
labelBBox?: BBox
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export interface DiagramView extends Omit<ComputedView, 'nodes' | 'edges'> {
|
|
259
|
+
readonly nodes: DiagramNode[]
|
|
260
|
+
readonly edges: DiagramEdge[]
|
|
261
|
+
readonly width: number
|
|
262
|
+
readonly height: number
|
|
263
|
+
}
|