@mikro-orm/core 7.0.0-dev.3 → 7.0.0-dev.30

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 (103) hide show
  1. package/EntityManager.d.ts +50 -7
  2. package/EntityManager.js +141 -97
  3. package/MikroORM.js +0 -1
  4. package/README.md +1 -2
  5. package/cache/FileCacheAdapter.d.ts +2 -1
  6. package/cache/FileCacheAdapter.js +6 -4
  7. package/connections/Connection.d.ts +4 -2
  8. package/connections/Connection.js +2 -2
  9. package/decorators/Check.d.ts +2 -2
  10. package/decorators/Embeddable.d.ts +5 -5
  11. package/decorators/Embeddable.js +1 -1
  12. package/decorators/Embedded.d.ts +6 -12
  13. package/decorators/Entity.d.ts +20 -5
  14. package/decorators/Entity.js +0 -1
  15. package/decorators/Enum.d.ts +1 -1
  16. package/decorators/Formula.d.ts +1 -2
  17. package/decorators/Indexed.d.ts +10 -8
  18. package/decorators/Indexed.js +1 -1
  19. package/decorators/ManyToMany.d.ts +4 -2
  20. package/decorators/ManyToOne.d.ts +6 -2
  21. package/decorators/OneToMany.d.ts +4 -4
  22. package/decorators/OneToOne.d.ts +5 -1
  23. package/decorators/PrimaryKey.d.ts +2 -3
  24. package/decorators/Property.d.ts +1 -1
  25. package/decorators/Transactional.d.ts +1 -0
  26. package/decorators/Transactional.js +3 -3
  27. package/drivers/IDatabaseDriver.d.ts +8 -1
  28. package/entity/ArrayCollection.d.ts +4 -2
  29. package/entity/ArrayCollection.js +18 -6
  30. package/entity/Collection.d.ts +1 -2
  31. package/entity/Collection.js +16 -10
  32. package/entity/EntityAssigner.d.ts +1 -1
  33. package/entity/EntityAssigner.js +9 -1
  34. package/entity/EntityFactory.d.ts +6 -0
  35. package/entity/EntityFactory.js +21 -7
  36. package/entity/EntityHelper.js +8 -1
  37. package/entity/EntityLoader.d.ts +3 -2
  38. package/entity/EntityLoader.js +54 -35
  39. package/entity/EntityRepository.d.ts +1 -1
  40. package/entity/EntityValidator.js +1 -1
  41. package/entity/Reference.d.ts +8 -7
  42. package/entity/Reference.js +22 -1
  43. package/entity/WrappedEntity.js +1 -1
  44. package/entity/defineEntity.d.ts +537 -0
  45. package/entity/defineEntity.js +693 -0
  46. package/entity/index.d.ts +2 -0
  47. package/entity/index.js +2 -0
  48. package/entity/utils.d.ts +7 -0
  49. package/entity/utils.js +15 -3
  50. package/enums.d.ts +16 -3
  51. package/enums.js +13 -0
  52. package/errors.d.ts +6 -0
  53. package/errors.js +14 -0
  54. package/events/EventSubscriber.d.ts +3 -1
  55. package/hydration/ObjectHydrator.js +10 -2
  56. package/index.d.ts +1 -1
  57. package/index.js +1 -1
  58. package/metadata/EntitySchema.d.ts +6 -4
  59. package/metadata/EntitySchema.js +33 -19
  60. package/metadata/MetadataDiscovery.d.ts +0 -1
  61. package/metadata/MetadataDiscovery.js +51 -29
  62. package/metadata/MetadataStorage.js +1 -1
  63. package/metadata/MetadataValidator.js +4 -3
  64. package/package.json +5 -5
  65. package/platforms/Platform.d.ts +3 -1
  66. package/serialization/EntitySerializer.d.ts +2 -0
  67. package/serialization/EntitySerializer.js +1 -1
  68. package/serialization/SerializationContext.js +13 -10
  69. package/types/BigIntType.d.ts +9 -6
  70. package/types/BigIntType.js +3 -0
  71. package/types/BooleanType.d.ts +1 -1
  72. package/types/DecimalType.d.ts +6 -4
  73. package/types/DecimalType.js +1 -1
  74. package/types/DoubleType.js +1 -1
  75. package/typings.d.ts +72 -35
  76. package/typings.js +24 -4
  77. package/unit-of-work/ChangeSetComputer.js +3 -1
  78. package/unit-of-work/ChangeSetPersister.d.ts +4 -2
  79. package/unit-of-work/ChangeSetPersister.js +21 -11
  80. package/unit-of-work/UnitOfWork.d.ts +2 -1
  81. package/unit-of-work/UnitOfWork.js +71 -24
  82. package/utils/AbstractSchemaGenerator.js +5 -2
  83. package/utils/Configuration.d.ts +15 -5
  84. package/utils/Configuration.js +7 -7
  85. package/utils/ConfigurationLoader.d.ts +0 -2
  86. package/utils/ConfigurationLoader.js +2 -24
  87. package/utils/Cursor.d.ts +3 -3
  88. package/utils/Cursor.js +3 -0
  89. package/utils/DataloaderUtils.d.ts +7 -2
  90. package/utils/DataloaderUtils.js +38 -7
  91. package/utils/EntityComparator.d.ts +6 -2
  92. package/utils/EntityComparator.js +98 -59
  93. package/utils/QueryHelper.d.ts +6 -0
  94. package/utils/QueryHelper.js +48 -5
  95. package/utils/RawQueryFragment.d.ts +34 -0
  96. package/utils/RawQueryFragment.js +40 -1
  97. package/utils/TransactionManager.d.ts +65 -0
  98. package/utils/TransactionManager.js +220 -0
  99. package/utils/Utils.d.ts +11 -7
  100. package/utils/Utils.js +67 -33
  101. package/utils/index.d.ts +1 -0
  102. package/utils/index.js +1 -0
  103. package/utils/upsert-utils.js +9 -1
@@ -45,6 +45,7 @@ export class EntityFactory {
45
45
  let wrapped = exists && helper(exists);
46
46
  if (wrapped && !options.refresh) {
47
47
  wrapped.__processing = true;
48
+ Utils.dropUndefinedProperties(data);
48
49
  this.mergeData(meta2, exists, data, options);
49
50
  wrapped.__processing = false;
50
51
  if (wrapped.isInitialized()) {
@@ -70,7 +71,7 @@ export class EntityFactory {
70
71
  continue;
71
72
  }
72
73
  if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && Utils.isPlainObject(data[prop.name])) {
73
- data[prop.name] = Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta.primaryKeys, true);
74
+ data[prop.name] = Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta, true);
74
75
  }
75
76
  data[prop.name] = prop.customType.convertToDatabaseValue(data[prop.name], this.platform, { key: prop.name, mode: 'hydration' });
76
77
  }
@@ -86,7 +87,9 @@ export class EntityFactory {
86
87
  }
87
88
  if (options.merge && wrapped.hasPrimaryKey()) {
88
89
  this.unitOfWork.register(entity, data, {
89
- refresh: options.refresh && options.initialized,
90
+ // Always refresh to ensure the payload is in correct shape for joined strategy. When loading nested relations,
91
+ // they will be created early without `Type.ensureComparable` being properly handled, resulting in extra updates.
92
+ refresh: options.initialized,
90
93
  newEntity: options.newEntity,
91
94
  loaded: options.initialized,
92
95
  });
@@ -110,7 +113,7 @@ export class EntityFactory {
110
113
  if (meta.versionProperty && data[meta.versionProperty] && data[meta.versionProperty] !== originalEntityData[meta.versionProperty]) {
111
114
  diff[meta.versionProperty] = data[meta.versionProperty];
112
115
  }
113
- const diff2 = this.comparator.diffEntities(meta.className, existsData, data);
116
+ const diff2 = this.comparator.diffEntities(meta.className, existsData, data, { includeInverseSides: true });
114
117
  // do not override values changed by user
115
118
  Utils.keys(diff).forEach(key => delete diff2[key]);
116
119
  Utils.keys(diff2).filter(key => {
@@ -168,8 +171,8 @@ export class EntityFactory {
168
171
  if (Array.isArray(id)) {
169
172
  id = Utils.getPrimaryKeyCondFromArray(id, meta);
170
173
  }
171
- const pks = Utils.getOrderedPrimaryKeys(id, meta, this.platform, options.convertCustomTypes);
172
- 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);
173
176
  if (exists) {
174
177
  return exists;
175
178
  }
@@ -228,6 +231,13 @@ export class EntityFactory {
228
231
  }
229
232
  return entity;
230
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
+ }
231
241
  hydrate(entity, meta, data, options) {
232
242
  if (options.initialized) {
233
243
  this.hydrator.hydrate(entity, meta, data, this, 'full', options.newEntity, options.convertCustomTypes, options.schema, this.driver.getSchemaName(meta, options));
@@ -239,6 +249,10 @@ export class EntityFactory {
239
249
  helper(entity)?.__loadedProperties.add(key);
240
250
  helper(entity)?.__serializationContext.fields?.add(key);
241
251
  });
252
+ const processOnCreateHooksEarly = options.processOnCreateHooksEarly ?? this.config.get('processOnCreateHooksEarly');
253
+ if (options.newEntity && processOnCreateHooksEarly) {
254
+ this.assignDefaultValues(entity, meta);
255
+ }
242
256
  }
243
257
  findEntity(data, meta, options) {
244
258
  const schema = this.driver.getSchemaName(meta, options);
@@ -248,7 +262,7 @@ export class EntityFactory {
248
262
  if (!Array.isArray(data) && meta.primaryKeys.some(pk => data[pk] == null)) {
249
263
  return undefined;
250
264
  }
251
- const pks = Utils.getOrderedPrimaryKeys(data, meta, this.platform);
265
+ const pks = Utils.getOrderedPrimaryKeys(data, meta, this.platform, options.convertCustomTypes);
252
266
  return this.unitOfWork.getById(meta.className, pks, schema);
253
267
  }
254
268
  processDiscriminatorColumn(meta, data) {
@@ -282,7 +296,7 @@ export class EntityFactory {
282
296
  return meta.constructorParams.map(k => {
283
297
  if (meta.properties[k] && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(meta.properties[k].kind) && data[k]) {
284
298
  const pk = Reference.unwrapReference(data[k]);
285
- 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);
286
300
  if (entity) {
287
301
  return entity;
288
302
  }
@@ -146,10 +146,13 @@ export class EntityHelper {
146
146
  set(val) {
147
147
  const entity = Reference.unwrapReference(val ?? wrapped.__data[prop.name]);
148
148
  const old = Reference.unwrapReference(wrapped.__data[prop.name]);
149
+ if (old && old !== entity && prop.kind === ReferenceKind.MANY_TO_ONE && prop.inversedBy && old[prop.inversedBy]) {
150
+ old[prop.inversedBy].removeWithoutPropagation(this);
151
+ }
149
152
  wrapped.__data[prop.name] = Reference.wrapReference(val, prop);
150
153
  // when propagation from inside hydration, we set the FK to the entity data immediately
151
154
  if (val && hydrator.isRunning() && wrapped.__originalEntityData && prop.owner) {
152
- 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);
153
156
  }
154
157
  else {
155
158
  wrapped.__touched = !hydrator.isRunning();
@@ -169,6 +172,9 @@ export class EntityHelper {
169
172
  continue;
170
173
  }
171
174
  const inverse = value?.[prop2.name];
175
+ if (Utils.isCollection(inverse) && inverse.isPartial()) {
176
+ continue;
177
+ }
172
178
  if (prop.kind === ReferenceKind.MANY_TO_ONE && Utils.isCollection(inverse) && inverse.isInitialized()) {
173
179
  inverse.addWithoutPropagation(owner);
174
180
  helper(owner).__em?.getUnitOfWork().cancelOrphanRemoval(owner);
@@ -207,6 +213,7 @@ export class EntityHelper {
207
213
  }
208
214
  if (old?.[prop2.name] != null) {
209
215
  delete helper(old).__data[prop2.name];
216
+ old[prop2.name] = null;
210
217
  }
211
218
  }
212
219
  static ensurePropagation(entity) {
@@ -1,4 +1,4 @@
1
- import type { ConnectionType, Dictionary, FilterQuery, PopulateOptions } from '../typings.js';
1
+ import type { AnyEntity, ConnectionType, Dictionary, EntityProperty, FilterQuery, PopulateOptions } from '../typings.js';
2
2
  import type { EntityManager } from '../EntityManager.js';
3
3
  import { LoadStrategy, type LockMode, type PopulateHint, PopulatePath, type QueryOrderMap } from '../enums.js';
4
4
  import type { EntityField } from '../drivers/IDatabaseDriver.js';
@@ -49,7 +49,8 @@ export declare class EntityLoader {
49
49
  private findChildren;
50
50
  private mergePrimaryCondition;
51
51
  private populateField;
52
- private findChildrenFromPivotTable;
52
+ /** @internal */
53
+ findChildrenFromPivotTable<Entity extends object>(filtered: Entity[], prop: EntityProperty<Entity>, options: Required<EntityLoaderOptions<Entity>>, orderBy?: QueryOrderMap<Entity>[], populate?: PopulateOptions<Entity>, pivotJoin?: boolean): Promise<AnyEntity[][]>;
53
54
  private extractChildCondition;
54
55
  private buildFields;
55
56
  private getChildReferences;
@@ -38,7 +38,7 @@ export class EntityLoader {
38
38
  options.refresh ??= false;
39
39
  options.convertCustomTypes ??= true;
40
40
  if (references.length > 0) {
41
- await this.populateScalar(meta, references, options);
41
+ await this.populateScalar(meta, references, { ...options, populateWhere: undefined });
42
42
  }
43
43
  populate = this.normalizePopulate(entityName, populate, options.strategy, options.lookup);
44
44
  const invalid = populate.find(({ field }) => !this.em.canPopulate(entityName, field));
@@ -140,17 +140,22 @@ export class EntityLoader {
140
140
  const innerOrderBy = Utils.asArray(options.orderBy)
141
141
  .filter(orderBy => (Array.isArray(orderBy[prop.name]) && orderBy[prop.name].length > 0) || Utils.isObject(orderBy[prop.name]))
142
142
  .flatMap(orderBy => orderBy[prop.name]);
143
+ const where = await this.extractChildCondition(options, prop);
143
144
  if (prop.kind === ReferenceKind.MANY_TO_MANY && this.driver.getPlatform().usesPivotTable()) {
144
- return this.findChildrenFromPivotTable(filtered, prop, options, innerOrderBy, populate, !!ref);
145
+ const res = await this.findChildrenFromPivotTable(filtered, prop, options, innerOrderBy, populate, !!ref);
146
+ return Utils.flatten(res);
145
147
  }
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;
148
+ const { items, partial } = await this.findChildren(options.filtered ?? entities, prop, populate, {
149
+ ...options,
150
+ where,
151
+ orderBy: innerOrderBy,
152
+ }, !!(ref || prop.mapToPk));
153
+ this.initializeCollections(filtered, prop, field, items, innerOrderBy.length > 0, partial);
154
+ return items;
150
155
  }
151
156
  async populateScalar(meta, filtered, options) {
152
157
  const pk = Utils.getPrimaryKeyHash(meta.primaryKeys);
153
- const ids = Utils.unique(filtered.map(e => Utils.getPrimaryKeyValues(e, meta.primaryKeys, true)));
158
+ const ids = Utils.unique(filtered.map(e => Utils.getPrimaryKeyValues(e, meta, true)));
154
159
  const where = this.mergePrimaryCondition(ids, pk, options, meta, this.metadata, this.driver.getPlatform());
155
160
  const { filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging, fields } = options;
156
161
  await this.em.find(meta.className, where, {
@@ -159,15 +164,15 @@ export class EntityLoader {
159
164
  populate: [],
160
165
  });
161
166
  }
162
- initializeCollections(filtered, prop, field, children, customOrder) {
167
+ initializeCollections(filtered, prop, field, children, customOrder, partial) {
163
168
  if (prop.kind === ReferenceKind.ONE_TO_MANY) {
164
- this.initializeOneToMany(filtered, children, prop, field);
169
+ this.initializeOneToMany(filtered, children, prop, field, partial);
165
170
  }
166
171
  if (prop.kind === ReferenceKind.MANY_TO_MANY && !this.driver.getPlatform().usesPivotTable()) {
167
- this.initializeManyToMany(filtered, children, prop, field, customOrder);
172
+ this.initializeManyToMany(filtered, children, prop, field, customOrder, partial);
168
173
  }
169
174
  }
170
- initializeOneToMany(filtered, children, prop, field) {
175
+ initializeOneToMany(filtered, children, prop, field, partial) {
171
176
  const mapToPk = prop.targetMeta.properties[prop.mappedBy].mapToPk;
172
177
  const map = {};
173
178
  for (const entity of filtered) {
@@ -183,14 +188,14 @@ export class EntityLoader {
183
188
  }
184
189
  for (const entity of filtered) {
185
190
  const key = helper(entity).getSerializedPrimaryKey();
186
- entity[field].hydrate(map[key]);
191
+ entity[field].hydrate(map[key], undefined, partial);
187
192
  }
188
193
  }
189
- initializeManyToMany(filtered, children, prop, field, customOrder) {
194
+ initializeManyToMany(filtered, children, prop, field, customOrder, partial) {
190
195
  if (prop.mappedBy) {
191
196
  for (const entity of filtered) {
192
197
  const items = children.filter(child => child[prop.mappedBy].contains(entity, false));
193
- entity[field].hydrate(items, true);
198
+ entity[field].hydrate(items, true, partial);
194
199
  }
195
200
  }
196
201
  else { // owning side of M:N without pivot table needs to be reordered
@@ -200,7 +205,7 @@ export class EntityLoader {
200
205
  if (!customOrder) {
201
206
  items.sort((a, b) => order.indexOf(a) - order.indexOf(b));
202
207
  }
203
- entity[field].hydrate(items, true);
208
+ entity[field].hydrate(items, true, partial);
204
209
  }
205
210
  }
206
211
  }
@@ -209,6 +214,7 @@ export class EntityLoader {
209
214
  const meta = prop.targetMeta;
210
215
  let fk = Utils.getPrimaryKeyHash(meta.primaryKeys);
211
216
  let schema = options.schema;
217
+ const partial = !Utils.isEmpty(prop.where) || !Utils.isEmpty(options.where);
212
218
  if (prop.kind === ReferenceKind.ONE_TO_MANY || (prop.kind === ReferenceKind.MANY_TO_MANY && !prop.owner)) {
213
219
  fk = meta.properties[prop.mappedBy].name;
214
220
  }
@@ -218,7 +224,7 @@ export class EntityLoader {
218
224
  children.push(...this.filterByReferences(entities, prop.name, options.refresh));
219
225
  }
220
226
  if (children.length === 0) {
221
- return [];
227
+ return { items: [], partial };
222
228
  }
223
229
  if (!schema && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind)) {
224
230
  schema = children.find(e => e.__helper.__schema)?.__helper.__schema;
@@ -227,7 +233,7 @@ export class EntityLoader {
227
233
  let where = this.mergePrimaryCondition(ids, fk, options, meta, this.metadata, this.driver.getPlatform());
228
234
  const fields = this.buildFields(options.fields, prop, ref);
229
235
  /* eslint-disable prefer-const */
230
- let { refresh, filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging, } = options;
236
+ let { refresh, filters, convertCustomTypes, lockMode, strategy, populateWhere = 'infer', connectionType, logging, } = options;
231
237
  /* eslint-enable prefer-const */
232
238
  if (typeof populateWhere === 'object') {
233
239
  populateWhere = await this.extractChildCondition({ where: populateWhere }, prop);
@@ -249,9 +255,13 @@ export class EntityLoader {
249
255
  }
250
256
  }
251
257
  }
258
+ const orderBy = [...Utils.asArray(options.orderBy), ...propOrderBy].filter((order, idx, array) => {
259
+ // skip consecutive ordering with the same key to get around mongo issues
260
+ return idx === 0 || !Utils.equals(Object.keys(array[idx - 1]), Object.keys(order));
261
+ });
252
262
  const items = await this.em.find(prop.type, where, {
253
263
  filters, convertCustomTypes, lockMode, populateWhere, logging,
254
- orderBy: [...Utils.asArray(options.orderBy), ...propOrderBy],
264
+ orderBy,
255
265
  populate: populate.children ?? populate.all ?? [],
256
266
  exclude: Array.isArray(options.exclude) ? Utils.extractChildElements(options.exclude, prop.name) : options.exclude,
257
267
  strategy, fields, schema, connectionType,
@@ -267,7 +277,7 @@ export class EntityLoader {
267
277
  this.em.getUnitOfWork()['loadedEntities'].delete(item);
268
278
  }
269
279
  }
270
- return items;
280
+ return { items, partial };
271
281
  }
272
282
  mergePrimaryCondition(ids, pk, options, meta, metadata, platform) {
273
283
  const cond1 = QueryHelper.processWhere({ where: { [pk]: { $in: ids } }, entityName: meta.className, metadata, platform, convertCustomTypes: !options.convertCustomTypes });
@@ -310,10 +320,18 @@ export class EntityLoader {
310
320
  const innerOrderBy = Utils.asArray(options.orderBy)
311
321
  .filter(orderBy => Utils.isObject(orderBy[prop.name]))
312
322
  .map(orderBy => orderBy[prop.name]);
313
- const { refresh, filters, ignoreLazyScalarProperties, populateWhere, connectionType, logging } = options;
323
+ const { refresh, filters, ignoreLazyScalarProperties, populateWhere, connectionType, logging, schema } = options;
314
324
  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, {
325
+ const visited = options.visited;
326
+ for (const entity of entities) {
327
+ visited.delete(entity);
328
+ }
329
+ const unique = Utils.unique(children);
330
+ const filtered = unique.filter(e => !visited.has(e));
331
+ for (const entity of entities) {
332
+ visited.add(entity);
333
+ }
334
+ await this.populate(prop.type, unique, populate.children ?? populate.all, {
317
335
  where: await this.extractChildCondition(options, prop, false),
318
336
  orderBy: innerOrderBy,
319
337
  fields,
@@ -325,12 +343,16 @@ export class EntityLoader {
325
343
  populateWhere,
326
344
  connectionType,
327
345
  logging,
346
+ schema,
328
347
  // @ts-ignore not a public option, will be propagated to the populate call
329
348
  refresh: refresh && !filtered.every(item => options.visited.has(item)),
330
349
  // @ts-ignore not a public option, will be propagated to the populate call
331
350
  visited: options.visited,
351
+ // @ts-ignore not a public option
352
+ filtered,
332
353
  });
333
354
  }
355
+ /** @internal */
334
356
  async findChildrenFromPivotTable(filtered, prop, options, orderBy, populate, pivotJoin) {
335
357
  const ids = filtered.map(e => e.__helper.__primaryKeys);
336
358
  const refresh = options.refresh;
@@ -368,7 +390,7 @@ export class EntityLoader {
368
390
  return this.em.getUnitOfWork().register(entity, item, { refresh, loaded: true });
369
391
  });
370
392
  entity[prop.name].hydrate(items, true);
371
- children.push(...items);
393
+ children.push(items);
372
394
  }
373
395
  return children;
374
396
  }
@@ -444,23 +466,20 @@ export class EntityLoader {
444
466
  }
445
467
  getChildReferences(entities, prop, options, ref) {
446
468
  const filtered = this.filterCollections(entities, prop.name, options, ref);
447
- const children = [];
448
469
  if (prop.kind === ReferenceKind.ONE_TO_MANY) {
449
- children.push(...filtered.map(e => e[prop.name].owner));
470
+ return filtered.map(e => e[prop.name].owner);
450
471
  }
451
- else if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner) {
452
- children.push(...filtered.reduce((a, b) => {
472
+ if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner) {
473
+ return filtered.reduce((a, b) => {
453
474
  a.push(...b[prop.name].getItems());
454
475
  return a;
455
- }, []));
476
+ }, []);
456
477
  }
457
- else if (prop.kind === ReferenceKind.MANY_TO_MANY) { // inverse side
458
- children.push(...filtered);
478
+ if (prop.kind === ReferenceKind.MANY_TO_MANY) { // inverse side
479
+ return filtered;
459
480
  }
460
- else { // MANY_TO_ONE or ONE_TO_ONE
461
- children.push(...this.filterReferences(entities, prop.name, options, ref));
462
- }
463
- return children;
481
+ // MANY_TO_ONE or ONE_TO_ONE
482
+ return this.filterReferences(entities, prop.name, options, ref);
464
483
  }
465
484
  filterCollections(entities, field, options, ref) {
466
485
  if (options.refresh) {
@@ -514,7 +533,7 @@ export class EntityLoader {
514
533
  if (refresh) {
515
534
  return entities;
516
535
  }
517
- return entities.filter(e => !e[field]?.__helper?.__initialized);
536
+ return entities.filter(e => e[field] !== null && !e[field]?.__helper?.__initialized);
518
537
  }
519
538
  lookupAllRelationships(entityName) {
520
539
  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,5 +1,5 @@
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;
@@ -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;
@@ -72,15 +77,11 @@ export interface LoadReferenceOrFailOptions<T extends object, P extends string =
72
77
  /**
73
78
  * shortcut for `wrap(entity).toReference()`
74
79
  */
75
- export declare function ref<T>(entity: T | Ref<T>): Ref<T> & LoadedReference<Loaded<T, AddEager<T>>>;
80
+ 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
81
  /**
77
82
  * shortcut for `Reference.createFromPK(entityType, pk)`
78
83
  */
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>>>;
84
+ 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
85
  /**
85
86
  * shortcut for `Reference.createNakedFromPK(entityType, pk)`
86
87
  */
@@ -2,6 +2,7 @@ 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 { NotFoundError } from '../errors.js';
5
6
  export class Reference {
6
7
  entity;
7
8
  constructor(entity) {
@@ -33,10 +34,15 @@ export class Reference {
33
34
  }
34
35
  static createFromPK(entityType, pk, options) {
35
36
  const ref = this.createNakedFromPK(entityType, pk, options);
36
- return helper(ref).toReference();
37
+ return helper(ref)?.toReference() ?? ref;
37
38
  }
38
39
  static createNakedFromPK(entityType, pk, options) {
39
40
  const factory = entityType.prototype.__factory;
41
+ if (!factory) {
42
+ // this can happen only if `ref()` is used as a property initializer, and the value is important only for the
43
+ // inference of defaults, so it's fine to return it directly without wrapping with `Reference` class
44
+ return pk;
45
+ }
40
46
  const entity = factory.createReference(entityType, pk, {
41
47
  merge: false,
42
48
  convertCustomTypes: false,
@@ -171,6 +177,21 @@ export class ScalarReference {
171
177
  }
172
178
  return this.value;
173
179
  }
180
+ /**
181
+ * Ensures the underlying entity is loaded first (without reloading it if it already is loaded).
182
+ * Returns the entity or throws an error just like `em.findOneOrFail()` (and respects the same config options).
183
+ */
184
+ async loadOrFail(options = {}) {
185
+ const ret = await this.load(options);
186
+ if (!ret) {
187
+ const wrapped = helper(this.entity);
188
+ options.failHandler ??= wrapped.__em.config.get('findOneOrFailHandler');
189
+ const entityName = this.entity.constructor.name;
190
+ const where = wrapped.getPrimaryKey();
191
+ throw new NotFoundError(`${entityName} (${where}) failed to load property '${this.property}'`);
192
+ }
193
+ return ret;
194
+ }
174
195
  set(value) {
175
196
  this.value = value;
176
197
  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]() {