metal-orm 1.0.90 → 1.0.92
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 +214 -118
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +71 -32
- package/dist/index.d.ts +71 -32
- package/dist/index.js +206 -118
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
- package/scripts/generate-entities/render.mjs +16 -3
- package/src/core/ddl/introspect/utils.ts +45 -45
- package/src/decorators/bootstrap.ts +37 -37
- package/src/decorators/column-decorator.ts +3 -1
- package/src/dto/apply-filter.ts +279 -281
- package/src/dto/dto-types.ts +229 -229
- package/src/dto/filter-types.ts +193 -193
- package/src/dto/index.ts +97 -97
- package/src/dto/openapi/generators/base.ts +29 -29
- package/src/dto/openapi/generators/column.ts +37 -34
- package/src/dto/openapi/generators/dto.ts +94 -94
- package/src/dto/openapi/generators/filter.ts +75 -74
- package/src/dto/openapi/generators/nested-dto.ts +618 -532
- package/src/dto/openapi/generators/pagination.ts +111 -111
- package/src/dto/openapi/generators/relation-filter.ts +228 -210
- package/src/dto/openapi/index.ts +17 -17
- package/src/dto/openapi/type-mappings.ts +191 -191
- package/src/dto/openapi/types.ts +101 -83
- package/src/dto/openapi/utilities.ts +90 -45
- package/src/dto/pagination-utils.ts +150 -150
- package/src/dto/transform.ts +197 -193
- package/src/index.ts +69 -69
- package/src/orm/entity-context.ts +9 -9
- package/src/orm/entity-metadata.ts +14 -14
- package/src/orm/entity.ts +74 -74
- package/src/orm/orm-session.ts +159 -159
- package/src/orm/relation-change-processor.ts +3 -3
- package/src/orm/runtime-types.ts +5 -5
- package/src/schema/column-types.ts +4 -4
- package/src/schema/types.ts +5 -1
package/src/orm/entity.ts
CHANGED
|
@@ -7,20 +7,20 @@ import { RelationIncludeOptions } from '../query-builder/relation-types.js';
|
|
|
7
7
|
import { populateHydrationCache } from './entity-hydration.js';
|
|
8
8
|
import { getRelationWrapper, RelationEntityFactory } from './entity-relations.js';
|
|
9
9
|
|
|
10
|
-
export { relationLoaderCache } from './entity-relation-cache.js';
|
|
11
|
-
|
|
12
|
-
const isRelationWrapperLoaded = (value: unknown): boolean => {
|
|
13
|
-
if (!value || typeof value !== 'object') return false;
|
|
14
|
-
return Boolean((value as { loaded?: boolean }).loaded);
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
type JsonSource<TTable extends TableDef> = EntityInstance<TTable> & Record<string, unknown>;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Creates an entity proxy with lazy loading capabilities.
|
|
21
|
-
* @template TTable - The table type
|
|
22
|
-
* @template TLazy - The lazy relation keys
|
|
23
|
-
* @param ctx - The entity context
|
|
10
|
+
export { relationLoaderCache } from './entity-relation-cache.js';
|
|
11
|
+
|
|
12
|
+
const isRelationWrapperLoaded = (value: unknown): boolean => {
|
|
13
|
+
if (!value || typeof value !== 'object') return false;
|
|
14
|
+
return Boolean((value as { loaded?: boolean }).loaded);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type JsonSource<TTable extends TableDef> = EntityInstance<TTable> & Record<string, unknown>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Creates an entity proxy with lazy loading capabilities.
|
|
21
|
+
* @template TTable - The table type
|
|
22
|
+
* @template TLazy - The lazy relation keys
|
|
23
|
+
* @param ctx - The entity context
|
|
24
24
|
* @param table - The table definition
|
|
25
25
|
* @param row - The database row
|
|
26
26
|
* @param lazyRelations - Optional lazy relations
|
|
@@ -35,39 +35,39 @@ export const createEntityProxy = <
|
|
|
35
35
|
row: Record<string, unknown>,
|
|
36
36
|
lazyRelations: TLazy[] = [] as TLazy[],
|
|
37
37
|
lazyRelationOptions: Map<string, RelationIncludeOptions> = new Map()
|
|
38
|
-
): EntityInstance<TTable> => {
|
|
39
|
-
const target: Record<string, unknown> = { ...row };
|
|
40
|
-
const meta: EntityMeta<TTable> = {
|
|
41
|
-
ctx,
|
|
42
|
-
table,
|
|
38
|
+
): EntityInstance<TTable> => {
|
|
39
|
+
const target: Record<string, unknown> = { ...row };
|
|
40
|
+
const meta: EntityMeta<TTable> = {
|
|
41
|
+
ctx,
|
|
42
|
+
table,
|
|
43
43
|
lazyRelations: [...lazyRelations],
|
|
44
44
|
lazyRelationOptions: new Map(lazyRelationOptions),
|
|
45
45
|
relationCache: new Map(),
|
|
46
|
-
relationHydration: new Map(),
|
|
47
|
-
relationWrappers: new Map()
|
|
48
|
-
};
|
|
49
|
-
const createRelationEntity: RelationEntityFactory = (relationTable, relationRow) =>
|
|
50
|
-
createEntityFromRow(meta.ctx, relationTable, relationRow);
|
|
51
|
-
|
|
52
|
-
const buildJson = (self: JsonSource<TTable>): Record<string, unknown> => {
|
|
53
|
-
const json: Record<string, unknown> = {};
|
|
54
|
-
for (const key of Object.keys(target)) {
|
|
55
|
-
json[key] = self[key];
|
|
56
|
-
}
|
|
57
|
-
for (const [relationName, wrapper] of meta.relationWrappers) {
|
|
58
|
-
if (Object.prototype.hasOwnProperty.call(json, relationName)) continue;
|
|
59
|
-
if (isRelationWrapperLoaded(wrapper)) {
|
|
60
|
-
json[relationName] = wrapper;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return json;
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
Object.defineProperty(target, ENTITY_META, {
|
|
67
|
-
value: meta,
|
|
68
|
-
enumerable: false,
|
|
69
|
-
writable: false
|
|
70
|
-
});
|
|
46
|
+
relationHydration: new Map(),
|
|
47
|
+
relationWrappers: new Map()
|
|
48
|
+
};
|
|
49
|
+
const createRelationEntity: RelationEntityFactory = (relationTable, relationRow) =>
|
|
50
|
+
createEntityFromRow(meta.ctx, relationTable, relationRow);
|
|
51
|
+
|
|
52
|
+
const buildJson = (self: JsonSource<TTable>): Record<string, unknown> => {
|
|
53
|
+
const json: Record<string, unknown> = {};
|
|
54
|
+
for (const key of Object.keys(target)) {
|
|
55
|
+
json[key] = self[key];
|
|
56
|
+
}
|
|
57
|
+
for (const [relationName, wrapper] of meta.relationWrappers) {
|
|
58
|
+
if (Object.prototype.hasOwnProperty.call(json, relationName)) continue;
|
|
59
|
+
if (isRelationWrapperLoaded(wrapper)) {
|
|
60
|
+
json[relationName] = wrapper;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return json;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
Object.defineProperty(target, ENTITY_META, {
|
|
67
|
+
value: meta,
|
|
68
|
+
enumerable: false,
|
|
69
|
+
writable: false
|
|
70
|
+
});
|
|
71
71
|
|
|
72
72
|
const handler: ProxyHandler<object> = {
|
|
73
73
|
get(targetObj, prop, receiver) {
|
|
@@ -75,28 +75,28 @@ export const createEntityProxy = <
|
|
|
75
75
|
return meta;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
if (prop === '$load') {
|
|
79
|
-
return async (relationName: RelationKey<TTable>) => {
|
|
80
|
-
const wrapper = getRelationWrapper(meta, relationName, receiver, createRelationEntity);
|
|
81
|
-
if (wrapper && typeof wrapper.load === 'function') {
|
|
82
|
-
return wrapper.load();
|
|
83
|
-
}
|
|
84
|
-
return undefined;
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (prop === 'toJSON') {
|
|
89
|
-
if (prop in targetObj) {
|
|
90
|
-
return Reflect.get(targetObj, prop, receiver);
|
|
91
|
-
}
|
|
92
|
-
return () => buildJson(receiver as JsonSource<TTable>);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (typeof prop === 'string' && table.relations[prop]) {
|
|
96
|
-
return getRelationWrapper(meta, prop as RelationKey<TTable>, receiver, createRelationEntity);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return Reflect.get(targetObj, prop, receiver);
|
|
78
|
+
if (prop === '$load') {
|
|
79
|
+
return async (relationName: RelationKey<TTable>) => {
|
|
80
|
+
const wrapper = getRelationWrapper(meta, relationName, receiver, createRelationEntity);
|
|
81
|
+
if (wrapper && typeof wrapper.load === 'function') {
|
|
82
|
+
return wrapper.load();
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (prop === 'toJSON') {
|
|
89
|
+
if (prop in targetObj) {
|
|
90
|
+
return Reflect.get(targetObj, prop, receiver);
|
|
91
|
+
}
|
|
92
|
+
return () => buildJson(receiver as JsonSource<TTable>);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (typeof prop === 'string' && table.relations[prop]) {
|
|
96
|
+
return getRelationWrapper(meta, prop as RelationKey<TTable>, receiver, createRelationEntity);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return Reflect.get(targetObj, prop, receiver);
|
|
100
100
|
},
|
|
101
101
|
|
|
102
102
|
set(targetObj, prop, value, receiver) {
|
|
@@ -104,14 +104,14 @@ export const createEntityProxy = <
|
|
|
104
104
|
if (typeof prop === 'string' && table.columns[prop]) {
|
|
105
105
|
ctx.markDirty(receiver);
|
|
106
106
|
}
|
|
107
|
-
return result;
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
const proxy = new Proxy(target, handler) as EntityInstance<TTable>;
|
|
112
|
-
populateHydrationCache(proxy, row, meta);
|
|
113
|
-
return proxy;
|
|
114
|
-
};
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const proxy = new Proxy(target, handler) as EntityInstance<TTable>;
|
|
112
|
+
populateHydrationCache(proxy, row, meta);
|
|
113
|
+
return proxy;
|
|
114
|
+
};
|
|
115
115
|
|
|
116
116
|
/**
|
|
117
117
|
* Creates an entity instance from a database row.
|
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, PrimaryKey } from './entity-context.js';
|
|
21
|
+
import type { EntityContext, PrimaryKey } from './entity-context.js';
|
|
22
22
|
import {
|
|
23
23
|
DomainEvent,
|
|
24
24
|
OrmDomainEvent,
|
|
@@ -28,9 +28,9 @@ import {
|
|
|
28
28
|
TrackedEntity
|
|
29
29
|
} from './runtime-types.js';
|
|
30
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';
|
|
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.
|
|
@@ -53,7 +53,7 @@ export interface OrmInterceptor {
|
|
|
53
53
|
* Options for creating an OrmSession instance.
|
|
54
54
|
* @template E - The domain event type
|
|
55
55
|
*/
|
|
56
|
-
export interface OrmSessionOptions<E extends DomainEvent = OrmDomainEvent> {
|
|
56
|
+
export interface OrmSessionOptions<E extends DomainEvent = OrmDomainEvent> {
|
|
57
57
|
/** The ORM instance */
|
|
58
58
|
orm: Orm<E>;
|
|
59
59
|
/** The database executor */
|
|
@@ -63,21 +63,21 @@ export interface OrmSessionOptions<E extends DomainEvent = OrmDomainEvent> {
|
|
|
63
63
|
/** Optional interceptors for flush lifecycle hooks */
|
|
64
64
|
interceptors?: OrmInterceptor[];
|
|
65
65
|
/** Optional domain event handlers */
|
|
66
|
-
domainEventHandlers?: InitialHandlers<E, OrmSession<E>>;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export interface SaveGraphSessionOptions extends SaveGraphOptions {
|
|
70
|
-
/** Wrap the save operation in a transaction (default: true). */
|
|
71
|
-
transactional?: boolean;
|
|
72
|
-
/** Flush after saveGraph when not transactional (default: false). */
|
|
73
|
-
flush?: boolean;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* ORM Session that manages entity lifecycle, identity mapping, and database operations.
|
|
78
|
-
* @template E - The domain event type
|
|
79
|
-
*/
|
|
80
|
-
export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements EntityContext {
|
|
66
|
+
domainEventHandlers?: InitialHandlers<E, OrmSession<E>>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface SaveGraphSessionOptions extends SaveGraphOptions {
|
|
70
|
+
/** Wrap the save operation in a transaction (default: true). */
|
|
71
|
+
transactional?: boolean;
|
|
72
|
+
/** Flush after saveGraph when not transactional (default: false). */
|
|
73
|
+
flush?: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* ORM Session that manages entity lifecycle, identity mapping, and database operations.
|
|
78
|
+
* @template E - The domain event type
|
|
79
|
+
*/
|
|
80
|
+
export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements EntityContext {
|
|
81
81
|
/** The ORM instance */
|
|
82
82
|
readonly orm: Orm<E>;
|
|
83
83
|
/** The database executor */
|
|
@@ -88,11 +88,11 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
|
|
|
88
88
|
readonly unitOfWork: UnitOfWork;
|
|
89
89
|
/** The domain event bus */
|
|
90
90
|
readonly domainEvents: DomainEventBus<E, OrmSession<E>>;
|
|
91
|
-
/** The relation change processor */
|
|
92
|
-
readonly relationChanges: RelationChangeProcessor;
|
|
93
|
-
|
|
94
|
-
private readonly interceptors: OrmInterceptor[];
|
|
95
|
-
private saveGraphDefaults?: SaveGraphSessionOptions;
|
|
91
|
+
/** The relation change processor */
|
|
92
|
+
readonly relationChanges: RelationChangeProcessor;
|
|
93
|
+
|
|
94
|
+
private readonly interceptors: OrmInterceptor[];
|
|
95
|
+
private saveGraphDefaults?: SaveGraphSessionOptions;
|
|
96
96
|
|
|
97
97
|
/**
|
|
98
98
|
* Creates a new OrmSession instance.
|
|
@@ -150,9 +150,9 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
|
|
|
150
150
|
* @param pk - The primary key value
|
|
151
151
|
* @returns The entity or undefined if not found
|
|
152
152
|
*/
|
|
153
|
-
getEntity(table: TableDef, pk: PrimaryKey): object | undefined {
|
|
154
|
-
return this.unitOfWork.getEntity(table, pk);
|
|
155
|
-
}
|
|
153
|
+
getEntity(table: TableDef, pk: PrimaryKey): object | undefined {
|
|
154
|
+
return this.unitOfWork.getEntity(table, pk);
|
|
155
|
+
}
|
|
156
156
|
|
|
157
157
|
/**
|
|
158
158
|
* Sets an entity in the identity map.
|
|
@@ -160,9 +160,9 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
|
|
|
160
160
|
* @param pk - The primary key value
|
|
161
161
|
* @param entity - The entity instance
|
|
162
162
|
*/
|
|
163
|
-
setEntity(table: TableDef, pk: PrimaryKey, entity: object): void {
|
|
164
|
-
this.unitOfWork.setEntity(table, pk, entity);
|
|
165
|
-
}
|
|
163
|
+
setEntity(table: TableDef, pk: PrimaryKey, entity: object): void {
|
|
164
|
+
this.unitOfWork.setEntity(table, pk, entity);
|
|
165
|
+
}
|
|
166
166
|
|
|
167
167
|
/**
|
|
168
168
|
* Tracks a new entity.
|
|
@@ -170,9 +170,9 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
|
|
|
170
170
|
* @param entity - The entity instance
|
|
171
171
|
* @param pk - Optional primary key value
|
|
172
172
|
*/
|
|
173
|
-
trackNew(table: TableDef, entity: object, pk?: PrimaryKey): void {
|
|
174
|
-
this.unitOfWork.trackNew(table, entity, pk);
|
|
175
|
-
}
|
|
173
|
+
trackNew(table: TableDef, entity: object, pk?: PrimaryKey): void {
|
|
174
|
+
this.unitOfWork.trackNew(table, entity, pk);
|
|
175
|
+
}
|
|
176
176
|
|
|
177
177
|
/**
|
|
178
178
|
* Tracks a managed entity.
|
|
@@ -180,25 +180,25 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
|
|
|
180
180
|
* @param pk - The primary key value
|
|
181
181
|
* @param entity - The entity instance
|
|
182
182
|
*/
|
|
183
|
-
trackManaged(table: TableDef, pk: PrimaryKey, entity: object): void {
|
|
184
|
-
this.unitOfWork.trackManaged(table, pk, entity);
|
|
185
|
-
}
|
|
183
|
+
trackManaged(table: TableDef, pk: PrimaryKey, entity: object): void {
|
|
184
|
+
this.unitOfWork.trackManaged(table, pk, entity);
|
|
185
|
+
}
|
|
186
186
|
|
|
187
187
|
/**
|
|
188
188
|
* Marks an entity as dirty (modified).
|
|
189
189
|
* @param entity - The entity to mark as dirty
|
|
190
190
|
*/
|
|
191
|
-
markDirty(entity: object): void {
|
|
192
|
-
this.unitOfWork.markDirty(entity);
|
|
193
|
-
}
|
|
191
|
+
markDirty(entity: object): void {
|
|
192
|
+
this.unitOfWork.markDirty(entity);
|
|
193
|
+
}
|
|
194
194
|
|
|
195
195
|
/**
|
|
196
196
|
* Marks an entity as removed.
|
|
197
197
|
* @param entity - The entity to mark as removed
|
|
198
198
|
*/
|
|
199
|
-
markRemoved(entity: object): void {
|
|
200
|
-
this.unitOfWork.markRemoved(entity);
|
|
201
|
-
}
|
|
199
|
+
markRemoved(entity: object): void {
|
|
200
|
+
this.unitOfWork.markRemoved(entity);
|
|
201
|
+
}
|
|
202
202
|
|
|
203
203
|
/**
|
|
204
204
|
* Registers a relation change.
|
|
@@ -244,22 +244,22 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
|
|
|
244
244
|
* @param type - The event type
|
|
245
245
|
* @param handler - The event handler
|
|
246
246
|
*/
|
|
247
|
-
registerDomainEventHandler<TType extends E['type']>(
|
|
248
|
-
type: TType,
|
|
249
|
-
handler: DomainEventHandler<Extract<E, { type: TType }>, OrmSession<E>>
|
|
250
|
-
): void {
|
|
251
|
-
this.domainEvents.on(type, handler);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Sets default options applied to all saveGraph calls for this session.
|
|
256
|
-
* Per-call options override these defaults.
|
|
257
|
-
* @param defaults - Default saveGraph options for the session
|
|
258
|
-
*/
|
|
259
|
-
withSaveGraphDefaults(defaults: SaveGraphSessionOptions): this {
|
|
260
|
-
this.saveGraphDefaults = { ...defaults };
|
|
261
|
-
return this;
|
|
262
|
-
}
|
|
247
|
+
registerDomainEventHandler<TType extends E['type']>(
|
|
248
|
+
type: TType,
|
|
249
|
+
handler: DomainEventHandler<Extract<E, { type: TType }>, OrmSession<E>>
|
|
250
|
+
): void {
|
|
251
|
+
this.domainEvents.on(type, handler);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Sets default options applied to all saveGraph calls for this session.
|
|
256
|
+
* Per-call options override these defaults.
|
|
257
|
+
* @param defaults - Default saveGraph options for the session
|
|
258
|
+
*/
|
|
259
|
+
withSaveGraphDefaults(defaults: SaveGraphSessionOptions): this {
|
|
260
|
+
this.saveGraphDefaults = { ...defaults };
|
|
261
|
+
return this;
|
|
262
|
+
}
|
|
263
263
|
|
|
264
264
|
/**
|
|
265
265
|
* Finds an entity by its primary key.
|
|
@@ -323,85 +323,85 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
|
|
|
323
323
|
* @param options - Graph save options
|
|
324
324
|
* @returns The root entity instance
|
|
325
325
|
*/
|
|
326
|
-
async saveGraph<TCtor extends EntityConstructor<object>>(
|
|
327
|
-
entityClass: TCtor,
|
|
328
|
-
payload: SaveGraphInputPayload<InstanceType<TCtor>>,
|
|
329
|
-
options?: SaveGraphSessionOptions
|
|
330
|
-
): Promise<InstanceType<TCtor>>;
|
|
331
|
-
async saveGraph<TCtor extends EntityConstructor<object>>(
|
|
332
|
-
entityClass: TCtor,
|
|
333
|
-
payload: Record<string, unknown>,
|
|
334
|
-
options?: SaveGraphSessionOptions
|
|
335
|
-
): Promise<InstanceType<TCtor>> {
|
|
336
|
-
const resolved = this.resolveSaveGraphOptions(options);
|
|
337
|
-
const { transactional = true, flush = false, ...graphOptions } = resolved;
|
|
338
|
-
const execute = () => saveGraphInternal(this, entityClass, payload, graphOptions);
|
|
339
|
-
if (!transactional) {
|
|
340
|
-
const result = await execute();
|
|
341
|
-
if (flush) {
|
|
342
|
-
await this.flush();
|
|
343
|
-
}
|
|
344
|
-
return result;
|
|
345
|
-
}
|
|
346
|
-
return this.transaction(() => execute());
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
/**
|
|
350
|
-
* Saves an entity graph and flushes immediately (defaults to transactional: false).
|
|
351
|
-
* @param entityClass - Root entity constructor
|
|
352
|
-
* @param payload - DTO payload containing column values and nested relations
|
|
353
|
-
* @param options - Graph save options
|
|
354
|
-
* @returns The root entity instance
|
|
355
|
-
*/
|
|
356
|
-
async saveGraphAndFlush<TCtor extends EntityConstructor<object>>(
|
|
357
|
-
entityClass: TCtor,
|
|
358
|
-
payload: SaveGraphInputPayload<InstanceType<TCtor>>,
|
|
359
|
-
options?: SaveGraphSessionOptions
|
|
360
|
-
): Promise<InstanceType<TCtor>> {
|
|
361
|
-
const merged = { ...(options ?? {}), flush: true, transactional: options?.transactional ?? false };
|
|
362
|
-
return this.saveGraph(entityClass, payload, merged);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Updates an existing entity graph (requires a primary key in the payload).
|
|
367
|
-
* @param entityClass - Root entity constructor
|
|
368
|
-
* @param payload - DTO payload containing column values and nested relations
|
|
369
|
-
* @param options - Graph save options
|
|
370
|
-
* @returns The root entity instance or null if not found
|
|
371
|
-
*/
|
|
372
|
-
async updateGraph<TCtor extends EntityConstructor<object>>(
|
|
373
|
-
entityClass: TCtor,
|
|
374
|
-
payload: SaveGraphInputPayload<InstanceType<TCtor>>,
|
|
375
|
-
options?: SaveGraphSessionOptions
|
|
376
|
-
): Promise<InstanceType<TCtor> | null> {
|
|
377
|
-
const table = getTableDefFromEntity(entityClass);
|
|
378
|
-
if (!table) {
|
|
379
|
-
throw new Error('Entity metadata has not been bootstrapped');
|
|
380
|
-
}
|
|
381
|
-
const primaryKey = findPrimaryKey(table);
|
|
382
|
-
const pkValue = (payload as Record<string, unknown>)[primaryKey];
|
|
383
|
-
if (pkValue === undefined || pkValue === null) {
|
|
384
|
-
throw new Error(`updateGraph requires a primary key value for "${primaryKey}"`);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
const resolved = this.resolveSaveGraphOptions(options);
|
|
388
|
-
const { transactional = true, flush = false, ...graphOptions } = resolved;
|
|
389
|
-
const execute = async (): Promise<InstanceType<TCtor> | null> => {
|
|
390
|
-
const tracked = this.getEntity(table, pkValue as PrimaryKey) as InstanceType<TCtor> | undefined;
|
|
391
|
-
const existing = tracked ?? await this.find(entityClass, pkValue);
|
|
392
|
-
if (!existing) return null;
|
|
393
|
-
return saveGraphInternal(this, entityClass, payload, graphOptions);
|
|
394
|
-
};
|
|
395
|
-
|
|
396
|
-
if (!transactional) {
|
|
397
|
-
const result = await execute();
|
|
398
|
-
if (result && flush) {
|
|
399
|
-
await this.flush();
|
|
400
|
-
}
|
|
401
|
-
return result;
|
|
402
|
-
}
|
|
403
|
-
return this.transaction(() => execute());
|
|
404
|
-
}
|
|
326
|
+
async saveGraph<TCtor extends EntityConstructor<object>>(
|
|
327
|
+
entityClass: TCtor,
|
|
328
|
+
payload: SaveGraphInputPayload<InstanceType<TCtor>>,
|
|
329
|
+
options?: SaveGraphSessionOptions
|
|
330
|
+
): Promise<InstanceType<TCtor>>;
|
|
331
|
+
async saveGraph<TCtor extends EntityConstructor<object>>(
|
|
332
|
+
entityClass: TCtor,
|
|
333
|
+
payload: Record<string, unknown>,
|
|
334
|
+
options?: SaveGraphSessionOptions
|
|
335
|
+
): Promise<InstanceType<TCtor>> {
|
|
336
|
+
const resolved = this.resolveSaveGraphOptions(options);
|
|
337
|
+
const { transactional = true, flush = false, ...graphOptions } = resolved;
|
|
338
|
+
const execute = () => saveGraphInternal(this, entityClass, payload, graphOptions);
|
|
339
|
+
if (!transactional) {
|
|
340
|
+
const result = await execute();
|
|
341
|
+
if (flush) {
|
|
342
|
+
await this.flush();
|
|
343
|
+
}
|
|
344
|
+
return result;
|
|
345
|
+
}
|
|
346
|
+
return this.transaction(() => execute());
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Saves an entity graph and flushes immediately (defaults to transactional: false).
|
|
351
|
+
* @param entityClass - Root entity constructor
|
|
352
|
+
* @param payload - DTO payload containing column values and nested relations
|
|
353
|
+
* @param options - Graph save options
|
|
354
|
+
* @returns The root entity instance
|
|
355
|
+
*/
|
|
356
|
+
async saveGraphAndFlush<TCtor extends EntityConstructor<object>>(
|
|
357
|
+
entityClass: TCtor,
|
|
358
|
+
payload: SaveGraphInputPayload<InstanceType<TCtor>>,
|
|
359
|
+
options?: SaveGraphSessionOptions
|
|
360
|
+
): Promise<InstanceType<TCtor>> {
|
|
361
|
+
const merged = { ...(options ?? {}), flush: true, transactional: options?.transactional ?? false };
|
|
362
|
+
return this.saveGraph(entityClass, payload, merged);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Updates an existing entity graph (requires a primary key in the payload).
|
|
367
|
+
* @param entityClass - Root entity constructor
|
|
368
|
+
* @param payload - DTO payload containing column values and nested relations
|
|
369
|
+
* @param options - Graph save options
|
|
370
|
+
* @returns The root entity instance or null if not found
|
|
371
|
+
*/
|
|
372
|
+
async updateGraph<TCtor extends EntityConstructor<object>>(
|
|
373
|
+
entityClass: TCtor,
|
|
374
|
+
payload: SaveGraphInputPayload<InstanceType<TCtor>>,
|
|
375
|
+
options?: SaveGraphSessionOptions
|
|
376
|
+
): Promise<InstanceType<TCtor> | null> {
|
|
377
|
+
const table = getTableDefFromEntity(entityClass);
|
|
378
|
+
if (!table) {
|
|
379
|
+
throw new Error('Entity metadata has not been bootstrapped');
|
|
380
|
+
}
|
|
381
|
+
const primaryKey = findPrimaryKey(table);
|
|
382
|
+
const pkValue = (payload as Record<string, unknown>)[primaryKey];
|
|
383
|
+
if (pkValue === undefined || pkValue === null) {
|
|
384
|
+
throw new Error(`updateGraph requires a primary key value for "${primaryKey}"`);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const resolved = this.resolveSaveGraphOptions(options);
|
|
388
|
+
const { transactional = true, flush = false, ...graphOptions } = resolved;
|
|
389
|
+
const execute = async (): Promise<InstanceType<TCtor> | null> => {
|
|
390
|
+
const tracked = this.getEntity(table, pkValue as PrimaryKey) as InstanceType<TCtor> | undefined;
|
|
391
|
+
const existing = tracked ?? await this.find(entityClass, pkValue);
|
|
392
|
+
if (!existing) return null;
|
|
393
|
+
return saveGraphInternal(this, entityClass, payload, graphOptions);
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
if (!transactional) {
|
|
397
|
+
const result = await execute();
|
|
398
|
+
if (result && flush) {
|
|
399
|
+
await this.flush();
|
|
400
|
+
}
|
|
401
|
+
return result;
|
|
402
|
+
}
|
|
403
|
+
return this.transaction(() => execute());
|
|
404
|
+
}
|
|
405
405
|
|
|
406
406
|
/**
|
|
407
407
|
* Persists an entity (either inserts or updates).
|
|
@@ -420,7 +420,7 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
|
|
|
420
420
|
const primaryKey = findPrimaryKey(table);
|
|
421
421
|
const pkValue = (entity as Record<string, unknown>)[primaryKey];
|
|
422
422
|
if (pkValue !== undefined && pkValue !== null) {
|
|
423
|
-
this.trackManaged(table, pkValue as PrimaryKey, entity);
|
|
423
|
+
this.trackManaged(table, pkValue as PrimaryKey, entity);
|
|
424
424
|
} else {
|
|
425
425
|
this.trackNew(table, entity);
|
|
426
426
|
}
|
|
@@ -476,7 +476,7 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
|
|
|
476
476
|
* @returns The result of the function
|
|
477
477
|
* @throws If the transaction fails
|
|
478
478
|
*/
|
|
479
|
-
async transaction<T>(fn: (session: OrmSession<E>) => Promise<T>): Promise<T> {
|
|
479
|
+
async transaction<T>(fn: (session: OrmSession<E>) => Promise<T>): Promise<T> {
|
|
480
480
|
// If the executor can't do transactions, just run and commit once.
|
|
481
481
|
if (!this.executor.capabilities.transactions) {
|
|
482
482
|
const result = await fn(this);
|
|
@@ -524,25 +524,25 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
|
|
|
524
524
|
* Gets the hydration context.
|
|
525
525
|
* @returns The hydration context
|
|
526
526
|
*/
|
|
527
|
-
getHydrationContext(): HydrationContext<E> {
|
|
528
|
-
return {
|
|
529
|
-
identityMap: this.identityMap,
|
|
530
|
-
unitOfWork: this.unitOfWork,
|
|
531
|
-
domainEvents: this.domainEvents,
|
|
532
|
-
relationChanges: this.relationChanges,
|
|
533
|
-
entityContext: this
|
|
534
|
-
};
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
/**
|
|
538
|
-
* Merges session defaults with per-call saveGraph options.
|
|
539
|
-
* @param options - Per-call saveGraph options
|
|
540
|
-
* @returns Combined options with per-call values taking precedence
|
|
541
|
-
*/
|
|
542
|
-
private resolveSaveGraphOptions(options?: SaveGraphSessionOptions): SaveGraphSessionOptions {
|
|
543
|
-
return { ...(this.saveGraphDefaults ?? {}), ...(options ?? {}) };
|
|
544
|
-
}
|
|
545
|
-
}
|
|
527
|
+
getHydrationContext(): HydrationContext<E> {
|
|
528
|
+
return {
|
|
529
|
+
identityMap: this.identityMap,
|
|
530
|
+
unitOfWork: this.unitOfWork,
|
|
531
|
+
domainEvents: this.domainEvents,
|
|
532
|
+
relationChanges: this.relationChanges,
|
|
533
|
+
entityContext: this
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Merges session defaults with per-call saveGraph options.
|
|
539
|
+
* @param options - Per-call saveGraph options
|
|
540
|
+
* @returns Combined options with per-call values taking precedence
|
|
541
|
+
*/
|
|
542
|
+
private resolveSaveGraphOptions(options?: SaveGraphSessionOptions): SaveGraphSessionOptions {
|
|
543
|
+
return { ...(this.saveGraphDefaults ?? {}), ...(options ?? {}) };
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
546
|
|
|
547
547
|
const buildRelationChangeEntry = (
|
|
548
548
|
root: unknown,
|
|
@@ -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 as object);
|
|
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 as object);
|
|
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 as object);
|
|
157
|
+
this.unitOfWork.markRemoved(entry.change.entity as object);
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
}
|
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: object;
|
|
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 */
|