Features Syntax Tooling Interop Roadmap Docs ← Aquarium Get Started →
v0.1.0 — now on npm · open source

Write less.
Ship more.

AquaScript is a friendlier superset of TypeScript that compiles to clean JavaScript. Less ceremony, full type safety, and seamless compatibility with every JS and TS package you already use.

app.aq
1import express from "express"
2
3struct User { id: Int, name: Str, role: Maybe<Str> }
4
5type Status = "active" | "inactive" | "pending"
6type EventName = `user:${Status}` // template literal type
7
8fun getRole(user: User) =>
9  match user.role {
10    "admin" => "Administrator"
11    is Str => user.role
12    _ => "Member"
13  }
14
15async fun loadUsers(ids: List<Int>) {
16  val users = [await fetchUser(id) for id in ids]
17  return users
18    |> filter(_, u => u.role != null)
19    |> map(_, getRole)
20}
40+
language features
100%
JS/TS package compat
0
runtime overhead
85%+
test coverage

Everything you love about TS,
without the friction.

AquaScript keeps the safety and expressiveness of TypeScript while adding the syntax that makes daily development genuinely enjoyable.

Language
Cleaner Declarations
val and var replace const and let. Access modifiers pub, priv, prot. Constructors via init.
val / var / init
🎯
Pattern Matching
First-class match with literal, OR, object, binding, and is type-guard patterns. Exhaustiveness checked by the linter.
match / is
🔗
Pipe Operator
Chain transformations left-to-right with |>. Use _ as a placement placeholder. Compiles to nested calls.
|> pipe
📋
Comprehensions
List and object comprehensions — map, filter, and cartesian products in one readable line. Compiles to .map().filter().
[x for x in xs if ...]
🛡️
Guard Statement
Unwrap nullable values and exit early with guard. Narrows the binding to non-null for the rest of the function.
guard val x = ...
Result<T>
Built-in Ok<T> and Err<E> for explicit error handling without exceptions. Pairs naturally with match.
Result<T>
🎪
Named Arguments
Call functions with named args in any order. The compiler reorders them at compile time — zero runtime cost, maximum clarity.
f(name: "Alice")
🔧
Decorators
Full TypeScript-compatible decorator support on classes, methods, properties, and parameters. Works with DI frameworks and ORMs.
@Injectable @Log
⚙️
Async Generators
Native async fun* and yield support for streaming data, paginated APIs, and event sequences with for await.
async fun* / yield
Type System
🏗️
Friendly Type Aliases
Str, Int, Bool, List<T>, Maybe<T>, Dict<K,V>. Use AquaScript aliases or native TS types — both always valid.
Str, Int, Maybe<T>
📐
Tuple Types
Fixed-length typed arrays: [Int, Str, Bool]. Named elements, optional and rest positions, full generic inference. Fully erased at compile time.
[A, B, C]
🔀
Unions & Intersections
Union types A | B, intersection types A & B, discriminated unions, and the Either<A,B> alias.
A | B / A & B
🧩
Template Literal Types
Construct string literal union types from combinations: `task:${Status}` expands to all variants. Includes Uppercase, Lowercase, Capitalize.
`prefix:${Union}`
🔬
Conditional Types
Type-level if-else with T extends U ? A : B. Full infer support, distributive evaluation, and built-in utilities like ReturnType<T>.
T extends U ? A : B
🔒
satisfies Operator
Validate a value against a type without widening its inferred type. Keep full specificity while checking conformance — like as but safe.
expr satisfies Type

Familiar. Expressive.
Delightful to write.

AquaScript feels like TypeScript learned from the best parts of modern languages. Same safety, cleaner surface.

match.aq
// Literal + OR patterns
val msg = match code {
  200 => "OK"
  301 | 302 => "Redirect"
  404 => "Not Found"
  _ => `Unknown: ${code}`
}

// is type-guard pattern
fun describe(v: Any) => match v {
  is Str => `string: "${v}"`
  is Int => `number: ${v}`
  is Bool => `bool: ${v}`
  _ => "unknown"
}
pipe.aq
// Reads top-to-bottom, not inside-out
val result = rawData
  |> normalize
  |> filter(_, isValid)
  |> transform
  |> JSON.stringify

// _ controls insertion position
val total = users
  |> filter(_, u => u.active)
  |> map(_, u => u.score)
  |> reduce(_, (a, n) => a + n, 0)
lists.aq
val doubled = [x * 2 for x in nums]
val evens = [x for x in nums if x % 2 == 0]

// Object comprehension
val byId = { u.id: u for u in users }

// Nested (cartesian product)
val pairs = [[a, b] for a in xs for b in ys]

// All compile to .map().filter()
guard.aq
async fun processOrder(id: Int) {
  guard val order = await fetchOrder(id) else {
    throw Error("Not found")
  }
  guard val user = await fetchUser(order.userId) else {
    return null
  }
  // order + user are non-null here
  return fulfil(order, user)
}
result.aq
fun divide(a: Num, b: Num): Result<Num> {
  if (b == 0) return Err("Division by zero")
  return Ok(a / b)
}

match divide(10, 2) {
  { ok: true, value: val v } => console.log(v)
  { ok: false, error: val e } => console.error(e)
}
service.aq
// Class + method decorators
@Injectable({ scope: "singleton" })
class TaskService {
  init(priv db: Database) {}

  @Log("info")
  @Validate
  async fun createTask(data: CreateTaskDto) {
    return this.db.insert(data)
  }

  @Memoize({ ttl: 60 })
  async fun getStats(): Promise<Stats> {
    return this.db.aggregate()
  }
}
stream.aq
// Async generator — streams paginated data
async fun* fetchPages<T>(url: Str) {
  var cursor: Maybe<Str> = null
  do {
    val page = await get(url, cursor)
    yield page.items
    cursor = page.nextCursor
  } while (cursor != null)
}

// Consuming the stream
for await (val batch of fetchPages<User>("/users")) {
  batch.forEach(u => console.log(u.name))
}
types.aq
// Template literal types
type Status = "todo" | "done" | "cancelled"
type EventName = `task:${Status}`

// Conditional types + infer
type Unwrap<T> = T extends Promise<infer U> ? U : T

// Tuple types
type Point = [x: Int, y: Int]
fun swap<A,B>(p: [A,B]): [B,A] => [p[1], p[0]]

// satisfies — validate without widening
val cfg = { port: 3000, host: "localhost" }
  satisfies Partial<ServerConfig>
// cfg.port is still Int, not widened

Same program. Half the noise.

AquaScript compiles to identical JavaScript. The only difference is what you write.

TypeScriptbefore
// 22 lines
interface Product {
  id: number;
  name: string;
  price: number;
  category: string | undefined;
}
function getLabel(p: Product): string {
  if (p.category === "electronics") {
    return `[Tech] ${p.name}`
  } else if (p.category === "books") {
    return `[Book] ${p.name}`
  } else if (p.category === "clothing") {
    return `[Fashion] ${p.name}`
  } else {
    return p.name
  }
}
AquaScriptafter
// 9 lines
struct Product {
  id: Int, name: Str
  price: Float, category: Maybe<Str>
}
fun getLabel(p: Product) =>
  match p.category {
    "electronics" => `[Tech] ${p.name}`
    "books" => `[Book] ${p.name}`
    "clothing" => `[Fashion] ${p.name}`
    _ => p.name
  }

A complete development
experience, out of the box.

AquaScript ships with a formatter, linter, REPL, and CLI. No config required to get started.

aqc build
Compiler
Compiles .aq files to clean, readable JavaScript with source maps. Single file or full directory.
aqc fmt
Formatter
Opinionated, non-configurable formatter (like gofmt). Idempotent. --check mode for CI. No style debates.
aqc lint
Linter
9 built-in rules covering null safety, exhaustiveness, style, and correctness. --fix auto-fixes what it can.
aqc repl
REPL
Interactive session with persistent state, tab completion, dot-commands, and history saved to ~/.aquascript_history.
aqc run
Runner
Compile and execute in one step — no output files. Ideal for scripts, CI tasks, and quick iteration.
aqc watch
Watcher
Incremental recompilation on every save. Fast feedback loop for development — only recompiles changed files.
aqc check
Type Checker
Type-check only — no output files generated. Perfect for CI pipelines where you want errors without emitting.
aqc init
Scaffolder
Sets up a new project with aqua.config.json, tsconfig.json, and a starter src/index.aq.

Your entire ecosystem,
on day one.

AquaScript doesn't ask you to abandon your tools. Every npm package, TypeScript library, and JavaScript file works without any changes.

📦
Full npm compatibility
Import any package from npm exactly as you would in TypeScript. Module resolution follows Node.js semantics — no wrappers, no bridges.
🔷
TypeScript type acquisition
AquaScript reads .d.ts files and @types/* packages automatically, bringing full type safety to all your TS dependencies.
🔀
CJS + ESM bridged transparently
CommonJS and ESM modules are bridged automatically. Use require() packages with import syntax — the compiler handles it.
📝
Raw JS escape hatch
Need to drop to raw JavaScript? Wrap it in js { ... } and it passes through unchanged.
interop.aq
1// JS packages — no config needed
2import express from "express"
3import _ from "lodash"
4import axios from "axios"
5
6// TS packages — types included
7import { useState } from "react"
8import type { FC } from "react"
9import { PrismaClient } from "@prisma/client"
10
11// Your existing .ts files
12import { db } from "./database.ts"
13import { auth } from "./auth.js"
14
15// Raw JS when you need it
16js {
17  const legacy = require('./old-module')
18}

Built in the open.
Shipping fast.

The compiler is feature-complete. Here's what's shipped and what's coming next.

Shipped in v0.1.0
Core compiler — lexer, parser, type checker, codegen
Pattern matching — literal, OR, object, binding, is type guards
Pipe operator |> with _ placeholder
List & object comprehensions
Guard statement with null narrowing
Result<T>, Ok, Err — explicit error handling
Decorators — class, method, property, parameter
Async generators — async fun* and yield
Tuple types, intersection types, computed keys
Template literal types + string utility types
Conditional types with infer and distribution
satisfies operator
Full JS/TS interop — npm, .d.ts, CJS/ESM bridge
Formatter, linter (9 rules + auto-fix), REPL
Coming next
VS Code extension — syntax highlighting, error squiggles, hover typessoon
LSP server — autocomplete, go-to-definition, find referencessoon
In-browser playground — edit and compile live on the docs sitesoon
Standard library expansion — collections, async utils, validationlater
Project templates — Node API, React app, CLI toollater
Performance pass — incremental compilation cachelater
Community packages — aquascript.dev registrylater

Start writing AquaScript
today.

Feature-complete compiler, formatter, linter, and REPL.
Install in one command and bring your whole ecosystem with you.

$ npm install -g aquascript click to copy