@lucent-lang/lucent 0.0.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 (125) hide show
  1. package/app.plugin.js +92 -0
  2. package/bin/lucent.cjs +16 -0
  3. package/core/index.cjs +11 -0
  4. package/dist/bench-CR3E7e2e.js +304 -0
  5. package/dist/build-CAIrRSYA.js +7 -0
  6. package/dist/build-check-QozuNJm5.js +92 -0
  7. package/dist/check-1SCmYlqA.js +7 -0
  8. package/dist/clean-BVwpbJ5K.js +35 -0
  9. package/dist/cli.js +406 -0
  10. package/dist/codes-CIuwXt1S.js +260 -0
  11. package/dist/compiler.js +4 -0
  12. package/dist/confirm-BYF5eY3q.js +34 -0
  13. package/dist/dashboard-B-NzjjaN.js +91 -0
  14. package/dist/dev-BYWMP7oC.js +221 -0
  15. package/dist/diagnostic-BM9sXmwl.js +30 -0
  16. package/dist/diff-BTDo3Sro.js +44 -0
  17. package/dist/doctor-C0PrfWT6.js +230 -0
  18. package/dist/doctor-DdR4JnVc.js +30 -0
  19. package/dist/explain--vV1oaRl.js +35 -0
  20. package/dist/format-D9fnIMEx.js +53 -0
  21. package/dist/init-gIabu1S_.js +207 -0
  22. package/dist/live-steps-BGcWFM9M.js +79 -0
  23. package/dist/new-module-C2mtb0op.js +66 -0
  24. package/dist/package-manager-BnxwCySi.js +30 -0
  25. package/dist/package.json +3 -0
  26. package/dist/packages-HyskKIsY.js +150 -0
  27. package/dist/pipeline-CH7-2cxu.js +232 -0
  28. package/dist/project-D8tEet0k.js +150 -0
  29. package/dist/sdk-coverage-BoW_3Efd.js +64 -0
  30. package/dist/sdk-prefetch-mo04JDZV.js +89 -0
  31. package/dist/sdk-search-Iv4K3-8a.js +124 -0
  32. package/dist/sdk-show-CkrdlqlP.js +74 -0
  33. package/dist/sdks-Xoyiy2E4.js +28 -0
  34. package/dist/src-BD7f1_aZ.js +11493 -0
  35. package/dist/steps--_h-FFS7.js +24 -0
  36. package/dist/theme-uv5bc0aK.js +134 -0
  37. package/dist/tsconfig-S4uyLNzf.js +59 -0
  38. package/dist/version-bqpIO5sP.js +26 -0
  39. package/gradle/lucent-classpath.gradle +38 -0
  40. package/gradle/lucent-classpath.init.gradle +6 -0
  41. package/gradle/lucent.gradle +24 -0
  42. package/lib/core.cjs +106 -0
  43. package/lib/globals.d.ts +36 -0
  44. package/lib/sdk/android.d.ts +8 -0
  45. package/lib/sdk/core.d.ts +23 -0
  46. package/lib/sdk/ios.d.ts +33 -0
  47. package/lib/sdk/platform.d.ts +8 -0
  48. package/lib/sdk/thread.d.ts +8 -0
  49. package/metro/index.cjs +72 -0
  50. package/metro/index.d.ts +6 -0
  51. package/metro/transformer.cjs +70 -0
  52. package/package.json +61 -0
  53. package/runtime/cpp/lucent/abort.cpp +61 -0
  54. package/runtime/cpp/lucent/abort.h +54 -0
  55. package/runtime/cpp/lucent/array.h +442 -0
  56. package/runtime/cpp/lucent/async.h +236 -0
  57. package/runtime/cpp/lucent/builtins.cpp +163 -0
  58. package/runtime/cpp/lucent/bytes.h +129 -0
  59. package/runtime/cpp/lucent/console.h +17 -0
  60. package/runtime/cpp/lucent/core.h +129 -0
  61. package/runtime/cpp/lucent/date.cpp +441 -0
  62. package/runtime/cpp/lucent/date.h +82 -0
  63. package/runtime/cpp/lucent/equality.h +87 -0
  64. package/runtime/cpp/lucent/function.h +61 -0
  65. package/runtime/cpp/lucent/generator.h +244 -0
  66. package/runtime/cpp/lucent/helpers.h +226 -0
  67. package/runtime/cpp/lucent/jserror.cpp +51 -0
  68. package/runtime/cpp/lucent/jserror.h +66 -0
  69. package/runtime/cpp/lucent/jsi/convert.cpp +202 -0
  70. package/runtime/cpp/lucent/jsi/convert.h +612 -0
  71. package/runtime/cpp/lucent/jsi/host.cpp +322 -0
  72. package/runtime/cpp/lucent/jsi/host.h +166 -0
  73. package/runtime/cpp/lucent/json.cpp +243 -0
  74. package/runtime/cpp/lucent/json.h +164 -0
  75. package/runtime/cpp/lucent/json_parse.h +138 -0
  76. package/runtime/cpp/lucent/jsstring.cpp +874 -0
  77. package/runtime/cpp/lucent/jsstring.h +152 -0
  78. package/runtime/cpp/lucent/lucent.h +24 -0
  79. package/runtime/cpp/lucent/map.h +344 -0
  80. package/runtime/cpp/lucent/native.cpp +76 -0
  81. package/runtime/cpp/lucent/native.h +116 -0
  82. package/runtime/cpp/lucent/number.cpp +545 -0
  83. package/runtime/cpp/lucent/number.h +190 -0
  84. package/runtime/cpp/lucent/ops.h +165 -0
  85. package/runtime/cpp/lucent/platform/android.cpp +437 -0
  86. package/runtime/cpp/lucent/platform/android.h +117 -0
  87. package/runtime/cpp/lucent/platform/ios.h +297 -0
  88. package/runtime/cpp/lucent/platform/ios.mm +31 -0
  89. package/runtime/cpp/lucent/regexp.cpp +440 -0
  90. package/runtime/cpp/lucent/regexp.h +107 -0
  91. package/runtime/cpp/lucent/scheduler.cpp +153 -0
  92. package/runtime/cpp/lucent/scheduler.h +160 -0
  93. package/runtime/cpp/lucent/unicode_data.inc +4051 -0
  94. package/runtime/cpp/rn/LucentModule.cpp +35 -0
  95. package/runtime/cpp/rn/LucentModule.h +37 -0
  96. package/runtime/cpp/third_party/quickjs/LICENSE +22 -0
  97. package/runtime/cpp/third_party/quickjs/README.md +11 -0
  98. package/runtime/cpp/third_party/quickjs/cutils.c +641 -0
  99. package/runtime/cpp/third_party/quickjs/cutils.h +457 -0
  100. package/runtime/cpp/third_party/quickjs/libregexp-opcode.h +73 -0
  101. package/runtime/cpp/third_party/quickjs/libregexp.c +3448 -0
  102. package/runtime/cpp/third_party/quickjs/libregexp.h +65 -0
  103. package/runtime/cpp/third_party/quickjs/libunicode-table.h +5206 -0
  104. package/runtime/cpp/third_party/quickjs/libunicode.c +2124 -0
  105. package/runtime/cpp/third_party/quickjs/libunicode.h +196 -0
  106. package/runtime/js/index.d.ts +7 -0
  107. package/runtime/js/index.js +60 -0
  108. package/runtime/native/LucentNative.podspec +28 -0
  109. package/runtime/native/android/CMakeLists.txt +51 -0
  110. package/runtime/native/android/build.gradle +22 -0
  111. package/runtime/native/android/include/lucentnative.h +16 -0
  112. package/runtime/native/android/src/main/java/dev/lucent/LucentPackage.java +25 -0
  113. package/runtime/native/android/src/main/java/dev/lucent/NativeProxy.java +132 -0
  114. package/runtime/native/ios/LucentRegistration.mm +23 -0
  115. package/runtime/native/package.json +6 -0
  116. package/runtime/native/react-native.config.js +19 -0
  117. package/runtime/test/jsi/harness.cpp +181 -0
  118. package/schemas/build.schema.json +152 -0
  119. package/schemas/check.schema.json +74 -0
  120. package/schemas/doctor.schema.json +40 -0
  121. package/schemas/lucent.schema.json +43 -0
  122. package/schemas/sdk-prefetch.schema.json +37 -0
  123. package/schemas/sdk-search.schema.json +54 -0
  124. package/ts-plugin/index.d.ts +4 -0
  125. package/ts-plugin/index.js +99 -0
@@ -0,0 +1,322 @@
1
+ #include "host.h"
2
+
3
+ #include <cstring>
4
+ #include <vector>
5
+
6
+ namespace lucent::js {
7
+
8
+ namespace {
9
+
10
+ std::mutex& registryMutex() {
11
+ static std::mutex m;
12
+ return m;
13
+ }
14
+ std::unordered_map<jsi::Runtime*, std::weak_ptr<Host>>& registry() {
15
+ static auto* r = new std::unordered_map<jsi::Runtime*, std::weak_ptr<Host>>();
16
+ return *r;
17
+ }
18
+
19
+ // Changes whenever the registry does, so a thread's cached host is known
20
+ // stale. Constant-initialized: reading it needs no guard.
21
+ std::atomic<uint64_t> registryGeneration{1};
22
+
23
+ // The last host this thread looked up: every call from JavaScript asks for
24
+ // it, and a thread nearly always talks to one runtime. One thread_local, as
25
+ // each costs a lookup on some platforms (__tlv_get_addr on Apple's).
26
+ struct CachedHost {
27
+ jsi::Runtime* runtime = nullptr;
28
+ Host* host = nullptr;
29
+ uint64_t generation = 0;
30
+ };
31
+ thread_local CachedHost cached;
32
+
33
+ // Installed on the JS global object so the runtime's teardown releases our
34
+ // JSI references on the JS thread while the runtime still exists.
35
+ class HostAnchor : public jsi::HostObject {
36
+ public:
37
+ explicit HostAnchor(std::shared_ptr<Host> host) : host_(std::move(host)) {}
38
+ ~HostAnchor() override {
39
+ if (host_) host_->invalidate();
40
+ }
41
+
42
+ private:
43
+ std::shared_ptr<Host> host_;
44
+ };
45
+
46
+ } // namespace
47
+
48
+ std::shared_ptr<Host> Host::create(jsi::Runtime& rt, JsPoster poster) {
49
+ std::shared_ptr<Host> host(new Host(rt, std::move(poster)));
50
+ {
51
+ std::lock_guard<std::mutex> g(registryMutex());
52
+ registry()[&rt] = host;
53
+ registryGeneration++;
54
+ }
55
+ rt.global().setProperty(rt, "__lucentHost", jsi::Object::createFromHostObject(rt, std::make_shared<HostAnchor>(host)));
56
+ {
57
+ LucentScope scope;
58
+ resetModuleState();
59
+ }
60
+ return host;
61
+ }
62
+
63
+ Host& Host::get(jsi::Runtime& rt) {
64
+ uint64_t generation = registryGeneration.load(std::memory_order_acquire);
65
+ CachedHost& c = cached;
66
+ if (c.runtime == &rt && c.generation == generation) return *c.host;
67
+ std::lock_guard<std::mutex> g(registryMutex());
68
+ auto it = registry().find(&rt);
69
+ if (it != registry().end()) {
70
+ if (auto h = it->second.lock()) {
71
+ c = {&rt, h.get(), registryGeneration.load(std::memory_order_relaxed)};
72
+ return *h;
73
+ }
74
+ }
75
+ throw jsi::JSError(rt, "Lucent is not initialized for this runtime");
76
+ }
77
+
78
+ const jsi::PropNameID& Host::makeProp(jsi::Runtime& rt, const PropName& name) {
79
+ if (name.id >= props_.size()) props_.resize(name.id + 1);
80
+ props_[name.id] = std::make_unique<jsi::PropNameID>(jsi::PropNameID::forUtf8(rt, name.text));
81
+ return *props_[name.id];
82
+ }
83
+
84
+ Host::Host(jsi::Runtime& rt, JsPoster poster) : rt_(rt), poster_(std::move(poster)), jsThread_(std::this_thread::get_id()) {}
85
+
86
+ Host::~Host() {
87
+ std::lock_guard<std::mutex> g(registryMutex());
88
+ auto it = registry().find(&rt_);
89
+ if (it != registry().end() && it->second.expired()) registry().erase(it);
90
+ registryGeneration++;
91
+ }
92
+
93
+ void Host::postToJs(JsTask task) {
94
+ if (!alive_.load()) return;
95
+ std::weak_ptr<Host> weak = weak_from_this();
96
+ poster_([weak, task = std::move(task)](jsi::Runtime& rt) {
97
+ auto self = weak.lock();
98
+ if (!self || !self->alive()) return;
99
+ task(rt);
100
+ });
101
+ }
102
+
103
+ void Host::invalidate() {
104
+ if (!alive_.exchange(false)) return;
105
+ promises_.clear();
106
+ functions_.clear();
107
+ prototypes_.clear();
108
+ modules_.clear();
109
+ identities_.clear();
110
+ props_.clear();
111
+ std::lock_guard<std::mutex> g(registryMutex());
112
+ auto it = registry().find(&rt_);
113
+ if (it != registry().end()) registry().erase(it);
114
+ registryGeneration++;
115
+ }
116
+
117
+ jsi::Value Host::module(jsi::Runtime& rt, const std::string& name) {
118
+ auto it = modules_.find(name);
119
+ if (it != modules_.end()) return jsi::Value(rt, it->second);
120
+ size_t count = 0;
121
+ const ModuleDef* defs = registeredModules(count);
122
+ for (size_t i = 0; i < count; i++) {
123
+ if (name == defs[i].name) {
124
+ jsi::Object exports(rt);
125
+ {
126
+ LucentScope scope;
127
+ defs[i].install(rt, *this, exports);
128
+ }
129
+ jsi::Value v(rt, exports);
130
+ modules_.emplace(name, jsi::Value(rt, v));
131
+ return v;
132
+ }
133
+ }
134
+ return jsi::Value::undefined();
135
+ }
136
+
137
+ jsi::Object Host::modules(jsi::Runtime& rt) {
138
+ jsi::Object all(rt);
139
+ size_t count = 0;
140
+ const ModuleDef* defs = registeredModules(count);
141
+ for (size_t i = 0; i < count; i++) all.setProperty(rt, defs[i].name, module(rt, defs[i].name));
142
+ return all;
143
+ }
144
+
145
+ jsi::Value Host::createPromise(jsi::Runtime& rt, uint64_t& id) {
146
+ id = nextId_++;
147
+ auto slot = std::make_shared<std::pair<std::optional<jsi::Function>, std::optional<jsi::Function>>>();
148
+ auto executor = jsi::Function::createFromHostFunction(
149
+ rt, jsi::PropNameID::forAscii(rt, "executor"), 2,
150
+ [slot](jsi::Runtime& rt, const jsi::Value&, const jsi::Value* args, size_t count) -> jsi::Value {
151
+ slot->first.emplace(args[0].getObject(rt).getFunction(rt));
152
+ slot->second.emplace(args[1].getObject(rt).getFunction(rt));
153
+ return jsi::Value::undefined();
154
+ });
155
+ jsi::Value promise = rt.global().getPropertyAsFunction(rt, "Promise").callAsConstructor(rt, executor);
156
+ promises_.emplace(id, Resolvers{std::move(*slot->first), std::move(*slot->second)});
157
+ slot->first.reset();
158
+ slot->second.reset();
159
+ return promise;
160
+ }
161
+
162
+ void Host::resolve(jsi::Runtime& rt, uint64_t id, const jsi::Value& value) {
163
+ auto it = promises_.find(id);
164
+ if (it == promises_.end()) return;
165
+ Resolvers r = std::move(it->second);
166
+ promises_.erase(it);
167
+ r.resolve.call(rt, value);
168
+ }
169
+
170
+ void Host::reject(jsi::Runtime& rt, uint64_t id, const jsi::Value& error) {
171
+ auto it = promises_.find(id);
172
+ if (it == promises_.end()) return;
173
+ Resolvers r = std::move(it->second);
174
+ promises_.erase(it);
175
+ r.reject.call(rt, error);
176
+ }
177
+
178
+ uint64_t Host::retain(jsi::Runtime&, jsi::Function fn) {
179
+ uint64_t id = nextId_++;
180
+ functions_.emplace(id, std::move(fn));
181
+ return id;
182
+ }
183
+
184
+ jsi::Function* Host::retained(uint64_t id) {
185
+ auto it = functions_.find(id);
186
+ return it == functions_.end() ? nullptr : &it->second;
187
+ }
188
+
189
+ void Host::release(uint64_t id) {
190
+ if (onJsThread()) {
191
+ functions_.erase(id);
192
+ return;
193
+ }
194
+ std::weak_ptr<Host> weak = weak_from_this();
195
+ postToJs([weak, id](jsi::Runtime&) {
196
+ if (auto self = weak.lock()) self->functions_.erase(id);
197
+ });
198
+ }
199
+
200
+ jsi::Object& Host::prototype(jsi::Runtime& rt, const char* key, PrototypeInit init) {
201
+ auto it = prototypes_.find(key);
202
+ if (it != prototypes_.end()) return it->second;
203
+ // A class keeps one prototype per runtime, not per host: JavaScript may
204
+ // hold a replaced host's exports, and instanceof compares their prototypes
205
+ // with the ones this host gives new instances. A kept prototype gets this
206
+ // host's methods, so they call it directly.
207
+ jsi::Object global = rt.global();
208
+ jsi::Value storeValue = global.getProperty(rt, "__lucentPrototypes");
209
+ jsi::Object store = storeValue.isObject() ? storeValue.getObject(rt) : jsi::Object(rt);
210
+ if (!storeValue.isObject()) global.setProperty(rt, "__lucentPrototypes", store);
211
+ jsi::Value kept = store.getProperty(rt, key);
212
+ jsi::Object proto = kept.isObject() ? kept.getObject(rt) : jsi::Object(rt);
213
+ init(rt, *this, proto);
214
+ if (!kept.isObject()) store.setProperty(rt, key, proto);
215
+ return prototypes_.emplace(key, std::move(proto)).first->second;
216
+ }
217
+
218
+ namespace {
219
+ std::atomic<uint64_t> nextIdentity{1};
220
+ } // namespace
221
+
222
+ Ref<Object> instanceOf(jsi::Runtime& rt, const jsi::Value& v) {
223
+ if (!v.isObject()) return nullptr;
224
+ jsi::Object o = v.getObject(rt);
225
+ if (!o.hasNativeState(rt)) return nullptr;
226
+ auto s = std::dynamic_pointer_cast<InstanceState>(o.getNativeState(rt));
227
+ return s ? s->object : nullptr;
228
+ }
229
+
230
+ void defineFunction(jsi::Runtime& rt, jsi::Object& target, const char* name, unsigned argc, HostFn fn) {
231
+ target.setProperty(rt, name, jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forUtf8(rt, name), argc, std::move(fn)));
232
+ }
233
+
234
+ void defineAccessor(jsi::Runtime& rt, jsi::Object& target, const char* name, HostFn getter, HostFn setter) {
235
+ jsi::Object descriptor(rt);
236
+ descriptor.setProperty(rt, "enumerable", true);
237
+ descriptor.setProperty(rt, "configurable", true);
238
+ descriptor.setProperty(rt, "get", jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forUtf8(rt, name), 0, std::move(getter)));
239
+ if (setter) {
240
+ descriptor.setProperty(rt, "set", jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forUtf8(rt, name), 1, std::move(setter)));
241
+ }
242
+ rt.global()
243
+ .getPropertyAsObject(rt, "Object")
244
+ .getPropertyAsFunction(rt, "defineProperty")
245
+ .call(rt, target, jsi::String::createFromUtf8(rt, name), descriptor);
246
+ }
247
+
248
+ void defineClass(jsi::Runtime& rt, Host& host, jsi::Object& exports, const char* name, const char* key, Host::PrototypeInit init,
249
+ unsigned argc, HostFn construct) {
250
+ jsi::Function factory = jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forUtf8(rt, name), argc, std::move(construct));
251
+ factory.setProperty(rt, "prototype", jsi::Value(rt, host.prototype(rt, key, init)));
252
+ exports.setProperty(rt, name, factory);
253
+ }
254
+
255
+ jsi::Value Host::wrap(jsi::Runtime& rt, const Ref<Object>& instance, const char* key, PrototypeInit init) {
256
+ if (!instance) return jsi::Value::null();
257
+ if (instance->jsIdentity != 0) {
258
+ auto it = identities_.find(instance->jsIdentity);
259
+ if (it != identities_.end()) {
260
+ jsi::Value v = it->second.lock(rt);
261
+ if (v.isObject()) return v;
262
+ }
263
+ } else {
264
+ instance->jsIdentity = nextIdentity.fetch_add(1);
265
+ }
266
+ jsi::Object& proto = prototype(rt, key, init);
267
+ jsi::Object obj = jsi::Object::create(rt, jsi::Value(rt, proto));
268
+ obj.setNativeState(rt, std::make_shared<InstanceState>(instance));
269
+ // Periodically drop cache entries whose JS objects were collected.
270
+ if (++identitySweep_ >= 256) {
271
+ identitySweep_ = 0;
272
+ std::vector<uint64_t> dead;
273
+ for (auto& [id, weak] : identities_) {
274
+ if (!weak.lock(rt).isObject()) dead.push_back(id);
275
+ }
276
+ for (uint64_t id : dead) identities_.erase(id);
277
+ }
278
+ identities_.insert_or_assign(instance->jsIdentity, jsi::WeakObject(rt, obj));
279
+ return jsi::Value(std::move(obj));
280
+ }
281
+
282
+ jsi::Value Host::errorToJs(jsi::Runtime& rt, const Error& e) {
283
+ std::string name = e->name.toUtf8();
284
+ const char* ctor = (name == "TypeError" || name == "RangeError") ? name.c_str() : "Error";
285
+ jsi::Object err = rt.global()
286
+ .getPropertyAsFunction(rt, ctor)
287
+ .callAsConstructor(rt, jsi::String::createFromUtf8(rt, e->message.toUtf8()))
288
+ .getObject(rt);
289
+ if (name != ctor) err.setProperty(rt, "name", jsi::String::createFromUtf8(rt, name));
290
+ if (e->code.has()) err.setProperty(rt, "code", jsi::String::createFromUtf8(rt, e->code.get().toUtf8()));
291
+ if (e->stack.has()) {
292
+ // An error that came from JavaScript keeps its original stack.
293
+ err.setProperty(rt, "stack", jsi::String::createFromUtf8(rt, e->stack.get().toUtf8()));
294
+ } else if (e->site.has()) {
295
+ // The Lucent frame where the error was created, above the JavaScript frames.
296
+ jsi::Value current = err.getProperty(rt, "stack");
297
+ std::string stack = current.isString() ? current.getString(rt).utf8(rt) : "";
298
+ size_t firstFrame = stack.find('\n');
299
+ std::string head = firstFrame == std::string::npos ? stack : stack.substr(0, firstFrame);
300
+ std::string frames = firstFrame == std::string::npos ? "" : stack.substr(firstFrame);
301
+ err.setProperty(rt, "stack", jsi::String::createFromUtf8(rt, head + "\n at " + e->site.get().toUtf8() + frames));
302
+ }
303
+ return jsi::Value(std::move(err));
304
+ }
305
+
306
+ Error Host::errorFromJs(jsi::Runtime& rt, const jsi::JSError& e) {
307
+ Error out = makeError(String::fromLatin1("Error"), String::fromUtf8(e.getMessage()));
308
+ out->stack = String::fromUtf8(e.getStack());
309
+ const jsi::Value& v = e.value();
310
+ if (v.isObject()) {
311
+ jsi::Object o = v.getObject(rt);
312
+ jsi::Value name = o.getProperty(rt, "name");
313
+ if (name.isString()) out->name = String::fromUtf8(name.getString(rt).utf8(rt));
314
+ jsi::Value code = o.getProperty(rt, "code");
315
+ if (code.isString()) out->code = String::fromUtf8(code.getString(rt).utf8(rt));
316
+ jsi::Value message = o.getProperty(rt, "message");
317
+ if (message.isString()) out->message = String::fromUtf8(message.getString(rt).utf8(rt));
318
+ }
319
+ return out;
320
+ }
321
+
322
+ } // namespace lucent::js
@@ -0,0 +1,166 @@
1
+ // Lucent runtime — the JavaScript side of the boundary.
2
+ //
3
+ // A Host is created per JS runtime (the React Native TurboModule creates it;
4
+ // tests create it directly). It owns every JSI object Lucent keeps alive:
5
+ // pending promise resolvers, JS callbacks, class prototypes and the identity
6
+ // cache. JSI values may only be touched on the JS thread, so native code
7
+ // refers to them by id and hops to the JS thread to use them.
8
+ #pragma once
9
+
10
+ #include <jsi/jsi.h>
11
+
12
+ #include <atomic>
13
+ #include <functional>
14
+ #include <memory>
15
+ #include <mutex>
16
+ #include <string>
17
+ #include <thread>
18
+ #include <unordered_map>
19
+ #include <vector>
20
+
21
+ #include "../lucent.h"
22
+
23
+ namespace lucent::js {
24
+
25
+ namespace jsi = facebook::jsi;
26
+
27
+ using JsTask = std::function<void(jsi::Runtime&)>;
28
+ /// Schedules a task on the JS thread (CallInvoker::invokeAsync in React Native).
29
+ using JsPoster = std::function<void(JsTask)>;
30
+
31
+ class Host;
32
+
33
+ /// A property name generated code reads or writes on JavaScript objects
34
+ /// (a struct's fields): its jsi::PropNameID is made once per runtime and kept
35
+ /// by the Host, instead of interning the name on every access.
36
+ struct PropName {
37
+ const char* text;
38
+ size_t id;
39
+ explicit PropName(const char* t) : text(t), id(next()) {}
40
+
41
+ private:
42
+ static size_t next() {
43
+ static std::atomic<size_t> count{0};
44
+ return count++;
45
+ }
46
+ };
47
+
48
+ /// One Lucent module as seen from JavaScript: fills `exports` with the
49
+ /// module's functions, classes and constants.
50
+ struct ModuleDef {
51
+ const char* name;
52
+ void (*install)(jsi::Runtime& rt, Host& host, jsi::Object& exports);
53
+ };
54
+
55
+ /// Implemented by generated code: the modules of this app.
56
+ const ModuleDef* registeredModules(size_t& count);
57
+ /// Implemented by generated code: resets module-level state for a new runtime.
58
+ void resetModuleState();
59
+
60
+ class Host : public std::enable_shared_from_this<Host> {
61
+ public:
62
+ /// Creates the host for `rt`. Call on the JS thread.
63
+ static std::shared_ptr<Host> create(jsi::Runtime& rt, JsPoster poster);
64
+ static Host& get(jsi::Runtime& rt);
65
+ /// The host for a call to a function `installed` defined: that host while
66
+ /// it lives, without get()'s lookup (a thread_local, which Android builds
67
+ /// for minSdk < 29 emulate with pthread_getspecific), else get()'s.
68
+ static Host& from(jsi::Runtime& rt, const std::shared_ptr<Host>& installed) {
69
+ return installed->alive() ? *installed : get(rt);
70
+ }
71
+ ~Host();
72
+
73
+ jsi::Runtime& runtime() { return rt_; }
74
+ bool onJsThread() const { return std::this_thread::get_id() == jsThread_; }
75
+ bool alive() const { return alive_.load(); }
76
+
77
+ /// Runs `task` on the JS thread unless the host was invalidated.
78
+ void postToJs(JsTask task);
79
+ /// Drops every JSI reference. Call on the JS thread before the runtime dies.
80
+ void invalidate();
81
+
82
+ /// The exports object of a module, created on first use.
83
+ jsi::Value module(jsi::Runtime& rt, const std::string& name);
84
+ /// The object with every module as a property (for tests and debugging).
85
+ jsi::Object modules(jsi::Runtime& rt);
86
+
87
+ // --- promises ---------------------------------------------------------
88
+ /// Creates a JS promise; settle it later with resolve/reject on the JS thread.
89
+ jsi::Value createPromise(jsi::Runtime& rt, uint64_t& id);
90
+ void resolve(jsi::Runtime& rt, uint64_t id, const jsi::Value& value);
91
+ void reject(jsi::Runtime& rt, uint64_t id, const jsi::Value& error);
92
+
93
+ // --- callbacks ----------------------------------------------------------
94
+ uint64_t retain(jsi::Runtime& rt, jsi::Function fn);
95
+ jsi::Function* retained(uint64_t id);
96
+ /// Safe from any thread: releases on the JS thread.
97
+ void release(uint64_t id);
98
+
99
+ // --- classes --------------------------------------------------------------
100
+ using PrototypeInit = void (*)(jsi::Runtime& rt, Host& host, jsi::Object& proto);
101
+ /// The prototype for class `key`: the runtime's own across hosts (kept on
102
+ /// the global object), with this host's methods.
103
+ jsi::Object& prototype(jsi::Runtime& rt, const char* key, PrototypeInit init);
104
+ /// The JS object for a native instance: the same object each time while
105
+ /// JavaScript still references it.
106
+ jsi::Value wrap(jsi::Runtime& rt, const Ref<Object>& instance, const char* key, PrototypeInit init);
107
+
108
+ /// Converts a Lucent error into a JS Error object (not thrown).
109
+ jsi::Value errorToJs(jsi::Runtime& rt, const Error& e);
110
+ /// Converts a caught JS exception into a Lucent error.
111
+ static Error errorFromJs(jsi::Runtime& rt, const jsi::JSError& e);
112
+
113
+ /// The runtime's id for a property name, made on first use.
114
+ const jsi::PropNameID& prop(jsi::Runtime& rt, const PropName& name) {
115
+ if (name.id < props_.size() && props_[name.id]) return *props_[name.id];
116
+ return makeProp(rt, name);
117
+ }
118
+
119
+ private:
120
+ Host(jsi::Runtime& rt, JsPoster poster);
121
+
122
+ struct Resolvers {
123
+ jsi::Function resolve;
124
+ jsi::Function reject;
125
+ };
126
+
127
+ jsi::Runtime& rt_;
128
+ JsPoster poster_;
129
+ std::thread::id jsThread_;
130
+ std::atomic<bool> alive_{true};
131
+ uint64_t nextId_ = 1;
132
+ std::unordered_map<uint64_t, Resolvers> promises_;
133
+ std::unordered_map<uint64_t, jsi::Function> functions_;
134
+ std::unordered_map<std::string, jsi::Object> prototypes_;
135
+ std::unordered_map<std::string, jsi::Value> modules_;
136
+ std::unordered_map<uint64_t, jsi::WeakObject> identities_;
137
+ size_t identitySweep_ = 0;
138
+ std::vector<std::unique_ptr<jsi::PropNameID>> props_;
139
+ const jsi::PropNameID& makeProp(jsi::Runtime& rt, const PropName& name);
140
+ };
141
+
142
+ /// Holds a strong reference to the Host while posting results back to JS.
143
+ using HostRef = std::shared_ptr<Host>;
144
+
145
+ /// NativeState attached to the JS object of a Lucent class instance.
146
+ struct InstanceState : jsi::NativeState {
147
+ explicit InstanceState(Ref<Object> o) : object(std::move(o)) {}
148
+ Ref<Object> object;
149
+ };
150
+ using InstanceStateBase = InstanceState;
151
+
152
+ /// The native instance behind a JS object, or null.
153
+ Ref<Object> instanceOf(jsi::Runtime& rt, const jsi::Value& v);
154
+
155
+ using HostFn = jsi::HostFunctionType;
156
+
157
+ void defineFunction(jsi::Runtime& rt, jsi::Object& target, const char* name, unsigned argc, HostFn fn);
158
+ /// Defines an enumerable accessor property; `setter` may be null.
159
+ void defineAccessor(jsi::Runtime& rt, jsi::Object& target, const char* name, HostFn getter, HostFn setter);
160
+ /// Exports a class: `name` is a factory function whose `prototype` is the
161
+ /// class prototype. The JS proxy wraps it in a real constructor so `new` and
162
+ /// `instanceof` work.
163
+ void defineClass(jsi::Runtime& rt, Host& host, jsi::Object& exports, const char* name, const char* key, Host::PrototypeInit init,
164
+ unsigned argc, HostFn construct);
165
+
166
+ } // namespace lucent::js