expo-modules-core 1.0.4 → 1.1.0

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 (70) hide show
  1. package/CHANGELOG.md +26 -6
  2. package/ExpoModulesCore.podspec +38 -4
  3. package/android/CMakeLists.txt +21 -71
  4. package/android/ExpoModulesCorePlugin.gradle +200 -0
  5. package/android/build.gradle +42 -205
  6. package/android/legacy/CMakeLists.txt +194 -0
  7. package/android/{src → legacy}/fabric/Android-prebuilt.cmake +2 -0
  8. package/android/legacy/fabric/CMakeLists.txt +40 -0
  9. package/android/proguard-rules.pro +13 -0
  10. package/android/src/fabric/CMakeLists.txt +23 -16
  11. package/android/src/main/java/expo/modules/kotlin/AppContext.kt +7 -0
  12. package/android/src/main/java/expo/modules/kotlin/exception/CommonExceptions.kt +5 -0
  13. package/android/src/main/java/expo/modules/kotlin/objects/ObjectDefinitionBuilder.kt +44 -0
  14. package/android/src/main/java/expo/modules/kotlin/views/ViewDefinitionBuilder.kt +9 -1
  15. package/android/src/main/java/expo/modules/kotlin/views/ViewManagerDefinitionBuilder.kt +46 -0
  16. package/build/EventEmitter.d.ts +2 -2
  17. package/build/EventEmitter.d.ts.map +1 -1
  18. package/build/NativeModulesProxy.types.d.ts +1 -1
  19. package/build/NativeModulesProxy.types.d.ts.map +1 -1
  20. package/build/NativeViewManagerAdapter.native.d.ts +1 -1
  21. package/build/NativeViewManagerAdapter.native.d.ts.map +1 -1
  22. package/build/NativeViewManagerAdapter.native.js +3 -3
  23. package/build/NativeViewManagerAdapter.native.js.map +1 -1
  24. package/build/PermissionsHook.d.ts +4 -4
  25. package/build/PermissionsHook.d.ts.map +1 -1
  26. package/build/PermissionsInterface.d.ts +1 -1
  27. package/build/PermissionsInterface.d.ts.map +1 -1
  28. package/build/Platform.d.ts +2 -2
  29. package/build/Platform.d.ts.map +1 -1
  30. package/build/TypedArrays.types.d.ts +9 -0
  31. package/build/TypedArrays.types.d.ts.map +1 -0
  32. package/build/TypedArrays.types.js +2 -0
  33. package/build/TypedArrays.types.js.map +1 -0
  34. package/build/index.d.ts +1 -0
  35. package/build/index.d.ts.map +1 -1
  36. package/build/index.js +1 -0
  37. package/build/index.js.map +1 -1
  38. package/build/requireNativeModule.d.ts +1 -1
  39. package/build/requireNativeModule.d.ts.map +1 -1
  40. package/common/cpp/fabric/ExpoViewProps.cpp +2 -0
  41. package/ios/AppDelegates/EXAppDelegateWrapper.h +22 -0
  42. package/ios/AppDelegates/EXAppDelegateWrapper.mm +100 -0
  43. package/ios/JSI/EXJavaScriptRuntime.mm +5 -5
  44. package/ios/ReactDelegates/EXReactCompatibleHelpers.h +1 -1
  45. package/ios/ReactDelegates/EXReactCompatibleHelpers.m +8 -2
  46. package/ios/ReactDelegates/EXReactDelegateWrapper.h +5 -0
  47. package/ios/ReactDelegates/EXReactDelegateWrapper.m +14 -1
  48. package/ios/ReactDelegates/ExpoReactDelegate.swift +7 -2
  49. package/ios/Swift/AppContext.swift +9 -0
  50. package/ios/Swift/Arguments/Enumerable.swift +2 -16
  51. package/ios/Swift/Classes/ClassComponentElement.swift +1 -1
  52. package/ios/Swift/Convertibles/Either.swift +5 -14
  53. package/ios/Swift/DynamicTypes/DynamicEnumType.swift +1 -1
  54. package/ios/Swift/DynamicTypes/DynamicSharedObjectType.swift +4 -0
  55. package/ios/Swift/DynamicTypes/DynamicType.swift +1 -1
  56. package/ios/Swift/Functions/AnyFunction.swift +18 -0
  57. package/ios/Swift/JavaScriptUtils.swift +25 -8
  58. package/ios/Swift/Objects/ObjectDefinition.swift +4 -4
  59. package/ios/Swift/Objects/ObjectDefinitionComponents.swift +7 -0
  60. package/ios/Swift/Objects/PropertyComponent.swift +138 -48
  61. package/ios/Swift/SharedObjects/SharedObject.swift +5 -0
  62. package/ios/Tests/ClassComponentSpec.swift +15 -0
  63. package/ios/Tests/EitherSpec.swift +28 -0
  64. package/ios/Tests/FunctionSpec.swift +31 -0
  65. package/ios/Tests/PropertyComponentSpec.swift +131 -33
  66. package/package.json +2 -2
  67. package/src/NativeViewManagerAdapter.native.tsx +4 -4
  68. package/src/TypedArrays.types.ts +11 -0
  69. package/src/index.ts +1 -0
  70. package/ios/AppDelegates/EXAppDelegateWrapper.m +0 -45
@@ -22,10 +22,15 @@ public class ExpoReactDelegate: NSObject {
22
22
  }
23
23
 
24
24
  @objc
25
- public func createRootView(bridge: RCTBridge, moduleName: String, initialProperties: [AnyHashable: Any]?) -> UIView {
25
+ public func createRootView(
26
+ bridge: RCTBridge,
27
+ moduleName: String,
28
+ initialProperties: [AnyHashable: Any]?,
29
+ fabricEnabled: Bool = EXAppDefines.APP_NEW_ARCH_ENABLED
30
+ ) -> UIView {
26
31
  return self.handlers.lazy
27
32
  .compactMap { $0.createRootView(reactDelegate: self, bridge: bridge, moduleName: moduleName, initialProperties: initialProperties) }
28
- .first(where: { _ in true }) ?? EXAppSetupDefaultRootView(bridge, moduleName, initialProperties)
33
+ .first(where: { _ in true }) ?? EXAppSetupDefaultRootView(bridge, moduleName, initialProperties, fabricEnabled)
29
34
  }
30
35
 
31
36
  @objc
@@ -91,6 +91,15 @@ public final class AppContext: NSObject {
91
91
  return view as? ViewType
92
92
  }
93
93
 
94
+ // MARK: - Running on specific queues
95
+
96
+ /**
97
+ Runs a code block on the JavaScript thread.
98
+ */
99
+ public func executeOnJavaScriptThread(runBlock: @escaping (() -> Void)) {
100
+ reactBridge?.dispatchBlock(runBlock, queue: RCTJSThread)
101
+ }
102
+
94
103
  // MARK: - Legacy modules
95
104
 
96
105
  /**
@@ -3,7 +3,7 @@
3
3
  /**
4
4
  A protocol that allows converting raw values to enum cases.
5
5
  */
6
- public protocol Enumerable: AnyArgument {
6
+ public protocol Enumerable: AnyArgument, CaseIterable {
7
7
  /**
8
8
  Tries to create an enum case using given raw value.
9
9
  May throw errors, e.g. when the raw value doesn't match any case.
@@ -44,21 +44,7 @@ public extension Enumerable where Self: RawRepresentable, Self: Hashable {
44
44
  }
45
45
 
46
46
  static var allRawValues: [Any] {
47
- // Be careful it operates on unsafe pointers!
48
- let sequence = AnySequence { () -> AnyIterator<RawValue> in
49
- var raw = 0
50
- return AnyIterator {
51
- let current: Self? = withUnsafePointer(to: &raw) { ptr in
52
- ptr.withMemoryRebound(to: Self.self, capacity: 1) { $0.pointee }
53
- }
54
- guard let value = current?.rawValue else {
55
- return nil
56
- }
57
- raw += 1
58
- return value
59
- }
60
- }
61
- return Array(sequence)
47
+ return allCases.map { $0.rawValue }
62
48
  }
63
49
  }
64
50
 
@@ -25,7 +25,7 @@ extension AsyncFunctionComponent: ClassComponentElement {
25
25
  }
26
26
 
27
27
  extension PropertyComponent: ClassComponentElement {
28
- public typealias OwnerType = Void
28
+ // It already has the `OwnerType`
29
29
  }
30
30
 
31
31
  extension ConstantsDefinition: ClassComponentElement {
@@ -51,8 +51,11 @@ open class Either<FirstType, SecondType>: Convertible {
51
51
  // MARK: - Convertible
52
52
 
53
53
  public class func convert(from value: Any?) throws -> Self {
54
- if value is FirstType || value is SecondType {
55
- return Self(value)
54
+ for type in dynamicTypes() {
55
+ // Initialize the "either" when the current type can cast given value.
56
+ if let value = try? type.cast(value) {
57
+ return Self(value)
58
+ }
56
59
  }
57
60
  throw NeitherTypeException(Self.dynamicTypes())
58
61
  }
@@ -79,12 +82,6 @@ open class EitherOfThree<FirstType, SecondType, ThirdType>: Either<FirstType, Se
79
82
  public func get() -> ThirdType? {
80
83
  return value as? ThirdType
81
84
  }
82
-
83
- // MARK: - Convertible
84
-
85
- public override class func convert(from value: Any?) throws -> Self {
86
- return value is ThirdType ? Self(value) : try super.convert(from: value)
87
- }
88
85
  }
89
86
 
90
87
  /*
@@ -108,12 +105,6 @@ open class EitherOfFour<FirstType, SecondType, ThirdType, FourthType>: EitherOfT
108
105
  public func get() -> FourthType? {
109
106
  return value as? FourthType
110
107
  }
111
-
112
- // MARK: - Convertible
113
-
114
- public override class func convert(from value: Any?) throws -> Self {
115
- return value is FourthType ? Self(value) : try super.convert(from: value)
116
- }
117
108
  }
118
109
 
119
110
  // MARK: - Exceptions
@@ -4,7 +4,7 @@
4
4
  A dynamic type representing an enum that conforms to `Enumerable`.
5
5
  */
6
6
  internal struct DynamicEnumType: AnyDynamicType {
7
- let innerType: Enumerable.Type
7
+ let innerType: any Enumerable.Type
8
8
 
9
9
  func wraps<InnerType>(_ type: InnerType.Type) -> Bool {
10
10
  return innerType == InnerType.self
@@ -18,6 +18,10 @@ internal struct DynamicSharedObjectType: AnyDynamicType {
18
18
  }
19
19
 
20
20
  func cast<ValueType>(_ value: ValueType) throws -> Any {
21
+ if let value = value as? SharedObject, type(of: value) == innerType {
22
+ // Given value is a shared object already
23
+ return value
24
+ }
21
25
  if let jsObject = try (value as? JavaScriptValue)?.asObject(),
22
26
  let nativeSharedObject = SharedObjectRegistry.toNativeObject(jsObject) {
23
27
  return nativeSharedObject
@@ -18,7 +18,7 @@ internal func DynamicType<T>(_ type: T.Type) -> AnyDynamicType {
18
18
  if let ConvertibleType = T.self as? Convertible.Type {
19
19
  return DynamicConvertibleType(innerType: ConvertibleType)
20
20
  }
21
- if let EnumType = T.self as? Enumerable.Type {
21
+ if let EnumType = T.self as? any Enumerable.Type {
22
22
  return DynamicEnumType(innerType: EnumType)
23
23
  }
24
24
  if let SharedObjectType = T.self as? SharedObject.Type {
@@ -23,6 +23,11 @@ internal protocol AnyFunction: AnyDefinition, JavaScriptObjectBuilder {
23
23
  */
24
24
  var argumentsCount: Int { get }
25
25
 
26
+ /**
27
+ A minimum number of arguments the functions needs which equals to `argumentsCount` reduced by the number of trailing optional arguments.
28
+ */
29
+ var requiredArgumentsCount: Int { get }
30
+
26
31
  /**
27
32
  Indicates whether the function's arguments starts from the owner that calls this function.
28
33
  */
@@ -42,6 +47,19 @@ internal protocol AnyFunction: AnyDefinition, JavaScriptObjectBuilder {
42
47
  }
43
48
 
44
49
  extension AnyFunction {
50
+ var requiredArgumentsCount: Int {
51
+ var trailingOptionalArgumentsCount: Int = 0
52
+
53
+ for dynamicArgumentType in dynamicArgumentTypes.reversed() {
54
+ if dynamicArgumentType is DynamicOptionalType {
55
+ trailingOptionalArgumentsCount += 1
56
+ } else {
57
+ break
58
+ }
59
+ }
60
+ return argumentsCount - trailingOptionalArgumentsCount
61
+ }
62
+
45
63
  /**
46
64
  Calls the function just like `call(by:withArguments:callback:)`, but without an owner
47
65
  and with an empty callback. Might be useful when you only want to call the function,
@@ -28,10 +28,16 @@ internal func cast(_ value: Any, toType type: AnyDynamicType) throws -> Any {
28
28
  of function's arguments (without an owner and promise). Rethrows exceptions thrown by `cast(_:toType:)`.
29
29
  */
30
30
  internal func cast(arguments: [Any], forFunction function: AnyFunction) throws -> [Any] {
31
- if arguments.count != function.argumentsCount {
32
- throw InvalidArgsNumberException((received: arguments.count, expected: function.argumentsCount))
33
- }
31
+ let requiredArgumentsCount = function.requiredArgumentsCount
34
32
  let argumentTypeOffset = function.takesOwner ? 1 : 0
33
+
34
+ if arguments.count < requiredArgumentsCount || arguments.count > function.argumentsCount {
35
+ throw InvalidArgsNumberException((
36
+ received: arguments.count,
37
+ expected: function.argumentsCount,
38
+ required: requiredArgumentsCount
39
+ ))
40
+ }
35
41
  return try arguments.enumerated().map { index, argument in
36
42
  let argumentType = function.dynamicArgumentTypes[index + argumentTypeOffset]
37
43
 
@@ -44,20 +50,31 @@ internal func cast(arguments: [Any], forFunction function: AnyFunction) throws -
44
50
  }
45
51
 
46
52
  /**
47
- Prepends the owner to the array of arguments if the given function can take it.
53
+ Ensures the provided array of arguments matches the number of arguments expected by the function.
54
+ - If the function takes the owner, it's added to the beginning.
55
+ - If the array is still too small, missing arguments are very likely to be optional so it puts `nil` in their place.
48
56
  */
49
57
  internal func concat(arguments: [Any], withOwner owner: AnyObject?, forFunction function: AnyFunction) -> [Any] {
58
+ var result = arguments
59
+
50
60
  if function.takesOwner, let owner = try? function.dynamicArgumentTypes.first?.cast(owner) {
51
- return [owner] + arguments
61
+ result = [owner] + arguments
62
+ }
63
+ if arguments.count < function.argumentsCount {
64
+ result += Array(repeating: Any?.none as Any, count: function.argumentsCount - arguments.count)
52
65
  }
53
- return arguments
66
+ return result
54
67
  }
55
68
 
56
69
  // MARK: - Exceptions
57
70
 
58
- internal class InvalidArgsNumberException: GenericException<(received: Int, expected: Int)> {
71
+ internal class InvalidArgsNumberException: GenericException<(received: Int, expected: Int, required: Int)> {
59
72
  override var reason: String {
60
- "Received \(param.received) arguments, but \(param.expected) was expected"
73
+ if param.required < param.expected {
74
+ return "Received \(param.received) arguments, but \(param.expected) was expected and at least \(param.required) is required"
75
+ } else {
76
+ return "Received \(param.received) arguments, but \(param.expected) was expected"
77
+ }
61
78
  }
62
79
  }
63
80
 
@@ -17,7 +17,7 @@ public class ObjectDefinition: AnyDefinition, JavaScriptObjectBuilder {
17
17
  /**
18
18
  A map of dynamic properties defined by the object.
19
19
  */
20
- let properties: [String: PropertyComponent]
20
+ let properties: [String: AnyPropertyComponent]
21
21
 
22
22
  /**
23
23
  A map of classes defined within the object.
@@ -38,8 +38,8 @@ public class ObjectDefinition: AnyDefinition, JavaScriptObjectBuilder {
38
38
  .compactMap { $0 as? ConstantsDefinition }
39
39
 
40
40
  self.properties = definitions
41
- .compactMap { $0 as? PropertyComponent }
42
- .reduce(into: [String: PropertyComponent]()) { dict, property in
41
+ .compactMap { $0 as? AnyPropertyComponent }
42
+ .reduce(into: [String: AnyPropertyComponent]()) { dict, property in
43
43
  dict[property.name] = property
44
44
  }
45
45
 
@@ -90,7 +90,7 @@ public class ObjectDefinition: AnyDefinition, JavaScriptObjectBuilder {
90
90
 
91
91
  internal func decorateWithProperties(runtime: JavaScriptRuntime, object: JavaScriptObject) {
92
92
  for property in properties.values {
93
- let descriptor = property.buildDescriptor(inRuntime: runtime, withCaller: object)
93
+ let descriptor = property.buildDescriptor(inRuntime: runtime)
94
94
  object.defineProperty(property.name, descriptor: descriptor)
95
95
  }
96
96
  }
@@ -26,6 +26,13 @@ public func Events(_ names: String...) -> EventsDefinition {
26
26
  return EventsDefinition(names: names)
27
27
  }
28
28
 
29
+ /**
30
+ Defines event names that the object can send to JavaScript.
31
+ */
32
+ public func Events(_ names: [String]) -> EventsDefinition {
33
+ return EventsDefinition(names: names)
34
+ }
35
+
29
36
  /**
30
37
  Function that is invoked when the first event listener is added.
31
38
  */
@@ -1,6 +1,18 @@
1
1
  // Copyright 2022-present 650 Industries. All rights reserved.
2
2
 
3
- public final class PropertyComponent: AnyDefinition {
3
+ protocol AnyPropertyComponent {
4
+ /**
5
+ Name of the property.
6
+ */
7
+ var name: String { get }
8
+
9
+ /**
10
+ Creates the JavaScript object representing the property descriptor.
11
+ */
12
+ func buildDescriptor(inRuntime runtime: JavaScriptRuntime) -> JavaScriptObject
13
+ }
14
+
15
+ public final class PropertyComponent<OwnerType>: AnyDefinition, AnyPropertyComponent {
4
16
  /**
5
17
  Name of the property.
6
18
  */
@@ -16,135 +28,213 @@ public final class PropertyComponent: AnyDefinition {
16
28
  */
17
29
  var setter: AnySyncFunctionComponent?
18
30
 
31
+ /**
32
+ Initializes an unowned PropertyComponent without getter and setter functions.
33
+ */
19
34
  init(name: String) {
20
35
  self.name = name
21
36
  }
22
37
 
38
+ /**
39
+ Initializes an unowned PropertyComponent with a getter without arguments.
40
+ */
41
+ init<ReturnType>(name: String, getter: @escaping () -> ReturnType) {
42
+ self.name = name
43
+
44
+ // Set the getter right away
45
+ self.get(getter)
46
+ }
47
+
48
+ /**
49
+ Initializes an owned PropertyComponent with a getter that takes the owner as its first argument.
50
+ */
51
+ init<ReturnType>(name: String, getter: @escaping (_ this: OwnerType) -> ReturnType) {
52
+ self.name = name
53
+
54
+ // Set the getter right away
55
+ self.get(getter)
56
+ }
57
+
23
58
  // MARK: - Modifiers
24
59
 
25
60
  /**
26
- Modifier that sets property getter that has no arguments (the caller is not used).
61
+ Modifier that sets property getter that has no arguments (the owner is not used).
27
62
  */
28
- public func get<Value>(_ getter: @escaping () -> Value) -> Self {
63
+ @discardableResult
64
+ public func get<ReturnType>(_ getter: @escaping () -> ReturnType) -> Self {
29
65
  self.getter = SyncFunctionComponent(
30
66
  "get",
31
67
  firstArgType: Void.self,
32
- dynamicArgumentTypes: [~Any.self],
33
- { (caller: Any) in getter() }
68
+ dynamicArgumentTypes: [],
69
+ getter
34
70
  )
35
71
  return self
36
72
  }
37
73
 
38
74
  /**
39
- Modifier that sets property setter that receives only the new value as an argument.
75
+ Modifier that sets property getter that receives the owner as an argument.
76
+ The owner is an object on which the function is called, like `this` in JavaScript.
40
77
  */
41
- public func set<Value>(_ setter: @escaping (_ newValue: Value) -> ()) -> Self {
42
- self.setter = SyncFunctionComponent(
43
- "set",
44
- firstArgType: Void.self,
45
- dynamicArgumentTypes: [~Any.self, ~Value.self],
46
- { (caller: Any, value: Value) in setter(value) }
78
+ @discardableResult
79
+ public func get<ReturnType>(_ getter: @escaping (_ this: OwnerType) -> ReturnType) -> Self {
80
+ self.getter = SyncFunctionComponent(
81
+ "get",
82
+ firstArgType: OwnerType.self,
83
+ dynamicArgumentTypes: [~OwnerType.self],
84
+ getter
47
85
  )
86
+ self.getter?.takesOwner = true
48
87
  return self
49
88
  }
50
89
 
51
90
  /**
52
- Modifier that sets property getter that receives the caller as an argument.
53
- The caller is an object on which the function is called, like `this` in JavaScript.
91
+ Modifier that sets property setter that receives only the new value as an argument.
54
92
  */
55
- public func get<Value, Caller>(_ getter: @escaping (_ this: Caller) -> Value) -> Self {
56
- self.getter = SyncFunctionComponent(
57
- "get",
58
- firstArgType: Caller.self,
59
- dynamicArgumentTypes: [~Caller.self],
60
- getter
93
+ @discardableResult
94
+ public func set<ValueType>(_ setter: @escaping (_ newValue: ValueType) -> Void) -> Self {
95
+ self.setter = SyncFunctionComponent(
96
+ "set",
97
+ firstArgType: ValueType.self,
98
+ dynamicArgumentTypes: [~ValueType.self],
99
+ setter
61
100
  )
62
101
  return self
63
102
  }
64
103
 
65
104
  /**
66
- Modifier that sets property setter that receives the caller and the new value as arguments.
67
- The caller is an object on which the function is called, like `this` in JavaScript.
105
+ Modifier that sets property setter that receives the owner and the new value as arguments.
106
+ The owner is an object on which the function is called, like `this` in JavaScript.
68
107
  */
69
- public func set<Value, Caller>(_ setter: @escaping (_ this: Caller, _ newValue: Value) -> ()) -> Self {
108
+ @discardableResult
109
+ public func set<ValueType>(_ setter: @escaping (_ this: OwnerType, _ newValue: ValueType) -> Void) -> Self {
70
110
  self.setter = SyncFunctionComponent(
71
111
  "set",
72
- firstArgType: Caller.self,
73
- dynamicArgumentTypes: [~Caller.self, ~Value.self],
112
+ firstArgType: OwnerType.self,
113
+ dynamicArgumentTypes: [~OwnerType.self, ~ValueType.self],
74
114
  setter
75
115
  )
116
+ self.setter?.takesOwner = true
76
117
  return self
77
118
  }
78
119
 
79
120
  // MARK: - Internals
80
121
 
81
- internal func getValue<Value>(caller: AnyObject? = nil) -> Value? {
82
- let value = try? getter?.call(by: caller, withArguments: [caller as Any])
83
- return value as? Value
122
+ internal func getValue<ValueType>(owner: OwnerType? = nil) throws -> ValueType? {
123
+ let owner = owner as? AnyObject
124
+ let value = try getter?.call(by: owner, withArguments: [])
125
+ return value as? ValueType
84
126
  }
85
127
 
86
- internal func setValue(_ value: Any, caller: AnyObject? = nil) {
87
- let _ = try? setter?.call(by: caller, withArguments: [caller as Any, value])
128
+ internal func setValue(_ value: Any, owner: OwnerType? = nil) {
129
+ let owner = owner as? AnyObject
130
+ _ = try? setter?.call(by: owner, withArguments: [value])
88
131
  }
89
132
 
90
133
  /**
91
134
  Creates the JavaScript function that will be used as a getter of the property.
92
135
  */
93
- internal func buildGetter(inRuntime runtime: JavaScriptRuntime, withCaller caller: AnyObject?) -> JavaScriptObject {
94
- return runtime.createSyncFunction(name, argsCount: 0) { [weak self, weak caller] this, args in
95
- return self?.getValue(caller: caller)
136
+ internal func buildGetter(inRuntime runtime: JavaScriptRuntime) -> JavaScriptObject {
137
+ return runtime.createSyncFunction(name, argsCount: 0) { [weak self, name] this, args in
138
+ guard let self = self else {
139
+ throw NativePropertyUnavailableException(name)
140
+ }
141
+ guard let getter = self.getter else {
142
+ return
143
+ }
144
+ return try getter.call(by: this, withArguments: args)
96
145
  }
97
146
  }
98
147
 
99
148
  /**
100
149
  Creates the JavaScript function that will be used as a setter of the property.
101
150
  */
102
- internal func buildSetter(inRuntime runtime: JavaScriptRuntime, withCaller caller: AnyObject?) -> JavaScriptObject {
103
- return runtime.createSyncFunction(name, argsCount: 1) { [weak self, weak caller] this, args in
104
- return self?.setValue(args.first as Any, caller: caller)
151
+ internal func buildSetter(inRuntime runtime: JavaScriptRuntime) -> JavaScriptObject {
152
+ return runtime.createSyncFunction(name, argsCount: 1) { [weak self, name] this, args in
153
+ guard let self = self else {
154
+ throw NativePropertyUnavailableException(name)
155
+ }
156
+ guard let setter = self.setter else {
157
+ return
158
+ }
159
+ return try setter.call(by: this, withArguments: args)
105
160
  }
106
161
  }
107
162
 
108
163
  /**
109
164
  Creates the JavaScript object representing the property descriptor.
110
165
  */
111
- internal func buildDescriptor(inRuntime runtime: JavaScriptRuntime, withCaller caller: AnyObject?) -> JavaScriptObject {
166
+ internal func buildDescriptor(inRuntime runtime: JavaScriptRuntime) -> JavaScriptObject {
112
167
  let descriptor = runtime.createObject()
113
168
 
114
169
  descriptor.setProperty("enumerable", value: true)
115
170
 
116
171
  if getter != nil {
117
- descriptor.setProperty("get", value: buildGetter(inRuntime: runtime, withCaller: caller))
172
+ descriptor.setProperty("get", value: buildGetter(inRuntime: runtime))
118
173
  }
119
174
  if setter != nil {
120
- descriptor.setProperty("set", value: buildSetter(inRuntime: runtime, withCaller: caller))
175
+ descriptor.setProperty("set", value: buildSetter(inRuntime: runtime))
121
176
  }
122
177
  return descriptor
123
178
  }
124
179
  }
125
180
 
181
+ // MARK: - Exceptions
182
+
183
+ internal final class NativePropertyUnavailableException: GenericException<String> {
184
+ override var reason: String {
185
+ return "Native property '\(param)' is no longer available in memory"
186
+ }
187
+ }
188
+
126
189
  // MARK: - Factory functions
127
190
 
128
191
  /**
129
192
  Creates the property with given name. The component is basically no-op if you don't call `.get(_:)` or `.set(_:)` on it.
130
193
  */
131
- public func Property(_ name: String) -> PropertyComponent {
194
+ public func Property(_ name: String) -> PropertyComponent<Void> {
132
195
  return PropertyComponent(name: name)
133
196
  }
134
197
 
135
198
  /**
136
- Creates the read-only property whose getter doesn't take the caller as an argument.
199
+ Creates the read-only property whose getter doesn't take the owner as an argument.
137
200
  */
138
- public func Property<Value: AnyArgument>(_ name: String, @_implicitSelfCapture get: @escaping () -> Value) -> PropertyComponent {
139
- return PropertyComponent(name: name).get(get)
201
+ public func Property<Value: AnyArgument>(_ name: String, @_implicitSelfCapture get: @escaping () -> Value) -> PropertyComponent<Void> {
202
+ return PropertyComponent(name: name, getter: get)
140
203
  }
141
204
 
142
205
  /**
143
- Creates the read-only property whose getter takes the caller as an argument.
206
+ Creates the read-only property whose getter takes the owner as an argument.
144
207
  */
145
- public func Property<Value: AnyArgument, Caller>(
208
+ public func Property<Value: AnyArgument, OwnerType>(
146
209
  _ name: String,
147
- @_implicitSelfCapture get: @escaping (Caller) -> Value
148
- ) -> PropertyComponent {
149
- return PropertyComponent(name: name).get(get)
210
+ @_implicitSelfCapture get: @escaping (_ this: OwnerType) -> Value
211
+ ) -> PropertyComponent<OwnerType> {
212
+ return PropertyComponent<OwnerType>(name: name, getter: get)
213
+ }
214
+
215
+ /**
216
+ Creates the property that references to an immutable property of the owner object using the key path.
217
+ */
218
+ public func Property<Value: AnyArgument, OwnerType>(
219
+ _ name: String,
220
+ _ keyPath: KeyPath<OwnerType, Value>
221
+ ) -> PropertyComponent<OwnerType> {
222
+ return PropertyComponent<OwnerType>(name: name) { owner in
223
+ return owner[keyPath: keyPath]
224
+ }
225
+ }
226
+
227
+ /**
228
+ Creates the property that references to a mutable property of the owner object using the key path.
229
+ */
230
+ public func Property<Value: AnyArgument, OwnerType>(
231
+ _ name: String,
232
+ _ keyPath: ReferenceWritableKeyPath<OwnerType, Value>
233
+ ) -> PropertyComponent<OwnerType> {
234
+ return PropertyComponent<OwnerType>(name: name) { owner in
235
+ return owner[keyPath: keyPath]
236
+ }
237
+ .set { owner, newValue in
238
+ owner[keyPath: keyPath] = newValue
239
+ }
150
240
  }
@@ -11,6 +11,11 @@ open class SharedObject: AnySharedObject {
11
11
  */
12
12
  public internal(set) var sharedObjectId: SharedObjectId = 0
13
13
 
14
+ /**
15
+ The default public initializer of the shared object.
16
+ */
17
+ public init() {}
18
+
14
19
  /**
15
20
  Returns the JavaScript shared object associated with the native shared object.
16
21
  */
@@ -169,6 +169,17 @@ class ClassComponentSpec: ExpoSpec {
169
169
  expect(newValue.kind) == .number
170
170
  expect(newValue.getInt()) == value + incrementBy
171
171
  }
172
+
173
+ it("gets value from the dynamic property") {
174
+ let initialValue = Int.random(in: 1..<100)
175
+ let value = try runtime!.eval([
176
+ "object = new expo.modules.TestModule.Counter(\(initialValue))",
177
+ "object.currentValue"
178
+ ])
179
+
180
+ expect(value.kind) == .number
181
+ expect(value.getInt()) == initialValue
182
+ }
172
183
  }
173
184
  }
174
185
  }
@@ -190,6 +201,10 @@ fileprivate final class ModuleWithCounterClass: Module {
190
201
  Function("getValue") { counter in
191
202
  return counter.currentValue
192
203
  }
204
+
205
+ Property("currentValue") { counter in
206
+ return counter.currentValue
207
+ }
193
208
  }
194
209
  }
195
210
  }
@@ -40,6 +40,34 @@ final class EitherSpec: ExpoSpec {
40
40
  it("throws when converting from neither type") {
41
41
  expect({ try Either<String, Int>.convert(from: true) }).to(throwError(errorType: NeitherTypeException.self))
42
42
  }
43
+
44
+ it("supports arrays") {
45
+ let either = try Either<String, [String]>.convert(from: ["foo"])
46
+ let value: [String]? = either.get()
47
+
48
+ expect(either.is([String].self)) == true
49
+ expect(value) == ["foo"]
50
+ }
51
+
52
+ it("supports convertibles (UIColor)") {
53
+ let either = try Either<Int, UIColor>.convert(from: "blue")
54
+ let color: UIColor? = either.get()
55
+
56
+ expect(either.is(UIColor.self)) == true
57
+ expect(color?.cgColor.components) == CGColor(red: 0, green: 0, blue: 1, alpha: 1).components
58
+ }
59
+
60
+ it("supports records") {
61
+ struct TestRecord: Record {
62
+ @Field
63
+ var foo: String
64
+ }
65
+ let either = try Either<String, TestRecord>.convert(from: ["foo": "bar"])
66
+ let record: TestRecord? = either.get()
67
+
68
+ expect(either.is(TestRecord.self)) == true
69
+ expect(record?.foo) == "bar"
70
+ }
43
71
  }
44
72
  describe("EitherOfThree") {
45
73
  it("is the third type") {