Skip to content

Commit 727280e

Browse files
committed
Added foreign key support
1 parent 9ace79b commit 727280e

File tree

13 files changed

+2672
-400
lines changed

13 files changed

+2672
-400
lines changed

plan.md

Lines changed: 1524 additions & 375 deletions
Large diffs are not rendered by default.

src/packages/dumbo/src/core/schema/components/databaseSchemaComponent.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ export type DatabaseSchemaComponent<
3636
}>
3737
>;
3838

39+
export type AnyDatabaseSchemaComponent =
40+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
41+
DatabaseSchemaComponent<any>;
42+
3943
export const databaseSchemaComponent = <
4044
Schemas extends DatabaseSchemas = DatabaseSchemas,
4145
>({
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import type {
2+
AnyDatabaseSchemaSchemaComponent,
3+
AnyTableSchemaComponent,
4+
DatabaseSchemaComponent,
5+
DatabaseSchemas,
6+
DatabaseSchemaSchemaComponent,
7+
DatabaseSchemaTables,
8+
TableColumnNames,
9+
TableColumns,
10+
TableSchemaComponent,
11+
} from '../';
12+
13+
export type ExtractSchemaNames<DB> =
14+
DB extends DatabaseSchemaComponent<infer Schemas extends DatabaseSchemas>
15+
? keyof Schemas & string
16+
: never;
17+
18+
export type ExtractTableNames<Schema extends AnyDatabaseSchemaSchemaComponent> =
19+
Schema extends DatabaseSchemaSchemaComponent<
20+
infer Tables extends DatabaseSchemaTables
21+
>
22+
? keyof Tables & string
23+
: never;
24+
25+
export type ExtractColumnNames<Table extends AnyTableSchemaComponent> =
26+
Table extends TableSchemaComponent<infer Columns extends TableColumns>
27+
? TableColumnNames<TableSchemaComponent<Columns>>
28+
: never;
29+
30+
export type AllColumnReferences<DB> =
31+
DB extends DatabaseSchemaComponent<infer Schemas extends DatabaseSchemas>
32+
? {
33+
[SchemaName in keyof Schemas]: Schemas[SchemaName] extends DatabaseSchemaSchemaComponent<
34+
infer Tables
35+
>
36+
? {
37+
[TableName in keyof Tables]: Tables[TableName] extends TableSchemaComponent<
38+
infer Columns
39+
>
40+
? {
41+
[ColumnName in keyof Columns]: `${SchemaName &
42+
string}.${TableName & string}.${ColumnName & string}`;
43+
}[keyof Columns]
44+
: never;
45+
}[keyof Tables]
46+
: never;
47+
}[keyof Schemas]
48+
: never;
49+
50+
export type ForeignKeyDefinition<Columns = string, References = string> = {
51+
readonly columns: readonly Columns[];
52+
readonly references: readonly References[];
53+
};
54+
55+
export const foreignKey = <
56+
const Columns extends readonly string[],
57+
const References extends readonly string[],
58+
>(
59+
columns: Columns,
60+
references: References,
61+
) => {
62+
return {
63+
columns,
64+
references,
65+
} as const;
66+
};
67+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
68+
export type AnyForeignKeyDefinition = ForeignKeyDefinition<any, any>;
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import type {
2+
AnyDatabaseSchemaComponent,
3+
AnyDatabaseSchemaSchemaComponent,
4+
DatabaseSchemaComponent,
5+
DatabaseSchemaSchemaComponent,
6+
} from '../';
7+
import type {
8+
AnyTableSchemaComponent,
9+
TableSchemaComponent,
10+
} from '../tableSchemaComponent';
11+
import type { TableColumnNames } from '../tableTypesInference';
12+
import type {
13+
AllColumnReferences,
14+
AnyForeignKeyDefinition,
15+
} from './foreignKeyTypes';
16+
17+
export type ValidationResult<
18+
Valid extends boolean,
19+
Error = never,
20+
> = Valid extends true ? { valid: true } : { valid: false; error: Error };
21+
22+
type GetArrayLength<T extends readonly unknown[]> = T['length'];
23+
24+
export type ValidateForeignKeyLength<
25+
FK extends { columns: readonly unknown[]; references: readonly unknown[] },
26+
> =
27+
GetArrayLength<FK['columns']> extends GetArrayLength<FK['references']>
28+
? ValidationResult<true>
29+
: ValidationResult<
30+
false,
31+
`Foreign key columns and references must have the same length. Got ${GetArrayLength<FK['columns']>} columns and ${GetArrayLength<FK['references']>} references.`
32+
>;
33+
34+
type FindInvalidColumns<
35+
Columns extends readonly string[],
36+
ValidColumns extends string,
37+
Invalid extends string[] = [],
38+
> = Columns extends readonly [infer First, ...infer Rest]
39+
? First extends string
40+
? Rest extends readonly string[]
41+
? First extends ValidColumns
42+
? FindInvalidColumns<Rest, ValidColumns, Invalid>
43+
: FindInvalidColumns<Rest, ValidColumns, [...Invalid, First]>
44+
: Invalid
45+
: Invalid
46+
: Invalid;
47+
48+
type AllInTuple<
49+
Tuple extends readonly string[],
50+
Union extends string,
51+
> = Tuple extends readonly [infer First, ...infer Rest]
52+
? First extends Union
53+
? Rest extends readonly string[]
54+
? AllInTuple<Rest, Union>
55+
: true
56+
: false
57+
: true;
58+
59+
export type ValidateForeignKeyColumns<
60+
FK extends { columns: readonly string[] },
61+
ValidColumns extends string,
62+
> =
63+
AllInTuple<FK['columns'], ValidColumns> extends true
64+
? ValidationResult<true>
65+
: ValidationResult<
66+
false,
67+
`Invalid foreign key columns: ${FindInvalidColumns<FK['columns'], ValidColumns> extends infer Invalid ? (Invalid extends string[] ? Invalid[number] : never) : never}. Available columns: ${ValidColumns}`
68+
>;
69+
70+
type FindInvalidReferences<
71+
References extends readonly string[],
72+
ValidReferences extends string,
73+
Invalid extends string[] = [],
74+
> = References extends readonly [infer First, ...infer Rest]
75+
? First extends string
76+
? Rest extends readonly string[]
77+
? First extends ValidReferences
78+
? FindInvalidReferences<Rest, ValidReferences, Invalid>
79+
: FindInvalidReferences<Rest, ValidReferences, [...Invalid, First]>
80+
: Invalid
81+
: Invalid
82+
: Invalid;
83+
84+
export type ValidateForeignKeyReferences<
85+
FK extends { references: readonly string[] },
86+
ValidReferences extends string,
87+
> =
88+
AllInTuple<FK['references'], ValidReferences> extends true
89+
? ValidationResult<true>
90+
: ValidationResult<
91+
false,
92+
`Invalid foreign key references: ${FindInvalidReferences<FK['references'], ValidReferences> extends infer Invalid ? (Invalid extends string[] ? Invalid[number] : never) : never}. Available references: ${ValidReferences}`
93+
>;
94+
95+
export type ValidateSingleForeignKey<
96+
FK extends { columns: readonly string[]; references: readonly string[] },
97+
TableColumns extends string,
98+
ValidReferences extends string,
99+
> =
100+
ValidateForeignKeyLength<FK> extends { valid: false; error: infer E }
101+
? ValidationResult<false, E>
102+
: ValidateForeignKeyColumns<FK, TableColumns> extends {
103+
valid: false;
104+
error: infer E;
105+
}
106+
? ValidationResult<false, E>
107+
: ValidateForeignKeyReferences<FK, ValidReferences> extends {
108+
valid: false;
109+
error: infer E;
110+
}
111+
? ValidationResult<false, E>
112+
: ValidationResult<true>;
113+
114+
export type ValidateForeignKeyArray<
115+
FKs extends readonly AnyForeignKeyDefinition[],
116+
TableColumns extends string,
117+
ValidReferences extends string,
118+
> = FKs extends readonly []
119+
? ValidationResult<true>
120+
: ValidateSingleForeignKey<
121+
FKs[number],
122+
TableColumns,
123+
ValidReferences
124+
> extends {
125+
valid: false;
126+
error: infer E;
127+
}
128+
? ValidationResult<false, E>
129+
: ValidationResult<true>;
130+
131+
export type ValidateTableForeignKeys<
132+
Table extends AnyTableSchemaComponent,
133+
ValidReferences extends string,
134+
> =
135+
Table extends TableSchemaComponent<infer _Columns, infer FKs>
136+
? ValidateForeignKeyArray<
137+
FKs,
138+
TableColumnNames<Table> & string,
139+
ValidReferences
140+
>
141+
: ValidationResult<true>;
142+
143+
export type ValidateTablesInSchema<
144+
Tables extends Record<string, AnyTableSchemaComponent>,
145+
ValidReferences extends string,
146+
> = {
147+
[TableName in keyof Tables]: ValidateTableForeignKeys<
148+
Tables[TableName],
149+
ValidReferences
150+
>;
151+
}[keyof Tables] extends infer Results
152+
? Results extends { valid: true }
153+
? ValidationResult<true>
154+
: Results extends { valid: false; error: infer E }
155+
? ValidationResult<false, E>
156+
: ValidationResult<true>
157+
: ValidationResult<true>;
158+
159+
export type ValidateSchemaForeignKeys<
160+
Schema extends AnyDatabaseSchemaSchemaComponent,
161+
ValidReferences extends string,
162+
> =
163+
Schema extends DatabaseSchemaSchemaComponent<infer Tables>
164+
? ValidateTablesInSchema<Tables, ValidReferences>
165+
: ValidationResult<true>;
166+
167+
export type ValidateSchemasInDatabase<
168+
Schemas extends Record<string, AnyDatabaseSchemaSchemaComponent>,
169+
ValidReferences extends string,
170+
> = {
171+
[SchemaName in keyof Schemas]: ValidateSchemaForeignKeys<
172+
Schemas[SchemaName],
173+
ValidReferences
174+
>;
175+
}[keyof Schemas] extends infer Results
176+
? Results extends { valid: true }
177+
? ValidationResult<true>
178+
: Results extends { valid: false; error: infer E }
179+
? ValidationResult<false, E>
180+
: ValidationResult<true>
181+
: ValidationResult<true>;
182+
183+
export type ValidateDatabaseForeignKeys<DB extends AnyDatabaseSchemaComponent> =
184+
DB extends DatabaseSchemaComponent<infer Schemas>
185+
? ValidateSchemasInDatabase<Schemas, AllColumnReferences<DB>>
186+
: ValidationResult<true>;

0 commit comments

Comments
 (0)