@mikro-orm/core 7.0.0-dev.225 → 7.0.0-dev.227

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/typings.js CHANGED
@@ -1,6 +1,7 @@
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';
6
7
  import { BaseEntity } from './entity/BaseEntity.js';
@@ -167,7 +168,10 @@ export class EntityMetadata {
167
168
  wrapped.__data[prop.name] = Reference.wrapReference(val, prop);
168
169
  // when propagation from inside hydration, we set the FK to the entity data immediately
169
170
  if (val && hydrator.isRunning() && wrapped.__originalEntityData && prop.owner) {
170
- wrapped.__originalEntityData[prop.name] = Utils.getPrimaryKeyValues(val, prop.targetMeta, true);
171
+ const targetMeta = prop.targetMeta ?? helper(entity)?.__meta;
172
+ if (targetMeta) {
173
+ wrapped.__originalEntityData[prop.name] = Utils.getPrimaryKeyValues(val, targetMeta, true);
174
+ }
171
175
  }
172
176
  EntityHelper.propagate(meta, entity, this, prop, Reference.unwrapReference(val), old);
173
177
  },
@@ -1,7 +1,10 @@
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 {
7
10
  collectionUpdates;
@@ -130,9 +133,10 @@ 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
- // When targetKey is set, use that property value instead of the PK identifier
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) {
136
140
  let value = prop.targetKey ? target[prop.targetKey] : target.__helper.__identifier;
137
141
  /* v8 ignore next */
138
142
  if (prop.targetKey && prop.targetMeta) {
@@ -141,7 +145,13 @@ export class ChangeSetComputer {
141
145
  value = targetProp.customType.convertToDatabaseValue(value, this.platform, { mode: 'serialization' });
142
146
  }
143
147
  }
144
- Utils.setPayloadProperty(changeSet.payload, changeSet.meta, prop, value, idx);
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
+ }
145
155
  }
146
156
  });
147
157
  }
@@ -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';
@@ -376,6 +377,12 @@ export class ChangeSetPersister {
376
377
  changeSet.payload[prop.name] = value.getValue();
377
378
  return;
378
379
  }
380
+ if (value instanceof PolymorphicRef) {
381
+ if (value.id instanceof EntityIdentifier) {
382
+ value.id = value.id.getValue();
383
+ }
384
+ return;
385
+ }
379
386
  if (Array.isArray(value) && value.every(item => item instanceof EntityIdentifier)) {
380
387
  changeSet.payload[prop.name] = value.map(item => item.getValue());
381
388
  return;
@@ -77,7 +77,10 @@ export class UnitOfWork {
77
77
  continue;
78
78
  }
79
79
  if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && Utils.isPlainObject(data[prop.name])) {
80
- data[prop.name] = Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta, true);
80
+ // Skip polymorphic relations - they use PolymorphicRef wrapper
81
+ if (!prop.polymorphic) {
82
+ data[prop.name] = Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta, true);
83
+ }
81
84
  }
82
85
  else if (prop.kind === ReferenceKind.EMBEDDED && !prop.object && Utils.isPlainObject(data[prop.name])) {
83
86
  for (const p of prop.targetMeta.props) {
@@ -262,6 +265,7 @@ export class UnitOfWork {
262
265
  if (!cs || this.checkUniqueProps(cs)) {
263
266
  return;
264
267
  }
268
+ /* v8 ignore next */
265
269
  if (type) {
266
270
  cs.type = type;
267
271
  }
@@ -1013,7 +1017,14 @@ export class UnitOfWork {
1013
1017
  set.forEach(meta => calc.addNode(meta._id));
1014
1018
  for (const meta of set) {
1015
1019
  for (const prop of meta.relations) {
1016
- calc.discoverProperty(prop, meta._id);
1020
+ if (prop.polymorphTargets) {
1021
+ for (const targetMeta of prop.polymorphTargets) {
1022
+ calc.discoverProperty({ ...prop, targetMeta }, meta._id);
1023
+ }
1024
+ }
1025
+ else {
1026
+ calc.discoverProperty(prop, meta._id);
1027
+ }
1017
1028
  }
1018
1029
  }
1019
1030
  return calc.sort().map(id => this.metadata.getById(id));
@@ -85,6 +85,11 @@ export declare class EntityComparator {
85
85
  private getPropertyComparator;
86
86
  private wrap;
87
87
  private safeKey;
88
+ /**
89
+ * Sets the toArray helper in the context if not already set.
90
+ * Used for converting composite PKs to arrays.
91
+ */
92
+ private setToArrayHelper;
88
93
  /**
89
94
  * perf: used to generate list of comparable properties during discovery, so we speed up the runtime comparison
90
95
  */
@@ -4,6 +4,7 @@ import { compareArrays, compareBooleans, compareBuffers, compareObjects, equals,
4
4
  import { JsonType } from '../types/JsonType.js';
5
5
  import { Raw } from './RawQueryFragment.js';
6
6
  import { EntityIdentifier } from '../entity/EntityIdentifier.js';
7
+ import { PolymorphicRef } from '../entity/PolymorphicRef.js';
7
8
  export class EntityComparator {
8
9
  metadata;
9
10
  platform;
@@ -285,6 +286,7 @@ export class EntityComparator {
285
286
  }
286
287
  const lines = [];
287
288
  const context = new Map();
289
+ context.set('PolymorphicRef', PolymorphicRef);
288
290
  const tz = this.platform.getTimezone();
289
291
  const parseDate = (key, value, padding = '') => {
290
292
  lines.push(`${padding} if (${value} == null || ${value} instanceof Date) {`);
@@ -311,12 +313,28 @@ export class EntityComparator {
311
313
  if (!prop.fieldNames) {
312
314
  continue;
313
315
  }
316
+ if (prop.polymorphic && prop.fieldNames.length >= 2) {
317
+ const discriminatorField = prop.fieldNames[0];
318
+ const idFields = prop.fieldNames.slice(1);
319
+ lines.push(`${padding} if (${prop.fieldNames.map(field => `typeof ${this.propName(field)} === 'undefined'`).join(' && ')}) {`);
320
+ lines.push(`${padding} } else if (${prop.fieldNames.map(field => `${this.propName(field)} != null`).join(' && ')}) {`);
321
+ if (idFields.length === 1) {
322
+ lines.push(`${padding} ret${this.wrap(prop.name)} = new PolymorphicRef(${this.propName(discriminatorField)}, ${this.propName(idFields[0])});`);
323
+ }
324
+ else {
325
+ lines.push(`${padding} ret${this.wrap(prop.name)} = new PolymorphicRef(${this.propName(discriminatorField)}, [${idFields.map(f => this.propName(f)).join(', ')}]);`);
326
+ }
327
+ lines.push(...prop.fieldNames.map(field => `${padding} ${this.propName(field, 'mapped')} = true;`));
328
+ lines.push(`${padding} } else if (${prop.fieldNames.map(field => `${this.propName(field)} == null`).join(' && ')}) {\n${padding} ret${this.wrap(prop.name)} = null;`);
329
+ lines.push(...prop.fieldNames.map(field => `${padding} ${this.propName(field, 'mapped')} = true;`), ' }');
330
+ continue;
331
+ }
314
332
  if (prop.targetMeta && prop.fieldNames.length > 1) {
315
333
  lines.push(`${padding} if (${prop.fieldNames.map(field => `typeof ${this.propName(field)} === 'undefined'`).join(' && ')}) {`);
316
334
  lines.push(`${padding} } else if (${prop.fieldNames.map(field => `${this.propName(field)} != null`).join(' && ')}) {`);
317
335
  lines.push(`${padding} ret${this.wrap(prop.name)} = ${this.createCompositeKeyArray(prop)};`);
318
336
  lines.push(...prop.fieldNames.map(field => `${padding} ${this.propName(field, 'mapped')} = true;`));
319
- lines.push(`${padding} } else if (${prop.fieldNames.map(field => `${this.propName(field)} == null`).join(' && ')}) {\n ret${this.wrap(prop.name)} = null;`);
337
+ lines.push(`${padding} } else if (${prop.fieldNames.map(field => `${this.propName(field)} == null`).join(' && ')}) {\n${padding} ret${this.wrap(prop.name)} = null;`);
320
338
  lines.push(...prop.fieldNames.map(field => `${padding} ${this.propName(field, 'mapped')} = true;`), ' }');
321
339
  continue;
322
340
  }
@@ -517,6 +535,27 @@ export class EntityComparator {
517
535
  ret += ` ret${dataKey} = entity${entityKey};\n`;
518
536
  }
519
537
  }
538
+ else if (prop.polymorphic) {
539
+ const discriminatorMapKey = `discriminatorMapReverse_${prop.name}`;
540
+ const reverseMap = new Map();
541
+ for (const [key, value] of Object.entries(prop.discriminatorMap)) {
542
+ reverseMap.set(value, key);
543
+ }
544
+ context.set(discriminatorMapKey, reverseMap);
545
+ this.setToArrayHelper(context);
546
+ context.set('EntityIdentifier', EntityIdentifier);
547
+ context.set('PolymorphicRef', PolymorphicRef);
548
+ ret += ` if (entity${entityKey} === null) {\n`;
549
+ ret += ` ret${dataKey} = null;\n`;
550
+ ret += ` } else if (typeof entity${entityKey} !== 'undefined') {\n`;
551
+ ret += ` const val${level} = entity${entityKey}${unwrap};\n`;
552
+ ret += ` const discriminator = ${discriminatorMapKey}.get(val${level}?.constructor);\n`;
553
+ ret += ` const pk = val${level}?.__helper?.__identifier && !val${level}?.__helper?.hasPrimaryKey()\n`;
554
+ ret += ` ? val${level}.__helper.__identifier\n`;
555
+ ret += ` : toArray(val${level}?.__helper?.getPrimaryKey(true));\n`;
556
+ ret += ` ret${dataKey} = new PolymorphicRef(discriminator, pk);\n`;
557
+ ret += ` }\n`;
558
+ }
520
559
  else if (prop.targetKey) {
521
560
  // When targetKey is set, extract that property value instead of the PK
522
561
  const targetProp = prop.targetMeta?.properties[prop.targetKey];
@@ -535,13 +574,7 @@ export class EntityComparator {
535
574
  ret += ` }\n`;
536
575
  }
537
576
  else {
538
- const toArray = (val) => {
539
- if (Utils.isPlainObject(val)) {
540
- return Object.values(val).map(v => toArray(v));
541
- }
542
- return val;
543
- };
544
- context.set('toArray', toArray);
577
+ this.setToArrayHelper(context);
545
578
  context.set('EntityIdentifier', EntityIdentifier);
546
579
  ret += ` if (entity${entityKey} === null) {\n`;
547
580
  ret += ` ret${dataKey} = null;\n`;
@@ -613,12 +646,17 @@ export class EntityComparator {
613
646
  getPropertyComparator(prop, context) {
614
647
  let type = prop.type.toLowerCase();
615
648
  if (prop.kind !== ReferenceKind.SCALAR && prop.kind !== ReferenceKind.EMBEDDED) {
616
- const meta2 = prop.targetMeta;
617
- if (meta2.primaryKeys.length > 1) {
618
- type = 'array';
649
+ if (prop.polymorphic) {
650
+ type = 'object';
619
651
  }
620
652
  else {
621
- type = meta2.getPrimaryProp().type.toLowerCase();
653
+ const meta2 = prop.targetMeta;
654
+ if (meta2.primaryKeys.length > 1) {
655
+ type = 'array';
656
+ }
657
+ else {
658
+ type = meta2.getPrimaryProp().type.toLowerCase();
659
+ }
622
660
  }
623
661
  }
624
662
  if (prop.customType) {
@@ -670,6 +708,22 @@ export class EntityComparator {
670
708
  safeKey(key) {
671
709
  return key.replace(/\W/g, '_');
672
710
  }
711
+ /**
712
+ * Sets the toArray helper in the context if not already set.
713
+ * Used for converting composite PKs to arrays.
714
+ */
715
+ setToArrayHelper(context) {
716
+ if (context.has('toArray')) {
717
+ return;
718
+ }
719
+ const toArray = (val) => {
720
+ if (Utils.isPlainObject(val)) {
721
+ return Object.values(val).map(v => toArray(v));
722
+ }
723
+ return val;
724
+ };
725
+ context.set('toArray', toArray);
726
+ }
673
727
  /**
674
728
  * perf: used to generate list of comparable properties during discovery, so we speed up the runtime comparison
675
729
  */
@@ -5,6 +5,10 @@ import type { FilterOptions } from '../drivers/IDatabaseDriver.js';
5
5
  /** @internal */
6
6
  export declare class QueryHelper {
7
7
  static readonly SUPPORTED_OPERATORS: string[];
8
+ /**
9
+ * Finds the discriminator value (key) for a given entity class in a discriminator map.
10
+ */
11
+ static findDiscriminatorValue<T>(discriminatorMap: Dictionary<T>, targetClass: T): string | undefined;
8
12
  static processParams(params: unknown): any;
9
13
  static processObjectParams<T extends Dictionary>(params?: T): T;
10
14
  /**
@@ -7,6 +7,12 @@ import { isRaw, Raw } from './RawQueryFragment.js';
7
7
  /** @internal */
8
8
  export class QueryHelper {
9
9
  static SUPPORTED_OPERATORS = ['>', '<', '<=', '>=', '!', '!='];
10
+ /**
11
+ * Finds the discriminator value (key) for a given entity class in a discriminator map.
12
+ */
13
+ static findDiscriminatorValue(discriminatorMap, targetClass) {
14
+ return Object.entries(discriminatorMap).find(([, cls]) => cls === targetClass)?.[0];
15
+ }
10
16
  static processParams(params) {
11
17
  if (Reference.isReference(params)) {
12
18
  params = params.unwrap();
@@ -62,7 +68,10 @@ export class QueryHelper {
62
68
  for (const k of keys) {
63
69
  const value = where[k];
64
70
  const prop = meta.properties[k];
65
- if (!prop || ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
71
+ // Polymorphic relations use multiple columns (discriminator + FK), so they cannot
72
+ // participate in the standard single-column FK expansion. Query by discriminator
73
+ // column directly instead, e.g. { likeableType: 'post', likeableId: 1 }.
74
+ if (!prop || ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) || prop.polymorphic) {
66
75
  continue;
67
76
  }
68
77
  const op = this.liftGroupOperators(value, prop.targetMeta, metadata, k);
package/utils/Utils.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { CompiledFunctions, Dictionary, EntityData, EntityDictionary, EntityKey, EntityMetadata, EntityName, EntityProperty, Primary } from '../typings.js';
2
2
  import type { Collection } from '../entity/Collection.js';
3
3
  import type { Platform } from '../platforms/Platform.js';
4
- import type { ScalarReference } from '../entity/Reference.js';
4
+ import { type ScalarReference } from '../entity/Reference.js';
5
5
  import { type RawQueryFragmentSymbol } from './RawQueryFragment.js';
6
6
  export declare function compareObjects(a: any, b: any): boolean;
7
7
  export declare function compareArrays(a: any[] | string, b: any[] | string): boolean;
package/utils/Utils.js CHANGED
@@ -123,7 +123,7 @@ export function parseJsonSafe(value) {
123
123
  }
124
124
  export class Utils {
125
125
  static PK_SEPARATOR = '~~~';
126
- static #ORM_VERSION = '7.0.0-dev.225';
126
+ static #ORM_VERSION = '7.0.0-dev.227';
127
127
  /**
128
128
  * Checks if the argument is instance of `Object`. Returns false for arrays.
129
129
  */