@mikro-orm/core 7.0.0-dev.6 → 7.0.0-dev.61

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 (123) hide show
  1. package/EntityManager.d.ts +85 -32
  2. package/EntityManager.js +281 -178
  3. package/MikroORM.d.ts +8 -8
  4. package/MikroORM.js +31 -74
  5. package/README.md +3 -2
  6. package/cache/FileCacheAdapter.d.ts +2 -1
  7. package/cache/FileCacheAdapter.js +5 -4
  8. package/connections/Connection.d.ts +11 -7
  9. package/connections/Connection.js +16 -13
  10. package/decorators/Embeddable.d.ts +2 -0
  11. package/decorators/Embedded.d.ts +5 -11
  12. package/decorators/Entity.d.ts +20 -3
  13. package/decorators/Indexed.d.ts +2 -2
  14. package/decorators/ManyToMany.d.ts +2 -0
  15. package/decorators/ManyToOne.d.ts +4 -0
  16. package/decorators/OneToOne.d.ts +4 -0
  17. package/decorators/Property.d.ts +53 -9
  18. package/decorators/Transactional.d.ts +3 -1
  19. package/decorators/Transactional.js +6 -3
  20. package/decorators/index.d.ts +1 -1
  21. package/drivers/DatabaseDriver.d.ts +11 -5
  22. package/drivers/DatabaseDriver.js +13 -4
  23. package/drivers/IDatabaseDriver.d.ts +29 -5
  24. package/entity/ArrayCollection.d.ts +6 -4
  25. package/entity/ArrayCollection.js +27 -12
  26. package/entity/BaseEntity.d.ts +0 -1
  27. package/entity/BaseEntity.js +0 -3
  28. package/entity/Collection.d.ts +3 -4
  29. package/entity/Collection.js +34 -17
  30. package/entity/EntityAssigner.d.ts +1 -1
  31. package/entity/EntityAssigner.js +9 -1
  32. package/entity/EntityFactory.d.ts +7 -0
  33. package/entity/EntityFactory.js +63 -40
  34. package/entity/EntityHelper.js +26 -9
  35. package/entity/EntityLoader.d.ts +5 -4
  36. package/entity/EntityLoader.js +69 -36
  37. package/entity/EntityRepository.d.ts +1 -1
  38. package/entity/EntityValidator.js +2 -2
  39. package/entity/Reference.d.ts +9 -7
  40. package/entity/Reference.js +32 -5
  41. package/entity/WrappedEntity.d.ts +0 -2
  42. package/entity/WrappedEntity.js +1 -5
  43. package/entity/defineEntity.d.ts +555 -0
  44. package/entity/defineEntity.js +529 -0
  45. package/entity/index.d.ts +2 -0
  46. package/entity/index.js +2 -0
  47. package/entity/utils.d.ts +7 -0
  48. package/entity/utils.js +15 -3
  49. package/enums.d.ts +18 -5
  50. package/enums.js +13 -0
  51. package/errors.d.ts +6 -1
  52. package/errors.js +14 -4
  53. package/events/EventSubscriber.d.ts +3 -1
  54. package/hydration/ObjectHydrator.d.ts +4 -4
  55. package/hydration/ObjectHydrator.js +35 -24
  56. package/index.d.ts +2 -1
  57. package/index.js +1 -1
  58. package/logging/DefaultLogger.d.ts +1 -1
  59. package/logging/SimpleLogger.d.ts +1 -1
  60. package/metadata/EntitySchema.d.ts +8 -4
  61. package/metadata/EntitySchema.js +41 -23
  62. package/metadata/MetadataDiscovery.d.ts +5 -7
  63. package/metadata/MetadataDiscovery.js +151 -159
  64. package/metadata/MetadataStorage.js +1 -1
  65. package/metadata/MetadataValidator.js +4 -3
  66. package/metadata/discover-entities.d.ts +5 -0
  67. package/metadata/discover-entities.js +39 -0
  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 +14 -8
  72. package/platforms/Platform.d.ts +5 -8
  73. package/platforms/Platform.js +4 -17
  74. package/serialization/EntitySerializer.d.ts +2 -0
  75. package/serialization/EntitySerializer.js +29 -11
  76. package/serialization/EntityTransformer.js +22 -12
  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/BlobType.d.ts +0 -1
  81. package/types/BlobType.js +0 -3
  82. package/types/BooleanType.d.ts +2 -1
  83. package/types/BooleanType.js +3 -0
  84. package/types/DecimalType.d.ts +6 -4
  85. package/types/DecimalType.js +1 -1
  86. package/types/DoubleType.js +1 -1
  87. package/types/JsonType.d.ts +1 -1
  88. package/types/JsonType.js +7 -2
  89. package/types/Type.d.ts +2 -1
  90. package/types/Type.js +1 -1
  91. package/types/Uint8ArrayType.d.ts +0 -1
  92. package/types/Uint8ArrayType.js +0 -3
  93. package/types/index.d.ts +1 -1
  94. package/typings.d.ts +95 -52
  95. package/typings.js +31 -31
  96. package/unit-of-work/ChangeSetComputer.js +8 -3
  97. package/unit-of-work/ChangeSetPersister.d.ts +4 -2
  98. package/unit-of-work/ChangeSetPersister.js +37 -16
  99. package/unit-of-work/UnitOfWork.d.ts +8 -1
  100. package/unit-of-work/UnitOfWork.js +110 -53
  101. package/utils/AbstractSchemaGenerator.js +3 -1
  102. package/utils/Configuration.d.ts +201 -184
  103. package/utils/Configuration.js +143 -151
  104. package/utils/ConfigurationLoader.d.ts +9 -22
  105. package/utils/ConfigurationLoader.js +53 -76
  106. package/utils/Cursor.d.ts +3 -3
  107. package/utils/Cursor.js +3 -0
  108. package/utils/DataloaderUtils.d.ts +15 -5
  109. package/utils/DataloaderUtils.js +53 -7
  110. package/utils/EntityComparator.d.ts +8 -4
  111. package/utils/EntityComparator.js +105 -58
  112. package/utils/QueryHelper.d.ts +9 -1
  113. package/utils/QueryHelper.js +66 -5
  114. package/utils/RawQueryFragment.d.ts +36 -4
  115. package/utils/RawQueryFragment.js +34 -13
  116. package/utils/TransactionManager.d.ts +65 -0
  117. package/utils/TransactionManager.js +223 -0
  118. package/utils/Utils.d.ts +16 -31
  119. package/utils/Utils.js +129 -107
  120. package/utils/index.d.ts +1 -0
  121. package/utils/index.js +1 -0
  122. package/utils/upsert-utils.d.ts +7 -2
  123. package/utils/upsert-utils.js +52 -1
@@ -4,6 +4,7 @@ import { EventType, ReferenceKind } from '../enums.js';
4
4
  import { Reference } from './Reference.js';
5
5
  import { helper } from './wrap.js';
6
6
  import { EntityHelper } from './EntityHelper.js';
7
+ import { JsonType } from '../types/JsonType.js';
7
8
  export class EntityFactory {
8
9
  em;
9
10
  driver;
@@ -37,14 +38,15 @@ export class EntityFactory {
37
38
  this.hydrate(entity, meta, data, options);
38
39
  return entity;
39
40
  }
40
- if (this.platform.usesDifferentSerializedPrimaryKey()) {
41
- meta.primaryKeys.forEach(pk => this.denormalizePrimaryKey(data, pk, meta.properties[pk]));
41
+ if (meta.serializedPrimaryKey) {
42
+ this.denormalizePrimaryKey(meta, data);
42
43
  }
43
44
  const meta2 = this.processDiscriminatorColumn(meta, data);
44
45
  const exists = this.findEntity(data, meta2, options);
45
46
  let wrapped = exists && helper(exists);
46
47
  if (wrapped && !options.refresh) {
47
48
  wrapped.__processing = true;
49
+ Utils.dropUndefinedProperties(data);
48
50
  this.mergeData(meta2, exists, data, options);
49
51
  wrapped.__processing = false;
50
52
  if (wrapped.isInitialized()) {
@@ -58,7 +60,7 @@ export class EntityFactory {
58
60
  wrapped.__initialized = options.initialized;
59
61
  if (options.newEntity || meta.forceConstructor || meta.virtual) {
60
62
  const tmp = { ...data };
61
- meta.constructorParams.forEach(prop => delete tmp[prop]);
63
+ meta.constructorParams?.forEach(prop => delete tmp[prop]);
62
64
  this.hydrate(entity, meta2, tmp, options);
63
65
  // since we now process only a copy of the `data` via hydrator, but later we register the state with the full snapshot,
64
66
  // we need to go through all props with custom types that have `ensureComparable: true` and ensure they are comparable
@@ -70,9 +72,11 @@ export class EntityFactory {
70
72
  continue;
71
73
  }
72
74
  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);
75
+ data[prop.name] = Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta, true);
76
+ }
77
+ if (prop.customType instanceof JsonType && this.platform.convertsJsonAutomatically()) {
78
+ data[prop.name] = prop.customType.convertToDatabaseValue(data[prop.name], this.platform, { key: prop.name, mode: 'hydration' });
74
79
  }
75
- data[prop.name] = prop.customType.convertToDatabaseValue(data[prop.name], this.platform, { key: prop.name, mode: 'hydration' });
76
80
  }
77
81
  }
78
82
  }
@@ -80,7 +84,6 @@ export class EntityFactory {
80
84
  else {
81
85
  this.hydrate(entity, meta2, data, options);
82
86
  }
83
- wrapped.__touched = false;
84
87
  if (exists && meta.discriminatorColumn && !(entity instanceof meta2.class)) {
85
88
  Object.setPrototypeOf(entity, meta2.prototype);
86
89
  }
@@ -112,7 +115,7 @@ export class EntityFactory {
112
115
  if (meta.versionProperty && data[meta.versionProperty] && data[meta.versionProperty] !== originalEntityData[meta.versionProperty]) {
113
116
  diff[meta.versionProperty] = data[meta.versionProperty];
114
117
  }
115
- const diff2 = this.comparator.diffEntities(meta.className, existsData, data);
118
+ const diff2 = this.comparator.diffEntities(meta.className, existsData, data, { includeInverseSides: true });
116
119
  // do not override values changed by user
117
120
  Utils.keys(diff).forEach(key => delete diff2[key]);
118
121
  Utils.keys(diff2).filter(key => {
@@ -135,6 +138,10 @@ export class EntityFactory {
135
138
  if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && Utils.isPlainObject(data[prop.name])) {
136
139
  diff2[key] = entity[prop.name] ? helper(entity[prop.name]).getPrimaryKey(options.convertCustomTypes) : null;
137
140
  }
141
+ if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE, ReferenceKind.SCALAR].includes(prop.kind) && prop.customType?.ensureComparable(meta, prop) && diff2[key] != null) {
142
+ const converted = prop.customType.convertToJSValue(diff2[key], this.platform, { force: true });
143
+ diff2[key] = prop.customType.convertToDatabaseValue(converted, this.platform, { fromQuery: true });
144
+ }
138
145
  originalEntityData[key] = diff2[key] === null ? nullVal : diff2[key];
139
146
  helper(entity).__loadedProperties.add(key);
140
147
  });
@@ -152,7 +159,7 @@ export class EntityFactory {
152
159
  this.create(prop.type, data[prop.name], options); // we can ignore the value, we just care about the `mergeData` call
153
160
  }
154
161
  });
155
- helper(entity).__touched = false;
162
+ this.unitOfWork.normalizeEntityData(meta, originalEntityData);
156
163
  }
157
164
  createReference(entityName, id, options = {}) {
158
165
  options.convertCustomTypes ??= true;
@@ -170,8 +177,8 @@ export class EntityFactory {
170
177
  if (Array.isArray(id)) {
171
178
  id = Utils.getPrimaryKeyCondFromArray(id, meta);
172
179
  }
173
- const pks = Utils.getOrderedPrimaryKeys(id, meta, this.platform, options.convertCustomTypes);
174
- const exists = this.unitOfWork.getById(entityName, pks, schema);
180
+ const pks = Utils.getOrderedPrimaryKeys(id, meta, this.platform);
181
+ const exists = this.unitOfWork.getById(entityName, pks, schema, options.convertCustomTypes);
175
182
  if (exists) {
176
183
  return exists;
177
184
  }
@@ -230,17 +237,28 @@ export class EntityFactory {
230
237
  }
231
238
  return entity;
232
239
  }
240
+ assignDefaultValues(entity, meta) {
241
+ for (const prop of meta.props) {
242
+ if (prop.onCreate) {
243
+ entity[prop.name] ??= prop.onCreate(entity, this.em);
244
+ }
245
+ }
246
+ }
233
247
  hydrate(entity, meta, data, options) {
234
248
  if (options.initialized) {
235
- this.hydrator.hydrate(entity, meta, data, this, 'full', options.newEntity, options.convertCustomTypes, options.schema, this.driver.getSchemaName(meta, options));
249
+ this.hydrator.hydrate(entity, meta, data, this, 'full', options.newEntity, options.convertCustomTypes, options.schema, this.driver.getSchemaName(meta, options), options.normalizeAccessors);
236
250
  }
237
251
  else {
238
- this.hydrator.hydrateReference(entity, meta, data, this, options.convertCustomTypes, options.schema, this.driver.getSchemaName(meta, options));
252
+ this.hydrator.hydrateReference(entity, meta, data, this, options.convertCustomTypes, options.schema, this.driver.getSchemaName(meta, options), options.normalizeAccessors);
239
253
  }
240
254
  Utils.keys(data).forEach(key => {
241
255
  helper(entity)?.__loadedProperties.add(key);
242
256
  helper(entity)?.__serializationContext.fields?.add(key);
243
257
  });
258
+ const processOnCreateHooksEarly = options.processOnCreateHooksEarly ?? this.config.get('processOnCreateHooksEarly');
259
+ if (options.newEntity && processOnCreateHooksEarly) {
260
+ this.assignDefaultValues(entity, meta);
261
+ }
244
262
  }
245
263
  findEntity(data, meta, options) {
246
264
  const schema = this.driver.getSchemaName(meta, options);
@@ -250,7 +268,7 @@ export class EntityFactory {
250
268
  if (!Array.isArray(data) && meta.primaryKeys.some(pk => data[pk] == null)) {
251
269
  return undefined;
252
270
  }
253
- const pks = Utils.getOrderedPrimaryKeys(data, meta, this.platform);
271
+ const pks = Utils.getOrderedPrimaryKeys(data, meta, this.platform, options.convertCustomTypes);
254
272
  return this.unitOfWork.getById(meta.className, pks, schema);
255
273
  }
256
274
  processDiscriminatorColumn(meta, data) {
@@ -266,47 +284,52 @@ export class EntityFactory {
266
284
  /**
267
285
  * denormalize PK to value required by driver (e.g. ObjectId)
268
286
  */
269
- denormalizePrimaryKey(data, primaryKey, prop) {
270
- const pk = this.platform.getSerializedPrimaryKeyField(primaryKey);
271
- if (data[pk] != null || data[primaryKey] != null) {
272
- let id = (data[pk] || data[primaryKey]);
273
- if (prop.type.toLowerCase() === 'objectid') {
274
- id = this.platform.denormalizePrimaryKey(id);
275
- }
276
- delete data[pk];
277
- data[primaryKey] = id;
287
+ denormalizePrimaryKey(meta, data) {
288
+ const pk = meta.getPrimaryProp();
289
+ const spk = meta.properties[meta.serializedPrimaryKey];
290
+ if (!spk?.serializedPrimaryKey) {
291
+ return;
292
+ }
293
+ if (pk.type.toLowerCase() === 'objectid' && (data[pk.name] != null || data[spk.name] != null)) {
294
+ data[pk.name] = this.platform.denormalizePrimaryKey((data[spk.name] || data[pk.name]));
295
+ delete data[spk.name];
278
296
  }
279
297
  }
280
298
  /**
281
299
  * returns parameters for entity constructor, creating references from plain ids
282
300
  */
283
301
  extractConstructorParams(meta, data, options) {
302
+ if (!meta.constructorParams) {
303
+ return [data];
304
+ }
284
305
  return meta.constructorParams.map(k => {
285
- if (meta.properties[k] && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(meta.properties[k].kind) && data[k]) {
286
- const pk = Reference.unwrapReference(data[k]);
287
- const entity = this.unitOfWork.getById(meta.properties[k].type, pk, options.schema);
306
+ const prop = meta.properties[k];
307
+ const value = data[k];
308
+ if (prop && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && value) {
309
+ const pk = Reference.unwrapReference(value);
310
+ const entity = this.unitOfWork.getById(prop.type, pk, options.schema, true);
288
311
  if (entity) {
289
312
  return entity;
290
313
  }
291
- if (Utils.isEntity(data[k])) {
292
- return data[k];
314
+ if (Utils.isEntity(value)) {
315
+ return value;
293
316
  }
294
- const nakedPk = Utils.extractPK(data[k], meta.properties[k].targetMeta, true);
295
- if (Utils.isObject(data[k]) && !nakedPk) {
296
- return this.create(meta.properties[k].type, data[k], options);
317
+ const nakedPk = Utils.extractPK(value, prop.targetMeta, true);
318
+ if (Utils.isObject(value) && !nakedPk) {
319
+ return this.create(prop.type, value, options);
297
320
  }
298
321
  const { newEntity, initialized, ...rest } = options;
299
- const target = this.createReference(meta.properties[k].type, nakedPk, rest);
300
- return Reference.wrapReference(target, meta.properties[k]);
322
+ const target = this.createReference(prop.type, nakedPk, rest);
323
+ return Reference.wrapReference(target, prop);
301
324
  }
302
- if (meta.properties[k]?.kind === ReferenceKind.EMBEDDED && data[k]) {
325
+ if (prop?.kind === ReferenceKind.EMBEDDED && value) {
303
326
  /* v8 ignore next 3 */
304
- if (Utils.isEntity(data[k])) {
305
- return data[k];
327
+ if (Utils.isEntity(value)) {
328
+ return value;
306
329
  }
307
- return this.createEmbeddable(meta.properties[k].type, data[k], options);
330
+ return this.createEmbeddable(prop.type, value, options);
308
331
  }
309
- if (!meta.properties[k]) {
332
+ if (!prop) {
310
333
  const tmp = { ...data };
311
334
  for (const prop of meta.props) {
312
335
  if (!options.convertCustomTypes || !prop.customType || tmp[prop.name] == null) {
@@ -321,10 +344,10 @@ export class EntityFactory {
321
344
  }
322
345
  return tmp;
323
346
  }
324
- if (options.convertCustomTypes && meta.properties[k].customType && data[k] != null) {
325
- return meta.properties[k].customType.convertToJSValue(data[k], this.platform);
347
+ if (options.convertCustomTypes && prop.customType && value != null) {
348
+ return prop.customType.convertToJSValue(value, this.platform);
326
349
  }
327
- return data[k];
350
+ return value;
328
351
  });
329
352
  }
330
353
  get unitOfWork() {
@@ -32,7 +32,7 @@ export class EntityHelper {
32
32
  const prototype = meta.prototype;
33
33
  if (!prototype.toJSON) { // toJSON can be overridden
34
34
  prototype.toJSON = function (...args) {
35
- return EntityTransformer.toObject(this, ...args.slice(meta.toJsonParams.length));
35
+ return EntityTransformer.toObject(this, ...args);
36
36
  };
37
37
  }
38
38
  }
@@ -87,7 +87,7 @@ export class EntityHelper {
87
87
  });
88
88
  return;
89
89
  }
90
- if (prop.inherited || prop.primary || prop.persist === false || prop.trackChanges === false || prop.embedded || isCollection) {
90
+ if (prop.inherited || prop.primary || prop.accessor || prop.persist === false || prop.embedded || isCollection) {
91
91
  return;
92
92
  }
93
93
  Object.defineProperty(meta.prototype, prop.name, {
@@ -98,13 +98,11 @@ export class EntityHelper {
98
98
  },
99
99
  set(val) {
100
100
  this.__helper.__data[prop.name] = val;
101
- this.__helper.__touched = !this.__helper.hydrator.isRunning();
102
101
  },
103
102
  enumerable: true,
104
103
  configurable: true,
105
104
  });
106
105
  this.__helper.__data[prop.name] = val;
107
- this.__helper.__touched = !this.__helper.hydrator.isRunning();
108
106
  },
109
107
  configurable: true,
110
108
  });
@@ -113,7 +111,18 @@ export class EntityHelper {
113
111
  static defineCustomInspect(meta) {
114
112
  // @ts-ignore
115
113
  meta.prototype[inspect.custom] ??= function (depth = 2) {
116
- const object = { ...this };
114
+ const object = {};
115
+ const keys = new Set(Utils.keys(this));
116
+ for (const prop of meta.props) {
117
+ if (keys.has(prop.name) || (prop.getter && prop.accessor === prop.name)) {
118
+ object[prop.name] = this[prop.name];
119
+ }
120
+ }
121
+ for (const key of keys) {
122
+ if (!meta.properties[key]) {
123
+ object[key] = this[key];
124
+ }
125
+ }
117
126
  // ensure we dont have internal symbols in the POJO
118
127
  [OptionalProps, EntityRepositoryType, PrimaryKeyProp, EagerProps, HiddenProps].forEach(sym => delete object[sym]);
119
128
  meta.props
@@ -146,13 +155,13 @@ export class EntityHelper {
146
155
  set(val) {
147
156
  const entity = Reference.unwrapReference(val ?? wrapped.__data[prop.name]);
148
157
  const old = Reference.unwrapReference(wrapped.__data[prop.name]);
158
+ if (old && old !== entity && prop.kind === ReferenceKind.MANY_TO_ONE && prop.inversedBy && old[prop.inversedBy]) {
159
+ old[prop.inversedBy].removeWithoutPropagation(this);
160
+ }
149
161
  wrapped.__data[prop.name] = Reference.wrapReference(val, prop);
150
162
  // when propagation from inside hydration, we set the FK to the entity data immediately
151
163
  if (val && hydrator.isRunning() && wrapped.__originalEntityData && prop.owner) {
152
- wrapped.__originalEntityData[prop.name] = Utils.getPrimaryKeyValues(wrapped.__data[prop.name], prop.targetMeta.primaryKeys, true);
153
- }
154
- else {
155
- wrapped.__touched = !hydrator.isRunning();
164
+ wrapped.__originalEntityData[prop.name] = Utils.getPrimaryKeyValues(wrapped.__data[prop.name], prop.targetMeta, true);
156
165
  }
157
166
  EntityHelper.propagate(meta, entity, this, prop, Reference.unwrapReference(val), old);
158
167
  },
@@ -169,6 +178,13 @@ export class EntityHelper {
169
178
  continue;
170
179
  }
171
180
  const inverse = value?.[prop2.name];
181
+ if (prop.ref && owner[prop.name]) {
182
+ // eslint-disable-next-line dot-notation
183
+ owner[prop.name]['property'] = prop;
184
+ }
185
+ if (Utils.isCollection(inverse) && inverse.isPartial()) {
186
+ continue;
187
+ }
172
188
  if (prop.kind === ReferenceKind.MANY_TO_ONE && Utils.isCollection(inverse) && inverse.isInitialized()) {
173
189
  inverse.addWithoutPropagation(owner);
174
190
  helper(owner).__em?.getUnitOfWork().cancelOrphanRemoval(owner);
@@ -207,6 +223,7 @@ export class EntityHelper {
207
223
  }
208
224
  if (old?.[prop2.name] != null) {
209
225
  delete helper(old).__data[prop2.name];
226
+ old[prop2.name] = null;
210
227
  }
211
228
  }
212
229
  static ensurePropagation(entity) {
@@ -1,7 +1,7 @@
1
- import type { ConnectionType, Dictionary, FilterQuery, PopulateOptions } from '../typings.js';
1
+ import type { AnyEntity, ConnectionType, 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
- import type { EntityField } from '../drivers/IDatabaseDriver.js';
4
+ import type { EntityField, FilterOptions } from '../drivers/IDatabaseDriver.js';
5
5
  import type { LoggingOptions } from '../logging/Logger.js';
6
6
  export type EntityLoaderOptions<Entity, Fields extends string = PopulatePath.ALL, Excludes extends string = never> = {
7
7
  where?: FilterQuery<Entity>;
@@ -14,7 +14,7 @@ export type EntityLoaderOptions<Entity, Fields extends string = PopulatePath.ALL
14
14
  lookup?: boolean;
15
15
  convertCustomTypes?: boolean;
16
16
  ignoreLazyScalarProperties?: boolean;
17
- filters?: Dictionary<boolean | Dictionary> | string[] | boolean;
17
+ filters?: FilterOptions;
18
18
  strategy?: LoadStrategy;
19
19
  lockMode?: Exclude<LockMode, LockMode.OPTIMISTIC>;
20
20
  schema?: string;
@@ -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;
@@ -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);
@@ -264,6 +269,24 @@ export class EntityLoader {
264
269
  // @ts-ignore not a public option, will be propagated to the populate call
265
270
  visited: options.visited,
266
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
+ }
267
290
  for (const item of items) {
268
291
  if (ref && !helper(item).__onLoadFired) {
269
292
  helper(item).__initialized = false;
@@ -271,7 +294,7 @@ export class EntityLoader {
271
294
  this.em.getUnitOfWork()['loadedEntities'].delete(item);
272
295
  }
273
296
  }
274
- return items;
297
+ return { items, partial };
275
298
  }
276
299
  mergePrimaryCondition(ids, pk, options, meta, metadata, platform) {
277
300
  const cond1 = QueryHelper.processWhere({ where: { [pk]: { $in: ids } }, entityName: meta.className, metadata, platform, convertCustomTypes: !options.convertCustomTypes });
@@ -287,6 +310,7 @@ export class EntityLoader {
287
310
  if (prop.kind === ReferenceKind.SCALAR && !prop.lazy) {
288
311
  return;
289
312
  }
313
+ options = { ...options, filters: QueryHelper.mergePropertyFilters(prop.filters, options.filters) };
290
314
  const populated = await this.populateMany(entityName, entities, populate, options);
291
315
  if (!populate.children && !populate.all) {
292
316
  return;
@@ -314,10 +338,18 @@ export class EntityLoader {
314
338
  const innerOrderBy = Utils.asArray(options.orderBy)
315
339
  .filter(orderBy => Utils.isObject(orderBy[prop.name]))
316
340
  .map(orderBy => orderBy[prop.name]);
317
- const { refresh, filters, ignoreLazyScalarProperties, populateWhere, connectionType, logging } = options;
341
+ const { refresh, filters, ignoreLazyScalarProperties, populateWhere, connectionType, logging, schema } = options;
318
342
  const exclude = Array.isArray(options.exclude) ? Utils.extractChildElements(options.exclude, prop.name) : options.exclude;
319
- const filtered = Utils.unique(children.filter(e => !options.visited.has(e)));
320
- 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, {
321
353
  where: await this.extractChildCondition(options, prop, false),
322
354
  orderBy: innerOrderBy,
323
355
  fields,
@@ -329,12 +361,16 @@ export class EntityLoader {
329
361
  populateWhere,
330
362
  connectionType,
331
363
  logging,
364
+ schema,
332
365
  // @ts-ignore not a public option, will be propagated to the populate call
333
366
  refresh: refresh && !filtered.every(item => options.visited.has(item)),
334
367
  // @ts-ignore not a public option, will be propagated to the populate call
335
368
  visited: options.visited,
369
+ // @ts-ignore not a public option
370
+ filtered,
336
371
  });
337
372
  }
373
+ /** @internal */
338
374
  async findChildrenFromPivotTable(filtered, prop, options, orderBy, populate, pivotJoin) {
339
375
  const ids = filtered.map(e => e.__helper.__primaryKeys);
340
376
  const refresh = options.refresh;
@@ -372,7 +408,7 @@ export class EntityLoader {
372
408
  return this.em.getUnitOfWork().register(entity, item, { refresh, loaded: true });
373
409
  });
374
410
  entity[prop.name].hydrate(items, true);
375
- children.push(...items);
411
+ children.push(items);
376
412
  }
377
413
  return children;
378
414
  }
@@ -448,23 +484,20 @@ export class EntityLoader {
448
484
  }
449
485
  getChildReferences(entities, prop, options, ref) {
450
486
  const filtered = this.filterCollections(entities, prop.name, options, ref);
451
- const children = [];
452
487
  if (prop.kind === ReferenceKind.ONE_TO_MANY) {
453
- children.push(...filtered.map(e => e[prop.name].owner));
488
+ return filtered.map(e => e[prop.name].owner);
454
489
  }
455
- else if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner) {
456
- children.push(...filtered.reduce((a, b) => {
490
+ if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner) {
491
+ return filtered.reduce((a, b) => {
457
492
  a.push(...b[prop.name].getItems());
458
493
  return a;
459
- }, []));
494
+ }, []);
460
495
  }
461
- else if (prop.kind === ReferenceKind.MANY_TO_MANY) { // inverse side
462
- children.push(...filtered);
496
+ if (prop.kind === ReferenceKind.MANY_TO_MANY) { // inverse side
497
+ return filtered;
463
498
  }
464
- else { // MANY_TO_ONE or ONE_TO_ONE
465
- children.push(...this.filterReferences(entities, prop.name, options, ref));
466
- }
467
- return children;
499
+ // MANY_TO_ONE or ONE_TO_ONE
500
+ return this.filterReferences(entities, prop.name, options, ref);
468
501
  }
469
502
  filterCollections(entities, field, options, ref) {
470
503
  if (options.refresh) {
@@ -518,7 +551,7 @@ export class EntityLoader {
518
551
  if (refresh) {
519
552
  return entities;
520
553
  }
521
- return entities.filter(e => !e[field]?.__helper?.__initialized);
554
+ return entities.filter(e => e[field] !== null && !e[field]?.__helper?.__initialized);
522
555
  }
523
556
  lookupAllRelationships(entityName) {
524
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)) {
@@ -89,7 +89,7 @@ export class EntityValidator {
89
89
  }
90
90
  }
91
91
  validatePrimaryKey(entity, meta) {
92
- const pkExists = meta.primaryKeys.every(pk => entity[pk] != null) || entity[meta.serializedPrimaryKey] != null;
92
+ const pkExists = meta.primaryKeys.every(pk => entity[pk] != null) || (meta.serializedPrimaryKey && entity[meta.serializedPrimaryKey] != null);
93
93
  if (!entity || !pkExists) {
94
94
  throw ValidationError.fromMergeWithoutPK(meta);
95
95
  }