@auto_js/napi 0.0.1

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 (49) hide show
  1. package/CMakeLists.txt +55 -0
  2. package/LICENSE +13 -0
  3. package/README.md +255 -0
  4. package/_module.cc +19 -0
  5. package/api/api.cc +10 -0
  6. package/api/callback_info.cc +41 -0
  7. package/api/finalizer.cc +44 -0
  8. package/api/handle_scope.cc +23 -0
  9. package/api/invoke.cc +99 -0
  10. package/api/uv_handle.cc +113 -0
  11. package/api/uv_scheduler.cc +56 -0
  12. package/api/uv_scheduler.h.cc +73 -0
  13. package/handle/bound_value.cc +49 -0
  14. package/handle/reference.cc +93 -0
  15. package/handle/remote.cc +92 -0
  16. package/handle/types.cc +136 -0
  17. package/handle/value.cc +52 -0
  18. package/include/napi_js_initialize.h +7 -0
  19. package/package.json +16 -0
  20. package/support/callback.cc +266 -0
  21. package/support/callback_storage.cc +64 -0
  22. package/support/class_template_storage.cc +62 -0
  23. package/support/container.cc +78 -0
  24. package/support/environment.cc +33 -0
  25. package/support/environment.h.cc +56 -0
  26. package/support/environment_fwd.cc +15 -0
  27. package/support/error_scope.cc +22 -0
  28. package/support/initialize.cc +25 -0
  29. package/support/initialize.h.cc +48 -0
  30. package/support/promise.cc +130 -0
  31. package/support/string_table.cc +45 -0
  32. package/support/utility.cc +76 -0
  33. package/transfer/accept.cc +349 -0
  34. package/transfer/visit.cc +268 -0
  35. package/upload/macro.jpg +0 -0
  36. package/value/array.cc +32 -0
  37. package/value/array.h.cc +55 -0
  38. package/value/class.cc +147 -0
  39. package/value/class.h.cc +32 -0
  40. package/value/dictionary.cc +33 -0
  41. package/value/dictionary.h.cc +56 -0
  42. package/value/external.cc +54 -0
  43. package/value/function.cc +52 -0
  44. package/value/function.h.cc +30 -0
  45. package/value/object.cc +30 -0
  46. package/value/object.h.cc +59 -0
  47. package/value/primitive.cc +160 -0
  48. package/value/primitive.h.cc +101 -0
  49. package/value/value.cc +10 -0
package/CMakeLists.txt ADDED
@@ -0,0 +1,55 @@
1
+ find_package_from_npm(@auto_js/js)
2
+ find_package_from_npm(@auto_js/napi_exports)
3
+
4
+ add_library(napi_js OBJECT)
5
+ target_compile_features(napi_js PUBLIC cxx_std_23)
6
+ target_include_directories(napi_js INTERFACE include)
7
+ target_link_libraries(napi_js PRIVATE auto_js nodejs utility_js)
8
+
9
+ target_sources(napi_js
10
+ PRIVATE
11
+ api/uv_scheduler.cc
12
+ support/environment.cc
13
+ support/initialize.cc
14
+ value/array.cc
15
+ value/dictionary.cc
16
+ value/object.cc
17
+ value/primitive.cc
18
+ PUBLIC FILE_SET CXX_MODULES FILES
19
+ _module.cc
20
+ api/api.cc
21
+ api/callback_info.cc
22
+ api/finalizer.cc
23
+ api/handle_scope.cc
24
+ api/invoke.cc
25
+ api/uv_handle.cc
26
+ api/uv_scheduler.h.cc
27
+ handle/bound_value.cc
28
+ handle/reference.cc
29
+ handle/remote.cc
30
+ handle/types.cc
31
+ handle/value.cc
32
+ support/callback_storage.cc
33
+ support/callback.cc
34
+ support/class_template_storage.cc
35
+ support/container.cc
36
+ support/environment_fwd.cc
37
+ support/environment.h.cc
38
+ support/error_scope.cc
39
+ support/initialize.h.cc
40
+ support/promise.cc
41
+ support/string_table.cc
42
+ support/utility.cc
43
+ transfer/accept.cc
44
+ transfer/visit.cc
45
+ value/array.h.cc
46
+ value/class.cc
47
+ value/class.h.cc
48
+ value/dictionary.h.cc
49
+ value/external.cc
50
+ value/function.cc
51
+ value/function.h.cc
52
+ value/object.h.cc
53
+ value/primitive.h.cc
54
+ value/value.cc
55
+ )
package/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2017 Marcel Laverdet
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any
4
+ purpose with or without fee is hereby granted, provided that the above
5
+ copyright notice and this permission notice appear in all copies.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
8
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
9
+ FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
10
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
11
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
12
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
13
+ PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,255 @@
1
+ [![npm version](https://badgen.now.sh/npm/v/@auto_js/napi)](https://www.npmjs.com/package/@auto_js/napi)
2
+ [![isc license](https://badgen.now.sh/npm/license/@auto_js/napi)](https://github.com/laverdet/isolated-vm/blob/experimental/LICENSE)
3
+ [![npm downloads](https://badgen.now.sh/npm/dm/@auto_js/napi)](https://www.npmjs.com/package/@auto_js/napi)
4
+
5
+ `@auto_js` -- Automatic C++ bindings for JavaScript
6
+ ===================================================
7
+
8
+ <img align="right" width="33%" height="33%" src="upload/macro.jpg">
9
+
10
+ I made this C++ template thing that converts JavaScript functions and values automatically.
11
+
12
+ ```c++
13
+ auto log(std::string message) -> void {
14
+ std::print("{}", message);
15
+ }
16
+
17
+ js::napi::napi_js_module module_namespace{
18
+ std::type_identity<environment>{},
19
+ [](environment& env) -> auto {
20
+ return std::tuple{
21
+ std::in_place,
22
+ std::pair{util::cw<"log">, js::free_function{log}},
23
+ };
24
+ }
25
+ };
26
+ ```
27
+
28
+ ## It's better than what you're writing by hand
29
+
30
+ ```c++
31
+ struct point {
32
+ int x;
33
+ int y;
34
+
35
+ constexpr static auto struct_template = js::struct_template{
36
+ js::struct_member{util::cw<"x">, &point::x},
37
+ js::struct_member{util::cw<"y">, &point::y},
38
+ };
39
+ };
40
+
41
+ auto take_points(std::vector<point> points) -> void {
42
+ for (const auto& [ x, y ] : points) {
43
+ std::print("({}, {})\n", x, y);
44
+ }
45
+ }
46
+
47
+ constexpr auto string_literals = std::tuple{
48
+ "x"sv,
49
+ "y"sv,
50
+ };
51
+
52
+ class environment
53
+ : public napi::environment,
54
+ public napi::string_table<string_literals> {
55
+ public:
56
+ using napi::environment::environment;
57
+ };
58
+
59
+ js::napi::napi_js_module module_namespace{
60
+ std::type_identity<environment>{},
61
+ [](environment& env) -> auto {
62
+ return std::tuple{
63
+ std::in_place,
64
+ std::pair{util::cw<"take_points">, js::free_function{take_points}},
65
+ };
66
+ }
67
+ };
68
+ ```
69
+
70
+ `@auto_js/napi` compiles this in a way such that the "x" and "y" strings each get their own
71
+ `v8::Persistent<v8::String>` handles. Furthermore, for each invocation to the translation layer
72
+ those strings only need to dereference the persistent handle once, then they're cached in
73
+ `v8::Local<v8::String>` handles. The upshot is that your code is not hammering the v8 runtime with
74
+ unneeded work.
75
+
76
+
77
+ Runtimes
78
+ ========
79
+
80
+ The core `@auto_js/js` module is runtime-agnostic. It describes *JavaScript* values in an abstract
81
+ way. A bunch of generic support code then provides the translation. The same support code which
82
+ translates an array of JavaScript arguments to C++ can be used to implement the structured clone
83
+ algorithm with a one-liner.
84
+
85
+ Modules `@auto_js/napi` and `@auto_js/v8` are provided which implement runtime support. The Napi
86
+ implementation is more complete than v8.
87
+
88
+
89
+ Transferable Types
90
+ ==================
91
+
92
+ ## Integrals
93
+
94
+ `bool` and `double` transfer exactly how you would expect.
95
+
96
+ `int32_t` and `uint32_t` will be coerced from the internal JavaScript `double` type. If the user
97
+ passes a value that is out of range an error will be thrown.
98
+
99
+
100
+ ## Strings
101
+
102
+ Strings are interesting. Internally, JavaScript specifies that strings are UTF-16. Runtimes
103
+ typically also provide an optimized "one byte" representation for ASCII strings. On the C++ side you
104
+ probably want UTF-8. So that's at least three types of strings you'll want to know how to handle.
105
+
106
+ You can accept `std::u16string` which will always work. You can also accept `std::u8string` which
107
+ will automatically interpolate the string from UTF-16 to UTF-8. Another option is `std::string`
108
+ which would be an optimized "one byte" ASCII string. This string type will throw if a string
109
+ contains non-ASCII data.
110
+
111
+
112
+ ## Bigints
113
+
114
+ You can accept `int64_t` or `uint64_t` for bigint types. These will throw if the bigint cannot be
115
+ coerced to the given C++ type. A utility class `js::bigint` is provided which can losslessly
116
+ represent any JavaScript bigint. Mathematical operations are not provided.
117
+
118
+
119
+ ## Dates
120
+
121
+ A `std::chrono`-compatible `js::js_clock` is provided. This losslessly represents a JavaScript date
122
+ using the same `double` value that the runtime uses. It provides `std::chrono::clock_cast` for
123
+ casting to other C++ clock ranges.
124
+
125
+
126
+ ## Optionals
127
+
128
+ `std::optional<T>` can be used on top of any other type to accept values of `undefined`.
129
+
130
+
131
+ ## Enumerations
132
+
133
+ Enumerated values can be represented as strings in JavaScript by declaring those strings statically in C++.
134
+
135
+ ```c++
136
+ enum class enum_test : std::int8_t {
137
+ first,
138
+ second,
139
+ third,
140
+ };
141
+
142
+ namespace js {
143
+ template <>
144
+ struct enum_values<enum_test> {
145
+ constexpr static auto values = std::array{
146
+ std::pair{"first", enum_test::first},
147
+ std::pair{"second", enum_test::second},
148
+ std::pair{"third", enum_test::third}
149
+ };
150
+ };
151
+ }
152
+ ```
153
+
154
+
155
+ ## Variants
156
+
157
+ `std::variant<std::u16string, double>` could be used to accept *either* a string or a number value.
158
+ You can mix and match types as needed.
159
+
160
+
161
+ ## Covariants
162
+
163
+ `std::variant<double, int32_t>` can be used to accept *either* a floating point or a signed integer.
164
+ Note that this uses runtime function calls to resolve the covariance. In v8 it will invoke
165
+ `value->IsInt32()` to discover what to return. Napi does not support this, so a `double` will always
166
+ be returned.
167
+
168
+ `std::variant<std::string, std::u16string>` can be used in a similar way to accept either a UTF-16
169
+ string or the optimized ASCII string. This uses `value->IsOneByte()` to resolve the covariance.
170
+ Again, in napi a UTF-16 string will always be returned.
171
+
172
+
173
+ ## Structures
174
+
175
+ `struct` & `class` types can be defined with either direct member access, or accessor functions.
176
+ Structures defined in this way are completely transferred to/from JavaScript. This isn't the "object
177
+ wrap" thing where a C++ value lives in JavaScript.
178
+
179
+ Direct member access:
180
+ ```c++
181
+ struct point {
182
+ int x;
183
+ int y;
184
+
185
+ constexpr static auto struct_template = js::struct_template{
186
+ js::struct_member{util::cw<"x">, &point::x},
187
+ js::struct_member{util::cw<"y">, &point::y},
188
+ };
189
+ };
190
+ ```
191
+
192
+ Getter & setter:
193
+ ```c++
194
+ struct point {
195
+ auto get_x() const -> int;
196
+ auto set_x(int x) const -> void;
197
+
198
+ constexpr static auto struct_template = js::struct_template{
199
+ // Getter and setter are both optional. Pass `nullptr` to define a type without a
200
+ // getter/setter. If an accessor is missing the type would only be transferable
201
+ // one way, for example a type missing a setter could only be `return`ed and not
202
+ // accepted as a function parameter.
203
+ js::struct_accessor{util::cw<"x">, util::fn<&point::get_x>, util::fn<&point::set_x>},
204
+ js::struct_member{util::cw<"y">, &point::y},
205
+ };
206
+ };
207
+ ```
208
+
209
+ ## Discriminated Unions
210
+
211
+ TypeScript-style [discriminated
212
+ unions](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types) can be
213
+ defined as a `std::variant` with a discriminant.
214
+
215
+ The following would accept either something like `{ type: "bear", honey: 1 }` or
216
+ `{ type: "horse", hay: 1 }`
217
+
218
+ ```c++
219
+ struct bear; // imagine it defines: honey
220
+ struct horse; // imagine it defines: hay
221
+
222
+ namespace js {
223
+ template <>
224
+ struct union_of<std::variant<bear, horse>> {
225
+ constexpr static auto& discriminant = util::cw<"type">;
226
+ constexpr static auto alternatives = std::tuple{
227
+ alternative<bear>{"bear"},
228
+ alternative<horse>{"horse"},
229
+ };
230
+ };
231
+ }
232
+ ```
233
+
234
+
235
+ ## Arrays
236
+
237
+ `std::vector<T>` will accept a JavaScript array of the given type.
238
+
239
+
240
+ ## Classes
241
+
242
+ "Object wrap" Napi functions are supported.
243
+
244
+ ```c++
245
+ export class database {
246
+ public:
247
+ using transfer_type = js::tagged_external<database>;
248
+
249
+ auto query(environment& env, std::string query) -> std::string;
250
+ static auto connect(environment& env, std::string uri) -> js::forward<js::napi::value<>>;
251
+ static auto class_template(environment& env) -> js::napi::value<class_tag_of<database>> {
252
+ // incomplete
253
+ }
254
+ };
255
+ ```
package/_module.cc ADDED
@@ -0,0 +1,19 @@
1
+ export module napi_js;
2
+ // nb: These must come first, for some reason
3
+ import :accept;
4
+ import :visit;
5
+ // Before these:
6
+ export import :api; // should be removed
7
+ export import :class_template_storage;
8
+ export import :environment;
9
+ export import :external;
10
+ export import :function;
11
+ export import :initialize;
12
+ export import :object;
13
+ export import :promise;
14
+ export import :reference;
15
+ export import :remote;
16
+ export import :string_table;
17
+ export import :value;
18
+ import :class_definitions;
19
+ import :function_definitions;
package/api/api.cc ADDED
@@ -0,0 +1,10 @@
1
+ export module napi_js:api;
2
+ // Internal nodejs API utilities which have no dependencies on anything else in `napi_js`
3
+ export import :api.callback_info;
4
+ export import :api.finalizer;
5
+ export import :api.handle_scope;
6
+ export import :api.invoke;
7
+ export import :api.uv_handle;
8
+ export import :api.uv_scheduler;
9
+ export import auto_js;
10
+ export import nodejs;
@@ -0,0 +1,41 @@
1
+ module;
2
+ #include <array>
3
+ #include <span>
4
+ #if _LIBCPP_VERSION
5
+ // clang 22.1.0: Without this I get a big horrible ICE in this file
6
+ #include <new>
7
+ #endif
8
+ export module napi_js:api.callback_info;
9
+ import :api.invoke;
10
+ import auto_js;
11
+ import util;
12
+
13
+ namespace js::napi {
14
+
15
+ // Helper which reads arguments from napi invocation
16
+ export struct callback_info : util::non_copyable {
17
+ public:
18
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
19
+ callback_info(napi_env env, napi_callback_info info) {
20
+ napi::invoke0(napi_get_cb_info, env, info, &count_, storage_.data(), &this_, &data_);
21
+ if (count_ > storage_.size()) {
22
+ throw js::range_error{u"Too many arguments provided for a function call"};
23
+ }
24
+ }
25
+
26
+ [[nodiscard]] auto arguments() const -> std::span<const napi_value> { return std::span{storage_}.first(count_); }
27
+ [[nodiscard]] auto data() const -> void* { return data_; }
28
+ [[nodiscard]] auto this_arg() const -> napi_value { return this_; }
29
+
30
+ private:
31
+ napi_value this_;
32
+ std::array<napi_value, 8> storage_;
33
+ std::size_t count_{storage_.size()};
34
+ void* data_;
35
+ };
36
+
37
+ // Common declaration for `napi_type_tag` by type
38
+ export template <class Type>
39
+ constexpr auto type_tag_for = napi_type_tag{.lower = util::type_hash<Type>, .upper = 0};
40
+
41
+ } // namespace js::napi
@@ -0,0 +1,44 @@
1
+ module;
2
+ #include <concepts>
3
+ #include <memory>
4
+ #include <type_traits>
5
+ export module napi_js:api.finalizer;
6
+ import nodejs;
7
+ import util;
8
+
9
+ namespace js::napi {
10
+
11
+ // Finalizer for basic unique_ptr
12
+ export template <class Type, class Deleter>
13
+ auto apply_finalizer(std::unique_ptr<Type, Deleter> unique, std::invocable<Type*, napi_finalize, void*> auto accept) -> decltype(auto)
14
+ requires std::is_empty_v<Deleter> {
15
+ // Finalizer uses a default-constructed deleter
16
+ const napi_finalize finalize = [](napi_env /*env*/, void* data, void* /*hint*/) -> void {
17
+ Deleter deleter{};
18
+ deleter(static_cast<Type*>(data));
19
+ };
20
+ auto result = util::regular_return{[ & ]() -> decltype(auto) {
21
+ return accept(unique.get(), finalize, nullptr);
22
+ }}();
23
+ unique.release();
24
+ return *std::move(result);
25
+ }
26
+
27
+ // Finalizer for shared_ptr
28
+ export template <class Type>
29
+ auto apply_finalizer(std::shared_ptr<Type> shared, std::invocable<Type*, napi_finalize, void*> auto accept) -> decltype(auto) {
30
+ // `shared_ptr<T>*` is passed as the finalizer hint
31
+ using shared_ptr_type = std::shared_ptr<Type>;
32
+ auto* ptr = shared.get();
33
+ auto ptr_ptr = std::make_unique<shared_ptr_type>(std::move(shared));
34
+ const napi_finalize finalize = [](napi_env /*env*/, void* /*data*/, void* hint) -> void {
35
+ delete static_cast<shared_ptr_type*>(hint);
36
+ };
37
+ auto result = util::regular_return{[ & ]() -> decltype(auto) {
38
+ return accept(ptr, finalize, ptr_ptr.get());
39
+ }}();
40
+ ptr_ptr.release();
41
+ return *std::move(result);
42
+ }
43
+
44
+ } // namespace js::napi
@@ -0,0 +1,23 @@
1
+ export module napi_js:api.handle_scope;
2
+ import :api.invoke;
3
+ import util;
4
+
5
+ namespace js::napi {
6
+
7
+ // RAII napi handle scope
8
+ export class handle_scope : util::non_moveable {
9
+ public:
10
+ explicit handle_scope(napi_env env) :
11
+ env_{env},
12
+ handle_scope_{napi::invoke(napi_open_handle_scope, env)} {}
13
+
14
+ ~handle_scope() {
15
+ napi::invoke0_noexcept(napi_close_handle_scope, env_, handle_scope_);
16
+ }
17
+
18
+ private:
19
+ napi_env env_;
20
+ napi_handle_scope handle_scope_;
21
+ };
22
+
23
+ } // namespace js::napi
package/api/invoke.cc ADDED
@@ -0,0 +1,99 @@
1
+ module;
2
+ #include <concepts>
3
+ #include <exception>
4
+ #include <optional>
5
+ #include <stdexcept>
6
+ #include <type_traits>
7
+ export module napi_js:api.invoke;
8
+ export import nodejs;
9
+ import util;
10
+
11
+ namespace js::napi {
12
+
13
+ // Thrown from `invoke` to indicate that a napi operation failed and there is an exception waiting
14
+ // on the JS stack.
15
+ export class pending_error : public std::exception {
16
+ public:
17
+ [[nodiscard]] auto what() const noexcept -> const char* final { return "[pending napi error]"; }
18
+ };
19
+
20
+ // Invoke the given napi function, returning void, or throw if it fails
21
+ export template <class... Params>
22
+ auto invoke0(auto(function)(Params...)->napi_status, auto&&... args) -> void
23
+ requires std::invocable<decltype(function), decltype(args)...> {
24
+ // nb: Don't lint on something like:
25
+ // `napi::invoke0(napi_throw_type_error, napi_env{env}, nullptr, "Illegal constructor");`
26
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
27
+ switch (function(std::forward<decltype(args)>(args)...)) {
28
+ case napi_ok: return;
29
+ case napi_pending_exception: throw napi::pending_error{};
30
+ default: throw std::runtime_error{"napi dispatch error"};
31
+ }
32
+ }
33
+
34
+ // Invoke the given napi function, returning a bool indicating success
35
+ export template <class... Params>
36
+ auto invoke0_maybe(auto(function)(Params...)->napi_status, auto&&... args) -> bool
37
+ requires std::invocable<decltype(function), decltype(args)...> {
38
+ switch (function(std::forward<decltype(args)>(args)...)) {
39
+ case napi_ok: return true;
40
+ case napi_pending_exception: return false;
41
+ default: throw std::runtime_error{"napi dispatch error"};
42
+ }
43
+ }
44
+
45
+ // Invoke the given napi function, returning void, or terminate if it fails.
46
+ export template <class... Params>
47
+ auto invoke0_noexcept(auto(function)(Params...)->napi_status, auto&&... args) noexcept -> void
48
+ requires std::invocable<decltype(function), decltype(args)...> {
49
+ if (function(std::forward<decltype(args)>(args)...) != napi_ok) {
50
+ std::terminate();
51
+ }
52
+ }
53
+
54
+ // Extract the `out` parameter type from a napi function signature
55
+ constexpr auto napi_invoke_out = [](auto... types) {
56
+ constexpr auto take = util::overloaded{
57
+ []<class Param>(std::type_identity<Param> /*type*/) -> void {
58
+ static_assert(false, "Last parameter is not a pointer");
59
+ },
60
+ []<class Param>(std::type_identity<Param*> /*type*/) -> auto {
61
+ return type<Param>;
62
+ },
63
+ };
64
+ return take(types...[ sizeof...(types) - 1 ]);
65
+ };
66
+
67
+ template <class... Params>
68
+ using napi_invoke_out_t = type_t<napi_invoke_out(type<Params>...)>;
69
+
70
+ // Invoke the given napi function and throw if it fails.
71
+ export template <class... Params>
72
+ auto invoke(auto(function)(Params...)->napi_status, auto&&... args)
73
+ requires std::invocable<decltype(function), decltype(args)..., napi_invoke_out_t<Params...>*> {
74
+ napi_invoke_out_t<Params...> out_arg;
75
+ napi::invoke0(function, std::forward<decltype(args)>(args)..., &out_arg);
76
+ return std::move(out_arg);
77
+ }
78
+
79
+ // Invoke the given napi function and return `optional<T>` indicating failure.
80
+ export template <class... Params>
81
+ auto invoke_maybe(auto(function)(Params...)->napi_status, auto&&... args)
82
+ requires std::invocable<decltype(function), decltype(args)..., napi_invoke_out_t<Params...>*> {
83
+ using out_type = napi_invoke_out_t<Params...>;
84
+ out_type out_arg;
85
+ return napi::invoke0_maybe(function, std::forward<decltype(args)>(args)..., &out_arg)
86
+ ? std::optional<out_type>{std::move(out_arg)}
87
+ : std::nullopt;
88
+ }
89
+
90
+ // Invoke the given napi function and terminate if it fails.
91
+ export template <class... Params>
92
+ auto invoke_noexcept(auto(function)(Params...)->napi_status, auto&&... args) noexcept
93
+ requires std::invocable<decltype(function), decltype(args)..., napi_invoke_out_t<Params...>*> {
94
+ napi_invoke_out_t<Params...> out_arg;
95
+ napi::invoke0_noexcept(function, std::forward<decltype(args)>(args)..., &out_arg);
96
+ return std::move(out_arg);
97
+ }
98
+
99
+ } // namespace js::napi
@@ -0,0 +1,113 @@
1
+ module;
2
+ #include <memory>
3
+ #include <stdexcept>
4
+ #include <type_traits>
5
+ #include <utility>
6
+ export module napi_js:api.uv_handle;
7
+ import nodejs;
8
+ import util;
9
+
10
+ namespace js::napi {
11
+
12
+ // Simple `uv_handle` wrapper which isolates all the pointer casting that needs to be done w/ the C
13
+ // API.
14
+ template <class Handle, class Type>
15
+ requires std::is_trivial_v<Type> && (sizeof(Type) == sizeof(Handle::data))
16
+ class uv_typed_handle {
17
+ public:
18
+ // NOLINTNEXTLINE(google-explicit-constructor)
19
+ operator Handle*() { return static_cast<Handle*>(&handle_); }
20
+ // NOLINTNEXTLINE(google-explicit-constructor)
21
+ operator uv_handle_t*() { return reinterpret_cast<uv_handle_t*>(&handle_); }
22
+ // (non-const) returns data lvalue
23
+ auto data() -> Type& {
24
+ void* ptr = static_cast<void*>(&handle_.data);
25
+ return *static_cast<Type*>(ptr);
26
+ }
27
+ // (const) returns data rvalue
28
+ [[nodiscard]] auto data() const -> Type { return static_cast<Type>(handle_.data); }
29
+ [[nodiscard]] auto loop() const -> uv_loop_t* { return handle_.loop; }
30
+ [[nodiscard]] auto type() const -> uv_handle_type { return handle_.type; }
31
+
32
+ private:
33
+ Handle handle_;
34
+ };
35
+
36
+ // `uv_handle_t` subtype wrapper with uv-owned shared memory. An open handle must be closed before
37
+ // the handle is destroyed or it is UB.
38
+ export template <class Handle, class Type>
39
+ class uv_handle_of : public util::pointer_facade {
40
+ private:
41
+ struct private_constructor {
42
+ explicit private_constructor() = default;
43
+ };
44
+ using shared_ptr_type = std::shared_ptr<uv_handle_of>;
45
+ using weak_ptr_type = std::weak_ptr<uv_handle_of>;
46
+ // An open handle's `data` pointer refers to a `shared_ptr` of itself. A closed handle refers to
47
+ // a `weak_ptr`, which will be deleted in the destructor.
48
+ union shared_or_weak_ptr {
49
+ shared_ptr_type* shared;
50
+ weak_ptr_type* weak;
51
+ };
52
+
53
+ public:
54
+ explicit uv_handle_of(const private_constructor& /*private*/, auto&&... args) :
55
+ value_{std::forward<decltype(args)>(args)...} {}
56
+ ~uv_handle_of();
57
+ uv_handle_of() = delete;
58
+ uv_handle_of(const uv_handle_of&) = delete;
59
+ auto operator=(const uv_handle_of&) -> uv_handle_of& = delete;
60
+
61
+ auto operator*() -> auto& { return value_; }
62
+ auto close() -> void;
63
+ auto handle() -> auto& { return handle_; }
64
+ auto open(const auto& init, uv_loop_t* loop, auto&&... args) -> void;
65
+
66
+ static auto make(auto&&... args) -> shared_ptr_type;
67
+ static auto unwrap(const Handle* handle) -> const shared_ptr_type&;
68
+
69
+ private:
70
+ uv_typed_handle<Handle, shared_or_weak_ptr> handle_;
71
+ Type value_;
72
+ };
73
+
74
+ // ---
75
+
76
+ template <class Handle, class Type>
77
+ uv_handle_of<Handle, Type>::~uv_handle_of() {
78
+ delete handle_.data().weak;
79
+ }
80
+
81
+ template <class Handle, class Type>
82
+ auto uv_handle_of<Handle, Type>::close() -> void {
83
+ uv_close(handle(), [](uv_handle_t* handle) -> void {
84
+ delete static_cast<shared_ptr_type*>(std::exchange(handle->data, nullptr));
85
+ });
86
+ }
87
+
88
+ template <class Handle, class Type>
89
+ auto uv_handle_of<Handle, Type>::open(const auto& init, uv_loop_t* loop, auto&&... args) -> void {
90
+ auto weak_ptr_ptr = std::unique_ptr<weak_ptr_type>{std::exchange(handle_.data().weak, nullptr)};
91
+ auto shared_ptr_ptr = std::make_unique<shared_ptr_type>(*weak_ptr_ptr);
92
+ if (init(loop, handle(), std::forward<decltype(args)>(args)...) == 0) {
93
+ handle_.data().shared = shared_ptr_ptr.release();
94
+ } else {
95
+ handle_.data().weak = weak_ptr_ptr.release();
96
+ throw std::runtime_error{"Failed to open async handle"};
97
+ }
98
+ }
99
+
100
+ template <class Handle, class Type>
101
+ auto uv_handle_of<Handle, Type>::make(auto&&... args) -> shared_ptr_type {
102
+ auto shared_with_block = std::make_shared<uv_handle_of>(private_constructor{}, std::forward<decltype(args)>(args)...);
103
+ auto weak_ptr_ptr = std::make_unique<weak_ptr_type>(shared_with_block);
104
+ shared_with_block->handle_.data().weak = weak_ptr_ptr.release();
105
+ return shared_with_block;
106
+ }
107
+
108
+ template <class Handle, class Type>
109
+ auto uv_handle_of<Handle, Type>::unwrap(const Handle* handle) -> const shared_ptr_type& {
110
+ return *static_cast<shared_ptr_type*>(handle->data);
111
+ }
112
+
113
+ } // namespace js::napi