@mikro-orm/core 7.0.0-dev.99 → 7.0.0-rc.1

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 (107) hide show
  1. package/EntityManager.d.ts +34 -17
  2. package/EntityManager.js +95 -103
  3. package/MikroORM.d.ts +5 -5
  4. package/MikroORM.js +25 -20
  5. package/cache/FileCacheAdapter.js +11 -3
  6. package/connections/Connection.d.ts +3 -2
  7. package/connections/Connection.js +4 -3
  8. package/drivers/DatabaseDriver.d.ts +11 -11
  9. package/drivers/DatabaseDriver.js +91 -25
  10. package/drivers/IDatabaseDriver.d.ts +50 -20
  11. package/entity/BaseEntity.d.ts +61 -1
  12. package/entity/Collection.d.ts +8 -1
  13. package/entity/Collection.js +12 -13
  14. package/entity/EntityAssigner.js +9 -9
  15. package/entity/EntityFactory.d.ts +6 -1
  16. package/entity/EntityFactory.js +40 -22
  17. package/entity/EntityHelper.d.ts +2 -2
  18. package/entity/EntityHelper.js +27 -4
  19. package/entity/EntityLoader.d.ts +5 -4
  20. package/entity/EntityLoader.js +193 -80
  21. package/entity/EntityRepository.d.ts +27 -7
  22. package/entity/EntityRepository.js +8 -2
  23. package/entity/PolymorphicRef.d.ts +12 -0
  24. package/entity/PolymorphicRef.js +18 -0
  25. package/entity/WrappedEntity.d.ts +2 -2
  26. package/entity/WrappedEntity.js +1 -1
  27. package/entity/defineEntity.d.ts +89 -50
  28. package/entity/defineEntity.js +12 -0
  29. package/entity/index.d.ts +1 -0
  30. package/entity/index.js +1 -0
  31. package/entity/utils.d.ts +6 -1
  32. package/entity/utils.js +33 -0
  33. package/entity/validators.js +2 -2
  34. package/enums.d.ts +2 -2
  35. package/enums.js +1 -0
  36. package/errors.d.ts +16 -8
  37. package/errors.js +40 -13
  38. package/hydration/ObjectHydrator.js +63 -21
  39. package/index.d.ts +1 -1
  40. package/logging/colors.d.ts +1 -1
  41. package/logging/colors.js +7 -6
  42. package/logging/inspect.js +1 -6
  43. package/metadata/EntitySchema.d.ts +43 -13
  44. package/metadata/EntitySchema.js +82 -27
  45. package/metadata/MetadataDiscovery.d.ts +60 -3
  46. package/metadata/MetadataDiscovery.js +665 -154
  47. package/metadata/MetadataProvider.js +3 -1
  48. package/metadata/MetadataStorage.d.ts +13 -6
  49. package/metadata/MetadataStorage.js +64 -19
  50. package/metadata/MetadataValidator.d.ts +32 -2
  51. package/metadata/MetadataValidator.js +196 -31
  52. package/metadata/discover-entities.js +5 -5
  53. package/metadata/types.d.ts +111 -14
  54. package/naming-strategy/AbstractNamingStrategy.d.ts +11 -3
  55. package/naming-strategy/AbstractNamingStrategy.js +12 -0
  56. package/naming-strategy/EntityCaseNamingStrategy.d.ts +3 -3
  57. package/naming-strategy/EntityCaseNamingStrategy.js +6 -5
  58. package/naming-strategy/MongoNamingStrategy.d.ts +3 -3
  59. package/naming-strategy/MongoNamingStrategy.js +6 -6
  60. package/naming-strategy/NamingStrategy.d.ts +17 -3
  61. package/naming-strategy/UnderscoreNamingStrategy.d.ts +3 -3
  62. package/naming-strategy/UnderscoreNamingStrategy.js +6 -6
  63. package/package.json +2 -2
  64. package/platforms/Platform.d.ts +4 -2
  65. package/platforms/Platform.js +5 -2
  66. package/serialization/EntitySerializer.d.ts +3 -0
  67. package/serialization/EntitySerializer.js +15 -13
  68. package/serialization/EntityTransformer.js +6 -6
  69. package/serialization/SerializationContext.d.ts +6 -6
  70. package/typings.d.ts +325 -110
  71. package/typings.js +84 -17
  72. package/unit-of-work/ChangeSet.d.ts +4 -3
  73. package/unit-of-work/ChangeSet.js +2 -3
  74. package/unit-of-work/ChangeSetComputer.d.ts +3 -6
  75. package/unit-of-work/ChangeSetComputer.js +34 -13
  76. package/unit-of-work/ChangeSetPersister.d.ts +12 -10
  77. package/unit-of-work/ChangeSetPersister.js +55 -25
  78. package/unit-of-work/CommitOrderCalculator.d.ts +12 -10
  79. package/unit-of-work/CommitOrderCalculator.js +13 -13
  80. package/unit-of-work/IdentityMap.d.ts +12 -0
  81. package/unit-of-work/IdentityMap.js +39 -1
  82. package/unit-of-work/UnitOfWork.d.ts +21 -3
  83. package/unit-of-work/UnitOfWork.js +203 -56
  84. package/utils/AbstractSchemaGenerator.js +17 -8
  85. package/utils/AsyncContext.d.ts +6 -0
  86. package/utils/AsyncContext.js +42 -0
  87. package/utils/Configuration.d.ts +52 -11
  88. package/utils/Configuration.js +12 -8
  89. package/utils/Cursor.js +21 -8
  90. package/utils/DataloaderUtils.js +13 -11
  91. package/utils/EntityComparator.d.ts +14 -7
  92. package/utils/EntityComparator.js +132 -46
  93. package/utils/QueryHelper.d.ts +16 -6
  94. package/utils/QueryHelper.js +53 -18
  95. package/utils/RawQueryFragment.d.ts +28 -23
  96. package/utils/RawQueryFragment.js +34 -56
  97. package/utils/RequestContext.js +2 -2
  98. package/utils/TransactionContext.js +2 -2
  99. package/utils/TransactionManager.js +1 -1
  100. package/utils/Utils.d.ts +7 -26
  101. package/utils/Utils.js +25 -79
  102. package/utils/clone.js +7 -21
  103. package/utils/env-vars.d.ts +4 -0
  104. package/utils/env-vars.js +13 -3
  105. package/utils/fs-utils.d.ts +21 -0
  106. package/utils/fs-utils.js +106 -11
  107. package/utils/upsert-utils.d.ts +4 -4
package/typings.js CHANGED
@@ -1,8 +1,10 @@
1
1
  import { ReferenceKind, } from './enums.js';
2
2
  import { Reference } from './entity/Reference.js';
3
3
  import { EntityHelper } from './entity/EntityHelper.js';
4
+ import { helper } from './entity/wrap.js';
4
5
  import { Utils } from './utils/Utils.js';
5
6
  import { EntityComparator } from './utils/EntityComparator.js';
7
+ import { BaseEntity } from './entity/BaseEntity.js';
6
8
  export const EntityRepositoryType = Symbol('EntityRepositoryType');
7
9
  export const PrimaryKeyProp = Symbol('PrimaryKeyProp');
8
10
  export const OptionalProps = Symbol('OptionalProps');
@@ -25,22 +27,24 @@ export class EntityMetadata {
25
27
  this.referencingProperties = [];
26
28
  this.concurrencyCheckKeys = new Set();
27
29
  Object.assign(this, meta);
28
- }
29
- addProperty(prop, sync = true) {
30
- if (prop.pivotTable && !prop.pivotEntity) {
31
- prop.pivotEntity = prop.pivotTable;
30
+ const name = meta.className ?? meta.name;
31
+ if (!this.class && name) {
32
+ const Class = this.extends === BaseEntity
33
+ ? ({ [name]: class extends BaseEntity {
34
+ } })[name]
35
+ : ({ [name]: class {
36
+ } })[name];
37
+ this.class = Class;
32
38
  }
39
+ }
40
+ addProperty(prop) {
33
41
  this.properties[prop.name] = prop;
34
42
  this.propertyOrder.set(prop.name, this.props.length);
35
- /* v8 ignore next */
36
- if (sync) {
37
- this.sync();
38
- }
43
+ this.sync();
39
44
  }
40
45
  removeProperty(name, sync = true) {
41
46
  delete this.properties[name];
42
47
  this.propertyOrder.delete(name);
43
- /* v8 ignore next */
44
48
  if (sync) {
45
49
  this.sync();
46
50
  }
@@ -60,8 +64,52 @@ export class EntityMetadata {
60
64
  getPrimaryProp() {
61
65
  return this.properties[this.primaryKeys[0]];
62
66
  }
63
- createColumnMappingObject() {
64
- return Object.values(this.properties).reduce((o, prop) => {
67
+ /**
68
+ * Creates a mapping from property names to field names.
69
+ * @param alias - Optional alias to prefix field names. Can be a string (same for all) or a function (per-property).
70
+ * When provided, also adds toString() returning the alias for backwards compatibility with formulas.
71
+ * @param toStringAlias - Optional alias to return from toString(). Defaults to `alias` when it's a string.
72
+ */
73
+ createColumnMappingObject(alias, toStringAlias) {
74
+ const resolveAlias = typeof alias === 'function' ? alias : () => alias;
75
+ const defaultAlias = toStringAlias ?? (typeof alias === 'string' ? alias : undefined);
76
+ const result = Object.values(this.properties).reduce((o, prop) => {
77
+ if (prop.fieldNames) {
78
+ const propAlias = resolveAlias(prop);
79
+ o[prop.name] = propAlias ? `${propAlias}.${prop.fieldNames[0]}` : prop.fieldNames[0];
80
+ }
81
+ return o;
82
+ }, {});
83
+ // Add toString() for backwards compatibility when alias is provided
84
+ Object.defineProperty(result, 'toString', {
85
+ value: () => defaultAlias ?? '',
86
+ enumerable: false,
87
+ });
88
+ // Wrap in Proxy to detect old formula signature usage where the first param was FormulaTable.
89
+ // If user accesses `.alias` or `.qualifiedName` (FormulaTable-only properties), warn them.
90
+ const warnedProps = new Set(['alias', 'qualifiedName']);
91
+ return new Proxy(result, {
92
+ get(target, prop, receiver) {
93
+ if (typeof prop === 'string' && warnedProps.has(prop) && !(prop in target)) {
94
+ // eslint-disable-next-line no-console
95
+ console.warn(`[MikroORM] Detected old formula callback signature. The first parameter is now 'columns', not 'table'. ` +
96
+ `Accessing '.${prop}' on the columns object will return undefined. ` +
97
+ `Update your formula: formula(cols => quote\`\${cols.propName} ...\`). See the v7 upgrade guide.`);
98
+ }
99
+ return Reflect.get(target, prop, receiver);
100
+ },
101
+ });
102
+ }
103
+ /**
104
+ * Creates a column mapping for schema callbacks (indexes, checks, generated columns).
105
+ * For TPT entities, only includes properties that belong to the current table (ownProps).
106
+ */
107
+ createSchemaColumnMappingObject() {
108
+ // For TPT entities, only include properties that belong to this entity's table
109
+ const props = this.inheritanceType === 'tpt' && this.ownProps
110
+ ? this.ownProps
111
+ : Object.values(this.properties);
112
+ return props.reduce((o, prop) => {
65
113
  if (prop.fieldNames) {
66
114
  o[prop.name] = prop.fieldNames[0];
67
115
  }
@@ -74,6 +122,9 @@ export class EntityMetadata {
74
122
  set tableName(name) {
75
123
  this.collection = name;
76
124
  }
125
+ get uniqueName() {
126
+ return this.tableName + '_' + this._id;
127
+ }
77
128
  sync(initIndexes = false, config) {
78
129
  this.root ??= this;
79
130
  const props = Object.values(this.properties).sort((a, b) => this.propertyOrder.get(a.name) - this.propertyOrder.get(b.name));
@@ -100,10 +151,19 @@ export class EntityMetadata {
100
151
  return !prop.getter && !prop.setter && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind);
101
152
  });
102
153
  this.selfReferencing = this.relations.some(prop => {
103
- return [this.className, this.root.className].includes(prop.targetMeta?.root.className ?? prop.type);
154
+ return this.root.uniqueName === prop.targetMeta?.root.uniqueName;
104
155
  });
105
156
  this.hasUniqueProps = this.uniques.length + this.uniqueProps.length > 0;
106
- this.virtual = !!this.expression;
157
+ // Normalize object-form `view` option: `view: { materialized: true, withData: false }`
158
+ // into flat metadata fields (`view: true`, `materialized: true`, `withData: false`).
159
+ if (typeof this.view === 'object') {
160
+ this.materialized = this.view.materialized;
161
+ this.withData = this.view.withData;
162
+ this.view = true;
163
+ }
164
+ // If `view` is set, this is a database view entity (not a virtual entity).
165
+ // Virtual entities evaluate expressions at query time, view entities create actual database views.
166
+ this.virtual = !!this.expression && !this.view;
107
167
  if (config) {
108
168
  for (const prop of this.props) {
109
169
  if (prop.enum && !prop.nativeEnumName && prop.items?.every(item => typeof item === 'string')) {
@@ -126,15 +186,15 @@ export class EntityMetadata {
126
186
  for (const hook of Utils.keys(this.hooks)) {
127
187
  this.hooks[hook] = Utils.removeDuplicates(this.hooks[hook]);
128
188
  }
129
- if (this.virtual) {
189
+ if (this.virtual || this.view) {
130
190
  this.readonly = true;
131
191
  }
132
192
  if (initIndexes && this.name) {
133
193
  this.props.forEach(prop => this.initIndexes(prop));
134
194
  }
135
195
  this.definedProperties = this.trackingProps.reduce((o, prop) => {
136
- const isReference = (prop.inversedBy || prop.mappedBy) && !prop.mapToPk;
137
- if (isReference) {
196
+ const hasInverse = (prop.inversedBy || prop.mappedBy) && !prop.mapToPk;
197
+ if (hasInverse) {
138
198
  // eslint-disable-next-line @typescript-eslint/no-this-alias
139
199
  const meta = this;
140
200
  o[prop.name] = {
@@ -152,7 +212,10 @@ export class EntityMetadata {
152
212
  wrapped.__data[prop.name] = Reference.wrapReference(val, prop);
153
213
  // when propagation from inside hydration, we set the FK to the entity data immediately
154
214
  if (val && hydrator.isRunning() && wrapped.__originalEntityData && prop.owner) {
155
- wrapped.__originalEntityData[prop.name] = Utils.getPrimaryKeyValues(val, prop.targetMeta, true);
215
+ const targetMeta = prop.targetMeta ?? helper(entity)?.__meta;
216
+ if (targetMeta) {
217
+ wrapped.__originalEntityData[prop.name] = Utils.getPrimaryKeyValues(val, targetMeta, true);
218
+ }
156
219
  }
157
220
  EntityHelper.propagate(meta, entity, this, prop, Reference.unwrapReference(val), old);
158
221
  },
@@ -191,4 +254,8 @@ export class EntityMetadata {
191
254
  clone() {
192
255
  return this;
193
256
  }
257
+ /** @ignore */
258
+ [Symbol.for('nodejs.util.inspect.custom')]() {
259
+ return `[${this.constructor.name}<${this.className}>]`;
260
+ }
194
261
  }
@@ -11,15 +11,16 @@ export declare class ChangeSet<T extends object> {
11
11
  getSerializedPrimaryKey(): string | null;
12
12
  }
13
13
  export interface ChangeSet<T> {
14
- name: string;
15
- rootName: string;
16
- collection: string;
14
+ meta: EntityMetadata<T>;
15
+ rootMeta: EntityMetadata<T>;
17
16
  schema?: string;
18
17
  type: ChangeSetType;
19
18
  entity: T;
20
19
  payload: EntityDictionary<T>;
21
20
  persisted: boolean;
22
21
  originalEntity?: EntityData<T>;
22
+ /** For TPT: changesets for parent tables, ordered from immediate parent to root */
23
+ tptChangeSets?: ChangeSet<T>[];
23
24
  }
24
25
  export declare enum ChangeSetType {
25
26
  CREATE = "create",
@@ -13,9 +13,8 @@ export class ChangeSet {
13
13
  this.type = type;
14
14
  this.payload = payload;
15
15
  this.meta = meta;
16
- this.name = meta.className;
17
- this.rootName = meta.root.className;
18
- this.collection = meta.root.collection;
16
+ this.meta = meta;
17
+ this.rootMeta = meta.root;
19
18
  this.schema = helper(entity).__schema ?? meta.root.schema;
20
19
  }
21
20
  getPrimaryKey(object = false) {
@@ -1,18 +1,15 @@
1
- import { type Configuration } from '../utils/Configuration.js';
2
- import type { MetadataStorage } from '../metadata/MetadataStorage.js';
3
1
  import type { AnyEntity } from '../typings.js';
4
2
  import { ChangeSet } from './ChangeSet.js';
5
3
  import { type Collection } from '../entity/Collection.js';
6
- import type { Platform } from '../platforms/Platform.js';
7
4
  import type { EntityManager } from '../EntityManager.js';
8
5
  export declare class ChangeSetComputer {
6
+ private readonly em;
9
7
  private readonly collectionUpdates;
8
+ private readonly comparator;
10
9
  private readonly metadata;
11
10
  private readonly platform;
12
11
  private readonly config;
13
- private readonly em;
14
- private readonly comparator;
15
- constructor(collectionUpdates: Set<Collection<AnyEntity>>, metadata: MetadataStorage, platform: Platform, config: Configuration, em: EntityManager);
12
+ constructor(em: EntityManager, collectionUpdates: Set<Collection<AnyEntity>>);
16
13
  computeChangeSet<T extends object>(entity: T): ChangeSet<T> | null;
17
14
  /**
18
15
  * Traverses entity graph and executes `onCreate` and `onUpdate` methods, assigning the values to given properties.
@@ -1,25 +1,28 @@
1
1
  import { Utils } from '../utils/Utils.js';
2
+ import { QueryHelper } from '../utils/QueryHelper.js';
2
3
  import { ChangeSet, ChangeSetType } from './ChangeSet.js';
3
4
  import { helper } from '../entity/wrap.js';
4
5
  import { validateEntity } from '../entity/validators.js';
6
+ import { Reference } from '../entity/Reference.js';
7
+ import { PolymorphicRef } from '../entity/PolymorphicRef.js';
5
8
  import { ReferenceKind } from '../enums.js';
6
9
  export class ChangeSetComputer {
10
+ em;
7
11
  collectionUpdates;
12
+ comparator;
8
13
  metadata;
9
14
  platform;
10
15
  config;
11
- em;
12
- comparator;
13
- constructor(collectionUpdates, metadata, platform, config, em) {
14
- this.collectionUpdates = collectionUpdates;
15
- this.metadata = metadata;
16
- this.platform = platform;
17
- this.config = config;
16
+ constructor(em, collectionUpdates) {
18
17
  this.em = em;
18
+ this.collectionUpdates = collectionUpdates;
19
+ this.config = this.em.config;
20
+ this.metadata = this.em.getMetadata();
21
+ this.platform = this.em.getPlatform();
19
22
  this.comparator = this.config.getComparator(this.metadata);
20
23
  }
21
24
  computeChangeSet(entity) {
22
- const meta = this.metadata.get(entity.constructor.name);
25
+ const meta = this.metadata.get(entity.constructor);
23
26
  if (meta.readonly) {
24
27
  return null;
25
28
  }
@@ -91,7 +94,7 @@ export class ChangeSetComputer {
91
94
  computePayload(entity, ignoreUndefined = false) {
92
95
  const data = this.comparator.prepareEntity(entity);
93
96
  const wrapped = helper(entity);
94
- const entityName = wrapped.__meta.className;
97
+ const entityName = wrapped.__meta.class;
95
98
  const originalEntityData = wrapped.__originalEntityData;
96
99
  if (!wrapped.__initialized) {
97
100
  for (const prop of wrapped.__meta.primaryKeys) {
@@ -130,9 +133,25 @@ export class ChangeSetComputer {
130
133
  return;
131
134
  }
132
135
  const targets = Utils.unwrapProperty(changeSet.entity, changeSet.meta, prop);
133
- targets.forEach(([target, idx]) => {
134
- if (!target.__helper.hasPrimaryKey()) {
135
- Utils.setPayloadProperty(changeSet.payload, this.metadata.find(changeSet.name), prop, target.__helper.__identifier, idx);
136
+ targets.forEach(([rawTarget, idx]) => {
137
+ const target = Reference.unwrapReference(rawTarget);
138
+ const needsProcessing = target != null && (prop.targetKey != null || !target.__helper.hasPrimaryKey());
139
+ if (needsProcessing) {
140
+ let value = prop.targetKey ? target[prop.targetKey] : target.__helper.__identifier;
141
+ /* v8 ignore next */
142
+ if (prop.targetKey && prop.targetMeta) {
143
+ const targetProp = prop.targetMeta.properties[prop.targetKey];
144
+ if (targetProp?.customType) {
145
+ value = targetProp.customType.convertToDatabaseValue(value, this.platform, { mode: 'serialization' });
146
+ }
147
+ }
148
+ if (prop.polymorphic) {
149
+ const discriminator = QueryHelper.findDiscriminatorValue(prop.discriminatorMap, target.constructor);
150
+ Utils.setPayloadProperty(changeSet.payload, changeSet.meta, prop, new PolymorphicRef(discriminator, value), idx);
151
+ }
152
+ else {
153
+ Utils.setPayloadProperty(changeSet.payload, changeSet.meta, prop, value, idx);
154
+ }
136
155
  }
137
156
  });
138
157
  }
@@ -145,7 +164,9 @@ export class ChangeSetComputer {
145
164
  this.collectionUpdates.add(target);
146
165
  }
147
166
  if (prop.owner && !this.platform.usesPivotTable()) {
148
- changeSet.payload[prop.name] = target.getItems(false).map((item) => item.__helper.__identifier ?? item.__helper.getPrimaryKey());
167
+ changeSet.payload[prop.name] = target.getItems(false).map((item) => {
168
+ return item.__helper.__identifier ?? item.__helper.getPrimaryKey();
169
+ });
149
170
  }
150
171
  }
151
172
  }
@@ -1,21 +1,18 @@
1
- import type { MetadataStorage } from '../metadata/MetadataStorage.js';
2
- import type { Dictionary, EntityDictionary, EntityMetadata, IHydrator } from '../typings.js';
3
- import { type EntityFactory } from '../entity/EntityFactory.js';
1
+ import type { Dictionary, EntityDictionary, EntityMetadata } from '../typings.js';
4
2
  import { type ChangeSet } from './ChangeSet.js';
5
- import { type Configuration } from '../utils/Configuration.js';
6
- import type { DriverMethodOptions, IDatabaseDriver } from '../drivers/IDatabaseDriver.js';
3
+ import type { DriverMethodOptions } from '../drivers/IDatabaseDriver.js';
7
4
  import type { EntityManager } from '../EntityManager.js';
8
5
  export declare class ChangeSetPersister {
6
+ private readonly em;
7
+ private readonly platform;
8
+ private readonly comparator;
9
+ private readonly usesReturningStatement;
9
10
  private readonly driver;
10
11
  private readonly metadata;
11
12
  private readonly hydrator;
12
13
  private readonly factory;
13
14
  private readonly config;
14
- private readonly em;
15
- private readonly platform;
16
- private readonly comparator;
17
- private readonly usesReturningStatement;
18
- constructor(driver: IDatabaseDriver, metadata: MetadataStorage, hydrator: IHydrator, factory: EntityFactory, config: Configuration, em: EntityManager);
15
+ constructor(em: EntityManager);
19
16
  executeInserts<T extends object>(changeSets: ChangeSet<T>[], options?: DriverMethodOptions, withSchema?: boolean): Promise<void>;
20
17
  executeUpdates<T extends object>(changeSets: ChangeSet<T>[], batched: boolean, options?: DriverMethodOptions, withSchema?: boolean): Promise<void>;
21
18
  executeDeletes<T extends object>(changeSets: ChangeSet<T>[], options?: DriverMethodOptions, withSchema?: boolean): Promise<void>;
@@ -43,6 +40,11 @@ export declare class ChangeSetPersister {
43
40
  * so we use a single query in case of both versioning and default values is used.
44
41
  */
45
42
  private reloadVersionValues;
43
+ /**
44
+ * For TPT child tables, resolve EntityIdentifier values in PK fields.
45
+ * The parent table insert assigns the actual PK value, which the child table references.
46
+ */
47
+ private resolveTPTIdentifiers;
46
48
  private processProperty;
47
49
  /**
48
50
  * Maps values returned via `returning` statement (postgres) or the inserted id (other sql drivers).
@@ -1,4 +1,5 @@
1
1
  import { EntityIdentifier } from '../entity/EntityIdentifier.js';
2
+ import { PolymorphicRef } from '../entity/PolymorphicRef.js';
2
3
  import { helper } from '../entity/wrap.js';
3
4
  import { ChangeSetType } from './ChangeSet.js';
4
5
  import { isRaw } from '../utils/RawQueryFragment.js';
@@ -6,23 +7,23 @@ import { Utils } from '../utils/Utils.js';
6
7
  import { OptimisticLockError, ValidationError } from '../errors.js';
7
8
  import { ReferenceKind } from '../enums.js';
8
9
  export class ChangeSetPersister {
10
+ em;
11
+ platform;
12
+ comparator;
13
+ usesReturningStatement;
9
14
  driver;
10
15
  metadata;
11
16
  hydrator;
12
17
  factory;
13
18
  config;
14
- em;
15
- platform;
16
- comparator;
17
- usesReturningStatement;
18
- constructor(driver, metadata, hydrator, factory, config, em) {
19
- this.driver = driver;
20
- this.metadata = metadata;
21
- this.hydrator = hydrator;
22
- this.factory = factory;
23
- this.config = config;
19
+ constructor(em) {
24
20
  this.em = em;
21
+ this.driver = this.em.getDriver();
22
+ this.config = this.em.config;
23
+ this.metadata = this.em.getMetadata();
24
+ this.factory = this.em.getEntityFactory();
25
25
  this.platform = this.driver.getPlatform();
26
+ this.hydrator = this.config.getHydrator(this.metadata);
26
27
  this.comparator = this.config.getComparator(this.metadata);
27
28
  this.usesReturningStatement = this.platform.usesReturningStatement() || this.platform.usesOutputStatement();
28
29
  }
@@ -30,7 +31,7 @@ export class ChangeSetPersister {
30
31
  if (!withSchema) {
31
32
  return this.runForEachSchema(changeSets, 'executeInserts', options);
32
33
  }
33
- const meta = this.metadata.find(changeSets[0].name);
34
+ const meta = changeSets[0].meta;
34
35
  changeSets.forEach(changeSet => this.processProperties(changeSet));
35
36
  if (changeSets.length > 1 && this.config.get('useBatchInserts', this.platform.usesBatchInserts())) {
36
37
  return this.persistNewEntities(meta, changeSets, options);
@@ -43,9 +44,12 @@ export class ChangeSetPersister {
43
44
  if (!withSchema) {
44
45
  return this.runForEachSchema(changeSets, 'executeUpdates', options, batched);
45
46
  }
46
- const meta = this.metadata.find(changeSets[0].name);
47
+ const meta = changeSets[0].meta;
47
48
  changeSets.forEach(changeSet => this.processProperties(changeSet));
48
- if (batched && changeSets.length > 1 && this.config.get('useBatchUpdates', this.platform.usesBatchUpdates())) {
49
+ // For STI with conflicting fieldNames (renamedFrom properties), we can't batch mixed child types
50
+ const hasSTIConflicts = meta.root.props.some(p => p.renamedFrom);
51
+ const hasMixedTypes = hasSTIConflicts && changeSets.some(cs => cs.meta.class !== meta.class);
52
+ if (batched && changeSets.length > 1 && !hasMixedTypes && this.config.get('useBatchUpdates', this.platform.usesBatchUpdates())) {
49
53
  return this.persistManagedEntities(meta, changeSets, options);
50
54
  }
51
55
  for (const changeSet of changeSets) {
@@ -63,7 +67,7 @@ export class ChangeSetPersister {
63
67
  const chunk = changeSets.slice(i, i + size);
64
68
  const pks = chunk.map(cs => cs.getPrimaryKey());
65
69
  options = this.prepareOptions(meta, options);
66
- await this.driver.nativeDelete(meta.root.className, { [pk]: { $in: pks } }, options);
70
+ await this.driver.nativeDelete(meta.root.class, { [pk]: { $in: pks } }, options);
67
71
  }
68
72
  }
69
73
  async runForEachSchema(changeSets, method, options, ...args) {
@@ -99,7 +103,7 @@ export class ChangeSetPersister {
99
103
  }
100
104
  }
101
105
  processProperties(changeSet) {
102
- const meta = this.metadata.find(changeSet.name);
106
+ const meta = changeSet.meta;
103
107
  for (const prop of meta.relations) {
104
108
  this.processProperty(changeSet, prop);
105
109
  }
@@ -112,7 +116,9 @@ export class ChangeSetPersister {
112
116
  options = this.prepareOptions(meta, options, {
113
117
  convertCustomTypes: false,
114
118
  });
115
- const res = await this.driver.nativeInsertMany(meta.className, [changeSet.payload], options);
119
+ this.resolveTPTIdentifiers(changeSet);
120
+ // Use changeSet's own meta for STI entities to get correct field mappings
121
+ const res = await this.driver.nativeInsertMany(changeSet.meta.class, [changeSet.payload], options);
116
122
  if (!wrapped.hasPrimaryKey()) {
117
123
  this.mapPrimaryKey(meta, res.insertId, changeSet);
118
124
  }
@@ -149,7 +155,10 @@ export class ChangeSetPersister {
149
155
  convertCustomTypes: false,
150
156
  processCollections: false,
151
157
  });
152
- const res = await this.driver.nativeInsertMany(meta.className, changeSets.map(cs => cs.payload), options);
158
+ for (const changeSet of changeSets) {
159
+ this.resolveTPTIdentifiers(changeSet);
160
+ }
161
+ const res = await this.driver.nativeInsertMany(meta.class, changeSets.map(cs => cs.payload), options);
153
162
  for (let i = 0; i < changeSets.length; i++) {
154
163
  const changeSet = changeSets[i];
155
164
  const wrapped = helper(changeSet.entity);
@@ -167,7 +176,7 @@ export class ChangeSetPersister {
167
176
  }
168
177
  }
169
178
  async persistManagedEntity(changeSet, options) {
170
- const meta = this.metadata.find(changeSet.name);
179
+ const meta = changeSet.meta;
171
180
  const res = await this.updateEntity(meta, changeSet, options);
172
181
  this.checkOptimisticLock(meta, changeSet, res);
173
182
  this.mapReturnedValues(changeSet.entity, changeSet.payload, res.row, meta);
@@ -208,7 +217,7 @@ export class ChangeSetPersister {
208
217
  cond.push(where);
209
218
  payload.push(changeSet.payload);
210
219
  }
211
- const res = await this.driver.nativeUpdateMany(meta.className, cond, payload, options);
220
+ const res = await this.driver.nativeUpdateMany(meta.class, cond, payload, options);
212
221
  const map = new Map();
213
222
  res.rows?.forEach(item => map.set(Utils.getCompositeKeyHash(item, meta, true, this.platform, true), item));
214
223
  for (const changeSet of changeSets) {
@@ -260,13 +269,13 @@ export class ChangeSetPersister {
260
269
  convertCustomTypes: false,
261
270
  });
262
271
  if (meta.concurrencyCheckKeys.size === 0 && (!meta.versionProperty || changeSet.entity[meta.versionProperty] == null)) {
263
- return this.driver.nativeUpdate(changeSet.name, cond, changeSet.payload, options);
272
+ return this.driver.nativeUpdate(changeSet.meta.class, cond, changeSet.payload, options);
264
273
  }
265
274
  if (meta.versionProperty) {
266
275
  cond[meta.versionProperty] = this.platform.quoteVersionValue(changeSet.entity[meta.versionProperty], meta.properties[meta.versionProperty]);
267
276
  }
268
277
  this.checkConcurrencyKeys(meta, changeSet, cond);
269
- return this.driver.nativeUpdate(changeSet.name, cond, changeSet.payload, options);
278
+ return this.driver.nativeUpdate(changeSet.meta.class, cond, changeSet.payload, options);
270
279
  }
271
280
  async checkOptimisticLocks(meta, changeSets, options) {
272
281
  if (meta.concurrencyCheckKeys.size === 0 && (!meta.versionProperty || changeSets.every(cs => cs.entity[meta.versionProperty] == null))) {
@@ -286,7 +295,7 @@ export class ChangeSetPersister {
286
295
  options = this.prepareOptions(meta, options, {
287
296
  fields: primaryKeys,
288
297
  });
289
- const res = await this.driver.find(meta.root.className, { $or }, options);
298
+ const res = await this.driver.find(meta.root.class, { $or }, options);
290
299
  if (res.length !== changeSets.length) {
291
300
  const compare = (a, b, keys) => keys.every(k => a[k] === b[k]);
292
301
  const entity = changeSets.find(cs => {
@@ -356,7 +365,7 @@ export class ChangeSetPersister {
356
365
  options = this.prepareOptions(meta, options, {
357
366
  fields: Utils.unique(reloadProps.map(prop => prop.name)),
358
367
  });
359
- const data = await this.driver.find(meta.className, { [pk]: { $in: pks } }, options);
368
+ const data = await this.driver.find(meta.class, { [pk]: { $in: pks } }, options);
360
369
  const map = new Map();
361
370
  data.forEach(item => map.set(Utils.getCompositeKeyHash(item, meta, false, this.platform, true), item));
362
371
  for (const changeSet of changeSets) {
@@ -365,13 +374,34 @@ export class ChangeSetPersister {
365
374
  Object.assign(changeSet.payload, data); // merge to the changeset payload, so it gets saved to the entity snapshot
366
375
  }
367
376
  }
377
+ /**
378
+ * For TPT child tables, resolve EntityIdentifier values in PK fields.
379
+ * The parent table insert assigns the actual PK value, which the child table references.
380
+ */
381
+ resolveTPTIdentifiers(changeSet) {
382
+ if (changeSet.meta.inheritanceType !== 'tpt' || !changeSet.meta.tptParent) {
383
+ return;
384
+ }
385
+ for (const pk of changeSet.meta.primaryKeys) {
386
+ const value = changeSet.payload[pk];
387
+ if (value instanceof EntityIdentifier) {
388
+ changeSet.payload[pk] = value.getValue();
389
+ }
390
+ }
391
+ }
368
392
  processProperty(changeSet, prop) {
369
- const meta = this.metadata.find(changeSet.name);
393
+ const meta = changeSet.meta;
370
394
  const value = changeSet.payload[prop.name]; // for inline embeddables
371
395
  if (value instanceof EntityIdentifier) {
372
396
  changeSet.payload[prop.name] = value.getValue();
373
397
  return;
374
398
  }
399
+ if (value instanceof PolymorphicRef) {
400
+ if (value.id instanceof EntityIdentifier) {
401
+ value.id = value.id.getValue();
402
+ }
403
+ return;
404
+ }
375
405
  if (Array.isArray(value) && value.every(item => item instanceof EntityIdentifier)) {
376
406
  changeSet.payload[prop.name] = value.map(item => item.getValue());
377
407
  return;
@@ -399,7 +429,7 @@ export class ChangeSetPersister {
399
429
  if ((!this.usesReturningStatement && !upsert) || !row || !Utils.hasObjectKeys(row)) {
400
430
  return;
401
431
  }
402
- const mapped = this.comparator.mapResult(meta.className, row);
432
+ const mapped = this.comparator.mapResult(meta, row);
403
433
  if (entity) {
404
434
  this.hydrator.hydrate(entity, meta, mapped, this.factory, 'full', false, true);
405
435
  }
@@ -1,17 +1,18 @@
1
- import type { Dictionary, EntityProperty } from '../typings.js';
1
+ import type { EntityProperty } from '../typings.js';
2
2
  export declare const enum NodeState {
3
3
  NOT_VISITED = 0,
4
4
  IN_PROGRESS = 1,
5
5
  VISITED = 2
6
6
  }
7
+ type Hash = number;
7
8
  export interface Node {
8
- hash: string;
9
+ hash: Hash;
9
10
  state: NodeState;
10
- dependencies: Dictionary<Edge>;
11
+ dependencies: Map<Hash, Edge>;
11
12
  }
12
13
  export interface Edge {
13
- from: string;
14
- to: string;
14
+ from: Hash;
15
+ to: Hash;
15
16
  weight: number;
16
17
  }
17
18
  /**
@@ -32,23 +33,23 @@ export declare class CommitOrderCalculator {
32
33
  /**
33
34
  * Checks for node existence in graph.
34
35
  */
35
- hasNode(hash: string): boolean;
36
+ hasNode(hash: Hash): boolean;
36
37
  /**
37
38
  * Adds a new node to the graph, assigning its hash.
38
39
  */
39
- addNode(hash: string): void;
40
+ addNode(hash: Hash): void;
40
41
  /**
41
42
  * Adds a new dependency (edge) to the graph using their hashes.
42
43
  */
43
- addDependency(from: string, to: string, weight: number): void;
44
- discoverProperty(prop: EntityProperty, entityName: string): void;
44
+ addDependency(from: Hash, to: Hash, weight: number): void;
45
+ discoverProperty(prop: EntityProperty, entityName: Hash): void;
45
46
  /**
46
47
  * Return a valid order list of all current nodes.
47
48
  * The desired topological sorting is the reverse post order of these searches.
48
49
  *
49
50
  * @internal Highly performance-sensitive method.
50
51
  */
51
- sort(): string[];
52
+ sort(): Hash[];
52
53
  /**
53
54
  * Visit a given node definition for reordering.
54
55
  *
@@ -60,3 +61,4 @@ export declare class CommitOrderCalculator {
60
61
  */
61
62
  private visitOpenNode;
62
63
  }
64
+ export {};