@mikro-orm/core 7.0.0-dev.336 → 7.0.0-dev.338
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/connections/Connection.d.ts +1 -0
- package/connections/Connection.js +27 -11
- package/drivers/DatabaseDriver.js +2 -2
- package/enums.d.ts +2 -1
- package/enums.js +1 -0
- package/logging/DefaultLogger.js +3 -2
- package/logging/Logger.d.ts +2 -1
- package/package.json +1 -1
- package/typings.d.ts +11 -1
- package/utils/Configuration.d.ts +23 -0
- package/utils/Configuration.js +16 -0
- package/utils/QueryHelper.js +1 -1
- package/utils/Utils.js +1 -1
|
@@ -6,6 +6,7 @@ import type { Platform } from '../platforms/Platform.js';
|
|
|
6
6
|
import type { TransactionEventBroadcaster } from '../events/TransactionEventBroadcaster.js';
|
|
7
7
|
import type { IsolationLevel } from '../enums.js';
|
|
8
8
|
export declare abstract class Connection {
|
|
9
|
+
#private;
|
|
9
10
|
protected readonly config: Configuration;
|
|
10
11
|
protected readonly type: ConnectionType;
|
|
11
12
|
protected metadata: MetadataStorage;
|
|
@@ -7,6 +7,12 @@ export class Connection {
|
|
|
7
7
|
options;
|
|
8
8
|
logger;
|
|
9
9
|
connected = false;
|
|
10
|
+
get #connectionLabel() {
|
|
11
|
+
return {
|
|
12
|
+
type: this.type,
|
|
13
|
+
name: this.options.name || this.config.get('name') || this.options.host || this.options.dbName,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
10
16
|
constructor(config, options, type = 'write') {
|
|
11
17
|
this.config = config;
|
|
12
18
|
this.type = type;
|
|
@@ -120,28 +126,38 @@ export class Connection {
|
|
|
120
126
|
const now = Date.now();
|
|
121
127
|
try {
|
|
122
128
|
const res = await cb();
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
affected: Utils.isPlainObject(res) ? res.affectedRows : undefined,
|
|
128
|
-
});
|
|
129
|
+
const took = Date.now() - now;
|
|
130
|
+
const results = Array.isArray(res) ? res.length : undefined;
|
|
131
|
+
const affected = Utils.isPlainObject(res) ? res.affectedRows : undefined;
|
|
132
|
+
this.logQuery(query, { ...context, took, results, affected });
|
|
129
133
|
return res;
|
|
130
134
|
}
|
|
131
135
|
catch (e) {
|
|
132
|
-
|
|
136
|
+
const took = Date.now() - now;
|
|
137
|
+
this.logQuery(query, { ...context, took, level: 'error' });
|
|
133
138
|
throw e;
|
|
134
139
|
}
|
|
135
140
|
}
|
|
136
141
|
logQuery(query, context = {}) {
|
|
142
|
+
const connection = this.#connectionLabel;
|
|
137
143
|
this.logger.logQuery({
|
|
138
144
|
level: 'info',
|
|
139
|
-
connection
|
|
140
|
-
type: this.type,
|
|
141
|
-
name: this.options.name || this.config.get('name') || this.options.host,
|
|
142
|
-
},
|
|
145
|
+
connection,
|
|
143
146
|
...context,
|
|
144
147
|
query,
|
|
145
148
|
});
|
|
149
|
+
const threshold = this.config.get('slowQueryThreshold');
|
|
150
|
+
if (threshold != null && (context.took ?? 0) >= threshold) {
|
|
151
|
+
this.config.getSlowQueryLogger().logQuery({
|
|
152
|
+
...context,
|
|
153
|
+
// `enabled: true` bypasses the debug-mode check in isEnabled(),
|
|
154
|
+
// ensuring slow query logs are always emitted regardless of the `debug` setting.
|
|
155
|
+
enabled: true,
|
|
156
|
+
level: context.level ?? 'warning',
|
|
157
|
+
namespace: 'slow-query',
|
|
158
|
+
connection,
|
|
159
|
+
query,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
146
162
|
}
|
|
147
163
|
}
|
|
@@ -346,8 +346,8 @@ export class DatabaseDriver {
|
|
|
346
346
|
const props = prop.embeddedProps;
|
|
347
347
|
let unknownProp = false;
|
|
348
348
|
Object.keys(data[prop.name]).forEach(kk => {
|
|
349
|
-
// explicitly allow `$exists`, `$eq` and `$
|
|
350
|
-
const operator = Object.keys(data[prop.name]).some(f => Utils.isOperator(f) && !['$exists', '$ne', '$eq'].includes(f));
|
|
349
|
+
// explicitly allow `$exists`, `$eq`, `$ne` and `$elemMatch` operators here as they can't be misused this way
|
|
350
|
+
const operator = Object.keys(data[prop.name]).some(f => Utils.isOperator(f) && !['$exists', '$ne', '$eq', '$elemMatch'].includes(f));
|
|
351
351
|
if (operator) {
|
|
352
352
|
throw ValidationError.cannotUseOperatorsInsideEmbeddables(meta.class, prop.name, data);
|
|
353
353
|
}
|
package/enums.d.ts
CHANGED
|
@@ -45,7 +45,8 @@ export declare enum QueryOperator {
|
|
|
45
45
|
$size = "size",// collection operators, sql only
|
|
46
46
|
$hasKey = "?",// postgres only, json
|
|
47
47
|
$hasKeys = "?&",// postgres only, json
|
|
48
|
-
$hasSomeKeys = "?|"
|
|
48
|
+
$hasSomeKeys = "?|",// postgres only, json
|
|
49
|
+
$elemMatch = "elemMatch"
|
|
49
50
|
}
|
|
50
51
|
export declare const ARRAY_OPERATORS: string[];
|
|
51
52
|
export declare const JSON_KEY_OPERATORS: string[];
|
package/enums.js
CHANGED
|
@@ -48,6 +48,7 @@ export var QueryOperator;
|
|
|
48
48
|
QueryOperator["$hasKey"] = "?";
|
|
49
49
|
QueryOperator["$hasKeys"] = "?&";
|
|
50
50
|
QueryOperator["$hasSomeKeys"] = "?|";
|
|
51
|
+
QueryOperator["$elemMatch"] = "elemMatch";
|
|
51
52
|
})(QueryOperator || (QueryOperator = {}));
|
|
52
53
|
export const ARRAY_OPERATORS = ['$eq', '$gt', '$gte', '$lt', '$lte', '$ne', '$overlap', '$contains', '$contained'];
|
|
53
54
|
export const JSON_KEY_OPERATORS = ['$hasKey', '$hasKeys', '$hasSomeKeys'];
|
package/logging/DefaultLogger.js
CHANGED
|
@@ -68,7 +68,8 @@ export class DefaultLogger {
|
|
|
68
68
|
* @inheritDoc
|
|
69
69
|
*/
|
|
70
70
|
logQuery(context) {
|
|
71
|
-
|
|
71
|
+
const namespace = context.namespace ?? 'query';
|
|
72
|
+
if (!this.isEnabled(namespace, context)) {
|
|
72
73
|
return;
|
|
73
74
|
}
|
|
74
75
|
/* v8 ignore next */
|
|
@@ -86,7 +87,7 @@ export class DefaultLogger {
|
|
|
86
87
|
if (this.usesReplicas && context.connection) {
|
|
87
88
|
msg += colors.cyan(` (via ${context.connection.type} connection '${context.connection.name}')`);
|
|
88
89
|
}
|
|
89
|
-
return this.log(
|
|
90
|
+
return this.log(namespace, msg, context);
|
|
90
91
|
}
|
|
91
92
|
static create(options) {
|
|
92
93
|
return new DefaultLogger(options);
|
package/logging/Logger.d.ts
CHANGED
|
@@ -22,10 +22,11 @@ export interface Logger {
|
|
|
22
22
|
setDebugMode(debugMode: boolean | LoggerNamespace[]): void;
|
|
23
23
|
isEnabled(namespace: LoggerNamespace, context?: LogContext): boolean;
|
|
24
24
|
}
|
|
25
|
-
export type LoggerNamespace = 'query' | 'query-params' | 'schema' | 'discovery' | 'info' | 'deprecated';
|
|
25
|
+
export type LoggerNamespace = 'query' | 'query-params' | 'schema' | 'discovery' | 'info' | 'deprecated' | 'slow-query';
|
|
26
26
|
export interface LogContext extends Dictionary {
|
|
27
27
|
query?: string;
|
|
28
28
|
label?: string;
|
|
29
|
+
namespace?: LoggerNamespace;
|
|
29
30
|
params?: readonly unknown[];
|
|
30
31
|
took?: number;
|
|
31
32
|
results?: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/core",
|
|
3
|
-
"version": "7.0.0-dev.
|
|
3
|
+
"version": "7.0.0-dev.338",
|
|
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/typings.d.ts
CHANGED
|
@@ -234,8 +234,18 @@ export type FilterItemValue<T> = T | ExpandScalar<T> | Primary<T> | Raw;
|
|
|
234
234
|
export type FilterValue<T> = OperatorMap<FilterItemValue<T>> | FilterItemValue<T> | FilterItemValue<T>[] | null;
|
|
235
235
|
type FilterObjectProp<T, K extends PropertyKey> = K extends keyof MergeUnion<T> ? MergeUnion<T>[K] : K extends keyof T ? T[K] : never;
|
|
236
236
|
type ExpandQueryMerged<T> = [T] extends [object] ? [T] extends [Scalar] ? never : FilterQuery<MergeUnion<T>> : FilterValue<T>;
|
|
237
|
+
type ElemMatchCondition<T extends Record<string, any>> = {
|
|
238
|
+
[K in keyof T]?: T[K] | OperatorMap<T[K]>;
|
|
239
|
+
} & {
|
|
240
|
+
$or?: ElemMatchCondition<T>[];
|
|
241
|
+
$and?: ElemMatchCondition<T>[];
|
|
242
|
+
$not?: ElemMatchCondition<T>;
|
|
243
|
+
};
|
|
244
|
+
type ElemMatchFilter<T> = T extends readonly (infer E)[] ? E extends Record<string, any> ? {
|
|
245
|
+
$elemMatch: ElemMatchCondition<E>;
|
|
246
|
+
} : never : never;
|
|
237
247
|
export type FilterObject<T> = {
|
|
238
|
-
-readonly [K in EntityKey<T>]?: ExpandQuery<ExpandProperty<FilterObjectProp<T, K>>> | ExpandQueryMerged<ExpandProperty<FilterObjectProp<T, K>>> | FilterValue<ExpandProperty<FilterObjectProp<T, K>>> | null;
|
|
248
|
+
-readonly [K in EntityKey<T>]?: ExpandQuery<ExpandProperty<FilterObjectProp<T, K>>> | ExpandQueryMerged<ExpandProperty<FilterObjectProp<T, K>>> | FilterValue<ExpandProperty<FilterObjectProp<T, K>>> | ElemMatchFilter<FilterObjectProp<T, K>> | null;
|
|
239
249
|
};
|
|
240
250
|
export type ExpandQuery<T> = T extends object ? (T extends Scalar ? never : FilterQuery<T>) : FilterValue<T>;
|
|
241
251
|
export type EntityProps<T> = {
|
package/utils/Configuration.d.ts
CHANGED
|
@@ -36,6 +36,11 @@ export declare class Configuration<D extends IDatabaseDriver = IDatabaseDriver,
|
|
|
36
36
|
* Gets Logger instance.
|
|
37
37
|
*/
|
|
38
38
|
getLogger(): Logger;
|
|
39
|
+
/**
|
|
40
|
+
* Gets the logger instance for slow queries.
|
|
41
|
+
* Falls back to the main logger if no custom slow query logger factory is configured.
|
|
42
|
+
*/
|
|
43
|
+
getSlowQueryLogger(): Logger;
|
|
39
44
|
getDataloaderType(): DataloaderType;
|
|
40
45
|
getSchema(skipDefaultSchema?: boolean): string | undefined;
|
|
41
46
|
/**
|
|
@@ -671,6 +676,24 @@ export interface Options<Driver extends IDatabaseDriver = IDatabaseDriver, EM ex
|
|
|
671
676
|
* @default DefaultLogger.create
|
|
672
677
|
*/
|
|
673
678
|
loggerFactory?: (options: LoggerOptions) => Logger;
|
|
679
|
+
/**
|
|
680
|
+
* Threshold in milliseconds for logging slow queries.
|
|
681
|
+
* Queries taking at least this long will be logged via the 'slow-query' namespace at warning level.
|
|
682
|
+
* Slow query logs are always emitted when the threshold is met, regardless of the `debug` setting.
|
|
683
|
+
* Set to `0` to log every query as slow.
|
|
684
|
+
* @default undefined (slow query logging disabled)
|
|
685
|
+
*/
|
|
686
|
+
slowQueryThreshold?: number;
|
|
687
|
+
/**
|
|
688
|
+
* Factory function to create a custom logger instance for slow queries.
|
|
689
|
+
* Has the same shape as `loggerFactory`. When not provided, the main logger instance is used.
|
|
690
|
+
*
|
|
691
|
+
* Note: slow query log entries are emitted with `context.enabled = true` to bypass the
|
|
692
|
+
* debug-mode check. Custom logger implementations must respect `context.enabled` in their
|
|
693
|
+
* `isEnabled()` method (as `DefaultLogger` does) to ensure slow query logs are always emitted.
|
|
694
|
+
* @default undefined (falls back to main logger)
|
|
695
|
+
*/
|
|
696
|
+
slowQueryLoggerFactory?: (options: LoggerOptions) => Logger;
|
|
674
697
|
/**
|
|
675
698
|
* Custom error handler for `em.findOneOrFail()` when no entity is found.
|
|
676
699
|
* @param entityName - Name of the entity being queried
|
package/utils/Configuration.js
CHANGED
|
@@ -130,6 +130,7 @@ const DEFAULTS = {
|
|
|
130
130
|
export class Configuration {
|
|
131
131
|
#options;
|
|
132
132
|
#logger;
|
|
133
|
+
#slowQueryLogger;
|
|
133
134
|
#driver;
|
|
134
135
|
#platform;
|
|
135
136
|
#cache = new Map();
|
|
@@ -195,6 +196,20 @@ export class Configuration {
|
|
|
195
196
|
getLogger() {
|
|
196
197
|
return this.#logger;
|
|
197
198
|
}
|
|
199
|
+
/**
|
|
200
|
+
* Gets the logger instance for slow queries.
|
|
201
|
+
* Falls back to the main logger if no custom slow query logger factory is configured.
|
|
202
|
+
*/
|
|
203
|
+
getSlowQueryLogger() {
|
|
204
|
+
this.#slowQueryLogger ??=
|
|
205
|
+
this.#options.slowQueryLoggerFactory?.({
|
|
206
|
+
debugMode: this.#options.debug,
|
|
207
|
+
writer: this.#options.logger,
|
|
208
|
+
highlighter: this.#options.highlighter,
|
|
209
|
+
usesReplicas: (this.#options.replicas?.length ?? 0) > 0,
|
|
210
|
+
}) ?? this.#logger;
|
|
211
|
+
return this.#slowQueryLogger;
|
|
212
|
+
}
|
|
198
213
|
getDataloaderType() {
|
|
199
214
|
if (typeof this.#options.dataloader === 'boolean') {
|
|
200
215
|
return this.#options.dataloader ? DataloaderType.ALL : DataloaderType.NONE;
|
|
@@ -337,6 +352,7 @@ export class Configuration {
|
|
|
337
352
|
sync() {
|
|
338
353
|
setEnv('MIKRO_ORM_COLORS', this.#options.colors);
|
|
339
354
|
this.#logger.setDebugMode(this.#options.debug);
|
|
355
|
+
this.#slowQueryLogger = undefined;
|
|
340
356
|
}
|
|
341
357
|
validateOptions() {
|
|
342
358
|
/* v8 ignore next */
|
package/utils/QueryHelper.js
CHANGED
|
@@ -186,7 +186,7 @@ export class QueryHelper {
|
|
|
186
186
|
value = QueryHelper.processCustomType(prop, value, platform, undefined, true);
|
|
187
187
|
}
|
|
188
188
|
// oxfmt-ignore
|
|
189
|
-
const isJsonProperty = prop?.customType instanceof JsonType && !isRaw(value) && (Utils.isPlainObject(value) ? Object.keys(value)[0]
|
|
189
|
+
const isJsonProperty = prop?.customType instanceof JsonType && !isRaw(value) && (Utils.isPlainObject(value) ? !['$eq', '$elemMatch'].includes(Object.keys(value)[0]) : !Array.isArray(value));
|
|
190
190
|
if (isJsonProperty && prop?.kind !== ReferenceKind.EMBEDDED) {
|
|
191
191
|
return this.processJsonCondition(o, value, [prop.fieldNames[0]], platform, aliased);
|
|
192
192
|
}
|
package/utils/Utils.js
CHANGED
|
@@ -123,7 +123,7 @@ export function parseJsonSafe(value) {
|
|
|
123
123
|
}
|
|
124
124
|
export class Utils {
|
|
125
125
|
static PK_SEPARATOR = '~~~';
|
|
126
|
-
static #ORM_VERSION = '7.0.0-dev.
|
|
126
|
+
static #ORM_VERSION = '7.0.0-dev.338';
|
|
127
127
|
/**
|
|
128
128
|
* Checks if the argument is instance of `Object`. Returns false for arrays.
|
|
129
129
|
*/
|