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.
- package/dist/index.cjs +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +61 -59
- package/dist/index.d.ts +61 -59
- 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 +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/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 */
|
package/src/orm/save-graph.ts
CHANGED
|
@@ -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
|
+
};
|