expo-modules-core 56.0.3 → 56.0.5

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 CHANGED
@@ -10,6 +10,17 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 56.0.5 — 2026-05-08
14
+
15
+ ### 🐛 Bug fixes
16
+
17
+ - [iOS] Fix dictionary/array returns from sync functions invoked in the worklet runtime. ([#45419](https://github.com/expo/expo/pull/45419) by [@nishan](https://github.com/intergalacticspacehighway))
18
+ - [Android] Fix unsetting border width on views with border radius causing views to disappear ([#45467](https://github.com/expo/expo/pull/45467) by [@fractalbeauty](https://github.com/fractalbeauty))
19
+
20
+ ## 56.0.4 — 2026-05-07
21
+
22
+ _This version does not introduce any user-facing changes._
23
+
13
24
  ## 56.0.3 — 2026-05-06
14
25
 
15
26
  _This version does not introduce any user-facing changes._
@@ -1,6 +1,5 @@
1
- import com.android.build.gradle.tasks.ExternalNativeBuildJsonTask
2
- import expo.modules.plugin.gradle.ExpoModuleExtension
3
1
  import groovy.json.JsonSlurper
2
+ import expo.modules.plugin.gradle.ExpoModuleExtension
4
3
  import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
5
4
 
6
5
  buildscript {
@@ -17,7 +16,6 @@ buildscript {
17
16
  classpath("org.jetbrains.kotlin.plugin.compose:org.jetbrains.kotlin.plugin.compose.gradle.plugin:${kotlinVersion}")
18
17
  }
19
18
 
20
- classpath("org.apache.commons:commons-text:1.14.0")
21
19
  }
22
20
  }
23
21
 
@@ -29,7 +27,7 @@ if (shouldIncludeCompose) {
29
27
  }
30
28
 
31
29
  group = 'host.exp.exponent'
32
- version = '56.0.3'
30
+ version = '56.0.5'
33
31
 
34
32
  def isExpoModulesCoreTests = {
35
33
  Gradle gradle = getGradle()
@@ -96,7 +94,7 @@ android {
96
94
  defaultConfig {
97
95
  consumerProguardFiles 'proguard-rules.pro'
98
96
  versionCode 1
99
- versionName "56.0.3"
97
+ versionName "56.0.5"
100
98
  buildConfigField "String", "EXPO_MODULES_CORE_VERSION", "\"${versionName}\""
101
99
  buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", "true"
102
100
 
@@ -265,55 +263,56 @@ if (shouldTurnWarningsIntoErrors) {
265
263
  }
266
264
  }
267
265
 
268
- // Generates the PCH file during sync if it doesn't exist yet
269
- def generatePCHTask = tasks.register("generatePCH") {
270
- def configureTaskName = "configureCMakeDebug"
271
- dependsOn(configureTaskName)
266
+ // Generates minimal stub PCH files from an empty header so the IDE's C++ engine
267
+ // doesn't fail during sync. Near-instant unlike the full ExpoHeader.pch.
268
+ // The actual build regenerates proper PCH files via ninja.
269
+ def cxxDir = project.file(".cxx")
270
+ def generateStubPCHTask = tasks.register("generateStubPCH") {
271
+ dependsOn("configureCMakeDebug")
272
272
 
273
273
  doLast {
274
- reactNativeArchitectures().each { abi ->
275
- def configureTaskNameForAbi = configureTaskName + "[" + abi + "]"
276
- ExternalNativeBuildJsonTask configureTask = tasks.named(configureTaskNameForAbi).get() as ExternalNativeBuildJsonTask
277
-
278
- // Gets CxxModel for the given ABI
279
- File cxxBuildFolder = configureTask.abi.cxxBuildFolder
274
+ if (!cxxDir.exists()) {
275
+ return
276
+ }
280
277
 
281
- // Gets compile_commands.json file to find the command to generate the PCH file
282
- File compileCommandsFile = new File(cxxBuildFolder, "compile_commands.json")
283
- if (!compileCommandsFile.exists()) {
278
+ cxxDir.eachFileRecurse { file ->
279
+ if (file.name != "compile_commands.json") {
284
280
  return
285
281
  }
286
282
 
287
- def parsedJson = new JsonSlurper().parseText(compileCommandsFile.text)
288
- for (int i = 0; i < parsedJson.size(); i++) {
289
- def commandObj = parsedJson[i]
290
-
291
- def path = commandObj.file
292
- if (!path.endsWith("cmake_pch.hxx.cxx")) {
293
- continue
283
+ new JsonSlurper().parseText(file.text).each { entry ->
284
+ if (!entry.file.endsWith("cmake_pch.hxx.cxx")) {
285
+ return
294
286
  }
295
287
 
296
- def generatedFilePath = path.substring(0, path.length() - ".cxx".length()) + ".pch"
297
- // Checks if the file already exists, and skip if so
298
- if (new File(generatedFilePath).exists()) {
299
- continue
288
+ def pchFile = new File(entry.file.substring(0, entry.file.length() - ".cxx".length()) + ".pch")
289
+
290
+ if (!pchFile.exists() || pchFile.length() == 0) {
291
+ def stubHeader = new File(entry.directory, "stub_pch.hxx")
292
+ stubHeader.text = ""
293
+
294
+ def cmd = entry.command
295
+ // Replace the forced-include path: `-Xclang -include -Xclang <path>/cmake_pch.hxx`
296
+ .replaceAll(/-Xclang -include -Xclang [^\s]+cmake_pch\.hxx(?=\s)/, "-Xclang -include -Xclang ${stubHeader.absolutePath}")
297
+ // Replace the source file operand: `<path>/cmake_pch.hxx.cxx`
298
+ .replaceAll(/[^\s]+cmake_pch\.hxx\.cxx/, stubHeader.absolutePath)
299
+
300
+ def process = new ProcessBuilder(cmd.split(" ").toList())
301
+ .directory(new File(entry.directory))
302
+ .redirectErrorStream(true)
303
+ .start()
304
+ if (process.waitFor() != 0) {
305
+ throw new GradleException("Stub PCH generation failed: ${process.inputStream.text}")
306
+ }
300
307
  }
301
308
 
302
- def tokenizer = new org.apache.commons.text.StringTokenizer(commandObj.command, " ")
303
- def tokens = tokenizer.tokenList
304
-
305
- def workingDirFile = new File(commandObj.directory)
306
-
307
- providers.exec {
308
- workingDir(providers.provider { workingDirFile }.get())
309
- commandLine(tokens)
310
- }.getResult().get().assertNormalExitValue()
309
+ // Ensure PCH is older than source so ninja rebuilds the real one during build
310
+ pchFile.setLastModified(new File(entry.file).lastModified() - 1)
311
311
  }
312
312
  }
313
313
  }
314
314
  }
315
315
 
316
- // This task will run on the IDE project sync, ensuring the PCH file is generated early enough
317
316
  tasks.register("prepareKotlinBuildScriptModel") {
318
- dependsOn(generatePCHTask)
317
+ dependsOn(generateStubPCHTask)
319
318
  }
@@ -3,7 +3,6 @@ package expo.modules.kotlin.types
3
3
  import android.net.Uri
4
4
  import android.os.Bundle
5
5
  import expo.modules.kotlin.jni.ReturnType
6
- import expo.modules.kotlin.records.Field
7
6
  import expo.modules.kotlin.records.Record
8
7
  import expo.modules.kotlin.records.formatters.FormattedRecord
9
8
  import expo.modules.kotlin.typedarray.RawTypedArrayHolder
@@ -121,7 +120,7 @@ interface JSTypeConverter<T> {
121
120
  object RecordConverter : JSTypeConverter<Record> {
122
121
  override fun convertToJS(value: Any?): Any? {
123
122
  enforceType<Record?>(value)
124
- return value?.toJSValueExperimental()
123
+ return value?.toJSValueExperimental(null)
125
124
  }
126
125
 
127
126
  override val returnType: ReturnType
@@ -129,49 +128,11 @@ interface JSTypeConverter<T> {
129
128
  }
130
129
 
131
130
  class IntrospectableRecordConverter(
132
- introspectableData: PIntrospectionData<Record>
131
+ private val introspectableData: PIntrospectionData<Record>
133
132
  ) : JSTypeConverter<Record> {
134
- data class Property(
135
- val key: String,
136
- val getter: (Record) -> Any?
137
- )
138
-
139
- private val properties = introspectableData
140
- .properties
141
- .mapNotNull { property ->
142
- val fieldAnnotation = property
143
- .annotations
144
- .firstOrNull { annotation -> annotation.jClass == Field::class.java }
145
- ?: return@mapNotNull null
146
-
147
- val propertyName = (
148
- fieldAnnotation
149
- .arguments
150
- .getOrDefault("key", property.name) as String
151
- )
152
- .ifEmpty { property.name }
153
-
154
- Property(
155
- propertyName,
156
- property::get
157
- )
158
- }
159
-
160
133
  override fun convertToJS(value: Any?): Any? {
161
- if (value == null) {
162
- return null
163
- }
164
-
165
- enforceType<Record>(value)
166
- val result = mutableMapOf<String, Any?>()
167
-
168
- properties.forEach { property ->
169
- val propValue = property.getter(value)
170
- val convertedValue = JSTypeConverterProvider.convertToJSValue(propValue, useExperimentalConverter = true)
171
- result[property.key] = convertedValue
172
- }
173
-
174
- return result
134
+ enforceType<Record?>(value)
135
+ return value?.toJSValueExperimental(introspectableData)
175
136
  }
176
137
 
177
138
  override val returnType: ReturnType
@@ -10,6 +10,9 @@ import expo.modules.kotlin.records.Field
10
10
  import expo.modules.kotlin.records.Record
11
11
  import expo.modules.kotlin.records.formatters.FormattedRecord
12
12
  import expo.modules.kotlin.records.formatters.ValueOrSkip
13
+ import io.github.lukmccall.pika.PIntrospectionData
14
+ import io.github.lukmccall.pika.PIntrospectionProvider
15
+ import io.github.lukmccall.pika.PProperty
13
16
  import java.io.File
14
17
  import java.net.URI
15
18
  import java.net.URL
@@ -20,22 +23,63 @@ import kotlin.reflect.full.memberProperties
20
23
  import kotlin.reflect.full.primaryConstructor
21
24
  import kotlin.reflect.jvm.isAccessible
22
25
 
23
- fun Record.toJSValueExperimental(): Map<String, Any?> {
26
+ /**
27
+ * Returns field key if property is annotated with [Field]
28
+ */
29
+ private fun PProperty<Record, *>.asFieldKey(): String? {
30
+ val fieldAnnotation = annotations
31
+ .firstOrNull { annotation -> annotation.jClass == Field::class.java }
32
+ ?: return null
33
+
34
+ val propertyName = (
35
+ fieldAnnotation
36
+ .arguments
37
+ .getOrDefault("key", name) as String
38
+ )
39
+ .ifEmpty { name }
40
+
41
+ return propertyName
42
+ }
43
+
44
+ private fun Record.getIntrospectionData(): PIntrospectionData<Record>? {
45
+ return if (this is PIntrospectionProvider) {
46
+ @Suppress("UNCHECKED_CAST")
47
+ getIntrospectionData() as? PIntrospectionData<Record>
48
+ } else {
49
+ null
50
+ }
51
+ }
52
+
53
+ fun Record.toJSValueExperimental(
54
+ introspectionData: PIntrospectionData<Record>? = this.getIntrospectionData()
55
+ ): Map<String, Any?> {
24
56
  val result = mutableMapOf<String, Any?>()
25
57
 
26
- javaClass
27
- .kotlin
28
- .memberProperties
29
- .forEach { property ->
30
- val fieldInformation = property.findAnnotation<Field>() ?: return@forEach
31
- val jsKey = fieldInformation.key.takeUnless { it == "" } ?: property.name
58
+ if (introspectionData == null) {
59
+ javaClass
60
+ .kotlin
61
+ .memberProperties
62
+ .forEach { property ->
63
+ val fieldInformation = property.findAnnotation<Field>() ?: return@forEach
64
+ val jsKey = fieldInformation.key.takeUnless { it == "" } ?: property.name
32
65
 
33
- property.isAccessible = true
66
+ property.isAccessible = true
34
67
 
35
- val value = property.get(this)
36
- val convertedValue = JSTypeConverterProvider.convertToJSValue(value, useExperimentalConverter = true)
37
- result[jsKey] = convertedValue
38
- }
68
+ val value = property.get(this)
69
+ val convertedValue = JSTypeConverterProvider.convertToJSValue(value, useExperimentalConverter = true)
70
+ result[jsKey] = convertedValue
71
+ }
72
+ } else {
73
+ introspectionData
74
+ .properties
75
+ .forEach { property ->
76
+ val propertyKey = property.asFieldKey() ?: return@forEach
77
+
78
+ val propValue = property.get(this)
79
+ val convertedValue = JSTypeConverterProvider.convertToJSValue(propValue, useExperimentalConverter = true)
80
+ result[propertyKey] = convertedValue
81
+ }
82
+ }
39
83
 
40
84
  return result
41
85
  }
@@ -74,21 +118,37 @@ fun FormattedRecord<*>.toJSValueExperimental(): Map<String, Any?> {
74
118
  return result
75
119
  }
76
120
 
77
- fun Record.toJSValue(containerProvider: JSTypeConverterProvider.ContainerProvider): WritableMap {
121
+ fun Record.toJSValue(
122
+ containerProvider: JSTypeConverterProvider.ContainerProvider,
123
+ introspectionData: PIntrospectionData<Record>? = this.getIntrospectionData()
124
+ ): WritableMap {
78
125
  val result = containerProvider.createMap()
79
126
 
80
- javaClass
81
- .kotlin
82
- .memberProperties.map { property ->
83
- val fieldInformation = property.findAnnotation<Field>() ?: return@map
84
- val jsKey = fieldInformation.key.takeUnless { it == "" } ?: property.name
127
+ if (introspectionData == null) {
128
+ javaClass
129
+ .kotlin
130
+ .memberProperties
131
+ .forEach { property ->
132
+ val fieldInformation = property.findAnnotation<Field>() ?: return@forEach
133
+ val jsKey = fieldInformation.key.takeUnless { it == "" } ?: property.name
85
134
 
86
- property.isAccessible = true
135
+ property.isAccessible = true
87
136
 
88
- val value = property.get(this)
89
- val convertedValue = JSTypeConverterProvider.legacyConvertToJSValue(value, containerProvider)
90
- result.putGeneric(jsKey, convertedValue)
91
- }
137
+ val value = property.get(this)
138
+ val convertedValue = JSTypeConverterProvider.legacyConvertToJSValue(value, containerProvider)
139
+ result.putGeneric(jsKey, convertedValue)
140
+ }
141
+ } else {
142
+ introspectionData
143
+ .properties
144
+ .forEach { property ->
145
+ val propertyKey = property.asFieldKey() ?: return@forEach
146
+
147
+ val value = property.get(this)
148
+ val convertedValue = JSTypeConverterProvider.legacyConvertToJSValue(value, containerProvider)
149
+ result.putGeneric(propertyKey, convertedValue)
150
+ }
151
+ }
92
152
 
93
153
  return result
94
154
  }
@@ -64,7 +64,7 @@ private fun <T : View> ViewDefinitionBuilder<T>.UseBorderWidthProps() {
64
64
  BackgroundStyleApplicator.setBorderWidth(
65
65
  view,
66
66
  edge,
67
- width ?: Float.NaN
67
+ width
68
68
  )
69
69
  }
70
70
  }
@@ -138,13 +138,20 @@ public struct Conversions {
138
138
  `unknownToJavaScriptValue` which handles type-erased values through `NSNumber`/`NSDictionary` matching.
139
139
  */
140
140
  static func anyToJavaScriptValue<ValueType>(_ value: ValueType, appContext: AppContext) throws -> JavaScriptValue {
141
+ return try anyToJavaScriptValue(value, appContext: appContext, in: appContext.runtime)
142
+ }
143
+
144
+ /**
145
+ Variant that creates JS values in the given `runtime`, used by the worklet conversion.
146
+ */
147
+ static func anyToJavaScriptValue<ValueType>(_ value: ValueType, appContext: AppContext, in runtime: JavaScriptRuntime) throws -> JavaScriptValue {
141
148
  if ValueType.self is AnyOptional.Type, Optional.isNil(value) {
142
149
  return .null
143
150
  }
144
151
  if let value = value as? AnyArgument {
145
- return try type(of: value).getDynamicType().castToJS(value, appContext: appContext)
152
+ return try type(of: value).getDynamicType().castToJS(value, appContext: appContext, in: runtime)
146
153
  }
147
- return try unknownToJavaScriptValue(value, appContext: appContext)
154
+ return try unknownToJavaScriptValue(value, appContext: appContext, in: runtime)
148
155
  }
149
156
 
150
157
  /**
@@ -153,12 +160,18 @@ public struct Conversions {
153
160
  can call this without re-entering the `AnyArgument` check and causing infinite recursion.
154
161
  */
155
162
  static func unknownToJavaScriptValue(_ value: Any, appContext: AppContext) throws -> JavaScriptValue {
163
+ return try unknownToJavaScriptValue(value, appContext: appContext, in: appContext.runtime)
164
+ }
165
+
166
+ /**
167
+ Variant that creates JS values in the given `runtime` — used by the worklet conversion
168
+ path so produced dicts/arrays live in the calling runtime, not the main runtime.
169
+ */
170
+ static func unknownToJavaScriptValue(_ value: Any, appContext: AppContext, in runtime: JavaScriptRuntime) throws -> JavaScriptValue {
156
171
  if let value = value as? JavaScriptRepresentable {
157
- return .representing(value: value, in: try appContext.runtime)
172
+ return .representing(value: value, in: runtime)
158
173
  }
159
174
 
160
- let runtime = try appContext.runtime
161
-
162
175
  switch value {
163
176
  case is Void:
164
177
  return .undefined
@@ -179,14 +192,14 @@ public struct Conversions {
179
192
  case let array as NSArray:
180
193
  let jsArray = runtime.createArray(length: array.count)
181
194
  for (index, element) in array.enumerated() {
182
- try jsArray.set(value: anyToJavaScriptValue(element, appContext: appContext), at: index)
195
+ try jsArray.set(value: anyToJavaScriptValue(element, appContext: appContext, in: runtime), at: index)
183
196
  }
184
197
  return jsArray.asValue()
185
198
  case let dict as NSDictionary:
186
199
  let jsObject = runtime.createObject()
187
200
  for (key, element) in dict {
188
201
  guard let key = key as? String else { continue }
189
- jsObject.setProperty(key, value: try anyToJavaScriptValue(element, appContext: appContext))
202
+ jsObject.setProperty(key, value: try anyToJavaScriptValue(element, appContext: appContext, in: runtime))
190
203
  }
191
204
  return jsObject.asValue()
192
205
  default:
@@ -36,6 +36,11 @@ public protocol AnyDynamicType: CustomStringConvertible, Sendable {
36
36
 
37
37
  func castToJS<ValueType>(_ value: ValueType, appContext: AppContext) throws -> JavaScriptValue
38
38
 
39
+ /**
40
+ Runtime-aware `castToJS`.
41
+ */
42
+ func castToJS<ValueType>(_ value: ValueType, appContext: AppContext, in runtime: JavaScriptRuntime) throws -> JavaScriptValue
43
+
39
44
  /**
40
45
  Converts the given native value directly to `JavaScriptValue`.
41
46
  The default implementation uses `convertResult` and then `castToJS`, but dynamic types
@@ -43,6 +48,11 @@ public protocol AnyDynamicType: CustomStringConvertible, Sendable {
43
48
  */
44
49
  func convertToJS<ValueType>(_ value: ValueType, appContext: AppContext) throws -> JavaScriptValue
45
50
 
51
+ /**
52
+ Runtime-aware `convertToJS`.
53
+ */
54
+ func convertToJS<ValueType>(_ value: ValueType, appContext: AppContext, in runtime: JavaScriptRuntime) throws -> JavaScriptValue
55
+
46
56
  /**
47
57
  Converts function's result to the type that can later be converted to a JS value.
48
58
  For instance, types such as records, enumerables and shared objects need special handling
@@ -60,11 +70,21 @@ extension AnyDynamicType {
60
70
  return try Conversions.unknownToJavaScriptValue(value, appContext: appContext)
61
71
  }
62
72
 
73
+ // Default forwards to the legacy `castToJS`, dropping `runtime`
74
+ public func castToJS<ValueType>(_ value: ValueType, appContext: AppContext, in runtime: JavaScriptRuntime) throws -> JavaScriptValue {
75
+ return try castToJS(value, appContext: appContext)
76
+ }
77
+
63
78
  public func convertToJS<ValueType>(_ value: ValueType, appContext: AppContext) throws -> JavaScriptValue {
64
79
  let result = Conversions.convertFunctionResult(value, appContext: appContext, dynamicType: self)
65
80
  return try castToJS(result, appContext: appContext)
66
81
  }
67
82
 
83
+ public func convertToJS<ValueType>(_ value: ValueType, appContext: AppContext, in runtime: JavaScriptRuntime) throws -> JavaScriptValue {
84
+ let result = Conversions.convertFunctionResult(value, appContext: appContext, dynamicType: self)
85
+ return try castToJS(result, appContext: appContext, in: runtime)
86
+ }
87
+
68
88
  func convertResult<ResultType>(_ result: ResultType, appContext: AppContext) throws -> Any {
69
89
  return result
70
90
  }
@@ -55,13 +55,16 @@ internal struct DynamicArrayType: AnyDynamicType {
55
55
  are already in their post-conversion shape, such as `JavaScriptValue.undefined`.
56
56
  */
57
57
  func castToJS<ValueType>(_ value: ValueType, appContext: AppContext) throws -> JavaScriptValue {
58
+ return try castToJS(value, appContext: appContext, in: try appContext.runtime)
59
+ }
60
+
61
+ func castToJS<ValueType>(_ value: ValueType, appContext: AppContext, in runtime: JavaScriptRuntime) throws -> JavaScriptValue {
58
62
  guard let array = value as? [Any] else {
59
- return try Conversions.anyToJavaScriptValue(value, appContext: appContext)
63
+ return try Conversions.anyToJavaScriptValue(value, appContext: appContext, in: runtime)
60
64
  }
61
- let runtime = try appContext.runtime
62
65
  let jsArray = runtime.createArray(length: array.count)
63
66
  for (index, element) in array.enumerated() {
64
- try jsArray.set(value: try elementType.castToJS(element, appContext: appContext), at: index)
67
+ try jsArray.set(value: try elementType.castToJS(element, appContext: appContext, in: runtime), at: index)
65
68
  }
66
69
  return jsArray.asValue()
67
70
  }
@@ -71,13 +74,16 @@ internal struct DynamicArrayType: AnyDynamicType {
71
74
  to use their own direct conversion paths before any array-level normalization.
72
75
  */
73
76
  func convertToJS<ValueType>(_ value: ValueType, appContext: AppContext) throws -> JavaScriptValue {
77
+ return try convertToJS(value, appContext: appContext, in: try appContext.runtime)
78
+ }
79
+
80
+ func convertToJS<ValueType>(_ value: ValueType, appContext: AppContext, in runtime: JavaScriptRuntime) throws -> JavaScriptValue {
74
81
  guard let array = value as? [Any] else {
75
- return try Conversions.anyToJavaScriptValue(value, appContext: appContext)
82
+ return try Conversions.anyToJavaScriptValue(value, appContext: appContext, in: runtime)
76
83
  }
77
- let runtime = try appContext.runtime
78
84
  let jsArray = runtime.createArray(length: array.count)
79
85
  for (index, element) in array.enumerated() {
80
- try jsArray.set(value: try elementType.convertToJS(element, appContext: appContext), at: index)
86
+ try jsArray.set(value: try elementType.convertToJS(element, appContext: appContext, in: runtime), at: index)
81
87
  }
82
88
  return jsArray.asValue()
83
89
  }
@@ -61,14 +61,17 @@ internal struct DynamicDictionaryType: AnyDynamicType {
61
61
  are already in their post-conversion shape, such as `JavaScriptValue.undefined`.
62
62
  */
63
63
  func castToJS<ValueType>(_ value: ValueType, appContext: AppContext) throws -> JavaScriptValue {
64
+ return try castToJS(value, appContext: appContext, in: try appContext.runtime)
65
+ }
66
+
67
+ func castToJS<ValueType>(_ value: ValueType, appContext: AppContext, in runtime: JavaScriptRuntime) throws -> JavaScriptValue {
64
68
  guard let dict = value as? [AnyHashable: Any] else {
65
- return try Conversions.anyToJavaScriptValue(value, appContext: appContext)
69
+ return try Conversions.anyToJavaScriptValue(value, appContext: appContext, in: runtime)
66
70
  }
67
- let runtime = try appContext.runtime
68
71
  let jsObject = runtime.createObject()
69
72
  for (key, element) in dict {
70
73
  guard let key = key as? String else { continue }
71
- jsObject.setProperty(key, value: try valueType.castToJS(element, appContext: appContext))
74
+ jsObject.setProperty(key, value: try valueType.castToJS(element, appContext: appContext, in: runtime))
72
75
  }
73
76
  return jsObject.asValue()
74
77
  }
@@ -78,14 +81,17 @@ internal struct DynamicDictionaryType: AnyDynamicType {
78
81
  to use their own direct conversion paths before any dictionary-level normalization.
79
82
  */
80
83
  func convertToJS<ValueType>(_ value: ValueType, appContext: AppContext) throws -> JavaScriptValue {
84
+ return try convertToJS(value, appContext: appContext, in: try appContext.runtime)
85
+ }
86
+
87
+ func convertToJS<ValueType>(_ value: ValueType, appContext: AppContext, in runtime: JavaScriptRuntime) throws -> JavaScriptValue {
81
88
  guard let dict = value as? [AnyHashable: Any] else {
82
- return try Conversions.anyToJavaScriptValue(value, appContext: appContext)
89
+ return try Conversions.anyToJavaScriptValue(value, appContext: appContext, in: runtime)
83
90
  }
84
- let runtime = try appContext.runtime
85
91
  let jsObject = runtime.createObject()
86
92
  for (key, element) in dict {
87
93
  guard let key = key as? String else { continue }
88
- jsObject.setProperty(key, value: try valueType.convertToJS(element, appContext: appContext))
94
+ jsObject.setProperty(key, value: try valueType.convertToJS(element, appContext: appContext, in: runtime))
89
95
  }
90
96
  return jsObject.asValue()
91
97
  }
@@ -38,6 +38,10 @@ internal struct DynamicRawType<InnerType>: AnyDynamicType {
38
38
  }
39
39
 
40
40
  func castToJS<ValueType>(_ value: ValueType, appContext: AppContext) throws -> JavaScriptValue {
41
+ return try castToJS(value, appContext: appContext, in: try appContext.runtime)
42
+ }
43
+
44
+ func castToJS<ValueType>(_ value: ValueType, appContext: AppContext, in runtime: JavaScriptRuntime) throws -> JavaScriptValue {
41
45
  if Optional.isNil(value) {
42
46
  return .null
43
47
  }
@@ -46,9 +50,9 @@ internal struct DynamicRawType<InnerType>: AnyDynamicType {
46
50
  // handlers like `DynamicEnumType.castToJS`. Guarded against infinite recursion: the
47
51
  // dispatch only kicks in when the value's runtime type differs from `innerType`.
48
52
  if InnerType.self != type(of: value as Any), let argument = value as? AnyArgument {
49
- return try type(of: argument).getDynamicType().castToJS(argument, appContext: appContext)
53
+ return try type(of: argument).getDynamicType().castToJS(argument, appContext: appContext, in: runtime)
50
54
  }
51
- return try Conversions.unknownToJavaScriptValue(value, appContext: appContext)
55
+ return try Conversions.unknownToJavaScriptValue(value, appContext: appContext, in: runtime)
52
56
  }
53
57
 
54
58
  func convertResult<ResultType>(_ result: ResultType, appContext: AppContext) throws -> Any {
@@ -28,8 +28,12 @@ internal struct DynamicStringType: AnyDynamicType {
28
28
  }
29
29
 
30
30
  func castToJS<ValueType>(_ value: ValueType, appContext: AppContext) throws -> JavaScriptValue {
31
+ return try castToJS(value, appContext: appContext, in: try appContext.runtime)
32
+ }
33
+
34
+ func castToJS<ValueType>(_ value: ValueType, appContext: AppContext, in runtime: JavaScriptRuntime) throws -> JavaScriptValue {
31
35
  if let string = value as? String {
32
- return JavaScriptValue(try appContext.runtime, string)
36
+ return JavaScriptValue(runtime, string)
33
37
  }
34
38
  throw Conversions.ConversionToJSFailedException((kind: .string, nativeType: ValueType.self))
35
39
  }
@@ -101,7 +101,7 @@ public class SyncFunctionDefinition<Args, FirstArgType, ReturnType>: AnySyncFunc
101
101
  func call(_ appContext: AppContext, in runtime: JavaScriptRuntime, this: JavaScriptValue, arguments: consuming JavaScriptValuesBuffer) throws(Exception) -> JavaScriptValue {
102
102
  let result = try runBody(appContext, in: runtime, this: this, arguments: arguments)
103
103
  do {
104
- return try appContext.converter.toJS(result, returnType)
104
+ return try appContext.converter.toJS(result, returnType, in: runtime)
105
105
  } catch let error as Exception {
106
106
  throw FunctionCallException(name).causedBy(error)
107
107
  } catch {
@@ -42,4 +42,12 @@ public struct MainValueConverter: ~Copyable {
42
42
  public func toJS(_ value: Any, _ type: AnyDynamicType) throws -> JavaScriptValue {
43
43
  return try type.convertToJS(value, appContext: appContext)
44
44
  }
45
+
46
+ /**
47
+ `toJS` variant that targets a specific runtime. Useful for Worklet runtime conversions.
48
+ */
49
+ @JavaScriptActor
50
+ public func toJS(_ value: Any, _ type: AnyDynamicType, in runtime: JavaScriptRuntime) throws -> JavaScriptValue {
51
+ return try type.convertToJS(value, appContext: appContext, in: runtime)
52
+ }
45
53
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-modules-core",
3
- "version": "56.0.3",
3
+ "version": "56.0.5",
4
4
  "description": "The core of Expo Modules architecture",
5
5
  "main": "src/index.ts",
6
6
  "types": "build/index.d.ts",
@@ -47,7 +47,7 @@
47
47
  },
48
48
  "dependencies": {
49
49
  "@expo/expo-modules-macros-plugin": "~0.0.8",
50
- "expo-modules-jsi": "~56.0.1",
50
+ "expo-modules-jsi": "~56.0.2",
51
51
  "invariant": "^2.2.4"
52
52
  },
53
53
  "peerDependencies": {
@@ -66,7 +66,7 @@
66
66
  "@types/invariant": "^2.2.33",
67
67
  "expo-module-scripts": "56.0.2"
68
68
  },
69
- "gitHead": "a8ab3da510a34b7bdb2262aa9887d4f78b102280",
69
+ "gitHead": "a30353e69ca0d72b9fac5830abc631feda1ba3ae",
70
70
  "scripts": {
71
71
  "build": "expo-module build",
72
72
  "clean": "expo-module clean",