A JavaScript runtime built from scratch in Zig for serverless workloads. One binary, no dependencies, instant cold starts.
Where Node.js and Deno optimize for generality, zigttp optimizes for a single use case: running a request handler as fast as possible, then getting out of the way. It ships a pure-Zig JS engine (zigts) with a JIT compiler, NaN-boxed values, and hidden classes - but skips everything a FaaS handler doesn't need (event loop, Promises, require).
Validated release target: Zig 0.16.0. The build produces three binaries: zigttp (the primary developer CLI and local runtime entrypoint), zigttp-runtime (the internal runtime template used for self-contained outputs), and zigts (the compiler/analyzer plus interactive coding-agent CLI).
Opinionated language subset. TypeScript with the footguns removed. No classes, no this, no var, no while loops, no switch - just functions, let/const, arrow functions, destructuring, for...of, match expressions, and assert statements. Unsupported features fail at parse time with a suggested alternative, not at runtime with a cryptic stack trace. The payoff: each restriction enables a compiler guarantee that would be impossible with general JavaScript.
JSX as a first-class primitive. The parser handles JSX directly - no Babel, no build step. Write TSX handlers that return server-rendered HTML.
Compile-time verification. -Dverify proves your handler correct at build time: every code path returns a Response, Result values are checked before access, no unreachable code. The IR tree is the control flow graph: no back-edges, no exceptions. break and continue within for-of are forward jumps only and don't compromise this property. See verification docs.
Sound mode. The compiler uses type inference to catch bugs across all operators at compile time, not just in boolean contexts. Arithmetic on non-numeric types is rejected ("hello" - 1, true * 5, env("X") / 2). Mixed-type + is an error - use template literals. Tautological comparisons (typeof x === "number" when x is provably number) emit warnings. In boolean contexts, types with unambiguous falsy states are accepted (if (count), if (name)), while objects and functions are rejected (always truthy). Values the static checker cannot prove are caught by runtime VM assertions. When types are statically proven, the compiler emits specialized opcodes (add_num, lt_num, etc.) that skip runtime type dispatch. See sound mode docs.
Compile-time evaluation. comptime() folds expressions at load time, modeled after Zig's comptime. Hash a version string, uppercase a constant, precompute a config value - all before the handler runs.
Automatic runtime sandboxing. Every precompiled handler is sandboxed by default. The compiler extracts a contract of what the handler does (env vars, outbound hosts, cache namespaces, SQL query names) and derives a least-privilege policy that restricts runtime access to exactly the proven values. No configuration required. -Dcontract additionally emits a contract.json manifest. -Dpolicy=policy.json overrides auto-derived sandboxing with an explicit policy. Non-literal arguments honestly report "dynamic": true. Self-extracting binaries parse the embedded contract at startup: proven env vars are validated (missing vars fail fast instead of causing a 500 on first request), proven routes reject non-matching requests at the HTTP layer before entering JS, and proven handler properties are logged for operator visibility.
Handler effect classification. Every virtual module function carries a compile-time effect annotation (read, write, or none). During contract extraction, the compiler folds those calls into a small internal effect summary and derives handler-level properties: pure, read-only, stateless, retry-safe, deterministic, idempotent (safe for at-least-once delivery), injection-safe (no unvalidated input in sinks), and state-isolated (no cross-request data leakage). The I/O depth bound (max virtual module calls per request) enables compile-time Lambda timeout derivation. These properties flow into deployment manifests, OWASP compliance mapping, and the build report with no extra annotations. Handlers proven deterministic and read_only also have their GET/HEAD responses cached at runtime and served from Zig memory without entering JS. The X-Zigttp-Proof-Cache: hit response header confirms a cache hit.
Full TypeScript type checking. The compiler checks type annotations, not just strips them. Variable types, function argument types, return types, property access on records, and virtual module function signatures are validated at build time. Generic type aliases (type Result<T> = { ok: boolean; value: T }) are instantiated when used in annotations. Object literals are structurally matched against declared interface and type alias return types. Optional types from virtual modules (env(), cacheGet(), parseBearer()) are narrowed in if-guards: if (x), if (!x) return, and if (x !== undefined) all narrow nullable bindings to their inner type. Discriminated unions narrow in if-conditions: if (r.kind === "err") { return; } narrows r to the remaining member afterward. Type guard functions (x is T) narrow in both if-branches and after assert statements. distinct type UserId = string creates nominal types that prevent cross-type assignment while unwrapping for operations. readonly fields reject assignment at compile time. Template literal types (`/api/${string}`) validate string patterns at build time. const bindings preserve literal types; : Type annotations validate assignability without widening. let bindings widen literals so reassignment works. Interface declarations with all-function members are nominal to prevent structural forgery.
Structured concurrent I/O. parallel() and race() from zigttp:io overlap outbound HTTP without async/await or Promises. Handler code stays synchronous and linear; concurrency happens in the I/O layer using OS threads. Three API calls at 50ms each complete in ~50ms total.
One-command deploy. zigttp deploy cross-compiles the handler to a Linux musl binary, packages it as an OCI image with proven-fact labels (proof level, env vars, egress hosts, cache namespaces, routes, handler properties), pushes it through the zigttp control plane, and provisions the service. No flags, no config files, no registry to set up. First run prompts for a Zigttp access token in the terminal; browser-based device login remains available as fallback. Drift detection blocks accidental replaces; --confirm acknowledges and proceeds. See docs/deploy-tutorial.md.
Deterministic replay. Record every I/O boundary during handler execution with --trace, then replay against a new handler version with --replay or -Dreplay at build time. Because virtual modules are the only I/O boundary, recording their inputs and outputs captures all external state. Handlers become deterministic pure functions of (Request, VirtualModuleResponses).
Durable execution. --durable <dir> enables crash recovery and long-running workflows via write-ahead oplog. Wrap work in run(key, fn) from zigttp:durable with an idempotency key; each I/O call is persisted before returning to the handler. On crash recovery, recorded results are replayed without touching the network. Completed runs are deduplicated by key. Durable runs can suspend with sleep(ms), sleepUntil(unixMs), or waitSignal(name) and resume when the timer fires or a signal arrives via signal(key, name, payload). A background scheduler polls for ready timers and signals using the same replay-safe recovery path.
Guard composition. guard() from zigttp:compose combined with the pipe operator (|>) composes handlers with pre/post guards at compile time. The parser desugars guard(auth) |> guard(log) |> handler |> guard(cors) into a single flat function with sequential if-checks - zero runtime overhead.
Behavioral contract. -Dcontract enumerates every execution path through the handler and embeds them in contract.json as structured behaviors. Each path records the route, branching conditions (which I/O calls succeed or fail), the I/O sequence, and the resulting HTTP status. The restricted JS subset has no back-edges and no exceptions, so path enumeration is finite and exhaustive. Comparing two behavioral contracts shows which paths were preserved, which changed response codes, which were removed, and which are new.
Proven evolution. -Dprove=contract.json:traces.jsonl compares two handler versions by diffing their contracts (surface and behavior) and replaying recorded traces. The upgrade verifier produces a four-value verdict: safe, safe_with_additions, breaking, or needs_review. It factors in behavioral path changes, property regressions with severity (critical/warning/info), and trace coverage gaps. Output: proof.json, proof-report.txt, and upgrade-manifest.json. The standalone zigts prove old.json new.json CLI compares contracts without rebuilding (exit 0 for safe, 1 for breaking, 2 for needs_review).
Proven live reload. --watch --prove watches handler files and hot-swaps them in-process on every save. The server recompiles the handler, extracts its behavioral contract, diffs it against the running version, and applies the change only when the upgrade verdict is safe or safe_with_additions. Breaking changes (removed routes, lost critical properties) block the swap and print the diff. --force-swap overrides the block. Without --prove, --watch hot-reloads without contract proof. Compilation errors keep the old handler running. Durable handlers refuse live swap because replay state depends on handler identity.
Contract-driven mock server. zigts mock tests.jsonl --port 3001 serves mock HTTP responses from PathGenerator test cases. Frontend teams get a mock API provably consistent with the handler contract.
Generated tests. zigts gen-tests handler.ts -o handler.test.jsonl writes a JSONL test file from the same behavioral path enumeration the compiler uses internally. Each proven execution path becomes one runnable test case: synthesized request, ordered I/O stubs, expected status. The output runs immediately via --test or zigts mock. The zigts expert agent calls this automatically after edits so the test suite stays in sync with the proof.
Native modules over JS polyfills. Common FaaS needs (JWT auth, JSON Schema validation, caching, crypto) are implemented in Zig and exposed as zigttp:* virtual modules with zero interpretation overhead. Each module binding declares what runtime capabilities its implementation uses (clock, crypto, stderr, etc.); checked helpers enforce the declarations at call time, so implementation drift panics instead of silently misbehaving.
3ms runtime init. 1.2MB binary. 4MB memory baseline. Pre-warmed handler pool with per-request isolation. See performance docs for cold start breakdowns and deployment patterns.
Pre-built binaries for macOS and Linux (x86_64, aarch64):
curl -fsSL https://raw.githubusercontent.com/srdjan/zigttp/main/install.sh | shOr download a tarball from GitHub Releases.
To build from source (requires Zig 0.16.0):
git clone https://github.com/srdjan/zigttp.git && cd zigttp
zig build -Doptimize=ReleaseFast# Run with inline handler
./zig-out/bin/zigttp serve -e "function handler(r) { return Response.json({hello:'world'}) }"
# Or with a handler file
./zig-out/bin/zigttp serve examples/handler/handler.tsTest it:
curl http://localhost:8080/function HomePage() {
return (
<html>
<head>
<title>Hello World</title>
</head>
<body>
<h1>Hello World</h1>
<p>Welcome to zigttp!</p>
</body>
</html>
);
}
function handler(request) {
if (request.url === "/") {
return Response.html(renderToString(<HomePage />));
}
if (request.url === "/api/echo") {
return Response.json({
method: request.method,
url: request.url,
body: request.body,
});
}
return Response.text("Not Found", { status: 404 });
}zigttp includes native support for HTMX attributes in JSX:
function TodoForm() {
return (
<form
hx-post="/todos"
hx-target="#todo-list"
hx-swap="beforeend">
<input type="text" name="text" required />
<button type="submit">Add Todo</button>
</form>
);
}
function handler(request) {
if (request.url === "/" && request.method === "GET") {
return Response.html(renderToString(<TodoForm />));
}
if (request.url === "/todos" && request.method === "POST") {
// Parse form data, create todo item
const todoHtml = renderToString(
<div class="todo-item">New todo item</div>
);
return Response.html(todoHtml);
}
return Response.text("Not Found", { status: 404 });
}See examples/htmx-todo/ for a complete HTMX application.
zigttp provides native virtual modules via import { ... } from "zigttp:*" syntax. These run as native Zig code with zero JS interpretation overhead.
Most modules live in their own peer package packages/modules/, depending
only on packages/zigttp-sdk/. The zigts engine imports the module
bindings at comptime and adapts them through module_binding_adapter.zig.
A handful of modules (io, scope, durable, plus the install-shim
portions of sql, fetch, service, websocket) still live in
packages/zigts/src/modules/ because they require direct runtime
integration (GC roots, threadlocal fetch state, runtime bootstrap).
Modules are grouped by role; each cell in the Module column links to the per-module page. See docs/virtual-modules/README.md for the landing page and category index.
| Module | Exports | Description |
|---|---|---|
zigttp:env |
env |
Environment variable access |
zigttp:crypto |
sha256, hmacSha256, base64Encode, base64Decode |
Cryptographic functions |
zigttp:router |
routerMatch |
Pattern-matching HTTP router |
zigttp:auth |
parseBearer, jwtVerify, jwtSign, verifyWebhookSignature, timingSafeEqual |
JWT auth and webhook verification |
zigttp:validate |
schemaCompile, validateJson, validateObject, coerceJson, schemaDrop |
JSON Schema validation with format validators (email, uuid, iso-date, iso-datetime) |
zigttp:decode |
decodeJson, decodeForm, decodeQuery |
Schema-backed typed ingress for JSON, URL-encoded forms, and query strings |
zigttp:cache |
cacheGet, cacheSet, cacheDelete, cacheIncr, cacheStats |
In-memory key-value cache with TTL and LRU |
zigttp:sql |
sql, sqlOne, sqlMany, sqlExec |
Registered SQLite queries with build-time schema validation |
zigttp:service |
serviceCall |
Named internal service-to-service calls backed by system.json |
zigttp:fetch |
fetch |
Web-standard outbound HTTP with optional durable replay |
zigttp:websocket |
send, close, serializeAttachment, deserializeAttachment, getWebSockets, roomFromPath, setAutoResponse |
WebSocket protocol termination with rooms, attachments, and codec-level auto-replies |
zigttp:io |
parallel, race |
Structured concurrent I/O (overlaps fetchSync calls using OS threads) |
zigttp:scope |
scope, using, ensure |
Structured lifecycle management with deterministic cleanup at scope exit |
zigttp:compose |
guard, pipe |
Compile-time handler composition via pipe operator |
zigttp:durable |
run, step, stepWithTimeout, sleep, sleepUntil, waitSignal, signal, signalAt |
Durable execution with crash recovery, timers, signals, and timeout-aware steps |
zigttp:url |
urlParse, urlSearchParams, urlEncode, urlDecode |
URL parsing and query string encoding |
zigttp:id |
uuid, ulid, nanoid |
ID generation (UUID v4, ULID, NanoID) |
zigttp:http |
parseCookies, setCookie, negotiate, parseContentType, cors |
HTTP utilities for cookies, content negotiation, CORS |
zigttp:log |
logDebug, logInfo, logWarn, logError |
Structured logging to stderr |
zigttp:text |
escapeHtml, unescapeHtml, slugify, truncate, mask |
String utilities for HTML escaping, slugs, redaction |
zigttp:time |
formatIso, formatHttp, parseIso, addSeconds |
Time formatting and arithmetic |
zigttp:ratelimit |
rateCheck, rateReset |
Per-key rate limiting with sliding windows |
Each export carries an effect annotation used for handler property classification. See docs/virtual-modules/README.md for the full read/write/none breakdown across all 22 modules.
Each module binding also declares the runtime capabilities its Zig implementation consumes (clock, random, crypto, stderr, filesystem, sqlite, env, runtime_callback, policy_check, network). These declarations are governance metadata for the module internals and do not affect the handler-facing effect classification or sandbox policy. Capability-checked helpers enforce them at call time: if a module reads the system clock but its binding omits clock, the call panics with a diagnostic naming the module and the missing capability.
| Module | Runtime Capabilities |
|---|---|
zigttp:auth |
crypto, clock |
zigttp:cache |
clock, policy_check |
zigttp:crypto |
crypto |
zigttp:env |
env, policy_check |
zigttp:id |
clock, random |
zigttp:io |
runtime_callback |
zigttp:log |
clock, stderr |
zigttp:ratelimit |
clock |
zigttp:scope |
runtime_callback |
zigttp:service |
filesystem, runtime_callback |
zigttp:sql |
sqlite, policy_check |
zigttp:durable |
runtime_callback |
zigttp:compose, zigttp:decode, zigttp:http, zigttp:router, zigttp:text, zigttp:time, zigttp:url, zigttp:validate |
(none) |
Extension modules (via zigttp-sdk) declare capabilities the same way; the same checked helpers enforce them. Modules declaring no capabilities skip the enforcement wrapper at compile time.
import { parseBearer, jwtVerify, jwtSign } from "zigttp:auth";
function handler(req: Request): Response {
const token = parseBearer(req.headers["authorization"]);
if (token === undefined) return Response.json({ error: "unauthorized" }, { status: 401 });
const result = jwtVerify(token, "my-secret");
if (!result.ok) return Response.json({ error: result.error }, { status: 401 });
return Response.json({ user: result.value });
}import { schemaCompile } from "zigttp:validate";
import { decodeJson } from "zigttp:decode";
schemaCompile("user", JSON.stringify({
type: "object",
required: ["name", "email"],
properties: {
name: { type: "string", minLength: 1, maxLength: 100 },
email: { type: "string", minLength: 5 },
age: { type: "integer", minimum: 0, maximum: 200 }
}
}));
function handler(req: Request): Response {
if (req.method === "POST") {
const result = decodeJson("user", req.body ?? "{}");
if (!result.ok) return Response.json({ errors: result.errors }, { status: 400 });
return Response.json({ user: result.value }, { status: 201 });
}
return Response.json({ ok: true });
}Use decodeJson, decodeForm, and decodeQuery as the default request-ingress helpers when the payload shape is schema-backed. validateJson and coerceJson remain available for lower-level validation flows.
import { cacheGet, cacheSet, cacheStats } from "zigttp:cache";
function handler(req: Request): Response {
const cached = cacheGet("api", req.url);
if (cached !== undefined) return Response.json(JSON.parse(cached));
const data = { message: "computed", path: req.url };
cacheSet("api", req.url, JSON.stringify(data), 60); // TTL: 60 seconds
return Response.json(data);
}import { parallel, race } from "zigttp:io";
function handler(req: Request): Response {
// Three API calls in ~50ms instead of ~150ms
const [user, orders, inventory] = parallel([
() => fetchSync("https://users.internal/api/v1/123"),
() => fetchSync("https://orders.internal/api/v1?user=123"),
() => fetchSync("https://inventory.internal/api/v1/789")
]);
return Response.json({
user: user.json(),
orders: orders.json(),
inventory: inventory.json()
});
}See examples/modules/modules_all.ts for an integration example using all modules together.
import { sql, sqlMany, sqlExec } from "zigttp:sql";
sql("listTodos", "SELECT id, title, done FROM todos ORDER BY id ASC");
sql("createTodo", "INSERT INTO todos (title, done) VALUES (:title, 0)");
function handler(req: Request): Response {
if (req.method === "GET") {
return Response.json({ items: sqlMany("listTodos") });
}
const body = JSON.parse(req.body);
return Response.json(sqlExec("createTodo", { title: body.title }), { status: 201 });
}Build-time validation requires a schema snapshot:
zig build -Dhandler=examples/sql/sql-crud.ts -Dsql-schema=examples/sql/schema.sqlzigttp serve [options] <handler.js>
Options:
-p, --port <PORT> Port (default: 8080)
-h, --host <HOST> Host (default: 127.0.0.1)
-e, --eval <CODE> Inline JavaScript handler
-m, --memory <SIZE> JS runtime memory limit (default: 0 = no limit)
-n, --pool <N> Runtime pool size (default: auto)
--cors Enable CORS headers
--static <DIR> Serve static files
--outbound-http Enable native outbound bridge (fetchSync/httpRequest)
--outbound-host <H> Restrict outbound bridge to exact host H
--outbound-timeout-ms Connect timeout for outbound bridge (ms)
--outbound-max-response <SIZE>
--watch Watch handler files and hot-swap on change
--prove With --watch: diff behavioral contracts before swapping
--force-swap With --watch --prove: apply breaking changes anyway
--trace <FILE> Record handler I/O traces to JSONL file
--replay <FILE> Replay recorded traces and verify handler output
--sqlite <FILE> SQLite database path for zigttp:sql
--durable <DIR> Enable durable execution with write-ahead oplog
--system <FILE> System registry for zigttp:service
--no-env-check Skip startup env var validation (development use)Cross-compiles the handler to a Linux musl binary, packages it as an OCI image, pushes it through the zigttp control plane, and provisions the service. The control plane mints short-lived registry credentials per deploy and forwards the image to the upstream provider, so there is no account to create, no registry to configure, and no API token to manage on the client.
zigttp deploy [options]
Options:
--region <name> Override the deployment region for this run
--confirm Acknowledge drift and proceed with a replace-like update
--wait Block until the service reports ready (default)
--no-wait Return immediately after the deploy is accepted
-h, --help Show usageIf credentials are missing, the CLI first prompts for a Zigttp access token
directly in the terminal. The intended hosted flow is to create that token in
zigttp-admin, then paste it into the CLI. Submit an empty token to fall back
to browser-based device login. Tokens are stored at ~/.zigttp/credentials;
zigttp logout clears them. You can also sign in ahead of time with
zigttp login or zigttp login --token-stdin. The control plane base URL
defaults to https://api.zigttp.dev; set ZIGTTP_CONTROL_PLANE_URL to point
at a self-hosted instance.
Everything else is auto-detected from the current directory:
- Handler file: first match of
handler.ts,handler.tsx,handler.jsx,handler.js, or the same paths undersrc/. - Service name: the
namefield inpackage.json, then the basename of the git origin remote, then the current directory name. Slugified to lowercase with dashes. - Runtime environment:
KEY=valuepairs from.envin the current directory, one per line. Missing file is fine; a malformed line aborts the deploy with apath:linediagnostic. - Region:
--regionif given, then the region from the previous deploy of this service, thenus-central.
Before the CLI requests deploy credentials, it compiles the handler contract and sends that contract plus its canonical SHA-256 to the control plane. A self-hosted control plane can use that to auto-approve safe changes, auto-approve previously granted risky changes, or return a review URL when a deploy needs capability approval before continuing.
zigttp deploy
zigttp deploy --region eu-west
zigttp deploy --no-wait
zigttp review <plan-id>
zigttp review <plan-id> --approve --grant
zigttp grants
zigttp revoke-grant <grant-id>Reconciliation reads .zigttp/deploy-state.json, which stores non-secret
identifiers (scope, region, plan, managed env keys, last image digest)
from the last successful deploy of each service. A change to scope,
region, plan, or removal of a previously managed env var is flagged as
drift; the CLI prints the warning and exits with code 2. Re-run with
--confirm to acknowledge and proceed. Even with --confirm, the
old service is rebound and updated, never deleted.
After the push the CLI polls the provider until the service reports
ready (120s default). --no-wait skips the poll. Exit codes:
0success2drift detected, re-run with--confirm3timed out waiting for the service to report ready4service failed to start
OCI image references are content-addressed by the manifest digest, which is printed alongside the public URL on success. Identical handlers produce identical digests. Proof facts from the handler contract (proof level, env var names, egress hosts, cache namespaces, routes, handler properties) are encoded as JSON arrays in OCI image labels so provenance survives in the registry.
Standalone analysis and compilation without starting a server.
zigts expert calls the Anthropic API directly (ANTHROPIC_API_KEY). The persona, reference material, skill catalog, prompt templates, themes, and compiler metadata are all baked into the binary; there is no runtime plugin surface. The one external input that reaches the system prompt is AGENTS.md / CLAUDE.md, walked up from cwd to the enclosing .git/ directory and appended as a labelled read-only project-context section with a 128 KiB cap. Disable with --no-context-files. Full pi architecture: packages/pi/README.md.
# Interactive REPL
zigts expert
zigts expert --resume # resume newest session for this cwd
zigts expert --continue # alias for --resume
zigts expert --session-id <id> # named or resumed session
zigts expert --fork <session-id> # branch from an existing session
zigts expert --yes # auto-approve all verified edits
zigts expert --no-edit # auto-reject all verified edits
zigts expert --no-context-files # skip AGENTS.md / CLAUDE.md load
zigts expert --tools minimal # workspace-read-only tool preset
zigts expert --tools full # full compiler tool preset (default)
# Non-interactive (one turn and exit)
zigts expert --print "add a GET /health route"
zigts expert --print "..." --mode json # NDJSON event stream to stdout
# Line-delimited JSON-RPC 2.0 over stdio (long-lived session)
zigts expert --mode rpcIn --mode json, each event is {"v":1,"k":"<kind>","d":<payload>}. Kinds: user_text, model_text, tool_use, tool_result, proof_card, diagnostic_box, system_note, end. Persisted events.jsonl lines use the same envelope for transcript events, but end is a live-stream-only sentinel emitted last on success. Errored runs may terminate without end, and session files do not persist it.
In --mode rpc, each stdin line is a JSON-RPC 2.0 request; each stdout response line is a result or error keyed by id. Methods: turn, compact, session.info, tools.list, tools.invoke, skills.list, templates.{list,expand}, model.{list,set}, shutdown. During a turn, each new transcript entry emits as a notification with method "event" before the final result lands.
The interactive REPL accepts slash commands alongside natural language:
/compact collapse the session transcript into a summary
/fork branch the current session into a new directory
/tree list all sessions for this workspace
/resume /continue reload the newest session for this cwd
/new start a fresh session
/model show the active model and available IDs
/model <id> switch to a different model mid-session
/skills list available skill shortcuts
/skill:<name> send the named skill body as a prompt
/templates list available prompt templates
/template:<name> [args...] expand a template and send it as a prompt
/settings show compile-time defaults (model, token limits)
/settings theme list available TUI themes
/settings theme <name> switch the session's TUI theme
/hotkeys list keyboard shortcuts
/changelog recent expert subsystem additions
After each model turn the REPL prints cumulative token use for the session: [tokens: in=N cache_r=N cache_w=N out=N]. The totals reset when you start a new session or reload with /new or /resume.
The interactive surface runs as a bottom-anchored TUI: the status line (session, model, token totals) and input line stay pinned at the bottom while scrollback flows above. Redraws wrap in CSI ?2026h / ?2026l synchronized-output sequences so supporting terminals render each frame atomically. Two themes ship (default, solarized-dark); swap mid-session with /settings theme <name>.
On resume, the session's meta.json carries the policy_hash it was created under. If the current binary's hash differs, the REPL prepends a [policy drift] system note to the transcript so the model knows prior rule citations may be stale against today's compiler.
zigts check [handler.ts] [options] # Verify handler, show proof card
zigts compile [--system path] <handler.ts> <out.zig> # Compile to embedded bytecode
zigts prove <old.json> <new.json> # Compare contracts (exit 0=safe, 1=breaking)
zigts mock <tests.jsonl> [--port N] # Mock server from test cases
zigts link <system.json> # Cross-handler contract linking
zigts features [--json] # List allowed/blocked language features
zigts modules [--json] # List virtual modules and exports
zigts meta [--json] # Policy metadata (version, hash, rule count)
zigts gen-tests [handler.ts] [-o output.jsonl] # Generate tests from proven paths
zigts verify-paths <f>... [--json] # Full analysis on files
zigts verify-modules --builtins --strict --json # Governance auditzigts check --json handler.ts writes machine-readable diagnostics to stdout. Add --system system.json when the handler uses serviceCall() and you want compile-time typing for internal service responses and request-shape validation.
{
"success": false,
"diagnostics": [{
"code": "ZTS001",
"severity": "error",
"message": "'try/catch' is not supported",
"file": "handler.ts",
"line": 23,
"column": 3,
"suggestion": "use Result types for error handling"
}]
}On success, the output includes a proof summary: env vars, outbound hosts, virtual modules, and handler properties.
Code ranges: ZTS0xx (parser), ZTS1xx (sound mode), ZTS2xx (type checker), ZTS3xx (handler verifier).
Performance: Type-prefix NaN-boxing (single-instruction type checks), index-based hidden classes with SoA layout and O(1) transition lookups, polymorphic inline cache (PIC), generational GC, hybrid arena allocation for request-scoped workloads.
HTTP/FaaS Optimizations: Shape preallocation for Request/Response objects, pre-interned HTTP atoms, HTTP string caching, LockFreePool handler isolation, zero-copy response mode.
Compile-Time Analysis: Handler verification (-Dverify) proves correctness at build time. Contract extraction with behavioral paths and auto-sandboxing restrict runtime capabilities to proven values. Proven properties also control runtime behavior: deterministic+read_only handlers have their responses cached and served without JS execution. zigttp:sql queries are prepared against a build-time schema snapshot via -Dsql-schema=.... Sound mode rejects non-numeric arithmetic, mixed-type +, and tautological comparisons at compile time, and emits type-specialized opcodes when types are proven. TypeScript type checking validates annotations, narrows optionals through if-guards, and structurally matches object literals against declared types.
Structured Concurrency: parallel() and race() overlap outbound HTTP using OS threads. No async/await, no event loop - handler code stays synchronous and linear.
Deployment Pipeline: Contract manifests with behavioral paths (-Dcontract), one-command runtime deploy (zigttp deploy), auto-derived runtime sandboxing, deterministic replay (--trace/--replay/-Dreplay), proven evolution with upgrade verdicts (-Dprove), proven live reload (--watch --prove), and durable execution (--durable) form a pipeline from source analysis to production deployment with crash recovery.
Language Support: ES5 + select ES6 features (for...of with break/continue, typed arrays, exponentiation, pipe operator, compound assignments), native TypeScript/TSX stripping with type checking, compile-time evaluation with comptime(), direct JSX parsing, match expression, assert statement, distinct type, readonly fields, template literal types, type guards (x is T).
JIT Compilation: Baseline JIT for x86-64 and ARM64, inline cache integration, object literal shapes, type feedback, adaptive compilation.
Virtual Modules: Native zigttp:auth (JWT/HS256, webhook signatures), zigttp:validate (JSON Schema registry), zigttp:decode (typed request ingress), zigttp:cache (TTL/LRU key-value store), zigttp:service (named internal service calls), zigttp:io (structured concurrent I/O), zigttp:scope (deterministic cleanup tied to lexical scope), zigttp:compose (guard composition), zigttp:durable (crash recovery, timers, signals), plus zigttp:env, zigttp:crypto, zigttp:router. Module bindings declare runtime capabilities (clock, crypto, stderr, etc.) enforced by shared checked helpers, with a comptime short-circuit that skips the wrapper for modules declaring no capabilities.
Developer Experience: Fetch-like HTTP surface (Response.*, Response(body, init?), Request(url, init?), Headers(init?), request.text(), request.json(), headers.get(), fetchSync()), console methods (log, error, warn, info, debug), static file serving with LRU cache, CORS support, pool metrics.
When enabled with --outbound-http, handlers can call the higher-level fetchSync() helper:
const resp = fetchSync("http://127.0.0.1:8787/v1/ops?view=state", {
method: "GET",
headers: { Authorization: "Bearer ..." }
});
const data = resp.json();fetchSync() returns a response-shaped object with status, ok, headers.get(name), text(), and json().
Current helper semantics:
headers.get(name)is case-insensitive and returns the last observed value for that header name, ornull.Headers(init?),Request(url, init?), andResponse(body, init?)are available as factory-style HTTP types.newis not supported by the parser, so call them as plain functions.Headersinstances supportget(name),set(name, value),append(name, value),has(name), anddelete(name).text()returns the raw body string, or""when no body is present.json()returns parsed JSON, orundefinedwhen the body is empty or invalid JSON.- Body readers are single-use. Once
text()orjson()is called on a request/response object, subsequent body reads throw. Userequest.bodyif you need the raw body string without consuming it. - Validation, allowlist, network, timeout, and size-limit failures do not throw into handler code;
fetchSync()returns a599response with a JSON body containingerroranddetails.
The lower-level httpRequest(jsonString) bridge remains available:
const raw = httpRequest(JSON.stringify({
url: "http://127.0.0.1:8787/v1/ops?view=state",
method: "GET",
headers: { Authorization: "Bearer ..." }
}));httpRequest returns JSON with either { ok: true, status, reason, body, content_type? } or { ok: false, error, details }.
Use --outbound-host to restrict egress to a single host.
For internal zigttp-to-zigttp calls, prefer serviceCall() from zigttp:service over hard-coded internal URLs:
import { serviceCall } from "zigttp:service";
function handler(req: Request): Response {
const user = serviceCall("users", "GET /api/users/:id", {
params: { id: "123" },
});
if (user.status !== 200) {
return Response.json({ error: "user service unavailable" }, { status: 502 });
}
return Response.json(user.json());
}serviceCall(serviceName, "METHOD /path", init?) resolves through system.json, lowers to the existing outbound bridge, and gives zigts link a first-class internal edge to verify.
With zigts check --system <FILE> or zigts compile --system <FILE>, literal serviceCall() sites also become payload-aware at compile time:
statusnarrows to the target route's proven status codes.json()returns the target route's proven JSON type when there is a single compatible response schema- if different status codes produce different schemas, narrow on
resp.statusbefore calling.json() - required path/query/header/body inputs are validated against the target route contract during type checking
init supports:
paramsfor:pathplaceholdersqueryfor query string keysheadersfor request headersbodyfor string request bodies
Run handlers that use zigttp:service with --system <FILE>:
zigttp serve --system examples/system/system.json examples/system/gateway.tssystem.json now requires a stable name for each handler:
{
"version": 1,
"handlers": [
{ "name": "gateway", "path": "examples/system/gateway.ts", "baseUrl": "https://gateway.internal" },
{ "name": "users", "path": "examples/system/users.ts", "baseUrl": "https://users.internal" }
]
}zigts link <system.json> uses those names and routes to prove internal service composition. Raw fetchSync("https://users.internal/...") still works, but named service calls produce stronger linking and clearer diagnostics.
The linker now reports payload proof separately from route proof. proofLevel keeps its current meaning, while system-contract.json and system-report.txt add explicit payload fields:
payloadProvenpayloadCompatiblepayloadDetail
zigts rollout <old-system.json> <new-system.json> extends the same proof pipeline across time. It compiles and links both systems, checks mixed-version states, and emits rollout-plan.json plus rollout-report.txt with the smallest rollout phases it can prove. If a change only becomes safe when multiple handlers move together, the planner collapses them into one coordinated phase instead of pretending they can deploy independently.
zigttp's compile-time toolchain goes beyond precompilation. It verifies correctness, checks types, extracts a capability contract, auto-derives a runtime sandbox, and can generate platform-specific deployment configs - all at build time.
# Development build (runtime handler loading)
zig build -Doptimize=ReleaseFast
# Production build (embedded bytecode, auto-sandboxed, 16% faster cold starts)
zig build -Doptimize=ReleaseFast -Dhandler=examples/handler/handler.ts
# Verify handler correctness at compile time
zig build -Dhandler=handler.ts -Dverify
# Emit contract manifest (what the handler is allowed to do)
zig build -Dhandler=handler.ts -Dcontract
# Validate zigttp:sql queries against a schema snapshot
zig build -Dhandler=examples/sql/sql-crud.ts -Dsql-schema=examples/sql/schema.sql
# Override auto-derived sandbox with an explicit capability policy
zig build -Dhandler=handler.ts -Dpolicy=policy.json
# Replay-verify handler against recorded traces before embedding
zig build -Dhandler=handler.ts -Dreplay=traces.jsonl
# Compare handler versions (equivalent, additive, or breaking)
zig build -Dhandler=handler.ts -Dprove=old-contract.json:traces.jsonl
# Plan a safe multi-handler rollout between two system definitions
./zig-out/bin/zigts rollout old/system.json new/system.json
# Combine build-time verification passes
zig build -Doptimize=ReleaseFast -Dhandler=handler.ts -Dverify -Dcontract
# External enrichment flags (optional, for code generator integration)
zig build -Dhandler=handler.ts -Dmanifest=governance-manifest.json
zig build -Dhandler=handler.ts -Dexpect-properties=properties.json
zig build -Dhandler=handler.ts -Ddata-labels=data-labels.json
zig build -Dhandler=handler.ts -Dfault-severity=fault-severity.json
zig build -Dhandler=handler.ts -Dreport=jsonThe verifier statically proves seven properties of your handler at compile time:
- Every code path returns a Response. Missing
elsebranches and paths that fall through without returning are caught. - Result values are checked before access. Calls like
jwtVerify,decodeJson, anddecodeQueryreturn Result objects. The verifier ensures.okis checked before.valueis accessed. - No unreachable code. Statements after an unconditional return produce a warning.
- No unused variables. Declared variables that are never referenced produce a warning. Suppress with an underscore prefix (
_unused). - Match expressions have default arms. A
matchwithout a default arm produces a warning. - Optional values are checked before use. Values from
env(),cacheGet(),parseBearer(), androuterMatch()must be narrowed viaif (val),val !== undefined,val ?? default, or reassignment before use in expressions. - No cross-request state leakage. Module-scope variables must not be mutated inside the handler body. This prevents one request from affecting another through shared mutable state.
zigttp's JS subset eliminates back-edges: no while, no switch, no try/catch, no exceptions. break and continue are allowed within for-of (forward jumps only). The IR tree is the control flow graph. Verification is a recursive tree walk, not a fixpoint dataflow analysis.
$ zig build -Dhandler=handler.ts -Dverify
verify error: not all code paths return a Response
--> handler.ts:2:17
|
2 | function handler(req) {
| ^
= help: ensure every branch (if/else) ends with a return statement
See docs/verification.md for the full specification.
Every precompilation automatically extracts a contract from the handler's IR. The contract describes what your handler does before it runs and is used to derive the runtime sandbox. Add -Dcontract to also emit it as contract.json. It extracts:
- Virtual modules imported and which functions are used
- Environment variables accessed via
env("NAME")- literal names are enumerated, dynamic access is flagged - Outbound hosts called via
fetchSync("https://...")- hosts are extracted from URL literals - Internal service calls made via
serviceCall("name", "METHOD /path", init)- service names, route signatures, and statically proven params/query/header/body keys are captured - System-linked payload facts for named internal edges - target response statuses, JSON payload proof, and payload-proof gaps are reported in system-level output
- Cache namespaces used by
cacheGet/cacheSet/etc. - SQL queries registered with
sql("name", "...")- names, statement kinds, and touched tables are captured after schema validation - Scope usage from
scope("name", fn)- literal scope names, whether scope callbacks stay dynamic, and the maximum nested scope depth are captured - API surface from proven routes: method/path, path/query/header params, JSON request bodies, response variants, and bearer auth metadata
- Handler properties derived from the internal effect summary of virtual module calls, cache reads, scope usage, egress, and nondeterministic builtins (pure, read_only, stateless, retry_safe, deterministic)
- Behavioral paths - every execution path through the handler with route, branching conditions, I/O sequence, and response status (exhaustive when the path count stays below 1024)
- Verification results (when combined with
-Dverify) - Route patterns (when combined with
-Daot)
{
"version": 12,
"modules": ["zigttp:auth", "zigttp:cache", "zigttp:scope"],
"functions": {
"zigttp:auth": ["jwtVerify", "parseBearer"],
"zigttp:cache": ["cacheGet", "cacheSet"],
"zigttp:scope": ["scope", "ensure"]
},
"env": { "literal": ["JWT_SECRET"], "dynamic": false },
"egress": { "hosts": ["api.example.com"], "dynamic": false },
"cache": { "namespaces": ["sessions"], "dynamic": false },
"scope": {
"used": true,
"names": ["request", "enrich-user"],
"dynamic": false,
"maxDepth": 2
},
"sql": {
"backend": "sqlite",
"queries": [
{ "name": "listTodos", "operation": "select", "tables": ["todos"] }
],
"dynamic": false
},
"properties": {
"pure": false,
"readOnly": false,
"stateless": false,
"retrySafe": false,
"deterministic": true,
"hasEgress": true
},
"behaviors": [
{
"method": "GET",
"pattern": "/users/:id",
"status": 200,
"ioDepth": 2,
"failurePath": false,
"conditions": [
{"kind": "io_ok", "module": "auth", "func": "jwtVerify"},
{"kind": "io_ok", "module": "cache", "func": "cacheGet"}
],
"ioSequence": [
{"module": "auth", "func": "jwtVerify"},
{"module": "cache", "func": "cacheGet"}
]
},
{
"method": "GET",
"pattern": "/users/:id",
"status": 401,
"ioDepth": 1,
"failurePath": true,
"conditions": [
{"kind": "io_fail", "module": "auth", "func": "jwtVerify"}
],
"ioSequence": [
{"module": "auth", "func": "jwtVerify"}
]
}
],
"behaviorsExhaustive": true
}The "dynamic": false fields are the key signal. They mean "we can enumerate every value statically." When a handler uses a variable instead of a string literal (env(someVar) instead of env("JWT_SECRET")), the contract honestly reports "dynamic": true.
The same proven route facts can also be emitted as OpenAPI and as a generated TypeScript client:
zig build -Dhandler=examples/routing/api-surface.ts -Dcontract -Dopenapi -Dsdk=tsThis writes three sibling artifacts in src/generated/:
contract.jsonopenapi.jsonclient.ts
The current API emitters include facts the compiler can prove without guessing:
- route method and path
- path, query, and header params reached through literal access
- proven JSON request bodies from
validateJson(...),coerceJson(...), anddecodeJson(...) - proven form request bodies from
decodeForm(...) - typed query params from
decodeQuery(...) - proven response variants, including multiple status codes when statically visible
- bearer auth metadata
x-zigttp-*hints whenever part of the surface stays dynamic
The generated SDK only exposes typed helpers for routes it can prove end to end. Everything else remains available through requestRaw() and is listed in skippedOperations.
A fully proven route can be consumed like this:
import { createClient } from "./src/generated/client";
const api = createClient({ baseUrl: "https://api.example.com" });
const result = await api.postProfilesId({
params: { id: "user_123" },
query: { verbose: true },
body: { displayName: "Ada" },
headers: { "x-client-id": "cli-42" },
});
console.log(result.data.displayName);Auto-sandboxing: The contract is used to derive a RuntimePolicy embedded in the binary. Sections with dynamic: false are restricted to exactly the proven literals. Sections with dynamic: true remain unrestricted. The build reports what was proven:
Sandbox: complete (all access statically proven)
env: restricted to [JWT_SECRET] (1 proven, no dynamic access)
egress: restricted to [api.example.com] (1 proven, no dynamic access)
cache: restricted to [sessions] (1 proven, no dynamic access)
sql: restricted to [listTodos] (1 proven, no dynamic access)
Handler Properties:
--- pure handler is a deterministic function of the request
--- read_only no state mutations via virtual modules
--- stateless independent of mutable state
--- retry_safe disabled when scope-managed cleanup or bare writes are present
PROVEN deterministic no Date.now() or Math.random()
To override auto-derived sandboxing with a stricter or different policy, pass an explicit policy file. The policy is validated against the contract at build time and enforced at runtime. Local file-import handlers are covered: capability usage is aggregated across the module graph before validation.
{
"env": { "allow": ["JWT_SECRET"] },
"egress": { "allow_hosts": ["api.example.com"] },
"cache": { "allow_namespaces": ["sessions"] },
"sql": { "allow_queries": ["listTodos"] }
}Omit a section to leave that capability unrestricted. If a section is present, dynamic access in that category is rejected because zigttp cannot fully enumerate it.
zigttp deploy consumes the same compiler-proven contract used for
sandboxing and verification, then ships the handler through the zigttp
control plane in one command. The full flow lives in
docs/deploy-tutorial.md; the short version is
above under zigttp deploy.
Proof facts from the contract are encoded as JSON arrays in OCI image
labels (zigttp.proof-level, zigttp.env-vars, zigttp.egress-hosts,
zigttp.cache-namespaces, zigttp.routes, plus boolean labels for
retry-safe, read-only, idempotent, and so on) so registries and
downstream tooling can audit what the binary is allowed to do without
running it. Proof levels: complete (all checks pass, no dynamic
flags), partial (some verification but dynamic access detected),
none (no verification ran).
zigttp's restricted JS subset (no async, no exceptions, no side-effecting builtins) makes handlers deterministic pure functions of their request and virtual module responses. The replay system exploits this property.
Record traces during normal operation:
zigttp serve handler.ts --trace traces.jsonlEvery virtual module call, fetchSync response, Date.now() timestamp, and Math.random() value is recorded alongside the request and response.
Replay traces against a modified handler to detect regressions:
zigttp serve --replay traces.jsonl handler-v2.tsReports identical, status-changed, and body-changed results with structured diffs.
Build-time replay fails the build if regressions are detected:
zig build -Dhandler=handler-v2.ts -Dreplay=traces.jsonlEnable crash recovery with a write-ahead oplog:
zigttp serve handler.ts --durable ./oplogsHandlers opt into durability via the zigttp:durable virtual module:
import { run, step, stepWithTimeout, sleep, waitSignal, signal } from "zigttp:durable";
function handler(req: Request): Response {
// Deliver a signal to a waiting run
if (req.url === "/approve") {
signal("order:42", "approved", { by: "admin" });
return Response.json({ ok: true });
}
// Durable workflow with steps, timers, and signals
return run("order:42", () => {
const order = step("create", () =>
fetchSync("https://api.internal/orders", { method: "POST", body: "{}" }));
sleep(5000);
const approval = waitSignal("approved");
const confirmed = step("confirm", () =>
fetchSync("https://api.internal/orders/42/confirm", {
method: "POST", body: JSON.stringify(approval)
}));
return Response.json(confirmed.json());
});
}run(key, fn) wraps a unit of work with an idempotency key. Each step(name, fn) persists its result to the oplog before returning to the handler. sleep(ms) and sleepUntil(unixMs) suspend the run until a timer fires. waitSignal(name) suspends until a signal arrives via signal(key, name, payload) or signalAt(key, name, unixMs, payload). Pending runs return 202 Accepted with a JSON body describing the wait. On crash recovery, recorded results are replayed without touching the network. A background scheduler polls for ready timers and signals. Completed runs are deduplicated by key. stepWithTimeout(name, timeoutMs, fn) executes a step with a deadline - returns { ok: true, value } on completion or { ok: false, error: "timeout" } if the deadline is exceeded.
The runtime no longer exposes admin routes. Use the sibling zigttp-admin
service to inspect runs and enqueue signals against the same durable directory:
GET /GET /runs/:keyGET /contractGET /_zigttp/durable/contractGET /_zigttp/durable/runsGET /_zigttp/durable/runs/:keyPOST /_zigttp/durable/runs/:key/signals/:name
The durable section of contract.json also includes a workflow graph with
workflowId, proofLevel, nodes, and edges.
cd ../zigttp-admin
deno task start --durable-dir ../zigttp/.zigttp-durable --contract ../zigttp/contract.json
curl http://127.0.0.1:8787/_zigttp/durable/contract
curl http://127.0.0.1:8787/_zigttp/durable/runs
curl -X POST \
http://127.0.0.1:8787/_zigttp/durable/runs/order%3A42/signals/approved \
-H 'content-type: application/json' \
-d '{"approvedBy":"ops"}'Compare two handler versions and classify the upgrade:
zig build -Dhandler=handler-v2.ts -Dprove=old-contract.json:traces.jsonlTwo diff levels run against the old and new contracts. The surface diff compares I/O capabilities (env vars, egress hosts, cache namespaces, SQL query names, routes). The behavioral diff compares every execution path, matching by route and branching conditions, then classifying each as preserved, response-changed, removed, or added.
Property regressions carry severity. Losing retry_safe or injection_safe is critical. Losing deterministic or idempotent is a warning. Losing pure is informational.
The upgrade verdict combines these signals:
- safe: Identical behavior, no property regressions
- safe_with_additions: New paths or capabilities added, existing behavior preserved
- breaking: Paths removed, responses changed, or critical property lost
- needs_review: Structurally OK but warning-level regressions or significant coverage gaps
Output: proof.json (machine-readable certificate), proof-report.txt (human-readable), and upgrade-manifest.json (verdict with full breakdown).
Compose handlers with pre/post guards using the pipe operator:
import { guard } from "zigttp:compose";
const withAuth = guard((req: Request): Response | undefined => {
if (req.headers["authorization"] === undefined)
return Response.json({ error: "unauthorized" }, { status: 401 });
return undefined;
});
const withCors = guard((res: Response): Response | undefined => {
return Response.json(res.body, {
status: res.status,
headers: { "access-control-allow-origin": "*" }
});
});
const handler = withAuth |> mainHandler |> withCors;The parser desugars the pipe chain into a single flat function with sequential if-checks at compile time. Pre-guards receive the request and short-circuit on non-undefined return. Post-guards receive the response and can replace it. Exactly one non-guard handler is required.
Five optional build flags accept external JSON files for cross-referencing against compiler-proven contracts. These work with any code generator or hand-written files - no specific tooling required.
-Dmanifest=<path>: Cross-references a declared manifest (routes, SQL tables, env vars) against the handler contract. Errors on declared items missing from code, warns on undeclared items found in code. Emitsmanifest-alignment.json.-Dexpect-properties=<path>: Verifies handler-derived properties (state_isolated, injection_safe, read_only, etc.) match external expectations. Build fails on mismatches.-Ddata-labels=<path>: Merges externally declared data sensitivity labels (secret, credential, etc.) with the flow checker's heuristic labels. Violations of declared labels become build errors.-Dfault-severity=<path>: Overrides fault severity classification at the route level. A route declared "critical" elevates all failable calls within it to critical severity for fault coverage diagnostics.-Dreport=json: Emits a structured JSON build report aggregating verification, properties, fault coverage, flow analysis, manifest alignment, and property expectations intoreport.json.
All flags are optional and additive. Without them, nothing changes.
Cold Start Performance:
| Platform | Cold Start | Runtime Init | Status |
|---|---|---|---|
| macOS (development) | ~103ms | 3ms | Current |
| Linux (production) | ~18-33ms (planned) | 3ms | Planned |
macOS Performance (development environment):
- Total cold start: ~103ms (2-3x faster than Deno)
- Runtime initialization: 3ms
- dyld overhead: 80-90ms (unavoidable on macOS, affects all binaries)
- Acceptable for development; not suitable for latency-sensitive production
Linux Target (future production optimization):
- Static linking with musl libc
- Expected cold start: 18-33ms (70-85ms faster than macOS)
- Zero dynamic dependencies
- Requires fixing JIT cross-compilation issues
Embedded Bytecode Optimization (recommended for all platforms):
zig build -Doptimize=ReleaseFast -Dhandler=path/to/handler.jsEliminates runtime parsing and compilation. Single binary, smaller container image, lower memory baseline.
Platform Strategy:
- macOS: Development only (~100ms is acceptable)
- Linux: Production target (sub-10ms goal via static linking)
- Pre-fork/daemon: Alternative for sub-millisecond response times
See Performance for detailed profiling analysis and deployment patterns.
When revalidating against a newer Zig compiler, run:
zig version
zig build
zig build test
zig build test-zigts
zig build test-zruntime
bash scripts/test-examples.sh
ZTS_RUN_STRESS_TESTS=1 zig build test-zruntime
ZTS_RUN_FLAKY_IO_TESTS=1 zig build test -- --test-filter "parseRequest rejects long header"Then update the validated compiler version in this README and docs/user-guide.md.
For faster local edit/build loops on ELF targets, keep incremental and new-linker usage opt-in:
zig build -fincremental --error-style=minimal_clear
zig build -Doptimize=Debug -fincremental --error-style=minimal_clearzigttp does not enable these by default because Zig 0.16.0 still documents incremental compilation as experimental and the new ELF linker as not yet feature-complete.
- User Guide - Complete handler API reference, routing patterns, examples
- Verification - Compile-time handler verification: checks, diagnostics, examples
- Sound Mode - Type-directed analysis: arithmetic safety,
+safety, tautology detection, truthiness, type-specialized codegen - Architecture - System design, runtime model, concurrency, project structure
- Frontier - Strategic direction: core moat, next-frontier features, identity guardrails
- JSX Guide - JSX/TSX usage and server-side rendering
- TypeScript - Type stripping, compile-time evaluation
- Performance - Benchmarks, cold starts, optimizations, concurrent I/O
- Feature Detection - Unsupported feature detection matrix
- API Reference - Zig embedding API, extending with native functions
zigts implements ES5 with select ES6+ extensions:
Supported: Strict mode, let/const, arrow functions, template literals, destructuring, spread operator, for...of (arrays) with break/continue, optional chaining, nullish coalescing, typed arrays, exponentiation operator, compound assignments (+=, -=, *=, /=, %=, **=, bitwise), pipe operator (|>), array higher-order methods (.map(), .filter(), .reduce(), .find(), .findIndex(), .some(), .every(), .forEach()), Object.keys() / .values() / .entries(), Math extensions, modern string methods (replaceAll, trimStart/End), globalThis, range(), match expression (pattern matching).
Not Supported: Classes, async/await, Promises, var, while/do-while loops, this, new, try/catch, null, regular expressions, labeled break/continue, as/satisfies type assertions. All unsupported features are detected at parse time with helpful error messages suggesting alternatives. Use undefined as the sole absent-value sentinel.
See User Guide for full details.
MIT licensed.
- zigts - Pure Zig JavaScript engine (part of this project)
- Zig programming language
- Codex & Claude
