├── .husky ├── .gitignore └── pre-commit ├── README.md ├── examples ├── napi │ ├── .gitignore │ ├── build.rs │ ├── __test__ │ │ ├── values.spec.ts.snap │ │ ├── typegen.spec.ts.snap │ │ ├── tsfn-error.js │ │ ├── typegen.spec.ts │ │ ├── object-attr.spec.ts │ │ ├── unload.spec.js │ │ ├── worker.js │ │ ├── values.spec.ts.md │ │ ├── generator.spec.ts │ │ └── error-msg.spec.ts │ ├── src │ │ ├── promise.rs │ │ ├── number.rs │ │ ├── nullable.rs │ │ ├── map.rs │ │ ├── class_factory.rs │ │ ├── symbol.rs │ │ ├── external.rs │ │ ├── date.rs │ │ ├── string.rs │ │ ├── async.rs │ │ ├── bigint.rs │ │ ├── array.rs │ │ ├── enum.rs │ │ ├── error.rs │ │ ├── task.rs │ │ ├── serde.rs │ │ ├── fn_ts_override.rs │ │ ├── lib.rs │ │ ├── typed_array.rs │ │ └── js_mod.rs │ ├── tests │ │ ├── build_error_tests │ │ │ ├── mod.rs │ │ │ ├── ts_arg_type_2.stderr │ │ │ ├── ts_arg_type_4.stderr │ │ │ ├── ts_arg_type_3.stderr │ │ │ ├── ts_arg_type_1.stderr │ │ │ ├── ts_arg_type_2.rs │ │ │ ├── ts_arg_type_4.rs │ │ │ ├── ts_arg_type_3.rs │ │ │ └── ts_arg_type_1.rs │ │ ├── README.md │ │ └── macro_tests.rs │ ├── tsconfig.json │ ├── package.json │ ├── Cargo.toml │ └── electron.js ├── binary │ ├── .gitignore │ ├── src │ │ └── main.rs │ ├── Cargo.toml │ └── package.json ├── napi-compat-mode │ ├── __test__ │ │ ├── napi4 │ │ │ ├── example.txt │ │ │ ├── tsfn-throw.js │ │ │ ├── tsfn-dua-instance.js │ │ │ ├── deferred.spec.ts │ │ │ ├── tsfn_error.spec.ts │ │ │ ├── tokio_readfile.spec.ts │ │ │ └── tokio_rt.spec.ts │ │ ├── napi-version.ts │ │ ├── napi8 │ │ │ ├── sub-process.js │ │ │ ├── sub-process-removable.js │ │ │ ├── object.spec.ts │ │ │ └── async-cleanup.spec.ts │ │ ├── object.spec.ts.snap │ │ ├── string.spec.ts.snap │ │ ├── serde │ │ │ ├── ser.spec.ts.snap │ │ │ ├── serde-json.spec.ts │ │ │ ├── ser.spec.ts │ │ │ └── de.spec.ts │ │ ├── get-napi-version.spec.ts │ │ ├── either.spec.ts │ │ ├── cleanup-env.spec.ts │ │ ├── create-external.spec.ts │ │ ├── throw.spec.ts │ │ ├── string.spec.ts.md │ │ ├── string.spec.ts │ │ ├── symbol.spec.ts │ │ ├── napi6 │ │ │ ├── instance-data.spec.ts │ │ │ └── bigint.spec.ts │ │ ├── global.spec.ts │ │ ├── spawn.spec.ts │ │ ├── object.spec.ts.md │ │ ├── function.spec.ts │ │ ├── env.spec.ts │ │ ├── napi7 │ │ │ └── arraybuffer.spec.ts │ │ ├── class.spec.ts │ │ ├── napi5 │ │ │ └── date.spec.ts │ │ ├── array.spec.ts │ │ ├── buffer.spec.ts │ │ ├── arraybuffer.spec.ts │ │ └── js-value.spec.ts │ ├── build.rs │ ├── src │ │ ├── napi_version.rs │ │ ├── napi7 │ │ │ ├── mod.rs │ │ │ └── buffer.rs │ │ ├── tokio_rt │ │ │ ├── mod.rs │ │ │ └── read_file.rs │ │ ├── napi5 │ │ │ ├── mod.rs │ │ │ └── date.rs │ │ ├── napi8 │ │ │ ├── object.rs │ │ │ ├── mod.rs │ │ │ └── async_cleanup.rs │ │ ├── napi4 │ │ │ ├── deferred.rs │ │ │ ├── mod.rs │ │ │ └── tsfn_dua_instance.rs │ │ ├── global.rs │ │ ├── napi6 │ │ │ ├── instance.rs │ │ │ ├── mod.rs │ │ │ └── bigint.rs │ │ ├── symbol.rs │ │ ├── cleanup_env.rs │ │ ├── error.rs │ │ ├── either.rs │ │ ├── external.rs │ │ ├── string.rs │ │ ├── array.rs │ │ └── function.rs │ ├── Cargo.toml │ └── package.json └── tsconfig.json ├── rustfmt.toml ├── .dockerignore ├── crates ├── napi │ └── src │ │ ├── js_values │ │ ├── either.rs │ │ ├── value.rs │ │ ├── tagged_object.rs │ │ ├── undefined.rs │ │ ├── boolean.rs │ │ ├── escapable_handle_scope.rs │ │ ├── string │ │ │ ├── latin1.rs │ │ │ ├── utf8.rs │ │ │ └── utf16.rs │ │ ├── date.rs │ │ └── value_ref.rs │ │ ├── bindgen_runtime │ │ ├── js_values │ │ │ ├── function.rs │ │ │ ├── boolean.rs │ │ │ ├── map.rs │ │ │ └── symbol.rs │ │ ├── error.rs │ │ └── env.rs │ │ ├── cleanup_env.rs │ │ ├── task.rs │ │ ├── version.rs │ │ ├── async_cleanup_hook.rs │ │ └── value_type.rs ├── backend │ ├── README.md │ ├── Cargo.toml │ └── src │ │ ├── typegen │ │ ├── const.rs │ │ └── enum.rs │ │ ├── codegen.rs │ │ └── codegen │ │ └── const.rs ├── build │ ├── src │ │ ├── macos.rs │ │ ├── lib.rs │ │ └── android.rs │ ├── Cargo.toml │ └── README.md ├── sys │ ├── README.md │ └── Cargo.toml └── macro │ ├── README.md │ └── Cargo.toml ├── .yarnrc.yml ├── bench ├── build.rs ├── src │ ├── noop.rs │ ├── plus.rs │ ├── buffer.rs │ ├── lib.rs │ ├── create_array.rs │ └── get_value_from_js.rs ├── tsconfig.json ├── package.json ├── noop.ts ├── plus.ts ├── query.ts ├── buffer.ts ├── create-array.ts ├── Cargo.toml ├── get-array-from-js.ts ├── async.ts ├── bench.ts └── get-set-property.ts ├── .github ├── FUNDING.yml └── workflows │ ├── linux-musl.yaml │ ├── lint.yaml │ ├── check-all-features.yml │ ├── msrv.yml │ ├── android.yml │ ├── linux-aarch64-musl.yaml │ ├── windows-arm.yml │ ├── cli-binary.yml │ ├── android-armv7.yml │ ├── linux-aarch64.yaml │ ├── linux-armv7.yaml │ ├── asan.yml │ └── windows-i686.yml ├── cli ├── .npmignore ├── src │ ├── debug.ts │ ├── new │ │ ├── lib-rs.ts │ │ ├── npmignore.ts │ │ ├── cargo-config.ts │ │ ├── cargo.ts │ │ └── package.ts │ ├── help.ts │ ├── update-package.ts │ ├── utils.ts │ ├── spawn.ts │ ├── index.ts │ ├── consts.ts │ └── version.ts ├── LICENSE └── package.json ├── memory-testing ├── build.rs ├── index.mjs ├── package.json ├── tsfn.mjs ├── tokio-future.mjs ├── returns-future.mjs ├── Cargo.toml ├── reference.mjs ├── util.mjs ├── buffer.mjs └── serde.mjs ├── .prettierignore ├── lerna.json ├── .editorconfig ├── .eslintignore ├── tsconfig.root-lint.json ├── triples ├── index.d.ts ├── CHANGELOG.md ├── package.json ├── LICENSE └── target-list ├── .npmignore ├── renovate.json ├── armhf.Dockerfile ├── aarch64.Dockerfile ├── .gitattributes ├── Cargo.toml ├── debian-zig.Dockerfile ├── alpine-zig.Dockerfile ├── ava.config.mjs ├── alpine.Dockerfile ├── tsconfig.json ├── images └── nextjs.svg ├── generate-triple-list.ts ├── .cirrus.yml ├── debian-aarch64.Dockerfile └── debian.Dockerfile /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | crates/napi/README.md -------------------------------------------------------------------------------- /examples/napi/.gitignore: -------------------------------------------------------------------------------- 1 | *.node 2 | wip/ 3 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | tab_spaces = 2 2 | edition = "2021" 3 | -------------------------------------------------------------------------------- /examples/binary/.gitignore: -------------------------------------------------------------------------------- 1 | *.node 2 | napi-examples-binary 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | target 2 | test_module/target 3 | node_modules 4 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/napi4/example.txt: -------------------------------------------------------------------------------- 1 | Hello world! 2 | -------------------------------------------------------------------------------- /crates/napi/src/js_values/either.rs: -------------------------------------------------------------------------------- 1 | pub use crate::bindgen_runtime::Either; 2 | -------------------------------------------------------------------------------- /examples/binary/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello World!"); 3 | } 4 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | yarnPath: .yarn/releases/yarn-3.3.0.cjs 4 | -------------------------------------------------------------------------------- /examples/napi/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | use napi_build::setup; 3 | 4 | setup(); 5 | } 6 | -------------------------------------------------------------------------------- /bench/build.rs: -------------------------------------------------------------------------------- 1 | extern crate napi_build; 2 | 3 | fn main() { 4 | napi_build::setup(); 5 | } 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [napi-rs, Brooooooklyn, forehalo, messense] 2 | open_collective: napi-rs 3 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint-staged && cargo fmt --all -------------------------------------------------------------------------------- /cli/.npmignore: -------------------------------------------------------------------------------- 1 | .git 2 | yarn.lock 3 | target 4 | package-template/npm 5 | package-template/README.md 6 | -------------------------------------------------------------------------------- /memory-testing/build.rs: -------------------------------------------------------------------------------- 1 | extern crate napi_build; 2 | 3 | fn main() { 4 | napi_build::setup(); 5 | } 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | target 2 | node_modules 3 | scripts 4 | triples/index.js 5 | examples/napi/index.d.ts 6 | .yarn -------------------------------------------------------------------------------- /examples/napi-compat-mode/build.rs: -------------------------------------------------------------------------------- 1 | extern crate napi_build; 2 | 3 | fn main() { 4 | napi_build::setup(); 5 | } 6 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/napi-version.ts: -------------------------------------------------------------------------------- 1 | export const napiVersion = parseInt(process.versions.napi ?? '1', 10) 2 | -------------------------------------------------------------------------------- /cli/src/debug.ts: -------------------------------------------------------------------------------- 1 | import debug from 'debug' 2 | 3 | export const debugFactory = (namespace: string) => debug(`napi:${namespace}`) 4 | -------------------------------------------------------------------------------- /examples/napi/__test__/values.spec.ts.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pose/napi-rs/main/examples/napi/__test__/values.spec.ts.snap -------------------------------------------------------------------------------- /examples/napi/__test__/typegen.spec.ts.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pose/napi-rs/main/examples/napi/__test__/typegen.spec.ts.snap -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/napi8/sub-process.js: -------------------------------------------------------------------------------- 1 | const bindings = require('../../index.node') 2 | 3 | bindings.testAddAsyncCleanupHook() 4 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/object.spec.ts.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pose/napi-rs/main/examples/napi-compat-mode/__test__/object.spec.ts.snap -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/string.spec.ts.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pose/napi-rs/main/examples/napi-compat-mode/__test__/string.spec.ts.snap -------------------------------------------------------------------------------- /examples/binary/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2021" 3 | name = "napi-examples-binary" 4 | publish = false 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/serde/ser.spec.ts.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pose/napi-rs/main/examples/napi-compat-mode/__test__/serde/ser.spec.ts.snap -------------------------------------------------------------------------------- /examples/napi/__test__/tsfn-error.js: -------------------------------------------------------------------------------- 1 | const { threadsafeFunctionFatalModeError } = require('../index.node') 2 | 3 | threadsafeFunctionFatalModeError(() => {}) 4 | -------------------------------------------------------------------------------- /crates/backend/README.md: -------------------------------------------------------------------------------- 1 | # napi-derive-backend 2 | 3 | Take care the ast parsing from `napi-derive` and generate "bridge" runtime code for both nodejs and rust. 4 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/napi8/sub-process-removable.js: -------------------------------------------------------------------------------- 1 | const bindings = require('../../index.node') 2 | 3 | bindings.testAddRemovableAsyncCleanupHook() 4 | -------------------------------------------------------------------------------- /crates/napi/src/bindgen_runtime/js_values/function.rs: -------------------------------------------------------------------------------- 1 | use super::ValidateNapiValue; 2 | 3 | pub use crate::JsFunction; 4 | 5 | impl ValidateNapiValue for JsFunction {} 6 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": ["cli", "triples"], 3 | "version": "independent", 4 | "useWorkspaces": true, 5 | "npmClient": "yarn", 6 | "message": "chore: publish" 7 | } 8 | -------------------------------------------------------------------------------- /bench/src/noop.rs: -------------------------------------------------------------------------------- 1 | use napi::{ContextlessResult, Env, JsUndefined}; 2 | 3 | #[contextless_function] 4 | pub fn noop(_env: Env) -> ContextlessResult { 5 | Ok(None) 6 | } 7 | -------------------------------------------------------------------------------- /examples/napi/src/promise.rs: -------------------------------------------------------------------------------- 1 | use napi::bindgen_prelude::*; 2 | 3 | #[napi] 4 | pub async fn async_plus_100(p: Promise) -> Result { 5 | let v = p.await?; 6 | Ok(v + 100) 7 | } 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | indent_size = 2 9 | indent_style = space 10 | -------------------------------------------------------------------------------- /cli/src/new/lib-rs.ts: -------------------------------------------------------------------------------- 1 | export const LibRs = `#![deny(clippy::all)] 2 | 3 | #[macro_use] 4 | extern crate napi_derive; 5 | 6 | #[napi] 7 | pub fn sum(a: i32, b: i32) -> i32 { 8 | a + b 9 | } 10 | ` 11 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/napi4/tsfn-throw.js: -------------------------------------------------------------------------------- 1 | const bindings = require('../../index.node') 2 | 3 | bindings.testThreadsafeFunction(() => { 4 | throw Error('Throw in thread safe function') 5 | }) 6 | -------------------------------------------------------------------------------- /crates/build/src/macos.rs: -------------------------------------------------------------------------------- 1 | pub fn setup() { 2 | println!("cargo:rustc-cdylib-link-arg=-Wl"); 3 | println!("cargo:rustc-cdylib-link-arg=-undefined"); 4 | println!("cargo:rustc-cdylib-link-arg=dynamic_lookup"); 5 | } 6 | -------------------------------------------------------------------------------- /bench/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ESNext", 5 | "rootDir": "." 6 | }, 7 | "include": ["."], 8 | "exclude": ["target", "src"] 9 | } 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | lib 4 | esm 5 | next 6 | coverage 7 | output 8 | static 9 | temp 10 | .nyc_output 11 | *.d.ts 12 | __mock__ 13 | target 14 | scripts 15 | triples/index.js 16 | rollup.config.js 17 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/src/napi_version.rs: -------------------------------------------------------------------------------- 1 | use napi::{CallContext, JsNumber, Result}; 2 | 3 | #[js_function] 4 | pub fn get_napi_version(ctx: CallContext) -> Result { 5 | ctx.env.create_uint32(ctx.env.get_napi_version()?) 6 | } 7 | -------------------------------------------------------------------------------- /examples/napi/tests/build_error_tests/mod.rs: -------------------------------------------------------------------------------- 1 | //! Include the test files here so they can be formatted properly with `cargo fmt` 2 | 3 | pub mod ts_arg_type_1; 4 | pub mod ts_arg_type_2; 5 | pub mod ts_arg_type_3; 6 | pub mod ts_arg_type_4; 7 | -------------------------------------------------------------------------------- /examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "include": ["."], 4 | "compilerOptions": { 5 | "outDir": "./dist", 6 | "rootDir": ".", 7 | "target": "ES2015" 8 | }, 9 | "exclude": ["dist"] 10 | } 11 | -------------------------------------------------------------------------------- /cli/src/new/npmignore.ts: -------------------------------------------------------------------------------- 1 | export const NPMIgnoreFiles = `target 2 | Cargo.lock 3 | .cargo 4 | .github 5 | npm 6 | .eslintrc 7 | .prettierignore 8 | rustfmt.toml 9 | yarn.lock 10 | *.node 11 | .yarn 12 | __test__ 13 | renovate.json 14 | ` 15 | -------------------------------------------------------------------------------- /crates/napi/src/js_values/value.rs: -------------------------------------------------------------------------------- 1 | use crate::sys; 2 | 3 | use super::ValueType; 4 | 5 | #[derive(Clone, Copy)] 6 | pub struct Value { 7 | pub env: sys::napi_env, 8 | pub value: sys::napi_value, 9 | pub value_type: ValueType, 10 | } 11 | -------------------------------------------------------------------------------- /examples/napi/src/number.rs: -------------------------------------------------------------------------------- 1 | #[napi] 2 | fn add(a: u32, b: u32) -> u32 { 3 | a + b 4 | } 5 | 6 | #[napi(strict)] 7 | fn fibonacci(n: u32) -> u32 { 8 | match n { 9 | 1 | 2 => 1, 10 | _ => fibonacci(n - 1) + fibonacci(n - 2), 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.root-lint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": [ 4 | "./ava.config.js", 5 | "./generate-triplelist.js", 6 | "./triples/index.js", 7 | "./memory-testing/*.js", 8 | "./memory-testing/*.mjs" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /bench/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bench", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "node ../cli/scripts/index.js build --js false --release" 7 | }, 8 | "devDependencies": { 9 | "benny": "^3.7.1" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/napi/tests/build_error_tests/ts_arg_type_2.stderr: -------------------------------------------------------------------------------- 1 | error: Expected a string literal 2 | --> tests/build_error_tests/ts_arg_type_2.rs:7:41 3 | | 4 | 7 | pub fn add(u: u32, #[napi(ts_arg_type = 32)] f: Option) { 5 | | ^^ 6 | -------------------------------------------------------------------------------- /examples/napi/__test__/typegen.spec.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs' 2 | import { join } from 'path' 3 | 4 | import test from 'ava' 5 | 6 | test('should generate correct type def file', (t) => { 7 | t.snapshot(readFileSync(join(__dirname, '..', 'index.d.ts'), 'utf8')) 8 | }) 9 | -------------------------------------------------------------------------------- /examples/napi/tests/build_error_tests/ts_arg_type_4.stderr: -------------------------------------------------------------------------------- 1 | error: Did not find 'ts_arg_type' 2 | --> tests/build_error_tests/ts_arg_type_4.rs:7:27 3 | | 4 | 7 | pub fn add(u: u32, #[napi(not_expected = "obj")] f: Option) { 5 | | ^^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /memory-testing/index.mjs: -------------------------------------------------------------------------------- 1 | import { createSuite } from './test-util.mjs' 2 | 3 | await createSuite('reference') 4 | await createSuite('tokio-future') 5 | await createSuite('serde') 6 | await createSuite('tsfn') 7 | await createSuite('buffer') 8 | await createSuite('returns-future') 9 | -------------------------------------------------------------------------------- /examples/napi/tests/build_error_tests/ts_arg_type_3.stderr: -------------------------------------------------------------------------------- 1 | error: Expected Name Value 2 | --> tests/build_error_tests/ts_arg_type_3.rs:7:27 3 | | 4 | 7 | pub fn add(u: u32, #[napi(ts_arg_type, not_expected)] f: Option) { 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /triples/index.d.ts: -------------------------------------------------------------------------------- 1 | interface Triple { 2 | platform: string 3 | arch: string 4 | abi: string | null 5 | platformArchABI: string 6 | raw: string 7 | } 8 | 9 | export const platformArchTriples: { 10 | [index: string]: { 11 | [index: string]: Triple[] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/napi/src/nullable.rs: -------------------------------------------------------------------------------- 1 | use napi::bindgen_prelude::*; 2 | 3 | #[napi] 4 | fn map_option(val: Option) -> Option { 5 | val.map(|v| v + 1) 6 | } 7 | 8 | #[napi] 9 | fn return_null() -> Null { 10 | Null 11 | } 12 | 13 | #[napi] 14 | fn return_undefined() -> Undefined {} 15 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | target 2 | build 3 | sys 4 | napi 5 | napi-derive 6 | napi-derive-example 7 | test_module 8 | .yarnrc 9 | Cargo.lock 10 | rustfmt.toml 11 | .github 12 | .dockerignore 13 | .eslintignore 14 | .eslintrc.yml 15 | Cargo.toml 16 | alpine.Dockerfile 17 | arm64v8.Dockerfile 18 | yarn.lock 19 | -------------------------------------------------------------------------------- /examples/napi/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "include": ["."], 4 | "compilerOptions": { 5 | "outDir": "./dist", 6 | "rootDir": "__test__", 7 | "target": "ES2018", 8 | "skipLibCheck": false 9 | }, 10 | "exclude": ["dist", "electron.js"] 11 | } 12 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base", ":preserveSemverRanges"], 3 | "packageRules": [ 4 | { 5 | "automerge": true, 6 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"] 7 | } 8 | ], 9 | "lockFileMaintenance": { 10 | "enabled": true, 11 | "extends": ["schedule:monthly"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /armhf.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM multiarch/ubuntu-core:armhf-focal 2 | 3 | ARG NODE_VERSION=14 4 | 5 | RUN apt-get update && \ 6 | apt-get install -y ca-certificates gnupg2 curl apt-transport-https && \ 7 | curl -sL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash - && \ 8 | apt-get install -y nodejs && \ 9 | npm install -g yarn pnpm 10 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/get-napi-version.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | const bindings = require('../index.node') 4 | 5 | test('should get napi version', (t) => { 6 | const napiVersion = bindings.getNapiVersion() 7 | t.true(typeof napiVersion === 'number') 8 | t.is(`${napiVersion}`, process.versions.napi!) 9 | }) 10 | -------------------------------------------------------------------------------- /aarch64.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM multiarch/ubuntu-core:arm64-focal 2 | 3 | ARG NODE_VERSION=14 4 | 5 | RUN apt-get update && \ 6 | apt-get install -y ca-certificates gnupg2 curl apt-transport-https && \ 7 | curl -sL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash - && \ 8 | apt-get install -y nodejs && \ 9 | npm install -g yarn pnpm 10 | -------------------------------------------------------------------------------- /crates/napi/src/bindgen_runtime/error.rs: -------------------------------------------------------------------------------- 1 | #[doc(hidden)] 2 | #[macro_export] 3 | macro_rules! check_status_or_throw { 4 | ($env:expr, $code:expr, $($msg:tt)*) => { 5 | if let Err(e) = $crate::check_status!($code, $($msg)*) { 6 | #[allow(unused_unsafe)] 7 | unsafe { $crate::JsError::from(e).throw_into($env) }; 8 | } 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /cli/src/help.ts: -------------------------------------------------------------------------------- 1 | import { Command } from 'clipanion' 2 | 3 | /** 4 | * A command that prints the usage of all commands. 5 | * 6 | * Paths: `-h`, `--help` 7 | */ 8 | export class HelpCommand extends Command { 9 | static paths = [[`-h`], [`--help`]] 10 | async execute() { 11 | await this.context.stdout.write(this.cli.usage()) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /cli/src/new/cargo-config.ts: -------------------------------------------------------------------------------- 1 | export const createCargoConfig = (enableLinuxArm8Musl: boolean) => { 2 | const result: string[] = [] 3 | if (enableLinuxArm8Musl) { 4 | result.push(`[target.aarch64-unknown-linux-musl] 5 | linker = "aarch64-linux-musl-gcc" 6 | rustflags = ["-C", "target-feature=-crt-static"]`) 7 | } 8 | return result.join('\n') 9 | } 10 | -------------------------------------------------------------------------------- /crates/build/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod android; 2 | mod macos; 3 | 4 | pub fn setup() { 5 | println!("cargo:rerun-if-env-changed=DEBUG_GENERATED_CODE"); 6 | match std::env::var("CARGO_CFG_TARGET_OS").as_deref() { 7 | Ok("macos") => { 8 | macos::setup(); 9 | } 10 | Ok("android") => if android::setup().is_ok() {}, 11 | _ => {} 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /crates/napi/src/cleanup_env.rs: -------------------------------------------------------------------------------- 1 | pub(crate) struct CleanupEnvHookData { 2 | pub(crate) data: T, 3 | pub(crate) hook: Box, 4 | } 5 | 6 | /// Created by `Env::add_env_cleanup_hook` 7 | /// And used by `Env::remove_env_cleanup_hook` 8 | #[derive(Clone, Copy)] 9 | pub struct CleanupEnvHook(pub(crate) *mut CleanupEnvHookData); 10 | -------------------------------------------------------------------------------- /examples/napi/src/map.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | #[napi] 4 | fn get_mapping() -> HashMap { 5 | let mut map = HashMap::new(); 6 | map.insert("a".to_string(), 101); 7 | map.insert("b".to_string(), 102); 8 | map 9 | } 10 | 11 | #[napi] 12 | fn sum_mapping(nums: HashMap) -> u32 { 13 | nums.into_values().sum() 14 | } 15 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/src/napi7/mod.rs: -------------------------------------------------------------------------------- 1 | use napi::{JsObject, Result}; 2 | 3 | mod buffer; 4 | 5 | use buffer::*; 6 | 7 | pub fn register_js(exports: &mut JsObject) -> Result<()> { 8 | exports.create_named_method("testDetachArrayBuffer", detach_arraybuffer)?; 9 | exports.create_named_method("testIsDetachedArrayBuffer", is_detach_arraybuffer)?; 10 | Ok(()) 11 | } 12 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | 5 | *.ts text eol=lf merge=union 6 | *.tsx text eol=lf merge=union 7 | *.rs text eol=lf merge=union 8 | *.js text eol=lf merge=union 9 | *.json text eol=lf merge=union 10 | *.debug text eol=lf merge=union 11 | 12 | .yarn/releases/*.js linguist-detectable=false 13 | -------------------------------------------------------------------------------- /bench/noop.ts: -------------------------------------------------------------------------------- 1 | import b from 'benny' 2 | 3 | const { noop: napiNoop } = require('./index.node') 4 | 5 | function noop() {} 6 | 7 | export const benchNoop = () => 8 | b.suite( 9 | 'noop', 10 | b.add('napi-rs', () => { 11 | napiNoop() 12 | }), 13 | b.add('JavaScript', () => { 14 | noop() 15 | }), 16 | 17 | b.cycle(), 18 | b.complete(), 19 | ) 20 | -------------------------------------------------------------------------------- /crates/napi/src/js_values/tagged_object.rs: -------------------------------------------------------------------------------- 1 | use std::any::TypeId; 2 | 3 | #[repr(C)] 4 | pub struct TaggedObject { 5 | type_id: TypeId, 6 | pub(crate) object: Option, 7 | } 8 | 9 | impl TaggedObject { 10 | pub fn new(object: T) -> Self { 11 | TaggedObject { 12 | type_id: TypeId::of::(), 13 | object: Some(object), 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/src/tokio_rt/mod.rs: -------------------------------------------------------------------------------- 1 | use napi::{JsObject, Result}; 2 | 3 | mod read_file; 4 | 5 | use read_file::*; 6 | 7 | pub fn register_js(exports: &mut JsObject) -> Result<()> { 8 | exports.create_named_method("testExecuteTokioReadfile", test_execute_tokio_readfile)?; 9 | exports.create_named_method("testTokioError", error_from_tokio_future)?; 10 | Ok(()) 11 | } 12 | -------------------------------------------------------------------------------- /examples/napi/__test__/object-attr.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | import { NotWritableClass } from '../index' 4 | 5 | test('Not Writable Class', (t) => { 6 | const obj = new NotWritableClass('1') 7 | t.throws(() => { 8 | obj.name = '2' 9 | }) 10 | obj.setName('2') 11 | t.is(obj.name, '2') 12 | t.throws(() => { 13 | obj.setName = () => {} 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /examples/napi/src/class_factory.rs: -------------------------------------------------------------------------------- 1 | #[napi] 2 | pub struct ClassWithFactory { 3 | pub name: String, 4 | } 5 | 6 | #[napi] 7 | impl ClassWithFactory { 8 | #[napi(factory)] 9 | pub fn with_name(name: String) -> Self { 10 | Self { name } 11 | } 12 | 13 | #[napi] 14 | pub fn set_name(&mut self, name: String) -> &Self { 15 | self.name = name; 16 | self 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/napi/tests/build_error_tests/ts_arg_type_1.stderr: -------------------------------------------------------------------------------- 1 | error: Found a 'ts_args_type'="u: number, fn: object" override. Cannot use 'ts_arg_type' at the same time since they are mutually exclusive. 2 | --> tests/build_error_tests/ts_arg_type_1.rs:7:22 3 | | 4 | 7 | pub fn add(u: u32, #[napi(ts_arg_type = "object")] f: Option) { 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "./crates/backend", 5 | "./crates/build", 6 | "./crates/macro", 7 | "./crates/napi", 8 | "./crates/sys", 9 | "./examples/napi", 10 | "./examples/napi-compat-mode", 11 | "./examples/binary", 12 | "./bench", 13 | "./memory-testing", 14 | ] 15 | 16 | exclude = ["./testing"] 17 | 18 | [profile.release] 19 | lto = true 20 | -------------------------------------------------------------------------------- /examples/napi/src/symbol.rs: -------------------------------------------------------------------------------- 1 | use napi::{bindgen_prelude::*, Env, JsObject, JsSymbol}; 2 | 3 | #[napi] 4 | pub fn set_symbol_in_obj(env: Env, symbol: JsSymbol) -> Result { 5 | let mut obj = env.create_object()?; 6 | obj.set_property(symbol, env.create_string("a symbol")?)?; 7 | Ok(obj) 8 | } 9 | 10 | #[napi] 11 | pub fn create_symbol() -> Symbol { 12 | Symbol::new("a symbol".to_owned()) 13 | } 14 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/src/napi5/mod.rs: -------------------------------------------------------------------------------- 1 | use napi::{JsObject, Result}; 2 | 3 | mod date; 4 | 5 | pub fn register_js(exports: &mut JsObject) -> Result<()> { 6 | exports.create_named_method("testObjectIsDate", date::test_object_is_date)?; 7 | exports.create_named_method("testCreateDate", date::test_create_date)?; 8 | exports.create_named_method("testGetDateValue", date::test_get_date_value)?; 9 | Ok(()) 10 | } 11 | -------------------------------------------------------------------------------- /examples/napi/__test__/unload.spec.js: -------------------------------------------------------------------------------- 1 | // use the commonjs syntax to prevent compiler from transpiling the module syntax 2 | 3 | const test = require('ava').default 4 | 5 | test('unload module', (t) => { 6 | const { add } = require('../index.node') 7 | t.is(add(1, 2), 3) 8 | delete require.cache[require.resolve('../index.node')] 9 | const { add: add2 } = require('../index.node') 10 | t.is(add2(1, 2), 3) 11 | }) 12 | -------------------------------------------------------------------------------- /examples/napi/tests/build_error_tests/ts_arg_type_2.rs: -------------------------------------------------------------------------------- 1 | //! This is testing that `#[napi(ts_arg_type="...")]` fails if the argument for `ts_arg_type` 2 | //! is not a string literal. 3 | 4 | use napi_derive::napi; 5 | 6 | #[napi] 7 | pub fn add(u: u32, #[napi(ts_arg_type = 32)] f: Option) { 8 | println!("Hello, world! {f:?}-{u}"); 9 | } 10 | 11 | // Needed for the trybuild tests. 12 | #[allow(unused)] 13 | fn main() {} 14 | -------------------------------------------------------------------------------- /examples/napi/tests/build_error_tests/ts_arg_type_4.rs: -------------------------------------------------------------------------------- 1 | //! This is testing that `#[napi(ts_arg_type="...")]` fails if the attribute is something other than 2 | //! `ts_arg_type` 3 | 4 | use napi_derive::napi; 5 | 6 | #[napi] 7 | pub fn add(u: u32, #[napi(not_expected = "obj")] f: Option) { 8 | println!("Hello, world! {f:?}-{u}"); 9 | } 10 | 11 | // Needed for the trybuild tests. 12 | #[allow(unused)] 13 | fn main() {} 14 | -------------------------------------------------------------------------------- /debian-zig.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian 2 | 3 | ARG ZIG_VERSION=0.10.0 4 | 5 | RUN wget https://ziglang.org/download/${ZIG_VERSION}/zig-linux-x86_64-${ZIG_VERSION}.tar.xz && \ 6 | tar -xvf zig-linux-x86_64-${ZIG_VERSION}.tar.xz && \ 7 | mv zig-linux-x86_64-${ZIG_VERSION} /usr/local/zig && \ 8 | ln -sf /usr/local/zig/zig /usr/local/bin/zig && \ 9 | rm zig-linux-x86_64-${ZIG_VERSION}.tar.xz 10 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/src/napi8/object.rs: -------------------------------------------------------------------------------- 1 | use napi::*; 2 | 3 | #[js_function(1)] 4 | pub fn seal_object(ctx: CallContext) -> Result { 5 | let mut obj: JsObject = ctx.get(0)?; 6 | obj.seal()?; 7 | ctx.env.get_undefined() 8 | } 9 | 10 | #[js_function(1)] 11 | pub fn freeze_object(ctx: CallContext) -> Result { 12 | let mut obj: JsObject = ctx.get(0)?; 13 | obj.freeze()?; 14 | ctx.env.get_undefined() 15 | } 16 | -------------------------------------------------------------------------------- /examples/napi/tests/build_error_tests/ts_arg_type_3.rs: -------------------------------------------------------------------------------- 1 | //! This is testing that `#[napi(ts_arg_type="...")]` fails if the attribute is not a `MetaNameValue` 2 | //! i.e. it's a name value pair. 3 | 4 | use napi_derive::napi; 5 | 6 | #[napi] 7 | pub fn add(u: u32, #[napi(ts_arg_type, not_expected)] f: Option) { 8 | println!("Hello, world! {f:?}-{u}"); 9 | } 10 | 11 | // Needed for the trybuild tests. 12 | #[allow(unused)] 13 | fn main() {} 14 | -------------------------------------------------------------------------------- /memory-testing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "memory-testing", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "node ../cli/scripts/index.js build --release --js false" 7 | }, 8 | "dependencies": { 9 | "colorette": "^2.0.19", 10 | "dockerode": "^3.3.4", 11 | "pretty-bytes": "^6.0.0", 12 | "table": "^6.8.1" 13 | }, 14 | "devDependencies": { 15 | "@types/dockerode": "^3.3.14" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /bench/plus.ts: -------------------------------------------------------------------------------- 1 | import b from 'benny' 2 | 3 | const { plus } = require('./index.node') 4 | 5 | function plusJavascript(a: number, b: number) { 6 | return a + b 7 | } 8 | 9 | export const benchPlus = () => 10 | b.suite( 11 | 'Plus number', 12 | b.add('napi-rs', () => { 13 | plus(1, 100) 14 | }), 15 | b.add('JavaScript', () => { 16 | plusJavascript(1, 100) 17 | }), 18 | 19 | b.cycle(), 20 | b.complete(), 21 | ) 22 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/napi4/tsfn-dua-instance.js: -------------------------------------------------------------------------------- 1 | const bindings = require('../../index.node') 2 | 3 | async function main() { 4 | await Promise.resolve() 5 | const a1 = new bindings.A((err, s) => { 6 | console.info(s) 7 | a1.unref() 8 | }) 9 | const a2 = new bindings.A((err, s) => { 10 | console.info(s) 11 | a2.unref() 12 | }) 13 | a1.call() 14 | a2.call() 15 | } 16 | 17 | main().catch((e) => { 18 | console.error(e) 19 | }) 20 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/src/napi7/buffer.rs: -------------------------------------------------------------------------------- 1 | use napi::*; 2 | 3 | #[js_function(1)] 4 | pub fn detach_arraybuffer(ctx: CallContext) -> Result { 5 | let input = ctx.get::(0)?; 6 | input.detach()?; 7 | ctx.env.get_undefined() 8 | } 9 | 10 | #[js_function(1)] 11 | pub fn is_detach_arraybuffer(ctx: CallContext) -> Result { 12 | let input = ctx.get::(0)?; 13 | ctx.env.get_boolean(input.is_detached()?) 14 | } 15 | -------------------------------------------------------------------------------- /examples/napi/tests/build_error_tests/ts_arg_type_1.rs: -------------------------------------------------------------------------------- 1 | //! This is testing that `#[napi(ts_args_type="...")]` and `#[napi(ts_arg_type="...")]` 2 | //! are mutually exclusive 3 | 4 | use napi_derive::napi; 5 | 6 | #[napi(ts_args_type = "u: number, fn: object")] 7 | pub fn add(u: u32, #[napi(ts_arg_type = "object")] f: Option) { 8 | println!("Hello, world! {f:?}-{u}"); 9 | } 10 | 11 | // Needed for the trybuild tests. 12 | #[allow(unused)] 13 | fn main() {} 14 | -------------------------------------------------------------------------------- /bench/query.ts: -------------------------------------------------------------------------------- 1 | import b from 'benny' 2 | 3 | const { query, engine } = require('./index.node') 4 | 5 | const e = engine('model A {}') 6 | 7 | export const benchQuery = () => 8 | b.suite( 9 | 'Query', 10 | b.add('query * 100', async () => { 11 | await Promise.all(Array.from({ length: 100 }).map(() => query(e))) 12 | }), 13 | b.add('query * 1', async () => { 14 | await query(e) 15 | }), 16 | 17 | b.cycle(), 18 | b.complete(), 19 | ) 20 | -------------------------------------------------------------------------------- /examples/napi/tests/README.md: -------------------------------------------------------------------------------- 1 | ## [trybuild](https://docs.rs/trybuild/latest/trybuild/) tests 2 | 3 | Set of tests to exercise various macro expansion errors. 4 | 5 | ### Notes on adding a new test 6 | 7 | - Make sure to include the new test in [build_error_tests/mod.rs](./build_error_tests/mod.rs) so it gets included as 8 | part of the crate for formatting purposes. 9 | 10 | ### Running the tests 11 | 12 | > yarn test:macro 13 | 14 | or 15 | 16 | > cargo test -p napi-examples 17 | -------------------------------------------------------------------------------- /bench/src/plus.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryInto; 2 | 3 | use napi::{CallContext, JsNumber, JsObject, Result}; 4 | 5 | #[js_function(2)] 6 | fn bench_plus(ctx: CallContext) -> Result { 7 | let a: u32 = ctx.get::(0)?.try_into()?; 8 | let b: u32 = ctx.get::(1)?.try_into()?; 9 | ctx.env.create_uint32(a + b) 10 | } 11 | 12 | pub fn register_js(exports: &mut JsObject) -> Result<()> { 13 | exports.create_named_method("plus", bench_plus)?; 14 | Ok(()) 15 | } 16 | -------------------------------------------------------------------------------- /crates/build/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["LongYinan "] 3 | description = "N-API build support" 4 | edition = "2021" 5 | include = ["src/**/*", "Cargo.toml", "README.md", "LICENSE"] 6 | keywords = ["NodeJS", "FFI", "NAPI", "n-api"] 7 | license = "MIT" 8 | name = "napi-build" 9 | readme = "README.md" 10 | repository = "https://github.com/napi-rs/napi-rs" 11 | rust-version = "1.57" 12 | version = "2.0.1" 13 | 14 | [package.metadata.workspaces] 15 | independent = true 16 | -------------------------------------------------------------------------------- /crates/napi/src/js_values/undefined.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | bindgen_runtime::{TypeName, ValidateNapiValue}, 3 | ValueType, 4 | }; 5 | 6 | use super::Value; 7 | 8 | #[derive(Clone, Copy)] 9 | pub struct JsUndefined(pub(crate) Value); 10 | 11 | impl TypeName for JsUndefined { 12 | fn type_name() -> &'static str { 13 | "undefined" 14 | } 15 | 16 | fn value_type() -> crate::ValueType { 17 | ValueType::Undefined 18 | } 19 | } 20 | 21 | impl ValidateNapiValue for JsUndefined {} 22 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/either.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | const bindings = require('../index.node') 4 | 5 | test('either should work', (t) => { 6 | const fixture = 'napi' 7 | t.is(bindings.eitherNumberString(1), 101) 8 | t.is(bindings.eitherNumberString(fixture), `Either::B(${fixture})`) 9 | }) 10 | 11 | test('dynamic argument length should work', (t) => { 12 | t.is(bindings.dynamicArgumentLength(1), 101) 13 | t.is(bindings.dynamicArgumentLength(), 42) 14 | }) 15 | -------------------------------------------------------------------------------- /alpine-zig.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine 2 | 3 | ARG ZIG_VERSION=0.10.0 4 | 5 | RUN apk add xz && \ 6 | rustup target add x86_64-unknown-linux-gnu && \ 7 | wget https://ziglang.org/download/${ZIG_VERSION}/zig-linux-x86_64-${ZIG_VERSION}.tar.xz && \ 8 | tar -xvf zig-linux-x86_64-${ZIG_VERSION}.tar.xz && \ 9 | mv zig-linux-x86_64-${ZIG_VERSION} /usr/local/zig && \ 10 | ln -sf /usr/local/zig/zig /usr/local/bin/zig && \ 11 | rm zig-linux-x86_64-${ZIG_VERSION}.tar.xz 12 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/cleanup-env.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | const bindings = require('../index.node') 4 | 5 | test('should be able to add cleanup hook', (t) => { 6 | t.notThrows(() => { 7 | const ret = bindings.addCleanupHook() 8 | t.is(typeof ret, 'object') 9 | }) 10 | }) 11 | 12 | test('should be able to remove cleanup hook', (t) => { 13 | t.notThrows(() => { 14 | const ret = bindings.addCleanupHook() 15 | bindings.removeCleanupHook(ret) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /examples/napi/tests/macro_tests.rs: -------------------------------------------------------------------------------- 1 | // `error_try_builds` is not really a feature. 2 | // This is used just to make the test files part of the crate so that they can get included by things 3 | // like `cargo fmt` for example, BUT not be part of compilation when running `cargo test`. 4 | #[cfg(feature = "error_try_builds")] 5 | mod build_error_tests; 6 | 7 | #[test] 8 | fn run_build_error_tests() { 9 | let t = trybuild::TestCases::new(); 10 | t.compile_fail("tests/build_error_tests/ts_arg_type_*.rs"); 11 | } 12 | -------------------------------------------------------------------------------- /examples/napi/src/external.rs: -------------------------------------------------------------------------------- 1 | use napi::bindgen_prelude::*; 2 | 3 | #[napi] 4 | pub fn create_external(size: u32) -> External { 5 | External::new(size) 6 | } 7 | 8 | #[napi] 9 | pub fn create_external_string(content: String) -> External { 10 | External::new(content) 11 | } 12 | 13 | #[napi] 14 | pub fn get_external(external: External) -> u32 { 15 | *external 16 | } 17 | 18 | #[napi] 19 | pub fn mutate_external(mut external: External, new_val: u32) { 20 | *external = new_val; 21 | } 22 | -------------------------------------------------------------------------------- /bench/buffer.ts: -------------------------------------------------------------------------------- 1 | import b from 'benny' 2 | 3 | const { benchCreateBuffer } = require('./index.node') 4 | 5 | function createBuffer() { 6 | const buf = Buffer.allocUnsafe(1024) 7 | buf[0] = 1 8 | buf[1] = 2 9 | return buf 10 | } 11 | 12 | export const benchBuffer = () => 13 | b.suite( 14 | 'Create buffer', 15 | b.add('napi-rs', () => { 16 | benchCreateBuffer() 17 | }), 18 | b.add('JavaScript', () => { 19 | createBuffer() 20 | }), 21 | b.cycle(), 22 | b.complete(), 23 | ) 24 | -------------------------------------------------------------------------------- /bench/src/buffer.rs: -------------------------------------------------------------------------------- 1 | use napi::{ContextlessResult, Env, JsBuffer, JsObject, Result}; 2 | 3 | #[contextless_function] 4 | pub fn bench_create_buffer(env: Env) -> ContextlessResult { 5 | let mut output = Vec::with_capacity(1024); 6 | output.push(1); 7 | output.push(2); 8 | env 9 | .create_buffer_with_data(output) 10 | .map(|v| Some(v.into_raw())) 11 | } 12 | 13 | pub fn register_js(exports: &mut JsObject) -> Result<()> { 14 | exports.create_named_method("benchCreateBuffer", bench_create_buffer)?; 15 | Ok(()) 16 | } 17 | -------------------------------------------------------------------------------- /cli/src/update-package.ts: -------------------------------------------------------------------------------- 1 | import { debugFactory } from './debug' 2 | import { writeFileAsync, fileExists } from './utils' 3 | 4 | const debug = debugFactory('update-package') 5 | 6 | export async function updatePackageJson( 7 | path: string, 8 | partial: Record, 9 | ) { 10 | const exists = await fileExists(path) 11 | if (!exists) { 12 | debug(`File not exists ${path}`) 13 | return 14 | } 15 | const old = require(path) 16 | await writeFileAsync(path, JSON.stringify({ ...old, ...partial }, null, 2)) 17 | } 18 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/napi4/deferred.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | const bindings = require('../../index.node') 4 | 5 | test('should resolve deferred from background thread', async (t) => { 6 | const promise = bindings.testDeferred(false) 7 | t.assert(promise instanceof Promise) 8 | 9 | const result = await promise 10 | t.is(result, 15) 11 | }) 12 | 13 | test('should reject deferred from background thread', async (t) => { 14 | await t.throwsAsync(() => bindings.testDeferred(true), { message: 'Fail' }) 15 | }) 16 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/create-external.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | const bindings = require('../index.node') 4 | 5 | test('should create external object and get it back', (t) => { 6 | const fixture = 42 7 | const externalObject = bindings.createExternal(42) 8 | t.is(bindings.getExternalCount(externalObject), fixture) 9 | }) 10 | 11 | test('should create external with size hint', (t) => { 12 | const fixture = 42 13 | const externalObject = bindings.createExternalWithHint(42) 14 | t.is(bindings.getExternalCount(externalObject), fixture) 15 | }) 16 | -------------------------------------------------------------------------------- /crates/build/src/android.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs; 3 | use std::io::{Error, Write}; 4 | use std::path; 5 | 6 | // Workaround from https://github.com/rust-lang/rust/pull/85806#issuecomment-1096266946 7 | pub fn setup() -> Result<(), Error> { 8 | let out_dir = env::var("OUT_DIR").expect("OUT_DIR not set"); 9 | let mut dist = path::PathBuf::from(&out_dir); 10 | dist.push("libgcc.a"); 11 | let mut libgcc = fs::File::create(&dist)?; 12 | let _ = libgcc.write(b"INPUT(-lunwind)")?; 13 | drop(libgcc); 14 | println!("cargo:rustc-link-search={}", &out_dir); 15 | Ok(()) 16 | } 17 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/src/napi4/deferred.rs: -------------------------------------------------------------------------------- 1 | use std::thread; 2 | 3 | use napi::{CallContext, Error, JsObject, Result}; 4 | 5 | #[js_function(1)] 6 | pub fn test_deferred(ctx: CallContext) -> Result { 7 | let reject: bool = ctx.get(0)?; 8 | let (deferred, promise) = ctx.env.create_deferred()?; 9 | 10 | thread::spawn(move || { 11 | thread::sleep(std::time::Duration::from_millis(10)); 12 | if reject { 13 | deferred.reject(Error::from_reason("Fail")); 14 | } else { 15 | deferred.resolve(|_| Ok(15)); 16 | } 17 | }); 18 | 19 | Ok(promise) 20 | } 21 | -------------------------------------------------------------------------------- /bench/create-array.ts: -------------------------------------------------------------------------------- 1 | import b from 'benny' 2 | 3 | const { 4 | createArrayJson, 5 | createArray, 6 | createArrayWithSerdeTrait, 7 | } = require('./index.node') 8 | 9 | export const benchCreateArray = () => 10 | b.suite( 11 | 'createArray', 12 | b.add('createArrayJson', () => { 13 | JSON.parse(createArrayJson()) 14 | }), 15 | b.add('create array for loop', () => { 16 | createArray() 17 | }), 18 | 19 | b.add('create array with serde trait', () => { 20 | createArrayWithSerdeTrait() 21 | }), 22 | 23 | b.cycle(), 24 | b.complete(), 25 | ) 26 | -------------------------------------------------------------------------------- /examples/napi/src/date.rs: -------------------------------------------------------------------------------- 1 | use chrono::{Duration, Utc}; 2 | use napi::bindgen_prelude::*; 3 | 4 | #[napi] 5 | fn date_to_number(input: Date) -> Result { 6 | input.value_of() 7 | } 8 | 9 | #[napi] 10 | fn chrono_date_to_millis(input: chrono::DateTime) -> i64 { 11 | input.timestamp_millis() 12 | } 13 | 14 | #[napi] 15 | fn chrono_date_add_1_minute(input: chrono::DateTime) -> chrono::DateTime { 16 | input + Duration::minutes(1) 17 | } 18 | 19 | #[napi(object)] 20 | pub struct Dates { 21 | pub start: chrono::DateTime, 22 | pub end: Option>, 23 | } 24 | -------------------------------------------------------------------------------- /cli/src/new/cargo.ts: -------------------------------------------------------------------------------- 1 | export const createCargoContent = (name: string) => `[package] 2 | edition = "2021" 3 | name = "${name.replace('@', '').replace('/', '_').toLowerCase()}" 4 | version = "0.0.0" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | # Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix 11 | napi = { version = "NAPI_VERSION", default-features = false, features = ["napi4"] } 12 | napi-derive = "NAPI_DERIVE_VERSION" 13 | 14 | [build-dependencies] 15 | napi-build = "NAPI_BUILD_VERSION" 16 | 17 | [profile.release] 18 | lto = true 19 | ` 20 | -------------------------------------------------------------------------------- /examples/napi/src/string.rs: -------------------------------------------------------------------------------- 1 | use napi::bindgen_prelude::*; 2 | 3 | #[napi] 4 | fn contains(source: String, target: String) -> bool { 5 | source.contains(&target) 6 | } 7 | 8 | #[napi] 9 | fn concat_str(mut s: String) -> String { 10 | s.push_str(" + Rust 🦀 string!"); 11 | s 12 | } 13 | 14 | #[napi] 15 | fn concat_utf16(s: Utf16String) -> Utf16String { 16 | Utf16String::from(format!("{} + Rust 🦀 string!", s)) 17 | } 18 | 19 | #[napi] 20 | fn concat_latin1(s: Latin1String) -> String { 21 | format!("{} + Rust 🦀 string!", s) 22 | } 23 | 24 | #[napi] 25 | pub fn roundtrip_str(s: String) -> String { 26 | s 27 | } 28 | -------------------------------------------------------------------------------- /memory-testing/tsfn.mjs: -------------------------------------------------------------------------------- 1 | import { createRequire } from 'module' 2 | import { setTimeout } from 'timers/promises' 3 | 4 | import { displayMemoryUsageFromNode } from './util.mjs' 5 | 6 | const initialMemoryUsage = process.memoryUsage() 7 | 8 | const require = createRequire(import.meta.url) 9 | 10 | const api = require(`./index.node`) 11 | 12 | let i = 1 13 | // eslint-disable-next-line no-constant-condition 14 | while (true) { 15 | api.leakingFunc(() => {}) 16 | if (i % 100000 === 0) { 17 | await setTimeout(100) 18 | global.gc?.() 19 | displayMemoryUsageFromNode(initialMemoryUsage) 20 | } 21 | i++ 22 | } 23 | -------------------------------------------------------------------------------- /examples/napi/src/async.rs: -------------------------------------------------------------------------------- 1 | use futures::prelude::*; 2 | use napi::bindgen_prelude::*; 3 | use napi::tokio::{self, fs}; 4 | 5 | #[napi] 6 | async fn read_file_async(path: String) -> Result { 7 | fs::read(path) 8 | .map(|r| match r { 9 | Ok(content) => Ok(content.into()), 10 | Err(e) => Err(Error::new( 11 | Status::GenericFailure, 12 | format!("failed to read file, {}", e), 13 | )), 14 | }) 15 | .await 16 | } 17 | 18 | #[napi] 19 | async fn async_multi_two(arg: u32) -> Result { 20 | tokio::task::spawn(async move { Ok(arg * 2) }) 21 | .await 22 | .unwrap() 23 | } 24 | -------------------------------------------------------------------------------- /ava.config.mjs: -------------------------------------------------------------------------------- 1 | import { cpus } from 'os' 2 | 3 | const configuration = { 4 | extensions: ['ts', 'tsx'], 5 | files: ['cli/**/*.spec.ts', 'examples/**/__test__/**/*.spec.ts'], 6 | require: ['ts-node/register/transpile-only'], 7 | environmentVariables: { 8 | TS_NODE_PROJECT: './examples/tsconfig.json', 9 | }, 10 | timeout: '5m', 11 | workerThreads: true, 12 | concurrency: process.env.CI ? 2 : cpus().length, 13 | failFast: false, 14 | verbose: !!process.env.CI, 15 | } 16 | 17 | if (parseInt(process.versions.napi, 10) < 4) { 18 | configuration.compileEnhancements = false 19 | } 20 | 21 | export default configuration 22 | -------------------------------------------------------------------------------- /crates/build/README.md: -------------------------------------------------------------------------------- 1 | # napi-build 2 | 3 | 4 | 5 | 6 | chat 8 | 9 | 10 | > Build support for napi-rs 11 | 12 | Setup `N-API` build in your `build.rs`: 13 | 14 | ```rust 15 | extern crate napi_build; 16 | 17 | fn main() { 18 | napi_build::setup(); 19 | } 20 | ``` 21 | -------------------------------------------------------------------------------- /triples/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # [1.1.0](https://github.com/napi-rs/napi-rs/compare/@napi-rs/triples@1.0.3...@napi-rs/triples@1.1.0) (2021-12-02) 7 | 8 | ### Features 9 | 10 | - **triples:** support android armv7 ([809350b](https://github.com/napi-rs/napi-rs/commit/809350b42ff4b3abca421c421a4049053d2e60cb)) 11 | 12 | ## 1.0.3 (2021-07-22) 13 | 14 | ### Chore 15 | 16 | - Reduce publish size [0c3918d](https://github.com/napi-rs/napi-rs/commit/0c3918df24805c8e90cca26e6cbc2492cad23306) 17 | -------------------------------------------------------------------------------- /bench/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["LongYinan "] 3 | edition = "2021" 4 | name = "napi-bench" 5 | publish = false 6 | version = "0.1.0" 7 | 8 | [lib] 9 | crate-type = ["cdylib"] 10 | 11 | [dependencies] 12 | napi = { path = "../crates/napi", features = [ 13 | "tokio_rt", 14 | "serde-json", 15 | "compat-mode", 16 | ] } 17 | napi-derive = { path = "../crates/macro", features = ["compat-mode"] } 18 | serde = "1" 19 | serde_json = "1" 20 | 21 | [target.'cfg(all(target_arch = "x86_64", not(target_env = "musl")))'.dependencies] 22 | mimalloc = { version = "0.1" } 23 | 24 | [build-dependencies] 25 | napi-build = { path = "../crates/build" } 26 | -------------------------------------------------------------------------------- /memory-testing/tokio-future.mjs: -------------------------------------------------------------------------------- 1 | import { createRequire } from 'module' 2 | 3 | import { displayMemoryUsageFromNode } from './util.mjs' 4 | 5 | const initialMemoryUsage = process.memoryUsage() 6 | 7 | const require = createRequire(import.meta.url) 8 | 9 | const api = require(`./index.node`) 10 | 11 | async function main() { 12 | let i = 1 13 | // eslint-disable-next-line no-constant-condition 14 | while (true) { 15 | await api.testAsync() 16 | if (i % 100000 === 0) { 17 | displayMemoryUsageFromNode(initialMemoryUsage) 18 | } 19 | i++ 20 | } 21 | } 22 | 23 | main().catch((e) => { 24 | console.error(e) 25 | process.exit(1) 26 | }) 27 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/napi8/object.spec.ts: -------------------------------------------------------------------------------- 1 | import ava from 'ava' 2 | 3 | import { napiVersion } from '../napi-version' 4 | 5 | const bindings = require('../../index.node') 6 | 7 | const test = napiVersion >= 8 ? ava : ava.skip 8 | 9 | test('should be able to freeze object', (t) => { 10 | const obj: any = {} 11 | bindings.testFreezeObject(obj) 12 | t.true(Object.isFrozen(obj)) 13 | t.throws(() => { 14 | obj.a = 1 15 | }) 16 | }) 17 | 18 | test('should be able to seal object', (t) => { 19 | const obj: any = {} 20 | bindings.testSealObject(obj) 21 | t.true(Object.isSealed(obj)) 22 | t.throws(() => { 23 | obj.a = 1 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/throw.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | const bindings = require('../index.node') 4 | 5 | test('should be able to throw error from native', (t) => { 6 | t.throws(bindings.testThrow) 7 | }) 8 | 9 | test('should be able to throw error from native with reason', (t) => { 10 | const reason = 'Fatal' 11 | t.throws(() => bindings.testThrowWithReason(reason), void 0, reason) 12 | }) 13 | 14 | test('should throw if argument type is not match', (t) => { 15 | t.throws(() => bindings.testThrowWithReason(2)) 16 | }) 17 | 18 | test('should throw if Rust code panic', (t) => { 19 | t.throws(() => bindings.testThrowWithPanic()) 20 | }) 21 | -------------------------------------------------------------------------------- /memory-testing/returns-future.mjs: -------------------------------------------------------------------------------- 1 | import { createRequire } from 'module' 2 | 3 | import { displayMemoryUsageFromNode } from './util.mjs' 4 | 5 | const initialMemoryUsage = process.memoryUsage() 6 | 7 | const require = createRequire(import.meta.url) 8 | 9 | const api = require(`./index.node`) 10 | 11 | async function main() { 12 | let i = 1 13 | // eslint-disable-next-line no-constant-condition 14 | while (true) { 15 | await api.returnsFuture() 16 | if (i % 100000 === 0) { 17 | displayMemoryUsageFromNode(initialMemoryUsage) 18 | } 19 | i++ 20 | } 21 | } 22 | 23 | main().catch((e) => { 24 | console.error(e) 25 | process.exit(1) 26 | }) 27 | -------------------------------------------------------------------------------- /bench/get-array-from-js.ts: -------------------------------------------------------------------------------- 1 | import b from 'benny' 2 | 3 | const { 4 | getArrayFromJson, 5 | getArrayFromJsArray, 6 | getArrayWithForLoop, 7 | } = require('./index.node') 8 | 9 | const FIXTURE = Array.from({ length: 1000 }).fill(42) 10 | 11 | export const benchGetArray = () => 12 | b.suite( 13 | 'getArrayFromJs', 14 | b.add('get array from json string', () => { 15 | getArrayFromJson(JSON.stringify(FIXTURE)) 16 | }), 17 | b.add('get array from serde', () => { 18 | getArrayFromJsArray(FIXTURE) 19 | }), 20 | 21 | b.add('get array with for loop', () => { 22 | getArrayWithForLoop(FIXTURE) 23 | }), 24 | 25 | b.cycle(), 26 | b.complete(), 27 | ) 28 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/napi4/tsfn_error.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | import { napiVersion } from '../napi-version' 4 | 5 | const bindings = require('../../index.node') 6 | 7 | test('should call callback with the first arguments as an Error', async (t) => { 8 | if (napiVersion < 4) { 9 | t.is(bindings.testTsfnError, undefined) 10 | return 11 | } 12 | await new Promise((resolve, reject) => { 13 | bindings.testTsfnError((err: Error) => { 14 | try { 15 | t.is(err instanceof Error, true) 16 | t.is(err.message, 'invalid') 17 | resolve() 18 | } catch (err) { 19 | reject(err) 20 | } 21 | }) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/string.spec.ts.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `examples/napi-compat-mode/__test__/string.spec.ts` 2 | 3 | The actual snapshot is saved in `string.spec.ts.snap`. 4 | 5 | Generated by [AVA](https://avajs.dev). 6 | 7 | ## should be able to concat string 8 | 9 | > Snapshot 1 10 | 11 | 'JavaScript 🌳 你好 napi + Rust 🦀 string!' 12 | 13 | ## should be able to concat utf16 string 14 | 15 | > Snapshot 1 16 | 17 | 'JavaScript 🌳 你好 napi + Rust 🦀 string!' 18 | 19 | ## should be able to concat latin1 string 20 | 21 | > Snapshot 1 22 | 23 | 'æ¶½¾DEL + Rust 🦀 string!' 24 | 25 | ## should be able to crate latin1 string 26 | 27 | > Snapshot 1 28 | 29 | '©¿' 30 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/src/napi8/mod.rs: -------------------------------------------------------------------------------- 1 | use napi::{JsObject, Result}; 2 | 3 | mod async_cleanup; 4 | mod object; 5 | 6 | use async_cleanup::*; 7 | use object::*; 8 | 9 | pub fn register_js(exports: &mut JsObject) -> Result<()> { 10 | exports.create_named_method("testSealObject", seal_object)?; 11 | exports.create_named_method("testFreezeObject", freeze_object)?; 12 | exports.create_named_method( 13 | "testAddRemovableAsyncCleanupHook", 14 | add_removable_async_cleanup_hook, 15 | )?; 16 | exports.create_named_method("testRemoveAsyncCleanupHook", remove_async_cleanup_hook)?; 17 | exports.create_named_method("testAddAsyncCleanupHook", add_async_cleanup_hook)?; 18 | Ok(()) 19 | } 20 | -------------------------------------------------------------------------------- /examples/napi/src/bigint.rs: -------------------------------------------------------------------------------- 1 | use napi::bindgen_prelude::*; 2 | 3 | #[napi] 4 | fn bigint_add(a: BigInt, b: BigInt) -> u128 { 5 | a.get_u128().1 + b.get_u128().1 6 | } 7 | 8 | #[napi] 9 | fn create_big_int() -> BigInt { 10 | BigInt { 11 | words: vec![100u64, 200u64], 12 | sign_bit: true, 13 | } 14 | } 15 | 16 | #[napi] 17 | fn create_big_int_i64() -> i64n { 18 | i64n(100) 19 | } 20 | 21 | #[napi] 22 | pub fn bigint_get_u64_as_string(bi: BigInt) -> String { 23 | bi.get_u64().1.to_string() 24 | } 25 | 26 | #[napi] 27 | pub fn bigint_from_i64() -> BigInt { 28 | BigInt::from(100i64) 29 | } 30 | 31 | #[napi] 32 | pub fn bigint_from_i128() -> BigInt { 33 | BigInt::from(-100i128) 34 | } 35 | -------------------------------------------------------------------------------- /crates/sys/README.md: -------------------------------------------------------------------------------- 1 | # napi-sys 2 | 3 | Dynamic loading logic copied from https://github.com/neon-bindings/neon/tree/0.10.0/crates/neon-runtime/src/napi/bindings. 4 | 5 | 6 | 7 | 8 | chat 10 | 11 | 12 | Low-level N-API bindings for Node.js addons written in Rust. 13 | 14 | See the [napi](https://nodejs.org/api/n-api.html) for the high-level API. 15 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/string.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | const bindings = require('../index.node') 4 | 5 | test('should be able to concat string', (t) => { 6 | const fixture = 'JavaScript 🌳 你好 napi' 7 | t.snapshot(bindings.concatString(fixture)) 8 | }) 9 | 10 | test('should be able to concat utf16 string', (t) => { 11 | const fixture = 'JavaScript 🌳 你好 napi' 12 | t.snapshot(bindings.concatUTF16String(fixture)) 13 | }) 14 | 15 | test('should be able to concat latin1 string', (t) => { 16 | const fixture = 'æ¶½¾DEL' 17 | t.snapshot(bindings.concatLatin1String(fixture)) 18 | }) 19 | 20 | test('should be able to crate latin1 string', (t) => { 21 | t.snapshot(bindings.createLatin1()) 22 | }) 23 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/src/napi5/date.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryInto; 2 | 3 | use napi::{CallContext, JsBoolean, JsDate, JsNumber, JsUnknown, Result}; 4 | 5 | #[js_function(1)] 6 | pub fn test_object_is_date(ctx: CallContext) -> Result { 7 | let obj = ctx.get::(0)?; 8 | ctx.env.get_boolean(obj.is_date()?) 9 | } 10 | 11 | #[js_function(1)] 12 | pub fn test_create_date(ctx: CallContext) -> Result { 13 | let timestamp: f64 = ctx.get::(0)?.try_into()?; 14 | ctx.env.create_date(timestamp) 15 | } 16 | 17 | #[js_function(1)] 18 | pub fn test_get_date_value(ctx: CallContext) -> Result { 19 | let date = ctx.get::(0)?; 20 | ctx.env.create_double(date.value_of()?) 21 | } 22 | -------------------------------------------------------------------------------- /examples/napi/src/array.rs: -------------------------------------------------------------------------------- 1 | use napi::{Env, JsObject}; 2 | 3 | #[napi] 4 | pub fn get_words() -> Vec<&'static str> { 5 | vec!["foo", "bar"] 6 | } 7 | 8 | #[napi] 9 | /// Gets some numbers 10 | fn get_nums() -> Vec { 11 | vec![1, 1, 2, 3, 5, 8] 12 | } 13 | 14 | #[napi] 15 | fn sum_nums(nums: Vec) -> u32 { 16 | nums.iter().sum() 17 | } 18 | 19 | #[napi] 20 | fn to_js_obj(env: Env) -> napi::Result { 21 | let mut arr = env.create_array(0)?; 22 | arr.insert("a string")?; 23 | arr.insert(42)?; 24 | arr.coerce_to_object() 25 | } 26 | 27 | #[napi] 28 | fn get_num_arr() -> [u32; 2] { 29 | [1, 2] 30 | } 31 | 32 | #[napi] 33 | fn get_nested_num_arr() -> [[[u32; 1]; 1]; 2] { 34 | [[[1]], [[1]]] 35 | } 36 | -------------------------------------------------------------------------------- /memory-testing/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["LongYinan "] 3 | edition = "2021" 4 | name = "memory-testing" 5 | publish = false 6 | version = "0.1.0" 7 | 8 | [lib] 9 | crate-type = ["cdylib"] 10 | 11 | [dependencies] 12 | futures = "0.3" 13 | napi = { path = "../crates/napi", features = [ 14 | "tokio_rt", 15 | "serde-json", 16 | "latin1", 17 | "compat-mode", 18 | ] } 19 | napi-derive = { path = "../crates/macro", default-features = false, features = [ 20 | "compat-mode", 21 | ] } 22 | serde = "1" 23 | serde_bytes = "0.11" 24 | serde_derive = "1" 25 | serde_json = "1" 26 | tokio = { version = "1", features = ["default", "fs"] } 27 | 28 | [build-dependencies] 29 | napi-build = { path = "../crates/build" } 30 | -------------------------------------------------------------------------------- /examples/napi/src/enum.rs: -------------------------------------------------------------------------------- 1 | use napi::bindgen_prelude::*; 2 | 3 | /// default enum values are continuos i32s start from 0 4 | #[napi] 5 | pub enum Kind { 6 | /// Barks 7 | Dog, 8 | /// Kills birds 9 | Cat, 10 | /// Tasty 11 | Duck, 12 | } 13 | 14 | #[napi] 15 | pub enum Empty {} 16 | 17 | /// You could break the step and for an new continuous value. 18 | #[napi] 19 | pub enum CustomNumEnum { 20 | One = 1, 21 | Two, 22 | Three = 3, 23 | Four, 24 | Six = 6, 25 | Eight = 8, 26 | Nine, // would be 9 27 | Ten, // 10 28 | } 29 | 30 | #[napi] 31 | fn enum_to_i32(e: CustomNumEnum) -> i32 { 32 | e as i32 33 | } 34 | 35 | #[napi(skip_typescript)] 36 | pub enum SkippedEnums { 37 | One = 1, 38 | Two, 39 | Tree, 40 | } 41 | -------------------------------------------------------------------------------- /crates/sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["LongYinan "] 3 | description = "NodeJS N-API raw binding" 4 | edition = "2021" 5 | include = ["src/**/*", "Cargo.toml"] 6 | keywords = ["NodeJS", "FFI", "NAPI", "n-api"] 7 | license = "MIT" 8 | name = "napi-sys" 9 | readme = "README.md" 10 | repository = "https://github.com/napi-rs/napi-rs" 11 | rust-version = "1.57" 12 | version = "2.2.3" 13 | 14 | [features] 15 | experimental = [] 16 | napi1 = [] 17 | napi2 = ["napi1"] 18 | napi3 = ["napi2"] 19 | napi4 = ["napi3"] 20 | napi5 = ["napi4"] 21 | napi6 = ["napi5"] 22 | napi7 = ["napi6"] 23 | napi8 = ["napi7"] 24 | 25 | [package.metadata.workspaces] 26 | independent = true 27 | 28 | [target.'cfg(windows)'.dependencies.libloading] 29 | version = "0.7" 30 | -------------------------------------------------------------------------------- /crates/napi/src/task.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | bindgen_runtime::{ToNapiValue, TypeName}, 3 | Env, Error, Result, 4 | }; 5 | 6 | pub trait Task: Send + Sized { 7 | type Output: Send + Sized + 'static; 8 | type JsValue: ToNapiValue + TypeName; 9 | 10 | /// Compute logic in libuv thread 11 | fn compute(&mut self) -> Result; 12 | 13 | /// Into this method if `compute` return `Ok` 14 | fn resolve(&mut self, env: Env, output: Self::Output) -> Result; 15 | 16 | /// Into this method if `compute` return `Err` 17 | fn reject(&mut self, _env: Env, err: Error) -> Result { 18 | Err(err) 19 | } 20 | 21 | // after resolve or reject 22 | fn finally(&mut self, _env: Env) -> Result<()> { 23 | Ok(()) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/binary/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "binary", 3 | "private": true, 4 | "version": "0.0.0", 5 | "bin": "napi-examples-binary", 6 | "scripts": { 7 | "build": "node ../../cli/scripts/index.js build --js false", 8 | "build-aarch64": "node ../../cli/scripts/index.js build --js false --target aarch64-unknown-linux-gnu", 9 | "build-armv7": "node ../../cli/scripts/index.js build --js false --target armv7-unknown-linux-gnueabihf", 10 | "build-i686": "node ../../cli/scripts/index.js build --js false --target i686-pc-windows-msvc", 11 | "build-i686-release": "node ../../cli/scripts/index.js build --js false --release --target i686-pc-windows-msvc", 12 | "build-release": "node ../../cli/scripts/index.js build --js false --release" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/symbol.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | const bindings = require('../index.node') 4 | 5 | test('should create named symbol', (t) => { 6 | const symbol = bindings.createNamedSymbol() 7 | t.true(typeof symbol === 'symbol') 8 | t.is(symbol.toString(), 'Symbol(native)') 9 | }) 10 | 11 | test('should create unnamed symbol', (t) => { 12 | const symbol = bindings.createUnnamedSymbol() 13 | t.true(typeof symbol === 'symbol') 14 | t.is(symbol.toString(), 'Symbol()') 15 | }) 16 | 17 | test('should create symbol from JsString', (t) => { 18 | const fixture = 'N-API Symbol' 19 | const symbol = bindings.createSymbolFromJsString(fixture) 20 | t.true(typeof symbol === 'symbol') 21 | t.is(symbol.toString(), `Symbol(${fixture})`) 22 | }) 23 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/napi6/instance-data.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | import { napiVersion } from '../napi-version' 4 | 5 | const bindings = require('../../index.node') 6 | 7 | test('should set and get instance data', (t) => { 8 | if (napiVersion >= 6) { 9 | t.is(bindings.getInstanceData(), undefined) 10 | bindings.setInstanceData() 11 | t.is(bindings.getInstanceData(), 1024) 12 | } else { 13 | t.is(bindings.getInstanceData, undefined) 14 | t.is(bindings.setInstanceData, undefined) 15 | } 16 | }) 17 | 18 | test('should throw if get instance data type mismatched', (t) => { 19 | if (napiVersion >= 6) { 20 | t.throws(bindings.getWrongTypeInstanceData) 21 | } else { 22 | t.is(bindings.getWrongTypeInstanceData, undefined) 23 | } 24 | }) 25 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/serde/serde-json.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | const bindings = require('../../index.node') 4 | 5 | const ValidObject = { 6 | a: 1, 7 | b: [-1.2, 1.1, 2.2, 3.3], 8 | c: 'Hi', 9 | } 10 | 11 | const InValidObject = { 12 | a: -1, 13 | b: [-1, 1.1, 2.2, 3.3], 14 | c: 'Hello', 15 | } 16 | 17 | test('should from json string', (t) => { 18 | t.throws(() => bindings.from_json_string(JSON.stringify(InValidObject))) 19 | t.deepEqual( 20 | ValidObject, 21 | bindings.from_json_string(JSON.stringify(ValidObject)), 22 | ) 23 | }) 24 | 25 | test('should convert to json string', (t) => { 26 | t.throws(() => bindings.json_to_string(InValidObject)) 27 | t.deepEqual(JSON.stringify(ValidObject), bindings.json_to_string(ValidObject)) 28 | }) 29 | -------------------------------------------------------------------------------- /crates/backend/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | description = "Codegen backend for napi procedural macro" 3 | edition = "2021" 4 | homepage = "https://napi.rs" 5 | license = "MIT" 6 | name = "napi-derive-backend" 7 | readme = "README.md" 8 | repository = "https://github.com/napi-rs/napi-rs" 9 | rust-version = "1.57" 10 | version = "1.0.44" 11 | 12 | [package.metadata.workspaces] 13 | independent = true 14 | 15 | [features] 16 | noop = [] 17 | strict = [] 18 | type-def = ["regex", "once_cell"] 19 | 20 | [dependencies] 21 | convert_case = "0.6" 22 | proc-macro2 = "1" 23 | quote = "1" 24 | syn = { version = "1.0.61", features = ["fold", "full", "extra-traits"] } 25 | 26 | [dependencies.regex] 27 | optional = true 28 | version = "1" 29 | 30 | [dependencies.once_cell] 31 | optional = true 32 | version = "1" 33 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/global.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import Sinon from 'sinon' 3 | 4 | const bindings = require('../index.node') 5 | 6 | function wait(delay: number) { 7 | return new Promise((resolve) => setTimeout(resolve, delay)) 8 | } 9 | 10 | const delay = 100 11 | 12 | test('should setTimeout', async (t) => { 13 | const handler = Sinon.spy() 14 | bindings.setTimeout(handler, delay) 15 | t.is(handler.callCount, 0) 16 | await wait(delay + 10) 17 | t.is(handler.callCount, 1) 18 | }) 19 | 20 | test('should clearTimeout', async (t) => { 21 | const handler = Sinon.spy() 22 | const timer = setTimeout(() => handler(), delay) 23 | t.is(handler.callCount, 0) 24 | bindings.clearTimeout(timer) 25 | await wait(delay + 10) 26 | t.is(handler.callCount, 0) 27 | }) 28 | -------------------------------------------------------------------------------- /cli/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { readFile, writeFile, copyFile, mkdir, unlink, stat } from 'fs' 2 | import { promisify } from 'util' 3 | 4 | export const readFileAsync = promisify(readFile) 5 | export const writeFileAsync = promisify(writeFile) 6 | export const unlinkAsync = promisify(unlink) 7 | export const copyFileAsync = promisify(copyFile) 8 | export const mkdirAsync = promisify(mkdir) 9 | export const statAsync = promisify(stat) 10 | 11 | export async function fileExists(path: string) { 12 | const exists = await statAsync(path) 13 | .then(() => true) 14 | .catch(() => false) 15 | return exists 16 | } 17 | 18 | export function pick(o: O, ...keys: K[]): Pick { 19 | return keys.reduce((acc, key) => { 20 | acc[key] = o[key] 21 | return acc 22 | }, {} as O) 23 | } 24 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["LongYinan "] 3 | edition = "2021" 4 | name = "napi-compat-mode-examples" 5 | publish = false 6 | version = "0.1.0" 7 | 8 | [lib] 9 | crate-type = ["cdylib"] 10 | 11 | [features] 12 | latest = ["napi/napi8"] 13 | napi3 = ["napi/napi3"] 14 | 15 | [dependencies] 16 | futures = "0.3" 17 | napi = { path = "../../crates/napi", features = [ 18 | "tokio_rt", 19 | "serde-json", 20 | "latin1", 21 | "compat-mode", 22 | ] } 23 | napi-derive = { path = "../../crates/macro", features = ["compat-mode"] } 24 | serde = "1" 25 | serde_bytes = "0.11" 26 | serde_derive = "1" 27 | serde_json = "1" 28 | tokio = { version = "1", features = ["default", "fs"] } 29 | 30 | [build-dependencies] 31 | napi-build = { path = "../../crates/build" } 32 | -------------------------------------------------------------------------------- /crates/napi/src/version.rs: -------------------------------------------------------------------------------- 1 | use crate::{sys, Error, Status}; 2 | use std::convert::TryFrom; 3 | use std::ffi::CStr; 4 | 5 | #[derive(Debug, Clone, Copy)] 6 | pub struct NodeVersion { 7 | pub major: u32, 8 | pub minor: u32, 9 | pub patch: u32, 10 | pub release: &'static str, 11 | } 12 | 13 | impl TryFrom for NodeVersion { 14 | type Error = Error; 15 | 16 | fn try_from(value: sys::napi_node_version) -> Result { 17 | Ok(NodeVersion { 18 | major: value.major, 19 | minor: value.minor, 20 | patch: value.patch, 21 | release: unsafe { 22 | CStr::from_ptr(value.release) 23 | .to_str() 24 | .map_err(|_| Error::new(Status::StringExpected, "Invalid release name".to_owned()))? 25 | }, 26 | }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/napi/src/error.rs: -------------------------------------------------------------------------------- 1 | use napi::bindgen_prelude::*; 2 | 3 | #[napi] 4 | pub fn throw_error() -> Result<()> { 5 | Err(Error::new(Status::InvalidArg, "Manual Error".to_owned())) 6 | } 7 | 8 | #[napi(catch_unwind)] 9 | pub fn panic() { 10 | panic!("Don't panic"); 11 | } 12 | 13 | #[napi] 14 | pub fn receive_string(s: String) -> String { 15 | s 16 | } 17 | 18 | pub enum CustomError { 19 | NapiError(Error), 20 | Panic, 21 | } 22 | 23 | impl AsRef for CustomError { 24 | fn as_ref(&self) -> &str { 25 | match self { 26 | CustomError::Panic => "Panic", 27 | CustomError::NapiError(e) => e.status.as_ref(), 28 | } 29 | } 30 | } 31 | 32 | #[napi] 33 | pub fn custom_status_code() -> Result<(), CustomError> { 34 | Err(Error::new(CustomError::Panic, "don't panic")) 35 | } 36 | -------------------------------------------------------------------------------- /examples/napi/src/task.rs: -------------------------------------------------------------------------------- 1 | use std::thread::sleep; 2 | 3 | use napi::bindgen_prelude::*; 4 | 5 | struct DelaySum(u32, u32); 6 | 7 | #[napi] 8 | impl napi::Task for DelaySum { 9 | type Output = u32; 10 | type JsValue = u32; 11 | 12 | fn compute(&mut self) -> Result { 13 | sleep(std::time::Duration::from_millis(100)); 14 | Ok(self.0 + self.1) 15 | } 16 | 17 | fn resolve(&mut self, _env: napi::Env, output: Self::Output) -> Result { 18 | Ok(output) 19 | } 20 | } 21 | 22 | #[napi] 23 | fn without_abort_controller(a: u32, b: u32) -> AsyncTask { 24 | AsyncTask::new(DelaySum(a, b)) 25 | } 26 | 27 | #[napi] 28 | fn with_abort_controller(a: u32, b: u32, signal: AbortSignal) -> AsyncTask { 29 | AsyncTask::with_signal(DelaySum(a, b), signal) 30 | } 31 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/serde/ser.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | import { napiVersion } from '../napi-version' 4 | 5 | const bindings = require('../../index.node') 6 | 7 | const testFunc = [ 8 | 'make_num_77', 9 | 'make_num_32', 10 | 'make_str_hello', 11 | 'make_num_array', 12 | 'make_buff', 13 | 'make_obj', 14 | 'make_map', 15 | 'make_bytes_struct', 16 | ] 17 | 18 | if (napiVersion >= 6) { 19 | // bigint inside 20 | testFunc.push('make_object') 21 | } 22 | 23 | for (const func of testFunc) { 24 | test(`serialize ${func} from bindings`, (t) => { 25 | t.snapshot(bindings[func]()) 26 | }) 27 | } 28 | 29 | test('serialize make_bytes_struct', (t) => { 30 | t.deepEqual(bindings.make_bytes_struct(), { 31 | code: Buffer.from([0, 1, 2, 3]), 32 | map: 'source map', 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /crates/backend/src/typegen/const.rs: -------------------------------------------------------------------------------- 1 | use super::{ToTypeDef, TypeDef}; 2 | 3 | use crate::{js_doc_from_comments, ty_to_ts_type, typegen::add_alias, NapiConst}; 4 | 5 | impl ToTypeDef for NapiConst { 6 | fn to_type_def(&self) -> Option { 7 | if self.skip_typescript { 8 | return None; 9 | } 10 | 11 | add_alias(self.name.to_string(), self.js_name.to_string()); 12 | 13 | Some(TypeDef { 14 | kind: "const".to_owned(), 15 | name: self.js_name.to_owned(), 16 | original_name: Some(self.name.to_string()), 17 | def: format!( 18 | "export const {}: {}", 19 | &self.js_name, 20 | ty_to_ts_type(&self.type_name, false, false, false).0 21 | ), 22 | js_mod: self.js_mod.to_owned(), 23 | js_doc: js_doc_from_comments(&self.comments), 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/spawn.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | const bindings = require('../index.node') 4 | 5 | test('should be able to spawn thread and return promise', async (t) => { 6 | const result = await bindings.testSpawnThread(20) 7 | t.is(result, 6765) 8 | }) 9 | 10 | test('should be able to spawn thread with ref value', async (t) => { 11 | const fixture = 'hello' 12 | const result = await bindings.testSpawnThreadWithRef(Buffer.from(fixture)) 13 | t.is(result, fixture.length) 14 | }) 15 | 16 | test('should be able to spawn with error', async (t) => { 17 | const fixture = Array.from({ length: 10 }).fill('0').join('') 18 | const err = new Error('Unreachable') 19 | try { 20 | await bindings.testSpawnThreadWithRef(Buffer.from(fixture)) 21 | throw err 22 | } catch (e) { 23 | t.not(e, err) 24 | } 25 | }) 26 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/src/global.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryInto; 2 | 3 | use napi::{CallContext, JsFunction, JsNumber, JsObject, JsTimeout, JsUndefined, Result}; 4 | 5 | #[js_function(2)] 6 | pub fn set_timeout(ctx: CallContext) -> Result { 7 | let handler: JsFunction = ctx.get(0)?; 8 | let timeout: JsNumber = ctx.get(1)?; 9 | ctx 10 | .env 11 | .get_global()? 12 | .set_timeout(handler, timeout.try_into()?) 13 | } 14 | 15 | #[js_function(1)] 16 | pub fn clear_timeout(ctx: CallContext) -> Result { 17 | let timer: JsTimeout = ctx.get(0)?; 18 | ctx.env.get_global()?.clear_timeout(timer) 19 | } 20 | 21 | pub fn register_js(exports: &mut JsObject) -> Result<()> { 22 | exports.create_named_method("setTimeout", set_timeout)?; 23 | exports.create_named_method("clearTimeout", clear_timeout)?; 24 | Ok(()) 25 | } 26 | -------------------------------------------------------------------------------- /memory-testing/reference.mjs: -------------------------------------------------------------------------------- 1 | import { createRequire } from 'module' 2 | 3 | import { displayMemoryUsageFromNode } from './util.mjs' 4 | 5 | const initialMemoryUsage = process.memoryUsage() 6 | 7 | const require = createRequire(import.meta.url) 8 | 9 | const { MemoryHolder } = require(`./index.node`) 10 | 11 | const sleep = () => 12 | new Promise((resolve) => { 13 | setTimeout(() => { 14 | resolve() 15 | }, 1000) 16 | }) 17 | 18 | let i = 1 19 | // eslint-disable-next-line no-constant-condition 20 | while (true) { 21 | const holder = new MemoryHolder(1024 * 1024) 22 | for (const _ of Array.from({ length: 100 })) { 23 | const child = holder.createReference() 24 | child.count() 25 | } 26 | if (i % 100 === 0) { 27 | displayMemoryUsageFromNode(initialMemoryUsage) 28 | await sleep() 29 | global.gc() 30 | } 31 | i++ 32 | } 33 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/object.spec.ts.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `examples/napi-compat-mode/__test__/object.spec.ts` 2 | 3 | The actual snapshot is saved in `object.spec.ts.snap`. 4 | 5 | Generated by [AVA](https://avajs.dev). 6 | 7 | ## setProperty 8 | 9 | > Snapshot 1 10 | 11 | 'Rust object property' 12 | 13 | ## setNamedProperty 14 | 15 | > Snapshot 1 16 | 17 | 'RustPropertyKey' 18 | 19 | ## testGetPropertyNames 20 | 21 | > Snapshot 1 22 | 23 | [ 24 | '2', 25 | 'k3', 26 | ] 27 | 28 | ## testSetElement 29 | 30 | > Snapshot 1 31 | 32 | [ 33 | undefined, 34 | 1, 35 | undefined, 36 | undefined, 37 | undefined, 38 | 'foo', 39 | ] 40 | 41 | ## testDeleteElement 42 | 43 | > Snapshot 1 44 | 45 | [ 46 | 0, 47 | undefined, 48 | undefined, 49 | 3, 50 | ] 51 | -------------------------------------------------------------------------------- /triples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@napi-rs/triples", 3 | "version": "1.1.0", 4 | "description": "Rust target triples objects", 5 | "keywords": [ 6 | "Rust", 7 | "cross-compile", 8 | "napi", 9 | "n-api", 10 | "node-rs", 11 | "napi-rs" 12 | ], 13 | "author": "LongYinan ", 14 | "homepage": "https://github.com/napi-rs/napi-rs/tree/main/triples#readme", 15 | "license": "MIT", 16 | "main": "./index.js", 17 | "types": "./index.d.ts", 18 | "publishConfig": { 19 | "registry": "https://registry.npmjs.org/", 20 | "access": "public" 21 | }, 22 | "files": [ 23 | "index.js", 24 | "index.d.ts" 25 | ], 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/napi-rs/napi-rs.git" 29 | }, 30 | "bugs": { 31 | "url": "https://github.com/napi-rs/napi-rs/issues" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /crates/napi/src/async_cleanup_hook.rs: -------------------------------------------------------------------------------- 1 | use std::mem; 2 | 3 | use crate::{sys, Status}; 4 | 5 | /// Notice 6 | /// The hook will be removed if `AsyncCleanupHook` was `dropped`. 7 | /// If you want keep the hook until node process exited, call the `AsyncCleanupHook::forget`. 8 | #[repr(transparent)] 9 | pub struct AsyncCleanupHook(pub(crate) sys::napi_async_cleanup_hook_handle); 10 | 11 | impl AsyncCleanupHook { 12 | /// Safe to forget it. 13 | /// Things will be cleanup before process exited. 14 | pub fn forget(self) { 15 | mem::forget(self); 16 | } 17 | } 18 | 19 | impl Drop for AsyncCleanupHook { 20 | fn drop(&mut self) { 21 | let status = unsafe { sys::napi_remove_async_cleanup_hook(self.0) }; 22 | assert!( 23 | status == sys::Status::napi_ok, 24 | "Delete async cleanup hook failed: {}", 25 | Status::from(status) 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/src/napi6/instance.rs: -------------------------------------------------------------------------------- 1 | use napi::*; 2 | 3 | struct NativeObject { 4 | count: i64, 5 | } 6 | 7 | #[contextless_function] 8 | pub fn set_instance_data(env: Env) -> ContextlessResult { 9 | env.set_instance_data(NativeObject { count: 1024 }, 0, |_ctx| {})?; 10 | env.get_undefined().map(Some) 11 | } 12 | 13 | #[contextless_function] 14 | pub fn get_instance_data(env: Env) -> ContextlessResult { 15 | if let Some(obj) = env.get_instance_data::()? { 16 | env.create_int64(obj.count).map(Some) 17 | } else { 18 | Ok(None) 19 | } 20 | } 21 | 22 | #[contextless_function] 23 | pub fn get_wrong_type_instance_data(env: Env) -> ContextlessResult { 24 | if let Some(count) = env.get_instance_data::()? { 25 | env.create_int64(*count as i64).map(Some) 26 | } else { 27 | Ok(None) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/napi/src/serde.rs: -------------------------------------------------------------------------------- 1 | use napi::bindgen_prelude::*; 2 | use serde_json::{Map, Value}; 3 | use std::fs; 4 | 5 | #[napi(object)] 6 | #[derive(Serialize, Deserialize, Debug)] 7 | /// This is an interface for package.json 8 | struct PackageJson { 9 | pub name: String, 10 | /// The version of the package 11 | pub version: String, 12 | pub dependencies: Option>, 13 | #[serde(rename = "devDependencies")] 14 | pub dev_dependencies: Option>, 15 | } 16 | 17 | #[napi] 18 | fn read_package_json() -> Result { 19 | let raw = fs::read_to_string("package.json")?; 20 | let p: PackageJson = serde_json::from_str(&raw)?; 21 | Ok(p) 22 | } 23 | 24 | #[napi] 25 | fn get_package_json_name(package_json: PackageJson) -> String { 26 | package_json.name 27 | } 28 | 29 | #[napi] 30 | fn test_serde_roundtrip(data: Value) -> Value { 31 | data 32 | } 33 | -------------------------------------------------------------------------------- /memory-testing/util.mjs: -------------------------------------------------------------------------------- 1 | import { whiteBright, red, green, gray } from 'colorette' 2 | import prettyBytes from 'pretty-bytes' 3 | import { table } from 'table' 4 | 5 | export function displayMemoryUsageFromNode(initialMemoryUsage) { 6 | const finalMemoryUsage = process.memoryUsage() 7 | const titles = Object.keys(initialMemoryUsage).map((k) => whiteBright(k)) 8 | const tableData = [titles] 9 | const diffColumn = [] 10 | for (const [key, value] of Object.entries(initialMemoryUsage)) { 11 | const diff = finalMemoryUsage[key] - value 12 | const prettyDiff = prettyBytes(diff, { signed: true }) 13 | if (diff > 0) { 14 | diffColumn.push(red(prettyDiff)) 15 | } else if (diff < 0) { 16 | diffColumn.push(green(prettyDiff)) 17 | } else { 18 | diffColumn.push(gray(prettyDiff)) 19 | } 20 | } 21 | tableData.push(diffColumn) 22 | console.info(table(tableData)) 23 | } 24 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/napi4/tokio_readfile.spec.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import path from 'path' 3 | 4 | import test from 'ava' 5 | 6 | import { napiVersion } from '../napi-version' 7 | 8 | const bindings = require('../../index.node') 9 | 10 | const filepath = path.resolve(__dirname, './example.txt') 11 | 12 | test('should read a file and return its a buffer', async (t) => { 13 | if (napiVersion < 4) { 14 | t.is(bindings.testTokioReadfile, undefined) 15 | return 16 | } 17 | await new Promise((resolve, reject) => { 18 | bindings.testTokioReadfile(filepath, (err: Error | null, value: Buffer) => { 19 | try { 20 | t.is(err, null) 21 | t.is(Buffer.isBuffer(value), true) 22 | t.is(value.toString(), fs.readFileSync(filepath, 'utf8')) 23 | resolve() 24 | } catch (err) { 25 | reject(err) 26 | } 27 | }) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /alpine.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14-alpine 2 | 3 | ENV PATH="/aarch64-linux-musl-cross/bin:/usr/local/cargo/bin/rustup:/root/.cargo/bin:$PATH" \ 4 | RUSTFLAGS="-C target-feature=-crt-static" \ 5 | CC="clang" \ 6 | CXX="clang++" \ 7 | GN_EXE=gn 8 | 9 | RUN apk add --update --no-cache bash wget cmake musl-dev clang llvm build-base python3 && \ 10 | sed -i -e 's/v[[:digit:]]\..*\//edge\//g' /etc/apk/repositories && \ 11 | apk add --update --no-cache --repository https://dl-cdn.alpinelinux.org/alpine/edge/testing \ 12 | rustup \ 13 | git \ 14 | gn \ 15 | tar \ 16 | ninja 17 | 18 | RUN rustup-init -y && \ 19 | yarn global add pnpm lerna && \ 20 | rustup target add aarch64-unknown-linux-musl && \ 21 | wget https://github.com/napi-rs/napi-rs/releases/download/linux-musl-cross%4010/aarch64-linux-musl-cross.tgz && \ 22 | tar -xvf aarch64-linux-musl-cross.tgz && \ 23 | rm aarch64-linux-musl-cross.tgz 24 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/src/symbol.rs: -------------------------------------------------------------------------------- 1 | use napi::{CallContext, JsObject, JsString, JsSymbol, Result}; 2 | 3 | #[js_function] 4 | pub fn create_named_symbol(ctx: CallContext) -> Result { 5 | ctx.env.create_symbol(Some("native")) 6 | } 7 | 8 | #[js_function] 9 | pub fn create_unnamed_symbol(ctx: CallContext) -> Result { 10 | ctx.env.create_symbol(None) 11 | } 12 | 13 | #[js_function(1)] 14 | pub fn create_symbol_from_js_string(ctx: CallContext) -> Result { 15 | let name = ctx.get::(0)?; 16 | ctx.env.create_symbol_from_js_string(name) 17 | } 18 | 19 | pub fn register_js(exports: &mut JsObject) -> Result<()> { 20 | exports.create_named_method("createNamedSymbol", create_named_symbol)?; 21 | exports.create_named_method("createUnnamedSymbol", create_unnamed_symbol)?; 22 | exports.create_named_method("createSymbolFromJsString", create_symbol_from_js_string)?; 23 | Ok(()) 24 | } 25 | -------------------------------------------------------------------------------- /examples/napi/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples", 3 | "private": true, 4 | "version": "0.0.0", 5 | "main": "./index.node", 6 | "types": "./index.d.ts", 7 | "scripts": { 8 | "build": "node ../../cli/scripts/index.js build --js false", 9 | "build-aarch64": "node ../../cli/scripts/index.js build --js false --target aarch64-unknown-linux-gnu", 10 | "build-armv7": "node ../../cli/scripts/index.js build --js false --target armv7-unknown-linux-gnueabihf", 11 | "build-i686": "node ../../cli/scripts/index.js build --js false --target i686-pc-windows-msvc", 12 | "build-i686-release": "node ../../cli/scripts/index.js build --js false --release --target i686-pc-windows-msvc", 13 | "build-release": "node ../../cli/scripts/index.js build --js false --release" 14 | }, 15 | "dependencies": { 16 | "@types/lodash": "^4.14.191", 17 | "lodash": "^4.17.21", 18 | "sinon": "^15.0.1" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/src/napi8/async_cleanup.rs: -------------------------------------------------------------------------------- 1 | use napi::*; 2 | 3 | #[js_function] 4 | pub fn add_removable_async_cleanup_hook(ctx: CallContext) -> Result { 5 | let cleanup_hook = ctx 6 | .env 7 | .add_removable_async_cleanup_hook(1u32, |_arg: u32| { 8 | println!("Exit from sub process"); 9 | })?; 10 | cleanup_hook.forget(); 11 | ctx.env.get_undefined() 12 | } 13 | 14 | #[js_function] 15 | pub fn add_async_cleanup_hook(ctx: CallContext) -> Result { 16 | ctx.env.add_async_cleanup_hook(1u32, |_arg: u32| { 17 | println!("Exit from sub process"); 18 | })?; 19 | ctx.env.get_undefined() 20 | } 21 | 22 | #[js_function] 23 | pub fn remove_async_cleanup_hook(ctx: CallContext) -> Result { 24 | ctx 25 | .env 26 | .add_removable_async_cleanup_hook(1u32, |_arg: u32| { 27 | println!("Exit from sub process"); 28 | })?; 29 | ctx.env.get_undefined() 30 | } 31 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/function.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | const bindings = require('../index.node') 4 | 5 | test('should call the function', (t) => { 6 | bindings.testCallFunction((arg1: string, arg2: string) => { 7 | t.is(`${arg1} ${arg2}`, 'hello world') 8 | }) 9 | }) 10 | 11 | test('should call function with ref args', (t) => { 12 | bindings.testCallFunctionWithRefArguments((arg1: string, arg2: string) => { 13 | t.is(`${arg1} ${arg2}`, 'hello world') 14 | }) 15 | }) 16 | 17 | test('should set "this" properly', (t) => { 18 | const obj = {} 19 | bindings.testCallFunctionWithThis.call(obj, function (this: typeof obj) { 20 | t.is(this, obj) 21 | }) 22 | }) 23 | 24 | test('should handle errors', (t) => { 25 | bindings.testCallFunctionError( 26 | () => { 27 | throw new Error('Testing') 28 | }, 29 | (err: Error) => { 30 | t.is(err.message, 'Testing') 31 | }, 32 | ) 33 | }) 34 | -------------------------------------------------------------------------------- /crates/macro/README.md: -------------------------------------------------------------------------------- 1 | # napi-derive 2 | 3 | 4 | 5 | 6 | chat 8 | 9 | 10 | Checkout more examples in [examples](../../examples) folder 11 | 12 | ```rust 13 | #[macro_use] 14 | extern crate napi_derive; 15 | use napi::bindgen_prelude::*; 16 | 17 | #[napi] 18 | fn fibonacci(n: u32) -> u32 { 19 | match n { 20 | 1 | 2 => 1, 21 | _ => fibonacci_native(n - 1) + fibonacci_native(n - 2), 22 | } 23 | } 24 | 25 | #[napi] 26 | fn get_cwd Result<()>>(callback: T) { 27 | callback(env::current_dir().unwrap().to_string_lossy().to_string()).unwrap(); 28 | } 29 | ``` 30 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/napi8/async-cleanup.spec.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process' 2 | import { join } from 'path' 3 | 4 | import ava from 'ava' 5 | 6 | import { napiVersion } from '../napi-version' 7 | 8 | const bindings = require('../../index.node') 9 | 10 | const test = napiVersion >= 8 ? ava : ava.skip 11 | 12 | test('should be able to add async cleanup hook', (t) => { 13 | const output = execSync( 14 | `node ${join(__dirname, 'sub-process.js')}`, 15 | ).toString() 16 | t.is(output.trim(), 'Exit from sub process') 17 | }) 18 | 19 | test('should be able to add removable async cleanup hook', (t) => { 20 | const output = execSync( 21 | `node ${join(__dirname, 'sub-process-removable.js')}`, 22 | ).toString() 23 | t.is(output.trim(), 'Exit from sub process') 24 | }) 25 | 26 | test('should be able to remove cleanup hook after added', (t) => { 27 | t.notThrows(() => bindings.testRemoveAsyncCleanupHook()) 28 | }) 29 | -------------------------------------------------------------------------------- /memory-testing/buffer.mjs: -------------------------------------------------------------------------------- 1 | import { createRequire } from 'module' 2 | import { setTimeout } from 'timers/promises' 3 | 4 | import { displayMemoryUsageFromNode } from './util.mjs' 5 | 6 | const initialMemoryUsage = process.memoryUsage() 7 | 8 | const require = createRequire(import.meta.url) 9 | 10 | const api = require(`./index.node`) 11 | 12 | let i = 1 13 | const FIXTURE = Buffer.allocUnsafe(1000 * 1000 * 20) 14 | // eslint-disable-next-line no-constant-condition 15 | while (true) { 16 | api.bufferLen() 17 | api.arrayBufferLen() 18 | api.bufferConvert(Buffer.from(FIXTURE)) 19 | api.arrayBufferConvert(Uint8Array.from(FIXTURE)) 20 | api.arrayBufferCreateFromVecWithSpareCapacity() 21 | api.bufferPassThrough(Buffer.from(FIXTURE)) 22 | api.arrayBufferPassThrough(Uint8Array.from(FIXTURE)) 23 | if (i % 10 === 0) { 24 | await setTimeout(1000) 25 | global?.gc?.() 26 | displayMemoryUsageFromNode(initialMemoryUsage) 27 | } 28 | i++ 29 | } 30 | -------------------------------------------------------------------------------- /crates/napi/src/js_values/boolean.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | 3 | use super::Value; 4 | use crate::bindgen_runtime::{TypeName, ValidateNapiValue}; 5 | use crate::{check_status, ValueType}; 6 | use crate::{sys, Error, Result}; 7 | 8 | #[derive(Clone, Copy)] 9 | pub struct JsBoolean(pub(crate) Value); 10 | 11 | impl TypeName for JsBoolean { 12 | fn type_name() -> &'static str { 13 | "bool" 14 | } 15 | 16 | fn value_type() -> crate::ValueType { 17 | ValueType::Boolean 18 | } 19 | } 20 | 21 | impl ValidateNapiValue for JsBoolean {} 22 | 23 | impl JsBoolean { 24 | pub fn get_value(&self) -> Result { 25 | let mut result = false; 26 | check_status!(unsafe { sys::napi_get_value_bool(self.0.env, self.0.value, &mut result) })?; 27 | Ok(result) 28 | } 29 | } 30 | 31 | impl TryFrom for bool { 32 | type Error = Error; 33 | 34 | fn try_from(value: JsBoolean) -> Result { 35 | value.get_value() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/env.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | const bindings = require('../index.node') 4 | 5 | test('should be able to access env variable from native', (t) => { 6 | t.is(bindings.getEnvVariable(), 'napi-rs') 7 | }) 8 | 9 | test('should be able to throw syntax error', (t) => { 10 | const msg = 'Custom Syntax Error' 11 | try { 12 | bindings.throwSyntaxError(msg) 13 | throw new Error('Unreachable') 14 | } catch (e) { 15 | t.true(e instanceof SyntaxError) 16 | t.is((e as SyntaxError).message, msg) 17 | } 18 | }) 19 | 20 | test('should be able to coerceToBool', (t) => { 21 | t.true(bindings.coerceToBool(true)) 22 | t.true(bindings.coerceToBool(1)) 23 | t.true(bindings.coerceToBool({})) 24 | t.true(bindings.coerceToBool(Symbol())) 25 | t.false(bindings.coerceToBool(0)) 26 | t.false(bindings.coerceToBool(undefined)) 27 | t.false(bindings.coerceToBool(null)) 28 | t.false(bindings.coerceToBool(NaN)) 29 | }) 30 | -------------------------------------------------------------------------------- /crates/macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["LongYinan ", "Forehalo "] 3 | description = "N-API procedural macros" 4 | edition = "2021" 5 | keywords = ["NodeJS", "FFI", "NAPI", "n-api"] 6 | license = "MIT" 7 | name = "napi-derive" 8 | readme = "README.md" 9 | repository = "https://github.com/napi-rs/napi-rs" 10 | rust-version = "1.57" 11 | version = "2.11.1" 12 | 13 | [package.metadata.workspaces] 14 | independent = true 15 | 16 | [features] 17 | compat-mode = [] 18 | default = ["compat-mode", "full"] 19 | full = ["type-def", "strict"] 20 | noop = ["napi-derive-backend/noop"] 21 | strict = ["napi-derive-backend/strict"] 22 | type-def = ["napi-derive-backend/type-def"] 23 | 24 | [dependencies] 25 | convert_case = "0.6" 26 | napi-derive-backend = { version = "1.0.44", path = "../backend" } 27 | proc-macro2 = "1.0" 28 | quote = "1.0" 29 | syn = { version = "1.0.61", features = ["fold", "full", "extra-traits"] } 30 | 31 | [lib] 32 | proc-macro = true 33 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/src/cleanup_env.rs: -------------------------------------------------------------------------------- 1 | use napi::{ 2 | CallContext, CleanupEnvHook, ContextlessResult, Env, JsExternal, JsObject, JsUndefined, Result, 3 | }; 4 | 5 | #[contextless_function] 6 | fn add_cleanup_hook(mut env: Env) -> ContextlessResult { 7 | let hook = env.add_env_cleanup_hook((), |_| { 8 | println!("cleanup hook executed"); 9 | })?; 10 | env.create_external(hook, None).map(Some) 11 | } 12 | 13 | #[js_function(1)] 14 | fn remove_cleanup_hook(ctx: CallContext) -> Result { 15 | let hook_external = ctx.get::(0)?; 16 | let hook = *ctx 17 | .env 18 | .get_value_external::>(&hook_external)?; 19 | ctx.env.remove_env_cleanup_hook(hook)?; 20 | ctx.env.get_undefined() 21 | } 22 | 23 | pub fn register_js(exports: &mut JsObject) -> Result<()> { 24 | exports.create_named_method("addCleanupHook", add_cleanup_hook)?; 25 | exports.create_named_method("removeCleanupHook", remove_cleanup_hook)?; 26 | Ok(()) 27 | } 28 | -------------------------------------------------------------------------------- /cli/src/spawn.ts: -------------------------------------------------------------------------------- 1 | import { spawn as _spawn, SpawnOptionsWithoutStdio } from 'child_process' 2 | 3 | import { debugFactory } from './debug' 4 | 5 | const debug = debugFactory('spawn') 6 | 7 | export function spawn( 8 | command: string, 9 | options: SpawnOptionsWithoutStdio = {}, 10 | ): Promise { 11 | const [cmd, ...args] = command.split(' ').map((s) => s.trim()) 12 | debug(`execute ${cmd} ${args.join(' ')}`) 13 | return new Promise((resolve, reject) => { 14 | const spawnStream = _spawn(cmd, args, { ...options, shell: true }) 15 | const chunks: Buffer[] = [] 16 | process.stdin.pipe(spawnStream.stdin) 17 | spawnStream.stdout?.on('data', (chunk) => { 18 | chunks.push(chunk) 19 | }) 20 | spawnStream.stdout.pipe(process.stdout) 21 | spawnStream.stderr.pipe(process.stderr) 22 | spawnStream.on('close', (code) => { 23 | if (code !== 0) { 24 | reject() 25 | } else { 26 | resolve(Buffer.concat(chunks)) 27 | } 28 | }) 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /examples/napi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["LongYinan "] 3 | edition = "2021" 4 | name = "napi-examples" 5 | publish = false 6 | version = "0.1.0" 7 | 8 | [lib] 9 | crate-type = ["cdylib"] 10 | 11 | [features] 12 | snmalloc = ["snmalloc-rs"] 13 | 14 | [dependencies] 15 | chrono = "0.4" 16 | futures = "0.3" 17 | napi = { path = "../../crates/napi", default-features = false, features = [ 18 | "tokio_fs", 19 | "napi8", 20 | "tokio_rt", 21 | "serde-json", 22 | "async", 23 | "experimental", 24 | "latin1", 25 | "chrono_date", 26 | ] } 27 | napi-derive = { path = "../../crates/macro", features = ["type-def"] } 28 | serde = "1" 29 | serde_derive = "1" 30 | serde_json = "1" 31 | tokio = { version = "1.20.0", features = ["full"] } 32 | 33 | [dependencies.snmalloc-rs] 34 | version = "0.3" 35 | features = ["build_cc", "local_dynamic_tls"] 36 | optional = true 37 | 38 | [build-dependencies] 39 | napi-build = { path = "../../crates/build" } 40 | 41 | [dev-dependencies] 42 | trybuild = "1.0" 43 | -------------------------------------------------------------------------------- /bench/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::uninlined_format_args)] 2 | 3 | #[macro_use] 4 | extern crate napi_derive; 5 | 6 | use napi::{Env, JsObject, Result}; 7 | 8 | #[cfg(all( 9 | target_arch = "x86_64", 10 | not(target_env = "musl"), 11 | not(debug_assertions) 12 | ))] 13 | #[global_allocator] 14 | static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc; 15 | 16 | mod async_compute; 17 | mod buffer; 18 | mod create_array; 19 | mod get_set_property; 20 | mod get_value_from_js; 21 | mod noop; 22 | mod plus; 23 | mod query; 24 | 25 | #[module_exports] 26 | fn init(mut exports: JsObject, env: Env) -> Result<()> { 27 | exports.create_named_method("noop", noop::noop)?; 28 | 29 | async_compute::register_js(&mut exports)?; 30 | buffer::register_js(&mut exports)?; 31 | plus::register_js(&mut exports)?; 32 | get_set_property::register_js(&mut exports, &env)?; 33 | create_array::register_js(&mut exports)?; 34 | get_value_from_js::register_js(&mut exports)?; 35 | query::register_js(&mut exports)?; 36 | 37 | Ok(()) 38 | } 39 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "declaration": true, 5 | "downlevelIteration": true, 6 | "importHelpers": true, 7 | "allowJs": true, 8 | "module": "CommonJS", 9 | "moduleResolution": "node", 10 | "newLine": "LF", 11 | "noEmitHelpers": true, 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "strict": true, 15 | "skipLibCheck": true, 16 | "suppressImplicitAnyIndexErrors": true, 17 | "suppressExcessPropertyErrors": true, 18 | "forceConsistentCasingInFileNames": true, 19 | "preserveSymlinks": true, 20 | "target": "ES2015", 21 | "sourceMap": true, 22 | "esModuleInterop": true, 23 | "stripInternal": true, 24 | "resolveJsonModule": true, 25 | "importsNotUsedAsValues": "remove", 26 | "outDir": "scripts", 27 | "lib": ["dom", "DOM.Iterable", "ES2019", "ES2020", "esnext"] 28 | }, 29 | "include": ["."], 30 | "exclude": ["node_modules", "bench", "cli/scripts", "scripts", "target"] 31 | } 32 | -------------------------------------------------------------------------------- /crates/backend/src/typegen/enum.rs: -------------------------------------------------------------------------------- 1 | use super::{add_alias, ToTypeDef, TypeDef}; 2 | use crate::{js_doc_from_comments, NapiEnum}; 3 | 4 | impl ToTypeDef for NapiEnum { 5 | fn to_type_def(&self) -> Option { 6 | if self.skip_typescript { 7 | return None; 8 | } 9 | 10 | add_alias(self.name.to_string(), self.js_name.to_string()); 11 | 12 | Some(TypeDef { 13 | kind: "enum".to_owned(), 14 | name: self.js_name.to_owned(), 15 | original_name: Some(self.name.to_string()), 16 | def: self.gen_ts_variants(), 17 | js_doc: js_doc_from_comments(&self.comments), 18 | js_mod: self.js_mod.to_owned(), 19 | }) 20 | } 21 | } 22 | 23 | impl NapiEnum { 24 | fn gen_ts_variants(&self) -> String { 25 | self 26 | .variants 27 | .iter() 28 | .map(|v| { 29 | format!( 30 | "{}{} = {}", 31 | js_doc_from_comments(&v.comments), 32 | v.name, 33 | v.val, 34 | ) 35 | }) 36 | .collect::>() 37 | .join(",\n ") 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.github/workflows/linux-musl.yaml: -------------------------------------------------------------------------------- 1 | name: Linux musl 2 | 3 | env: 4 | DEBUG: 'napi:*' 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | pull_request: 11 | 12 | jobs: 13 | build: 14 | name: stable - x86_64-unknown-linux-musl - node@18 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Setup node 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: 18 24 | check-latest: true 25 | cache: 'yarn' 26 | 27 | - name: 'Install dependencies' 28 | run: yarn install --immutable --mode=skip-build 29 | 30 | - name: 'Build TypeScript' 31 | run: yarn build 32 | 33 | - name: Setup and run tests 34 | uses: addnab/docker-run-action@v3 35 | with: 36 | image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine 37 | options: -v ${{ github.workspace }}:/napi-rs -w /napi-rs 38 | run: | 39 | cargo check -vvv 40 | yarn build:test 41 | yarn test 42 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "compat-mode-examples", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "node ../../cli/scripts/index.js build --js false --features \"latest\"", 7 | "build-napi3": "node ../../cli/scripts/index.js build --js false --features \"napi3\"", 8 | "build-aarch64": "node ../../cli/scripts/index.js build --js false --features \"latest\" --target aarch64-unknown-linux-gnu", 9 | "build-armv7": "node ../../cli/scripts/index.js build --js false --features \"latest\" --target armv7-unknown-linux-gnueabihf", 10 | "build-i686": "node ../../cli/scripts/index.js build --js false --features \"latest\" --target i686-pc-windows-msvc", 11 | "build-i686-release": "node ../../cli/scripts/index.js build --js false --release --features \"latest\" --target i686-pc-windows-msvc", 12 | "build-release": "node ../../cli/scripts/index.js build --js false --features \"latest\" --release", 13 | "test": "node ./index.js" 14 | }, 15 | "devDependencies": { 16 | "sinon": "^15.0.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/napi7/arraybuffer.spec.ts: -------------------------------------------------------------------------------- 1 | import ava from 'ava' 2 | 3 | import { napiVersion } from '../napi-version' 4 | 5 | const bindings = require('../../index.node') 6 | 7 | const test = napiVersion >= 7 ? ava : ava.skip 8 | 9 | test('should be able to detach ArrayBuffer', (t) => { 10 | const buf = Buffer.from('hello world') 11 | const ab = buf.buffer.slice(0, buf.length) 12 | try { 13 | bindings.testDetachArrayBuffer(ab) 14 | t.is(ab.byteLength, 0) 15 | } catch (e) { 16 | t.is((e as any).code, 'DetachableArraybufferExpected') 17 | } 18 | }) 19 | 20 | test('is detached arraybuffer should work fine', (t) => { 21 | const buf = Buffer.from('hello world') 22 | const ab = buf.buffer.slice(0, buf.length) 23 | try { 24 | bindings.testDetachArrayBuffer(ab) 25 | const nonDetachedArrayBuffer = new ArrayBuffer(10) 26 | t.true(bindings.testIsDetachedArrayBuffer(ab)) 27 | t.false(bindings.testIsDetachedArrayBuffer(nonDetachedArrayBuffer)) 28 | } catch (e) { 29 | t.is((e as any).code, 'DetachableArraybufferExpected') 30 | } 31 | }) 32 | -------------------------------------------------------------------------------- /crates/napi/src/bindgen_runtime/js_values/boolean.rs: -------------------------------------------------------------------------------- 1 | use crate::{bindgen_prelude::*, check_status, sys, ValueType}; 2 | 3 | impl TypeName for bool { 4 | fn type_name() -> &'static str { 5 | "bool" 6 | } 7 | 8 | fn value_type() -> ValueType { 9 | ValueType::Boolean 10 | } 11 | } 12 | 13 | impl ValidateNapiValue for bool {} 14 | 15 | impl ToNapiValue for bool { 16 | unsafe fn to_napi_value(env: sys::napi_env, val: bool) -> Result { 17 | let mut ptr = std::ptr::null_mut(); 18 | 19 | check_status!( 20 | unsafe { sys::napi_get_boolean(env, val, &mut ptr) }, 21 | "Failed to convert rust type `bool` into napi value", 22 | )?; 23 | 24 | Ok(ptr) 25 | } 26 | } 27 | 28 | impl FromNapiValue for bool { 29 | unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { 30 | let mut ret = false; 31 | 32 | check_status!( 33 | unsafe { sys::napi_get_value_bool(env, napi_val, &mut ret) }, 34 | "Failed to convert napi value into rust type `bool`", 35 | )?; 36 | 37 | Ok(ret) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/serde/de.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | import { napiVersion } from '../napi-version' 4 | 5 | const bindings = require('../../index.node') 6 | 7 | test('deserialize string', (t) => { 8 | t.notThrows(() => bindings.expect_hello_world('hello world')) 9 | }) 10 | 11 | test('deserialize object', (t) => { 12 | if (napiVersion < 6) { 13 | t.throws(() => { 14 | bindings.expect_obj({}) 15 | }) 16 | } else { 17 | t.notThrows(() => 18 | bindings.expect_obj({ 19 | a: 1, 20 | b: [1, 2], 21 | c: 'abc', 22 | d: false, 23 | e: null, 24 | f: null, 25 | g: [9, false, 'efg'], 26 | h: '🤷', 27 | i: 'Empty', 28 | j: { Tuple: [27, 'hij'] }, 29 | k: { Struct: { a: 128, b: [9, 8, 7] } }, 30 | l: 'jkl', 31 | m: [0, 1, 2, 3, 4], 32 | o: { Value: ['z', 'y', 'x'] }, 33 | p: [1, 2, 3.5], 34 | q: BigInt('9998881288248882845242411222333'), 35 | r: BigInt('-3332323888900001232323022221345'), 36 | }), 37 | ) 38 | } 39 | }) 40 | -------------------------------------------------------------------------------- /examples/napi/src/fn_ts_override.rs: -------------------------------------------------------------------------------- 1 | use napi::bindgen_prelude::{Object, Result}; 2 | use napi::JsFunction; 3 | 4 | #[napi(ts_args_type = "a: { foo: number }", ts_return_type = "string[]")] 5 | fn ts_rename(a: Object) -> Result { 6 | a.get_property_names() 7 | } 8 | 9 | #[napi] 10 | fn override_individual_arg_on_function( 11 | not_overridden: String, 12 | #[napi(ts_arg_type = "() => string")] f: JsFunction, 13 | not_overridden2: u32, 14 | ) -> String { 15 | let u = f.call_without_args(None).unwrap(); 16 | let s = u 17 | .coerce_to_string() 18 | .unwrap() 19 | .into_utf8() 20 | .unwrap() 21 | .as_str() 22 | .unwrap() 23 | .to_string(); 24 | 25 | format!("oia: {}-{}-{}", not_overridden, not_overridden2, s) 26 | } 27 | 28 | #[napi] 29 | fn override_individual_arg_on_function_with_cb_arg< 30 | T: Fn(String, Option) -> Result, 31 | >( 32 | #[napi(ts_arg_type = "(town: string, name?: string | undefined | null) => string")] callback: T, 33 | not_overridden: u32, 34 | ) -> Result { 35 | callback(format!("World({})", not_overridden), None) 36 | } 37 | -------------------------------------------------------------------------------- /memory-testing/serde.mjs: -------------------------------------------------------------------------------- 1 | import { createRequire } from 'module' 2 | 3 | import { displayMemoryUsageFromNode } from './util.mjs' 4 | 5 | const initialMemoryUsage = process.memoryUsage() 6 | 7 | const require = createRequire(import.meta.url) 8 | 9 | const api = require(`./index.node`) 10 | 11 | const data = { 12 | id: 'ckovh15xa104945sj64rdk8oas', 13 | name: '1883da9ff9152', 14 | forename: '221c99bedc6a4', 15 | description: '8bf86b62ce6a', 16 | email: '9d57a869661cc', 17 | phone: '7e0c58d147215', 18 | arrivalDate: -92229669, 19 | departureDate: 202138795, 20 | price: -1592700387, 21 | advance: -369294193, 22 | advanceDueDate: 925000428, 23 | kids: 520124290, 24 | adults: 1160258464, 25 | status: 'NO_PAYMENT', 26 | nourishment: 'BB', 27 | createdAt: '2021-05-19T12:58:37.246Z', 28 | room: { id: 'ckovh15xa104955sj6r2tqaw1c', name: '38683b87f2664' }, 29 | } 30 | 31 | let i = 1 32 | // eslint-disable-next-line no-constant-condition 33 | while (true) { 34 | api.fromJs(data) 35 | if (i % 100000 === 0) { 36 | displayMemoryUsageFromNode(initialMemoryUsage) 37 | } 38 | i++ 39 | } 40 | -------------------------------------------------------------------------------- /crates/napi/src/js_values/escapable_handle_scope.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | use std::ptr; 3 | 4 | use crate::check_status; 5 | use crate::{sys, Env, NapiRaw, Result}; 6 | 7 | pub struct EscapableHandleScope { 8 | handle_scope: sys::napi_escapable_handle_scope, 9 | value: T, 10 | } 11 | 12 | impl EscapableHandleScope { 13 | pub fn open(env: Env, value: T) -> Result { 14 | let mut handle_scope = ptr::null_mut(); 15 | check_status!(unsafe { sys::napi_open_escapable_handle_scope(env.0, &mut handle_scope) })?; 16 | let mut result = ptr::null_mut(); 17 | check_status!(unsafe { 18 | sys::napi_escape_handle(env.0, handle_scope, NapiRaw::raw(&value), &mut result) 19 | })?; 20 | Ok(Self { 21 | handle_scope, 22 | value, 23 | }) 24 | } 25 | 26 | pub fn close(self, env: Env) -> Result<()> { 27 | check_status!(unsafe { sys::napi_close_escapable_handle_scope(env.0, self.handle_scope) }) 28 | } 29 | } 30 | 31 | impl Deref for EscapableHandleScope { 32 | type Target = T; 33 | 34 | fn deref(&self) -> &T { 35 | &self.value 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/src/error.rs: -------------------------------------------------------------------------------- 1 | use napi::{CallContext, Error, JsBoolean, JsObject, JsString, JsUnknown, Result, Status}; 2 | 3 | #[js_function] 4 | fn test_throw(_ctx: CallContext) -> Result { 5 | Err(Error::from_status(Status::GenericFailure)) 6 | } 7 | 8 | #[js_function(1)] 9 | fn test_throw_with_reason(ctx: CallContext) -> Result { 10 | let reason = ctx.get::(0)?; 11 | Err(Error::new( 12 | Status::GenericFailure, 13 | reason.into_utf8()?.into_owned()?, 14 | )) 15 | } 16 | 17 | #[js_function] 18 | pub fn test_throw_with_panic(_ctx: CallContext) -> Result { 19 | panic!("don't panic."); 20 | } 21 | 22 | #[js_function(1)] 23 | pub fn is_error(ctx: CallContext) -> Result { 24 | let js_value = ctx.get::(0)?; 25 | ctx.env.get_boolean(js_value.is_error()?) 26 | } 27 | 28 | pub fn register_js(exports: &mut JsObject) -> Result<()> { 29 | exports.create_named_method("testThrow", test_throw)?; 30 | exports.create_named_method("testThrowWithReason", test_throw_with_reason)?; 31 | exports.create_named_method("isError", is_error)?; 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /cli/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 LongYinan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /crates/napi/src/js_values/string/latin1.rs: -------------------------------------------------------------------------------- 1 | use std::mem::ManuallyDrop; 2 | 3 | use crate::JsString; 4 | 5 | #[cfg(feature = "latin1")] 6 | use crate::Result; 7 | 8 | pub struct JsStringLatin1 { 9 | pub(crate) inner: JsString, 10 | pub(crate) buf: ManuallyDrop>, 11 | } 12 | 13 | impl JsStringLatin1 { 14 | pub fn as_slice(&self) -> &[u8] { 15 | &self.buf 16 | } 17 | 18 | pub fn len(&self) -> usize { 19 | self.buf.len() 20 | } 21 | 22 | pub fn is_empty(&self) -> bool { 23 | self.buf.is_empty() 24 | } 25 | 26 | pub fn take(self) -> Vec { 27 | self.as_slice().to_vec() 28 | } 29 | 30 | pub fn into_value(self) -> JsString { 31 | self.inner 32 | } 33 | 34 | #[cfg(feature = "latin1")] 35 | pub fn into_latin1_string(self) -> Result { 36 | let mut dst_str = unsafe { String::from_utf8_unchecked(vec![0; self.len() * 2 + 1]) }; 37 | encoding_rs::mem::convert_latin1_to_str(self.buf.as_slice(), dst_str.as_mut_str()); 38 | Ok(dst_str) 39 | } 40 | } 41 | 42 | impl From for Vec { 43 | fn from(value: JsStringLatin1) -> Self { 44 | value.take() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /triples/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 LongYinan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /crates/napi/src/js_values/date.rs: -------------------------------------------------------------------------------- 1 | use std::ptr; 2 | 3 | use super::check_status; 4 | use crate::{ 5 | bindgen_runtime::{TypeName, ValidateNapiValue}, 6 | sys, Error, Result, Status, Value, ValueType, 7 | }; 8 | 9 | pub struct JsDate(pub(crate) Value); 10 | 11 | impl TypeName for JsDate { 12 | fn type_name() -> &'static str { 13 | "Date" 14 | } 15 | 16 | fn value_type() -> crate::ValueType { 17 | ValueType::Object 18 | } 19 | } 20 | 21 | impl ValidateNapiValue for JsDate { 22 | unsafe fn validate(env: sys::napi_env, napi_val: sys::napi_value) -> Result { 23 | let mut is_date = false; 24 | check_status!(unsafe { sys::napi_is_date(env, napi_val, &mut is_date) })?; 25 | if !is_date { 26 | return Err(Error::new( 27 | Status::InvalidArg, 28 | "Expected a Date object".to_owned(), 29 | )); 30 | } 31 | 32 | Ok(ptr::null_mut()) 33 | } 34 | } 35 | 36 | impl JsDate { 37 | pub fn value_of(&self) -> Result { 38 | let mut timestamp: f64 = 0.0; 39 | check_status!(unsafe { sys::napi_get_date_value(self.0.env, self.0.value, &mut timestamp) })?; 40 | Ok(timestamp) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/src/napi6/mod.rs: -------------------------------------------------------------------------------- 1 | use napi::{JsObject, Result}; 2 | 3 | mod bigint; 4 | mod instance; 5 | 6 | use bigint::*; 7 | use instance::*; 8 | 9 | pub fn register_js(exports: &mut JsObject) -> Result<()> { 10 | exports.create_named_method("testCreateBigintFromI64", test_create_bigint_from_i64)?; 11 | exports.create_named_method("testCreateBigintFromU64", test_create_bigint_from_u64)?; 12 | exports.create_named_method("testCreateBigintFromI128", test_create_bigint_from_i128)?; 13 | exports.create_named_method("testCreateBigintFromU128", test_create_bigint_from_u128)?; 14 | exports.create_named_method("testCreateBigintFromWords", test_create_bigint_from_words)?; 15 | exports.create_named_method("testGetBigintI64", test_get_bigint_i64)?; 16 | exports.create_named_method("testGetBigintU64", test_get_bigint_u64)?; 17 | exports.create_named_method("testGetBigintWords", test_get_bigint_words)?; 18 | 19 | exports.create_named_method("setInstanceData", set_instance_data)?; 20 | exports.create_named_method("getInstanceData", get_instance_data)?; 21 | exports.create_named_method("getWrongTypeInstanceData", get_wrong_type_instance_data)?; 22 | Ok(()) 23 | } 24 | -------------------------------------------------------------------------------- /examples/napi/__test__/worker.js: -------------------------------------------------------------------------------- 1 | const { parentPort } = require('worker_threads') 2 | 3 | const native = require('../index') 4 | 5 | parentPort.on('message', ({ type }) => { 6 | switch (type) { 7 | case 'require': 8 | parentPort.postMessage( 9 | native.Animal.withKind(native.Kind.Cat).whoami() + native.DEFAULT_COST, 10 | ) 11 | break 12 | case 'async:buffer': 13 | Promise.all( 14 | Array.from({ length: 100 }).map(() => 15 | native.bufferPassThrough(Buffer.from([1, 2, 3])), 16 | ), 17 | ) 18 | .then(() => { 19 | parentPort.postMessage('done') 20 | }) 21 | .catch((e) => { 22 | throw e 23 | }) 24 | break 25 | case 'async:arraybuffer': 26 | Promise.all( 27 | Array.from({ length: 100 }).map(() => 28 | native.arrayBufferPassThrough(Uint8Array.from([1, 2, 3])), 29 | ), 30 | ) 31 | .then(() => { 32 | parentPort.postMessage('done') 33 | }) 34 | .catch((e) => { 35 | throw e 36 | }) 37 | 38 | break 39 | default: 40 | throw new TypeError(`Unknown message type: ${type}`) 41 | } 42 | }) 43 | -------------------------------------------------------------------------------- /examples/napi/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(unreachable_code)] 3 | #![allow(clippy::disallowed_names)] 4 | #![allow(clippy::uninlined_format_args)] 5 | 6 | use napi::{Env, JsUnknown}; 7 | 8 | #[macro_use] 9 | extern crate napi_derive; 10 | #[macro_use] 11 | extern crate serde_derive; 12 | 13 | #[cfg(feature = "snmalloc")] 14 | #[global_allocator] 15 | static ALLOC: snmalloc_rs::SnMalloc = snmalloc_rs::SnMalloc; 16 | 17 | #[napi] 18 | /// This is a const 19 | pub const DEFAULT_COST: u32 = 12; 20 | 21 | #[napi(skip_typescript)] 22 | pub const TYPE_SKIPPED_CONST: u32 = 12; 23 | 24 | mod array; 25 | mod r#async; 26 | mod bigint; 27 | mod callback; 28 | mod class; 29 | mod class_factory; 30 | mod date; 31 | mod either; 32 | mod r#enum; 33 | mod error; 34 | mod external; 35 | mod fn_strict; 36 | mod fn_ts_override; 37 | mod generator; 38 | mod js_mod; 39 | mod map; 40 | mod nullable; 41 | mod number; 42 | mod object; 43 | mod promise; 44 | mod reference; 45 | mod serde; 46 | mod string; 47 | mod symbol; 48 | mod task; 49 | mod threadsafe_function; 50 | mod typed_array; 51 | 52 | #[napi] 53 | pub fn run_script(env: Env, script: String) -> napi::Result { 54 | env.run_script(script) 55 | } 56 | -------------------------------------------------------------------------------- /examples/napi/__test__/values.spec.ts.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `examples/napi/__test__/values.spec.ts` 2 | 3 | The actual snapshot is saved in `values.spec.ts.snap`. 4 | 5 | Generated by [AVA](https://avajs.dev). 6 | 7 | ## serde-json 8 | 9 | > Snapshot 1 10 | 11 | [ 12 | '@rollup/plugin-alias', 13 | '@rollup/plugin-commonjs', 14 | '@rollup/plugin-json', 15 | '@rollup/plugin-node-resolve', 16 | '@rollup/plugin-replace', 17 | '@taplo/cli', 18 | '@types/debug', 19 | '@types/lodash-es', 20 | '@types/node', 21 | '@types/sinon', 22 | '@typescript-eslint/eslint-plugin', 23 | '@typescript-eslint/parser', 24 | 'ava', 25 | 'benny', 26 | 'c8', 27 | 'colorette', 28 | 'cross-env', 29 | 'electron', 30 | 'esbuild', 31 | 'eslint', 32 | 'eslint-config-prettier', 33 | 'eslint-plugin-import', 34 | 'eslint-plugin-prettier', 35 | 'husky', 36 | 'lerna', 37 | 'lint-staged', 38 | 'npm-run-all', 39 | 'prettier', 40 | 'rollup', 41 | 'shx', 42 | 'sinon', 43 | 'source-map-support', 44 | 'ts-node', 45 | 'tslib', 46 | 'typescript', 47 | ] 48 | -------------------------------------------------------------------------------- /images/nextjs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /cli/src/index.ts: -------------------------------------------------------------------------------- 1 | import 'core-js/es/string/replace-all' 2 | 3 | import { Cli } from 'clipanion' 4 | 5 | import { version } from '../package.json' 6 | 7 | import { ArtifactsCommand } from './artifacts' 8 | import { BuildCommand } from './build' 9 | import { CreateNpmDirCommand } from './create-npm-dir' 10 | import { HelpCommand } from './help' 11 | import { NewProjectCommand } from './new' 12 | import { PrePublishCommand } from './pre-publish' 13 | import { RenameCommand } from './rename' 14 | import { UniversalCommand } from './universal' 15 | import { VersionCommand } from './version' 16 | 17 | const cli = new Cli({ 18 | binaryName: 'napi', 19 | binaryVersion: version, 20 | }) 21 | 22 | cli.register(ArtifactsCommand) 23 | cli.register(BuildCommand) 24 | cli.register(CreateNpmDirCommand) 25 | cli.register(PrePublishCommand) 26 | cli.register(VersionCommand) 27 | cli.register(UniversalCommand) 28 | cli.register(NewProjectCommand) 29 | cli.register(RenameCommand) 30 | cli.register(HelpCommand) 31 | 32 | cli 33 | .run(process.argv.slice(2), { 34 | ...Cli.defaultContext, 35 | }) 36 | .then((status) => { 37 | process.exit(status) 38 | }) 39 | .catch((e) => { 40 | console.error(e) 41 | process.exit(1) 42 | }) 43 | -------------------------------------------------------------------------------- /bench/async.ts: -------------------------------------------------------------------------------- 1 | import { cpus } from 'os' 2 | 3 | import b from 'benny' 4 | 5 | const { 6 | benchAsyncTask, 7 | benchThreadsafeFunction, 8 | benchTokioFuture, 9 | } = require('./index.node') 10 | 11 | const buffer = Buffer.from('hello 🚀 rust!') 12 | 13 | const ALL_THREADS = Array.from({ length: cpus().length }) 14 | 15 | export const benchAsync = () => 16 | b.suite( 17 | 'Async task', 18 | b.add('spawn task', async () => { 19 | await Promise.all(ALL_THREADS.map(() => benchAsyncTask(buffer))) 20 | }), 21 | b.add('ThreadSafeFunction', async () => { 22 | await Promise.all( 23 | ALL_THREADS.map( 24 | () => 25 | new Promise((resolve, reject) => { 26 | benchThreadsafeFunction(buffer, (err?: Error, value?: number) => { 27 | if (err) { 28 | reject(err) 29 | } else { 30 | resolve(value) 31 | } 32 | }) 33 | }), 34 | ), 35 | ) 36 | }), 37 | b.add('Tokio future to Promise', async () => { 38 | await Promise.all(ALL_THREADS.map(() => benchTokioFuture(buffer))) 39 | }), 40 | b.cycle(), 41 | b.complete(), 42 | ) 43 | -------------------------------------------------------------------------------- /bench/src/create_array.rs: -------------------------------------------------------------------------------- 1 | use napi::{ContextlessResult, Env, JsObject, JsString, JsUnknown, Result}; 2 | use serde_json::to_string; 3 | 4 | pub fn register_js(exports: &mut JsObject) -> Result<()> { 5 | exports.create_named_method("createArrayJson", create_array_json)?; 6 | exports.create_named_method("createArray", create_array)?; 7 | exports.create_named_method("createArrayWithSerdeTrait", create_array_with_serde_trait)?; 8 | Ok(()) 9 | } 10 | 11 | #[contextless_function] 12 | pub fn create_array_json(env: Env) -> ContextlessResult { 13 | let a: Vec = vec![42; 1000]; 14 | let arr_string = to_string(&a)?; 15 | env.create_string(arr_string.as_str()).map(Some) 16 | } 17 | 18 | #[contextless_function] 19 | pub fn create_array(env: Env) -> ContextlessResult { 20 | let a: Vec = vec![42; 1000]; 21 | let mut ret = env.create_array_with_length(a.len())?; 22 | for (index, item) in a.iter().enumerate() { 23 | ret.set_element(index as u32, env.create_uint32(*item)?)?; 24 | } 25 | Ok(Some(ret)) 26 | } 27 | 28 | #[contextless_function] 29 | pub fn create_array_with_serde_trait(env: Env) -> ContextlessResult { 30 | let a: Vec = vec![42; 1000]; 31 | env.to_js_value(&a).map(Some) 32 | } 33 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/class.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | const bindings = require('../index.node') 4 | 5 | test('should create class', (t) => { 6 | const TestClass = bindings.createTestClass() 7 | const fixture = 20 8 | const testClass = new TestClass(fixture) 9 | t.is(testClass.count, fixture) 10 | const add = 101 11 | testClass.addCount(add) 12 | t.is(testClass.count, fixture + add) 13 | }) 14 | 15 | test('should be able to manipulate wrapped native value', (t) => { 16 | const TestClass = bindings.createTestClass() 17 | const fixture = 20 18 | const testClass = new TestClass(fixture) 19 | const add = 101 20 | t.is(testClass.addNativeCount(add), fixture + add + 100) 21 | }) 22 | 23 | test('should be able to re-create wrapped native value', (t) => { 24 | const TestClass = bindings.createTestClass() 25 | const fixture = 20 26 | const testClass = new TestClass(fixture) 27 | const add = 101 28 | t.is(testClass.addNativeCount(add), fixture + add + 100) 29 | testClass.renewWrapped() 30 | t.is(testClass.addNativeCount(0), 42) 31 | }) 32 | 33 | test('should be able to new class instance in native side', (t) => { 34 | const instance = bindings.newTestClass() 35 | t.is(instance.count, 42) 36 | }) 37 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/src/either.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryInto; 2 | 3 | use napi::{CallContext, Either, JsNumber, JsObject, JsString, Result}; 4 | 5 | #[js_function(1)] 6 | pub fn either_number_string(ctx: CallContext) -> Result> { 7 | let arg = ctx.get::>(0)?; 8 | match arg { 9 | Either::A(n) => { 10 | let n: u32 = n.try_into()?; 11 | ctx.env.create_uint32(n + 100).map(Either::A) 12 | } 13 | Either::B(s) => { 14 | let content = format!("Either::B({})", s.into_utf8()?.as_str()?); 15 | ctx.env.create_string_from_std(content).map(Either::B) 16 | } 17 | } 18 | } 19 | 20 | #[js_function(1)] 21 | pub fn dynamic_argument_length(ctx: CallContext) -> Result { 22 | let value: Option = ctx.try_get::(0)?.into(); 23 | if let Some(n) = value { 24 | let n: u32 = n.try_into()?; 25 | ctx.env.create_uint32(n + 100) 26 | } else { 27 | ctx.env.create_uint32(42) 28 | } 29 | } 30 | 31 | pub fn register_js(exports: &mut JsObject) -> Result<()> { 32 | exports.create_named_method("eitherNumberString", either_number_string)?; 33 | exports.create_named_method("dynamicArgumentLength", dynamic_argument_length)?; 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /crates/backend/src/codegen.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Ident, Span, TokenStream}; 2 | 3 | use crate::BindgenResult; 4 | 5 | mod r#const; 6 | mod r#enum; 7 | mod r#fn; 8 | mod r#struct; 9 | 10 | pub const PROPERTY_ATTRIBUTE_DEFAULT: i32 = 0; 11 | pub const PROPERTY_ATTRIBUTE_WRITABLE: i32 = 1 << 0; 12 | pub const PROPERTY_ATTRIBUTE_ENUMERABLE: i32 = 1 << 1; 13 | pub const PROPERTY_ATTRIBUTE_CONFIGURABLE: i32 = 1 << 2; 14 | 15 | pub trait TryToTokens { 16 | fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()>; 17 | 18 | fn try_to_token_stream(&self) -> BindgenResult { 19 | let mut tokens = TokenStream::default(); 20 | self.try_to_tokens(&mut tokens)?; 21 | 22 | Ok(tokens) 23 | } 24 | } 25 | 26 | fn get_intermediate_ident(name: &str) -> Ident { 27 | let new_name = format!("__napi__{}", name); 28 | Ident::new(&new_name, Span::call_site()) 29 | } 30 | 31 | fn get_register_ident(name: &str) -> Ident { 32 | let new_name = format!("__napi_register__{}", name); 33 | Ident::new(&new_name, Span::call_site()) 34 | } 35 | 36 | fn js_mod_to_token_stream(js_mod: Option<&String>) -> TokenStream { 37 | js_mod 38 | .map(|i| { 39 | let i = format!("{}\0", i); 40 | quote! { Some(#i) } 41 | }) 42 | .unwrap_or_else(|| quote! { None }) 43 | } 44 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/src/external.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryInto; 2 | 3 | use napi::{CallContext, JsExternal, JsNumber, JsObject, Result}; 4 | 5 | struct NativeObject { 6 | count: i32, 7 | } 8 | 9 | #[js_function(1)] 10 | pub fn create_external(ctx: CallContext) -> Result { 11 | let count = ctx.get::(0)?.try_into()?; 12 | let native = NativeObject { count }; 13 | ctx.env.create_external(native, None) 14 | } 15 | 16 | #[js_function(1)] 17 | pub fn create_external_with_hint(ctx: CallContext) -> Result { 18 | let count = ctx.get::(0)?.try_into()?; 19 | let native = NativeObject { count }; 20 | ctx.env.create_external(native, Some(5)) 21 | } 22 | 23 | #[js_function(1)] 24 | pub fn get_external_count(ctx: CallContext) -> Result { 25 | let attached_obj = ctx.get::(0)?; 26 | let native_object = ctx.env.get_value_external::(&attached_obj)?; 27 | ctx.env.create_int32(native_object.count) 28 | } 29 | 30 | pub fn register_js(exports: &mut JsObject) -> Result<()> { 31 | exports.create_named_method("createExternal", create_external)?; 32 | exports.create_named_method("createExternalWithHint", create_external_with_hint)?; 33 | exports.create_named_method("getExternalCount", get_external_count)?; 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /generate-triple-list.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync, writeFileSync } from 'fs' 2 | import { join } from 'path' 3 | 4 | import * as esbuild from 'esbuild' 5 | import { groupBy, mapValues } from 'lodash' 6 | 7 | import { parseTriple } from './cli/src/parse-triple' 8 | 9 | const RAW_LIST = readFileSync(join(__dirname, 'triples', 'target-list'), 'utf8') 10 | 11 | const SUPPORTED_PLATFORM = new Set([ 12 | 'darwin', 13 | 'ios', 14 | 'android', 15 | 'win32', 16 | 'linux', 17 | 'freebsd', 18 | ]) 19 | 20 | const tripleLists: { [key: string]: { platform?: string } } = RAW_LIST.trim() 21 | .split('\n') 22 | .filter((line) => !line.startsWith('wasm') && line.trim().length) 23 | .map(parseTriple) 24 | .reduce((acc, cur) => { 25 | acc[cur.raw] = cur 26 | return acc 27 | }, {}) 28 | 29 | const platformArchTriples = mapValues( 30 | groupBy( 31 | Object.values(tripleLists).filter((k) => 32 | SUPPORTED_PLATFORM.has(k.platform!), 33 | ), 34 | 'platform', 35 | ), 36 | (v) => groupBy(v, 'arch'), 37 | ) 38 | 39 | const fileContent = ` 40 | module.exports.platformArchTriples = ${JSON.stringify(platformArchTriples)} 41 | ` 42 | 43 | writeFileSync( 44 | join(__dirname, 'triples', 'index.js'), 45 | esbuild.transformSync(fileContent, { 46 | minify: true, 47 | }).code, 48 | ) 49 | -------------------------------------------------------------------------------- /bench/bench.ts: -------------------------------------------------------------------------------- 1 | import { promises as fs } from 'fs' 2 | import { join } from 'path' 3 | 4 | import { Summary } from 'benny/lib/internal/common-types' 5 | 6 | import { benchAsync } from './async' 7 | import { benchBuffer } from './buffer' 8 | import { benchCreateArray } from './create-array' 9 | import { benchGetArray } from './get-array-from-js' 10 | import { benchGetSetProperty } from './get-set-property' 11 | import { benchNoop } from './noop' 12 | import { benchPlus } from './plus' 13 | import { benchQuery } from './query' 14 | 15 | async function run() { 16 | const output = [ 17 | await benchNoop(), 18 | await benchPlus(), 19 | await benchBuffer(), 20 | await benchCreateArray(), 21 | await benchGetArray(), 22 | await benchGetSetProperty(), 23 | await benchAsync(), 24 | await benchQuery(), 25 | ] 26 | .map(formatSummary) 27 | .join('\n') 28 | 29 | await fs.writeFile(join(process.cwd(), 'bench.txt'), output, 'utf8') 30 | } 31 | 32 | function formatSummary(summary: Summary): string { 33 | return summary.results 34 | .map( 35 | (result) => 36 | `${summary.name}#${result.name} x ${result.ops} ops/sec ±${result.margin}% (${result.samples} runs sampled)`, 37 | ) 38 | .join('\n') 39 | } 40 | 41 | run().catch((e) => { 42 | console.error(e) 43 | }) 44 | -------------------------------------------------------------------------------- /cli/src/consts.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path' 2 | 3 | import { DefaultPlatforms, PlatformDetail, parseTriple } from './parse-triple' 4 | 5 | export function getNapiConfig( 6 | packageJson = 'package.json', 7 | cwd = process.cwd(), 8 | ) { 9 | const packageJsonPath = join(cwd, packageJson) 10 | 11 | const pkgJson = require(packageJsonPath) 12 | const { version: packageVersion, napi, name } = pkgJson 13 | const additionPlatforms: PlatformDetail[] = ( 14 | napi?.triples?.additional ?? [] 15 | ).map(parseTriple) 16 | const defaultPlatforms = 17 | napi?.triples?.defaults === false ? [] : [...DefaultPlatforms] 18 | const platforms = [...defaultPlatforms, ...additionPlatforms] 19 | const releaseVersion = process.env.RELEASE_VERSION 20 | const releaseVersionWithoutPrefix = releaseVersion?.startsWith('v') 21 | ? releaseVersion.substring(1) 22 | : releaseVersion 23 | const version = releaseVersionWithoutPrefix ?? packageVersion 24 | const packageName = napi?.package?.name ?? name 25 | const npmClient: string = napi?.npmClient ?? 'npm' 26 | 27 | const binaryName: string = napi?.name ?? 'index' 28 | 29 | return { 30 | platforms, 31 | version, 32 | packageName, 33 | binaryName, 34 | packageJsonPath, 35 | content: pkgJson, 36 | npmClient, 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /bench/get-set-property.ts: -------------------------------------------------------------------------------- 1 | import b from 'benny' 2 | 3 | const { TestClass } = require('./index.node') 4 | 5 | function createClass() { 6 | const testObject = new TestClass() 7 | 8 | Object.defineProperty(testObject, '_miterLimit', { 9 | value: 10, 10 | configurable: false, 11 | enumerable: false, 12 | writable: true, 13 | }) 14 | 15 | Object.defineProperty(testObject, '_lineJoin', { 16 | value: 'miter', 17 | configurable: false, 18 | enumerable: false, 19 | writable: true, 20 | }) 21 | 22 | return testObject 23 | } 24 | 25 | export const benchGetSetProperty = () => 26 | b.suite( 27 | 'Get Set property', 28 | b.add('Get Set from native#u32', () => { 29 | const o = createClass() 30 | o.miterNative 31 | o.miterNative = 1 32 | }), 33 | b.add('Get Set from JavaScript#u32', () => { 34 | const o = createClass() 35 | o.miter 36 | o.miter = 1 37 | }), 38 | 39 | b.add('Get Set from native#string', () => { 40 | const o = createClass() 41 | o.lineJoinNative 42 | o.lineJoinNative = 'bevel' 43 | }), 44 | b.add('Get Set from JavaScript#string', () => { 45 | const o = createClass() 46 | o.lineJoin 47 | o.lineJoin = 'bevel' 48 | }), 49 | 50 | b.cycle(), 51 | b.complete(), 52 | ) 53 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/napi5/date.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | import { napiVersion } from '../napi-version' 4 | 5 | const bindings = require('../../index.node') 6 | 7 | test('should return false if value is not date', (t) => { 8 | if (napiVersion >= 5) { 9 | t.false(bindings.testObjectIsDate({})) 10 | t.false(bindings.testObjectIsDate(null)) 11 | t.false(bindings.testObjectIsDate()) 12 | t.false(bindings.testObjectIsDate(10249892)) 13 | } else { 14 | t.is(bindings.testObjectIsDate, undefined) 15 | } 16 | }) 17 | 18 | test('should return true if value is date', (t) => { 19 | if (napiVersion >= 5) { 20 | t.true(bindings.testObjectIsDate(new Date())) 21 | } else { 22 | t.is(bindings.testObjectIsDate, undefined) 23 | } 24 | }) 25 | 26 | test('should create date', (t) => { 27 | if (napiVersion >= 5) { 28 | const timestamp = new Date().valueOf() 29 | t.deepEqual(bindings.testCreateDate(timestamp), new Date(timestamp)) 30 | } else { 31 | t.is(bindings.testObjectIsDate, undefined) 32 | } 33 | }) 34 | 35 | test('should get date value', (t) => { 36 | if (napiVersion >= 5) { 37 | const date = new Date() 38 | t.is(bindings.testGetDateValue(date), date.valueOf()) 39 | } else { 40 | t.is(bindings.testObjectIsDate, undefined) 41 | } 42 | }) 43 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/src/tokio_rt/read_file.rs: -------------------------------------------------------------------------------- 1 | use futures::prelude::*; 2 | use napi::{CallContext, Error, JsObject, JsString, Result, Status}; 3 | 4 | #[js_function(1)] 5 | pub fn test_execute_tokio_readfile(ctx: CallContext) -> Result { 6 | let js_filepath = ctx.get::(0)?; 7 | let path_str = js_filepath.into_utf8()?.into_owned()?; 8 | ctx.env.execute_tokio_future( 9 | tokio::fs::read(path_str).map(|v| { 10 | v.map_err(|e| { 11 | Error::new( 12 | Status::GenericFailure, 13 | format!("failed to read file, {}", e), 14 | ) 15 | }) 16 | }), 17 | |&mut env, data| env.create_buffer_with_data(data).map(|v| v.into_raw()), 18 | ) 19 | } 20 | 21 | #[js_function(1)] 22 | pub fn error_from_tokio_future(ctx: CallContext) -> Result { 23 | let js_filepath = ctx.get::(0)?; 24 | let path_str = js_filepath.into_utf8()?.into_owned()?; 25 | ctx.env.execute_tokio_future( 26 | tokio::fs::read(path_str) 27 | .map_err(Error::from) 28 | .and_then(|_| async move { 29 | Err::, Error>(Error::new( 30 | Status::GenericFailure, 31 | "Error from tokio future".to_owned(), 32 | )) 33 | }), 34 | |&mut env, data| env.create_buffer_with_data(data).map(|v| v.into_raw()), 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/napi6/bigint.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | import { napiVersion } from '../napi-version' 4 | 5 | const bindings = require('../../index.node') 6 | 7 | test('should create bigints', (t) => { 8 | if (napiVersion >= 6) { 9 | t.is(bindings.testCreateBigintFromI64(), BigInt('9223372036854775807')) 10 | t.is(bindings.testCreateBigintFromU64(), BigInt('18446744073709551615')) 11 | t.is( 12 | bindings.testCreateBigintFromI128(), 13 | BigInt('170141183460469231731687303715884105727'), 14 | ) 15 | t.is( 16 | bindings.testCreateBigintFromU128(), 17 | BigInt('340282366920938463463374607431768211455'), 18 | ) 19 | t.is( 20 | bindings.testCreateBigintFromWords(), 21 | BigInt('-340282366920938463463374607431768211455'), 22 | ) 23 | } else { 24 | t.is(bindings.testCreateBigintFromI64, undefined) 25 | } 26 | }) 27 | 28 | test('should get integers from bigints', (t) => { 29 | if (napiVersion >= 6) { 30 | t.is(bindings.testGetBigintI64(BigInt('-123')), -123) 31 | t.is(bindings.testGetBigintU64(BigInt(123)), 123) 32 | t.deepEqual(bindings.testGetBigintWords(), [ 33 | BigInt('9223372036854775807'), 34 | BigInt('9223372036854775807'), 35 | ]) 36 | } else { 37 | t.is(bindings.testGetBigintI64, undefined) 38 | } 39 | }) 40 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/src/napi4/mod.rs: -------------------------------------------------------------------------------- 1 | use napi::{Env, JsObject, Property, Result}; 2 | 3 | mod deferred; 4 | mod tsfn; 5 | mod tsfn_dua_instance; 6 | 7 | use tsfn::*; 8 | use tsfn_dua_instance::*; 9 | 10 | pub fn register_js(exports: &mut JsObject, env: &Env) -> Result<()> { 11 | exports.create_named_method("testThreadsafeFunction", test_threadsafe_function)?; 12 | exports.create_named_method("testTsfnError", test_tsfn_error)?; 13 | exports.create_named_method("testTokioReadfile", test_tokio_readfile)?; 14 | exports.create_named_method( 15 | "testAbortThreadsafeFunction", 16 | test_abort_threadsafe_function, 17 | )?; 18 | exports.create_named_method( 19 | "testAbortIndependentThreadsafeFunction", 20 | test_abort_independent_threadsafe_function, 21 | )?; 22 | exports.create_named_method( 23 | "testCallAbortedThreadsafeFunction", 24 | test_call_aborted_threadsafe_function, 25 | )?; 26 | exports.create_named_method("testTsfnWithRef", test_tsfn_with_ref)?; 27 | exports.create_named_method("testDeferred", deferred::test_deferred)?; 28 | 29 | let obj = env.define_class( 30 | "A", 31 | constructor, 32 | &[ 33 | Property::new("call")?.with_method(call), 34 | Property::new("unref")?.with_method(unref), 35 | ], 36 | )?; 37 | 38 | exports.set_named_property("A", obj)?; 39 | Ok(()) 40 | } 41 | -------------------------------------------------------------------------------- /bench/src/get_value_from_js.rs: -------------------------------------------------------------------------------- 1 | use napi::{CallContext, JsObject, JsString, JsUndefined, JsUnknown, Result}; 2 | use serde_json::from_str; 3 | 4 | pub fn register_js(exports: &mut JsObject) -> Result<()> { 5 | exports.create_named_method("getArrayFromJson", get_array_from_json)?; 6 | exports.create_named_method("getArrayFromJsArray", get_array_from_js_array)?; 7 | exports.create_named_method("getArrayWithForLoop", get_array_with_for_loop)?; 8 | Ok(()) 9 | } 10 | 11 | #[js_function(1)] 12 | fn get_array_from_json(ctx: CallContext) -> Result { 13 | let input = ctx.get::(0)?.into_utf8()?; 14 | let _: Vec = from_str(input.as_str()?)?; 15 | ctx.env.get_undefined() 16 | } 17 | 18 | #[js_function(1)] 19 | fn get_array_from_js_array(ctx: CallContext) -> Result { 20 | let input = ctx.get::(0)?; 21 | let _: Vec = ctx.env.from_js_value(input)?; 22 | ctx.env.get_undefined() 23 | } 24 | 25 | #[js_function(1)] 26 | fn get_array_with_for_loop(ctx: CallContext) -> Result { 27 | let input = ctx.get::(0)?; 28 | let array_length = input.get_array_length_unchecked()? as usize; 29 | let mut result: Vec = Vec::with_capacity(array_length); 30 | for i in 0..array_length { 31 | result.insert(i, input.get_element::(i as u32)?); 32 | } 33 | ctx.env.get_undefined() 34 | } 35 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | lint: 11 | name: Lint SourceCode 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | 17 | - name: Setup node 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: 18 21 | check-latest: true 22 | cache: 'yarn' 23 | 24 | - name: Install 25 | uses: actions-rs/toolchain@v1 26 | with: 27 | toolchain: stable 28 | profile: default 29 | override: true 30 | components: rustfmt, clippy 31 | 32 | - name: Cache cargo 33 | uses: actions/cache@v3 34 | with: 35 | path: | 36 | ~/.cargo/registry 37 | ~/.cargo/git 38 | key: lint-cargo-cache 39 | 40 | - name: 'Install dependencies' 41 | run: yarn install --immutable --mode=skip-build 42 | 43 | - name: 'Lint JS/TS' 44 | run: yarn lint 45 | 46 | - name: Cargo fmt 47 | run: cargo fmt -- --check 48 | 49 | - name: Clippy 50 | run: cargo clippy 51 | 52 | - name: Clear the cargo caches 53 | run: | 54 | cargo install cargo-cache --no-default-features --features ci-autoclean 55 | cargo-cache 56 | -------------------------------------------------------------------------------- /.github/workflows/check-all-features.yml: -------------------------------------------------------------------------------- 1 | name: Check NAPI-RS features 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | build_and_test: 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | features: 15 | [ 16 | 'napi3', 17 | 'napi4', 18 | 'napi5', 19 | 'napi6', 20 | 'napi7', 21 | 'napi8', 22 | 'experimental', 23 | 'async', 24 | 'chrono_date', 25 | 'latin1', 26 | 'full', 27 | ] 28 | 29 | name: stable - ubuntu-latest 30 | runs-on: ubuntu-latest 31 | 32 | steps: 33 | - uses: actions/checkout@v3 34 | 35 | - name: Install 36 | uses: actions-rs/toolchain@v1 37 | with: 38 | toolchain: stable 39 | profile: minimal 40 | override: true 41 | 42 | - name: Cache cargo 43 | uses: actions/cache@v3 44 | with: 45 | path: | 46 | ~/.cargo/registry 47 | ~/.cargo/git 48 | key: stable-check-ubuntu-latest-cargo-cache-features-${{ matrix.features }} 49 | 50 | - name: Check build 51 | uses: actions-rs/cargo@v1 52 | with: 53 | command: check 54 | args: -p napi --no-default-features --features ${{ matrix.features }} 55 | -------------------------------------------------------------------------------- /crates/napi/src/js_values/string/utf8.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | use std::ffi::CStr; 3 | use std::os::raw::c_char; 4 | use std::str; 5 | 6 | use crate::{Error, JsString, Result, Status}; 7 | 8 | pub struct JsStringUtf8 { 9 | pub(crate) inner: JsString, 10 | pub(crate) buf: Vec, 11 | } 12 | 13 | impl JsStringUtf8 { 14 | pub fn as_str(&self) -> Result<&str> { 15 | unsafe { CStr::from_ptr(self.buf.as_ptr()) } 16 | .to_str() 17 | .map_err(|e| Error::new(Status::InvalidArg, format!("{}", e))) 18 | } 19 | 20 | pub fn as_slice(&self) -> &[u8] { 21 | unsafe { CStr::from_ptr(self.buf.as_ptr()) }.to_bytes() 22 | } 23 | 24 | pub fn len(&self) -> usize { 25 | self.buf.len() 26 | } 27 | 28 | pub fn is_empty(&self) -> bool { 29 | self.buf.is_empty() 30 | } 31 | 32 | pub fn into_owned(self) -> Result { 33 | Ok(self.as_str()?.to_owned()) 34 | } 35 | 36 | pub fn take(self) -> Vec { 37 | self.as_slice().to_vec() 38 | } 39 | 40 | pub fn into_value(self) -> JsString { 41 | self.inner 42 | } 43 | } 44 | 45 | impl TryFrom for String { 46 | type Error = Error; 47 | 48 | fn try_from(value: JsStringUtf8) -> Result { 49 | value.into_owned() 50 | } 51 | } 52 | 53 | impl From for Vec { 54 | fn from(value: JsStringUtf8) -> Self { 55 | value.take() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /examples/napi/electron.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const { readFileSync } = require('fs') 3 | 4 | const { 5 | readFileAsync, 6 | callThreadsafeFunction, 7 | withAbortController, 8 | createExternalTypedArray, 9 | } = require('./index') 10 | 11 | const FILE_CONTENT = readFileSync(__filename, 'utf8') 12 | 13 | async function main() { 14 | const ctrl = new AbortController() 15 | const promise = withAbortController(1, 2, ctrl.signal) 16 | try { 17 | ctrl.abort() 18 | await promise 19 | throw new Error('Should throw AbortError') 20 | } catch (err) { 21 | assert(err.message === 'AbortError') 22 | } 23 | 24 | const buf = await readFileAsync(__filename) 25 | assert(FILE_CONTENT === buf.toString('utf8')) 26 | 27 | const value = await new Promise((resolve, reject) => { 28 | let i = 0 29 | let value = 0 30 | callThreadsafeFunction((err, v) => { 31 | if (err != null) { 32 | reject(err) 33 | return 34 | } 35 | i++ 36 | value += v 37 | if (i === 100) { 38 | resolve(value) 39 | } 40 | }) 41 | }) 42 | 43 | assert( 44 | value === 45 | Array.from({ length: 100 }, (_, i) => i + 1).reduce((a, b) => a + b), 46 | ) 47 | console.info(createExternalTypedArray()) 48 | process.exit(0) 49 | } 50 | 51 | main().catch((e) => { 52 | console.error(e) 53 | process.exit(1) 54 | }) 55 | -------------------------------------------------------------------------------- /crates/napi/src/bindgen_runtime/env.rs: -------------------------------------------------------------------------------- 1 | use std::ptr; 2 | 3 | use crate::{check_status, sys, JsGlobal, JsNull, JsUndefined, NapiValue, Result}; 4 | 5 | use super::Array; 6 | 7 | pub use crate::Env; 8 | 9 | impl Env { 10 | pub fn create_array(&self, len: u32) -> Result { 11 | Array::new(self.0, len) 12 | } 13 | 14 | /// Get [JsUndefined](./struct.JsUndefined.html) value 15 | pub fn get_undefined(&self) -> Result { 16 | let mut raw_value = ptr::null_mut(); 17 | check_status!(unsafe { sys::napi_get_undefined(self.0, &mut raw_value) })?; 18 | let js_undefined = unsafe { JsUndefined::from_raw_unchecked(self.0, raw_value) }; 19 | Ok(js_undefined) 20 | } 21 | 22 | pub fn get_null(&self) -> Result { 23 | let mut raw_value = ptr::null_mut(); 24 | check_status!(unsafe { sys::napi_get_null(self.0, &mut raw_value) })?; 25 | let js_null = unsafe { JsNull::from_raw_unchecked(self.0, raw_value) }; 26 | Ok(js_null) 27 | } 28 | 29 | pub fn get_global(&self) -> Result { 30 | let mut global = std::ptr::null_mut(); 31 | crate::check_status!( 32 | unsafe { sys::napi_get_global(self.0, &mut global) }, 33 | "Get global object from Env failed" 34 | )?; 35 | Ok(JsGlobal(crate::Value { 36 | value: global, 37 | env: self.0, 38 | value_type: crate::ValueType::Object, 39 | })) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /cli/src/version.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path' 2 | 3 | import { Command, Option } from 'clipanion' 4 | import * as chalk from 'colorette' 5 | 6 | import { getNapiConfig } from './consts' 7 | import { debugFactory } from './debug' 8 | import { spawn } from './spawn' 9 | import { updatePackageJson } from './update-package' 10 | 11 | const debug = debugFactory('version') 12 | 13 | export class VersionCommand extends Command { 14 | static usage = Command.Usage({ 15 | description: 'Update versions in created npm dir', 16 | }) 17 | 18 | static paths = [['version']] 19 | 20 | static async updatePackageJson(prefix: string, configFileName?: string) { 21 | const { version, platforms } = getNapiConfig(configFileName) 22 | for (const platformDetail of platforms) { 23 | const pkgDir = join(process.cwd(), prefix, platformDetail.platformArchABI) 24 | debug( 25 | `Update version to ${chalk.greenBright( 26 | version, 27 | )} in [${chalk.yellowBright(pkgDir)}]`, 28 | ) 29 | await updatePackageJson(join(pkgDir, 'package.json'), { 30 | version, 31 | }) 32 | } 33 | } 34 | 35 | prefix = Option.String(`-p,--prefix`, 'npm') 36 | 37 | configFileName?: string = Option.String('-c,--config') 38 | 39 | async execute() { 40 | await VersionCommand.updatePackageJson(this.prefix, this.configFileName) 41 | await spawn('git add .') 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /crates/napi/src/bindgen_runtime/js_values/map.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::hash::Hash; 3 | 4 | use crate::bindgen_prelude::{Env, Result, ToNapiValue, *}; 5 | 6 | impl TypeName for HashMap { 7 | fn type_name() -> &'static str { 8 | "HashMap" 9 | } 10 | 11 | fn value_type() -> ValueType { 12 | ValueType::Object 13 | } 14 | } 15 | 16 | impl + Eq + Hash, V: FromNapiValue> ValidateNapiValue for HashMap {} 17 | 18 | impl ToNapiValue for HashMap 19 | where 20 | K: AsRef, 21 | V: ToNapiValue, 22 | { 23 | unsafe fn to_napi_value(raw_env: sys::napi_env, val: Self) -> Result { 24 | let env = Env::from(raw_env); 25 | let mut obj = env.create_object()?; 26 | for (k, v) in val.into_iter() { 27 | obj.set(k.as_ref(), v)?; 28 | } 29 | 30 | unsafe { Object::to_napi_value(raw_env, obj) } 31 | } 32 | } 33 | 34 | impl FromNapiValue for HashMap 35 | where 36 | K: From + Eq + Hash, 37 | V: FromNapiValue, 38 | { 39 | unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { 40 | let obj = unsafe { Object::from_napi_value(env, napi_val)? }; 41 | let mut map = HashMap::new(); 42 | for key in Object::keys(&obj)?.into_iter() { 43 | if let Some(val) = obj.get(&key)? { 44 | map.insert(K::from(key), val); 45 | } 46 | } 47 | 48 | Ok(map) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /.github/workflows/msrv.yml: -------------------------------------------------------------------------------- 1 | name: Test MSRV Rust 2 | 3 | env: 4 | DEBUG: 'napi:*' 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | pull_request: 11 | 12 | jobs: 13 | test-msrv-rust: 14 | name: 1.57.0 - ubuntu-latest - node@18 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Setup node 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: 18 24 | check-latest: true 25 | cache: 'yarn' 26 | 27 | - name: Install 28 | uses: actions-rs/toolchain@v1 29 | with: 30 | toolchain: 1.57.0 31 | profile: minimal 32 | override: true 33 | 34 | - name: Cache cargo 35 | uses: actions/cache@v3 36 | with: 37 | path: | 38 | ~/.cargo/registry 39 | ~/.cargo/git 40 | key: stable-ubuntu-latest-node@16-cargo-cache 41 | 42 | - name: 'Install dependencies' 43 | run: yarn install --mode=skip-build --immutable 44 | 45 | - name: 'Build TypeScript' 46 | run: yarn build 47 | 48 | - name: Check build 49 | uses: actions-rs/cargo@v1 50 | with: 51 | command: check 52 | args: --all --bins --examples --tests -vvv 53 | 54 | - name: Unit tests 55 | run: | 56 | yarn build:test 57 | yarn test --verbose 58 | env: 59 | RUST_BACKTRACE: 1 60 | -------------------------------------------------------------------------------- /crates/napi/src/value_type.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Formatter, Result}; 2 | 3 | use crate::sys; 4 | 5 | #[repr(i32)] 6 | #[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)] 7 | pub enum ValueType { 8 | Undefined = 0, 9 | Null = 1, 10 | Boolean = 2, 11 | Number = 3, 12 | String = 4, 13 | Symbol = 5, 14 | Object = 6, 15 | Function = 7, 16 | External = 8, 17 | #[cfg(feature = "napi6")] 18 | BigInt = 9, 19 | Unknown = 1024, 20 | } 21 | 22 | impl Display for ValueType { 23 | fn fmt(&self, f: &mut Formatter<'_>) -> Result { 24 | let status_string = format!("{:?}", self); 25 | write!(f, "{}", status_string) 26 | } 27 | } 28 | 29 | impl From for ValueType { 30 | fn from(value: i32) -> ValueType { 31 | match value { 32 | #[cfg(feature = "napi6")] 33 | sys::ValueType::napi_bigint => ValueType::BigInt, 34 | sys::ValueType::napi_boolean => ValueType::Boolean, 35 | sys::ValueType::napi_external => ValueType::External, 36 | sys::ValueType::napi_function => ValueType::Function, 37 | sys::ValueType::napi_null => ValueType::Null, 38 | sys::ValueType::napi_number => ValueType::Number, 39 | sys::ValueType::napi_object => ValueType::Object, 40 | sys::ValueType::napi_string => ValueType::String, 41 | sys::ValueType::napi_symbol => ValueType::Symbol, 42 | sys::ValueType::napi_undefined => ValueType::Undefined, 43 | _ => ValueType::Unknown, 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /crates/napi/src/js_values/string/utf16.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | use std::ops::Deref; 3 | 4 | use crate::{Error, JsString, Result, Status}; 5 | 6 | pub struct JsStringUtf16 { 7 | pub(crate) inner: JsString, 8 | pub(crate) buf: Vec, 9 | } 10 | 11 | impl JsStringUtf16 { 12 | pub fn as_str(&self) -> Result { 13 | if let Some((_, prefix)) = self.as_slice().split_last() { 14 | String::from_utf16(prefix).map_err(|e| Error::new(Status::InvalidArg, format!("{}", e))) 15 | } else { 16 | Ok(String::new()) 17 | } 18 | } 19 | 20 | pub fn as_slice(&self) -> &[u16] { 21 | self.buf.as_slice() 22 | } 23 | 24 | pub fn len(&self) -> usize { 25 | self.buf.len() 26 | } 27 | 28 | pub fn is_empty(&self) -> bool { 29 | self.buf.is_empty() 30 | } 31 | 32 | pub fn into_value(self) -> JsString { 33 | self.inner 34 | } 35 | } 36 | 37 | impl TryFrom for String { 38 | type Error = Error; 39 | 40 | fn try_from(value: JsStringUtf16) -> Result { 41 | value.as_str() 42 | } 43 | } 44 | 45 | impl Deref for JsStringUtf16 { 46 | type Target = [u16]; 47 | 48 | fn deref(&self) -> &[u16] { 49 | self.buf.as_slice() 50 | } 51 | } 52 | 53 | impl AsRef> for JsStringUtf16 { 54 | fn as_ref(&self) -> &Vec { 55 | &self.buf 56 | } 57 | } 58 | 59 | impl From for Vec { 60 | fn from(value: JsStringUtf16) -> Self { 61 | value.as_slice().to_vec() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/src/string.rs: -------------------------------------------------------------------------------- 1 | use napi::{CallContext, JsObject, JsString, Result}; 2 | 3 | #[js_function(1)] 4 | fn concat_string(ctx: CallContext) -> Result { 5 | let in_string = ctx.get::(0)?; 6 | let out_string = format!("{} + Rust 🦀 string!", in_string.into_utf8()?.as_str()?); 7 | ctx.env.create_string_from_std(out_string) 8 | } 9 | 10 | #[js_function(1)] 11 | fn concat_utf16_string(ctx: CallContext) -> Result { 12 | let in_string = ctx.get::(0)?; 13 | let out_string = format!("{} + Rust 🦀 string!", in_string.into_utf16()?.as_str()?); 14 | ctx.env.create_string_from_std(out_string) 15 | } 16 | 17 | #[js_function(1)] 18 | fn concat_latin1_string(ctx: CallContext) -> Result { 19 | let in_string = ctx.get::(0)?; 20 | let out_string = format!( 21 | "{} + Rust 🦀 string!", 22 | in_string.into_latin1()?.into_latin1_string()? 23 | ); 24 | ctx.env.create_string_from_std(out_string) 25 | } 26 | 27 | #[js_function] 28 | fn create_latin1(ctx: CallContext) -> Result { 29 | let bytes = vec![169, 191]; 30 | ctx.env.create_string_latin1(bytes.as_slice()) 31 | } 32 | 33 | pub fn register_js(exports: &mut JsObject) -> Result<()> { 34 | exports.create_named_method("concatString", concat_string)?; 35 | exports.create_named_method("concatUTF16String", concat_utf16_string)?; 36 | exports.create_named_method("concatLatin1String", concat_latin1_string)?; 37 | exports.create_named_method("createLatin1", create_latin1)?; 38 | Ok(()) 39 | } 40 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/array.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | const bindings = require('../index.node') 4 | 5 | test('should be able to create array', (t) => { 6 | const arr: number[] = bindings.testCreateArray() 7 | t.true(arr instanceof Array) 8 | t.true(Array.isArray(arr)) 9 | arr.push(1, 2, 3) 10 | t.deepEqual(arr, [1, 2, 3]) 11 | }) 12 | 13 | test('should be able to create array with length', (t) => { 14 | const len = 100 15 | const arr: number[] = bindings.testCreateArrayWithLength(len) 16 | t.true(arr instanceof Array) 17 | t.true(Array.isArray(arr)) 18 | t.is(arr.length, len) 19 | }) 20 | 21 | test('should be able to set element', (t) => { 22 | const obj = {} 23 | const index = 29 24 | const arr: unknown[] = [] 25 | bindings.testSetElement(arr, index, obj) 26 | t.is(arr[index], obj) 27 | }) 28 | 29 | test('should be able to use has_element', (t) => { 30 | const arr: any[] = [1, '3', undefined] 31 | const index = 29 32 | arr[index] = {} 33 | t.true(bindings.testHasElement(arr, 0)) 34 | t.true(bindings.testHasElement(arr, 1)) 35 | t.true(bindings.testHasElement(arr, 2)) 36 | t.false(bindings.testHasElement(arr, 3)) 37 | t.false(bindings.testHasElement(arr, 10)) 38 | t.true(bindings.testHasElement(arr, index)) 39 | }) 40 | 41 | test('should be able to delete element', (t) => { 42 | const arr: number[] = [0, 1, 2, 3] 43 | for (const [index] of arr.entries()) { 44 | t.true(bindings.testDeleteElement(arr, index)) 45 | t.true(arr[index] === undefined) 46 | } 47 | }) 48 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/src/napi4/tsfn_dua_instance.rs: -------------------------------------------------------------------------------- 1 | use napi::{ 2 | threadsafe_function::{ThreadSafeCallContext, ThreadsafeFunction}, 3 | CallContext, JsFunction, JsObject, JsUndefined, 4 | }; 5 | use napi_derive::js_function; 6 | 7 | #[derive(Clone)] 8 | pub struct A { 9 | pub cb: ThreadsafeFunction, 10 | } 11 | 12 | #[js_function(1)] 13 | pub fn constructor(ctx: CallContext) -> napi::Result { 14 | let callback = ctx.get::(0)?; 15 | 16 | let cb = 17 | ctx 18 | .env 19 | .create_threadsafe_function(&callback, 0, |ctx: ThreadSafeCallContext| { 20 | ctx 21 | .env 22 | .create_string_from_std(ctx.value) 23 | .map(|js_string| vec![js_string]) 24 | })?; 25 | 26 | let mut this: JsObject = ctx.this_unchecked(); 27 | let obj = A { cb }; 28 | 29 | ctx.env.wrap(&mut this, obj)?; 30 | ctx.env.get_undefined() 31 | } 32 | 33 | #[js_function] 34 | pub fn call(ctx: CallContext) -> napi::Result { 35 | let this = ctx.this_unchecked(); 36 | let obj = ctx.env.unwrap::(&this)?; 37 | obj.cb.call( 38 | Ok("ThreadsafeFunction NonBlocking Call".to_owned()), 39 | napi::threadsafe_function::ThreadsafeFunctionCallMode::NonBlocking, 40 | ); 41 | ctx.env.get_undefined() 42 | } 43 | 44 | #[js_function] 45 | pub fn unref(ctx: CallContext) -> napi::Result { 46 | let this = ctx.this_unchecked(); 47 | let obj = ctx.env.unwrap::(&this)?; 48 | obj.cb.unref(ctx.env)?; 49 | ctx.env.get_undefined() 50 | } 51 | -------------------------------------------------------------------------------- /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android-aarch64 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | env: 10 | DEBUG: 'napi:*' 11 | 12 | jobs: 13 | build-android-aarch64: 14 | name: Build - Android - aarch64 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Setup node 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: 18 23 | cache: 'yarn' 24 | check-latest: true 25 | 26 | - name: List NDK Home 27 | run: ls -R "${ANDROID_NDK_LATEST_HOME}" 28 | 29 | - name: Install 30 | uses: actions-rs/toolchain@v1 31 | with: 32 | toolchain: stable 33 | profile: minimal 34 | override: true 35 | target: 'aarch64-linux-android' 36 | 37 | - name: Cache cargo 38 | uses: actions/cache@v3 39 | with: 40 | path: | 41 | ~/.cargo/registry 42 | ~/.cargo/git 43 | target 44 | key: stable-linux-android-node@16-cargo-cache 45 | 46 | - name: Install dependencies 47 | run: yarn install --immutable --mode=skip-build 48 | 49 | - name: 'Build TypeScript' 50 | run: yarn build 51 | 52 | - name: Cross build native tests 53 | run: | 54 | export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang" 55 | yarn build:test:android 56 | -------------------------------------------------------------------------------- /examples/napi/__test__/generator.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | import { Fib, Fib2, Fib3 } from '../index' 4 | 5 | for (const [index, factory] of [ 6 | () => new Fib(), 7 | () => Fib2.create(0), 8 | () => new Fib3(0, 1), 9 | ].entries()) { 10 | test(`should be able to stop a generator #${index}`, (t) => { 11 | const fib = factory() 12 | const gen = fib[Symbol.iterator] 13 | t.is(typeof gen, 'function') 14 | const iterator = gen() 15 | t.deepEqual(iterator.next(), { 16 | done: false, 17 | value: 1, 18 | }) 19 | iterator.next() 20 | iterator.next() 21 | iterator.next() 22 | iterator.next() 23 | t.deepEqual(iterator.next(), { 24 | done: false, 25 | value: 8, 26 | }) 27 | t.deepEqual(iterator.return?.(), { 28 | done: true, 29 | }) 30 | t.deepEqual(iterator.next(), { 31 | done: true, 32 | }) 33 | }) 34 | 35 | test(`should be able to throw to generator #${index}`, (t) => { 36 | const fib = factory() 37 | const gen = fib[Symbol.iterator] 38 | t.is(typeof gen, 'function') 39 | const iterator = gen() 40 | t.deepEqual(iterator.next(), { 41 | done: false, 42 | value: 1, 43 | }) 44 | iterator.next() 45 | iterator.next() 46 | iterator.next() 47 | iterator.next() 48 | t.deepEqual(iterator.next(), { 49 | done: false, 50 | value: 8, 51 | }) 52 | t.throws(() => iterator.throw!(new Error())) 53 | t.deepEqual(iterator.next(), { 54 | done: true, 55 | }) 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/napi4/tokio_rt.spec.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs' 2 | import { join } from 'path' 3 | 4 | import test from 'ava' 5 | 6 | import { napiVersion } from '../napi-version' 7 | 8 | const bindings = require('../../index.node') 9 | 10 | const filepath = join(__dirname, './example.txt') 11 | 12 | test.serial('should execute future on tokio runtime', async (t) => { 13 | if (napiVersion < 4) { 14 | t.is(bindings.testExecuteTokioReadfile, undefined) 15 | return 16 | } 17 | const fileContent = await bindings.testExecuteTokioReadfile(filepath) 18 | t.true(Buffer.isBuffer(fileContent)) 19 | t.deepEqual(readFileSync(filepath), fileContent) 20 | }) 21 | 22 | test.serial('should reject error from tokio future', async (t) => { 23 | if (napiVersion < 4) { 24 | t.is(bindings.testTokioError, undefined) 25 | return 26 | } 27 | try { 28 | await bindings.testTokioError(filepath) 29 | throw new TypeError('Unreachable') 30 | } catch (e) { 31 | t.is((e as Error).message, 'Error from tokio future') 32 | } 33 | }) 34 | 35 | test.serial('should be able to execute future paralleled', async (t) => { 36 | if (napiVersion < 4) { 37 | t.is(bindings.testExecuteTokioReadfile, undefined) 38 | return 39 | } 40 | const buffers = await Promise.all( 41 | Array.from({ length: 50 }).map((_) => 42 | bindings.testExecuteTokioReadfile(filepath), 43 | ), 44 | ) 45 | for (const fileContent of buffers) { 46 | t.true(Buffer.isBuffer(fileContent)) 47 | t.deepEqual(readFileSync(filepath), fileContent) 48 | } 49 | }) 50 | -------------------------------------------------------------------------------- /.github/workflows/linux-aarch64-musl.yaml: -------------------------------------------------------------------------------- 1 | name: Linux-aarch64-musl 2 | 3 | env: 4 | DEBUG: 'napi:*' 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | pull_request: 11 | 12 | jobs: 13 | build: 14 | name: stable - aarch64-unknown-linux-gnu - node@18 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - run: docker run --rm --privileged multiarch/qemu-user-static:register --reset 19 | 20 | - uses: actions/checkout@v3 21 | 22 | - name: Setup node 23 | uses: actions/setup-node@v3 24 | with: 25 | node-version: 18 26 | check-latest: true 27 | cache: 'yarn' 28 | 29 | - name: Install dependencies 30 | run: yarn install --immutable --mode=skip-build 31 | 32 | - name: 'Build TypeScript' 33 | run: yarn build 34 | 35 | - name: Cross build native tests 36 | uses: addnab/docker-run-action@v3 37 | with: 38 | image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine 39 | options: -v ${{ github.workspace }}:/napi-rs -w /napi-rs 40 | run: | 41 | export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-musl-gcc 42 | yarn workspace compat-mode-examples build --target aarch64-unknown-linux-musl 43 | yarn workspace examples build --target aarch64-unknown-linux-musl 44 | 45 | - name: Setup and run tests 46 | uses: docker://multiarch/alpine:aarch64-latest-stable 47 | with: 48 | args: > 49 | sh -c " 50 | apk add nodejs yarn && \ 51 | yarn test 52 | " 53 | -------------------------------------------------------------------------------- /cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@napi-rs/cli", 3 | "version": "2.14.8", 4 | "description": "Cli tools for napi-rs", 5 | "keywords": [ 6 | "cli", 7 | "rust", 8 | "napi", 9 | "n-api", 10 | "neon" 11 | ], 12 | "author": "LongYinan ", 13 | "homepage": "https://github.com/napi-rs/napi-rs", 14 | "license": "MIT", 15 | "bin": { 16 | "napi": "./scripts/index.js" 17 | }, 18 | "files": [ 19 | "scripts" 20 | ], 21 | "engines": { 22 | "node": ">= 10" 23 | }, 24 | "maintainers": [ 25 | { 26 | "name": "LongYinan", 27 | "email": "lynweklm@gmail.com", 28 | "homepage": "https://github.com/Brooooooklyn" 29 | } 30 | ], 31 | "repository": { 32 | "type": "git", 33 | "url": "git+https://github.com/napi-rs/napi-rs.git" 34 | }, 35 | "publishConfig": { 36 | "registry": "https://registry.npmjs.org/", 37 | "access": "public" 38 | }, 39 | "bugs": { 40 | "url": "https://github.com/napi-rs/napi-rs/issues" 41 | }, 42 | "devDependencies": { 43 | "@octokit/rest": "^19.0.5", 44 | "@types/inquirer": "^9.0.3", 45 | "@types/js-yaml": "^4.0.5", 46 | "@types/lodash-es": "^4.17.6", 47 | "clipanion": "^3.1.0", 48 | "colorette": "^2.0.19", 49 | "core-js": "^3.27.1", 50 | "debug": "^4.3.4", 51 | "env-paths": "^3.0.0", 52 | "fdir": "^5.3.0", 53 | "inquirer": "^9.1.4", 54 | "js-yaml": "^4.1.0", 55 | "lodash-es": "4.17.21", 56 | "toml": "^3.0.0", 57 | "tslib": "^2.4.1", 58 | "typanion": "^3.12.1" 59 | }, 60 | "funding": { 61 | "type": "github", 62 | "url": "https://github.com/sponsors/Brooooooklyn" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /examples/napi/src/typed_array.rs: -------------------------------------------------------------------------------- 1 | use napi::bindgen_prelude::*; 2 | 3 | #[napi] 4 | fn get_buffer() -> Buffer { 5 | String::from("Hello world").as_bytes().into() 6 | } 7 | 8 | #[napi] 9 | fn append_buffer(buf: Buffer) -> Buffer { 10 | let mut buf = Vec::::from(buf); 11 | buf.push(b'!'); 12 | buf.into() 13 | } 14 | 15 | #[napi] 16 | fn get_empty_buffer() -> Buffer { 17 | vec![].into() 18 | } 19 | 20 | #[napi] 21 | fn convert_u32_array(input: Uint32Array) -> Vec { 22 | input.to_vec() 23 | } 24 | 25 | #[napi] 26 | fn create_external_typed_array() -> Uint32Array { 27 | Uint32Array::new(vec![1, 2, 3, 4, 5]) 28 | } 29 | 30 | #[napi] 31 | fn mutate_typed_array(mut input: Float32Array) { 32 | for item in input.as_mut() { 33 | *item *= 2.0; 34 | } 35 | } 36 | 37 | #[napi] 38 | fn deref_uint8_array(a: Uint8Array, b: Uint8ClampedArray) -> u32 { 39 | (a.len() + b.len()) as u32 40 | } 41 | 42 | #[napi] 43 | async fn buffer_pass_through(buf: Buffer) -> Result { 44 | Ok(buf) 45 | } 46 | 47 | #[napi] 48 | async fn array_buffer_pass_through(buf: Uint8Array) -> Result { 49 | Ok(buf) 50 | } 51 | 52 | struct AsyncBuffer { 53 | buf: Buffer, 54 | } 55 | 56 | #[napi] 57 | impl Task for AsyncBuffer { 58 | type Output = u32; 59 | type JsValue = u32; 60 | 61 | fn compute(&mut self) -> Result { 62 | Ok(self.buf.iter().fold(0u32, |a, b| a + *b as u32)) 63 | } 64 | 65 | fn resolve(&mut self, _env: Env, output: Self::Output) -> Result { 66 | Ok(output) 67 | } 68 | } 69 | 70 | #[napi] 71 | fn async_reduce_buffer(buf: Buffer) -> Result> { 72 | Ok(AsyncTask::new(AsyncBuffer { buf })) 73 | } 74 | -------------------------------------------------------------------------------- /.cirrus.yml: -------------------------------------------------------------------------------- 1 | env: 2 | DEBUG: 'napi:*' 3 | CI: '1' 4 | 5 | build_and_test: &BUILD_AND_TEST 6 | registry_cache: 7 | folder: $HOME/.cargo/registry 8 | fingerprint_script: 9 | - echo $CIRRUS_OS 10 | - cat crates/*/Cargo.toml 11 | target_cache: 12 | folder: target 13 | fingerprint_script: 14 | - echo $CIRRUS_OS 15 | - cat crates/*/Cargo.toml 16 | install_script: 17 | - curl -qL https://www.npmjs.com/install.sh | sh 18 | - npm install --location=global --ignore-scripts yarn 19 | - curl https://sh.rustup.rs -sSf --output rustup.sh 20 | - sh rustup.sh -y --profile minimal --default-toolchain beta 21 | - | 22 | echo "~~~~ rustc --version ~~~~" 23 | rustc --version 24 | echo "~~~~ node -v ~~~~" 25 | node -v 26 | echo "~~~~ yarn --version ~~~~" 27 | yarn --version 28 | test_script: 29 | - yarn install --immutable --mode=skip-build 30 | - yarn build 31 | - cargo test -p napi-sys --lib -- --nocapture 32 | - yarn build:test 33 | - yarn test --verbose 34 | 35 | freebsd_task: 36 | name: FreeBSD 37 | freebsd_instance: 38 | image: freebsd-13-1-release-amd64 39 | env: 40 | RUSTUP_HOME: /usr/local/rustup 41 | CARGO_HOME: /usr/local/cargo 42 | PATH: /usr/local/cargo/bin:$PATH 43 | RUSTUP_IO_THREADS: '1' 44 | setup_script: 45 | - pkg update 46 | - pkg install -y -f curl node libnghttp2 47 | <<: *BUILD_AND_TEST 48 | 49 | macos_arm64_task: 50 | name: macOS arm64 51 | macos_instance: 52 | image: ghcr.io/cirruslabs/macos-monterey-xcode 53 | env: 54 | PATH: $HOME/.cargo/bin:$PATH 55 | setup_script: 56 | - brew install node 57 | <<: *BUILD_AND_TEST 58 | -------------------------------------------------------------------------------- /crates/backend/src/codegen/const.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Ident, Literal, TokenStream}; 2 | use quote::ToTokens; 3 | 4 | use crate::{ 5 | codegen::{get_register_ident, js_mod_to_token_stream}, 6 | BindgenResult, NapiConst, TryToTokens, 7 | }; 8 | 9 | impl TryToTokens for NapiConst { 10 | fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()> { 11 | let register = self.gen_module_register(); 12 | (quote! { 13 | #register 14 | }) 15 | .to_tokens(tokens); 16 | 17 | Ok(()) 18 | } 19 | } 20 | 21 | impl NapiConst { 22 | fn gen_module_register(&self) -> TokenStream { 23 | let name_str = self.name.to_string(); 24 | let name_ident = self.name.clone(); 25 | let js_name_lit = Literal::string(&format!("{}\0", self.name)); 26 | let register_name = get_register_ident(&name_str); 27 | let type_name = &self.type_name; 28 | let cb_name = Ident::new( 29 | &format!("__register__const__{}_callback__", register_name), 30 | self.name.span(), 31 | ); 32 | let js_mod_ident = js_mod_to_token_stream(self.js_mod.as_ref()); 33 | quote! { 34 | #[allow(non_snake_case)] 35 | #[allow(clippy::all)] 36 | unsafe fn #cb_name(env: napi::sys::napi_env) -> napi::Result { 37 | <#type_name as napi::bindgen_prelude::ToNapiValue>::to_napi_value(env, #name_ident) 38 | } 39 | #[allow(non_snake_case)] 40 | #[allow(clippy::all)] 41 | #[cfg(all(not(test), not(feature = "noop")))] 42 | #[napi::bindgen_prelude::ctor] 43 | fn #register_name() { 44 | napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name_lit, #cb_name); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /cli/src/new/package.ts: -------------------------------------------------------------------------------- 1 | import { version } from '../../package.json' 2 | import { DefaultPlatforms } from '../parse-triple' 3 | 4 | export const createPackageJson = ( 5 | name: string, 6 | binaryName: string, 7 | targets: string[], 8 | ) => { 9 | const pkgContent = { 10 | name, 11 | version: '0.0.0', 12 | main: 'index.js', 13 | types: 'index.d.ts', 14 | napi: { 15 | name: binaryName, 16 | }, 17 | license: 'MIT', 18 | devDependencies: { 19 | '@napi-rs/cli': `^${version}`, 20 | ava: '^5.1.1', 21 | }, 22 | ava: { 23 | timeout: '3m', 24 | }, 25 | engines: { 26 | node: '>= 10', 27 | }, 28 | scripts: { 29 | artifacts: 'napi artifacts', 30 | build: 'napi build --platform --release', 31 | 'build:debug': 'napi build --platform', 32 | prepublishOnly: 'napi prepublish -t npm', 33 | test: 'ava', 34 | universal: 'napi universal', 35 | version: 'napi version', 36 | }, 37 | } 38 | 39 | const triples: any = {} 40 | 41 | const defaultTargetsSupported = DefaultPlatforms.every((p) => 42 | targets!.includes(p.raw), 43 | ) 44 | 45 | const isOnlyDefaultTargets = 46 | targets.length === 3 && 47 | DefaultPlatforms.every((p) => targets.includes(p.raw)) 48 | 49 | if (!isOnlyDefaultTargets) { 50 | if (!defaultTargetsSupported) { 51 | triples.defaults = false 52 | triples.additional = targets 53 | } else { 54 | triples.additional = targets.filter( 55 | (t) => !DefaultPlatforms.map((p) => p.raw).includes(t), 56 | ) 57 | } 58 | } 59 | 60 | // @ts-expect-error 61 | pkgContent.napi.triples = triples 62 | 63 | return pkgContent 64 | } 65 | -------------------------------------------------------------------------------- /.github/workflows/windows-arm.yml: -------------------------------------------------------------------------------- 1 | name: Windows arm64 2 | 3 | env: 4 | DEBUG: 'napi:*' 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | pull_request: 11 | 12 | jobs: 13 | build_and_test: 14 | name: stable - windows-latest - arm64 - node@18 15 | runs-on: windows-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Setup node 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: 18 24 | check-latest: true 25 | cache: 'yarn' 26 | 27 | - name: 'Install dependencies' 28 | run: yarn install --mode=skip-build --immutable 29 | 30 | - name: 'Build TypeScript' 31 | run: yarn build 32 | 33 | - name: Install 34 | uses: actions-rs/toolchain@v1 35 | with: 36 | toolchain: stable 37 | profile: minimal 38 | override: true 39 | target: aarch64-pc-windows-msvc 40 | 41 | - name: Cache cargo 42 | uses: actions/cache@v3 43 | with: 44 | path: | 45 | ~/.cargo/registry 46 | ~/.cargo/git 47 | target 48 | key: stable-windows-arm64-node@16-cargo-cache 49 | 50 | - name: Check build 51 | uses: actions-rs/cargo@v1 52 | with: 53 | command: check 54 | args: --all --bins --examples --tests --target aarch64-pc-windows-msvc -vvv 55 | 56 | - name: Build release target 57 | run: cargo build --release --target aarch64-pc-windows-msvc 58 | 59 | - name: Clear the cargo caches 60 | run: | 61 | cargo install cargo-cache --no-default-features --features ci-autoclean 62 | cargo-cache 63 | -------------------------------------------------------------------------------- /.github/workflows/cli-binary.yml: -------------------------------------------------------------------------------- 1 | name: Cli Build Binary 2 | 3 | env: 4 | DEBUG: 'napi:*' 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | pull_request: 11 | 12 | jobs: 13 | build_binary_crate: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Setup node 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: 18 23 | check-latest: true 24 | cache: 'yarn' 25 | 26 | - name: Install 27 | uses: actions-rs/toolchain@v1 28 | with: 29 | toolchain: stable 30 | profile: minimal 31 | override: true 32 | 33 | - name: Cache cargo 34 | uses: actions/cache@v3 35 | with: 36 | path: | 37 | ~/.cargo/registry 38 | ~/.cargo/git 39 | target 40 | key: stable-cargo-cache-build-binary 41 | 42 | - name: 'Install dependencies' 43 | run: yarn install --mode=skip-build --immutable 44 | 45 | - name: 'Build TypeScript' 46 | run: yarn build 47 | 48 | - name: Build and run binary 49 | run: | 50 | yarn workspace binary build 51 | ./examples/binary/napi-examples-binary 52 | env: 53 | RUST_BACKTRACE: 1 54 | 55 | - name: Pass -p and --cargo-name to build 56 | run: | 57 | node ./cli/scripts/index.js build -p napi-examples-binary --cargo-name napi-examples-binary 58 | ./napi-examples-binary 59 | env: 60 | RUST_BACKTRACE: 1 61 | 62 | - name: Clear the cargo caches 63 | run: | 64 | cargo install cargo-cache --no-default-features --features ci-autoclean 65 | cargo-cache 66 | -------------------------------------------------------------------------------- /.github/workflows/android-armv7.yml: -------------------------------------------------------------------------------- 1 | name: Android-armv7 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | env: 10 | DEBUG: 'napi:*' 11 | 12 | jobs: 13 | build-android-armv7: 14 | name: Build - Android - armv7 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Setup node 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: 18 23 | cache: 'yarn' 24 | check-latest: true 25 | 26 | - name: List NDK Home 27 | run: ls -R "${ANDROID_NDK_LATEST_HOME}" 28 | 29 | - name: Install 30 | uses: actions-rs/toolchain@v1 31 | with: 32 | toolchain: stable 33 | profile: minimal 34 | override: true 35 | target: 'armv7-linux-androideabi' 36 | 37 | - name: Cache cargo 38 | uses: actions/cache@v3 39 | with: 40 | path: | 41 | ~/.cargo/registry 42 | ~/.cargo/git 43 | target 44 | key: stable-linux-android-armv7-node@18-cargo-cache 45 | 46 | - name: Install dependencies 47 | run: yarn install --immutable --mode=skip-build 48 | 49 | - name: 'Build TypeScript' 50 | run: yarn build 51 | 52 | - name: Cross build 53 | run: | 54 | export CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi24-clang" 55 | yarn build:test:android:armv7 56 | du -sh examples/napi/index.node 57 | ${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip examples/napi/index.node 58 | du -sh examples/napi/index.node 59 | -------------------------------------------------------------------------------- /.github/workflows/linux-aarch64.yaml: -------------------------------------------------------------------------------- 1 | name: Linux-aarch64 2 | 3 | env: 4 | DEBUG: 'napi:*' 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | pull_request: 11 | 12 | jobs: 13 | build: 14 | name: stable - aarch64-unknown-linux-gnu - node@18 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - run: docker run --rm --privileged multiarch/qemu-user-static:register --reset 19 | 20 | - uses: actions/checkout@v3 21 | 22 | - name: Setup node 23 | uses: actions/setup-node@v3 24 | with: 25 | node-version: 18 26 | check-latest: true 27 | cache: 'yarn' 28 | 29 | - name: Install 30 | uses: actions-rs/toolchain@v1 31 | with: 32 | toolchain: stable 33 | profile: minimal 34 | override: true 35 | target: aarch64-unknown-linux-gnu 36 | 37 | - name: Cache cargo 38 | uses: actions/cache@v3 39 | with: 40 | path: | 41 | ~/.cargo/registry 42 | ~/.cargo/git 43 | key: stable-linux-aarch64-gnu-node@18-cargo-cache 44 | 45 | - name: Install ziglang 46 | uses: goto-bus-stop/setup-zig@v2 47 | with: 48 | version: 0.10.0 49 | 50 | - name: Install dependencies 51 | run: yarn install --immutable --mode=skip-build 52 | 53 | - name: 'Build TypeScript' 54 | run: yarn build 55 | 56 | - name: Cross build native tests 57 | run: yarn build:test:aarch64 58 | 59 | - name: Setup and run tests 60 | uses: addnab/docker-run-action@v3 61 | with: 62 | image: ghcr.io/napi-rs/napi-rs/nodejs:aarch64-16 63 | options: -v ${{ github.workspace }}:/build -w /build 64 | run: yarn test 65 | -------------------------------------------------------------------------------- /debian-aarch64.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM messense/manylinux2014-cross:aarch64 2 | 3 | ENV RUSTUP_HOME=/usr/local/rustup \ 4 | CARGO_HOME=/usr/local/cargo \ 5 | PATH=/usr/local/cargo/bin:$PATH \ 6 | CC=clang \ 7 | CXX=clang++ \ 8 | CC_aarch64_unknown_linux_gnu="clang --sysroot=/usr/aarch64-unknown-linux-gnu/aarch64-unknown-linux-gnu/sysroot" \ 9 | CXX_aarch64_unknown_linux_gnu="clang++ --sysroot=/usr/aarch64-unknown-linux-gnu/aarch64-unknown-linux-gnu/sysroot" \ 10 | C_INCLUDE_PATH=/usr/aarch64-unknown-linux-gnu/aarch64-unknown-linux-gnu/sysroot/usr/include 11 | 12 | RUN apt-get update && \ 13 | apt-get install -y --fix-missing --no-install-recommends gpg-agent ca-certificates openssl && \ 14 | wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - && \ 15 | echo "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-15 main" >> /etc/apt/sources.list && \ 16 | echo "deb-src http://apt.llvm.org/jammy/ llvm-toolchain-jammy-15 main" >> /etc/apt/sources.list && \ 17 | curl -sL https://deb.nodesource.com/setup_16.x | bash - && \ 18 | apt-get install -y --fix-missing --no-install-recommends \ 19 | curl \ 20 | llvm-15 \ 21 | clang-15 \ 22 | lld-15 \ 23 | libc++-15-dev \ 24 | libc++abi-15-dev \ 25 | nodejs \ 26 | xz-utils \ 27 | rcs \ 28 | git \ 29 | make \ 30 | cmake \ 31 | ninja-build && \ 32 | apt-get autoremove -y && \ 33 | curl https://sh.rustup.rs -sSf | sh -s -- -y && \ 34 | rustup target add aarch64-unknown-linux-gnu && \ 35 | npm install -g yarn pnpm lerna && \ 36 | npm cache clean --force && \ 37 | npm cache verify && \ 38 | ln -sf /usr/bin/clang-15 /usr/bin/clang && \ 39 | ln -sf /usr/bin/clang++-15 /usr/bin/clang++ && \ 40 | ln -sf /usr/bin/lld-15 /usr/bin/lld && \ 41 | ln -sf /usr/bin/clang-15 /usr/bin/cc 42 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/buffer.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | const bindings = require('../index.node') 4 | 5 | test('should get buffer length', (t) => { 6 | const fixture = Buffer.from('wow, hello') 7 | t.is(bindings.getBufferLength(fixture), fixture.length) 8 | }) 9 | 10 | test('should stringify buffer', (t) => { 11 | const fixture = 'wow, hello' 12 | t.is(bindings.bufferToString(Buffer.from(fixture)), fixture) 13 | }) 14 | 15 | test('should copy', (t) => { 16 | const fixture = Buffer.from('wow, hello') 17 | const copyBuffer = bindings.copyBuffer(fixture) 18 | t.deepEqual(copyBuffer, fixture) 19 | t.not(fixture, copyBuffer) 20 | }) 21 | 22 | test('should create borrowed buffer with noop finalize', (t) => { 23 | t.deepEqual( 24 | bindings.createBorrowedBufferWithNoopFinalize(), 25 | Buffer.from([1, 2, 3]), 26 | ) 27 | }) 28 | 29 | test('should create borrowed buffer with finalize', (t) => { 30 | t.deepEqual( 31 | bindings.createBorrowedBufferWithFinalize(), 32 | Buffer.from([1, 2, 3]), 33 | ) 34 | }) 35 | 36 | test('should create empty borrowed buffer with finalize', (t) => { 37 | t.throws(() => bindings.createEmptyBorrowedBufferWithFinalize().toString(), { 38 | message: 'Borrowed data should not be null', 39 | }) 40 | t.throws(() => bindings.createEmptyBorrowedBufferWithFinalize().toString(), { 41 | message: 'Borrowed data should not be null', 42 | }) 43 | }) 44 | 45 | test('should create empty buffer', (t) => { 46 | t.is(bindings.createEmptyBuffer().toString(), '') 47 | t.is(bindings.createEmptyBuffer().toString(), '') 48 | }) 49 | 50 | test('should be able to mutate buffer', (t) => { 51 | const fixture = Buffer.from([0, 1]) 52 | bindings.mutateBuffer(fixture) 53 | t.is(fixture[1], 42) 54 | }) 55 | -------------------------------------------------------------------------------- /.github/workflows/linux-armv7.yaml: -------------------------------------------------------------------------------- 1 | name: Linux-armv7 2 | 3 | env: 4 | DEBUG: 'napi:*' 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | pull_request: 11 | 12 | jobs: 13 | build: 14 | name: stable - armv7-unknown-linux-gnu - node@18 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - run: docker run --rm --privileged multiarch/qemu-user-static:register --reset 19 | 20 | - uses: actions/checkout@v3 21 | 22 | - name: Setup node 23 | uses: actions/setup-node@v3 24 | with: 25 | node-version: 18 26 | check-latest: true 27 | cache: 'yarn' 28 | 29 | - name: Install 30 | uses: actions-rs/toolchain@v1 31 | with: 32 | toolchain: stable 33 | profile: minimal 34 | override: true 35 | target: armv7-unknown-linux-gnueabihf 36 | 37 | - name: Install ziglang 38 | uses: goto-bus-stop/setup-zig@v2 39 | with: 40 | version: 0.10.1 41 | 42 | - name: Cache cargo 43 | uses: actions/cache@v3 44 | with: 45 | path: | 46 | ~/.cargo/registry 47 | ~/.cargo/git 48 | target 49 | key: stable-linux-armv7-gnu-node@18-cargo-cache 50 | 51 | - name: Install dependencies 52 | run: yarn install --immutable --mode=skip-build 53 | 54 | - name: 'Build TypeScript' 55 | run: yarn build 56 | 57 | - name: Cross build native tests 58 | run: yarn build:test:armv7 59 | 60 | - name: Setup and run tests 61 | uses: addnab/docker-run-action@v3 62 | with: 63 | image: ghcr.io/napi-rs/napi-rs/nodejs:armhf-16 64 | options: -v ${{ github.workspace }}:/build -w /build 65 | run: yarn test 66 | -------------------------------------------------------------------------------- /crates/napi/src/bindgen_runtime/js_values/symbol.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::CString, ptr}; 2 | 3 | use crate::{check_status, sys}; 4 | 5 | use super::{FromNapiValue, ToNapiValue, TypeName, ValidateNapiValue}; 6 | 7 | pub struct Symbol { 8 | desc: Option, 9 | } 10 | 11 | impl TypeName for Symbol { 12 | fn type_name() -> &'static str { 13 | "Symbol" 14 | } 15 | 16 | fn value_type() -> crate::ValueType { 17 | crate::ValueType::Symbol 18 | } 19 | } 20 | 21 | impl ValidateNapiValue for Symbol {} 22 | 23 | impl Symbol { 24 | pub fn new(desc: String) -> Self { 25 | Self { desc: Some(desc) } 26 | } 27 | 28 | pub fn identity() -> Self { 29 | Self { desc: None } 30 | } 31 | } 32 | 33 | impl ToNapiValue for Symbol { 34 | unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> crate::Result { 35 | let mut symbol_value = ptr::null_mut(); 36 | check_status!(unsafe { 37 | sys::napi_create_symbol( 38 | env, 39 | match val.desc { 40 | Some(desc) => { 41 | let mut desc_string = ptr::null_mut(); 42 | let desc_len = desc.len(); 43 | let desc_c_string = CString::new(desc)?; 44 | check_status!(sys::napi_create_string_utf8( 45 | env, 46 | desc_c_string.as_ptr(), 47 | desc_len, 48 | &mut desc_string 49 | ))?; 50 | desc_string 51 | } 52 | None => ptr::null_mut(), 53 | }, 54 | &mut symbol_value, 55 | ) 56 | })?; 57 | Ok(symbol_value) 58 | } 59 | } 60 | 61 | impl FromNapiValue for Symbol { 62 | unsafe fn from_napi_value( 63 | _env: sys::napi_env, 64 | _napi_val: sys::napi_value, 65 | ) -> crate::Result { 66 | Ok(Self { desc: None }) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /examples/napi/src/js_mod.rs: -------------------------------------------------------------------------------- 1 | #[napi] 2 | mod xxh3 { 3 | use napi::bindgen_prelude::{BigInt, Buffer}; 4 | 5 | #[napi] 6 | pub const ALIGNMENT: u32 = 16; 7 | 8 | #[napi(js_name = "xxh3_64")] 9 | pub fn xxh64(input: Buffer) -> u64 { 10 | let mut h: u64 = 0; 11 | for i in input.as_ref() { 12 | h = h.wrapping_add(*i as u64); 13 | } 14 | h 15 | } 16 | 17 | #[napi] 18 | /// xxh128 function 19 | pub fn xxh128(input: Buffer) -> u128 { 20 | let mut h: u128 = 0; 21 | for i in input.as_ref() { 22 | h = h.wrapping_add(*i as u128); 23 | } 24 | h 25 | } 26 | 27 | #[napi] 28 | /// Xxh3 class 29 | pub struct Xxh3 { 30 | inner: BigInt, 31 | } 32 | 33 | #[napi] 34 | impl Xxh3 { 35 | #[napi(constructor)] 36 | #[allow(clippy::new_without_default)] 37 | pub fn new() -> Xxh3 { 38 | Xxh3 { 39 | inner: BigInt { 40 | sign_bit: false, 41 | words: vec![0], 42 | }, 43 | } 44 | } 45 | 46 | #[napi] 47 | /// update 48 | pub fn update(&mut self, input: Buffer) { 49 | for i in input.as_ref() { 50 | self.inner = BigInt { 51 | sign_bit: false, 52 | words: vec![self.inner.get_u64().1.wrapping_add(*i as u64)], 53 | }; 54 | } 55 | } 56 | #[napi] 57 | pub fn digest(&self) -> BigInt { 58 | self.inner.clone() 59 | } 60 | } 61 | } 62 | 63 | #[napi] 64 | mod xxh2 { 65 | use napi::bindgen_prelude::*; 66 | 67 | #[napi] 68 | pub fn xxh2_plus(a: u32, b: u32) -> u32 { 69 | a + b 70 | } 71 | 72 | #[napi] 73 | pub fn xxh3_xxh64_alias(input: Buffer) -> u64 { 74 | super::xxh3::xxh64(input) 75 | } 76 | } 77 | 78 | use napi::bindgen_prelude::Buffer; 79 | 80 | #[napi] 81 | pub fn xxh64_alias(input: Buffer) -> u64 { 82 | xxh3::xxh64(input) 83 | } 84 | -------------------------------------------------------------------------------- /examples/napi/__test__/error-msg.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | import { receiveString } from '../index' 4 | 5 | test('Function message', (t) => { 6 | // @ts-expect-error 7 | t.throws(() => receiveString(function a() {}), { 8 | message: 9 | 'Failed to convert JavaScript value `function a(..) ` into rust type `String`', 10 | }) 11 | // @ts-expect-error 12 | t.throws(() => receiveString(() => {}), { 13 | message: 14 | 'Failed to convert JavaScript value `function anonymous(..) ` into rust type `String`', 15 | }) 16 | // @ts-expect-error 17 | t.throws(() => receiveString(1), { 18 | message: 19 | 'Failed to convert JavaScript value `Number 1 ` into rust type `String`', 20 | }) 21 | t.throws( 22 | () => 23 | // @ts-expect-error 24 | receiveString({ 25 | a: 1, 26 | b: { 27 | foo: 'bar', 28 | s: false, 29 | }, 30 | }), 31 | { 32 | message: 33 | 'Failed to convert JavaScript value `Object {"a":1,"b":{"foo":"bar","s":false}}` into rust type `String`', 34 | }, 35 | ) 36 | // @ts-expect-error 37 | t.throws(() => receiveString(Symbol('1')), { 38 | message: 39 | 'Failed to convert JavaScript value `Symbol` into rust type `String`', 40 | }) 41 | 42 | // @ts-expect-error 43 | t.throws(() => receiveString(), { 44 | message: 45 | 'Failed to convert JavaScript value `Undefined` into rust type `String`', 46 | }) 47 | 48 | // @ts-expect-error 49 | t.throws(() => receiveString(null), { 50 | message: 51 | 'Failed to convert JavaScript value `Null` into rust type `String`', 52 | }) 53 | 54 | // @ts-expect-error 55 | t.throws(() => receiveString(100n), { 56 | message: 57 | 'Failed to convert JavaScript value `BigInt 100 ` into rust type `String`', 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /debian.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM messense/manylinux2014-cross:x86_64 2 | 3 | ARG NASM_VERSION=2.15.05 4 | 5 | ENV RUSTUP_HOME=/usr/local/rustup \ 6 | CARGO_HOME=/usr/local/cargo \ 7 | PATH=/usr/local/cargo/bin:$PATH \ 8 | CC=clang \ 9 | CXX=clang++ \ 10 | CC_x86_64_unknown_linux_gnu=clang \ 11 | CXX_x86_64_unknown_linux_gnu=clang++ \ 12 | RUST_TARGET=x86_64-unknown-linux-gnu \ 13 | LDFLAGS="-fuse-ld=lld" 14 | 15 | RUN apt-get update && \ 16 | apt-get install -y --fix-missing --no-install-recommends gpg-agent ca-certificates openssl && \ 17 | wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - && \ 18 | echo "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-15 main" >> /etc/apt/sources.list && \ 19 | echo "deb-src http://apt.llvm.org/jammy/ llvm-toolchain-jammy-15 main" >> /etc/apt/sources.list && \ 20 | curl -sL https://deb.nodesource.com/setup_16.x | bash - && \ 21 | apt-get install -y --fix-missing --no-install-recommends \ 22 | curl \ 23 | llvm-15 \ 24 | clang-15 \ 25 | lld-15 \ 26 | libc++-15-dev \ 27 | libc++abi-15-dev \ 28 | nodejs \ 29 | xz-utils \ 30 | rcs \ 31 | git \ 32 | make \ 33 | cmake \ 34 | ninja-build && \ 35 | apt-get autoremove -y && \ 36 | curl https://sh.rustup.rs -sSf | sh -s -- -y && \ 37 | npm install -g yarn pnpm lerna && \ 38 | ln -sf /usr/bin/clang-15 /usr/bin/clang && \ 39 | ln -sf /usr/bin/clang++-15 /usr/bin/clang++ && \ 40 | ln -sf /usr/bin/lld-15 /usr/bin/lld && \ 41 | ln -sf /usr/bin/clang-15 /usr/bin/cc 42 | 43 | RUN wget https://www.nasm.us/pub/nasm/releasebuilds/${NASM_VERSION}/nasm-${NASM_VERSION}.tar.xz && \ 44 | tar -xf nasm-${NASM_VERSION}.tar.xz && \ 45 | cd nasm-${NASM_VERSION} && \ 46 | ./configure --prefix=/usr/ && \ 47 | make && \ 48 | make install && \ 49 | cd / && \ 50 | rm -rf nasm-${NASM_VERSION} && \ 51 | rm nasm-${NASM_VERSION}.tar.xz 52 | -------------------------------------------------------------------------------- /crates/napi/src/js_values/value_ref.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | use std::ptr; 3 | 4 | use super::{check_status, Value}; 5 | use crate::{sys, Env, Result}; 6 | 7 | pub struct Ref { 8 | pub(crate) raw_ref: sys::napi_ref, 9 | pub(crate) count: u32, 10 | pub(crate) inner: T, 11 | pub(crate) raw_value: sys::napi_value, 12 | } 13 | 14 | #[allow(clippy::non_send_fields_in_send_ty)] 15 | unsafe impl Send for Ref {} 16 | unsafe impl Sync for Ref {} 17 | 18 | impl Ref { 19 | pub(crate) fn new(js_value: Value, ref_count: u32, inner: T) -> Result> { 20 | let mut raw_ref = ptr::null_mut(); 21 | assert_ne!(ref_count, 0, "Initial `ref_count` must be > 0"); 22 | check_status!(unsafe { 23 | sys::napi_create_reference(js_value.env, js_value.value, ref_count, &mut raw_ref) 24 | })?; 25 | Ok(Ref { 26 | raw_ref, 27 | count: ref_count, 28 | inner, 29 | raw_value: js_value.value, 30 | }) 31 | } 32 | 33 | pub fn reference(&mut self, env: &Env) -> Result { 34 | check_status!(unsafe { sys::napi_reference_ref(env.0, self.raw_ref, &mut self.count) })?; 35 | Ok(self.count) 36 | } 37 | 38 | pub fn unref(&mut self, env: Env) -> Result { 39 | check_status!(unsafe { sys::napi_reference_unref(env.0, self.raw_ref, &mut self.count) })?; 40 | 41 | if self.count == 0 { 42 | check_status!(unsafe { sys::napi_delete_reference(env.0, self.raw_ref) })?; 43 | } 44 | Ok(self.count) 45 | } 46 | } 47 | 48 | impl Deref for Ref { 49 | type Target = T; 50 | 51 | fn deref(&self) -> &T { 52 | &self.inner 53 | } 54 | } 55 | 56 | #[cfg(debug_assertions)] 57 | impl Drop for Ref { 58 | fn drop(&mut self) { 59 | debug_assert_eq!( 60 | self.count, 0, 61 | "Ref count is not equal to 0 while dropping Ref, potential memory leak" 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /.github/workflows/asan.yml: -------------------------------------------------------------------------------- 1 | name: Address Sanitizer 2 | 3 | env: 4 | DEBUG: 'napi:*' 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | pull_request: 11 | 12 | jobs: 13 | build_and_test: 14 | name: nightly - ubuntu - node@16 15 | runs-on: ubuntu-20.04 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Setup node 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: 16 24 | check-latest: true 25 | cache: 'yarn' 26 | 27 | - name: Install 28 | uses: actions-rs/toolchain@v1 29 | with: 30 | toolchain: nightly 31 | profile: minimal 32 | components: rust-src 33 | override: true 34 | 35 | - name: Cache cargo 36 | uses: actions/cache@v3 37 | with: 38 | path: | 39 | ~/.cargo/registry 40 | ~/.cargo/git 41 | key: nightly-ubuntu-node@16-cargo-cache 42 | 43 | - name: Cache NPM dependencies 44 | uses: actions/cache@v3 45 | with: 46 | path: .yarn/cache 47 | key: npm-cache-ubuntu-node@16 48 | 49 | - name: 'Install dependencies' 50 | run: yarn install --immutable --mode=skip-build 51 | 52 | - name: 'Build TypeScript' 53 | run: yarn build 54 | 55 | - name: Unit tests with address sanitizer 56 | run: | 57 | yarn build:test:asan 58 | LD_PRELOAD=/usr/lib/gcc/x86_64-linux-gnu/9/libasan.so yarn test 59 | env: 60 | RUST_TARGET: x86_64-unknown-linux-gnu 61 | RUST_BACKTRACE: 1 62 | RUSTFLAGS: -Z sanitizer=address 63 | ASAN_OPTIONS: detect_leaks=0 64 | 65 | - name: Clear the cargo caches 66 | run: | 67 | cargo install cargo-cache --no-default-features --features ci-autoclean 68 | cargo-cache 69 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/src/array.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryInto; 2 | 3 | use napi::{ 4 | CallContext, ContextlessResult, Env, JsBoolean, JsNumber, JsObject, JsUndefined, JsUnknown, 5 | Result, 6 | }; 7 | 8 | #[contextless_function] 9 | fn test_create_array(env: Env) -> ContextlessResult { 10 | env.create_empty_array().map(Some) 11 | } 12 | 13 | #[js_function(1)] 14 | fn test_create_array_with_length(ctx: CallContext) -> Result { 15 | let length: u32 = ctx.get::(0)?.try_into()?; 16 | ctx.env.create_array_with_length(length as usize) 17 | } 18 | 19 | #[js_function(3)] 20 | fn test_set_element(ctx: CallContext) -> Result { 21 | let mut arr = ctx.get::(0)?; 22 | let index = ctx.get::(1)?; 23 | let ele = ctx.get::(2)?; 24 | arr.set_element(index.try_into()?, ele)?; 25 | 26 | ctx.env.get_undefined() 27 | } 28 | 29 | #[js_function(2)] 30 | fn test_has_element(ctx: CallContext) -> Result { 31 | let arr = ctx.get::(0)?; 32 | let index = ctx.get::(1)?; 33 | 34 | ctx.env.get_boolean(arr.has_element(index.try_into()?)?) 35 | } 36 | 37 | #[js_function(2)] 38 | fn test_delete_element(ctx: CallContext) -> Result { 39 | let mut arr = ctx.get::(0)?; 40 | let index = ctx.get::(1)?; 41 | 42 | ctx.env.get_boolean(arr.delete_element(index.try_into()?)?) 43 | } 44 | 45 | pub fn register_js(exports: &mut JsObject) -> Result<()> { 46 | exports.create_named_method("testCreateArray", test_create_array)?; 47 | exports.create_named_method("testCreateArrayWithLength", test_create_array_with_length)?; 48 | exports.create_named_method("testSetElement", test_set_element)?; 49 | exports.create_named_method("testHasElement", test_has_element)?; 50 | exports.create_named_method("testDeleteElement", test_delete_element)?; 51 | 52 | Ok(()) 53 | } 54 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/arraybuffer.spec.ts: -------------------------------------------------------------------------------- 1 | import ava from 'ava' 2 | 3 | import { napiVersion } from './napi-version' 4 | 5 | const bindings = require('../index.node') 6 | 7 | const test = napiVersion >= 6 ? ava : ava.skip 8 | 9 | test('should get arraybuffer length', (t) => { 10 | const fixture = Buffer.from('wow, hello') 11 | t.is(bindings.getArraybufferLength(fixture.buffer), fixture.buffer.byteLength) 12 | }) 13 | 14 | test('should be able to mutate Uint8Array', (t) => { 15 | const fixture = new Uint8Array([0, 1, 2]) 16 | bindings.mutateUint8Array(fixture) 17 | t.is(fixture[0], 42) 18 | }) 19 | 20 | test('should be able to mutate Uint8Array in its middle', (t) => { 21 | const fixture = new Uint8Array([0, 1, 2]) 22 | const view = new Uint8Array(fixture.buffer, 1, 1) 23 | bindings.mutateUint8Array(view) 24 | t.is(fixture[1], 42) 25 | }) 26 | 27 | test('should be able to mutate Uint16Array', (t) => { 28 | const fixture = new Uint16Array([0, 1, 2]) 29 | bindings.mutateUint16Array(fixture) 30 | t.is(fixture[0], 65535) 31 | }) 32 | 33 | test('should be able to mutate Int16Array', (t) => { 34 | const fixture = new Int16Array([0, 1, 2]) 35 | bindings.mutateInt16Array(fixture) 36 | t.is(fixture[0], 32767) 37 | }) 38 | 39 | test('should be able to mutate Float32Array', (t) => { 40 | const fixture = new Float32Array([0, 1, 2]) 41 | bindings.mutateFloat32Array(fixture) 42 | t.true(Math.abs(fixture[0] - 3.33) <= 0.0001) 43 | }) 44 | 45 | test('should be able to mutate Float64Array', (t) => { 46 | const fixture = new Float64Array([0, 1, 2]) 47 | bindings.mutateFloat64Array(fixture) 48 | t.true(Math.abs(fixture[0] - Math.PI) <= 0.0000001) 49 | }) 50 | 51 | test('should be able to mutate BigInt64Array', (t) => { 52 | const fixture = new BigInt64Array([BigInt(0), BigInt(1), BigInt(2)]) 53 | bindings.mutateI64Array(fixture) 54 | t.deepEqual(fixture[0], BigInt('9223372036854775807')) 55 | }) 56 | -------------------------------------------------------------------------------- /triples/target-list: -------------------------------------------------------------------------------- 1 | aarch64-apple-darwin 2 | aarch64-apple-ios 3 | aarch64-fuchsia 4 | aarch64-linux-android 5 | aarch64-pc-windows-msvc 6 | aarch64-unknown-linux-gnu 7 | aarch64-unknown-linux-musl 8 | aarch64-unknown-none 9 | aarch64-unknown-none-softfloat 10 | arm-unknown-linux-gnueabi 11 | arm-unknown-linux-gnueabihf 12 | arm-unknown-linux-musleabi 13 | arm-unknown-linux-musleabihf 14 | armebv7r-none-eabi 15 | armebv7r-none-eabihf 16 | armv5te-unknown-linux-gnueabi 17 | armv5te-unknown-linux-musleabi 18 | armv7-linux-androideabi 19 | armv7-unknown-linux-gnueabi 20 | armv7-unknown-linux-gnueabihf 21 | armv7-unknown-linux-musleabi 22 | armv7-unknown-linux-musleabihf 23 | armv7a-none-eabi 24 | armv7r-none-eabi 25 | armv7r-none-eabihf 26 | i686-linux-android 27 | i686-pc-windows-gnu 28 | i686-pc-windows-msvc 29 | i686-unknown-freebsd 30 | i686-unknown-linux-gnu 31 | i686-unknown-linux-musl 32 | mips-unknown-linux-gnu 33 | mips-unknown-linux-musl 34 | mips64-unknown-linux-gnuabi64 35 | mips64-unknown-linux-muslabi64 36 | mips64el-unknown-linux-gnuabi64 37 | mips64el-unknown-linux-muslabi64 38 | mipsel-unknown-linux-gnu 39 | mipsel-unknown-linux-musl 40 | nvptx64-nvidia-cuda 41 | powerpc-unknown-linux-gnu 42 | powerpc64-unknown-linux-gnu 43 | powerpc64le-unknown-linux-gnu 44 | riscv32i-unknown-none-elf 45 | riscv32imac-unknown-none-elf 46 | riscv32imc-unknown-none-elf 47 | riscv64gc-unknown-linux-gnu 48 | riscv64gc-unknown-none-elf 49 | riscv64imac-unknown-none-elf 50 | s390x-unknown-linux-gnu 51 | sparc64-unknown-linux-gnu 52 | sparcv9-sun-solaris 53 | x86_64-apple-darwin 54 | x86_64-apple-ios 55 | x86_64-fortanix-unknown-sgx 56 | x86_64-fuchsia 57 | x86_64-linux-android 58 | x86_64-pc-solaris 59 | x86_64-pc-windows-gnu 60 | x86_64-pc-windows-msvc 61 | x86_64-sun-solaris 62 | x86_64-unknown-freebsd 63 | x86_64-unknown-illumos 64 | x86_64-unknown-linux-gnu 65 | x86_64-unknown-linux-gnux32 66 | x86_64-unknown-linux-musl 67 | x86_64-unknown-netbsd 68 | x86_64-unknown-redox 69 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/__test__/js-value.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | const bindings = require('../index.node') 4 | 5 | test('instanceof', (t) => { 6 | const day = new Date() 7 | t.true(bindings.instanceof(day, Date)) 8 | t.false(bindings.instanceof(day, Number)) 9 | t.false(bindings.instanceof(1, Date)) 10 | }) 11 | 12 | test('is_error', (t) => { 13 | t.true(bindings.isError(new Error())) 14 | t.true(bindings.isError(new TypeError())) 15 | t.true(bindings.isError(new SyntaxError())) 16 | t.false(bindings.isError('111')) 17 | t.false(bindings.isError(2)) 18 | t.false(bindings.isError(Symbol())) 19 | }) 20 | 21 | test('is_typedarray', (t) => { 22 | t.true(bindings.isTypedarray(new Uint8Array())) 23 | t.true(bindings.isTypedarray(new Uint16Array())) 24 | t.true(bindings.isTypedarray(new Uint32Array())) 25 | t.true(bindings.isTypedarray(new Int8Array())) 26 | t.true(bindings.isTypedarray(new Int16Array())) 27 | t.true(bindings.isTypedarray(new Int32Array())) 28 | t.true(bindings.isTypedarray(Buffer.from('123'))) 29 | t.false(bindings.isTypedarray(Buffer.from('123').buffer)) 30 | t.false(bindings.isTypedarray([])) 31 | }) 32 | 33 | test('is_dataview', (t) => { 34 | const data = new Uint8Array(100) 35 | t.true(bindings.isDataview(new DataView(data.buffer))) 36 | t.false(bindings.isDataview(Buffer.from('123'))) 37 | }) 38 | 39 | test('strict_equals', (t) => { 40 | const a = { 41 | foo: 'bar', 42 | } 43 | const b = { ...a } 44 | t.false(bindings.strictEquals(a, b)) 45 | t.false(bindings.strictEquals(1, '1')) 46 | t.false(bindings.strictEquals(null, undefined)) 47 | t.false(bindings.strictEquals(NaN, NaN)) 48 | t.true(bindings.strictEquals(a, a)) 49 | }) 50 | 51 | test('cast_unknown', (t) => { 52 | const f = {} 53 | const r = bindings.castUnknown(f) 54 | t.is(f, r) 55 | }) 56 | 57 | test('cast_unknown will not throw', (t) => { 58 | const f = 1 59 | t.notThrows(() => bindings.castUnknown(f)) 60 | }) 61 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/src/napi6/bigint.rs: -------------------------------------------------------------------------------- 1 | use napi::{CallContext, JsBigInt, JsNumber, JsObject, Result}; 2 | use std::convert::TryFrom; 3 | 4 | #[js_function] 5 | pub fn test_create_bigint_from_i64(ctx: CallContext) -> Result { 6 | ctx.env.create_bigint_from_i64(i64::max_value()) 7 | } 8 | 9 | #[js_function] 10 | pub fn test_create_bigint_from_u64(ctx: CallContext) -> Result { 11 | ctx.env.create_bigint_from_u64(u64::max_value()) 12 | } 13 | 14 | #[js_function] 15 | pub fn test_create_bigint_from_i128(ctx: CallContext) -> Result { 16 | ctx.env.create_bigint_from_i128(i128::max_value()) 17 | } 18 | 19 | #[js_function] 20 | pub fn test_create_bigint_from_u128(ctx: CallContext) -> Result { 21 | ctx.env.create_bigint_from_u128(u128::max_value()) 22 | } 23 | 24 | #[js_function] 25 | pub fn test_create_bigint_from_words(ctx: CallContext) -> Result { 26 | ctx 27 | .env 28 | .create_bigint_from_words(true, vec![u64::max_value(), u64::max_value()]) 29 | } 30 | 31 | #[js_function(1)] 32 | pub fn test_get_bigint_i64(ctx: CallContext) -> Result { 33 | let js_bigint = ctx.get::(0)?; 34 | let val = i64::try_from(js_bigint)?; 35 | ctx.env.create_int32(val as i32) 36 | } 37 | 38 | #[js_function(1)] 39 | pub fn test_get_bigint_u64(ctx: CallContext) -> Result { 40 | let js_bigint = ctx.get::(0)?; 41 | let val = u64::try_from(js_bigint)?; 42 | ctx.env.create_int32(val as i32) 43 | } 44 | 45 | #[js_function(0)] 46 | pub fn test_get_bigint_words(ctx: CallContext) -> Result { 47 | let mut js_bigint = ctx 48 | .env 49 | .create_bigint_from_words(true, vec![i64::max_value() as u64, i64::max_value() as u64])?; 50 | let mut js_arr = ctx.env.create_array_with_length(2)?; 51 | let (_signed, words) = js_bigint.get_words()?; 52 | js_arr.set_element(0, ctx.env.create_bigint_from_u64(words[0])?)?; 53 | js_arr.set_element(1, ctx.env.create_bigint_from_u64(words[1])?)?; 54 | Ok(js_arr) 55 | } 56 | -------------------------------------------------------------------------------- /.github/workflows/windows-i686.yml: -------------------------------------------------------------------------------- 1 | name: Windows i686 2 | 3 | env: 4 | DEBUG: 'napi:*' 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | pull_request: 11 | 12 | jobs: 13 | build_and_test: 14 | name: stable - windows-latest - i686 - node@18 15 | runs-on: windows-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Setup node 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: 18 24 | check-latest: true 25 | architecture: 'x64' 26 | cache: 'yarn' 27 | 28 | - name: 'Install dependencies' 29 | run: | 30 | yarn install --mode=skip-build --immutable 31 | 32 | - name: 'Build TypeScript' 33 | run: yarn build 34 | 35 | - name: Install 36 | uses: actions-rs/toolchain@v1 37 | with: 38 | toolchain: stable 39 | profile: minimal 40 | override: true 41 | target: i686-pc-windows-msvc 42 | 43 | - name: Cache cargo 44 | uses: actions/cache@v3 45 | with: 46 | path: | 47 | ~/.cargo/registry 48 | ~/.cargo/git 49 | target 50 | key: stable-windows-i686-node@16-cargo-cache 51 | 52 | - name: Check build 53 | uses: actions-rs/cargo@v1 54 | with: 55 | command: check 56 | args: --all --bins --examples --tests --target i686-pc-windows-msvc -vvv 57 | 58 | - name: Setup node 59 | uses: actions/setup-node@v3 60 | with: 61 | node-version: 16 62 | check-latest: true 63 | architecture: 'x86' 64 | 65 | - name: Build Tests 66 | run: | 67 | yarn workspace compat-mode-examples build-i686 --release 68 | yarn workspace examples build-i686 --release 69 | yarn test --verbose 70 | node ./node_modules/electron/install.js 71 | yarn test:electron 72 | env: 73 | RUST_BACKTRACE: 1 74 | -------------------------------------------------------------------------------- /examples/napi-compat-mode/src/function.rs: -------------------------------------------------------------------------------- 1 | use napi::{CallContext, JsError, JsFunction, JsNull, JsObject, JsUnknown, Result}; 2 | 3 | #[js_function(1)] 4 | pub fn call_function(ctx: CallContext) -> Result { 5 | let js_func = ctx.get::(0)?; 6 | let js_string_hello = ctx.env.create_string("hello".as_ref())?.into_unknown(); 7 | let js_string_world = ctx.env.create_string("world".as_ref())?.into_unknown(); 8 | 9 | js_func.call(None, &[js_string_hello, js_string_world])?; 10 | 11 | ctx.env.get_null() 12 | } 13 | 14 | #[js_function(1)] 15 | pub fn call_function_with_ref_arguments(ctx: CallContext) -> Result { 16 | let js_func = ctx.get::(0)?; 17 | let js_string_hello = ctx.env.create_string("hello".as_ref())?; 18 | let js_string_world = ctx.env.create_string("world".as_ref())?; 19 | 20 | js_func.call(None, &[&js_string_hello, &js_string_world])?; 21 | 22 | ctx.env.get_null() 23 | } 24 | 25 | #[js_function(1)] 26 | pub fn call_function_with_this(ctx: CallContext) -> Result { 27 | let js_this: JsObject = ctx.this_unchecked(); 28 | let js_func = ctx.get::(0)?; 29 | 30 | js_func.call_without_args(Some(&js_this))?; 31 | 32 | ctx.env.get_null() 33 | } 34 | 35 | #[js_function(2)] 36 | pub fn call_function_error(ctx: CallContext) -> Result { 37 | let js_func = ctx.get::(0)?; 38 | let error_func = ctx.get::(1)?; 39 | 40 | match js_func.call_without_args(None) { 41 | Ok(v) => Ok(v), 42 | Err(e) => error_func.call(None, &[JsError::from(e).into_unknown(*ctx.env)]), 43 | } 44 | } 45 | 46 | pub fn register_js(exports: &mut JsObject) -> Result<()> { 47 | exports.create_named_method("testCallFunction", call_function)?; 48 | exports.create_named_method( 49 | "testCallFunctionWithRefArguments", 50 | call_function_with_ref_arguments, 51 | )?; 52 | exports.create_named_method("testCallFunctionWithThis", call_function_with_this)?; 53 | exports.create_named_method("testCallFunctionError", call_function_error)?; 54 | Ok(()) 55 | } 56 | --------------------------------------------------------------------------------