@powersync/node 0.1.0 → 0.2.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 +7 -4
- package/lib/db/AsyncDatabase.d.ts +1 -0
- package/lib/db/BetterSQLite3DBAdapter.d.ts +4 -2
- package/lib/db/BetterSQLite3DBAdapter.js +32 -2
- package/lib/db/DefaultWorker.d.ts +1 -0
- package/lib/db/DefaultWorker.js +2 -0
- package/lib/db/PowerSyncDatabase.d.ts +6 -1
- package/lib/db/PowerSyncDatabase.js +3 -0
- package/lib/db/RemoteConnection.d.ts +1 -0
- package/lib/db/RemoteConnection.js +3 -0
- package/lib/db/SqliteWorker.d.ts +9 -1
- package/lib/db/SqliteWorker.js +47 -20
- package/lib/db/options.d.ts +22 -0
- package/lib/db/options.js +1 -0
- package/lib/worker.d.ts +1 -0
- package/lib/worker.js +1 -0
- package/package.json +23 -10
package/README.md
CHANGED
|
@@ -9,7 +9,11 @@ _[PowerSync](https://www.powersync.com) is a sync engine for building local-firs
|
|
|
9
9
|
This package (`packages/node`) is the PowerSync SDK for Node.js clients. It is an extension of `packages/common`.
|
|
10
10
|
Using this package is not necessary for PowerSync on servers, see [our documentation](https://docs.powersync.com/installation/app-backend-setup) for more details on that.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
See a summary of features [here](https://docs.powersync.com/client-sdk-references/node).
|
|
13
|
+
|
|
14
|
+
## Alpha Release
|
|
15
|
+
|
|
16
|
+
The `@powersync/node` package is currently in an Alpha release.
|
|
13
17
|
|
|
14
18
|
# Installation
|
|
15
19
|
|
|
@@ -24,9 +28,8 @@ or download sqlite3 and PowerSync binaries.
|
|
|
24
28
|
|
|
25
29
|
# Getting Started
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Replace imports of `@powersync/web` with `@powersync/node` where necessary.
|
|
31
|
+
The [Node.js SDK reference](https://docs.powersync.com/client-sdk-references/node)
|
|
32
|
+
contains everything you need to know to get started implementing PowerSync in your project.
|
|
30
33
|
|
|
31
34
|
# Examples
|
|
32
35
|
|
|
@@ -10,6 +10,7 @@ export interface AsyncDatabaseOpener {
|
|
|
10
10
|
}
|
|
11
11
|
export interface AsyncDatabase {
|
|
12
12
|
execute: (query: string, params: any[]) => Promise<ProxiedQueryResult>;
|
|
13
|
+
executeRaw: (query: string, params: any[]) => Promise<any[][]>;
|
|
13
14
|
executeBatch: (query: string, params: any[][]) => Promise<ProxiedQueryResult>;
|
|
14
15
|
close: () => Promise<void>;
|
|
15
16
|
collectCommittedUpdates: () => Promise<string[]>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { BaseObserver, DBAdapter, DBAdapterListener, LockContext, Transaction, DBLockOptions, QueryResult
|
|
1
|
+
import { BaseObserver, DBAdapter, DBAdapterListener, LockContext, Transaction, DBLockOptions, QueryResult } from '@powersync/common';
|
|
2
|
+
import { NodeSQLOpenOptions } from './options.js';
|
|
2
3
|
export type BetterSQLite3LockContext = LockContext & {
|
|
3
4
|
executeBatch(query: string, params?: any[][]): Promise<QueryResult>;
|
|
4
5
|
};
|
|
@@ -13,7 +14,7 @@ export declare class BetterSQLite3DBAdapter extends BaseObserver<DBAdapterListen
|
|
|
13
14
|
private writeConnection;
|
|
14
15
|
private readonly readQueue;
|
|
15
16
|
private readonly writeQueue;
|
|
16
|
-
constructor(options:
|
|
17
|
+
constructor(options: NodeSQLOpenOptions);
|
|
17
18
|
initialize(): Promise<void>;
|
|
18
19
|
close(): Promise<void>;
|
|
19
20
|
readLock<T>(fn: (tx: BetterSQLite3LockContext) => Promise<T>, _options?: DBLockOptions | undefined): Promise<T>;
|
|
@@ -25,6 +26,7 @@ export declare class BetterSQLite3DBAdapter extends BaseObserver<DBAdapterListen
|
|
|
25
26
|
getOptional<T>(sql: string, parameters?: any[]): Promise<T | null>;
|
|
26
27
|
get<T>(sql: string, parameters?: any[]): Promise<T>;
|
|
27
28
|
execute(query: string, params?: any[] | undefined): Promise<QueryResult>;
|
|
29
|
+
executeRaw(query: string, params?: any[] | undefined): Promise<any[][]>;
|
|
28
30
|
executeBatch(query: string, params?: any[][]): Promise<QueryResult>;
|
|
29
31
|
refreshSchema(): Promise<void>;
|
|
30
32
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
1
2
|
import * as path from 'node:path';
|
|
2
3
|
import { Worker } from 'node:worker_threads';
|
|
3
4
|
import * as Comlink from 'comlink';
|
|
@@ -17,16 +18,40 @@ export class BetterSQLite3DBAdapter extends BaseObserver {
|
|
|
17
18
|
writeQueue = [];
|
|
18
19
|
constructor(options) {
|
|
19
20
|
super();
|
|
21
|
+
if (options.readWorkerCount != null && options.readWorkerCount < 1) {
|
|
22
|
+
throw `Needs at least one worker for reads, got ${options.readWorkerCount}`;
|
|
23
|
+
}
|
|
20
24
|
this.options = options;
|
|
21
25
|
this.name = options.dbFilename;
|
|
22
26
|
}
|
|
23
27
|
async initialize() {
|
|
24
28
|
let dbFilePath = this.options.dbFilename;
|
|
25
29
|
if (this.options.dbLocation !== undefined) {
|
|
30
|
+
// Make sure the dbLocation exists, we get a TypeError from better-sqlite3 otherwise.
|
|
31
|
+
let directoryExists = false;
|
|
32
|
+
try {
|
|
33
|
+
const stat = await fs.stat(this.options.dbLocation);
|
|
34
|
+
directoryExists = stat.isDirectory();
|
|
35
|
+
}
|
|
36
|
+
catch (_) {
|
|
37
|
+
// If we can't even stat, the directory won't be accessible to SQLite either.
|
|
38
|
+
}
|
|
39
|
+
if (!directoryExists) {
|
|
40
|
+
throw new Error(`The dbLocation directory at "${this.options.dbLocation}" does not exist. Please create it before opening the PowerSync database!`);
|
|
41
|
+
}
|
|
26
42
|
dbFilePath = path.join(this.options.dbLocation, dbFilePath);
|
|
27
43
|
}
|
|
28
44
|
const openWorker = async (isWriter) => {
|
|
29
|
-
const
|
|
45
|
+
const isCommonJsModule = import.meta.isBundlingToCommonJs ?? false;
|
|
46
|
+
let worker;
|
|
47
|
+
const workerName = isWriter ? `write ${dbFilePath}` : `read ${dbFilePath}`;
|
|
48
|
+
const workerFactory = this.options.openWorker ?? ((...args) => new Worker(...args));
|
|
49
|
+
if (isCommonJsModule) {
|
|
50
|
+
worker = workerFactory(path.resolve(__dirname, 'DefaultWorker.cjs'), { name: workerName });
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
worker = workerFactory(new URL('./DefaultWorker.js', import.meta.url), { name: workerName });
|
|
54
|
+
}
|
|
30
55
|
const listeners = new WeakMap();
|
|
31
56
|
const comlink = Comlink.wrap({
|
|
32
57
|
postMessage: worker.postMessage.bind(worker),
|
|
@@ -59,7 +84,8 @@ export class BetterSQLite3DBAdapter extends BaseObserver {
|
|
|
59
84
|
// Open the writer first to avoid multiple threads enabling WAL concurrently (causing "database is locked" errors).
|
|
60
85
|
this.writeConnection = await openWorker(true);
|
|
61
86
|
const createWorkers = [];
|
|
62
|
-
|
|
87
|
+
const amountOfReaders = this.options.readWorkerCount ?? READ_CONNECTIONS;
|
|
88
|
+
for (let i = 0; i < amountOfReaders; i++) {
|
|
63
89
|
createWorkers.push(openWorker(false));
|
|
64
90
|
}
|
|
65
91
|
this.readConnections = await Promise.all(createWorkers);
|
|
@@ -166,6 +192,7 @@ export class BetterSQLite3DBAdapter extends BaseObserver {
|
|
|
166
192
|
await connection.execute('BEGIN');
|
|
167
193
|
const result = await fn({
|
|
168
194
|
execute: (query, params) => connection.execute(query, params),
|
|
195
|
+
executeRaw: (query, params) => connection.executeRaw(query, params),
|
|
169
196
|
executeBatch: (query, params) => connection.executeBatch(query, params),
|
|
170
197
|
get: (query, params) => connection.get(query, params),
|
|
171
198
|
getAll: (query, params) => connection.getAll(query, params),
|
|
@@ -199,6 +226,9 @@ export class BetterSQLite3DBAdapter extends BaseObserver {
|
|
|
199
226
|
execute(query, params) {
|
|
200
227
|
return this.writeLock((ctx) => ctx.execute(query, params));
|
|
201
228
|
}
|
|
229
|
+
executeRaw(query, params) {
|
|
230
|
+
return this.writeLock((ctx) => ctx.executeRaw(query, params));
|
|
231
|
+
}
|
|
202
232
|
executeBatch(query, params) {
|
|
203
233
|
return this.writeTransaction((ctx) => ctx.executeBatch(query, params));
|
|
204
234
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import { AbstractPowerSyncDatabase, AbstractStreamingSyncImplementation, BucketStorageAdapter, DBAdapter, PowerSyncBackendConnector, PowerSyncDatabaseOptionsWithSettings } from '@powersync/common';
|
|
1
|
+
import { AbstractPowerSyncDatabase, AbstractStreamingSyncImplementation, BucketStorageAdapter, DBAdapter, PowerSyncBackendConnector, PowerSyncDatabaseOptions, PowerSyncDatabaseOptionsWithSettings, SQLOpenFactory } from '@powersync/common';
|
|
2
|
+
import { NodeSQLOpenOptions } from './options.js';
|
|
3
|
+
export type NodePowerSyncDatabaseOptions = PowerSyncDatabaseOptions & {
|
|
4
|
+
database: DBAdapter | SQLOpenFactory | NodeSQLOpenOptions;
|
|
5
|
+
};
|
|
2
6
|
/**
|
|
3
7
|
* A PowerSync database which provides SQLite functionality
|
|
4
8
|
* which is automatically synced.
|
|
@@ -14,6 +18,7 @@ import { AbstractPowerSyncDatabase, AbstractStreamingSyncImplementation, BucketS
|
|
|
14
18
|
* ```
|
|
15
19
|
*/
|
|
16
20
|
export declare class PowerSyncDatabase extends AbstractPowerSyncDatabase {
|
|
21
|
+
constructor(options: NodePowerSyncDatabaseOptions);
|
|
17
22
|
_initialize(): Promise<void>;
|
|
18
23
|
/**
|
|
19
24
|
* Opens a DBAdapter using better-sqlite3 as the default SQLite open factory.
|
|
@@ -17,6 +17,9 @@ import { BetterSQLite3DBAdapter } from './BetterSQLite3DBAdapter.js';
|
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
19
|
export class PowerSyncDatabase extends AbstractPowerSyncDatabase {
|
|
20
|
+
constructor(options) {
|
|
21
|
+
super(options);
|
|
22
|
+
}
|
|
20
23
|
async _initialize() {
|
|
21
24
|
await this.database.initialize();
|
|
22
25
|
}
|
|
@@ -13,6 +13,7 @@ export declare class RemoteConnection implements LockContext {
|
|
|
13
13
|
constructor(worker: Worker, comlink: Remote<AsyncDatabaseOpener>, database: Remote<AsyncDatabase>);
|
|
14
14
|
executeBatch(query: string, params?: any[][]): Promise<QueryResult>;
|
|
15
15
|
execute(query: string, params?: any[] | undefined): Promise<QueryResult>;
|
|
16
|
+
executeRaw(query: string, params?: any[] | undefined): Promise<any[][]>;
|
|
16
17
|
getAll<T>(sql: string, parameters?: any[]): Promise<T[]>;
|
|
17
18
|
getOptional<T>(sql: string, parameters?: any[]): Promise<T | null>;
|
|
18
19
|
get<T>(sql: string, parameters?: any[]): Promise<T>;
|
|
@@ -20,6 +20,9 @@ export class RemoteConnection {
|
|
|
20
20
|
const result = await this.database.execute(query, params ?? []);
|
|
21
21
|
return RemoteConnection.wrapQueryResult(result);
|
|
22
22
|
}
|
|
23
|
+
async executeRaw(query, params) {
|
|
24
|
+
return await this.database.executeRaw(query, params ?? []);
|
|
25
|
+
}
|
|
23
26
|
async getAll(sql, parameters) {
|
|
24
27
|
const res = await this.execute(sql, parameters);
|
|
25
28
|
return res.rows?._array ?? [];
|
package/lib/db/SqliteWorker.d.ts
CHANGED
|
@@ -1 +1,9 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export interface PowerSyncWorkerOptions {
|
|
2
|
+
/**
|
|
3
|
+
* A function responsible for finding the powersync DLL/so/dylib file.
|
|
4
|
+
*
|
|
5
|
+
* @returns The absolute path of the PowerSync SQLite core extensions library.
|
|
6
|
+
*/
|
|
7
|
+
extensionPath: () => string;
|
|
8
|
+
}
|
|
9
|
+
export declare function startPowerSyncWorker(options?: Partial<PowerSyncWorkerOptions>): void;
|
package/lib/db/SqliteWorker.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
1
2
|
import BetterSQLite3Database from '@powersync/better-sqlite3';
|
|
2
3
|
import * as Comlink from 'comlink';
|
|
3
4
|
import { parentPort, threadId } from 'node:worker_threads';
|
|
@@ -54,6 +55,16 @@ class BlockingAsyncDatabase {
|
|
|
54
55
|
};
|
|
55
56
|
}
|
|
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
|
+
}
|
|
57
68
|
async executeBatch(query, params) {
|
|
58
69
|
params = params ?? [];
|
|
59
70
|
let rowsAffected = 0;
|
|
@@ -66,10 +77,14 @@ class BlockingAsyncDatabase {
|
|
|
66
77
|
}
|
|
67
78
|
}
|
|
68
79
|
class BetterSqliteWorker {
|
|
80
|
+
options;
|
|
81
|
+
constructor(options) {
|
|
82
|
+
this.options = options;
|
|
83
|
+
}
|
|
69
84
|
async open(path, isWriter) {
|
|
70
85
|
const baseDB = new BetterSQLite3Database(path);
|
|
71
86
|
baseDB.pragma('journal_mode = WAL');
|
|
72
|
-
loadExtension(
|
|
87
|
+
baseDB.loadExtension(this.options.extensionPath(), 'sqlite3_powersync_init');
|
|
73
88
|
if (!isWriter) {
|
|
74
89
|
baseDB.pragma('query_only = true');
|
|
75
90
|
}
|
|
@@ -78,22 +93,34 @@ class BetterSqliteWorker {
|
|
|
78
93
|
return Comlink.proxy(asyncDb);
|
|
79
94
|
}
|
|
80
95
|
}
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
96
|
+
export function startPowerSyncWorker(options) {
|
|
97
|
+
const resolvedOptions = {
|
|
98
|
+
extensionPath() {
|
|
99
|
+
const isCommonJsModule = import.meta.isBundlingToCommonJs ?? false;
|
|
100
|
+
const platform = OS.platform();
|
|
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
|
+
}
|
|
114
|
+
let resolved;
|
|
115
|
+
if (isCommonJsModule) {
|
|
116
|
+
resolved = path.resolve(__dirname, '../lib/', extensionPath);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
resolved = url.fileURLToPath(new URL(`../${extensionPath}`, import.meta.url));
|
|
120
|
+
}
|
|
121
|
+
return resolved;
|
|
122
|
+
},
|
|
123
|
+
...options
|
|
124
|
+
};
|
|
125
|
+
Comlink.expose(new BetterSqliteWorker(resolvedOptions), parentPort);
|
|
126
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Worker } from 'node:worker_threads';
|
|
2
|
+
import { SQLOpenOptions } from '@powersync/common';
|
|
3
|
+
export type WorkerOpener = (...args: ConstructorParameters<typeof Worker>) => InstanceType<typeof Worker>;
|
|
4
|
+
/**
|
|
5
|
+
* The {@link SQLOpenOptions} available across all PowerSync SDKs for JavaScript extended with
|
|
6
|
+
* Node.JS-specific options.
|
|
7
|
+
*/
|
|
8
|
+
export interface NodeSQLOpenOptions extends SQLOpenOptions {
|
|
9
|
+
/**
|
|
10
|
+
* The Node.JS SDK will use one worker to run writing queries and additional workers to run reads.
|
|
11
|
+
* This option controls how many workers to use for reads.
|
|
12
|
+
*/
|
|
13
|
+
readWorkerCount?: number;
|
|
14
|
+
/**
|
|
15
|
+
* A callback to allow customizing how the Node.JS SDK loads workers. This can be customized to
|
|
16
|
+
* use workers at different paths.
|
|
17
|
+
*
|
|
18
|
+
* @param args The arguments that would otherwise be passed to the {@link Worker} constructor.
|
|
19
|
+
* @returns the resolved worker.
|
|
20
|
+
*/
|
|
21
|
+
openWorker?: WorkerOpener;
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/lib/worker.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './db/SqliteWorker.js';
|
package/lib/worker.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './db/SqliteWorker.js';
|
package/package.json
CHANGED
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@powersync/node",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"registry": "https://registry.npmjs.org/",
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
8
|
-
"description": "PowerSync
|
|
9
|
-
"main": "./lib/index.js",
|
|
10
|
-
"module": "./lib/index.js",
|
|
11
|
-
"types": "./lib/index.d.ts",
|
|
8
|
+
"description": "PowerSync Node.js SDK. Sync Postgres, MongoDB or MySQL with SQLite in your Node.js app",
|
|
12
9
|
"files": [
|
|
13
10
|
"lib",
|
|
14
11
|
"dist",
|
|
15
12
|
"download_core.js"
|
|
16
13
|
],
|
|
17
14
|
"type": "module",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"import": "./lib/index.js",
|
|
18
|
+
"require": "./dist/bundle.cjs",
|
|
19
|
+
"types": "./lib/index.d.ts"
|
|
20
|
+
},
|
|
21
|
+
"./worker.js": {
|
|
22
|
+
"import": "./lib/worker.js",
|
|
23
|
+
"require": "./dist/worker.cjs",
|
|
24
|
+
"types": "./lib/worker.d.ts"
|
|
25
|
+
},
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
18
28
|
"repository": {
|
|
19
29
|
"type": "git",
|
|
20
30
|
"url": "git+https://github.com/powersync-ja/powersync-js.git"
|
|
@@ -26,19 +36,22 @@
|
|
|
26
36
|
},
|
|
27
37
|
"homepage": "https://docs.powersync.com/",
|
|
28
38
|
"peerDependencies": {
|
|
29
|
-
"@powersync/common": "^1.
|
|
30
|
-
"@powersync/better-sqlite3": "^0.1.0"
|
|
39
|
+
"@powersync/common": "^1.26.0"
|
|
31
40
|
},
|
|
32
41
|
"dependencies": {
|
|
42
|
+
"@powersync/better-sqlite3": "^0.1.1",
|
|
33
43
|
"async-lock": "^1.4.0",
|
|
34
44
|
"bson": "^6.6.0",
|
|
35
45
|
"comlink": "^4.4.2",
|
|
36
|
-
"@powersync/common": "1.
|
|
46
|
+
"@powersync/common": "1.26.0"
|
|
37
47
|
},
|
|
38
48
|
"devDependencies": {
|
|
39
49
|
"@types/async-lock": "^1.4.0",
|
|
50
|
+
"drizzle-orm": "^0.35.2",
|
|
51
|
+
"rollup": "4.14.3",
|
|
40
52
|
"typescript": "^5.5.3",
|
|
41
|
-
"vitest": "^3.0.5"
|
|
53
|
+
"vitest": "^3.0.5",
|
|
54
|
+
"@powersync/drizzle-driver": "0.4.0"
|
|
42
55
|
},
|
|
43
56
|
"keywords": [
|
|
44
57
|
"data sync",
|
|
@@ -49,7 +62,7 @@
|
|
|
49
62
|
],
|
|
50
63
|
"scripts": {
|
|
51
64
|
"install": "node download_core.js",
|
|
52
|
-
"build": "tsc -b",
|
|
65
|
+
"build": "tsc -b && rollup --config",
|
|
53
66
|
"build:prod": "tsc -b --sourceMap false",
|
|
54
67
|
"clean": "rm -rf lib dist tsconfig.tsbuildinfo dist",
|
|
55
68
|
"watch": "tsc -b -w",
|