Skip to content

Commit 93bb11e

Browse files
committed
chore: add branded model string types
1 parent fe6f3ba commit 93bb11e

File tree

8 files changed

+119
-23
lines changed

8 files changed

+119
-23
lines changed

vscode/bus/src/brand.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
declare const __brand: unique symbol
2+
type Brand<B> = { [__brand]: B }
3+
4+
/**
5+
* Branded is a type that adds a brand to a type. It is a type that is used to
6+
* ensure that the type is unique and that it is not possible to mix up types
7+
* with the same brand.
8+
*
9+
* @example
10+
*
11+
* type UserId = Branded<string, 'UserId'>
12+
* type UserName = Branded<string, 'UserName'>
13+
*
14+
* const userId = '123' as UserId
15+
* const userName = 'John Doe' as UserName
16+
*
17+
* userId == userName -> compile error
18+
*/
19+
export type Branded<T, B> = T & Brand<B>

vscode/react/src/components/graph/ModelColumns.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import { Popover, Transition } from '@headlessui/react'
4646
import { useApiColumnLineage } from '@/api/index'
4747
import SourceList from '@/components/sourceList/SourceList'
4848
import type { Lineage } from '@/domain/lineage'
49+
import type { ModelName } from '@/domain/models'
4950

5051
export default function ModelColumns({
5152
nodeId,
@@ -732,5 +733,5 @@ function getColumnFromLineage(
732733
nodeId: string,
733734
columnName: string,
734735
): LineageColumn | undefined {
735-
return lineage?.[nodeId]?.columns?.[encodeURI(columnName)]
736+
return lineage?.[nodeId]?.columns?.[encodeURI(columnName) as ModelName]
736737
}

vscode/react/src/components/graph/help.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
} from './ModelNode'
1818
import type { Lineage } from '@/domain/lineage'
1919
import type { ConnectedNode } from '@/workers/lineage'
20+
import type { ModelEncodedFQN, ModelName } from '@/domain/models'
2021

2122
export interface GraphNodeData {
2223
label: string
@@ -108,7 +109,7 @@ function getEdges(lineage: Record<string, Lineage> = {}): Edge[] {
108109
})
109110

110111
for (const targetColumnName in targetModel.columns) {
111-
const sourceModel = targetModel.columns[targetColumnName]
112+
const sourceModel = targetModel.columns[targetColumnName as ModelName]
112113

113114
if (isNil(sourceModel) || isNil(sourceModel.models)) continue
114115

@@ -210,7 +211,7 @@ function getNodeMap({
210211
node.targetPosition = Position.Left
211212
}
212213

213-
if (sources.has(node.id)) {
214+
if (sources.has(node.id as ModelEncodedFQN)) {
214215
node.sourcePosition = Position.Right
215216
}
216217

@@ -343,7 +344,7 @@ function mergeLineageWithColumns(
343344
}
344345

345346
// New Column Lineage delivers fresh data, so we can just assign it
346-
currentLineageModel.columns[targetColumnNameEncoded] = {
347+
currentLineageModel.columns[targetColumnNameEncoded as ModelName] = {
347348
expression: newLineageModelColumn.expression,
348349
source: newLineageModelColumn.source,
349350
models: {},
@@ -353,7 +354,7 @@ function mergeLineageWithColumns(
353354
if (isObjectEmpty(newLineageModelColumn.models)) continue
354355

355356
const currentLineageModelColumn =
356-
currentLineageModel.columns[targetColumnNameEncoded]!
357+
currentLineageModel.columns[targetColumnNameEncoded as ModelName]!
357358
const currentLineageModelColumnModels = currentLineageModelColumn.models
358359

359360
for (const sourceColumnName in newLineageModelColumn.models) {
@@ -371,7 +372,7 @@ function mergeLineageWithColumns(
371372
newLineageModelColumnModel,
372373
),
373374
),
374-
).map(encodeURI)
375+
).map((uri: string) => encodeURI(uri))
375376
}
376377
}
377378
}
@@ -481,7 +482,7 @@ function getLineageIndex(lineage: Record<string, Lineage> = {}): string {
481482

482483
if (isNotNil(columns)) {
483484
Object.keys(columns).forEach(columnName => {
484-
const column = columns[columnName]
485+
const column = columns[columnName as ModelName]
485486

486487
if (isNotNil(column) && isNotNil(column.models)) {
487488
Object.keys(column.models).forEach(m => allModels.add(m))

vscode/react/src/domain/lineage.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { type LineageColumn } from '@/api/client'
2+
import type { ModelEncodedFQN, ModelName } from '@/domain/models'
23

34
export interface Lineage {
4-
models: string[]
5-
columns?: Record<string, LineageColumn>
5+
models: ModelEncodedFQN[]
6+
columns?: Record<ModelName, LineageColumn>
67
}

vscode/react/src/domain/models.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import type { Branded } from '@bus/brand'
2+
3+
/**
4+
* ModelName is a type that represents the name of a model.
5+
*/
6+
export type ModelName = Branded<string, 'ModelName'>
7+
8+
/**
9+
* ModelEncodedName is a type that represents the encoded name of a model.
10+
*/
11+
export type ModelEncodedName = Branded<string, 'ModelEncodedName'>
12+
13+
/**
14+
* ModelFQN is a type that represents the fully qualified name of a model.
15+
*/
16+
export type ModelFQN = Branded<string, 'ModelFQN'>
17+
18+
/**
19+
* ModelEncodedFQN is a type that represents the encoded fully qualified name of a model.
20+
*/
21+
export type ModelEncodedFQN = Branded<string, 'ModelEncodedFQN'>
22+
23+
/**
24+
* ModelURI is a type that represents the URI of a model.
25+
*/
26+
export type ModelURI = Branded<string, 'ModelURI'>
27+
28+
/**
29+
* ModelEncodedURI is a type that represents the encoded URI of a model.
30+
*/
31+
export type ModelEncodedURI = Branded<string, 'ModelEncodedURI'>
32+
33+
/**
34+
* ModelPath is a type that represents the path of a model.
35+
* A model path is relative to the project root.
36+
*/
37+
export type ModelPath = Branded<string, 'ModelPath'>
38+
39+
/**
40+
* ModelFullPath is a type that represents the full path of a model.
41+
* A model full path is a fully qualified path to a model.
42+
*/
43+
export type ModelFullPath = Branded<string, 'ModelFullPath'>

vscode/react/src/domain/sqlmesh-model.ts

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,24 @@ import {
88
type ModelDefaultCatalog,
99
type ModelDefinition,
1010
} from '@/api/client'
11+
import type {
12+
ModelEncodedFQN,
13+
ModelName,
14+
ModelPath,
15+
ModelEncodedName,
16+
ModelFullPath,
17+
} from '@/domain/models'
1118
import { isArrayNotEmpty } from '@/utils/index'
1219
import { ModelInitial } from './initial'
1320
import type { Lineage } from './lineage'
1421

15-
export interface InitialSQLMeshModel extends Model {
16-
lineage?: Record<string, Lineage>
22+
export interface InitialSQLMeshModel
23+
extends Omit<Model, 'name' | 'fqn' | 'path' | 'full_path'> {
24+
name: ModelName
25+
fqn: ModelEncodedFQN
26+
path: ModelPath
27+
full_path: ModelFullPath
28+
lineage?: Record<ModelName, Lineage>
1729
}
1830

1931
export class ModelSQLMeshModel<
@@ -22,9 +34,10 @@ export class ModelSQLMeshModel<
2234
_details: ModelDetails = {}
2335
_detailsIndex: string = ''
2436

25-
name: string
26-
fqn: string
27-
path: string
37+
name: ModelEncodedName
38+
fqn: ModelEncodedFQN
39+
path: ModelPath
40+
full_path: ModelFullPath
2841
dialect: string
2942
type: ModelType
3043
columns: Column[]
@@ -46,10 +59,11 @@ export class ModelSQLMeshModel<
4659
},
4760
)
4861

49-
this.name = encodeURI(this.initial.name)
50-
this.fqn = encodeURI(this.initial.fqn)
62+
this.name = encodeURI(this.initial.name) as ModelEncodedName
63+
this.fqn = encodeURI(this.initial.fqn) as ModelEncodedFQN
5164
this.default_catalog = this.initial.default_catalog
52-
this.path = this.initial.path
65+
this.path = this.initial.path as ModelPath
66+
this.full_path = this.initial.full_path as ModelFullPath
5367
this.dialect = this.initial.dialect
5468
this.description = this.initial.description
5569
this.sql = this.initial.sql
@@ -127,17 +141,21 @@ export class ModelSQLMeshModel<
127141
} else if (key === 'details') {
128142
this.details = value as ModelDetails
129143
} else if (key === 'name') {
130-
this.name = encodeURI(value as string)
144+
this.name = encodeURI(value as string) as ModelEncodedName
131145
} else if (key === 'fqn') {
132-
this.fqn = encodeURI(value as string)
146+
this.fqn = encodeURI(value as string) as ModelEncodedFQN
133147
} else if (key === 'type') {
134148
this.type = value as ModelType
135149
} else if (key === 'default_catalog') {
136150
this.default_catalog = value as ModelDefaultCatalog
137151
} else if (key === 'description') {
138152
this.description = value as ModelDescription
139153
} else if (key in this) {
140-
this[key as 'path' | 'dialect' | 'sql'] = value as string
154+
if (key === 'path' || key === 'full_path') {
155+
;(this as any)[key] = value as ModelPath
156+
} else {
157+
;(this as any)[key] = value as string
158+
}
141159
}
142160
}
143161
}

vscode/react/src/pages/lineage.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ import type { VSCodeEvent } from '@bus/callbacks'
1616
import { URI } from 'vscode-uri'
1717
import type { Model } from '@/api/client'
1818
import { useRpc } from '@/utils/rpc'
19+
import type {
20+
ModelEncodedFQN,
21+
ModelName,
22+
ModelPath,
23+
ModelFullPath,
24+
} from '@/domain/models'
1925

2026
export function LineagePage() {
2127
const { emit } = useEventBus()
@@ -198,7 +204,13 @@ export function LineageComponentFromWeb({
198204
}
199205

200206
const sqlmModel = new ModelSQLMeshModel()
201-
sqlmModel.update(model)
207+
sqlmModel.update({
208+
...model,
209+
name: model.name as ModelName,
210+
fqn: model.fqn as ModelEncodedFQN,
211+
path: model.path as ModelPath,
212+
full_path: model.full_path as ModelFullPath,
213+
})
202214

203215
return (
204216
<div className="h-[100vh] w-[100vw]">

vscode/react/src/workers/lineage.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { isFalse, isNil, isStringEmptyOrNil, toID } from '@/utils/index'
22
import { type Lineage } from '@/domain/lineage'
3+
import type { ModelEncodedFQN } from '@/domain/models'
34

45
export interface ConnectedNode {
56
id?: string
@@ -47,7 +48,7 @@ async function mergeLineageWithModels(
4748
key = encodeURI(key)
4849

4950
acc[key] = {
50-
models: models.map(encodeURI),
51+
models: models.map(encodeURI) as ModelEncodedFQN[],
5152
columns: currentLineage?.[key]?.columns ?? undefined,
5253
}
5354

@@ -88,7 +89,7 @@ function getConnectedNodes(
8889

8990
if (isDownstream) {
9091
models = Object.keys(lineage).filter(key =>
91-
lineage[key]!.models.includes(node),
92+
lineage[key]!.models.includes(node as ModelEncodedFQN),
9293
)
9394
} else {
9495
models = lineage[node]?.models ?? []

0 commit comments

Comments
 (0)