@mikro-orm/core 7.0.0-dev.21 → 7.0.0-dev.23

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 (72) hide show
  1. package/EntityManager.d.ts +12 -2
  2. package/EntityManager.js +40 -53
  3. package/README.md +1 -2
  4. package/connections/Connection.d.ts +4 -2
  5. package/connections/Connection.js +2 -2
  6. package/decorators/Entity.d.ts +15 -0
  7. package/decorators/Indexed.d.ts +2 -2
  8. package/decorators/ManyToMany.d.ts +2 -0
  9. package/decorators/ManyToOne.d.ts +2 -0
  10. package/decorators/OneToOne.d.ts +2 -0
  11. package/decorators/Transactional.d.ts +1 -0
  12. package/decorators/Transactional.js +3 -3
  13. package/drivers/IDatabaseDriver.d.ts +4 -0
  14. package/entity/ArrayCollection.d.ts +3 -1
  15. package/entity/ArrayCollection.js +7 -2
  16. package/entity/Collection.js +3 -2
  17. package/entity/EntityFactory.d.ts +6 -0
  18. package/entity/EntityFactory.js +17 -6
  19. package/entity/EntityHelper.js +5 -1
  20. package/entity/EntityLoader.js +27 -19
  21. package/entity/Reference.d.ts +5 -0
  22. package/entity/Reference.js +16 -0
  23. package/entity/WrappedEntity.js +1 -1
  24. package/entity/defineEntity.d.ts +537 -0
  25. package/entity/defineEntity.js +690 -0
  26. package/entity/index.d.ts +2 -0
  27. package/entity/index.js +2 -0
  28. package/entity/utils.d.ts +7 -0
  29. package/entity/utils.js +15 -3
  30. package/enums.d.ts +15 -2
  31. package/enums.js +13 -0
  32. package/errors.d.ts +6 -0
  33. package/errors.js +14 -0
  34. package/hydration/ObjectHydrator.js +1 -1
  35. package/index.d.ts +1 -1
  36. package/metadata/EntitySchema.js +10 -2
  37. package/metadata/MetadataDiscovery.d.ts +0 -1
  38. package/metadata/MetadataDiscovery.js +27 -18
  39. package/package.json +4 -5
  40. package/platforms/Platform.d.ts +3 -1
  41. package/serialization/SerializationContext.js +13 -10
  42. package/types/BooleanType.d.ts +1 -1
  43. package/types/DecimalType.js +1 -1
  44. package/types/DoubleType.js +1 -1
  45. package/typings.d.ts +32 -10
  46. package/typings.js +21 -4
  47. package/unit-of-work/ChangeSetComputer.js +3 -1
  48. package/unit-of-work/ChangeSetPersister.d.ts +4 -2
  49. package/unit-of-work/ChangeSetPersister.js +14 -10
  50. package/unit-of-work/UnitOfWork.d.ts +2 -1
  51. package/unit-of-work/UnitOfWork.js +36 -13
  52. package/utils/Configuration.d.ts +7 -1
  53. package/utils/Configuration.js +1 -0
  54. package/utils/ConfigurationLoader.js +2 -2
  55. package/utils/Cursor.js +3 -0
  56. package/utils/DataloaderUtils.d.ts +6 -1
  57. package/utils/DataloaderUtils.js +37 -20
  58. package/utils/EntityComparator.d.ts +6 -2
  59. package/utils/EntityComparator.js +29 -8
  60. package/utils/QueryHelper.d.ts +6 -0
  61. package/utils/QueryHelper.js +47 -4
  62. package/utils/RawQueryFragment.d.ts +34 -0
  63. package/utils/RawQueryFragment.js +34 -0
  64. package/utils/TransactionManager.d.ts +65 -0
  65. package/utils/TransactionManager.js +199 -0
  66. package/utils/Utils.d.ts +2 -2
  67. package/utils/Utils.js +31 -7
  68. package/utils/index.d.ts +1 -0
  69. package/utils/index.js +1 -0
  70. package/utils/upsert-utils.js +9 -1
  71. package/exports.d.ts +0 -24
  72. package/exports.js +0 -23
@@ -33,6 +33,7 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
33
33
  readonly name: string;
34
34
  protected readonly refLoader: DataLoader<[Reference<any>, (Omit<import("./entity/Reference.js").LoadReferenceOptions<any, any, "*", never>, "dataloader"> | undefined)?], any, [Reference<any>, (Omit<import("./entity/Reference.js").LoadReferenceOptions<any, any, "*", never>, "dataloader"> | undefined)?]>;
35
35
  protected readonly colLoader: DataLoader<[import("./index.js").Collection<any, object>, (Omit<import("./index.js").InitCollectionOptions<any, any, "*", never>, "dataloader"> | undefined)?], any, [import("./index.js").Collection<any, object>, (Omit<import("./index.js").InitCollectionOptions<any, any, "*", never>, "dataloader"> | undefined)?]>;
36
+ protected readonly colLoaderMtoN: DataLoader<[import("./index.js").Collection<any, object>, (Omit<import("./index.js").InitCollectionOptions<any, any, "*", never>, "dataloader"> | undefined)?], any, [import("./index.js").Collection<any, object>, (Omit<import("./index.js").InitCollectionOptions<any, any, "*", never>, "dataloader"> | undefined)?]>;
36
37
  private readonly validator;
37
38
  private readonly repositoryMap;
38
39
  private readonly entityLoader;
@@ -117,7 +118,9 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
117
118
  /**
118
119
  * Gets logger context for this entity manager.
119
120
  */
120
- getLoggerContext<T extends Dictionary = Dictionary>(): T;
121
+ getLoggerContext<T extends Dictionary = Dictionary>(options?: {
122
+ disableContextResolution?: boolean;
123
+ }): T;
121
124
  setFlushMode(flushMode?: FlushMode): void;
122
125
  protected processWhere<Entity extends object, Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(entityName: string, where: FilterQuery<Entity>, options: FindOptions<Entity, Hint, Fields, Excludes> | FindOneOptions<Entity, Hint, Fields, Excludes>, type: 'read' | 'update' | 'delete'): Promise<FilterQuery<Entity>>;
123
126
  protected applyDiscriminatorCondition<Entity extends object>(entityName: string, where: FilterQuery<Entity>): FilterQuery<Entity>;
@@ -487,7 +490,7 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
487
490
  * some additional lazy properties, if so, we reload and merge the data from database
488
491
  */
489
492
  protected shouldRefresh<T extends object, P extends string = never, F extends string = '*', E extends string = never>(meta: EntityMetadata<T>, entity: T, options: FindOneOptions<T, P, F, E>): boolean;
490
- protected prepareOptions(options: FindOptions<any, any, any, any> | FindOneOptions<any, any, any, any>): void;
493
+ protected prepareOptions(options: FindOptions<any, any, any, any> | FindOneOptions<any, any, any, any> | CountOptions<any, any>): void;
491
494
  /**
492
495
  * @internal
493
496
  */
@@ -547,11 +550,18 @@ export interface CreateOptions<Convert extends boolean> {
547
550
  partial?: boolean;
548
551
  /** convert raw database values based on mapped types (by default, already converted values are expected) */
549
552
  convertCustomTypes?: Convert;
553
+ /**
554
+ * Property `onCreate` hooks are normally executed during `flush` operation.
555
+ * With this option, they will be processed early inside `em.create()` method.
556
+ */
557
+ processOnCreateHooksEarly?: boolean;
550
558
  }
551
559
  export interface MergeOptions {
552
560
  refresh?: boolean;
553
561
  convertCustomTypes?: boolean;
554
562
  schema?: string;
563
+ disableContextResolution?: boolean;
564
+ keepIdentity?: boolean;
555
565
  }
556
566
  export interface ForkOptions {
557
567
  /** do we want a clear identity map? defaults to true */
package/EntityManager.js CHANGED
@@ -19,6 +19,8 @@ import { EventType, FlushMode, LoadStrategy, LockMode, PopulateHint, PopulatePat
19
19
  import { EventManager } from './events/EventManager.js';
20
20
  import { TransactionEventBroadcaster } from './events/TransactionEventBroadcaster.js';
21
21
  import { OptimisticLockError, ValidationError } from './errors.js';
22
+ import { getLoadingStrategy } from './entity/utils.js';
23
+ import { TransactionManager } from './utils/TransactionManager.js';
22
24
  /**
23
25
  * The EntityManager is the central access point to ORM functionality. It is a facade to all different ORM subsystems
24
26
  * such as UnitOfWork, Query Language, and Repository API.
@@ -36,6 +38,7 @@ export class EntityManager {
36
38
  name;
37
39
  refLoader = new DataLoader(DataloaderUtils.getRefBatchLoadFn(this));
38
40
  colLoader = new DataLoader(DataloaderUtils.getColBatchLoadFn(this));
41
+ colLoaderMtoN = new DataLoader(DataloaderUtils.getManyToManyColBatchLoadFn(this));
39
42
  validator;
40
43
  repositoryMap = {};
41
44
  entityLoader;
@@ -137,7 +140,6 @@ export class EntityManager {
137
140
  await em.entityLoader.populate(entityName, cached.data, populate, {
138
141
  ...options,
139
142
  ...em.getPopulateWhere(where, options),
140
- convertCustomTypes: false,
141
143
  ignoreLazyScalarProperties: true,
142
144
  lookup: false,
143
145
  });
@@ -168,7 +170,6 @@ export class EntityManager {
168
170
  await em.entityLoader.populate(entityName, unique, populate, {
169
171
  ...options,
170
172
  ...em.getPopulateWhere(where, options),
171
- convertCustomTypes: false,
172
173
  ignoreLazyScalarProperties: true,
173
174
  lookup: false,
174
175
  });
@@ -232,8 +233,8 @@ export class EntityManager {
232
233
  /**
233
234
  * Gets logger context for this entity manager.
234
235
  */
235
- getLoggerContext() {
236
- const em = this.getContext();
236
+ getLoggerContext(options) {
237
+ const em = options?.disableContextResolution ? this : this.getContext();
237
238
  em.loggerContext ??= {};
238
239
  return em.loggerContext;
239
240
  }
@@ -289,7 +290,8 @@ export class EntityManager {
289
290
  for (const hint of options.populate) {
290
291
  const field = hint.field.split(':')[0];
291
292
  const prop = meta.properties[field];
292
- const joined = (prop.strategy || options.strategy || hint.strategy || this.config.get('loadStrategy')) === LoadStrategy.JOINED && prop.kind !== ReferenceKind.SCALAR;
293
+ const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
294
+ const joined = strategy === LoadStrategy.JOINED && prop.kind !== ReferenceKind.SCALAR;
293
295
  if (!joined && !hint.filter) {
294
296
  continue;
295
297
  }
@@ -326,10 +328,19 @@ export class EntityManager {
326
328
  const cond = await this.applyFilters(prop.type, {}, options.filters ?? {}, 'read', options);
327
329
  if (!Utils.isEmpty(cond)) {
328
330
  const populated = options.populate.filter(({ field }) => field.split(':')[0] === prop.name);
331
+ let found = false;
329
332
  if (populated.length > 0) {
330
- populated.forEach(hint => hint.filter = true);
333
+ for (const hint of populated) {
334
+ if (!hint.all) {
335
+ hint.filter = true;
336
+ found = true;
337
+ }
338
+ else if (hint.field === `${prop.name}:ref`) {
339
+ found = true;
340
+ }
341
+ }
331
342
  }
332
- else {
343
+ if (!found) {
333
344
  ret.push({ field: `${prop.name}:ref`, strategy: LoadStrategy.JOINED, filter: true });
334
345
  }
335
346
  }
@@ -494,11 +505,17 @@ export class EntityManager {
494
505
  ...options,
495
506
  flushMode: FlushMode.COMMIT,
496
507
  });
508
+ const em = this.getContext();
497
509
  if (reloaded) {
498
- this.config.getHydrator(this.metadata).hydrate(entity, helper(entity).__meta, helper(reloaded).toPOJO(), this.getEntityFactory(), 'full');
510
+ for (const e of fork.unitOfWork.getIdentityMap()) {
511
+ const ref = em.getReference(e.constructor.name, helper(e).getPrimaryKey());
512
+ const data = this.comparator.prepareEntity(e);
513
+ em.config.getHydrator(this.metadata).hydrate(ref, helper(ref).__meta, helper(e).serialize({ ignoreSerializers: true }), em.entityFactory, 'full', false, true);
514
+ helper(ref).__originalEntityData = data;
515
+ }
499
516
  }
500
517
  else {
501
- this.getUnitOfWork().unsetIdentity(entity);
518
+ em.unitOfWork.unsetIdentity(entity);
502
519
  }
503
520
  return reloaded ? entity : reloaded;
504
521
  }
@@ -541,7 +558,6 @@ export class EntityManager {
541
558
  await em.entityLoader.populate(entityName, [cached.data], options.populate, {
542
559
  ...options,
543
560
  ...em.getPopulateWhere(where, options),
544
- convertCustomTypes: false,
545
561
  ignoreLazyScalarProperties: true,
546
562
  lookup: false,
547
563
  });
@@ -969,39 +985,8 @@ export class EntityManager {
969
985
  if (this.disableTransactions || em.disableTransactions) {
970
986
  return cb(em);
971
987
  }
972
- const fork = em.fork({
973
- clear: options.clear ?? false, // state will be merged once resolves
974
- flushMode: options.flushMode,
975
- cloneEventManager: true,
976
- disableTransactions: options.ignoreNestedTransactions,
977
- loggerContext: options.loggerContext,
978
- });
979
- options.ctx ??= em.transactionContext;
980
- const propagateToUpperContext = !em.global || this.config.get('allowGlobalContext');
981
- return TransactionContext.create(fork, async () => {
982
- return fork.getConnection().transactional(async (trx) => {
983
- fork.transactionContext = trx;
984
- if (propagateToUpperContext) {
985
- fork.eventManager.registerSubscriber({
986
- afterFlush(args) {
987
- args.uow.getChangeSets()
988
- .filter(cs => [ChangeSetType.DELETE, ChangeSetType.DELETE_EARLY].includes(cs.type))
989
- .forEach(cs => em.unitOfWork.unsetIdentity(cs.entity));
990
- },
991
- });
992
- }
993
- const ret = await cb(fork);
994
- await fork.flush();
995
- if (propagateToUpperContext) {
996
- // ensure all entities from inner context are merged to the upper one
997
- for (const entity of fork.unitOfWork.getIdentityMap()) {
998
- em.unitOfWork.register(entity);
999
- entity.__helper.__em = em;
1000
- }
1001
- }
1002
- return ret;
1003
- }, { ...options, eventBroadcaster: new TransactionEventBroadcaster(fork, { topLevelTransaction: !options.ctx }) });
1004
- });
988
+ const manager = new TransactionManager(this);
989
+ return manager.handle(cb, options);
1005
990
  }
1006
991
  /**
1007
992
  * Starts new transaction bound to this EntityManager. Use `ctx` parameter to provide the parent when nesting transactions.
@@ -1181,10 +1166,10 @@ export class EntityManager {
1181
1166
  * via second parameter. By default, it will return already loaded entities without modifying them.
1182
1167
  */
1183
1168
  merge(entityName, data, options = {}) {
1184
- const em = this.getContext();
1185
1169
  if (Utils.isEntity(entityName)) {
1186
- return em.merge(entityName.constructor.name, entityName, data);
1170
+ return this.merge(entityName.constructor.name, entityName, data);
1187
1171
  }
1172
+ const em = options.disableContextResolution ? this : this.getContext();
1188
1173
  options.schema ??= em._schema;
1189
1174
  entityName = Utils.className(entityName);
1190
1175
  em.validator.validatePrimaryKey(data, em.metadata.get(entityName));
@@ -1194,7 +1179,12 @@ export class EntityManager {
1194
1179
  }
1195
1180
  const meta = em.metadata.find(entityName);
1196
1181
  const childMeta = em.metadata.getByDiscriminatorColumn(meta, data);
1197
- entity = Utils.isEntity(data) ? data : em.entityFactory.create(entityName, data, { merge: true, ...options });
1182
+ const dataIsEntity = Utils.isEntity(data);
1183
+ if (options.keepIdentity && entity && dataIsEntity && entity !== data) {
1184
+ em.entityFactory.mergeData(meta, entity, helper(data).__originalEntityData, { initialized: true, merge: true, ...options });
1185
+ return entity;
1186
+ }
1187
+ entity = dataIsEntity ? data : em.entityFactory.create(entityName, data, { merge: true, ...options });
1198
1188
  em.validator.validate(entity, data, childMeta ?? meta);
1199
1189
  em.unitOfWork.merge(entity);
1200
1190
  return entity;
@@ -1259,10 +1249,8 @@ export class EntityManager {
1259
1249
  async count(entityName, where = {}, options = {}) {
1260
1250
  const em = this.getContext(false);
1261
1251
  // Shallow copy options since the object will be modified when deleting orderBy
1262
- options = {
1263
- schema: em._schema,
1264
- ...options,
1265
- };
1252
+ options = { ...options };
1253
+ em.prepareOptions(options);
1266
1254
  entityName = Utils.className(entityName);
1267
1255
  await em.tryFlush(entityName, options);
1268
1256
  where = await em.processWhere(entityName, where, options, 'read');
@@ -1280,7 +1268,7 @@ export class EntityManager {
1280
1268
  if (cached?.data !== undefined) {
1281
1269
  return cached.data;
1282
1270
  }
1283
- const count = await em.driver.count(entityName, where, { ctx: em.transactionContext, ...options });
1271
+ const count = await em.driver.count(entityName, where, { ctx: em.transactionContext, em, ...options });
1284
1272
  await em.storeCache(options.cache, cached, () => +count);
1285
1273
  return +count;
1286
1274
  }
@@ -1568,7 +1556,6 @@ export class EntityManager {
1568
1556
  ...options,
1569
1557
  ...this.getPopulateWhere(where, options),
1570
1558
  orderBy: options.populateOrderBy ?? options.orderBy,
1571
- convertCustomTypes: false,
1572
1559
  ignoreLazyScalarProperties: true,
1573
1560
  lookup: false,
1574
1561
  });
@@ -1691,7 +1678,7 @@ export class EntityManager {
1691
1678
  throw new ValidationError(`Cannot combine 'fields' and 'exclude' option.`);
1692
1679
  }
1693
1680
  options.schema ??= this._schema;
1694
- options.logging = Utils.merge({ id: this.id }, this.loggerContext, options.loggerContext, options.logging);
1681
+ options.logging = options.loggerContext = Utils.merge({ id: this.id }, this.loggerContext, options.loggerContext, options.logging);
1695
1682
  }
1696
1683
  /**
1697
1684
  * @internal
package/README.md CHANGED
@@ -11,7 +11,6 @@ TypeScript ORM for Node.js based on Data Mapper, [Unit of Work](https://mikro-or
11
11
  [![Chat on discord](https://img.shields.io/discord/1214904142443839538?label=discord&color=blue)](https://discord.gg/w8bjxFHS7X)
12
12
  [![Downloads](https://img.shields.io/npm/dm/@mikro-orm/core.svg)](https://www.npmjs.com/package/@mikro-orm/core)
13
13
  [![Coverage Status](https://img.shields.io/coveralls/mikro-orm/mikro-orm.svg)](https://coveralls.io/r/mikro-orm/mikro-orm?branch=master)
14
- [![Maintainability](https://api.codeclimate.com/v1/badges/27999651d3adc47cfa40/maintainability)](https://codeclimate.com/github/mikro-orm/mikro-orm/maintainability)
15
14
  [![Build Status](https://github.com/mikro-orm/mikro-orm/workflows/tests/badge.svg?branch=master)](https://github.com/mikro-orm/mikro-orm/actions?workflow=tests)
16
15
 
17
16
  ## 🤔 Unit of What?
@@ -141,7 +140,7 @@ There is also auto-generated [CHANGELOG.md](CHANGELOG.md) file based on commit m
141
140
  - [Composite and Foreign Keys as Primary Key](https://mikro-orm.io/docs/composite-keys)
142
141
  - [Filters](https://mikro-orm.io/docs/filters)
143
142
  - [Using `QueryBuilder`](https://mikro-orm.io/docs/query-builder)
144
- - [Preloading Deeply Nested Structures via populate](https://mikro-orm.io/docs/nested-populate)
143
+ - [Populating relations](https://mikro-orm.io/docs/populating-relations)
145
144
  - [Property Validation](https://mikro-orm.io/docs/property-validation)
146
145
  - [Lifecycle Hooks](https://mikro-orm.io/docs/events#hooks)
147
146
  - [Vanilla JS Support](https://mikro-orm.io/docs/usage-with-js)
@@ -45,15 +45,17 @@ export declare abstract class Connection {
45
45
  readOnly?: boolean;
46
46
  ctx?: Transaction;
47
47
  eventBroadcaster?: TransactionEventBroadcaster;
48
+ loggerContext?: LogContext;
48
49
  }): Promise<T>;
49
50
  begin(options?: {
50
51
  isolationLevel?: IsolationLevel;
51
52
  readOnly?: boolean;
52
53
  ctx?: Transaction;
53
54
  eventBroadcaster?: TransactionEventBroadcaster;
55
+ loggerContext?: LogContext;
54
56
  }): Promise<Transaction>;
55
- commit(ctx: Transaction, eventBroadcaster?: TransactionEventBroadcaster): Promise<void>;
56
- rollback(ctx: Transaction, eventBroadcaster?: TransactionEventBroadcaster): Promise<void>;
57
+ commit(ctx: Transaction, eventBroadcaster?: TransactionEventBroadcaster, loggerContext?: LogContext): Promise<void>;
58
+ rollback(ctx: Transaction, eventBroadcaster?: TransactionEventBroadcaster, loggerContext?: LogContext): Promise<void>;
57
59
  abstract execute<T>(query: string, params?: any[], method?: 'all' | 'get' | 'run', ctx?: Transaction): Promise<QueryResult<T> | any | any[]>;
58
60
  getConnectionOptions(): ConnectionConfig;
59
61
  getClientUrl(): string;
@@ -46,10 +46,10 @@ export class Connection {
46
46
  async begin(options) {
47
47
  throw new Error(`Transactions are not supported by current driver`);
48
48
  }
49
- async commit(ctx, eventBroadcaster) {
49
+ async commit(ctx, eventBroadcaster, loggerContext) {
50
50
  throw new Error(`Transactions are not supported by current driver`);
51
51
  }
52
- async rollback(ctx, eventBroadcaster) {
52
+ async rollback(ctx, eventBroadcaster, loggerContext) {
53
53
  throw new Error(`Transactions are not supported by current driver`);
54
54
  }
55
55
  getConnectionOptions() {
@@ -2,17 +2,32 @@ import type { AnyString, Constructor, Dictionary, EntityClass, ObjectQuery } fro
2
2
  import type { FindOptions } from '../drivers/IDatabaseDriver.js';
3
3
  export declare function Entity<T extends EntityClass<unknown>>(options?: EntityOptions<T>): (target: T) => void;
4
4
  export type EntityOptions<T, E = T extends EntityClass<infer P> ? P : T> = {
5
+ /** Override default collection/table name. Alias for `collection`. */
5
6
  tableName?: string;
7
+ /** Sets the schema name. */
6
8
  schema?: string;
9
+ /** Override default collection/table name. Alias for `tableName`. */
7
10
  collection?: string;
11
+ /** For {@doclink inheritance-mapping#single-table-inheritance | Single Table Inheritance}. */
8
12
  discriminatorColumn?: (T extends EntityClass<infer P> ? keyof P : string) | AnyString;
13
+ /** For {@doclink inheritance-mapping#single-table-inheritance | Single Table Inheritance}. */
9
14
  discriminatorMap?: Dictionary<string>;
15
+ /** For {@doclink inheritance-mapping#single-table-inheritance | Single Table Inheritance}. */
10
16
  discriminatorValue?: number | string;
17
+ /** Enforce use of constructor when creating managed entity instances. */
11
18
  forceConstructor?: boolean;
19
+ /** Specify comment to table. (SQL only) */
12
20
  comment?: string;
21
+ /** Marks entity as abstract, such entities are inlined during discovery. */
13
22
  abstract?: boolean;
23
+ /** Disables change tracking - such entities are ignored during flush. */
14
24
  readonly?: boolean;
25
+ /** Marks entity as {@doclink virtual-entities | virtual}. This is set automatically when you use `expression` option. */
15
26
  virtual?: boolean;
27
+ /** Used to make ORM aware of externally defined triggers. This is needed for MS SQL Server multi inserts, ignored in other dialects. */
28
+ hasTriggers?: boolean;
29
+ /** SQL query that maps to a {@doclink virtual-entities | virtual entity}. */
16
30
  expression?: string | ((em: any, where: ObjectQuery<E>, options: FindOptions<E, any, any, any>) => object);
31
+ /** Set {@doclink repositories#custom-repository | custom repository class}. */
17
32
  repository?: () => Constructor;
18
33
  };
@@ -1,4 +1,4 @@
1
- import type { EntityClass, Dictionary, AutoPath } from '../typings.js';
1
+ import type { EntityClass, Dictionary, AutoPath, IndexCallback } from '../typings.js';
2
2
  import type { DeferMode } from '../enums.js';
3
3
  export declare function Index<T extends object, H extends string>(options?: IndexOptions<T, H>): (target: T, propertyName?: (T extends EntityClass<unknown> ? undefined : keyof T) | undefined) => any;
4
4
  export declare function Unique<T extends object, H extends string>(options?: UniqueOptions<T, H>): (target: T, propertyName?: (T extends EntityClass<unknown> ? undefined : keyof T) | undefined) => any;
@@ -8,7 +8,7 @@ interface BaseOptions<T, H extends string> {
8
8
  name?: string;
9
9
  properties?: (T extends EntityClass<infer P> ? Properties<P, H> : Properties<T, H>);
10
10
  options?: Dictionary;
11
- expression?: string;
11
+ expression?: string | (T extends EntityClass<infer P> ? IndexCallback<P> : IndexCallback<T>);
12
12
  }
13
13
  export interface UniqueOptions<T, H extends string = string> extends BaseOptions<T, H> {
14
14
  deferMode?: DeferMode | `${DeferMode}`;
@@ -37,4 +37,6 @@ export interface ManyToManyOptions<Owner, Target> extends ReferenceOptions<Owner
37
37
  deleteRule?: 'cascade' | 'no action' | 'set null' | 'set default' | AnyString;
38
38
  /** What to do when the reference to the target entity gets updated. */
39
39
  updateRule?: 'cascade' | 'no action' | 'set null' | 'set default' | AnyString;
40
+ /** Enable/disable foreign key constraint creation on this relation */
41
+ createForeignKeyConstraint?: boolean;
40
42
  }
@@ -27,6 +27,8 @@ export interface ManyToOneOptions<Owner, Target> extends ReferenceOptions<Owner,
27
27
  updateRule?: 'cascade' | 'no action' | 'set null' | 'set default' | AnyString;
28
28
  /** Set the constraint type. Immediate constraints are checked for each statement, while deferred ones are only checked at the end of the transaction. Only for postgres unique constraints. */
29
29
  deferMode?: DeferMode | `${DeferMode}`;
30
+ /** Enable/disable foreign key constraint creation on this relation */
31
+ createForeignKeyConstraint?: boolean;
30
32
  /** Set a custom foreign key constraint name, overriding NamingStrategy.indexName(). */
31
33
  foreignKeyName?: string;
32
34
  }
@@ -23,4 +23,6 @@ export interface OneToOneOptions<Owner, Target> extends Partial<Omit<OneToManyOp
23
23
  deferMode?: DeferMode | `${DeferMode}`;
24
24
  /** Set a custom foreign key constraint name, overriding NamingStrategy.indexName(). */
25
25
  foreignKeyName?: string;
26
+ /** Enable/disable foreign key constraint creation on this relation */
27
+ createForeignKeyConstraint?: boolean;
26
28
  }
@@ -2,6 +2,7 @@ import type { TransactionOptions } from '../enums.js';
2
2
  import type { ContextProvider } from '../typings.js';
3
3
  type TransactionalOptions<T> = TransactionOptions & {
4
4
  context?: ContextProvider<T>;
5
+ contextName?: string;
5
6
  };
6
7
  /**
7
8
  * This decorator wraps the method with `em.transactional()`, so you can provide `TransactionOptions` just like with `em.transactional()`.
@@ -14,10 +14,10 @@ export function Transactional(options = {}) {
14
14
  throw new Error('@Transactional() should be use with async functions');
15
15
  }
16
16
  descriptor.value = async function (...args) {
17
- const { context, ...txOptions } = options;
17
+ const { context, contextName, ...txOptions } = options;
18
18
  const em = (await resolveContextProvider(this, context))
19
- || TransactionContext.getEntityManager()
20
- || RequestContext.getEntityManager();
19
+ || TransactionContext.getEntityManager(contextName)
20
+ || RequestContext.getEntityManager(contextName);
21
21
  if (!em) {
22
22
  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.`);
23
23
  }
@@ -166,6 +166,7 @@ export interface NativeInsertUpdateOptions<T> {
166
166
  schema?: string;
167
167
  /** `nativeUpdate()` only option */
168
168
  upsert?: boolean;
169
+ loggerContext?: LogContext;
169
170
  }
170
171
  export interface NativeInsertUpdateManyOptions<T> extends NativeInsertUpdateOptions<T> {
171
172
  processCollections?: boolean;
@@ -200,6 +201,8 @@ export interface CountOptions<T extends object, P extends string = never> {
200
201
  hintComments?: string | string[];
201
202
  loggerContext?: LogContext;
202
203
  logging?: LoggingOptions;
204
+ /** @internal used to apply filters to the auto-joined relations */
205
+ em?: EntityManager;
203
206
  }
204
207
  export interface UpdateOptions<T> {
205
208
  filters?: FilterOptions;
@@ -221,6 +224,7 @@ export interface LockOptions extends DriverMethodOptions {
221
224
  export interface DriverMethodOptions {
222
225
  ctx?: Transaction;
223
226
  schema?: string;
227
+ loggerContext?: LogContext;
224
228
  }
225
229
  export interface GetReferenceOptions {
226
230
  wrapped?: boolean;
@@ -6,6 +6,7 @@ export declare class ArrayCollection<T extends object, O extends object> {
6
6
  protected readonly items: Set<T>;
7
7
  protected initialized: boolean;
8
8
  protected dirty: boolean;
9
+ protected partial: boolean;
9
10
  protected snapshot: T[] | undefined;
10
11
  protected _count?: number;
11
12
  private _property?;
@@ -25,7 +26,7 @@ export declare class ArrayCollection<T extends object, O extends object> {
25
26
  /**
26
27
  * @internal
27
28
  */
28
- hydrate(items: T[], forcePropagate?: boolean): void;
29
+ hydrate(items: T[], forcePropagate?: boolean, partial?: boolean): void;
29
30
  /**
30
31
  * Remove specified item(s) from the collection. Note that removing item from collection does not necessarily imply deleting the target entity,
31
32
  * it means we are disconnecting the relation - removing items from collection, not removing entities from database - `Collection.remove()`
@@ -83,6 +84,7 @@ export declare class ArrayCollection<T extends object, O extends object> {
83
84
  count(): number;
84
85
  isInitialized(fully?: boolean): boolean;
85
86
  isDirty(): boolean;
87
+ isPartial(): boolean;
86
88
  isEmpty(): boolean;
87
89
  setDirty(dirty?: boolean): void;
88
90
  get length(): number;
@@ -9,6 +9,7 @@ export class ArrayCollection {
9
9
  items = new Set();
10
10
  initialized = true;
11
11
  dirty = false;
12
+ partial = false; // mark partially loaded collections, propagation is disabled for those
12
13
  snapshot = []; // used to create a diff of the collection at commit time, undefined marks overridden values so we need to wipe when flushing
13
14
  _count;
14
15
  _property;
@@ -107,11 +108,12 @@ export class ArrayCollection {
107
108
  /**
108
109
  * @internal
109
110
  */
110
- hydrate(items, forcePropagate) {
111
+ hydrate(items, forcePropagate, partial) {
111
112
  for (let i = 0; i < this.items.size; i++) {
112
113
  delete this[i];
113
114
  }
114
115
  this.initialized = true;
116
+ this.partial = !!partial;
115
117
  this.items.clear();
116
118
  this._count = 0;
117
119
  this.add(items);
@@ -274,6 +276,9 @@ export class ArrayCollection {
274
276
  isDirty() {
275
277
  return this.dirty;
276
278
  }
279
+ isPartial() {
280
+ return this.partial;
281
+ }
277
282
  isEmpty() {
278
283
  return this.count() === 0;
279
284
  }
@@ -390,7 +395,7 @@ export class ArrayCollection {
390
395
  /** @ignore */
391
396
  [inspect.custom](depth = 2) {
392
397
  const object = { ...this };
393
- const hidden = ['items', 'owner', '_property', '_count', 'snapshot', '_populated', '_snapshot', '_lazyInitialized', '_em', 'readonly'];
398
+ const hidden = ['items', 'owner', '_property', '_count', 'snapshot', '_populated', '_snapshot', '_lazyInitialized', '_em', 'readonly', 'partial'];
394
399
  hidden.forEach(k => delete object[k]);
395
400
  const ret = inspect(object, { depth });
396
401
  const name = `${this.constructor.name}<${this.property?.type ?? 'unknown'}>`;
@@ -221,8 +221,9 @@ export class Collection extends ArrayCollection {
221
221
  const order = [...this.items]; // copy order of references
222
222
  const orderBy = this.createOrderBy(options.orderBy);
223
223
  const customOrder = orderBy.length > 0;
224
- // eslint-disable-next-line dot-notation
225
- const items = await em['colLoader'].load([
224
+ const pivotTable = this.property.kind === ReferenceKind.MANY_TO_MANY && em.getPlatform().usesPivotTable();
225
+ const loader = pivotTable ? 'colLoaderMtoN' : 'colLoader';
226
+ const items = await em[loader].load([
226
227
  this,
227
228
  { ...options, orderBy },
228
229
  ]);
@@ -4,6 +4,11 @@ import type { EntityComparator } from '../utils/EntityComparator.js';
4
4
  export interface FactoryOptions {
5
5
  initialized?: boolean;
6
6
  newEntity?: boolean;
7
+ /**
8
+ * Property `onCreate` hooks are normally executed during `flush` operation.
9
+ * With this option, they will be processed early inside `em.create()` method.
10
+ */
11
+ processOnCreateHooksEarly?: boolean;
7
12
  merge?: boolean;
8
13
  refresh?: boolean;
9
14
  convertCustomTypes?: boolean;
@@ -27,6 +32,7 @@ export declare class EntityFactory {
27
32
  createEmbeddable<T extends object>(entityName: EntityName<T>, data: EntityData<T>, options?: Pick<FactoryOptions, 'newEntity' | 'convertCustomTypes'>): T;
28
33
  getComparator(): EntityComparator;
29
34
  private createEntity;
35
+ private assignDefaultValues;
30
36
  private hydrate;
31
37
  private findEntity;
32
38
  private processDiscriminatorColumn;
@@ -71,7 +71,7 @@ export class EntityFactory {
71
71
  continue;
72
72
  }
73
73
  if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && Utils.isPlainObject(data[prop.name])) {
74
- data[prop.name] = Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta.primaryKeys, true);
74
+ data[prop.name] = Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta, true);
75
75
  }
76
76
  data[prop.name] = prop.customType.convertToDatabaseValue(data[prop.name], this.platform, { key: prop.name, mode: 'hydration' });
77
77
  }
@@ -113,7 +113,7 @@ export class EntityFactory {
113
113
  if (meta.versionProperty && data[meta.versionProperty] && data[meta.versionProperty] !== originalEntityData[meta.versionProperty]) {
114
114
  diff[meta.versionProperty] = data[meta.versionProperty];
115
115
  }
116
- const diff2 = this.comparator.diffEntities(meta.className, existsData, data);
116
+ const diff2 = this.comparator.diffEntities(meta.className, existsData, data, { includeInverseSides: true });
117
117
  // do not override values changed by user
118
118
  Utils.keys(diff).forEach(key => delete diff2[key]);
119
119
  Utils.keys(diff2).filter(key => {
@@ -171,8 +171,8 @@ export class EntityFactory {
171
171
  if (Array.isArray(id)) {
172
172
  id = Utils.getPrimaryKeyCondFromArray(id, meta);
173
173
  }
174
- const pks = Utils.getOrderedPrimaryKeys(id, meta, this.platform, options.convertCustomTypes);
175
- const exists = this.unitOfWork.getById(entityName, pks, schema);
174
+ const pks = Utils.getOrderedPrimaryKeys(id, meta, this.platform);
175
+ const exists = this.unitOfWork.getById(entityName, pks, schema, options.convertCustomTypes);
176
176
  if (exists) {
177
177
  return exists;
178
178
  }
@@ -231,6 +231,13 @@ export class EntityFactory {
231
231
  }
232
232
  return entity;
233
233
  }
234
+ assignDefaultValues(entity, meta) {
235
+ for (const prop of meta.props) {
236
+ if (prop.onCreate) {
237
+ entity[prop.name] ??= prop.onCreate(entity, this.em);
238
+ }
239
+ }
240
+ }
234
241
  hydrate(entity, meta, data, options) {
235
242
  if (options.initialized) {
236
243
  this.hydrator.hydrate(entity, meta, data, this, 'full', options.newEntity, options.convertCustomTypes, options.schema, this.driver.getSchemaName(meta, options));
@@ -242,6 +249,10 @@ export class EntityFactory {
242
249
  helper(entity)?.__loadedProperties.add(key);
243
250
  helper(entity)?.__serializationContext.fields?.add(key);
244
251
  });
252
+ const processOnCreateHooksEarly = options.processOnCreateHooksEarly ?? this.config.get('processOnCreateHooksEarly');
253
+ if (options.newEntity && processOnCreateHooksEarly) {
254
+ this.assignDefaultValues(entity, meta);
255
+ }
245
256
  }
246
257
  findEntity(data, meta, options) {
247
258
  const schema = this.driver.getSchemaName(meta, options);
@@ -251,7 +262,7 @@ export class EntityFactory {
251
262
  if (!Array.isArray(data) && meta.primaryKeys.some(pk => data[pk] == null)) {
252
263
  return undefined;
253
264
  }
254
- const pks = Utils.getOrderedPrimaryKeys(data, meta, this.platform);
265
+ const pks = Utils.getOrderedPrimaryKeys(data, meta, this.platform, options.convertCustomTypes);
255
266
  return this.unitOfWork.getById(meta.className, pks, schema);
256
267
  }
257
268
  processDiscriminatorColumn(meta, data) {
@@ -285,7 +296,7 @@ export class EntityFactory {
285
296
  return meta.constructorParams.map(k => {
286
297
  if (meta.properties[k] && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(meta.properties[k].kind) && data[k]) {
287
298
  const pk = Reference.unwrapReference(data[k]);
288
- const entity = this.unitOfWork.getById(meta.properties[k].type, pk, options.schema);
299
+ const entity = this.unitOfWork.getById(meta.properties[k].type, pk, options.schema, true);
289
300
  if (entity) {
290
301
  return entity;
291
302
  }
@@ -152,7 +152,7 @@ export class EntityHelper {
152
152
  wrapped.__data[prop.name] = Reference.wrapReference(val, prop);
153
153
  // when propagation from inside hydration, we set the FK to the entity data immediately
154
154
  if (val && hydrator.isRunning() && wrapped.__originalEntityData && prop.owner) {
155
- wrapped.__originalEntityData[prop.name] = Utils.getPrimaryKeyValues(wrapped.__data[prop.name], prop.targetMeta.primaryKeys, true);
155
+ wrapped.__originalEntityData[prop.name] = Utils.getPrimaryKeyValues(wrapped.__data[prop.name], prop.targetMeta, true);
156
156
  }
157
157
  else {
158
158
  wrapped.__touched = !hydrator.isRunning();
@@ -172,6 +172,9 @@ export class EntityHelper {
172
172
  continue;
173
173
  }
174
174
  const inverse = value?.[prop2.name];
175
+ if (Utils.isCollection(inverse) && inverse.isPartial()) {
176
+ continue;
177
+ }
175
178
  if (prop.kind === ReferenceKind.MANY_TO_ONE && Utils.isCollection(inverse) && inverse.isInitialized()) {
176
179
  inverse.addWithoutPropagation(owner);
177
180
  helper(owner).__em?.getUnitOfWork().cancelOrphanRemoval(owner);
@@ -210,6 +213,7 @@ export class EntityHelper {
210
213
  }
211
214
  if (old?.[prop2.name] != null) {
212
215
  delete helper(old).__data[prop2.name];
216
+ old[prop2.name] = null;
213
217
  }
214
218
  }
215
219
  static ensurePropagation(entity) {