From 7b15982ab006b35b3223af45efab7d9aae36f1f3 Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Wed, 27 May 2026 12:19:28 +1000 Subject: [PATCH 1/4] fix: resolve TS2322 when passing SQLiteDatabase to createExpoSQLitePersistence --- .../e2e/expo-runtime-app/App.tsx | 53 ++++---------- .../e2e/runtime-protocol.ts | 13 ++-- .../src/expo-sqlite-driver.ts | 54 ++++++++------ .../helpers/expo-emulator-database-factory.ts | 16 ++--- .../tests/helpers/expo-emulator-runtime.ts | 70 ++++++++++++++----- .../tests/helpers/expo-sqlite-test-db.ts | 31 ++++---- 6 files changed, 131 insertions(+), 106 deletions(-) diff --git a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/App.tsx b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/App.tsx index 616b733f7..3b61fd4aa 100644 --- a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/App.tsx +++ b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/App.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import { ScrollView, Text, View } from 'react-native' import * as SQLite from 'expo-sqlite' import { createCollection } from '@tanstack/db' @@ -14,20 +14,12 @@ import type { } from '../runtime-protocol' type DatabaseHandle = Awaited> -type TransactionHandleLike = { - execAsync: (sql: string) => Promise - getAllAsync: ( - sql: string, - params?: ReadonlyArray | Record, - ) => Promise> - runAsync: ( - sql: string, - params?: ReadonlyArray | Record, - ) => Promise -} +type TransactionHandle = Parameters< + Parameters[0] +>[0] type ActiveTransaction = { - transaction: TransactionHandleLike + transaction: TransactionHandle complete: { resolve: () => void reject: (error?: unknown) => void @@ -63,12 +55,6 @@ async function postJson( } } -function normalizeSqliteParams( - params?: ReadonlyArray | Record, -): ReadonlyArray | Record | undefined { - return params === undefined ? undefined : params -} - async function closeDatabaseHandle(database: DatabaseHandle): Promise { const closableDatabase = database as DatabaseHandle & { closeAsync?: () => Promise @@ -129,14 +115,7 @@ export default function App() { ): Promise => { const database = await SQLite.openDatabaseAsync(databaseName) const collectionId = `expo-runtime-smoke-${Date.now().toString(36)}` - const persistence = createExpoSQLitePersistence< - { - id: string - title: string - score: number - }, - string - >({ + const persistence = createExpoSQLitePersistence({ database, }) const collection = createCollection( @@ -198,20 +177,18 @@ export default function App() { command.databaseId, command.databaseName, ) - const params = normalizeSqliteParams(command.params) - return params === undefined + return command.params === undefined ? database.getAllAsync(command.sql) - : database.getAllAsync(command.sql, params) + : database.getAllAsync(command.sql, command.params) } case `db:run`: { const database = await getDatabase( command.databaseId, command.databaseName, ) - const params = normalizeSqliteParams(command.params) - return params === undefined + return command.params === undefined ? database.runAsync(command.sql) - : database.runAsync(command.sql, params) + : database.runAsync(command.sql, command.params) } case `db:close`: { const database = databaseHandles.get(command.databaseId) @@ -270,20 +247,18 @@ export default function App() { if (!transaction) { throw new Error(`Unknown transaction id`) } - const params = normalizeSqliteParams(command.params) - return params === undefined + return command.params === undefined ? transaction.transaction.getAllAsync(command.sql) - : transaction.transaction.getAllAsync(command.sql, params) + : transaction.transaction.getAllAsync(command.sql, command.params) } case `tx:run`: { const transaction = activeTransactions.get(command.transactionId) if (!transaction) { throw new Error(`Unknown transaction id`) } - const params = normalizeSqliteParams(command.params) - return params === undefined + return command.params === undefined ? transaction.transaction.runAsync(command.sql) - : transaction.transaction.runAsync(command.sql, params) + : transaction.transaction.runAsync(command.sql, command.params) } case `tx:commit`: { const transaction = activeTransactions.get(command.transactionId) diff --git a/packages/expo-db-sqlite-persistence/e2e/runtime-protocol.ts b/packages/expo-db-sqlite-persistence/e2e/runtime-protocol.ts index 21f672e5c..75352825a 100644 --- a/packages/expo-db-sqlite-persistence/e2e/runtime-protocol.ts +++ b/packages/expo-db-sqlite-persistence/e2e/runtime-protocol.ts @@ -1,3 +1,8 @@ +export type ExpoRuntimeSQLiteBindValue = string | number | boolean | null +export type ExpoRuntimeSQLiteBindParams = + | ReadonlyArray + | Record + export type ExpoRuntimeCommand = | { id: string @@ -12,7 +17,7 @@ export type ExpoRuntimeCommand = databaseId: string databaseName: string sql: string - params?: ReadonlyArray | Record + params?: ExpoRuntimeSQLiteBindParams } | { id: string @@ -20,7 +25,7 @@ export type ExpoRuntimeCommand = databaseId: string databaseName: string sql: string - params?: ReadonlyArray | Record + params?: ExpoRuntimeSQLiteBindParams } | { id: string @@ -46,14 +51,14 @@ export type ExpoRuntimeCommand = type: `tx:getAll` transactionId: string sql: string - params?: ReadonlyArray | Record + params?: ExpoRuntimeSQLiteBindParams } | { id: string type: `tx:run` transactionId: string sql: string - params?: ReadonlyArray | Record + params?: ExpoRuntimeSQLiteBindParams } | { id: string diff --git a/packages/expo-db-sqlite-persistence/src/expo-sqlite-driver.ts b/packages/expo-db-sqlite-persistence/src/expo-sqlite-driver.ts index e85df20aa..e7e1b1a57 100644 --- a/packages/expo-db-sqlite-persistence/src/expo-sqlite-driver.ts +++ b/packages/expo-db-sqlite-persistence/src/expo-sqlite-driver.ts @@ -1,33 +1,29 @@ import { InvalidPersistedCollectionConfigError } from '@tanstack/db-sqlite-persistence-core' import type { SQLiteDriver } from '@tanstack/db-sqlite-persistence-core' - -export type ExpoSQLiteBindParams = - | ReadonlyArray - | Record - -export type ExpoSQLiteRunResult = { - changes: number - lastInsertRowId: number -} +import type { + SQLiteBindParams, + SQLiteBindValue, + SQLiteRunResult, +} from 'expo-sqlite' export type ExpoSQLiteQueryable = { execAsync: (sql: string) => Promise getAllAsync: ( sql: string, - params?: ExpoSQLiteBindParams, + params?: SQLiteBindParams, ) => Promise> runAsync: ( sql: string, - params?: ExpoSQLiteBindParams, - ) => Promise + params?: SQLiteBindParams, + ) => Promise } export type ExpoSQLiteTransaction = ExpoSQLiteQueryable export type ExpoSQLiteDatabaseLike = ExpoSQLiteQueryable & { - withExclusiveTransactionAsync: ( - task: (transaction: ExpoSQLiteTransaction) => Promise, - ) => Promise + withExclusiveTransactionAsync: ( + task: (transaction: ExpoSQLiteTransaction) => Promise, + ) => Promise closeAsync?: () => Promise } @@ -144,10 +140,12 @@ export class ExpoSQLiteDriver implements SQLiteDriver { ): Promise { return this.enqueue(async () => { const database = await this.getDatabase() - return database.withExclusiveTransactionAsync(async (transaction) => { + let result: T | undefined + await database.withExclusiveTransactionAsync(async (transaction) => { const transactionDriver = this.createTransactionDriver(transaction) - return fn(transactionDriver) + result = await fn(transactionDriver) }) + return result as T }) } @@ -225,10 +223,24 @@ export class ExpoSQLiteDriver implements SQLiteDriver { } } -function normalizeParams( - params: ReadonlyArray, -): ExpoSQLiteBindParams | undefined { - return params.length > 0 ? [...params] : undefined +function normalizeBindValue(value: unknown): SQLiteBindValue { + if ( + value === null || + typeof value === `string` || + typeof value === `number` || + typeof value === `boolean` || + value instanceof Uint8Array + ) { + return value + } + + throw new TypeError( + `Expo SQLite bind parameters must be strings, numbers, booleans, null, or Uint8Array values`, + ) +} + +function normalizeParams(params: ReadonlyArray): SQLiteBindParams { + return params.map(normalizeBindValue) } export function createExpoSQLiteDriver( diff --git a/packages/expo-db-sqlite-persistence/tests/helpers/expo-emulator-database-factory.ts b/packages/expo-db-sqlite-persistence/tests/helpers/expo-emulator-database-factory.ts index de347ee07..faa323238 100644 --- a/packages/expo-db-sqlite-persistence/tests/helpers/expo-emulator-database-factory.ts +++ b/packages/expo-db-sqlite-persistence/tests/helpers/expo-emulator-database-factory.ts @@ -3,10 +3,8 @@ import type { ExpoSQLiteTestDatabase, ExpoSQLiteTestDatabaseFactory, } from './expo-sqlite-test-db' -import type { - ExpoSQLiteBindParams, - ExpoSQLiteTransaction, -} from '../../src/expo-sqlite-driver' +import type { SQLiteBindParams } from 'expo-sqlite' +import type { ExpoSQLiteTransaction } from '../../src/expo-sqlite-driver' function resolvePlatform(): `ios` | `android` { const platform = process.env.TANSTACK_DB_EXPO_RUNTIME_PLATFORM?.trim() @@ -46,13 +44,13 @@ export function createMobileSQLiteTestDatabaseFactory(): ExpoSQLiteTestDatabaseF execAsync: async (sql: string) => { await (await getDatabase()).execAsync(sql) }, - getAllAsync: async (sql: string, params?: ExpoSQLiteBindParams) => + getAllAsync: async (sql: string, params?: SQLiteBindParams) => (await getDatabase()).getAllAsync(sql, params), - runAsync: async (sql: string, params?: ExpoSQLiteBindParams) => + runAsync: async (sql: string, params?: SQLiteBindParams) => (await getDatabase()).runAsync(sql, params), - withExclusiveTransactionAsync: async ( - task: (transaction: ExpoSQLiteTransaction) => Promise, - ): Promise => + withExclusiveTransactionAsync: async ( + task: (transaction: ExpoSQLiteTransaction) => Promise, + ): Promise => (await getDatabase()).withExclusiveTransactionAsync(task), closeAsync: async () => { if (!databasePromise) { diff --git a/packages/expo-db-sqlite-persistence/tests/helpers/expo-emulator-runtime.ts b/packages/expo-db-sqlite-persistence/tests/helpers/expo-emulator-runtime.ts index 85cf208a6..6ba301114 100644 --- a/packages/expo-db-sqlite-persistence/tests/helpers/expo-emulator-runtime.ts +++ b/packages/expo-db-sqlite-persistence/tests/helpers/expo-emulator-runtime.ts @@ -7,15 +7,20 @@ import { spawn } from 'node:child_process' import type { IncomingMessage, ServerResponse } from 'node:http' import type { ChildProcessWithoutNullStreams } from 'node:child_process' import type { - ExpoSQLiteBindParams, + SQLiteBindParams, + SQLiteBindValue, + SQLiteRunResult, +} from 'expo-sqlite' +import type { ExpoSQLiteDatabaseLike, - ExpoSQLiteRunResult, ExpoSQLiteTransaction, } from '../../src/expo-sqlite-driver' import type { ExpoRuntimeCommand, ExpoRuntimeCommandResult, ExpoRuntimeRegistration, + ExpoRuntimeSQLiteBindParams, + ExpoRuntimeSQLiteBindValue, ExpoRuntimeSmokeTestResult, } from '../../e2e/runtime-protocol' @@ -189,6 +194,34 @@ function parseCommandOverride(rawCommand: string): Array { .filter((part) => part.length > 0) } +function normalizeRuntimeSqliteValue( + value: SQLiteBindValue, +): ExpoRuntimeSQLiteBindValue { + if (value instanceof Uint8Array) { + throw new TypeError(`Expo runtime bridge does not support binary SQLite params`) + } + + return value +} + +function normalizeRuntimeSqliteParams( + params?: SQLiteBindParams, +): ExpoRuntimeSQLiteBindParams | undefined { + if (params === undefined) { + return undefined + } + + if (Array.isArray(params)) { + return params.map(normalizeRuntimeSqliteValue) + } + + const normalized: Record = {} + for (const [key, value] of Object.entries(params)) { + normalized[key] = normalizeRuntimeSqliteValue(value) + } + return normalized +} + class ExpoEmulatorRuntime { private static readonly instances = new Map< RuntimePlatform, @@ -239,29 +272,29 @@ class ExpoEmulatorRuntime { }, getAllAsync: async ( sql: string, - params?: ExpoSQLiteBindParams, + params?: SQLiteBindParams, ): Promise> => (await this.sendCommand({ type: `db:getAll`, databaseId, databaseName, sql, - params, + params: normalizeRuntimeSqliteParams(params), })) as ReadonlyArray, runAsync: async ( sql: string, - params?: ExpoSQLiteBindParams, - ): Promise => + params?: SQLiteBindParams, + ): Promise => (await this.sendCommand({ type: `db:run`, databaseId, databaseName, sql, - params, - })) as ExpoSQLiteRunResult, - withExclusiveTransactionAsync: async ( - task: (transaction: ExpoSQLiteTransaction) => Promise, - ): Promise => { + params: normalizeRuntimeSqliteParams(params), + })) as SQLiteRunResult, + withExclusiveTransactionAsync: async ( + task: (transaction: ExpoSQLiteTransaction) => Promise, + ): Promise => { const transactionId = randomUUID() await this.sendCommand({ type: `tx:start`, @@ -279,33 +312,32 @@ class ExpoEmulatorRuntime { }, getAllAsync: async ( sql: string, - params?: ExpoSQLiteBindParams, + params?: SQLiteBindParams, ): Promise> => (await this.sendCommand({ type: `tx:getAll`, transactionId, sql, - params, + params: normalizeRuntimeSqliteParams(params), })) as ReadonlyArray, runAsync: async ( sql: string, - params?: ExpoSQLiteBindParams, - ): Promise => + params?: SQLiteBindParams, + ): Promise => (await this.sendCommand({ type: `tx:run`, transactionId, sql, - params, - })) as ExpoSQLiteRunResult, + params: normalizeRuntimeSqliteParams(params), + })) as SQLiteRunResult, } try { - const result = await task(transaction) + await task(transaction) await this.sendCommand({ type: `tx:commit`, transactionId, }) - return result } catch (error) { await this.sendCommand({ type: `tx:rollback`, diff --git a/packages/expo-db-sqlite-persistence/tests/helpers/expo-sqlite-test-db.ts b/packages/expo-db-sqlite-persistence/tests/helpers/expo-sqlite-test-db.ts index 70cc5ede5..2108a452b 100644 --- a/packages/expo-db-sqlite-persistence/tests/helpers/expo-sqlite-test-db.ts +++ b/packages/expo-db-sqlite-persistence/tests/helpers/expo-sqlite-test-db.ts @@ -1,8 +1,11 @@ import BetterSqlite3 from 'better-sqlite3' import type { - ExpoSQLiteBindParams, + SQLiteBindParams, + SQLiteBindValue, + SQLiteRunResult, +} from 'expo-sqlite' +import type { ExpoSQLiteDatabaseLike, - ExpoSQLiteRunResult, ExpoSQLiteTransaction, } from '../../src/expo-sqlite-driver' @@ -23,7 +26,7 @@ declare global { function normalizeRunResult( result: BetterSqlite3.RunResult, -): ExpoSQLiteRunResult { +): SQLiteRunResult { return { changes: result.changes, lastInsertRowId: @@ -34,15 +37,15 @@ function normalizeRunResult( } function hasNamedParameters( - params: ExpoSQLiteBindParams | undefined, -): params is Record { + params: SQLiteBindParams | undefined, +): params is Record { return params !== undefined && !Array.isArray(params) } function executeAll( database: InstanceType, sql: string, - params?: ExpoSQLiteBindParams, + params?: SQLiteBindParams, ): ReadonlyArray { const statement = database.prepare(sql) @@ -60,8 +63,8 @@ function executeAll( function executeRun( database: InstanceType, sql: string, - params?: ExpoSQLiteBindParams, -): ExpoSQLiteRunResult { + params?: SQLiteBindParams, +): SQLiteRunResult { const statement = database.prepare(sql) const result = params === undefined @@ -83,13 +86,13 @@ function createTransactionHandle( }, getAllAsync: ( sql: string, - params?: ExpoSQLiteBindParams, + params?: SQLiteBindParams, ): Promise> => Promise.resolve(executeAll(database, sql, params)), runAsync: ( sql: string, - params?: ExpoSQLiteBindParams, - ): Promise => + params?: SQLiteBindParams, + ): Promise => Promise.resolve(executeRun(database, sql, params)), } } @@ -123,13 +126,13 @@ export function createExpoSQLiteTestDatabase(options: { }, getAllAsync: async ( sql: string, - params?: ExpoSQLiteBindParams, + params?: SQLiteBindParams, ): Promise> => enqueue(() => executeAll(nativeDatabase, sql, params)), runAsync: async ( sql: string, - params?: ExpoSQLiteBindParams, - ): Promise => + params?: SQLiteBindParams, + ): Promise => enqueue(() => executeRun(nativeDatabase, sql, params)), withExclusiveTransactionAsync: async ( task: (transaction: ExpoSQLiteTransaction) => Promise, From 4fe12c25f81a60635dfd80df854c397cc160063e Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Wed, 27 May 2026 12:31:34 +1000 Subject: [PATCH 2/4] style: prettier --- .../expo-db-sqlite-persistence/src/expo-sqlite-driver.ts | 5 +---- .../tests/helpers/expo-emulator-runtime.ts | 4 +++- .../tests/helpers/expo-sqlite-test-db.ts | 4 +--- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/expo-db-sqlite-persistence/src/expo-sqlite-driver.ts b/packages/expo-db-sqlite-persistence/src/expo-sqlite-driver.ts index e7e1b1a57..68da7b8a8 100644 --- a/packages/expo-db-sqlite-persistence/src/expo-sqlite-driver.ts +++ b/packages/expo-db-sqlite-persistence/src/expo-sqlite-driver.ts @@ -12,10 +12,7 @@ export type ExpoSQLiteQueryable = { sql: string, params?: SQLiteBindParams, ) => Promise> - runAsync: ( - sql: string, - params?: SQLiteBindParams, - ) => Promise + runAsync: (sql: string, params?: SQLiteBindParams) => Promise } export type ExpoSQLiteTransaction = ExpoSQLiteQueryable diff --git a/packages/expo-db-sqlite-persistence/tests/helpers/expo-emulator-runtime.ts b/packages/expo-db-sqlite-persistence/tests/helpers/expo-emulator-runtime.ts index 6ba301114..4da4f01c0 100644 --- a/packages/expo-db-sqlite-persistence/tests/helpers/expo-emulator-runtime.ts +++ b/packages/expo-db-sqlite-persistence/tests/helpers/expo-emulator-runtime.ts @@ -198,7 +198,9 @@ function normalizeRuntimeSqliteValue( value: SQLiteBindValue, ): ExpoRuntimeSQLiteBindValue { if (value instanceof Uint8Array) { - throw new TypeError(`Expo runtime bridge does not support binary SQLite params`) + throw new TypeError( + `Expo runtime bridge does not support binary SQLite params`, + ) } return value diff --git a/packages/expo-db-sqlite-persistence/tests/helpers/expo-sqlite-test-db.ts b/packages/expo-db-sqlite-persistence/tests/helpers/expo-sqlite-test-db.ts index 2108a452b..d4f27c400 100644 --- a/packages/expo-db-sqlite-persistence/tests/helpers/expo-sqlite-test-db.ts +++ b/packages/expo-db-sqlite-persistence/tests/helpers/expo-sqlite-test-db.ts @@ -24,9 +24,7 @@ declare global { | undefined } -function normalizeRunResult( - result: BetterSqlite3.RunResult, -): SQLiteRunResult { +function normalizeRunResult(result: BetterSqlite3.RunResult): SQLiteRunResult { return { changes: result.changes, lastInsertRowId: From 39f0e75b99419e75d2141cc01006e945b6017b6f Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Wed, 27 May 2026 12:34:49 +1000 Subject: [PATCH 3/4] chore: changeset --- .changeset/six-ties-leave.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/six-ties-leave.md diff --git a/.changeset/six-ties-leave.md b/.changeset/six-ties-leave.md new file mode 100644 index 000000000..c6c48af3b --- /dev/null +++ b/.changeset/six-ties-leave.md @@ -0,0 +1,6 @@ +--- +'@tanstack/expo-db-sqlite-persistence-e2e-app': patch +'@tanstack/expo-db-sqlite-persistence': patch +--- + +fixes a TS2322 type error when passing a SQLiteDatabase from expo-sqlite to createExpoSQLitePersistence From 5fe3a0aa62ac13eb82b14ba51b280cc5e1eae5b5 Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Wed, 27 May 2026 16:50:59 +1000 Subject: [PATCH 4/4] fix: mirror expo-sqlite call signatures --- .../e2e/runtime-protocol.ts | 2 +- .../src/expo-sqlite-driver.ts | 13 ++++++++----- .../tests/helpers/expo-emulator-database-factory.ts | 8 ++++++-- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/expo-db-sqlite-persistence/e2e/runtime-protocol.ts b/packages/expo-db-sqlite-persistence/e2e/runtime-protocol.ts index 75352825a..7377b09a2 100644 --- a/packages/expo-db-sqlite-persistence/e2e/runtime-protocol.ts +++ b/packages/expo-db-sqlite-persistence/e2e/runtime-protocol.ts @@ -1,6 +1,6 @@ export type ExpoRuntimeSQLiteBindValue = string | number | boolean | null export type ExpoRuntimeSQLiteBindParams = - | ReadonlyArray + | Array | Record export type ExpoRuntimeCommand = diff --git a/packages/expo-db-sqlite-persistence/src/expo-sqlite-driver.ts b/packages/expo-db-sqlite-persistence/src/expo-sqlite-driver.ts index 68da7b8a8..c3bc25a1d 100644 --- a/packages/expo-db-sqlite-persistence/src/expo-sqlite-driver.ts +++ b/packages/expo-db-sqlite-persistence/src/expo-sqlite-driver.ts @@ -8,11 +8,14 @@ import type { export type ExpoSQLiteQueryable = { execAsync: (sql: string) => Promise - getAllAsync: ( - sql: string, - params?: SQLiteBindParams, - ) => Promise> - runAsync: (sql: string, params?: SQLiteBindParams) => Promise + getAllAsync: { + (sql: string, params: SQLiteBindParams): Promise> + (sql: string): Promise> + } + runAsync: { + (sql: string, params: SQLiteBindParams): Promise + (sql: string): Promise + } } export type ExpoSQLiteTransaction = ExpoSQLiteQueryable diff --git a/packages/expo-db-sqlite-persistence/tests/helpers/expo-emulator-database-factory.ts b/packages/expo-db-sqlite-persistence/tests/helpers/expo-emulator-database-factory.ts index faa323238..2f68fbd98 100644 --- a/packages/expo-db-sqlite-persistence/tests/helpers/expo-emulator-database-factory.ts +++ b/packages/expo-db-sqlite-persistence/tests/helpers/expo-emulator-database-factory.ts @@ -45,9 +45,13 @@ export function createMobileSQLiteTestDatabaseFactory(): ExpoSQLiteTestDatabaseF await (await getDatabase()).execAsync(sql) }, getAllAsync: async (sql: string, params?: SQLiteBindParams) => - (await getDatabase()).getAllAsync(sql, params), + params !== undefined + ? (await getDatabase()).getAllAsync(sql, params) + : (await getDatabase()).getAllAsync(sql), runAsync: async (sql: string, params?: SQLiteBindParams) => - (await getDatabase()).runAsync(sql, params), + params !== undefined + ? (await getDatabase()).runAsync(sql, params) + : (await getDatabase()).runAsync(sql), withExclusiveTransactionAsync: async ( task: (transaction: ExpoSQLiteTransaction) => Promise, ): Promise =>