@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 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
- This package has an API similar to the PowerSync web SDK, for which a summary of features is available [here](https://docs.powersync.com/client-sdk-references/js-web).
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
- You can follow along our [web SDK reference](https://docs.powersync.com/client-sdk-references/js-web)
28
- which contains everything you need to know to get started implementing PowerSync in your project.
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, 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.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 - sync Postgres with SQLite in your Node.js app for offline-first and real-time data",
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.22.0",
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.25.0"
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",