metal-orm 1.0.15 → 1.0.17
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/README.md +64 -61
- package/dist/decorators/index.cjs +490 -175
- package/dist/decorators/index.cjs.map +1 -1
- package/dist/decorators/index.d.cts +1 -5
- package/dist/decorators/index.d.ts +1 -5
- package/dist/decorators/index.js +490 -175
- package/dist/decorators/index.js.map +1 -1
- package/dist/index.cjs +1044 -483
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +67 -15
- package/dist/index.d.ts +67 -15
- package/dist/index.js +1033 -482
- package/dist/index.js.map +1 -1
- package/dist/{select-Bkv8g8u_.d.cts → select-BPCn6MOH.d.cts} +486 -32
- package/dist/{select-Bkv8g8u_.d.ts → select-BPCn6MOH.d.ts} +486 -32
- package/package.json +2 -1
- package/src/codegen/naming-strategy.ts +64 -0
- package/src/codegen/typescript.ts +48 -53
- package/src/core/ast/aggregate-functions.ts +50 -4
- package/src/core/ast/expression-builders.ts +22 -15
- package/src/core/ast/expression-nodes.ts +6 -0
- package/src/core/ddl/introspect/functions/postgres.ts +2 -6
- package/src/core/ddl/schema-generator.ts +3 -2
- package/src/core/ddl/schema-introspect.ts +1 -1
- package/src/core/dialect/abstract.ts +40 -8
- package/src/core/dialect/mssql/functions.ts +24 -15
- package/src/core/dialect/postgres/functions.ts +33 -24
- package/src/core/dialect/sqlite/functions.ts +19 -12
- package/src/core/functions/datetime.ts +2 -1
- package/src/core/functions/numeric.ts +2 -1
- package/src/core/functions/standard-strategy.ts +52 -12
- package/src/core/functions/text.ts +2 -1
- package/src/core/functions/types.ts +8 -8
- package/src/decorators/column.ts +13 -4
- package/src/index.ts +13 -5
- package/src/orm/domain-event-bus.ts +43 -25
- package/src/orm/entity-context.ts +30 -0
- package/src/orm/entity-meta.ts +42 -2
- package/src/orm/entity-metadata.ts +1 -6
- package/src/orm/entity.ts +88 -88
- package/src/orm/execute.ts +42 -25
- package/src/orm/execution-context.ts +18 -0
- package/src/orm/hydration-context.ts +16 -0
- package/src/orm/identity-map.ts +4 -0
- package/src/orm/interceptor-pipeline.ts +29 -0
- package/src/orm/lazy-batch.ts +6 -6
- package/src/orm/orm-session.ts +245 -0
- package/src/orm/orm.ts +58 -0
- package/src/orm/query-logger.ts +15 -0
- package/src/orm/relation-change-processor.ts +5 -1
- package/src/orm/relations/belongs-to.ts +45 -44
- package/src/orm/relations/has-many.ts +44 -43
- package/src/orm/relations/has-one.ts +140 -139
- package/src/orm/relations/many-to-many.ts +46 -45
- package/src/orm/runtime-types.ts +60 -2
- package/src/orm/transaction-runner.ts +7 -0
- package/src/orm/unit-of-work.ts +7 -1
- package/src/query-builder/insert-query-state.ts +13 -3
- package/src/query-builder/select-helpers.ts +50 -0
- package/src/query-builder/select.ts +616 -18
- package/src/query-builder/update-query-state.ts +31 -9
- package/src/schema/types.ts +16 -6
- package/src/orm/orm-context.ts +0 -159
|
@@ -1,32 +1,33 @@
|
|
|
1
1
|
import { HasManyCollection } from '../../schema/types.js';
|
|
2
|
-
import {
|
|
2
|
+
import { EntityContext } from '../entity-context.js';
|
|
3
|
+
import { RelationKey } from '../runtime-types.js';
|
|
3
4
|
import { HasManyRelation } from '../../schema/relation.js';
|
|
4
5
|
import { TableDef } from '../../schema/table.js';
|
|
5
6
|
import { EntityMeta, getHydrationRows } from '../entity-meta.js';
|
|
6
7
|
|
|
7
|
-
type Rows = Record<string, any>[];
|
|
8
|
-
|
|
9
|
-
const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
|
|
10
|
-
|
|
11
|
-
const hideInternal = (obj: any, keys: string[]): void => {
|
|
12
|
-
for (const key of keys) {
|
|
13
|
-
Object.defineProperty(obj, key, {
|
|
14
|
-
value: obj[key],
|
|
15
|
-
writable: false,
|
|
16
|
-
configurable: false,
|
|
17
|
-
enumerable: false
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export class DefaultHasManyCollection<TChild> implements HasManyCollection<TChild> {
|
|
23
|
-
private loaded = false;
|
|
24
|
-
private items: TChild[] = [];
|
|
25
|
-
private readonly added = new Set<TChild>();
|
|
26
|
-
private readonly removed = new Set<TChild>();
|
|
8
|
+
type Rows = Record<string, any>[];
|
|
9
|
+
|
|
10
|
+
const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
|
|
11
|
+
|
|
12
|
+
const hideInternal = (obj: any, keys: string[]): void => {
|
|
13
|
+
for (const key of keys) {
|
|
14
|
+
Object.defineProperty(obj, key, {
|
|
15
|
+
value: obj[key],
|
|
16
|
+
writable: false,
|
|
17
|
+
configurable: false,
|
|
18
|
+
enumerable: false
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export class DefaultHasManyCollection<TChild> implements HasManyCollection<TChild> {
|
|
24
|
+
private loaded = false;
|
|
25
|
+
private items: TChild[] = [];
|
|
26
|
+
private readonly added = new Set<TChild>();
|
|
27
|
+
private readonly removed = new Set<TChild>();
|
|
27
28
|
|
|
28
29
|
constructor(
|
|
29
|
-
private readonly ctx:
|
|
30
|
+
private readonly ctx: EntityContext,
|
|
30
31
|
private readonly meta: EntityMeta<any>,
|
|
31
32
|
private readonly root: any,
|
|
32
33
|
private readonly relationName: string,
|
|
@@ -34,14 +35,14 @@ export class DefaultHasManyCollection<TChild> implements HasManyCollection<TChil
|
|
|
34
35
|
private readonly rootTable: TableDef,
|
|
35
36
|
private readonly loader: () => Promise<Map<string, Rows>>,
|
|
36
37
|
private readonly createEntity: (row: Record<string, any>) => TChild,
|
|
37
|
-
private readonly localKey: string
|
|
38
|
-
) {
|
|
39
|
-
hideInternal(this, ['ctx', 'meta', 'root', 'relationName', 'relation', 'rootTable', 'loader', 'createEntity', 'localKey']);
|
|
40
|
-
this.hydrateFromCache();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async load(): Promise<TChild[]> {
|
|
44
|
-
if (this.loaded) return this.items;
|
|
38
|
+
private readonly localKey: string
|
|
39
|
+
) {
|
|
40
|
+
hideInternal(this, ['ctx', 'meta', 'root', 'relationName', 'relation', 'rootTable', 'loader', 'createEntity', 'localKey']);
|
|
41
|
+
this.hydrateFromCache();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async load(): Promise<TChild[]> {
|
|
45
|
+
if (this.loaded) return this.items;
|
|
45
46
|
const map = await this.loader();
|
|
46
47
|
const key = toKey(this.root[this.localKey]);
|
|
47
48
|
const rows = map.get(key) ?? [];
|
|
@@ -112,16 +113,16 @@ export class DefaultHasManyCollection<TChild> implements HasManyCollection<TChil
|
|
|
112
113
|
return `${this.rootTable.name}.${this.relationName}`;
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
private hydrateFromCache(): void {
|
|
116
|
-
const keyValue = this.root[this.localKey];
|
|
117
|
-
if (keyValue === undefined || keyValue === null) return;
|
|
118
|
-
const rows = getHydrationRows(this.meta, this.relationName, keyValue);
|
|
119
|
-
if (!rows?.length) return;
|
|
120
|
-
this.items = rows.map(row => this.createEntity(row));
|
|
121
|
-
this.loaded = true;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
toJSON(): TChild[] {
|
|
125
|
-
return this.items;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
116
|
+
private hydrateFromCache(): void {
|
|
117
|
+
const keyValue = this.root[this.localKey];
|
|
118
|
+
if (keyValue === undefined || keyValue === null) return;
|
|
119
|
+
const rows = getHydrationRows(this.meta, this.relationName, keyValue);
|
|
120
|
+
if (!rows?.length) return;
|
|
121
|
+
this.items = rows.map(row => this.createEntity(row));
|
|
122
|
+
this.loaded = true;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
toJSON(): TChild[] {
|
|
126
|
+
return this.items;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -1,139 +1,140 @@
|
|
|
1
|
-
import { HasOneReference } from '../../schema/types.js';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
private
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
private readonly
|
|
29
|
-
private readonly
|
|
30
|
-
private readonly
|
|
31
|
-
private readonly
|
|
32
|
-
private readonly
|
|
33
|
-
private readonly
|
|
34
|
-
private readonly
|
|
35
|
-
private readonly
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
'
|
|
40
|
-
'
|
|
41
|
-
'
|
|
42
|
-
'
|
|
43
|
-
'
|
|
44
|
-
'
|
|
45
|
-
'
|
|
46
|
-
'
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
this.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
this.
|
|
79
|
-
this.
|
|
80
|
-
this.
|
|
81
|
-
this.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
this.
|
|
88
|
-
this.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
this.
|
|
93
|
-
this.
|
|
94
|
-
this.
|
|
95
|
-
this.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
this.
|
|
111
|
-
this.
|
|
112
|
-
|
|
113
|
-
this.
|
|
114
|
-
this.
|
|
115
|
-
this.
|
|
116
|
-
this.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
this.
|
|
138
|
-
|
|
139
|
-
}
|
|
1
|
+
import { HasOneReference } from '../../schema/types.js';
|
|
2
|
+
import { EntityContext } from '../entity-context.js';
|
|
3
|
+
import { RelationKey } from '../runtime-types.js';
|
|
4
|
+
import { HasOneRelation } from '../../schema/relation.js';
|
|
5
|
+
import { TableDef } from '../../schema/table.js';
|
|
6
|
+
import { EntityMeta, getHydrationRecord, hasEntityMeta } from '../entity-meta.js';
|
|
7
|
+
|
|
8
|
+
type Row = Record<string, any>;
|
|
9
|
+
|
|
10
|
+
const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
|
|
11
|
+
|
|
12
|
+
const hideInternal = (obj: any, keys: string[]): void => {
|
|
13
|
+
for (const key of keys) {
|
|
14
|
+
Object.defineProperty(obj, key, {
|
|
15
|
+
value: obj[key],
|
|
16
|
+
writable: false,
|
|
17
|
+
configurable: false,
|
|
18
|
+
enumerable: false
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export class DefaultHasOneReference<TChild> implements HasOneReference<TChild> {
|
|
24
|
+
private loaded = false;
|
|
25
|
+
private current: TChild | null = null;
|
|
26
|
+
|
|
27
|
+
constructor(
|
|
28
|
+
private readonly ctx: EntityContext,
|
|
29
|
+
private readonly meta: EntityMeta<any>,
|
|
30
|
+
private readonly root: any,
|
|
31
|
+
private readonly relationName: string,
|
|
32
|
+
private readonly relation: HasOneRelation,
|
|
33
|
+
private readonly rootTable: TableDef,
|
|
34
|
+
private readonly loader: () => Promise<Map<string, Row>>,
|
|
35
|
+
private readonly createEntity: (row: Row) => TChild,
|
|
36
|
+
private readonly localKey: string
|
|
37
|
+
) {
|
|
38
|
+
hideInternal(this, [
|
|
39
|
+
'ctx',
|
|
40
|
+
'meta',
|
|
41
|
+
'root',
|
|
42
|
+
'relationName',
|
|
43
|
+
'relation',
|
|
44
|
+
'rootTable',
|
|
45
|
+
'loader',
|
|
46
|
+
'createEntity',
|
|
47
|
+
'localKey'
|
|
48
|
+
]);
|
|
49
|
+
this.populateFromHydrationCache();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async load(): Promise<TChild | null> {
|
|
53
|
+
if (this.loaded) return this.current;
|
|
54
|
+
const map = await this.loader();
|
|
55
|
+
const keyValue = this.root[this.localKey];
|
|
56
|
+
if (keyValue === undefined || keyValue === null) {
|
|
57
|
+
this.loaded = true;
|
|
58
|
+
return this.current;
|
|
59
|
+
}
|
|
60
|
+
const row = map.get(toKey(keyValue));
|
|
61
|
+
this.current = row ? this.createEntity(row) : null;
|
|
62
|
+
this.loaded = true;
|
|
63
|
+
return this.current;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get(): TChild | null {
|
|
67
|
+
return this.current;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
set(data: Partial<TChild> | TChild | null): TChild | null {
|
|
71
|
+
if (data === null) {
|
|
72
|
+
return this.detachCurrent();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const entity = hasEntityMeta(data) ? (data as TChild) : this.createEntity(data as Row);
|
|
76
|
+
if (this.current && this.current !== entity) {
|
|
77
|
+
this.ctx.registerRelationChange(
|
|
78
|
+
this.root,
|
|
79
|
+
this.relationKey,
|
|
80
|
+
this.rootTable,
|
|
81
|
+
this.relationName,
|
|
82
|
+
this.relation,
|
|
83
|
+
{ kind: 'remove', entity: this.current }
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.assignForeignKey(entity);
|
|
88
|
+
this.current = entity;
|
|
89
|
+
this.loaded = true;
|
|
90
|
+
|
|
91
|
+
this.ctx.registerRelationChange(
|
|
92
|
+
this.root,
|
|
93
|
+
this.relationKey,
|
|
94
|
+
this.rootTable,
|
|
95
|
+
this.relationName,
|
|
96
|
+
this.relation,
|
|
97
|
+
{ kind: 'attach', entity }
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
return entity;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
toJSON(): TChild | null {
|
|
104
|
+
return this.current;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private detachCurrent(): TChild | null {
|
|
108
|
+
const previous = this.current;
|
|
109
|
+
if (!previous) return null;
|
|
110
|
+
this.current = null;
|
|
111
|
+
this.loaded = true;
|
|
112
|
+
this.ctx.registerRelationChange(
|
|
113
|
+
this.root,
|
|
114
|
+
this.relationKey,
|
|
115
|
+
this.rootTable,
|
|
116
|
+
this.relationName,
|
|
117
|
+
this.relation,
|
|
118
|
+
{ kind: 'remove', entity: previous }
|
|
119
|
+
);
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private assignForeignKey(entity: TChild): void {
|
|
124
|
+
const keyValue = this.root[this.localKey];
|
|
125
|
+
(entity as Row)[this.relation.foreignKey] = keyValue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private get relationKey(): RelationKey {
|
|
129
|
+
return `${this.rootTable.name}.${this.relationName}`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private populateFromHydrationCache(): void {
|
|
133
|
+
const keyValue = this.root[this.localKey];
|
|
134
|
+
if (keyValue === undefined || keyValue === null) return;
|
|
135
|
+
const row = getHydrationRecord(this.meta, this.relationName, keyValue);
|
|
136
|
+
if (!row) return;
|
|
137
|
+
this.current = this.createEntity(row);
|
|
138
|
+
this.loaded = true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -1,46 +1,47 @@
|
|
|
1
1
|
import { ManyToManyCollection } from '../../schema/types.js';
|
|
2
|
-
import {
|
|
2
|
+
import { EntityContext } from '../entity-context.js';
|
|
3
|
+
import { RelationKey } from '../runtime-types.js';
|
|
3
4
|
import { BelongsToManyRelation } from '../../schema/relation.js';
|
|
4
5
|
import { TableDef } from '../../schema/table.js';
|
|
5
6
|
import { findPrimaryKey } from '../../query-builder/hydration-planner.js';
|
|
6
7
|
import { EntityMeta, getHydrationRows } from '../entity-meta.js';
|
|
7
8
|
|
|
8
|
-
type Rows = Record<string, any>[];
|
|
9
|
-
|
|
10
|
-
const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
|
|
11
|
-
|
|
12
|
-
const hideInternal = (obj: any, keys: string[]): void => {
|
|
13
|
-
for (const key of keys) {
|
|
14
|
-
Object.defineProperty(obj, key, {
|
|
15
|
-
value: obj[key],
|
|
16
|
-
writable: false,
|
|
17
|
-
configurable: false,
|
|
18
|
-
enumerable: false
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export class DefaultManyToManyCollection<TTarget> implements ManyToManyCollection<TTarget> {
|
|
24
|
-
private loaded = false;
|
|
25
|
-
private items: TTarget[] = [];
|
|
26
|
-
|
|
27
|
-
constructor(
|
|
28
|
-
private readonly ctx:
|
|
9
|
+
type Rows = Record<string, any>[];
|
|
10
|
+
|
|
11
|
+
const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
|
|
12
|
+
|
|
13
|
+
const hideInternal = (obj: any, keys: string[]): void => {
|
|
14
|
+
for (const key of keys) {
|
|
15
|
+
Object.defineProperty(obj, key, {
|
|
16
|
+
value: obj[key],
|
|
17
|
+
writable: false,
|
|
18
|
+
configurable: false,
|
|
19
|
+
enumerable: false
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export class DefaultManyToManyCollection<TTarget> implements ManyToManyCollection<TTarget> {
|
|
25
|
+
private loaded = false;
|
|
26
|
+
private items: TTarget[] = [];
|
|
27
|
+
|
|
28
|
+
constructor(
|
|
29
|
+
private readonly ctx: EntityContext,
|
|
29
30
|
private readonly meta: EntityMeta<any>,
|
|
30
31
|
private readonly root: any,
|
|
31
32
|
private readonly relationName: string,
|
|
32
33
|
private readonly relation: BelongsToManyRelation,
|
|
33
34
|
private readonly rootTable: TableDef,
|
|
34
35
|
private readonly loader: () => Promise<Map<string, Rows>>,
|
|
35
|
-
private readonly createEntity: (row: Record<string, any>) => TTarget,
|
|
36
|
-
private readonly localKey: string
|
|
37
|
-
) {
|
|
38
|
-
hideInternal(this, ['ctx', 'meta', 'root', 'relationName', 'relation', 'rootTable', 'loader', 'createEntity', 'localKey']);
|
|
39
|
-
this.hydrateFromCache();
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async load(): Promise<TTarget[]> {
|
|
43
|
-
if (this.loaded) return this.items;
|
|
36
|
+
private readonly createEntity: (row: Record<string, any>) => TTarget,
|
|
37
|
+
private readonly localKey: string
|
|
38
|
+
) {
|
|
39
|
+
hideInternal(this, ['ctx', 'meta', 'root', 'relationName', 'relation', 'rootTable', 'loader', 'createEntity', 'localKey']);
|
|
40
|
+
this.hydrateFromCache();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async load(): Promise<TTarget[]> {
|
|
44
|
+
if (this.loaded) return this.items;
|
|
44
45
|
const map = await this.loader();
|
|
45
46
|
const key = toKey(this.root[this.localKey]);
|
|
46
47
|
const rows = map.get(key) ?? [];
|
|
@@ -144,22 +145,22 @@ export class DefaultManyToManyCollection<TTarget> implements ManyToManyCollectio
|
|
|
144
145
|
return this.relation.targetKey || findPrimaryKey(this.relation.target);
|
|
145
146
|
}
|
|
146
147
|
|
|
147
|
-
private hydrateFromCache(): void {
|
|
148
|
-
const keyValue = this.root[this.localKey];
|
|
149
|
-
if (keyValue === undefined || keyValue === null) return;
|
|
150
|
-
const rows = getHydrationRows(this.meta, this.relationName, keyValue);
|
|
151
|
-
if (!rows?.length) return;
|
|
148
|
+
private hydrateFromCache(): void {
|
|
149
|
+
const keyValue = this.root[this.localKey];
|
|
150
|
+
if (keyValue === undefined || keyValue === null) return;
|
|
151
|
+
const rows = getHydrationRows(this.meta, this.relationName, keyValue);
|
|
152
|
+
if (!rows?.length) return;
|
|
152
153
|
this.items = rows.map(row => {
|
|
153
154
|
const entity = this.createEntity(row);
|
|
154
155
|
if ((row as any)._pivot) {
|
|
155
156
|
(entity as any)._pivot = (row as any)._pivot;
|
|
156
157
|
}
|
|
157
|
-
return entity;
|
|
158
|
-
});
|
|
159
|
-
this.loaded = true;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
toJSON(): TTarget[] {
|
|
163
|
-
return this.items;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
158
|
+
return entity;
|
|
159
|
+
});
|
|
160
|
+
this.loaded = true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
toJSON(): TTarget[] {
|
|
164
|
+
return this.items;
|
|
165
|
+
}
|
|
166
|
+
}
|
package/src/orm/runtime-types.ts
CHANGED
|
@@ -1,39 +1,97 @@
|
|
|
1
1
|
import { RelationDef } from '../schema/relation.js';
|
|
2
2
|
import { TableDef } from '../schema/table.js';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Entity status enum representing the lifecycle state of an entity
|
|
6
|
+
*/
|
|
4
7
|
export enum EntityStatus {
|
|
8
|
+
/** Entity is newly created and not yet persisted */
|
|
5
9
|
New = 'new',
|
|
10
|
+
/** Entity is managed by the ORM and synchronized with the database */
|
|
6
11
|
Managed = 'managed',
|
|
12
|
+
/** Entity has been modified but not yet persisted */
|
|
7
13
|
Dirty = 'dirty',
|
|
14
|
+
/** Entity has been marked for removal */
|
|
8
15
|
Removed = 'removed',
|
|
16
|
+
/** Entity is detached from the ORM context */
|
|
9
17
|
Detached = 'detached'
|
|
10
18
|
}
|
|
11
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Represents an entity being tracked by the ORM
|
|
22
|
+
*/
|
|
12
23
|
export interface TrackedEntity {
|
|
24
|
+
/** The table definition this entity belongs to */
|
|
13
25
|
table: TableDef;
|
|
26
|
+
/** The actual entity instance */
|
|
14
27
|
entity: any;
|
|
28
|
+
/** Primary key value of the entity */
|
|
15
29
|
pk: string | number | null;
|
|
30
|
+
/** Current status of the entity */
|
|
16
31
|
status: EntityStatus;
|
|
32
|
+
/** Original values of the entity when it was loaded */
|
|
17
33
|
original: Record<string, any> | null;
|
|
18
34
|
}
|
|
19
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Type representing a key for relation navigation
|
|
38
|
+
*/
|
|
20
39
|
export type RelationKey = string;
|
|
21
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Represents a change operation on a relation
|
|
43
|
+
* @typeParam T - Type of the related entity
|
|
44
|
+
*/
|
|
22
45
|
export type RelationChange<T> =
|
|
23
46
|
| { kind: 'add'; entity: T }
|
|
24
47
|
| { kind: 'attach'; entity: T }
|
|
25
48
|
| { kind: 'remove'; entity: T }
|
|
26
49
|
| { kind: 'detach'; entity: T };
|
|
27
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Represents a relation change entry in the unit of work
|
|
53
|
+
*/
|
|
28
54
|
export interface RelationChangeEntry {
|
|
55
|
+
/** Root entity that owns the relation */
|
|
29
56
|
root: any;
|
|
57
|
+
/** Key of the relation being changed */
|
|
30
58
|
relationKey: RelationKey;
|
|
59
|
+
/** Table definition of the root entity */
|
|
31
60
|
rootTable: TableDef;
|
|
61
|
+
/** Name of the relation */
|
|
32
62
|
relationName: string;
|
|
63
|
+
/** Relation definition */
|
|
33
64
|
relation: RelationDef;
|
|
65
|
+
/** The change being applied */
|
|
34
66
|
change: RelationChange<any>;
|
|
35
67
|
}
|
|
36
68
|
|
|
37
|
-
|
|
38
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Represents a domain event that can be emitted by entities
|
|
71
|
+
* @typeParam TType - Type of the event (string literal)
|
|
72
|
+
*/
|
|
73
|
+
export interface DomainEvent<TType extends string = string> {
|
|
74
|
+
/** Type identifier for the event */
|
|
75
|
+
readonly type: TType;
|
|
76
|
+
/** Timestamp when the event occurred */
|
|
77
|
+
readonly occurredAt?: Date;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Type representing any domain event
|
|
82
|
+
*/
|
|
83
|
+
export type AnyDomainEvent = DomainEvent<string>;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Type representing ORM-specific domain events
|
|
87
|
+
*/
|
|
88
|
+
export type OrmDomainEvent = AnyDomainEvent;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Interface for entities that can emit domain events
|
|
92
|
+
* @typeParam E - Type of domain events this entity can emit
|
|
93
|
+
*/
|
|
94
|
+
export interface HasDomainEvents<E extends DomainEvent = AnyDomainEvent> {
|
|
95
|
+
/** Array of domain events emitted by this entity */
|
|
96
|
+
domainEvents?: E[];
|
|
39
97
|
}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import type { DbExecutor } from '../core/execution/db-executor.js';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Executes a function within a database transaction
|
|
5
|
+
* @param executor - Database executor to use for transaction operations
|
|
6
|
+
* @param action - Function to execute within the transaction
|
|
7
|
+
* @returns Promise that resolves when the transaction is complete
|
|
8
|
+
* @throws Re-throws any errors that occur during the transaction (after rolling back)
|
|
9
|
+
*/
|
|
3
10
|
export const runInTransaction = async (executor: DbExecutor, action: () => Promise<void>): Promise<void> => {
|
|
4
11
|
if (!executor.beginTransaction) {
|
|
5
12
|
await action();
|
package/src/orm/unit-of-work.ts
CHANGED
|
@@ -18,7 +18,7 @@ export class UnitOfWork {
|
|
|
18
18
|
private readonly executor: DbExecutor,
|
|
19
19
|
private readonly identityMap: IdentityMap,
|
|
20
20
|
private readonly hookContext: () => unknown
|
|
21
|
-
) {}
|
|
21
|
+
) { }
|
|
22
22
|
|
|
23
23
|
get identityBuckets(): Map<string, Map<string, TrackedEntity>> {
|
|
24
24
|
return this.identityMap.bucketsMap;
|
|
@@ -117,6 +117,11 @@ export class UnitOfWork {
|
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
reset(): void {
|
|
121
|
+
this.trackedEntities.clear();
|
|
122
|
+
this.identityMap.clear();
|
|
123
|
+
}
|
|
124
|
+
|
|
120
125
|
private async flushInsert(tracked: TrackedEntity): Promise<void> {
|
|
121
126
|
await this.runHook(tracked.table.hooks?.beforeInsert, tracked);
|
|
122
127
|
|
|
@@ -210,6 +215,7 @@ export class UnitOfWork {
|
|
|
210
215
|
private extractColumns(table: TableDef, entity: any): Record<string, unknown> {
|
|
211
216
|
const payload: Record<string, unknown> = {};
|
|
212
217
|
for (const column of Object.keys(table.columns)) {
|
|
218
|
+
if (entity[column] === undefined) continue;
|
|
213
219
|
payload[column] = entity[column];
|
|
214
220
|
}
|
|
215
221
|
return payload;
|