A small, fast, batteries-included JavaScript runtime.
A QuickJS fork with a native, SIMD-accelerated standard library — no npm, no node_modules, no supply chain.
Quick start · Standard library · Numbers · Why · The Guide · API Reference
DynaJS starts in microseconds, idles in a couple of megabytes, and ships a curated set of
native modules — crypto, HTTP, files, compression, vector math, machine learning, data structures —
compiled into the binary and reachable with one import. It began as a fork of Fabrice Bellard's
QuickJS (release 2026-06-04) and grew the standard library a real
runtime needs, without adopting a package ecosystem.
Important
A deliberate stance. DynaJS does not and will not implement Node.js compatibility — no
require, no node: modules, no npm. Instead, the functionality of the most-used packages is
brought in as first-party native modules under the dyna: namespace: audited, curated,
SIMD-accelerated, dependency-free, and re-arranged for DynaJS (not one-to-one clones).
# Clone, build with the full native standard library, and install (macOS / Linux / FreeBSD).
# Re-run any time to upgrade or repair — it overrides the previous install.
./install.sh # → /usr/local/bin/dynajs (or ~/.local/bin without sudo)
./install.sh --prefix "$HOME/.local"
./install.sh --uninstallRequires git, make, and a C compiler (clang preferred). Pass --with-deps to let the
installer fetch them via your package manager.
dynajs hello.js # run a file
dynajs -e 'print(1+1)' # eval
dynajs -i # REPLTrain a model on some data and serve predictions over HTTP — two native modules working together, no dependencies, no build step:
import { LinearRegression } from "dyna:ml";
import { App } from "dyna:http";
// 1. Tabular training data for y = 2x + 1.
const X = [[1], [2], [3], [4]];
const y = [3, 5, 7, 9];
// 2. Fit a model natively (vectorised) — recovers y = 2x + 1.
const model = new LinearRegression();
model.fit(X, y);
// 3. Serve predictions as a strict JSON-RPC 2.0 service. Your handler is plain
// JavaScript and runs on the single-thread reactor (kqueue / epoll / io_uring).
const app = new App({ port: 8080 });
app.rpc("/predict", {
predict: ([x]) => Math.round(model.predict([[x]])[0]),
});
app.start(); // folds the reactor into the event loop; serves until killedcurl -sd '{"jsonrpc":"2.0","id":1,"method":"predict","params":[10]}' :8080/predict
# → {"jsonrpc":"2.0","result":21,"id":1}No package.json, no install step, no build — the capabilities are in the binary.
Build with CONFIG_NATIVE_MODULES=y (the installer does this) to get a curated native standard
library — one import each, no dependencies.
Text & bytes
| Module | What it gives you |
|---|---|
dyna:strings |
Go-style string utilities (split/fields/trim/pad/title/replace/equalFold), SIMD substring search (index/indexOfAll/contains/count) and a compiled Matcher |
dyna:bytes |
Byte-buffer ops (compare/search/copy/fill) + read/write every int & float width in LE and BE + the UTF‑8 boundary |
dyna:encoding |
Every binary-to-text codec: hex, base64 / base64url, base32, Ascii85, LEB128 var-ints |
dyna:text |
SIMD text kernels: UTF‑8 validate/count, Latin‑1↔UTF‑8, UTF‑8↔UTF‑16 |
dyna:csv |
File-oriented CSV CRUD (create / read / edit rows & columns), RFC 4180, mmap + atomic writes |
Math, crypto & identity
| Module | What it gives you |
|---|---|
dyna:simd |
Multi-ISA vector math over typed arrays (f32/f64/i32): dot/norm/distance/GEMM/activations/scans |
dyna:ml |
14 model families: linear/logistic regression, kNN, decision trees, random forests, gradient boosting, kernel SVM, naive Bayes, k-means, DBScan, Gaussian mixtures, PCA, scalers, metrics |
dyna:mathx |
Special functions (gamma/erf/hypot) + exact integer math (gcd/lcm/factorial/isPrime, BigInt) |
dyna:bits |
Go math/bits: leading/trailing zeros, popcount, rotate, 64‑bit carry arithmetic |
dyna:crypto |
SHA‑1/224/256/384/512, MD5, HMAC, CRC‑32/32C, and a streaming Hasher |
dyna:random |
A fast, seedable PRNG (reproducible streams) |
dyna:uuid |
RFC 9562 UUIDs: v4, v7, v3/v5, parse/validate |
I/O, system & networking
| Module | What it gives you |
|---|---|
dyna:http |
An HTTP client and App — typed routes (JSON-RPC, static sendfile, upload, WebSocket) on a single-thread kqueue/epoll/io_uring reactor |
dyna:file |
Filesystem: buffered reader/writer (per-OS fast paths) + metadata, dirs, glob, links, temp |
dyna:uring |
High-queue-depth bulk file reads via Linux io_uring |
dyna:path |
POSIX path manipulation (join/resolve/normalize/dirname/relative) |
dyna:sys |
Process & environment (env, args, cwd, platform, pid, hostName, homeDir) |
dyna:netip |
Typed IPv4/IPv6 addresses, CIDR prefixes and ranges |
dyna:time |
Nanosecond durations, a monotonic clock, RFC 3339 formatting |
dyna:semver |
SemVer 2.0.0 parsing/comparison and the full npm range grammar |
dyna:compress |
gzip / gunzip (a real DEFLATE implementation) |
Data structures
| Module | What it gives you |
|---|---|
dyna:structures |
What JS has no builtin for: Heap, BitSet, UnionFind, Deque, List, Fenwick, SegTree, RingBuffer, BloomFilter, Trie, LRU, SortedSet/SortedMap |
dyna:graph |
A native Graph with BFS/DFS/Dijkstra/Bellman-Ford/Floyd-Warshall/topo-sort/components/MST/A* as methods |
Every example in the docs is real and runs. See the API Reference for complete signatures.
The dyna:* modules are opt-in imports. The other half of the batteries needs no import at all:
DynaJS bakes the non-ECMAScript methods of SugarJS 2.0 and RamdaJS 0.32 straight onto the
built-in prototypes — native C, non-enumerable, and careful to never shadow a standard method.
[1, 1, 2, 3, 3].dropRepeats(); // [1, 2, 3] — only adjacent dupes
[10, 9, 1, 2].sortBy(); // [1, 2, 9, 10] — numeric, not lexicographic
[1, 3, 5, 7, 9].sortedIndexOf(7); // 3 — O(log n) binary search
["a","b","c","d"].mapFromIndex(2, true, s => s); // ["c","d","a","b"] — start at 2, wrap
[[1,2],[3,4]].sequence(Array); // [[1,3],[1,4],[2,3],[2,4]] — cartesian product
Object.mergeDeepRight({a:{x:1}}, {a:{y:2}}); // {a:{x:1, y:2}}
(x => x+1).pipe(x => x*2, x => -x)(3); // -8 — point-free composition
new Date(2024,1,29).endOfMonth().getDate(); // 29 — Sugar dates, immutableHundreds of methods across Array/String/Number/Object/Function/Date, plus a Lens type
and Ramda transducers — all in the binary, all documented in the
API Reference → Built-in prototype extensions.
Measured, not estimated. Method and full tables are in bench/; each figure links to the
report that produced it.
HTTP, vs Node 26 — same JS-handler-per-request workload, Apple M1 Pro, macOS
(bench/REPORT.md):
| Connections | req/s | p99 | peak RSS | |
|---|---|---|---|---|
| 1 | DynaJS | 45,996 | 85µs | 1.8 MB |
| 1 | Node 26 | 29,894 | 152µs | 64.3 MB |
| 256 | DynaJS | 137,306 | 3.56ms | 2.0 MB |
| 256 | Node 26 | 61,242 | 93.5ms | 73.9 MB |
| 1024 | DynaJS | 136,787 | 8.54ms | 2.2 MB |
| 1024 | Node 26 | 59,377 | 767ms | 96.7 MB |
1.5×–2.3× the throughput at every concurrency, 36×–45× lower RSS, and a tail that stays bounded where Node's explodes (p99 8.5ms vs 767ms at 1024 connections). RSS stays flat because the reactor uses one shared receive buffer rather than a per-connection allocation.
SIMD substring search — the same kernel the HTTP server uses for header scanning
(tests/test_strings_simd.js, 200 MiB scan):
| throughput | |
|---|---|
dyna:strings index() |
14,642 MiB/s |
String.prototype.indexOf |
475 MiB/s |
Machine learning — vectorising the models, before → after
(bench/ML_REPORT.md, bench/VECTORIZATION_AUDIT.md):
| operation | before | after | |
|---|---|---|---|
GaussianNB.predict 20000×128 |
26.18 ms | 2.08 ms | 12.6× |
GaussianMixture.fit 4000×128 |
200.8 ms | 39.2 ms | 5.1× |
LinearRegression.fit 4000×128 |
18.65 ms | 5.15 ms | 3.6× |
KMeans.fit 5000×128 k=8 |
11.60 ms | 4.45 ms | 2.6× |
Note
Two findings from that work are worth more than the speedups, and both are written up in
bench/: the shared SIMD dispatch-table kernels lost to portable multi-accumulator C (an
indirect call can't be inlined or fused into its caller), and two hot loops weren't vectorised at
all because they called log() inside the loop body — which no amount of source tuning can fix.
Verified by reading the generated assembly, not by assuming.
- ⚡ Instant startup, low memory — an interpreter with no JIT warmup and a tiny baseline. Ideal for CLI tools, edge/serverless cold starts, embedding, and short-lived workers.
- 📉 Predictable, flat memory — reference-counting GC frees promptly; classes that own a
descriptor, socket or large buffer dispose deterministically via
close()/[Symbol.dispose](), so RSS stays flat under load. - 🧮 Native SIMD from JavaScript — a verified, cross-ISA (NEON/SSE4.2/AVX2/AVX‑512/SVE) kernel set, no native-addon build step. Dual-use: the same kernels accelerate the engine internally.
- 📦 A dependency-free standard library — what ships in the binary is what runs. No
npm audit, no lockfile drift, no transitive dependency trees. - 🔬 Evidence over adjectives — every performance claim in this repo has a benchmark behind it, and the reports record the changes that were tried and reverted as well as the ones that landed.
Honest boundaries — where DynaJS is the wrong tool
- Long numeric hot loops. A tracing JIT (V8/JSC) out-throughputs an interpreter once a loop runs for minutes. DynaJS's answer is native SIMD kernels for the heavy lifting, not out-JIT'ing V8.
- You need npm. There is no compatibility layer and there will not be one. If your value is in the ecosystem, use Node or Bun.
- AVX-512. Certain kernels are conservatively gated pending verification on real AVX-512 silicon; the runtime falls back to the verified AVX2 path. DynaJS ships the proven path.
- io_uring is opt-in (
CONFIG_IO_URING) and measured slower than epoll for small JS-handler-bound requests — its win is bulk transfer. Seebench/IO_TUNING_REPORT.md.
The complete book lives in docs/dynajs-guide/:
| Chapter | What's in it | |
|---|---|---|
| 1 | Introduction & Philosophy | What DynaJS is, the QuickJS lineage, positioning against Node/Bun/Go |
| 2 | Installation & First Steps | Building, the CLI, the REPL, your first programs, the dyna: namespace |
| 3 | The Language & the Runtime | The ES2023–2026 baseline, resource disposal, std/os, workers, BigInt |
| 4 | The Standard Library | Every dyna:* module with worked examples |
| ★ | API Reference | Every module, every signature, every throwing condition |
make CONFIG_NATIVE_MODULES=y -j"$(getconf _NPROCESSORS_ONLN)" # engine + native stdlib
make test # language test suites
make test-native # the dyna:* module suites
./dynajs -e 'print("ok")'make alone builds the core engine plus the classic std/os modules. Sanitizer builds:
make CONFIG_ASAN=y, make CONFIG_UBSAN=y. ./dev.sh gate runs the full proof — a zero-warning
build, ASan, UBSan, the test suites, and the test262 baseline. See
Chapter 2.
The QuickJS-lineage engine and the core language are mature — the project holds a fixed
ECMAScript test262 conformance baseline (58 failures out of 83,744, and every change re-runs it).
The standard library is solid and shipping: each module is verified against standard vectors or a
reference implementation, sanitizer-clean, and adversarially tested for reentrancy. It is also
growing — the "top npm functionality as first-party modules" plan is early. The docs flag landed
vs. planned explicitly, and never claim an unverified platform.
MIT. DynaJS is derived from QuickJS, © 2017‑2021 Fabrice Bellard and Charlie Gordon; see the license headers. DynaJS additions are under the same terms.