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.
Files changed (34) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/apple/Benchmarks/ContainerCodingBenchmarks.swift +74 -0
  3. package/apple/Benchmarks/HostFunctionBenchmarks.swift +41 -0
  4. package/apple/Benchmarks/RuntimeCacheBenchmarks.swift +71 -0
  5. package/apple/Benchmarks/UnownedDecodeBenchmarks.swift +121 -0
  6. package/apple/Benchmarks/ValueAccessBenchmarks.swift +23 -0
  7. package/apple/Sources/ExpoModulesJSI/Coding/JavaScriptCodable+Containers.swift +52 -10
  8. package/apple/Sources/ExpoModulesJSI/Coding/JavaScriptCodable+Data.swift +5 -0
  9. package/apple/Sources/ExpoModulesJSI/Coding/JavaScriptCodable+Date.swift +27 -5
  10. package/apple/Sources/ExpoModulesJSI/Coding/JavaScriptCodable+Primitives.swift +210 -21
  11. package/apple/Sources/ExpoModulesJSI/Coding/JavaScriptCodable+Set.swift +79 -0
  12. package/apple/Sources/ExpoModulesJSI/Coding/JavaScriptDecodable.swift +34 -5
  13. package/apple/Sources/ExpoModulesJSI/Coding/JavaScriptValueKinds.swift +73 -0
  14. package/apple/Sources/ExpoModulesJSI/Protocols/JSIRepresentable.swift +4 -2
  15. package/apple/Sources/ExpoModulesJSI/Runtime/JavaScriptRuntime.swift +43 -31
  16. package/apple/Sources/ExpoModulesJSI/Runtime/JavaScriptRuntimeCache.swift +74 -0
  17. package/apple/Sources/ExpoModulesJSI/Runtime/Values/JavaScriptArray.swift +26 -0
  18. package/apple/Sources/ExpoModulesJSI/Runtime/Values/JavaScriptObject.swift +97 -7
  19. package/apple/Sources/ExpoModulesJSI/Runtime/Values/JavaScriptPromise.swift +14 -14
  20. package/apple/Sources/ExpoModulesJSI/Runtime/Values/JavaScriptUnownedValue.swift +17 -0
  21. package/apple/Sources/ExpoModulesJSI/Runtime/Values/JavaScriptValue.swift +54 -1
  22. package/apple/Sources/ExpoModulesJSI/Utilities/Errors.swift +19 -0
  23. package/apple/Sources/ExpoModulesJSI/Utilities/String+JSI.swift +4 -3
  24. package/apple/Sources/ExpoModulesJSI/Utilities/UncheckedSendable.swift +13 -0
  25. package/apple/Sources/ExpoModulesJSI-Cxx/include/JSIUtils.h +68 -0
  26. package/apple/Sources/ExpoModulesJSI-Cxx/include/RuntimeScheduler.h +18 -5
  27. package/apple/Tests/JavaScriptCodableSetTests.swift +115 -0
  28. package/apple/Tests/JavaScriptDecodableKindsTests.swift +132 -0
  29. package/apple/Tests/JavaScriptRuntimeCacheTests.swift +117 -0
  30. package/apple/Tests/JavaScriptRuntimeTests.swift +86 -3
  31. package/apple/Tests/JavaScriptUnownedDecodeTests.swift +99 -0
  32. package/apple/Tests/JavaScriptUnownedElementsTests.swift +76 -0
  33. package/apple/scripts/build-xcframework.sh +15 -0
  34. 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}"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-modules-jsi",
3
- "version": "58.0.7",
3
+ "version": "58.0.9",
4
4
  "description": "The JavaScript Interface for Expo Modules",
5
5
  "main": "index.js",
6
6
  "sideEffects": [],