/ parascript

ParaScript

A TypeScript dialect with reactive signals, integer ranges, a pipeline operator, edge-triggered handlers, chained .catch / .finally / await, and compile-time function purity.

Files end in .pts. All extensions desugar to standard JavaScript at parse time. The output imports a small npm package, parabun-browser-shims, that provides the runtime side of the language.

Syntax

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 and await operators

..! is .catch. ..& is .finally. ..= is await-assign in declaration position; combined, they remove the boilerplate from a try/catch chain.

const data ..= fetch(url).then(r => r.json())
  ..! err => defaults
  ..& ()  => spinner.hide();

Compilation

ParaScript files (.pts) are parsed by Bun's transpiler, which understands the syntax. The output is standard JavaScript with a handful of imports from para:* module specifiers.

To resolve those specifiers in a non-Bun runtime, alias them to parabun-browser-shims in your bundler.

// vite.config.ts
import { defineConfig } from "vite";

export default defineConfig({
  resolve: {
    alias: [{ find: /^para:(.*)$/, replacement: "parabun-browser-shims/$1" }],
  },
});

The same one-line alias works for esbuild, webpack, and rollup. See the install guide for the variants.

$npm install parabun-browser-shims
$bun build src/main.pts --outdir dist/

Bun is required for the build step today. A standalone npm-installable transpiler (@parascript/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.

Parabun is a fork of Bun that ships ParaScript natively (no build or alias step) along with native modules for parallel CPU work, raw CUDA / Metal kernels, V4L2 camera capture, ALSA audio, GGUF LLM inference, and GPIO / I²C / SPI on Linux SBCs.