expo-modules-jsi 58.0.5 → 58.0.7

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 (23) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/apple/Benchmarks/FreeFormBenchmarks.swift +61 -0
  3. package/apple/Benchmarks/HostObjectBenchmarks.swift +57 -0
  4. package/apple/Sources/ExpoModulesJSI/Coding/JavaScriptCodable+Any.swift +500 -0
  5. package/apple/Sources/ExpoModulesJSI/Coding/JavaScriptCodable+Primitives.swift +4 -6
  6. package/apple/Sources/ExpoModulesJSI/Protocols/JSIRepresentable.swift +2 -2
  7. package/apple/Sources/ExpoModulesJSI/Protocols/JavaScriptRepresentable.swift +2 -3
  8. package/apple/Sources/ExpoModulesJSI/Runtime/JavaScriptRuntime.swift +24 -17
  9. package/apple/Sources/ExpoModulesJSI/Runtime/JavaScriptRuntimeHandle.swift +67 -0
  10. package/apple/Sources/ExpoModulesJSI/Runtime/JavaScriptValuesBuffer.swift +3 -2
  11. package/apple/Sources/ExpoModulesJSI/Runtime/Values/JavaScriptArray.swift +53 -35
  12. package/apple/Sources/ExpoModulesJSI/Runtime/Values/JavaScriptFunction.swift +2 -2
  13. package/apple/Sources/ExpoModulesJSI/Runtime/Values/JavaScriptObject.swift +86 -58
  14. package/apple/Sources/ExpoModulesJSI/Runtime/Values/JavaScriptValue.swift +73 -47
  15. package/apple/Sources/ExpoModulesJSI/Utilities/String+JSI.swift +10 -0
  16. package/apple/Sources/ExpoModulesJSI-Cxx/include/HostObject.h +2 -2
  17. package/apple/Sources/ExpoModulesJSI-Cxx/include/HostObjectCallbacks.h +17 -8
  18. package/apple/Sources/ExpoModulesJSI-Cxx/include/JSIUtils.h +20 -0
  19. package/apple/Tests/JavaScriptCodableAnyTests.swift +531 -0
  20. package/apple/Tests/JavaScriptRuntimeTests.swift +40 -0
  21. package/apple/Tests/JavaScriptValueTests.swift +18 -0
  22. package/apple/Tests/JavaScriptValuesBufferTests.swift +14 -0
  23. package/package.json +1 -1
@@ -6,32 +6,53 @@ internal import jsi
6
6
  /// A Swift representation of a JavaScript object. Provides access to JavaScript object properties and methods,
7
7
  /// supporting property access, modification, enumeration, prototype manipulation, and function calling.
8
8
  public struct JavaScriptObject: JavaScriptType, Sendable, ~Copyable {
9
- internal weak let runtime: JavaScriptRuntime?
9
+ /// Handle to the runtime the object belongs to.
10
+ internal let runtimeHandle: JavaScriptRuntimeHandle
10
11
  internal var pointee: facebook.jsi.Object
11
12
 
13
+ /// The runtime the object belongs to, or `nil` if it has been deallocated. Prefer ``jsiRuntime`` on
14
+ /// hot paths: it costs no reference counting.
15
+ internal var runtime: JavaScriptRuntime? {
16
+ return runtimeHandle.runtime
17
+ }
18
+
19
+ /// The engine runtime the object belongs to, or `nil` if the runtime has been deallocated.
20
+ internal var jsiRuntime: facebook.jsi.IRuntime? {
21
+ return runtimeHandle.pointee
22
+ }
23
+
12
24
  /// Creates a new object in the given runtime.
13
- public init(_ runtime: JavaScriptRuntime) {
25
+ public init(_ runtime: borrowing JavaScriptRuntime) {
14
26
  self.init(runtime, facebook.jsi.Object(runtime.pointee))
15
27
  }
16
28
 
17
29
  /// Creates a new object from the dictionary whose values are representable in JS.
18
- public init<DictValue: JavaScriptRepresentable>(_ runtime: JavaScriptRuntime, _ dictionary: [String: DictValue]) {
19
- self.runtime = runtime
30
+ public init<DictValue: JavaScriptRepresentable>(
31
+ _ runtime: borrowing JavaScriptRuntime,
32
+ _ dictionary: [String: DictValue]
33
+ ) {
34
+ self.runtimeHandle = runtime.handle
20
35
  self.pointee = dictionary.toJavaScriptValue(in: runtime).getObject().pointee
21
36
  }
22
37
 
23
38
  /// Creates a new object from existing JSI object.
24
- internal init(_ runtime: JavaScriptRuntime, _ object: consuming facebook.jsi.Object) {
25
- self.runtime = runtime
39
+ internal init(_ runtime: borrowing JavaScriptRuntime, _ object: consuming facebook.jsi.Object) {
40
+ self.runtimeHandle = runtime.handle
41
+ self.pointee = object
42
+ }
43
+
44
+ /// Creates a new object from existing JSI object, which belongs to the runtime behind `runtimeHandle`.
45
+ internal init(_ runtimeHandle: JavaScriptRuntimeHandle, _ object: consuming facebook.jsi.Object) {
46
+ self.runtimeHandle = runtimeHandle
26
47
  self.pointee = object
27
48
  }
28
49
 
29
50
  /// Result of `object instanceof constructor`, which tests if the prototype property of a constructor appears anywhere in the prototype chain of an object.
30
51
  public func instanceOf(_ constructor: borrowing JavaScriptFunction) -> Bool {
31
- guard let runtime else {
52
+ guard let jsiRuntime else {
32
53
  FatalError.runtimeLost()
33
54
  }
34
- return pointee.instanceOf(runtime.pointee, constructor.pointee)
55
+ return pointee.instanceOf(jsiRuntime, constructor.pointee)
35
56
  }
36
57
 
37
58
  /// Result of `object instanceof constructor`, which tests if the prototype property of a constructor appears anywhere in the prototype chain of an object.
@@ -41,34 +62,42 @@ public struct JavaScriptObject: JavaScriptType, Sendable, ~Copyable {
41
62
 
42
63
  /// Equivalent to `Array.isArray()` in JS. If it returns `true`, then `getArray()` will succeed.
43
64
  public func isArray() -> Bool {
44
- guard let runtime else {
65
+ guard let jsiRuntime else {
45
66
  FatalError.runtimeLost()
46
67
  }
47
- return pointee.isArray(runtime.pointee)
68
+ return pointee.isArray(jsiRuntime)
48
69
  }
49
70
 
50
71
  /// Returns `true` if the object is callable. If so, then `getFunction()` will succeed.
51
72
  public func isFunction() -> Bool {
52
- guard let runtime else {
73
+ guard let jsiRuntime else {
53
74
  FatalError.runtimeLost()
54
75
  }
55
- return pointee.isFunction(runtime.pointee)
76
+ return pointee.isFunction(jsiRuntime)
56
77
  }
57
78
 
58
79
  /// Returns `true` if the object is backed by a `jsi::HostObject`, including host objects
59
80
  /// created via `JavaScriptRuntime.createHostObject` and ones produced by other native code.
60
81
  public func isHostObject() -> Bool {
61
- guard let runtime else {
82
+ guard let jsiRuntime else {
62
83
  FatalError.runtimeLost()
63
84
  }
64
- return expo.isHostObject(runtime.pointee, pointee)
85
+ return expo.isHostObject(jsiRuntime, pointee)
65
86
  }
66
87
 
67
88
  public func isArrayBuffer() -> Bool {
89
+ guard let jsiRuntime else {
90
+ FatalError.runtimeLost()
91
+ }
92
+ return pointee.isArrayBuffer(jsiRuntime)
93
+ }
94
+
95
+ /// Returns `true` if the object is a typed array, such as `Uint8Array`.
96
+ internal func isTypedArray() -> Bool {
68
97
  guard let runtime else {
69
98
  FatalError.runtimeLost()
70
99
  }
71
- return pointee.isArrayBuffer(runtime.pointee)
100
+ return expo.isTypedArray(runtime.pointee, pointee)
72
101
  }
73
102
 
74
103
  /// Returns the object as an array buffer, or asserts if not an array buffer.
@@ -82,11 +111,11 @@ public struct JavaScriptObject: JavaScriptType, Sendable, ~Copyable {
82
111
 
83
112
  /// Returns the object as an array, or asserts if not an array.
84
113
  public func getArray() -> JavaScriptArray {
85
- guard let runtime else {
114
+ guard let jsiRuntime else {
86
115
  FatalError.runtimeLost()
87
116
  }
88
117
  assert(isArray(), "Object is not an array")
89
- return JavaScriptArray(runtime, pointee.getArray(runtime.pointee))
118
+ return JavaScriptArray(runtimeHandle, pointee.getArray(jsiRuntime))
90
119
  }
91
120
 
92
121
  /// Returns the object as a function, or asserts if not a function.
@@ -102,28 +131,28 @@ public struct JavaScriptObject: JavaScriptType, Sendable, ~Copyable {
102
131
 
103
132
  /// Checks whether the object has a property with the given name.
104
133
  public func hasProperty(_ name: String) -> Bool {
105
- guard let runtime else {
134
+ guard let jsiRuntime else {
106
135
  FatalError.runtimeLost()
107
136
  }
108
- return pointee.hasProperty(runtime.pointee, name.toJSIPropNameID(in: runtime.pointee))
137
+ return pointee.hasProperty(jsiRuntime, name.toJSIPropNameID(in: jsiRuntime))
109
138
  }
110
139
 
111
140
  /// Returns the property of the object with the given name,
112
141
  /// or `undefined` value if the name is not a property of the object.
113
142
  public func getProperty(_ name: String) -> JavaScriptValue {
114
- guard let runtime else {
143
+ guard let jsiRuntime else {
115
144
  FatalError.runtimeLost()
116
145
  }
117
- return JavaScriptValue(runtime, pointee.getProperty(runtime.pointee, name.toJSIPropNameID(in: runtime.pointee)))
146
+ return JavaScriptValue(runtimeHandle, pointee.getProperty(jsiRuntime, name.toJSIPropNameID(in: jsiRuntime)))
118
147
  }
119
148
 
120
149
  /// Returns the property of the object with the given prop name id,
121
150
  /// or `undefined` value if the name is not a property of the object.
122
151
  public func getProperty(_ propName: JavaScriptPropNameID) -> JavaScriptValue {
123
- guard let runtime else {
152
+ guard let jsiRuntime else {
124
153
  FatalError.runtimeLost()
125
154
  }
126
- return JavaScriptValue(runtime, pointee.getProperty(runtime.pointee, propName.pointee))
155
+ return JavaScriptValue(runtimeHandle, pointee.getProperty(jsiRuntime, propName.pointee))
127
156
  }
128
157
 
129
158
  /// Accesses nested properties in a single subscript operation by traversing the object chain.
@@ -138,16 +167,15 @@ public struct JavaScriptObject: JavaScriptType, Sendable, ~Copyable {
138
167
  /// - Note: Each intermediate value in the chain (except the last) must be an object.
139
168
  /// If any intermediate value is not an object, the behavior is undefined and may crash.
140
169
  public subscript(_ key: String, _ nestedKeys: String...) -> JavaScriptValue {
141
- guard let runtime else {
170
+ guard let jsiRuntime else {
142
171
  FatalError.runtimeLost()
143
172
  }
144
- let jsiRuntime = runtime.pointee
145
173
  var value = pointee.getProperty(jsiRuntime, key)
146
174
 
147
175
  for key in nestedKeys {
148
176
  value = value.getObject(jsiRuntime).getProperty(jsiRuntime, key)
149
177
  }
150
- return JavaScriptValue(runtime, value)
178
+ return JavaScriptValue(runtimeHandle, value)
151
179
  }
152
180
 
153
181
  /// Returns an array of the object's own enumerable property names.
@@ -156,7 +184,7 @@ public struct JavaScriptObject: JavaScriptType, Sendable, ~Copyable {
156
184
  ///
157
185
  /// - Returns: An array of property names as strings
158
186
  public func getPropertyNames() -> [String] {
159
- guard let jsiRuntime = runtime?.pointee else {
187
+ guard let jsiRuntime else {
160
188
  FatalError.runtimeLost()
161
189
  }
162
190
  let propertyNames: facebook.jsi.Array = pointee.getPropertyNames(jsiRuntime)
@@ -209,32 +237,32 @@ public struct JavaScriptObject: JavaScriptType, Sendable, ~Copyable {
209
237
 
210
238
  /// Returns a prototype of the object. Same as `Object.getPrototypeOf(object)` in JS.
211
239
  public func getPrototype() -> JavaScriptValue {
212
- guard let runtime else {
240
+ guard let jsiRuntime else {
213
241
  FatalError.runtimeLost()
214
242
  }
215
- return JavaScriptValue(runtime, pointee.getPrototype(runtime.pointee))
243
+ return JavaScriptValue(runtimeHandle, pointee.getPrototype(jsiRuntime))
216
244
  }
217
245
 
218
246
  /// Sets a prototype of the object. Same as `Object.setPrototypeOf(object, prototype)` in JS.
219
247
  public func setPrototype(_ prototype: JavaScriptValue) {
220
- guard let runtime else {
248
+ guard let jsiRuntime else {
221
249
  FatalError.runtimeLost()
222
250
  }
223
- pointee.setPrototype(runtime.pointee, prototype.pointee)
251
+ pointee.setPrototype(jsiRuntime, prototype.pointee)
224
252
  }
225
253
 
226
254
  // MARK: - Modifying object properties
227
255
 
228
256
  public func setProperty(_ name: String, value: JavaScriptValue) {
229
- guard let runtime else {
257
+ guard let jsiRuntime else {
230
258
  FatalError.runtimeLost()
231
259
  }
232
260
  // This specialization is to avoid copying the value; `asValue()` on `JavaScriptValue` needs to do a copy.
233
261
  expo.setProperty(
234
- runtime.pointee,
262
+ jsiRuntime,
235
263
  pointee,
236
- name.toJSIPropNameID(in: runtime.pointee),
237
- value.toJSIValue(in: runtime.pointee)
264
+ name.toJSIPropNameID(in: jsiRuntime),
265
+ value.toJSIValue(in: jsiRuntime)
238
266
  )
239
267
  }
240
268
 
@@ -247,22 +275,22 @@ public struct JavaScriptObject: JavaScriptType, Sendable, ~Copyable {
247
275
  }
248
276
 
249
277
  internal func setProperty<T: JavaScriptRepresentable>(_ name: String, value: consuming T) where T: JSIRepresentable {
250
- guard let runtime else {
278
+ guard let jsiRuntime else {
251
279
  FatalError.runtimeLost()
252
280
  }
253
- let jsiValue = value.toJSIValue(in: runtime.pointee)
254
- expo.setProperty(runtime.pointee, pointee, name.toJSIPropNameID(in: runtime.pointee), jsiValue)
281
+ let jsiValue = value.toJSIValue(in: jsiRuntime)
282
+ expo.setProperty(jsiRuntime, pointee, name.toJSIPropNameID(in: jsiRuntime), jsiValue)
255
283
  }
256
284
 
257
285
  public func setProperty(_ name: String, _ object: consuming JavaScriptObject) {
258
- guard let runtime else {
286
+ guard let jsiRuntime else {
259
287
  FatalError.runtimeLost()
260
288
  }
261
289
  expo.setProperty(
262
- runtime.pointee,
290
+ jsiRuntime,
263
291
  pointee,
264
- name.toJSIPropNameID(in: runtime.pointee),
265
- facebook.jsi.Value(runtime.pointee, object.pointee)
292
+ name.toJSIPropNameID(in: jsiRuntime),
293
+ facebook.jsi.Value(jsiRuntime, object.pointee)
266
294
  )
267
295
  }
268
296
 
@@ -314,10 +342,10 @@ public struct JavaScriptObject: JavaScriptType, Sendable, ~Copyable {
314
342
  #if !os(macOS)
315
343
  // TODO: remove when bumping to react-native-macos 0.86
316
344
  public func deleteProperty(_ name: String) {
317
- guard let runtime else {
345
+ guard let jsiRuntime else {
318
346
  FatalError.runtimeLost()
319
347
  }
320
- pointee.deleteProperty(runtime.pointee, name.toJSIPropNameID(in: runtime.pointee))
348
+ pointee.deleteProperty(jsiRuntime, name.toJSIPropNameID(in: jsiRuntime))
321
349
  }
322
350
  #endif
323
351
 
@@ -381,18 +409,18 @@ public struct JavaScriptObject: JavaScriptType, Sendable, ~Copyable {
381
409
  // MARK: - Conversions
382
410
 
383
411
  public func asValue() -> JavaScriptValue {
384
- guard let runtime else {
412
+ guard let jsiRuntime else {
385
413
  FatalError.runtimeLost()
386
414
  }
387
- return JavaScriptValue(runtime, facebook.jsi.Value(runtime.pointee, pointee))
415
+ return JavaScriptValue(runtimeHandle, facebook.jsi.Value(jsiRuntime, pointee))
388
416
  }
389
417
 
390
418
  /// Returns the object as a `facebook.jsi.Value` instance.
391
419
  internal func asJSIValue() -> facebook.jsi.Value {
392
- guard let runtime else {
420
+ guard let jsiRuntime else {
393
421
  FatalError.runtimeLost()
394
422
  }
395
- return facebook.jsi.Value(runtime.pointee, pointee)
423
+ return facebook.jsi.Value(jsiRuntime, pointee)
396
424
  }
397
425
 
398
426
  /// Provides scoped access to a raw pointer to the underlying `facebook.jsi.Object`.
@@ -428,10 +456,10 @@ public struct JavaScriptObject: JavaScriptType, Sendable, ~Copyable {
428
456
  /// wrapper: C++-produced states carry a null context and are unrecoverable from
429
457
  /// the Swift side by design.
430
458
  public func hasNativeState() -> Bool {
431
- guard let runtime else {
459
+ guard let jsiRuntime else {
432
460
  FatalError.runtimeLost()
433
461
  }
434
- return expo.hasNativeState(runtime.pointee, pointee)
462
+ return expo.hasNativeState(jsiRuntime, pointee)
435
463
  }
436
464
 
437
465
  /// Returns the Swift `JavaScriptNativeState` wrapper attached to this object via
@@ -439,10 +467,10 @@ public struct JavaScriptObject: JavaScriptType, Sendable, ~Copyable {
439
467
  /// produced C++-side without a Swift back-pointer, or its concrete type doesn't
440
468
  /// match `T`.
441
469
  public func getNativeState<T: JavaScriptNativeState>(as: T.Type = JavaScriptNativeState.self) -> T? {
442
- guard let runtime else {
470
+ guard let jsiRuntime else {
443
471
  FatalError.runtimeLost()
444
472
  }
445
- guard let cxxNativeState = expo.getExpoNativeState(runtime.pointee, pointee) else {
473
+ guard let cxxNativeState = expo.getExpoNativeState(jsiRuntime, pointee) else {
446
474
  return nil
447
475
  }
448
476
  return T.from(cxx: cxxNativeState)
@@ -452,27 +480,27 @@ public struct JavaScriptObject: JavaScriptType, Sendable, ~Copyable {
452
480
  /// Creates a new shared_ptr to the object managed by state, which will live until the value at this property becomes unreachable.
453
481
  /// - TODO: throw a type error if this object is a proxy or host object.
454
482
  public func setNativeState<T: JavaScriptNativeState>(_ nativeState: T) {
455
- guard let runtime else {
483
+ guard let jsiRuntime else {
456
484
  FatalError.runtimeLost()
457
485
  }
458
- expo.setNativeState(runtime.pointee, self.pointee, nativeState.acquireShared())
486
+ expo.setNativeState(jsiRuntime, self.pointee, nativeState.acquireShared())
459
487
  }
460
488
 
461
489
  /// Unsets the native state of this object.
462
490
  public func unsetNativeState() {
463
- guard let runtime else {
491
+ guard let jsiRuntime else {
464
492
  FatalError.runtimeLost()
465
493
  }
466
- expo.unsetNativeState(runtime.pointee, pointee)
494
+ expo.unsetNativeState(jsiRuntime, pointee)
467
495
  }
468
496
 
469
497
  // MARK: - Memory pressure
470
498
 
471
499
  public func setExternalMemoryPressure(_ size: Int) {
472
- guard let runtime else {
500
+ guard let jsiRuntime else {
473
501
  FatalError.runtimeLost()
474
502
  }
475
- pointee.setExternalMemoryPressure(runtime.pointee, size)
503
+ pointee.setExternalMemoryPressure(jsiRuntime, size)
476
504
  }
477
505
 
478
506
  // MARK: - Equality
@@ -7,21 +7,38 @@ internal import jsi
7
7
  /// this one is a reference type so can be safely captured in closures, passed to other isolation context,
8
8
  /// and stored in containers that don't support non-copyable types etc.
9
9
  public final class JavaScriptValue: JavaScriptType, Equatable, Escapable {
10
- internal weak let runtime: JavaScriptRuntime?
10
+ /// Handle to the runtime the value belongs to. `nil` only for runtime-free values (undefined, null,
11
+ /// booleans and numbers).
12
+ internal let runtimeHandle: JavaScriptRuntimeHandle?
11
13
  internal let pointee: facebook.jsi.Value
12
14
 
13
- /// Takes ownership of the given JSI value. Only runtime-free values (undefined, null, booleans and
14
- /// numbers) pass `nil` here.
15
- internal init(_ runtime: JavaScriptRuntime?, _ pointee: consuming facebook.jsi.Value) {
16
- self.runtime = runtime
15
+ /// The runtime the value belongs to, or `nil` if it has been deallocated or the value is runtime-free.
16
+ /// Prefer ``jsiRuntime`` on hot paths: it costs no reference counting.
17
+ internal var runtime: JavaScriptRuntime? {
18
+ return runtimeHandle?.runtime
19
+ }
20
+
21
+ /// The engine runtime the value belongs to, or `nil` if the runtime has been deallocated or the value
22
+ /// is runtime-free.
23
+ internal var jsiRuntime: facebook.jsi.IRuntime? {
24
+ return runtimeHandle?.pointee
25
+ }
26
+
27
+ /// Takes ownership of the given JSI value, which belongs to the runtime behind `runtimeHandle`.
28
+ internal init(_ runtimeHandle: JavaScriptRuntimeHandle, _ pointee: consuming facebook.jsi.Value) {
29
+ self.runtimeHandle = runtimeHandle
30
+ self.pointee = pointee
31
+ }
32
+
33
+ /// Takes ownership of the given runtime-free JSI value (undefined, null, a boolean or a number).
34
+ internal init(_ pointee: consuming facebook.jsi.Value) {
35
+ self.runtimeHandle = nil
17
36
  self.pointee = pointee
18
37
  }
19
38
 
20
- /// Takes ownership of the given JSI value. Same as the optional-runtime overload, but the exact
21
- /// parameter type keeps the caller from promoting its runtime to an optional, which costs a
22
- /// retain/release pair around every call on the hot paths (property reads, function results).
23
- internal init(_ runtime: JavaScriptRuntime, _ pointee: consuming facebook.jsi.Value) {
24
- self.runtime = runtime
39
+ /// Takes ownership of the given JSI value.
40
+ internal init(_ runtime: borrowing JavaScriptRuntime, _ pointee: consuming facebook.jsi.Value) {
41
+ self.runtimeHandle = runtime.handle
25
42
  self.pointee = pointee
26
43
  }
27
44
 
@@ -29,51 +46,51 @@ public final class JavaScriptValue: JavaScriptType, Equatable, Escapable {
29
46
  /// The `copying:` label keeps this initializer from being picked for temporaries: with a plain
30
47
  /// second argument, a non-optional runtime used to win overload resolution over the consuming
31
48
  /// initializer above, cloning every freshly returned value instead of taking it over.
32
- internal init(_ runtime: JavaScriptRuntime, copying pointee: borrowing facebook.jsi.Value) {
33
- self.runtime = runtime
49
+ internal init(_ runtime: borrowing JavaScriptRuntime, copying pointee: borrowing facebook.jsi.Value) {
50
+ self.runtimeHandle = runtime.handle
34
51
  self.pointee = facebook.jsi.Value(runtime.pointee, pointee)
35
52
  }
36
53
 
37
54
  /// Creates a boolean JS value.
38
- public init(_ runtime: JavaScriptRuntime, _ bool: Bool) {
39
- self.runtime = runtime
55
+ public init(_ runtime: borrowing JavaScriptRuntime, _ bool: Bool) {
56
+ self.runtimeHandle = runtime.handle
40
57
  self.pointee = facebook.jsi.Value(bool)
41
58
  }
42
59
 
43
60
  /// Creates a string JS value.
44
- public init(_ runtime: JavaScriptRuntime, _ string: String) {
45
- self.runtime = runtime
61
+ public init(_ runtime: borrowing JavaScriptRuntime, _ string: String) {
62
+ self.runtimeHandle = runtime.handle
46
63
  self.pointee = string.toJSIValue(in: runtime.pointee)
47
64
  }
48
65
 
49
66
  /// Creates a BigInt JS value from an Int64.
50
- public init(_ runtime: JavaScriptRuntime, bigInt: Int64) {
51
- self.runtime = runtime
67
+ public init(_ runtime: borrowing JavaScriptRuntime, bigInt: Int64) {
68
+ self.runtimeHandle = runtime.handle
52
69
  self.pointee = facebook.jsi.Value(runtime.pointee, facebook.jsi.BigInt.fromInt64(runtime.pointee, bigInt))
53
70
  }
54
71
 
55
72
  /// Creates a BigInt JS value from a UInt64.
56
- public init(_ runtime: JavaScriptRuntime, bigInt: UInt64) {
57
- self.runtime = runtime
73
+ public init(_ runtime: borrowing JavaScriptRuntime, bigInt: UInt64) {
74
+ self.runtimeHandle = runtime.handle
58
75
  self.pointee = facebook.jsi.Value(runtime.pointee, facebook.jsi.BigInt.fromUint64(runtime.pointee, bigInt))
59
76
  }
60
77
 
61
78
  /// Creates a JS value from a JS representable.
62
- public init(_ runtime: JavaScriptRuntime, _ value: JavaScriptRepresentable) {
63
- self.runtime = runtime
79
+ public init(_ runtime: borrowing JavaScriptRuntime, _ value: JavaScriptRepresentable) {
80
+ self.runtimeHandle = runtime.handle
64
81
  self.pointee = value.toJavaScriptValue(in: runtime).toJSIValue(in: runtime.pointee)
65
82
  }
66
83
 
67
84
  /// Creates a JS value from a JSI representable.
68
- internal init(_ runtime: JavaScriptRuntime, _ value: JSIRepresentable) {
69
- self.runtime = runtime
85
+ internal init(_ runtime: borrowing JavaScriptRuntime, _ value: JSIRepresentable) {
86
+ self.runtimeHandle = runtime.handle
70
87
  self.pointee = value.toJSIValue(in: runtime.pointee)
71
88
  }
72
89
 
73
90
  /// Copies the value.
74
91
  public func copy() -> JavaScriptValue {
75
- if let runtime {
76
- return JavaScriptValue(runtime, facebook.jsi.Value(runtime.pointee, pointee))
92
+ if let runtimeHandle, let jsiRuntime = runtimeHandle.pointee {
93
+ return JavaScriptValue(runtimeHandle, facebook.jsi.Value(jsiRuntime, pointee))
77
94
  }
78
95
  // Some simple value kinds do not require the runtime.
79
96
  switch kind {
@@ -82,9 +99,9 @@ public final class JavaScriptValue: JavaScriptType, Equatable, Escapable {
82
99
  case .null:
83
100
  return .null
84
101
  case .bool:
85
- return .init(nil, facebook.jsi.Value(getBool()))
102
+ return .init(facebook.jsi.Value(getBool()))
86
103
  case .number:
87
- return .init(nil, facebook.jsi.Value(getDouble()))
104
+ return .init(facebook.jsi.Value(getDouble()))
88
105
  default:
89
106
  FatalError.runtimeLost()
90
107
  }
@@ -137,21 +154,21 @@ public final class JavaScriptValue: JavaScriptType, Equatable, Escapable {
137
154
  }
138
155
 
139
156
  public func isArray() -> Bool {
140
- guard let jsiRuntime = runtime?.pointee else {
157
+ guard let jsiRuntime else {
141
158
  FatalError.runtimeLost()
142
159
  }
143
160
  return pointee.isObject() && pointee.getObject(jsiRuntime).isArray(jsiRuntime)
144
161
  }
145
162
 
146
163
  public func isFunction() -> Bool {
147
- guard let jsiRuntime = runtime?.pointee else {
164
+ guard let jsiRuntime else {
148
165
  FatalError.runtimeLost()
149
166
  }
150
167
  return pointee.isObject() && pointee.getObject(jsiRuntime).isFunction(jsiRuntime)
151
168
  }
152
169
 
153
170
  public func isTypedArray() -> Bool {
154
- guard let jsiRuntime = runtime?.pointee else {
171
+ guard let jsiRuntime else {
155
172
  FatalError.runtimeLost()
156
173
  }
157
174
  return pointee.isObject() && expo.isTypedArray(jsiRuntime, pointee.getObject(jsiRuntime))
@@ -159,7 +176,7 @@ public final class JavaScriptValue: JavaScriptType, Equatable, Escapable {
159
176
 
160
177
  /// Checks whether the value is an `ArrayBuffer`.
161
178
  public func isArrayBuffer() -> Bool {
162
- guard let jsiRuntime = runtime?.pointee else {
179
+ guard let jsiRuntime else {
163
180
  FatalError.runtimeLost()
164
181
  }
165
182
  return pointee.isObject() && pointee.getObject(jsiRuntime).isArrayBuffer(jsiRuntime)
@@ -258,7 +275,7 @@ public final class JavaScriptValue: JavaScriptType, Equatable, Escapable {
258
275
 
259
276
  /// Returns the value as a string, or asserts if not a string.
260
277
  public func getString() -> String {
261
- guard let jsiRuntime = runtime?.pointee else {
278
+ guard let jsiRuntime else {
262
279
  FatalError.runtimeLost()
263
280
  }
264
281
  assert(isString(), "Value is not a string")
@@ -276,20 +293,20 @@ public final class JavaScriptValue: JavaScriptType, Equatable, Escapable {
276
293
 
277
294
  /// Returns the value as an object, or asserts if not an object.
278
295
  public func getObject() -> JavaScriptObject {
279
- guard let runtime else {
296
+ guard let runtimeHandle, let jsiRuntime = runtimeHandle.pointee else {
280
297
  FatalError.runtimeLost()
281
298
  }
282
299
  assert(isObject(), "Value is not an object")
283
- return JavaScriptObject(runtime, pointee.getObject(runtime.pointee))
300
+ return JavaScriptObject(runtimeHandle, pointee.getObject(jsiRuntime))
284
301
  }
285
302
 
286
303
  /// Returns the value as an array, or asserts if not an array.
287
304
  public func getArray() -> JavaScriptArray {
288
- guard let runtime else {
305
+ guard let runtimeHandle, let jsiRuntime = runtimeHandle.pointee else {
289
306
  FatalError.runtimeLost()
290
307
  }
291
308
  assert(isArray(), "Value is not an array")
292
- return JavaScriptArray(runtime, pointee.getObject(runtime.pointee).getArray(runtime.pointee))
309
+ return JavaScriptArray(runtimeHandle, pointee.getObject(jsiRuntime).getArray(jsiRuntime))
293
310
  }
294
311
 
295
312
  /// Returns the value as a function, or asserts if not a function.
@@ -426,7 +443,7 @@ public final class JavaScriptValue: JavaScriptType, Equatable, Escapable {
426
443
 
427
444
  /// Returns a string representing the value. Same as calling `toString()` in JS.
428
445
  public func toString() -> String {
429
- guard let jsiRuntime = runtime?.pointee else {
446
+ guard let jsiRuntime else {
430
447
  FatalError.runtimeLost()
431
448
  }
432
449
  return String(jsiString: pointee.toString(jsiRuntime), in: jsiRuntime)
@@ -536,8 +553,8 @@ public final class JavaScriptValue: JavaScriptType, Equatable, Escapable {
536
553
  }
537
554
 
538
555
  internal func asJSIValue() -> facebook.jsi.Value {
539
- if let runtime {
540
- return facebook.jsi.Value(runtime.pointee, pointee)
556
+ if let jsiRuntime {
557
+ return facebook.jsi.Value(jsiRuntime, pointee)
541
558
  }
542
559
  // Some simple value kinds do not require the runtime.
543
560
  switch kind {
@@ -597,7 +614,7 @@ public final class JavaScriptValue: JavaScriptType, Equatable, Escapable {
597
614
 
598
615
  /// Tests whether two values are strictly equal, according to https://262.ecma-international.org/11.0/#sec-strict-equality-comparison
599
616
  public func isEqual(to another: JavaScriptValue) -> Bool {
600
- if let jsiRuntime = runtime?.pointee ?? another.runtime?.pointee {
617
+ if let jsiRuntime = jsiRuntime ?? another.jsiRuntime {
601
618
  return facebook.jsi.Value.strictEquals(jsiRuntime, pointee, another.pointee)
602
619
  }
603
620
  // Some types don't have to be tied to any runtime. Since `strictEquals` needs a runtime, we need to handle this case ourselves.
@@ -659,20 +676,20 @@ public final class JavaScriptValue: JavaScriptType, Equatable, Escapable {
659
676
  /// This is a lightweight way to create a boolean true value that can be used in contexts
660
677
  /// where a runtime is not available or needed.
661
678
  public static func `true`() -> JavaScriptValue {
662
- return JavaScriptValue(nil, facebook.jsi.Value(true))
679
+ return JavaScriptValue(facebook.jsi.Value(true))
663
680
  }
664
681
 
665
682
  /// This is a lightweight way to create a boolean false value that can be used in contexts
666
683
  /// where a runtime is not available or needed.
667
684
  public static func `false`() -> JavaScriptValue {
668
- return JavaScriptValue(nil, facebook.jsi.Value(false))
685
+ return JavaScriptValue(facebook.jsi.Value(false))
669
686
  }
670
687
 
671
688
  /// This is a lightweight way to create a numeric value that can be used in contexts
672
689
  /// where a runtime is not available or needed. JavaScript numbers are represented
673
690
  /// as double-precision floating-point values following the IEEE 754 standard.
674
691
  public static func number(_ number: Double) -> JavaScriptValue {
675
- return JavaScriptValue(nil, facebook.jsi.Value(number))
692
+ return JavaScriptValue(facebook.jsi.Value(number))
676
693
  }
677
694
 
678
695
  public static func representing(value: JavaScriptRepresentable, in runtime: JavaScriptRuntime) -> JavaScriptValue {
@@ -715,9 +732,18 @@ extension JavaScriptValue: JSIRepresentable {
715
732
  extension JavaScriptValue {
716
733
  public struct TypeError: Error, CustomStringConvertible {
717
734
  let type: any ~Copyable.Type
735
+ /// A specific explanation that replaces the generic message, when the failure needs one.
736
+ var reason: String? = nil
737
+ /// Where inside the converted value the failure happened, such as `.user.tags[1]`. Empty when it's
738
+ /// the value itself.
739
+ var path: String = ""
718
740
 
719
741
  public var description: String {
720
- return "TypeError: Value cannot be represented as \(type)"
742
+ let message = "TypeError: \(reason ?? "Value cannot be represented as \(type)")"
743
+ guard !path.isEmpty else {
744
+ return message
745
+ }
746
+ return "\(message) (path: \(path.hasPrefix(".") ? path.dropFirst() : Substring(path)))"
721
747
  }
722
748
  }
723
749
  }
@@ -725,6 +751,6 @@ extension JavaScriptValue {
725
751
  // Shared instances behind `JavaScriptValue.undefined` and `JavaScriptValue.null`; see the comment on
726
752
  // those accessors for why these are globals.
727
753
  @usableFromInline
728
- internal let sharedUndefinedValue = JavaScriptValue(nil, facebook.jsi.Value.undefined())
754
+ internal let sharedUndefinedValue = JavaScriptValue(facebook.jsi.Value.undefined())
729
755
  @usableFromInline
730
- internal let sharedNullValue = JavaScriptValue(nil, facebook.jsi.Value.null())
756
+ internal let sharedNullValue = JavaScriptValue(facebook.jsi.Value.null())
@@ -25,6 +25,16 @@ extension String {
25
25
  self = result
26
26
  }
27
27
 
28
+ /// Builds a Swift `String` from a `jsi::PropNameID` the same way as `init(jsiString:in:)`, reading
29
+ /// the engine's internal representation without materializing a `std::string` first.
30
+ internal init(jsiPropNameID: borrowing facebook.jsi.PropNameID, in runtime: facebook.jsi.IRuntime) {
31
+ var result = ""
32
+ withUnsafeMutablePointer(to: &result) { resultPtr in
33
+ expo.getPropNameIdData(runtime, jsiPropNameID, UnsafeMutableRawPointer(resultPtr), appendEngineStringChunk)
34
+ }
35
+ self = result
36
+ }
37
+
28
38
  /// Builds a `jsi::PropNameID` from the string's UTF-8 bytes. This is how every String-keyed
29
39
  /// property API turns its name into a key: JSI's `const char*` overloads treat the bytes as ASCII
30
40
  /// and would mangle any non-ASCII name.
@@ -26,7 +26,7 @@ public:
26
26
  jsi::Value result;
27
27
  // If the Swift getter stored a pending error, rethrow its JSError directly
28
28
  // to preserve all properties (message, code, stack, etc.).
29
- if (_callbacks.get(name.utf8(runtime).c_str(), result)) {
29
+ if (_callbacks.get(name, result)) {
30
30
  if (auto *error = CppError::getCurrent()) {
31
31
  throw error->release();
32
32
  }
@@ -39,7 +39,7 @@ public:
39
39
  // `jsi::JSError` directly and the `CppError` check below is never reached.
40
40
  // For writable host objects, a throwing Swift setter routes its error through
41
41
  // `CppError`'s thread-local slot, which we drain and rethrow here.
42
- if (_callbacks.set(runtime, name.utf8(runtime).c_str(), value)) {
42
+ if (_callbacks.set(runtime, name, value)) {
43
43
  if (auto *error = CppError::getCurrent()) {
44
44
  throw error->release();
45
45
  }