@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.
- package/CMakeLists.txt +55 -0
- package/LICENSE +13 -0
- package/README.md +255 -0
- package/_module.cc +19 -0
- package/api/api.cc +10 -0
- package/api/callback_info.cc +41 -0
- package/api/finalizer.cc +44 -0
- package/api/handle_scope.cc +23 -0
- package/api/invoke.cc +99 -0
- package/api/uv_handle.cc +113 -0
- package/api/uv_scheduler.cc +56 -0
- package/api/uv_scheduler.h.cc +73 -0
- package/handle/bound_value.cc +49 -0
- package/handle/reference.cc +93 -0
- package/handle/remote.cc +92 -0
- package/handle/types.cc +136 -0
- package/handle/value.cc +52 -0
- package/include/napi_js_initialize.h +7 -0
- package/package.json +16 -0
- package/support/callback.cc +266 -0
- package/support/callback_storage.cc +64 -0
- package/support/class_template_storage.cc +62 -0
- package/support/container.cc +78 -0
- package/support/environment.cc +33 -0
- package/support/environment.h.cc +56 -0
- package/support/environment_fwd.cc +15 -0
- package/support/error_scope.cc +22 -0
- package/support/initialize.cc +25 -0
- package/support/initialize.h.cc +48 -0
- package/support/promise.cc +130 -0
- package/support/string_table.cc +45 -0
- package/support/utility.cc +76 -0
- package/transfer/accept.cc +349 -0
- package/transfer/visit.cc +268 -0
- package/upload/macro.jpg +0 -0
- package/value/array.cc +32 -0
- package/value/array.h.cc +55 -0
- package/value/class.cc +147 -0
- package/value/class.h.cc +32 -0
- package/value/dictionary.cc +33 -0
- package/value/dictionary.h.cc +56 -0
- package/value/external.cc +54 -0
- package/value/function.cc +52 -0
- package/value/function.h.cc +30 -0
- package/value/object.cc +30 -0
- package/value/object.h.cc +59 -0
- package/value/primitive.cc +160 -0
- package/value/primitive.h.cc +101 -0
- 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
|
+
[](https://www.npmjs.com/package/@auto_js/napi)
|
|
2
|
+
[](https://github.com/laverdet/isolated-vm/blob/experimental/LICENSE)
|
|
3
|
+
[](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
|
package/api/finalizer.cc
ADDED
|
@@ -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
|
package/api/uv_handle.cc
ADDED
|
@@ -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
|