@mikro-orm/core 7.2.0-dev.1 → 7.2.0-dev.11
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 +12 -6
- package/EntityManager.js +41 -27
- package/MikroORM.d.ts +4 -0
- package/MikroORM.js +6 -0
- package/cache/CacheAdapter.d.ts +6 -4
- package/cache/FileCacheAdapter.d.ts +1 -1
- package/cache/FileCacheAdapter.js +7 -2
- package/connections/Connection.d.ts +7 -0
- package/connections/Connection.js +9 -0
- package/drivers/DatabaseDriver.d.ts +7 -0
- package/drivers/DatabaseDriver.js +72 -9
- package/entity/EntityRepository.d.ts +4 -5
- package/entity/EntityRepository.js +2 -1
- package/hydration/ObjectHydrator.js +3 -0
- package/metadata/EntitySchema.js +5 -2
- package/metadata/MetadataDiscovery.js +34 -11
- package/metadata/MetadataProvider.js +1 -1
- package/metadata/MetadataStorage.d.ts +4 -3
- package/metadata/MetadataStorage.js +15 -1
- package/metadata/Routine.js +4 -1
- package/naming-strategy/AbstractNamingStrategy.js +2 -1
- package/naming-strategy/NamingStrategy.d.ts +2 -1
- package/package.json +1 -1
- package/platforms/Platform.d.ts +2 -0
- package/platforms/Platform.js +4 -0
- package/typings.d.ts +2 -0
- package/unit-of-work/ChangeSet.js +10 -7
- package/unit-of-work/ChangeSetPersister.js +15 -7
- package/utils/AbstractMigrator.d.ts +1 -1
- package/utils/AbstractMigrator.js +3 -2
- package/utils/Configuration.d.ts +6 -0
- package/utils/Configuration.js +3 -1
- package/utils/Cursor.js +1 -6
- package/utils/EntityComparator.js +4 -1
- package/utils/QueryHelper.d.ts +5 -0
- package/utils/QueryHelper.js +25 -0
- package/utils/RawQueryFragment.d.ts +6 -0
- package/utils/RawQueryFragment.js +15 -6
- package/utils/RequestContext.d.ts +2 -2
- package/utils/RequestContext.js +11 -2
- package/utils/TransactionManager.d.ts +6 -0
- package/utils/TransactionManager.js +35 -2
- package/utils/Utils.js +6 -3
- package/utils/clone.js +6 -0
- package/utils/env-vars.js +1 -0
package/EntityManager.d.ts
CHANGED
|
@@ -375,12 +375,10 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
375
375
|
*/
|
|
376
376
|
nativeDelete<Entity extends object>(entityName: EntityName<Entity>, where: FilterQuery<NoInfer<Entity>>, options?: DeleteOptions<Entity>): Promise<number>;
|
|
377
377
|
/**
|
|
378
|
-
* Maps raw database result to an entity and merges it to this EntityManager.
|
|
378
|
+
* Maps raw database result to an entity and merges it to this EntityManager by default.
|
|
379
|
+
* Use `disableIdentityMap` to return an isolated entity without affecting the current context.
|
|
379
380
|
*/
|
|
380
|
-
map<Entity extends object>(entityName: EntityName<Entity>, result: EntityDictionary<Entity>, options?:
|
|
381
|
-
schema?: string;
|
|
382
|
-
mapped?: boolean;
|
|
383
|
-
}): Entity;
|
|
381
|
+
map<Entity extends object>(entityName: EntityName<Entity>, result: EntityDictionary<Entity>, options?: MapOptions): Entity;
|
|
384
382
|
/**
|
|
385
383
|
* Merges given entity to this EntityManager so it becomes managed. You can force refreshing of existing entities
|
|
386
384
|
* via second parameter. By default, it will return already loaded entities without modifying them.
|
|
@@ -601,7 +599,7 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
601
599
|
* some additional lazy properties, if so, we reload and merge the data from database
|
|
602
600
|
*/
|
|
603
601
|
protected shouldRefresh<T extends object, P extends string = never, F extends string = never, E extends string = never>(meta: EntityMetadata<T>, entity: T, options: FindOneOptions<T, P, F, E>): boolean;
|
|
604
|
-
protected prepareOptions
|
|
602
|
+
protected prepareOptions<Options extends (FindOptions<any, any, any, any> | FindOneOptions<any, any, any, any> | CountOptions<any, any> | CountByOptions<any>) & AbortQueryOptions>(options: Options): Options;
|
|
605
603
|
/**
|
|
606
604
|
* @internal
|
|
607
605
|
*/
|
|
@@ -667,6 +665,14 @@ export interface CreateOptions<Convert extends boolean> {
|
|
|
667
665
|
*/
|
|
668
666
|
processOnCreateHooksEarly?: boolean;
|
|
669
667
|
}
|
|
668
|
+
export interface MapOptions {
|
|
669
|
+
/** schema to use when mapping the entity */
|
|
670
|
+
schema?: string;
|
|
671
|
+
/** set to true when the result is already mapped to entity property names */
|
|
672
|
+
mapped?: boolean;
|
|
673
|
+
/** map the entity in an isolated context without adding it to the identity map */
|
|
674
|
+
disableIdentityMap?: boolean;
|
|
675
|
+
}
|
|
670
676
|
export interface MergeOptions {
|
|
671
677
|
refresh?: boolean;
|
|
672
678
|
convertCustomTypes?: boolean;
|
package/EntityManager.js
CHANGED
|
@@ -116,7 +116,7 @@ export class EntityManager {
|
|
|
116
116
|
return ret;
|
|
117
117
|
}
|
|
118
118
|
const em = this.getContext();
|
|
119
|
-
em.prepareOptions(options);
|
|
119
|
+
options = em.prepareOptions(options);
|
|
120
120
|
const meta = this.metadata.get(entityName);
|
|
121
121
|
em.validateIndexUsage(meta, where, options);
|
|
122
122
|
await em.tryFlush(entityName, options);
|
|
@@ -199,7 +199,7 @@ export class EntityManager {
|
|
|
199
199
|
*/
|
|
200
200
|
async *stream(entityName, options = {}) {
|
|
201
201
|
const em = this.getContext();
|
|
202
|
-
em.prepareOptions(options);
|
|
202
|
+
options = em.prepareOptions(options);
|
|
203
203
|
options.strategy = 'joined';
|
|
204
204
|
await em.tryFlush(entityName, options);
|
|
205
205
|
const where = (await em.processWhere(entityName, options.where ?? {}, options, 'read'));
|
|
@@ -258,6 +258,7 @@ export class EntityManager {
|
|
|
258
258
|
* Registers global filter to this entity manager. Global filters are enabled by default (unless disabled via last parameter).
|
|
259
259
|
*/
|
|
260
260
|
addFilter(options) {
|
|
261
|
+
options = { ...options };
|
|
261
262
|
if (options.entity) {
|
|
262
263
|
options.entity = Utils.asArray(options.entity).map(n => Utils.className(n));
|
|
263
264
|
}
|
|
@@ -528,7 +529,7 @@ export class EntityManager {
|
|
|
528
529
|
async findAndCount(entityName, where, options = {}) {
|
|
529
530
|
const em = this.getContext(false);
|
|
530
531
|
await em.tryFlush(entityName, options);
|
|
531
|
-
options
|
|
532
|
+
options = { ...options, flushMode: 'commit' }; // do not try to auto flush again
|
|
532
533
|
return Promise.all([
|
|
533
534
|
em.find(entityName, where, options),
|
|
534
535
|
em.count(entityName, where, options),
|
|
@@ -592,6 +593,7 @@ export class EntityManager {
|
|
|
592
593
|
*/
|
|
593
594
|
async findByCursor(entityName, options) {
|
|
594
595
|
const em = this.getContext(false);
|
|
596
|
+
options = { ...options };
|
|
595
597
|
options.overfetch ??= true;
|
|
596
598
|
options.where ??= {};
|
|
597
599
|
if (Utils.isEmpty(options.orderBy) && !Raw.hasObjectFragments(options.orderBy)) {
|
|
@@ -610,10 +612,10 @@ export class EntityManager {
|
|
|
610
612
|
async refreshOrFail(entity, options = {}) {
|
|
611
613
|
const ret = await this.refresh(entity, options);
|
|
612
614
|
if (!ret) {
|
|
613
|
-
options.failHandler
|
|
615
|
+
const failHandler = options.failHandler ?? this.config.get('findOneOrFailHandler');
|
|
614
616
|
const wrapped = helper(entity);
|
|
615
617
|
const where = wrapped.getPrimaryKey();
|
|
616
|
-
throw
|
|
618
|
+
throw failHandler(wrapped.__meta.className, where);
|
|
617
619
|
}
|
|
618
620
|
return ret;
|
|
619
621
|
}
|
|
@@ -667,7 +669,7 @@ export class EntityManager {
|
|
|
667
669
|
return ret;
|
|
668
670
|
}
|
|
669
671
|
const em = this.getContext();
|
|
670
|
-
em.prepareOptions(options);
|
|
672
|
+
options = em.prepareOptions(options);
|
|
671
673
|
let entity = em.#unitOfWork.tryGetById(entityName, where, options.schema);
|
|
672
674
|
// query for a not managed entity which is already in the identity map as it
|
|
673
675
|
// was provided with a PK this entity does not exist in the db, there can't
|
|
@@ -739,11 +741,11 @@ export class EntityManager {
|
|
|
739
741
|
}
|
|
740
742
|
if (!entity || isStrictViolation) {
|
|
741
743
|
const key = options.strict ? 'findExactlyOneOrFailHandler' : 'findOneOrFailHandler';
|
|
742
|
-
options.failHandler
|
|
744
|
+
const failHandler = options.failHandler ?? this.config.get(key);
|
|
743
745
|
const name = Utils.className(entityName);
|
|
744
746
|
/* v8 ignore next */
|
|
745
747
|
where = Utils.isEntity(where) ? helper(where).getPrimaryKey() : where;
|
|
746
|
-
throw
|
|
748
|
+
throw failHandler(name, where);
|
|
747
749
|
}
|
|
748
750
|
return entity;
|
|
749
751
|
}
|
|
@@ -778,7 +780,7 @@ export class EntityManager {
|
|
|
778
780
|
return ret;
|
|
779
781
|
}
|
|
780
782
|
const em = this.getContext(false);
|
|
781
|
-
em.prepareOptions(options);
|
|
783
|
+
options = em.prepareOptions(options);
|
|
782
784
|
let entityName;
|
|
783
785
|
let where;
|
|
784
786
|
let entity = null;
|
|
@@ -916,7 +918,7 @@ export class EntityManager {
|
|
|
916
918
|
return ret;
|
|
917
919
|
}
|
|
918
920
|
const em = this.getContext(false);
|
|
919
|
-
em.prepareOptions(options);
|
|
921
|
+
options = em.prepareOptions(options);
|
|
920
922
|
let entityName;
|
|
921
923
|
let propIndex;
|
|
922
924
|
if (data === undefined) {
|
|
@@ -1202,7 +1204,7 @@ export class EntityManager {
|
|
|
1202
1204
|
*/
|
|
1203
1205
|
async lock(entity, lockMode, options = {}) {
|
|
1204
1206
|
options = Utils.isPlainObject(options) ? options : { lockVersion: options };
|
|
1205
|
-
this.getContext(false).prepareOptions(options);
|
|
1207
|
+
options = this.getContext(false).prepareOptions(options);
|
|
1206
1208
|
await this.getUnitOfWork().lock(entity, { lockMode, ...options });
|
|
1207
1209
|
}
|
|
1208
1210
|
/**
|
|
@@ -1210,7 +1212,7 @@ export class EntityManager {
|
|
|
1210
1212
|
*/
|
|
1211
1213
|
async insert(entityNameOrEntity, data, options = {}) {
|
|
1212
1214
|
const em = this.getContext(false);
|
|
1213
|
-
em.prepareOptions(options);
|
|
1215
|
+
options = em.prepareOptions(options);
|
|
1214
1216
|
let entityName;
|
|
1215
1217
|
if (data === undefined) {
|
|
1216
1218
|
entityName = entityNameOrEntity.constructor;
|
|
@@ -1277,7 +1279,7 @@ export class EntityManager {
|
|
|
1277
1279
|
overrides = overridesOrOptions;
|
|
1278
1280
|
}
|
|
1279
1281
|
options ??= {};
|
|
1280
|
-
em.prepareOptions(options);
|
|
1282
|
+
options = em.prepareOptions(options);
|
|
1281
1283
|
const meta = em.metadata.get(entityName);
|
|
1282
1284
|
const res = await em.driver.nativeClone(entityName, where, overrides, {
|
|
1283
1285
|
ctx: em.#transactionContext,
|
|
@@ -1293,7 +1295,7 @@ export class EntityManager {
|
|
|
1293
1295
|
*/
|
|
1294
1296
|
async insertMany(entityNameOrEntities, data, options = {}) {
|
|
1295
1297
|
const em = this.getContext(false);
|
|
1296
|
-
em.prepareOptions(options);
|
|
1298
|
+
options = em.prepareOptions(options);
|
|
1297
1299
|
let entityName;
|
|
1298
1300
|
if (data === undefined) {
|
|
1299
1301
|
entityName = entityNameOrEntities[0].constructor;
|
|
@@ -1336,7 +1338,7 @@ export class EntityManager {
|
|
|
1336
1338
|
*/
|
|
1337
1339
|
async nativeUpdate(entityName, where, data, options = {}) {
|
|
1338
1340
|
const em = this.getContext(false);
|
|
1339
|
-
em.prepareOptions(options);
|
|
1341
|
+
options = em.prepareOptions(options);
|
|
1340
1342
|
await em.processUnionWhere(entityName, options, 'update');
|
|
1341
1343
|
data = QueryHelper.processObjectParams(data);
|
|
1342
1344
|
where = await em.processWhere(entityName, where, { ...options, convertCustomTypes: false }, 'update');
|
|
@@ -1385,7 +1387,7 @@ export class EntityManager {
|
|
|
1385
1387
|
*/
|
|
1386
1388
|
async nativeDelete(entityName, where, options = {}) {
|
|
1387
1389
|
const em = this.getContext(false);
|
|
1388
|
-
em.prepareOptions(options);
|
|
1390
|
+
options = em.prepareOptions(options);
|
|
1389
1391
|
await em.processUnionWhere(entityName, options, 'delete');
|
|
1390
1392
|
where = (await em.processWhere(entityName, where, options, 'delete'));
|
|
1391
1393
|
validateParams(where, 'delete condition');
|
|
@@ -1397,10 +1399,18 @@ export class EntityManager {
|
|
|
1397
1399
|
return res.affectedRows;
|
|
1398
1400
|
}
|
|
1399
1401
|
/**
|
|
1400
|
-
* Maps raw database result to an entity and merges it to this EntityManager.
|
|
1402
|
+
* Maps raw database result to an entity and merges it to this EntityManager by default.
|
|
1403
|
+
* Use `disableIdentityMap` to return an isolated entity without affecting the current context.
|
|
1401
1404
|
*/
|
|
1402
1405
|
map(entityName, result, options = {}) {
|
|
1403
|
-
|
|
1406
|
+
if (options.disableIdentityMap ?? this.config.get('disableIdentityMap')) {
|
|
1407
|
+
const em = this.getContext(false);
|
|
1408
|
+
const fork = em.fork({ keepTransactionContext: true });
|
|
1409
|
+
const ret = fork.map(entityName, result, { ...options, disableIdentityMap: false });
|
|
1410
|
+
fork.clear();
|
|
1411
|
+
return ret;
|
|
1412
|
+
}
|
|
1413
|
+
const { mapped, disableIdentityMap, ...rest } = options;
|
|
1404
1414
|
const meta = this.metadata.get(entityName);
|
|
1405
1415
|
const data = (mapped ? result : this.driver.mapResult(result, meta));
|
|
1406
1416
|
for (const k of Object.keys(data)) {
|
|
@@ -1428,6 +1438,7 @@ export class EntityManager {
|
|
|
1428
1438
|
return this.merge(entityName.constructor, entityName, data);
|
|
1429
1439
|
}
|
|
1430
1440
|
const em = options.disableContextResolution ? this : this.getContext();
|
|
1441
|
+
options = { ...options };
|
|
1431
1442
|
options.schema ??= em.#schema;
|
|
1432
1443
|
options.validate ??= true;
|
|
1433
1444
|
options.cascade ??= true;
|
|
@@ -1461,6 +1472,7 @@ export class EntityManager {
|
|
|
1461
1472
|
*/
|
|
1462
1473
|
create(entityName, data, options = {}) {
|
|
1463
1474
|
const em = this.getContext();
|
|
1475
|
+
options = { ...options };
|
|
1464
1476
|
options.schema ??= em.#schema;
|
|
1465
1477
|
const entity = em.#entityFactory.create(entityName, data, {
|
|
1466
1478
|
...options,
|
|
@@ -1484,6 +1496,7 @@ export class EntityManager {
|
|
|
1484
1496
|
* Gets a reference to the entity identified by the given type and identifier without actually loading it, if the entity is not yet loaded
|
|
1485
1497
|
*/
|
|
1486
1498
|
getReference(entityName, id, options = {}) {
|
|
1499
|
+
options = { ...options };
|
|
1487
1500
|
options.schema ??= this.schema;
|
|
1488
1501
|
options.convertCustomTypes ??= false;
|
|
1489
1502
|
const meta = this.metadata.get(entityName);
|
|
@@ -1504,13 +1517,10 @@ export class EntityManager {
|
|
|
1504
1517
|
*/
|
|
1505
1518
|
async count(entityName, where = {}, options = {}) {
|
|
1506
1519
|
const em = this.getContext(false);
|
|
1507
|
-
|
|
1508
|
-
options = { ...options };
|
|
1509
|
-
em.prepareOptions(options);
|
|
1520
|
+
options = em.prepareOptions(options);
|
|
1510
1521
|
await em.tryFlush(entityName, options);
|
|
1511
1522
|
where = await em.processWhere(entityName, where, options, 'read');
|
|
1512
1523
|
options.populate = (await em.preparePopulate(entityName, options));
|
|
1513
|
-
options = { ...options };
|
|
1514
1524
|
// save the original hint value so we know it was infer/all
|
|
1515
1525
|
const meta = em.metadata.find(entityName);
|
|
1516
1526
|
options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
|
|
@@ -1654,7 +1664,7 @@ export class EntityManager {
|
|
|
1654
1664
|
return entities;
|
|
1655
1665
|
}
|
|
1656
1666
|
const em = this.getContext();
|
|
1657
|
-
em.prepareOptions(options);
|
|
1667
|
+
options = em.prepareOptions(options);
|
|
1658
1668
|
const entityName = arr[0].constructor;
|
|
1659
1669
|
const preparedPopulate = await em.preparePopulate(entityName, { populate: populate, filters: options.filters, populateHints: options.populateHints }, options.validate);
|
|
1660
1670
|
await em.#entityLoader.populate(entityName, arr, preparedPopulate, options);
|
|
@@ -1665,6 +1675,7 @@ export class EntityManager {
|
|
|
1665
1675
|
*/
|
|
1666
1676
|
fork(options = {}) {
|
|
1667
1677
|
const em = options.disableContextResolution ? this : this.getContext(false);
|
|
1678
|
+
options = { ...options };
|
|
1668
1679
|
options.clear ??= true;
|
|
1669
1680
|
options.useContext ??= false;
|
|
1670
1681
|
options.freshEventManager ??= false;
|
|
@@ -2073,10 +2084,13 @@ export class EntityManager {
|
|
|
2073
2084
|
if (!Utils.isEmpty(options.fields) && !Utils.isEmpty(options.exclude)) {
|
|
2074
2085
|
throw new ValidationError(`Cannot combine 'fields' and 'exclude' option.`);
|
|
2075
2086
|
}
|
|
2076
|
-
options
|
|
2077
|
-
options
|
|
2078
|
-
|
|
2079
|
-
|
|
2087
|
+
// the options object belongs to the caller, everything below works on our own copy
|
|
2088
|
+
const opts = { ...options };
|
|
2089
|
+
opts.schema ??= this.#schema;
|
|
2090
|
+
opts.signal ??= this.signal;
|
|
2091
|
+
opts.inflightQueryAbortStrategy ??= this.inflightQueryAbortStrategy;
|
|
2092
|
+
opts.logging = opts.loggerContext = Utils.merge({ id: this.id }, this.loggerContext, opts.loggerContext, opts.logging);
|
|
2093
|
+
return opts;
|
|
2080
2094
|
}
|
|
2081
2095
|
/**
|
|
2082
2096
|
* @internal
|
package/MikroORM.d.ts
CHANGED
|
@@ -82,6 +82,10 @@ export declare class MikroORM<Driver extends IDatabaseDriver = IDatabaseDriver,
|
|
|
82
82
|
* Closes the database connection.
|
|
83
83
|
*/
|
|
84
84
|
close(force?: boolean): Promise<void>;
|
|
85
|
+
/**
|
|
86
|
+
* Closes the database connection, allows using the ORM instance with `await using`.
|
|
87
|
+
*/
|
|
88
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
85
89
|
/**
|
|
86
90
|
* Gets the `MetadataStorage`.
|
|
87
91
|
*/
|
package/MikroORM.js
CHANGED
|
@@ -159,6 +159,12 @@ export class MikroORM {
|
|
|
159
159
|
await this.config.getMetadataCacheAdapter()?.close?.();
|
|
160
160
|
await this.config.getResultCacheAdapter()?.close?.();
|
|
161
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Closes the database connection, allows using the ORM instance with `await using`.
|
|
164
|
+
*/
|
|
165
|
+
async [Symbol.asyncDispose]() {
|
|
166
|
+
await this.close();
|
|
167
|
+
}
|
|
162
168
|
/**
|
|
163
169
|
* Gets the `MetadataStorage` (without parameters) or `EntityMetadata` instance when provided with the `entityName` parameter.
|
|
164
170
|
*/
|
package/cache/CacheAdapter.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/** Interface for async-capable cache storage used by result cache and metadata cache. */
|
|
2
2
|
export interface CacheAdapter {
|
|
3
3
|
/**
|
|
4
|
-
* Gets the items under `name` key from the cache.
|
|
4
|
+
* Gets the items under `name` key from the cache. When `origin` is provided, adapters that
|
|
5
|
+
* track the entry origin should ignore entries cached from a different source file.
|
|
5
6
|
*/
|
|
6
|
-
get<T = any>(name: string): T | Promise<T | undefined> | undefined;
|
|
7
|
+
get<T = any>(name: string, origin?: string): T | Promise<T | undefined> | undefined;
|
|
7
8
|
/**
|
|
8
9
|
* Sets the item to the cache. `origin` is used for cache invalidation and should reflect the change in data.
|
|
9
10
|
*/
|
|
@@ -24,9 +25,10 @@ export interface CacheAdapter {
|
|
|
24
25
|
/** Synchronous variant of CacheAdapter, used for metadata cache where async access is not needed. */
|
|
25
26
|
export interface SyncCacheAdapter extends CacheAdapter {
|
|
26
27
|
/**
|
|
27
|
-
* Gets the items under `name` key from the cache.
|
|
28
|
+
* Gets the items under `name` key from the cache. When `origin` is provided, adapters that
|
|
29
|
+
* track the entry origin should ignore entries cached from a different source file.
|
|
28
30
|
*/
|
|
29
|
-
get<T = any>(name: string): T | undefined;
|
|
31
|
+
get<T = any>(name: string, origin?: string): T | undefined;
|
|
30
32
|
/**
|
|
31
33
|
* Sets the item to the cache. `origin` is used for cache invalidation and should reflect the change in data.
|
|
32
34
|
*/
|
|
@@ -16,12 +16,17 @@ export class FileCacheAdapter {
|
|
|
16
16
|
/**
|
|
17
17
|
* @inheritDoc
|
|
18
18
|
*/
|
|
19
|
-
get(name) {
|
|
19
|
+
get(name, origin) {
|
|
20
20
|
const path = this.path(name);
|
|
21
21
|
if (!existsSync(path)) {
|
|
22
22
|
return null;
|
|
23
23
|
}
|
|
24
24
|
const payload = fs.readJSONSync(path);
|
|
25
|
+
// Two classes with the same name share the cache key, ignore entries cached
|
|
26
|
+
// from a different source file.
|
|
27
|
+
if (origin && fs.absolutePath(payload.origin, this.#baseDir) !== fs.absolutePath(origin, this.#baseDir)) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
25
30
|
const hash = this.getHash(payload.origin);
|
|
26
31
|
if (!hash || payload.hash !== hash) {
|
|
27
32
|
return null;
|
|
@@ -71,7 +76,7 @@ export class FileCacheAdapter {
|
|
|
71
76
|
let path = typeof this.#options.combined === 'string' ? this.#options.combined : './metadata.json';
|
|
72
77
|
path = fs.normalizePath(this.#options.cacheDir, path);
|
|
73
78
|
this.#options.combined = path; // override in the options, so we can log it from the CLI in `cache:generate` command
|
|
74
|
-
writeFileSync(path, JSON.stringify(this.#cache, null, this.#pretty ? 2 : undefined));
|
|
79
|
+
writeFileSync(path, JSON.stringify(this.#cache, null, this.#pretty ? 2 : undefined), { flush: true });
|
|
75
80
|
return path;
|
|
76
81
|
}
|
|
77
82
|
path(name) {
|
|
@@ -51,6 +51,13 @@ export declare abstract class Connection {
|
|
|
51
51
|
* This method doesn't support transactions, as opposed to `orm.schema.execute()`, which is used internally.
|
|
52
52
|
*/
|
|
53
53
|
executeDump(dump: string): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Returns the underlying database client the connection drives — e.g. the `pg` pool, the
|
|
56
|
+
* `better-sqlite3` database, or the `PGlite` instance — for vendor APIs MikroORM does not wrap.
|
|
57
|
+
* Each driver narrows the return type to its own client. Its lifecycle belongs to the ORM, so
|
|
58
|
+
* leave closing it to `orm.close()` unless you supplied the client yourself via `driverOptions`.
|
|
59
|
+
*/
|
|
60
|
+
getNativeClient(): Promise<unknown>;
|
|
54
61
|
protected onConnect(): Promise<void>;
|
|
55
62
|
/** Executes a callback inside a transaction, committing on success and rolling back on failure. */
|
|
56
63
|
transactional<T>(cb: (trx: Transaction) => Promise<T>, options?: {
|
|
@@ -65,6 +65,15 @@ export class Connection {
|
|
|
65
65
|
async executeDump(dump) {
|
|
66
66
|
throw new Error(`Executing SQL dumps is not supported by current driver`);
|
|
67
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Returns the underlying database client the connection drives — e.g. the `pg` pool, the
|
|
70
|
+
* `better-sqlite3` database, or the `PGlite` instance — for vendor APIs MikroORM does not wrap.
|
|
71
|
+
* Each driver narrows the return type to its own client. Its lifecycle belongs to the ORM, so
|
|
72
|
+
* leave closing it to `orm.close()` unless you supplied the client yourself via `driverOptions`.
|
|
73
|
+
*/
|
|
74
|
+
async getNativeClient() {
|
|
75
|
+
throw new Error(`Accessing the native client is not supported by current driver`);
|
|
76
|
+
}
|
|
68
77
|
async onConnect() {
|
|
69
78
|
const schemaGenerator = this.config.getExtension('@mikro-orm/schema-generator');
|
|
70
79
|
if (this.type === 'write' && schemaGenerator) {
|
|
@@ -64,6 +64,13 @@ export declare abstract class DatabaseDriver<C extends Connection> implements ID
|
|
|
64
64
|
orderBy: OrderDefinition<T>[];
|
|
65
65
|
where: FilterQuery<T>;
|
|
66
66
|
};
|
|
67
|
+
/**
|
|
68
|
+
* Restores the JS value of a single cursor offset: ISO strings become `Date` instances based on the
|
|
69
|
+
* property type (never based on the string shape alone), and custom types are restored via
|
|
70
|
+
* `convertToJSValue`. Values compared against a JSON document keep their serialized form instead,
|
|
71
|
+
* unless the platform preserves native date types inside JSON documents (mongo).
|
|
72
|
+
*/
|
|
73
|
+
private mapCursorOffset;
|
|
67
74
|
protected createCursorCondition<T extends object>(definition: (readonly [keyof T & string, QueryOrder])[], offsets: Dictionary[], inverse: boolean, meta: EntityMetadata<T>): FilterQuery<T>;
|
|
68
75
|
/** @internal */
|
|
69
76
|
mapDataToFieldNames(data: Dictionary, stringifyJsonArrays: boolean, properties?: Record<string, EntityProperty>, convertCustomTypes?: boolean, object?: boolean): Dictionary;
|
|
@@ -7,8 +7,11 @@ import { EntityManager } from '../EntityManager.js';
|
|
|
7
7
|
import { CursorError, ValidationError } from '../errors.js';
|
|
8
8
|
import { DriverException } from '../exceptions.js';
|
|
9
9
|
import { helper } from '../entity/wrap.js';
|
|
10
|
+
import { Reference } from '../entity/Reference.js';
|
|
10
11
|
import { PolymorphicRef } from '../entity/PolymorphicRef.js';
|
|
11
12
|
import { JsonType } from '../types/JsonType.js';
|
|
13
|
+
import { DateTimeType } from '../types/DateTimeType.js';
|
|
14
|
+
import { QueryHelper } from '../utils/QueryHelper.js';
|
|
12
15
|
import { MikroORM } from '../MikroORM.js';
|
|
13
16
|
/** Abstract base class for all database drivers, implementing common driver logic. */
|
|
14
17
|
export class DatabaseDriver {
|
|
@@ -141,13 +144,24 @@ export class DatabaseDriver {
|
|
|
141
144
|
return !!val && typeof val === 'object' && key in val;
|
|
142
145
|
};
|
|
143
146
|
const createCursor = (val, key, inverse = false) => {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
+
const def = Reference.unwrapReference((isCursor(val, key) ? val[key] : val));
|
|
148
|
+
let offsets;
|
|
149
|
+
// entity (and reference) instances are supported as cursors too, their properties are read the same way
|
|
150
|
+
if (Utils.isPlainObject(def) || Utils.isEntity(def)) {
|
|
151
|
+
// POJO values are already JS values, extract them ordered per the definition,
|
|
152
|
+
// without the JSON round trip `Cursor.for` + `Cursor.decode` would impose
|
|
153
|
+
offsets = definition.map(([key]) => {
|
|
154
|
+
if (def[key] === undefined) {
|
|
155
|
+
throw CursorError.missingValue(meta.className, key);
|
|
156
|
+
}
|
|
157
|
+
return def[key];
|
|
158
|
+
});
|
|
147
159
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
160
|
+
else {
|
|
161
|
+
/* v8 ignore next */
|
|
162
|
+
offsets = def ? Cursor.decode(def) : [];
|
|
163
|
+
}
|
|
164
|
+
if (definition.length > 0 && definition.length === offsets.length) {
|
|
151
165
|
return this.createCursorCondition(definition, offsets, inverse, meta);
|
|
152
166
|
}
|
|
153
167
|
/* v8 ignore next */
|
|
@@ -174,19 +188,67 @@ export class DatabaseDriver {
|
|
|
174
188
|
const dir = Utils.xor(desc, isLast) ? 'desc' : 'asc';
|
|
175
189
|
return { [prop]: dir };
|
|
176
190
|
};
|
|
191
|
+
// the cursor condition is created at the driver level, after the EM already converted custom types
|
|
192
|
+
// in the user `where`, so we need to run the same conversion over it explicitly
|
|
193
|
+
const where = QueryHelper.processWhere({
|
|
194
|
+
where: ($and.length > 1 ? { $and } : { ...$and[0] }),
|
|
195
|
+
entityName: meta.class,
|
|
196
|
+
metadata: this.metadata,
|
|
197
|
+
platform: this.platform,
|
|
198
|
+
convertCustomTypes: options.convertCustomTypes,
|
|
199
|
+
});
|
|
177
200
|
return {
|
|
178
201
|
orderBy: definition.map(([prop, direction]) => createOrderBy(prop, direction)),
|
|
179
|
-
where
|
|
202
|
+
where,
|
|
180
203
|
};
|
|
181
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* Restores the JS value of a single cursor offset: ISO strings become `Date` instances based on the
|
|
207
|
+
* property type (never based on the string shape alone), and custom types are restored via
|
|
208
|
+
* `convertToJSValue`. Values compared against a JSON document keep their serialized form instead,
|
|
209
|
+
* unless the platform preserves native date types inside JSON documents (mongo).
|
|
210
|
+
*/
|
|
211
|
+
mapCursorOffset(prop, value, insideJson) {
|
|
212
|
+
if (Utils.isScalarReference(value)) {
|
|
213
|
+
value = value.unwrap();
|
|
214
|
+
}
|
|
215
|
+
// scalar direction on a relation orders by its primary key
|
|
216
|
+
if (Utils.isEntity(value, true)) {
|
|
217
|
+
value = helper(value).getPrimaryKey();
|
|
218
|
+
}
|
|
219
|
+
if (value == null) {
|
|
220
|
+
return value;
|
|
221
|
+
}
|
|
222
|
+
if (insideJson && !this.platform.preservesDatesInsideJson()) {
|
|
223
|
+
// compared against the JSON document, which holds the serialized form
|
|
224
|
+
if (value instanceof Date) {
|
|
225
|
+
return value.toISOString();
|
|
226
|
+
}
|
|
227
|
+
// restore the JS value from the serialized form, `processWhere` then converts it to
|
|
228
|
+
// the database form, which is what the JSON document holds for custom typed props
|
|
229
|
+
return prop?.customType ? prop.customType.convertToJSValue(value, this.platform) : value;
|
|
230
|
+
}
|
|
231
|
+
if (typeof value === 'string' &&
|
|
232
|
+
(prop?.runtimeType === 'Date' ||
|
|
233
|
+
(prop?.customType && this.platform.getMappedType(prop.columnTypes?.[0] ?? '') instanceof DateTimeType))) {
|
|
234
|
+
value = new Date(value);
|
|
235
|
+
}
|
|
236
|
+
return prop?.customType ? prop.customType.convertToJSValue(value, this.platform) : value;
|
|
237
|
+
}
|
|
182
238
|
createCursorCondition(definition, offsets, inverse, meta) {
|
|
183
|
-
const createCondition = (prop, direction, offset, eq = false, path = prop) => {
|
|
239
|
+
const createCondition = (prop, direction, offset, eq = false, path = prop, properties = meta.properties, insideJson = false) => {
|
|
240
|
+
const propMeta = properties[prop];
|
|
184
241
|
if (Utils.isPlainObject(direction)) {
|
|
185
242
|
if (offset === undefined) {
|
|
186
243
|
throw CursorError.missingValue(meta.className, path);
|
|
187
244
|
}
|
|
245
|
+
// POJO cursors can carry entity, reference or embeddable class instances, read their properties directly
|
|
246
|
+
offset = Reference.unwrapReference(offset);
|
|
247
|
+
const childProps = propMeta?.kind === ReferenceKind.EMBEDDED ? propMeta.embeddedProps : propMeta?.targetMeta?.properties;
|
|
248
|
+
insideJson ||=
|
|
249
|
+
(propMeta?.kind === ReferenceKind.EMBEDDED && !!propMeta.object) || propMeta?.customType instanceof JsonType;
|
|
188
250
|
const value = Utils.keys(direction).reduce((o, key) => {
|
|
189
|
-
Object.assign(o, createCondition(key, direction[key], offset?.[key], eq, `${path}.${key}
|
|
251
|
+
Object.assign(o, createCondition(key, direction[key], offset?.[key], eq, `${path}.${key}`, childProps ?? {}, insideJson));
|
|
190
252
|
return o;
|
|
191
253
|
}, {});
|
|
192
254
|
return { [prop]: value };
|
|
@@ -209,6 +271,7 @@ export class DatabaseDriver {
|
|
|
209
271
|
if (offset === undefined) {
|
|
210
272
|
throw CursorError.missingValue(meta.className, path);
|
|
211
273
|
}
|
|
274
|
+
offset = this.mapCursorOffset(propMeta, offset, insideJson);
|
|
212
275
|
// Handle null offset (intentional null cursor value)
|
|
213
276
|
if (offset === null) {
|
|
214
277
|
if (eq) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PopulatePath } from '../enums.js';
|
|
2
|
-
import type { CreateOptions, EntityManager, MergeOptions } from '../EntityManager.js';
|
|
2
|
+
import type { CreateOptions, EntityManager, MapOptions, MergeOptions } from '../EntityManager.js';
|
|
3
3
|
import type { AssignOptions } from './EntityAssigner.js';
|
|
4
4
|
import type { Dictionary, EntityData, EntityDictionary, EntityKey, EntityName, FilterQuery, Loaded, Primary, AutoPath, RequiredEntityData, Ref, EntityType, EntityDTO, MergeSelected, FromEntityType, IsSubset, MergeLoaded, ArrayElement, IndexFilterQuery, WithUsingOptions } from '../typings.js';
|
|
5
5
|
import type { CountByOptions, CountOptions, DeleteOptions, FindAllOptions, FindByCursorOptions, FindOneOptions, FindOneOrFailOptions, FindOptions, GetReferenceOptions, NativeInsertUpdateOptions, StreamOptions, UpdateOptions, UpsertManyOptions, UpsertOptions } from '../drivers/IDatabaseDriver.js';
|
|
@@ -115,11 +115,10 @@ export declare class EntityRepository<Entity extends object> {
|
|
|
115
115
|
*/
|
|
116
116
|
nativeDelete(where: FilterQuery<Entity>, options?: DeleteOptions<Entity>): Promise<number>;
|
|
117
117
|
/**
|
|
118
|
-
* Maps raw database result to an entity and merges it to this EntityManager.
|
|
118
|
+
* Maps raw database result to an entity and merges it to this EntityManager by default.
|
|
119
|
+
* Use `disableIdentityMap` to return an isolated entity without affecting the current context.
|
|
119
120
|
*/
|
|
120
|
-
map(result: EntityDictionary<Entity>, options?:
|
|
121
|
-
schema?: string;
|
|
122
|
-
}): Entity;
|
|
121
|
+
map(result: EntityDictionary<Entity>, options?: Omit<MapOptions, 'mapped'>): Entity;
|
|
123
122
|
/**
|
|
124
123
|
* Gets a reference to the entity identified by the given type and alternate key property without actually loading it.
|
|
125
124
|
* The key option specifies which property to use for identity map lookup instead of the primary key.
|
|
@@ -131,7 +131,8 @@ export class EntityRepository {
|
|
|
131
131
|
return this.getEntityManager().nativeDelete(this.entityName, where, options);
|
|
132
132
|
}
|
|
133
133
|
/**
|
|
134
|
-
* Maps raw database result to an entity and merges it to this EntityManager.
|
|
134
|
+
* Maps raw database result to an entity and merges it to this EntityManager by default.
|
|
135
|
+
* Use `disableIdentityMap` to return an isolated entity without affecting the current context.
|
|
135
136
|
*/
|
|
136
137
|
map(result, options) {
|
|
137
138
|
return this.getEntityManager().map(this.entityName, result, options);
|
|
@@ -364,6 +364,9 @@ export class ObjectHydrator extends Hydrator {
|
|
|
364
364
|
ret.push(` data${dataKey}.forEach((_, idx_${idx}) => {`);
|
|
365
365
|
ret.push(...hydrateEmbedded(prop, [...path, `[idx_${idx}]`], `${dataKey}[idx_${idx}]`).map(l => ' ' + l));
|
|
366
366
|
ret.push(` });`);
|
|
367
|
+
ret.push(` } else if (data${dataKey} === null) {`);
|
|
368
|
+
/* v8 ignore next */
|
|
369
|
+
ret.push(` entity${entityKey} = ${this.config.get('forceUndefined') ? 'undefined' : 'null'};`);
|
|
367
370
|
ret.push(` }`);
|
|
368
371
|
return ret;
|
|
369
372
|
};
|
package/metadata/EntitySchema.js
CHANGED
|
@@ -4,6 +4,8 @@ import { Cascade, ReferenceKind } from '../enums.js';
|
|
|
4
4
|
import { Type } from '../types/Type.js';
|
|
5
5
|
import { Utils } from '../utils/Utils.js';
|
|
6
6
|
import { EnumArrayType } from '../types/EnumArrayType.js';
|
|
7
|
+
// mirrors `MetadataStorage.META_SYMBOL`, we can't import it here due to a module cycle
|
|
8
|
+
const META_SYMBOL = Symbol.for('@mikro-orm/core/MetadataStorage.META_SYMBOL');
|
|
7
9
|
/** Class-less entity definition that provides a programmatic API for defining entities without decorators. */
|
|
8
10
|
export class EntitySchema {
|
|
9
11
|
/**
|
|
@@ -220,8 +222,9 @@ export class EntitySchema {
|
|
|
220
222
|
// Only set extends if the parent is NOT the auto-generated class for this same entity.
|
|
221
223
|
// When the user extends the auto-generated class (from defineEntity without a class option)
|
|
222
224
|
// and registers their custom class via setClass, we don't want to discover the
|
|
223
|
-
// auto-generated class as a separate parent entity.
|
|
224
|
-
|
|
225
|
+
// auto-generated class as a separate parent entity. A parent carrying its own decorator
|
|
226
|
+
// metadata is a real base class even when a minifier mangles it to the same name as the child.
|
|
227
|
+
if (base !== BaseEntity && (base.name !== this._meta.className || Object.hasOwn(base, META_SYMBOL))) {
|
|
225
228
|
this._meta.extends ??= base.name ? base : undefined;
|
|
226
229
|
}
|
|
227
230
|
}
|