@mikro-orm/core 6.4.17-dev.5 → 6.4.17-dev.50

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 (45) hide show
  1. package/EntityManager.d.ts +7 -0
  2. package/EntityManager.js +18 -8
  3. package/decorators/Indexed.d.ts +2 -2
  4. package/decorators/ManyToMany.d.ts +2 -0
  5. package/decorators/ManyToOne.d.ts +2 -0
  6. package/decorators/OneToOne.d.ts +2 -0
  7. package/decorators/Transactional.d.ts +1 -0
  8. package/decorators/Transactional.js +3 -3
  9. package/drivers/IDatabaseDriver.d.ts +2 -0
  10. package/entity/ArrayCollection.d.ts +3 -1
  11. package/entity/ArrayCollection.js +7 -2
  12. package/entity/EntityFactory.d.ts +6 -0
  13. package/entity/EntityFactory.js +12 -1
  14. package/entity/EntityHelper.js +4 -1
  15. package/entity/EntityLoader.js +16 -14
  16. package/entity/Reference.d.ts +5 -0
  17. package/entity/Reference.js +16 -0
  18. package/entity/WrappedEntity.js +1 -1
  19. package/entity/defineEntity.d.ts +528 -0
  20. package/entity/defineEntity.js +684 -0
  21. package/entity/index.d.ts +1 -0
  22. package/entity/index.js +1 -0
  23. package/hydration/ObjectHydrator.js +1 -1
  24. package/index.d.ts +1 -1
  25. package/index.mjs +2 -0
  26. package/metadata/EntitySchema.js +10 -2
  27. package/metadata/MetadataDiscovery.d.ts +0 -1
  28. package/metadata/MetadataDiscovery.js +7 -11
  29. package/package.json +3 -3
  30. package/platforms/Platform.d.ts +3 -1
  31. package/typings.d.ts +16 -8
  32. package/typings.js +10 -2
  33. package/unit-of-work/ChangeSetComputer.js +3 -1
  34. package/unit-of-work/UnitOfWork.d.ts +1 -0
  35. package/unit-of-work/UnitOfWork.js +17 -10
  36. package/utils/Configuration.d.ts +6 -0
  37. package/utils/Configuration.js +1 -0
  38. package/utils/ConfigurationLoader.js +1 -1
  39. package/utils/EntityComparator.js +6 -3
  40. package/utils/QueryHelper.js +3 -3
  41. package/utils/RawQueryFragment.d.ts +34 -0
  42. package/utils/RawQueryFragment.js +35 -0
  43. package/utils/Utils.d.ts +2 -2
  44. package/utils/Utils.js +24 -5
  45. package/utils/upsert-utils.js +9 -1
@@ -542,11 +542,18 @@ export interface CreateOptions<Convert extends boolean> {
542
542
  partial?: boolean;
543
543
  /** convert raw database values based on mapped types (by default, already converted values are expected) */
544
544
  convertCustomTypes?: Convert;
545
+ /**
546
+ * Property `onCreate` hooks are normally executed during `flush` operation.
547
+ * With this option, they will be processed early inside `em.create()` method.
548
+ */
549
+ processOnCreateHooksEarly?: boolean;
545
550
  }
546
551
  export interface MergeOptions {
547
552
  refresh?: boolean;
548
553
  convertCustomTypes?: boolean;
549
554
  schema?: string;
555
+ disableContextResolution?: boolean;
556
+ keepIdentity?: boolean;
550
557
  }
551
558
  export interface ForkOptions {
552
559
  /** do we want a clear identity map? defaults to true */
package/EntityManager.js CHANGED
@@ -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).toPOJO(), 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
  }
@@ -982,8 +988,7 @@ class EntityManager {
982
988
  if (propagateToUpperContext) {
983
989
  // ensure all entities from inner context are merged to the upper one
984
990
  for (const entity of fork.unitOfWork.getIdentityMap()) {
985
- em.unitOfWork.register(entity);
986
- entity.__helper.__em = em;
991
+ em.merge(entity, { disableContextResolution: true, keepIdentity: true, refresh: true });
987
992
  }
988
993
  }
989
994
  return ret;
@@ -1168,10 +1173,10 @@ class EntityManager {
1168
1173
  * via second parameter. By default, it will return already loaded entities without modifying them.
1169
1174
  */
1170
1175
  merge(entityName, data, options = {}) {
1171
- const em = this.getContext();
1172
1176
  if (utils_1.Utils.isEntity(entityName)) {
1173
- return em.merge(entityName.constructor.name, entityName, data);
1177
+ return this.merge(entityName.constructor.name, entityName, data);
1174
1178
  }
1179
+ const em = options.disableContextResolution ? this : this.getContext();
1175
1180
  options.schema ??= em._schema;
1176
1181
  entityName = utils_1.Utils.className(entityName);
1177
1182
  em.validator.validatePrimaryKey(data, em.metadata.get(entityName));
@@ -1181,7 +1186,12 @@ class EntityManager {
1181
1186
  }
1182
1187
  const meta = em.metadata.find(entityName);
1183
1188
  const childMeta = em.metadata.getByDiscriminatorColumn(meta, data);
1184
- entity = utils_1.Utils.isEntity(data) ? data : em.entityFactory.create(entityName, data, { merge: true, ...options });
1189
+ const dataIsEntity = utils_1.Utils.isEntity(data);
1190
+ if (options.keepIdentity && entity && dataIsEntity && entity !== data) {
1191
+ em.entityFactory.mergeData(meta, entity, (0, entity_1.helper)(data).__originalEntityData, { initialized: true, merge: true, ...options });
1192
+ return entity;
1193
+ }
1194
+ entity = dataIsEntity ? data : em.entityFactory.create(entityName, data, { merge: true, ...options });
1185
1195
  em.validator.validate(entity, data, childMeta ?? meta);
1186
1196
  em.unitOfWork.merge(entity);
1187
1197
  return entity;
@@ -1267,7 +1277,7 @@ class EntityManager {
1267
1277
  if (cached?.data !== undefined) {
1268
1278
  return cached.data;
1269
1279
  }
1270
- const count = await em.driver.count(entityName, where, { ctx: em.transactionContext, ...options });
1280
+ const count = await em.driver.count(entityName, where, { ctx: em.transactionContext, em, ...options });
1271
1281
  await em.storeCache(options.cache, cached, () => +count);
1272
1282
  return +count;
1273
1283
  }
@@ -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}`;
@@ -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';
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
  }
@@ -200,6 +200,8 @@ export interface CountOptions<T extends object, P extends string = never> {
200
200
  hintComments?: string | string[];
201
201
  loggerContext?: LogContext;
202
202
  logging?: LoggingOptions;
203
+ /** @internal used to apply filters to the auto-joined relations */
204
+ em?: EntityManager;
203
205
  }
204
206
  export interface UpdateOptions<T> {
205
207
  filters?: FilterOptions;
@@ -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);
@@ -148,13 +148,13 @@ class EntityLoader {
148
148
  return Utils_1.Utils.flatten(res);
149
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(entities, prop, populate, { ...options, where, orderBy: innerOrderBy }, !!(ref || prop.mapToPk));
152
+ this.initializeCollections(filtered, prop, field, items, innerOrderBy.length > 0, partial);
153
+ return items;
154
154
  }
155
155
  async populateScalar(meta, filtered, options) {
156
156
  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)));
157
+ const ids = Utils_1.Utils.unique(filtered.map(e => Utils_1.Utils.getPrimaryKeyValues(e, meta, true)));
158
158
  const where = this.mergePrimaryCondition(ids, pk, options, meta, this.metadata, this.driver.getPlatform());
159
159
  const { filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging, fields } = options;
160
160
  await this.em.find(meta.className, where, {
@@ -163,15 +163,15 @@ class EntityLoader {
163
163
  populate: [],
164
164
  });
165
165
  }
166
- initializeCollections(filtered, prop, field, children, customOrder) {
166
+ initializeCollections(filtered, prop, field, children, customOrder, partial) {
167
167
  if (prop.kind === enums_1.ReferenceKind.ONE_TO_MANY) {
168
- this.initializeOneToMany(filtered, children, prop, field);
168
+ this.initializeOneToMany(filtered, children, prop, field, partial);
169
169
  }
170
170
  if (prop.kind === enums_1.ReferenceKind.MANY_TO_MANY && !this.driver.getPlatform().usesPivotTable()) {
171
- this.initializeManyToMany(filtered, children, prop, field, customOrder);
171
+ this.initializeManyToMany(filtered, children, prop, field, customOrder, partial);
172
172
  }
173
173
  }
174
- initializeOneToMany(filtered, children, prop, field) {
174
+ initializeOneToMany(filtered, children, prop, field, partial) {
175
175
  const mapToPk = prop.targetMeta.properties[prop.mappedBy].mapToPk;
176
176
  const map = {};
177
177
  for (const entity of filtered) {
@@ -187,14 +187,14 @@ class EntityLoader {
187
187
  }
188
188
  for (const entity of filtered) {
189
189
  const key = (0, wrap_1.helper)(entity).getSerializedPrimaryKey();
190
- entity[field].hydrate(map[key]);
190
+ entity[field].hydrate(map[key], undefined, partial);
191
191
  }
192
192
  }
193
- initializeManyToMany(filtered, children, prop, field, customOrder) {
193
+ initializeManyToMany(filtered, children, prop, field, customOrder, partial) {
194
194
  if (prop.mappedBy) {
195
195
  for (const entity of filtered) {
196
196
  const items = children.filter(child => child[prop.mappedBy].contains(entity, false));
197
- entity[field].hydrate(items, true);
197
+ entity[field].hydrate(items, true, partial);
198
198
  }
199
199
  }
200
200
  else { // owning side of M:N without pivot table needs to be reordered
@@ -204,7 +204,7 @@ class EntityLoader {
204
204
  if (!customOrder) {
205
205
  items.sort((a, b) => order.indexOf(a) - order.indexOf(b));
206
206
  }
207
- entity[field].hydrate(items, true);
207
+ entity[field].hydrate(items, true, partial);
208
208
  }
209
209
  }
210
210
  }
@@ -213,6 +213,7 @@ class EntityLoader {
213
213
  const meta = prop.targetMeta;
214
214
  let fk = Utils_1.Utils.getPrimaryKeyHash(meta.primaryKeys);
215
215
  let schema = options.schema;
216
+ let partial = !Utils_1.Utils.isEmpty(prop.where);
216
217
  if (prop.kind === enums_1.ReferenceKind.ONE_TO_MANY || (prop.kind === enums_1.ReferenceKind.MANY_TO_MANY && !prop.owner)) {
217
218
  fk = meta.properties[prop.mappedBy].name;
218
219
  }
@@ -222,7 +223,7 @@ class EntityLoader {
222
223
  children.push(...this.filterByReferences(entities, prop.name, options.refresh));
223
224
  }
224
225
  if (children.length === 0) {
225
- return [];
226
+ return { items: [], partial };
226
227
  }
227
228
  if (!schema && [enums_1.ReferenceKind.ONE_TO_ONE, enums_1.ReferenceKind.MANY_TO_ONE].includes(prop.kind)) {
228
229
  schema = children.find(e => e.__helper.__schema)?.__helper.__schema;
@@ -253,6 +254,7 @@ class EntityLoader {
253
254
  }
254
255
  }
255
256
  }
257
+ partial = !Utils_1.Utils.isEmpty(where);
256
258
  const items = await this.em.find(prop.type, where, {
257
259
  filters, convertCustomTypes, lockMode, populateWhere, logging,
258
260
  orderBy: [...Utils_1.Utils.asArray(options.orderBy), ...propOrderBy],
@@ -271,7 +273,7 @@ class EntityLoader {
271
273
  this.em.getUnitOfWork()['loadedEntities'].delete(item);
272
274
  }
273
275
  }
274
- return items;
276
+ return { items, partial };
275
277
  }
276
278
  mergePrimaryCondition(ids, pk, options, meta, metadata, platform) {
277
279
  const cond1 = QueryHelper_1.QueryHelper.processWhere({ where: { [pk]: { $in: ids } }, entityName: meta.className, metadata, platform, convertCustomTypes: !options.convertCustomTypes });
@@ -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;
@@ -153,7 +153,7 @@ class WrappedEntity {
153
153
  return this.__em?.config ?? this.entity.__config;
154
154
  }
155
155
  get __primaryKeys() {
156
- return Utils_1.Utils.getPrimaryKeyValues(this.entity, this.__meta.primaryKeys);
156
+ return Utils_1.Utils.getPrimaryKeyValues(this.entity, this.__meta);
157
157
  }
158
158
  /** @ignore */
159
159
  [node_util_1.inspect.custom]() {