From 61e76117fd2070f6e5483684e79e6f757e3efdbd Mon Sep 17 00:00:00 2001 From: damlayildiz Date: Tue, 16 Dec 2025 17:32:25 +0100 Subject: [PATCH 001/129] chore: update comments --- src/hooks/useSqliteExecute.ts | 9 ++------- src/hooks/useSqliteSyncQuery.ts | 5 ++--- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/hooks/useSqliteExecute.ts b/src/hooks/useSqliteExecute.ts index 76cd9f0..3ac95e7 100644 --- a/src/hooks/useSqliteExecute.ts +++ b/src/hooks/useSqliteExecute.ts @@ -42,7 +42,9 @@ export function useSqliteExecute() { * * @param sql - The SQL statement to execute * @param params - Optional array of parameters to bind to the query + * * @returns Promise resolving to the QueryResult (rowsAffected, insertId, etc.) + * * @throws Error if execution fails (allows for try/catch in UI handler) */ const execute = useCallback( @@ -50,10 +52,7 @@ export function useSqliteExecute() { sql: string, params: any[] = [] ): Promise => { - // Safety check: if DB isn't ready, we throw. - // Usually buttons should be disabled if app is initializing, but this protects logic. if (!db) { - console.warn('[useSqliteExecute] Database is not open yet.'); return undefined; } @@ -61,18 +60,14 @@ export function useSqliteExecute() { setError(null); try { - // We await the result. SQLite (via op-sqlite) handles the serialization - // of concurrent writes thanks to WAL mode. const result = await db.execute(sql, params); return result; } catch (err) { const errorObj = err instanceof Error ? err : new Error('Execution failed'); - // Update local state for UI rendering setError(errorObj); - // Re-throw the error so the caller can handle it (e.g. show Alert/Toast) throw errorObj; } finally { setIsExecuting(false); diff --git a/src/hooks/useSqliteSyncQuery.ts b/src/hooks/useSqliteSyncQuery.ts index a53dc51..34db1e3 100644 --- a/src/hooks/useSqliteSyncQuery.ts +++ b/src/hooks/useSqliteSyncQuery.ts @@ -24,7 +24,6 @@ import { SQLiteSyncActionsContext } from '../SQLiteSyncActionsContext'; * @returns Object containing data, loading states, error, and a manual refresh function */ export function useSqliteSyncQuery(sql: string) { - console.log('NEW useSqliteSyncQuery called with SQL:', sql); const { db } = useContext(SQLiteDbContext); const { subscribe } = useContext(SQLiteSyncActionsContext); @@ -35,8 +34,8 @@ export function useSqliteSyncQuery(sql: string) { const [error, setError] = useState(null); // Refs for concurrency management - const lastQueryIdRef = useRef(0); // Tracks the sequence ID of the requests - const hasLoadedRef = useRef(false); // Tracks if initial load is complete + const lastQueryIdRef = useRef(0); + const hasLoadedRef = useRef(false); const executeQuery = useCallback(async () => { // Early return if DB isn't available yet (App startup safety) From 4e9f50cbff4e68355473b30b0bf6894a01ad67b0 Mon Sep 17 00:00:00 2001 From: damlayildiz Date: Tue, 16 Dec 2025 23:00:51 +0100 Subject: [PATCH 002/129] feat: implement useSqliteSyncQuery and useOnTableUpdate --- examples/sync-demo/src/App.tsx | 157 ++++++++++++++++------------- src/hooks/useOnTableUpdate.ts | 110 ++++++++++++++++++++ src/hooks/useSqliteSyncQuery.ts | 166 +++++++++++++++---------------- src/index.tsx | 6 ++ src/types/ReactiveQueryConfig.ts | 27 +++++ src/types/TableUpdateConfig.ts | 76 ++++++++++++++ 6 files changed, 390 insertions(+), 152 deletions(-) create mode 100644 src/hooks/useOnTableUpdate.ts create mode 100644 src/types/ReactiveQueryConfig.ts create mode 100644 src/types/TableUpdateConfig.ts diff --git a/examples/sync-demo/src/App.tsx b/examples/sync-demo/src/App.tsx index 3ce38e5..d3a91e6 100644 --- a/examples/sync-demo/src/App.tsx +++ b/examples/sync-demo/src/App.tsx @@ -14,9 +14,9 @@ import { useOnSqliteSync, useTriggerSqliteSync, useSqliteSyncQuery, + useOnTableUpdate, useSqliteDb, useSyncStatus, - useSqliteExecute, } from '@sqliteai/sqlite-sync-react-native'; import { SQLITE_CLOUD_CONNECTION_STRING, @@ -25,39 +25,71 @@ import { TABLE_NAME, } from '@env'; +/** + * Demo app showcasing the reactive hooks: + * + * 1. useSqliteSyncQuery - Table-level reactive queries using op-sqlite's reactiveExecute + * - Automatically re-runs when the table changes (transaction-based) + * - No manual refresh needed! + * + * 2. useOnTableUpdate - Row-level update notifications using op-sqlite's updateHook + * - Fires for individual INSERT/UPDATE/DELETE operations + * - Automatically fetches row data for you + * - Shows notifications when rows change + * + * 3. useOnSqliteSync - Sync completion notifications + * - Fires when cloud sync completes + */ function TestApp() { const { db, initError } = useSqliteDb(); const { isSyncReady, isSyncing, lastSyncTime, syncError } = useSyncStatus(); const [searchText, setSearchText] = useState(''); const [text, setText] = useState(''); + const [rowNotification, setRowNotification] = useState(null); const [syncNotification, setSyncNotification] = useState(null); const { triggerSync } = useTriggerSqliteSync(); - const { execute, isExecuting, error: executeError } = useSqliteExecute(); - - // 2. Dynamic SQL Generation - // We sanitize the input purely to prevent syntax crashes with apostrophes. - // In a real scenario, use parameterized queries if the hook supports them, - // or sanitize thoroughly. - const sanitizedSearch = searchText.replace(/'/g, "''"); - - const querySql = searchText.trim() - ? `SELECT * FROM ${TABLE_NAME} WHERE value LIKE '%${sanitizedSearch}%' ORDER BY created_at DESC;` - : `SELECT * FROM ${TABLE_NAME} ORDER BY created_at DESC;`; - - // Hook 1: useSqliteSyncQuery - Automatic data loading with offline-first support - // Loads immediately from local DB, auto-refreshes when sync brings changes + // Hook 1: useSqliteSyncQuery - Reactive query with table-level granularity + // Uses op-sqlite's reactiveExecute to automatically re-run when the table changes + // Changes are detected at the transaction level const { data: rows, isLoading, - isRefreshing, error, - refresh, - } = useSqliteSyncQuery<{ id: string; value: string; created_at: string }>( - querySql - ); + } = useSqliteSyncQuery<{ id: string; value: string; created_at: string }>({ + query: searchText.trim() + ? `SELECT * FROM ${TABLE_NAME} WHERE value LIKE ? ORDER BY created_at DESC` + : `SELECT * FROM ${TABLE_NAME} ORDER BY created_at DESC`, + arguments: searchText.trim() ? [`%${searchText}%`] : [], + fireOn: [{ table: TABLE_NAME }], + }); + + // Hook 2: useOnTableUpdate - Row-level update notifications + // Fires for individual row changes with automatic row data fetching + useOnTableUpdate<{ id: string; value: string; created_at: string }>({ + tables: [TABLE_NAME], + onUpdate: (data) => { + const operationName = + data.operation === 'INSERT' + ? 'added' + : data.operation === 'UPDATE' + ? 'updated' + : 'deleted'; - // Hook 2: useOnSqliteSync - Event listener for sync completion + if (data.row) { + setRowNotification( + `πŸ”” Row ${operationName}: "${data.row.value.substring(0, 20)}${ + data.row.value.length > 20 ? '...' : '' + }"` + ); + } else { + setRowNotification(`πŸ”” Row ${operationName}`); + } + setTimeout(() => setRowNotification(null), 2000); + }, + }); + + // Hook 3: useOnSqliteSync - Event listener for sync completion // Shows a notification when cloud data arrives (doesn't run on mount) useOnSqliteSync(() => { setSyncNotification('βœ… New data synced from cloud!'); @@ -65,17 +97,20 @@ function TestApp() { }); const addRow = async () => { - if (!text.trim()) return; + if (!text.trim() || !db) return; try { - await execute( - `INSERT INTO ${TABLE_NAME} (id, value) VALUES (cloudsync_uuid(), ?);`, - [text] - ); + // Use transaction to trigger reactive query updates + // Reactive queries only fire on committed transactions, not on direct execute + await db.transaction(async (tx) => { + await tx.execute( + `INSERT INTO ${TABLE_NAME} (id, value) VALUES (cloudsync_uuid(), ?);`, + [text] + ); + }); console.log('[sqlite-sync-demo] βœ… Row inserted:', text); setText(''); - // Manually refresh to show new row immediately - refresh(); + // No manual refresh needed - reactive query updates automatically when transaction commits! } catch (err) { console.error('[sqlite-sync-demo] Failed to insert row:', err); } @@ -110,7 +145,6 @@ function TestApp() { Query Error {error.message} -