@mikro-orm/core 6.3.14-dev.8 → 6.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/EntityManager.js +5 -5
  2. package/MikroORM.d.ts +4 -2
  3. package/MikroORM.js +8 -3
  4. package/connections/Connection.d.ts +4 -2
  5. package/decorators/CreateRequestContext.d.ts +3 -9
  6. package/decorators/CreateRequestContext.js +16 -43
  7. package/decorators/ManyToOne.d.ts +2 -0
  8. package/decorators/Property.d.ts +13 -0
  9. package/decorators/Transactional.d.ts +13 -0
  10. package/decorators/Transactional.js +31 -0
  11. package/decorators/index.d.ts +1 -0
  12. package/decorators/index.js +1 -0
  13. package/drivers/DatabaseDriver.js +4 -9
  14. package/entity/BaseEntity.d.ts +1 -1
  15. package/entity/EntityFactory.js +7 -1
  16. package/entity/EntityHelper.js +8 -6
  17. package/errors.d.ts +1 -0
  18. package/errors.js +3 -0
  19. package/index.d.ts +1 -1
  20. package/index.mjs +1 -0
  21. package/logging/DefaultLogger.js +6 -0
  22. package/logging/Logger.d.ts +2 -1
  23. package/metadata/MetadataDiscovery.d.ts +1 -0
  24. package/metadata/MetadataDiscovery.js +19 -2
  25. package/metadata/MetadataValidator.js +5 -2
  26. package/package.json +2 -2
  27. package/serialization/EntitySerializer.js +19 -12
  28. package/serialization/EntityTransformer.js +24 -13
  29. package/types/DecimalType.d.ts +2 -0
  30. package/types/DecimalType.js +11 -0
  31. package/typings.d.ts +18 -1
  32. package/utils/Configuration.d.ts +17 -2
  33. package/utils/Configuration.js +8 -1
  34. package/utils/ConfigurationLoader.d.ts +25 -1
  35. package/utils/ConfigurationLoader.js +92 -18
  36. package/utils/DataloaderUtils.d.ts +2 -2
  37. package/utils/DataloaderUtils.js +13 -11
  38. package/utils/resolveContextProvider.d.ts +10 -0
  39. package/utils/resolveContextProvider.js +31 -0
package/EntityManager.js CHANGED
@@ -623,7 +623,7 @@ class EntityManager {
623
623
  const convertCustomTypes = !utils_1.Utils.isEntity(data);
624
624
  if (utils_1.Utils.isEntity(data)) {
625
625
  entity = data;
626
- if ((0, entity_1.helper)(entity).__managed && (0, entity_1.helper)(entity).__em === em) {
626
+ if ((0, entity_1.helper)(entity).__managed && (0, entity_1.helper)(entity).__em === em && !this.config.get('upsertManaged')) {
627
627
  em.entityFactory.mergeData(meta, entity, data, { initialized: true });
628
628
  return entity;
629
629
  }
@@ -633,7 +633,7 @@ class EntityManager {
633
633
  else {
634
634
  data = utils_1.Utils.copy(utils_1.QueryHelper.processParams(data));
635
635
  where = utils_1.Utils.extractPK(data, meta);
636
- if (where) {
636
+ if (where && !this.config.get('upsertManaged')) {
637
637
  const exists = em.unitOfWork.getById(entityName, where, options.schema);
638
638
  if (exists) {
639
639
  return em.assign(exists, data);
@@ -763,7 +763,7 @@ class EntityManager {
763
763
  let where;
764
764
  if (utils_1.Utils.isEntity(row)) {
765
765
  const entity = row;
766
- if ((0, entity_1.helper)(entity).__managed && (0, entity_1.helper)(entity).__em === em) {
766
+ if ((0, entity_1.helper)(entity).__managed && (0, entity_1.helper)(entity).__em === em && !this.config.get('upsertManaged')) {
767
767
  em.entityFactory.mergeData(meta, entity, row, { initialized: true });
768
768
  entities.set(entity, row);
769
769
  entitiesByData.set(row, entity);
@@ -775,7 +775,7 @@ class EntityManager {
775
775
  else {
776
776
  row = data[i] = utils_1.Utils.copy(utils_1.QueryHelper.processParams(row));
777
777
  where = utils_1.Utils.extractPK(row, meta);
778
- if (where) {
778
+ if (where && !this.config.get('upsertManaged')) {
779
779
  const exists = em.unitOfWork.getById(entityName, where, options.schema);
780
780
  if (exists) {
781
781
  em.assign(exists, row);
@@ -1103,7 +1103,7 @@ class EntityManager {
1103
1103
  em.prepareOptions(options);
1104
1104
  entityName = utils_1.Utils.className(entityName);
1105
1105
  data = utils_1.QueryHelper.processObjectParams(data);
1106
- where = await em.processWhere(entityName, where, options, 'update');
1106
+ where = await em.processWhere(entityName, where, { ...options, convertCustomTypes: false }, 'update');
1107
1107
  em.validator.validateParams(data, 'update data');
1108
1108
  em.validator.validateParams(where, 'update condition');
1109
1109
  const res = await em.driver.nativeUpdate(entityName, where, data, { ctx: em.transactionContext, ...options });
package/MikroORM.d.ts CHANGED
@@ -44,8 +44,10 @@ export declare class MikroORM<D extends IDatabaseDriver = IDatabaseDriver, EM ex
44
44
  * Checks whether the database connection is active, returns .
45
45
  */
46
46
  checkConnection(): Promise<{
47
- ok: boolean;
48
- reason?: string;
47
+ ok: true;
48
+ } | {
49
+ ok: false;
50
+ reason: string;
49
51
  error?: Error;
50
52
  }>;
51
53
  /**
package/MikroORM.js CHANGED
@@ -29,7 +29,12 @@ class MikroORM {
29
29
  const coreVersion = await utils_1.ConfigurationLoader.checkPackageVersion();
30
30
  const env = utils_1.ConfigurationLoader.loadEnvironmentVars();
31
31
  if (!options) {
32
- options = (await utils_1.ConfigurationLoader.getConfiguration()).getAll();
32
+ const configPathFromArg = utils_1.ConfigurationLoader.configPathsFromArg();
33
+ const config = (await utils_1.ConfigurationLoader.getConfiguration(process.env.MIKRO_ORM_CONTEXT_NAME ?? 'default', configPathFromArg ?? utils_1.ConfigurationLoader.getConfigPaths()));
34
+ options = config.getAll();
35
+ if (configPathFromArg) {
36
+ config.getLogger().warn('deprecated', 'Path for config file was inferred from the command line arguments. Instead, you should set the MIKRO_ORM_CLI_CONFIG environment variable to specify the path, or if you really must use the command line arguments, import the config manually based on them, and pass it to init.', { label: 'D0001' });
37
+ }
33
38
  }
34
39
  options = utils_1.Utils.mergeConfig(options, env);
35
40
  await utils_1.ConfigurationLoader.commonJSCompat(options);
@@ -169,11 +174,11 @@ class MikroORM {
169
174
  return this.metadata;
170
175
  }
171
176
  async discoverEntities() {
172
- this.metadata = await this.discovery.discover(this.config.get('tsNode'));
177
+ this.metadata = await this.discovery.discover(this.config.get('preferTs'));
173
178
  this.createEntityManager();
174
179
  }
175
180
  discoverEntitiesSync() {
176
- this.metadata = this.discovery.discoverSync(this.config.get('tsNode'));
181
+ this.metadata = this.discovery.discoverSync(this.config.get('preferTs'));
177
182
  this.createEntityManager();
178
183
  }
179
184
  createEntityManager() {
@@ -26,8 +26,10 @@ export declare abstract class Connection {
26
26
  * Are we connected to the database
27
27
  */
28
28
  abstract checkConnection(): Promise<{
29
- ok: boolean;
30
- reason?: string;
29
+ ok: true;
30
+ } | {
31
+ ok: false;
32
+ reason: string;
31
33
  error?: Error;
32
34
  }>;
33
35
  /**
@@ -1,9 +1,3 @@
1
- import { MikroORM } from '../MikroORM';
2
- import { EntityManager } from '../EntityManager';
3
- import { EntityRepository } from '../entity/EntityRepository';
4
- export declare function CreateRequestContext<T>(getContext?: MikroORM | Promise<MikroORM> | ((type: T) => MikroORM | Promise<MikroORM> | EntityManager | EntityRepository<any> | {
5
- getEntityManager(): EntityManager;
6
- }), respectExistingContext?: boolean): MethodDecorator;
7
- export declare function EnsureRequestContext<T>(getContext?: MikroORM | Promise<MikroORM> | ((type: T) => MikroORM | Promise<MikroORM> | EntityManager | EntityRepository<any> | {
8
- getEntityManager(): EntityManager;
9
- })): MethodDecorator;
1
+ import type { ContextProvider } from '../typings';
2
+ export declare function CreateRequestContext<T extends object>(context?: ContextProvider<T>, respectExistingContext?: boolean): MethodDecorator;
3
+ export declare function EnsureRequestContext<T extends object>(context?: ContextProvider<T>): MethodDecorator;
@@ -2,59 +2,32 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CreateRequestContext = CreateRequestContext;
4
4
  exports.EnsureRequestContext = EnsureRequestContext;
5
- const MikroORM_1 = require("../MikroORM");
6
5
  const RequestContext_1 = require("../utils/RequestContext");
7
- const EntityManager_1 = require("../EntityManager");
8
- const EntityRepository_1 = require("../entity/EntityRepository");
6
+ const resolveContextProvider_1 = require("../utils/resolveContextProvider");
9
7
  const TransactionContext_1 = require("../utils/TransactionContext");
10
- function CreateRequestContext(getContext, respectExistingContext = false) {
8
+ function CreateRequestContext(context, respectExistingContext = false) {
11
9
  return function (target, propertyKey, descriptor) {
12
10
  const originalMethod = descriptor.value;
13
11
  descriptor.value = async function (...args) {
14
- // Otherwise, the outer tx context would be preferred.
15
- const txContext = TransactionContext_1.TransactionContext.currentTransactionContext();
16
- const provider = txContext ? TransactionContext_1.TransactionContext : RequestContext_1.RequestContext;
17
- let orm;
18
- let em;
19
- if (typeof getContext === 'function') {
20
- const context = await getContext(this);
21
- orm = context ?? await this.orm;
22
- em = context ?? await this.em;
23
- }
24
- else if (getContext) {
25
- orm = em = await getContext;
26
- }
27
- else {
28
- orm = await this.orm;
29
- em = await this.em;
30
- }
31
- const create = async (em) => {
32
- // reuse existing context if available for given respect `contextName`
33
- if (respectExistingContext && RequestContext_1.RequestContext.getEntityManager(em.name)) {
34
- return originalMethod.apply(this, args);
35
- }
36
- if (txContext) {
37
- em = em.fork({ useContext: true });
38
- }
39
- return await provider.create(em, () => {
40
- return originalMethod.apply(this, args);
41
- });
42
- };
43
- if (em instanceof EntityManager_1.EntityManager) {
44
- return await create(em);
45
- }
46
- if (orm instanceof EntityRepository_1.EntityRepository) {
47
- return await create(orm.getEntityManager());
48
- }
49
- if (!(orm instanceof MikroORM_1.MikroORM)) {
12
+ const em = await (0, resolveContextProvider_1.resolveContextProvider)(this, context);
13
+ if (!em) {
50
14
  const name = respectExistingContext ? 'EnsureRequestContext' : 'CreateRequestContext';
51
15
  throw new Error(`@${name}() decorator can only be applied to methods of classes with \`orm: MikroORM\` property, \`em: EntityManager\` property, or with a callback parameter like \`@${name}(() => orm)\` that returns one of those types. The parameter will contain a reference to current \`this\`. Returning an EntityRepository from it is also supported.`);
52
16
  }
53
- return await create(orm.em);
17
+ // reuse existing context if available for given respect `contextName`
18
+ if (respectExistingContext && RequestContext_1.RequestContext.getEntityManager(em.name)) {
19
+ return originalMethod.apply(this, args);
20
+ }
21
+ // Otherwise, the outer tx context would be preferred.
22
+ const txContext = TransactionContext_1.TransactionContext.currentTransactionContext();
23
+ const provider = txContext ? TransactionContext_1.TransactionContext : RequestContext_1.RequestContext;
24
+ return txContext
25
+ ? provider.create(em.fork({ useContext: true }), () => originalMethod.apply(this, args))
26
+ : provider.create(em, () => originalMethod.apply(this, args));
54
27
  };
55
28
  return descriptor;
56
29
  };
57
30
  }
58
- function EnsureRequestContext(getContext) {
59
- return CreateRequestContext(getContext, true);
31
+ function EnsureRequestContext(context) {
32
+ return CreateRequestContext(context, true);
60
33
  }
@@ -15,6 +15,8 @@ export interface ManyToOneOptions<Owner, Target> extends ReferenceOptions<Owner,
15
15
  joinColumn?: string;
16
16
  /** Override the default database column name on the owning side (see {@doclink naming-strategy | Naming Strategy}). This option is suitable for composite keys, where one property is represented by multiple columns. */
17
17
  joinColumns?: string[];
18
+ /** When a part of a composite column is shared in other properties, use this option to specify what columns are considered as owned by this property. This is useful when your composite property is nullable, but parts of it are not. */
19
+ ownColumns?: string[];
18
20
  /** Override the default database column name on the target entity (see {@doclink naming-strategy | Naming Strategy}). This option is only for simple properties represented by a single column. */
19
21
  referenceColumnName?: string;
20
22
  /** Override the default database column name on the target entity (see {@doclink naming-strategy | Naming Strategy}). This option is suitable for composite keys, where one property is represented by multiple columns. */
@@ -177,6 +177,19 @@ export interface PropertyOptions<Owner> {
177
177
  * ```
178
178
  */
179
179
  getter?: boolean;
180
+ /**
181
+ * When defining a property over a method (not a getter, a regular function), you can use this option to point
182
+ * to the method name.
183
+ *
184
+ * @example
185
+ * ```
186
+ * @Property({ getter: true })
187
+ * getFullName() {
188
+ * return this.firstName + this.lastName;
189
+ * }
190
+ * ```
191
+ */
192
+ getterName?: keyof Owner;
180
193
  /**
181
194
  * Set to define serialized primary key for MongoDB. (virtual)
182
195
  * Alias for `@SerializedPrimaryKey()` decorator.
@@ -0,0 +1,13 @@
1
+ import type { TransactionOptions } from '../enums';
2
+ import type { ContextProvider } from '../typings';
3
+ type TransactionalOptions<T> = TransactionOptions & {
4
+ context?: ContextProvider<T>;
5
+ };
6
+ /**
7
+ * This decorator wraps the method with `em.transactional()`, so you can provide `TransactionOptions` just like with `em.transactional()`.
8
+ * The difference is that you can specify the context in which the transaction begins by providing `context` option,
9
+ * and if omitted, the transaction will begin in the current context implicitly.
10
+ * It works on async functions and can be nested with `em.transactional()`.
11
+ */
12
+ export declare function Transactional<T extends object>(options?: TransactionalOptions<T>): MethodDecorator;
13
+ export {};
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Transactional = Transactional;
4
+ const RequestContext_1 = require("../utils/RequestContext");
5
+ const resolveContextProvider_1 = require("../utils/resolveContextProvider");
6
+ const TransactionContext_1 = require("../utils/TransactionContext");
7
+ /**
8
+ * This decorator wraps the method with `em.transactional()`, so you can provide `TransactionOptions` just like with `em.transactional()`.
9
+ * The difference is that you can specify the context in which the transaction begins by providing `context` option,
10
+ * and if omitted, the transaction will begin in the current context implicitly.
11
+ * It works on async functions and can be nested with `em.transactional()`.
12
+ */
13
+ function Transactional(options = {}) {
14
+ return function (target, propertyKey, descriptor) {
15
+ const originalMethod = descriptor.value;
16
+ if (originalMethod.constructor.name !== 'AsyncFunction') {
17
+ throw new Error('@Transactional() should be use with async functions');
18
+ }
19
+ descriptor.value = async function (...args) {
20
+ const { context, ...txOptions } = options;
21
+ const em = await (0, resolveContextProvider_1.resolveContextProvider)(this, context)
22
+ || TransactionContext_1.TransactionContext.getEntityManager()
23
+ || RequestContext_1.RequestContext.getEntityManager();
24
+ if (!em) {
25
+ throw new Error(`@Transactional() decorator can only be applied to methods of classes with \`orm: MikroORM\` property, \`em: EntityManager\` property, or with a callback parameter like \`@Transactional(() => orm)\` that returns one of those types. The parameter will contain a reference to current \`this\`. Returning an EntityRepository from it is also supported.`);
26
+ }
27
+ return em.transactional(() => originalMethod.apply(this, args), txOptions);
28
+ };
29
+ return descriptor;
30
+ };
31
+ }
@@ -14,3 +14,4 @@ export * from './Embedded';
14
14
  export * from './Filter';
15
15
  export * from './CreateRequestContext';
16
16
  export * from './hooks';
17
+ export * from './Transactional';
@@ -33,3 +33,4 @@ __exportStar(require("./Embedded"), exports);
33
33
  __exportStar(require("./Filter"), exports);
34
34
  __exportStar(require("./CreateRequestContext"), exports);
35
35
  __exportStar(require("./hooks"), exports);
36
+ __exportStar(require("./Transactional"), exports);
@@ -223,7 +223,7 @@ class DatabaseDriver {
223
223
  }
224
224
  if (prop.joinColumns?.length > 1 && data[k] == null) {
225
225
  delete data[k];
226
- prop.joinColumns.forEach((joinColumn, idx) => data[joinColumn] = null);
226
+ prop.ownColumns.forEach(joinColumn => data[joinColumn] = null);
227
227
  return;
228
228
  }
229
229
  if (prop.customType && convertCustomTypes && !(prop.customType instanceof JsonType_1.JsonType && object) && !this.platform.isRaw(data[k])) {
@@ -354,16 +354,11 @@ class DatabaseDriver {
354
354
  */
355
355
  getTableName(meta, options, quote = true) {
356
356
  const schema = this.getSchemaName(meta, options);
357
- if (schema) {
358
- if (quote) {
359
- return this.platform.quoteIdentifier(schema + '.' + meta.tableName);
360
- }
361
- return schema + '.' + meta.tableName;
362
- }
357
+ const tableName = schema ? `${schema}.${meta.tableName}` : meta.tableName;
363
358
  if (quote) {
364
- return this.platform.quoteIdentifier(meta.tableName);
359
+ return this.platform.quoteIdentifier(tableName);
365
360
  }
366
- return meta.tableName;
361
+ return tableName;
367
362
  }
368
363
  /**
369
364
  * @internal
@@ -14,7 +14,7 @@ export declare abstract class BaseEntity {
14
14
  toObject<Entity extends this = this>(ignoreFields: never[]): EntityDTO<Entity>;
15
15
  toObject<Entity extends this = this, Ignored extends EntityKey<Entity> = never>(ignoreFields: Ignored[]): Omit<EntityDTO<Entity>, Ignored>;
16
16
  toPOJO<Entity extends this = this>(): EntityDTO<Entity>;
17
- serialize<Entity extends this = this, Hint extends string = never, Exclude extends string = never>(options?: SerializeOptions<Entity, Hint, Exclude>): EntityDTO<Loaded<Entity, Hint>>;
17
+ serialize<Entity extends this = this, Naked extends FromEntityType<Entity> = FromEntityType<Entity>, Hint extends string = never, Exclude extends string = never>(options?: SerializeOptions<Naked, Hint, Exclude>): EntityDTO<Loaded<Naked, Hint>>;
18
18
  assign<Entity extends this, Naked extends FromEntityType<Entity> = FromEntityType<Entity>, Convert extends boolean = false, Data extends EntityData<Naked, Convert> | Partial<EntityDTO<Naked>> = EntityData<Naked, Convert> | Partial<EntityDTO<Naked>>>(data: Data & IsSubset<EntityData<Naked>, Data>, options?: AssignOptions<Convert>): MergeSelected<Entity, Naked, keyof Data & string>;
19
19
  init<Entity extends this = this, Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(options?: FindOneOptions<Entity, Hint, Fields, Excludes>): Promise<Loaded<Entity, Hint, Fields, Excludes> | null>;
20
20
  getSchema(): string | undefined;
@@ -69,6 +69,12 @@ class EntityFactory {
69
69
  if (options.convertCustomTypes) {
70
70
  for (const prop of meta.props) {
71
71
  if (prop.customType?.ensureComparable(meta, prop) && data[prop.name]) {
72
+ if ([enums_1.ReferenceKind.ONE_TO_MANY, enums_1.ReferenceKind.MANY_TO_MANY].includes(prop.kind)) {
73
+ continue;
74
+ }
75
+ if ([enums_1.ReferenceKind.MANY_TO_ONE, enums_1.ReferenceKind.ONE_TO_ONE].includes(prop.kind) && Utils_1.Utils.isPlainObject(data[prop.name])) {
76
+ data[prop.name] = Utils_1.Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta.primaryKeys, true);
77
+ }
72
78
  data[prop.name] = prop.customType.convertToDatabaseValue(data[prop.name], this.platform, { key: prop.name, mode: 'hydration' });
73
79
  }
74
80
  }
@@ -244,7 +250,7 @@ class EntityFactory {
244
250
  if (!Array.isArray(data) && meta.primaryKeys.some(pk => data[pk] == null)) {
245
251
  return undefined;
246
252
  }
247
- const pks = Utils_1.Utils.getOrderedPrimaryKeys(data, meta, this.platform, options.convertCustomTypes);
253
+ const pks = Utils_1.Utils.getOrderedPrimaryKeys(data, meta, this.platform);
248
254
  return this.unitOfWork.getById(meta.className, pks, schema);
249
255
  }
250
256
  processDiscriminatorColumn(meta, data) {
@@ -176,10 +176,15 @@ class EntityHelper {
176
176
  inverse.addWithoutPropagation(owner);
177
177
  (0, wrap_1.helper)(owner).__em?.getUnitOfWork().cancelOrphanRemoval(owner);
178
178
  }
179
- if (prop.kind === enums_1.ReferenceKind.ONE_TO_ONE && entity && (!prop.owner || (0, wrap_1.helper)(entity).__initialized)) {
179
+ if (prop.kind === enums_1.ReferenceKind.ONE_TO_ONE) {
180
180
  if ((value != null && Reference_1.Reference.unwrapReference(inverse) !== owner) ||
181
- (value == null && entity[prop2.name] != null)) {
182
- EntityHelper.propagateOneToOne(entity, owner, prop, prop2, value, old);
181
+ (value == null && entity?.[prop2.name] != null)) {
182
+ if (entity && (!prop.owner || (0, wrap_1.helper)(entity).__initialized)) {
183
+ EntityHelper.propagateOneToOne(entity, owner, prop, prop2, value, old);
184
+ }
185
+ if (old && prop.orphanRemoval) {
186
+ (0, wrap_1.helper)(old).__em?.getUnitOfWork().scheduleOrphanRemoval(old);
187
+ }
183
188
  }
184
189
  }
185
190
  }
@@ -203,9 +208,6 @@ class EntityHelper {
203
208
  else {
204
209
  entity[prop2.name] = Reference_1.Reference.wrapReference(owner, prop);
205
210
  }
206
- if (old && prop.orphanRemoval) {
207
- (0, wrap_1.helper)(old).__em?.getUnitOfWork().scheduleOrphanRemoval(old);
208
- }
209
211
  if (old?.[prop2.name] != null) {
210
212
  delete (0, wrap_1.helper)(old).__data[prop2.name];
211
213
  }
package/errors.d.ts CHANGED
@@ -55,6 +55,7 @@ export declare class MetadataError<T extends AnyEntity = AnyEntity> extends Vali
55
55
  static invalidPrimaryKey(meta: EntityMetadata, prop: EntityProperty, requiredName: string): MetadataError<Partial<any>>;
56
56
  static invalidManyToManyWithPivotEntity(meta1: EntityMetadata, prop1: EntityProperty, meta2: EntityMetadata, prop2: EntityProperty): MetadataError<Partial<any>>;
57
57
  static targetIsAbstract(meta: EntityMetadata, prop: EntityProperty): MetadataError<Partial<any>>;
58
+ static nonPersistentCompositeProp(meta: EntityMetadata, prop: EntityProperty): MetadataError<Partial<any>>;
58
59
  static propertyTargetsEntityType(meta: EntityMetadata, prop: EntityProperty, target: EntityMetadata): MetadataError<Partial<any>>;
59
60
  static fromMissingOption(meta: EntityMetadata, prop: EntityProperty, option: string): MetadataError<Partial<any>>;
60
61
  private static fromMessage;
package/errors.js CHANGED
@@ -198,6 +198,9 @@ class MetadataError extends ValidationError {
198
198
  static targetIsAbstract(meta, prop) {
199
199
  return this.fromMessage(meta, prop, `targets abstract entity ${prop.type}. Maybe you forgot to put @Entity() decorator on the ${prop.type} class?`);
200
200
  }
201
+ static nonPersistentCompositeProp(meta, prop) {
202
+ return this.fromMessage(meta, prop, `is non-persistent relation which targets composite primary key. This is not supported and will cause issues, 'persist: false' should be added to the properties representing single columns instead.`);
203
+ }
201
204
  static propertyTargetsEntityType(meta, prop, target) {
202
205
  /* istanbul ignore next */
203
206
  const suggestion = target.embeddable ? 'Embedded' : 'ManyToOne';
package/index.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * @packageDocumentation
3
3
  * @module core
4
4
  */
5
- export { Constructor, ConnectionType, Dictionary, PrimaryKeyProp, Primary, IPrimaryKey, ObjectQuery, FilterQuery, IWrappedEntity, EntityName, EntityData, Highlighter, AnyEntity, EntityClass, EntityProperty, EntityMetadata, QBFilterQuery, PopulateOptions, Populate, Loaded, New, LoadedReference, LoadedCollection, IMigrator, IMigrationGenerator, GetRepository, EntityRepositoryType, MigrationObject, DeepPartial, PrimaryProperty, Cast, IsUnknown, EntityDictionary, EntityDTO, MigrationDiff, GenerateOptions, FilterObject, IEntityGenerator, ISeedManager, EntityClassGroup, OptionalProps, EagerProps, HiddenProps, RequiredEntityData, CheckCallback, SimpleColumnMeta, Rel, Ref, ScalarRef, EntityRef, ISchemaGenerator, UmzugMigration, MigrateOptions, MigrationResult, MigrationRow, EntityKey, EntityValue, FilterKey, Opt, EntityType, FromEntityType, Selected, IsSubset, NoInfer, EntityProps, ExpandProperty, ExpandScalar, FilterItemValue, ExpandQuery, Scalar, ExpandHint, Hidden, FilterValue, MergeLoaded, MergeSelected, Config, DefineConfig, TypeConfig, ClearDatabaseOptions, CreateSchemaOptions, EnsureDatabaseOptions, UpdateSchemaOptions, DropSchemaOptions, RefreshDatabaseOptions, AutoPath, UnboxArray, MetadataProcessor, ImportsResolver, } from './typings';
5
+ export { Constructor, ConnectionType, Dictionary, PrimaryKeyProp, Primary, IPrimaryKey, ObjectQuery, FilterQuery, IWrappedEntity, EntityName, EntityData, Highlighter, MaybePromise, AnyEntity, EntityClass, EntityProperty, EntityMetadata, QBFilterQuery, PopulateOptions, Populate, Loaded, New, LoadedReference, LoadedCollection, IMigrator, IMigrationGenerator, MigratorEvent, GetRepository, EntityRepositoryType, MigrationObject, DeepPartial, PrimaryProperty, Cast, IsUnknown, EntityDictionary, EntityDTO, MigrationDiff, GenerateOptions, FilterObject, IEntityGenerator, ISeedManager, EntityClassGroup, OptionalProps, EagerProps, HiddenProps, RequiredEntityData, CheckCallback, SimpleColumnMeta, Rel, Ref, ScalarRef, EntityRef, ISchemaGenerator, UmzugMigration, MigrateOptions, MigrationResult, MigrationRow, EntityKey, EntityValue, FilterKey, Opt, EntityType, FromEntityType, Selected, IsSubset, NoInfer, EntityProps, ExpandProperty, ExpandScalar, FilterItemValue, ExpandQuery, Scalar, ExpandHint, Hidden, FilterValue, MergeLoaded, MergeSelected, Config, DefineConfig, TypeConfig, ClearDatabaseOptions, CreateSchemaOptions, EnsureDatabaseOptions, UpdateSchemaOptions, DropSchemaOptions, RefreshDatabaseOptions, AutoPath, UnboxArray, MetadataProcessor, ImportsResolver, } from './typings';
6
6
  export * from './enums';
7
7
  export * from './errors';
8
8
  export * from './exceptions';
package/index.mjs CHANGED
@@ -162,6 +162,7 @@ export const TimeType = mod.TimeType;
162
162
  export const TinyIntType = mod.TinyIntType;
163
163
  export const TransactionContext = mod.TransactionContext;
164
164
  export const TransactionEventBroadcaster = mod.TransactionEventBroadcaster;
165
+ export const Transactional = mod.Transactional;
165
166
  export const Type = mod.Type;
166
167
  export const Uint8ArrayType = mod.Uint8ArrayType;
167
168
  export const UnderscoreNamingStrategy = mod.UnderscoreNamingStrategy;
@@ -60,6 +60,12 @@ class DefaultLogger {
60
60
  return context.enabled;
61
61
  }
62
62
  const debugMode = context?.debugMode ?? this.debugMode;
63
+ if (namespace === 'deprecated') {
64
+ const { ignoreDeprecations = false } = this.options;
65
+ return Array.isArray(ignoreDeprecations)
66
+ ? !ignoreDeprecations.includes(context?.label ?? '')
67
+ : !ignoreDeprecations;
68
+ }
63
69
  return !!debugMode && (!Array.isArray(debugMode) || debugMode.includes(namespace));
64
70
  }
65
71
  /**
@@ -22,7 +22,7 @@ export interface Logger {
22
22
  setDebugMode(debugMode: boolean | LoggerNamespace[]): void;
23
23
  isEnabled(namespace: LoggerNamespace, context?: LogContext): boolean;
24
24
  }
25
- export type LoggerNamespace = 'query' | 'query-params' | 'schema' | 'discovery' | 'info';
25
+ export type LoggerNamespace = 'query' | 'query-params' | 'schema' | 'discovery' | 'info' | 'deprecated';
26
26
  export interface LogContext extends Dictionary {
27
27
  query?: string;
28
28
  label?: string;
@@ -41,6 +41,7 @@ export interface LogContext extends Dictionary {
41
41
  export interface LoggerOptions {
42
42
  writer: (message: string) => void;
43
43
  debugMode?: boolean | LoggerNamespace[];
44
+ ignoreDeprecations?: boolean | string[];
44
45
  highlighter?: Highlighter;
45
46
  usesReplicas?: boolean;
46
47
  }
@@ -31,6 +31,7 @@ export declare class MetadataDiscovery {
31
31
  private saveToCache;
32
32
  private initNullability;
33
33
  private applyNamingStrategy;
34
+ private initOwnColumns;
34
35
  private initFieldName;
35
36
  private initManyToOneFieldName;
36
37
  private initManyToManyFieldName;
@@ -127,10 +127,10 @@ class MetadataDiscovery {
127
127
  return meta;
128
128
  });
129
129
  }
130
- findEntities(preferTsNode, sync = false) {
130
+ findEntities(preferTs, sync = false) {
131
131
  this.discovered.length = 0;
132
132
  const options = this.config.get('discovery');
133
- const key = (preferTsNode && this.config.get('tsNode', Utils_1.Utils.detectTsNode()) && this.config.get('entitiesTs').length > 0) ? 'entitiesTs' : 'entities';
133
+ const key = (preferTs && this.config.get('preferTs', Utils_1.Utils.detectTsNode()) && this.config.get('entitiesTs').length > 0) ? 'entitiesTs' : 'entities';
134
134
  const paths = this.config.get(key).filter(item => Utils_1.Utils.isString(item));
135
135
  const refs = this.config.get(key).filter(item => !Utils_1.Utils.isString(item));
136
136
  if (paths.length > 0) {
@@ -361,6 +361,22 @@ class MetadataDiscovery {
361
361
  this.initOneToManyFields(prop);
362
362
  }
363
363
  }
364
+ initOwnColumns(meta) {
365
+ meta.sync();
366
+ for (const prop of meta.props) {
367
+ if (!prop.joinColumns || prop.ownColumns) {
368
+ continue;
369
+ }
370
+ if (prop.joinColumns.length > 1) {
371
+ prop.ownColumns = prop.joinColumns.filter(col => {
372
+ return !meta.props.find(p => p.name !== prop.name && (!p.fieldNames || p.fieldNames.includes(col)));
373
+ });
374
+ }
375
+ if (!prop.ownColumns || prop.ownColumns.length === 0) {
376
+ prop.ownColumns = prop.joinColumns;
377
+ }
378
+ }
379
+ }
364
380
  initFieldName(prop, object = false) {
365
381
  if (prop.fieldNames && prop.fieldNames.length > 0) {
366
382
  return;
@@ -469,6 +485,7 @@ class MetadataDiscovery {
469
485
  this.initColumnType(prop);
470
486
  this.initRelation(prop);
471
487
  }
488
+ this.initOwnColumns(meta);
472
489
  meta.simplePK = pks.length === 1 && pks[0].kind === enums_1.ReferenceKind.SCALAR && !pks[0].customType && pks[0].runtimeType !== 'Date';
473
490
  meta.serializedPrimaryKey = this.platform.getSerializedPrimaryKeyField(meta.primaryKeys[0]);
474
491
  const serializedPKProp = meta.properties[meta.serializedPrimaryKey];
@@ -41,7 +41,7 @@ class MetadataValidator {
41
41
  this.validateIndexes(meta, meta.uniques ?? [], 'unique');
42
42
  for (const prop of utils_1.Utils.values(meta.properties)) {
43
43
  if (prop.kind !== enums_1.ReferenceKind.SCALAR) {
44
- this.validateReference(meta, prop, metadata);
44
+ this.validateReference(meta, prop, metadata, options);
45
45
  this.validateBidirectional(meta, prop, metadata);
46
46
  }
47
47
  else if (metadata.has(prop.type)) {
@@ -98,7 +98,7 @@ class MetadataValidator {
98
98
  }
99
99
  });
100
100
  }
101
- validateReference(meta, prop, metadata) {
101
+ validateReference(meta, prop, metadata, options) {
102
102
  // references do have types
103
103
  if (!prop.type) {
104
104
  throw errors_1.MetadataError.fromWrongTypeDefinition(meta, prop);
@@ -110,6 +110,9 @@ class MetadataValidator {
110
110
  if (metadata.find(prop.type).abstract && !metadata.find(prop.type).discriminatorColumn) {
111
111
  throw errors_1.MetadataError.targetIsAbstract(meta, prop);
112
112
  }
113
+ if ([enums_1.ReferenceKind.MANY_TO_ONE, enums_1.ReferenceKind.ONE_TO_ONE].includes(prop.kind) && prop.persist === false && metadata.find(prop.type).compositePK && options.checkNonPersistentCompositeProps) {
114
+ throw errors_1.MetadataError.nonPersistentCompositeProp(meta, prop);
115
+ }
113
116
  }
114
117
  validateBidirectional(meta, prop, metadata) {
115
118
  if (prop.inversedBy) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/core",
3
- "version": "6.3.14-dev.8",
3
+ "version": "6.4.0",
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
  "main": "index.js",
6
6
  "module": "index.mjs",
@@ -64,7 +64,7 @@
64
64
  "esprima": "4.0.1",
65
65
  "fs-extra": "11.2.0",
66
66
  "globby": "11.1.0",
67
- "mikro-orm": "6.3.14-dev.8",
67
+ "mikro-orm": "6.4.0",
68
68
  "reflect-metadata": "0.2.2"
69
69
  }
70
70
  }
@@ -111,7 +111,7 @@ class EntitySerializer {
111
111
  const parts = prop.split('.');
112
112
  prop = parts[0];
113
113
  const wrapped = (0, wrap_1.helper)(entity);
114
- const property = wrapped.__meta.properties[prop];
114
+ const property = wrapped.__meta.properties[prop] ?? { name: prop };
115
115
  const serializer = property?.serializer;
116
116
  const value = entity[prop];
117
117
  // getter method
@@ -127,10 +127,10 @@ class EntitySerializer {
127
127
  return serializer(value);
128
128
  }
129
129
  if (Utils_1.Utils.isCollection(value)) {
130
- return this.processCollection(prop, entity, options);
130
+ return this.processCollection(property, entity, options);
131
131
  }
132
132
  if (Utils_1.Utils.isEntity(value, true)) {
133
- return this.processEntity(prop, entity, wrapped.__platform, options);
133
+ return this.processEntity(property, entity, wrapped.__platform, options);
134
134
  }
135
135
  if (Utils_1.Utils.isScalarReference(value)) {
136
136
  return value.unwrap();
@@ -158,17 +158,20 @@ class EntitySerializer {
158
158
  };
159
159
  }
160
160
  static processEntity(prop, entity, platform, options) {
161
- const child = Reference_1.Reference.unwrapReference(entity[prop]);
161
+ const child = Reference_1.Reference.unwrapReference(entity[prop.name]);
162
162
  const wrapped = (0, wrap_1.helper)(child);
163
- const populated = isPopulated(prop, options) && wrapped.isInitialized();
163
+ const populated = isPopulated(prop.name, options) && wrapped.isInitialized();
164
164
  const expand = populated || !wrapped.__managed;
165
165
  const meta = wrapped.__meta;
166
- const childOptions = this.extractChildOptions(options, prop);
166
+ const childOptions = this.extractChildOptions(options, prop.name);
167
167
  const visible = meta.primaryKeys.filter(prop => isVisible(meta, prop, childOptions));
168
168
  if (expand) {
169
169
  return this.serialize(child, childOptions);
170
170
  }
171
- const pk = wrapped.getPrimaryKey(true);
171
+ let pk = wrapped.getPrimaryKey();
172
+ if (prop.customType) {
173
+ pk = prop.customType.toJSON(pk, wrapped.__platform);
174
+ }
172
175
  if (options.forceObject || wrapped.__config.get('serialization').forceObject) {
173
176
  return Utils_1.Utils.primaryKeyToObject(meta, pk, visible);
174
177
  }
@@ -182,20 +185,24 @@ class EntitySerializer {
182
185
  return platform.normalizePrimaryKey(pk);
183
186
  }
184
187
  static processCollection(prop, entity, options) {
185
- const col = entity[prop];
188
+ const col = entity[prop.name];
186
189
  if (!col.isInitialized()) {
187
190
  return undefined;
188
191
  }
189
192
  return col.getItems(false).map(item => {
190
- const populated = isPopulated(prop, options);
193
+ const populated = isPopulated(prop.name, options);
191
194
  const wrapped = (0, wrap_1.helper)(item);
192
195
  if (populated || !wrapped.__managed) {
193
- return this.serialize(item, this.extractChildOptions(options, prop));
196
+ return this.serialize(item, this.extractChildOptions(options, prop.name));
197
+ }
198
+ let pk = wrapped.getPrimaryKey();
199
+ if (prop.customType) {
200
+ pk = prop.customType.toJSON(pk, wrapped.__platform);
194
201
  }
195
202
  if (options.forceObject || wrapped.__config.get('serialization').forceObject) {
196
- return Utils_1.Utils.primaryKeyToObject(wrapped.__meta, wrapped.getPrimaryKey(true));
203
+ return Utils_1.Utils.primaryKeyToObject(wrapped.__meta, pk);
197
204
  }
198
- return (0, wrap_1.helper)(item).getPrimaryKey();
205
+ return pk;
199
206
  });
200
207
  }
201
208
  }
@@ -18,6 +18,9 @@ class EntityTransformer {
18
18
  }
19
19
  const wrapped = (0, wrap_1.helper)(entity);
20
20
  let contextCreated = false;
21
+ if (!wrapped) {
22
+ return entity;
23
+ }
21
24
  if (!wrapped.__serializationContext.root) {
22
25
  const root = new SerializationContext_1.SerializationContext(wrapped.__config, wrapped.__serializationContext.populate, wrapped.__serializationContext.fields, wrapped.__serializationContext.exclude);
23
26
  SerializationContext_1.SerializationContext.propagate(root, entity, isVisible);
@@ -108,7 +111,7 @@ class EntityTransformer {
108
111
  }
109
112
  static processProperty(prop, entity, raw, populated) {
110
113
  const wrapped = (0, wrap_1.helper)(entity);
111
- const property = wrapped.__meta.properties[prop];
114
+ const property = wrapped.__meta.properties[prop] ?? { name: prop };
112
115
  const serializer = property?.serializer;
113
116
  const value = entity[prop];
114
117
  // getter method
@@ -123,10 +126,10 @@ class EntityTransformer {
123
126
  return serializer(value);
124
127
  }
125
128
  if (Utils_1.Utils.isCollection(value)) {
126
- return EntityTransformer.processCollection(prop, entity, raw, populated);
129
+ return EntityTransformer.processCollection(property, entity, raw, populated);
127
130
  }
128
131
  if (Utils_1.Utils.isEntity(value, true)) {
129
- return EntityTransformer.processEntity(prop, entity, wrapped.__platform, raw, populated);
132
+ return EntityTransformer.processEntity(property, entity, wrapped.__platform, raw, populated);
130
133
  }
131
134
  if (Utils_1.Utils.isScalarReference(value)) {
132
135
  return value.unwrap();
@@ -151,7 +154,7 @@ class EntityTransformer {
151
154
  return value;
152
155
  }
153
156
  static processEntity(prop, entity, platform, raw, populated) {
154
- const child = entity[prop];
157
+ const child = entity[prop.name];
155
158
  const wrapped = (0, wrap_1.helper)(child);
156
159
  const meta = wrapped.__meta;
157
160
  const visible = meta.primaryKeys.filter(prop => isVisible(meta, prop));
@@ -170,7 +173,10 @@ class EntityTransformer {
170
173
  if (wrapped.isInitialized() && isPopulated() && child !== entity) {
171
174
  return (0, wrap_1.wrap)(child).toJSON();
172
175
  }
173
- const pk = wrapped.getPrimaryKey(true);
176
+ let pk = wrapped.getPrimaryKey();
177
+ if (prop.customType) {
178
+ pk = prop.customType.toJSON(pk, wrapped.__platform);
179
+ }
174
180
  if (wrapped.__config.get('serialization').forceObject) {
175
181
  return Utils_1.Utils.primaryKeyToObject(meta, pk, visible);
176
182
  }
@@ -184,7 +190,7 @@ class EntityTransformer {
184
190
  return platform.normalizePrimaryKey(pk);
185
191
  }
186
192
  static processCollection(prop, entity, raw, populated) {
187
- const col = entity[prop];
193
+ const col = entity[prop.name];
188
194
  if (raw && col.isInitialized(true)) {
189
195
  return col.map(item => (0, wrap_1.helper)(item).toPOJO());
190
196
  }
@@ -193,13 +199,18 @@ class EntityTransformer {
193
199
  }
194
200
  if (col.isInitialized()) {
195
201
  const wrapped = (0, wrap_1.helper)(entity);
196
- if (wrapped.__config.get('serialization').forceObject) {
197
- return col.map(item => {
198
- const wrapped = (0, wrap_1.helper)(item);
199
- return Utils_1.Utils.primaryKeyToObject(wrapped.__meta, wrapped.getPrimaryKey(true));
200
- });
201
- }
202
- return col.map(i => (0, wrap_1.helper)(i).getPrimaryKey(true));
202
+ const forceObject = wrapped.__config.get('serialization').forceObject;
203
+ return col.map(item => {
204
+ const wrapped = (0, wrap_1.helper)(item);
205
+ const pk = wrapped.getPrimaryKey();
206
+ if (prop.customType) {
207
+ return prop.customType.toJSON(pk, wrapped.__platform);
208
+ }
209
+ if (forceObject) {
210
+ return Utils_1.Utils.primaryKeyToObject(wrapped.__meta, pk);
211
+ }
212
+ return pk;
213
+ });
203
214
  }
204
215
  return undefined;
205
216
  }
@@ -8,6 +8,8 @@ export declare class DecimalType extends Type<string | number, string> {
8
8
  mode?: ("number" | "string") | undefined;
9
9
  constructor(mode?: ("number" | "string") | undefined);
10
10
  convertToJSValue(value: string): number | string;
11
+ compareValues(a: string, b: string): boolean;
12
+ private format;
11
13
  getColumnType(prop: EntityProperty, platform: Platform): string;
12
14
  compareAsType(): string;
13
15
  }
@@ -17,6 +17,17 @@ class DecimalType extends Type_1.Type {
17
17
  }
18
18
  return value;
19
19
  }
20
+ compareValues(a, b) {
21
+ return this.format(a) === this.format(b);
22
+ }
23
+ format(val) {
24
+ /* istanbul ignore next */
25
+ if (this.prop?.scale == null) {
26
+ return +val;
27
+ }
28
+ const base = Math.pow(10, this.prop.scale);
29
+ return Math.round((+val + Number.EPSILON) * base) / base;
30
+ }
20
31
  getColumnType(prop, platform) {
21
32
  return platform.getDecimalTypeDeclarationSQL(prop);
22
33
  }
package/typings.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { Transaction } from './connections';
2
2
  import { type Cascade, type DeferMode, type EventType, type LoadStrategy, type PopulatePath, type QueryOrderMap, ReferenceKind } from './enums';
3
3
  import { type AssignOptions, type Collection, type EntityFactory, type EntityIdentifier, type EntityLoaderOptions, type EntityRepository, Reference, type ScalarReference } from './entity';
4
+ import type { MikroORM } from './MikroORM';
4
5
  import type { SerializationContext, SerializeOptions } from './serialization';
5
6
  import type { EntitySchema, MetadataStorage } from './metadata';
6
7
  import type { Type, types } from './types';
@@ -143,7 +144,7 @@ export interface IWrappedEntity<Entity extends object> {
143
144
  toObject<Ignored extends EntityKey<Entity>>(ignoreFields: Ignored[]): Omit<EntityDTO<Entity>, Ignored>;
144
145
  toJSON(...args: any[]): EntityDTO<Entity>;
145
146
  toPOJO(): EntityDTO<Entity>;
146
- serialize<Hint extends string = never, Exclude extends string = never>(options?: SerializeOptions<Entity, Hint, Exclude>): EntityDTO<Loaded<Entity, Hint>>;
147
+ serialize<Naked extends FromEntityType<Entity> = FromEntityType<Entity>, Hint extends string = never, Exclude extends string = never>(options?: SerializeOptions<Naked, Hint, Exclude>): EntityDTO<Loaded<Naked, Hint>>;
147
148
  setSerializationContext<Hint extends string = never, Fields extends string = '*', Exclude extends string = never>(options: LoadHint<Entity, Hint, Fields, Exclude>): void;
148
149
  assign<Naked extends FromEntityType<Entity> = FromEntityType<Entity>, Convert extends boolean = false, Data extends EntityData<Naked, Convert> | Partial<EntityDTO<Naked>> = EntityData<Naked, Convert> | Partial<EntityDTO<Naked>>>(data: Data & IsSubset<EntityData<Naked, Convert>, Data>, options?: AssignOptions<Convert>): MergeSelected<Entity, Naked, keyof Data & string>;
149
150
  getSchema(): string | undefined;
@@ -364,6 +365,7 @@ export interface EntityProperty<Owner = any, Target = any> {
364
365
  pivotTable: string;
365
366
  pivotEntity: string;
366
367
  joinColumns: string[];
368
+ ownColumns: string[];
367
369
  inverseJoinColumns: string[];
368
370
  referencedColumnNames: string[];
369
371
  referencedTableName: string;
@@ -626,11 +628,20 @@ export interface IMigrator {
626
628
  * Executes down migrations to the given point. Without parameter it will migrate one version down.
627
629
  */
628
630
  down(options?: string | string[] | MigrateOptions): Promise<UmzugMigration[]>;
631
+ /**
632
+ * Registers event handler.
633
+ */
634
+ on(event: MigratorEvent, listener: (event: UmzugMigration) => MaybePromise<void>): IMigrator;
635
+ /**
636
+ * Removes event handler.
637
+ */
638
+ off(event: MigratorEvent, listener: (event: UmzugMigration) => MaybePromise<void>): IMigrator;
629
639
  /**
630
640
  * @internal
631
641
  */
632
642
  getStorage(): IMigratorStorage;
633
643
  }
644
+ export type MigratorEvent = 'migrating' | 'migrated' | 'reverting' | 'reverted';
634
645
  export interface MigrationDiff {
635
646
  up: string[];
636
647
  down: string[];
@@ -786,4 +797,10 @@ export interface Seeder<T extends Dictionary = Dictionary> {
786
797
  }
787
798
  export type ConnectionType = 'read' | 'write';
788
799
  export type MetadataProcessor = (metadata: EntityMetadata[], platform: Platform) => MaybePromise<void>;
800
+ /**
801
+ * The type of context that the user intends to inject.
802
+ */
803
+ export type ContextProvider<T> = MaybePromise<MikroORM> | ((type: T) => MaybePromise<MikroORM | EntityManager | EntityRepository<any> | {
804
+ getEntityManager(): EntityManager;
805
+ }>);
789
806
  export {};
@@ -34,9 +34,10 @@ export declare class Configuration<D extends IDatabaseDriver = IDatabaseDriver,
34
34
  requireEntitiesArray: false;
35
35
  checkDuplicateTableNames: true;
36
36
  checkDuplicateFieldNames: true;
37
+ checkDuplicateEntities: true;
38
+ checkNonPersistentCompositeProps: true;
37
39
  alwaysAnalyseProperties: true;
38
40
  disableDynamicFileAccess: false;
39
- checkDuplicateEntities: true;
40
41
  inferDefaultValues: true;
41
42
  };
42
43
  strict: false;
@@ -57,6 +58,7 @@ export declare class Configuration<D extends IDatabaseDriver = IDatabaseDriver,
57
58
  populateWhere: PopulateHint.ALL;
58
59
  connect: true;
59
60
  ignoreUndefinedInQuery: false;
61
+ onQuery: (sql: string) => string;
60
62
  autoJoinOneToOneOwner: true;
61
63
  autoJoinRefsForFilters: true;
62
64
  propagationOnPrototype: true;
@@ -71,12 +73,14 @@ export declare class Configuration<D extends IDatabaseDriver = IDatabaseDriver,
71
73
  mergeEmbeddedProperties: true;
72
74
  };
73
75
  persistOnCreate: true;
76
+ upsertManaged: true;
74
77
  forceEntityConstructor: false;
75
78
  forceUndefined: false;
76
79
  ensureDatabase: true;
77
80
  ensureIndexes: false;
78
81
  batchSize: number;
79
82
  debug: false;
83
+ ignoreDeprecations: false;
80
84
  verbose: false;
81
85
  driverOptions: {};
82
86
  migrations: {
@@ -291,11 +295,12 @@ export interface MetadataDiscoveryOptions {
291
295
  requireEntitiesArray?: boolean;
292
296
  checkDuplicateTableNames?: boolean;
293
297
  checkDuplicateFieldNames?: boolean;
298
+ checkDuplicateEntities?: boolean;
299
+ checkNonPersistentCompositeProps?: boolean;
294
300
  alwaysAnalyseProperties?: boolean;
295
301
  disableDynamicFileAccess?: boolean;
296
302
  inferDefaultValues?: boolean;
297
303
  getMappedType?: (type: string, platform: Platform) => Type<unknown> | undefined;
298
- checkDuplicateEntities?: boolean;
299
304
  onMetadata?: (meta: EntityMetadata, platform: Platform) => MaybePromise<void>;
300
305
  afterDiscovered?: (storage: MetadataStorage, platform: Platform) => MaybePromise<void>;
301
306
  tsConfigPath?: string;
@@ -322,6 +327,7 @@ export interface MikroORMOptions<D extends IDatabaseDriver = IDatabaseDriver, EM
322
327
  connect: boolean;
323
328
  verbose: boolean;
324
329
  ignoreUndefinedInQuery?: boolean;
330
+ onQuery: (sql: string, params: unknown[]) => string;
325
331
  autoJoinOneToOneOwner: boolean;
326
332
  autoJoinRefsForFilters: boolean;
327
333
  propagationOnPrototype: boolean;
@@ -333,6 +339,7 @@ export interface MikroORMOptions<D extends IDatabaseDriver = IDatabaseDriver, EM
333
339
  };
334
340
  assign: AssignOptions<boolean>;
335
341
  persistOnCreate: boolean;
342
+ upsertManaged: boolean;
336
343
  forceEntityConstructor: boolean | (Constructor<AnyEntity> | string)[];
337
344
  forceUndefined: boolean;
338
345
  forceUtcTimezone?: boolean;
@@ -363,7 +370,15 @@ export interface MikroORMOptions<D extends IDatabaseDriver = IDatabaseDriver, EM
363
370
  findOneOrFailHandler: (entityName: string, where: Dictionary | IPrimaryKey) => Error;
364
371
  findExactlyOneOrFailHandler: (entityName: string, where: Dictionary | IPrimaryKey) => Error;
365
372
  debug: boolean | LoggerNamespace[];
373
+ ignoreDeprecations: boolean | string[];
366
374
  highlighter: Highlighter;
375
+ /**
376
+ * Using this option, you can force the ORM to use the TS options regardless of whether the TypeScript support
377
+ * is detected or not. This effectively means using `entitiesTs` for discovery and `pathTs` for migrations and
378
+ * seeders. Should be used only for tests and stay disabled for production builds.
379
+ */
380
+ preferTs?: boolean;
381
+ /** @deprecated use `preferTs` instead */
367
382
  tsNode?: boolean;
368
383
  baseDir: string;
369
384
  migrations: MigrationsOptions;
@@ -27,9 +27,10 @@ class Configuration {
27
27
  requireEntitiesArray: false,
28
28
  checkDuplicateTableNames: true,
29
29
  checkDuplicateFieldNames: true,
30
+ checkDuplicateEntities: true,
31
+ checkNonPersistentCompositeProps: true,
30
32
  alwaysAnalyseProperties: true,
31
33
  disableDynamicFileAccess: false,
32
- checkDuplicateEntities: true,
33
34
  inferDefaultValues: true,
34
35
  },
35
36
  strict: false,
@@ -51,6 +52,7 @@ class Configuration {
51
52
  populateWhere: enums_1.PopulateHint.ALL,
52
53
  connect: true,
53
54
  ignoreUndefinedInQuery: false,
55
+ onQuery: sql => sql,
54
56
  autoJoinOneToOneOwner: true,
55
57
  autoJoinRefsForFilters: true,
56
58
  propagationOnPrototype: true,
@@ -65,12 +67,14 @@ class Configuration {
65
67
  mergeEmbeddedProperties: true,
66
68
  },
67
69
  persistOnCreate: true,
70
+ upsertManaged: true,
68
71
  forceEntityConstructor: false,
69
72
  forceUndefined: false,
70
73
  ensureDatabase: true,
71
74
  ensureIndexes: false,
72
75
  batchSize: 300,
73
76
  debug: false,
77
+ ignoreDeprecations: false,
74
78
  verbose: false,
75
79
  driverOptions: {},
76
80
  migrations: {
@@ -142,12 +146,14 @@ class Configuration {
142
146
  }
143
147
  this.options = Utils_1.Utils.mergeConfig({}, Configuration.DEFAULTS, options);
144
148
  this.options.baseDir = Utils_1.Utils.absolutePath(this.options.baseDir);
149
+ this.options.preferTs ??= options.tsNode;
145
150
  if (validate) {
146
151
  this.validateOptions();
147
152
  }
148
153
  this.options.loggerFactory ??= logging_1.DefaultLogger.create;
149
154
  this.logger = this.options.loggerFactory({
150
155
  debugMode: this.options.debug,
156
+ ignoreDeprecations: this.options.ignoreDeprecations,
151
157
  usesReplicas: (this.options.replicas?.length ?? 0) > 0,
152
158
  highlighter: this.options.highlighter,
153
159
  writer: this.options.logger,
@@ -326,6 +332,7 @@ class Configuration {
326
332
  }
327
333
  sync() {
328
334
  process.env.MIKRO_ORM_COLORS = '' + this.options.colors;
335
+ this.options.tsNode = this.options.preferTs;
329
336
  this.logger.setDebugMode(this.options.debug);
330
337
  }
331
338
  /**
@@ -6,9 +6,33 @@ import { Configuration, type Options } from './Configuration';
6
6
  * @internal
7
7
  */
8
8
  export declare class ConfigurationLoader {
9
- static getConfiguration<D extends IDatabaseDriver = IDatabaseDriver, EM extends D[typeof EntityManagerType] & EntityManager = EntityManager>(validate?: boolean, options?: Partial<Options>): Promise<Configuration<D, EM>>;
9
+ /**
10
+ * Gets a named configuration
11
+ *
12
+ * @param contextName Load a config with the given `contextName` value. Used when config file exports array or factory function. Setting it to "default" matches also config objects without `contextName` set.
13
+ * @param paths Array of possible paths for a configuration file. Files will be checked in order, and the first existing one will be used. Defaults to the output of {@link ConfigurationLoader.getConfigPaths}.
14
+ * @param options Additional options to augment the final configuration with.
15
+ */
16
+ static getConfiguration<D extends IDatabaseDriver = IDatabaseDriver, EM extends D[typeof EntityManagerType] & EntityManager = EntityManager>(contextName: string, paths?: string[], options?: Partial<Options>): Promise<Configuration<D, EM>>;
17
+ /**
18
+ * Gets the default config from the default paths
19
+ *
20
+ * @deprecated Prefer to explicitly set the `contextName` at the first argument. This signature is available for backwards compatibility, and may be removed in v7.
21
+ */
22
+ static getConfiguration<D extends IDatabaseDriver = IDatabaseDriver, EM extends D[typeof EntityManagerType] & EntityManager = EntityManager>(): Promise<Configuration<D, EM>>;
23
+ /**
24
+ * Gets default configuration out of the default paths, and possibly from `process.argv`
25
+ *
26
+ * @param validate Whether to validate the final configuration.
27
+ * @param options Additional options to augment the final configuration with (just before validation).
28
+ *
29
+ * @deprecated Use the other overloads of this method. This signature will be removed in v7.
30
+ */
31
+ static getConfiguration<D extends IDatabaseDriver = IDatabaseDriver, EM extends D[typeof EntityManagerType] & EntityManager = EntityManager>(validate: boolean, options?: Partial<Options>): Promise<Configuration<D, EM>>;
32
+ static getConfigFile(paths: string[]): Promise<[string, unknown] | []>;
10
33
  static getPackageConfig(basePath?: string): Dictionary;
11
34
  static getSettings(): Settings;
35
+ static configPathsFromArg(): string[] | undefined;
12
36
  static getConfigPaths(): string[];
13
37
  static isESM(): boolean;
14
38
  static registerTsNode(configPath?: string): boolean;
@@ -16,32 +16,103 @@ const Utils_1 = require("./Utils");
16
16
  * @internal
17
17
  */
18
18
  class ConfigurationLoader {
19
- static async getConfiguration(validate = true, options = {}) {
20
- this.commonJSCompat(options);
21
- this.registerDotenv(options);
22
- const paths = this.getConfigPaths();
19
+ static async getConfiguration(contextName = 'default', paths = ConfigurationLoader.getConfigPaths(), options = {}) {
20
+ // Backwards compatibility layer
21
+ if (typeof contextName === 'boolean' || !Array.isArray(paths)) {
22
+ this.commonJSCompat(options);
23
+ this.registerDotenv(options);
24
+ const configPathFromArg = ConfigurationLoader.configPathsFromArg();
25
+ const configPaths = configPathFromArg ?? (Array.isArray(paths) ? paths : ConfigurationLoader.getConfigPaths());
26
+ const config = contextName
27
+ ? (await ConfigurationLoader.getConfiguration(process.env.MIKRO_ORM_CONTEXT_NAME ?? 'default', configPaths, Array.isArray(paths) ? {} : paths))
28
+ : await (async () => {
29
+ const env = this.loadEnvironmentVars();
30
+ const [path, tmp] = await this.getConfigFile(configPaths);
31
+ if (!path) {
32
+ if (Utils_1.Utils.hasObjectKeys(env)) {
33
+ return new Configuration_1.Configuration(Utils_1.Utils.mergeConfig({}, options, env), false);
34
+ }
35
+ throw new Error(`MikroORM config file not found in ['${configPaths.join(`', '`)}']`);
36
+ }
37
+ return new Configuration_1.Configuration(Utils_1.Utils.mergeConfig(tmp, options, env), false);
38
+ })();
39
+ if (configPathFromArg) {
40
+ config.getLogger().warn('deprecated', 'Path for config file was inferred from the command line arguments. Instead, you should set the MIKRO_ORM_CLI_CONFIG environment variable to specify the path, or if you really must use the command line arguments, import the config manually based on them, and pass it to init.', { label: 'D0001' });
41
+ }
42
+ return config;
43
+ }
23
44
  const env = this.loadEnvironmentVars();
45
+ const configFinder = (cfg) => {
46
+ return typeof cfg === 'object' && cfg !== null && ('contextName' in cfg ? cfg.contextName === contextName : (contextName === 'default'));
47
+ };
48
+ const isValidConfigFactoryResult = (cfg) => {
49
+ return typeof cfg === 'object' && cfg !== null && (!('contextName' in cfg) || cfg.contextName === contextName);
50
+ };
51
+ const result = await this.getConfigFile(paths);
52
+ if (!result[0]) {
53
+ if (Utils_1.Utils.hasObjectKeys(env)) {
54
+ return new Configuration_1.Configuration(Utils_1.Utils.mergeConfig({ contextName }, options, env));
55
+ }
56
+ throw new Error(`MikroORM config file not found in ['${paths.join(`', '`)}']`);
57
+ }
58
+ const path = result[0];
59
+ let tmp = result[1];
60
+ if (Array.isArray(tmp)) {
61
+ const tmpFirstIndex = tmp.findIndex(configFinder);
62
+ if (tmpFirstIndex === -1) {
63
+ // Static config not found. Try factory functions
64
+ let configCandidate;
65
+ for (let i = 0, l = tmp.length; i < l; ++i) {
66
+ const f = tmp[i];
67
+ if (typeof f !== 'function') {
68
+ continue;
69
+ }
70
+ configCandidate = await f(contextName);
71
+ if (!isValidConfigFactoryResult(configCandidate)) {
72
+ continue;
73
+ }
74
+ tmp = configCandidate;
75
+ break;
76
+ }
77
+ if (Array.isArray(tmp)) {
78
+ throw new Error(`MikroORM config '${contextName}' was not found within the config file '${path}'. Either add a config with this name to the array, or add a function that when given this name will return a configuration object without a name, or with name set to this name.`);
79
+ }
80
+ }
81
+ else {
82
+ const tmpLastIndex = tmp.findLastIndex(configFinder);
83
+ if (tmpLastIndex !== tmpFirstIndex) {
84
+ throw new Error(`MikroORM config '${contextName}' is not unique within the array exported by '${path}' (first occurrence index: ${tmpFirstIndex}; last occurrence index: ${tmpLastIndex})`);
85
+ }
86
+ tmp = tmp[tmpFirstIndex];
87
+ }
88
+ }
89
+ else {
90
+ if (tmp instanceof Function) {
91
+ tmp = await tmp(contextName);
92
+ if (!isValidConfigFactoryResult(tmp)) {
93
+ throw new Error(`MikroORM config '${contextName}' was not what the function exported from '${path}' provided. Ensure it returns a config object with no name, or name matching the requested one.`);
94
+ }
95
+ }
96
+ else {
97
+ if (!configFinder(tmp)) {
98
+ throw new Error(`MikroORM config '${contextName}' was not what the default export from '${path}' provided.`);
99
+ }
100
+ }
101
+ }
102
+ const esmConfigOptions = this.isESM() ? { entityGenerator: { esmImport: true } } : {};
103
+ return new Configuration_1.Configuration(Utils_1.Utils.mergeConfig({}, esmConfigOptions, tmp, options, env));
104
+ }
105
+ static async getConfigFile(paths) {
24
106
  for (let path of paths) {
25
107
  path = Utils_1.Utils.absolutePath(path);
26
108
  path = Utils_1.Utils.normalizePath(path);
27
109
  if ((0, fs_extra_1.pathExistsSync)(path)) {
28
110
  const config = await Utils_1.Utils.dynamicImport(path);
29
111
  /* istanbul ignore next */
30
- let tmp = config.default ?? config;
31
- if (tmp instanceof Function) {
32
- tmp = tmp();
33
- }
34
- if (tmp instanceof Promise) {
35
- tmp = await tmp;
36
- }
37
- const esmConfigOptions = this.isESM() ? { entityGenerator: { esmImport: true } } : {};
38
- return new Configuration_1.Configuration(Utils_1.Utils.mergeConfig({}, esmConfigOptions, tmp, options, env), validate);
112
+ return [path, await (config.default ?? config)];
39
113
  }
40
114
  }
41
- if (Utils_1.Utils.hasObjectKeys(env)) {
42
- return new Configuration_1.Configuration(Utils_1.Utils.mergeConfig({}, options, env), validate);
43
- }
44
- throw new Error(`MikroORM config file not found in ['${paths.join(`', '`)}']`);
115
+ return [];
45
116
  }
46
117
  static getPackageConfig(basePath = process.cwd()) {
47
118
  if ((0, fs_extra_1.pathExistsSync)(`${basePath}/package.json`)) {
@@ -73,12 +144,15 @@ class ConfigurationLoader {
73
144
  }
74
145
  return settings;
75
146
  }
76
- static getConfigPaths() {
147
+ static configPathsFromArg() {
77
148
  const options = Utils_1.Utils.parseArgs();
78
149
  const configArgName = process.env.MIKRO_ORM_CONFIG_ARG_NAME ?? 'config';
79
150
  if (options[configArgName]) {
80
151
  return [options[configArgName]];
81
152
  }
153
+ return undefined;
154
+ }
155
+ static getConfigPaths() {
82
156
  const paths = [];
83
157
  const settings = ConfigurationLoader.getSettings();
84
158
  if (process.env.MIKRO_ORM_CLI_CONFIG) {
@@ -23,7 +23,7 @@ export declare class DataloaderUtils {
23
23
  static groupInversedOrMappedKeysByEntityAndOpts(collsWithOpts: readonly [Collection<any>, Omit<InitCollectionOptions<any, any>, 'dataloader'>?][]): Map<string, Map<string, Set<Primary<any>>>>;
24
24
  /**
25
25
  * Turn the entity+options map into actual queries.
26
- * The keys are the entity names + a strigified version of the options and the values are filter Maps which will be used as the values of an $or operator so we end up with a query per entity+opts.
26
+ * The keys are the entity names + a stringified version of the options and the values are filter Maps which will be used as the values of an $or operator so we end up with a query per entity+opts.
27
27
  * We must populate the inverse side of the relationship in order to be able to later retrieve the PK(s) from its item(s).
28
28
  * Together with the query the promises will also return the key which can be used to narrow down the results pertaining to a certain set of options.
29
29
  */
@@ -33,7 +33,7 @@ export declare class DataloaderUtils {
33
33
  * First checks if the Entity type matches, then retrieves the inverse side of the relationship
34
34
  * where the filtering will be done in order to match the target collection.
35
35
  */
36
- static getColFilter<T, S extends T>(collection: Collection<any, object>): (result: T) => result is S;
36
+ static getColFilter<T, S extends T>(collection: Collection<any>): (result: T) => result is S;
37
37
  /**
38
38
  * Returns the collection dataloader batchLoadFn, which aggregates collections by entity,
39
39
  * makes one query per entity and maps each input collection to the corresponding result.
@@ -4,6 +4,7 @@ exports.DataloaderUtils = void 0;
4
4
  const Collection_1 = require("../entity/Collection");
5
5
  const wrap_1 = require("../entity/wrap");
6
6
  const enums_1 = require("../enums");
7
+ const Reference_1 = require("../entity/Reference");
7
8
  class DataloaderUtils {
8
9
  /**
9
10
  * Groups identified references by entity and returns a Map with the
@@ -92,7 +93,7 @@ class DataloaderUtils {
92
93
  }
93
94
  /**
94
95
  * Turn the entity+options map into actual queries.
95
- * The keys are the entity names + a strigified version of the options and the values are filter Maps which will be used as the values of an $or operator so we end up with a query per entity+opts.
96
+ * The keys are the entity names + a stringified version of the options and the values are filter Maps which will be used as the values of an $or operator so we end up with a query per entity+opts.
96
97
  * We must populate the inverse side of the relationship in order to be able to later retrieve the PK(s) from its item(s).
97
98
  * Together with the query the promises will also return the key which can be used to narrow down the results pertaining to a certain set of options.
98
99
  */
@@ -139,20 +140,21 @@ class DataloaderUtils {
139
140
  // There is no need to check if Entity matches because we already matched the key which is entity+options.
140
141
  // This is the inverse side of the relationship where the filtering will be done in order to match the target collection
141
142
  // Either inversedBy or mappedBy exist because we already checked in groupInversedOrMappedKeysByEntity
142
- const refOrCol = result[(collection.property.inversedBy ?? collection.property.mappedBy)];
143
- if (refOrCol instanceof Collection_1.Collection) {
144
- // The inverse side is a Collection
145
- // We keep the result if any of PKs of the inverse side matches the PK of the collection owner
146
- for (const item of refOrCol.getItems()) {
147
- if ((0, wrap_1.helper)(item).getSerializedPrimaryKey() === (0, wrap_1.helper)(collection.owner).getSerializedPrimaryKey()) {
143
+ const inverseProp = collection.property.inversedBy ?? collection.property.mappedBy;
144
+ const target = Reference_1.Reference.unwrapReference(result[inverseProp]);
145
+ if (target instanceof Collection_1.Collection) {
146
+ for (const item of target) {
147
+ if (item === collection.owner) {
148
148
  return true;
149
149
  }
150
150
  }
151
151
  }
152
- else {
153
- // The inverse side is a Reference
154
- // We keep the result if the PK of the inverse side matches the PK of the collection owner
155
- return (0, wrap_1.helper)(refOrCol).getSerializedPrimaryKey() === (0, wrap_1.helper)(collection.owner).getSerializedPrimaryKey();
152
+ else if (target) {
153
+ return target === collection.owner;
154
+ }
155
+ // FIXME https://github.com/mikro-orm/mikro-orm/issues/6031
156
+ if (!target && collection.property.kind === enums_1.ReferenceKind.MANY_TO_MANY) {
157
+ throw new Error(`Inverse side is required for M:N relations with dataloader: ${collection.owner.constructor.name}.${collection.property.name}`);
156
158
  }
157
159
  return false;
158
160
  };
@@ -0,0 +1,10 @@
1
+ import { EntityManager } from '../EntityManager';
2
+ import { MikroORM } from '../MikroORM';
3
+ import type { ContextProvider, MaybePromise } from '../typings';
4
+ /**
5
+ * Find `EntityManager` in provided context, or else in instance's `orm` or `em` properties.
6
+ */
7
+ export declare function resolveContextProvider<T>(caller: T & {
8
+ orm?: MaybePromise<MikroORM>;
9
+ em?: MaybePromise<EntityManager>;
10
+ }, provider?: ContextProvider<T>): Promise<EntityManager | undefined>;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveContextProvider = resolveContextProvider;
4
+ const EntityRepository_1 = require("../entity/EntityRepository");
5
+ const EntityManager_1 = require("../EntityManager");
6
+ const MikroORM_1 = require("../MikroORM");
7
+ function getEntityManager(caller, context) {
8
+ if (context instanceof EntityManager_1.EntityManager) {
9
+ return context;
10
+ }
11
+ if (context instanceof EntityRepository_1.EntityRepository) {
12
+ return context.getEntityManager();
13
+ }
14
+ if (context instanceof MikroORM_1.MikroORM) {
15
+ return context.em;
16
+ }
17
+ if (caller.em instanceof EntityManager_1.EntityManager) {
18
+ return caller.em;
19
+ }
20
+ if (caller.orm instanceof MikroORM_1.MikroORM) {
21
+ return caller.orm.em;
22
+ }
23
+ return undefined;
24
+ }
25
+ /**
26
+ * Find `EntityManager` in provided context, or else in instance's `orm` or `em` properties.
27
+ */
28
+ async function resolveContextProvider(caller, provider) {
29
+ const context = typeof provider === 'function' ? await provider(caller) : await provider;
30
+ return getEntityManager({ orm: await caller.orm, em: await caller.em }, context);
31
+ }