@opra/mongodb 1.0.0-beta.2 → 1.0.0-beta.4

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.
@@ -15,65 +15,69 @@ var MongoAdapter;
15
15
  MongoAdapter.prepareProjection = prepare_projection_js_1.default;
16
16
  MongoAdapter.prepareSort = prepare_sort_js_1.default;
17
17
  async function parseRequest(context) {
18
- const { operation } = context;
18
+ if (context.protocol !== 'http') {
19
+ throw new TypeError('MongoAdapter can parse only HttpContext');
20
+ }
21
+ const ctx = context;
22
+ const { operation } = ctx;
19
23
  if (operation?.composition?.startsWith('Entity.') && operation.compositionOptions?.type) {
20
24
  const controller = operation.owner;
21
25
  switch (operation.composition) {
22
26
  case 'Entity.Create': {
23
- const data = await context.getBody();
27
+ const data = await ctx.getBody();
24
28
  const options = {
25
- projection: context.queryParams.projection,
29
+ projection: ctx.queryParams.projection,
26
30
  };
27
31
  return { method: 'create', data, options };
28
32
  }
29
33
  case 'Entity.Delete': {
30
34
  const keyParam = operation.parameters.find(p => p.keyParam) || controller.parameters.find(p => p.keyParam);
31
- const key = keyParam && context.pathParams[String(keyParam.name)];
35
+ const key = keyParam && ctx.pathParams[String(keyParam.name)];
32
36
  const options = {
33
- filter: context.queryParams.filter,
37
+ filter: ctx.queryParams.filter,
34
38
  };
35
39
  return { method: 'delete', key, options };
36
40
  }
37
41
  case 'Entity.DeleteMany': {
38
42
  const options = {
39
- filter: context.queryParams.filter,
43
+ filter: ctx.queryParams.filter,
40
44
  };
41
45
  return { method: 'deleteMany', options };
42
46
  }
43
47
  case 'Entity.FindMany': {
44
48
  const options = {
45
- filter: context.queryParams.filter,
46
- projection: context.queryParams.projection || operation.compositionOptions.defaultProjection,
47
- count: context.queryParams.count,
48
- limit: context.queryParams.limit || operation.compositionOptions.defaultLimit,
49
- skip: context.queryParams.skip,
50
- sort: context.queryParams.sort || operation.compositionOptions.defaultSort,
49
+ filter: ctx.queryParams.filter,
50
+ projection: ctx.queryParams.projection || operation.compositionOptions.defaultProjection,
51
+ count: ctx.queryParams.count,
52
+ limit: ctx.queryParams.limit || operation.compositionOptions.defaultLimit,
53
+ skip: ctx.queryParams.skip,
54
+ sort: ctx.queryParams.sort || operation.compositionOptions.defaultSort,
51
55
  };
52
56
  return { method: 'findMany', options };
53
57
  }
54
58
  case 'Entity.Get': {
55
59
  const keyParam = operation.parameters.find(p => p.keyParam) || controller.parameters.find(p => p.keyParam);
56
- const key = keyParam && context.pathParams[String(keyParam.name)];
60
+ const key = keyParam && ctx.pathParams[String(keyParam.name)];
57
61
  const options = {
58
- projection: context.queryParams.projection,
59
- filter: context.queryParams.filter,
62
+ projection: ctx.queryParams.projection,
63
+ filter: ctx.queryParams.filter,
60
64
  };
61
65
  return { method: 'get', key, options };
62
66
  }
63
67
  case 'Entity.Update': {
64
- const data = await context.getBody();
68
+ const data = await ctx.getBody();
65
69
  const keyParam = operation.parameters.find(p => p.keyParam) || controller.parameters.find(p => p.keyParam);
66
- const key = keyParam && context.pathParams[String(keyParam.name)];
70
+ const key = keyParam && ctx.pathParams[String(keyParam.name)];
67
71
  const options = {
68
- projection: context.queryParams.projection,
69
- filter: context.queryParams.filter,
72
+ projection: ctx.queryParams.projection,
73
+ filter: ctx.queryParams.filter,
70
74
  };
71
75
  return { method: 'update', key, data, options };
72
76
  }
73
77
  case 'Entity.UpdateMany': {
74
- const data = await context.getBody();
78
+ const data = await ctx.getBody();
75
79
  const options = {
76
- filter: context.queryParams.filter,
80
+ filter: ctx.queryParams.filter,
77
81
  };
78
82
  return { method: 'updateMany', data, options };
79
83
  }
@@ -39,7 +39,7 @@ class MongoEntityService extends mongo_service_js_1.MongoService {
39
39
  const collection = await this.getCollection(db);
40
40
  const r = await collection.insertOne(document, {
41
41
  ...options,
42
- session: options?.session || this.getSession(),
42
+ session: options?.session ?? this.getSession(),
43
43
  });
44
44
  /* istanbul ignore next */
45
45
  if (!r.insertedId) {
@@ -61,7 +61,7 @@ class MongoEntityService extends mongo_service_js_1.MongoService {
61
61
  return ((await collection.countDocuments(filter || {}, {
62
62
  ...options,
63
63
  limit: undefined,
64
- session: options?.session || this.getSession(),
64
+ session: options?.session ?? this.getSession(),
65
65
  })) || 0);
66
66
  }
67
67
  /**
@@ -79,9 +79,10 @@ class MongoEntityService extends mongo_service_js_1.MongoService {
79
79
  ]);
80
80
  const db = this.getDatabase();
81
81
  const collection = await this.getCollection(db);
82
+ const session = options?.session ?? this.getSession();
82
83
  return (await collection.deleteOne(filter || {}, {
83
84
  ...options,
84
- session: options?.session || this.getSession(),
85
+ session,
85
86
  })).deletedCount;
86
87
  }
87
88
  /**
@@ -97,7 +98,7 @@ class MongoEntityService extends mongo_service_js_1.MongoService {
97
98
  const collection = await this.getCollection(db);
98
99
  return (await collection.deleteMany(filter || {}, {
99
100
  ...options,
100
- session: options?.session || this.getSession(),
101
+ session: options?.session ?? this.getSession(),
101
102
  })).deletedCount;
102
103
  }
103
104
  /**
@@ -113,7 +114,7 @@ class MongoEntityService extends mongo_service_js_1.MongoService {
113
114
  const collection = await this.getCollection(db);
114
115
  return await collection.distinct(field, filter || {}, {
115
116
  ...options,
116
- session: options?.session || this.getSession(),
117
+ session: options?.session ?? this.getSession(),
117
118
  });
118
119
  }
119
120
  /**
@@ -132,7 +133,7 @@ class MongoEntityService extends mongo_service_js_1.MongoService {
132
133
  const collection = await this.getCollection(db);
133
134
  const out = await collection.findOne(filter || {}, {
134
135
  ...(0, lodash_omit_1.default)(options, 'filter'),
135
- session: options?.session || this.getSession(),
136
+ session: options?.session ?? this.getSession(),
136
137
  projection: mongo_adapter_js_1.MongoAdapter.prepareProjection(this.dataType, options?.projection),
137
138
  limit: undefined,
138
139
  skip: undefined,
@@ -155,7 +156,7 @@ class MongoEntityService extends mongo_service_js_1.MongoService {
155
156
  const collection = await this.getCollection(db);
156
157
  const out = await collection.findOne(filter || {}, {
157
158
  ...(0, lodash_omit_1.default)(options, 'filter'),
158
- session: options?.session || this.getSession(),
159
+ session: options?.session ?? this.getSession(),
159
160
  sort: options?.sort ? mongo_adapter_js_1.MongoAdapter.prepareSort(options.sort) : undefined,
160
161
  projection: mongo_adapter_js_1.MongoAdapter.prepareProjection(this.dataType, options?.projection),
161
162
  limit: undefined,
@@ -195,7 +196,7 @@ class MongoEntityService extends mongo_service_js_1.MongoService {
195
196
  const collection = await this.getCollection(db);
196
197
  const cursor = collection.aggregate(stages, {
197
198
  ...(0, lodash_omit_1.default)(options, ['projection', 'sort', 'skip', 'limit', 'filter']),
198
- session: options?.session || this.getSession(),
199
+ session: options?.session ?? this.getSession(),
199
200
  });
200
201
  /** Execute db command */
201
202
  try {
@@ -253,7 +254,7 @@ class MongoEntityService extends mongo_service_js_1.MongoService {
253
254
  const collection = await this.getCollection(db);
254
255
  const cursor = collection.aggregate(stages, {
255
256
  ...(0, lodash_omit_1.default)(options, ['projection', 'sort', 'skip', 'limit', 'filter']),
256
- session: options?.session || this.getSession(),
257
+ session: options?.session ?? this.getSession(),
257
258
  });
258
259
  /** Fetch the cursor and decode the result objects */
259
260
  try {
@@ -301,7 +302,7 @@ class MongoEntityService extends mongo_service_js_1.MongoService {
301
302
  ...options,
302
303
  returnDocument: 'after',
303
304
  includeResultMetadata: false,
304
- session: options?.session || this.getSession(),
305
+ session: options?.session ?? this.getSession(),
305
306
  projection: mongo_adapter_js_1.MongoAdapter.prepareProjection(this.dataType, options?.projection),
306
307
  });
307
308
  const outputCodec = this._getOutputCodec('update');
@@ -339,7 +340,7 @@ class MongoEntityService extends mongo_service_js_1.MongoService {
339
340
  const collection = await this.getCollection(db);
340
341
  return (await collection.updateOne(filter || {}, update, {
341
342
  ...options,
342
- session: options?.session || this.getSession(),
343
+ session: options?.session ?? this.getSession(),
343
344
  upsert: undefined,
344
345
  })).matchedCount;
345
346
  }
@@ -371,7 +372,7 @@ class MongoEntityService extends mongo_service_js_1.MongoService {
371
372
  const collection = await this.getCollection(db);
372
373
  return (await collection.updateMany(filter || {}, update, {
373
374
  ...(0, lodash_omit_1.default)(options, 'filter'),
374
- session: options?.session || this.getSession(),
375
+ session: options?.session ?? this.getSession(),
375
376
  upsert: false,
376
377
  })).matchedCount;
377
378
  }
@@ -123,7 +123,7 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
123
123
  };
124
124
  const r = await collection.updateOne(docFilter, update, {
125
125
  ...options,
126
- session: options?.session || this.getSession(),
126
+ session: options?.session ?? this.getSession(),
127
127
  upsert: undefined,
128
128
  });
129
129
  if (!r.matchedCount) {
@@ -173,7 +173,7 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
173
173
  const collection = await this.getCollection(db);
174
174
  const cursor = collection.aggregate(stages, {
175
175
  ...(0, lodash_omit_1.default)(options, ['documentFilter', 'nestedFilter', 'projection', 'sort', 'skip', 'limit', 'filter', 'count']),
176
- session: options?.session || this.getSession(),
176
+ session: options?.session ?? this.getSession(),
177
177
  });
178
178
  try {
179
179
  const n = await cursor.next();
@@ -223,7 +223,7 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
223
223
  const collection = await this.getCollection(db);
224
224
  const r = await collection.updateOne(matchFilter, update, {
225
225
  ...options,
226
- session: options?.session || this.getSession(),
226
+ session: options?.session ?? this.getSession(),
227
227
  upsert: undefined,
228
228
  });
229
229
  return r.modifiedCount ? 1 : 0;
@@ -273,7 +273,7 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
273
273
  const collection = await this.getCollection(db);
274
274
  await collection.updateOne(matchFilter, update, {
275
275
  ...options,
276
- session: options?.session || this.getSession(),
276
+ session: options?.session ?? this.getSession(),
277
277
  upsert: undefined,
278
278
  });
279
279
  return matchCount;
@@ -447,7 +447,7 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
447
447
  const collection = await this.getCollection(db);
448
448
  const cursor = collection.aggregate(stages, {
449
449
  ...(0, lodash_omit_1.default)(options, ['documentFilter', 'nestedFilter', 'projection', 'sort', 'skip', 'limit', 'filter', 'count']),
450
- session: options?.session || this.getSession(),
450
+ session: options?.session ?? this.getSession(),
451
451
  });
452
452
  try {
453
453
  const outputCodec = this._getOutputCodec('find');
@@ -518,7 +518,7 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
518
518
  const collection = await this.getCollection(db);
519
519
  const cursor = collection.aggregate(stages, {
520
520
  ...(0, lodash_omit_1.default)(options, ['documentFilter', 'nestedFilter', 'projection', 'sort', 'skip', 'limit', 'filter', 'count']),
521
- session: options?.session || this.getSession(),
521
+ session: options?.session ?? this.getSession(),
522
522
  });
523
523
  try {
524
524
  const facetResult = await cursor.toArray();
@@ -677,7 +677,7 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
677
677
  const collection = await this.getCollection(db);
678
678
  await collection.updateOne(matchFilter, update, {
679
679
  ...options,
680
- session: options?.session || this.getSession(),
680
+ session: options?.session ?? this.getSession(),
681
681
  upsert: undefined,
682
682
  });
683
683
  return count;
@@ -5,6 +5,7 @@ const common_1 = require("@opra/common");
5
5
  const core_1 = require("@opra/core");
6
6
  const mongodb_1 = require("mongodb");
7
7
  const mongo_adapter_js_1 = require("./mongo-adapter.js");
8
+ const transactionKey = Symbol.for('transaction');
8
9
  /**
9
10
  * Class representing a MongoDB service for interacting with a collection.
10
11
  * @extends ServiceBase
@@ -95,38 +96,43 @@ class MongoService extends core_1.ServiceBase {
95
96
  * @param [options] - Optional options for the transaction.
96
97
  */
97
98
  async withTransaction(callback, options) {
98
- let session = this.getSession();
99
- if (session)
100
- return callback(session);
101
- // Backup old session property
102
- const hasOldSession = Object.prototype.hasOwnProperty.call(this, 'session');
103
- const oldSessionGetter = hasOldSession ? this.session : undefined;
104
- const db = this.getDatabase();
105
- const client = db.client;
106
- session = client.startSession();
107
- this.session = session;
99
+ const ctx = this.context;
100
+ let closeSessionOnFinish = false;
101
+ let transaction = ctx[transactionKey];
102
+ let session;
103
+ if (transaction) {
104
+ session = transaction.session;
105
+ }
106
+ else {
107
+ const db = this.getDatabase();
108
+ const client = db.client;
109
+ session = client.startSession();
110
+ closeSessionOnFinish = true;
111
+ transaction = {
112
+ db,
113
+ session,
114
+ };
115
+ ctx[transactionKey] = transaction;
116
+ }
108
117
  const oldInTransaction = session.inTransaction();
109
118
  try {
110
119
  if (!oldInTransaction)
111
120
  session.startTransaction(options);
112
- const out = await callback(session);
113
- if (!oldInTransaction)
121
+ const out = await callback(session, this);
122
+ if (!oldInTransaction && session.inTransaction())
114
123
  await session.commitTransaction();
115
124
  return out;
116
125
  }
117
126
  catch (e) {
118
- if (!oldInTransaction)
127
+ if (!oldInTransaction && session.inTransaction())
119
128
  await session.abortTransaction();
120
129
  throw e;
121
130
  }
122
131
  finally {
123
- // Restore old session property
124
- if (hasOldSession)
125
- this.session = oldSessionGetter;
126
- else
127
- delete this.session;
128
- if (!oldInTransaction)
132
+ delete ctx[transactionKey];
133
+ if (closeSessionOnFinish) {
129
134
  await session.endSession();
135
+ }
130
136
  }
131
137
  }
132
138
  /**
@@ -137,10 +143,14 @@ class MongoService extends core_1.ServiceBase {
137
143
  * @throws {Error} If the context or database is not set.
138
144
  */
139
145
  getDatabase() {
146
+ const ctx = this.context;
147
+ const transaction = ctx[transactionKey];
148
+ if (transaction)
149
+ return transaction.db;
140
150
  const db = typeof this.db === 'function' ? this.db(this) : this.db;
141
- if (!db)
142
- throw new Error(`Database not set!`);
143
- return db;
151
+ if (db)
152
+ return db;
153
+ throw new Error(`Database not set!`);
144
154
  }
145
155
  /**
146
156
  * Retrieves the database session.
@@ -150,7 +160,13 @@ class MongoService extends core_1.ServiceBase {
150
160
  * @throws {Error} If the context or database is not set.
151
161
  */
152
162
  getSession() {
153
- return typeof this.session === 'function' ? this.session(this) : this.session;
163
+ const ctx = this.context;
164
+ const transaction = ctx[transactionKey];
165
+ if (transaction)
166
+ return transaction.session;
167
+ const session = typeof this.session === 'function' ? this.session(this) : this.session;
168
+ if (session)
169
+ return session;
154
170
  }
155
171
  /**
156
172
  * Retrieves a MongoDB collection from the given database.
@@ -173,7 +173,7 @@ class MongoSingletonService extends mongo_entity_service_js_1.MongoEntityService
173
173
  });
174
174
  }
175
175
  /**
176
- * Updates a document in the MongoDB collection.
176
+ * Updates a document in the MongoDB collection
177
177
  *
178
178
  * @param {PatchDTO<T>} input - The partial input to update the document.
179
179
  * @param {MongoEntityService.UpdateOneOptions<T>} [options] - The update options.
@@ -11,65 +11,69 @@ export var MongoAdapter;
11
11
  MongoAdapter.prepareProjection = _prepareProjection;
12
12
  MongoAdapter.prepareSort = _prepareSort;
13
13
  async function parseRequest(context) {
14
- const { operation } = context;
14
+ if (context.protocol !== 'http') {
15
+ throw new TypeError('MongoAdapter can parse only HttpContext');
16
+ }
17
+ const ctx = context;
18
+ const { operation } = ctx;
15
19
  if (operation?.composition?.startsWith('Entity.') && operation.compositionOptions?.type) {
16
20
  const controller = operation.owner;
17
21
  switch (operation.composition) {
18
22
  case 'Entity.Create': {
19
- const data = await context.getBody();
23
+ const data = await ctx.getBody();
20
24
  const options = {
21
- projection: context.queryParams.projection,
25
+ projection: ctx.queryParams.projection,
22
26
  };
23
27
  return { method: 'create', data, options };
24
28
  }
25
29
  case 'Entity.Delete': {
26
30
  const keyParam = operation.parameters.find(p => p.keyParam) || controller.parameters.find(p => p.keyParam);
27
- const key = keyParam && context.pathParams[String(keyParam.name)];
31
+ const key = keyParam && ctx.pathParams[String(keyParam.name)];
28
32
  const options = {
29
- filter: context.queryParams.filter,
33
+ filter: ctx.queryParams.filter,
30
34
  };
31
35
  return { method: 'delete', key, options };
32
36
  }
33
37
  case 'Entity.DeleteMany': {
34
38
  const options = {
35
- filter: context.queryParams.filter,
39
+ filter: ctx.queryParams.filter,
36
40
  };
37
41
  return { method: 'deleteMany', options };
38
42
  }
39
43
  case 'Entity.FindMany': {
40
44
  const options = {
41
- filter: context.queryParams.filter,
42
- projection: context.queryParams.projection || operation.compositionOptions.defaultProjection,
43
- count: context.queryParams.count,
44
- limit: context.queryParams.limit || operation.compositionOptions.defaultLimit,
45
- skip: context.queryParams.skip,
46
- sort: context.queryParams.sort || operation.compositionOptions.defaultSort,
45
+ filter: ctx.queryParams.filter,
46
+ projection: ctx.queryParams.projection || operation.compositionOptions.defaultProjection,
47
+ count: ctx.queryParams.count,
48
+ limit: ctx.queryParams.limit || operation.compositionOptions.defaultLimit,
49
+ skip: ctx.queryParams.skip,
50
+ sort: ctx.queryParams.sort || operation.compositionOptions.defaultSort,
47
51
  };
48
52
  return { method: 'findMany', options };
49
53
  }
50
54
  case 'Entity.Get': {
51
55
  const keyParam = operation.parameters.find(p => p.keyParam) || controller.parameters.find(p => p.keyParam);
52
- const key = keyParam && context.pathParams[String(keyParam.name)];
56
+ const key = keyParam && ctx.pathParams[String(keyParam.name)];
53
57
  const options = {
54
- projection: context.queryParams.projection,
55
- filter: context.queryParams.filter,
58
+ projection: ctx.queryParams.projection,
59
+ filter: ctx.queryParams.filter,
56
60
  };
57
61
  return { method: 'get', key, options };
58
62
  }
59
63
  case 'Entity.Update': {
60
- const data = await context.getBody();
64
+ const data = await ctx.getBody();
61
65
  const keyParam = operation.parameters.find(p => p.keyParam) || controller.parameters.find(p => p.keyParam);
62
- const key = keyParam && context.pathParams[String(keyParam.name)];
66
+ const key = keyParam && ctx.pathParams[String(keyParam.name)];
63
67
  const options = {
64
- projection: context.queryParams.projection,
65
- filter: context.queryParams.filter,
68
+ projection: ctx.queryParams.projection,
69
+ filter: ctx.queryParams.filter,
66
70
  };
67
71
  return { method: 'update', key, data, options };
68
72
  }
69
73
  case 'Entity.UpdateMany': {
70
- const data = await context.getBody();
74
+ const data = await ctx.getBody();
71
75
  const options = {
72
- filter: context.queryParams.filter,
76
+ filter: ctx.queryParams.filter,
73
77
  };
74
78
  return { method: 'updateMany', data, options };
75
79
  }
@@ -35,7 +35,7 @@ export class MongoEntityService extends MongoService {
35
35
  const collection = await this.getCollection(db);
36
36
  const r = await collection.insertOne(document, {
37
37
  ...options,
38
- session: options?.session || this.getSession(),
38
+ session: options?.session ?? this.getSession(),
39
39
  });
40
40
  /* istanbul ignore next */
41
41
  if (!r.insertedId) {
@@ -57,7 +57,7 @@ export class MongoEntityService extends MongoService {
57
57
  return ((await collection.countDocuments(filter || {}, {
58
58
  ...options,
59
59
  limit: undefined,
60
- session: options?.session || this.getSession(),
60
+ session: options?.session ?? this.getSession(),
61
61
  })) || 0);
62
62
  }
63
63
  /**
@@ -75,9 +75,10 @@ export class MongoEntityService extends MongoService {
75
75
  ]);
76
76
  const db = this.getDatabase();
77
77
  const collection = await this.getCollection(db);
78
+ const session = options?.session ?? this.getSession();
78
79
  return (await collection.deleteOne(filter || {}, {
79
80
  ...options,
80
- session: options?.session || this.getSession(),
81
+ session,
81
82
  })).deletedCount;
82
83
  }
83
84
  /**
@@ -93,7 +94,7 @@ export class MongoEntityService extends MongoService {
93
94
  const collection = await this.getCollection(db);
94
95
  return (await collection.deleteMany(filter || {}, {
95
96
  ...options,
96
- session: options?.session || this.getSession(),
97
+ session: options?.session ?? this.getSession(),
97
98
  })).deletedCount;
98
99
  }
99
100
  /**
@@ -109,7 +110,7 @@ export class MongoEntityService extends MongoService {
109
110
  const collection = await this.getCollection(db);
110
111
  return await collection.distinct(field, filter || {}, {
111
112
  ...options,
112
- session: options?.session || this.getSession(),
113
+ session: options?.session ?? this.getSession(),
113
114
  });
114
115
  }
115
116
  /**
@@ -128,7 +129,7 @@ export class MongoEntityService extends MongoService {
128
129
  const collection = await this.getCollection(db);
129
130
  const out = await collection.findOne(filter || {}, {
130
131
  ...omit(options, 'filter'),
131
- session: options?.session || this.getSession(),
132
+ session: options?.session ?? this.getSession(),
132
133
  projection: MongoAdapter.prepareProjection(this.dataType, options?.projection),
133
134
  limit: undefined,
134
135
  skip: undefined,
@@ -151,7 +152,7 @@ export class MongoEntityService extends MongoService {
151
152
  const collection = await this.getCollection(db);
152
153
  const out = await collection.findOne(filter || {}, {
153
154
  ...omit(options, 'filter'),
154
- session: options?.session || this.getSession(),
155
+ session: options?.session ?? this.getSession(),
155
156
  sort: options?.sort ? MongoAdapter.prepareSort(options.sort) : undefined,
156
157
  projection: MongoAdapter.prepareProjection(this.dataType, options?.projection),
157
158
  limit: undefined,
@@ -191,7 +192,7 @@ export class MongoEntityService extends MongoService {
191
192
  const collection = await this.getCollection(db);
192
193
  const cursor = collection.aggregate(stages, {
193
194
  ...omit(options, ['projection', 'sort', 'skip', 'limit', 'filter']),
194
- session: options?.session || this.getSession(),
195
+ session: options?.session ?? this.getSession(),
195
196
  });
196
197
  /** Execute db command */
197
198
  try {
@@ -249,7 +250,7 @@ export class MongoEntityService extends MongoService {
249
250
  const collection = await this.getCollection(db);
250
251
  const cursor = collection.aggregate(stages, {
251
252
  ...omit(options, ['projection', 'sort', 'skip', 'limit', 'filter']),
252
- session: options?.session || this.getSession(),
253
+ session: options?.session ?? this.getSession(),
253
254
  });
254
255
  /** Fetch the cursor and decode the result objects */
255
256
  try {
@@ -297,7 +298,7 @@ export class MongoEntityService extends MongoService {
297
298
  ...options,
298
299
  returnDocument: 'after',
299
300
  includeResultMetadata: false,
300
- session: options?.session || this.getSession(),
301
+ session: options?.session ?? this.getSession(),
301
302
  projection: MongoAdapter.prepareProjection(this.dataType, options?.projection),
302
303
  });
303
304
  const outputCodec = this._getOutputCodec('update');
@@ -335,7 +336,7 @@ export class MongoEntityService extends MongoService {
335
336
  const collection = await this.getCollection(db);
336
337
  return (await collection.updateOne(filter || {}, update, {
337
338
  ...options,
338
- session: options?.session || this.getSession(),
339
+ session: options?.session ?? this.getSession(),
339
340
  upsert: undefined,
340
341
  })).matchedCount;
341
342
  }
@@ -367,7 +368,7 @@ export class MongoEntityService extends MongoService {
367
368
  const collection = await this.getCollection(db);
368
369
  return (await collection.updateMany(filter || {}, update, {
369
370
  ...omit(options, 'filter'),
370
- session: options?.session || this.getSession(),
371
+ session: options?.session ?? this.getSession(),
371
372
  upsert: false,
372
373
  })).matchedCount;
373
374
  }
@@ -119,7 +119,7 @@ export class MongoNestedService extends MongoService {
119
119
  };
120
120
  const r = await collection.updateOne(docFilter, update, {
121
121
  ...options,
122
- session: options?.session || this.getSession(),
122
+ session: options?.session ?? this.getSession(),
123
123
  upsert: undefined,
124
124
  });
125
125
  if (!r.matchedCount) {
@@ -169,7 +169,7 @@ export class MongoNestedService extends MongoService {
169
169
  const collection = await this.getCollection(db);
170
170
  const cursor = collection.aggregate(stages, {
171
171
  ...omit(options, ['documentFilter', 'nestedFilter', 'projection', 'sort', 'skip', 'limit', 'filter', 'count']),
172
- session: options?.session || this.getSession(),
172
+ session: options?.session ?? this.getSession(),
173
173
  });
174
174
  try {
175
175
  const n = await cursor.next();
@@ -219,7 +219,7 @@ export class MongoNestedService extends MongoService {
219
219
  const collection = await this.getCollection(db);
220
220
  const r = await collection.updateOne(matchFilter, update, {
221
221
  ...options,
222
- session: options?.session || this.getSession(),
222
+ session: options?.session ?? this.getSession(),
223
223
  upsert: undefined,
224
224
  });
225
225
  return r.modifiedCount ? 1 : 0;
@@ -269,7 +269,7 @@ export class MongoNestedService extends MongoService {
269
269
  const collection = await this.getCollection(db);
270
270
  await collection.updateOne(matchFilter, update, {
271
271
  ...options,
272
- session: options?.session || this.getSession(),
272
+ session: options?.session ?? this.getSession(),
273
273
  upsert: undefined,
274
274
  });
275
275
  return matchCount;
@@ -443,7 +443,7 @@ export class MongoNestedService extends MongoService {
443
443
  const collection = await this.getCollection(db);
444
444
  const cursor = collection.aggregate(stages, {
445
445
  ...omit(options, ['documentFilter', 'nestedFilter', 'projection', 'sort', 'skip', 'limit', 'filter', 'count']),
446
- session: options?.session || this.getSession(),
446
+ session: options?.session ?? this.getSession(),
447
447
  });
448
448
  try {
449
449
  const outputCodec = this._getOutputCodec('find');
@@ -514,7 +514,7 @@ export class MongoNestedService extends MongoService {
514
514
  const collection = await this.getCollection(db);
515
515
  const cursor = collection.aggregate(stages, {
516
516
  ...omit(options, ['documentFilter', 'nestedFilter', 'projection', 'sort', 'skip', 'limit', 'filter', 'count']),
517
- session: options?.session || this.getSession(),
517
+ session: options?.session ?? this.getSession(),
518
518
  });
519
519
  try {
520
520
  const facetResult = await cursor.toArray();
@@ -673,7 +673,7 @@ export class MongoNestedService extends MongoService {
673
673
  const collection = await this.getCollection(db);
674
674
  await collection.updateOne(matchFilter, update, {
675
675
  ...options,
676
- session: options?.session || this.getSession(),
676
+ session: options?.session ?? this.getSession(),
677
677
  upsert: undefined,
678
678
  });
679
679
  return count;
@@ -2,6 +2,7 @@ import { DATATYPE_METADATA } from '@opra/common';
2
2
  import { ServiceBase } from '@opra/core';
3
3
  import { ObjectId } from 'mongodb';
4
4
  import { MongoAdapter } from './mongo-adapter.js';
5
+ const transactionKey = Symbol.for('transaction');
5
6
  /**
6
7
  * Class representing a MongoDB service for interacting with a collection.
7
8
  * @extends ServiceBase
@@ -92,38 +93,43 @@ export class MongoService extends ServiceBase {
92
93
  * @param [options] - Optional options for the transaction.
93
94
  */
94
95
  async withTransaction(callback, options) {
95
- let session = this.getSession();
96
- if (session)
97
- return callback(session);
98
- // Backup old session property
99
- const hasOldSession = Object.prototype.hasOwnProperty.call(this, 'session');
100
- const oldSessionGetter = hasOldSession ? this.session : undefined;
101
- const db = this.getDatabase();
102
- const client = db.client;
103
- session = client.startSession();
104
- this.session = session;
96
+ const ctx = this.context;
97
+ let closeSessionOnFinish = false;
98
+ let transaction = ctx[transactionKey];
99
+ let session;
100
+ if (transaction) {
101
+ session = transaction.session;
102
+ }
103
+ else {
104
+ const db = this.getDatabase();
105
+ const client = db.client;
106
+ session = client.startSession();
107
+ closeSessionOnFinish = true;
108
+ transaction = {
109
+ db,
110
+ session,
111
+ };
112
+ ctx[transactionKey] = transaction;
113
+ }
105
114
  const oldInTransaction = session.inTransaction();
106
115
  try {
107
116
  if (!oldInTransaction)
108
117
  session.startTransaction(options);
109
- const out = await callback(session);
110
- if (!oldInTransaction)
118
+ const out = await callback(session, this);
119
+ if (!oldInTransaction && session.inTransaction())
111
120
  await session.commitTransaction();
112
121
  return out;
113
122
  }
114
123
  catch (e) {
115
- if (!oldInTransaction)
124
+ if (!oldInTransaction && session.inTransaction())
116
125
  await session.abortTransaction();
117
126
  throw e;
118
127
  }
119
128
  finally {
120
- // Restore old session property
121
- if (hasOldSession)
122
- this.session = oldSessionGetter;
123
- else
124
- delete this.session;
125
- if (!oldInTransaction)
129
+ delete ctx[transactionKey];
130
+ if (closeSessionOnFinish) {
126
131
  await session.endSession();
132
+ }
127
133
  }
128
134
  }
129
135
  /**
@@ -134,10 +140,14 @@ export class MongoService extends ServiceBase {
134
140
  * @throws {Error} If the context or database is not set.
135
141
  */
136
142
  getDatabase() {
143
+ const ctx = this.context;
144
+ const transaction = ctx[transactionKey];
145
+ if (transaction)
146
+ return transaction.db;
137
147
  const db = typeof this.db === 'function' ? this.db(this) : this.db;
138
- if (!db)
139
- throw new Error(`Database not set!`);
140
- return db;
148
+ if (db)
149
+ return db;
150
+ throw new Error(`Database not set!`);
141
151
  }
142
152
  /**
143
153
  * Retrieves the database session.
@@ -147,7 +157,13 @@ export class MongoService extends ServiceBase {
147
157
  * @throws {Error} If the context or database is not set.
148
158
  */
149
159
  getSession() {
150
- return typeof this.session === 'function' ? this.session(this) : this.session;
160
+ const ctx = this.context;
161
+ const transaction = ctx[transactionKey];
162
+ if (transaction)
163
+ return transaction.session;
164
+ const session = typeof this.session === 'function' ? this.session(this) : this.session;
165
+ if (session)
166
+ return session;
151
167
  }
152
168
  /**
153
169
  * Retrieves a MongoDB collection from the given database.
@@ -169,7 +169,7 @@ export class MongoSingletonService extends MongoEntityService {
169
169
  });
170
170
  }
171
171
  /**
172
- * Updates a document in the MongoDB collection.
172
+ * Updates a document in the MongoDB collection
173
173
  *
174
174
  * @param {PatchDTO<T>} input - The partial input to update the document.
175
175
  * @param {MongoEntityService.UpdateOneOptions<T>} [options] - The update options.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opra/mongodb",
3
- "version": "1.0.0-beta.2",
3
+ "version": "1.0.0-beta.4",
4
4
  "description": "Opra MongoDB adapter package",
5
5
  "author": "Panates",
6
6
  "license": "MIT",
@@ -8,11 +8,12 @@
8
8
  "lodash.omit": "^4.5.0",
9
9
  "putil-isplainobject": "^1.1.5",
10
10
  "tslib": "^2.7.0",
11
- "valgen": "^5.9.0"
11
+ "valgen": "^5.10.0"
12
12
  },
13
13
  "peerDependencies": {
14
- "@opra/common": "^1.0.0-beta.2",
15
- "@opra/core": "^1.0.0-beta.2",
14
+ "@opra/common": "^1.0.0-beta.4",
15
+ "@opra/core": "^1.0.0-beta.4",
16
+ "@opra/http": "^1.0.0-beta.4",
16
17
  "mongodb": ">= 6.0.0"
17
18
  },
18
19
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  import { OpraFilter } from '@opra/common';
2
- import { HttpContext } from '@opra/core';
3
- import mongodb, { ClientSession, ObjectId } from 'mongodb';
2
+ import type { ExecutionContext } from '@opra/core';
3
+ import mongodb, { ObjectId } from 'mongodb';
4
4
  import _prepareFilter from './adapter-utils/prepare-filter.js';
5
5
  import _prepareKeyValues from './adapter-utils/prepare-key-values.js';
6
6
  import _preparePatch from './adapter-utils/prepare-patch.js';
@@ -9,7 +9,6 @@ import _prepareSort from './adapter-utils/prepare-sort.js';
9
9
  export declare namespace MongoAdapter {
10
10
  type AnyId = string | number | ObjectId;
11
11
  type FilterInput<T = any> = OpraFilter.Expression | mongodb.Filter<T> | string | undefined;
12
- type WithTransactionCallback = (session: ClientSession) => any;
13
12
  const prepareFilter: typeof _prepareFilter;
14
13
  const prepareKeyValues: typeof _prepareKeyValues;
15
14
  const preparePatch: typeof _preparePatch;
@@ -21,5 +20,5 @@ export declare namespace MongoAdapter {
21
20
  data?: any;
22
21
  options: any;
23
22
  }
24
- function parseRequest(context: HttpContext): Promise<TransformedRequest>;
23
+ function parseRequest(context: ExecutionContext): Promise<TransformedRequest>;
25
24
  }
@@ -1,6 +1,6 @@
1
1
  import { ComplexType } from '@opra/common';
2
- import { HttpContext, ServiceBase } from '@opra/core';
3
- import mongodb, { type Document, type TransactionOptions } from 'mongodb';
2
+ import { ExecutionContext, ServiceBase } from '@opra/core';
3
+ import mongodb, { ClientSession, type Document, type TransactionOptions } from 'mongodb';
4
4
  import type { Nullish, StrictOmit, Type } from 'ts-gems';
5
5
  import type { IsObject } from 'valgen';
6
6
  import { MongoAdapter } from './mongo-adapter.js';
@@ -188,7 +188,7 @@ export declare class MongoService<T extends mongodb.Document = mongodb.Document>
188
188
  * @constructor
189
189
  */
190
190
  constructor(dataType: Type | string, options?: MongoService.Options);
191
- for<C extends HttpContext, P extends Partial<this>>(context: C, overwriteProperties?: Nullish<P>, overwriteContext?: Partial<C>): this & Required<P>;
191
+ for<C extends ExecutionContext, P extends Partial<this>>(context: C | ServiceBase, overwriteProperties?: Nullish<P>, overwriteContext?: Partial<C>): this & Required<P>;
192
192
  /**
193
193
  * Retrieves the collection name.
194
194
  *
@@ -217,7 +217,7 @@ export declare class MongoService<T extends mongodb.Document = mongodb.Document>
217
217
  * @param callback - The function to be executed within the transaction.
218
218
  * @param [options] - Optional options for the transaction.
219
219
  */
220
- withTransaction(callback: MongoAdapter.WithTransactionCallback, options?: TransactionOptions): Promise<any>;
220
+ withTransaction(callback: (session: ClientSession, _this: this) => any, options?: TransactionOptions): Promise<any>;
221
221
  /**
222
222
  * Retrieves the database connection.
223
223
  *
@@ -91,12 +91,12 @@ export declare class MongoSingletonService<T extends mongodb.Document> extends M
91
91
  *
92
92
  * @param {MongoEntityService.FindOneOptions<T>} options - The options to customize the query.
93
93
  * @return {Promise<PartialDTO<T>>} - A promise that resolves to the fetched document.
94
- * @throws {ResourceNotAvailableError} - If the document is not found in the collection.
94
+ * @throws {ResourceNotAvailableError} - If the document is not found in the collection
95
95
  */
96
96
  get(options: RequiredSome<MongoEntityService.FindOneOptions<T>, 'projection'>): Promise<PartialDTO<T>>;
97
97
  get(options?: MongoEntityService.FindOneOptions<T>): Promise<T>;
98
98
  /**
99
- * Updates a document in the MongoDB collection.
99
+ * Updates a document in the MongoDB collection
100
100
  *
101
101
  * @param {PatchDTO<T>} input - The partial input to update the document.
102
102
  * @param {MongoEntityService.UpdateOneOptions<T>} [options] - The update options.
@@ -106,7 +106,7 @@ export declare class MongoSingletonService<T extends mongodb.Document> extends M
106
106
  update(input: PatchDTO<T> | UpdateFilter<T>, options: RequiredSome<MongoEntityService.UpdateOneOptions<T>, 'projection'>): Promise<PartialDTO<T> | undefined>;
107
107
  update(input: PatchDTO<T> | UpdateFilter<T>, options?: MongoEntityService.UpdateOneOptions<T>): Promise<T | undefined>;
108
108
  /**
109
- * Updates a document in the MongoDB collection.
109
+ * Updates a document in the MongoDB collection
110
110
  *
111
111
  * @param {PatchDTO<T>} input - The partial input to update the document.
112
112
  * @param {MongoEntityService.UpdateOneOptions<T>} [options] - The update options.