From 55af032065cc94c2cb5e846fc934dac6ed0e45a3 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tumanov Date: Wed, 10 Jul 2024 16:14:30 +0500 Subject: [PATCH] prevent double apply of transactions (#5990) Signed-off-by: Vyacheslav Tumanov --- packages/core/src/client.ts | 11 ++++++-- packages/query/src/__tests__/query.test.ts | 31 +++++++++++++--------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index 2c68ac8f36..b2615e7cd6 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -96,6 +96,7 @@ class ClientImpl implements AccountClient, BackupClient { notify?: (...tx: Tx[]) => void hierarchy!: Hierarchy model!: ModelDb + private readonly appliedModelTransactions = new Set>() constructor (private readonly conn: ClientConnection) {} setModel (hierarchy: Hierarchy, model: ModelDb): void { @@ -147,6 +148,7 @@ class ClientImpl implements AccountClient, BackupClient { if (tx.objectSpace === core.space.Model) { this.hierarchy.tx(tx) await this.model.tx(tx) + this.appliedModelTransactions.add(tx._id) } // We need to handle it on server, before performing local live query updates. return await this.conn.tx(tx) @@ -156,8 +158,13 @@ class ClientImpl implements AccountClient, BackupClient { for (const t of tx) { try { if (t.objectSpace === core.space.Model) { - this.hierarchy.tx(t) - await this.model.tx(t) + const hasTx = this.appliedModelTransactions.has(t._id) + if (!hasTx) { + this.hierarchy.tx(t) + await this.model.tx(t) + } else { + this.appliedModelTransactions.delete(t._id) + } } } catch (err) { console.error('failed to apply model transaction, skipping', t) diff --git a/packages/query/src/__tests__/query.test.ts b/packages/query/src/__tests__/query.test.ts index 1755c0a0f0..b42e4a845c 100644 --- a/packages/query/src/__tests__/query.test.ts +++ b/packages/query/src/__tests__/query.test.ts @@ -281,13 +281,17 @@ describe('query', () => { const expectedLength = 2 let attempt = 0 + let x: undefined | ((s: any) => void) + const y = new Promise((resolve) => (x = resolve)) + const pp = new Promise((resolve) => { liveQuery.query(core.class.Space, { private: false }, (result) => { expect(result).toHaveLength(expectedLength - attempt) + if (attempt === 0) x?.(null) if (attempt++ === expectedLength) resolve(null) }) }) - + await y const spaces = await liveQuery.findAll(core.class.Space, {}) for (const space of spaces) { await factory.removeDoc(space._class, space.space, space._id) @@ -298,13 +302,16 @@ describe('query', () => { it('remove with limit', async () => { const { liveQuery, factory } = await getClient() - const expectedLength = 1 + const expectedLength = 2 let attempt = 0 + let x: undefined | ((s: any) => void) + const y = new Promise((resolve) => (x = resolve)) const pp = new Promise((resolve) => { liveQuery.query( core.class.Space, { private: false }, (result) => { + if (attempt === 0) x?.(null) expect(result).toHaveLength(attempt++ === expectedLength ? 0 : 1) if (attempt === expectedLength) resolve(null) }, @@ -312,6 +319,7 @@ describe('query', () => { ) }) + await y const spaces = await liveQuery.findAll(core.class.Space, {}) for (const space of spaces) { await factory.removeDoc(space._class, space.space, space._id) @@ -415,6 +423,14 @@ describe('query', () => { } else { expect(comment.$lookup?.space).toBeUndefined() attempt++ + void factory.createDoc( + core.class.Space, + futureSpace.space, + { + ...futureSpace + }, + futureSpace._id + ) } } }, @@ -422,14 +438,6 @@ describe('query', () => { ) }) - await factory.createDoc( - core.class.Space, - futureSpace.space, - { - ...futureSpace - }, - futureSpace._id - ) await pp }) @@ -580,6 +588,7 @@ describe('query', () => { } else { expect((comment.$lookup?.space as Doc)?._id).toEqual(futureSpace) attempt++ + void factory.removeDoc(core.class.Space, core.space.Model, futureSpace) } } }, @@ -587,8 +596,6 @@ describe('query', () => { ) }) - await factory.removeDoc(core.class.Space, core.space.Model, futureSpace) - await pp })