metal-orm 1.0.57 → 1.0.58
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 +22 -15
- package/dist/index.cjs +640 -83
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +113 -45
- package/dist/index.d.ts +113 -45
- package/dist/index.js +639 -83
- package/dist/index.js.map +1 -1
- package/package.json +69 -69
- package/src/decorators/bootstrap.ts +39 -3
- package/src/orm/entity-meta.ts +6 -3
- package/src/orm/entity.ts +81 -14
- package/src/orm/execute.ts +87 -20
- package/src/orm/lazy-batch.ts +237 -54
- package/src/orm/relations/belongs-to.ts +2 -2
- package/src/orm/relations/has-many.ts +23 -9
- package/src/orm/relations/has-one.ts +2 -2
- package/src/orm/relations/many-to-many.ts +27 -13
- package/src/orm/save-graph-types.ts +2 -2
- package/src/orm/save-graph.ts +18 -18
- package/src/query-builder/relation-conditions.ts +80 -59
- package/src/query-builder/relation-service.ts +399 -95
- package/src/query-builder/relation-types.ts +2 -2
- package/src/query-builder/select.ts +58 -40
- package/src/schema/types.ts +106 -89
package/package.json
CHANGED
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "metal-orm",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"type": "module",
|
|
5
|
-
"types": "./dist/index.d.ts",
|
|
6
|
-
"engines": {
|
|
7
|
-
"node": ">=20.0.0"
|
|
8
|
-
},
|
|
9
|
-
"bin": {
|
|
10
|
-
"metal-orm-gen": "./scripts/generate-entities.mjs"
|
|
11
|
-
},
|
|
12
|
-
"exports": {
|
|
13
|
-
".": {
|
|
14
|
-
"types": "./dist/index.d.ts",
|
|
15
|
-
"import": "./dist/index.js",
|
|
16
|
-
"require": "./dist/index.cjs"
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
"files": [
|
|
20
|
-
"dist",
|
|
21
|
-
"src",
|
|
22
|
-
"global.d.ts",
|
|
23
|
-
"scripts"
|
|
24
|
-
],
|
|
25
|
-
"scripts": {
|
|
26
|
-
"build": "tsup",
|
|
27
|
-
"check": "tsc --noEmit",
|
|
28
|
-
"gen:entities": "node scripts/generate-entities.mjs",
|
|
29
|
-
"test": "vitest",
|
|
30
|
-
"test:ui": "vitest --ui",
|
|
31
|
-
"show-sql": "node scripts/show-sql.mjs",
|
|
32
|
-
"lint": "node scripts/run-eslint.mjs",
|
|
33
|
-
"lint:fix": "node scripts/run-eslint.mjs --fix"
|
|
34
|
-
},
|
|
35
|
-
"peerDependencies": {
|
|
36
|
-
"mysql2": "^3.9.0",
|
|
37
|
-
"pg": "^8.0.0",
|
|
38
|
-
"sqlite3": "^5.1.6",
|
|
39
|
-
"tedious": "^16.0.0"
|
|
40
|
-
},
|
|
41
|
-
"peerDependenciesMeta": {
|
|
42
|
-
"mysql2": {
|
|
43
|
-
"optional": true
|
|
44
|
-
},
|
|
45
|
-
"pg": {
|
|
46
|
-
"optional": true
|
|
47
|
-
},
|
|
48
|
-
"sqlite3": {
|
|
49
|
-
"optional": true
|
|
50
|
-
},
|
|
51
|
-
"tedious": {
|
|
52
|
-
"optional": true
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
"devDependencies": {
|
|
56
|
-
"@vitest/ui": "^4.0.14",
|
|
57
|
-
"@typescript-eslint/eslint-plugin": "^8.20.0",
|
|
58
|
-
"@typescript-eslint/parser": "^8.20.0",
|
|
59
|
-
"eslint": "^8.57.0",
|
|
60
|
-
"eslint-plugin-deprecation": "^3.0.0",
|
|
61
|
-
"mysql2": "^3.15.3",
|
|
62
|
-
"pg": "^8.16.3",
|
|
63
|
-
"sqlite3": "^5.1.7",
|
|
64
|
-
"tedious": "^19.1.3",
|
|
65
|
-
"tsup": "^8.0.0",
|
|
66
|
-
"typescript": "^5.5.0",
|
|
67
|
-
"vitest": "^4.0.14"
|
|
68
|
-
}
|
|
69
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "metal-orm",
|
|
3
|
+
"version": "1.0.58",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"types": "./dist/index.d.ts",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=20.0.0"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"metal-orm-gen": "./scripts/generate-entities.mjs"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js",
|
|
16
|
+
"require": "./dist/index.cjs"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"src",
|
|
22
|
+
"global.d.ts",
|
|
23
|
+
"scripts"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsup",
|
|
27
|
+
"check": "tsc --noEmit",
|
|
28
|
+
"gen:entities": "node scripts/generate-entities.mjs",
|
|
29
|
+
"test": "vitest",
|
|
30
|
+
"test:ui": "vitest --ui",
|
|
31
|
+
"show-sql": "node scripts/show-sql.mjs",
|
|
32
|
+
"lint": "node scripts/run-eslint.mjs",
|
|
33
|
+
"lint:fix": "node scripts/run-eslint.mjs --fix"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"mysql2": "^3.9.0",
|
|
37
|
+
"pg": "^8.0.0",
|
|
38
|
+
"sqlite3": "^5.1.6",
|
|
39
|
+
"tedious": "^16.0.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependenciesMeta": {
|
|
42
|
+
"mysql2": {
|
|
43
|
+
"optional": true
|
|
44
|
+
},
|
|
45
|
+
"pg": {
|
|
46
|
+
"optional": true
|
|
47
|
+
},
|
|
48
|
+
"sqlite3": {
|
|
49
|
+
"optional": true
|
|
50
|
+
},
|
|
51
|
+
"tedious": {
|
|
52
|
+
"optional": true
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@vitest/ui": "^4.0.14",
|
|
57
|
+
"@typescript-eslint/eslint-plugin": "^8.20.0",
|
|
58
|
+
"@typescript-eslint/parser": "^8.20.0",
|
|
59
|
+
"eslint": "^8.57.0",
|
|
60
|
+
"eslint-plugin-deprecation": "^3.0.0",
|
|
61
|
+
"mysql2": "^3.15.3",
|
|
62
|
+
"pg": "^8.16.3",
|
|
63
|
+
"sqlite3": "^5.1.7",
|
|
64
|
+
"tedious": "^19.1.3",
|
|
65
|
+
"tsup": "^8.0.0",
|
|
66
|
+
"typescript": "^5.5.0",
|
|
67
|
+
"vitest": "^4.0.14"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -5,6 +5,10 @@ import {
|
|
|
5
5
|
belongsTo,
|
|
6
6
|
belongsToMany,
|
|
7
7
|
RelationKinds,
|
|
8
|
+
type HasManyRelation,
|
|
9
|
+
type HasOneRelation,
|
|
10
|
+
type BelongsToRelation,
|
|
11
|
+
type BelongsToManyRelation,
|
|
8
12
|
type RelationDef
|
|
9
13
|
} from '../schema/relation.js';
|
|
10
14
|
import { TableDef } from '../schema/table.js';
|
|
@@ -20,7 +24,14 @@ import {
|
|
|
20
24
|
} from '../orm/entity-metadata.js';
|
|
21
25
|
|
|
22
26
|
import { tableRef, type TableRef } from '../schema/table.js';
|
|
23
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
SelectableKeys,
|
|
29
|
+
ColumnDef,
|
|
30
|
+
HasManyCollection,
|
|
31
|
+
HasOneReference,
|
|
32
|
+
BelongsToReference,
|
|
33
|
+
ManyToManyCollection
|
|
34
|
+
} from '../schema/types.js';
|
|
24
35
|
|
|
25
36
|
const unwrapTarget = (target: EntityOrTableTargetResolver): EntityOrTableTarget => {
|
|
26
37
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
@@ -144,14 +155,39 @@ export const getTableDefFromEntity = <TTable extends TableDef = TableDef>(ctor:
|
|
|
144
155
|
* @param ctor - The entity constructor.
|
|
145
156
|
* @returns A select query builder for the entity.
|
|
146
157
|
*/
|
|
158
|
+
type NonFunctionKeys<T> = {
|
|
159
|
+
[K in keyof T]-?: T[K] extends (...args: unknown[]) => unknown ? never : K
|
|
160
|
+
}[keyof T];
|
|
161
|
+
|
|
162
|
+
type RelationKeys<TEntity extends object> =
|
|
163
|
+
Exclude<NonFunctionKeys<TEntity>, SelectableKeys<TEntity>> & string;
|
|
164
|
+
|
|
165
|
+
type EntityTable<TEntity extends object> =
|
|
166
|
+
Omit<TableDef<{ [K in SelectableKeys<TEntity>]: ColumnDef }>, 'relations'> & {
|
|
167
|
+
relations: {
|
|
168
|
+
[K in RelationKeys<TEntity>]:
|
|
169
|
+
NonNullable<TEntity[K]> extends HasManyCollection<infer TChild>
|
|
170
|
+
? HasManyRelation<EntityTable<NonNullable<TChild> & object>>
|
|
171
|
+
: NonNullable<TEntity[K]> extends ManyToManyCollection<infer TTarget>
|
|
172
|
+
? BelongsToManyRelation<EntityTable<NonNullable<TTarget> & object>>
|
|
173
|
+
: NonNullable<TEntity[K]> extends HasOneReference<infer TChild>
|
|
174
|
+
? HasOneRelation<EntityTable<NonNullable<TChild> & object>>
|
|
175
|
+
: NonNullable<TEntity[K]> extends BelongsToReference<infer TParent>
|
|
176
|
+
? BelongsToRelation<EntityTable<NonNullable<TParent> & object>>
|
|
177
|
+
: NonNullable<TEntity[K]> extends object
|
|
178
|
+
? BelongsToRelation<EntityTable<NonNullable<TEntity[K]> & object>>
|
|
179
|
+
: never;
|
|
180
|
+
};
|
|
181
|
+
};
|
|
182
|
+
|
|
147
183
|
export const selectFromEntity = <TEntity extends object>(
|
|
148
184
|
ctor: EntityConstructor<TEntity>
|
|
149
|
-
): SelectQueryBuilder<unknown,
|
|
185
|
+
): SelectQueryBuilder<unknown, EntityTable<TEntity>> => {
|
|
150
186
|
const table = getTableDefFromEntity(ctor);
|
|
151
187
|
if (!table) {
|
|
152
188
|
throw new Error(`Entity '${ctor.name}' is not registered with decorators or has not been bootstrapped`);
|
|
153
189
|
}
|
|
154
|
-
return new SelectQueryBuilder(table as unknown as
|
|
190
|
+
return new SelectQueryBuilder(table as unknown as EntityTable<TEntity>);
|
|
155
191
|
};
|
|
156
192
|
|
|
157
193
|
/**
|
package/src/orm/entity-meta.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { TableDef } from '../schema/table.js';
|
|
1
|
+
import { TableDef } from '../schema/table.js';
|
|
2
|
+
import { RelationIncludeOptions } from '../query-builder/relation-types.js';
|
|
2
3
|
import { EntityContext } from './entity-context.js';
|
|
3
4
|
import { RelationMap } from '../schema/types.js';
|
|
4
5
|
|
|
@@ -18,8 +19,10 @@ export interface EntityMeta<TTable extends TableDef> {
|
|
|
18
19
|
ctx: EntityContext;
|
|
19
20
|
/** Table definition */
|
|
20
21
|
table: TTable;
|
|
21
|
-
/** Relations that should be loaded lazily */
|
|
22
|
-
lazyRelations: (keyof RelationMap<TTable>)[];
|
|
22
|
+
/** Relations that should be loaded lazily */
|
|
23
|
+
lazyRelations: (keyof RelationMap<TTable>)[];
|
|
24
|
+
/** Include options for lazy relations */
|
|
25
|
+
lazyRelationOptions: Map<string, RelationIncludeOptions>;
|
|
23
26
|
/** Cache for relation promises */
|
|
24
27
|
relationCache: Map<string, Promise<unknown>>;
|
|
25
28
|
/** Hydration data for relations */
|
package/src/orm/entity.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { DefaultManyToManyCollection } from './relations/many-to-many.js';
|
|
|
9
9
|
import { HasManyRelation, HasOneRelation, BelongsToRelation, BelongsToManyRelation, RelationKinds } from '../schema/relation.js';
|
|
10
10
|
import { loadHasManyRelation, loadHasOneRelation, loadBelongsToRelation, loadBelongsToManyRelation } from './lazy-batch.js';
|
|
11
11
|
import { findPrimaryKey } from '../query-builder/hydration-planner.js';
|
|
12
|
+
import { RelationIncludeOptions } from '../query-builder/relation-types.js';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Type representing an array of database rows.
|
|
@@ -23,7 +24,7 @@ type Rows = Record<string, unknown>[];
|
|
|
23
24
|
* @param factory - The factory function to create the cache
|
|
24
25
|
* @returns Promise with the cached relation data
|
|
25
26
|
*/
|
|
26
|
-
const relationLoaderCache = <T extends Map<string, unknown>>(
|
|
27
|
+
export const relationLoaderCache = <T extends Map<string, unknown>>(
|
|
27
28
|
meta: EntityMeta<TableDef>,
|
|
28
29
|
relationName: string,
|
|
29
30
|
factory: () => Promise<T>
|
|
@@ -69,13 +70,15 @@ export const createEntityProxy = <
|
|
|
69
70
|
ctx: EntityContext,
|
|
70
71
|
table: TTable,
|
|
71
72
|
row: Record<string, unknown>,
|
|
72
|
-
lazyRelations: TLazy[] = [] as TLazy[]
|
|
73
|
+
lazyRelations: TLazy[] = [] as TLazy[],
|
|
74
|
+
lazyRelationOptions: Map<string, RelationIncludeOptions> = new Map()
|
|
73
75
|
): EntityInstance<TTable> => {
|
|
74
76
|
const target: Record<string, unknown> = { ...row };
|
|
75
77
|
const meta: EntityMeta<TTable> = {
|
|
76
78
|
ctx,
|
|
77
79
|
table,
|
|
78
80
|
lazyRelations: [...lazyRelations],
|
|
81
|
+
lazyRelationOptions: new Map(lazyRelationOptions),
|
|
79
82
|
relationCache: new Map(),
|
|
80
83
|
relationHydration: new Map(),
|
|
81
84
|
relationWrappers: new Map()
|
|
@@ -141,7 +144,8 @@ export const createEntityFromRow = <
|
|
|
141
144
|
ctx: EntityContext,
|
|
142
145
|
table: TTable,
|
|
143
146
|
row: Record<string, unknown>,
|
|
144
|
-
lazyRelations: (keyof RelationMap<TTable>)[] = []
|
|
147
|
+
lazyRelations: (keyof RelationMap<TTable>)[] = [],
|
|
148
|
+
lazyRelationOptions: Map<string, RelationIncludeOptions> = new Map()
|
|
145
149
|
): TResult => {
|
|
146
150
|
const pkName = findPrimaryKey(table);
|
|
147
151
|
const pkValue = row[pkName];
|
|
@@ -150,7 +154,7 @@ export const createEntityFromRow = <
|
|
|
150
154
|
if (tracked) return tracked as TResult;
|
|
151
155
|
}
|
|
152
156
|
|
|
153
|
-
const entity = createEntityProxy(ctx, table, row, lazyRelations);
|
|
157
|
+
const entity = createEntityProxy(ctx, table, row, lazyRelations, lazyRelationOptions);
|
|
154
158
|
if (pkValue !== undefined && pkValue !== null) {
|
|
155
159
|
ctx.trackManaged(table, pkValue, entity);
|
|
156
160
|
} else {
|
|
@@ -223,6 +227,68 @@ const populateHydrationCache = <TTable extends TableDef>(
|
|
|
223
227
|
}
|
|
224
228
|
};
|
|
225
229
|
|
|
230
|
+
const proxifyRelationWrapper = <T extends object>(wrapper: T): T => {
|
|
231
|
+
return new Proxy(wrapper, {
|
|
232
|
+
get(target, prop, receiver) {
|
|
233
|
+
if (typeof prop === 'symbol') {
|
|
234
|
+
return Reflect.get(target, prop, receiver);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (prop in target) {
|
|
238
|
+
return Reflect.get(target, prop, receiver);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const getItems = (target as { getItems?: () => unknown }).getItems;
|
|
242
|
+
if (typeof getItems === 'function') {
|
|
243
|
+
const items = getItems.call(target);
|
|
244
|
+
if (items && prop in (items as object)) {
|
|
245
|
+
const propName = prop as string;
|
|
246
|
+
const value = (items as Record<string, unknown>)[propName];
|
|
247
|
+
return typeof value === 'function' ? value.bind(items) : value;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const getRef = (target as { get?: () => unknown }).get;
|
|
252
|
+
if (typeof getRef === 'function') {
|
|
253
|
+
const current = getRef.call(target);
|
|
254
|
+
if (current && prop in (current as object)) {
|
|
255
|
+
const propName = prop as string;
|
|
256
|
+
const value = (current as Record<string, unknown>)[propName];
|
|
257
|
+
return typeof value === 'function' ? value.bind(current) : value;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return undefined;
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
set(target, prop, value, receiver) {
|
|
265
|
+
if (typeof prop === 'symbol') {
|
|
266
|
+
return Reflect.set(target, prop, value, receiver);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (prop in target) {
|
|
270
|
+
return Reflect.set(target, prop, value, receiver);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const getRef = (target as { get?: () => unknown }).get;
|
|
274
|
+
if (typeof getRef === 'function') {
|
|
275
|
+
const current = getRef.call(target);
|
|
276
|
+
if (current && typeof current === 'object') {
|
|
277
|
+
return Reflect.set(current as object, prop, value);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const getItems = (target as { getItems?: () => unknown }).getItems;
|
|
282
|
+
if (typeof getItems === 'function') {
|
|
283
|
+
const items = getItems.call(target);
|
|
284
|
+
return Reflect.set(items as object, prop, value);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return Reflect.set(target, prop, value, receiver);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
};
|
|
291
|
+
|
|
226
292
|
/**
|
|
227
293
|
* Gets a relation wrapper for an entity.
|
|
228
294
|
* @param meta - The entity metadata
|
|
@@ -234,7 +300,7 @@ const getRelationWrapper = (
|
|
|
234
300
|
meta: EntityMeta<TableDef>,
|
|
235
301
|
relationName: string,
|
|
236
302
|
owner: unknown
|
|
237
|
-
): HasManyCollection<unknown> | HasOneReference<
|
|
303
|
+
): HasManyCollection<unknown> | HasOneReference<object> | BelongsToReference<object> | ManyToManyCollection<unknown> | undefined => {
|
|
238
304
|
if (meta.relationWrappers.has(relationName)) {
|
|
239
305
|
return meta.relationWrappers.get(relationName) as HasManyCollection<unknown>;
|
|
240
306
|
}
|
|
@@ -243,11 +309,11 @@ const getRelationWrapper = (
|
|
|
243
309
|
if (!relation) return undefined;
|
|
244
310
|
|
|
245
311
|
const wrapper = instantiateWrapper(meta, relationName, relation, owner);
|
|
246
|
-
if (wrapper)
|
|
247
|
-
meta.relationWrappers.set(relationName, wrapper);
|
|
248
|
-
}
|
|
312
|
+
if (!wrapper) return undefined;
|
|
249
313
|
|
|
250
|
-
|
|
314
|
+
const proxied = proxifyRelationWrapper(wrapper as object);
|
|
315
|
+
meta.relationWrappers.set(relationName, proxied);
|
|
316
|
+
return proxied as HasManyCollection<unknown>;
|
|
251
317
|
};
|
|
252
318
|
|
|
253
319
|
/**
|
|
@@ -263,13 +329,14 @@ const instantiateWrapper = (
|
|
|
263
329
|
relationName: string,
|
|
264
330
|
relation: HasManyRelation | HasOneRelation | BelongsToRelation | BelongsToManyRelation,
|
|
265
331
|
owner: unknown
|
|
266
|
-
): HasManyCollection<unknown> | HasOneReference<
|
|
332
|
+
): HasManyCollection<unknown> | HasOneReference<object> | BelongsToReference<object> | ManyToManyCollection<unknown> | undefined => {
|
|
333
|
+
const lazyOptions = meta.lazyRelationOptions.get(relationName);
|
|
267
334
|
switch (relation.type) {
|
|
268
335
|
case RelationKinds.HasOne: {
|
|
269
336
|
const hasOne = relation as HasOneRelation;
|
|
270
337
|
const localKey = hasOne.localKey || findPrimaryKey(meta.table);
|
|
271
338
|
const loader = () => relationLoaderCache(meta, relationName, () =>
|
|
272
|
-
loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne)
|
|
339
|
+
loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne, lazyOptions)
|
|
273
340
|
);
|
|
274
341
|
return new DefaultHasOneReference(
|
|
275
342
|
meta.ctx,
|
|
@@ -287,7 +354,7 @@ const instantiateWrapper = (
|
|
|
287
354
|
const hasMany = relation as HasManyRelation;
|
|
288
355
|
const localKey = hasMany.localKey || findPrimaryKey(meta.table);
|
|
289
356
|
const loader = () => relationLoaderCache(meta, relationName, () =>
|
|
290
|
-
loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany)
|
|
357
|
+
loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany, lazyOptions)
|
|
291
358
|
);
|
|
292
359
|
return new DefaultHasManyCollection(
|
|
293
360
|
meta.ctx,
|
|
@@ -305,7 +372,7 @@ const instantiateWrapper = (
|
|
|
305
372
|
const belongsTo = relation as BelongsToRelation;
|
|
306
373
|
const targetKey = belongsTo.localKey || findPrimaryKey(belongsTo.target);
|
|
307
374
|
const loader = () => relationLoaderCache(meta, relationName, () =>
|
|
308
|
-
loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo)
|
|
375
|
+
loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo, lazyOptions)
|
|
309
376
|
);
|
|
310
377
|
return new DefaultBelongsToReference(
|
|
311
378
|
meta.ctx,
|
|
@@ -323,7 +390,7 @@ const instantiateWrapper = (
|
|
|
323
390
|
const many = relation as BelongsToManyRelation;
|
|
324
391
|
const localKey = many.localKey || findPrimaryKey(meta.table);
|
|
325
392
|
const loader = () => relationLoaderCache(meta, relationName, () =>
|
|
326
|
-
loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many)
|
|
393
|
+
loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many, lazyOptions)
|
|
327
394
|
);
|
|
328
395
|
return new DefaultManyToManyCollection(
|
|
329
396
|
meta.ctx,
|
package/src/orm/execute.ts
CHANGED
|
@@ -1,16 +1,29 @@
|
|
|
1
|
-
import { TableDef } from '../schema/table.js';
|
|
2
|
-
import { EntityInstance } from '../schema/types.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { TableDef } from '../schema/table.js';
|
|
2
|
+
import { EntityInstance, RelationMap } from '../schema/types.js';
|
|
3
|
+
import { RelationKinds } from '../schema/relation.js';
|
|
4
|
+
import { hydrateRows } from './hydration.js';
|
|
5
|
+
import { OrmSession } from './orm-session.ts';
|
|
6
|
+
import { SelectQueryBuilder } from '../query-builder/select.js';
|
|
7
|
+
import {
|
|
8
|
+
createEntityProxy,
|
|
9
|
+
createEntityFromRow,
|
|
10
|
+
relationLoaderCache
|
|
11
|
+
} from './entity.js';
|
|
7
12
|
import { EntityContext } from './entity-context.js';
|
|
8
13
|
import { ExecutionContext } from './execution-context.js';
|
|
9
14
|
import { HydrationContext } from './hydration-context.js';
|
|
15
|
+
import { RelationIncludeOptions } from '../query-builder/relation-types.js';
|
|
16
|
+
import { getEntityMeta } from './entity-meta.js';
|
|
17
|
+
import {
|
|
18
|
+
loadHasManyRelation,
|
|
19
|
+
loadHasOneRelation,
|
|
20
|
+
loadBelongsToRelation,
|
|
21
|
+
loadBelongsToManyRelation
|
|
22
|
+
} from './lazy-batch.js';
|
|
10
23
|
|
|
11
24
|
type Row = Record<string, unknown>;
|
|
12
25
|
|
|
13
|
-
const flattenResults = (results: { columns: string[]; values: unknown[][] }[]): Row[] => {
|
|
26
|
+
const flattenResults = (results: { columns: string[]; values: unknown[][] }[]): Row[] => {
|
|
14
27
|
const rows: Row[] = [];
|
|
15
28
|
for (const result of results) {
|
|
16
29
|
const { columns, values } = result;
|
|
@@ -23,8 +36,8 @@ const flattenResults = (results: { columns: string[]; values: unknown[][] }[]):
|
|
|
23
36
|
}
|
|
24
37
|
}
|
|
25
38
|
return rows;
|
|
26
|
-
};
|
|
27
|
-
|
|
39
|
+
};
|
|
40
|
+
|
|
28
41
|
const executeWithContexts = async <TTable extends TableDef>(
|
|
29
42
|
execCtx: ExecutionContext,
|
|
30
43
|
entityCtx: EntityContext,
|
|
@@ -34,14 +47,20 @@ const executeWithContexts = async <TTable extends TableDef>(
|
|
|
34
47
|
const compiled = execCtx.dialect.compileSelect(ast);
|
|
35
48
|
const executed = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
|
|
36
49
|
const rows = flattenResults(executed);
|
|
50
|
+
const lazyRelations = qb.getLazyRelations();
|
|
51
|
+
const lazyRelationOptions = qb.getLazyRelationOptions();
|
|
37
52
|
|
|
38
53
|
if (ast.setOps && ast.setOps.length > 0) {
|
|
39
|
-
|
|
54
|
+
const proxies = rows.map(row => createEntityProxy(entityCtx, qb.getTable(), row, lazyRelations, lazyRelationOptions));
|
|
55
|
+
await loadLazyRelationsForTable(entityCtx, qb.getTable(), lazyRelations, lazyRelationOptions);
|
|
56
|
+
return proxies;
|
|
40
57
|
}
|
|
41
|
-
|
|
42
|
-
const hydrated = hydrateRows(rows, qb.getHydrationPlan());
|
|
43
|
-
|
|
44
|
-
|
|
58
|
+
|
|
59
|
+
const hydrated = hydrateRows(rows, qb.getHydrationPlan());
|
|
60
|
+
const entities = hydrated.map(row => createEntityFromRow(entityCtx, qb.getTable(), row, lazyRelations, lazyRelationOptions));
|
|
61
|
+
await loadLazyRelationsForTable(entityCtx, qb.getTable(), lazyRelations, lazyRelationOptions);
|
|
62
|
+
return entities;
|
|
63
|
+
};
|
|
45
64
|
|
|
46
65
|
/**
|
|
47
66
|
* Executes a hydrated query using the ORM session.
|
|
@@ -50,12 +69,12 @@ const executeWithContexts = async <TTable extends TableDef>(
|
|
|
50
69
|
* @param qb - The select query builder
|
|
51
70
|
* @returns Promise resolving to array of entity instances
|
|
52
71
|
*/
|
|
53
|
-
export async function executeHydrated<TTable extends TableDef>(
|
|
54
|
-
session: OrmSession,
|
|
55
|
-
qb: SelectQueryBuilder<unknown, TTable>
|
|
56
|
-
): Promise<EntityInstance<TTable>[]> {
|
|
57
|
-
return executeWithContexts(session.getExecutionContext(), session, qb);
|
|
58
|
-
}
|
|
72
|
+
export async function executeHydrated<TTable extends TableDef>(
|
|
73
|
+
session: OrmSession,
|
|
74
|
+
qb: SelectQueryBuilder<unknown, TTable>
|
|
75
|
+
): Promise<EntityInstance<TTable>[]> {
|
|
76
|
+
return executeWithContexts(session.getExecutionContext(), session, qb);
|
|
77
|
+
}
|
|
59
78
|
|
|
60
79
|
/**
|
|
61
80
|
* Executes a hydrated query using execution and hydration contexts.
|
|
@@ -76,3 +95,51 @@ export async function executeHydratedWithContexts<TTable extends TableDef>(
|
|
|
76
95
|
}
|
|
77
96
|
return executeWithContexts(execCtx, entityCtx, qb);
|
|
78
97
|
}
|
|
98
|
+
|
|
99
|
+
const loadLazyRelationsForTable = async <TTable extends TableDef>(
|
|
100
|
+
ctx: EntityContext,
|
|
101
|
+
table: TTable,
|
|
102
|
+
lazyRelations: (keyof RelationMap<TTable>)[],
|
|
103
|
+
lazyRelationOptions: Map<string, RelationIncludeOptions>
|
|
104
|
+
): Promise<void> => {
|
|
105
|
+
if (!lazyRelations.length) return;
|
|
106
|
+
|
|
107
|
+
const tracked = ctx.getEntitiesForTable(table);
|
|
108
|
+
if (!tracked.length) return;
|
|
109
|
+
|
|
110
|
+
const meta = getEntityMeta(tracked[0].entity);
|
|
111
|
+
if (!meta) return;
|
|
112
|
+
|
|
113
|
+
for (const relationName of lazyRelations) {
|
|
114
|
+
const relation = table.relations[relationName as string];
|
|
115
|
+
if (!relation) continue;
|
|
116
|
+
const key = relationName as string;
|
|
117
|
+
const options = lazyRelationOptions.get(key);
|
|
118
|
+
if (!options) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
switch (relation.type) {
|
|
123
|
+
case RelationKinds.HasOne:
|
|
124
|
+
await relationLoaderCache(meta, key, () =>
|
|
125
|
+
loadHasOneRelation(ctx, table, key, relation, options)
|
|
126
|
+
);
|
|
127
|
+
break;
|
|
128
|
+
case RelationKinds.HasMany:
|
|
129
|
+
await relationLoaderCache(meta, key, () =>
|
|
130
|
+
loadHasManyRelation(ctx, table, key, relation, options)
|
|
131
|
+
);
|
|
132
|
+
break;
|
|
133
|
+
case RelationKinds.BelongsTo:
|
|
134
|
+
await relationLoaderCache(meta, key, () =>
|
|
135
|
+
loadBelongsToRelation(ctx, table, key, relation, options)
|
|
136
|
+
);
|
|
137
|
+
break;
|
|
138
|
+
case RelationKinds.BelongsToMany:
|
|
139
|
+
await relationLoaderCache(meta, key, () =>
|
|
140
|
+
loadBelongsToManyRelation(ctx, table, key, relation, options)
|
|
141
|
+
);
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
};
|