@tursodatabase/sync-react-native 0.5.0-pre.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +117 -0
- package/android/CMakeLists.txt +53 -0
- package/android/build.gradle +84 -0
- package/android/cpp-adapter.cpp +49 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/turso/sync/reactnative/TursoBridge.java +44 -0
- package/android/src/main/java/com/turso/sync/reactnative/TursoModule.java +82 -0
- package/android/src/main/java/com/turso/sync/reactnative/TursoPackage.java +29 -0
- package/cpp/TursoConnectionHostObject.cpp +179 -0
- package/cpp/TursoConnectionHostObject.h +52 -0
- package/cpp/TursoDatabaseHostObject.cpp +98 -0
- package/cpp/TursoDatabaseHostObject.h +49 -0
- package/cpp/TursoHostObject.cpp +561 -0
- package/cpp/TursoHostObject.h +24 -0
- package/cpp/TursoStatementHostObject.cpp +414 -0
- package/cpp/TursoStatementHostObject.h +65 -0
- package/cpp/TursoSyncChangesHostObject.cpp +41 -0
- package/cpp/TursoSyncChangesHostObject.h +52 -0
- package/cpp/TursoSyncDatabaseHostObject.cpp +328 -0
- package/cpp/TursoSyncDatabaseHostObject.h +61 -0
- package/cpp/TursoSyncIoItemHostObject.cpp +304 -0
- package/cpp/TursoSyncIoItemHostObject.h +52 -0
- package/cpp/TursoSyncOperationHostObject.cpp +168 -0
- package/cpp/TursoSyncOperationHostObject.h +53 -0
- package/ios/TursoModule.h +8 -0
- package/ios/TursoModule.mm +95 -0
- package/lib/commonjs/Database.js +445 -0
- package/lib/commonjs/Database.js.map +1 -0
- package/lib/commonjs/Statement.js +339 -0
- package/lib/commonjs/Statement.js.map +1 -0
- package/lib/commonjs/index.js +229 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/internal/asyncOperation.js +124 -0
- package/lib/commonjs/internal/asyncOperation.js.map +1 -0
- package/lib/commonjs/internal/ioProcessor.js +315 -0
- package/lib/commonjs/internal/ioProcessor.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/types.js +133 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/module/Database.js +441 -0
- package/lib/module/Database.js.map +1 -0
- package/lib/module/Statement.js +335 -0
- package/lib/module/Statement.js.map +1 -0
- package/lib/module/index.js +205 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/internal/asyncOperation.js +116 -0
- package/lib/module/internal/asyncOperation.js.map +1 -0
- package/lib/module/internal/ioProcessor.js +309 -0
- package/lib/module/internal/ioProcessor.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +163 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/Database.d.ts +140 -0
- package/lib/typescript/Database.d.ts.map +1 -0
- package/lib/typescript/Statement.d.ts +105 -0
- package/lib/typescript/Statement.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +175 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/internal/asyncOperation.d.ts +39 -0
- package/lib/typescript/internal/asyncOperation.d.ts.map +1 -0
- package/lib/typescript/internal/ioProcessor.d.ts +48 -0
- package/lib/typescript/internal/ioProcessor.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +316 -0
- package/lib/typescript/types.d.ts.map +1 -0
- package/package.json +97 -0
- package/src/Database.ts +480 -0
- package/src/Statement.ts +372 -0
- package/src/index.ts +240 -0
- package/src/internal/asyncOperation.ts +147 -0
- package/src/internal/ioProcessor.ts +328 -0
- package/src/types.ts +391 -0
- package/turso-sync-react-native.podspec +56 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async Operation Driver
|
|
3
|
+
*
|
|
4
|
+
* Drives async operations returned by sync SDK-KIT methods.
|
|
5
|
+
* This is where ALL the async logic lives - the C++ layer is just a thin bridge.
|
|
6
|
+
*
|
|
7
|
+
* Key responsibilities:
|
|
8
|
+
* - Call resume() in a loop until DONE
|
|
9
|
+
* - When IO is needed, process all pending IO items
|
|
10
|
+
* - Extract and return the final result
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
NativeSyncOperation,
|
|
15
|
+
NativeSyncDatabase,
|
|
16
|
+
NativeConnection,
|
|
17
|
+
NativeSyncChanges,
|
|
18
|
+
SyncStats,
|
|
19
|
+
} from '../types';
|
|
20
|
+
import { TursoStatus, SyncOperationResultType } from '../types';
|
|
21
|
+
import { processIoItem } from './ioProcessor';
|
|
22
|
+
import type { IoContext } from './ioProcessor';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Drive an async operation to completion
|
|
26
|
+
*
|
|
27
|
+
* @param operation - The native operation to drive
|
|
28
|
+
* @param database - The native sync database (for IO queue access)
|
|
29
|
+
* @param context - IO context with auth and URL information
|
|
30
|
+
* @returns Promise that resolves when operation completes
|
|
31
|
+
*/
|
|
32
|
+
export async function driveOperation<T = void>(
|
|
33
|
+
operation: NativeSyncOperation,
|
|
34
|
+
database: NativeSyncDatabase,
|
|
35
|
+
context: IoContext
|
|
36
|
+
): Promise<T> {
|
|
37
|
+
while (true) {
|
|
38
|
+
// Resume the operation
|
|
39
|
+
const status = operation.resume();
|
|
40
|
+
|
|
41
|
+
// Operation completed successfully
|
|
42
|
+
if (status === TursoStatus.DONE) {
|
|
43
|
+
// Extract and return the result based on result type
|
|
44
|
+
const resultKind = operation.resultKind();
|
|
45
|
+
|
|
46
|
+
switch (resultKind) {
|
|
47
|
+
case SyncOperationResultType.NONE:
|
|
48
|
+
return undefined as T;
|
|
49
|
+
|
|
50
|
+
case SyncOperationResultType.CONNECTION:
|
|
51
|
+
return operation.extractConnection() as T;
|
|
52
|
+
|
|
53
|
+
case SyncOperationResultType.CHANGES:
|
|
54
|
+
return operation.extractChanges() as T;
|
|
55
|
+
|
|
56
|
+
case SyncOperationResultType.STATS:
|
|
57
|
+
return operation.extractStats() as T;
|
|
58
|
+
|
|
59
|
+
default:
|
|
60
|
+
throw new Error(`Unknown result type: ${resultKind}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Operation needs IO
|
|
65
|
+
if (status === TursoStatus.IO) {
|
|
66
|
+
// Process all pending IO items
|
|
67
|
+
await processIoQueue(database, context);
|
|
68
|
+
|
|
69
|
+
// Step callbacks after IO processing
|
|
70
|
+
database.ioStepCallbacks();
|
|
71
|
+
|
|
72
|
+
// Continue resume loop
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Any other status is an error
|
|
77
|
+
throw new Error(`Unexpected status from operation.resume(): ${status}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Process all pending IO items in the queue
|
|
83
|
+
*
|
|
84
|
+
* @param database - The native sync database
|
|
85
|
+
* @param context - IO context with auth and URL information
|
|
86
|
+
*/
|
|
87
|
+
async function processIoQueue(database: NativeSyncDatabase, context: IoContext): Promise<void> {
|
|
88
|
+
const promises: Promise<void>[] = [];
|
|
89
|
+
|
|
90
|
+
// Take all available IO items from the queue
|
|
91
|
+
while (true) {
|
|
92
|
+
const ioItem = database.ioTakeItem();
|
|
93
|
+
if (!ioItem) {
|
|
94
|
+
break; // No more items
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Process each item (potentially in parallel)
|
|
98
|
+
promises.push(processIoItem(ioItem, context));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Wait for all IO operations to complete
|
|
102
|
+
await Promise.all(promises);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Helper type for operations that return connections
|
|
107
|
+
*/
|
|
108
|
+
export async function driveConnectionOperation(
|
|
109
|
+
operation: NativeSyncOperation,
|
|
110
|
+
database: NativeSyncDatabase,
|
|
111
|
+
context: IoContext
|
|
112
|
+
): Promise<NativeConnection> {
|
|
113
|
+
return driveOperation<NativeConnection>(operation, database, context);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Helper type for operations that return changes
|
|
118
|
+
*/
|
|
119
|
+
export async function driveChangesOperation(
|
|
120
|
+
operation: NativeSyncOperation,
|
|
121
|
+
database: NativeSyncDatabase,
|
|
122
|
+
context: IoContext
|
|
123
|
+
): Promise<NativeSyncChanges | null> {
|
|
124
|
+
return driveOperation<NativeSyncChanges | null>(operation, database, context);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Helper type for operations that return stats
|
|
129
|
+
*/
|
|
130
|
+
export async function driveStatsOperation(
|
|
131
|
+
operation: NativeSyncOperation,
|
|
132
|
+
database: NativeSyncDatabase,
|
|
133
|
+
context: IoContext
|
|
134
|
+
): Promise<SyncStats> {
|
|
135
|
+
return driveOperation<SyncStats>(operation, database, context);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Helper type for operations that return void
|
|
140
|
+
*/
|
|
141
|
+
export async function driveVoidOperation(
|
|
142
|
+
operation: NativeSyncOperation,
|
|
143
|
+
database: NativeSyncDatabase,
|
|
144
|
+
context: IoContext
|
|
145
|
+
): Promise<void> {
|
|
146
|
+
return driveOperation<void>(operation, database, context);
|
|
147
|
+
}
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IO Processor
|
|
3
|
+
*
|
|
4
|
+
* Processes IO requests from the sync engine using JavaScript APIs.
|
|
5
|
+
* This is the key benefit of the thin JSI layer - all IO is handled by
|
|
6
|
+
* React Native's standard APIs (fetch, file system), not C++ code.
|
|
7
|
+
*
|
|
8
|
+
* Benefits:
|
|
9
|
+
* - Network requests visible in React Native debugger
|
|
10
|
+
* - Can customize fetch (add proxies, custom headers, etc.)
|
|
11
|
+
* - Easier to test (can mock fetch)
|
|
12
|
+
* - Uses platform-native networking (not C++ HTTP libraries)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { NativeSyncIoItem, NativeSyncDatabase } from '../types';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* IO context contains auth and URL information for HTTP requests
|
|
19
|
+
*/
|
|
20
|
+
export interface IoContext {
|
|
21
|
+
/** Auth token for HTTP requests */
|
|
22
|
+
authToken?: string | (() => string | Promise<string> | null);
|
|
23
|
+
/** Base URL for normalization (e.g., 'libsql://mydb.turso.io') */
|
|
24
|
+
baseUrl?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Allow users to optionally override file system implementation
|
|
28
|
+
let fsReadFileOverride: ((path: string) => Promise<Uint8Array>) | null = null;
|
|
29
|
+
let fsWriteFileOverride: ((path: string, data: Uint8Array) => Promise<void>) | null = null;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Set custom file system implementation (optional)
|
|
33
|
+
* By default, uses built-in JSI file system functions.
|
|
34
|
+
* Only call this if you need custom behavior (e.g., encryption, compression).
|
|
35
|
+
*
|
|
36
|
+
* @param readFile - Function to read file as Uint8Array
|
|
37
|
+
* @param writeFile - Function to write Uint8Array to file
|
|
38
|
+
*/
|
|
39
|
+
export function setFileSystemImpl(
|
|
40
|
+
readFile: (path: string) => Promise<Uint8Array>,
|
|
41
|
+
writeFile: (path: string, data: Uint8Array) => Promise<void>
|
|
42
|
+
): void {
|
|
43
|
+
fsReadFileOverride = readFile;
|
|
44
|
+
fsWriteFileOverride = writeFile;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Read file using custom implementation or built-in JSI function
|
|
49
|
+
*/
|
|
50
|
+
async function fsReadFile(path: string): Promise<Uint8Array> {
|
|
51
|
+
if (fsReadFileOverride) {
|
|
52
|
+
return fsReadFileOverride(path);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Use built-in JSI function
|
|
56
|
+
const buffer = __TursoProxy.fsReadFile(path);
|
|
57
|
+
if (buffer === null) {
|
|
58
|
+
// File not found - return empty
|
|
59
|
+
return new Uint8Array(0);
|
|
60
|
+
}
|
|
61
|
+
return new Uint8Array(buffer);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Write file using custom implementation or built-in JSI function
|
|
66
|
+
*/
|
|
67
|
+
async function fsWriteFile(path: string, data: Uint8Array): Promise<void> {
|
|
68
|
+
if (fsWriteFileOverride) {
|
|
69
|
+
return fsWriteFileOverride(path, data);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Use built-in JSI function
|
|
73
|
+
__TursoProxy.fsWriteFile(path, data.buffer as ArrayBuffer);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Process a single IO item
|
|
78
|
+
*
|
|
79
|
+
* @param item - The IO item to process
|
|
80
|
+
* @param context - IO context with auth and URL information
|
|
81
|
+
*/
|
|
82
|
+
export async function processIoItem(item: NativeSyncIoItem, context: IoContext): Promise<void> {
|
|
83
|
+
try {
|
|
84
|
+
const kind = item.getKind();
|
|
85
|
+
|
|
86
|
+
switch (kind) {
|
|
87
|
+
case 'HTTP':
|
|
88
|
+
await processHttpRequest(item, context);
|
|
89
|
+
break;
|
|
90
|
+
|
|
91
|
+
case 'FULL_READ':
|
|
92
|
+
await processFullRead(item);
|
|
93
|
+
break;
|
|
94
|
+
|
|
95
|
+
case 'FULL_WRITE':
|
|
96
|
+
await processFullWrite(item);
|
|
97
|
+
break;
|
|
98
|
+
|
|
99
|
+
case 'NONE':
|
|
100
|
+
default:
|
|
101
|
+
// Nothing to do
|
|
102
|
+
item.done();
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
} catch (error) {
|
|
106
|
+
// Poison the item with error message
|
|
107
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
108
|
+
item.poison(errorMsg);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Normalize URL from libsql:// to https://
|
|
114
|
+
*/
|
|
115
|
+
function normalizeUrl(url: string): string {
|
|
116
|
+
if (url.startsWith('libsql://')) {
|
|
117
|
+
return url.replace('libsql://', 'https://');
|
|
118
|
+
}
|
|
119
|
+
return url;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get auth token from context (handles both string and function)
|
|
124
|
+
*/
|
|
125
|
+
function getAuthToken(context: IoContext): string | null {
|
|
126
|
+
if (!context.authToken) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (typeof context.authToken === 'function') {
|
|
131
|
+
return context.authToken() as unknown as string | null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return context.authToken;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Process an HTTP request using fetch()
|
|
139
|
+
*
|
|
140
|
+
* @param item - The IO item
|
|
141
|
+
* @param context - IO context with auth and URL information
|
|
142
|
+
*/
|
|
143
|
+
async function processHttpRequest(item: NativeSyncIoItem, context: IoContext): Promise<void> {
|
|
144
|
+
const request = item.getHttpRequest();
|
|
145
|
+
|
|
146
|
+
// Build full URL
|
|
147
|
+
let fullUrl = '';
|
|
148
|
+
|
|
149
|
+
if (request.url) {
|
|
150
|
+
// Normalize URL (libsql:// -> https://)
|
|
151
|
+
let baseUrl = normalizeUrl(request.url);
|
|
152
|
+
|
|
153
|
+
// Combine base URL with path if path is provided
|
|
154
|
+
if (request.path) {
|
|
155
|
+
// Ensure proper URL formatting (avoid double slashes, ensure single slash)
|
|
156
|
+
if (baseUrl.endsWith('/') && request.path.startsWith('/')) {
|
|
157
|
+
fullUrl = baseUrl + request.path.substring(1);
|
|
158
|
+
} else if (!baseUrl.endsWith('/') && !request.path.startsWith('/')) {
|
|
159
|
+
fullUrl = baseUrl + '/' + request.path;
|
|
160
|
+
} else {
|
|
161
|
+
fullUrl = baseUrl + request.path;
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
fullUrl = baseUrl;
|
|
165
|
+
}
|
|
166
|
+
} else if (request.path) {
|
|
167
|
+
// Path without base URL - shouldn't happen
|
|
168
|
+
throw new Error('HTTP request missing base URL');
|
|
169
|
+
} else {
|
|
170
|
+
throw new Error('HTTP request missing URL and path');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Build fetch options
|
|
174
|
+
const options: RequestInit = {
|
|
175
|
+
method: request.method,
|
|
176
|
+
headers: { ...request.headers },
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// Inject Authorization header if auth token is available
|
|
180
|
+
const authToken = getAuthToken(context);
|
|
181
|
+
if (authToken) {
|
|
182
|
+
(options.headers as Record<string, string>)['Authorization'] = `Bearer ${authToken}`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Add body if present
|
|
186
|
+
if (request.body) {
|
|
187
|
+
options.body = request.body;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Debug logging for HTTP requests
|
|
191
|
+
console.log('[Turso HTTP] Request:', {
|
|
192
|
+
method: request.method,
|
|
193
|
+
url: fullUrl,
|
|
194
|
+
hasBody: !!request.body,
|
|
195
|
+
bodySize: request.body ? request.body.byteLength : 0,
|
|
196
|
+
headers: options.headers,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Make the HTTP request
|
|
200
|
+
let response;
|
|
201
|
+
try {
|
|
202
|
+
response = await fetch(fullUrl, options);
|
|
203
|
+
} catch (e) {
|
|
204
|
+
// Detailed error logging
|
|
205
|
+
const errorDetails = {
|
|
206
|
+
url: fullUrl,
|
|
207
|
+
method: request.method,
|
|
208
|
+
hasBody: !!request.body,
|
|
209
|
+
bodySize: request.body ? request.body.byteLength : 0,
|
|
210
|
+
bodyType: request.body ? Object.prototype.toString.call(options.body) : 'none',
|
|
211
|
+
error: e instanceof Error ? {
|
|
212
|
+
message: e.message,
|
|
213
|
+
name: e.name,
|
|
214
|
+
stack: e.stack,
|
|
215
|
+
} : String(e),
|
|
216
|
+
};
|
|
217
|
+
console.error('[Turso HTTP] Request failed:', JSON.stringify(errorDetails, null, 2));
|
|
218
|
+
throw new Error(`HTTP request failed: ${e instanceof Error ? e.message : String(e)}. URL: ${fullUrl}, Method: ${request.method}, Body size: ${request.body ? request.body.byteLength : 0} bytes`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
// Set status code
|
|
223
|
+
item.setStatus(response.status);
|
|
224
|
+
|
|
225
|
+
// Read response body and push to item
|
|
226
|
+
const responseData = await response.arrayBuffer();
|
|
227
|
+
if (responseData.byteLength > 0) {
|
|
228
|
+
item.pushBuffer(responseData);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Mark as done
|
|
232
|
+
item.done();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Process a full read request (atomic file read)
|
|
237
|
+
*
|
|
238
|
+
* @param item - The IO item
|
|
239
|
+
*/
|
|
240
|
+
async function processFullRead(item: NativeSyncIoItem): Promise<void> {
|
|
241
|
+
const path = item.getFullReadPath();
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
// Read the file (uses built-in JSI function or custom override)
|
|
245
|
+
const data = await fsReadFile(path);
|
|
246
|
+
|
|
247
|
+
// Push data to item
|
|
248
|
+
if (data.byteLength > 0) {
|
|
249
|
+
item.pushBuffer(data.buffer as ArrayBuffer);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Mark as done
|
|
253
|
+
item.done();
|
|
254
|
+
} catch (error) {
|
|
255
|
+
// File not found is okay - treat as empty file
|
|
256
|
+
if (isFileNotFoundError(error)) {
|
|
257
|
+
// Empty file - just mark as done without pushing data
|
|
258
|
+
item.done();
|
|
259
|
+
} else {
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Process a full write request (atomic file write)
|
|
267
|
+
*
|
|
268
|
+
* @param item - The IO item
|
|
269
|
+
*/
|
|
270
|
+
async function processFullWrite(item: NativeSyncIoItem): Promise<void> {
|
|
271
|
+
const request = item.getFullWriteRequest();
|
|
272
|
+
|
|
273
|
+
// Convert ArrayBuffer to Uint8Array
|
|
274
|
+
const data = request.content ? new Uint8Array(request.content) : new Uint8Array(0);
|
|
275
|
+
|
|
276
|
+
// Write the file atomically (uses built-in JSI function or custom override)
|
|
277
|
+
await fsWriteFile(request.path, data);
|
|
278
|
+
|
|
279
|
+
// Mark as done
|
|
280
|
+
item.done();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Check if error is a file-not-found error
|
|
285
|
+
*
|
|
286
|
+
* @param error - The error to check
|
|
287
|
+
* @returns true if file not found
|
|
288
|
+
*/
|
|
289
|
+
function isFileNotFoundError(error: unknown): boolean {
|
|
290
|
+
if (error instanceof Error) {
|
|
291
|
+
const message = error.message.toLowerCase();
|
|
292
|
+
return (
|
|
293
|
+
message.includes('enoent') ||
|
|
294
|
+
message.includes('not found') ||
|
|
295
|
+
message.includes('no such file')
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Drain all pending IO items from sync engine queue and process them.
|
|
303
|
+
* This is called during statement execution when partial sync needs to load missing pages.
|
|
304
|
+
*
|
|
305
|
+
* @param database - The native sync database
|
|
306
|
+
* @param context - IO context with auth and URL information
|
|
307
|
+
*/
|
|
308
|
+
export async function drainSyncIo(database: NativeSyncDatabase, context: IoContext): Promise<void> {
|
|
309
|
+
const promises: Promise<void>[] = [];
|
|
310
|
+
|
|
311
|
+
// Take all available IO items from the queue
|
|
312
|
+
while (true) {
|
|
313
|
+
const ioItem = database.ioTakeItem();
|
|
314
|
+
if (!ioItem) {
|
|
315
|
+
break; // No more items
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Process each item
|
|
319
|
+
promises.push(processIoItem(ioItem, context));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Wait for all IO operations to complete
|
|
323
|
+
await Promise.all(promises);
|
|
324
|
+
|
|
325
|
+
// Step callbacks after IO processing
|
|
326
|
+
// This allows the sync engine to run any post-IO callbacks
|
|
327
|
+
database.ioStepCallbacks();
|
|
328
|
+
}
|