npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

relaxnative

v0.2.0

Published

Zero-config native C/C++/Rust execution for Node.js

Readme


What you get

  • Five languages: import a .c, .cpp, .rs, .zig, or .go file and call its exported functions from JS/TS.
  • Compile-on-demand: first import compiles; subsequent imports hit the cache.
  • Ahead-of-time builds: relaxnative build compiles native sources once so the app runs at runtime with no compiler installed (foreign code compiled at build time, loaded at run time).
  • Multi-file builds: link several sources into one module (C/C++), or bring in mod/@import/package files (Rust/Zig/Go).
  • Deterministic cache: keyed by full compile inputs (all sources' contents, include/lib paths, flags, platform).
  • Isolation modes:
    • in-process (fastest) — native runs in the JS thread; lowest overhead, least isolation.
    • worker — async dispatch via worker threads; more isolation, some overhead. Not crash-safe for sync functions.
    • process — separate process: crash isolation, per-call timeouts, and best-effort fs/net/spawn guards. Highest overhead, strongest safety.
  • RelaxRegistry packages: install native “packages” into native/registry/.
  • Supply-chain trust: local, community, verified — with real Ed25519 signature verification for verified and content-pinned trust.

Native code is inherently unsafe. Isolation and guards help, but they’re not a perfect sandbox.


Installation

npm i relaxnative

Requirements (per language you use):

  • Node.js >= 18.
  • C / C++: clang, gcc, MSVC cl, or zig — one Zig install provides zig cc/zig c++ (clang-based) and covers C, C++, and Zig.
  • Rust: rustc (+ cargo).
  • Zig: zig on PATH (or set the ZIG env var to the binary).
  • Go: go. On Windows, cross-compilation to a native DLL is done via WSL with go + mingw-w64 (x86_64-w64-mingw32-gcc) installed in the distro; or a native Windows go + C compiler.

Run npx relaxnative doctor to see exactly which toolchains are detected.

Tip: none of these are needed at runtime if you ship an ahead-of-time build.


Quickstart

0) Full end-to-end example (new folder → run)

This is the fastest way to try Relaxnative in a clean folder.

mkdir -p my-relaxnative-app/native
cd my-relaxnative-app
npm init -y
npm i relaxnative

Optional package.json (ESM + a run script):

{
  "name": "my-relaxnative-app",
  "private": true,
  "type": "module",
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {
    "relaxnative": "^0.1.0"
  }
}

Create native/add.c:

// @sync
int add(int a, int b) {
  return a + b;
}

Create index.js:

import { loadNative } from 'relaxnative';

const mod = await loadNative('native/add.c', { isolation: 'worker' });
console.log('add(1,2)=', mod.add(1, 2));

Run it:

npm start

If something goes wrong, re-run with tracing:

RELAXNATIVE_TRACE=1 npm start

1) Create a native file

native/add.c

// @sync
int add(int a, int b) {
  return a + b;
}

2) Import and call it from JS/TS

import { loadNative } from 'relaxnative';

const mod = await loadNative('native/add.c', { isolation: 'worker' });
console.log(mod.add(1, 2));

Supported languages

Each source compiles to a native shared library (.dll/.so/.dylib) exposing C-ABI functions, then Relaxnative parses the signatures and binds them.

| Language | Extension | How functions are exported | Compiler | |----------|-----------|----------------------------|----------| | C | .c | plain top-level functions | clang / gcc / cl / zig cc | | C++ | .cpp .cc .cxx | extern "C" functions | clang++ / g++ / cl / zig c++ | | Rust | .rs | #[no_mangle] pub extern "C" fn | rustc (--crate-type cdylib) | | Zig | .zig | export fn | zig build-lib -dynamic | | Go | .go | //export Name (+ import "C", func main(){}) | go build -buildmode=c-shared |

Minimal exports per language:

// add.c
int add(int a, int b) { return a + b; }
// add.cpp
extern "C" int add(int a, int b) { return a + b; }
// add.rs
#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 { a + b }
// add.zig
export fn add(a: i32, b: i32) i32 { return a + b; }
// add.go
package main
import "C"
//export add
func add(a, b C.int) C.int { return a + b }
func main() {}

All are consumed identically:

import { loadNative } from 'relaxnative';
const mod = await loadNative('native/add.zig', { isolation: 'in-process' });
console.log(mod.add(20, 22)); // 42

Go on Windows builds a real Windows DLL by cross-compiling inside WSL (GOOS=windows CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc go build -buildmode=c-shared). Install go and gcc-mingw-w64-x86-64 in your WSL distro.


Multi-file builds

Pass additional sources via build.sources. Exports are discovered across all files and merged; the compiler links/imports them into one library.

const mod = await loadNative('native/main.c', {
  isolation: 'in-process',
  build: {
    sources: ['native/helper.c'],       // linked into the same .dll
    includePaths: ['native/include'],   // -I / /I
    libraryPaths: ['native/lib'],       // -L / /LIBPATH
    libraries: ['m'],                   // -lm / m.lib
    flags: ['-O3'],                     // extra compiler flags
  },
});

Per-language semantics:

  • C / C++ — every file in sources is compiled and linked together.
  • Rust — additional files are mod-declared from the crate root and read by rustc; point sourcePath at the root.
  • Zig — additional files are @imported from the root source.
  • Go — all files form one main package; each file with an //export must import "C".

The cache key includes the contents of every source plus include/library paths, libraries, flags, and platform — so changing any of them rebuilds correctly.


Ahead-of-time builds (relaxnative build)

Compile foreign code at build time and load it at run time with no compiler installed. This is ideal for CI, Docker images, or shipping an app to machines without a toolchain.

Build one or more sources:

npx relaxnative build native/add.c native/kernel.zig native/svc.go

…or declare targets in relaxnative.build.json at your project root:

{
  "targets": [
    { "source": "native/add.c" },
    { "source": "native/kernel.zig" },
    {
      "source": "native/mathkernel/main.c",
      "sources": ["native/mathkernel/helper.c"],
      "includePaths": ["native/mathkernel/include"],
      "libraries": ["m"],
      "flags": ["-O3"]
    }
  ]
}
npx relaxnative build

This writes compiled libraries + parsed bindings under .relaxnative/prebuilt/ (commit/ship this directory). At runtime, loadNative() transparently prefers a prebuilt artifact when one exists, is for the current platform, and matches the source's current content hash — skipping compilation entirely:

// No compiler needed here if native/add.c was prebuilt for this platform.
const mod = await loadNative('native/add.c', { isolation: 'in-process' });

Notes:

  • Prebuilt libs are platform-specific — run relaxnative build on/for each target platform you ship.
  • If a source changes after building, the hash no longer matches and Relaxnative falls back to compiling (dev workflow). Re-run relaxnative build to refresh.
  • Force-disable the AOT fast path with RELAXNATIVE_NO_PREBUILT=1.
  • Programmatic API: prebuildSource(source, opts), findPrebuilt(source, projectRoot, platformTag), readPrebuiltIndex(projectRoot).

Tracing (RELAXNATIVE_TRACE)

When native code crashes or a worker/process boundary hides the real error, enable tracing.

RELAXNATIVE_TRACE=1 node index.js

More control:

  • RELAXNATIVE_TRACE=1 enables trace events.
  • RELAXNATIVE_TRACE_LEVEL=info|debug controls verbosity (default: info).

What you’ll see (examples):

  • Load lifecycle: loadNative.begin, loadNative.build.begin, loadNative.build.done, loadNative.done
  • Parsed signatures (debug): loadNative.bindings
  • Dispatch decisions: dispatch, isolation.worker.dispatch, isolation.process.call
  • Process helper lifecycle: isolation.process.helper.start|exit|error

Tip: RELAXNATIVE_TRACE_LEVEL=debug will print parsed function signatures.


API

loadNative(sourcePath, options?)

import { loadNative } from 'relaxnative';

const mod = await loadNative('native/add.c', {
  isolation: 'worker',
  config: {
    functionMode: { add: 'sync' },
    defaultMode: 'sync',
  },
});

Options:

  • isolation?: 'in-process' | 'worker' | 'process'
  • config?: { functionMode?: Record<string, 'sync'|'async'>; defaultMode?: 'sync'|'async' }

Notes:

  • Default isolation is worker.
  • In process isolation, calls are IPC-based and therefore async.

Native memory helpers

import { native } from 'relaxnative';

const buf = native.alloc(1024);
buf.write(Uint8Array.from([1, 2, 3]));
console.log(buf.address); // numeric pointer

Isolation modes

in-process

  • fastest
  • unsafe: native crashes take down your Node process

Example:

import { loadNative } from 'relaxnative';

const mod = await loadNative('native/add.c', { isolation: 'in-process' });
console.log(mod.add(1, 2));

worker

  • worker-thread dispatch for async calls
  • sync calls may execute directly for low overhead

Example (async heavy work):

// native/heavy.c
// @async
int heavy(int n) {
  long x = 0;
  for (int i = 0; i < n * 10000000; i++) x += i;
  return (int)x;
}
import { loadNative } from 'relaxnative';

const mod = await loadNative('native/heavy.c', { isolation: 'worker' });
const result = await mod.heavy(5);
console.log(result);

process

  • forked helper process
  • crash containment
  • best-effort Node runtime guards (module import denial for fs/network/spawn)
  • call timeout enforcement (kills helper)

Example:

import { loadNative } from 'relaxnative';

// Process isolation always returns async wrappers.
const mod = await loadNative('native/heavy.c', { isolation: 'process' });
const result = await mod.heavy(5);
console.log(result);

Annotations

Relaxnative reads annotations from up to 3 lines above a function definition.

Supported:

  • @sync
  • @async
  • @cost low|medium|high

Annotation + isolation quick rules

  • @sync + in-process: fastest, but a crash kills your app.
  • @sync + worker: may execute directly (fast) unless the binding is marked async/high-cost.
  • @async + worker: always goes through the worker thread and returns a Promise.
  • process isolation: always async, regardless of annotations (IPC boundary).

What annotations mean

  • @sync
    • The JS wrapper returns a plain value.
    • In worker isolation, this may still execute on the main thread for low overhead.
    • Best for quick, safe-ish functions (or when you explicitly accept crash risk in in-process).
  • @async
    • The JS wrapper returns a Promise.
    • In worker isolation, the call always goes through a worker thread.
    • Best for CPU-heavy work where you don't want to block the event loop.
  • @cost low|medium|high
    • A hint used for readability and future scheduling heuristics.
    • Today it doesn't change performance by itself, but it's useful documentation.

C/C++ example

// @async
// @cost high
int heavy(int n) {
  long x = 0;
  for (int i = 0; i < n * 10000000; i++) x += i;
  return (int)x;
}

Rust example

// @sync
#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
    a + b
}

Types & FFI contract

Relaxnative parses your function signatures and maps them to FFI types. This is intentionally conservative: if we don't recognize a type, we fail fast.

Core rule of thumb

  • Scalars (like int, double, uint32_t) map to JS number; 64-bit ints (int64_t/uint64_t/size_t) may come back as a bigint and are accepted as number or bigint.
  • bool maps to a JS boolean.
  • Pointers (like double*, uint32_t*) map to one of:
    • a TypedArray (preferred when available)
    • a NativeBuffer / NativePointer (from native.alloc(...))

Supported scalar C types

  • bool / _Bool
  • int, unsigned int, short / unsigned short
  • long / unsigned long (platform width — 4 bytes on Windows LLP64), long long / unsigned long long
  • char / signed char / unsigned char
  • float, double
  • size_t
  • fixed-width ints: int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t
  • void (return only; a lone foo(void) parameter list = no arguments)

Type mapping across languages

| Concept | C / C++ | Rust | Zig | Go (cgo) | |---------|---------|------|-----|----------| | 32-bit int | int / int32_t | i32 | i32 | C.int / int32 | | 64-bit int | int64_t | i64 | i64 | int64 / C.longlong | | unsigned 64 | uint64_t | u64 | u64 | uint64 / C.ulonglong | | pointer-width | size_t | usize | usize | int / uintptr | | float / double | float / double | f32 / f64 | f32 / f64 | float32 / float64 | | bool | bool | bool | bool | bool | | byte buffer | uint8_t* | *mut u8 | [*]u8 | *byte | | typed pointer | int32_t* | *mut i32 | [*]i32 | *int32 | | C string | const char* | *const i8 | [*c]const u8 | *C.char |

Supported pointer forms

  • uint8_t* / unsigned char* treated as byte buffers
    • Pass a Uint8Array (recommended)
    • Or pass a numeric pointer from native.alloc()
  • Typed pointers: uint32_t* becomes pointer<uint32_t> internally
    • Pass a Uint32Array directly

Strings

  • const char* parameters are treated as cstring.
    • JS side: pass a JS string.
  • char*/const char* returns are treated as cstring.
    • JS side: expect a JS string.

Example: buffer in + buffer out

// @sync
void xor_u8(const uint8_t* a, const uint8_t* b, uint8_t* out, int n) {
  for (int i = 0; i < n; i++) out[i] = a[i] ^ b[i];
}
import { loadNative } from 'relaxnative';

const { xor_u8 } = await loadNative('native/xor.c', { isolation: 'worker' });
const a = new Uint8Array(1024);
const b = new Uint8Array(1024);
const out = new Uint8Array(1024);
xor_u8(a, b, out, out.length);

Example: histogram output (typed pointer)

// @async
void histogram_u8(const uint8_t* data, int n, uint32_t* out256) {
  for (int i = 0; i < 256; i++) out256[i] = 0;
  for (int i = 0; i < n; i++) out256[data[i]]++;
}
import { loadNative } from 'relaxnative';

const { histogram_u8 } = await loadNative('native/histogram.c', { isolation: 'worker' });
const data = new Uint8Array(1024 * 1024);
const out = new Uint32Array(256);
await histogram_u8(data, data.length, out);

If you see a type error like Unexpected Uint32Array value, expected number, it usually means the C signature was parsed as a generic pointer instead of a typed pointer. Prefer fixed-width types like uint32_t*.


Benchmarks (measured)

Native code isn't automatically faster — every FFI call has a fixed cost (~microseconds). Native wins when a call does enough work to amortize that, and wins big on things JS is bad at (64-bit integers, raw memory). It loses on trivial, high-frequency calls where V8 would just inline the work.

Measured on one Windows dev machine, in-process isolation (lowest overhead), warmup + fixed iterations. Absolute numbers vary by machine/load — the relative speedups are the point.

By workload shape — native (C via zig cc) vs JS

| Objective | JS calls/s | Native calls/s | Speedup | Note | |-----------|-----------:|---------------:|:-------:|------| | Call overhead (a+b) | 22,372,466 | 277,360 | 0.01× | FFI cost dominates; JS inlines — don't cross FFI for trivial ops | | Float loop (Leibniz π, 20k) | 5,346 | 10,934 | 2.05× | native edges out the JIT on hot float loops | | 64-bit int (xorshift, 4k) | 588 | 31,377 | 53× | JS must use slow BigInt; native uses raw u64 | | Buffer scan (sum 1 MiB) | 334 | 4,695 | 14× | raw memory, no bounds checks |

Same kernel across languages — u64 xorshift vs JS

| Language | calls/s | Speedup vs JS | |----------|--------:|:-------------:| | JS (V8, BigInt) | 1,945 | 1.00× | | Rust | 28,503 | 14.7× | | Go | 40,560 | 20.9× | | C (zig cc) | 42,242 | 21.7× | | Zig | 45,304 | 23.3× |

Real-world kernels — native (C via zig cc) vs best JS (TypedArray, same algorithm)

| Kernel | JS ms/op | Native ms/op | Speedup | Native throughput | |--------|---------:|-------------:|:-------:|-------------------| | Image grayscale (RGBA→gray, 2.1 MP) | 7.61 | 1.14 | 6.7× | ~1,800 Mpx/s | | Matrix multiply (128×128 f32) | 4.21 | 2.07 | 2.0× | ~2.0 GFLOP/s | | Mandelbrot render (512², 100 it) | 25.67 | 18.01 | 1.4× | ~15 Mpx/s | | CRC32 checksum (1 MiB, table) | 3.16 | 2.39 | 1.3× | ~440 MB/s | | Dot product (1M × f64) | 1.68 | 1.52 | 1.1× | ~690 M elem/s |

Image/integer buffer work is the sweet spot; float-heavy kernels (Mandelbrot, dot) are closer because V8's JIT is strong there. These use in-process isolation — add per-call IPC cost for process isolation, so batch work into fewer, larger calls.

Takeaways

  • ✅ Great for: 64-bit integer math, byte/buffer processing, tight numeric kernels, batched work.
  • ⚠️ Break-even for: simple number-only float loops (V8's JIT is excellent).
  • ❌ Bad for: tiny functions called millions of times (FFI overhead swamps the work) — batch instead.

Reproduce with the CLI (bench) or the in-repo suites (benchmark.report.test.ts, benchmark.multilang.test.ts).


When to use Relaxnative (good fits)

Relaxnative shines when you have large batches of work and the native call does enough computation to amortize the FFI overhead.

Good fits:

  • CPU-bound kernels on large arrays (SIMD-able loops)
    • image/audio primitives, DSP, analytics kernels, checksums/hashing
  • tight numeric loops (matmul-ish, dot, saxpy) where JS becomes the bottleneck
  • code you already have in C/C++/Rust and want to reuse from Node
  • isolating risky/3rd-party native code in process mode with best-effort guards

When not to use it (bad fits)

Avoid Relaxnative when:

  • you're calling a native function many times with tiny inputs (per-call overhead dominates)
  • the work is IO-bound (files/network); native won't magically make IO faster
  • you need a strict sandbox (process guards are not a syscall-enforced sandbox)
  • your function depends on complex C structs/callbacks (today's type support is intentionally small)
  • the native code isn't deterministic/pure and can corrupt process memory

CLI

npx relaxnative --help

Diagnostics

npx relaxnative doctor

Reports every detected toolchain (C, C++, Rust, Zig, Go), worker-thread support, and cache health.

Build (ahead-of-time)

# compile specific sources for compiler-free runtime
npx relaxnative build native/add.c native/kernel.zig

# or use relaxnative.build.json { "targets": [...] }
npx relaxnative build

See Ahead-of-time builds.

Native test harness

npx relaxnative test native/examples --isolation worker
npx relaxnative test native/examples --isolation process

Test signatures:

  • int test_name()0 pass, non‑zero fail
  • const char* test_name()NULL/"" pass, non‑empty message fail

Benchmarks

npx relaxnative bench examples/add.c add --traditional
npx relaxnative bench examples/loop.c loop_sum --traditional --iterations 5 --warmup 1
npx relaxnative bench examples/buffer.c sum_u8 --traditional --iterations 3 --warmup 1
npx relaxnative bench examples/dot.c dot_f64 --traditional --iterations 2 --warmup 1
npx relaxnative bench examples/saxpy.c saxpy_f64 --traditional --iterations 1 --warmup 1
npx relaxnative bench examples/matmul.c matmul_f32 --traditional --iterations 1 --warmup 1
npx relaxnative bench examples/xor.c xor_u8 --traditional --iterations 1 --warmup 1
npx relaxnative bench examples/crc32.c crc32_u8 --traditional --iterations 1 --warmup 1

Additional built-in demo baselines are provided for:

  • dot_f64 (dot product)
  • saxpy_f64 (vector kernel)
  • matmul_f32 (naive matrix multiply)
  • xor_u8 (buffer XOR)
  • crc32_u8 (checksum)
  • histogram_u8 (analytics/image primitive)

Benchmark results:

  1. A Simple Vector Kernal
❯ npx relaxnative bench examples/saxpy.c saxpy_f64 --traditional --iterations 1 --warmup 1
traditional-js (baseline)
  iterations: 1 (warmup 1)
  calls/sec:   49.636
  avg ms:      19.818
  min ms:      19.818
  max ms:      19.818

Speedup vs baseline (higher is better)
  sync:   364.94x
  worker: 368.56x

saxpy_f64 (sync)
  iterations: 1 (warmup 1)
  calls/sec:   18114.301
  avg ms:      0.036
  min ms:      0.036
  max ms:      0.036

saxpy_f64 (worker)
  iterations: 1 (warmup 1)
  calls/sec:   18293.575
  avg ms:      0.025
  min ms:      0.025
  max ms:      0.025
  1. Matrix Multiplication
❯ npx relaxnative bench examples/matmul.c matmul_f32 --traditional --iterations 1 --warmup 1
traditional-js (baseline)
  iterations: 1 (warmup 1)
  calls/sec:   5.829
  avg ms:      171.228
  min ms:      171.228
  max ms:      171.228

Speedup vs baseline (higher is better)
  sync:   1240.74x
  worker: 2710.71x

matmul_f32 (sync)
  iterations: 1 (warmup 1)
  calls/sec:   7232.070
  avg ms:      0.112
  min ms:      0.112
  max ms:      0.112

matmul_f32 (worker)
  iterations: 1 (warmup 1)
  calls/sec:   15800.284
  avg ms:      0.025
  min ms:      0.025

Cache

npx relaxnative cache status
npx relaxnative cache clean

Registry (RelaxRegistry)

Install local packages (offline, deterministic):

npx relaxnative add file:examples/registry/fast-matrix
npx relaxnative list
npx relaxnative remove fast-matrix

Trust levels

relax.json:

  • trust: "local" | "community" | "verified"

Behavior:

  • local → trusted by the author, no prompts.
  • community → warning + confirmation (once; decision content-pinned in native/registry/.trust.json). Elevated permissions (fs/net/spawn) are refused and isolation is clamped to process.
  • verified → elevated trust, only when a real signature is verified (see below). Otherwise it is safely demoted to community (a bare digest is forgeable).

Trust is content-pinned: it stores the package's content digest, so a re-published name@version with different bytes re-prompts instead of being silently trusted.

Verified signature (Ed25519)

A verified package carries a signature covering the manifest and every source file's content:

{
  "trust": "verified",
  "registrySignature": {
    "alg": "sha256",
    "digest": "<sha256 over canonical manifest + per-source content hashes>",
    "sources": { "add.c": "<sha256>" },
    "keyId": "relaxnative-registry-2026",
    "signature": "<base64 Ed25519 over the digest>"
  }
}
  • Integrity (digest): recomputed at install; any tampering of the manifest or a source fails the install. Canonicalized so formatting/key-order can't change it.
  • Authenticity (keyId + signature): an Ed25519 signature verified against a pinned public key. Only a valid signature grants verified privileges. Pinned keys come from a bundled set or RELAXNATIVE_TRUSTED_KEYS (JSON {"<keyId>":"<SPKI PEM>"}).
  • Sign packages with the exported signPackage(pkgDir, { keyId, privateKey }) helper.

The low-level installPackageRaw is intentionally not part of the public API — all installs go through installPackageEnforcingTrust, so there's no path that skips consent/permission gating.


Express example

mkdir my-app
cd my-app
npm init -y
npm i express relaxnative

native/loop.c

// @sync
long loop_sum(long n) {
  long x = 0;
  for (long i = 0; i < n; i++) x += i;
  return x;
}

server.mjs

import express from 'express';
import { loadNative } from 'relaxnative';

const app = express();
const native = await loadNative('native/loop.c', { isolation: 'worker' });

app.get('/sum', (req, res) => {
  const n = Number(req.query.n ?? 1_000_000);
  res.json({ n, v: native.loop_sum(n) });
});

app.listen(3000, () => console.log('http://localhost:3000'));

Developer documentation

High-level structure:

  • src/loader.ts — compile + parse + bind + wrap
  • src/compiler/* — compiler detection + cached compilation
  • src/parser/* — Tree-sitter parsing + annotations
  • src/ffi/* — koffi binding generation
  • src/worker/* — worker/process isolation
  • src/registry/* — registry installer + trust enforcement

Debug flags:

  • RELAXNATIVE_DEBUG=1
  • RELAXNATIVE_TRACE=1 (prints extra call tracing; useful for debugging segfaults)

LLM Prompt (Architecture + code generation)

Copy/paste this prompt into ChatGPT / Claude / Copilot Chat when you want the model to plan and scaffold an app using Relaxnative.

Prompt

You are a Senior Node.js + Native Systems Engineer.

I’m using the Relaxnative library for Node.js, which provides:

  • loadNative(path, { isolation }) to compile+load .c/.cpp/.rs
  • isolation modes: in-process, worker, process
  • native annotations: @sync, @async, @cost low|medium|high
  • a CLI (relaxnative doctor/test/bench/cache/add/list/remove)
  • RelaxRegistry packages with supply-chain trust levels: local, community, verified
  • a small runtime safety guard layer in process isolation for permissions/timeouts

You must follow these rules:

  • Prefer fixed-width types in C signatures (uint32_t, uint8_t, etc.) to avoid ambiguity.
  • For bulk data, prefer TypedArrays (Uint8Array, Float64Array, Uint32Array) over lists.
  • Avoid tiny-call micro-optimizations; solve performance by batching and reducing call count.
  • If you can crash Node (native code!), default to isolation: 'process' during development.

My question/problem:

Your output must include:

  1. Feasibility & fit
  • Is this a good use case for Relaxnative? If no, explain briefly and propose a safer alternative.
  • Identify which parts should remain in JS and which should become native.
  1. Isolation + security defaults
  • Choose an isolation mode and justify it.
  • If 3rd-party code is involved, use process isolation and explain trust levels.
  • Propose a relax.json permissions/limits policy if packaging a RelaxRegistry module.
  1. Native API design contract
  • Function signatures (C or Rust) with types suitable for FFI.
  • How data buffers/arrays are passed (TypedArray ↔ pointer address / NativeBuffer).
  • Error-handling strategy (return codes, sentinel values, etc.).
  1. Implementation plan
  • Step-by-step tasks (files to create, where they live).
  • A minimal working prototype first, then optimizations.
  1. Code generation
  • Provide:
    • native source file(s) with Relaxnative annotations
    • the Node/TS loader code using loadNative()
    • a benchmark command using npx relaxnative bench ... --traditional
    • optional: a test using Vitest
  1. Performance checklist
  • Specify what to measure and how.
  • Identify what sizes/iteration counts are needed to overcome FFI overhead.

Constraints:

  • Use ESM syntax.
  • Prefer deterministic builds and offline-friendly behavior.
  • Keep the first version simple and correct.

Minimal copy/paste prompt (for ChatGPT / Claude)

Paste this when you want an LLM to generate a Relaxnative kernel:

You are a Senior Node.js + C/Rust engineer. Generate a Relaxnative native kernel.

Requirements:

  • Provide a .c (or .rs) file with exported functions.
  • Use annotations on the 1 lines above each function: @sync/@async and @cost low|medium|high.
  • Use fixed-width C types where possible: uint8_t, uint32_t, int32_t, etc.
  • For buffers, use uint8_t* and pass Uint8Array from JS.
  • For uint32_t* outputs, pass Uint32Array from JS.
  • Provide a Node ESM usage snippet using loadNative(path, { isolation: 'worker' }).
  • Provide a benchmark command using: npx relaxnative bench <file> <fn> --traditional.
  • Include a quick correctness test (Vitest preferred). Example:
import { loadNative } from 'relaxnative';

const { add } = await loadNative('./add.so', { isolation: 'worker' });

// Test the native function
test('add', () => {
  expect(add(1, 2)).toBe(3);
});
#include <stdint.h>

// Example native function
@sync @cost low
uint32_t add(uint32_t a, uint32_t b) {
  return a + b;
}

Support ☕

If you found this project helpful, consider buying me a coffee!

Buy Me A Coffee

License

MIT © Ravi Kishan Portfolio