# Super.js — Full Documentation > A strict, type-safe superset of JavaScript that follows the ECMAScript standard. Sound types, null safety, sum types, no any — compiles to clean JS today, with native and WASM backends on the roadmap. This file inlines every documentation page in reading order. ======================================================================== # Introduction Source: https://superjs.org/docs/intro # Introduction to SuperJS SuperJS (SJS) is a strict, type-safe superset of JavaScript that follows the ECMAScript standard — every valid `.js` file is also valid `.sjs`, with every modern JS feature (ES5 through ES2025) type-checked. It adds a sound, Go-inspired type system designed for clarity and safety. It compiles to clean JavaScript today; native binaries and WebAssembly are on the roadmap (see [v2.0](https://github.com/hbarve1/super-js/blob/main/specs/roadmap/v2.0-native-compiler.md)) — one source, every target. SuperJS is **not** TypeScript. It deliberately discards the parts of TypeScript that make reasoning about types difficult (`any`, mapped types, conditional types, `infer`) and replaces them with simpler, safer constructs: sum types, match expressions, and non-nullable types by default. --- ## How SJS Differs from TypeScript | Feature | TypeScript | SuperJS | |---------|-----------|---------| | `any` | Allowed (soundness hole) | Banned — use `dynamic` instead | | Null safety | Opt-in (`strictNullChecks`) | On by default; `T?` is the nullable form | | Mapped types | Supported | Banned — use object types | | Conditional types | Supported | Banned — use sum types | | Algebraic sum types | Not supported | First-class: `type R = Ok(T) \| Err(E)` | | Match expressions | Not supported | Built-in with exhaustiveness checking | | `!` non-null assertion | Supported | Banned — use narrowing | | JSX | Requires config | On by default | | Type philosophy | Structural + escape hatches | Sound, gradual, Go-inspired | --- ## The 10 SJS Types `number` `string` `boolean` `bigint` `symbol` `void` `null` `never` `dynamic` `object T` Types are **non-nullable by default**. A `string` cannot hold `null`. Use `string?` to declare a nullable string. --- ## Quick Start Install the CLI from npm — a self-contained bundle with no runtime dependencies: ```bash npm install -g @superjsorg/cli # provides the `superjs` command ``` Compile your first file: ```bash superjs build hello.sjs # → hello.js (+ source map) ``` Prefer the programmatic API? Install [`@superjsorg/compiler`](https://www.npmjs.com/package/@superjsorg/compiler) and call `transform()` / `compile()`. --- ## Your First SJS File ```sjs // hello.sjs const message: string = "Hello, World!" console.log(message) function greet(name: string): string { return `Hello, ${name}!` } console.log(greet("SuperJS")) ``` ### Null Safety Types are non-nullable by default. Use `T?` to opt into nullability: ```sjs function findUser(id: number): string? { if (id === 1) return "Alice" return null } const name = findUser(42) // type: string? const display = name ?? "Unknown" // ?? is type-checked against string? console.log(display) ``` ### Sum Types and Match ```sjs type Result = Ok(T) | Err(E) function divide(a: number, b: number): Result { if (b === 0) return Err("division by zero") return Ok(a / b) } const result = divide(10, 2) const msg = match result { Ok(val) => `Result: ${val}`, Err(e) => `Error: ${e}`, // compiler enforces all variants are covered } console.log(msg) ``` --- ## CLI at a Glance ```bash superjs build src/ # compile .sjs → .js (+ source maps) superjs check src/ # type-check only superjs explain SJS-E001 # explain a diagnostic code superjs init # write a default config ``` Config lives in `superjs.config.json`: ```json { "compilerOptions": { "target": "ES2022", "outDir": "dist", "strict": false } } ``` --- ## What's Next - [Language Reference](./language-reference) — full syntax and type system documentation - [Type System](./type-system) — null safety, sum types, generics, and structural object types - [Tooling](./tooling) — CLI options, config schema, and diagnostic codes - [Examples](./examples) — practical SJS code samples ======================================================================== # Language Reference Source: https://superjs.org/docs/language-reference # Language Reference SuperJS (SJS) is a strict, type-safe superset of JavaScript that follows the ECMAScript standard — every valid `.js` file is valid `.sjs`, with every feature ES5 through ES2025 type-checked. It has a sound static type system, null safety, sum types, match expressions, and JSX on by default. It compiles to clean JS today (native binaries and WASM are on the roadmap), and is **not** TypeScript with a different extension. --- ## Basic Types SJS has exactly 10 built-in types. There is no `any`. | Type | Description | |------|-------------| | `number` | Floating-point number (IEEE 754) | | `string` | UTF-16 string | | `boolean` | `true` or `false` | | `bigint` | Arbitrary-precision integer | | `symbol` | Unique symbol | | `void` | No return value | | `null` | Explicit null | | `never` | Unreachable code path | | `dynamic` | Runtime-checked escape hatch — use instead of `any` | | `object T` | Heap-allocated typed object | ```sjs const message: string = "Hello, World!" const count: number = 42 const active: boolean = true const big: bigint = 9007199254740993n function greet(name: string): string { return `Hello, ${name}!` } ``` --- ## Type Annotations Type annotations are optional. When omitted, SJS infers the type from context. In strict mode, missing annotations on public API boundaries emit `SJS-W001`. ```sjs // annotated const x: number = 10 // inferred — x is still typed as number const y = 10 // function with full annotations function add(a: number, b: number): number { return a + b } // inferred return type function addInferred(a: number, b: number) { return a + b // inferred: number } ``` --- ## Null Safety Non-nullable by default. A variable of type `string` cannot hold `null` or `undefined`. This is enforced at compile time. ```sjs const name: string = null // SJS-E001: null not assignable to string ``` To allow null, use `T?` (nullable): ```sjs function findUser(id: number): string? { if (id === 1) return "Alice" return null // OK — return type is string? } const user: string? = findUser(42) ``` Use `??` (nullish coalescing) and `?.` (optional chaining) to work with nullable values. Both are type-checked against `T?`. ```sjs const display: string = user ?? "Unknown" const length: number? = user?.length ``` **There is no `!` non-null assertion operator.** Use narrowing instead: ```sjs if (user !== null) { // user is narrowed to string here console.log(user.toUpperCase()) } ``` --- ## Sum Types Sum types (tagged unions / variant types) are a first-class SJS feature. They use a syntax distinct from TypeScript discriminated unions. ```sjs type Result = Ok(T) | Err(E) type Shape = Circle({ radius: number }) | Rect({ w: number, h: number }) type Option = Some(T) | None ``` Constructors are callable as functions: ```sjs const success: Result = Ok(42) const failure: Result = Err("something went wrong") ``` At runtime, sum type values compile to `{ _tag: "Ok", _0: 42 }` discriminated objects. SJS code never accesses `_tag` or `_0` directly — use match expressions instead. --- ## Match Expressions `match` is an expression (it returns a value) used to destructure sum types. The compiler enforces exhaustiveness — if a variant is missing and there is no `default` branch, `SJS-E007` is emitted. ```sjs function divide(a: number, b: number): Result { if (b === 0) return Err("division by zero") return Ok(a / b) } const r = divide(10, 2) const msg = match r { Ok(val) => `Result: ${val}`, Err(e) => `Error: ${e}`, } ``` Destructuring works for variants with payload objects: ```sjs type Shape = Circle({ radius: number }) | Rect({ w: number, h: number }) const area = match shape { Circle({ radius }) => Math.PI * radius * radius, Rect({ w, h }) => w * h, } ``` Use `default` for partial matches: ```sjs const label = match status { Ok(_) => "success", default => "failure", } ``` --- ## Structural Object Types SJS object types are satisfied implicitly — Go-style. A type satisfies an object type if it has all the required members. No `implements` keyword is needed or supported. Object types use the brace form of `type` (no `=`). ```sjs type Shape { area(): number perimeter(): number } class Circle { constructor(public radius: number) {} area(): number { return Math.PI * this.radius ** 2 } perimeter(): number { return 2 * Math.PI * this.radius } } class Rect { constructor(public w: number, public h: number) {} area(): number { return this.w * this.h } perimeter(): number { return 2 * (this.w + this.h) } } // Both Circle and Rect satisfy Shape — no declaration needed function printShape(s: Shape): void { console.log(`Area: ${s.area()}, Perimeter: ${s.perimeter()}`) } printShape(new Circle(5)) printShape(new Rect(4, 6)) ``` Object types can extend other object types: ```sjs type Printable { toString(): string } type Serializable extends Printable { serialize(): string } ``` **Intersection types (`A & B`) are banned.** Compose object types with `extends` instead. --- ## Generics Generics use angle-bracket syntax and are monomorphized at compile time. ```sjs function identity(x: T): T { return x } function max(a: T, b: T): T { return a.compareTo(b) > 0 ? a : b } ``` Generic classes: ```sjs class Stack { private items: T[] = [] push(item: T): void { this.items.push(item) } pop(): T? { return this.items.pop() ?? null } peek(): T? { return this.items[this.items.length - 1] ?? null } get size(): number { return this.items.length } } const s = new Stack() s.push(1) s.push(2) const top: number? = s.pop() // number? ``` Generic object types: ```sjs type Container { get(): T? set(value: T): void } ``` **Banned generic features:** conditional types (`T extends U ? A : B`), `infer`, mapped types (`{ [K in keyof T]: ... }`), and template literal types are not part of SJS. --- ## Classes SJS classes are standard JavaScript classes with type annotations. Constructor parameter shorthand is supported. ```sjs class Point { constructor( public x: number, public y: number ) {} distanceTo(other: Point): number { return Math.sqrt((this.x - other.x) ** 2 + (this.y - other.y) ** 2) } toString(): string { return `Point(${this.x}, ${this.y})` } } const p1 = new Point(0, 0) const p2 = new Point(3, 4) console.log(p1.distanceTo(p2)) // 5 ``` Inheritance uses standard `extends`: ```sjs class Animal { constructor(public name: string) {} speak(): string { return `${this.name} makes a sound` } } class Dog extends Animal { speak(): string { return `${this.name} barks` } } ``` --- ## JSX JSX is on by default in SJS — no pragma or config needed. ```sjs type ButtonProps { label: string onClick: () => void disabled?: boolean } function Button({ label, onClick, disabled = false }: ButtonProps) { return } function App() { return (

Hello

) } ``` The JSX transform targets the React 17+ automatic runtime by default. Configure the runtime in `superjs.config.json`. --- ## Modules SJS uses standard ES module syntax: ```sjs // Named exports export function add(a: number, b: number): number { return a + b } export class Calculator { // ... } // Default export export default class App { // ... } // Type-only import (erased at compile time) import type { UserRecord } from './types' // Value imports import { readFileSync } from 'fs' import { add, Calculator } from './math' import App from './app' ``` Re-exports: ```sjs export { add } from './math' export type { UserRecord } from './types' export * from './utils' ``` Imports resolve to the exporting module's real types — named, default, namespace (`import * as M`), and `export … from` re-exports all carry types across files. Relative specifiers resolve against the importing file; bare specifiers resolve through `superjs.config.json` `paths` (how `superjs add` wires in package types). An unresolved specifier leaves its bindings `dynamic` rather than erroring. --- ## The `dynamic` Type `dynamic` is the runtime-checked escape hatch. Use it when interfacing with untyped external data (JSON responses, third-party libraries without types). It is **not** `any` — accesses on `dynamic` values are checked at runtime and do not silently propagate through the type system. ```sjs function parseConfig(raw: string): dynamic { return JSON.parse(raw) } const config: dynamic = parseConfig('{"port": 3000}') const port = config.port // runtime-checked ``` Assigning a `dynamic` value to a typed variable requires an explicit narrowing check. Unlike `any`, `dynamic` never silently widens the types of surrounding expressions. --- ## What Is Banned (and Why) These features from TypeScript are **permanently excluded** from SJS to keep the type system sound and simple: | Banned Feature | Use Instead | |----------------|-------------| | `any` | `dynamic` | | `T extends U ? A : B` (conditional types) | Sum types + match | | `{ [K in keyof T]: ... }` (mapped types) | Explicit object types | | Template literal types | — | | `infer` | — | | `namespace` | ES modules | | TypeScript `enum` | Sum types | | `A & B` (intersection types) | Object type extension (`extends`) | | `!` non-null assertion | Narrowing (`if (x !== null)`) | These are not missing features — they are deliberate omissions. SJS prioritizes a sound, predictable type system over maximum expressiveness. --- ## Diagnostic Codes | Code | Severity | Meaning | |------|----------|---------| | `SJS-E001` | Error | Null/undefined assigned to non-nullable type | | `SJS-E002` | Error | Type mismatch on assignment or return | | `SJS-E007` | Error | Non-exhaustive match on sum type | | `SJS-W001` | Warning | Implicit `dynamic` — only in strict mode | --- ## CLI Quick Reference ```sh superjs build src/index.sjs # compile to JS superjs build --watch # watch mode superjs lint src/ # lint superjs format src/ # format superjs test # run tests ``` ======================================================================== # Examples Source: https://superjs.org/docs/examples # Examples Practical SJS examples organized by category. Every example uses valid SJS syntax and can be compiled with `superjs build`. ## 1. Basics ### Hello World ```sjs // hello-world.sjs const message: string = "Hello, World!" console.log(message) function greet(name: string): string { return `Hello, ${name}!` } console.log(greet("Super.js")) ``` ### Variables and Type Annotations ```sjs // variables.sjs const count: number = 42 const label: string = "items" const active: boolean = true // Type inference — annotation optional when value is clear const ratio = 0.75 // inferred: number const title = "SuperJS" // inferred: string console.log(`${count} ${label}`) ``` ### Functions ```sjs // functions.sjs function add(a: number, b: number): number { return a + b } function repeat(text: string, times: number): string { return text.repeat(times) } const double = (n: number): number => n * 2 console.log(add(3, 4)) // 7 console.log(repeat("ha", 3)) // hahaha console.log(double(10)) // 20 ``` ## 2. Null Safety SJS types are non-nullable by default. Append `?` to allow `null` or `undefined`. ```sjs // null-safety.sjs function findUser(id: number): string? { if (id === 1) return "Alice" return null } const name = findUser(42) // type: string? const display = name ?? "Unknown" console.log(display) // "Unknown" ``` ### Optional Chaining and Nullish Coalescing ```sjs type Address { street: string city: string zip: string? } type User { name: string address: Address? } function getZip(user: User): string { return user.address?.zip ?? "N/A" } ``` ### Nullable Return Types ```sjs function parseInt10(s: string): number? { const n = parseInt(s, 10) return isNaN(n) ? null : n } const result = parseInt10("abc") if (result !== null) { console.log(result * 2) } ``` ## 3. Sum Types and Match Sum types declare a closed set of variants. The `match` expression handles each variant and the compiler enforces exhaustiveness (SJS-E007). ### Result Type ```sjs // result.sjs type Result = Ok(T) | Err(E) function divide(a: number, b: number): Result { if (b === 0) return Err("division by zero") return Ok(a / b) } const r = divide(10, 2) const msg = match r { Ok(val) => `Result: ${val}`, Err(e) => `Error: ${e}`, } console.log(msg) // "Result: 5" ``` ### Option Type ```sjs // option.sjs type Option = Some(T) | None function head(arr: T[]): Option { return arr.length > 0 ? Some(arr[0]) : None } const first = head([10, 20, 30]) const display = match first { Some(v) => `First: ${v}`, None => "Empty list", } console.log(display) // "First: 10" ``` ### Exhaustiveness Checking Missing a variant produces a SJS-E007 error at compile time: ```sjs type Color = Red | Green | Blue function label(c: Color): string { return match c { Red => "red", Green => "green", Blue => "blue", } } ``` ## 4. Generics ### Generic Stack ```sjs // stack.sjs class Stack { private items: T[] = [] push(item: T): void { this.items.push(item) } pop(): T? { if (this.items.length === 0) return null return this.items.pop() ?? null } peek(): T? { return this.items.length > 0 ? this.items[this.items.length - 1] : null } get size(): number { return this.items.length } } const stack = new Stack() stack.push(1) stack.push(2) stack.push(3) console.log(stack.pop()) // 3 console.log(stack.size) // 2 ``` ### Generic Functions ```sjs function identity(value: T): T { return value } function first(arr: T[]): T? { return arr.length > 0 ? arr[0] : null } function zip(as: A[], bs: B[]): [A, B][] { const len = Math.min(as.length, bs.length) const result: [A, B][] = [] for (let i = 0; i < len; i++) { result.push([as[i], bs[i]]) } return result } console.log(zip([1, 2, 3], ["a", "b", "c"])) // [[1, "a"], [2, "b"], [3, "c"]] ``` ## 5. Structural Object Types SJS object types are structural — a class satisfies an object type by having the right shape, no `implements` keyword required. Object types use the brace form of `type` (no `=`). ```sjs type Shape { area(): number perimeter(): number } class Circle { constructor(private radius: number) {} area(): number { return Math.PI * this.radius ** 2 } perimeter(): number { return 2 * Math.PI * this.radius } } class Rectangle { constructor(private width: number, private height: number) {} area(): number { return this.width * this.height } perimeter(): number { return 2 * (this.width + this.height) } } function describe(s: Shape): string { return `area=${s.area().toFixed(2)}, perimeter=${s.perimeter().toFixed(2)}` } console.log(describe(new Circle(5))) console.log(describe(new Rectangle(4, 6))) ``` ### Object Type Composition ```sjs type Named { name: string } type Aged { age: number } type Person extends Named, Aged { email: string } function greet(p: Person): string { return `Hello, ${p.name} (age ${p.age})` } ``` ## 6. JSX JSX is enabled by default in SJS. Use it directly in `.sjs` files with no extra configuration. ### Typed Props ```sjs // card.sjs type CardProps { title: string body: string footer?: string } function Card({ title, body, footer }: CardProps) { return (

{title}

{body}

{footer &&
{footer}
}
) } ``` ### Component with Event Handlers ```sjs type ButtonProps { label: string onClick: () => void disabled?: boolean } function Button({ label, onClick, disabled = false }: ButtonProps) { return ( ) } ``` ### List Rendering ```sjs type ListProps { items: string[] emptyMessage?: string } function List({ items, emptyMessage = "Nothing here." }: ListProps) { if (items.length === 0) { return

{emptyMessage}

} return (
    {items.map((item, i) => (
  • {item}
  • ))}
) } ``` ## 7. Node.js Usage ### File Analysis CLI Tool ```sjs // analyze.sjs import fs from 'fs' import path from 'path' type FileStats { path: string lines: number sizeBytes: number } type AnalysisResult = Success(T) | Failure(string) function analyzeFile(filePath: string): AnalysisResult { try { const content = fs.readFileSync(filePath, 'utf-8') return Success({ path: filePath, lines: content.split('\n').length, sizeBytes: Buffer.byteLength(content), }) } catch (e) { return Failure(`Cannot read ${filePath}`) } } const result = analyzeFile(process.argv[2]) match result { Success(stats) => console.log(`${stats.lines} lines, ${stats.sizeBytes} bytes`), Failure(msg) => console.error(msg), } ``` ### Directory Walker ```sjs // walk.sjs import fs from 'fs' import path from 'path' function walkDir(dir: string): string[] { const entries = fs.readdirSync(dir, { withFileTypes: true }) const files: string[] = [] for (const entry of entries) { const full = path.join(dir, entry.name) if (entry.isDirectory()) { files.push(...walkDir(full)) } else { files.push(full) } } return files } const target = process.argv[2] ?? "." const files = walkDir(target) console.log(`Found ${files.length} files`) files.forEach(f => console.log(f)) ``` ### HTTP Server ```sjs // server.sjs import http from 'http' type Route { method: string path: string handler: (body: string) => string } const routes: Route[] = [ { method: "GET", path: "/", handler: () => JSON.stringify({ message: "Hello from SJS!" }), }, ] const server = http.createServer((req, res) => { const route = routes.find( r => r.method === req.method && r.path === req.url ) if (!route) { res.writeHead(404) res.end(JSON.stringify({ error: "Not found" })) return } let body = "" req.on("data", chunk => { body += chunk }) req.on("end", () => { res.writeHead(200, { "Content-Type": "application/json" }) res.end(route.handler(body)) }) }) server.listen(3000, () => console.log("Listening on :3000")) ``` ======================================================================== # Type System Source: https://superjs.org/docs/type-system # Type System SuperJS extends JavaScript with a sound, gradual type system. Types are non-nullable by default, sum types replace discriminated unions, and `match` replaces `switch` for exhaustive dispatch. ## What's banned These TypeScript features do not exist in SJS: | Feature | Reason | |---|---| | `any` | Silent unsafety — use `dynamic` or `unknown` instead | | `A & B` intersection types | Unsound merging — compose object types with `extends` | | `T extends U ? X : Y` conditional types | Complexity without soundness gain | | Mapped types (`{ [P in keyof T]: ... }`) | Implicit metaprogramming — spell out the shape | | `T!` non-null assertion | Defeats null safety — narrow explicitly | | `enum` | Use `type` union literals instead | --- ## Null safety All types are **non-nullable by default**. Append `?` to opt into nullability. ```sjs let name: string = "Alice" // cannot be null let nickname: string? = null // OK function greet(user: string?): string { if (user === null) return "Hello, stranger" return `Hello, ${user}` } ``` Nullable types must be narrowed before use. SJS emits `SJS-E001` on unsafe access. --- ## Primitive types ```sjs let s: string = "hello" let n: number = 42 let b: boolean = true let sym: symbol = Symbol("key") let big: bigint = 123n ``` --- ## Object types Object types describe structural shapes. They use the brace form of `type` (no `=`). SJS uses structural typing — any value that satisfies the shape is compatible. ```sjs type User { id: number name: string email: string age?: number // optional property readonly createdAt: Date } type AdminUser extends User { role: string permissions: string[] } ``` Index signatures use `unknown`, not `any`: ```sjs type Registry { [key: string]: unknown } ``` --- ## Sum types Sum types replace discriminated unions. Each variant is a constructor that carries typed payload. ```sjs type Shape = | Circle(radius: number) | Rect(width: number, height: number) | Point type Result = | Ok(value: T) | Err(error: E) ``` Constructing and consuming: ```sjs const s: Shape = Circle(5) const area = match s { Circle(r) => Math.PI * r * r, Rect(w, h) => w * h, Point => 0, } ``` --- ## Match expressions `match` is exhaustive — SJS emits `SJS-E008` if a variant arm is missing. ```sjs type ApiResult = Ok(data: string) | NotFound | ServerError(code: number) function handle(r: ApiResult): string { return match r { Ok(data) => data, NotFound => "404", ServerError(code) => `Error ${code}`, } } ``` Use `_` for a catch-all arm: ```sjs return match r { Ok(data) => data, _ => "failed", } ``` --- ## Generics Standard generic syntax. Constrain with `extends`: ```sjs function first(arr: T[]): T? { return arr.length > 0 ? arr[0] : null } type Repository { find(id: number): T? save(item: T): void findAll(): T[] } class Stack { private items: T[] = [] push(item: T): void { this.items.push(item) } pop(): T? { return this.items.pop() ?? null } get size(): number { return this.items.length } } ``` Generic constraints: ```sjs type Measurable { length: number } function longest(a: T, b: T): T { return a.length >= b.length ? a : b } ``` --- ## The `dynamic` escape hatch `dynamic` opts a value out of type checking. Use only at JS interop boundaries — never inside pure SJS code. ```sjs // wrapping an untyped third-party library const raw: dynamic = require('some-legacy-lib').getData() // narrow before use if (typeof raw === 'string') { console.log(raw.toUpperCase()) } ``` `dynamic` propagates: operations on a `dynamic` value return `dynamic`. Narrow explicitly with `typeof`, `instanceof`, or a sum-type guard before using the value in typed code. --- ## Type inference SJS infers types for variable initializers, function return types, and array/object literals: ```sjs const message = "hello" // inferred: string const items = [1, 2, 3] // inferred: number[] const user = { name: "Alice" } // inferred: { name: string } function double(n: number) { return n * 2 // inferred return: number } ``` --- ## Structural typing SJS is structurally typed. A value satisfies an object type if it has the required shape — no `implements` needed at call sites. ```sjs type Point { x: number; y: number } function distance(a: Point, b: Point): number { return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2) } // Any object with x and y satisfies Point distance({ x: 0, y: 0 }, { x: 3, y: 4 }) // OK ``` --- ## Narrowing Narrow nullable and `unknown` values with standard JS checks. SJS flow-analyzes these: ```sjs function process(val: string | number): string { if (typeof val === "string") { return val.toUpperCase() // val: string here } return val.toString() // val: number here } function display(name: string?): string { if (name === null) return "anonymous" return name // name: string here } ``` --- ## Error codes | Code | Condition | |---|---| | `SJS-E001` | Nullable access without null check | | `SJS-E002` | Type mismatch on assignment or return | | `SJS-E003` | Missing required property | | `SJS-E004` | Use of banned syntax (`any`, `A & B`, etc.) | | `SJS-E008` | Non-exhaustive `match` (missing variant arm) | | `SJS-W001` | Implicit `dynamic` propagation | ======================================================================== # JavaScript & TypeScript Interop Source: https://superjs.org/docs/interop # JavaScript & TypeScript Interop > **Status: planned (Stage 2).** Interop is specified and on the roadmap; it is > not shipped yet. This page documents the designed behavior so you can plan > migrations. Today, consume untyped JS through [`dynamic`](/docs/type-system). Super.js runs on the JavaScript runtime, so it must consume the existing npm ecosystem. The goal: bring third-party types in **without** importing TypeScript's unsound features. Anything that can't be translated soundly becomes an explicit, greppable `dynamic` — never a silent `any`. ## Translating a declaration file today The `.d.ts` → `.d.sjs` translator is available now via the [`superjs translate`](/docs/cli#translate) command — point it at any TypeScript declaration file and it writes the SuperJS equivalent: ```bash superjs translate node_modules/some-lib/index.d.ts --out-dir types ``` Forms SuperJS doesn't model degrade to `dynamic` and are reported as warnings. ## How a whole package gets types (planned) Running `superjs add ` (Stage 2) will resolve a package and obtain SJS type declarations (`.d.sjs`) in this order: 1. **Curated wrapper** — a hand-maintained `@superjs/types-` on npm (preferred). 2. **Automatic translation** — the translator reads the package's `.d.ts` and emits `.d.sjs`. The result is written under `node_modules/@superjs/types//` and wired up via the `paths` map in your `superjs.config.json`. The translator depends on `typescript` (pinned, **dev/tools tier only**) to walk the TypeScript AST. The Super.js core and runtime stay TypeScript-free. ## Type mapping rules Constructs that map soundly are preserved; constructs that don't are lowered to `dynamic` with an explicit, machine-checkable reason. | TypeScript | Super.js | Notes | |------------|----------|-------| | `unknown` | `unknown` | Top type — must be narrowed before use | | `any` | `dynamic` | Marked `// @sjs:dynamic reason="any"` | | `A \| B` unions | `A \| B` | Preserved | | `interface` / `type` aliases | `type` (brace or alias form) | TS `interface X {}` → SJS `type X {}`; `type` aliases stay aliases | | ``, ``, `` | preserved | Generics, constraints, defaults | | `enum` | string/number union | `enum E { A, B }` → `"A" \| "B"` | | `A & B` intersection | merged object type, else `dynamic` | Auto-merges when fields don't conflict | | Conditional / mapped / template-literal types | `dynamic` | Marked with the matching reason | | `infer`, `keyof T`, `T[K]`, `this` types | `dynamic` | Marked with the matching reason | ### The `// @sjs:dynamic` marker Every unmappable construct emits an explicit marker drawn from a **closed set** of reasons — the translator's CI rejects any reason outside the set, so a fallback to `dynamic` can never be silent: ```sjs // @sjs:dynamic reason="conditional-type" // @sjs:dynamic reason="mapped-type" // @sjs:dynamic reason="intersection-not-mergeable" // @sjs:dynamic reason="infer-type" // @sjs:dynamic reason="keyof" ``` ## Coverage target Wrapper quality is measured by **typed surface** — the share of identifier positions whose translation is *not* `dynamic`. The target is ≥ 70% average across the most-used packages, with each wrapper shipping a `STATUS.md` reporting its typed-surface percentage, license audit, and ESM/CJS support. ## Current limitations These TypeScript features cannot be translated soundly and fall back to `dynamic`: - Conditional types (`T extends U ? A : B`) - Mapped types (`{ [K in keyof T]: ... }`) - Intersections with conflicting fields - `infer`, `keyof`, indexed access (`T[K]`), and `this` types This is by design — see [banned features](/docs/type-system) for why Super.js keeps these out of the language itself. ## Runtime boundary The `@superjs/interop` package (which powers `superjs translate`) is also planned to provide runtime helpers for validating values as they cross the JS→SJS boundary, so a `dynamic` from the outside world is checked before it becomes a typed value rather than trusted blindly. ======================================================================== # Tooling Source: https://superjs.org/docs/tooling # Tooling SuperJS ships a unified CLI that covers every stage of the development workflow: compilation, linting, formatting, and testing. There are no separate packages to install or plugins to configure. ## Installation Install the CLI from npm — a self-contained bundle with no runtime dependencies: ```bash npm install -g @superjsorg/cli ``` This makes the `superjs` command available globally. ## CLI Reference Commands take one or more file or directory paths as positional arguments — a directory expands to every `.sjs` file under it. ### `superjs build` Compiles `.sjs` files to JavaScript (plus source maps). ```bash superjs build [--out-dir ] [--source-map none|inline|external] [--watch] [--no-cache] ``` **Flags:** | Flag | Description | |------|-------------| | `--out-dir ` | Output directory (default: `dist`) | | `--source-map ` | `none` (default), `inline`, or `external` (`.js.map` alongside output) | | `--watch` | Watch mode — recompile on file change | | `--no-cache` | Disable the incremental build cache | **Examples:** ```bash # Compile a single file to ./dist superjs build src/main.sjs # Compile a whole directory with external source maps superjs build src --out-dir dist --source-map external # Watch mode during development superjs build src --out-dir dist --watch ``` ### `superjs check` Type-checks without emitting output. Exits non-zero if any errors are found. ```bash superjs check [--format pretty|json] ``` | Flag | Description | |------|-------------| | `--format ` | `pretty` (default, colored terminal) or `json` (machine-readable) | ```bash superjs check src # type-check a directory superjs check src --format json # machine-readable diagnostics for CI ``` ### `superjs translate` Translates TypeScript `.d.ts` declaration files into SuperJS `.d.sjs`, so you can consume existing typed packages. Each file is written next to its source, or under `--out-dir`. ```bash superjs translate types.d.ts # → types.d.sjs superjs translate types.d.ts --out-dir gen # → gen/types.d.sjs ``` | Flag | Description | |------|-------------| | `--out-dir ` | Write the `.d.sjs` output to this directory instead of alongside the source | Enums translate to sum types, and intersections of object literals merge into one object type. TypeScript constructs SuperJS doesn't model (conditional, mapped, `infer`, `any`, unmergeable intersections, …) degrade to `dynamic`, and top-level functions/classes/namespaces are flagged as not-yet-translated — every case is reported as a warning, never silently dropped: ```text $ superjs translate types.d.ts warning: intersection `A & B` mapped to `dynamic` (reason: intersection-not-mergeable) translated types.d.ts → types.d.sjs ``` ### `superjs add` Resolves an installed npm package's types into SuperJS `.d.sjs` and wires them into your project. Finds the package's `.d.ts` (its own `types` entry or the DefinitelyTyped `@types/` fallback), translates it to `node_modules/@superjs/types//index.d.sjs`, and maps the import specifier in `superjs.config.json` `paths`. ```bash npm install fastify superjs add fastify # → node_modules/@superjs/types/fastify/index.d.sjs ``` Unmappable TypeScript forms degrade to `dynamic` and are reported — never silently dropped. `add` prints a typed-surface estimate (how much of the API kept a real type) and records it for `superjs doctor`. (Hand-curated wrappers for the top packages land in a later Stage 2 sprint; today `add` always translates the published `.d.ts`.) ### `superjs format` Rewrites `.sjs` files in the canonical style (2-space indent, semicolons, one statement per line). `--check` reports what would change without writing — for CI. The formatter reparses its own output and only rewrites a file when the result is provably the same program, so it never corrupts code. ```bash superjs format src/ # format in place superjs format src/ --check # CI: non-zero exit if anything would change ``` A gitignore-style `.sjsignore` at the project root excludes files from directory walks (for `format`, `lint`, `check`, `build`, and `doc`); explicitly-named files are always processed. See the [formatter integration guide](https://github.com/hbarve1/super-js/blob/main/specs/design/formatter-integration.md) for the `.sjsignore` syntax and the Prettier / Husky coexistence story. ### `superjs lint` Reports style findings (`SJS-L*`) and exits non-zero when any are present, so it gates CI. Current rules: prefer-`const` (L001), no-`var` (L002), `===`/`!==` (L003), `for…of` over `for…in` (L004), no-`debugger` (L005), no-empty-`match` (L006), no-redundant-`match`-arm (L007), prefer-arrow-callback (L008), no-unused-import (L009), import-order (L010), no-unused-var (L012), no-explicit-dynamic (L013), no-shadowing (L014), no-floating-promise (L015), no-unhandled-result (L016), prefer-result-over-throw (L017), and no-mixed-spaces-tabs (L018) — 17 rules in total. ```bash superjs lint src/ # human-readable superjs lint src/ --format json # machine-readable for CI superjs lint src/ --fix # apply auto-fixes (no-var → let, drop debugger) in place ``` ### `superjs doc` Generates API documentation from a module's exported declarations — a built-in **JSDoc / TypeDoc alternative**. SJS types are explicit and sound, so the signature is the documentation; a leading doc comment adds prose and tags. Outputs Markdown (default) or JSON. ```bash superjs doc src/api.sjs # Markdown to stdout superjs doc src/ --out-dir docs # one .md per file superjs doc src/ --format json # machine-readable ``` (MVP: signatures + doc comments. HTML site / `--serve` / validated `@example` blocks are later work — see ADR-009.) ### `superjs explain` Prints the full spec write-up for a diagnostic code (description, example, fix). ```bash superjs explain SJS-E001 superjs explain E007 # the SJS- prefix is optional ``` ### `superjs init` Writes a default `superjs.config.json` into the current directory. ### `superjs doctor` Reports environment health — Node version, config presence and validity — plus a per-package typed-surface report for everything pulled in with `superjs add`. ### `superjs lsp` Starts the SuperJS **language server** over stdio for editor integration. Speaks standard [LSP](https://microsoft.github.io/language-server-protocol/) and implements the full M1 method set: diagnostics, hover, go-to-definition, document outline, folding, completion, signature help, semantic tokens, and formatting. ```bash superjs lsp # JSON-RPC over stdin/stdout — launched by your editor ``` Normally an editor spawns it. The VS Code extension does so automatically; for Neovim, Helix, or any LSP-aware editor, point the client at `superjs lsp` — see the **Editor Setup** guide for ready-to-paste configs. ### Planned commands These are reserved and currently print a not-yet-implemented notice: - **`superjs test`** *(Stage 5)* — a **Jest alternative** for `.sjs`. Planned as both `@superjs/jest-transform` / `@superjs/vitest-transform` (run `.sjs` tests in your existing Jest/Vitest) and a native zero-config `superjs test` runner. Full plan on the [roadmap](https://github.com/hbarve1/super-js/tree/main/specs/roadmap). ## Project Configuration SuperJS looks for a `superjs.config.json` file in the project root. All fields are optional and have defaults. Compiler settings are nested under `compilerOptions`: ```json { "compilerOptions": { "strict": false, "target": "ES2022", "outDir": "dist", "sourceMap": "none", "noEmitOnError": false }, "jsx": { "runtime": "automatic", "importSource": "react" } } ``` | Field | Type | Default | Description | |-------|------|---------|-------------| | `compilerOptions.strict` | boolean | `false` | Enable strict mode (promotes SJS-W001 implicit-`dynamic` warnings to errors) | | `compilerOptions.target` | string | `"ES2022"` | Output target: `ES2020`–`ES2024` or `ESNext` | | `compilerOptions.outDir` | string | — | Output directory for compiled `.js` files | | `compilerOptions.sourceMap` | string | `"none"` | `none`, `inline`, or `external` | | `compilerOptions.noEmitOnError` | boolean | `false` | Skip emitting JS if type errors are present | | `jsx.runtime` | string | `"automatic"` | `automatic` (react/jsx-runtime) or `classic` (`React.createElement`) | | `jsx.importSource` | string | `"react"` | Import source for the automatic JSX runtime | The full schema (including `paths`, `output.variants`, `lsp`, and `env`) lives in [`specs/config-schema.json`](https://github.com/hbarve1/super-js/blob/main/specs/config-schema.json). **CLI flags always override config file values.** ## Error Output ### Terminal Output (default) By default, `superjs build` and `superjs check` print colored diagnostics to the terminal: ``` error SJS-E001 src/main.sjs:12:5 Null/undefined assigned to non-nullable type 'string'. warning SJS-W001 src/utils.sjs:8:10 Implicit 'dynamic' type — enable strict mode to enforce annotations. ``` Each diagnostic includes: - Severity (`error` / `warning`) - Diagnostic code (e.g., `SJS-E001`) - File path and line/column position - Human-readable message ### JSON Mode (`--format json`) Pass `--format json` to `superjs check` to receive machine-readable diagnostics, suitable for editor integrations, CI pipelines, and automated tooling: ```json {"severity":"error","code":"SJS-E001","file":"src/main.sjs","line":12,"col":5,"message":"Null/undefined assigned to non-nullable type 'string'."} {"severity":"warning","code":"SJS-W001","file":"src/utils.sjs","line":8,"col":10,"message":"Implicit 'dynamic' type."} ``` ## Diagnostic Codes | Code | Severity | Description | |------|----------|-------------| | SJS-E001 | error | Null or undefined assigned to a non-nullable type | | SJS-E002 | error | Type mismatch on assignment or return | | SJS-W001 | warning | Implicit `dynamic` type (strict mode only) | | SJS-E007 | error | Non-exhaustive match — a sum type variant is not handled | ### SJS-E001 — Null Safety SJS types are non-nullable by default. Use `T?` to declare a nullable type: ```sjs // error: 'string' is not nullable const name: string = null // SJS-E001 // correct: use T? for nullable const name: string? = null ``` ### SJS-E002 — Type Mismatch ```sjs const count: number = "hello" // SJS-E002: expected number, got string ``` ### SJS-W001 — Implicit Dynamic (strict mode) ```sjs // in strict mode, this emits SJS-W001 function process(data) { return data } // fix: annotate the parameter function process(data: string): string { return data } ``` ### SJS-E007 — Non-Exhaustive Match ```sjs type Status = Active | Inactive | Pending const s: Status = Active // SJS-E007: 'Pending' variant not handled const label = match s { Active => "active", Inactive => "inactive", } ``` ## Exit Codes | Code | Meaning | |------|---------| | `0` | Success — no errors | | `1` | One or more errors found | | `2` | CLI usage error (bad flags, missing arguments) | ======================================================================== # CLI Reference Source: https://superjs.org/docs/cli # CLI Reference The `superjs` binary drives compilation, type-checking, and project setup. All build behavior is configured through [`superjs.config.json`](#configuration) — the CLI keeps its flag surface small and reads the rest from config. ```bash superjs [files...] [flags] ``` ## Global flags | Flag | Effect | |------|--------| | `-h`, `--help` | Print usage and exit `0` | | `-v`, `--version` | Print the compiler version and exit `0` | ## Commands ### `build` Compile `.sjs` source files to JavaScript. ```bash superjs build src/ # compile every .sjs under src/ → dist/ superjs build app.sjs --watch # recompile on change ``` Directories are walked recursively for `.sjs` files. Output `.js` (and, when enabled, `.js.map`) is written to the output directory. | Flag | Values | Default | Effect | |------|--------|---------|--------| | `--out-dir ` | path | `dist` | Output directory | | `--source-map ` | `none` \| `inline` \| `external` | `external` | Sourcemap emission | | `--no-cache` | — | cache on | Force a cold build (ignore the `.superjs/` cache) | | `--watch` | — | off | Recompile when files change | ### `check` Type-check without emitting any output. Ideal for CI and editors. ```bash superjs check src/ # human-readable diagnostics superjs check src/ --format json # one JSON diagnostic per line ``` | Flag | Values | Default | Effect | |------|--------|---------|--------| | `--format ` | `pretty` \| `json` | `pretty` | Diagnostic output format | The `json` format emits one object per line: ```json { "code": "SJS-E001", "severity": "error", "message": "Null or undefined assigned to non-nullable type `string`", "file": "/abs/path/src/app.sjs", "line": 42, "column": 14, "endLine": 42, "endColumn": 21 } ``` ### `translate` Translate TypeScript `.d.ts` declaration files into SuperJS `.d.sjs`. Output is written next to each source file, or under `--out-dir`. ```bash superjs translate types.d.ts # → types.d.sjs superjs translate types.d.ts --out-dir gen # → gen/types.d.sjs ``` | Flag | Values | Default | Effect | |------|--------|---------|--------| | `--out-dir ` | path | alongside source | Directory for the `.d.sjs` output | Enums become sum types (`enum Color { Red, Green }` → `type Color = Red | Green`), and an intersection of object literals (`{ a } & { b }`) merges into one object type. TypeScript forms SuperJS doesn't model (conditional, mapped, `infer`, `any`, and intersections that can't be merged) degrade to `dynamic`, and top-level functions, classes and namespaces are reported as not-yet-translated — every dropped or degraded construct is surfaced as a warning, never silently lost. ### `add` Resolve an already-installed npm package's types into SuperJS so you can `import` it with type safety. `add` finds the package's `.d.ts` (its own `types`/`typings` entry, or the DefinitelyTyped `@types/` fallback), runs the translator, writes the result to `node_modules/@superjs/types//index.d.sjs`, and maps the import specifier in `superjs.config.json` `paths`. ```bash npm install fastify # install the package as usual superjs add fastify # → node_modules/@superjs/types/fastify/index.d.sjs ``` Once added, `import { … } from "fastify"` resolves to these types during `superjs check` / `superjs build` — the bare specifier is matched through the `paths` map `add` wrote. `add` prints a **typed-surface** estimate — how much of the package's API kept a real type vs degraded to `dynamic` — and records it in a `surface.json` sidecar that `superjs doctor` reads back. The same `dynamic` fallbacks as `translate` apply and are reported as warnings. > Today `add` always translates the package's published `.d.ts`. Hand-curated > `@superjs/types-` wrappers (richer types for the top packages) are > resolved first in a later Stage 2 sprint. ### `format` Rewrite `.sjs` files in the canonical style — 2-space indent, semicolons, one statement per line. There are no options to bikeshed. ```bash superjs format src/ # format every .sjs under src/, in place superjs format src/ --check # report what would change, write nothing (CI) ``` | Flag | Effect | |------|--------| | `--check` | Don't write; list files that would change and exit non-zero if any | The formatter is **safe by construction**: it reparses its own output and only rewrites a file when the result is provably the same program (identical AST). Anything it can't reproduce faithfully is left exactly as-is. A gitignore-style `.sjsignore` at the project root excludes files from directory walks (shared by `format`, `lint`, `check`, `build`, and `doc`); a file passed explicitly on the command line is always processed. ### `lint` Report style findings — the `SJS-L*` rules. Exits non-zero when any are found, so it gates CI. ```bash superjs lint src/ # human-readable findings superjs lint src/ --format json # one JSON diagnostic per line superjs lint src/ --fix # apply auto-fixes in place ``` | Flag | Values | Default | Effect | |------|--------|---------|--------| | `--format ` | `pretty` \| `json` | `pretty` | Diagnostic output format | | `--fix` | — | off | Apply auto-fixes in place, then report what remains | `--fix` rewrites the findings that carry a safe, unambiguous fix — today no-`var` (`var` → `let`, L002) and no-`debugger` (delete the statement, L005) — writes each file back, and reports anything left unfixed. Rules: | Code | Rule | |------|------| | SJS-L001 | prefer `const` — a `let` binding never reassigned | | SJS-L002 | no `var` — use `let` / `const` | | SJS-L003 | use `===` / `!==`, not `==` / `!=` | | SJS-L004 | prefer `for…of` over `for…in` | | SJS-L005 | no `debugger` statement | | SJS-L006 | no empty `match` (a `match` with no arms) | | SJS-L007 | no redundant `match` arm (a variant handled twice) | | SJS-L008 | prefer an arrow over a `function`-expression callback | | SJS-L009 | no unused import | | SJS-L010 | import-order (sort imports by source) | | SJS-L012 | no unused variable, function, or class | | SJS-L013 | no explicit `dynamic` (opt out with `// @sjs:dynamic-ok`) | | SJS-L014 | no shadowing of an enclosing-scope binding | | SJS-L015 | no floating promise (await/return/consume it) | | SJS-L016 | no unhandled `Result` (match/return/consume it) | | SJS-L017 | prefer returning `Result` over `throw` | | SJS-L018 | no mixed spaces and tabs in indentation | ### `doc` Generate API documentation from a module's exported declarations. Because SJS types are explicit and sound, the signature *is* the documentation — no `@param {type}` tags to drift. A leading JSDoc-style comment adds prose and tags (`@example`, `@since`, `@deprecated`, `@see`, …). ```bash superjs doc src/api.sjs # Markdown to stdout superjs doc src/ --format json # machine-readable superjs doc src/ --out-dir docs # write one .md per file ``` | Flag | Values | Default | Effect | |------|--------|---------|--------| | `--format ` | `md` \| `json` | `md` | Output format | | `--out-dir ` | path | stdout | Write one file per input instead of printing | > MVP: signatures + doc comments. The full doc site (`--serve`, HTML, validated > `@example` blocks) is later work — see [ADR-009](https://github.com/hbarve1/super-js/blob/main/specs/design/ADR-009-doc-gen.md). ### `explain` Print the full reference for a diagnostic code. ```bash superjs explain E001 # short form superjs explain SJS-E001 # full code, case-insensitive ``` See the complete list on the [error code reference](/errors) page. ### `init` Write a default `superjs.config.json`, or scaffold a starter project with a template. Existing files are never overwritten. ```bash superjs init # just superjs.config.json superjs init fastify-api # scaffold a Fastify API project ``` Templates: `node-cli`, `fastify-api`, `workers-api`, `lambda-handler`. Each writes a `package.json`, a `superjs.config.json`, a starter `src/*.sjs`, and a README (plus `wrangler.toml` for `workers-api`). ### `doctor` Report environment health: Node.js version (must be ≥ 18), compiler version, whether a `superjs.config.json` is present, and a typed-surface report for every package brought in with `superjs add`. ```bash superjs doctor ``` ### `lsp` Start the SuperJS **language server** over stdio, for editor integration. The server speaks standard [LSP](https://microsoft.github.io/language-server-protocol/) and implements the full M1 method set — diagnostics, hover, go-to-definition, document outline, folding, completion, signature help, semantic tokens, and formatting. ```bash superjs lsp # reads/writes JSON-RPC on stdin/stdout ``` It is normally launched by an editor, not by hand. The VS Code extension spawns it automatically; for Neovim, Helix, or any LSP-aware editor, point the client at `superjs lsp` (see the **Editor Setup** guide for ready-to-paste configs). `stdout` is the JSON-RPC channel, so the command prints nothing else there; the process runs until the client sends `exit`. ### `verify` Recompile an input tree and **byte-diff** the emitted JavaScript against an expected output tree — the build-determinism gate as a user-auditable command. Exits non-zero on any difference or missing file. Source maps are disabled so the comparison is path-independent. ```bash superjs verify src/ expected-dist/ ``` | Exit | Meaning | |------|---------| | `0` | every emitted file matches the expected tree | | `1` | a file differs, is missing, or the inputs failed to compile | | `2` | usage error (needs both `` and ``) | ### `migrate` Assisted migration of a TypeScript tree to SuperJS. ```bash superjs migrate from-ts src/ ``` A best-effort **textual** pass: each `.ts` becomes a `.sjs` with `any` rewritten to `dynamic`, and constructs that need a human rewrite — `enum`, `namespace`, decorators, `as const`, non-null `!` — are flagged by line in a generated `MIGRATION_REPORT.md`. It does not type-check the result; run `superjs check` afterward. Idempotent: a directory of already-migrated `.sjs` has no `.ts` to process. ### Planned commands These subcommands are reserved — invoking one today prints a not-yet-implemented notice and exits non-zero. #### `superjs test` — test runner (planned, Stage 5) A **Jest alternative** for `.sjs`, planned along two tracks: - **Run in your existing runner** — `@superjs/jest-transform` and `@superjs/vitest-transform` will compile `.sjs` test files through the compiler's `transform()` API so they run under Jest or Vitest unchanged. - **Native runner** — `superjs test`, a zero-config runner for `.sjs` test files (watch + coverage), is planned for Stage 5 / post-1.0. ## Exit codes | Code | Meaning | |------|---------| | `0` | Success — no errors | | `1` | Compilation errors, or a named file was missing | | `2` | Usage error (e.g. no files passed to `build`/`check`) | | `70` | Internal compiler error (reported to stderr) | These codes are stable; scripts and CI can rely on them. ## Configuration Build options live in `superjs.config.json` rather than on the command line, so one config drives `build`, `check`, and your editor identically. ```json { "language": "1.0", "compilerOptions": { "strict": false, "noEmitOnError": false, "target": "ES2022", "outDir": "dist", "sourceMap": "none" }, "jsx": { "runtime": "automatic", "importSource": "react" }, "paths": {}, "output": { "eol": "lf" } } ``` | Key | Type | Default | Notes | |-----|------|---------|-------| | `compilerOptions.strict` | boolean | `false` | Promotes warnings to errors; warns on implicit `dynamic` | | `compilerOptions.noEmitOnError` | boolean | `false` | Skip emit when any error is present | | `compilerOptions.target` | `ES2020`…`ESNext` | `ES2022` | ECMAScript output level | | `compilerOptions.outDir` | path | — | Default output directory | | `compilerOptions.sourceMap` | `none` \| `inline` \| `external` | `none` | Sourcemap mode | | `jsx.runtime` | `automatic` \| `classic` | `automatic` | JSX transform | | `jsx.importSource` | string | `react` | JSX import source | | `paths` | record | `{}` | `tsconfig`-style path mapping | | `output.eol` | `lf` \| `crlf` \| `auto` | `lf` | Line ending of emitted files | > `--strict` is **not** a command-line flag — set `compilerOptions.strict` in > config so every tool (CLI, CI, editor) agrees on one source of truth. ======================================================================== # Editor Setup Source: https://superjs.org/docs/editors # Editor Setup SuperJS ships a language server in the CLI — `superjs lsp` speaks the standard [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) over stdio, so any LSP-aware editor gets diagnostics, hover types, go-to-definition, completion, signature help, inlay hints, document outline, folding, semantic tokens, formatting, rename, find-all-references, and quick-fix code actions. Install the CLI first so `superjs` is on your `PATH`: ```bash npm install -g @superjsorg/cli ``` ## VS Code Install the **SuperJS** extension from the Marketplace. It bundles the syntax grammar and snippets and launches `superjs lsp` automatically — no configuration needed. Settings: - `superjs.server.path` — path to the `superjs` binary (default: found on `PATH`). - `superjs.lsp.memoryBudgetMB` — language-server cache budget (default `128`). The **SuperJS: Restart Language Server** command reloads the server. ## Neovim SuperJS isn't a built-in `nvim-lspconfig` server yet, so register it directly. On Neovim 0.11+ with the built-in client: ```lua -- Treat .sjs files as the `superjs` filetype. vim.filetype.add({ extension = { sjs = 'superjs' } }) vim.lsp.config('superjs', { cmd = { 'superjs', 'lsp' }, filetypes = { 'superjs' }, root_markers = { 'superjs.config.json', '.git' }, }) vim.lsp.enable('superjs') ``` On older Neovim, start it from an autocmd: ```lua vim.filetype.add({ extension = { sjs = 'superjs' } }) vim.api.nvim_create_autocmd('FileType', { pattern = 'superjs', callback = function(args) vim.lsp.start({ name = 'superjs', cmd = { 'superjs', 'lsp' }, root_dir = vim.fs.root(args.buf, { 'superjs.config.json', '.git' }), }) end, }) ``` ## Helix Add the server and language to your `languages.toml` (`~/.config/helix/languages.toml`): ```toml [language-server.superjs] command = "superjs" args = ["lsp"] [[language]] name = "superjs" scope = "source.sjs" file-types = ["sjs"] roots = ["superjs.config.json"] language-servers = ["superjs"] comment-token = "//" indent = { tab-width = 2, unit = " " } ``` Run `hx --health superjs` to confirm Helix found the binary. ## Zed Zed discovers language servers through extensions; a dedicated SuperJS extension is planned. Until then, point Zed at `superjs lsp` via a custom local extension using the same command (`superjs lsp`) and the `.sjs` file association. ## Other editors Any LSP client works — the command is always `superjs lsp` over stdio. Pass the server cache budget as the `memoryBudgetMB` field of the LSP `initializationOptions` if you want to override the 128 MB default. ======================================================================== # Changelog Source: https://superjs.org/docs/changelog # Changelog ## v0.1.0 — 2026 The first published release of SuperJS, shipped as two self-contained npm packages with zero runtime dependencies: - [`@superjsorg/cli`](https://www.npmjs.com/package/@superjsorg/cli) — the `superjs` command. - [`@superjsorg/compiler`](https://www.npmjs.com/package/@superjsorg/compiler) — the programmatic API. ```bash npm install -g @superjsorg/cli ``` ### Compiler A hand-written compiler — no Babel, no TypeScript at runtime. The pipeline is lexer → recursive-descent + Pratt parser (with error recovery) → bidirectional type checker → SJS-IR lowering → JavaScript codegen, with deterministic output and Source Map v3 generation. ### CLI - `superjs build ` — compile `.sjs` to JavaScript - `--out-dir ` — output directory (default `dist`) - `--source-map none|inline|external` - `--watch` — recompile on file change - `--no-cache` — disable the incremental build cache - `superjs check ` — type-check only (`--format pretty|json`) - `superjs translate ` — translate TypeScript `.d.ts` declarations to `.d.sjs` - `superjs explain ` — print the full spec write-up for a diagnostic - `superjs init` — write a default `superjs.config.json` - `superjs doctor` — report environment health ### Type system - **Sound null safety** (SJS-E001 / SJS-E003): types are non-nullable by default; `T?` is the nullable form, with control-flow narrowing - **Type mismatch detection** (SJS-E002): assignment and return-type checking - **Sum types + exhaustive `match`** (SJS-E007): `type Result = Ok(T) | Err(E)`, lowered to tagged objects; non-exhaustive matches are a compile error - **Structural object types** (Go-style), **generics** with inference, and a `dynamic` escape hatch instead of `any` - **Strict mode** (SJS-W001): promotes implicit-`dynamic` warnings to errors ### Configuration & JSX - `superjs.config.json` with nested `compilerOptions` (`target` ES2020–ESNext, `outDir`, `sourceMap`, `strict`, `noEmitOnError`) - JSX on by default — `automatic` or `classic` runtime via the `jsx` config block ### Incremental builds - Persistent on-disk cache keyed by `(file hash, compiler version, config hash)`; warm rebuilds are served from cache and are byte-identical. --- ## What's Next Stage 2 (interop) adds `.d.ts` → `.sjs` translation and `tsconfig.json` paths; Stage 3 brings the formatter, linter, and a marketplace VS Code extension. See the [Roadmap](/docs/roadmap) for the full plan. ======================================================================== # Roadmap Source: https://superjs.org/docs/roadmap # Roadmap > **v1.0:** All workstreams merged — we are preparing for RC. See > **[v1.0 RC Status](/docs/roadmap/v1.0-rc-status)** for what shipped, what was cut, and the path to GA. SuperJS ships a hand-written compiler that owns its full pipeline today, and is growing outward into interop, developer tooling, a standard library, and an eventual native backend. Development is organized into stages — the authoritative per-stage plans live in [`specs/roadmap/`](https://github.com/hbarve1/super-js/tree/main/specs/roadmap). ## Foundations & Compiler Core (Complete, v0.1.0) **Status: Published to npm** ([`@superjsorg/cli`](https://www.npmjs.com/package/@superjsorg/cli), [`@superjsorg/compiler`](https://www.npmjs.com/package/@superjsorg/compiler)) The compiler is written from scratch — no Babel, no TypeScript at runtime. An early Babel-based prototype validated the language design and was then fully replaced by this hand-written pipeline. ### What shipped - **Hand-written pipeline**: lexer → recursive-descent + Pratt parser (with error recovery) → bidirectional type checker → SJS-IR lowering → JavaScript codegen - **Type checker** - Sound null safety with control-flow narrowing (SJS-E001 / SJS-E003) - Type mismatch detection on assignment and return (SJS-E002) - Sum types + exhaustive `match` (SJS-E007), structural object types, generics with inference - Strict mode promotes implicit-`dynamic` warnings to errors (SJS-W001) - **CLI**: `superjs build` (`--out-dir`, `--source-map`, `--watch`, `--no-cache`), `superjs check` (`--format pretty|json`), `superjs explain`, `superjs init`, `superjs doctor` - **Deterministic output** with Source Map v3 generation and a persistent incremental build cache - **Config file**: `superjs.config.json`; **JSX** on by default (automatic or classic runtime) - Distributed as self-contained, zero-dependency npm bundles with build provenance --- ## Interop (In progress) Making SuperJS consume the existing JS/TS ecosystem. - **`.d.ts` → `.d.sjs` translation** — landed as `@superjs/interop`, exposed via the `superjs translate` command - `tsconfig.json` `paths` inheritance and npm package wrappers — next --- ## Developer Tooling (In progress) - **VS Code extension** — TextMate grammar highlighting (shipped) - **Watch mode** — `superjs build --watch` (shipped) - **Formatter & linter** (17 rules), LSP server with hover/go-to-definition — next --- ## Standard Library & Ecosystem (Planned) - Typed `Result`, `Option`, `Iterator`, and collections - React wrapper, Node.js types, and bundler/test-runner integrations --- ## LLVM Native Backend (Future, v2.0) An optional native compilation path. The SJS frontend feeds a backend targeting LLVM, with monomorphized generics, standalone native binaries, and a WebAssembly target. See the [v2.0 vision](https://github.com/hbarve1/super-js/blob/main/specs/roadmap/v2.0-native-compiler.md). --- ## Stage Summary | Stage | Status | Key Deliverable | |-------|--------|-----------------| | Foundations + Compiler Core | **Complete (v0.1.0)** | Hand-written compiler, CLI, type checker, sum types, match, published to npm | | Interop | **In progress** | `.d.ts` → `.sjs` translator, `tsconfig` paths | | Developer Tooling | **In progress** | VS Code extension + watch shipped; formatter, linter, LSP next | | Standard Library & Ecosystem | Planned | Result/Option/collections, framework integrations | | LLVM Native | Future (v2.0) | Native binaries, WASM target | --- ## Contributing SuperJS is open source. See the [GitHub repository](https://github.com/hbarve1/super-js) to follow progress, file issues, or submit pull requests. ======================================================================== # Specification Source: https://superjs.org/docs/specification # Language Specification This document is the technical specification for SuperJS (SJS). It defines the type system semantics, syntax forms, compilation pipeline, and diagnostic codes. It is authoritative over the language reference for matters of precision. --- ## 1. Design Philosophy SJS is designed around four convictions: **Sound type system.** Every type error caught at compile time is a guarantee. SJS does not include escape hatches that silently undermine soundness (no `any`, no `!` assertion). The `dynamic` type is the only opt-out, and it is explicit and runtime-checked. **Go-inspired simplicity.** The type system has a fixed, small surface area. There are exactly 10 types. Object types are structural and satisfied implicitly. There are no mapped types, conditional types, or `infer` — features that make TypeScript's type system Turing-complete but also opaque. **Dart 2.12-style null safety.** Non-nullable by default. `T?` is the only way to express nullability. The compiler tracks null flow through `?.`, `??`, and narrowing. There is no non-null assertion operator. **Rust-inspired sum types.** Variant types are a first-class construct, not a convention over discriminated union objects. `match` is an expression with compiler-enforced exhaustiveness. --- ## 2. The 10 Types SJS has exactly 10 built-in types. The set is closed — new built-in types cannot be added by user code. | SJS Type | Description | Runtime representation | |----------|-------------|------------------------| | `number` | IEEE 754 double-precision float | JS `number` | | `string` | UTF-16 string | JS `string` | | `boolean` | `true` or `false` | JS `boolean` | | `bigint` | Arbitrary-precision integer | JS `bigint` | | `symbol` | Unique opaque symbol | JS `symbol` | | `void` | Absence of a return value | JS `undefined` | | `null` | Explicit null | JS `null` | | `never` | Unreachable / bottom type | — (no value reaches this) | | `dynamic` | Runtime-checked escape hatch | JS value, checked at use sites | | `object T` | Heap-allocated typed object | JS object | `any` does not exist in SJS. Using `any` in an `.sjs` file is a parse error. --- ## 3. Null Safety Semantics ### 3.1 Non-nullable by default Every type `T` is non-nullable unless explicitly declared `T?`. This includes all 10 built-in types and all user-defined classes and object types. ```sjs const x: string = null // SJS-E001 const y: string = undefined // SJS-E001 const z: string? = null // OK ``` ### 3.2 Nullable types `T?` desugars to `T | null | undefined` in the type algebra, but SJS surfaces it only as `T?`. The distinction matters: `T | null` is not valid SJS syntax — write `T?` instead. ### 3.3 Null-safe operators `?.` (optional chaining) and `??` (nullish coalescing) are both type-checked. The operand on the left of `?.` must be `T?`; the result is `U?` where `U` is the property type. ```sjs const len: number? = user?.length // user must be string? const name: string = user ?? "Anon" // result is string (non-nullable) ``` ### 3.4 Narrowing The compiler tracks null flow through `if`/`else` and `typeof` guards. After a null check, the type is narrowed to the non-nullable variant. ```sjs const user: string? = findUser(id) if (user !== null) { console.log(user.toUpperCase()) // user: string here } ``` ### 3.5 No non-null assertion `!` is not a postfix type operator in SJS. There is no way to tell the compiler "trust me, this is not null" without an actual runtime check. This is intentional — `!` is a common source of null pointer exceptions in TypeScript codebases. --- ## 4. Sum Type Syntax and Runtime Representation ### 4.1 Declaration syntax ```sjs type Result = Ok(T) | Err(E) type Shape = Circle({ radius: number }) | Rect({ w: number, h: number }) type Option = Some(T) | None ``` Each variant is either: - A unit variant: `None` (no payload) - A tuple variant: `Ok(T)` (single positional payload) - A record variant: `Circle({ radius: number })` (named payload fields) ### 4.2 Constructor functions Each variant name is a constructor function at runtime: ```sjs const r: Result = Ok(42) const e: Result = Err("bad input") const s: Shape = Circle({ radius: 5 }) const n: Option = None ``` ### 4.3 Runtime representation The compiler emits discriminated union objects. The `_tag` field holds the variant name as a string literal. The payload is placed in `_0` (tuple variants) or spread into the object (record variants): | SJS expression | Emitted JS object | |----------------|-------------------| | `Ok(42)` | `{ _tag: "Ok", _0: 42 }` | | `Err("bad")` | `{ _tag: "Err", _0: "bad" }` | | `Circle({ radius: 5 })` | `{ _tag: "Circle", radius: 5 }` | | `None` | `{ _tag: "None" }` | SJS code never references `_tag` or `_0` directly. These are internal to the compilation target. Use `match` to destructure. --- ## 5. Match Expression Semantics and Exhaustiveness ### 5.1 Syntax `match` is an expression, not a statement. It always produces a value. ```sjs const result = match expr { Variant1(x) => expression1, Variant2({ a, b }) => expression2, default => expressionDefault, } ``` ### 5.2 Compilation target Match expressions compile to IIFE switch statements on `._tag`: ```js // SJS: const msg = match r { Ok(val) => `Got ${val}`, Err(e) => `Failed: ${e}` } // Compiled JS: const msg = (() => { switch (r._tag) { case "Ok": { const val = r._0; return `Got ${val}`; } case "Err": { const e = r._0; return `Failed: ${e}`; } } })() ``` ### 5.3 Exhaustiveness When the matched expression has a sum type, the compiler verifies that every variant is covered. If any variant is missing and there is no `default` branch, `SJS-E007` is emitted at compile time. ```sjs type Color = Red | Green | Blue const label = match color { Red => "red", Green => "green", // SJS-E007: match is not exhaustive — missing variant: Blue } ``` Adding `default` suppresses the check: ```sjs const label = match color { Red => "red", default => "other", } ``` ### 5.4 Destructuring in arms Tuple payload: `Ok(val)` binds `val` to `r._0`. Record payload: `Circle({ radius })` destructures the record fields from the variant object. Unit variants: `None` matches when `_tag === "None"` with no binding. --- ## 6. Structural Object Types ### 6.1 Definition Object types use the brace form of `type` (no `=`): ```sjs type Printable { toString(): string } ``` ### 6.2 Implicit satisfaction A value of type `C` satisfies object type `I` if and only if `C` exposes every member declared in `I` with a compatible type. No `implements` declaration is required or supported. ```sjs class Celsius { constructor(public value: number) {} toString(): string { return `${this.value}°C` } } function print(p: Printable): void { console.log(p.toString()) } print(new Celsius(100)) // OK — Celsius satisfies Printable structurally ``` ### 6.3 Object type extension Object types may extend one or more other object types. The extending type inherits all member requirements. ```sjs type Serializable extends Printable { serialize(): string } ``` ### 6.4 No intersection types `A & B` is not valid SJS syntax. Use object type extension to compose contracts: ```sjs // Wrong (banned): type Named = HasName & HasAge // Correct: type Named extends HasName, HasAge {} ``` --- ## 7. Generics ### 7.1 Syntax Generic type parameters use angle brackets on functions, classes, and object types: ```sjs function identity(x: T): T { return x } class Stack { private items: T[] = [] push(item: T): void { this.items.push(item) } pop(): T? { return this.items.pop() ?? null } } type Container { get(): T? set(value: T): void } ``` ### 7.2 Constraints Use `: TypeName` to constrain a type parameter: ```sjs function max(a: T, b: T): T { return a.compareTo(b) > 0 ? a : b } ``` The constraint is checked structurally — `T` must satisfy the `Comparable` object type. ### 7.3 Monomorphization SJS generics are monomorphized at compile time, not type-erased. Each instantiation of a generic at a distinct type produces a distinct specialization. This means: - Generic code has no runtime type-erasure cost. - Type parameters are not available at runtime (no `T.name`, no `instanceof T`). - The compiled output is larger than a type-erased equivalent for many distinct instantiations. ### 7.4 Banned generic features The following TypeScript generic features are not in SJS: - Conditional types: `T extends U ? A : B` - `infer` keyword - Mapped types: `{ [K in keyof T]: ... }` - Template literal types: `` `prefix_${T}` `` --- ## 8. The `dynamic` Type ### 8.1 Purpose `dynamic` is the opt-out from the static type system. It exists for interoperability with untyped external data: JSON responses, third-party libraries without type definitions, and runtime-constructed objects. ### 8.2 Semantics - A `dynamic` value may hold any JavaScript value at runtime. - Accessing a property or calling a method on `dynamic` succeeds at compile time but is checked at runtime. - `dynamic` does not propagate silently. Assigning a `dynamic` to a statically typed variable requires a runtime narrowing check. - In strict mode, positions that would implicitly receive `dynamic` emit `SJS-W001`. ```sjs function parseJSON(raw: string): dynamic { return JSON.parse(raw) } const data: dynamic = parseJSON('{"count": 3}') const count = data.count // dynamic — runtime-checked // To use as a typed value, narrow explicitly: if (typeof count === "number") { const n: number = count // OK } ``` ### 8.3 Difference from `any` `any` in TypeScript is unsound — it silently opts out of type checking for all downstream expressions. `dynamic` in SJS is explicitly runtime-checked: the compiler inserts guards at use sites and the type does not widen surrounding expressions. --- ## 9. Compilation Pipeline SJS compiles `.sjs` → `.js` through a hand-written pipeline — no Babel and no TypeScript at runtime — in five ordered phases: ### Phase 1: Lex The source file is read as UTF-8 and tokenized by a hand-written lexer (numbers in all bases, templates with nested interpolation, regex-vs-division disambiguation, BiDi-control rejection). ### Phase 2: Parse A recursive-descent parser with a Pratt expression layer produces an AST that is a superset of the JavaScript AST, including SJS-specific nodes (sum type declarations, `match` expressions, type annotations). The parser recovers from errors so a single mistake does not abort the whole file. ### Phase 3: Type check A bidirectional type checker (`synth`/`check`) runs over the AST and emits diagnostics: - `SJS-E001` / `SJS-E003` — null/undefined assigned to a non-nullable type; access on a possibly-null value - `SJS-E002` — type mismatch on assignment or return - `SJS-E007` — non-exhaustive match on a sum type - `SJS-W001` — implicit `dynamic` (strict mode only) Type errors do not block emission by default. Set `compilerOptions.noEmitOnError` to halt compilation when any error is present. ### Phase 4: Lower to SJS-IR The typed AST is lowered to SJS-IR (an ESTree-subset JavaScript AST). All type syntax is erased and SJS constructs are desugared: - **Sum type constructors** → tagged objects (`{ _tag, _0 }`) - **Match expressions** → an invoked arrow (IIFE) that switches on `_tag` and binds payloads - **JSX** → `React.createElement` (or the configured runtime); **class parameter properties** → `this.x = x` ### Phase 5: Codegen & emit A precedence-aware printer renders the IR to JavaScript at the configured ES target, generating a Source Map v3 alongside. The compiler writes one `.js` (and `.js.map`, when source maps are enabled) per input file, preserving directory structure under `outDir`. --- ## 10. Diagnostic Code Reference All SJS diagnostics have stable, permanent codes. Codes are never reused after retirement. ### Error codes (SJS-E) | Code | Name | Description | |------|------|-------------| | `SJS-E001` | Null safety violation | A value of type `null`, `undefined`, or `T?` was assigned to a non-nullable binding. | | `SJS-E002` | Type mismatch | The type of an expression is not compatible with the declared type at an assignment, return site, or call argument position. | | `SJS-E007` | Non-exhaustive match | A `match` expression on a sum type is missing one or more variants and has no `default` arm. | **SJS-E001 example:** ```sjs const name: string = null // error[SJS-E001]: cannot assign null to non-nullable type 'string' // --> app.sjs:1:22 // hint: use 'string?' to allow null, or assign a non-null value ``` **SJS-E002 example:** ```sjs function double(n: number): number { return "oops" } // error[SJS-E002]: expected return type 'number', found 'string' // --> app.sjs:2:10 ``` **SJS-E007 example:** ```sjs type Color = Red | Green | Blue const label = match color { Red => "red", Green => "green", } // error[SJS-E007]: match is not exhaustive — missing variant: Blue // --> app.sjs:2:15 // hint: add an arm for 'Blue', or add a 'default' arm ``` ### Warning codes (SJS-W) | Code | Name | Activated by | Description | |------|------|--------------|-------------| | `SJS-W001` | Implicit dynamic | strict mode | A variable or parameter has no type annotation and would implicitly receive type `dynamic`. | **SJS-W001 example (with `compilerOptions.strict: true`):** ```sjs function add(a, b) { return a + b } // warning[SJS-W001]: parameter 'a' has implicit type 'dynamic' // warning[SJS-W001]: parameter 'b' has implicit type 'dynamic' ``` ### Diagnostic output format Default (human-readable): ``` error[SJS-E001]: cannot assign null to non-nullable type 'string' --> src/app.sjs:3:22 ``` JSON mode (`--json` flag, one object per line): ```json {"code":"SJS-E001","severity":"error","message":"cannot assign null to non-nullable type 'string'","file":"src/app.sjs","line":3,"column":22} ``` --- ## 11. Permanently Banned Features The following features are not part of SJS and will not be added. They are excluded by design, not omission. | Feature | Reason for exclusion | |---------|----------------------| | `any` | Unsound. Use `dynamic` — it is explicit and runtime-checked. | | `T extends U ? A : B` (conditional types) | Makes the type system Turing-complete; produces inscrutable error messages. Use sum types and match instead. | | `{ [K in keyof T]: ... }` (mapped types) | Produces types that are correct by construction but hard to read and diagnose. Use explicit object types. | | Template literal types | Expressive but adds significant type checker complexity for marginal practical benefit. | | `infer` | Tied to conditional types; removed along with them. | | `namespace` | Superseded by ES modules. | | TypeScript `enum` | Enums have confusing runtime semantics. Use sum types — they are explicit, exhaustively matchable, and compile cleanly. | | `A & B` (intersection types) | Intersection of two object types is rarely what the author intends and is unsound in several positions. Use object type extension. | | `!` (non-null assertion) | Allows bypassing null safety without a runtime check. Use narrowing — it is both safe and readable. | --- ## 12. File Format - **Extension**: `.sjs` - **Encoding**: UTF-8 - **Line endings**: LF preferred - **Comments**: Standard JavaScript `//` and `/* */` - **Shebang**: Supported (`#!/usr/bin/env superjs`) - **JSX**: Enabled in all `.sjs` files — no opt-in required ======================================================================== # FAQ Source: https://superjs.org/docs/faq # Frequently Asked Questions ## Is Super.js a superset of TypeScript? No — it's a superset of **JavaScript**. Where TypeScript's motto is "JavaScript + Types," Super.js is "JavaScript + Type *Safety*." It keeps the parts of gradual, structural typing that work and removes the unsound escape hatches. Existing TypeScript types can be consumed through [interop](/docs/interop), but the language itself is intentionally smaller and stricter than TS. ## Why is `any` banned? What do I use instead? `any` is an invisible escape hatch: it silently disables type checking and spreads through a codebase without a trace. Super.js rejects it (`SJS-E004`) and gives you two explicit replacements: - **`dynamic`** — a gradual type for genuinely untyped values (JSON, JS interop). It's explicit in the source, it propagates through operations, and in strict mode an *implicit* `dynamic` warns (`SJS-W001`). It's the **only** escape hatch. - **`unknown`** — a safe top type you must narrow before using. ```sjs const data: dynamic = JSON.parse(input) // explicit, greppable console.log(data.user.name) // allowed; you opted in ``` ## Is it null-safe? Yes — null safety is a core invariant, always on. A plain `T` can never hold `null`; assigning `null` to it is an error (`SJS-E001`). Nullable values are spelled `T?` (shorthand for `T | null`): ```sjs const a: string = null // ❌ SJS-E001 const b: string? = null // ✅ const len = b?.length ?? 0 // narrow with ?. and ?? ``` The compiler narrows by control flow (`if (b !== null) { /* b: string */ }`). The non-null assertion `!` is banned (`SJS-E011`) — it's an unverifiable claim, so you narrow instead. ## What else is banned, and why? Features that make the type system undecidable or unsound to compile are removed, each with a sound alternative: | Banned | Code | Use instead | |--------|------|-------------| | `any` | E004 | `dynamic` or `unknown` | | `!` non-null assertion | E011 | narrowing (`?.`, `??`, `if`) | | `enum` | E010 | string-literal union | | `A & B` intersection | E005 | `type AB extends A, B {}` | | conditional types | E008 | overloads / separate functions | | mapped types | E006 | write the object type explicitly | | `infer` | E009 | explicit annotations | | `namespace` | E012 | ES modules | See the full list with messages on the [error code reference](/errors). ## What does it compile to? Is there runtime overhead? It compiles to **plain JavaScript**. Types are erased entirely — `T?` and `dynamic` have no runtime representation in the JS output. Modern syntax like `?.` and `??` lowers to native operators (or polyfills when targeting ES5). Sum types compile to a small tagged object; `match` to an exhaustive dispatch. ```sjs function greet(name: string?): string { return name ?? "stranger" } ``` ```js function greet(name) { return name ?? "stranger" } ``` A future LLVM backend will compile non-nullable types to bare values with zero overhead; that's on the roadmap, not in the current JS compiler. ## What is gradual typing here? You annotate where you want and lean on inference elsewhere. `dynamic` is the explicit bridge for untyped code, so you can port JavaScript first and tighten types incrementally. In strict mode the compiler points out every implicit `dynamic` (`SJS-W001`) and every `dynamic` flowing into a typed position (`SJS-W002`), giving you a worklist for migration. ## What runtimes and targets are supported? The current compiler emits JavaScript targeting ES2020 through ESNext (default `ES2022`), configurable via `compilerOptions.target`. It runs on **Node.js ≥ 18** (checked by `superjs doctor`), and the output runs anywhere that JS does — browsers and edge runtimes included. A native LLVM target is planned for a later stage. ## How do I try it? Use the [playground](/playground) to compile and run Super.js in the browser with no install, or take the [guided tour](/tour). To set up a project locally, see the [CLI reference](/docs/cli). ======================================================================== # v1.0 RC Status Source: https://superjs.org/docs/roadmap/v1.0-rc-status # v1.0 RC Status **Last updated:** 2026-06-24 SuperJS v1.0 is the first **stable** release of the hand-written compiler, CLI, LSP, docs site, and ecosystem wrappers. All v1.0 workstreams (WS-A1…A8, WS-B1, WS-B3) have merged to `main`. We are **preparing for RC** — not yet tagging `1.0.0-rc.1`. ## What shipped (v1.0 scope) | Area | Status | Notes | |------|--------|-------| | Language spec freeze | **Done** | `specs/language.md` frozen; grammar CI gate | | Docs site | **Done** | Tour (20 lessons), migration, API ref, why-SJS, compat matrix, perf | | Error-code reference | **Done** | Per-code pages + CI gate | | `migrate from-prototype` | **Done** | CLI command | | 30 `@superjs/types-*` wrappers | **Done** | Hand-curated interop surface | | Playground | **Infra done** | Runbook + smoke test; maintainer deploy pending | | Governance | **Done** | STABILITY, Dependabot, CodeQL, SECURITY.md | | Node 20 / 22 / 24 CI | **Done** | Blocking matrix | | Performance targets | **Done** | Compile + LSP benches in CI (`docs/perf/`) | | LSP memory audit | **Done** | Close + LRU verified (`docs/perf/lsp-memory-audit.md`) | | Threat model | **Reviewed** | v1.0 pass — `docs/security/threat-model.md` | | Launch drafts + press kit | **Scaffolded** | `docs/launch/`, `docs/press/`; GA posts pending | | Security review (S7) | **Scaffolded** | [`docs/security-review.md`](../security-review.md); external reviewer TBD | | Trademark non-claim | **Published** | [`TRADEMARK.md`](../../TRADEMARK.md); USPTO/EU search pending | ## Cut from v1.0 (deferred) | Item | Target | Rationale | |------|--------|-----------| | **DAP debugger** (WS-B2) | Post-1.0 full impl | Phase 0 skeleton shipped; breakpoints/CDP Phase 1+ | | **LLVM native backend** | v2.0 | Separate milestone | | Full parser/lexer hard caps | Post-1.0 hardening | Recovery + BiDi shipped; numeric caps tracked in threat model T1/T2 | | LSP 8 MiB message cap | Post-1.0 hardening | Memory budget + bench shipped; wire cap planned | ## Path to GA 1. **Playground production deploy** — runbook at [`docs/ops/playground-deploy.md`](../ops/playground-deploy.md); set `CLOUDFLARE_API_TOKEN`, deploy, wire `NEXT_PUBLIC_PLAYGROUND_RUN_URL`. 2. **Beta program** — three friendly teams on `1.0.0-rc.X`; see [Beta Program](../beta/index.md). 3. **Bug bash** — one structured week; fix all `severity=blocker` issues. 4. **External security review (S7)** — engage reviewer; track in [`docs/security-review.md`](../security-review.md). 5. **RC cycle** — `1.0.0-rc.1` → `rc.2` → `rc.3`, ≥2 weeks apart. 6. **`superjs@1.0.0` GA** — npm publish with provenance, GitHub Release, launch artefacts. Human-gated steps (npm token rotation, trademark, on-call) are tracked in [`specs/roadmap/v1.0-release-checklist.md`](https://github.com/hbarve1/super-js/blob/main/specs/roadmap/v1.0-release-checklist.md). Maintainer-ordered gates: [`docs/ops/rc-maintainer-gates.md`](../ops/rc-maintainer-gates.md). ## How to follow progress - [Release checklist](https://github.com/hbarve1/super-js/blob/main/specs/roadmap/v1.0-release-checklist.md) (maintainer tracker) - [Workstream manifest](https://github.com/hbarve1/super-js/blob/main/specs/roadmap/v1.0/manifest.json) - [GitHub Issues / Discussions](https://github.com/hbarve1/super-js/discussions) ======================================================================== # Playground Worker Deploy Source: https://superjs.org/docs/ops/playground-deploy # Playground Worker Deploy Production playground can run on **Next.js `/api/run`** (default) or a **Cloudflare Worker** (`@superjs/playground-worker`). This runbook covers the Worker path for lower latency and isolated compute off the docs host. **Prerequisites:** Cloudflare account, Workers Scripts edit permission, GitHub repo admin (for secrets). ## 1. Create Cloudflare API token 1. Cloudflare Dashboard → **My Profile** → **API Tokens** → **Create Token**. 2. Use the **Edit Cloudflare Workers** template, or custom token with: - Account → Workers Scripts → **Edit** - Zone → Workers Routes → **Edit** (only if using custom domain) 3. Copy the token — you will not see it again. ## 2. Add GitHub secret Repository → **Settings** → **Secrets and variables** → **Actions** → **New repository secret**: | Name | Value | |------|--------| | `CLOUDFLARE_API_TOKEN` | Token from step 1 | Optional if deploy fails with account errors: | Name | Value | |------|--------| | `CLOUDFLARE_ACCOUNT_ID` | Cloudflare account ID (dashboard URL) | ## 3. Deploy the worker **GitHub Actions (recommended):** 1. **Actions** → **Playground Worker** → **Run workflow** → **Run workflow**. 2. Wait for **Deploy to Cloudflare** + **Smoke test /run endpoint** to pass. 3. Note the deployment URL in the workflow log (e.g. `https://superjs-playground..workers.dev`). **Local (one-off):** ```bash cd superjs bunx nx build compiler cd apps/playground-worker bunx wrangler login # interactive, once bunx wrangler deploy ``` ## 4. Smoke test ```bash # Health + /run PLAYGROUND_RUN_URL=https://superjs-playground..workers.dev/run \ node scripts/smoke-playground-run.mjs ``` Or health only: ```bash curl -s https://superjs-playground..workers.dev/health # → {"ok":true,"service":"superjs-playground"} ``` Expect: `playground smoke OK`. ## 5. Wire the website Set on the **website** host (Vercel project env or Cloudflare Pages): ```bash NEXT_PUBLIC_PLAYGROUND_RUN_URL=https://superjs-playground..workers.dev/run ``` Redeploy the website. The playground will POST to the Worker instead of `/api/run`. Optional iframe fallback when the Worker is down: ```bash NEXT_PUBLIC_USE_WORKERS_SANDBOX=true ``` See `superjs/apps/website/.env.example`. ## 6. Custom domain (optional) In `superjs/apps/playground-worker/wrangler.toml`, uncomment: ```toml routes = [{ pattern = "api.superjs.dev/run*", zone_name = "superjs.dev" }] ``` Redeploy, then set: ```bash NEXT_PUBLIC_PLAYGROUND_RUN_URL=https://api.superjs.dev/run ``` ## Security Rate limits, input caps, and sandbox scope are documented in `docs/security/threat-model.md` (T4). Global rate limiting should use Cloudflare **Rate Limiting** rules in production. ## CI - Every PR: `node scripts/check-playground-worker.mjs` (wrangler dry-run bundle). - Manual deploy: `.github/workflows/playground-worker.yml` (`workflow_dispatch`). ## Troubleshooting | Symptom | Fix | |---------|-----| | Deploy job skipped | Use **Run workflow**, not push-only | | `deployment-url` empty in logs | Check wrangler output; smoke step may be skipped | | CORS errors in browser | Worker sends `Access-Control-Allow-Origin: *` on `/run` | | 429 rate limit | Wait 1 minute; 20 req/min per IP in Worker | | Website still hits `/api/run` | Rebuild website after setting `NEXT_PUBLIC_PLAYGROUND_RUN_URL` | ======================================================================== # Maintenance & On-Call Source: https://superjs.org/docs/ops/maintenance # Maintenance & On-Call SuperJS v1.0 uses a **solo-path** maintenance model until a co-maintainer is enrolled (see README solo-path gates). This document satisfies Stage 6 exit criterion **R10** and references [`RELEASING.md`](../../RELEASING.md) (C8). ## Scope | In scope | Out of scope | |----------|----------------| | `@superjsorg/cli`, `@superjsorg/compiler`, LSP, published `@superjs/*` packages | Application code compiled *by* SuperJS | | Docs site, playground worker | npm registry infrastructure | | Security advisories per `SECURITY.md` | Trademark / legal | ## On-call rotation (solo path) Until a co-maintainer is named: - **Primary:** repository maintainer (`@hbarve1`). - **Backup triage:** GitHub Discussions volunteers tagged `help-wanted` for non-critical issues. - **Escalation:** critical security → rotate credentials immediately; file GitHub Security Advisory. When a co-maintainer joins, update this section with names and a weekly rotation schedule. ## SLAs (v1.0.x) Aligned with [`RELEASING.md`](../../RELEASING.md): | Severity | Triage | Patch target | |----------|--------|--------------| | **Critical** (RCE, data loss, publish compromise) | < 48 h | < 1 week | | **High** (DoS compiler/LSP, sandbox escape) | < 3 business days | Next patch release | | **Medium / low** | Best effort | Scheduled minor | Security reports follow the 24 h triage / 7 day critical patch bar in `SECURITY.md`. ## Triage labels | Label | Meaning | |-------|---------| | `severity=blocker` | Blocks RC or GA; fix before next tag | | `severity=critical` | Security or data-loss; on-call SLA | | `type=regression` | Worked in previous release | RC cycle accepts **blocker fixes only** between `rc.N` tags (see `RELEASING.md`). ## Patch release checklist 1. Reproduce on `main` with minimal fixture. 2. Fix + test (`bunx nx run-many -t test` in `superjs/`). 3. Changeset or changelog entry. 4. Verify bench targets: `node scripts/check-bench-results.mjs`. 5. Tag `vX.Y.Z` → `release-npm.yml` publishes with provenance. 6. GitHub Release + advisory if security-related. ## Monitoring (first week post-GA) - npm download trend (`npm view superjs version`) - GitHub Issues opened/day, `severity=*` count - Playground smoke (if Worker deployed): `scripts/smoke-playground-run.mjs` _Last updated: 2026-06-24._ ======================================================================== # VS Code Marketplace Source: https://superjs.org/docs/ops/vscode-marketplace # VS Code Marketplace publish Extension package: `superjs/apps/vscode-extension` (`superjs-syntax`, publisher `hbarve1`). **RC gate (R5):** co-publisher enrolled before GA publish; rotation documented in `RELEASING.md`. ## Prerequisites 1. [Visual Studio Marketplace publisher account](https://marketplace.visualstudio.com/manage) 2. Personal Access Token with **Marketplace (Publish)** scope 3. `vsce` CLI: `npm install -g @vscode/vsce` 4. Co-publisher account added to the extension publisher (failover per R5) ## Build & test ```bash cd superjs/apps/vscode-extension npm ci npm run compile npm test ``` Ensure `superjs` is on `PATH` or configure `superjs.lsp.serverPath` — the extension spawns `superjs lsp`. ## Package ```bash cd superjs/apps/vscode-extension vsce package # produces superjs-syntax-.vsix ``` ## Publish ```bash vsce publish -p ``` Or upload the `.vsix` manually in the publisher portal. ## Versioning - Bump `version` in `package.json` + `CHANGELOG.md` - Align with compiler/LSP capabilities documented in extension README - Do **not** publish `1.0.0` until maintainer approves RC → GA ## Post-publish - [ ] Update website [`editors.mdx`](../../superjs/apps/website/content/docs/editors.mdx) with Marketplace install link - [ ] Verify install on VS Code + Cursor (Open VSX is separate) - [ ] Smoke: open `.sjs` file, hover, diagnostics ## Open VSX (optional) Cursor and VSCodium users may need a separate Open VSX publish — track as post-GA if needed. ======================================================================== # RC maintainer gates Source: https://superjs.org/docs/ops/rc-maintainer-gates # RC maintainer gates All v1.0 **agent work is complete** (workstreams WS-A1…B3 + post-workstream PRs #194–#201). These steps require maintainer credentials or external parties. Run automated gates first: ```bash node scripts/rc-preflight.mjs # or: Actions → RC preflight → Run workflow ``` ## Ordered checklist | # | Gate | Action | Doc | |---|------|--------|-----| | 1 | Playground deploy | Add `CLOUDFLARE_API_TOKEN`; run **Playground Worker** workflow | [playground-deploy.md](./playground-deploy.md) | | 2 | npm credentials | Rotate leaked token; verify `NPM_TOKEN` in CI | [RELEASING.md](../../RELEASING.md) | | 3 | Patch publish | Tag `v0.1.1` if ReDoS fixes need npm republish | [release-npm.yml](../../.github/workflows/release-npm.yml) | | 4 | Beta recruitment | Open **Beta interest** discussions; onboard 3 teams | [beta/index.md](../beta/index.md) | | 5 | Security review | Engage external reviewer; fill findings | [security-review.md](../security-review.md) | | 6 | Bug bash | One week; clear `severity=blocker` | [beta/bug-bash.md](../beta/bug-bash.md) | | 7 | RC tag | `v1.0.0-rc.1` — **maintainer approval required** | [RELEASING.md](../../RELEASING.md) | ## After RC.1 - Wire `NEXT_PUBLIC_PLAYGROUND_RUN_URL` on the website host (if using CF Worker). - Beta teams install `@superjsorg/cli@1.0.0-rc.1` (when published). - Run `scripts/smoke-playground-run.mjs` against production worker URL. ## What agents cannot do - Store Cloudflare or npm secrets - Tag or publish without explicit maintainer approval - Recruit beta teams or run external security reviews _Last updated: 2026-06-24._ ======================================================================== # Launch drafts Source: https://superjs.org/docs/launch/index # Launch announcement drafts **Do not publish until `superjs@1.0.0` GA.** Edit URLs and version numbers before posting. ## Show HN (draft) **Title:** Show HN: SuperJS – a sound superset of JS with sum types (not TypeScript++ **Body:** We built SuperJS (SJS) — a strict superset of JavaScript with a hand-written compiler (no Babel/tsc at runtime). It's designed for teams who want null safety and algebraic types without TypeScript's unsound escape hatches (`any`, intersections, conditional types). What's in 1.0: - Sound null safety, sum types, exhaustive `match` - CLI + LSP + VS Code extension - 20-lesson tour, migration guide, 30 `@superjs/types-*` wrappers - Playground with server-side compile+run Bench on ~14k LOC: cold compile ~81ms vs tsc typecheck ~721ms (not apples-to-apples — we codegen too). Try: https://superjs.org/playground Docs: https://superjs.org/docs Repo: https://github.com/hbarve1/super-js Happy to answer questions on interop (`dynamic` vs banning `any`), the LLVM roadmap, and where we deliberately cut scope (no DAP in 1.0). --- ## lobste.rs (draft) **Title:** SuperJS 1.0 – sound types for JavaScript without the TS complexity budget **Tags:** javascript, typescript, compilers, programming **Body:** (shorter than HN — link to Why SJS page) SuperJS compiles `.sjs` to ES2022. We banned `any` and TS-only type features that break soundness; we added sum types and `match`. Compiler is from scratch, GPL-3.0. https://superjs.org/docs/why --- ## r/typescript (draft) **Title:** SuperJS 1.0 GA — a sound alternative when TS's escape hatches hurt **Body:** If this isn't appropriate for r/typescript, mod please remove. SuperJS isn't "TS with different syntax" — we removed features (any, intersections, conditionals) and added sum types + default null safety. Migration guide for common patterns: https://superjs.org/docs/migration We'd love feedback from teams who've hit `any`-shaped holes in large codebases. ======================================================================== # Language Tour Source: https://superjs.org/docs/tour/index # Language Tour Twenty short lessons (~5 minutes each). Each includes a compile-ready example and a playground link. ## Lessons - [01 — Hello world](./01-hello-world.md) - [02 — Variables and types](./02-variables-and-types.md) - [03 — Functions](./03-functions.md) - [04 — Control flow](./04-control-flow.md) - [05 — Null safety](./05-null-safety.md) - [06 — Pattern matching](./06-pattern-matching.md) - [07 — Sum types](./07-sum-types.md) - [08 — Object types](./08-interfaces.md) - [09 — Generics](./09-generics.md) - [10 — Classes](./10-classes.md) - [11 — Modules](./11-modules.md) - [12 — Async and await](./12-async-await.md) - [13 — JSX](./13-jsx.md) - [14 — Calling JS from SJS](./14-calling-js-from-sjs.md) - [15 — dynamic and Schema](./15-dynamic-and-schema.md) - [16 — Errors and Result](./16-errors-and-result.md) - [17 — Iterators and for...of](./17-iterators-and-for-of.md) - [18 — Serverless handlers](./18-serverless-handlers.md) - [19 — Tooling tour](./19-tooling-tour.md) - [20 — Migrating a TS file](./20-migrating-a-ts-file.md) Start with [01 — Hello world](./01-hello-world.md). ======================================================================== # 01 — Hello world Source: https://superjs.org/docs/tour/01-hello-world # Hello world **Goal:** Run a minimal `.sjs` file with `superjs check`. SuperJS files use the `.sjs` extension. The compiler type-checks them and emits plain JavaScript. ```bash superjs check hello.sjs superjs build hello.sjs --out-dir dist ``` ## Example ```sjs export function greet(name: string): string { return "Hello, " + name } const msg: string = greet("SuperJS") console.log(msg) ``` [Open in playground](https://superjs.org/playground#code=ZXhwb3J0IGZ1bmN0aW9uIGdyZWV0KG5hbWU6IHN0cmluZyk6IHN0cmluZyB7CiAgcmV0dXJuICJIZWxsbywgIiArIG5hbWUKfQoKY29uc3QgbXNnOiBzdHJpbmcgPSBncmVldCgiU3VwZXJKUyIpCmNvbnNvbGUubG9nKG1zZyk) ## Key takeaways - `.sjs` is a typed superset of JavaScript. - Use `superjs check` before `build`. - Types are erased at emit — runtime is JS. **Next:** [Variables and types](./02-variables-and-types.md) ======================================================================== # 02 — Variables and types Source: https://superjs.org/docs/tour/02-variables-and-types # Variables and types **Goal:** Annotate values and let the compiler catch mismatches. Use `const` by default; `let` when reassignment is required. Primitives: `string`, `number`, `boolean`. ## Example ```sjs const pi: number = 3.14 let count: number = 0 count = count + 1 const label: string = "items" const ok: boolean = count > 0 ``` [Open in playground](https://superjs.org/playground#code=Y29uc3QgcGk6IG51bWJlciA9IDMuMTQKbGV0IGNvdW50OiBudW1iZXIgPSAwCmNvdW50ID0gY291bnQgKyAxCgpjb25zdCBsYWJlbDogc3RyaW5nID0gIml0ZW1zIgpjb25zdCBvazogYm9vbGVhbiA9IGNvdW50ID4gMA) ## Key takeaways - `const` bindings cannot be reassigned. - Annotations are optional when inference is obvious. - SJS-E001 fires on type mismatches. **Next:** [Functions](./03-functions.md) ======================================================================== # 03 — Functions Source: https://superjs.org/docs/tour/03-functions # Functions **Goal:** Write typed functions with clear signatures. Arrow functions and `function` declarations both accept parameter and return types. ## Example ```sjs function add(a: number, b: number): number { return a + b } const double = (n: number): number => n * 2 export function greet(name: string): string { return "hi " + name } ``` [Open in playground](https://superjs.org/playground#code=ZnVuY3Rpb24gYWRkKGE6IG51bWJlciwgYjogbnVtYmVyKTogbnVtYmVyIHsKICByZXR1cm4gYSArIGIKfQoKY29uc3QgZG91YmxlID0gKG46IG51bWJlcik6IG51bWJlciA9PiBuICogMgoKZXhwb3J0IGZ1bmN0aW9uIGdyZWV0KG5hbWU6IHN0cmluZyk6IHN0cmluZyB7CiAgcmV0dXJuICJoaSAiICsgbmFtZQp9) ## Key takeaways - Return types are checked at every `return`. - Exported functions form your module API. - Prefer explicit returns on public functions. **Next:** [Control flow](./04-control-flow.md) ======================================================================== # 04 — Control flow Source: https://superjs.org/docs/tour/04-control-flow # Control flow **Goal:** Use branches to narrow types safely. Conditions must be `boolean` — no truthy coercion in `--strict` lint paths. ## Example ```sjs function abs(n: number): number { if (n < 0) { return -n } return n } function label(n: number): string { return n > 0 ? "positive" : n < 0 ? "negative" : "zero" } ``` [Open in playground](https://superjs.org/playground#code=ZnVuY3Rpb24gYWJzKG46IG51bWJlcik6IG51bWJlciB7CiAgaWYgKG4gPCAwKSB7CiAgICByZXR1cm4gLW4KICB9CiAgcmV0dXJuIG4KfQoKZnVuY3Rpb24gbGFiZWwobjogbnVtYmVyKTogc3RyaW5nIHsKICByZXR1cm4gbiA-IDAgPyAicG9zaXRpdmUiIDogbiA8IDAgPyAibmVnYXRpdmUiIDogInplcm8iCn0) ## Key takeaways - Each branch can refine types for locals. - Ternary expressions must share a common result type. - Prefer `===` over `==`. **Next:** [Null safety](./05-null-safety.md) ======================================================================== # 05 — Null safety Source: https://superjs.org/docs/tour/05-null-safety # Null safety **Goal:** Model absence with `T?` instead of abusing `undefined`. `T` is non-nullable by default. `T?` means `T | null`. Use `?.` and `??` like modern JS. ## Example ```sjs function nick(name: string?): string { return name?.toUpperCase() ?? "stranger" } function firstChar(s: string?): string { if (s === null) return "" return s.slice(0, 1) } ``` [Open in playground](https://superjs.org/playground#code=ZnVuY3Rpb24gbmljayhuYW1lOiBzdHJpbmc_KTogc3RyaW5nIHsKICByZXR1cm4gbmFtZT8udG9VcHBlckNhc2UoKSA_PyAic3RyYW5nZXIiCn0KCmZ1bmN0aW9uIGZpcnN0Q2hhcihzOiBzdHJpbmc_KTogc3RyaW5nIHsKICBpZiAocyA9PT0gbnVsbCkgcmV0dXJuICIiCiAgcmV0dXJuIHMuc2xpY2UoMCwgMSkKfQ) ## Key takeaways - No `!` non-null assertion — narrow with `if`. - `T?` is shorthand for `T | null`. - Optional chaining preserves nullability. **Next:** [Pattern matching](./06-pattern-matching.md) ======================================================================== # 06 — Pattern matching Source: https://superjs.org/docs/tour/06-pattern-matching # Pattern matching **Goal:** Replace fragile `switch` with exhaustive `match`. `match` is an expression — every arm produces a value. Missing variants are compile errors (SJS-E007). ## Example ```sjs type Status = Active | Paused | Done function label(s: Status): string { return match s { Active => "active", Paused => "paused", Done => "done", } } ``` [Open in playground](https://superjs.org/playground#code=dHlwZSBTdGF0dXMgPSBBY3RpdmUgfCBQYXVzZWQgfCBEb25lCgpmdW5jdGlvbiBsYWJlbChzOiBTdGF0dXMpOiBzdHJpbmcgewogIHJldHVybiBtYXRjaCBzIHsKICAgIEFjdGl2ZSA9PiAiYWN0aXZlIiwKICAgIFBhdXNlZCA9PiAicGF1c2VkIiwKICAgIERvbmUgPT4gImRvbmUiLAogIH0KfQ) ## Key takeaways - Arms are separated by commas. - Exhaustiveness is enforced at compile time. - `match` works on sum types, not arbitrary strings. **Next:** [Sum types](./07-sum-types.md) ======================================================================== # 07 — Sum types Source: https://superjs.org/docs/tour/07-sum-types # Sum types **Goal:** Model alternatives with tagged variants instead of booleans + fields. Sum types compose: `type Result<T, E> = Ok(T) | Err(E)` (see `@superjs/std-core`). ## Example ```sjs type Result = Ok(T) | Err(E) function divide(a: number, b: number): Result { if (b === 0) return Err("divide by zero") return Ok(a / b) } function run(): void { const r: Result = divide(10, 2) match r { Ok(n) => console.log(n), Err(e) => console.log(e), } } ``` [Open in playground](https://superjs.org/playground#code=dHlwZSBSZXN1bHQ8VCwgRT4gPSBPayhUKSB8IEVycihFKQoKZnVuY3Rpb24gZGl2aWRlKGE6IG51bWJlciwgYjogbnVtYmVyKTogUmVzdWx0PG51bWJlciwgc3RyaW5nPiB7CiAgaWYgKGIgPT09IDApIHJldHVybiBFcnIoImRpdmlkZSBieSB6ZXJvIikKICByZXR1cm4gT2soYSAvIGIpCn0KCmZ1bmN0aW9uIHJ1bigpOiB2b2lkIHsKICBjb25zdCByOiBSZXN1bHQ8bnVtYmVyLCBzdHJpbmc-ID0gZGl2aWRlKDEwLCAyKQogIG1hdGNoIHIgewogICAgT2sobikgPT4gY29uc29sZS5sb2cobiksCiAgICBFcnIoZSkgPT4gY29uc29sZS5sb2coZSksCiAgfQp9) ## Key takeaways - Variants carry payloads: `Some(42)`, `None`. - Prefer `Result` over thrown exceptions for expected errors. - Import helpers from `@superjs/std-core` in real projects. **Next:** [Object types](./08-interfaces.md) ======================================================================== # 08 — Object types Source: https://superjs.org/docs/tour/08-interfaces # Object types **Goal:** Declare object shapes with the `type` brace form. SJS uses `type Name { ... }` for structural types. Conformance is checked structurally — no `implements` keyword. ## Example ```sjs type Point { x: number; y: number; } type Named { name: string; } type Place extends Named { city: string; } function label(p: Place): string { return p.name + " @ " + p.city } ``` [Open in playground](https://superjs.org/playground#code=dHlwZSBQb2ludCB7CiAgeDogbnVtYmVyOwogIHk6IG51bWJlcjsKfQoKdHlwZSBOYW1lZCB7CiAgbmFtZTogc3RyaW5nOwp9Cgp0eXBlIFBsYWNlIGV4dGVuZHMgTmFtZWQgewogIGNpdHk6IHN0cmluZzsKfQoKZnVuY3Rpb24gbGFiZWwocDogUGxhY2UpOiBzdHJpbmcgewogIHJldHVybiBwLm5hbWUgKyAiIEAgIiArIHAuY2l0eQp9) ## Key takeaways - Members end with semicolons. - `extends` composes object types. - Classes satisfy object types implicitly. **Next:** [Generics](./09-generics.md) ======================================================================== # 09 — Generics Source: https://superjs.org/docs/tour/09-generics # Generics **Goal:** Write reusable functions with type parameters. Type parameters use angle brackets: `function id<T>(x: T): T`. No `extends` constraints on type parameters. ## Example ```sjs function id(value: T): T { return value } function first(items: T[]): T? { if (items.length === 0) return null return items[0] } const n: number = id(42) const s: string = id("ok") ``` [Open in playground](https://superjs.org/playground#code=ZnVuY3Rpb24gaWQ8VD4odmFsdWU6IFQpOiBUIHsKICByZXR1cm4gdmFsdWUKfQoKZnVuY3Rpb24gZmlyc3Q8VD4oaXRlbXM6IFRbXSk6IFQ_IHsKICBpZiAoaXRlbXMubGVuZ3RoID09PSAwKSByZXR1cm4gbnVsbAogIHJldHVybiBpdGVtc1swXQp9Cgpjb25zdCBuOiBudW1iZXIgPSBpZCg0MikKY29uc3Qgczogc3RyaW5nID0gaWQoIm9rIik) ## Key takeaways - Generics monomorphize at compile time. - Use structural object types for bounds, not `T extends U`. - Type args can be inferred at call sites. **Next:** [Classes](./10-classes.md) ======================================================================== # 10 — Classes Source: https://superjs.org/docs/tour/10-classes # Classes **Goal:** Encapsulate state with classes — structural conformance, not `implements`. `public` / `private` modifiers are allowed. Do not use `implements` — it is a parse error. ## Example ```sjs class Counter { private value: number = 0 increment(): void { this.value = this.value + 1 } read(): number { return this.value } } const c: Counter = new Counter() c.increment() ``` [Open in playground](https://superjs.org/playground#code=Y2xhc3MgQ291bnRlciB7CiAgcHJpdmF0ZSB2YWx1ZTogbnVtYmVyID0gMAoKICBpbmNyZW1lbnQoKTogdm9pZCB7CiAgICB0aGlzLnZhbHVlID0gdGhpcy52YWx1ZSArIDEKICB9CgogIHJlYWQoKTogbnVtYmVyIHsKICAgIHJldHVybiB0aGlzLnZhbHVlCiAgfQp9Cgpjb25zdCBjOiBDb3VudGVyID0gbmV3IENvdW50ZXIoKQpjLmluY3JlbWVudCgp) ## Key takeaways - Fields are typed like object type members. - Structural object types describe required methods. - No decorator support. **Next:** [Modules](./11-modules.md) ======================================================================== # 11 — Modules Source: https://superjs.org/docs/tour/11-modules # Modules **Goal:** Split code across ES modules. SuperJS emits standard ES modules. Use named exports and explicit import paths. ## Example ```sjs import { ok, err } from "@superjs/std-core" export type UserId = string export function parseId(raw: string): UserId { return raw } export function demo(): void { const r = ok(1) console.log(r) } ``` [Open in playground](https://superjs.org/playground#code=aW1wb3J0IHsgb2ssIGVyciB9IGZyb20gIkBzdXBlcmpzL3N0ZC1jb3JlIgoKZXhwb3J0IHR5cGUgVXNlcklkID0gc3RyaW5nCgpleHBvcnQgZnVuY3Rpb24gcGFyc2VJZChyYXc6IHN0cmluZyk6IFVzZXJJZCB7CiAgcmV0dXJuIHJhdwp9CgpleHBvcnQgZnVuY3Rpb24gZGVtbygpOiB2b2lkIHsKICBjb25zdCByID0gb2soMSkKICBjb25zb2xlLmxvZyhyKQp9) ## Key takeaways - One module per file is the default. - Type-only imports are not required — types erase. - Configure paths in `superjs.config.json`. **Next:** [Async and await](./12-async-await.md) ======================================================================== # 12 — Async and await Source: https://superjs.org/docs/tour/12-async-await # Async and await **Goal:** Type async workflows with `Promise<T>`. `async` functions return `Promise<T>` when annotated. Await only inside `async` bodies. ## Example ```sjs async function fetchText(url: string): Promise { const res: dynamic = await fetch(url) const text: dynamic = await res.text() return text as string } async function main(): Promise { const body: string = await fetchText("https://example.com") console.log(body.length) } ``` [Open in playground](https://superjs.org/playground#code=YXN5bmMgZnVuY3Rpb24gZmV0Y2hUZXh0KHVybDogc3RyaW5nKTogUHJvbWlzZTxzdHJpbmc-IHsKICBjb25zdCByZXM6IGR5bmFtaWMgPSBhd2FpdCBmZXRjaCh1cmwpCiAgY29uc3QgdGV4dDogZHluYW1pYyA9IGF3YWl0IHJlcy50ZXh0KCkKICByZXR1cm4gdGV4dCBhcyBzdHJpbmcKfQoKYXN5bmMgZnVuY3Rpb24gbWFpbigpOiBQcm9taXNlPHZvaWQ-IHsKICBjb25zdCBib2R5OiBzdHJpbmcgPSBhd2FpdCBmZXRjaFRleHQoImh0dHBzOi8vZXhhbXBsZS5jb20iKQogIGNvbnNvbGUubG9nKGJvZHkubGVuZ3RoKQp9) ## Key takeaways - Untyped fetch results start as `dynamic`. - Narrow or validate before treating as `string`. - Lint SJS-L015 warns on missing `await` in async paths. **Next:** [JSX](./13-jsx.md) ======================================================================== # 13 — JSX Source: https://superjs.org/docs/tour/13-jsx # JSX **Goal:** Write components with JSX enabled. Enable JSX in `superjs.config.json` (`"jsx": true`) or use the `.sjsx` extension. JSX lowers to your configured factory (e.g. `React.createElement`). ## Example ```sjs // Save as component.sjsx with jsx enabled export function Greeting(props: { name: string }): dynamic { return

Hello, {props.name}

} ``` [Open in playground](https://superjs.org/playground#code=Ly8gU2F2ZSBhcyBjb21wb25lbnQuc2pzeCB3aXRoIGpzeCBlbmFibGVkCmV4cG9ydCBmdW5jdGlvbiBHcmVldGluZyhwcm9wczogeyBuYW1lOiBzdHJpbmcgfSk6IGR5bmFtaWMgewogIHJldHVybiA8cD5IZWxsbywge3Byb3BzLm5hbWV9PC9wPgp9) ## Key takeaways - JSX requires jsx mode — not valid in plain `.sjs` by default. - Props are usually a structural object type. - See specs/language/039-jsx.md for factory config. **Next:** [Calling JS from SJS](./14-calling-js-from-sjs.md) ======================================================================== # 14 — Calling JS from SJS Source: https://superjs.org/docs/tour/14-calling-js-from-sjs # Calling JS from SJS **Goal:** Call JavaScript libraries safely at the boundary. Import runtime values normally. Treat unknown shapes as `dynamic`, then narrow. ## Example ```sjs import { readFileSync } from "node:fs" function readJson(path: string): dynamic { const text: string = readFileSync(path, "utf8") return JSON.parse(text) } function getName(doc: dynamic): string? { if (doc === null || typeof doc !== "object") return null const name: dynamic = doc.name return typeof name === "string" ? name : null } ``` [Open in playground](https://superjs.org/playground#code=aW1wb3J0IHsgcmVhZEZpbGVTeW5jIH0gZnJvbSAibm9kZTpmcyIKCmZ1bmN0aW9uIHJlYWRKc29uKHBhdGg6IHN0cmluZyk6IGR5bmFtaWMgewogIGNvbnN0IHRleHQ6IHN0cmluZyA9IHJlYWRGaWxlU3luYyhwYXRoLCAidXRmOCIpCiAgcmV0dXJuIEpTT04ucGFyc2UodGV4dCkKfQoKZnVuY3Rpb24gZ2V0TmFtZShkb2M6IGR5bmFtaWMpOiBzdHJpbmc_IHsKICBpZiAoZG9jID09PSBudWxsIHx8IHR5cGVvZiBkb2MgIT09ICJvYmplY3QiKSByZXR1cm4gbnVsbAogIGNvbnN0IG5hbWU6IGR5bmFtaWMgPSBkb2MubmFtZQogIHJldHVybiB0eXBlb2YgbmFtZSA9PT0gInN0cmluZyIgPyBuYW1lIDogbnVsbAp9) ## Key takeaways - `dynamic` replaces TypeScript `any`. - Validate at boundaries — not in hot inner loops. - Use `@superjs/types-*` when available. **Next:** [dynamic and Schema](./15-dynamic-and-schema.md) ======================================================================== # 15 — dynamic and Schema Source: https://superjs.org/docs/tour/15-dynamic-and-schema # dynamic and Schema **Goal:** Parse JSON into typed values with `Schema.parse`. `@superjs/std-schema` provides composable validators returning `Validated<T>`. ## Example ```sjs import { string, object, field } from "@superjs/std-schema" const NameSchema = object([field("name", string())]) function parseName(doc: dynamic): string? { const v = NameSchema.parse(doc) return match v { Valid(payload) => payload.name as string, Invalid(_) => null, } } ``` [Open in playground](https://superjs.org/playground#code=aW1wb3J0IHsgc3RyaW5nLCBvYmplY3QsIGZpZWxkIH0gZnJvbSAiQHN1cGVyanMvc3RkLXNjaGVtYSIKCmNvbnN0IE5hbWVTY2hlbWEgPSBvYmplY3QoW2ZpZWxkKCJuYW1lIiwgc3RyaW5nKCkpXSkKCmZ1bmN0aW9uIHBhcnNlTmFtZShkb2M6IGR5bmFtaWMpOiBzdHJpbmc_IHsKICBjb25zdCB2ID0gTmFtZVNjaGVtYS5wYXJzZShkb2MpCiAgcmV0dXJuIG1hdGNoIHYgewogICAgVmFsaWQocGF5bG9hZCkgPT4gcGF5bG9hZC5uYW1lIGFzIHN0cmluZywKICAgIEludmFsaWQoXykgPT4gbnVsbCwKICB9Cn0) ## Key takeaways - Schemas are reified — compose with `object`, `optional`, etc. - Prefer schema validation over repeated `typeof` chains. - See generated [std-schema API](../api/std-schema.md). **Next:** [Errors and Result](./16-errors-and-result.md) ======================================================================== # 16 — Errors and Result Source: https://superjs.org/docs/tour/16-errors-and-result # Errors and Result **Goal:** Propagate failures explicitly through call stacks. Unexpected bugs may still throw at runtime, but expected failures belong in `Result`. ## Example ```sjs type Result = Ok(T) | Err(E) function step1(): Result { return Ok(2) } function step2(n: number): Result { if (n < 0) return Err("negative") return Ok(n * 10) } function pipeline(): Result { const a: Result = step1() return match a { Ok(n) => step2(n), Err(e) => Err(e), } } ``` [Open in playground](https://superjs.org/playground#code=dHlwZSBSZXN1bHQ8VCwgRT4gPSBPayhUKSB8IEVycihFKQoKZnVuY3Rpb24gc3RlcDEoKTogUmVzdWx0PG51bWJlciwgc3RyaW5nPiB7CiAgcmV0dXJuIE9rKDIpCn0KCmZ1bmN0aW9uIHN0ZXAyKG46IG51bWJlcik6IFJlc3VsdDxudW1iZXIsIHN0cmluZz4gewogIGlmIChuIDwgMCkgcmV0dXJuIEVycigibmVnYXRpdmUiKQogIHJldHVybiBPayhuICogMTApCn0KCmZ1bmN0aW9uIHBpcGVsaW5lKCk6IFJlc3VsdDxudW1iZXIsIHN0cmluZz4gewogIGNvbnN0IGE6IFJlc3VsdDxudW1iZXIsIHN0cmluZz4gPSBzdGVwMSgpCiAgcmV0dXJuIG1hdGNoIGEgewogICAgT2sobikgPT4gc3RlcDIobiksCiAgICBFcnIoZSkgPT4gRXJyKGUpLAogIH0KfQ) ## Key takeaways - Callers must handle `Err` — the type system enforces it. - Combine steps with `match` or helper functions. - See [migration guide](../migration/02-idioms.md). **Next:** [Iterators and for...of](./17-iterators-and-for-of.md) ======================================================================== # 17 — Iterators and for...of Source: https://superjs.org/docs/tour/17-iterators-and-for-of # Iterators and for...of **Goal:** Loop over arrays and iterable values. Standard `for...of` works on arrays and other iterables. ## Example ```sjs function sum(nums: number[]): number { let total: number = 0 for (const n of nums) { total = total + n } return total } const values: number[] = [1, 2, 3] console.log(sum(values)) ``` [Open in playground](https://superjs.org/playground#code=ZnVuY3Rpb24gc3VtKG51bXM6IG51bWJlcltdKTogbnVtYmVyIHsKICBsZXQgdG90YWw6IG51bWJlciA9IDAKICBmb3IgKGNvbnN0IG4gb2YgbnVtcykgewogICAgdG90YWwgPSB0b3RhbCArIG4KICB9CiAgcmV0dXJuIHRvdGFsCn0KCmNvbnN0IHZhbHVlczogbnVtYmVyW10gPSBbMSwgMiwgM10KY29uc29sZS5sb2coc3VtKHZhbHVlcykp) ## Key takeaways - Loop variables are inferred from the iterable element type. - Use `@superjs/std-collections` `List` for functional helpers. - Generators follow ECMAScript rules. **Next:** [Serverless handlers](./18-serverless-handlers.md) ======================================================================== # 18 — Serverless handlers Source: https://superjs.org/docs/tour/18-serverless-handlers # Serverless handlers **Goal:** Structure edge and serverless entrypoints in SJS. Scaffold templates with `superjs init workers-api` or `lambda-handler`. For a full Node backend, see [mvb-fastify](../../examples/mvb-fastify/). ## Example ```sjs export async function fetch(request: dynamic): Promise { const url: dynamic = new URL(request.url as string) const path: string = url.pathname as string if (path === "/health") { return new Response("ok", { status: 200 }) } return new Response("not found", { status: 404 }) } ``` [Open in playground](https://superjs.org/playground?mode=workers#code=ZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGZldGNoKHJlcXVlc3Q6IGR5bmFtaWMpOiBQcm9taXNlPGR5bmFtaWM-IHsKICBjb25zdCB1cmw6IGR5bmFtaWMgPSBuZXcgVVJMKHJlcXVlc3QudXJsIGFzIHN0cmluZykKICBjb25zdCBwYXRoOiBzdHJpbmcgPSB1cmwucGF0aG5hbWUgYXMgc3RyaW5nCiAgaWYgKHBhdGggPT09ICIvaGVhbHRoIikgewogICAgcmV0dXJuIG5ldyBSZXNwb25zZSgib2siLCB7IHN0YXR1czogMjAwIH0pCiAgfQogIHJldHVybiBuZXcgUmVzcG9uc2UoIm5vdCBmb3VuZCIsIHsgc3RhdHVzOiA0MDQgfSkKfQ) ## Key takeaways - Handlers take `dynamic` at the platform boundary. - Use `match` on paths and event shapes. - Not decorators — serverless export handlers only. **Next:** [Tooling tour](./19-tooling-tour.md) ======================================================================== # 19 — Tooling tour Source: https://superjs.org/docs/tour/19-tooling-tour # Tooling tour **Goal:** Know the core `superjs` subcommands. | Command | Purpose | |---------|---------| | `check` | Type-check without emit | | `build` | Compile to JS | | `lint` | Style rules SJS-L* | | `format` | Canonical formatting | | `doc` / `docgen` | API docs from exports | | `init` | Scaffold project templates | | `lsp` | Language server (stdio) | ## Example ```sjs // Typical loop // superjs check src/**/*.sjs // superjs lint src/**/*.sjs // superjs build src --out-dir dist ``` [Open in playground](https://superjs.org/playground#code=Ly8gVHlwaWNhbCBsb29wCi8vIHN1cGVyanMgY2hlY2sgc3JjLyoqLyouc2pzCi8vIHN1cGVyanMgbGludCBzcmMvKiovKi5zanMKLy8gc3VwZXJqcyBidWlsZCBzcmMgLS1vdXQtZGlyIGRpc3Q) ## Key takeaways - Run `check` in CI on every PR. - `format --check` prevents style drift. - LSP powers editor diagnostics. **Next:** [Migrating a TS file](./20-migrating-a-ts-file.md) ======================================================================== # 20 — Migrating a TS file Source: https://superjs.org/docs/tour/20-migrating-a-ts-file # Migrating a TS file **Goal:** Apply the TS→SJS rewrite checklist on a real module. 1. Rename `.ts` → `.sjs`. 2. Fix banned constructs (`any`→`dynamic`, `enum`→sum types). 3. Run `superjs migrate from-prototype` if imports still point at prototype paths. 4. Run `superjs check` until clean. Full guide: [Migration](../migration/index.md). ## Example ```sjs // After migration — no any, no enum type Role = Admin | Member type User { name: string; role: Role; } function roleLabel(u: User): string { return match u.role { Admin => "admin", Member => "member", } } ``` [Open in playground](https://superjs.org/playground#code=Ly8gQWZ0ZXIgbWlncmF0aW9uIOKAlCBubyBhbnksIG5vIGVudW0KdHlwZSBSb2xlID0gQWRtaW4gfCBNZW1iZXIKCnR5cGUgVXNlciB7CiAgbmFtZTogc3RyaW5nOwogIHJvbGU6IFJvbGU7Cn0KCmZ1bmN0aW9uIHJvbGVMYWJlbCh1OiBVc2VyKTogc3RyaW5nIHsKICByZXR1cm4gbWF0Y2ggdS5yb2xlIHsKICAgIEFkbWluID0-ICJhZG1pbiIsCiAgICBNZW1iZXIgPT4gIm1lbWJlciIsCiAgfQp9) ## Key takeaways - Migrate leaf modules first. - Use the [compat matrix](../compat/index.md) for npm wrappers. - `superjs migrate from-ts` assists bulk moves. ======================================================================== # README Source: https://superjs.org/docs/press/README # Press kit (v1.0 launch) Assets and copy for launch announcements. GA versions live under `docs/launch/` as drafts. ## Quick links | Asset | Location | |-------|----------| | Logo / brand | _TBD — add SVG/PNG before GA_ | | Website | https://superjs.org | | Playground | https://superjs.org/playground | | Why SJS | [`docs/why/`](../why/index.md) | | Benchmarks | [`docs/perf/`](../perf/index.md) | | 5-minute backend demo | [`examples/mvb-fastify/`](../../examples/mvb-fastify/) | | TypeScript one-pager | [`sjs-vs-typescript-one-pager.md`](./sjs-vs-typescript-one-pager.md) | ## Elevator pitch (30 s) SuperJS is a strict, sound superset of JavaScript — not TypeScript with a new extension. It bans `any`, intersection types, and conditional types; adds sum types, exhaustive `match`, and default null safety. One hand-written compiler ships today as clean ES2022; native and WASM targets are on the roadmap. ## Key numbers (2026-06-24 bench) - Cold compile ~14k LOC: **81 ms** (vs tsc typecheck ~721 ms on same corpus) - LSP idle heap @ ~140k LOC: **196 MB** (target ≤ 250 MB) - 30 `@superjs/types-*` wrappers for npm interop ## Screenshots _Capture before GA:_ 1. Playground with sum type + match 2. VS Code hover showing SJS type 3. `superjs check` pretty diagnostics 4. Docs tour lesson 18 (serverless mode) ## Contact - GitHub: https://github.com/hbarve1/super-js - Issues / security: see [`SECURITY.md`](../../SECURITY.md) ======================================================================== # sjs-vs-typescript-one-pager Source: https://superjs.org/docs/press/sjs-vs-typescript-one-pager # SuperJS vs TypeScript — one pager _For press / launch. Full comparison: [`docs/comparisons/sjs-vs-typescript.md`](../comparisons/sjs-vs-typescript.md)._ ## One sentence SuperJS is JavaScript with a **sound** type system — it removes TypeScript's unsound escape hatches instead of adding more configuration. ## When to choose SuperJS - You want **provable null safety** without `strictNullChecks` footguns - You're tired of `any` silently infecting call chains - You want **sum types + exhaustive match** without discriminated-union ceremony - You need a **smaller language** that's easier to teach and review ## When to stay on TypeScript - You depend heavily on mapped/conditional types and library inference - Your team needs the full npm `@types/*` ecosystem without wrappers - You require a mature IDE/debugger story today (SJS DAP is post-1.0) ## Feature contrast | | TypeScript | SuperJS | |---|------------|---------| | `any` | Allowed; propagates silently | **Banned** — use `dynamic` (explicit, lintable) | | Nullability | Opt-in strict | **Non-null by default**; `T?` for nullable | | Unions | Discriminated unions (manual) | **Sum types** + exhaustive `match` | | Intersections | `A & B` | **Banned** — structural `type X extends A, B` | | Conditional types | Core feature | **Banned** — use sum types | | JSX | Config required | **On by default** | | Output | Erased types | Clean ES2022 JS today | ## Benchmark snapshot Synthetic ~14k LOC corpus (macOS arm64, Node 24): | Tool | Cold wall time | |------|----------------| | SuperJS compile | **81 ms** | | tsc `--noEmit` | 721 ms | | esbuild transpile | 431 ms | See [`docs/perf/`](../perf/index.md). ## Try it ```bash npm install -g @superjsorg/cli superjs init superjs build ``` Playground: https://superjs.org/playground ======================================================================== # Migration Guide Source: https://superjs.org/docs/migration/index # Migration Guide Move from TypeScript to SuperJS incrementally: rename files, fix banned constructs, adopt idioms, then wire npm libraries through `@superjs/types-*` wrappers or `dynamic` boundaries. ## How this guide is organized | Part | Page | Focus | |------|------|--------| | 1 | [Syntax rewrites](./01-syntax.md) | Every banned TS construct → SJS equivalent + error code | | 2 | [Idiom changes](./02-idioms.md) | Errors, nulls, sum types, modules | | 3 | [Library ecosystem](./03-library.md) | npm interop, wrappers, tooling, gradual rollout | Authoritative ban list: [ADR-004](../../specs/design/ADR-004-banned-ts-constructs.md) and [banned features](../../specs/language/007-banned-features.md). ## Your first file (30-line walkthrough) Start with one leaf module — a small utility with no framework decorators. Below is a typical TypeScript handler; each numbered change maps to a section in Part 1 or 2. ### Before (TypeScript) ```typescript enum Role { Admin = "admin", Member = "member" } interface User { id: string name: string role: Role email?: string } function findUser(users: User[], id: string): User | undefined { return users.find((u) => u.id === id) } function parseBody(body: any): User { if (!body || typeof body.name !== "string") { throw new Error("invalid body") } const role = body.role as Role return { id: String(body.id ?? crypto.randomUUID()), name: body.name, role, email: body.email, } } export function createUser(users: User[], body: unknown): User { const user = parseBody(body) const existing = findUser(users, user.id) if (existing) throw new Error("duplicate id") users.push(user) return user } ``` ### After (SuperJS) ```sjs type Role = Admin | Member type User { id: string; name: string; role: Role; email: string?; } type Result = Ok(T) | Err(E) function findUser(users: User[], id: string): User? { for (const u of users) { if (u.id === id) return u } return null } function parseBody(body: dynamic): Result { if (body === null || typeof body !== "object") { return Err("invalid body") } const name: dynamic = body.name const role: dynamic = body.role const email: dynamic = body.email if (typeof name !== "string") return Err("invalid body") let roleVal: Role if (role === "admin") { roleVal = Admin } else if (role === "member") { roleVal = Member } else { return Err("invalid role") } const id: dynamic = body.id const user: User = { id: typeof id === "string" ? id : crypto.randomUUID(), name, role: roleVal, email: typeof email === "string" ? email : null, } return Ok(user) } export function createUser(users: User[], body: dynamic): Result { const parsed: Result = parseBody(body) return match parsed { Err(e) => Err(e), Ok(user) => { const existing: User? = findUser(users, user.id) if (existing !== null) return Err("duplicate id") users.push(user) return Ok(user) }, } } ``` ### Change log 1. **`enum` → sum type** — `type Role = Admin | Member` ([SJS-E010](../error-codes/SJS-E010.md)) 2. **`interface` → structural `type`** — SJS object types use `type Name { ... }` 3. **Optional `email?` → nullable `email: string?`** — non-nullable by default; use `T?` for `null` ([null safety](../../specs/language/001-null-safety.md)) 4. **`User | undefined` → `User?`** — nullable return; explicit `null` instead of `undefined` for absence 5. **`any` / `unknown` boundary → `dynamic`** — parse untrusted JSON at the edge ([SJS-E004](../error-codes/SJS-E004.md)) 6. **`throw` → `Result` + `match`** — errors stay in the type system (Part 2) 7. **String role → exhaustive `match`** — replaces unsafe `as Role` cast ### Next steps ```bash # Type-check the file superjs check src/users.sjs # Migrate prototype-era import paths (if applicable) superjs migrate from-prototype ./src --dry-run ``` Continue with [Part 1 — Syntax rewrites](./01-syntax.md) for the full banned-construct table. ======================================================================== # Syntax Rewrites Source: https://superjs.org/docs/migration/01-syntax # Part 1 — Syntax rewrites SuperJS rejects twelve TypeScript construct categories at parse or type-check time. There is no flag or pragma to re-enable them ([ADR-004](../../specs/design/ADR-004-banned-ts-constructs.md)). ## Quick reference | TypeScript | SuperJS | Error | |------------|---------|-------| | `any` | `dynamic` | [SJS-E004](../error-codes/SJS-E004.md) | | `A & B` intersection | `type C extends A, B { }` | [SJS-E005](../error-codes/SJS-E005.md) | | `T extends U ? A : B` | sum type + `match` | [SJS-E008](../error-codes/SJS-E008.md) | | `infer T` | not supported — explicit types | [SJS-E009](../error-codes/SJS-E009.md) | | `enum E { ... }` | sum type / unit variants | [SJS-E010](../error-codes/SJS-E010.md) | | `value!` non-null assertion | `if` / `?.` narrowing | [SJS-E011](../error-codes/SJS-E011.md) | | `namespace N { }` | ES `import` / `export` | [SJS-E012](../error-codes/SJS-E012.md) | | `{ [K in keyof T]: U }` mapped | structural index signature | [SJS-E006](../error-codes/SJS-E006.md) | | `T['key']` indexed access | name fields explicitly | [SJS-E006](../error-codes/SJS-E006.md) | | `typeof x` in type position | explicit type declaration | [SJS-E006](../error-codes/SJS-E006.md) | | `expr` angle cast | `expr as T` only | parse error | | `==` / `!=` | `===` / `!==` | [SJS-L003](../error-codes/SJS-L003.md) | | `@Decorator` | not supported | banned | | `prop?: T` (optional) | `prop: T?` or `prop?: T` per semantics | — | Decorators are **not** on the roadmap — they are incompatible with the ECMAScript-first emit model. --- ## `any` → `dynamic` ```typescript // TypeScript function load(raw: any): string { return raw.toUpperCase() } ``` ```sjs // SuperJS function load(raw: dynamic): string { if (typeof raw !== "string") { throw new Error("expected string") } return raw.toUpperCase() } ``` `dynamic` is explicit and lintable. See [dynamic type](../../specs/language/004-dynamic.md). --- ## Intersection `A & B` → structural `extends` ```typescript // TypeScript type Named = { name: string } type Aged = { age: number } type Person = Named & Aged ``` ```sjs // SuperJS type Named { name: string; } type Aged { age: number; } type Person extends Named, Aged { } ``` --- ## Conditional types → sum types + `match` ```typescript // TypeScript type ApiResult = T extends string ? { text: T } : { value: T } ``` ```sjs // SuperJS type TextResult(text: string) | ValueResult(value: dynamic) function wrap(x: dynamic): TextResult | ValueResult { if (typeof x === "string") return TextResult(x) return ValueResult(x) } ``` --- ## `enum` → sum types ```typescript // TypeScript enum Status { Active, Inactive } ``` ```sjs // SuperJS — unit variants type Status = Active | Inactive // With payload type Status = Active(string) | Inactive ``` --- ## Non-null assertion `!` → narrowing ```typescript // TypeScript function len(s: string | null): number { return s!.length } ``` ```sjs // SuperJS function len(s: string?): number { if (s === null) return 0 return s.length } ``` --- ## `namespace` → ES modules ```typescript // TypeScript namespace Util { export function id(x: T): T { return x } } ``` ```sjs // SuperJS — util.sjs export function id(x: T): T { return x } ``` --- ## Mapped / indexed / `typeof` types → explicit shapes ```typescript // TypeScript type Partial = { [K in keyof T]?: T[K] } type Name = Person["name"] type Inferred = typeof someValue ``` ```sjs // SuperJS — declare the shape you need type StringMap { [key: string]: string; } type Person { name: string; age: number; } // Use Person.name fields directly; no T["key"] operator ``` --- ## Optional and nullable properties ```typescript // TypeScript interface Config { host: string port?: number } ``` ```sjs // SuperJS — nullable field (may be null when present) type Config { host: string; port: number?; } ``` See [null safety](../../specs/language/001-null-safety.md): `T?` means `T | null`; optional `prop?: T` on object types means `T | undefined` when absent. --- ## Next [Part 2 — Idiom changes](./02-idioms.md) ======================================================================== # Idiom Changes Source: https://superjs.org/docs/migration/02-idioms # Part 2 — Idiom changes Syntax rewrites get files compiling; idioms determine whether migrated code stays safe and readable. ## 1. Error handling — `throw` → `Result` TypeScript often uses exceptions for expected failures. SuperJS codebases typically model those as sum types: ```typescript // TypeScript function parseConfig(json: string): Config { try { return JSON.parse(json) as Config } catch { throw new Error("parse failed") } } ``` ```sjs // SuperJS type Result = Ok(T) | Err(E) function parseConfig(json: string): Result { const raw: dynamic = JSON.parse(json) if (!isConfig(raw)) return Err("parse failed") return Ok(raw as Config) } function isConfig(v: dynamic): boolean { return v !== null && typeof v === "object" } ``` Callers use exhaustive `match` instead of `try/catch` for control flow. --- ## 2. Null handling — `undefined` checks → `T?` + narrowing - Non-nullable by default: `string` cannot hold `null`. - Nullable: `string?` ≡ `string | null`. - Use `?.` and `??` for optional chaining and defaults (same as modern JS). - Replace `value!` with an explicit `if (value === null)` guard ([SJS-E011](../error-codes/SJS-E011.md)). ```sjs function greet(name: string?): string { if (name === null) return "stranger" return "hello " + name } ``` --- ## 3. Sum types — object unions → variants + `match` ```typescript // TypeScript type Shape = | { kind: "circle"; r: number } | { kind: "rect"; w: number; h: number } function area(s: Shape): number { switch (s.kind) { case "circle": return Math.PI * s.r * s.r case "rect": return s.w * s.h } } ``` ```sjs // SuperJS type Shape = Circle(number) | Rect(number, number) function area(s: Shape): number { return match s { Circle(r) => Math.PI * r * r, Rect(w, h) => w * h, } } ``` The compiler emits [SJS-E007](../error-codes/SJS-E007.md) if a variant is not handled. --- ## 4. `as` casts — narrow `dynamic`, trust structure elsewhere - `expr as T` is allowed for narrowing `dynamic` after runtime checks. - Do **not** use `as` to silence errors the way TS uses `as any`. - Structural object types infer field types without casts when shapes align. --- ## 5. `const enum` → unit sum type ```typescript const enum Dir { Up, Down } ``` ```sjs type Dir = Up | Down ``` No reverse numeric mapping at runtime — variants lower to tagged values. --- ## 6. Generics — no `extends` constraints on type parameters SJS supports `` type parameters with optional defaults, but **not** `T extends U` constraints ([generics](../../specs/language/005-generics.md)). Encode bounds structurally: ```sjs type HasLength { length: number; } function count(xs: HasLength): number { return xs.length } ``` If overloads differ by shape, use separate functions or a sum-type argument instead of `T extends U` constraints. --- ## 7. Module augmentation — not supported TypeScript `declare module "pkg" { ... }` augmentation is not available. Options: - Wrap the library in your own module and expose a typed facade. - Use `@superjs/types-*` when a wrapper exists ([compat matrix](../compat/index.md)). - Hold foreign values as `dynamic` and validate at the boundary. --- ## 8. `import type` → regular `import` Type-only imports merge into value imports. Types are erased at emit — no `import type` keyword required. ```sjs import { fastify } from "fastify" import type { User } from "./user.sjs" ``` Both forms parse; emitted JS contains only runtime imports. --- ## Next [Part 3 — Library ecosystem](./03-library.md) ======================================================================== # Library Ecosystem Source: https://superjs.org/docs/migration/03-library # Part 3 — Library ecosystem ## 1. Using JavaScript libraries directly Any npm package works at runtime. Types from `.d.ts` files are not consumed as-is — treat untrusted values as `dynamic` and narrow: ```sjs import { readFileSync } from "node:fs" function loadJson(path: string): Result { try { const text: string = readFileSync(path, "utf8") const value: dynamic = JSON.parse(text) return Ok(value) } catch (e) { return Err("read failed") } } ``` For repeated shapes, add validators (manual `if` chains, or `@superjs/stdlib` schema helpers when available). --- ## 2. `@superjs/types-*` wrappers Wave-1 typed bindings cover 30 popular packages. Install the wrapper alongside the runtime package: ```bash npm install fastify @superjs/types-fastify ``` ```sjs import { fastify } from "fastify" const app = fastify() app.get("/health", async () => ({ ok: true })) ``` Coverage and status per package: **[Compatibility matrix](../compat/index.md)** (generated from each wrapper's `STATUS.md`). ### Wave-1 backends and data fastify, express, hono, koa, connect, pg, mysql2, prisma, mongoose, redis ### Utilities and auth zod, joi, axios, node-fetch, undici, pino, winston, dotenv, jsonwebtoken, passport ### Cloud and testing aws-sdk-core, cloudflare-workers, bullmq, multer, supertest ### Frontend and test runners react, nextjs, vite, vitest, jest When no wrapper exists, use `dynamic` at the import boundary and file an issue or contribute a wrapper under `superjs/libs/types-*`. --- ## 3. `superjs migrate from-prototype` Early SuperJS prototypes used deep relative imports into `prototype/` packages. The CLI rewrites those to `@superjs/*` workspace packages and emits a report: ```bash # Preview changes superjs migrate from-prototype ./src --dry-run # Write migrated tree + MIGRATION_REPORT.md superjs migrate from-prototype ./src --out ./migrated ``` This does **not** convert TypeScript syntax — run it after `.sjs` files exist and you need import-path cleanup (WS-A5). Assisted TS → SJS migration (separate subcommand): ```bash superjs migrate from-ts ./src ``` --- ## 4. Gradual migration strategy Recommended order for a brownfield TypeScript repo: 1. **Pick a leaf module** — no decorators, few dependencies ([index walkthrough](./index.md)). 2. **Rename** `.ts` → `.sjs` one file at a time; fix parse errors first. 3. **Clear banned constructs** — `any`, `enum`, `namespace`, intersections, conditional types ([Part 1](./01-syntax.md)). 4. **Replace `any`** with `dynamic` + validation at module boundaries. 5. **Convert `enum`** to sum types; replace `switch` with `match`. 6. **Replace throw-based control flow** with `Result` + `match` where failures are expected ([Part 2](./02-idioms.md)). 7. **Add wrappers** — swap `dynamic` facades for `@superjs/types-*` as they land on the [compat matrix](../compat/index.md). 8. **Wire CI** — `superjs check` on `src/**/*.sjs` in your pipeline. Keep TypeScript files until their dependency cone is migrated — mixed `.ts` / `.sjs` repos are fine during transition if your bundler resolves both. --- ## Related - [Why SuperJS](../why/index.md) — when migration is worth the cost - [CLI reference](../../superjs/apps/cli/README.md) — `check`, `build`, `migrate` - [mvb-fastify example](../../examples/mvb-fastify/) — end-to-end backend sample ======================================================================== # API Reference Source: https://superjs.org/docs/api/index # API Reference Generated from `superjs/libs/stdlib/src/modules/*.sjs` via `superjs docgen`. Regenerate: ```bash node scripts/sync-api-docs.mjs ``` ## Stdlib modules - [std-async](./std-async.md) — `@superjs/std-async` - [std-collections](./std-collections.md) — `@superjs/std-collections` - [std-core](./std-core.md) — `@superjs/std-core` - [std-fs](./std-fs.md) — `@superjs/std-fs` - [std-json](./std-json.md) — `@superjs/std-json` - [std-math](./std-math.md) — `@superjs/std-math` - [std-path](./std-path.md) — `@superjs/std-path` - [std-process](./std-process.md) — `@superjs/std-process` - [std-schema](./std-schema.md) — `@superjs/std-schema` - [std-string](./std-string.md) — `@superjs/std-string` - [std-time](./std-time.md) — `@superjs/std-time` _Last updated: 2026-06-24._ ======================================================================== # std-async Source: https://superjs.org/docs/api/std-async # std-async small async helpers over Promise. ## Functions ### `sleep` ```sjs function sleep(ms: number): Promise ``` ### `delayValue` ```sjs async function delayValue(value: T, ms: number): Promise ``` ======================================================================== # std-collections Source: https://superjs.org/docs/api/std-collections # std-collections typed wrappers over built-in collections. ## Classes ### `List` ```sjs class List ``` ## Functions ### `listOf` ```sjs function listOf(items: T[]): List ``` ======================================================================== # std-core Source: https://superjs.org/docs/api/std-core # std-core Option and Result, the canonical optional/error types. ## Types ### `Option` ```sjs type Option = Some(T) | None ``` `Some(value)` — an optional value is present. ### `Result` ```sjs type Result = Ok(T) | Err(E) ``` `Ok(value)` or `Err(error)` — explicit success/failure without exceptions. ## Functions ### `some` ```sjs function some(value: T): Option ``` Wrap `value` in `Some`. ### `isSome` ```sjs function isSome(o: Option): boolean ``` Return `true` when the option is `Some`. ### `unwrapOr` ```sjs function unwrapOr(o: Option, fallback: T): T ``` Return `value` from `Some`, or `fallback` for `None`. ### `ok` ```sjs function ok(value: T): Result ``` Construct `Ok(value)`. ### `err` ```sjs function err(error: E): Result ``` Construct `Err(error)`. ### `isOk` ```sjs function isOk(r: Result): boolean ``` ### `resultOr` ```sjs function resultOr(r: Result, fallback: T): T ``` ======================================================================== # std-fs Source: https://superjs.org/docs/api/std-fs # std-fs Result-returning wrappers over Node's synchronous fs. ## Types ### `FsResult` ```sjs type FsResult = FsOk(T) | FsErr(string) ``` ## Functions ### `readText` ```sjs function readText(path: string): FsResult ``` ### `writeText` ```sjs function writeText(path: string, data: string): FsResult ``` ### `exists` ```sjs function exists(path: string): boolean ``` ======================================================================== # std-json Source: https://superjs.org/docs/api/std-json # std-json Result-returning JSON parse/stringify. ## Types ### `JsonResult` ```sjs type JsonResult = JsonOk(T) | JsonErr(string) ``` ## Functions ### `parse` ```sjs function parse(text: string): JsonResult ``` ### `stringify` ```sjs function stringify(value: dynamic): string ``` ### `stringifyPretty` ```sjs function stringifyPretty(value: dynamic, indent: number): string ``` ======================================================================== # std-math Source: https://superjs.org/docs/api/std-math # std-math numeric helpers over the JS Math global. ## Functions ### `abs` ```sjs function abs(x: number): number ``` Absolute value (sign stripped). ### `sign` ```sjs function sign(x: number): number ``` ### `min` ```sjs function min(a: number, b: number): number ``` ### `max` ```sjs function max(a: number, b: number): number ``` ### `clamp` ```sjs function clamp(x: number, lo: number, hi: number): number ``` Clamp `x` to the inclusive range `[lo, hi]`. ### `lerp` ```sjs function lerp(a: number, b: number, t: number): number ``` ### `floor` ```sjs function floor(x: number): number ``` ### `ceil` ```sjs function ceil(x: number): number ``` ### `round` ```sjs function round(x: number): number ``` ### `sqrt` ```sjs function sqrt(x: number): number ``` ### `pow` ```sjs function pow(base: number, exp: number): number ``` ## Constants ### `PI` ```sjs const PI: number ``` Ratio of a circle's circumference to its diameter. ### `E` ```sjs const E: number ``` Euler's number. ======================================================================== # std-path Source: https://superjs.org/docs/api/std-path # std-path POSIX-style path helpers (pure string logic, no host deps). ## Functions ### `basename` ```sjs function basename(p: string): string ``` ### `dirname` ```sjs function dirname(p: string): string ``` ### `extname` ```sjs function extname(p: string): string ``` ### `join` ```sjs function join(a: string, b: string): string ``` ### `isAbsolute` ```sjs function isAbsolute(p: string): boolean ``` ======================================================================== # std-process Source: https://superjs.org/docs/api/std-process # std-process typed access to the process environment. ## Functions ### `args` ```sjs function args(): string[] ``` ### `env` ```sjs function env(key: string): string? ``` ### `cwd` ```sjs function cwd(): string ``` ### `platform` ```sjs function platform(): string ``` ### `exit` ```sjs function exit(code: number): void ``` ======================================================================== # std-schema Source: https://superjs.org/docs/api/std-schema # std-schema a small reified Schema<T> validator (M7, MVP). ## Types ### `Validated` ```sjs type Validated = Valid(T) | Invalid(string) ``` Outcome of `Schema.parse` — either `Valid(T)` or `Invalid(message)`. ## Classes ### `Schema` ```sjs class Schema ``` Reified validator with `accepts` and `parse`. ### `Field` ```sjs class Field ``` ## Functions ### `string` ```sjs function string(): Schema ``` Schema that accepts JavaScript strings. ### `number` ```sjs function number(): Schema ``` ### `boolean` ```sjs function boolean(): Schema ``` ### `array` ```sjs function array(item: Schema): Schema ``` ### `field` ```sjs function field(key: string, schema: Schema): Field ``` ### `object` ```sjs function object(fields: Field[]): Schema ``` ### `literal` ```sjs function literal(expected: string): Schema ``` ### `optional` ```sjs function optional(item: Schema): Schema ``` ### `nullable` ```sjs function nullable(item: Schema): Schema ``` ======================================================================== # std-string Source: https://superjs.org/docs/api/std-string # std-string string helpers (thin, total wrappers over JS string ops). ## Functions ### `trim` ```sjs function trim(s: string): string ``` Remove leading and trailing whitespace. ### `lower` ```sjs function lower(s: string): string ``` ### `upper` ```sjs function upper(s: string): string ``` ### `split` ```sjs function split(s: string, sep: string): string[] ``` ### `join` ```sjs function join(parts: string[], sep: string): string ``` ### `includes` ```sjs function includes(s: string, needle: string): boolean ``` ### `startsWith` ```sjs function startsWith(s: string, prefix: string): boolean ``` ### `endsWith` ```sjs function endsWith(s: string, suffix: string): boolean ``` ### `replace` ```sjs function replace(s: string, target: string, replacement: string): string ``` ======================================================================== # std-time Source: https://superjs.org/docs/api/std-time # std-time instants and durations over the JS Date/clock. ## Functions ### `nowMs` ```sjs function nowMs(): number ``` ### `toISO` ```sjs function toISO(ms: number): string ``` ### `seconds` ```sjs function seconds(n: number): number ``` ### `minutes` ```sjs function minutes(n: number): number ``` ## Constants ### `SECOND` ```sjs const SECOND: number ``` ### `MINUTE` ```sjs const MINUTE: number ``` ### `HOUR` ```sjs const HOUR: number ``` ### `DAY` ```sjs const DAY: number ``` ======================================================================== # Why SuperJS Source: https://superjs.org/docs/why/index # Why SuperJS ## The one-sentence pitch SuperJS is JavaScript with a stricter type system: sum types, null safety by default, and no silent escape hatches — for teams who want TypeScript's ergonomics without `any` eroding their guarantees. ## What SJS solves TypeScript made typed JavaScript mainstream, but it kept the exits open. SuperJS targets pain points that accumulate in real backends: - **`any` spreads silently** — one untyped value poisons every function it touches. SJS bans `any`; use explicit `dynamic` when you need runtime-checked interop. - **`null` and `undefined` are easy to confuse** — `T` is non-nullable by default; `T?` means nullable. No `!` non-null assertion to lie to the compiler. - **Discriminated unions without exhaustiveness** — TypeScript unions compile without forcing you to handle every case. SJS `match` is exhaustive; missing a variant is a compile error. - **Invisible error paths** — `throw` makes failure types disappear from signatures. SJS encourages `Result` sum types and `match` so errors stay in the type system. See [mission](../../specs/mission.md) and [ADR-004 banned constructs](../../specs/design/ADR-004-banned-ts-constructs.md) for the full design rationale. ## 5-minute backend demo The [mvb-fastify example](../../examples/mvb-fastify/) is a minimal Fastify API written entirely in `.sjs`. It demonstrates sum types, exhaustive `match`, nullable lookup, and `Result`-based validation in one route module: ```sjs type User = Admin(string) | Member(string) type Result = Ok(T) | Err(E) function parseCreate(body: dynamic): Result { if (body === null || typeof body !== "object") { return err("body must be an object") } const name: dynamic = body.name const role: dynamic = body.role if (typeof name !== "string" || name.length === 0) { return err("name is required") } if (role === "admin") return ok(userAdmin(name)) if (role === "member") return ok(userMember(name)) return err('role must be "admin" or "member"') } // POST /users — Result + match in a real handler app.post("/users", async (req: dynamic) => { const created: Result = parseCreate(req.body) return match created { Ok(user) => { store.push(user) return { id: displayName(user), role: roleLabel(user) } }, Err(message) => ({ error: message }), } }) ``` Clone the repo and run it: ```bash cd examples/mvb-fastify npm install && npm run build && npm start curl http://127.0.0.1:3000/users ``` Full source: [`examples/mvb-fastify/src/routes/users.sjs`](../../examples/mvb-fastify/src/routes/users.sjs). ## What SJS wins vs TypeScript | Area | SuperJS | TypeScript | |------|---------|------------| | Sum types + `match` | First-class, exhaustive | Discriminated unions; exhaustiveness manual | | Null safety | Default non-nullable `T`; `T?` for nullable | `strictNullChecks` opt-in; `!` assertion allowed | | Escape hatches | `dynamic` only — explicit, lintable | `any`, `as`, `!` disable checking silently | | Banned complexity | No enums, decorators, intersections, mapped/conditional types | Full type-level programming surface | | JS syntax | ECMAScript superset — same files, same tooling path | Same | | npm interop | `@superjs/types-*` wrappers + `dynamic` | Native `.d.ts` ecosystem | Deeper comparison: [SJS vs TypeScript](../comparisons/sjs-vs-typescript.md). ## What SJS loses vs TypeScript This section is mandatory — SuperJS is a trade, not a free upgrade. - **No `any` escape hatch** — intentional. Untyped boundaries require `dynamic` and runtime checks. - **No decorators** — NestJS, Angular, and similar frameworks that depend on TC39-stage-3 decorators are not targets today. - **No conditional, mapped, or `infer` types** — no type-level string manipulation or deep utility-type gymnastics. - **Smaller typed-wrapper ecosystem** — 30 wave-1 `@superjs/types-*` packages exist; TypeScript has years of DefinitelyTyped coverage. - **No self-hosting compiler yet** — the production CLI is TypeScript + Babel today; the native LLVM backend is on the roadmap (v2.0). - **Smaller community** — fewer Stack Overflow answers, fewer blog posts, pre-1.0 semver. If you need the full npm type graph on day one, or your architecture depends on decorators and advanced conditional types, TypeScript is the better fit. ## Comparison table: alternatives | Feature | SJS | TypeScript | ReScript | Flow | Elm | |---------|-----|------------|----------|------|-----| | Sum types | ✓ | partial (discriminated union) | ✓ | ✓ | ✓ | | Null safety (non-nullable default) | ✓ | ✗ (`strictNullChecks` opt-in) | ✓ | ✓ | ✓ | | Exhaustive `match` | ✓ | ✗ (manual workaround) | ✓ | ✓ | ✓ | | JS interop | good (`dynamic`) | excellent | complex | good | limited | | Compiles to JS | ✓ | ✓ | ✓ | ✗ (checker only) | ✓ | | npm ecosystem | via `@superjs/types-*` | native | via bindings | native | limited | | Learning curve from TS | low | — | high | medium | high | ### ReScript comparison ReScript is the closest peer: sound types, sum types, pattern matching, JS output. The divergence is audience and syntax. ReScript intentionally moves away from JavaScript — new syntax, new stdlib idioms, a distinct ecosystem. SuperJS keeps JavaScript syntax and targets **TypeScript teams** who want stronger guarantees without learning a new surface language. If you are greenfield and happy in ReScript's world, stay there. If you have `.ts` files, Node services, and npm dependencies today, SJS is the incremental path. ### Why not Elm / PureScript? Elm and PureScript are pure FP languages with excellent correctness stories and limited JavaScript interop. They suit frontend or fully FP codebases willing to accept ecosystem boundaries. SuperJS is for **incremental adoption** in existing JS/TS shops — same syntax, same bundlers, same deployment targets. ### Why not Civet / Imba? Civet and Imba are **syntax extensions** — they compile to JavaScript/TypeScript but inherit TypeScript's type system (or layer on top of it). SuperJS is a **different type system** with different guarantees (banned `any`, exhaustive match, no intersection types). Syntax sugar and a stricter language solve different problems. ## When to use SJS vs TypeScript **Choose SuperJS when:** - You want sum types and exhaustive `match` without `switch` fall-through bugs - You want null safety without remembering to enable `strictNullChecks` - You want errors as values (`Result`) visible in types, not only in docs - You are starting a **new backend** on Node, Workers, or Lambda and can adopt `.sjs` from day one - You are willing to use `@superjs/types-*` wrappers or `dynamic` at npm boundaries **Stick with TypeScript when:** - You maintain a large existing `.ts` codebase and migration cost outweighs gains - You need decorators (NestJS, Angular) or heavy conditional/mapped type utilities - You need immediate, complete DefinitelyTyped coverage for niche packages - You depend on tooling that only understands TypeScript ASTs today ## Learn more - [Language tour](../tour/index.md) — guided lessons (WS-A4a) - [Compatibility matrix](../compat/index.md) — `@superjs/types-*` coverage - [Migration guide](../migration/index.md) — TS → SJS (WS-A4b) - [Mission & principles](../../specs/mission.md) ======================================================================== # Compatibility Matrix Source: https://superjs.org/docs/compat/index # Compatibility Matrix Typed SJS wrappers (`@superjs/types-*`) for popular npm packages. Each row reflects the wrapper's `STATUS.md` in `superjs/libs/types-/`. **Status:** stable (production-ready), beta (partial coverage), wip (in progress). Click column headers on [superjs.org](https://superjs.org/docs/compat/index) to sort the table. | Package | Wrapper | Coverage | Status | Tested version | ESM | CJS | License | |---------|---------|----------|--------|----------------|-----|-----|--------| | @aws-sdk/client-* | `@superjs/types-aws-sdk-core` | 71% | beta | 3.x | ✓ | ✓ | MIT | | @cloudflare/workers-types | `@superjs/types-cloudflare-workers` | 76% | beta | 4.x | ✓ | ✓ | MIT | | @prisma/client | `@superjs/types-prisma` | 72% | beta | 5.x | ✓ | ✓ | MIT | | axios | `@superjs/types-axios` | 80% | beta | 1.x | ✓ | ✓ | MIT | | bullmq | `@superjs/types-bullmq` | 78% | beta | 5.x | ✓ | ✓ | MIT | | connect | `@superjs/types-connect` | 71% | beta | 3.x | ✓ | ✓ | MIT | | dotenv | `@superjs/types-dotenv` | 85% | beta | 16.x | ✓ | ✓ | MIT | | express | `@superjs/types-express` | 78% | beta | 4.x | ✓ | ✓ | MIT | | fastify | `@superjs/types-fastify` | 82% | beta | 4.x | ✓ | ✓ | MIT | | hono | `@superjs/types-hono` | 76% | beta | 4.x | ✓ | ✓ | MIT | | ioredis | `@superjs/types-redis` | 79% | beta | 5.x | ✓ | ✓ | MIT | | jest | `@superjs/types-jest` | 76% | beta | 29.x | ✓ | ✓ | MIT | | joi | `@superjs/types-joi` | 74% | beta | 17.x | ✓ | ✓ | MIT | | jsonwebtoken | `@superjs/types-jsonwebtoken` | 79% | beta | 9.x | ✓ | ✓ | MIT | | koa | `@superjs/types-koa` | 73% | beta | 2.x | ✓ | ✓ | MIT | | mongoose | `@superjs/types-mongoose` | 74% | beta | 8.x | ✓ | ✓ | MIT | | multer | `@superjs/types-multer` | 80% | beta | 1.x | ✓ | ✓ | MIT | | mysql2 | `@superjs/types-mysql2` | 77% | beta | 3.x | ✓ | ✓ | MIT | | next | `@superjs/types-nextjs` | 70% | beta | 14.x | ✓ | ✓ | MIT | | node-fetch | `@superjs/types-node-fetch` | 78% | beta | 3.x | ✓ | ✓ | MIT | | passport | `@superjs/types-passport` | 73% | beta | 0.7.x | ✓ | ✓ | MIT | | pg | `@superjs/types-pg` | 81% | beta | 8.x | ✓ | ✓ | MIT | | pino | `@superjs/types-pino` | 82% | beta | 9.x | ✓ | ✓ | MIT | | react | `@superjs/types-react` | 72% | beta | 18.x | ✓ | ✓ | MIT | | supertest | `@superjs/types-supertest` | 75% | beta | 6.x | ✓ | ✓ | MIT | | undici | `@superjs/types-undici` | 75% | beta | 6.x | ✓ | ✓ | MIT | | vite | `@superjs/types-vite` | 74% | beta | 5.x | ✓ | ✓ | MIT | | vitest | `@superjs/types-vitest` | 78% | beta | 1.x | ✓ | ✓ | MIT | | winston | `@superjs/types-winston` | 77% | beta | 3.x | ✓ | ✓ | MIT | | zod | `@superjs/types-zod` | 76% | beta | 3.x | ✓ | ✓ | MIT | > Wave 1 targets backend-first packages. See [WS-B3 spec](../../specs/roadmap/v1.0/WS-B3-types-wrappers.md) for the full list. ======================================================================== # Performance Benchmarks Source: https://superjs.org/docs/perf/index # Performance Benchmarks SuperJS compiler throughput on real hardware. Numbers below are **measured**, not estimated. **Environment:** macOS arm64, Node v24.14.1, 10 CPU cores — 2026-06-24. Reproduce locally: ```bash cd superjs && bunx nx build compiler && bunx nx build lsp cd .. && node scripts/gen-bench-corpus.mjs node scripts/bench.mjs # compile metrics → benchmarks/results.json node --expose-gc scripts/bench-lsp.mjs # LSP metrics (merge into results.json) ``` ## Stage 6 targets | Metric | Target | Measured (corpus) | Status | |--------|--------|-------------------|--------| | Cold compile (~10k LOC) | ≤ 2s | **81.5 ms** | ✓ | | Warm rebuild (avg 5×) | ≤ 100 ms | **56.5 ms** | ✓ | | LSP idle memory (100k LOC) | ≤ 250 MB | **195.8 MB** | ✓ | | LSP P99 hover | ≤ 200 ms | **13.2 ms** | ✓ | ## Compile time Synthetic corpus: **~14k LOC** of functions, sum types, and `match` arms (`benchmarks/corpus-10k.sjs`). Stdlib corpus: all `superjs/libs/stdlib/src/modules/*.sjs` (**382 LOC**, 11 files). | Input | LOC | Cold compile | Warm (avg 5×) | |-------|-----|--------------|---------------| | stdlib | 382 | 19.9 ms | 3.7 ms | | synthetic corpus | 14,000 | 81.5 ms | 56.5 ms | ### vs TypeScript tooling (same synthetic TS corpus) Comparisons use an equivalent **TypeScript** file (`benchmarks/corpus-10k.ts`) with the same function count and control flow (discriminated unions instead of SJS `match`). This is not apples-to-apples: SuperJS runs parse + typecheck + IR + codegen; `tsc` is typecheck-only; `esbuild` is transpile-only. | Tool | Wall time (cold) | Relative to SJS cold | |------|------------------|----------------------| | **SuperJS** `compile()` | 81.5 ms | 1.00× | | **tsc** `--noEmit` | 720.7 ms | 8.8× slower than SJS | | **esbuild** transpile | 431.0 ms | 5.3× slower than SJS | SJS cold compile is **~0.11×** the wall time of `tsc` and **~0.19×** `esbuild` on this corpus. Your mileage will vary with module graph size, import resolution, and source maps. ## LSP Single-file workspace: **~140k LOC** synthetic corpus (`genSjsCorpus(100_000)`), one `textDocument/didOpen`, memory budget 512 MB. | Metric | Value | Target | Status | |--------|-------|--------|--------| | Open + index (cold) | 735.5 ms | — | — | | Idle heap after open | **195.8 MB** | ≤ 250 MB | ✓ | | P50 hover latency | 5.6 ms | — | — | | P99 hover latency | **13.2 ms** | ≤ 200 ms | ✓ | Harness: [`scripts/bench-lsp.mjs`](../../scripts/bench-lsp.mjs) (200 hover samples at a fixed identifier position). Run with `--expose-gc` for stable heap readings. See also: [LSP Memory Audit](./lsp-memory-audit.md) — `didClose` release and LRU eviction checks. ## Methodology - **Cold:** first `compile()` call after process start, no persistent cache. - **Warm:** average of 5 subsequent `compile()` calls on identical sources (in-memory incremental session). - **Harness:** [`scripts/bench.mjs`](../../scripts/bench.mjs) (compile), [`scripts/bench-lsp.mjs`](../../scripts/bench-lsp.mjs) (LSP) - **Corpus generator:** [`scripts/gen-bench-corpus.mjs`](../../scripts/gen-bench-corpus.mjs) - **Committed results:** [`benchmarks/results.json`](../../benchmarks/results.json) Per-phase lexer/parser/checker/codegen breakdown is not yet instrumented in the compiler pipeline. _Last updated: 2026-06-24._ ======================================================================== # LSP Memory Audit Source: https://superjs.org/docs/perf/lsp-memory-audit # LSP Memory Audit Stage 6 / v1.0 RC readiness review of LSP heap behaviour. Complements the latency + idle-heap benchmarks in [Performance Benchmarks](./index.md). **Audit date:** 2026-06-24 **Harness:** [`scripts/audit-lsp-memory.mjs`](../../scripts/audit-lsp-memory.mjs) **Committed report:** [`benchmarks/lsp-memory-audit.json`](../../benchmarks/lsp-memory-audit.json) Reproduce: ```bash cd superjs && bunx nx build lsp cd .. && node --expose-gc scripts/audit-lsp-memory.mjs # optional heap snapshot for Chrome DevTools: node --expose-gc scripts/audit-lsp-memory.mjs --snapshot ``` ## Summary | Check | Result | Target | |-------|--------|--------| | Idle heap (~100k LOC corpus, single file) | **195.8 MB** | ≤ 250 MB | | `didClose` releases analysis state | **186.8 MB** freed (ratio 1.0) | ≥ 35% release | | LRU evicts oldest file under 2 MiB budget | oldest file empty on query | — | | Multi-file plateau (5 × ~20k LOC) | **45 MB** growth | ≤ 120 MB | **Verdict:** No unbounded retention found. Per-file state is released on close and LRU eviction; dominant cost is the in-memory AST + type environment for the active file(s). ## What is retained per open document 1. **Source text** — `LspServer.sources` and `Compiler.rawSources` share the same string reference from `textDocument/didOpen` (not triple-allocated). 2. **`FileState`** (compiler) — parsed AST, diagnostics, typed spans, lowered IR snapshot, and module export surface. This is the bulk of heap usage (~190 MB for ~140k LOC synthetic corpus vs ~5.3 MB source text). 3. **Touch metadata** — URI → LRU tick map (negligible). Eviction path (`enforceBudget` or `didClose`) calls `compiler.removeFile`, which drops `files` and `rawSources` entries for that URI. ## Known limitations (post-1.0) | Item | Status | |------|--------| | `lsp.memoryBudgetMB` estimates bytes from source length only | Does not account for AST/type overhead; a single huge file can exceed true heap budget while under byte estimate | | No `SJS-W010` client diagnostic on heap pressure | Spec'd; not yet emitted (README notes as later work) | | JSON-RPC 8 MiB message cap | Planned (threat model T3) | | Per-phase memory breakdown | Not instrumented in compiler pipeline | ## Relation to CI - **Bench gate:** `scripts/check-bench-results.mjs` enforces idle heap + P99 hover from `benchmarks/results.json`. - **Audit gate:** `scripts/check-lsp-memory-audit.mjs` enforces close + LRU checks from `benchmarks/lsp-memory-audit.json`. _Last updated: 2026-06-24._ ======================================================================== # Error Codes Source: https://superjs.org/docs/error-codes/index # Error Codes All SuperJS diagnostic codes. Codes are permanent — never renumbered or reused. - **SJS-E** — type errors - **SJS-P** — parser errors - **SJS-W** — warnings (promoted to errors in `strict` mode) - **SJS-L** — lint rules See the full list at [/errors](/errors). ======================================================================== # SJS-E001 — Null or undefined assigned to non-nullable type Source: https://superjs.org/docs/error-codes/SJS-E001 **Severity:** error **Category:** null-safety **Stage:** Stage 0 (prototype) ## Description All types in SJS are non-nullable by default. Assigning `null` or `undefined` to a type `T` without opting into nullability (the `T?` suffix) is a type error. ## Example ```sjs // ✗ error let name: string = null // SJS-E001 function greet(user: string) { return `Hello, ${user}` } greet(undefined) // SJS-E001 ``` ## Fix Append `?` to make the type nullable, then narrow before use: ```sjs // ✓ correct let name: string? = null function greet(user: string?): string { if (user === null) return "Hello, stranger" return `Hello, ${user}` } ``` Or, if the value must always be present, ensure the assignment can never be `null`. ## Related codes - `SJS-E003` — property access on possibly-null value - `SJS-E011` — non-null assertion (`!`) is banned ======================================================================== # SJS-E002 — Type mismatch Source: https://superjs.org/docs/error-codes/SJS-E002 **Severity:** error **Category:** type-check **Stage:** Stage 0 (prototype) ## Description The inferred or declared type of an expression does not match the expected type at an assignment, return statement, function call, or similar site. ## Example ```sjs // ✗ error — string assigned where number expected let count: number = "five" // SJS-E002 function double(n: number): number { return "two times " + n // SJS-E002: string, not number } ``` ## Fix Correct the expression to produce the expected type, or update the declared type if the intent changed: ```sjs // ✓ correct let count: number = 5 function double(n: number): number { return n * 2 } ``` ## Related codes - `SJS-E001` — null/undefined assigned to non-nullable type - `SJS-W001` — implicit `dynamic` (unannotated parameter in strict mode) ======================================================================== # SJS-E003 — Property access on possibly-null value Source: https://superjs.org/docs/error-codes/SJS-E003 **Severity:** error **Category:** null-safety **Stage:** Stage 0 (prototype) ## Description Accessing a property or calling a method on a value of type `T?` without first narrowing away the null case is a type error. SJS does not support optional chaining as a suppressor — narrow explicitly. ## Example ```sjs // ✗ error function getLength(s: string?): number { return s.length // SJS-E003: s might be null } ``` ## Fix Narrow with a null check first: ```sjs // ✓ correct function getLength(s: string?): number { if (s === null) return 0 return s.length // s: string here } ``` Or use optional chaining only as an expression (not as a suppressor of the error — narrow the result): ```sjs const len: number = s?.length ?? 0 // ✓ ``` ## Related codes - `SJS-E001` — null assigned to non-nullable type - `SJS-E011` — non-null assertion (`!`) is banned ======================================================================== # SJS-E004 — `any` is not a valid type in SJS Source: https://superjs.org/docs/error-codes/SJS-E004 **Severity:** error **Category:** type-check **Stage:** Stage 1 ## Description The TypeScript `any` type does not exist in SJS. It is a silent unsafety escape hatch that defeats the entire type system. SJS provides two typed alternatives: - `unknown` — the value has an unknown type; must be narrowed before use. - `dynamic` — opts a value out of type checking entirely, for JS interop only. ## Example ```sjs // ✗ error function process(data: any) { // SJS-E004 return data.value } ``` ## Fix Use `unknown` when the type is genuinely not known but should be narrowed: ```sjs // ✓ correct — unknown, narrow before use function process(data: unknown): string { if (typeof data === "object" && data !== null && "value" in data) { return String((data as { value: unknown }).value) } return "" } ``` Use `dynamic` only at JS interop boundaries: ```sjs // ✓ correct — dynamic for third-party lib interop const raw: dynamic = legacyLib.getData() if (typeof raw === "string") { console.log(raw.toUpperCase()) } ``` ## Related codes - `SJS-W001` — implicit `dynamic` propagation - `SJS-L001` — `dynamic` used without narrowing ======================================================================== # SJS-E005 — Intersection type `A & B` is not allowed Source: https://superjs.org/docs/error-codes/SJS-E005 **Severity:** error **Category:** type-check **Stage:** Stage 1 ## Description TypeScript intersection types (`A & B`) are not valid SJS syntax. They allow types to be merged silently, producing shapes that are difficult to reason about and can be unsound. Use object-type extension (`extends`) to compose named shapes explicitly. ## Example ```sjs // ✗ error type AdminUser = User & { role: string } // SJS-E005 ``` ## Fix ```sjs // ✓ correct — explicit extension type AdminUser extends User { role: string } ``` If the two object types are from separate sources and you cannot extend them, define a new object type that includes both sets of properties explicitly. ## Related codes - `SJS-E008` — conditional types are also banned - `SJS-E006` — mapped types are also banned ======================================================================== # SJS-E006 — Mapped type is not allowed in SJS Source: https://superjs.org/docs/error-codes/SJS-E006 **Severity:** error **Category:** type-check **Stage:** Stage 1 ## Description TypeScript mapped types (`{ [P in keyof T]: ... }`) are not valid SJS syntax. They generate types implicitly from other types through metaprogramming, making shapes harder to audit and the type system harder to reason about statically. Spell out the shape you need explicitly. ## Example ```sjs // ✗ error type Optional = { [P in keyof T]?: T[P] // SJS-E006 } ``` ## Fix Declare the explicit object type: ```sjs // ✓ correct — explicit optional shape type OptionalUser { id?: number name?: string email?: string } ``` For library code that genuinely needs parameterised shapes, restructure as explicit generic object types with optional members. ## Related codes - `SJS-E005` — intersection types are also banned - `SJS-E008` — conditional types are also banned ======================================================================== # SJS-E007 — Non-exhaustive match expression Source: https://superjs.org/docs/error-codes/SJS-E007 **Severity:** error **Category:** match **Stage:** Stage 0 (prototype) ## Description A `match` expression over a sum type must have an arm for every variant. If one or more variants are missing and there is no catch-all `_` arm, SJS emits SJS-E007. Exhaustiveness is enforced at compile time so that adding a new variant to a sum type immediately flags every `match` that does not handle it. ## Example ```sjs type Shape = Circle(radius: number) | Rect(w: number, h: number) | Point // ✗ error — Point arm missing function area(s: Shape): number { return match s { Circle(r) => Math.PI * r * r, Rect(w, h) => w * h, // SJS-E007: missing arm for Point } } ``` ## Fix Add the missing arm, or use `_` as a catch-all: ```sjs // ✓ correct — all variants handled function area(s: Shape): number { return match s { Circle(r) => Math.PI * r * r, Rect(w, h) => w * h, Point => 0, } } // ✓ correct — catch-all arm function describe(s: Shape): string { return match s { Circle(r) => `circle r=${r}`, _ => "other shape", } } ``` ## Related codes - `SJS-W003` — unreachable match arm (inverse problem: arm that can never be reached) ======================================================================== # SJS-E008 — Conditional type is not allowed in SJS Source: https://superjs.org/docs/error-codes/SJS-E008 **Severity:** error **Category:** type-check **Stage:** Stage 1 ## Description TypeScript conditional types (`T extends U ? A : B`) are not valid SJS syntax. They add metaprogramming complexity without soundness guarantees. If the goal is to select a type based on a condition, use explicit overloads or separate typed functions instead. ## Example ```sjs // ✗ error type Flatten = T extends Array ? U : T // SJS-E008 ``` ## Fix Write explicit typed functions or use `unknown` + narrowing: ```sjs // ✓ correct — explicit function per case function flattenNumber(arr: number[]): number { return arr[0] } function flattenString(arr: string[]): string { return arr[0] } ``` For library utility types, restructure as separate explicitly-typed interfaces or generic functions with explicit constraints. ## Related codes - `SJS-E005` — intersection types also banned - `SJS-E006` — mapped types also banned - `SJS-E009` — `infer` keyword also banned ======================================================================== # SJS-E009 — `infer` keyword is not allowed in SJS Source: https://superjs.org/docs/error-codes/SJS-E009 **Severity:** error **Category:** type-check **Stage:** Stage 1 ## Description The TypeScript `infer` keyword is part of conditional types and is not valid SJS syntax. ## Example ```sjs // ✗ error type ReturnType = T extends (...args: any[]) => infer R ? R : never // SJS-E009 ``` ## Fix Annotate return types explicitly rather than inferring them from function signatures at the type level: ```sjs // ✓ correct — annotate explicitly function fetchUser(id: number): Promise { ... } // consumer: use the known return type directly const result: Promise = fetchUser(1) ``` ## Related codes - `SJS-E008` — conditional types are also banned ======================================================================== # SJS-E010 — TypeScript `enum` is not allowed in SJS Source: https://superjs.org/docs/error-codes/SJS-E010 **Severity:** error **Category:** type-check **Stage:** Stage 1 ## Description TypeScript `enum` declarations are not valid SJS syntax. Enums generate runtime objects with non-obvious semantics (reverse mappings, numeric/string duality) and are not part of ECMAScript. Use sum types with unit variants for discriminated constant sets. ## Example ```sjs // ✗ error enum Direction { // SJS-E010 Up, Down, Left, Right, } ``` ## Fix ```sjs // ✓ correct — sum type with unit variants type Direction = Up | Down | Left | Right function move(d: Direction): string { return match d { Up => "up", Down => "down", Left => "left", Right => "right", } } ``` Or use a string union for simple string constants: ```sjs type Direction = "up" | "down" | "left" | "right" ``` ## Related codes - `SJS-E007` — non-exhaustive match ======================================================================== # SJS-E011 — Non-null assertion `!` is not allowed Source: https://superjs.org/docs/error-codes/SJS-E011 **Severity:** error **Category:** null-safety **Stage:** Stage 1 ## Description The TypeScript non-null assertion operator (`expr!`) is not valid SJS syntax. It silently strips `null | undefined` from a type at the call site without any runtime check, defeating null safety. Narrow explicitly with a conditional or early return. ## Example ```sjs // ✗ error const el = document.getElementById("app")! // SJS-E011 el.innerHTML = "hello" ``` ## Fix ```sjs // ✓ correct — narrow explicitly const el = document.getElementById("app") if (el === null) throw new Error("Required element #app not found") el.innerHTML = "hello" // or with early return function init(): void { const el = document.getElementById("app") if (el === null) return el.innerHTML = "hello" } ``` ## Related codes - `SJS-E001` — null assigned to non-nullable type - `SJS-E003` — property access on possibly-null value ======================================================================== # SJS-E012 — `namespace` is not allowed in SJS Source: https://superjs.org/docs/error-codes/SJS-E012 **Severity:** error **Category:** type-check **Stage:** Stage 1 ## Description TypeScript `namespace` declarations are not valid SJS syntax. Namespaces are a TypeScript-specific module system predating ES modules and have no ECMAScript equivalent. Use ES module `import`/`export` syntax. ## Example ```sjs // ✗ error namespace Utils { // SJS-E012 export function format(s: string): string { return s.trim() } } ``` ## Fix ```sjs // ✓ correct — ES module // utils.sjs export function format(s: string): string { return s.trim() } // consumer.sjs import { format } from "./utils.sjs" ``` ## Related codes - `SJS-E010` — `enum` is also banned (use sum types) ======================================================================== # SJS-E013 — `with` statement not allowed (SJS is always strict mode) Source: https://superjs.org/docs/error-codes/SJS-E013 **Severity:** error **Category:** control-flow **Stage:** Stage 1 ## Description SJS always runs in strict mode. The `with` statement is unconditionally banned by the ECMAScript strict mode specification (ECMA-262 §13.11). `with` alters the scope chain at runtime in ways that cannot be statically analysed, making type-checking and tooling impossible. The parser rejects any `with` statement before type-checking begins. ## Example ```sjs // ✗ error const obj = { x: 1, y: 2 } with (obj) { // SJS-E013 console.log(x + y) } ``` ## Fix Access properties explicitly: ```sjs // ✓ correct const obj = { x: 1, y: 2 } console.log(obj.x + obj.y) ``` Or destructure when many members are needed in a local block: ```sjs // ✓ correct — destructure const { x, y } = obj console.log(x + y) ``` ## Related codes - `SJS-E012` — `namespace` is also banned for similar static-analysis reasons ======================================================================== # SJS-E014 — Private or protected member not accessible from this scope Source: https://superjs.org/docs/error-codes/SJS-E014 **Severity:** error **Category:** access-modifiers **Stage:** Stage 1 ## Description SJS enforces access modifiers at compile time: - `private` members are accessible only within the class that declares them. - `protected` members are accessible within the declaring class and its subclasses. Accessing a `private` member from outside the class, or a `protected` member from a non-subclass context, is a type error. ## Example ```sjs // ✗ error class Account { private balance: number = 0 protected internalId: string = "acc-1" } const a = new Account() console.log(a.balance) // SJS-E014 — private console.log(a.internalId) // SJS-E014 — protected, not in a subclass ``` ## Fix Expose state through a public accessor or method: ```sjs // ✓ correct class Account { private balance: number = 0 protected internalId: string = "acc-1" getBalance(): number { return this.balance } } const a = new Account() console.log(a.getBalance()) // ✓ public method ``` For `protected` members, access them from a subclass: ```sjs // ✓ correct — subclass access class SavingsAccount extends Account { describe(): string { return `ID: ${this.internalId}` // ✓ allowed in subclass } } ``` ## Related codes - `SJS-E015` — cannot narrow an access modifier on an overriding method ======================================================================== # SJS-E015 — Cannot narrow an access modifier on an overriding method or property Source: https://superjs.org/docs/error-codes/SJS-E015 **Severity:** error **Category:** access-modifiers **Stage:** Stage 1 ## Description When a subclass overrides a method or property, it must not make the member less accessible than the base class declaration. Narrowing visibility would violate the Liskov Substitution Principle: code that holds a reference typed as the base class could no longer rely on the member being accessible. Permitted direction: a subclass may widen access (e.g., `protected` → `public`). Forbidden direction: a subclass may **not** narrow access (e.g., `public` → `protected`, `public` → `private`, or `protected` → `private`). ## Example ```sjs // ✗ error class Shape { public area(): number { return 0 } } class Circle extends Shape { private area(): number { return 3.14 } // SJS-E015 — narrowed from public to private } ``` ```sjs // ✗ error class Base { protected describe(): string { return "base" } } class Child extends Base { private describe(): string { return "child" } // SJS-E015 — narrowed from protected to private } ``` ## Fix Keep the same (or wider) access modifier on the overriding member: ```sjs // ✓ correct — same visibility class Shape { public area(): number { return 0 } } class Circle extends Shape { public area(): number { return 3.14 } } ``` ```sjs // ✓ correct — widened from protected to public class Base { protected describe(): string { return "base" } } class Child extends Base { public describe(): string { return "child" } } ``` ## Related codes - `SJS-E014` — private or protected member not accessible from this scope - `SJS-E016` — cannot instantiate an abstract class directly ======================================================================== # SJS-E016 — Cannot instantiate an abstract class directly with `new` Source: https://superjs.org/docs/error-codes/SJS-E016 **Severity:** error **Category:** classes **Stage:** Stage 1 ## Description An `abstract` class is an incomplete type intended only as a base for subclasses. It may declare abstract methods that have no implementation. Calling `new` on an abstract class directly is a type error because the resulting object would have unimplemented methods, making it unsound. Abstract classes must be subclassed, and all abstract members must be implemented by the concrete subclass before an instance can be created. ## Example ```sjs // ✗ error abstract class Animal { abstract speak(): string } const a = new Animal() // SJS-E016 ``` ## Fix Extend the abstract class with a concrete subclass that implements every abstract member, then instantiate the subclass: ```sjs // ✓ correct abstract class Animal { abstract speak(): string describe(): string { return `I say: ${this.speak()}` } } class Dog extends Animal { speak(): string { return "woof" } } const d = new Dog() // ✓ console.log(d.describe()) // "I say: woof" ``` ## Related codes - `SJS-E015` — cannot narrow an access modifier on an overriding method ======================================================================== # SJS-E017 — Circular import detected — module graph contains a cycle Source: https://superjs.org/docs/error-codes/SJS-E017 **Severity:** error **Category:** modules **Stage:** Stage 1 ## Description SJS performs a static analysis of the module dependency graph during compilation. If two or more modules form an import cycle (A imports B and B imports A, directly or transitively), the compiler raises this error. Circular imports are banned because: 1. They cause unpredictable initialisation order — some module's exports may be `undefined` at the point they are consumed. 2. They indicate that responsibilities are not properly separated between modules. The error is reported on the `import` statement that closes the cycle. ## Example ```sjs // ✗ error — a.sjs import { bar } from "./b.sjs" // SJS-E017 — closes the cycle export function foo(): string { return bar() } ``` ```sjs // ✗ error — b.sjs import { foo } from "./a.sjs" export function bar(): string { return foo() } ``` ## Fix Break the cycle by extracting shared logic into a third module that neither of the original modules imports back: ```sjs // ✓ correct — shared.sjs (no imports from a or b) export function shared(): string { return "hello" } ``` ```sjs // ✓ correct — a.sjs import { shared } from "./shared.sjs" export function foo(): string { return shared() } ``` ```sjs // ✓ correct — b.sjs import { shared } from "./shared.sjs" export function bar(): string { return shared() } ``` ## Related codes - `SJS-E012` — `namespace` is banned; use ES module imports instead - `SJS-E018` — top-level `await` used outside an ES module context ======================================================================== # SJS-E018 — Top-level `await` used outside an ES module context Source: https://superjs.org/docs/error-codes/SJS-E018 **Severity:** error **Category:** async-await **Stage:** Stage 1 ## Description Top-level `await` (ECMAScript 2022, ECMA-262 §15.2) is only valid in ES modules — files loaded with `type="module"` in browsers, or treated as ESM by a Node.js-compatible runtime. SJS detects whether the current file is an ES module by the presence of at least one `import` or `export` declaration at the top level. A file without any such declaration is treated as a CommonJS-style script, and top-level `await` inside it is a type error. ## Example ```sjs // ✗ error — no import/export, so not an ES module const data = await fetch("/api/data") // SJS-E018 console.log(data) ``` ## Fix Option 1 — Make the file an ES module by adding at least one `export`: ```sjs // ✓ correct — ES module (has export) export {} const data = await fetch("/api/data") console.log(data) ``` Option 2 — Wrap the top-level logic in an async IIFE if you intentionally cannot use ESM: ```sjs // ✓ correct — async IIFE, no top-level await ;(async () => { const data = await fetch("/api/data") console.log(data) })() ``` ## Related codes - `SJS-E017` — circular import detected in module graph ======================================================================== # SJS-E019 — Unknown JSX element type — identifier not in scope or not a valid component Source: https://superjs.org/docs/error-codes/SJS-E019 **Severity:** error **Category:** jsx **Stage:** Stage 2 ## Description When SJS processes a JSX expression such as ``, it resolves `Foo` as a regular identifier in the current scope. If `Foo` is not imported, not declared in the current file, or does not have a type compatible with a JSX component (a function returning JSX, or a class with a `render` method returning JSX), the compiler raises this error. Lowercase JSX tags (e.g. `
`) are treated as intrinsic HTML elements and are always valid. Capitalised tags must refer to a component that is in scope. ## Example ```sjs // ✗ error — Foo is never imported or declared function App(): JSX.Element { return // SJS-E019 } ``` ```sjs // ✗ error — Button is used before it is imported function Page(): JSX.Element { return