From aa65c910bff9d8f5ef0111d36d51a455363bc9d8 Mon Sep 17 00:00:00 2001 From: Nikita Sivukhin Date: Mon, 15 Sep 2025 10:55:01 +0400 Subject: [PATCH] fix sync-browser bug and add more tests --- .../sync/packages/browser/promise.test.ts | 55 +++++++++++- .../sync/packages/browser/promise.ts | 4 +- .../sync/packages/native/promise.test.ts | 84 +++++++++++++++++-- .../sync/packages/native/promise.ts | 4 +- 4 files changed, 135 insertions(+), 12 deletions(-) diff --git a/bindings/javascript/sync/packages/browser/promise.test.ts b/bindings/javascript/sync/packages/browser/promise.test.ts index 152a7841a..bdced08fa 100644 --- a/bindings/javascript/sync/packages/browser/promise.test.ts +++ b/bindings/javascript/sync/packages/browser/promise.test.ts @@ -160,7 +160,7 @@ test('checkpoint', async () => { expect((await db1.stats()).revertWal).toBe(revertWal); }) -test('persistence', async () => { +test('persistence-push', async () => { { const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); @@ -203,6 +203,59 @@ test('persistence', async () => { } }) +test('persistence-offline', async () => { + { + const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); + await db.exec("DELETE FROM q"); + await db.push(); + await db.close(); + } + const path = `test-${(Math.random() * 10000) | 0}.db`; + { + const db = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL }); + await db.exec(`INSERT INTO q VALUES ('k1', 'v1')`); + await db.exec(`INSERT INTO q VALUES ('k2', 'v2')`); + await db.push(); + await db.close(); + } + { + const db = await connect({ path: path, url: "https://not-valid-url.localhost" }); + const rows = await db.prepare("SELECT * FROM q").all(); + const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }]; + expect(rows.sort(localeCompare)).toEqual(expected.sort(localeCompare)) + await db.close(); + } +}) + +test('persistence-pull-push', async () => { + { + const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); + await db.exec("DELETE FROM q"); + await db.push(); + await db.close(); + } + const path1 = `test-${(Math.random() * 10000) | 0}.db`; + const path2 = `test-${(Math.random() * 10000) | 0}.db`; + const db1 = await connect({ path: path1, url: process.env.VITE_TURSO_DB_URL }); + await db1.exec(`INSERT INTO q VALUES ('k1', 'v1')`); + await db1.exec(`INSERT INTO q VALUES ('k2', 'v2')`); + + const db2 = await connect({ path: path2, url: process.env.VITE_TURSO_DB_URL }); + await db2.exec(`INSERT INTO q VALUES ('k3', 'v3')`); + await db2.exec(`INSERT INTO q VALUES ('k4', 'v4')`); + + await Promise.all([db1.push(), db2.push()]); + await Promise.all([db1.pull(), db2.pull()]); + + const rows1 = await db1.prepare('SELECT * FROM q').all(); + const rows2 = await db2.prepare('SELECT * FROM q').all(); + const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }, { x: 'k3', y: 'v3' }, { x: 'k4', y: 'v4' }]; + expect(rows1.sort(localeCompare)).toEqual(expected.sort(localeCompare)) + expect(rows2.sort(localeCompare)).toEqual(expected.sort(localeCompare)) +}) + test('transform', async () => { { const db = await connect({ diff --git a/bindings/javascript/sync/packages/browser/promise.ts b/bindings/javascript/sync/packages/browser/promise.ts index f6198a3fd..45d1d75c6 100644 --- a/bindings/javascript/sync/packages/browser/promise.ts +++ b/bindings/javascript/sync/packages/browser/promise.ts @@ -54,7 +54,7 @@ class Database extends DatabasePromise { await Promise.all([ unregisterFileAtWorker(this.worker, this.fsPath), unregisterFileAtWorker(this.worker, `${this.fsPath}-wal`), - unregisterFileAtWorker(this.worker, `${this.fsPath}-revert`), + unregisterFileAtWorker(this.worker, `${this.fsPath}-wal-revert`), unregisterFileAtWorker(this.worker, `${this.fsPath}-info`), unregisterFileAtWorker(this.worker, `${this.fsPath}-changes`), ]); @@ -95,7 +95,7 @@ async function connect(opts: SyncOpts, connect: (any) => any, init: () => Promis await Promise.all([ registerFileAtWorker(worker, opts.path), registerFileAtWorker(worker, `${opts.path}-wal`), - registerFileAtWorker(worker, `${opts.path}-revert`), + registerFileAtWorker(worker, `${opts.path}-wal-revert`), registerFileAtWorker(worker, `${opts.path}-info`), registerFileAtWorker(worker, `${opts.path}-changes`), ]); diff --git a/bindings/javascript/sync/packages/native/promise.test.ts b/bindings/javascript/sync/packages/native/promise.test.ts index ec8381190..8b30a72af 100644 --- a/bindings/javascript/sync/packages/native/promise.test.ts +++ b/bindings/javascript/sync/packages/native/promise.test.ts @@ -4,6 +4,14 @@ import { connect, DatabaseRowMutation, DatabaseRowTransformResult } from './prom const localeCompare = (a, b) => a.x.localeCompare(b.x); +function cleanup(path) { + unlinkSync(path); + unlinkSync(`${path}-wal`); + unlinkSync(`${path}-info`); + unlinkSync(`${path}-changes`); + try { unlinkSync(`${path}-wal-revert`) } catch (e) { } +} + test('select-after-push', async () => { { const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); @@ -161,7 +169,8 @@ test('checkpoint', async () => { expect((await db1.stats()).revertWal).toBe(revertWal); }) -test('persistence', async () => { + +test('persistence-push', async () => { { const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); @@ -182,9 +191,11 @@ test('persistence', async () => { const db2 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL }); await db2.exec(`INSERT INTO q VALUES ('k3', 'v3')`); await db2.exec(`INSERT INTO q VALUES ('k4', 'v4')`); - const rows = await db2.prepare('SELECT * FROM q').all(); + const stmt = db2.prepare('SELECT * FROM q'); + const rows = await stmt.all(); const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }, { x: 'k3', y: 'v3' }, { x: 'k4', y: 'v4' }]; expect(rows).toEqual(expected) + stmt.close(); await db2.close(); } @@ -201,12 +212,71 @@ test('persistence', async () => { expect(rows).toEqual(expected) await db4.close(); } + } + finally { + cleanup(path); + } +}) + +test('persistence-offline', async () => { + { + const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); + await db.exec("DELETE FROM q"); + await db.push(); + await db.close(); + } + const path = `test-${(Math.random() * 10000) | 0}.db`; + try { + { + const db = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL }); + await db.exec(`INSERT INTO q VALUES ('k1', 'v1')`); + await db.exec(`INSERT INTO q VALUES ('k2', 'v2')`); + await db.push(); + await db.close(); + } + { + const db = await connect({ path: path, url: "https://not-valid-url.localhost" }); + const rows = await db.prepare("SELECT * FROM q").all(); + const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }]; + expect(rows.sort(localeCompare)).toEqual(expected.sort(localeCompare)) + await db.close(); + } } finally { - unlinkSync(path); - unlinkSync(`${path}-wal`); - unlinkSync(`${path}-info`); - unlinkSync(`${path}-changes`); - try { unlinkSync(`${path}-revert`) } catch (e) { } + cleanup(path); + } +}) + +test('persistence-pull-push', async () => { + { + const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); + await db.exec("DELETE FROM q"); + await db.push(); + await db.close(); + } + const path1 = `test-${(Math.random() * 10000) | 0}.db`; + const path2 = `test-${(Math.random() * 10000) | 0}.db`; + try { + const db1 = await connect({ path: path1, url: process.env.VITE_TURSO_DB_URL }); + await db1.exec(`INSERT INTO q VALUES ('k1', 'v1')`); + await db1.exec(`INSERT INTO q VALUES ('k2', 'v2')`); + + const db2 = await connect({ path: path2, url: process.env.VITE_TURSO_DB_URL }); + await db2.exec(`INSERT INTO q VALUES ('k3', 'v3')`); + await db2.exec(`INSERT INTO q VALUES ('k4', 'v4')`); + + await Promise.all([db1.push(), db2.push()]); + await Promise.all([db1.pull(), db2.pull()]); + + const rows1 = await db1.prepare('SELECT * FROM q').all(); + const rows2 = await db2.prepare('SELECT * FROM q').all(); + const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }, { x: 'k3', y: 'v3' }, { x: 'k4', y: 'v4' }]; + expect(rows1.sort(localeCompare)).toEqual(expected.sort(localeCompare)) + expect(rows2.sort(localeCompare)).toEqual(expected.sort(localeCompare)) + } finally { + cleanup(path1); + cleanup(path2); } }) diff --git a/bindings/javascript/sync/packages/native/promise.ts b/bindings/javascript/sync/packages/native/promise.ts index 86f020109..3d473c8a9 100644 --- a/bindings/javascript/sync/packages/native/promise.ts +++ b/bindings/javascript/sync/packages/native/promise.ts @@ -1,5 +1,5 @@ import { DatabasePromise, DatabaseOpts, NativeDatabase } from "@tursodatabase/database-common" -import { ProtocolIo, run, SyncOpts, RunOpts, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult } from "@tursodatabase/sync-common"; +import { ProtocolIo, run, SyncOpts, RunOpts, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult, SyncEngineStats } from "@tursodatabase/sync-common"; import { Database as NativeDB, SyncEngine } from "#index"; import { promises } from "node:fs"; @@ -61,7 +61,7 @@ class Database extends DatabasePromise { async checkpoint() { await run(this.runOpts, this.io, this.engine, this.engine.checkpoint()); } - async stats(): Promise<{ operations: number, mainWal: number, revertWal: number, lastPullUnixTime: number, lastPushUnixTime: number | null }> { + async stats(): Promise { return (await run(this.runOpts, this.io, this.engine, this.engine.stats())); } override async close(): Promise {