@powersync/node 0.1.1 → 0.2.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
@@ -26,6 +26,27 @@ npm install @powersync/node
26
26
  Both `@powersync/node` and the `better-sqlite3` packages have install scripts that need to run to compile
27
27
  or download sqlite3 and PowerSync binaries.
28
28
 
29
+ ### Common Installation Issues
30
+
31
+ The `better-sqlite` package requires native compilation, which depends on certain system tools. This compilation process is handled by `node-gyp` and may fail if required dependencies are missing or misconfigured.
32
+
33
+ #### Node-gyp Version Conflicts
34
+
35
+ `better-sqlite` depends on `node-gyp@^11`, but some project configurations may introduce multiple versions of `node-gyp`, potentially causing build issues.
36
+
37
+ #### Python Dependency Issues
38
+
39
+ `node-gyp` requires Python for compilation. If your project uses `node-gyp` below version `10` and your system has Python `3.12` or later, you may encounter the following error:
40
+
41
+ ```python
42
+ ModuleNotFoundError: No module named 'distutils'
43
+ ```
44
+
45
+ To resolve this, either:
46
+
47
+ - Upgrade `node-gyp` to version 10 or later.
48
+ - Install Python [setuptools](https://pypi.org/project/setuptools/), which includes `distutils`.
49
+
29
50
  # Getting Started
30
51
 
31
52
  The [Node.js SDK reference](https://docs.powersync.com/client-sdk-references/node)
@@ -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, SQLOpenOptions } from '@powersync/common';
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: SQLOpenOptions);
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 worker = new Worker(new URL('./SqliteWorker.js', import.meta.url), { name: isWriter ? `write ${dbFilePath}` : `read ${dbFilePath}` });
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
- for (let i = 0; i < READ_CONNECTIONS; i++) {
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 {};
@@ -0,0 +1,2 @@
1
+ import { startPowerSyncWorker } from './SqliteWorker.js';
2
+ startPowerSyncWorker();
@@ -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 ?? [];
@@ -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;
@@ -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(baseDB);
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
- const loadExtension = (db) => {
82
- const platform = OS.platform();
83
- let extensionPath;
84
- if (platform === 'win32') {
85
- extensionPath = 'powersync.dll';
86
- }
87
- else if (platform === 'linux') {
88
- extensionPath = 'libpowersync.so';
89
- }
90
- else if (platform === 'darwin') {
91
- extensionPath = 'libpowersync.dylib';
92
- }
93
- else {
94
- throw 'Unknown platform, PowerSync for Node.js currently supports Windows, Linux and macOS.';
95
- }
96
- const resolved = url.fileURLToPath(new URL(`../${extensionPath}`, import.meta.url));
97
- db.loadExtension(resolved, 'sqlite3_powersync_init');
98
- };
99
- Comlink.expose(new BetterSqliteWorker(), parentPort);
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 {};
@@ -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.1.1",
3
+ "version": "0.2.1",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/",
6
6
  "access": "public"
7
7
  },
8
8
  "description": "PowerSync Node.js SDK. Sync Postgres, MongoDB or MySQL with SQLite in your Node.js app",
9
- "main": "./lib/index.js",
10
- "module": "./lib/index.js",
11
- "types": "./lib/index.d.ts",
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.22.0",
30
- "@powersync/better-sqlite3": "^0.1.0"
39
+ "@powersync/common": "^1.27.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.25.0"
46
+ "@powersync/common": "1.27.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",