metal-orm 1.0.64 → 1.0.66
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +119 -117
- package/dist/index.d.ts +119 -117
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/ast/aggregate-functions.ts +1 -1
- package/src/core/ast/window-functions.ts +6 -6
- package/src/core/functions/array.ts +4 -4
- package/src/core/functions/control-flow.ts +6 -6
- package/src/core/functions/json.ts +6 -6
- package/src/index.ts +2 -1
- package/src/orm/entity-context.ts +9 -7
- package/src/orm/entity-relations.ts +207 -207
- package/src/orm/entity.ts +124 -124
- package/src/orm/execute.ts +166 -166
- package/src/orm/identity-map.ts +3 -2
- package/src/orm/lazy-batch/shared.ts +1 -1
- package/src/orm/orm-session.ts +54 -54
- package/src/orm/relation-change-processor.ts +3 -3
- package/src/orm/relations/has-many.ts +1 -1
- package/src/orm/runtime-types.ts +5 -5
- package/src/orm/save-graph.ts +164 -166
- package/src/orm/unit-of-work.ts +17 -14
- package/src/query-builder/insert-query-state.ts +156 -155
- package/src/query-builder/insert.ts +5 -2
- package/src/query-builder/select.ts +112 -111
- package/src/schema/column-types.ts +14 -14
- package/src/schema/table.ts +39 -31
- package/src/schema/types.ts +54 -54
package/src/orm/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';
|
|
@@ -20,25 +20,23 @@ import { createEntityFromRow } from './entity.js';
|
|
|
20
20
|
import type { EntityConstructor } from './entity-metadata.js';
|
|
21
21
|
import { getTableDefFromEntity } from '../decorators/bootstrap.js';
|
|
22
22
|
import type { OrmSession } from './orm-session.js';
|
|
23
|
+
import type { PrimaryKey } from './entity-context.js';
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Options for controlling the behavior of save graph operations.
|
|
26
27
|
*/
|
|
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
|
-
}
|
|
28
|
+
export interface SaveGraphOptions {
|
|
29
|
+
/** Remove existing collection members that are not present in the payload */
|
|
30
|
+
pruneMissing?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Coerce JSON-friendly input values into DB-friendly primitives.
|
|
33
|
+
* Currently:
|
|
34
|
+
* - Date -> ISO string (for DATE/DATETIME/TIMESTAMP/TIMESTAMPTZ columns)
|
|
35
|
+
*/
|
|
36
|
+
coerce?: 'json';
|
|
37
|
+
}
|
|
37
38
|
|
|
38
39
|
/** Represents an entity object with arbitrary properties. */
|
|
39
|
-
|
|
40
|
-
/** Represents an entity object with arbitrary properties. */
|
|
41
|
-
|
|
42
40
|
type AnyEntity = Record<string, unknown>;
|
|
43
41
|
|
|
44
42
|
/**
|
|
@@ -51,58 +49,58 @@ type AnyEntity = Record<string, unknown>;
|
|
|
51
49
|
|
|
52
50
|
*/
|
|
53
51
|
|
|
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];
|
|
52
|
+
const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
|
|
53
|
+
|
|
54
|
+
const coerceColumnValue = (
|
|
55
|
+
table: TableDef,
|
|
56
|
+
columnName: string,
|
|
57
|
+
value: unknown,
|
|
58
|
+
options: SaveGraphOptions
|
|
59
|
+
): unknown => {
|
|
60
|
+
if (options.coerce !== 'json') return value;
|
|
61
|
+
if (value === null || value === undefined) return value;
|
|
62
|
+
|
|
63
|
+
const column = table.columns[columnName] as unknown as ColumnDef | undefined;
|
|
64
|
+
if (!column) return value;
|
|
65
|
+
|
|
66
|
+
const normalized = normalizeColumnType(column.type);
|
|
67
|
+
|
|
68
|
+
const isDateLikeColumn =
|
|
69
|
+
normalized === 'date' ||
|
|
70
|
+
normalized === 'datetime' ||
|
|
71
|
+
normalized === 'timestamp' ||
|
|
72
|
+
normalized === 'timestamptz';
|
|
73
|
+
|
|
74
|
+
if (isDateLikeColumn && value instanceof Date) {
|
|
75
|
+
return value.toISOString();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Future coercions can be added here based on `normalized`.
|
|
79
|
+
return value;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const pickColumns = (table: TableDef, payload: AnyEntity, options: SaveGraphOptions): Record<string, unknown> => {
|
|
83
|
+
const columns: Record<string, unknown> = {};
|
|
84
|
+
for (const key of Object.keys(table.columns)) {
|
|
85
|
+
if (payload[key] !== undefined) {
|
|
86
|
+
columns[key] = coerceColumnValue(table, key, payload[key], options);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return columns;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const ensureEntity = <TTable extends TableDef>(
|
|
93
|
+
session: OrmSession,
|
|
94
|
+
table: TTable,
|
|
95
|
+
payload: AnyEntity,
|
|
96
|
+
options: SaveGraphOptions
|
|
97
|
+
): EntityInstance<TTable> => {
|
|
98
|
+
const pk = findPrimaryKey(table);
|
|
99
|
+
const row = pickColumns(table, payload, options);
|
|
100
|
+
const pkValue = payload[pk];
|
|
103
101
|
|
|
104
102
|
if (pkValue !== undefined && pkValue !== null) {
|
|
105
|
-
const tracked = session.getEntity(table, pkValue);
|
|
103
|
+
const tracked = session.getEntity(table, pkValue as PrimaryKey);
|
|
106
104
|
if (tracked) {
|
|
107
105
|
return tracked as EntityInstance<TTable>;
|
|
108
106
|
}
|
|
@@ -112,16 +110,16 @@ const ensureEntity = <TTable extends TableDef>(
|
|
|
112
110
|
}
|
|
113
111
|
}
|
|
114
112
|
|
|
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
|
-
};
|
|
113
|
+
return createEntityFromRow(session, table, row) as EntityInstance<TTable>;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const assignColumns = (table: TableDef, entity: AnyEntity, payload: AnyEntity, options: SaveGraphOptions): void => {
|
|
117
|
+
for (const key of Object.keys(table.columns)) {
|
|
118
|
+
if (payload[key] !== undefined) {
|
|
119
|
+
entity[key] = coerceColumnValue(table, key, payload[key], options);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
};
|
|
125
123
|
|
|
126
124
|
const isEntityInCollection = (items: AnyEntity[], pkName: string, entity: AnyEntity): boolean => {
|
|
127
125
|
if (items.includes(entity)) return true;
|
|
@@ -157,13 +155,13 @@ const handleHasMany = async (
|
|
|
157
155
|
const asObj = typeof item === 'object' ? (item as AnyEntity) : { [targetPk]: item };
|
|
158
156
|
const pkValue = asObj[targetPk];
|
|
159
157
|
|
|
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);
|
|
158
|
+
const current =
|
|
159
|
+
findInCollectionByPk(existing, targetPk, pkValue) ??
|
|
160
|
+
(pkValue !== undefined && pkValue !== null ? session.getEntity(targetTable, pkValue as PrimaryKey) : undefined);
|
|
161
|
+
|
|
162
|
+
const entity = current ?? ensureEntity(session, targetTable, asObj, options);
|
|
163
|
+
assignColumns(targetTable, entity as AnyEntity, asObj, options);
|
|
164
|
+
await applyGraphToEntity(session, targetTable, entity as AnyEntity, asObj, options);
|
|
167
165
|
|
|
168
166
|
if (!isEntityInCollection(collection.getItems() as unknown as AnyEntity[], targetPk, entity as unknown as AnyEntity)) {
|
|
169
167
|
collection.attach(entity);
|
|
@@ -184,15 +182,15 @@ const handleHasMany = async (
|
|
|
184
182
|
}
|
|
185
183
|
};
|
|
186
184
|
|
|
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>;
|
|
185
|
+
const handleHasOne = async (
|
|
186
|
+
session: OrmSession,
|
|
187
|
+
root: AnyEntity,
|
|
188
|
+
relationName: string,
|
|
189
|
+
relation: HasOneRelation,
|
|
190
|
+
payload: unknown,
|
|
191
|
+
options: SaveGraphOptions
|
|
192
|
+
): Promise<void> => {
|
|
193
|
+
const ref = root[relationName] as unknown as HasOneReference<object>;
|
|
196
194
|
if (payload === undefined) return;
|
|
197
195
|
if (payload === null) {
|
|
198
196
|
ref.set(null);
|
|
@@ -202,25 +200,25 @@ const handleHasOne = async (
|
|
|
202
200
|
if (typeof payload === 'number' || typeof payload === 'string') {
|
|
203
201
|
const entity = ref.set({ [pk]: payload });
|
|
204
202
|
if (entity) {
|
|
205
|
-
await applyGraphToEntity(session, relation.target, entity as AnyEntity, { [pk]: payload }, options);
|
|
203
|
+
await applyGraphToEntity(session, relation.target, entity as AnyEntity, { [pk]: payload as PrimaryKey }, options);
|
|
206
204
|
}
|
|
207
205
|
return;
|
|
208
206
|
}
|
|
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>;
|
|
207
|
+
const attached = ref.set(payload as AnyEntity);
|
|
208
|
+
if (attached) {
|
|
209
|
+
await applyGraphToEntity(session, relation.target, attached as AnyEntity, payload as AnyEntity, options);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const handleBelongsTo = async (
|
|
214
|
+
session: OrmSession,
|
|
215
|
+
root: AnyEntity,
|
|
216
|
+
relationName: string,
|
|
217
|
+
relation: BelongsToRelation,
|
|
218
|
+
payload: unknown,
|
|
219
|
+
options: SaveGraphOptions
|
|
220
|
+
): Promise<void> => {
|
|
221
|
+
const ref = root[relationName] as unknown as BelongsToReference<object>;
|
|
224
222
|
if (payload === undefined) return;
|
|
225
223
|
if (payload === null) {
|
|
226
224
|
ref.set(null);
|
|
@@ -230,15 +228,15 @@ const handleBelongsTo = async (
|
|
|
230
228
|
if (typeof payload === 'number' || typeof payload === 'string') {
|
|
231
229
|
const entity = ref.set({ [pk]: payload });
|
|
232
230
|
if (entity) {
|
|
233
|
-
await applyGraphToEntity(session, relation.target, entity as AnyEntity, { [pk]: payload }, options);
|
|
231
|
+
await applyGraphToEntity(session, relation.target, entity as AnyEntity, { [pk]: payload as PrimaryKey }, options);
|
|
234
232
|
}
|
|
235
233
|
return;
|
|
236
234
|
}
|
|
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
|
-
};
|
|
235
|
+
const attached = ref.set(payload as AnyEntity);
|
|
236
|
+
if (attached) {
|
|
237
|
+
await applyGraphToEntity(session, relation.target, attached as AnyEntity, payload as AnyEntity, options);
|
|
238
|
+
}
|
|
239
|
+
};
|
|
242
240
|
|
|
243
241
|
const handleBelongsToMany = async (
|
|
244
242
|
session: OrmSession,
|
|
@@ -265,14 +263,14 @@ const handleBelongsToMany = async (
|
|
|
265
263
|
continue;
|
|
266
264
|
}
|
|
267
265
|
|
|
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);
|
|
266
|
+
const asObj = item as AnyEntity;
|
|
267
|
+
const pkValue = asObj[targetPk];
|
|
268
|
+
const entity = pkValue !== undefined && pkValue !== null
|
|
269
|
+
? session.getEntity(targetTable, pkValue as PrimaryKey) ?? ensureEntity(session, targetTable, asObj, options)
|
|
270
|
+
: ensureEntity(session, targetTable, asObj, options);
|
|
271
|
+
|
|
272
|
+
assignColumns(targetTable, entity as AnyEntity, asObj, options);
|
|
273
|
+
await applyGraphToEntity(session, targetTable, entity as AnyEntity, asObj, options);
|
|
276
274
|
|
|
277
275
|
if (!isEntityInCollection(collection.getItems() as unknown as AnyEntity[], targetPk, entity as unknown as AnyEntity)) {
|
|
278
276
|
collection.attach(entity);
|
|
@@ -314,36 +312,36 @@ const applyRelation = async (
|
|
|
314
312
|
}
|
|
315
313
|
};
|
|
316
314
|
|
|
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>> => {
|
|
315
|
+
const applyGraphToEntity = async (
|
|
316
|
+
session: OrmSession,
|
|
317
|
+
table: TableDef,
|
|
318
|
+
entity: AnyEntity,
|
|
319
|
+
payload: AnyEntity,
|
|
320
|
+
options: SaveGraphOptions
|
|
321
|
+
): Promise<void> => {
|
|
322
|
+
assignColumns(table, entity, payload, options);
|
|
323
|
+
|
|
324
|
+
for (const [relationName, relation] of Object.entries(table.relations)) {
|
|
325
|
+
if (!(relationName in payload)) continue;
|
|
326
|
+
await applyRelation(session, table, entity, relationName, relation as RelationDef, payload[relationName], options);
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
export const saveGraph = async <TTable extends TableDef>(
|
|
331
|
+
session: OrmSession,
|
|
332
|
+
entityClass: EntityConstructor,
|
|
333
|
+
payload: AnyEntity,
|
|
334
|
+
options: SaveGraphOptions = {}
|
|
335
|
+
): Promise<EntityInstance<TTable>> => {
|
|
338
336
|
const table = getTableDefFromEntity(entityClass);
|
|
339
337
|
if (!table) {
|
|
340
338
|
throw new Error('Entity metadata has not been bootstrapped');
|
|
341
339
|
}
|
|
342
340
|
|
|
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
|
-
};
|
|
341
|
+
const root = ensureEntity<TTable>(session, table as TTable, payload, options);
|
|
342
|
+
await applyGraphToEntity(session, table, root as AnyEntity, payload, options);
|
|
343
|
+
return root;
|
|
344
|
+
};
|
|
347
345
|
|
|
348
346
|
/**
|
|
349
347
|
|
|
@@ -361,7 +359,7 @@ export const saveGraph = async <TTable extends TableDef>(
|
|
|
361
359
|
|
|
362
360
|
*/
|
|
363
361
|
|
|
364
|
-
export const saveGraphInternal = async <TCtor extends EntityConstructor>(
|
|
362
|
+
export const saveGraphInternal = async <TCtor extends EntityConstructor>(
|
|
365
363
|
|
|
366
364
|
session: OrmSession,
|
|
367
365
|
|
|
@@ -373,7 +371,7 @@ export const saveGraphInternal = async <TCtor extends EntityConstructor>(
|
|
|
373
371
|
|
|
374
372
|
): Promise<InstanceType<TCtor>> => {
|
|
375
373
|
|
|
376
|
-
const table = getTableDefFromEntity(entityClass);
|
|
374
|
+
const table = getTableDefFromEntity(entityClass);
|
|
377
375
|
|
|
378
376
|
if (!table) {
|
|
379
377
|
|
|
@@ -381,10 +379,10 @@ export const saveGraphInternal = async <TCtor extends EntityConstructor>(
|
|
|
381
379
|
|
|
382
380
|
}
|
|
383
381
|
|
|
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
|
-
};
|
|
382
|
+
const root = ensureEntity(session, table, payload, options);
|
|
383
|
+
|
|
384
|
+
await applyGraphToEntity(session, table, root as AnyEntity, payload, options);
|
|
385
|
+
|
|
386
|
+
return root as unknown as InstanceType<TCtor>;
|
|
387
|
+
|
|
388
|
+
};
|