@fragno-dev/db 0.0.1 → 0.1.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.
- package/.turbo/turbo-build.log +137 -13
- package/.turbo/turbo-test.log +36 -0
- package/CHANGELOG.md +7 -0
- package/dist/adapters/adapters.d.ts +18 -0
- package/dist/adapters/adapters.d.ts.map +1 -0
- package/dist/adapters/drizzle/drizzle-adapter.d.ts +21 -0
- package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +1 -0
- package/dist/adapters/drizzle/drizzle-adapter.js +62 -0
- package/dist/adapters/drizzle/drizzle-adapter.js.map +1 -0
- package/dist/adapters/drizzle/drizzle-query.d.ts +17 -0
- package/dist/adapters/drizzle/drizzle-query.d.ts.map +1 -0
- package/dist/adapters/drizzle/drizzle-query.js +139 -0
- package/dist/adapters/drizzle/drizzle-query.js.map +1 -0
- package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts +9 -0
- package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts.map +1 -0
- package/dist/adapters/drizzle/drizzle-uow-compiler.js +300 -0
- package/dist/adapters/drizzle/drizzle-uow-compiler.js.map +1 -0
- package/dist/adapters/drizzle/drizzle-uow-decoder.js +82 -0
- package/dist/adapters/drizzle/drizzle-uow-decoder.js.map +1 -0
- package/dist/adapters/drizzle/drizzle-uow-executor.js +125 -0
- package/dist/adapters/drizzle/drizzle-uow-executor.js.map +1 -0
- package/dist/adapters/drizzle/generate.js +273 -0
- package/dist/adapters/drizzle/generate.js.map +1 -0
- package/dist/adapters/drizzle/join-column-utils.js +28 -0
- package/dist/adapters/drizzle/join-column-utils.js.map +1 -0
- package/dist/adapters/drizzle/shared.js +11 -0
- package/dist/adapters/drizzle/shared.js.map +1 -0
- package/dist/adapters/kysely/kysely-adapter.d.ts +23 -0
- package/dist/adapters/kysely/kysely-adapter.d.ts.map +1 -0
- package/dist/adapters/kysely/kysely-adapter.js +119 -0
- package/dist/adapters/kysely/kysely-adapter.js.map +1 -0
- package/dist/adapters/kysely/kysely-query-builder.js +306 -0
- package/dist/adapters/kysely/kysely-query-builder.js.map +1 -0
- package/dist/adapters/kysely/kysely-query-compiler.js +67 -0
- package/dist/adapters/kysely/kysely-query-compiler.js.map +1 -0
- package/dist/adapters/kysely/kysely-query.js +158 -0
- package/dist/adapters/kysely/kysely-query.js.map +1 -0
- package/dist/adapters/kysely/kysely-uow-compiler.js +139 -0
- package/dist/adapters/kysely/kysely-uow-compiler.js.map +1 -0
- package/dist/adapters/kysely/kysely-uow-executor.js +89 -0
- package/dist/adapters/kysely/kysely-uow-executor.js.map +1 -0
- package/dist/adapters/kysely/migration/execute.js +176 -0
- package/dist/adapters/kysely/migration/execute.js.map +1 -0
- package/dist/fragment.d.ts +54 -0
- package/dist/fragment.d.ts.map +1 -0
- package/dist/fragment.js +92 -0
- package/dist/fragment.js.map +1 -0
- package/dist/id.d.ts +2 -0
- package/dist/migration-engine/auto-from-schema.js +116 -0
- package/dist/migration-engine/auto-from-schema.js.map +1 -0
- package/dist/migration-engine/create.d.ts +41 -0
- package/dist/migration-engine/create.d.ts.map +1 -0
- package/dist/migration-engine/create.js +58 -0
- package/dist/migration-engine/create.js.map +1 -0
- package/dist/migration-engine/shared.d.ts +90 -0
- package/dist/migration-engine/shared.d.ts.map +1 -0
- package/dist/migration-engine/shared.js +8 -0
- package/dist/migration-engine/shared.js.map +1 -0
- package/dist/mod.d.ts +55 -2
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +111 -2
- package/dist/mod.js.map +1 -1
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/column-builder.js +108 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/column-builder.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/column.js +55 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/column.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/entity.js +18 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/entity.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/columns/common.js +183 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/columns/common.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/columns/enum.js +58 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/columns/enum.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/foreign-keys.js +68 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/foreign-keys.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/unique-constraint.js +56 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/unique-constraint.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/utils/array.js +65 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/utils/array.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/expressions/conditions.js +81 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/expressions/conditions.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/expressions/select.js +13 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/expressions/select.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/functions/aggregate.js +10 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/functions/aggregate.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/sql.js +372 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/sql.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/subquery.js +23 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/subquery.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/table.js +62 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/table.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/table.utils.js +6 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/table.utils.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/tracing-utils.js +8 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/tracing-utils.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/tracing.js +8 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/tracing.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/view-common.js +6 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/view-common.js.map +1 -0
- package/dist/query/condition-builder.d.ts +41 -0
- package/dist/query/condition-builder.d.ts.map +1 -0
- package/dist/query/condition-builder.js +93 -0
- package/dist/query/condition-builder.js.map +1 -0
- package/dist/query/cursor.d.ts +88 -0
- package/dist/query/cursor.d.ts.map +1 -0
- package/dist/query/cursor.js +103 -0
- package/dist/query/cursor.js.map +1 -0
- package/dist/query/orm/orm.d.ts +18 -0
- package/dist/query/orm/orm.d.ts.map +1 -0
- package/dist/query/orm/orm.js +48 -0
- package/dist/query/orm/orm.js.map +1 -0
- package/dist/query/query.d.ts +79 -0
- package/dist/query/query.d.ts.map +1 -0
- package/dist/query/query.js +1 -0
- package/dist/query/result-transform.js +155 -0
- package/dist/query/result-transform.js.map +1 -0
- package/dist/query/unit-of-work.d.ts +435 -0
- package/dist/query/unit-of-work.d.ts.map +1 -0
- package/dist/query/unit-of-work.js +549 -0
- package/dist/query/unit-of-work.js.map +1 -0
- package/dist/schema/create.d.ts +273 -116
- package/dist/schema/create.d.ts.map +1 -1
- package/dist/schema/create.js +410 -222
- package/dist/schema/create.js.map +1 -1
- package/dist/schema/serialize.js +101 -0
- package/dist/schema/serialize.js.map +1 -0
- package/dist/schema-generator/schema-generator.d.ts +15 -0
- package/dist/schema-generator/schema-generator.d.ts.map +1 -0
- package/dist/shared/providers.d.ts +6 -0
- package/dist/shared/providers.d.ts.map +1 -0
- package/dist/util/import-generator.js +26 -0
- package/dist/util/import-generator.js.map +1 -0
- package/dist/util/parse.js +15 -0
- package/dist/util/parse.js.map +1 -0
- package/dist/util/types.d.ts +8 -0
- package/dist/util/types.d.ts.map +1 -0
- package/package.json +63 -2
- package/src/adapters/adapters.ts +22 -0
- package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +433 -0
- package/src/adapters/drizzle/drizzle-adapter.test.ts +122 -0
- package/src/adapters/drizzle/drizzle-adapter.ts +118 -0
- package/src/adapters/drizzle/drizzle-query.ts +234 -0
- package/src/adapters/drizzle/drizzle-uow-compiler.test.ts +1084 -0
- package/src/adapters/drizzle/drizzle-uow-compiler.ts +546 -0
- package/src/adapters/drizzle/drizzle-uow-decoder.ts +165 -0
- package/src/adapters/drizzle/drizzle-uow-executor.ts +213 -0
- package/src/adapters/drizzle/generate.test.ts +643 -0
- package/src/adapters/drizzle/generate.ts +481 -0
- package/src/adapters/drizzle/join-column-utils.test.ts +79 -0
- package/src/adapters/drizzle/join-column-utils.ts +39 -0
- package/src/adapters/drizzle/migrate-drizzle.test.ts +226 -0
- package/src/adapters/drizzle/shared.ts +22 -0
- package/src/adapters/drizzle/test-utils.ts +56 -0
- package/src/adapters/kysely/kysely-adapter-pglite.test.ts +789 -0
- package/src/adapters/kysely/kysely-adapter.ts +196 -0
- package/src/adapters/kysely/kysely-query-builder.test.ts +1344 -0
- package/src/adapters/kysely/kysely-query-builder.ts +611 -0
- package/src/adapters/kysely/kysely-query-compiler.ts +124 -0
- package/src/adapters/kysely/kysely-query.ts +254 -0
- package/src/adapters/kysely/kysely-uow-compiler.test.ts +916 -0
- package/src/adapters/kysely/kysely-uow-compiler.ts +271 -0
- package/src/adapters/kysely/kysely-uow-executor.ts +149 -0
- package/src/adapters/kysely/kysely-uow-joins.test.ts +811 -0
- package/src/adapters/kysely/migration/execute-mysql.test.ts +1173 -0
- package/src/adapters/kysely/migration/execute-postgres.test.ts +2657 -0
- package/src/adapters/kysely/migration/execute.ts +382 -0
- package/src/adapters/kysely/migration/kysely-migrator.test.ts +197 -0
- package/src/fragment.test.ts +287 -0
- package/src/fragment.ts +198 -0
- package/src/migration-engine/auto-from-schema.test.ts +118 -58
- package/src/migration-engine/auto-from-schema.ts +103 -32
- package/src/migration-engine/create.test.ts +34 -46
- package/src/migration-engine/create.ts +41 -26
- package/src/migration-engine/shared.ts +26 -6
- package/src/mod.ts +197 -1
- package/src/query/condition-builder.test.ts +379 -0
- package/src/query/condition-builder.ts +294 -0
- package/src/query/cursor.test.ts +296 -0
- package/src/query/cursor.ts +147 -0
- package/src/query/orm/orm.ts +92 -0
- package/src/query/query-type.test.ts +429 -0
- package/src/query/query.ts +200 -0
- package/src/query/result-transform.test.ts +795 -0
- package/src/query/result-transform.ts +247 -0
- package/src/query/unit-of-work-types.test.ts +192 -0
- package/src/query/unit-of-work.test.ts +947 -0
- package/src/query/unit-of-work.ts +1199 -0
- package/src/schema/create.test.ts +653 -110
- package/src/schema/create.ts +708 -337
- package/src/schema/serialize.test.ts +559 -0
- package/src/schema/serialize.ts +359 -0
- package/src/schema-generator/schema-generator.ts +12 -0
- package/src/shared/config.ts +0 -8
- package/src/util/import-generator.ts +28 -0
- package/src/util/parse.ts +16 -0
- package/src/util/types.ts +4 -0
- package/tsconfig.json +1 -1
- package/tsdown.config.ts +11 -1
- package/vitest.config.ts +3 -0
- /package/dist/{cuid.js → id.js} +0 -0
- /package/src/{cuid.ts → id.ts} +0 -0
package/src/schema/create.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createId } from "../
|
|
1
|
+
import { createId } from "../id";
|
|
2
2
|
|
|
3
3
|
export type AnySchema = Schema<Record<string, AnyTable>>;
|
|
4
4
|
|
|
@@ -8,17 +8,23 @@ export type AnyTable = Table;
|
|
|
8
8
|
|
|
9
9
|
export type AnyColumn =
|
|
10
10
|
| Column<keyof TypeMap, unknown, unknown>
|
|
11
|
-
| IdColumn<IdColumnType, unknown, unknown
|
|
12
|
-
|
|
11
|
+
| IdColumn<IdColumnType, unknown, unknown>
|
|
12
|
+
| InternalIdColumn<unknown, unknown>
|
|
13
|
+
| VersionColumn<unknown, unknown>;
|
|
13
14
|
/**
|
|
14
|
-
*
|
|
15
|
+
* Sub-operations that can be performed within table operations.
|
|
16
|
+
* These are stored in order within add-table and alter-table operations.
|
|
15
17
|
*/
|
|
16
|
-
export type
|
|
17
|
-
type: "add-
|
|
18
|
-
name: string;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
export type TableSubOperation =
|
|
19
|
+
| { type: "add-column"; columnName: string; column: AnyColumn }
|
|
20
|
+
| { type: "add-index"; name: string; columns: string[]; unique: boolean }
|
|
21
|
+
| {
|
|
22
|
+
type: "add-foreign-key";
|
|
23
|
+
name: string;
|
|
24
|
+
columns: string[];
|
|
25
|
+
referencedTable: string;
|
|
26
|
+
referencedColumns: string[];
|
|
27
|
+
};
|
|
22
28
|
|
|
23
29
|
/**
|
|
24
30
|
* Operations that can be performed on a schema during its definition.
|
|
@@ -28,24 +34,22 @@ export type SchemaOperation =
|
|
|
28
34
|
| {
|
|
29
35
|
type: "add-table";
|
|
30
36
|
tableName: string;
|
|
31
|
-
|
|
37
|
+
operations: TableSubOperation[]; // Ordered list of sub-operations
|
|
32
38
|
}
|
|
33
39
|
| {
|
|
34
|
-
type: "
|
|
40
|
+
type: "alter-table";
|
|
35
41
|
tableName: string;
|
|
42
|
+
operations: TableSubOperation[]; // Ordered list of sub-operations
|
|
43
|
+
}
|
|
44
|
+
| {
|
|
45
|
+
type: "add-reference";
|
|
46
|
+
tableName: string; // The table that has the foreign key
|
|
36
47
|
referenceName: string;
|
|
37
48
|
config: {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
49
|
+
type: "one" | "many";
|
|
50
|
+
from: { table: string; column: string };
|
|
51
|
+
to: { table: string; column: string };
|
|
41
52
|
};
|
|
42
|
-
}
|
|
43
|
-
| {
|
|
44
|
-
type: "add-index";
|
|
45
|
-
tableName: string;
|
|
46
|
-
name: string;
|
|
47
|
-
columns: string[];
|
|
48
|
-
unique: boolean;
|
|
49
53
|
};
|
|
50
54
|
|
|
51
55
|
export interface ForeignKey {
|
|
@@ -74,61 +78,26 @@ class RelationInit<
|
|
|
74
78
|
}
|
|
75
79
|
}
|
|
76
80
|
|
|
77
|
-
export interface Index
|
|
81
|
+
export interface Index<
|
|
82
|
+
TColumns extends AnyColumn[] = AnyColumn[],
|
|
83
|
+
TColumnNames extends readonly string[] = readonly string[],
|
|
84
|
+
> {
|
|
78
85
|
name: string;
|
|
79
|
-
columns:
|
|
86
|
+
columns: TColumns;
|
|
87
|
+
columnNames: TColumnNames;
|
|
80
88
|
unique: boolean;
|
|
81
89
|
}
|
|
82
90
|
|
|
83
|
-
/**
|
|
84
|
-
* Helper function to add an index to a table's index array
|
|
85
|
-
*/
|
|
86
|
-
function addIndexToTable(
|
|
87
|
-
indexes: Index[],
|
|
88
|
-
name: string,
|
|
89
|
-
columns: AnyColumn[],
|
|
90
|
-
unique: boolean,
|
|
91
|
-
): void {
|
|
92
|
-
indexes.push({
|
|
93
|
-
name,
|
|
94
|
-
columns,
|
|
95
|
-
unique,
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
91
|
export class ExplicitRelationInit<
|
|
100
92
|
TRelationType extends RelationType,
|
|
101
93
|
TTables extends Record<string, AnyTable>,
|
|
102
94
|
TTableName extends keyof TTables,
|
|
103
95
|
> extends RelationInit<TRelationType, TTables, TTableName> {
|
|
104
|
-
private foreignKeyName?: string;
|
|
105
|
-
|
|
106
|
-
private initForeignKey(ormName: string): ForeignKey {
|
|
107
|
-
const columns: AnyColumn[] = [];
|
|
108
|
-
const referencedColumns: AnyColumn[] = [];
|
|
109
|
-
|
|
110
|
-
for (const [left, right] of this.on) {
|
|
111
|
-
columns.push(this.referencer.columns[left]);
|
|
112
|
-
referencedColumns.push(this.referencedTable.columns[right]);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return {
|
|
116
|
-
columns,
|
|
117
|
-
referencedColumns,
|
|
118
|
-
referencedTable: this.referencedTable,
|
|
119
|
-
table: this.referencer,
|
|
120
|
-
name:
|
|
121
|
-
this.foreignKeyName ??
|
|
122
|
-
`${this.referencer.ormName}_${this.referencedTable.ormName}_${ormName}_fk`,
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
|
|
126
96
|
init(ormName: string): Relation<TRelationType, TTables[TTableName]> {
|
|
127
97
|
const id = `${this.referencer.ormName}_${this.referencedTable.ormName}`;
|
|
128
98
|
|
|
129
99
|
return {
|
|
130
100
|
id,
|
|
131
|
-
foreignKey: this.initForeignKey(ormName),
|
|
132
101
|
on: this.on,
|
|
133
102
|
name: ormName,
|
|
134
103
|
referencer: this.referencer,
|
|
@@ -136,14 +105,6 @@ export class ExplicitRelationInit<
|
|
|
136
105
|
type: this.type,
|
|
137
106
|
};
|
|
138
107
|
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Define custom foreign key name.
|
|
142
|
-
*/
|
|
143
|
-
foreignKey(name: string) {
|
|
144
|
-
this.foreignKeyName = name;
|
|
145
|
-
return this;
|
|
146
|
-
}
|
|
147
108
|
}
|
|
148
109
|
|
|
149
110
|
export interface Relation<
|
|
@@ -158,28 +119,36 @@ export interface Relation<
|
|
|
158
119
|
referencer: AnyTable;
|
|
159
120
|
|
|
160
121
|
on: [string, string][];
|
|
161
|
-
foreignKey: ForeignKey;
|
|
162
122
|
}
|
|
163
123
|
|
|
164
124
|
export interface Table<
|
|
165
125
|
TColumns extends Record<string, AnyColumn> = Record<string, AnyColumn>,
|
|
166
126
|
TRelations extends Record<string, AnyRelation> = Record<string, AnyRelation>,
|
|
127
|
+
TIndexes extends Record<string, Index> = Record<string, Index>,
|
|
167
128
|
> {
|
|
168
129
|
name: string;
|
|
169
130
|
ormName: string;
|
|
170
131
|
|
|
171
132
|
columns: TColumns;
|
|
172
133
|
relations: TRelations;
|
|
173
|
-
|
|
174
|
-
indexes: Index[];
|
|
134
|
+
indexes: TIndexes;
|
|
175
135
|
|
|
176
136
|
/**
|
|
177
137
|
* Get column by name
|
|
178
138
|
*/
|
|
179
139
|
getColumnByName: (name: string) => AnyColumn | undefined;
|
|
140
|
+
/**
|
|
141
|
+
* Get the external ID column (user-facing)
|
|
142
|
+
*/
|
|
180
143
|
getIdColumn: () => AnyColumn;
|
|
181
|
-
|
|
182
|
-
|
|
144
|
+
/**
|
|
145
|
+
* Get the internal ID column (database-native, used for joins)
|
|
146
|
+
*/
|
|
147
|
+
getInternalIdColumn: () => AnyColumn;
|
|
148
|
+
/**
|
|
149
|
+
* Get the version column (for optimistic concurrency control)
|
|
150
|
+
*/
|
|
151
|
+
getVersionColumn: () => AnyColumn;
|
|
183
152
|
}
|
|
184
153
|
|
|
185
154
|
type DefaultFunctionMap = {
|
|
@@ -214,15 +183,16 @@ export class Column<TType extends keyof TypeMap, TIn = unknown, TOut = unknown>
|
|
|
214
183
|
name: string = "";
|
|
215
184
|
ormName: string = "";
|
|
216
185
|
isNullable: boolean = false;
|
|
217
|
-
|
|
218
|
-
|
|
186
|
+
role: "external-id" | "internal-id" | "version" | "reference" | "regular" = "regular";
|
|
187
|
+
isHidden: boolean = false;
|
|
188
|
+
|
|
219
189
|
default?:
|
|
220
190
|
| { value: TypeMap[TType] }
|
|
221
191
|
| {
|
|
222
192
|
runtime: DefaultFunction<TType>;
|
|
223
193
|
};
|
|
224
194
|
|
|
225
|
-
|
|
195
|
+
tableName: string = "";
|
|
226
196
|
|
|
227
197
|
constructor(type: TType) {
|
|
228
198
|
this.type = type;
|
|
@@ -238,6 +208,11 @@ export class Column<TType extends keyof TypeMap, TIn = unknown, TOut = unknown>
|
|
|
238
208
|
>;
|
|
239
209
|
}
|
|
240
210
|
|
|
211
|
+
hidden<THidden extends boolean = true>(hidden?: THidden) {
|
|
212
|
+
this.isHidden = hidden ?? true;
|
|
213
|
+
return this as Column<TType, null, null>;
|
|
214
|
+
}
|
|
215
|
+
|
|
241
216
|
/**
|
|
242
217
|
* Generate default value on runtime
|
|
243
218
|
*/
|
|
@@ -256,22 +231,6 @@ export class Column<TType extends keyof TypeMap, TIn = unknown, TOut = unknown>
|
|
|
256
231
|
return this;
|
|
257
232
|
}
|
|
258
233
|
|
|
259
|
-
clone() {
|
|
260
|
-
const clone = new Column(this.type);
|
|
261
|
-
clone.name = this.name;
|
|
262
|
-
clone.ormName = this.ormName;
|
|
263
|
-
clone.isNullable = this.isNullable;
|
|
264
|
-
clone.isUnique = this.isUnique;
|
|
265
|
-
clone.isReference = this.isReference;
|
|
266
|
-
clone.default = this.default;
|
|
267
|
-
clone.table = this.table;
|
|
268
|
-
return clone;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
getUniqueConstraintName(): string {
|
|
272
|
-
return `unique_c_${this.table.ormName}_${this.ormName}`;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
234
|
/**
|
|
276
235
|
* Generate default value for the column on runtime.
|
|
277
236
|
*/
|
|
@@ -293,11 +252,20 @@ export class Column<TType extends keyof TypeMap, TIn = unknown, TOut = unknown>
|
|
|
293
252
|
return this.default.runtime();
|
|
294
253
|
}
|
|
295
254
|
|
|
255
|
+
/**
|
|
256
|
+
* @description This is used for type inference only. Runtime value will be undefined.
|
|
257
|
+
* @internal
|
|
258
|
+
*/
|
|
296
259
|
get $in(): TIn {
|
|
297
|
-
|
|
260
|
+
return undefined as unknown as TIn;
|
|
298
261
|
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* @description This is used for type inference only. Runtime value will be undefined.
|
|
265
|
+
* @internal
|
|
266
|
+
*/
|
|
299
267
|
get $out(): TOut {
|
|
300
|
-
|
|
268
|
+
return undefined as unknown as TOut;
|
|
301
269
|
}
|
|
302
270
|
}
|
|
303
271
|
|
|
@@ -308,18 +276,6 @@ export class IdColumn<
|
|
|
308
276
|
> extends Column<TType, TIn, TOut> {
|
|
309
277
|
id = true;
|
|
310
278
|
|
|
311
|
-
clone() {
|
|
312
|
-
const clone = new IdColumn(this.type);
|
|
313
|
-
clone.name = this.name;
|
|
314
|
-
clone.ormName = this.ormName;
|
|
315
|
-
clone.isNullable = this.isNullable;
|
|
316
|
-
clone.isUnique = this.isUnique;
|
|
317
|
-
clone.isReference = this.isReference;
|
|
318
|
-
clone.default = this.default;
|
|
319
|
-
clone.table = this.table;
|
|
320
|
-
return clone;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
279
|
override defaultTo$(fn: DefaultFunction<TType>) {
|
|
324
280
|
return super.defaultTo$(fn) as IdColumn<TType, TIn | null, TOut>;
|
|
325
281
|
}
|
|
@@ -329,6 +285,32 @@ export class IdColumn<
|
|
|
329
285
|
}
|
|
330
286
|
}
|
|
331
287
|
|
|
288
|
+
/**
|
|
289
|
+
* Internal ID column - used for database-native joins and foreign keys.
|
|
290
|
+
* Hidden from user API by default.
|
|
291
|
+
*/
|
|
292
|
+
export class InternalIdColumn<TIn = unknown, TOut = unknown> extends Column<"bigint", TIn, TOut> {
|
|
293
|
+
override role = "internal-id" as const;
|
|
294
|
+
|
|
295
|
+
constructor() {
|
|
296
|
+
super("bigint");
|
|
297
|
+
this.hidden();
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Version column - used for optimistic concurrency control.
|
|
303
|
+
* Automatically incremented on each update.
|
|
304
|
+
*/
|
|
305
|
+
export class VersionColumn<TIn = unknown, TOut = unknown> extends Column<"integer", TIn, TOut> {
|
|
306
|
+
override role = "version" as const;
|
|
307
|
+
|
|
308
|
+
constructor() {
|
|
309
|
+
super("integer");
|
|
310
|
+
this.defaultTo(0).hidden();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
332
314
|
export function column<TType extends keyof TypeMap>(
|
|
333
315
|
type: TType,
|
|
334
316
|
): Column<TType, TypeMap[TType], TypeMap[TType]> {
|
|
@@ -336,140 +318,316 @@ export function column<TType extends keyof TypeMap>(
|
|
|
336
318
|
}
|
|
337
319
|
|
|
338
320
|
/**
|
|
339
|
-
* Create a reference column that points to another table.
|
|
321
|
+
* Create a reference column that points to another table's internal ID.
|
|
340
322
|
* This is used for foreign key relationships.
|
|
323
|
+
* Always uses bigint to match the internal ID type.
|
|
341
324
|
*/
|
|
342
|
-
export function referenceColumn
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
col
|
|
348
|
-
|
|
325
|
+
export function referenceColumn(): Column<
|
|
326
|
+
"bigint",
|
|
327
|
+
string | bigint | FragnoId | FragnoReference,
|
|
328
|
+
FragnoReference
|
|
329
|
+
> {
|
|
330
|
+
const col = new Column<"bigint", string | bigint | FragnoId | FragnoReference, FragnoReference>(
|
|
331
|
+
"bigint",
|
|
332
|
+
);
|
|
333
|
+
col.role = "reference";
|
|
334
|
+
return col;
|
|
349
335
|
}
|
|
350
336
|
|
|
351
|
-
|
|
352
|
-
|
|
337
|
+
/**
|
|
338
|
+
* Create an external ID column (user-facing).
|
|
339
|
+
* This is a CUID string that can be auto-generated or user-provided.
|
|
340
|
+
* Input accepts string | FragnoId | null, output returns FragnoId.
|
|
341
|
+
*/
|
|
342
|
+
export function idColumn(): IdColumn<"varchar(30)", string | FragnoId | null, FragnoId> {
|
|
343
|
+
const col = new IdColumn<"varchar(30)", string | FragnoId | null, FragnoId>("varchar(30)");
|
|
344
|
+
col.role = "external-id";
|
|
353
345
|
col.defaultTo$("auto");
|
|
354
|
-
return col
|
|
346
|
+
return col;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Create an internal ID column (database-native, hidden from user API).
|
|
351
|
+
* Used for joins and foreign keys.
|
|
352
|
+
* @internal
|
|
353
|
+
*/
|
|
354
|
+
export function internalIdColumn(): InternalIdColumn<null, bigint> {
|
|
355
|
+
const col = new InternalIdColumn<null, bigint>();
|
|
356
|
+
col.role = "internal-id";
|
|
357
|
+
col.hidden();
|
|
358
|
+
return col;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Create a version column for optimistic concurrency control.
|
|
363
|
+
* @internal
|
|
364
|
+
*/
|
|
365
|
+
export function versionColumn(): VersionColumn<null, number> {
|
|
366
|
+
const col = new VersionColumn<null, number>();
|
|
367
|
+
col.role = "version";
|
|
368
|
+
col.hidden();
|
|
369
|
+
return col;
|
|
355
370
|
}
|
|
356
371
|
|
|
357
|
-
|
|
372
|
+
/**
|
|
373
|
+
* FragnoId represents a unified ID object that can contain external ID, internal ID, or both.
|
|
374
|
+
* @internal
|
|
375
|
+
*
|
|
376
|
+
* For query inputs: externalId is sufficient (internalId is optional)
|
|
377
|
+
* For query results: both externalId and internalId are provided
|
|
378
|
+
*/
|
|
379
|
+
export class FragnoId {
|
|
380
|
+
readonly #externalId: string;
|
|
381
|
+
readonly #internalId?: bigint;
|
|
382
|
+
readonly #version: number;
|
|
383
|
+
|
|
384
|
+
constructor({
|
|
385
|
+
externalId,
|
|
386
|
+
internalId,
|
|
387
|
+
version,
|
|
388
|
+
}: {
|
|
389
|
+
externalId: string;
|
|
390
|
+
internalId?: bigint;
|
|
391
|
+
version: number;
|
|
392
|
+
}) {
|
|
393
|
+
this.#externalId = externalId;
|
|
394
|
+
this.#internalId = internalId;
|
|
395
|
+
this.#version = version;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Create a FragnoId from just an external ID (for inputs)
|
|
400
|
+
*/
|
|
401
|
+
static fromExternal(externalId: string, version: number): FragnoId {
|
|
402
|
+
return new FragnoId({ externalId, version });
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
get version(): number {
|
|
406
|
+
return this.#version;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
get externalId(): string {
|
|
410
|
+
return this.#externalId;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
get internalId(): bigint | undefined {
|
|
414
|
+
return this.#internalId;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Get the appropriate ID for database operations
|
|
419
|
+
* Prefers internal ID if available, falls back to external ID
|
|
420
|
+
*/
|
|
421
|
+
get databaseId(): string | bigint {
|
|
422
|
+
return this.#internalId ?? this.#externalId;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Convert to a plain object for serialization
|
|
427
|
+
*/
|
|
428
|
+
toJSON(): { externalId: string; internalId?: string } {
|
|
429
|
+
return {
|
|
430
|
+
externalId: this.#externalId,
|
|
431
|
+
internalId: this.#internalId?.toString(),
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
toString(): string {
|
|
436
|
+
return this.#externalId;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
valueOf(): string {
|
|
440
|
+
return this.#externalId;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* FragnoReference represents a foreign key reference to another table's internal ID.
|
|
446
|
+
* Unlike FragnoId, it only contains the internal ID (bigint) of the referenced record.
|
|
447
|
+
* This is used for reference columns in query results.
|
|
448
|
+
* @internal
|
|
449
|
+
*/
|
|
450
|
+
export class FragnoReference {
|
|
451
|
+
readonly #internalId: bigint;
|
|
452
|
+
|
|
453
|
+
constructor(internalId: bigint) {
|
|
454
|
+
this.#internalId = internalId;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Create a FragnoReference from an internal ID
|
|
459
|
+
*/
|
|
460
|
+
static fromInternal(internalId: bigint): FragnoReference {
|
|
461
|
+
return new FragnoReference(internalId);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Get the internal ID for database operations
|
|
466
|
+
*/
|
|
467
|
+
get internalId(): bigint {
|
|
468
|
+
return this.#internalId;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
type RelationType = "one" | "many";
|
|
358
473
|
|
|
359
474
|
export class TableBuilder<
|
|
360
475
|
TColumns extends Record<string, AnyColumn> = Record<string, AnyColumn>,
|
|
361
476
|
TRelations extends Record<string, AnyRelation> = Record<string, AnyRelation>,
|
|
477
|
+
TIndexes extends Record<string, Index> = Record<string, Index>,
|
|
362
478
|
> {
|
|
363
479
|
#name: string;
|
|
364
480
|
#columns: TColumns;
|
|
365
481
|
#relations: TRelations;
|
|
366
|
-
#
|
|
367
|
-
#indexes: Index[] = [];
|
|
368
|
-
#version: number = 0;
|
|
482
|
+
#indexes: TIndexes;
|
|
369
483
|
#ormName: string = "";
|
|
370
|
-
#
|
|
484
|
+
#columnOrder: string[] = [];
|
|
371
485
|
|
|
372
486
|
constructor(name: string) {
|
|
373
487
|
this.#name = name;
|
|
374
488
|
this.#columns = {} as TColumns;
|
|
375
489
|
this.#relations = {} as TRelations;
|
|
490
|
+
this.#indexes = {} as TIndexes;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// For alterTable to set existing state
|
|
494
|
+
setColumns(columns: TColumns): void {
|
|
495
|
+
this.#columns = { ...columns };
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
setRelations(relations: TRelations): void {
|
|
499
|
+
this.#relations = { ...relations };
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
setIndexes(indexes: TIndexes): void {
|
|
503
|
+
this.#indexes = { ...indexes };
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// For SchemaBuilder to read collected indexes
|
|
507
|
+
getIndexes(): Index[] {
|
|
508
|
+
return Object.values(this.#indexes) as Index[];
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
getColumnOrder(): string[] {
|
|
512
|
+
return this.#columnOrder;
|
|
376
513
|
}
|
|
377
514
|
|
|
378
515
|
/**
|
|
379
|
-
* Add a column to the table.
|
|
516
|
+
* Add a column to the table.
|
|
380
517
|
*/
|
|
381
518
|
addColumn<TColumnName extends string, TColumn extends AnyColumn>(
|
|
382
519
|
ormName: TColumnName,
|
|
383
520
|
col: TColumn,
|
|
384
|
-
): TableBuilder<TColumns & Record<TColumnName, TColumn>, TRelations>;
|
|
521
|
+
): TableBuilder<TColumns & Record<TColumnName, TColumn>, TRelations, TIndexes>;
|
|
385
522
|
|
|
386
523
|
/**
|
|
387
|
-
* Add a column to the table with simplified syntax.
|
|
524
|
+
* Add a column to the table with simplified syntax.
|
|
388
525
|
*/
|
|
389
526
|
addColumn<TColumnName extends string, TType extends keyof TypeMap>(
|
|
390
527
|
ormName: TColumnName,
|
|
391
528
|
type: TType,
|
|
392
529
|
): TableBuilder<
|
|
393
530
|
TColumns & Record<TColumnName, Column<TType, TypeMap[TType], TypeMap[TType]>>,
|
|
394
|
-
TRelations
|
|
531
|
+
TRelations,
|
|
532
|
+
TIndexes
|
|
395
533
|
>;
|
|
396
534
|
|
|
397
535
|
addColumn<TColumnName extends string, TColumn extends AnyColumn, TType extends keyof TypeMap>(
|
|
398
536
|
ormName: TColumnName,
|
|
399
537
|
colOrType: TColumn | TType,
|
|
400
|
-
): TableBuilder<TColumns & Record<TColumnName, TColumn>, TRelations> {
|
|
401
|
-
this.#version++;
|
|
402
|
-
|
|
538
|
+
): TableBuilder<TColumns & Record<TColumnName, TColumn>, TRelations, TIndexes> {
|
|
403
539
|
// Create the column if a type string was provided
|
|
404
540
|
const col = typeof colOrType === "string" ? column(colOrType) : colOrType;
|
|
405
541
|
|
|
406
|
-
// Create a new instance to ensure immutability semantics
|
|
407
|
-
const builder = new TableBuilder<TColumns & Record<TColumnName, TColumn>, TRelations>(
|
|
408
|
-
this.#name,
|
|
409
|
-
);
|
|
410
|
-
builder.#columns = { ...this.#columns, [ormName]: col } as TColumns &
|
|
411
|
-
Record<TColumnName, TColumn>;
|
|
412
|
-
builder.#relations = this.#relations;
|
|
413
|
-
builder.#foreignKeys = this.#foreignKeys;
|
|
414
|
-
builder.#indexes = this.#indexes;
|
|
415
|
-
builder.#version = this.#version;
|
|
416
|
-
builder.#ormName = this.#ormName;
|
|
417
|
-
builder.#operations = this.#operations;
|
|
418
|
-
|
|
419
542
|
// Set column metadata
|
|
420
543
|
col.ormName = ormName;
|
|
421
544
|
col.name = ormName;
|
|
422
545
|
|
|
423
|
-
|
|
546
|
+
// Add column directly to this builder
|
|
547
|
+
this.#columns[ormName] = col as unknown as TColumns[TColumnName];
|
|
548
|
+
this.#columnOrder.push(ormName);
|
|
549
|
+
|
|
550
|
+
return this as unknown as TableBuilder<
|
|
551
|
+
TColumns & Record<TColumnName, TColumn>,
|
|
552
|
+
TRelations,
|
|
553
|
+
TIndexes
|
|
554
|
+
>;
|
|
424
555
|
}
|
|
425
556
|
|
|
426
557
|
/**
|
|
427
|
-
* Create an index on the specified columns.
|
|
558
|
+
* Create an index on the specified columns.
|
|
428
559
|
*/
|
|
429
|
-
createIndex<
|
|
430
|
-
|
|
431
|
-
|
|
560
|
+
createIndex<
|
|
561
|
+
TIndexName extends string,
|
|
562
|
+
const TColumnNames extends readonly (string & keyof TColumns)[],
|
|
563
|
+
>(
|
|
564
|
+
name: TIndexName,
|
|
565
|
+
columns: TColumnNames,
|
|
432
566
|
options?: { unique?: boolean },
|
|
433
|
-
): TableBuilder<
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
567
|
+
): TableBuilder<
|
|
568
|
+
TColumns,
|
|
569
|
+
TRelations,
|
|
570
|
+
TIndexes & Record<TIndexName, Index<ColumnsToTuple<TColumns, TColumnNames>, TColumnNames>>
|
|
571
|
+
> {
|
|
572
|
+
const cols = columns.map((colName) => {
|
|
573
|
+
const column = this.#columns[colName];
|
|
438
574
|
if (!column) {
|
|
439
|
-
throw new Error(`Unknown column name ${
|
|
575
|
+
throw new Error(`Unknown column name ${colName}`);
|
|
440
576
|
}
|
|
441
577
|
return column;
|
|
442
578
|
});
|
|
443
579
|
|
|
444
580
|
const unique = options?.unique ?? false;
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
// Record the operation
|
|
448
|
-
this.#operations.push({
|
|
449
|
-
type: "add-index",
|
|
581
|
+
// Safe: we're adding the index to the internal indexes object
|
|
582
|
+
this.#indexes[name] = {
|
|
450
583
|
name,
|
|
451
|
-
columns:
|
|
584
|
+
columns: cols,
|
|
585
|
+
columnNames: columns,
|
|
452
586
|
unique,
|
|
453
|
-
}
|
|
587
|
+
} as unknown as TIndexes[TIndexName];
|
|
454
588
|
|
|
455
|
-
return this
|
|
589
|
+
return this as unknown as TableBuilder<
|
|
590
|
+
TColumns,
|
|
591
|
+
TRelations,
|
|
592
|
+
TIndexes & Record<TIndexName, Index<ColumnsToTuple<TColumns, TColumnNames>, TColumnNames>>
|
|
593
|
+
>;
|
|
456
594
|
}
|
|
457
595
|
|
|
458
596
|
/**
|
|
459
597
|
* Build the final table. This should be called after all columns are added.
|
|
460
598
|
*/
|
|
461
|
-
build(): Table<TColumns, TRelations> {
|
|
599
|
+
build(): Table<TColumns, TRelations, TIndexes> {
|
|
462
600
|
let idCol: AnyColumn | undefined;
|
|
601
|
+
let internalIdCol: AnyColumn | undefined;
|
|
602
|
+
let versionCol: AnyColumn | undefined;
|
|
603
|
+
|
|
604
|
+
// TODO: Throw if user manually added version/internalId columns
|
|
605
|
+
|
|
606
|
+
// Auto-add _internalId and _version columns if not already present
|
|
607
|
+
if (!this.#columns["_internalId"]) {
|
|
608
|
+
const col = internalIdColumn();
|
|
609
|
+
col.ormName = "_internalId";
|
|
610
|
+
col.name = "_internalId";
|
|
611
|
+
// Safe: we're adding system columns to the internal columns object
|
|
612
|
+
(this.#columns as Record<string, AnyColumn>)["_internalId"] = col;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (!this.#columns["_version"]) {
|
|
616
|
+
const col = versionColumn();
|
|
617
|
+
col.ormName = "_version";
|
|
618
|
+
col.name = "_version";
|
|
619
|
+
// Safe: we're adding system columns to the internal columns object
|
|
620
|
+
(this.#columns as Record<string, AnyColumn>)["_version"] = col;
|
|
621
|
+
}
|
|
463
622
|
|
|
464
623
|
// Use name as ormName if ormName is not set
|
|
465
624
|
const ormName = this.#ormName || this.#name;
|
|
466
625
|
|
|
467
|
-
const table: Table<TColumns, TRelations> = {
|
|
626
|
+
const table: Table<TColumns, TRelations, TIndexes> = {
|
|
468
627
|
name: this.#name,
|
|
469
628
|
ormName,
|
|
470
629
|
columns: this.#columns,
|
|
471
630
|
relations: this.#relations,
|
|
472
|
-
foreignKeys: this.#foreignKeys,
|
|
473
631
|
indexes: this.#indexes,
|
|
474
632
|
getColumnByName: (name) => {
|
|
475
633
|
return Object.values(this.#columns).find((c) => c.name === name);
|
|
@@ -477,78 +635,45 @@ export class TableBuilder<
|
|
|
477
635
|
getIdColumn: () => {
|
|
478
636
|
return idCol!;
|
|
479
637
|
},
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
const builder = new TableBuilder<TColumns, TRelations>(this.#name);
|
|
488
|
-
builder.#columns = cloneColumns as TColumns;
|
|
489
|
-
builder.#relations = this.#relations;
|
|
490
|
-
builder.#foreignKeys = [...this.#foreignKeys];
|
|
491
|
-
builder.#indexes = [...this.#indexes];
|
|
492
|
-
builder.#version = this.#version;
|
|
493
|
-
builder.#ormName = this.#ormName;
|
|
494
|
-
builder.#operations = [...this.#operations];
|
|
495
|
-
|
|
496
|
-
const cloned = builder.build();
|
|
497
|
-
|
|
498
|
-
return cloned;
|
|
638
|
+
getInternalIdColumn: () => {
|
|
639
|
+
return internalIdCol!;
|
|
640
|
+
},
|
|
641
|
+
getVersionColumn: () => {
|
|
642
|
+
return versionCol!;
|
|
499
643
|
},
|
|
500
644
|
};
|
|
501
645
|
|
|
502
|
-
// Set table reference and find
|
|
646
|
+
// Set table reference and find special columns
|
|
503
647
|
for (const k in this.#columns) {
|
|
504
648
|
const column = this.#columns[k];
|
|
505
649
|
if (!column) {
|
|
506
650
|
continue;
|
|
507
651
|
}
|
|
508
652
|
|
|
509
|
-
column.
|
|
510
|
-
if (column instanceof IdColumn) {
|
|
653
|
+
column.tableName = table.name;
|
|
654
|
+
if (column instanceof IdColumn || column.role === "external-id") {
|
|
511
655
|
idCol = column;
|
|
512
656
|
}
|
|
657
|
+
if (column instanceof InternalIdColumn || column.role === "internal-id") {
|
|
658
|
+
internalIdCol = column;
|
|
659
|
+
}
|
|
660
|
+
if (column instanceof VersionColumn || column.role === "version") {
|
|
661
|
+
versionCol = column;
|
|
662
|
+
}
|
|
513
663
|
}
|
|
514
664
|
|
|
515
665
|
if (idCol === undefined) {
|
|
516
666
|
throw new Error(`there's no id column in your table ${this.#name}`);
|
|
517
667
|
}
|
|
668
|
+
if (internalIdCol === undefined) {
|
|
669
|
+
throw new Error(`there's no internal id column in your table ${this.#name}`);
|
|
670
|
+
}
|
|
671
|
+
if (versionCol === undefined) {
|
|
672
|
+
throw new Error(`there's no version column in your table ${this.#name}`);
|
|
673
|
+
}
|
|
518
674
|
|
|
519
675
|
return table;
|
|
520
676
|
}
|
|
521
|
-
|
|
522
|
-
/**
|
|
523
|
-
* Get the current version of the table builder.
|
|
524
|
-
*/
|
|
525
|
-
getVersion(): number {
|
|
526
|
-
return this.#version;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
/**
|
|
530
|
-
* Get the operations performed on this table.
|
|
531
|
-
*/
|
|
532
|
-
getOperations(): TableOperation[] {
|
|
533
|
-
return this.#operations;
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
/**
|
|
538
|
-
* Create a new table with callback pattern.
|
|
539
|
-
*/
|
|
540
|
-
export function table<
|
|
541
|
-
TColumns extends Record<string, AnyColumn> = Record<string, AnyColumn>,
|
|
542
|
-
TRelations extends Record<string, AnyRelation> = Record<string, AnyRelation>,
|
|
543
|
-
>(
|
|
544
|
-
name: string,
|
|
545
|
-
callback: (
|
|
546
|
-
builder: TableBuilder<Record<string, AnyColumn>, Record<string, AnyRelation>>,
|
|
547
|
-
) => TableBuilder<TColumns, TRelations>,
|
|
548
|
-
): Table<TColumns, TRelations> {
|
|
549
|
-
const builder = new TableBuilder(name);
|
|
550
|
-
const result = callback(builder);
|
|
551
|
-
return result.build();
|
|
552
677
|
}
|
|
553
678
|
|
|
554
679
|
export interface Schema<TTables extends Record<string, AnyTable> = Record<string, AnyTable>> {
|
|
@@ -566,13 +691,90 @@ export interface Schema<TTables extends Record<string, AnyTable> = Record<string
|
|
|
566
691
|
clone: () => Schema<TTables>;
|
|
567
692
|
}
|
|
568
693
|
|
|
569
|
-
|
|
694
|
+
/**
|
|
695
|
+
* Utility type for updating a single table's relations in a schema.
|
|
696
|
+
* Used to properly type the return value of addReference.
|
|
697
|
+
*/
|
|
698
|
+
type UpdateTableRelations<
|
|
699
|
+
TTables extends Record<string, AnyTable>,
|
|
700
|
+
TTableName extends keyof TTables,
|
|
701
|
+
TReferenceName extends string,
|
|
702
|
+
TReferencedTableName extends keyof TTables,
|
|
703
|
+
TRelationType extends RelationType = RelationType,
|
|
704
|
+
> = {
|
|
705
|
+
[K in keyof TTables]: K extends TTableName
|
|
706
|
+
? Table<
|
|
707
|
+
TTables[TTableName]["columns"],
|
|
708
|
+
TTables[TTableName]["relations"] &
|
|
709
|
+
Record<TReferenceName, Relation<TRelationType, TTables[TReferencedTableName]>>,
|
|
710
|
+
TTables[TTableName]["indexes"]
|
|
711
|
+
>
|
|
712
|
+
: TTables[K];
|
|
713
|
+
};
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Utility type for updating a single table in a schema.
|
|
717
|
+
* Used to properly type the return value of alterTable.
|
|
718
|
+
*/
|
|
719
|
+
type UpdateTable<
|
|
720
|
+
TTables extends Record<string, AnyTable>,
|
|
721
|
+
TTableName extends keyof TTables,
|
|
722
|
+
TNewColumns extends Record<string, AnyColumn>,
|
|
723
|
+
TNewRelations extends Record<string, AnyRelation>,
|
|
724
|
+
TNewIndexes extends Record<string, Index>,
|
|
725
|
+
> = {
|
|
726
|
+
[K in keyof TTables]: K extends TTableName
|
|
727
|
+
? Table<TNewColumns, TNewRelations, TNewIndexes>
|
|
728
|
+
: TTables[K];
|
|
729
|
+
};
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Map an array of column names to a tuple of their actual column types
|
|
733
|
+
*/
|
|
734
|
+
type ColumnsToTuple<
|
|
735
|
+
TColumns extends Record<string, AnyColumn>,
|
|
736
|
+
TColumnNames extends readonly (keyof TColumns)[],
|
|
737
|
+
> = {
|
|
738
|
+
[K in keyof TColumnNames]: TColumnNames[K] extends keyof TColumns
|
|
739
|
+
? TColumns[TColumnNames[K]]
|
|
740
|
+
: never;
|
|
741
|
+
} & AnyColumn[];
|
|
742
|
+
|
|
743
|
+
export class SchemaBuilder<TTables extends Record<string, AnyTable> = {}> {
|
|
570
744
|
#tables: TTables;
|
|
571
745
|
#version: number = 0;
|
|
572
746
|
#operations: SchemaOperation[] = [];
|
|
573
747
|
|
|
574
|
-
constructor() {
|
|
575
|
-
|
|
748
|
+
constructor(existingSchema?: Schema<TTables>) {
|
|
749
|
+
if (existingSchema) {
|
|
750
|
+
this.#tables = existingSchema.tables;
|
|
751
|
+
this.#version = existingSchema.version;
|
|
752
|
+
this.#operations = [...existingSchema.operations];
|
|
753
|
+
} else {
|
|
754
|
+
this.#tables = {} as TTables;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Add an existing schema to this builder.
|
|
760
|
+
* Merges tables and operations from the provided schema.
|
|
761
|
+
*
|
|
762
|
+
* @example
|
|
763
|
+
* ```ts
|
|
764
|
+
* const builder = new SchemaBuilder()
|
|
765
|
+
* .add(userSchema)
|
|
766
|
+
* .add(postSchema)
|
|
767
|
+
* .addTable("comments", ...);
|
|
768
|
+
* ```
|
|
769
|
+
*/
|
|
770
|
+
mergeWithExistingSchema<TNewTables extends Record<string, AnyTable>>(
|
|
771
|
+
schema: Schema<TNewTables>,
|
|
772
|
+
): SchemaBuilder<TTables & TNewTables> {
|
|
773
|
+
this.#tables = { ...this.#tables, ...schema.tables } as TTables & TNewTables;
|
|
774
|
+
this.#operations = [...this.#operations, ...schema.operations];
|
|
775
|
+
this.#version += schema.version;
|
|
776
|
+
|
|
777
|
+
return this as unknown as SchemaBuilder<TTables & TNewTables>;
|
|
576
778
|
}
|
|
577
779
|
|
|
578
780
|
/**
|
|
@@ -582,172 +784,309 @@ export class SchemaBuilder<TTables extends Record<string, AnyTable> = Record<str
|
|
|
582
784
|
TTableName extends string,
|
|
583
785
|
TColumns extends Record<string, AnyColumn>,
|
|
584
786
|
TRelations extends Record<string, AnyRelation>,
|
|
787
|
+
TIndexes extends Record<string, Index> = Record<string, Index>,
|
|
585
788
|
>(
|
|
586
789
|
ormName: TTableName,
|
|
587
790
|
callback: (
|
|
588
|
-
builder: TableBuilder<
|
|
589
|
-
|
|
590
|
-
|
|
791
|
+
builder: TableBuilder<
|
|
792
|
+
Record<string, AnyColumn>,
|
|
793
|
+
Record<string, AnyRelation>,
|
|
794
|
+
Record<string, Index>
|
|
795
|
+
>,
|
|
796
|
+
) => TableBuilder<TColumns, TRelations, TIndexes>,
|
|
797
|
+
): SchemaBuilder<TTables & Record<TTableName, Table<TColumns, TRelations, TIndexes>>> {
|
|
591
798
|
this.#version++;
|
|
592
799
|
|
|
593
800
|
const tableBuilder = new TableBuilder(ormName);
|
|
594
801
|
const result = callback(tableBuilder);
|
|
595
802
|
const builtTable = result.build();
|
|
596
|
-
|
|
597
|
-
// Set table metadata
|
|
598
803
|
builtTable.ormName = ormName;
|
|
599
804
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
const
|
|
606
|
-
|
|
607
|
-
{
|
|
608
|
-
type: "add-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
}
|
|
612
|
-
];
|
|
613
|
-
|
|
614
|
-
// Promote table operations to schema operations and increment version for each
|
|
615
|
-
const tableOps = result.getOperations();
|
|
616
|
-
for (const tableOp of tableOps) {
|
|
617
|
-
if (tableOp.type === "add-index") {
|
|
618
|
-
this.#version++;
|
|
619
|
-
newOperations.push({
|
|
620
|
-
type: "add-index",
|
|
621
|
-
tableName: ormName,
|
|
622
|
-
name: tableOp.name,
|
|
623
|
-
columns: tableOp.columns,
|
|
624
|
-
unique: tableOp.unique,
|
|
625
|
-
});
|
|
626
|
-
}
|
|
805
|
+
// Collect sub-operations in order
|
|
806
|
+
const subOperations: TableSubOperation[] = [];
|
|
807
|
+
|
|
808
|
+
// Add user-defined columns first
|
|
809
|
+
const columnOrder = result.getColumnOrder();
|
|
810
|
+
for (const colName of columnOrder) {
|
|
811
|
+
const col = builtTable.columns[colName];
|
|
812
|
+
subOperations.push({
|
|
813
|
+
type: "add-column",
|
|
814
|
+
columnName: colName,
|
|
815
|
+
column: col,
|
|
816
|
+
});
|
|
627
817
|
}
|
|
628
818
|
|
|
629
|
-
|
|
630
|
-
|
|
819
|
+
// Add system columns (_internalId and _version) that were auto-added
|
|
820
|
+
if (builtTable.columns["_internalId"]) {
|
|
821
|
+
subOperations.push({
|
|
822
|
+
type: "add-column",
|
|
823
|
+
columnName: "_internalId",
|
|
824
|
+
column: builtTable.columns["_internalId"],
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
if (builtTable.columns["_version"]) {
|
|
828
|
+
subOperations.push({
|
|
829
|
+
type: "add-column",
|
|
830
|
+
columnName: "_version",
|
|
831
|
+
column: builtTable.columns["_version"],
|
|
832
|
+
});
|
|
833
|
+
}
|
|
631
834
|
|
|
632
|
-
|
|
835
|
+
// Add indexes from builder
|
|
836
|
+
for (const idx of result.getIndexes()) {
|
|
837
|
+
subOperations.push({
|
|
838
|
+
type: "add-index",
|
|
839
|
+
name: idx.name,
|
|
840
|
+
columns: idx.columns.map((c) => c.ormName),
|
|
841
|
+
unique: idx.unique,
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// Add the add-table operation
|
|
846
|
+
this.#operations.push({
|
|
847
|
+
type: "add-table",
|
|
848
|
+
tableName: ormName,
|
|
849
|
+
operations: subOperations,
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
// Update tables map
|
|
853
|
+
this.#tables = { ...this.#tables, [ormName]: builtTable } as TTables &
|
|
854
|
+
Record<TTableName, Table<TColumns, TRelations, TIndexes>>;
|
|
855
|
+
|
|
856
|
+
return this as unknown as SchemaBuilder<
|
|
857
|
+
TTables & Record<TTableName, Table<TColumns, TRelations, TIndexes>>
|
|
858
|
+
>;
|
|
633
859
|
}
|
|
634
860
|
|
|
635
861
|
/**
|
|
636
|
-
* Add a
|
|
862
|
+
* Add a relation between two tables.
|
|
637
863
|
*
|
|
638
|
-
* @param
|
|
639
|
-
* @param
|
|
640
|
-
* @param config - Configuration specifying the foreign key mapping
|
|
864
|
+
* @param referenceName - A name for this relation (e.g., "author", "posts")
|
|
865
|
+
* @param config - Configuration specifying the relation type and foreign key mapping
|
|
641
866
|
*
|
|
642
867
|
* @example
|
|
643
868
|
* ```ts
|
|
644
|
-
* //
|
|
869
|
+
* // One-to-one or many-to-one: post -> user
|
|
645
870
|
* schema(s => s
|
|
646
871
|
* .addTable("users", t => t.addColumn("id", idColumn()))
|
|
647
872
|
* .addTable("posts", t => t
|
|
648
873
|
* .addColumn("id", idColumn())
|
|
649
|
-
* .addColumn("
|
|
650
|
-
* .addReference("
|
|
651
|
-
*
|
|
652
|
-
*
|
|
653
|
-
*
|
|
874
|
+
* .addColumn("userId", referenceColumn()))
|
|
875
|
+
* .addReference("author", {
|
|
876
|
+
* type: "one",
|
|
877
|
+
* from: { table: "posts", column: "userId" },
|
|
878
|
+
* to: { table: "users", column: "id" },
|
|
654
879
|
* })
|
|
655
880
|
* )
|
|
656
881
|
*
|
|
657
|
-
* //
|
|
658
|
-
* .addReference("
|
|
659
|
-
*
|
|
660
|
-
*
|
|
661
|
-
*
|
|
882
|
+
* // One-to-many (inverse relation): user -> posts
|
|
883
|
+
* .addReference("posts", {
|
|
884
|
+
* type: "many",
|
|
885
|
+
* from: { table: "users", column: "id" },
|
|
886
|
+
* to: { table: "posts", column: "userId" },
|
|
662
887
|
* })
|
|
663
888
|
*
|
|
664
|
-
* //
|
|
665
|
-
* .addReference("
|
|
666
|
-
*
|
|
667
|
-
*
|
|
668
|
-
*
|
|
669
|
-
* })
|
|
670
|
-
* .addReference("posts", "category", {
|
|
671
|
-
* columns: ["categoryId"],
|
|
672
|
-
* targetTable: "categories",
|
|
673
|
-
* targetColumns: ["id"],
|
|
889
|
+
* // Self-referencing foreign key
|
|
890
|
+
* .addReference("inviter", {
|
|
891
|
+
* type: "one",
|
|
892
|
+
* from: { table: "users", column: "invitedBy" },
|
|
893
|
+
* to: { table: "users", column: "id" },
|
|
674
894
|
* })
|
|
675
895
|
* ```
|
|
676
896
|
*/
|
|
677
897
|
addReference<
|
|
678
|
-
|
|
679
|
-
|
|
898
|
+
TFromTableName extends string & keyof TTables,
|
|
899
|
+
TToTableName extends string & keyof TTables,
|
|
900
|
+
TReferenceName extends string,
|
|
901
|
+
TRelationType extends RelationType,
|
|
680
902
|
>(
|
|
681
|
-
|
|
682
|
-
referenceName: string,
|
|
903
|
+
referenceName: TReferenceName,
|
|
683
904
|
config: {
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
905
|
+
type: TRelationType;
|
|
906
|
+
from: {
|
|
907
|
+
table: TFromTableName;
|
|
908
|
+
column: keyof TTables[TFromTableName]["columns"];
|
|
909
|
+
};
|
|
910
|
+
to: {
|
|
911
|
+
table: TToTableName;
|
|
912
|
+
column: keyof TTables[TToTableName]["columns"];
|
|
913
|
+
};
|
|
687
914
|
},
|
|
688
|
-
): SchemaBuilder<
|
|
915
|
+
): SchemaBuilder<
|
|
916
|
+
UpdateTableRelations<TTables, TFromTableName, TReferenceName, TToTableName, TRelationType>
|
|
917
|
+
> {
|
|
689
918
|
this.#version++;
|
|
690
919
|
|
|
691
|
-
const table = this.#tables[
|
|
692
|
-
const referencedTable = this.#tables[config.
|
|
920
|
+
const table = this.#tables[config.from.table];
|
|
921
|
+
const referencedTable = this.#tables[config.to.table];
|
|
693
922
|
|
|
694
923
|
if (!table) {
|
|
695
|
-
throw new Error(`Table ${
|
|
924
|
+
throw new Error(`Table ${config.from.table} not found in schema`);
|
|
696
925
|
}
|
|
697
926
|
if (!referencedTable) {
|
|
698
|
-
throw new Error(`Referenced table ${config.
|
|
927
|
+
throw new Error(`Referenced table ${config.to.table} not found in schema`);
|
|
699
928
|
}
|
|
700
929
|
|
|
701
|
-
const
|
|
930
|
+
const columnName = config.from.column as string;
|
|
931
|
+
const targetColumnName = config.to.column as string;
|
|
702
932
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
);
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
// For now, only support single column foreign keys
|
|
710
|
-
if (columns.length !== 1) {
|
|
711
|
-
throw new Error(
|
|
712
|
-
`Reference ${referenceName}: currently only single column foreign keys are supported`,
|
|
713
|
-
);
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
const columnName = columns[0] as string;
|
|
717
|
-
const targetColumnName = targetColumns[0] as string;
|
|
933
|
+
// Foreign keys always reference internal IDs, not external IDs
|
|
934
|
+
// If user specifies "id", translate to "_internalId" for the actual FK
|
|
935
|
+
const actualTargetColumnName = targetColumnName === "id" ? "_internalId" : targetColumnName;
|
|
718
936
|
|
|
719
937
|
const column = table.columns[columnName];
|
|
720
|
-
const referencedColumn = referencedTable.columns[
|
|
938
|
+
const referencedColumn = referencedTable.columns[actualTargetColumnName];
|
|
721
939
|
|
|
722
940
|
if (!column) {
|
|
723
|
-
throw new Error(`Column ${columnName} not found in table ${
|
|
941
|
+
throw new Error(`Column ${columnName} not found in table ${config.from.table}`);
|
|
724
942
|
}
|
|
725
943
|
if (!referencedColumn) {
|
|
726
|
-
throw new Error(`Column ${
|
|
944
|
+
throw new Error(`Column ${actualTargetColumnName} not found in table ${config.to.table}`);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// Verify that reference columns are bigint (matching internal ID type)
|
|
948
|
+
if (column.role === "reference" && column.type !== "bigint") {
|
|
949
|
+
throw new Error(
|
|
950
|
+
`Reference column ${columnName} must be of type bigint to match internal ID type`,
|
|
951
|
+
);
|
|
727
952
|
}
|
|
728
953
|
|
|
729
|
-
// Create the relation
|
|
730
|
-
const init = new ExplicitRelationInit(
|
|
954
|
+
// Create the relation (use the user-facing column name for the relation)
|
|
955
|
+
const init = new ExplicitRelationInit(config.type, referencedTable, table);
|
|
731
956
|
init.on.push([columnName, targetColumnName]);
|
|
732
957
|
const relation = init.init(referenceName);
|
|
733
958
|
|
|
734
|
-
// Add relation
|
|
959
|
+
// Add relation to the table
|
|
735
960
|
table.relations[referenceName] = relation;
|
|
736
|
-
table.foreignKeys.push(relation.foreignKey);
|
|
737
961
|
|
|
738
962
|
// Record the operation
|
|
739
963
|
this.#operations.push({
|
|
740
964
|
type: "add-reference",
|
|
741
|
-
tableName:
|
|
965
|
+
tableName: config.from.table,
|
|
742
966
|
referenceName,
|
|
743
967
|
config: {
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
968
|
+
type: config.type,
|
|
969
|
+
from: { table: config.from.table, column: columnName },
|
|
970
|
+
to: { table: config.to.table, column: actualTargetColumnName },
|
|
747
971
|
},
|
|
748
972
|
});
|
|
749
973
|
|
|
750
|
-
|
|
974
|
+
// Return this with updated type
|
|
975
|
+
// Safe: The relation was added to the table in place and now has the updated relations
|
|
976
|
+
return this as unknown as SchemaBuilder<
|
|
977
|
+
UpdateTableRelations<TTables, TFromTableName, TReferenceName, TToTableName, TRelationType>
|
|
978
|
+
>;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
/**
|
|
982
|
+
* Alter an existing table by adding columns or indexes.
|
|
983
|
+
* This is used for append-only schema modifications.
|
|
984
|
+
*
|
|
985
|
+
* @param tableName - The name of the table to modify
|
|
986
|
+
* @param callback - A callback that receives a table builder for adding columns/indexes
|
|
987
|
+
*
|
|
988
|
+
* @example
|
|
989
|
+
* ```ts
|
|
990
|
+
* // Add a new column to an existing table
|
|
991
|
+
* schema(s => s
|
|
992
|
+
* .addTable("users", t => t
|
|
993
|
+
* .addColumn("id", idColumn())
|
|
994
|
+
* .addColumn("name", column("string")))
|
|
995
|
+
* .alterTable("users", t => t
|
|
996
|
+
* .addColumn("email", column("string"))
|
|
997
|
+
* .addColumn("age", column("integer").nullable())
|
|
998
|
+
* .createIndex("idx_email", ["email"]))
|
|
999
|
+
* )
|
|
1000
|
+
* ```
|
|
1001
|
+
*/
|
|
1002
|
+
alterTable<
|
|
1003
|
+
TTableName extends string & keyof TTables,
|
|
1004
|
+
TNewColumns extends Record<string, AnyColumn>,
|
|
1005
|
+
TNewRelations extends Record<string, AnyRelation>,
|
|
1006
|
+
TNewIndexes extends Record<string, Index> = Record<string, Index>,
|
|
1007
|
+
>(
|
|
1008
|
+
tableName: TTableName,
|
|
1009
|
+
callback: (
|
|
1010
|
+
builder: TableBuilder<
|
|
1011
|
+
TTables[TTableName]["columns"],
|
|
1012
|
+
TTables[TTableName]["relations"],
|
|
1013
|
+
Record<string, Index>
|
|
1014
|
+
>,
|
|
1015
|
+
) => TableBuilder<TNewColumns, TNewRelations, TNewIndexes>,
|
|
1016
|
+
): SchemaBuilder<UpdateTable<TTables, TTableName, TNewColumns, TNewRelations, TNewIndexes>> {
|
|
1017
|
+
const table = this.#tables[tableName];
|
|
1018
|
+
|
|
1019
|
+
if (!table) {
|
|
1020
|
+
throw new Error(`Table ${tableName} not found in schema`);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// Create builder with existing table state
|
|
1024
|
+
const tableBuilder = new TableBuilder(tableName);
|
|
1025
|
+
tableBuilder.setColumns(table.columns);
|
|
1026
|
+
tableBuilder.setRelations(table.relations);
|
|
1027
|
+
tableBuilder.setIndexes(table.indexes);
|
|
1028
|
+
|
|
1029
|
+
// Track existing columns and indexes
|
|
1030
|
+
const existingColumns = new Set(Object.keys(table.columns));
|
|
1031
|
+
const existingIndexes = new Set(Object.keys(table.indexes));
|
|
1032
|
+
|
|
1033
|
+
// Apply modifications
|
|
1034
|
+
const resultBuilder = callback(
|
|
1035
|
+
tableBuilder as TableBuilder<
|
|
1036
|
+
TTables[TTableName]["columns"],
|
|
1037
|
+
TTables[TTableName]["relations"],
|
|
1038
|
+
Record<string, Index>
|
|
1039
|
+
>,
|
|
1040
|
+
);
|
|
1041
|
+
const newTable = resultBuilder.build();
|
|
1042
|
+
|
|
1043
|
+
// Collect sub-operations
|
|
1044
|
+
const subOperations: TableSubOperation[] = [];
|
|
1045
|
+
|
|
1046
|
+
// Find new columns (preserve order from builder)
|
|
1047
|
+
const columnOrder = resultBuilder.getColumnOrder();
|
|
1048
|
+
for (const colName of columnOrder) {
|
|
1049
|
+
if (!existingColumns.has(colName)) {
|
|
1050
|
+
subOperations.push({
|
|
1051
|
+
type: "add-column",
|
|
1052
|
+
columnName: colName,
|
|
1053
|
+
column: newTable.columns[colName],
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
// Add only new indexes
|
|
1059
|
+
for (const idx of resultBuilder.getIndexes()) {
|
|
1060
|
+
if (!existingIndexes.has(idx.name)) {
|
|
1061
|
+
subOperations.push({
|
|
1062
|
+
type: "add-index",
|
|
1063
|
+
name: idx.name,
|
|
1064
|
+
columns: idx.columns.map((c) => c.ormName),
|
|
1065
|
+
unique: idx.unique,
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
if (subOperations.length > 0) {
|
|
1071
|
+
this.#version++;
|
|
1072
|
+
this.#operations.push({
|
|
1073
|
+
type: "alter-table",
|
|
1074
|
+
tableName,
|
|
1075
|
+
operations: subOperations,
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
// Update table reference in schema
|
|
1080
|
+
this.#tables[tableName] = newTable as unknown as TTables[TTableName];
|
|
1081
|
+
|
|
1082
|
+
// Set table name for all columns
|
|
1083
|
+
for (const col of Object.values(newTable.columns)) {
|
|
1084
|
+
col.tableName = newTable.name;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
return this as unknown as SchemaBuilder<
|
|
1088
|
+
UpdateTable<TTables, TTableName, TNewColumns, TNewRelations, TNewIndexes>
|
|
1089
|
+
>;
|
|
751
1090
|
}
|
|
752
1091
|
|
|
753
1092
|
/**
|
|
@@ -766,15 +1105,45 @@ export class SchemaBuilder<TTables extends Record<string, AnyTable> = Record<str
|
|
|
766
1105
|
const cloneTables: Record<string, AnyTable> = {};
|
|
767
1106
|
|
|
768
1107
|
for (const [k, v] of Object.entries(tables)) {
|
|
769
|
-
|
|
1108
|
+
// Create a new table with cloned columns
|
|
1109
|
+
const clonedColumns: Record<string, AnyColumn> = {};
|
|
1110
|
+
for (const [colName, col] of Object.entries(v.columns)) {
|
|
1111
|
+
// Create a new column with the same properties, preserving the column type
|
|
1112
|
+
let clonedCol: AnyColumn;
|
|
1113
|
+
if (col instanceof InternalIdColumn) {
|
|
1114
|
+
clonedCol = new InternalIdColumn();
|
|
1115
|
+
} else if (col instanceof VersionColumn) {
|
|
1116
|
+
clonedCol = new VersionColumn();
|
|
1117
|
+
} else if (col instanceof IdColumn) {
|
|
1118
|
+
clonedCol = new IdColumn(col.type);
|
|
1119
|
+
} else {
|
|
1120
|
+
clonedCol = new Column(col.type);
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
clonedCol.name = col.name;
|
|
1124
|
+
clonedCol.ormName = col.ormName;
|
|
1125
|
+
clonedCol.isNullable = col.isNullable;
|
|
1126
|
+
clonedCol.role = col.role;
|
|
1127
|
+
clonedCol.isHidden = col.isHidden;
|
|
1128
|
+
clonedCol.default = col.default;
|
|
1129
|
+
clonedCol.tableName = col.tableName;
|
|
1130
|
+
clonedColumns[colName] = clonedCol;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
cloneTables[k] = {
|
|
1134
|
+
...v,
|
|
1135
|
+
columns: clonedColumns,
|
|
1136
|
+
};
|
|
770
1137
|
}
|
|
771
1138
|
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
1139
|
+
return new SchemaBuilder<TTables>({
|
|
1140
|
+
version,
|
|
1141
|
+
tables: cloneTables as TTables,
|
|
1142
|
+
operations: [...operations],
|
|
1143
|
+
clone: () => {
|
|
1144
|
+
throw new Error("Cannot clone during clone");
|
|
1145
|
+
},
|
|
1146
|
+
}).build();
|
|
778
1147
|
},
|
|
779
1148
|
};
|
|
780
1149
|
|
|
@@ -792,18 +1161,20 @@ export class SchemaBuilder<TTables extends Record<string, AnyTable> = Record<str
|
|
|
792
1161
|
/**
|
|
793
1162
|
* Create a new schema with callback pattern.
|
|
794
1163
|
*/
|
|
795
|
-
export function schema<TTables extends Record<string, AnyTable> =
|
|
796
|
-
callback: (builder: SchemaBuilder<
|
|
1164
|
+
export function schema<const TTables extends Record<string, AnyTable> = {}>(
|
|
1165
|
+
callback: (builder: SchemaBuilder<{}>) => SchemaBuilder<TTables>,
|
|
797
1166
|
): Schema<TTables> {
|
|
798
1167
|
return callback(new SchemaBuilder()).build();
|
|
799
1168
|
}
|
|
800
1169
|
|
|
801
|
-
export function compileForeignKey(key: ForeignKey) {
|
|
1170
|
+
export function compileForeignKey(key: ForeignKey, nameType: "sql" | "orm" = "orm") {
|
|
802
1171
|
return {
|
|
803
1172
|
name: key.name,
|
|
804
|
-
table: key.table.name,
|
|
805
|
-
referencedTable: key.referencedTable.name,
|
|
806
|
-
referencedColumns: key.referencedColumns.map((col) =>
|
|
807
|
-
|
|
1173
|
+
table: nameType === "sql" ? key.table.name : key.table.ormName,
|
|
1174
|
+
referencedTable: nameType === "sql" ? key.referencedTable.name : key.referencedTable.ormName,
|
|
1175
|
+
referencedColumns: key.referencedColumns.map((col) =>
|
|
1176
|
+
nameType === "sql" ? col.name : col.ormName,
|
|
1177
|
+
),
|
|
1178
|
+
columns: key.columns.map((col) => (nameType === "sql" ? col.name : col.ormName)),
|
|
808
1179
|
};
|
|
809
1180
|
}
|