@powersync/capacitor 0.0.0-dev-20260305124002 → 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.
@@ -4,15 +4,17 @@ import { Capacitor } from '@capacitor/core';
4
4
  import {
5
5
  BaseObserver,
6
6
  BatchedUpdateNotification,
7
+ ConnectionPool,
7
8
  DBAdapter,
9
+ DBAdapterDefaultMixin,
8
10
  DBAdapterListener,
9
11
  DBLockOptions,
10
12
  LockContext,
11
- mutexRunExclusive,
13
+ Mutex,
12
14
  QueryResult,
15
+ timeoutSignal,
13
16
  Transaction
14
17
  } from '@powersync/web';
15
- import { Mutex } from 'async-mutex';
16
18
  import { PowerSyncCore } from '../plugin/PowerSyncCore.js';
17
19
  import { messageForErrorCode } from '../plugin/PowerSyncPlugin.js';
18
20
  import { CapacitorSQLiteOpenFactoryOptions, DEFAULT_SQLITE_OPTIONS } from './CapacitorSQLiteOpenFactory.js';
@@ -30,13 +32,8 @@ async function monitorQuery(sql: string, executor: () => Promise<QueryResult>):
30
32
  throw e;
31
33
  }
32
34
  }
33
- /**
34
- * An implementation of {@link DBAdapter} using the Capacitor Community SQLite [plugin](https://github.com/capacitor-community/sqlite).
35
- *
36
- * @experimental
37
- * @alpha This is currently experimental and may change without a major version bump.
38
- */
39
- export class CapacitorSQLiteAdapter extends BaseObserver<DBAdapterListener> implements DBAdapter {
35
+
36
+ class CapacitorConnectionPool extends BaseObserver<DBAdapterListener> implements ConnectionPool {
40
37
  protected _writeConnection: SQLiteDBConnection | null;
41
38
  protected _readConnection: SQLiteDBConnection | null;
42
39
  protected initializedPromise: Promise<void>;
@@ -206,26 +203,8 @@ export class CapacitorSQLiteAdapter extends BaseObserver<DBAdapterListener> impl
206
203
  return results.rows?._array.map((row) => Object.values(row)) ?? [];
207
204
  };
208
205
 
209
- return {
210
- getAll,
211
- getOptional,
212
- get,
213
- executeRaw,
214
- execute
215
- };
216
- }
217
-
218
- execute(query: string, params?: any[]): Promise<QueryResult> {
219
- return this.writeLock((tx) => tx.execute(query, params));
220
- }
221
-
222
- executeRaw(query: string, params?: any[]): Promise<any[][]> {
223
- return this.writeLock((tx) => tx.executeRaw(query, params));
224
- }
225
-
226
- async executeBatch(query: string, params: any[][] = []): Promise<QueryResult> {
227
- return this.writeLock(async (tx) => {
228
- let result = await this.writeConnection.executeSet(
206
+ const executeBatch = async (query: string, params: any[][] = []): Promise<QueryResult> => {
207
+ let result = await db.executeSet(
229
208
  params.map((param) => ({
230
209
  statement: query,
231
210
  values: param
@@ -236,55 +215,44 @@ export class CapacitorSQLiteAdapter extends BaseObserver<DBAdapterListener> impl
236
215
  rowsAffected: result.changes?.changes ?? 0,
237
216
  insertId: result.changes?.lastId
238
217
  };
239
- });
240
- }
218
+ };
241
219
 
242
- readLock<T>(fn: (tx: LockContext) => Promise<T>, options?: DBLockOptions): Promise<T> {
243
- return mutexRunExclusive(
244
- this.readMutex,
245
- async () => {
246
- await this.initializedPromise;
247
- return await fn(this.generateLockContext(this.readConnection));
248
- },
249
- options
250
- );
220
+ return {
221
+ getAll,
222
+ getOptional,
223
+ get,
224
+ executeRaw,
225
+ execute,
226
+ executeBatch
227
+ };
251
228
  }
252
229
 
253
- readTransaction<T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions): Promise<T> {
254
- return this.readLock(async (ctx) => {
255
- return this.internalTransaction(ctx, fn);
256
- });
230
+ readLock<T>(fn: (tx: LockContext) => Promise<T>, options?: DBLockOptions): Promise<T> {
231
+ return this.readMutex.runExclusive(async () => {
232
+ await this.initializedPromise;
233
+ return fn(this.generateLockContext(this.readConnection));
234
+ }, timeoutSignal(options?.timeoutMs));
257
235
  }
258
236
 
259
237
  writeLock<T>(fn: (tx: LockContext) => Promise<T>, options?: DBLockOptions): Promise<T> {
260
- return mutexRunExclusive(
261
- this.writeMutex,
262
- async () => {
263
- await this.initializedPromise;
264
- const result = await fn(this.generateLockContext(this.writeConnection));
265
-
266
- // Fetch table updates
267
- const updates = await this.writeConnection.query("SELECT powersync_update_hooks('get') AS table_name");
268
- const jsonUpdates = updates.values?.[0];
269
- if (!jsonUpdates || !jsonUpdates.table_name) {
270
- throw new Error('Could not fetch table updates');
271
- }
272
- const notification: BatchedUpdateNotification = {
273
- rawUpdates: [],
274
- tables: JSON.parse(jsonUpdates.table_name),
275
- groupedUpdates: {}
276
- };
277
- this.iterateListeners((l) => l.tablesUpdated?.(notification));
278
- return result;
279
- },
280
- options
281
- );
282
- }
283
-
284
- writeTransaction<T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions): Promise<T> {
285
- return this.writeLock(async (ctx) => {
286
- return this.internalTransaction(ctx, fn);
287
- });
238
+ return this.writeMutex.runExclusive(async () => {
239
+ await this.initializedPromise;
240
+ const result = await fn(this.generateLockContext(this.writeConnection));
241
+
242
+ // Fetch table updates
243
+ const updates = await this.writeConnection.query("SELECT powersync_update_hooks('get') AS table_name");
244
+ const jsonUpdates = updates.values?.[0];
245
+ if (!jsonUpdates || !jsonUpdates.table_name) {
246
+ throw new Error('Could not fetch table updates');
247
+ }
248
+ const notification: BatchedUpdateNotification = {
249
+ rawUpdates: [],
250
+ tables: JSON.parse(jsonUpdates.table_name),
251
+ groupedUpdates: {}
252
+ };
253
+ this.iterateListeners((l) => l.tablesUpdated?.(notification));
254
+ return result;
255
+ }, timeoutSignal(options?.timeoutMs));
288
256
  }
289
257
 
290
258
  refreshSchema(): Promise<void> {
@@ -296,52 +264,12 @@ export class CapacitorSQLiteAdapter extends BaseObserver<DBAdapterListener> impl
296
264
  });
297
265
  });
298
266
  }
299
-
300
- getAll<T>(sql: string, parameters?: any[]): Promise<T[]> {
301
- return this.readLock((tx) => tx.getAll<T>(sql, parameters));
302
- }
303
-
304
- getOptional<T>(sql: string, parameters?: any[]): Promise<T | null> {
305
- return this.readLock((tx) => tx.getOptional<T>(sql, parameters));
306
- }
307
-
308
- get<T>(sql: string, parameters?: any[]): Promise<T> {
309
- return this.readLock((tx) => tx.get<T>(sql, parameters));
310
- }
311
-
312
- protected async internalTransaction<T>(ctx: LockContext, fn: (tx: Transaction) => Promise<T>): Promise<T> {
313
- let finalized = false;
314
- const commit = async (): Promise<QueryResult> => {
315
- if (finalized) {
316
- return { rowsAffected: 0 };
317
- }
318
- finalized = true;
319
- return ctx.execute('COMMIT');
320
- };
321
- const rollback = async (): Promise<QueryResult> => {
322
- if (finalized) {
323
- return { rowsAffected: 0 };
324
- }
325
- finalized = true;
326
- return ctx.execute('ROLLBACK');
327
- };
328
- try {
329
- await ctx.execute('BEGIN');
330
- const result = await fn({
331
- ...ctx,
332
- commit,
333
- rollback
334
- });
335
- await commit();
336
- return result;
337
- } catch (ex) {
338
- try {
339
- await rollback();
340
- } catch (ex2) {
341
- // In rare cases, a rollback may fail.
342
- // Safe to ignore.
343
- }
344
- throw ex;
345
- }
346
- }
347
267
  }
268
+
269
+ /**
270
+ * An implementation of {@link DBAdapter} using the Capacitor Community SQLite [plugin](https://github.com/capacitor-community/sqlite).
271
+ *
272
+ * @experimental
273
+ * @alpha This is currently experimental and may change without a major version bump.
274
+ */
275
+ export class CapacitorSQLiteAdapter extends DBAdapterDefaultMixin(CapacitorConnectionPool) {}
@@ -0,0 +1,22 @@
1
+ import { PowerSyncControlCommand, SqliteBucketStorage } from '@powersync/web';
2
+
3
+ export class CapacitorBucketStorageAdapter extends SqliteBucketStorage {
4
+ control(op: PowerSyncControlCommand, payload: string | Uint8Array | ArrayBuffer | null): Promise<string> {
5
+ if (payload instanceof Uint8Array && (payload as any)['_isBuffer'] == true) {
6
+ /**
7
+ * The Buffer polyfill, used in @powersync/common, is a Uint8Array subclass which defines additional fields like
8
+ * `_isBuffer` and `parent` on its `prototype`. The additional fields are serialized when passed through the native bridge.
9
+ * The Capacitor Community SQLite lib expects a dictionarty of indexes to numerical bytes.
10
+ * The additiona fields (which are not an index to numerical byte mapping) cause the parsing logic in the SQLite lib to throw an error:
11
+ * "Error in reading buffer".
12
+ *
13
+ * Re-wrapping the same backing buffer as a plain Uint8Array removes the Buffer subclass wrapper
14
+ * while keeping the same underlying bytes. This creates a new view, not a byte copy, so the
15
+ * overhead should be minimal.
16
+ */
17
+ payload = new Uint8Array(payload.buffer, payload.byteOffset, payload.byteLength);
18
+ }
19
+
20
+ return super.control(op, payload);
21
+ }
22
+ }
@@ -0,0 +1,12 @@
1
+ import { Capacitor } from '@capacitor/core';
2
+ import { WebRemote } from '@powersync/web';
3
+
4
+ export class CapacitorRemote extends WebRemote {
5
+ protected get supportsStreamingBinaryResponses(): boolean {
6
+ /**
7
+ * We'd like to avoid passing Binary buffers to SQLite when using
8
+ * iOS for now. This is due to inefficient binary processing.
9
+ */
10
+ return Capacitor.getPlatform() !== 'ios';
11
+ }
12
+ }
@@ -1,5 +1,4 @@
1
- import { AbstractStreamingSyncImplementation, LockOptions, LockType, mutexRunExclusive } from '@powersync/web';
2
- import { Mutex } from 'async-mutex';
1
+ import { AbstractStreamingSyncImplementation, LockOptions, LockType, Mutex } from '@powersync/web';
3
2
 
4
3
  type MutexMap = {
5
4
  /**
@@ -56,11 +55,8 @@ export class CapacitorStreamingSyncImplementation extends AbstractStreamingSyncI
56
55
  mutexRecord.tracking.add(this.instanceId);
57
56
  const mutex = mutexRecord.locks[lockOptions.type];
58
57
 
59
- return mutexRunExclusive(mutex, async () => {
60
- if (lockOptions.signal?.aborted) {
61
- throw new Error('Aborted');
62
- }
58
+ return mutex.runExclusive(async () => {
63
59
  return await lockOptions.callback();
64
- });
60
+ }, lockOptions.signal);
65
61
  }
66
62
  }