mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
fix(napi): Make napi_wrap work on regular objects (#15622)
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
This commit is contained in:
@@ -1025,6 +1025,21 @@ extern "C" void napi_module_register(napi_module* mod)
|
||||
globalObject->m_pendingNapiModuleAndExports[1].set(vm, globalObject, object);
|
||||
}
|
||||
|
||||
static inline NapiRef* getWrapContentsIfExists(VM& vm, JSGlobalObject* globalObject, JSObject* object)
|
||||
{
|
||||
if (auto* napi_instance = jsDynamicCast<NapiPrototype*>(object)) {
|
||||
return napi_instance->napiRef;
|
||||
} else {
|
||||
JSValue contents = object->getDirect(vm, WebCore::builtinNames(vm).napiWrappedContentsPrivateName());
|
||||
if (contents.isEmpty()) {
|
||||
return nullptr;
|
||||
} else {
|
||||
// jsCast asserts: we should not have stored anything but a NapiExternal here
|
||||
return static_cast<NapiRef*>(jsCast<Bun::NapiExternal*>(contents)->value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" napi_status napi_wrap(napi_env env,
|
||||
napi_value js_object,
|
||||
void* native_object,
|
||||
@@ -1039,50 +1054,46 @@ extern "C" napi_status napi_wrap(napi_env env,
|
||||
{
|
||||
NAPI_PREMABLE
|
||||
|
||||
JSValue value = toJS(js_object);
|
||||
if (!value || value.isUndefinedOrNull()) {
|
||||
return napi_object_expected;
|
||||
}
|
||||
|
||||
auto* globalObject = toJS(env);
|
||||
|
||||
NapiRef** refPtr = nullptr;
|
||||
if (auto* val = jsDynamicCast<NapiPrototype*>(value)) {
|
||||
refPtr = &val->napiRef;
|
||||
} else if (auto* val = jsDynamicCast<NapiClass*>(value)) {
|
||||
refPtr = &val->napiRef;
|
||||
auto& vm = globalObject->vm();
|
||||
JSValue jsc_value = toJS(js_object);
|
||||
if (jsc_value.isEmpty()) {
|
||||
return napi_invalid_arg;
|
||||
}
|
||||
|
||||
if (!refPtr) {
|
||||
JSObject* jsc_object = jsc_value.getObject();
|
||||
if (!jsc_object) {
|
||||
return napi_object_expected;
|
||||
}
|
||||
|
||||
if (*refPtr) {
|
||||
// Calling napi_wrap() a second time on an object will return an error.
|
||||
// To associate another native instance with the object, use
|
||||
// napi_remove_wrap() first.
|
||||
// NapiPrototype has an inline field to store a napi_ref, so we use that if we can
|
||||
auto* napi_instance = jsDynamicCast<NapiPrototype*>(jsc_object);
|
||||
|
||||
const JSC::Identifier& propertyName = WebCore::builtinNames(vm).napiWrappedContentsPrivateName();
|
||||
|
||||
if (getWrapContentsIfExists(vm, globalObject, jsc_object)) {
|
||||
// already wrapped
|
||||
return napi_invalid_arg;
|
||||
}
|
||||
|
||||
// create a new weak reference (refcount 0)
|
||||
auto* ref = new NapiRef(globalObject, 0);
|
||||
ref->weakValueRef.set(jsc_value, weakValueHandleOwner(), ref);
|
||||
|
||||
ref->weakValueRef.set(value, weakValueHandleOwner(), ref);
|
||||
ref->finalizer.finalize_cb = finalize_cb;
|
||||
ref->finalizer.finalize_hint = finalize_hint;
|
||||
ref->data = native_object;
|
||||
|
||||
if (finalize_cb) {
|
||||
ref->finalizer.finalize_cb = finalize_cb;
|
||||
ref->finalizer.finalize_hint = finalize_hint;
|
||||
if (napi_instance) {
|
||||
napi_instance->napiRef = ref;
|
||||
} else {
|
||||
// wrap the ref in an external so that it can serve as a JSValue
|
||||
auto* external = Bun::NapiExternal::create(globalObject->vm(), globalObject->NapiExternalStructure(), ref, nullptr, nullptr);
|
||||
jsc_object->putDirect(vm, propertyName, JSValue(external));
|
||||
}
|
||||
|
||||
if (native_object) {
|
||||
ref->data = native_object;
|
||||
}
|
||||
|
||||
*refPtr = ref;
|
||||
|
||||
if (result) {
|
||||
*result = toNapi(ref);
|
||||
}
|
||||
|
||||
return napi_ok;
|
||||
}
|
||||
|
||||
@@ -1091,35 +1102,41 @@ extern "C" napi_status napi_remove_wrap(napi_env env, napi_value js_object,
|
||||
{
|
||||
NAPI_PREMABLE
|
||||
|
||||
JSValue value = toJS(js_object);
|
||||
if (!value || value.isUndefinedOrNull()) {
|
||||
JSValue jsc_value = toJS(js_object);
|
||||
if (jsc_value.isEmpty()) {
|
||||
return napi_invalid_arg;
|
||||
}
|
||||
JSObject* jsc_object = jsc_value.getObject();
|
||||
if (!js_object) {
|
||||
return napi_object_expected;
|
||||
}
|
||||
// may be null
|
||||
auto* napi_instance = jsDynamicCast<NapiPrototype*>(jsc_object);
|
||||
|
||||
NapiRef** refPtr = nullptr;
|
||||
if (auto* val = jsDynamicCast<NapiPrototype*>(value)) {
|
||||
refPtr = &val->napiRef;
|
||||
} else if (auto* val = jsDynamicCast<NapiClass*>(value)) {
|
||||
refPtr = &val->napiRef;
|
||||
auto* globalObject = toJS(env);
|
||||
auto& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
NapiRef* ref = getWrapContentsIfExists(vm, globalObject, jsc_object);
|
||||
|
||||
if (!ref) {
|
||||
return napi_invalid_arg;
|
||||
}
|
||||
|
||||
if (!refPtr) {
|
||||
return napi_object_expected;
|
||||
if (napi_instance) {
|
||||
napi_instance->napiRef = nullptr;
|
||||
} else {
|
||||
const JSC::Identifier& propertyName = WebCore::builtinNames(vm).napiWrappedContentsPrivateName();
|
||||
jsc_object->deleteProperty(globalObject, propertyName);
|
||||
}
|
||||
|
||||
if (!(*refPtr)) {
|
||||
// not sure if this should succeed or return an error
|
||||
return napi_ok;
|
||||
}
|
||||
|
||||
auto* ref = *refPtr;
|
||||
*refPtr = nullptr;
|
||||
|
||||
if (result) {
|
||||
*result = ref->data;
|
||||
}
|
||||
delete ref;
|
||||
ref->finalizer.finalize_cb = nullptr;
|
||||
|
||||
// don't delete the ref: if weak, it'll delete itself when the JS object is deleted;
|
||||
// if strong, native addon needs to clean it up.
|
||||
// the external is garbage collected.
|
||||
return napi_ok;
|
||||
}
|
||||
|
||||
@@ -1128,23 +1145,24 @@ extern "C" napi_status napi_unwrap(napi_env env, napi_value js_object,
|
||||
{
|
||||
NAPI_PREMABLE
|
||||
|
||||
JSValue value = toJS(js_object);
|
||||
|
||||
if (!value.isObject()) {
|
||||
return NAPI_OBJECT_EXPECTED;
|
||||
JSValue jsc_value = toJS(js_object);
|
||||
if (jsc_value.isEmpty()) {
|
||||
return napi_invalid_arg;
|
||||
}
|
||||
JSObject* jsc_object = jsc_value.getObject();
|
||||
if (!jsc_object) {
|
||||
return napi_object_expected;
|
||||
}
|
||||
|
||||
NapiRef* ref = nullptr;
|
||||
if (auto* val = jsDynamicCast<NapiPrototype*>(value)) {
|
||||
ref = val->napiRef;
|
||||
} else if (auto* val = jsDynamicCast<NapiClass*>(value)) {
|
||||
ref = val->napiRef;
|
||||
} else {
|
||||
ASSERT(false);
|
||||
auto* globalObject = toJS(env);
|
||||
auto& vm = globalObject->vm();
|
||||
NapiRef* ref = getWrapContentsIfExists(vm, globalObject, jsc_object);
|
||||
if (!ref) {
|
||||
return napi_invalid_arg;
|
||||
}
|
||||
|
||||
if (ref && result) {
|
||||
*result = ref ? ref->data : nullptr;
|
||||
if (result) {
|
||||
*result = ref->data;
|
||||
}
|
||||
|
||||
return napi_ok;
|
||||
|
||||
@@ -255,6 +255,7 @@ using namespace JSC;
|
||||
macro(writing) \
|
||||
macro(written) \
|
||||
macro(napiDlopenHandle) \
|
||||
macro(napiWrappedContents) \
|
||||
BUN_ADDITIONAL_BUILTIN_NAMES(macro)
|
||||
// --- END of BUN_COMMON_PRIVATE_IDENTIFIERS_EACH_PROPERTY_NAME ---
|
||||
|
||||
|
||||
BIN
test/bun.lockb
BIN
test/bun.lockb
Binary file not shown.
175
test/js/third_party/@napi-rs/canvas/.gitignore
vendored
Normal file
175
test/js/third_party/@napi-rs/canvas/.gitignore
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
|
||||
|
||||
# Logs
|
||||
|
||||
logs
|
||||
_.log
|
||||
npm-debug.log_
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Caches
|
||||
|
||||
.cache
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# Runtime data
|
||||
|
||||
pids
|
||||
_.pid
|
||||
_.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
|
||||
.temp
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
# IntelliJ based IDEs
|
||||
.idea
|
||||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
BIN
test/js/third_party/@napi-rs/canvas/expected.png
vendored
Normal file
BIN
test/js/third_party/@napi-rs/canvas/expected.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
BIN
test/js/third_party/@napi-rs/canvas/icon-small.png
vendored
Normal file
BIN
test/js/third_party/@napi-rs/canvas/icon-small.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
25
test/js/third_party/@napi-rs/canvas/napi-rs-canvas.test.ts
vendored
Normal file
25
test/js/third_party/@napi-rs/canvas/napi-rs-canvas.test.ts
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
// Create an image, then print it as binary to stdout
|
||||
import { createCanvas, loadImage } from "@napi-rs/canvas";
|
||||
import { Jimp } from "jimp";
|
||||
import { join } from "path";
|
||||
|
||||
describe("@napi-rs/canvas", () => {
|
||||
it("produces correct output", async () => {
|
||||
const canvas = createCanvas(200, 200);
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
ctx.lineWidth = 10;
|
||||
ctx.strokeStyle = "red";
|
||||
ctx.fillStyle = "blue";
|
||||
|
||||
ctx.fillRect(0, 0, 200, 200);
|
||||
ctx.strokeRect(50, 50, 100, 100);
|
||||
|
||||
const image = await loadImage(join(__dirname, "icon-small.png"));
|
||||
ctx.drawImage(image, 0, 0);
|
||||
|
||||
const expected = await Jimp.read(join(__dirname, "expected.png"));
|
||||
const actual = await Jimp.read(await canvas.encode("png"));
|
||||
expect(Array.from(actual.bitmap.data)).toEqual(Array.from(expected.bitmap.data));
|
||||
});
|
||||
});
|
||||
@@ -10,7 +10,7 @@
|
||||
"AdditionalOptions": ["/std:c++20"],
|
||||
},
|
||||
},
|
||||
"sources": ["main.cpp"],
|
||||
"sources": ["main.cpp", "wrap_tests.cpp"],
|
||||
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
|
||||
"libraries": [],
|
||||
"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
|
||||
@@ -18,6 +18,17 @@
|
||||
"NAPI_DISABLE_CPP_EXCEPTIONS",
|
||||
"NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT=1",
|
||||
],
|
||||
}
|
||||
},
|
||||
{
|
||||
"target_name": "second_addon",
|
||||
"sources": ["second_addon.c"],
|
||||
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
|
||||
"libraries": [],
|
||||
"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
|
||||
"defines": [
|
||||
"NAPI_DISABLE_CPP_EXCEPTIONS",
|
||||
"NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT=1",
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <node.h>
|
||||
|
||||
#include <napi.h>
|
||||
#include "napi_with_version.h"
|
||||
#include "utils.h"
|
||||
#include "wrap_tests.h"
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
@@ -30,23 +30,6 @@ napi_value fail_fmt(napi_env env, const char *fmt, ...) {
|
||||
return fail(env, buf);
|
||||
}
|
||||
|
||||
napi_value ok(napi_env env) {
|
||||
napi_value result;
|
||||
napi_get_undefined(env, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void run_gc(const Napi::CallbackInfo &info) {
|
||||
info[0].As<Napi::Function>().Call(0, nullptr);
|
||||
}
|
||||
|
||||
// calls napi_typeof and asserts it returns napi_ok
|
||||
static napi_valuetype get_typeof(napi_env env, napi_value value) {
|
||||
napi_valuetype result;
|
||||
assert(napi_typeof(env, value, &result) == napi_ok);
|
||||
return result;
|
||||
}
|
||||
|
||||
napi_value test_issue_7685(const Napi::CallbackInfo &info) {
|
||||
Napi::Env env(info.Env());
|
||||
Napi::HandleScope scope(env);
|
||||
@@ -595,33 +578,6 @@ napi_value was_finalize_called(const Napi::CallbackInfo &info) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const char *napi_valuetype_to_string(napi_valuetype type) {
|
||||
switch (type) {
|
||||
case napi_undefined:
|
||||
return "undefined";
|
||||
case napi_null:
|
||||
return "null";
|
||||
case napi_boolean:
|
||||
return "boolean";
|
||||
case napi_number:
|
||||
return "number";
|
||||
case napi_string:
|
||||
return "string";
|
||||
case napi_symbol:
|
||||
return "symbol";
|
||||
case napi_object:
|
||||
return "object";
|
||||
case napi_function:
|
||||
return "function";
|
||||
case napi_external:
|
||||
return "external";
|
||||
case napi_bigint:
|
||||
return "bigint";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
// calls a function (the sole argument) which must throw. catches and returns
|
||||
// the thrown error
|
||||
napi_value call_and_get_exception(const Napi::CallbackInfo &info) {
|
||||
@@ -1080,6 +1036,8 @@ Napi::Object InitAll(Napi::Env env, Napi::Object exports1) {
|
||||
exports.Set("try_add_tag", Napi::Function::New(env, try_add_tag));
|
||||
exports.Set("check_tag", Napi::Function::New(env, check_tag));
|
||||
|
||||
napitests::register_wrap_tests(env, exports);
|
||||
|
||||
return exports;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,30 @@
|
||||
const nativeTests = require("./build/Release/napitests.node");
|
||||
const secondAddon = require("./build/Release/second_addon.node");
|
||||
|
||||
function assert(ok) {
|
||||
if (!ok) {
|
||||
throw new Error("assertion failed");
|
||||
}
|
||||
}
|
||||
|
||||
async function gcUntil(fn) {
|
||||
const MAX = 100;
|
||||
for (let i = 0; i < MAX; i++) {
|
||||
await new Promise(resolve => {
|
||||
setTimeout(resolve, 1);
|
||||
});
|
||||
if (typeof Bun == "object") {
|
||||
Bun.gc(true);
|
||||
} else {
|
||||
// if this fails, you need to pass --expose-gc to node
|
||||
global.gc();
|
||||
}
|
||||
if (fn()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new Error(`Condition was not met after ${MAX} GC attempts`);
|
||||
}
|
||||
|
||||
nativeTests.test_napi_class_constructor_handle_scope = () => {
|
||||
const NapiClass = nativeTests.get_class_with_constructor();
|
||||
@@ -270,4 +296,198 @@ nativeTests.test_type_tag = () => {
|
||||
console.log("o2 matches o2:", nativeTests.check_tag(o2, 3, 4));
|
||||
};
|
||||
|
||||
nativeTests.test_napi_wrap = () => {
|
||||
const values = [
|
||||
{},
|
||||
{}, // should be able to be wrapped differently than the distinct empty object above
|
||||
5,
|
||||
new Number(5),
|
||||
"abc",
|
||||
new String("abc"),
|
||||
null,
|
||||
Symbol("abc"),
|
||||
Symbol.for("abc"),
|
||||
new (nativeTests.get_class_with_constructor())(),
|
||||
new Proxy(
|
||||
{},
|
||||
Object.fromEntries(
|
||||
[
|
||||
"apply",
|
||||
"construct",
|
||||
"defineProperty",
|
||||
"deleteProperty",
|
||||
"get",
|
||||
"getOwnPropertyDescriptor",
|
||||
"getPrototypeOf",
|
||||
"has",
|
||||
"isExtensible",
|
||||
"ownKeys",
|
||||
"preventExtensions",
|
||||
"set",
|
||||
"setPrototypeOf",
|
||||
].map(name => [
|
||||
name,
|
||||
() => {
|
||||
throw new Error("oops");
|
||||
},
|
||||
]),
|
||||
),
|
||||
),
|
||||
];
|
||||
const wrapSuccess = Array(values.length).fill(false);
|
||||
for (const [i, v] of values.entries()) {
|
||||
wrapSuccess[i] = nativeTests.try_wrap(v, i + 1);
|
||||
console.log(`${typeof v} did wrap: `, wrapSuccess[i]);
|
||||
}
|
||||
|
||||
for (const [i, v] of values.entries()) {
|
||||
if (wrapSuccess[i]) {
|
||||
if (nativeTests.try_unwrap(v) !== i + 1) {
|
||||
throw new Error("could not unwrap same value");
|
||||
}
|
||||
} else {
|
||||
if (nativeTests.try_unwrap(v) !== undefined) {
|
||||
throw new Error("value unwraps without being successfully wrapped");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
nativeTests.test_napi_wrap_proxy = () => {
|
||||
const target = {};
|
||||
const proxy = new Proxy(target, {});
|
||||
assert(nativeTests.try_wrap(target, 5));
|
||||
assert(nativeTests.try_wrap(proxy, 6));
|
||||
console.log(nativeTests.try_unwrap(target), nativeTests.try_unwrap(proxy));
|
||||
};
|
||||
|
||||
nativeTests.test_napi_wrap_cross_addon = () => {
|
||||
const wrapped = {};
|
||||
console.log("wrap succeeds:", nativeTests.try_wrap(wrapped, 42));
|
||||
console.log("unwrapped from other addon", secondAddon.try_unwrap(wrapped));
|
||||
};
|
||||
|
||||
nativeTests.test_napi_wrap_prototype = () => {
|
||||
class Foo {}
|
||||
console.log("wrap prototype succeeds:", nativeTests.try_wrap(Foo.prototype, 42));
|
||||
// wrapping should not look at prototype chain
|
||||
console.log("unwrap instance:", nativeTests.try_unwrap(new Foo()));
|
||||
};
|
||||
|
||||
nativeTests.test_napi_remove_wrap = () => {
|
||||
const targets = [{}, new (nativeTests.get_class_with_constructor())()];
|
||||
for (const t of targets) {
|
||||
const target = {};
|
||||
// fails
|
||||
assert(nativeTests.try_remove_wrap(target) === undefined);
|
||||
// wrap it
|
||||
assert(nativeTests.try_wrap(target, 5));
|
||||
// remove yields the wrapped value
|
||||
assert(nativeTests.try_remove_wrap(target) === 5);
|
||||
// neither remove nor unwrap work anymore
|
||||
assert(nativeTests.try_unwrap(target) === undefined);
|
||||
assert(nativeTests.try_remove_wrap(target) === undefined);
|
||||
// can re-wrap
|
||||
assert(nativeTests.try_wrap(target, 6));
|
||||
assert(nativeTests.try_unwrap(target) === 6);
|
||||
}
|
||||
};
|
||||
|
||||
// parameters to create_wrap are: object, ask_for_ref, strong
|
||||
const createWrapWithoutRef = o => nativeTests.create_wrap(o, false, false);
|
||||
const createWrapWithWeakRef = o => nativeTests.create_wrap(o, true, false);
|
||||
const createWrapWithStrongRef = o => nativeTests.create_wrap(o, true, true);
|
||||
|
||||
nativeTests.test_wrap_lifetime_without_ref = async () => {
|
||||
let object = { foo: "bar" };
|
||||
assert(createWrapWithoutRef(object) === object);
|
||||
assert(nativeTests.get_wrap_data(object) === 42);
|
||||
object = undefined;
|
||||
await gcUntil(() => nativeTests.was_wrap_finalize_called());
|
||||
};
|
||||
|
||||
nativeTests.test_wrap_lifetime_with_weak_ref = async () => {
|
||||
// this looks the same as test_wrap_lifetime_without_ref because it is -- these cases should behave the same
|
||||
let object = { foo: "bar" };
|
||||
assert(createWrapWithWeakRef(object) === object);
|
||||
assert(nativeTests.get_wrap_data(object) === 42);
|
||||
object = undefined;
|
||||
await gcUntil(() => nativeTests.was_wrap_finalize_called());
|
||||
};
|
||||
|
||||
nativeTests.test_wrap_lifetime_with_strong_ref = async () => {
|
||||
let object = { foo: "bar" };
|
||||
assert(createWrapWithStrongRef(object) === object);
|
||||
assert(nativeTests.get_wrap_data(object) === 42);
|
||||
|
||||
object = undefined;
|
||||
// still referenced by native module so this should fail
|
||||
try {
|
||||
await gcUntil(() => nativeTests.was_wrap_finalize_called());
|
||||
throw new Error("object was garbage collected while still referenced by native code");
|
||||
} catch (e) {
|
||||
if (!e.toString().includes("Condition was not met")) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// can still get the value using the ref
|
||||
assert(nativeTests.get_wrap_data_from_ref() === 42);
|
||||
|
||||
// now we free it
|
||||
nativeTests.unref_wrapped_value();
|
||||
await gcUntil(() => nativeTests.was_wrap_finalize_called());
|
||||
};
|
||||
|
||||
nativeTests.test_remove_wrap_lifetime_with_weak_ref = async () => {
|
||||
let object = { foo: "bar" };
|
||||
assert(createWrapWithWeakRef(object) === object);
|
||||
|
||||
assert(nativeTests.get_wrap_data(object) === 42);
|
||||
|
||||
nativeTests.remove_wrap(object);
|
||||
assert(nativeTests.get_wrap_data(object) === undefined);
|
||||
assert(nativeTests.get_wrap_data_from_ref() === undefined);
|
||||
assert(nativeTests.get_object_from_ref() === object);
|
||||
|
||||
object = undefined;
|
||||
|
||||
// ref will stop working once the object is collected
|
||||
await gcUntil(() => nativeTests.get_object_from_ref() === undefined);
|
||||
|
||||
// finalizer shouldn't have been called
|
||||
assert(nativeTests.was_wrap_finalize_called() === false);
|
||||
};
|
||||
|
||||
nativeTests.test_remove_wrap_lifetime_with_strong_ref = async () => {
|
||||
let object = { foo: "bar" };
|
||||
assert(createWrapWithStrongRef(object) === object);
|
||||
|
||||
assert(nativeTests.get_wrap_data(object) === 42);
|
||||
|
||||
nativeTests.remove_wrap(object);
|
||||
assert(nativeTests.get_wrap_data(object) === undefined);
|
||||
assert(nativeTests.get_wrap_data_from_ref() === undefined);
|
||||
assert(nativeTests.get_object_from_ref() === object);
|
||||
|
||||
object = undefined;
|
||||
|
||||
// finalizer should not be called and object should not be freed
|
||||
try {
|
||||
await gcUntil(() => nativeTests.was_wrap_finalize_called() || nativeTests.get_object_from_ref() === undefined);
|
||||
throw new Error("finalizer ran");
|
||||
} catch (e) {
|
||||
if (!e.toString().includes("Condition was not met")) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// native code can still get the object
|
||||
assert(JSON.stringify(nativeTests.get_object_from_ref()) === `{"foo":"bar"}`);
|
||||
|
||||
// now it gets deleted
|
||||
nativeTests.unref_wrapped_value();
|
||||
await gcUntil(() => nativeTests.get_object_from_ref() === undefined);
|
||||
};
|
||||
|
||||
module.exports = nativeTests;
|
||||
|
||||
8
test/napi/napi-app/napi_with_version.h
Normal file
8
test/napi/napi-app/napi_with_version.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
#define NAPI_EXPERIMENTAL
|
||||
#include <napi.h>
|
||||
#include <node.h>
|
||||
|
||||
// TODO(@190n): remove this when CI has Node 22.6
|
||||
typedef struct napi_env__ *napi_env;
|
||||
typedef napi_env node_api_basic_env;
|
||||
53
test/napi/napi-app/second_addon.c
Normal file
53
test/napi/napi-app/second_addon.c
Normal file
@@ -0,0 +1,53 @@
|
||||
#include <js_native_api.h>
|
||||
#include <node_api.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define NODE_API_CALL(env, call) \
|
||||
do { \
|
||||
napi_status status = (call); \
|
||||
if (status != napi_ok) { \
|
||||
const napi_extended_error_info *error_info = NULL; \
|
||||
napi_get_last_error_info((env), &error_info); \
|
||||
const char *err_message = error_info->error_message; \
|
||||
bool is_pending; \
|
||||
napi_is_exception_pending((env), &is_pending); \
|
||||
/* If an exception is already pending, don't rethrow it */ \
|
||||
if (!is_pending) { \
|
||||
const char *message = \
|
||||
(err_message == NULL) ? "empty error message" : err_message; \
|
||||
napi_throw_error((env), NULL, message); \
|
||||
} \
|
||||
return NULL; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static napi_value try_unwrap(napi_env env, napi_callback_info info) {
|
||||
size_t argc = 1;
|
||||
napi_value argv[1];
|
||||
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
|
||||
if (argc != 1) {
|
||||
napi_throw_error(env, NULL, "Wrong number of arguments to try_unwrap");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
double *pointer;
|
||||
if (napi_unwrap(env, argv[0], (void **)(&pointer)) != napi_ok) {
|
||||
napi_value undefined;
|
||||
NODE_API_CALL(env, napi_get_undefined(env, &undefined));
|
||||
return undefined;
|
||||
} else {
|
||||
napi_value number;
|
||||
NODE_API_CALL(env, napi_create_double(env, *pointer, &number));
|
||||
return number;
|
||||
}
|
||||
}
|
||||
|
||||
/* napi_value */ NAPI_MODULE_INIT(/* napi_env env, napi_value exports */) {
|
||||
napi_value try_unwrap_function;
|
||||
NODE_API_CALL(env,
|
||||
napi_create_function(env, "try_unwrap", NAPI_AUTO_LENGTH,
|
||||
try_unwrap, NULL, &try_unwrap_function));
|
||||
NODE_API_CALL(env, napi_set_named_property(env, exports, "try_unwrap",
|
||||
try_unwrap_function));
|
||||
return exports;
|
||||
}
|
||||
89
test/napi/napi-app/utils.h
Normal file
89
test/napi/napi-app/utils.h
Normal file
@@ -0,0 +1,89 @@
|
||||
#pragma once
|
||||
#include "napi_with_version.h"
|
||||
#include <climits>
|
||||
|
||||
// e.g NODE_API_CALL(env, napi_create_int32(env, 5, &my_napi_integer))
|
||||
#define NODE_API_CALL(env, call) NODE_API_CALL_CUSTOM_RETURN(env, NULL, call)
|
||||
|
||||
// Version of NODE_API_CALL for functions not returning napi_value
|
||||
#define NODE_API_CALL_CUSTOM_RETURN(env, value_to_return_if_threw, call) \
|
||||
NODE_API_ASSERT_CUSTOM_RETURN(env, value_to_return_if_threw, \
|
||||
(call) == napi_ok)
|
||||
|
||||
// Throw an error in the given napi_env and return if expr is false
|
||||
#define NODE_API_ASSERT(env, expr) \
|
||||
NODE_API_ASSERT_CUSTOM_RETURN(env, NULL, expr)
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define CURRENT_FUNCTION_NAME __FUNCSIG__
|
||||
#else
|
||||
#define CURRENT_FUNCTION_NAME __PRETTY_FUNCTION__
|
||||
#endif
|
||||
|
||||
// Version of NODE_API_ASSERT for functions not returning napi_value
|
||||
#define NODE_API_ASSERT_CUSTOM_RETURN(ENV, VALUE_TO_RETURN_IF_THREW, EXPR) \
|
||||
do { \
|
||||
if (!(EXPR)) { \
|
||||
bool is_pending; \
|
||||
napi_is_exception_pending((ENV), &is_pending); \
|
||||
/* If an exception is already pending, don't rethrow it */ \
|
||||
if (!is_pending) { \
|
||||
char buf[4096] = {0}; \
|
||||
snprintf(buf, sizeof(buf) - 1, "%s (%s:%d): Assertion failed: %s", \
|
||||
CURRENT_FUNCTION_NAME, __FILE__, __LINE__, #EXPR); \
|
||||
napi_throw_error((ENV), NULL, buf); \
|
||||
} \
|
||||
return (VALUE_TO_RETURN_IF_THREW); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define REGISTER_FUNCTION(ENV, EXPORTS, FUNCTION) \
|
||||
EXPORTS.Set(#FUNCTION, Napi::Function::New(ENV, FUNCTION))
|
||||
|
||||
static inline napi_value ok(napi_env env) {
|
||||
napi_value result;
|
||||
napi_get_undefined(env, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// For functions that take a garbage collection callback as the first argument
|
||||
// (functions not called directly by module.js), use this to trigger GC
|
||||
static inline void run_gc(const Napi::CallbackInfo &info) {
|
||||
info[0].As<Napi::Function>().Call(0, nullptr);
|
||||
}
|
||||
|
||||
// calls napi_typeof and asserts it returns napi_ok
|
||||
static inline napi_valuetype get_typeof(napi_env env, napi_value value) {
|
||||
napi_valuetype result;
|
||||
// return an invalid napi_valuetype if the call to napi_typeof fails
|
||||
NODE_API_CALL_CUSTOM_RETURN(env, static_cast<napi_valuetype>(INT_MAX),
|
||||
napi_typeof(env, value, &result));
|
||||
return result;
|
||||
}
|
||||
|
||||
static inline const char *napi_valuetype_to_string(napi_valuetype type) {
|
||||
switch (type) {
|
||||
case napi_undefined:
|
||||
return "undefined";
|
||||
case napi_null:
|
||||
return "null";
|
||||
case napi_boolean:
|
||||
return "boolean";
|
||||
case napi_number:
|
||||
return "number";
|
||||
case napi_string:
|
||||
return "string";
|
||||
case napi_symbol:
|
||||
return "symbol";
|
||||
case napi_object:
|
||||
return "object";
|
||||
case napi_function:
|
||||
return "function";
|
||||
case napi_external:
|
||||
return "external";
|
||||
case napi_bigint:
|
||||
return "bigint";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
232
test/napi/napi-app/wrap_tests.cpp
Normal file
232
test/napi/napi-app/wrap_tests.cpp
Normal file
@@ -0,0 +1,232 @@
|
||||
#include "wrap_tests.h"
|
||||
|
||||
#include "utils.h"
|
||||
#include <cassert>
|
||||
|
||||
namespace napitests {
|
||||
|
||||
static napi_ref ref_to_wrapped_object = nullptr;
|
||||
static bool wrap_finalize_called = false;
|
||||
|
||||
// static void delete_the_ref(napi_env env, void *_data, void *_hint) {
|
||||
// printf("delete_the_ref\n");
|
||||
// // not using NODE_API_ASSERT as this runs in a finalizer where allocating
|
||||
// an
|
||||
// // error might cause a harder-to-debug crash
|
||||
// assert(ref_to_wrapped_object);
|
||||
// napi_delete_reference(env, ref_to_wrapped_object);
|
||||
// ref_to_wrapped_object = nullptr;
|
||||
// }
|
||||
|
||||
static void finalize_for_create_wrap(napi_env env, void *opaque_data,
|
||||
void *opaque_hint) {
|
||||
int *data = reinterpret_cast<int *>(opaque_data);
|
||||
int *hint = reinterpret_cast<int *>(opaque_hint);
|
||||
printf("finalize_for_create_wrap, data = %d, hint = %d\n", *data, *hint);
|
||||
delete data;
|
||||
delete hint;
|
||||
// TODO: this needs https://github.com/oven-sh/bun/pulls/14501 to work
|
||||
// if (ref_to_wrapped_object) {
|
||||
// node_api_post_finalizer(env, delete_the_ref, nullptr, nullptr);
|
||||
// }
|
||||
wrap_finalize_called = true;
|
||||
}
|
||||
|
||||
// create_wrap(js_object: object, ask_for_ref: boolean, strong: boolean): object
|
||||
static napi_value create_wrap(const Napi::CallbackInfo &info) {
|
||||
wrap_finalize_called = false;
|
||||
napi_env env = info.Env();
|
||||
napi_value js_object = info[0];
|
||||
|
||||
napi_value js_ask_for_ref = info[1];
|
||||
bool ask_for_ref;
|
||||
NODE_API_CALL(env, napi_get_value_bool(env, js_ask_for_ref, &ask_for_ref));
|
||||
napi_value js_strong = info[2];
|
||||
bool strong;
|
||||
NODE_API_CALL(env, napi_get_value_bool(env, js_strong, &strong));
|
||||
|
||||
// wrap it
|
||||
int *wrap_data = new int(42);
|
||||
int *wrap_hint = new int(123);
|
||||
|
||||
NODE_API_CALL(env, napi_wrap(env, js_object, wrap_data,
|
||||
finalize_for_create_wrap, wrap_hint,
|
||||
ask_for_ref ? &ref_to_wrapped_object : nullptr));
|
||||
if (ask_for_ref && strong) {
|
||||
uint32_t new_refcount;
|
||||
NODE_API_CALL(
|
||||
env, napi_reference_ref(env, ref_to_wrapped_object, &new_refcount));
|
||||
NODE_API_ASSERT(env, new_refcount == 1);
|
||||
}
|
||||
|
||||
if (!ask_for_ref) {
|
||||
ref_to_wrapped_object = nullptr;
|
||||
}
|
||||
|
||||
return js_object;
|
||||
}
|
||||
|
||||
// get_wrap_data(js_object: object): number
|
||||
static napi_value get_wrap_data(const Napi::CallbackInfo &info) {
|
||||
napi_env env = info.Env();
|
||||
napi_value js_object = info[0];
|
||||
|
||||
void *wrapped_data;
|
||||
napi_status status = napi_unwrap(env, js_object, &wrapped_data);
|
||||
if (status != napi_ok) {
|
||||
napi_value undefined;
|
||||
NODE_API_CALL(env, napi_get_undefined(env, &undefined));
|
||||
return undefined;
|
||||
}
|
||||
|
||||
napi_value js_number;
|
||||
NODE_API_CALL(env,
|
||||
napi_create_int32(env, *reinterpret_cast<int *>(wrapped_data),
|
||||
&js_number));
|
||||
return js_number;
|
||||
}
|
||||
|
||||
// get_object_from_ref(): object
|
||||
static napi_value get_object_from_ref(const Napi::CallbackInfo &info) {
|
||||
napi_env env = info.Env();
|
||||
|
||||
napi_value wrapped_object;
|
||||
NODE_API_CALL(env, napi_get_reference_value(env, ref_to_wrapped_object,
|
||||
&wrapped_object));
|
||||
|
||||
if (!wrapped_object) {
|
||||
NODE_API_CALL(env, napi_get_undefined(env, &wrapped_object));
|
||||
}
|
||||
return wrapped_object;
|
||||
}
|
||||
|
||||
// get_wrap_data_from_ref(): number|undefined
|
||||
static napi_value get_wrap_data_from_ref(const Napi::CallbackInfo &info) {
|
||||
napi_env env = info.Env();
|
||||
|
||||
napi_value wrapped_object;
|
||||
NODE_API_CALL(env, napi_get_reference_value(env, ref_to_wrapped_object,
|
||||
&wrapped_object));
|
||||
|
||||
void *wrapped_data;
|
||||
napi_status status = napi_unwrap(env, wrapped_object, &wrapped_data);
|
||||
if (status == napi_ok) {
|
||||
napi_value js_number;
|
||||
NODE_API_CALL(env,
|
||||
napi_create_int32(env, *reinterpret_cast<int *>(wrapped_data),
|
||||
&js_number));
|
||||
return js_number;
|
||||
} else if (status == napi_invalid_arg) {
|
||||
// no longer wrapped
|
||||
napi_value undefined;
|
||||
NODE_API_CALL(env, napi_get_undefined(env, &undefined));
|
||||
return undefined;
|
||||
} else {
|
||||
NODE_API_ASSERT(env, false && "this should not be reached");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// remove_wrap_data(js_object: object): undefined
|
||||
static napi_value remove_wrap(const Napi::CallbackInfo &info) {
|
||||
napi_env env = info.Env();
|
||||
napi_value js_object = info[0];
|
||||
|
||||
void *wrap_data;
|
||||
NODE_API_CALL(env, napi_remove_wrap(env, js_object, &wrap_data));
|
||||
|
||||
napi_value undefined;
|
||||
NODE_API_CALL(env, napi_get_undefined(env, &undefined));
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// unref_wrapped_value(): undefined
|
||||
static napi_value unref_wrapped_value(const Napi::CallbackInfo &info) {
|
||||
napi_env env = info.Env();
|
||||
uint32_t new_refcount;
|
||||
NODE_API_CALL(
|
||||
env, napi_reference_unref(env, ref_to_wrapped_object, &new_refcount));
|
||||
// should never have been set higher than 1
|
||||
NODE_API_ASSERT(env, new_refcount == 0);
|
||||
|
||||
napi_value undefined;
|
||||
NODE_API_CALL(env, napi_get_undefined(env, &undefined));
|
||||
return undefined;
|
||||
}
|
||||
|
||||
static napi_value was_wrap_finalize_called(const Napi::CallbackInfo &info) {
|
||||
Napi::Env env = info.Env();
|
||||
return Napi::Boolean::New(env, wrap_finalize_called);
|
||||
}
|
||||
|
||||
// try_wrap(value: any, num: number): bool
|
||||
// wraps value in a C++ object corresponding to the number num
|
||||
// true if success
|
||||
static napi_value try_wrap(const Napi::CallbackInfo &info) {
|
||||
Napi::Env env = info.Env();
|
||||
napi_value value = info[0];
|
||||
napi_value js_num = info[1];
|
||||
double c_num;
|
||||
NODE_API_CALL(env, napi_get_value_double(env, js_num, &c_num));
|
||||
|
||||
napi_status status = napi_wrap(
|
||||
env, value, reinterpret_cast<void *>(new double{c_num}),
|
||||
[](napi_env env, void *data, void *hint) {
|
||||
(void)env;
|
||||
(void)hint;
|
||||
delete reinterpret_cast<double *>(data);
|
||||
},
|
||||
nullptr, nullptr);
|
||||
|
||||
napi_value js_result;
|
||||
assert(napi_get_boolean(env, status == napi_ok, &js_result) == napi_ok);
|
||||
return js_result;
|
||||
}
|
||||
|
||||
// try_unwrap(any): number|undefined
|
||||
static napi_value try_unwrap(const Napi::CallbackInfo &info) {
|
||||
Napi::Env env = info.Env();
|
||||
napi_value value = info[0];
|
||||
|
||||
double *wrapped;
|
||||
napi_status status =
|
||||
napi_unwrap(env, value, reinterpret_cast<void **>(&wrapped));
|
||||
napi_value result;
|
||||
if (status == napi_ok) {
|
||||
NODE_API_CALL(env, napi_create_double(env, *wrapped, &result));
|
||||
} else {
|
||||
NODE_API_CALL(env, napi_get_undefined(env, &result));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static napi_value try_remove_wrap(const Napi::CallbackInfo &info) {
|
||||
Napi::Env env = info.Env();
|
||||
napi_value value = info[0];
|
||||
|
||||
double *wrapped;
|
||||
napi_status status =
|
||||
napi_remove_wrap(env, value, reinterpret_cast<void **>(&wrapped));
|
||||
napi_value result;
|
||||
if (status == napi_ok) {
|
||||
NODE_API_CALL(env, napi_create_double(env, *wrapped, &result));
|
||||
} else {
|
||||
NODE_API_CALL(env, napi_get_undefined(env, &result));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void register_wrap_tests(Napi::Env env, Napi::Object exports) {
|
||||
REGISTER_FUNCTION(env, exports, create_wrap);
|
||||
REGISTER_FUNCTION(env, exports, get_wrap_data);
|
||||
REGISTER_FUNCTION(env, exports, get_object_from_ref);
|
||||
REGISTER_FUNCTION(env, exports, get_wrap_data_from_ref);
|
||||
REGISTER_FUNCTION(env, exports, remove_wrap);
|
||||
REGISTER_FUNCTION(env, exports, unref_wrapped_value);
|
||||
REGISTER_FUNCTION(env, exports, was_wrap_finalize_called);
|
||||
REGISTER_FUNCTION(env, exports, try_wrap);
|
||||
REGISTER_FUNCTION(env, exports, try_unwrap);
|
||||
REGISTER_FUNCTION(env, exports, try_remove_wrap);
|
||||
}
|
||||
|
||||
} // namespace napitests
|
||||
11
test/napi/napi-app/wrap_tests.h
Normal file
11
test/napi/napi-app/wrap_tests.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
// Helper functions used by JS to test napi_wrap
|
||||
|
||||
#include "napi_with_version.h"
|
||||
|
||||
namespace napitests {
|
||||
|
||||
void register_wrap_tests(Napi::Env env, Napi::Object exports);
|
||||
|
||||
} // namespace napitests
|
||||
@@ -319,6 +319,36 @@ describe("napi", () => {
|
||||
checkSameOutput("test_type_tag", []);
|
||||
});
|
||||
});
|
||||
|
||||
describe("napi_wrap", () => {
|
||||
it("accepts the right kinds of values", () => {
|
||||
checkSameOutput("test_napi_wrap", []);
|
||||
});
|
||||
|
||||
it("is shared between addons", () => {
|
||||
checkSameOutput("test_napi_wrap_cross_addon", []);
|
||||
});
|
||||
|
||||
it("does not follow prototypes", () => {
|
||||
checkSameOutput("test_napi_wrap_prototype", []);
|
||||
});
|
||||
|
||||
it("does not consider proxies", () => {
|
||||
checkSameOutput("test_napi_wrap_proxy", []);
|
||||
});
|
||||
|
||||
it("can remove a wrap", () => {
|
||||
checkSameOutput("test_napi_remove_wrap", []);
|
||||
});
|
||||
|
||||
it("has the right lifetime", () => {
|
||||
checkSameOutput("test_wrap_lifetime_without_ref", []);
|
||||
checkSameOutput("test_wrap_lifetime_with_weak_ref", []);
|
||||
checkSameOutput("test_wrap_lifetime_with_strong_ref", []);
|
||||
checkSameOutput("test_remove_wrap_lifetime_with_weak_ref", []);
|
||||
checkSameOutput("test_remove_wrap_lifetime_with_strong_ref", []);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function checkSameOutput(test: string, args: any[] | string) {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"@azure/service-bus": "7.9.4",
|
||||
"@grpc/grpc-js": "1.12.0",
|
||||
"@grpc/proto-loader": "0.7.10",
|
||||
"@napi-rs/canvas": "0.1.47",
|
||||
"@napi-rs/canvas": "0.1.65",
|
||||
"@prisma/client": "5.8.0",
|
||||
"@remix-run/react": "2.10.3",
|
||||
"@remix-run/serve": "2.10.3",
|
||||
@@ -69,7 +69,8 @@
|
||||
"webpack": "5.88.0",
|
||||
"webpack-cli": "4.7.2",
|
||||
"xml2js": "0.6.2",
|
||||
"yargs": "17.7.2"
|
||||
"yargs": "17.7.2",
|
||||
"jimp": "1.6.0"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
Reference in New Issue
Block a user