Para
Para is two things: a set of TypeScript libraries (signals, parallel,
pipeline, arena, simd, csv, arrow,
rtp, mcp) and an optional .pts syntax that compiles to JS calls into
them. The libraries are pure JS / Wasm and work in any JS runtime.
ParaBun is a separate runtime (Bun fork) that bundles Para and adds modules for GPU, camera, audio, GPIO/I²C/SPI, and on-device LLM.
Lib + Lang
Para Lib
Nine modules under the para:* import prefix. para:signals provides reactive cells,
derived values, and effects. The other modules use it to expose their state — para:audio's
capture stream has a peakLevel signal, para:gpio's line value is a signal, and so
on. So effect() from para:signals works against any of them.
Each module is its own @para/* npm package — install only the ones you use.
Install guide → · Module docs →
Para Lang
.pts / .pjs files compile to standard JavaScript that imports from Para Lib. Added
forms:
pure/memofunction declarators-
signal x = 0/effect { … }/x ~> y/x -> fn(reactive bindings) defer EXPR/arena { … }blocks|>pipelines, integer ranges (0..n,0..=n)..!/..&chained.catch/.finally
The reactive forms desugar to para:signals imports, so the language requires at least that
module at runtime. The other libraries' JS APIs work without the language. Compiler today is bundled in
ParaBun; standalone @para/transpile in development.
Language reference →
Libraries (Para Lib)
Signals — reactive state
Cells, derived values, effects. The other libraries expose their state as signals, so reactive composition is uniform across the suite.
import { signal, derived, effect } from "@para/signals";
const count = signal(0);
const doubled = derived(() => count.get() * 2);
effect(() => console.log(count.get(), doubled.get())); // 0, 0
count.set(5); // logs: 5, 10
count.update(n => n + 1); // logs: 6, 12
Parallel — work over a Worker pool
pmap / preduce ship pure functions to a persistent Worker pool via
fn.toString(). Functions must be pure — no closures, no outer references.
import { pmap } from "@para/parallel";
function score(row) {
let h = 0;
for (let i = 0; i < row.length; i++) h = (h * 31 + row.charCodeAt(i)) | 0;
return h * h;
}
const scores = await pmap(score, rows, { concurrency: 8 });
Pipeline — fused streaming combinators
Adjacent maps over Float32Array / Float64Array sources fuse into a single
SIMD pass via @para/simd instead of allocating an intermediate per stage.
import { map, sum } from "@para/pipeline";
const arr = new Float32Array(1_000_000);
const total = await sum(map(x => x * 2)(map(x => x + 1)(arr)));
// One SIMD pass through @para/simd, not three.
SIMD — vector primitives over typed arrays
WebAssembly v128 kernels (simd.wasm) with a scalar JS fallback for hosts without v128.
import { add, dot, sum, mulScalar } from "@para/simd";
const a = new Float32Array([1, 2, 3, 4]);
const b = new Float32Array([5, 6, 7, 8]);
add(a, b); // Float32Array([6, 8, 10, 12])
mulScalar(a, 3); // Float32Array([3, 6, 9, 12])
dot(a, b); // 70
sum(a); // 10
Arrow — in-memory tables, IPC, Parquet
Columnar tables with vectorized computes, Arrow IPC streaming, Parquet read/write. Wire-compatible with apache-arrow 21.1.0.
import { fromRows, mean, toParquet } from "@para/arrow";
const t = fromRows([
{ id: 1, age: 30 },
{ id: 2, age: 25 },
]);
mean(t.column("age")); // 27.5
const buf = await toParquet(t, { compression: "snappy" });
Syntax (Para Lang)
Signals and effects
A signal declaration creates a reactive cell. Bare reads inside a tracked context (an
effect, a derived, a when block, or another signal's RHS) compile to
.get(). Bare writes compile to .set(). A signal whose initializer reads other signals
is auto-promoted to a derived value.
signal count = 0;
signal doubled = count * 2; // derived; recomputes when count changes
effect { console.log(doubled); } // runs once now, again on each change
count++; // count.set(count.get() + 1)
Edge-triggered handlers
A when block fires its body once on each false→true transition of the predicate.
when not fires on the true→false transition. The predicate is tracked the same way an effect
body is.
signal score = 0;
when score >= 100 { unlockAchievement("century"); }
when not online { showOfflineBanner(); }
Reactive bindings
A ~> B desugars to effect(() => { B = A; }), an assignment that stays in sync.
A -> fn desugars to effect(() => { fn(A); }), a call binding. Both are shorthand
for the common single-statement effect.
signal name = "world";
name ~> document.title; // title tracks name
name -> console.log; // logs on every change
Ranges and pipelines
a..b is an exclusive integer range; a..=b is inclusive. The pipeline operator
|> threads a value through a sequence of unary calls.
for (const i of 0..n) work(i);
const evens = 0..=20 |> filter(i => i % 2 === 0);
const out = pixels |> map(p => p * 1.2) |> clamp(0, 255);
Error operators
..! is .catch. ..& is .finally. They chain naturally
with await to flatten a try/catch block.
const data = await fetch(url).then(r => r.json())
..! err => defaults
..& () => spinner.hide();
Compilation
Para files (.pts) are parsed by ParaBun's transpiler.
(Mainline Bun doesn't recognize the syntax — the parser additions are part of the ParaBun fork.) The output is
standard JavaScript with a handful of imports from para:* module specifiers.
On ParaBun, those specifiers resolve to built-in modules. On any other host (browser, Node, Bun, Deno,
Cloudflare Workers, …) alias them to the matching @para/* npm packages in your bundler.
// vite.config.ts
import { defineConfig } from "vite";
export default defineConfig({
resolve: {
alias: [{ find: /^para:(.*)$/, replacement: "@para/$1" }],
},
});
The same one-line alias works for esbuild, webpack, and rollup. See the install guide for the variants.
ParaBun is required for the build step today (it owns the .pts parser). A standalone
npm-installable transpiler (@para/transpile) is on the roadmap.
Examples
Three worked projects, one per host environment. Each is a complete project with file layout, source, and build commands.