metal-orm 1.0.64 → 1.0.66
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/dist/index.cjs +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +119 -117
- package/dist/index.d.ts +119 -117
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/ast/aggregate-functions.ts +1 -1
- package/src/core/ast/window-functions.ts +6 -6
- package/src/core/functions/array.ts +4 -4
- package/src/core/functions/control-flow.ts +6 -6
- package/src/core/functions/json.ts +6 -6
- package/src/index.ts +2 -1
- package/src/orm/entity-context.ts +9 -7
- package/src/orm/entity-relations.ts +207 -207
- package/src/orm/entity.ts +124 -124
- package/src/orm/execute.ts +166 -166
- package/src/orm/identity-map.ts +3 -2
- package/src/orm/lazy-batch/shared.ts +1 -1
- package/src/orm/orm-session.ts +54 -54
- package/src/orm/relation-change-processor.ts +3 -3
- package/src/orm/relations/has-many.ts +1 -1
- package/src/orm/runtime-types.ts +5 -5
- package/src/orm/save-graph.ts +164 -166
- package/src/orm/unit-of-work.ts +17 -14
- package/src/query-builder/insert-query-state.ts +156 -155
- package/src/query-builder/insert.ts +5 -2
- package/src/query-builder/select.ts +112 -111
- package/src/schema/column-types.ts +14 -14
- package/src/schema/table.ts +39 -31
- package/src/schema/types.ts +54 -54
package/src/orm/entity.ts
CHANGED
|
@@ -1,124 +1,124 @@
|
|
|
1
|
-
import { TableDef } from '../schema/table.js';
|
|
2
|
-
import { EntityInstance } from '../schema/types.js';
|
|
3
|
-
import { EntityContext } from './entity-context.js';
|
|
4
|
-
import { ENTITY_META, EntityMeta, RelationKey } from './entity-meta.js';
|
|
5
|
-
import { findPrimaryKey } from '../query-builder/hydration-planner.js';
|
|
6
|
-
import { RelationIncludeOptions } from '../query-builder/relation-types.js';
|
|
7
|
-
import { populateHydrationCache } from './entity-hydration.js';
|
|
8
|
-
import { getRelationWrapper, RelationEntityFactory } from './entity-relations.js';
|
|
9
|
-
|
|
10
|
-
export { relationLoaderCache } from './entity-relation-cache.js';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Creates an entity proxy with lazy loading capabilities.
|
|
14
|
-
* @template TTable - The table type
|
|
15
|
-
* @template TLazy - The lazy relation keys
|
|
16
|
-
* @param ctx - The entity context
|
|
17
|
-
* @param table - The table definition
|
|
18
|
-
* @param row - The database row
|
|
19
|
-
* @param lazyRelations - Optional lazy relations
|
|
20
|
-
* @returns The entity instance
|
|
21
|
-
*/
|
|
22
|
-
export const createEntityProxy = <
|
|
23
|
-
TTable extends TableDef,
|
|
24
|
-
TLazy extends RelationKey<TTable> = RelationKey<TTable>
|
|
25
|
-
>(
|
|
26
|
-
ctx: EntityContext,
|
|
27
|
-
table: TTable,
|
|
28
|
-
row: Record<string, unknown>,
|
|
29
|
-
lazyRelations: TLazy[] = [] as TLazy[],
|
|
30
|
-
lazyRelationOptions: Map<string, RelationIncludeOptions> = new Map()
|
|
31
|
-
): EntityInstance<TTable> => {
|
|
32
|
-
const target: Record<string, unknown> = { ...row };
|
|
33
|
-
const meta: EntityMeta<TTable> = {
|
|
34
|
-
ctx,
|
|
35
|
-
table,
|
|
36
|
-
lazyRelations: [...lazyRelations],
|
|
37
|
-
lazyRelationOptions: new Map(lazyRelationOptions),
|
|
38
|
-
relationCache: new Map(),
|
|
39
|
-
relationHydration: new Map(),
|
|
40
|
-
relationWrappers: new Map()
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const createRelationEntity: RelationEntityFactory = (relationTable, relationRow) =>
|
|
44
|
-
createEntityFromRow(meta.ctx, relationTable, relationRow);
|
|
45
|
-
|
|
46
|
-
Object.defineProperty(target, ENTITY_META, {
|
|
47
|
-
value: meta,
|
|
48
|
-
enumerable: false,
|
|
49
|
-
writable: false
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
const handler: ProxyHandler<object> = {
|
|
53
|
-
get(targetObj, prop, receiver) {
|
|
54
|
-
if (prop === ENTITY_META) {
|
|
55
|
-
return meta;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (prop === '$load') {
|
|
59
|
-
return async (relationName: RelationKey<TTable>) => {
|
|
60
|
-
const wrapper = getRelationWrapper(meta, relationName, receiver, createRelationEntity);
|
|
61
|
-
if (wrapper && typeof wrapper.load === 'function') {
|
|
62
|
-
return wrapper.load();
|
|
63
|
-
}
|
|
64
|
-
return undefined;
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (typeof prop === 'string' && table.relations[prop]) {
|
|
69
|
-
return getRelationWrapper(meta, prop as RelationKey<TTable>, receiver, createRelationEntity);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return Reflect.get(targetObj, prop, receiver);
|
|
73
|
-
},
|
|
74
|
-
|
|
75
|
-
set(targetObj, prop, value, receiver) {
|
|
76
|
-
const result = Reflect.set(targetObj, prop, value, receiver);
|
|
77
|
-
if (typeof prop === 'string' && table.columns[prop]) {
|
|
78
|
-
ctx.markDirty(receiver);
|
|
79
|
-
}
|
|
80
|
-
return result;
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
const proxy = new Proxy(target, handler) as EntityInstance<TTable>;
|
|
85
|
-
populateHydrationCache(proxy, row, meta);
|
|
86
|
-
return proxy;
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Creates an entity instance from a database row.
|
|
91
|
-
* @template TTable - The table type
|
|
92
|
-
* @template TResult - The result type
|
|
93
|
-
* @param ctx - The entity context
|
|
94
|
-
* @param table - The table definition
|
|
95
|
-
* @param row - The database row
|
|
96
|
-
* @param lazyRelations - Optional lazy relations
|
|
97
|
-
* @returns The entity instance
|
|
98
|
-
*/
|
|
99
|
-
export const createEntityFromRow = <
|
|
100
|
-
TTable extends TableDef,
|
|
101
|
-
TResult extends EntityInstance<TTable> = EntityInstance<TTable>
|
|
102
|
-
>(
|
|
103
|
-
ctx: EntityContext,
|
|
104
|
-
table: TTable,
|
|
105
|
-
row: Record<string, unknown>,
|
|
106
|
-
lazyRelations: RelationKey<TTable>[] = [],
|
|
107
|
-
lazyRelationOptions: Map<string, RelationIncludeOptions> = new Map()
|
|
108
|
-
): TResult => {
|
|
109
|
-
const pkName = findPrimaryKey(table);
|
|
110
|
-
const pkValue = row[pkName];
|
|
111
|
-
if (pkValue !== undefined && pkValue !== null) {
|
|
112
|
-
const tracked = ctx.getEntity(table, pkValue);
|
|
113
|
-
if (tracked) return tracked as TResult;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const entity = createEntityProxy(ctx, table, row, lazyRelations, lazyRelationOptions);
|
|
117
|
-
if (pkValue !== undefined && pkValue !== null) {
|
|
118
|
-
ctx.trackManaged(table, pkValue, entity);
|
|
119
|
-
} else {
|
|
120
|
-
ctx.trackNew(table, entity);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return entity as TResult;
|
|
124
|
-
};
|
|
1
|
+
import { TableDef } from '../schema/table.js';
|
|
2
|
+
import { EntityInstance } from '../schema/types.js';
|
|
3
|
+
import type { EntityContext, PrimaryKey } from './entity-context.js';
|
|
4
|
+
import { ENTITY_META, EntityMeta, RelationKey } from './entity-meta.js';
|
|
5
|
+
import { findPrimaryKey } from '../query-builder/hydration-planner.js';
|
|
6
|
+
import { RelationIncludeOptions } from '../query-builder/relation-types.js';
|
|
7
|
+
import { populateHydrationCache } from './entity-hydration.js';
|
|
8
|
+
import { getRelationWrapper, RelationEntityFactory } from './entity-relations.js';
|
|
9
|
+
|
|
10
|
+
export { relationLoaderCache } from './entity-relation-cache.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Creates an entity proxy with lazy loading capabilities.
|
|
14
|
+
* @template TTable - The table type
|
|
15
|
+
* @template TLazy - The lazy relation keys
|
|
16
|
+
* @param ctx - The entity context
|
|
17
|
+
* @param table - The table definition
|
|
18
|
+
* @param row - The database row
|
|
19
|
+
* @param lazyRelations - Optional lazy relations
|
|
20
|
+
* @returns The entity instance
|
|
21
|
+
*/
|
|
22
|
+
export const createEntityProxy = <
|
|
23
|
+
TTable extends TableDef,
|
|
24
|
+
TLazy extends RelationKey<TTable> = RelationKey<TTable>
|
|
25
|
+
>(
|
|
26
|
+
ctx: EntityContext,
|
|
27
|
+
table: TTable,
|
|
28
|
+
row: Record<string, unknown>,
|
|
29
|
+
lazyRelations: TLazy[] = [] as TLazy[],
|
|
30
|
+
lazyRelationOptions: Map<string, RelationIncludeOptions> = new Map()
|
|
31
|
+
): EntityInstance<TTable> => {
|
|
32
|
+
const target: Record<string, unknown> = { ...row };
|
|
33
|
+
const meta: EntityMeta<TTable> = {
|
|
34
|
+
ctx,
|
|
35
|
+
table,
|
|
36
|
+
lazyRelations: [...lazyRelations],
|
|
37
|
+
lazyRelationOptions: new Map(lazyRelationOptions),
|
|
38
|
+
relationCache: new Map(),
|
|
39
|
+
relationHydration: new Map(),
|
|
40
|
+
relationWrappers: new Map()
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const createRelationEntity: RelationEntityFactory = (relationTable, relationRow) =>
|
|
44
|
+
createEntityFromRow(meta.ctx, relationTable, relationRow);
|
|
45
|
+
|
|
46
|
+
Object.defineProperty(target, ENTITY_META, {
|
|
47
|
+
value: meta,
|
|
48
|
+
enumerable: false,
|
|
49
|
+
writable: false
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const handler: ProxyHandler<object> = {
|
|
53
|
+
get(targetObj, prop, receiver) {
|
|
54
|
+
if (prop === ENTITY_META) {
|
|
55
|
+
return meta;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (prop === '$load') {
|
|
59
|
+
return async (relationName: RelationKey<TTable>) => {
|
|
60
|
+
const wrapper = getRelationWrapper(meta, relationName, receiver, createRelationEntity);
|
|
61
|
+
if (wrapper && typeof wrapper.load === 'function') {
|
|
62
|
+
return wrapper.load();
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (typeof prop === 'string' && table.relations[prop]) {
|
|
69
|
+
return getRelationWrapper(meta, prop as RelationKey<TTable>, receiver, createRelationEntity);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return Reflect.get(targetObj, prop, receiver);
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
set(targetObj, prop, value, receiver) {
|
|
76
|
+
const result = Reflect.set(targetObj, prop, value, receiver);
|
|
77
|
+
if (typeof prop === 'string' && table.columns[prop]) {
|
|
78
|
+
ctx.markDirty(receiver);
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const proxy = new Proxy(target, handler) as EntityInstance<TTable>;
|
|
85
|
+
populateHydrationCache(proxy, row, meta);
|
|
86
|
+
return proxy;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Creates an entity instance from a database row.
|
|
91
|
+
* @template TTable - The table type
|
|
92
|
+
* @template TResult - The result type
|
|
93
|
+
* @param ctx - The entity context
|
|
94
|
+
* @param table - The table definition
|
|
95
|
+
* @param row - The database row
|
|
96
|
+
* @param lazyRelations - Optional lazy relations
|
|
97
|
+
* @returns The entity instance
|
|
98
|
+
*/
|
|
99
|
+
export const createEntityFromRow = <
|
|
100
|
+
TTable extends TableDef,
|
|
101
|
+
TResult extends EntityInstance<TTable> = EntityInstance<TTable>
|
|
102
|
+
>(
|
|
103
|
+
ctx: EntityContext,
|
|
104
|
+
table: TTable,
|
|
105
|
+
row: Record<string, unknown>,
|
|
106
|
+
lazyRelations: RelationKey<TTable>[] = [],
|
|
107
|
+
lazyRelationOptions: Map<string, RelationIncludeOptions> = new Map()
|
|
108
|
+
): TResult => {
|
|
109
|
+
const pkName = findPrimaryKey(table);
|
|
110
|
+
const pkValue = row[pkName];
|
|
111
|
+
if (pkValue !== undefined && pkValue !== null) {
|
|
112
|
+
const tracked = ctx.getEntity(table, pkValue as PrimaryKey);
|
|
113
|
+
if (tracked) return tracked as TResult;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const entity = createEntityProxy(ctx, table, row, lazyRelations, lazyRelationOptions);
|
|
117
|
+
if (pkValue !== undefined && pkValue !== null) {
|
|
118
|
+
ctx.trackManaged(table, pkValue as PrimaryKey, entity);
|
|
119
|
+
} else {
|
|
120
|
+
ctx.trackNew(table, entity);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return entity as TResult;
|
|
124
|
+
};
|
package/src/orm/execute.ts
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import { TableDef } from '../schema/table.js';
|
|
2
|
-
import { EntityInstance } 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';
|
|
12
|
-
import { EntityContext } from './entity-context.js';
|
|
13
|
-
import { ExecutionContext } from './execution-context.js';
|
|
14
|
-
import { HydrationContext } from './hydration-context.js';
|
|
15
|
-
import { RelationIncludeOptions } from '../query-builder/relation-types.js';
|
|
16
|
-
import { getEntityMeta, RelationKey } from './entity-meta.js';
|
|
17
|
-
import {
|
|
18
|
-
loadHasManyRelation,
|
|
19
|
-
loadHasOneRelation,
|
|
20
|
-
loadBelongsToRelation,
|
|
21
|
-
loadBelongsToManyRelation
|
|
22
|
-
} from './lazy-batch.js';
|
|
1
|
+
import { TableDef } from '../schema/table.js';
|
|
2
|
+
import { EntityInstance } 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';
|
|
12
|
+
import { EntityContext } from './entity-context.js';
|
|
13
|
+
import { ExecutionContext } from './execution-context.js';
|
|
14
|
+
import { HydrationContext } from './hydration-context.js';
|
|
15
|
+
import { RelationIncludeOptions } from '../query-builder/relation-types.js';
|
|
16
|
+
import { getEntityMeta, RelationKey } from './entity-meta.js';
|
|
17
|
+
import {
|
|
18
|
+
loadHasManyRelation,
|
|
19
|
+
loadHasOneRelation,
|
|
20
|
+
loadBelongsToRelation,
|
|
21
|
+
loadBelongsToManyRelation
|
|
22
|
+
} from './lazy-batch.js';
|
|
23
23
|
|
|
24
24
|
type Row = Record<string, unknown>;
|
|
25
25
|
|
|
@@ -38,152 +38,152 @@ const flattenResults = (results: { columns: string[]; values: unknown[][] }[]):
|
|
|
38
38
|
return rows;
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
-
const executeWithContexts = async <TTable extends TableDef>(
|
|
42
|
-
execCtx: ExecutionContext,
|
|
43
|
-
entityCtx: EntityContext,
|
|
44
|
-
qb: SelectQueryBuilder<unknown, TTable>
|
|
45
|
-
): Promise<EntityInstance<TTable>[]> => {
|
|
46
|
-
const ast = qb.getAST();
|
|
47
|
-
const compiled = execCtx.dialect.compileSelect(ast);
|
|
48
|
-
const executed = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
|
|
49
|
-
const rows = flattenResults(executed);
|
|
50
|
-
const lazyRelations = qb.getLazyRelations() as RelationKey<TTable>[];
|
|
51
|
-
const lazyRelationOptions = qb.getLazyRelationOptions();
|
|
52
|
-
|
|
53
|
-
if (ast.setOps && ast.setOps.length > 0) {
|
|
54
|
-
const proxies = rows.map(row => createEntityProxy(entityCtx, qb.getTable(), row, lazyRelations, lazyRelationOptions));
|
|
55
|
-
await loadLazyRelationsForTable(entityCtx, qb.getTable(), lazyRelations, lazyRelationOptions);
|
|
56
|
-
return proxies;
|
|
57
|
-
}
|
|
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
|
-
};
|
|
64
|
-
|
|
65
|
-
const executePlainWithContexts = async <TTable extends TableDef>(
|
|
66
|
-
execCtx: ExecutionContext,
|
|
67
|
-
qb: SelectQueryBuilder<unknown, TTable>
|
|
68
|
-
): Promise<Record<string, unknown>[]> => {
|
|
69
|
-
const ast = qb.getAST();
|
|
70
|
-
const compiled = execCtx.dialect.compileSelect(ast);
|
|
71
|
-
const executed = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
|
|
72
|
-
const rows = flattenResults(executed);
|
|
73
|
-
|
|
74
|
-
if (ast.setOps && ast.setOps.length > 0) {
|
|
75
|
-
return rows;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return hydrateRows(rows, qb.getHydrationPlan());
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Executes a hydrated query using the ORM session.
|
|
83
|
-
* @template TTable - The table type
|
|
84
|
-
* @param session - The ORM session
|
|
85
|
-
* @param qb - The select query builder
|
|
41
|
+
const executeWithContexts = async <TTable extends TableDef>(
|
|
42
|
+
execCtx: ExecutionContext,
|
|
43
|
+
entityCtx: EntityContext,
|
|
44
|
+
qb: SelectQueryBuilder<unknown, TTable>
|
|
45
|
+
): Promise<EntityInstance<TTable>[]> => {
|
|
46
|
+
const ast = qb.getAST();
|
|
47
|
+
const compiled = execCtx.dialect.compileSelect(ast);
|
|
48
|
+
const executed = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
|
|
49
|
+
const rows = flattenResults(executed);
|
|
50
|
+
const lazyRelations = qb.getLazyRelations() as RelationKey<TTable>[];
|
|
51
|
+
const lazyRelationOptions = qb.getLazyRelationOptions();
|
|
52
|
+
|
|
53
|
+
if (ast.setOps && ast.setOps.length > 0) {
|
|
54
|
+
const proxies = rows.map(row => createEntityProxy(entityCtx, qb.getTable(), row, lazyRelations, lazyRelationOptions));
|
|
55
|
+
await loadLazyRelationsForTable(entityCtx, qb.getTable(), lazyRelations, lazyRelationOptions);
|
|
56
|
+
return proxies;
|
|
57
|
+
}
|
|
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
|
+
};
|
|
64
|
+
|
|
65
|
+
const executePlainWithContexts = async <TTable extends TableDef>(
|
|
66
|
+
execCtx: ExecutionContext,
|
|
67
|
+
qb: SelectQueryBuilder<unknown, TTable>
|
|
68
|
+
): Promise<Record<string, unknown>[]> => {
|
|
69
|
+
const ast = qb.getAST();
|
|
70
|
+
const compiled = execCtx.dialect.compileSelect(ast);
|
|
71
|
+
const executed = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
|
|
72
|
+
const rows = flattenResults(executed);
|
|
73
|
+
|
|
74
|
+
if (ast.setOps && ast.setOps.length > 0) {
|
|
75
|
+
return rows;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return hydrateRows(rows, qb.getHydrationPlan());
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Executes a hydrated query using the ORM session.
|
|
83
|
+
* @template TTable - The table type
|
|
84
|
+
* @param session - The ORM session
|
|
85
|
+
* @param qb - The select query builder
|
|
86
86
|
* @returns Promise resolving to array of entity instances
|
|
87
87
|
*/
|
|
88
|
-
export async function executeHydrated<TTable extends TableDef>(
|
|
89
|
-
session: OrmSession,
|
|
90
|
-
qb: SelectQueryBuilder<unknown, TTable>
|
|
91
|
-
): Promise<EntityInstance<TTable>[]> {
|
|
92
|
-
return executeWithContexts(session.getExecutionContext(), session, qb);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Executes a hydrated query and returns plain row objects (no entity proxies).
|
|
97
|
-
* @template TTable - The table type
|
|
98
|
-
* @param session - The ORM session
|
|
99
|
-
* @param qb - The select query builder
|
|
100
|
-
* @returns Promise resolving to array of plain row objects
|
|
101
|
-
*/
|
|
102
|
-
export async function executeHydratedPlain<TTable extends TableDef>(
|
|
103
|
-
session: OrmSession,
|
|
104
|
-
qb: SelectQueryBuilder<unknown, TTable>
|
|
105
|
-
): Promise<Record<string, unknown>[]> {
|
|
106
|
-
return executePlainWithContexts(session.getExecutionContext(), qb);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Executes a hydrated query using execution and hydration contexts.
|
|
111
|
-
* @template TTable - The table type
|
|
112
|
-
* @param _execCtx - The execution context (unused)
|
|
113
|
-
* @param hydCtx - The hydration context
|
|
88
|
+
export async function executeHydrated<TTable extends TableDef>(
|
|
89
|
+
session: OrmSession,
|
|
90
|
+
qb: SelectQueryBuilder<unknown, TTable>
|
|
91
|
+
): Promise<EntityInstance<TTable>[]> {
|
|
92
|
+
return executeWithContexts(session.getExecutionContext(), session, qb);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Executes a hydrated query and returns plain row objects (no entity proxies).
|
|
97
|
+
* @template TTable - The table type
|
|
98
|
+
* @param session - The ORM session
|
|
99
|
+
* @param qb - The select query builder
|
|
100
|
+
* @returns Promise resolving to array of plain row objects
|
|
101
|
+
*/
|
|
102
|
+
export async function executeHydratedPlain<TTable extends TableDef>(
|
|
103
|
+
session: OrmSession,
|
|
104
|
+
qb: SelectQueryBuilder<unknown, TTable>
|
|
105
|
+
): Promise<Record<string, unknown>[]> {
|
|
106
|
+
return executePlainWithContexts(session.getExecutionContext(), qb);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Executes a hydrated query using execution and hydration contexts.
|
|
111
|
+
* @template TTable - The table type
|
|
112
|
+
* @param _execCtx - The execution context (unused)
|
|
113
|
+
* @param hydCtx - The hydration context
|
|
114
114
|
* @param qb - The select query builder
|
|
115
115
|
* @returns Promise resolving to array of entity instances
|
|
116
116
|
*/
|
|
117
|
-
export async function executeHydratedWithContexts<TTable extends TableDef>(
|
|
118
|
-
execCtx: ExecutionContext,
|
|
119
|
-
hydCtx: HydrationContext,
|
|
120
|
-
qb: SelectQueryBuilder<unknown, TTable>
|
|
121
|
-
): Promise<EntityInstance<TTable>[]> {
|
|
122
|
-
const entityCtx = hydCtx.entityContext;
|
|
123
|
-
if (!entityCtx) {
|
|
124
|
-
throw new Error('Hydration context is missing an EntityContext');
|
|
125
|
-
}
|
|
126
|
-
return executeWithContexts(execCtx, entityCtx, qb);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Executes a hydrated query using execution context and returns plain row objects.
|
|
131
|
-
* @template TTable - The table type
|
|
132
|
-
* @param execCtx - The execution context
|
|
133
|
-
* @param qb - The select query builder
|
|
134
|
-
* @returns Promise resolving to array of plain row objects
|
|
135
|
-
*/
|
|
136
|
-
export async function executeHydratedPlainWithContexts<TTable extends TableDef>(
|
|
137
|
-
execCtx: ExecutionContext,
|
|
138
|
-
qb: SelectQueryBuilder<unknown, TTable>
|
|
139
|
-
): Promise<Record<string, unknown>[]> {
|
|
140
|
-
return executePlainWithContexts(execCtx, qb);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const loadLazyRelationsForTable = async <TTable extends TableDef>(
|
|
144
|
-
ctx: EntityContext,
|
|
145
|
-
table: TTable,
|
|
146
|
-
lazyRelations: RelationKey<TTable>[],
|
|
147
|
-
lazyRelationOptions: Map<string, RelationIncludeOptions>
|
|
148
|
-
): Promise<void> => {
|
|
149
|
-
if (!lazyRelations.length) return;
|
|
150
|
-
|
|
151
|
-
const tracked = ctx.getEntitiesForTable(table);
|
|
152
|
-
if (!tracked.length) return;
|
|
153
|
-
|
|
154
|
-
const meta = getEntityMeta(tracked[0].entity);
|
|
155
|
-
if (!meta) return;
|
|
156
|
-
|
|
157
|
-
for (const relationName of lazyRelations) {
|
|
158
|
-
const relation = table.relations[relationName as string];
|
|
159
|
-
if (!relation) continue;
|
|
160
|
-
const key = relationName as string;
|
|
161
|
-
const options = lazyRelationOptions.get(key);
|
|
162
|
-
if (!options) {
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
switch (relation.type) {
|
|
167
|
-
case RelationKinds.HasOne:
|
|
168
|
-
await relationLoaderCache(meta, key, () =>
|
|
169
|
-
loadHasOneRelation(ctx, table, key, relation, options)
|
|
170
|
-
);
|
|
171
|
-
break;
|
|
172
|
-
case RelationKinds.HasMany:
|
|
173
|
-
await relationLoaderCache(meta, key, () =>
|
|
174
|
-
loadHasManyRelation(ctx, table, key, relation, options)
|
|
175
|
-
);
|
|
176
|
-
break;
|
|
177
|
-
case RelationKinds.BelongsTo:
|
|
178
|
-
await relationLoaderCache(meta, key, () =>
|
|
179
|
-
loadBelongsToRelation(ctx, table, key, relation, options)
|
|
180
|
-
);
|
|
181
|
-
break;
|
|
182
|
-
case RelationKinds.BelongsToMany:
|
|
183
|
-
await relationLoaderCache(meta, key, () =>
|
|
184
|
-
loadBelongsToManyRelation(ctx, table, key, relation, options)
|
|
185
|
-
);
|
|
186
|
-
break;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
};
|
|
117
|
+
export async function executeHydratedWithContexts<TTable extends TableDef>(
|
|
118
|
+
execCtx: ExecutionContext,
|
|
119
|
+
hydCtx: HydrationContext,
|
|
120
|
+
qb: SelectQueryBuilder<unknown, TTable>
|
|
121
|
+
): Promise<EntityInstance<TTable>[]> {
|
|
122
|
+
const entityCtx = hydCtx.entityContext;
|
|
123
|
+
if (!entityCtx) {
|
|
124
|
+
throw new Error('Hydration context is missing an EntityContext');
|
|
125
|
+
}
|
|
126
|
+
return executeWithContexts(execCtx, entityCtx, qb);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Executes a hydrated query using execution context and returns plain row objects.
|
|
131
|
+
* @template TTable - The table type
|
|
132
|
+
* @param execCtx - The execution context
|
|
133
|
+
* @param qb - The select query builder
|
|
134
|
+
* @returns Promise resolving to array of plain row objects
|
|
135
|
+
*/
|
|
136
|
+
export async function executeHydratedPlainWithContexts<TTable extends TableDef>(
|
|
137
|
+
execCtx: ExecutionContext,
|
|
138
|
+
qb: SelectQueryBuilder<unknown, TTable>
|
|
139
|
+
): Promise<Record<string, unknown>[]> {
|
|
140
|
+
return executePlainWithContexts(execCtx, qb);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const loadLazyRelationsForTable = async <TTable extends TableDef>(
|
|
144
|
+
ctx: EntityContext,
|
|
145
|
+
table: TTable,
|
|
146
|
+
lazyRelations: RelationKey<TTable>[],
|
|
147
|
+
lazyRelationOptions: Map<string, RelationIncludeOptions>
|
|
148
|
+
): Promise<void> => {
|
|
149
|
+
if (!lazyRelations.length) return;
|
|
150
|
+
|
|
151
|
+
const tracked = ctx.getEntitiesForTable(table);
|
|
152
|
+
if (!tracked.length) return;
|
|
153
|
+
|
|
154
|
+
const meta = getEntityMeta(tracked[0].entity);
|
|
155
|
+
if (!meta) return;
|
|
156
|
+
|
|
157
|
+
for (const relationName of lazyRelations) {
|
|
158
|
+
const relation = table.relations[relationName as string];
|
|
159
|
+
if (!relation) continue;
|
|
160
|
+
const key = relationName as string;
|
|
161
|
+
const options = lazyRelationOptions.get(key);
|
|
162
|
+
if (!options) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
switch (relation.type) {
|
|
167
|
+
case RelationKinds.HasOne:
|
|
168
|
+
await relationLoaderCache(meta, key, () =>
|
|
169
|
+
loadHasOneRelation(ctx, table, key, relation, options)
|
|
170
|
+
);
|
|
171
|
+
break;
|
|
172
|
+
case RelationKinds.HasMany:
|
|
173
|
+
await relationLoaderCache(meta, key, () =>
|
|
174
|
+
loadHasManyRelation(ctx, table, key, relation, options)
|
|
175
|
+
);
|
|
176
|
+
break;
|
|
177
|
+
case RelationKinds.BelongsTo:
|
|
178
|
+
await relationLoaderCache(meta, key, () =>
|
|
179
|
+
loadBelongsToRelation(ctx, table, key, relation, options)
|
|
180
|
+
);
|
|
181
|
+
break;
|
|
182
|
+
case RelationKinds.BelongsToMany:
|
|
183
|
+
await relationLoaderCache(meta, key, () =>
|
|
184
|
+
loadBelongsToManyRelation(ctx, table, key, relation, options)
|
|
185
|
+
);
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
};
|
package/src/orm/identity-map.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { TableDef } from '../schema/table.js';
|
|
2
2
|
import type { TrackedEntity } from './runtime-types.js';
|
|
3
|
+
import type { PrimaryKey } from './entity-context.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Simple identity map for tracking entities within a session.
|
|
@@ -18,7 +19,7 @@ export class IdentityMap {
|
|
|
18
19
|
* @param pk The primary key value.
|
|
19
20
|
* @returns The entity instance if found, undefined otherwise.
|
|
20
21
|
*/
|
|
21
|
-
getEntity(table: TableDef, pk:
|
|
22
|
+
getEntity(table: TableDef, pk: PrimaryKey): object | undefined {
|
|
22
23
|
const bucket = this.buckets.get(table.name);
|
|
23
24
|
return bucket?.get(this.toIdentityKey(pk))?.entity;
|
|
24
25
|
}
|
|
@@ -54,7 +55,7 @@ export class IdentityMap {
|
|
|
54
55
|
this.buckets.clear();
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
private toIdentityKey(pk:
|
|
58
|
+
private toIdentityKey(pk: PrimaryKey): string {
|
|
58
59
|
return String(pk);
|
|
59
60
|
}
|
|
60
61
|
}
|
|
@@ -71,7 +71,7 @@ export const toKey = (value: unknown): string => (value === null || value === un
|
|
|
71
71
|
export const collectKeysFromRoots = (roots: EntityTracker[], key: string): Set<unknown> => {
|
|
72
72
|
const collected = new Set<unknown>();
|
|
73
73
|
for (const tracked of roots) {
|
|
74
|
-
const value = tracked.entity[key];
|
|
74
|
+
const value = (tracked.entity as Record<string, unknown>)[key];
|
|
75
75
|
if (value !== null && value !== undefined) {
|
|
76
76
|
collected.add(value);
|
|
77
77
|
}
|