@powersync/web 0.0.0-dev-20250711131120 → 0.0.0-dev-20250714151300

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.
@@ -82,7 +82,7 @@ export class PowerSyncDatabase extends AbstractPowerSyncDatabase {
82
82
  });
83
83
  }
84
84
  generateBucketStorageAdapter() {
85
- return new SqliteBucketStorage(this.database, AbstractPowerSyncDatabase.transactionMutex);
85
+ return new SqliteBucketStorage(this.database);
86
86
  }
87
87
  async runExclusive(cb) {
88
88
  if (this.resolvedFlags.ssrMode) {
@@ -91,7 +91,7 @@ export class PowerSyncDatabase extends AbstractPowerSyncDatabase {
91
91
  return getNavigatorLocks().request(`lock-${this.database.name}`, cb);
92
92
  }
93
93
  generateSyncStreamImplementation(connector, options) {
94
- const remote = new WebRemote(connector, this.logger);
94
+ const remote = new WebRemote(connector);
95
95
  const syncOptions = {
96
96
  ...this.options,
97
97
  retryDelayMs: options.retryDelayMs,
@@ -103,8 +103,7 @@ export class PowerSyncDatabase extends AbstractPowerSyncDatabase {
103
103
  await this.waitForReady();
104
104
  await connector.uploadData(this);
105
105
  },
106
- identifier: this.database.name,
107
- logger: this.logger
106
+ identifier: this.database.name
108
107
  };
109
108
  switch (true) {
110
109
  case this.resolvedFlags.ssrMode:
@@ -29,6 +29,9 @@ export declare class LockedAsyncDatabaseAdapter extends BaseObserver<LockedAsync
29
29
  private _db;
30
30
  protected _disposeTableChangeListener: (() => void) | null;
31
31
  private _config;
32
+ protected pendingAbortControllers: Set<AbortController>;
33
+ closing: boolean;
34
+ closed: boolean;
32
35
  constructor(options: LockedAsyncDatabaseAdapterOptions);
33
36
  protected get baseDB(): AsyncDatabaseConnection<ResolvedWebSQLOpenOptions>;
34
37
  get name(): string;
@@ -63,7 +66,9 @@ export declare class LockedAsyncDatabaseAdapter extends BaseObserver<LockedAsync
63
66
  get<T>(sql: string, parameters?: any[] | undefined): Promise<T>;
64
67
  readLock<T>(fn: (tx: LockContext) => Promise<T>, options?: DBLockOptions | undefined): Promise<T>;
65
68
  writeLock<T>(fn: (tx: LockContext) => Promise<T>, options?: DBLockOptions | undefined): Promise<T>;
66
- protected acquireLock(callback: () => Promise<any>): Promise<any>;
69
+ protected acquireLock(callback: () => Promise<any>, options?: {
70
+ timeoutMs?: number;
71
+ }): Promise<any>;
67
72
  readTransaction<T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions | undefined): Promise<T>;
68
73
  writeTransaction<T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions | undefined): Promise<T>;
69
74
  private generateDBHelpers;
@@ -16,11 +16,17 @@ export class LockedAsyncDatabaseAdapter extends BaseObserver {
16
16
  _db = null;
17
17
  _disposeTableChangeListener = null;
18
18
  _config = null;
19
+ pendingAbortControllers;
20
+ closing;
21
+ closed;
19
22
  constructor(options) {
20
23
  super();
21
24
  this.options = options;
22
25
  this._dbIdentifier = options.name;
23
26
  this.logger = options.logger ?? createLogger(`LockedAsyncDatabaseAdapter - ${this._dbIdentifier}`);
27
+ this.pendingAbortControllers = new Set();
28
+ this.closed = false;
29
+ this.closing = false;
24
30
  // Set the name if provided. We can query for the name if not available yet
25
31
  this.debugMode = options.debugMode ?? false;
26
32
  if (this.debugMode) {
@@ -110,8 +116,11 @@ export class LockedAsyncDatabaseAdapter extends BaseObserver {
110
116
  * tabs are still using it.
111
117
  */
112
118
  async close() {
119
+ this.closing = true;
113
120
  this._disposeTableChangeListener?.();
121
+ this.pendingAbortControllers.forEach((controller) => controller.abort('Closed'));
114
122
  await this.baseDB?.close?.();
123
+ this.closed = true;
115
124
  }
116
125
  async getAll(sql, parameters) {
117
126
  await this.waitForInitialized();
@@ -127,14 +136,37 @@ export class LockedAsyncDatabaseAdapter extends BaseObserver {
127
136
  }
128
137
  async readLock(fn, options) {
129
138
  await this.waitForInitialized();
130
- return this.acquireLock(async () => fn(this.generateDBHelpers({ execute: this._execute, executeRaw: this._executeRaw })));
139
+ return this.acquireLock(async () => fn(this.generateDBHelpers({ execute: this._execute, executeRaw: this._executeRaw })), {
140
+ timeoutMs: options?.timeoutMs
141
+ });
131
142
  }
132
143
  async writeLock(fn, options) {
133
144
  await this.waitForInitialized();
134
- return this.acquireLock(async () => fn(this.generateDBHelpers({ execute: this._execute, executeRaw: this._executeRaw })));
145
+ return this.acquireLock(async () => fn(this.generateDBHelpers({ execute: this._execute, executeRaw: this._executeRaw })), {
146
+ timeoutMs: options?.timeoutMs
147
+ });
135
148
  }
136
- acquireLock(callback) {
137
- return getNavigatorLocks().request(`db-lock-${this._dbIdentifier}`, callback);
149
+ async acquireLock(callback, options) {
150
+ await this.waitForInitialized();
151
+ if (this.closing) {
152
+ throw new Error(`Cannot acquire lock, the database is closing`);
153
+ }
154
+ const abortController = new AbortController();
155
+ this.pendingAbortControllers.add(abortController);
156
+ const { timeoutMs } = options ?? {};
157
+ const timoutId = timeoutMs
158
+ ? setTimeout(() => {
159
+ abortController.abort(`Timeout after ${timeoutMs}ms`);
160
+ this.pendingAbortControllers.delete(abortController);
161
+ }, timeoutMs)
162
+ : null;
163
+ return getNavigatorLocks().request(`db-lock-${this._dbIdentifier}`, { signal: abortController.signal }, () => {
164
+ this.pendingAbortControllers.delete(abortController);
165
+ if (timoutId) {
166
+ clearTimeout(timoutId);
167
+ }
168
+ return callback();
169
+ });
138
170
  }
139
171
  async readTransaction(fn, options) {
140
172
  return this.readLock(this.wrapTransaction(fn));
@@ -168,6 +168,7 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
168
168
  }
169
169
  async dispose() {
170
170
  await this.waitForReady();
171
+ await super.dispose();
171
172
  await new Promise((resolve) => {
172
173
  // Listen for the close acknowledgment from the worker
173
174
  this.messagePort.addEventListener('message', (event) => {
@@ -256,7 +256,7 @@ export class SharedSyncImplementation extends BaseObserver {
256
256
  const syncParams = this.syncParams;
257
257
  // Create a new StreamingSyncImplementation for each connect call. This is usually done is all SDKs.
258
258
  return new WebStreamingSyncImplementation({
259
- adapter: new SqliteBucketStorage(this.dbAdapter, new Mutex(), this.logger),
259
+ adapter: new SqliteBucketStorage(this.dbAdapter, this.logger),
260
260
  remote: new WebRemote({
261
261
  invalidateCredentials: async () => {
262
262
  const lastPort = this.ports[this.ports.length - 1];
@@ -354,8 +354,7 @@ export class SharedSyncImplementation extends BaseObserver {
354
354
  */
355
355
  async _testUpdateAllStatuses(status) {
356
356
  if (!this.connectionManager.syncStreamImplementation) {
357
- // This is just for testing purposes
358
- this.connectionManager.syncStreamImplementation = this.generateStreamingImplementation();
357
+ throw new Error('Cannot update status without a sync stream implementation');
359
358
  }
360
359
  // Only assigning, don't call listeners for this test
361
360
  this.connectionManager.syncStreamImplementation.syncStatus = new SyncStatus(status);