Skip to content

fix(expo-db-sqlite-persistence): resolve TS2322 when passing SQLiteDatabase to createExpoSQLitePersistence#1560

Open
PHILLIPS71 wants to merge 4 commits into
TanStack:mainfrom
PHILLIPS71:fix/expo-sqlite-database-type-compatibility
Open

fix(expo-db-sqlite-persistence): resolve TS2322 when passing SQLiteDatabase to createExpoSQLitePersistence#1560
PHILLIPS71 wants to merge 4 commits into
TanStack:mainfrom
PHILLIPS71:fix/expo-sqlite-database-type-compatibility

Conversation

@PHILLIPS71
Copy link
Copy Markdown

@PHILLIPS71 PHILLIPS71 commented May 27, 2026

🎯 Changes

The basic usage pattern for createExpoSQLitePersistence produced a TS2322 error:

import * as SQLite from 'expo-sqlite'
import { createExpoSQLitePersistence } from '@tanstack/expo-db-sqlite-persistence'

const database = await SQLite.openDatabaseAsync('database.sqlite')

export const persistence = createExpoSQLitePersistence({
  database,
  ^^^^^^^^
})
TS2322: Type SQLiteDatabase is not assignable to type ExpoSQLiteDatabaseLike
    Type SQLiteDatabase is not assignable to type ExpoSQLiteQueryable
        Types of property getAllAsync are incompatible.
            Type '{ <T>(source: string, params: SQLiteBindParams): Promise<T[]>; <T>(source: string, ...params SQLiteVariadicBindParams): Promise<T[]>; }' is not assignable to type '<T>(sql: string, params?: ExpoSQLiteBindParams | undefined) => Promise<readonly T[]>'.
                Types of parameters params and params are incompatible.
                    Type ExpoSQLiteBindParams | undefined is not assignable to type SQLiteBindParams
                        Type undefined is not assignable to type SQLiteBindParams
                        
expo.d.ts(7, 5): The expected type comes from property database which is declared here on type ExpoSQLitePersistenceOptions

The root cause was that ExpoSQLiteQueryable defined getAllAsync and runAsync with locally-defined loose types (ReadonlyArray<unknown> | Record<string, unknown>) for bind params. expo-sqlite's SQLiteDatabase defines these methods using strict TypeScript overloads, which TypeScript treats as incompatible with the optional params form using a different type, making SQLiteDatabase unassignable to ExpoSQLiteQueryable.

What changed

  • Replaced the locally-defined ExpoSQLiteBindParams and ExpoSQLiteRunResult types in the driver with direct imports of SQLiteBindParams and SQLiteRunResult from expo-sqlite, making ExpoSQLiteQueryable structurally compatible with the real SQLiteDatabase.

  • Changed withExclusiveTransactionAsync to match expo-sqlite's actual signature which returns Promise<void>, not a generic Promise<T>. The driver captures the transaction result via a local variable to preserve the return value.

  • Added normalizeBindValue to validate bind parameter types at the driver boundary, replacing the previous untyped spread. Also added normalizeRuntimeSqliteParams in the emulator runtime to explicitly reject Uint8Array values before they get JSON-serialized as {} over the HTTP bridge.

✅ Checklist

  • I have tested this code locally with pnpm test.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed a TypeScript type error when using Expo SQLite database instances with the persistence layer.
  • Refactor

    • Tightened validation and handling of SQL bind parameters for more predictable behavior.
    • Made transaction handling and runtime persistence behavior more consistent and reliable.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e19847b9-b633-44f4-8f49-ac0ce2dd3a2c

📥 Commits

Reviewing files that changed from the base of the PR and between 39f0e75 and 5fe3a0a.

📒 Files selected for processing (3)
  • packages/expo-db-sqlite-persistence/e2e/runtime-protocol.ts
  • packages/expo-db-sqlite-persistence/src/expo-sqlite-driver.ts
  • packages/expo-db-sqlite-persistence/tests/helpers/expo-emulator-database-factory.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/expo-db-sqlite-persistence/e2e/runtime-protocol.ts
  • packages/expo-db-sqlite-persistence/src/expo-sqlite-driver.ts

📝 Walkthrough

Walkthrough

This PR standardizes on expo-sqlite bind/run types, tightens runtime param validation (rejects unsupported types), switches transaction callbacks to void-only semantics, and propagates these changes across driver, tests, emulator runtime, and the E2E app.

Changes

Expo SQLite Type Unification and Bridge Parameter Normalization

Layer / File(s) Summary
Runtime Protocol Type Definitions
packages/expo-db-sqlite-persistence/e2e/runtime-protocol.ts
Introduces ExpoRuntimeSQLiteBindValue and ExpoRuntimeSQLiteBindParams; applies these types to db:run and tx:run command variants.
Driver Core Type Refactoring and Transaction Semantics
packages/expo-db-sqlite-persistence/src/expo-sqlite-driver.ts
Replaces local bind/run types with SQLiteBindParams/SQLiteBindValue/SQLiteRunResult from expo-sqlite; adds normalizeBindValue/normalizeParams enforcing allowed runtime types; retypes withExclusiveTransactionAsync to accept a Promise<void> callback and adapts transactionWithDriver to capture/return callback results appropriately.
Test Helper Type Alignment
packages/expo-db-sqlite-persistence/tests/helpers/expo-emulator-database-factory.ts, packages/expo-db-sqlite-persistence/tests/helpers/expo-sqlite-test-db.ts
Updates imports and signatures to use SQLiteBindParams, SQLiteBindValue, and SQLiteRunResult from expo-sqlite; make test helpers conditionally forward params only when provided; change transaction helper signatures to void-only callbacks.
E2E Runtime Bridge Parameter Normalization
packages/expo-db-sqlite-persistence/tests/helpers/expo-emulator-runtime.ts
Adds normalizeBindValue and normalizeRuntimeSqliteParams to validate/convert params before bridge transmission (throws on Uint8Array); updates createDatabase and transaction wrappers to send normalized params and use void-only transaction callbacks.
E2E App Integration
packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/App.tsx
Removes local normalizeSqliteParams; derives TransactionHandle from withExclusiveTransactionAsync types; simplifies command handlers to pass command.params directly when present and omit when undefined; updates transaction handling to void-only semantics.
Release Changeset
.changeset/six-ties-leave.md
Documents patch releases for @tanstack/expo-db-sqlite-persistence-e2e-app and @tanstack/expo-db-sqlite-persistence and notes a TS2322 fix for passing SQLiteDatabase into createExpoSQLitePersistence.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I’m a rabbit who types and hops on keys,
I nudged the binds and chased the type fleas.
Params now tidy, no mystery bytes,
Transactions quiet—no returned delights.
TS2322 sleeps—oh what peace! 🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and accurately summarizes the main change: fixing a TS2322 TypeScript error when passing SQLiteDatabase to createExpoSQLitePersistence, which is the primary objective.
Description check ✅ Passed The PR description comprehensively covers all required template sections: detailed explanation of the TS2322 error with code example, root cause analysis, specific changes made, testing confirmation, and changeset generation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/App.tsx (1)

180-183: ⚡ Quick win

Extract repeated optional-params dispatch into one helper.

The same params === undefined ? ... : ... branching is duplicated four times; centralizing it reduces drift risk between db and tx paths.

♻️ Proposed refactor
+type CommandParams = Extract<ExpoRuntimeCommand, { params?: unknown }>['params']
+
+function callWithOptionalParams<TResult>(
+  sql: string,
+  params: CommandParams,
+  withoutParams: (sql: string) => Promise<TResult>,
+  withParams: (sql: string, params: NonNullable<CommandParams>) => Promise<TResult>,
+): Promise<TResult> {
+  return params === undefined
+    ? withoutParams(sql)
+    : withParams(sql, params)
+}
@@
-          return command.params === undefined
-            ? database.getAllAsync(command.sql)
-            : database.getAllAsync(command.sql, command.params)
+          return callWithOptionalParams(
+            command.sql,
+            command.params,
+            (sql) => database.getAllAsync(sql),
+            (sql, params) => database.getAllAsync(sql, params),
+          )
@@
-          return command.params === undefined
-            ? database.runAsync(command.sql)
-            : database.runAsync(command.sql, command.params)
+          return callWithOptionalParams(
+            command.sql,
+            command.params,
+            (sql) => database.runAsync(sql),
+            (sql, params) => database.runAsync(sql, params),
+          )
@@
-          return command.params === undefined
-            ? transaction.transaction.getAllAsync(command.sql)
-            : transaction.transaction.getAllAsync(command.sql, command.params)
+          return callWithOptionalParams(
+            command.sql,
+            command.params,
+            (sql) => transaction.transaction.getAllAsync(sql),
+            (sql, params) => transaction.transaction.getAllAsync(sql, params),
+          )
@@
-          return command.params === undefined
-            ? transaction.transaction.runAsync(command.sql)
-            : transaction.transaction.runAsync(command.sql, command.params)
+          return callWithOptionalParams(
+            command.sql,
+            command.params,
+            (sql) => transaction.transaction.runAsync(sql),
+            (sql, params) => transaction.transaction.runAsync(sql, params),
+          )

As per coding guidelines, Extract common logic into utility functions when identical or near-identical code blocks appear in multiple places.

Also applies to: 189-192, 250-253, 259-262

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/App.tsx` around
lines 180 - 183, Extract the repeated optional-params branching into a single
helper (e.g., getAllWithOptionalParams) that takes an executor object (database
or transaction) and the command, and calls executor.getAllAsync(command.sql)
when command.params is undefined or executor.getAllAsync(command.sql,
command.params) otherwise; replace the four duplicate ternary uses (the blocks
referencing command.params and database.getAllAsync) with calls to this helper
so both db and tx code paths use the same implementation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/App.tsx`:
- Around line 180-183: Extract the repeated optional-params branching into a
single helper (e.g., getAllWithOptionalParams) that takes an executor object
(database or transaction) and the command, and calls
executor.getAllAsync(command.sql) when command.params is undefined or
executor.getAllAsync(command.sql, command.params) otherwise; replace the four
duplicate ternary uses (the blocks referencing command.params and
database.getAllAsync) with calls to this helper so both db and tx code paths use
the same implementation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9b45186f-368c-4078-9f7b-1ed5f469dd3b

📥 Commits

Reviewing files that changed from the base of the PR and between be656be and 39f0e75.

📒 Files selected for processing (7)
  • .changeset/six-ties-leave.md
  • packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/App.tsx
  • packages/expo-db-sqlite-persistence/e2e/runtime-protocol.ts
  • packages/expo-db-sqlite-persistence/src/expo-sqlite-driver.ts
  • packages/expo-db-sqlite-persistence/tests/helpers/expo-emulator-database-factory.ts
  • packages/expo-db-sqlite-persistence/tests/helpers/expo-emulator-runtime.ts
  • packages/expo-db-sqlite-persistence/tests/helpers/expo-sqlite-test-db.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant