@powersync/web 0.0.0-dev-20260311103504 → 0.0.0-dev-20260503073249

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 (121) hide show
  1. package/dist/2075a31bb151adbb9767.wasm +0 -0
  2. package/dist/3322bc84de986b63c2cd.wasm +0 -0
  3. package/dist/8e97452e297be23b5e50.wasm +0 -0
  4. package/dist/fbc178b70d530e8ce02b.wasm +0 -0
  5. package/dist/index.umd.js +5341 -1279
  6. package/dist/index.umd.js.map +1 -1
  7. package/dist/worker/SharedSyncImplementation.umd.js +1113 -3526
  8. package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
  9. package/dist/worker/WASQLiteDB.umd.js +1397 -1332
  10. package/dist/worker/WASQLiteDB.umd.js.map +1 -1
  11. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-9af0a7.umd.js +31 -0
  12. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-9af0a7.umd.js.map +1 -0
  13. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-bbf5a9.umd.js +31 -0
  14. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-bbf5a9.umd.js.map +1 -0
  15. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_dist_wa-sqli-c26e0f.umd.js +31 -0
  16. package/dist/worker/{node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_dist_wa-sqli-cc5fcc.umd.js.map → node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_dist_wa-sqli-c26e0f.umd.js.map} +1 -1
  17. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js +31 -0
  18. package/dist/worker/{node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js.map → node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js.map} +1 -1
  19. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_src_examples-2fb422.umd.js +3562 -0
  20. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_src_examples-2fb422.umd.js.map +1 -0
  21. package/dist/worker/{node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_src_examples-0df390.umd.js → node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_src_examples-96fb23.umd.js} +16 -16
  22. package/dist/worker/{node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_src_examples-0df390.umd.js.map → node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_src_examples-96fb23.umd.js.map} +1 -1
  23. package/dist/worker/{node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_src_examples-151024.umd.js → node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_src_examples-c89911.umd.js} +12 -12
  24. package/dist/worker/{node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_src_examples-151024.umd.js.map → node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_src_examples-c89911.umd.js.map} +1 -1
  25. package/dist/worker/{node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_src_examples-c01ef0.umd.js → node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_src_examples-ec4eb1.umd.js} +14 -14
  26. package/dist/worker/{node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_src_examples-c01ef0.umd.js.map → node_modules_pnpm_journeyapps_wa-sqlite_1_7_0_node_modules_journeyapps_wa-sqlite_src_examples-ec4eb1.umd.js.map} +1 -1
  27. package/lib/package.json +4 -5
  28. package/lib/src/db/PowerSyncDatabase.d.ts +2 -3
  29. package/lib/src/db/PowerSyncDatabase.js +3 -12
  30. package/lib/src/db/adapters/AsyncWebAdapter.d.ts +50 -0
  31. package/lib/src/db/adapters/AsyncWebAdapter.js +163 -0
  32. package/lib/src/db/adapters/SSRDBAdapter.d.ts +1 -2
  33. package/lib/src/db/adapters/SSRDBAdapter.js +5 -6
  34. package/lib/src/db/adapters/wa-sqlite/ConcurrentConnection.d.ts +56 -0
  35. package/lib/src/db/adapters/wa-sqlite/ConcurrentConnection.js +121 -0
  36. package/lib/src/db/adapters/wa-sqlite/DatabaseClient.d.ts +54 -0
  37. package/lib/src/db/adapters/wa-sqlite/DatabaseClient.js +227 -0
  38. package/lib/src/db/adapters/wa-sqlite/DatabaseServer.d.ts +47 -0
  39. package/lib/src/db/adapters/wa-sqlite/DatabaseServer.js +145 -0
  40. package/lib/src/db/adapters/wa-sqlite/RawSqliteConnection.d.ts +46 -0
  41. package/lib/src/db/adapters/wa-sqlite/RawSqliteConnection.js +147 -0
  42. package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.d.ts +28 -6
  43. package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.js +104 -55
  44. package/lib/src/db/adapters/wa-sqlite/vfs.d.ts +50 -0
  45. package/lib/src/db/adapters/wa-sqlite/vfs.js +76 -0
  46. package/lib/src/db/adapters/web-sql-flags.d.ts +5 -0
  47. package/lib/src/db/sync/SSRWebStreamingSyncImplementation.d.ts +5 -2
  48. package/lib/src/db/sync/SSRWebStreamingSyncImplementation.js +6 -3
  49. package/lib/src/db/sync/SharedWebStreamingSyncImplementation.js +4 -19
  50. package/lib/src/index.d.ts +1 -4
  51. package/lib/src/index.js +1 -4
  52. package/lib/src/shared/tab_close_signal.d.ts +11 -0
  53. package/lib/src/shared/tab_close_signal.js +26 -0
  54. package/lib/src/worker/db/MultiDatabaseServer.d.ts +17 -0
  55. package/lib/src/worker/db/MultiDatabaseServer.js +89 -0
  56. package/lib/src/worker/db/WASQLiteDB.worker.js +9 -48
  57. package/lib/src/worker/db/open-worker-database.d.ts +3 -3
  58. package/lib/src/worker/db/open-worker-database.js +2 -2
  59. package/lib/src/worker/sync/SharedSyncImplementation.d.ts +5 -6
  60. package/lib/src/worker/sync/SharedSyncImplementation.js +88 -54
  61. package/lib/tsconfig.tsbuildinfo +1 -1
  62. package/package.json +5 -6
  63. package/src/db/PowerSyncDatabase.ts +4 -12
  64. package/src/db/adapters/AsyncWebAdapter.ts +207 -0
  65. package/src/db/adapters/SSRDBAdapter.ts +7 -7
  66. package/src/db/adapters/wa-sqlite/ConcurrentConnection.ts +137 -0
  67. package/src/db/adapters/wa-sqlite/DatabaseClient.ts +325 -0
  68. package/src/db/adapters/wa-sqlite/DatabaseServer.ts +203 -0
  69. package/src/db/adapters/wa-sqlite/RawSqliteConnection.ts +194 -0
  70. package/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts +152 -63
  71. package/src/db/adapters/wa-sqlite/vfs.ts +96 -0
  72. package/src/db/adapters/web-sql-flags.ts +6 -0
  73. package/src/db/sync/SSRWebStreamingSyncImplementation.ts +7 -3
  74. package/src/db/sync/SharedWebStreamingSyncImplementation.ts +4 -20
  75. package/src/index.ts +1 -4
  76. package/src/shared/tab_close_signal.ts +28 -0
  77. package/src/worker/db/MultiDatabaseServer.ts +107 -0
  78. package/src/worker/db/WASQLiteDB.worker.ts +10 -57
  79. package/src/worker/db/open-worker-database.ts +4 -4
  80. package/src/worker/sync/SharedSyncImplementation.ts +114 -58
  81. package/dist/26d61ca9f5694d064635.wasm +0 -0
  82. package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapp-89f0ba.index.umd.js +0 -1878
  83. package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapp-89f0ba.index.umd.js.map +0 -1
  84. package/dist/_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapps_wa-sqlite_src_example-97ebe9.index.umd.js +0 -555
  85. package/dist/_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapps_wa-sqlite_src_example-97ebe9.index.umd.js.map +0 -1
  86. package/dist/b4c6283dc473b6b3fd24.wasm +0 -0
  87. package/dist/c78985091a0b22aaef03.wasm +0 -0
  88. package/dist/ca59e199e1138b553fad.wasm +0 -0
  89. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-b9c070.umd.js +0 -31
  90. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-b9c070.umd.js.map +0 -1
  91. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-c99c07.umd.js +0 -31
  92. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-c99c07.umd.js.map +0 -1
  93. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_dist_wa-sqli-cc5fcc.umd.js +0 -31
  94. package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_5_0_node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js +0 -31
  95. package/lib/src/db/adapters/AbstractWebSQLOpenFactory.d.ts +0 -17
  96. package/lib/src/db/adapters/AbstractWebSQLOpenFactory.js +0 -33
  97. package/lib/src/db/adapters/AsyncDatabaseConnection.d.ts +0 -49
  98. package/lib/src/db/adapters/AsyncDatabaseConnection.js +0 -1
  99. package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.d.ts +0 -109
  100. package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.js +0 -401
  101. package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.d.ts +0 -59
  102. package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.js +0 -147
  103. package/lib/src/db/adapters/wa-sqlite/InternalWASQLiteDBAdapter.d.ts +0 -12
  104. package/lib/src/db/adapters/wa-sqlite/InternalWASQLiteDBAdapter.js +0 -19
  105. package/lib/src/db/adapters/wa-sqlite/WASQLiteConnection.d.ts +0 -155
  106. package/lib/src/db/adapters/wa-sqlite/WASQLiteConnection.js +0 -401
  107. package/lib/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.d.ts +0 -32
  108. package/lib/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.js +0 -49
  109. package/lib/src/worker/db/SharedWASQLiteConnection.d.ts +0 -42
  110. package/lib/src/worker/db/SharedWASQLiteConnection.js +0 -90
  111. package/lib/src/worker/db/WorkerWASQLiteConnection.d.ts +0 -9
  112. package/lib/src/worker/db/WorkerWASQLiteConnection.js +0 -12
  113. package/src/db/adapters/AbstractWebSQLOpenFactory.ts +0 -48
  114. package/src/db/adapters/AsyncDatabaseConnection.ts +0 -55
  115. package/src/db/adapters/LockedAsyncDatabaseAdapter.ts +0 -490
  116. package/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.ts +0 -201
  117. package/src/db/adapters/wa-sqlite/InternalWASQLiteDBAdapter.ts +0 -23
  118. package/src/db/adapters/wa-sqlite/WASQLiteConnection.ts +0 -497
  119. package/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.ts +0 -86
  120. package/src/worker/db/SharedWASQLiteConnection.ts +0 -131
  121. package/src/worker/db/WorkerWASQLiteConnection.ts +0 -14
@@ -4,66 +4,27 @@
4
4
  import '@journeyapps/wa-sqlite';
5
5
  import { createBaseLogger, createLogger } from '@powersync/common';
6
6
  import * as Comlink from 'comlink';
7
- import { getNavigatorLocks } from '../../shared/navigator.js';
8
- import { SharedWASQLiteConnection } from './SharedWASQLiteConnection.js';
9
- import { WorkerWASQLiteConnection } from './WorkerWASQLiteConnection.js';
7
+ import { isSharedWorker, MultiDatabaseServer } from './MultiDatabaseServer.js';
10
8
  const baseLogger = createBaseLogger();
11
9
  baseLogger.useDefaults();
12
10
  const logger = createLogger('db-worker');
13
- const DBMap = new Map();
14
- const OPEN_DB_LOCK = 'open-wasqlite-db';
15
- let nextClientId = 1;
16
- const openDBShared = async (options) => {
17
- // Prevent multiple simultaneous opens from causing race conditions
18
- return getNavigatorLocks().request(OPEN_DB_LOCK, async () => {
19
- const clientId = nextClientId++;
20
- const { dbFilename, logLevel } = options;
21
- logger.setLevel(logLevel);
22
- if (!DBMap.has(dbFilename)) {
23
- const clientIds = new Set();
24
- // This format returns proxy objects for function callbacks
25
- const connection = new WorkerWASQLiteConnection(options);
26
- await connection.init();
27
- connection.registerListener({
28
- holdOverwritten: async () => {
29
- /**
30
- * The previous hold has been overwritten, without being released.
31
- * we need to cleanup any resources associated with it.
32
- * We can perform a rollback to release any potential transactions that were started.
33
- */
34
- await connection.execute('ROLLBACK').catch(() => { });
35
- }
36
- });
37
- DBMap.set(dbFilename, {
38
- clientIds,
39
- db: connection
40
- });
41
- }
42
- // Associates this clientId with the shared connection entry
43
- const sharedConnection = new SharedWASQLiteConnection({
44
- dbMap: DBMap,
45
- dbFilename,
46
- clientId,
47
- logger
48
- });
49
- return Comlink.proxy(sharedConnection);
50
- });
11
+ const server = new MultiDatabaseServer(logger);
12
+ const exposedFunctions = {
13
+ connect: (config) => server.handleConnection(config),
14
+ connectToExisting: ({ identifier, lockName }) => server.connectToExisting(identifier, lockName)
51
15
  };
52
16
  // Check if we're in a SharedWorker context
53
- if (typeof SharedWorkerGlobalScope !== 'undefined') {
17
+ if (isSharedWorker) {
54
18
  const _self = self;
55
19
  _self.onconnect = function (event) {
56
20
  const port = event.ports[0];
57
- Comlink.expose(openDBShared, port);
21
+ Comlink.expose(exposedFunctions, port);
58
22
  };
59
23
  }
60
24
  else {
61
25
  // A dedicated worker can be shared externally
62
- Comlink.expose(openDBShared);
26
+ Comlink.expose(exposedFunctions);
63
27
  }
64
28
  addEventListener('unload', () => {
65
- Array.from(DBMap.values()).forEach(async (dbConnection) => {
66
- const { db } = dbConnection;
67
- db.close?.();
68
- });
29
+ server.closeAll();
69
30
  });
@@ -1,6 +1,6 @@
1
1
  import * as Comlink from 'comlink';
2
- import { OpenAsyncDatabaseConnection } from '../../db/adapters/AsyncDatabaseConnection.js';
3
- import { WASQLiteVFS } from '../../db/adapters/wa-sqlite/WASQLiteConnection.js';
2
+ import { WASQLiteVFS } from '../../db/adapters/wa-sqlite/vfs.js';
3
+ import { OpenWorkerConnection } from '../../db/adapters/wa-sqlite/DatabaseClient.js';
4
4
  /**
5
5
  * Opens a shared or dedicated worker which exposes opening of database connections
6
6
  */
@@ -9,6 +9,6 @@ export declare function openWorkerDatabasePort(workerIdentifier: string, multipl
9
9
  * @returns A function which allows for opening database connections inside
10
10
  * a worker.
11
11
  */
12
- export declare function getWorkerDatabaseOpener(workerIdentifier: string, multipleTabs?: boolean, worker?: string | URL): Comlink.Remote<OpenAsyncDatabaseConnection>;
12
+ export declare function getWorkerDatabaseOpener(workerIdentifier: string, multipleTabs?: boolean, worker?: string | URL): Comlink.Remote<OpenWorkerConnection>;
13
13
  export declare function resolveWorkerDatabasePortFactory(worker: () => Worker | SharedWorker): Worker | MessagePort;
14
14
  export declare function isSharedWorker(worker: Worker | SharedWorker): worker is SharedWorker;
@@ -1,10 +1,10 @@
1
1
  import * as Comlink from 'comlink';
2
- import { WASQLiteVFS } from '../../db/adapters/wa-sqlite/WASQLiteConnection.js';
2
+ import { vfsRequiresDedicatedWorkers } from '../../db/adapters/wa-sqlite/vfs.js';
3
3
  /**
4
4
  * Opens a shared or dedicated worker which exposes opening of database connections
5
5
  */
6
6
  export function openWorkerDatabasePort(workerIdentifier, multipleTabs = true, worker = '', vfs) {
7
- const needsDedicated = vfs == WASQLiteVFS.AccessHandlePoolVFS || vfs == WASQLiteVFS.OPFSCoopSyncVFS;
7
+ const needsDedicated = vfs && vfsRequiresDedicatedWorkers(vfs);
8
8
  if (worker) {
9
9
  return !needsDedicated && multipleTabs
10
10
  ? new SharedWorker(`${worker}`, {
@@ -1,8 +1,6 @@
1
- import { BaseObserver, ConnectionManager, DBAdapter, SubscribedStream, SyncStatus, type ILogLevel, type ILogger, type PowerSyncConnectionOptions, type StreamingSyncImplementation, type StreamingSyncImplementationListener, type SyncStatusOptions } from '@powersync/common';
2
- import { Mutex } from 'async-mutex';
1
+ import { BaseObserver, ConnectionManager, DBAdapter, SubscribedStream, SyncStatus, Mutex, type ILogLevel, type ILogger, type PowerSyncConnectionOptions, type StreamingSyncImplementation, type StreamingSyncImplementationListener, type SyncStatusOptions } from '@powersync/common';
3
2
  import * as Comlink from 'comlink';
4
3
  import { WebStreamingSyncImplementation, WebStreamingSyncImplementationOptions } from '../../db/sync/WebStreamingSyncImplementation.js';
5
- import { WorkerWrappedAsyncDatabaseConnection } from '../../db/adapters/WorkerWrappedAsyncDatabaseConnection.js';
6
4
  import { ResolvedWebSQLOpenOptions } from '../../db/adapters/web-sql-flags.js';
7
5
  import { AbstractSharedSyncClientProvider } from './AbstractSharedSyncClientProvider.js';
8
6
  /**
@@ -73,7 +71,7 @@ export declare class SharedSyncImplementation extends BaseObserver<SharedSyncImp
73
71
  protected connectionManager: ConnectionManager;
74
72
  syncStatus: SyncStatus;
75
73
  broadCastLogger: ILogger;
76
- protected distributedDB: DBAdapter | null;
74
+ protected readonly database: DBAdapter;
77
75
  constructor();
78
76
  get lastSyncedAt(): Date | undefined;
79
77
  get isConnected(): boolean;
@@ -126,9 +124,10 @@ export declare class SharedSyncImplementation extends BaseObserver<SharedSyncImp
126
124
  protected withSyncImplementation<T>(callback: (sync: StreamingSyncImplementation) => Promise<T>): Promise<T>;
127
125
  protected generateStreamingImplementation(): WebStreamingSyncImplementation;
128
126
  /**
129
- * Opens a worker wrapped database connection. Using the last connected client port.
127
+ * Requests a random client to share its database connection with us.
130
128
  */
131
- protected openInternalDB(): Promise<WorkerWrappedAsyncDatabaseConnection<ResolvedWebSQLOpenOptions>>;
129
+ private openInternalDB;
130
+ private generateReconnectableDatabase;
132
131
  /**
133
132
  * A method to update the all shared statuses for each
134
133
  * client.
@@ -1,11 +1,10 @@
1
- import { AbortOperation, BaseObserver, ConnectionManager, SqliteBucketStorage, SyncStatus, createLogger } from '@powersync/common';
2
- import { Mutex } from 'async-mutex';
1
+ import { AbortOperation, BaseObserver, ConnectionManager, DBAdapterDefaultMixin, SqliteBucketStorage, SyncStatus, createLogger, Mutex } from '@powersync/common';
3
2
  import * as Comlink from 'comlink';
4
3
  import { WebRemote } from '../../db/sync/WebRemote.js';
5
4
  import { WebStreamingSyncImplementation } from '../../db/sync/WebStreamingSyncImplementation.js';
6
- import { LockedAsyncDatabaseAdapter } from '../../db/adapters/LockedAsyncDatabaseAdapter.js';
7
- import { WorkerWrappedAsyncDatabaseConnection } from '../../db/adapters/WorkerWrappedAsyncDatabaseConnection.js';
8
5
  import { BroadcastLogger } from './BroadcastLogger.js';
6
+ import { DatabaseClient } from '../../db/adapters/wa-sqlite/DatabaseClient.js';
7
+ import { generateTabCloseSignal } from '../../shared/tab_close_signal.js';
9
8
  /**
10
9
  * @internal
11
10
  * Manual message events for shared sync clients
@@ -43,7 +42,7 @@ export class SharedSyncImplementation extends BaseObserver {
43
42
  connectionManager;
44
43
  syncStatus;
45
44
  broadCastLogger;
46
- distributedDB;
45
+ database = this.generateReconnectableDatabase();
47
46
  constructor() {
48
47
  super();
49
48
  this.ports = [];
@@ -59,8 +58,6 @@ export class SharedSyncImplementation extends BaseObserver {
59
58
  }
60
59
  });
61
60
  });
62
- // Should be configured once we get params
63
- this.distributedDB = null;
64
61
  this.syncStatus = new SyncStatus({});
65
62
  this.broadCastLogger = new BroadcastLogger(this.ports);
66
63
  this.connectionManager = new ConnectionManager({
@@ -160,38 +157,8 @@ export class SharedSyncImplementation extends BaseObserver {
160
157
  if (params.streamOptions?.flags?.broadcastLogs) {
161
158
  this.logger = this.broadCastLogger;
162
159
  }
163
- const lockedAdapter = new LockedAsyncDatabaseAdapter({
164
- name: params.dbParams.dbFilename,
165
- openConnection: async () => {
166
- // Gets a connection from the clients when a new connection is requested.
167
- const db = await this.openInternalDB();
168
- db.registerListener({
169
- closing: () => {
170
- lockedAdapter.reOpenInternalDB();
171
- }
172
- });
173
- return db;
174
- },
175
- logger: this.logger,
176
- reOpenOnConnectionClosed: true
177
- });
178
- this.distributedDB = lockedAdapter;
179
- await lockedAdapter.init();
180
- lockedAdapter.registerListener({
181
- databaseReOpened: () => {
182
- // We may have missed some table updates while the database was closed.
183
- // We can poke the crud in case we missed any updates.
184
- this.connectionManager.syncStreamImplementation?.triggerCrudUpload();
185
- /**
186
- * FIXME or IMPROVE ME
187
- * The Rust client implementation stores sync state on the connection level.
188
- * Reopening the database causes a state machine error which should cause the
189
- * StreamingSyncImplementation to reconnect. It would be nicer if we could trigger
190
- * this reconnect earlier.
191
- * This reconnect is not required for IndexedDB.
192
- */
193
- }
194
- });
160
+ // Ensure we have a usable database connection, the reconnectable database will connect lazily on first use.
161
+ await this.database.readLock(async () => { });
195
162
  self.onerror = (event) => {
196
163
  // Share any uncaught events on the broadcast logger
197
164
  this.logger.error('Uncaught exception in PowerSync shared sync worker', event);
@@ -308,7 +275,7 @@ export class SharedSyncImplementation extends BaseObserver {
308
275
  const syncParams = this.syncParams;
309
276
  // Create a new StreamingSyncImplementation for each connect call. This is usually done is all SDKs.
310
277
  return new WebStreamingSyncImplementation({
311
- adapter: new SqliteBucketStorage(this.distributedDB, this.logger),
278
+ adapter: new SqliteBucketStorage(this.database, this.logger),
312
279
  remote: new WebRemote({
313
280
  invalidateCredentials: async () => {
314
281
  const lastPort = await this.getLastWrappedPort();
@@ -379,9 +346,9 @@ export class SharedSyncImplementation extends BaseObserver {
379
346
  });
380
347
  }
381
348
  /**
382
- * Opens a worker wrapped database connection. Using the last connected client port.
349
+ * Requests a random client to share its database connection with us.
383
350
  */
384
- async openInternalDB() {
351
+ async openInternalDB(handleClosed) {
385
352
  const client = await this.getRandomWrappedPort();
386
353
  if (!client) {
387
354
  // Should not really happen in practice
@@ -417,6 +384,7 @@ export class SharedSyncImplementation extends BaseObserver {
417
384
  });
418
385
  const remote = Comlink.wrap(workerPort);
419
386
  const identifier = this.syncParams.dbParams.dbFilename;
387
+ const clientLockName = await generateTabCloseSignal();
420
388
  /**
421
389
  * The open could fail if the tab is closed while we're busy opening the database.
422
390
  * This operation is typically executed inside an exclusive portMutex lock.
@@ -424,7 +392,16 @@ export class SharedSyncImplementation extends BaseObserver {
424
392
  * We can't rely on the closeListeners to abort the operation if the tab is closed.
425
393
  */
426
394
  const db = await withAbort({
427
- action: () => remote(this.syncParams.dbParams),
395
+ action: async () => {
396
+ const clientView = await remote.connectToExisting({ identifier, lockName: clientLockName });
397
+ return new DatabaseClient({
398
+ connection: clientView,
399
+ source: remote,
400
+ // It's possible for this worker to outlive the client hosting the database for us. We need to be prepared for
401
+ // that and ensure pending requests are aborted when the tab is closed.
402
+ remoteCanCloseUnexpectedly: true
403
+ }, this.syncParams.dbParams);
404
+ },
428
405
  signal: abortController.signal,
429
406
  cleanupOnAbort: (db) => {
430
407
  db.close();
@@ -434,25 +411,82 @@ export class SharedSyncImplementation extends BaseObserver {
434
411
  removeCloseListener();
435
412
  });
436
413
  clearTimeout(timeout);
437
- const wrapped = new WorkerWrappedAsyncDatabaseConnection({
438
- remote,
439
- baseConnection: db,
440
- identifier,
441
- // It's possible for this worker to outlive the client hosting the database for us. We need to be prepared for
442
- // that and ensure pending requests are aborted when the tab is closed.
443
- remoteCanCloseUnexpectedly: true
444
- });
445
414
  client.closeListeners.push(async () => {
446
415
  this.logger.info('Aborting open connection because associated tab closed.');
416
+ handleClosed(db);
447
417
  /**
448
418
  * Don't await this close operation. It might never resolve if the tab is closed.
449
419
  * We mark the remote as closed first, this will reject any pending requests.
450
420
  * We then call close. The close operation is configured to fire-and-forget, the main promise will reject immediately.
451
421
  */
452
- wrapped.markRemoteClosed();
453
- wrapped.close().catch((ex) => this.logger.warn('error closing database connection', ex));
422
+ db.markRemoteClosed();
423
+ db.close().catch((ex) => this.logger.warn('error closing database connection', ex));
454
424
  });
455
- return wrapped;
425
+ return db;
426
+ }
427
+ generateReconnectableDatabase() {
428
+ const syncParams = this.syncParams;
429
+ const sharedSync = this;
430
+ class ReconnectPool extends BaseObserver {
431
+ connectionState = null;
432
+ get name() {
433
+ return syncParams?.dbParams.dbFilename;
434
+ }
435
+ async connect() {
436
+ if (this.connectionState == null) {
437
+ const handleClosed = this.handleClientClosed.bind(this);
438
+ this.connectionState = (async () => {
439
+ try {
440
+ const db = await sharedSync.openInternalDB(handleClosed);
441
+ db.registerListener({
442
+ tablesUpdated: (notification) => {
443
+ this.iterateListeners((l) => l.tablesUpdated?.(notification));
444
+ }
445
+ });
446
+ this.connectionState = db;
447
+ return db;
448
+ }
449
+ catch (e) {
450
+ // Allow reconnecting when the database is used again.
451
+ this.connectionState = null;
452
+ throw e;
453
+ }
454
+ })();
455
+ }
456
+ return await this.connectionState;
457
+ }
458
+ async close() {
459
+ if (this.connectionState != null) {
460
+ await (await this.connectionState).close();
461
+ }
462
+ }
463
+ handleClientClosed(client) {
464
+ if (client === this.connectionState) {
465
+ this.connectionState = null;
466
+ // We may have missed some table updates while the database was closed.
467
+ // We can poke the crud in case we missed any updates.
468
+ const impl = sharedSync.connectionManager.syncStreamImplementation;
469
+ impl?.triggerCrudUpload();
470
+ // The Rust client implementation stores sync state on the connection level. Reopening the database causes a
471
+ // disruption of the connection state and forces us to reconnect. We want to do that as soon as possible to
472
+ // minimize downtime.
473
+ impl?.markConnectionMayHaveChanged();
474
+ }
475
+ }
476
+ async readLock(fn, options) {
477
+ const db = await this.connect();
478
+ return db.readLock(fn, options);
479
+ }
480
+ async writeLock(fn, options) {
481
+ const db = await this.connect();
482
+ return db.writeLock(fn, options);
483
+ }
484
+ async refreshSchema() {
485
+ // Not used by sync client.
486
+ }
487
+ }
488
+ const Adapter = DBAdapterDefaultMixin(ReconnectPool);
489
+ return new Adapter();
456
490
  }
457
491
  /**
458
492
  * A method to update the all shared statuses for each