@powersync/node 0.12.0 → 0.14.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.
Files changed (53) hide show
  1. package/dist/DefaultWorker.cjs.map +1 -1
  2. package/dist/bundle.cjs +45 -21
  3. package/dist/bundle.cjs.map +1 -1
  4. package/dist/worker.cjs.map +1 -1
  5. package/lib/db/AsyncDatabase.js +1 -0
  6. package/lib/db/AsyncDatabase.js.map +1 -0
  7. package/lib/db/BetterSqliteWorker.js +1 -0
  8. package/lib/db/BetterSqliteWorker.js.map +1 -0
  9. package/lib/db/DefaultWorker.js +1 -0
  10. package/lib/db/DefaultWorker.js.map +1 -0
  11. package/lib/db/NodeSqliteWorker.js +1 -0
  12. package/lib/db/NodeSqliteWorker.js.map +1 -0
  13. package/lib/db/PowerSyncDatabase.js +1 -0
  14. package/lib/db/PowerSyncDatabase.js.map +1 -0
  15. package/lib/db/RemoteConnection.d.ts +3 -2
  16. package/lib/db/RemoteConnection.js +30 -12
  17. package/lib/db/RemoteConnection.js.map +1 -0
  18. package/lib/db/SqliteWorker.js +1 -0
  19. package/lib/db/SqliteWorker.js.map +1 -0
  20. package/lib/db/WorkerConnectionPool.d.ts +1 -1
  21. package/lib/db/WorkerConnectionPool.js +9 -4
  22. package/lib/db/WorkerConnectionPool.js.map +1 -0
  23. package/lib/db/options.js +1 -0
  24. package/lib/db/options.js.map +1 -0
  25. package/lib/index.js +1 -0
  26. package/lib/index.js.map +1 -0
  27. package/lib/sync/stream/NodeRemote.js +1 -0
  28. package/lib/sync/stream/NodeRemote.js.map +1 -0
  29. package/lib/sync/stream/NodeStreamingSyncImplementation.d.ts +3 -3
  30. package/lib/sync/stream/NodeStreamingSyncImplementation.js +8 -4
  31. package/lib/sync/stream/NodeStreamingSyncImplementation.js.map +1 -0
  32. package/lib/utils/modules.js +1 -0
  33. package/lib/utils/modules.js.map +1 -0
  34. package/lib/utils/modules_commonjs.js +1 -0
  35. package/lib/utils/modules_commonjs.js.map +1 -0
  36. package/lib/worker.js +1 -0
  37. package/lib/worker.js.map +1 -0
  38. package/package.json +10 -10
  39. package/src/db/AsyncDatabase.ts +26 -0
  40. package/src/db/BetterSqliteWorker.ts +72 -0
  41. package/src/db/DefaultWorker.ts +3 -0
  42. package/src/db/NodeSqliteWorker.ts +63 -0
  43. package/src/db/PowerSyncDatabase.ts +100 -0
  44. package/src/db/RemoteConnection.ts +129 -0
  45. package/src/db/SqliteWorker.ts +119 -0
  46. package/src/db/WorkerConnectionPool.ts +328 -0
  47. package/src/db/options.ts +51 -0
  48. package/src/index.ts +4 -0
  49. package/src/sync/stream/NodeRemote.ts +123 -0
  50. package/src/sync/stream/NodeStreamingSyncImplementation.ts +57 -0
  51. package/src/utils/modules.ts +6 -0
  52. package/src/utils/modules_commonjs.ts +7 -0
  53. package/src/worker.ts +1 -0
@@ -0,0 +1,129 @@
1
+ import { Worker } from 'node:worker_threads';
2
+ import { LockContext, QueryResult } from '@powersync/common';
3
+ import { releaseProxy, Remote } from 'comlink';
4
+ import { AsyncDatabase, AsyncDatabaseOpener, ProxiedQueryResult } from './AsyncDatabase.js';
5
+
6
+ /**
7
+ * A PowerSync database connection implemented with RPC calls to a background worker.
8
+ */
9
+ export class RemoteConnection implements LockContext {
10
+ isBusy = false;
11
+
12
+ private readonly worker: Worker;
13
+ private readonly comlink: Remote<AsyncDatabaseOpener>;
14
+ private readonly database: Remote<AsyncDatabase>;
15
+
16
+ private readonly notifyWorkerClosed = new AbortController();
17
+
18
+ constructor(worker: Worker, comlink: Remote<AsyncDatabaseOpener>, database: Remote<AsyncDatabase>) {
19
+ this.worker = worker;
20
+ this.comlink = comlink;
21
+ this.database = database;
22
+
23
+ this.worker.once('exit', (_) => {
24
+ this.notifyWorkerClosed.abort();
25
+ });
26
+ }
27
+
28
+ /**
29
+ * Runs the inner function, but appends the stack trace where this function was called. This is useful for workers
30
+ * because stack traces from worker errors are otherwise unrelated to the application issue that has caused them.
31
+ */
32
+ private withRemote<T>(inner: () => Promise<T>): Promise<T> {
33
+ const trace = {};
34
+ Error.captureStackTrace(trace);
35
+ const controller = this.notifyWorkerClosed;
36
+
37
+ return new Promise((resolve, reject) => {
38
+ if (controller.signal.aborted) {
39
+ reject(new Error('Called operation on closed remote'));
40
+ }
41
+
42
+ function handleAbort() {
43
+ reject(new Error('Remote peer closed with request in flight'));
44
+ }
45
+
46
+ function completePromise(action: () => void) {
47
+ controller!.signal.removeEventListener('abort', handleAbort);
48
+ action();
49
+ }
50
+
51
+ controller.signal.addEventListener('abort', handleAbort);
52
+
53
+ inner()
54
+ .then((data) => completePromise(() => resolve(data)))
55
+ .catch((e) => {
56
+ if (e instanceof Error && e.stack) {
57
+ e.stack += (trace as any).stack;
58
+ }
59
+
60
+ return completePromise(() => reject(e));
61
+ });
62
+ });
63
+ }
64
+
65
+ executeBatch(query: string, params: any[][] = []): Promise<QueryResult> {
66
+ return this.withRemote(async () => {
67
+ const result = await this.database.executeBatch(query, params ?? []);
68
+ return RemoteConnection.wrapQueryResult(result);
69
+ });
70
+ }
71
+
72
+ execute(query: string, params?: any[] | undefined): Promise<QueryResult> {
73
+ return this.withRemote(async () => {
74
+ const result = await this.database.execute(query, params ?? []);
75
+ return RemoteConnection.wrapQueryResult(result);
76
+ });
77
+ }
78
+
79
+ executeRaw(query: string, params?: any[] | undefined): Promise<any[][]> {
80
+ return this.withRemote(async () => {
81
+ return await this.database.executeRaw(query, params ?? []);
82
+ });
83
+ }
84
+
85
+ async getAll<T>(sql: string, parameters?: any[]): Promise<T[]> {
86
+ const res = await this.execute(sql, parameters);
87
+ return res.rows?._array ?? [];
88
+ }
89
+
90
+ async getOptional<T>(sql: string, parameters?: any[]): Promise<T | null> {
91
+ const res = await this.execute(sql, parameters);
92
+ return res.rows?.item(0) ?? null;
93
+ }
94
+
95
+ async get<T>(sql: string, parameters?: any[]): Promise<T> {
96
+ const res = await this.execute(sql, parameters);
97
+ const first = res.rows?.item(0);
98
+ if (!first) {
99
+ throw new Error('Result set is empty');
100
+ }
101
+ return first;
102
+ }
103
+
104
+ async refreshSchema() {
105
+ await this.execute("pragma table_info('sqlite_master')");
106
+ }
107
+
108
+ async close() {
109
+ await this.database.close();
110
+ this.database[releaseProxy]();
111
+ this.comlink[releaseProxy]();
112
+ await this.worker.terminate();
113
+ }
114
+
115
+ static wrapQueryResult(result: ProxiedQueryResult): QueryResult {
116
+ let rows: QueryResult['rows'] | undefined = undefined;
117
+ if (result.rows) {
118
+ rows = {
119
+ ...result.rows,
120
+ item: (idx) => result.rows?._array[idx]
121
+ } satisfies QueryResult['rows'];
122
+ }
123
+
124
+ return {
125
+ ...result,
126
+ rows
127
+ };
128
+ }
129
+ }
@@ -0,0 +1,119 @@
1
+ import * as Comlink from 'comlink';
2
+ import OS from 'node:os';
3
+ import * as path from 'node:path';
4
+ import url from 'node:url';
5
+ import { parentPort } from 'node:worker_threads';
6
+ import { dynamicImport, isBundledToCommonJs } from '../utils/modules.js';
7
+ import { AsyncDatabase, AsyncDatabaseOpener, AsyncDatabaseOpenOptions } from './AsyncDatabase.js';
8
+ import { openDatabase as openBetterSqliteDatabase } from './BetterSqliteWorker.js';
9
+ import { openDatabase as openNodeDatabase } from './NodeSqliteWorker.js';
10
+
11
+ export interface PowerSyncWorkerOptions {
12
+ /**
13
+ * A function responsible for finding the powersync DLL/so/dylib file.
14
+ *
15
+ * @returns The absolute path of the PowerSync SQLite core extensions library.
16
+ */
17
+ extensionPath: () => string;
18
+
19
+ /**
20
+ * A function that returns the `Database` constructor from the `better-sqlite3` package.
21
+ */
22
+ loadBetterSqlite3: () => Promise<any>;
23
+ }
24
+
25
+ /**
26
+ * @returns The relevant PowerSync extension binary filename for the current platform and architecture
27
+ */
28
+ export function getPowerSyncExtensionFilename() {
29
+ const platform = OS.platform();
30
+ const arch = OS.arch();
31
+ let extensionFile: string;
32
+
33
+ if (platform == 'win32') {
34
+ if (arch == 'x64') {
35
+ extensionFile = 'powersync_x64.dll';
36
+ } else if (arch == 'ia32') {
37
+ extensionFile = 'powersync_x86.dll';
38
+ } else if (arch == 'arm64') {
39
+ extensionFile = 'powersync_aarch64.dll';
40
+ } else {
41
+ throw new Error('Windows platform only supports arm64, ia32 and x64 architecture.');
42
+ }
43
+ } else if (platform == 'linux') {
44
+ if (arch == 'x64') {
45
+ extensionFile = 'libpowersync_x64.so';
46
+ } else if (arch == 'arm64') {
47
+ // TODO detect armv7 as an option
48
+ extensionFile = 'libpowersync_aarch64.so';
49
+ } else if (arch == 'riscv64') {
50
+ extensionFile = 'libpowersync_riscv64gc.so';
51
+ } else {
52
+ throw new Error('Linux platform only supports x64, arm64 and riscv64 architectures.');
53
+ }
54
+ } else if (platform == 'darwin') {
55
+ if (arch == 'x64') {
56
+ extensionFile = 'libpowersync_x64.dylib';
57
+ } else if (arch == 'arm64') {
58
+ extensionFile = 'libpowersync_aarch64.dylib';
59
+ } else {
60
+ throw new Error('macOS platform only supports x64 and arm64 architectures.');
61
+ }
62
+ } else {
63
+ throw new Error(
64
+ `Unknown platform: ${platform}, PowerSync for Node.js currently supports Windows, Linux and macOS.`
65
+ );
66
+ }
67
+
68
+ return extensionFile;
69
+ }
70
+
71
+ export function startPowerSyncWorker(options?: Partial<PowerSyncWorkerOptions>) {
72
+ const resolvedOptions: PowerSyncWorkerOptions = {
73
+ extensionPath() {
74
+ const isCommonJsModule = isBundledToCommonJs;
75
+ const extensionFilename = getPowerSyncExtensionFilename();
76
+ let resolved: string;
77
+ if (isCommonJsModule) {
78
+ resolved = path.resolve(__dirname, '../lib/', extensionFilename);
79
+ } else {
80
+ resolved = url.fileURLToPath(new URL(`../${extensionFilename}`, import.meta.url));
81
+ }
82
+
83
+ return resolved;
84
+ },
85
+ async loadBetterSqlite3() {
86
+ const module = await dynamicImport('better-sqlite3');
87
+ return module.default;
88
+ },
89
+ ...options
90
+ };
91
+
92
+ Comlink.expose(new DatabaseOpenHelper(resolvedOptions), parentPort! as Comlink.Endpoint);
93
+ }
94
+
95
+ class DatabaseOpenHelper implements AsyncDatabaseOpener {
96
+ private options: PowerSyncWorkerOptions;
97
+
98
+ constructor(options: PowerSyncWorkerOptions) {
99
+ this.options = options;
100
+ }
101
+
102
+ async open(options: AsyncDatabaseOpenOptions): Promise<AsyncDatabase> {
103
+ let database: AsyncDatabase;
104
+
105
+ const implementation = options.implementation;
106
+ switch (implementation.type) {
107
+ case 'better-sqlite3':
108
+ database = await openBetterSqliteDatabase(this.options, options);
109
+ break;
110
+ case 'node:sqlite':
111
+ database = await openNodeDatabase(this.options, options);
112
+ break;
113
+ default:
114
+ throw new Error(`Unknown database implementation: ${options.implementation}.`);
115
+ }
116
+
117
+ return Comlink.proxy(database);
118
+ }
119
+ }
@@ -0,0 +1,328 @@
1
+ import * as Comlink from 'comlink';
2
+ import fs from 'node:fs/promises';
3
+ import * as path from 'node:path';
4
+ import { Worker } from 'node:worker_threads';
5
+
6
+ import {
7
+ BaseObserver,
8
+ BatchedUpdateNotification,
9
+ DBAdapter,
10
+ DBAdapterListener,
11
+ DBLockOptions,
12
+ LockContext,
13
+ QueryResult,
14
+ Transaction
15
+ } from '@powersync/common';
16
+ import { Remote } from 'comlink';
17
+ import { AsyncResource } from 'node:async_hooks';
18
+ import { isBundledToCommonJs } from '../utils/modules.js';
19
+ import { AsyncDatabase, AsyncDatabaseOpener } from './AsyncDatabase.js';
20
+ import { RemoteConnection } from './RemoteConnection.js';
21
+ import { NodeDatabaseImplementation, NodeSQLOpenOptions } from './options.js';
22
+
23
+ export type BetterSQLite3LockContext = LockContext & {
24
+ executeBatch(query: string, params?: any[][]): Promise<QueryResult>;
25
+ };
26
+
27
+ export type BetterSQLite3Transaction = Transaction & BetterSQLite3LockContext;
28
+
29
+ const READ_CONNECTIONS = 5;
30
+
31
+ const defaultDatabaseImplementation: NodeDatabaseImplementation = {
32
+ type: 'better-sqlite3'
33
+ };
34
+
35
+ /**
36
+ * Adapter for better-sqlite3
37
+ */
38
+ export class WorkerConnectionPool extends BaseObserver<DBAdapterListener> implements DBAdapter {
39
+ private readonly options: NodeSQLOpenOptions;
40
+ public readonly name: string;
41
+
42
+ private readConnections: RemoteConnection[];
43
+ private writeConnection: RemoteConnection;
44
+
45
+ private readonly readQueue: Array<(connection: RemoteConnection) => void> = [];
46
+ private readonly writeQueue: Array<() => void> = [];
47
+
48
+ constructor(options: NodeSQLOpenOptions) {
49
+ super();
50
+
51
+ if (options.readWorkerCount != null && options.readWorkerCount < 1) {
52
+ throw `Needs at least one worker for reads, got ${options.readWorkerCount}`;
53
+ }
54
+
55
+ this.options = options;
56
+ this.name = options.dbFilename;
57
+ }
58
+
59
+ async initialize() {
60
+ let dbFilePath = this.options.dbFilename;
61
+ if (this.options.dbLocation !== undefined) {
62
+ // Make sure the dbLocation exists, we get a TypeError from better-sqlite3 otherwise.
63
+ let directoryExists = false;
64
+ try {
65
+ const stat = await fs.stat(this.options.dbLocation);
66
+ directoryExists = stat.isDirectory();
67
+ } catch (_) {
68
+ // If we can't even stat, the directory won't be accessible to SQLite either.
69
+ }
70
+
71
+ if (!directoryExists) {
72
+ throw new Error(
73
+ `The dbLocation directory at "${this.options.dbLocation}" does not exist. Please create it before opening the PowerSync database!`
74
+ );
75
+ }
76
+
77
+ dbFilePath = path.join(this.options.dbLocation, dbFilePath);
78
+ }
79
+
80
+ const openWorker = async (isWriter: boolean) => {
81
+ const isCommonJsModule = isBundledToCommonJs;
82
+ let worker: Worker;
83
+ const workerName = isWriter ? `write ${dbFilePath}` : `read ${dbFilePath}`;
84
+
85
+ const workerFactory = this.options.openWorker ?? ((...args) => new Worker(...args));
86
+ if (isCommonJsModule) {
87
+ worker = workerFactory(path.resolve(__dirname, 'DefaultWorker.cjs'), { name: workerName });
88
+ } else {
89
+ worker = workerFactory(new URL('./DefaultWorker.js', import.meta.url), { name: workerName });
90
+ }
91
+
92
+ const listeners = new WeakMap<EventListenerOrEventListenerObject, (e: any) => void>();
93
+
94
+ const comlink = Comlink.wrap<AsyncDatabaseOpener>({
95
+ postMessage: worker.postMessage.bind(worker),
96
+ addEventListener: (type, listener) => {
97
+ let resolved: (event: any) => void =
98
+ 'handleEvent' in listener ? listener.handleEvent.bind(listener) : listener;
99
+
100
+ // Comlink wants message events, but the message event on workers in Node returns the data only.
101
+ if (type === 'message') {
102
+ const original = resolved;
103
+
104
+ resolved = (data) => {
105
+ original({ data });
106
+ };
107
+ }
108
+
109
+ listeners.set(listener, resolved);
110
+ worker.addListener(type, resolved);
111
+ },
112
+ removeEventListener: (type, listener) => {
113
+ const resolved = listeners.get(listener);
114
+ if (!resolved) {
115
+ return;
116
+ }
117
+ worker.removeListener(type, resolved);
118
+ }
119
+ });
120
+
121
+ worker.once('error', (e) => {
122
+ console.error('Unexpected PowerSync database worker error', e);
123
+ });
124
+
125
+ const database = (await comlink.open({
126
+ path: dbFilePath,
127
+ isWriter,
128
+ implementation: this.options.implementation ?? defaultDatabaseImplementation
129
+ })) as Remote<AsyncDatabase>;
130
+ if (isWriter) {
131
+ await database.execute("SELECT powersync_update_hooks('install');", []);
132
+ }
133
+
134
+ const connection = new RemoteConnection(worker, comlink, database);
135
+ if (this.options.initializeConnection) {
136
+ await this.options.initializeConnection(connection, isWriter);
137
+ }
138
+ if (!isWriter) {
139
+ await connection.execute('pragma query_only = true');
140
+ } else {
141
+ // We only need to enable this on the writer connection.
142
+ // We can get `database is locked` errors if we enable this on concurrently opening read connections.
143
+ await connection.execute('pragma journal_mode = WAL');
144
+ }
145
+
146
+ return connection;
147
+ };
148
+
149
+ // Open the writer first to avoid multiple threads enabling WAL concurrently (causing "database is locked" errors).
150
+ this.writeConnection = await openWorker(true);
151
+ const createWorkers: Promise<RemoteConnection>[] = [];
152
+ const amountOfReaders = this.options.readWorkerCount ?? READ_CONNECTIONS;
153
+ for (let i = 0; i < amountOfReaders; i++) {
154
+ createWorkers.push(openWorker(false));
155
+ }
156
+ this.readConnections = await Promise.all(createWorkers);
157
+ }
158
+
159
+ async close() {
160
+ await this.writeConnection.close();
161
+ for (const connection of this.readConnections) {
162
+ await connection.close();
163
+ }
164
+ }
165
+
166
+ readLock<T>(fn: (tx: BetterSQLite3LockContext) => Promise<T>, _options?: DBLockOptions | undefined): Promise<T> {
167
+ let resolveConnectionPromise!: (connection: RemoteConnection) => void;
168
+ const connectionPromise = new Promise<RemoteConnection>((resolve, _reject) => {
169
+ resolveConnectionPromise = AsyncResource.bind(resolve);
170
+ });
171
+
172
+ const connection = this.readConnections.find((connection) => !connection.isBusy);
173
+ if (connection) {
174
+ connection.isBusy = true;
175
+ resolveConnectionPromise(connection);
176
+ } else {
177
+ this.readQueue.push(resolveConnectionPromise);
178
+ }
179
+
180
+ return (async () => {
181
+ const connection = await connectionPromise;
182
+
183
+ try {
184
+ return await fn(connection);
185
+ } finally {
186
+ const next = this.readQueue.shift();
187
+ if (next) {
188
+ next(connection);
189
+ } else {
190
+ connection.isBusy = false;
191
+ }
192
+ }
193
+ })();
194
+ }
195
+
196
+ writeLock<T>(fn: (tx: BetterSQLite3LockContext) => Promise<T>, _options?: DBLockOptions | undefined): Promise<T> {
197
+ let resolveLockPromise!: () => void;
198
+ const lockPromise = new Promise<void>((resolve, _reject) => {
199
+ resolveLockPromise = AsyncResource.bind(resolve);
200
+ });
201
+
202
+ if (!this.writeConnection.isBusy) {
203
+ this.writeConnection.isBusy = true;
204
+ resolveLockPromise();
205
+ } else {
206
+ this.writeQueue.push(resolveLockPromise);
207
+ }
208
+
209
+ return (async () => {
210
+ await lockPromise;
211
+
212
+ try {
213
+ try {
214
+ return await fn(this.writeConnection);
215
+ } finally {
216
+ const serializedUpdates = await this.writeConnection.executeRaw("SELECT powersync_update_hooks('get');", []);
217
+ const updates = JSON.parse(serializedUpdates[0][0] as string) as string[];
218
+
219
+ if (updates.length > 0) {
220
+ const event: BatchedUpdateNotification = {
221
+ tables: updates,
222
+ groupedUpdates: {},
223
+ rawUpdates: []
224
+ };
225
+ this.iterateListeners((cb) => cb.tablesUpdated?.(event));
226
+ }
227
+ }
228
+ } finally {
229
+ const next = this.writeQueue.shift();
230
+ if (next) {
231
+ next();
232
+ } else {
233
+ this.writeConnection.isBusy = false;
234
+ }
235
+ }
236
+ })();
237
+ }
238
+
239
+ readTransaction<T>(
240
+ fn: (tx: BetterSQLite3Transaction) => Promise<T>,
241
+ _options?: DBLockOptions | undefined
242
+ ): Promise<T> {
243
+ return this.readLock((ctx) => this.internalTransaction(ctx as RemoteConnection, fn));
244
+ }
245
+
246
+ writeTransaction<T>(
247
+ fn: (tx: BetterSQLite3Transaction) => Promise<T>,
248
+ _options?: DBLockOptions | undefined
249
+ ): Promise<T> {
250
+ return this.writeLock((ctx) => this.internalTransaction(ctx as RemoteConnection, fn));
251
+ }
252
+
253
+ private async internalTransaction<T>(
254
+ connection: RemoteConnection,
255
+ fn: (tx: BetterSQLite3Transaction) => Promise<T>
256
+ ): Promise<T> {
257
+ let finalized = false;
258
+ const commit = async (): Promise<QueryResult> => {
259
+ if (!finalized) {
260
+ finalized = true;
261
+ await connection.execute('COMMIT');
262
+ }
263
+ return { rowsAffected: 0 };
264
+ };
265
+ const rollback = async (): Promise<QueryResult> => {
266
+ if (!finalized) {
267
+ finalized = true;
268
+ await connection.execute('ROLLBACK');
269
+ }
270
+ return { rowsAffected: 0 };
271
+ };
272
+ try {
273
+ await connection.execute('BEGIN');
274
+ const result = await fn({
275
+ execute: (query, params) => connection.execute(query, params),
276
+ executeRaw: (query, params) => connection.executeRaw(query, params),
277
+ executeBatch: (query, params) => connection.executeBatch(query, params),
278
+ get: (query, params) => connection.get(query, params),
279
+ getAll: (query, params) => connection.getAll(query, params),
280
+ getOptional: (query, params) => connection.getOptional(query, params),
281
+ commit,
282
+ rollback
283
+ });
284
+ await commit();
285
+ return result;
286
+ } catch (ex) {
287
+ try {
288
+ await rollback();
289
+ } catch (ex2) {
290
+ // In rare cases, a rollback may fail.
291
+ // Safe to ignore.
292
+ }
293
+ throw ex;
294
+ }
295
+ }
296
+
297
+ getAll<T>(sql: string, parameters?: any[]): Promise<T[]> {
298
+ return this.readLock((ctx) => ctx.getAll(sql, parameters));
299
+ }
300
+
301
+ getOptional<T>(sql: string, parameters?: any[]): Promise<T | null> {
302
+ return this.readLock((ctx) => ctx.getOptional(sql, parameters));
303
+ }
304
+
305
+ get<T>(sql: string, parameters?: any[]): Promise<T> {
306
+ return this.readLock((ctx) => ctx.get(sql, parameters));
307
+ }
308
+
309
+ execute(query: string, params?: any[] | undefined): Promise<QueryResult> {
310
+ return this.writeLock((ctx) => ctx.execute(query, params));
311
+ }
312
+
313
+ executeRaw(query: string, params?: any[] | undefined): Promise<any[][]> {
314
+ return this.writeLock((ctx) => ctx.executeRaw(query, params));
315
+ }
316
+
317
+ executeBatch(query: string, params?: any[][]): Promise<QueryResult> {
318
+ return this.writeTransaction((ctx) => ctx.executeBatch(query, params));
319
+ }
320
+
321
+ async refreshSchema() {
322
+ await this.writeConnection.refreshSchema();
323
+
324
+ for (const readConnection of this.readConnections) {
325
+ await readConnection.refreshSchema();
326
+ }
327
+ }
328
+ }
@@ -0,0 +1,51 @@
1
+ import { type Worker } from 'node:worker_threads';
2
+ import { LockContext, SQLOpenOptions } from '@powersync/common';
3
+
4
+ export type WorkerOpener = (...args: ConstructorParameters<typeof Worker>) => InstanceType<typeof Worker>;
5
+
6
+ /**
7
+ * Use the [better-sqlite3](https://github.com/WiseLibs/better-sqlite3) package as a SQLite driver for PowerSync.
8
+ */
9
+ export interface BetterSqlite3Options {
10
+ type: 'better-sqlite3';
11
+ }
12
+
13
+ /**
14
+ * Use the experimental `node:sqlite` interface as a SQLite driver for PowerSync.
15
+ *
16
+ * Note that this option is not currently tested and highly unstable.
17
+ */
18
+ export interface NodeSqliteOptions {
19
+ type: 'node:sqlite';
20
+ }
21
+
22
+ export type NodeDatabaseImplementation = BetterSqlite3Options | NodeSqliteOptions;
23
+
24
+ /**
25
+ * The {@link SQLOpenOptions} available across all PowerSync SDKs for JavaScript extended with
26
+ * Node.JS-specific options.
27
+ */
28
+ export interface NodeSQLOpenOptions extends SQLOpenOptions {
29
+ implementation?: NodeDatabaseImplementation;
30
+
31
+ /**
32
+ * The Node.JS SDK will use one worker to run writing queries and additional workers to run reads.
33
+ * This option controls how many workers to use for reads.
34
+ */
35
+ readWorkerCount?: number;
36
+ /**
37
+ * A callback to allow customizing how the Node.JS SDK loads workers. This can be customized to
38
+ * use workers at different paths.
39
+ *
40
+ * @param args The arguments that would otherwise be passed to the {@link Worker} constructor.
41
+ * @returns the resolved worker.
42
+ */
43
+ openWorker?: WorkerOpener;
44
+
45
+ /**
46
+ * Initializes a created database connection.
47
+ *
48
+ * This can be used to e.g. set encryption keys, if an encrypted database should be used.
49
+ */
50
+ initializeConnection?: (db: LockContext, isWriter: boolean) => Promise<void>;
51
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ // Re export to only require one import in client side code
2
+ export * from '@powersync/common';
3
+
4
+ export * from './db/PowerSyncDatabase.js';