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.
Files changed (77) hide show
  1. package/README.md +769 -764
  2. package/dist/index.cjs +2255 -284
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +559 -39
  5. package/dist/index.d.ts +559 -39
  6. package/dist/index.js +2227 -284
  7. package/dist/index.js.map +1 -1
  8. package/package.json +17 -12
  9. package/scripts/generate-entities/render.mjs +21 -12
  10. package/scripts/generate-entities/schema.mjs +87 -73
  11. package/scripts/generate-entities/tree-detection.mjs +67 -61
  12. package/src/bulk/bulk-context.ts +83 -0
  13. package/src/bulk/bulk-delete-executor.ts +87 -0
  14. package/src/bulk/bulk-executor.base.ts +73 -0
  15. package/src/bulk/bulk-insert-executor.ts +74 -0
  16. package/src/bulk/bulk-types.ts +70 -0
  17. package/src/bulk/bulk-update-executor.ts +192 -0
  18. package/src/bulk/bulk-upsert-executor.ts +93 -0
  19. package/src/bulk/bulk-utils.ts +91 -0
  20. package/src/bulk/index.ts +18 -0
  21. package/src/codegen/typescript.ts +30 -21
  22. package/src/core/ast/expression-builders.ts +107 -10
  23. package/src/core/ast/expression-nodes.ts +52 -22
  24. package/src/core/ast/expression-visitor.ts +23 -13
  25. package/src/core/ddl/introspect/mysql.ts +113 -36
  26. package/src/core/dialect/abstract.ts +30 -17
  27. package/src/core/dialect/mysql/index.ts +20 -5
  28. package/src/core/execution/db-executor.ts +96 -64
  29. package/src/core/execution/executors/better-sqlite3-executor.ts +94 -0
  30. package/src/core/execution/executors/mssql-executor.ts +66 -34
  31. package/src/core/execution/executors/mysql-executor.ts +98 -66
  32. package/src/core/execution/executors/postgres-executor.ts +33 -11
  33. package/src/core/execution/executors/sqlite-executor.ts +86 -30
  34. package/src/decorators/bootstrap.ts +482 -398
  35. package/src/decorators/column-decorator.ts +87 -96
  36. package/src/decorators/decorator-metadata.ts +100 -24
  37. package/src/decorators/entity.ts +27 -24
  38. package/src/decorators/relations.ts +231 -149
  39. package/src/decorators/transformers/transformer-decorators.ts +26 -29
  40. package/src/decorators/validators/country-validators-decorators.ts +9 -15
  41. package/src/dto/apply-filter.ts +568 -551
  42. package/src/index.ts +16 -9
  43. package/src/orm/entity-hydration.ts +116 -72
  44. package/src/orm/entity-metadata.ts +347 -301
  45. package/src/orm/entity-relations.ts +264 -207
  46. package/src/orm/entity.ts +199 -199
  47. package/src/orm/execute.ts +13 -13
  48. package/src/orm/lazy-batch/morph-many.ts +70 -0
  49. package/src/orm/lazy-batch/morph-one.ts +69 -0
  50. package/src/orm/lazy-batch/morph-to.ts +59 -0
  51. package/src/orm/lazy-batch.ts +4 -1
  52. package/src/orm/orm-session.ts +170 -104
  53. package/src/orm/pooled-executor-factory.ts +99 -58
  54. package/src/orm/query-logger.ts +49 -40
  55. package/src/orm/relation-change-processor.ts +198 -96
  56. package/src/orm/relations/belongs-to.ts +143 -143
  57. package/src/orm/relations/has-many.ts +204 -204
  58. package/src/orm/relations/has-one.ts +174 -174
  59. package/src/orm/relations/many-to-many.ts +288 -288
  60. package/src/orm/relations/morph-many.ts +156 -0
  61. package/src/orm/relations/morph-one.ts +151 -0
  62. package/src/orm/relations/morph-to.ts +162 -0
  63. package/src/orm/save-graph.ts +116 -1
  64. package/src/query-builder/expression-table-mapper.ts +5 -0
  65. package/src/query-builder/hydration-manager.ts +345 -345
  66. package/src/query-builder/hydration-planner.ts +178 -148
  67. package/src/query-builder/relation-conditions.ts +171 -151
  68. package/src/query-builder/relation-cte-builder.ts +5 -1
  69. package/src/query-builder/relation-filter-utils.ts +9 -6
  70. package/src/query-builder/relation-include-strategies.ts +44 -2
  71. package/src/query-builder/relation-join-strategies.ts +8 -1
  72. package/src/query-builder/relation-service.ts +250 -241
  73. package/src/query-builder/select/select-operations.ts +110 -105
  74. package/src/query-builder/update-include.ts +4 -0
  75. package/src/schema/relation.ts +296 -188
  76. package/src/schema/types.ts +138 -123
  77. 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
+ });