Examples

Every program on this page is a real file in examples/, and the output under each one is what it actually prints. The code below is inlined from those files when this page is built, so it cannot drift out of step with them. Clone the repo and run them, or paste one into a file and run that.

They are all .sx, because that is the language this project is for. Everything a .sx file can do here, a plain .js, .mjs or .ts file can do too — the annotations and the ownership syntax are the only difference, and the quick start shows the same program in each.

Types and borrows

examples/hello.sx

// Run it: sxn examples/hello.sx
//
// This is a .sx file, so the type annotations below are parsed and stripped
// by the runtime itself. There is no tsc, no bundler, and no build step --
// sxn reads this file and runs it.

interface Repo {
  name: string;
  stars: i32;
}

const describe = (repo: Repo): string =>
  `${repo.name} has ${repo.stars} star${repo.stars === 1 ? "" : "s"}`;

console.log(describe({ name: "sxfescript", stars: 1 }));

// `let mut` is a mutable owner, `let` an immutable one. `&mut` borrows a
// value exclusively, so a function can change what it was handed without
// taking ownership of it.
interface Counter {
  hits: i32;
}

function bump(c: &mut Counter): void {
  c.hits += 1;
}

let mut counter: Counter = { hits: 0 };
bump(&mut counter);
bump(&mut counter);
console.log(`counter: ${counter.hits}`);
sxn examples/hello.sx
sxfescript has 1 star
counter: 2

A fixed-layout struct

examples/velocity.sx

An interface whose fields are all primitives (i32, f32, f64, bool) describes a struct with declared field order and natural alignment — the same layout on every supported target, which is what code crossing into native memory needs.

interface Transform {
    x: f32;
    y: f32;
    z: f32;
}

const applyVelocity = (transform: &mut Transform, velocity: &Transform, dt: f32): void => {
    transform.x += velocity.x * dt;
    transform.y += velocity.y * dt;
    transform.z += velocity.z * dt;
};

let mut pos: Transform = { x: 0.0, y: 10.0, z: 5.0 };
let vel: Transform = { x: 1.0, y: 0.0, z: 0.0 };
applyVelocity(&mut pos, &vel, 0.016);
console.log(JSON.stringify(pos));
sxn examples/velocity.sx
{"x":0.016,"y":10,"z":5}

An HTTP server

examples/server.sx

The handler receives a Request and returns a Response. req.url is absolute, so new URL(req.url) gives you the path and query, and await req.json() reads the body.

// An HTTP server. Run it: sxn examples/server.sx
//
// The handler takes a Request and returns a Response, the same two objects a
// handler on Cloudflare Workers, Deno or Bun receives. `port: 0` asks the OS
// for a free port; pass a real one to pick it yourself.

interface Note {
  id: number;
  text: string;
}

const notes: Map<number, string> = new Map([[1, "the first note"]]);
let mut nextId: number = 2;

const server = Sxn.serve({ port: 0 }, async (req: Request): Promise<Response> => {
  const url = new URL(req.url);

  if (url.pathname === "/") {
    return new Response("try /notes");
  }

  if (url.pathname === "/notes" && req.method === "GET") {
    const all: Note[] = [...notes].map(([id, text]) => ({ id, text }));
    return Response.json(all);
  }

  if (url.pathname === "/notes" && req.method === "POST") {
    const { text } = await req.json();
    const note: Note = { id: nextId++, text };
    notes.set(note.id, note.text);
    return Response.json(note, { status: 201 });
  }

  return new Response("not found", { status: 404 });
});

console.log(`listening on ${server.url}`);

// Call the server we just started, from the same process.
const created = await fetch(`${server.url}/notes`, {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ text: "written by the example" }),
});
console.log("POST /notes ->", created.status, await created.text());

const all = await fetch(`${server.url}/notes`);
console.log("GET  /notes ->", all.status, await all.text());

// Without stop() the listening socket keeps the process alive, which is what
// you want for a real server and not for a script that has finished.
server.stop();
sxn examples/server.sx
listening on http://127.0.0.1:56690
POST /notes -> 201 {"id":2,"text":"written by the example"}
GET  /notes -> 200 [{"id":1,"text":"the first note"},{"id":2,"text":"written by the example"}]

The port differs every run, because port: 0 asks the operating system to pick a free one. Pass a real port number to choose it yourself.

fetch and streams

examples/fetch.sx

A response body is a real ReadableStream, so it can be piped and consumed a chunk at a time rather than only read whole.

// fetch and Web Streams. Run it: sxn examples/fetch.sx
//
// fetch is the global one from the Fetch standard, backed by libcurl. The
// response body is a real ReadableStream, so it can be consumed a chunk at a
// time instead of all at once.

const res: Response = await fetch("https://example.com/");
console.log(res.status, res.headers.get("content-type"));

// Read it as text, in whole.
const html: string = await res.text();
console.log(`${html.length} bytes`);

// Or a chunk at a time, decoding as the bytes arrive.
const streamed: Response = await fetch("https://example.com/");
let mut chunks: number = 0;
let mut characters: number = 0;
for await (const chunk of streamed.body.pipeThrough(new TextDecoderStream())) {
  chunks += 1;
  characters += chunk.length;
}
console.log(`${chunks} chunk(s), ${characters} characters`);
sxn examples/fetch.sx
200 text/html
559 bytes
1 chunk(s), 559 characters

Files

examples/files.sx

Sxn.file and Sxn.write are the runtime's own file I/O. The node:fs import is the compatibility layer reading the same file back, for code that already expects Node.

// Reading and writing files. Run it: sxn examples/files.sx
//
// Sxn.file(path) and Sxn.write(path, data) are the runtime's own file I/O.
// The node:fs import below is the compatibility layer reading the same file,
// for code that already expects Node.

import { tmpdir } from "node:os";
import { join } from "node:path";
import { readFile } from "node:fs/promises";

const path: string = join(tmpdir(), "sxn-example.txt");

await Sxn.write(path, "written by the example\n");

const file = Sxn.file(path);
console.log(JSON.stringify(await file.text()));

// The same file through the Node surface.
console.log("via node:fs ->", JSON.stringify(await readFile(path, "utf8")));

console.log("sxn version:", Sxn.version);
sxn examples/files.sx
"written by the example\n"
via node:fs -> "written by the example\n"
sxn version: 0.0.1

Calling a C function

examples/ffi.sx

Sxn.ffi(library, symbol, argumentTypes, returnType) returns a callable function, through libffi and dlopen.

// Calling a C function directly. Run it: sxn examples/ffi.sx
//
// Sxn.ffi(library, symbol, argumentTypes, returnType) returns a callable
// SxfeScript function, through libffi and dlopen. This is an engine
// capability, not a Node one -- see spec/NATIVE.md for the type list and for
// what is deliberately unsupported (structs by value, callbacks, variadics).

type LibraryName = string;

const libm: LibraryName | undefined = {
  darwin: "libSystem.B.dylib",
  linux: "libm.so.6",
  win32: "msvcrt.dll",
}[process.platform];

if (!libm) throw new Error(`no libm name known for ${process.platform}`);

const pow = Sxn.ffi(libm, "pow", ["f64", "f64"], "f64");
const sqrt = Sxn.ffi(libm, "sqrt", ["f64"], "f64");

console.log("pow(2, 10) =", pow(2, 10));
console.log("sqrt(144)  =", sqrt(144));
sxn examples/ffi.sx
pow(2, 10) = 1024
sqrt(144)  = 12

Structs by value, callbacks and variadics are rejected rather than half-supported. The native-code spec has the full type list and the reasoning.

This page is generated from docs/guide/examples.md. Machine-readable copies of the whole set: llms.txt, llms-full.txt.