@powersync/web 0.0.0-dev-20250922104723 → 0.0.0-dev-20250922105207
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/dist/2dcb8a60ed4b341d3046.wasm +0 -0
- package/dist/50eaffc7435d7a77ffb5.wasm +0 -0
- package/dist/88ca2d3820062df8ea26.wasm +0 -0
- package/dist/efe40e50208db1807722.wasm +0 -0
- package/dist/index.umd.js +103 -72
- package/dist/index.umd.js.map +1 -1
- package/dist/worker/SharedSyncImplementation.umd.js +112 -221
- package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
- package/dist/worker/WASQLiteDB.umd.js +232 -121
- package/dist/worker/WASQLiteDB.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite-async_mjs.umd.js +15 -3
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite-async_mjs.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite_mjs.umd.js +15 -3
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite_mjs.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite-async_mjs.umd.js +15 -3
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite-async_mjs.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js +15 -3
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js.umd.js +46 -218
- package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js.umd.js +46 -218
- package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_OPFSCoopSyncVFS_js.umd.js +46 -218
- package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_OPFSCoopSyncVFS_js.umd.js.map +1 -1
- package/lib/package.json +4 -4
- package/lib/src/db/PowerSyncDatabase.js +3 -1
- package/lib/src/db/adapters/AsyncDatabaseConnection.d.ts +13 -1
- package/lib/src/db/adapters/wa-sqlite/WASQLiteConnection.d.ts +3 -1
- package/lib/src/db/adapters/wa-sqlite/WASQLiteConnection.js +28 -2
- package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.d.ts +1 -1
- package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.js +6 -2
- package/lib/src/db/sync/SSRWebStreamingSyncImplementation.d.ts +0 -4
- package/lib/src/db/sync/SSRWebStreamingSyncImplementation.js +0 -4
- package/lib/src/db/sync/SharedWebStreamingSyncImplementation.d.ts +3 -8
- package/lib/src/db/sync/SharedWebStreamingSyncImplementation.js +2 -22
- package/lib/src/worker/db/LogHandler.d.ts +12 -0
- package/lib/src/worker/db/LogHandler.js +6 -0
- package/lib/src/worker/db/WASQLiteDB.worker.js +80 -17
- package/lib/src/worker/sync/SharedSyncImplementation.d.ts +6 -14
- package/lib/src/worker/sync/SharedSyncImplementation.js +4 -28
- package/lib/src/worker/sync/SharedSyncImplementation.worker.js +19 -3
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/dist/31ba59416bad61e8fb1f.wasm +0 -0
- package/dist/d0a1e43030b814ed322f.wasm +0 -0
- package/dist/f4ad8bfeb6e6e5326142.wasm +0 -0
- package/dist/fbde47713220d7baec73.wasm +0 -0
- package/lib/src/worker/sync/WorkerClient.d.ts +0 -32
- package/lib/src/worker/sync/WorkerClient.js +0 -84
|
@@ -59,6 +59,7 @@ export const DEFAULT_MODULE_FACTORIES = {
|
|
|
59
59
|
},
|
|
60
60
|
[WASQLiteVFS.AccessHandlePoolVFS]: async (options) => {
|
|
61
61
|
let module;
|
|
62
|
+
options.logger?.debug(`Opening VFS with options`, JSON.stringify(options));
|
|
62
63
|
if (options.encryptionKey) {
|
|
63
64
|
module = await MultiCipherSyncWASQLiteModuleFactory();
|
|
64
65
|
}
|
|
@@ -67,9 +68,26 @@ export const DEFAULT_MODULE_FACTORIES = {
|
|
|
67
68
|
}
|
|
68
69
|
// @ts-expect-error The types for this static method are missing upstream
|
|
69
70
|
const { AccessHandlePoolVFS } = await import('@journeyapps/wa-sqlite/src/examples/AccessHandlePoolVFS.js');
|
|
71
|
+
const vfs = new AccessHandlePoolVFS(options.dbFileName, module);
|
|
72
|
+
// Allow extra logs
|
|
73
|
+
const proxiedVFS = new Proxy(vfs, {
|
|
74
|
+
get(target, prop, receiver) {
|
|
75
|
+
const value = Reflect.get(target, prop, receiver);
|
|
76
|
+
// Only wrap methods, not properties
|
|
77
|
+
if (typeof value === 'function') {
|
|
78
|
+
return function (...args) {
|
|
79
|
+
options.logger?.debug(`VFS: Called ${String(prop)} with:`, args);
|
|
80
|
+
return value.apply(vfs, args);
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
await vfs.isReady();
|
|
87
|
+
// const vfs = await AccessHandlePoolVFS.create(options.dbFileName, module);
|
|
70
88
|
return {
|
|
71
89
|
module,
|
|
72
|
-
vfs:
|
|
90
|
+
vfs: proxiedVFS
|
|
73
91
|
};
|
|
74
92
|
},
|
|
75
93
|
[WASQLiteVFS.OPFSCoopSyncVFS]: async (options) => {
|
|
@@ -142,7 +160,9 @@ export class WASqliteConnection extends BaseObserver {
|
|
|
142
160
|
async openSQLiteAPI() {
|
|
143
161
|
const { module, vfs } = await this._moduleFactory({
|
|
144
162
|
dbFileName: this.options.dbFilename,
|
|
145
|
-
encryptionKey: this.options.encryptionKey
|
|
163
|
+
encryptionKey: this.options.encryptionKey,
|
|
164
|
+
debugMode: this.options.debugMode,
|
|
165
|
+
logger: this.options.logger
|
|
146
166
|
});
|
|
147
167
|
const sqlite3 = SQLite.Factory(module);
|
|
148
168
|
sqlite3.vfs_register(vfs, true);
|
|
@@ -180,6 +200,7 @@ export class WASqliteConnection extends BaseObserver {
|
|
|
180
200
|
}
|
|
181
201
|
}
|
|
182
202
|
async init() {
|
|
203
|
+
this.options.logger?.debug('Opening WASQLite connection for', this.options.dbFilename);
|
|
183
204
|
this._sqliteAPI = await this.openSQLiteAPI();
|
|
184
205
|
await this.openDB();
|
|
185
206
|
this.registerBroadcastListeners();
|
|
@@ -277,6 +298,7 @@ export class WASqliteConnection extends BaseObserver {
|
|
|
277
298
|
});
|
|
278
299
|
}
|
|
279
300
|
async close() {
|
|
301
|
+
this.options.logger?.debug('Closing WASQLite connection', this.options.dbFilename);
|
|
280
302
|
this.broadcastChannel?.close();
|
|
281
303
|
await this.sqliteAPI.close(this.dbP);
|
|
282
304
|
}
|
|
@@ -325,6 +347,10 @@ export class WASqliteConnection extends BaseObserver {
|
|
|
325
347
|
return results.flatMap((resultset) => resultset.rows.map((row) => resultset.columns.map((_, index) => row[index])));
|
|
326
348
|
}
|
|
327
349
|
async _execute(sql, bindings) {
|
|
350
|
+
// TODO, this might be redundant
|
|
351
|
+
if (this.options.debugMode) {
|
|
352
|
+
this.options.logger?.debug(`Executing SQL: ${sql}`);
|
|
353
|
+
}
|
|
328
354
|
const results = [];
|
|
329
355
|
for await (const stmt of this.sqliteAPI.statements(this.dbP, sql)) {
|
|
330
356
|
let columns;
|
|
@@ -9,7 +9,7 @@ export interface WASQLiteOpenFactoryOptions extends WebSQLOpenFactoryOptions {
|
|
|
9
9
|
export interface ResolvedWASQLiteOpenFactoryOptions extends ResolvedWebSQLOpenOptions {
|
|
10
10
|
vfs: WASQLiteVFS;
|
|
11
11
|
}
|
|
12
|
-
export interface WorkerDBOpenerOptions extends ResolvedWASQLiteOpenFactoryOptions {
|
|
12
|
+
export interface WorkerDBOpenerOptions extends Omit<ResolvedWASQLiteOpenFactoryOptions, 'logger'> {
|
|
13
13
|
logLevel: ILogLevel;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
@@ -52,8 +52,12 @@ export class WASQLiteOpenFactory extends AbstractWebSQLOpenFactory {
|
|
|
52
52
|
cacheSizeKb,
|
|
53
53
|
flags: this.resolvedFlags,
|
|
54
54
|
encryptionKey: encryptionKey,
|
|
55
|
-
logLevel: this.logger.getLevel()
|
|
56
|
-
|
|
55
|
+
logLevel: this.logger.getLevel(),
|
|
56
|
+
debugMode: this.options.debugMode
|
|
57
|
+
}, Comlink.proxy((event) => {
|
|
58
|
+
// TODO
|
|
59
|
+
this.logger[event.logLevel.toLocaleLowerCase()]('[DB Worker] ', ...event.messages);
|
|
60
|
+
})),
|
|
57
61
|
identifier: this.options.dbFilename,
|
|
58
62
|
onClose: () => {
|
|
59
63
|
if (workerPort instanceof Worker) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { PowerSyncConnectionOptions, PowerSyncCredentials,
|
|
1
|
+
import { PowerSyncConnectionOptions, PowerSyncCredentials, SyncStatusOptions } from '@powersync/common';
|
|
2
2
|
import * as Comlink from 'comlink';
|
|
3
3
|
import { AbstractSharedSyncClientProvider } from '../../worker/sync/AbstractSharedSyncClientProvider';
|
|
4
|
+
import { SharedSyncImplementation } from '../../worker/sync/SharedSyncImplementation';
|
|
4
5
|
import { WebDBAdapter } from '../adapters/WebDBAdapter';
|
|
5
6
|
import { WebStreamingSyncImplementation, WebStreamingSyncImplementationOptions } from './WebStreamingSyncImplementation';
|
|
6
|
-
import { WorkerClient } from '../../worker/sync/WorkerClient';
|
|
7
7
|
/**
|
|
8
8
|
* The shared worker will trigger methods on this side of the message port
|
|
9
9
|
* via this client provider.
|
|
@@ -30,16 +30,12 @@ declare class SharedSyncClientProvider extends AbstractSharedSyncClientProvider
|
|
|
30
30
|
export interface SharedWebStreamingSyncImplementationOptions extends WebStreamingSyncImplementationOptions {
|
|
31
31
|
db: WebDBAdapter;
|
|
32
32
|
}
|
|
33
|
-
/**
|
|
34
|
-
* The local part of the sync implementation on the web, which talks to a sync implementation hosted in a shared worker.
|
|
35
|
-
*/
|
|
36
33
|
export declare class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplementation {
|
|
37
|
-
protected syncManager: Comlink.Remote<
|
|
34
|
+
protected syncManager: Comlink.Remote<SharedSyncImplementation>;
|
|
38
35
|
protected clientProvider: SharedSyncClientProvider;
|
|
39
36
|
protected messagePort: MessagePort;
|
|
40
37
|
protected isInitialized: Promise<void>;
|
|
41
38
|
protected dbAdapter: WebDBAdapter;
|
|
42
|
-
private abortOnClose;
|
|
43
39
|
constructor(options: SharedWebStreamingSyncImplementationOptions);
|
|
44
40
|
/**
|
|
45
41
|
* Starts the sync process, this effectively acts as a call to
|
|
@@ -51,7 +47,6 @@ export declare class SharedWebStreamingSyncImplementation extends WebStreamingSy
|
|
|
51
47
|
hasCompletedSync(): Promise<boolean>;
|
|
52
48
|
dispose(): Promise<void>;
|
|
53
49
|
waitForReady(): Promise<void>;
|
|
54
|
-
updateSubscriptions(subscriptions: SubscribedStream[]): void;
|
|
55
50
|
/**
|
|
56
51
|
* Used in tests to force a connection states
|
|
57
52
|
*/
|
|
@@ -3,7 +3,6 @@ import { AbstractSharedSyncClientProvider } from '../../worker/sync/AbstractShar
|
|
|
3
3
|
import { SharedSyncClientEvent } from '../../worker/sync/SharedSyncImplementation';
|
|
4
4
|
import { DEFAULT_CACHE_SIZE_KB, resolveWebSQLFlags, TemporaryStorageOption } from '../adapters/web-sql-flags';
|
|
5
5
|
import { WebStreamingSyncImplementation } from './WebStreamingSyncImplementation';
|
|
6
|
-
import { getNavigatorLocks } from '../../shared/navigator';
|
|
7
6
|
/**
|
|
8
7
|
* The shared worker will trigger methods on this side of the message port
|
|
9
8
|
* via this client provider.
|
|
@@ -76,16 +75,12 @@ class SharedSyncClientProvider extends AbstractSharedSyncClientProvider {
|
|
|
76
75
|
this.logger?.timeEnd(label);
|
|
77
76
|
}
|
|
78
77
|
}
|
|
79
|
-
/**
|
|
80
|
-
* The local part of the sync implementation on the web, which talks to a sync implementation hosted in a shared worker.
|
|
81
|
-
*/
|
|
82
78
|
export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplementation {
|
|
83
79
|
syncManager;
|
|
84
80
|
clientProvider;
|
|
85
81
|
messagePort;
|
|
86
82
|
isInitialized;
|
|
87
83
|
dbAdapter;
|
|
88
|
-
abortOnClose = new AbortController();
|
|
89
84
|
constructor(options) {
|
|
90
85
|
super(options);
|
|
91
86
|
this.dbAdapter = options.db;
|
|
@@ -138,7 +133,7 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
|
|
|
138
133
|
retryDelayMs,
|
|
139
134
|
flags: flags
|
|
140
135
|
}
|
|
141
|
-
}
|
|
136
|
+
});
|
|
142
137
|
/**
|
|
143
138
|
* Pass along any sync status updates to this listener
|
|
144
139
|
*/
|
|
@@ -151,17 +146,6 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
|
|
|
151
146
|
* This performs bi-directional method calling.
|
|
152
147
|
*/
|
|
153
148
|
Comlink.expose(this.clientProvider, this.messagePort);
|
|
154
|
-
// Request a random lock until this client is disposed. The name of the lock is sent to the shared worker, which
|
|
155
|
-
// will also attempt to acquire it. Since the lock is returned when the tab is closed, this allows the share worker
|
|
156
|
-
// to free resources associated with this tab.
|
|
157
|
-
getNavigatorLocks().request(`tab-close-signal-${crypto.randomUUID()}`, async (lock) => {
|
|
158
|
-
if (!this.abortOnClose.signal.aborted) {
|
|
159
|
-
this.syncManager.addLockBasedCloseSignal(lock.name);
|
|
160
|
-
await new Promise((r) => {
|
|
161
|
-
this.abortOnClose.signal.onabort = () => r();
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
149
|
}
|
|
166
150
|
/**
|
|
167
151
|
* Starts the sync process, this effectively acts as a call to
|
|
@@ -200,7 +184,6 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
|
|
|
200
184
|
};
|
|
201
185
|
this.messagePort.postMessage(closeMessagePayload);
|
|
202
186
|
});
|
|
203
|
-
this.abortOnClose.abort();
|
|
204
187
|
// Release the proxy
|
|
205
188
|
this.syncManager[Comlink.releaseProxy]();
|
|
206
189
|
this.messagePort.close();
|
|
@@ -208,14 +191,11 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
|
|
|
208
191
|
async waitForReady() {
|
|
209
192
|
return this.isInitialized;
|
|
210
193
|
}
|
|
211
|
-
updateSubscriptions(subscriptions) {
|
|
212
|
-
this.syncManager.updateSubscriptions(subscriptions);
|
|
213
|
-
}
|
|
214
194
|
/**
|
|
215
195
|
* Used in tests to force a connection states
|
|
216
196
|
*/
|
|
217
197
|
async _testUpdateStatus(status) {
|
|
218
198
|
await this.isInitialized;
|
|
219
|
-
return this.syncManager
|
|
199
|
+
return this.syncManager['_testUpdateAllStatuses'](status.toJSON());
|
|
220
200
|
}
|
|
221
201
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseListener, BaseObserver } from '@powersync/common';
|
|
2
|
+
export type LogEvent = {
|
|
3
|
+
loggerName: string;
|
|
4
|
+
logLevel: string;
|
|
5
|
+
messages: string[];
|
|
6
|
+
};
|
|
7
|
+
export interface LogHandlerListener extends BaseListener {
|
|
8
|
+
onLog: (event: LogEvent) => void;
|
|
9
|
+
}
|
|
10
|
+
export declare class LogHandler extends BaseObserver<LogHandlerListener> {
|
|
11
|
+
pushLog(entry: LogEvent): void;
|
|
12
|
+
}
|
|
@@ -2,21 +2,70 @@
|
|
|
2
2
|
* Supports both shared and dedicated workers, based on how the worker is constructed (new SharedWorker vs new Worker()).
|
|
3
3
|
*/
|
|
4
4
|
import '@journeyapps/wa-sqlite';
|
|
5
|
-
import { createBaseLogger, createLogger } from '@powersync/common';
|
|
5
|
+
import { createBaseLogger, createLogger, LogLevel } from '@powersync/common';
|
|
6
6
|
import * as Comlink from 'comlink';
|
|
7
7
|
import { WASqliteConnection } from '../../db/adapters/wa-sqlite/WASQLiteConnection';
|
|
8
8
|
import { getNavigatorLocks } from '../../shared/navigator';
|
|
9
|
-
|
|
10
|
-
baseLogger.useDefaults();
|
|
11
|
-
const logger = createLogger('db-worker');
|
|
9
|
+
import { LogHandler } from './LogHandler';
|
|
12
10
|
const DBMap = new Map();
|
|
13
11
|
const OPEN_DB_LOCK = 'open-wasqlite-db';
|
|
12
|
+
function logToAll(logLevel, ...messages) {
|
|
13
|
+
for (const dbFilename of DBMap.keys()) {
|
|
14
|
+
logBroadcaster.pushLog({
|
|
15
|
+
loggerName: dbFilename,
|
|
16
|
+
logLevel,
|
|
17
|
+
messages
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
14
21
|
let nextClientId = 1;
|
|
22
|
+
function logUnhandledException(error) {
|
|
23
|
+
const errorMessage = `
|
|
24
|
+
Name: ${error.name}
|
|
25
|
+
Cause: ${error.cause}
|
|
26
|
+
Message: ${error.message}
|
|
27
|
+
Stack: ${error.stack}`.trim();
|
|
28
|
+
logToAll(LogLevel.ERROR.name, 'Uncaught Exception in DB worker', errorMessage);
|
|
29
|
+
}
|
|
30
|
+
// Report unhandled exceptions to all loggers
|
|
31
|
+
addEventListener('unhandledrejection', (event) => {
|
|
32
|
+
logUnhandledException(event.reason);
|
|
33
|
+
});
|
|
34
|
+
addEventListener('error', (event) => {
|
|
35
|
+
logUnhandledException(event.error);
|
|
36
|
+
});
|
|
37
|
+
addEventListener('unload', () => {
|
|
38
|
+
logToAll(LogLevel.INFO.name, 'DB worker is unloading');
|
|
39
|
+
Array.from(DBMap.values()).forEach(async (dbConnection) => {
|
|
40
|
+
const { db, clientIds } = dbConnection;
|
|
41
|
+
logToAll(LogLevel.INFO.name, `closing db with ids ${clientIds}`);
|
|
42
|
+
db.close();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
const baseLogger = createBaseLogger();
|
|
46
|
+
const logBroadcaster = new LogHandler();
|
|
47
|
+
const defaultHandler = baseLogger.createDefaultHandler();
|
|
48
|
+
const logHandler = (messages, context) => {
|
|
49
|
+
logBroadcaster.pushLog({
|
|
50
|
+
loggerName: context.name ?? 'unknown',
|
|
51
|
+
logLevel: context.level.name,
|
|
52
|
+
messages: messages.map((m) => String(m))
|
|
53
|
+
});
|
|
54
|
+
defaultHandler(messages, context);
|
|
55
|
+
};
|
|
56
|
+
baseLogger.useDefaults({
|
|
57
|
+
formatter: logHandler
|
|
58
|
+
});
|
|
59
|
+
const workerLogger = createLogger('db-worker');
|
|
15
60
|
const openWorkerConnection = async (options) => {
|
|
16
61
|
const connection = new WASqliteConnection(options);
|
|
17
62
|
return {
|
|
18
63
|
init: Comlink.proxy(() => connection.init()),
|
|
19
|
-
getConfig: Comlink.proxy(() =>
|
|
64
|
+
getConfig: Comlink.proxy(async () => {
|
|
65
|
+
// Can't send the logger over
|
|
66
|
+
const { logger, ...rest } = await connection.getConfig();
|
|
67
|
+
return rest;
|
|
68
|
+
}),
|
|
20
69
|
close: Comlink.proxy(() => connection.close()),
|
|
21
70
|
execute: Comlink.proxy(async (sql, params) => connection.execute(sql, params)),
|
|
22
71
|
executeRaw: Comlink.proxy(async (sql, params) => connection.executeRaw(sql, params)),
|
|
@@ -27,15 +76,34 @@ const openWorkerConnection = async (options) => {
|
|
|
27
76
|
})
|
|
28
77
|
};
|
|
29
78
|
};
|
|
30
|
-
const openDBShared = async (options) => {
|
|
79
|
+
const openDBShared = async (options, logHandler) => {
|
|
31
80
|
// Prevent multiple simultaneous opens from causing race conditions
|
|
32
81
|
return getNavigatorLocks().request(OPEN_DB_LOCK, async () => {
|
|
33
82
|
const clientId = nextClientId++;
|
|
34
83
|
const { dbFilename, logLevel } = options;
|
|
35
|
-
|
|
84
|
+
logToAll(`Worker opening DB connection for`, JSON.stringify(options));
|
|
85
|
+
// This updates the log level for the worker-level logger
|
|
86
|
+
// The DB connection logger will automatically track the main context logger
|
|
87
|
+
// since it passes logs to it.
|
|
88
|
+
workerLogger.setLevel(logLevel);
|
|
89
|
+
let disposeLogListener = logHandler
|
|
90
|
+
? logBroadcaster.registerListener({
|
|
91
|
+
onLog: (event) => {
|
|
92
|
+
if (event.loggerName !== dbFilename) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
logHandler(event);
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
: null;
|
|
36
99
|
if (!DBMap.has(dbFilename)) {
|
|
37
100
|
const clientIds = new Set();
|
|
38
|
-
const
|
|
101
|
+
const logger = createLogger(dbFilename);
|
|
102
|
+
logger.debug(`Worker opened a new connection`);
|
|
103
|
+
const connection = await openWorkerConnection({
|
|
104
|
+
...options,
|
|
105
|
+
logger
|
|
106
|
+
});
|
|
39
107
|
await connection.init();
|
|
40
108
|
DBMap.set(dbFilename, {
|
|
41
109
|
clientIds,
|
|
@@ -52,14 +120,15 @@ const openDBShared = async (options) => {
|
|
|
52
120
|
}),
|
|
53
121
|
close: Comlink.proxy(async () => {
|
|
54
122
|
const { clientIds } = dbEntry;
|
|
55
|
-
|
|
123
|
+
disposeLogListener?.();
|
|
124
|
+
workerLogger.debug(`Close requested from client ${clientId} of ${[...clientIds]}`);
|
|
56
125
|
clientIds.delete(clientId);
|
|
57
126
|
if (clientIds.size == 0) {
|
|
58
|
-
|
|
127
|
+
workerLogger.debug(`Closing connection to ${dbFilename}.`);
|
|
59
128
|
DBMap.delete(dbFilename);
|
|
60
129
|
return db.close?.();
|
|
61
130
|
}
|
|
62
|
-
|
|
131
|
+
workerLogger.debug(`Connection to ${dbFilename} not closed yet due to active clients.`);
|
|
63
132
|
return;
|
|
64
133
|
})
|
|
65
134
|
};
|
|
@@ -78,9 +147,3 @@ else {
|
|
|
78
147
|
// A dedicated worker can be shared externally
|
|
79
148
|
Comlink.expose(openDBShared);
|
|
80
149
|
}
|
|
81
|
-
addEventListener('unload', () => {
|
|
82
|
-
Array.from(DBMap.values()).forEach(async (dbConnection) => {
|
|
83
|
-
const { db } = dbConnection;
|
|
84
|
-
db.close?.();
|
|
85
|
-
});
|
|
86
|
-
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ILogger, type ILogLevel, type PowerSyncConnectionOptions, type StreamingSyncImplementation, type StreamingSyncImplementationListener, type SyncStatusOptions, BaseObserver, ConnectionManager, DBAdapter,
|
|
1
|
+
import { type ILogger, type ILogLevel, type PowerSyncConnectionOptions, type StreamingSyncImplementation, type StreamingSyncImplementationListener, type SyncStatusOptions, BaseObserver, ConnectionManager, DBAdapter, SyncStatus } from '@powersync/common';
|
|
2
2
|
import { Mutex } from 'async-mutex';
|
|
3
3
|
import * as Comlink from 'comlink';
|
|
4
4
|
import { WebStreamingSyncImplementation, WebStreamingSyncImplementationOptions } from '../../db/sync/WebStreamingSyncImplementation';
|
|
@@ -27,7 +27,7 @@ export type ManualSharedSyncPayload = {
|
|
|
27
27
|
* @internal
|
|
28
28
|
*/
|
|
29
29
|
export type SharedSyncInitOptions = {
|
|
30
|
-
streamOptions: Omit<WebStreamingSyncImplementationOptions, 'adapter' | 'uploadCrud' | 'remote'
|
|
30
|
+
streamOptions: Omit<WebStreamingSyncImplementationOptions, 'adapter' | 'uploadCrud' | 'remote'>;
|
|
31
31
|
dbParams: ResolvedWebSQLOpenOptions;
|
|
32
32
|
};
|
|
33
33
|
/**
|
|
@@ -43,7 +43,6 @@ export type WrappedSyncPort = {
|
|
|
43
43
|
port: MessagePort;
|
|
44
44
|
clientProvider: Comlink.Remote<AbstractSharedSyncClientProvider>;
|
|
45
45
|
db?: DBAdapter;
|
|
46
|
-
currentSubscriptions: SubscribedStream[];
|
|
47
46
|
};
|
|
48
47
|
/**
|
|
49
48
|
* @internal
|
|
@@ -56,7 +55,7 @@ export type RemoteOperationAbortController = {
|
|
|
56
55
|
* @internal
|
|
57
56
|
* Shared sync implementation which runs inside a shared webworker
|
|
58
57
|
*/
|
|
59
|
-
export declare class SharedSyncImplementation extends BaseObserver<SharedSyncImplementationListener> {
|
|
58
|
+
export declare class SharedSyncImplementation extends BaseObserver<SharedSyncImplementationListener> implements StreamingSyncImplementation {
|
|
60
59
|
protected ports: WrappedSyncPort[];
|
|
61
60
|
protected isInitialized: Promise<void>;
|
|
62
61
|
protected statusListener?: () => void;
|
|
@@ -67,7 +66,6 @@ export declare class SharedSyncImplementation extends BaseObserver<SharedSyncImp
|
|
|
67
66
|
protected logger: ILogger;
|
|
68
67
|
protected lastConnectOptions: PowerSyncConnectionOptions | undefined;
|
|
69
68
|
protected portMutex: Mutex;
|
|
70
|
-
private subscriptions;
|
|
71
69
|
protected connectionManager: ConnectionManager;
|
|
72
70
|
syncStatus: SyncStatus;
|
|
73
71
|
broadCastLogger: ILogger;
|
|
@@ -77,8 +75,6 @@ export declare class SharedSyncImplementation extends BaseObserver<SharedSyncImp
|
|
|
77
75
|
waitForStatus(status: SyncStatusOptions): Promise<void>;
|
|
78
76
|
waitUntilStatusMatches(predicate: (status: SyncStatus) => boolean): Promise<void>;
|
|
79
77
|
waitForReady(): Promise<void>;
|
|
80
|
-
private collectActiveSubscriptions;
|
|
81
|
-
updateSubscriptions(port: WrappedSyncPort, subscriptions: SubscribedStream[]): void;
|
|
82
78
|
setLogLevel(level: ILogLevel): void;
|
|
83
79
|
/**
|
|
84
80
|
* Configures the DBAdapter connection and a streaming sync client.
|
|
@@ -96,16 +92,12 @@ export declare class SharedSyncImplementation extends BaseObserver<SharedSyncImp
|
|
|
96
92
|
/**
|
|
97
93
|
* Adds a new client tab's message port to the list of connected ports
|
|
98
94
|
*/
|
|
99
|
-
addPort(port: MessagePort): Promise<
|
|
100
|
-
port: MessagePort;
|
|
101
|
-
clientProvider: Comlink.Remote<AbstractSharedSyncClientProvider>;
|
|
102
|
-
currentSubscriptions: never[];
|
|
103
|
-
}>;
|
|
95
|
+
addPort(port: MessagePort): Promise<void>;
|
|
104
96
|
/**
|
|
105
97
|
* Removes a message port client from this manager's managed
|
|
106
98
|
* clients.
|
|
107
99
|
*/
|
|
108
|
-
removePort(port:
|
|
100
|
+
removePort(port: MessagePort): Promise<() => void>;
|
|
109
101
|
triggerCrudUpload(): void;
|
|
110
102
|
hasCompletedSync(): Promise<boolean>;
|
|
111
103
|
getWriteCheckpoint(): Promise<string>;
|
|
@@ -121,5 +113,5 @@ export declare class SharedSyncImplementation extends BaseObserver<SharedSyncImp
|
|
|
121
113
|
* A function only used for unit tests which updates the internal
|
|
122
114
|
* sync stream client and all tab client's sync status
|
|
123
115
|
*/
|
|
124
|
-
_testUpdateAllStatuses
|
|
116
|
+
private _testUpdateAllStatuses;
|
|
125
117
|
}
|
|
@@ -40,7 +40,6 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
40
40
|
logger;
|
|
41
41
|
lastConnectOptions;
|
|
42
42
|
portMutex;
|
|
43
|
-
subscriptions = [];
|
|
44
43
|
connectionManager;
|
|
45
44
|
syncStatus;
|
|
46
45
|
broadCastLogger;
|
|
@@ -103,23 +102,6 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
103
102
|
async waitForReady() {
|
|
104
103
|
return this.isInitialized;
|
|
105
104
|
}
|
|
106
|
-
collectActiveSubscriptions() {
|
|
107
|
-
this.logger.debug('Collecting active stream subscriptions across tabs');
|
|
108
|
-
const active = new Map();
|
|
109
|
-
for (const port of this.ports) {
|
|
110
|
-
for (const stream of port.currentSubscriptions) {
|
|
111
|
-
const serializedKey = JSON.stringify(stream);
|
|
112
|
-
active.set(serializedKey, stream);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
this.subscriptions = [...active.values()];
|
|
116
|
-
this.logger.debug('Collected stream subscriptions', this.subscriptions);
|
|
117
|
-
this.connectionManager.syncStreamImplementation?.updateSubscriptions(this.subscriptions);
|
|
118
|
-
}
|
|
119
|
-
updateSubscriptions(port, subscriptions) {
|
|
120
|
-
port.currentSubscriptions = subscriptions;
|
|
121
|
-
this.collectActiveSubscriptions();
|
|
122
|
-
}
|
|
123
105
|
setLogLevel(level) {
|
|
124
106
|
this.logger.setLevel(level);
|
|
125
107
|
this.broadCastLogger.setLevel(level);
|
|
@@ -129,7 +111,6 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
129
111
|
*/
|
|
130
112
|
async setParams(params) {
|
|
131
113
|
await this.portMutex.runExclusive(async () => {
|
|
132
|
-
this.collectActiveSubscriptions();
|
|
133
114
|
if (this.syncParams) {
|
|
134
115
|
// Cannot modify already existing sync implementation params
|
|
135
116
|
// But we can ask for a DB adapter, if required, at this point.
|
|
@@ -175,11 +156,10 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
175
156
|
* Adds a new client tab's message port to the list of connected ports
|
|
176
157
|
*/
|
|
177
158
|
async addPort(port) {
|
|
178
|
-
|
|
159
|
+
await this.portMutex.runExclusive(() => {
|
|
179
160
|
const portProvider = {
|
|
180
161
|
port,
|
|
181
|
-
clientProvider: Comlink.wrap(port)
|
|
182
|
-
currentSubscriptions: []
|
|
162
|
+
clientProvider: Comlink.wrap(port)
|
|
183
163
|
};
|
|
184
164
|
this.ports.push(portProvider);
|
|
185
165
|
// Give the newly connected client the latest status
|
|
@@ -187,7 +167,6 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
187
167
|
if (status) {
|
|
188
168
|
portProvider.clientProvider.statusChanged(status.toJSON());
|
|
189
169
|
}
|
|
190
|
-
return portProvider;
|
|
191
170
|
});
|
|
192
171
|
}
|
|
193
172
|
/**
|
|
@@ -199,7 +178,7 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
199
178
|
// Warns if the port is not found. This should not happen in practice.
|
|
200
179
|
// We return early if the port is not found.
|
|
201
180
|
const { trackedPort, shouldReconnect } = await this.portMutex.runExclusive(async () => {
|
|
202
|
-
const index = this.ports.findIndex((p) => p == port);
|
|
181
|
+
const index = this.ports.findIndex((p) => p.port == port);
|
|
203
182
|
if (index < 0) {
|
|
204
183
|
this.logger.warn(`Could not remove port ${port} since it is not present in active ports.`);
|
|
205
184
|
return {};
|
|
@@ -212,7 +191,7 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
212
191
|
* not resolve. Abort them here.
|
|
213
192
|
*/
|
|
214
193
|
[this.fetchCredentialsController, this.uploadDataController].forEach((abortController) => {
|
|
215
|
-
if (abortController?.activePort == port) {
|
|
194
|
+
if (abortController?.activePort.port == port) {
|
|
216
195
|
abortController.controller.abort(new AbortOperation('Closing pending requests after client port is removed'));
|
|
217
196
|
}
|
|
218
197
|
});
|
|
@@ -239,8 +218,6 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
239
218
|
if (trackedPort.db) {
|
|
240
219
|
await trackedPort.db.close();
|
|
241
220
|
}
|
|
242
|
-
// Re-index subscriptions, the subscriptions of the removed port would no longer be considered.
|
|
243
|
-
this.collectActiveSubscriptions();
|
|
244
221
|
// Release proxy
|
|
245
222
|
return () => trackedPort.clientProvider[Comlink.releaseProxy]();
|
|
246
223
|
}
|
|
@@ -335,7 +312,6 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
335
312
|
});
|
|
336
313
|
},
|
|
337
314
|
...syncParams.streamOptions,
|
|
338
|
-
subscriptions: this.subscriptions,
|
|
339
315
|
// Logger cannot be transferred just yet
|
|
340
316
|
logger: this.logger
|
|
341
317
|
});
|
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
import { createBaseLogger } from '@powersync/common';
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
2
|
+
import * as Comlink from 'comlink';
|
|
3
|
+
import { SharedSyncClientEvent, SharedSyncImplementation } from './SharedSyncImplementation';
|
|
4
4
|
const _self = self;
|
|
5
5
|
const logger = createBaseLogger();
|
|
6
6
|
logger.useDefaults();
|
|
7
7
|
const sharedSyncImplementation = new SharedSyncImplementation();
|
|
8
8
|
_self.onconnect = async function (event) {
|
|
9
9
|
const port = event.ports[0];
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Adds an extra listener which can remove this port
|
|
12
|
+
* from the list of monitored ports.
|
|
13
|
+
*/
|
|
14
|
+
port.addEventListener('message', async (event) => {
|
|
15
|
+
const payload = event.data;
|
|
16
|
+
if (payload?.event == SharedSyncClientEvent.CLOSE_CLIENT) {
|
|
17
|
+
const release = await sharedSyncImplementation.removePort(port);
|
|
18
|
+
port.postMessage({
|
|
19
|
+
event: SharedSyncClientEvent.CLOSE_ACK,
|
|
20
|
+
data: {}
|
|
21
|
+
});
|
|
22
|
+
release?.();
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
await sharedSyncImplementation.addPort(port);
|
|
26
|
+
Comlink.expose(sharedSyncImplementation, port);
|
|
11
27
|
};
|