@lucent-lang/lucent 0.0.4 → 0.1.1

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 (152) hide show
  1. package/README.md +97 -0
  2. package/app.plugin.js +46 -10
  3. package/dist/{bench-CR3E7e2e.js → bench-eEez4jhQ.js} +10 -6
  4. package/dist/{build-CAIrRSYA.js → build-CpUjeHFz.js} +1 -1
  5. package/dist/{build-check-QozuNJm5.js → build-check-BA1ZEp9s.js} +28 -13
  6. package/dist/{check-1SCmYlqA.js → check-BZx3FgI6.js} +1 -1
  7. package/dist/cli.js +62 -13
  8. package/dist/{codes-CIuwXt1S.js → codes-D-6LxUqD.js} +163 -7
  9. package/dist/compiler.js +4 -4
  10. package/dist/{dashboard-B-NzjjaN.js → dashboard-CC-HXhcd.js} +2 -2
  11. package/dist/{dev-BYWMP7oC.js → dev-DI5yL8zx.js} +62 -23
  12. package/dist/{diagnostic-BM9sXmwl.js → diagnostic-BvwLZQS1.js} +3 -1
  13. package/dist/{doctor-C0PrfWT6.js → doctor-CTAX3euc.js} +1 -1
  14. package/dist/{doctor-DdR4JnVc.js → doctor-CrgOAV5L.js} +1 -1
  15. package/dist/{explain--vV1oaRl.js → explain-Byit07F-.js} +1 -1
  16. package/dist/{packages-HyskKIsY.js → packages-CSmKeaAh.js} +2 -33
  17. package/dist/pipeline-BM_VM2X_.js +700 -0
  18. package/dist/project-BKNoTEEp.js +342 -0
  19. package/dist/sdk-coverage-iiv3JqMp.js +113 -0
  20. package/dist/sdk-diff-ByJ1Gw-F.js +109 -0
  21. package/dist/sdk-lock-BQn7IyRs.js +38 -0
  22. package/dist/{sdk-prefetch-mo04JDZV.js → sdk-prefetch-Bv2pNega.js} +2 -2
  23. package/dist/{sdk-search-Iv4K3-8a.js → sdk-search-CPGJm_Ne.js} +2 -2
  24. package/dist/{sdk-show-CkrdlqlP.js → sdk-show-BUkLqRnA.js} +5 -4
  25. package/dist/sdk-usage-0ZFMAYg1.js +95 -0
  26. package/dist/src-ChnmWLwn.js +40851 -0
  27. package/dist/trace-Bi4hkmPx.js +139 -0
  28. package/lib/core.cjs +368 -1
  29. package/lib/sdk/android.d.ts +54 -1
  30. package/lib/sdk/compose.d.ts +94 -0
  31. package/lib/sdk/compose.schemas.json.gz +0 -0
  32. package/lib/sdk/core.d.ts +201 -0
  33. package/lib/sdk/ios.d.ts +75 -2
  34. package/lib/sdk/ui.d.ts +129 -0
  35. package/metro/transformer.cjs +11 -2
  36. package/package.json +4 -3
  37. package/runtime/cpp/lucent/abort.cpp +59 -11
  38. package/runtime/cpp/lucent/abort.h +22 -6
  39. package/runtime/cpp/lucent/array.h +12 -2
  40. package/runtime/cpp/lucent/async.h +101 -21
  41. package/runtime/cpp/lucent/bigint.cpp +898 -0
  42. package/runtime/cpp/lucent/bigint.h +302 -0
  43. package/runtime/cpp/lucent/buffer.cpp +228 -0
  44. package/runtime/cpp/lucent/buffer.h +328 -0
  45. package/runtime/cpp/lucent/builtins.cpp +4 -1
  46. package/runtime/cpp/lucent/bytes.h +21 -2
  47. package/runtime/cpp/lucent/callback.cpp +29 -0
  48. package/runtime/cpp/lucent/callback.h +230 -0
  49. package/runtime/cpp/lucent/compute.cpp +370 -0
  50. package/runtime/cpp/lucent/compute.h +455 -0
  51. package/runtime/cpp/lucent/conversions.h +111 -0
  52. package/runtime/cpp/lucent/core.h +16 -5
  53. package/runtime/cpp/lucent/equality.h +107 -28
  54. package/runtime/cpp/lucent/execution.cpp +416 -0
  55. package/runtime/cpp/lucent/execution.h +270 -0
  56. package/runtime/cpp/lucent/extension.cpp +97 -0
  57. package/runtime/cpp/lucent/extension.h +210 -0
  58. package/runtime/cpp/lucent/helpers.h +12 -5
  59. package/runtime/cpp/lucent/items.h +52 -0
  60. package/runtime/cpp/lucent/jserror.cpp +9 -0
  61. package/runtime/cpp/lucent/jserror.h +11 -0
  62. package/runtime/cpp/lucent/jsi/convert.cpp +138 -0
  63. package/runtime/cpp/lucent/jsi/convert.h +128 -59
  64. package/runtime/cpp/lucent/jsi/host.cpp +213 -35
  65. package/runtime/cpp/lucent/jsi/host.h +116 -16
  66. package/runtime/cpp/lucent/json.h +19 -1
  67. package/runtime/cpp/lucent/lifecycle.cpp +260 -0
  68. package/runtime/cpp/lucent/lifecycle.h +124 -0
  69. package/runtime/cpp/lucent/lucent.h +7 -0
  70. package/runtime/cpp/lucent/map.h +41 -7
  71. package/runtime/cpp/lucent/native.cpp +35 -47
  72. package/runtime/cpp/lucent/native.h +116 -26
  73. package/runtime/cpp/lucent/number.cpp +38 -38
  74. package/runtime/cpp/lucent/number.h +33 -0
  75. package/runtime/cpp/lucent/operation.h +65 -0
  76. package/runtime/cpp/lucent/ops.h +14 -6
  77. package/runtime/cpp/lucent/platform/android.cpp +223 -10
  78. package/runtime/cpp/lucent/platform/android.h +227 -4
  79. package/runtime/cpp/lucent/platform/android_activity.cpp +258 -0
  80. package/runtime/cpp/lucent/platform/android_requests.h +140 -0
  81. package/runtime/cpp/lucent/platform/compose.h +256 -0
  82. package/runtime/cpp/lucent/platform/ios.h +325 -25
  83. package/runtime/cpp/lucent/platform/ios.mm +1 -14
  84. package/runtime/cpp/lucent/platform/ios_ui.h +192 -0
  85. package/runtime/cpp/lucent/platform/ios_ui.mm +454 -0
  86. package/runtime/cpp/lucent/platform/swiftui.h +190 -0
  87. package/runtime/cpp/lucent/presentation.cpp +11 -0
  88. package/runtime/cpp/lucent/presentation.h +109 -0
  89. package/runtime/cpp/lucent/reactive.cpp +406 -0
  90. package/runtime/cpp/lucent/reactive.h +566 -0
  91. package/runtime/cpp/lucent/report.cpp +40 -0
  92. package/runtime/cpp/lucent/report.h +16 -0
  93. package/runtime/cpp/lucent/resource.cpp +83 -0
  94. package/runtime/cpp/lucent/resource.h +75 -0
  95. package/runtime/cpp/lucent/scheduler.cpp +38 -124
  96. package/runtime/cpp/lucent/scheduler.h +102 -83
  97. package/runtime/cpp/lucent/scope.cpp +308 -0
  98. package/runtime/cpp/lucent/scope.h +452 -0
  99. package/runtime/cpp/lucent/sizing.h +263 -0
  100. package/runtime/cpp/lucent/slots.h +231 -0
  101. package/runtime/cpp/lucent/trace.cpp +539 -0
  102. package/runtime/cpp/lucent/trace.h +251 -0
  103. package/runtime/cpp/lucent/transport.cpp +9 -0
  104. package/runtime/cpp/lucent/transport.h +341 -0
  105. package/runtime/cpp/lucent/view.cpp +123 -0
  106. package/runtime/cpp/lucent/view.h +157 -0
  107. package/runtime/cpp/rn/LucentComponentView.h +121 -0
  108. package/runtime/cpp/rn/LucentComponentView.mm +479 -0
  109. package/runtime/cpp/rn/LucentModule.cpp +37 -7
  110. package/runtime/cpp/rn/LucentModule.h +3 -0
  111. package/runtime/cpp/rn/LucentViewChildren.h +80 -0
  112. package/runtime/cpp/rn/LucentViewChildren.mm +211 -0
  113. package/runtime/cpp/rn/LucentViewRegistry.h +62 -0
  114. package/runtime/cpp/rn/LucentViewRequests.cpp +91 -0
  115. package/runtime/cpp/rn/LucentViewRequests.h +104 -0
  116. package/runtime/cpp/rn/LucentViewSizing.h +345 -0
  117. package/runtime/cpp/rn/LucentViewSlots.h +242 -0
  118. package/runtime/cpp/rn/LucentViewValues.h +125 -0
  119. package/runtime/cpp/rn/LucentViews.h +440 -0
  120. package/runtime/cpp/rn/LucentViewsAndroid.cpp +439 -0
  121. package/runtime/cpp/rn/LucentViewsAndroid.h +138 -0
  122. package/runtime/cpp/third_party/dragonbox/LICENSE-Boost +23 -0
  123. package/runtime/cpp/third_party/dragonbox/README.md +16 -0
  124. package/runtime/cpp/third_party/dragonbox/dragonbox.h +4440 -0
  125. package/runtime/js/index.d.ts +11 -1
  126. package/runtime/js/index.js +84 -8
  127. package/runtime/js/views.d.ts +38 -0
  128. package/runtime/js/views.js +226 -0
  129. package/runtime/native/android/CMakeLists.txt +4 -1
  130. package/runtime/native/android/src/compose/java/dev/lucent/compose/CompositionRules.kt +44 -0
  131. package/runtime/native/android/src/compose/java/dev/lucent/compose/LucentComposition.kt +161 -0
  132. package/runtime/native/android/src/main/java/dev/lucent/LucentActivities.java +292 -0
  133. package/runtime/native/android/src/main/java/dev/lucent/LucentChildren.java +174 -0
  134. package/runtime/native/android/src/main/java/dev/lucent/LucentHostView.java +232 -0
  135. package/runtime/native/android/src/main/java/dev/lucent/LucentInitializer.java +46 -0
  136. package/runtime/native/android/src/main/java/dev/lucent/LucentPackage.java +4 -3
  137. package/runtime/native/android/src/main/java/dev/lucent/LucentRequestActivity.java +92 -0
  138. package/runtime/native/android/src/main/java/dev/lucent/LucentSlotView.java +102 -0
  139. package/runtime/native/android/src/main/java/dev/lucent/LucentViewManager.java +149 -0
  140. package/runtime/native/android/src/main/java/dev/lucent/LucentViewManagers.java +16 -0
  141. package/runtime/native/android/src/main/java/dev/lucent/LucentViews.java +44 -0
  142. package/runtime/test/jsi/harness.cpp +9 -0
  143. package/schemas/build.schema.json +96 -1
  144. package/schemas/check.schema.json +56 -1
  145. package/schemas/lucent.schema.json +495 -9
  146. package/schemas/sdk-diff.schema.json +70 -0
  147. package/schemas/sdk-lock.schema.json +67 -0
  148. package/ts-plugin/index.js +85 -4
  149. package/dist/pipeline-CH7-2cxu.js +0 -232
  150. package/dist/project-D8tEet0k.js +0 -150
  151. package/dist/sdk-coverage-BoW_3Efd.js +0 -64
  152. package/dist/src-BD7f1_aZ.js +0 -11493
@@ -26,6 +26,13 @@ struct ErrorObject : Object {
26
26
  };
27
27
  using Error = Ref<ErrorObject>;
28
28
 
29
+ /// What a `using` declaration throws when disposing fails while an error is
30
+ /// pending: JavaScript's SuppressedError, with both.
31
+ struct SuppressedErrorObject : ErrorObject {
32
+ Error error;
33
+ Error suppressed;
34
+ };
35
+
29
36
  Error makeError(const String& name, const String& message);
30
37
  inline Error makeError(const String& message) { return makeError(String::fromLatin1("Error"), message); }
31
38
 
@@ -63,4 +70,8 @@ Error currentError(std::exception_ptr ex);
63
70
 
64
71
  String errorToString(const Error& e);
65
72
 
73
+ /// A SuppressedError of `error` (thrown while disposing) and `suppressed` (the
74
+ /// error pending then), as an exception to rethrow.
75
+ std::exception_ptr suppressedError(std::exception_ptr error, std::exception_ptr suppressed);
76
+
66
77
  } // namespace lucent
@@ -27,6 +27,28 @@ void throwBoundaryError(jsi::Runtime& rt, const Path& path, const char* expected
27
27
  throw jsi::JSError(rt, jsi::Value(rt, err));
28
28
  }
29
29
 
30
+ BigInt Convert<BigInt>::fromJs(jsi::Runtime& rt, const jsi::Value& v, const Path& p) {
31
+ if (!v.isBigInt()) throwBoundaryError(rt, p, "a bigint", v);
32
+
33
+ jsi::BigInt b = v.getBigInt(rt);
34
+
35
+ if (b.isInt64(rt)) return BigInt::fromInt64(b.getInt64(rt));
36
+ if (b.isUint64(rt)) return BigInt::fromUint64(b.getUint64(rt));
37
+
38
+ // Beyond 64 bits: its hex digits, after a "-" when negative.
39
+ return BigInt::fromDigits(b.toString(rt, 16).utf8(rt), 16);
40
+ }
41
+
42
+ jsi::Value Convert<BigInt>::toJs(jsi::Runtime& rt, Host&, const BigInt& v) {
43
+ if (auto i = v.tryInt64()) return jsi::Value(jsi::BigInt::fromInt64(rt, *i));
44
+ if (auto u = v.tryUint64()) return jsi::Value(jsi::BigInt::fromUint64(rt, *u));
45
+
46
+ // Beyond 64 bits: BigInt(string) builds it. Decimal, since BigInt()
47
+ // takes no sign before a 0x prefix.
48
+ jsi::Function make = rt.global().getPropertyAsFunction(rt, "BigInt");
49
+ return make.call(rt, jsi::String::createFromAscii(rt, v.toString().toUtf8()));
50
+ }
51
+
30
52
  String stringFromJs(jsi::Runtime& rt, const jsi::String& s) {
31
53
  // Hermes hands most strings over in one chunk: an ASCII one becomes the
32
54
  // String directly (inline when short). Later chunks go to the accumulator.
@@ -100,6 +122,23 @@ class OwnedBuffer : public jsi::MutableBuffer {
100
122
  private:
101
123
  std::vector<uint8_t> data_;
102
124
  };
125
+
126
+ /// Bytes no Lucent code holds (a fresh copy), lent to an ArrayBuffer as they are.
127
+ class FreshBytes : public jsi::MutableBuffer {
128
+ public:
129
+ explicit FreshBytes(Bytes bytes) : bytes_(std::move(bytes)) {}
130
+ size_t size() const override { return bytes_.size(); }
131
+ uint8_t* data() override { return bytes_.data(); }
132
+ const Bytes& bytes() const { return bytes_; }
133
+
134
+ private:
135
+ Bytes bytes_;
136
+ };
137
+
138
+ jsi::Value uint8ArrayOver(jsi::Runtime& rt, const std::shared_ptr<FreshBytes>& bytes) {
139
+ jsi::ArrayBuffer ab(rt, bytes);
140
+ return rt.global().getPropertyAsFunction(rt, "Uint8Array").callAsConstructor(rt, ab);
141
+ }
103
142
  } // namespace
104
143
 
105
144
  Bytes Convert<Bytes>::fromJs(jsi::Runtime& rt, const jsi::Value& v, const Path& p) {
@@ -122,6 +161,105 @@ jsi::Value Convert<Bytes>::toJs(jsi::Runtime& rt, Host&, const Bytes& b) {
122
161
  return rt.global().getPropertyAsFunction(rt, "Uint8Array").callAsConstructor(rt, ab);
123
162
  }
124
163
 
164
+ namespace {
165
+ const char* const kBufferPrototype = "lucent:NativeBuffer";
166
+
167
+ NativeBuffer bufferThis(jsi::Runtime& rt, const jsi::Value& self, const char* method) {
168
+ return Convert<NativeBuffer>::fromJs(rt, self, Path{method, "this"});
169
+ }
170
+
171
+ jsi::Function callbackArg(jsi::Runtime& rt, const jsi::Value* args, size_t count, const char* method) {
172
+ const jsi::Value& f = arg(args, count, 0);
173
+ if (!f.isObject() || !f.getObject(rt).isFunction(rt)) throwBoundaryError(rt, Path{method, "argument 0"}, "a function", f);
174
+
175
+ return f.getObject(rt).getFunction(rt);
176
+ }
177
+
178
+ /// A method of the handle, calling `body(rt, host, buffer, args, count)`.
179
+ template <class F>
180
+ jsi::Function bufferMethod(jsi::Runtime& rt, const char* name, unsigned argc, F body) {
181
+ return jsi::Function::createFromHostFunction(
182
+ rt, jsi::PropNameID::forAscii(rt, name), argc,
183
+ [name, body](jsi::Runtime& rt, const jsi::Value& self, const jsi::Value* args, size_t count) -> jsi::Value {
184
+ Host& host = Host::get(rt);
185
+ return callSync(rt, host, [&] { return body(rt, host, bufferThis(rt, self, name), args, count); });
186
+ });
187
+ }
188
+
189
+ void bufferPrototype(jsi::Runtime& rt, Host&, jsi::Object& proto) {
190
+ defineAccessor(
191
+ rt, proto, "byteLength",
192
+ [](jsi::Runtime& rt, const jsi::Value& self, const jsi::Value*, size_t) {
193
+ return jsi::Value(static_cast<double>(bufferThis(rt, self, "NativeBuffer.byteLength")->size()));
194
+ },
195
+ nullptr);
196
+
197
+ proto.setProperty(rt, "toUint8Array",
198
+ bufferMethod(rt, "toUint8Array", 0, [](jsi::Runtime& rt, Host&, const NativeBuffer& b, const jsi::Value*, size_t) {
199
+ return uint8ArrayOver(rt, std::make_shared<FreshBytes>(b->toBytes()));
200
+ }));
201
+
202
+ proto.setProperty(rt, "withRead",
203
+ bufferMethod(rt, "withRead", 1, [](jsi::Runtime& rt, Host&, const NativeBuffer& b, const jsi::Value* args, size_t count) {
204
+ jsi::Function read = callbackArg(rt, args, count, "withRead");
205
+
206
+ return b->withRead([&](std::span<const uint8_t> bytes) {
207
+ return read.call(rt, uint8ArrayOver(rt, std::make_shared<FreshBytes>(NativeBufferObject::copyOut(bytes))));
208
+ });
209
+ }));
210
+
211
+ proto.setProperty(rt, "withWrite",
212
+ bufferMethod(rt, "withWrite", 1, [](jsi::Runtime& rt, Host&, const NativeBuffer& b, const jsi::Value* args, size_t count) {
213
+ jsi::Function write = callbackArg(rt, args, count, "withWrite");
214
+
215
+ return b->withWrite([&](std::span<uint8_t> bytes) {
216
+ auto lent = std::make_shared<FreshBytes>(NativeBufferObject::copyOut(bytes));
217
+
218
+ // What the callback wrote comes back, even if it then threw.
219
+ struct CopyBack {
220
+ std::span<uint8_t> to;
221
+ const FreshBytes& from;
222
+ ~CopyBack() { NativeBufferObject::copyIn(to, from.bytes()); }
223
+ } back{bytes, *lent};
224
+
225
+ return write.call(rt, uint8ArrayOver(rt, lent));
226
+ });
227
+ }));
228
+
229
+ proto.setProperty(rt, "transfer",
230
+ bufferMethod(rt, "transfer", 0, [](jsi::Runtime& rt, Host& host, const NativeBuffer& b, const jsi::Value*, size_t) {
231
+ return Convert<NativeBuffer>::toJs(rt, host, b->transfer());
232
+ }));
233
+
234
+ jsi::Function close = bufferMethod(rt, "close", 0, [](jsi::Runtime&, Host&, const NativeBuffer& b, const jsi::Value*, size_t) {
235
+ b->close();
236
+ return jsi::Value::undefined();
237
+ });
238
+
239
+ // `using` in JavaScript, where the runtime has Symbol.dispose.
240
+ jsi::Value dispose = rt.global().getPropertyAsObject(rt, "Symbol").getProperty(rt, "dispose");
241
+ if (dispose.isSymbol()) {
242
+ jsi::Object descriptor(rt);
243
+ descriptor.setProperty(rt, "value", jsi::Value(rt, close));
244
+ descriptor.setProperty(rt, "configurable", true);
245
+ descriptor.setProperty(rt, "writable", true);
246
+ rt.global().getPropertyAsObject(rt, "Object").getPropertyAsFunction(rt, "defineProperty").call(rt, proto, dispose, descriptor);
247
+ }
248
+
249
+ proto.setProperty(rt, "close", std::move(close));
250
+ }
251
+ } // namespace
252
+
253
+ NativeBuffer Convert<NativeBuffer>::fromJs(jsi::Runtime& rt, const jsi::Value& v, const Path& p) {
254
+ if (auto b = std::dynamic_pointer_cast<NativeBufferObject>(instanceOf(rt, v))) return b;
255
+
256
+ throwBoundaryError(rt, p, "a NativeBuffer", v);
257
+ }
258
+
259
+ jsi::Value Convert<NativeBuffer>::toJs(jsi::Runtime& rt, Host& h, const NativeBuffer& b) {
260
+ return h.wrap(rt, b, kBufferPrototype, bufferPrototype);
261
+ }
262
+
125
263
  Error Convert<Error>::fromJs(jsi::Runtime& rt, const jsi::Value& v, const Path&) {
126
264
  Error e = makeError(String::fromLatin1("Error"), String());
127
265
  if (v.isObject()) {
@@ -11,10 +11,12 @@
11
11
 
12
12
  #include <jsi/jsi.h>
13
13
 
14
+ #include <optional>
14
15
  #include <string>
15
16
  #include <vector>
16
17
 
17
18
  #include "../lucent.h"
19
+ #include "../trace.h"
18
20
  #include "host.h"
19
21
 
20
22
  namespace lucent::js {
@@ -109,6 +111,15 @@ struct Convert<String> {
109
111
  static jsi::Value toJs(jsi::Runtime& rt, Host&, const String& v) { return jsi::Value(stringToJs(rt, v)); }
110
112
  };
111
113
 
114
+ /// Bigints cross exactly: through jsi::BigInt's 64-bit forms when the value
115
+ /// fits (no string, no allocation on the Lucent side), else through its
116
+ /// digits, and the JS side's global BigInt for the value it gets.
117
+ template <>
118
+ struct Convert<BigInt> {
119
+ static BigInt fromJs(jsi::Runtime& rt, const jsi::Value& v, const Path& p);
120
+ static jsi::Value toJs(jsi::Runtime& rt, Host& h, const BigInt& v);
121
+ };
122
+
112
123
  template <>
113
124
  struct Convert<Undefined> {
114
125
  static Undefined fromJs(jsi::Runtime& rt, const jsi::Value& v, const Path& p) {
@@ -287,6 +298,20 @@ struct Convert<Bytes> {
287
298
  static jsi::Value toJs(jsi::Runtime& rt, Host& h, const Bytes& b);
288
299
  };
289
300
 
301
+ // --- native buffers ------------------------------------------------------------------------
302
+
303
+ /// A NativeBuffer crosses as an opaque handle, the same JS object for the
304
+ /// same buffer while JavaScript holds it. Its methods mirror lucent:core's:
305
+ /// `byteLength`, `toUint8Array()`, `withRead(f)`, `withWrite(f)`,
306
+ /// `transfer()`, `close()`. JavaScript never gets an ArrayBuffer over the
307
+ /// storage: a borrow lends it a copy (a write borrow copies it back when
308
+ /// the callback returns), counted in the buffer stats.
309
+ template <>
310
+ struct Convert<NativeBuffer> {
311
+ static NativeBuffer fromJs(jsi::Runtime& rt, const jsi::Value& v, const Path& p);
312
+ static jsi::Value toJs(jsi::Runtime& rt, Host& h, const NativeBuffer& b);
313
+ };
314
+
290
315
  // --- errors ------------------------------------------------------------------------------
291
316
 
292
317
  template <>
@@ -338,6 +363,50 @@ struct Convert<AbortSignal> {
338
363
 
339
364
  // --- promises ------------------------------------------------------------------------------
340
365
 
366
+ /// Settles `host`'s JS promise `id` like `p`, once `p` settles: on the JS
367
+ /// thread, where its value converts. If the host is torn down first, the
368
+ /// value is released on the legacy module context instead.
369
+ /// Traced (`traceId`), the result's wait for the JS thread and its
370
+ /// delivery are spans of that id, the delivery named after `site`.
371
+ template <class T>
372
+ void settleLater(Host& host, uint64_t id, const Promise<T>& p, uint64_t traceId = 0, const trace::Site* site = nullptr) {
373
+ std::weak_ptr<Host> weak = host.weak_from_this();
374
+
375
+ p.onSettled([p, weak, id, traceId, site] {
376
+ auto host = weak.lock();
377
+ if (!host) return;
378
+
379
+ trace::Mark wait = traceId ? trace::begin(trace::Category::Queue, "js.wait") : trace::Mark{};
380
+
381
+ host->postToJs([p, id, weak, traceId, site, wait](jsi::Runtime& rt) {
382
+ auto host = weak.lock();
383
+ if (!host) return;
384
+
385
+ trace::end(wait, trace::Category::Queue, "js.wait", {.id = traceId});
386
+ std::optional<trace::Scope> delivery;
387
+ if (traceId) delivery.emplace(trace::Category::Completion, site ? site->name : "completion", site, traceId);
388
+
389
+ LucentScope scope;
390
+ if (!p.fulfilled()) {
391
+ host->reject(rt, id, host->errorToJs(rt, p.error()));
392
+ return;
393
+ }
394
+
395
+ jsi::Value value = jsi::Value::undefined();
396
+ if constexpr (!std::is_void_v<T>) {
397
+ try {
398
+ value = Convert<T>::toJs(rt, *host, p.value());
399
+ } catch (const jsi::JSError& e) {
400
+ host->reject(rt, id, jsi::Value(rt, e.value()));
401
+ return;
402
+ }
403
+ }
404
+
405
+ host->resolve(rt, id, value);
406
+ });
407
+ });
408
+ }
409
+
341
410
  /// Hands a Lucent promise to JavaScript: the JS promise settles on the JS
342
411
  /// thread when the Lucent one does.
343
412
  template <class T>
@@ -345,32 +414,7 @@ struct Convert<Promise<T>> {
345
414
  static jsi::Value toJs(jsi::Runtime& rt, Host& h, const Promise<T>& p) {
346
415
  uint64_t id = 0;
347
416
  jsi::Value jsPromise = h.createPromise(rt, id);
348
- std::weak_ptr<Host> weak = h.weak_from_this();
349
- p.onSettled([p, weak, id] {
350
- auto host = weak.lock();
351
- if (!host) return;
352
- host->postToJs([p, id, weak](jsi::Runtime& rt) {
353
- auto host = weak.lock();
354
- if (!host) return;
355
- LucentScope scope;
356
- if (p.fulfilled()) {
357
- jsi::Value value = jsi::Value::undefined();
358
- try {
359
- if constexpr (std::is_void_v<T>) {
360
- value = jsi::Value::undefined();
361
- } else {
362
- value = Convert<T>::toJs(rt, *host, p.value());
363
- }
364
- } catch (const jsi::JSError& e) {
365
- host->reject(rt, id, jsi::Value(rt, e.value()));
366
- return;
367
- }
368
- host->resolve(rt, id, value);
369
- } else {
370
- host->reject(rt, id, host->errorToJs(rt, p.error()));
371
- }
372
- });
373
- });
417
+ settleLater(h, id, p);
374
418
  return jsPromise;
375
419
  }
376
420
  /// A JS promise passed into Lucent (e.g. returned by a callback).
@@ -415,7 +459,7 @@ struct Convert<Fn<R(A...)>> {
415
459
  auto host = cb->host();
416
460
  if (!host || !host->alive()) {
417
461
  if constexpr (std::is_void_v<R>) return;
418
- else throwError(String::fromLatin1("Error"), String::fromLatin1("The JavaScript runtime is gone"));
462
+ else throwError(Host::goneError());
419
463
  }
420
464
  if (host->onJsThread()) {
421
465
  jsi::Runtime& rt = host->runtime();
@@ -450,6 +494,8 @@ struct Convert<Fn<R(A...)>> {
450
494
  return;
451
495
  } else if constexpr (IsPromiseType<R>::value) {
452
496
  R out;
497
+ // If the task never runs (the runtime went), the promise rejects.
498
+ auto gone = [out] { out.reject(Host::goneError()); };
453
499
  host->postToJs([cb, out, where, args...](jsi::Runtime& rt) mutable {
454
500
  auto host = cb->host();
455
501
  jsi::Function* fn = host ? host->retained(cb->id()) : nullptr;
@@ -474,7 +520,7 @@ struct Convert<Fn<R(A...)>> {
474
520
  } catch (const Exception& e) {
475
521
  out.reject(e.error());
476
522
  }
477
- });
523
+ }, gone);
478
524
  return out;
479
525
  } else {
480
526
  throwError(String::fromLatin1("Error"),
@@ -487,13 +533,14 @@ struct Convert<Fn<R(A...)>> {
487
533
  return jsi::Function::createFromHostFunction(
488
534
  rt, jsi::PropNameID::forAscii(rt, "lucentFunction"), sizeof...(A),
489
535
  [f, weak](jsi::Runtime& rt, const jsi::Value&, const jsi::Value* args, size_t count) -> jsi::Value {
490
- auto host = weak.lock();
491
- if (!host) throw jsi::JSError(rt, "Lucent host is gone");
536
+ // A torn-down host's function answers through the runtime's host.
537
+ auto kept = weak.lock();
538
+ Host& host = kept && kept->alive() ? *kept : Host::get(rt);
492
539
  LucentScope scope;
493
540
  try {
494
- return call(rt, *host, f, args, count, std::index_sequence_for<A...>{});
541
+ return call(rt, host, f, args, count, std::index_sequence_for<A...>{});
495
542
  } catch (const Exception& e) {
496
- throw jsi::JSError(rt, host->errorToJs(rt, e.error()));
543
+ throw jsi::JSError(rt, host.errorToJs(rt, e.error()));
497
544
  }
498
545
  });
499
546
  }
@@ -521,10 +568,22 @@ Promise<T> Convert<Promise<T>>::fromJs(jsi::Runtime& rt, const jsi::Value& v, co
521
568
  else out.resolve(Convert<T>::fromJs(rt, v, p));
522
569
  return out;
523
570
  }
571
+
572
+ // Rejected if the runtime's host is torn down before JavaScript settles
573
+ // it, so what awaits it goes on (and releases what it holds).
574
+ const std::shared_ptr<Scope>& scope = Host::get(rt).scope();
575
+ Scope::CleanupId pending = scope->onDispose([out] { out.reject(Host::goneError()); });
576
+ if (pending == 0) return out;
577
+
578
+ auto settled = [owner = std::weak_ptr<Scope>(scope), pending] {
579
+ if (auto s = owner.lock()) s->remove(pending);
580
+ };
581
+
524
582
  std::string where = std::string(p.fn) + " " + p.where();
525
583
  auto onFulfilled = jsi::Function::createFromHostFunction(
526
584
  rt, jsi::PropNameID::forAscii(rt, "onFulfilled"), 1,
527
- [out, where](jsi::Runtime& rt, const jsi::Value&, const jsi::Value* args, size_t count) -> jsi::Value {
585
+ [out, where, settled](jsi::Runtime& rt, const jsi::Value&, const jsi::Value* args, size_t count) -> jsi::Value {
586
+ settled();
528
587
  LucentScope scope;
529
588
  try {
530
589
  if constexpr (std::is_void_v<T>) out.resolve(undefined);
@@ -536,7 +595,8 @@ Promise<T> Convert<Promise<T>>::fromJs(jsi::Runtime& rt, const jsi::Value& v, co
536
595
  });
537
596
  auto onRejected = jsi::Function::createFromHostFunction(
538
597
  rt, jsi::PropNameID::forAscii(rt, "onRejected"), 1,
539
- [out](jsi::Runtime& rt, const jsi::Value&, const jsi::Value* args, size_t count) -> jsi::Value {
598
+ [out, settled](jsi::Runtime& rt, const jsi::Value&, const jsi::Value* args, size_t count) -> jsi::Value {
599
+ settled();
540
600
  LucentScope scope;
541
601
  out.reject(Convert<Error>::fromJs(rt, arg(args, count, 0), Path{"promise", "rejection"}));
542
602
  return jsi::Value::undefined();
@@ -551,6 +611,17 @@ Promise<T> Convert<Promise<T>>::fromJs(jsi::Runtime& rt, const jsi::Value& v, co
551
611
 
552
612
  /// Runs a synchronous Lucent call from JS, translating Lucent errors into
553
613
  /// JS exceptions.
614
+ template <class F>
615
+ jsi::Value callSync(jsi::Runtime& rt, Host& host, F&& body);
616
+
617
+ /// A synchronous call from JS, traced as an entry at the export's site
618
+ /// (the compiler passes it: LUCENT_TRACE_SITE_AT).
619
+ template <class F>
620
+ jsi::Value callSync(jsi::Runtime& rt, Host& host, const trace::Site* site, F&& body) {
621
+ trace::Scope entry(trace::Category::Entry, site->name, site);
622
+ return callSync(rt, host, std::forward<F>(body));
623
+ }
624
+
554
625
  template <class F>
555
626
  jsi::Value callSync(jsi::Runtime& rt, Host& host, F&& body) {
556
627
  LucentScope scope;
@@ -569,43 +640,41 @@ jsi::Value callSync(jsi::Runtime& rt, Host& host, F&& body) {
569
640
 
570
641
  /// Starts an exported async function on the Lucent thread and returns a JS
571
642
  /// promise for its result. `start` runs on the Lucent thread and returns the
572
- /// Lucent promise.
643
+ /// Lucent promise; it does not start if the host is torn down first.
573
644
  template <class T, class F>
574
645
  jsi::Value callAsync(jsi::Runtime& rt, Host& host, F&& start) {
646
+ return callAsync<T>(rt, host, nullptr, std::forward<F>(start));
647
+ }
648
+
649
+ /// As above, traced from the export's `site`: the call, the job it posts
650
+ /// (its wait and its run), the result's wait for the JS thread and its
651
+ /// delivery share one id.
652
+ template <class T, class F>
653
+ jsi::Value callAsync(jsi::Runtime& rt, Host& host, const trace::Site* site, F&& start) {
654
+ uint64_t traceId = site && trace::enabled() ? trace::newId() : 0;
655
+
656
+ std::optional<trace::Scope> entry;
657
+ std::optional<trace::Correlate> correlate;
658
+ if (traceId) [[unlikely]] {
659
+ entry.emplace(trace::Category::Entry, site->name, site, traceId);
660
+ correlate.emplace(traceId);
661
+ }
662
+
575
663
  uint64_t id = 0;
576
664
  jsi::Value jsPromise = host.createPromise(rt, id);
577
665
  std::weak_ptr<Host> weak = host.weak_from_this();
578
- Scheduler::instance().post([weak, id, start = std::forward<F>(start)]() mutable {
666
+
667
+ host.postToModule([weak, id, traceId, site, start = std::forward<F>(start)]() mutable {
579
668
  Promise<T> p;
580
669
  try {
581
670
  p = start();
582
671
  } catch (...) {
583
672
  p = Promise<T>::rejected(currentError(std::current_exception()));
584
673
  }
585
- p.onSettled([p, weak, id] {
586
- auto host = weak.lock();
587
- if (!host) return;
588
- host->postToJs([p, id, weak](jsi::Runtime& rt) {
589
- auto host = weak.lock();
590
- if (!host) return;
591
- LucentScope scope;
592
- if (p.fulfilled()) {
593
- jsi::Value value = jsi::Value::undefined();
594
- if constexpr (!std::is_void_v<T>) {
595
- try {
596
- value = Convert<T>::toJs(rt, *host, p.value());
597
- } catch (const jsi::JSError& e) {
598
- host->reject(rt, id, jsi::Value(rt, e.value()));
599
- return;
600
- }
601
- }
602
- host->resolve(rt, id, value);
603
- } else {
604
- host->reject(rt, id, host->errorToJs(rt, p.error()));
605
- }
606
- });
607
- });
674
+
675
+ if (auto host = weak.lock()) settleLater(*host, id, p, traceId, site);
608
676
  });
677
+
609
678
  return jsPromise;
610
679
  }
611
680