@mikro-orm/core 7.0.0-dev.3 → 7.0.0-dev.31
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 +50 -7
- package/EntityManager.js +141 -97
- package/MikroORM.js +0 -1
- package/README.md +1 -2
- package/cache/FileCacheAdapter.d.ts +2 -1
- package/cache/FileCacheAdapter.js +6 -4
- package/connections/Connection.d.ts +4 -2
- package/connections/Connection.js +2 -2
- package/decorators/Check.d.ts +2 -2
- package/decorators/Embeddable.d.ts +5 -5
- package/decorators/Embeddable.js +1 -1
- package/decorators/Embedded.d.ts +6 -12
- package/decorators/Entity.d.ts +20 -5
- package/decorators/Entity.js +0 -1
- package/decorators/Enum.d.ts +1 -1
- package/decorators/Formula.d.ts +1 -2
- package/decorators/Indexed.d.ts +10 -8
- package/decorators/Indexed.js +1 -1
- package/decorators/ManyToMany.d.ts +4 -2
- package/decorators/ManyToOne.d.ts +6 -2
- package/decorators/OneToMany.d.ts +4 -4
- package/decorators/OneToOne.d.ts +5 -1
- package/decorators/PrimaryKey.d.ts +2 -3
- package/decorators/Property.d.ts +1 -1
- package/decorators/Transactional.d.ts +1 -0
- package/decorators/Transactional.js +3 -3
- package/drivers/IDatabaseDriver.d.ts +8 -1
- package/entity/ArrayCollection.d.ts +4 -2
- package/entity/ArrayCollection.js +18 -6
- package/entity/Collection.d.ts +1 -2
- package/entity/Collection.js +16 -10
- package/entity/EntityAssigner.d.ts +1 -1
- package/entity/EntityAssigner.js +9 -1
- package/entity/EntityFactory.d.ts +6 -0
- package/entity/EntityFactory.js +21 -7
- package/entity/EntityHelper.js +8 -1
- package/entity/EntityLoader.d.ts +3 -2
- package/entity/EntityLoader.js +54 -35
- package/entity/EntityRepository.d.ts +1 -1
- package/entity/EntityValidator.js +1 -1
- package/entity/Reference.d.ts +8 -7
- package/entity/Reference.js +22 -1
- package/entity/WrappedEntity.js +1 -1
- package/entity/defineEntity.d.ts +537 -0
- package/entity/defineEntity.js +693 -0
- package/entity/index.d.ts +2 -0
- package/entity/index.js +2 -0
- package/entity/utils.d.ts +7 -0
- package/entity/utils.js +15 -3
- package/enums.d.ts +16 -3
- package/enums.js +13 -0
- package/errors.d.ts +6 -0
- package/errors.js +14 -0
- package/events/EventSubscriber.d.ts +3 -1
- package/hydration/ObjectHydrator.js +10 -2
- package/index.d.ts +1 -1
- package/metadata/EntitySchema.d.ts +6 -4
- package/metadata/EntitySchema.js +33 -19
- package/metadata/MetadataDiscovery.d.ts +0 -1
- package/metadata/MetadataDiscovery.js +51 -29
- package/metadata/MetadataStorage.js +1 -1
- package/metadata/MetadataValidator.js +4 -3
- package/package.json +5 -5
- package/platforms/Platform.d.ts +3 -1
- package/serialization/EntitySerializer.d.ts +2 -0
- package/serialization/EntitySerializer.js +1 -1
- package/serialization/SerializationContext.js +13 -10
- package/types/BigIntType.d.ts +9 -6
- package/types/BigIntType.js +3 -0
- package/types/BooleanType.d.ts +1 -1
- package/types/DecimalType.d.ts +6 -4
- package/types/DecimalType.js +1 -1
- package/types/DoubleType.js +1 -1
- package/typings.d.ts +72 -35
- package/typings.js +24 -4
- package/unit-of-work/ChangeSetComputer.js +3 -1
- package/unit-of-work/ChangeSetPersister.d.ts +4 -2
- package/unit-of-work/ChangeSetPersister.js +21 -11
- package/unit-of-work/UnitOfWork.d.ts +2 -1
- package/unit-of-work/UnitOfWork.js +71 -24
- package/utils/AbstractSchemaGenerator.js +5 -2
- package/utils/Configuration.d.ts +15 -5
- package/utils/Configuration.js +7 -7
- package/utils/ConfigurationLoader.d.ts +0 -2
- package/utils/ConfigurationLoader.js +2 -24
- package/utils/Cursor.d.ts +3 -3
- package/utils/Cursor.js +3 -0
- package/utils/DataloaderUtils.d.ts +7 -2
- package/utils/DataloaderUtils.js +38 -7
- package/utils/EntityComparator.d.ts +6 -2
- package/utils/EntityComparator.js +98 -59
- package/utils/QueryHelper.d.ts +6 -0
- package/utils/QueryHelper.js +48 -5
- package/utils/RawQueryFragment.d.ts +34 -0
- package/utils/RawQueryFragment.js +40 -1
- package/utils/TransactionManager.d.ts +65 -0
- package/utils/TransactionManager.js +220 -0
- package/utils/Utils.d.ts +11 -7
- package/utils/Utils.js +67 -33
- package/utils/index.d.ts +1 -0
- package/utils/index.js +1 -0
- package/utils/upsert-utils.js +9 -1
package/EntityManager.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { EntityFactory } from './entity/EntityFactory.js';
|
|
|
6
6
|
import { type AssignOptions } from './entity/EntityAssigner.js';
|
|
7
7
|
import { EntityValidator } from './entity/EntityValidator.js';
|
|
8
8
|
import { type EntityRepository } from './entity/EntityRepository.js';
|
|
9
|
-
import { type EntityLoaderOptions } from './entity/EntityLoader.js';
|
|
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
12
|
import type { CountOptions, DeleteOptions, FindAllOptions, FindByCursorOptions, FindOneOptions, FindOneOrFailOptions, FindOptions, GetReferenceOptions, IDatabaseDriver, LockOptions, NativeInsertUpdateOptions, UpdateOptions, UpsertManyOptions, UpsertOptions } from './drivers/IDatabaseDriver.js';
|
|
@@ -33,6 +33,7 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
33
33
|
readonly name: string;
|
|
34
34
|
protected readonly refLoader: DataLoader<[Reference<any>, (Omit<import("./entity/Reference.js").LoadReferenceOptions<any, any, "*", never>, "dataloader"> | undefined)?], any, [Reference<any>, (Omit<import("./entity/Reference.js").LoadReferenceOptions<any, any, "*", never>, "dataloader"> | undefined)?]>;
|
|
35
35
|
protected readonly colLoader: DataLoader<[import("./index.js").Collection<any, object>, (Omit<import("./index.js").InitCollectionOptions<any, any, "*", never>, "dataloader"> | undefined)?], any, [import("./index.js").Collection<any, object>, (Omit<import("./index.js").InitCollectionOptions<any, any, "*", never>, "dataloader"> | undefined)?]>;
|
|
36
|
+
protected readonly colLoaderMtoN: DataLoader<[import("./index.js").Collection<any, object>, (Omit<import("./index.js").InitCollectionOptions<any, any, "*", never>, "dataloader"> | undefined)?], any, [import("./index.js").Collection<any, object>, (Omit<import("./index.js").InitCollectionOptions<any, any, "*", never>, "dataloader"> | undefined)?]>;
|
|
36
37
|
private readonly validator;
|
|
37
38
|
private readonly repositoryMap;
|
|
38
39
|
private readonly entityLoader;
|
|
@@ -117,7 +118,9 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
117
118
|
/**
|
|
118
119
|
* Gets logger context for this entity manager.
|
|
119
120
|
*/
|
|
120
|
-
getLoggerContext<T extends Dictionary = Dictionary>(
|
|
121
|
+
getLoggerContext<T extends Dictionary = Dictionary>(options?: {
|
|
122
|
+
disableContextResolution?: boolean;
|
|
123
|
+
}): T;
|
|
121
124
|
setFlushMode(flushMode?: FlushMode): void;
|
|
122
125
|
protected processWhere<Entity extends object, Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(entityName: string, where: FilterQuery<Entity>, options: FindOptions<Entity, Hint, Fields, Excludes> | FindOneOptions<Entity, Hint, Fields, Excludes>, type: 'read' | 'update' | 'delete'): Promise<FilterQuery<Entity>>;
|
|
123
126
|
protected applyDiscriminatorCondition<Entity extends object>(entityName: string, where: FilterQuery<Entity>): FilterQuery<Entity>;
|
|
@@ -171,6 +174,10 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
171
174
|
* });
|
|
172
175
|
* ```
|
|
173
176
|
*
|
|
177
|
+
* The options also support an `includeCount` (true by default) option. If set to false, the `totalCount` is not
|
|
178
|
+
* returned as part of the cursor. This is useful for performance reason, when you don't care about the total number
|
|
179
|
+
* of pages.
|
|
180
|
+
*
|
|
174
181
|
* The `Cursor` object provides the following interface:
|
|
175
182
|
*
|
|
176
183
|
* ```ts
|
|
@@ -180,7 +187,7 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
180
187
|
* User { ... },
|
|
181
188
|
* User { ... },
|
|
182
189
|
* ],
|
|
183
|
-
* totalCount: 50,
|
|
190
|
+
* totalCount: 50, // not included if `includeCount: false`
|
|
184
191
|
* startCursor: 'WzRd',
|
|
185
192
|
* endCursor: 'WzZd',
|
|
186
193
|
* hasPrevPage: true,
|
|
@@ -188,7 +195,7 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
188
195
|
* }
|
|
189
196
|
* ```
|
|
190
197
|
*/
|
|
191
|
-
findByCursor<Entity extends object, Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(entityName: EntityName<Entity>, where: FilterQuery<NoInfer<Entity>>, options: FindByCursorOptions<Entity, Hint, Fields, Excludes>): Promise<Cursor<Entity, Hint, Fields, Excludes>>;
|
|
198
|
+
findByCursor<Entity extends object, Hint extends string = never, Fields extends string = '*', Excludes extends string = never, IncludeCount extends boolean = true>(entityName: EntityName<Entity>, where: FilterQuery<NoInfer<Entity>>, options: FindByCursorOptions<Entity, Hint, Fields, Excludes, IncludeCount>): Promise<Cursor<Entity, Hint, Fields, Excludes, IncludeCount>>;
|
|
192
199
|
/**
|
|
193
200
|
* Refreshes the persistent state of an entity from the database, overriding any local changes that have not yet been
|
|
194
201
|
* persisted. Returns the same entity instance (same object reference), but re-hydrated. If the entity is no longer
|
|
@@ -263,6 +270,29 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
263
270
|
upsertMany<Entity extends object, Fields extends string = any>(entityNameOrEntity: EntityName<Entity> | Entity[], data?: (EntityData<Entity> | NoInfer<Entity>)[], options?: UpsertManyOptions<Entity, Fields>): Promise<Entity[]>;
|
|
264
271
|
/**
|
|
265
272
|
* Runs your callback wrapped inside a database transaction.
|
|
273
|
+
*
|
|
274
|
+
* If a transaction is already active, a new savepoint (nested transaction) will be created by default. This behavior
|
|
275
|
+
* can be controlled via the `propagation` option. Use the provided EntityManager instance for all operations that
|
|
276
|
+
* should be part of the transaction. You can safely use a global EntityManager instance from a DI container, as this
|
|
277
|
+
* method automatically creates an async context for the transaction.
|
|
278
|
+
*
|
|
279
|
+
* **Concurrency note:** When running multiple transactions concurrently (e.g. in parallel requests or jobs), use the
|
|
280
|
+
* `clear: true` option. This ensures the callback runs in a clear fork of the EntityManager, providing full isolation
|
|
281
|
+
* between concurrent transactional handlers. Using `clear: true` is an alternative to forking explicitly and calling
|
|
282
|
+
* the method on the new fork – it already provides the necessary isolation for safe concurrent usage.
|
|
283
|
+
*
|
|
284
|
+
* **Propagation note:** Changes made within a transaction (whether top-level or nested) are always propagated to the
|
|
285
|
+
* parent context, unless the parent context is a global one. If you want to avoid that, fork the EntityManager first
|
|
286
|
+
* and then call this method on the fork.
|
|
287
|
+
*
|
|
288
|
+
* **Example:**
|
|
289
|
+
* ```ts
|
|
290
|
+
* await em.transactional(async (em) => {
|
|
291
|
+
* const author = new Author('Jon');
|
|
292
|
+
* em.persist(author);
|
|
293
|
+
* // flush is called automatically at the end of the callback
|
|
294
|
+
* });
|
|
295
|
+
* ```
|
|
266
296
|
*/
|
|
267
297
|
transactional<T>(cb: (em: this) => T | Promise<T>, options?: TransactionOptions): Promise<T>;
|
|
268
298
|
/**
|
|
@@ -419,7 +449,7 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
419
449
|
/**
|
|
420
450
|
* Loads specified relations in batch. This will execute one query for each relation, that will populate it on all the specified entities.
|
|
421
451
|
*/
|
|
422
|
-
populate<Entity extends object, Naked extends FromEntityType<UnboxArray<Entity>> = FromEntityType<UnboxArray<Entity>>, Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(entities: Entity, populate: AutoPath<Naked, Hint, PopulatePath.ALL>[] | false, options?: EntityLoaderOptions<Naked, Fields, Excludes>): Promise<Entity extends object[] ? MergeLoaded<ArrayElement<Entity>, Naked, Hint, Fields, Excludes>[] : MergeLoaded<Entity, Naked, Hint, Fields, Excludes>>;
|
|
452
|
+
populate<Entity extends object, Naked extends FromEntityType<UnboxArray<Entity>> = FromEntityType<UnboxArray<Entity>>, Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(entities: Entity, populate: readonly AutoPath<Naked, Hint, PopulatePath.ALL>[] | false, options?: EntityLoaderOptions<Naked, Fields, Excludes>): Promise<Entity extends object[] ? MergeLoaded<ArrayElement<Entity>, Naked, Hint, Fields, Excludes>[] : MergeLoaded<Entity, Naked, Hint, Fields, Excludes>>;
|
|
423
453
|
/**
|
|
424
454
|
* Returns new EntityManager instance with its own identity map
|
|
425
455
|
*/
|
|
@@ -432,6 +462,10 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
432
462
|
* Gets the EntityFactory used by the EntityManager.
|
|
433
463
|
*/
|
|
434
464
|
getEntityFactory(): EntityFactory;
|
|
465
|
+
/**
|
|
466
|
+
* @internal use `em.populate()` as the user facing API, this is exposed only for internal usage
|
|
467
|
+
*/
|
|
468
|
+
getEntityLoader(): EntityLoader;
|
|
435
469
|
/**
|
|
436
470
|
* Gets the Hydrator used by the EntityManager.
|
|
437
471
|
*/
|
|
@@ -479,7 +513,7 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
479
513
|
* some additional lazy properties, if so, we reload and merge the data from database
|
|
480
514
|
*/
|
|
481
515
|
protected shouldRefresh<T extends object, P extends string = never, F extends string = '*', E extends string = never>(meta: EntityMetadata<T>, entity: T, options: FindOneOptions<T, P, F, E>): boolean;
|
|
482
|
-
protected prepareOptions(options: FindOptions<any, any, any, any> | FindOneOptions<any, any, any, any>): void;
|
|
516
|
+
protected prepareOptions(options: FindOptions<any, any, any, any> | FindOneOptions<any, any, any, any> | CountOptions<any, any>): void;
|
|
483
517
|
/**
|
|
484
518
|
* @internal
|
|
485
519
|
*/
|
|
@@ -488,7 +522,7 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
488
522
|
* @internal
|
|
489
523
|
*/
|
|
490
524
|
tryCache<T extends object, R>(entityName: string, config: boolean | number | [string, number] | undefined, key: unknown, refresh?: boolean, merge?: boolean): Promise<{
|
|
491
|
-
data?: R;
|
|
525
|
+
data?: R | null;
|
|
492
526
|
key: string;
|
|
493
527
|
} | undefined>;
|
|
494
528
|
/**
|
|
@@ -539,11 +573,20 @@ export interface CreateOptions<Convert extends boolean> {
|
|
|
539
573
|
partial?: boolean;
|
|
540
574
|
/** convert raw database values based on mapped types (by default, already converted values are expected) */
|
|
541
575
|
convertCustomTypes?: Convert;
|
|
576
|
+
/**
|
|
577
|
+
* Property `onCreate` hooks are normally executed during `flush` operation.
|
|
578
|
+
* With this option, they will be processed early inside `em.create()` method.
|
|
579
|
+
*/
|
|
580
|
+
processOnCreateHooksEarly?: boolean;
|
|
542
581
|
}
|
|
543
582
|
export interface MergeOptions {
|
|
544
583
|
refresh?: boolean;
|
|
545
584
|
convertCustomTypes?: boolean;
|
|
546
585
|
schema?: string;
|
|
586
|
+
disableContextResolution?: boolean;
|
|
587
|
+
keepIdentity?: boolean;
|
|
588
|
+
validate?: boolean;
|
|
589
|
+
cascade?: boolean; /** @default true */
|
|
547
590
|
}
|
|
548
591
|
export interface ForkOptions {
|
|
549
592
|
/** do we want a clear identity map? defaults to true */
|
package/EntityManager.js
CHANGED
|
@@ -19,6 +19,8 @@ import { EventType, FlushMode, LoadStrategy, LockMode, PopulateHint, PopulatePat
|
|
|
19
19
|
import { EventManager } from './events/EventManager.js';
|
|
20
20
|
import { TransactionEventBroadcaster } from './events/TransactionEventBroadcaster.js';
|
|
21
21
|
import { OptimisticLockError, ValidationError } from './errors.js';
|
|
22
|
+
import { getLoadingStrategy } from './entity/utils.js';
|
|
23
|
+
import { TransactionManager } from './utils/TransactionManager.js';
|
|
22
24
|
/**
|
|
23
25
|
* The EntityManager is the central access point to ORM functionality. It is a facade to all different ORM subsystems
|
|
24
26
|
* such as UnitOfWork, Query Language, and Repository API.
|
|
@@ -36,6 +38,7 @@ export class EntityManager {
|
|
|
36
38
|
name;
|
|
37
39
|
refLoader = new DataLoader(DataloaderUtils.getRefBatchLoadFn(this));
|
|
38
40
|
colLoader = new DataLoader(DataloaderUtils.getColBatchLoadFn(this));
|
|
41
|
+
colLoaderMtoN = new DataLoader(DataloaderUtils.getManyToManyColBatchLoadFn(this));
|
|
39
42
|
validator;
|
|
40
43
|
repositoryMap = {};
|
|
41
44
|
entityLoader;
|
|
@@ -137,7 +140,6 @@ export class EntityManager {
|
|
|
137
140
|
await em.entityLoader.populate(entityName, cached.data, populate, {
|
|
138
141
|
...options,
|
|
139
142
|
...em.getPopulateWhere(where, options),
|
|
140
|
-
convertCustomTypes: false,
|
|
141
143
|
ignoreLazyScalarProperties: true,
|
|
142
144
|
lookup: false,
|
|
143
145
|
});
|
|
@@ -149,7 +151,7 @@ export class EntityManager {
|
|
|
149
151
|
options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
|
|
150
152
|
options.populateWhere = this.createPopulateWhere({ ...where }, options);
|
|
151
153
|
options.populateFilter = await this.getJoinedFilters(meta, { ...where }, options);
|
|
152
|
-
const results = await em.driver.find(entityName, where, { ctx: em.transactionContext, ...options });
|
|
154
|
+
const results = await em.driver.find(entityName, where, { ctx: em.transactionContext, em, ...options });
|
|
153
155
|
if (results.length === 0) {
|
|
154
156
|
await em.storeCache(options.cache, cached, []);
|
|
155
157
|
return [];
|
|
@@ -168,7 +170,6 @@ export class EntityManager {
|
|
|
168
170
|
await em.entityLoader.populate(entityName, unique, populate, {
|
|
169
171
|
...options,
|
|
170
172
|
...em.getPopulateWhere(where, options),
|
|
171
|
-
convertCustomTypes: false,
|
|
172
173
|
ignoreLazyScalarProperties: true,
|
|
173
174
|
lookup: false,
|
|
174
175
|
});
|
|
@@ -232,8 +233,8 @@ export class EntityManager {
|
|
|
232
233
|
/**
|
|
233
234
|
* Gets logger context for this entity manager.
|
|
234
235
|
*/
|
|
235
|
-
getLoggerContext() {
|
|
236
|
-
const em = this.getContext();
|
|
236
|
+
getLoggerContext(options) {
|
|
237
|
+
const em = options?.disableContextResolution ? this : this.getContext();
|
|
237
238
|
em.loggerContext ??= {};
|
|
238
239
|
return em.loggerContext;
|
|
239
240
|
}
|
|
@@ -289,7 +290,8 @@ export class EntityManager {
|
|
|
289
290
|
for (const hint of options.populate) {
|
|
290
291
|
const field = hint.field.split(':')[0];
|
|
291
292
|
const prop = meta.properties[field];
|
|
292
|
-
const
|
|
293
|
+
const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
|
|
294
|
+
const joined = strategy === LoadStrategy.JOINED && prop.kind !== ReferenceKind.SCALAR;
|
|
293
295
|
if (!joined && !hint.filter) {
|
|
294
296
|
continue;
|
|
295
297
|
}
|
|
@@ -326,10 +328,19 @@ export class EntityManager {
|
|
|
326
328
|
const cond = await this.applyFilters(prop.type, {}, options.filters ?? {}, 'read', options);
|
|
327
329
|
if (!Utils.isEmpty(cond)) {
|
|
328
330
|
const populated = options.populate.filter(({ field }) => field.split(':')[0] === prop.name);
|
|
331
|
+
let found = false;
|
|
329
332
|
if (populated.length > 0) {
|
|
330
|
-
|
|
333
|
+
for (const hint of populated) {
|
|
334
|
+
if (!hint.all) {
|
|
335
|
+
hint.filter = true;
|
|
336
|
+
found = true;
|
|
337
|
+
}
|
|
338
|
+
else if (hint.field === `${prop.name}:ref`) {
|
|
339
|
+
found = true;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
331
342
|
}
|
|
332
|
-
|
|
343
|
+
if (!found) {
|
|
333
344
|
ret.push({ field: `${prop.name}:ref`, strategy: LoadStrategy.JOINED, filter: true });
|
|
334
345
|
}
|
|
335
346
|
}
|
|
@@ -367,7 +378,7 @@ export class EntityManager {
|
|
|
367
378
|
if (!args && filter.cond.length > 0 && filter.args !== false) {
|
|
368
379
|
throw new Error(`No arguments provided for filter '${filter.name}'`);
|
|
369
380
|
}
|
|
370
|
-
cond = await filter.cond(args, type, this, findOptions);
|
|
381
|
+
cond = await filter.cond(args, type, this, findOptions, entityName);
|
|
371
382
|
}
|
|
372
383
|
else {
|
|
373
384
|
cond = filter.cond;
|
|
@@ -433,6 +444,10 @@ export class EntityManager {
|
|
|
433
444
|
* });
|
|
434
445
|
* ```
|
|
435
446
|
*
|
|
447
|
+
* The options also support an `includeCount` (true by default) option. If set to false, the `totalCount` is not
|
|
448
|
+
* returned as part of the cursor. This is useful for performance reason, when you don't care about the total number
|
|
449
|
+
* of pages.
|
|
450
|
+
*
|
|
436
451
|
* The `Cursor` object provides the following interface:
|
|
437
452
|
*
|
|
438
453
|
* ```ts
|
|
@@ -442,7 +457,7 @@ export class EntityManager {
|
|
|
442
457
|
* User { ... },
|
|
443
458
|
* User { ... },
|
|
444
459
|
* ],
|
|
445
|
-
* totalCount: 50,
|
|
460
|
+
* totalCount: 50, // not included if `includeCount: false`
|
|
446
461
|
* startCursor: 'WzRd',
|
|
447
462
|
* endCursor: 'WzZd',
|
|
448
463
|
* hasPrevPage: true,
|
|
@@ -457,7 +472,9 @@ export class EntityManager {
|
|
|
457
472
|
if (Utils.isEmpty(options.orderBy)) {
|
|
458
473
|
throw new Error('Explicit `orderBy` option required');
|
|
459
474
|
}
|
|
460
|
-
const [entities, count] =
|
|
475
|
+
const [entities, count] = options.includeCount !== false
|
|
476
|
+
? await em.findAndCount(entityName, where, options)
|
|
477
|
+
: [await em.find(entityName, where, options)];
|
|
461
478
|
return new Cursor(entities, count, options, this.metadata.get(entityName));
|
|
462
479
|
}
|
|
463
480
|
/**
|
|
@@ -488,13 +505,25 @@ export class EntityManager {
|
|
|
488
505
|
...options,
|
|
489
506
|
flushMode: FlushMode.COMMIT,
|
|
490
507
|
});
|
|
491
|
-
|
|
492
|
-
|
|
508
|
+
const em = this.getContext();
|
|
509
|
+
if (!reloaded) {
|
|
510
|
+
em.unitOfWork.unsetIdentity(entity);
|
|
511
|
+
return null;
|
|
493
512
|
}
|
|
494
|
-
|
|
495
|
-
|
|
513
|
+
let found = false;
|
|
514
|
+
for (const e of fork.unitOfWork.getIdentityMap()) {
|
|
515
|
+
const ref = em.getReference(e.constructor.name, helper(e).getPrimaryKey());
|
|
516
|
+
const data = helper(e).serialize({ ignoreSerializers: true, includeHidden: true });
|
|
517
|
+
em.config.getHydrator(this.metadata).hydrate(ref, helper(ref).__meta, data, em.entityFactory, 'full', false, true);
|
|
518
|
+
helper(ref).__originalEntityData = this.comparator.prepareEntity(e);
|
|
519
|
+
found ||= ref === entity;
|
|
496
520
|
}
|
|
497
|
-
|
|
521
|
+
if (!found) {
|
|
522
|
+
const data = helper(reloaded).serialize({ ignoreSerializers: true, includeHidden: true });
|
|
523
|
+
em.config.getHydrator(this.metadata).hydrate(entity, helper(entity).__meta, data, em.entityFactory, 'full', false, true);
|
|
524
|
+
helper(entity).__originalEntityData = this.comparator.prepareEntity(reloaded);
|
|
525
|
+
}
|
|
526
|
+
return entity;
|
|
498
527
|
}
|
|
499
528
|
/**
|
|
500
529
|
* Finds first entity matching your `where` query.
|
|
@@ -530,14 +559,15 @@ export class EntityManager {
|
|
|
530
559
|
options.populate = await em.preparePopulate(entityName, options);
|
|
531
560
|
const cacheKey = em.cacheKey(entityName, options, 'em.findOne', where);
|
|
532
561
|
const cached = await em.tryCache(entityName, options.cache, cacheKey, options.refresh, true);
|
|
533
|
-
if (cached?.data) {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
562
|
+
if (cached?.data !== undefined) {
|
|
563
|
+
if (cached.data) {
|
|
564
|
+
await em.entityLoader.populate(entityName, [cached.data], options.populate, {
|
|
565
|
+
...options,
|
|
566
|
+
...em.getPopulateWhere(where, options),
|
|
567
|
+
ignoreLazyScalarProperties: true,
|
|
568
|
+
lookup: false,
|
|
569
|
+
});
|
|
570
|
+
}
|
|
541
571
|
return cached.data;
|
|
542
572
|
}
|
|
543
573
|
options = { ...options };
|
|
@@ -547,6 +577,7 @@ export class EntityManager {
|
|
|
547
577
|
options.populateFilter = await this.getJoinedFilters(meta, { ...where }, options);
|
|
548
578
|
const data = await em.driver.findOne(entityName, where, {
|
|
549
579
|
ctx: em.transactionContext,
|
|
580
|
+
em,
|
|
550
581
|
...options,
|
|
551
582
|
});
|
|
552
583
|
if (!data) {
|
|
@@ -716,8 +747,9 @@ export class EntityManager {
|
|
|
716
747
|
ctx: em.transactionContext,
|
|
717
748
|
convertCustomTypes: true,
|
|
718
749
|
connectionType: 'write',
|
|
750
|
+
schema: options.schema,
|
|
719
751
|
});
|
|
720
|
-
em.getHydrator().hydrate(entity, meta, data2, em.entityFactory, 'full');
|
|
752
|
+
em.getHydrator().hydrate(entity, meta, data2, em.entityFactory, 'full', false, true);
|
|
721
753
|
}
|
|
722
754
|
// recompute the data as there might be some values missing (e.g. those with db column defaults)
|
|
723
755
|
const snapshot = this.comparator.prepareEntity(entity);
|
|
@@ -896,6 +928,7 @@ export class EntityManager {
|
|
|
896
928
|
ctx: em.transactionContext,
|
|
897
929
|
convertCustomTypes: true,
|
|
898
930
|
connectionType: 'write',
|
|
931
|
+
schema: options.schema,
|
|
899
932
|
});
|
|
900
933
|
for (const [entity, cond] of loadPK.entries()) {
|
|
901
934
|
const row = data2.find(row => {
|
|
@@ -911,7 +944,7 @@ export class EntityManager {
|
|
|
911
944
|
if (!row) {
|
|
912
945
|
throw new Error(`Cannot find matching entity for condition ${JSON.stringify(cond)}`);
|
|
913
946
|
}
|
|
914
|
-
em.getHydrator().hydrate(entity, meta, row, em.entityFactory, 'full');
|
|
947
|
+
em.getHydrator().hydrate(entity, meta, row, em.entityFactory, 'full', false, true);
|
|
915
948
|
}
|
|
916
949
|
if (loadPK.size !== data2.length && Array.isArray(uniqueFields)) {
|
|
917
950
|
for (let i = 0; i < allData.length; i++) {
|
|
@@ -952,45 +985,37 @@ export class EntityManager {
|
|
|
952
985
|
}
|
|
953
986
|
/**
|
|
954
987
|
* Runs your callback wrapped inside a database transaction.
|
|
988
|
+
*
|
|
989
|
+
* If a transaction is already active, a new savepoint (nested transaction) will be created by default. This behavior
|
|
990
|
+
* can be controlled via the `propagation` option. Use the provided EntityManager instance for all operations that
|
|
991
|
+
* should be part of the transaction. You can safely use a global EntityManager instance from a DI container, as this
|
|
992
|
+
* method automatically creates an async context for the transaction.
|
|
993
|
+
*
|
|
994
|
+
* **Concurrency note:** When running multiple transactions concurrently (e.g. in parallel requests or jobs), use the
|
|
995
|
+
* `clear: true` option. This ensures the callback runs in a clear fork of the EntityManager, providing full isolation
|
|
996
|
+
* between concurrent transactional handlers. Using `clear: true` is an alternative to forking explicitly and calling
|
|
997
|
+
* the method on the new fork – it already provides the necessary isolation for safe concurrent usage.
|
|
998
|
+
*
|
|
999
|
+
* **Propagation note:** Changes made within a transaction (whether top-level or nested) are always propagated to the
|
|
1000
|
+
* parent context, unless the parent context is a global one. If you want to avoid that, fork the EntityManager first
|
|
1001
|
+
* and then call this method on the fork.
|
|
1002
|
+
*
|
|
1003
|
+
* **Example:**
|
|
1004
|
+
* ```ts
|
|
1005
|
+
* await em.transactional(async (em) => {
|
|
1006
|
+
* const author = new Author('Jon');
|
|
1007
|
+
* em.persist(author);
|
|
1008
|
+
* // flush is called automatically at the end of the callback
|
|
1009
|
+
* });
|
|
1010
|
+
* ```
|
|
955
1011
|
*/
|
|
956
1012
|
async transactional(cb, options = {}) {
|
|
957
1013
|
const em = this.getContext(false);
|
|
958
1014
|
if (this.disableTransactions || em.disableTransactions) {
|
|
959
1015
|
return cb(em);
|
|
960
1016
|
}
|
|
961
|
-
const
|
|
962
|
-
|
|
963
|
-
flushMode: options.flushMode,
|
|
964
|
-
cloneEventManager: true,
|
|
965
|
-
disableTransactions: options.ignoreNestedTransactions,
|
|
966
|
-
loggerContext: options.loggerContext,
|
|
967
|
-
});
|
|
968
|
-
options.ctx ??= em.transactionContext;
|
|
969
|
-
const propagateToUpperContext = !em.global || this.config.get('allowGlobalContext');
|
|
970
|
-
return TransactionContext.create(fork, async () => {
|
|
971
|
-
return fork.getConnection().transactional(async (trx) => {
|
|
972
|
-
fork.transactionContext = trx;
|
|
973
|
-
if (propagateToUpperContext) {
|
|
974
|
-
fork.eventManager.registerSubscriber({
|
|
975
|
-
afterFlush(args) {
|
|
976
|
-
args.uow.getChangeSets()
|
|
977
|
-
.filter(cs => [ChangeSetType.DELETE, ChangeSetType.DELETE_EARLY].includes(cs.type))
|
|
978
|
-
.forEach(cs => em.unitOfWork.unsetIdentity(cs.entity));
|
|
979
|
-
},
|
|
980
|
-
});
|
|
981
|
-
}
|
|
982
|
-
const ret = await cb(fork);
|
|
983
|
-
await fork.flush();
|
|
984
|
-
if (propagateToUpperContext) {
|
|
985
|
-
// ensure all entities from inner context are merged to the upper one
|
|
986
|
-
for (const entity of fork.unitOfWork.getIdentityMap()) {
|
|
987
|
-
em.unitOfWork.register(entity);
|
|
988
|
-
entity.__helper.__em = em;
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
return ret;
|
|
992
|
-
}, { ...options, eventBroadcaster: new TransactionEventBroadcaster(fork, { topLevelTransaction: !options.ctx }) });
|
|
993
|
-
});
|
|
1017
|
+
const manager = new TransactionManager(this);
|
|
1018
|
+
return manager.handle(cb, options);
|
|
994
1019
|
}
|
|
995
1020
|
/**
|
|
996
1021
|
* Starts new transaction bound to this EntityManager. Use `ctx` parameter to provide the parent when nesting transactions.
|
|
@@ -1170,22 +1195,35 @@ export class EntityManager {
|
|
|
1170
1195
|
* via second parameter. By default, it will return already loaded entities without modifying them.
|
|
1171
1196
|
*/
|
|
1172
1197
|
merge(entityName, data, options = {}) {
|
|
1173
|
-
const em = this.getContext();
|
|
1174
1198
|
if (Utils.isEntity(entityName)) {
|
|
1175
|
-
return
|
|
1199
|
+
return this.merge(entityName.constructor.name, entityName, data);
|
|
1176
1200
|
}
|
|
1201
|
+
const em = options.disableContextResolution ? this : this.getContext();
|
|
1177
1202
|
options.schema ??= em._schema;
|
|
1203
|
+
options.validate ??= true;
|
|
1204
|
+
options.cascade ??= true;
|
|
1178
1205
|
entityName = Utils.className(entityName);
|
|
1179
|
-
|
|
1206
|
+
if (options.validate) {
|
|
1207
|
+
em.validator.validatePrimaryKey(data, em.metadata.get(entityName));
|
|
1208
|
+
}
|
|
1180
1209
|
let entity = em.unitOfWork.tryGetById(entityName, data, options.schema, false);
|
|
1181
1210
|
if (entity && helper(entity).__managed && helper(entity).__initialized && !options.refresh) {
|
|
1182
1211
|
return entity;
|
|
1183
1212
|
}
|
|
1184
1213
|
const meta = em.metadata.find(entityName);
|
|
1185
1214
|
const childMeta = em.metadata.getByDiscriminatorColumn(meta, data);
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1215
|
+
const dataIsEntity = Utils.isEntity(data);
|
|
1216
|
+
if (options.keepIdentity && entity && dataIsEntity && entity !== data) {
|
|
1217
|
+
helper(entity).__data = helper(data).__data;
|
|
1218
|
+
helper(entity).__originalEntityData = helper(data).__originalEntityData;
|
|
1219
|
+
return entity;
|
|
1220
|
+
}
|
|
1221
|
+
entity = dataIsEntity ? data : em.entityFactory.create(entityName, data, { merge: true, ...options });
|
|
1222
|
+
if (options.validate) {
|
|
1223
|
+
em.validator.validate(entity, data, childMeta ?? meta);
|
|
1224
|
+
}
|
|
1225
|
+
const visited = options.cascade ? undefined : new Set([entity]);
|
|
1226
|
+
em.unitOfWork.merge(entity, visited);
|
|
1189
1227
|
return entity;
|
|
1190
1228
|
}
|
|
1191
1229
|
/**
|
|
@@ -1248,10 +1286,8 @@ export class EntityManager {
|
|
|
1248
1286
|
async count(entityName, where = {}, options = {}) {
|
|
1249
1287
|
const em = this.getContext(false);
|
|
1250
1288
|
// Shallow copy options since the object will be modified when deleting orderBy
|
|
1251
|
-
options = {
|
|
1252
|
-
|
|
1253
|
-
...options,
|
|
1254
|
-
};
|
|
1289
|
+
options = { ...options };
|
|
1290
|
+
em.prepareOptions(options);
|
|
1255
1291
|
entityName = Utils.className(entityName);
|
|
1256
1292
|
await em.tryFlush(entityName, options);
|
|
1257
1293
|
where = await em.processWhere(entityName, where, options, 'read');
|
|
@@ -1266,10 +1302,10 @@ export class EntityManager {
|
|
|
1266
1302
|
delete options.orderBy;
|
|
1267
1303
|
const cacheKey = em.cacheKey(entityName, options, 'em.count', where);
|
|
1268
1304
|
const cached = await em.tryCache(entityName, options.cache, cacheKey);
|
|
1269
|
-
if (cached?.data) {
|
|
1305
|
+
if (cached?.data !== undefined) {
|
|
1270
1306
|
return cached.data;
|
|
1271
1307
|
}
|
|
1272
|
-
const count = await em.driver.count(entityName, where, { ctx: em.transactionContext, ...options });
|
|
1308
|
+
const count = await em.driver.count(entityName, where, { ctx: em.transactionContext, em, ...options });
|
|
1273
1309
|
await em.storeCache(options.cache, cached, () => +count);
|
|
1274
1310
|
return +count;
|
|
1275
1311
|
}
|
|
@@ -1431,6 +1467,9 @@ export class EntityManager {
|
|
|
1431
1467
|
for (const entity of em.unitOfWork.getIdentityMap()) {
|
|
1432
1468
|
fork.unitOfWork.register(entity);
|
|
1433
1469
|
}
|
|
1470
|
+
for (const entity of em.unitOfWork.getPersistStack()) {
|
|
1471
|
+
fork.unitOfWork.persist(entity);
|
|
1472
|
+
}
|
|
1434
1473
|
for (const entity of em.unitOfWork.getOrphanRemoveStack()) {
|
|
1435
1474
|
fork.unitOfWork.getOrphanRemoveStack().add(entity);
|
|
1436
1475
|
}
|
|
@@ -1452,6 +1491,12 @@ export class EntityManager {
|
|
|
1452
1491
|
getEntityFactory() {
|
|
1453
1492
|
return this.getContext().entityFactory;
|
|
1454
1493
|
}
|
|
1494
|
+
/**
|
|
1495
|
+
* @internal use `em.populate()` as the user facing API, this is exposed only for internal usage
|
|
1496
|
+
*/
|
|
1497
|
+
getEntityLoader() {
|
|
1498
|
+
return this.getContext().entityLoader;
|
|
1499
|
+
}
|
|
1455
1500
|
/**
|
|
1456
1501
|
* Gets the Hydrator used by the EntityManager.
|
|
1457
1502
|
*/
|
|
@@ -1548,7 +1593,6 @@ export class EntityManager {
|
|
|
1548
1593
|
...options,
|
|
1549
1594
|
...this.getPopulateWhere(where, options),
|
|
1550
1595
|
orderBy: options.populateOrderBy ?? options.orderBy,
|
|
1551
|
-
convertCustomTypes: false,
|
|
1552
1596
|
ignoreLazyScalarProperties: true,
|
|
1553
1597
|
lookup: false,
|
|
1554
1598
|
});
|
|
@@ -1671,7 +1715,7 @@ export class EntityManager {
|
|
|
1671
1715
|
throw new ValidationError(`Cannot combine 'fields' and 'exclude' option.`);
|
|
1672
1716
|
}
|
|
1673
1717
|
options.schema ??= this._schema;
|
|
1674
|
-
options.logging = Utils.merge({ id: this.id }, this.loggerContext, options.loggerContext, options.logging);
|
|
1718
|
+
options.logging = options.loggerContext = Utils.merge({ id: this.id }, this.loggerContext, options.loggerContext, options.logging);
|
|
1675
1719
|
}
|
|
1676
1720
|
/**
|
|
1677
1721
|
* @internal
|
|
@@ -1695,31 +1739,31 @@ export class EntityManager {
|
|
|
1695
1739
|
const em = this.getContext();
|
|
1696
1740
|
const cacheKey = Array.isArray(config) ? config[0] : JSON.stringify(key);
|
|
1697
1741
|
const cached = await em.resultCache.get(cacheKey);
|
|
1698
|
-
if (cached) {
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
data = em.entityFactory.create(entityName, cached, {
|
|
1710
|
-
merge: true,
|
|
1711
|
-
convertCustomTypes: true,
|
|
1712
|
-
refresh,
|
|
1713
|
-
recomputeSnapshot: true,
|
|
1714
|
-
});
|
|
1715
|
-
}
|
|
1716
|
-
else {
|
|
1717
|
-
data = cached;
|
|
1718
|
-
}
|
|
1719
|
-
await em.unitOfWork.dispatchOnLoadEvent();
|
|
1720
|
-
return { key: cacheKey, data };
|
|
1742
|
+
if (!cached) {
|
|
1743
|
+
return { key: cacheKey, data: cached };
|
|
1744
|
+
}
|
|
1745
|
+
let data;
|
|
1746
|
+
if (Array.isArray(cached) && merge) {
|
|
1747
|
+
data = cached.map(item => em.entityFactory.create(entityName, item, {
|
|
1748
|
+
merge: true,
|
|
1749
|
+
convertCustomTypes: true,
|
|
1750
|
+
refresh,
|
|
1751
|
+
recomputeSnapshot: true,
|
|
1752
|
+
}));
|
|
1721
1753
|
}
|
|
1722
|
-
|
|
1754
|
+
else if (Utils.isObject(cached) && merge) {
|
|
1755
|
+
data = em.entityFactory.create(entityName, cached, {
|
|
1756
|
+
merge: true,
|
|
1757
|
+
convertCustomTypes: true,
|
|
1758
|
+
refresh,
|
|
1759
|
+
recomputeSnapshot: true,
|
|
1760
|
+
});
|
|
1761
|
+
}
|
|
1762
|
+
else {
|
|
1763
|
+
data = cached;
|
|
1764
|
+
}
|
|
1765
|
+
await em.unitOfWork.dispatchOnLoadEvent();
|
|
1766
|
+
return { key: cacheKey, data };
|
|
1723
1767
|
}
|
|
1724
1768
|
/**
|
|
1725
1769
|
* @internal
|
package/MikroORM.js
CHANGED
package/README.md
CHANGED
|
@@ -11,7 +11,6 @@ TypeScript ORM for Node.js based on Data Mapper, [Unit of Work](https://mikro-or
|
|
|
11
11
|
[](https://discord.gg/w8bjxFHS7X)
|
|
12
12
|
[](https://www.npmjs.com/package/@mikro-orm/core)
|
|
13
13
|
[](https://coveralls.io/r/mikro-orm/mikro-orm?branch=master)
|
|
14
|
-
[](https://codeclimate.com/github/mikro-orm/mikro-orm/maintainability)
|
|
15
14
|
[](https://github.com/mikro-orm/mikro-orm/actions?workflow=tests)
|
|
16
15
|
|
|
17
16
|
## 🤔 Unit of What?
|
|
@@ -141,7 +140,7 @@ There is also auto-generated [CHANGELOG.md](CHANGELOG.md) file based on commit m
|
|
|
141
140
|
- [Composite and Foreign Keys as Primary Key](https://mikro-orm.io/docs/composite-keys)
|
|
142
141
|
- [Filters](https://mikro-orm.io/docs/filters)
|
|
143
142
|
- [Using `QueryBuilder`](https://mikro-orm.io/docs/query-builder)
|
|
144
|
-
- [
|
|
143
|
+
- [Populating relations](https://mikro-orm.io/docs/populating-relations)
|
|
145
144
|
- [Property Validation](https://mikro-orm.io/docs/property-validation)
|
|
146
145
|
- [Lifecycle Hooks](https://mikro-orm.io/docs/events#hooks)
|
|
147
146
|
- [Vanilla JS Support](https://mikro-orm.io/docs/usage-with-js)
|
|
@@ -3,12 +3,13 @@ export declare class FileCacheAdapter implements SyncCacheAdapter {
|
|
|
3
3
|
private readonly options;
|
|
4
4
|
private readonly baseDir;
|
|
5
5
|
private readonly pretty;
|
|
6
|
+
private readonly hashAlgorithm;
|
|
6
7
|
private readonly VERSION;
|
|
7
8
|
private cache;
|
|
8
9
|
constructor(options: {
|
|
9
10
|
cacheDir: string;
|
|
10
11
|
combined?: boolean | string;
|
|
11
|
-
}, baseDir: string, pretty?: boolean);
|
|
12
|
+
}, baseDir: string, pretty?: boolean, hashAlgorithm?: 'md5' | 'sha256');
|
|
12
13
|
/**
|
|
13
14
|
* @inheritDoc
|
|
14
15
|
*/
|