@gjsify/napi 0.22.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.
- package/README.md +203 -0
- package/lib/esm/_virtual/_rolldown/runtime.js +1 -0
- package/lib/esm/index.js +1 -0
- package/lib/types/index.d.ts +25 -0
- package/meson.build +126 -0
- package/package.json +100 -0
- package/prebuilds/linux-x86_64/GjsifyNapi-1.0.gir +19 -0
- package/prebuilds/linux-x86_64/GjsifyNapi-1.0.typelib +0 -0
- package/prebuilds/linux-x86_64/libgjsifynapi.so +0 -0
- package/src/cc/arraybuffer.cc +772 -0
- package/src/cc/async_work.cc +439 -0
- package/src/cc/common.h +519 -0
- package/src/cc/env.cc +262 -0
- package/src/cc/error.cc +300 -0
- package/src/cc/function.cc +358 -0
- package/src/cc/module.cc +625 -0
- package/src/cc/napi.def +22 -0
- package/src/cc/napi.export.map +16 -0
- package/src/cc/napi.exported-symbols.txt +15 -0
- package/src/cc/object.cc +728 -0
- package/src/cc/ref.cc +510 -0
- package/src/cc/stubs.cc +100 -0
- package/src/cc/tsfn.cc +602 -0
- package/src/cc/value.cc +656 -0
- package/src/cc/wrap.cc +321 -0
- package/src/napi-headers/js_native_api.h +639 -0
- package/src/napi-headers/js_native_api_types.h +249 -0
- package/src/napi-headers/node_api.h +270 -0
- package/src/napi-headers/node_api_types.h +63 -0
- package/src/vala/gjs-1.0.vapi +6 -0
- package/src/vala/mozjs-140.vapi +6 -0
- package/src/vala/napi.vala +49 -0
package/src/cc/wrap.cc
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// @gjsify/napi — §5d wrap/unwrap/external/type tags: the uniform
|
|
3
|
+
// Reference-based lifecycle over the two-tier InstanceState design.
|
|
4
|
+
//
|
|
5
|
+
// Reference: refs/node/src/js_native_api_v8.cc (Node.js contributors, MIT) —
|
|
6
|
+
// v8impl::Wrap/Unwrap (cc:339-374, 525-586), napi_create_external
|
|
7
|
+
// (cc:2679-2708), type tags (cc:2710-2799). Two-tier storage (Bun's shape):
|
|
8
|
+
// - FAST TIER: instances of napi_define_class classes + externals carry
|
|
9
|
+
// reserved slot 0 = JS::PrivateValue(InstanceState*), freed by a
|
|
10
|
+
// FOREGROUND finalize hook that frees memory ONLY (no JSAPI, no user
|
|
11
|
+
// callbacks — legal during GC, incl. nursery deaths). Pattern: GJS
|
|
12
|
+
// cwrapper.h reserved slots + gi/object.cpp:3404-3407 foreground
|
|
13
|
+
// finalize.
|
|
14
|
+
// - FALLBACK TIER: foreign objects get the same InstanceState via an
|
|
15
|
+
// env-held WeakMap (JS::NewWeakMapObject/Get/SetWeakMapEntry,
|
|
16
|
+
// js/WeakMap.h) whose value is a hostdata_class carrier — ephemeron
|
|
17
|
+
// semantics keep the carrier (and so the InstanceState) alive exactly as
|
|
18
|
+
// long as the wrapped object, invisible to script.
|
|
19
|
+
// Lifecycle is uniform and lives in the wrap Reference, not the map: death
|
|
20
|
+
// → §5b weak sweep → §5c queue; the class finalize hook only frees the
|
|
21
|
+
// InstanceState struct.
|
|
22
|
+
|
|
23
|
+
#include "common.h"
|
|
24
|
+
|
|
25
|
+
namespace gjsify_napi {
|
|
26
|
+
|
|
27
|
+
// Frees memory only — runs during GC sweeping (JSFinalizeOp gets a
|
|
28
|
+
// JS::GCContext*, js/Class.h:301; foreground = main thread).
|
|
29
|
+
static void instance_state_finalize(JS::GCContext* /* gcx */, JSObject* obj) {
|
|
30
|
+
const JS::Value slot = JS::GetReservedSlot(obj, kInstanceSlotState);
|
|
31
|
+
if (!slot.isUndefined()) {
|
|
32
|
+
delete static_cast<InstanceState*>(slot.toPrivate());
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static const JSClassOps instance_state_class_ops = {
|
|
37
|
+
nullptr, // addProperty
|
|
38
|
+
nullptr, // delProperty
|
|
39
|
+
nullptr, // enumerate
|
|
40
|
+
nullptr, // newEnumerate
|
|
41
|
+
nullptr, // resolve
|
|
42
|
+
nullptr, // mayResolve
|
|
43
|
+
instance_state_finalize, // finalize
|
|
44
|
+
nullptr, // call
|
|
45
|
+
nullptr, // construct
|
|
46
|
+
nullptr, // trace
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const JSClass instance_class = {
|
|
50
|
+
"GjsifyNapiInstance",
|
|
51
|
+
JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_FOREGROUND_FINALIZE,
|
|
52
|
+
&instance_state_class_ops,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const JSClass external_class = {
|
|
56
|
+
"GjsifyNapiExternal",
|
|
57
|
+
JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_FOREGROUND_FINALIZE,
|
|
58
|
+
&instance_state_class_ops,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const JSClass hostdata_class = {
|
|
62
|
+
"GjsifyNapiHostData",
|
|
63
|
+
JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_FOREGROUND_FINALIZE,
|
|
64
|
+
&instance_state_class_ops,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
static bool has_state_slot(JSObject* obj) {
|
|
68
|
+
const JSClass* cls = JS::GetClass(obj);
|
|
69
|
+
return cls == &instance_class || cls == &external_class ||
|
|
70
|
+
cls == &hostdata_class;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
static InstanceState* state_from_slot(JSObject* obj, bool create) {
|
|
74
|
+
const JS::Value slot = JS::GetReservedSlot(obj, kInstanceSlotState);
|
|
75
|
+
if (!slot.isUndefined()) {
|
|
76
|
+
return static_cast<InstanceState*>(slot.toPrivate());
|
|
77
|
+
}
|
|
78
|
+
if (!create) {
|
|
79
|
+
return nullptr;
|
|
80
|
+
}
|
|
81
|
+
auto* state = new InstanceState();
|
|
82
|
+
JS::SetReservedSlot(obj, kInstanceSlotState, JS::PrivateValue(state));
|
|
83
|
+
return state;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
napi_status get_instance_state(napi_env env, JS::HandleObject obj,
|
|
87
|
+
bool create, InstanceState** out) {
|
|
88
|
+
*out = nullptr;
|
|
89
|
+
if (has_state_slot(obj)) {
|
|
90
|
+
*out = state_from_slot(obj, create);
|
|
91
|
+
return napi_ok;
|
|
92
|
+
}
|
|
93
|
+
// Foreign object → WeakMap tier.
|
|
94
|
+
if (!env->wrap_map) {
|
|
95
|
+
if (!create) {
|
|
96
|
+
return napi_ok;
|
|
97
|
+
}
|
|
98
|
+
JSObject* map = JS::NewWeakMapObject(env->cx);
|
|
99
|
+
if (map == nullptr) {
|
|
100
|
+
return set_last_error(env, napi_generic_failure);
|
|
101
|
+
}
|
|
102
|
+
env->wrap_map = map;
|
|
103
|
+
}
|
|
104
|
+
JS::RootedObject map(env->cx, env->wrap_map);
|
|
105
|
+
JS::RootedValue key(env->cx, JS::ObjectValue(*obj));
|
|
106
|
+
JS::RootedValue carrier_v(env->cx);
|
|
107
|
+
if (!JS::GetWeakMapEntry(env->cx, map, key, &carrier_v)) {
|
|
108
|
+
return set_last_error(env, napi_generic_failure);
|
|
109
|
+
}
|
|
110
|
+
if (carrier_v.isObject()) {
|
|
111
|
+
*out = state_from_slot(&carrier_v.toObject(), create);
|
|
112
|
+
return napi_ok;
|
|
113
|
+
}
|
|
114
|
+
if (!create) {
|
|
115
|
+
return napi_ok;
|
|
116
|
+
}
|
|
117
|
+
JS::RootedObject carrier(
|
|
118
|
+
env->cx, JS_NewObjectWithGivenProto(env->cx, &hostdata_class,
|
|
119
|
+
nullptr));
|
|
120
|
+
if (!carrier) {
|
|
121
|
+
return set_last_error(env, napi_generic_failure);
|
|
122
|
+
}
|
|
123
|
+
auto* state = new InstanceState();
|
|
124
|
+
JS::SetReservedSlot(carrier, kInstanceSlotState, JS::PrivateValue(state));
|
|
125
|
+
JS::RootedValue carrier_val(env->cx, JS::ObjectValue(*carrier));
|
|
126
|
+
if (!JS::SetWeakMapEntry(env->cx, map, key, carrier_val)) {
|
|
127
|
+
// Slot ownership already with the carrier; it dies unreferenced and
|
|
128
|
+
// its finalize hook frees the state.
|
|
129
|
+
return set_last_error(env, napi_generic_failure);
|
|
130
|
+
}
|
|
131
|
+
*out = state;
|
|
132
|
+
return napi_ok;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
} // namespace gjsify_napi
|
|
136
|
+
|
|
137
|
+
// ---- wrap / unwrap / remove_wrap (uniform lifecycle, cc:525-586) ----
|
|
138
|
+
|
|
139
|
+
napi_status NAPI_CDECL napi_wrap(napi_env env, napi_value js_object,
|
|
140
|
+
void* native_object,
|
|
141
|
+
node_api_basic_finalize basic_finalize_cb,
|
|
142
|
+
void* finalize_hint, napi_ref* result) {
|
|
143
|
+
GJSIFY_NAPI_PREAMBLE(env);
|
|
144
|
+
GJSIFY_NAPI_CHECK_ARG(env, js_object);
|
|
145
|
+
auto finalize_cb = reinterpret_cast<napi_finalize>(basic_finalize_cb);
|
|
146
|
+
JS::Value v = gjsify_napi::napi_value_to_js(js_object);
|
|
147
|
+
GJSIFY_NAPI_RETURN_STATUS_IF_FALSE(env, v.isObject(),
|
|
148
|
+
napi_object_expected);
|
|
149
|
+
JS::RootedObject obj(env->cx, &v.toObject());
|
|
150
|
+
if (result != nullptr) {
|
|
151
|
+
// The returned reference should be deleted via napi_delete_reference
|
|
152
|
+
// ONLY in response to the finalize callback — so one is required
|
|
153
|
+
// (cc:546-556).
|
|
154
|
+
GJSIFY_NAPI_CHECK_ARG(env, finalize_cb);
|
|
155
|
+
}
|
|
156
|
+
gjsify_napi::InstanceState* state = nullptr;
|
|
157
|
+
napi_status status =
|
|
158
|
+
gjsify_napi::get_instance_state(env, obj, /* create = */ true, &state);
|
|
159
|
+
if (status != napi_ok) {
|
|
160
|
+
return status;
|
|
161
|
+
}
|
|
162
|
+
// Double-wrap = napi_invalid_arg (cc:540-544).
|
|
163
|
+
if (state->wrap != nullptr) {
|
|
164
|
+
return gjsify_napi::set_last_error(env, napi_invalid_arg);
|
|
165
|
+
}
|
|
166
|
+
JS::RootedValue obj_v(env->cx, v);
|
|
167
|
+
gjsify_napi::Reference* ref = gjsify_napi::Reference::New(
|
|
168
|
+
env, obj_v, 0,
|
|
169
|
+
result != nullptr ? gjsify_napi::ReferenceOwnership::kUserland
|
|
170
|
+
: gjsify_napi::ReferenceOwnership::kRuntime,
|
|
171
|
+
finalize_cb, native_object, finalize_hint);
|
|
172
|
+
state->wrap = ref;
|
|
173
|
+
if (result != nullptr) {
|
|
174
|
+
*result = reinterpret_cast<napi_ref>(ref);
|
|
175
|
+
}
|
|
176
|
+
return napi_ok; // preamble already cleared last_error
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
static napi_status unwrap_impl(napi_env env, napi_value js_object,
|
|
180
|
+
void** result, bool remove) {
|
|
181
|
+
GJSIFY_NAPI_CHECK_ENV(env);
|
|
182
|
+
GJSIFY_NAPI_CHECK_ARG(env, js_object);
|
|
183
|
+
GJSIFY_NAPI_CHECK_ARG(env, result);
|
|
184
|
+
JS::Value v = gjsify_napi::napi_value_to_js(js_object);
|
|
185
|
+
GJSIFY_NAPI_RETURN_STATUS_IF_FALSE(env, v.isObject(),
|
|
186
|
+
napi_object_expected);
|
|
187
|
+
JS::RootedObject obj(env->cx, &v.toObject());
|
|
188
|
+
gjsify_napi::InstanceState* state = nullptr;
|
|
189
|
+
napi_status status = gjsify_napi::get_instance_state(
|
|
190
|
+
env, obj, /* create = */ false, &state);
|
|
191
|
+
if (status != napi_ok) {
|
|
192
|
+
return status;
|
|
193
|
+
}
|
|
194
|
+
if (state == nullptr || state->wrap == nullptr) {
|
|
195
|
+
return gjsify_napi::set_last_error(env, napi_invalid_arg);
|
|
196
|
+
}
|
|
197
|
+
gjsify_napi::Reference* ref = state->wrap;
|
|
198
|
+
*result = ref->Data();
|
|
199
|
+
if (remove) {
|
|
200
|
+
state->wrap = nullptr;
|
|
201
|
+
// kRuntime (no napi_ref handed out): the runtime owns it — delete
|
|
202
|
+
// WITHOUT running the finalizer (the caller takes ownership back).
|
|
203
|
+
// kUserland: the ref stays; userland deletes it (cc:365-374).
|
|
204
|
+
if (ref->Ownership() == gjsify_napi::ReferenceOwnership::kRuntime) {
|
|
205
|
+
delete ref;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return gjsify_napi::clear_last_error(env);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
napi_status NAPI_CDECL napi_unwrap(napi_env env, napi_value obj,
|
|
212
|
+
void** result) {
|
|
213
|
+
return unwrap_impl(env, obj, result, /* remove = */ false);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
napi_status NAPI_CDECL napi_remove_wrap(napi_env env, napi_value obj,
|
|
217
|
+
void** result) {
|
|
218
|
+
return unwrap_impl(env, obj, result, /* remove = */ true);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ---- externals (cc:2679-2708) ----
|
|
222
|
+
|
|
223
|
+
napi_status NAPI_CDECL napi_create_external(napi_env env, void* data,
|
|
224
|
+
node_api_basic_finalize
|
|
225
|
+
basic_finalize_cb,
|
|
226
|
+
void* finalize_hint,
|
|
227
|
+
napi_value* result) {
|
|
228
|
+
GJSIFY_NAPI_PREAMBLE(env);
|
|
229
|
+
GJSIFY_NAPI_CHECK_ARG(env, result);
|
|
230
|
+
auto finalize_cb = reinterpret_cast<napi_finalize>(basic_finalize_cb);
|
|
231
|
+
JS::RootedObject external(
|
|
232
|
+
env->cx, JS_NewObjectWithGivenProto(env->cx,
|
|
233
|
+
&gjsify_napi::external_class,
|
|
234
|
+
nullptr));
|
|
235
|
+
if (!external) {
|
|
236
|
+
return gjsify_napi::set_last_error(env, napi_generic_failure);
|
|
237
|
+
}
|
|
238
|
+
auto* state = new gjsify_napi::InstanceState();
|
|
239
|
+
state->is_external = true;
|
|
240
|
+
state->external_data = data;
|
|
241
|
+
JS::SetReservedSlot(external, gjsify_napi::kInstanceSlotState,
|
|
242
|
+
JS::PrivateValue(state));
|
|
243
|
+
if (finalize_cb != nullptr) {
|
|
244
|
+
// kRuntime ReferenceWithFinalizer, weak refcount 0 — the uniform
|
|
245
|
+
// §5b/§5c death path.
|
|
246
|
+
JS::RootedValue ext_v(env->cx, JS::ObjectValue(*external));
|
|
247
|
+
gjsify_napi::Reference::New(env, ext_v, 0,
|
|
248
|
+
gjsify_napi::ReferenceOwnership::kRuntime,
|
|
249
|
+
finalize_cb, data, finalize_hint);
|
|
250
|
+
}
|
|
251
|
+
*result = gjsify_napi::arena_push(env, JS::ObjectValue(*external));
|
|
252
|
+
return napi_ok;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
napi_status NAPI_CDECL napi_get_value_external(napi_env env, napi_value value,
|
|
256
|
+
void** result) {
|
|
257
|
+
GJSIFY_NAPI_CHECK_ENV(env);
|
|
258
|
+
GJSIFY_NAPI_CHECK_ARG(env, value);
|
|
259
|
+
GJSIFY_NAPI_CHECK_ARG(env, result);
|
|
260
|
+
JS::Value v = gjsify_napi::napi_value_to_js(value);
|
|
261
|
+
GJSIFY_NAPI_RETURN_STATUS_IF_FALSE(
|
|
262
|
+
env,
|
|
263
|
+
v.isObject() &&
|
|
264
|
+
JS::GetClass(&v.toObject()) == &gjsify_napi::external_class,
|
|
265
|
+
napi_invalid_arg);
|
|
266
|
+
gjsify_napi::InstanceState* state =
|
|
267
|
+
gjsify_napi::state_from_slot(&v.toObject(), /* create = */ false);
|
|
268
|
+
*result = state != nullptr ? state->external_data : nullptr;
|
|
269
|
+
return gjsify_napi::clear_last_error(env);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ---- type tags (cc:2710-2799) ----
|
|
273
|
+
|
|
274
|
+
napi_status NAPI_CDECL napi_type_tag_object(napi_env env, napi_value object,
|
|
275
|
+
const napi_type_tag* type_tag) {
|
|
276
|
+
GJSIFY_NAPI_PREAMBLE(env);
|
|
277
|
+
GJSIFY_NAPI_CHECK_ARG(env, object);
|
|
278
|
+
GJSIFY_NAPI_CHECK_ARG(env, type_tag);
|
|
279
|
+
JS::Value v = gjsify_napi::napi_value_to_js(object);
|
|
280
|
+
GJSIFY_NAPI_RETURN_STATUS_IF_FALSE(env, v.isObject(),
|
|
281
|
+
napi_object_expected);
|
|
282
|
+
JS::RootedObject obj(env->cx, &v.toObject());
|
|
283
|
+
gjsify_napi::InstanceState* state = nullptr;
|
|
284
|
+
napi_status status =
|
|
285
|
+
gjsify_napi::get_instance_state(env, obj, /* create = */ true, &state);
|
|
286
|
+
if (status != napi_ok) {
|
|
287
|
+
return status;
|
|
288
|
+
}
|
|
289
|
+
// Tag-once: re-tagging is napi_invalid_arg (cc:2725-2735).
|
|
290
|
+
if (state->has_tag) {
|
|
291
|
+
return gjsify_napi::set_last_error(env, napi_invalid_arg);
|
|
292
|
+
}
|
|
293
|
+
state->tag = *type_tag;
|
|
294
|
+
state->has_tag = true;
|
|
295
|
+
return napi_ok;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
napi_status NAPI_CDECL napi_check_object_type_tag(napi_env env,
|
|
299
|
+
napi_value object,
|
|
300
|
+
const napi_type_tag* type_tag,
|
|
301
|
+
bool* result) {
|
|
302
|
+
GJSIFY_NAPI_CHECK_ENV(env);
|
|
303
|
+
GJSIFY_NAPI_CHECK_ARG(env, object);
|
|
304
|
+
GJSIFY_NAPI_CHECK_ARG(env, type_tag);
|
|
305
|
+
GJSIFY_NAPI_CHECK_ARG(env, result);
|
|
306
|
+
JS::Value v = gjsify_napi::napi_value_to_js(object);
|
|
307
|
+
GJSIFY_NAPI_RETURN_STATUS_IF_FALSE(env, v.isObject(),
|
|
308
|
+
napi_object_expected);
|
|
309
|
+
JS::RootedObject obj(env->cx, &v.toObject());
|
|
310
|
+
gjsify_napi::InstanceState* state = nullptr;
|
|
311
|
+
napi_status status = gjsify_napi::get_instance_state(
|
|
312
|
+
env, obj, /* create = */ false, &state);
|
|
313
|
+
if (status != napi_ok) {
|
|
314
|
+
return status;
|
|
315
|
+
}
|
|
316
|
+
// Compare both words; untagged → false (cc:2770-2799).
|
|
317
|
+
*result = state != nullptr && state->has_tag &&
|
|
318
|
+
state->tag.lower == type_tag->lower &&
|
|
319
|
+
state->tag.upper == type_tag->upper;
|
|
320
|
+
return gjsify_napi::clear_last_error(env);
|
|
321
|
+
}
|