@crossbind/core-embind-rust 2.0.0-beta.60 → 2.0.0-beta.63

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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Rust **producer** for the crossbind embind registration protocol. Write an idiomatic Rust
4
4
  struct, register it, and it surfaces in JS as a real class - on web through emscripten's
5
- embind, on mobile through [`@crossbind/core-embind-jsi`](../core-embind-jsi) - **with no
5
+ embind, on mobile through [`@crossbind/core-embind-jsi`](../embind-jsi) - **with no
6
6
  generated C++ wrapper and no cbindgen header**. The Rust side talks straight to the
7
7
  registration ABI that both hosts already consume.
8
8
 
@@ -31,7 +31,7 @@ Rust (this package) ──┼── crossbind_embind_register_* ─┤
31
31
  - `adapters/web.cpp` - passthrough to emscripten's `_embind_*` (10-param, identical).
32
32
  - `adapters/jsi.cpp` - wraps the raw invokers into `jsi::Function` (via
33
33
  `createFromHostFunction`) and drops `isNonnullReturn` for embind-jsi's 9-param API.
34
- Compiled inside the React Native native module; shape-validated by `e2e/jsi-shape-check.cpp`.
34
+ Compiled inside the React Native native module and native Node.js addons.
35
35
 
36
36
  ## Demo: plain Rust in, JS classes out
37
37
 
@@ -92,7 +92,8 @@ On import the toolchain synthesizes a self-contained bridge crate under
92
92
  `<project>/.crossbind/rust-bridges/<name>/` (the user file is embedded via `#[path] mod user;` -
93
93
  no copy) plus a `counter.rs.d.ts` next to the source for full editor typing. The native builds
94
94
  bundle every app-local surface into ONE super staticlib (`rust-bridges/_app_super`) and link it
95
- into the app lib (iOS: merged into the force_loaded react-native-crossbind.a; Android: whole-archive).
95
+ into the app lib (iOS: merged into the force_loaded react-native-crossbind.a; Android and Node.js
96
+ addons: whole-archive, on Windows through its keep symbol).
96
97
 
97
98
  App-local surfaces can use UPSTREAM crates directly - the C++-style model where the app writes
98
99
  its own thin surface over a linked library and no crossbind package is involved. Declare the crates
@@ -165,7 +166,10 @@ constructor share one object), and is linked lazily with that symbol pinned
165
166
  (iOS `-Wl,-u,_crossbind_keep_<lib>`, Android `-Wl,--undefined=crossbind_keep_<lib>`) - the registration
166
167
  object is pulled, libstd stays lazy and deduplicates. Manual-bindings crates (no keep symbol)
167
168
  still fall back to force_load/whole-archive and are therefore safe only as the app's single
168
- Rust archive.
169
+ Rust archive. On Windows the super staticlib links lazily too: two crates in it may import one
170
+ DLL through raw-dylib (std and getrandom both import `ProcessPrng`), and lld cannot load the
171
+ repeated import tables whole. There the link pins `crossbind_keep_crossbind_app_super()`, which
172
+ reaches the keep symbol each app bridge exports; the super builds every bridge as one object.
169
173
 
170
174
  Web specifics: wasm-ld's `-u` does NOT pull archive members, so the wasm link pins keep symbols
171
175
  with `-Wl,--export=crossbind_keep_<lib>` instead; and any Rust archive in the link makes `buildWasm`
@@ -181,17 +185,20 @@ Generation rules (`crossbind/src/utils/rustBridgeGen.js`, run by `buildCargo` be
181
185
  |---|---|
182
186
  | `#[repr(i32)] pub enum Mode { Slow = 0, .. }` | embind enum |
183
187
  | `#[repr(C)] #[derive(..Default, Copy..)] pub struct Point { pub x: i32, .. }` | value object `{x, ..}` |
184
- | `pub fn new(..) -> Self` | constructor (max 3 args) |
185
- | other `pub fn ..(..) -> Self` | `smart_ptr` + static factory (max 2 args) |
186
- | `pub fn m(&mut self / &self, ..) -> R` | method (max 4 args), JS name camelCased |
188
+ | `pub fn new(..) -> Self` | constructor (max 7 args) |
189
+ | other `pub fn ..(..) -> Self` | `smart_ptr` + static factory (max 6 args) |
190
+ | `pub fn m(&mut self / &self, ..) -> R` | method (max 6 args), JS name camelCased |
187
191
  | `text: &str` / `text: &String` parameter | `string` on the wire, borrowed at the call site |
188
192
  | `i64` / `u64` param, return or field | JS **BigInt** (full 64-bit range, sign-correct) |
189
- | top-level `pub fn f(..) -> R` | module-level free function (max 4 args), camelCased |
193
+ | top-level `pub fn f(..) -> R` | module-level free function (max 6 args), camelCased |
190
194
  | `impl Display for C` | `toString()` on the class (so `` `${obj}` `` formats) |
191
195
  | `-> Result<T, E>` (ctor, factory, method or free fn; `E: Display`) | Err becomes a **thrown JS exception** |
192
196
  | `-> Option<Self>` (factory) | None becomes **JS null** (typed `X \| null`) |
193
197
  | `-> Option<i32 / f64 / bool / String>` (method or free fn) | Some -> value, None -> **null** (typed `T \| null`) |
194
198
  | `Option<i32 / f64 / bool / String>` parameter | JS undefined/null -> None (typed `T \| null \| undefined`) |
199
+ | `Option<i64 / u64>` parameter (`JsBigIntOptArg<T>`) | a BigInt or safe-integer Number; undefined/null -> None (typed `bigint \| null \| undefined`) |
200
+ | `Option<&[u8] / &[f64]>` parameter (`JsViewOptArg<T>`) | the typed array, read as for `&[T]`; undefined/null -> None |
201
+ | `NonZeroU64` (and the other `NonZero` integers) parameter, also in `Option` where the integer's `Option` crosses | the integer; 0 is a thrown JS exception before the call |
195
202
  | `other: &SomeClass` parameter | pass another bound instance (borrowed for the call) |
196
203
  | `export.bindings.vectors: [{ of: 'i32', name: '..' }]` in crossbind.config.mjs | `register_vector` |
197
204
 
@@ -207,30 +214,26 @@ scattered `Box::leak`.
207
214
 
208
215
  ## Test
209
216
 
210
- `pnpm e2e:prod` runs two legs (needs Rust + the emscripten target + a local emsdk):
211
-
212
- 1. **web** - `demo` crate → wasm, linked with `adapters/web.cpp` + `-lembind`, called from
213
- node. The class is materialized by **unmodified embind-js**.
214
- 2. **mobile-shape** - same crate compiled native, run against a jsi-*shaped* mock consumer
215
- that mirrors embind-jsi's real signatures. Validates the adapter's wrapping /
216
- marshalling / param-dropping.
217
+ The conformance kit calls the `demo` crate's classes and functions (the `rust:` checks in
218
+ `e2e/conformance/spec/run.mjs`) on every leg listed in `e2e/conformance/README.md`: through
219
+ `adapters/web.cpp` and **unmodified embind-js** on the wasm legs, through `adapters/jsi.cpp` on
220
+ React Native and native Node.js. `e2e/conformance-rust` checks each Rust construct in more depth.
217
221
 
218
222
  ## Status matrix
219
223
 
220
224
  | Path | State |
221
225
  |---|---|
222
226
  | web: Rust → flat ABI → embind-js (emsdk 5.0.3, 6.0.2 & 6.0.9) | ✅ tested, runs (full demo incl. f64 + string) |
223
- | mobile: adapter shape vs real embind-jsi signatures | ✅ shape-validated (native mock) |
224
227
  | mobile: real Hermes / device smoke (iOS simulator, RN app) | ✅ GREEN 32/32 — ctor, N-arity, bool, enum, string both ways, value object both ways, vector, smart_ptr factory, f64 both ways, delete, app-local .rs, idioms, BigInt, Display, free fns, Option both ways, class params, semver/regex imports |
225
228
  | mobile: real Hermes / device smoke (Android emulator, same RN app) | ✅ GREEN 32/32 — same demo, same adapter; cargo dep joins the cmake depends graph, keep-symbol linked; a NEW bare-crate import now links in the FIRST build (rust set busts the CMake configure) |
226
229
  | app-local `.rs` import (`./native/counter.rs`, both mobile platforms) | ✅ GREEN — synthesized bridge crate + app super staticlib + typed `counter.rs.d.ts` |
227
230
  | typed package import (`import { X } from '@crossbind/embind-rust-demo'`) | ✅ GREEN on iOS, Android AND web (vite) — resolver + proxy module + generated d.ts; web verified by playwright on chromium/firefox/webkit |
228
231
  | real-world upstream crate, packageless (`geo` via `cargoDependencies` + app-local surface) | ✅ GREEN — Hull (ConvexHull) on iOS + Android (26/26 smoke) AND in the browser (worker-mode playwright, 3 browsers): the same surface .rs serves all three platforms — a wasm output upstream geo never shipped |
229
- | Rust idioms: `&str`/`&String` params, `Result` → JS exception, `Option<Self>` → null | ✅ GREEN — e2e (web + jsi-mock legs), worker-mode playwright on 3 browsers, iOS sim + Android emulator (throw message, null, borrowed str all asserted) |
230
- | i64/u64 → BigInt, `impl Display` → toString(), top-level free functions | ✅ GREEN — same full bar (e2e both legs, 3-browser worker playwright, both devices); u64::MAX round-trips exactly, free `Result` fns throw |
232
+ | Rust idioms: `&str`/`&String` params, `Result` → JS exception, `Option<Self>` → null | ✅ GREEN — conformance kit on every leg, worker-mode playwright on 3 browsers, iOS sim + Android emulator (throw message, null, borrowed str all asserted) |
233
+ | i64/u64 → BigInt, `impl Display` → toString(), top-level free functions | ✅ GREEN — same full bar (conformance kit on every leg, 3-browser worker playwright, both devices); u64::MAX round-trips exactly, free `Result` fns throw |
231
234
  | DIRECT crate import (`import { Uuid } from 'cargo:uuid'` — untouched crates.io source, multi-file parse, feature-gated modules) | ✅ GREEN — worker-mode playwright on 3 browsers + iOS sim + Android emulator 26/26 (newV4 format, parse roundtrip, Display toString, Err throws) |
232
- | `Option<i32/f64/bool/String>` returns → value-or-null | ✅ GREEN — e2e both legs (mock reads the nullable cell natively), 3-browser worker playwright, both devices |
233
- | Option params + `&SomeClass` params + re-export following (`semver` matches, `regex` throwing ctor as DIRECT imports) | ✅ GREEN — e2e both legs, 3-browser worker playwright, Android 32/32 + iOS 32/32 — via the explicit cargo: scheme, so npm names never collide |
235
+ | `Option<i32/f64/bool/String>` returns → value-or-null | ✅ GREEN — conformance kit on every leg, 3-browser worker playwright, both devices |
236
+ | Option params + `&SomeClass` params + re-export following (`semver` matches, `regex` throwing ctor as DIRECT imports) | ✅ GREEN — conformance kit on every leg, 3-browser worker playwright, Android 32/32 + iOS 32/32 — via the explicit cargo: scheme, so npm names never collide |
234
237
  | Rust crate → real mobile archives (android/ios arm64) | ✅ compiles; iOS and Android linked+run |
235
238
 
236
239
  ### Wire contract notes (native vs wasm)
package/adapters/jsi.cpp CHANGED
@@ -29,11 +29,13 @@
29
29
  #include <optional>
30
30
  #include <string>
31
31
  #include <typeinfo>
32
+ #include <unordered_map>
32
33
  #include <unordered_set>
33
34
  // Real embind-jsi declarations (correct namespaces/linkage/signatures): jsRuntime lives in
34
35
  // `emscripten`, the _embind_register_* functions and InitFunc in `emscripten::internal` with C++
35
36
  // linkage and jsi::Function& slots. Including its header avoids any hand-declaration mismatch.
36
37
  #include <emscripten/bind.h>
38
+ #include <crossbind/direct_reads.h>
37
39
  #include "../include/crossbind_embind.h"
38
40
 
39
41
  namespace jsi = facebook::jsi;
@@ -68,6 +70,12 @@ const void* crossbind_tid_optional_int() { static const char t = 0; return &t; }
68
70
  const void* crossbind_tid_optional_double() { static const char t = 0; return &t; }
69
71
  const void* crossbind_tid_optional_bool() { static const char t = 0; return &t; }
70
72
  const void* crossbind_tid_optional_string() { static const char t = 0; return &t; }
73
+ const void* crossbind_tid_optional_int64() { static const char t = 0; return &t; }
74
+ const void* crossbind_tid_optional_uint64() { static const char t = 0; return &t; }
75
+ const void* crossbind_tid_bytes_arg() { static const char t = 0; return &t; }
76
+ const void* crossbind_tid_f64s_arg() { static const char t = 0; return &t; }
77
+ const void* crossbind_tid_bytes_opt_arg() { static const char t = 0; return &t; }
78
+ const void* crossbind_tid_f64s_opt_arg() { static const char t = 0; return &t; }
71
79
 
72
80
  // Idempotent: every Rust archive in the app may ask for the same optional type.
73
81
  void crossbind_embind_register_optional(const void* opt, const void* /*inner: adapter converts*/) {
@@ -129,17 +137,62 @@ static uint8_t* crossbindWireFromString(const std::string& s) {
129
137
  std::memcpy(w + 4, s.data(), s.size());
130
138
  return w;
131
139
  }
132
- static jsi::Function crossbindHelper(const char* name) {
133
- return jsRuntime->global().getPropertyAsFunction(*jsRuntime, name);
140
+ // Helpers and constructors are looked up once per runtime: a global lookup on every call cost more
141
+ // than the call. Each bindings initialisation starts a new cache; the old one is leaked on purpose,
142
+ // because the runtime its functions belong to may already be gone.
143
+ static std::unordered_map<const char*, jsi::Function>* g_helpers = new std::unordered_map<const char*, jsi::Function>();
144
+ struct ViewNames {
145
+ jsi::PropNameID buffer;
146
+ jsi::PropNameID byteOffset;
147
+ jsi::PropNameID length;
148
+ };
149
+ static ViewNames* g_viewNames = nullptr;
150
+ static void resetHelpers() {
151
+ g_helpers = new std::unordered_map<const char*, jsi::Function>();
152
+ g_viewNames = nullptr;
153
+ }
154
+ static InitFunc g_resetHelpersOnInit(resetHelpers);
155
+
156
+ static ViewNames& viewNames(jsi::Runtime& rt) {
157
+ if (g_viewNames == nullptr) {
158
+ g_viewNames = new ViewNames{jsi::PropNameID::forAscii(rt, "buffer"), jsi::PropNameID::forAscii(rt, "byteOffset"),
159
+ jsi::PropNameID::forAscii(rt, "length")};
160
+ }
161
+ return *g_viewNames;
162
+ }
163
+
164
+ // Typed array arguments ('y' bytes, 'z' doubles; 'Y'/'Z' when optional): embind-jsi hands the
165
+ // invoker the view itself, or null for a missing optional one.
166
+ static void registerTypedArgs() {
167
+ auto& rt = *jsRuntime;
168
+ auto registerTypedArg = rt.global().getPropertyAsFunction(rt, "__crossbind_register_typed_arg");
169
+ registerTypedArg.call(rt, jsi::BigInt::fromUint64(rt, (uint64_t)(uintptr_t)crossbind_tid_bytes_arg()), "Uint8Array", false, false);
170
+ registerTypedArg.call(rt, jsi::BigInt::fromUint64(rt, (uint64_t)(uintptr_t)crossbind_tid_f64s_arg()), "Float64Array", true, false);
171
+ registerTypedArg.call(rt, jsi::BigInt::fromUint64(rt, (uint64_t)(uintptr_t)crossbind_tid_bytes_opt_arg()), "Uint8Array | null", false, true);
172
+ registerTypedArg.call(rt, jsi::BigInt::fromUint64(rt, (uint64_t)(uintptr_t)crossbind_tid_f64s_opt_arg()), "Float64Array | null", true, true);
173
+ }
174
+ static InitFunc g_registerTypedArgsOnInit(registerTypedArgs);
175
+
176
+ // `name` must be a string literal: the cache is keyed by its address.
177
+ static jsi::Function& crossbindHelper(const char* name) {
178
+ auto found = g_helpers->find(name);
179
+ if (found == g_helpers->end()) {
180
+ found = g_helpers->emplace(name, jsRuntime->global().getPropertyAsFunction(*jsRuntime, name)).first;
181
+ }
182
+ return found->second;
134
183
  }
135
184
  static thread_local std::string g_cbError;
136
185
  static thread_local bool g_hasCbError = false;
137
186
 
138
187
  extern "C" {
188
+ // Rust values holding JS handles can outlive the runtime (thread-local ones drop inside exit(),
189
+ // after the host cleared jsRuntime); their handles then have nothing left to release.
139
190
  void crossbind_v_ref(size_t h) {
191
+ if (jsRuntime == nullptr) return;
140
192
  crossbindHelper("__emval_incref").call(*jsRuntime, crossbindVHandle(h));
141
193
  }
142
194
  void crossbind_v_unref(size_t h) {
195
+ if (jsRuntime == nullptr) return;
143
196
  crossbindHelper("__emval_decref").call(*jsRuntime, crossbindVHandle(h));
144
197
  }
145
198
  // Fork emval constants: undefined = 1n (init_emval seeds 1..4, reserved = 5).
@@ -202,46 +255,54 @@ static thread_local bool g_hasPendingError = false;
202
255
 
203
256
  static thread_local std::string g_pendingErrorCode;
204
257
 
205
- // Typed arrays on the native runtime: the adapter builds the JS view and hands back its handle,
206
- // one copy in each direction.
207
- // jsi::Object carries no index setter, so the elements go into a jsi::Array first and the
208
- // TypedArray constructor copies them (`new Uint8Array([...])`).
209
- static jsi::Object typedArrayFrom(jsi::Runtime& rt, const char* ctor, jsi::Array values) {
210
- return rt.global().getPropertyAsFunction(rt, ctor)
211
- .callAsConstructor(rt, values).asObject(rt);
258
+ // Typed arrays on the native runtime: one copy in each direction, through the backing store of an
259
+ // array JavaScript allocated, so no ArrayBuffer ever wraps native memory. Copying element by element
260
+ // through jsi cost about 100 ns a byte.
261
+ static size_t typedArrayToJs(jsi::Runtime& rt, const char* ctor, const void* data, size_t length, size_t bytes) {
262
+ auto array = crossbindHelper(ctor).callAsConstructor(rt, (double)length).asObject(rt);
263
+ if (bytes) std::memcpy(array.getPropertyAsObject(rt, "buffer").getArrayBuffer(rt).data(rt), data, bytes);
264
+ return (size_t)crossbindHandleOf(crossbindHelper("__crossbind_v_from_value").call(rt, array));
265
+ }
266
+
267
+ // A view of one of `kinds` is copied straight out of its backing store; false leaves a plain array
268
+ // or another view to the element-by-element path.
269
+ static bool typedArrayFromJs(jsi::Runtime& rt, size_t h, std::initializer_list<const char*> kinds, void* out, size_t bytes) {
270
+ auto value = crossbindHelper("__crossbind_v_value").call(rt, crossbindVHandle(h));
271
+ if (!value.isObject()) return false;
272
+ auto view = value.getObject(rt);
273
+ bool isKind = false;
274
+ for (const char* kind : kinds) isKind = isKind || view.instanceOf(rt, crossbindHelper(kind));
275
+ if (!isKind) return false;
276
+ auto buffer = view.getPropertyAsObject(rt, "buffer");
277
+ if (!buffer.isArrayBuffer(rt)) return false;
278
+ auto offset = (size_t)view.getProperty(rt, "byteOffset").getNumber();
279
+ if (bytes) std::memcpy(out, buffer.getArrayBuffer(rt).data(rt) + offset, bytes);
280
+ return true;
212
281
  }
213
282
 
214
283
  extern "C" size_t crossbind_v_bytes_to_js(const uint8_t* data, unsigned len) {
215
- auto& rt = *jsRuntime;
216
- auto values = jsi::Array(rt, (size_t)len);
217
- for (unsigned i = 0; i < len; ++i) values.setValueAtIndex(rt, i, jsi::Value((double)data[i]));
218
- auto array = typedArrayFrom(rt, "Uint8Array", std::move(values));
219
- return (size_t)crossbindHandleOf(crossbindHelper("__crossbind_v_from_value").call(rt, array));
284
+ return typedArrayToJs(*jsRuntime, "Uint8Array", data, len, len);
220
285
  }
221
286
 
222
287
  extern "C" size_t crossbind_v_f64s_to_js(const double* data, unsigned len) {
223
- auto& rt = *jsRuntime;
224
- auto values = jsi::Array(rt, (size_t)len);
225
- for (unsigned i = 0; i < len; ++i) values.setValueAtIndex(rt, i, jsi::Value(data[i]));
226
- auto array = typedArrayFrom(rt, "Float64Array", std::move(values));
227
- return (size_t)crossbindHandleOf(crossbindHelper("__crossbind_v_from_value").call(rt, array));
288
+ return typedArrayToJs(*jsRuntime, "Float64Array", data, len, (size_t)len * sizeof(double));
228
289
  }
229
290
 
230
291
  extern "C" unsigned crossbind_v_typed_len(size_t h) {
231
- auto& rt = *jsRuntime;
232
- auto value = crossbindHelper("__crossbind_v_get").call(rt, crossbindVHandle(h), jsi::String::createFromAscii(rt, "length"));
233
- auto number = crossbindHelper("__crossbind_v_as_num").call(rt, value);
292
+ auto number = crossbindHelper("__crossbind_v_length").call(*jsRuntime, crossbindVHandle(h));
234
293
  return number.isNumber() ? (unsigned)number.getNumber() : 0;
235
294
  }
236
295
 
237
296
  extern "C" void crossbind_v_bytes_from_js(size_t h, uint8_t* out, unsigned len) {
238
297
  auto& rt = *jsRuntime;
298
+ if (typedArrayFromJs(rt, h, {"Uint8Array", "Uint8ClampedArray", "Int8Array"}, out, len)) return;
239
299
  auto array = crossbindHelper("__crossbind_v_to_array").call(rt, crossbindVHandle(h)).asObject(rt).asArray(rt);
240
300
  for (unsigned i = 0; i < len; ++i) out[i] = (uint8_t)array.getValueAtIndex(rt, i).getNumber();
241
301
  }
242
302
 
243
303
  extern "C" void crossbind_v_f64s_from_js(size_t h, double* out, unsigned len) {
244
304
  auto& rt = *jsRuntime;
305
+ if (typedArrayFromJs(rt, h, {"Float64Array"}, out, (size_t)len * sizeof(double))) return;
245
306
  auto array = crossbindHelper("__crossbind_v_to_array").call(rt, crossbindVHandle(h)).asObject(rt).asArray(rt);
246
307
  for (unsigned i = 0; i < len; ++i) out[i] = array.getValueAtIndex(rt, i).getNumber();
247
308
  }
@@ -280,7 +341,49 @@ static jsi::Value wireStringToJsi(jsi::Runtime& rt, uint64_t w) {
280
341
  return jsi::Value(jsi::String::createFromUtf8(rt, str));
281
342
  }
282
343
 
283
- static uint64_t readArg(jsi::Runtime& rt, const jsi::Value& v, char c) {
344
+ // Where a typed array argument's elements are: the crate's ViewWire.
345
+ struct ViewWire {
346
+ const void* data;
347
+ size_t length;
348
+ };
349
+
350
+ static crossbind::IDirectReads* directReads(jsi::Runtime& rt) {
351
+ return static_cast<crossbind::IDirectReads*>(rt.castInterface(crossbind::IDirectReads::uuid));
352
+ }
353
+
354
+ // embind-jsi's toWireType already made the argument a view of the element kind over an ArrayBuffer,
355
+ // so its elements are lent in place for the call. Reading its properties through the Node-API
356
+ // runtime cost about 440 ns, which the runtime's own interface does in one call.
357
+ static uint64_t readView(jsi::Runtime& rt, const jsi::Value& v, ViewWire& view) {
358
+ auto* reads = directReads(rt);
359
+ void* data = nullptr;
360
+ size_t length = 0;
361
+ if (reads != nullptr && reads->typedArrayElements(v, data, length)) {
362
+ view.data = data;
363
+ view.length = length;
364
+ return (uint64_t)(uintptr_t)&view;
365
+ }
366
+ auto& names = viewNames(rt);
367
+ auto object = v.asObject(rt);
368
+ auto buffer = object.getProperty(rt, names.buffer).asObject(rt);
369
+ if (!buffer.isArrayBuffer(rt)) throw jsi::JSError(rt, "a typed array argument needs an ArrayBuffer");
370
+ view.length = (size_t)object.getProperty(rt, names.length).asNumber();
371
+ view.data = view.length == 0 ? nullptr
372
+ : buffer.getArrayBuffer(rt).data(rt) + (size_t)object.getProperty(rt, names.byteOffset).asNumber();
373
+ return (uint64_t)(uintptr_t)&view;
374
+ }
375
+
376
+ static uint64_t readArg(jsi::Runtime& rt, const jsi::Value& v, char c, ViewWire& view) {
377
+ if (c == 'y' || c == 'z') return readView(rt, v, view);
378
+ if (c == 'Y' || c == 'Z') return v.isUndefined() || v.isNull() ? 0 : readView(rt, v, view);
379
+ // Optional 64-bit args ('J'/'U'): the value a plain 'j'/'u' takes, in a cell this adapter frees.
380
+ if (c == 'J' || c == 'U') {
381
+ if (v.isUndefined() || v.isNull()) return 0;
382
+ uint64_t bits = readArg(rt, v, c == 'J' ? 'j' : 'u', view);
383
+ void* cell = std::malloc(8);
384
+ std::memcpy(cell, &bits, 8);
385
+ return (uint64_t)(uintptr_t)cell;
386
+ }
284
387
  if (c == 's') {
285
388
  // embind-jsi passes std::string args as a jsi::String; build the crate's [u32 len][bytes]
286
389
  // wire buffer here (freed by the caller after dispatch - the crate's from_wire only copies).
@@ -303,7 +406,12 @@ static uint64_t readArg(jsi::Runtime& rt, const jsi::Value& v, char c) {
303
406
  // i64/u64 ('j'/'u') arrive as JS BigInt (embind bigint types), Number accepted as a courtesy;
304
407
  // both directions are raw 64-bit slots, so the unsigned bit copy is sign-correct either way.
305
408
  if (c == 'j' || c == 'u') {
306
- if (v.isBigInt()) return v.getBigInt(rt).getUint64(rt);
409
+ if (v.isBigInt()) {
410
+ auto* reads = directReads(rt);
411
+ uint64_t bits = 0;
412
+ bool lossless = false;
413
+ return reads != nullptr && reads->bigIntToUint64(v, bits, lossless) ? bits : v.getBigInt(rt).getUint64(rt);
414
+ }
307
415
  // A Number is accepted only while it is an exact integer JS can represent; anything
308
416
  // else would be truncated silently, and a 64-bit slot deserves the exact value.
309
417
  double d = v.isNumber() ? v.getNumber() : 0;
@@ -316,7 +424,7 @@ static uint64_t readArg(jsi::Runtime& rt, const jsi::Value& v, char c) {
316
424
  // adapter owns and frees after dispatch ('S' carries a plain string wire).
317
425
  if (c == 'I' || c == 'D' || c == 'B' || c == 'S') {
318
426
  if (v.isUndefined() || v.isNull()) return 0;
319
- if (c == 'S') return readArg(rt, v, 's');
427
+ if (c == 'S') return readArg(rt, v, 's', view);
320
428
  if (c == 'I') { int32_t x = (int32_t)v.getNumber(); void* p = std::malloc(4); std::memcpy(p, &x, 4); return (uint64_t)(uintptr_t)p; }
321
429
  if (c == 'D') { double x = v.getNumber(); void* p = std::malloc(8); std::memcpy(p, &x, 8); return (uint64_t)(uintptr_t)p; }
322
430
  uint8_t b = v.getBool() ? 1 : 0; void* p = std::malloc(1); std::memcpy(p, &b, 1); return (uint64_t)(uintptr_t)p;
@@ -344,10 +452,11 @@ static jsi::Value callInvoker(jsi::Runtime& rt, const std::string& sig, void* in
344
452
  // (embind sliced it off), so the jsi args a[0..] fill sig[2..]; otherwise a[0..] fill sig[1..].
345
453
  // Widest call: a baked target + `this` + six declared arguments.
346
454
  uint64_t s[8] = {0, 0, 0, 0, 0, 0, 0, 0};
455
+ ViewWire views[8];
347
456
  int base = 0;
348
457
  if (prepend) { s[0] = ctx; base = 1; }
349
458
  // A call with fewer JS args than the signature must not read past the argument array.
350
- for (int j = base; j < total && j < 8; ++j) s[j] = (size_t)(j - base) < count ? readArg(rt, a[j - base], sig[1 + j]) : 0;
459
+ for (int j = base; j < total && j < 8; ++j) s[j] = (size_t)(j - base) < count ? readArg(rt, a[j - base], sig[1 + j], views[j]) : 0;
351
460
 
352
461
  uint64_t r = 0;
353
462
  if (ret == 'v') {
@@ -380,7 +489,7 @@ static jsi::Value callInvoker(jsi::Runtime& rt, const std::string& sig, void* in
380
489
  // now that the crate has copied.
381
490
  for (int j = base; j < total && j < 8; ++j) {
382
491
  const char cj = sig[1 + j];
383
- if (cj == 's' || cj == 'S' || cj == 'I' || cj == 'D' || cj == 'B') std::free((void*)(uintptr_t)s[j]);
492
+ if (cj == 's' || cj == 'S' || cj == 'I' || cj == 'D' || cj == 'B' || cj == 'J' || cj == 'U') std::free((void*)(uintptr_t)s[j]);
384
493
  }
385
494
 
386
495
  // A shim raised (Result::Err): drop the sentinel return and surface a real JS exception.
package/adapters/web.cpp CHANGED
@@ -72,9 +72,57 @@ const void* crossbind_tid_optional_int() { static const char t = 0; return &t; }
72
72
  const void* crossbind_tid_optional_double() { static const char t = 0; return &t; }
73
73
  const void* crossbind_tid_optional_bool() { static const char t = 0; return &t; }
74
74
  const void* crossbind_tid_optional_string() { static const char t = 0; return &t; }
75
+ const void* crossbind_tid_optional_int64() { static const char t = 0; return &t; }
76
+ const void* crossbind_tid_optional_uint64() { static const char t = 0; return &t; }
77
+ const void* crossbind_tid_bytes_arg() { static const char t = 0; return &t; }
78
+ const void* crossbind_tid_f64s_arg() { static const char t = 0; return &t; }
79
+ const void* crossbind_tid_bytes_opt_arg() { static const char t = 0; return &t; }
80
+ const void* crossbind_tid_f64s_opt_arg() { static const char t = 0; return &t; }
75
81
 
76
82
  }
77
83
 
84
+ // Typed array arguments: the view is copied once into wasm memory behind a { data, length } header
85
+ // (the crate's ViewWire) and freed after the call. Another typed array or a plain array is converted
86
+ // element by element first. An optional one passes null or undefined as a null pointer.
87
+ EM_JS_DEPS(crossbind_typed_arg_deps, "$registerType,$throwBindingError,$embindRepr");
88
+ EM_JS(void, crossbind_register_typed_arg, (const void* rawType, const char* name, int isDoubles, int isOptional), {
89
+ const typeName = UTF8ToString(name);
90
+ const View = isDoubles ? Float64Array : Uint8Array;
91
+ const isLent = isDoubles
92
+ ? (value) => value instanceof Float64Array
93
+ : (value) => value instanceof Uint8Array || value instanceof Uint8ClampedArray || value instanceof Int8Array;
94
+ registerType(rawType, {
95
+ name: typeName,
96
+ toWireType(destructors, value) {
97
+ if (isOptional && (value === null || value === undefined)) return 0;
98
+ if (!isLent(value)) {
99
+ if (!Array.isArray(value) && !(ArrayBuffer.isView(value) && !(value instanceof DataView))) {
100
+ throwBindingError(`Cannot pass ${embindRepr(value)} as a ${typeName}`);
101
+ }
102
+ value = View.from(value);
103
+ }
104
+ const base = _malloc(8 + value.byteLength);
105
+ if (!base) throwBindingError(`Cannot copy ${value.byteLength} bytes into wasm memory`);
106
+ HEAPU32[base >> 2] = base + 8;
107
+ HEAPU32[(base >> 2) + 1] = value.length;
108
+ if (isDoubles) HEAPF64.set(value, (base + 8) >> 3);
109
+ else HEAPU8.set(value, base + 8);
110
+ if (destructors !== null) destructors.push(_free, base);
111
+ return base;
112
+ },
113
+ destructorFunction(ptr) {
114
+ _free(ptr);
115
+ },
116
+ });
117
+ });
118
+
119
+ EMSCRIPTEN_BINDINGS(crossbind_typed_args) {
120
+ crossbind_register_typed_arg(crossbind_tid_bytes_arg(), "Uint8Array", 0, 0);
121
+ crossbind_register_typed_arg(crossbind_tid_f64s_arg(), "Float64Array", 1, 0);
122
+ crossbind_register_typed_arg(crossbind_tid_bytes_opt_arg(), "Uint8Array | null", 0, 1);
123
+ crossbind_register_typed_arg(crossbind_tid_f64s_opt_arg(), "Float64Array | null", 1, 1);
124
+ }
125
+
78
126
  // Optional wire on wasm is an EM_VAL handle (None = the reserved null constant from
79
127
  // emscripten's own header: a missing value is null on every runtime, like C++ nullptr). Some-values become handles through Emval.toHandle; the registered
80
128
  // optional type's fromWireType does toValue + decref, so the refcount stays balanced.
@@ -118,6 +166,14 @@ int crossbind_emval_opt_bool(void* h, uint8_t* out) {
118
166
  *out = v.as<bool>() ? 1 : 0;
119
167
  return 1;
120
168
  }
169
+ // A 64-bit optional reads through the registered bigint type, so it takes what a plain 64-bit
170
+ // parameter takes: a BigInt or a safe-integer Number.
171
+ int crossbind_emval_opt_bits64(void* h, uint64_t* out, int isSigned) {
172
+ emscripten::val v = emscripten::val::take_ownership((emscripten::EM_VAL)h);
173
+ if (v.isUndefined() || v.isNull()) return 0;
174
+ *out = isSigned ? (uint64_t)v.as<int64_t>() : v.as<uint64_t>();
175
+ return 1;
176
+ }
121
177
  int crossbind_emval_opt_string(void* h, uint8_t** out) {
122
178
  emscripten::val v = emscripten::val::take_ownership((emscripten::EM_VAL)h);
123
179
  if (v.isUndefined() || v.isNull()) return 0;
package/crate/src/lib.rs CHANGED
@@ -173,7 +173,9 @@ macro_rules! bindings {
173
173
  }
174
174
  #[used]
175
175
  #[cfg_attr(target_vendor = "apple", link_section = "__DATA,__mod_init_func")]
176
- #[cfg_attr(not(target_vendor = "apple"), link_section = ".init_array")]
176
+ // The Windows CRT runs no .init_array; it calls what .CRT$XCU holds when the DLL loads.
177
+ #[cfg_attr(windows, link_section = ".CRT$XCU")]
178
+ #[cfg_attr(not(any(target_vendor = "apple", windows)), link_section = ".init_array")]
177
179
  static __CROSSBIND_EMBIND_CTOR: extern "C" fn() = __crossbind_embind_ctor;
178
180
  };
179
181
  };
@@ -418,6 +420,65 @@ option_wire! {
418
420
  String, crossbind_tid_optional_string, 'S';
419
421
  }
420
422
 
423
+ extern "C" {
424
+ fn crossbind_tid_optional_int64() -> *const c_void;
425
+ fn crossbind_tid_optional_uint64() -> *const c_void;
426
+ }
427
+ #[cfg(target_family = "wasm")]
428
+ extern "C" {
429
+ fn crossbind_emval_opt_bits64(handle: usize, out: *mut u64, signed: i32) -> i32;
430
+ }
431
+
432
+ mod sealed {
433
+ pub trait Sealed {}
434
+ impl Sealed for i64 {}
435
+ impl Sealed for u64 {}
436
+ }
437
+
438
+ #[doc(hidden)]
439
+ pub trait BigIntElement: sealed::Sealed + Copy + 'static {
440
+ const SIGNED: bool;
441
+ }
442
+ impl BigIntElement for i64 { const SIGNED: bool = true; }
443
+ impl BigIntElement for u64 { const SIGNED: bool = false; }
444
+
445
+ /// An `Option<i64>` / `Option<u64>` parameter: a BigInt or a safe-integer Number, null or undefined
446
+ /// for `None`. Generic, so it is compiled into the crate that uses it and a cargo archive built
447
+ /// before it existed still links next to one built after.
448
+ pub struct JsBigIntOptArg<T>(pub Option<T>);
449
+
450
+ impl<T: BigIntElement> WireType for JsBigIntOptArg<T> {
451
+ #[cfg(target_family = "wasm")]
452
+ type Wire = usize;
453
+ #[cfg(not(target_family = "wasm"))]
454
+ type Wire = *mut u8;
455
+ #[cfg(target_family = "wasm")]
456
+ const SIG: char = 'i';
457
+ #[cfg(not(target_family = "wasm"))]
458
+ const SIG: char = if T::SIGNED { 'J' } else { 'U' };
459
+ fn tid() -> *const c_void {
460
+ unsafe { if T::SIGNED { crossbind_tid_optional_int64() } else { crossbind_tid_optional_uint64() } }
461
+ }
462
+ #[cfg(target_family = "wasm")]
463
+ fn from_wire(w: usize) -> Self {
464
+ let mut bits: u64 = 0;
465
+ if unsafe { crossbind_emval_opt_bits64(w, &mut bits, T::SIGNED as i32) } == 0 { return JsBigIntOptArg(None); }
466
+ JsBigIntOptArg(Some(unsafe { core::mem::transmute_copy::<u64, T>(&bits) }))
467
+ }
468
+ #[cfg(not(target_family = "wasm"))]
469
+ fn from_wire(w: *mut u8) -> Self {
470
+ if w.is_null() { return JsBigIntOptArg(None); }
471
+ JsBigIntOptArg(Some(unsafe { core::ptr::read_unaligned(w as *const T) }))
472
+ }
473
+ fn to_wire(self) -> Self::Wire { unreachable!("an optional 64-bit integer is only a parameter") }
474
+ }
475
+
476
+ /// Registers the optional type a `JsBigIntOptArg<T>` parameter names (adapter-idempotent).
477
+ pub fn register_optional_bigint<T: BigIntElement>() {
478
+ let inner = unsafe { if T::SIGNED { crossbind_tid_int64() } else { crossbind_tid_uint64() } };
479
+ unsafe { crossbind_embind_register_optional(<JsBigIntOptArg<T> as WireType>::tid(), inner) };
480
+ }
481
+
421
482
  // wasm Some-path: hand the inner's in-memory repr to _emval_take_value. JS copies the value
422
483
  // synchronously, so stack temporaries suffice; the string wire is freed by embind itself.
423
484
  #[cfg(target_family = "wasm")]
@@ -879,6 +940,102 @@ impl WireType for JsF64s {
879
940
  impl ErrSentinel for JsBytes { fn err_sentinel() -> Self { JsBytes(Vec::new()) } }
880
941
  impl ErrSentinel for JsF64s { fn err_sentinel() -> Self { JsF64s(Vec::new()) } }
881
942
 
943
+ extern "C" {
944
+ fn crossbind_tid_bytes_arg() -> *const c_void;
945
+ fn crossbind_tid_f64s_arg() -> *const c_void;
946
+ }
947
+
948
+ // A typed array argument reaches the adapter as the JS view itself, and the adapter says where its
949
+ // elements are: in the view's own backing store on the native runtime, in a copy in wasm memory on
950
+ // the web. Either stays valid until the call returns.
951
+ #[repr(C)]
952
+ struct ViewWire {
953
+ data: *const c_void,
954
+ length: usize,
955
+ }
956
+
957
+ #[doc(hidden)]
958
+ pub trait ViewElement: Copy + 'static {
959
+ const NATIVE_SIG: char;
960
+ fn tid() -> *const c_void;
961
+ }
962
+
963
+ impl ViewElement for u8 {
964
+ const NATIVE_SIG: char = 'y';
965
+ fn tid() -> *const c_void { unsafe { crossbind_tid_bytes_arg() } }
966
+ }
967
+
968
+ impl ViewElement for f64 {
969
+ const NATIVE_SIG: char = 'z';
970
+ fn tid() -> *const c_void { unsafe { crossbind_tid_f64s_arg() } }
971
+ }
972
+
973
+ /// A byte or double parameter: `&[T]` reads the caller's view in place, `Vec<T>` copies it once.
974
+ pub struct JsViewArg<T> {
975
+ data: *const T,
976
+ length: usize,
977
+ }
978
+
979
+ pub type JsBytesArg = JsViewArg<u8>;
980
+ pub type JsF64sArg = JsViewArg<f64>;
981
+
982
+ impl<T: ViewElement> JsViewArg<T> {
983
+ pub fn as_slice(&self) -> &[T] {
984
+ if self.length == 0 { &[] } else { unsafe { std::slice::from_raw_parts(self.data, self.length) } }
985
+ }
986
+ pub fn to_vec(&self) -> Vec<T> {
987
+ self.as_slice().to_vec()
988
+ }
989
+ }
990
+
991
+ impl<T: ViewElement> WireType for JsViewArg<T> {
992
+ type Wire = usize;
993
+ #[cfg(target_family = "wasm")]
994
+ const SIG: char = 'p';
995
+ #[cfg(not(target_family = "wasm"))]
996
+ const SIG: char = T::NATIVE_SIG;
997
+ fn tid() -> *const c_void { T::tid() }
998
+ fn from_wire(w: usize) -> Self {
999
+ if w == 0 { return JsViewArg { data: core::ptr::null(), length: 0 }; }
1000
+ let view = unsafe { &*(w as *const ViewWire) };
1001
+ JsViewArg { data: view.data as *const T, length: view.length }
1002
+ }
1003
+ fn to_wire(self) -> usize { unreachable!("a typed array argument is never returned") }
1004
+ }
1005
+
1006
+ extern "C" {
1007
+ fn crossbind_tid_bytes_opt_arg() -> *const c_void;
1008
+ fn crossbind_tid_f64s_opt_arg() -> *const c_void;
1009
+ }
1010
+
1011
+ /// An `Option<&[T]>` parameter: null or undefined is `None`, a view is read as for `JsViewArg`.
1012
+ /// Generic like `JsBigIntOptArg`, for the same reason.
1013
+ pub struct JsViewOptArg<T>(Option<JsViewArg<T>>);
1014
+
1015
+ pub type JsBytesOptArg = JsViewOptArg<u8>;
1016
+ pub type JsF64sOptArg = JsViewOptArg<f64>;
1017
+
1018
+ impl<T: ViewElement> JsViewOptArg<T> {
1019
+ pub fn as_slice(&self) -> Option<&[T]> {
1020
+ self.0.as_ref().map(|view| view.as_slice())
1021
+ }
1022
+ }
1023
+
1024
+ impl<T: ViewElement> WireType for JsViewOptArg<T> {
1025
+ type Wire = usize;
1026
+ #[cfg(target_family = "wasm")]
1027
+ const SIG: char = 'p';
1028
+ #[cfg(not(target_family = "wasm"))]
1029
+ const SIG: char = if T::NATIVE_SIG == 'y' { 'Y' } else { 'Z' };
1030
+ fn tid() -> *const c_void {
1031
+ unsafe { if T::NATIVE_SIG == 'y' { crossbind_tid_bytes_opt_arg() } else { crossbind_tid_f64s_opt_arg() } }
1032
+ }
1033
+ fn from_wire(w: usize) -> Self {
1034
+ JsViewOptArg(if w == 0 { None } else { Some(JsViewArg::from_wire(w)) })
1035
+ }
1036
+ fn to_wire(self) -> usize { unreachable!("a typed array argument is never returned") }
1037
+ }
1038
+
882
1039
  impl Drop for JsValue {
883
1040
  fn drop(&mut self) {
884
1041
  unsafe { crossbind_v_unref(self.handle) };
@@ -1287,6 +1444,10 @@ arc_factories! {
1287
1444
  create_arc0 / arc_factory_invoker0 : ;
1288
1445
  create_arc1 / arc_factory_invoker1 : A0;
1289
1446
  create_arc2 / arc_factory_invoker2 : A0, A1;
1447
+ create_arc3 / arc_factory_invoker3 : A0, A1, A2;
1448
+ create_arc4 / arc_factory_invoker4 : A0, A1, A2, A3;
1449
+ create_arc5 / arc_factory_invoker5 : A0, A1, A2, A3, A4;
1450
+ create_arc6 / arc_factory_invoker6 : A0, A1, A2, A3, A4, A5;
1290
1451
  }
1291
1452
 
1292
1453
  // N-arity constructors: one monomorphized invoker + builder method per argument count.
@@ -1380,6 +1541,7 @@ constructors_ptr! {
1380
1541
  constructor_ptr4 / ctor_ptr_invoker4 : A0, A1, A2, A3;
1381
1542
  constructor_ptr5 / ctor_ptr_invoker5 : A0, A1, A2, A3, A4;
1382
1543
  constructor_ptr6 / ctor_ptr_invoker6 : A0, A1, A2, A3, A4, A5;
1544
+ constructor_ptr7 / ctor_ptr_invoker7 : A0, A1, A2, A3, A4, A5, A6;
1383
1545
  }
1384
1546
 
1385
1547
  // Raw-pointer factory variants: `Result<Self, E>` raises on Err, `Option<Self>` returns null
@@ -1522,3 +1684,45 @@ free_functions! {
1522
1684
  fn5 / free_invoker5 : A0, A1, A2, A3, A4;
1523
1685
  fn6 / free_invoker6 : A0, A1, A2, A3, A4, A5;
1524
1686
  }
1687
+
1688
+ // N-arity STATIC functions (`Class.name(..)` in JS): a free function registered on its class,
1689
+ // so it rides the free-function invokers. Generic only, so a cargo archive built before these
1690
+ // existed still links next to one built after.
1691
+ macro_rules! static_functions {
1692
+ ($( $method:ident / $invoker:ident : $($arg:ident),* ; )*) => {
1693
+ impl<T: 'static> ClassBuilder<T> {
1694
+ $(
1695
+ pub fn $method<$($arg: WireType,)* R: WireType>(self, name: &str, f: fn($($arg),*) -> R) -> Self {
1696
+ let mut sig = String::new();
1697
+ sig.push(R::SIG); sig.push('p');
1698
+ let mut args = vec![R::tid()];
1699
+ $( sig.push(<$arg as WireType>::SIG); args.push(<$arg as WireType>::tid()); )*
1700
+ let (argc, arg_ptr, name_ptr, sig_ptr);
1701
+ {
1702
+ let mut r = registry().lock().unwrap();
1703
+ argc = args.len() as u32;
1704
+ arg_ptr = r.argtypes(args);
1705
+ name_ptr = r.cstr(name);
1706
+ sig_ptr = r.cstr(&sig);
1707
+ }
1708
+ unsafe {
1709
+ crossbind_embind_register_class_class_function(
1710
+ self.cls, name_ptr, argc, arg_ptr, sig_ptr,
1711
+ $invoker::<$($arg,)* R> as *const () as usize, f as *const () as usize, false, false,
1712
+ );
1713
+ }
1714
+ self
1715
+ }
1716
+ )*
1717
+ }
1718
+ };
1719
+ }
1720
+ static_functions! {
1721
+ static_function0 / free_invoker0 : ;
1722
+ static_function1 / free_invoker1 : A0;
1723
+ static_function2 / free_invoker2 : A0, A1;
1724
+ static_function3 / free_invoker3 : A0, A1, A2;
1725
+ static_function4 / free_invoker4 : A0, A1, A2, A3;
1726
+ static_function5 / free_invoker5 : A0, A1, A2, A3, A4;
1727
+ static_function6 / free_invoker6 : A0, A1, A2, A3, A4, A5;
1728
+ }
@@ -33,6 +33,8 @@ CrossbindTid crossbind_tid_optional_int(void);
33
33
  CrossbindTid crossbind_tid_optional_double(void);
34
34
  CrossbindTid crossbind_tid_optional_bool(void);
35
35
  CrossbindTid crossbind_tid_optional_string(void);
36
+ CrossbindTid crossbind_tid_optional_int64(void);
37
+ CrossbindTid crossbind_tid_optional_uint64(void);
36
38
  void crossbind_embind_register_smart_ptr(
37
39
  CrossbindTid ptr_ty, CrossbindTid pointee_ty, const char* name, int sharing_policy,
38
40
  const char* gp_sig, CrossbindFn get_pointee, const char* ctor_sig, CrossbindFn constructor,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crossbind/core-embind-rust",
3
- "version": "2.0.0-beta.60",
3
+ "version": "2.0.0-beta.63",
4
4
  "description": "Rust producer for the crossbind embind registration protocol - bind Rust structs as classes reachable from JS on web (embind) and mobile (embind-jsi). Research/spike stage.",
5
5
  "homepage": "https://github.com/crossbind/crossbind/tree/main/core/embind-rust#readme",
6
6
  "repository": {
@@ -19,7 +19,6 @@
19
19
  "react-native"
20
20
  ],
21
21
  "scripts": {
22
- "e2e:prod": "node e2e/run.mjs",
23
- "clear": "rm -rf crate/target demo/target e2e/demo.mjs e2e/demo.wasm e2e/jsi-shape-check"
22
+ "clear": "rm -rf crate/target demo/target"
24
23
  }
25
24
  }