@mikro-orm/mongodb 7.0.5-dev.9 → 7.0.5

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.
@@ -1,428 +1,433 @@
1
- import { MongoClient, ObjectId, } from 'mongodb';
2
- import { Connection, EventType, inspect, QueryOrder, Utils, ValidationError, } from '@mikro-orm/core';
1
+ import { MongoClient, ObjectId } from 'mongodb';
2
+ import { Connection, EventType, inspect, QueryOrder, Utils, ValidationError } from '@mikro-orm/core';
3
3
  /** MongoDB database connection using the official `mongodb` driver. */
4
4
  export class MongoConnection extends Connection {
5
- #client;
6
- #db;
7
- constructor(config, options, type = 'write') {
8
- super(config, options, type);
9
- // @ts-ignore
10
- ObjectId.prototype[Symbol.for('nodejs.util.inspect.custom')] = function () {
11
- return `ObjectId('${this.toHexString()}')`;
12
- };
13
- // @ts-ignore
14
- Date.prototype[Symbol.for('nodejs.util.inspect.custom')] = function () {
15
- return `ISODate('${this.toISOString()}')`;
16
- };
17
- }
18
- async connect(options) {
19
- this.getClient();
20
- this.connected = true;
21
- if (options?.skipOnConnect !== true) {
22
- await this.onConnect();
23
- }
24
- }
25
- createClient() {
26
- let driverOptions = this.options.driverOptions ?? this.config.get('driverOptions');
27
- if (typeof driverOptions === 'function') {
28
- driverOptions = driverOptions();
29
- }
30
- if (driverOptions instanceof MongoClient) {
31
- this.logger.log('info', 'Reusing MongoClient provided via `driverOptions`');
32
- this.#client = driverOptions;
33
- }
34
- else {
35
- this.#client = new MongoClient(this.config.get('clientUrl'), this.mapOptions(driverOptions));
36
- this.#client.appendMetadata({
37
- name: 'MikroORM',
38
- version: Utils.getORMVersion(),
39
- });
40
- const onCreateConnection = this.options.onCreateConnection ?? this.config.get('onCreateConnection');
41
- /* v8 ignore next */
42
- this.#client.on('connectionCreated', () => {
43
- void onCreateConnection?.(this.#client);
44
- });
45
- }
46
- this.#db = this.#client.db(this.config.get('dbName'));
47
- }
48
- async close(force) {
49
- await this.#client?.close(force);
50
- this.connected = false;
51
- this.#client = undefined;
52
- }
53
- async isConnected() {
54
- try {
55
- const res = await this.#db?.command({ ping: 1 });
56
- return (this.connected = !!res?.ok);
57
- }
58
- catch (error) {
59
- return (this.connected = false);
60
- }
61
- }
62
- async checkConnection() {
63
- try {
64
- const res = await this.#db?.command({ ping: 1 });
65
- return res?.ok
66
- ? { ok: true }
67
- : { ok: false, reason: 'Ping reply does not feature "ok" property, or it evaluates to "false"' };
68
- }
69
- catch (error) {
70
- return { ok: false, reason: error.message, error };
71
- }
72
- }
73
- getClient() {
74
- if (!this.#client) {
75
- this.createClient();
76
- }
77
- return this.#client;
78
- }
79
- getCollection(name) {
80
- return this.getDb().collection(this.getCollectionName(name));
81
- }
82
- async createCollection(name) {
83
- return this.getDb().createCollection(this.getCollectionName(name));
84
- }
85
- async listCollections() {
86
- const collections = await this.getDb().listCollections({}, { nameOnly: true }).toArray();
87
- return collections.map(c => c.name);
88
- }
89
- async dropCollection(name) {
90
- return this.getDb().dropCollection(this.getCollectionName(name));
91
- }
92
- mapOptions(overrides) {
93
- const ret = {};
94
- const pool = this.config.get('pool');
95
- const username = this.config.get('user');
96
- const password = this.config.get('password');
97
- if (this.config.get('host')) {
98
- throw new ValidationError('Mongo driver does not support `host` options, use `clientUrl` instead!');
99
- }
100
- if (username && password) {
101
- ret.auth = { username, password };
102
- }
103
- ret.minPoolSize = pool.min;
104
- ret.maxPoolSize = pool.max;
105
- ret.waitQueueTimeoutMS = pool.idleTimeoutMillis;
106
- return Utils.mergeConfig(ret, overrides);
107
- }
108
- getDb() {
109
- this.#db ??= this.getClient().db(this.config.get('dbName'));
110
- return this.#db;
111
- }
112
- async execute(query) {
113
- throw new Error(`${this.constructor.name} does not support generic execute method`);
114
- }
115
- async find(entityName, where, opts = {}) {
116
- const { cursor, query } = await this._find(entityName, where, opts);
117
- const now = Date.now();
118
- const res = await cursor.toArray();
119
- this.logQuery(`${query}.toArray();`, { took: Date.now() - now, results: res.length, ...opts.loggerContext });
120
- return res;
121
- }
122
- async *stream(entityName, where, opts = {}) {
123
- const { cursor, query } = await this._find(entityName, where, opts);
124
- this.logQuery(`${query}.toArray();`, opts.loggerContext);
125
- yield* cursor;
126
- }
127
- async _find(entityName, where, opts = {}) {
128
- await this.ensureConnection();
129
- const collection = this.getCollectionName(entityName);
130
- const options = opts.ctx ? { session: opts.ctx } : {};
131
- if (opts.fields) {
132
- options.projection = opts.fields.reduce((o, k) => Object.assign(o, { [k]: 1 }), {});
133
- }
134
- if (opts.collation) {
135
- options.collation = opts.collation;
136
- }
137
- if (opts.indexHint != null) {
138
- options.hint = opts.indexHint;
139
- }
140
- if (opts.maxTimeMS != null) {
141
- options.maxTimeMS = opts.maxTimeMS;
142
- }
143
- if (opts.allowDiskUse != null) {
144
- options.allowDiskUse = opts.allowDiskUse;
145
- }
146
- const resultSet = this.getCollection(entityName).find(where, options);
147
- let query = `db.getCollection('${collection}').find(${this.logObject(where)}, ${this.logObject(options)})`;
148
- const orderBy = Utils.asArray(opts.orderBy);
149
- if (orderBy.length > 0) {
150
- const orderByTuples = [];
151
- orderBy.forEach(o => {
152
- Utils.keys(o).forEach(k => {
153
- const direction = o[k];
154
- if (typeof direction === 'string') {
155
- orderByTuples.push([k.toString(), direction.toUpperCase() === QueryOrder.ASC ? 1 : -1]);
156
- }
157
- else {
158
- orderByTuples.push([k.toString(), direction]);
159
- }
160
- });
161
- });
162
- if (orderByTuples.length > 0) {
163
- query += `.sort(${this.logObject(orderByTuples)})`;
164
- resultSet.sort(orderByTuples);
165
- }
166
- }
167
- if (opts.limit !== undefined) {
168
- query += `.limit(${opts.limit})`;
169
- resultSet.limit(opts.limit);
170
- }
171
- if (opts.offset !== undefined) {
172
- query += `.skip(${opts.offset})`;
173
- resultSet.skip(opts.offset);
174
- }
175
- return { cursor: resultSet, query };
176
- }
177
- async insertOne(entityName, data, ctx) {
178
- return this.runQuery('insertOne', entityName, data, undefined, ctx);
179
- }
180
- async insertMany(entityName, data, ctx) {
181
- return this.runQuery('insertMany', entityName, data, undefined, ctx);
182
- }
183
- async updateMany(entityName, where, data, ctx, upsert, upsertOptions) {
184
- return this.runQuery('updateMany', entityName, data, where, ctx, { upsert, upsertOptions });
185
- }
186
- async bulkUpdateMany(entityName, where, data, ctx, upsert, upsertOptions) {
187
- return this.runQuery('bulkUpdateMany', entityName, data, where, ctx, { upsert, upsertOptions });
188
- }
189
- async deleteMany(entityName, where, ctx) {
190
- return this.runQuery('deleteMany', entityName, undefined, where, ctx);
191
- }
192
- async aggregate(entityName, pipeline, ctx, loggerContext) {
193
- await this.ensureConnection();
194
- const collection = this.getCollectionName(entityName);
195
- /* v8 ignore next */
196
- const options = ctx ? { session: ctx } : {};
197
- const query = `db.getCollection('${collection}').aggregate(${this.logObject(pipeline)}, ${this.logObject(options)}).toArray();`;
198
- const now = Date.now();
199
- const res = await this.getCollection(entityName).aggregate(pipeline, options).toArray();
200
- this.logQuery(query, { took: Date.now() - now, results: res.length, ...loggerContext });
201
- return res;
202
- }
203
- async *streamAggregate(entityName, pipeline, ctx, loggerContext, stream = false) {
204
- await this.ensureConnection();
205
- const collection = this.getCollectionName(entityName);
206
- /* v8 ignore next */
207
- const options = ctx ? { session: ctx } : {};
208
- const query = `db.getCollection('${collection}').aggregate(${this.logObject(pipeline)}, ${this.logObject(options)})};`;
209
- const cursor = this.getCollection(entityName).aggregate(pipeline, options);
210
- this.logQuery(query, { ...loggerContext });
211
- yield* cursor;
212
- }
213
- async countDocuments(entityName, where, opts = {}) {
214
- return this.runQuery('countDocuments', entityName, undefined, where, opts.ctx, {
215
- loggerContext: opts.loggerContext,
216
- collation: opts.collation,
217
- indexHint: opts.indexHint,
218
- maxTimeMS: opts.maxTimeMS,
5
+ #client;
6
+ #db;
7
+ constructor(config, options, type = 'write') {
8
+ super(config, options, type);
9
+ // @ts-ignore
10
+ ObjectId.prototype[Symbol.for('nodejs.util.inspect.custom')] = function () {
11
+ return `ObjectId('${this.toHexString()}')`;
12
+ };
13
+ // @ts-ignore
14
+ Date.prototype[Symbol.for('nodejs.util.inspect.custom')] = function () {
15
+ return `ISODate('${this.toISOString()}')`;
16
+ };
17
+ }
18
+ async connect(options) {
19
+ this.getClient();
20
+ this.connected = true;
21
+ if (options?.skipOnConnect !== true) {
22
+ await this.onConnect();
23
+ }
24
+ }
25
+ createClient() {
26
+ let driverOptions = this.options.driverOptions ?? this.config.get('driverOptions');
27
+ if (typeof driverOptions === 'function') {
28
+ driverOptions = driverOptions();
29
+ }
30
+ if (driverOptions instanceof MongoClient) {
31
+ this.logger.log('info', 'Reusing MongoClient provided via `driverOptions`');
32
+ this.#client = driverOptions;
33
+ } else {
34
+ this.#client = new MongoClient(this.config.get('clientUrl'), this.mapOptions(driverOptions));
35
+ this.#client.appendMetadata({
36
+ name: 'MikroORM',
37
+ version: Utils.getORMVersion(),
38
+ });
39
+ const onCreateConnection = this.options.onCreateConnection ?? this.config.get('onCreateConnection');
40
+ /* v8 ignore next */
41
+ this.#client.on('connectionCreated', () => {
42
+ void onCreateConnection?.(this.#client);
43
+ });
44
+ }
45
+ this.#db = this.#client.db(this.config.get('dbName'));
46
+ }
47
+ async close(force) {
48
+ await this.#client?.close(force);
49
+ this.connected = false;
50
+ this.#client = undefined;
51
+ }
52
+ async isConnected() {
53
+ try {
54
+ const res = await this.#db?.command({ ping: 1 });
55
+ return (this.connected = !!res?.ok);
56
+ } catch (error) {
57
+ return (this.connected = false);
58
+ }
59
+ }
60
+ async checkConnection() {
61
+ try {
62
+ const res = await this.#db?.command({ ping: 1 });
63
+ return res?.ok
64
+ ? { ok: true }
65
+ : { ok: false, reason: 'Ping reply does not feature "ok" property, or it evaluates to "false"' };
66
+ } catch (error) {
67
+ return { ok: false, reason: error.message, error };
68
+ }
69
+ }
70
+ getClient() {
71
+ if (!this.#client) {
72
+ this.createClient();
73
+ }
74
+ return this.#client;
75
+ }
76
+ getCollection(name) {
77
+ return this.getDb().collection(this.getCollectionName(name));
78
+ }
79
+ async createCollection(name) {
80
+ return this.getDb().createCollection(this.getCollectionName(name));
81
+ }
82
+ async listCollections() {
83
+ const collections = await this.getDb().listCollections({}, { nameOnly: true }).toArray();
84
+ return collections.map(c => c.name);
85
+ }
86
+ async dropCollection(name) {
87
+ return this.getDb().dropCollection(this.getCollectionName(name));
88
+ }
89
+ mapOptions(overrides) {
90
+ const ret = {};
91
+ const pool = this.config.get('pool');
92
+ const username = this.config.get('user');
93
+ const password = this.config.get('password');
94
+ if (this.config.get('host')) {
95
+ throw new ValidationError('Mongo driver does not support `host` options, use `clientUrl` instead!');
96
+ }
97
+ if (username && password) {
98
+ ret.auth = { username, password };
99
+ }
100
+ ret.minPoolSize = pool.min;
101
+ ret.maxPoolSize = pool.max;
102
+ ret.waitQueueTimeoutMS = pool.idleTimeoutMillis;
103
+ return Utils.mergeConfig(ret, overrides);
104
+ }
105
+ getDb() {
106
+ this.#db ??= this.getClient().db(this.config.get('dbName'));
107
+ return this.#db;
108
+ }
109
+ async execute(query) {
110
+ throw new Error(`${this.constructor.name} does not support generic execute method`);
111
+ }
112
+ async find(entityName, where, opts = {}) {
113
+ const { cursor, query } = await this._find(entityName, where, opts);
114
+ const now = Date.now();
115
+ const res = await cursor.toArray();
116
+ this.logQuery(`${query}.toArray();`, { took: Date.now() - now, results: res.length, ...opts.loggerContext });
117
+ return res;
118
+ }
119
+ async *stream(entityName, where, opts = {}) {
120
+ const { cursor, query } = await this._find(entityName, where, opts);
121
+ this.logQuery(`${query}.toArray();`, opts.loggerContext);
122
+ yield* cursor;
123
+ }
124
+ async _find(entityName, where, opts = {}) {
125
+ await this.ensureConnection();
126
+ const collection = this.getCollectionName(entityName);
127
+ const options = opts.ctx ? { session: opts.ctx } : {};
128
+ if (opts.fields) {
129
+ options.projection = opts.fields.reduce((o, k) => Object.assign(o, { [k]: 1 }), {});
130
+ }
131
+ if (opts.collation) {
132
+ options.collation = opts.collation;
133
+ }
134
+ if (opts.indexHint != null) {
135
+ options.hint = opts.indexHint;
136
+ }
137
+ if (opts.maxTimeMS != null) {
138
+ options.maxTimeMS = opts.maxTimeMS;
139
+ }
140
+ if (opts.allowDiskUse != null) {
141
+ options.allowDiskUse = opts.allowDiskUse;
142
+ }
143
+ const resultSet = this.getCollection(entityName).find(where, options);
144
+ let query = `db.getCollection('${collection}').find(${this.logObject(where)}, ${this.logObject(options)})`;
145
+ const orderBy = Utils.asArray(opts.orderBy);
146
+ if (orderBy.length > 0) {
147
+ const orderByTuples = [];
148
+ orderBy.forEach(o => {
149
+ Utils.keys(o).forEach(k => {
150
+ const direction = o[k];
151
+ if (typeof direction === 'string') {
152
+ orderByTuples.push([k.toString(), direction.toUpperCase() === QueryOrder.ASC ? 1 : -1]);
153
+ } else {
154
+ orderByTuples.push([k.toString(), direction]);
155
+ }
219
156
  });
220
- }
221
- async transactional(cb, options = {}) {
222
- await this.ensureConnection();
223
- const session = await this.begin(options);
224
- try {
225
- const ret = await cb(session);
226
- await this.commit(session, options.eventBroadcaster);
227
- return ret;
228
- }
229
- catch (error) {
230
- await this.rollback(session, options.eventBroadcaster);
231
- throw error;
232
- }
233
- finally {
234
- await session.endSession();
235
- }
236
- }
237
- async begin(options = {}) {
238
- await this.ensureConnection();
239
- const { ctx, isolationLevel, eventBroadcaster, ...txOptions } = options;
240
- if (!ctx) {
241
- await eventBroadcaster?.dispatchEvent(EventType.beforeTransactionStart);
242
- }
243
- const session = ctx || this.getClient().startSession();
244
- session.startTransaction(txOptions);
245
- this.logQuery('db.begin();');
246
- await eventBroadcaster?.dispatchEvent(EventType.afterTransactionStart, session);
247
- return session;
248
- }
249
- async commit(ctx, eventBroadcaster) {
250
- await this.ensureConnection();
251
- await eventBroadcaster?.dispatchEvent(EventType.beforeTransactionCommit, ctx);
252
- await ctx.commitTransaction();
253
- this.logQuery('db.commit();');
254
- await eventBroadcaster?.dispatchEvent(EventType.afterTransactionCommit, ctx);
255
- }
256
- async rollback(ctx, eventBroadcaster) {
257
- await this.ensureConnection();
258
- await eventBroadcaster?.dispatchEvent(EventType.beforeTransactionRollback, ctx);
259
- await ctx.abortTransaction();
260
- this.logQuery('db.rollback();');
261
- await eventBroadcaster?.dispatchEvent(EventType.afterTransactionRollback, ctx);
262
- }
263
- async runQuery(method, entityName, data, where, ctx, opts) {
264
- await this.ensureConnection();
265
- const { upsert, upsertOptions, loggerContext, collation, indexHint, maxTimeMS } = opts ?? {};
266
- const collection = this.getCollectionName(entityName);
267
- const logger = this.config.getLogger();
268
- const options = ctx ? { session: ctx, upsert } : { upsert };
269
- if (options.upsert === undefined) {
270
- delete options.upsert;
271
- }
272
- const now = Date.now();
273
- let res;
274
- let query;
275
- const log = (msg) => (logger.isEnabled('query') ? msg() : '');
276
- switch (method) {
277
- case 'insertOne':
278
- Object.keys(data)
279
- .filter(k => typeof data[k] === 'undefined')
280
- .forEach(k => delete data[k]);
281
- query = log(() => `db.getCollection('${collection}').insertOne(${this.logObject(data)}, ${this.logObject(options)});`);
282
- res = await this.rethrow(this.getCollection(entityName).insertOne(data, options), query);
283
- break;
284
- case 'insertMany':
285
- data.forEach(data => Object.keys(data)
286
- .filter(k => typeof data[k] === 'undefined')
287
- .forEach(k => delete data[k]));
288
- query = log(() => `db.getCollection('${collection}').insertMany(${this.logObject(data)}, ${this.logObject(options)});`);
289
- res = await this.rethrow(this.getCollection(entityName).insertMany(data, options), query);
290
- break;
291
- case 'updateMany': {
292
- const payload = Object.keys(data).every(k => k.startsWith('$'))
293
- ? data
294
- : this.createUpdatePayload(data, upsertOptions);
295
- query = log(() => `db.getCollection('${collection}').updateMany(${this.logObject(where)}, ${this.logObject(payload)}, ${this.logObject(options)});`);
296
- res = (await this.rethrow(this.getCollection(entityName).updateMany(where, payload, options), query));
297
- break;
298
- }
299
- case 'bulkUpdateMany': {
300
- query = log(() => `bulk = db.getCollection('${collection}').initializeUnorderedBulkOp(${this.logObject(options)});\n`);
301
- const bulk = this.getCollection(entityName).initializeUnorderedBulkOp(options);
302
- data.forEach((row, idx) => {
303
- const id = where[idx];
304
- const cond = Utils.isPlainObject(id) ? id : { _id: id };
305
- const doc = this.createUpdatePayload(row, upsertOptions);
306
- if (upsert) {
307
- if (Utils.isEmpty(cond)) {
308
- query += log(() => `bulk.insert(${this.logObject(row)});\n`);
309
- bulk.insert(row);
310
- }
311
- else {
312
- query += log(() => `bulk.find(${this.logObject(cond)}).upsert().update(${this.logObject(doc)});\n`);
313
- bulk.find(cond).upsert().update(doc);
314
- }
315
- return;
316
- }
317
- query += log(() => `bulk.find(${this.logObject(cond)}).update(${this.logObject(doc)});\n`);
318
- bulk.find(cond).update(doc);
319
- });
320
- query += log(() => `bulk.execute()`);
321
- res = await this.rethrow(bulk.execute(), query);
322
- break;
157
+ });
158
+ if (orderByTuples.length > 0) {
159
+ query += `.sort(${this.logObject(orderByTuples)})`;
160
+ resultSet.sort(orderByTuples);
161
+ }
162
+ }
163
+ if (opts.limit !== undefined) {
164
+ query += `.limit(${opts.limit})`;
165
+ resultSet.limit(opts.limit);
166
+ }
167
+ if (opts.offset !== undefined) {
168
+ query += `.skip(${opts.offset})`;
169
+ resultSet.skip(opts.offset);
170
+ }
171
+ return { cursor: resultSet, query };
172
+ }
173
+ async insertOne(entityName, data, ctx) {
174
+ return this.runQuery('insertOne', entityName, data, undefined, ctx);
175
+ }
176
+ async insertMany(entityName, data, ctx) {
177
+ return this.runQuery('insertMany', entityName, data, undefined, ctx);
178
+ }
179
+ async updateMany(entityName, where, data, ctx, upsert, upsertOptions) {
180
+ return this.runQuery('updateMany', entityName, data, where, ctx, { upsert, upsertOptions });
181
+ }
182
+ async bulkUpdateMany(entityName, where, data, ctx, upsert, upsertOptions) {
183
+ return this.runQuery('bulkUpdateMany', entityName, data, where, ctx, { upsert, upsertOptions });
184
+ }
185
+ async deleteMany(entityName, where, ctx) {
186
+ return this.runQuery('deleteMany', entityName, undefined, where, ctx);
187
+ }
188
+ async aggregate(entityName, pipeline, ctx, loggerContext) {
189
+ await this.ensureConnection();
190
+ const collection = this.getCollectionName(entityName);
191
+ /* v8 ignore next */
192
+ const options = ctx ? { session: ctx } : {};
193
+ const query = `db.getCollection('${collection}').aggregate(${this.logObject(pipeline)}, ${this.logObject(options)}).toArray();`;
194
+ const now = Date.now();
195
+ const res = await this.getCollection(entityName).aggregate(pipeline, options).toArray();
196
+ this.logQuery(query, { took: Date.now() - now, results: res.length, ...loggerContext });
197
+ return res;
198
+ }
199
+ async *streamAggregate(entityName, pipeline, ctx, loggerContext, stream = false) {
200
+ await this.ensureConnection();
201
+ const collection = this.getCollectionName(entityName);
202
+ /* v8 ignore next */
203
+ const options = ctx ? { session: ctx } : {};
204
+ const query = `db.getCollection('${collection}').aggregate(${this.logObject(pipeline)}, ${this.logObject(options)})};`;
205
+ const cursor = this.getCollection(entityName).aggregate(pipeline, options);
206
+ this.logQuery(query, { ...loggerContext });
207
+ yield* cursor;
208
+ }
209
+ async countDocuments(entityName, where, opts = {}) {
210
+ return this.runQuery('countDocuments', entityName, undefined, where, opts.ctx, {
211
+ loggerContext: opts.loggerContext,
212
+ collation: opts.collation,
213
+ indexHint: opts.indexHint,
214
+ maxTimeMS: opts.maxTimeMS,
215
+ });
216
+ }
217
+ async transactional(cb, options = {}) {
218
+ await this.ensureConnection();
219
+ const session = await this.begin(options);
220
+ try {
221
+ const ret = await cb(session);
222
+ await this.commit(session, options.eventBroadcaster);
223
+ return ret;
224
+ } catch (error) {
225
+ await this.rollback(session, options.eventBroadcaster);
226
+ throw error;
227
+ } finally {
228
+ await session.endSession();
229
+ }
230
+ }
231
+ async begin(options = {}) {
232
+ await this.ensureConnection();
233
+ const { ctx, isolationLevel, eventBroadcaster, ...txOptions } = options;
234
+ if (!ctx) {
235
+ await eventBroadcaster?.dispatchEvent(EventType.beforeTransactionStart);
236
+ }
237
+ const session = ctx || this.getClient().startSession();
238
+ session.startTransaction(txOptions);
239
+ this.logQuery('db.begin();');
240
+ await eventBroadcaster?.dispatchEvent(EventType.afterTransactionStart, session);
241
+ return session;
242
+ }
243
+ async commit(ctx, eventBroadcaster) {
244
+ await this.ensureConnection();
245
+ await eventBroadcaster?.dispatchEvent(EventType.beforeTransactionCommit, ctx);
246
+ await ctx.commitTransaction();
247
+ this.logQuery('db.commit();');
248
+ await eventBroadcaster?.dispatchEvent(EventType.afterTransactionCommit, ctx);
249
+ }
250
+ async rollback(ctx, eventBroadcaster) {
251
+ await this.ensureConnection();
252
+ await eventBroadcaster?.dispatchEvent(EventType.beforeTransactionRollback, ctx);
253
+ await ctx.abortTransaction();
254
+ this.logQuery('db.rollback();');
255
+ await eventBroadcaster?.dispatchEvent(EventType.afterTransactionRollback, ctx);
256
+ }
257
+ async runQuery(method, entityName, data, where, ctx, opts) {
258
+ await this.ensureConnection();
259
+ const { upsert, upsertOptions, loggerContext, collation, indexHint, maxTimeMS } = opts ?? {};
260
+ const collection = this.getCollectionName(entityName);
261
+ const logger = this.config.getLogger();
262
+ const options = ctx ? { session: ctx, upsert } : { upsert };
263
+ if (options.upsert === undefined) {
264
+ delete options.upsert;
265
+ }
266
+ const now = Date.now();
267
+ let res;
268
+ let query;
269
+ const log = msg => (logger.isEnabled('query') ? msg() : '');
270
+ switch (method) {
271
+ case 'insertOne':
272
+ Object.keys(data)
273
+ .filter(k => typeof data[k] === 'undefined')
274
+ .forEach(k => delete data[k]);
275
+ query = log(
276
+ () => `db.getCollection('${collection}').insertOne(${this.logObject(data)}, ${this.logObject(options)});`,
277
+ );
278
+ res = await this.rethrow(this.getCollection(entityName).insertOne(data, options), query);
279
+ break;
280
+ case 'insertMany':
281
+ data.forEach(data =>
282
+ Object.keys(data)
283
+ .filter(k => typeof data[k] === 'undefined')
284
+ .forEach(k => delete data[k]),
285
+ );
286
+ query = log(
287
+ () => `db.getCollection('${collection}').insertMany(${this.logObject(data)}, ${this.logObject(options)});`,
288
+ );
289
+ res = await this.rethrow(this.getCollection(entityName).insertMany(data, options), query);
290
+ break;
291
+ case 'updateMany': {
292
+ const payload = Object.keys(data).every(k => k.startsWith('$'))
293
+ ? data
294
+ : this.createUpdatePayload(data, upsertOptions);
295
+ query = log(
296
+ () =>
297
+ `db.getCollection('${collection}').updateMany(${this.logObject(where)}, ${this.logObject(payload)}, ${this.logObject(options)});`,
298
+ );
299
+ res = await this.rethrow(this.getCollection(entityName).updateMany(where, payload, options), query);
300
+ break;
301
+ }
302
+ case 'bulkUpdateMany': {
303
+ query = log(
304
+ () => `bulk = db.getCollection('${collection}').initializeUnorderedBulkOp(${this.logObject(options)});\n`,
305
+ );
306
+ const bulk = this.getCollection(entityName).initializeUnorderedBulkOp(options);
307
+ data.forEach((row, idx) => {
308
+ const id = where[idx];
309
+ const cond = Utils.isPlainObject(id) ? id : { _id: id };
310
+ const doc = this.createUpdatePayload(row, upsertOptions);
311
+ if (upsert) {
312
+ if (Utils.isEmpty(cond)) {
313
+ query += log(() => `bulk.insert(${this.logObject(row)});\n`);
314
+ bulk.insert(row);
315
+ } else {
316
+ query += log(() => `bulk.find(${this.logObject(cond)}).upsert().update(${this.logObject(doc)});\n`);
317
+ bulk.find(cond).upsert().update(doc);
323
318
  }
324
- case 'deleteMany':
325
- case 'countDocuments':
326
- if (method === 'countDocuments') {
327
- if (collation) {
328
- options.collation = collation;
329
- }
330
- if (indexHint != null) {
331
- options.hint = indexHint;
332
- }
333
- if (maxTimeMS != null) {
334
- options.maxTimeMS = maxTimeMS;
335
- }
336
- }
337
- query = log(() => `db.getCollection('${collection}').${method}(${this.logObject(where)}, ${this.logObject(options)});`);
338
- res = await this.rethrow(this.getCollection(entityName)[method](where, options), query);
339
- break;
340
- }
341
- this.logQuery(query, { took: Date.now() - now, ...loggerContext });
319
+ return;
320
+ }
321
+ query += log(() => `bulk.find(${this.logObject(cond)}).update(${this.logObject(doc)});\n`);
322
+ bulk.find(cond).update(doc);
323
+ });
324
+ query += log(() => `bulk.execute()`);
325
+ res = await this.rethrow(bulk.execute(), query);
326
+ break;
327
+ }
328
+ case 'deleteMany':
329
+ case 'countDocuments':
342
330
  if (method === 'countDocuments') {
343
- return res;
344
- }
345
- return this.transformResult(res);
346
- }
347
- rethrow(promise, query) {
348
- return promise.catch(e => {
349
- this.logQuery(query, { level: 'error' });
350
- e.message += '\nQuery: ' + query;
351
- throw e;
331
+ if (collation) {
332
+ options.collation = collation;
333
+ }
334
+ if (indexHint != null) {
335
+ options.hint = indexHint;
336
+ }
337
+ if (maxTimeMS != null) {
338
+ options.maxTimeMS = maxTimeMS;
339
+ }
340
+ }
341
+ query = log(
342
+ () => `db.getCollection('${collection}').${method}(${this.logObject(where)}, ${this.logObject(options)});`,
343
+ );
344
+ res = await this.rethrow(this.getCollection(entityName)[method](where, options), query);
345
+ break;
346
+ }
347
+ this.logQuery(query, { took: Date.now() - now, ...loggerContext });
348
+ if (method === 'countDocuments') {
349
+ return res;
350
+ }
351
+ return this.transformResult(res);
352
+ }
353
+ rethrow(promise, query) {
354
+ return promise.catch(e => {
355
+ this.logQuery(query, { level: 'error' });
356
+ e.message += '\nQuery: ' + query;
357
+ throw e;
358
+ });
359
+ }
360
+ createUpdatePayload(row, upsertOptions) {
361
+ row = { ...row };
362
+ const doc = { $set: row };
363
+ Utils.keys(row).forEach(k => {
364
+ if (k.toString().startsWith('$')) {
365
+ doc[k] = row[k];
366
+ delete row[k];
367
+ }
368
+ });
369
+ const $unset = {};
370
+ const $inc = {};
371
+ for (const k of Utils.keys(row)) {
372
+ const item = row[k];
373
+ if (typeof item === 'undefined') {
374
+ $unset[k] = '';
375
+ delete row[k];
376
+ continue;
377
+ }
378
+ if (Utils.isPlainObject(item) && '$inc' in item) {
379
+ $inc[k] = item.$inc;
380
+ delete row[k];
381
+ }
382
+ }
383
+ if (upsertOptions) {
384
+ if (upsertOptions.onConflictAction === 'ignore') {
385
+ doc.$setOnInsert = doc.$set;
386
+ delete doc.$set;
387
+ }
388
+ if (upsertOptions.onConflictMergeFields) {
389
+ doc.$setOnInsert = {};
390
+ upsertOptions.onConflictMergeFields.forEach(f => {
391
+ doc.$setOnInsert[f] = doc.$set[f];
392
+ delete doc.$set[f];
352
393
  });
353
- }
354
- createUpdatePayload(row, upsertOptions) {
355
- row = { ...row };
356
- const doc = { $set: row };
357
- Utils.keys(row).forEach(k => {
358
- if (k.toString().startsWith('$')) {
359
- doc[k] = row[k];
360
- delete row[k];
361
- }
394
+ const { $set, $setOnInsert } = doc;
395
+ doc.$set = $setOnInsert;
396
+ doc.$setOnInsert = $set;
397
+ } else if (upsertOptions.onConflictExcludeFields) {
398
+ doc.$setOnInsert = {};
399
+ upsertOptions.onConflictExcludeFields.forEach(f => {
400
+ doc.$setOnInsert[f] = doc.$set[f];
401
+ delete doc.$set[f];
362
402
  });
363
- const $unset = {};
364
- const $inc = {};
365
- for (const k of Utils.keys(row)) {
366
- const item = row[k];
367
- if (typeof item === 'undefined') {
368
- $unset[k] = '';
369
- delete row[k];
370
- continue;
371
- }
372
- if (Utils.isPlainObject(item) && '$inc' in item) {
373
- $inc[k] = item.$inc;
374
- delete row[k];
375
- }
376
- }
377
- if (upsertOptions) {
378
- if (upsertOptions.onConflictAction === 'ignore') {
379
- doc.$setOnInsert = doc.$set;
380
- delete doc.$set;
381
- }
382
- if (upsertOptions.onConflictMergeFields) {
383
- doc.$setOnInsert = {};
384
- upsertOptions.onConflictMergeFields.forEach(f => {
385
- doc.$setOnInsert[f] = doc.$set[f];
386
- delete doc.$set[f];
387
- });
388
- const { $set, $setOnInsert } = doc;
389
- doc.$set = $setOnInsert;
390
- doc.$setOnInsert = $set;
391
- }
392
- else if (upsertOptions.onConflictExcludeFields) {
393
- doc.$setOnInsert = {};
394
- upsertOptions.onConflictExcludeFields.forEach(f => {
395
- doc.$setOnInsert[f] = doc.$set[f];
396
- delete doc.$set[f];
397
- });
398
- }
399
- }
400
- if (Utils.hasObjectKeys($unset)) {
401
- doc.$unset = $unset;
402
- }
403
- if (Utils.hasObjectKeys($inc)) {
404
- doc.$inc = $inc;
405
- }
406
- if (!Utils.hasObjectKeys(doc.$set)) {
407
- delete doc.$set;
408
- }
409
- return doc;
410
- }
411
- transformResult(res) {
412
- return {
413
- affectedRows: res.modifiedCount || res.deletedCount || res.insertedCount || 0,
414
- insertId: res.insertedId ?? res.insertedIds?.[0],
415
- insertedIds: res.insertedIds ? Object.values(res.insertedIds) : undefined,
416
- };
417
- }
418
- getCollectionName(entityName) {
419
- const meta = this.metadata.find(entityName);
420
- return meta ? meta.collection : Utils.className(entityName);
421
- }
422
- logObject(o) {
423
- if (o?.session) {
424
- o = { ...o, session: `[ClientSession]` };
425
- }
426
- return inspect(o, { depth: 5, compact: true, breakLength: 300 });
427
- }
403
+ }
404
+ }
405
+ if (Utils.hasObjectKeys($unset)) {
406
+ doc.$unset = $unset;
407
+ }
408
+ if (Utils.hasObjectKeys($inc)) {
409
+ doc.$inc = $inc;
410
+ }
411
+ if (!Utils.hasObjectKeys(doc.$set)) {
412
+ delete doc.$set;
413
+ }
414
+ return doc;
415
+ }
416
+ transformResult(res) {
417
+ return {
418
+ affectedRows: res.modifiedCount || res.deletedCount || res.insertedCount || 0,
419
+ insertId: res.insertedId ?? res.insertedIds?.[0],
420
+ insertedIds: res.insertedIds ? Object.values(res.insertedIds) : undefined,
421
+ };
422
+ }
423
+ getCollectionName(entityName) {
424
+ const meta = this.metadata.find(entityName);
425
+ return meta ? meta.collection : Utils.className(entityName);
426
+ }
427
+ logObject(o) {
428
+ if (o?.session) {
429
+ o = { ...o, session: `[ClientSession]` };
430
+ }
431
+ return inspect(o, { depth: 5, compact: true, breakLength: 300 });
432
+ }
428
433
  }