Automattic / mongoose

MongoDB object modeling designed to work in an asynchronous environment.
https://mongoosejs.com
MIT License
26.93k stars 3.84k forks source link

transactionAsyncLocalStorage not honored in bulk operations like insertMany #14738

Closed mlh758 closed 3 months ago

mlh758 commented 3 months ago

Prerequisites

Mongoose version

8.4.0

Node.js version

20.15.0

MongoDB server version

6

Typescript version (if applicable)

No response

Description

The new transactionAsyncLocalStorage mechanism seems to work great for individual operations like save and create on models. It doesn't seem to be honored on bulk operations such as insertMany.

Steps to Reproduce

Forgive the slightly convoluted code here, we have a wrapper over mongoose models with some singleton logic for working in AWS Lambda.

This test passes as I'd expect:

it.only('supports transactions without explicitly passing the session object', async () => {
  mongoose.set('transactionAsyncLocalStorage', true);
  await ChildDao.instance.find({ filter: {}, context }); // just forcing a preflight to get a connection, ignore
  await ChildDao.instance.model.db
    .transaction(async () => {
      await ChildDao.instance.model.create({ name: 'test' });
      throw new Error('Rollback');
    })
    .catch(() => {});

  expect(await ChildDao.instance.model.findOne({ name: 'test' })).toBeNull();
});

This test fails:

it.only('supports transactions without explicitly passing the session object', async () => {
  mongoose.set('transactionAsyncLocalStorage', true);
  await ChildDao.instance.find({ filter: {}, context });
  await ChildDao.instance.model.db
    .transaction(async () => {
      await ChildDao.instance.model.insertMany([{ name: 'test' }]);
      throw new Error('Rollback');
    })
    .catch(() => {});

  expect(await ChildDao.instance.model.findOne({ name: 'test' })).toBeNull();
});

This test passes, explicitly passing the session:

it.only('supports transactions on bulk operations if explicitly passed a session', async () => {
  mongoose.set('transactionAsyncLocalStorage', true);
  await ChildDao.instance.find({ filter: {}, context });
  await ChildDao.instance.model.db
    .transaction(async (session) => {
      await ChildDao.instance.model.insertMany([{ name: 'test' }], { session });
      throw new Error('Rollback');
    })
    .catch(() => {});

  expect(await ChildDao.instance.model.findOne({ name: 'test' })).toBeNull();
});

Expected Behavior

The transactionAsyncLocalStorage should allow the more ergonomic flow for bulk operations as well. This might fall under a feature request, but without the caveats explained in the docs I'd be inclined to call it a bug.

mlh758 commented 3 months ago

I just came back to check on this since I didn't get any notifications. Thanks for the fast fix!