@mikro-orm/core 7.0.0-dev.20 → 7.0.0-dev.201

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 (211) hide show
  1. package/EntityManager.d.ts +99 -57
  2. package/EntityManager.js +302 -276
  3. package/MikroORM.d.ts +44 -35
  4. package/MikroORM.js +103 -143
  5. package/README.md +3 -2
  6. package/cache/FileCacheAdapter.d.ts +1 -1
  7. package/cache/FileCacheAdapter.js +8 -7
  8. package/cache/GeneratedCacheAdapter.d.ts +0 -1
  9. package/cache/GeneratedCacheAdapter.js +0 -2
  10. package/cache/index.d.ts +0 -1
  11. package/cache/index.js +0 -1
  12. package/connections/Connection.d.ts +16 -7
  13. package/connections/Connection.js +23 -14
  14. package/drivers/DatabaseDriver.d.ts +25 -16
  15. package/drivers/DatabaseDriver.js +80 -35
  16. package/drivers/IDatabaseDriver.d.ts +44 -17
  17. package/entity/BaseEntity.d.ts +2 -2
  18. package/entity/BaseEntity.js +0 -3
  19. package/entity/Collection.d.ts +94 -29
  20. package/entity/Collection.js +434 -97
  21. package/entity/EntityAssigner.d.ts +1 -1
  22. package/entity/EntityAssigner.js +26 -18
  23. package/entity/EntityFactory.d.ts +13 -1
  24. package/entity/EntityFactory.js +84 -53
  25. package/entity/EntityHelper.d.ts +2 -2
  26. package/entity/EntityHelper.js +35 -15
  27. package/entity/EntityLoader.d.ts +6 -6
  28. package/entity/EntityLoader.js +117 -77
  29. package/entity/EntityRepository.d.ts +24 -4
  30. package/entity/EntityRepository.js +8 -2
  31. package/entity/Reference.d.ts +6 -5
  32. package/entity/Reference.js +34 -9
  33. package/entity/WrappedEntity.d.ts +2 -7
  34. package/entity/WrappedEntity.js +3 -8
  35. package/entity/defineEntity.d.ts +594 -0
  36. package/entity/defineEntity.js +533 -0
  37. package/entity/index.d.ts +3 -2
  38. package/entity/index.js +3 -2
  39. package/entity/utils.d.ts +7 -0
  40. package/entity/utils.js +16 -4
  41. package/entity/validators.d.ts +11 -0
  42. package/entity/validators.js +65 -0
  43. package/enums.d.ts +21 -5
  44. package/enums.js +15 -1
  45. package/errors.d.ts +23 -9
  46. package/errors.js +59 -21
  47. package/events/EventManager.d.ts +2 -1
  48. package/events/EventManager.js +19 -11
  49. package/hydration/Hydrator.js +1 -2
  50. package/hydration/ObjectHydrator.d.ts +4 -4
  51. package/hydration/ObjectHydrator.js +52 -33
  52. package/index.d.ts +2 -2
  53. package/index.js +1 -2
  54. package/logging/DefaultLogger.d.ts +1 -1
  55. package/logging/DefaultLogger.js +1 -0
  56. package/logging/SimpleLogger.d.ts +1 -1
  57. package/logging/colors.d.ts +1 -1
  58. package/logging/colors.js +7 -6
  59. package/logging/index.d.ts +1 -0
  60. package/logging/index.js +1 -0
  61. package/logging/inspect.d.ts +2 -0
  62. package/logging/inspect.js +11 -0
  63. package/metadata/EntitySchema.d.ts +40 -23
  64. package/metadata/EntitySchema.js +81 -34
  65. package/metadata/MetadataDiscovery.d.ts +7 -10
  66. package/metadata/MetadataDiscovery.js +391 -331
  67. package/metadata/MetadataProvider.d.ts +11 -2
  68. package/metadata/MetadataProvider.js +46 -2
  69. package/metadata/MetadataStorage.d.ts +13 -11
  70. package/metadata/MetadataStorage.js +70 -37
  71. package/metadata/MetadataValidator.d.ts +17 -9
  72. package/metadata/MetadataValidator.js +97 -40
  73. package/metadata/discover-entities.d.ts +5 -0
  74. package/metadata/discover-entities.js +40 -0
  75. package/metadata/index.d.ts +1 -1
  76. package/metadata/index.js +1 -1
  77. package/metadata/types.d.ts +502 -0
  78. package/metadata/types.js +1 -0
  79. package/naming-strategy/AbstractNamingStrategy.d.ts +12 -4
  80. package/naming-strategy/AbstractNamingStrategy.js +14 -2
  81. package/naming-strategy/EntityCaseNamingStrategy.d.ts +3 -3
  82. package/naming-strategy/EntityCaseNamingStrategy.js +6 -5
  83. package/naming-strategy/MongoNamingStrategy.d.ts +3 -3
  84. package/naming-strategy/MongoNamingStrategy.js +6 -6
  85. package/naming-strategy/NamingStrategy.d.ts +24 -4
  86. package/naming-strategy/UnderscoreNamingStrategy.d.ts +3 -3
  87. package/naming-strategy/UnderscoreNamingStrategy.js +6 -6
  88. package/not-supported.d.ts +2 -0
  89. package/not-supported.js +4 -0
  90. package/package.json +18 -11
  91. package/platforms/ExceptionConverter.js +1 -1
  92. package/platforms/Platform.d.ts +7 -13
  93. package/platforms/Platform.js +20 -43
  94. package/serialization/EntitySerializer.d.ts +5 -0
  95. package/serialization/EntitySerializer.js +47 -27
  96. package/serialization/EntityTransformer.js +28 -18
  97. package/serialization/SerializationContext.d.ts +6 -6
  98. package/serialization/SerializationContext.js +16 -13
  99. package/types/ArrayType.d.ts +1 -1
  100. package/types/ArrayType.js +2 -3
  101. package/types/BigIntType.d.ts +8 -6
  102. package/types/BigIntType.js +1 -1
  103. package/types/BlobType.d.ts +0 -1
  104. package/types/BlobType.js +0 -3
  105. package/types/BooleanType.d.ts +2 -1
  106. package/types/BooleanType.js +3 -0
  107. package/types/DecimalType.d.ts +6 -4
  108. package/types/DecimalType.js +3 -3
  109. package/types/DoubleType.js +2 -2
  110. package/types/EnumArrayType.js +1 -2
  111. package/types/JsonType.d.ts +1 -1
  112. package/types/JsonType.js +7 -2
  113. package/types/TinyIntType.js +1 -1
  114. package/types/Type.d.ts +2 -4
  115. package/types/Type.js +3 -3
  116. package/types/Uint8ArrayType.d.ts +0 -1
  117. package/types/Uint8ArrayType.js +1 -4
  118. package/types/index.d.ts +1 -1
  119. package/typings.d.ts +290 -137
  120. package/typings.js +59 -44
  121. package/unit-of-work/ChangeSet.d.ts +2 -6
  122. package/unit-of-work/ChangeSet.js +4 -5
  123. package/unit-of-work/ChangeSetComputer.d.ts +1 -3
  124. package/unit-of-work/ChangeSetComputer.js +26 -13
  125. package/unit-of-work/ChangeSetPersister.d.ts +5 -4
  126. package/unit-of-work/ChangeSetPersister.js +70 -34
  127. package/unit-of-work/CommitOrderCalculator.d.ts +12 -10
  128. package/unit-of-work/CommitOrderCalculator.js +13 -13
  129. package/unit-of-work/IdentityMap.d.ts +12 -0
  130. package/unit-of-work/IdentityMap.js +39 -1
  131. package/unit-of-work/UnitOfWork.d.ts +23 -3
  132. package/unit-of-work/UnitOfWork.js +175 -98
  133. package/utils/AbstractSchemaGenerator.d.ts +5 -5
  134. package/utils/AbstractSchemaGenerator.js +18 -16
  135. package/utils/AsyncContext.d.ts +6 -0
  136. package/utils/AsyncContext.js +42 -0
  137. package/utils/Configuration.d.ts +779 -207
  138. package/utils/Configuration.js +146 -190
  139. package/utils/ConfigurationLoader.d.ts +1 -54
  140. package/utils/ConfigurationLoader.js +1 -352
  141. package/utils/Cursor.d.ts +0 -3
  142. package/utils/Cursor.js +27 -11
  143. package/utils/DataloaderUtils.d.ts +15 -5
  144. package/utils/DataloaderUtils.js +64 -30
  145. package/utils/EntityComparator.d.ts +13 -9
  146. package/utils/EntityComparator.js +101 -42
  147. package/utils/QueryHelper.d.ts +14 -6
  148. package/utils/QueryHelper.js +87 -25
  149. package/utils/RawQueryFragment.d.ts +48 -25
  150. package/utils/RawQueryFragment.js +66 -70
  151. package/utils/RequestContext.js +2 -2
  152. package/utils/TransactionContext.js +2 -2
  153. package/utils/TransactionManager.d.ts +65 -0
  154. package/utils/TransactionManager.js +223 -0
  155. package/utils/Utils.d.ts +13 -126
  156. package/utils/Utils.js +100 -391
  157. package/utils/clone.js +8 -23
  158. package/utils/env-vars.d.ts +7 -0
  159. package/utils/env-vars.js +97 -0
  160. package/utils/fs-utils.d.ts +32 -0
  161. package/utils/fs-utils.js +178 -0
  162. package/utils/index.d.ts +2 -1
  163. package/utils/index.js +2 -1
  164. package/utils/upsert-utils.d.ts +9 -4
  165. package/utils/upsert-utils.js +55 -4
  166. package/decorators/Check.d.ts +0 -3
  167. package/decorators/Check.js +0 -13
  168. package/decorators/CreateRequestContext.d.ts +0 -3
  169. package/decorators/CreateRequestContext.js +0 -32
  170. package/decorators/Embeddable.d.ts +0 -8
  171. package/decorators/Embeddable.js +0 -11
  172. package/decorators/Embedded.d.ts +0 -12
  173. package/decorators/Embedded.js +0 -18
  174. package/decorators/Entity.d.ts +0 -18
  175. package/decorators/Entity.js +0 -12
  176. package/decorators/Enum.d.ts +0 -9
  177. package/decorators/Enum.js +0 -16
  178. package/decorators/Filter.d.ts +0 -2
  179. package/decorators/Filter.js +0 -8
  180. package/decorators/Formula.d.ts +0 -4
  181. package/decorators/Formula.js +0 -15
  182. package/decorators/Indexed.d.ts +0 -19
  183. package/decorators/Indexed.js +0 -20
  184. package/decorators/ManyToMany.d.ts +0 -40
  185. package/decorators/ManyToMany.js +0 -14
  186. package/decorators/ManyToOne.d.ts +0 -32
  187. package/decorators/ManyToOne.js +0 -14
  188. package/decorators/OneToMany.d.ts +0 -28
  189. package/decorators/OneToMany.js +0 -17
  190. package/decorators/OneToOne.d.ts +0 -26
  191. package/decorators/OneToOne.js +0 -7
  192. package/decorators/PrimaryKey.d.ts +0 -8
  193. package/decorators/PrimaryKey.js +0 -20
  194. package/decorators/Property.d.ts +0 -250
  195. package/decorators/Property.js +0 -32
  196. package/decorators/Transactional.d.ts +0 -13
  197. package/decorators/Transactional.js +0 -28
  198. package/decorators/hooks.d.ts +0 -16
  199. package/decorators/hooks.js +0 -47
  200. package/decorators/index.d.ts +0 -17
  201. package/decorators/index.js +0 -17
  202. package/entity/ArrayCollection.d.ts +0 -116
  203. package/entity/ArrayCollection.js +0 -402
  204. package/entity/EntityValidator.d.ts +0 -19
  205. package/entity/EntityValidator.js +0 -150
  206. package/exports.d.ts +0 -24
  207. package/exports.js +0 -23
  208. package/metadata/ReflectMetadataProvider.d.ts +0 -8
  209. package/metadata/ReflectMetadataProvider.js +0 -44
  210. package/utils/resolveContextProvider.d.ts +0 -10
  211. package/utils/resolveContextProvider.js +0 -28
@@ -4,8 +4,8 @@ import { ValidationError } from '../errors.js';
4
4
  import { LoadStrategy, PopulatePath, ReferenceKind, } from '../enums.js';
5
5
  import { Reference } from './Reference.js';
6
6
  import { helper } from './wrap.js';
7
- import { raw, RawQueryFragment } from '../utils/RawQueryFragment.js';
8
7
  import { expandDotPaths } from './utils.js';
8
+ import { Raw } from '../utils/RawQueryFragment.js';
9
9
  export class EntityLoader {
10
10
  em;
11
11
  metadata;
@@ -32,17 +32,16 @@ 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
- populate = this.normalizePopulate(entityName, populate, options.strategy, options.lookup);
42
+ populate = this.normalizePopulate(entityName, populate, options.strategy, options.lookup, options.exclude);
44
43
  const invalid = populate.find(({ field }) => !this.em.canPopulate(entityName, field));
45
- /* v8 ignore next 3 */
44
+ /* v8 ignore next */
46
45
  if (options.validate && invalid) {
47
46
  throw ValidationError.invalidPropertyName(entityName, invalid.field);
48
47
  }
@@ -57,7 +56,7 @@ export class EntityLoader {
57
56
  visited.delete(entity);
58
57
  }
59
58
  }
60
- normalizePopulate(entityName, populate, strategy, lookup = true) {
59
+ normalizePopulate(entityName, populate, strategy, lookup = true, exclude) {
61
60
  const meta = this.metadata.find(entityName);
62
61
  let normalized = Utils.asArray(populate).map(field => {
63
62
  return typeof field === 'boolean' || field.field === PopulatePath.ALL ? { all: !!field, field: meta.primaryKeys[0] } : field;
@@ -68,7 +67,7 @@ export class EntityLoader {
68
67
  // convert nested `field` with dot syntax to PopulateOptions with `children` array
69
68
  expandDotPaths(meta, normalized, true);
70
69
  if (lookup && populate !== false) {
71
- normalized = this.lookupEagerLoadedRelationships(entityName, normalized, strategy);
70
+ normalized = this.lookupEagerLoadedRelationships(entityName, normalized, strategy, '', [], exclude);
72
71
  // convert nested `field` with dot syntax produced by eager relations
73
72
  expandDotPaths(meta, normalized, true);
74
73
  }
@@ -140,35 +139,39 @@ 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
144
  const res = await this.findChildrenFromPivotTable(filtered, prop, options, innerOrderBy, populate, !!ref);
145
145
  return Utils.flatten(res);
146
146
  }
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;
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;
151
154
  }
152
155
  async populateScalar(meta, filtered, options) {
153
156
  const pk = Utils.getPrimaryKeyHash(meta.primaryKeys);
154
- 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)));
155
158
  const where = this.mergePrimaryCondition(ids, pk, options, meta, this.metadata, this.driver.getPlatform());
156
159
  const { filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging, fields } = options;
157
- await this.em.find(meta.className, where, {
160
+ await this.em.find(meta.class, where, {
158
161
  filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging,
159
162
  fields: fields,
160
163
  populate: [],
161
164
  });
162
165
  }
163
- initializeCollections(filtered, prop, field, children, customOrder) {
166
+ initializeCollections(filtered, prop, field, children, customOrder, partial) {
164
167
  if (prop.kind === ReferenceKind.ONE_TO_MANY) {
165
- this.initializeOneToMany(filtered, children, prop, field);
168
+ this.initializeOneToMany(filtered, children, prop, field, partial);
166
169
  }
167
170
  if (prop.kind === ReferenceKind.MANY_TO_MANY && !this.driver.getPlatform().usesPivotTable()) {
168
- this.initializeManyToMany(filtered, children, prop, field, customOrder);
171
+ this.initializeManyToMany(filtered, children, prop, field, customOrder, partial);
169
172
  }
170
173
  }
171
- initializeOneToMany(filtered, children, prop, field) {
174
+ initializeOneToMany(filtered, children, prop, field, partial) {
172
175
  const mapToPk = prop.targetMeta.properties[prop.mappedBy].mapToPk;
173
176
  const map = {};
174
177
  for (const entity of filtered) {
@@ -178,20 +181,20 @@ export class EntityLoader {
178
181
  for (const child of children) {
179
182
  const pk = child.__helper.__data[prop.mappedBy] ?? child[prop.mappedBy];
180
183
  if (pk) {
181
- const key = helper(mapToPk ? this.em.getReference(prop.type, pk) : pk).getSerializedPrimaryKey();
184
+ const key = helper(mapToPk ? this.em.getReference(prop.targetMeta.class, pk) : pk).getSerializedPrimaryKey();
182
185
  map[key]?.push(child);
183
186
  }
184
187
  }
185
188
  for (const entity of filtered) {
186
189
  const key = helper(entity).getSerializedPrimaryKey();
187
- entity[field].hydrate(map[key]);
190
+ entity[field].hydrate(map[key], undefined, partial);
188
191
  }
189
192
  }
190
- initializeManyToMany(filtered, children, prop, field, customOrder) {
193
+ initializeManyToMany(filtered, children, prop, field, customOrder, partial) {
191
194
  if (prop.mappedBy) {
192
195
  for (const entity of filtered) {
193
196
  const items = children.filter(child => child[prop.mappedBy].contains(entity, false));
194
- entity[field].hydrate(items, true);
197
+ entity[field].hydrate(items, true, partial);
195
198
  }
196
199
  }
197
200
  else { // owning side of M:N without pivot table needs to be reordered
@@ -201,15 +204,17 @@ export class EntityLoader {
201
204
  if (!customOrder) {
202
205
  items.sort((a, b) => order.indexOf(a) - order.indexOf(b));
203
206
  }
204
- entity[field].hydrate(items, true);
207
+ entity[field].hydrate(items, true, partial);
205
208
  }
206
209
  }
207
210
  }
208
211
  async findChildren(entities, prop, populate, options, ref) {
209
- const children = this.getChildReferences(entities, prop, options, ref);
212
+ const children = Utils.unique(this.getChildReferences(entities, prop, options, ref));
210
213
  const meta = prop.targetMeta;
211
- let fk = Utils.getPrimaryKeyHash(meta.primaryKeys);
214
+ // When targetKey is set, use it for FK lookup instead of the PK
215
+ let fk = prop.targetKey ?? 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,42 +224,29 @@ 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;
226
231
  }
227
- const ids = Utils.unique(children.map(e => e.__helper.getPrimaryKey()));
232
+ // When targetKey is set, get the targetKey value instead of PK
233
+ const ids = Utils.unique(children.map(e => prop.targetKey ? e[prop.targetKey] : e.__helper.getPrimaryKey()));
228
234
  let where = this.mergePrimaryCondition(ids, fk, options, meta, this.metadata, this.driver.getPlatform());
229
235
  const fields = this.buildFields(options.fields, prop, ref);
230
236
  /* eslint-disable prefer-const */
231
- let { refresh, filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging, } = options;
237
+ let { refresh, filters, convertCustomTypes, lockMode, strategy, populateWhere = 'infer', connectionType, logging, } = options;
232
238
  /* eslint-enable prefer-const */
233
239
  if (typeof populateWhere === 'object') {
234
240
  populateWhere = await this.extractChildCondition({ where: populateWhere }, prop);
235
241
  }
236
- if (!Utils.isEmpty(prop.where)) {
242
+ if (!Utils.isEmpty(prop.where) || Raw.hasObjectFragments(prop.where)) {
237
243
  where = { $and: [where, prop.where] };
238
244
  }
239
- const propOrderBy = [];
240
- if (prop.orderBy) {
241
- for (const item of Utils.asArray(prop.orderBy)) {
242
- for (const field of Utils.keys(item)) {
243
- const rawField = RawQueryFragment.getKnownFragment(field, false);
244
- if (rawField) {
245
- const raw2 = raw(rawField.sql, rawField.params);
246
- propOrderBy.push({ [raw2.toString()]: item[field] });
247
- continue;
248
- }
249
- propOrderBy.push({ [field]: item[field] });
250
- }
251
- }
252
- }
253
- const orderBy = [...Utils.asArray(options.orderBy), ...propOrderBy].filter((order, idx, array) => {
245
+ const orderBy = [...Utils.asArray(options.orderBy), ...Utils.asArray(prop.orderBy)].filter((order, idx, array) => {
254
246
  // skip consecutive ordering with the same key to get around mongo issues
255
- return idx === 0 || !Utils.equals(Object.keys(array[idx - 1]), Object.keys(order));
247
+ return idx === 0 || !Utils.equals(Utils.getObjectQueryKeys(array[idx - 1]), Utils.getObjectQueryKeys(order));
256
248
  });
257
- const items = await this.em.find(prop.type, where, {
249
+ const items = await this.em.find(meta.class, where, {
258
250
  filters, convertCustomTypes, lockMode, populateWhere, logging,
259
251
  orderBy,
260
252
  populate: populate.children ?? populate.all ?? [],
@@ -265,6 +257,49 @@ export class EntityLoader {
265
257
  // @ts-ignore not a public option, will be propagated to the populate call
266
258
  visited: options.visited,
267
259
  });
260
+ // For targetKey relations, wire up loaded entities to parent references
261
+ // This is needed because the references were created under alternate key,
262
+ // but loaded entities are stored under PK, so they don't automatically merge
263
+ if (prop.targetKey && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind)) {
264
+ const itemsByKey = new Map();
265
+ for (const item of items) {
266
+ itemsByKey.set('' + item[prop.targetKey], item);
267
+ }
268
+ for (const entity of entities) {
269
+ const ref = entity[prop.name];
270
+ /* v8 ignore next */
271
+ if (!ref) {
272
+ continue;
273
+ }
274
+ const keyValue = '' + (Reference.isReference(ref) ? ref.unwrap()[prop.targetKey] : ref[prop.targetKey]);
275
+ const loadedItem = itemsByKey.get(keyValue);
276
+ if (loadedItem) {
277
+ entity[prop.name] = (Reference.isReference(ref) ? Reference.create(loadedItem) : loadedItem);
278
+ }
279
+ }
280
+ }
281
+ if ([ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && items.length !== children.length) {
282
+ const nullVal = this.em.config.get('forceUndefined') ? undefined : null;
283
+ const itemsMap = new Set();
284
+ const childrenMap = new Set();
285
+ // Use targetKey value if set, otherwise use serialized PK
286
+ const getKey = (e) => prop.targetKey ? '' + e[prop.targetKey] : helper(e).getSerializedPrimaryKey();
287
+ for (const item of items) {
288
+ /* v8 ignore next */
289
+ itemsMap.add(getKey(item));
290
+ }
291
+ for (const child of children) {
292
+ childrenMap.add(getKey(child));
293
+ }
294
+ for (const entity of entities) {
295
+ const ref = entity[prop.name] ?? {};
296
+ const key = helper(ref) ? getKey(ref) : undefined;
297
+ if (key && childrenMap.has(key) && !itemsMap.has(key)) {
298
+ entity[prop.name] = nullVal;
299
+ helper(entity).__originalEntityData[prop.name] = null;
300
+ }
301
+ }
302
+ }
268
303
  for (const item of items) {
269
304
  if (ref && !helper(item).__onLoadFired) {
270
305
  helper(item).__initialized = false;
@@ -272,10 +307,10 @@ export class EntityLoader {
272
307
  this.em.getUnitOfWork()['loadedEntities'].delete(item);
273
308
  }
274
309
  }
275
- return items;
310
+ return { items, partial };
276
311
  }
277
312
  mergePrimaryCondition(ids, pk, options, meta, metadata, platform) {
278
- const cond1 = QueryHelper.processWhere({ where: { [pk]: { $in: ids } }, entityName: meta.className, metadata, platform, convertCustomTypes: !options.convertCustomTypes });
313
+ const cond1 = QueryHelper.processWhere({ where: { [pk]: { $in: ids } }, entityName: meta.class, metadata, platform, convertCustomTypes: !options.convertCustomTypes });
279
314
  const where = { ...options.where };
280
315
  Utils.dropUndefinedProperties(where);
281
316
  return where[pk]
@@ -288,6 +323,7 @@ export class EntityLoader {
288
323
  if (prop.kind === ReferenceKind.SCALAR && !prop.lazy) {
289
324
  return;
290
325
  }
326
+ options = { ...options, filters: QueryHelper.mergePropertyFilters(prop.filters, options.filters) };
291
327
  const populated = await this.populateMany(entityName, entities, populate, options);
292
328
  if (!populate.children && !populate.all) {
293
329
  return;
@@ -321,11 +357,16 @@ export class EntityLoader {
321
357
  for (const entity of entities) {
322
358
  visited.delete(entity);
323
359
  }
324
- const filtered = Utils.unique(children.filter(e => !visited.has(e)));
360
+ const unique = Utils.unique(children);
361
+ const filtered = unique.filter(e => !visited.has(e));
325
362
  for (const entity of entities) {
326
363
  visited.add(entity);
327
364
  }
328
- await this.populate(prop.type, filtered, populate.children ?? populate.all, {
365
+ // skip lazy scalar properties
366
+ if (!prop.targetMeta) {
367
+ return;
368
+ }
369
+ await this.populate(prop.targetMeta.class, unique, populate.children ?? populate.all, {
329
370
  where: await this.extractChildCondition(options, prop, false),
330
371
  orderBy: innerOrderBy,
331
372
  fields,
@@ -342,6 +383,8 @@ export class EntityLoader {
342
383
  refresh: refresh && !filtered.every(item => options.visited.has(item)),
343
384
  // @ts-ignore not a public option, will be propagated to the populate call
344
385
  visited: options.visited,
386
+ // @ts-ignore not a public option
387
+ filtered,
345
388
  });
346
389
  }
347
390
  /** @internal */
@@ -368,12 +411,12 @@ export class EntityLoader {
368
411
  for (const entity of filtered) {
369
412
  const items = map[entity.__helper.getSerializedPrimaryKey()].map(item => {
370
413
  if (pivotJoin) {
371
- return this.em.getReference(prop.type, item, {
414
+ return this.em.getReference(prop.targetMeta.class, item, {
372
415
  convertCustomTypes: true,
373
416
  schema: options.schema ?? this.em.config.get('schema'),
374
417
  });
375
418
  }
376
- const entity = this.em.getEntityFactory().create(prop.type, item, {
419
+ const entity = this.em.getEntityFactory().create(prop.targetMeta.class, item, {
377
420
  refresh,
378
421
  merge: true,
379
422
  convertCustomTypes: true,
@@ -389,16 +432,13 @@ export class EntityLoader {
389
432
  async extractChildCondition(options, prop, filters = false) {
390
433
  const where = options.where;
391
434
  const subCond = Utils.isPlainObject(where[prop.name]) ? where[prop.name] : {};
392
- const meta2 = this.metadata.find(prop.type);
393
- if (!meta2) {
394
- return {};
395
- }
435
+ const meta2 = prop.targetMeta;
396
436
  const pk = Utils.getPrimaryKeyHash(meta2.primaryKeys);
397
437
  ['$and', '$or'].forEach(op => {
398
438
  if (where[op]) {
399
439
  const child = where[op]
400
440
  .map((cond) => cond[prop.name])
401
- .filter((sub) => sub != null && !(Utils.isPlainObject(sub) && Object.keys(sub).every(key => Utils.isOperator(key, false))))
441
+ .filter((sub) => sub != null && !(Utils.isPlainObject(sub) && Utils.getObjectQueryKeys(sub).every(key => Utils.isOperator(key, false))))
402
442
  .map((cond) => {
403
443
  if (Utils.isPrimaryKey(cond)) {
404
444
  return { [pk]: cond };
@@ -419,7 +459,7 @@ export class EntityLoader {
419
459
  });
420
460
  }
421
461
  if (filters) {
422
- return this.em.applyFilters(prop.type, subCond, options.filters, 'read', options);
462
+ return this.em.applyFilters(meta2.class, subCond, options.filters, 'read', options);
423
463
  }
424
464
  return subCond;
425
465
  }
@@ -437,7 +477,7 @@ export class EntityLoader {
437
477
  const parts = f.toString().split('.');
438
478
  const propName = parts.shift();
439
479
  const childPropName = parts.join('.');
440
- /* v8 ignore next 3 */
480
+ /* v8 ignore next */
441
481
  if (propName === prop.name) {
442
482
  ret.push(childPropName);
443
483
  }
@@ -458,23 +498,20 @@ export class EntityLoader {
458
498
  }
459
499
  getChildReferences(entities, prop, options, ref) {
460
500
  const filtered = this.filterCollections(entities, prop.name, options, ref);
461
- const children = [];
462
501
  if (prop.kind === ReferenceKind.ONE_TO_MANY) {
463
- children.push(...filtered.map(e => e[prop.name].owner));
502
+ return filtered.map(e => e[prop.name].owner);
464
503
  }
465
- else if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner) {
466
- children.push(...filtered.reduce((a, b) => {
504
+ if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner) {
505
+ return filtered.reduce((a, b) => {
467
506
  a.push(...b[prop.name].getItems());
468
507
  return a;
469
- }, []));
470
- }
471
- else if (prop.kind === ReferenceKind.MANY_TO_MANY) { // inverse side
472
- children.push(...filtered);
508
+ }, []);
473
509
  }
474
- else { // MANY_TO_ONE or ONE_TO_ONE
475
- children.push(...this.filterReferences(entities, prop.name, options, ref));
510
+ if (prop.kind === ReferenceKind.MANY_TO_MANY) { // inverse side
511
+ return filtered;
476
512
  }
477
- return children;
513
+ // MANY_TO_ONE or ONE_TO_ONE
514
+ return this.filterReferences(entities, prop.name, options, ref);
478
515
  }
479
516
  filterCollections(entities, field, options, ref) {
480
517
  if (options.refresh) {
@@ -491,7 +528,7 @@ export class EntityLoader {
491
528
  return wrapped.__loadedProperties.has(field);
492
529
  }
493
530
  const [f, ...r] = field.split('.');
494
- /* v8 ignore next 3 */
531
+ /* v8 ignore next */
495
532
  if (!wrapped.__loadedProperties.has(f) || !wrapped.__meta.properties[f]?.targetMeta) {
496
533
  return false;
497
534
  }
@@ -524,7 +561,7 @@ export class EntityLoader {
524
561
  .map(e => Reference.unwrapReference(e[field]));
525
562
  }
526
563
  filterByReferences(entities, field, refresh) {
527
- /* v8 ignore next 3 */
564
+ /* v8 ignore next */
528
565
  if (refresh) {
529
566
  return entities;
530
567
  }
@@ -550,33 +587,36 @@ export class EntityLoader {
550
587
  }
551
588
  return `${this.getRelationName(meta, meta.properties[prop.embedded[0]])}.${prop.embedded[1]}`;
552
589
  }
553
- lookupEagerLoadedRelationships(entityName, populate, strategy, prefix = '', visited = []) {
590
+ lookupEagerLoadedRelationships(entityName, populate, strategy, prefix = '', visited = [], exclude) {
554
591
  const meta = this.metadata.find(entityName);
555
592
  if (!meta && !prefix) {
556
593
  return populate;
557
594
  }
558
- if (visited.includes(entityName) || !meta) {
595
+ if (!meta || visited.includes(meta)) {
559
596
  return [];
560
597
  }
561
- visited.push(entityName);
598
+ visited.push(meta);
562
599
  const ret = prefix === '' ? [...populate] : [];
563
600
  meta.relations
564
601
  .filter(prop => {
602
+ const field = this.getRelationName(meta, prop);
603
+ const prefixed = prefix ? `${prefix}.${field}` : field;
604
+ const isExcluded = exclude?.includes(prefixed);
565
605
  const eager = prop.eager && !populate.some(p => p.field === `${prop.name}:ref`);
566
606
  const populated = populate.some(p => p.field === prop.name);
567
607
  const disabled = populate.some(p => p.field === prop.name && p.all === false);
568
- return !disabled && (eager || populated);
608
+ return !disabled && !isExcluded && (eager || populated);
569
609
  })
570
610
  .forEach(prop => {
571
611
  const field = this.getRelationName(meta, prop);
572
612
  const prefixed = prefix ? `${prefix}.${field}` : field;
573
613
  const nestedPopulate = populate.filter(p => p.field === prop.name).flatMap(p => p.children).filter(Boolean);
574
- const nested = this.lookupEagerLoadedRelationships(prop.type, nestedPopulate, strategy, prefixed, visited.slice());
614
+ const nested = this.lookupEagerLoadedRelationships(prop.targetMeta.class, nestedPopulate, strategy, prefixed, visited.slice(), exclude);
575
615
  if (nested.length > 0) {
576
616
  ret.push(...nested);
577
617
  }
578
618
  else {
579
- const selfReferencing = [meta.className, meta.root.className, ...visited].includes(prop.type) && prop.eager;
619
+ const selfReferencing = [meta.tableName, ...visited.map(m => m.tableName)].includes(prop.targetMeta.tableName) && prop.eager;
580
620
  ret.push({
581
621
  field: prefixed,
582
622
  // enforce select-in strategy for self-referencing relations
@@ -2,7 +2,7 @@ import type { PopulatePath } from '../enums.js';
2
2
  import type { CreateOptions, EntityManager, MergeOptions } from '../EntityManager.js';
3
3
  import type { AssignOptions } from './EntityAssigner.js';
4
4
  import type { EntityData, EntityName, Primary, Loaded, FilterQuery, EntityDictionary, AutoPath, RequiredEntityData, Ref, EntityType, EntityDTO, MergeSelected, FromEntityType, IsSubset, MergeLoaded, ArrayElement } from '../typings.js';
5
- import type { CountOptions, DeleteOptions, FindAllOptions, FindByCursorOptions, FindOneOptions, FindOneOrFailOptions, FindOptions, GetReferenceOptions, NativeInsertUpdateOptions, UpdateOptions, UpsertManyOptions, UpsertOptions } from '../drivers/IDatabaseDriver.js';
5
+ import type { CountOptions, DeleteOptions, FindAllOptions, FindByCursorOptions, FindOneOptions, FindOneOrFailOptions, FindOptions, GetReferenceOptions, NativeInsertUpdateOptions, StreamOptions, UpdateOptions, UpsertManyOptions, UpsertOptions } from '../drivers/IDatabaseDriver.js';
6
6
  import type { EntityLoaderOptions } from './EntityLoader.js';
7
7
  import type { Cursor } from '../utils/Cursor.js';
8
8
  export declare class EntityRepository<Entity extends object> {
@@ -80,11 +80,15 @@ 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>(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
  */
87
87
  findAll<Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(options?: FindAllOptions<Entity, Hint, Fields, Excludes>): Promise<Loaded<Entity, Hint, Fields, Excludes>[]>;
88
+ /**
89
+ * @inheritDoc EntityManager.stream
90
+ */
91
+ stream<Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(options?: StreamOptions<Entity, Hint, Fields, Excludes>): AsyncIterableIterator<Loaded<Entity, Hint, Fields, Excludes>>;
88
92
  /**
89
93
  * @inheritDoc EntityManager.insert
90
94
  */
@@ -107,10 +111,26 @@ export declare class EntityRepository<Entity extends object> {
107
111
  map(result: EntityDictionary<Entity>, options?: {
108
112
  schema?: string;
109
113
  }): Entity;
114
+ /**
115
+ * Gets a reference to the entity identified by the given type and alternate key property without actually loading it.
116
+ * The key option specifies which property to use for identity map lookup instead of the primary key.
117
+ */
118
+ getReference<K extends string & keyof Entity>(id: Entity[K], options: Omit<GetReferenceOptions, 'key' | 'wrapped'> & {
119
+ key: K;
120
+ wrapped: true;
121
+ }): Ref<Entity>;
122
+ /**
123
+ * Gets a reference to the entity identified by the given type and alternate key property without actually loading it.
124
+ * The key option specifies which property to use for identity map lookup instead of the primary key.
125
+ */
126
+ getReference<K extends string & keyof Entity>(id: Entity[K], options: Omit<GetReferenceOptions, 'key'> & {
127
+ key: K;
128
+ wrapped?: false;
129
+ }): Entity;
110
130
  /**
111
131
  * Gets a reference to the entity identified by the given type and identifier without actually loading it, if the entity is not yet loaded
112
132
  */
113
- getReference(id: Primary<Entity>, options: Omit<GetReferenceOptions, 'wrapped'> & {
133
+ getReference(id: Primary<Entity>, options: Omit<GetReferenceOptions, 'wrapped' | 'key'> & {
114
134
  wrapped: true;
115
135
  }): Ref<Entity>;
116
136
  /**
@@ -120,7 +140,7 @@ export declare class EntityRepository<Entity extends object> {
120
140
  /**
121
141
  * Gets a reference to the entity identified by the given type and identifier without actually loading it, if the entity is not yet loaded
122
142
  */
123
- getReference(id: Primary<Entity>, options: Omit<GetReferenceOptions, 'wrapped'> & {
143
+ getReference(id: Primary<Entity>, options: Omit<GetReferenceOptions, 'wrapped' | 'key'> & {
124
144
  wrapped: false;
125
145
  }): Entity;
126
146
  /**
@@ -90,8 +90,8 @@ export class EntityRepository {
90
90
  /**
91
91
  * @inheritDoc EntityManager.findByCursor
92
92
  */
93
- async findByCursor(where, options) {
94
- return this.getEntityManager().findByCursor(this.entityName, where, options);
93
+ async findByCursor(options) {
94
+ return this.getEntityManager().findByCursor(this.entityName, options);
95
95
  }
96
96
  /**
97
97
  * Finds all entities of given type. You can pass additional options via the `options` parameter.
@@ -99,6 +99,12 @@ export class EntityRepository {
99
99
  async findAll(options) {
100
100
  return this.getEntityManager().findAll(this.entityName, options);
101
101
  }
102
+ /**
103
+ * @inheritDoc EntityManager.stream
104
+ */
105
+ async *stream(options) {
106
+ yield* this.getEntityManager().stream(this.entityName, options);
107
+ }
102
108
  /**
103
109
  * @inheritDoc EntityManager.insert
104
110
  */
@@ -1,8 +1,8 @@
1
- import { inspect } from 'node:util';
2
1
  import type { AddEager, AddOptional, Dictionary, EntityClass, EntityKey, EntityProperty, Loaded, LoadedReference, Primary, Ref } from '../typings.js';
3
2
  import type { FindOneOptions, FindOneOrFailOptions } from '../drivers/IDatabaseDriver.js';
4
3
  export declare class Reference<T extends object> {
5
4
  private entity;
5
+ private property?;
6
6
  constructor(entity: T);
7
7
  static create<T extends object>(entity: T | Ref<T>): Ref<T>;
8
8
  static createFromPK<T extends object>(entityType: EntityClass<T>, pk: Primary<T>, options?: {
@@ -42,8 +42,6 @@ export declare class Reference<T extends object> {
42
42
  isInitialized(): boolean;
43
43
  populated(populated?: boolean): void;
44
44
  toJSON(...args: any[]): Dictionary;
45
- /** @ignore */
46
- [inspect.custom](depth?: number): string;
47
45
  }
48
46
  export declare class ScalarReference<Value> {
49
47
  private value?;
@@ -56,12 +54,15 @@ export declare class ScalarReference<Value> {
56
54
  * Returns either the whole entity, or the requested property.
57
55
  */
58
56
  load(options?: Omit<LoadReferenceOptions<any, any>, 'populate' | 'fields' | 'exclude'>): Promise<Value | undefined>;
57
+ /**
58
+ * Ensures the underlying entity is loaded first (without reloading it if it already is loaded).
59
+ * Returns the entity or throws an error just like `em.findOneOrFail()` (and respects the same config options).
60
+ */
61
+ loadOrFail(options?: Omit<LoadReferenceOrFailOptions<any, any>, 'populate' | 'fields' | 'exclude'>): Promise<Value>;
59
62
  set(value: Value): void;
60
63
  bind<Entity extends object>(entity: Entity, property: EntityKey<Entity>): void;
61
64
  unwrap(): Value | undefined;
62
65
  isInitialized(): boolean;
63
- /** @ignore */
64
- [inspect.custom](): string;
65
66
  }
66
67
  export interface LoadReferenceOptions<T extends object, P extends string = never, F extends string = '*', E extends string = never> extends FindOneOptions<T, P, F, E> {
67
68
  dataloader?: boolean;
@@ -1,9 +1,12 @@
1
- import { inspect } from 'node:util';
2
1
  import { DataloaderType } from '../enums.js';
3
2
  import { helper, wrap } from './wrap.js';
4
3
  import { Utils } from '../utils/Utils.js';
4
+ import { QueryHelper } from '../utils/QueryHelper.js';
5
+ import { NotFoundError } from '../errors.js';
6
+ import { inspect } from '../logging/inspect.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,13 +88,14 @@ 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
  }
84
95
  if (!this.isInitialized() || options.refresh) {
85
96
  if (options.dataloader ?? [DataloaderType.ALL, DataloaderType.REFERENCE].includes(wrapped.__em.config.getDataloaderType())) {
86
- // eslint-disable-next-line dot-notation
87
- return wrapped.__em['refLoader'].load([this, options]);
97
+ const dataLoader = await wrapped.__em.getDataLoader('ref');
98
+ return dataLoader.load([this, options]);
88
99
  }
89
100
  return wrapped.init(options);
90
101
  }
@@ -135,9 +146,9 @@ export class Reference {
135
146
  return wrap(this.entity).toJSON(...args);
136
147
  }
137
148
  /** @ignore */
138
- [inspect.custom](depth = 2) {
149
+ [Symbol.for('nodejs.util.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,20 @@ 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
+ throw NotFoundError.failedToLoadProperty(entityName, this.property, wrapped.getPrimaryKey());
196
+ }
197
+ return ret;
198
+ }
174
199
  set(value) {
175
200
  this.value = value;
176
201
  this.initialized = true;
@@ -186,9 +211,9 @@ export class ScalarReference {
186
211
  isInitialized() {
187
212
  return this.initialized;
188
213
  }
189
- /* v8 ignore next 4 */
190
214
  /** @ignore */
191
- [inspect.custom]() {
215
+ /* v8 ignore next */
216
+ [Symbol.for('nodejs.util.inspect.custom')]() {
192
217
  return this.initialized ? `Ref<${inspect(this.value)}>` : `Ref<?>`;
193
218
  }
194
219
  }