@mikro-orm/core 7.0.0-dev.33 → 7.0.0-dev.331

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 (218) hide show
  1. package/EntityManager.d.ts +70 -75
  2. package/EntityManager.js +487 -402
  3. package/MikroORM.d.ts +45 -38
  4. package/MikroORM.js +123 -156
  5. package/README.md +7 -4
  6. package/cache/FileCacheAdapter.d.ts +2 -7
  7. package/cache/FileCacheAdapter.js +35 -30
  8. package/cache/GeneratedCacheAdapter.d.ts +1 -2
  9. package/cache/GeneratedCacheAdapter.js +6 -8
  10. package/cache/MemoryCacheAdapter.d.ts +1 -2
  11. package/cache/MemoryCacheAdapter.js +8 -8
  12. package/cache/index.d.ts +1 -2
  13. package/cache/index.js +0 -2
  14. package/connections/Connection.d.ts +12 -5
  15. package/connections/Connection.js +37 -15
  16. package/drivers/DatabaseDriver.d.ts +25 -18
  17. package/drivers/DatabaseDriver.js +144 -45
  18. package/drivers/IDatabaseDriver.d.ts +118 -23
  19. package/entity/BaseEntity.d.ts +63 -4
  20. package/entity/BaseEntity.js +0 -3
  21. package/entity/Collection.d.ts +95 -31
  22. package/entity/Collection.js +487 -139
  23. package/entity/EntityAssigner.js +37 -25
  24. package/entity/EntityFactory.d.ts +8 -9
  25. package/entity/EntityFactory.js +152 -100
  26. package/entity/EntityHelper.d.ts +2 -2
  27. package/entity/EntityHelper.js +69 -27
  28. package/entity/EntityLoader.d.ts +12 -13
  29. package/entity/EntityLoader.js +286 -125
  30. package/entity/EntityRepository.d.ts +28 -8
  31. package/entity/EntityRepository.js +8 -2
  32. package/entity/PolymorphicRef.d.ts +12 -0
  33. package/entity/PolymorphicRef.js +18 -0
  34. package/entity/Reference.d.ts +3 -8
  35. package/entity/Reference.js +62 -29
  36. package/entity/WrappedEntity.d.ts +7 -10
  37. package/entity/WrappedEntity.js +6 -7
  38. package/entity/defineEntity.d.ts +472 -313
  39. package/entity/defineEntity.js +134 -290
  40. package/entity/index.d.ts +2 -2
  41. package/entity/index.js +2 -2
  42. package/entity/utils.d.ts +6 -1
  43. package/entity/utils.js +46 -11
  44. package/entity/validators.d.ts +11 -0
  45. package/entity/validators.js +66 -0
  46. package/enums.d.ts +8 -6
  47. package/enums.js +13 -17
  48. package/errors.d.ts +26 -16
  49. package/errors.js +63 -31
  50. package/events/EventManager.d.ts +3 -5
  51. package/events/EventManager.js +37 -26
  52. package/events/index.d.ts +1 -1
  53. package/events/index.js +0 -1
  54. package/exceptions.js +9 -2
  55. package/hydration/Hydrator.js +1 -2
  56. package/hydration/ObjectHydrator.d.ts +5 -6
  57. package/hydration/ObjectHydrator.js +109 -50
  58. package/index.d.ts +2 -2
  59. package/index.js +1 -2
  60. package/logging/DefaultLogger.d.ts +1 -1
  61. package/logging/DefaultLogger.js +3 -4
  62. package/logging/SimpleLogger.d.ts +1 -1
  63. package/logging/colors.d.ts +1 -1
  64. package/logging/colors.js +4 -6
  65. package/logging/index.d.ts +2 -1
  66. package/logging/index.js +1 -1
  67. package/logging/inspect.d.ts +2 -0
  68. package/logging/inspect.js +11 -0
  69. package/metadata/EntitySchema.d.ts +47 -23
  70. package/metadata/EntitySchema.js +103 -34
  71. package/metadata/MetadataDiscovery.d.ts +65 -18
  72. package/metadata/MetadataDiscovery.js +940 -424
  73. package/metadata/MetadataProvider.d.ts +11 -2
  74. package/metadata/MetadataProvider.js +71 -2
  75. package/metadata/MetadataStorage.d.ts +11 -13
  76. package/metadata/MetadataStorage.js +79 -48
  77. package/metadata/MetadataValidator.d.ts +32 -9
  78. package/metadata/MetadataValidator.js +214 -44
  79. package/metadata/discover-entities.d.ts +5 -0
  80. package/metadata/discover-entities.js +40 -0
  81. package/metadata/index.d.ts +1 -1
  82. package/metadata/index.js +0 -1
  83. package/metadata/types.d.ts +577 -0
  84. package/metadata/types.js +1 -0
  85. package/naming-strategy/AbstractNamingStrategy.d.ts +16 -4
  86. package/naming-strategy/AbstractNamingStrategy.js +26 -5
  87. package/naming-strategy/EntityCaseNamingStrategy.d.ts +3 -3
  88. package/naming-strategy/EntityCaseNamingStrategy.js +7 -6
  89. package/naming-strategy/MongoNamingStrategy.d.ts +3 -3
  90. package/naming-strategy/MongoNamingStrategy.js +6 -6
  91. package/naming-strategy/NamingStrategy.d.ts +28 -4
  92. package/naming-strategy/UnderscoreNamingStrategy.d.ts +3 -3
  93. package/naming-strategy/UnderscoreNamingStrategy.js +6 -6
  94. package/naming-strategy/index.d.ts +1 -1
  95. package/naming-strategy/index.js +0 -1
  96. package/not-supported.d.ts +2 -0
  97. package/not-supported.js +8 -0
  98. package/package.json +47 -36
  99. package/platforms/ExceptionConverter.js +1 -1
  100. package/platforms/Platform.d.ts +33 -15
  101. package/platforms/Platform.js +125 -69
  102. package/serialization/EntitySerializer.d.ts +6 -3
  103. package/serialization/EntitySerializer.js +54 -30
  104. package/serialization/EntityTransformer.js +37 -22
  105. package/serialization/SerializationContext.d.ts +10 -14
  106. package/serialization/SerializationContext.js +24 -19
  107. package/types/ArrayType.d.ts +1 -1
  108. package/types/ArrayType.js +2 -3
  109. package/types/BigIntType.js +1 -1
  110. package/types/BlobType.d.ts +0 -1
  111. package/types/BlobType.js +0 -3
  112. package/types/BooleanType.d.ts +1 -0
  113. package/types/BooleanType.js +3 -0
  114. package/types/DecimalType.js +2 -2
  115. package/types/DoubleType.js +1 -1
  116. package/types/EnumArrayType.js +1 -2
  117. package/types/JsonType.d.ts +1 -1
  118. package/types/JsonType.js +7 -2
  119. package/types/TinyIntType.js +1 -1
  120. package/types/Type.d.ts +2 -4
  121. package/types/Type.js +3 -3
  122. package/types/Uint8ArrayType.d.ts +0 -1
  123. package/types/Uint8ArrayType.js +1 -4
  124. package/types/UuidType.d.ts +2 -0
  125. package/types/UuidType.js +14 -2
  126. package/types/index.d.ts +3 -2
  127. package/typings.d.ts +427 -170
  128. package/typings.js +100 -45
  129. package/unit-of-work/ChangeSet.d.ts +4 -6
  130. package/unit-of-work/ChangeSet.js +8 -9
  131. package/unit-of-work/ChangeSetComputer.d.ts +2 -12
  132. package/unit-of-work/ChangeSetComputer.js +61 -38
  133. package/unit-of-work/ChangeSetPersister.d.ts +10 -17
  134. package/unit-of-work/ChangeSetPersister.js +136 -73
  135. package/unit-of-work/CommitOrderCalculator.d.ts +13 -14
  136. package/unit-of-work/CommitOrderCalculator.js +22 -20
  137. package/unit-of-work/IdentityMap.d.ts +12 -3
  138. package/unit-of-work/IdentityMap.js +51 -13
  139. package/unit-of-work/UnitOfWork.d.ts +39 -23
  140. package/unit-of-work/UnitOfWork.js +441 -246
  141. package/utils/AbstractMigrator.d.ts +101 -0
  142. package/utils/AbstractMigrator.js +303 -0
  143. package/utils/AbstractSchemaGenerator.d.ts +5 -5
  144. package/utils/AbstractSchemaGenerator.js +30 -18
  145. package/utils/AsyncContext.d.ts +6 -0
  146. package/utils/AsyncContext.js +42 -0
  147. package/utils/Configuration.d.ts +647 -185
  148. package/utils/Configuration.js +215 -252
  149. package/utils/ConfigurationLoader.d.ts +1 -52
  150. package/utils/ConfigurationLoader.js +1 -330
  151. package/utils/Cursor.d.ts +3 -6
  152. package/utils/Cursor.js +32 -17
  153. package/utils/DataloaderUtils.d.ts +10 -5
  154. package/utils/DataloaderUtils.js +42 -22
  155. package/utils/EntityComparator.d.ts +21 -21
  156. package/utils/EntityComparator.js +224 -118
  157. package/utils/QueryHelper.d.ts +34 -7
  158. package/utils/QueryHelper.js +183 -72
  159. package/utils/RawQueryFragment.d.ts +28 -34
  160. package/utils/RawQueryFragment.js +37 -72
  161. package/utils/RequestContext.js +2 -2
  162. package/utils/TransactionContext.js +2 -2
  163. package/utils/TransactionManager.js +11 -8
  164. package/utils/Utils.d.ts +16 -127
  165. package/utils/Utils.js +104 -402
  166. package/utils/clone.js +13 -23
  167. package/utils/env-vars.d.ts +7 -0
  168. package/utils/env-vars.js +98 -0
  169. package/utils/fs-utils.d.ts +20 -0
  170. package/utils/fs-utils.js +193 -0
  171. package/utils/index.d.ts +1 -3
  172. package/utils/index.js +1 -3
  173. package/utils/upsert-utils.d.ts +9 -4
  174. package/utils/upsert-utils.js +51 -5
  175. package/decorators/Check.d.ts +0 -3
  176. package/decorators/Check.js +0 -13
  177. package/decorators/CreateRequestContext.d.ts +0 -3
  178. package/decorators/CreateRequestContext.js +0 -32
  179. package/decorators/Embeddable.d.ts +0 -8
  180. package/decorators/Embeddable.js +0 -11
  181. package/decorators/Embedded.d.ts +0 -12
  182. package/decorators/Embedded.js +0 -18
  183. package/decorators/Entity.d.ts +0 -33
  184. package/decorators/Entity.js +0 -12
  185. package/decorators/Enum.d.ts +0 -9
  186. package/decorators/Enum.js +0 -16
  187. package/decorators/Filter.d.ts +0 -2
  188. package/decorators/Filter.js +0 -8
  189. package/decorators/Formula.d.ts +0 -4
  190. package/decorators/Formula.js +0 -15
  191. package/decorators/Indexed.d.ts +0 -19
  192. package/decorators/Indexed.js +0 -20
  193. package/decorators/ManyToMany.d.ts +0 -42
  194. package/decorators/ManyToMany.js +0 -14
  195. package/decorators/ManyToOne.d.ts +0 -34
  196. package/decorators/ManyToOne.js +0 -14
  197. package/decorators/OneToMany.d.ts +0 -28
  198. package/decorators/OneToMany.js +0 -17
  199. package/decorators/OneToOne.d.ts +0 -28
  200. package/decorators/OneToOne.js +0 -7
  201. package/decorators/PrimaryKey.d.ts +0 -8
  202. package/decorators/PrimaryKey.js +0 -20
  203. package/decorators/Property.d.ts +0 -250
  204. package/decorators/Property.js +0 -32
  205. package/decorators/Transactional.d.ts +0 -14
  206. package/decorators/Transactional.js +0 -28
  207. package/decorators/hooks.d.ts +0 -16
  208. package/decorators/hooks.js +0 -47
  209. package/decorators/index.d.ts +0 -17
  210. package/decorators/index.js +0 -17
  211. package/entity/ArrayCollection.d.ts +0 -118
  212. package/entity/ArrayCollection.js +0 -407
  213. package/entity/EntityValidator.d.ts +0 -19
  214. package/entity/EntityValidator.js +0 -150
  215. package/metadata/ReflectMetadataProvider.d.ts +0 -8
  216. package/metadata/ReflectMetadataProvider.js +0 -44
  217. package/utils/resolveContextProvider.d.ts +0 -10
  218. package/utils/resolveContextProvider.js +0 -28
@@ -1,17 +1,30 @@
1
- import { ArrayCollection } from './ArrayCollection.js';
2
1
  import { Utils } from '../utils/Utils.js';
3
- import { ValidationError } from '../errors.js';
4
- import { ReferenceKind, DataloaderType } from '../enums.js';
2
+ import { MetadataError, ValidationError } from '../errors.js';
3
+ import { DataloaderType, ReferenceKind } from '../enums.js';
5
4
  import { Reference } from './Reference.js';
6
- import { helper } from './wrap.js';
7
- export class Collection extends ArrayCollection {
8
- readonly;
9
- _populated;
10
- // this is for some reason needed for TS, otherwise it can fail with `Type instantiation is excessively deep and possibly infinite.`
11
- _snapshot;
5
+ import { helper, wrap } from './wrap.js';
6
+ import { QueryHelper } from '../utils/QueryHelper.js';
7
+ import { inspect } from '../logging/inspect.js';
8
+ export class Collection {
9
+ owner;
10
+ #items = new Set();
11
+ #initialized = true;
12
+ #dirty = false;
13
+ #partial = false; // mark partially loaded collections, propagation is disabled for those
14
+ #snapshot = []; // used to create a diff of the collection at commit time, undefined marks overridden values so we need to wipe when flushing
15
+ #readonly;
16
+ #count;
17
+ #property;
18
+ #populated;
12
19
  constructor(owner, items, initialized = true) {
13
- super(owner, items);
14
- this.initialized = !!items || initialized;
20
+ this.owner = owner;
21
+ /* v8 ignore next */
22
+ if (items) {
23
+ let i = 0;
24
+ this.#items = new Set(items);
25
+ this.#items.forEach(item => (this[i++] = item));
26
+ }
27
+ this.#initialized = !!items || initialized;
15
28
  }
16
29
  /**
17
30
  * Creates new Collection instance, assigns it to the owning entity and sets the items to it (propagating them to their inverse sides)
@@ -31,8 +44,9 @@ export class Collection extends ArrayCollection {
31
44
  */
32
45
  async load(options = {}) {
33
46
  if (this.isInitialized(true) && !options.refresh) {
34
- const em = this.getEntityManager(this.items, false);
35
- await em?.populate(this.items, options.populate, options);
47
+ const em = this.getEntityManager(this.#items, false);
48
+ options = { ...options, filters: QueryHelper.mergePropertyFilters(this.property.filters, options.filters) };
49
+ await em?.populate(this.#items, options.populate, options);
36
50
  this.setSerializationContext(options);
37
51
  }
38
52
  else {
@@ -44,7 +58,7 @@ export class Collection extends ArrayCollection {
44
58
  helper(this.owner).setSerializationContext({
45
59
  populate: Array.isArray(options.populate)
46
60
  ? options.populate.map(hint => `${this.property.name}.${hint}`)
47
- : options.populate ?? [this.property.name],
61
+ : (options.populate ?? [this.property.name]),
48
62
  });
49
63
  }
50
64
  /**
@@ -52,7 +66,7 @@ export class Collection extends ArrayCollection {
52
66
  */
53
67
  async loadItems(options) {
54
68
  await this.load(options);
55
- return super.getItems();
69
+ return this.getItems(false);
56
70
  }
57
71
  /**
58
72
  * Gets the count of collection items from database instead of counting loaded items.
@@ -61,38 +75,47 @@ export class Collection extends ArrayCollection {
61
75
  async loadCount(options = {}) {
62
76
  options = typeof options === 'boolean' ? { refresh: options } : options;
63
77
  const { refresh, where, ...countOptions } = options;
64
- if (!refresh && !where && Utils.isDefined(this._count)) {
65
- return this._count;
78
+ if (!refresh && !where && this.#count != null) {
79
+ return this.#count;
66
80
  }
67
81
  const em = this.getEntityManager();
68
- if (!em.getPlatform().usesPivotTable() && this.property.kind === ReferenceKind.MANY_TO_MANY && this.property.owner) {
69
- return this._count = this.length;
82
+ if (!em.getPlatform().usesPivotTable() &&
83
+ this.property.kind === ReferenceKind.MANY_TO_MANY &&
84
+ this.property.owner) {
85
+ return (this.#count = this.length);
70
86
  }
71
87
  const cond = this.createLoadCountCondition(where ?? {});
72
- const count = await em.count(this.property.type, cond, countOptions);
88
+ const count = await em.count(this.property.targetMeta.class, cond, countOptions);
73
89
  if (!where) {
74
- this._count = count;
90
+ this.#count = count;
75
91
  }
76
92
  return count;
77
93
  }
78
94
  async matching(options) {
79
95
  const em = this.getEntityManager();
80
96
  const { where, ctx, ...opts } = options;
81
- opts.orderBy = this.createOrderBy(opts.orderBy);
82
97
  let items;
83
98
  if (this.property.kind === ReferenceKind.MANY_TO_MANY && em.getPlatform().usesPivotTable()) {
84
- const cond = await em.applyFilters(this.property.type, where, options.filters ?? {}, 'read');
85
- const map = await em.getDriver().loadFromPivotTable(this.property, [helper(this.owner).__primaryKeys], cond, opts.orderBy, ctx, options);
86
- items = map[helper(this.owner).getSerializedPrimaryKey()].map((item) => em.merge(this.property.type, item, { convertCustomTypes: true }));
99
+ // M:N via pivot table bypasses em.find(), so merge all 3 levels here
100
+ opts.orderBy = QueryHelper.mergeOrderBy(opts.orderBy, this.property.orderBy, this.property.targetMeta?.orderBy);
101
+ options.populate = (await em.preparePopulate(this.property.targetMeta.class, options));
102
+ const cond = (await em.applyFilters(this.property.targetMeta.class, where, options.filters ?? {}, 'read'));
103
+ const map = await em
104
+ .getDriver()
105
+ .loadFromPivotTable(this.property, [helper(this.owner).__primaryKeys], cond, opts.orderBy, ctx, options);
106
+ items = map[helper(this.owner).getSerializedPrimaryKey()].map((item) => em.merge(this.property.targetMeta.class, item, { convertCustomTypes: true }));
107
+ await em.populate(items, options.populate, options);
87
108
  }
88
109
  else {
89
- items = await em.find(this.property.type, this.createCondition(where), opts);
110
+ // em.find() merges entity-level orderBy, so only merge runtime + relation here
111
+ opts.orderBy = QueryHelper.mergeOrderBy(opts.orderBy, this.property.orderBy);
112
+ items = (await em.find(this.property.targetMeta.class, this.createCondition(where), opts));
90
113
  }
91
114
  if (options.store) {
92
115
  this.hydrate(items, true);
93
116
  this.setSerializationContext(options);
94
117
  this.populated();
95
- this.readonly = true;
118
+ this.#readonly = true;
96
119
  }
97
120
  return items;
98
121
  }
@@ -103,154 +126,149 @@ export class Collection extends ArrayCollection {
103
126
  if (check) {
104
127
  this.checkInitialized();
105
128
  }
106
- return super.getItems();
129
+ return [...this.#items];
107
130
  }
108
131
  toJSON() {
109
132
  if (!this.isInitialized()) {
110
133
  return [];
111
134
  }
112
- return super.toJSON();
135
+ return this.toArray();
113
136
  }
114
137
  add(entity, ...entities) {
115
138
  entities = Utils.asArray(entity).concat(entities);
116
139
  const unwrapped = entities.map(i => Reference.unwrapReference(i));
117
- unwrapped.forEach(entity => this.validateItemType(entity));
118
- this.modify('add', unwrapped);
140
+ this.validateModification(unwrapped);
141
+ const em = this.getEntityManager(entities, false);
142
+ let added = 0;
143
+ for (const item of entities) {
144
+ const entity = Reference.unwrapReference(item);
145
+ if (!this.contains(entity, false)) {
146
+ this.incrementCount(1);
147
+ this[this.#items.size] = entity;
148
+ this.#items.add(entity);
149
+ added++;
150
+ this.#dirty = true;
151
+ this.propagate(entity, 'add');
152
+ }
153
+ }
154
+ if (this.property.kind === ReferenceKind.ONE_TO_MANY && em) {
155
+ em.persist(entities);
156
+ }
119
157
  this.cancelOrphanRemoval(unwrapped);
158
+ return added;
120
159
  }
121
160
  /**
122
- * @inheritDoc
161
+ * Remove specified item(s) from the collection. Note that removing item from collection does not necessarily imply deleting the target entity,
162
+ * it means we are disconnecting the relation - removing items from collection, not removing entities from database - `Collection.remove()`
163
+ * is not the same as `em.remove()`. If we want to delete the entity by removing it from collection, we need to enable `orphanRemoval: true`,
164
+ * which tells the ORM we don't want orphaned entities to exist, so we know those should be removed.
123
165
  */
124
166
  remove(entity, ...entities) {
125
167
  if (entity instanceof Function) {
126
- for (const item of this.items) {
168
+ let removed = 0;
169
+ for (const item of this.#items) {
127
170
  if (entity(item)) {
128
- this.remove(item);
171
+ removed += this.remove(item);
129
172
  }
130
173
  }
131
- return;
174
+ return removed;
132
175
  }
176
+ this.checkInitialized();
133
177
  entities = Utils.asArray(entity).concat(entities);
134
178
  const unwrapped = entities.map(i => Reference.unwrapReference(i));
135
- this.modify('remove', unwrapped);
136
- const em = this.getEntityManager(unwrapped, false);
137
- if (this.property.orphanRemoval && em) {
138
- for (const item of unwrapped) {
139
- em.getUnitOfWork().scheduleOrphanRemoval(item);
179
+ this.validateModification(unwrapped);
180
+ const em = this.getEntityManager(entities, false);
181
+ let removed = 0;
182
+ for (const item of entities) {
183
+ if (!item) {
184
+ continue;
185
+ }
186
+ const entity = Reference.unwrapReference(item);
187
+ if (this.#items.delete(entity)) {
188
+ this.incrementCount(-1);
189
+ delete this[this.#items.size]; // remove last item
190
+ this.propagate(entity, 'remove');
191
+ removed++;
192
+ this.#dirty = true;
140
193
  }
194
+ if (this.property.orphanRemoval && em) {
195
+ em.getUnitOfWork().scheduleOrphanRemoval(entity);
196
+ }
197
+ }
198
+ if (this.property.kind === ReferenceKind.ONE_TO_MANY && !this.property.orphanRemoval && em) {
199
+ em.persist(entities);
200
+ }
201
+ if (removed > 0) {
202
+ Object.assign(this, [...this.#items]); // reassign array access
141
203
  }
204
+ return removed;
142
205
  }
143
206
  contains(item, check = true) {
144
207
  if (check) {
145
208
  this.checkInitialized();
146
209
  }
147
- return super.contains(item);
210
+ const entity = Reference.unwrapReference(item);
211
+ return this.#items.has(entity);
148
212
  }
149
213
  count() {
150
214
  this.checkInitialized();
151
- return super.count();
215
+ return this.#items.size;
152
216
  }
153
217
  isEmpty() {
154
218
  this.checkInitialized();
155
- return super.isEmpty();
156
- }
157
- /**
158
- * @inheritDoc
159
- */
160
- slice(start, end) {
161
- this.checkInitialized();
162
- return super.slice(start, end);
163
- }
164
- /**
165
- * @inheritDoc
166
- */
167
- exists(cb) {
168
- this.checkInitialized();
169
- return super.exists(cb);
170
- }
171
- /**
172
- * @inheritDoc
173
- */
174
- find(cb) {
175
- this.checkInitialized();
176
- return super.find(cb);
177
- }
178
- /**
179
- * @inheritDoc
180
- */
181
- filter(cb) {
182
- this.checkInitialized();
183
- return super.filter(cb);
184
- }
185
- /**
186
- * @inheritDoc
187
- */
188
- map(mapper) {
189
- this.checkInitialized();
190
- return super.map(mapper);
191
- }
192
- /**
193
- * @inheritDoc
194
- */
195
- indexBy(key, valueKey) {
196
- this.checkInitialized();
197
- return super.indexBy(key, valueKey);
219
+ return this.count() === 0;
198
220
  }
199
221
  shouldPopulate(populated) {
200
222
  if (!this.isInitialized(true)) {
201
223
  return false;
202
224
  }
203
- if (this._populated != null) {
204
- return this._populated;
225
+ if (this.#populated != null) {
226
+ return this.#populated;
205
227
  }
206
228
  return !!populated;
207
229
  }
208
230
  populated(populated = true) {
209
- this._populated = populated;
231
+ this.#populated = populated;
210
232
  }
211
233
  async init(options = {}) {
212
- if (this.dirty) {
213
- const items = [...this.items];
214
- this.dirty = false;
234
+ if (this.#dirty) {
235
+ const items = [...this.#items];
236
+ this.#dirty = false;
215
237
  await this.init(options);
216
238
  items.forEach(i => this.add(i));
217
239
  return this;
218
240
  }
219
241
  const em = this.getEntityManager();
242
+ options = { ...options, filters: QueryHelper.mergePropertyFilters(this.property.filters, options.filters) };
220
243
  if (options.dataloader ?? [DataloaderType.ALL, DataloaderType.COLLECTION].includes(em.config.getDataloaderType())) {
221
- const order = [...this.items]; // copy order of references
222
- const orderBy = this.createOrderBy(options.orderBy);
244
+ const order = [...this.#items]; // copy order of references
245
+ const orderBy = QueryHelper.mergeOrderBy(options.orderBy, this.property.orderBy, this.property.targetMeta?.orderBy);
223
246
  const customOrder = orderBy.length > 0;
224
247
  const pivotTable = this.property.kind === ReferenceKind.MANY_TO_MANY && em.getPlatform().usesPivotTable();
225
- const loader = pivotTable ? 'colLoaderMtoN' : 'colLoader';
226
- const items = await em[loader].load([
227
- this,
228
- { ...options, orderBy },
229
- ]);
248
+ const loader = await em.getDataLoader(pivotTable ? 'm:n' : '1:m');
249
+ const items = await loader.load([this, { ...options, orderBy }]);
230
250
  if (this.property.kind === ReferenceKind.MANY_TO_MANY) {
231
- this.initialized = true;
232
- this.dirty = false;
251
+ this.#initialized = true;
252
+ this.#dirty = false;
233
253
  if (!customOrder) {
234
254
  this.reorderItems(items, order);
235
255
  }
236
256
  return this;
237
257
  }
238
- this.items.clear();
258
+ this.#items.clear();
239
259
  let i = 0;
240
260
  for (const item of items) {
241
- this.items.add(item);
261
+ this.#items.add(item);
242
262
  this[i++] = item;
243
263
  }
244
- this.initialized = true;
245
- this.dirty = false;
264
+ this.#initialized = true;
265
+ this.#dirty = false;
246
266
  return this;
247
267
  }
248
268
  const populate = Array.isArray(options.populate)
249
- ? options.populate.map(f => f === '*' ? f : `${this.property.name}.${f}`)
269
+ ? options.populate.map(f => (f === '*' ? f : `${this.property.name}.${f}`))
250
270
  : [`${this.property.name}${options.ref ? ':ref' : ''}`];
251
- const schema = this.property.targetMeta.schema === '*'
252
- ? helper(this.owner).__schema
253
- : undefined;
271
+ const schema = this.property.targetMeta.schema === '*' ? helper(this.owner).__schema : undefined;
254
272
  await em.populate(this.owner, populate, {
255
273
  refresh: true,
256
274
  ...options,
@@ -281,24 +299,19 @@ export class Collection extends ArrayCollection {
281
299
  if (this.property.kind === ReferenceKind.ONE_TO_MANY) {
282
300
  cond[this.property.mappedBy] = helper(this.owner).getPrimaryKey();
283
301
  }
284
- else { // MANY_TO_MANY
302
+ else {
303
+ // MANY_TO_MANY
285
304
  this.createManyToManyCondition(cond);
286
305
  }
287
306
  return cond;
288
307
  }
289
- createOrderBy(orderBy = []) {
290
- if (Utils.isEmpty(orderBy) && this.property.orderBy) {
291
- orderBy = this.property.orderBy;
292
- }
293
- return Utils.asArray(orderBy);
294
- }
295
308
  createManyToManyCondition(cond) {
296
309
  const dict = cond;
297
310
  if (this.property.owner || this.property.pivotTable) {
298
311
  // we know there is at least one item as it was checked in load method
299
312
  const pk = this.property.targetMeta.primaryKeys[0];
300
313
  dict[pk] = { $in: [] };
301
- this.items.forEach(item => dict[pk].$in.push(helper(item).getPrimaryKey()));
314
+ this.#items.forEach(item => dict[pk].$in.push(helper(item).getPrimaryKey()));
302
315
  }
303
316
  else {
304
317
  dict[this.property.mappedBy] = helper(this.owner).getPrimaryKey();
@@ -317,17 +330,9 @@ export class Collection extends ArrayCollection {
317
330
  }
318
331
  return cond;
319
332
  }
320
- modify(method, items) {
321
- if (method === 'remove') {
322
- this.checkInitialized();
323
- }
324
- this.validateModification(items);
325
- super[method](items);
326
- this.setDirty();
327
- }
328
333
  checkInitialized() {
329
334
  if (!this.isInitialized()) {
330
- throw new Error(`Collection<${this.property.type}> of entity ${this.owner.constructor.name}[${helper(this.owner).getSerializedPrimaryKey()}] not initialized`);
335
+ throw new Error(`Collection<${this.property.type}> of entity ${helper(this.owner).__meta.name}[${helper(this.owner).getSerializedPrimaryKey()}] not initialized`);
331
336
  }
332
337
  }
333
338
  /**
@@ -347,32 +352,375 @@ export class Collection extends ArrayCollection {
347
352
  em.getUnitOfWork().cancelOrphanRemoval(item);
348
353
  }
349
354
  }
350
- validateItemType(item) {
351
- if (!Utils.isEntity(item)) {
352
- throw ValidationError.notEntity(this.owner, this.property, item);
353
- }
354
- }
355
355
  validateModification(items) {
356
- if (this.readonly) {
356
+ if (this.#readonly) {
357
357
  throw ValidationError.cannotModifyReadonlyCollection(this.owner, this.property);
358
358
  }
359
- // currently we allow persisting to inverse sides only in SQL drivers
360
- if (this.property.pivotTable || !this.property.mappedBy) {
361
- return;
362
- }
363
359
  const check = (item) => {
364
- if (!item || helper(item).__initialized) {
360
+ if (!item) {
361
+ return false;
362
+ }
363
+ if (!Utils.isEntity(item)) {
364
+ throw ValidationError.notEntity(this.owner, this.property, item);
365
+ }
366
+ // currently we allow persisting to inverse sides only in SQL drivers
367
+ if (this.property.pivotTable || !this.property.mappedBy) {
368
+ return false;
369
+ }
370
+ if (helper(item).__initialized) {
365
371
  return false;
366
372
  }
367
373
  return !item[this.property.mappedBy] && this.property.kind === ReferenceKind.MANY_TO_MANY;
368
374
  };
369
375
  // throw if we are modifying inverse side of M:N collection when owning side is initialized (would be ignored when persisting)
370
- if (items.find(item => check(item))) {
376
+ if (items.some(item => check(item))) {
371
377
  throw ValidationError.cannotModifyInverseCollection(this.owner, this.property);
372
378
  }
373
379
  }
380
+ toArray() {
381
+ if (this.#items.size === 0) {
382
+ return [];
383
+ }
384
+ return this.map(item => wrap(item).toJSON());
385
+ }
386
+ getIdentifiers(field) {
387
+ const items = this.getItems();
388
+ const targetMeta = this.property.targetMeta;
389
+ if (items.length === 0) {
390
+ return [];
391
+ }
392
+ field ??= targetMeta.compositePK
393
+ ? targetMeta.primaryKeys
394
+ : (targetMeta.serializedPrimaryKey ?? targetMeta.primaryKeys[0]);
395
+ const cb = (i, f) => {
396
+ if (Utils.isEntity(i[f], true)) {
397
+ return wrap(i[f], true).getPrimaryKey();
398
+ }
399
+ return i[f];
400
+ };
401
+ return items.map(i => {
402
+ if (Array.isArray(field)) {
403
+ return field.map(f => cb(i, f));
404
+ }
405
+ return cb(i, field);
406
+ });
407
+ }
408
+ /**
409
+ * @internal
410
+ */
411
+ addWithoutPropagation(entity) {
412
+ if (!this.contains(entity, false)) {
413
+ this.incrementCount(1);
414
+ this[this.#items.size] = entity;
415
+ this.#items.add(entity);
416
+ this.#dirty = true;
417
+ }
418
+ }
419
+ set(items) {
420
+ if (!this.#initialized) {
421
+ this.#initialized = true;
422
+ this.#snapshot = undefined;
423
+ }
424
+ if (this.compare(Utils.asArray(items).map(item => Reference.unwrapReference(item)))) {
425
+ return;
426
+ }
427
+ this.remove(this.#items);
428
+ this.add(items);
429
+ }
430
+ compare(items) {
431
+ if (items.length !== this.#items.size) {
432
+ return false;
433
+ }
434
+ let idx = 0;
435
+ for (const item of this.#items) {
436
+ if (item !== items[idx++]) {
437
+ return false;
438
+ }
439
+ }
440
+ return true;
441
+ }
442
+ /**
443
+ * @internal
444
+ */
445
+ hydrate(items, forcePropagate, partial) {
446
+ for (let i = 0; i < this.#items.size; i++) {
447
+ delete this[i];
448
+ }
449
+ this.#initialized = true;
450
+ this.#partial = !!partial;
451
+ this.#items.clear();
452
+ this.#count = 0;
453
+ this.add(items);
454
+ this.takeSnapshot(forcePropagate);
455
+ }
456
+ /**
457
+ * Remove all items from the collection. Note that removing items from collection does not necessarily imply deleting the target entity,
458
+ * it means we are disconnecting the relation - removing items from collection, not removing entities from database - `Collection.remove()`
459
+ * is not the same as `em.remove()`. If we want to delete the entity by removing it from collection, we need to enable `orphanRemoval: true`,
460
+ * which tells the ORM we don't want orphaned entities to exist, so we know those should be removed.
461
+ */
462
+ removeAll() {
463
+ if (!this.#initialized) {
464
+ this.#initialized = true;
465
+ this.#snapshot = undefined;
466
+ }
467
+ this.remove(this.#items);
468
+ this.#dirty = true;
469
+ }
470
+ /**
471
+ * @internal
472
+ */
473
+ removeWithoutPropagation(entity) {
474
+ if (!this.#items.delete(entity)) {
475
+ return;
476
+ }
477
+ this.incrementCount(-1);
478
+ delete this[this.#items.size];
479
+ Object.assign(this, [...this.#items]);
480
+ this.#dirty = true;
481
+ }
482
+ /**
483
+ * Extracts a slice of the collection items starting at position start to end (exclusive) of the collection.
484
+ * If end is null it returns all elements from start to the end of the collection.
485
+ */
486
+ slice(start = 0, end) {
487
+ this.checkInitialized();
488
+ let index = 0;
489
+ end ??= this.#items.size;
490
+ const items = [];
491
+ for (const item of this.#items) {
492
+ if (index === end) {
493
+ break;
494
+ }
495
+ if (index >= start && index < end) {
496
+ items.push(item);
497
+ }
498
+ index++;
499
+ }
500
+ return items;
501
+ }
502
+ /**
503
+ * Tests for the existence of an element that satisfies the given predicate.
504
+ */
505
+ exists(cb) {
506
+ this.checkInitialized();
507
+ for (const item of this.#items) {
508
+ if (cb(item)) {
509
+ return true;
510
+ }
511
+ }
512
+ return false;
513
+ }
514
+ /**
515
+ * Returns the first element of this collection that satisfies the predicate.
516
+ */
517
+ find(cb) {
518
+ this.checkInitialized();
519
+ let index = 0;
520
+ for (const item of this.#items) {
521
+ if (cb(item, index++)) {
522
+ return item;
523
+ }
524
+ }
525
+ return undefined;
526
+ }
527
+ /**
528
+ * Extracts a subset of the collection items.
529
+ */
530
+ filter(cb) {
531
+ this.checkInitialized();
532
+ const items = [];
533
+ let index = 0;
534
+ for (const item of this.#items) {
535
+ if (cb(item, index++)) {
536
+ items.push(item);
537
+ }
538
+ }
539
+ return items;
540
+ }
541
+ /**
542
+ * Maps the collection items based on your provided mapper function.
543
+ */
544
+ map(mapper) {
545
+ this.checkInitialized();
546
+ const items = [];
547
+ let index = 0;
548
+ for (const item of this.#items) {
549
+ items.push(mapper(item, index++));
550
+ }
551
+ return items;
552
+ }
553
+ /**
554
+ * Maps the collection items based on your provided mapper function to a single object.
555
+ */
556
+ reduce(cb, initial = {}) {
557
+ this.checkInitialized();
558
+ let index = 0;
559
+ for (const item of this.#items) {
560
+ initial = cb(initial, item, index++);
561
+ }
562
+ return initial;
563
+ }
564
+ /**
565
+ * Maps the collection items to a dictionary, indexed by the key you specify.
566
+ * If there are more items with the same key, only the first one will be present.
567
+ */
568
+ indexBy(key, valueKey) {
569
+ return this.reduce((obj, item) => {
570
+ obj[item[key]] ??= valueKey ? item[valueKey] : item;
571
+ return obj;
572
+ }, {});
573
+ }
574
+ isInitialized(fully = false) {
575
+ if (!this.#initialized || !fully) {
576
+ return this.#initialized;
577
+ }
578
+ for (const item of this.#items) {
579
+ if (!helper(item).__initialized) {
580
+ return false;
581
+ }
582
+ }
583
+ return true;
584
+ }
585
+ isDirty() {
586
+ return this.#dirty;
587
+ }
588
+ isPartial() {
589
+ return this.#partial;
590
+ }
591
+ setDirty(dirty = true) {
592
+ this.#dirty = dirty;
593
+ }
594
+ get length() {
595
+ return this.count();
596
+ }
597
+ *[Symbol.iterator]() {
598
+ for (const item of this.getItems()) {
599
+ yield item;
600
+ }
601
+ }
602
+ /**
603
+ * @internal
604
+ */
605
+ takeSnapshot(forcePropagate) {
606
+ this.#snapshot = [...this.#items];
607
+ this.#dirty = false;
608
+ if (this.property.owner || forcePropagate) {
609
+ this.#items.forEach(item => {
610
+ this.propagate(item, 'takeSnapshot');
611
+ });
612
+ }
613
+ }
614
+ /**
615
+ * @internal
616
+ */
617
+ getSnapshot() {
618
+ return this.#snapshot;
619
+ }
620
+ /**
621
+ * @internal
622
+ */
623
+ get property() {
624
+ // cannot be typed to `EntityProperty<O, T>` as it causes issues in assignability of `Loaded` type
625
+ if (!this.#property) {
626
+ const meta = wrap(this.owner, true).__meta;
627
+ /* v8 ignore next */
628
+ if (!meta) {
629
+ throw MetadataError.fromUnknownEntity(this.owner.constructor.name, 'Collection.property getter, maybe you just forgot to initialize the ORM?');
630
+ }
631
+ this.#property = meta.relations.find(prop => this.owner[prop.name] === this);
632
+ }
633
+ return this.#property;
634
+ }
635
+ /**
636
+ * @internal
637
+ */
638
+ set property(prop) {
639
+ // cannot be typed to `EntityProperty<O, T>` as it causes issues in assignability of `Loaded` type
640
+ this.#property = prop;
641
+ }
642
+ propagate(item, method) {
643
+ if (this.property.owner && this.property.inversedBy) {
644
+ this.propagateToInverseSide(item, method);
645
+ }
646
+ else if (!this.property.owner && this.property.mappedBy) {
647
+ this.propagateToOwningSide(item, method);
648
+ }
649
+ }
650
+ propagateToInverseSide(item, method) {
651
+ const collection = item[this.property.inversedBy];
652
+ if (this.shouldPropagateToCollection(collection, method)) {
653
+ method = method === 'takeSnapshot' ? method : (method + 'WithoutPropagation');
654
+ collection[method](this.owner);
655
+ }
656
+ }
657
+ propagateToOwningSide(item, method) {
658
+ const mappedBy = this.property.mappedBy;
659
+ const collection = item[mappedBy];
660
+ if (this.property.kind === ReferenceKind.MANY_TO_MANY) {
661
+ if (this.shouldPropagateToCollection(collection, method)) {
662
+ collection[method](this.owner);
663
+ }
664
+ }
665
+ else if (this.property.kind === ReferenceKind.ONE_TO_MANY && method !== 'takeSnapshot') {
666
+ const prop2 = this.property.targetMeta.properties[mappedBy];
667
+ const owner = prop2.mapToPk ? helper(this.owner).getPrimaryKey() : this.owner;
668
+ const value = method === 'add' ? owner : null;
669
+ if (this.property.orphanRemoval && method === 'remove') {
670
+ // cache the PK before we propagate, as its value might be needed when flushing
671
+ helper(item).__pk = helper(item).getPrimaryKey();
672
+ }
673
+ if (!prop2.nullable && prop2.deleteRule !== 'cascade' && method === 'remove') {
674
+ if (!this.property.orphanRemoval) {
675
+ throw ValidationError.cannotRemoveFromCollectionWithoutOrphanRemoval(this.owner, this.property);
676
+ }
677
+ return;
678
+ }
679
+ // skip if already propagated
680
+ if (Reference.unwrapReference(item[mappedBy]) !== value) {
681
+ item[mappedBy] = value;
682
+ }
683
+ }
684
+ }
685
+ shouldPropagateToCollection(collection, method) {
686
+ if (!collection) {
687
+ return false;
688
+ }
689
+ switch (method) {
690
+ case 'add':
691
+ return !collection.contains(this.owner, false);
692
+ case 'remove':
693
+ return collection.isInitialized() && collection.contains(this.owner, false);
694
+ case 'takeSnapshot':
695
+ return collection.isDirty();
696
+ }
697
+ }
698
+ incrementCount(value) {
699
+ if (typeof this.#count === 'number' && this.#initialized) {
700
+ this.#count += value;
701
+ }
702
+ }
703
+ /** @ignore */
704
+ [Symbol.for('nodejs.util.inspect.custom')](depth = 2) {
705
+ const object = { ...this };
706
+ delete object.owner;
707
+ object.initialized = this.#initialized;
708
+ object.dirty = this.#dirty;
709
+ const ret = inspect(object, { depth });
710
+ const name = `${this.constructor.name}<${this.property?.type ?? 'unknown'}>`;
711
+ return ret === '[Object]' ? `[${name}]` : name + ' ' + ret;
712
+ }
374
713
  }
375
714
  Object.defineProperties(Collection.prototype, {
376
- $: { get() { return this; } },
377
- get: { get() { return () => this; } },
715
+ $: {
716
+ get() {
717
+ return this;
718
+ },
719
+ },
720
+ get: {
721
+ get() {
722
+ return () => this;
723
+ },
724
+ },
725
+ __collection: { value: true, enumerable: false, writable: false },
378
726
  });