uql-orm 0.66.0 → 0.67.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/dist/browser/querier/httpQuerier.js +1 -8
- package/dist/browser/uql-browser.min.js.map +5 -5
- package/dist/bunSql/bunSql.util.d.ts +2 -6
- package/dist/bunSql/bunSql.util.js +2 -6
- package/dist/bunSql/bunSqlQuerier.d.ts +2 -5
- package/dist/bunSql/bunSqlQuerier.js +2 -5
- package/dist/cockroachdb/cockroachDialect.d.ts +4 -13
- package/dist/cockroachdb/cockroachDialect.js +4 -13
- package/dist/context/context.browser.js +2 -10
- package/dist/context/context.d.ts +4 -17
- package/dist/context/context.js +4 -17
- package/dist/dialect/abstractDialect.d.ts +4 -19
- package/dist/dialect/abstractDialect.js +2 -20
- package/dist/dialect/abstractSqlDialect.d.ts +47 -212
- package/dist/dialect/abstractSqlDialect.js +68 -222
- package/dist/dialect/aliases.d.ts +2 -12
- package/dist/dialect/aliases.js +4 -12
- package/dist/dialect/hydrateColumn.d.ts +2 -6
- package/dist/dialect/hydrateColumn.js +3 -13
- package/dist/dialect/jsonArrayElemMatchUtils.d.ts +1 -7
- package/dist/dialect/jsonArrayElemMatchUtils.js +1 -7
- package/dist/dialect/jsonSql.d.ts +6 -27
- package/dist/dialect/jsonSql.js +6 -27
- package/dist/dialect/mergeSqlDialect.d.ts +4 -22
- package/dist/dialect/mergeSqlDialect.js +4 -22
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +11 -37
- package/dist/dialect/mysqlLikeSqlDialect.js +35 -51
- package/dist/dialect/pgLikeSqlDialect.d.ts +8 -22
- package/dist/dialect/pgLikeSqlDialect.js +36 -39
- package/dist/dialect/queryContext.d.ts +4 -22
- package/dist/dialect/queryContext.js +4 -22
- package/dist/dialect/queryJoins.d.ts +3 -12
- package/dist/dialect/queryJoins.js +3 -12
- package/dist/dialect/vectorCast.d.ts +2 -12
- package/dist/dialect/vectorCast.js +3 -19
- package/dist/dialect/vectorSqlDialect.d.ts +8 -38
- package/dist/dialect/vectorSqlDialect.js +7 -38
- package/dist/entity/decorator/bag.d.ts +6 -19
- package/dist/entity/decorator/bag.js +6 -22
- package/dist/entity/decorator/entity.d.ts +2 -7
- package/dist/entity/decorator/entity.js +2 -7
- package/dist/entity/decorator/members.d.ts +7 -30
- package/dist/entity/decorator/members.js +3 -12
- package/dist/entity/metadata/definition.d.ts +2 -18
- package/dist/entity/metadata/definition.js +6 -28
- package/dist/http/handler.d.ts +2 -14
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/libsql/libsqlDialect.d.ts +1 -8
- package/dist/libsql/libsqlDialect.js +1 -8
- package/dist/maria/mariaDialect.d.ts +3 -5
- package/dist/maria/mariaDialect.js +5 -5
- package/dist/maria/mariadbQuerier.js +2 -2
- package/dist/maria/mariadbQuerierPool.js +1 -6
- package/dist/migrate/builder/migrationBuilder.js +3 -19
- package/dist/migrate/builder/splitSqlStatements.d.ts +1 -14
- package/dist/migrate/builder/splitSqlStatements.js +2 -22
- package/dist/migrate/builder/types.d.ts +2 -15
- package/dist/migrate/cli-config.js +2 -11
- package/dist/migrate/cli.js +2 -7
- package/dist/migrate/codegen/entityCodeGenerator.d.ts +0 -15
- package/dist/migrate/codegen/entityCodeGenerator.js +15 -44
- package/dist/migrate/codegen/fieldOptionsSource.d.ts +1 -8
- package/dist/migrate/codegen/fieldOptionsSource.js +3 -22
- package/dist/migrate/ddl/indexDdl.d.ts +2 -5
- package/dist/migrate/ddl/indexDdl.js +2 -5
- package/dist/migrate/ddl/pgIndexDdl.d.ts +3 -13
- package/dist/migrate/ddl/pgIndexDdl.js +3 -13
- package/dist/migrate/generator/definitionToNode.d.ts +2 -9
- package/dist/migrate/generator/definitionToNode.js +3 -17
- package/dist/migrate/generator/indexNodeToSchema.d.ts +2 -3
- package/dist/migrate/generator/indexNodeToSchema.js +2 -3
- package/dist/migrate/generator/mongoCommand.d.ts +1 -8
- package/dist/migrate/generator/mongoSchemaGenerator.d.ts +1 -8
- package/dist/migrate/generator/mongoSchemaGenerator.js +1 -8
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +6 -26
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +9 -41
- package/dist/migrate/introspection/baseSqlIntrospector.js +0 -1
- package/dist/migrate/introspection/mongoIntrospector.d.ts +3 -1
- package/dist/migrate/introspection/mongoIntrospector.js +48 -46
- package/dist/migrate/introspection/mssqlIntrospector.d.ts +4 -4
- package/dist/migrate/introspection/mssqlIntrospector.js +18 -27
- package/dist/migrate/introspection/mysqlIntrospector.d.ts +7 -2
- package/dist/migrate/introspection/mysqlIntrospector.js +16 -14
- package/dist/migrate/introspection/postgresIntrospector.d.ts +24 -9
- package/dist/migrate/introspection/postgresIntrospector.js +68 -59
- package/dist/migrate/introspection/sqliteIntrospector.d.ts +1 -1
- package/dist/migrate/introspection/sqliteIntrospector.js +8 -10
- package/dist/migrate/migrator.d.ts +9 -53
- package/dist/migrate/migrator.js +32 -65
- package/dist/migrate/schemaGenerator.d.ts +16 -66
- package/dist/migrate/schemaGenerator.js +21 -74
- package/dist/mongo/mongoDialect.d.ts +21 -53
- package/dist/mongo/mongoDialect.js +25 -70
- package/dist/mongo/mongodbQuerier.d.ts +5 -8
- package/dist/mongo/mongodbQuerier.js +31 -65
- package/dist/mssql/mssqlDialect.d.ts +8 -34
- package/dist/mssql/mssqlDialect.js +37 -51
- package/dist/mssql/mssqlQuerier.d.ts +37 -4
- package/dist/mssql/mssqlQuerier.js +2 -2
- package/dist/mssql/mssqlWireTypes.d.ts +2 -14
- package/dist/mssql/mssqlWireTypes.js +2 -14
- package/dist/nestjs/uqlModule.js +2 -7
- package/dist/pglite/pgliteQuerier.d.ts +1 -9
- package/dist/pglite/pgliteQuerierPool.d.ts +4 -26
- package/dist/pglite/pgliteQuerierPool.js +3 -18
- package/dist/postgres/abstractPgQuerierPool.d.ts +1 -8
- package/dist/postgres/abstractPgQuerierPool.js +1 -8
- package/dist/postgres/pgNumericTypes.d.ts +3 -26
- package/dist/postgres/pgNumericTypes.js +3 -26
- package/dist/postgres/postgresDialect.d.ts +4 -10
- package/dist/postgres/postgresDialect.js +4 -10
- package/dist/querier/abstractQuerier.d.ts +35 -103
- package/dist/querier/abstractQuerier.js +105 -201
- package/dist/querier/abstractSharedHandleQuerierPool.d.ts +3 -17
- package/dist/querier/abstractSharedHandleQuerierPool.js +3 -17
- package/dist/querier/abstractSqlQuerier.d.ts +15 -36
- package/dist/querier/abstractSqlQuerier.js +49 -131
- package/dist/schema/canonicalType.d.ts +3 -21
- package/dist/schema/canonicalType.js +22 -67
- package/dist/schema/dependencyGraph.d.ts +2 -8
- package/dist/schema/dependencyGraph.js +2 -32
- package/dist/schema/index.d.ts +1 -25
- package/dist/schema/index.js +0 -26
- package/dist/schema/indexColumns.d.ts +1 -8
- package/dist/schema/indexColumns.js +1 -8
- package/dist/schema/indexDifferences.d.ts +7 -40
- package/dist/schema/indexDifferences.js +6 -31
- package/dist/schema/schemaAST.d.ts +8 -175
- package/dist/schema/schemaAST.js +13 -365
- package/dist/schema/schemaASTBuilder.d.ts +2 -24
- package/dist/schema/schemaASTBuilder.js +2 -30
- package/dist/schema/schemaASTDiffer.d.ts +6 -46
- package/dist/schema/schemaASTDiffer.js +8 -56
- package/dist/schema/types.d.ts +5 -61
- package/dist/schema/types.js +3 -6
- package/dist/sqlite/abstractSqliteQuerier.d.ts +1 -8
- package/dist/sqlite/localSqliteQuerierPool.d.ts +1 -7
- package/dist/sqlite/localSqliteQuerierPool.js +1 -7
- package/dist/sqlite/nodeSqliteQuerierPool.d.ts +2 -7
- package/dist/sqlite/nodeSqliteQuerierPool.js +2 -7
- package/dist/sqlite/sqliteDialect.d.ts +5 -20
- package/dist/sqlite/sqliteDialect.js +29 -35
- package/dist/turso/tursoDialect.d.ts +4 -6
- package/dist/turso/tursoDialect.js +4 -6
- package/dist/turso/tursoLocalQuerierPool.d.ts +1 -7
- package/dist/turso/tursoLocalQuerierPool.js +1 -7
- package/dist/turso/tursoQuerierPool.d.ts +2 -6
- package/dist/turso/tursoQuerierPool.js +2 -6
- package/dist/turso/tursoSessionQuerier.d.ts +1 -7
- package/dist/turso/tursoSessionQuerier.js +1 -7
- package/dist/type/dialect.d.ts +42 -94
- package/dist/type/dialect.js +3 -13
- package/dist/type/entity.d.ts +163 -534
- package/dist/type/entity.js +26 -9
- package/dist/type/logger.d.ts +2 -14
- package/dist/type/migration.d.ts +9 -38
- package/dist/type/querier.d.ts +9 -28
- package/dist/type/querierPool.d.ts +4 -26
- package/dist/type/query.d.ts +19 -73
- package/dist/type/query.js +2 -7
- package/dist/type/queryAggregate.d.ts +18 -98
- package/dist/type/queryRaw.d.ts +1 -8
- package/dist/type/queryRaw.js +1 -8
- package/dist/type/queryWhere.d.ts +13 -61
- package/dist/type/universalQuerier.d.ts +18 -105
- package/dist/type/utility.d.ts +12 -24
- package/dist/type/vector.d.ts +8 -38
- package/dist/type/vector.js +1 -1
- package/dist/type/wire.d.ts +2 -5
- package/dist/util/dialect.util.d.ts +9 -27
- package/dist/util/dialect.util.js +10 -27
- package/dist/util/field.util.d.ts +2 -31
- package/dist/util/field.util.js +3 -43
- package/dist/util/fieldOption.util.d.ts +7 -15
- package/dist/util/fieldOption.util.js +1 -1
- package/dist/util/filters.util.d.ts +2 -5
- package/dist/util/filters.util.js +2 -5
- package/dist/util/logger.d.ts +2 -6
- package/dist/util/logger.js +2 -6
- package/dist/util/object.util.d.ts +2 -6
- package/dist/util/object.util.js +1 -5
- package/dist/util/raw.d.ts +3 -23
- package/dist/util/relationQuery.util.d.ts +3 -14
- package/dist/util/relationQuery.util.js +3 -14
- package/dist/util/rowKey.util.d.ts +2 -10
- package/dist/util/rowKey.util.js +2 -10
- package/dist/util/sql.util.d.ts +6 -37
- package/dist/util/sql.util.js +13 -73
- package/dist/util/sqlLiteral.d.ts +2 -13
- package/dist/util/sqlLiteral.js +8 -13
- package/dist/util/string.util.js +0 -2
- package/package.json +4 -4
package/dist/schema/schemaAST.js
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SchemaAST Class
|
|
3
|
-
*
|
|
4
|
-
* The main class for working with schema graphs.
|
|
5
|
-
* Provides graph operations like navigation, validation, and topological sorting.
|
|
6
|
-
*/
|
|
7
1
|
import { qualifyName } from '../util/sql.util.js';
|
|
8
|
-
import { createOrder, dropOrder
|
|
9
|
-
/**
|
|
10
|
-
* A table node with its collections empty, ready to be filled. Six places build one, and the fields
|
|
11
|
-
* that are pure boilerplate are exactly the ones a new field gets forgotten in: {@link TableNode.schema}
|
|
12
|
-
* was added and `SchemaAST.clone` kept rebuilding nodes without it.
|
|
13
|
-
*/
|
|
2
|
+
import { createOrder, dropOrder } from './dependencyGraph.js';
|
|
3
|
+
/** A table node with its collections empty, ready to be filled. */
|
|
14
4
|
export function createTableNode(name, schema, comment) {
|
|
15
5
|
return {
|
|
16
6
|
name,
|
|
@@ -24,264 +14,40 @@ export function createTableNode(name, schema, comment) {
|
|
|
24
14
|
outgoingRelations: [],
|
|
25
15
|
};
|
|
26
16
|
}
|
|
27
|
-
/**
|
|
28
|
-
* Schema AST - A graph representation of a database schema.
|
|
29
|
-
*
|
|
30
|
-
* Enables:
|
|
31
|
-
* - Graph navigation (dependencies, dependents)
|
|
32
|
-
* - Circular dependency detection
|
|
33
|
-
* - Topological sorting for correct DDL order
|
|
34
|
-
* - Smart relation inference
|
|
35
|
-
* - Schema validation
|
|
36
|
-
*/
|
|
17
|
+
/** A database schema as a graph: tables, the foreign keys between them, and their indexes. */
|
|
37
18
|
export class SchemaAST {
|
|
38
19
|
tables = new Map();
|
|
39
20
|
relationships = [];
|
|
40
21
|
indexes = [];
|
|
41
|
-
/**
|
|
42
|
-
* Get a table by the name it is keyed under: schema-qualified where it has one, so two tables of
|
|
43
|
-
* the same name in different schemas stay distinct. Build the key with `qualifyName`.
|
|
44
|
-
*/
|
|
22
|
+
/** A table by the key it is stored under: schema-qualified where it has one (see `qualifyName`). */
|
|
45
23
|
getTable(name) {
|
|
46
24
|
return this.tables.get(name);
|
|
47
25
|
}
|
|
48
|
-
/**
|
|
49
|
-
* Add a table to the schema.
|
|
50
|
-
*/
|
|
51
26
|
addTable(table) {
|
|
52
27
|
this.tables.set(qualifyName(table.name, table.schema), table);
|
|
53
28
|
}
|
|
54
|
-
/**
|
|
55
|
-
* Remove a table from the schema.
|
|
56
|
-
*/
|
|
57
|
-
removeTable(name) {
|
|
58
|
-
const table = this.tables.get(name);
|
|
59
|
-
if (!table)
|
|
60
|
-
return false;
|
|
61
|
-
// Remove all relationships involving this table
|
|
62
|
-
for (let i = this.relationships.length - 1; i >= 0; i--) {
|
|
63
|
-
const rel = this.relationships[i];
|
|
64
|
-
if (rel.from.table === table || rel.to.table === table) {
|
|
65
|
-
this.relationships.splice(i, 1);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
// Remove all indexes for this table
|
|
69
|
-
for (let i = this.indexes.length - 1; i >= 0; i--) {
|
|
70
|
-
if (this.indexes[i].table === table) {
|
|
71
|
-
this.indexes.splice(i, 1);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return this.tables.delete(name);
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Get all table nodes.
|
|
78
|
-
*/
|
|
79
29
|
getTables() {
|
|
80
|
-
return
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Get all table names.
|
|
84
|
-
*/
|
|
85
|
-
getTableNames() {
|
|
86
|
-
return Array.from(this.tables.keys());
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Get all tables that depend on this table (have FKs pointing to it).
|
|
90
|
-
* These are tables that reference this table's primary key.
|
|
91
|
-
*/
|
|
92
|
-
getDependentTables(table) {
|
|
93
|
-
return table.incomingRelations.map((r) => r.from.table);
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Get all tables this table depends on (has FKs to).
|
|
97
|
-
* These are tables that this table references.
|
|
98
|
-
*/
|
|
99
|
-
getDependencies(table) {
|
|
100
|
-
return table.outgoingRelations.map((r) => r.to.table);
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Get the relationship between two tables (if any).
|
|
104
|
-
*/
|
|
105
|
-
getRelationship(from, to) {
|
|
106
|
-
return this.relationships.find((r) => r.from.table === from && r.to.table === to);
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Get all relationships for a table.
|
|
110
|
-
*/
|
|
111
|
-
getTableRelationships(table) {
|
|
112
|
-
return this.relationships.filter((r) => r.from.table === table || r.to.table === table);
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Get the column that a foreign key column references.
|
|
116
|
-
*/
|
|
117
|
-
getReferencedColumn(fkColumn) {
|
|
118
|
-
return fkColumn.references?.to.columns[0];
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Detect circular foreign key dependencies.
|
|
122
|
-
* Returns arrays of tables that form cycles.
|
|
123
|
-
*/
|
|
124
|
-
detectCircularDependencies() {
|
|
125
|
-
return findCycles(this.tables.values(), (table) => this.getDependencies(table));
|
|
30
|
+
return [...this.tables.values()];
|
|
126
31
|
}
|
|
127
|
-
/**
|
|
128
|
-
* Check if there are any circular dependencies.
|
|
129
|
-
*/
|
|
130
|
-
hasCircularDependencies() {
|
|
131
|
-
return this.detectCircularDependencies().length > 0;
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Get tables in correct order for CREATE (dependencies first).
|
|
135
|
-
* Tables with no dependencies come first, then tables that depend on them, etc.
|
|
136
|
-
*/
|
|
32
|
+
/** Tables in `CREATE` order, each after the tables it references. */
|
|
137
33
|
getCreateOrder() {
|
|
138
|
-
return createOrder(this.tables.values(),
|
|
34
|
+
return createOrder(this.tables.values(), referencedTables);
|
|
139
35
|
}
|
|
140
|
-
/**
|
|
141
|
-
* Get tables in correct order for DROP (dependents first).
|
|
142
|
-
* Tables that depend on others come first, then the tables they depend on.
|
|
143
|
-
*/
|
|
36
|
+
/** Tables in `DROP` order, each before the tables it references. */
|
|
144
37
|
getDropOrder() {
|
|
145
|
-
return dropOrder(this.tables.values(),
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Validate schema integrity.
|
|
149
|
-
* Checks for:
|
|
150
|
-
* - Missing FK targets
|
|
151
|
-
* - Circular dependencies
|
|
152
|
-
* - Orphan columns
|
|
153
|
-
* - Duplicate indexes
|
|
154
|
-
*/
|
|
155
|
-
validate() {
|
|
156
|
-
const errors = [];
|
|
157
|
-
// Check all FK targets exist
|
|
158
|
-
for (const rel of this.relationships) {
|
|
159
|
-
if (!this.tables.has(rel.to.table.name)) {
|
|
160
|
-
errors.push({
|
|
161
|
-
type: 'missing_fk_target',
|
|
162
|
-
message: `FK target table "${rel.to.table.name}" does not exist`,
|
|
163
|
-
relationship: rel,
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
// Check for circular dependencies
|
|
168
|
-
const cycles = this.detectCircularDependencies();
|
|
169
|
-
for (const cycle of cycles) {
|
|
170
|
-
errors.push({
|
|
171
|
-
type: 'circular_dependency',
|
|
172
|
-
message: `Circular FK: ${cycle.map((t) => t.name).join(' -> ')}`,
|
|
173
|
-
tables: cycle,
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
// Check for duplicate index names within same table
|
|
177
|
-
for (const table of this.tables.values()) {
|
|
178
|
-
const indexNames = new Set();
|
|
179
|
-
for (const index of table.indexes) {
|
|
180
|
-
if (indexNames.has(index.name)) {
|
|
181
|
-
errors.push({
|
|
182
|
-
type: 'duplicate_index',
|
|
183
|
-
message: `Duplicate index name "${index.name}" in table "${table.name}"`,
|
|
184
|
-
table,
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
indexNames.add(index.name);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
return errors;
|
|
38
|
+
return dropOrder(this.tables.values(), referencedTables);
|
|
191
39
|
}
|
|
192
|
-
/**
|
|
193
|
-
* Check if the schema is valid (no validation errors).
|
|
194
|
-
*/
|
|
195
|
-
isValid() {
|
|
196
|
-
return this.validate().length === 0;
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* Check if a table looks like a junction table (ManyToMany through).
|
|
200
|
-
* Junction tables typically have:
|
|
201
|
-
* - Exactly 2 foreign keys
|
|
202
|
-
* - Few other columns (id, maybe timestamps)
|
|
203
|
-
* - Primary key might be composite of the FKs
|
|
204
|
-
*/
|
|
205
|
-
isJunctionTable(table) {
|
|
206
|
-
const fkCount = table.outgoingRelations.length;
|
|
207
|
-
const columnCount = table.columns.size;
|
|
208
|
-
// Must have exactly 2 FKs
|
|
209
|
-
if (fkCount !== 2) {
|
|
210
|
-
return false;
|
|
211
|
-
}
|
|
212
|
-
// Should have few columns (typically: id + 2 FKs + maybe timestamps)
|
|
213
|
-
if (columnCount > 6) {
|
|
214
|
-
return false;
|
|
215
|
-
}
|
|
216
|
-
// Check if name suggests a junction (contains both related table names)
|
|
217
|
-
const relatedTables = table.outgoingRelations.map((r) => r.to.table.name.toLowerCase());
|
|
218
|
-
const tableName = table.name.toLowerCase();
|
|
219
|
-
// Common patterns: user_roles, post_tags, etc.
|
|
220
|
-
const containsBothNames = relatedTables.every((name) => tableName.includes(name.replace(/s$/, '')) || tableName.includes(name));
|
|
221
|
-
return containsBothNames || columnCount <= 5;
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* Infer relation type from schema structure.
|
|
225
|
-
*/
|
|
226
|
-
inferRelationType(rel) {
|
|
227
|
-
const fromCol = rel.from.columns[0];
|
|
228
|
-
// Check if source is junction table -> ManyToMany
|
|
229
|
-
if (this.isJunctionTable(rel.from.table)) {
|
|
230
|
-
return 'ManyToMany';
|
|
231
|
-
}
|
|
232
|
-
// Unique FK -> OneToOne
|
|
233
|
-
if (fromCol?.isUnique) {
|
|
234
|
-
return 'OneToOne';
|
|
235
|
-
}
|
|
236
|
-
// Default: ManyToOne (many rows can reference same target)
|
|
237
|
-
return 'ManyToOne';
|
|
238
|
-
}
|
|
239
|
-
/**
|
|
240
|
-
* Get the inverse relation type.
|
|
241
|
-
*/
|
|
242
|
-
getInverseRelationType(type) {
|
|
243
|
-
switch (type) {
|
|
244
|
-
case 'OneToOne':
|
|
245
|
-
return 'OneToOne';
|
|
246
|
-
case 'OneToMany':
|
|
247
|
-
return 'ManyToOne';
|
|
248
|
-
case 'ManyToOne':
|
|
249
|
-
return 'OneToMany';
|
|
250
|
-
case 'ManyToMany':
|
|
251
|
-
return 'ManyToMany';
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
/**
|
|
255
|
-
* Add an index to the schema.
|
|
256
|
-
*/
|
|
257
40
|
addIndex(index) {
|
|
258
41
|
this.indexes.push(index);
|
|
259
42
|
if (!index.table.indexes.includes(index)) {
|
|
260
43
|
index.table.indexes.push(index);
|
|
261
44
|
}
|
|
262
45
|
}
|
|
263
|
-
/**
|
|
264
|
-
* Get all indexes for a table.
|
|
265
|
-
*/
|
|
266
|
-
getTableIndexes(tableName) {
|
|
267
|
-
const table = this.tables.get(tableName);
|
|
268
|
-
return table?.indexes ?? [];
|
|
269
|
-
}
|
|
270
|
-
/**
|
|
271
|
-
* Find an index by name.
|
|
272
|
-
*/
|
|
273
|
-
getIndex(name) {
|
|
274
|
-
return this.indexes.find((i) => i.name === name);
|
|
275
|
-
}
|
|
276
|
-
/**
|
|
277
|
-
* Add a relationship to the schema.
|
|
278
|
-
*/
|
|
46
|
+
/** Adds a foreign key, linking it from both tables and both column sets. */
|
|
279
47
|
addRelationship(rel) {
|
|
280
48
|
this.relationships.push(rel);
|
|
281
|
-
// Update table links
|
|
282
49
|
rel.from.table.outgoingRelations.push(rel);
|
|
283
50
|
rel.to.table.incomingRelations.push(rel);
|
|
284
|
-
// Update column links
|
|
285
51
|
for (const col of rel.from.columns) {
|
|
286
52
|
col.references = rel;
|
|
287
53
|
}
|
|
@@ -289,125 +55,7 @@ export class SchemaAST {
|
|
|
289
55
|
col.referencedBy.push(rel);
|
|
290
56
|
}
|
|
291
57
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
removeRelationship(name) {
|
|
296
|
-
const index = this.relationships.findIndex((r) => r.name === name);
|
|
297
|
-
if (index === -1)
|
|
298
|
-
return false;
|
|
299
|
-
const rel = this.relationships[index];
|
|
300
|
-
// Remove from table links
|
|
301
|
-
const fromIdx = rel.from.table.outgoingRelations.indexOf(rel);
|
|
302
|
-
if (fromIdx !== -1)
|
|
303
|
-
rel.from.table.outgoingRelations.splice(fromIdx, 1);
|
|
304
|
-
const toIdx = rel.to.table.incomingRelations.indexOf(rel);
|
|
305
|
-
if (toIdx !== -1)
|
|
306
|
-
rel.to.table.incomingRelations.splice(toIdx, 1);
|
|
307
|
-
// Remove from column links
|
|
308
|
-
for (const col of rel.from.columns) {
|
|
309
|
-
if (col.references === rel) {
|
|
310
|
-
col.references = undefined;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
for (const col of rel.to.columns) {
|
|
314
|
-
const refIdx = col.referencedBy.indexOf(rel);
|
|
315
|
-
if (refIdx !== -1)
|
|
316
|
-
col.referencedBy.splice(refIdx, 1);
|
|
317
|
-
}
|
|
318
|
-
this.relationships.splice(index, 1);
|
|
319
|
-
return true;
|
|
320
|
-
}
|
|
321
|
-
/**
|
|
322
|
-
* Create a deep clone of this schema.
|
|
323
|
-
*/
|
|
324
|
-
clone() {
|
|
325
|
-
const clone = new SchemaAST();
|
|
326
|
-
// First pass: tables and columns. The table is built before its columns so each clone can link
|
|
327
|
-
// back to it on creation - `columns` and `primaryKey` are readonly properties holding mutable
|
|
328
|
-
// containers, so they are filled in afterwards without reassigning anything.
|
|
329
|
-
for (const [name, table] of this.tables) {
|
|
330
|
-
const clonedTable = createTableNode(table.name, table.schema, table.comment);
|
|
331
|
-
for (const [colName, col] of table.columns) {
|
|
332
|
-
clonedTable.columns.set(colName, { ...col, table: clonedTable, referencedBy: [], references: undefined });
|
|
333
|
-
}
|
|
334
|
-
clonedTable.primaryKey.push(...table.primaryKey.flatMap((pk) => clonedTable.columns.get(pk.name) ?? []));
|
|
335
|
-
clone.tables.set(name, clonedTable);
|
|
336
|
-
}
|
|
337
|
-
// Second pass: clone relationships
|
|
338
|
-
for (const rel of this.relationships) {
|
|
339
|
-
const fromTable = clone.tables.get(rel.from.table.name);
|
|
340
|
-
const toTable = clone.tables.get(rel.to.table.name);
|
|
341
|
-
if (!fromTable || !toTable)
|
|
342
|
-
continue;
|
|
343
|
-
const fromColumns = rel.from.columns
|
|
344
|
-
.map((c) => fromTable.columns.get(c.name))
|
|
345
|
-
.filter((c) => c !== undefined);
|
|
346
|
-
const toColumns = rel.to.columns
|
|
347
|
-
.map((c) => toTable.columns.get(c.name))
|
|
348
|
-
.filter((c) => c !== undefined);
|
|
349
|
-
const clonedRel = {
|
|
350
|
-
...rel,
|
|
351
|
-
from: {
|
|
352
|
-
table: fromTable,
|
|
353
|
-
columns: fromColumns,
|
|
354
|
-
},
|
|
355
|
-
to: {
|
|
356
|
-
table: toTable,
|
|
357
|
-
columns: toColumns,
|
|
358
|
-
},
|
|
359
|
-
through: rel.through ? clone.tables.get(rel.through.name) : undefined,
|
|
360
|
-
};
|
|
361
|
-
clone.addRelationship(clonedRel);
|
|
362
|
-
}
|
|
363
|
-
// Third pass: clone indexes
|
|
364
|
-
for (const idx of this.indexes) {
|
|
365
|
-
const table = clone.tables.get(idx.table.name);
|
|
366
|
-
if (!table)
|
|
367
|
-
continue;
|
|
368
|
-
clone.addIndex({ ...idx, table });
|
|
369
|
-
}
|
|
370
|
-
return clone;
|
|
371
|
-
}
|
|
372
|
-
/**
|
|
373
|
-
* Get statistics about the schema.
|
|
374
|
-
*/
|
|
375
|
-
getStats() {
|
|
376
|
-
let columnCount = 0;
|
|
377
|
-
for (const table of this.tables.values()) {
|
|
378
|
-
columnCount += table.columns.size;
|
|
379
|
-
}
|
|
380
|
-
return {
|
|
381
|
-
tableCount: this.tables.size,
|
|
382
|
-
columnCount,
|
|
383
|
-
relationshipCount: this.relationships.length,
|
|
384
|
-
indexCount: this.indexes.length,
|
|
385
|
-
};
|
|
386
|
-
}
|
|
387
|
-
/**
|
|
388
|
-
* The schema as a plain object, for serialization and debugging. The graph links are what is left
|
|
389
|
-
* out - they are cycles, and nothing else is: listing the fields to keep instead dropped every
|
|
390
|
-
* option a column had gained since, `defaultValue` and `enum` included.
|
|
391
|
-
*/
|
|
392
|
-
toJSON() {
|
|
393
|
-
return {
|
|
394
|
-
tables: Array.from(this.tables.values()).map((t) => ({
|
|
395
|
-
name: t.name,
|
|
396
|
-
columns: Array.from(t.columns.values()).map(({ table: _table, referencedBy: _referencedBy, references: _references, ...column }) => column),
|
|
397
|
-
indexes: t.indexes.map((i) => ({
|
|
398
|
-
name: i.name,
|
|
399
|
-
columns: i.entries.map((entry) => entry.column),
|
|
400
|
-
unique: i.unique,
|
|
401
|
-
})),
|
|
402
|
-
})),
|
|
403
|
-
relationships: this.relationships.map((r) => ({
|
|
404
|
-
name: r.name,
|
|
405
|
-
type: r.type,
|
|
406
|
-
from: `${r.from.table.name}.${r.from.columns.map((c) => c.name).join(',')}`,
|
|
407
|
-
to: `${r.to.table.name}.${r.to.columns.map((c) => c.name).join(',')}`,
|
|
408
|
-
onDelete: r.onDelete,
|
|
409
|
-
onUpdate: r.onUpdate,
|
|
410
|
-
})),
|
|
411
|
-
};
|
|
412
|
-
}
|
|
58
|
+
}
|
|
59
|
+
function referencedTables(table) {
|
|
60
|
+
return table.outgoingRelations.map((rel) => rel.to.table);
|
|
413
61
|
}
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SchemaAST Builder
|
|
3
|
-
*
|
|
4
|
-
* Constructs a SchemaAST from:
|
|
5
|
-
* - Entity metadata (decorator-based entities)
|
|
6
|
-
* - Database introspection results (TableSchema[])
|
|
7
|
-
*/
|
|
8
1
|
import type { EntityGetter } from '../type/entity.js';
|
|
9
2
|
import type { EntityMeta, EntityWhereMeta, FieldMeta, FieldOptions, Type } from '../type/index.js';
|
|
10
3
|
import type { NamingStrategy } from '../type/namingStrategy.js';
|
|
@@ -40,22 +33,7 @@ export interface BuildSchemaASTOptions {
|
|
|
40
33
|
*/
|
|
41
34
|
export declare function buildSchemaAST(entities: readonly Type<object>[], options?: BuildSchemaASTOptions): SchemaAST;
|
|
42
35
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* (`@Field({ references: () => SomeEntity })`) with no explicit type of its
|
|
46
|
-
* own.
|
|
47
|
-
*
|
|
48
|
-
* Without this, a field like `creatorId?: UUID` (a bare TypeScript alias for
|
|
49
|
-
* `string`, erased at runtime) falls back to the generic string inference in
|
|
50
|
-
* {@link fieldOptionsToCanonical} and gets typed as TEXT/VARCHAR - producing a
|
|
51
|
-
* foreign key column whose type doesn't match the UUID primary key it
|
|
52
|
-
* references, which Postgres (and most databases) reject outright.
|
|
53
|
-
*
|
|
54
|
-
* `field.typeFromReference` (set by `defineField`, see entity/metadata/definition.ts)
|
|
55
|
-
* is what distinguishes "no type was given" from "the decorator explicitly set
|
|
56
|
-
* a type" - including explicit constructor overrides like `type: BigInt`, which
|
|
57
|
-
* a value-based check (e.g. `typeof field.type === 'string'`) would miss since
|
|
58
|
-
* reflection also produces constructor values like `String`/`Number`.
|
|
59
|
-
* `columnType` remains the unambiguous, always-respected explicit override.
|
|
36
|
+
* A field's canonical type, taken from the referenced key where the field gave `references` and no
|
|
37
|
+
* `type` (`typeFromReference`), so a foreign key matches the key it points at; `columnType` always wins.
|
|
60
38
|
*/
|
|
61
39
|
export declare function resolveColumnCanonicalType(field: FieldMeta, seen?: Set<EntityGetter>): CanonicalType;
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SchemaAST Builder
|
|
3
|
-
*
|
|
4
|
-
* Constructs a SchemaAST from:
|
|
5
|
-
* - Entity metadata (decorator-based entities)
|
|
6
|
-
* - Database introspection results (TableSchema[])
|
|
7
|
-
*/
|
|
8
1
|
import { fieldOf, foreignKeysOf, getMeta, soleIdOf } from '../entity/metadata/definition.js';
|
|
9
2
|
import { declaredIndexes, indexNameParts, renderIndexColumn } from '../util/ddlExpression.util.js';
|
|
10
3
|
import { isInlinedExpression } from '../util/field.util.js';
|
|
@@ -46,23 +39,8 @@ function refuseDdl() {
|
|
|
46
39
|
throw new TypeError('building the schema of an entity that declares SQL (a check, a stored computed column, an index expression or predicate) needs a dialect to render it: pass `compileDdl`, as `buildEntityAST` does');
|
|
47
40
|
}
|
|
48
41
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* (`@Field({ references: () => SomeEntity })`) with no explicit type of its
|
|
52
|
-
* own.
|
|
53
|
-
*
|
|
54
|
-
* Without this, a field like `creatorId?: UUID` (a bare TypeScript alias for
|
|
55
|
-
* `string`, erased at runtime) falls back to the generic string inference in
|
|
56
|
-
* {@link fieldOptionsToCanonical} and gets typed as TEXT/VARCHAR - producing a
|
|
57
|
-
* foreign key column whose type doesn't match the UUID primary key it
|
|
58
|
-
* references, which Postgres (and most databases) reject outright.
|
|
59
|
-
*
|
|
60
|
-
* `field.typeFromReference` (set by `defineField`, see entity/metadata/definition.ts)
|
|
61
|
-
* is what distinguishes "no type was given" from "the decorator explicitly set
|
|
62
|
-
* a type" - including explicit constructor overrides like `type: BigInt`, which
|
|
63
|
-
* a value-based check (e.g. `typeof field.type === 'string'`) would miss since
|
|
64
|
-
* reflection also produces constructor values like `String`/`Number`.
|
|
65
|
-
* `columnType` remains the unambiguous, always-respected explicit override.
|
|
42
|
+
* A field's canonical type, taken from the referenced key where the field gave `references` and no
|
|
43
|
+
* `type` (`typeFromReference`), so a foreign key matches the key it points at; `columnType` always wins.
|
|
66
44
|
*/
|
|
67
45
|
export function resolveColumnCanonicalType(field, seen = new Set()) {
|
|
68
46
|
const hasExplicitType = !!field.columnType || !field.typeFromReference;
|
|
@@ -156,8 +134,6 @@ function addRelationshipsFromEntity(ctx, meta) {
|
|
|
156
134
|
// references, onDelete })` work with no relation declared at all.
|
|
157
135
|
onDelete: foreignKey.onDelete ?? meta.fields[foreignKey.references[0].local]?.onDelete ?? ctx.defaultForeignKeyAction,
|
|
158
136
|
onUpdate: foreignKey.onUpdate ?? ctx.defaultForeignKeyAction,
|
|
159
|
-
confidence: 1.0,
|
|
160
|
-
inferredFrom: 'entity_decorator',
|
|
161
137
|
});
|
|
162
138
|
}
|
|
163
139
|
}
|
|
@@ -190,8 +166,6 @@ function addForeignKeyIndexes(ctx, meta, table) {
|
|
|
190
166
|
table,
|
|
191
167
|
entries: columns.map((column) => ({ column })),
|
|
192
168
|
unique: false,
|
|
193
|
-
source: 'entity',
|
|
194
|
-
syncStatus: 'entity_only',
|
|
195
169
|
});
|
|
196
170
|
}
|
|
197
171
|
}
|
|
@@ -237,7 +211,5 @@ function addCompositeIndex(ctx, table, meta, idxMeta) {
|
|
|
237
211
|
m: idxMeta.m,
|
|
238
212
|
efConstruction: idxMeta.efConstruction,
|
|
239
213
|
lists: idxMeta.lists,
|
|
240
|
-
source: 'entity',
|
|
241
|
-
syncStatus: 'entity_only',
|
|
242
214
|
});
|
|
243
215
|
}
|
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SchemaAST Differ
|
|
3
|
-
*
|
|
4
|
-
* Compares two SchemaAST instances and produces a detailed diff.
|
|
5
|
-
* Used for:
|
|
6
|
-
* - Migration generation (entity vs database)
|
|
7
|
-
* - Drift detection (expected vs actual)
|
|
8
|
-
* - Schema synchronization
|
|
9
|
-
*/
|
|
10
1
|
import { type IndexFacet } from './indexDifferences.js';
|
|
11
2
|
import type { SchemaAST } from './schemaAST.js';
|
|
12
3
|
import type { CanonicalType } from './types.js';
|
|
@@ -26,52 +17,21 @@ export interface DiffOptions {
|
|
|
26
17
|
/** Tables to exclude from comparison */
|
|
27
18
|
excludeTables?: string[];
|
|
28
19
|
/**
|
|
29
|
-
* A type as the engine
|
|
30
|
-
*
|
|
31
|
-
* Several canonical types share one storage type per engine - a `boolean` is `TINYINT(1)` on MySQL
|
|
32
|
-
* and `INTEGER` on SQLite - so comparing them canonically reports an alteration on every sync for
|
|
33
|
-
* those columns. Passing both sides through the engine first is what settles that, and it is the
|
|
34
|
-
* only thing here a dialect is needed for, so it arrives as a function rather than as a dependency.
|
|
20
|
+
* A type as the engine stores it, since several canonical types share one storage type (a boolean is
|
|
21
|
+
* `TINYINT(1)` on MySQL): the one thing a dialect is needed for, passed as a function.
|
|
35
22
|
*/
|
|
36
23
|
normalizeType?: (type: CanonicalType) => CanonicalType;
|
|
37
|
-
/**
|
|
38
|
-
* Whether two defaults are the same value, for the caller that has a dialect.
|
|
39
|
-
*
|
|
40
|
-
* A database reprints a default from its parse tree, so `'active'` comes back as
|
|
41
|
-
* `'active'::character varying` on Postgres and a symbolic `now()` matches no spelling of
|
|
42
|
-
* `CURRENT_TIMESTAMP`. Undoing that needs the dialect that wrote it, so it arrives as a function.
|
|
43
|
-
*/
|
|
24
|
+
/** Whether two defaults are one value, as the dialect that reprinted them can tell: `'a'::character varying` is `'a'`. */
|
|
44
25
|
defaultsEqual?: (expected: unknown, actual: unknown) => boolean;
|
|
45
26
|
}
|
|
46
|
-
/**
|
|
47
|
-
* Compare two schemas and return the differences.
|
|
48
|
-
*
|
|
49
|
-
* @param source - The "expected" or "desired" schema (e.g., from entities)
|
|
50
|
-
* @param target - The "actual" or "current" schema (e.g., from database)
|
|
51
|
-
* @param options - Diff options
|
|
52
|
-
* @returns Detailed diff result
|
|
53
|
-
*/
|
|
27
|
+
/** The differences between the expected schema (the entities) and the actual one (the database). */
|
|
54
28
|
export declare function diffSchemas(source: SchemaAST, target: SchemaAST, options?: DiffOptions): SchemaDiffResult;
|
|
55
|
-
/**
|
|
56
|
-
* Compare two tables and return the differences.
|
|
57
|
-
*
|
|
58
|
-
* Exported because it is also how a migration is planned: the generator diffs one entity's table
|
|
59
|
-
* against the one the database reported, then projects the result into a `SchemaDiff`. One
|
|
60
|
-
* comparison serves both, so drift and migrations can no longer disagree about what has changed.
|
|
61
|
-
*/
|
|
29
|
+
/** The differences between two tables, shared by migrations and drift detection so they cannot disagree. */
|
|
62
30
|
export declare function diffTable(source: TableNode, target: TableNode, options?: DiffOptions): (TableDiff & {
|
|
63
31
|
readonly columnDiffs: ColumnDiff[];
|
|
64
32
|
readonly indexDiffs: IndexDiff[];
|
|
65
33
|
}) | undefined;
|
|
66
|
-
/**
|
|
67
|
-
* Compare two lists of relationships.
|
|
68
|
-
*
|
|
69
|
-
* Lists rather than whole schemas, because a migration diffs one table: its two sides are that
|
|
70
|
-
* table's `outgoingRelations`, where `diffSchemas` passes the schema's every relationship.
|
|
71
|
-
*
|
|
72
|
-
* Matched by columns, never by name - the engine named every constraint that already exists, so
|
|
73
|
-
* pairing on names would report every hand-named one as a drop and an add.
|
|
74
|
-
*/
|
|
34
|
+
/** The differences between two lists of foreign keys, matched by their columns and never by the name the engine gave them. */
|
|
75
35
|
export declare function diffRelationshipNodes(source: readonly RelationshipNode[], target: readonly RelationshipNode[], opts?: DiffOptions): RelationshipDiff[];
|
|
76
36
|
/** A relationship's `ON DELETE` and `ON UPDATE`, an unstated one read as the action the database applies. */
|
|
77
37
|
export declare function referentialActions(rel: RelationshipNode): {
|