@mikro-orm/core 7.2.0-dev.0 → 7.2.0-dev.10
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 +1 -1
- package/EntityManager.js +32 -26
- 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/EntityAssigner.js +8 -2
- package/entity/EntityFactory.js +1 -1
- package/entity/EntityLoader.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/MetadataValidator.js +1 -13
- 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/ChangeSetComputer.js +2 -1
- package/unit-of-work/ChangeSetPersister.js +15 -7
- package/unit-of-work/IdentityMap.d.ts +5 -0
- package/unit-of-work/IdentityMap.js +7 -0
- package/unit-of-work/UnitOfWork.d.ts +1 -1
- package/unit-of-work/UnitOfWork.js +2 -2
- package/utils/AbstractMigrator.d.ts +3 -1
- package/utils/AbstractMigrator.js +14 -3
- package/utils/Configuration.d.ts +6 -0
- package/utils/Configuration.js +3 -1
- package/utils/Cursor.js +1 -6
- package/utils/EntityComparator.js +8 -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 +36 -3
- package/utils/Utils.d.ts +12 -0
- package/utils/Utils.js +19 -4
- package/utils/clone.js +6 -0
- package/utils/env-vars.js +1 -0
package/EntityManager.d.ts
CHANGED
|
@@ -601,7 +601,7 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
601
601
|
* some additional lazy properties, if so, we reload and merge the data from database
|
|
602
602
|
*/
|
|
603
603
|
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
|
|
604
|
+
protected prepareOptions<Options extends (FindOptions<any, any, any, any> | FindOneOptions<any, any, any, any> | CountOptions<any, any> | CountByOptions<any>) & AbortQueryOptions>(options: Options): Options;
|
|
605
605
|
/**
|
|
606
606
|
* @internal
|
|
607
607
|
*/
|
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');
|
|
@@ -1428,11 +1430,12 @@ export class EntityManager {
|
|
|
1428
1430
|
return this.merge(entityName.constructor, entityName, data);
|
|
1429
1431
|
}
|
|
1430
1432
|
const em = options.disableContextResolution ? this : this.getContext();
|
|
1433
|
+
options = { ...options };
|
|
1431
1434
|
options.schema ??= em.#schema;
|
|
1432
1435
|
options.validate ??= true;
|
|
1433
1436
|
options.cascade ??= true;
|
|
1434
1437
|
validatePrimaryKey(data, em.metadata.get(entityName));
|
|
1435
|
-
let entity = em.#unitOfWork.tryGetById(entityName, data, options.schema, false);
|
|
1438
|
+
let entity = em.#unitOfWork.tryGetById(entityName, data, options.schema, false, options.convertCustomTypes);
|
|
1436
1439
|
if (entity && helper(entity).__managed && helper(entity).__initialized && !options.refresh) {
|
|
1437
1440
|
return entity;
|
|
1438
1441
|
}
|
|
@@ -1461,6 +1464,7 @@ export class EntityManager {
|
|
|
1461
1464
|
*/
|
|
1462
1465
|
create(entityName, data, options = {}) {
|
|
1463
1466
|
const em = this.getContext();
|
|
1467
|
+
options = { ...options };
|
|
1464
1468
|
options.schema ??= em.#schema;
|
|
1465
1469
|
const entity = em.#entityFactory.create(entityName, data, {
|
|
1466
1470
|
...options,
|
|
@@ -1484,6 +1488,7 @@ export class EntityManager {
|
|
|
1484
1488
|
* 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
1489
|
*/
|
|
1486
1490
|
getReference(entityName, id, options = {}) {
|
|
1491
|
+
options = { ...options };
|
|
1487
1492
|
options.schema ??= this.schema;
|
|
1488
1493
|
options.convertCustomTypes ??= false;
|
|
1489
1494
|
const meta = this.metadata.get(entityName);
|
|
@@ -1504,13 +1509,10 @@ export class EntityManager {
|
|
|
1504
1509
|
*/
|
|
1505
1510
|
async count(entityName, where = {}, options = {}) {
|
|
1506
1511
|
const em = this.getContext(false);
|
|
1507
|
-
|
|
1508
|
-
options = { ...options };
|
|
1509
|
-
em.prepareOptions(options);
|
|
1512
|
+
options = em.prepareOptions(options);
|
|
1510
1513
|
await em.tryFlush(entityName, options);
|
|
1511
1514
|
where = await em.processWhere(entityName, where, options, 'read');
|
|
1512
1515
|
options.populate = (await em.preparePopulate(entityName, options));
|
|
1513
|
-
options = { ...options };
|
|
1514
1516
|
// save the original hint value so we know it was infer/all
|
|
1515
1517
|
const meta = em.metadata.find(entityName);
|
|
1516
1518
|
options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
|
|
@@ -1654,7 +1656,7 @@ export class EntityManager {
|
|
|
1654
1656
|
return entities;
|
|
1655
1657
|
}
|
|
1656
1658
|
const em = this.getContext();
|
|
1657
|
-
em.prepareOptions(options);
|
|
1659
|
+
options = em.prepareOptions(options);
|
|
1658
1660
|
const entityName = arr[0].constructor;
|
|
1659
1661
|
const preparedPopulate = await em.preparePopulate(entityName, { populate: populate, filters: options.filters, populateHints: options.populateHints }, options.validate);
|
|
1660
1662
|
await em.#entityLoader.populate(entityName, arr, preparedPopulate, options);
|
|
@@ -1665,6 +1667,7 @@ export class EntityManager {
|
|
|
1665
1667
|
*/
|
|
1666
1668
|
fork(options = {}) {
|
|
1667
1669
|
const em = options.disableContextResolution ? this : this.getContext(false);
|
|
1670
|
+
options = { ...options };
|
|
1668
1671
|
options.clear ??= true;
|
|
1669
1672
|
options.useContext ??= false;
|
|
1670
1673
|
options.freshEventManager ??= false;
|
|
@@ -2073,10 +2076,13 @@ export class EntityManager {
|
|
|
2073
2076
|
if (!Utils.isEmpty(options.fields) && !Utils.isEmpty(options.exclude)) {
|
|
2074
2077
|
throw new ValidationError(`Cannot combine 'fields' and 'exclude' option.`);
|
|
2075
2078
|
}
|
|
2076
|
-
options
|
|
2077
|
-
options
|
|
2078
|
-
|
|
2079
|
-
|
|
2079
|
+
// the options object belongs to the caller, everything below works on our own copy
|
|
2080
|
+
const opts = { ...options };
|
|
2081
|
+
opts.schema ??= this.#schema;
|
|
2082
|
+
opts.signal ??= this.signal;
|
|
2083
|
+
opts.inflightQueryAbortStrategy ??= this.inflightQueryAbortStrategy;
|
|
2084
|
+
opts.logging = opts.loggerContext = Utils.merge({ id: this.id }, this.loggerContext, opts.loggerContext, opts.logging);
|
|
2085
|
+
return opts;
|
|
2080
2086
|
}
|
|
2081
2087
|
/**
|
|
2082
2088
|
* @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) {
|
package/entity/EntityAssigner.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Collection } from './Collection.js';
|
|
2
|
-
import { Utils } from '../utils/Utils.js';
|
|
2
|
+
import { DANGEROUS_PROPERTY_NAMES, Utils } from '../utils/Utils.js';
|
|
3
3
|
import { Reference } from './Reference.js';
|
|
4
4
|
import { ReferenceKind, SCALAR_TYPES } from '../enums.js';
|
|
5
5
|
import { validateProperty } from './validators.js';
|
|
@@ -35,6 +35,10 @@ export class EntityAssigner {
|
|
|
35
35
|
return entity;
|
|
36
36
|
}
|
|
37
37
|
static assignProperty(entity, propName, props, data, options) {
|
|
38
|
+
// needs to happen before the `props` lookup, as those keys resolve to inherited accessors
|
|
39
|
+
if (DANGEROUS_PROPERTY_NAMES.includes(propName)) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
38
42
|
let value = data[propName];
|
|
39
43
|
const onlyProperties = options.onlyProperties && !(propName in props);
|
|
40
44
|
const ignoreUndefined = options.ignoreUndefined === true && value === undefined;
|
|
@@ -183,7 +187,9 @@ export class EntityAssigner {
|
|
|
183
187
|
if (options.updateNestedEntities && options.updateByPrimaryKey && Utils.isPlainObject(item)) {
|
|
184
188
|
const pk = Utils.extractPK(item, prop.targetMeta);
|
|
185
189
|
if (pk && EntityAssigner.validateEM(em)) {
|
|
186
|
-
const ref = em
|
|
190
|
+
const ref = em
|
|
191
|
+
.getUnitOfWork()
|
|
192
|
+
.getById(prop.targetMeta.class, pk, options.schema, options.convertCustomTypes);
|
|
187
193
|
if (ref) {
|
|
188
194
|
return EntityAssigner.assign(ref, item, options);
|
|
189
195
|
}
|
package/entity/EntityFactory.js
CHANGED
|
@@ -421,7 +421,7 @@ export class EntityFactory {
|
|
|
421
421
|
const value = data[k];
|
|
422
422
|
if (prop && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && value) {
|
|
423
423
|
const pk = Reference.unwrapReference(value);
|
|
424
|
-
const entity = this.unitOfWork.getById(prop.targetMeta.class, pk, options.schema,
|
|
424
|
+
const entity = this.unitOfWork.getById(prop.targetMeta.class, pk, options.schema, options.convertCustomTypes);
|
|
425
425
|
if (entity) {
|
|
426
426
|
return entity;
|
|
427
427
|
}
|
package/entity/EntityLoader.js
CHANGED
|
@@ -291,7 +291,8 @@ export class EntityLoader {
|
|
|
291
291
|
}
|
|
292
292
|
for (const child of children) {
|
|
293
293
|
const fk = child.__helper.__data[prop.mappedBy] ?? child[prop.mappedBy];
|
|
294
|
-
|
|
294
|
+
// check for `null`/`undefined` explicitly, the FK can be a falsy raw PK value like `0` (e.g. with `mapToPk`)
|
|
295
|
+
if (fk != null) {
|
|
295
296
|
let key;
|
|
296
297
|
if (targetKey) {
|
|
297
298
|
// `fk` is the owner reference (resolve its targetKey) unless the relation maps to the raw PK value
|
|
@@ -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
|
}
|