From 4bbada59ce3d84439b1e8ce10b913c86d3345081 Mon Sep 17 00:00:00 2001 From: Nicola Marogna Date: Mon, 22 Jun 2026 12:25:36 +0200 Subject: [PATCH] fix: pre-check reads now inherit options.session; read-your-own-write in transaction works. --- .../__tests__/findOneAndUpdate.test.ts | 21 +++++++++++++ .../src/services/mongodb-atlas/index.ts | 31 +++++++++++++------ 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/packages/flowerbase/src/services/mongodb-atlas/__tests__/findOneAndUpdate.test.ts b/packages/flowerbase/src/services/mongodb-atlas/__tests__/findOneAndUpdate.test.ts index bc001ddc..b5c5948e 100644 --- a/packages/flowerbase/src/services/mongodb-atlas/__tests__/findOneAndUpdate.test.ts +++ b/packages/flowerbase/src/services/mongodb-atlas/__tests__/findOneAndUpdate.test.ts @@ -67,6 +67,27 @@ describe('mongodb-atlas findOneAndUpdate', () => { expect(result).toEqual(updatedDoc) }) + it('passes the transaction session to the permission pre-check read', async () => { + const id = new ObjectId() + const session = { id: 'txn-session' } as any + const existingDoc = { _id: id, title: 'Old', userId: 'user-1' } + const findOne = jest.fn().mockResolvedValue(existingDoc) + const findOneAndUpdate = jest.fn().mockResolvedValue(existingDoc) + const collection = { collectionName: 'todos', findOne, findOneAndUpdate } + + const app = createAppWithCollection(collection) + const operators = MongoDbAtlas(app as any, { + rules: createRules(), + user: { id: 'user-1' } + }) + .db('db') + .collection('todos') + + await operators.findOneAndUpdate({ _id: id }, { $set: { title: 'New' } }, { session }) + + expect(findOne).toHaveBeenCalledWith({ $and: [{ _id: id }] }, { session }) + }) + it('rejects updates when write permission is denied', async () => { const id = new ObjectId() const existingDoc = { _id: id, title: 'Old', userId: 'user-1' } diff --git a/packages/flowerbase/src/services/mongodb-atlas/index.ts b/packages/flowerbase/src/services/mongodb-atlas/index.ts index 5ee71422..abc71bdf 100644 --- a/packages/flowerbase/src/services/mongodb-atlas/index.ts +++ b/packages/flowerbase/src/services/mongodb-atlas/index.ts @@ -881,7 +881,11 @@ const getOperators: GetOperatorsFunction = ( ? normalizeQuery(formattedQuery) : formattedQuery - const result = await collection.findOne(buildAndQuery(safeQuery)) + // Read inside the active transaction so the pre-check sees writes + // made earlier in the same session (read-your-own-write). + const result = await collection.findOne(buildAndQuery(safeQuery), { + session: options?.session + }) if (!result) { if (options?.upsert) { @@ -970,7 +974,10 @@ const getOperators: GetOperatorsFunction = ( const normalizedData = Array.isArray(data) ? data : normalizeUpdatePayload(data as Document) - const currentDoc = await collection.findOne(buildAndQuery(safeQuery)) + // Read inside the active transaction (read-your-own-write). + const currentDoc = await collection.findOne(buildAndQuery(safeQuery), { + session: options?.session + }) const updatedPaths = Array.isArray(normalizedData) ? [] : getUpdatedPaths(normalizedData as Document) @@ -991,11 +998,14 @@ const getOperators: GetOperatorsFunction = ( } else { const [computedDoc] = Array.isArray(normalizedData) ? await collection - .aggregate([ - { $match: buildAndQuery(safeQuery) }, - { $limit: 1 }, - ...normalizedData - ]) + .aggregate( + [ + { $match: buildAndQuery(safeQuery) }, + { $limit: 1 }, + ...normalizedData + ], + { session: options?.session } + ) .toArray() : [applyDocumentUpdateOperators(currentDoc, normalizedData as Document)] docToCheck = computedDoc @@ -1557,8 +1567,11 @@ const getOperators: GetOperatorsFunction = ( // Apply access control filters const formattedQuery = getFormattedQuery(filters, query, user) - // Retrieve the document to check permissions before updating - const result = await collection.find({ $and: formattedQuery }).toArray() + // Retrieve the document to check permissions before updating. + // Read inside the active transaction (read-your-own-write). + const result = await collection + .find({ $and: formattedQuery }, { session: options?.session }) + .toArray() if (!result) { throw new Error('Update not permitted') }