Skip to main content

napi 0.15.0 copy "napi: ^0.15.0" to clipboard
napi: ^0.15.0 copied to clipboard

Generate Dart bindings for Node-API addons and WebAssembly modules.

napi #

A Dart binding compiler for native Node-API .node addons and Dart Wasm. Declare an API in Dart, build an npm package, and import it normally:

// lib/counter.dart
import 'package:napi/napi.dart';

@NAPI()
class Counter {
  int value;
  Counter([this.value = 1]);
  Counter.seed(this.value);
  int increment([int amount = 1]) => value += amount;
}
import { Counter } from '@scope/counter';
const counter = new Counter();
counter.increment(); // 2, executed in Dart

@scope/counter is the example package built below. Imports initialize the selected backend; consumers do not register bindings or call application main.

The product follows napi-rs's model: one Dart export declaration, generated bindings and TypeScript types, and npm packages for native and Wasm targets. Generated JavaScript loaders and runtime helpers initialize bindings on import. Both targets preserve the same supported calls, including new Counter() and argument presence. The experimental native target generates scalar, structural, value-class, reference-class, resource, Future, Stream and retained/async-callback AOT bindings for macOS arm64; see native builds for runtime downloads and offline builds. See the architecture for backend boundaries and compatibility for supported toolchains.

Supported bindings #

  • Resolved @NAPI() discovery with renaming, class/member selection and reexports
  • External selections for unmodified third-party libraries, sharing annotation metadata
  • Required positional scalar functions: bool, String, double, int, BigInt, and nullable forms
  • Optional positional and named scalar arguments with resolved Dart defaults and exact presence capture
  • Nested Lists, Maps, explicit Dictionaries and records with strict fields, reference/opaque value leaves and bounded traversal on both targets (opaque Map keys remain unsupported)
  • Independent Uint8List/ByteBuffer snapshots with strict brands, visible byte ranges and invalidation checks on both targets
  • Explicit SharedBytes live views with checked resizable windows, weak identity and bulk operations on both targets
  • JsArray/JsMap handles with scalar or direct opaque payloads, retaining the original JavaScript container even inside copied structures and value classes
  • Safe-number int by default, or fixed JavaScript bigint with @NAPI(bigint: true)
  • A pinned Dart SDK adapter and linker producing actual Wasm named exports
  • Generated reference classes, named/factory constructors and weak wrapper identity on both targets
  • Generic payload storage and instance/static generic methods, including inherited instance methods, with identity, nullish presence and shared TypeScript relationships
  • Finite concrete reference, resource and value aliases with real Dart type arguments, nested substitutions and policy-specific representations
  • Structural value classes with explicit constructors, recursive fields and direction-specific TypeScript
  • Future-returning functions/methods/getters, including generic payloads, with standard Promise resolution and shared error conversion
  • Resource cleanup with shared state, stable completion identity and operation leases through Promise settlement
  • Typed callbacks with scalar/List/record/JS-handle conversions, weak adapter identity, retained ownership and Promise results
  • Stream async iterators with FIFO reads, bounded snapshots, cancellation and resource cleanup integration
  • Explicit AbortSignal inputs with cooperative Future cancellation and once-only Stream subscription cancellation
  • Fresh single- or dual-target npm packages with explicit entries, assets and atomic no-replace publication
  • Precompiled native bridge/runtime; business builds use only the ordinary Dart SDK

Wasm uses generated JavaScript dependencies; native uses an automatic ESM entry. buildPackage defaults to Wasm. When both targets are included, target selects its root at build time; /native and /wasm select a backend explicitly. There is no automatic runtime selection or implicit fallback.

Build-time checks reject unsupported signatures before writing output. See annotation syntax and scope and the scalar conversion contract, argument presence and defaults, structural values, JS container handles and collection ownership, binary snapshots, shared byte views, value classes, reference classes, opaque payloads, concrete class aliases, resource cleanup, Future exports, callbacks, streams, explicit cancellation, and npm package assembly.

Build a package #

Annotated sources import package:napi/napi.dart. Use a separate Dart VM script to build the example package:

// tool/build.dart
import 'dart:io';
import 'package:napi/build.dart';

Future<void> main() async {
  await buildPackage(
    library: File('lib/counter.dart').absolute.uri,
    output: Directory('npm-package'),
    packagesPath: File('.dart_tool/package_config.json').absolute.path,
    metadata: {'name': '@scope/counter', 'version': '0.0.0'},
  );
}

Run dart run tool/build.dart, then pack/install npm-package in the Node consumer. Use a new output path, or follow the managed rebuild rules for a package created by this builder on local macOS APFS. For native, supply target: BuildTarget.native; the builder downloads and caches the matching precompiled runtime. See native prerequisites and explicit dual-target builds. Use external selections for unmodified Dart dependencies, or buildLibrary when raw artifacts are wanted. See migration notes for the former annotation, container and runtime assumptions.

Scalar conversions #

The same conversion rules apply to package imports on both targets. The example below uses the supported direct-Wasm path; ship its generated dependencies too.

import 'package:napi/napi.dart';

@NAPI()
String? echo(String? value) => value;

@NAPI(bigint: true)
int addWide(int left, int right) => left + right;
import { echo, addWide } from './module.wasm';
echo(null);           // null
echo('🙂');           // '🙂'
addWide(1n, 2n);      // 3n

Conversions never coerce input values. Required nullable parameters accept null, but not undefined or omission. Optional parameters default for omission and undefined. double preserves NaN, infinities and negative zero; strings preserve UTF-16 code units, including lone surrogates. Default int checks the JavaScript safe-integer range in both directions. The bigint option maps all Dart int inputs/results of that function to JavaScript bigint with a signed 64-bit input range; Dart BigInt always uses arbitrary-precision JavaScript bigint. See the full contract, including error behavior.

Verify #

The current tested toolchain is Dart 3.13.5 stable, Node 24.19.0 and 26.11.1, and TypeScript 7.0.2. The SDK adapter deliberately rejects other Dart versions. Dart's Wasm integration remains experimental. Generated conformance consumers use both Node versions; native execution is qualified only for the experimental macOS arm64 ABI 15 runtime bundle supplied by the author. See test commands and native verification. Other native targets, Bun and browser compatibility remain unverified.

dart pub get
npm install --prefix .dart_tool/js --no-save --no-package-lock --ignore-scripts typescript@7.0.2
dart format --output=none --set-exit-if-changed lib test tool
dart analyze --fatal-infos
dart test
dart test -P callback-wasm
dart run tool/verify.dart
dart run tool/check_package.dart

tool/verify.dart builds fresh artifacts and executes the real Node ESM consumers, including an npm-packed fixture installed offline into a separate consumer. Set NAPI_NODE_EXECUTABLES to a JSON array of absolute Node executable paths (e.g. '["/path/to/node24","/path/to/node26"]') to compile and install once, then run each runtime and TypeScript consumer against the same artifacts on every selected Node. The same setting works for tool/native_verify.dart; an unset value uses node from PATH. Any selected runtime failure stops the run. CI checks the Wasm artifacts on Node 24.19.0 and 26.11.1. Independent Dart unit probes still use their configured/default Node; this setting controls the conformance tools. Callback consumers use the callback-wasm and callback-native package:test presets: each builds source/external once, then runs every scenario on each selected Node. Their eight TypeScript configurations run only on NAPI_TEST_NODE or PATH node, independently of the runtime matrix. See Running tests for the explicit native bundle command and single-scenario selection.

It checks TypeScript in NodeNext and Bundler modes. NAPI_TSC can select an already installed TypeScript executable. Package validation permits only the expected warning for the exact analyzer pin; every other warning or error fails.

To inspect just the generated scalar fixture:

dart run tool/scalar_fixture.dart .dart_tool/scalar-example
node .dart_tool/scalar-example/test.mjs

Relative .wasm imports use a sibling module.d.wasm.ts and TypeScript's allowArbitraryExtensions option. A single-target Wasm package's root and ./module.wasm subpath both resolve directly to the Wasm file.

See the Wasm backend design for export representation, automatic initialization, SDK coupling and fresh-output publication. See npm packaging for managed rebuilds on macOS APFS and preservation of author files and metadata.

License #

MIT. See LICENSE. Generated Dart runtime helpers originate in the Dart SDK. Generated artifacts include its license as DART_LICENSE; distributors must retain that notice.

2
likes
0
points
1.17k
downloads

Publisher

unverified uploader

Weekly Downloads

Generate Dart bindings for Node-API addons and WebAssembly modules.

Repository (GitHub)
View/report issues

Topics

#wasm #node #interop

License

unknown (license)

Dependencies

analyzer, crypto, path

More

Packages that depend on napi