Skip to content

Code samples

Runnable Elide samples in TypeScript, JavaScript, Java, Kotlin, Python and WebAssembly: HTTP servers, SQLite, files, streams, workers, coroutines, JSON, Maven dependencies, annotations, tests, interop and OpenTelemetry, each with its command and output.

On this page

Run each sample in an empty directory.

TypeScript without a build

TypeScriptData

Generics, union types and satisfies run directly, with no build step or tsconfig.json.

// shapes.ts
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "rect"; width: number; height: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "rect":
      return shape.width * shape.height;
  }
}

function largest<T>(items: T[], size: (item: T) => number): T {
  return items.reduce((a, b) => (size(b) > size(a) ? b : a));
}

const shapes = [
  { kind: "circle", radius: 2 },
  { kind: "rect", width: 3, height: 5 },
] satisfies Shape[];

for (const s of shapes) console.log(`${s.kind}: ${area(s).toFixed(2)}`);
console.log(`largest: ${largest(shapes, area).kind}`);
elide run shapes.ts
circle: 12.57
rect: 15.00
largest: rect

HTTP handler

TypeScriptHTTP
// server.ts
export default {
  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url);
    return Response.json({ path: url.pathname });
  },
};
elide serve --no-tui server.ts
curl http://127.0.0.1:8080/hello
{"path":"/hello"}

Routes with URLPattern

TypeScriptHTTP

Matches request paths with URLPattern in a handler served by elide serve.

// api.ts
const users = new Map([
  ["1", { id: 1, name: "Ada" }],
  ["2", { id: 2, name: "Grace" }],
]);

const userRoute = new URLPattern({ pathname: "/users/:id" });

export default {
  async fetch(request: Request): Promise<Response> {
    const match = userRoute.exec(request.url);
    if (!match) return new Response("Not found\n", { status: 404 });
    const user = users.get(match.pathname.groups.id!);
    return user ? Response.json(user) : Response.json({ error: "no such user" }, { status: 404 });
  },
};
elide serve --no-tui api.ts
curl -s http://127.0.0.1:8080/users/2; echo
curl -s -w ' %{http_code}\n' http://127.0.0.1:8080/users/9
{"id":2,"name":"Grace"}
{"error":"no such user"} 404

Local server and fetch

TypeScriptHTTP

Starts a node:http server on a free port, calls it with fetch, and shuts it down.

// roundtrip.ts
import { createServer } from "node:http";
import type { AddressInfo } from "node:net";

const server = createServer((req, res) => {
  let body = "";
  req.on("data", (chunk) => (body += chunk));
  req.on("end", () => {
    const { items } = JSON.parse(body) as { items: number[] };
    res.setHeader("content-type", "application/json");
    res.end(JSON.stringify({ count: items.length, sum: items.reduce((a, b) => a + b, 0) }));
  });
});

server.listen(0, "127.0.0.1", async () => {
  const { port } = server.address() as AddressInfo;
  const response = await fetch(`http://127.0.0.1:${port}/sum`, {
    method: "POST",
    body: JSON.stringify({ items: [4, 8, 15, 16, 23, 42] }),
  });
  console.log(response.status, response.headers.get("content-type"));
  console.log(JSON.stringify(await response.json()));
  server.close();
});
elide run roundtrip.ts
200 application/json
{"count":6,"sum":108}

Embedded SQLite

TypeScriptDatabases
// database.ts
import { Database } from "elide:sqlite";

const db = new Database();
db.exec("CREATE TABLE users (name TEXT)");
db.exec("INSERT INTO users VALUES ('Ada')");
console.log(db.query("SELECT name FROM users").get().name);
elide run database.ts
Ada

SQLite transactions

TypeScriptDatabases

Moves money between accounts in a SQLite transaction that rolls back when a balance would go negative.

// bank.ts
import { Database } from "elide:sqlite";

const db = new Database();
db.exec("CREATE TABLE accounts (name TEXT PRIMARY KEY, balance INTEGER NOT NULL)");
const open = db.prepare("INSERT INTO accounts VALUES ($name, $balance)");
open.run({ $name: "alice", $balance: 100 });
open.run({ $name: "bob", $balance: 20 });

const adjust = db.prepare("UPDATE accounts SET balance = balance + ? WHERE name = ?");
const balance = db.prepare("SELECT balance FROM accounts WHERE name = ?");

const transfer = db.transaction((from: string, to: string, amount: number) => {
  adjust.run(-amount, from);
  adjust.run(amount, to);
  if (balance.get(from).balance < 0) throw new Error(`${from} cannot send ${amount}`);
});

transfer("alice", "bob", 30);
try {
  transfer("bob", "alice", 500);
} catch (e) {
  console.log("rolled back:", (e as Error).message);
}

for (const [name, amount] of db.prepare("SELECT name, balance FROM accounts ORDER BY name").values()) {
  console.log(name, amount);
}
elide run bank.ts
rolled back: bob cannot send 500
alice 70
bob 50

Access rules with CEL

TypeScriptExpressions

Compiles permission rules written in CEL once with elide:cel, then checks them against each user and document. CEL expressions have no side effects and always terminate, so rules can come from configuration.

// rules.ts
import { compile } from "elide:cel";

const rules = {
  "read": "true",
  "edit": "user.id == doc.owner || 'admin' in user.roles",
  "export": "user.plan == 'pro' && !doc.tags.exists(t, t.startsWith('pii'))",
};
const programs = Object.entries(rules).map(([name, rule]) => ({ name, program: compile(rule) }));

const docs = [
  { id: 1, owner: "ada", tags: ["report"] },
  { id: 2, owner: "grace", tags: ["pii-email", "report"] },
];
const users = [
  { id: "ada", plan: "pro", roles: [] },
  { id: "grace", plan: "free", roles: ["admin"] },
];

for (const user of users) {
  for (const doc of docs) {
    const allowed = programs.filter(({ program }) => program.test({ user, doc }));
    console.log(`${user.id} on doc ${doc.id}: ${allowed.map(({ name }) => name).join(", ")}`);
  }
}

console.log("export reads:", programs[2]?.program.variables.join(", "));
elide run rules.ts
ada on doc 1: read, edit, export
ada on doc 2: read
grace on doc 1: read, edit
grace on doc 2: read, edit
export reads: doc, user

Files and paths

TypeScriptFiles

Writes a small directory tree with node:fs, then walks it and totals file sizes by extension.

// sizes.ts
import { mkdirSync, readdirSync, statSync, writeFileSync } from "node:fs";
import { extname, join } from "node:path";

mkdirSync("site/css", { recursive: true });
writeFileSync("site/index.html", "<h1>Home</h1>");
writeFileSync("site/about.html", "<h1>About us</h1>");
writeFileSync("site/css/main.css", "body { margin: 0 }");

const totals = new Map<string, number>();
for (const entry of readdirSync("site", { recursive: true, withFileTypes: true })) {
  if (!entry.isFile()) continue;
  const ext = extname(entry.name);
  const size = statSync(join(entry.parentPath, entry.name)).size;
  totals.set(ext, (totals.get(ext) ?? 0) + size);
}

for (const [ext, bytes] of [...totals].sort()) console.log(`${ext}: ${bytes} bytes`);
elide run sizes.ts
.css: 18 bytes
.html: 30 bytes

CSV report

TypeScriptDataFiles

Parses a CSV file and prints revenue per region, largest first.

# sales.csv
region,product,units,price
north,widget,12,2.50
south,widget,4,2.50
north,gadget,3,10.00
east,gizmo,7,4.00
south,gadget,6,10.00
// report.ts
import { readFileSync } from "node:fs";

type Row = { region: string; product: string; units: number; price: number };

const [header, ...lines] = readFileSync("sales.csv", "utf8").trim().split("\n");
const keys = header.split(",");
const rows: Row[] = lines.map((line) => {
  const v = line.split(",");
  return { region: v[0], product: v[1], units: Number(v[2]), price: Number(v[3]) };
});

const revenue = new Map<string, number>();
for (const r of rows) revenue.set(r.region, (revenue.get(r.region) ?? 0) + r.units * r.price);

console.log(`${keys.length} columns, ${rows.length} rows`);
for (const [region, total] of [...revenue].sort((a, b) => b[1] - a[1])) {
  console.log(`${region.padEnd(6)} ${total.toFixed(2).padStart(8)}`);
}
elide run report.ts
4 columns, 5 rows
south     70.00
north     60.00
east      28.00

Streams and async iteration

JavaScriptData

Reads a log file line by line with node:readline and counts entries per level.

// levels.mjs
import { createReadStream, writeFileSync } from "node:fs";
import { createInterface } from "node:readline";

writeFileSync("app.log", [
  "INFO server started",
  "WARN slow query: 812ms",
  "INFO request /users",
  "ERROR connection reset",
  "INFO request /orders",
].join("\n"));

const counts = {};
const lines = createInterface({ input: createReadStream("app.log") });
for await (const line of lines) {
  const level = line.split(" ", 1)[0];
  counts[level] = (counts[level] ?? 0) + 1;
}
console.log(JSON.stringify(counts));
elide run levels.mjs
{"INFO":3,"WARN":1,"ERROR":1}

Transform stream pipeline

JavaScriptData

Chains a generator source, a Transform and a writable sink with pipeline from node:stream/promises.

// pipeline.mjs
import { Readable, Transform, Writable } from "node:stream";
import { pipeline } from "node:stream/promises";

async function* records() {
  yield { sku: "A-1", qty: 3, price: 4.5 };
  yield { sku: "B-7", qty: 1, price: 19.99 };
  yield { sku: "C-2", qty: 10, price: 0.25 };
}

const toCsv = new Transform({
  objectMode: true,
  transform(r, _enc, done) {
    done(null, `${r.sku},${r.qty},${(r.qty * r.price).toFixed(2)}\n`);
  },
});

const lines = [];
const sink = new Writable({
  objectMode: true,
  write(chunk, _enc, done) {
    lines.push(chunk.toString().trim());
    done();
  },
});

await pipeline(Readable.from(records()), toCsv, sink);
console.log(["sku,qty,total", ...lines].join("\n"));
elide run pipeline.mjs
sku,qty,total
A-1,3,13.50
B-7,1,19.99
C-2,10,2.50

Command-line arguments

JavaScriptCLI

Parses flags and positional arguments with parseArgs from node:util.

// greet.mjs
import { parseArgs } from "node:util";

const { values, positionals } = parseArgs({
  args: process.argv.slice(2),
  options: {
    greeting: { type: "string", short: "g", default: "Hello" },
    shout: { type: "boolean", default: false },
  },
  allowPositionals: true,
});

for (const name of positionals) {
  const line = `${values.greeting}, ${name}!`;
  console.log(values.shout ? line.toUpperCase() : line);
}
elide run greet.mjs -- --greeting Hi Ada Grace
elide run greet.mjs -- --shout Alan
Hi, Ada!
Hi, Grace!
HELLO, ALAN!

Worker threads

JavaScriptConcurrency

Counts primes below 200,000 in four node:worker_threads workers and adds up their results.

// primes.mjs
import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads";

function countPrimes(from, to) {
  let count = 0;
  for (let n = Math.max(from, 2); n < to; n++) {
    let prime = true;
    for (let d = 2; d * d <= n; d++) if (n % d === 0) { prime = false; break; }
    if (prime) count++;
  }
  return count;
}

if (isMainThread) {
  const limit = 200_000, parts = 4, step = limit / parts;
  const jobs = Array.from({ length: parts }, (_, i) =>
    new Promise((resolve, reject) => {
      const w = new Worker(new URL(import.meta.url), { workerData: [i * step, (i + 1) * step] });
      w.once("message", resolve);
      w.once("error", reject);
    }));
  const counts = await Promise.all(jobs);
  console.log("per worker:", counts.join(", "));
  console.log("primes below", limit, "=", counts.reduce((a, b) => a + b));
} else {
  parentPort.postMessage(countPrimes(...workerData));
}
elide run primes.mjs
per worker: 5133, 4459, 4256, 4136
primes below 200000 = 17984

TypeScript tests

TypeScriptTesting

Runs node:test tests written in TypeScript with elide test, with no configuration. --reporter=tap prints TAP instead of the summary.

// slugify.ts
export function slugify(title: string): string {
  return title
    .toLowerCase()
    .trim()
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-|-$/g, "");
}
// slugify.test.ts
import { describe, test } from "node:test";
import assert from "node:assert";
import { slugify } from "./slugify.ts";

describe("slugify", () => {
  test("lowercases and joins words", () => {
    assert.equal(slugify("Hello World"), "hello-world");
  });

  test("drops punctuation at the ends", () => {
    assert.equal(slugify("  Elide: a runtime! "), "elide-a-runtime");
  });
});
elide test --reporter=tap
TAP version 13
# start 1: slugify > lowercases and joins words
ok 1 - slugify > lowercases and joins words
# start 2: slugify > drops punctuation at the ends
ok 2 - slugify > drops punctuation at the ends
1..2

Java records and streams

JavaData

Runs a single Java file with records, switch expressions and streams, with no build file.

// Payroll.java
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

public class Payroll {
  enum Role { ENGINEER, DESIGNER, MANAGER }

  record Employee(String name, Role role, int salary) {}

  public static void main(String[] args) {
    var staff = List.of(
        new Employee("Ada", Role.ENGINEER, 120_000),
        new Employee("Linus", Role.ENGINEER, 110_000),
        new Employee("Susan", Role.DESIGNER, 95_000),
        new Employee("Grace", Role.MANAGER, 130_000));

    Map<Role, Double> average = staff.stream().collect(
        Collectors.groupingBy(Employee::role, TreeMap::new, Collectors.averagingInt(Employee::salary)));

    average.forEach((role, avg) -> {
      String label = switch (role) {
        case ENGINEER -> "Engineering";
        case DESIGNER -> "Design";
        case MANAGER -> "Management";
      };
      System.out.printf("%-12s %,8.0f%n", label, avg);
    });
  }
}
elide run Payroll.java
Engineering   115,000
Design         95,000
Management    130,000

Kotlin collections

KotlinData

Groups, sums, sorts and partitions a list of orders with the Kotlin standard library.

// Orders.kt
data class Order(val customer: String, val item: String, val amount: Double)

fun main() {
  val orders = listOf(
    Order("ada", "book", 24.0),
    Order("grace", "laptop", 1299.0),
    Order("ada", "pen", 3.5),
    Order("alan", "book", 24.0),
    Order("grace", "mouse", 25.0),
  )

  orders.groupBy { it.customer }
    .mapValues { (_, list) -> list.sumOf { it.amount } }
    .entries
    .sortedByDescending { it.value }
    .forEach { (customer, total) -> println("%-6s %8.2f".format(customer, total)) }

  val (books, other) = orders.partition { it.item == "book" }
  println("books: ${books.size}, other items: ${other.map { it.item }.distinct().sorted()}")
}
elide run Orders.kt
grace   1324.00
ada       27.50
alan      24.00
books: 2, other items: [laptop, mouse, pen]

Kotlin tests

KotlinTesting

Runs kotlin.test tests with elide test in a project with an elide.pkl. --reporter=tap prints TAP instead of the summary.

// elide.pkl
amends "elide:project.pkl"

name = "pricing"
// src/main/kotlin/Pricing.kt
fun priceWithTax(net: Double, ratePercent: Int): Double = Math.round(net * (100 + ratePercent)) / 100.0
// src/test/kotlin/PricingTest.kt
import kotlin.test.Test
import kotlin.test.assertEquals

class PricingTest {
  @Test
  fun addsTax() {
    assertEquals(12.0, priceWithTax(10.0, 20))
  }

  @Test
  fun roundsToCents() {
    assertEquals(10.69, priceWithTax(9.99, 7))
  }
}
elide test --reporter=tap
TAP version 13
# start 1: PricingTest > addsTax()
ok 1 - PricingTest > addsTax()
# start 2: PricingTest > roundsToCents()
ok 2 - PricingTest > roundsToCents()
1..2

Kotlin coroutines

KotlinConcurrency

Starts three loads with async and waits for all of them; the shortest delay finishes first. kotlinx.coroutines is bundled, so a single file needs no build file.

// Fetch.kt
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking

suspend fun load(name: String, ms: Long): Int {
  delay(ms)
  println("loaded $name")
  return name.length
}

fun main() = runBlocking {
  val sizes = listOf("users" to 300L, "orders" to 200L, "prices" to 100L)
    .map { (name, ms) -> async { load(name, ms) } }
    .awaitAll()
  println("sizes in request order: $sizes")
}
elide run Fetch.kt
loaded prices
loaded orders
loaded users
sizes in request order: [5, 6, 6]

Kotlin JSON serialization

KotlinData

Decodes JSON into a @Serializable data class, filling defaults, and encodes a copy. The serialization plugin and runtime are bundled.

// Config.kt
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json

@Serializable
data class Server(val host: String, val port: Int = 8080, val tags: List<String> = emptyList())

fun main() {
  val json = Json { prettyPrint = true }
  val server = json.decodeFromString<Server>("""{"host": "localhost", "tags": ["dev"]}""")
  println(server)
  println(json.encodeToString(server.copy(port = 9090)))
}
elide run Config.kt
Server(host=localhost, port=8080, tags=[dev])
{
    "host": "localhost",
    "port": 9090,
    "tags": [
        "dev"
    ]
}

Java with a Maven dependency

JavaDependencies

Declares Gson in elide.pkl; elide run resolves it, compiles and runs jvm.main.

// elide.pkl
amends "elide:project.pkl"

name = "inventory"

jvm {
  main = "app.Inventory"
}

dependencies {
  maven {
    packages { "com.google.code.gson:gson:2.13.1" }
  }
}
// src/main/java/app/Inventory.java
package app;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.util.List;

public class Inventory {
  record Item(String sku, int quantity, double price) {}

  public static void main(String[] args) {
    Gson gson = new GsonBuilder().setPrettyPrinting().create();
    Item[] items = gson.fromJson("""
        [{"sku": "A-1", "quantity": 3, "price": 2.5},
         {"sku": "B-2", "quantity": 1, "price": 10.0}]
        """, Item[].class);

    double total = List.of(items).stream().mapToDouble(i -> i.quantity() * i.price()).sum();
    System.out.println(items.length + " items, total " + total);
    System.out.println(gson.toJson(items[0]));
  }
}
elide run
2 items, total 17.5
{
  "sku": "A-1",
  "quantity": 3,
  "price": 2.5
}

Elide annotations in Kotlin

KotlinAnnotations

Marks an interface with @API and a method with @Delicate, which callers must opt in to. Kotlin projects get dev.elide.annotations on the classpath; see Elide annotations.

// elide.pkl
amends "elide:project.pkl"

name = "cache"

jvm {
  main = "MainKt"
}
// src/main/kotlin/Main.kt
import dev.elide.annotations.API
import dev.elide.annotations.Delicate

@API
interface Cache {
  fun get(key: String): String?
  fun put(key: String, value: String)
}

class MemoryCache : Cache {
  private val entries = mutableMapOf<String, String>()

  override fun get(key: String) = entries[key]

  override fun put(key: String, value: String) {
    entries[key] = value
  }

  @Delicate
  fun clear() = entries.clear()
}

@OptIn(Delicate::class)
fun main() {
  val cache = MemoryCache()
  cache.put("user:1", "Ada")
  println(cache.get("user:1"))
  cache.clear()
  println(cache.get("user:1"))
  println(Cache::class.java.isAnnotationPresent(API::class.java))
}
elide run
Ada
null
true

Without @OptIn(Delicate::class), the build fails with This API is marked delicate; opt in with @OptIn(Delicate::class) if you understand its caveats.

Python calling JavaScript

PythonJavaScriptInterop

Needs the Python component and --interop.

# interop.py
from elide import js

total = js("[1, 2, 3, 4].reduce((a, b) => a + b, 0)")
print(total)
elide --interop python interop.py
10

JavaScript importing Python

JavaScriptPythonInterop

Needs the Python component and --interop.

# greet.py
def hello():
    return "Hello from Python"
// main.mjs
import greet from "./greet.py";
console.log(greet.hello());
elide --interop run main.mjs
Hello from Python

WebAssembly from JavaScript

JavaScriptWebAssembly

Builds a WebAssembly library from C with zig, then imports its exports as an ES module. Needs zig to build the .wasm file.

// checksum.c
unsigned int adler32(const unsigned char *data, unsigned int len) {
  unsigned int a = 1, b = 0;
  for (unsigned int i = 0; i < len; i++) {
    a = (a + data[i]) % 65521;
    b = (b + a) % 65521;
  }
  return (b << 16) | a;
}
// main.mjs
import { adler32, memory } from "./checksum.wasm";

const input = new TextEncoder().encode("Wikipedia");
new Uint8Array(memory.buffer).set(input, 1024);
console.log(adler32(1024, input.length).toString(16));
zig cc --target=wasm32-freestanding -O2 -nostdlib -Wl,--no-entry -Wl,--export=adler32 -o checksum.wasm checksum.c
elide run main.mjs
11e60398

WASI command

WebAssemblyCLI

Builds a WASI command from C with zig and runs it with arguments. Needs zig to build the .wasm file.

// wc.c
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv) {
  int chars = 0;
  for (int i = 1; i < argc; i++) {
    chars += strlen(argv[i]);
    printf("%d: %s\n", i, argv[i]);
  }
  printf("%d words, %d characters\n", argc - 1, chars);
  return 0;
}
zig cc --target=wasm32-wasi -O2 -o wc.wasm wc.c
elide run wc.wasm -- polyglot runtime toolchain
1: polyglot
2: runtime
3: toolchain
3 words, 24 characters

Python script

PythonData

Python’s standard library runs with no virtual environment. Needs the Python component (elide setup python).

# words.py
import json
import re
from collections import Counter
from dataclasses import asdict, dataclass

@dataclass
class Report:
    words: int
    unique: int
    top: list[tuple[str, int]]

text = """Elide runs Python, JavaScript and TypeScript.
Python runs without a separate interpreter; JavaScript runs too."""

words = re.findall(r"[a-z]+", text.lower())
report = Report(len(words), len(set(words)), Counter(words).most_common(3))
print(json.dumps(asdict(report)))
elide python words.py
{"words": 15, "unique": 11, "top": [["runs", 3], ["python", 2], ["javascript", 2]]}

PostgreSQL queries

TypeScriptDatabases

A prepared batch insert, a transaction and an aggregate query with elide:postgres. Start a server first, for example docker run -d -e POSTGRES_USER=app -e POSTGRES_PASSWORD=secret -e POSTGRES_DB=app -p 127.0.0.1:5432:5432 postgres:17.

// orders.ts
import { Database } from "elide:postgres";

const db = await Database.connect(process.env.DATABASE_URL!);

await db.exec("DROP TABLE IF EXISTS orders");
await db.exec("CREATE TABLE orders (id SERIAL PRIMARY KEY, customer TEXT NOT NULL, total NUMERIC(10, 2))");

const insert = await db.prepare("INSERT INTO orders (customer, total) VALUES ($1, $2)");
await insert.executeBatch([["ada", 42.5], ["grace", 18], ["ada", 7.25]]);
await insert.close();

await db.transaction(async (tx) => {
  await tx.exec("UPDATE orders SET total = total * 0.9 WHERE customer = $1", "ada");
});

const result = await db.query(
  "SELECT customer, count(*)::int AS orders, sum(total)::float AS spent FROM orders GROUP BY customer ORDER BY customer",
);
for (const row of await result.all()) console.log(row);

await db.close();
DATABASE_URL=postgresql://app:secret@127.0.0.1:5432/app elide run orders.ts
{customer: "ada", orders: 2, spent: 44.78}
{customer: "grace", orders: 1, spent: 18}

Server-sent events

TypeScriptHTTP

A ReadableStream body streams events as they are produced; request.signal stops the timer when the client leaves.

// events.ts
const encoder = new TextEncoder();

Elide.serve({
  hostname: "127.0.0.1",
  port: 3000,
  fetch(request) {
    let timer: ReturnType<typeof setInterval>;
    const body = new ReadableStream({
      start(controller) {
        let n = 0;
        timer = setInterval(() => {
          n += 1;
          controller.enqueue(encoder.encode(`event: tick\ndata: ${n}\n\n`));
          if (n === 3) {
            clearInterval(timer);
            controller.close();
          }
        }, 500);
        request.signal.addEventListener("abort", () => clearInterval(timer));
      },
    });
    return new Response(body, {
      headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache" },
    });
  },
});
elide run events.ts
curl -N http://127.0.0.1:3000/
event: tick
data: 1

event: tick
data: 2

event: tick
data: 3

Each event arrives half a second after the previous one.

Request limits

JavaScriptHTTPSandbox

maxBodyBytes rejects large request bodies before the handler reads them.

// limits.mjs
const server = Elide.serve({
  hostname: "127.0.0.1",
  port: 0,
  maxBodyBytes: 1024,
  async fetch(request) {
    const body = await request.text();
    return new Response(`received ${body.length} bytes\n`);
  },
});
setTimeout(async () => {
  for (const size of [512, 4096]) {
    const res = await fetch(`http://127.0.0.1:${server.port}/`, { method: "POST", body: "x".repeat(size) });
    console.log(size, res.status, (await res.text()).trim());
  }
  server.close();
}, 0);
elide run limits.mjs
512 200 received 512 bytes
4096 413 Payload Too Large

node:http server and client

JavaScriptHTTP
// http.mjs
import http from "node:http";

const server = http.createServer((req, res) => {
  let body = "";
  req.on("data", (chunk) => (body += chunk));
  req.on("end", () => {
    res.writeHead(200, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ method: req.method, url: req.url, received: body.length }));
  });
});

server.listen(0, "127.0.0.1", () => {
  const { port } = server.address();
  const req = http.request({ port, host: "127.0.0.1", method: "POST", path: "/items" }, (res) => {
    let data = "";
    res.setEncoding("utf8");
    res.on("data", (chunk) => (data += chunk));
    res.on("end", () => {
      console.log(res.statusCode, res.headers["content-type"]);
      console.log(data);
      server.close();
    });
  });
  req.end("hello");
});
elide run http.mjs
200 application/json
{"method":"POST","url":"/items","received":5}

Sandboxed subprocess

JavaScriptSandboxCLI

--allow-run lists the commands a program may start; any other command fails.

// spawn.mjs
import { execFileSync } from "node:child_process";

console.log(execFileSync("echo", ["allowed"], { encoding: "utf8" }).trim());
try {
  execFileSync("ls", ["/"]);
} catch (e) {
  console.log(e.message);
}
elide --allow-run=echo run spawn.mjs
allowed
spawn of 'ls' is not permitted; grant it with --allow-run=ls

Sign data with Web Crypto

TypeScriptCrypto
// sign.ts
const { privateKey, publicKey } = await crypto.subtle.generateKey(
  { name: "ECDSA", namedCurve: "P-256" },
  true,
  ["sign", "verify"],
);

const message = new TextEncoder().encode("ship order 1042");
const algorithm = { name: "ECDSA", hash: "SHA-256" };
const signature = await crypto.subtle.sign(algorithm, privateKey, message);

console.log("signature bytes:", signature.byteLength);
console.log("valid:", await crypto.subtle.verify(algorithm, publicKey, signature, message));

const tampered = new TextEncoder().encode("ship order 9999");
console.log("tampered valid:", await crypto.subtle.verify(algorithm, publicKey, signature, tampered));

const jwk = await crypto.subtle.exportKey("jwk", publicKey);
console.log("public key:", jwk.kty, jwk.crv);
elide run sign.ts
signature bytes: 64
valid: true
tampered valid: false
public key: EC P-256

Gzip with CompressionStream

TypeScriptData
// gzip.ts
const text = "elide ".repeat(1000);

const compressed = await new Response(
  new Blob([text]).stream().pipeThrough(new CompressionStream("gzip")),
).arrayBuffer();

const restored = await new Response(
  new Blob([compressed]).stream().pipeThrough(new DecompressionStream("gzip")),
).text();

console.log(`original: ${text.length} bytes`);
console.log(`gzip: ${compressed.byteLength} bytes`);
console.log(`round trip: ${restored === text}`);
elide run gzip.ts
original: 6000 bytes
gzip: 73 bytes
round trip: true

Dates with Temporal

TypeScriptData

Month arithmetic clamps to the end of the month, and zoned times keep their wall-clock time across a daylight-saving change.

// dates.ts
const start = Temporal.PlainDate.from("2026-01-30");
console.log(start.add({ months: 1 }).toString());

const meeting = Temporal.ZonedDateTime.from("2026-03-28T09:00[Europe/London]");
console.log(meeting.add({ days: 1 }).toString());
console.log(meeting.withTimeZone("America/New_York").toPlainTime().toString());

const due = Temporal.PlainDate.from("2026-12-25");
console.log(start.until(due, { largestUnit: "months" }).toString());
elide run dates.ts
2026-02-28
2026-03-29T09:00:00+01:00[Europe/London]
05:00:00
P10M25D

Limit workers

JavaScriptConcurrencySandbox

--limit=workers=N caps live workers; one more throws ERR_WORKER_LIMIT.

// workers.mjs
import { Worker } from "node:worker_threads";

const source = "setInterval(() => {}, 1000)"; // stays alive until terminated
const workers = [];
for (let i = 1; i <= 3; i++) {
  try {
    workers.push(new Worker(source, { eval: true }));
    console.log(`worker ${i} started`);
  } catch (e) {
    console.log(`worker ${i}: ${e.code}`);
  }
}
await Promise.all(workers.map((w) => w.terminate()));
elide --limit=workers=2 run workers.mjs
worker 1 started
worker 2 started
worker 3: ERR_WORKER_LIMIT

Filter tests with CEL

TypeScriptTestingExpressions

--where selects tests with a CEL expression over name, parents and file.

// cart.test.ts
import { describe, test } from "node:test";
import assert from "node:assert/strict";

describe("cart", () => {
  test("adds items", () => assert.equal([1, 2].length, 2));
  test("applies discount [slow]", () => assert.equal(Math.round(10 * 0.9), 9));
});

describe("checkout", () => {
  test("charges card", () => assert.ok(true));
  test("sends receipt [slow]", () => assert.ok(true));
});
elide test --where 'name.endsWith("[slow]")'
cart.test.ts
⇥ cart > adds items
✓ cart > applies discount [slow] [1ms]
⇥ checkout > charges card
✓ checkout > sends receipt [slow] [3ms]

 2 pass
 2 skip
 0 fail
Ran 4 tests across one file. (0.14s)

JUnit XML in CI

TypeScriptTestingCi

--reporter junit writes one XML file per test file for CI test-report steps; a failure exits 1.

// price.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";

const total = (items: number[]) => items.reduce((a, b) => a + b, 0);

test("sums prices", () => assert.equal(total([2, 3]), 5));
test("empty cart", () => assert.equal(total([]), 1));
elide test --reporter junit --reporter-outfile reports
 1 pass
 0 skip
 1 fail
Ran 2 tests across one file. (0.11s)

reports/js/TEST-price.test.ts.xml:

<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="price.test.ts" tests="2" failures="1" errors="0" skipped="0" time="0.026">
  <testcase name="sums prices" classname="price.test.ts" time="0.005"/>
  <testcase name="empty cart" classname="price.test.ts" time="0.021">
    <failure message="Expected values to be strictly equal:&#10;&#10;0 !== 1&#10;" type="ERR_ASSERTION">…</failure>
  </testcase>
</testsuite>

Multi-context server

TypeScriptHTTPConcurrency

--contexts=auto runs one copy of the module per core (Linux). Each copy keeps its own globals, so the counter counts per context.

// counter.ts
const id = crypto.randomUUID().slice(0, 8);
let count = 0;

export default {
  fetch(): Response {
    count += 1;
    return Response.json({ context: id, count });
  },
};
elide serve --no-tui --contexts=auto --port 8080 counter.ts
for i in 1 2 3 4; do curl -s http://127.0.0.1:8080/; echo; done
{"context":"60cf3464","count":1}
{"context":"60e90376","count":1}
{"context":"90086101","count":1}
{"context":"4adfe8e1","count":1}

Each curl opens a new connection, which the server may hand to any context.

Custom span in TypeScript

TypeScriptObservability

Spans from @opentelemetry/api, exported by Elide with no SDK installed. using ends the inner span with its block. Start a local collector first: docker run --rm -p 4318:4318 otel/opentelemetry-collector:0.115.1.

// checkout.ts
import { trace } from "@opentelemetry/api";
import { currentSpanContext, flush } from "elide:telemetry";

const tracer = trace.getTracer("checkout");

async function chargeCard(amount: number): Promise<void> {
  using span = tracer.startSpan("charge card");
  span.setAttribute("payment.amount", amount);
  await new Promise((resolve) => setTimeout(resolve, 20));
}

await tracer.startActiveSpan("checkout", async (span) => {
  span.setAttribute("order.id", 42);
  await chargeCard(19.99);
  console.log("charged order 42, sampled:", currentSpanContext()?.traceFlags === 1);
  span.end();
});

console.log("exported:", await flush());
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 OTEL_SERVICE_NAME=checkout elide run checkout.ts
charged order 42, sampled: true
exported: true

The collector receives checkout with its child charge card, and the console.log line as a log record with their trace id.

Metrics instruments

TypeScriptObservability

A counter, a histogram with explicit buckets, and an observable gauge.

// metrics.ts
import { metrics } from "@opentelemetry/api";
import { flush } from "elide:telemetry";

const meter = metrics.getMeter("shop");
const orders = meter.createCounter("shop.orders", { unit: "{order}" });
const latency = meter.createHistogram("shop.charge.duration", {
  unit: "s",
  advice: { explicitBucketBoundaries: [0.05, 0.1, 0.25, 0.5] },
});
const queue: string[] = ["a", "b"];
meter
  .createObservableGauge("shop.queue.depth", { unit: "{job}" })
  .addCallback((result) => result.observe(queue.length));

for (const [region, seconds] of [["eu", 0.08], ["us", 0.21], ["eu", 0.04]] as const) {
  orders.add(1, { region });
  latency.record(seconds, { region });
}

console.log("exported:", await flush());
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 OTEL_SERVICE_NAME=shop elide run metrics.ts
exported: true

The collector’s debug output, trimmed:

InstrumentationScope shop
     -> Name: shop.orders
Data point attributes:
     -> region: Str(eu)
Value: 2.000000
     -> Name: shop.charge.duration
Data point attributes:
     -> region: Str(eu)
Count: 2
Sum: 0.120000
     -> Name: shop.queue.depth
Value: 2.000000

Python span and logging

PythonObservability

Needs the Python component. logging output is exported as log records with the span’s ids; no opentelemetry package is needed.

# orders.py
import logging

import elide.telemetry
from opentelemetry import trace

logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s: %(message)s")
log = logging.getLogger("orders")
tracer = trace.get_tracer("orders")

with tracer.start_as_current_span("import orders") as span:
    span.set_attribute("orders.count", 3)
    log.info("imported %d orders", 3)
    print("sampled:", elide.telemetry.current_span_context()["trace_flags"] == 1)

print("exported:", elide.telemetry.flush())
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 OTEL_SERVICE_NAME=orders elide python orders.py
INFO orders: imported 3 orders
sampled: True
exported: True

OTLP to a local collector

TypeScriptHTTPObservability

A served handler continues the caller’s trace: the server span takes the traceparent parent, and the handler’s own span is its child.

// server.ts
import { trace } from "@opentelemetry/api";

const tracer = trace.getTracer("inventory");

export default {
  async fetch(request: Request): Promise<Response> {
    const id = new URL(request.url).pathname.split("/").pop() ?? "";
    return tracer.startActiveSpan("load item", async (span) => {
      span.setAttribute("item.id", id);
      span.end();
      return Response.json({ id, stock: 7 });
    });
  },
};
docker run --rm -d -p 4317:4317 -p 4318:4318 otel/opentelemetry-collector:0.115.1
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 OTEL_SERVICE_NAME=inventory elide serve server.ts
curl -H 'traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01' http://localhost:8080/items/17
{"id":"17","stock":7}

The collector’s debug output, trimmed:

InstrumentationScope inventory
    Trace ID       : 4bf92f3577b34da6a3ce929d0e0e4736
    Parent ID      : 6a28bf795e58acfb
    Name           : load item
     -> item.id: Str(17)
InstrumentationScope dev.elide.runtime 1.6.0
    Trace ID       : 4bf92f3577b34da6a3ce929d0e0e4736
    Parent ID      : 00f067aa0ba902b7
    ID             : 6a28bf795e58acfb
    Name           : GET
    Kind           : Server
     -> url.path: Str(/items/17)
     -> http.response.status_code: Int(200)
     -> Name: http.server.request.duration
Exemplar #0
     -> Trace ID: 4bf92f3577b34da6a3ce929d0e0e4736
     -> Span ID: 6a28bf795e58acfb

Baggage across services

TypeScriptHTTPObservability

Elide’s server ignores an incoming baggage header until inbound baggage is on, and then admits only allowed keys.

// server.ts
import { context, propagation } from "@opentelemetry/api";

export default {
  async fetch(): Promise<Response> {
    const baggage = propagation.getBaggage(context.active());
    const tenant = baggage?.getEntry("tenant")?.value ?? "none";
    const debug = baggage?.getEntry("debug")?.value ?? "none";
    return new Response(`tenant=${tenant} debug=${debug}\n`);
  },
};
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 \
ELIDE_OTEL_BAGGAGE_INBOUND=true ELIDE_OTEL_BAGGAGE_ALLOW=tenant \
elide serve server.ts
curl -H 'baggage: tenant=acme,debug=true' http://localhost:8080/
tenant=acme debug=none

Without ELIDE_OTEL_BAGGAGE_INBOUND=true, the response is tenant=none debug=none.

Was this page helpful?