react-native-nitro-modules 0.4.0 → 0.5.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 (34) hide show
  1. package/NitroModules.podspec +3 -1
  2. package/README.md +32 -1
  3. package/android/gradle.properties +1 -1
  4. package/cpp/core/ArrayBuffer.cpp +88 -0
  5. package/cpp/core/ArrayBuffer.hpp +31 -70
  6. package/cpp/jsi/JSIConverter+Function.hpp +3 -3
  7. package/cpp/jsi/JSIConverter+Promise.hpp +2 -1
  8. package/ios/core/ArrayBufferHolder.hpp +78 -0
  9. package/ios/core/ArrayBufferHolder.swift +48 -0
  10. package/ios/core/Promise.swift +162 -0
  11. package/ios/core/PromiseHolder.hpp +82 -0
  12. package/ios/turbomodule/NitroModuleOnLoad.mm +5 -0
  13. package/ios/utils/ClosureWrapper.swift +45 -0
  14. package/lib/tsconfig.tsbuildinfo +1 -1
  15. package/package.json +1 -1
  16. package/ios/core/HybridContext.cpp +0 -8
  17. package/ios/core/Promise.cpp +0 -10
  18. package/ios/core/Promise.hpp +0 -43
  19. package/lib/typescript/AnyMap.d.ts +0 -17
  20. package/lib/typescript/AnyMap.d.ts.map +0 -1
  21. package/lib/typescript/HybridObject.d.ts +0 -83
  22. package/lib/typescript/HybridObject.d.ts.map +0 -1
  23. package/lib/typescript/ModuleNotFoundError.d.ts +0 -7
  24. package/lib/typescript/ModuleNotFoundError.d.ts.map +0 -1
  25. package/lib/typescript/NativeNitroModules.d.ts +0 -15
  26. package/lib/typescript/NativeNitroModules.d.ts.map +0 -1
  27. package/lib/typescript/NativeNitroModules.web.d.ts +0 -5
  28. package/lib/typescript/NativeNitroModules.web.d.ts.map +0 -1
  29. package/lib/typescript/NitroModules.d.ts +0 -26
  30. package/lib/typescript/NitroModules.d.ts.map +0 -1
  31. package/lib/typescript/__tests__/index.test.d.ts +0 -1
  32. package/lib/typescript/__tests__/index.test.d.ts.map +0 -1
  33. package/lib/typescript/index.d.ts +0 -4
  34. package/lib/typescript/index.d.ts.map +0 -1
@@ -37,7 +37,9 @@ Pod::Spec.new do |s|
37
37
  "cpp/utils/NitroHash.hpp",
38
38
  "cpp/utils/NitroDefines.hpp",
39
39
  # Public iOS-specific headers that will be exposed in modulemap (for Swift)
40
- "ios/core/HybridContext.hpp"
40
+ "ios/core/ArrayBufferHolder.hpp",
41
+ "ios/core/PromiseHolder.hpp",
42
+ "ios/core/HybridContext.hpp",
41
43
  ]
42
44
 
43
45
  s.pod_target_xcconfig = {
package/README.md CHANGED
@@ -111,67 +111,98 @@ The following C++ / JS types are supported out of the box:
111
111
  <tr>
112
112
  <th>JS Type</th>
113
113
  <th>C++ Type</th>
114
+ <th>Swift Type</th>
114
115
  </tr>
115
116
 
116
117
  <tr>
117
118
  <td><code>number</code></td>
118
119
  <td><code>double</code> / <code>int</code> / <code>float</code></td>
120
+ <td><code>Double</code> / <code>Int</code> / <code>Float</code></td>
119
121
  </tr>
120
122
  <tr>
121
123
  <td><code>boolean</code></td>
122
124
  <td><code>bool</code></td>
125
+ <td><code>Bool</code></td>
123
126
  </tr>
124
127
  <tr>
125
128
  <td><code>string</code></td>
126
129
  <td><code>std::string</code></td>
130
+ <td><code>String</code></td>
127
131
  </tr>
128
132
  <tr>
129
133
  <td><code>bigint</code></td>
130
134
  <td><code>int64_t</code> / <code>uint64_t</code></td>
135
+ <td><code>Int64</code> / <code>UInt64</code></td>
131
136
  </tr>
132
137
  <tr>
133
138
  <td><code>T[]</code></td>
134
139
  <td><code>std::vector&lt;T&gt;</code></td>
140
+ <td><code>[T]</code></td>
135
141
  </tr>
136
142
  <tr>
137
143
  <td><code>[A, B, C, ...]</code></td>
138
144
  <td><code>std::tuple&lt;A, B, C, ...&gt;</code></td>
145
+ <td><code>(A, B, C)</code></td>
139
146
  </tr>
140
147
  <tr>
141
148
  <td><code>A | B | C | ...</code></td>
142
149
  <td><code>std::variant&lt;A, B, C, ...&gt;</code></td>
150
+ <td><code>Variant_A_B_C</code></td>
143
151
  </tr>
144
152
  <tr>
145
153
  <td><code>Record&lt;string, T&gt;</code></td>
146
154
  <td><code>std::unordered_map&lt;std::string, T&gt;</code></td>
155
+ <td><code>Dictionary&lt;String, T&gt;</code></td>
147
156
  </tr>
148
157
  <tr>
149
158
  <td><code>T?</code></td>
150
159
  <td><code>std::optional&lt;T&gt;</code></td>
160
+ <td><code>T?</code></td>
151
161
  </tr>
152
162
  <tr>
153
163
  <td><code>Promise&lt;T&gt;</code></td>
154
164
  <td><code>std::future&lt;T&gt;</code></td>
165
+ <td><code><a href="./ios/core/Promise.swift">Promise&lt;T&gt;</a></code></td>
155
166
  </tr>
156
167
  <tr>
157
168
  <td><code>(TArgs...) =&gt; void</code></td>
158
169
  <td><code>std::function&lt;void (TArgs...)&gt;</code></td>
170
+ <td><code>@escaping (TArgs...) -&gt; Void</code></td>
159
171
  </tr>
160
172
  <tr>
161
173
  <td><code>(TArgs...) =&gt; TReturn</code></td>
162
174
  <td><code>std::function&lt;std::future&lt;TReturn&gt; (TArgs...)&gt;</code></td>
175
+ <td>❌</td>
163
176
  </tr>
164
177
  <tr>
165
178
  <td><code>{ ... }</code></td>
166
179
  <td><code>std::shared_ptr&lt;<a href="./cpp/core/AnyMap.hpp">AnyMap</a>&gt;</code></td>
180
+ <td>❌</td>
167
181
  </tr>
168
182
  <tr>
169
183
  <td><code>ArrayBuffer</code></td>
170
184
  <td><code>std::shared_ptr&lt;<a href="./cpp/core/ArrayBuffer.hpp">ArrayBuffer</a>&gt;</code></td>
185
+ <td><code><a href="./ios/core/ArrayBufferHolder.hpp">ArrayBufferHolder</a></code></td>
171
186
  </tr>
172
187
  <tr>
173
- <td><code><a href="./src/HybridObject.ts">HybridObject</a></code></td>
188
+ <td>..any <code><a href="./src/HybridObject.ts">HybridObject</a></code></td>
174
189
  <td><code>std::shared_ptr&lt;<a href="./cpp/core/HybridObject.hpp">HybridObject</a>&gt;</code></td>
190
+ <td><code><a href="./ios/core/HybridObjectSpec.swift">HybridObjectSpec</a></code></td>
191
+ </tr>
192
+ <tr>
193
+ <td>..any <code>interface</code></td>
194
+ <td><code>T</code></td>
195
+ <td><code>T</code></td>
196
+ </tr>
197
+ <tr>
198
+ <td>..any <code>enum</code></td>
199
+ <td><code>T</code></td>
200
+ <td><code>T</code></td>
201
+ </tr>
202
+ <tr>
203
+ <td>..any <code>union</code></td>
204
+ <td><code>T</code></td>
205
+ <td><code>T</code></td>
175
206
  </tr>
176
207
  </table>
177
208
 
@@ -2,4 +2,4 @@ Nitro_kotlinVersion=1.7.0
2
2
  Nitro_minSdkVersion=21
3
3
  Nitro_targetSdkVersion=31
4
4
  Nitro_compileSdkVersion=31
5
- Nitro_ndkversion=21.4.7075529
5
+ Nitro_ndkVersion=21.4.7075529
@@ -0,0 +1,88 @@
1
+ //
2
+ // ArrayBuffer.cpp
3
+ // react-native-nitro
4
+ //
5
+ // Created by Marc Rousavy on 14.07.24.
6
+ //
7
+
8
+ #include "ArrayBuffer.hpp"
9
+ #include "OwningReference.hpp"
10
+ #include <functional>
11
+ #include <jsi/jsi.h>
12
+ #include <thread>
13
+
14
+ namespace margelo::nitro {
15
+
16
+ using namespace facebook;
17
+
18
+ // 1. ArrayBuffer
19
+
20
+ std::shared_ptr<ArrayBuffer> ArrayBuffer::makeBuffer(uint8_t* data, size_t size, DeleteFn deleteFunc, void* deleteFuncContext) {
21
+ return std::make_shared<NativeArrayBuffer>(data, size, deleteFunc, deleteFuncContext);
22
+ }
23
+
24
+ // 2. NativeArrayBuffer
25
+
26
+ NativeArrayBuffer::NativeArrayBuffer(uint8_t* data, size_t size, DeleteFn deleteFunc, void* deleteFuncContext)
27
+ : ArrayBuffer(), _data(data), _size(size), _deleteFunc(deleteFunc), _deleteFuncContext(deleteFuncContext) {}
28
+
29
+ NativeArrayBuffer::~NativeArrayBuffer() {
30
+ if (_deleteFunc != nullptr) {
31
+ _deleteFunc(_deleteFuncContext);
32
+ }
33
+ }
34
+
35
+ uint8_t* NativeArrayBuffer::data() {
36
+ return _data;
37
+ }
38
+
39
+ size_t NativeArrayBuffer::size() const {
40
+ return _size;
41
+ }
42
+
43
+ bool NativeArrayBuffer::isOwner() const noexcept {
44
+ return _deleteFunc != nullptr;
45
+ }
46
+
47
+ // 3. JSArrayBuffer
48
+
49
+ JSArrayBuffer::JSArrayBuffer(jsi::Runtime* runtime, OwningReference<jsi::ArrayBuffer> jsReference)
50
+ : ArrayBuffer(), _runtime(runtime), _jsReference(jsReference), _initialThreadId(std::this_thread::get_id()) {}
51
+
52
+ JSArrayBuffer::~JSArrayBuffer() {}
53
+
54
+ uint8_t* JSArrayBuffer::data() {
55
+ if (_initialThreadId != std::this_thread::get_id()) [[unlikely]] {
56
+ throw std::runtime_error("`data()` can only be accessed synchronously on the JS Thread! "
57
+ "If you want to access it elsewhere, copy it first.");
58
+ }
59
+
60
+ OwningLock<jsi::ArrayBuffer> lock = _jsReference.lock();
61
+ if (!_jsReference) [[unlikely]] {
62
+ // JS Part has been deleted - data is now nullptr.
63
+ return nullptr;
64
+ }
65
+ // JS Part is still alive - we can assume that the jsi::Runtime is safe to access here too.
66
+ return _jsReference->data(*_runtime);
67
+ }
68
+
69
+ size_t JSArrayBuffer::size() const {
70
+ if (_initialThreadId != std::this_thread::get_id()) [[unlikely]] {
71
+ throw std::runtime_error("`size()` can only be accessed synchronously on the JS Thread! "
72
+ "If you want to access it elsewhere, copy it first.");
73
+ }
74
+
75
+ OwningLock<jsi::ArrayBuffer> lock = _jsReference.lock();
76
+ if (!_jsReference) [[unlikely]] {
77
+ // JS Part has been deleted - size is now 0.
78
+ return 0;
79
+ }
80
+ // JS Part is still alive - we can assume that the jsi::Runtime is safe to access here too.
81
+ return _jsReference->size(*_runtime);
82
+ }
83
+
84
+ bool JSArrayBuffer::isOwner() const noexcept {
85
+ return false;
86
+ }
87
+
88
+ } // namespace margelo::nitro
@@ -8,7 +8,6 @@
8
8
  #pragma once
9
9
 
10
10
  #include "OwningReference.hpp"
11
- #include <functional>
12
11
  #include <jsi/jsi.h>
13
12
  #include <thread>
14
13
 
@@ -16,9 +15,7 @@ namespace margelo::nitro {
16
15
 
17
16
  using namespace facebook;
18
17
 
19
- using DeleteFn = std::function<void(uint8_t*)>;
20
-
21
- static DeleteFn defaultDeleteFn = [](uint8_t* buffer) { delete[] buffer; };
18
+ using DeleteFn = void (*)(void* context);
22
19
 
23
20
  /**
24
21
  * Represents a raw byte buffer that can be read from-, and
@@ -41,7 +38,7 @@ public:
41
38
  ArrayBuffer(const ArrayBuffer&) = delete;
42
39
  ArrayBuffer(ArrayBuffer&&) = delete;
43
40
  virtual ~ArrayBuffer() = default;
44
-
41
+
45
42
  public:
46
43
  /**
47
44
  * Returns whether this `ArrayBuffer` is actually owning the data,
@@ -49,6 +46,13 @@ public:
49
46
  * memory that we didn't allocate, or from JS - which can be deleted at any point).
50
47
  */
51
48
  virtual bool isOwner() const noexcept = 0;
49
+
50
+ public:
51
+ /**
52
+ * Create a new `NativeArrayBuffer` that wraps the given data (without copy) of the given size,
53
+ * and calls `deleteFunc` with the given `deleteFuncContext` as a parameter in which `data` should be deleted.
54
+ */
55
+ static std::shared_ptr<ArrayBuffer> makeBuffer(uint8_t* data, size_t size, DeleteFn deleteFunc, void* deleteFuncContext);
52
56
  };
53
57
 
54
58
  /**
@@ -62,46 +66,31 @@ public:
62
66
  *
63
67
  * It is safe to access `data()` and `size()` from any Thread, but there are no synchronization/mutexes implemented by default.
64
68
  */
65
- class NativeArrayBuffer : public ArrayBuffer {
69
+ class NativeArrayBuffer final : public ArrayBuffer {
66
70
  public:
67
71
  /**
68
72
  * Create a new **owning** `ArrayBuffer`.
69
73
  * The `ArrayBuffer` can be kept in memory, as C++ owns the data
70
- * and will only delete it once this `ArrayBuffer` gets deleted
71
- */
72
- NativeArrayBuffer(uint8_t* data, size_t size, DeleteFn&& deleteFunc)
73
- : ArrayBuffer(), _data(data), _size(size), _deleteFunc(std::move(deleteFunc)) {}
74
- /**
75
- * Create a new `ArrayBuffer`.
76
- * If `destroyOnDeletion` is `true`, the `ArrayBuffer` is **owning**, otherwise it is **non-owning**.
77
- * The `ArrayBuffer` can only be safely kept in memory if it is owning (`isOwning()`).
74
+ * and will only delete it once this `ArrayBuffer` gets deleted.
75
+ *
76
+ * Once this `ArrayBuffer` goes out of scope, `deleteFunc` will be called.
77
+ * The caller is responsible for deleting the memory (`data` and `deleteFuncContext`) here.
78
78
  */
79
- NativeArrayBuffer(uint8_t* data, size_t size, bool destroyOnDeletion) : ArrayBuffer(), _data(data), _size(size) {
80
- _deleteFunc = destroyOnDeletion ? defaultDeleteFn : nullptr;
81
- }
82
-
83
- ~NativeArrayBuffer() {
84
- if (_deleteFunc != nullptr) {
85
- _deleteFunc(_data);
86
- }
87
- }
79
+ NativeArrayBuffer(uint8_t* data, size_t size, DeleteFn deleteFunc, void* deleteFuncContext);
80
+ ~NativeArrayBuffer();
88
81
 
89
- uint8_t* data() override {
90
- return _data;
91
- }
92
-
93
- size_t size() const override {
94
- return _size;
95
- }
82
+ public:
83
+ uint8_t* data() override;
84
+ size_t size() const override;
85
+ bool isOwner() const noexcept override;
96
86
 
97
- bool isOwner() const noexcept override {
98
- return _deleteFunc != nullptr;
99
- }
87
+ double something();
100
88
 
101
89
  private:
102
90
  uint8_t* _data;
103
91
  size_t _size;
104
92
  DeleteFn _deleteFunc;
93
+ void* _deleteFuncContext;
105
94
  };
106
95
 
107
96
  /**
@@ -115,52 +104,24 @@ private:
115
104
  *
116
105
  * If the JS ArrayBuffer (or it's JS Runtime) have already been deleted, `data()` returns `nullptr`.
117
106
  */
118
- class JSArrayBuffer : public ArrayBuffer {
107
+ class JSArrayBuffer final : public ArrayBuffer {
119
108
  public:
120
- explicit JSArrayBuffer(jsi::Runtime* runtime, OwningReference<jsi::ArrayBuffer> jsReference)
121
- : ArrayBuffer(), _runtime(runtime), _jsReference(jsReference), _initialThreadId(std::this_thread::get_id()) {}
122
- ~JSArrayBuffer() {}
109
+ explicit JSArrayBuffer(jsi::Runtime* runtime, OwningReference<jsi::ArrayBuffer> jsReference);
110
+ ~JSArrayBuffer();
123
111
 
124
112
  public:
125
113
  /**
126
114
  * Gets the data this `ArrayBuffer` points to, or `nullptr` if it has already been deleted.
127
115
  */
128
- uint8_t* data() override {
129
- if (_initialThreadId != std::this_thread::get_id()) [[unlikely]] {
130
- throw std::runtime_error("`data()` can only be accessed synchronously on the JS Thread! "
131
- "If you want to access it elsewhere, copy it first.");
132
- }
133
-
134
- OwningLock<jsi::ArrayBuffer> lock = _jsReference.lock();
135
- if (!_jsReference) [[unlikely]] {
136
- // JS Part has been deleted - data is now nullptr.
137
- return nullptr;
138
- }
139
- // JS Part is still alive - we can assume that the jsi::Runtime is safe to access here too.
140
- return _jsReference->data(*_runtime);
141
- }
142
-
116
+ uint8_t* data() override;
143
117
  /**
144
118
  * Gets the size of the data this `ArrayBuffer` points to, or `0` if it has already been deleted.
145
119
  */
146
- size_t size() const override {
147
- if (_initialThreadId != std::this_thread::get_id()) [[unlikely]] {
148
- throw std::runtime_error("`size()` can only be accessed synchronously on the JS Thread! "
149
- "If you want to access it elsewhere, copy it first.");
150
- }
151
-
152
- OwningLock<jsi::ArrayBuffer> lock = _jsReference.lock();
153
- if (!_jsReference) [[unlikely]] {
154
- // JS Part has been deleted - size is now 0.
155
- return 0;
156
- }
157
- // JS Part is still alive - we can assume that the jsi::Runtime is safe to access here too.
158
- return _jsReference->size(*_runtime);
159
- }
160
-
161
- bool isOwner() const noexcept override {
162
- return false;
163
- }
120
+ size_t size() const override;
121
+ /**
122
+ * Returns `false` for JS-based ArrayBuffers.
123
+ */
124
+ bool isOwner() const noexcept override;
164
125
 
165
126
  private:
166
127
  jsi::Runtime* _runtime;
@@ -67,9 +67,9 @@ struct JSIConverter<std::function<ReturnType(Args...)>> {
67
67
  };
68
68
  }
69
69
 
70
- static inline jsi::Value toJSI(jsi::Runtime& runtime, std::function<ReturnType(Args...)>&& function) {
71
- jsi::HostFunctionType jsFunction = [function = std::move(function)](jsi::Runtime& runtime, const jsi::Value& thisValue,
72
- const jsi::Value* args, size_t count) -> jsi::Value {
70
+ static inline jsi::Value toJSI(jsi::Runtime& runtime, const std::function<ReturnType(Args...)>& function) {
71
+ jsi::HostFunctionType jsFunction = [function](jsi::Runtime& runtime, const jsi::Value& thisValue,
72
+ const jsi::Value* args, size_t count) -> jsi::Value {
73
73
  if (count != sizeof...(Args)) [[unlikely]] {
74
74
  throw jsi::JSError(runtime, "Function expected " + std::to_string(sizeof...(Args)) + " arguments, but received " +
75
75
  std::to_string(count) + "!");
@@ -7,7 +7,8 @@
7
7
  // Forward declare a few of the common types that might have cyclic includes.
8
8
  namespace margelo::nitro {
9
9
  class Dispatcher;
10
- class Promise;
10
+
11
+ class JSPromise;
11
12
 
12
13
  template <typename T, typename Enable>
13
14
  struct JSIConverter;
@@ -0,0 +1,78 @@
1
+ //
2
+ // ArrayBufferHolder.hpp
3
+ // react-native-nitro
4
+ //
5
+ // Created by Marc Rousavy on 14.08.24.
6
+ //
7
+
8
+ #pragma once
9
+
10
+ #include "ArrayBuffer.hpp"
11
+ #include <swift/bridging>
12
+ #include <memory>
13
+
14
+ namespace margelo::nitro {
15
+
16
+ using namespace facebook;
17
+
18
+ /**
19
+ * Holds instances of `std::shared_ptr<ArrayBuffer>`.
20
+ * The reason this exists is because we cannot directly use `shared_ptr`,
21
+ * nor virtual functions (`jsi::MutableBuffer`) in Swift.
22
+ *
23
+ * Passing around instances of `ArrayBufferHolder` (or `std::shared_ptr<ArrayBuffer>`)
24
+ * does not involve any data copies and is almost zero-overhead - even when passed to JS.
25
+ */
26
+ class ArrayBufferHolder {
27
+ public:
28
+ ArrayBufferHolder(const std::shared_ptr<ArrayBuffer>& arrayBuffer) : _arrayBuffer(arrayBuffer) {}
29
+
30
+ public:
31
+ /**
32
+ * Create a new `NativeArrayBuffer` that wraps the given data of the given size, without copying it.
33
+ *
34
+ * Once the `ArrayBuffer` is no longer in use, the given `deleteFunc` will be called with the given `deleteFuncContext`
35
+ * as an argument. The caller is responsible for deleting `data` (and `deleteFuncContext`) once this is called.
36
+ */
37
+ static ArrayBufferHolder makeBuffer(uint8_t* data, size_t size, DeleteFn deleteFunc, void* deleteFuncContext) {
38
+ auto arrayBuffer = ArrayBuffer::makeBuffer(data, size, deleteFunc, deleteFuncContext);
39
+ return ArrayBufferHolder(arrayBuffer);
40
+ }
41
+
42
+ public:
43
+ /**
44
+ * Gets the raw bytes the underlying `ArrayBuffer` points to.
45
+ */
46
+ void* getData() const SWIFT_COMPUTED_PROPERTY {
47
+ return _arrayBuffer->data();
48
+ }
49
+ /**
50
+ * Gets the size of the raw bytes the underlying `ArrayBuffer` points to.
51
+ */
52
+ size_t getSize() const SWIFT_COMPUTED_PROPERTY {
53
+ return _arrayBuffer->size();
54
+ }
55
+
56
+ /**
57
+ * Whether the underlying `ArrayBuffer` actually owns the data it points to, or not.
58
+ *
59
+ * - If an `ArrayBuffer` owns the data, it is likely an ArrayBuffer created on the native side (C++/Swift).
60
+ * This means the `ArrayBuffer` is safe to access as long as you have a reference to it, and cannot be deleted otherwise.
61
+ * - If an `ArrayBuffer` doesn't own the data, it is likely an ArrayBuffer coming from JS.
62
+ * This means the `ArrayBuffer` is **NOT** safe to access outside of the synchronous function's scope.
63
+ * If you plan on hopping do a different Thread, or storing a long-lived reference to it, make sure to **copy** the data.
64
+ */
65
+ bool getIsOwner() const SWIFT_COMPUTED_PROPERTY {
66
+ return _arrayBuffer->isOwner();
67
+ }
68
+
69
+ public:
70
+ inline std::shared_ptr<ArrayBuffer> getArrayBuffer() const {
71
+ return _arrayBuffer;
72
+ }
73
+
74
+ private:
75
+ std::shared_ptr<ArrayBuffer> _arrayBuffer;
76
+ };
77
+
78
+ } // namespace margelo::nitro
@@ -0,0 +1,48 @@
1
+ //
2
+ // ArrayBufferHolder.swift
3
+ // NitroModules
4
+ //
5
+ // Created by Marc Rousavy on 17.07.24.
6
+ //
7
+
8
+ import Foundation
9
+
10
+ /**
11
+ * Holds instances of `std::shared_ptr<ArrayBuffer>`, which can be passed
12
+ * between native and JS **without copy**.
13
+ *
14
+ * See `data`, `size` and `isOwning`.
15
+ */
16
+ public typealias ArrayBufferHolder = margelo.nitro.ArrayBufferHolder
17
+
18
+ public extension ArrayBufferHolder {
19
+ /**
20
+ * Create a new `ArrayBufferHolder` that wraps the given `data` of the given `size`
21
+ * without performing a copy.
22
+ * When the `ArrayBuffer` is no longer used, `onDelete` will be called, in which
23
+ * you as a caller are responsible for deleting `data`.
24
+ */
25
+ static func wrap(dataWithoutCopy data: UnsafeMutablePointer<UInt8>,
26
+ size: Int,
27
+ onDelete delete: @escaping () -> Void) -> ArrayBufferHolder {
28
+ // Convert escaping Swift closure to a `void*`
29
+ let (wrappedClosure, context) = ClosureWrapper.wrap(closure: delete)
30
+ // Create ArrayBufferHolder with our wrapped Swift closure to make it callable as a C-function pointer
31
+ return ArrayBufferHolder.makeBuffer(data, size, wrappedClosure, context)
32
+ }
33
+
34
+ /**
35
+ * Allocate a new buffer of the given `size`.
36
+ * If `initializeToZero` is `true`, all bytes are set to `0`, otherwise they are left untouched.
37
+ */
38
+ static func allocate(size: Int, initializeToZero: Bool = false) -> ArrayBufferHolder {
39
+ let data = UnsafeMutablePointer<UInt8>.allocate(capacity: size)
40
+ if initializeToZero {
41
+ data.initialize(repeating: 0, count: size)
42
+ }
43
+
44
+ return ArrayBufferHolder.makeBuffer(data, size, { data in
45
+ data?.deallocate()
46
+ }, data)
47
+ }
48
+ }
@@ -0,0 +1,162 @@
1
+ //
2
+ // Promise.swift
3
+ // NitroModules
4
+ //
5
+ // Created by Marc Rousavy on 15.08.24.
6
+ //
7
+
8
+ import Foundation
9
+
10
+ /**
11
+ * Represents a Promise that can be passed to JS.
12
+ *
13
+ * Create a new Promise with the following APIs:
14
+ * - `Promise<T>.async { ... }` - Creates a new Promise that runs the given code in a Swift `async`/`await` Task.
15
+ * - `Promise<T>.parallel { ... }` - Creates a new Promise that runs the given code in a parallel `DispatchQueue`.
16
+ * - `Promise<T>.resolved(withResult:)` - Creates a new already resolved Promise.
17
+ * - `Promise<T>.rejected(withError:)` - Creates a new already rejected Promise.
18
+ * - `Promise<T>()` - Creates a new Promise with fully manual control over the `resolve(..)`/`reject(..)` functions.
19
+ */
20
+ public class Promise<T> {
21
+ private enum State {
22
+ case result(T)
23
+ case error(Error)
24
+ }
25
+
26
+ private var state: State?
27
+ private var onResolvedListeners: [(T) -> Void] = []
28
+ private var onRejectedListeners: [(Error) -> Void] = []
29
+
30
+ /**
31
+ * Create a new pending Promise.
32
+ * It can (and must) be resolved **or** rejected later.
33
+ */
34
+ public init() {
35
+ state = nil
36
+ }
37
+
38
+ deinit {
39
+ if state == nil {
40
+ print("⚠️ Promise<\(String(describing: T.self))> got destroyed, but was never resolved or rejected! It is probably left hanging in JS now.")
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Resolves this `Promise<T>` with the given `T` and notifies all listeners.
46
+ */
47
+ public func resolve(withResult result: T) {
48
+ guard state == nil else {
49
+ fatalError("Failed to resolve promise with \(result) - it has already been resolved or rejected!")
50
+ }
51
+ state = .result(result)
52
+ onResolvedListeners.forEach { listener in listener(result) }
53
+ }
54
+
55
+ /**
56
+ * Rejects this `Promise<T>` with the given `Error` and notifies all listeners.
57
+ */
58
+ public func reject(withError error: Error) {
59
+ guard state == nil else {
60
+ fatalError("Failed to reject promise with \(error) - it has already been resolved or rejected!")
61
+ }
62
+ state = .error(error)
63
+ onRejectedListeners.forEach { listener in listener(error) }
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Extensions to easily create new Promises.
69
+ */
70
+ extension Promise {
71
+ /**
72
+ * Create a new `Promise<T>` already resolved with the given `T`.
73
+ */
74
+ public static func resolved(withResult result: T) -> Promise {
75
+ let promise = Promise()
76
+ promise.state = .result(result)
77
+ return promise
78
+ }
79
+
80
+ /**
81
+ * Create a new `Promise<T>` already rejected with the given `Error`.
82
+ */
83
+ public static func rejected(withError error: Error) -> Promise {
84
+ let promise = Promise()
85
+ promise.state = .error(error)
86
+ return promise
87
+ }
88
+
89
+ /**
90
+ * Create a new `Promise<T>` that runs the given `async` code in a `Task`.
91
+ * This does not necessarily run the code in a different Thread, but supports Swift's `async`/`await`.
92
+ */
93
+ public static func `async`(_ priority: TaskPriority? = nil,
94
+ _ run: @escaping () async throws -> T) -> Promise {
95
+ let promise = Promise()
96
+ Task(priority: priority) {
97
+ do {
98
+ let result = try await run()
99
+ promise.resolve(withResult: result)
100
+ } catch {
101
+ promise.reject(withError: error)
102
+ }
103
+ }
104
+ return promise
105
+ }
106
+
107
+ /**
108
+ * Create a new `Promise<T>` that runs the given `run` function on a parallel Thread/`DispatchQueue`.
109
+ */
110
+ public static func parallel(_ queue: DispatchQueue = .global(),
111
+ _ run: @escaping () throws -> T) -> Promise {
112
+ let promise = Promise()
113
+ queue.async {
114
+ do {
115
+ let result = try run()
116
+ promise.resolve(withResult: result)
117
+ } catch {
118
+ promise.reject(withError: error)
119
+ }
120
+ }
121
+ return promise
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Extensions to support then/catch syntax.
127
+ */
128
+ extension Promise {
129
+ /**
130
+ * Add a continuation listener to this `Promise<T>`.
131
+ * Once the `Promise<T>` resolves, the `onResolvedListener` will be called.
132
+ */
133
+ @discardableResult
134
+ public func then(_ onResolvedListener: @escaping (T) -> Void) -> Promise {
135
+ switch state {
136
+ case .result(let result):
137
+ onResolvedListener(result)
138
+ break
139
+ default:
140
+ onResolvedListeners.append(onResolvedListener)
141
+ break
142
+ }
143
+ return self
144
+ }
145
+
146
+ /**
147
+ * Add an error continuation listener to this `Promise<T>`.
148
+ * Once the `Promise<T>` rejects, the `onRejectedListener` will be called with the error.
149
+ */
150
+ @discardableResult
151
+ public func `catch`(_ onRejectedListener: @escaping (Error) -> Void) -> Promise {
152
+ switch state {
153
+ case .error(let error):
154
+ onRejectedListener(error)
155
+ break
156
+ default:
157
+ onRejectedListeners.append(onRejectedListener)
158
+ break
159
+ }
160
+ return self
161
+ }
162
+ }