metal-orm 1.0.63 → 1.0.65
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 +10 -8
- package/dist/index.cjs +130 -107
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +848 -457
- package/dist/index.d.ts +848 -457
- package/dist/index.js +130 -107
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/ast/aggregate-functions.ts +14 -5
- package/src/core/ast/expression-builders.ts +325 -111
- package/src/core/ast/window-functions.ts +62 -52
- package/src/core/functions/array.ts +12 -3
- package/src/core/functions/control-flow.ts +28 -18
- package/src/core/functions/datetime.ts +113 -84
- package/src/core/functions/json.ts +40 -8
- package/src/core/functions/numeric.ts +116 -79
- package/src/core/functions/text.ts +181 -114
- 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 +1 -1
- 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 +161 -161
- package/src/orm/unit-of-work.ts +58 -56
- 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/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
|
@@ -18,7 +18,7 @@ export class IdentityMap {
|
|
|
18
18
|
* @param pk The primary key value.
|
|
19
19
|
* @returns The entity instance if found, undefined otherwise.
|
|
20
20
|
*/
|
|
21
|
-
getEntity(table: TableDef, pk: string | number):
|
|
21
|
+
getEntity(table: TableDef, pk: string | number): object | undefined {
|
|
22
22
|
const bucket = this.buckets.get(table.name);
|
|
23
23
|
return bucket?.get(this.toIdentityKey(pk))?.entity;
|
|
24
24
|
}
|
|
@@ -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
|
}
|
package/src/orm/orm-session.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { RelationChangeProcessor } from './relation-change-processor.js';
|
|
|
18
18
|
import { createQueryLoggingExecutor, QueryLogger } from './query-logger.js';
|
|
19
19
|
import { ExecutionContext } from './execution-context.js';
|
|
20
20
|
import type { HydrationContext } from './hydration-context.js';
|
|
21
|
-
import type { EntityContext } from './entity-context.js';
|
|
21
|
+
import type { EntityContext, PrimaryKey } from './entity-context.js';
|
|
22
22
|
import {
|
|
23
23
|
DomainEvent,
|
|
24
24
|
OrmDomainEvent,
|
|
@@ -27,10 +27,10 @@ import {
|
|
|
27
27
|
RelationKey,
|
|
28
28
|
TrackedEntity
|
|
29
29
|
} from './runtime-types.js';
|
|
30
|
-
import { executeHydrated } from './execute.js';
|
|
31
|
-
import { runInTransaction } from './transaction-runner.js';
|
|
32
|
-
import { saveGraphInternal, SaveGraphOptions } from './save-graph.js';
|
|
33
|
-
import type { SaveGraphInputPayload } from './save-graph-types.js';
|
|
30
|
+
import { executeHydrated } from './execute.js';
|
|
31
|
+
import { runInTransaction } from './transaction-runner.js';
|
|
32
|
+
import { saveGraphInternal, SaveGraphOptions } from './save-graph.js';
|
|
33
|
+
import type { SaveGraphInputPayload } from './save-graph-types.js';
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
36
|
* Interface for ORM interceptors that allow hooking into the flush lifecycle.
|
|
@@ -142,9 +142,9 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
|
|
|
142
142
|
* @param pk - The primary key value
|
|
143
143
|
* @returns The entity or undefined if not found
|
|
144
144
|
*/
|
|
145
|
-
getEntity(table: TableDef, pk:
|
|
146
|
-
return this.unitOfWork.getEntity(table, pk
|
|
147
|
-
}
|
|
145
|
+
getEntity(table: TableDef, pk: PrimaryKey): object | undefined {
|
|
146
|
+
return this.unitOfWork.getEntity(table, pk);
|
|
147
|
+
}
|
|
148
148
|
|
|
149
149
|
/**
|
|
150
150
|
* Sets an entity in the identity map.
|
|
@@ -152,9 +152,9 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
|
|
|
152
152
|
* @param pk - The primary key value
|
|
153
153
|
* @param entity - The entity instance
|
|
154
154
|
*/
|
|
155
|
-
setEntity(table: TableDef, pk:
|
|
156
|
-
this.unitOfWork.setEntity(table, pk
|
|
157
|
-
}
|
|
155
|
+
setEntity(table: TableDef, pk: PrimaryKey, entity: object): void {
|
|
156
|
+
this.unitOfWork.setEntity(table, pk, entity);
|
|
157
|
+
}
|
|
158
158
|
|
|
159
159
|
/**
|
|
160
160
|
* Tracks a new entity.
|
|
@@ -162,9 +162,9 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
|
|
|
162
162
|
* @param entity - The entity instance
|
|
163
163
|
* @param pk - Optional primary key value
|
|
164
164
|
*/
|
|
165
|
-
trackNew(table: TableDef, entity:
|
|
166
|
-
this.unitOfWork.trackNew(table, entity, pk
|
|
167
|
-
}
|
|
165
|
+
trackNew(table: TableDef, entity: object, pk?: PrimaryKey): void {
|
|
166
|
+
this.unitOfWork.trackNew(table, entity, pk);
|
|
167
|
+
}
|
|
168
168
|
|
|
169
169
|
/**
|
|
170
170
|
* Tracks a managed entity.
|
|
@@ -172,25 +172,25 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
|
|
|
172
172
|
* @param pk - The primary key value
|
|
173
173
|
* @param entity - The entity instance
|
|
174
174
|
*/
|
|
175
|
-
trackManaged(table: TableDef, pk:
|
|
176
|
-
this.unitOfWork.trackManaged(table, pk
|
|
177
|
-
}
|
|
175
|
+
trackManaged(table: TableDef, pk: PrimaryKey, entity: object): void {
|
|
176
|
+
this.unitOfWork.trackManaged(table, pk, entity);
|
|
177
|
+
}
|
|
178
178
|
|
|
179
179
|
/**
|
|
180
180
|
* Marks an entity as dirty (modified).
|
|
181
181
|
* @param entity - The entity to mark as dirty
|
|
182
182
|
*/
|
|
183
|
-
markDirty(entity:
|
|
184
|
-
this.unitOfWork.markDirty(entity);
|
|
185
|
-
}
|
|
183
|
+
markDirty(entity: object): void {
|
|
184
|
+
this.unitOfWork.markDirty(entity);
|
|
185
|
+
}
|
|
186
186
|
|
|
187
187
|
/**
|
|
188
188
|
* Marks an entity as removed.
|
|
189
189
|
* @param entity - The entity to mark as removed
|
|
190
190
|
*/
|
|
191
|
-
markRemoved(entity:
|
|
192
|
-
this.unitOfWork.markRemoved(entity);
|
|
193
|
-
}
|
|
191
|
+
markRemoved(entity: object): void {
|
|
192
|
+
this.unitOfWork.markRemoved(entity);
|
|
193
|
+
}
|
|
194
194
|
|
|
195
195
|
/**
|
|
196
196
|
* Registers a relation change.
|
|
@@ -298,30 +298,30 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
|
|
|
298
298
|
return executeHydrated(this, qb);
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
-
/**
|
|
302
|
-
* Saves an entity graph (root + nested relations) based on a DTO-like payload.
|
|
303
|
-
* @param entityClass - Root entity constructor
|
|
304
|
-
* @param payload - DTO payload containing column values and nested relations
|
|
305
|
-
* @param options - Graph save options
|
|
306
|
-
* @returns The root entity instance
|
|
307
|
-
*/
|
|
308
|
-
async saveGraph<TCtor extends EntityConstructor<object>>(
|
|
309
|
-
entityClass: TCtor,
|
|
310
|
-
payload: SaveGraphInputPayload<InstanceType<TCtor>>,
|
|
311
|
-
options?: SaveGraphOptions & { transactional?: boolean }
|
|
312
|
-
): Promise<InstanceType<TCtor>>;
|
|
313
|
-
async saveGraph<TCtor extends EntityConstructor<object>>(
|
|
314
|
-
entityClass: TCtor,
|
|
315
|
-
payload: Record<string, unknown>,
|
|
316
|
-
options?: SaveGraphOptions & { transactional?: boolean }
|
|
317
|
-
): Promise<InstanceType<TCtor>> {
|
|
318
|
-
const { transactional = true, ...graphOptions } = options ?? {};
|
|
319
|
-
const execute = () => saveGraphInternal(this, entityClass, payload, graphOptions);
|
|
320
|
-
if (!transactional) {
|
|
321
|
-
return execute();
|
|
322
|
-
}
|
|
323
|
-
return this.transaction(() => execute());
|
|
324
|
-
}
|
|
301
|
+
/**
|
|
302
|
+
* Saves an entity graph (root + nested relations) based on a DTO-like payload.
|
|
303
|
+
* @param entityClass - Root entity constructor
|
|
304
|
+
* @param payload - DTO payload containing column values and nested relations
|
|
305
|
+
* @param options - Graph save options
|
|
306
|
+
* @returns The root entity instance
|
|
307
|
+
*/
|
|
308
|
+
async saveGraph<TCtor extends EntityConstructor<object>>(
|
|
309
|
+
entityClass: TCtor,
|
|
310
|
+
payload: SaveGraphInputPayload<InstanceType<TCtor>>,
|
|
311
|
+
options?: SaveGraphOptions & { transactional?: boolean }
|
|
312
|
+
): Promise<InstanceType<TCtor>>;
|
|
313
|
+
async saveGraph<TCtor extends EntityConstructor<object>>(
|
|
314
|
+
entityClass: TCtor,
|
|
315
|
+
payload: Record<string, unknown>,
|
|
316
|
+
options?: SaveGraphOptions & { transactional?: boolean }
|
|
317
|
+
): Promise<InstanceType<TCtor>> {
|
|
318
|
+
const { transactional = true, ...graphOptions } = options ?? {};
|
|
319
|
+
const execute = () => saveGraphInternal(this, entityClass, payload, graphOptions);
|
|
320
|
+
if (!transactional) {
|
|
321
|
+
return execute();
|
|
322
|
+
}
|
|
323
|
+
return this.transaction(() => execute());
|
|
324
|
+
}
|
|
325
325
|
|
|
326
326
|
/**
|
|
327
327
|
* Persists an entity (either inserts or updates).
|
|
@@ -340,7 +340,7 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
|
|
|
340
340
|
const primaryKey = findPrimaryKey(table);
|
|
341
341
|
const pkValue = (entity as Record<string, unknown>)[primaryKey];
|
|
342
342
|
if (pkValue !== undefined && pkValue !== null) {
|
|
343
|
-
this.trackManaged(table, pkValue, entity);
|
|
343
|
+
this.trackManaged(table, pkValue as PrimaryKey, entity);
|
|
344
344
|
} else {
|
|
345
345
|
this.trackNew(table, entity);
|
|
346
346
|
}
|
|
@@ -354,12 +354,12 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
|
|
|
354
354
|
this.markRemoved(entity);
|
|
355
355
|
}
|
|
356
356
|
|
|
357
|
-
/**
|
|
358
|
-
* Flushes pending changes to the database without session hooks, relation processing, or domain events.
|
|
359
|
-
*/
|
|
360
|
-
async flush(): Promise<void> {
|
|
361
|
-
await this.unitOfWork.flush();
|
|
362
|
-
}
|
|
357
|
+
/**
|
|
358
|
+
* Flushes pending changes to the database without session hooks, relation processing, or domain events.
|
|
359
|
+
*/
|
|
360
|
+
async flush(): Promise<void> {
|
|
361
|
+
await this.unitOfWork.flush();
|
|
362
|
+
}
|
|
363
363
|
|
|
364
364
|
/**
|
|
365
365
|
* Flushes pending changes with interceptors and relation processing.
|
|
@@ -78,7 +78,7 @@ export class RelationChangeProcessor {
|
|
|
78
78
|
const target = entry.change.entity;
|
|
79
79
|
if (!target) return;
|
|
80
80
|
|
|
81
|
-
const tracked = this.unitOfWork.findTracked(target);
|
|
81
|
+
const tracked = this.unitOfWork.findTracked(target as object);
|
|
82
82
|
if (!tracked) return;
|
|
83
83
|
|
|
84
84
|
const localKey = relation.localKey || findPrimaryKey(entry.rootTable);
|
|
@@ -105,7 +105,7 @@ export class RelationChangeProcessor {
|
|
|
105
105
|
const target = entry.change.entity;
|
|
106
106
|
if (!target) return;
|
|
107
107
|
|
|
108
|
-
const tracked = this.unitOfWork.findTracked(target);
|
|
108
|
+
const tracked = this.unitOfWork.findTracked(target as object);
|
|
109
109
|
if (!tracked) return;
|
|
110
110
|
|
|
111
111
|
const localKey = relation.localKey || findPrimaryKey(entry.rootTable);
|
|
@@ -154,7 +154,7 @@ export class RelationChangeProcessor {
|
|
|
154
154
|
await this.deletePivotRow(relation, rootId, targetId);
|
|
155
155
|
|
|
156
156
|
if (relation.cascade === 'all' || relation.cascade === 'remove') {
|
|
157
|
-
this.unitOfWork.markRemoved(entry.change.entity);
|
|
157
|
+
this.unitOfWork.markRemoved(entry.change.entity as object);
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
}
|
|
@@ -125,7 +125,7 @@ export class DefaultHasManyCollection<TChild> implements HasManyCollection<TChil
|
|
|
125
125
|
attach(entity: TChild): void {
|
|
126
126
|
const keyValue = this.root[this.localKey];
|
|
127
127
|
(entity as Record<string, unknown>)[this.relation.foreignKey] = keyValue;
|
|
128
|
-
this.ctx.markDirty(entity);
|
|
128
|
+
this.ctx.markDirty(entity as object);
|
|
129
129
|
this.items.push(entity);
|
|
130
130
|
this.ctx.registerRelationChange(
|
|
131
131
|
this.root,
|
package/src/orm/runtime-types.ts
CHANGED
|
@@ -20,11 +20,11 @@ export enum EntityStatus {
|
|
|
20
20
|
/**
|
|
21
21
|
* Represents an entity being tracked by the ORM
|
|
22
22
|
*/
|
|
23
|
-
export interface TrackedEntity {
|
|
24
|
-
/** The table definition this entity belongs to */
|
|
25
|
-
table: TableDef;
|
|
26
|
-
/** The actual entity instance */
|
|
27
|
-
entity:
|
|
23
|
+
export interface TrackedEntity {
|
|
24
|
+
/** The table definition this entity belongs to */
|
|
25
|
+
table: TableDef;
|
|
26
|
+
/** The actual entity instance */
|
|
27
|
+
entity: object;
|
|
28
28
|
/** Primary key value of the entity */
|
|
29
29
|
pk: string | number | null;
|
|
30
30
|
/** Current status of the entity */
|