From 5ada0837c293a4762a6d08dde54814f9dffa7d23 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Thu, 13 Nov 2025 09:40:33 +0200 Subject: [PATCH 1/3] fix: capacitor readTransaction not allowed errors --- .changeset/gorgeous-scissors-drive.md | 5 ++++ .../src/adapter/CapacitorSQLiteAdapter.ts | 29 ++++++++++++------- 2 files changed, 24 insertions(+), 10 deletions(-) create mode 100644 .changeset/gorgeous-scissors-drive.md diff --git a/.changeset/gorgeous-scissors-drive.md b/.changeset/gorgeous-scissors-drive.md new file mode 100644 index 000000000..390dcc1f0 --- /dev/null +++ b/.changeset/gorgeous-scissors-drive.md @@ -0,0 +1,5 @@ +--- +'@powersync/common': patch +--- + +Fixed readTransaction method throwing "not allowed in read-only mode" errors diff --git a/packages/capacitor/src/adapter/CapacitorSQLiteAdapter.ts b/packages/capacitor/src/adapter/CapacitorSQLiteAdapter.ts index 7a4e64951..a64b095e1 100644 --- a/packages/capacitor/src/adapter/CapacitorSQLiteAdapter.ts +++ b/packages/capacitor/src/adapter/CapacitorSQLiteAdapter.ts @@ -120,20 +120,29 @@ export class CapacitorSQLiteAdapter extends BaseObserver impl protected generateLockContext(db: SQLiteDBConnection): LockContext { const _execute = async (query: string, params: any[] = []): Promise => { const platform = Capacitor.getPlatform(); + + const _query = async (query: string, params: any[] = []) => { + const result = await db.query(query, params); + const arrayResult = result.values ?? []; + return { + rowsAffected: 0, + rows: { + _array: arrayResult, + length: arrayResult.length, + item: (idx: number) => arrayResult[idx] + } + }; + }; + + if (db.getConnectionReadOnly()) { + return _query(query, params); + } + if (platform == 'android') { // Android: use query for SELECT and executeSet for mutations // We cannot use `run` here for both cases. if (query.toLowerCase().trim().startsWith('select')) { - const result = await db.query(query, params); - const arrayResult = result.values ?? []; - return { - rowsAffected: 0, - rows: { - _array: arrayResult, - length: arrayResult.length, - item: (idx: number) => arrayResult[idx] - } - }; + return _query(query, params); } else { const result = await db.executeSet([{ statement: query, values: params }], false); return { From 5f3380a2e7eee021d51384b7c0cbfcaf66a73af8 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Thu, 13 Nov 2025 09:46:51 +0200 Subject: [PATCH 2/3] Code cleanup --- .../src/adapter/CapacitorSQLiteAdapter.ts | 45 +++++++------------ 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/packages/capacitor/src/adapter/CapacitorSQLiteAdapter.ts b/packages/capacitor/src/adapter/CapacitorSQLiteAdapter.ts index a64b095e1..3081d8755 100644 --- a/packages/capacitor/src/adapter/CapacitorSQLiteAdapter.ts +++ b/packages/capacitor/src/adapter/CapacitorSQLiteAdapter.ts @@ -118,22 +118,22 @@ export class CapacitorSQLiteAdapter extends BaseObserver impl } protected generateLockContext(db: SQLiteDBConnection): LockContext { + const _query = async (query: string, params: any[] = []) => { + const result = await db.query(query, params); + const arrayResult = result.values ?? []; + return { + rowsAffected: 0, + rows: { + _array: arrayResult, + length: arrayResult.length, + item: (idx: number) => arrayResult[idx] + } + }; + }; + const _execute = async (query: string, params: any[] = []): Promise => { const platform = Capacitor.getPlatform(); - const _query = async (query: string, params: any[] = []) => { - const result = await db.query(query, params); - const arrayResult = result.values ?? []; - return { - rowsAffected: 0, - rows: { - _array: arrayResult, - length: arrayResult.length, - item: (idx: number) => arrayResult[idx] - } - }; - }; - if (db.getConnectionReadOnly()) { return _query(query, params); } @@ -175,24 +175,9 @@ export class CapacitorSQLiteAdapter extends BaseObserver impl ? (sql: string, params?: any[]) => monitorQuery(sql, () => _execute(sql, params)) : _execute; - const _executeQuery = async (query: string, params?: any[]): Promise => { - let result = await db.query(query, params); - - let arrayResult = result.values ?? []; - - return { - rowsAffected: 0, - rows: { - _array: arrayResult, - length: arrayResult.length, - item: (idx: number) => arrayResult[idx] - } - }; - }; - const executeQuery = this.options.debugMode - ? (sql: string, params?: any[]) => monitorQuery(sql, () => _executeQuery(sql, params)) - : _executeQuery; + ? (sql: string, params?: any[]) => monitorQuery(sql, () => _query(sql, params)) + : _query; const getAll = async (query: string, params?: any[]): Promise => { const result = await executeQuery(query, params); From 14896557739563a877ed80485197ffefdb8c9923 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Thu, 13 Nov 2025 10:00:04 +0200 Subject: [PATCH 3/3] Add limitation note --- packages/capacitor/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/capacitor/README.md b/packages/capacitor/README.md index ab23df8c1..3a8ee4d17 100644 --- a/packages/capacitor/README.md +++ b/packages/capacitor/README.md @@ -82,7 +82,7 @@ const db = new PowerSyncDatabase({ - Encryption for native mobile platforms is not yet supported. - `PowerSyncDatabase.executeRaw` does not support results where multiple columns would have the same name in SQLite -- `PowerSyncDatabase.execute` has limited support on Android. The SQLCipher Android driver exposes queries and executions as separate APIs, so there is no single method that handles both. While `PowerSyncDatabase.execute` accepts both, on Android we treat a statement as a query only when the SQL starts with `select` (case-insensitive). +- `PowerSyncDatabase.execute` has limited support on Android. The SQLCipher Android driver exposes queries and executions as separate APIs, so there is no single method that handles both. While `PowerSyncDatabase.execute` accepts both, on Android we treat a statement as a query only when the SQL starts with `select` (case-insensitive). Queries such as `INSERT into customers (id, name) VALUES (uuid(), 'name') RETURNING *` do not work on Android. - Multiple tab support is not available for native Android and iOS targets. If you're not opening a second webview in your native app using something like `@jackobo/capacitor-webview`, you are unaffected by this. ## Examples