relaxnative
v0.2.0
Published
Zero-config native C/C++/Rust execution for Node.js
Maintainers
Readme
What you get
- Five languages: import a
.c,.cpp,.rs,.zig, or.gofile and call its exported functions from JS/TS. - Compile-on-demand: first import compiles; subsequent imports hit the cache.
- Ahead-of-time builds:
relaxnative buildcompiles 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 forverifiedand content-pinned trust.
Native code is inherently unsafe. Isolation and guards help, but they’re not a perfect sandbox.
Installation
npm i relaxnativeRequirements (per language you use):
- Node.js >= 18.
- C / C++:
clang,gcc, MSVCcl, orzig— one Zig install provideszig cc/zig c++(clang-based) and covers C, C++, and Zig. - Rust:
rustc(+cargo). - Zig:
zigonPATH(or set theZIGenv var to the binary). - Go:
go. On Windows, cross-compilation to a native DLL is done via WSL withgo+mingw-w64(x86_64-w64-mingw32-gcc) installed in the distro; or a native Windowsgo+ 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 relaxnativeOptional 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 startIf something goes wrong, re-run with tracing:
RELAXNATIVE_TRACE=1 npm start1) 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)); // 42Go 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). Installgoandgcc-mingw-w64-x86-64in 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
sourcesis compiled and linked together. - Rust — additional files are
mod-declared from the crate root and read byrustc; pointsourcePathat the root. - Zig — additional files are
@imported from the root source. - Go — all files form one
mainpackage; each file with an//exportmustimport "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 buildThis 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 buildon/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 buildto 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.jsMore control:
RELAXNATIVE_TRACE=1enables trace events.RELAXNATIVE_TRACE_LEVEL=info|debugcontrols 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
processisolation, 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 pointerIsolation 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.processisolation: always async, regardless of annotations (IPC boundary).
What annotations mean
@sync- The JS wrapper returns a plain value.
- In
workerisolation, 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
workerisolation, the call always goes through a worker thread. - Best for CPU-heavy work where you don't want to block the event loop.
- The JS wrapper returns a
@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 JSnumber; 64-bit ints (int64_t/uint64_t/size_t) may come back as abigintand are accepted asnumberorbigint. boolmaps to a JS boolean.- Pointers (like
double*,uint32_t*) map to one of:- a TypedArray (preferred when available)
- a
NativeBuffer/NativePointer(fromnative.alloc(...))
Supported scalar C types
bool/_Boolint,unsigned int,short/unsigned shortlong/unsigned long(platform width — 4 bytes on Windows LLP64),long long/unsigned long longchar/signed char/unsigned charfloat,doublesize_t- fixed-width ints:
int8_t,uint8_t,int16_t,uint16_t,int32_t,uint32_t,int64_t,uint64_t void(return only; a lonefoo(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()
- Pass a
- Typed pointers:
uint32_t*becomespointer<uint32_t>internally- Pass a
Uint32Arraydirectly
- Pass a
Strings
const char*parameters are treated as cstring.- JS side: pass a JS
string.
- JS side: pass a JS
char*/const char*returns are treated as cstring.- JS side: expect a JS
string.
- JS side: expect a JS
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
processmode 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 --helpDiagnostics
npx relaxnative doctorReports 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 buildSee Ahead-of-time builds.
Native test harness
npx relaxnative test native/examples --isolation worker
npx relaxnative test native/examples --isolation processTest signatures:
int test_name()→0pass, non‑zero failconst 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 1Additional 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:
- 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- 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.025Cache
npx relaxnative cache status
npx relaxnative cache cleanRegistry (RelaxRegistry)
Install local packages (offline, deterministic):
npx relaxnative add file:examples/registry/fast-matrix
npx relaxnative list
npx relaxnative remove fast-matrixTrust levels
relax.json:
trust: "local" | "community" | "verified"
Behavior:
local→ trusted by the author, no prompts.community→ warning + confirmation (once; decision content-pinned innative/registry/.trust.json). Elevated permissions (fs/net/spawn) are refused and isolation is clamped toprocess.verified→ elevated trust, only when a real signature is verified (see below). Otherwise it is safely demoted tocommunity(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 grantsverifiedprivileges. Pinned keys come from a bundled set orRELAXNATIVE_TRUSTED_KEYS(JSON{"<keyId>":"<SPKI PEM>"}). - Sign packages with the exported
signPackage(pkgDir, { keyId, privateKey })helper.
The low-level
installPackageRawis intentionally not part of the public API — all installs go throughinstallPackageEnforcingTrust, so there's no path that skips consent/permission gating.
Express example
mkdir my-app
cd my-app
npm init -y
npm i express relaxnativenative/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 + wrapsrc/compiler/*— compiler detection + cached compilationsrc/parser/*— Tree-sitter parsing + annotationssrc/ffi/*— koffi binding generationsrc/worker/*— worker/process isolationsrc/registry/*— registry installer + trust enforcement
Debug flags:
RELAXNATIVE_DEBUG=1RELAXNATIVE_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
processisolation 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:
- 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.
- Isolation + security defaults
- Choose an isolation mode and justify it.
- If 3rd-party code is involved, use
processisolation and explain trust levels. - Propose a
relax.jsonpermissions/limits policy if packaging a RelaxRegistry module.
- 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.).
- Implementation plan
- Step-by-step tasks (files to create, where they live).
- A minimal working prototype first, then optimizations.
- 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
- 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/@asyncand@cost low|medium|high.- Use fixed-width C types where possible:
uint8_t,uint32_t,int32_t, etc.- For buffers, use
uint8_t*and passUint8Arrayfrom JS.- For
uint32_t*outputs, passUint32Arrayfrom 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!
License
MIT © Ravi Kishan Portfolio

