@powersync/node 0.0.0-dev-20250925184532 → 0.0.0-dev-20251003085035
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 +111 -50
- 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 +111 -50
- package/dist/worker.cjs.map +1 -1
- package/dist/worker.d.cts +4 -0
- package/download_core.js +87 -38
- 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 +4 -0
- package/lib/db/SqliteWorker.js +57 -97
- 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.dylib +0 -0
- package/lib/libpowersync.so +0 -0
- package/lib/powersync.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 +15 -8
package/download_core.js
CHANGED
|
@@ -1,39 +1,27 @@
|
|
|
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
|
-
const version = '0.4.
|
|
8
|
+
const version = '0.4.6';
|
|
12
9
|
const versionHashes = {
|
|
13
|
-
'powersync_x64.dll': '
|
|
14
|
-
'libpowersync_x64.so': '
|
|
15
|
-
'libpowersync_aarch64.so': '
|
|
16
|
-
'libpowersync_x64.dylib': '
|
|
17
|
-
'libpowersync_aarch64.dylib': '
|
|
10
|
+
'powersync_x64.dll': '5efaa9ad4975094912a36843cb7b503376cacd233d21ae0956f0f4b42dcb457b',
|
|
11
|
+
'libpowersync_x64.so': 'e9d78620d69d3cf7d57353891fe0bf85b79d326b42c4669b9500b9e610388f76',
|
|
12
|
+
'libpowersync_aarch64.so': '0d84c0dc0134fc89af65724d11e2c45e3c15569c575ecda52d0ec2fa2aeec495',
|
|
13
|
+
'libpowersync_x64.dylib': '9b484eaf361451f7758ca6ad53190a73563be930a8f8a39ccefd29390046ef6c',
|
|
14
|
+
'libpowersync_aarch64.dylib': 'bfb4f1ec207b298aff560f1825f8123d24316edaa27b6df3a17dd49466576b92'
|
|
18
15
|
};
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
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);
|
|
17
|
+
// Map of all assets to their destinations
|
|
18
|
+
const assetMap = {
|
|
19
|
+
'powersync_x64.dll': 'powersync.dll',
|
|
20
|
+
'libpowersync_x64.so': 'libpowersync.so',
|
|
21
|
+
'libpowersync_aarch64.so': 'libpowersync-aarch64.so',
|
|
22
|
+
'libpowersync_x64.dylib': 'libpowersync.dylib',
|
|
23
|
+
'libpowersync_aarch64.dylib': 'libpowersync-aarch64.dylib'
|
|
24
|
+
};
|
|
37
25
|
|
|
38
26
|
const hashStream = async (input) => {
|
|
39
27
|
for await (const chunk of input.pipe(createHash('sha256')).setEncoding('hex')) {
|
|
@@ -41,9 +29,9 @@ const hashStream = async (input) => {
|
|
|
41
29
|
}
|
|
42
30
|
};
|
|
43
31
|
|
|
44
|
-
const hashLocal = async () => {
|
|
32
|
+
const hashLocal = async (filePath) => {
|
|
45
33
|
try {
|
|
46
|
-
const handle = await fs.open(
|
|
34
|
+
const handle = await fs.open(filePath, 'r');
|
|
47
35
|
const input = handle.createReadStream();
|
|
48
36
|
|
|
49
37
|
const result = await hashStream(input);
|
|
@@ -54,31 +42,92 @@ const hashLocal = async () => {
|
|
|
54
42
|
}
|
|
55
43
|
};
|
|
56
44
|
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
45
|
+
const downloadAsset = async (asset, destination) => {
|
|
46
|
+
const destinationPath = path.resolve('lib', destination);
|
|
47
|
+
const expectedHash = versionHashes[asset];
|
|
48
|
+
|
|
49
|
+
// Check if file exists and has correct hash
|
|
50
|
+
const currentHash = await hashLocal(destinationPath);
|
|
51
|
+
if (currentHash === expectedHash) {
|
|
52
|
+
console.debug(`${destination} is up-to-date, skipping download`);
|
|
53
|
+
return;
|
|
61
54
|
}
|
|
62
55
|
|
|
63
56
|
const url = `https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v${version}/${asset}`;
|
|
57
|
+
console.log(`Downloading ${url}`);
|
|
64
58
|
const response = await fetch(url);
|
|
65
59
|
if (response.status != 200) {
|
|
66
60
|
throw `Could not download ${url}`;
|
|
67
61
|
}
|
|
68
62
|
|
|
63
|
+
const file = await fs.open(destinationPath, 'w');
|
|
64
|
+
await finished(Readable.fromWeb(response.body).pipe(file.createWriteStream()));
|
|
65
|
+
await file.close();
|
|
66
|
+
|
|
67
|
+
const hashAfterDownloading = await hashLocal(destinationPath);
|
|
68
|
+
if (hashAfterDownloading != expectedHash) {
|
|
69
|
+
throw `Unexpected hash after downloading ${asset} (got ${hashAfterDownloading}, expected ${expectedHash})`;
|
|
70
|
+
}
|
|
71
|
+
console.log(`Successfully downloaded ${destination}`);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const checkAsset = async (asset, destination) => {
|
|
75
|
+
const destinationPath = path.resolve('lib', destination);
|
|
76
|
+
const expectedHash = versionHashes[asset];
|
|
77
|
+
const currentHash = await hashLocal(destinationPath);
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
asset,
|
|
81
|
+
destination,
|
|
82
|
+
destinationPath,
|
|
83
|
+
expectedHash,
|
|
84
|
+
currentHash,
|
|
85
|
+
exists: currentHash !== null,
|
|
86
|
+
isValid: currentHash === expectedHash
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const download = async () => {
|
|
69
91
|
try {
|
|
70
92
|
await fs.access('lib');
|
|
71
93
|
} catch {
|
|
72
94
|
await fs.mkdir('lib');
|
|
73
95
|
}
|
|
74
96
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
await
|
|
97
|
+
// First check all assets
|
|
98
|
+
console.log('Checking existing files...');
|
|
99
|
+
const checks = await Promise.all(
|
|
100
|
+
Object.entries(assetMap).map(([asset, destination]) => checkAsset(asset, destination))
|
|
101
|
+
);
|
|
78
102
|
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
103
|
+
const toDownload = checks.filter((check) => !check.isValid);
|
|
104
|
+
const upToDate = checks.filter((check) => check.isValid);
|
|
105
|
+
|
|
106
|
+
// Print summary
|
|
107
|
+
if (upToDate.length > 0) {
|
|
108
|
+
console.log('\nUp-to-date files:');
|
|
109
|
+
for (const check of upToDate) {
|
|
110
|
+
console.log(` ✓ ${check.destination}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (toDownload.length > 0) {
|
|
115
|
+
console.log('\nFiles to download:');
|
|
116
|
+
for (const check of toDownload) {
|
|
117
|
+
if (!check.exists) {
|
|
118
|
+
console.log(` • ${check.destination} (missing)`);
|
|
119
|
+
} else {
|
|
120
|
+
console.log(` • ${check.destination} (hash mismatch)`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
console.log('\nStarting downloads...');
|
|
125
|
+
// Download required assets in parallel
|
|
126
|
+
await Promise.all(toDownload.map((check) => downloadAsset(check.asset, check.destination)));
|
|
127
|
+
|
|
128
|
+
console.log('\nAll downloads completed successfully!');
|
|
129
|
+
} else {
|
|
130
|
+
console.log('\nAll files are up-to-date, nothing to download.');
|
|
82
131
|
}
|
|
83
132
|
};
|
|
84
133
|
|
|
@@ -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,9 @@ 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
|
}
|
|
9
13
|
export declare function startPowerSyncWorker(options?: Partial<PowerSyncWorkerOptions>): void;
|
package/lib/db/SqliteWorker.js
CHANGED
|
@@ -1,112 +1,47 @@
|
|
|
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
|
-
constructor(db) {
|
|
12
|
-
this.db = db;
|
|
13
|
-
db.function('node_thread_id', () => threadId);
|
|
14
|
-
}
|
|
15
|
-
collectCommittedUpdates() {
|
|
16
|
-
const resolved = Promise.resolve([...this.committedUpdatedTables]);
|
|
17
|
-
this.committedUpdatedTables.clear();
|
|
18
|
-
return resolved;
|
|
19
|
-
}
|
|
20
|
-
installUpdateHooks() {
|
|
21
|
-
this.db.updateHook((_op, _dbName, tableName, _rowid) => {
|
|
22
|
-
this.uncommittedUpdatedTables.add(tableName);
|
|
23
|
-
});
|
|
24
|
-
this.db.commitHook(() => {
|
|
25
|
-
for (const tableName of this.uncommittedUpdatedTables) {
|
|
26
|
-
this.committedUpdatedTables.add(tableName);
|
|
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
|
-
};
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
const info = stmt.run(params);
|
|
52
|
-
return {
|
|
53
|
-
rowsAffected: info.changes,
|
|
54
|
-
insertId: Number(info.lastInsertRowid)
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
async executeRaw(query, params) {
|
|
59
|
-
const stmt = this.db.prepare(query);
|
|
60
|
-
if (stmt.reader) {
|
|
61
|
-
return stmt.raw().all(params);
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
stmt.raw().run(params);
|
|
65
|
-
return [];
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
async executeBatch(query, params) {
|
|
69
|
-
params = params ?? [];
|
|
70
|
-
let rowsAffected = 0;
|
|
71
|
-
const stmt = this.db.prepare(query);
|
|
72
|
-
for (const paramSet of params) {
|
|
73
|
-
const info = stmt.run(paramSet);
|
|
74
|
-
rowsAffected += info.changes;
|
|
75
|
-
}
|
|
76
|
-
return { rowsAffected };
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
class BetterSqliteWorker {
|
|
80
|
-
options;
|
|
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');
|
|
90
|
-
}
|
|
91
|
-
const asyncDb = new BlockingAsyncDatabase(baseDB);
|
|
92
|
-
asyncDb.installUpdateHooks();
|
|
93
|
-
return Comlink.proxy(asyncDb);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
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';
|
|
96
9
|
export function startPowerSyncWorker(options) {
|
|
97
10
|
const resolvedOptions = {
|
|
98
11
|
extensionPath() {
|
|
99
|
-
const isCommonJsModule =
|
|
12
|
+
const isCommonJsModule = isBundledToCommonJs;
|
|
100
13
|
const platform = OS.platform();
|
|
14
|
+
const arch = OS.arch();
|
|
101
15
|
let extensionPath;
|
|
102
16
|
if (platform === 'win32') {
|
|
103
|
-
|
|
17
|
+
if (arch === 'x64') {
|
|
18
|
+
extensionPath = 'powersync.dll';
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
throw 'Windows platform only supports x64 architecture.';
|
|
22
|
+
}
|
|
104
23
|
}
|
|
105
24
|
else if (platform === 'linux') {
|
|
106
|
-
|
|
25
|
+
if (arch === 'x64') {
|
|
26
|
+
extensionPath = 'libpowersync.so';
|
|
27
|
+
}
|
|
28
|
+
else if (arch === 'arm64') {
|
|
29
|
+
extensionPath = 'libpowersync-aarch64.so';
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
throw 'Linux platform only supports x64 and arm64 architectures.';
|
|
33
|
+
}
|
|
107
34
|
}
|
|
108
35
|
else if (platform === 'darwin') {
|
|
109
|
-
|
|
36
|
+
if (arch === 'x64') {
|
|
37
|
+
extensionPath = 'libpowersync.dylib';
|
|
38
|
+
}
|
|
39
|
+
else if (arch === 'arm64') {
|
|
40
|
+
extensionPath = 'libpowersync-aarch64.dylib';
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
throw 'macOS platform only supports x64 and arm64 architectures.';
|
|
44
|
+
}
|
|
110
45
|
}
|
|
111
46
|
else {
|
|
112
47
|
throw 'Unknown platform, PowerSync for Node.js currently supports Windows, Linux and macOS.';
|
|
@@ -120,7 +55,32 @@ export function startPowerSyncWorker(options) {
|
|
|
120
55
|
}
|
|
121
56
|
return resolved;
|
|
122
57
|
},
|
|
58
|
+
async loadBetterSqlite3() {
|
|
59
|
+
const module = await dynamicImport('better-sqlite3');
|
|
60
|
+
return module.default;
|
|
61
|
+
},
|
|
123
62
|
...options
|
|
124
63
|
};
|
|
125
|
-
Comlink.expose(new
|
|
64
|
+
Comlink.expose(new DatabaseOpenHelper(resolvedOptions), parentPort);
|
|
65
|
+
}
|
|
66
|
+
class DatabaseOpenHelper {
|
|
67
|
+
options;
|
|
68
|
+
constructor(options) {
|
|
69
|
+
this.options = options;
|
|
70
|
+
}
|
|
71
|
+
async open(options) {
|
|
72
|
+
let database;
|
|
73
|
+
const implementation = options.implementation;
|
|
74
|
+
switch (implementation.type) {
|
|
75
|
+
case 'better-sqlite3':
|
|
76
|
+
database = await openBetterSqliteDatabase(this.options, options);
|
|
77
|
+
break;
|
|
78
|
+
case 'node:sqlite':
|
|
79
|
+
database = await openNodeDatabase(this.options, options);
|
|
80
|
+
break;
|
|
81
|
+
default:
|
|
82
|
+
throw new Error(`Unknown database implementation: ${options.implementation}.`);
|
|
83
|
+
}
|
|
84
|
+
return Comlink.proxy(database);
|
|
85
|
+
}
|
|
126
86
|
}
|
|
@@ -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,
|
package/lib/db/options.d.ts
CHANGED
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
import { type Worker } from 'node:worker_threads';
|
|
2
|
-
import { SQLOpenOptions } from '@powersync/common';
|
|
2
|
+
import { LockContext, SQLOpenOptions } from '@powersync/common';
|
|
3
3
|
export type WorkerOpener = (...args: ConstructorParameters<typeof Worker>) => InstanceType<typeof Worker>;
|
|
4
|
+
/**
|
|
5
|
+
* Use the [better-sqlite3](https://github.com/WiseLibs/better-sqlite3) package as a SQLite driver for PowerSync.
|
|
6
|
+
*/
|
|
7
|
+
export interface BetterSqlite3Options {
|
|
8
|
+
type: 'better-sqlite3';
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Use the experimental `node:sqlite` interface as a SQLite driver for PowerSync.
|
|
12
|
+
*
|
|
13
|
+
* Note that this option is not currently tested and highly unstable.
|
|
14
|
+
*/
|
|
15
|
+
export interface NodeSqliteOptions {
|
|
16
|
+
type: 'node:sqlite';
|
|
17
|
+
}
|
|
18
|
+
export type NodeDatabaseImplementation = BetterSqlite3Options | NodeSqliteOptions;
|
|
4
19
|
/**
|
|
5
20
|
* The {@link SQLOpenOptions} available across all PowerSync SDKs for JavaScript extended with
|
|
6
21
|
* Node.JS-specific options.
|
|
7
22
|
*/
|
|
8
23
|
export interface NodeSQLOpenOptions extends SQLOpenOptions {
|
|
24
|
+
implementation?: NodeDatabaseImplementation;
|
|
9
25
|
/**
|
|
10
26
|
* The Node.JS SDK will use one worker to run writing queries and additional workers to run reads.
|
|
11
27
|
* This option controls how many workers to use for reads.
|
|
@@ -19,4 +35,10 @@ export interface NodeSQLOpenOptions extends SQLOpenOptions {
|
|
|
19
35
|
* @returns the resolved worker.
|
|
20
36
|
*/
|
|
21
37
|
openWorker?: WorkerOpener;
|
|
38
|
+
/**
|
|
39
|
+
* Initializes a created database connection.
|
|
40
|
+
*
|
|
41
|
+
* This can be used to e.g. set encryption keys, if an encrypted database should be used.
|
|
42
|
+
*/
|
|
43
|
+
initializeConnection?: (db: LockContext, isWriter: boolean) => Promise<void>;
|
|
22
44
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|