-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexecuteSync.ts
More file actions
130 lines (114 loc) · 3.69 KB
/
Copy pathexecuteSync.ts
File metadata and controls
130 lines (114 loc) · 3.69 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
121
122
123
124
125
126
127
128
129
130
import type { DB, QueryResult } from '@op-engineering/op-sqlite';
import type { Logger } from '../common/logger';
import { decodeSQLiteText, extractFirstRowValue } from './cloudsyncResultUtils';
/**
* Extracts the number of received rows from a CloudSync query result.
*
* The result row contains a JSON string:
* {"send":{...},"receive":{"rows":N,"tables":["table1"]}}
*
* We only use receive.rows since polling is for downloading remote changes.
*/
const extractChangesFromResult = (result: QueryResult | undefined): number => {
const raw = decodeSQLiteText(extractFirstRowValue(result));
if (raw) {
try {
const parsed = JSON.parse(raw);
return typeof parsed?.receive?.rows === 'number'
? parsed.receive.rows
: 0;
} catch {
return 0;
}
}
return 0;
};
/**
* Options for executeSync
*/
export interface ExecuteSyncOptions {
/** Whether to wrap sync in a transaction (needed for reactive queries) */
useTransaction?: boolean;
/** Maximum number of sync attempts */
maxAttempts?: number;
/** Delay between attempts in ms */
attemptDelay?: number;
/**
* Use native retry logic (passes params to cloudsync_network_sync)
* - true: retry/delay happens in native code (better for background - won't be killed by OS)
* - false: retry/delay happens in JS (better for foreground - doesn't block write connection)
*/
useNativeRetry?: boolean;
}
/**
* Perform a sync operation with retry logic
*
* This is the core sync logic used by both:
* - useSyncManager hook (foreground)
* - executeBackgroundSync (background/terminated)
*
* @param db - Database instance
* @param logger - Logger instance
* @param options - Optional configuration
*
* @returns Number of changes synced
*/
export async function executeSync(
db: DB,
logger: Logger,
options?: ExecuteSyncOptions
): Promise<number> {
const {
useTransaction = false,
maxAttempts = 4,
attemptDelay = 1000,
useNativeRetry = false,
} = options ?? {};
let changes = 0;
if (useNativeRetry) {
/** NATIVE RETRY MODE */
// Retry/delay happens in native code - better for background (won't be killed by OS)
logger.info(
`🔄 Sync with native retry (max: ${maxAttempts}, delay: ${attemptDelay}ms)...`
);
const result = await db.execute('SELECT cloudsync_network_sync(?, ?);', [
attemptDelay,
maxAttempts,
]);
changes = extractChangesFromResult(result);
logger.info(`🔄 Sync result: ${changes} changes downloaded`);
} else {
/** JS RETRY MODE */
// Retry/delay in JS thread - better for foreground (doesn't block write connection)
for (let attempt = 0; attempt < maxAttempts; attempt++) {
logger.info(`🔄 Sync attempt ${attempt + 1}/${maxAttempts}...`);
let result: QueryResult | undefined;
if (useTransaction) {
// Wrap in transaction for reactive query compatibility
await db.transaction(async (tx) => {
result = await tx.execute('SELECT cloudsync_network_sync();');
});
} else {
result = await db.execute('SELECT cloudsync_network_sync();');
}
changes = extractChangesFromResult(result);
logger.info(
`🔄 Sync attempt ${attempt + 1} result: ${changes} changes downloaded`
);
if (changes > 0) {
break;
}
// Wait before next attempt (except on last attempt)
if (attempt < maxAttempts - 1) {
await new Promise<void>((resolve) => setTimeout(resolve, attemptDelay));
}
}
}
/** LOG RESULT */
if (changes > 0) {
logger.info(`✅ Sync completed: ${changes} changes downloaded`);
} else {
logger.info('✅ Sync completed: no changes downloaded');
}
return changes;
}