A numeric library for Bun, implemented in Rust and exposed through bun:ffi. All numeric work happens in Rust — the TypeScript layer is a thin validated wrapper over the native binary.
Hot reductions (dot, sum, l1, squared-diff-sum, max-abs) use explicit SIMD — NEON on AArch64 (Apple Silicon and Linux arm64), AVX2/SSE2 on x86_64 chosen at runtime via CPU feature detection — with auto-vectorized scalar fallbacks on other targets.
bun add rustkitRequires Bun >= 1.4.
import { vector, matrix, stats } from "rustkit";
vector.add(new Float32Array([1, 2, 3]), new Float32Array([10, 20, 30]));
// Float32Array(3) [ 11, 22, 33 ]
matrix.mul(new Float32Array([1, 2, 3, 4]), new Float32Array([5, 6, 7, 8]), 2, 2, 2);
// Float32Array(4) [ 19, 22, 43, 50 ]
stats.mean(new Float32Array([1, 2, 3, 4]));
// 2.5Element-wise and reduction operations on Float32Array.
import { vector } from "rustkit";
vector.add(new Float32Array([1, 2]), new Float32Array([3, 4])); // [4, 6]
vector.dot(new Float32Array([1, 2, 3]), new Float32Array([4, 5, 6])); // 32
vector.norm(new Float32Array([3, 4])); // 5
vector.argsort(new Float32Array([30, 10, 20])); // [1, 2, 0]Also: sub, mul, div, cross, normalize, scale, argmin, argmax, sum, mean, lerp, clamp, abs, min, max, sqrt, reciprocal, l1Norm, lInfNorm, outer, argsort, sort.
Row-major Float32Array matrices with explicit dimensions.
import { matrix } from "rustkit";
matrix.mul(new Float32Array([1, 2, 3, 4]), new Float32Array([5, 6, 7, 8]), 2, 2, 2);
// [19, 22, 43, 50]
matrix.transpose(new Float32Array([1, 2, 3, 4, 5, 6]), 2, 3);
// [1, 4, 2, 5, 3, 6]
matrix.determinant(new Float32Array([1, 2, 3, 4]), 2); // -2
matrix.inverse(new Float32Array([1, 2, 3, 4]), 2); // [-2, 1, 1.5, -0.5]
matrix.eye(3); // [1, 0, 0, 0, 1, 0, 0, 0, 1]Also: add, sub, trace, scale, hadamard, frobeniusNorm, luDecompose, cholesky, eigenvalues.
Descriptive statistics over Float32Array.
import { stats } from "rustkit";
stats.mean(new Float32Array([1, 2, 3, 4])); // 2.5
stats.median(new Float32Array([3, 1, 2])); // 2
stats.variance(new Float32Array([1, 2, 3, 4, 5])); // 2
stats.correlation(new Float32Array([1, 2, 3]), new Float32Array([2, 4, 6])); // 1
stats.histogram(new Float32Array([1, 2, 3, 4, 5]), 5); // Uint32Array(5) [1, 1, 1, 1, 1]Also: stddev, percentile, quantile, covariance, zscore, mode, skewness, kurtosis, geometricMean, weightedMean, iqr.
Fixed-size bit sets backed by BigUint64Array.
import { bitset } from "rustkit";
const bits = bitset.create(128);
bitset.set(bits, 0);
bitset.set(bits, 127);
bitset.popcount(bits); // 2
bitset.nextSetBit(bits, 1); // 127Also: clear, toggle, and, or, xor, cardinality.
String similarity and matching algorithms.
import { string } from "rustkit";
string.levenshtein("kitten", "sitting"); // 3
string.hamming("abc", "axc"); // 1
string.longestCommonSubseq("abcde", "ace"); // 3
string.soundex("Robert"); // "R163"
string.jaroWinkler("martha", "marhta"); // ~0.961Also: fuzzyMatch, longestCommonSubstr, damerauLevenshtein, trigramSimilarity.
Geohash encoding, decoding, neighbors, and distance.
import { geohash } from "rustkit";
geohash.encode(48.8566, 2.3522, 6); // "u09tvw"
geohash.decode("u09tun"); // { lat: ~48.86, lng: ~2.29 }
geohash.distance(48.8566, 2.3522, 52.52, 13.405); // ~877 (km)
geohash.isValid("u09tun"); // trueAlso: neighbor, allNeighbors, bbox.
Non-cryptographic hashes (fast, deterministic).
import { crypto } from "rustkit";
crypto.crc32(new TextEncoder().encode("hello")); // 0x3610a686
crypto.xxhash64(new TextEncoder().encode("test")); // bigint
crypto.blake3(new TextEncoder().encode("hello")); // Uint8Array(32)
crypto.murmur3(new TextEncoder().encode("hello")); // 0x248bfa47Also: fnv1a.
Streaming sketches for quantiles, cardinality, and set similarity.
import { quantile } from "rustkit";
const digest = quantile.createTDigest();
quantile.tDigestAdd(digest, 1);
quantile.tDigestAdd(digest, 2);
quantile.tDigestAdd(digest, 3);
quantile.tDigestQuantile(digest, 0.5); // 1.5
const sketch = quantile.hyperloglogCreate(10);
quantile.hyperloglogAdd(sketch, new TextEncoder().encode("item"));
quantile.hyperloglogEstimate(sketch); // ~1Also: createCountMinSketch, countMinSketchAdd, countMinSketchQuery, createBloomFilter, bloomFilterInsert, bloomFilterContains, minhashCreate, minhashAdd, minhashSimilarity.
Distance and similarity metrics between vectors.
import { distance } from "rustkit";
distance.euclidean(new Float32Array([0, 0]), new Float32Array([3, 4])); // 5
distance.manhattan(new Float32Array([0, 0]), new Float32Array([3, 4])); // 7
distance.cosineSimilarity(new Float32Array([1, 0]), new Float32Array([5, 0])); // 1
distance.jaccardSimilarity(new Int32Array([1, 2, 3]), new Int32Array([2, 3, 4])); // 0.5Also: hammingDistance, chebyshev.
Fast Fourier transforms and spectral analysis.
import { fft } from "rustkit";
fft.rfft(new Float32Array([1, 2, 3, 4, 5, 6, 7, 8]));
// { real: Float32Array(5), imag: Float32Array(5) }
fft.convolve(new Float32Array([1, 2, 3]), new Float32Array([4, 5]));
// Float32Array(4) — length a + b - 1
fft.powerSpectrum(new Float32Array([1, 2, 3, 4, 5, 6, 7, 8]));
// Float32Array(5)Also: fft, ifft, irfft.
Information-theoretic measures over probability distributions.
import { entropy } from "rustkit";
entropy.shannonEntropy(new Float32Array([0.5, 0.5])); // 1
entropy.klDivergence(new Float32Array([0.5, 0.5]), new Float32Array([0.9, 0.1])); // > 0
entropy.crossEntropy(new Float32Array([0.5, 0.5]), new Float32Array([0.9, 0.1]));Also: mutualInformation.
Symmetric int8/int4 quantization with dequantization.
import { quantize } from "rustkit";
const { quantized, scale } = quantize.quantizeInt8(new Float32Array([0.5, -0.5, 1, -1]));
quantize.dequantizeInt8(quantized, scale); // ≈ original
const packed = quantize.quantizeInt4(new Float32Array([0.5, -0.5, 1, -1]));
// { quantized: Uint8Array(2), scale } — two 4-bit values per byteAlso: dequantizeInt4.
Runtime introspection: the loaded library's version, resolved platform, native binary path, and SIMD backend. Note that config is a named export, not a namespace.
import { config } from "rustkit";
config.version; // "0.1.0" — kept in sync with package.json and the Rust crates
config.platform; // "darwin-arm64"
config.binaryPath; // absolute path to the loaded librustkit_ffi binary
config.simd; // "neon" | "avx2" | "sse2" | "scalar"| Platform | Binary |
|---|---|
| macOS arm64 | darwin-arm64 |
| macOS x64 | darwin-x64 |
| Linux x64 (glibc) | linux-x64-gnu |
| Linux arm64 (glibc) | linux-arm64-gnu |
| Linux x64 (musl) | linux-x64-musl |
| Linux arm64 (musl) | linux-arm64-musl |
0.x: minor bumps may break the API. The API stabilizes toward 1.0.0.
- Call the TypeScript wrappers, not
dlopendirectly. The wrappers validate inputs; the Rust core asserts preconditions and a panic insideextern "C"aborts the process. - All vector operations are
f32(Float32Array). - Mutating operations (clamp, sort, zscore) never mutate the caller's array — the wrappers copy first.
The library is a five-layer pipeline. Every new function touches all five:
crates/rustkit-core/src/<module>/<op>.rs # 1. pure Rust algorithm
crates/rustkit-ffi/src/<module>.rs # 2. extern "C" wrapper (rk_<module>_<op>[_<type>])
src/native.ts # 3. dlopen symbol table entry
src/packages/<module>.ts # 4. validated TS wrapper
tests/<module>.test.ts # 5. tests
- Core — implement the algorithm in
crates/rustkit-core/src/<module>/<op>.rsand export it from the module'smod.rs. Keep it pure: no null checks, no FFI concerns. - FFI — add an
extern "C"wrapper incrates/rustkit-ffi/src/<module>.rsusing#[unsafe(no_mangle)](edition 2024 syntax). Null-check the pointers here — the core never does. Name itrk_<module>_<op>_<type>— the_<type>suffix applies where the data type is ambiguous (vector/matrix/stats/distance/fft/entropy/quantize); modules with a single obvious type (bitset/string/geohash/crypto/quantile/config) omit it. - Symbol table — register the symbol in
src/native.tswith the correct arg/return types (ptr,u64,float, etc.). - TS wrapper — add the validated wrapper in
src/packages/<module>.ts. Validate every user-supplied input here (lengths, bounds, ranges) and re-export fromsrc/index.tsif it's a new public entry point. - Tests — add tests in
tests/<module>.test.tscovering happy path, edge cases, and every validation throw. - Verify —
bun run compile:rs && bun test && bun run typecheck.
- Create
crates/rustkit-core/src/<module>/with amod.rsexporting each operation. - Register the module in
crates/rustkit-core/src/lib.rs. - Create
crates/rustkit-ffi/src/<module>.rsand register it incrates/rustkit-ffi/src/lib.rs. - Add a
native<Module>dlopen block insrc/native.tsand export it. - Create
src/packages/<module>.tsand re-export it fromsrc/index.ts. - Create
tests/<module>.test.ts. - Add the module to the README Modules section and the CHANGELOG.
- Validation invariant — every
assert!/assert_eq!in rustkit-core must have a corresponding input-validation throw in the TS wrapper (src/packages/*.ts) that fires first. Rust must never panic on user input through the published API. A panic insideextern "C"aborts the process. - All vector ops are
f32(Float32Array) — mismatched types silently corrupt memory. - In-place FFI ops (clamp, sort, zscore) mutate the Rust buffer directly; the TS wrapper copies first so the caller's array is never mutated.
- Null checks live in the FFI layer, never in core.
- SIMD — hot reductions (dot, sum, l1, squared-diff-sum, max-abs) live in
crates/rustkit-core/src/simd.rs: explicit NEON on AArch64, AVX2/SSE2 on x86_64 (runtime dispatch viais_x86_feature_detected!, nevertarget-cpu=native), scalar fallbacks elsewhere. Add new kernels there, not inline in algorithms.
bun run compile:rs # build the Rust cdylib (required before any test run)
bun test # TS test suite (coverage always on)
bun run typecheck # tsc --noEmit
bun run build # bundle dist/ (index.js + .d.ts)
bun run build:platforms # build all 6 platform binaries into platforms/
bun run verify:platforms # assert all 6 platform binaries exist
bun run smoke # pack + install tarball in a temp project + exercise all modules
bun run release # full release pipeline (dry-run; pass --publish to ship)Rust unit tests run separately: cargo test.
The npm package is published from this repo with a single command. The version is sourced from crates/rustkit-ffi/Cargo.toml — bump it there, and bun run release syncs package.json automatically.
- npm auth for local publishes:
npm login(or a valid token in~/.npmrc). Verify withnpm whoami. CI publishes use OIDC instead — no token needed (see below). cargo-zigbuildfor the Linux targets:brew install cargo-zigbuild(orcargo install cargo-zigbuild). Without it, the 4 Linux binaries are skipped and--publishwill fail the platform check.- The
rustkitname is already reserved on npm (0.0.0placeholder) — publishing a real version publishes over it.
bun run release # dry-run: build, verify, test, pack, smoke — no publish
bun run release --publish # the same, then publish to npmThe pipeline runs, in order:
sync:version— copy the Cargo.toml version intopackage.jsonbuild:platforms— build all 6 platform binaries intoplatforms/verify:platforms— hard-fail if any platform binary is missing (dry-run warns instead)bun test— full TS suitebuild— bundledist/smoke— pack the tarball, install it in a temp Bun project, exercise all 13 modulesnpm publish— only with--publish
Always run the dry-run first and inspect the output before shipping. The smoke step verifies the actual tarball, not the working tree.
Pushing a v*.*.* tag triggers .github/workflows/npm-publish.yml:
- Syncs the version from
crates/rustkit-ffi/Cargo.tomlintopackage.jsonand fails if it doesn't match the tag. - Builds all 6 platform binaries on a macOS arm64 runner (via
cargo-zigbuild), runs the audit/typecheck/test/build/smoke pipeline, then publishes to npm. - Publishing uses OIDC (
id-token: write) with npm provenance — noNPM_TOKENsecret required. One-time setup: register this repo as a Trusted Publisher for therustkitpackage on npmjs.com (Package → Access → Trusted Publishers → GitHub Actions). - A GitHub release with auto-generated notes is created from the tag.
CI (.github/workflows/ci.yml) runs the Rust + TypeScript test suites on Linux x64 and macOS arm64 for every push and pull request; CodeQL (.github/workflows/codeql.yml) analyzes the TypeScript layer. Local publishing (bun run release --publish) still works as before and uses your npm login.
MIT