@mikro-orm/core 7.2.8-dev.1 → 7.2.8-dev.3
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.js +3 -6
- package/cache/cache-key.d.ts +5 -0
- package/cache/cache-key.js +30 -0
- package/package.json +1 -1
- package/utils/DataloaderUtils.js +19 -8
- package/utils/Utils.js +1 -1
package/EntityManager.js
CHANGED
|
@@ -19,6 +19,7 @@ import { TransactionEventBroadcaster } from './events/TransactionEventBroadcaste
|
|
|
19
19
|
import { MetadataError, OptimisticLockError, ValidationError } from './errors.js';
|
|
20
20
|
import { applyPopulateHints, getLoadingStrategy } from './entity/utils.js';
|
|
21
21
|
import { TransactionManager } from './utils/TransactionManager.js';
|
|
22
|
+
import { scopeCacheKey, stringifyCacheKey } from './cache/cache-key.js';
|
|
22
23
|
/**
|
|
23
24
|
* The EntityManager is the central access point to ORM functionality. It is a facade to all different ORM subsystems
|
|
24
25
|
* such as UnitOfWork, Query Language, and Repository API.
|
|
@@ -2458,11 +2459,7 @@ export class EntityManager {
|
|
|
2458
2459
|
const em = this.getContext();
|
|
2459
2460
|
// a named cache key (`config[0]`) discards the computed `key`, which already carries the session context — so
|
|
2460
2461
|
// scope it here too, otherwise a fork's rows would be served to another session context under the same name
|
|
2461
|
-
const cacheKey = Array.isArray(config)
|
|
2462
|
-
? em.#sessionContext
|
|
2463
|
-
? `${config[0]}|${JSON.stringify(em.#sessionContext)}`
|
|
2464
|
-
: config[0]
|
|
2465
|
-
: JSON.stringify(key);
|
|
2462
|
+
const cacheKey = Array.isArray(config) ? scopeCacheKey(config[0], em.#sessionContext) : stringifyCacheKey(key);
|
|
2466
2463
|
const cached = await em.#resultCache.get(cacheKey);
|
|
2467
2464
|
if (!cached) {
|
|
2468
2465
|
return { key: cacheKey, data: cached };
|
|
@@ -2515,7 +2512,7 @@ export class EntityManager {
|
|
|
2515
2512
|
await em.#resultCache.remove(cacheKey);
|
|
2516
2513
|
// named keys are scoped by the session context (see `tryCache`), so clear this context's variant too
|
|
2517
2514
|
if (em.#sessionContext) {
|
|
2518
|
-
await em.#resultCache.remove(
|
|
2515
|
+
await em.#resultCache.remove(scopeCacheKey(cacheKey, em.#sessionContext));
|
|
2519
2516
|
}
|
|
2520
2517
|
}
|
|
2521
2518
|
/**
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { SessionContext } from '../typings.js';
|
|
2
|
+
/** Serializes a result cache or dataloader batch key, telling apart all values a query can contain. */
|
|
3
|
+
export declare function stringifyCacheKey(key: unknown): string;
|
|
4
|
+
/** Scopes a named result cache key by the session context, so rows are not served across session contexts. */
|
|
5
|
+
export declare function scopeCacheKey(name: string, sessionContext?: SessionContext): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { isRaw, isSubquery, Raw } from '../utils/RawQueryFragment.js';
|
|
2
|
+
import { Utils } from '../utils/Utils.js';
|
|
3
|
+
// JSON loses RegExp patterns, `raw()` params and `raw()` keys (symbols), and throws on bigints and sub-queries; `this[key]` is the value before `toJSON()`
|
|
4
|
+
function replacer(key, value) {
|
|
5
|
+
const original = isSubquery(this[key]) ? this[key].toRaw() : this[key];
|
|
6
|
+
if (typeof original === 'bigint') {
|
|
7
|
+
return { bigint: original.toString() };
|
|
8
|
+
}
|
|
9
|
+
if (original instanceof RegExp) {
|
|
10
|
+
return { regexp: original.source, flags: original.flags };
|
|
11
|
+
}
|
|
12
|
+
if (isRaw(original)) {
|
|
13
|
+
return { sql: original.sql, params: original.params };
|
|
14
|
+
}
|
|
15
|
+
if (Raw.hasObjectFragments(value)) {
|
|
16
|
+
return Utils.getObjectQueryKeys(value).map(k => [
|
|
17
|
+
Raw.getKnownFragment(k) ?? k,
|
|
18
|
+
value[k],
|
|
19
|
+
]);
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
/** Serializes a result cache or dataloader batch key, telling apart all values a query can contain. */
|
|
24
|
+
export function stringifyCacheKey(key) {
|
|
25
|
+
return JSON.stringify(key, replacer);
|
|
26
|
+
}
|
|
27
|
+
/** Scopes a named result cache key by the session context, so rows are not served across session contexts. */
|
|
28
|
+
export function scopeCacheKey(name, sessionContext) {
|
|
29
|
+
return sessionContext ? `${name}|${JSON.stringify(sessionContext)}` : name;
|
|
30
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/core",
|
|
3
|
-
"version": "7.2.8-dev.
|
|
3
|
+
"version": "7.2.8-dev.3",
|
|
4
4
|
"description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"data-mapper",
|
package/utils/DataloaderUtils.js
CHANGED
|
@@ -3,6 +3,9 @@ import { helper } from '../entity/wrap.js';
|
|
|
3
3
|
import { Reference } from '../entity/Reference.js';
|
|
4
4
|
import { ReferenceKind } from '../enums.js';
|
|
5
5
|
import { Utils } from './Utils.js';
|
|
6
|
+
import { stringifyCacheKey } from '../cache/cache-key.js';
|
|
7
|
+
// the original options of each group key, JSON cannot restore values like RegExp or `raw()` from the key
|
|
8
|
+
const groupOptions = new WeakMap();
|
|
6
9
|
export class DataloaderUtils {
|
|
7
10
|
static DataLoader;
|
|
8
11
|
/**
|
|
@@ -11,6 +14,8 @@ export class DataloaderUtils {
|
|
|
11
14
|
*/
|
|
12
15
|
static groupPrimaryKeysByEntityAndOpts(refsWithOpts) {
|
|
13
16
|
const map = new Map();
|
|
17
|
+
const options = new Map();
|
|
18
|
+
groupOptions.set(map, options);
|
|
14
19
|
for (const [ref, opts] of refsWithOpts) {
|
|
15
20
|
/* The key is a combination of the uniqueName (a unique table name based identifier) and a stringified version if the load options because we want
|
|
16
21
|
to map each combination of entities/options into separate find queries in order to return accurate results.
|
|
@@ -25,7 +30,8 @@ export class DataloaderUtils {
|
|
|
25
30
|
Thus such approach should probably be configurable, if not opt-in.
|
|
26
31
|
NOTE: meta + opts multi maps (https://github.com/martian17/ds-js) might be a more elegant way
|
|
27
32
|
to implement this but not necessarily faster. */
|
|
28
|
-
const key = `${helper(ref).__meta.uniqueName}|${
|
|
33
|
+
const key = `${helper(ref).__meta.uniqueName}|${stringifyCacheKey(opts ?? {})}`;
|
|
34
|
+
options.set(key, opts ?? {});
|
|
29
35
|
let primaryKeysSet = map.get(key);
|
|
30
36
|
if (primaryKeysSet == null) {
|
|
31
37
|
primaryKeysSet = new Set();
|
|
@@ -44,7 +50,7 @@ export class DataloaderUtils {
|
|
|
44
50
|
const groupedIdsMap = DataloaderUtils.groupPrimaryKeysByEntityAndOpts(refsWithOpts);
|
|
45
51
|
const promises = Array.from(groupedIdsMap).map(([key, idsSet]) => {
|
|
46
52
|
const uniqueName = key.substring(0, key.indexOf('|'));
|
|
47
|
-
const opts =
|
|
53
|
+
const opts = groupOptions.get(groupedIdsMap).get(key);
|
|
48
54
|
const meta = em.getMetadata().getByUniqueName(uniqueName);
|
|
49
55
|
return em.find(meta.class, Array.from(idsSet), opts);
|
|
50
56
|
});
|
|
@@ -64,6 +70,8 @@ export class DataloaderUtils {
|
|
|
64
70
|
*/
|
|
65
71
|
static groupInversedOrMappedKeysByEntityAndOpts(collsWithOpts) {
|
|
66
72
|
const entitiesMap = new Map();
|
|
73
|
+
const options = new Map();
|
|
74
|
+
groupOptions.set(entitiesMap, options);
|
|
67
75
|
for (const [col, opts] of collsWithOpts) {
|
|
68
76
|
/*
|
|
69
77
|
We first get the entity name of the Collection and together with its options (see groupPrimaryKeysByEntityAndOpts
|
|
@@ -72,7 +80,8 @@ export class DataloaderUtils {
|
|
|
72
80
|
The value is another Map which we can use to filter the find query to get results pertaining to the collections that have been dataloaded:
|
|
73
81
|
its keys are the props we are going to filter to and its values are the corresponding PKs.
|
|
74
82
|
*/
|
|
75
|
-
const key = `${col.property.targetMeta.uniqueName}|${
|
|
83
|
+
const key = `${col.property.targetMeta.uniqueName}|${stringifyCacheKey(opts ?? {})}`;
|
|
84
|
+
options.set(key, opts ?? {});
|
|
76
85
|
let filterMap = entitiesMap.get(key); // We are going to use this map to filter the entities pertaining to the collections that have been dataloaded.
|
|
77
86
|
if (filterMap == null) {
|
|
78
87
|
filterMap = new Map();
|
|
@@ -100,7 +109,9 @@ export class DataloaderUtils {
|
|
|
100
109
|
static entitiesAndOptsMapToQueries(entitiesAndOptsMap, em) {
|
|
101
110
|
return Array.from(entitiesAndOptsMap, async ([key, filterMap]) => {
|
|
102
111
|
const uniqueName = key.substring(0, key.indexOf('|'));
|
|
103
|
-
const opts =
|
|
112
|
+
const opts = groupOptions
|
|
113
|
+
.get(entitiesAndOptsMap)
|
|
114
|
+
?.get(key) ?? JSON.parse(key.substring(key.indexOf('|') + 1));
|
|
104
115
|
const meta = em.getMetadata().getByUniqueName(uniqueName);
|
|
105
116
|
const res = await em.find(meta.class, opts?.where != null && Object.keys(opts.where).length > 0
|
|
106
117
|
? {
|
|
@@ -168,7 +179,7 @@ export class DataloaderUtils {
|
|
|
168
179
|
// We need to filter the results in order to map each input collection
|
|
169
180
|
// to a subset of each query matching the collection items.
|
|
170
181
|
return collsWithOpts.map(([col, opts]) => {
|
|
171
|
-
const key = `${col.property.targetMeta.uniqueName}|${
|
|
182
|
+
const key = `${col.property.targetMeta.uniqueName}|${stringifyCacheKey(opts ?? {})}`;
|
|
172
183
|
const entities = resultsMap.get(key);
|
|
173
184
|
if (entities == null) {
|
|
174
185
|
// Should never happen
|
|
@@ -187,7 +198,7 @@ export class DataloaderUtils {
|
|
|
187
198
|
return async (collsWithOpts) => {
|
|
188
199
|
const groups = new Map();
|
|
189
200
|
for (const [col, opts] of collsWithOpts) {
|
|
190
|
-
const key = `${col.property.targetMeta.uniqueName}.${col.property.name}|${
|
|
201
|
+
const key = `${col.property.targetMeta.uniqueName}.${col.property.name}|${stringifyCacheKey(opts ?? {})}`;
|
|
191
202
|
const value = groups.get(key) ?? [];
|
|
192
203
|
value.push([col, opts ?? {}]);
|
|
193
204
|
groups.set(key, value);
|
|
@@ -252,7 +263,7 @@ export class DataloaderUtils {
|
|
|
252
263
|
// sharing a relation with the same property name (e.g. `Author.books` and `Publisher.books`)
|
|
253
264
|
// don't collide into a single batch that would use one owner's FK mapping for the other.
|
|
254
265
|
const ownerUniqueName = helper(col.owner).__meta.uniqueName;
|
|
255
|
-
const key = `${ownerUniqueName}.${prop.name}|${
|
|
266
|
+
const key = `${ownerUniqueName}.${prop.name}|${stringifyCacheKey(opts ?? {})}`;
|
|
256
267
|
keys.push(key);
|
|
257
268
|
let group = groups.get(key);
|
|
258
269
|
if (!group) {
|
|
@@ -260,7 +271,7 @@ export class DataloaderUtils {
|
|
|
260
271
|
groups.set(key, group);
|
|
261
272
|
}
|
|
262
273
|
const pk = helper(col.owner).getPrimaryKey();
|
|
263
|
-
group.ownerPKs.set(
|
|
274
|
+
group.ownerPKs.set(stringifyCacheKey(pk), pk);
|
|
264
275
|
}
|
|
265
276
|
const promises = Array.from(groups.entries()).map(async ([key, group]) => {
|
|
266
277
|
const allPKs = Array.from(group.ownerPKs.values());
|
package/utils/Utils.js
CHANGED
|
@@ -153,7 +153,7 @@ export function parseJsonSafe(value) {
|
|
|
153
153
|
/** Collection of general-purpose utility methods used throughout the ORM. */
|
|
154
154
|
export class Utils {
|
|
155
155
|
static PK_SEPARATOR = '~~~';
|
|
156
|
-
static #ORM_VERSION = '7.2.8-dev.
|
|
156
|
+
static #ORM_VERSION = '7.2.8-dev.3';
|
|
157
157
|
/** Default session variable name backing an RLS filter argument (`current_setting('mikro.<filter>.<arg>')`). */
|
|
158
158
|
static getRlsSettingName(filterName, argName) {
|
|
159
159
|
return `mikro.${filterName}.${argName}`;
|