fix(napi): resuse threads worker on Node.js (#2399)

This commit is contained in:
LongYinan 2024-12-18 23:48:30 +08:00 committed by GitHub
parent 3d62aca408
commit 8fffa49282
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 323 additions and 104 deletions

View File

@ -1,2 +1,7 @@
[target.'cfg(target_family = "wasm")']
rustflags = ["-C", "target-feature=+atomics,+bulk-memory"]
rustflags = [
"--cfg",
"tokio_unstable",
"-C",
"target-feature=+atomics,+bulk-memory",
]

View File

@ -153,7 +153,7 @@ jobs:
- name: 'Install dependencies'
shell: bash
run: |
yarn config set supportedArchitectures.cpu --json '["current", "x64", "x86"]'
yarn config set supportedArchitectures.cpu --json '["current", "x64", "ia32", "wasm32"]'
yarn install --mode=skip-build --immutable
- name: Check build

View File

@ -1,3 +1,8 @@
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.5.3.cjs
supportedArchitectures:
cpu:
- current
- wasm32

View File

@ -5,7 +5,7 @@ import { resolve } from 'path'
import { fileURLToPath } from 'url'
execSync(
`node --loader ts-node/esm/transpile-only ${resolve(
`node --import @oxc-node/core/register ${resolve(
fileURLToPath(import.meta.url),
'../src/cli.ts',
)} ${process.argv.slice(2).join(' ')}`,

View File

@ -1,9 +1,9 @@
import * as esbuild from 'esbuild'
import { build} from 'esbuild'
import { pull } from 'lodash-es'
import packageJson from './package.json' assert { type: 'json' }
await esbuild.build({
await build({
entryPoints: ['./dist/index.js'],
outfile: './dist/index.cjs',
bundle: true,

View File

@ -15,7 +15,7 @@ const __wasi = new __WASI({
fs: __fs,
preopens: {
'/': '/',
}
},
})`
: `
const __wasi = new __WASI({
@ -147,11 +147,11 @@ const { instance: __napiInstance, module: __wasiModule, napiModule: __napiModule
return 4
}
})(),
reuseWorker: true,
wasi: __wasi,
onCreateWorker() {
const worker = new Worker(__nodePath.join(__dirname, 'wasi-worker.mjs'), {
env: process.env,
execArgv: ['--experimental-wasi-unstable-preview1'],
})
worker.onmessage = ({ data }) => {
__wasmCreateOnMessageForFsProxy(__nodeFs)(data)

View File

@ -79,7 +79,7 @@ impl TryToTokens for NapiFn {
#(#refs)*
#[cfg(debug_assert)]
#[cfg(debug_assertions)]
{
for a in &_args_array {
assert!(!a.is_null(), "failed to initialize napi ref");

View File

@ -25,4 +25,9 @@ pub fn setup() {
println!("cargo:rustc-link-search={setjmp_link_dir}");
println!("cargo:rustc-link-lib=static=setjmp-mt");
}
if let Ok(wasi_sdk_path) = env::var("WASI_SDK_PATH") {
println!(
"cargo:rustc-link-search={wasi_sdk_path}/share/wasi-sysroot/lib/wasm32-wasip1-threads"
);
}
}

View File

@ -6,12 +6,6 @@ use std::ffi::CStr;
use std::mem::MaybeUninit;
#[cfg(not(feature = "noop"))]
use std::ptr;
#[cfg(all(
not(any(target_os = "macos", target_family = "wasm")),
feature = "napi4",
feature = "tokio_rt"
))]
use std::sync::atomic::AtomicUsize;
#[cfg(not(feature = "noop"))]
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{LazyLock, RwLock};
@ -411,13 +405,13 @@ pub unsafe extern "C" fn napi_register_module_v1(
}
}
});
});
REGISTERED_CLASSES.borrow_mut(|map| {
map.insert(
std::thread::current().id(),
PersistedPerInstanceHashMap::from_hashmap(registered_classes),
)
});
REGISTERED_CLASSES.borrow_mut(|map| {
map.insert(
std::thread::current().id(),
PersistedPerInstanceHashMap::from_hashmap(registered_classes),
)
});
#[cfg(feature = "compat-mode")]
@ -430,23 +424,25 @@ pub unsafe extern "C" fn napi_register_module_v1(
})
}
#[cfg(all(
not(any(target_os = "macos", target_family = "wasm")),
feature = "napi4",
feature = "tokio_rt"
))]
#[cfg(all(feature = "napi4", feature = "tokio_rt"))]
{
crate::tokio_runtime::ensure_runtime();
static init_counter: AtomicUsize = AtomicUsize::new(0);
let cleanup_hook_payload =
init_counter.fetch_add(1, Ordering::Relaxed) as *mut std::ffi::c_void;
#[cfg(not(target_family = "wasm"))]
unsafe {
sys::napi_add_env_cleanup_hook(
env,
Some(crate::tokio_runtime::drop_runtime),
cleanup_hook_payload,
ptr::null_mut(),
)
};
#[cfg(target_family = "wasm")]
unsafe {
crate::napi_add_env_cleanup_hook(
env,
Some(crate::tokio_runtime::drop_runtime),
ptr::null_mut(),
)
};
}

View File

@ -1011,13 +1011,27 @@ impl Env {
hook: Box::new(cleanup_fn),
};
let hook_ref = Box::leak(Box::new(hook));
check_status!(unsafe {
sys::napi_add_env_cleanup_hook(
self.0,
Some(cleanup_env::<T>),
hook_ref as *mut CleanupEnvHookData<T> as *mut _,
)
})?;
#[cfg(not(target_family = "wasm"))]
{
check_status!(unsafe {
sys::napi_add_env_cleanup_hook(
self.0,
Some(cleanup_env::<T>),
(hook_ref as *mut CleanupEnvHookData<T>).cast(),
)
})?;
}
#[cfg(target_family = "wasm")]
{
check_status!(unsafe {
crate::napi_add_env_cleanup_hook(
self.0,
Some(cleanup_env::<T>),
(hook_ref as *mut CleanupEnvHookData<T>).cast(),
)
})?;
}
Ok(CleanupEnvHook(hook_ref))
}
@ -1146,8 +1160,7 @@ impl Env {
check_status!(unsafe {
sys::napi_set_instance_data(
self.0,
Box::leak(Box::new((TaggedObject::new(native), finalize_cb))) as *mut (TaggedObject<T>, F)
as *mut c_void,
Box::into_raw(Box::new((TaggedObject::new(native), finalize_cb))).cast(),
Some(
set_instance_finalize_callback::<T, Hint, F>
as unsafe extern "C" fn(
@ -1156,7 +1169,7 @@ impl Env {
finalize_hint: *mut c_void,
),
),
Box::leak(Box::new(hint)) as *mut Hint as *mut c_void,
Box::into_raw(Box::new(hint)).cast(),
)
})
}
@ -1377,6 +1390,7 @@ unsafe extern "C" fn drop_buffer(
mem::drop(unsafe { Vec::from_raw_parts(finalize_data as *mut u8, length, cap) });
}
#[cfg_attr(target_family = "wasm", allow(unused_variables))]
pub(crate) unsafe extern "C" fn raw_finalize<T>(
env: sys::napi_env,
finalize_data: *mut c_void,

View File

@ -65,6 +65,16 @@
//! ```
//!
#[cfg(target_family = "wasm")]
#[link(wasm_import_module = "napi")]
extern "C" {
fn napi_add_env_cleanup_hook(
env: sys::napi_env,
fun: Option<unsafe extern "C" fn(arg: *mut core::ffi::c_void)>,
arg: *mut core::ffi::c_void,
) -> sys::napi_status;
}
#[cfg(feature = "napi8")]
mod async_cleanup_hook;
#[cfg(feature = "napi8")]

View File

@ -11,18 +11,24 @@ use crate::{
};
fn create_runtime() -> Option<Runtime> {
#[cfg(not(target_family = "wasm"))]
{
let runtime = tokio::runtime::Runtime::new().expect("Create tokio runtime failed");
Some(runtime)
}
#[cfg(target_family = "wasm")]
{
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.ok()
Some(
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("Create tokio runtime failed"),
)
}
#[cfg(not(target_family = "wasm"))]
{
Some(
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.expect("Create tokio runtime failed"),
)
}
}
@ -56,14 +62,12 @@ pub fn create_custom_tokio_runtime(rt: Runtime) {
USER_DEFINED_RT.get_or_init(move || Mutex::new(Some(rt)));
}
#[cfg(not(any(target_os = "macos", target_family = "wasm")))]
static RT_REFERENCE_COUNT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
/// Ensure that the Tokio runtime is initialized.
/// In windows the Tokio runtime will be dropped when Node env exits.
/// But in Electron renderer process, the Node env will exits and recreate when the window reloads.
/// So we need to ensure that the Tokio runtime is initialized when the Node env is created.
#[cfg(not(any(target_os = "macos", target_family = "wasm")))]
pub(crate) fn ensure_runtime() {
use std::sync::atomic::Ordering;
@ -75,12 +79,13 @@ pub(crate) fn ensure_runtime() {
RT_REFERENCE_COUNT.fetch_add(1, Ordering::Relaxed);
}
#[cfg(not(any(target_os = "macos", target_family = "wasm")))]
pub(crate) unsafe extern "C" fn drop_runtime(_arg: *mut std::ffi::c_void) {
use std::sync::atomic::Ordering;
if RT_REFERENCE_COUNT.fetch_sub(1, Ordering::AcqRel) == 1 {
RT.write().unwrap().take();
if let Some(rt) = RT.write().unwrap().take() {
rt.shutdown_background();
}
}
}
@ -198,23 +203,23 @@ pub fn execute_tokio_future<
};
#[cfg(not(target_family = "wasm"))]
{
let jh = spawn(inner);
spawn(async move {
if let Err(err) = jh.await {
if let Ok(reason) = err.try_into_panic() {
if let Some(s) = reason.downcast_ref::<&str>() {
deferred_for_panic.reject(Error::new(crate::Status::GenericFailure, s));
} else {
deferred_for_panic.reject(Error::new(
crate::Status::GenericFailure,
"Panic in async function",
));
}
let jh = spawn(inner);
#[cfg(not(target_family = "wasm"))]
spawn(async move {
if let Err(err) = jh.await {
if let Ok(reason) = err.try_into_panic() {
if let Some(s) = reason.downcast_ref::<&str>() {
deferred_for_panic.reject(Error::new(crate::Status::GenericFailure, s));
} else {
deferred_for_panic.reject(Error::new(
crate::Status::GenericFailure,
"Panic in async function",
));
}
}
});
}
}
});
#[cfg(target_family = "wasm")]
{

View File

@ -0,0 +1,3 @@
{
"extends": "../../tsconfig.json"
}

View File

@ -6,6 +6,7 @@ test('setProperty', (t) => {
const obj = {}
const key = 'jsPropertyKey'
bindings.testSetProperty(obj, key)
// @ts-expect-error
t.snapshot(obj[key])
})
@ -26,6 +27,7 @@ test('setNamedProperty', (t) => {
const [key] = keys
t.is(keys.length, 1)
t.snapshot(key)
// @ts-expect-error
t.is(obj[key], property)
})

View File

@ -23,7 +23,7 @@
"__tests__/**/*.spec.ts"
],
"environmentVariables": {
"TS_NODE_PROJECT": "../tsconfig.json"
"TS_NODE_PROJECT": "./tsconfig.json"
},
"workerThreads": false,
"cache": false,

View File

@ -0,0 +1,16 @@
{
"extends": "../../tsconfig.json",
"include": ["."],
"compilerOptions": {
"moduleResolution": "node",
"outDir": "./dist",
"rootDir": ".",
"target": "ESNext",
"module": "CommonJS",
"skipLibCheck": true,
"noEmit": true,
"importHelpers": false,
"noEmitHelpers": false
},
"exclude": ["dist"]
}

View File

@ -12,6 +12,7 @@ Generated by [AVA](https://avajs.dev).
'@napi-rs/cli',
'@napi-rs/triples',
'@napi-rs/wasm-runtime',
'@oxc-node/core',
'@types/lodash',
'@vitest/browser',
'@vitest/ui',

View File

@ -14,7 +14,7 @@ const __wasi = new __WASI({
fs: __fs,
preopens: {
'/': '/',
}
},
})
const __emnapiContext = __emnapiGetDefaultContext()

View File

@ -56,11 +56,11 @@ const { instance: __napiInstance, module: __wasiModule, napiModule: __napiModule
return 4
}
})(),
reuseWorker: true,
wasi: __wasi,
onCreateWorker() {
const worker = new Worker(__nodePath.join(__dirname, 'wasi-worker.mjs'), {
env: process.env,
execArgv: ['--experimental-wasi-unstable-preview1'],
})
worker.onmessage = ({ data }) => {
__wasmCreateOnMessageForFsProxy(__nodeFs)(data)

View File

@ -8,7 +8,7 @@
"scripts": {
"browser": "vite",
"build": "napi-raw build --platform --js index.cjs --dts index.d.cts",
"test": "cross-env TS_NODE_PROJECT=./tsconfig.json node --es-module-specifier-resolution=node --loader ts-node/esm/transpile-only --experimental-wasi-unstable-preview1 ../../node_modules/ava/entrypoints/cli.mjs"
"test": "cross-env TS_NODE_PROJECT=./tsconfig.json node --loader ts-node/esm/transpile-only ../../node_modules/ava/entrypoints/cli.mjs"
},
"napi": {
"binaryName": "example",
@ -28,6 +28,7 @@
"@napi-rs/cli": "workspace:*",
"@napi-rs/triples": "workspace:*",
"@napi-rs/wasm-runtime": "workspace:*",
"@oxc-node/core": "^0.0.16",
"@types/lodash": "^4.17.10",
"@vitest/browser": "^2.1.2",
"@vitest/ui": "^2.1.2",

View File

@ -174,11 +174,11 @@ pub fn generate_function_and_call_it(env: &Env) -> Result<FunctionData> {
}
#[napi]
fn get_null_byte_property(obj: JsObject) -> Result<Option<String>> {
pub fn get_null_byte_property(obj: JsObject) -> Result<Option<String>> {
obj.get::<String>("\0virtual")
}
#[napi]
fn set_null_byte_property(mut obj: JsObject) -> Result<()> {
pub fn set_null_byte_property(mut obj: JsObject) -> Result<()> {
obj.set("\0virtual", "test")
}

View File

@ -1,5 +1,5 @@
{
"extends": "../tsconfig.json",
"extends": "../../tsconfig.json",
"include": ["."],
"compilerOptions": {
"moduleResolution": "node",
@ -7,7 +7,6 @@
"rootDir": ".",
"target": "ESNext",
"module": "ESNext",
"skipLibCheck": false,
"noEmit": true,
"types": ["bun-types"],
"importHelpers": false

View File

@ -1,10 +0,0 @@
{
"extends": "../tsconfig.json",
"include": ["."],
"compilerOptions": {
"outDir": "./dist",
"rootDir": ".",
"target": "ES2015"
},
"exclude": ["dist"]
}

View File

@ -0,0 +1,3 @@
{
"extends": "../tsconfig.json"
}

View File

@ -65,6 +65,7 @@
},
"devDependencies": {
"@napi-rs/cli": "workspace:*",
"@oxc-node/core": "^0.0.16",
"@taplo/cli": "^0.7.0",
"@types/debug": "^4.1.12",
"@types/lodash-es": "^4.17.12",

View File

@ -24,6 +24,11 @@
"outDir": "scripts",
"lib": ["ES2022"]
},
"include": ["."],
"include": [],
"references": [
{ "path": "./examples/napi-compat-mode/tsconfig.json" },
{ "path": "./examples/napi/tsconfig.json" },
{ "path": "./cli/tsconfig.json" }
],
"exclude": ["node_modules", "bench", "cli/scripts", "scripts", "target"]
}

View File

@ -1,15 +0,0 @@
{
"extends": "./tsconfig.json",
"include": [
"./ava.config.mjs",
"./triples/index.js",
"./wasm-runtime/*.js",
"./wasm-runtime/*.cjs",
"./memory-testing/*.js",
"./memory-testing/*.mjs",
"./crates/cli/index.js",
"./examples/**/*.js",
"./examples/**/*.mjs",
"./examples/**/*.cjs"
]
}

175
yarn.lock
View File

@ -521,6 +521,7 @@ __metadata:
"@napi-rs/cli": "workspace:*"
"@napi-rs/triples": "workspace:*"
"@napi-rs/wasm-runtime": "workspace:*"
"@oxc-node/core": "npm:^0.0.16"
"@types/lodash": "npm:^4.17.10"
"@vitest/browser": "npm:^2.1.2"
"@vitest/ui": "npm:^2.1.2"
@ -1399,7 +1400,7 @@ __metadata:
languageName: node
linkType: hard
"@napi-rs/wasm-runtime@npm:^0.2.3, @napi-rs/wasm-runtime@npm:^0.2.4, @napi-rs/wasm-runtime@workspace:*, @napi-rs/wasm-runtime@workspace:wasm-runtime":
"@napi-rs/wasm-runtime@npm:^0.2.3, @napi-rs/wasm-runtime@npm:^0.2.4, @napi-rs/wasm-runtime@npm:^0.2.5, @napi-rs/wasm-runtime@workspace:*, @napi-rs/wasm-runtime@workspace:wasm-runtime":
version: 0.0.0-use.local
resolution: "@napi-rs/wasm-runtime@workspace:wasm-runtime"
dependencies:
@ -2183,6 +2184,177 @@ __metadata:
languageName: node
linkType: hard
"@oxc-node/core-android-arm-eabi@npm:0.0.16":
version: 0.0.16
resolution: "@oxc-node/core-android-arm-eabi@npm:0.0.16"
conditions: os=android & cpu=arm
languageName: node
linkType: hard
"@oxc-node/core-android-arm64@npm:0.0.16":
version: 0.0.16
resolution: "@oxc-node/core-android-arm64@npm:0.0.16"
conditions: os=android & cpu=arm64
languageName: node
linkType: hard
"@oxc-node/core-darwin-arm64@npm:0.0.16":
version: 0.0.16
resolution: "@oxc-node/core-darwin-arm64@npm:0.0.16"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@oxc-node/core-darwin-x64@npm:0.0.16":
version: 0.0.16
resolution: "@oxc-node/core-darwin-x64@npm:0.0.16"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@oxc-node/core-freebsd-x64@npm:0.0.16":
version: 0.0.16
resolution: "@oxc-node/core-freebsd-x64@npm:0.0.16"
conditions: os=freebsd & cpu=x64
languageName: node
linkType: hard
"@oxc-node/core-linux-arm-gnueabihf@npm:0.0.16":
version: 0.0.16
resolution: "@oxc-node/core-linux-arm-gnueabihf@npm:0.0.16"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
"@oxc-node/core-linux-arm64-gnu@npm:0.0.16":
version: 0.0.16
resolution: "@oxc-node/core-linux-arm64-gnu@npm:0.0.16"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
"@oxc-node/core-linux-arm64-musl@npm:0.0.16":
version: 0.0.16
resolution: "@oxc-node/core-linux-arm64-musl@npm:0.0.16"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
"@oxc-node/core-linux-ppc64-gnu@npm:0.0.16":
version: 0.0.16
resolution: "@oxc-node/core-linux-ppc64-gnu@npm:0.0.16"
conditions: os=linux & cpu=ppc64 & libc=glibc
languageName: node
linkType: hard
"@oxc-node/core-linux-s390x-gnu@npm:0.0.16":
version: 0.0.16
resolution: "@oxc-node/core-linux-s390x-gnu@npm:0.0.16"
conditions: os=linux & cpu=s390x & libc=glibc
languageName: node
linkType: hard
"@oxc-node/core-linux-x64-gnu@npm:0.0.16":
version: 0.0.16
resolution: "@oxc-node/core-linux-x64-gnu@npm:0.0.16"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
"@oxc-node/core-linux-x64-musl@npm:0.0.16":
version: 0.0.16
resolution: "@oxc-node/core-linux-x64-musl@npm:0.0.16"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
"@oxc-node/core-wasm32-wasi@npm:0.0.16":
version: 0.0.16
resolution: "@oxc-node/core-wasm32-wasi@npm:0.0.16"
dependencies:
"@napi-rs/wasm-runtime": "npm:^0.2.5"
conditions: cpu=wasm32
languageName: node
linkType: hard
"@oxc-node/core-win32-arm64-msvc@npm:0.0.16":
version: 0.0.16
resolution: "@oxc-node/core-win32-arm64-msvc@npm:0.0.16"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
"@oxc-node/core-win32-ia32-msvc@npm:0.0.16":
version: 0.0.16
resolution: "@oxc-node/core-win32-ia32-msvc@npm:0.0.16"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
"@oxc-node/core-win32-x64-msvc@npm:0.0.16":
version: 0.0.16
resolution: "@oxc-node/core-win32-x64-msvc@npm:0.0.16"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"@oxc-node/core@npm:^0.0.16":
version: 0.0.16
resolution: "@oxc-node/core@npm:0.0.16"
dependencies:
"@oxc-node/core-android-arm-eabi": "npm:0.0.16"
"@oxc-node/core-android-arm64": "npm:0.0.16"
"@oxc-node/core-darwin-arm64": "npm:0.0.16"
"@oxc-node/core-darwin-x64": "npm:0.0.16"
"@oxc-node/core-freebsd-x64": "npm:0.0.16"
"@oxc-node/core-linux-arm-gnueabihf": "npm:0.0.16"
"@oxc-node/core-linux-arm64-gnu": "npm:0.0.16"
"@oxc-node/core-linux-arm64-musl": "npm:0.0.16"
"@oxc-node/core-linux-ppc64-gnu": "npm:0.0.16"
"@oxc-node/core-linux-s390x-gnu": "npm:0.0.16"
"@oxc-node/core-linux-x64-gnu": "npm:0.0.16"
"@oxc-node/core-linux-x64-musl": "npm:0.0.16"
"@oxc-node/core-wasm32-wasi": "npm:0.0.16"
"@oxc-node/core-win32-arm64-msvc": "npm:0.0.16"
"@oxc-node/core-win32-ia32-msvc": "npm:0.0.16"
"@oxc-node/core-win32-x64-msvc": "npm:0.0.16"
dependenciesMeta:
"@oxc-node/core-android-arm-eabi":
optional: true
"@oxc-node/core-android-arm64":
optional: true
"@oxc-node/core-darwin-arm64":
optional: true
"@oxc-node/core-darwin-x64":
optional: true
"@oxc-node/core-freebsd-x64":
optional: true
"@oxc-node/core-linux-arm-gnueabihf":
optional: true
"@oxc-node/core-linux-arm64-gnu":
optional: true
"@oxc-node/core-linux-arm64-musl":
optional: true
"@oxc-node/core-linux-ppc64-gnu":
optional: true
"@oxc-node/core-linux-s390x-gnu":
optional: true
"@oxc-node/core-linux-x64-gnu":
optional: true
"@oxc-node/core-linux-x64-musl":
optional: true
"@oxc-node/core-wasm32-wasi":
optional: true
"@oxc-node/core-win32-arm64-msvc":
optional: true
"@oxc-node/core-win32-ia32-msvc":
optional: true
"@oxc-node/core-win32-x64-msvc":
optional: true
checksum: 10c0/b986b8f6a89c378b9fe06e3c3a50ee086b42709b5c3057bd3b56ff374407bd44fd1efd6b090f5de13dc3256ff47e77c1cb5717a4a655788b647fbdba7c6ad390
languageName: node
linkType: hard
"@oxlint/darwin-arm64@npm:0.15.0":
version: 0.15.0
resolution: "@oxlint/darwin-arm64@npm:0.15.0"
@ -8801,6 +8973,7 @@ __metadata:
resolution: "napi-rs@workspace:."
dependencies:
"@napi-rs/cli": "workspace:*"
"@oxc-node/core": "npm:^0.0.16"
"@taplo/cli": "npm:^0.7.0"
"@types/debug": "npm:^4.1.12"
"@types/lodash-es": "npm:^4.17.12"