@mikro-orm/core 7.0.0-dev.34 → 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.
- package/EntityManager.d.ts +19 -1
- package/EntityManager.js +64 -40
- package/decorators/Entity.d.ts +1 -1
- package/drivers/DatabaseDriver.d.ts +4 -3
- package/drivers/IDatabaseDriver.d.ts +14 -1
- package/entity/EntityFactory.js +4 -0
- package/entity/defineEntity.d.ts +305 -291
- package/entity/defineEntity.js +105 -268
- package/package.json +2 -2
- package/platforms/Platform.d.ts +2 -2
- package/platforms/Platform.js +3 -7
- package/types/JsonType.d.ts +1 -1
- package/types/JsonType.js +7 -2
- package/types/Type.d.ts +2 -1
- package/types/Type.js +1 -1
- package/typings.d.ts +2 -2
- package/unit-of-work/UnitOfWork.js +7 -0
- package/utils/EntityComparator.js +1 -4
- package/utils/upsert-utils.d.ts +7 -2
- package/utils/upsert-utils.js +43 -0
package/EntityManager.d.ts
CHANGED
|
@@ -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
|
-
|
|
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 =>
|
|
850
|
-
|
|
851
|
-
|
|
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
|
}
|
package/decorators/Entity.d.ts
CHANGED
|
@@ -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
|
|
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:
|
|
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>;
|
package/entity/EntityFactory.js
CHANGED
|
@@ -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
|
});
|