@mikro-orm/core 7.0.0-dev.224 → 7.0.0-dev.226

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.d.ts CHANGED
@@ -295,7 +295,14 @@ export type EntityName<T = any> = EntityClass<T> | EntityCtor<T> | EntitySchema<
295
295
  export type GetRepository<Entity extends {
296
296
  [k: PropertyKey]: any;
297
297
  }, Fallback> = Entity[typeof EntityRepositoryType] extends EntityRepository<any> | undefined ? NonNullable<Entity[typeof EntityRepositoryType]> : Fallback;
298
- export type EntityDataPropValue<T> = T | Primary<T>;
298
+ type PolymorphicPrimaryInner<T> = T extends object ? Primary<T> extends readonly [infer First, infer Second, ...infer Rest] ? readonly [string, First, Second, ...Rest] | [string, First, Second, ...Rest] : readonly [string, Primary<T>] | [string, Primary<T>] : never;
299
+ /**
300
+ * Tuple format for polymorphic FK values: [discriminator, ...pkValues]
301
+ * Distributes over unions, so `Post | Comment` becomes `['post', number] | ['comment', number]`
302
+ * For composite keys like [tenantId, orgId], becomes ['discriminator', tenantId, orgId]
303
+ */
304
+ export type PolymorphicPrimary<T> = true extends IsUnion<T> ? PolymorphicPrimaryInner<T> : never;
305
+ export type EntityDataPropValue<T> = T | Primary<T> | PolymorphicPrimary<T>;
299
306
  type ExpandEntityProp<T, C extends boolean = false> = T extends Record<string, any> ? {
300
307
  [K in keyof T as CleanKeys<T, K>]?: EntityDataProp<ExpandProperty<T[K]>, C> | EntityDataPropValue<ExpandProperty<T[K]>> | null;
301
308
  } | EntityDataPropValue<ExpandProperty<T>> : T;
@@ -308,16 +315,17 @@ type ExpandRequiredEntityPropObject<T, I = never, C extends boolean = false> = {
308
315
  type NonArrayObject = object & {
309
316
  [Symbol.iterator]?: never;
310
317
  };
311
- export type EntityDataProp<T, C extends boolean> = T extends Date ? string | Date : T extends Scalar ? T : T extends {
318
+ export type EntityDataProp<T, C extends boolean> = T extends Date ? string | Date : T extends Scalar ? T : T extends ScalarReference<infer U> ? EntityDataProp<U, C> : T extends {
312
319
  __runtime?: infer Runtime;
313
320
  __raw?: infer Raw;
314
- } ? (C extends true ? Raw : Runtime) : T extends ReferenceShape<infer U> ? EntityDataNested<U, C> : T extends ScalarReference<infer U> ? EntityDataProp<U, C> : T extends CollectionShape<infer U> ? U | U[] | EntityDataNested<U & object, C> | EntityDataNested<U & object, C>[] : T extends readonly (infer U)[] ? U extends NonArrayObject ? U | U[] | EntityDataNested<U, C> | EntityDataNested<U, C>[] : U[] | EntityDataNested<U, C>[] : EntityDataNested<T, C>;
315
- export type RequiredEntityDataProp<T, O, C extends boolean> = T extends Date ? string | Date : Exclude<T, null> extends RequiredNullable.Brand ? T | null : T extends Scalar ? T : T extends {
321
+ } ? (C extends true ? Raw : Runtime) : T extends ReferenceShape<infer U> ? EntityDataNested<U, C> : T extends CollectionShape<infer U> ? U | U[] | EntityDataNested<U & object, C> | EntityDataNested<U & object, C>[] : T extends readonly (infer U)[] ? U extends NonArrayObject ? U | U[] | EntityDataNested<U, C> | EntityDataNested<U, C>[] : U[] | EntityDataNested<U, C>[] : EntityDataNested<T, C>;
322
+ export type RequiredEntityDataProp<T, O, C extends boolean> = T extends Date ? string | Date : Exclude<T, null> extends RequiredNullable.Brand ? T | null : T extends Scalar ? T : T extends ScalarReference<infer U> ? RequiredEntityDataProp<U, O, C> : T extends {
316
323
  __runtime?: infer Runtime;
317
324
  __raw?: infer Raw;
318
- } ? (C extends true ? Raw : Runtime) : T extends ReferenceShape<infer U> ? RequiredEntityDataNested<U, O, C> : T extends ScalarReference<infer U> ? RequiredEntityDataProp<U, O, C> : T extends CollectionShape<infer U> ? U | U[] | RequiredEntityDataNested<U & object, O, C> | RequiredEntityDataNested<U & object, O, C>[] : T extends readonly (infer U)[] ? U extends NonArrayObject ? U | U[] | RequiredEntityDataNested<U, O, C> | RequiredEntityDataNested<U, O, C>[] : U[] | RequiredEntityDataNested<U, O, C>[] : RequiredEntityDataNested<T, O, C>;
325
+ } ? (C extends true ? Raw : Runtime) : T extends ReferenceShape<infer U> ? RequiredEntityDataNested<U, O, C> : T extends CollectionShape<infer U> ? U | U[] | RequiredEntityDataNested<U & object, O, C> | RequiredEntityDataNested<U & object, O, C>[] : T extends readonly (infer U)[] ? U extends NonArrayObject ? U | U[] | RequiredEntityDataNested<U, O, C> | RequiredEntityDataNested<U, O, C>[] : U[] | RequiredEntityDataNested<U, O, C>[] : RequiredEntityDataNested<T, O, C>;
319
326
  export type EntityDataNested<T, C extends boolean = false> = T extends undefined ? never : T extends any[] ? Readonly<T> : EntityData<T, C> | ExpandEntityProp<T, C>;
320
- type EntityDataItem<T, C extends boolean> = C extends false ? T | EntityDataProp<T, C> | Raw | null : EntityDataProp<T, C> | Raw | null;
327
+ type UnwrapScalarRef<T> = T extends ScalarReference<infer U> ? U : T;
328
+ type EntityDataItem<T, C extends boolean> = C extends false ? UnwrapScalarRef<T> | EntityDataProp<T, C> | Raw | null : EntityDataProp<T, C> | Raw | null;
321
329
  export type RequiredEntityDataNested<T, O, C extends boolean> = T extends any[] ? Readonly<T> : RequiredEntityData<T, O> | ExpandRequiredEntityProp<T, O, C>;
322
330
  type ExplicitlyOptionalProps<T> = (T extends {
323
331
  [OptionalProps]?: infer K;
@@ -338,9 +346,9 @@ export type EntityData<T, C extends boolean = false> = {
338
346
  [K in EntityKey<T>]?: EntityDataItem<T[K] & {}, C>;
339
347
  };
340
348
  export type RequiredEntityData<T, I = never, C extends boolean = false> = {
341
- [K in keyof T as RequiredKeys<T, K, I>]: T[K] | RequiredEntityDataProp<T[K], T, C> | Primary<T[K]> | Raw;
349
+ [K in keyof T as RequiredKeys<T, K, I>]: T[K] | RequiredEntityDataProp<T[K], T, C> | Primary<T[K]> | PolymorphicPrimary<T[K]> | Raw;
342
350
  } & {
343
- [K in keyof T as OptionalKeys<T, K, I>]?: T[K] | RequiredEntityDataProp<T[K], T, C> | Primary<T[K]> | Raw | null;
351
+ [K in keyof T as OptionalKeys<T, K, I>]?: T[K] | RequiredEntityDataProp<T[K], T, C> | Primary<T[K]> | PolymorphicPrimary<T[K]> | Raw | null;
344
352
  };
345
353
  export type EntityDictionary<T> = EntityData<T> & Record<any, any>;
346
354
  type ExtractEagerProps<T> = T extends {
@@ -375,9 +383,9 @@ type PreferExplicitConfig<E, I> = IsNever<E, I, E>;
375
383
  type PrimaryOrObject<T, U, C extends TypeConfig> = PreferExplicitConfig<C, ExtractConfig<T>>['forceObject'] extends true ? {
376
384
  [K in PrimaryProperty<U> & keyof U]: U[K];
377
385
  } : Primary<U>;
378
- export type EntityDTOProp<E, T, C extends TypeConfig = never> = T extends Scalar ? T : T extends {
386
+ export type EntityDTOProp<E, T, C extends TypeConfig = never> = T extends Scalar ? T : T extends ScalarReference<infer U> ? U : T extends {
379
387
  __serialized?: infer U;
380
- } ? (IsUnknown<U> extends false ? U : T) : T extends LoadedReferenceShape<infer U> ? EntityDTO<U, C> : T extends ReferenceShape<infer U> ? PrimaryOrObject<E, U, C> : T extends ScalarReference<infer U> ? U : T extends LoadedCollectionShape<infer U> ? EntityDTO<U & object, C>[] : T extends CollectionShape<infer U> ? PrimaryOrObject<E, U & object, C>[] : T extends readonly (infer U)[] ? U extends Scalar ? T : EntityDTOProp<E, U, C>[] : T extends Relation<T> ? EntityDTO<T, C> : T;
388
+ } ? (IsUnknown<U> extends false ? U : T) : T extends LoadedReferenceShape<infer U> ? EntityDTO<U, C> : T extends ReferenceShape<infer U> ? PrimaryOrObject<E, U, C> : T extends LoadedCollectionShape<infer U> ? EntityDTO<U & object, C>[] : T extends CollectionShape<infer U> ? PrimaryOrObject<E, U & object, C>[] : T extends readonly (infer U)[] ? U extends Scalar ? T : EntityDTOProp<E, U, C>[] : T extends Relation<T> ? EntityDTO<T, C> : T;
381
389
  type UnwrapLoadedEntity<T> = T extends {
382
390
  [__loadedType]?: infer U;
383
391
  } ? NonNullable<U> : T;
@@ -452,6 +460,11 @@ export interface EntityProperty<Owner = any, Target = any> {
452
460
  embeddable: EntityClass<Owner>;
453
461
  embeddedProps: Dictionary<EntityProperty>;
454
462
  discriminatorColumn?: string;
463
+ discriminator?: string;
464
+ polymorphic?: boolean;
465
+ polymorphTargets?: EntityMetadata[];
466
+ discriminatorMap?: Dictionary<EntityClass<Target>>;
467
+ discriminatorValue?: string;
455
468
  object?: boolean;
456
469
  index?: boolean | string;
457
470
  unique?: boolean | string;
@@ -613,6 +626,8 @@ export interface EntityMetadata<Entity = any, Class extends EntityCtor<Entity> =
613
626
  polymorphs?: EntityMetadata[];
614
627
  root: EntityMetadata<Entity>;
615
628
  definedProperties: Dictionary;
629
+ /** For polymorphic M:N pivot tables, maps discriminator values to entity classes */
630
+ polymorphicDiscriminatorMap?: Dictionary<EntityClass>;
616
631
  hasTriggers?: boolean;
617
632
  /** @internal can be used for computed numeric cache keys */
618
633
  readonly _id: number;
@@ -852,10 +867,10 @@ type ExtractStringKeys<T> = {
852
867
  type StringKeys<T, E extends string = never> = T extends object ? ExtractStringKeys<ExtractType<T>> | E : never;
853
868
  type GetStringKey<T, K extends StringKeys<T, string>, E extends string> = K extends keyof T ? ExtractType<T[K]> : (K extends E ? keyof T : never);
854
869
  type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
855
- type CollectionKeys<T> = T extends object ? {
856
- [K in keyof T]-?: T[K] extends CollectionShape ? IsAny<T[K]> extends true ? never : K & string : never;
870
+ type RelationKeys<T> = T extends object ? {
871
+ [K in keyof T]-?: CleanKeys<T, K, true>;
857
872
  }[keyof T] & {} : never;
858
- export type AutoPath<O, P extends string | boolean, E extends string = never, D extends Prev[number] = 9> = P extends boolean ? P : [D] extends [never] ? never : P extends any ? P extends string ? P extends `${infer A}.${infer B}` ? A extends StringKeys<O, E> ? `${A}.${AutoPath<NonNullable<GetStringKey<O, A, E>>, B, E, Prev[D]>}` : never : P extends StringKeys<O, E> ? (NonNullable<GetStringKey<O, P & StringKeys<O, E>, E>> extends unknown ? Exclude<P, `${string}.`> : never) | (StringKeys<NonNullable<GetStringKey<O, P & StringKeys<O, E>, E>>, E> extends never ? never : `${P & string}.`) : StringKeys<O, E> | `${CollectionKeys<O>}:ref` : never : never;
873
+ export type AutoPath<O, P extends string | boolean, E extends string = never, D extends Prev[number] = 9> = P extends boolean ? P : [D] extends [never] ? never : P extends any ? P extends string ? P extends `${infer A}.${infer B}` ? A extends StringKeys<O, E> ? `${A}.${AutoPath<NonNullable<GetStringKey<O, A, E>>, B, E, Prev[D]>}` : never : P extends StringKeys<O, E> ? (NonNullable<GetStringKey<O, P & StringKeys<O, E>, E>> extends unknown ? Exclude<P, `${string}.`> : never) | (StringKeys<NonNullable<GetStringKey<O, P & StringKeys<O, E>, E>>, E> extends never ? never : `${P & string}.`) : StringKeys<O, E> | `${RelationKeys<O>}:ref` : never : never;
859
874
  export type UnboxArray<T> = T extends any[] ? ArrayElement<T> : T;
860
875
  export type ArrayElement<ArrayType extends unknown[]> = ArrayType extends (infer ElementType)[] ? ElementType : never;
861
876
  export type ExpandProperty<T> = T extends ReferenceShape<infer U> ? NonNullable<U> : T extends CollectionShape<infer U> ? NonNullable<U> : T extends (infer U)[] ? NonNullable<U> : NonNullable<T>;
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) {
@@ -254,7 +257,7 @@ export class UnitOfWork {
254
257
  }
255
258
  computeChangeSet(entity, type) {
256
259
  const wrapped = helper(entity);
257
- if (type) {
260
+ if (type === ChangeSetType.DELETE || type === ChangeSetType.DELETE_EARLY) {
258
261
  this.changeSets.set(entity, new ChangeSet(entity, type, {}, wrapped.__meta));
259
262
  return;
260
263
  }
@@ -262,6 +265,10 @@ export class UnitOfWork {
262
265
  if (!cs || this.checkUniqueProps(cs)) {
263
266
  return;
264
267
  }
268
+ /* v8 ignore next */
269
+ if (type) {
270
+ cs.type = type;
271
+ }
265
272
  this.initIdentifier(entity);
266
273
  this.changeSets.set(entity, cs);
267
274
  this.persistStack.delete(entity);
@@ -1010,7 +1017,14 @@ export class UnitOfWork {
1010
1017
  set.forEach(meta => calc.addNode(meta._id));
1011
1018
  for (const meta of set) {
1012
1019
  for (const prop of meta.relations) {
1013
- 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
+ }
1014
1028
  }
1015
1029
  }
1016
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.224';
126
+ static #ORM_VERSION = '7.0.0-dev.226';
127
127
  /**
128
128
  * Checks if the argument is instance of `Object`. Returns false for arrays.
129
129
  */