@mikro-orm/core 7.1.0-dev.29 → 7.1.0-dev.30

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
@@ -354,7 +354,8 @@ export class EntityManager {
354
354
  const field = hint.field.split(':')[0];
355
355
  const prop = meta.properties[field];
356
356
  const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
357
- const joined = strategy === LoadStrategy.JOINED && prop.kind !== ReferenceKind.SCALAR;
357
+ const joined = (strategy === LoadStrategy.JOINED || (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner)) &&
358
+ prop.kind !== ReferenceKind.SCALAR;
358
359
  if (!joined && !hint.filter) {
359
360
  continue;
360
361
  }
@@ -423,11 +424,13 @@ export class EntityManager {
423
424
  const populated = options.populate.filter(({ field }) => field.split(':')[0] === prop.name);
424
425
  let found = false;
425
426
  for (const hint of populated) {
426
- if (!hint.all) {
427
+ const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
428
+ // inverse 1:1 always produces a JOIN (forced by `joinedProps()`), regardless of strategy
429
+ const willJoin = strategy === LoadStrategy.JOINED || (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner);
430
+ if (!hint.all || willJoin) {
427
431
  hint.filter = true;
428
432
  }
429
- const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
430
- if (hint.field === `${prop.name}:ref` || (hint.filter && strategy === LoadStrategy.JOINED)) {
433
+ if (hint.field === `${prop.name}:ref` || (hint.filter && willJoin)) {
431
434
  found = true;
432
435
  }
433
436
  }
@@ -615,12 +615,15 @@ export type PropertyBuilders = {
615
615
  /** Own keys + base entity keys (when TBase is not `never`). Guards against `keyof never = string | number | symbol`. */
616
616
  type AllKeys<TProperties, TBase> = keyof TProperties | (IsNever<TBase> extends true ? never : keyof TBase);
617
617
  /** Metadata descriptor for `defineEntity()`, combining entity options with property definitions. */
618
- 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> extends Omit<Partial<EntityMetadata<InferEntityFromProperties<TProperties, TPK, TBase, TRepository>>>, 'properties' | 'extends' | 'primaryKeys' | 'hooks' | 'discriminatorColumn' | 'versionProperty' | 'concurrencyCheckKeys' | 'serializedPrimaryKey' | 'indexes' | 'uniques' | 'triggers' | 'repository' | 'filters' | 'orderBy'> {
618
+ 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' | 'discriminatorColumn' | 'discriminatorValue' | 'versionProperty' | 'concurrencyCheckKeys' | 'serializedPrimaryKey' | 'indexes' | 'uniques' | 'triggers' | 'repository' | 'filters' | 'orderBy'> {
619
619
  name: TName;
620
620
  tableName?: TTableName;
621
621
  extends?: {
622
622
  '~entity': TBase;
623
- } | EntityCtor<TBase>;
623
+ '~discriminatorColumn'?: TBaseDiscriminatorColumn;
624
+ } | (EntityCtor<TBase> & {
625
+ '~discriminatorColumn'?: TBaseDiscriminatorColumn;
626
+ });
624
627
  properties: TProperties | ((properties: PropertyBuilders) => TProperties);
625
628
  primaryKeys?: TPK & InferPrimaryKeyConstraint<TProperties>[];
626
629
  hooks?: DefineEntityHooks;
@@ -640,7 +643,8 @@ export interface EntityMetadataWithProperties<TName extends string, TTableName e
640
643
  } | {
641
644
  [K in Extract<AllKeys<TProperties, TBase>, string>]?: QueryOrderKeysFlat;
642
645
  }[];
643
- discriminatorColumn?: string;
646
+ discriminatorColumn?: TDiscriminatorColumn;
647
+ discriminatorValue?: TDiscriminatorValue;
644
648
  versionProperty?: AllKeys<TProperties, TBase>;
645
649
  concurrencyCheckKeys?: Set<AllKeys<TProperties, TBase>>;
646
650
  serializedPrimaryKey?: AllKeys<TProperties, TBase>;
@@ -681,7 +685,7 @@ export interface EntityMetadataWithProperties<TName extends string, TTableName e
681
685
  }[];
682
686
  }
683
687
  /** Defines an entity schema using property builders, with full type inference from the property definitions. */
684
- 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>(meta: EntityMetadataWithProperties<TName, TTableName, TProperties, TPK, TBase, TRepository, TForceObject>): EntitySchemaWithMeta<TName, TTableName, InferEntityFromProperties<TProperties, TPK, TBase, TRepository, TForceObject>, TBase, TProperties>;
688
+ 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>;
685
689
  /** Defines an entity schema for an existing class, combining the class with property builders. */
686
690
  export declare function defineEntity<const TEntity = any, const TProperties extends Record<string, any> = Record<string, any>, const TClassName extends string = string, const TTableName extends string = string, const TBase = never, const TClass extends EntityCtor = EntityCtor<TEntity>>(meta: Omit<Partial<EntityMetadata<TEntity>>, 'properties' | 'extends' | 'className' | 'tableName' | 'hooks'> & {
687
691
  class: TClass;
@@ -717,7 +721,7 @@ type InferJSType<T> = T extends typeof Type<infer TValue, any> ? NonNullable<TVa
717
721
  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;
718
722
  type BaseEntityMethodKeys = 'toObject' | 'toPOJO' | 'serialize' | 'assign' | 'populate' | 'init' | 'toReference';
719
723
  /** Infers the entity type from a `defineEntity()` properties map, resolving builders, base classes, and primary keys. */
720
- export type InferEntityFromProperties<Properties extends Record<string, any>, PK extends (keyof Properties)[] | undefined = undefined, Base = never, Repository = never, ForceObject extends boolean = false> = (IsNever<Base> extends true ? {} : Base extends {
724
+ 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 {
721
725
  toObject(...args: any[]): any;
722
726
  } ? Pick<IWrappedEntity<{
723
727
  -readonly [K in keyof Properties]: InferBuilderValue<MaybeReturnType<Properties[K]>>;
@@ -725,19 +729,22 @@ export type InferEntityFromProperties<Properties extends Record<string, any>, PK
725
729
  [PrimaryKeyProp]?: InferCombinedPrimaryKey<Properties, PK, Base>;
726
730
  } & (IsNever<Repository> extends true ? {} : {
727
731
  [EntityRepositoryType]?: Repository extends Constructor<infer R> ? R : Repository;
728
- }) & Omit<Base, typeof PrimaryKeyProp>>, BaseEntityMethodKeys> : {}) & {
732
+ }) & NarrowDiscriminator<Omit<Base, typeof PrimaryKeyProp>, BaseDiscriminatorColumn, DiscriminatorValue>>, BaseEntityMethodKeys> : {}) & {
729
733
  -readonly [K in keyof Properties]: InferBuilderValue<MaybeReturnType<Properties[K]>>;
730
734
  } & {
731
735
  [PrimaryKeyProp]?: InferCombinedPrimaryKey<Properties, PK, Base>;
732
736
  } & (IsNever<Repository> extends true ? {} : {
733
737
  [EntityRepositoryType]?: Repository extends Constructor<infer R> ? R : Repository;
734
- }) & (IsNever<Base> extends true ? {} : Omit<Base, typeof PrimaryKeyProp>) & (ForceObject extends true ? {
738
+ }) & (IsNever<Base> extends true ? {} : NarrowDiscriminator<Omit<Base, typeof PrimaryKeyProp>, BaseDiscriminatorColumn, DiscriminatorValue>) & (ForceObject extends true ? {
735
739
  [Config]?: DefineConfig<{
736
740
  forceObject: true;
737
741
  }>;
738
742
  } : {}) & {
739
743
  [IndexHints]?: [Properties];
740
744
  };
745
+ type NarrowDiscriminator<Base, DiscColumn extends string | undefined, DiscValue> = DiscColumn extends string ? DiscColumn extends keyof Base ? DiscValue extends string | number ? Omit<Base, DiscColumn> & {
746
+ [K in DiscColumn]: DiscValue;
747
+ } : Base : Base : Base;
741
748
  type InferCombinedPrimaryKey<Properties extends Record<string, any>, PK, Base> = PK extends undefined ? CombinePrimaryKeys<InferPrimaryKey<Properties>, ExtractBasePrimaryKey<Base>> : PK;
742
749
  type ExtractBasePrimaryKey<Base> = Base extends {
743
750
  [PrimaryKeyProp]?: infer BasePK;
@@ -1840,7 +1840,7 @@ export class MetadataDiscovery {
1840
1840
  if (!prop.customType && prop.array && prop.items) {
1841
1841
  prop.customType = new t.enumArray(`${meta.className}.${prop.name}`, prop.items);
1842
1842
  }
1843
- const isArray = prop.type?.toLowerCase() === 'array' || prop.type?.toString().endsWith('[]');
1843
+ const isArray = prop.array || prop.type?.toLowerCase() === 'array' || prop.type?.toString().endsWith('[]');
1844
1844
  if (objectEmbeddable && !prop.customType && isArray) {
1845
1845
  prop.customType = new t.json();
1846
1846
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/core",
3
- "version": "7.1.0-dev.29",
3
+ "version": "7.1.0-dev.30",
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",
package/typings.d.ts CHANGED
@@ -1129,7 +1129,7 @@ export type MigrationRow = {
1129
1129
  * @internal
1130
1130
  */
1131
1131
  export interface IMigrationRunner {
1132
- run(migration: Migration, method: 'up' | 'down'): Promise<void>;
1132
+ run(migration: Migration, method: 'up' | 'down', afterRun?: (tx?: Transaction) => Promise<void>): Promise<void>;
1133
1133
  setMasterMigration(trx: Transaction): void;
1134
1134
  unsetMasterMigration(): void;
1135
1135
  setRunSchema?(schema?: string): void;
@@ -1140,8 +1140,8 @@ export interface IMigrationRunner {
1140
1140
  */
1141
1141
  export interface IMigratorStorage {
1142
1142
  executed(): Promise<string[]>;
1143
- logMigration(params: Dictionary): Promise<void>;
1144
- unlogMigration(params: Dictionary): Promise<void>;
1143
+ logMigration(params: Dictionary, tx?: Transaction): Promise<void>;
1144
+ unlogMigration(params: Dictionary, tx?: Transaction): Promise<void>;
1145
1145
  getExecutedMigrations(): Promise<MigrationRow[]>;
1146
1146
  ensureTable?(): Promise<void>;
1147
1147
  setMasterMigration(trx: Transaction): void;
@@ -1477,15 +1477,18 @@ export type MaybeReturnType<T> = T extends (...args: any[]) => infer R ? R : T;
1477
1477
  * Extended `EntitySchema` interface that carries additional type-level metadata (entity name, properties, table name).
1478
1478
  * Returned by `defineEntity()` to provide strong type inference without explicit generics.
1479
1479
  */
1480
- export interface EntitySchemaWithMeta<TName extends string = string, TTableName extends string = string, TEntity = any, TBase = never, TProperties extends Record<string, any> = Record<string, any>, TClass extends EntityCtor = EntityCtor<TEntity>> extends EntitySchema<TEntity, TBase, TClass> {
1480
+ export interface EntitySchemaWithMeta<TName extends string = string, TTableName extends string = string, TEntity = any, TBase = never, TProperties extends Record<string, any> = Record<string, any>, TClass extends EntityCtor = EntityCtor<TEntity>, TDiscriminatorColumn extends string | undefined = undefined> extends EntitySchema<TEntity, TBase, TClass> {
1481
1481
  readonly name: TName;
1482
1482
  readonly properties: TProperties;
1483
1483
  readonly tableName: TTableName;
1484
1484
  /** @internal Direct entity type access - avoids expensive pattern matching */
1485
1485
  readonly '~entity': TEntity;
1486
+ /** @internal Type-level marker carrying the discriminator column name, used by `defineEntity()` to narrow inherited discriminator properties to the literal `discriminatorValue` of each child schema. */
1487
+ readonly '~discriminatorColumn'?: TDiscriminatorColumn;
1486
1488
  /** @internal */
1487
1489
  readonly class: TClass & {
1488
1490
  '~entityName'?: TName;
1491
+ '~discriminatorColumn'?: TDiscriminatorColumn;
1489
1492
  };
1490
1493
  }
1491
1494
  /**
package/typings.js CHANGED
@@ -166,7 +166,7 @@ export class EntityMetadata {
166
166
  if (prop.inherited || (prop.persist === false && prop.userDefined !== false)) {
167
167
  return false;
168
168
  }
169
- return prop.kind === ReferenceKind.SCALAR && ['string', 'number', 'boolean', 'Date'].includes(prop.type);
169
+ return (prop.kind === ReferenceKind.SCALAR && !prop.array && ['string', 'number', 'boolean', 'Date'].includes(prop.type));
170
170
  });
171
171
  this.hydrateProps = this.props.filter(prop => {
172
172
  // `prop.userDefined` is either `undefined` or `false`
@@ -5,8 +5,8 @@ import type { EntityManagerType, IDatabaseDriver } from '../drivers/IDatabaseDri
5
5
  interface RunnableMigration {
6
6
  name: string;
7
7
  path?: string;
8
- up: () => MaybePromise<void>;
9
- down: () => MaybePromise<void>;
8
+ up: (afterRun?: (tx?: Transaction) => Promise<void>) => MaybePromise<void>;
9
+ down: (afterRun?: (tx?: Transaction) => Promise<void>) => MaybePromise<void>;
10
10
  }
11
11
  type MigrateOptions = {
12
12
  from?: string | number;
@@ -331,26 +331,26 @@ export class AbstractMigrator {
331
331
  }
332
332
  }
333
333
  resolve(params) {
334
- const createMigrationHandler = async (method) => {
334
+ const createMigrationHandler = async (method, afterRun) => {
335
335
  const { fs } = await import('@mikro-orm/core/fs-utils');
336
336
  const migration = await fs.dynamicImport(params.path);
337
337
  const MigrationClass = Object.values(migration).find(cls => typeof cls === 'function' && typeof cls.constructor === 'function');
338
338
  const instance = new MigrationClass(this.driver, this.config);
339
- await this.runner.run(instance, method);
339
+ await this.runner.run(instance, method, afterRun);
340
340
  };
341
341
  return {
342
342
  name: this.storage.getMigrationName(params.name),
343
343
  path: params.path,
344
- up: () => createMigrationHandler('up'),
345
- down: () => createMigrationHandler('down'),
344
+ up: afterRun => createMigrationHandler('up', afterRun),
345
+ down: afterRun => createMigrationHandler('down', afterRun),
346
346
  };
347
347
  }
348
348
  initialize(MigrationClass, name) {
349
349
  const instance = new MigrationClass(this.driver, this.config);
350
350
  return {
351
351
  name: this.storage.getMigrationName(name),
352
- up: () => this.runner.run(instance, 'up'),
353
- down: () => this.runner.run(instance, 'down'),
352
+ up: afterRun => this.runner.run(instance, 'up', afterRun),
353
+ down: afterRun => this.runner.run(instance, 'down', afterRun),
354
354
  };
355
355
  }
356
356
  /**
@@ -429,13 +429,15 @@ export class AbstractMigrator {
429
429
  for (const migration of toRun) {
430
430
  const event = { name: migration.name, path: migration.path };
431
431
  await this.emit(eventBefore, event);
432
- await migration[method]();
433
- if (method === 'up') {
434
- await this.storage.logMigration({ name: migration.name });
435
- }
436
- else {
437
- await this.storage.unlogMigration({ name: migration.name });
438
- }
432
+ // log inside the runner's tx (if any) so a logMigration failure rolls the migration back
433
+ await migration[method](async (tx) => {
434
+ if (method === 'up') {
435
+ await this.storage.logMigration({ name: migration.name }, tx);
436
+ }
437
+ else {
438
+ await this.storage.unlogMigration({ name: migration.name }, tx);
439
+ }
440
+ });
439
441
  await this.emit(eventAfter, event);
440
442
  result.push(event);
441
443
  }
@@ -536,7 +536,7 @@ export class EntityComparator {
536
536
  ret += ` && entity${path.map(k => this.wrap(k)).join('')}?.isInitialized()`;
537
537
  }
538
538
  ret += `) {\n`;
539
- if (['number', 'string', 'boolean'].includes(prop.type.toLowerCase())) {
539
+ if (!prop.array && ['number', 'string', 'boolean'].includes(prop.type.toLowerCase())) {
540
540
  return ret + ` ret${dataKey} = entity${entityKey}${unwrap};\n }\n`;
541
541
  }
542
542
  if (prop.kind === ReferenceKind.EMBEDDED) {
package/utils/Utils.js CHANGED
@@ -141,7 +141,7 @@ export function parseJsonSafe(value) {
141
141
  /** Collection of general-purpose utility methods used throughout the ORM. */
142
142
  export class Utils {
143
143
  static PK_SEPARATOR = '~~~';
144
- static #ORM_VERSION = '7.1.0-dev.29';
144
+ static #ORM_VERSION = '7.1.0-dev.30';
145
145
  /**
146
146
  * Checks if the argument is instance of `Object`. Returns false for arrays.
147
147
  */