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

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.
@@ -9,7 +9,7 @@ import { type EntityRepository } from './entity/EntityRepository.js';
9
9
  import { EntityLoader, type EntityLoaderOptions } from './entity/EntityLoader.js';
10
10
  import { Reference } from './entity/Reference.js';
11
11
  import { UnitOfWork } from './unit-of-work/UnitOfWork.js';
12
- import type { CountOptions, DeleteOptions, FindAllOptions, FindByCursorOptions, FindOneOptions, FindOneOrFailOptions, FindOptions, GetReferenceOptions, IDatabaseDriver, LockOptions, NativeInsertUpdateOptions, UpdateOptions, UpsertManyOptions, UpsertOptions } from './drivers/IDatabaseDriver.js';
12
+ import type { CountOptions, DeleteOptions, FindAllOptions, FindByCursorOptions, FindOneOptions, FindOneOrFailOptions, FindOptions, GetReferenceOptions, IDatabaseDriver, LockOptions, NativeInsertUpdateOptions, StreamOptions, UpdateOptions, UpsertManyOptions, UpsertOptions } from './drivers/IDatabaseDriver.js';
13
13
  import type { AnyEntity, AnyString, ArrayElement, AutoPath, ConnectionType, Dictionary, EntityData, EntityDictionary, EntityDTO, EntityMetadata, EntityName, FilterQuery, FromEntityType, GetRepository, IHydrator, IsSubset, Loaded, MaybePromise, MergeLoaded, MergeSelected, NoInfer, ObjectQuery, Primary, Ref, RequiredEntityData, UnboxArray } from './typings.js';
14
14
  import { FlushMode, LockMode, PopulatePath, type TransactionOptions } from './enums.js';
15
15
  import type { MetadataStorage } from './metadata/MetadataStorage.js';
@@ -81,6 +81,24 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
81
81
  * Finds all entities matching your `where` query. You can pass additional options via the `options` parameter.
82
82
  */
83
83
  find<Entity extends object, Hint extends string = never, Fields extends string = PopulatePath.ALL, Excludes extends string = never>(entityName: EntityName<Entity>, where: FilterQuery<NoInfer<Entity>>, options?: FindOptions<Entity, Hint, Fields, Excludes>): Promise<Loaded<Entity, Hint, Fields, Excludes>[]>;
84
+ /**
85
+ * Finds all entities and returns an async iterable (async generator) that yields results one by one.
86
+ * The results are merged and mapped to entity instances, without adding them to the identity map.
87
+ * You can disable merging by passing the options `{ mergeResults: false }`.
88
+ * With `mergeResults` disabled, to-many collections will contain at most one item, and you will get duplicate
89
+ * root entities when there are multiple items in the populated collection.
90
+ * This is useful for processing large datasets without loading everything into memory at once.
91
+ *
92
+ * ```ts
93
+ * const stream = em.stream(Book, { populate: ['author'] });
94
+ *
95
+ * for await (const book of stream) {
96
+ * // book is an instance of Book entity
97
+ * console.log(book.title, book.author.name);
98
+ * }
99
+ * ```
100
+ */
101
+ stream<Entity extends object, Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(entityName: EntityName<Entity>, options?: StreamOptions<NoInfer<Entity>, Hint, Fields, Excludes>): AsyncIterableIterator<Loaded<Entity, Hint, Fields, Excludes>>;
84
102
  /**
85
103
  * Finds all entities of given type, optionally matching the `where` condition provided in the `options` parameter.
86
104
  */
package/EntityManager.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { inspect } from 'node:util';
2
2
  import DataLoader from 'dataloader';
3
- import { getOnConflictReturningFields } from './utils/upsert-utils.js';
3
+ import { getOnConflictReturningFields, getWhereCondition } from './utils/upsert-utils.js';
4
4
  import { Utils } from './utils/Utils.js';
5
5
  import { Cursor } from './utils/Cursor.js';
6
6
  import { DataloaderUtils } from './utils/DataloaderUtils.js';
@@ -182,6 +182,61 @@ export class EntityManager {
182
182
  }
183
183
  return unique;
184
184
  }
185
+ /**
186
+ * Finds all entities and returns an async iterable (async generator) that yields results one by one.
187
+ * The results are merged and mapped to entity instances, without adding them to the identity map.
188
+ * You can disable merging by passing the options `{ mergeResults: false }`.
189
+ * With `mergeResults` disabled, to-many collections will contain at most one item, and you will get duplicate
190
+ * root entities when there are multiple items in the populated collection.
191
+ * This is useful for processing large datasets without loading everything into memory at once.
192
+ *
193
+ * ```ts
194
+ * const stream = em.stream(Book, { populate: ['author'] });
195
+ *
196
+ * for await (const book of stream) {
197
+ * // book is an instance of Book entity
198
+ * console.log(book.title, book.author.name);
199
+ * }
200
+ * ```
201
+ */
202
+ async *stream(entityName, options = {}) {
203
+ const em = this.getContext();
204
+ em.prepareOptions(options);
205
+ options.strategy = 'joined';
206
+ await em.tryFlush(entityName, options);
207
+ entityName = Utils.className(entityName);
208
+ const where = await em.processWhere(entityName, options.where ?? {}, options, 'read');
209
+ em.validator.validateParams(where);
210
+ options.orderBy = options.orderBy || {};
211
+ options.populate = await em.preparePopulate(entityName, options);
212
+ const meta = this.metadata.get(entityName);
213
+ options = { ...options };
214
+ // save the original hint value so we know it was infer/all
215
+ options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
216
+ options.populateWhere = this.createPopulateWhere({ ...where }, options);
217
+ options.populateFilter = await this.getJoinedFilters(meta, { ...where }, options);
218
+ const stream = em.driver.stream(entityName, where, {
219
+ ctx: em.transactionContext,
220
+ mapResults: false,
221
+ ...options,
222
+ });
223
+ for await (const data of stream) {
224
+ const fork = em.fork();
225
+ const entity = fork.entityFactory.create(entityName, data, {
226
+ refresh: options.refresh,
227
+ schema: options.schema,
228
+ convertCustomTypes: true,
229
+ });
230
+ helper(entity).setSerializationContext({
231
+ populate: options.populate,
232
+ fields: options.fields,
233
+ exclude: options.exclude,
234
+ });
235
+ await fork.unitOfWork.dispatchOnLoadEvent();
236
+ fork.clear();
237
+ yield entity;
238
+ }
239
+ }
185
240
  /**
186
241
  * Finds all entities of given type, optionally matching the `where` condition provided in the `options` parameter.
187
242
  */
@@ -685,24 +740,7 @@ export class EntityManager {
685
740
  }
686
741
  }
687
742
  }
688
- const unique = options.onConflictFields ?? meta.props.filter(p => p.unique).map(p => p.name);
689
- const propIndex = !isRaw(unique) && unique.findIndex(p => data[p] != null);
690
- if (options.onConflictFields || where == null) {
691
- if (propIndex !== false && propIndex >= 0) {
692
- where = { [unique[propIndex]]: data[unique[propIndex]] };
693
- }
694
- else if (meta.uniques.length > 0) {
695
- for (const u of meta.uniques) {
696
- if (Utils.asArray(u.properties).every(p => data[p] != null)) {
697
- where = Utils.asArray(u.properties).reduce((o, key) => {
698
- o[key] = data[key];
699
- return o;
700
- }, {});
701
- break;
702
- }
703
- }
704
- }
705
- }
743
+ where = getWhereCondition(meta, options.onConflictFields, data, where).where;
706
744
  data = QueryHelper.processObjectParams(data);
707
745
  em.validator.validateParams(data, 'insert data');
708
746
  if (em.eventManager.hasListeners(EventType.beforeUpsert, meta)) {
@@ -845,31 +883,17 @@ export class EntityManager {
845
883
  }
846
884
  }
847
885
  }
848
- const unique = meta.props.filter(p => p.unique).map(p => p.name);
849
- propIndex = unique.findIndex(p => row[p] != null);
850
- if (options.onConflictFields || where == null) {
851
- if (propIndex >= 0) {
852
- where = { [unique[propIndex]]: row[unique[propIndex]] };
853
- }
854
- else if (meta.uniques.length > 0) {
855
- for (const u of meta.uniques) {
856
- if (Utils.asArray(u.properties).every(p => row[p] != null)) {
857
- where = Utils.asArray(u.properties).reduce((o, key) => {
858
- o[key] = row[key];
859
- return o;
860
- }, {});
861
- break;
862
- }
863
- }
864
- }
865
- }
866
- row = QueryHelper.processObjectParams(row);
886
+ const unique = options.onConflictFields ?? meta.props.filter(p => p.unique).map(p => p.name);
887
+ propIndex = !isRaw(unique) && unique.findIndex(p => data[p] ?? data[p.substring(0, p.indexOf('.'))] != null);
888
+ const tmp = getWhereCondition(meta, options.onConflictFields, row, where);
889
+ propIndex = tmp.propIndex;
867
890
  where = QueryHelper.processWhere({
868
- where,
891
+ where: tmp.where,
869
892
  entityName,
870
893
  metadata: this.metadata,
871
894
  platform: this.getPlatform(),
872
895
  });
896
+ row = QueryHelper.processObjectParams(row);
873
897
  em.validator.validateParams(row, 'insert data');
874
898
  allData.push(row);
875
899
  allWhere.push(where);
@@ -911,7 +935,7 @@ export class EntityManager {
911
935
  const reloadFields = returning.length > 0 && !(this.getPlatform().usesReturningStatement() && res.rows?.length);
912
936
  if (options.onConflictAction === 'ignore' || (!res.rows?.length && loadPK.size > 0) || reloadFields) {
913
937
  const unique = meta.hydrateProps.filter(p => !p.lazy).map(p => p.name);
914
- const add = new Set(propIndex >= 0 ? [unique[propIndex]] : []);
938
+ const add = new Set(propIndex !== false && propIndex >= 0 ? [unique[propIndex]] : []);
915
939
  for (const cond of loadPK.values()) {
916
940
  Utils.keys(cond).forEach(key => add.add(key));
917
941
  }
@@ -27,7 +27,7 @@ export type EntityOptions<T, E = T extends EntityClass<infer P> ? P : T> = {
27
27
  /** Used to make ORM aware of externally defined triggers. This is needed for MS SQL Server multi inserts, ignored in other dialects. */
28
28
  hasTriggers?: boolean;
29
29
  /** SQL query that maps to a {@doclink virtual-entities | virtual entity}. */
30
- expression?: string | ((em: any, where: ObjectQuery<E>, options: FindOptions<E, any, any, any>) => object);
30
+ expression?: string | ((em: any, where: ObjectQuery<E>, options: FindOptions<E, any, any, any>, stream?: boolean) => object);
31
31
  /** Set {@doclink repositories#custom-repository | custom repository class}. */
32
32
  repository?: () => Constructor;
33
33
  };
@@ -1,5 +1,5 @@
1
- import { type CountOptions, type DeleteOptions, type DriverMethodOptions, EntityManagerType, type FindOneOptions, type FindOptions, type IDatabaseDriver, type LockOptions, type NativeInsertUpdateManyOptions, type NativeInsertUpdateOptions, type OrderDefinition } from './IDatabaseDriver.js';
2
- import type { ConnectionType, Dictionary, EntityData, EntityDictionary, EntityMetadata, EntityProperty, FilterQuery, PopulateOptions, Primary } from '../typings.js';
1
+ import { type CountOptions, type DeleteOptions, type DriverMethodOptions, EntityManagerType, type FindOneOptions, type FindOptions, type IDatabaseDriver, type LockOptions, type NativeInsertUpdateManyOptions, type NativeInsertUpdateOptions, type OrderDefinition, type StreamOptions } from './IDatabaseDriver.js';
2
+ import type { ConnectionType, Dictionary, EntityData, EntityDictionary, EntityMetadata, EntityName, EntityProperty, FilterQuery, PopulateOptions, Primary } from '../typings.js';
3
3
  import type { MetadataStorage } from '../metadata/MetadataStorage.js';
4
4
  import type { Connection, QueryResult, Transaction } from '../connections/Connection.js';
5
5
  import { type Configuration, type ConnectionOptions } from '../utils/Configuration.js';
@@ -30,7 +30,7 @@ export declare abstract class DatabaseDriver<C extends Connection> implements ID
30
30
  abstract nativeDelete<T extends object>(entityName: string, where: FilterQuery<T>, options?: DeleteOptions<T>): Promise<QueryResult<T>>;
31
31
  abstract count<T extends object, P extends string = never>(entityName: string, where: FilterQuery<T>, options?: CountOptions<T, P>): Promise<number>;
32
32
  createEntityManager(useContext?: boolean): this[typeof EntityManagerType];
33
- findVirtual<T extends object>(entityName: string, where: FilterQuery<T>, options: FindOptions<T, any, any, any>): Promise<EntityData<T>[]>;
33
+ findVirtual<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, options: FindOptions<T, any, any, any>): Promise<EntityData<T>[]>;
34
34
  countVirtual<T extends object>(entityName: string, where: FilterQuery<T>, options: CountOptions<T, any>): Promise<number>;
35
35
  aggregate(entityName: string, pipeline: any[]): Promise<any[]>;
36
36
  loadFromPivotTable<T extends object, O extends object>(prop: EntityProperty, owners: Primary<O>[][], where?: FilterQuery<any>, orderBy?: OrderDefinition<T>, ctx?: Transaction, options?: FindOptions<T, any, any, any>, pivotJoin?: boolean): Promise<Dictionary<T[]>>;
@@ -55,6 +55,7 @@ export declare abstract class DatabaseDriver<C extends Connection> implements ID
55
55
  protected getPrimaryKeyFields(entityName: string): string[];
56
56
  protected createReplicas(cb: (c: ConnectionOptions) => C): C[];
57
57
  lockPessimistic<T extends object>(entity: T, options: LockOptions): Promise<void>;
58
+ abstract stream<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, options: StreamOptions<T>): AsyncIterableIterator<T>;
58
59
  /**
59
60
  * @inheritDoc
60
61
  */
@@ -1,4 +1,4 @@
1
- import type { ConnectionType, EntityData, EntityMetadata, EntityProperty, FilterQuery, Primary, Dictionary, QBFilterQuery, IPrimaryKey, PopulateOptions, EntityDictionary, AutoPath, ObjectQuery, FilterObject, Populate } from '../typings.js';
1
+ import type { ConnectionType, EntityData, EntityMetadata, EntityProperty, FilterQuery, Primary, Dictionary, QBFilterQuery, IPrimaryKey, PopulateOptions, EntityDictionary, AutoPath, ObjectQuery, FilterObject, Populate, EntityName } from '../typings.js';
2
2
  import type { Connection, QueryResult, Transaction } from '../connections/Connection.js';
3
3
  import type { FlushMode, LockMode, QueryOrderMap, QueryFlag, LoadStrategy, PopulateHint, PopulatePath } from '../enums.js';
4
4
  import type { Platform } from '../platforms/Platform.js';
@@ -27,6 +27,7 @@ export interface IDatabaseDriver<C extends Connection = Connection> {
27
27
  */
28
28
  findOne<T extends object, P extends string = never, F extends string = '*', E extends string = never>(entityName: string, where: FilterQuery<T>, options?: FindOneOptions<T, P, F, E>): Promise<EntityData<T> | null>;
29
29
  findVirtual<T extends object>(entityName: string, where: FilterQuery<T>, options: FindOptions<T, any, any, any>): Promise<EntityData<T>[]>;
30
+ stream<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, options: StreamOptions<T>): AsyncIterableIterator<T>;
30
31
  nativeInsert<T extends object>(entityName: string, data: EntityDictionary<T>, options?: NativeInsertUpdateOptions<T>): Promise<QueryResult<T>>;
31
32
  nativeInsertMany<T extends object>(entityName: string, data: EntityDictionary<T>[], options?: NativeInsertUpdateManyOptions<T>, transform?: (sql: string) => string): Promise<QueryResult<T>>;
32
33
  nativeUpdate<T extends object>(entityName: string, where: FilterQuery<T>, data: EntityDictionary<T>, options?: NativeInsertUpdateOptions<T>): Promise<QueryResult<T>>;
@@ -68,6 +69,18 @@ export type OrderDefinition<T> = (QueryOrderMap<T> & {
68
69
  export interface FindAllOptions<T, P extends string = never, F extends string = '*', E extends string = never> extends FindOptions<T, P, F, E> {
69
70
  where?: FilterQuery<T>;
70
71
  }
72
+ export interface StreamOptions<Entity, Populate extends string = never, Fields extends string = '*', Exclude extends string = never> extends Omit<FindAllOptions<Entity, Populate, Fields, Exclude>, 'cache' | 'before' | 'after' | 'first' | 'last' | 'overfetch' | 'strategy'> {
73
+ /**
74
+ * When populating to-many relations, the ORM streams fully merged entities instead of yielding every row.
75
+ * You can opt out of this behavior by specifying `mergeResults: false`. This will yield every row from
76
+ * the SQL result, but still mapped to entities, meaning that to-many collections will contain at most
77
+ * a single item, and you will get duplicate root entities when they have multiple items in the populated
78
+ * collection.
79
+ *
80
+ * @default true
81
+ */
82
+ mergeResults?: boolean;
83
+ }
71
84
  export type FilterOptions = Dictionary<boolean | Dictionary> | string[] | boolean;
72
85
  export interface LoadHint<Entity, Hint extends string = never, Fields extends string = PopulatePath.ALL, Excludes extends string = never> {
73
86
  populate?: Populate<Entity, Hint>;
@@ -136,6 +136,10 @@ export class EntityFactory {
136
136
  if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && Utils.isPlainObject(data[prop.name])) {
137
137
  diff2[key] = entity[prop.name] ? helper(entity[prop.name]).getPrimaryKey(options.convertCustomTypes) : null;
138
138
  }
139
+ if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE, ReferenceKind.SCALAR].includes(prop.kind) && prop.customType?.ensureComparable(meta, prop) && diff2[key] != null) {
140
+ const converted = prop.customType.convertToJSValue(diff2[key], this.platform, { force: true });
141
+ diff2[key] = prop.customType.convertToDatabaseValue(converted, this.platform, { fromQuery: true });
142
+ }
139
143
  originalEntityData[key] = diff2[key] === null ? nullVal : diff2[key];
140
144
  helper(entity).__loadedProperties.add(key);
141
145
  });