@mikro-orm/core 7.0.0-dev.22 → 7.0.0-dev.24

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 (70) hide show
  1. package/EntityManager.d.ts +12 -2
  2. package/EntityManager.js +40 -53
  3. package/README.md +1 -2
  4. package/connections/Connection.d.ts +4 -2
  5. package/connections/Connection.js +2 -2
  6. package/decorators/Entity.d.ts +15 -0
  7. package/decorators/Indexed.d.ts +2 -2
  8. package/decorators/ManyToMany.d.ts +2 -0
  9. package/decorators/ManyToOne.d.ts +2 -0
  10. package/decorators/OneToOne.d.ts +2 -0
  11. package/decorators/Transactional.d.ts +1 -0
  12. package/decorators/Transactional.js +3 -3
  13. package/drivers/IDatabaseDriver.d.ts +4 -0
  14. package/entity/ArrayCollection.d.ts +3 -1
  15. package/entity/ArrayCollection.js +7 -2
  16. package/entity/Collection.js +3 -2
  17. package/entity/EntityFactory.d.ts +6 -0
  18. package/entity/EntityFactory.js +17 -6
  19. package/entity/EntityHelper.js +5 -1
  20. package/entity/EntityLoader.js +27 -19
  21. package/entity/Reference.d.ts +5 -0
  22. package/entity/Reference.js +16 -0
  23. package/entity/WrappedEntity.js +1 -1
  24. package/entity/defineEntity.d.ts +537 -0
  25. package/entity/defineEntity.js +690 -0
  26. package/entity/index.d.ts +2 -0
  27. package/entity/index.js +2 -0
  28. package/entity/utils.d.ts +7 -0
  29. package/entity/utils.js +15 -3
  30. package/enums.d.ts +15 -2
  31. package/enums.js +13 -0
  32. package/errors.d.ts +6 -0
  33. package/errors.js +14 -0
  34. package/hydration/ObjectHydrator.js +1 -1
  35. package/index.d.ts +1 -1
  36. package/metadata/EntitySchema.js +10 -2
  37. package/metadata/MetadataDiscovery.d.ts +0 -1
  38. package/metadata/MetadataDiscovery.js +27 -18
  39. package/package.json +3 -3
  40. package/platforms/Platform.d.ts +3 -1
  41. package/serialization/SerializationContext.js +13 -10
  42. package/types/BooleanType.d.ts +1 -1
  43. package/types/DecimalType.js +1 -1
  44. package/types/DoubleType.js +1 -1
  45. package/typings.d.ts +32 -10
  46. package/typings.js +21 -4
  47. package/unit-of-work/ChangeSetComputer.js +3 -1
  48. package/unit-of-work/ChangeSetPersister.d.ts +4 -2
  49. package/unit-of-work/ChangeSetPersister.js +14 -10
  50. package/unit-of-work/UnitOfWork.d.ts +2 -1
  51. package/unit-of-work/UnitOfWork.js +36 -13
  52. package/utils/Configuration.d.ts +7 -1
  53. package/utils/Configuration.js +1 -0
  54. package/utils/ConfigurationLoader.js +2 -2
  55. package/utils/Cursor.js +3 -0
  56. package/utils/DataloaderUtils.d.ts +6 -1
  57. package/utils/DataloaderUtils.js +37 -20
  58. package/utils/EntityComparator.d.ts +6 -2
  59. package/utils/EntityComparator.js +29 -8
  60. package/utils/QueryHelper.d.ts +6 -0
  61. package/utils/QueryHelper.js +47 -4
  62. package/utils/RawQueryFragment.d.ts +34 -0
  63. package/utils/RawQueryFragment.js +35 -1
  64. package/utils/TransactionManager.d.ts +65 -0
  65. package/utils/TransactionManager.js +199 -0
  66. package/utils/Utils.d.ts +2 -2
  67. package/utils/Utils.js +31 -7
  68. package/utils/index.d.ts +1 -0
  69. package/utils/index.js +1 -0
  70. package/utils/upsert-utils.js +9 -1
@@ -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,18 +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
145
  const res = await this.findChildrenFromPivotTable(filtered, prop, options, innerOrderBy, populate, !!ref);
145
146
  return Utils.flatten(res);
146
147
  }
147
- const where = await this.extractChildCondition(options, prop);
148
- const data = await this.findChildren(entities, prop, populate, { ...options, where, orderBy: innerOrderBy }, !!(ref || prop.mapToPk));
149
- this.initializeCollections(filtered, prop, field, data, innerOrderBy.length > 0);
150
- 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;
151
155
  }
152
156
  async populateScalar(meta, filtered, options) {
153
157
  const pk = Utils.getPrimaryKeyHash(meta.primaryKeys);
154
- 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)));
155
159
  const where = this.mergePrimaryCondition(ids, pk, options, meta, this.metadata, this.driver.getPlatform());
156
160
  const { filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging, fields } = options;
157
161
  await this.em.find(meta.className, where, {
@@ -160,15 +164,15 @@ export class EntityLoader {
160
164
  populate: [],
161
165
  });
162
166
  }
163
- initializeCollections(filtered, prop, field, children, customOrder) {
167
+ initializeCollections(filtered, prop, field, children, customOrder, partial) {
164
168
  if (prop.kind === ReferenceKind.ONE_TO_MANY) {
165
- this.initializeOneToMany(filtered, children, prop, field);
169
+ this.initializeOneToMany(filtered, children, prop, field, partial);
166
170
  }
167
171
  if (prop.kind === ReferenceKind.MANY_TO_MANY && !this.driver.getPlatform().usesPivotTable()) {
168
- this.initializeManyToMany(filtered, children, prop, field, customOrder);
172
+ this.initializeManyToMany(filtered, children, prop, field, customOrder, partial);
169
173
  }
170
174
  }
171
- initializeOneToMany(filtered, children, prop, field) {
175
+ initializeOneToMany(filtered, children, prop, field, partial) {
172
176
  const mapToPk = prop.targetMeta.properties[prop.mappedBy].mapToPk;
173
177
  const map = {};
174
178
  for (const entity of filtered) {
@@ -184,14 +188,14 @@ export class EntityLoader {
184
188
  }
185
189
  for (const entity of filtered) {
186
190
  const key = helper(entity).getSerializedPrimaryKey();
187
- entity[field].hydrate(map[key]);
191
+ entity[field].hydrate(map[key], undefined, partial);
188
192
  }
189
193
  }
190
- initializeManyToMany(filtered, children, prop, field, customOrder) {
194
+ initializeManyToMany(filtered, children, prop, field, customOrder, partial) {
191
195
  if (prop.mappedBy) {
192
196
  for (const entity of filtered) {
193
197
  const items = children.filter(child => child[prop.mappedBy].contains(entity, false));
194
- entity[field].hydrate(items, true);
198
+ entity[field].hydrate(items, true, partial);
195
199
  }
196
200
  }
197
201
  else { // owning side of M:N without pivot table needs to be reordered
@@ -201,7 +205,7 @@ export class EntityLoader {
201
205
  if (!customOrder) {
202
206
  items.sort((a, b) => order.indexOf(a) - order.indexOf(b));
203
207
  }
204
- entity[field].hydrate(items, true);
208
+ entity[field].hydrate(items, true, partial);
205
209
  }
206
210
  }
207
211
  }
@@ -210,6 +214,7 @@ export class EntityLoader {
210
214
  const meta = prop.targetMeta;
211
215
  let fk = Utils.getPrimaryKeyHash(meta.primaryKeys);
212
216
  let schema = options.schema;
217
+ const partial = !Utils.isEmpty(prop.where) || !Utils.isEmpty(options.where);
213
218
  if (prop.kind === ReferenceKind.ONE_TO_MANY || (prop.kind === ReferenceKind.MANY_TO_MANY && !prop.owner)) {
214
219
  fk = meta.properties[prop.mappedBy].name;
215
220
  }
@@ -219,7 +224,7 @@ export class EntityLoader {
219
224
  children.push(...this.filterByReferences(entities, prop.name, options.refresh));
220
225
  }
221
226
  if (children.length === 0) {
222
- return [];
227
+ return { items: [], partial };
223
228
  }
224
229
  if (!schema && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind)) {
225
230
  schema = children.find(e => e.__helper.__schema)?.__helper.__schema;
@@ -228,7 +233,7 @@ export class EntityLoader {
228
233
  let where = this.mergePrimaryCondition(ids, fk, options, meta, this.metadata, this.driver.getPlatform());
229
234
  const fields = this.buildFields(options.fields, prop, ref);
230
235
  /* eslint-disable prefer-const */
231
- let { refresh, filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging, } = options;
236
+ let { refresh, filters, convertCustomTypes, lockMode, strategy, populateWhere = 'infer', connectionType, logging, } = options;
232
237
  /* eslint-enable prefer-const */
233
238
  if (typeof populateWhere === 'object') {
234
239
  populateWhere = await this.extractChildCondition({ where: populateWhere }, prop);
@@ -272,7 +277,7 @@ export class EntityLoader {
272
277
  this.em.getUnitOfWork()['loadedEntities'].delete(item);
273
278
  }
274
279
  }
275
- return items;
280
+ return { items, partial };
276
281
  }
277
282
  mergePrimaryCondition(ids, pk, options, meta, metadata, platform) {
278
283
  const cond1 = QueryHelper.processWhere({ where: { [pk]: { $in: ids } }, entityName: meta.className, metadata, platform, convertCustomTypes: !options.convertCustomTypes });
@@ -321,11 +326,12 @@ export class EntityLoader {
321
326
  for (const entity of entities) {
322
327
  visited.delete(entity);
323
328
  }
324
- const filtered = Utils.unique(children.filter(e => !visited.has(e)));
329
+ const unique = Utils.unique(children);
330
+ const filtered = unique.filter(e => !visited.has(e));
325
331
  for (const entity of entities) {
326
332
  visited.add(entity);
327
333
  }
328
- await this.populate(prop.type, filtered, populate.children ?? populate.all, {
334
+ await this.populate(prop.type, unique, populate.children ?? populate.all, {
329
335
  where: await this.extractChildCondition(options, prop, false),
330
336
  orderBy: innerOrderBy,
331
337
  fields,
@@ -342,6 +348,8 @@ export class EntityLoader {
342
348
  refresh: refresh && !filtered.every(item => options.visited.has(item)),
343
349
  // @ts-ignore not a public option, will be propagated to the populate call
344
350
  visited: options.visited,
351
+ // @ts-ignore not a public option
352
+ filtered,
345
353
  });
346
354
  }
347
355
  /** @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;
@@ -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) {
@@ -171,6 +172,21 @@ export class ScalarReference {
171
172
  }
172
173
  return this.value;
173
174
  }
175
+ /**
176
+ * Ensures the underlying entity is loaded first (without reloading it if it already is loaded).
177
+ * Returns the entity or throws an error just like `em.findOneOrFail()` (and respects the same config options).
178
+ */
179
+ async loadOrFail(options = {}) {
180
+ const ret = await this.load(options);
181
+ if (!ret) {
182
+ const wrapped = helper(this.entity);
183
+ options.failHandler ??= wrapped.__em.config.get('findOneOrFailHandler');
184
+ const entityName = this.entity.constructor.name;
185
+ const where = wrapped.getPrimaryKey();
186
+ throw new NotFoundError(`${entityName} (${where}) failed to load property '${this.property}'`);
187
+ }
188
+ return ret;
189
+ }
174
190
  set(value) {
175
191
  this.value = value;
176
192
  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]() {