@mikro-orm/core 6.4.17-dev.7 → 6.4.17-dev.71

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 (53) hide show
  1. package/EntityManager.d.ts +11 -2
  2. package/EntityManager.js +26 -20
  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 +14 -0
  7. package/decorators/Indexed.d.ts +2 -2
  8. package/decorators/Transactional.d.ts +1 -0
  9. package/decorators/Transactional.js +3 -3
  10. package/drivers/IDatabaseDriver.d.ts +4 -0
  11. package/entity/ArrayCollection.d.ts +3 -1
  12. package/entity/ArrayCollection.js +7 -2
  13. package/entity/EntityFactory.d.ts +6 -0
  14. package/entity/EntityFactory.js +12 -1
  15. package/entity/EntityHelper.js +4 -1
  16. package/entity/EntityLoader.js +25 -17
  17. package/entity/Reference.d.ts +5 -0
  18. package/entity/Reference.js +16 -0
  19. package/entity/WrappedEntity.js +1 -1
  20. package/entity/defineEntity.d.ts +528 -0
  21. package/entity/defineEntity.js +684 -0
  22. package/entity/index.d.ts +2 -0
  23. package/entity/index.js +2 -0
  24. package/entity/utils.d.ts +7 -0
  25. package/entity/utils.js +16 -2
  26. package/enums.d.ts +3 -1
  27. package/enums.js +2 -0
  28. package/hydration/ObjectHydrator.js +1 -1
  29. package/index.d.ts +1 -1
  30. package/index.mjs +4 -0
  31. package/metadata/MetadataDiscovery.d.ts +0 -1
  32. package/metadata/MetadataDiscovery.js +7 -12
  33. package/package.json +3 -3
  34. package/platforms/Platform.d.ts +3 -1
  35. package/types/BooleanType.d.ts +1 -1
  36. package/typings.d.ts +17 -8
  37. package/typings.js +10 -2
  38. package/unit-of-work/ChangeSetComputer.js +3 -1
  39. package/unit-of-work/ChangeSetPersister.d.ts +4 -2
  40. package/unit-of-work/ChangeSetPersister.js +13 -9
  41. package/unit-of-work/UnitOfWork.d.ts +1 -0
  42. package/unit-of-work/UnitOfWork.js +25 -11
  43. package/utils/Configuration.d.ts +7 -1
  44. package/utils/Configuration.js +1 -0
  45. package/utils/ConfigurationLoader.js +2 -2
  46. package/utils/Cursor.js +3 -0
  47. package/utils/EntityComparator.js +6 -3
  48. package/utils/QueryHelper.js +3 -3
  49. package/utils/RawQueryFragment.d.ts +34 -0
  50. package/utils/RawQueryFragment.js +35 -0
  51. package/utils/Utils.d.ts +2 -2
  52. package/utils/Utils.js +32 -8
  53. package/utils/upsert-utils.js +9 -1
@@ -112,7 +112,9 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
112
112
  /**
113
113
  * Gets logger context for this entity manager.
114
114
  */
115
- getLoggerContext<T extends Dictionary = Dictionary>(): T;
115
+ getLoggerContext<T extends Dictionary = Dictionary>(options?: {
116
+ disableContextResolution?: boolean;
117
+ }): T;
116
118
  setFlushMode(flushMode?: FlushMode): void;
117
119
  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>>;
118
120
  protected applyDiscriminatorCondition<Entity extends object>(entityName: string, where: FilterQuery<Entity>): FilterQuery<Entity>;
@@ -482,7 +484,7 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
482
484
  * some additional lazy properties, if so, we reload and merge the data from database
483
485
  */
484
486
  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;
485
- protected prepareOptions(options: FindOptions<any, any, any, any> | FindOneOptions<any, any, any, any>): void;
487
+ protected prepareOptions(options: FindOptions<any, any, any, any> | FindOneOptions<any, any, any, any> | CountOptions<any, any>): void;
486
488
  /**
487
489
  * @internal
488
490
  */
@@ -542,11 +544,18 @@ export interface CreateOptions<Convert extends boolean> {
542
544
  partial?: boolean;
543
545
  /** convert raw database values based on mapped types (by default, already converted values are expected) */
544
546
  convertCustomTypes?: Convert;
547
+ /**
548
+ * Property `onCreate` hooks are normally executed during `flush` operation.
549
+ * With this option, they will be processed early inside `em.create()` method.
550
+ */
551
+ processOnCreateHooksEarly?: boolean;
545
552
  }
546
553
  export interface MergeOptions {
547
554
  refresh?: boolean;
548
555
  convertCustomTypes?: boolean;
549
556
  schema?: string;
557
+ disableContextResolution?: boolean;
558
+ keepIdentity?: boolean;
550
559
  }
551
560
  export interface ForkOptions {
552
561
  /** do we want a clear identity map? defaults to true */
package/EntityManager.js CHANGED
@@ -12,6 +12,7 @@ const unit_of_work_1 = require("./unit-of-work");
12
12
  const enums_1 = require("./enums");
13
13
  const events_1 = require("./events");
14
14
  const errors_1 = require("./errors");
15
+ const utils_2 = require("./entity/utils");
15
16
  /**
16
17
  * The EntityManager is the central access point to ORM functionality. It is a facade to all different ORM subsystems
17
18
  * such as UnitOfWork, Query Language, and Repository API.
@@ -131,7 +132,6 @@ class EntityManager {
131
132
  await em.entityLoader.populate(entityName, cached.data, populate, {
132
133
  ...options,
133
134
  ...em.getPopulateWhere(where, options),
134
- convertCustomTypes: false,
135
135
  ignoreLazyScalarProperties: true,
136
136
  lookup: false,
137
137
  });
@@ -162,7 +162,6 @@ class EntityManager {
162
162
  await em.entityLoader.populate(entityName, unique, populate, {
163
163
  ...options,
164
164
  ...em.getPopulateWhere(where, options),
165
- convertCustomTypes: false,
166
165
  ignoreLazyScalarProperties: true,
167
166
  lookup: false,
168
167
  });
@@ -226,8 +225,8 @@ class EntityManager {
226
225
  /**
227
226
  * Gets logger context for this entity manager.
228
227
  */
229
- getLoggerContext() {
230
- const em = this.getContext();
228
+ getLoggerContext(options) {
229
+ const em = options?.disableContextResolution ? this : this.getContext();
231
230
  em.loggerContext ??= {};
232
231
  return em.loggerContext;
233
232
  }
@@ -283,7 +282,8 @@ class EntityManager {
283
282
  for (const hint of options.populate) {
284
283
  const field = hint.field.split(':')[0];
285
284
  const prop = meta.properties[field];
286
- const joined = (prop.strategy || options.strategy || hint.strategy || this.config.get('loadStrategy')) === enums_1.LoadStrategy.JOINED && prop.kind !== enums_1.ReferenceKind.SCALAR;
285
+ const strategy = (0, utils_2.getLoadingStrategy)(prop.strategy || options.strategy || hint.strategy || this.config.get('loadStrategy'), prop.kind);
286
+ const joined = strategy === enums_1.LoadStrategy.JOINED && prop.kind !== enums_1.ReferenceKind.SCALAR;
287
287
  if (!joined && !hint.filter) {
288
288
  continue;
289
289
  }
@@ -488,11 +488,17 @@ class EntityManager {
488
488
  ...options,
489
489
  flushMode: enums_1.FlushMode.COMMIT,
490
490
  });
491
+ const em = this.getContext();
491
492
  if (reloaded) {
492
- this.config.getHydrator(this.metadata).hydrate(entity, (0, entity_1.helper)(entity).__meta, (0, entity_1.helper)(reloaded).toPOJO(), this.getEntityFactory(), 'full', false, true);
493
+ for (const e of fork.unitOfWork.getIdentityMap()) {
494
+ const ref = em.getReference(e.constructor.name, (0, entity_1.helper)(e).getPrimaryKey());
495
+ const data = this.comparator.prepareEntity(e);
496
+ em.config.getHydrator(this.metadata).hydrate(ref, (0, entity_1.helper)(ref).__meta, (0, entity_1.helper)(e).serialize({ ignoreSerializers: true }), em.entityFactory, 'full', false, true);
497
+ (0, entity_1.helper)(ref).__originalEntityData = data;
498
+ }
493
499
  }
494
500
  else {
495
- this.getUnitOfWork().unsetIdentity(entity);
501
+ em.unitOfWork.unsetIdentity(entity);
496
502
  }
497
503
  return reloaded ? entity : reloaded;
498
504
  }
@@ -535,7 +541,6 @@ class EntityManager {
535
541
  await em.entityLoader.populate(entityName, [cached.data], options.populate, {
536
542
  ...options,
537
543
  ...em.getPopulateWhere(where, options),
538
- convertCustomTypes: false,
539
544
  ignoreLazyScalarProperties: true,
540
545
  lookup: false,
541
546
  });
@@ -982,8 +987,7 @@ class EntityManager {
982
987
  if (propagateToUpperContext) {
983
988
  // ensure all entities from inner context are merged to the upper one
984
989
  for (const entity of fork.unitOfWork.getIdentityMap()) {
985
- em.unitOfWork.register(entity);
986
- entity.__helper.__em = em;
990
+ em.merge(entity, { disableContextResolution: true, keepIdentity: true, refresh: true });
987
991
  }
988
992
  }
989
993
  return ret;
@@ -1168,10 +1172,10 @@ class EntityManager {
1168
1172
  * via second parameter. By default, it will return already loaded entities without modifying them.
1169
1173
  */
1170
1174
  merge(entityName, data, options = {}) {
1171
- const em = this.getContext();
1172
1175
  if (utils_1.Utils.isEntity(entityName)) {
1173
- return em.merge(entityName.constructor.name, entityName, data);
1176
+ return this.merge(entityName.constructor.name, entityName, data);
1174
1177
  }
1178
+ const em = options.disableContextResolution ? this : this.getContext();
1175
1179
  options.schema ??= em._schema;
1176
1180
  entityName = utils_1.Utils.className(entityName);
1177
1181
  em.validator.validatePrimaryKey(data, em.metadata.get(entityName));
@@ -1181,7 +1185,12 @@ class EntityManager {
1181
1185
  }
1182
1186
  const meta = em.metadata.find(entityName);
1183
1187
  const childMeta = em.metadata.getByDiscriminatorColumn(meta, data);
1184
- entity = utils_1.Utils.isEntity(data) ? data : em.entityFactory.create(entityName, data, { merge: true, ...options });
1188
+ const dataIsEntity = utils_1.Utils.isEntity(data);
1189
+ if (options.keepIdentity && entity && dataIsEntity && entity !== data) {
1190
+ em.entityFactory.mergeData(meta, entity, (0, entity_1.helper)(data).__originalEntityData, { initialized: true, merge: true, ...options });
1191
+ return entity;
1192
+ }
1193
+ entity = dataIsEntity ? data : em.entityFactory.create(entityName, data, { merge: true, ...options });
1185
1194
  em.validator.validate(entity, data, childMeta ?? meta);
1186
1195
  em.unitOfWork.merge(entity);
1187
1196
  return entity;
@@ -1246,10 +1255,8 @@ class EntityManager {
1246
1255
  async count(entityName, where = {}, options = {}) {
1247
1256
  const em = this.getContext(false);
1248
1257
  // Shallow copy options since the object will be modified when deleting orderBy
1249
- options = {
1250
- schema: em._schema,
1251
- ...options,
1252
- };
1258
+ options = { ...options };
1259
+ em.prepareOptions(options);
1253
1260
  entityName = utils_1.Utils.className(entityName);
1254
1261
  await em.tryFlush(entityName, options);
1255
1262
  where = await em.processWhere(entityName, where, options, 'read');
@@ -1267,7 +1274,7 @@ class EntityManager {
1267
1274
  if (cached?.data !== undefined) {
1268
1275
  return cached.data;
1269
1276
  }
1270
- const count = await em.driver.count(entityName, where, { ctx: em.transactionContext, ...options });
1277
+ const count = await em.driver.count(entityName, where, { ctx: em.transactionContext, em, ...options });
1271
1278
  await em.storeCache(options.cache, cached, () => +count);
1272
1279
  return +count;
1273
1280
  }
@@ -1547,7 +1554,6 @@ class EntityManager {
1547
1554
  ...options,
1548
1555
  ...this.getPopulateWhere(where, options),
1549
1556
  orderBy: options.populateOrderBy ?? options.orderBy,
1550
- convertCustomTypes: false,
1551
1557
  ignoreLazyScalarProperties: true,
1552
1558
  lookup: false,
1553
1559
  });
@@ -1670,7 +1676,7 @@ class EntityManager {
1670
1676
  throw new errors_1.ValidationError(`Cannot combine 'fields' and 'exclude' option.`);
1671
1677
  }
1672
1678
  options.schema ??= this._schema;
1673
- options.logging = utils_1.Utils.merge({ id: this.id }, this.loggerContext, options.loggerContext, options.logging);
1679
+ options.logging = options.loggerContext = utils_1.Utils.merge({ id: this.id }, this.loggerContext, options.loggerContext, options.logging);
1674
1680
  }
1675
1681
  /**
1676
1682
  * @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)
@@ -49,15 +49,17 @@ export declare abstract class Connection {
49
49
  readOnly?: boolean;
50
50
  ctx?: Transaction;
51
51
  eventBroadcaster?: TransactionEventBroadcaster;
52
+ loggerContext?: LogContext;
52
53
  }): Promise<T>;
53
54
  begin(options?: {
54
55
  isolationLevel?: IsolationLevel;
55
56
  readOnly?: boolean;
56
57
  ctx?: Transaction;
57
58
  eventBroadcaster?: TransactionEventBroadcaster;
59
+ loggerContext?: LogContext;
58
60
  }): Promise<Transaction>;
59
- commit(ctx: Transaction, eventBroadcaster?: TransactionEventBroadcaster): Promise<void>;
60
- rollback(ctx: Transaction, eventBroadcaster?: TransactionEventBroadcaster): Promise<void>;
61
+ commit(ctx: Transaction, eventBroadcaster?: TransactionEventBroadcaster, loggerContext?: LogContext): Promise<void>;
62
+ rollback(ctx: Transaction, eventBroadcaster?: TransactionEventBroadcaster, loggerContext?: LogContext): Promise<void>;
61
63
  abstract execute<T>(query: string, params?: any[], method?: 'all' | 'get' | 'run', ctx?: Transaction): Promise<QueryResult<T> | any | any[]>;
62
64
  getConnectionOptions(): ConnectionConfig;
63
65
  getClientUrl(): string;
@@ -49,10 +49,10 @@ class Connection {
49
49
  async begin(options) {
50
50
  throw new Error(`Transactions are not supported by current driver`);
51
51
  }
52
- async commit(ctx, eventBroadcaster) {
52
+ async commit(ctx, eventBroadcaster, loggerContext) {
53
53
  throw new Error(`Transactions are not supported by current driver`);
54
54
  }
55
- async rollback(ctx, eventBroadcaster) {
55
+ async rollback(ctx, eventBroadcaster, loggerContext) {
56
56
  throw new Error(`Transactions are not supported by current driver`);
57
57
  }
58
58
  getConnectionOptions() {
@@ -2,18 +2,32 @@ import type { AnyString, Constructor, Dictionary, EntityClass, ObjectQuery } fro
2
2
  import type { FindOptions } from '../drivers/IDatabaseDriver';
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. */
16
28
  hasTriggers?: boolean;
29
+ /** SQL query that maps to a {@doclink virtual-entities | virtual entity}. */
17
30
  expression?: string | ((em: any, where: ObjectQuery<E>, options: FindOptions<E, any, any, any>) => object);
31
+ /** Set {@doclink repositories#custom-repository | custom repository class}. */
18
32
  repository?: () => Constructor;
19
33
  };
@@ -1,4 +1,4 @@
1
- import type { EntityClass, Dictionary, AutoPath } from '../typings';
1
+ import type { EntityClass, Dictionary, AutoPath, IndexCallback } from '../typings';
2
2
  import type { DeferMode } from '../enums';
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}`;
@@ -2,6 +2,7 @@ import type { TransactionOptions } from '../enums';
2
2
  import type { ContextProvider } from '../typings';
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()`.
@@ -17,10 +17,10 @@ function Transactional(options = {}) {
17
17
  throw new Error('@Transactional() should be use with async functions');
18
18
  }
19
19
  descriptor.value = async function (...args) {
20
- const { context, ...txOptions } = options;
20
+ const { context, contextName, ...txOptions } = options;
21
21
  const em = await (0, resolveContextProvider_1.resolveContextProvider)(this, context)
22
- || TransactionContext_1.TransactionContext.getEntityManager()
23
- || RequestContext_1.RequestContext.getEntityManager();
22
+ || TransactionContext_1.TransactionContext.getEntityManager(contextName)
23
+ || RequestContext_1.RequestContext.getEntityManager(contextName);
24
24
  if (!em) {
25
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
26
  }
@@ -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;
@@ -12,6 +12,7 @@ class ArrayCollection {
12
12
  items = new Set();
13
13
  initialized = true;
14
14
  dirty = false;
15
+ partial = false; // mark partially loaded collections, propagation is disabled for those
15
16
  snapshot = []; // used to create a diff of the collection at commit time, undefined marks overridden values so we need to wipe when flushing
16
17
  _count;
17
18
  _property;
@@ -110,11 +111,12 @@ class ArrayCollection {
110
111
  /**
111
112
  * @internal
112
113
  */
113
- hydrate(items, forcePropagate) {
114
+ hydrate(items, forcePropagate, partial) {
114
115
  for (let i = 0; i < this.items.size; i++) {
115
116
  delete this[i];
116
117
  }
117
118
  this.initialized = true;
119
+ this.partial = !!partial;
118
120
  this.items.clear();
119
121
  this._count = 0;
120
122
  this.add(items);
@@ -277,6 +279,9 @@ class ArrayCollection {
277
279
  isDirty() {
278
280
  return this.dirty;
279
281
  }
282
+ isPartial() {
283
+ return this.partial;
284
+ }
280
285
  isEmpty() {
281
286
  return this.count() === 0;
282
287
  }
@@ -393,7 +398,7 @@ class ArrayCollection {
393
398
  /** @ignore */
394
399
  [node_util_1.inspect.custom](depth = 2) {
395
400
  const object = { ...this };
396
- const hidden = ['items', 'owner', '_property', '_count', 'snapshot', '_populated', '_snapshot', '_lazyInitialized', '_em', 'readonly'];
401
+ const hidden = ['items', 'owner', '_property', '_count', 'snapshot', '_populated', '_snapshot', '_lazyInitialized', '_em', 'readonly', 'partial'];
397
402
  hidden.forEach(k => delete object[k]);
398
403
  const ret = (0, node_util_1.inspect)(object, { depth });
399
404
  const name = `${this.constructor.name}<${this.property?.type ?? 'unknown'}>`;
@@ -4,6 +4,11 @@ import type { EntityComparator } from '../utils/EntityComparator';
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;
@@ -74,7 +74,7 @@ class EntityFactory {
74
74
  continue;
75
75
  }
76
76
  if ([enums_1.ReferenceKind.MANY_TO_ONE, enums_1.ReferenceKind.ONE_TO_ONE].includes(prop.kind) && Utils_1.Utils.isPlainObject(data[prop.name])) {
77
- data[prop.name] = Utils_1.Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta.primaryKeys, true);
77
+ data[prop.name] = Utils_1.Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta, true);
78
78
  }
79
79
  data[prop.name] = prop.customType.convertToDatabaseValue(data[prop.name], this.platform, { key: prop.name, mode: 'hydration' });
80
80
  }
@@ -234,6 +234,13 @@ class EntityFactory {
234
234
  }
235
235
  return entity;
236
236
  }
237
+ assignDefaultValues(entity, meta) {
238
+ for (const prop of meta.props) {
239
+ if (prop.onCreate) {
240
+ entity[prop.name] ??= prop.onCreate(entity, this.em);
241
+ }
242
+ }
243
+ }
237
244
  hydrate(entity, meta, data, options) {
238
245
  if (options.initialized) {
239
246
  this.hydrator.hydrate(entity, meta, data, this, 'full', options.newEntity, options.convertCustomTypes, options.schema, this.driver.getSchemaName(meta, options));
@@ -245,6 +252,10 @@ class EntityFactory {
245
252
  (0, wrap_1.helper)(entity)?.__loadedProperties.add(key);
246
253
  (0, wrap_1.helper)(entity)?.__serializationContext.fields?.add(key);
247
254
  });
255
+ const processOnCreateHooksEarly = options.processOnCreateHooksEarly ?? this.config.get('processOnCreateHooksEarly');
256
+ if (options.newEntity && processOnCreateHooksEarly) {
257
+ this.assignDefaultValues(entity, meta);
258
+ }
248
259
  }
249
260
  findEntity(data, meta, options) {
250
261
  const schema = this.driver.getSchemaName(meta, options);
@@ -155,7 +155,7 @@ class EntityHelper {
155
155
  wrapped.__data[prop.name] = Reference_1.Reference.wrapReference(val, prop);
156
156
  // when propagation from inside hydration, we set the FK to the entity data immediately
157
157
  if (val && hydrator.isRunning() && wrapped.__originalEntityData && prop.owner) {
158
- wrapped.__originalEntityData[prop.name] = Utils_1.Utils.getPrimaryKeyValues(wrapped.__data[prop.name], prop.targetMeta.primaryKeys, true);
158
+ wrapped.__originalEntityData[prop.name] = Utils_1.Utils.getPrimaryKeyValues(wrapped.__data[prop.name], prop.targetMeta, true);
159
159
  }
160
160
  else {
161
161
  wrapped.__touched = !hydrator.isRunning();
@@ -175,6 +175,9 @@ class EntityHelper {
175
175
  continue;
176
176
  }
177
177
  const inverse = value?.[prop2.name];
178
+ if (Utils_1.Utils.isCollection(inverse) && inverse.isPartial()) {
179
+ continue;
180
+ }
178
181
  if (prop.kind === enums_1.ReferenceKind.MANY_TO_ONE && Utils_1.Utils.isCollection(inverse) && inverse.isInitialized()) {
179
182
  inverse.addWithoutPropagation(owner);
180
183
  (0, wrap_1.helper)(owner).__em?.getUnitOfWork().cancelOrphanRemoval(owner);
@@ -143,18 +143,22 @@ class EntityLoader {
143
143
  const innerOrderBy = Utils_1.Utils.asArray(options.orderBy)
144
144
  .filter(orderBy => (Array.isArray(orderBy[prop.name]) && orderBy[prop.name].length > 0) || Utils_1.Utils.isObject(orderBy[prop.name]))
145
145
  .flatMap(orderBy => orderBy[prop.name]);
146
+ const where = await this.extractChildCondition(options, prop);
146
147
  if (prop.kind === enums_1.ReferenceKind.MANY_TO_MANY && this.driver.getPlatform().usesPivotTable()) {
147
148
  const res = await this.findChildrenFromPivotTable(filtered, prop, options, innerOrderBy, populate, !!ref);
148
149
  return Utils_1.Utils.flatten(res);
149
150
  }
150
- const where = await this.extractChildCondition(options, prop);
151
- const data = await this.findChildren(entities, prop, populate, { ...options, where, orderBy: innerOrderBy }, !!(ref || prop.mapToPk));
152
- this.initializeCollections(filtered, prop, field, data, innerOrderBy.length > 0);
153
- return data;
151
+ const { items, partial } = await this.findChildren(options.filtered ?? entities, prop, populate, {
152
+ ...options,
153
+ where,
154
+ orderBy: innerOrderBy,
155
+ }, !!(ref || prop.mapToPk));
156
+ this.initializeCollections(filtered, prop, field, items, innerOrderBy.length > 0, partial);
157
+ return items;
154
158
  }
155
159
  async populateScalar(meta, filtered, options) {
156
160
  const pk = Utils_1.Utils.getPrimaryKeyHash(meta.primaryKeys);
157
- const ids = Utils_1.Utils.unique(filtered.map(e => Utils_1.Utils.getPrimaryKeyValues(e, meta.primaryKeys, true)));
161
+ const ids = Utils_1.Utils.unique(filtered.map(e => Utils_1.Utils.getPrimaryKeyValues(e, meta, true)));
158
162
  const where = this.mergePrimaryCondition(ids, pk, options, meta, this.metadata, this.driver.getPlatform());
159
163
  const { filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging, fields } = options;
160
164
  await this.em.find(meta.className, where, {
@@ -163,15 +167,15 @@ class EntityLoader {
163
167
  populate: [],
164
168
  });
165
169
  }
166
- initializeCollections(filtered, prop, field, children, customOrder) {
170
+ initializeCollections(filtered, prop, field, children, customOrder, partial) {
167
171
  if (prop.kind === enums_1.ReferenceKind.ONE_TO_MANY) {
168
- this.initializeOneToMany(filtered, children, prop, field);
172
+ this.initializeOneToMany(filtered, children, prop, field, partial);
169
173
  }
170
174
  if (prop.kind === enums_1.ReferenceKind.MANY_TO_MANY && !this.driver.getPlatform().usesPivotTable()) {
171
- this.initializeManyToMany(filtered, children, prop, field, customOrder);
175
+ this.initializeManyToMany(filtered, children, prop, field, customOrder, partial);
172
176
  }
173
177
  }
174
- initializeOneToMany(filtered, children, prop, field) {
178
+ initializeOneToMany(filtered, children, prop, field, partial) {
175
179
  const mapToPk = prop.targetMeta.properties[prop.mappedBy].mapToPk;
176
180
  const map = {};
177
181
  for (const entity of filtered) {
@@ -187,14 +191,14 @@ class EntityLoader {
187
191
  }
188
192
  for (const entity of filtered) {
189
193
  const key = (0, wrap_1.helper)(entity).getSerializedPrimaryKey();
190
- entity[field].hydrate(map[key]);
194
+ entity[field].hydrate(map[key], undefined, partial);
191
195
  }
192
196
  }
193
- initializeManyToMany(filtered, children, prop, field, customOrder) {
197
+ initializeManyToMany(filtered, children, prop, field, customOrder, partial) {
194
198
  if (prop.mappedBy) {
195
199
  for (const entity of filtered) {
196
200
  const items = children.filter(child => child[prop.mappedBy].contains(entity, false));
197
- entity[field].hydrate(items, true);
201
+ entity[field].hydrate(items, true, partial);
198
202
  }
199
203
  }
200
204
  else { // owning side of M:N without pivot table needs to be reordered
@@ -204,7 +208,7 @@ class EntityLoader {
204
208
  if (!customOrder) {
205
209
  items.sort((a, b) => order.indexOf(a) - order.indexOf(b));
206
210
  }
207
- entity[field].hydrate(items, true);
211
+ entity[field].hydrate(items, true, partial);
208
212
  }
209
213
  }
210
214
  }
@@ -213,6 +217,7 @@ class EntityLoader {
213
217
  const meta = prop.targetMeta;
214
218
  let fk = Utils_1.Utils.getPrimaryKeyHash(meta.primaryKeys);
215
219
  let schema = options.schema;
220
+ const partial = !Utils_1.Utils.isEmpty(prop.where) || !Utils_1.Utils.isEmpty(options.where);
216
221
  if (prop.kind === enums_1.ReferenceKind.ONE_TO_MANY || (prop.kind === enums_1.ReferenceKind.MANY_TO_MANY && !prop.owner)) {
217
222
  fk = meta.properties[prop.mappedBy].name;
218
223
  }
@@ -222,7 +227,7 @@ class EntityLoader {
222
227
  children.push(...this.filterByReferences(entities, prop.name, options.refresh));
223
228
  }
224
229
  if (children.length === 0) {
225
- return [];
230
+ return { items: [], partial };
226
231
  }
227
232
  if (!schema && [enums_1.ReferenceKind.ONE_TO_ONE, enums_1.ReferenceKind.MANY_TO_ONE].includes(prop.kind)) {
228
233
  schema = children.find(e => e.__helper.__schema)?.__helper.__schema;
@@ -271,7 +276,7 @@ class EntityLoader {
271
276
  this.em.getUnitOfWork()['loadedEntities'].delete(item);
272
277
  }
273
278
  }
274
- return items;
279
+ return { items, partial };
275
280
  }
276
281
  mergePrimaryCondition(ids, pk, options, meta, metadata, platform) {
277
282
  const cond1 = QueryHelper_1.QueryHelper.processWhere({ where: { [pk]: { $in: ids } }, entityName: meta.className, metadata, platform, convertCustomTypes: !options.convertCustomTypes });
@@ -320,11 +325,12 @@ class EntityLoader {
320
325
  for (const entity of entities) {
321
326
  visited.delete(entity);
322
327
  }
323
- const filtered = Utils_1.Utils.unique(children.filter(e => !visited.has(e)));
328
+ const unique = Utils_1.Utils.unique(children);
329
+ const filtered = unique.filter(e => !visited.has(e));
324
330
  for (const entity of entities) {
325
331
  visited.add(entity);
326
332
  }
327
- await this.populate(prop.type, filtered, populate.children ?? populate.all, {
333
+ await this.populate(prop.type, unique, populate.children ?? populate.all, {
328
334
  where: await this.extractChildCondition(options, prop, false),
329
335
  orderBy: innerOrderBy,
330
336
  fields,
@@ -341,6 +347,8 @@ class EntityLoader {
341
347
  refresh: refresh && !filtered.every(item => options.visited.has(item)),
342
348
  // @ts-ignore not a public option, will be propagated to the populate call
343
349
  visited: options.visited,
350
+ // @ts-ignore not a public option
351
+ filtered,
344
352
  });
345
353
  }
346
354
  /** @internal */
@@ -56,6 +56,11 @@ export declare class ScalarReference<Value> {
56
56
  * Returns either the whole entity, or the requested property.
57
57
  */
58
58
  load(options?: Omit<LoadReferenceOptions<any, any>, 'populate' | 'fields' | 'exclude'>): Promise<Value | undefined>;
59
+ /**
60
+ * Ensures the underlying entity is loaded first (without reloading it if it already is loaded).
61
+ * Returns the entity or throws an error just like `em.findOneOrFail()` (and respects the same config options).
62
+ */
63
+ loadOrFail(options?: Omit<LoadReferenceOrFailOptions<any, any>, 'populate' | 'fields' | 'exclude'>): Promise<Value>;
59
64
  set(value: Value): void;
60
65
  bind<Entity extends object>(entity: Entity, property: EntityKey<Entity>): void;
61
66
  unwrap(): Value | undefined;
@@ -7,6 +7,7 @@ const node_util_1 = require("node:util");
7
7
  const enums_1 = require("../enums");
8
8
  const wrap_1 = require("./wrap");
9
9
  const utils_1 = require("../utils");
10
+ const errors_1 = require("../errors");
10
11
  class Reference {
11
12
  entity;
12
13
  constructor(entity) {
@@ -178,6 +179,21 @@ class ScalarReference {
178
179
  }
179
180
  return this.value;
180
181
  }
182
+ /**
183
+ * Ensures the underlying entity is loaded first (without reloading it if it already is loaded).
184
+ * Returns the entity or throws an error just like `em.findOneOrFail()` (and respects the same config options).
185
+ */
186
+ async loadOrFail(options = {}) {
187
+ const ret = await this.load(options);
188
+ if (!ret) {
189
+ const wrapped = (0, wrap_1.helper)(this.entity);
190
+ options.failHandler ??= wrapped.__em.config.get('findOneOrFailHandler');
191
+ const entityName = this.entity.constructor.name;
192
+ const where = wrapped.getPrimaryKey();
193
+ throw new errors_1.NotFoundError(`${entityName} (${where}) failed to load property '${this.property}'`);
194
+ }
195
+ return ret;
196
+ }
181
197
  set(value) {
182
198
  this.value = value;
183
199
  this.initialized = true;