expo-modules-core 56.0.9 → 56.0.10

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,16 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 56.0.10 — 2026-05-19
14
+
15
+ ### 🎉 New features
16
+
17
+ - [iOS] `Decodable` types can now be used as native function arguments. JS values are decoded through the dynamic-type registry, so arrays, dictionaries, optionals, `RawRepresentable` enums and `Convertible`s are coerced consistently. ([#45705](https://github.com/expo/expo/pull/45705) by [@tsapeta](https://github.com/tsapeta))
18
+
19
+ ### 🐛 Bug fixes
20
+
21
+ - [Android] Keep `ExpoComposeView` content visible during parent view transitions (e.g., `react-native-screens` pop navigation). ([#45942](https://github.com/expo/expo/pull/45942) by [@intergalacticspacehighway](https://github.com/intergalacticspacehighway))
22
+
13
23
  ## 56.0.9 — 2026-05-15
14
24
 
15
25
  ### 🎉 New features
@@ -27,7 +27,7 @@ if (shouldIncludeCompose) {
27
27
  }
28
28
 
29
29
  group = 'host.exp.exponent'
30
- version = '56.0.9'
30
+ version = '56.0.10'
31
31
 
32
32
  def isExpoModulesCoreTests = {
33
33
  Gradle gradle = getGradle()
@@ -94,7 +94,7 @@ android {
94
94
  defaultConfig {
95
95
  consumerProguardFiles 'proguard-rules.pro'
96
96
  versionCode 1
97
- versionName "56.0.9"
97
+ versionName "56.0.10"
98
98
  buildConfigField "String", "EXPO_MODULES_CORE_VERSION", "\"${versionName}\""
99
99
  buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", "true"
100
100
 
@@ -13,6 +13,7 @@ import androidx.compose.runtime.getValue
13
13
  import androidx.compose.runtime.key
14
14
  import androidx.compose.runtime.mutableStateOf
15
15
  import androidx.compose.runtime.rememberUpdatedState
16
+ import androidx.annotation.UiThread
16
17
  import androidx.compose.ui.platform.ComposeView
17
18
  import androidx.compose.ui.platform.ViewCompositionStrategy
18
19
  import androidx.core.view.size
@@ -218,8 +219,42 @@ abstract class ExpoComposeView<T : ComposeProps>(
218
219
 
219
220
  override fun onViewRemoved(child: View?) {
220
221
  super.onViewRemoved(child)
222
+ // Keep compose views alive when view is transitioning
223
+ // e.g. pop transition from RN screens https://github.com/expo/expo/issues/45914
224
+ if (child != null && isViewTransitioning(child)) {
225
+ return
226
+ }
221
227
  recomposeScope?.invalidate()
222
228
  }
229
+
230
+ // Children currently animating out via startViewTransition. While a view is in this set,
231
+ // onViewRemoved skips invalidating the recompose scope so the child's compose subtree
232
+ // stays alive for the duration of the transition. Mirrors ViewGroup.mTransitioningViews.
233
+ private val transitioningChildren: MutableSet<View> = mutableSetOf()
234
+
235
+ override fun startViewTransition(view: View) {
236
+ super.startViewTransition(view)
237
+ if (view.parent == this) {
238
+ transitioningChildren.add(view)
239
+ }
240
+ }
241
+
242
+ override fun endViewTransition(view: View) {
243
+ super.endViewTransition(view)
244
+ if (transitioningChildren.remove(view) && view.parent != this) {
245
+ recomposeScope?.invalidate()
246
+ }
247
+ }
248
+
249
+ @UiThread
250
+ private fun isViewTransitioning(view: View): Boolean {
251
+ return transitioningChildren.contains(view)
252
+ }
253
+
254
+ override fun onDetachedFromWindow() {
255
+ super.onDetachedFromWindow()
256
+ transitioningChildren.clear()
257
+ }
223
258
  }
224
259
 
225
260
  /**
@@ -208,7 +208,7 @@ private fun Project.expoPublishBody(publicationInfo: PublicationInfo, expoModule
208
208
  providers.exec { env ->
209
209
  env.workingDir(layout.projectDirectory.file(".."))
210
210
  // TODO(@lukmccall): support other package managers
211
- env.commandLine("yarn", "prettier", "--write", "expo-module.config.json")
211
+ env.commandLine("pnpm", "prettier", "--write", "expo-module.config.json")
212
212
  }.result.get()
213
213
  }
214
214
 
@@ -0,0 +1,21 @@
1
+ // Copyright 2026-present 650 Industries. All rights reserved.
2
+
3
+ /**
4
+ A coding key carrying just a string and an integer index. Used to extend
5
+ `codingPath` with array indices in unkeyed containers, since unkeyed
6
+ containers have no associated `Key` type to draw from.
7
+ */
8
+ internal struct AnyCodingKey: CodingKey {
9
+ let stringValue: String
10
+ let intValue: Int?
11
+
12
+ init(stringValue: String) {
13
+ self.stringValue = stringValue
14
+ self.intValue = Int(stringValue)
15
+ }
16
+
17
+ init(intValue: Int) {
18
+ self.stringValue = String(intValue)
19
+ self.intValue = intValue
20
+ }
21
+ }
@@ -3,29 +3,33 @@
3
3
  import ExpoModulesJSI
4
4
 
5
5
  /**
6
- Dynamic type for values conforming to `Encodable` protocol.
7
- Note that currently it can only encode from native to JavaScript values, thus cannot be used for arguments.
6
+ Dynamic type for values that conform to `Encodable` and/or `Decodable`. Native→JS
7
+ goes through `JSValueEncoder` (requires `Encodable`); JS→native goes through
8
+ `JSValueDecoder` (requires `Decodable`). Either direction throws if the wrapped
9
+ type doesn't conform to the protocol it needs.
8
10
  */
9
- internal struct DynamicEncodableType: AnyDynamicType {
10
- static let shared = DynamicEncodableType()
11
-
12
- func wraps<InnerType>(_ type: InnerType.Type) -> Bool {
13
- return type is Encodable
11
+ internal struct DynamicCodableType<InnerType>: AnyDynamicType {
12
+ func wraps<AnyInnerType>(_ type: AnyInnerType.Type) -> Bool {
13
+ return type == InnerType.self
14
14
  }
15
15
 
16
16
  func equals(_ type: any AnyDynamicType) -> Bool {
17
- // Just mocking it here as we don't really need this function and we rather want to keep it a singleton
18
- return false
17
+ return type is Self
19
18
  }
20
19
 
21
20
  func cast(jsValue: JavaScriptValue, appContext: AppContext) throws -> Any {
22
- // TODO: Create DynamicDecodableType and reuse it here – that would work perfectly with Codable types
23
- fatalError("DynamicEncodableType can only cast to JavaScript, not from")
21
+ guard let DecodableType = InnerType.self as? Decodable.Type else {
22
+ throw Conversions.CastingJSValueException<InnerType>(jsValue.kind)
23
+ }
24
+ let decoder = try JSValueDecoder(value: jsValue, appContext: appContext)
25
+ return try DecodableType.init(from: decoder)
24
26
  }
25
27
 
26
28
  func cast<ValueType>(_ value: ValueType, appContext: AppContext) throws -> Any {
27
- // TODO: Create DynamicDecodableType and reuse it here – that would work perfectly with Codable types
28
- fatalError("DynamicEncodableType can only cast to JavaScript, not from")
29
+ if let value = value as? InnerType {
30
+ return value
31
+ }
32
+ throw Conversions.CastingException<InnerType>(value)
29
33
  }
30
34
 
31
35
  func castToJS<ValueType>(_ value: ValueType, appContext: AppContext) throws -> JavaScriptValue {
@@ -50,6 +54,6 @@ internal struct DynamicEncodableType: AnyDynamicType {
50
54
  }
51
55
 
52
56
  var description: String {
53
- "Encodable"
57
+ "Codable<\(InnerType.self)>"
54
58
  }
55
59
  }
@@ -19,10 +19,10 @@ private func DynamicType<T>(_ type: T.Type) -> AnyDynamicType {
19
19
  if T.self == Void.self {
20
20
  return DynamicVoidType.shared
21
21
  }
22
- if T.self is Encodable.Type {
23
- // There is no dedicated `~` operator overload for encodables to avoid ambiguity
24
- // when the type is both `AnyArgument` and `Encodable` (e.g. strings, numeric types).
25
- return DynamicEncodableType.shared
22
+ if T.self is Encodable.Type || T.self is Decodable.Type {
23
+ // There is no dedicated `~` operator overload for Codable types to avoid ambiguity
24
+ // when the type is both `AnyArgument` and `Encodable`/`Decodable` (e.g. strings, numeric types).
25
+ return DynamicCodableType<T>()
26
26
  }
27
27
  return DynamicRawType(innerType: T.self)
28
28
  }
@@ -0,0 +1,452 @@
1
+ // Copyright 2026-present 650 Industries. All rights reserved.
2
+
3
+ import ExpoModulesJSI
4
+
5
+ /**
6
+ Decodes `JavaScriptValue`s into `Decodable` Swift values.
7
+
8
+ For any property whose static type is known to the dynamic-type registry
9
+ (anything routed via the `~` operator: arrays, dictionaries, optionals,
10
+ `RawRepresentable` enums, `Convertible`s, `JavaScriptValue` and so on),
11
+ the decoder takes the fast path through `cast(jsValue:)`, preserving full
12
+ element-type metadata and picking up `Convertible` coercions for free.
13
+
14
+ Only types the registry doesn't recognize (i.e. plain `Decodable` structs and
15
+ classes) fall back to Swift's `Decodable` machinery via `T.init(from: decoder)`.
16
+ */
17
+ internal final class JSValueDecoder: Decoder {
18
+ private let appContext: AppContext
19
+ private let runtime: JavaScriptRuntime
20
+ private let value: JavaScriptValue
21
+
22
+ /**
23
+ Initializes the decoder with the given JS value and app context.
24
+ Throws if the runtime has been lost.
25
+ */
26
+ convenience init(value: JavaScriptValue, appContext: AppContext) throws {
27
+ try self.init(value: value, appContext: appContext, runtime: appContext.runtime)
28
+ }
29
+
30
+ /**
31
+ Initializes the decoder with the given JS value, app context and an explicit runtime.
32
+ Use this when the source JS value lives in a runtime other than the one currently
33
+ held by the app context.
34
+ */
35
+ convenience init(value: JavaScriptValue, appContext: AppContext, runtime: JavaScriptRuntime) {
36
+ self.init(value: value, appContext: appContext, runtime: runtime, codingPath: [])
37
+ }
38
+
39
+ fileprivate init(
40
+ value: JavaScriptValue,
41
+ appContext: AppContext,
42
+ runtime: JavaScriptRuntime,
43
+ codingPath: [any CodingKey]
44
+ ) {
45
+ self.value = value
46
+ self.appContext = appContext
47
+ self.runtime = runtime
48
+ self.codingPath = codingPath
49
+ }
50
+
51
+ // MARK: - Decoder
52
+
53
+ let codingPath: [any CodingKey]
54
+ let userInfo: [CodingUserInfoKey: Any] = [:]
55
+
56
+ func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key: CodingKey {
57
+ guard value.isObject() else {
58
+ throw DecodingError.typeMismatch(
59
+ [String: Any].self,
60
+ DecodingError.Context(
61
+ codingPath: codingPath,
62
+ debugDescription: "Expected a JavaScript object to decode keyed container, got \(value.kind.rawValue)"
63
+ )
64
+ )
65
+ }
66
+ let container = JSObjectDecodingContainer<Key>(
67
+ object: value.getObject(),
68
+ appContext: appContext,
69
+ runtime: runtime,
70
+ codingPath: codingPath
71
+ )
72
+ return KeyedDecodingContainer(container)
73
+ }
74
+
75
+ func unkeyedContainer() throws -> any UnkeyedDecodingContainer {
76
+ guard value.isArray() else {
77
+ throw DecodingError.typeMismatch(
78
+ [Any].self,
79
+ DecodingError.Context(
80
+ codingPath: codingPath,
81
+ debugDescription: "Expected a JavaScript array to decode unkeyed container, got \(value.kind.rawValue)"
82
+ )
83
+ )
84
+ }
85
+ return JSArrayDecodingContainer(
86
+ array: value.getArray(),
87
+ appContext: appContext,
88
+ runtime: runtime,
89
+ codingPath: codingPath
90
+ )
91
+ }
92
+
93
+ func singleValueContainer() throws -> any SingleValueDecodingContainer {
94
+ return JSValueDecodingContainer(
95
+ value: value,
96
+ appContext: appContext,
97
+ runtime: runtime,
98
+ codingPath: codingPath
99
+ )
100
+ }
101
+ }
102
+
103
+ // MARK: - Shared decoding logic
104
+
105
+ /**
106
+ Decodes a single JS value into a typed Swift value, routing through the
107
+ dynamic-type registry whenever possible and falling back to `Decodable` only
108
+ for plain types.
109
+ */
110
+ private func decodeUsingDynamicType<ValueType: Decodable>(
111
+ _ type: ValueType.Type,
112
+ from jsValue: JavaScriptValue,
113
+ appContext: AppContext,
114
+ runtime: JavaScriptRuntime,
115
+ codingPath: [any CodingKey]
116
+ ) throws -> ValueType {
117
+ let dynamicType = ~ValueType.self
118
+
119
+ if !(dynamicType is DynamicCodableType<ValueType>) {
120
+ do {
121
+ let anyValue = try JavaScriptActor.assumeIsolated {
122
+ return try dynamicType.cast(jsValue: jsValue, appContext: appContext)
123
+ }
124
+ guard let typed = anyValue as? ValueType else {
125
+ throw DecodingError.typeMismatch(
126
+ ValueType.self,
127
+ DecodingError.Context(
128
+ codingPath: codingPath,
129
+ debugDescription: "Dynamic type \(dynamicType) produced \(Swift.type(of: anyValue)) which is not \(ValueType.self)"
130
+ )
131
+ )
132
+ }
133
+ return typed
134
+ } catch let error as DecodingError {
135
+ throw error
136
+ } catch {
137
+ throw DecodingError.dataCorrupted(
138
+ DecodingError.Context(
139
+ codingPath: codingPath,
140
+ debugDescription: "Failed to cast JS value to \(ValueType.self)",
141
+ underlyingError: error
142
+ )
143
+ )
144
+ }
145
+ }
146
+
147
+ // Plain Decodable type the registry doesn't recognize — recurse via Decodable.
148
+ let decoder = JSValueDecoder(value: jsValue, appContext: appContext, runtime: runtime, codingPath: codingPath)
149
+ return try ValueType(from: decoder)
150
+ }
151
+
152
+ // MARK: - Containers
153
+
154
+ /**
155
+ Single value container that reads a JS primitive (or unwraps an Optional).
156
+ */
157
+ private struct JSValueDecodingContainer: SingleValueDecodingContainer {
158
+ private let appContext: AppContext
159
+ private let runtime: JavaScriptRuntime
160
+ private let value: JavaScriptValue
161
+ let codingPath: [any CodingKey]
162
+
163
+ init(value: JavaScriptValue, appContext: AppContext, runtime: JavaScriptRuntime, codingPath: [any CodingKey]) {
164
+ self.value = value
165
+ self.appContext = appContext
166
+ self.runtime = runtime
167
+ self.codingPath = codingPath
168
+ }
169
+
170
+ func decodeNil() -> Bool {
171
+ return value.isNull() || value.isUndefined()
172
+ }
173
+
174
+ func decode<ValueType: Decodable>(_ type: ValueType.Type) throws -> ValueType {
175
+ return try decodeUsingDynamicType(
176
+ type,
177
+ from: value,
178
+ appContext: appContext,
179
+ runtime: runtime,
180
+ codingPath: codingPath
181
+ )
182
+ }
183
+ }
184
+
185
+ /**
186
+ Keyed container that reads from a JS object.
187
+
188
+ - Note: This is a `class` (not a `struct`) for the same reason as the encoder's
189
+ keyed container: it holds a non-copyable `JavaScriptObject`. See
190
+ `JSObjectEncodingContainer` for the full rationale.
191
+ */
192
+ private final class JSObjectDecodingContainer<Key: CodingKey>: KeyedDecodingContainerProtocol {
193
+ private let appContext: AppContext
194
+ private let runtime: JavaScriptRuntime
195
+ private let object: JavaScriptObject
196
+ let codingPath: [any CodingKey]
197
+
198
+ init(object: consuming JavaScriptObject, appContext: AppContext, runtime: JavaScriptRuntime, codingPath: [any CodingKey]) {
199
+ self.object = object
200
+ self.appContext = appContext
201
+ self.runtime = runtime
202
+ self.codingPath = codingPath
203
+ }
204
+
205
+ lazy var allKeys: [Key] = object.getPropertyNames().compactMap { Key(stringValue: $0) }
206
+
207
+ func contains(_ key: Key) -> Bool {
208
+ return object.hasProperty(key.stringValue)
209
+ }
210
+
211
+ func decodeNil(forKey key: Key) throws -> Bool {
212
+ let value = object.getProperty(key.stringValue)
213
+ return value.isNull() || value.isUndefined()
214
+ }
215
+
216
+ // JS doesn't meaningfully distinguish "key absent" from "value undefined" at the
217
+ // consumer level — a single `getProperty` fetch returns `undefined` in both cases.
218
+ // Collapse both into `keyNotFound` so the caller gets the more useful error.
219
+ func decode<ValueType: Decodable>(_ type: ValueType.Type, forKey key: Key) throws -> ValueType {
220
+ let jsValue = object.getProperty(key.stringValue)
221
+ if jsValue.isUndefined() {
222
+ throw DecodingError.keyNotFound(
223
+ key,
224
+ DecodingError.Context(
225
+ codingPath: codingPath,
226
+ debugDescription: "No value found for key \(key.stringValue)"
227
+ )
228
+ )
229
+ }
230
+ return try decodeUsingDynamicType(
231
+ type,
232
+ from: jsValue,
233
+ appContext: appContext,
234
+ runtime: runtime,
235
+ codingPath: codingPath + [key]
236
+ )
237
+ }
238
+
239
+ // Swift's default `decodeIfPresent` calls `contains(key)` and then `decodeNil(forKey:)`;
240
+ // for present-but-null JS keys this works, but it doesn't reach our dynamic-type
241
+ // fast path. Override to make optional decoding consistently route through it.
242
+ // `JavaScriptObject.getProperty` returns `undefined` for missing keys, so a single
243
+ // fetch covers both "absent" and "explicit null/undefined" cases.
244
+ func decodeIfPresent<ValueType: Decodable>(_ type: ValueType.Type, forKey key: Key) throws -> ValueType? {
245
+ let jsValue = object.getProperty(key.stringValue)
246
+ if jsValue.isNull() || jsValue.isUndefined() {
247
+ return nil
248
+ }
249
+ return try decodeUsingDynamicType(
250
+ type,
251
+ from: jsValue,
252
+ appContext: appContext,
253
+ runtime: runtime,
254
+ codingPath: codingPath + [key]
255
+ )
256
+ }
257
+
258
+ func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer<NestedKey> where NestedKey: CodingKey {
259
+ let jsValue = object.getProperty(key.stringValue)
260
+ guard jsValue.isObject() else {
261
+ throw DecodingError.typeMismatch(
262
+ [String: Any].self,
263
+ DecodingError.Context(
264
+ codingPath: codingPath + [key],
265
+ debugDescription: "Expected a JavaScript object for nested keyed container, got \(jsValue.kind.rawValue)"
266
+ )
267
+ )
268
+ }
269
+ let container = JSObjectDecodingContainer<NestedKey>(
270
+ object: jsValue.getObject(),
271
+ appContext: appContext,
272
+ runtime: runtime,
273
+ codingPath: codingPath + [key]
274
+ )
275
+ return KeyedDecodingContainer(container)
276
+ }
277
+
278
+ func nestedUnkeyedContainer(forKey key: Key) throws -> any UnkeyedDecodingContainer {
279
+ let jsValue = object.getProperty(key.stringValue)
280
+ guard jsValue.isArray() else {
281
+ throw DecodingError.typeMismatch(
282
+ [Any].self,
283
+ DecodingError.Context(
284
+ codingPath: codingPath + [key],
285
+ debugDescription: "Expected a JavaScript array for nested unkeyed container, got \(jsValue.kind.rawValue)"
286
+ )
287
+ )
288
+ }
289
+ return JSArrayDecodingContainer(
290
+ array: jsValue.getArray(),
291
+ appContext: appContext,
292
+ runtime: runtime,
293
+ codingPath: codingPath + [key]
294
+ )
295
+ }
296
+
297
+ func superDecoder() throws -> any Decoder {
298
+ throw DecodingError.dataCorrupted(
299
+ DecodingError.Context(
300
+ codingPath: codingPath,
301
+ debugDescription: "JSValueDecoder does not support superDecoder()"
302
+ )
303
+ )
304
+ }
305
+
306
+ func superDecoder(forKey key: Key) throws -> any Decoder {
307
+ throw DecodingError.dataCorrupted(
308
+ DecodingError.Context(
309
+ codingPath: codingPath + [key],
310
+ debugDescription: "JSValueDecoder does not support superDecoder(forKey:)"
311
+ )
312
+ )
313
+ }
314
+ }
315
+
316
+ /**
317
+ Unkeyed container that reads from a JS array.
318
+
319
+ Like `JSObjectDecodingContainer`, this is a `class` so it can hold the
320
+ non-copyable `JavaScriptArray` directly.
321
+ */
322
+ private final class JSArrayDecodingContainer: UnkeyedDecodingContainer {
323
+ private let appContext: AppContext
324
+ private let runtime: JavaScriptRuntime
325
+ private let array: JavaScriptArray
326
+ let codingPath: [any CodingKey]
327
+ var currentIndex: Int = 0
328
+
329
+ init(array: consuming JavaScriptArray, appContext: AppContext, runtime: JavaScriptRuntime, codingPath: [any CodingKey]) {
330
+ self.array = array
331
+ self.appContext = appContext
332
+ self.runtime = runtime
333
+ self.codingPath = codingPath
334
+ }
335
+
336
+ var count: Int? { array.length }
337
+ var isAtEnd: Bool { currentIndex >= array.length }
338
+
339
+ func decodeNil() throws -> Bool {
340
+ let jsValue = try nextValue(Any?.self)
341
+ if jsValue.isNull() || jsValue.isUndefined() {
342
+ currentIndex += 1
343
+ return true
344
+ }
345
+ return false
346
+ }
347
+
348
+ func decode<ValueType: Decodable>(_ type: ValueType.Type) throws -> ValueType {
349
+ let jsValue = try nextValue(type)
350
+ let key = AnyCodingKey(intValue: currentIndex)
351
+ let decoded = try decodeUsingDynamicType(
352
+ type,
353
+ from: jsValue,
354
+ appContext: appContext,
355
+ runtime: runtime,
356
+ codingPath: codingPath + [key]
357
+ )
358
+ currentIndex += 1
359
+ return decoded
360
+ }
361
+
362
+ func decodeIfPresent<ValueType: Decodable>(_ type: ValueType.Type) throws -> ValueType? {
363
+ if isAtEnd {
364
+ return nil
365
+ }
366
+ let jsValue = try array.getValue(at: currentIndex)
367
+ if jsValue.isNull() || jsValue.isUndefined() {
368
+ currentIndex += 1
369
+ return nil
370
+ }
371
+ let key = AnyCodingKey(intValue: currentIndex)
372
+ let decoded = try decodeUsingDynamicType(
373
+ type,
374
+ from: jsValue,
375
+ appContext: appContext,
376
+ runtime: runtime,
377
+ codingPath: codingPath + [key]
378
+ )
379
+ currentIndex += 1
380
+ return decoded
381
+ }
382
+
383
+ func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type) throws -> KeyedDecodingContainer<NestedKey> where NestedKey: CodingKey {
384
+ let jsValue = try nextValue(KeyedDecodingContainer<NestedKey>.self)
385
+ let key = AnyCodingKey(intValue: currentIndex)
386
+ guard jsValue.isObject() else {
387
+ throw DecodingError.typeMismatch(
388
+ [String: Any].self,
389
+ DecodingError.Context(
390
+ codingPath: codingPath + [key],
391
+ debugDescription: "Expected a JavaScript object for nested keyed container, got \(jsValue.kind.rawValue)"
392
+ )
393
+ )
394
+ }
395
+ let container = JSObjectDecodingContainer<NestedKey>(
396
+ object: jsValue.getObject(),
397
+ appContext: appContext,
398
+ runtime: runtime,
399
+ codingPath: codingPath + [key]
400
+ )
401
+ currentIndex += 1
402
+ return KeyedDecodingContainer(container)
403
+ }
404
+
405
+ func nestedUnkeyedContainer() throws -> any UnkeyedDecodingContainer {
406
+ let jsValue = try nextValue((any UnkeyedDecodingContainer).self)
407
+ let key = AnyCodingKey(intValue: currentIndex)
408
+ guard jsValue.isArray() else {
409
+ throw DecodingError.typeMismatch(
410
+ [Any].self,
411
+ DecodingError.Context(
412
+ codingPath: codingPath + [key],
413
+ debugDescription: "Expected a JavaScript array for nested unkeyed container, got \(jsValue.kind.rawValue)"
414
+ )
415
+ )
416
+ }
417
+ let container = JSArrayDecodingContainer(
418
+ array: jsValue.getArray(),
419
+ appContext: appContext,
420
+ runtime: runtime,
421
+ codingPath: codingPath + [key]
422
+ )
423
+ currentIndex += 1
424
+ return container
425
+ }
426
+
427
+ // Reads the element at `currentIndex`, throwing `valueNotFound` (rather than letting
428
+ // `JavaScriptArray.getValue(at:)` throw its non-`DecodingError` out-of-range error)
429
+ // when the unkeyed container has been exhausted.
430
+ private func nextValue<RequestedType>(_ expected: RequestedType.Type) throws -> JavaScriptValue {
431
+ if isAtEnd {
432
+ throw DecodingError.valueNotFound(
433
+ expected,
434
+ DecodingError.Context(
435
+ codingPath: codingPath + [AnyCodingKey(intValue: currentIndex)],
436
+ debugDescription: "Unkeyed container is at end — no value available at index \(currentIndex)"
437
+ )
438
+ )
439
+ }
440
+ return try array.getValue(at: currentIndex)
441
+ }
442
+
443
+ func superDecoder() throws -> any Decoder {
444
+ throw DecodingError.dataCorrupted(
445
+ DecodingError.Context(
446
+ codingPath: codingPath,
447
+ debugDescription: "JSValueDecoder does not support superDecoder()"
448
+ )
449
+ )
450
+ }
451
+ }
452
+
@@ -107,7 +107,7 @@ private func encodeUsingDynamicType<ValueType: Encodable>(
107
107
  ) throws -> JavaScriptValue {
108
108
  let dynamicType = ~ValueType.self
109
109
 
110
- if !(dynamicType is DynamicEncodableType) {
110
+ if !(dynamicType is DynamicCodableType<ValueType>) {
111
111
  return try dynamicType.castToJS(value, appContext: appContext, in: runtime)
112
112
  }
113
113
 
@@ -345,24 +345,3 @@ private final class JSArrayEncodingContainer: UnkeyedEncodingContainer {
345
345
  }
346
346
  }
347
347
 
348
- // MARK: - Helpers
349
-
350
- /**
351
- A coding key carrying just a string and an integer index. Used to extend
352
- `codingPath` with array indices in the unkeyed container, since unkeyed
353
- containers have no associated `Key` type to draw from.
354
- */
355
- private struct AnyCodingKey: CodingKey {
356
- let stringValue: String
357
- let intValue: Int?
358
-
359
- init(stringValue: String) {
360
- self.stringValue = stringValue
361
- self.intValue = Int(stringValue)
362
- }
363
-
364
- init(intValue: Int) {
365
- self.stringValue = String(intValue)
366
- self.intValue = intValue
367
- }
368
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-modules-core",
3
- "version": "56.0.9",
3
+ "version": "56.0.10",
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.9",
50
- "expo-modules-jsi": "~56.0.5",
50
+ "expo-modules-jsi": "~56.0.6",
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": "f26be3dd9396bf7c399a1d607865d0fabdbc0d64",
69
+ "gitHead": "290368bc41026449a05a4ebf991b85c3a2fb0e3a",
70
70
  "scripts": {
71
71
  "build": "expo-module build",
72
72
  "clean": "expo-module clean",