@react-native-native/nativ-fabric 0.1.0

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 (42) hide show
  1. package/NativFabric.podspec +41 -0
  2. package/android/build.gradle +128 -0
  3. package/android/src/main/AndroidManifest.xml +2 -0
  4. package/android/src/main/cpp/CMakeLists.txt +59 -0
  5. package/android/src/main/cpp/NativBindingsInstaller.cpp +393 -0
  6. package/android/src/main/cpp/NativRuntime.cpp +508 -0
  7. package/android/src/main/java/com/nativfabric/ComposeHost.kt +26 -0
  8. package/android/src/main/java/com/nativfabric/NativContainerPackage.kt +35 -0
  9. package/android/src/main/java/com/nativfabric/NativContainerView.kt +51 -0
  10. package/android/src/main/java/com/nativfabric/NativContainerViewManager.kt +62 -0
  11. package/android/src/main/java/com/nativfabric/NativRuntime.kt +201 -0
  12. package/android/src/main/java/com/nativfabric/NativRuntimeModule.kt +37 -0
  13. package/android/src/main/java/com/nativfabric/compose/ComposeWrappers.kt +45 -0
  14. package/app.plugin.js +159 -0
  15. package/expo-module.config.json +6 -0
  16. package/ios/NativContainerComponentView.mm +137 -0
  17. package/ios/NativRuntime.h +36 -0
  18. package/ios/NativRuntime.mm +549 -0
  19. package/metro/Nativ.h +126 -0
  20. package/metro/compilers/android-compiler.js +339 -0
  21. package/metro/compilers/dylib-compiler.js +474 -0
  22. package/metro/compilers/kotlin-compiler.js +632 -0
  23. package/metro/compilers/rust-compiler.js +722 -0
  24. package/metro/compilers/static-compiler.js +1118 -0
  25. package/metro/compilers/swift-compiler.js +363 -0
  26. package/metro/extractors/cpp-ast-extractor.js +126 -0
  27. package/metro/extractors/kotlin-extractor.js +125 -0
  28. package/metro/extractors/rust-extractor.js +118 -0
  29. package/metro/index.js +236 -0
  30. package/metro/transformer.js +649 -0
  31. package/metro/utils/bridge-generator.js +50 -0
  32. package/metro/utils/compile-commands.js +104 -0
  33. package/metro/utils/cpp-daemon.js +344 -0
  34. package/metro/utils/dts-generator.js +32 -0
  35. package/metro/utils/include-resolver.js +73 -0
  36. package/metro/utils/kotlin-daemon.js +394 -0
  37. package/metro/utils/type-mapper.js +63 -0
  38. package/package.json +43 -0
  39. package/react-native.config.js +13 -0
  40. package/src/NativContainerNativeComponent.ts +9 -0
  41. package/src/NativeNativRuntime.ts +8 -0
  42. package/src/index.ts +4 -0
@@ -0,0 +1,549 @@
1
+ // NativRuntime.mm — C++ function registry + JSI bridge.
2
+ //
3
+ // 1. Bridge files call nativ_register_sync/async at +load time.
4
+ // 2. When the JS runtime is ready, we install global.__nativ with callSync/callAsync.
5
+ // 3. JS shims generated by the Metro transformer call global.__nativ.callSync(...).
6
+
7
+ #import <Foundation/Foundation.h>
8
+ #include <jsi/jsi.h>
9
+ #include <ReactCommon/CallInvoker.h>
10
+ #if DEBUG
11
+ #include <dlfcn.h> // Dev only — loadDylib uses dlopen for hot-reload
12
+ #endif
13
+ #include <string>
14
+ #include <unordered_map>
15
+ #include <unordered_set>
16
+ #include <mutex>
17
+ #include <vector>
18
+
19
+ #import "NativRuntime.h"
20
+
21
+ using namespace facebook;
22
+
23
+ // ─── Registry ──────────────────────────────────────────────────────────
24
+
25
+ struct FnEntry {
26
+ NativSyncFn syncFn = nullptr;
27
+ NativAsyncFn asyncFn = nullptr;
28
+ };
29
+
30
+ // key = "moduleId::fnName"
31
+ static std::unordered_map<std::string, FnEntry>& getRegistry() {
32
+ static std::unordered_map<std::string, FnEntry> reg;
33
+ return reg;
34
+ }
35
+
36
+ static std::string makeKey(const char* moduleId, const char* fnName) {
37
+ return std::string(moduleId) + "::" + fnName;
38
+ }
39
+
40
+ extern "C" void nativ_register_sync(const char* moduleId, const char* fnName, NativSyncFn fn) {
41
+ getRegistry()[makeKey(moduleId, fnName)].syncFn = fn;
42
+ NSLog(@"[Nativ] registered sync: %s::%s", moduleId, fnName);
43
+ }
44
+
45
+ extern "C" void nativ_register_async(const char* moduleId, const char* fnName, NativAsyncFn fn) {
46
+ getRegistry()[makeKey(moduleId, fnName)].asyncFn = fn;
47
+ NSLog(@"[Nativ] registered async: %s::%s", moduleId, fnName);
48
+ }
49
+
50
+ // ─── Component render registry ────────────────────────────────────────
51
+
52
+ static std::unordered_map<std::string, NativRenderFn>& getRenderRegistry() {
53
+ static std::unordered_map<std::string, NativRenderFn> reg;
54
+ return reg;
55
+ }
56
+
57
+ extern "C" void nativ_register_render(const char* componentId, NativRenderFn fn) {
58
+ getRenderRegistry()[std::string(componentId)] = fn;
59
+ NSLog(@"[Nativ] registered render: %s", componentId);
60
+ }
61
+
62
+ // ─── Props storage ─────────────────────────────────────────────────────
63
+ // Props are stored as live jsi::Object references. Both setComponentProps and
64
+ // nativ_try_render run on the JS thread, so the object is alive during render.
65
+ // This supports ALL prop types: strings, numbers, bools, objects, arrays, null.
66
+
67
+ static jsi::Runtime* g_runtime = nullptr;
68
+ static std::shared_ptr<facebook::react::CallInvoker> g_callInvoker = nullptr;
69
+
70
+ static std::unordered_map<std::string, std::shared_ptr<jsi::Object>>& getPropsStore() {
71
+ static std::unordered_map<std::string, std::shared_ptr<jsi::Object>> store;
72
+ return store;
73
+ }
74
+
75
+ extern "C" void nativ_register_view(const char*, void*) {}
76
+ extern "C" void nativ_unregister_view(const char*) {}
77
+
78
+ static std::string g_currentRenderingComponent;
79
+
80
+ extern "C" const char* nativ_try_render(const char* componentId, void* view, float w, float h) {
81
+ auto &reg = getRenderRegistry();
82
+ auto it = reg.find(std::string(componentId));
83
+ if (it == reg.end()) return nullptr;
84
+
85
+ void* runtime_ptr = nullptr;
86
+ void* props_ptr = nullptr;
87
+ auto &store = getPropsStore();
88
+ auto sit = store.find(std::string(componentId));
89
+ if (sit != store.end() && sit->second && g_runtime) {
90
+ runtime_ptr = reinterpret_cast<void*>(g_runtime);
91
+ props_ptr = reinterpret_cast<void*>(sit->second.get());
92
+ }
93
+
94
+ g_currentRenderingComponent = std::string(componentId);
95
+ it->second(view, w, h, runtime_ptr, props_ptr);
96
+ g_currentRenderingComponent.clear();
97
+ return "ok";
98
+ }
99
+
100
+ // ─── JSI value access (C wrappers for Rust/C++/Swift) ─────────────────
101
+ // All functions receive a live jsi::Runtime* and jsi::Object* from the
102
+ // props store. Supports all JS types: string, number, bool, object, array, null.
103
+
104
+ extern "C" const char* nativ_jsi_get_string(void* runtime, void* object, const char* prop_name) {
105
+ if (!runtime || !object) return "";
106
+ auto &rt = *reinterpret_cast<jsi::Runtime*>(runtime);
107
+ auto &obj = *reinterpret_cast<jsi::Object*>(object);
108
+ auto val = obj.getProperty(rt, prop_name);
109
+ if (val.isString()) {
110
+ static thread_local std::string buf;
111
+ buf = val.asString(rt).utf8(rt);
112
+ return buf.c_str();
113
+ }
114
+ return "";
115
+ }
116
+
117
+ extern "C" double nativ_jsi_get_number(void* runtime, void* object, const char* prop_name) {
118
+ if (!runtime || !object) return 0.0;
119
+ auto &rt = *reinterpret_cast<jsi::Runtime*>(runtime);
120
+ auto &obj = *reinterpret_cast<jsi::Object*>(object);
121
+ auto val = obj.getProperty(rt, prop_name);
122
+ if (val.isNumber()) return val.asNumber();
123
+ return 0.0;
124
+ }
125
+
126
+ extern "C" int nativ_jsi_get_bool(void* runtime, void* object, const char* prop_name) {
127
+ if (!runtime || !object) return 0;
128
+ auto &rt = *reinterpret_cast<jsi::Runtime*>(runtime);
129
+ auto &obj = *reinterpret_cast<jsi::Object*>(object);
130
+ auto val = obj.getProperty(rt, prop_name);
131
+ if (val.isBool()) return val.getBool() ? 1 : 0;
132
+ return 0;
133
+ }
134
+
135
+ extern "C" int nativ_jsi_has_prop(void* runtime, void* object, const char* prop_name) {
136
+ if (!runtime || !object) return 0;
137
+ auto &rt = *reinterpret_cast<jsi::Runtime*>(runtime);
138
+ auto &obj = *reinterpret_cast<jsi::Object*>(object);
139
+ return obj.hasProperty(rt, prop_name) ? 1 : 0;
140
+ }
141
+
142
+ extern "C" void nativ_jsi_call_function(void* runtime, void* object, const char* prop_name) {
143
+ if (!runtime || !object) return;
144
+ auto &rt = *reinterpret_cast<jsi::Runtime*>(runtime);
145
+ auto &obj = *reinterpret_cast<jsi::Object*>(object);
146
+ auto val = obj.getProperty(rt, prop_name);
147
+ if (val.isObject() && val.asObject(rt).isFunction(rt)) {
148
+ val.asObject(rt).asFunction(rt).call(rt);
149
+ }
150
+ }
151
+
152
+ extern "C" void nativ_jsi_call_function_with_string(void* runtime, void* object,
153
+ const char* prop_name, const char* arg) {
154
+ if (!runtime || !object) return;
155
+ auto &rt = *reinterpret_cast<jsi::Runtime*>(runtime);
156
+ auto &obj = *reinterpret_cast<jsi::Object*>(object);
157
+ auto val = obj.getProperty(rt, prop_name);
158
+ if (val.isObject() && val.asObject(rt).isFunction(rt)) {
159
+ val.asObject(rt).asFunction(rt).call(rt, jsi::String::createFromUtf8(rt, arg));
160
+ }
161
+ }
162
+
163
+ // ─── New: array and object access ─────────────────────────────────────
164
+
165
+ extern "C" int nativ_jsi_is_array(void* runtime, void* object, const char* prop_name) {
166
+ if (!runtime || !object) return 0;
167
+ auto &rt = *reinterpret_cast<jsi::Runtime*>(runtime);
168
+ auto &obj = *reinterpret_cast<jsi::Object*>(object);
169
+ auto val = obj.getProperty(rt, prop_name);
170
+ return (val.isObject() && val.asObject(rt).isArray(rt)) ? 1 : 0;
171
+ }
172
+
173
+ extern "C" int nativ_jsi_get_array_length(void* runtime, void* object, const char* prop_name) {
174
+ if (!runtime || !object) return 0;
175
+ auto &rt = *reinterpret_cast<jsi::Runtime*>(runtime);
176
+ auto &obj = *reinterpret_cast<jsi::Object*>(object);
177
+ auto val = obj.getProperty(rt, prop_name);
178
+ if (val.isObject() && val.asObject(rt).isArray(rt)) {
179
+ return (int)val.asObject(rt).asArray(rt).size(rt);
180
+ }
181
+ return 0;
182
+ }
183
+
184
+ extern "C" int nativ_jsi_is_object(void* runtime, void* object, const char* prop_name) {
185
+ if (!runtime || !object) return 0;
186
+ auto &rt = *reinterpret_cast<jsi::Runtime*>(runtime);
187
+ auto &obj = *reinterpret_cast<jsi::Object*>(object);
188
+ auto val = obj.getProperty(rt, prop_name);
189
+ return (val.isObject() && !val.asObject(rt).isArray(rt) && !val.asObject(rt).isFunction(rt)) ? 1 : 0;
190
+ }
191
+
192
+ extern "C" int nativ_jsi_is_null(void* runtime, void* object, const char* prop_name) {
193
+ if (!runtime || !object) return 1;
194
+ auto &rt = *reinterpret_cast<jsi::Runtime*>(runtime);
195
+ auto &obj = *reinterpret_cast<jsi::Object*>(object);
196
+ auto val = obj.getProperty(rt, prop_name);
197
+ return (val.isNull() || val.isUndefined()) ? 1 : 0;
198
+ }
199
+
200
+ // ─── JSI installation ──────────────────────────────────────────────────
201
+
202
+ static void installNativRuntime(jsi::Runtime &rt, std::shared_ptr<facebook::react::CallInvoker> callInvoker = nullptr) {
203
+ g_callInvoker = callInvoker;
204
+ // Props from a previous runtime are now invalid — the old runtime is gone.
205
+ // We can't destroy jsi::Objects tied to a dead runtime (invalidate() crashes).
206
+ // Leak them intentionally — small, only happens on hot-reload.
207
+ {
208
+ auto &store = getPropsStore();
209
+ for (auto &[k, v] : store) {
210
+ new std::shared_ptr<jsi::Object>(std::move(v)); // leak the shared_ptr
211
+ }
212
+ store.clear();
213
+ }
214
+ g_runtime = nullptr;
215
+
216
+ auto nativObj = jsi::Object(rt);
217
+
218
+ // __nativ.callSync(moduleId, fnName, argsJson) → string
219
+ auto callSync = jsi::Function::createFromHostFunction(
220
+ rt,
221
+ jsi::PropNameID::forAscii(rt, "callSync"),
222
+ 3,
223
+ [](jsi::Runtime &rt,
224
+ const jsi::Value &,
225
+ const jsi::Value *args,
226
+ size_t count) -> jsi::Value {
227
+
228
+ if (count < 3) {
229
+ throw jsi::JSError(rt, "__nativ.callSync requires 3 args: moduleId, fnName, argsJson");
230
+ }
231
+
232
+ auto moduleId = args[0].asString(rt).utf8(rt);
233
+ auto fnName = args[1].asString(rt).utf8(rt);
234
+ auto argsJson = args[2].asString(rt).utf8(rt);
235
+
236
+ auto key = moduleId + "::" + fnName;
237
+ auto &reg = getRegistry();
238
+ auto it = reg.find(key);
239
+ if (it == reg.end() || !it->second.syncFn) {
240
+ throw jsi::JSError(rt, "Unknown Nativ function: " + key);
241
+ }
242
+
243
+ const char* result = it->second.syncFn(argsJson.c_str());
244
+ if (!result) return jsi::Value::null();
245
+
246
+ return jsi::String::createFromUtf8(rt, result);
247
+ });
248
+
249
+ // __nativ.callAsync(moduleId, fnName, argsJson) → Promise
250
+ auto callAsync = jsi::Function::createFromHostFunction(
251
+ rt,
252
+ jsi::PropNameID::forAscii(rt, "callAsync"),
253
+ 3,
254
+ [](jsi::Runtime &rt,
255
+ const jsi::Value &,
256
+ const jsi::Value *args,
257
+ size_t count) -> jsi::Value {
258
+
259
+ if (count < 3) {
260
+ throw jsi::JSError(rt, "__nativ.callAsync requires 3 args: moduleId, fnName, argsJson");
261
+ }
262
+
263
+ auto moduleId = args[0].asString(rt).utf8(rt);
264
+ auto fnName = args[1].asString(rt).utf8(rt);
265
+ auto argsJson = args[2].asString(rt).utf8(rt);
266
+
267
+ auto key = moduleId + "::" + fnName;
268
+ auto &reg = getRegistry();
269
+ auto it = reg.find(key);
270
+ if (it == reg.end() || !it->second.asyncFn) {
271
+ throw jsi::JSError(rt, "Unknown Nativ async function: " + key);
272
+ }
273
+
274
+ auto asyncFn = it->second.asyncFn;
275
+
276
+ // Create a Promise via JSI
277
+ auto Promise = rt.global().getPropertyAsFunction(rt, "Promise");
278
+ auto executor = jsi::Function::createFromHostFunction(
279
+ rt,
280
+ jsi::PropNameID::forAscii(rt, "executor"),
281
+ 2,
282
+ [asyncFn, argsJson](jsi::Runtime &rt,
283
+ const jsi::Value &,
284
+ const jsi::Value *pargs,
285
+ size_t) -> jsi::Value {
286
+
287
+ auto resolve = std::make_shared<jsi::Function>(pargs[0].asObject(rt).asFunction(rt));
288
+ auto reject = std::make_shared<jsi::Function>(pargs[1].asObject(rt).asFunction(rt));
289
+ auto invoker = g_callInvoker;
290
+
291
+ // Context struct for C callback bridge.
292
+ // NativAsyncFn uses plain C function pointers — can't capture.
293
+ // We pass context via a thread-local pointer.
294
+ struct AsyncCtx {
295
+ std::shared_ptr<jsi::Function> resolve;
296
+ std::shared_ptr<jsi::Function> reject;
297
+ std::shared_ptr<facebook::react::CallInvoker> invoker;
298
+ };
299
+ // Stored on heap, freed after callbacks fire.
300
+ static thread_local AsyncCtx* _asyncCtx = nullptr;
301
+ auto ctx = new AsyncCtx{resolve, reject, invoker};
302
+
303
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
304
+ auto argsCopy = argsJson;
305
+ _asyncCtx = ctx;
306
+
307
+ asyncFn(
308
+ argsCopy.c_str(),
309
+ // resolve callback (plain C function pointer)
310
+ [](const char* result) {
311
+ auto* c = _asyncCtx;
312
+ if (!c) return;
313
+ _asyncCtx = nullptr;
314
+ auto resultStr = std::string(result ? result : "null");
315
+ auto res = c->resolve;
316
+ auto inv = c->invoker;
317
+ delete c;
318
+ if (inv) {
319
+ inv->invokeAsync([res, resultStr]() {
320
+ auto &rt = *g_runtime;
321
+ auto json = rt.global().getPropertyAsObject(rt, "JSON");
322
+ auto parse = json.getPropertyAsFunction(rt, "parse");
323
+ auto parsed = parse.call(rt, jsi::String::createFromUtf8(rt, resultStr));
324
+ res->call(rt, std::move(parsed));
325
+ });
326
+ }
327
+ },
328
+ // reject callback (plain C function pointer)
329
+ [](const char* code, const char* msg) {
330
+ auto* c = _asyncCtx;
331
+ if (!c) return;
332
+ _asyncCtx = nullptr;
333
+ auto codeStr = std::string(code ? code : "ASYNC_ERROR");
334
+ auto msgStr = std::string(msg ? msg : "Unknown error");
335
+ auto rej = c->reject;
336
+ auto inv = c->invoker;
337
+ delete c;
338
+ if (inv) {
339
+ inv->invokeAsync([rej, msgStr]() {
340
+ auto &rt = *g_runtime;
341
+ rej->call(rt, jsi::String::createFromUtf8(rt, msgStr));
342
+ });
343
+ }
344
+ });
345
+ });
346
+
347
+ return jsi::Value::undefined();
348
+ });
349
+
350
+ return Promise.callAsConstructor(rt, executor);
351
+ });
352
+
353
+ // __nativ.modules() → list registered module::fn keys (for debugging)
354
+ auto listModules = jsi::Function::createFromHostFunction(
355
+ rt,
356
+ jsi::PropNameID::forAscii(rt, "modules"),
357
+ 0,
358
+ [](jsi::Runtime &rt,
359
+ const jsi::Value &,
360
+ const jsi::Value *,
361
+ size_t) -> jsi::Value {
362
+ auto &reg = getRegistry();
363
+ auto arr = jsi::Array(rt, reg.size());
364
+ size_t i = 0;
365
+ for (auto &[key, _] : reg) {
366
+ arr.setValueAtIndex(rt, i++, jsi::String::createFromUtf8(rt, key));
367
+ }
368
+ return arr;
369
+ });
370
+
371
+ // In release builds there are no dylibs to load — all native code is statically linked.
372
+ #if DEBUG
373
+ auto loadDylib = jsi::Function::createFromHostFunction(
374
+ rt,
375
+ jsi::PropNameID::forAscii(rt, "loadDylib"),
376
+ 1,
377
+ [](jsi::Runtime &rt,
378
+ const jsi::Value &,
379
+ const jsi::Value *args,
380
+ size_t count) -> jsi::Value {
381
+
382
+ if (count < 1 || !args[0].isString()) {
383
+ throw jsi::JSError(rt, "__nativ.loadDylib requires a URL string");
384
+ }
385
+
386
+ auto urlStr = args[0].asString(rt).utf8(rt);
387
+ NSURL *url = [NSURL URLWithString:[NSString stringWithUTF8String:urlStr.c_str()]];
388
+
389
+ // Synchronous download to app sandbox (PoC — production would be async)
390
+ NSError *err = nil;
391
+ NSLog(@"[Nativ] Downloading dylib from: %@", url);
392
+ // Use NSURLRequest with no-cache policy to bypass NSURLCache
393
+ NSURLRequest *request = [NSURLRequest requestWithURL:url
394
+ cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
395
+ timeoutInterval:120.0];
396
+ NSURLResponse *response = nil;
397
+ NSData *data = [NSURLConnection sendSynchronousRequest:request
398
+ returningResponse:&response
399
+ error:&err];
400
+ if (!data) {
401
+ auto msg = std::string("Failed to download dylib: ") +
402
+ (err ? err.localizedDescription.UTF8String : "unknown");
403
+ throw jsi::JSError(rt, msg);
404
+ }
405
+
406
+ // Verify we got a Mach-O file (magic: 0xFEEDFACF for 64-bit)
407
+ NSLog(@"[Nativ] Downloaded %lu bytes", (unsigned long)data.length);
408
+ if (data.length >= 4) {
409
+ uint32_t magic = 0;
410
+ [data getBytes:&magic length:4];
411
+ NSLog(@"[Nativ] Magic: 0x%08X (expected 0xFEEDFACF for arm64)", magic);
412
+ if (magic != 0xFEEDFACF && magic != 0xCFFAEDFE) {
413
+ // Not a Mach-O — log first bytes for debugging
414
+ const char *bytes = (const char *)data.bytes;
415
+ NSString *preview = [[NSString alloc] initWithBytes:bytes
416
+ length:MIN(100, data.length)
417
+ encoding:NSUTF8StringEncoding];
418
+ NSLog(@"[Nativ] Not Mach-O! First bytes: %@", preview ?: @"(binary)");
419
+ throw jsi::JSError(rt, "Downloaded data is not a valid Mach-O dylib");
420
+ }
421
+ }
422
+
423
+ // Write to sandbox with unique name (timestamp-based)
424
+ NSString *docsDir = NSSearchPathForDirectoriesInDomains(
425
+ NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
426
+ NSString *dylibDir = [docsDir stringByAppendingPathComponent:@"nativ_dylibs"];
427
+ [[NSFileManager defaultManager] createDirectoryAtPath:dylibDir
428
+ withIntermediateDirectories:YES
429
+ attributes:nil
430
+ error:nil];
431
+
432
+ // Use a unique filename so dlopen sees it as a new library
433
+ NSString *filename = [NSString stringWithFormat:@"nativ_%llu.dylib",
434
+ (unsigned long long)[[NSDate date] timeIntervalSince1970] * 1000];
435
+ NSString *localPath = [dylibDir stringByAppendingPathComponent:filename];
436
+ [data writeToFile:localPath atomically:YES];
437
+
438
+ // dlopen with RTLD_GLOBAL so symbols from shared dylibs (native.dylib)
439
+ // are available to subsequently loaded component dylibs.
440
+ void *handle = dlopen(localPath.UTF8String, RTLD_NOW | RTLD_GLOBAL);
441
+ if (!handle) {
442
+ auto msg = std::string("dlopen failed: ") + (dlerror() ?: "unknown");
443
+ NSLog(@"[Nativ] %s", msg.c_str());
444
+ throw jsi::JSError(rt, msg);
445
+ }
446
+
447
+ NSLog(@"[Nativ] Hot-reloaded dylib: %@", localPath);
448
+ return jsi::Value(true);
449
+ });
450
+ #endif
451
+
452
+ // __nativ.setComponentProps(componentId, propsObject)
453
+ // Stores the live JSI object. nativ_try_render passes it directly to the
454
+ // render function. Supports all prop types (objects, arrays, functions, etc).
455
+ auto setComponentProps = jsi::Function::createFromHostFunction(
456
+ rt,
457
+ jsi::PropNameID::forAscii(rt, "setComponentProps"),
458
+ 2,
459
+ [](jsi::Runtime &rt,
460
+ const jsi::Value &,
461
+ const jsi::Value *args,
462
+ size_t count) -> jsi::Value {
463
+ if (count < 2 || !args[0].isString()) return jsi::Value::undefined();
464
+
465
+ auto componentId = args[0].asString(rt).utf8(rt);
466
+ auto &store = getPropsStore();
467
+
468
+ if (args[1].isObject()) {
469
+ store[componentId] = std::make_shared<jsi::Object>(args[1].asObject(rt));
470
+ } else {
471
+ store.erase(componentId);
472
+ }
473
+
474
+ return jsi::Value::undefined();
475
+ });
476
+
477
+ nativObj.setProperty(rt, "callSync", std::move(callSync));
478
+ nativObj.setProperty(rt, "callAsync", std::move(callAsync));
479
+ nativObj.setProperty(rt, "modules", std::move(listModules));
480
+ #if DEBUG
481
+ nativObj.setProperty(rt, "loadDylib", std::move(loadDylib));
482
+ #endif
483
+ nativObj.setProperty(rt, "setComponentProps", std::move(setComponentProps));
484
+
485
+ // Device/simulator target — used by JS shim to request correct dylib
486
+ #if TARGET_OS_SIMULATOR
487
+ nativObj.setProperty(rt, "target", jsi::String::createFromUtf8(rt, "simulator"));
488
+ #else
489
+ nativObj.setProperty(rt, "target", jsi::String::createFromUtf8(rt, "device"));
490
+ #endif
491
+
492
+ rt.global().setProperty(rt, "__nativ", std::move(nativObj));
493
+ g_runtime = &rt; // Store for render function prop access
494
+ NSLog(@"[Nativ] __nativ installed with %lu registered functions", (unsigned long)getRegistry().size());
495
+ }
496
+
497
+ // ─── TurboModule with JSI bindings ─────────────────────────────────────
498
+ // Codegen-backed TurboModule. RN calls installJSIBindingsWithRuntime:
499
+ // when the module is first accessed → installs global.__nativ.
500
+ // No dependency on the old expo-ferrum module.
501
+
502
+ #import <React/RCTBridgeModule.h>
503
+ #import <ReactCommon/RCTTurboModule.h>
504
+ #import <ReactCommon/RCTTurboModuleWithJSIBindings.h>
505
+ #import <ReactCommon/CallInvoker.h>
506
+
507
+ #if __has_include(<NativFabricSpec/NativFabricSpec.h>)
508
+ #import <NativFabricSpec/NativFabricSpec.h>
509
+ #elif __has_include(<ReactCodegen/NativFabricSpec.h>)
510
+ #import <ReactCodegen/NativFabricSpec.h>
511
+ #elif __has_include("NativFabricSpec.h")
512
+ #import "NativFabricSpec.h"
513
+ #endif
514
+
515
+ @interface NativRuntimeModule : NSObject <NativeNativRuntimeSpec, RCTTurboModuleWithJSIBindings>
516
+ @end
517
+
518
+ @implementation NativRuntimeModule
519
+
520
+ RCT_EXPORT_MODULE(NativRuntime)
521
+
522
+ - (NSDictionary *)getConstants {
523
+ return @{};
524
+ }
525
+
526
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
527
+ (const facebook::react::ObjCTurboModule::InitParams &)params {
528
+ return std::make_shared<facebook::react::NativeNativRuntimeSpecJSI>(params);
529
+ }
530
+
531
+ - (void)installJSIBindingsWithRuntime:(facebook::jsi::Runtime &)runtime
532
+ callInvoker:(const std::shared_ptr<facebook::react::CallInvoker> &)callinvoker
533
+ {
534
+ installNativRuntime(runtime, callinvoker);
535
+ }
536
+
537
+ + (BOOL)requiresMainQueueSetup {
538
+ return NO;
539
+ }
540
+
541
+ @end
542
+
543
+ // Keep nativ_cpp_install for backward compatibility with expo-ferrum (if installed)
544
+ extern "C" void nativ_cpp_install(void *runtimePtr) {
545
+ if (!runtimePtr) return;
546
+ auto &rt = *reinterpret_cast<jsi::Runtime *>(runtimePtr);
547
+ installNativRuntime(rt);
548
+ }
549
+
package/metro/Nativ.h ADDED
@@ -0,0 +1,126 @@
1
+ // Nativ.h — Runtime header for react-native-native C++/ObjC++ files.
2
+ // Include this in any .cpp or .mm file that exports functions or components to JS.
3
+
4
+ #pragma once
5
+
6
+ #include <string>
7
+ #include <vector>
8
+ #include <cstdint>
9
+ #include <stdexcept>
10
+ #include <optional>
11
+ #include <functional>
12
+
13
+ // ─── Function export annotation ───────────────────────────────────────
14
+ // NATIV_EXPORT(sync)
15
+ // int add(int a, int b) { return a + b; }
16
+
17
+ #define NATIV_EXPORT(...) __attribute__((annotate("nativ_export:" #__VA_ARGS__)))
18
+
19
+ // ─── Component system ─────────────────────────────────────────────────
20
+ // Define a props struct, then use NATIV_COMPONENT to register:
21
+ //
22
+ // struct MyProps {
23
+ // std::string title = "Hello";
24
+ // double opacity = 1.0;
25
+ // std::function<void()> onPress;
26
+ // };
27
+ //
28
+ // NATIV_COMPONENT(MyComponent, MyProps)
29
+ // void mount(void* view, float w, float h, MyProps props) {
30
+ // UIView* v = (__bridge UIView*)view;
31
+ // // ... use props.title, props.opacity, props.onPress()
32
+ // }
33
+
34
+ // JSI C API — defined in NativRuntime, resolved at dlopen
35
+ extern "C" {
36
+ typedef void (*NativRenderFn)(void*, float, float, void*, void*);
37
+ void nativ_register_render(const char*, NativRenderFn);
38
+
39
+ // Scalar props
40
+ const char* nativ_jsi_get_string(void* rt, void* obj, const char* name);
41
+ double nativ_jsi_get_number(void* rt, void* obj, const char* name);
42
+ int nativ_jsi_get_bool(void* rt, void* obj, const char* name);
43
+ int nativ_jsi_has_prop(void* rt, void* obj, const char* name);
44
+
45
+ // Callbacks
46
+ void nativ_jsi_call_function(void* rt, void* obj, const char* name);
47
+ void nativ_jsi_call_function_with_string(void* rt, void* obj, const char* name, const char* arg);
48
+
49
+ // Type checking
50
+ int nativ_jsi_is_array(void* rt, void* obj, const char* name);
51
+ int nativ_jsi_get_array_length(void* rt, void* obj, const char* name);
52
+ int nativ_jsi_is_object(void* rt, void* obj, const char* name);
53
+ int nativ_jsi_is_null(void* rt, void* obj, const char* name);
54
+ }
55
+
56
+ // Helper: extract a std::string prop
57
+ inline std::string _nativ_get_string(void* rt, void* obj, const char* name, const std::string& def = "") {
58
+ if (!rt || !obj) return def;
59
+ const char* s = nativ_jsi_get_string(rt, obj, name);
60
+ return (s && s[0]) ? std::string(s) : def;
61
+ }
62
+
63
+ // Helper: extract a double prop
64
+ inline double _nativ_get_number(void* rt, void* obj, const char* name, double def = 0.0) {
65
+ if (!rt || !obj || !nativ_jsi_has_prop(rt, obj, name)) return def;
66
+ return nativ_jsi_get_number(rt, obj, name);
67
+ }
68
+
69
+ // Helper: extract a bool prop
70
+ inline bool _nativ_get_bool(void* rt, void* obj, const char* name, bool def = false) {
71
+ if (!rt || !obj || !nativ_jsi_has_prop(rt, obj, name)) return def;
72
+ return nativ_jsi_get_bool(rt, obj, name) != 0;
73
+ }
74
+
75
+ // Helper: extract a callback prop
76
+ inline std::function<void()> _nativ_get_callback(void* rt, void* obj, const char* name) {
77
+ if (!rt || !obj || !nativ_jsi_has_prop(rt, obj, name)) return nullptr;
78
+ // Capture by value — safe only during mount()
79
+ return [rt, obj, n = std::string(name)]() {
80
+ nativ_jsi_call_function(rt, obj, n.c_str());
81
+ };
82
+ }
83
+
84
+ // nativ::component marker (parsed by Metro transformer)
85
+ // NATIV_COMPONENT(Name, PropsStruct) must be followed by:
86
+ // void mount(void* view, float w, float h, PropsStruct props) { ... }
87
+ #define NATIV_COMPONENT(name, props_type) \
88
+ /* Forward declare mount */ \
89
+ static void mount(void* view, float w, float h, props_type props); \
90
+ \
91
+ /* Render entry point — unique per component for static linking */ \
92
+ extern "C" void nativ_##name##_render(void* view, float w, float h, \
93
+ void* jsi_runtime, void* jsi_props); \
94
+ \
95
+ /* Registration */ \
96
+ __attribute__((constructor, used)) \
97
+ static void _nativ_register_##name() { \
98
+ nativ_register_render("nativ." #name, nativ_##name##_render); \
99
+ }
100
+
101
+ // ─── JSON helpers (for NATIV_EXPORT functions) ──────────────────────────
102
+
103
+ namespace nativ {
104
+
105
+ inline std::string toJson(int v) { return std::to_string(v); }
106
+ inline std::string toJson(int64_t v) { return std::to_string(v); }
107
+ inline std::string toJson(uint32_t v) { return std::to_string(v); }
108
+ inline std::string toJson(float v) { return std::to_string(v); }
109
+ inline std::string toJson(double v) { return std::to_string(v); }
110
+ inline std::string toJson(bool v) { return v ? "true" : "false"; }
111
+ inline std::string toJson(const std::string& v) {
112
+ std::string out = "\"";
113
+ for (char c : v) {
114
+ switch (c) {
115
+ case '"': out += "\\\""; break;
116
+ case '\\': out += "\\\\"; break;
117
+ case '\n': out += "\\n"; break;
118
+ default: out += c; break;
119
+ }
120
+ }
121
+ out += '"';
122
+ return out;
123
+ }
124
+ inline std::string toJson(const char* v) { return v ? toJson(std::string(v)) : "null"; }
125
+
126
+ } // namespace nativ