@powersync/node 0.11.1 → 0.12.0
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 +91 -10
- package/dist/DefaultWorker.cjs +140 -62
- package/dist/DefaultWorker.cjs.map +1 -1
- package/dist/bundle.cjs +25 -11
- package/dist/bundle.cjs.map +1 -1
- package/dist/bundle.d.cts +23 -1
- package/dist/worker.cjs +141 -62
- package/dist/worker.cjs.map +1 -1
- package/dist/worker.d.cts +9 -1
- package/download_core.js +75 -32
- package/lib/db/AsyncDatabase.d.ts +7 -2
- package/lib/db/BetterSqliteWorker.d.ts +26 -0
- package/lib/db/BetterSqliteWorker.js +58 -0
- package/lib/db/NodeSqliteWorker.d.ts +21 -0
- package/lib/db/NodeSqliteWorker.js +46 -0
- package/lib/db/PowerSyncDatabase.js +2 -2
- package/lib/db/SqliteWorker.d.ts +8 -0
- package/lib/db/SqliteWorker.js +75 -98
- package/lib/db/{BetterSQLite3DBAdapter.d.ts → WorkerConnectionPool.d.ts} +1 -1
- package/lib/db/{BetterSQLite3DBAdapter.js → WorkerConnectionPool.js} +25 -5
- package/lib/db/options.d.ts +23 -1
- package/lib/libpowersync_aarch64.dylib +0 -0
- package/lib/libpowersync_aarch64.so +0 -0
- package/lib/libpowersync_armv7.so +0 -0
- package/lib/libpowersync_riscv64gc.so +0 -0
- package/lib/libpowersync_x64.dylib +0 -0
- package/lib/libpowersync_x86.so +0 -0
- package/lib/powersync_aarch64.dll +0 -0
- package/lib/powersync_x64.dll +0 -0
- package/lib/powersync_x86.dll +0 -0
- package/lib/utils/modules.d.ts +2 -0
- package/lib/utils/modules.js +5 -0
- package/lib/utils/modules_commonjs.d.ts +2 -0
- package/lib/utils/modules_commonjs.js +6 -0
- package/package.json +14 -7
- /package/lib/{libpowersync.so → libpowersync_x64.so} +0 -0
package/download_core.js
CHANGED
|
@@ -1,39 +1,25 @@
|
|
|
1
|
-
// TODO: Make this a pre-publish hook and just bundle everything
|
|
2
1
|
import { createHash } from 'node:crypto';
|
|
3
|
-
import * as OS from 'node:os';
|
|
4
2
|
import * as fs from 'node:fs/promises';
|
|
5
3
|
import * as path from 'node:path';
|
|
6
4
|
import { Readable } from 'node:stream';
|
|
7
5
|
import { finished } from 'node:stream/promises';
|
|
8
|
-
import { exit } from 'node:process';
|
|
9
6
|
|
|
10
7
|
// When changing this version, run node download_core.js update_hashes
|
|
11
8
|
const version = '0.4.6';
|
|
12
9
|
const versionHashes = {
|
|
13
10
|
'powersync_x64.dll': '5efaa9ad4975094912a36843cb7b503376cacd233d21ae0956f0f4b42dcb457b',
|
|
11
|
+
'powersync_x86.dll': '4151ba8aa6f024b50b7aebe52ba59f2c5be54e3fed26f7f3f48e1127dcda027d',
|
|
12
|
+
'powersync_aarch64.dll': '3abe46074432593ff5cfc2098b186c592f020c5cfa81285f8e49962732a94bf5',
|
|
13
|
+
'libpowersync_x86.so': '1321a7de13fda0b2de7d2bc231a68cb5691f84010f3858e5cf02e47f88ba6f4a',
|
|
14
14
|
'libpowersync_x64.so': 'e9d78620d69d3cf7d57353891fe0bf85b79d326b42c4669b9500b9e610388f76',
|
|
15
15
|
'libpowersync_aarch64.so': '0d84c0dc0134fc89af65724d11e2c45e3c15569c575ecda52d0ec2fa2aeec495',
|
|
16
|
+
'libpowersync_armv7.so': 'c7887181ce9c524b68a7ac284ab447b8584511c87527ca26186e5874bf9ba3d6',
|
|
17
|
+
'libpowersync_riscv64gc.so': 'a89f3a71f22f707707d97517e9310e42e2a57dc5343cee08d09002a8cea048d5',
|
|
16
18
|
'libpowersync_x64.dylib': '9b484eaf361451f7758ca6ad53190a73563be930a8f8a39ccefd29390046ef6c',
|
|
17
19
|
'libpowersync_aarch64.dylib': 'bfb4f1ec207b298aff560f1825f8123d24316edaa27b6df3a17dd49466576b92'
|
|
18
20
|
};
|
|
19
21
|
|
|
20
|
-
const
|
|
21
|
-
let destination;
|
|
22
|
-
let asset;
|
|
23
|
-
|
|
24
|
-
if (platform === 'win32') {
|
|
25
|
-
asset = 'powersync_x64.dll';
|
|
26
|
-
destination = 'powersync.dll';
|
|
27
|
-
} else if (platform === 'linux') {
|
|
28
|
-
asset = OS.arch() === 'x64' ? 'libpowersync_x64.so' : 'libpowersync_aarch64.so';
|
|
29
|
-
destination = 'libpowersync.so';
|
|
30
|
-
} else if (platform === 'darwin') {
|
|
31
|
-
asset = OS.arch() === 'x64' ? 'libpowersync_x64.dylib' : 'libpowersync_aarch64.dylib';
|
|
32
|
-
destination = 'libpowersync.dylib';
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const expectedHash = versionHashes[asset];
|
|
36
|
-
const destinationPath = path.resolve('lib', destination);
|
|
22
|
+
const assets = Object.keys(versionHashes);
|
|
37
23
|
|
|
38
24
|
const hashStream = async (input) => {
|
|
39
25
|
for await (const chunk of input.pipe(createHash('sha256')).setEncoding('hex')) {
|
|
@@ -41,9 +27,9 @@ const hashStream = async (input) => {
|
|
|
41
27
|
}
|
|
42
28
|
};
|
|
43
29
|
|
|
44
|
-
const hashLocal = async () => {
|
|
30
|
+
const hashLocal = async (filePath) => {
|
|
45
31
|
try {
|
|
46
|
-
const handle = await fs.open(
|
|
32
|
+
const handle = await fs.open(filePath, 'r');
|
|
47
33
|
const input = handle.createReadStream();
|
|
48
34
|
|
|
49
35
|
const result = await hashStream(input);
|
|
@@ -54,31 +40,88 @@ const hashLocal = async () => {
|
|
|
54
40
|
}
|
|
55
41
|
};
|
|
56
42
|
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
43
|
+
const downloadAsset = async (asset) => {
|
|
44
|
+
const destinationPath = path.resolve('lib', asset);
|
|
45
|
+
const expectedHash = versionHashes[asset];
|
|
46
|
+
|
|
47
|
+
// Check if file exists and has correct hash
|
|
48
|
+
const currentHash = await hashLocal(destinationPath);
|
|
49
|
+
if (currentHash == expectedHash) {
|
|
50
|
+
console.debug(`${asset} is up-to-date, skipping download`);
|
|
51
|
+
return;
|
|
61
52
|
}
|
|
62
53
|
|
|
63
54
|
const url = `https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v${version}/${asset}`;
|
|
55
|
+
console.log(`Downloading ${url}`);
|
|
64
56
|
const response = await fetch(url);
|
|
65
57
|
if (response.status != 200) {
|
|
66
58
|
throw `Could not download ${url}`;
|
|
67
59
|
}
|
|
68
60
|
|
|
61
|
+
const file = await fs.open(destinationPath, 'w');
|
|
62
|
+
await finished(Readable.fromWeb(response.body).pipe(file.createWriteStream()));
|
|
63
|
+
await file.close();
|
|
64
|
+
|
|
65
|
+
const hashAfterDownloading = await hashLocal(destinationPath);
|
|
66
|
+
if (hashAfterDownloading != expectedHash) {
|
|
67
|
+
throw `Unexpected hash after downloading ${asset} (got ${hashAfterDownloading}, expected ${expectedHash})`;
|
|
68
|
+
}
|
|
69
|
+
console.log(`Successfully downloaded ${asset}`);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const checkAsset = async (asset) => {
|
|
73
|
+
const destinationPath = path.resolve('lib', asset);
|
|
74
|
+
const expectedHash = versionHashes[asset];
|
|
75
|
+
const currentHash = await hashLocal(destinationPath);
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
asset,
|
|
79
|
+
destinationPath,
|
|
80
|
+
expectedHash,
|
|
81
|
+
currentHash,
|
|
82
|
+
exists: currentHash !== null,
|
|
83
|
+
isValid: currentHash == expectedHash
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const download = async () => {
|
|
69
88
|
try {
|
|
70
89
|
await fs.access('lib');
|
|
71
90
|
} catch {
|
|
72
91
|
await fs.mkdir('lib');
|
|
73
92
|
}
|
|
74
93
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
await
|
|
94
|
+
// First check all assets
|
|
95
|
+
console.log('Checking existing files...');
|
|
96
|
+
const checks = await Promise.all(assets.map((asset) => checkAsset(asset, asset)));
|
|
78
97
|
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
98
|
+
const toDownload = checks.filter((check) => !check.isValid);
|
|
99
|
+
const upToDate = checks.filter((check) => check.isValid);
|
|
100
|
+
|
|
101
|
+
// Print summary
|
|
102
|
+
if (upToDate.length > 0) {
|
|
103
|
+
console.log('\nUp-to-date files:');
|
|
104
|
+
for (const check of upToDate) {
|
|
105
|
+
console.log(` ✓ ${check.asset}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (toDownload.length > 0) {
|
|
110
|
+
console.log('\nFiles to download:');
|
|
111
|
+
for (const check of toDownload) {
|
|
112
|
+
if (!check.exists) {
|
|
113
|
+
console.log(` • ${check.asset} (missing)`);
|
|
114
|
+
} else {
|
|
115
|
+
console.log(` • ${check.asset} (hash mismatch)`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
console.log('\nStarting downloads...');
|
|
120
|
+
await Promise.all(toDownload.map((check) => downloadAsset(check.asset)));
|
|
121
|
+
|
|
122
|
+
console.log('\nAll downloads completed successfully!');
|
|
123
|
+
} else {
|
|
124
|
+
console.log('\nAll files are up-to-date, nothing to download.');
|
|
82
125
|
}
|
|
83
126
|
};
|
|
84
127
|
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import { QueryResult } from '@powersync/common';
|
|
2
|
+
import { NodeDatabaseImplementation } from './options.js';
|
|
2
3
|
export type ProxiedQueryResult = Omit<QueryResult, 'rows'> & {
|
|
3
4
|
rows?: {
|
|
4
5
|
_array: any[];
|
|
5
6
|
length: number;
|
|
6
7
|
};
|
|
7
8
|
};
|
|
9
|
+
export interface AsyncDatabaseOpenOptions {
|
|
10
|
+
path: string;
|
|
11
|
+
isWriter: boolean;
|
|
12
|
+
implementation: NodeDatabaseImplementation;
|
|
13
|
+
}
|
|
8
14
|
export interface AsyncDatabaseOpener {
|
|
9
|
-
open(
|
|
15
|
+
open(options: AsyncDatabaseOpenOptions): Promise<AsyncDatabase>;
|
|
10
16
|
}
|
|
11
17
|
export interface AsyncDatabase {
|
|
12
18
|
execute: (query: string, params: any[]) => Promise<ProxiedQueryResult>;
|
|
13
19
|
executeRaw: (query: string, params: any[]) => Promise<any[][]>;
|
|
14
20
|
executeBatch: (query: string, params: any[][]) => Promise<ProxiedQueryResult>;
|
|
15
21
|
close: () => Promise<void>;
|
|
16
|
-
collectCommittedUpdates: () => Promise<string[]>;
|
|
17
22
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Database } from 'better-sqlite3';
|
|
2
|
+
import { AsyncDatabase, AsyncDatabaseOpenOptions } from './AsyncDatabase.js';
|
|
3
|
+
import { PowerSyncWorkerOptions } from './SqliteWorker.js';
|
|
4
|
+
declare class BlockingAsyncDatabase implements AsyncDatabase {
|
|
5
|
+
private readonly db;
|
|
6
|
+
constructor(db: Database);
|
|
7
|
+
close(): Promise<void>;
|
|
8
|
+
execute(query: string, params: any[]): Promise<{
|
|
9
|
+
rowsAffected: number;
|
|
10
|
+
rows: {
|
|
11
|
+
_array: unknown[];
|
|
12
|
+
length: number;
|
|
13
|
+
};
|
|
14
|
+
insertId?: undefined;
|
|
15
|
+
} | {
|
|
16
|
+
rowsAffected: number;
|
|
17
|
+
insertId: number;
|
|
18
|
+
rows?: undefined;
|
|
19
|
+
}>;
|
|
20
|
+
executeRaw(query: string, params: any[]): Promise<any[][]>;
|
|
21
|
+
executeBatch(query: string, params: any[][]): Promise<{
|
|
22
|
+
rowsAffected: number;
|
|
23
|
+
}>;
|
|
24
|
+
}
|
|
25
|
+
export declare function openDatabase(worker: PowerSyncWorkerOptions, options: AsyncDatabaseOpenOptions): Promise<BlockingAsyncDatabase>;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { threadId } from 'node:worker_threads';
|
|
2
|
+
class BlockingAsyncDatabase {
|
|
3
|
+
db;
|
|
4
|
+
constructor(db) {
|
|
5
|
+
this.db = db;
|
|
6
|
+
db.function('node_thread_id', () => threadId);
|
|
7
|
+
}
|
|
8
|
+
async close() {
|
|
9
|
+
this.db.close();
|
|
10
|
+
}
|
|
11
|
+
async execute(query, params) {
|
|
12
|
+
const stmt = this.db.prepare(query);
|
|
13
|
+
if (stmt.reader) {
|
|
14
|
+
const rows = stmt.all(params);
|
|
15
|
+
return {
|
|
16
|
+
rowsAffected: 0,
|
|
17
|
+
rows: {
|
|
18
|
+
_array: rows,
|
|
19
|
+
length: rows.length
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
const info = stmt.run(params);
|
|
25
|
+
return {
|
|
26
|
+
rowsAffected: info.changes,
|
|
27
|
+
insertId: Number(info.lastInsertRowid)
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async executeRaw(query, params) {
|
|
32
|
+
const stmt = this.db.prepare(query);
|
|
33
|
+
if (stmt.reader) {
|
|
34
|
+
return stmt.raw().all(params);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
stmt.raw().run(params);
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async executeBatch(query, params) {
|
|
42
|
+
params = params ?? [];
|
|
43
|
+
let rowsAffected = 0;
|
|
44
|
+
const stmt = this.db.prepare(query);
|
|
45
|
+
for (const paramSet of params) {
|
|
46
|
+
const info = stmt.run(paramSet);
|
|
47
|
+
rowsAffected += info.changes;
|
|
48
|
+
}
|
|
49
|
+
return { rowsAffected };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export async function openDatabase(worker, options) {
|
|
53
|
+
const BetterSQLite3Database = await worker.loadBetterSqlite3();
|
|
54
|
+
const baseDB = new BetterSQLite3Database(options.path);
|
|
55
|
+
baseDB.loadExtension(worker.extensionPath(), 'sqlite3_powersync_init');
|
|
56
|
+
const asyncDb = new BlockingAsyncDatabase(baseDB);
|
|
57
|
+
return asyncDb;
|
|
58
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import { AsyncDatabase, AsyncDatabaseOpenOptions } from './AsyncDatabase.js';
|
|
3
|
+
import { PowerSyncWorkerOptions } from './SqliteWorker.js';
|
|
4
|
+
declare class BlockingNodeDatabase implements AsyncDatabase {
|
|
5
|
+
private readonly db;
|
|
6
|
+
constructor(db: DatabaseSync, write: boolean);
|
|
7
|
+
close(): Promise<void>;
|
|
8
|
+
execute(query: string, params: any[]): Promise<{
|
|
9
|
+
rowsAffected: number;
|
|
10
|
+
rows: {
|
|
11
|
+
_array: Record<string, import("node:sqlite").SQLOutputValue>[];
|
|
12
|
+
length: number;
|
|
13
|
+
};
|
|
14
|
+
}>;
|
|
15
|
+
executeRaw(query: string, params: any[]): Promise<any[][]>;
|
|
16
|
+
executeBatch(query: string, params: any[][]): Promise<{
|
|
17
|
+
rowsAffected: number;
|
|
18
|
+
}>;
|
|
19
|
+
}
|
|
20
|
+
export declare function openDatabase(worker: PowerSyncWorkerOptions, options: AsyncDatabaseOpenOptions): Promise<BlockingNodeDatabase>;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { threadId } from 'node:worker_threads';
|
|
2
|
+
import { dynamicImport } from '../utils/modules.js';
|
|
3
|
+
class BlockingNodeDatabase {
|
|
4
|
+
db;
|
|
5
|
+
constructor(db, write) {
|
|
6
|
+
this.db = db;
|
|
7
|
+
db.function('node_thread_id', () => threadId);
|
|
8
|
+
}
|
|
9
|
+
async close() {
|
|
10
|
+
this.db.close();
|
|
11
|
+
}
|
|
12
|
+
async execute(query, params) {
|
|
13
|
+
const stmt = this.db.prepare(query);
|
|
14
|
+
const rows = stmt.all(...params);
|
|
15
|
+
return {
|
|
16
|
+
rowsAffected: 0,
|
|
17
|
+
rows: {
|
|
18
|
+
_array: rows,
|
|
19
|
+
length: rows.length
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
async executeRaw(query, params) {
|
|
24
|
+
const stmt = this.db.prepare(query);
|
|
25
|
+
stmt.setReturnArrays(true); // Missing in @types/node, https://nodejs.org/api/sqlite.html#statementsetreturnarraysenabled
|
|
26
|
+
return stmt.all(...params);
|
|
27
|
+
}
|
|
28
|
+
async executeBatch(query, params) {
|
|
29
|
+
params = params ?? [];
|
|
30
|
+
let rowsAffected = 0;
|
|
31
|
+
const stmt = this.db.prepare(query);
|
|
32
|
+
for (const paramSet of params) {
|
|
33
|
+
const info = stmt.run(...paramSet);
|
|
34
|
+
rowsAffected += info.changes;
|
|
35
|
+
}
|
|
36
|
+
return { rowsAffected };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export async function openDatabase(worker, options) {
|
|
40
|
+
// NOTE: We want to import node:sqlite dynamically, to avoid bundlers unconditionally requiring node:sqlite in the
|
|
41
|
+
// end, since that would make us incompatible with older Node.JS versions.
|
|
42
|
+
const { DatabaseSync } = await dynamicImport('node:sqlite');
|
|
43
|
+
const baseDB = new DatabaseSync(options.path, { allowExtension: true });
|
|
44
|
+
baseDB.loadExtension(worker.extensionPath(), 'sqlite3_powersync_init');
|
|
45
|
+
return new BlockingNodeDatabase(baseDB, options.isWriter);
|
|
46
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AbstractPowerSyncDatabase, SqliteBucketStorage } from '@powersync/common';
|
|
2
2
|
import { NodeRemote } from '../sync/stream/NodeRemote.js';
|
|
3
3
|
import { NodeStreamingSyncImplementation } from '../sync/stream/NodeStreamingSyncImplementation.js';
|
|
4
|
-
import {
|
|
4
|
+
import { WorkerConnectionPool } from './WorkerConnectionPool.js';
|
|
5
5
|
/**
|
|
6
6
|
* A PowerSync database which provides SQLite functionality
|
|
7
7
|
* which is automatically synced.
|
|
@@ -27,7 +27,7 @@ export class PowerSyncDatabase extends AbstractPowerSyncDatabase {
|
|
|
27
27
|
* Opens a DBAdapter using better-sqlite3 as the default SQLite open factory.
|
|
28
28
|
*/
|
|
29
29
|
openDBAdapter(options) {
|
|
30
|
-
return new
|
|
30
|
+
return new WorkerConnectionPool(options.database);
|
|
31
31
|
}
|
|
32
32
|
generateBucketStorageAdapter() {
|
|
33
33
|
return new SqliteBucketStorage(this.database, this.logger);
|
package/lib/db/SqliteWorker.d.ts
CHANGED
|
@@ -5,5 +5,13 @@ export interface PowerSyncWorkerOptions {
|
|
|
5
5
|
* @returns The absolute path of the PowerSync SQLite core extensions library.
|
|
6
6
|
*/
|
|
7
7
|
extensionPath: () => string;
|
|
8
|
+
/**
|
|
9
|
+
* A function that returns the `Database` constructor from the `better-sqlite3` package.
|
|
10
|
+
*/
|
|
11
|
+
loadBetterSqlite3: () => Promise<any>;
|
|
8
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* @returns The relevant PowerSync extension binary filename for the current platform and architecture
|
|
15
|
+
*/
|
|
16
|
+
export declare function getPowerSyncExtensionFilename(): string;
|
|
9
17
|
export declare function startPowerSyncWorker(options?: Partial<PowerSyncWorkerOptions>): void;
|
package/lib/db/SqliteWorker.js
CHANGED
|
@@ -1,126 +1,103 @@
|
|
|
1
|
-
import * as path from 'node:path';
|
|
2
|
-
import BetterSQLite3Database from '@powersync/better-sqlite3';
|
|
3
1
|
import * as Comlink from 'comlink';
|
|
4
|
-
import { parentPort, threadId } from 'node:worker_threads';
|
|
5
2
|
import OS from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
6
4
|
import url from 'node:url';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
this.uncommittedUpdatedTables.clear();
|
|
29
|
-
return true;
|
|
30
|
-
});
|
|
31
|
-
this.db.rollbackHook(() => {
|
|
32
|
-
this.uncommittedUpdatedTables.clear();
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
async close() {
|
|
36
|
-
this.db.close();
|
|
37
|
-
}
|
|
38
|
-
async execute(query, params) {
|
|
39
|
-
const stmt = this.db.prepare(query);
|
|
40
|
-
if (stmt.reader) {
|
|
41
|
-
const rows = stmt.all(params);
|
|
42
|
-
return {
|
|
43
|
-
rowsAffected: 0,
|
|
44
|
-
rows: {
|
|
45
|
-
_array: rows,
|
|
46
|
-
length: rows.length
|
|
47
|
-
}
|
|
48
|
-
};
|
|
5
|
+
import { parentPort } from 'node:worker_threads';
|
|
6
|
+
import { dynamicImport, isBundledToCommonJs } from '../utils/modules.js';
|
|
7
|
+
import { openDatabase as openBetterSqliteDatabase } from './BetterSqliteWorker.js';
|
|
8
|
+
import { openDatabase as openNodeDatabase } from './NodeSqliteWorker.js';
|
|
9
|
+
/**
|
|
10
|
+
* @returns The relevant PowerSync extension binary filename for the current platform and architecture
|
|
11
|
+
*/
|
|
12
|
+
export function getPowerSyncExtensionFilename() {
|
|
13
|
+
const platform = OS.platform();
|
|
14
|
+
const arch = OS.arch();
|
|
15
|
+
let extensionFile;
|
|
16
|
+
if (platform == 'win32') {
|
|
17
|
+
if (arch == 'x64') {
|
|
18
|
+
extensionFile = 'powersync_x64.dll';
|
|
19
|
+
}
|
|
20
|
+
else if (arch == 'ia32') {
|
|
21
|
+
extensionFile = 'powersync_x86.dll';
|
|
22
|
+
}
|
|
23
|
+
else if (arch == 'arm64') {
|
|
24
|
+
extensionFile = 'powersync_aarch64.dll';
|
|
49
25
|
}
|
|
50
26
|
else {
|
|
51
|
-
|
|
52
|
-
return {
|
|
53
|
-
rowsAffected: info.changes,
|
|
54
|
-
insertId: Number(info.lastInsertRowid)
|
|
55
|
-
};
|
|
27
|
+
throw new Error('Windows platform only supports arm64, ia32 and x64 architecture.');
|
|
56
28
|
}
|
|
57
29
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
30
|
+
else if (platform == 'linux') {
|
|
31
|
+
if (arch == 'x64') {
|
|
32
|
+
extensionFile = 'libpowersync_x64.so';
|
|
33
|
+
}
|
|
34
|
+
else if (arch == 'arm64') {
|
|
35
|
+
// TODO detect armv7 as an option
|
|
36
|
+
extensionFile = 'libpowersync_aarch64.so';
|
|
37
|
+
}
|
|
38
|
+
else if (arch == 'riscv64') {
|
|
39
|
+
extensionFile = 'libpowersync_riscv64gc.so';
|
|
62
40
|
}
|
|
63
41
|
else {
|
|
64
|
-
|
|
65
|
-
return [];
|
|
42
|
+
throw new Error('Linux platform only supports x64, arm64 and riscv64 architectures.');
|
|
66
43
|
}
|
|
67
44
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const stmt = this.db.prepare(query);
|
|
72
|
-
for (const paramSet of params) {
|
|
73
|
-
const info = stmt.run(paramSet);
|
|
74
|
-
rowsAffected += info.changes;
|
|
45
|
+
else if (platform == 'darwin') {
|
|
46
|
+
if (arch == 'x64') {
|
|
47
|
+
extensionFile = 'libpowersync_x64.dylib';
|
|
75
48
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
constructor(options) {
|
|
82
|
-
this.options = options;
|
|
83
|
-
}
|
|
84
|
-
async open(path, isWriter) {
|
|
85
|
-
const baseDB = new BetterSQLite3Database(path);
|
|
86
|
-
baseDB.pragma('journal_mode = WAL');
|
|
87
|
-
baseDB.loadExtension(this.options.extensionPath(), 'sqlite3_powersync_init');
|
|
88
|
-
if (!isWriter) {
|
|
89
|
-
baseDB.pragma('query_only = true');
|
|
49
|
+
else if (arch == 'arm64') {
|
|
50
|
+
extensionFile = 'libpowersync_aarch64.dylib';
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
throw new Error('macOS platform only supports x64 and arm64 architectures.');
|
|
90
54
|
}
|
|
91
|
-
const asyncDb = new BlockingAsyncDatabase(baseDB);
|
|
92
|
-
asyncDb.installUpdateHooks();
|
|
93
|
-
return Comlink.proxy(asyncDb);
|
|
94
55
|
}
|
|
56
|
+
else {
|
|
57
|
+
throw new Error(`Unknown platform: ${platform}, PowerSync for Node.js currently supports Windows, Linux and macOS.`);
|
|
58
|
+
}
|
|
59
|
+
return extensionFile;
|
|
95
60
|
}
|
|
96
61
|
export function startPowerSyncWorker(options) {
|
|
97
62
|
const resolvedOptions = {
|
|
98
63
|
extensionPath() {
|
|
99
|
-
const isCommonJsModule =
|
|
100
|
-
const
|
|
101
|
-
let extensionPath;
|
|
102
|
-
if (platform === 'win32') {
|
|
103
|
-
extensionPath = 'powersync.dll';
|
|
104
|
-
}
|
|
105
|
-
else if (platform === 'linux') {
|
|
106
|
-
extensionPath = 'libpowersync.so';
|
|
107
|
-
}
|
|
108
|
-
else if (platform === 'darwin') {
|
|
109
|
-
extensionPath = 'libpowersync.dylib';
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
throw 'Unknown platform, PowerSync for Node.js currently supports Windows, Linux and macOS.';
|
|
113
|
-
}
|
|
64
|
+
const isCommonJsModule = isBundledToCommonJs;
|
|
65
|
+
const extensionFilename = getPowerSyncExtensionFilename();
|
|
114
66
|
let resolved;
|
|
115
67
|
if (isCommonJsModule) {
|
|
116
|
-
resolved = path.resolve(__dirname, '../lib/',
|
|
68
|
+
resolved = path.resolve(__dirname, '../lib/', extensionFilename);
|
|
117
69
|
}
|
|
118
70
|
else {
|
|
119
|
-
resolved = url.fileURLToPath(new URL(`../${
|
|
71
|
+
resolved = url.fileURLToPath(new URL(`../${extensionFilename}`, import.meta.url));
|
|
120
72
|
}
|
|
121
73
|
return resolved;
|
|
122
74
|
},
|
|
75
|
+
async loadBetterSqlite3() {
|
|
76
|
+
const module = await dynamicImport('better-sqlite3');
|
|
77
|
+
return module.default;
|
|
78
|
+
},
|
|
123
79
|
...options
|
|
124
80
|
};
|
|
125
|
-
Comlink.expose(new
|
|
81
|
+
Comlink.expose(new DatabaseOpenHelper(resolvedOptions), parentPort);
|
|
82
|
+
}
|
|
83
|
+
class DatabaseOpenHelper {
|
|
84
|
+
options;
|
|
85
|
+
constructor(options) {
|
|
86
|
+
this.options = options;
|
|
87
|
+
}
|
|
88
|
+
async open(options) {
|
|
89
|
+
let database;
|
|
90
|
+
const implementation = options.implementation;
|
|
91
|
+
switch (implementation.type) {
|
|
92
|
+
case 'better-sqlite3':
|
|
93
|
+
database = await openBetterSqliteDatabase(this.options, options);
|
|
94
|
+
break;
|
|
95
|
+
case 'node:sqlite':
|
|
96
|
+
database = await openNodeDatabase(this.options, options);
|
|
97
|
+
break;
|
|
98
|
+
default:
|
|
99
|
+
throw new Error(`Unknown database implementation: ${options.implementation}.`);
|
|
100
|
+
}
|
|
101
|
+
return Comlink.proxy(database);
|
|
102
|
+
}
|
|
126
103
|
}
|
|
@@ -7,7 +7,7 @@ export type BetterSQLite3Transaction = Transaction & BetterSQLite3LockContext;
|
|
|
7
7
|
/**
|
|
8
8
|
* Adapter for better-sqlite3
|
|
9
9
|
*/
|
|
10
|
-
export declare class
|
|
10
|
+
export declare class WorkerConnectionPool extends BaseObserver<DBAdapterListener> implements DBAdapter {
|
|
11
11
|
private readonly options;
|
|
12
12
|
readonly name: string;
|
|
13
13
|
private readConnections;
|
|
@@ -5,11 +5,15 @@ import * as Comlink from 'comlink';
|
|
|
5
5
|
import { BaseObserver } from '@powersync/common';
|
|
6
6
|
import { AsyncResource } from 'node:async_hooks';
|
|
7
7
|
import { RemoteConnection } from './RemoteConnection.js';
|
|
8
|
+
import { isBundledToCommonJs } from '../utils/modules.js';
|
|
8
9
|
const READ_CONNECTIONS = 5;
|
|
10
|
+
const defaultDatabaseImplementation = {
|
|
11
|
+
type: 'better-sqlite3'
|
|
12
|
+
};
|
|
9
13
|
/**
|
|
10
14
|
* Adapter for better-sqlite3
|
|
11
15
|
*/
|
|
12
|
-
export class
|
|
16
|
+
export class WorkerConnectionPool extends BaseObserver {
|
|
13
17
|
options;
|
|
14
18
|
name;
|
|
15
19
|
readConnections;
|
|
@@ -42,7 +46,7 @@ export class BetterSQLite3DBAdapter extends BaseObserver {
|
|
|
42
46
|
dbFilePath = path.join(this.options.dbLocation, dbFilePath);
|
|
43
47
|
}
|
|
44
48
|
const openWorker = async (isWriter) => {
|
|
45
|
-
const isCommonJsModule =
|
|
49
|
+
const isCommonJsModule = isBundledToCommonJs;
|
|
46
50
|
let worker;
|
|
47
51
|
const workerName = isWriter ? `write ${dbFilePath}` : `read ${dbFilePath}`;
|
|
48
52
|
const workerFactory = this.options.openWorker ?? ((...args) => new Worker(...args));
|
|
@@ -78,8 +82,23 @@ export class BetterSQLite3DBAdapter extends BaseObserver {
|
|
|
78
82
|
worker.once('error', (e) => {
|
|
79
83
|
console.error('Unexpected PowerSync database worker error', e);
|
|
80
84
|
});
|
|
81
|
-
const database = (await comlink.open(
|
|
82
|
-
|
|
85
|
+
const database = (await comlink.open({
|
|
86
|
+
path: dbFilePath,
|
|
87
|
+
isWriter,
|
|
88
|
+
implementation: this.options.implementation ?? defaultDatabaseImplementation
|
|
89
|
+
}));
|
|
90
|
+
if (isWriter) {
|
|
91
|
+
await database.execute("SELECT powersync_update_hooks('install');", []);
|
|
92
|
+
}
|
|
93
|
+
const connection = new RemoteConnection(worker, comlink, database);
|
|
94
|
+
if (this.options.initializeConnection) {
|
|
95
|
+
await this.options.initializeConnection(connection, isWriter);
|
|
96
|
+
}
|
|
97
|
+
await connection.execute('pragma journal_mode = WAL');
|
|
98
|
+
if (!isWriter) {
|
|
99
|
+
await connection.execute('pragma query_only = true');
|
|
100
|
+
}
|
|
101
|
+
return connection;
|
|
83
102
|
};
|
|
84
103
|
// Open the writer first to avoid multiple threads enabling WAL concurrently (causing "database is locked" errors).
|
|
85
104
|
this.writeConnection = await openWorker(true);
|
|
@@ -144,7 +163,8 @@ export class BetterSQLite3DBAdapter extends BaseObserver {
|
|
|
144
163
|
return await fn(this.writeConnection);
|
|
145
164
|
}
|
|
146
165
|
finally {
|
|
147
|
-
const
|
|
166
|
+
const serializedUpdates = await this.writeConnection.database.executeRaw("SELECT powersync_update_hooks('get');", []);
|
|
167
|
+
const updates = JSON.parse(serializedUpdates[0][0]);
|
|
148
168
|
if (updates.length > 0) {
|
|
149
169
|
const event = {
|
|
150
170
|
tables: updates,
|