Para

Para Lib

Eight @para/* npm packages: signals, parallel, arena, simd, csv, arrow, rtp, mcp. Pure JS / Wasm, no native deps. Runs on Node, Bun, Deno, browsers, Cloudflare Workers — anywhere V8 or JSC runs.

The optional .pts syntax desugars to imports from these packages. ParaBun bundles them and adds GPU / hardware modules.

Install

Each module is its own npm package. Install only the ones your code uses:

$npm install @para/signals @para/parallel @para/csv

Full per-bundler alias setup (Vite / esbuild / webpack) and the rest of the runtime + build instructions live in the install guide.

Libraries

Signals — a shopping cart that recomputes itself

Three signals (items, tax rate, shipping country) feed a chain of derived values; the only side-effect is one effect() that re-runs whenever any transitive dep changes. No subscriptions, no onChange handlers, no manual recompute.

import { signal, derived, effect } from "@para/signals";

type Item = { name: string; price: number };
const items    = signal<Item[]>([{ name: "book", price: 12 }, { name: "pen", price: 3 }]);
const country  = signal("US");
const taxRate  = derived(() => ({ US: 0.0825, JP: 0.10, NO: 0.25 })[country.get()] ?? 0);

const subtotal = derived(() => items.get().reduce((s, i) => s + i.price, 0));
const tax      = derived(() => subtotal.get() * taxRate.get());
const total    = derived(() => subtotal.get() + tax.get());

effect(() => console.log(`${items.get().length} items · $${total.get().toFixed(2)}`));

items.update(arr => [...arr, { name: "mug", price: 8 }]);   // 3 items · $24.88
country.set("NO");                                          // 3 items · $28.75
items.update(arr => arr.slice(1));                          // 2 items · $13.75

Parallel — find duplicate photos in a directory

Hashing 10k images on one thread sits the event loop. pmap ships the pure hash function to a persistent Worker pool (8 cores → 8 hashes in flight) and returns results in input order. No worker file, no postMessage wiring; the constraint is that the function must be pure — no closures, no outer references — because it's serialized via fn.toString().

import { pmap } from "@para/parallel";
import { readdir, readFile } from "node:fs/promises";

// pmap functions can't close over outer scope (they're shipped to the
// worker via fn.toString()). Pre-load bytes here, then send (path,
// bytes) pairs into the worker for hashing.
function hashBytes({ path, bytes }: { path: string; bytes: Uint8Array }) {
  let h = 0xcbf29ce484222325n;
  for (const b of bytes) h = ((h ^ BigInt(b)) * 0x100000001b3n) & 0xffffffffffffffffn;
  return { path, hash: h.toString(16) };
}

const files  = await readdir("./photos");
const inputs = await Promise.all(files.map(async name => ({
  path: `./photos/${name}`,
  bytes: new Uint8Array(await readFile(`./photos/${name}`)),
})));

const hashed = await pmap(hashBytes, inputs, { concurrency: 8 });

const groups = new Map<string, string[]>();
for (const { path, hash } of hashed) groups.set(hash, [...(groups.get(hash) ?? []), path]);

const dupes = [...groups.values()].filter(g => g.length > 1);
console.log(`${dupes.length} duplicate sets across ${files.length} files`);

CSV + Arrow — turn a 5 GB orders.csv into a per-country Parquet

@para/csv yields rows as it parses; the loop folds them into ~250 per-country totals; @para/arrow writes those out as Parquet. The CSV never sits in memory.

import { parseStream } from "@para/csv";
import { fromRows, toParquet } from "@para/arrow";

// Tally revenue per country without loading the file into memory.
const totals = new Map<string, number>();
const stream = Bun.file("orders.csv").stream();
for await (const row of parseStream(stream, { header: true, dynamicTyping: true })) {
  totals.set(row.country, (totals.get(row.country) ?? 0) + row.revenue);
}

// 5 GB of CSV → ~250 rows of summary → one Parquet file.
const summary = fromRows(
  [...totals].map(([country, revenue]) => ({ country, revenue })).sort((a, b) => b.revenue - a.revenue),
);
await Bun.write("revenue-by-country.parquet", await toParquet(summary, { compression: "snappy" }));

MCP — connect to a filesystem server, list tools, call them

Spawns the reference filesystem MCP server over stdio, sandboxed to one directory. The same client connects to any MCP server — Slack, GitHub, Postgres — without per-server adapter code. await using tears the subprocess down on scope exit.

import { connectStdio } from "@para/mcp";

await using fs = await connectStdio("npx", [
  "-y", "@modelcontextprotocol/server-filesystem", "/tmp/sandbox",
]);

await fs.callTool("write_file", { path: "/tmp/sandbox/note.txt", content: "from para-mcp" });

const dir = await fs.callTool("list_directory", { path: "/tmp/sandbox" });
console.log(dir.content[0].text);                          // note.txt

// Hand the same fs.listTools() output straight to a model and you have
// a tool-using agent over real disk — no glue per server, no schema
// translation, just whatever the MCP server advertises.

The rest

Three more libraries with their own docs:

Para Lang — optional .pts syntax that desugars to imports from these packages.

Para Runtime (ParaBun) — Bun fork. Bundles these packages, parses .pts natively, adds native modules for GPU, camera, audio, GPIO/I²C/SPI, and on-device LLM.