Para

Para Lib

Eight @lyku/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 @lyku/para-signals @lyku/para-parallel @lyku/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 "@lyku/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 "@lyku/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

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

import { parseStream } from "@lyku/para-csv";
import { fromRows, toParquet } from "@lyku/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 "@lyku/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.

Schema — one declaration, three artifacts

@lyku/para-schema is the spine's type side: a schema declaration yields a runtime validator (.parse returning a Result), a TS type, and a standard JSON Schema 2020-12 document — plus a schema-driven msgpack codec. Recursion compiles to $refs (data, not evaluation); cyclic values, depth caps, and identity-preserving decode are per-declaration capabilities. A checker-driven extractor (ts<import('./x').T>) derives schemas from existing TS types.

schema Comment = {
  type: "object",
  properties: {
    body:    { type: "string" },
    replies: { type: "array", items: Comment }   // → { $ref: "#Comment" }
  },
  required: ["body"]
};

Comment.parse(input)   // Result<Comment, string>
Comment.schema         // JSON Schema 2020-12 — hand to OpenAPI / Mongo
Comment.encode(value)  // schema-driven msgpack

Sync — a profile that follows the server

@lyku/para-sync replicates server-authoritative state into read-only reactive cells — every envelope parse-gated by a schema, reconciled by (schema_version, sequence), stale and duplicate deliveries ignored, gaps recovered. Typed queries stay live because the server knows their read-sets; opaque server code declares its refresh policy instead. In .pui components the sync keyword does the binding (Para Lang); this is the direct API:

import { synced, syncedOne } from "@lyku/para-sync";

// a keyed replica — the authority pushes, the cell follows
const user = synced(`user:${id}`, User);
user.subscribe(u => render(u));

// a typed scalar query — live via read-set invalidation
const me = syncedOne(User, { where: u => u.id == id });

The rest

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.