metal-orm 1.0.65 → 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.map +1 -1
- package/dist/index.d.cts +70 -70
- package/dist/index.d.ts +70 -70
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/orm/identity-map.ts +3 -2
- package/src/orm/save-graph.ts +6 -8
- package/src/orm/unit-of-work.ts +59 -58
package/package.json
CHANGED
package/src/orm/identity-map.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { TableDef } from '../schema/table.js';
|
|
2
2
|
import type { TrackedEntity } from './runtime-types.js';
|
|
3
|
+
import type { PrimaryKey } from './entity-context.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Simple identity map for tracking entities within a session.
|
|
@@ -18,7 +19,7 @@ export class IdentityMap {
|
|
|
18
19
|
* @param pk The primary key value.
|
|
19
20
|
* @returns The entity instance if found, undefined otherwise.
|
|
20
21
|
*/
|
|
21
|
-
getEntity(table: TableDef, pk:
|
|
22
|
+
getEntity(table: TableDef, pk: PrimaryKey): object | undefined {
|
|
22
23
|
const bucket = this.buckets.get(table.name);
|
|
23
24
|
return bucket?.get(this.toIdentityKey(pk))?.entity;
|
|
24
25
|
}
|
|
@@ -54,7 +55,7 @@ export class IdentityMap {
|
|
|
54
55
|
this.buckets.clear();
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
private toIdentityKey(pk:
|
|
58
|
+
private toIdentityKey(pk: PrimaryKey): string {
|
|
58
59
|
return String(pk);
|
|
59
60
|
}
|
|
60
61
|
}
|
package/src/orm/save-graph.ts
CHANGED
|
@@ -20,6 +20,7 @@ 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.
|
|
@@ -36,9 +37,6 @@ export interface SaveGraphOptions {
|
|
|
36
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
|
/**
|
|
@@ -102,7 +100,7 @@ const ensureEntity = <TTable extends TableDef>(
|
|
|
102
100
|
const pkValue = payload[pk];
|
|
103
101
|
|
|
104
102
|
if (pkValue !== undefined && pkValue !== null) {
|
|
105
|
-
const tracked = session.getEntity(table, pkValue as
|
|
103
|
+
const tracked = session.getEntity(table, pkValue as PrimaryKey);
|
|
106
104
|
if (tracked) {
|
|
107
105
|
return tracked as EntityInstance<TTable>;
|
|
108
106
|
}
|
|
@@ -159,7 +157,7 @@ const handleHasMany = async (
|
|
|
159
157
|
|
|
160
158
|
const current =
|
|
161
159
|
findInCollectionByPk(existing, targetPk, pkValue) ??
|
|
162
|
-
(pkValue !== undefined && pkValue !== null ? session.getEntity(targetTable, pkValue as
|
|
160
|
+
(pkValue !== undefined && pkValue !== null ? session.getEntity(targetTable, pkValue as PrimaryKey) : undefined);
|
|
163
161
|
|
|
164
162
|
const entity = current ?? ensureEntity(session, targetTable, asObj, options);
|
|
165
163
|
assignColumns(targetTable, entity as AnyEntity, asObj, options);
|
|
@@ -202,7 +200,7 @@ 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
|
}
|
|
@@ -230,7 +228,7 @@ 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
|
}
|
|
@@ -268,7 +266,7 @@ const handleBelongsToMany = async (
|
|
|
268
266
|
const asObj = item as AnyEntity;
|
|
269
267
|
const pkValue = asObj[targetPk];
|
|
270
268
|
const entity = pkValue !== undefined && pkValue !== null
|
|
271
|
-
? session.getEntity(targetTable, pkValue as
|
|
269
|
+
? session.getEntity(targetTable, pkValue as PrimaryKey) ?? ensureEntity(session, targetTable, asObj, options)
|
|
272
270
|
: ensureEntity(session, targetTable, asObj, options);
|
|
273
271
|
|
|
274
272
|
assignColumns(targetTable, entity as AnyEntity, asObj, options);
|
package/src/orm/unit-of-work.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ColumnNode, eq } from '../core/ast/expression.js';
|
|
2
|
-
import type { ValueOperandInput } from '../core/ast/expression.js';
|
|
1
|
+
import { ColumnNode, eq } from '../core/ast/expression.js';
|
|
2
|
+
import type { ValueOperandInput } from '../core/ast/expression.js';
|
|
3
3
|
import type { Dialect, CompiledQuery } from '../core/dialect/abstract.js';
|
|
4
4
|
import { InsertQueryBuilder } from '../query-builder/insert.js';
|
|
5
5
|
import { UpdateQueryBuilder } from '../query-builder/update.js';
|
|
@@ -10,12 +10,13 @@ import type { DbExecutor, QueryResult } from '../core/execution/db-executor.js';
|
|
|
10
10
|
import { IdentityMap } from './identity-map.js';
|
|
11
11
|
import { EntityStatus } from './runtime-types.js';
|
|
12
12
|
import type { TrackedEntity } from './runtime-types.js';
|
|
13
|
+
import type { PrimaryKey } from './entity-context.js';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Unit of Work pattern implementation for tracking entity changes.
|
|
16
17
|
*/
|
|
17
|
-
export class UnitOfWork {
|
|
18
|
-
private readonly trackedEntities = new Map<object, TrackedEntity>();
|
|
18
|
+
export class UnitOfWork {
|
|
19
|
+
private readonly trackedEntities = new Map<object, TrackedEntity>();
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Creates a new UnitOfWork instance.
|
|
@@ -52,9 +53,9 @@ export class UnitOfWork {
|
|
|
52
53
|
* @param pk - The primary key value
|
|
53
54
|
* @returns The entity or undefined if not found
|
|
54
55
|
*/
|
|
55
|
-
getEntity(table: TableDef, pk:
|
|
56
|
-
return this.identityMap.getEntity(table, pk);
|
|
57
|
-
}
|
|
56
|
+
getEntity(table: TableDef, pk: PrimaryKey): object | undefined {
|
|
57
|
+
return this.identityMap.getEntity(table, pk);
|
|
58
|
+
}
|
|
58
59
|
|
|
59
60
|
/**
|
|
60
61
|
* Gets all tracked entities for a specific table.
|
|
@@ -70,9 +71,9 @@ export class UnitOfWork {
|
|
|
70
71
|
* @param entity - The entity to find
|
|
71
72
|
* @returns The tracked entity or undefined if not found
|
|
72
73
|
*/
|
|
73
|
-
findTracked(entity: object): TrackedEntity | undefined {
|
|
74
|
-
return this.trackedEntities.get(entity);
|
|
75
|
-
}
|
|
74
|
+
findTracked(entity: object): TrackedEntity | undefined {
|
|
75
|
+
return this.trackedEntities.get(entity);
|
|
76
|
+
}
|
|
76
77
|
|
|
77
78
|
/**
|
|
78
79
|
* Sets an entity in the identity map.
|
|
@@ -80,9 +81,9 @@ export class UnitOfWork {
|
|
|
80
81
|
* @param pk - The primary key value
|
|
81
82
|
* @param entity - The entity instance
|
|
82
83
|
*/
|
|
83
|
-
setEntity(table: TableDef, pk:
|
|
84
|
-
if (pk === null || pk === undefined) return;
|
|
85
|
-
let tracked = this.trackedEntities.get(entity);
|
|
84
|
+
setEntity(table: TableDef, pk: PrimaryKey, entity: object): void {
|
|
85
|
+
if (pk === null || pk === undefined) return;
|
|
86
|
+
let tracked = this.trackedEntities.get(entity);
|
|
86
87
|
if (!tracked) {
|
|
87
88
|
tracked = {
|
|
88
89
|
table,
|
|
@@ -105,7 +106,7 @@ export class UnitOfWork {
|
|
|
105
106
|
* @param entity - The entity instance
|
|
106
107
|
* @param pk - Optional primary key value
|
|
107
108
|
*/
|
|
108
|
-
trackNew(table: TableDef, entity: object, pk?:
|
|
109
|
+
trackNew(table: TableDef, entity: object, pk?: PrimaryKey): void {
|
|
109
110
|
const tracked: TrackedEntity = {
|
|
110
111
|
table,
|
|
111
112
|
entity,
|
|
@@ -125,7 +126,7 @@ export class UnitOfWork {
|
|
|
125
126
|
* @param pk - The primary key value
|
|
126
127
|
* @param entity - The entity instance
|
|
127
128
|
*/
|
|
128
|
-
trackManaged(table: TableDef, pk:
|
|
129
|
+
trackManaged(table: TableDef, pk: PrimaryKey, entity: object): void {
|
|
129
130
|
const tracked: TrackedEntity = {
|
|
130
131
|
table,
|
|
131
132
|
entity,
|
|
@@ -141,22 +142,22 @@ export class UnitOfWork {
|
|
|
141
142
|
* Marks an entity as dirty (modified).
|
|
142
143
|
* @param entity - The entity to mark as dirty
|
|
143
144
|
*/
|
|
144
|
-
markDirty(entity: object): void {
|
|
145
|
-
const tracked = this.trackedEntities.get(entity);
|
|
146
|
-
if (!tracked) return;
|
|
147
|
-
if (tracked.status === EntityStatus.New || tracked.status === EntityStatus.Removed) return;
|
|
148
|
-
tracked.status = EntityStatus.Dirty;
|
|
149
|
-
}
|
|
145
|
+
markDirty(entity: object): void {
|
|
146
|
+
const tracked = this.trackedEntities.get(entity);
|
|
147
|
+
if (!tracked) return;
|
|
148
|
+
if (tracked.status === EntityStatus.New || tracked.status === EntityStatus.Removed) return;
|
|
149
|
+
tracked.status = EntityStatus.Dirty;
|
|
150
|
+
}
|
|
150
151
|
|
|
151
152
|
/**
|
|
152
153
|
* Marks an entity as removed.
|
|
153
154
|
* @param entity - The entity to mark as removed
|
|
154
155
|
*/
|
|
155
|
-
markRemoved(entity: object): void {
|
|
156
|
-
const tracked = this.trackedEntities.get(entity);
|
|
157
|
-
if (!tracked) return;
|
|
158
|
-
tracked.status = EntityStatus.Removed;
|
|
159
|
-
}
|
|
156
|
+
markRemoved(entity: object): void {
|
|
157
|
+
const tracked = this.trackedEntities.get(entity);
|
|
158
|
+
if (!tracked) return;
|
|
159
|
+
tracked.status = EntityStatus.Removed;
|
|
160
|
+
}
|
|
160
161
|
|
|
161
162
|
/**
|
|
162
163
|
* Flushes pending changes to the database.
|
|
@@ -195,8 +196,8 @@ export class UnitOfWork {
|
|
|
195
196
|
private async flushInsert(tracked: TrackedEntity): Promise<void> {
|
|
196
197
|
await this.runHook(tracked.table.hooks?.beforeInsert, tracked);
|
|
197
198
|
|
|
198
|
-
const payload = this.extractColumns(tracked.table, tracked.entity as Record<string, unknown>);
|
|
199
|
-
let builder = new InsertQueryBuilder(tracked.table).values(payload as Record<string, ValueOperandInput>);
|
|
199
|
+
const payload = this.extractColumns(tracked.table, tracked.entity as Record<string, unknown>);
|
|
200
|
+
let builder = new InsertQueryBuilder(tracked.table).values(payload as Record<string, ValueOperandInput>);
|
|
200
201
|
if (this.dialect.supportsReturning()) {
|
|
201
202
|
builder = builder.returning(...this.getReturningColumns(tracked.table));
|
|
202
203
|
}
|
|
@@ -288,16 +289,16 @@ export class UnitOfWork {
|
|
|
288
289
|
* @param tracked - The tracked entity
|
|
289
290
|
* @returns Object with changed column values
|
|
290
291
|
*/
|
|
291
|
-
private computeChanges(tracked: TrackedEntity): Record<string, unknown> {
|
|
292
|
-
const snapshot = tracked.original ?? {};
|
|
293
|
-
const changes: Record<string, unknown> = {};
|
|
294
|
-
for (const column of Object.keys(tracked.table.columns)) {
|
|
295
|
-
const current = (tracked.entity as Record<string, unknown>)[column];
|
|
296
|
-
if (snapshot[column] !== current) {
|
|
297
|
-
changes[column] = current;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
return changes;
|
|
292
|
+
private computeChanges(tracked: TrackedEntity): Record<string, unknown> {
|
|
293
|
+
const snapshot = tracked.original ?? {};
|
|
294
|
+
const changes: Record<string, unknown> = {};
|
|
295
|
+
for (const column of Object.keys(tracked.table.columns)) {
|
|
296
|
+
const current = (tracked.entity as Record<string, unknown>)[column];
|
|
297
|
+
if (snapshot[column] !== current) {
|
|
298
|
+
changes[column] = current;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return changes;
|
|
301
302
|
}
|
|
302
303
|
|
|
303
304
|
/**
|
|
@@ -343,18 +344,18 @@ export class UnitOfWork {
|
|
|
343
344
|
* @param tracked - The tracked entity
|
|
344
345
|
* @param results - Query results
|
|
345
346
|
*/
|
|
346
|
-
private applyReturningResults(tracked: TrackedEntity, results: QueryResult[]): void {
|
|
347
|
-
if (!this.dialect.supportsReturning()) return;
|
|
348
|
-
const first = results[0];
|
|
349
|
-
if (!first || first.values.length === 0) return;
|
|
350
|
-
|
|
351
|
-
const row = first.values[0];
|
|
352
|
-
for (let i = 0; i < first.columns.length; i++) {
|
|
353
|
-
const columnName = this.normalizeColumnName(first.columns[i]);
|
|
354
|
-
if (!(columnName in tracked.table.columns)) continue;
|
|
355
|
-
(tracked.entity as Record<string, unknown>)[columnName] = row[i];
|
|
356
|
-
}
|
|
357
|
-
}
|
|
347
|
+
private applyReturningResults(tracked: TrackedEntity, results: QueryResult[]): void {
|
|
348
|
+
if (!this.dialect.supportsReturning()) return;
|
|
349
|
+
const first = results[0];
|
|
350
|
+
if (!first || first.values.length === 0) return;
|
|
351
|
+
|
|
352
|
+
const row = first.values[0];
|
|
353
|
+
for (let i = 0; i < first.columns.length; i++) {
|
|
354
|
+
const columnName = this.normalizeColumnName(first.columns[i]);
|
|
355
|
+
if (!(columnName in tracked.table.columns)) continue;
|
|
356
|
+
(tracked.entity as Record<string, unknown>)[columnName] = row[i];
|
|
357
|
+
}
|
|
358
|
+
}
|
|
358
359
|
|
|
359
360
|
/**
|
|
360
361
|
* Normalizes a column name by removing quotes and table prefixes.
|
|
@@ -395,11 +396,11 @@ export class UnitOfWork {
|
|
|
395
396
|
* @param tracked - The tracked entity
|
|
396
397
|
* @returns Primary key value or null
|
|
397
398
|
*/
|
|
398
|
-
private getPrimaryKeyValue(tracked: TrackedEntity):
|
|
399
|
-
const key = findPrimaryKey(tracked.table);
|
|
400
|
-
const val = (tracked.entity as Record<string, unknown>)[key];
|
|
401
|
-
if (val === undefined || val === null) return null;
|
|
402
|
-
if (typeof val !== 'string' && typeof val !== 'number') return null;
|
|
403
|
-
return val;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
399
|
+
private getPrimaryKeyValue(tracked: TrackedEntity): PrimaryKey | null {
|
|
400
|
+
const key = findPrimaryKey(tracked.table);
|
|
401
|
+
const val = (tracked.entity as Record<string, unknown>)[key];
|
|
402
|
+
if (val === undefined || val === null) return null;
|
|
403
|
+
if (typeof val !== 'string' && typeof val !== 'number') return null;
|
|
404
|
+
return val as PrimaryKey;
|
|
405
|
+
}
|
|
406
|
+
}
|