expo-modules-jsi 58.0.7 → 58.0.9
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 +22 -0
- package/apple/Benchmarks/ContainerCodingBenchmarks.swift +74 -0
- package/apple/Benchmarks/HostFunctionBenchmarks.swift +41 -0
- package/apple/Benchmarks/RuntimeCacheBenchmarks.swift +71 -0
- package/apple/Benchmarks/UnownedDecodeBenchmarks.swift +121 -0
- package/apple/Benchmarks/ValueAccessBenchmarks.swift +23 -0
- package/apple/Sources/ExpoModulesJSI/Coding/JavaScriptCodable+Containers.swift +52 -10
- package/apple/Sources/ExpoModulesJSI/Coding/JavaScriptCodable+Data.swift +5 -0
- package/apple/Sources/ExpoModulesJSI/Coding/JavaScriptCodable+Date.swift +27 -5
- package/apple/Sources/ExpoModulesJSI/Coding/JavaScriptCodable+Primitives.swift +210 -21
- package/apple/Sources/ExpoModulesJSI/Coding/JavaScriptCodable+Set.swift +79 -0
- package/apple/Sources/ExpoModulesJSI/Coding/JavaScriptDecodable.swift +34 -5
- package/apple/Sources/ExpoModulesJSI/Coding/JavaScriptValueKinds.swift +73 -0
- package/apple/Sources/ExpoModulesJSI/Protocols/JSIRepresentable.swift +4 -2
- package/apple/Sources/ExpoModulesJSI/Runtime/JavaScriptRuntime.swift +43 -31
- package/apple/Sources/ExpoModulesJSI/Runtime/JavaScriptRuntimeCache.swift +74 -0
- package/apple/Sources/ExpoModulesJSI/Runtime/Values/JavaScriptArray.swift +26 -0
- package/apple/Sources/ExpoModulesJSI/Runtime/Values/JavaScriptObject.swift +97 -7
- package/apple/Sources/ExpoModulesJSI/Runtime/Values/JavaScriptPromise.swift +14 -14
- package/apple/Sources/ExpoModulesJSI/Runtime/Values/JavaScriptUnownedValue.swift +17 -0
- package/apple/Sources/ExpoModulesJSI/Runtime/Values/JavaScriptValue.swift +54 -1
- package/apple/Sources/ExpoModulesJSI/Utilities/Errors.swift +19 -0
- package/apple/Sources/ExpoModulesJSI/Utilities/String+JSI.swift +4 -3
- package/apple/Sources/ExpoModulesJSI/Utilities/UncheckedSendable.swift +13 -0
- package/apple/Sources/ExpoModulesJSI-Cxx/include/JSIUtils.h +68 -0
- package/apple/Sources/ExpoModulesJSI-Cxx/include/RuntimeScheduler.h +18 -5
- package/apple/Tests/JavaScriptCodableSetTests.swift +115 -0
- package/apple/Tests/JavaScriptDecodableKindsTests.swift +132 -0
- package/apple/Tests/JavaScriptRuntimeCacheTests.swift +117 -0
- package/apple/Tests/JavaScriptRuntimeTests.swift +86 -3
- package/apple/Tests/JavaScriptUnownedDecodeTests.swift +99 -0
- package/apple/Tests/JavaScriptUnownedElementsTests.swift +76 -0
- package/apple/scripts/build-xcframework.sh +15 -0
- package/package.json +1 -1
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Copyright 2026-present 650 Industries. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import ExpoModulesJSI
|
|
4
|
+
import Foundation
|
|
5
|
+
import Testing
|
|
6
|
+
|
|
7
|
+
/// Decodes through the `JavaScriptUnownedValue` overloads, and through `withUnownedValue(in:_:)` from an
|
|
8
|
+
/// owning value.
|
|
9
|
+
@Suite("JavaScriptDecodable unowned decode")
|
|
10
|
+
@JavaScriptActor
|
|
11
|
+
struct JavaScriptUnownedDecodeTests {
|
|
12
|
+
let runtime = JavaScriptRuntime()
|
|
13
|
+
|
|
14
|
+
private func unowned<T: JavaScriptDecodable>(_ type: T.Type, from source: String) throws -> T {
|
|
15
|
+
let value = try runtime.eval(source)
|
|
16
|
+
let buffer = JavaScriptValuesBuffer.allocate(in: runtime, with: value)
|
|
17
|
+
return try T.decode(buffer.unownedValue(at: 0), in: runtime)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// MARK: - withUnownedValue
|
|
21
|
+
|
|
22
|
+
@Test
|
|
23
|
+
func `withUnownedValue borrows the value it is called on`() throws {
|
|
24
|
+
let number = try runtime.eval("42")
|
|
25
|
+
#expect(number.withUnownedValue(in: runtime) { $0.getDouble() } == 42)
|
|
26
|
+
|
|
27
|
+
let string = try runtime.eval("'text'")
|
|
28
|
+
#expect(string.withUnownedValue(in: runtime) { $0.getString() } == "text")
|
|
29
|
+
|
|
30
|
+
let object = try runtime.eval("({ answer: 42 })")
|
|
31
|
+
let answer = try object.withUnownedValue(in: runtime) { try $0.asObject(in: runtime).getProperty("answer").asInt() }
|
|
32
|
+
#expect(answer == 42)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@Test
|
|
36
|
+
func `withUnownedValue works for a value without a runtime`() {
|
|
37
|
+
let value = JavaScriptValue.number(1.5)
|
|
38
|
+
#expect(value.withUnownedValue(in: runtime) { $0.getDouble() } == 1.5)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// MARK: - Default owning decode
|
|
42
|
+
|
|
43
|
+
@Test
|
|
44
|
+
func `a type that only decodes unowned values also decodes owning ones`() throws {
|
|
45
|
+
let value = try runtime.eval("7")
|
|
46
|
+
#expect(try UnownedOnly.decode(value, in: runtime) == UnownedOnly(number: 7))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// MARK: - Containers
|
|
50
|
+
|
|
51
|
+
@Test
|
|
52
|
+
func `Array decodes an array and arrayizes a scalar`() throws {
|
|
53
|
+
#expect(try unowned([Int].self, from: "[1, 2, 3]") == [1, 2, 3])
|
|
54
|
+
#expect(try unowned([Int].self, from: "4") == [4])
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@Test
|
|
58
|
+
func `Dictionary decodes an object and skips undefined properties`() throws {
|
|
59
|
+
#expect(try unowned([String: Int].self, from: "({ a: 1, b: undefined })") == ["a": 1])
|
|
60
|
+
#expect(throws: (any Error).self) {
|
|
61
|
+
_ = try unowned([String: Int].self, from: "42")
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@Test
|
|
66
|
+
func `Date decodes a number, a string and a Date`() throws {
|
|
67
|
+
#expect(try unowned(Date.self, from: "1000") == Date(timeIntervalSince1970: 1))
|
|
68
|
+
#expect(try unowned(Date.self, from: "'1970-01-01T00:00:02Z'") == Date(timeIntervalSince1970: 2))
|
|
69
|
+
#expect(try unowned(Date.self, from: "new Date(3000)") == Date(timeIntervalSince1970: 3))
|
|
70
|
+
#expect(throws: (any Error).self) {
|
|
71
|
+
_ = try unowned(Date.self, from: "true")
|
|
72
|
+
}
|
|
73
|
+
#expect(throws: (any Error).self) {
|
|
74
|
+
_ = try unowned(Date.self, from: "({ getTime() { return 0 } })")
|
|
75
|
+
}
|
|
76
|
+
#expect(throws: (any Error).self) {
|
|
77
|
+
_ = try unowned(Date.self, from: "'not a date'")
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@Test
|
|
82
|
+
func `Date decodes the same from an owning value`() throws {
|
|
83
|
+
#expect(try Date.decode(runtime.eval("1000"), in: runtime) == Date(timeIntervalSince1970: 1))
|
|
84
|
+
#expect(try Date.decode(runtime.eval("'1970-01-01T00:00:02Z'"), in: runtime) == Date(timeIntervalSince1970: 2))
|
|
85
|
+
#expect(try Date.decode(runtime.eval("new Date(3000)"), in: runtime) == Date(timeIntervalSince1970: 3))
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// Implements only the unowned decode, so its owning decode is the protocol default.
|
|
90
|
+
private struct UnownedOnly: JavaScriptDecodable, Equatable {
|
|
91
|
+
let number: Double
|
|
92
|
+
|
|
93
|
+
@JavaScriptActor
|
|
94
|
+
static func decode(_ value: borrowing JavaScriptUnownedValue, in runtime: borrowing JavaScriptRuntime) throws
|
|
95
|
+
-> UnownedOnly
|
|
96
|
+
{
|
|
97
|
+
return UnownedOnly(number: try value.asDouble())
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Copyright 2026-present 650 Industries. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import ExpoModulesJSI
|
|
4
|
+
import Testing
|
|
5
|
+
|
|
6
|
+
/// Lending array elements and object properties out as `JavaScriptUnownedValue`s.
|
|
7
|
+
@Suite("Unowned elements and properties")
|
|
8
|
+
@JavaScriptActor
|
|
9
|
+
struct JavaScriptUnownedElementsTests {
|
|
10
|
+
let runtime = JavaScriptRuntime()
|
|
11
|
+
|
|
12
|
+
@Test
|
|
13
|
+
func `mapUnowned transforms every element in order`() throws {
|
|
14
|
+
let array = try runtime.eval("[1, 'two', null, { four: 4 }]").getArray()
|
|
15
|
+
let kinds = array.mapUnowned { element -> String in
|
|
16
|
+
if element.isNumber() {
|
|
17
|
+
return "number \(element.getDouble())"
|
|
18
|
+
}
|
|
19
|
+
if element.isString() {
|
|
20
|
+
return "string \(element.getString())"
|
|
21
|
+
}
|
|
22
|
+
if element.isNull() {
|
|
23
|
+
return "null"
|
|
24
|
+
}
|
|
25
|
+
return "object"
|
|
26
|
+
}
|
|
27
|
+
#expect(kinds == ["number 1.0", "string two", "null", "object"])
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@Test
|
|
31
|
+
func `mapUnowned of an empty array is empty`() throws {
|
|
32
|
+
let array = try runtime.eval("[]").getArray()
|
|
33
|
+
#expect(array.mapUnowned { $0.isNumber() }.isEmpty)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@Test
|
|
37
|
+
func `mapUnowned rethrows the transform's error`() throws {
|
|
38
|
+
let array = try runtime.eval("[1, 'two']").getArray()
|
|
39
|
+
#expect(throws: (any Error).self) {
|
|
40
|
+
_ = try array.mapUnowned { try $0.asDouble() }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@Test
|
|
45
|
+
func `withUnownedProperty lends the property and undefined for a missing one`() throws {
|
|
46
|
+
let object = try runtime.eval("({ answer: 42, name: 'expo' })").getObject()
|
|
47
|
+
#expect(object.withUnownedProperty("answer") { $0.getDouble() } == 42)
|
|
48
|
+
#expect(object.withUnownedProperty("name") { $0.getString() } == "expo")
|
|
49
|
+
#expect(object.withUnownedProperty("missing") { $0.isUndefined() })
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@Test
|
|
53
|
+
func `containers decode nested values through the unowned path`() throws {
|
|
54
|
+
let value = try runtime.eval("[[1, 2], [3]]")
|
|
55
|
+
#expect(try [[Int]].decode(value, in: runtime) == [[1, 2], [3]])
|
|
56
|
+
let dictionary = try runtime.eval("({ a: [1], b: undefined, c: [2, 3] })")
|
|
57
|
+
#expect(try [String: [Int]].decode(dictionary, in: runtime) == ["a": [1], "c": [2, 3]])
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@Test
|
|
61
|
+
func `an unowned value tells arrays apart and reads them`() throws {
|
|
62
|
+
let buffer = JavaScriptValuesBuffer.allocate(
|
|
63
|
+
in: runtime,
|
|
64
|
+
with: try runtime.eval("[1, 2, 3]"),
|
|
65
|
+
try runtime.eval("({ length: 3 })"),
|
|
66
|
+
try runtime.eval("new Uint8Array(3)"),
|
|
67
|
+
try runtime.eval("3")
|
|
68
|
+
)
|
|
69
|
+
// `#expect` can't capture the `~Copyable` value, so compare the results instead.
|
|
70
|
+
let isArray = (0..<4).map { buffer.unownedValue(at: $0).isArray() }
|
|
71
|
+
#expect(isArray == [true, false, false, false])
|
|
72
|
+
let array = buffer.unownedValue(at: 0).getArray(in: runtime)
|
|
73
|
+
#expect(array.length == 3)
|
|
74
|
+
#expect(array.mapUnowned { $0.getDouble() } == [1, 2, 3])
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -208,6 +208,18 @@ build_slice() {
|
|
|
208
208
|
# `___profc_*` symbols and `__llvm_prf_*` sections) plus ~40% extra binary size. Setting
|
|
209
209
|
# CLANG_COVERAGE_MAPPING=NO is what removes the flags; CLANG_ENABLE_CODE_COVERAGE=NO alone
|
|
210
210
|
# does not, and `-enableCodeCoverage NO` is rejected outside of `test`.
|
|
211
|
+
#
|
|
212
|
+
# With a dSYM, Xcode's default STRIP_SWIFT_SYMBOLS=YES runs `strip -T`, which removes or renames to
|
|
213
|
+
# `<redacted>` every Swift symbol that isn't exported. Crash reports symbolicated on the device then
|
|
214
|
+
# show no function name for any frame in this framework. Keeping the symbols adds about 250 KB of
|
|
215
|
+
# symbol table to the device slice; the code itself doesn't change.
|
|
216
|
+
#
|
|
217
|
+
# The binary .swiftmodule must not embed this checkout's absolute paths: consumers key
|
|
218
|
+
# the Xcode compilation cache on its content, so any PACKAGE_DIR or PODS_ROOT path inside
|
|
219
|
+
# it makes every module that imports ExpoModulesJSI a cache miss in another checkout or
|
|
220
|
+
# worktree. SWIFT_SERIALIZE_DEBUGGING_OPTIONS=NO drops the serialized search paths and
|
|
221
|
+
# SWIFT_ENABLE_EXPLICIT_MODULES=NO the explicit-module cache directories under
|
|
222
|
+
# .DerivedData. The .swiftinterface and the dSYM are unaffected.
|
|
211
223
|
(cd "$PACKAGE_DIR" && env -i PATH="$PATH" HOME="$HOME" PODS_ROOT="$PODS_ROOT" RN_ROOT="$RN_ROOT" \
|
|
212
224
|
xcodebuild \
|
|
213
225
|
build \
|
|
@@ -226,10 +238,13 @@ build_slice() {
|
|
|
226
238
|
BUILD_LIBRARY_FOR_DISTRIBUTION=YES \
|
|
227
239
|
SKIP_INSTALL=NO \
|
|
228
240
|
DEBUG_INFORMATION_FORMAT=dwarf-with-dsym \
|
|
241
|
+
STRIP_SWIFT_SYMBOLS=NO \
|
|
229
242
|
COMPILER_INDEX_STORE_ENABLE=NO \
|
|
230
243
|
SWIFT_COMPILATION_MODE=wholemodule \
|
|
231
244
|
CLANG_ENABLE_CODE_COVERAGE=NO \
|
|
232
245
|
CLANG_COVERAGE_MAPPING=NO \
|
|
246
|
+
SWIFT_SERIALIZE_DEBUGGING_OPTIONS=NO \
|
|
247
|
+
SWIFT_ENABLE_EXPLICIT_MODULES=NO \
|
|
233
248
|
)
|
|
234
249
|
|
|
235
250
|
local product_path="${BUILD_PRODUCTS_PATH}/${build_dir_name}"
|