@tursodatabase/sync 0.1.5 → 0.2.0-pre.1

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 CHANGED
@@ -1,9 +1,9 @@
1
1
  <p align="center">
2
- <h1 align="center">Turso Sync for JavaScript</h1>
2
+ <h1 align="center">Turso Database for JavaScript in Node</h1>
3
3
  </p>
4
4
 
5
5
  <p align="center">
6
- <a title="JavaScript" target="_blank" href="https://www.npmjs.com/package/@tursodatabase/sync"><img alt="npm" src="https://img.shields.io/npm/v/@tursodatabase/sync"></a>
6
+ <a title="JavaScript" target="_blank" href="https://www.npmjs.com/package/@tursodatabase/database"><img alt="npm" src="https://img.shields.io/npm/v/@tursodatabase/database"></a>
7
7
  <a title="MIT" target="_blank" href="https://github.com/tursodatabase/turso/blob/main/LICENSE.md"><img src="http://img.shields.io/badge/license-MIT-orange.svg?style=flat-square"></a>
8
8
  </p>
9
9
  <p align="center">
@@ -14,40 +14,105 @@
14
14
 
15
15
  ## About
16
16
 
17
- This package is for syncing local Turso databases to the Turso Cloud and back.
17
+ This package is the Turso embedded database library for JavaScript in Node.
18
18
 
19
19
  > **⚠️ Warning:** This software is ALPHA, only use for development, testing, and experimentation. We are working to make it production ready, but do not use it for critical data right now.
20
20
 
21
+ ## Features
22
+
23
+ - **SQLite compatible:** SQLite query language and file format support ([status](https://github.com/tursodatabase/turso/blob/main/COMPAT.md)).
24
+ - **In-process**: No network overhead, runs directly in your Node.js process
25
+ - **TypeScript support**: Full TypeScript definitions included
26
+ - **Cross-platform**: Supports Linux (x86 and arm64), macOS, Windows (browser is supported in the separate package `@tursodatabase/database-browser` package)
27
+
21
28
  ## Installation
22
29
 
23
30
  ```bash
24
- npm install @tursodatabase/sync
31
+ npm install @tursodatabase/database
25
32
  ```
26
33
 
27
34
  ## Getting Started
28
35
 
29
- To sync a database hosted at [Turso Cloud](https://turso.tech):
36
+ ### In-Memory Database
37
+
38
+ ```javascript
39
+ import { connect } from '@tursodatabase/database';
40
+
41
+ // Create an in-memory database
42
+ const db = await connect(':memory:');
43
+
44
+ // Create a table
45
+ await db.exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)');
46
+
47
+ // Insert data
48
+ const insert = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)');
49
+ await insert.run('Alice', 'alice@example.com');
50
+ await insert.run('Bob', 'bob@example.com');
51
+
52
+ // Query data
53
+ const users = await db.prepare('SELECT * FROM users').all();
54
+ console.log(users);
55
+ // Output: [
56
+ // { id: 1, name: 'Alice', email: 'alice@example.com' },
57
+ // { id: 2, name: 'Bob', email: 'bob@example.com' }
58
+ // ]
59
+ ```
60
+
61
+ ### File-Based Database
62
+
63
+ ```javascript
64
+ import { connect } from '@tursodatabase/database';
30
65
 
31
- ```js
32
- import { connect } from '@tursodatabase/sync';
66
+ // Create or open a database file
67
+ const db = await connect('my-database.db');
33
68
 
34
- const db = await connect({
35
- path: 'local.db', // path used as a prefix for local files created by sync-engine
36
- url: 'https://<db>.turso.io', // URL of the remote database: turso db show <db>
37
- authToken: '...', // auth token issued from the Turso Cloud: turso db tokens create <db>
38
- clientName: 'turso-sync-example' // arbitrary client name
69
+ // Create a table
70
+ await db.exec(`
71
+ CREATE TABLE IF NOT EXISTS posts (
72
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
73
+ title TEXT NOT NULL,
74
+ content TEXT,
75
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
76
+ )
77
+ `);
78
+
79
+ // Insert a post
80
+ const insertPost = db.prepare('INSERT INTO posts (title, content) VALUES (?, ?)');
81
+ const result = await insertPost.run('Hello World', 'This is my first blog post!');
82
+
83
+ console.log(`Inserted post with ID: ${result.lastInsertRowid}`);
84
+ ```
85
+
86
+ ### Transactions
87
+
88
+ ```javascript
89
+ import { connect } from '@tursodatabase/database';
90
+
91
+ const db = await connect('transactions.db');
92
+
93
+ // Using transactions for atomic operations
94
+ const transaction = db.transaction(async (users) => {
95
+ const insert = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)');
96
+ for (const user of users) {
97
+ await insert.run(user.name, user.email);
98
+ }
39
99
  });
40
100
 
41
- // db has same functions as Database class from @tursodatabase/database package but adds few more methods for sync:
42
- await db.pull(); // pull changes from the remote
43
- await db.push(); // push changes to the remote
44
- await db.sync(); // pull & push changes
101
+ // Execute transaction
102
+ await transaction([
103
+ { name: 'Alice', email: 'alice@example.com' },
104
+ { name: 'Bob', email: 'bob@example.com' }
105
+ ]);
45
106
  ```
46
107
 
108
+ ## API Reference
109
+
110
+ For complete API documentation, see [JavaScript API Reference](../../../../docs/javascript-api-reference.md).
111
+
47
112
  ## Related Packages
48
113
 
49
- * The [@tursodatabase/database](https://www.npmjs.com/package/@tursodatabase/database) package provides the Turso in-memory database, compatible with SQLite.
50
114
  * The [@tursodatabase/serverless](https://www.npmjs.com/package/@tursodatabase/serverless) package provides a serverless driver with the same API.
115
+ * The [@tursodatabase/sync](https://www.npmjs.com/package/@tursodatabase/sync) package provides bidirectional sync between a local Turso database and Turso Cloud.
51
116
 
52
117
  ## License
53
118
 
@@ -57,4 +122,4 @@ This project is licensed under the [MIT license](../../LICENSE.md).
57
122
 
58
123
  - [GitHub Issues](https://github.com/tursodatabase/turso/issues)
59
124
  - [Documentation](https://docs.turso.tech)
60
- - [Discord Community](https://tur.so/discord)
125
+ - [Discord Community](https://tur.so/discord)
@@ -0,0 +1,30 @@
1
+ import { DatabasePromise, DatabaseOpts, NativeDatabase } from "@tursodatabase/database-common";
2
+ import { ProtocolIo, SyncOpts, RunOpts, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult } from "@tursodatabase/sync-common";
3
+ declare class Database extends DatabasePromise {
4
+ runOpts: RunOpts;
5
+ engine: any;
6
+ io: ProtocolIo;
7
+ constructor(db: NativeDatabase, io: ProtocolIo, runOpts: RunOpts, engine: any, opts?: DatabaseOpts);
8
+ sync(): Promise<void>;
9
+ pull(): Promise<void>;
10
+ push(): Promise<void>;
11
+ checkpoint(): Promise<void>;
12
+ stats(): Promise<{
13
+ operations: number;
14
+ mainWal: number;
15
+ revertWal: number;
16
+ lastPullUnixTime: number;
17
+ lastPushUnixTime: number | null;
18
+ }>;
19
+ close(): Promise<void>;
20
+ }
21
+ /**
22
+ * Creates a new database connection asynchronously.
23
+ *
24
+ * @param {string} path - Path to the database file.
25
+ * @param {Object} opts - Options for database behavior.
26
+ * @returns {Promise<Database>} - A promise that resolves to a Database instance.
27
+ */
28
+ declare function connect(opts: SyncOpts): Promise<Database>;
29
+ export { connect, Database, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult };
30
+ //# sourceMappingURL=promise.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"promise.d.ts","sourceRoot":"","sources":["../promise.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA;AAC9F,OAAO,EAAE,UAAU,EAAO,QAAQ,EAAE,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAwCvJ,cAAM,QAAS,SAAQ,eAAe;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,GAAG,CAAC;IACZ,EAAE,EAAE,UAAU,CAAC;gBACH,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,GAAE,YAAiB;IAMhG,IAAI;IAGJ,IAAI;IAGJ,IAAI;IAGJ,UAAU;IAGV,KAAK,IAAI,OAAO,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAG9H,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAGxC;AAED;;;;;;GAMG;AACH,iBAAe,OAAO,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAuBxD;AAED,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,0BAA0B,EAAE,CAAA"}
@@ -0,0 +1,102 @@
1
+ import { DatabasePromise } from "@tursodatabase/database-common";
2
+ import { run } from "@tursodatabase/sync-common";
3
+ import { SyncEngine } from "#index";
4
+ import { promises } from "node:fs";
5
+ let NodeIO = {
6
+ async read(path) {
7
+ try {
8
+ return await promises.readFile(path);
9
+ }
10
+ catch (error) {
11
+ if (error.code === 'ENOENT') {
12
+ return null;
13
+ }
14
+ throw error;
15
+ }
16
+ },
17
+ async write(path, data) {
18
+ const unix = Math.floor(Date.now() / 1000);
19
+ const nonce = Math.floor(Math.random() * 1000000000);
20
+ const tmp = `${path}.tmp.${unix}.${nonce}`;
21
+ await promises.writeFile(tmp, new Uint8Array(data));
22
+ try {
23
+ await promises.rename(tmp, path);
24
+ }
25
+ catch (err) {
26
+ await promises.unlink(tmp);
27
+ throw err;
28
+ }
29
+ }
30
+ };
31
+ function memoryIO() {
32
+ let values = new Map();
33
+ return {
34
+ async read(path) {
35
+ return values.get(path);
36
+ },
37
+ async write(path, data) {
38
+ values.set(path, data);
39
+ }
40
+ };
41
+ }
42
+ ;
43
+ class Database extends DatabasePromise {
44
+ runOpts;
45
+ engine;
46
+ io;
47
+ constructor(db, io, runOpts, engine, opts = {}) {
48
+ super(db, opts);
49
+ this.runOpts = runOpts;
50
+ this.engine = engine;
51
+ this.io = io;
52
+ }
53
+ async sync() {
54
+ await run(this.runOpts, this.io, this.engine, this.engine.sync());
55
+ }
56
+ async pull() {
57
+ await run(this.runOpts, this.io, this.engine, this.engine.pull());
58
+ }
59
+ async push() {
60
+ await run(this.runOpts, this.io, this.engine, this.engine.push());
61
+ }
62
+ async checkpoint() {
63
+ await run(this.runOpts, this.io, this.engine, this.engine.checkpoint());
64
+ }
65
+ async stats() {
66
+ return (await run(this.runOpts, this.io, this.engine, this.engine.stats()));
67
+ }
68
+ async close() {
69
+ this.engine.close();
70
+ }
71
+ }
72
+ /**
73
+ * Creates a new database connection asynchronously.
74
+ *
75
+ * @param {string} path - Path to the database file.
76
+ * @param {Object} opts - Options for database behavior.
77
+ * @returns {Promise<Database>} - A promise that resolves to a Database instance.
78
+ */
79
+ async function connect(opts) {
80
+ const engine = new SyncEngine({
81
+ path: opts.path,
82
+ clientName: opts.clientName,
83
+ tablesIgnore: opts.tablesIgnore,
84
+ useTransform: opts.transform != null,
85
+ tracing: opts.tracing,
86
+ protocolVersion: 1
87
+ });
88
+ const runOpts = {
89
+ url: opts.url,
90
+ headers: {
91
+ ...(opts.authToken != null && { "Authorization": `Bearer ${opts.authToken}` }),
92
+ ...(opts.encryptionKey != null && { "x-turso-encryption-key": opts.encryptionKey })
93
+ },
94
+ preemptionMs: 1,
95
+ transform: opts.transform,
96
+ };
97
+ let io = opts.path == ':memory:' ? memoryIO() : NodeIO;
98
+ await run(runOpts, io, engine, engine.init());
99
+ const nativeDb = engine.open();
100
+ return new Database(nativeDb, io, runOpts, engine, {});
101
+ }
102
+ export { connect, Database };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=promise.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"promise.test.d.ts","sourceRoot":"","sources":["../promise.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,259 @@
1
+ import { unlinkSync } from "node:fs";
2
+ import { expect, test } from 'vitest';
3
+ import { connect } from './promise.js';
4
+ const localeCompare = (a, b) => a.x.localeCompare(b.x);
5
+ test('select-after-push', async () => {
6
+ {
7
+ const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
8
+ await db.exec("CREATE TABLE IF NOT EXISTS t(x)");
9
+ await db.exec("DELETE FROM t");
10
+ await db.push();
11
+ await db.close();
12
+ }
13
+ {
14
+ const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
15
+ await db.exec("INSERT INTO t VALUES (1), (2), (3)");
16
+ await db.push();
17
+ }
18
+ {
19
+ const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
20
+ const rows = await db.prepare('SELECT * FROM t').all();
21
+ expect(rows).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]);
22
+ }
23
+ });
24
+ test('select-without-push', async () => {
25
+ {
26
+ const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
27
+ await db.exec("CREATE TABLE IF NOT EXISTS t(x)");
28
+ await db.exec("DELETE FROM t");
29
+ await db.push();
30
+ await db.close();
31
+ }
32
+ {
33
+ const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
34
+ await db.exec("INSERT INTO t VALUES (1), (2), (3)");
35
+ }
36
+ {
37
+ const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
38
+ const rows = await db.prepare('SELECT * FROM t').all();
39
+ expect(rows).toEqual([]);
40
+ }
41
+ });
42
+ test('merge-non-overlapping-keys', async () => {
43
+ {
44
+ const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
45
+ await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)");
46
+ await db.exec("DELETE FROM q");
47
+ await db.push();
48
+ await db.close();
49
+ }
50
+ const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
51
+ await db1.exec("INSERT INTO q VALUES ('k1', 'value1'), ('k2', 'value2')");
52
+ const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
53
+ await db2.exec("INSERT INTO q VALUES ('k3', 'value3'), ('k4', 'value4'), ('k5', 'value5')");
54
+ await Promise.all([db1.push(), db2.push()]);
55
+ await Promise.all([db1.pull(), db2.pull()]);
56
+ const rows1 = await db1.prepare('SELECT * FROM q').all();
57
+ const rows2 = await db1.prepare('SELECT * FROM q').all();
58
+ const expected = [{ x: 'k1', y: 'value1' }, { x: 'k2', y: 'value2' }, { x: 'k3', y: 'value3' }, { x: 'k4', y: 'value4' }, { x: 'k5', y: 'value5' }];
59
+ expect(rows1.sort(localeCompare)).toEqual(expected.sort(localeCompare));
60
+ expect(rows2.sort(localeCompare)).toEqual(expected.sort(localeCompare));
61
+ });
62
+ test('last-push-wins', async () => {
63
+ {
64
+ const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
65
+ await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)");
66
+ await db.exec("DELETE FROM q");
67
+ await db.push();
68
+ await db.close();
69
+ }
70
+ const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
71
+ await db1.exec("INSERT INTO q VALUES ('k1', 'value1'), ('k2', 'value2'), ('k4', 'value4')");
72
+ const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
73
+ await db2.exec("INSERT INTO q VALUES ('k1', 'value3'), ('k2', 'value4'), ('k3', 'value5')");
74
+ await db2.push();
75
+ await db1.push();
76
+ await Promise.all([db1.pull(), db2.pull()]);
77
+ const rows1 = await db1.prepare('SELECT * FROM q').all();
78
+ const rows2 = await db1.prepare('SELECT * FROM q').all();
79
+ const expected = [{ x: 'k1', y: 'value1' }, { x: 'k2', y: 'value2' }, { x: 'k3', y: 'value5' }, { x: 'k4', y: 'value4' }];
80
+ expect(rows1.sort(localeCompare)).toEqual(expected.sort(localeCompare));
81
+ expect(rows2.sort(localeCompare)).toEqual(expected.sort(localeCompare));
82
+ });
83
+ test('last-push-wins-with-delete', async () => {
84
+ {
85
+ const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
86
+ await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)");
87
+ await db.exec("DELETE FROM q");
88
+ await db.push();
89
+ await db.close();
90
+ }
91
+ const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
92
+ await db1.exec("INSERT INTO q VALUES ('k1', 'value1'), ('k2', 'value2'), ('k4', 'value4')");
93
+ await db1.exec("DELETE FROM q");
94
+ const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
95
+ await db2.exec("INSERT INTO q VALUES ('k1', 'value3'), ('k2', 'value4'), ('k3', 'value5')");
96
+ await db2.push();
97
+ await db1.push();
98
+ await Promise.all([db1.pull(), db2.pull()]);
99
+ const rows1 = await db1.prepare('SELECT * FROM q').all();
100
+ const rows2 = await db1.prepare('SELECT * FROM q').all();
101
+ const expected = [{ x: 'k3', y: 'value5' }];
102
+ expect(rows1).toEqual(expected);
103
+ expect(rows2).toEqual(expected);
104
+ });
105
+ test('constraint-conflict', async () => {
106
+ {
107
+ const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
108
+ await db.exec("CREATE TABLE IF NOT EXISTS u(x TEXT PRIMARY KEY, y UNIQUE)");
109
+ await db.exec("DELETE FROM u");
110
+ await db.push();
111
+ await db.close();
112
+ }
113
+ const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
114
+ await db1.exec("INSERT INTO u VALUES ('k1', 'value1')");
115
+ const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
116
+ await db2.exec("INSERT INTO u VALUES ('k2', 'value1')");
117
+ await db1.push();
118
+ await expect(async () => await db2.push()).rejects.toThrow('SQLite error: UNIQUE constraint failed: u.y');
119
+ });
120
+ test('checkpoint', async () => {
121
+ {
122
+ const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
123
+ await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)");
124
+ await db.exec("DELETE FROM q");
125
+ await db.push();
126
+ await db.close();
127
+ }
128
+ const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
129
+ for (let i = 0; i < 1000; i++) {
130
+ await db1.exec(`INSERT INTO q VALUES ('k${i}', 'v${i}')`);
131
+ }
132
+ expect((await db1.stats()).mainWal).toBeGreaterThan(4096 * 1000);
133
+ await db1.checkpoint();
134
+ expect((await db1.stats()).mainWal).toBe(0);
135
+ let revertWal = (await db1.stats()).revertWal;
136
+ expect(revertWal).toBeLessThan(4096 * 1000 / 100);
137
+ for (let i = 0; i < 1000; i++) {
138
+ await db1.exec(`UPDATE q SET y = 'u${i}' WHERE x = 'k${i}'`);
139
+ }
140
+ await db1.checkpoint();
141
+ expect((await db1.stats()).revertWal).toBe(revertWal);
142
+ });
143
+ test('persistence', async () => {
144
+ {
145
+ const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
146
+ await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)");
147
+ await db.exec("DELETE FROM q");
148
+ await db.push();
149
+ await db.close();
150
+ }
151
+ const path = `test-${(Math.random() * 10000) | 0}.db`;
152
+ try {
153
+ {
154
+ const db1 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL });
155
+ await db1.exec(`INSERT INTO q VALUES ('k1', 'v1')`);
156
+ await db1.exec(`INSERT INTO q VALUES ('k2', 'v2')`);
157
+ await db1.close();
158
+ }
159
+ {
160
+ const db2 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL });
161
+ await db2.exec(`INSERT INTO q VALUES ('k3', 'v3')`);
162
+ await db2.exec(`INSERT INTO q VALUES ('k4', 'v4')`);
163
+ const rows = await db2.prepare('SELECT * FROM q').all();
164
+ const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }, { x: 'k3', y: 'v3' }, { x: 'k4', y: 'v4' }];
165
+ expect(rows).toEqual(expected);
166
+ await db2.close();
167
+ }
168
+ {
169
+ const db3 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL });
170
+ await db3.push();
171
+ await db3.close();
172
+ }
173
+ {
174
+ const db4 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL });
175
+ const rows = await db4.prepare('SELECT * FROM q').all();
176
+ const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }, { x: 'k3', y: 'v3' }, { x: 'k4', y: 'v4' }];
177
+ expect(rows).toEqual(expected);
178
+ await db4.close();
179
+ }
180
+ }
181
+ finally {
182
+ unlinkSync(path);
183
+ unlinkSync(`${path}-wal`);
184
+ unlinkSync(`${path}-info`);
185
+ unlinkSync(`${path}-changes`);
186
+ try {
187
+ unlinkSync(`${path}-revert`);
188
+ }
189
+ catch (e) { }
190
+ }
191
+ });
192
+ test('transform', async () => {
193
+ {
194
+ const db = await connect({
195
+ path: ':memory:',
196
+ url: process.env.VITE_TURSO_DB_URL,
197
+ });
198
+ await db.exec("CREATE TABLE IF NOT EXISTS counter(key TEXT PRIMARY KEY, value INTEGER)");
199
+ await db.exec("DELETE FROM counter");
200
+ await db.exec("INSERT INTO counter VALUES ('1', 0)");
201
+ await db.push();
202
+ await db.close();
203
+ }
204
+ const transform = (m) => ({
205
+ operation: 'rewrite',
206
+ stmt: {
207
+ sql: `UPDATE counter SET value = value + ? WHERE key = ?`,
208
+ values: [m.after.value - m.before.value, m.after.key]
209
+ }
210
+ });
211
+ const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform });
212
+ const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform });
213
+ await db1.exec("UPDATE counter SET value = value + 1 WHERE key = '1'");
214
+ await db2.exec("UPDATE counter SET value = value + 1 WHERE key = '1'");
215
+ await Promise.all([db1.push(), db2.push()]);
216
+ await Promise.all([db1.pull(), db2.pull()]);
217
+ const rows1 = await db1.prepare('SELECT * FROM counter').all();
218
+ const rows2 = await db2.prepare('SELECT * FROM counter').all();
219
+ expect(rows1).toEqual([{ key: '1', value: 2 }]);
220
+ expect(rows2).toEqual([{ key: '1', value: 2 }]);
221
+ });
222
+ test('transform-many', async () => {
223
+ {
224
+ const db = await connect({
225
+ path: ':memory:',
226
+ url: process.env.VITE_TURSO_DB_URL,
227
+ });
228
+ await db.exec("CREATE TABLE IF NOT EXISTS counter(key TEXT PRIMARY KEY, value INTEGER)");
229
+ await db.exec("DELETE FROM counter");
230
+ await db.exec("INSERT INTO counter VALUES ('1', 0)");
231
+ await db.push();
232
+ await db.close();
233
+ }
234
+ const transform = (m) => ({
235
+ operation: 'rewrite',
236
+ stmt: {
237
+ sql: `UPDATE counter SET value = value + ? WHERE key = ?`,
238
+ values: [m.after.value - m.before.value, m.after.key]
239
+ }
240
+ });
241
+ const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform });
242
+ const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform });
243
+ for (let i = 0; i < 1002; i++) {
244
+ await db1.exec("UPDATE counter SET value = value + 1 WHERE key = '1'");
245
+ }
246
+ for (let i = 0; i < 1001; i++) {
247
+ await db2.exec("UPDATE counter SET value = value + 1 WHERE key = '1'");
248
+ }
249
+ let start = performance.now();
250
+ await Promise.all([db1.push(), db2.push()]);
251
+ console.info('push', performance.now() - start);
252
+ start = performance.now();
253
+ await Promise.all([db1.pull(), db2.pull()]);
254
+ console.info('pull', performance.now() - start);
255
+ const rows1 = await db1.prepare('SELECT * FROM counter').all();
256
+ const rows2 = await db2.prepare('SELECT * FROM counter').all();
257
+ expect(rows1).toEqual([{ key: '1', value: 1001 + 1002 }]);
258
+ expect(rows2).toEqual([{ key: '1', value: 1001 + 1002 }]);
259
+ });
package/index.js CHANGED
@@ -74,23 +74,33 @@ function requireNative() {
74
74
  } else if (process.platform === 'android') {
75
75
  if (process.arch === 'arm64') {
76
76
  try {
77
- return require('./turso-sync-js.android-arm64.node')
77
+ return require('./sync.android-arm64.node')
78
78
  } catch (e) {
79
79
  loadErrors.push(e)
80
80
  }
81
81
  try {
82
- return require('@tursodatabase/sync-android-arm64')
82
+ const binding = require('@tursodatabase/sync-android-arm64')
83
+ const bindingPackageVersion = require('@tursodatabase/sync-android-arm64/package.json').version
84
+ if (bindingPackageVersion !== '0.1.5' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
85
+ throw new Error(`Native binding package version mismatch, expected 0.1.5 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
86
+ }
87
+ return binding
83
88
  } catch (e) {
84
89
  loadErrors.push(e)
85
90
  }
86
91
  } else if (process.arch === 'arm') {
87
92
  try {
88
- return require('./turso-sync-js.android-arm-eabi.node')
93
+ return require('./sync.android-arm-eabi.node')
89
94
  } catch (e) {
90
95
  loadErrors.push(e)
91
96
  }
92
97
  try {
93
- return require('@tursodatabase/sync-android-arm-eabi')
98
+ const binding = require('@tursodatabase/sync-android-arm-eabi')
99
+ const bindingPackageVersion = require('@tursodatabase/sync-android-arm-eabi/package.json').version
100
+ if (bindingPackageVersion !== '0.1.5' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
101
+ throw new Error(`Native binding package version mismatch, expected 0.1.5 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
102
+ }
103
+ return binding
94
104
  } catch (e) {
95
105
  loadErrors.push(e)
96
106
  }
@@ -100,34 +110,49 @@ function requireNative() {
100
110
  } else if (process.platform === 'win32') {
101
111
  if (process.arch === 'x64') {
102
112
  try {
103
- return require('./turso-sync-js.win32-x64-msvc.node')
113
+ return require('./sync.win32-x64-msvc.node')
104
114
  } catch (e) {
105
115
  loadErrors.push(e)
106
116
  }
107
117
  try {
108
- return require('@tursodatabase/sync-win32-x64-msvc')
118
+ const binding = require('@tursodatabase/sync-win32-x64-msvc')
119
+ const bindingPackageVersion = require('@tursodatabase/sync-win32-x64-msvc/package.json').version
120
+ if (bindingPackageVersion !== '0.1.5' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
121
+ throw new Error(`Native binding package version mismatch, expected 0.1.5 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
122
+ }
123
+ return binding
109
124
  } catch (e) {
110
125
  loadErrors.push(e)
111
126
  }
112
127
  } else if (process.arch === 'ia32') {
113
128
  try {
114
- return require('./turso-sync-js.win32-ia32-msvc.node')
129
+ return require('./sync.win32-ia32-msvc.node')
115
130
  } catch (e) {
116
131
  loadErrors.push(e)
117
132
  }
118
133
  try {
119
- return require('@tursodatabase/sync-win32-ia32-msvc')
134
+ const binding = require('@tursodatabase/sync-win32-ia32-msvc')
135
+ const bindingPackageVersion = require('@tursodatabase/sync-win32-ia32-msvc/package.json').version
136
+ if (bindingPackageVersion !== '0.1.5' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
137
+ throw new Error(`Native binding package version mismatch, expected 0.1.5 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
138
+ }
139
+ return binding
120
140
  } catch (e) {
121
141
  loadErrors.push(e)
122
142
  }
123
143
  } else if (process.arch === 'arm64') {
124
144
  try {
125
- return require('./turso-sync-js.win32-arm64-msvc.node')
145
+ return require('./sync.win32-arm64-msvc.node')
126
146
  } catch (e) {
127
147
  loadErrors.push(e)
128
148
  }
129
149
  try {
130
- return require('@tursodatabase/sync-win32-arm64-msvc')
150
+ const binding = require('@tursodatabase/sync-win32-arm64-msvc')
151
+ const bindingPackageVersion = require('@tursodatabase/sync-win32-arm64-msvc/package.json').version
152
+ if (bindingPackageVersion !== '0.1.5' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
153
+ throw new Error(`Native binding package version mismatch, expected 0.1.5 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
154
+ }
155
+ return binding
131
156
  } catch (e) {
132
157
  loadErrors.push(e)
133
158
  }
@@ -136,34 +161,49 @@ function requireNative() {
136
161
  }
137
162
  } else if (process.platform === 'darwin') {
138
163
  try {
139
- return require('./turso-sync-js.darwin-universal.node')
164
+ return require('./sync.darwin-universal.node')
140
165
  } catch (e) {
141
166
  loadErrors.push(e)
142
167
  }
143
168
  try {
144
- return require('@tursodatabase/sync-darwin-universal')
169
+ const binding = require('@tursodatabase/sync-darwin-universal')
170
+ const bindingPackageVersion = require('@tursodatabase/sync-darwin-universal/package.json').version
171
+ if (bindingPackageVersion !== '0.1.5' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
172
+ throw new Error(`Native binding package version mismatch, expected 0.1.5 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
173
+ }
174
+ return binding
145
175
  } catch (e) {
146
176
  loadErrors.push(e)
147
177
  }
148
178
  if (process.arch === 'x64') {
149
179
  try {
150
- return require('./turso-sync-js.darwin-x64.node')
180
+ return require('./sync.darwin-x64.node')
151
181
  } catch (e) {
152
182
  loadErrors.push(e)
153
183
  }
154
184
  try {
155
- return require('@tursodatabase/sync-darwin-x64')
185
+ const binding = require('@tursodatabase/sync-darwin-x64')
186
+ const bindingPackageVersion = require('@tursodatabase/sync-darwin-x64/package.json').version
187
+ if (bindingPackageVersion !== '0.1.5' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
188
+ throw new Error(`Native binding package version mismatch, expected 0.1.5 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
189
+ }
190
+ return binding
156
191
  } catch (e) {
157
192
  loadErrors.push(e)
158
193
  }
159
194
  } else if (process.arch === 'arm64') {
160
195
  try {
161
- return require('./turso-sync-js.darwin-arm64.node')
196
+ return require('./sync.darwin-arm64.node')
162
197
  } catch (e) {
163
198
  loadErrors.push(e)
164
199
  }
165
200
  try {
166
- return require('@tursodatabase/sync-darwin-arm64')
201
+ const binding = require('@tursodatabase/sync-darwin-arm64')
202
+ const bindingPackageVersion = require('@tursodatabase/sync-darwin-arm64/package.json').version
203
+ if (bindingPackageVersion !== '0.1.5' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
204
+ throw new Error(`Native binding package version mismatch, expected 0.1.5 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
205
+ }
206
+ return binding
167
207
  } catch (e) {
168
208
  loadErrors.push(e)
169
209
  }
@@ -173,23 +213,33 @@ function requireNative() {
173
213
  } else if (process.platform === 'freebsd') {
174
214
  if (process.arch === 'x64') {
175
215
  try {
176
- return require('./turso-sync-js.freebsd-x64.node')
216
+ return require('./sync.freebsd-x64.node')
177
217
  } catch (e) {
178
218
  loadErrors.push(e)
179
219
  }
180
220
  try {
181
- return require('@tursodatabase/sync-freebsd-x64')
221
+ const binding = require('@tursodatabase/sync-freebsd-x64')
222
+ const bindingPackageVersion = require('@tursodatabase/sync-freebsd-x64/package.json').version
223
+ if (bindingPackageVersion !== '0.1.5' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
224
+ throw new Error(`Native binding package version mismatch, expected 0.1.5 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
225
+ }
226
+ return binding
182
227
  } catch (e) {
183
228
  loadErrors.push(e)
184
229
  }
185
230
  } else if (process.arch === 'arm64') {
186
231
  try {
187
- return require('./turso-sync-js.freebsd-arm64.node')
232
+ return require('./sync.freebsd-arm64.node')
188
233
  } catch (e) {
189
234
  loadErrors.push(e)
190
235
  }
191
236
  try {
192
- return require('@tursodatabase/sync-freebsd-arm64')
237
+ const binding = require('@tursodatabase/sync-freebsd-arm64')
238
+ const bindingPackageVersion = require('@tursodatabase/sync-freebsd-arm64/package.json').version
239
+ if (bindingPackageVersion !== '0.1.5' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
240
+ throw new Error(`Native binding package version mismatch, expected 0.1.5 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
241
+ }
242
+ return binding
193
243
  } catch (e) {
194
244
  loadErrors.push(e)
195
245
  }
@@ -200,23 +250,33 @@ function requireNative() {
200
250
  if (process.arch === 'x64') {
201
251
  if (isMusl()) {
202
252
  try {
203
- return require('./turso-sync-js.linux-x64-musl.node')
253
+ return require('./sync.linux-x64-musl.node')
204
254
  } catch (e) {
205
255
  loadErrors.push(e)
206
256
  }
207
257
  try {
208
- return require('@tursodatabase/sync-linux-x64-musl')
258
+ const binding = require('@tursodatabase/sync-linux-x64-musl')
259
+ const bindingPackageVersion = require('@tursodatabase/sync-linux-x64-musl/package.json').version
260
+ if (bindingPackageVersion !== '0.1.5' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
261
+ throw new Error(`Native binding package version mismatch, expected 0.1.5 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
262
+ }
263
+ return binding
209
264
  } catch (e) {
210
265
  loadErrors.push(e)
211
266
  }
212
267
  } else {
213
268
  try {
214
- return require('./turso-sync-js.linux-x64-gnu.node')
269
+ return require('./sync.linux-x64-gnu.node')
215
270
  } catch (e) {
216
271
  loadErrors.push(e)
217
272
  }
218
273
  try {
219
- return require('@tursodatabase/sync-linux-x64-gnu')
274
+ const binding = require('@tursodatabase/sync-linux-x64-gnu')
275
+ const bindingPackageVersion = require('@tursodatabase/sync-linux-x64-gnu/package.json').version
276
+ if (bindingPackageVersion !== '0.1.5' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
277
+ throw new Error(`Native binding package version mismatch, expected 0.1.5 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
278
+ }
279
+ return binding
220
280
  } catch (e) {
221
281
  loadErrors.push(e)
222
282
  }
@@ -224,23 +284,33 @@ function requireNative() {
224
284
  } else if (process.arch === 'arm64') {
225
285
  if (isMusl()) {
226
286
  try {
227
- return require('./turso-sync-js.linux-arm64-musl.node')
287
+ return require('./sync.linux-arm64-musl.node')
228
288
  } catch (e) {
229
289
  loadErrors.push(e)
230
290
  }
231
291
  try {
232
- return require('@tursodatabase/sync-linux-arm64-musl')
292
+ const binding = require('@tursodatabase/sync-linux-arm64-musl')
293
+ const bindingPackageVersion = require('@tursodatabase/sync-linux-arm64-musl/package.json').version
294
+ if (bindingPackageVersion !== '0.1.5' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
295
+ throw new Error(`Native binding package version mismatch, expected 0.1.5 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
296
+ }
297
+ return binding
233
298
  } catch (e) {
234
299
  loadErrors.push(e)
235
300
  }
236
301
  } else {
237
302
  try {
238
- return require('./turso-sync-js.linux-arm64-gnu.node')
303
+ return require('./sync.linux-arm64-gnu.node')
239
304
  } catch (e) {
240
305
  loadErrors.push(e)
241
306
  }
242
307
  try {
243
- return require('@tursodatabase/sync-linux-arm64-gnu')
308
+ const binding = require('@tursodatabase/sync-linux-arm64-gnu')
309
+ const bindingPackageVersion = require('@tursodatabase/sync-linux-arm64-gnu/package.json').version
310
+ if (bindingPackageVersion !== '0.1.5' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
311
+ throw new Error(`Native binding package version mismatch, expected 0.1.5 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
312
+ }
313
+ return binding
244
314
  } catch (e) {
245
315
  loadErrors.push(e)
246
316
  }
@@ -248,23 +318,33 @@ function requireNative() {
248
318
  } else if (process.arch === 'arm') {
249
319
  if (isMusl()) {
250
320
  try {
251
- return require('./turso-sync-js.linux-arm-musleabihf.node')
321
+ return require('./sync.linux-arm-musleabihf.node')
252
322
  } catch (e) {
253
323
  loadErrors.push(e)
254
324
  }
255
325
  try {
256
- return require('@tursodatabase/sync-linux-arm-musleabihf')
326
+ const binding = require('@tursodatabase/sync-linux-arm-musleabihf')
327
+ const bindingPackageVersion = require('@tursodatabase/sync-linux-arm-musleabihf/package.json').version
328
+ if (bindingPackageVersion !== '0.1.5' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
329
+ throw new Error(`Native binding package version mismatch, expected 0.1.5 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
330
+ }
331
+ return binding
257
332
  } catch (e) {
258
333
  loadErrors.push(e)
259
334
  }
260
335
  } else {
261
336
  try {
262
- return require('./turso-sync-js.linux-arm-gnueabihf.node')
337
+ return require('./sync.linux-arm-gnueabihf.node')
263
338
  } catch (e) {
264
339
  loadErrors.push(e)
265
340
  }
266
341
  try {
267
- return require('@tursodatabase/sync-linux-arm-gnueabihf')
342
+ const binding = require('@tursodatabase/sync-linux-arm-gnueabihf')
343
+ const bindingPackageVersion = require('@tursodatabase/sync-linux-arm-gnueabihf/package.json').version
344
+ if (bindingPackageVersion !== '0.1.5' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
345
+ throw new Error(`Native binding package version mismatch, expected 0.1.5 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
346
+ }
347
+ return binding
268
348
  } catch (e) {
269
349
  loadErrors.push(e)
270
350
  }
@@ -272,46 +352,66 @@ function requireNative() {
272
352
  } else if (process.arch === 'riscv64') {
273
353
  if (isMusl()) {
274
354
  try {
275
- return require('./turso-sync-js.linux-riscv64-musl.node')
355
+ return require('./sync.linux-riscv64-musl.node')
276
356
  } catch (e) {
277
357
  loadErrors.push(e)
278
358
  }
279
359
  try {
280
- return require('@tursodatabase/sync-linux-riscv64-musl')
360
+ const binding = require('@tursodatabase/sync-linux-riscv64-musl')
361
+ const bindingPackageVersion = require('@tursodatabase/sync-linux-riscv64-musl/package.json').version
362
+ if (bindingPackageVersion !== '0.1.5' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
363
+ throw new Error(`Native binding package version mismatch, expected 0.1.5 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
364
+ }
365
+ return binding
281
366
  } catch (e) {
282
367
  loadErrors.push(e)
283
368
  }
284
369
  } else {
285
370
  try {
286
- return require('./turso-sync-js.linux-riscv64-gnu.node')
371
+ return require('./sync.linux-riscv64-gnu.node')
287
372
  } catch (e) {
288
373
  loadErrors.push(e)
289
374
  }
290
375
  try {
291
- return require('@tursodatabase/sync-linux-riscv64-gnu')
376
+ const binding = require('@tursodatabase/sync-linux-riscv64-gnu')
377
+ const bindingPackageVersion = require('@tursodatabase/sync-linux-riscv64-gnu/package.json').version
378
+ if (bindingPackageVersion !== '0.1.5' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
379
+ throw new Error(`Native binding package version mismatch, expected 0.1.5 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
380
+ }
381
+ return binding
292
382
  } catch (e) {
293
383
  loadErrors.push(e)
294
384
  }
295
385
  }
296
386
  } else if (process.arch === 'ppc64') {
297
387
  try {
298
- return require('./turso-sync-js.linux-ppc64-gnu.node')
388
+ return require('./sync.linux-ppc64-gnu.node')
299
389
  } catch (e) {
300
390
  loadErrors.push(e)
301
391
  }
302
392
  try {
303
- return require('@tursodatabase/sync-linux-ppc64-gnu')
393
+ const binding = require('@tursodatabase/sync-linux-ppc64-gnu')
394
+ const bindingPackageVersion = require('@tursodatabase/sync-linux-ppc64-gnu/package.json').version
395
+ if (bindingPackageVersion !== '0.1.5' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
396
+ throw new Error(`Native binding package version mismatch, expected 0.1.5 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
397
+ }
398
+ return binding
304
399
  } catch (e) {
305
400
  loadErrors.push(e)
306
401
  }
307
402
  } else if (process.arch === 's390x') {
308
403
  try {
309
- return require('./turso-sync-js.linux-s390x-gnu.node')
404
+ return require('./sync.linux-s390x-gnu.node')
310
405
  } catch (e) {
311
406
  loadErrors.push(e)
312
407
  }
313
408
  try {
314
- return require('@tursodatabase/sync-linux-s390x-gnu')
409
+ const binding = require('@tursodatabase/sync-linux-s390x-gnu')
410
+ const bindingPackageVersion = require('@tursodatabase/sync-linux-s390x-gnu/package.json').version
411
+ if (bindingPackageVersion !== '0.1.5' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
412
+ throw new Error(`Native binding package version mismatch, expected 0.1.5 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
413
+ }
414
+ return binding
315
415
  } catch (e) {
316
416
  loadErrors.push(e)
317
417
  }
@@ -321,34 +421,49 @@ function requireNative() {
321
421
  } else if (process.platform === 'openharmony') {
322
422
  if (process.arch === 'arm64') {
323
423
  try {
324
- return require('./turso-sync-js.linux-arm64-ohos.node')
424
+ return require('./sync.openharmony-arm64.node')
325
425
  } catch (e) {
326
426
  loadErrors.push(e)
327
427
  }
328
428
  try {
329
- return require('@tursodatabase/sync-linux-arm64-ohos')
429
+ const binding = require('@tursodatabase/sync-openharmony-arm64')
430
+ const bindingPackageVersion = require('@tursodatabase/sync-openharmony-arm64/package.json').version
431
+ if (bindingPackageVersion !== '0.1.5' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
432
+ throw new Error(`Native binding package version mismatch, expected 0.1.5 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
433
+ }
434
+ return binding
330
435
  } catch (e) {
331
436
  loadErrors.push(e)
332
437
  }
333
438
  } else if (process.arch === 'x64') {
334
439
  try {
335
- return require('./turso-sync-js.linux-x64-ohos.node')
440
+ return require('./sync.openharmony-x64.node')
336
441
  } catch (e) {
337
442
  loadErrors.push(e)
338
443
  }
339
444
  try {
340
- return require('@tursodatabase/sync-linux-x64-ohos')
445
+ const binding = require('@tursodatabase/sync-openharmony-x64')
446
+ const bindingPackageVersion = require('@tursodatabase/sync-openharmony-x64/package.json').version
447
+ if (bindingPackageVersion !== '0.1.5' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
448
+ throw new Error(`Native binding package version mismatch, expected 0.1.5 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
449
+ }
450
+ return binding
341
451
  } catch (e) {
342
452
  loadErrors.push(e)
343
453
  }
344
454
  } else if (process.arch === 'arm') {
345
455
  try {
346
- return require('./turso-sync-js.linux-arm-ohos.node')
456
+ return require('./sync.openharmony-arm.node')
347
457
  } catch (e) {
348
458
  loadErrors.push(e)
349
459
  }
350
460
  try {
351
- return require('@tursodatabase/sync-linux-arm-ohos')
461
+ const binding = require('@tursodatabase/sync-openharmony-arm')
462
+ const bindingPackageVersion = require('@tursodatabase/sync-openharmony-arm/package.json').version
463
+ if (bindingPackageVersion !== '0.1.5' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
464
+ throw new Error(`Native binding package version mismatch, expected 0.1.5 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
465
+ }
466
+ return binding
352
467
  } catch (e) {
353
468
  loadErrors.push(e)
354
469
  }
@@ -364,7 +479,7 @@ nativeBinding = requireNative()
364
479
 
365
480
  if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) {
366
481
  try {
367
- nativeBinding = require('./turso-sync-js.wasi.cjs')
482
+ nativeBinding = require('./sync.wasi.cjs')
368
483
  } catch (err) {
369
484
  if (process.env.NAPI_RS_FORCE_WASI) {
370
485
  loadErrors.push(err)
@@ -393,14 +508,13 @@ if (!nativeBinding) {
393
508
  throw new Error(`Failed to load native binding`)
394
509
  }
395
510
 
396
- const { Database, Statement, GeneratorHolder, JsDataCompletion, JsDataPollResult, JsProtocolIo, JsProtocolRequestData, SyncEngine, DatabaseChangeTypeJs, SyncEngineProtocolVersion } = nativeBinding
511
+ const { Database, Statement, GeneratorHolder, JsDataCompletion, JsProtocolIo, JsProtocolRequestBytes, SyncEngine, DatabaseChangeTypeJs, SyncEngineProtocolVersion } = nativeBinding
397
512
  export { Database }
398
513
  export { Statement }
399
514
  export { GeneratorHolder }
400
515
  export { JsDataCompletion }
401
- export { JsDataPollResult }
402
516
  export { JsProtocolIo }
403
- export { JsProtocolRequestData }
517
+ export { JsProtocolRequestBytes }
404
518
  export { SyncEngine }
405
519
  export { DatabaseChangeTypeJs }
406
520
  export { SyncEngineProtocolVersion }
package/package.json CHANGED
@@ -1,70 +1,59 @@
1
1
  {
2
2
  "name": "@tursodatabase/sync",
3
- "version": "0.1.5",
3
+ "version": "0.2.0-pre.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/tursodatabase/turso"
7
7
  },
8
- "description": "Sync engine for the Turso database library",
9
- "module": "./dist/sync_engine.js",
10
- "main": "./dist/sync_engine.js",
8
+ "license": "MIT",
9
+ "module": "./dist/promise.js",
10
+ "main": "./dist/promise.js",
11
11
  "type": "module",
12
- "exports": "./dist/sync_engine.js",
12
+ "exports": {
13
+ ".": "./dist/promise.js",
14
+ "./compat": "./dist/compat.js"
15
+ },
13
16
  "files": [
14
- "browser.js",
15
17
  "index.js",
16
- "dist/**"
18
+ "dist/**",
19
+ "README.md"
17
20
  ],
18
- "types": "./dist/sync_engine.d.ts",
21
+ "packageManager": "yarn@4.9.2",
22
+ "devDependencies": {
23
+ "@napi-rs/cli": "^3.1.5",
24
+ "@types/node": "^24.3.1",
25
+ "typescript": "^5.9.2",
26
+ "vitest": "^3.2.4"
27
+ },
28
+ "scripts": {
29
+ "napi-build": "napi build --platform --release --esm --manifest-path ../../Cargo.toml --output-dir .",
30
+ "napi-dirs": "napi create-npm-dirs",
31
+ "napi-artifacts": "napi artifacts --output-dir .",
32
+ "tsc-build": "npm exec tsc",
33
+ "build": "npm run napi-build && npm run tsc-build",
34
+ "test": "VITE_TURSO_DB_URL=http://b--a--a.localhost:10000 vitest --run",
35
+ "prepublishOnly": "npm run napi-dirs && npm run napi-artifacts && napi prepublish -t npm"
36
+ },
19
37
  "napi": {
20
- "binaryName": "turso-sync-js",
38
+ "binaryName": "sync",
21
39
  "targets": [
22
40
  "x86_64-unknown-linux-gnu",
23
41
  "x86_64-pc-windows-msvc",
24
42
  "universal-apple-darwin",
25
- "aarch64-unknown-linux-gnu",
26
- "wasm32-wasip1-threads"
43
+ "aarch64-unknown-linux-gnu"
27
44
  ]
28
45
  },
29
- "license": "MIT",
30
- "devDependencies": {
31
- "@napi-rs/cli": "^3.0.4",
32
- "@napi-rs/wasm-runtime": "^1.0.1",
33
- "@types/node": "^24.2.0",
34
- "ava": "^6.0.1",
35
- "typescript": "^5.9.2"
36
- },
37
- "ava": {
38
- "timeout": "3m"
39
- },
40
- "engines": {
41
- "node": ">= 10"
42
- },
43
- "scripts": {
44
- "artifacts": "napi artifacts",
45
- "build": "npm exec tsc && napi build --platform --release --esm",
46
- "build:debug": "npm exec tsc && napi build --platform",
47
- "prepublishOnly": "npm exec tsc && napi prepublish -t npm",
48
- "test": "true",
49
- "universal": "napi universalize",
50
- "version": "napi version"
46
+ "dependencies": {
47
+ "@tursodatabase/database-common": "^0.2.0-pre.1",
48
+ "@tursodatabase/sync-common": "^0.2.0-pre.1"
51
49
  },
52
- "packageManager": "yarn@4.9.2",
53
50
  "imports": {
54
- "#entry-point": {
55
- "types": "./index.d.ts",
56
- "browser": "./browser.js",
57
- "node": "./index.js"
58
- }
59
- },
60
- "dependencies": {
61
- "@tursodatabase/database": "~0.1.4-pre.5"
51
+ "#index": "./index.js"
62
52
  },
63
53
  "optionalDependencies": {
64
- "@tursodatabase/sync-linux-x64-gnu": "0.1.5",
65
- "@tursodatabase/sync-win32-x64-msvc": "0.1.5",
66
- "@tursodatabase/sync-darwin-universal": "0.1.5",
67
- "@tursodatabase/sync-linux-arm64-gnu": "0.1.5",
68
- "@tursodatabase/sync-wasm32-wasi": "0.1.5"
54
+ "@tursodatabase/sync-linux-x64-gnu": "0.2.0-pre.1",
55
+ "@tursodatabase/sync-win32-x64-msvc": "0.2.0-pre.1",
56
+ "@tursodatabase/sync-darwin-universal": "0.2.0-pre.1",
57
+ "@tursodatabase/sync-linux-arm64-gnu": "0.2.0-pre.1"
69
58
  }
70
59
  }
package/browser.js DELETED
@@ -1 +0,0 @@
1
- export * from '@tursodatabase/sync-wasm32-wasi'
@@ -1,24 +0,0 @@
1
- import { DatabaseRowMutationJs, DatabaseRowStatementJs } from '#entry-point';
2
- import { Database } from '@tursodatabase/database';
3
- interface ConnectOpts {
4
- path: string;
5
- clientName?: string;
6
- url: string;
7
- authToken?: string;
8
- encryptionKey?: string;
9
- tablesIgnore?: string[];
10
- transform?: (arg: DatabaseRowMutationJs) => DatabaseRowStatementJs | null;
11
- enableTracing?: string;
12
- }
13
- interface Sync {
14
- sync(): Promise<void>;
15
- push(): Promise<void>;
16
- pull(): Promise<void>;
17
- checkpoint(): Promise<void>;
18
- stats(): Promise<{
19
- operations: number;
20
- wal: number;
21
- }>;
22
- }
23
- export declare function connect(opts: ConnectOpts): Database & Sync;
24
- export { Database, Sync };
@@ -1,152 +0,0 @@
1
- "use strict";
2
- import { SyncEngine } from '#entry-point';
3
- import { Database } from '@tursodatabase/database';
4
- const GENERATOR_RESUME_IO = 0;
5
- const GENERATOR_RESUME_DONE = 1;
6
- function trackPromise(p) {
7
- let status = { promise: null, finished: false };
8
- status.promise = p.finally(() => status.finished = true);
9
- return status;
10
- }
11
- function timeoutMs(ms) {
12
- return new Promise(resolve => setTimeout(resolve, ms));
13
- }
14
- async function read(opts, path) {
15
- if (opts.isMemory) {
16
- return opts.value;
17
- }
18
- if (typeof window === 'undefined') {
19
- const { promises } = await import('node:fs');
20
- try {
21
- return await promises.readFile(path);
22
- }
23
- catch (error) {
24
- if (error.code === 'ENOENT') {
25
- return null;
26
- }
27
- throw error;
28
- }
29
- }
30
- else {
31
- const data = localStorage.getItem(path);
32
- if (data != null) {
33
- return new TextEncoder().encode(data);
34
- }
35
- else {
36
- return null;
37
- }
38
- }
39
- }
40
- async function write(opts, path, content) {
41
- if (opts.isMemory) {
42
- opts.value = content;
43
- return;
44
- }
45
- const data = new Uint8Array(content);
46
- if (typeof window === 'undefined') {
47
- const { promises } = await import('node:fs');
48
- const unix = Math.floor(Date.now() / 1000);
49
- const nonce = Math.floor(Math.random() * 1000000000);
50
- const tmp = `${path}.tmp.${unix}.${nonce}`;
51
- await promises.writeFile(tmp, data);
52
- await promises.rename(tmp, path);
53
- }
54
- else {
55
- localStorage.setItem(path, new TextDecoder().decode(data));
56
- }
57
- }
58
- async function process(opts, request) {
59
- const requestType = request.request();
60
- const completion = request.completion();
61
- if (requestType.type == 'Http') {
62
- try {
63
- let headers = opts.headers;
64
- if (requestType.headers != null && requestType.headers.length > 0) {
65
- headers = { ...opts.headers };
66
- for (let header of requestType.headers) {
67
- headers[header[0]] = header[1];
68
- }
69
- }
70
- const response = await fetch(`${opts.url}${requestType.path}`, {
71
- method: requestType.method,
72
- headers: headers,
73
- body: requestType.body != null ? new Uint8Array(requestType.body) : null,
74
- });
75
- completion.status(response.status);
76
- const reader = response.body.getReader();
77
- while (true) {
78
- const { done, value } = await reader.read();
79
- if (done) {
80
- completion.done();
81
- break;
82
- }
83
- completion.push(value);
84
- }
85
- }
86
- catch (error) {
87
- completion.poison(`fetch error: ${error}`);
88
- }
89
- }
90
- else if (requestType.type == 'FullRead') {
91
- try {
92
- const metadata = await read(opts.metadata, requestType.path);
93
- if (metadata != null) {
94
- completion.push(metadata);
95
- }
96
- completion.done();
97
- }
98
- catch (error) {
99
- completion.poison(`metadata read error: ${error}`);
100
- }
101
- }
102
- else if (requestType.type == 'FullWrite') {
103
- try {
104
- await write(opts.metadata, requestType.path, requestType.content);
105
- completion.done();
106
- }
107
- catch (error) {
108
- completion.poison(`metadata write error: ${error}`);
109
- }
110
- }
111
- }
112
- async function run(opts, engine, generator) {
113
- let tasks = [];
114
- while (generator.resume(null) !== GENERATOR_RESUME_DONE) {
115
- for (let request = engine.protocolIo(); request != null; request = engine.protocolIo()) {
116
- tasks.push(trackPromise(process(opts, request)));
117
- }
118
- const tasksRace = tasks.length == 0 ? Promise.resolve() : Promise.race([timeoutMs(opts.preemptionMs), ...tasks.map(t => t.promise)]);
119
- await Promise.all([engine.ioLoopAsync(), tasksRace]);
120
- tasks = tasks.filter(t => !t.finished);
121
- }
122
- return generator.take();
123
- }
124
- export async function connect(opts) {
125
- const engine = new SyncEngine({
126
- path: opts.path,
127
- clientName: opts.clientName,
128
- tablesIgnore: opts.tablesIgnore,
129
- transform: opts.transform,
130
- enableTracing: opts.enableTracing
131
- });
132
- const httpOpts = {
133
- url: opts.url,
134
- headers: {
135
- ...(opts.authToken != null && { "Authorization": `Bearer ${opts.authToken}` }),
136
- ...(opts.encryptionKey != null && { "x-turso-encryption-key": opts.encryptionKey })
137
- },
138
- metadata: opts.path == ':memory:' ? { isMemory: true, value: null } : { isMemory: false },
139
- preemptionMs: 1,
140
- };
141
- await run(httpOpts, engine, engine.init());
142
- const nativeDb = engine.open();
143
- const db = Database.create();
144
- db.initialize(nativeDb, opts.path, false);
145
- db.sync = async function () { await run(httpOpts, engine, engine.sync()); };
146
- db.pull = async function () { await run(httpOpts, engine, engine.pull()); };
147
- db.push = async function () { await run(httpOpts, engine, engine.push()); };
148
- db.checkpoint = async function () { await run(httpOpts, engine, engine.checkpoint()); };
149
- db.stats = async function () { return (await run(httpOpts, engine, engine.stats())); };
150
- return db;
151
- }
152
- export { Database };