@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.
Files changed (72) hide show
  1. package/README.md +117 -0
  2. package/android/CMakeLists.txt +53 -0
  3. package/android/build.gradle +84 -0
  4. package/android/cpp-adapter.cpp +49 -0
  5. package/android/src/main/AndroidManifest.xml +2 -0
  6. package/android/src/main/java/com/turso/sync/reactnative/TursoBridge.java +44 -0
  7. package/android/src/main/java/com/turso/sync/reactnative/TursoModule.java +82 -0
  8. package/android/src/main/java/com/turso/sync/reactnative/TursoPackage.java +29 -0
  9. package/cpp/TursoConnectionHostObject.cpp +179 -0
  10. package/cpp/TursoConnectionHostObject.h +52 -0
  11. package/cpp/TursoDatabaseHostObject.cpp +98 -0
  12. package/cpp/TursoDatabaseHostObject.h +49 -0
  13. package/cpp/TursoHostObject.cpp +561 -0
  14. package/cpp/TursoHostObject.h +24 -0
  15. package/cpp/TursoStatementHostObject.cpp +414 -0
  16. package/cpp/TursoStatementHostObject.h +65 -0
  17. package/cpp/TursoSyncChangesHostObject.cpp +41 -0
  18. package/cpp/TursoSyncChangesHostObject.h +52 -0
  19. package/cpp/TursoSyncDatabaseHostObject.cpp +328 -0
  20. package/cpp/TursoSyncDatabaseHostObject.h +61 -0
  21. package/cpp/TursoSyncIoItemHostObject.cpp +304 -0
  22. package/cpp/TursoSyncIoItemHostObject.h +52 -0
  23. package/cpp/TursoSyncOperationHostObject.cpp +168 -0
  24. package/cpp/TursoSyncOperationHostObject.h +53 -0
  25. package/ios/TursoModule.h +8 -0
  26. package/ios/TursoModule.mm +95 -0
  27. package/lib/commonjs/Database.js +445 -0
  28. package/lib/commonjs/Database.js.map +1 -0
  29. package/lib/commonjs/Statement.js +339 -0
  30. package/lib/commonjs/Statement.js.map +1 -0
  31. package/lib/commonjs/index.js +229 -0
  32. package/lib/commonjs/index.js.map +1 -0
  33. package/lib/commonjs/internal/asyncOperation.js +124 -0
  34. package/lib/commonjs/internal/asyncOperation.js.map +1 -0
  35. package/lib/commonjs/internal/ioProcessor.js +315 -0
  36. package/lib/commonjs/internal/ioProcessor.js.map +1 -0
  37. package/lib/commonjs/package.json +1 -0
  38. package/lib/commonjs/types.js +133 -0
  39. package/lib/commonjs/types.js.map +1 -0
  40. package/lib/module/Database.js +441 -0
  41. package/lib/module/Database.js.map +1 -0
  42. package/lib/module/Statement.js +335 -0
  43. package/lib/module/Statement.js.map +1 -0
  44. package/lib/module/index.js +205 -0
  45. package/lib/module/index.js.map +1 -0
  46. package/lib/module/internal/asyncOperation.js +116 -0
  47. package/lib/module/internal/asyncOperation.js.map +1 -0
  48. package/lib/module/internal/ioProcessor.js +309 -0
  49. package/lib/module/internal/ioProcessor.js.map +1 -0
  50. package/lib/module/package.json +1 -0
  51. package/lib/module/types.js +163 -0
  52. package/lib/module/types.js.map +1 -0
  53. package/lib/typescript/Database.d.ts +140 -0
  54. package/lib/typescript/Database.d.ts.map +1 -0
  55. package/lib/typescript/Statement.d.ts +105 -0
  56. package/lib/typescript/Statement.d.ts.map +1 -0
  57. package/lib/typescript/index.d.ts +175 -0
  58. package/lib/typescript/index.d.ts.map +1 -0
  59. package/lib/typescript/internal/asyncOperation.d.ts +39 -0
  60. package/lib/typescript/internal/asyncOperation.d.ts.map +1 -0
  61. package/lib/typescript/internal/ioProcessor.d.ts +48 -0
  62. package/lib/typescript/internal/ioProcessor.d.ts.map +1 -0
  63. package/lib/typescript/types.d.ts +316 -0
  64. package/lib/typescript/types.d.ts.map +1 -0
  65. package/package.json +97 -0
  66. package/src/Database.ts +480 -0
  67. package/src/Statement.ts +372 -0
  68. package/src/index.ts +240 -0
  69. package/src/internal/asyncOperation.ts +147 -0
  70. package/src/internal/ioProcessor.ts +328 -0
  71. package/src/types.ts +391 -0
  72. package/turso-sync-react-native.podspec +56 -0
@@ -0,0 +1,561 @@
1
+ #include "TursoHostObject.h"
2
+ #include "TursoDatabaseHostObject.h"
3
+ #include "TursoSyncDatabaseHostObject.h"
4
+
5
+ #include <cstdio> // For FILE, fopen, fread, fwrite, fclose, fseek, ftell, remove, rename
6
+ #include <cstdlib> // For additional standard library functions
7
+
8
+ extern "C" {
9
+ #include <turso.h>
10
+ #include <turso_sync.h>
11
+ }
12
+
13
+ namespace turso
14
+ {
15
+
16
+ using namespace facebook;
17
+
18
+ // Global base path for database files
19
+ static std::string g_basePath;
20
+
21
+ /**
22
+ * Normalize a database path:
23
+ * - If path is absolute (starts with '/'), use as-is
24
+ * - If path is ':memory:', use as-is
25
+ * - Otherwise, prepend basePath
26
+ */
27
+ static std::string normalizePath(const std::string &path)
28
+ {
29
+ // Special cases: absolute path or in-memory
30
+ if (path.empty() || path[0] == '/' || path == ":memory:")
31
+ {
32
+ return path;
33
+ }
34
+
35
+ // Relative path - prepend basePath
36
+ if (g_basePath.empty())
37
+ {
38
+ return path;
39
+ }
40
+
41
+ // Combine basePath + path
42
+ if (g_basePath[g_basePath.length() - 1] == '/')
43
+ {
44
+ return g_basePath + path;
45
+ }
46
+ else
47
+ {
48
+ return g_basePath + "/" + path;
49
+ }
50
+ }
51
+
52
+ void install(
53
+ jsi::Runtime &rt,
54
+ const std::shared_ptr<react::CallInvoker> &invoker,
55
+ const char *basePath)
56
+ {
57
+ g_basePath = basePath ? basePath : "";
58
+
59
+ // Create the module object
60
+ jsi::Object module(rt);
61
+
62
+ // newDatabase(path, dbConfig) -> TursoDatabaseHostObject
63
+ // Factory for creating local-only databases
64
+ auto newDatabase = jsi::Function::createFromHostFunction(
65
+ rt,
66
+ jsi::PropNameID::forAscii(rt, "newDatabase"),
67
+ 1, // min args
68
+ [](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value
69
+ {
70
+ if (count < 1 || !args[0].isString())
71
+ {
72
+ throw jsi::JSError(rt, "newDatabase() requires path string as first argument");
73
+ }
74
+
75
+ std::string path = args[0].asString(rt).utf8(rt);
76
+
77
+ // Normalize path (prepend basePath if relative)
78
+ std::string normalizedPath = normalizePath(path);
79
+
80
+ // Build database config
81
+ turso_database_config_t db_config = {0};
82
+ db_config.async_io = 1; // Default to async IO for React Native
83
+ db_config.path = normalizedPath.c_str();
84
+ db_config.experimental_features = nullptr;
85
+ db_config.vfs = nullptr;
86
+ db_config.encryption_cipher = nullptr;
87
+ db_config.encryption_hexkey = nullptr;
88
+
89
+ // Parse optional dbConfig object (second argument)
90
+ if (count >= 2 && args[1].isObject())
91
+ {
92
+ jsi::Object config = args[1].asObject(rt);
93
+
94
+ // Parse async_io if provided
95
+ if (config.hasProperty(rt, "async_io"))
96
+ {
97
+ db_config.async_io = config.getProperty(rt, "async_io").getBool() ? 1 : 0;
98
+ }
99
+ }
100
+
101
+ // Create database instance
102
+ const turso_database_t* database = nullptr;
103
+ const char* error = nullptr;
104
+ turso_status_code_t status = turso_database_new(&db_config, &database, &error);
105
+
106
+ if (status != TURSO_OK)
107
+ {
108
+ std::string errorMsg = error ? error : "Failed to create database";
109
+ throw jsi::JSError(rt, errorMsg);
110
+ }
111
+
112
+ // Wrap in TursoDatabaseHostObject
113
+ auto dbObj = std::make_shared<TursoDatabaseHostObject>(
114
+ const_cast<turso_database_t*>(database)
115
+ );
116
+ return jsi::Object::createFromHostObject(rt, dbObj);
117
+ });
118
+
119
+ // newSyncDatabase(dbConfig, syncConfig) -> TursoSyncDatabaseHostObject
120
+ // Factory for creating sync-enabled embedded replica databases
121
+ auto newSyncDatabase = jsi::Function::createFromHostFunction(
122
+ rt,
123
+ jsi::PropNameID::forAscii(rt, "newSyncDatabase"),
124
+ 2, // min args
125
+ [](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value
126
+ {
127
+ if (count < 2 || !args[0].isObject() || !args[1].isObject())
128
+ {
129
+ throw jsi::JSError(rt, "newSyncDatabase() requires dbConfig and syncConfig objects");
130
+ }
131
+
132
+ jsi::Object dbConfigObj = args[0].asObject(rt);
133
+ jsi::Object syncConfigObj = args[1].asObject(rt);
134
+
135
+ // Parse dbConfig
136
+ if (!dbConfigObj.hasProperty(rt, "path"))
137
+ {
138
+ throw jsi::JSError(rt, "dbConfig must have 'path' property");
139
+ }
140
+ std::string path = dbConfigObj.getProperty(rt, "path").asString(rt).utf8(rt);
141
+
142
+ // Normalize path (prepend basePath if relative)
143
+ std::string normalizedPath = normalizePath(path);
144
+
145
+ turso_database_config_t db_config = {0};
146
+ db_config.async_io = 1; // Default to async IO for React Native
147
+ db_config.path = normalizedPath.c_str();
148
+
149
+ // Parse async_io if provided in dbConfig
150
+ if (dbConfigObj.hasProperty(rt, "async_io"))
151
+ {
152
+ db_config.async_io = dbConfigObj.getProperty(rt, "async_io").getBool() ? 1 : 0;
153
+ }
154
+ db_config.experimental_features = nullptr;
155
+ db_config.vfs = nullptr;
156
+ db_config.encryption_cipher = nullptr;
157
+ db_config.encryption_hexkey = nullptr;
158
+
159
+ // Parse syncConfig
160
+ turso_sync_database_config_t sync_config = {0};
161
+
162
+ // path (already set in db_config, but sync_config also needs it)
163
+ sync_config.path = normalizedPath.c_str();
164
+
165
+ // remoteUrl (optional)
166
+ static std::string remoteUrl;
167
+ if (syncConfigObj.hasProperty(rt, "remoteUrl"))
168
+ {
169
+ jsi::Value remoteUrlVal = syncConfigObj.getProperty(rt, "remoteUrl");
170
+ if (!remoteUrlVal.isNull() && !remoteUrlVal.isUndefined())
171
+ {
172
+ remoteUrl = remoteUrlVal.asString(rt).utf8(rt);
173
+ sync_config.remote_url = remoteUrl.c_str();
174
+ }
175
+ else
176
+ {
177
+ sync_config.remote_url = nullptr;
178
+ }
179
+ }
180
+ else
181
+ {
182
+ sync_config.remote_url = nullptr;
183
+ }
184
+
185
+ // clientName (optional)
186
+ static std::string clientName;
187
+ if (syncConfigObj.hasProperty(rt, "clientName"))
188
+ {
189
+ jsi::Value clientNameVal = syncConfigObj.getProperty(rt, "clientName");
190
+ if (!clientNameVal.isNull() && !clientNameVal.isUndefined())
191
+ {
192
+ clientName = clientNameVal.asString(rt).utf8(rt);
193
+ sync_config.client_name = clientName.c_str();
194
+ }
195
+ else
196
+ {
197
+ sync_config.client_name = nullptr;
198
+ }
199
+ }
200
+ else
201
+ {
202
+ sync_config.client_name = nullptr;
203
+ }
204
+
205
+ // longPollTimeoutMs
206
+ if (syncConfigObj.hasProperty(rt, "longPollTimeoutMs"))
207
+ {
208
+ jsi::Value longPollVal = syncConfigObj.getProperty(rt, "longPollTimeoutMs");
209
+ if (!longPollVal.isNull() && !longPollVal.isUndefined())
210
+ {
211
+ sync_config.long_poll_timeout_ms = static_cast<int32_t>(longPollVal.asNumber());
212
+ }
213
+ else
214
+ {
215
+ sync_config.long_poll_timeout_ms = 0;
216
+ }
217
+ }
218
+ else
219
+ {
220
+ sync_config.long_poll_timeout_ms = 0;
221
+ }
222
+
223
+ // bootstrapIfEmpty
224
+ if (syncConfigObj.hasProperty(rt, "bootstrapIfEmpty"))
225
+ {
226
+ jsi::Value bootstrapVal = syncConfigObj.getProperty(rt, "bootstrapIfEmpty");
227
+ if (!bootstrapVal.isNull() && !bootstrapVal.isUndefined())
228
+ {
229
+ sync_config.bootstrap_if_empty = bootstrapVal.getBool();
230
+ }
231
+ else
232
+ {
233
+ sync_config.bootstrap_if_empty = false;
234
+ }
235
+ }
236
+ else
237
+ {
238
+ sync_config.bootstrap_if_empty = false;
239
+ }
240
+
241
+ // reservedBytes
242
+ if (syncConfigObj.hasProperty(rt, "reservedBytes"))
243
+ {
244
+ jsi::Value reservedVal = syncConfigObj.getProperty(rt, "reservedBytes");
245
+ if (!reservedVal.isNull() && !reservedVal.isUndefined())
246
+ {
247
+ sync_config.reserved_bytes = static_cast<int32_t>(reservedVal.asNumber());
248
+ }
249
+ else
250
+ {
251
+ sync_config.reserved_bytes = 0;
252
+ }
253
+ }
254
+ else
255
+ {
256
+ sync_config.reserved_bytes = 0;
257
+ }
258
+
259
+ // Partial sync options
260
+ if (syncConfigObj.hasProperty(rt, "partialBootstrapStrategyPrefix"))
261
+ {
262
+ jsi::Value prefixVal = syncConfigObj.getProperty(rt, "partialBootstrapStrategyPrefix");
263
+ if (!prefixVal.isNull() && !prefixVal.isUndefined())
264
+ {
265
+ sync_config.partial_bootstrap_strategy_prefix = static_cast<int32_t>(prefixVal.asNumber());
266
+ }
267
+ else
268
+ {
269
+ sync_config.partial_bootstrap_strategy_prefix = 0;
270
+ }
271
+ }
272
+ else
273
+ {
274
+ sync_config.partial_bootstrap_strategy_prefix = 0;
275
+ }
276
+
277
+ static std::string partialBootstrapStrategyQuery;
278
+ if (syncConfigObj.hasProperty(rt, "partialBootstrapStrategyQuery"))
279
+ {
280
+ jsi::Value queryVal = syncConfigObj.getProperty(rt, "partialBootstrapStrategyQuery");
281
+ if (!queryVal.isNull() && !queryVal.isUndefined())
282
+ {
283
+ partialBootstrapStrategyQuery = queryVal.asString(rt).utf8(rt);
284
+ sync_config.partial_bootstrap_strategy_query = partialBootstrapStrategyQuery.c_str();
285
+ }
286
+ else
287
+ {
288
+ sync_config.partial_bootstrap_strategy_query = nullptr;
289
+ }
290
+ }
291
+ else
292
+ {
293
+ sync_config.partial_bootstrap_strategy_query = nullptr;
294
+ }
295
+
296
+ if (syncConfigObj.hasProperty(rt, "partialBootstrapSegmentSize"))
297
+ {
298
+ jsi::Value segmentVal = syncConfigObj.getProperty(rt, "partialBootstrapSegmentSize");
299
+ if (!segmentVal.isNull() && !segmentVal.isUndefined())
300
+ {
301
+ sync_config.partial_bootstrap_segment_size = static_cast<size_t>(segmentVal.asNumber());
302
+ }
303
+ else
304
+ {
305
+ sync_config.partial_bootstrap_segment_size = 0;
306
+ }
307
+ }
308
+ else
309
+ {
310
+ sync_config.partial_bootstrap_segment_size = 0;
311
+ }
312
+
313
+ if (syncConfigObj.hasProperty(rt, "partialBootstrapPrefetch"))
314
+ {
315
+ jsi::Value prefetchVal = syncConfigObj.getProperty(rt, "partialBootstrapPrefetch");
316
+ if (!prefetchVal.isNull() && !prefetchVal.isUndefined())
317
+ {
318
+ sync_config.partial_bootstrap_prefetch = prefetchVal.getBool();
319
+ }
320
+ else
321
+ {
322
+ sync_config.partial_bootstrap_prefetch = false;
323
+ }
324
+ }
325
+ else
326
+ {
327
+ sync_config.partial_bootstrap_prefetch = false;
328
+ }
329
+
330
+ // Remote encryption options
331
+ static std::string remoteEncryptionKey;
332
+ if (syncConfigObj.hasProperty(rt, "remoteEncryptionKey"))
333
+ {
334
+ jsi::Value keyVal = syncConfigObj.getProperty(rt, "remoteEncryptionKey");
335
+ if (!keyVal.isNull() && !keyVal.isUndefined())
336
+ {
337
+ remoteEncryptionKey = keyVal.asString(rt).utf8(rt);
338
+ sync_config.remote_encryption_key = remoteEncryptionKey.c_str();
339
+ }
340
+ else
341
+ {
342
+ sync_config.remote_encryption_key = nullptr;
343
+ }
344
+ }
345
+ else
346
+ {
347
+ sync_config.remote_encryption_key = nullptr;
348
+ }
349
+
350
+ static std::string remoteEncryptionCipher;
351
+ if (syncConfigObj.hasProperty(rt, "remoteEncryptionCipher"))
352
+ {
353
+ jsi::Value cipherVal = syncConfigObj.getProperty(rt, "remoteEncryptionCipher");
354
+ if (!cipherVal.isNull() && !cipherVal.isUndefined())
355
+ {
356
+ remoteEncryptionCipher = cipherVal.asString(rt).utf8(rt);
357
+ sync_config.remote_encryption_cipher = remoteEncryptionCipher.c_str();
358
+ }
359
+ else
360
+ {
361
+ sync_config.remote_encryption_cipher = nullptr;
362
+ }
363
+ }
364
+ else
365
+ {
366
+ sync_config.remote_encryption_cipher = nullptr;
367
+ }
368
+
369
+ // Create sync database instance
370
+ const turso_sync_database_t* database = nullptr;
371
+ const char* error = nullptr;
372
+ turso_status_code_t status = turso_sync_database_new(&db_config, &sync_config, &database, &error);
373
+
374
+ if (status != TURSO_OK)
375
+ {
376
+ std::string errorMsg = error ? error : "Failed to create sync database";
377
+ throw jsi::JSError(rt, errorMsg);
378
+ }
379
+
380
+ // Wrap in TursoSyncDatabaseHostObject
381
+ auto dbObj = std::make_shared<TursoSyncDatabaseHostObject>(
382
+ const_cast<turso_sync_database_t*>(database)
383
+ );
384
+ return jsi::Object::createFromHostObject(rt, dbObj);
385
+ });
386
+
387
+ // version() -> string
388
+ auto version = jsi::Function::createFromHostFunction(
389
+ rt,
390
+ jsi::PropNameID::forAscii(rt, "version"),
391
+ 0,
392
+ [](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value
393
+ {
394
+ const char *ver = turso_version();
395
+ return jsi::String::createFromUtf8(rt, ver);
396
+ });
397
+
398
+ // setup(options) -> void
399
+ auto setup = jsi::Function::createFromHostFunction(
400
+ rt,
401
+ jsi::PropNameID::forAscii(rt, "setup"),
402
+ 1,
403
+ [](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value
404
+ {
405
+ if (count < 1 || !args[0].isObject())
406
+ {
407
+ throw jsi::JSError(rt, "setup() requires an options object");
408
+ }
409
+
410
+ jsi::Object options = args[0].asObject(rt);
411
+
412
+ // Store log level in a static variable to ensure lifetime
413
+ static std::string logLevelStr;
414
+
415
+ // Get log level if provided
416
+ if (options.hasProperty(rt, "logLevel"))
417
+ {
418
+ jsi::Value logLevelVal = options.getProperty(rt, "logLevel");
419
+ if (logLevelVal.isString())
420
+ {
421
+ logLevelStr = logLevelVal.asString(rt).utf8(rt);
422
+ }
423
+ }
424
+
425
+ turso_config_t config = {nullptr, logLevelStr.empty() ? nullptr : logLevelStr.c_str()};
426
+
427
+ // Call turso_setup
428
+ const char *error = nullptr;
429
+ turso_status_code_t status = turso_setup(&config, &error);
430
+
431
+ if (status != TURSO_OK)
432
+ {
433
+ std::string errorMsg = error ? error : "Unknown error in turso_setup";
434
+ throw jsi::JSError(rt, errorMsg);
435
+ }
436
+
437
+ return jsi::Value::undefined();
438
+ });
439
+
440
+ // fsReadFile(path) -> ArrayBuffer
441
+ auto fsReadFile = jsi::Function::createFromHostFunction(
442
+ rt,
443
+ jsi::PropNameID::forAscii(rt, "fsReadFile"),
444
+ 1,
445
+ [](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value
446
+ {
447
+ if (count < 1 || !args[0].isString())
448
+ {
449
+ throw jsi::JSError(rt, "fsReadFile() requires path string");
450
+ }
451
+
452
+ std::string path = args[0].asString(rt).utf8(rt);
453
+
454
+ // Open file for reading
455
+ FILE* file = fopen(path.c_str(), "rb");
456
+ if (!file)
457
+ {
458
+ // File not found - return null (caller will handle as empty)
459
+ return jsi::Value::null();
460
+ }
461
+
462
+ // Get file size
463
+ fseek(file, 0, SEEK_END);
464
+ long size = ftell(file);
465
+ fseek(file, 0, SEEK_SET);
466
+
467
+ if (size <= 0)
468
+ {
469
+ fclose(file);
470
+ // Empty file - return empty ArrayBuffer
471
+ jsi::Function arrayBufferCtor = rt.global().getPropertyAsFunction(rt, "ArrayBuffer");
472
+ jsi::Object arrayBuffer = arrayBufferCtor.callAsConstructor(rt, 0).asObject(rt);
473
+ return arrayBuffer;
474
+ }
475
+
476
+ // Read file contents
477
+ jsi::Function arrayBufferCtor = rt.global().getPropertyAsFunction(rt, "ArrayBuffer");
478
+ jsi::Object arrayBuffer = arrayBufferCtor.callAsConstructor(rt, static_cast<int>(size)).asObject(rt);
479
+ jsi::ArrayBuffer buf = arrayBuffer.getArrayBuffer(rt);
480
+
481
+ size_t bytesRead = fread(buf.data(rt), 1, size, file);
482
+ fclose(file);
483
+
484
+ if (bytesRead != static_cast<size_t>(size))
485
+ {
486
+ throw jsi::JSError(rt, "Failed to read complete file");
487
+ }
488
+
489
+ return arrayBuffer;
490
+ });
491
+
492
+ // fsWriteFile(path, arrayBuffer) -> void
493
+ auto fsWriteFile = jsi::Function::createFromHostFunction(
494
+ rt,
495
+ jsi::PropNameID::forAscii(rt, "fsWriteFile"),
496
+ 2,
497
+ [](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value
498
+ {
499
+ if (count < 2 || !args[0].isString() || !args[1].isObject())
500
+ {
501
+ throw jsi::JSError(rt, "fsWriteFile() requires path string and ArrayBuffer");
502
+ }
503
+
504
+ std::string path = args[0].asString(rt).utf8(rt);
505
+ jsi::ArrayBuffer buffer = args[1].asObject(rt).getArrayBuffer(rt);
506
+
507
+ // Write atomically using temporary file + rename
508
+ std::string tempPath = path + ".tmp";
509
+
510
+ // Open temp file for writing
511
+ FILE* file = fopen(tempPath.c_str(), "wb");
512
+ if (!file)
513
+ {
514
+ throw jsi::JSError(rt, "Failed to open file for writing");
515
+ }
516
+
517
+ // Write data
518
+ size_t size = buffer.size(rt);
519
+ if (size > 0)
520
+ {
521
+ size_t written = fwrite(buffer.data(rt), 1, size, file);
522
+ fclose(file);
523
+
524
+ if (written != size)
525
+ {
526
+ remove(tempPath.c_str());
527
+ throw jsi::JSError(rt, "Failed to write complete file");
528
+ }
529
+ }
530
+ else
531
+ {
532
+ fclose(file);
533
+ }
534
+
535
+ // Atomic rename (replaces old file)
536
+ if (rename(tempPath.c_str(), path.c_str()) != 0)
537
+ {
538
+ remove(tempPath.c_str());
539
+ throw jsi::JSError(rt, "Failed to rename temp file");
540
+ }
541
+
542
+ return jsi::Value::undefined();
543
+ });
544
+
545
+ module.setProperty(rt, "newDatabase", std::move(newDatabase));
546
+ module.setProperty(rt, "newSyncDatabase", std::move(newSyncDatabase));
547
+ module.setProperty(rt, "version", std::move(version));
548
+ module.setProperty(rt, "setup", std::move(setup));
549
+ module.setProperty(rt, "fsReadFile", std::move(fsReadFile));
550
+ module.setProperty(rt, "fsWriteFile", std::move(fsWriteFile));
551
+
552
+ // Install as global __TursoProxy
553
+ rt.global().setProperty(rt, "__TursoProxy", std::move(module));
554
+ }
555
+
556
+ void invalidate()
557
+ {
558
+ // Cleanup if needed
559
+ }
560
+
561
+ } // namespace turso
@@ -0,0 +1,24 @@
1
+ #pragma once
2
+
3
+ #include <jsi/jsi.h>
4
+ #include <ReactCommon/CallInvoker.h>
5
+ #include <memory>
6
+ #include <string>
7
+
8
+ namespace turso {
9
+
10
+ using namespace facebook;
11
+
12
+ /**
13
+ * Install the Turso module into the JSI runtime.
14
+ * This creates a global __TursoProxy object with the open() function.
15
+ */
16
+ void install(
17
+ jsi::Runtime &rt,
18
+ const std::shared_ptr<react::CallInvoker> &invoker,
19
+ const char *basePath
20
+ );
21
+
22
+ void invalidate();
23
+
24
+ } // namespace turso