@crossbind/core-embind-jsi 2.0.0-beta.56 → 2.0.0-beta.60
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/CHANGELOG.md +10 -0
- package/cpp/src/emscripten/val.h +1 -1
- package/cpp/src/emscripten/wire.h +138 -4
- package/js/embind.js +47 -56
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @crossbind/core-embind-jsi
|
|
2
2
|
|
|
3
|
+
## 2.0.0-beta.60
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Supplied the `std::char_traits<unsigned char>` that embind's raw-byte string binding needs. The
|
|
8
|
+
standard has never defined one, and libc++ used to instantiate the primary template anyway; from
|
|
9
|
+
the release NDK r30 ships it static_asserts instead, so naming
|
|
10
|
+
`std::basic_string<unsigned char>` stopped compiling at all. The binding is unchanged, and the
|
|
11
|
+
traits cost nothing on the toolchains that did not need them.
|
|
12
|
+
|
|
3
13
|
## 2.0.0-beta.53
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
package/cpp/src/emscripten/val.h
CHANGED
|
@@ -172,7 +172,7 @@ struct GenericWireTypeConverter {
|
|
|
172
172
|
template<typename Pointee>
|
|
173
173
|
struct GenericWireTypeConverter<Pointee*> {
|
|
174
174
|
static Pointee* from(facebook::jsi::Value& wt) {
|
|
175
|
-
return reinterpret_cast<Pointee*>(
|
|
175
|
+
return reinterpret_cast<Pointee*>(addressFromValue(*jsRuntime, wt));
|
|
176
176
|
}
|
|
177
177
|
};
|
|
178
178
|
|
|
@@ -20,12 +20,63 @@
|
|
|
20
20
|
|
|
21
21
|
#include <cstdio>
|
|
22
22
|
#include <cstdlib>
|
|
23
|
+
#include <cstdint>
|
|
23
24
|
#include <memory>
|
|
24
25
|
#include <optional>
|
|
25
26
|
#include <string>
|
|
26
27
|
|
|
27
28
|
#include <jsi/jsi.h>
|
|
28
29
|
|
|
30
|
+
// embind registers std::basic_string<unsigned char> as the raw-byte counterpart to UTF-8
|
|
31
|
+
// std::string, but the standard has never defined std::char_traits for unsigned char. libc++ used
|
|
32
|
+
// to instantiate the primary template anyway; from the release NDK r30 ships it static_asserts
|
|
33
|
+
// instead, so merely naming the type stops compiling. Supplying the traits keeps the binding and
|
|
34
|
+
// costs nothing on the toolchains that did not need it.
|
|
35
|
+
namespace std {
|
|
36
|
+
template <>
|
|
37
|
+
struct char_traits<unsigned char> {
|
|
38
|
+
using char_type = unsigned char;
|
|
39
|
+
using int_type = int;
|
|
40
|
+
using off_type = streamoff;
|
|
41
|
+
using pos_type = streampos;
|
|
42
|
+
using state_type = mbstate_t;
|
|
43
|
+
|
|
44
|
+
static constexpr void assign(char_type& a, const char_type& b) noexcept { a = b; }
|
|
45
|
+
static constexpr bool eq(char_type a, char_type b) noexcept { return a == b; }
|
|
46
|
+
static constexpr bool lt(char_type a, char_type b) noexcept { return a < b; }
|
|
47
|
+
|
|
48
|
+
static int compare(const char_type* a, const char_type* b, size_t n) {
|
|
49
|
+
return n == 0 ? 0 : __builtin_memcmp(a, b, n);
|
|
50
|
+
}
|
|
51
|
+
static size_t length(const char_type* s) {
|
|
52
|
+
size_t n = 0;
|
|
53
|
+
while (s[n]) ++n;
|
|
54
|
+
return n;
|
|
55
|
+
}
|
|
56
|
+
static const char_type* find(const char_type* s, size_t n, const char_type& c) {
|
|
57
|
+
for (size_t i = 0; i < n; ++i)
|
|
58
|
+
if (s[i] == c) return s + i;
|
|
59
|
+
return nullptr;
|
|
60
|
+
}
|
|
61
|
+
static char_type* move(char_type* d, const char_type* s, size_t n) {
|
|
62
|
+
return n == 0 ? d : static_cast<char_type*>(__builtin_memmove(d, s, n));
|
|
63
|
+
}
|
|
64
|
+
static char_type* copy(char_type* d, const char_type* s, size_t n) {
|
|
65
|
+
return n == 0 ? d : static_cast<char_type*>(__builtin_memcpy(d, s, n));
|
|
66
|
+
}
|
|
67
|
+
static char_type* assign(char_type* d, size_t n, char_type c) {
|
|
68
|
+
for (size_t i = 0; i < n; ++i) d[i] = c;
|
|
69
|
+
return d;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static constexpr int_type not_eof(int_type c) noexcept { return eq_int_type(c, eof()) ? ~eof() : c; }
|
|
73
|
+
static constexpr char_type to_char_type(int_type c) noexcept { return char_type(c); }
|
|
74
|
+
static constexpr int_type to_int_type(char_type c) noexcept { return int_type(c); }
|
|
75
|
+
static constexpr bool eq_int_type(int_type a, int_type b) noexcept { return a == b; }
|
|
76
|
+
static constexpr int_type eof() noexcept { return int_type(EOF); }
|
|
77
|
+
};
|
|
78
|
+
} // namespace std
|
|
79
|
+
|
|
29
80
|
// #include <android/log.h>
|
|
30
81
|
// #define APPNAME "react-native-crossbind"
|
|
31
82
|
|
|
@@ -45,6 +96,19 @@ constexpr bool has_unbound_type_names = false;
|
|
|
45
96
|
|
|
46
97
|
namespace internal {
|
|
47
98
|
|
|
99
|
+
// JS hands native pointers over as BigInt addresses, but a null pointer arrives as the number 0
|
|
100
|
+
// and enum values as plain numbers: take both spellings.
|
|
101
|
+
inline uint64_t addressFromValue(facebook::jsi::Runtime& rt, const facebook::jsi::Value& v) {
|
|
102
|
+
if (v.isNumber()) return static_cast<uint64_t>(v.asNumber());
|
|
103
|
+
if (v.isNull() || v.isUndefined()) return 0;
|
|
104
|
+
return v.asBigInt(rt).asUint64(rt);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
inline int64_t integerFromValue(facebook::jsi::Runtime& rt, const facebook::jsi::Value& v) {
|
|
108
|
+
if (v.isNumber()) return static_cast<int64_t>(v.asNumber());
|
|
109
|
+
return v.asBigInt(rt).asInt64(rt);
|
|
110
|
+
}
|
|
111
|
+
|
|
48
112
|
typedef const void* TYPEID;
|
|
49
113
|
|
|
50
114
|
// Defined in bind.cpp: registers a JS-side ArrayBuffer window covering `ptr` so
|
|
@@ -369,6 +433,68 @@ struct BindingType<uint64_t> {
|
|
|
369
433
|
}
|
|
370
434
|
};
|
|
371
435
|
|
|
436
|
+
// jsi strings cross as UTF-8, so a std::u16string (embind's byte-preserving string) is transcoded; every code unit of
|
|
437
|
+
// well-formed UTF-16 survives the round trip.
|
|
438
|
+
inline std::string utf16ToUtf8(const std::u16string& text) {
|
|
439
|
+
std::string out;
|
|
440
|
+
out.reserve(text.size());
|
|
441
|
+
for (size_t i = 0; i < text.size(); ++i) {
|
|
442
|
+
char32_t cp = text[i];
|
|
443
|
+
if (cp >= 0xD800 && cp <= 0xDBFF && i + 1 < text.size() && text[i + 1] >= 0xDC00 && text[i + 1] <= 0xDFFF) {
|
|
444
|
+
cp = 0x10000 + ((cp - 0xD800) << 10) + (text[++i] - 0xDC00);
|
|
445
|
+
} else if (cp >= 0xD800 && cp <= 0xDFFF) {
|
|
446
|
+
cp = 0xFFFD;
|
|
447
|
+
}
|
|
448
|
+
if (cp < 0x80) {
|
|
449
|
+
out.push_back(static_cast<char>(cp));
|
|
450
|
+
} else if (cp < 0x800) {
|
|
451
|
+
out.push_back(static_cast<char>(0xC0 | (cp >> 6)));
|
|
452
|
+
out.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
|
|
453
|
+
} else if (cp < 0x10000) {
|
|
454
|
+
out.push_back(static_cast<char>(0xE0 | (cp >> 12)));
|
|
455
|
+
out.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3F)));
|
|
456
|
+
out.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
|
|
457
|
+
} else {
|
|
458
|
+
out.push_back(static_cast<char>(0xF0 | (cp >> 18)));
|
|
459
|
+
out.push_back(static_cast<char>(0x80 | ((cp >> 12) & 0x3F)));
|
|
460
|
+
out.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3F)));
|
|
461
|
+
out.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return out;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
inline std::u16string utf8ToUtf16(const std::string& text) {
|
|
468
|
+
std::u16string out;
|
|
469
|
+
out.reserve(text.size());
|
|
470
|
+
size_t i = 0;
|
|
471
|
+
while (i < text.size()) {
|
|
472
|
+
unsigned char lead = static_cast<unsigned char>(text[i]);
|
|
473
|
+
size_t extra = lead < 0x80 ? 0 : (lead >> 5) == 0x6 ? 1 : (lead >> 4) == 0xE ? 2 : (lead >> 3) == 0x1E ? 3 : 4;
|
|
474
|
+
char32_t cp = extra == 0 ? lead : extra == 1 ? (lead & 0x1F) : extra == 2 ? (lead & 0x0F) : (lead & 0x07);
|
|
475
|
+
bool valid = extra < 4 && i + extra < text.size();
|
|
476
|
+
for (size_t k = 1; valid && k <= extra; ++k) {
|
|
477
|
+
unsigned char next = static_cast<unsigned char>(text[i + k]);
|
|
478
|
+
valid = (next >> 6) == 0x2;
|
|
479
|
+
cp = (cp << 6) | (next & 0x3F);
|
|
480
|
+
}
|
|
481
|
+
if (!valid) {
|
|
482
|
+
out.push_back(0xFFFD);
|
|
483
|
+
++i;
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
i += extra + 1;
|
|
487
|
+
if (cp >= 0x10000) {
|
|
488
|
+
cp -= 0x10000;
|
|
489
|
+
out.push_back(static_cast<char16_t>(0xD800 + (cp >> 10)));
|
|
490
|
+
out.push_back(static_cast<char16_t>(0xDC00 + (cp & 0x3FF)));
|
|
491
|
+
} else {
|
|
492
|
+
out.push_back(static_cast<char16_t>(cp));
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return out;
|
|
496
|
+
}
|
|
497
|
+
|
|
372
498
|
template<typename T>
|
|
373
499
|
struct BindingType<std::basic_string<T>> {
|
|
374
500
|
using String = std::basic_string<T>;
|
|
@@ -389,10 +515,18 @@ struct BindingType<std::basic_string<T>> {
|
|
|
389
515
|
}
|
|
390
516
|
|
|
391
517
|
static WireType2 toWireType2(facebook::jsi::Runtime& rt, String v) {
|
|
392
|
-
|
|
518
|
+
if constexpr (std::is_same_v<T, char16_t>) {
|
|
519
|
+
return facebook::jsi::String::createFromUtf8(rt, utf16ToUtf8(v));
|
|
520
|
+
} else {
|
|
521
|
+
return facebook::jsi::String::createFromUtf8(rt, v);
|
|
522
|
+
}
|
|
393
523
|
}
|
|
394
524
|
static String fromWireType2(facebook::jsi::Runtime& rt, WireType2& v) {
|
|
395
|
-
|
|
525
|
+
if constexpr (std::is_same_v<T, char16_t>) {
|
|
526
|
+
return utf8ToUtf16(v.getString(rt).utf8(rt));
|
|
527
|
+
} else {
|
|
528
|
+
return v.getString(rt).utf8(rt);
|
|
529
|
+
}
|
|
396
530
|
}
|
|
397
531
|
};
|
|
398
532
|
|
|
@@ -459,7 +593,7 @@ struct BindingType<T*> {
|
|
|
459
593
|
return facebook::jsi::BigInt::fromUint64(rt, reinterpret_cast<uint64_t>(b));
|
|
460
594
|
}
|
|
461
595
|
static WireType fromWireType2(facebook::jsi::Runtime& rt, WireType2& wt) {
|
|
462
|
-
return (WireType)
|
|
596
|
+
return (WireType) addressFromValue(rt, wt);
|
|
463
597
|
}
|
|
464
598
|
};
|
|
465
599
|
|
|
@@ -537,7 +671,7 @@ struct EnumBindingType {
|
|
|
537
671
|
return WireType2((int) v);
|
|
538
672
|
}
|
|
539
673
|
static Enum fromWireType2(facebook::jsi::Runtime& rt, WireType2& v) {
|
|
540
|
-
return (WireType)
|
|
674
|
+
return (WireType) integerFromValue(rt, v);
|
|
541
675
|
}
|
|
542
676
|
};
|
|
543
677
|
|
package/js/embind.js
CHANGED
|
@@ -1411,10 +1411,10 @@ function __embind_register_optional(rawOptionalType, rawInnerType) {
|
|
|
1411
1411
|
var inner = innerTypes[0];
|
|
1412
1412
|
return [{
|
|
1413
1413
|
name: `std::optional<${inner.name}>`,
|
|
1414
|
-
// No value crosses as
|
|
1414
|
+
// No value crosses as null (a wire of undefined reads the same); a present value
|
|
1415
1415
|
// rides the inner type's own converters unchanged.
|
|
1416
1416
|
'fromWireType': function (wt) {
|
|
1417
|
-
if (wt === undefined || wt === null) return
|
|
1417
|
+
if (wt === undefined || wt === null) return null;
|
|
1418
1418
|
return inner['fromWireType'](wt);
|
|
1419
1419
|
},
|
|
1420
1420
|
'toWireType': function (destructors, o) {
|
|
@@ -1547,6 +1547,11 @@ function __embind_register_bigint(primitiveType, name, size, minRange, maxRange)
|
|
|
1547
1547
|
if (typeof value != "bigint" && typeof value != "number") {
|
|
1548
1548
|
throw new TypeError(`Cannot convert "${embindRepr(value)}" to ${this.name}`);
|
|
1549
1549
|
}
|
|
1550
|
+
// A Number crosses only while it is an exact integer: BigInt(2**53 + 1) would round
|
|
1551
|
+
// silently, and a 64-bit slot deserves the exact value (the wasm glue gets the same rule).
|
|
1552
|
+
if (typeof value == "number" && !Number.isSafeInteger(value)) {
|
|
1553
|
+
throw new TypeError(`a 64-bit integer parameter takes a BigInt or a safe integer Number, got ${embindRepr(value)}`);
|
|
1554
|
+
}
|
|
1550
1555
|
if (value < minRange || value > maxRange) {
|
|
1551
1556
|
throw new TypeError(`Passing a number "${embindRepr(value)}" from JS side to C/C++ side to an argument of type "${name}", which is outside the valid range [${minRange}, ${maxRange}]!`);
|
|
1552
1557
|
}
|
|
@@ -2006,66 +2011,17 @@ function lengthBytesUTF32(str) {
|
|
|
2006
2011
|
}
|
|
2007
2012
|
function __embind_register_std_wstring(rawType, charSize, name) {
|
|
2008
2013
|
name = readLatin1String(name);
|
|
2009
|
-
var decodeString, encodeString, getHeap, lengthBytesUTF, shift;
|
|
2010
|
-
if (charSize === 2) {
|
|
2011
|
-
decodeString = UTF16ToString;
|
|
2012
|
-
encodeString = stringToUTF16;
|
|
2013
|
-
lengthBytesUTF = lengthBytesUTF16;
|
|
2014
|
-
getHeap = () => HEAPU16;
|
|
2015
|
-
shift = 1;
|
|
2016
|
-
} else if (charSize === 4) {
|
|
2017
|
-
decodeString = UTF32ToString;
|
|
2018
|
-
encodeString = stringToUTF32;
|
|
2019
|
-
lengthBytesUTF = lengthBytesUTF32;
|
|
2020
|
-
getHeap = () => HEAPU32;
|
|
2021
|
-
shift = 2;
|
|
2022
|
-
}
|
|
2023
2014
|
registerType(rawType, {
|
|
2024
2015
|
name: name,
|
|
2016
|
+
// The C++ BindingType turns the jsi string into the C++ string and back (wire.h), as it does for std::string.
|
|
2025
2017
|
'fromWireType': function (value) {
|
|
2026
|
-
|
|
2027
|
-
var length = HEAPU32[value >> 2];
|
|
2028
|
-
var HEAP = getHeap();
|
|
2029
|
-
var str;
|
|
2030
|
-
|
|
2031
|
-
var decodeStartPtr = value + 4;
|
|
2032
|
-
// Looping here to support possible embedded '0' bytes
|
|
2033
|
-
for (var i = 0; i <= length; ++i) {
|
|
2034
|
-
var currentBytePtr = value + 4 + i * charSize;
|
|
2035
|
-
if (i == length || HEAP[currentBytePtr >> shift] == 0) {
|
|
2036
|
-
var maxReadBytes = currentBytePtr - decodeStartPtr;
|
|
2037
|
-
var stringSegment = decodeString(decodeStartPtr, maxReadBytes);
|
|
2038
|
-
if (str === undefined) {
|
|
2039
|
-
str = stringSegment;
|
|
2040
|
-
} else {
|
|
2041
|
-
str += String.fromCharCode(0);
|
|
2042
|
-
str += stringSegment;
|
|
2043
|
-
}
|
|
2044
|
-
decodeStartPtr = currentBytePtr + charSize;
|
|
2045
|
-
}
|
|
2046
|
-
}
|
|
2047
|
-
|
|
2048
|
-
_free(value);
|
|
2049
|
-
|
|
2050
|
-
return str;
|
|
2018
|
+
return value;
|
|
2051
2019
|
},
|
|
2052
2020
|
'toWireType': function (destructors, value) {
|
|
2053
2021
|
if (!(typeof value == 'string')) {
|
|
2054
2022
|
throwBindingError('Cannot pass non-string to C++ string type ' + name);
|
|
2055
2023
|
}
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
// assumes 4-byte alignment
|
|
2059
|
-
var length = lengthBytesUTF(value);
|
|
2060
|
-
var ptr = _malloc(4 + length + charSize);
|
|
2061
|
-
HEAPU32[ptr >> 2] = length >> shift;
|
|
2062
|
-
|
|
2063
|
-
encodeString(value, ptr + 4, length + charSize);
|
|
2064
|
-
|
|
2065
|
-
if (destructors !== null) {
|
|
2066
|
-
destructors.push(_free, ptr);
|
|
2067
|
-
}
|
|
2068
|
-
return ptr;
|
|
2024
|
+
return value;
|
|
2069
2025
|
},
|
|
2070
2026
|
'argPackAdvance': 8n,
|
|
2071
2027
|
'readValueFromPointer': simpleReadValueFromPointer,
|
|
@@ -2192,7 +2148,13 @@ function __crossbind_json_to_handle(text) {
|
|
|
2192
2148
|
globalThis.__crossbind_json_to_handle = __crossbind_json_to_handle;
|
|
2193
2149
|
|
|
2194
2150
|
function __crossbind_handle_to_json(handle) {
|
|
2195
|
-
|
|
2151
|
+
// A Set or Map has no JSON form of its own: write it as the array or object the Rust side
|
|
2152
|
+
// expects, the same rule the web adapter's replacer follows.
|
|
2153
|
+
const s = JSON.stringify(Emval.toValue(handle), (key, value) => {
|
|
2154
|
+
if (value instanceof Set) return Array.from(value);
|
|
2155
|
+
if (value instanceof Map) return Object.fromEntries(value);
|
|
2156
|
+
return value;
|
|
2157
|
+
});
|
|
2196
2158
|
__emval_decref(handle);
|
|
2197
2159
|
return s === undefined ? 'null' : s;
|
|
2198
2160
|
}
|
|
@@ -2215,6 +2177,18 @@ function __crossbind_v_from_num(v) {
|
|
|
2215
2177
|
}
|
|
2216
2178
|
globalThis.__crossbind_v_from_num = __crossbind_v_from_num;
|
|
2217
2179
|
|
|
2180
|
+
// A handle's value as a plain array, so the adapter can copy a typed array element by element.
|
|
2181
|
+
function __crossbind_v_to_array(h) {
|
|
2182
|
+
return Array.from(Emval.toValue(h));
|
|
2183
|
+
}
|
|
2184
|
+
globalThis.__crossbind_v_to_array = __crossbind_v_to_array;
|
|
2185
|
+
|
|
2186
|
+
// Any JS value the adapter already built (a typed array, say) gets a handle of its own.
|
|
2187
|
+
function __crossbind_v_from_value(v) {
|
|
2188
|
+
return Emval.toHandle(v);
|
|
2189
|
+
}
|
|
2190
|
+
globalThis.__crossbind_v_from_value = __crossbind_v_from_value;
|
|
2191
|
+
|
|
2218
2192
|
function __crossbind_v_from_bool(v) {
|
|
2219
2193
|
return Emval.toHandle(!!v);
|
|
2220
2194
|
}
|
|
@@ -2507,13 +2481,30 @@ function replacePublicSymbol(name, value, numArguments) {
|
|
|
2507
2481
|
|
|
2508
2482
|
|
|
2509
2483
|
|
|
2484
|
+
// A Rust binding cannot throw through Hermes: an exception escaping a host function comes back
|
|
2485
|
+
// as "Exception in HostFunction: ...", with the message rewritten and any property (the error
|
|
2486
|
+
// `code`) gone. The adapter parks the error here instead, and every invoker rethrows it on the
|
|
2487
|
+
// way out, so JS sees an Error this runtime built.
|
|
2488
|
+
globalThis.__crossbind_park_error = function (message, code) {
|
|
2489
|
+
var error = new Error(message);
|
|
2490
|
+
if (code !== undefined) error.code = code;
|
|
2491
|
+
globalThis.__crossbindParkedError = error;
|
|
2492
|
+
};
|
|
2493
|
+
|
|
2510
2494
|
function getDynCaller(signature, rawFunction, slice) {
|
|
2511
2495
|
return (...args) => {
|
|
2512
2496
|
//console.log('getDynCallerReturn', signature, rawFunction, args.length, ...args);
|
|
2513
2497
|
if (slice) {
|
|
2514
2498
|
args = args.slice(1);
|
|
2515
2499
|
}
|
|
2516
|
-
|
|
2500
|
+
globalThis.__crossbindParkedError = null;
|
|
2501
|
+
const result = rawFunction(...args);
|
|
2502
|
+
const parked = globalThis.__crossbindParkedError;
|
|
2503
|
+
if (parked) {
|
|
2504
|
+
globalThis.__crossbindParkedError = null;
|
|
2505
|
+
throw parked;
|
|
2506
|
+
}
|
|
2507
|
+
return result;
|
|
2517
2508
|
};
|
|
2518
2509
|
}
|
|
2519
2510
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crossbind/core-embind-jsi",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.60",
|
|
4
4
|
"description": "The Embind JSI integration tool enables seamless C++ integration with React Native and Expo.",
|
|
5
5
|
"homepage": "https://github.com/crossbind/crossbind/tree/main/core/embind-jsi#readme",
|
|
6
6
|
"repository": {
|