diff --git a/etc/notes/CHANGES_5.0.0.md b/etc/notes/CHANGES_5.0.0.md index 048e2bbef02..66462b02173 100644 --- a/etc/notes/CHANGES_5.0.0.md +++ b/etc/notes/CHANGES_5.0.0.md @@ -191,3 +191,17 @@ await collection.insertMany([{ name: 'fido' }, { name: 'luna' }]) The `keepGoing` option was a legacy name for setting `ordered` to `false` for bulk inserts. It was only supported by the legacy `collection.insert()` method which is now removed as noted above. + +### `withSession` and `withTransaction` now return the result of the provided callback. + +These two methods previously returned `Promise` but now users can control what the return value +in the promise is: + +```ts +const value = await client.withSession(async (session) => { + return session.withTransaction(async () => { + await collection.insertOne({ a: 1 }); + return true; + }); +}); // value is the boolean true; +``` \ No newline at end of file diff --git a/src/mongo_client.ts b/src/mongo_client.ts index 63b46cf9ecb..c563d85a54a 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -273,7 +273,7 @@ export interface MongoClientOptions extends BSONSerializeOptions, SupportedNodeC } /** @public */ -export type WithSessionCallback = (session: ClientSession) => Promise; +export type WithSessionCallback = (session: ClientSession) => Promise; /** @internal */ export interface MongoClientPrivate { @@ -643,12 +643,12 @@ export class MongoClient extends TypedEventEmitter { * @param options - Optional settings for the command * @param callback - An callback to execute with an implicitly created session */ - withSession(callback: WithSessionCallback): Promise; - withSession(options: ClientSessionOptions, callback: WithSessionCallback): Promise; - withSession( - optionsOrOperation?: ClientSessionOptions | WithSessionCallback, - callback?: WithSessionCallback - ): Promise { + withSession(callback: WithSessionCallback): Promise; + withSession(options: ClientSessionOptions, callback: WithSessionCallback): Promise; + withSession( + optionsOrOperation?: ClientSessionOptions | WithSessionCallback, + callback?: WithSessionCallback + ): Promise { const options = { // Always define an owner owner: Symbol(), @@ -666,8 +666,9 @@ export class MongoClient extends TypedEventEmitter { const session = this.startSession(options); return maybeCallback(async () => { + let value; try { - await withSessionCallback(session); + value = await withSessionCallback(session); } finally { try { await session.endSession(); @@ -675,6 +676,7 @@ export class MongoClient extends TypedEventEmitter { // We are not concerned with errors from endSession() } } + return value; }, null); } diff --git a/src/sessions.ts b/src/sessions.ts index 1774f7c7806..ff871da6d4c 100644 --- a/src/sessions.ts +++ b/src/sessions.ts @@ -61,7 +61,7 @@ export interface ClientSessionOptions { } /** @public */ -export type WithTransactionCallback = (session: ClientSession) => Promise; +export type WithTransactionCallback = (session: ClientSession) => Promise; /** @public */ export type ClientSessionEvents = { @@ -470,10 +470,7 @@ export class ClientSession extends TypedEventEmitter { * @param options - optional settings for the transaction * @returns A raw command response or undefined */ - withTransaction( - fn: WithTransactionCallback, - options?: TransactionOptions - ): Promise { + withTransaction(fn: WithTransactionCallback, options?: TransactionOptions): Promise { const startTime = now(); return attemptTransaction(this, startTime, fn, options); } @@ -615,12 +612,14 @@ function attemptTransaction( } return promise.then( - () => { + value => { if (userExplicitlyEndedTransaction(session)) { - return; + return value; } - return attemptTransactionCommit(session, startTime, fn, options); + return attemptTransactionCommit(session, startTime, fn, options).then(() => { + return value; + }); }, err => { function maybeRetryOrThrow(err: MongoError): Promise { diff --git a/test/integration/transactions/transactions.test.ts b/test/integration/transactions/transactions.test.ts index 70f6db68b08..1614adafd0b 100644 --- a/test/integration/transactions/transactions.test.ts +++ b/test/integration/transactions/transactions.test.ts @@ -90,19 +90,19 @@ describe('Transactions', function () { expect(withTransactionResult).to.be.undefined; }); - it('should return raw command when transaction is successfully committed', async () => { + it('should return callback return value when transaction is successfully committed', async () => { const session = client.startSession(); const withTransactionResult = await session .withTransaction(async session => { await collection.insertOne({ a: 1 }, { session }); - await collection.findOne({ a: 1 }, { session }); + return await collection.findOne({ a: 1 }, { session }); }) .finally(async () => await session.endSession()); expect(withTransactionResult).to.exist; expect(withTransactionResult).to.be.an('object'); - expect(withTransactionResult).to.have.property('ok', 1); + expect(withTransactionResult).to.have.property('a', 1); }); it('should throw when transaction is aborted due to an error', async () => { diff --git a/test/unit/mongo_client.test.js b/test/unit/mongo_client.test.js index 9256a761e67..28e0bc233f5 100644 --- a/test/unit/mongo_client.test.js +++ b/test/unit/mongo_client.test.js @@ -873,3 +873,25 @@ describe('MongoOptions', function () { }); }); }); + +describe('MongoClient', function () { + describe('#withSession', function () { + const client = new MongoClient('mongodb://localhost:27017'); + + context('when the callback returns a value', function () { + it('returns the value in the promise', async function () { + const value = await client.withSession(async () => { + return 'test'; + }); + expect(value).to.equal('test'); + }); + }); + + context('when the callback does not return a value', function () { + it('does not return a value in the promise', async function () { + const value = await client.withSession(async () => {}); + expect(value).to.equal(undefined); + }); + }); + }); +}); diff --git a/test/unit/sessions.test.js b/test/unit/sessions.test.js index f1a84342d9c..23e81529be5 100644 --- a/test/unit/sessions.test.js +++ b/test/unit/sessions.test.js @@ -32,6 +32,24 @@ describe('Sessions - unit', function () { }); }); + describe('#withTransaction', function () { + context('when the callback returns a value', function () { + it('returns the value in the promise', async function () { + const value = await session.withTransaction(async () => { + return 'test'; + }); + expect(value).to.equal('test'); + }); + }); + + context('when the callback does not return a value', function () { + it('does not return a value in the promise', async function () { + const value = await session.withTransaction(async () => {}); + expect(value).to.equal(undefined); + }); + }); + }); + describe('advanceClusterTime()', () => { it('should throw an error if the input cluster time is not an object', function () { const invalidInputs = [undefined, null, 3, 'a'];