metal-orm 1.1.9 → 1.1.11
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/README.md +769 -764
- package/dist/index.cjs +2255 -284
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +559 -39
- package/dist/index.d.ts +559 -39
- package/dist/index.js +2227 -284
- package/dist/index.js.map +1 -1
- package/package.json +17 -12
- package/scripts/generate-entities/render.mjs +21 -12
- package/scripts/generate-entities/schema.mjs +87 -73
- package/scripts/generate-entities/tree-detection.mjs +67 -61
- package/src/bulk/bulk-context.ts +83 -0
- package/src/bulk/bulk-delete-executor.ts +87 -0
- package/src/bulk/bulk-executor.base.ts +73 -0
- package/src/bulk/bulk-insert-executor.ts +74 -0
- package/src/bulk/bulk-types.ts +70 -0
- package/src/bulk/bulk-update-executor.ts +192 -0
- package/src/bulk/bulk-upsert-executor.ts +93 -0
- package/src/bulk/bulk-utils.ts +91 -0
- package/src/bulk/index.ts +18 -0
- package/src/codegen/typescript.ts +30 -21
- package/src/core/ast/expression-builders.ts +107 -10
- package/src/core/ast/expression-nodes.ts +52 -22
- package/src/core/ast/expression-visitor.ts +23 -13
- package/src/core/ddl/introspect/mysql.ts +113 -36
- package/src/core/dialect/abstract.ts +30 -17
- package/src/core/dialect/mysql/index.ts +20 -5
- package/src/core/execution/db-executor.ts +96 -64
- package/src/core/execution/executors/better-sqlite3-executor.ts +94 -0
- package/src/core/execution/executors/mssql-executor.ts +66 -34
- package/src/core/execution/executors/mysql-executor.ts +98 -66
- package/src/core/execution/executors/postgres-executor.ts +33 -11
- package/src/core/execution/executors/sqlite-executor.ts +86 -30
- package/src/decorators/bootstrap.ts +482 -398
- package/src/decorators/column-decorator.ts +87 -96
- package/src/decorators/decorator-metadata.ts +100 -24
- package/src/decorators/entity.ts +27 -24
- package/src/decorators/relations.ts +231 -149
- package/src/decorators/transformers/transformer-decorators.ts +26 -29
- package/src/decorators/validators/country-validators-decorators.ts +9 -15
- package/src/dto/apply-filter.ts +568 -551
- package/src/index.ts +16 -9
- package/src/orm/entity-hydration.ts +116 -72
- package/src/orm/entity-metadata.ts +347 -301
- package/src/orm/entity-relations.ts +264 -207
- package/src/orm/entity.ts +199 -199
- package/src/orm/execute.ts +13 -13
- package/src/orm/lazy-batch/morph-many.ts +70 -0
- package/src/orm/lazy-batch/morph-one.ts +69 -0
- package/src/orm/lazy-batch/morph-to.ts +59 -0
- package/src/orm/lazy-batch.ts +4 -1
- package/src/orm/orm-session.ts +170 -104
- package/src/orm/pooled-executor-factory.ts +99 -58
- package/src/orm/query-logger.ts +49 -40
- package/src/orm/relation-change-processor.ts +198 -96
- package/src/orm/relations/belongs-to.ts +143 -143
- package/src/orm/relations/has-many.ts +204 -204
- package/src/orm/relations/has-one.ts +174 -174
- package/src/orm/relations/many-to-many.ts +288 -288
- package/src/orm/relations/morph-many.ts +156 -0
- package/src/orm/relations/morph-one.ts +151 -0
- package/src/orm/relations/morph-to.ts +162 -0
- package/src/orm/save-graph.ts +116 -1
- package/src/query-builder/expression-table-mapper.ts +5 -0
- package/src/query-builder/hydration-manager.ts +345 -345
- package/src/query-builder/hydration-planner.ts +178 -148
- package/src/query-builder/relation-conditions.ts +171 -151
- package/src/query-builder/relation-cte-builder.ts +5 -1
- package/src/query-builder/relation-filter-utils.ts +9 -6
- package/src/query-builder/relation-include-strategies.ts +44 -2
- package/src/query-builder/relation-join-strategies.ts +8 -1
- package/src/query-builder/relation-service.ts +250 -241
- package/src/query-builder/select/select-operations.ts +110 -105
- package/src/query-builder/update-include.ts +4 -0
- package/src/schema/relation.ts +296 -188
- package/src/schema/types.ts +138 -123
- package/src/tree/tree-decorator.ts +127 -137
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
import { TableDef } from '../schema/table.js';
|
|
2
|
-
import { RelationDef, RelationKinds, BelongsToManyRelation } from '../schema/relation.js';
|
|
3
|
-
import { ProjectionNode } from './select-query-state.js';
|
|
4
|
-
import { HydrationPlan, HydrationRelationPlan } from '../core/hydration/types.js';
|
|
5
|
-
import { isRelationAlias } from './relation-alias.js';
|
|
6
|
-
import { buildDefaultPivotColumns } from './relation-utils.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Finds the primary key column name for a table
|
|
10
|
-
* @param table - Table definition
|
|
11
|
-
* @returns Name of the primary key column, defaults to 'id'
|
|
12
|
-
*/
|
|
13
|
-
export const findPrimaryKey = (table: TableDef): string => {
|
|
14
|
-
const pk = Object.values(table.columns).find(c => c.primary);
|
|
15
|
-
return pk?.name || 'id';
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Manages hydration planning for query results
|
|
20
|
-
*/
|
|
21
|
-
export class HydrationPlanner {
|
|
22
|
-
/**
|
|
23
|
-
* Creates a new HydrationPlanner instance
|
|
24
|
-
* @param table - Table definition
|
|
25
|
-
* @param plan - Optional existing hydration plan
|
|
26
|
-
*/
|
|
27
|
-
constructor(private readonly table: TableDef, private readonly plan?: HydrationPlan) { }
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Captures root table columns for hydration planning
|
|
31
|
-
* @param columns - Columns to capture
|
|
32
|
-
* @returns Updated HydrationPlanner with captured columns
|
|
33
|
-
*/
|
|
34
|
-
captureRootColumns(columns: ProjectionNode[]): HydrationPlanner {
|
|
35
|
-
const currentPlan = this.getPlanOrDefault();
|
|
36
|
-
const rootCols = new Set(currentPlan.rootColumns);
|
|
37
|
-
let changed = false;
|
|
38
|
-
|
|
39
|
-
columns.forEach(node => {
|
|
40
|
-
const alias = node.type === 'Column' ? (node.alias || node.name) : node.alias;
|
|
41
|
-
if (!alias || isRelationAlias(alias)) return;
|
|
42
|
-
if (node.type === 'Column' && node.table !== this.table.name) return;
|
|
43
|
-
if (!rootCols.has(alias)) {
|
|
44
|
-
rootCols.add(alias);
|
|
45
|
-
changed = true;
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
if (!changed) return this;
|
|
50
|
-
return new HydrationPlanner(this.table, {
|
|
51
|
-
...currentPlan,
|
|
52
|
-
rootColumns: Array.from(rootCols)
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Includes a relation in the hydration plan
|
|
58
|
-
* @param rel - Relation definition
|
|
59
|
-
* @param relationName - Name of the relation
|
|
60
|
-
* @param aliasPrefix - Alias prefix for relation columns
|
|
61
|
-
* @param columns - Columns to include from the relation
|
|
62
|
-
* @returns Updated HydrationPlanner with included relation
|
|
63
|
-
*/
|
|
1
|
+
import { TableDef } from '../schema/table.js';
|
|
2
|
+
import { RelationDef, RelationKinds, BelongsToManyRelation, MorphOneRelation, MorphManyRelation } from '../schema/relation.js';
|
|
3
|
+
import { ProjectionNode } from './select-query-state.js';
|
|
4
|
+
import { HydrationPlan, HydrationRelationPlan } from '../core/hydration/types.js';
|
|
5
|
+
import { isRelationAlias } from './relation-alias.js';
|
|
6
|
+
import { buildDefaultPivotColumns } from './relation-utils.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Finds the primary key column name for a table
|
|
10
|
+
* @param table - Table definition
|
|
11
|
+
* @returns Name of the primary key column, defaults to 'id'
|
|
12
|
+
*/
|
|
13
|
+
export const findPrimaryKey = (table: TableDef): string => {
|
|
14
|
+
const pk = Object.values(table.columns).find(c => c.primary);
|
|
15
|
+
return pk?.name || 'id';
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Manages hydration planning for query results
|
|
20
|
+
*/
|
|
21
|
+
export class HydrationPlanner {
|
|
22
|
+
/**
|
|
23
|
+
* Creates a new HydrationPlanner instance
|
|
24
|
+
* @param table - Table definition
|
|
25
|
+
* @param plan - Optional existing hydration plan
|
|
26
|
+
*/
|
|
27
|
+
constructor(private readonly table: TableDef, private readonly plan?: HydrationPlan) { }
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Captures root table columns for hydration planning
|
|
31
|
+
* @param columns - Columns to capture
|
|
32
|
+
* @returns Updated HydrationPlanner with captured columns
|
|
33
|
+
*/
|
|
34
|
+
captureRootColumns(columns: ProjectionNode[]): HydrationPlanner {
|
|
35
|
+
const currentPlan = this.getPlanOrDefault();
|
|
36
|
+
const rootCols = new Set(currentPlan.rootColumns);
|
|
37
|
+
let changed = false;
|
|
38
|
+
|
|
39
|
+
columns.forEach(node => {
|
|
40
|
+
const alias = node.type === 'Column' ? (node.alias || node.name) : node.alias;
|
|
41
|
+
if (!alias || isRelationAlias(alias)) return;
|
|
42
|
+
if (node.type === 'Column' && node.table !== this.table.name) return;
|
|
43
|
+
if (!rootCols.has(alias)) {
|
|
44
|
+
rootCols.add(alias);
|
|
45
|
+
changed = true;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (!changed) return this;
|
|
50
|
+
return new HydrationPlanner(this.table, {
|
|
51
|
+
...currentPlan,
|
|
52
|
+
rootColumns: Array.from(rootCols)
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Includes a relation in the hydration plan
|
|
58
|
+
* @param rel - Relation definition
|
|
59
|
+
* @param relationName - Name of the relation
|
|
60
|
+
* @param aliasPrefix - Alias prefix for relation columns
|
|
61
|
+
* @param columns - Columns to include from the relation
|
|
62
|
+
* @returns Updated HydrationPlanner with included relation
|
|
63
|
+
*/
|
|
64
64
|
includeRelation(
|
|
65
65
|
rel: RelationDef,
|
|
66
66
|
relationName: string,
|
|
@@ -68,39 +68,39 @@ export class HydrationPlanner {
|
|
|
68
68
|
columns: string[],
|
|
69
69
|
pivot?: { aliasPrefix: string; columns: string[]; merge?: boolean }
|
|
70
70
|
): HydrationPlanner {
|
|
71
|
-
const currentPlan = this.getPlanOrDefault();
|
|
72
|
-
const relations = currentPlan.relations.filter(r => r.name !== relationName);
|
|
73
|
-
relations.push(this.buildRelationPlan(rel, relationName, aliasPrefix, columns, pivot));
|
|
74
|
-
return new HydrationPlanner(this.table, {
|
|
75
|
-
...currentPlan,
|
|
76
|
-
relations
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Gets the current hydration plan
|
|
82
|
-
* @returns Current hydration plan or undefined
|
|
83
|
-
*/
|
|
84
|
-
getPlan(): HydrationPlan | undefined {
|
|
85
|
-
return this.plan;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Gets the current hydration plan or creates a default one
|
|
90
|
-
* @returns Current hydration plan or default plan
|
|
91
|
-
*/
|
|
92
|
-
private getPlanOrDefault(): HydrationPlan {
|
|
93
|
-
return this.plan ?? buildDefaultHydrationPlan(this.table);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Builds a relation plan for hydration
|
|
98
|
-
* @param rel - Relation definition
|
|
99
|
-
* @param relationName - Name of the relation
|
|
100
|
-
* @param aliasPrefix - Alias prefix for relation columns
|
|
101
|
-
* @param columns - Columns to include from the relation
|
|
102
|
-
* @returns Hydration relation plan
|
|
103
|
-
*/
|
|
71
|
+
const currentPlan = this.getPlanOrDefault();
|
|
72
|
+
const relations = currentPlan.relations.filter(r => r.name !== relationName);
|
|
73
|
+
relations.push(this.buildRelationPlan(rel, relationName, aliasPrefix, columns, pivot));
|
|
74
|
+
return new HydrationPlanner(this.table, {
|
|
75
|
+
...currentPlan,
|
|
76
|
+
relations
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Gets the current hydration plan
|
|
82
|
+
* @returns Current hydration plan or undefined
|
|
83
|
+
*/
|
|
84
|
+
getPlan(): HydrationPlan | undefined {
|
|
85
|
+
return this.plan;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Gets the current hydration plan or creates a default one
|
|
90
|
+
* @returns Current hydration plan or default plan
|
|
91
|
+
*/
|
|
92
|
+
private getPlanOrDefault(): HydrationPlan {
|
|
93
|
+
return this.plan ?? buildDefaultHydrationPlan(this.table);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Builds a relation plan for hydration
|
|
98
|
+
* @param rel - Relation definition
|
|
99
|
+
* @param relationName - Name of the relation
|
|
100
|
+
* @param aliasPrefix - Alias prefix for relation columns
|
|
101
|
+
* @param columns - Columns to include from the relation
|
|
102
|
+
* @returns Hydration relation plan
|
|
103
|
+
*/
|
|
104
104
|
private buildRelationPlan(
|
|
105
105
|
rel: RelationDef,
|
|
106
106
|
relationName: string,
|
|
@@ -108,45 +108,45 @@ export class HydrationPlanner {
|
|
|
108
108
|
columns: string[],
|
|
109
109
|
pivot?: { aliasPrefix: string; columns: string[]; merge?: boolean }
|
|
110
110
|
): HydrationRelationPlan {
|
|
111
|
-
switch (rel.type) {
|
|
112
|
-
case RelationKinds.HasMany:
|
|
113
|
-
case RelationKinds.HasOne: {
|
|
114
|
-
const localKey = rel.localKey || findPrimaryKey(this.table);
|
|
115
|
-
return {
|
|
116
|
-
name: relationName,
|
|
117
|
-
aliasPrefix,
|
|
118
|
-
type: rel.type,
|
|
119
|
-
targetTable: rel.target.name,
|
|
120
|
-
targetPrimaryKey: findPrimaryKey(rel.target),
|
|
121
|
-
foreignKey: rel.foreignKey,
|
|
122
|
-
localKey,
|
|
123
|
-
columns
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
case RelationKinds.BelongsTo: {
|
|
127
|
-
const localKey = rel.localKey || findPrimaryKey(rel.target);
|
|
128
|
-
return {
|
|
129
|
-
name: relationName,
|
|
130
|
-
aliasPrefix,
|
|
131
|
-
type: rel.type,
|
|
132
|
-
targetTable: rel.target.name,
|
|
133
|
-
targetPrimaryKey: findPrimaryKey(rel.target),
|
|
134
|
-
foreignKey: rel.foreignKey,
|
|
135
|
-
localKey,
|
|
136
|
-
columns
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
case RelationKinds.BelongsToMany: {
|
|
140
|
-
const many = rel as BelongsToManyRelation;
|
|
141
|
-
const localKey = many.localKey || findPrimaryKey(this.table);
|
|
142
|
-
const targetPk = many.targetKey || findPrimaryKey(many.target);
|
|
143
|
-
const pivotPk = many.pivotPrimaryKey || findPrimaryKey(many.pivotTable);
|
|
144
|
-
const pivotAliasPrefix = pivot?.aliasPrefix ?? `${aliasPrefix}_pivot`;
|
|
145
|
-
const pivotColumns =
|
|
146
|
-
pivot?.columns ??
|
|
147
|
-
many.defaultPivotColumns ??
|
|
148
|
-
buildDefaultPivotColumns(many, pivotPk);
|
|
149
|
-
|
|
111
|
+
switch (rel.type) {
|
|
112
|
+
case RelationKinds.HasMany:
|
|
113
|
+
case RelationKinds.HasOne: {
|
|
114
|
+
const localKey = rel.localKey || findPrimaryKey(this.table);
|
|
115
|
+
return {
|
|
116
|
+
name: relationName,
|
|
117
|
+
aliasPrefix,
|
|
118
|
+
type: rel.type,
|
|
119
|
+
targetTable: rel.target.name,
|
|
120
|
+
targetPrimaryKey: findPrimaryKey(rel.target),
|
|
121
|
+
foreignKey: rel.foreignKey,
|
|
122
|
+
localKey,
|
|
123
|
+
columns
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
case RelationKinds.BelongsTo: {
|
|
127
|
+
const localKey = rel.localKey || findPrimaryKey(rel.target);
|
|
128
|
+
return {
|
|
129
|
+
name: relationName,
|
|
130
|
+
aliasPrefix,
|
|
131
|
+
type: rel.type,
|
|
132
|
+
targetTable: rel.target.name,
|
|
133
|
+
targetPrimaryKey: findPrimaryKey(rel.target),
|
|
134
|
+
foreignKey: rel.foreignKey,
|
|
135
|
+
localKey,
|
|
136
|
+
columns
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
case RelationKinds.BelongsToMany: {
|
|
140
|
+
const many = rel as BelongsToManyRelation;
|
|
141
|
+
const localKey = many.localKey || findPrimaryKey(this.table);
|
|
142
|
+
const targetPk = many.targetKey || findPrimaryKey(many.target);
|
|
143
|
+
const pivotPk = many.pivotPrimaryKey || findPrimaryKey(many.pivotTable);
|
|
144
|
+
const pivotAliasPrefix = pivot?.aliasPrefix ?? `${aliasPrefix}_pivot`;
|
|
145
|
+
const pivotColumns =
|
|
146
|
+
pivot?.columns ??
|
|
147
|
+
many.defaultPivotColumns ??
|
|
148
|
+
buildDefaultPivotColumns(many, pivotPk);
|
|
149
|
+
|
|
150
150
|
return {
|
|
151
151
|
name: relationName,
|
|
152
152
|
aliasPrefix,
|
|
@@ -165,18 +165,48 @@ export class HydrationPlanner {
|
|
|
165
165
|
}
|
|
166
166
|
};
|
|
167
167
|
}
|
|
168
|
+
case RelationKinds.MorphOne: {
|
|
169
|
+
const morphRel = rel as MorphOneRelation;
|
|
170
|
+
const localKey = morphRel.localKey || findPrimaryKey(this.table);
|
|
171
|
+
return {
|
|
172
|
+
name: relationName,
|
|
173
|
+
aliasPrefix,
|
|
174
|
+
type: rel.type,
|
|
175
|
+
targetTable: morphRel.target.name,
|
|
176
|
+
targetPrimaryKey: findPrimaryKey(morphRel.target),
|
|
177
|
+
foreignKey: morphRel.idField,
|
|
178
|
+
localKey,
|
|
179
|
+
columns
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
case RelationKinds.MorphMany: {
|
|
183
|
+
const morphRel = rel as MorphManyRelation;
|
|
184
|
+
const localKey = morphRel.localKey || findPrimaryKey(this.table);
|
|
185
|
+
return {
|
|
186
|
+
name: relationName,
|
|
187
|
+
aliasPrefix,
|
|
188
|
+
type: rel.type,
|
|
189
|
+
targetTable: morphRel.target.name,
|
|
190
|
+
targetPrimaryKey: findPrimaryKey(morphRel.target),
|
|
191
|
+
foreignKey: morphRel.idField,
|
|
192
|
+
localKey,
|
|
193
|
+
columns
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
case RelationKinds.MorphTo:
|
|
197
|
+
throw new Error('MorphTo relations do not support hydration planning via JOIN.');
|
|
168
198
|
}
|
|
169
199
|
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Builds a default hydration plan for a table
|
|
174
|
-
* @param table - Table definition
|
|
175
|
-
* @returns Default hydration plan
|
|
176
|
-
*/
|
|
177
|
-
const buildDefaultHydrationPlan = (table: TableDef): HydrationPlan => ({
|
|
178
|
-
rootTable: table.name,
|
|
179
|
-
rootPrimaryKey: findPrimaryKey(table),
|
|
180
|
-
rootColumns: [],
|
|
181
|
-
relations: []
|
|
182
|
-
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Builds a default hydration plan for a table
|
|
204
|
+
* @param table - Table definition
|
|
205
|
+
* @returns Default hydration plan
|
|
206
|
+
*/
|
|
207
|
+
const buildDefaultHydrationPlan = (table: TableDef): HydrationPlan => ({
|
|
208
|
+
rootTable: table.name,
|
|
209
|
+
rootPrimaryKey: findPrimaryKey(table),
|
|
210
|
+
rootColumns: [],
|
|
211
|
+
relations: []
|
|
212
|
+
});
|