@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/utils/QueryHelper.js
CHANGED
|
@@ -227,6 +227,13 @@ export class QueryHelper {
|
|
|
227
227
|
if (prop?.customType && convertCustomTypes && !isRaw(value)) {
|
|
228
228
|
value = QueryHelper.processCustomType(prop, value, platform, undefined, true);
|
|
229
229
|
}
|
|
230
|
+
else if (!prop &&
|
|
231
|
+
meta?.compositePK &&
|
|
232
|
+
convertCustomTypes &&
|
|
233
|
+
Array.isArray(value) &&
|
|
234
|
+
key === Utils.getPrimaryKeyHash(meta.primaryKeys)) {
|
|
235
|
+
value = QueryHelper.processCompositeCustomTypes(value, meta, platform);
|
|
236
|
+
}
|
|
230
237
|
// oxfmt-ignore
|
|
231
238
|
const isJsonProperty = prop?.customType instanceof JsonType && !isRaw(value) && (Utils.isPlainObject(value) ? !['$eq', '$elemMatch'].includes(Object.keys(value)[0]) : !Array.isArray(value));
|
|
232
239
|
if (isJsonProperty && prop?.kind !== ReferenceKind.EMBEDDED) {
|
|
@@ -321,6 +328,24 @@ export class QueryHelper {
|
|
|
321
328
|
}
|
|
322
329
|
return prop.customType.convertToDatabaseValue(cond, platform, { fromQuery, key, mode: 'query' });
|
|
323
330
|
}
|
|
331
|
+
/**
|
|
332
|
+
* Composite PK conditions are keyed by a hash of all the PK names, which `findProperty` cannot
|
|
333
|
+
* resolve, so the custom types have to be applied positionally instead.
|
|
334
|
+
*/
|
|
335
|
+
static processCompositeCustomTypes(value, meta, platform) {
|
|
336
|
+
const props = meta.primaryKeys.map(pk => meta.properties[pk]);
|
|
337
|
+
if (!props.some(prop => prop.customType)) {
|
|
338
|
+
return value;
|
|
339
|
+
}
|
|
340
|
+
// the tuple can be longer than the PK when the user passes a malformed condition
|
|
341
|
+
const convert = (tuple) => tuple.map((val, idx) => {
|
|
342
|
+
if (!props[idx]?.customType) {
|
|
343
|
+
return val;
|
|
344
|
+
}
|
|
345
|
+
return QueryHelper.processCustomType(props[idx], val, platform, undefined, true);
|
|
346
|
+
});
|
|
347
|
+
return value.every(val => Array.isArray(val)) ? value.map(val => convert(val)) : convert(value);
|
|
348
|
+
}
|
|
324
349
|
static isSupportedOperator(key) {
|
|
325
350
|
return !!QueryHelper.SUPPORTED_OPERATORS.find(op => key === op);
|
|
326
351
|
}
|
|
@@ -61,6 +61,12 @@ export declare const ALIAS_REPLACEMENT_RE = "\\[::alias::\\]";
|
|
|
61
61
|
* await em.find(User, { [raw(alias => `lower(${alias}.name)`)]: name.toLowerCase() });
|
|
62
62
|
* ```
|
|
63
63
|
*
|
|
64
|
+
* Named parameters are supported via an object of parameters, use `:name` for values and `:name:` for identifiers:
|
|
65
|
+
*
|
|
66
|
+
* ```ts
|
|
67
|
+
* raw('select :col: from geo where city = :city or region = :city', { col: 'city', city: 'Brno' });
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
64
70
|
* You can also use the `sql` tagged template function, which works the same, but supports only the simple string signature:
|
|
65
71
|
*
|
|
66
72
|
* ```ts
|
|
@@ -146,6 +146,12 @@ export const ALIAS_REPLACEMENT_RE = '\\[::alias::\\]';
|
|
|
146
146
|
* await em.find(User, { [raw(alias => `lower(${alias}.name)`)]: name.toLowerCase() });
|
|
147
147
|
* ```
|
|
148
148
|
*
|
|
149
|
+
* Named parameters are supported via an object of parameters, use `:name` for values and `:name:` for identifiers:
|
|
150
|
+
*
|
|
151
|
+
* ```ts
|
|
152
|
+
* raw('select :col: from geo where city = :city or region = :city', { col: 'city', city: 'Brno' });
|
|
153
|
+
* ```
|
|
154
|
+
*
|
|
149
155
|
* You can also use the `sql` tagged template function, which works the same, but supports only the simple string signature:
|
|
150
156
|
*
|
|
151
157
|
* ```ts
|
|
@@ -191,13 +197,16 @@ export function raw(sql, params) {
|
|
|
191
197
|
return Utils.getPrimaryKeyHash(sql);
|
|
192
198
|
}
|
|
193
199
|
if (typeof params === 'object' && !Array.isArray(params)) {
|
|
194
|
-
const
|
|
200
|
+
const dict = params;
|
|
195
201
|
const objectParams = [];
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
202
|
+
// single left-to-right scan keeps values in SQL-placeholder order while `::` casts and unknown tokens stay untouched
|
|
203
|
+
sql = sql.replace(/(?<!:):([$\w]+)(:(?!:))?/g, (match, key, identifier) => {
|
|
204
|
+
if (!Object.hasOwn(dict, key)) {
|
|
205
|
+
return match;
|
|
206
|
+
}
|
|
207
|
+
objectParams.push(dict[key]);
|
|
208
|
+
return identifier ? '??' : '?';
|
|
209
|
+
});
|
|
201
210
|
return new RawQueryFragment(sql, objectParams);
|
|
202
211
|
}
|
|
203
212
|
return new RawQueryFragment(sql, params);
|
|
@@ -17,13 +17,13 @@ export declare class RequestContext {
|
|
|
17
17
|
* If the handler is async, the return value needs to be awaited.
|
|
18
18
|
* Uses `AsyncLocalStorage.run()`, suitable for regular express style middlewares with a `next` callback.
|
|
19
19
|
*/
|
|
20
|
-
static create<T>(em: EntityManager | EntityManager[], next: (...args: any[]) => T, options?: CreateContextOptions): T;
|
|
20
|
+
static create<T>(em: EntityManager | EntityManager[], next: (...args: any[]) => T, options?: ((name: string) => CreateContextOptions) | CreateContextOptions): T;
|
|
21
21
|
/**
|
|
22
22
|
* Creates new RequestContext instance and runs the code inside its domain.
|
|
23
23
|
* If the handler is async, the return value needs to be awaited.
|
|
24
24
|
* Uses `AsyncLocalStorage.enterWith()`, suitable for elysia style middlewares without a `next` callback.
|
|
25
25
|
*/
|
|
26
|
-
static enter(em: EntityManager | EntityManager[], options?: CreateContextOptions): void;
|
|
26
|
+
static enter(em: EntityManager | EntityManager[], options?: ((name: string) => CreateContextOptions) | CreateContextOptions): void;
|
|
27
27
|
/**
|
|
28
28
|
* Returns current RequestContext (if available).
|
|
29
29
|
*/
|
package/utils/RequestContext.js
CHANGED
|
@@ -50,10 +50,19 @@ export class RequestContext {
|
|
|
50
50
|
static createContext(em, options = {}) {
|
|
51
51
|
const forks = new Map();
|
|
52
52
|
if (Array.isArray(em)) {
|
|
53
|
-
|
|
53
|
+
if (typeof options === 'function') {
|
|
54
|
+
for (const emInstance of em) {
|
|
55
|
+
forks.set(emInstance.name, emInstance.fork({ useContext: true, ...options(emInstance.name) }));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
for (const emInstance of em) {
|
|
60
|
+
forks.set(emInstance.name, emInstance.fork({ useContext: true, ...options }));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
54
63
|
}
|
|
55
64
|
else {
|
|
56
|
-
forks.set(em.name, em.fork({ useContext: true, ...options }));
|
|
65
|
+
forks.set(em.name, em.fork({ useContext: true, ...(typeof options === 'function' ? options(em.name) : options) }));
|
|
57
66
|
}
|
|
58
67
|
return new RequestContext(forks);
|
|
59
68
|
}
|
|
@@ -50,6 +50,12 @@ export declare class TransactionManager {
|
|
|
50
50
|
* Merges entities from fork to parent EntityManager.
|
|
51
51
|
*/
|
|
52
52
|
private mergeEntitiesToParent;
|
|
53
|
+
/**
|
|
54
|
+
* Returns the property names a given property is tracked and snapshotted under, paired with their values.
|
|
55
|
+
* Inlined embeddables are hydrated as a single object, so only their leaves tell whether it is complete.
|
|
56
|
+
*/
|
|
57
|
+
private getTrackedValues;
|
|
58
|
+
private restore;
|
|
53
59
|
/**
|
|
54
60
|
* Registers a deletion handler to unset entity identities after flush.
|
|
55
61
|
*/
|
|
@@ -162,26 +162,59 @@ export class TransactionManager {
|
|
|
162
162
|
for (const entity of fork.getUnitOfWork(false).getIdentityMap()) {
|
|
163
163
|
const wrapped = helper(entity);
|
|
164
164
|
const meta = wrapped.__meta;
|
|
165
|
-
const parentEntity = parentUoW.
|
|
165
|
+
const parentEntity = parentUoW.getIdentityMap().getByEntity(entity);
|
|
166
166
|
if (parentEntity && parentEntity !== entity) {
|
|
167
167
|
const parentWrapped = helper(parentEntity);
|
|
168
168
|
// Don't overwrite a fully-loaded entity with an uninitialized reference
|
|
169
169
|
if (!wrapped.__initialized && parentWrapped.__initialized) {
|
|
170
170
|
continue;
|
|
171
171
|
}
|
|
172
|
-
|
|
173
|
-
|
|
172
|
+
const parentData = parentWrapped.__data;
|
|
173
|
+
const parentSnapshot = parentWrapped.__originalEntityData;
|
|
174
|
+
parentWrapped.__data = { ...wrapped.__data };
|
|
175
|
+
const originalEntityData = { ...wrapped.__originalEntityData };
|
|
174
176
|
for (const prop of meta.hydrateProps) {
|
|
177
|
+
const tracked = this.getTrackedValues(prop, entity[prop.name]);
|
|
178
|
+
// the fork entity can be partially loaded, and propagating a property it does not know about
|
|
179
|
+
// would clobber the parent state, so we restore both its value and its snapshot entries
|
|
180
|
+
if (!tracked.every(([key, value]) => value !== undefined || wrapped.__loadedProperties.has(key))) {
|
|
181
|
+
this.restore(parentWrapped.__data, parentData, prop.name);
|
|
182
|
+
tracked.forEach(([key]) => this.restore(originalEntityData, parentSnapshot, key));
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
175
185
|
if (prop.kind === ReferenceKind.SCALAR) {
|
|
176
186
|
parentEntity[prop.name] = entity[prop.name];
|
|
177
187
|
}
|
|
178
188
|
}
|
|
189
|
+
if (wrapped.__originalEntityData) {
|
|
190
|
+
parentWrapped.__originalEntityData = originalEntityData;
|
|
191
|
+
}
|
|
179
192
|
}
|
|
180
193
|
else {
|
|
181
194
|
parentUoW.merge(entity, new Set([entity]));
|
|
182
195
|
}
|
|
183
196
|
}
|
|
184
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* Returns the property names a given property is tracked and snapshotted under, paired with their values.
|
|
200
|
+
* Inlined embeddables are hydrated as a single object, so only their leaves tell whether it is complete.
|
|
201
|
+
*/
|
|
202
|
+
getTrackedValues(prop, value) {
|
|
203
|
+
if (prop.kind === ReferenceKind.EMBEDDED && !prop.object) {
|
|
204
|
+
return Object.values(prop.embeddedProps).flatMap(child => {
|
|
205
|
+
return this.getTrackedValues(child, value?.[child.embedded[1]]);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
return [[prop.name, value]];
|
|
209
|
+
}
|
|
210
|
+
restore(target, source, key) {
|
|
211
|
+
if (source && key in source) {
|
|
212
|
+
target[key] = source[key];
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
delete target[key];
|
|
216
|
+
}
|
|
217
|
+
}
|
|
185
218
|
/**
|
|
186
219
|
* Registers a deletion handler to unset entity identities after flush.
|
|
187
220
|
*/
|
package/utils/Utils.d.ts
CHANGED
|
@@ -3,6 +3,18 @@ import type { Platform } from '../platforms/Platform.js';
|
|
|
3
3
|
import { ScalarReference } from '../entity/Reference.js';
|
|
4
4
|
import { Collection } from '../entity/Collection.js';
|
|
5
5
|
import { type RawQueryFragmentSymbol } from './RawQueryFragment.js';
|
|
6
|
+
/**
|
|
7
|
+
* List of property names that could lead to prototype pollution vulnerabilities.
|
|
8
|
+
* These names should never be used as entity property names because they could
|
|
9
|
+
* allow malicious code to modify object prototypes when property values are assigned.
|
|
10
|
+
*
|
|
11
|
+
* - `__proto__`: Could modify the prototype chain
|
|
12
|
+
* - `constructor`: Could modify the constructor property
|
|
13
|
+
* - `prototype`: Could modify the prototype object
|
|
14
|
+
*
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export declare const DANGEROUS_PROPERTY_NAMES: readonly string[];
|
|
6
18
|
/** Deeply compares two objects for equality, handling dates, regexes, and raw fragments. */
|
|
7
19
|
export declare function compareObjects(a: any, b: any): boolean;
|
|
8
20
|
/** Compares two arrays element-by-element for deep equality. */
|
package/utils/Utils.js
CHANGED
|
@@ -5,6 +5,18 @@ import { Reference, ScalarReference } from '../entity/Reference.js';
|
|
|
5
5
|
import { Collection } from '../entity/Collection.js';
|
|
6
6
|
import { EntityHelper } from '../entity/EntityHelper.js';
|
|
7
7
|
import { Raw, isRaw } from './RawQueryFragment.js';
|
|
8
|
+
/**
|
|
9
|
+
* List of property names that could lead to prototype pollution vulnerabilities.
|
|
10
|
+
* These names should never be used as entity property names because they could
|
|
11
|
+
* allow malicious code to modify object prototypes when property values are assigned.
|
|
12
|
+
*
|
|
13
|
+
* - `__proto__`: Could modify the prototype chain
|
|
14
|
+
* - `constructor`: Could modify the constructor property
|
|
15
|
+
* - `prototype`: Could modify the prototype object
|
|
16
|
+
*
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
export const DANGEROUS_PROPERTY_NAMES = ['__proto__', 'constructor', 'prototype'];
|
|
8
20
|
function compareConstructors(a, b) {
|
|
9
21
|
if (a.constructor === b.constructor) {
|
|
10
22
|
return true;
|
|
@@ -141,7 +153,7 @@ export function parseJsonSafe(value) {
|
|
|
141
153
|
/** Collection of general-purpose utility methods used throughout the ORM. */
|
|
142
154
|
export class Utils {
|
|
143
155
|
static PK_SEPARATOR = '~~~';
|
|
144
|
-
static #ORM_VERSION = '7.2.0-dev.
|
|
156
|
+
static #ORM_VERSION = '7.2.0-dev.10';
|
|
145
157
|
/**
|
|
146
158
|
* Checks if the argument is instance of `Object`. Returns false for arrays.
|
|
147
159
|
*/
|
|
@@ -232,7 +244,7 @@ export class Utils {
|
|
|
232
244
|
const source = sources.shift();
|
|
233
245
|
if (Utils.isObject(target) && Utils.isPlainObject(source)) {
|
|
234
246
|
for (const [key, value] of Object.entries(source)) {
|
|
235
|
-
if (
|
|
247
|
+
if (DANGEROUS_PROPERTY_NAMES.includes(key)) {
|
|
236
248
|
continue;
|
|
237
249
|
}
|
|
238
250
|
if (ignoreUndefined && typeof value === 'undefined') {
|
|
@@ -390,7 +402,8 @@ export class Utils {
|
|
|
390
402
|
static getCompositeKeyHash(data, meta, convertCustomTypes = false, platform, flat = false) {
|
|
391
403
|
let pks = this.getCompositeKeyValue(data, meta, convertCustomTypes, platform);
|
|
392
404
|
if (flat) {
|
|
393
|
-
|
|
405
|
+
// deep flatten, nested composite PKs produce nested arrays that would be comma-joined by the hash
|
|
406
|
+
pks = Utils.flatten(pks, true);
|
|
394
407
|
}
|
|
395
408
|
return Utils.getPrimaryKeyHash(pks);
|
|
396
409
|
}
|
|
@@ -450,7 +463,9 @@ export class Utils {
|
|
|
450
463
|
}
|
|
451
464
|
static getPrimaryKeyCond(entity, primaryKeys) {
|
|
452
465
|
const cond = primaryKeys.reduce((o, pk) => {
|
|
453
|
-
|
|
466
|
+
const value = entity[pk];
|
|
467
|
+
// FKs pointing to a composite PK are arrays, which `extractPK` rejects
|
|
468
|
+
o[pk] = Utils.isPrimaryKey(value, true) ? value : Utils.extractPK(value);
|
|
454
469
|
return o;
|
|
455
470
|
}, {});
|
|
456
471
|
if (Object.values(cond).some(v => v === null)) {
|
package/utils/clone.js
CHANGED
|
@@ -112,6 +112,12 @@ export function clone(parent, respectCustomCloneMethod = true) {
|
|
|
112
112
|
});
|
|
113
113
|
}
|
|
114
114
|
for (const i in parent) {
|
|
115
|
+
// an own `__proto__` key (as produced by `JSON.parse`) has no own counterpart on
|
|
116
|
+
// `child` to shadow the inherited accessor, so assigning it would replace the
|
|
117
|
+
// clone's prototype instead of copying the value
|
|
118
|
+
if (i === '__proto__') {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
115
121
|
let attrs;
|
|
116
122
|
if (proto) {
|
|
117
123
|
attrs = getPropertyDescriptor(proto, i);
|
package/utils/env-vars.js
CHANGED