@tursodatabase/sync 0.5.0-pre.11 → 0.5.0-pre.13

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.
@@ -1,840 +0,0 @@
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
- const intCompare = (a, b) => a.x - b.x;
6
- function cleanup(path) {
7
- unlinkSync(path);
8
- unlinkSync(`${path}-wal`);
9
- unlinkSync(`${path}-info`);
10
- unlinkSync(`${path}-changes`);
11
- try {
12
- unlinkSync(`${path}-wal-revert`);
13
- }
14
- catch (e) { }
15
- }
16
- test('partial sync concurrency', async () => {
17
- {
18
- const db = await connect({
19
- path: ':memory:',
20
- url: process.env.VITE_TURSO_DB_URL,
21
- longPollTimeoutMs: 100,
22
- });
23
- await db.exec("CREATE TABLE IF NOT EXISTS partial(value BLOB)");
24
- await db.exec("DELETE FROM partial");
25
- await db.exec("INSERT INTO partial SELECT randomblob(1024) FROM generate_series(1, 2000)");
26
- await db.push();
27
- await db.close();
28
- }
29
- const dbs = [];
30
- for (let i = 0; i < 16; i++) {
31
- dbs.push(await connect({
32
- path: 'partial-1.db',
33
- url: process.env.VITE_TURSO_DB_URL,
34
- longPollTimeoutMs: 100,
35
- partialSyncExperimental: {
36
- bootstrapStrategy: { kind: 'prefix', length: 128 * 1024 },
37
- segmentSize: 128 * 1024,
38
- },
39
- }));
40
- }
41
- const qs = [];
42
- for (const db of dbs) {
43
- qs.push(db.prepare("SELECT COUNT(*) as cnt FROM partial").all());
44
- }
45
- const values = await Promise.all(qs);
46
- expect(values).toEqual(new Array(16).fill([{ cnt: 2000 }]));
47
- });
48
- test('partial sync (prefix bootstrap strategy)', async () => {
49
- {
50
- const db = await connect({
51
- path: ':memory:',
52
- url: process.env.VITE_TURSO_DB_URL,
53
- longPollTimeoutMs: 100,
54
- });
55
- await db.exec("CREATE TABLE IF NOT EXISTS partial(value BLOB)");
56
- await db.exec("DELETE FROM partial");
57
- await db.exec("INSERT INTO partial SELECT randomblob(1024) FROM generate_series(1, 2000)");
58
- await db.push();
59
- await db.close();
60
- }
61
- const db = await connect({
62
- path: ':memory:',
63
- url: process.env.VITE_TURSO_DB_URL,
64
- longPollTimeoutMs: 100,
65
- partialSyncExperimental: {
66
- bootstrapStrategy: { kind: 'prefix', length: 128 * 1024 },
67
- segmentSize: 4096,
68
- },
69
- });
70
- // 128kb plus some overhead
71
- expect((await db.stats()).networkReceivedBytes).toBeLessThanOrEqual(128 * (1024 + 128));
72
- // select of one record shouldn't increase amount of received data
73
- expect(await db.prepare("SELECT length(value) as length FROM partial LIMIT 1").all()).toEqual([{ length: 1024 }]);
74
- expect((await db.stats()).networkReceivedBytes).toBeLessThanOrEqual(128 * (1024 + 128));
75
- await db.prepare("INSERT INTO partial VALUES (-1)").run();
76
- expect(await db.prepare("SELECT COUNT(*) as cnt FROM partial").all()).toEqual([{ cnt: 2001 }]);
77
- expect((await db.stats()).networkReceivedBytes).toBeGreaterThanOrEqual(2000 * 1024);
78
- });
79
- test('partial sync (prefix bootstrap strategy; large segment size)', async () => {
80
- {
81
- const db = await connect({
82
- path: ':memory:',
83
- url: process.env.VITE_TURSO_DB_URL,
84
- longPollTimeoutMs: 100,
85
- });
86
- await db.exec("CREATE TABLE IF NOT EXISTS partial(value BLOB)");
87
- await db.exec("DELETE FROM partial");
88
- await db.exec("INSERT INTO partial SELECT randomblob(1024) FROM generate_series(1, 2000)");
89
- await db.push();
90
- await db.close();
91
- }
92
- const db = await connect({
93
- path: ':memory:',
94
- url: process.env.VITE_TURSO_DB_URL,
95
- longPollTimeoutMs: 100,
96
- partialSyncExperimental: {
97
- bootstrapStrategy: { kind: 'prefix', length: 128 * 1024 },
98
- segmentSize: 128 * 1024,
99
- },
100
- });
101
- // 128kb plus some overhead
102
- expect((await db.stats()).networkReceivedBytes).toBeLessThanOrEqual(128 * (1024 + 128));
103
- const startLast = performance.now();
104
- // select of one record shouldn't increase amount of received data
105
- expect(await db.prepare("SELECT length(value) as length FROM partial LIMIT 1").all()).toEqual([{ length: 1024 }]);
106
- console.info('select last', 'elapsed', performance.now() - startLast);
107
- expect((await db.stats()).networkReceivedBytes).toBeLessThanOrEqual(2 * 128 * (1024 + 128));
108
- await db.prepare("INSERT INTO partial VALUES (-1)").run();
109
- const startAll = performance.now();
110
- expect(await db.prepare("SELECT COUNT(*) as cnt FROM partial").all()).toEqual([{ cnt: 2001 }]);
111
- console.info('select all', 'elapsed', performance.now() - startAll);
112
- expect((await db.stats()).networkReceivedBytes).toBeGreaterThanOrEqual(2000 * 1024);
113
- });
114
- test('partial sync (prefix bootstrap strategy; prefetch)', async () => {
115
- {
116
- const db = await connect({
117
- path: ':memory:',
118
- url: process.env.VITE_TURSO_DB_URL,
119
- longPollTimeoutMs: 100,
120
- });
121
- await db.exec("CREATE TABLE IF NOT EXISTS partial(value BLOB)");
122
- await db.exec("DELETE FROM partial");
123
- await db.exec("INSERT INTO partial SELECT randomblob(1024) FROM generate_series(1, 2000)");
124
- await db.push();
125
- await db.close();
126
- }
127
- const db = await connect({
128
- path: ':memory:',
129
- url: process.env.VITE_TURSO_DB_URL,
130
- longPollTimeoutMs: 100,
131
- partialSyncExperimental: {
132
- bootstrapStrategy: { kind: 'prefix', length: 128 * 1024 },
133
- segmentSize: 4 * 1024,
134
- prefetch: true,
135
- },
136
- });
137
- // 128kb plus some overhead
138
- expect((await db.stats()).networkReceivedBytes).toBeLessThanOrEqual(128 * (1024 + 128));
139
- const startLast = performance.now();
140
- // select of one record shouldn't increase amount of received data
141
- expect(await db.prepare("SELECT length(value) as length FROM partial LIMIT 1").all()).toEqual([{ length: 1024 }]);
142
- console.info('select last', 'elapsed', performance.now() - startLast);
143
- expect((await db.stats()).networkReceivedBytes).toBeLessThanOrEqual(10 * 128 * (1024 + 128));
144
- await db.prepare("INSERT INTO partial VALUES (-1)").run();
145
- const startAll = performance.now();
146
- expect(await db.prepare("SELECT COUNT(*) as cnt FROM partial").all()).toEqual([{ cnt: 2001 }]);
147
- console.info('select all', 'elapsed', performance.now() - startAll);
148
- expect((await db.stats()).networkReceivedBytes).toBeGreaterThanOrEqual(2000 * 1024);
149
- });
150
- test('partial sync (query bootstrap strategy)', async () => {
151
- {
152
- const db = await connect({
153
- path: ':memory:',
154
- url: process.env.VITE_TURSO_DB_URL,
155
- longPollTimeoutMs: 100,
156
- });
157
- await db.exec("CREATE TABLE IF NOT EXISTS partial_keyed(key INTEGER PRIMARY KEY, value BLOB)");
158
- await db.exec("DELETE FROM partial_keyed");
159
- await db.exec("INSERT INTO partial_keyed SELECT value, randomblob(1024) FROM generate_series(1, 2000)");
160
- await db.push();
161
- await db.close();
162
- }
163
- const db = await connect({
164
- path: ':memory:',
165
- url: process.env.VITE_TURSO_DB_URL,
166
- longPollTimeoutMs: 100,
167
- partialSyncExperimental: {
168
- bootstrapStrategy: { kind: 'query', query: 'SELECT * FROM partial_keyed WHERE key = 1000' },
169
- segmentSize: 4096,
170
- },
171
- });
172
- // we must sync only few pages
173
- expect((await db.stats()).networkReceivedBytes).toBeLessThanOrEqual(10 * (4096 + 128));
174
- // select of one record shouldn't increase amount of received data by a lot
175
- expect(await db.prepare("SELECT length(value) as length FROM partial_keyed LIMIT 1").all()).toEqual([{ length: 1024 }]);
176
- expect((await db.stats()).networkReceivedBytes).toBeLessThanOrEqual(10 * (4096 + 128));
177
- await db.prepare("INSERT INTO partial_keyed VALUES (-1, -1)").run();
178
- const n1 = await db.stats();
179
- // same as bootstrap query - we shouldn't bring any more pages
180
- expect(await db.prepare("SELECT length(value) as length FROM partial_keyed WHERE key = 1000").all()).toEqual([{ length: 1024 }]);
181
- const n2 = await db.stats();
182
- expect(n1.networkReceivedBytes).toEqual(n2.networkReceivedBytes);
183
- });
184
- test('concurrent-actions-consistency', async () => {
185
- {
186
- const db = await connect({
187
- path: ':memory:',
188
- url: process.env.VITE_TURSO_DB_URL,
189
- longPollTimeoutMs: 100,
190
- });
191
- await db.exec("CREATE TABLE IF NOT EXISTS rows(key TEXT PRIMARY KEY, value INTEGER)");
192
- await db.exec("DELETE FROM rows");
193
- await db.exec("INSERT INTO rows VALUES ('key', 0)");
194
- await db.push();
195
- await db.close();
196
- }
197
- const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
198
- console.info('run_info', await db1.prepare("SELECT * FROM sqlite_master").all());
199
- await db1.exec("PRAGMA busy_timeout=100");
200
- const pull = async function (iterations) {
201
- for (let i = 0; i < iterations; i++) {
202
- console.info('pull', i);
203
- try {
204
- await db1.pull();
205
- }
206
- catch (e) {
207
- console.error('pull', e);
208
- }
209
- await new Promise(resolve => setTimeout(resolve, 0));
210
- }
211
- };
212
- const push = async function (iterations) {
213
- for (let i = 0; i < iterations; i++) {
214
- await new Promise(resolve => setTimeout(resolve, (Math.random() + 1)));
215
- console.info('push', i);
216
- try {
217
- if ((await db1.stats()).cdcOperations > 0) {
218
- const start = performance.now();
219
- await db1.push();
220
- console.info('push', performance.now() - start);
221
- }
222
- }
223
- catch (e) {
224
- console.error('push', e);
225
- }
226
- }
227
- };
228
- const run = async function (iterations) {
229
- let rows = 0;
230
- for (let i = 0; i < iterations; i++) {
231
- // console.info('run', i, rows);
232
- // console.info('run_info', 'update', 'start');
233
- await db1.prepare("UPDATE rows SET value = value + 1 WHERE key = ?").run('key');
234
- // console.info('run_info', 'update', 'end');
235
- rows += 1;
236
- // console.info('run_info', 'select', 'start');
237
- const { cnt } = await db1.prepare("SELECT value as cnt FROM rows WHERE key = ?").get(['key']);
238
- // console.info('run_info', 'select', 'end', cnt, '(', rows, ')');
239
- expect(cnt).toBe(rows);
240
- await new Promise(resolve => setTimeout(resolve, 10 * (Math.random() + 1)));
241
- }
242
- };
243
- await Promise.all([pull(100), push(100), run(200)]);
244
- });
245
- test('simple-db', async () => {
246
- const db = new Database({ path: ':memory:' });
247
- expect(await db.prepare("SELECT 1 as x").all()).toEqual([{ x: 1 }]);
248
- await db.exec("CREATE TABLE t(x)");
249
- await db.exec("INSERT INTO t VALUES (1), (2), (3)");
250
- expect(await db.prepare("SELECT * FROM t").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]);
251
- await expect(async () => await db.pull()).rejects.toThrowError(/sync is disabled as database was opened without sync support/);
252
- });
253
- test('implicit connect', async () => {
254
- const db = new Database({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
255
- const defer = db.prepare("SELECT * FROM not_found");
256
- await expect(async () => await defer.all()).rejects.toThrowError(/no such table: not_found/);
257
- expect(() => db.prepare("SELECT * FROM not_found")).toThrowError(/no such table: not_found/);
258
- expect(await db.prepare("SELECT 1 as x").all()).toEqual([{ x: 1 }]);
259
- });
260
- test('defered sync', async () => {
261
- {
262
- const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
263
- await db.exec("CREATE TABLE IF NOT EXISTS t(x)");
264
- await db.exec("DELETE FROM t");
265
- await db.exec("INSERT INTO t VALUES (100)");
266
- await db.push();
267
- await db.close();
268
- }
269
- let url = null;
270
- const db = new Database({ path: ':memory:', url: () => url });
271
- await db.prepare("CREATE TABLE t(x)").run();
272
- await db.prepare("INSERT INTO t VALUES (1), (2), (3), (42)").run();
273
- expect(await db.prepare("SELECT * FROM t").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }, { x: 42 }]);
274
- await expect(async () => await db.pull()).rejects.toThrow(/url is empty - sync is paused/);
275
- url = process.env.VITE_TURSO_DB_URL;
276
- await db.pull();
277
- expect(await db.prepare("SELECT * FROM t").all()).toEqual([{ x: 100 }, { x: 1 }, { x: 2 }, { x: 3 }, { x: 42 }]);
278
- });
279
- test('encryption sync', async () => {
280
- const KEY = 'l/FWopMfZisTLgBX4A42AergrCrYKjiO3BfkJUwv83I=';
281
- const URL = 'http://encrypted--a--a.localhost:10000';
282
- {
283
- const db = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } });
284
- await db.exec("CREATE TABLE IF NOT EXISTS t(x)");
285
- await db.exec("DELETE FROM t");
286
- await db.push();
287
- await db.close();
288
- }
289
- const db1 = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } });
290
- const db2 = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } });
291
- await db1.exec("INSERT INTO t VALUES (1), (2), (3)");
292
- await db2.exec("INSERT INTO t VALUES (4), (5), (6)");
293
- expect(await db1.prepare("SELECT * FROM t").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]);
294
- expect(await db2.prepare("SELECT * FROM t").all()).toEqual([{ x: 4 }, { x: 5 }, { x: 6 }]);
295
- await Promise.all([db1.push(), db2.push()]);
296
- await Promise.all([db1.pull(), db2.pull()]);
297
- const expected = [{ x: 1 }, { x: 2 }, { x: 3 }, { x: 4 }, { x: 5 }, { x: 6 }];
298
- expect((await db1.prepare("SELECT * FROM t").all()).sort(intCompare)).toEqual(expected.sort(intCompare));
299
- expect((await db2.prepare("SELECT * FROM t").all()).sort(intCompare)).toEqual(expected.sort(intCompare));
300
- });
301
- test('defered encryption sync', async () => {
302
- const URL = 'http://encrypted--a--a.localhost:10000';
303
- const KEY = 'l/FWopMfZisTLgBX4A42AergrCrYKjiO3BfkJUwv83I=';
304
- let url = null;
305
- {
306
- const db = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } });
307
- await db.exec("CREATE TABLE IF NOT EXISTS t(x)");
308
- await db.exec("DELETE FROM t");
309
- await db.exec("INSERT INTO t VALUES (100)");
310
- await db.push();
311
- await db.close();
312
- }
313
- const db = await connect({ path: ':memory:', url: () => url, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } });
314
- await db.exec("CREATE TABLE IF NOT EXISTS t(x)");
315
- await db.exec("INSERT INTO t VALUES (1), (2), (3)");
316
- expect(await db.prepare("SELECT * FROM t").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]);
317
- url = URL;
318
- await db.pull();
319
- const expected = [{ x: 100 }, { x: 1 }, { x: 2 }, { x: 3 }];
320
- expect((await db.prepare("SELECT * FROM t").all())).toEqual(expected);
321
- });
322
- test('select-after-push', async () => {
323
- {
324
- const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
325
- await db.exec("CREATE TABLE IF NOT EXISTS t(x)");
326
- await db.exec("DELETE FROM t");
327
- await db.push();
328
- await db.close();
329
- }
330
- {
331
- const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
332
- await db.exec("INSERT INTO t VALUES (1), (2), (3)");
333
- await db.push();
334
- }
335
- {
336
- const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
337
- const rows = await db.prepare('SELECT * FROM t').all();
338
- expect(rows).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]);
339
- }
340
- });
341
- test('select-without-push', async () => {
342
- {
343
- const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
344
- await db.exec("CREATE TABLE IF NOT EXISTS t(x)");
345
- await db.exec("DELETE FROM t");
346
- await db.push();
347
- await db.close();
348
- }
349
- {
350
- const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
351
- await db.exec("INSERT INTO t VALUES (1), (2), (3)");
352
- }
353
- {
354
- const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
355
- const rows = await db.prepare('SELECT * FROM t').all();
356
- expect(rows).toEqual([]);
357
- }
358
- });
359
- test('merge-non-overlapping-keys', async () => {
360
- {
361
- const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
362
- await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)");
363
- await db.exec("DELETE FROM q");
364
- await db.push();
365
- await db.close();
366
- }
367
- const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
368
- await db1.exec("INSERT INTO q VALUES ('k1', 'value1'), ('k2', 'value2')");
369
- const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
370
- await db2.exec("INSERT INTO q VALUES ('k3', 'value3'), ('k4', 'value4'), ('k5', 'value5')");
371
- await Promise.all([db1.push(), db2.push()]);
372
- await Promise.all([db1.pull(), db2.pull()]);
373
- const rows1 = await db1.prepare('SELECT * FROM q').all();
374
- const rows2 = await db1.prepare('SELECT * FROM q').all();
375
- const expected = [{ x: 'k1', y: 'value1' }, { x: 'k2', y: 'value2' }, { x: 'k3', y: 'value3' }, { x: 'k4', y: 'value4' }, { x: 'k5', y: 'value5' }];
376
- expect(rows1.sort(localeCompare)).toEqual(expected.sort(localeCompare));
377
- expect(rows2.sort(localeCompare)).toEqual(expected.sort(localeCompare));
378
- });
379
- test('last-push-wins', async () => {
380
- {
381
- const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
382
- await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)");
383
- await db.exec("DELETE FROM q");
384
- await db.push();
385
- await db.close();
386
- }
387
- const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
388
- await db1.exec("INSERT INTO q VALUES ('k1', 'value1'), ('k2', 'value2'), ('k4', 'value4')");
389
- const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
390
- await db2.exec("INSERT INTO q VALUES ('k1', 'value3'), ('k2', 'value4'), ('k3', 'value5')");
391
- await db2.push();
392
- await db1.push();
393
- await Promise.all([db1.pull(), db2.pull()]);
394
- const rows1 = await db1.prepare('SELECT * FROM q').all();
395
- const rows2 = await db1.prepare('SELECT * FROM q').all();
396
- const expected = [{ x: 'k1', y: 'value1' }, { x: 'k2', y: 'value2' }, { x: 'k3', y: 'value5' }, { x: 'k4', y: 'value4' }];
397
- expect(rows1.sort(localeCompare)).toEqual(expected.sort(localeCompare));
398
- expect(rows2.sort(localeCompare)).toEqual(expected.sort(localeCompare));
399
- });
400
- test('last-push-wins-with-delete', async () => {
401
- {
402
- const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
403
- await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)");
404
- await db.exec("DELETE FROM q");
405
- await db.push();
406
- await db.close();
407
- }
408
- const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
409
- await db1.exec("INSERT INTO q VALUES ('k1', 'value1'), ('k2', 'value2'), ('k4', 'value4')");
410
- await db1.exec("DELETE FROM q");
411
- const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
412
- await db2.exec("INSERT INTO q VALUES ('k1', 'value3'), ('k2', 'value4'), ('k3', 'value5')");
413
- await db2.push();
414
- await db1.push();
415
- await Promise.all([db1.pull(), db2.pull()]);
416
- const rows1 = await db1.prepare('SELECT * FROM q').all();
417
- const rows2 = await db1.prepare('SELECT * FROM q').all();
418
- const expected = [{ x: 'k3', y: 'value5' }];
419
- expect(rows1).toEqual(expected);
420
- expect(rows2).toEqual(expected);
421
- });
422
- test('constraint-conflict', async () => {
423
- {
424
- const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
425
- await db.exec("CREATE TABLE IF NOT EXISTS u(x TEXT PRIMARY KEY, y UNIQUE)");
426
- await db.exec("DELETE FROM u");
427
- await db.push();
428
- await db.close();
429
- }
430
- const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
431
- await db1.exec("INSERT INTO u VALUES ('k1', 'value1')");
432
- const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
433
- await db2.exec("INSERT INTO u VALUES ('k2', 'value1')");
434
- await db1.push();
435
- await expect(async () => await db2.push()).rejects.toThrow('SQLite error: UNIQUE constraint failed: u.y');
436
- });
437
- test('checkpoint', async () => {
438
- {
439
- const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
440
- await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)");
441
- await db.exec("DELETE FROM q");
442
- await db.push();
443
- await db.close();
444
- }
445
- const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
446
- for (let i = 0; i < 1000; i++) {
447
- await db1.exec(`INSERT INTO q VALUES ('k${i}', 'v${i}')`);
448
- }
449
- expect((await db1.stats()).mainWalSize).toBeGreaterThan(4096 * 1000);
450
- await db1.checkpoint();
451
- expect((await db1.stats()).mainWalSize).toBe(0);
452
- let revertWal = (await db1.stats()).revertWalSize;
453
- expect(revertWal).toBeLessThan(4096 * 1000 / 50);
454
- for (let i = 0; i < 1000; i++) {
455
- await db1.exec(`UPDATE q SET y = 'u${i}' WHERE x = 'k${i}'`);
456
- }
457
- await db1.checkpoint();
458
- expect((await db1.stats()).revertWalSize).toBe(revertWal);
459
- });
460
- test('persistence-push', async () => {
461
- {
462
- const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
463
- await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)");
464
- await db.exec("DELETE FROM q");
465
- await db.push();
466
- await db.close();
467
- }
468
- const path = `test-${(Math.random() * 10000) | 0}.db`;
469
- try {
470
- {
471
- const db1 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL });
472
- await db1.exec(`INSERT INTO q VALUES ('k1', 'v1')`);
473
- await db1.exec(`INSERT INTO q VALUES ('k2', 'v2')`);
474
- await db1.close();
475
- }
476
- {
477
- const db2 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL });
478
- await db2.exec(`INSERT INTO q VALUES ('k3', 'v3')`);
479
- await db2.exec(`INSERT INTO q VALUES ('k4', 'v4')`);
480
- const stmt = db2.prepare('SELECT * FROM q');
481
- const rows = await stmt.all();
482
- const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }, { x: 'k3', y: 'v3' }, { x: 'k4', y: 'v4' }];
483
- expect(rows).toEqual(expected);
484
- stmt.close();
485
- await db2.close();
486
- }
487
- {
488
- const db3 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL });
489
- await db3.push();
490
- await db3.close();
491
- }
492
- {
493
- const db4 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL });
494
- const rows = await db4.prepare('SELECT * FROM q').all();
495
- const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }, { x: 'k3', y: 'v3' }, { x: 'k4', y: 'v4' }];
496
- expect(rows).toEqual(expected);
497
- await db4.close();
498
- }
499
- }
500
- finally {
501
- cleanup(path);
502
- }
503
- });
504
- test('persistence-offline', async () => {
505
- {
506
- const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
507
- await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)");
508
- await db.exec("DELETE FROM q");
509
- await db.push();
510
- await db.close();
511
- }
512
- const path = `test-${(Math.random() * 10000) | 0}.db`;
513
- try {
514
- {
515
- const db = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL });
516
- await db.exec(`INSERT INTO q VALUES ('k1', 'v1')`);
517
- await db.exec(`INSERT INTO q VALUES ('k2', 'v2')`);
518
- await db.push();
519
- await db.close();
520
- }
521
- {
522
- const db = await connect({ path: path, url: "https://not-valid-url.localhost" });
523
- const rows = await db.prepare("SELECT * FROM q").all();
524
- const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }];
525
- expect(rows.sort(localeCompare)).toEqual(expected.sort(localeCompare));
526
- await db.close();
527
- }
528
- }
529
- finally {
530
- cleanup(path);
531
- }
532
- });
533
- test('persistence-pull-push', async () => {
534
- {
535
- const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
536
- await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)");
537
- await db.exec("DELETE FROM q");
538
- await db.push();
539
- await db.close();
540
- }
541
- const path1 = `test-${(Math.random() * 10000) | 0}.db`;
542
- const path2 = `test-${(Math.random() * 10000) | 0}.db`;
543
- try {
544
- const db1 = await connect({ path: path1, url: process.env.VITE_TURSO_DB_URL });
545
- await db1.exec(`INSERT INTO q VALUES ('k1', 'v1')`);
546
- await db1.exec(`INSERT INTO q VALUES ('k2', 'v2')`);
547
- const stats1 = await db1.stats();
548
- const db2 = await connect({ path: path2, url: process.env.VITE_TURSO_DB_URL });
549
- await db2.exec(`INSERT INTO q VALUES ('k3', 'v3')`);
550
- await db2.exec(`INSERT INTO q VALUES ('k4', 'v4')`);
551
- await Promise.all([db1.push(), db2.push()]);
552
- await Promise.all([db1.pull(), db2.pull()]);
553
- const stats2 = await db1.stats();
554
- console.info(stats1, stats2);
555
- expect(stats1.revision).not.toBe(stats2.revision);
556
- const rows1 = await db1.prepare('SELECT * FROM q').all();
557
- const rows2 = await db2.prepare('SELECT * FROM q').all();
558
- const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }, { x: 'k3', y: 'v3' }, { x: 'k4', y: 'v4' }];
559
- expect(rows1.sort(localeCompare)).toEqual(expected.sort(localeCompare));
560
- expect(rows2.sort(localeCompare)).toEqual(expected.sort(localeCompare));
561
- }
562
- finally {
563
- cleanup(path1);
564
- cleanup(path2);
565
- }
566
- });
567
- test('update', async () => {
568
- {
569
- const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, longPollTimeoutMs: 5000 });
570
- await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)");
571
- await db.exec("DELETE FROM q");
572
- await db.push();
573
- await db.close();
574
- }
575
- const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
576
- await db.exec("INSERT INTO q VALUES ('1', '2')");
577
- await db.push();
578
- await db.exec("INSERT INTO q VALUES ('1', '2') ON CONFLICT DO UPDATE SET y = '3'");
579
- await db.push();
580
- });
581
- test('concurrent-updates', async () => {
582
- {
583
- const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, longPollTimeoutMs: 5000 });
584
- await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)");
585
- await db.exec("DELETE FROM q");
586
- await db.push();
587
- await db.close();
588
- }
589
- const db1 = await connect({
590
- path: ':memory:',
591
- url: process.env.VITE_TURSO_DB_URL,
592
- });
593
- await db1.exec("PRAGMA busy_timeout=100");
594
- async function pull(db) {
595
- try {
596
- await db.pull();
597
- }
598
- catch (e) {
599
- console.error('pull error', e);
600
- }
601
- finally {
602
- console.error('pull ok');
603
- setTimeout(async () => await pull(db), 0);
604
- }
605
- }
606
- async function push(db) {
607
- try {
608
- await db.push();
609
- }
610
- catch (e) {
611
- console.error('push error', e);
612
- }
613
- finally {
614
- console.error('push ok');
615
- setTimeout(async () => await push(db), 0);
616
- }
617
- }
618
- setTimeout(async () => await pull(db1), 0);
619
- setTimeout(async () => await push(db1), 0);
620
- for (let i = 0; i < 1000; i++) {
621
- try {
622
- await Promise.all([
623
- db1.exec(`INSERT INTO q VALUES ('1', 0) ON CONFLICT DO UPDATE SET y = randomblob(1024)`),
624
- db1.exec(`INSERT INTO q VALUES ('1', 0) ON CONFLICT DO UPDATE SET y = randomblob(1024)`)
625
- ]);
626
- console.info('insert ok');
627
- }
628
- catch (e) {
629
- console.error('insert error', e);
630
- }
631
- await new Promise(resolve => setTimeout(resolve, 1));
632
- }
633
- });
634
- test('corruption-bug-1', async () => {
635
- {
636
- const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, longPollTimeoutMs: 5000 });
637
- await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)");
638
- await db.exec("DELETE FROM q");
639
- await db.push();
640
- await db.close();
641
- }
642
- const db1 = await connect({
643
- path: ':memory:',
644
- url: process.env.VITE_TURSO_DB_URL,
645
- });
646
- for (let i = 0; i < 100; i++) {
647
- await db1.exec(`INSERT INTO q VALUES ('1', 0) ON CONFLICT DO UPDATE SET y = randomblob(1024)`);
648
- }
649
- await db1.pull();
650
- await db1.push();
651
- for (let i = 0; i < 100; i++) {
652
- await db1.exec(`INSERT INTO q VALUES ('1', 0) ON CONFLICT DO UPDATE SET y = randomblob(1024)`);
653
- }
654
- await db1.pull();
655
- await db1.push();
656
- });
657
- test('pull-push-concurrent', async () => {
658
- {
659
- const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, longPollTimeoutMs: 5000 });
660
- await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)");
661
- await db.exec("DELETE FROM q");
662
- await db.push();
663
- await db.close();
664
- }
665
- let pullResolve = null;
666
- const pullFinish = new Promise(resolve => pullResolve = resolve);
667
- let pushResolve = null;
668
- const pushFinish = new Promise(resolve => pushResolve = resolve);
669
- let stopPull = false;
670
- let stopPush = false;
671
- const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
672
- let pull = async () => {
673
- try {
674
- await db.pull();
675
- }
676
- catch (e) {
677
- console.error('pull', e);
678
- }
679
- finally {
680
- if (!stopPull) {
681
- setTimeout(pull, 0);
682
- }
683
- else {
684
- pullResolve();
685
- }
686
- }
687
- };
688
- let push = async () => {
689
- try {
690
- if ((await db.stats()).cdcOperations > 0) {
691
- await db.push();
692
- }
693
- }
694
- catch (e) {
695
- console.error('push', e);
696
- }
697
- finally {
698
- if (!stopPush) {
699
- setTimeout(push, 0);
700
- }
701
- else {
702
- pushResolve();
703
- }
704
- }
705
- };
706
- setTimeout(pull, 0);
707
- setTimeout(push, 0);
708
- for (let i = 0; i < 1000; i++) {
709
- await db.exec(`INSERT INTO q VALUES ('k${i}', 'v${i}')`);
710
- }
711
- await new Promise(resolve => setTimeout(resolve, 1000));
712
- stopPush = true;
713
- await pushFinish;
714
- stopPull = true;
715
- await pullFinish;
716
- console.info(await db.stats());
717
- });
718
- test('checkpoint-and-actions', async () => {
719
- {
720
- const db = await connect({
721
- path: ':memory:',
722
- url: process.env.VITE_TURSO_DB_URL,
723
- longPollTimeoutMs: 100,
724
- });
725
- await db.exec("CREATE TABLE IF NOT EXISTS rows(key TEXT PRIMARY KEY, value INTEGER)");
726
- await db.exec("DELETE FROM rows");
727
- await db.exec("INSERT INTO rows VALUES ('key', 0)");
728
- await db.push();
729
- await db.close();
730
- }
731
- const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
732
- await db1.exec("PRAGMA busy_timeout=100");
733
- const pull = async function (iterations) {
734
- for (let i = 0; i < iterations; i++) {
735
- try {
736
- await db1.pull();
737
- }
738
- catch (e) {
739
- console.error('pull', e);
740
- }
741
- await new Promise(resolve => setTimeout(resolve, 0));
742
- }
743
- };
744
- const push = async function (iterations) {
745
- for (let i = 0; i < iterations; i++) {
746
- await new Promise(resolve => setTimeout(resolve, 5));
747
- try {
748
- if ((await db1.stats()).cdcOperations > 0) {
749
- const start = performance.now();
750
- await db1.push();
751
- console.info('push', performance.now() - start);
752
- }
753
- }
754
- catch (e) {
755
- console.error('push', e);
756
- }
757
- }
758
- };
759
- let rows = 0;
760
- const run = async function (iterations) {
761
- for (let i = 0; i < iterations; i++) {
762
- await db1.prepare("UPDATE rows SET value = value + 1 WHERE key = ?").run('key');
763
- rows += 1;
764
- const { cnt } = await db1.prepare("SELECT value as cnt FROM rows WHERE key = ?").get(['key']);
765
- console.info('CHECK', cnt, rows);
766
- expect(cnt).toBe(rows);
767
- await new Promise(resolve => setTimeout(resolve, 10 * (1 + Math.random())));
768
- }
769
- };
770
- // await run(100);
771
- await Promise.all([pull(40), push(40), run(100)]);
772
- });
773
- test('transform', async () => {
774
- {
775
- const db = await connect({
776
- path: ':memory:',
777
- url: process.env.VITE_TURSO_DB_URL,
778
- });
779
- await db.exec("CREATE TABLE IF NOT EXISTS counter(key TEXT PRIMARY KEY, value INTEGER)");
780
- await db.exec("DELETE FROM counter");
781
- await db.exec("INSERT INTO counter VALUES ('1', 0)");
782
- await db.push();
783
- await db.close();
784
- }
785
- const transform = (m) => ({
786
- operation: 'rewrite',
787
- stmt: {
788
- sql: `UPDATE counter SET value = value + ? WHERE key = ?`,
789
- values: [m.after.value - m.before.value, m.after.key]
790
- }
791
- });
792
- const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform });
793
- const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform });
794
- await db1.exec("UPDATE counter SET value = value + 1 WHERE key = '1'");
795
- await db2.exec("UPDATE counter SET value = value + 1 WHERE key = '1'");
796
- await Promise.all([db1.push(), db2.push()]);
797
- await Promise.all([db1.pull(), db2.pull()]);
798
- const rows1 = await db1.prepare('SELECT * FROM counter').all();
799
- const rows2 = await db2.prepare('SELECT * FROM counter').all();
800
- expect(rows1).toEqual([{ key: '1', value: 2 }]);
801
- expect(rows2).toEqual([{ key: '1', value: 2 }]);
802
- });
803
- test('transform-many', async () => {
804
- {
805
- const db = await connect({
806
- path: ':memory:',
807
- url: process.env.VITE_TURSO_DB_URL,
808
- });
809
- await db.exec("CREATE TABLE IF NOT EXISTS counter(key TEXT PRIMARY KEY, value INTEGER)");
810
- await db.exec("DELETE FROM counter");
811
- await db.exec("INSERT INTO counter VALUES ('1', 0)");
812
- await db.push();
813
- await db.close();
814
- }
815
- const transform = (m) => ({
816
- operation: 'rewrite',
817
- stmt: {
818
- sql: `UPDATE counter SET value = value + ? WHERE key = ?`,
819
- values: [m.after.value - m.before.value, m.after.key]
820
- }
821
- });
822
- const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform });
823
- const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform });
824
- for (let i = 0; i < 1002; i++) {
825
- await db1.exec("UPDATE counter SET value = value + 1 WHERE key = '1'");
826
- }
827
- for (let i = 0; i < 1001; i++) {
828
- await db2.exec("UPDATE counter SET value = value + 1 WHERE key = '1'");
829
- }
830
- let start = performance.now();
831
- await Promise.all([db1.push(), db2.push()]);
832
- console.info('push', performance.now() - start);
833
- start = performance.now();
834
- await Promise.all([db1.pull(), db2.pull()]);
835
- console.info('pull', performance.now() - start);
836
- const rows1 = await db1.prepare('SELECT * FROM counter').all();
837
- const rows2 = await db2.prepare('SELECT * FROM counter').all();
838
- expect(rows1).toEqual([{ key: '1', value: 1001 + 1002 }]);
839
- expect(rows2).toEqual([{ key: '1', value: 1001 + 1002 }]);
840
- });