@tursodatabase/sync 0.1.5 → 0.2.0-pre.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -18
- package/dist/promise.d.ts +43 -0
- package/dist/promise.d.ts.map +1 -0
- package/dist/promise.js +137 -0
- package/dist/promise.test.d.ts +2 -0
- package/dist/promise.test.d.ts.map +1 -0
- package/dist/promise.test.js +454 -0
- package/index.js +166 -50
- package/package.json +37 -48
- package/browser.js +0 -1
- package/dist/sync_engine.d.ts +0 -24
- package/dist/sync_engine.js +0 -152
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
import { unlinkSync } from "node:fs";
|
|
2
|
+
import { expect, test } from 'vitest';
|
|
3
|
+
import { connect, Database } from './promise.js';
|
|
4
|
+
const localeCompare = (a, b) => a.x.localeCompare(b.x);
|
|
5
|
+
function cleanup(path) {
|
|
6
|
+
unlinkSync(path);
|
|
7
|
+
unlinkSync(`${path}-wal`);
|
|
8
|
+
unlinkSync(`${path}-info`);
|
|
9
|
+
unlinkSync(`${path}-changes`);
|
|
10
|
+
try {
|
|
11
|
+
unlinkSync(`${path}-wal-revert`);
|
|
12
|
+
}
|
|
13
|
+
catch (e) { }
|
|
14
|
+
}
|
|
15
|
+
test('explicit connect', async () => {
|
|
16
|
+
const db = new Database({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
|
17
|
+
expect(() => db.prepare("SELECT 1")).toThrowError(/database must be connected/g);
|
|
18
|
+
await db.connect();
|
|
19
|
+
expect(await db.prepare("SELECT 1 as x").all()).toEqual([{ x: 1 }]);
|
|
20
|
+
});
|
|
21
|
+
test('select-after-push', async () => {
|
|
22
|
+
{
|
|
23
|
+
const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
|
24
|
+
await db.exec("CREATE TABLE IF NOT EXISTS t(x)");
|
|
25
|
+
await db.exec("DELETE FROM t");
|
|
26
|
+
await db.push();
|
|
27
|
+
await db.close();
|
|
28
|
+
}
|
|
29
|
+
{
|
|
30
|
+
const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
|
31
|
+
await db.exec("INSERT INTO t VALUES (1), (2), (3)");
|
|
32
|
+
await db.push();
|
|
33
|
+
}
|
|
34
|
+
{
|
|
35
|
+
const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
|
36
|
+
const rows = await db.prepare('SELECT * FROM t').all();
|
|
37
|
+
expect(rows).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
test('select-without-push', async () => {
|
|
41
|
+
{
|
|
42
|
+
const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
|
43
|
+
await db.exec("CREATE TABLE IF NOT EXISTS t(x)");
|
|
44
|
+
await db.exec("DELETE FROM t");
|
|
45
|
+
await db.push();
|
|
46
|
+
await db.close();
|
|
47
|
+
}
|
|
48
|
+
{
|
|
49
|
+
const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
|
50
|
+
await db.exec("INSERT INTO t VALUES (1), (2), (3)");
|
|
51
|
+
}
|
|
52
|
+
{
|
|
53
|
+
const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
|
54
|
+
const rows = await db.prepare('SELECT * FROM t').all();
|
|
55
|
+
expect(rows).toEqual([]);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
test('merge-non-overlapping-keys', async () => {
|
|
59
|
+
{
|
|
60
|
+
const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
|
61
|
+
await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)");
|
|
62
|
+
await db.exec("DELETE FROM q");
|
|
63
|
+
await db.push();
|
|
64
|
+
await db.close();
|
|
65
|
+
}
|
|
66
|
+
const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
|
67
|
+
await db1.exec("INSERT INTO q VALUES ('k1', 'value1'), ('k2', 'value2')");
|
|
68
|
+
const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
|
69
|
+
await db2.exec("INSERT INTO q VALUES ('k3', 'value3'), ('k4', 'value4'), ('k5', 'value5')");
|
|
70
|
+
await Promise.all([db1.push(), db2.push()]);
|
|
71
|
+
await Promise.all([db1.pull(), db2.pull()]);
|
|
72
|
+
const rows1 = await db1.prepare('SELECT * FROM q').all();
|
|
73
|
+
const rows2 = await db1.prepare('SELECT * FROM q').all();
|
|
74
|
+
const expected = [{ x: 'k1', y: 'value1' }, { x: 'k2', y: 'value2' }, { x: 'k3', y: 'value3' }, { x: 'k4', y: 'value4' }, { x: 'k5', y: 'value5' }];
|
|
75
|
+
expect(rows1.sort(localeCompare)).toEqual(expected.sort(localeCompare));
|
|
76
|
+
expect(rows2.sort(localeCompare)).toEqual(expected.sort(localeCompare));
|
|
77
|
+
});
|
|
78
|
+
test('last-push-wins', async () => {
|
|
79
|
+
{
|
|
80
|
+
const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
|
81
|
+
await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)");
|
|
82
|
+
await db.exec("DELETE FROM q");
|
|
83
|
+
await db.push();
|
|
84
|
+
await db.close();
|
|
85
|
+
}
|
|
86
|
+
const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
|
87
|
+
await db1.exec("INSERT INTO q VALUES ('k1', 'value1'), ('k2', 'value2'), ('k4', 'value4')");
|
|
88
|
+
const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
|
89
|
+
await db2.exec("INSERT INTO q VALUES ('k1', 'value3'), ('k2', 'value4'), ('k3', 'value5')");
|
|
90
|
+
await db2.push();
|
|
91
|
+
await db1.push();
|
|
92
|
+
await Promise.all([db1.pull(), db2.pull()]);
|
|
93
|
+
const rows1 = await db1.prepare('SELECT * FROM q').all();
|
|
94
|
+
const rows2 = await db1.prepare('SELECT * FROM q').all();
|
|
95
|
+
const expected = [{ x: 'k1', y: 'value1' }, { x: 'k2', y: 'value2' }, { x: 'k3', y: 'value5' }, { x: 'k4', y: 'value4' }];
|
|
96
|
+
expect(rows1.sort(localeCompare)).toEqual(expected.sort(localeCompare));
|
|
97
|
+
expect(rows2.sort(localeCompare)).toEqual(expected.sort(localeCompare));
|
|
98
|
+
});
|
|
99
|
+
test('last-push-wins-with-delete', async () => {
|
|
100
|
+
{
|
|
101
|
+
const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
|
102
|
+
await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)");
|
|
103
|
+
await db.exec("DELETE FROM q");
|
|
104
|
+
await db.push();
|
|
105
|
+
await db.close();
|
|
106
|
+
}
|
|
107
|
+
const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
|
108
|
+
await db1.exec("INSERT INTO q VALUES ('k1', 'value1'), ('k2', 'value2'), ('k4', 'value4')");
|
|
109
|
+
await db1.exec("DELETE FROM q");
|
|
110
|
+
const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
|
111
|
+
await db2.exec("INSERT INTO q VALUES ('k1', 'value3'), ('k2', 'value4'), ('k3', 'value5')");
|
|
112
|
+
await db2.push();
|
|
113
|
+
await db1.push();
|
|
114
|
+
await Promise.all([db1.pull(), db2.pull()]);
|
|
115
|
+
const rows1 = await db1.prepare('SELECT * FROM q').all();
|
|
116
|
+
const rows2 = await db1.prepare('SELECT * FROM q').all();
|
|
117
|
+
const expected = [{ x: 'k3', y: 'value5' }];
|
|
118
|
+
expect(rows1).toEqual(expected);
|
|
119
|
+
expect(rows2).toEqual(expected);
|
|
120
|
+
});
|
|
121
|
+
test('constraint-conflict', async () => {
|
|
122
|
+
{
|
|
123
|
+
const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
|
124
|
+
await db.exec("CREATE TABLE IF NOT EXISTS u(x TEXT PRIMARY KEY, y UNIQUE)");
|
|
125
|
+
await db.exec("DELETE FROM u");
|
|
126
|
+
await db.push();
|
|
127
|
+
await db.close();
|
|
128
|
+
}
|
|
129
|
+
const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
|
130
|
+
await db1.exec("INSERT INTO u VALUES ('k1', 'value1')");
|
|
131
|
+
const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
|
132
|
+
await db2.exec("INSERT INTO u VALUES ('k2', 'value1')");
|
|
133
|
+
await db1.push();
|
|
134
|
+
await expect(async () => await db2.push()).rejects.toThrow('SQLite error: UNIQUE constraint failed: u.y');
|
|
135
|
+
});
|
|
136
|
+
test('checkpoint', async () => {
|
|
137
|
+
{
|
|
138
|
+
const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
|
139
|
+
await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)");
|
|
140
|
+
await db.exec("DELETE FROM q");
|
|
141
|
+
await db.push();
|
|
142
|
+
await db.close();
|
|
143
|
+
}
|
|
144
|
+
const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
|
145
|
+
for (let i = 0; i < 1000; i++) {
|
|
146
|
+
await db1.exec(`INSERT INTO q VALUES ('k${i}', 'v${i}')`);
|
|
147
|
+
}
|
|
148
|
+
expect((await db1.stats()).mainWal).toBeGreaterThan(4096 * 1000);
|
|
149
|
+
await db1.checkpoint();
|
|
150
|
+
expect((await db1.stats()).mainWal).toBe(0);
|
|
151
|
+
let revertWal = (await db1.stats()).revertWal;
|
|
152
|
+
expect(revertWal).toBeLessThan(4096 * 1000 / 50);
|
|
153
|
+
for (let i = 0; i < 1000; i++) {
|
|
154
|
+
await db1.exec(`UPDATE q SET y = 'u${i}' WHERE x = 'k${i}'`);
|
|
155
|
+
}
|
|
156
|
+
await db1.checkpoint();
|
|
157
|
+
expect((await db1.stats()).revertWal).toBe(revertWal);
|
|
158
|
+
});
|
|
159
|
+
test('persistence-push', async () => {
|
|
160
|
+
{
|
|
161
|
+
const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
|
162
|
+
await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)");
|
|
163
|
+
await db.exec("DELETE FROM q");
|
|
164
|
+
await db.push();
|
|
165
|
+
await db.close();
|
|
166
|
+
}
|
|
167
|
+
const path = `test-${(Math.random() * 10000) | 0}.db`;
|
|
168
|
+
try {
|
|
169
|
+
{
|
|
170
|
+
const db1 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL });
|
|
171
|
+
await db1.exec(`INSERT INTO q VALUES ('k1', 'v1')`);
|
|
172
|
+
await db1.exec(`INSERT INTO q VALUES ('k2', 'v2')`);
|
|
173
|
+
await db1.close();
|
|
174
|
+
}
|
|
175
|
+
{
|
|
176
|
+
const db2 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL });
|
|
177
|
+
await db2.exec(`INSERT INTO q VALUES ('k3', 'v3')`);
|
|
178
|
+
await db2.exec(`INSERT INTO q VALUES ('k4', 'v4')`);
|
|
179
|
+
const stmt = db2.prepare('SELECT * FROM q');
|
|
180
|
+
const rows = await stmt.all();
|
|
181
|
+
const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }, { x: 'k3', y: 'v3' }, { x: 'k4', y: 'v4' }];
|
|
182
|
+
expect(rows).toEqual(expected);
|
|
183
|
+
stmt.close();
|
|
184
|
+
await db2.close();
|
|
185
|
+
}
|
|
186
|
+
{
|
|
187
|
+
const db3 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL });
|
|
188
|
+
await db3.push();
|
|
189
|
+
await db3.close();
|
|
190
|
+
}
|
|
191
|
+
{
|
|
192
|
+
const db4 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL });
|
|
193
|
+
const rows = await db4.prepare('SELECT * FROM q').all();
|
|
194
|
+
const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }, { x: 'k3', y: 'v3' }, { x: 'k4', y: 'v4' }];
|
|
195
|
+
expect(rows).toEqual(expected);
|
|
196
|
+
await db4.close();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
finally {
|
|
200
|
+
cleanup(path);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
test('persistence-offline', async () => {
|
|
204
|
+
{
|
|
205
|
+
const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
|
206
|
+
await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)");
|
|
207
|
+
await db.exec("DELETE FROM q");
|
|
208
|
+
await db.push();
|
|
209
|
+
await db.close();
|
|
210
|
+
}
|
|
211
|
+
const path = `test-${(Math.random() * 10000) | 0}.db`;
|
|
212
|
+
try {
|
|
213
|
+
{
|
|
214
|
+
const db = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL });
|
|
215
|
+
await db.exec(`INSERT INTO q VALUES ('k1', 'v1')`);
|
|
216
|
+
await db.exec(`INSERT INTO q VALUES ('k2', 'v2')`);
|
|
217
|
+
await db.push();
|
|
218
|
+
await db.close();
|
|
219
|
+
}
|
|
220
|
+
{
|
|
221
|
+
const db = await connect({ path: path, url: "https://not-valid-url.localhost" });
|
|
222
|
+
const rows = await db.prepare("SELECT * FROM q").all();
|
|
223
|
+
const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }];
|
|
224
|
+
expect(rows.sort(localeCompare)).toEqual(expected.sort(localeCompare));
|
|
225
|
+
await db.close();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
cleanup(path);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
test('persistence-pull-push', async () => {
|
|
233
|
+
{
|
|
234
|
+
const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
|
235
|
+
await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)");
|
|
236
|
+
await db.exec("DELETE FROM q");
|
|
237
|
+
await db.push();
|
|
238
|
+
await db.close();
|
|
239
|
+
}
|
|
240
|
+
const path1 = `test-${(Math.random() * 10000) | 0}.db`;
|
|
241
|
+
const path2 = `test-${(Math.random() * 10000) | 0}.db`;
|
|
242
|
+
try {
|
|
243
|
+
const db1 = await connect({ path: path1, url: process.env.VITE_TURSO_DB_URL });
|
|
244
|
+
await db1.exec(`INSERT INTO q VALUES ('k1', 'v1')`);
|
|
245
|
+
await db1.exec(`INSERT INTO q VALUES ('k2', 'v2')`);
|
|
246
|
+
const stats1 = await db1.stats();
|
|
247
|
+
const db2 = await connect({ path: path2, url: process.env.VITE_TURSO_DB_URL });
|
|
248
|
+
await db2.exec(`INSERT INTO q VALUES ('k3', 'v3')`);
|
|
249
|
+
await db2.exec(`INSERT INTO q VALUES ('k4', 'v4')`);
|
|
250
|
+
await Promise.all([db1.push(), db2.push()]);
|
|
251
|
+
await Promise.all([db1.pull(), db2.pull()]);
|
|
252
|
+
const stats2 = await db1.stats();
|
|
253
|
+
console.info(stats1, stats2);
|
|
254
|
+
expect(stats1.revision).not.toBe(stats2.revision);
|
|
255
|
+
const rows1 = await db1.prepare('SELECT * FROM q').all();
|
|
256
|
+
const rows2 = await db2.prepare('SELECT * FROM q').all();
|
|
257
|
+
const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }, { x: 'k3', y: 'v3' }, { x: 'k4', y: 'v4' }];
|
|
258
|
+
expect(rows1.sort(localeCompare)).toEqual(expected.sort(localeCompare));
|
|
259
|
+
expect(rows2.sort(localeCompare)).toEqual(expected.sort(localeCompare));
|
|
260
|
+
}
|
|
261
|
+
finally {
|
|
262
|
+
cleanup(path1);
|
|
263
|
+
cleanup(path2);
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
test('update', async () => {
|
|
267
|
+
{
|
|
268
|
+
const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, longPollTimeoutMs: 5000 });
|
|
269
|
+
await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)");
|
|
270
|
+
await db.exec("DELETE FROM q");
|
|
271
|
+
await db.push();
|
|
272
|
+
await db.close();
|
|
273
|
+
}
|
|
274
|
+
const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
|
275
|
+
await db.exec("INSERT INTO q VALUES ('1', '2')");
|
|
276
|
+
await db.push();
|
|
277
|
+
await db.exec("INSERT INTO q VALUES ('1', '2') ON CONFLICT DO UPDATE SET y = '3'");
|
|
278
|
+
await db.push();
|
|
279
|
+
});
|
|
280
|
+
test('concurrent-updates', async () => {
|
|
281
|
+
{
|
|
282
|
+
const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, longPollTimeoutMs: 5000 });
|
|
283
|
+
await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)");
|
|
284
|
+
await db.exec("DELETE FROM q");
|
|
285
|
+
await db.push();
|
|
286
|
+
await db.close();
|
|
287
|
+
}
|
|
288
|
+
const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
|
289
|
+
async function pull(db) {
|
|
290
|
+
try {
|
|
291
|
+
await db.pull();
|
|
292
|
+
}
|
|
293
|
+
catch (e) {
|
|
294
|
+
// ignore
|
|
295
|
+
}
|
|
296
|
+
finally {
|
|
297
|
+
setTimeout(async () => await pull(db), 0);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
async function push(db) {
|
|
301
|
+
try {
|
|
302
|
+
await db.push();
|
|
303
|
+
}
|
|
304
|
+
catch (e) {
|
|
305
|
+
// ignore
|
|
306
|
+
}
|
|
307
|
+
finally {
|
|
308
|
+
setTimeout(async () => await push(db), 0);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
setTimeout(async () => await pull(db1), 0);
|
|
312
|
+
setTimeout(async () => await push(db1), 0);
|
|
313
|
+
for (let i = 0; i < 1000; i++) {
|
|
314
|
+
try {
|
|
315
|
+
await Promise.all([
|
|
316
|
+
db1.exec(`INSERT INTO q VALUES ('1', 0) ON CONFLICT DO UPDATE SET y = ${i + 1}`),
|
|
317
|
+
db1.exec(`INSERT INTO q VALUES ('2', 0) ON CONFLICT DO UPDATE SET y = ${i + 1}`)
|
|
318
|
+
]);
|
|
319
|
+
}
|
|
320
|
+
catch (e) {
|
|
321
|
+
// ignore
|
|
322
|
+
}
|
|
323
|
+
await new Promise(resolve => setTimeout(resolve, 1));
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
test('pull-push-concurrent', async () => {
|
|
327
|
+
{
|
|
328
|
+
const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, longPollTimeoutMs: 5000 });
|
|
329
|
+
await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)");
|
|
330
|
+
await db.exec("DELETE FROM q");
|
|
331
|
+
await db.push();
|
|
332
|
+
await db.close();
|
|
333
|
+
}
|
|
334
|
+
let pullResolve = null;
|
|
335
|
+
const pullFinish = new Promise(resolve => pullResolve = resolve);
|
|
336
|
+
let pushResolve = null;
|
|
337
|
+
const pushFinish = new Promise(resolve => pushResolve = resolve);
|
|
338
|
+
let stopPull = false;
|
|
339
|
+
let stopPush = false;
|
|
340
|
+
const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
|
341
|
+
let pull = async () => {
|
|
342
|
+
try {
|
|
343
|
+
await db.pull();
|
|
344
|
+
}
|
|
345
|
+
catch (e) {
|
|
346
|
+
console.error('pull', e);
|
|
347
|
+
}
|
|
348
|
+
finally {
|
|
349
|
+
if (!stopPull) {
|
|
350
|
+
setTimeout(pull, 0);
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
pullResolve();
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
let push = async () => {
|
|
358
|
+
try {
|
|
359
|
+
if ((await db.stats()).operations > 0) {
|
|
360
|
+
await db.push();
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
catch (e) {
|
|
364
|
+
console.error('push', e);
|
|
365
|
+
}
|
|
366
|
+
finally {
|
|
367
|
+
if (!stopPush) {
|
|
368
|
+
setTimeout(push, 0);
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
pushResolve();
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
setTimeout(pull, 0);
|
|
376
|
+
setTimeout(push, 0);
|
|
377
|
+
for (let i = 0; i < 1000; i++) {
|
|
378
|
+
await db.exec(`INSERT INTO q VALUES ('k${i}', 'v${i}')`);
|
|
379
|
+
}
|
|
380
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
381
|
+
stopPush = true;
|
|
382
|
+
await pushFinish;
|
|
383
|
+
stopPull = true;
|
|
384
|
+
await pullFinish;
|
|
385
|
+
console.info(await db.stats());
|
|
386
|
+
});
|
|
387
|
+
test('transform', async () => {
|
|
388
|
+
{
|
|
389
|
+
const db = await connect({
|
|
390
|
+
path: ':memory:',
|
|
391
|
+
url: process.env.VITE_TURSO_DB_URL,
|
|
392
|
+
});
|
|
393
|
+
await db.exec("CREATE TABLE IF NOT EXISTS counter(key TEXT PRIMARY KEY, value INTEGER)");
|
|
394
|
+
await db.exec("DELETE FROM counter");
|
|
395
|
+
await db.exec("INSERT INTO counter VALUES ('1', 0)");
|
|
396
|
+
await db.push();
|
|
397
|
+
await db.close();
|
|
398
|
+
}
|
|
399
|
+
const transform = (m) => ({
|
|
400
|
+
operation: 'rewrite',
|
|
401
|
+
stmt: {
|
|
402
|
+
sql: `UPDATE counter SET value = value + ? WHERE key = ?`,
|
|
403
|
+
values: [m.after.value - m.before.value, m.after.key]
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform });
|
|
407
|
+
const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform });
|
|
408
|
+
await db1.exec("UPDATE counter SET value = value + 1 WHERE key = '1'");
|
|
409
|
+
await db2.exec("UPDATE counter SET value = value + 1 WHERE key = '1'");
|
|
410
|
+
await Promise.all([db1.push(), db2.push()]);
|
|
411
|
+
await Promise.all([db1.pull(), db2.pull()]);
|
|
412
|
+
const rows1 = await db1.prepare('SELECT * FROM counter').all();
|
|
413
|
+
const rows2 = await db2.prepare('SELECT * FROM counter').all();
|
|
414
|
+
expect(rows1).toEqual([{ key: '1', value: 2 }]);
|
|
415
|
+
expect(rows2).toEqual([{ key: '1', value: 2 }]);
|
|
416
|
+
});
|
|
417
|
+
test('transform-many', async () => {
|
|
418
|
+
{
|
|
419
|
+
const db = await connect({
|
|
420
|
+
path: ':memory:',
|
|
421
|
+
url: process.env.VITE_TURSO_DB_URL,
|
|
422
|
+
});
|
|
423
|
+
await db.exec("CREATE TABLE IF NOT EXISTS counter(key TEXT PRIMARY KEY, value INTEGER)");
|
|
424
|
+
await db.exec("DELETE FROM counter");
|
|
425
|
+
await db.exec("INSERT INTO counter VALUES ('1', 0)");
|
|
426
|
+
await db.push();
|
|
427
|
+
await db.close();
|
|
428
|
+
}
|
|
429
|
+
const transform = (m) => ({
|
|
430
|
+
operation: 'rewrite',
|
|
431
|
+
stmt: {
|
|
432
|
+
sql: `UPDATE counter SET value = value + ? WHERE key = ?`,
|
|
433
|
+
values: [m.after.value - m.before.value, m.after.key]
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform });
|
|
437
|
+
const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform });
|
|
438
|
+
for (let i = 0; i < 1002; i++) {
|
|
439
|
+
await db1.exec("UPDATE counter SET value = value + 1 WHERE key = '1'");
|
|
440
|
+
}
|
|
441
|
+
for (let i = 0; i < 1001; i++) {
|
|
442
|
+
await db2.exec("UPDATE counter SET value = value + 1 WHERE key = '1'");
|
|
443
|
+
}
|
|
444
|
+
let start = performance.now();
|
|
445
|
+
await Promise.all([db1.push(), db2.push()]);
|
|
446
|
+
console.info('push', performance.now() - start);
|
|
447
|
+
start = performance.now();
|
|
448
|
+
await Promise.all([db1.pull(), db2.pull()]);
|
|
449
|
+
console.info('pull', performance.now() - start);
|
|
450
|
+
const rows1 = await db1.prepare('SELECT * FROM counter').all();
|
|
451
|
+
const rows2 = await db2.prepare('SELECT * FROM counter').all();
|
|
452
|
+
expect(rows1).toEqual([{ key: '1', value: 1001 + 1002 }]);
|
|
453
|
+
expect(rows2).toEqual([{ key: '1', value: 1001 + 1002 }]);
|
|
454
|
+
});
|