metal-orm 1.0.56 → 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 +41 -33
- package/dist/index.cjs +1461 -195
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +541 -114
- package/dist/index.d.ts +541 -114
- package/dist/index.js +1424 -195
- package/dist/index.js.map +1 -1
- package/package.json +69 -69
- package/src/codegen/naming-strategy.ts +3 -1
- package/src/codegen/typescript.ts +20 -10
- package/src/core/ast/aggregate-functions.ts +14 -0
- package/src/core/ast/builders.ts +38 -20
- package/src/core/ast/expression-builders.ts +70 -2
- package/src/core/ast/expression-nodes.ts +305 -274
- package/src/core/ast/expression-visitor.ts +11 -1
- package/src/core/ast/expression.ts +4 -0
- package/src/core/ast/query.ts +3 -0
- package/src/core/ddl/introspect/catalogs/mysql.ts +5 -0
- package/src/core/ddl/introspect/catalogs/sqlite.ts +3 -0
- package/src/core/ddl/introspect/functions/mssql.ts +13 -0
- package/src/core/ddl/introspect/mssql.ts +4 -0
- package/src/core/ddl/introspect/mysql.ts +4 -0
- package/src/core/ddl/introspect/sqlite.ts +4 -0
- package/src/core/dialect/abstract.ts +552 -531
- package/src/core/dialect/base/function-table-formatter.ts +9 -30
- package/src/core/dialect/base/sql-dialect.ts +24 -0
- package/src/core/dialect/mssql/functions.ts +40 -2
- package/src/core/dialect/mysql/functions.ts +16 -2
- package/src/core/dialect/postgres/functions.ts +66 -2
- package/src/core/dialect/postgres/index.ts +17 -4
- package/src/core/dialect/postgres/table-functions.ts +27 -0
- package/src/core/dialect/sqlite/functions.ts +34 -0
- package/src/core/dialect/sqlite/index.ts +17 -1
- package/src/core/driver/database-driver.ts +9 -1
- package/src/core/driver/mssql-driver.ts +3 -0
- package/src/core/driver/mysql-driver.ts +3 -0
- package/src/core/driver/postgres-driver.ts +3 -0
- package/src/core/driver/sqlite-driver.ts +3 -0
- package/src/core/execution/executors/mssql-executor.ts +5 -0
- package/src/core/execution/executors/mysql-executor.ts +5 -0
- package/src/core/execution/executors/postgres-executor.ts +5 -0
- package/src/core/execution/executors/sqlite-executor.ts +5 -0
- package/src/core/functions/array.ts +26 -0
- package/src/core/functions/control-flow.ts +69 -0
- package/src/core/functions/datetime.ts +50 -0
- package/src/core/functions/definitions/aggregate.ts +16 -0
- package/src/core/functions/definitions/control-flow.ts +24 -0
- package/src/core/functions/definitions/datetime.ts +36 -0
- package/src/core/functions/definitions/helpers.ts +29 -0
- package/src/core/functions/definitions/json.ts +49 -0
- package/src/core/functions/definitions/numeric.ts +55 -0
- package/src/core/functions/definitions/string.ts +43 -0
- package/src/core/functions/function-registry.ts +48 -0
- package/src/core/functions/group-concat-helpers.ts +57 -0
- package/src/core/functions/json.ts +38 -0
- package/src/core/functions/numeric.ts +14 -0
- package/src/core/functions/standard-strategy.ts +86 -115
- package/src/core/functions/standard-table-strategy.ts +13 -0
- package/src/core/functions/table-types.ts +15 -0
- package/src/core/functions/text.ts +57 -0
- package/src/core/sql/sql.ts +59 -38
- package/src/decorators/bootstrap.ts +41 -4
- package/src/index.ts +18 -11
- 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/hydration-context.ts +10 -0
- package/src/orm/identity-map.ts +19 -0
- package/src/orm/interceptor-pipeline.ts +4 -0
- package/src/orm/lazy-batch.ts +237 -54
- package/src/orm/relations/belongs-to.ts +19 -2
- package/src/orm/relations/has-many.ts +23 -9
- package/src/orm/relations/has-one.ts +19 -2
- package/src/orm/relations/many-to-many.ts +59 -4
- 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 +124 -106
- package/src/schema/table-guards.ts +6 -0
- package/src/schema/types.ts +109 -85
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
|
+
};
|
|
@@ -6,11 +6,21 @@ import type { EntityContext } from './entity-context.js';
|
|
|
6
6
|
import type { AnyDomainEvent, DomainEvent } from './runtime-types.js';
|
|
7
7
|
import type { OrmSession } from './orm-session.js';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Context used during the hydration of entities from database results.
|
|
11
|
+
* It carries necessary services and processors to handle identity management,
|
|
12
|
+
* unit of work registration, and relation changes.
|
|
13
|
+
*/
|
|
9
14
|
export interface HydrationContext<E extends DomainEvent = AnyDomainEvent> {
|
|
15
|
+
/** The identity map used to track and reuse entity instances. */
|
|
10
16
|
identityMap: IdentityMap;
|
|
17
|
+
/** The unit of work used to track changes in hydrated entities. */
|
|
11
18
|
unitOfWork: UnitOfWork;
|
|
19
|
+
/** The bus used to dispatch domain events during or after hydration. */
|
|
12
20
|
domainEvents: DomainEventBus<E, OrmSession<E>>;
|
|
21
|
+
/** Processor for handling changes in entity relations during hydration. */
|
|
13
22
|
relationChanges: RelationChangeProcessor;
|
|
23
|
+
/** Context providing access to entity-specific metadata and services. */
|
|
14
24
|
entityContext: EntityContext;
|
|
15
25
|
// maybe mapping registry, converters, etc.
|
|
16
26
|
}
|
package/src/orm/identity-map.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { TableDef } from '../schema/table.js';
|
|
2
2
|
import type { TrackedEntity } from './runtime-types.js';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Simple identity map for tracking entities within a session.
|
|
6
|
+
* Ensures that the same database record is represented by a single entity instance.
|
|
7
|
+
*/
|
|
4
8
|
export class IdentityMap {
|
|
5
9
|
private readonly buckets = new Map<string, Map<string, TrackedEntity>>();
|
|
6
10
|
|
|
@@ -8,11 +12,21 @@ export class IdentityMap {
|
|
|
8
12
|
return this.buckets;
|
|
9
13
|
}
|
|
10
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Retrieves an entity from the identity map if it exists.
|
|
17
|
+
* @param table The table definition of the entity.
|
|
18
|
+
* @param pk The primary key value.
|
|
19
|
+
* @returns The entity instance if found, undefined otherwise.
|
|
20
|
+
*/
|
|
11
21
|
getEntity(table: TableDef, pk: string | number): unknown | undefined {
|
|
12
22
|
const bucket = this.buckets.get(table.name);
|
|
13
23
|
return bucket?.get(this.toIdentityKey(pk))?.entity;
|
|
14
24
|
}
|
|
15
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Registers a tracked entity in the identity map.
|
|
28
|
+
* @param tracked The tracked entity metadata and instance.
|
|
29
|
+
*/
|
|
16
30
|
register(tracked: TrackedEntity): void {
|
|
17
31
|
if (tracked.pk == null) return;
|
|
18
32
|
const bucket = this.buckets.get(tracked.table.name) ?? new Map<string, TrackedEntity>();
|
|
@@ -26,6 +40,11 @@ export class IdentityMap {
|
|
|
26
40
|
bucket?.delete(this.toIdentityKey(tracked.pk));
|
|
27
41
|
}
|
|
28
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Returns all tracked entities for a specific table.
|
|
45
|
+
* @param table The table definition.
|
|
46
|
+
* @returns Array of tracked entities.
|
|
47
|
+
*/
|
|
29
48
|
getEntitiesForTable(table: TableDef): TrackedEntity[] {
|
|
30
49
|
const bucket = this.buckets.get(table.name);
|
|
31
50
|
return bucket ? Array.from(bucket.values()) : [];
|
|
@@ -8,6 +8,10 @@ export interface QueryContext {
|
|
|
8
8
|
|
|
9
9
|
export type QueryInterceptor = (ctx: QueryContext, next: () => Promise<QueryResult[]>) => Promise<QueryResult[]>;
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Pipeline for query interceptors.
|
|
13
|
+
* Interceptors can wrap query execution to add logging, tracing, caching, etc.
|
|
14
|
+
*/
|
|
11
15
|
export class InterceptorPipeline {
|
|
12
16
|
private interceptors: QueryInterceptor[] = [];
|
|
13
17
|
|