@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
@@ -62,7 +62,6 @@ export declare class Collection<T extends object, O extends object = object> {
62
62
  init<TT extends T, P extends string = never>(options?: InitCollectionOptions<TT, P>): Promise<LoadedCollection<Loaded<TT, P>>>;
63
63
  private getEntityManager;
64
64
  private createCondition;
65
- private createOrderBy;
66
65
  private createManyToManyCondition;
67
66
  private createLoadCountCondition;
68
67
  private checkInitialized;
@@ -104,10 +103,18 @@ export declare class Collection<T extends object, O extends object = object> {
104
103
  * Tests for the existence of an element that satisfies the given predicate.
105
104
  */
106
105
  exists(cb: (item: T) => boolean): boolean;
106
+ /**
107
+ * Returns the first element of this collection that satisfies the predicate.
108
+ */
109
+ find<S extends T>(cb: (item: T, index: number) => item is S): S | undefined;
107
110
  /**
108
111
  * Returns the first element of this collection that satisfies the predicate.
109
112
  */
110
113
  find(cb: (item: T, index: number) => boolean): T | undefined;
114
+ /**
115
+ * Extracts a subset of the collection items.
116
+ */
117
+ filter<S extends T>(cb: (item: T, index: number) => item is S): S[];
111
118
  /**
112
119
  * Extracts a subset of the collection items.
113
120
  */
@@ -83,7 +83,7 @@ export class Collection {
83
83
  return this._count = this.length;
84
84
  }
85
85
  const cond = this.createLoadCountCondition(where ?? {});
86
- const count = await em.count(this.property.type, cond, countOptions);
86
+ const count = await em.count(this.property.targetMeta.class, cond, countOptions);
87
87
  if (!where) {
88
88
  this._count = count;
89
89
  }
@@ -92,15 +92,20 @@ export class Collection {
92
92
  async matching(options) {
93
93
  const em = this.getEntityManager();
94
94
  const { where, ctx, ...opts } = options;
95
- opts.orderBy = this.createOrderBy(opts.orderBy);
96
95
  let items;
97
96
  if (this.property.kind === ReferenceKind.MANY_TO_MANY && em.getPlatform().usesPivotTable()) {
98
- const cond = await em.applyFilters(this.property.type, where, options.filters ?? {}, 'read');
97
+ // M:N via pivot table bypasses em.find(), so merge all 3 levels here
98
+ opts.orderBy = QueryHelper.mergeOrderBy(opts.orderBy, this.property.orderBy, this.property.targetMeta?.orderBy);
99
+ options.populate = await em.preparePopulate(this.property.targetMeta.class, options);
100
+ const cond = await em.applyFilters(this.property.targetMeta.class, where, options.filters ?? {}, 'read');
99
101
  const map = await em.getDriver().loadFromPivotTable(this.property, [helper(this.owner).__primaryKeys], cond, opts.orderBy, ctx, options);
100
- items = map[helper(this.owner).getSerializedPrimaryKey()].map((item) => em.merge(this.property.type, item, { convertCustomTypes: true }));
102
+ items = map[helper(this.owner).getSerializedPrimaryKey()].map((item) => em.merge(this.property.targetMeta.class, item, { convertCustomTypes: true }));
103
+ await em.populate(items, options.populate, options);
101
104
  }
102
105
  else {
103
- items = await em.find(this.property.type, this.createCondition(where), opts);
106
+ // em.find() merges entity-level orderBy, so only merge runtime + relation here
107
+ opts.orderBy = QueryHelper.mergeOrderBy(opts.orderBy, this.property.orderBy);
108
+ items = await em.find(this.property.targetMeta.class, this.createCondition(where), opts);
104
109
  }
105
110
  if (options.store) {
106
111
  this.hydrate(items, true);
@@ -233,7 +238,7 @@ export class Collection {
233
238
  options = { ...options, filters: QueryHelper.mergePropertyFilters(this.property.filters, options.filters) };
234
239
  if (options.dataloader ?? [DataloaderType.ALL, DataloaderType.COLLECTION].includes(em.config.getDataloaderType())) {
235
240
  const order = [...this.items]; // copy order of references
236
- const orderBy = this.createOrderBy(options.orderBy);
241
+ const orderBy = QueryHelper.mergeOrderBy(options.orderBy, this.property.orderBy, this.property.targetMeta?.orderBy);
237
242
  const customOrder = orderBy.length > 0;
238
243
  const pivotTable = this.property.kind === ReferenceKind.MANY_TO_MANY && em.getPlatform().usesPivotTable();
239
244
  const loader = await em.getDataLoader(pivotTable ? 'm:n' : '1:m');
@@ -297,12 +302,6 @@ export class Collection {
297
302
  }
298
303
  return cond;
299
304
  }
300
- createOrderBy(orderBy = []) {
301
- if (Utils.isEmpty(orderBy) && this.property.orderBy) {
302
- orderBy = this.property.orderBy;
303
- }
304
- return Utils.asArray(orderBy);
305
- }
306
305
  createManyToManyCondition(cond) {
307
306
  const dict = cond;
308
307
  if (this.property.owner || this.property.pivotTable) {
@@ -330,7 +329,7 @@ export class Collection {
330
329
  }
331
330
  checkInitialized() {
332
331
  if (!this.isInitialized()) {
333
- throw new Error(`Collection<${this.property.type}> of entity ${this.owner.constructor.name}[${helper(this.owner).getSerializedPrimaryKey()}] not initialized`);
332
+ throw new Error(`Collection<${this.property.type}> of entity ${helper(this.owner).__meta.name}[${helper(this.owner).getSerializedPrimaryKey()}] not initialized`);
334
333
  }
335
334
  }
336
335
  /**
@@ -77,7 +77,7 @@ export class EntityAssigner {
77
77
  if (options.updateByPrimaryKey) {
78
78
  const pk = Utils.extractPK(value, prop.targetMeta);
79
79
  if (pk) {
80
- const ref = options.em.getReference(prop.type, pk, options);
80
+ const ref = options.em.getReference(prop.targetMeta.class, pk, options);
81
81
  // if the PK differs, we want to change the target entity, not update it
82
82
  const wrappedChild = helper(ref);
83
83
  const sameTarget = wrappedChild.getSerializedPrimaryKey() === wrapped.getSerializedPrimaryKey();
@@ -141,13 +141,13 @@ export class EntityAssigner {
141
141
  entity[prop.name] = Reference.wrapReference(value, prop);
142
142
  }
143
143
  else if (Utils.isPrimaryKey(value, true) && EntityAssigner.validateEM(em)) {
144
- entity[prop.name] = prop.mapToPk ? value : Reference.wrapReference(em.getReference(prop.type, value, options), prop);
144
+ entity[prop.name] = prop.mapToPk ? value : Reference.wrapReference(em.getReference(prop.targetMeta.class, value, options), prop);
145
145
  }
146
146
  else if (Utils.isPlainObject(value) && options.merge && EntityAssigner.validateEM(em)) {
147
- entity[prop.name] = Reference.wrapReference(em.merge(prop.type, value, options), prop);
147
+ entity[prop.name] = Reference.wrapReference(em.merge(prop.targetMeta.class, value, options), prop);
148
148
  }
149
149
  else if (Utils.isPlainObject(value) && EntityAssigner.validateEM(em)) {
150
- entity[prop.name] = Reference.wrapReference(em.create(prop.type, value, options), prop);
150
+ entity[prop.name] = Reference.wrapReference(em.create(prop.targetMeta.class, value, options), prop);
151
151
  }
152
152
  else {
153
153
  const name = entity.constructor.name;
@@ -166,7 +166,7 @@ export class EntityAssigner {
166
166
  if (options.updateNestedEntities && options.updateByPrimaryKey && Utils.isPlainObject(item)) {
167
167
  const pk = Utils.extractPK(item, prop.targetMeta);
168
168
  if (pk && EntityAssigner.validateEM(em)) {
169
- const ref = em.getUnitOfWork().getById(prop.type, pk, options.schema);
169
+ const ref = em.getUnitOfWork().getById(prop.targetMeta.class, pk, options.schema);
170
170
  if (ref) {
171
171
  return EntityAssigner.assign(ref, item, options);
172
172
  }
@@ -207,7 +207,7 @@ export class EntityAssigner {
207
207
  entity[propName].push(...Object.values(tmp));
208
208
  });
209
209
  }
210
- const create = () => EntityAssigner.validateEM(em) && em.getEntityFactory().createEmbeddable(prop.type, value, {
210
+ const create = () => EntityAssigner.validateEM(em) && em.getEntityFactory().createEmbeddable(prop.targetMeta.class, value, {
211
211
  convertCustomTypes: options.convertCustomTypes,
212
212
  newEntity: options.mergeEmbeddedProperties ? !('propName' in entity) : true,
213
213
  });
@@ -221,13 +221,13 @@ export class EntityAssigner {
221
221
  return item;
222
222
  }
223
223
  if (Utils.isPrimaryKey(item) && EntityAssigner.validateEM(em)) {
224
- return em.getReference(prop.type, item, options);
224
+ return em.getReference(prop.targetMeta.class, item, options);
225
225
  }
226
226
  if (Utils.isPlainObject(item) && options.merge && EntityAssigner.validateEM(em)) {
227
- return em.merge(prop.type, item, options);
227
+ return em.merge(prop.targetMeta.class, item, options);
228
228
  }
229
229
  if (Utils.isPlainObject(item) && EntityAssigner.validateEM(em)) {
230
- return em.create(prop.type, item, options);
230
+ return em.create(prop.targetMeta.class, item, options);
231
231
  }
232
232
  invalid.push(item);
233
233
  return item;
@@ -16,6 +16,11 @@ export interface FactoryOptions {
16
16
  schema?: string;
17
17
  parentSchema?: string;
18
18
  normalizeAccessors?: boolean;
19
+ /**
20
+ * Property name to use for identity map lookup instead of the primary key.
21
+ * This is useful for creating references by unique non-PK properties.
22
+ */
23
+ key?: string;
19
24
  }
20
25
  export declare class EntityFactory {
21
26
  private readonly em;
@@ -29,7 +34,7 @@ export declare class EntityFactory {
29
34
  constructor(em: EntityManager);
30
35
  create<T extends object, P extends string = string>(entityName: EntityName<T>, data: EntityData<T>, options?: FactoryOptions): New<T, P>;
31
36
  mergeData<T extends object>(meta: EntityMetadata<T>, entity: T, data: EntityData<T>, options?: FactoryOptions): void;
32
- createReference<T extends object>(entityName: EntityName<T>, id: Primary<T> | Primary<T>[] | Record<string, Primary<T>>, options?: Pick<FactoryOptions, 'merge' | 'convertCustomTypes' | 'schema'>): T;
37
+ createReference<T extends object>(entityName: EntityName<T>, id: Primary<T> | Primary<T>[] | Record<string, Primary<T>>, options?: Pick<FactoryOptions, 'merge' | 'convertCustomTypes' | 'schema' | 'key'>): T;
33
38
  createEmbeddable<T extends object>(entityName: EntityName<T>, data: EntityData<T>, options?: Pick<FactoryOptions, 'newEntity' | 'convertCustomTypes'>): T;
34
39
  getComparator(): EntityComparator;
35
40
  private createEntity;
@@ -30,7 +30,6 @@ export class EntityFactory {
30
30
  if (data.__entity) {
31
31
  return data;
32
32
  }
33
- entityName = Utils.className(entityName);
34
33
  const meta = this.metadata.get(entityName);
35
34
  if (meta.virtual) {
36
35
  data = { ...data };
@@ -84,7 +83,7 @@ export class EntityFactory {
84
83
  else {
85
84
  this.hydrate(entity, meta2, data, options);
86
85
  }
87
- if (exists && meta.discriminatorColumn && !(entity instanceof meta2.class)) {
86
+ if (exists && meta.root.inheritanceType && !(entity instanceof meta2.class)) {
88
87
  Object.setPrototypeOf(entity, meta2.prototype);
89
88
  }
90
89
  if (options.merge && wrapped.hasPrimaryKey()) {
@@ -110,12 +109,12 @@ export class EntityFactory {
110
109
  data = QueryHelper.processParams(data);
111
110
  const existsData = this.comparator.prepareEntity(entity);
112
111
  const originalEntityData = helper(entity).__originalEntityData ?? {};
113
- const diff = this.comparator.diffEntities(meta.className, originalEntityData, existsData);
112
+ const diff = this.comparator.diffEntities(meta.class, originalEntityData, existsData);
114
113
  // version properties are not part of entity snapshots
115
114
  if (meta.versionProperty && data[meta.versionProperty] && data[meta.versionProperty] !== originalEntityData[meta.versionProperty]) {
116
115
  diff[meta.versionProperty] = data[meta.versionProperty];
117
116
  }
118
- const diff2 = this.comparator.diffEntities(meta.className, existsData, data, { includeInverseSides: true });
117
+ const diff2 = this.comparator.diffEntities(meta.class, existsData, data, { includeInverseSides: true });
119
118
  // do not override values changed by user
120
119
  Utils.keys(diff).forEach(key => delete diff2[key]);
121
120
  Utils.keys(diff2).filter(key => {
@@ -152,20 +151,31 @@ export class EntityFactory {
152
151
  // we just create the entity from scratch, which will automatically pick the right one from the identity map and call `mergeData` on it
153
152
  data[prop.name]
154
153
  .filter(child => Utils.isPlainObject(child)) // objects with prototype can be PKs (e.g. `ObjectId`)
155
- .forEach(child => this.create(prop.type, child, options)); // we can ignore the value, we just care about the `mergeData` call
154
+ .forEach(child => this.create(prop.targetMeta.class, child, options)); // we can ignore the value, we just care about the `mergeData` call
156
155
  return;
157
156
  }
158
157
  if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && Utils.isPlainObject(data[prop.name]) && entity[prop.name] && helper(entity[prop.name]).__initialized) {
159
- this.create(prop.type, data[prop.name], options); // we can ignore the value, we just care about the `mergeData` call
158
+ this.create(prop.targetMeta.class, data[prop.name], options); // we can ignore the value, we just care about the `mergeData` call
160
159
  }
161
160
  });
162
161
  this.unitOfWork.normalizeEntityData(meta, originalEntityData);
163
162
  }
164
163
  createReference(entityName, id, options = {}) {
165
164
  options.convertCustomTypes ??= true;
166
- entityName = Utils.className(entityName);
167
165
  const meta = this.metadata.get(entityName);
168
166
  const schema = this.driver.getSchemaName(meta, options);
167
+ // Handle alternate key lookup
168
+ if (options.key) {
169
+ const value = '' + (Array.isArray(id) ? id[0] : Utils.isPlainObject(id) ? id[options.key] : id);
170
+ const exists = this.unitOfWork.getByKey(entityName, options.key, value, schema, options.convertCustomTypes);
171
+ if (exists) {
172
+ return exists;
173
+ }
174
+ // Create entity stub - storeByKey will set the alternate key property and store in identity map
175
+ const entity = this.create(entityName, {}, { ...options, initialized: false });
176
+ this.unitOfWork.storeByKey(entity, options.key, value, schema, options.convertCustomTypes);
177
+ return entity;
178
+ }
169
179
  if (meta.simplePK) {
170
180
  const exists = this.unitOfWork.getById(entityName, id, schema);
171
181
  if (exists) {
@@ -188,7 +198,6 @@ export class EntityFactory {
188
198
  return this.create(entityName, id, { ...options, initialized: false });
189
199
  }
190
200
  createEmbeddable(entityName, data, options = {}) {
191
- entityName = Utils.className(entityName);
192
201
  data = { ...data };
193
202
  const meta = this.metadata.get(entityName);
194
203
  const meta2 = this.processDiscriminatorColumn(meta, data);
@@ -200,7 +209,7 @@ export class EntityFactory {
200
209
  createEntity(data, meta, options) {
201
210
  const schema = this.driver.getSchemaName(meta, options);
202
211
  if (options.newEntity || meta.forceConstructor || meta.virtual) {
203
- if (!meta.class) {
212
+ if (meta.polymorphs) {
204
213
  throw new Error(`Cannot create entity ${meta.className}, class prototype is unknown`);
205
214
  }
206
215
  const params = this.extractConstructorParams(meta, data, options);
@@ -263,22 +272,31 @@ export class EntityFactory {
263
272
  findEntity(data, meta, options) {
264
273
  const schema = this.driver.getSchemaName(meta, options);
265
274
  if (meta.simplePK) {
266
- return this.unitOfWork.getById(meta.className, data[meta.primaryKeys[0]], schema);
275
+ return this.unitOfWork.getById(meta.class, data[meta.primaryKeys[0]], schema);
267
276
  }
268
277
  if (!Array.isArray(data) && meta.primaryKeys.some(pk => data[pk] == null)) {
269
278
  return undefined;
270
279
  }
271
280
  const pks = Utils.getOrderedPrimaryKeys(data, meta, this.platform, options.convertCustomTypes);
272
- return this.unitOfWork.getById(meta.className, pks, schema);
281
+ return this.unitOfWork.getById(meta.class, pks, schema);
273
282
  }
274
283
  processDiscriminatorColumn(meta, data) {
275
- if (!meta.root.discriminatorColumn) {
284
+ // Handle STI discriminator (persisted column)
285
+ if (meta.root.inheritanceType === 'sti') {
286
+ const prop = meta.properties[meta.root.discriminatorColumn];
287
+ const value = data[prop.name];
288
+ const type = meta.root.discriminatorMap[value];
289
+ meta = type ? this.metadata.get(type) : meta;
276
290
  return meta;
277
291
  }
278
- const prop = meta.properties[meta.root.discriminatorColumn];
279
- const value = data[prop.name];
280
- const type = meta.root.discriminatorMap[value];
281
- meta = type ? this.metadata.find(type) : meta;
292
+ // Handle TPT discriminator (computed at query time)
293
+ if (meta.root.inheritanceType === 'tpt' && meta.root.discriminatorMap) {
294
+ const value = data[meta.root.tptDiscriminatorColumn];
295
+ if (value) {
296
+ const type = meta.root.discriminatorMap[value];
297
+ meta = type ? this.metadata.get(type) : meta;
298
+ }
299
+ }
282
300
  return meta;
283
301
  }
284
302
  /**
@@ -307,7 +325,7 @@ export class EntityFactory {
307
325
  const value = data[k];
308
326
  if (prop && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && value) {
309
327
  const pk = Reference.unwrapReference(value);
310
- const entity = this.unitOfWork.getById(prop.type, pk, options.schema, true);
328
+ const entity = this.unitOfWork.getById(prop.targetMeta.class, pk, options.schema, true);
311
329
  if (entity) {
312
330
  return entity;
313
331
  }
@@ -316,10 +334,10 @@ export class EntityFactory {
316
334
  }
317
335
  const nakedPk = Utils.extractPK(value, prop.targetMeta, true);
318
336
  if (Utils.isObject(value) && !nakedPk) {
319
- return this.create(prop.type, value, options);
337
+ return this.create(prop.targetMeta.class, value, options);
320
338
  }
321
339
  const { newEntity, initialized, ...rest } = options;
322
- const target = this.createReference(prop.type, nakedPk, rest);
340
+ const target = this.createReference(prop.targetMeta.class, nakedPk, rest);
323
341
  return Reference.wrapReference(target, prop);
324
342
  }
325
343
  if (prop?.kind === ReferenceKind.EMBEDDED && value) {
@@ -327,7 +345,7 @@ export class EntityFactory {
327
345
  if (Utils.isEntity(value)) {
328
346
  return value;
329
347
  }
330
- return this.createEmbeddable(prop.type, value, options);
348
+ return this.createEmbeddable(prop.targetMeta.class, value, options);
331
349
  }
332
350
  if (!prop) {
333
351
  const tmp = { ...data };
@@ -335,8 +353,8 @@ export class EntityFactory {
335
353
  if (!options.convertCustomTypes || !prop.customType || tmp[prop.name] == null) {
336
354
  continue;
337
355
  }
338
- if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && Utils.isPlainObject(tmp[prop.name]) && !Utils.extractPK(tmp[prop.name], meta.properties[prop.name].targetMeta, true)) {
339
- tmp[prop.name] = Reference.wrapReference(this.create(meta.properties[prop.name].type, tmp[prop.name], options), prop);
356
+ if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && Utils.isPlainObject(tmp[prop.name]) && !Utils.extractPK(tmp[prop.name], prop.targetMeta, true)) {
357
+ tmp[prop.name] = Reference.wrapReference(this.create(prop.targetMeta.class, tmp[prop.name], options), prop);
340
358
  }
341
359
  else if (prop.kind === ReferenceKind.SCALAR) {
342
360
  tmp[prop.name] = prop.customType.convertToJSValue(tmp[prop.name], this.platform);
@@ -6,9 +6,9 @@ import { type EntityMetadata, type EntityProperty, type IHydrator } from '../typ
6
6
  export declare class EntityHelper {
7
7
  static decorate<T extends object>(meta: EntityMetadata<T>, em: EntityManager): void;
8
8
  /**
9
- * As a performance optimization, we create entity state methods in a lazy manner. We first add
9
+ * As a performance optimization, we create entity state methods lazily. We first add
10
10
  * the `null` value to the prototype to reserve space in memory. Then we define a setter on the
11
- * prototype, that will be executed exactly once per entity instance. There we redefine given
11
+ * prototype that will be executed exactly once per entity instance. There we redefine the given
12
12
  * property on the entity instance, so shadowing the prototype setter.
13
13
  */
14
14
  private static defineBaseProperties;
@@ -6,6 +6,7 @@ import { WrappedEntity } from './WrappedEntity.js';
6
6
  import { ReferenceKind } from '../enums.js';
7
7
  import { helper } from './wrap.js';
8
8
  import { inspect } from '../logging/inspect.js';
9
+ import { getEnv } from '../utils/env-vars.js';
9
10
  /**
10
11
  * @internal
11
12
  */
@@ -32,14 +33,19 @@ export class EntityHelper {
32
33
  const prototype = meta.prototype;
33
34
  if (!prototype.toJSON) { // toJSON can be overridden
34
35
  prototype.toJSON = function (...args) {
36
+ // Guard against being called on the prototype itself (e.g. by serializers
37
+ // walking the object graph and calling toJSON on prototype objects)
38
+ if (this === prototype) {
39
+ return {};
40
+ }
35
41
  return EntityTransformer.toObject(this, ...args);
36
42
  };
37
43
  }
38
44
  }
39
45
  /**
40
- * As a performance optimization, we create entity state methods in a lazy manner. We first add
46
+ * As a performance optimization, we create entity state methods lazily. We first add
41
47
  * the `null` value to the prototype to reserve space in memory. Then we define a setter on the
42
- * prototype, that will be executed exactly once per entity instance. There we redefine given
48
+ * prototype that will be executed exactly once per entity instance. There we redefine the given
43
49
  * property on the entity instance, so shadowing the prototype setter.
44
50
  */
45
51
  static defineBaseProperties(meta, prototype, em) {
@@ -130,7 +136,7 @@ export class EntityHelper {
130
136
  .forEach(prop => delete object[prop.name]);
131
137
  const ret = inspect(object, { depth });
132
138
  let name = this.constructor.name;
133
- const showEM = ['true', 't', '1'].includes(process.env.MIKRO_ORM_LOG_EM_ID?.toString().toLowerCase() ?? '');
139
+ const showEM = ['true', 't', '1'].includes(getEnv('MIKRO_ORM_LOG_EM_ID')?.toLowerCase() ?? '');
134
140
  if (showEM) {
135
141
  if (helper(this).__em) {
136
142
  name += ` [managed by ${helper(this).__em.id}]`;
@@ -170,7 +176,19 @@ export class EntityHelper {
170
176
  });
171
177
  }
172
178
  static propagate(meta, entity, owner, prop, value, old) {
173
- for (const prop2 of prop.targetMeta.bidirectionalRelations) {
179
+ // For polymorphic relations, get bidirectional relations from the actual entity's metadata
180
+ let bidirectionalRelations;
181
+ if (prop.polymorphic && prop.polymorphTargets?.length) {
182
+ // For polymorphic relations, we need to get the bidirectional relations from the actual value's metadata
183
+ if (!value) {
184
+ return; // No value means no propagation needed
185
+ }
186
+ bidirectionalRelations = helper(value).__meta.bidirectionalRelations;
187
+ }
188
+ else {
189
+ bidirectionalRelations = prop.targetMeta.bidirectionalRelations;
190
+ }
191
+ for (const prop2 of bidirectionalRelations) {
174
192
  if ((prop2.inversedBy || prop2.mappedBy) !== prop.name) {
175
193
  continue;
176
194
  }
@@ -212,6 +230,11 @@ export class EntityHelper {
212
230
  helper(other).__em?.getUnitOfWork().scheduleOrphanRemoval(other);
213
231
  }
214
232
  }
233
+ // Skip setting the inverse side to null if it's a primary key - the entity will be removed via orphan removal
234
+ // Setting a primary key to null would corrupt the entity and cause validation errors
235
+ if (value == null && prop.orphanRemoval && prop2.primary) {
236
+ return;
237
+ }
215
238
  if (value == null) {
216
239
  entity[prop2.name] = value;
217
240
  }
@@ -1,4 +1,4 @@
1
- import type { AnyEntity, ConnectionType, EntityProperty, FilterQuery, PopulateOptions } from '../typings.js';
1
+ import type { AnyEntity, ConnectionType, EntityName, EntityProperty, FilterQuery, PopulateOptions } from '../typings.js';
2
2
  import type { EntityManager } from '../EntityManager.js';
3
3
  import { LoadStrategy, type LockMode, type PopulateHint, PopulatePath, type QueryOrderMap } from '../enums.js';
4
4
  import type { EntityField, FilterOptions } from '../drivers/IDatabaseDriver.js';
@@ -15,7 +15,7 @@ export type EntityLoaderOptions<Entity, Fields extends string = PopulatePath.ALL
15
15
  convertCustomTypes?: boolean;
16
16
  ignoreLazyScalarProperties?: boolean;
17
17
  filters?: FilterOptions;
18
- strategy?: LoadStrategy;
18
+ strategy?: LoadStrategy | `${LoadStrategy}`;
19
19
  lockMode?: Exclude<LockMode, LockMode.OPTIMISTIC>;
20
20
  schema?: string;
21
21
  connectionType?: ConnectionType;
@@ -30,8 +30,8 @@ export declare class EntityLoader {
30
30
  * Loads specified relations in batch.
31
31
  * This will execute one query for each relation, that will populate it on all the specified entities.
32
32
  */
33
- populate<Entity extends object, Fields extends string = PopulatePath.ALL>(entityName: string, entities: Entity[], populate: PopulateOptions<Entity>[] | boolean, options: EntityLoaderOptions<Entity, Fields>): Promise<void>;
34
- normalizePopulate<Entity>(entityName: string, populate: (PopulateOptions<Entity> | boolean)[] | PopulateOptions<Entity> | boolean, strategy?: LoadStrategy, lookup?: boolean): PopulateOptions<Entity>[];
33
+ populate<Entity extends object, Fields extends string = PopulatePath.ALL>(entityName: EntityName<Entity>, entities: Entity[], populate: PopulateOptions<Entity>[] | boolean, options: EntityLoaderOptions<Entity, Fields>): Promise<void>;
34
+ normalizePopulate<Entity>(entityName: EntityName<Entity>, populate: (PopulateOptions<Entity> | boolean)[] | PopulateOptions<Entity> | boolean, strategy?: LoadStrategy, lookup?: boolean, exclude?: string[]): PopulateOptions<Entity>[];
35
35
  private setSerializationContext;
36
36
  /**
37
37
  * Merge multiple populates for the same entity with different children. Also skips `*` fields, those can come from
@@ -43,6 +43,7 @@ export declare class EntityLoader {
43
43
  */
44
44
  private populateMany;
45
45
  private populateScalar;
46
+ private populatePolymorphic;
46
47
  private initializeCollections;
47
48
  private initializeOneToMany;
48
49
  private initializeManyToMany;