@powersync/web 0.0.0-dev-20251120085122 → 0.0.0-dev-20251127205344

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 (28) hide show
  1. package/dist/index.umd.js +63 -41
  2. package/dist/index.umd.js.map +1 -1
  3. package/dist/worker/SharedSyncImplementation.umd.js +14914 -10959
  4. package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
  5. package/dist/worker/WASQLiteDB.umd.js +12708 -10895
  6. package/dist/worker/WASQLiteDB.umd.js.map +1 -1
  7. package/lib/package.json +2 -2
  8. package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.d.ts +1 -0
  9. package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.js +11 -2
  10. package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.js +1 -1
  11. package/lib/src/db/sync/SharedWebStreamingSyncImplementation.d.ts +1 -1
  12. package/lib/src/db/sync/SharedWebStreamingSyncImplementation.js +15 -13
  13. package/lib/src/worker/db/SharedWASQLiteConnection.js +1 -0
  14. package/lib/src/worker/sync/SharedSyncImplementation.js +22 -12
  15. package/lib/src/worker/sync/WorkerClient.d.ts +1 -1
  16. package/lib/src/worker/sync/WorkerClient.js +2 -2
  17. package/lib/tsconfig.tsbuildinfo +1 -1
  18. package/package.json +3 -3
  19. package/src/db/adapters/LockedAsyncDatabaseAdapter.ts +12 -2
  20. package/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.ts +1 -1
  21. package/src/db/sync/SharedWebStreamingSyncImplementation.ts +18 -16
  22. package/src/worker/db/SharedWASQLiteConnection.ts +1 -0
  23. package/src/worker/sync/SharedSyncImplementation.ts +24 -14
  24. package/src/worker/sync/WorkerClient.ts +5 -4
  25. package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapp-81d3460.index.umd.js +0 -355
  26. package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapp-81d3460.index.umd.js.map +0 -1
  27. package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapp-81d3461.index.umd.js +0 -355
  28. package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapp-81d3461.index.umd.js.map +0 -1
package/lib/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powersync/web",
3
- "version": "1.28.2",
3
+ "version": "1.29.1",
4
4
  "description": "PowerSync web SDK. Sync Postgres, MongoDB or MySQL with SQLite in your web app",
5
5
  "main": "lib/src/index.js",
6
6
  "types": "lib/src/index.d.ts",
@@ -62,7 +62,7 @@
62
62
  "license": "Apache-2.0",
63
63
  "peerDependencies": {
64
64
  "@journeyapps/wa-sqlite": "^1.3.2",
65
- "@powersync/common": "workspace:^1.42.0"
65
+ "@powersync/common": "workspace:^1.43.1"
66
66
  },
67
67
  "dependencies": {
68
68
  "@powersync/common": "workspace:*",
@@ -30,6 +30,7 @@ export declare class LockedAsyncDatabaseAdapter extends BaseObserver<LockedAsync
30
30
  protected _disposeTableChangeListener: (() => void) | null;
31
31
  private _config;
32
32
  protected pendingAbortControllers: Set<AbortController>;
33
+ protected requiresHolds: boolean | null;
33
34
  closing: boolean;
34
35
  closed: boolean;
35
36
  constructor(options: LockedAsyncDatabaseAdapterOptions);
@@ -1,6 +1,7 @@
1
1
  import { BaseObserver, createLogger } from '@powersync/common';
2
2
  import { getNavigatorLocks } from '../..//shared/navigator';
3
3
  import { WorkerWrappedAsyncDatabaseConnection } from './WorkerWrappedAsyncDatabaseConnection';
4
+ import { WASQLiteVFS } from './wa-sqlite/WASQLiteConnection';
4
5
  /**
5
6
  * @internal
6
7
  * Wraps a {@link AsyncDatabaseConnection} and provides exclusive locking functions in
@@ -17,6 +18,7 @@ export class LockedAsyncDatabaseAdapter extends BaseObserver {
17
18
  _disposeTableChangeListener = null;
18
19
  _config = null;
19
20
  pendingAbortControllers;
21
+ requiresHolds;
20
22
  closing;
21
23
  closed;
22
24
  constructor(options) {
@@ -27,6 +29,7 @@ export class LockedAsyncDatabaseAdapter extends BaseObserver {
27
29
  this.pendingAbortControllers = new Set();
28
30
  this.closed = false;
29
31
  this.closing = false;
32
+ this.requiresHolds = null;
30
33
  // Set the name if provided. We can query for the name if not available yet
31
34
  this.debugMode = options.debugMode ?? false;
32
35
  if (this.debugMode) {
@@ -71,6 +74,10 @@ export class LockedAsyncDatabaseAdapter extends BaseObserver {
71
74
  this._config = await this._db.getConfig();
72
75
  await this.registerOnChangeListener(this._db);
73
76
  this.iterateListeners((cb) => cb.initialized?.());
77
+ /**
78
+ * This is only required for the long-lived shared IndexedDB connections.
79
+ */
80
+ this.requiresHolds = this._config.vfs == WASQLiteVFS.IDBBatchAtomicVFS;
74
81
  }
75
82
  getConfiguration() {
76
83
  if (!this._config) {
@@ -165,12 +172,14 @@ export class LockedAsyncDatabaseAdapter extends BaseObserver {
165
172
  if (timoutId) {
166
173
  clearTimeout(timoutId);
167
174
  }
168
- const holdId = await this.baseDB.markHold();
175
+ const holdId = this.requiresHolds ? await this.baseDB.markHold() : null;
169
176
  try {
170
177
  return await callback();
171
178
  }
172
179
  finally {
173
- await this.baseDB.releaseHold(holdId);
180
+ if (holdId) {
181
+ await this.baseDB.releaseHold(holdId);
182
+ }
174
183
  }
175
184
  });
176
185
  }
@@ -38,7 +38,7 @@ export class WorkerWrappedAsyncDatabaseConnection {
38
38
  return this.withRemote(() => this.baseConnection.releaseHold(holdId));
39
39
  }
40
40
  isAutoCommit() {
41
- return this.baseConnection.isAutoCommit();
41
+ return this.withRemote(() => this.baseConnection.isAutoCommit());
42
42
  }
43
43
  withRemote(workerPromise) {
44
44
  const controller = this.notifyRemoteClosed;
@@ -1,9 +1,9 @@
1
1
  import { PowerSyncConnectionOptions, PowerSyncCredentials, SubscribedStream, SyncStatusOptions } from '@powersync/common';
2
2
  import * as Comlink from 'comlink';
3
3
  import { AbstractSharedSyncClientProvider } from '../../worker/sync/AbstractSharedSyncClientProvider';
4
+ import { WorkerClient } from '../../worker/sync/WorkerClient';
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.
@@ -1,9 +1,9 @@
1
1
  import * as Comlink from 'comlink';
2
+ import { getNavigatorLocks } from '../../shared/navigator';
2
3
  import { AbstractSharedSyncClientProvider } from '../../worker/sync/AbstractSharedSyncClientProvider';
3
4
  import { SharedSyncClientEvent } from '../../worker/sync/SharedSyncImplementation';
4
- import { DEFAULT_CACHE_SIZE_KB, resolveWebSQLFlags, TemporaryStorageOption } from '../adapters/web-sql-flags';
5
+ import { DEFAULT_CACHE_SIZE_KB, TemporaryStorageOption, resolveWebSQLFlags } from '../adapters/web-sql-flags';
5
6
  import { WebStreamingSyncImplementation } from './WebStreamingSyncImplementation';
6
- import { getNavigatorLocks } from '../../shared/navigator';
7
7
  /**
8
8
  * The shared worker will trigger methods on this side of the message port
9
9
  * via this client provider.
@@ -130,6 +130,19 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
130
130
  */
131
131
  const { crudUploadThrottleMs, identifier, retryDelayMs } = this.options;
132
132
  const flags = { ...this.webOptions.flags, workers: undefined };
133
+ // Request a random lock until this client is disposed. The name of the lock is sent to the shared worker, which
134
+ // will also attempt to acquire it. Since the lock is returned when the tab is closed, this allows the share worker
135
+ // to free resources associated with this tab.
136
+ // We take hold of this lock as soon-as-possible in order to cater for potentially closed tabs.
137
+ getNavigatorLocks().request(`tab-close-signal-${crypto.randomUUID()}`, async (lock) => {
138
+ if (!this.abortOnClose.signal.aborted) {
139
+ // Awaiting here ensures the worker is waiting for the lock
140
+ await this.syncManager.addLockBasedCloseSignal(lock.name);
141
+ await new Promise((r) => {
142
+ this.abortOnClose.signal.onabort = () => r();
143
+ });
144
+ }
145
+ });
133
146
  this.isInitialized = this.syncManager.setParams({
134
147
  dbParams: this.dbAdapter.getConfiguration(),
135
148
  streamOptions: {
@@ -151,17 +164,6 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
151
164
  * This performs bi-directional method calling.
152
165
  */
153
166
  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
167
  }
166
168
  /**
167
169
  * Starts the sync process, this effectively acts as a call to
@@ -61,6 +61,7 @@ export class SharedWASQLiteConnection {
61
61
  const connection = this.connection;
62
62
  dbMap.delete(dbFilename);
63
63
  await connection.close();
64
+ return;
64
65
  }
65
66
  logger.debug(`Connection to ${dbFilename} not closed yet due to active clients.`);
66
67
  return;
@@ -218,6 +218,20 @@ export class SharedSyncImplementation extends BaseObserver {
218
218
  }
219
219
  });
220
220
  const shouldReconnect = !!this.connectionManager.syncStreamImplementation && this.ports.length > 0;
221
+ /**
222
+ * If the current database adapter is the one that is being closed, we need to disconnect from the backend.
223
+ * We can disconnect in the portMutex lock. This ensures the disconnect is not affected by potential other
224
+ * connect operations coming from other tabs.
225
+ */
226
+ if (this.dbAdapter && this.dbAdapter == trackedPort.db) {
227
+ this.logger.debug(`Disconnecting due to closed database: should reconnect: ${shouldReconnect}`);
228
+ this.dbAdapter = null;
229
+ // Unconditionally close the connection because the database it's writing to has just been closed.
230
+ // The connection has been closed previously, this might throw. We should be able to ignore it.
231
+ await this.connectionManager
232
+ .disconnect()
233
+ .catch((ex) => this.logger.warn('Error while disconnecting. Will attempt to reconnect.', ex));
234
+ }
221
235
  return {
222
236
  shouldReconnect,
223
237
  trackedPort
@@ -230,17 +244,8 @@ export class SharedSyncImplementation extends BaseObserver {
230
244
  for (const closeListener of trackedPort.closeListeners) {
231
245
  await closeListener();
232
246
  }
233
- if (this.dbAdapter && this.dbAdapter == trackedPort.db) {
234
- // Unconditionally close the connection because the database it's writing to has just been closed.
235
- // The connection has been closed previously, this might throw. We should be able to ignore it.
236
- await this.connectionManager
237
- .disconnect()
238
- .catch((ex) => this.logger.warn('Error while disconnecting. Will attempt to reconnect.', ex));
239
- // Clearing the adapter will result in a new one being opened in connect
240
- this.dbAdapter = null;
241
- if (shouldReconnect) {
242
- await this.connectionManager.connect(CONNECTOR_PLACEHOLDER, this.lastConnectOptions ?? {});
243
- }
247
+ if (shouldReconnect) {
248
+ await this.connectionManager.connect(CONNECTOR_PLACEHOLDER, this.lastConnectOptions ?? {});
244
249
  }
245
250
  // Re-index subscriptions, the subscriptions of the removed port would no longer be considered.
246
251
  this.collectActiveSubscriptions();
@@ -366,7 +371,12 @@ export class SharedSyncImplementation extends BaseObserver {
366
371
  });
367
372
  lastClient.closeListeners.push(async () => {
368
373
  this.logger.info('Aborting open connection because associated tab closed.');
369
- await wrapped.close().catch((ex) => this.logger.warn('error closing database connection', ex));
374
+ /**
375
+ * Don't await this close operation. It might never resolve if the tab is closed.
376
+ * We run the close operation first, before marking the remote as closed. This gives the database some chance
377
+ * to close the connection.
378
+ */
379
+ wrapped.close().catch((ex) => this.logger.warn('error closing database connection', ex));
370
380
  wrapped.markRemoteClosed();
371
381
  });
372
382
  return wrapped;
@@ -1,5 +1,5 @@
1
- import { SharedSyncImplementation, SharedSyncInitOptions } from './SharedSyncImplementation';
2
1
  import { ILogLevel, PowerSyncConnectionOptions, SubscribedStream, SyncStatusOptions } from '@powersync/common';
2
+ import { SharedSyncImplementation, SharedSyncInitOptions } from './SharedSyncImplementation';
3
3
  /**
4
4
  * A client to the shared sync worker.
5
5
  *
@@ -1,6 +1,6 @@
1
1
  import * as Comlink from 'comlink';
2
- import { SharedSyncClientEvent } from './SharedSyncImplementation';
3
2
  import { getNavigatorLocks } from '../../shared/navigator';
3
+ import { SharedSyncClientEvent } from './SharedSyncImplementation';
4
4
  /**
5
5
  * A client to the shared sync worker.
6
6
  *
@@ -14,6 +14,7 @@ export class WorkerClient {
14
14
  constructor(sync, port) {
15
15
  this.sync = sync;
16
16
  this.port = port;
17
+ Comlink.expose(this, this.port);
17
18
  }
18
19
  async initialize() {
19
20
  /**
@@ -27,7 +28,6 @@ export class WorkerClient {
27
28
  }
28
29
  });
29
30
  this.resolvedPort = await this.sync.addPort(this.port);
30
- Comlink.expose(this, this.port);
31
31
  }
32
32
  async removePort() {
33
33
  if (this.resolvedPort) {