@mikro-orm/core 7.0.0-dev.4 → 7.0.0-dev.40

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 (117) hide show
  1. package/EntityManager.d.ts +84 -18
  2. package/EntityManager.js +265 -172
  3. package/MikroORM.d.ts +7 -5
  4. package/MikroORM.js +0 -1
  5. package/README.md +3 -2
  6. package/cache/FileCacheAdapter.d.ts +2 -1
  7. package/cache/FileCacheAdapter.js +6 -4
  8. package/connections/Connection.d.ts +4 -2
  9. package/connections/Connection.js +2 -2
  10. package/decorators/Check.d.ts +2 -2
  11. package/decorators/Embeddable.d.ts +5 -5
  12. package/decorators/Embeddable.js +1 -1
  13. package/decorators/Embedded.d.ts +6 -12
  14. package/decorators/Entity.d.ts +18 -3
  15. package/decorators/Enum.d.ts +1 -1
  16. package/decorators/Formula.d.ts +1 -2
  17. package/decorators/Indexed.d.ts +2 -2
  18. package/decorators/ManyToMany.d.ts +4 -2
  19. package/decorators/ManyToOne.d.ts +6 -2
  20. package/decorators/OneToMany.d.ts +4 -4
  21. package/decorators/OneToOne.d.ts +5 -1
  22. package/decorators/PrimaryKey.d.ts +2 -3
  23. package/decorators/Property.d.ts +54 -4
  24. package/decorators/Transactional.d.ts +1 -0
  25. package/decorators/Transactional.js +3 -3
  26. package/decorators/index.d.ts +1 -1
  27. package/drivers/DatabaseDriver.d.ts +4 -3
  28. package/drivers/IDatabaseDriver.d.ts +22 -2
  29. package/entity/ArrayCollection.d.ts +4 -2
  30. package/entity/ArrayCollection.js +18 -6
  31. package/entity/Collection.d.ts +1 -2
  32. package/entity/Collection.js +19 -10
  33. package/entity/EntityAssigner.d.ts +1 -1
  34. package/entity/EntityAssigner.js +9 -1
  35. package/entity/EntityFactory.d.ts +7 -0
  36. package/entity/EntityFactory.js +29 -9
  37. package/entity/EntityHelper.js +25 -3
  38. package/entity/EntityLoader.d.ts +5 -4
  39. package/entity/EntityLoader.js +74 -37
  40. package/entity/EntityRepository.d.ts +1 -1
  41. package/entity/EntityValidator.js +1 -1
  42. package/entity/Reference.d.ts +9 -7
  43. package/entity/Reference.js +30 -3
  44. package/entity/WrappedEntity.js +1 -1
  45. package/entity/defineEntity.d.ts +561 -0
  46. package/entity/defineEntity.js +537 -0
  47. package/entity/index.d.ts +2 -0
  48. package/entity/index.js +2 -0
  49. package/entity/utils.d.ts +7 -0
  50. package/entity/utils.js +15 -3
  51. package/enums.d.ts +16 -3
  52. package/enums.js +13 -0
  53. package/errors.d.ts +6 -0
  54. package/errors.js +14 -0
  55. package/events/EventSubscriber.d.ts +3 -1
  56. package/hydration/ObjectHydrator.d.ts +4 -4
  57. package/hydration/ObjectHydrator.js +35 -24
  58. package/index.d.ts +2 -1
  59. package/index.js +1 -1
  60. package/logging/DefaultLogger.d.ts +1 -1
  61. package/logging/SimpleLogger.d.ts +1 -1
  62. package/metadata/EntitySchema.d.ts +8 -4
  63. package/metadata/EntitySchema.js +39 -19
  64. package/metadata/MetadataDiscovery.d.ts +1 -1
  65. package/metadata/MetadataDiscovery.js +88 -32
  66. package/metadata/MetadataStorage.js +1 -1
  67. package/metadata/MetadataValidator.js +4 -3
  68. package/naming-strategy/AbstractNamingStrategy.d.ts +5 -1
  69. package/naming-strategy/AbstractNamingStrategy.js +7 -1
  70. package/naming-strategy/NamingStrategy.d.ts +11 -1
  71. package/package.json +5 -5
  72. package/platforms/Platform.d.ts +5 -3
  73. package/platforms/Platform.js +4 -8
  74. package/serialization/EntitySerializer.d.ts +2 -0
  75. package/serialization/EntitySerializer.js +2 -2
  76. package/serialization/EntityTransformer.js +1 -1
  77. package/serialization/SerializationContext.js +14 -11
  78. package/types/BigIntType.d.ts +9 -6
  79. package/types/BigIntType.js +3 -0
  80. package/types/BooleanType.d.ts +1 -1
  81. package/types/DecimalType.d.ts +6 -4
  82. package/types/DecimalType.js +1 -1
  83. package/types/DoubleType.js +1 -1
  84. package/types/JsonType.d.ts +1 -1
  85. package/types/JsonType.js +7 -2
  86. package/types/Type.d.ts +2 -1
  87. package/types/Type.js +1 -1
  88. package/types/index.d.ts +1 -1
  89. package/typings.d.ts +88 -39
  90. package/typings.js +24 -4
  91. package/unit-of-work/ChangeSetComputer.js +3 -1
  92. package/unit-of-work/ChangeSetPersister.d.ts +4 -2
  93. package/unit-of-work/ChangeSetPersister.js +37 -16
  94. package/unit-of-work/UnitOfWork.d.ts +8 -1
  95. package/unit-of-work/UnitOfWork.js +109 -41
  96. package/utils/Configuration.d.ts +23 -5
  97. package/utils/Configuration.js +17 -3
  98. package/utils/ConfigurationLoader.d.ts +0 -2
  99. package/utils/ConfigurationLoader.js +2 -24
  100. package/utils/Cursor.d.ts +3 -3
  101. package/utils/Cursor.js +3 -0
  102. package/utils/DataloaderUtils.d.ts +7 -2
  103. package/utils/DataloaderUtils.js +38 -7
  104. package/utils/EntityComparator.d.ts +6 -2
  105. package/utils/EntityComparator.js +104 -58
  106. package/utils/QueryHelper.d.ts +9 -1
  107. package/utils/QueryHelper.js +66 -5
  108. package/utils/RawQueryFragment.d.ts +36 -2
  109. package/utils/RawQueryFragment.js +35 -1
  110. package/utils/TransactionManager.d.ts +65 -0
  111. package/utils/TransactionManager.js +218 -0
  112. package/utils/Utils.d.ts +11 -5
  113. package/utils/Utils.js +76 -33
  114. package/utils/index.d.ts +1 -0
  115. package/utils/index.js +1 -0
  116. package/utils/upsert-utils.d.ts +7 -2
  117. package/utils/upsert-utils.js +52 -1
@@ -32,13 +32,12 @@ export class EntityLoader {
32
32
  const visited = options.visited ??= new Set();
33
33
  options.where ??= {};
34
34
  options.orderBy ??= {};
35
- options.filters ??= {};
36
35
  options.lookup ??= true;
37
36
  options.validate ??= true;
38
37
  options.refresh ??= false;
39
38
  options.convertCustomTypes ??= true;
40
39
  if (references.length > 0) {
41
- await this.populateScalar(meta, references, options);
40
+ await this.populateScalar(meta, references, { ...options, populateWhere: undefined });
42
41
  }
43
42
  populate = this.normalizePopulate(entityName, populate, options.strategy, options.lookup);
44
43
  const invalid = populate.find(({ field }) => !this.em.canPopulate(entityName, field));
@@ -140,17 +139,22 @@ export class EntityLoader {
140
139
  const innerOrderBy = Utils.asArray(options.orderBy)
141
140
  .filter(orderBy => (Array.isArray(orderBy[prop.name]) && orderBy[prop.name].length > 0) || Utils.isObject(orderBy[prop.name]))
142
141
  .flatMap(orderBy => orderBy[prop.name]);
142
+ const where = await this.extractChildCondition(options, prop);
143
143
  if (prop.kind === ReferenceKind.MANY_TO_MANY && this.driver.getPlatform().usesPivotTable()) {
144
- return this.findChildrenFromPivotTable(filtered, prop, options, innerOrderBy, populate, !!ref);
144
+ const res = await this.findChildrenFromPivotTable(filtered, prop, options, innerOrderBy, populate, !!ref);
145
+ return Utils.flatten(res);
145
146
  }
146
- const where = await this.extractChildCondition(options, prop);
147
- const data = await this.findChildren(entities, prop, populate, { ...options, where, orderBy: innerOrderBy }, !!(ref || prop.mapToPk));
148
- this.initializeCollections(filtered, prop, field, data, innerOrderBy.length > 0);
149
- return data;
147
+ const { items, partial } = await this.findChildren(options.filtered ?? entities, prop, populate, {
148
+ ...options,
149
+ where,
150
+ orderBy: innerOrderBy,
151
+ }, !!(ref || prop.mapToPk));
152
+ this.initializeCollections(filtered, prop, field, items, innerOrderBy.length > 0, partial);
153
+ return items;
150
154
  }
151
155
  async populateScalar(meta, filtered, options) {
152
156
  const pk = Utils.getPrimaryKeyHash(meta.primaryKeys);
153
- const ids = Utils.unique(filtered.map(e => Utils.getPrimaryKeyValues(e, meta.primaryKeys, true)));
157
+ const ids = Utils.unique(filtered.map(e => Utils.getPrimaryKeyValues(e, meta, true)));
154
158
  const where = this.mergePrimaryCondition(ids, pk, options, meta, this.metadata, this.driver.getPlatform());
155
159
  const { filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging, fields } = options;
156
160
  await this.em.find(meta.className, where, {
@@ -159,15 +163,15 @@ export class EntityLoader {
159
163
  populate: [],
160
164
  });
161
165
  }
162
- initializeCollections(filtered, prop, field, children, customOrder) {
166
+ initializeCollections(filtered, prop, field, children, customOrder, partial) {
163
167
  if (prop.kind === ReferenceKind.ONE_TO_MANY) {
164
- this.initializeOneToMany(filtered, children, prop, field);
168
+ this.initializeOneToMany(filtered, children, prop, field, partial);
165
169
  }
166
170
  if (prop.kind === ReferenceKind.MANY_TO_MANY && !this.driver.getPlatform().usesPivotTable()) {
167
- this.initializeManyToMany(filtered, children, prop, field, customOrder);
171
+ this.initializeManyToMany(filtered, children, prop, field, customOrder, partial);
168
172
  }
169
173
  }
170
- initializeOneToMany(filtered, children, prop, field) {
174
+ initializeOneToMany(filtered, children, prop, field, partial) {
171
175
  const mapToPk = prop.targetMeta.properties[prop.mappedBy].mapToPk;
172
176
  const map = {};
173
177
  for (const entity of filtered) {
@@ -183,14 +187,14 @@ export class EntityLoader {
183
187
  }
184
188
  for (const entity of filtered) {
185
189
  const key = helper(entity).getSerializedPrimaryKey();
186
- entity[field].hydrate(map[key]);
190
+ entity[field].hydrate(map[key], undefined, partial);
187
191
  }
188
192
  }
189
- initializeManyToMany(filtered, children, prop, field, customOrder) {
193
+ initializeManyToMany(filtered, children, prop, field, customOrder, partial) {
190
194
  if (prop.mappedBy) {
191
195
  for (const entity of filtered) {
192
196
  const items = children.filter(child => child[prop.mappedBy].contains(entity, false));
193
- entity[field].hydrate(items, true);
197
+ entity[field].hydrate(items, true, partial);
194
198
  }
195
199
  }
196
200
  else { // owning side of M:N without pivot table needs to be reordered
@@ -200,15 +204,16 @@ export class EntityLoader {
200
204
  if (!customOrder) {
201
205
  items.sort((a, b) => order.indexOf(a) - order.indexOf(b));
202
206
  }
203
- entity[field].hydrate(items, true);
207
+ entity[field].hydrate(items, true, partial);
204
208
  }
205
209
  }
206
210
  }
207
211
  async findChildren(entities, prop, populate, options, ref) {
208
- const children = this.getChildReferences(entities, prop, options, ref);
212
+ const children = Utils.unique(this.getChildReferences(entities, prop, options, ref));
209
213
  const meta = prop.targetMeta;
210
214
  let fk = Utils.getPrimaryKeyHash(meta.primaryKeys);
211
215
  let schema = options.schema;
216
+ const partial = !Utils.isEmpty(prop.where) || !Utils.isEmpty(options.where);
212
217
  if (prop.kind === ReferenceKind.ONE_TO_MANY || (prop.kind === ReferenceKind.MANY_TO_MANY && !prop.owner)) {
213
218
  fk = meta.properties[prop.mappedBy].name;
214
219
  }
@@ -218,7 +223,7 @@ export class EntityLoader {
218
223
  children.push(...this.filterByReferences(entities, prop.name, options.refresh));
219
224
  }
220
225
  if (children.length === 0) {
221
- return [];
226
+ return { items: [], partial };
222
227
  }
223
228
  if (!schema && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind)) {
224
229
  schema = children.find(e => e.__helper.__schema)?.__helper.__schema;
@@ -227,7 +232,7 @@ export class EntityLoader {
227
232
  let where = this.mergePrimaryCondition(ids, fk, options, meta, this.metadata, this.driver.getPlatform());
228
233
  const fields = this.buildFields(options.fields, prop, ref);
229
234
  /* eslint-disable prefer-const */
230
- let { refresh, filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging, } = options;
235
+ let { refresh, filters, convertCustomTypes, lockMode, strategy, populateWhere = 'infer', connectionType, logging, } = options;
231
236
  /* eslint-enable prefer-const */
232
237
  if (typeof populateWhere === 'object') {
233
238
  populateWhere = await this.extractChildCondition({ where: populateWhere }, prop);
@@ -249,9 +254,13 @@ export class EntityLoader {
249
254
  }
250
255
  }
251
256
  }
257
+ const orderBy = [...Utils.asArray(options.orderBy), ...propOrderBy].filter((order, idx, array) => {
258
+ // skip consecutive ordering with the same key to get around mongo issues
259
+ return idx === 0 || !Utils.equals(Object.keys(array[idx - 1]), Object.keys(order));
260
+ });
252
261
  const items = await this.em.find(prop.type, where, {
253
262
  filters, convertCustomTypes, lockMode, populateWhere, logging,
254
- orderBy: [...Utils.asArray(options.orderBy), ...propOrderBy],
263
+ orderBy,
255
264
  populate: populate.children ?? populate.all ?? [],
256
265
  exclude: Array.isArray(options.exclude) ? Utils.extractChildElements(options.exclude, prop.name) : options.exclude,
257
266
  strategy, fields, schema, connectionType,
@@ -260,6 +269,24 @@ export class EntityLoader {
260
269
  // @ts-ignore not a public option, will be propagated to the populate call
261
270
  visited: options.visited,
262
271
  });
272
+ if ([ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && items.length !== children.length) {
273
+ const nullVal = this.em.config.get('forceUndefined') ? undefined : null;
274
+ const itemsMap = new Set();
275
+ const childrenMap = new Set();
276
+ for (const item of items) {
277
+ itemsMap.add(helper(item).getSerializedPrimaryKey());
278
+ }
279
+ for (const child of children) {
280
+ childrenMap.add(helper(child).getSerializedPrimaryKey());
281
+ }
282
+ for (const entity of entities) {
283
+ const key = helper(entity[prop.name] ?? {})?.getSerializedPrimaryKey();
284
+ if (childrenMap.has(key) && !itemsMap.has(key)) {
285
+ entity[prop.name] = nullVal;
286
+ helper(entity).__originalEntityData[prop.name] = null;
287
+ }
288
+ }
289
+ }
263
290
  for (const item of items) {
264
291
  if (ref && !helper(item).__onLoadFired) {
265
292
  helper(item).__initialized = false;
@@ -267,7 +294,7 @@ export class EntityLoader {
267
294
  this.em.getUnitOfWork()['loadedEntities'].delete(item);
268
295
  }
269
296
  }
270
- return items;
297
+ return { items, partial };
271
298
  }
272
299
  mergePrimaryCondition(ids, pk, options, meta, metadata, platform) {
273
300
  const cond1 = QueryHelper.processWhere({ where: { [pk]: { $in: ids } }, entityName: meta.className, metadata, platform, convertCustomTypes: !options.convertCustomTypes });
@@ -283,6 +310,7 @@ export class EntityLoader {
283
310
  if (prop.kind === ReferenceKind.SCALAR && !prop.lazy) {
284
311
  return;
285
312
  }
313
+ options = { ...options, filters: QueryHelper.mergePropertyFilters(prop.filters, options.filters) };
286
314
  const populated = await this.populateMany(entityName, entities, populate, options);
287
315
  if (!populate.children && !populate.all) {
288
316
  return;
@@ -310,10 +338,18 @@ export class EntityLoader {
310
338
  const innerOrderBy = Utils.asArray(options.orderBy)
311
339
  .filter(orderBy => Utils.isObject(orderBy[prop.name]))
312
340
  .map(orderBy => orderBy[prop.name]);
313
- const { refresh, filters, ignoreLazyScalarProperties, populateWhere, connectionType, logging } = options;
341
+ const { refresh, filters, ignoreLazyScalarProperties, populateWhere, connectionType, logging, schema } = options;
314
342
  const exclude = Array.isArray(options.exclude) ? Utils.extractChildElements(options.exclude, prop.name) : options.exclude;
315
- const filtered = Utils.unique(children.filter(e => !options.visited.has(e)));
316
- await this.populate(prop.type, filtered, populate.children ?? populate.all, {
343
+ const visited = options.visited;
344
+ for (const entity of entities) {
345
+ visited.delete(entity);
346
+ }
347
+ const unique = Utils.unique(children);
348
+ const filtered = unique.filter(e => !visited.has(e));
349
+ for (const entity of entities) {
350
+ visited.add(entity);
351
+ }
352
+ await this.populate(prop.type, unique, populate.children ?? populate.all, {
317
353
  where: await this.extractChildCondition(options, prop, false),
318
354
  orderBy: innerOrderBy,
319
355
  fields,
@@ -325,12 +361,16 @@ export class EntityLoader {
325
361
  populateWhere,
326
362
  connectionType,
327
363
  logging,
364
+ schema,
328
365
  // @ts-ignore not a public option, will be propagated to the populate call
329
366
  refresh: refresh && !filtered.every(item => options.visited.has(item)),
330
367
  // @ts-ignore not a public option, will be propagated to the populate call
331
368
  visited: options.visited,
369
+ // @ts-ignore not a public option
370
+ filtered,
332
371
  });
333
372
  }
373
+ /** @internal */
334
374
  async findChildrenFromPivotTable(filtered, prop, options, orderBy, populate, pivotJoin) {
335
375
  const ids = filtered.map(e => e.__helper.__primaryKeys);
336
376
  const refresh = options.refresh;
@@ -368,7 +408,7 @@ export class EntityLoader {
368
408
  return this.em.getUnitOfWork().register(entity, item, { refresh, loaded: true });
369
409
  });
370
410
  entity[prop.name].hydrate(items, true);
371
- children.push(...items);
411
+ children.push(items);
372
412
  }
373
413
  return children;
374
414
  }
@@ -444,23 +484,20 @@ export class EntityLoader {
444
484
  }
445
485
  getChildReferences(entities, prop, options, ref) {
446
486
  const filtered = this.filterCollections(entities, prop.name, options, ref);
447
- const children = [];
448
487
  if (prop.kind === ReferenceKind.ONE_TO_MANY) {
449
- children.push(...filtered.map(e => e[prop.name].owner));
488
+ return filtered.map(e => e[prop.name].owner);
450
489
  }
451
- else if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner) {
452
- children.push(...filtered.reduce((a, b) => {
490
+ if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner) {
491
+ return filtered.reduce((a, b) => {
453
492
  a.push(...b[prop.name].getItems());
454
493
  return a;
455
- }, []));
494
+ }, []);
456
495
  }
457
- else if (prop.kind === ReferenceKind.MANY_TO_MANY) { // inverse side
458
- children.push(...filtered);
496
+ if (prop.kind === ReferenceKind.MANY_TO_MANY) { // inverse side
497
+ return filtered;
459
498
  }
460
- else { // MANY_TO_ONE or ONE_TO_ONE
461
- children.push(...this.filterReferences(entities, prop.name, options, ref));
462
- }
463
- return children;
499
+ // MANY_TO_ONE or ONE_TO_ONE
500
+ return this.filterReferences(entities, prop.name, options, ref);
464
501
  }
465
502
  filterCollections(entities, field, options, ref) {
466
503
  if (options.refresh) {
@@ -514,7 +551,7 @@ export class EntityLoader {
514
551
  if (refresh) {
515
552
  return entities;
516
553
  }
517
- return entities.filter(e => !e[field]?.__helper?.__initialized);
554
+ return entities.filter(e => e[field] !== null && !e[field]?.__helper?.__initialized);
518
555
  }
519
556
  lookupAllRelationships(entityName) {
520
557
  const ret = [];
@@ -80,7 +80,7 @@ export declare class EntityRepository<Entity extends object> {
80
80
  /**
81
81
  * @inheritDoc EntityManager.findByCursor
82
82
  */
83
- findByCursor<Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(where: FilterQuery<Entity>, options: FindByCursorOptions<Entity, Hint, Fields, Excludes>): Promise<Cursor<Entity, Hint, Fields, Excludes>>;
83
+ findByCursor<Hint extends string = never, Fields extends string = '*', Excludes extends string = never, IncludeCount extends boolean = true>(where: FilterQuery<Entity>, options: FindByCursorOptions<Entity, Hint, Fields, Excludes, IncludeCount>): Promise<Cursor<Entity, Hint, Fields, Excludes, IncludeCount>>;
84
84
  /**
85
85
  * Finds all entities of given type. You can pass additional options via the `options` parameter.
86
86
  */
@@ -11,7 +11,7 @@ export class EntityValidator {
11
11
  }
12
12
  validate(entity, payload, meta) {
13
13
  meta.props.forEach(prop => {
14
- if (prop.inherited) {
14
+ if (prop.inherited || (prop.persist === false && prop.userDefined !== false)) {
15
15
  return;
16
16
  }
17
17
  if ([ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(prop.kind)) {
@@ -1,8 +1,9 @@
1
1
  import { inspect } from 'node:util';
2
- import type { AddEager, Dictionary, EntityClass, EntityKey, EntityProperty, Loaded, LoadedReference, Primary, Ref } from '../typings.js';
2
+ import type { AddEager, AddOptional, Dictionary, EntityClass, EntityKey, EntityProperty, Loaded, LoadedReference, Primary, Ref } from '../typings.js';
3
3
  import type { FindOneOptions, FindOneOrFailOptions } from '../drivers/IDatabaseDriver.js';
4
4
  export declare class Reference<T extends object> {
5
5
  private entity;
6
+ private property?;
6
7
  constructor(entity: T);
7
8
  static create<T extends object>(entity: T | Ref<T>): Ref<T>;
8
9
  static createFromPK<T extends object>(entityType: EntityClass<T>, pk: Primary<T>, options?: {
@@ -56,6 +57,11 @@ export declare class ScalarReference<Value> {
56
57
  * Returns either the whole entity, or the requested property.
57
58
  */
58
59
  load(options?: Omit<LoadReferenceOptions<any, any>, 'populate' | 'fields' | 'exclude'>): Promise<Value | undefined>;
60
+ /**
61
+ * Ensures the underlying entity is loaded first (without reloading it if it already is loaded).
62
+ * Returns the entity or throws an error just like `em.findOneOrFail()` (and respects the same config options).
63
+ */
64
+ loadOrFail(options?: Omit<LoadReferenceOrFailOptions<any, any>, 'populate' | 'fields' | 'exclude'>): Promise<Value>;
59
65
  set(value: Value): void;
60
66
  bind<Entity extends object>(entity: Entity, property: EntityKey<Entity>): void;
61
67
  unwrap(): Value | undefined;
@@ -72,15 +78,11 @@ export interface LoadReferenceOrFailOptions<T extends object, P extends string =
72
78
  /**
73
79
  * shortcut for `wrap(entity).toReference()`
74
80
  */
75
- export declare function ref<T>(entity: T | Ref<T>): Ref<T> & LoadedReference<Loaded<T, AddEager<T>>>;
81
+ export declare function ref<I extends unknown | Ref<unknown> | undefined | null, T extends I & {}>(entity: I): Ref<T> & LoadedReference<Loaded<T, AddEager<T>>> | AddOptional<typeof entity>;
76
82
  /**
77
83
  * shortcut for `Reference.createFromPK(entityType, pk)`
78
84
  */
79
- export declare function ref<T, PKV extends Primary<T> = Primary<T>>(entityType: EntityClass<T>, pk?: T | PKV): Ref<T>;
80
- /**
81
- * shortcut for `wrap(entity).toReference()`
82
- */
83
- export declare function ref<T>(value: T | Ref<T>): Ref<T> & LoadedReference<Loaded<T, AddEager<T>>>;
85
+ export declare function ref<I extends unknown | undefined | null, T, PKV extends Primary<T> = Primary<T>>(entityType: EntityClass<T>, pk: I): Ref<T> | AddOptional<typeof pk>;
84
86
  /**
85
87
  * shortcut for `Reference.createNakedFromPK(entityType, pk)`
86
88
  */
@@ -2,8 +2,11 @@ import { inspect } from 'node:util';
2
2
  import { DataloaderType } from '../enums.js';
3
3
  import { helper, wrap } from './wrap.js';
4
4
  import { Utils } from '../utils/Utils.js';
5
+ import { QueryHelper } from '../utils/QueryHelper.js';
6
+ import { NotFoundError } from '../errors.js';
5
7
  export class Reference {
6
8
  entity;
9
+ property;
7
10
  constructor(entity) {
8
11
  this.entity = entity;
9
12
  this.set(entity);
@@ -33,10 +36,15 @@ export class Reference {
33
36
  }
34
37
  static createFromPK(entityType, pk, options) {
35
38
  const ref = this.createNakedFromPK(entityType, pk, options);
36
- return helper(ref).toReference();
39
+ return helper(ref)?.toReference() ?? ref;
37
40
  }
38
41
  static createNakedFromPK(entityType, pk, options) {
39
42
  const factory = entityType.prototype.__factory;
43
+ if (!factory) {
44
+ // this can happen only if `ref()` is used as a property initializer, and the value is important only for the
45
+ // inference of defaults, so it's fine to return it directly without wrapping with `Reference` class
46
+ return pk;
47
+ }
40
48
  const entity = factory.createReference(entityType, pk, {
41
49
  merge: false,
42
50
  convertCustomTypes: false,
@@ -58,7 +66,9 @@ export class Reference {
58
66
  */
59
67
  static wrapReference(entity, prop) {
60
68
  if (entity && prop.ref && !Reference.isReference(entity)) {
61
- return Reference.create(entity);
69
+ const ref = Reference.create(entity);
70
+ ref.property = prop;
71
+ return ref;
62
72
  }
63
73
  return entity;
64
74
  }
@@ -78,6 +88,7 @@ export class Reference {
78
88
  if (!wrapped.__em) {
79
89
  return this.entity;
80
90
  }
91
+ options = { ...options, filters: QueryHelper.mergePropertyFilters(this.property?.filters, options.filters) };
81
92
  if (this.isInitialized() && !options.refresh && options.populate) {
82
93
  await wrapped.__em.populate(this.entity, options.populate, options);
83
94
  }
@@ -137,7 +148,7 @@ export class Reference {
137
148
  /** @ignore */
138
149
  [inspect.custom](depth = 2) {
139
150
  const object = { ...this };
140
- const hidden = ['meta'];
151
+ const hidden = ['meta', 'property'];
141
152
  hidden.forEach(k => delete object[k]);
142
153
  const ret = inspect(object, { depth });
143
154
  const wrapped = helper(this.entity);
@@ -171,6 +182,22 @@ export class ScalarReference {
171
182
  }
172
183
  return this.value;
173
184
  }
185
+ /**
186
+ * Ensures the underlying entity is loaded first (without reloading it if it already is loaded).
187
+ * Returns the entity or throws an error just like `em.findOneOrFail()` (and respects the same config options).
188
+ */
189
+ async loadOrFail(options = {}) {
190
+ const ret = await this.load(options);
191
+ if (ret == null) {
192
+ const wrapped = helper(this.entity);
193
+ options.failHandler ??= wrapped.__em.config.get('findOneOrFailHandler');
194
+ const entityName = this.entity.constructor.name;
195
+ const where = wrapped.getPrimaryKey();
196
+ const whereString = typeof where === 'object' ? inspect(where) : where;
197
+ throw new NotFoundError(`${entityName} (${whereString}) failed to load property '${this.property}'`);
198
+ }
199
+ return ret;
200
+ }
174
201
  set(value) {
175
202
  this.value = value;
176
203
  this.initialized = true;
@@ -150,7 +150,7 @@ export class WrappedEntity {
150
150
  return this.__em?.config ?? this.entity.__config;
151
151
  }
152
152
  get __primaryKeys() {
153
- return Utils.getPrimaryKeyValues(this.entity, this.__meta.primaryKeys);
153
+ return Utils.getPrimaryKeyValues(this.entity, this.__meta);
154
154
  }
155
155
  /** @ignore */
156
156
  [inspect.custom]() {