@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,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
|