@mikro-orm/mongodb 7.0.5 → 7.0.6-dev.1

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,433 +1,428 @@
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
- } 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
- }
156
- });
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);
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);
318
165
  }
319
- return;
320
- }
321
- query += log(() => `bulk.find(${this.logObject(cond)}).update(${this.logObject(doc)});\n`);
322
- bulk.find(cond).update(doc);
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,
323
219
  });
324
- query += log(() => `bulk.execute()`);
325
- res = await this.rethrow(bulk.execute(), query);
326
- break;
327
- }
328
- case 'deleteMany':
329
- case 'countDocuments':
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;
323
+ }
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 });
330
342
  if (method === 'countDocuments') {
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];
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;
393
352
  });
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];
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
+ }
402
362
  });
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
- }
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
+ }
433
428
  }