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.
Files changed (63) hide show
  1. package/README.md +64 -61
  2. package/dist/decorators/index.cjs +490 -175
  3. package/dist/decorators/index.cjs.map +1 -1
  4. package/dist/decorators/index.d.cts +1 -5
  5. package/dist/decorators/index.d.ts +1 -5
  6. package/dist/decorators/index.js +490 -175
  7. package/dist/decorators/index.js.map +1 -1
  8. package/dist/index.cjs +1044 -483
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +67 -15
  11. package/dist/index.d.ts +67 -15
  12. package/dist/index.js +1033 -482
  13. package/dist/index.js.map +1 -1
  14. package/dist/{select-Bkv8g8u_.d.cts → select-BPCn6MOH.d.cts} +486 -32
  15. package/dist/{select-Bkv8g8u_.d.ts → select-BPCn6MOH.d.ts} +486 -32
  16. package/package.json +2 -1
  17. package/src/codegen/naming-strategy.ts +64 -0
  18. package/src/codegen/typescript.ts +48 -53
  19. package/src/core/ast/aggregate-functions.ts +50 -4
  20. package/src/core/ast/expression-builders.ts +22 -15
  21. package/src/core/ast/expression-nodes.ts +6 -0
  22. package/src/core/ddl/introspect/functions/postgres.ts +2 -6
  23. package/src/core/ddl/schema-generator.ts +3 -2
  24. package/src/core/ddl/schema-introspect.ts +1 -1
  25. package/src/core/dialect/abstract.ts +40 -8
  26. package/src/core/dialect/mssql/functions.ts +24 -15
  27. package/src/core/dialect/postgres/functions.ts +33 -24
  28. package/src/core/dialect/sqlite/functions.ts +19 -12
  29. package/src/core/functions/datetime.ts +2 -1
  30. package/src/core/functions/numeric.ts +2 -1
  31. package/src/core/functions/standard-strategy.ts +52 -12
  32. package/src/core/functions/text.ts +2 -1
  33. package/src/core/functions/types.ts +8 -8
  34. package/src/decorators/column.ts +13 -4
  35. package/src/index.ts +13 -5
  36. package/src/orm/domain-event-bus.ts +43 -25
  37. package/src/orm/entity-context.ts +30 -0
  38. package/src/orm/entity-meta.ts +42 -2
  39. package/src/orm/entity-metadata.ts +1 -6
  40. package/src/orm/entity.ts +88 -88
  41. package/src/orm/execute.ts +42 -25
  42. package/src/orm/execution-context.ts +18 -0
  43. package/src/orm/hydration-context.ts +16 -0
  44. package/src/orm/identity-map.ts +4 -0
  45. package/src/orm/interceptor-pipeline.ts +29 -0
  46. package/src/orm/lazy-batch.ts +6 -6
  47. package/src/orm/orm-session.ts +245 -0
  48. package/src/orm/orm.ts +58 -0
  49. package/src/orm/query-logger.ts +15 -0
  50. package/src/orm/relation-change-processor.ts +5 -1
  51. package/src/orm/relations/belongs-to.ts +45 -44
  52. package/src/orm/relations/has-many.ts +44 -43
  53. package/src/orm/relations/has-one.ts +140 -139
  54. package/src/orm/relations/many-to-many.ts +46 -45
  55. package/src/orm/runtime-types.ts +60 -2
  56. package/src/orm/transaction-runner.ts +7 -0
  57. package/src/orm/unit-of-work.ts +7 -1
  58. package/src/query-builder/insert-query-state.ts +13 -3
  59. package/src/query-builder/select-helpers.ts +50 -0
  60. package/src/query-builder/select.ts +616 -18
  61. package/src/query-builder/update-query-state.ts +31 -9
  62. package/src/schema/types.ts +16 -6
  63. package/src/orm/orm-context.ts +0 -159
@@ -1,32 +1,33 @@
1
1
  import { HasManyCollection } from '../../schema/types.js';
2
- import { OrmContext, RelationKey } from '../orm-context.js';
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: OrmContext,
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 { OrmContext, RelationKey } from '../orm-context.js';
3
- import { HasOneRelation } from '../../schema/relation.js';
4
- import { TableDef } from '../../schema/table.js';
5
- import { EntityMeta, getHydrationRecord, hasEntityMeta } from '../entity-meta.js';
6
-
7
- type Row = 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 DefaultHasOneReference<TChild> implements HasOneReference<TChild> {
23
- private loaded = false;
24
- private current: TChild | null = null;
25
-
26
- constructor(
27
- private readonly ctx: OrmContext,
28
- private readonly meta: EntityMeta<any>,
29
- private readonly root: any,
30
- private readonly relationName: string,
31
- private readonly relation: HasOneRelation,
32
- private readonly rootTable: TableDef,
33
- private readonly loader: () => Promise<Map<string, Row>>,
34
- private readonly createEntity: (row: Row) => TChild,
35
- private readonly localKey: string
36
- ) {
37
- hideInternal(this, [
38
- 'ctx',
39
- 'meta',
40
- 'root',
41
- 'relationName',
42
- 'relation',
43
- 'rootTable',
44
- 'loader',
45
- 'createEntity',
46
- 'localKey'
47
- ]);
48
- this.populateFromHydrationCache();
49
- }
50
-
51
- async load(): Promise<TChild | null> {
52
- if (this.loaded) return this.current;
53
- const map = await this.loader();
54
- const keyValue = this.root[this.localKey];
55
- if (keyValue === undefined || keyValue === null) {
56
- this.loaded = true;
57
- return this.current;
58
- }
59
- const row = map.get(toKey(keyValue));
60
- this.current = row ? this.createEntity(row) : null;
61
- this.loaded = true;
62
- return this.current;
63
- }
64
-
65
- get(): TChild | null {
66
- return this.current;
67
- }
68
-
69
- set(data: Partial<TChild> | TChild | null): TChild | null {
70
- if (data === null) {
71
- return this.detachCurrent();
72
- }
73
-
74
- const entity = hasEntityMeta(data) ? (data as TChild) : this.createEntity(data as Row);
75
- if (this.current && this.current !== entity) {
76
- this.ctx.registerRelationChange(
77
- this.root,
78
- this.relationKey,
79
- this.rootTable,
80
- this.relationName,
81
- this.relation,
82
- { kind: 'remove', entity: this.current }
83
- );
84
- }
85
-
86
- this.assignForeignKey(entity);
87
- this.current = entity;
88
- this.loaded = true;
89
-
90
- this.ctx.registerRelationChange(
91
- this.root,
92
- this.relationKey,
93
- this.rootTable,
94
- this.relationName,
95
- this.relation,
96
- { kind: 'attach', entity }
97
- );
98
-
99
- return entity;
100
- }
101
-
102
- toJSON(): TChild | null {
103
- return this.current;
104
- }
105
-
106
- private detachCurrent(): TChild | null {
107
- const previous = this.current;
108
- if (!previous) return null;
109
- this.current = null;
110
- this.loaded = true;
111
- this.ctx.registerRelationChange(
112
- this.root,
113
- this.relationKey,
114
- this.rootTable,
115
- this.relationName,
116
- this.relation,
117
- { kind: 'remove', entity: previous }
118
- );
119
- return null;
120
- }
121
-
122
- private assignForeignKey(entity: TChild): void {
123
- const keyValue = this.root[this.localKey];
124
- (entity as Row)[this.relation.foreignKey] = keyValue;
125
- }
126
-
127
- private get relationKey(): RelationKey {
128
- return `${this.rootTable.name}.${this.relationName}`;
129
- }
130
-
131
- private populateFromHydrationCache(): void {
132
- const keyValue = this.root[this.localKey];
133
- if (keyValue === undefined || keyValue === null) return;
134
- const row = getHydrationRecord(this.meta, this.relationName, keyValue);
135
- if (!row) return;
136
- this.current = this.createEntity(row);
137
- this.loaded = true;
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 { OrmContext, RelationKey } from '../orm-context.js';
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: OrmContext,
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
+ }
@@ -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
- export interface HasDomainEvents {
38
- domainEvents?: any[];
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();
@@ -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;