UNPKG

@react-native-replicache/react-native-expo-sqlite

Version:

> Plug-in React Native compatibility bindings for [Replicache](https://replicache.dev/).

113 lines (100 loc) 3.23 kB
import { ReplicacheGenericSQLiteTransaction } from "@react-native-replicache/replicache-generic-sqlite"; import * as SQLite from "expo-sqlite"; export class ReplicacheExpoSQLiteTransaction extends ReplicacheGenericSQLiteTransaction { private _tx: | Parameters< Parameters<SQLite.SQLiteDatabase["withExclusiveTransactionAsync"]>[0] >[0] | null = null; private _transactionCommittedSubscriptions = new Set<() => void>(); private _txCommitted = false; private _transactionEndedSubscriptions = new Set<{ resolve: () => void; reject: () => void; }>(); private _txEnded = false; constructor(private readonly db: SQLite.SQLiteDatabase) { super(); } // expo-sqlite doesn't support readonly public async start() { return await new Promise<void>((resolve, reject) => { let didResolve = false; try { this.db.withExclusiveTransactionAsync(async (tx) => { didResolve = true; this._tx = tx; resolve(); try { // expo-sqlite auto-commits our transaction when this callback ends. // Lets artificially keep it open until we commit. await this._waitForTransactionCommitted(); this._setTransactionEnded(false); } catch { this._setTransactionEnded(true); } }); } catch { if (!didResolve) { reject(new Error("Did not resolve")); } } }); } public async execute( sqlStatement: string, args?: (string | number | null)[] | undefined, ) { const tx = this.assertTransactionReady(); const statement = await tx.prepareAsync(sqlStatement); let allRows: any; let result: any; try { result = await statement.executeAsync(...(args ?? [])); allRows = await result.getAllAsync(); } finally { await statement.finalizeAsync(); } return { item: (idx: number) => allRows[idx], length: allRows.length, }; } public async commit() { // Transaction is committed automatically. this._txCommitted = true; for (const resolver of this._transactionCommittedSubscriptions) { resolver(); } this._transactionCommittedSubscriptions.clear(); } public waitForTransactionEnded() { if (this._txEnded) return; return new Promise<void>((resolve, reject) => { this._transactionEndedSubscriptions.add({ resolve, reject }); }); } private assertTransactionReady() { if (this._tx === null) throw new Error("Transaction is not ready."); if (this._txCommitted) throw new Error("Transaction already committed."); if (this._txEnded) throw new Error("Transaction already ended."); return this._tx; } private _waitForTransactionCommitted() { if (this._txCommitted) return; return new Promise<void>((resolve) => { this._transactionCommittedSubscriptions.add(resolve); }); } private _setTransactionEnded(errored = false) { this._txEnded = true; for (const { resolve, reject } of this._transactionEndedSubscriptions) { if (errored) { reject(); } else { resolve(); } } this._transactionEndedSubscriptions.clear(); } }