@mikro-orm/core 7.2.0-dev.13 → 7.2.0-dev.14

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/EntityManager.js CHANGED
@@ -1,4 +1,4 @@
1
- import { getOnConflictReturningFields, getWhereCondition, resetUntouchedCollections } from './utils/upsert-utils.js';
1
+ import { getOnConflictReturningFields, getOnCreateGeneratedFields, getWhereCondition, resetUntouchedCollections, } from './utils/upsert-utils.js';
2
2
  import { Utils } from './utils/Utils.js';
3
3
  import { Cursor } from './utils/Cursor.js';
4
4
  import { QueryHelper } from './utils/QueryHelper.js';
@@ -260,7 +260,7 @@ export class EntityManager {
260
260
  addFilter(options) {
261
261
  options = { ...options };
262
262
  if (options.entity) {
263
- options.entity = Utils.asArray(options.entity).map(n => Utils.className(n));
263
+ options.entity = Utils.asArray(options.entity).map(n => Utils.classOrName(n));
264
264
  }
265
265
  options.default ??= true;
266
266
  this.getContext(false).#filters[options.name] = options;
@@ -793,6 +793,7 @@ export class EntityManager {
793
793
  }
794
794
  const meta = this.metadata.get(entityName);
795
795
  const convertCustomTypes = !Utils.isEntity(data);
796
+ let generatedFields = [];
796
797
  if (Utils.isEntity(data)) {
797
798
  entity = data;
798
799
  if (helper(entity).__managed && helper(entity).__em === em && !this.config.get('upsertManaged')) {
@@ -800,6 +801,7 @@ export class EntityManager {
800
801
  return entity;
801
802
  }
802
803
  where = helper(entity).getPrimaryKey();
804
+ generatedFields = getOnCreateGeneratedFields(meta, entity);
803
805
  em.#entityFactory.assignDefaultValues(entity, meta);
804
806
  data = em.#comparator.prepareEntity(entity);
805
807
  }
@@ -812,6 +814,7 @@ export class EntityManager {
812
814
  return em.assign(exists, data);
813
815
  }
814
816
  }
817
+ generatedFields = getOnCreateGeneratedFields(meta, data);
815
818
  em.#entityFactory.assignDefaultValues(data, meta, true);
816
819
  for (const key of Object.keys(data)) {
817
820
  const prop = meta.properties[key];
@@ -820,6 +823,10 @@ export class EntityManager {
820
823
  }
821
824
  }
822
825
  }
826
+ // `onCreate` generated values are for the insert clause only, they must not overwrite an existing row
827
+ if (generatedFields.length > 0 && !options.onConflictMergeFields) {
828
+ options.onConflictExcludeFields = [...(options.onConflictExcludeFields ?? []), ...generatedFields];
829
+ }
823
830
  where = getWhereCondition(meta, options.onConflictFields, data, where).where;
824
831
  data = QueryHelper.processObjectParams(data);
825
832
  validateParams(data, 'insert data');
@@ -939,6 +946,7 @@ export class EntityManager {
939
946
  }
940
947
  const meta = this.metadata.get(entityName);
941
948
  const convertCustomTypes = !Utils.isEntity(data[0]);
949
+ const generatedFields = new Set();
942
950
  const allData = [];
943
951
  const allWhere = [];
944
952
  const entities = new Map();
@@ -956,6 +964,7 @@ export class EntityManager {
956
964
  continue;
957
965
  }
958
966
  where = helper(entity).getPrimaryKey();
967
+ getOnCreateGeneratedFields(meta, entity).forEach(field => generatedFields.add(field));
959
968
  em.#entityFactory.assignDefaultValues(entity, meta);
960
969
  entitiesByAllDataIdx.set(allData.length, entity);
961
970
  row = em.#comparator.prepareEntity(entity);
@@ -972,6 +981,7 @@ export class EntityManager {
972
981
  continue;
973
982
  }
974
983
  }
984
+ getOnCreateGeneratedFields(meta, row).forEach(field => generatedFields.add(field));
975
985
  em.#entityFactory.assignDefaultValues(row, meta, true);
976
986
  for (const key of Object.keys(row)) {
977
987
  const prop = meta.properties[key];
@@ -1000,6 +1010,10 @@ export class EntityManager {
1000
1010
  if (entities.size === data.length) {
1001
1011
  return [...entities.keys()];
1002
1012
  }
1013
+ // `onCreate` generated values are for the insert clause only, they must not overwrite existing rows
1014
+ if (generatedFields.size > 0 && !options.onConflictMergeFields) {
1015
+ options.onConflictExcludeFields = [...(options.onConflictExcludeFields ?? []), ...generatedFields];
1016
+ }
1003
1017
  if (em.eventManager.hasListeners(EventType.beforeUpsert, meta)) {
1004
1018
  for (const dto of data) {
1005
1019
  const entity = entitiesByData.get(dto) ?? dto;
@@ -1649,8 +1663,9 @@ export class EntityManager {
1649
1663
  }
1650
1664
  // For TPT inheritance, check the entity's own properties, not just the root's
1651
1665
  // For STI, meta.properties includes all properties anyway
1652
- const ret = p in meta.properties;
1653
- if (parts.length > 0) {
1666
+ // use an own-property check so inherited `Object.prototype` members (e.g. `__proto__`, `constructor`) are not treated as populatable
1667
+ const ret = Object.hasOwn(meta.properties, p);
1668
+ if (ret && parts.length > 0) {
1654
1669
  return this.canPopulate(meta.properties[p].targetMeta.class, parts.join('.'));
1655
1670
  }
1656
1671
  return ret;
@@ -2109,7 +2124,11 @@ export class EntityManager {
2109
2124
  ]) {
2110
2125
  delete opts[k];
2111
2126
  }
2112
- return [Utils.className(entityName), method, opts, where];
2127
+ // the table name (plus discriminator value for STI) is stable across builds and processes,
2128
+ // unlike class names, which minifiers can mangle to the same short name for two entities
2129
+ const meta = this.metadata.find(entityName);
2130
+ const key = meta?.tableName ? [meta.schema, meta.tableName, meta.discriminatorValue] : Utils.className(entityName);
2131
+ return [key, method, opts, where];
2113
2132
  }
2114
2133
  /**
2115
2134
  * @internal
@@ -345,6 +345,7 @@ export interface CountByOptions<T extends object> {
345
345
  filters?: FilterOptions;
346
346
  having?: FilterQuery<T>;
347
347
  schema?: string;
348
+ connectionType?: ConnectionType;
348
349
  flushMode?: FlushMode | `${FlushMode}`;
349
350
  loggerContext?: LogContext;
350
351
  logging?: LoggingOptions;
@@ -77,6 +77,12 @@ export class EntityFactory {
77
77
  }
78
78
  }
79
79
  data = { ...data };
80
+ if (options.newEntity && meta2.root.inheritanceType === 'sti' && meta2.discriminatorValue != null) {
81
+ const prop = meta2.properties[meta2.root.discriminatorColumn];
82
+ if (prop && prop.userDefined !== false) {
83
+ data[prop.name] ??= meta2.discriminatorValue;
84
+ }
85
+ }
80
86
  const entity = exists ?? this.createEntity(data, meta2, options);
81
87
  wrapped = helper(entity);
82
88
  wrapped.__processing = true;
@@ -1,5 +1,5 @@
1
1
  import { QueryHelper } from '../utils/QueryHelper.js';
2
- import { Utils } from '../utils/Utils.js';
2
+ import { DANGEROUS_PROPERTY_NAMES, Utils } from '../utils/Utils.js';
3
3
  import { ValidationError } from '../errors.js';
4
4
  import { LoadStrategy, PopulatePath, ReferenceKind, } from '../enums.js';
5
5
  import { Reference } from './Reference.js';
@@ -124,10 +124,10 @@ export class EntityLoader {
124
124
  mergeNestedPopulate(populate) {
125
125
  const tmp = populate.reduce((ret, item) => {
126
126
  /* v8 ignore next */
127
- if (item.field === PopulatePath.ALL) {
127
+ if (item.field === PopulatePath.ALL || DANGEROUS_PROPERTY_NAMES.includes(item.field)) {
128
128
  return ret;
129
129
  }
130
- if (!ret[item.field]) {
130
+ if (!Object.hasOwn(ret, item.field)) {
131
131
  ret[item.field] = item;
132
132
  return ret;
133
133
  }
@@ -229,9 +229,10 @@ export class EntityLoader {
229
229
  }
230
230
  toPopulate.push(entity);
231
231
  }
232
- else if (refValue == null && !helper(entity).__loadedProperties.has(prop.name)) {
232
+ else if (refValue == null && !prop.object && !helper(entity).__loadedProperties.has(prop.name)) {
233
233
  // FK columns weren't loaded (partial loading) — need to re-fetch them.
234
234
  // If the property IS in __loadedProperties, the FK was loaded and is genuinely null.
235
+ // Object-embedded virtual props are skipped — they are populated via the embeddable instance.
235
236
  needsFkLoad.push(entity);
236
237
  }
237
238
  }
@@ -445,7 +446,10 @@ export class EntityLoader {
445
446
  }
446
447
  }
447
448
  }
448
- if ([ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && items.length !== children.length) {
449
+ // a missing target row means an orphaned reference, unless the query was narrowed by a populate condition
450
+ if ([ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) &&
451
+ items.length !== children.length &&
452
+ Utils.isEmpty(options.where)) {
449
453
  const nullVal = this.#em.config.get('forceUndefined') ? undefined : null;
450
454
  const itemsMap = new Set();
451
455
  const childrenMap = new Set();
@@ -667,7 +671,8 @@ export class EntityLoader {
667
671
  }
668
672
  return cond;
669
673
  });
670
- if (child.length > 0) {
674
+ // partial extraction from `$or` is unsound — the parent may have matched via a dropped branch
675
+ if (child.length > 0 && (op === '$and' || child.length === where[op].length)) {
671
676
  subCond[op] = child;
672
677
  }
673
678
  }
@@ -224,7 +224,11 @@ export class EntityRepository {
224
224
  }
225
225
  const entityName = entities[0].constructor.name;
226
226
  const repoType = Utils.className(this.entityName);
227
- if (entityName && repoType !== entityName) {
227
+ // compare class identity where possible, as minifiers can mangle two classes to the same name
228
+ const entityMeta = entities[0].__meta;
229
+ const repoMeta = this.getEntityManager().getMetadata?.().find(this.entityName);
230
+ const mismatch = entityMeta && repoMeta ? entityMeta.class !== repoMeta.class : entityName && repoType !== entityName;
231
+ if (mismatch) {
228
232
  throw ValidationError.fromWrongRepositoryType(entityName, repoType, method);
229
233
  }
230
234
  }
@@ -1,6 +1,6 @@
1
1
  import type { EntityManager } from '../EntityManager.js';
2
2
  import type { ColumnType, PropertyOptions, ReferenceOptions, EnumOptions, EmbeddedOptions, ManyToOneOptions, OneToManyOptions, OneToOneOptions, ManyToManyOptions, IndexColumnOptions } from '../metadata/types.js';
3
- import type { AnyString, GeneratedColumnCallback, Constructor, CheckCallback, FilterQuery, EntityName, Dictionary, EntityMetadata, PrimaryKeyProp, EntityRepositoryType, Hidden, Opt, Primary, EntityClass, EntitySchemaWithMeta, InferEntity, MaybeReturnType, Ref, LazyRef, IndexCallback, TriggerCallback, FormulaCallback, EntityCtor, IsNever, IWrappedEntity, DefineConfig, Config, MaybePromise, IndexHints, ExtractDefineEntityProperties } from '../typings.js';
3
+ import type { AnyString, GeneratedColumnCallback, Constructor, CheckCallback, FilterQuery, EntityName, Dictionary, EntityMetadata, PrimaryKeyProp, EntityRepositoryType, Hidden, Opt, Primary, EntityClass, EntitySchemaWithMeta, InferEntity, MaybeReturnType, Ref, LazyRef, IndexCallback, TriggerCallback, FormulaCallback, EntityCtor, IsNever, IWrappedEntity, DefineConfig, Config, MaybePromise, IndexHints, OptionalProps, ExtractDefineEntityProperties } from '../typings.js';
4
4
  import type { Raw } from '../utils/RawQueryFragment.js';
5
5
  import type { ScalarReference } from './Reference.js';
6
6
  import type { SerializeOptions } from '../serialization/EntitySerializer.js';
@@ -22,7 +22,7 @@ type HasKind<Options, K extends string> = Options extends {
22
22
  kind: infer X extends string;
23
23
  } ? X extends K ? true : false : false;
24
24
  /** Lightweight chain result type for property builders - reduces type instantiation cost by avoiding full class resolution. */
25
- export interface PropertyChain<Value, Options> {
25
+ export interface PropertyChain<in out Value, in out Options> {
26
26
  '~type'?: {
27
27
  value: Value;
28
28
  };
@@ -112,7 +112,7 @@ export interface PropertyChain<Value, Options> {
112
112
  getter(getter?: boolean): PropertyChain<Value, Options>;
113
113
  getterName(getterName: string): PropertyChain<Value, Options>;
114
114
  serializedPrimaryKey(serializedPrimaryKey?: boolean): PropertyChain<Value, Options>;
115
- serializer(serializer: (value: Value, options?: SerializeOptions<any>) => any): PropertyChain<Value, Options>;
115
+ serializer(serializer: (value: SerializerValue<Value, Options>, options?: SerializeOptions<any>) => any): PropertyChain<Value, Options>;
116
116
  serializedName(serializedName: string): PropertyChain<Value, Options>;
117
117
  groups(...groups: string[]): PropertyChain<Value, Options>;
118
118
  customOrder(...customOrder: string[] | number[] | boolean[]): PropertyChain<Value, Options>;
@@ -180,7 +180,7 @@ export interface PropertyChain<Value, Options> {
180
180
  foreignKeyName(foreignKeyName: string): HasKind<Options, 'm:1' | '1:m' | '1:1' | 'm:n'> extends true ? PropertyChain<Value, Options> : never;
181
181
  }
182
182
  /** @internal */
183
- export declare class UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys extends BuilderKeys> implements Record<Exclude<UniversalPropertyKeys, ExcludeKeys>, any> {
183
+ export declare class UniversalPropertyOptionsBuilder<in out Value, in out Options, in out IncludeKeys extends BuilderKeys> implements Record<Exclude<UniversalPropertyKeys, ExcludeKeys>, any> {
184
184
  '~options': Options;
185
185
  '~type'?: {
186
186
  value: Value;
@@ -627,7 +627,7 @@ type PartialWhere<TProperties, TBase> = string | FilterQuery<{
627
627
  [K in AllKeys<TProperties, TBase> & string]?: unknown;
628
628
  }>;
629
629
  /** Metadata descriptor for `defineEntity()`, combining entity options with property definitions. */
630
- export interface EntityMetadataWithProperties<TName extends string, TTableName extends string, TProperties extends Record<string, any>, TPK extends (keyof TProperties)[] | undefined = undefined, TBase = never, TRepository = never, TForceObject extends boolean = false, TDiscriminatorColumn extends string | undefined = undefined, TDiscriminatorValue extends string | number | undefined = undefined, TBaseDiscriminatorColumn extends string | undefined = undefined> extends Omit<Partial<EntityMetadata<InferEntityFromProperties<TProperties, TPK, TBase, TRepository>>>, 'properties' | 'extends' | 'primaryKeys' | 'hooks' | 'discriminator' | 'discriminatorColumn' | 'discriminatorValue' | 'versionProperty' | 'concurrencyCheckKeys' | 'serializedPrimaryKey' | 'indexes' | 'uniques' | 'triggers' | 'repository' | 'filters' | 'orderBy'> {
630
+ export interface EntityMetadataWithProperties<TName extends string, TTableName extends string, TProperties extends Record<string, any>, TPK extends (keyof TProperties)[] | undefined = undefined, TBase = never, TRepository = never, TForceObject extends boolean = false, TDiscriminatorColumn extends string | undefined = undefined, TDiscriminatorValue extends string | number | undefined = undefined, TBaseDiscriminatorColumn extends string | undefined = undefined, TEmbeddable extends boolean = false> extends Omit<Partial<EntityMetadata<InferEntityFromProperties<TProperties, TPK, TBase, TRepository>>>, 'properties' | 'extends' | 'primaryKeys' | 'hooks' | 'discriminator' | 'discriminatorColumn' | 'discriminatorValue' | 'versionProperty' | 'concurrencyCheckKeys' | 'serializedPrimaryKey' | 'indexes' | 'uniques' | 'triggers' | 'repository' | 'filters' | 'orderBy'> {
631
631
  name: TName;
632
632
  tableName?: TTableName;
633
633
  extends?: {
@@ -649,6 +649,7 @@ export interface EntityMetadataWithProperties<TName extends string, TTableName e
649
649
  strict?: boolean;
650
650
  }>;
651
651
  forceObject?: TForceObject;
652
+ embeddable?: TEmbeddable;
652
653
  inheritance?: 'tpt';
653
654
  orderBy?: {
654
655
  [K in Extract<AllKeys<TProperties, TBase>, string>]?: QueryOrderKeysFlat;
@@ -699,7 +700,7 @@ export interface EntityMetadataWithProperties<TName extends string, TTableName e
699
700
  }[];
700
701
  }
701
702
  /** Defines an entity schema using property builders, with full type inference from the property definitions. */
702
- export declare function defineEntity<const TName extends string, const TTableName extends string, const TProperties extends Record<string, any>, const TPK extends (keyof TProperties)[] | undefined = undefined, const TBase = never, const TRepository = never, const TForceObject extends boolean = false, const TDiscriminatorColumn extends string | undefined = undefined, const TDiscriminatorValue extends string | number | undefined = undefined, const TBaseDiscriminatorColumn extends string | undefined = undefined>(meta: EntityMetadataWithProperties<TName, TTableName, TProperties, TPK, TBase, TRepository, TForceObject, TDiscriminatorColumn, TDiscriminatorValue, TBaseDiscriminatorColumn>): EntitySchemaWithMeta<TName, TTableName, InferEntityFromProperties<TProperties, TPK, TBase, TRepository, TForceObject, TBaseDiscriminatorColumn, TDiscriminatorValue>, TBase, TProperties, EntityCtor<InferEntityFromProperties<TProperties, TPK, TBase, TRepository, TForceObject, TBaseDiscriminatorColumn, TDiscriminatorValue>>, TDiscriminatorColumn>;
703
+ export declare function defineEntity<const TName extends string, const TTableName extends string, const TProperties extends Record<string, any>, const TPK extends (keyof TProperties)[] | undefined = undefined, const TBase = never, const TRepository = never, const TForceObject extends boolean = false, const TDiscriminatorColumn extends string | undefined = undefined, const TDiscriminatorValue extends string | number | undefined = undefined, const TBaseDiscriminatorColumn extends string | undefined = undefined, const TEmbeddable extends boolean = false>(meta: EntityMetadataWithProperties<TName, TTableName, TProperties, TPK, TBase, TRepository, TForceObject, TDiscriminatorColumn, TDiscriminatorValue, TBaseDiscriminatorColumn, TEmbeddable>): EntitySchemaWithMeta<TName, TTableName, InferEntityFromProperties<TProperties, TPK, TBase, TRepository, TForceObject, TBaseDiscriminatorColumn, TDiscriminatorValue, TEmbeddable>, TBase, TProperties, EntityCtor<InferEntityFromProperties<TProperties, TPK, TBase, TRepository, TForceObject, TBaseDiscriminatorColumn, TDiscriminatorValue, TEmbeddable>>, TDiscriminatorColumn>;
703
704
  export declare namespace defineEntity {
704
705
  export { propertyBuilders as properties };
705
706
  }
@@ -734,22 +735,24 @@ type InferTypeByString<T extends string> = T extends keyof typeof types ? InferJ
734
735
  type InferJSType<T> = T extends typeof Type<infer TValue, any> ? NonNullable<TValue> : never;
735
736
  type InferColumnType<T extends string> = T extends 'int' | 'int4' | 'integer' | 'bigint' | 'int8' | 'int2' | 'tinyint' | 'smallint' | 'mediumint' ? number : T extends 'double' | 'double precision' | 'real' | 'float8' | 'decimal' | 'numeric' | 'float' | 'float4' ? number : T extends 'datetime' | 'time' | 'time with time zone' | 'timestamp' | 'timestamp with time zone' | 'timetz' | 'timestamptz' | 'date' | 'interval' ? Date : T extends 'ObjectId' | 'objectId' | 'character varying' | 'varchar' | 'char' | 'character' | 'uuid' | 'text' | 'tinytext' | 'mediumtext' | 'longtext' | 'enum' ? string : T extends 'boolean' | 'bool' | 'bit' ? boolean : T extends 'blob' | 'tinyblob' | 'mediumblob' | 'longblob' | 'bytea' ? Buffer : T extends 'point' | 'line' | 'lseg' | 'box' | 'circle' | 'path' | 'polygon' | 'geometry' ? number[] : T extends 'tsvector' | 'tsquery' ? string[] : T extends 'json' | 'jsonb' ? any : any;
736
737
  type BaseEntityMethodKeys = 'toObject' | 'toPOJO' | 'serialize' | 'assign' | 'populate' | 'init' | 'toReference';
738
+ interface BaseEntityMethods<in out Entity extends object> extends Pick<IWrappedEntity<Entity>, BaseEntityMethodKeys> {
739
+ }
737
740
  /** Infers the entity type from a `defineEntity()` properties map, resolving builders, base classes, and primary keys. */
738
- export type InferEntityFromProperties<Properties extends Record<string, any>, PK extends (keyof Properties)[] | undefined = undefined, Base = never, Repository = never, ForceObject extends boolean = false, BaseDiscriminatorColumn extends string | undefined = undefined, DiscriminatorValue extends string | number | undefined = undefined> = (IsNever<Base> extends true ? {} : Base extends {
741
+ export type InferEntityFromProperties<Properties extends Record<string, any>, PK extends (keyof Properties)[] | undefined = undefined, Base = never, Repository = never, ForceObject extends boolean = false, BaseDiscriminatorColumn extends string | undefined = undefined, DiscriminatorValue extends string | number | undefined = undefined, Embeddable extends boolean = false> = (IsNever<Base> extends true ? {} : Base extends {
739
742
  toObject(...args: any[]): any;
740
- } ? Pick<IWrappedEntity<{
743
+ } ? BaseEntityMethods<{
741
744
  -readonly [K in keyof Properties]: InferBuilderValue<MaybeReturnType<Properties[K]>>;
742
745
  } & {
743
746
  [PrimaryKeyProp]?: InferCombinedPrimaryKey<Properties, PK, Base>;
744
747
  } & (IsNever<Repository> extends true ? {} : {
745
748
  [EntityRepositoryType]?: Repository extends Constructor<infer R> ? R : Repository;
746
- }) & NarrowDiscriminator<Omit<Base, typeof PrimaryKeyProp>, BaseDiscriminatorColumn, DiscriminatorValue>>, BaseEntityMethodKeys> : {}) & {
749
+ }) & NarrowDiscriminator<Omit<Base, typeof PrimaryKeyProp>, BaseDiscriminatorColumn, DiscriminatorValue, Embeddable>> : {}) & {
747
750
  -readonly [K in keyof Properties]: InferBuilderValue<MaybeReturnType<Properties[K]>>;
748
751
  } & {
749
752
  [PrimaryKeyProp]?: InferCombinedPrimaryKey<Properties, PK, Base>;
750
753
  } & (IsNever<Repository> extends true ? {} : {
751
754
  [EntityRepositoryType]?: Repository extends Constructor<infer R> ? R : Repository;
752
- }) & (IsNever<Base> extends true ? {} : NarrowDiscriminator<Omit<Base, typeof PrimaryKeyProp>, BaseDiscriminatorColumn, DiscriminatorValue>) & (ForceObject extends true ? {
755
+ }) & (IsNever<Base> extends true ? {} : NarrowDiscriminator<Omit<Base, typeof PrimaryKeyProp>, BaseDiscriminatorColumn, DiscriminatorValue, Embeddable>) & (ForceObject extends true ? {
753
756
  [Config]?: DefineConfig<{
754
757
  forceObject: true;
755
758
  }>;
@@ -757,13 +760,26 @@ export type InferEntityFromProperties<Properties extends Record<string, any>, PK
757
760
  [IndexHints]?: [Omit<ExtractBaseProperties<Base>, keyof Properties> & Properties];
758
761
  };
759
762
  type ExtractBaseProperties<Base> = [ExtractDefineEntityProperties<Base>] extends [infer P extends Record<string, any>] ? [P] extends [never] ? {} : P : {};
760
- type NarrowDiscriminator<Base, DiscColumn extends string | undefined, DiscValue> = DiscColumn extends string ? DiscColumn extends keyof Base ? DiscValue extends string | number ? Omit<Base, DiscColumn> & {
763
+ type ExtractOptionalProps<Base> = Base extends {
764
+ [OptionalProps]?: infer K;
765
+ } ? (K extends string ? K : never) : never;
766
+ type NarrowDiscriminator<Base, DiscColumn extends string | undefined, DiscValue, Embeddable extends boolean = false> = DiscColumn extends string ? DiscColumn extends keyof Base ? DiscValue extends string | number ? Embeddable extends true ? Omit<Base, DiscColumn> & {
767
+ [K in DiscColumn]: DiscValue;
768
+ } : Omit<Base, DiscColumn | typeof OptionalProps> & {
761
769
  [K in DiscColumn]: DiscValue;
770
+ } & {
771
+ [OptionalProps]?: DiscColumn | ExtractOptionalProps<Base>;
762
772
  } : Base : Base : Base;
763
773
  type InferCombinedPrimaryKey<Properties extends Record<string, any>, PK, Base> = PK extends undefined ? CombinePrimaryKeys<InferPrimaryKey<Properties>, ExtractBasePrimaryKey<Base>> : PK;
764
- type ExtractBasePrimaryKey<Base> = Base extends {
774
+ type ExtractBasePrimaryKey<Base> = typeof PrimaryKeyProp extends keyof Base ? Base extends {
765
775
  [PrimaryKeyProp]?: infer BasePK;
766
- } ? BasePK : never;
776
+ } ? BasePK : never : [keyof Base] extends [never] ? never : Base extends {
777
+ _id?: any;
778
+ } ? '_id' : Base extends {
779
+ id?: any;
780
+ } ? 'id' : Base extends {
781
+ uuid?: any;
782
+ } ? 'uuid' : never;
767
783
  type CombinePrimaryKeys<ChildPK, BasePK> = [ChildPK] extends [never] ? BasePK : [BasePK] extends [never] ? IsUnion<ChildPK> extends true ? ChildPK[] : ChildPK : ChildPK | BasePK;
768
784
  /** Extracts the primary key property names from a properties map by finding builders with `primary: true`. */
769
785
  export type InferPrimaryKey<Properties extends Record<string, any>> = {
@@ -790,6 +806,8 @@ type InferBuilderValue<Builder> = Builder extends {
790
806
  type MaybeArray<Value, Options> = Options extends {
791
807
  array: true;
792
808
  } ? Value[] : Value;
809
+ /** The runtime value passed to a custom serializer — the property value as stored on the entity (e.g. `Collection`/`Ref` for relations). Skips `MaybeMapToPk`, as its `Primary<Value>` branch is too costly for the type checker. */
810
+ type SerializerValue<Value, Options> = MaybeNullable<MaybeRelationRef<MaybeArray<Value, Options>, Options>, Options>;
793
811
  type MaybeMapToPk<Value, Options> = Options extends {
794
812
  mapToPk: true;
795
813
  } ? Primary<Value> : Value;
@@ -496,7 +496,7 @@ export class StringPropertyOptionsBuilder extends UniversalPropertyOptionsBuilde
496
496
  export class OneToManyOptionsBuilderOnlyMappedBy extends UniversalPropertyOptionsBuilder {
497
497
  /** Point to the owning side property name. */
498
498
  mappedBy(mappedBy) {
499
- return new UniversalPropertyOptionsBuilder({ ...this['~options'], mappedBy });
499
+ return this.assignOptions({ mappedBy });
500
500
  }
501
501
  }
502
502
  function createPropertyBuilders(options) {
package/errors.d.ts CHANGED
@@ -60,6 +60,7 @@ export declare class MetadataError<T extends AnyEntity = AnyEntity> extends Vali
60
60
  static duplicateFieldName(entityName: EntityName, names: [string, string][]): MetadataError;
61
61
  static multipleDecorators(entityName: string, propertyName: string): MetadataError;
62
62
  static missingMetadata(entity: string): MetadataError;
63
+ static ambiguousEntityName(className: string): MetadataError;
63
64
  static invalidPrimaryKey(meta: EntityMetadata, prop: EntityProperty, requiredName: string): MetadataError;
64
65
  static invalidManyToManyWithPivotEntity(meta1: EntityMetadata, prop1: EntityProperty, meta2: EntityMetadata, prop2: EntityProperty): MetadataError;
65
66
  static targetIsAbstract(meta: EntityMetadata, prop: EntityProperty): MetadataError;
package/errors.js CHANGED
@@ -195,6 +195,9 @@ export class MetadataError extends ValidationError {
195
195
  static missingMetadata(entity) {
196
196
  return new MetadataError(`Metadata for entity ${entity} not found`);
197
197
  }
198
+ static ambiguousEntityName(className) {
199
+ return new MetadataError(`Entity name '${className}' is ambiguous, multiple discovered entity classes share it (possibly due to a minifier mangling class names). Use a class reference instead of a string name.`);
200
+ }
198
201
  static invalidPrimaryKey(meta, prop, requiredName) {
199
202
  return this.fromMessage(meta, prop, `has wrong field name, '${requiredName}' is required in current driver`);
200
203
  }
@@ -43,7 +43,10 @@ export class EventManager {
43
43
  }));
44
44
  for (const listener of this.#listeners[event] ?? new Set()) {
45
45
  const entities = this.#entities.get(listener);
46
- if (entities.size === 0 || !entity || entities.has(entity.constructor.name)) {
46
+ if (entities.size === 0 ||
47
+ !entity ||
48
+ entities.has(entity.constructor) ||
49
+ entities.has(entity.constructor.name)) {
47
50
  listeners.push(listener[event].bind(listener));
48
51
  }
49
52
  }
@@ -68,7 +71,7 @@ export class EventManager {
68
71
  }
69
72
  for (const listener of this.#listeners[event] ?? new Set()) {
70
73
  const entities = this.#entities.get(listener);
71
- if (entities.size === 0 || entities.has(meta.className)) {
74
+ if (entities.size === 0 || entities.has(meta.class) || entities.has(meta.className)) {
72
75
  this.#cache.set(cacheKey, true);
73
76
  return true;
74
77
  }
@@ -84,6 +87,6 @@ export class EventManager {
84
87
  if (!listener.getSubscribedEntities) {
85
88
  return new Set();
86
89
  }
87
- return new Set(listener.getSubscribedEntities().map(name => Utils.className(name)));
90
+ return new Set(listener.getSubscribedEntities().map(name => Utils.classOrName(name)));
88
91
  }
89
92
  }
@@ -19,6 +19,8 @@ export declare class ObjectHydrator extends Hydrator {
19
19
  getEntityHydrator<T extends object>(meta: EntityMetadata<T>, type: 'full' | 'reference', normalizeAccessors?: boolean): EntityHydrator<T>;
20
20
  private createCollectionItemMapper;
21
21
  private wrap;
22
+ /** Renders a key as a single-quoted JS string literal, safe to embed in generated code. */
23
+ private quote;
22
24
  private safeKey;
23
25
  }
24
26
  export {};
@@ -87,7 +87,7 @@ export class ObjectHydrator extends Hydrator {
87
87
  ret.push(` if (data${dataKey} === null) {`);
88
88
  if (prop.ref) {
89
89
  ret.push(` entity${entityKey} = new ScalarReference();`);
90
- ret.push(` entity${entityKey}.bind(entity, '${prop.name}');`);
90
+ ret.push(` entity${entityKey}.bind(entity, ${this.quote(prop.name)});`);
91
91
  ret.push(` entity${entityKey}.set(${nullVal});`);
92
92
  }
93
93
  else {
@@ -127,14 +127,14 @@ export class ObjectHydrator extends Hydrator {
127
127
  if (prop.ref) {
128
128
  ret.push(` const value = isScalarReference(entity${entityKey}) ? entity${entityKey}.unwrap() : entity${entityKey};`);
129
129
  ret.push(` entity${entityKey} = oldValue_${idx} ?? new ScalarReference(value);`);
130
- ret.push(` entity${entityKey}.bind(entity, '${prop.name}');`);
130
+ ret.push(` entity${entityKey}.bind(entity, ${this.quote(prop.name)});`);
131
131
  ret.push(` entity${entityKey}.set(value);`);
132
132
  }
133
133
  ret.push(` }`);
134
134
  if (prop.ref) {
135
135
  ret.push(` if (!entity${entityKey}) {`);
136
136
  ret.push(` entity${entityKey} = new ScalarReference();`);
137
- ret.push(` entity${entityKey}.bind(entity, '${prop.name}');`);
137
+ ret.push(` entity${entityKey}.bind(entity, ${this.quote(prop.name)});`);
138
138
  ret.push(` }`);
139
139
  }
140
140
  return ret;
@@ -220,7 +220,7 @@ export class ObjectHydrator extends Hydrator {
220
220
  ret.push(` }`);
221
221
  ret.push(` if (Array.isArray(data${dataKey})) {`);
222
222
  ret.push(` const items = data${dataKey}.map(value => createCollectionItem_${this.safeKey(prop.name)}(value, entity));`);
223
- ret.push(` const coll = Collection.create(entity, '${prop.name}', items, newEntity);`);
223
+ ret.push(` const coll = Collection.create(entity, ${this.quote(prop.name)}, items, newEntity);`);
224
224
  ret.push(` if (newEntity) {`);
225
225
  ret.push(` coll.setDirty();`);
226
226
  ret.push(` } else {`);
@@ -231,11 +231,11 @@ export class ObjectHydrator extends Hydrator {
231
231
  if (!this.platform.usesPivotTable() && prop.owner && prop.kind === ReferenceKind.MANY_TO_MANY) {
232
232
  ret.push(` } else if (!entity${entityKey} && Array.isArray(data${dataKey})) {`);
233
233
  const items = this.platform.usesPivotTable() || !prop.owner ? 'undefined' : '[]';
234
- ret.push(` const coll = Collection.create(entity, '${prop.name}', ${items}, !!data${dataKey} || newEntity);`);
234
+ ret.push(` const coll = Collection.create(entity, ${this.quote(prop.name)}, ${items}, !!data${dataKey} || newEntity);`);
235
235
  ret.push(` coll.setDirty(false);`);
236
236
  }
237
237
  ret.push(` } else if (!entity${entityKey}) {`);
238
- ret.push(` const coll = Collection.create(entity, '${prop.name}', undefined, newEntity);`);
238
+ ret.push(` const coll = Collection.create(entity, ${this.quote(prop.name)}, undefined, newEntity);`);
239
239
  ret.push(` coll.setDirty(false);`);
240
240
  ret.push(` }`);
241
241
  return ret;
@@ -431,10 +431,14 @@ export class ObjectHydrator extends Hydrator {
431
431
  return lines;
432
432
  }
433
433
  wrap(key) {
434
- if (/^\[.*]$/.exec(key)) {
434
+ if (/^\[idx_\d+]$/.exec(key)) {
435
435
  return key;
436
436
  }
437
- return /^\w+$/.exec(key) ? `.${key}` : `['${key}']`;
437
+ return /^\w+$/.exec(key) ? `.${key}` : `[${this.quote(key)}]`;
438
+ }
439
+ /** Renders a key as a single-quoted JS string literal, safe to embed in generated code. */
440
+ quote(key) {
441
+ return `'${key.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
438
442
  }
439
443
  safeKey(key) {
440
444
  return key.replace(/\W/g, '_');
@@ -441,7 +441,18 @@ export class MetadataDiscovery {
441
441
  if (prop.kind === ReferenceKind.SCALAR || prop.kind === ReferenceKind.EMBEDDED) {
442
442
  prop.fieldNames = [this.#namingStrategy.propertyToColumnName(prop.name, object)];
443
443
  }
444
- else if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && !prop.polymorphic) {
444
+ else if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && prop.polymorphic) {
445
+ if (prop.targetMeta) {
446
+ // same layout as `initManyToOneFields` builds later: `[discriminatorColumn, ...fkIdColumns]`
447
+ const pkFields = prop.targetMeta.getPrimaryProps().flatMap(pk => {
448
+ this.initFieldName(pk);
449
+ return pk.fieldNames;
450
+ });
451
+ const idColumns = pkFields.map(fieldName => this.#namingStrategy.joinKeyColumnName(prop.discriminator, fieldName, pkFields.length > 1));
452
+ prop.fieldNames = [prop.discriminatorColumn, ...idColumns];
453
+ }
454
+ }
455
+ else if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
445
456
  prop.fieldNames = this.initManyToOneFieldName(prop, prop.name);
446
457
  }
447
458
  else if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner) {
@@ -451,10 +462,13 @@ export class MetadataDiscovery {
451
462
  initManyToOneFieldName(prop, name) {
452
463
  const meta2 = prop.targetMeta;
453
464
  const ret = [];
454
- for (const primaryKey of meta2.primaryKeys) {
455
- this.initFieldName(meta2.properties[primaryKey]);
456
- for (const fieldName of meta2.properties[primaryKey].fieldNames) {
457
- ret.push(this.#namingStrategy.joinKeyColumnName(name, fieldName, meta2.compositePK));
465
+ // with `targetKey` on a composite PK target, derive the FK field name from that property
466
+ // instead of the PKs (simple PK targets keep the PK based naming for backwards compatibility)
467
+ const referencedKeys = prop.targetKey && meta2.compositePK ? [prop.targetKey] : meta2.primaryKeys;
468
+ for (const referencedKey of referencedKeys) {
469
+ this.initFieldName(meta2.properties[referencedKey]);
470
+ for (const fieldName of meta2.properties[referencedKey].fieldNames) {
471
+ ret.push(this.#namingStrategy.joinKeyColumnName(name, fieldName, !prop.targetKey && meta2.compositePK));
458
472
  }
459
473
  }
460
474
  return ret;
@@ -1055,11 +1069,12 @@ export class MetadataDiscovery {
1055
1069
  }
1056
1070
  // TPT children have their own tables that don't contain the parent's columns,
1057
1071
  // so propagating parent indexes/uniques/checks/triggers would target missing columns.
1072
+ // deep equality, as the subclass items might be copies of the base class ones (e.g. with TC39 decorators)
1058
1073
  if (meta.inheritanceType !== 'tpt' || !meta.tptParent) {
1059
- meta.indexes = Utils.unique([...base.indexes, ...meta.indexes]);
1060
- meta.uniques = Utils.unique([...base.uniques, ...meta.uniques]);
1061
- meta.checks = Utils.unique([...base.checks, ...meta.checks]);
1062
- meta.triggers = Utils.unique([...base.triggers, ...meta.triggers]);
1074
+ meta.indexes = Utils.unique([...base.indexes, ...meta.indexes], Utils.equals);
1075
+ meta.uniques = Utils.unique([...base.uniques, ...meta.uniques], Utils.equals);
1076
+ meta.checks = Utils.unique([...base.checks, ...meta.checks], Utils.equals);
1077
+ meta.triggers = Utils.unique([...base.triggers, ...meta.triggers], Utils.equals);
1063
1078
  }
1064
1079
  const pks = Object.values(meta.properties)
1065
1080
  .filter(p => p.primary)
@@ -1247,8 +1262,17 @@ export class MetadataDiscovery {
1247
1262
  if (embeddedProp.nullable || refInArray) {
1248
1263
  meta.properties[name].nullable = true;
1249
1264
  }
1265
+ // polymorphic relations derive their column names from the discriminator, so prefix it too
1266
+ if (meta.properties[name].polymorphic && !object) {
1267
+ meta.properties[name].discriminator = prefix + meta.properties[name].discriminator;
1268
+ meta.properties[name].discriminatorColumn = prefix + meta.properties[name].discriminatorColumn;
1269
+ }
1250
1270
  if (meta.properties[name].fieldNames) {
1251
- meta.properties[name].fieldNames[0] = prefix + meta.properties[name].fieldNames[0];
1271
+ const { fieldNames, polymorphic } = meta.properties[name];
1272
+ // polymorphic `fieldNames` hold `[discriminatorColumn, ...fkIdColumns]`, so prefix all of them
1273
+ for (let i = 0; i < (polymorphic ? fieldNames.length : 1); i++) {
1274
+ fieldNames[i] = prefix + fieldNames[i];
1275
+ }
1252
1276
  }
1253
1277
  else {
1254
1278
  const name2 = meta.properties[name].name;
@@ -1677,6 +1701,9 @@ export class MetadataDiscovery {
1677
1701
  if (prop.persist === false || prop.nativeEnumName || !prop.items?.every(item => typeof item === 'string')) {
1678
1702
  continue;
1679
1703
  }
1704
+ if (prop.customType instanceof t.json || ['json', 'jsonb'].includes(prop.columnTypes?.[0])) {
1705
+ continue;
1706
+ }
1680
1707
  this.initFieldName(prop);
1681
1708
  let expression = null;
1682
1709
  if (prop.enum) {
@@ -2080,6 +2107,7 @@ export class MetadataDiscovery {
2080
2107
  prop.columnTypes.push(...columnTypes);
2081
2108
  if (!targetMeta.compositePK || prop.targetKey) {
2082
2109
  prop.customType = referencedProp.customType;
2110
+ prop.collation ??= referencedProp.collation;
2083
2111
  }
2084
2112
  }
2085
2113
  }
@@ -2135,7 +2163,7 @@ export class MetadataDiscovery {
2135
2163
  shouldForceConstructorUsage(meta) {
2136
2164
  const forceConstructor = this.#config.get('forceEntityConstructor');
2137
2165
  if (Array.isArray(forceConstructor)) {
2138
- return forceConstructor.some(cls => Utils.className(cls) === meta.className);
2166
+ return forceConstructor.some(cls => Utils.matchesEntity(cls, meta));
2139
2167
  }
2140
2168
  return forceConstructor;
2141
2169
  }
@@ -17,6 +17,7 @@ export class MetadataStorage {
17
17
  #idMap;
18
18
  #classNameMap;
19
19
  #uniqueNameMap;
20
+ #ambiguousNames = new Set();
20
21
  constructor(metadata = {}) {
21
22
  this.#idMap = {};
22
23
  this.#uniqueNameMap = {};
@@ -64,6 +65,10 @@ export class MetadataStorage {
64
65
  }
65
66
  /** Returns metadata for the given entity, optionally initializing it if not found. */
66
67
  get(entityName, init = false) {
68
+ // string lookups cannot be resolved when several classes were minified to the same name
69
+ if (typeof entityName === 'string' && this.#ambiguousNames.has(entityName)) {
70
+ throw MetadataError.ambiguousEntityName(entityName);
71
+ }
67
72
  const exists = this.find(entityName);
68
73
  if (exists) {
69
74
  return exists;
@@ -99,7 +104,13 @@ export class MetadataStorage {
99
104
  this.#metadataMap.set(entityName, meta);
100
105
  this.#idMap[meta._id] = meta;
101
106
  this.#uniqueNameMap[meta.uniqueName] = meta;
102
- this.#classNameMap[Utils.className(entityName)] = meta;
107
+ const className = Utils.className(entityName);
108
+ const existing = this.#classNameMap[className];
109
+ // track name collisions caused by minifiers mangling two classes to the same name
110
+ if (existing && existing !== meta && existing.class !== meta.class) {
111
+ this.#ambiguousNames.add(className);
112
+ }
113
+ this.#classNameMap[className] = meta;
103
114
  return meta;
104
115
  }
105
116
  /** Removes metadata for the given entity from all internal maps. */
@@ -110,6 +121,11 @@ export class MetadataStorage {
110
121
  delete this.#idMap[meta._id];
111
122
  delete this.#uniqueNameMap[meta.uniqueName];
112
123
  delete this.#classNameMap[meta.className];
124
+ // the name may still be ambiguous among the remaining metas
125
+ const remaining = new Set([...this.#metadataMap.values()].filter(m => m.className === meta.className).map(m => m.class));
126
+ if (remaining.size <= 1) {
127
+ this.#ambiguousNames.delete(meta.className);
128
+ }
113
129
  }
114
130
  }
115
131
  /** Decorates all entity prototypes with helper methods (e.g. init, toJSON). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/core",
3
- "version": "7.2.0-dev.13",
3
+ "version": "7.2.0-dev.14",
4
4
  "description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
5
5
  "keywords": [
6
6
  "data-mapper",
@@ -211,8 +211,9 @@ export declare abstract class Platform {
211
211
  getBlobDeclarationSQL(): string;
212
212
  getJsonDeclarationSQL(): string;
213
213
  getSearchJsonPropertySQL(path: string, type: string, aliased: boolean): string | Raw;
214
- getSearchJsonPropertyKey(path: string[], type: string, aliased: boolean, value?: unknown): string | Raw;
215
- processJsonCondition<T extends object>(o: FilterQuery<T>, value: EntityValue<T>, path: EntityKey<T>[], alias: boolean): FilterQuery<T>;
214
+ /** When `aliased` is a string, it holds an explicit alias the key was prefixed with. */
215
+ getSearchJsonPropertyKey(path: string[], type: string, aliased: boolean | string, value?: unknown): string | Raw;
216
+ processJsonCondition<T extends object>(o: FilterQuery<T>, value: EntityValue<T>, path: EntityKey<T>[], alias: boolean | string): FilterQuery<T>;
216
217
  protected getJsonValueType(value: unknown): string;
217
218
  getJsonIndexDefinition(index: {
218
219
  columnNames: string[];
@@ -413,6 +413,7 @@ export class Platform {
413
413
  getSearchJsonPropertySQL(path, type, aliased) {
414
414
  return path;
415
415
  }
416
+ /** When `aliased` is a string, it holds an explicit alias the key was prefixed with. */
416
417
  getSearchJsonPropertyKey(path, type, aliased, value) {
417
418
  return path.join('.');
418
419
  }
@@ -424,7 +425,8 @@ export class Platform {
424
425
  return o;
425
426
  }
426
427
  if (path.length === 1) {
427
- o[path[0]] = value;
428
+ const key = typeof alias === 'string' ? `${alias}.${path[0]}` : path[0];
429
+ o[key] = value;
428
430
  return o;
429
431
  }
430
432
  const type = this.getJsonValueType(value);
package/types/Type.js CHANGED
@@ -55,11 +55,11 @@ export class Type {
55
55
  return prop.columnTypes?.[0] ?? platform.getTextTypeDeclarationSQL(prop);
56
56
  }
57
57
  static getType(cls) {
58
- const key = cls.name;
59
- if (!Type.types.has(key)) {
60
- Type.types.set(key, new cls());
58
+ // keyed by class reference, as minifiers can mangle two classes to the same name
59
+ if (!Type.types.has(cls)) {
60
+ Type.types.set(cls, new cls());
61
61
  }
62
- return Type.types.get(key);
62
+ return Type.types.get(cls);
63
63
  }
64
64
  /**
65
65
  * Checks whether the argument is instance of `Type`.
package/typings.d.ts CHANGED
@@ -478,12 +478,12 @@ type NonArrayObject = object & {
478
478
  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 {
479
479
  __runtime?: infer Runtime;
480
480
  __raw?: infer Raw;
481
- } ? C extends true ? Raw : Runtime : T extends LazyRef.Brand<infer U> ? EntityDataNested<U, C> : 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>;
481
+ } ? C extends true ? Raw : Runtime : T extends LazyRef.Brand<infer U> ? EntityDataNested<U, C> : 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>;
482
482
  /** Like `EntityDataProp` but used in `RequiredEntityData` context with required/optional key distinction. */
483
483
  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 {
484
484
  __runtime?: infer Runtime;
485
485
  __raw?: infer Raw;
486
- } ? C extends true ? Raw : Runtime : T extends LazyRef.Brand<infer U> ? RequiredEntityDataNested<U, O, C> : 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>;
486
+ } ? C extends true ? Raw : Runtime : T extends LazyRef.Brand<infer U> ? RequiredEntityDataNested<U, O, C> : 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>;
487
487
  /** Nested entity data shape for embedded or related entities within `EntityData`. */
488
488
  export type EntityDataNested<T, C extends boolean = false> = T extends undefined ? never : T extends any[] ? Readonly<T> : EntityData<T, C> | ExpandEntityProp<T, C>;
489
489
  type UnwrapScalarRef<T> = T extends ScalarReference<infer U> ? U : T;
@@ -1050,6 +1050,12 @@ export declare class EntityMetadata<Entity = any, Class extends EntityCtor<Entit
1050
1050
  constructor(meta?: Partial<EntityMetadata>);
1051
1051
  addProperty(prop: Partial<EntityProperty<Entity>>): void;
1052
1052
  removeProperty(name: string, sync?: boolean): void;
1053
+ /** For TPT entities, the version column exists only on the table of the entity that declares it. */
1054
+ ownsVersionProperty(): boolean;
1055
+ /** For TPT entities, concurrency check columns exist only on the table of the entity that declares them. */
1056
+ getOwnConcurrencyCheckKeys(): EntityKey<Entity>[];
1057
+ /** Whether updates of this table are guarded by a version property or concurrency check columns it owns. */
1058
+ hasOptimisticLock(): boolean;
1053
1059
  getPrimaryProps(flatten?: boolean): EntityProperty<Entity>[];
1054
1060
  getPrimaryProp(): EntityProperty<Entity>;
1055
1061
  /**
package/typings.js CHANGED
@@ -79,6 +79,25 @@ export class EntityMetadata {
79
79
  this.sync();
80
80
  }
81
81
  }
82
+ /** For TPT entities, the version column exists only on the table of the entity that declares it. */
83
+ ownsVersionProperty() {
84
+ if (!this.versionProperty) {
85
+ return false;
86
+ }
87
+ return this.inheritanceType !== 'tpt' || !this.ownProps || this.ownProps.some(p => p.name === this.versionProperty);
88
+ }
89
+ /** For TPT entities, concurrency check columns exist only on the table of the entity that declares them. */
90
+ getOwnConcurrencyCheckKeys() {
91
+ const keys = [...this.concurrencyCheckKeys];
92
+ if (this.inheritanceType !== 'tpt' || !this.ownProps) {
93
+ return keys;
94
+ }
95
+ return keys.filter(key => this.ownProps.some(p => p.name === key));
96
+ }
97
+ /** Whether updates of this table are guarded by a version property or concurrency check columns it owns. */
98
+ hasOptimisticLock() {
99
+ return this.ownsVersionProperty() || this.getOwnConcurrencyCheckKeys().length > 0;
100
+ }
82
101
  getPrimaryProps(flatten = false) {
83
102
  const pks = this.primaryKeys.map(pk => this.properties[pk]);
84
103
  if (flatten) {
@@ -216,7 +235,10 @@ export class EntityMetadata {
216
235
  if (config) {
217
236
  const platform = config.getPlatform();
218
237
  for (const prop of this.props) {
219
- if (prop.enum && !prop.nativeEnumName && prop.items?.every(item => typeof item === 'string')) {
238
+ if (prop.enum &&
239
+ !prop.nativeEnumName &&
240
+ prop.items?.every(item => typeof item === 'string') &&
241
+ !['json', 'jsonb'].includes(prop.columnTypes?.[0])) {
220
242
  const name = platform.getIndexName(this.tableName, prop.fieldNames, 'check');
221
243
  const exists = this.checks.findIndex(check => check.name === name);
222
244
  if (exists !== -1) {
@@ -204,13 +204,14 @@ export class ChangeSetPersister {
204
204
  }
205
205
  checkConcurrencyKeys(meta, changeSet, cond) {
206
206
  const tmp = [];
207
- for (const key of meta.concurrencyCheckKeys) {
207
+ const keys = meta.getOwnConcurrencyCheckKeys();
208
+ for (const key of keys) {
208
209
  cond[key] = changeSet.originalEntity[key];
209
210
  if (changeSet.payload[key]) {
210
211
  tmp.push(key);
211
212
  }
212
213
  }
213
- if (tmp.length === 0 && meta.concurrencyCheckKeys.size > 0) {
214
+ if (tmp.length === 0 && keys.length > 0) {
214
215
  throw OptimisticLockError.lockFailed(changeSet.entity);
215
216
  }
216
217
  }
@@ -303,32 +304,33 @@ export class ChangeSetPersister {
303
304
  options = this.prepareOptions(meta, options, {
304
305
  convertCustomTypes: false,
305
306
  });
306
- if (meta.concurrencyCheckKeys.size === 0 &&
307
- (!meta.versionProperty || changeSet.entity[meta.versionProperty] == null)) {
307
+ if (meta.getOwnConcurrencyCheckKeys().length === 0 &&
308
+ (!meta.ownsVersionProperty() || changeSet.entity[meta.versionProperty] == null)) {
308
309
  return this.#driver.nativeUpdate(changeSet.meta.class, cond, changeSet.payload, options);
309
310
  }
310
- if (meta.versionProperty) {
311
+ if (meta.ownsVersionProperty()) {
311
312
  cond[meta.versionProperty] = this.#platform.convertVersionValue(changeSet.entity[meta.versionProperty], meta.properties[meta.versionProperty]);
312
313
  }
313
314
  this.checkConcurrencyKeys(meta, changeSet, cond);
314
315
  return this.#driver.nativeUpdate(changeSet.meta.class, cond, changeSet.payload, options);
315
316
  }
316
317
  async checkOptimisticLocks(meta, changeSets, options) {
317
- if (meta.concurrencyCheckKeys.size === 0 &&
318
- (!meta.versionProperty || changeSets.every(cs => cs.entity[meta.versionProperty] == null))) {
318
+ const concurrencyCheckKeys = meta.getOwnConcurrencyCheckKeys();
319
+ if (concurrencyCheckKeys.length === 0 &&
320
+ (!meta.ownsVersionProperty() || changeSets.every(cs => cs.entity[meta.versionProperty] == null))) {
319
321
  return;
320
322
  }
321
323
  // skip entity references as they don't have version values loaded
322
324
  changeSets = changeSets.filter(cs => helper(cs.entity).__initialized);
325
+ const primaryKeys = meta.primaryKeys.concat(...concurrencyCheckKeys);
323
326
  const $or = changeSets.map(cs => {
324
- const cond = Utils.getPrimaryKeyCond(cs.originalEntity, meta.primaryKeys.concat(...meta.concurrencyCheckKeys));
325
- if (meta.versionProperty) {
327
+ const cond = Utils.getPrimaryKeyCond(cs.originalEntity, primaryKeys);
328
+ if (meta.ownsVersionProperty()) {
326
329
  // @ts-ignore
327
330
  cond[meta.versionProperty] = this.#platform.convertVersionValue(cs.entity[meta.versionProperty], meta.properties[meta.versionProperty]);
328
331
  }
329
332
  return cond;
330
333
  });
331
- const primaryKeys = meta.primaryKeys.concat(...meta.concurrencyCheckKeys);
332
334
  options = this.prepareOptions(meta, options, {
333
335
  fields: primaryKeys,
334
336
  orderBy: meta.primaryKeys.reduce((o, pk) => {
@@ -336,7 +338,9 @@ export class ChangeSetPersister {
336
338
  return o;
337
339
  }, {}),
338
340
  });
339
- const res = await this.#driver.find(meta.root.class, { $or }, options);
341
+ // TPT tables query their own metadata, as the version column might not live on the root table
342
+ const target = meta.inheritanceType === 'tpt' ? meta : meta.root;
343
+ const res = await this.#driver.find(target.class, { $or }, options);
340
344
  if (res.length !== changeSets.length) {
341
345
  // a FK pointing to a composite PK is an array, so the values need to be compared deeply
342
346
  const compare = (a, b, keys) => keys.every(k => equals(a[k], b[k]));
@@ -347,7 +351,7 @@ export class ChangeSetPersister {
347
351
  }
348
352
  }
349
353
  checkOptimisticLock(meta, changeSet, res) {
350
- if ((meta.versionProperty || meta.concurrencyCheckKeys.size > 0) && res && !res.affectedRows) {
354
+ if ((meta.ownsVersionProperty() || meta.getOwnConcurrencyCheckKeys().length > 0) && res && !res.affectedRows) {
351
355
  throw OptimisticLockError.lockFailed(changeSet.entity);
352
356
  }
353
357
  }
@@ -356,7 +360,7 @@ export class ChangeSetPersister {
356
360
  * so we use a single query in case of both versioning and default values is used.
357
361
  */
358
362
  async reloadVersionValues(meta, changeSets, options) {
359
- const reloadProps = meta.versionProperty && !this.#usesReturningStatement ? [meta.properties[meta.versionProperty]] : [];
363
+ const reloadProps = meta.ownsVersionProperty() && !this.#usesReturningStatement ? [meta.properties[meta.versionProperty]] : [];
360
364
  if (changeSets[0].type === ChangeSetType.CREATE) {
361
365
  for (const prop of meta.props) {
362
366
  if (prop.persist === false) {
@@ -124,7 +124,6 @@ export class UnitOfWork {
124
124
  if (options?.newEntity) {
125
125
  return entity;
126
126
  }
127
- const forceUndefined = this.#em.config.get('forceUndefined');
128
127
  const wrapped = helper(entity);
129
128
  if (options?.loaded && wrapped.__initialized && !wrapped.__onLoadFired) {
130
129
  this.#loadedEntities.add(entity);
@@ -332,6 +331,7 @@ export class UnitOfWork {
332
331
  let parentCs = changeSet.tptChangeSets.find(pc => pc.meta === current);
333
332
  if (!parentCs) {
334
333
  parentCs = new ChangeSet(entity, changeSet.type, {}, current);
334
+ parentCs.originalEntity = changeSet.originalEntity;
335
335
  changeSet.tptChangeSets.splice(idx, 0, parentCs);
336
336
  }
337
337
  idx++;
@@ -527,6 +527,10 @@ export class UnitOfWork {
527
527
  if (prop.formula) {
528
528
  delete referrer[prop.name];
529
529
  }
530
+ else if (prop.primary) {
531
+ // the referrer's primary key contains the removed entity, so its identity cannot survive the removal
532
+ this.unsetIdentity(referrer);
533
+ }
530
534
  else {
531
535
  delete helper(referrer).__data[prop.name];
532
536
  }
@@ -702,13 +706,14 @@ export class UnitOfWork {
702
706
  payload[pk] = identifiers[i] ?? originalChangeSet.payload[pk];
703
707
  }
704
708
  }
705
- if (!isCreate && Object.keys(payload).length === 0) {
709
+ // the table declaring the version property or a concurrency check still needs its bump and lock check
710
+ if (!isCreate && Object.keys(payload).length === 0 && !current.hasOptimisticLock()) {
706
711
  current = current.tptParent;
707
712
  continue;
708
713
  }
709
714
  const cs = new ChangeSet(entity, originalChangeSet.type, payload, current);
715
+ cs.originalEntity = originalChangeSet.originalEntity;
710
716
  if (current === meta) {
711
- cs.originalEntity = originalChangeSet.originalEntity;
712
717
  leafCs = cs;
713
718
  }
714
719
  else {
@@ -1205,7 +1210,8 @@ export class UnitOfWork {
1205
1210
  const addToGroup = (cs) => {
1206
1211
  // Skip stub TPT changesets with empty payload (e.g. leaf with no own-property changes on UPDATE)
1207
1212
  if ((cs.type === ChangeSetType.UPDATE || cs.type === ChangeSetType.UPDATE_EARLY) &&
1208
- !Utils.hasObjectKeys(cs.payload)) {
1213
+ !Utils.hasObjectKeys(cs.payload) &&
1214
+ !cs.meta.hasOptimisticLock()) {
1209
1215
  return;
1210
1216
  }
1211
1217
  const group = groups[cs.type];
@@ -197,7 +197,8 @@ export class DataloaderUtils {
197
197
  const prop = group[0][0].property;
198
198
  const options = {};
199
199
  const wrap = (cond) => ({ [prop.name]: cond });
200
- const orderBy = Utils.asArray(group[0][1]?.orderBy).map(o => wrap(o));
200
+ // `findChildrenFromPivotTable` expects the `orderBy` relative to the target entity, so no wrapping here
201
+ const orderBy = Utils.asArray(group[0][1]?.orderBy);
201
202
  const populate = wrap(group[0][1]?.populate);
202
203
  const owners = group.map(c => c[0].owner);
203
204
  const $or = [];
@@ -80,6 +80,8 @@ export declare class EntityComparator {
80
80
  private getGenericComparator;
81
81
  private getPropertyComparator;
82
82
  private wrap;
83
+ /** Renders a key as a single-quoted JS string literal, safe to embed in generated code. */
84
+ private quote;
83
85
  private safeKey;
84
86
  /**
85
87
  * Sets the toArray helper in the context if not already set.
@@ -587,7 +587,7 @@ export class EntityComparator {
587
587
  }
588
588
  }
589
589
  else if (prop.polymorphic) {
590
- const discriminatorMapKey = `discriminatorMapReverse_${prop.name}`;
590
+ const discriminatorMapKey = `discriminatorMapReverse_${this.safeKey(prop.name)}`;
591
591
  const reverseMap = new Map();
592
592
  for (const [key, value] of Object.entries(prop.discriminatorMap)) {
593
593
  reverseMap.set(value, key);
@@ -758,10 +758,14 @@ export class EntityComparator {
758
758
  return this.getGenericComparator(this.wrap(prop.name), `!equals(last${this.wrap(prop.name)}, current${this.wrap(prop.name)})`);
759
759
  }
760
760
  wrap(key) {
761
- if (/^\[.*]$/.exec(key)) {
761
+ if (/^\[idx_\d+]$/.exec(key)) {
762
762
  return key;
763
763
  }
764
- return /^\w+$/.exec(key) ? `.${key}` : `['${key}']`;
764
+ return /^\w+$/.exec(key) ? `.${key}` : `[${this.quote(key)}]`;
765
+ }
766
+ /** Renders a key as a single-quoted JS string literal, safe to embed in generated code. */
767
+ quote(key) {
768
+ return `'${key.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
765
769
  }
766
770
  safeKey(key) {
767
771
  return key.replace(/\W/g, '_');
@@ -1,5 +1,5 @@
1
1
  import { Reference } from '../entity/Reference.js';
2
- import { Utils } from './Utils.js';
2
+ import { DANGEROUS_PROPERTY_NAMES, Utils } from './Utils.js';
3
3
  import { ARRAY_OPERATORS, GroupOperator, JSON_KEY_OPERATORS, ReferenceKind } from '../enums.js';
4
4
  import { JsonType } from '../types/JsonType.js';
5
5
  import { helper } from '../entity/wrap.js';
@@ -237,7 +237,11 @@ export class QueryHelper {
237
237
  // oxfmt-ignore
238
238
  const isJsonProperty = prop?.customType instanceof JsonType && !isRaw(value) && (Utils.isPlainObject(value) ? !['$eq', '$elemMatch'].includes(Object.keys(value)[0]) : !Array.isArray(value));
239
239
  if (isJsonProperty && prop?.kind !== ReferenceKind.EMBEDDED) {
240
- return this.processJsonCondition(o, value, [prop.fieldNames[0]], platform, aliased);
240
+ // an explicit alias prefix (e.g. `a.meta`) has to survive, otherwise the condition falls back to the root alias
241
+ const explicitAlias = key.includes('.')
242
+ ? key.split('.').slice(0, -1).join('.')
243
+ : undefined;
244
+ return this.processJsonCondition(o, value, [prop.fieldNames[0]], platform, aliased && explicitAlias != null ? explicitAlias : aliased);
241
245
  }
242
246
  // oxfmt-ignore
243
247
  if (Array.isArray(value) && !Utils.isOperator(key) && !QueryHelper.isSupportedOperator(key) && !(customExpression && Raw.getKnownFragment(key).params.length > 0) && options.type !== 'orderBy') {
@@ -269,7 +273,11 @@ export class QueryHelper {
269
273
  options.forEach(filter => (opts[filter] = true));
270
274
  }
271
275
  else if (Utils.isPlainObject(options)) {
272
- Object.keys(options).forEach(filter => (opts[filter] = options[filter]));
276
+ Object.keys(options).forEach(filter => {
277
+ if (!DANGEROUS_PROPERTY_NAMES.includes(filter)) {
278
+ opts[filter] = options[filter];
279
+ }
280
+ });
273
281
  }
274
282
  return Object.keys(filters)
275
283
  .filter(f => QueryHelper.isFilterActive(meta, f, filters[f], opts))
@@ -297,7 +305,7 @@ export class QueryHelper {
297
305
  return Utils.mergeConfig({}, propFilters, options);
298
306
  }
299
307
  static isFilterActive(meta, filterName, filter, options) {
300
- if (filter.entity && !filter.entity.includes(meta.className)) {
308
+ if (filter.entity && !Utils.asArray(filter.entity).some(e => Utils.matchesEntity(e, meta))) {
301
309
  return false;
302
310
  }
303
311
  if (options[filterName] === false) {
package/utils/Utils.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { CompiledFunctions, Dictionary, EntityData, EntityDictionary, EntityKey, EntityMetadata, EntityName, EntityProperty, Primary } from '../typings.js';
1
+ import type { CompiledFunctions, Dictionary, EntityCtor, EntityData, EntityDictionary, EntityKey, EntityMetadata, EntityName, EntityProperty, Primary } from '../typings.js';
2
2
  import type { Platform } from '../platforms/Platform.js';
3
3
  import { ScalarReference } from '../entity/Reference.js';
4
4
  import { Collection } from '../entity/Collection.js';
@@ -58,7 +58,7 @@ export declare class Utils {
58
58
  /**
59
59
  * Gets array without duplicates.
60
60
  */
61
- static unique<T = string>(items: T[]): T[];
61
+ static unique<T = string>(items: T[], equals?: (a: T, b: T) => boolean): T[];
62
62
  /**
63
63
  * Merges all sources into the target recursively.
64
64
  */
@@ -129,6 +129,16 @@ export declare class Utils {
129
129
  * Gets string name of given class.
130
130
  */
131
131
  static className<T>(classOrName: string | EntityName<T>): string;
132
+ /**
133
+ * Normalizes an entity reference for identity-safe matching: keeps class references
134
+ * (minifiers can mangle two classes to the same name) and falls back to the class name otherwise.
135
+ */
136
+ static classOrName<T>(classOrName: string | EntityName<T>): EntityCtor<T> | string;
137
+ /**
138
+ * Checks whether the given entity reference points at the given metadata,
139
+ * comparing classes by identity and strings by class name.
140
+ */
141
+ static matchesEntity<T>(classOrName: string | EntityName<T>, meta: EntityMetadata<any>): boolean;
132
142
  static extractChildElements(items: readonly string[], prefix: string, allSymbol?: string): string[];
133
143
  /**
134
144
  * Tries to detect TypeScript support.
package/utils/Utils.js CHANGED
@@ -153,7 +153,7 @@ export function parseJsonSafe(value) {
153
153
  /** Collection of general-purpose utility methods used throughout the ORM. */
154
154
  export class Utils {
155
155
  static PK_SEPARATOR = '~~~';
156
- static #ORM_VERSION = '7.2.0-dev.13';
156
+ static #ORM_VERSION = '7.2.0-dev.14';
157
157
  /**
158
158
  * Checks if the argument is instance of `Object`. Returns false for arrays.
159
159
  */
@@ -216,10 +216,13 @@ export class Utils {
216
216
  /**
217
217
  * Gets array without duplicates.
218
218
  */
219
- static unique(items) {
219
+ static unique(items, equals) {
220
220
  if (items.length < 2) {
221
221
  return items;
222
222
  }
223
+ if (equals) {
224
+ return items.filter((a, idx) => items.findIndex(b => equals(a, b)) === idx);
225
+ }
223
226
  return [...new Set(items)];
224
227
  }
225
228
  /**
@@ -562,6 +565,21 @@ export class Utils {
562
565
  }
563
566
  return classOrName.name;
564
567
  }
568
+ /**
569
+ * Normalizes an entity reference for identity-safe matching: keeps class references
570
+ * (minifiers can mangle two classes to the same name) and falls back to the class name otherwise.
571
+ */
572
+ static classOrName(classOrName) {
573
+ return typeof classOrName === 'function' ? classOrName : Utils.className(classOrName);
574
+ }
575
+ /**
576
+ * Checks whether the given entity reference points at the given metadata,
577
+ * comparing classes by identity and strings by class name.
578
+ */
579
+ static matchesEntity(classOrName, meta) {
580
+ const ref = Utils.classOrName(classOrName);
581
+ return typeof ref === 'function' ? ref === meta.class : ref === meta.className;
582
+ }
565
583
  static extractChildElements(items, prefix, allSymbol) {
566
584
  return items
567
585
  .filter(field => field === allSymbol || field.startsWith(`${prefix}.`))
@@ -1,8 +1,16 @@
1
- import type { EntityData, EntityMetadata, FilterQuery } from '../typings.js';
1
+ import type { EntityData, EntityKey, EntityMetadata, FilterQuery } from '../typings.js';
2
2
  import type { UpsertOptions } from '../drivers/IDatabaseDriver.js';
3
3
  import { type Raw } from '../utils/RawQueryFragment.js';
4
4
  /** @internal */
5
5
  export declare function getOnConflictFields<T>(meta: EntityMetadata<T> | undefined, data: EntityData<T>, uniqueFields: (keyof T)[] | Raw, options: UpsertOptions<T>): (keyof T)[];
6
+ /**
7
+ * Detects properties that will get their value generated by an `onCreate` hook during the upsert,
8
+ * i.e. those with an `onCreate` hook and no value provided. Such values are meant for the insert
9
+ * clause only and must not overwrite an existing row via the `on conflict do update set` clause.
10
+ * The property filter mirrors `EntityFactory.assignDefaultValues`.
11
+ * @internal
12
+ */
13
+ export declare function getOnCreateGeneratedFields<T extends object>(meta: EntityMetadata<T>, data: T | EntityData<T>): EntityKey<T>[];
6
14
  /** @internal */
7
15
  export declare function getOnConflictReturningFields<T, P extends string>(meta: EntityMetadata<T> | undefined, data: EntityData<T>, uniqueFields: (keyof T)[] | Raw, options: UpsertOptions<T, P>): (keyof T)[] | '*';
8
16
  /** @internal */
@@ -72,6 +72,22 @@ export function getOnConflictFields(meta, data, uniqueFields, options) {
72
72
  }
73
73
  return keys;
74
74
  }
75
+ /**
76
+ * Detects properties that will get their value generated by an `onCreate` hook during the upsert,
77
+ * i.e. those with an `onCreate` hook and no value provided. Such values are meant for the insert
78
+ * clause only and must not overwrite an existing row via the `on conflict do update set` clause.
79
+ * The property filter mirrors `EntityFactory.assignDefaultValues`.
80
+ * @internal
81
+ */
82
+ export function getOnCreateGeneratedFields(meta, data) {
83
+ return meta.props
84
+ .filter(prop => prop.onCreate &&
85
+ !prop.embedded &&
86
+ ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
87
+ !(prop.getter && !prop.setter) &&
88
+ data[prop.name] == null)
89
+ .map(prop => prop.name);
90
+ }
75
91
  /** @internal */
76
92
  export function getOnConflictReturningFields(meta, data, uniqueFields, options) {
77
93
  /* v8 ignore next */
@@ -122,7 +138,14 @@ function getPropertyValue(obj, key) {
122
138
  }
123
139
  /** @internal */
124
140
  export function getWhereCondition(meta, onConflictFields, data, where) {
125
- const unique = onConflictFields ?? meta.props.filter(p => p.unique).map(p => p.name);
141
+ // TPT children do not inherit the unique flags and indexes of their parent tables
142
+ const uniqueProps = new Set();
143
+ const uniques = [];
144
+ for (let current = meta; current; current = current.tptParent) {
145
+ current.props.filter(p => p.unique).forEach(p => uniqueProps.add(p.name));
146
+ uniques.push(...current.uniques);
147
+ }
148
+ const unique = onConflictFields ?? [...uniqueProps];
126
149
  const propIndex = !isRaw(unique) &&
127
150
  unique.findIndex(p => data[p] ?? data[p.substring(0, p.indexOf('.'))] != null);
128
151
  if (onConflictFields || where == null) {
@@ -136,8 +159,8 @@ export function getWhereCondition(meta, onConflictFields, data, where) {
136
159
  }
137
160
  where = { [key]: getPropertyValue(data, unique[propIndex]) };
138
161
  }
139
- else if (meta.uniques.length > 0) {
140
- for (const u of meta.uniques) {
162
+ else {
163
+ for (const u of uniques) {
141
164
  if (Utils.asArray(u.properties).every(p => data[p] != null)) {
142
165
  where = Utils.asArray(u.properties).reduce((o, key) => {
143
166
  o[key] = data[key];