1271 lines
37 KiB
Markdown
1271 lines
37 KiB
Markdown
# NAPI-RS Framework Research Report
|
|
|
|
> Comprehensive research on the napi-rs project based on the source at `/workspace/napi-rs`
|
|
> and supplementary documentation from https://napi.rs
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
1. [Project Structure and Key Packages/Crates](#1-project-structure-and-key-packagescrates)
|
|
2. [Setting Up a New napi-rs Project](#2-setting-up-a-new-napi-rs-project)
|
|
3. [Core Patterns for Exposing Rust to JavaScript](#3-core-patterns-for-exposing-rust-to-javascript)
|
|
4. [Result/Option Types and Error Propagation](#4-resultoption-types-and-error-propagation)
|
|
5. [Async Support (Promises, async functions)](#5-async-support-promises-async-functions)
|
|
6. [Build System Configuration](#6-build-system-configuration)
|
|
7. [@napi-rs/cli Tool and Multi-Platform Builds](#7-napi-rscli-tool-and-multi-platform-builds)
|
|
8. [Thread-Safe Function Patterns (tsfn)](#8-thread-safe-function-patterns-tsfn)
|
|
9. [Serde/Serialization with napi-rs Types](#9-serdeserialization-with-napi-rs-types)
|
|
10. [Version Compatibility Notes](#10-version-compatibility-notes)
|
|
|
|
---
|
|
|
|
## 1. Project Structure and Key Packages/Crates
|
|
|
|
The napi-rs repository at `/workspace/napi-rs` is a monorepo using **Cargo workspaces** (Rust) and **Yarn workspaces** (JavaScript). The root `Cargo.toml` defines the workspace members.
|
|
|
|
### Rust Crates (`/crates/`)
|
|
|
|
| Crate | Path | Version | Purpose |
|
|
|-------|------|---------|---------|
|
|
| **`napi`** | `crates/napi/` | 3.8.5 | Main runtime library. Provides the high-level Node-API bindings, type conversions, error types, async runtime, thread-safe functions, and all `bindgen_prelude` types. |
|
|
| **`napi-sys`** | `crates/sys/` | 3.2.1 | Low-level FFI bindings. Raw `napi_*` C function declarations and type definitions. Uses `libloading` for dynamic symbol resolution. |
|
|
| **`napi-derive`** | `crates/macro/` | 3.5.4 | Procedural macro crate. Provides the `#[napi]` attribute macro that is the primary way to expose Rust code to JavaScript. |
|
|
| **`napi-derive-backend`** | `crates/backend/` | 5.0.3 | Code generation backend for `napi-derive`. Handles AST parsing, Rust-to-JS codegen, and TypeScript type definition generation. |
|
|
| **`napi-build`** | `crates/build/` | 2.3.1 | Build script utilities. Called from `build.rs` to configure linker flags for each platform (macOS dynamic_lookup, Android, WASI, Windows GNU). |
|
|
|
|
### JavaScript Packages
|
|
|
|
| Package | Path | Version | Purpose |
|
|
|---------|------|---------|---------|
|
|
| **`@napi-rs/cli`** | `cli/` | 3.6.2 | CLI tool for scaffolding, building, packaging, and publishing napi-rs projects. |
|
|
| **`@examples/napi`** | `examples/napi/` | (private) | Comprehensive test suite showcasing all napi-rs features. |
|
|
|
|
### Key Source Files
|
|
|
|
- `/crates/napi/src/lib.rs` -- Main library entry; re-exports modules, defines `bindgen_prelude`
|
|
- `/crates/napi/src/error.rs` -- `Error<S>` struct, `Result<T, S>`, `JsError`/`JsTypeError`/`JsRangeError`
|
|
- `/crates/napi/src/threadsafe_function.rs` -- `ThreadsafeFunction` implementation (869 lines)
|
|
- `/crates/napi/src/tokio_runtime.rs` -- Tokio runtime management and `execute_tokio_future`
|
|
- `/crates/napi/src/task.rs` -- `Task` and `ScopedTask` traits for async work on libuv threads
|
|
- `/crates/napi/src/async_work.rs` -- `AsyncWorkPromise` for libuv-based async tasks
|
|
- `/crates/napi/src/bindgen_runtime/` -- Core trait implementations: `ToNapiValue`, `FromNapiValue`, `TypeName`, `ValidateNapiValue`, class registration, module registration, iterator support
|
|
- `/crates/backend/src/typegen.rs` -- TypeScript `.d.ts` generation logic (981 lines)
|
|
- `/crates/macro/src/lib.rs` -- `#[napi]`, `#[module_init]`, `#[module_exports]` proc macros
|
|
- `/crates/build/src/lib.rs` -- Platform-specific linker configuration
|
|
|
|
---
|
|
|
|
## 2. Setting Up a New napi-rs Project
|
|
|
|
### Recommended: `napi new` (Scaffolding)
|
|
|
|
The `@napi-rs/cli` provides a `new` command that generates a fully configured project:
|
|
|
|
```sh
|
|
napi new <path> [options]
|
|
```
|
|
|
|
**Available options:**
|
|
|
|
| Option | CLI Flag | Default | Description |
|
|
|--------|----------|---------|-------------|
|
|
| `path` | `<path>` | -- | Directory where project is created |
|
|
| `name` | `--name,-n` | directory name | Project name |
|
|
| `minNodeApiVersion` | `--min-node-api,-v` | 4 | Minimum N-API version |
|
|
| `packageManager` | `--package-manager` | yarn | Package manager (yarn 4.x only for now) |
|
|
| `license` | `--license,-l` | MIT | License |
|
|
| `targets` | `--targets,-t` | [] | Compilation targets |
|
|
| `enableDefaultTargets` | `--enable-default-targets` | true | Enable default platform targets |
|
|
| `enableAllTargets` | `--enable-all-targets` | false | Enable all platform targets |
|
|
| `enableTypeDef` | `--enable-type-def` | true | Auto-generate TypeScript definitions |
|
|
| `enableGithubActions` | `--enable-github-actions` | true | Generate GitHub Actions CI workflow |
|
|
| `testFramework` | `--test-framework` | ava | JS test framework (ava only for now) |
|
|
|
|
**Example:**
|
|
|
|
```sh
|
|
napi new ./my-addon --name my-addon --min-node-api 4
|
|
```
|
|
|
|
### Alternative: package-template
|
|
|
|
The GitHub repository [napi-rs/package-template](https://github.com/napi-rs/package-template) is the canonical template referenced in the README.
|
|
|
|
### Minimal Manual Setup
|
|
|
|
If not using scaffolding, a minimal napi-rs project requires:
|
|
|
|
1. **`Cargo.toml`** with `crate-type = ["cdylib"]`
|
|
2. **`build.rs`** calling `napi_build::setup()`
|
|
3. **`package.json`** with `@napi-rs/cli` devDependency and napi config
|
|
4. Rust source with `#[napi]` annotated functions
|
|
|
|
(See Section 6 for full configuration details.)
|
|
|
|
---
|
|
|
|
## 3. Core Patterns for Exposing Rust to JavaScript
|
|
|
|
### 3.1 Functions
|
|
|
|
The `#[napi]` attribute on functions exposes them as JavaScript functions:
|
|
|
|
```rust
|
|
use napi::bindgen_prelude::*;
|
|
use napi_derive::napi;
|
|
|
|
#[napi]
|
|
pub fn fibonacci(n: u32) -> u32 {
|
|
match n {
|
|
1 | 2 => 1,
|
|
_ => fibonacci(n - 1) + fibonacci(n - 2),
|
|
}
|
|
}
|
|
```
|
|
|
|
**Key behaviors:**
|
|
- Function name is converted to camelCase in JavaScript (e.g., `fibonacci` stays `fibonacci`, `my_func` becomes `myFunc`)
|
|
- JSDoc comments (`///`) on Rust functions become TypeScript documentation
|
|
- The `#[napi(js_name = "...")]` attribute allows custom JavaScript naming
|
|
|
|
### 3.2 Structs as JavaScript Classes
|
|
|
|
There are two patterns for structs:
|
|
|
|
#### a) Struct with `#[napi]` -- JavaScript Class
|
|
|
|
```rust
|
|
#[napi]
|
|
pub struct Animal {
|
|
#[napi(readonly)]
|
|
pub kind: Kind,
|
|
name: String,
|
|
optional_value: Option<i32>,
|
|
}
|
|
|
|
#[napi]
|
|
impl Animal {
|
|
#[napi(constructor)]
|
|
pub fn new(kind: Kind, name: String) -> Self {
|
|
Animal { kind, name, optional_value: None }
|
|
}
|
|
|
|
#[napi(factory)]
|
|
pub fn with_kind(kind: Kind) -> Self {
|
|
Animal { kind, name: "Default".to_owned(), optional_value: None }
|
|
}
|
|
|
|
#[napi(getter)]
|
|
pub fn get_name(&self) -> &str { self.name.as_str() }
|
|
|
|
#[napi(setter)]
|
|
pub fn set_name(&mut self, name: String) { self.name = name; }
|
|
|
|
#[napi(getter, js_name = "type")]
|
|
pub fn kind(&self) -> Kind { self.kind }
|
|
|
|
#[napi]
|
|
pub fn whoami(&self) -> String { /* ... */ }
|
|
|
|
#[napi]
|
|
pub fn get_dog_kind() -> Kind { Kind::Dog } // static method
|
|
}
|
|
```
|
|
|
|
**Key attributes on struct:**
|
|
- `#[napi(constructor)]` -- All public fields become constructor parameters; a default constructor is generated
|
|
- `#[napi(js_name = "Assets")]` -- Rename in JavaScript
|
|
- `#[napi(custom_finalize)]` -- Enable `ObjectFinalize` trait for custom cleanup
|
|
|
|
**Key attributes on impl methods:**
|
|
- `#[napi(constructor)]` -- Mark as constructor
|
|
- `#[napi(factory)]` -- Static factory method
|
|
- `#[napi(getter)]` / `#[napi(setter)]` -- Define getters/setters
|
|
- `#[napi(js_name = "type")]` -- Custom JS name
|
|
- `#[napi(ts_arg_type = "...")]` -- Override TypeScript arg type
|
|
- `#[napi(skip_typescript)]` -- Exclude from TypeScript definitions
|
|
- `#[napi(writable = false)]` -- Make property read-only in JS
|
|
- `#[napi(catch_unwind)]` -- Catch panics and convert to JS errors
|
|
|
|
#### b) Struct with `#[napi(object)]` -- JavaScript Plain Object (Interface)
|
|
|
|
```rust
|
|
#[napi(object)]
|
|
struct AllOptionalObject {
|
|
pub name: Option<String>,
|
|
pub age: Option<u32>,
|
|
}
|
|
```
|
|
|
|
**Key attributes:**
|
|
- `#[napi(object)]` -- Generate as a TypeScript interface, not a class
|
|
- `#[napi(object, object_to_js = false)]` -- Only deserialize from JS (input-only)
|
|
- `#[napi(object, object_from_js = false)]` -- Only serialize to JS (output-only)
|
|
- `#[napi(object, use_nullable = true)]` -- Generate `nullable` TypeScript types instead of `optional`
|
|
- `#[napi(ts_type = "object")]` -- Override TypeScript field type
|
|
- `#[napi(js_name = "customField")]` -- Rename field in JS
|
|
|
|
### 3.3 Enums
|
|
|
|
#### a) Numeric Enums (default)
|
|
|
|
```rust
|
|
#[napi]
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub enum Kind {
|
|
Dog, // 0
|
|
Cat, // 1
|
|
Duck, // 2
|
|
}
|
|
```
|
|
|
|
Custom discriminant values with step resolution:
|
|
|
|
```rust
|
|
#[napi]
|
|
pub enum CustomNumEnum {
|
|
One = 1, // 1
|
|
Two, // 2
|
|
Three = 3, // 3
|
|
Four, // 4
|
|
Six = 6,
|
|
Eight = 8,
|
|
Nine, // 9
|
|
Ten, // 10
|
|
}
|
|
```
|
|
|
|
#### b) String Enums
|
|
|
|
```rust
|
|
#[napi(string_enum)]
|
|
pub enum Status {
|
|
Pristine, // "Pristine"
|
|
Loading, // "Loading"
|
|
Ready, // "Ready"
|
|
}
|
|
|
|
#[napi(string_enum = "lowercase")]
|
|
pub enum StringEnum {
|
|
VariantOne, // "variantone"
|
|
VariantTwo, // "varianttwo"
|
|
}
|
|
|
|
#[napi(string_enum)]
|
|
pub enum CustomStringEnum {
|
|
#[napi(value = "my-custom-value")]
|
|
Foo, // "my-custom-value"
|
|
Bar, // "Bar"
|
|
Baz, // "Baz"
|
|
}
|
|
```
|
|
|
|
#### c) Structured Enums (Tagged Unions)
|
|
|
|
```rust
|
|
#[napi(discriminant = "type2")]
|
|
pub enum StructuredKind {
|
|
Hello,
|
|
Greeting { name: String },
|
|
Birthday { name: String, age: u8 },
|
|
Tuple(u32, u32),
|
|
}
|
|
|
|
#[napi(discriminant_case = "lowercase")]
|
|
pub enum StructuredKindLowercase {
|
|
Hello, // { type2: "hello" }
|
|
Greeting { name: String }, // { type2: "greeting", name: "..." }
|
|
}
|
|
```
|
|
|
|
### 3.4 Transparent Types
|
|
|
|
Newtype pattern that wraps an existing JS-compatible type:
|
|
|
|
```rust
|
|
#[napi(transparent)]
|
|
struct MyVec(Vec<Either<u32, String>>);
|
|
|
|
#[napi]
|
|
fn get_my_vec() -> MyVec {
|
|
MyVec(vec![Either::A(42), Either::B("a string".to_owned())])
|
|
}
|
|
```
|
|
|
|
### 3.5 Constants
|
|
|
|
```rust
|
|
#[napi]
|
|
/// This is a const
|
|
pub const DEFAULT_COST: u32 = 12;
|
|
|
|
#[napi(skip_typescript)]
|
|
pub const TYPE_SKIPPED_CONST: u32 = 12;
|
|
```
|
|
|
|
### 3.6 Callbacks (Fn/FnMut/FnOnce traits)
|
|
|
|
```rust
|
|
#[napi]
|
|
pub fn get_cwd<T: Fn(String) -> Result<()>>(callback: T) {
|
|
callback(std::env::current_dir().unwrap().to_string_lossy().to_string()).unwrap();
|
|
}
|
|
|
|
#[napi]
|
|
pub fn test_callback<T>(callback: T) -> Result<()>
|
|
where
|
|
T: Fn(String) -> Result<()>,
|
|
{
|
|
callback(std::env::current_dir()?.to_string_lossy().to_string())
|
|
}
|
|
```
|
|
|
|
### 3.7 TypeScript Customization
|
|
|
|
The `#[napi]` macro supports several attributes for TypeScript generation:
|
|
|
|
- `ts_args_type` -- Override all function argument types
|
|
- `ts_return_type` -- Override function return type
|
|
- `ts_generic_types` -- Add generic type parameters
|
|
- `ts_type` -- Override field type in objects
|
|
- `skip_typescript` -- Skip generation in `.d.ts`
|
|
- `js_name` -- Rename in JS/TS
|
|
|
|
```rust
|
|
#[napi(
|
|
ts_generic_types = "T",
|
|
ts_args_type = "functionInput: () => T | Promise<T>, callback: (err: Error | null, result: T) => void",
|
|
ts_return_type = "T | Promise<T>"
|
|
)]
|
|
fn callback_return_promise<'env>(/* ... */) -> Result<Unknown<'env>> { /* ... */ }
|
|
```
|
|
|
|
### 3.8 Either Types
|
|
|
|
napi-rs provides `Either<A, B>`, `Either3<A, B, C>`, and `Either4<A, B, C, D>` for union types:
|
|
|
|
```rust
|
|
#[napi]
|
|
fn either_string_or_number(input: Either<String, u32>) -> u32 {
|
|
match input {
|
|
Either::A(s) => s.len() as u32,
|
|
Either::B(n) => n,
|
|
}
|
|
}
|
|
|
|
#[napi]
|
|
fn receive_class_or_number(either: Either<u32, &JsClassForEither>) -> u32 {
|
|
match either {
|
|
Either::A(n) => n + 1,
|
|
Either::B(_) => 100,
|
|
}
|
|
}
|
|
|
|
#[napi]
|
|
pub async fn promise_in_either(input: Either<u32, Promise<u32>>) -> Result<bool> {
|
|
match input {
|
|
Either::A(a) => Ok(a > 10),
|
|
Either::B(b) => {
|
|
let r = b.await?;
|
|
Ok(r > 10)
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3.9 Module Exports / Module Init
|
|
|
|
Custom module initialization can be done with `#[napi(module_exports)]` or `#[napi_derive::module_init]`:
|
|
|
|
```rust
|
|
#[napi(module_exports)]
|
|
pub fn exports(mut export: Object) -> Result<()> {
|
|
let symbol = Symbol::for_desc("NAPI_RS_SYMBOL");
|
|
export.set_named_property("NAPI_RS_SYMBOL", symbol)?;
|
|
Ok(())
|
|
}
|
|
|
|
// For custom tokio runtime:
|
|
#[napi_derive::module_init]
|
|
fn init() {
|
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
|
.enable_all()
|
|
.build()
|
|
.unwrap();
|
|
create_custom_tokio_runtime(rt);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Result/Option Types and Error Propagation
|
|
|
|
### 4.1 The `Result` Type
|
|
|
|
napi-rs defines its own `Result` type alias:
|
|
|
|
```rust
|
|
pub type Result<T, S = Status> = std::result::Result<T, Error<S>>;
|
|
```
|
|
|
|
Returning `Result<T>` from a `#[napi]` function causes errors to be thrown as JavaScript errors. Returning `Ok(value)` resolves normally.
|
|
|
|
### 4.2 The `Error` Struct
|
|
|
|
```rust
|
|
pub struct Error<S: AsRef<str> = Status> {
|
|
pub status: S,
|
|
pub reason: String,
|
|
pub cause: Option<Box<Error>>,
|
|
maybe_raw: sys::napi_ref,
|
|
maybe_env: sys::napi_env,
|
|
}
|
|
```
|
|
|
|
**Creating errors:**
|
|
|
|
```rust
|
|
// Standard error with status code
|
|
Err(Error::new(Status::InvalidArg, "Manual Error".to_owned()))
|
|
|
|
// Error with cause (chained errors)
|
|
let mut err = Error::new(Status::GenericFailure, "Manual Error".to_owned());
|
|
err.set_cause(Error::new(Status::InvalidArg, "Inner Error".to_owned()));
|
|
Err(err)
|
|
|
|
// From a reason string (uses GenericFailure status)
|
|
Error::from_reason("something went wrong")
|
|
```
|
|
|
|
### 4.3 Error Type Variants
|
|
|
|
napi-rs provides specialized error types for different JavaScript error classes:
|
|
|
|
| Rust Type | JavaScript Equivalent |
|
|
|-----------|----------------------|
|
|
| `JsError` | `Error` |
|
|
| `JsTypeError` | `TypeError` |
|
|
| `JsRangeError` | `RangeError` |
|
|
| `JsSyntaxError` (napi9) | `SyntaxError` |
|
|
|
|
### 4.4 Custom Error Status
|
|
|
|
You can define custom error status types by implementing `AsRef<str>` and `From<Status>`:
|
|
|
|
```rust
|
|
pub enum CustomError {
|
|
NapiError(Error<Status>),
|
|
Panic,
|
|
}
|
|
|
|
impl AsRef<str> for CustomError {
|
|
fn as_ref(&self) -> &str {
|
|
match self {
|
|
CustomError::Panic => "Panic",
|
|
CustomError::NapiError(e) => e.status.as_ref(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[napi]
|
|
pub fn custom_status_code() -> Result<(), CustomError> {
|
|
Err(Error::new(CustomError::Panic, "don't panic"))
|
|
}
|
|
```
|
|
|
|
### 4.5 Automatic Conversions from std::io::Error and Others
|
|
|
|
The `Error` struct implements `From` for common Rust error types:
|
|
|
|
- `From<std::io::Error>`
|
|
- `From<std::ffi::NulError>`
|
|
- `From<anyhow::Error>` (with `error_anyhow` feature)
|
|
- `From<serde_json::Error>` (with `serde-json` feature)
|
|
|
|
This allows using `?` operator to propagate standard Rust errors.
|
|
|
|
### 4.6 Catch Unwind
|
|
|
|
The `#[napi(catch_unwind)]` attribute catches Rust panics and converts them to JavaScript errors:
|
|
|
|
```rust
|
|
#[napi(catch_unwind)]
|
|
pub fn panic() {
|
|
panic!("Don't panic");
|
|
}
|
|
```
|
|
|
|
### 4.7 Option Type Mapping
|
|
|
|
| Rust `Option<T>` | JavaScript |
|
|
|------------------|-----------|
|
|
| `Option<u32>` | `number \| undefined` |
|
|
| `Option<String>` | `string \| undefined` |
|
|
| `None` | `undefined` (or `null` depending on context) |
|
|
| `Option<Struct>` where Struct is a class | `Struct \| null` |
|
|
|
|
Null and Undefined are explicit types:
|
|
|
|
```rust
|
|
#[napi]
|
|
fn return_null() -> Null { Null }
|
|
|
|
#[napi]
|
|
fn return_undefined() -> Undefined {}
|
|
```
|
|
|
|
For objects, `#[napi(object, use_nullable = true)]` generates `nullable` TS types (e.g., `string | null`) instead of `optional` (e.g., `string | undefined`).
|
|
|
|
---
|
|
|
|
## 5. Async Support (Promises, async functions)
|
|
|
|
### 5.1 Async Functions (tokio-based)
|
|
|
|
With the `async` feature enabled, any `async fn` annotated with `#[napi]` returns a JavaScript `Promise`:
|
|
|
|
```rust
|
|
// Cargo.toml: napi = { version = "3", features = ["async"] }
|
|
|
|
#[napi]
|
|
async fn read_file_async(path: String) -> Result<Buffer> {
|
|
Ok(tokio::fs::read(path).await?.into())
|
|
}
|
|
|
|
#[napi]
|
|
async fn async_multi_two(arg: u32) -> Result<u32> {
|
|
tokio::task::spawn(async move { Ok(arg * 2) })
|
|
.await
|
|
.unwrap()
|
|
}
|
|
```
|
|
|
|
**Requirements:**
|
|
- Enable the `async` feature (which includes `tokio_rt`)
|
|
- napi-rs manages a Tokio runtime internally (multi-threaded by default)
|
|
- Async errors are properly propagated as rejected promises
|
|
|
|
### 5.2 Custom Tokio Runtime
|
|
|
|
You can provide a custom Tokio runtime configuration:
|
|
|
|
```rust
|
|
#[napi_derive::module_init]
|
|
fn init() {
|
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
|
.enable_all()
|
|
.thread_stack_size(32 * 1024 * 1024)
|
|
.build()
|
|
.unwrap();
|
|
napi::bindgen_prelude::create_custom_tokio_runtime(rt);
|
|
}
|
|
```
|
|
|
|
### 5.3 `async_runtime` Attribute
|
|
|
|
Run a synchronous function inside the async runtime context:
|
|
|
|
```rust
|
|
#[napi(async_runtime)]
|
|
pub fn within_async_runtime_if_available() {
|
|
tokio::spawn(async {
|
|
println!("within_runtime_if_available");
|
|
});
|
|
}
|
|
```
|
|
|
|
### 5.4 AsyncTask (libuv thread pool)
|
|
|
|
For CPU-intensive work that should run on the libuv thread pool rather than the Tokio runtime, use the `Task` trait:
|
|
|
|
```rust
|
|
pub struct DelaySum(u32, u32);
|
|
|
|
#[napi]
|
|
impl napi::Task for DelaySum {
|
|
type Output = u32;
|
|
type JsValue = u32;
|
|
|
|
fn compute(&mut self) -> Result<Self::Output> {
|
|
// Runs on libuv thread pool
|
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
|
Ok(self.0 + self.1)
|
|
}
|
|
|
|
fn resolve(&mut self, _env: napi::Env, output: Self::Output) -> Result<Self::JsValue> {
|
|
Ok(output)
|
|
}
|
|
|
|
fn finally(self, _env: napi::Env) -> Result<()> {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[napi]
|
|
pub fn with_abort_controller(a: u32, b: u32, signal: AbortSignal) -> AsyncTask<DelaySum> {
|
|
AsyncTask::with_signal(DelaySum(a, b), signal)
|
|
}
|
|
```
|
|
|
|
**Task vs ScopedTask:**
|
|
- `Task` -- `resolve` takes `Env` by value; output and JsValue must be `'static`
|
|
- `ScopedTask<'task>` -- `resolve` takes `&'task Env`; JsValue can borrow from the env (e.g., `BufferSlice<'task>`, `Array<'task>`)
|
|
|
|
### 5.5 Promise and PromiseRaw
|
|
|
|
Work directly with JavaScript Promise objects:
|
|
|
|
```rust
|
|
// Await a Promise from JavaScript
|
|
#[napi]
|
|
pub async fn async_plus_100(p: Promise<u32>) -> Result<u32> {
|
|
let v = p.await?;
|
|
Ok(v + 100)
|
|
}
|
|
|
|
// Create resolved/rejected promises
|
|
#[napi]
|
|
pub fn create_resolved_promise<'env>(env: &'env Env, value: u32) -> Result<PromiseRaw<'env, u32>> {
|
|
PromiseRaw::resolve(env, value)
|
|
}
|
|
|
|
#[napi]
|
|
pub fn create_rejected_promise<'env>(env: &'env Env, message: String) -> Result<PromiseRaw<'env, u32>> {
|
|
PromiseRaw::reject(env, Error::from_reason(message))
|
|
}
|
|
|
|
// Chain .then/.catch/.finally
|
|
#[napi]
|
|
pub fn call_then_on_promise(input: PromiseRaw<u32>) -> Result<PromiseRaw<String>> {
|
|
input.then(|v| Ok(format!("{}", v.value)))
|
|
}
|
|
|
|
#[napi]
|
|
pub fn call_catch_on_promise(input: PromiseRaw<'_, u32>) -> Result<PromiseRaw<'_, String>> {
|
|
input.catch(|e: CallbackContext<String>| Ok(e.value))
|
|
}
|
|
```
|
|
|
|
### 5.6 Spawning Futures Manually
|
|
|
|
```rust
|
|
env.spawn_future(async move { Ok(some_value) })
|
|
env.spawn_future_with_callback(async move { Ok(some_value) }, |env, val| {
|
|
env.create_string(format!("{}", val))
|
|
})
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Build System Configuration
|
|
|
|
### 6.1 Cargo.toml
|
|
|
|
```toml
|
|
[package]
|
|
name = "my-addon"
|
|
edition = "2021"
|
|
|
|
[lib]
|
|
crate-type = ["cdylib"]
|
|
|
|
[dependencies]
|
|
napi = { version = "3", features = ["napi4"] } # or more features
|
|
napi-derive = "3"
|
|
|
|
[build-dependencies]
|
|
napi-build = "2"
|
|
|
|
[profile.release]
|
|
lto = true
|
|
```
|
|
|
|
**Critical:** `crate-type = ["cdylib"]` is required so cargo builds a C-style shared library that Node can dynamically load.
|
|
|
|
### 6.2 Feature Flags on `napi` Crate
|
|
|
|
| Feature | Requires | Description |
|
|
|---------|----------|-------------|
|
|
| `napi1` through `napi10` | incremental | Progressive N-API version support |
|
|
| `async` / `tokio_rt` | napi4 | Tokio runtime + async fn support |
|
|
| `serde-json` | -- | serde Serialize/Deserialize for JS <-> Rust |
|
|
| `serde-json-ordered` | serde-json | Preserves key order with `serde_json/preserve_order` |
|
|
| `latin1` | -- | Latin1 string decoding via `encoding_rs` |
|
|
| `chrono_date` | napi5 | `chrono::DateTime` support |
|
|
| `error_anyhow` | -- | `From<anyhow::Error>` for napi::Error |
|
|
| `web_stream` | napi4, tokio_rt | Web Streams API support |
|
|
| `deferred_trace` | napi4 | Deferred stack trace |
|
|
| `object_indexmap` | -- | `indexmap::IndexMap` support |
|
|
| `tracing` | -- | `tracing` crate integration |
|
|
| `dyn-symbols` | -- | Dynamic symbol resolution (default) |
|
|
| `compat-mode` | -- | Deprecated types/traits for v2 compatibility |
|
|
| `noop` | -- | Generate no-op code (for testing) |
|
|
|
|
**Common feature combinations:**
|
|
- Minimal: `napi = { version = "3", default-features = false, features = ["napi4"] }`
|
|
- With async: `napi = { version = "3", features = ["napi4", "async"] }`
|
|
- Full: `napi = { version = "3", features = ["full"] }` (includes napi10, async, serde-json, experimental, chrono_date, latin1)
|
|
|
|
### 6.3 napi-derive Features
|
|
|
|
| Feature | Description |
|
|
|---------|-------------|
|
|
| `type-def` (default) | Auto-generate TypeScript `.d.ts` definitions |
|
|
| `strict` (default) | Strict type checking in macro expansion |
|
|
| `compat-mode` | Deprecated attribute compatibility |
|
|
| `tracing` | Tracing in macro expansion |
|
|
| `noop` | Generate no-op code |
|
|
|
|
### 6.4 build.rs
|
|
|
|
Every napi-rs project must have a `build.rs` that calls `napi_build::setup()`:
|
|
|
|
```rust
|
|
fn main() {
|
|
napi_build::setup();
|
|
}
|
|
```
|
|
|
|
**What `napi_build::setup()` does:**
|
|
1. Sets `cargo:rerun-if-env-changed` for various NAPI environment variables
|
|
2. On **macOS**: adds linker flags `-Wl,-undefined,dynamic_lookup` (needed because Node.js symbols are resolved at runtime, not link time)
|
|
3. On **Windows (GNU)**: configures GNU toolchain linker settings
|
|
4. On **Android/WASI**: platform-specific setup
|
|
5. On **GNU libc / FreeBSD**: adds `-Wl,-z,nodelete` to prevent DSO unloading issues with pthread_key_create destructors
|
|
|
|
### 6.5 package.json (napi config)
|
|
|
|
```json
|
|
{
|
|
"name": "my-addon",
|
|
"devDependencies": {
|
|
"@napi-rs/cli": "^3.0.0"
|
|
},
|
|
"napi": {
|
|
"name": "jarvis",
|
|
"binaryName": "example",
|
|
"wasm": {
|
|
"initialMemory": 16384,
|
|
"browser": { "fs": true, "buffer": true }
|
|
},
|
|
"dtsHeader": "type MaybePromise<T> = T | Promise<T>",
|
|
"dtsHeaderFile": "./dts-header.d.ts",
|
|
"targets": ["wasm32-wasip1-threads"]
|
|
},
|
|
"scripts": {
|
|
"build": "napi build --release",
|
|
"build:debug": "napi build",
|
|
"build:platform": "napi build --platform"
|
|
}
|
|
}
|
|
```
|
|
|
|
The `napi.name` / `napi.binaryName` field determines the output `.node` file name. The naming convention converts hyphens to underscores: `my-addon` -> `my_addon.node`.
|
|
|
|
---
|
|
|
|
## 7. @napi-rs/cli Tool and Multi-Platform Builds
|
|
|
|
### 7.1 CLI Version: 3.6.2
|
|
|
|
The `@napi-rs/cli` package (at `/workspace/napi-rs/cli/`) is the primary tooling for building, packaging, and releasing napi-rs projects.
|
|
|
|
### 7.2 Commands Overview
|
|
|
|
| Command | Description |
|
|
|---------|-------------|
|
|
| `napi new` | Create a new project with pre-configured boilerplate |
|
|
| `napi build` | Build the napi-rs project |
|
|
| `napi create-npm-dirs` | Create per-platform npm package directories |
|
|
| `napi artifacts` | Copy build artifacts from GitHub Actions |
|
|
| `napi rename` | Rename the project |
|
|
| `napi universalize` | Combine binaries into a universal binary (e.g., macOS arm64 + x64) |
|
|
| `napi version` | Update version across per-platform npm packages |
|
|
| `napi pre-publish` | Prepare packages for npm publish |
|
|
|
|
### 7.3 Build Command Details
|
|
|
|
```sh
|
|
napi build [--release] [--platform] [--target <triple>] [options]
|
|
```
|
|
|
|
**Key options:**
|
|
|
|
| Option | Description |
|
|
|--------|-------------|
|
|
| `--target,-t` | Target triple (passed to `cargo build --target`) |
|
|
| `--platform` | Add platform triple suffix (e.g., `.linux-x64-gnu.node`) |
|
|
| `--release,-r` | Build in release mode |
|
|
| `--js` | Path/filename for generated JS binding |
|
|
| `--no-js` | Disable JS binding generation |
|
|
| `--dts` | Path/filename for generated TypeScript definitions |
|
|
| `--strip,-s` | Strip debug symbols for minimum file size |
|
|
| `--cross-compile,-x` | Cross-compile using `cargo-xwin` / `cargo-zigbuild` |
|
|
| `--use-cross` | Use [cross](https://github.com/cross-rs/cross) instead of `cargo` |
|
|
| `--use-napi-cross` | Use `@napi-rs/cross-toolchain` for Linux ARM/ARM64/x64 GNU |
|
|
| `--watch,-w` | Watch and rebuild continuously |
|
|
| `--features,-F` | Space-separatedCargo features to activate |
|
|
| `--output-dir,-o` | Output directory for built files |
|
|
| `--esm` | Generate ESM JS binding instead of CJS |
|
|
| `--const-enum` | Generate const enums in TypeScript |
|
|
|
|
### 7.4 Multi-Platform Build Workflow
|
|
|
|
The typical cross-compilation workflow:
|
|
|
|
1. **Build** for each target:
|
|
```sh
|
|
napi build --platform --target x86_64-unknown-linux-gnu
|
|
napi build --platform --target aarch64-apple-darwin
|
|
napi build --platform --target x86_64-pc-windows-msvc
|
|
```
|
|
|
|
2. **Universalize** (macOS only -- combine arm64 + x64 into a single universal binary):
|
|
```sh
|
|
napi universalize
|
|
```
|
|
|
|
3. **Create npm directories** for per-platform packages:
|
|
```sh
|
|
napi create-npm-dirs
|
|
```
|
|
|
|
4. **Artifacts** -- Collect `.node` files from GitHub Actions CI:
|
|
```sh
|
|
napi artifacts --output-dir ./artifacts
|
|
```
|
|
|
|
5. **Pre-publish** -- Copy platform-specific `.node` files into per-platform npm packages:
|
|
```sh
|
|
napi pre-publish --npm-dir npm
|
|
```
|
|
|
|
### 7.5 Supported Platforms
|
|
|
|
| Platform | Architectures | Variants |
|
|
|----------|---------------|----------|
|
|
| Windows | x64, x86, arm64 | MSVC, GNU |
|
|
| macOS | x64, aarch64 | - |
|
|
| Linux | x64, aarch64, arm, riscv64, s390x, ppc64le, loong64 | gnu, musl, gnueabihf, musleabihf |
|
|
| FreeBSD | x64 | - |
|
|
| Android | aarch64, armv7 | - |
|
|
|
|
---
|
|
|
|
## 8. Thread-Safe Function Patterns (tsfn)
|
|
|
|
Thread-safe functions (TSFNs) are the mechanism for calling JavaScript from background threads. They are the cornerstone of async and concurrent interop in napi-rs.
|
|
|
|
### 8.1 Basic Usage
|
|
|
|
```rust
|
|
use napi::threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode};
|
|
|
|
#[napi]
|
|
pub fn call_threadsafe_function(
|
|
tsfn: Arc<ThreadsafeFunction<u32, UnknownReturnValue>>,
|
|
) -> Result<()> {
|
|
for n in 0..100 {
|
|
let tsfn = tsfn.clone();
|
|
thread::spawn(move || {
|
|
tsfn.call(Ok(n), ThreadsafeFunctionCallMode::NonBlocking);
|
|
});
|
|
}
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
### 8.2 Type Parameters
|
|
|
|
```rust
|
|
ThreadsafeFunction<
|
|
T: 'static, // Input type (what you send from Rust)
|
|
Return: FromNapiValue, // Return type from JS callback
|
|
CallJsBackArgs: JsValuesTupleIntoVec, // Arguments passed to JS callback
|
|
ErrorStatus: AsRef<str> + From<Status>, // Custom error status
|
|
const CalleeHandled: bool, // Whether callback follows (err, result) pattern
|
|
const Weak: bool, // Weak reference (won't prevent event loop exit)
|
|
const MaxQueueSize: usize, // Max queued calls (0 = unlimited)
|
|
>
|
|
```
|
|
|
|
### 8.3 Caller-Handled vs Callee-Handled
|
|
|
|
**Callee-handled** (`CalleeHandled = true`, default): Follows Node.js callback convention, prepending `null` as the first arg on success:
|
|
|
|
```rust
|
|
// Callee-handled: JS receives (null, value) on success, (error, undefined) on failure
|
|
let tsfn: ThreadsafeFunction<u32, UnknownReturnValue>;
|
|
tsfn.call(Ok(42), ThreadsafeFunctionCallMode::NonBlocking);
|
|
```
|
|
|
|
**Caller-handled** (`CalleeHandled = false`): No error-first argument. On error, calls `napi_fatal_exception`:
|
|
|
|
```rust
|
|
// Fatal mode: JS receives just (value), no error-first convention
|
|
let tsfn: ThreadsafeFunction<u32, (), u32, Status, false>;
|
|
tsfn.call(42, ThreadsafeFunctionCallMode::NonBlocking);
|
|
```
|
|
|
|
### 8.4 Async Call with Return Value
|
|
|
|
```rust
|
|
#[napi]
|
|
pub async fn tsfn_return_promise(func: ThreadsafeFunction<u32, Promise<u32>>) -> Result<u32> {
|
|
let val = func.call_async(Ok(1)).await?.await?;
|
|
Ok(val + 2)
|
|
}
|
|
```
|
|
|
|
### 8.5 Call with Return Value Callback
|
|
|
|
```rust
|
|
#[napi]
|
|
pub fn tsfn_call_with_callback(tsfn: ThreadsafeFunction<(), String>) -> napi::Result<()> {
|
|
tsfn.call_with_return_value(
|
|
Ok(()),
|
|
ThreadsafeFunctionCallMode::NonBlocking,
|
|
|value: Result<String>, _| {
|
|
let value = value.expect("Failed to retrieve value from JS");
|
|
println!("{}", value);
|
|
Ok(())
|
|
},
|
|
);
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
### 8.6 Building from a Function
|
|
|
|
The builder pattern allows customizing TSFN behavior:
|
|
|
|
```rust
|
|
#[napi]
|
|
pub fn build_threadsafe_function_from_function(
|
|
callback: Function<FnArgs<(u32, u32)>, u32>,
|
|
) -> Result<()> {
|
|
let tsfn = callback.build_threadsafe_function().build()?;
|
|
let tsfn_fatal = callback
|
|
.build_threadsafe_function()
|
|
.callee_handled::<true>()
|
|
.build()?;
|
|
let tsfn_max_queue = callback
|
|
.build_threadsafe_function()
|
|
.max_queue_size::<1>()
|
|
.build()?;
|
|
let tsfn_weak = callback
|
|
.build_threadsafe_function()
|
|
.weak::<true>()
|
|
.build()?;
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
### 8.7 Custom Error Status
|
|
|
|
```rust
|
|
pub struct ErrorStatus(String);
|
|
impl AsRef<str> for ErrorStatus {
|
|
fn as_ref(&self) -> &str { &self.0 }
|
|
}
|
|
impl From<Status> for ErrorStatus {
|
|
fn from(value: Status) -> Self { ErrorStatus(value.to_string()) }
|
|
}
|
|
|
|
#[napi]
|
|
pub fn threadsafe_function_throw_error_with_status(
|
|
cb: ThreadsafeFunction<bool, UnknownReturnValue, bool, ErrorStatus>,
|
|
) -> Result<()> {
|
|
thread::spawn(move || {
|
|
cb.call(
|
|
Err(Error::new(ErrorStatus("CustomErrorStatus".to_string()), "ThrowFromNative".to_owned())),
|
|
ThreadsafeFunctionCallMode::Blocking,
|
|
);
|
|
});
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
### 8.8 Weak Threadsafe Functions
|
|
|
|
Weak TSFNs do not prevent the Node.js event loop from exiting:
|
|
|
|
```rust
|
|
#[napi]
|
|
pub async fn tsfn_weak(
|
|
tsfn: ThreadsafeFunction<(), (), (), Status, false, true>,
|
|
) -> napi::Result<()> {
|
|
tsfn.call_async(()).await
|
|
}
|
|
```
|
|
|
|
### 8.9 Tuple Arguments
|
|
|
|
```rust
|
|
#[napi]
|
|
pub fn accept_threadsafe_function_tuple_args(
|
|
func: ThreadsafeFunction<FnArgs<(u32, bool, String)>>,
|
|
) {
|
|
thread::spawn(move || {
|
|
func.call(
|
|
Ok((1, false, "NAPI-RS".into()).into()),
|
|
ThreadsafeFunctionCallMode::NonBlocking,
|
|
);
|
|
});
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 9. Serde/Serialization with napi-rs Types
|
|
|
|
### 9.1 Feature Flag
|
|
|
|
Enable `serde-json` feature:
|
|
```toml
|
|
[dependencies]
|
|
napi = { version = "3", features = ["serde-json"] }
|
|
```
|
|
|
|
This adds:
|
|
- `impl ser::Error for Error` and `impl de::Error for Error`
|
|
- `impl From<serde_json::Error> for Error`
|
|
- `env.from_js_value()` and `env.to_js_value()` for serde types
|
|
- Automatic (de)serialization between `serde_json::Value` and JS values
|
|
- Automatic (de)serialization between `serde_json::Map` and JS objects
|
|
|
|
### 9.2 Using `#[napi(object)]` with Serde
|
|
|
|
```rust
|
|
#[napi(object)]
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
struct PackageJson {
|
|
pub name: String,
|
|
pub version: String,
|
|
pub dependencies: Option<Map<String, Value>>,
|
|
#[serde(rename = "devDependencies")]
|
|
pub dev_dependencies: Option<Map<String, Value>>,
|
|
}
|
|
|
|
#[napi]
|
|
fn read_package_json() -> Result<PackageJson> {
|
|
let raw = fs::read_to_string("package.json")?;
|
|
let p: PackageJson = serde_json::from_str(&raw)?;
|
|
Ok(p)
|
|
}
|
|
```
|
|
|
|
When a struct has both `#[napi(object)]` and `#[derive(Serialize, Deserialize)]`, napi-rs uses serde for bidirectional conversion. The struct acts as a TypeScript interface with automatic serialization from JS objects and deserialization to JS objects.
|
|
|
|
### 9.3 Direct serde_json::Value
|
|
|
|
```rust
|
|
#[napi]
|
|
fn test_serde_roundtrip(data: Value) -> Value {
|
|
data // serde_json::Value <-> JavaScript any
|
|
}
|
|
|
|
#[napi]
|
|
fn test_serde_big_number_precision(number: String) -> Value {
|
|
let data = format!("{{\"number\":{}}}", number);
|
|
serde_json::from_str(&data).unwrap()
|
|
}
|
|
```
|
|
|
|
### 9.4 Manual Serde with env.from_js_value / env.to_js_value
|
|
|
|
```rust
|
|
#[derive(Serialize, Debug, Deserialize)]
|
|
struct BytesObject {
|
|
#[serde(with = "serde_bytes")]
|
|
code: Vec<u8>,
|
|
}
|
|
|
|
#[napi]
|
|
fn test_serde_buffer_bytes(obj: Object, env: Env) -> napi::Result<usize> {
|
|
let obj: BytesObject = env.from_js_value(obj)?;
|
|
Ok(obj.code.len())
|
|
}
|
|
```
|
|
|
|
### 9.5 Class with Serde
|
|
|
|
```rust
|
|
#[napi]
|
|
struct PackageJsonReader {
|
|
i: Value,
|
|
}
|
|
|
|
#[napi]
|
|
impl PackageJsonReader {
|
|
#[napi(constructor)]
|
|
pub fn new() -> Result<Self> {
|
|
let raw = fs::read_to_string("package.json")?;
|
|
Ok(Self { i: serde_json::from_str(&raw)? })
|
|
}
|
|
|
|
#[napi]
|
|
pub fn read(&self) -> &Value {
|
|
&self.i
|
|
}
|
|
}
|
|
```
|
|
|
|
### 9.6 serde_bytes Support
|
|
|
|
For `Vec<u8>` fields that should be serialized as binary data (Buffer in JS), use `serde_bytes`:
|
|
|
|
```rust
|
|
#[derive(Serialize, Deserialize)]
|
|
struct BytesObject {
|
|
#[serde(with = "serde_bytes")]
|
|
code: Vec<u8>,
|
|
}
|
|
```
|
|
|
|
### 9.7 Ordered JSON
|
|
|
|
Use `serde-json-ordered` feature to preserve JSON key insertion order:
|
|
|
|
```toml
|
|
napi = { version = "3", features = ["serde-json-ordered"] }
|
|
```
|
|
|
|
---
|
|
|
|
## 10. Version Compatibility Notes
|
|
|
|
### 10.1 Current Checkout Version
|
|
|
|
Based on git log and Cargo.toml files at `/workspace/napi-rs`:
|
|
|
|
| Component | Version |
|
|
|-----------|---------|
|
|
| **napi crate** | 3.8.5 |
|
|
| **napi-sys crate** | 3.2.1 |
|
|
| **napi-derive crate** | 3.5.4 |
|
|
| **napi-derive-backend crate** | 5.0.3 |
|
|
| **napi-build crate** | 2.3.1 |
|
|
| **@napi-rs/cli** | 3.6.2 |
|
|
| **Git tag** | `napi-v3.8.5` |
|
|
| **Rust MSRV** | 1.88.0 |
|
|
|
|
### 10.2 Versioning Scheme
|
|
|
|
napi-rs uses a monorepo with independent crate versioning. The major version of the `napi` crate (v3) and `napi-derive` (v3) should match. `napi-build` is v2.x. The npm CLI package follows its own SemVer (v3.6.x).
|
|
|
|
### 10.3 N-API Version Matrix
|
|
|
|
napi-rs supports N-API versions 1 through 10 via feature flags:
|
|
|
|
| Feature | N-API Version | Min Node.js | Key Capabilities |
|
|
|---------|---------------|-------------|------------------|
|
|
| `napi1` | 1 | v8.0.0 | Basic types, functions |
|
|
| `napi2` | 2 | v8.10.0 | Thread-safe functions (experimental) |
|
|
| `napi3` | 3 | v9.11.0 | Cleanup hooks |
|
|
| `napi4` | 4 | v10.6.0 | Thread-safe functions (stable), tokio_rt |
|
|
| `napi5` | 5 | v10.17.0 / v12.0.0 | Date |
|
|
| `napi6` | 6 | v10.7.0 / v12.0.0 | BigInt |
|
|
| `napi7` | 7 | v10.12.0 | Detached array buffers |
|
|
| `napi8` | 8 | v10.23+ / v12.23+ | Async cleanup hooks |
|
|
| `napi9` | 9 | v14.21+ / v16.17+ | SyntaxError, object property management |
|
|
| `napi10` | 10 | v18.17.0 | create_object_with_properties |
|
|
|
|
### 10.4 Type Conversion Table
|
|
|
|
From the README features table:
|
|
|
|
| Rust Type | JavaScript Type | N-API Version | Feature Flag |
|
|
|-----------|----------------|---------------|--------------|
|
|
| `u32` | Number | 1 | -- |
|
|
| `i32` / `i64` | Number | 1 | -- |
|
|
| `f64` | Number | 1 | -- |
|
|
| `bool` | Boolean | 1 | -- |
|
|
| `String` / `&str` | String | 1 | -- |
|
|
| `Latin1String` | String | 1 | `latin1` |
|
|
| `UTF16String` | String | 1 | -- |
|
|
| `Object` | Object | 1 | -- |
|
|
| `serde_json::Map` | Object | 1 | `serde-json` |
|
|
| `serde_json::Value` | any | 1 | `serde-json` |
|
|
| `Array` | Array<any> | 1 | -- |
|
|
| `Vec<T>` | Array<T> | 1 | -- |
|
|
| `Buffer` | Buffer | 1 | -- |
|
|
| `External<T>` | External<T> | 1 | -- |
|
|
| `Null` | null | 1 | -- |
|
|
| `Undefined` / `()` | undefined | 1 | -- |
|
|
| `T: Fn(...) -> Result<T>` | Function | 1 | -- |
|
|
| `Async/Future` | Promise<T> | 4 | `async` |
|
|
| `AsyncTask` | Promise<T> | 1 | -- |
|
|
| `JsGlobal` | global | 1 | -- |
|
|
| `JsSymbol` | Symbol | 1 | -- |
|
|
| `Int8Array`/`Uint8Array`... | TypedArray | 1 | -- |
|
|
| `JsFunction` | threadsafe function | 4 | `napi4` |
|
|
| `BigInt` | BigInt | 6 | `napi6` |
|
|
|
|
### 10.5 Electron Support
|
|
|
|
napi-rs works in Electron. The tokio runtime is designed to handle Electron renderer process environment recycling (Node env exits and recreates on window reload). The `start_async_runtime()` and `shutdown_async_runtime()` functions manage the runtime lifecycle.
|
|
|
|
### 10.6 WebAssembly Support
|
|
|
|
napi-rs has experimental WASM support via the `wasm32-wasip1-threads` target. Key notes:
|
|
- The `@napi-rs/wasm-runtime` and `@emnapi/runtime` packages provide the WASM runtime
|
|
- Build with: `napi build --platform --target wasm32-wasip1-threads`
|
|
- Some features (like `tokio/net`, file I/O) are conditionally compiled out for WASM
|
|
- `tokio_unstable` cfg is used for WASM-specific tokio configuration
|
|
|
|
### 10.7 Breaking Changes Notes
|
|
|
|
- Version 3.x uses declarative `#[ctor]` for module registration (as of commit `ba6597b3`)
|
|
- `compat-mode` feature enables deprecated v2 types/traits
|
|
- The `ThreadSafeCallContext` type was renamed to `ThreadsafeCallContext` (v2.17.0+)
|
|
- Manual `refer()`/`unref()`/`abort()` methods on ThreadsafeFunction are deprecated; use `Clone`/`Drop` instead
|
|
|
|
---
|
|
|
|
## Appendix: Type Conversion Quick Reference
|
|
|
|
### Primitive Types
|
|
|
|
| Rust | TypeScript |
|
|
|------|-----------|
|
|
| `u32`, `i32`, `f64` | `number` |
|
|
| `i64`, `u64` | `number` (or `BigInt` with `napi6`) |
|
|
| `bool` | `boolean` |
|
|
| `String`, `&str` | `string` |
|
|
| `()` | `void` |
|
|
| `Null` | `null` |
|
|
| `Undefined` | `undefined` |
|
|
|
|
### Compound Types
|
|
|
|
| Rust | TypeScript |
|
|
|------|-----------|
|
|
| `Vec<T>` | `Array<T>` |
|
|
| `Buffer` / `Uint8Array` | `Buffer` / `Uint8Array` |
|
|
| `Object` | `object` |
|
|
| `Option<T>` | `T \| undefined` (or `T \| null`) |
|
|
| `Either<A, B>` | `A \| B` |
|
|
| `Either3<A, B, C>` | `A \| B \| C` |
|
|
| `Either4<A, B, C, D>` | `A \| B \| C \| D` |
|
|
| `Result<T>` | throw/rethrow |
|
|
| `Promise<T>` | `Promise<T>` |
|
|
| `Function<Args, Return>` | `Function` |
|
|
| `serde_json::Value` | `any` |
|
|
|
|
### Class Patterns
|
|
|
|
| Rust Attribute | TypeScript |
|
|
|----------------|-----------|
|
|
| `#[napi] struct` | `class` |
|
|
| `#[napi(object)] struct` | `interface` |
|
|
| `#[napi(transparent)] struct` | same as inner type |
|
|
| `#[napi] enum` | numeric `enum` |
|
|
| `#[napi(string_enum)] enum` | string union type |
|
|
| `#[napi(discriminant = "type")] enum` | tagged union / discriminated union |
|
|
|
|
---
|
|
|
|
*Report generated from source at `/workspace/napi-rs` (commit `ba6597b3`, tag `napi-v3.8.5`) and https://napi.rs*
|