metal-orm 1.1.9 → 1.1.10
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 +2147 -239
- 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 +2119 -239
- package/dist/index.js.map +1 -1
- package/package.json +17 -12
- package/src/bulk/bulk-context.ts +83 -0
- package/src/bulk/bulk-delete-executor.ts +89 -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 +95 -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/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,398 +1,482 @@
|
|
|
1
|
-
import { SelectQueryBuilder } from '../query-builder/select.js';
|
|
2
|
-
import {
|
|
3
|
-
hasMany,
|
|
4
|
-
hasOne,
|
|
5
|
-
belongsTo,
|
|
6
|
-
belongsToMany,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
type
|
|
12
|
-
type
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
import {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
.replace(
|
|
69
|
-
.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
relation.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
relation.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
relation.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
1
|
+
import { SelectQueryBuilder } from '../query-builder/select.js';
|
|
2
|
+
import {
|
|
3
|
+
hasMany,
|
|
4
|
+
hasOne,
|
|
5
|
+
belongsTo,
|
|
6
|
+
belongsToMany,
|
|
7
|
+
morphTo,
|
|
8
|
+
morphOne,
|
|
9
|
+
morphMany,
|
|
10
|
+
RelationKinds,
|
|
11
|
+
type HasManyRelation,
|
|
12
|
+
type HasOneRelation,
|
|
13
|
+
type BelongsToRelation,
|
|
14
|
+
type BelongsToManyRelation,
|
|
15
|
+
type RelationDef
|
|
16
|
+
} from '../schema/relation.js';
|
|
17
|
+
import { TableDef } from '../schema/table.js';
|
|
18
|
+
import { isTableDef } from '../schema/table-guards.js';
|
|
19
|
+
import {
|
|
20
|
+
buildTableDef,
|
|
21
|
+
EntityConstructor,
|
|
22
|
+
EntityMetadata,
|
|
23
|
+
EntityOrTableTarget,
|
|
24
|
+
EntityOrTableTargetResolver,
|
|
25
|
+
getAllEntityMetadata,
|
|
26
|
+
getEntityMetadata,
|
|
27
|
+
addRelationMetadata,
|
|
28
|
+
addTransformerMetadata,
|
|
29
|
+
type RelationMetadata
|
|
30
|
+
} from '../orm/entity-metadata.js';
|
|
31
|
+
import { getDecoratorMetadata } from './decorator-metadata.js';
|
|
32
|
+
|
|
33
|
+
import { tableRef, type TableRef } from '../schema/table.js';
|
|
34
|
+
import {
|
|
35
|
+
SelectableKeys,
|
|
36
|
+
ColumnDef,
|
|
37
|
+
HasManyCollection,
|
|
38
|
+
HasOneReference,
|
|
39
|
+
BelongsToReference,
|
|
40
|
+
ManyToManyCollection,
|
|
41
|
+
EntityInstance
|
|
42
|
+
} from '../schema/types.js';
|
|
43
|
+
|
|
44
|
+
const unwrapTarget = (target: EntityOrTableTargetResolver): EntityOrTableTarget => {
|
|
45
|
+
if (typeof target === 'function' && (target as Function).prototype === undefined) {
|
|
46
|
+
return (target as () => EntityOrTableTarget)();
|
|
47
|
+
}
|
|
48
|
+
return target as EntityOrTableTarget;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const resolveTableTarget = (
|
|
52
|
+
target: EntityOrTableTargetResolver,
|
|
53
|
+
tableMap: Map<EntityConstructor, TableDef>
|
|
54
|
+
): TableDef => {
|
|
55
|
+
const resolved = unwrapTarget(target);
|
|
56
|
+
if (isTableDef(resolved)) {
|
|
57
|
+
return resolved;
|
|
58
|
+
}
|
|
59
|
+
const table = tableMap.get(resolved as EntityConstructor);
|
|
60
|
+
if (!table) {
|
|
61
|
+
throw new Error(`Entity '${(resolved as EntityConstructor).name}' is not registered with decorators`);
|
|
62
|
+
}
|
|
63
|
+
return table;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const toSnakeCase = (value: string): string => {
|
|
67
|
+
return value
|
|
68
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
69
|
+
.replace(/[^a-z0-9_]+/gi, '_')
|
|
70
|
+
.replace(/__+/g, '_')
|
|
71
|
+
.replace(/^_|_$/g, '')
|
|
72
|
+
.toLowerCase();
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const normalizeEntityName = (value: string): string => {
|
|
76
|
+
const stripped = value.replace(/Entity$/i, '');
|
|
77
|
+
const normalized = toSnakeCase(stripped || value);
|
|
78
|
+
return normalized || 'unknown';
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const getPivotKeyBaseFromTarget = (target: EntityOrTableTargetResolver): string => {
|
|
82
|
+
const resolved = unwrapTarget(target);
|
|
83
|
+
if (isTableDef(resolved)) {
|
|
84
|
+
return toSnakeCase(resolved.name || 'unknown');
|
|
85
|
+
}
|
|
86
|
+
const ctor = resolved as EntityConstructor;
|
|
87
|
+
return normalizeEntityName(ctor.name || 'unknown');
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const getPivotKeyBaseFromRoot = (meta: EntityMetadata): string => {
|
|
91
|
+
return normalizeEntityName(meta.target.name || meta.tableName || 'unknown');
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const buildRelationDefinitions = (
|
|
95
|
+
meta: EntityMetadata,
|
|
96
|
+
tableMap: Map<EntityConstructor, TableDef>
|
|
97
|
+
): Record<string, RelationDef> => {
|
|
98
|
+
const relations: Record<string, RelationDef> = {};
|
|
99
|
+
|
|
100
|
+
for (const [name, relation] of Object.entries(meta.relations)) {
|
|
101
|
+
switch (relation.kind) {
|
|
102
|
+
case RelationKinds.HasOne: {
|
|
103
|
+
const foreignKey = relation.foreignKey ?? `${getPivotKeyBaseFromRoot(meta)}_id`;
|
|
104
|
+
relations[name] = hasOne(
|
|
105
|
+
resolveTableTarget(relation.target, tableMap),
|
|
106
|
+
foreignKey,
|
|
107
|
+
relation.localKey,
|
|
108
|
+
relation.cascade
|
|
109
|
+
);
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
case RelationKinds.HasMany: {
|
|
113
|
+
const foreignKey = relation.foreignKey ?? `${getPivotKeyBaseFromRoot(meta)}_id`;
|
|
114
|
+
relations[name] = hasMany(
|
|
115
|
+
resolveTableTarget(relation.target, tableMap),
|
|
116
|
+
foreignKey,
|
|
117
|
+
relation.localKey,
|
|
118
|
+
relation.cascade
|
|
119
|
+
);
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
case RelationKinds.BelongsTo: {
|
|
123
|
+
relations[name] = belongsTo(
|
|
124
|
+
resolveTableTarget(relation.target, tableMap),
|
|
125
|
+
relation.foreignKey,
|
|
126
|
+
relation.localKey,
|
|
127
|
+
relation.cascade
|
|
128
|
+
);
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
case RelationKinds.BelongsToMany: {
|
|
132
|
+
const pivotForeignKeyToRoot =
|
|
133
|
+
relation.pivotForeignKeyToRoot ?? `${getPivotKeyBaseFromRoot(meta)}_id`;
|
|
134
|
+
const pivotForeignKeyToTarget =
|
|
135
|
+
relation.pivotForeignKeyToTarget ?? `${getPivotKeyBaseFromTarget(relation.target)}_id`;
|
|
136
|
+
relations[name] = belongsToMany(
|
|
137
|
+
resolveTableTarget(relation.target, tableMap),
|
|
138
|
+
resolveTableTarget(relation.pivotTable, tableMap),
|
|
139
|
+
{
|
|
140
|
+
pivotForeignKeyToRoot,
|
|
141
|
+
pivotForeignKeyToTarget,
|
|
142
|
+
localKey: relation.localKey,
|
|
143
|
+
targetKey: relation.targetKey,
|
|
144
|
+
pivotPrimaryKey: relation.pivotPrimaryKey,
|
|
145
|
+
defaultPivotColumns: relation.defaultPivotColumns,
|
|
146
|
+
cascade: relation.cascade
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
case RelationKinds.MorphOne: {
|
|
152
|
+
relations[name] = morphOne(
|
|
153
|
+
resolveTableTarget(relation.target, tableMap),
|
|
154
|
+
{
|
|
155
|
+
as: relation.morphName,
|
|
156
|
+
typeValue: relation.typeValue,
|
|
157
|
+
typeField: relation.typeField,
|
|
158
|
+
idField: relation.idField,
|
|
159
|
+
localKey: relation.localKey,
|
|
160
|
+
cascade: relation.cascade
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
case RelationKinds.MorphMany: {
|
|
166
|
+
relations[name] = morphMany(
|
|
167
|
+
resolveTableTarget(relation.target, tableMap),
|
|
168
|
+
{
|
|
169
|
+
as: relation.morphName,
|
|
170
|
+
typeValue: relation.typeValue,
|
|
171
|
+
typeField: relation.typeField,
|
|
172
|
+
idField: relation.idField,
|
|
173
|
+
localKey: relation.localKey,
|
|
174
|
+
cascade: relation.cascade
|
|
175
|
+
}
|
|
176
|
+
);
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
case RelationKinds.MorphTo: {
|
|
180
|
+
const resolvedTargets: Record<string, TableDef> = {};
|
|
181
|
+
for (const [typeValue, targetResolver] of Object.entries(relation.targets)) {
|
|
182
|
+
resolvedTargets[typeValue] = resolveTableTarget(targetResolver, tableMap);
|
|
183
|
+
}
|
|
184
|
+
relations[name] = morphTo({
|
|
185
|
+
typeField: relation.typeField,
|
|
186
|
+
idField: relation.idField,
|
|
187
|
+
targets: resolvedTargets,
|
|
188
|
+
targetKey: relation.targetKey,
|
|
189
|
+
cascade: relation.cascade
|
|
190
|
+
});
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return relations;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Bootstraps all entities by building their table definitions and relations.
|
|
201
|
+
* @returns An array of table definitions for all bootstrapped entities.
|
|
202
|
+
*/
|
|
203
|
+
export const bootstrapEntities = (): TableDef[] => {
|
|
204
|
+
const metas = getAllEntityMetadata();
|
|
205
|
+
const tableMap = new Map<EntityConstructor, TableDef>();
|
|
206
|
+
|
|
207
|
+
// Process decorator metadata for each entity
|
|
208
|
+
for (const meta of metas) {
|
|
209
|
+
const decoratorMetadata = getDecoratorMetadata(meta.target);
|
|
210
|
+
if (decoratorMetadata?.transformers) {
|
|
211
|
+
for (const { propertyName, metadata } of decoratorMetadata.transformers) {
|
|
212
|
+
addTransformerMetadata(meta.target, propertyName, metadata);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const table = buildTableDef(meta);
|
|
217
|
+
tableMap.set(meta.target, table);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
for (const meta of metas) {
|
|
221
|
+
const table = meta.table!;
|
|
222
|
+
const relations = buildRelationDefinitions(meta, tableMap);
|
|
223
|
+
table.relations = relations;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return metas.map(meta => meta.table!) as TableDef[];
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Builds a single RelationDef from RelationMetadata using the current set of
|
|
231
|
+
* already-bootstrapped entity tables as the resolution map.
|
|
232
|
+
*/
|
|
233
|
+
const resolveSingleRelation = (
|
|
234
|
+
relationName: string,
|
|
235
|
+
relation: RelationMetadata,
|
|
236
|
+
rootMeta: EntityMetadata
|
|
237
|
+
): RelationDef => {
|
|
238
|
+
// Build a tableMap from all entities that are already bootstrapped
|
|
239
|
+
const tableMap = new Map<EntityConstructor, TableDef>();
|
|
240
|
+
for (const m of getAllEntityMetadata()) {
|
|
241
|
+
if (m.table) tableMap.set(m.target, m.table);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
switch (relation.kind) {
|
|
245
|
+
case RelationKinds.HasOne: {
|
|
246
|
+
const foreignKey = relation.foreignKey ?? `${getPivotKeyBaseFromRoot(rootMeta)}_id`;
|
|
247
|
+
return hasOne(
|
|
248
|
+
resolveTableTarget(relation.target, tableMap),
|
|
249
|
+
foreignKey,
|
|
250
|
+
relation.localKey,
|
|
251
|
+
relation.cascade
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
case RelationKinds.HasMany: {
|
|
255
|
+
const foreignKey = relation.foreignKey ?? `${getPivotKeyBaseFromRoot(rootMeta)}_id`;
|
|
256
|
+
return hasMany(
|
|
257
|
+
resolveTableTarget(relation.target, tableMap),
|
|
258
|
+
foreignKey,
|
|
259
|
+
relation.localKey,
|
|
260
|
+
relation.cascade
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
case RelationKinds.BelongsTo: {
|
|
264
|
+
return belongsTo(
|
|
265
|
+
resolveTableTarget(relation.target, tableMap),
|
|
266
|
+
relation.foreignKey,
|
|
267
|
+
relation.localKey,
|
|
268
|
+
relation.cascade
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
case RelationKinds.BelongsToMany: {
|
|
272
|
+
const pivotForeignKeyToRoot =
|
|
273
|
+
relation.pivotForeignKeyToRoot ?? `${getPivotKeyBaseFromRoot(rootMeta)}_id`;
|
|
274
|
+
const pivotForeignKeyToTarget =
|
|
275
|
+
relation.pivotForeignKeyToTarget ?? `${getPivotKeyBaseFromTarget(relation.target)}_id`;
|
|
276
|
+
return belongsToMany(
|
|
277
|
+
resolveTableTarget(relation.target, tableMap),
|
|
278
|
+
resolveTableTarget(relation.pivotTable, tableMap),
|
|
279
|
+
{
|
|
280
|
+
pivotForeignKeyToRoot,
|
|
281
|
+
pivotForeignKeyToTarget,
|
|
282
|
+
localKey: relation.localKey,
|
|
283
|
+
targetKey: relation.targetKey,
|
|
284
|
+
pivotPrimaryKey: relation.pivotPrimaryKey,
|
|
285
|
+
defaultPivotColumns: relation.defaultPivotColumns,
|
|
286
|
+
cascade: relation.cascade
|
|
287
|
+
}
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
case RelationKinds.MorphOne: {
|
|
291
|
+
return morphOne(
|
|
292
|
+
resolveTableTarget(relation.target, tableMap),
|
|
293
|
+
{
|
|
294
|
+
as: relation.morphName,
|
|
295
|
+
typeValue: relation.typeValue,
|
|
296
|
+
typeField: relation.typeField,
|
|
297
|
+
idField: relation.idField,
|
|
298
|
+
localKey: relation.localKey,
|
|
299
|
+
cascade: relation.cascade
|
|
300
|
+
}
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
case RelationKinds.MorphMany: {
|
|
304
|
+
return morphMany(
|
|
305
|
+
resolveTableTarget(relation.target, tableMap),
|
|
306
|
+
{
|
|
307
|
+
as: relation.morphName,
|
|
308
|
+
typeValue: relation.typeValue,
|
|
309
|
+
typeField: relation.typeField,
|
|
310
|
+
idField: relation.idField,
|
|
311
|
+
localKey: relation.localKey,
|
|
312
|
+
cascade: relation.cascade
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
case RelationKinds.MorphTo: {
|
|
317
|
+
const resolvedTargets: Record<string, TableDef> = {};
|
|
318
|
+
for (const [typeValue, targetResolver] of Object.entries(relation.targets)) {
|
|
319
|
+
resolvedTargets[typeValue] = resolveTableTarget(targetResolver, tableMap);
|
|
320
|
+
}
|
|
321
|
+
return morphTo({
|
|
322
|
+
typeField: relation.typeField,
|
|
323
|
+
idField: relation.idField,
|
|
324
|
+
targets: resolvedTargets,
|
|
325
|
+
targetKey: relation.targetKey,
|
|
326
|
+
cascade: relation.cascade
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
default:
|
|
330
|
+
throw new Error(`Unknown relation kind for relation '${relationName}'`);
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Adds (or replaces) a single named relation on a decorator-based entity at any
|
|
336
|
+
* time — before or after `bootstrapEntities()` has been called.
|
|
337
|
+
*
|
|
338
|
+
* - Always writes the metadata into the entity's `EntityMetadata.relations` so
|
|
339
|
+
* that a future bootstrap will include it.
|
|
340
|
+
* - If the entity table has already been built, the resolved `RelationDef` is
|
|
341
|
+
* also patched directly into `table.relations` so the change is immediately
|
|
342
|
+
* visible to query builders and hydration.
|
|
343
|
+
*
|
|
344
|
+
* @param ctor - The entity class decorated with `@Entity`
|
|
345
|
+
* @param name - The relation property name (key used in `.include()`)
|
|
346
|
+
* @param relation - Relation metadata in the same format as decorators expect
|
|
347
|
+
*
|
|
348
|
+
* @example
|
|
349
|
+
* ```ts
|
|
350
|
+
* // Same options as @HasMany decorator, usable at runtime
|
|
351
|
+
* addEntityRelation(User, 'comments', {
|
|
352
|
+
* kind: RelationKinds.HasMany,
|
|
353
|
+
* propertyKey: 'comments',
|
|
354
|
+
* target: () => Comment,
|
|
355
|
+
* foreignKey: 'user_id',
|
|
356
|
+
* });
|
|
357
|
+
* ```
|
|
358
|
+
*/
|
|
359
|
+
export const addEntityRelation = (
|
|
360
|
+
ctor: EntityConstructor,
|
|
361
|
+
name: string,
|
|
362
|
+
relation: RelationMetadata
|
|
363
|
+
): void => {
|
|
364
|
+
// Check registration BEFORE auto-creating metadata via addRelationMetadata
|
|
365
|
+
const meta = getEntityMetadata(ctor);
|
|
366
|
+
if (!meta) {
|
|
367
|
+
throw new Error(`Entity '${ctor.name}' is not registered. Did you decorate it with @Entity?`);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// 1. Write to the decorator-layer metadata store (survives a future bootstrap)
|
|
371
|
+
addRelationMetadata(ctor, name, relation);
|
|
372
|
+
|
|
373
|
+
// 2. If the table is already built, patch it immediately
|
|
374
|
+
if (meta.table) {
|
|
375
|
+
meta.table.relations[name] = resolveSingleRelation(name, relation, meta);
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Gets the table definition for a given entity constructor.
|
|
381
|
+
* Bootstraps entities if necessary.
|
|
382
|
+
* @param ctor - The entity constructor.
|
|
383
|
+
* @returns The table definition or undefined if not found.
|
|
384
|
+
*/
|
|
385
|
+
export const getTableDefFromEntity = <TTable extends TableDef = TableDef>(ctor: EntityConstructor): TTable | undefined => {
|
|
386
|
+
const meta = getEntityMetadata(ctor);
|
|
387
|
+
if (!meta) return undefined;
|
|
388
|
+
if (!meta.table) {
|
|
389
|
+
bootstrapEntities();
|
|
390
|
+
}
|
|
391
|
+
if (!meta.table) {
|
|
392
|
+
throw new Error(`Failed to build table definition for entity '${ctor.name}'`);
|
|
393
|
+
}
|
|
394
|
+
return meta.table as TTable;
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Creates a select query builder for the given entity.
|
|
399
|
+
* @param ctor - The entity constructor.
|
|
400
|
+
* @returns A select query builder for the entity.
|
|
401
|
+
*/
|
|
402
|
+
type NonFunctionKeys<T> = {
|
|
403
|
+
[K in keyof T]-?: T[K] extends (...args: unknown[]) => unknown ? never : K
|
|
404
|
+
}[keyof T];
|
|
405
|
+
|
|
406
|
+
type RelationKeys<TEntity extends object> =
|
|
407
|
+
Exclude<NonFunctionKeys<TEntity>, SelectableKeys<TEntity>> & string;
|
|
408
|
+
|
|
409
|
+
type EntityTable<TEntity extends object> =
|
|
410
|
+
Omit<TableDef<{ [K in SelectableKeys<TEntity>]: ColumnDef }>, 'relations'> & {
|
|
411
|
+
relations: {
|
|
412
|
+
[K in RelationKeys<TEntity>]:
|
|
413
|
+
NonNullable<TEntity[K]> extends HasManyCollection<infer TChild>
|
|
414
|
+
? HasManyRelation<EntityTable<NonNullable<TChild> & object>>
|
|
415
|
+
: NonNullable<TEntity[K]> extends ManyToManyCollection<infer TTarget, infer TPivot>
|
|
416
|
+
? BelongsToManyRelation<
|
|
417
|
+
EntityTable<NonNullable<TTarget> & object>,
|
|
418
|
+
TPivot extends object ? EntityTable<NonNullable<TPivot> & object> : TableDef
|
|
419
|
+
>
|
|
420
|
+
: NonNullable<TEntity[K]> extends HasOneReference<infer TChild>
|
|
421
|
+
? HasOneRelation<EntityTable<NonNullable<TChild> & object>>
|
|
422
|
+
: NonNullable<TEntity[K]> extends BelongsToReference<infer TParent>
|
|
423
|
+
? BelongsToRelation<EntityTable<NonNullable<TParent> & object>>
|
|
424
|
+
: NonNullable<TEntity[K]> extends object
|
|
425
|
+
? BelongsToRelation<EntityTable<NonNullable<TEntity[K]> & object>>
|
|
426
|
+
: never;
|
|
427
|
+
};
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
export type DecoratedEntityInstance<TEntity extends object> =
|
|
431
|
+
TEntity & EntityInstance<EntityTable<TEntity>>;
|
|
432
|
+
|
|
433
|
+
export const selectFromEntity = <TEntity extends object>(
|
|
434
|
+
ctor: EntityConstructor<TEntity>
|
|
435
|
+
): SelectQueryBuilder<DecoratedEntityInstance<TEntity>, EntityTable<TEntity>> => {
|
|
436
|
+
const table = getTableDefFromEntity(ctor);
|
|
437
|
+
if (!table) {
|
|
438
|
+
throw new Error(`Entity '${ctor.name}' is not registered with decorators or has not been bootstrapped`);
|
|
439
|
+
}
|
|
440
|
+
return new SelectQueryBuilder(
|
|
441
|
+
table as unknown as EntityTable<TEntity>,
|
|
442
|
+
undefined,
|
|
443
|
+
undefined,
|
|
444
|
+
undefined,
|
|
445
|
+
undefined,
|
|
446
|
+
undefined,
|
|
447
|
+
ctor
|
|
448
|
+
);
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Public API: opt-in ergonomic entity reference (decorator-level).
|
|
453
|
+
*
|
|
454
|
+
* Lazily bootstraps entity metadata (via getTableDefFromEntity) and returns a
|
|
455
|
+
* `tableRef(...)`-style proxy so users can write `u.id` instead of `u.columns.id`.
|
|
456
|
+
*/
|
|
457
|
+
export const entityRef = <TEntity extends object>(
|
|
458
|
+
ctor: EntityConstructor<TEntity>
|
|
459
|
+
): TableRef<EntityTable<TEntity>> => {
|
|
460
|
+
const table = getTableDefFromEntity(ctor);
|
|
461
|
+
if (!table) {
|
|
462
|
+
throw new Error(`Entity '${ctor.name}' is not registered with decorators or has not been bootstrapped`);
|
|
463
|
+
}
|
|
464
|
+
return tableRef(table as EntityTable<TEntity>);
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
type EntityRefsTuple<T extends readonly EntityConstructor<object>[]> = {
|
|
468
|
+
[K in keyof T]: T[K] extends EntityConstructor<infer TEntity>
|
|
469
|
+
? TableRef<EntityTable<TEntity & object>>
|
|
470
|
+
: never;
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Public API: variadic entity references.
|
|
475
|
+
* Usage:
|
|
476
|
+
* const [u, p] = entityRefs(User, Post);
|
|
477
|
+
*/
|
|
478
|
+
export const entityRefs = <T extends readonly EntityConstructor<object>[]>(
|
|
479
|
+
...ctors: T
|
|
480
|
+
): EntityRefsTuple<T> => {
|
|
481
|
+
return ctors.map(ctor => entityRef(ctor)) as EntityRefsTuple<T>;
|
|
482
|
+
};
|