-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathuseOnTableUpdate.ts
More file actions
120 lines (111 loc) · 3.98 KB
/
useOnTableUpdate.ts
File metadata and controls
120 lines (111 loc) · 3.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import { useContext, useEffect, useRef } from 'react';
import { SQLiteDbContext } from '../../contexts/SQLiteDbContext';
import type { TableUpdateConfig } from '../../types/TableUpdateConfig';
import { useInternalLogger } from '../../core/common/useInternalLogger';
/**
* Hook that listens for row-level updates on specified tables using op-sqlite's updateHook.
*
* **Always uses the WRITE connection** to ensure update hooks see sync changes immediately.
*
* This hook provides fine-grained, row-level notifications when data changes in specified tables.
* Unlike reactive queries which re-run the entire query, this hook receives individual update events
* with the complete row data automatically fetched for you.
*
* Key Features:
* - **Row-level granularity**: Callback fires for each individual row change
* - **Operation details**: Know exactly what operation (INSERT/UPDATE/DELETE) occurred
* - **Automatic row fetching**: Row data is queried and provided in the callback
* - **Lightweight**: No full query re-execution, just individual row updates
* - **Real-time sync updates**: Automatically notified when cloud sync modifies data
* - **Uses write connection**: Sees all changes including sync operations
*
* @template T - The type of the row data
* @param config - Configuration with tables to monitor and callback function
*
* @example
* ```typescript
* interface Task {
* id: string;
* title: string;
* completed: boolean;
* }
*
* useOnTableUpdate<Task>({
* tables: ['tasks'],
* onUpdate: (data) => {
* console.log(`Table ${data.table} updated`);
* console.log(`Operation: ${data.operation}`);
*
* Row data is automatically provided and typed
* if (data.row) {
* console.log('Updated row:', data.row);
* Toast.show(`Task "${data.row.title}" was updated`);
* } else {
* console.log('Row was deleted');
* }
* },
* });
* ```
*
* @remarks
*
* The callback fires for ALL changes including:
* - Local changes (both transaction and direct execute)
* - Cloud sync updates (automatically wrapped in transactions)
*
* The hook automatically fetches row data using SQLite's internal rowid.
* For DELETE operations, `row` will be `null` since the row no longer exists.
*/
export function useOnTableUpdate<T = any>(config: TableUpdateConfig<T>) {
const { writeDb } = useContext(SQLiteDbContext);
const logger = useInternalLogger();
/** REFS */
// Store callback in ref to allow inline functions without causing infinite loops
const savedCallback = useRef(config.onUpdate);
const savedTables = useRef(config.tables);
/** KEEP REFS IN SYNC */
useEffect(() => {
savedCallback.current = config.onUpdate;
savedTables.current = config.tables;
}, [config.onUpdate, config.tables]);
/** UPDATE HOOK SUBSCRIPTION */
useEffect(() => {
if (!writeDb) return;
writeDb.updateHook(async (hookData) => {
// Only fire callback if the updated table is in our watch list
if (!savedTables.current.includes(hookData.table)) {
return;
}
/** FETCH ROW DATA */
let row: T | null = null;
// For DELETE operations, the row no longer exists, so row will be null
// For INSERT and UPDATE, we can fetch the row data
if (hookData.operation !== 'DELETE') {
try {
const result = await writeDb.execute(
`SELECT * FROM ${hookData.table} WHERE rowid = ?`,
[hookData.rowId]
);
row = (result.rows?.[0] as T) || null;
} catch (err) {
logger.warn(
`⚠️ Failed to fetch row data for table "${hookData.table}" (rowId: ${hookData.rowId}):`,
err
);
row = null;
}
}
/** INVOKE USER CALLBACK */
savedCallback.current({
table: hookData.table,
operation: hookData.operation,
rowId: hookData.rowId,
row,
});
});
/** CLEANUP */
return () => {
writeDb.updateHook(null);
};
}, [writeDb, logger]);
}