@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.
Files changed (49) hide show
  1. package/dist/2dcb8a60ed4b341d3046.wasm +0 -0
  2. package/dist/50eaffc7435d7a77ffb5.wasm +0 -0
  3. package/dist/88ca2d3820062df8ea26.wasm +0 -0
  4. package/dist/efe40e50208db1807722.wasm +0 -0
  5. package/dist/index.umd.js +103 -72
  6. package/dist/index.umd.js.map +1 -1
  7. package/dist/worker/SharedSyncImplementation.umd.js +112 -221
  8. package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
  9. package/dist/worker/WASQLiteDB.umd.js +232 -121
  10. package/dist/worker/WASQLiteDB.umd.js.map +1 -1
  11. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite-async_mjs.umd.js +15 -3
  12. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite-async_mjs.umd.js.map +1 -1
  13. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite_mjs.umd.js +15 -3
  14. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite_mjs.umd.js.map +1 -1
  15. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite-async_mjs.umd.js +15 -3
  16. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite-async_mjs.umd.js.map +1 -1
  17. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js +15 -3
  18. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js.map +1 -1
  19. package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js.umd.js +46 -218
  20. package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js.umd.js.map +1 -1
  21. package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js.umd.js +46 -218
  22. package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js.umd.js.map +1 -1
  23. package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_OPFSCoopSyncVFS_js.umd.js +46 -218
  24. package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_OPFSCoopSyncVFS_js.umd.js.map +1 -1
  25. package/lib/package.json +4 -4
  26. package/lib/src/db/PowerSyncDatabase.js +3 -1
  27. package/lib/src/db/adapters/AsyncDatabaseConnection.d.ts +13 -1
  28. package/lib/src/db/adapters/wa-sqlite/WASQLiteConnection.d.ts +3 -1
  29. package/lib/src/db/adapters/wa-sqlite/WASQLiteConnection.js +28 -2
  30. package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.d.ts +1 -1
  31. package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.js +6 -2
  32. package/lib/src/db/sync/SSRWebStreamingSyncImplementation.d.ts +0 -4
  33. package/lib/src/db/sync/SSRWebStreamingSyncImplementation.js +0 -4
  34. package/lib/src/db/sync/SharedWebStreamingSyncImplementation.d.ts +3 -8
  35. package/lib/src/db/sync/SharedWebStreamingSyncImplementation.js +2 -22
  36. package/lib/src/worker/db/LogHandler.d.ts +12 -0
  37. package/lib/src/worker/db/LogHandler.js +6 -0
  38. package/lib/src/worker/db/WASQLiteDB.worker.js +80 -17
  39. package/lib/src/worker/sync/SharedSyncImplementation.d.ts +6 -14
  40. package/lib/src/worker/sync/SharedSyncImplementation.js +4 -28
  41. package/lib/src/worker/sync/SharedSyncImplementation.worker.js +19 -3
  42. package/lib/tsconfig.tsbuildinfo +1 -1
  43. package/package.json +5 -5
  44. package/dist/31ba59416bad61e8fb1f.wasm +0 -0
  45. package/dist/d0a1e43030b814ed322f.wasm +0 -0
  46. package/dist/f4ad8bfeb6e6e5326142.wasm +0 -0
  47. package/dist/fbde47713220d7baec73.wasm +0 -0
  48. package/lib/src/worker/sync/WorkerClient.d.ts +0 -32
  49. 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: await AccessHandlePoolVFS.create(options.dbFileName, module)
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) {
@@ -41,8 +41,4 @@ export declare class SSRStreamingSyncImplementation extends BaseObserver impleme
41
41
  * This is a no-op in SSR mode.
42
42
  */
43
43
  triggerCrudUpload(): void;
44
- /**
45
- * No-op in SSR mode.
46
- */
47
- updateSubscriptions(): void;
48
44
  }
@@ -58,8 +58,4 @@ export class SSRStreamingSyncImplementation extends BaseObserver {
58
58
  * This is a no-op in SSR mode.
59
59
  */
60
60
  triggerCrudUpload() { }
61
- /**
62
- * No-op in SSR mode.
63
- */
64
- updateSubscriptions() { }
65
61
  }
@@ -1,9 +1,9 @@
1
- import { PowerSyncConnectionOptions, PowerSyncCredentials, SubscribedStream, SyncStatusOptions } from '@powersync/common';
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<WorkerClient>;
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
- }, options.subscriptions);
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._testUpdateAllStatuses(status.toJSON());
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
+ }
@@ -0,0 +1,6 @@
1
+ import { BaseObserver } from '@powersync/common';
2
+ export class LogHandler extends BaseObserver {
3
+ pushLog(entry) {
4
+ this.iterateListeners((l) => l.onLog?.(entry));
5
+ }
6
+ }
@@ -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
- const baseLogger = createBaseLogger();
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(() => connection.getConfig()),
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
- logger.setLevel(logLevel);
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 connection = await openWorkerConnection(options);
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
- logger.debug(`Close requested from client ${clientId} of ${[...clientIds]}`);
123
+ disposeLogListener?.();
124
+ workerLogger.debug(`Close requested from client ${clientId} of ${[...clientIds]}`);
56
125
  clientIds.delete(clientId);
57
126
  if (clientIds.size == 0) {
58
- logger.debug(`Closing connection to ${dbFilename}.`);
127
+ workerLogger.debug(`Closing connection to ${dbFilename}.`);
59
128
  DBMap.delete(dbFilename);
60
129
  return db.close?.();
61
130
  }
62
- logger.debug(`Connection to ${dbFilename} not closed yet due to active clients.`);
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, SubscribedStream, SyncStatus } from '@powersync/common';
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' | 'subscriptions'>;
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: WrappedSyncPort): Promise<() => void>;
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(status: SyncStatusOptions): Promise<void>;
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
- return await this.portMutex.runExclusive(() => {
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 { SharedSyncImplementation } from './SharedSyncImplementation';
3
- import { WorkerClient } from './WorkerClient';
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
- await new WorkerClient(sharedSyncImplementation, port).initialize();
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
  };