@powersync/web 1.24.0 → 1.25.0

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/lib/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powersync/web",
3
- "version": "1.24.0",
3
+ "version": "1.25.0",
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",
@@ -61,7 +61,7 @@
61
61
  "license": "Apache-2.0",
62
62
  "peerDependencies": {
63
63
  "@journeyapps/wa-sqlite": "^1.2.6",
64
- "@powersync/common": "workspace:^1.34.0"
64
+ "@powersync/common": "workspace:^1.35.0"
65
65
  },
66
66
  "dependencies": {
67
67
  "@powersync/common": "workspace:*",
@@ -1,6 +1,7 @@
1
1
  import { BatchedUpdateNotification, QueryResult } from '@powersync/common';
2
2
  import { ResolvedWebSQLOpenOptions } from './web-sql-flags';
3
3
  /**
4
+ * @internal
4
5
  * Proxied query result does not contain a function for accessing row values
5
6
  */
6
7
  export type ProxiedQueryResult = Omit<QueryResult, 'rows'> & {
@@ -9,6 +10,9 @@ export type ProxiedQueryResult = Omit<QueryResult, 'rows'> & {
9
10
  length: number;
10
11
  };
11
12
  };
13
+ /**
14
+ * @internal
15
+ */
12
16
  export type OnTableChangeCallback = (event: BatchedUpdateNotification) => void;
13
17
  /**
14
18
  * @internal
@@ -24,4 +28,7 @@ export interface AsyncDatabaseConnection<Config extends ResolvedWebSQLOpenOption
24
28
  registerOnTableChange(callback: OnTableChangeCallback): Promise<() => void>;
25
29
  getConfig(): Promise<Config>;
26
30
  }
31
+ /**
32
+ * @internal
33
+ */
27
34
  export type OpenAsyncDatabaseConnection<Config extends ResolvedWebSQLOpenOptions = ResolvedWebSQLOpenOptions> = (config: Config) => AsyncDatabaseConnection;
@@ -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) => {
@@ -1,4 +1,5 @@
1
1
  export * from '@powersync/common';
2
+ export * from './db/adapters/AsyncDatabaseConnection';
2
3
  export * from './db/adapters/AbstractWebPowerSyncDatabaseOpenFactory';
3
4
  export * from './db/adapters/AbstractWebSQLOpenFactory';
4
5
  export * from './db/adapters/wa-sqlite/WASQLiteConnection';
package/lib/src/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from '@powersync/common';
2
+ export * from './db/adapters/AsyncDatabaseConnection';
2
3
  export * from './db/adapters/AbstractWebPowerSyncDatabaseOpenFactory';
3
4
  export * from './db/adapters/AbstractWebSQLOpenFactory';
4
5
  export * from './db/adapters/wa-sqlite/WASQLiteConnection';
@@ -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);