metal-orm 1.0.64 → 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.
@@ -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: unknown): unknown | undefined {
146
- return this.unitOfWork.getEntity(table, pk as string | number);
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: unknown, entity: unknown): void {
156
- this.unitOfWork.setEntity(table, pk as string | number, entity);
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: unknown, pk?: unknown): void {
166
- this.unitOfWork.trackNew(table, entity, pk as string | number);
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: unknown, entity: unknown): void {
176
- this.unitOfWork.trackManaged(table, pk as string | number, entity);
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: unknown): void {
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: unknown): void {
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,
@@ -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: unknown;
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 */
@@ -1,16 +1,16 @@
1
- import type {
2
- EntityInstance,
3
- HasManyCollection,
4
- HasOneReference,
5
- BelongsToReference,
6
- ManyToManyCollection
7
- } from '../schema/types.js';
8
- import { normalizeColumnType, type ColumnDef } from '../schema/column-types.js';
9
- import {
10
- RelationKinds,
11
- type BelongsToManyRelation,
12
- type BelongsToRelation,
13
- type HasManyRelation,
1
+ import type {
2
+ EntityInstance,
3
+ HasManyCollection,
4
+ HasOneReference,
5
+ BelongsToReference,
6
+ ManyToManyCollection
7
+ } from '../schema/types.js';
8
+ import { normalizeColumnType, type ColumnDef } from '../schema/column-types.js';
9
+ import {
10
+ RelationKinds,
11
+ type BelongsToManyRelation,
12
+ type BelongsToRelation,
13
+ type HasManyRelation,
14
14
  type HasOneRelation,
15
15
  type RelationDef
16
16
  } from '../schema/relation.js';
@@ -24,16 +24,16 @@ import type { OrmSession } from './orm-session.js';
24
24
  /**
25
25
  * Options for controlling the behavior of save graph operations.
26
26
  */
27
- export interface SaveGraphOptions {
28
- /** Remove existing collection members that are not present in the payload */
29
- pruneMissing?: boolean;
30
- /**
31
- * Coerce JSON-friendly input values into DB-friendly primitives.
32
- * Currently:
33
- * - Date -> ISO string (for DATE/DATETIME/TIMESTAMP/TIMESTAMPTZ columns)
34
- */
35
- coerce?: 'json';
36
- }
27
+ export interface SaveGraphOptions {
28
+ /** Remove existing collection members that are not present in the payload */
29
+ pruneMissing?: boolean;
30
+ /**
31
+ * Coerce JSON-friendly input values into DB-friendly primitives.
32
+ * Currently:
33
+ * - Date -> ISO string (for DATE/DATETIME/TIMESTAMP/TIMESTAMPTZ columns)
34
+ */
35
+ coerce?: 'json';
36
+ }
37
37
 
38
38
  /** Represents an entity object with arbitrary properties. */
39
39
 
@@ -51,58 +51,58 @@ type AnyEntity = Record<string, unknown>;
51
51
 
52
52
  */
53
53
 
54
- const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
55
-
56
- const coerceColumnValue = (
57
- table: TableDef,
58
- columnName: string,
59
- value: unknown,
60
- options: SaveGraphOptions
61
- ): unknown => {
62
- if (options.coerce !== 'json') return value;
63
- if (value === null || value === undefined) return value;
64
-
65
- const column = table.columns[columnName] as unknown as ColumnDef | undefined;
66
- if (!column) return value;
67
-
68
- const normalized = normalizeColumnType(column.type);
69
-
70
- const isDateLikeColumn =
71
- normalized === 'date' ||
72
- normalized === 'datetime' ||
73
- normalized === 'timestamp' ||
74
- normalized === 'timestamptz';
75
-
76
- if (isDateLikeColumn && value instanceof Date) {
77
- return value.toISOString();
78
- }
79
-
80
- // Future coercions can be added here based on `normalized`.
81
- return value;
82
- };
83
-
84
- const pickColumns = (table: TableDef, payload: AnyEntity, options: SaveGraphOptions): Record<string, unknown> => {
85
- const columns: Record<string, unknown> = {};
86
- for (const key of Object.keys(table.columns)) {
87
- if (payload[key] !== undefined) {
88
- columns[key] = coerceColumnValue(table, key, payload[key], options);
89
- }
90
- }
91
- return columns;
92
- };
93
-
94
- const ensureEntity = <TTable extends TableDef>(
95
- session: OrmSession,
96
- table: TTable,
97
- payload: AnyEntity,
98
- options: SaveGraphOptions
99
- ): EntityInstance<TTable> => {
100
- const pk = findPrimaryKey(table);
101
- const row = pickColumns(table, payload, options);
102
- const pkValue = payload[pk];
54
+ const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
55
+
56
+ const coerceColumnValue = (
57
+ table: TableDef,
58
+ columnName: string,
59
+ value: unknown,
60
+ options: SaveGraphOptions
61
+ ): unknown => {
62
+ if (options.coerce !== 'json') return value;
63
+ if (value === null || value === undefined) return value;
64
+
65
+ const column = table.columns[columnName] as unknown as ColumnDef | undefined;
66
+ if (!column) return value;
67
+
68
+ const normalized = normalizeColumnType(column.type);
69
+
70
+ const isDateLikeColumn =
71
+ normalized === 'date' ||
72
+ normalized === 'datetime' ||
73
+ normalized === 'timestamp' ||
74
+ normalized === 'timestamptz';
75
+
76
+ if (isDateLikeColumn && value instanceof Date) {
77
+ return value.toISOString();
78
+ }
79
+
80
+ // Future coercions can be added here based on `normalized`.
81
+ return value;
82
+ };
83
+
84
+ const pickColumns = (table: TableDef, payload: AnyEntity, options: SaveGraphOptions): Record<string, unknown> => {
85
+ const columns: Record<string, unknown> = {};
86
+ for (const key of Object.keys(table.columns)) {
87
+ if (payload[key] !== undefined) {
88
+ columns[key] = coerceColumnValue(table, key, payload[key], options);
89
+ }
90
+ }
91
+ return columns;
92
+ };
93
+
94
+ const ensureEntity = <TTable extends TableDef>(
95
+ session: OrmSession,
96
+ table: TTable,
97
+ payload: AnyEntity,
98
+ options: SaveGraphOptions
99
+ ): EntityInstance<TTable> => {
100
+ const pk = findPrimaryKey(table);
101
+ const row = pickColumns(table, payload, options);
102
+ const pkValue = payload[pk];
103
103
 
104
104
  if (pkValue !== undefined && pkValue !== null) {
105
- const tracked = session.getEntity(table, pkValue);
105
+ const tracked = session.getEntity(table, pkValue as string | number);
106
106
  if (tracked) {
107
107
  return tracked as EntityInstance<TTable>;
108
108
  }
@@ -112,16 +112,16 @@ const ensureEntity = <TTable extends TableDef>(
112
112
  }
113
113
  }
114
114
 
115
- return createEntityFromRow(session, table, row) as EntityInstance<TTable>;
116
- };
117
-
118
- const assignColumns = (table: TableDef, entity: AnyEntity, payload: AnyEntity, options: SaveGraphOptions): void => {
119
- for (const key of Object.keys(table.columns)) {
120
- if (payload[key] !== undefined) {
121
- entity[key] = coerceColumnValue(table, key, payload[key], options);
122
- }
123
- }
124
- };
115
+ return createEntityFromRow(session, table, row) as EntityInstance<TTable>;
116
+ };
117
+
118
+ const assignColumns = (table: TableDef, entity: AnyEntity, payload: AnyEntity, options: SaveGraphOptions): void => {
119
+ for (const key of Object.keys(table.columns)) {
120
+ if (payload[key] !== undefined) {
121
+ entity[key] = coerceColumnValue(table, key, payload[key], options);
122
+ }
123
+ }
124
+ };
125
125
 
126
126
  const isEntityInCollection = (items: AnyEntity[], pkName: string, entity: AnyEntity): boolean => {
127
127
  if (items.includes(entity)) return true;
@@ -157,13 +157,13 @@ const handleHasMany = async (
157
157
  const asObj = typeof item === 'object' ? (item as AnyEntity) : { [targetPk]: item };
158
158
  const pkValue = asObj[targetPk];
159
159
 
160
- const current =
161
- findInCollectionByPk(existing, targetPk, pkValue) ??
162
- (pkValue !== undefined && pkValue !== null ? session.getEntity(targetTable, pkValue) : undefined);
163
-
164
- const entity = current ?? ensureEntity(session, targetTable, asObj, options);
165
- assignColumns(targetTable, entity as AnyEntity, asObj, options);
166
- await applyGraphToEntity(session, targetTable, entity as AnyEntity, asObj, options);
160
+ const current =
161
+ findInCollectionByPk(existing, targetPk, pkValue) ??
162
+ (pkValue !== undefined && pkValue !== null ? session.getEntity(targetTable, pkValue as string | number) : undefined);
163
+
164
+ const entity = current ?? ensureEntity(session, targetTable, asObj, options);
165
+ assignColumns(targetTable, entity as AnyEntity, asObj, options);
166
+ await applyGraphToEntity(session, targetTable, entity as AnyEntity, asObj, options);
167
167
 
168
168
  if (!isEntityInCollection(collection.getItems() as unknown as AnyEntity[], targetPk, entity as unknown as AnyEntity)) {
169
169
  collection.attach(entity);
@@ -184,15 +184,15 @@ const handleHasMany = async (
184
184
  }
185
185
  };
186
186
 
187
- const handleHasOne = async (
188
- session: OrmSession,
189
- root: AnyEntity,
190
- relationName: string,
191
- relation: HasOneRelation,
192
- payload: unknown,
193
- options: SaveGraphOptions
194
- ): Promise<void> => {
195
- const ref = root[relationName] as unknown as HasOneReference<object>;
187
+ const handleHasOne = async (
188
+ session: OrmSession,
189
+ root: AnyEntity,
190
+ relationName: string,
191
+ relation: HasOneRelation,
192
+ payload: unknown,
193
+ options: SaveGraphOptions
194
+ ): Promise<void> => {
195
+ const ref = root[relationName] as unknown as HasOneReference<object>;
196
196
  if (payload === undefined) return;
197
197
  if (payload === null) {
198
198
  ref.set(null);
@@ -206,21 +206,21 @@ const handleHasOne = async (
206
206
  }
207
207
  return;
208
208
  }
209
- const attached = ref.set(payload as AnyEntity);
210
- if (attached) {
211
- await applyGraphToEntity(session, relation.target, attached as AnyEntity, payload as AnyEntity, options);
212
- }
213
- };
214
-
215
- const handleBelongsTo = async (
216
- session: OrmSession,
217
- root: AnyEntity,
218
- relationName: string,
219
- relation: BelongsToRelation,
220
- payload: unknown,
221
- options: SaveGraphOptions
222
- ): Promise<void> => {
223
- const ref = root[relationName] as unknown as BelongsToReference<object>;
209
+ const attached = ref.set(payload as AnyEntity);
210
+ if (attached) {
211
+ await applyGraphToEntity(session, relation.target, attached as AnyEntity, payload as AnyEntity, options);
212
+ }
213
+ };
214
+
215
+ const handleBelongsTo = async (
216
+ session: OrmSession,
217
+ root: AnyEntity,
218
+ relationName: string,
219
+ relation: BelongsToRelation,
220
+ payload: unknown,
221
+ options: SaveGraphOptions
222
+ ): Promise<void> => {
223
+ const ref = root[relationName] as unknown as BelongsToReference<object>;
224
224
  if (payload === undefined) return;
225
225
  if (payload === null) {
226
226
  ref.set(null);
@@ -234,11 +234,11 @@ const handleBelongsTo = async (
234
234
  }
235
235
  return;
236
236
  }
237
- const attached = ref.set(payload as AnyEntity);
238
- if (attached) {
239
- await applyGraphToEntity(session, relation.target, attached as AnyEntity, payload as AnyEntity, options);
240
- }
241
- };
237
+ const attached = ref.set(payload as AnyEntity);
238
+ if (attached) {
239
+ await applyGraphToEntity(session, relation.target, attached as AnyEntity, payload as AnyEntity, options);
240
+ }
241
+ };
242
242
 
243
243
  const handleBelongsToMany = async (
244
244
  session: OrmSession,
@@ -265,14 +265,14 @@ const handleBelongsToMany = async (
265
265
  continue;
266
266
  }
267
267
 
268
- const asObj = item as AnyEntity;
269
- const pkValue = asObj[targetPk];
270
- const entity = pkValue !== undefined && pkValue !== null
271
- ? session.getEntity(targetTable, pkValue) ?? ensureEntity(session, targetTable, asObj, options)
272
- : ensureEntity(session, targetTable, asObj, options);
273
-
274
- assignColumns(targetTable, entity as AnyEntity, asObj, options);
275
- await applyGraphToEntity(session, targetTable, entity as AnyEntity, asObj, options);
268
+ const asObj = item as AnyEntity;
269
+ const pkValue = asObj[targetPk];
270
+ const entity = pkValue !== undefined && pkValue !== null
271
+ ? session.getEntity(targetTable, pkValue as string | number) ?? ensureEntity(session, targetTable, asObj, options)
272
+ : ensureEntity(session, targetTable, asObj, options);
273
+
274
+ assignColumns(targetTable, entity as AnyEntity, asObj, options);
275
+ await applyGraphToEntity(session, targetTable, entity as AnyEntity, asObj, options);
276
276
 
277
277
  if (!isEntityInCollection(collection.getItems() as unknown as AnyEntity[], targetPk, entity as unknown as AnyEntity)) {
278
278
  collection.attach(entity);
@@ -314,36 +314,36 @@ const applyRelation = async (
314
314
  }
315
315
  };
316
316
 
317
- const applyGraphToEntity = async (
318
- session: OrmSession,
319
- table: TableDef,
320
- entity: AnyEntity,
321
- payload: AnyEntity,
322
- options: SaveGraphOptions
323
- ): Promise<void> => {
324
- assignColumns(table, entity, payload, options);
325
-
326
- for (const [relationName, relation] of Object.entries(table.relations)) {
327
- if (!(relationName in payload)) continue;
328
- await applyRelation(session, table, entity, relationName, relation as RelationDef, payload[relationName], options);
329
- }
330
- };
331
-
332
- export const saveGraph = async <TTable extends TableDef>(
333
- session: OrmSession,
334
- entityClass: EntityConstructor,
335
- payload: AnyEntity,
336
- options: SaveGraphOptions = {}
337
- ): Promise<EntityInstance<TTable>> => {
317
+ const applyGraphToEntity = async (
318
+ session: OrmSession,
319
+ table: TableDef,
320
+ entity: AnyEntity,
321
+ payload: AnyEntity,
322
+ options: SaveGraphOptions
323
+ ): Promise<void> => {
324
+ assignColumns(table, entity, payload, options);
325
+
326
+ for (const [relationName, relation] of Object.entries(table.relations)) {
327
+ if (!(relationName in payload)) continue;
328
+ await applyRelation(session, table, entity, relationName, relation as RelationDef, payload[relationName], options);
329
+ }
330
+ };
331
+
332
+ export const saveGraph = async <TTable extends TableDef>(
333
+ session: OrmSession,
334
+ entityClass: EntityConstructor,
335
+ payload: AnyEntity,
336
+ options: SaveGraphOptions = {}
337
+ ): Promise<EntityInstance<TTable>> => {
338
338
  const table = getTableDefFromEntity(entityClass);
339
339
  if (!table) {
340
340
  throw new Error('Entity metadata has not been bootstrapped');
341
341
  }
342
342
 
343
- const root = ensureEntity<TTable>(session, table as TTable, payload, options);
344
- await applyGraphToEntity(session, table, root as AnyEntity, payload, options);
345
- return root;
346
- };
343
+ const root = ensureEntity<TTable>(session, table as TTable, payload, options);
344
+ await applyGraphToEntity(session, table, root as AnyEntity, payload, options);
345
+ return root;
346
+ };
347
347
 
348
348
  /**
349
349
 
@@ -361,7 +361,7 @@ export const saveGraph = async <TTable extends TableDef>(
361
361
 
362
362
  */
363
363
 
364
- export const saveGraphInternal = async <TCtor extends EntityConstructor>(
364
+ export const saveGraphInternal = async <TCtor extends EntityConstructor>(
365
365
 
366
366
  session: OrmSession,
367
367
 
@@ -373,7 +373,7 @@ export const saveGraphInternal = async <TCtor extends EntityConstructor>(
373
373
 
374
374
  ): Promise<InstanceType<TCtor>> => {
375
375
 
376
- const table = getTableDefFromEntity(entityClass);
376
+ const table = getTableDefFromEntity(entityClass);
377
377
 
378
378
  if (!table) {
379
379
 
@@ -381,10 +381,10 @@ export const saveGraphInternal = async <TCtor extends EntityConstructor>(
381
381
 
382
382
  }
383
383
 
384
- const root = ensureEntity(session, table, payload, options);
385
-
386
- await applyGraphToEntity(session, table, root as AnyEntity, payload, options);
387
-
388
- return root as unknown as InstanceType<TCtor>;
389
-
390
- };
384
+ const root = ensureEntity(session, table, payload, options);
385
+
386
+ await applyGraphToEntity(session, table, root as AnyEntity, payload, options);
387
+
388
+ return root as unknown as InstanceType<TCtor>;
389
+
390
+ };