npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@david0dev/ntl-lang

v3.5.0

Published

NTL — A programming language for teams that ship. Fast, typed, Node.js compatible.

Readme

License

Apache License © 2026 David Dev

GitHub: github.com/Megamexlevi2/ntl-lang

NTL — The Language That Ends the Conversation

One language. Backend, frontend, database, real-time systems, and a full GPU-accelerated 3D game engine.
Everything compiles to blazing-fast JavaScript. Zero dependencies. Zero compromises.

Node.js ≥18 Zero npm dependencies Tests: 74/74 License

Created by David Dev — github.com/Megamexlevi2/ntl-lang


Why NTL?

Most languages make you choose: server or client, compiled or interpreted, safe or fast. NTL refuses every tradeoff. It compiles to clean, optimized JavaScript, runs on Node.js, and ships with a standard library so complete that you'll never open npm install again.

NTL's syntax is deliberately expressive: pattern-matching guards (have), optional pipelines, macro expansion, decorators, traits, abstract classes, generators, and a structural type system all coexist without ceremony. This makes NTL particularly well-suited for domains that demand precise, declarative control over data flow, transformations, and rule systems — from compilers and interpreters to game logic, protocol parsers, and domain-specific rule engines.

Every feature listed below is built in, zero dependencies, ready on day one:

| System | What you get | |---|---| | HTTP Server | Routing, middleware, multipart, SSE, compression | | Database | SQLite ORM, query builder, migrations, transactions | | Frontend | JSX components, SSR, auto-compilation, custom pragma | | WebSockets | Full RFC 6455, rooms, broadcast, reconnection | | Auth | JWT, AES-256, bcrypt-style hashing, CSRF | | Validation | Zod-style schemas with full type inference | | AI / LLM | OpenAI, Anthropic, Ollama, Groq — one unified API | | Testing | Full test runner, assertions, mocks, coverage | | 3D Game Engine | GPU-accelerated rendering, physics, UI, particles, animation | | Camera Controllers | FPS, Orbit, Follow, Fly, CameraShake — all built-in | | Rigid Body Physics | Collision detection, impulse response, gravity | | Full UI System | Button, Slider, Checkbox, ProgressBar, TextInput — pixel-perfect | | Tweens & Easing | 24 easing functions, chainable, yoyo, loop, delay | | Particle System | Fire, smoke, explosion presets + fully customizable emitters | | Raycasting | Screen-to-world, AABB, sphere, triangle intersection | | Tilemap & Terrain | A pathfinding, procedural heightmap, noise terrain* | | Animation System | Keyframe clips, state machine, any-property targeting | | Display Backends | /dev/fb0 framebuffer, ANSI truecolor terminal, BMP file output | | Node.js interop | Every built-in and every npm package works via require() | | Binary Compilation | Compile to Linux, Android, Windows (17 targets) with ntl binary | | WebAssembly | Emit real .wasm binaries from NTL source — no emscripten, no toolchain | | Decorator system | @singleton @memo @retry @timeout @cache @log @deprecated @bind — built-in | | have operator | Pattern matching, membership, type guards, range checks — one operator |


Table of Contents

  1. Installation
  2. Quick Start
  3. Full-Stack Architecture
  4. JSX — Frontend Components
  5. CLI Reference
  6. Language Guide
  7. Using Node.js Modules
  8. Built-in Modules
  9. Project Configuration
  10. Complete Examples

Installation

npm install -g @david0dev/ntl-lang

Or clone and run directly:

git clone https://github.com/Megamexlevi2/ntl-lang
cd ntl-lang
node main.js run examples/fullstack_server.ntl

Requirements: Node.js ≥ 18.0.0


Project Structure

ntl/
├── main.js              # CLI entry point
├── src/
│   ├── compiler.js      # Compilation pipeline orchestrator
│   ├── error.js         # Error formatting
│   ├── utils.js         # Shared utilities
│   ├── native.js        # Binary compilation (ntl binary)
│   ├── pipeline/        # Core compiler stages
│   │   ├── lexer.js     # Tokenizer
│   │   ├── parser.js    # AST builder
│   │   ├── scope.js     # Scope & variable analysis
│   │   ├── typechecker.js
│   │   ├── typeinfer.js
│   │   ├── codegen.js   # JavaScript code generation
│   │   └── treeshaker.js
│   ├── transforms/      # Source transforms
│   │   ├── jsx.js       # JSX → React.createElement
│   │   └── formatter.js # Code formatter
│   └── runtime/         # Module system
│       ├── loader.js    # ntl: stdlib loader
│       ├── resolver.js  # Module path resolver
│       ├── bundler.js   # Bundler
│       └── nax.js       # Package manager
├── stdlib/              # Standard library (written in NTL)
│   ├── core/            # Language essentials
│   │   ├── fs.ntl       # File system (raw OS bindings, no require('fs'))
│   │   ├── crypto.ntl   # Hashing, encryption, JWT, UUID
│   │   ├── events.ntl   # EventEmitter
│   │   ├── queue.ntl    # Job queues
│   │   └── utils.ntl    # Utility helpers
│   ├── net/             # Networking
│   │   ├── http.ntl     # HTTP server/client
│   │   ├── ws.ntl       # WebSockets
│   │   └── mail.ntl     # SMTP email
│   ├── data/            # Data & storage
│   │   ├── db.ntl       # SQLite database
│   │   ├── validate.ntl # Schema validation
│   │   ├── cache.ntl    # In-memory cache
│   │   ├── web.ntl      # HTML/CSS/DOM tools
│   │   └── obf.ntl      # Code obfuscation
│   ├── tools/           # Developer tools
│   │   ├── test.ntl     # Test runner
│   │   ├── logger.ntl   # Structured logging
│   │   └── env.ntl      # Environment variables
│   ├── ai/
│   │   └── ai.ntl       # AI/LLM integrations
│   └── mobile/
│       └── android.ntl  # Android bridge
├── tests/
│   └── all.ntl          # Full stdlib test suite (74 tests)
└── examples/            # Example programs

Quick Start

// server.ntl — a complete REST API in 25 lines
val http = require("ntl:http")
val {Database} = require("ntl:db")

val db = new Database("./app.db")

db.createTable("todos", (t) => {
  t.id()
  t.text("title")
  t.boolean("done", false)
  t.timestamps()
})

val router = new http.Router()
router.use(http.cors())

router.get("/todos", (req, res) => {
  res.json(db.table("todos").orderByDesc("created_at").all())
})

router.post("/todos", (req, res) => {
  val id   = db.table("todos").insert({title: req.body.title})
  val todo = db.table("todos").find(id)
  res.status(201).json(todo)
})

router.delete("/todos/:id", (req, res) => {
  db.table("todos").where("id", req.params.id).delete()
  res.json({deleted: true})
})

http.listen(3000, router, () => log "Server at http://localhost:3000")
ntl run server.ntl

Full-Stack Architecture

NTL is designed for full-stack development. Your project uses one language for everything:

my-app/
├── src/
│   ├── server.ntl            ← backend: HTTP routes, auth, DB
│   ├── components/
│   │   ├── App.ntl           ← JSX component (frontend)
│   │   ├── Button.ntl        ← JSX component (frontend)
│   │   └── UserList.ntl      ← JSX component (frontend)
│   └── pages/
│       └── Home.ntl          ← SSR page (backend imports JSX)
└── ntl.json

Backend imports and renders JSX components for server-side rendering:

// src/server.ntl
val http = require("ntl:http")
val {renderToString} = require("react-dom/server")
val {App} = require("./components/App")    // ← import your JSX component

val router = new http.Router()

// Server-Side Rendering
router.get("/", (req, res) => {
  val html = renderToString(<App user={req.user} />)
  res.html("<!DOCTYPE html><html><body><div id=\"root\">{html}</div></body></html>")
})

// JSON API for the frontend to call
router.get("/api/users", (req, res) => {
  res.json(db.table("users").all())
})

http.listen(3000, router)

Frontend — JSX component in .ntl file:

// src/components/App.ntl
val React = require("react")

fn App({user}) {
  val [count, setCount] = React.useState(0)
  return (
    <div className="app">
      <h1>Hello, {user.name}!</h1>
      <button onClick={() => setCount(c => c + 1)}>
        Clicked {count} times
      </button>
    </div>
  )
}

export {App}

Compile frontend for the browser:

ntl build src/components/App.ntl -o dist/bundle.js --target browser --jsx

Run the full-stack server:

ntl run src/server.ntl

JSX — Frontend Components

NTL has first-class JSX support. JSX is auto-detected and compiled — no plugins or config needed.

How it works

You write JSX in any .ntl file:

fn Greeting({name}) {
  return <h1 className="greeting">Hello, {name}!</h1>
}

NTL compiles it to standard React.createElement calls:

function Greeting({name}) {
  return React.createElement("h1", {className: "greeting"}, "Hello, ", name, "!");
}

JSX Syntax

// Lowercase tag = HTML element (string)
val box = <div className="container">content</div>

// Uppercase tag = Component (variable reference)
val ui = <MyButton variant="primary" onClick={handler}>Click me</MyButton>

// Fragment — no wrapper element
val group = (
  <>
    <h1>Title</h1>
    <p>Paragraph</p>
  </>
)

// Self-closing
val field = <input type="email" value={email} onChange={handleChange} />

// Spread props
val comp = <Button {...defaultProps} {...overrides} />

// Boolean prop (value = true)
val btn = <button disabled>Disabled</button>

// Expressions inside JSX
val list = (
  <ul>
    {items.map(item => (
      <li key={item.id}>{item.name}</li>
    ))}
  </ul>
)

// Conditional rendering
val view = (
  <div>
    {isLoggedIn && <WelcomeBanner user={user} />}
    {!isLoggedIn && <LoginForm />}
    {status === "loading" ? <Spinner /> : <Content />}
  </div>
)

// Nested components
val page = (
  <Layout>
    <Header title="Dashboard" />
    <main>
      <Sidebar />
      <UserList users={users} loading={loading} />
    </main>
    <Footer />
  </Layout>
)

Import Rules for JSX

JSX can be used on both frontend and backend:

| Use case | Works? | Notes | |---|---|---| | Frontend .ntl file with JSX | ✅ | Compile with --target browser | | Backend .ntl file importing JSX | ✅ | Install react via npm | | SSR: backend renders JSX to HTML string | ✅ | Uses react-dom/server |

Important: JSX imports require react (or your chosen framework) installed via npm in your project. NTL transforms the syntax — the React runtime must be available.

# For SSR or backend usage:
npm install react react-dom

Build for the Browser

# Compile NTL + JSX → browser bundle
ntl build src/App.ntl -o dist/bundle.js --target browser

# Custom pragma (Preact, Solid, etc.)
ntl build src/App.ntl -o dist/bundle.js --jsx-pragma h --jsx-frag Fragment

# With minification
ntl build src/App.ntl -o dist/bundle.js --target browser --minify

JSX with Tailwind CSS

fn ProductCard({name, price, image, onBuy}) {
  return (
    <div className="bg-white rounded-2xl shadow-lg overflow-hidden hover:shadow-xl transition-shadow">
      <img src={image} alt={name} className="w-full h-48 object-cover" />
      <div className="p-5">
        <h3 className="text-lg font-semibold text-gray-900">{name}</h3>
        <p className="text-2xl font-bold text-blue-600 mt-1">${price}</p>
        <button
          onClick={onBuy}
          className="mt-4 w-full bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 rounded-lg"
        >
          Buy Now
        </button>
      </div>
    </div>
  )
}

CLI Reference

ntl run    <file.ntl>              Execute an NTL file immediately
ntl binary <file.ntl>              Compile to native binary  [--target] [--standalone] [--all]
ntl build  <file.ntl|ntl.json>    Compile to JavaScript
ntl bundle <file|dir> [-o out]    Bundle multiple files into one
ntl check  <file.ntl>             Type-check without emitting
ntl watch  <file.ntl>             Watch and recompile on changes
ntl dev    [dir]                  Dev server with hot reload
ntl repl                          Interactive REPL
ntl init   [dir]                  Scaffold a new project with ntl.json
ntl fmt    <file.ntl>             Format an NTL file
ntl nax    install <url>          Install a module from GitHub
ntl nax    list                   List installed modules
ntl version                       Show version info
ntl help                          Show help

Build Flags

| Flag | Description | |---|---| | --out <path> / -o <path> | Output file path | | --target <t> | node (default) | browser | deno | bun | esm | cjs | | --minify | Minify output | | --obfuscate | Obfuscate output for distribution | | --strict | Enable strict type checking | | --no-treeshake | Disable tree shaking | | --source-map | Generate source map | | --jsx | Force-enable JSX transform (auto-detected by default) | | --jsx-pragma <fn> | JSX factory function (default: React.createElement) | | --jsx-frag <fn> | JSX Fragment (default: React.Fragment) | | --jsx-import none | Don't auto-inject require("react") |


Language Guide

Variables

val name    = "Alice"          // const — never reassigned
var counter = 0                // let — mutable
counter++
counter += 10

val {a, b, c} = someObj       // destructure
val [x, y, ...rest] = list    // array destructure
val a: number = 42            // optional type annotation

Functions

fn add(a, b) { return a + b }

async fn fetchUser(id) {
  val res = await http.get("https://api.example.com/users/{id}")
  return res.data
}

// Default parameters
fn greet(name, greeting = "Hello") {
  return "{greeting}, {name}!"
}

// Rest parameters
fn sum(...nums) { return nums.reduce((a, b) => a + b, 0) }

// With return type
fn divide(a: number, b: number) -> number {
  guard b !== 0 else { return 0 }
  return a / b
}

// Destructured params (works with JSX components too)
fn UserCard({name, email, active = true}) {
  return "{name} <{email}> ({active ? 'active' : 'inactive'})"
}

// Generator
fn* counter(start, end) {
  var n = start
  while n <= end { yield n++ }
}

Arrow Functions

val double  = x => x * 2
val add     = (x, y) => x + y
val process = (x) => {
  val result = x * 2
  return result + 1
}
val load    = async (url) => { return await http.get(url) }
val noop    = async () => {}

Control Flow

// if / elif / else
if x > 0 { log "positive" }
elif x < 0 { log "negative" }
else { log "zero" }

// unless (if not)
unless ready { raise "Not ready" }

// guard — early exit
fn process(data) {
  guard data !== null else { return "null input" }
  guard data.length > 0 else { return "empty" }
  return data.join(", ")
}

// while / loop / do...while
while active { process() }
loop { val item = next(); if !item { break } }
do { attempt() } while failed && tries < 3

// for...of / each (alias)
for val item of list { log item }
each user in users { log user.name }

// repeat N times
repeat 3 { log "hello" }

// for...in (object keys)
for val key in config { log key, config[key] }

// defer — run on scope exit
fn withFile(path) {
  val file = fs.open(path)
  defer { file.close() }
  return file.read()
}

// break / continue with range
for val n of range(10) {
  if n % 2 === 0 { continue }
  if n > 7 { break }
  log n
}

Classes

class Animal {
  init(name, species) {
    this.name    = name
    this.species = species
  }

  speak() { log "{this.name} makes a sound" }

  get fullName() { return "{this.name} ({this.species})" }
  set fullName(v) { this.name = v.split(" ")[0] }

  static create(name, species) { return new Animal(name, species) }

  async fetchInfo() {
    return await http.get("https://api.example.com/animals/{this.species}")
  }
}

class Dog extends Animal {
  init(name) {
    super(name, "Canis lupus familiaris")
    this.tricks = []
  }

  speak() { log "{this.name}: Woof!" }

  learnTrick(trick) { this.tricks.push(trick) }
}

val rex = new Dog("Rex")
rex.speak()
log rex.fullName

Decorators

NTL has a full set of built-in decorators that work on functions and classes — no packages needed.

// ─── @class — attach metadata to a class ─────────────────
@class
class User {
  init(name, email) {
    this.name  = name
    this.email = email
  }
}
// User.__ntl_meta → { name: "User", decorators: ["class"], created: ... }

// ─── @singleton ───────────────────────────────────────────
@singleton
class Config {
  init() { this.env = process.env.NODE_ENV ?? "dev" }
}

val a = new Config()
val b = new Config()
log a === b   // true — same instance every time

// ─── @sealed — prevent subclassing and mutation ───────────
@sealed
class Token {
  init(value) { this.value = value }
}

// ─── @abstract — cannot be instantiated directly ──────────
@abstract
class Shape {
  area() { raise "not implemented" }
}

class Circle extends Shape {
  init(r) { this.r = r }
  area() { return Math.PI * this.r ** 2 }
}

// ─── @memo — memoize function results ─────────────────────
@memo
fn fib(n) {
  if n <= 1 { return n }
  return fib(n - 1) + fib(n - 2)
}

log fib(40)  // computed once per unique arg, then cached

// ─── @retry(n) — retry async functions on failure ─────────
@retry(3)
async fn fetchUser(id) {
  return await http.get("/users/{id}")
}
// auto-retries up to 3x with exponential backoff

// ─── @timeout(ms) — fail after N milliseconds ─────────────
@timeout(5000)
async fn slowQuery() {
  return await db.query("SELECT ...")
}
// throws "Timeout after 5000ms" if it takes longer

// ─── @deprecated(message) ────────────────────────────────
@deprecated("use newApi() instead")
fn oldApi() {
  return "legacy"
}
// logs warning to console on every call

// ─── @log — trace every call ──────────────────────────────
@log
fn processPayment(amount, card) {
  return charge(card, amount)
}
// logs: [processPayment] called with [...args]
// logs: [processPayment] returned [result]

// ─── @cache(ttl_ms) — cache return values ─────────────────
@cache(60000)
fn getExchangeRate(from, to) {
  return http.get("/rates/{from}/{to}")
}
// result cached for 60 seconds per unique argument combination

// ─── @bind — auto-bind class methods to 'this' ────────────
@bind
class Timer {
  init() { this.count = 0 }
  tick() { this.count++ }   // 'this' always bound, safe as callback
}

val timer = new Timer()
setInterval(timer.tick, 1000)  // works without .bind(timer)

// ─── @validate(schema) — runtime schema validation ────────
@validate
fn createUser(data) {
  return db.insert("users", data)
}

// ─── Stacking decorators ──────────────────────────────────
@memo
@retry(2)
@timeout(3000)
@log
async fn criticalFetch(id) {
  return await api.get("/critical/{id}")
}

// ─── @class on any target ────────────────────────────────
@singleton
@sealed
class AppState {
  init() {
    this.users    = []
    this.sessions = new Map()
  }
}

Destructuring

val {name, email, ...rest} = user
val {address: {city, zip}} = profile
val {name: userName, id: userId} = user

val [first, , third, ...others] = list

// In function params
fn show({name, age = 0}) { log name, age }

// In loops
for val {name, salary} of employees { log name, salary }
for val [key, value] of Object.entries(map) { log key, value }

Template Strings

val msg  = "Hello, {name}! You have {count} messages."
val url  = "https://api.example.com/users/{userId}"
val calc = "Result: {a + b} ({a * b} multiplied)"
val cond = "Status: {active ? 'active' : 'inactive'}"

Pattern Matching

// Match as statement
match status {
  case "active"  => { log "Running" }
  case "paused"  => { log "Paused" }
  default        => { log "Unknown:", status }
}

// Match as expression (return value)
fn describe(code) {
  return match code {
    case n when n >= 200 && n < 300 => { "Success" }
    case n when n >= 400 && n < 500 => { "Client error" }
    case n when n >= 500            => { "Server error" }
    default                         => { "Other" }
  }
}

// Match assigned to variable
val label = match role {
  case "admin"  => { "Administrator" }
  case "editor" => { "Editor" }
  default       => { "User" }
}

have — Pattern Guard Operator

have is NTL's unified guard and pattern-matching operator. It replaces if, optional chaining, in, instanceof, regex tests, and range checks with a single readable syntax.

// ─── membership ──────────────────────────────────────────
val fruits = ["apple", "banana", "mango"]

have "banana" in fruits {
  log "found"
}

have "grape" in fruits {
  log "exists"
} else {
  log "not in list"
}

// Works with arrays, Sets, objects (key check), and strings
have "hello" in "hello world" { log "substring found" }
have "port"  in config         { log "port is configured" }
have user.id in cache          { log "cache hit" }

// ─── early return (guard form) ───────────────────────────
fn process(req) {
  have req.body        else { return 400 }
  have req.body.email  else { return "missing email" }
  have req.body.name   else { return "missing name" }

  // guaranteed: body, email, name all exist here
  return save(req.body)
}

// ─── range check ─────────────────────────────────────────
have age between 18 99 {
  log "valid age"
}

have score between 0 100 else {
  raise "score out of range"
}

// ─── regex match ─────────────────────────────────────────
have email matches /^[\w.]+@[\w.]+\.[a-z]{2,}$/ {
  log "valid email"
} else {
  return "bad email format"
}

// ─── type check ──────────────────────────────────────────
have value is String  { log "it's a string" }
have value is Number  { log "it's a number" }
have value is Array   { log "it's an array" }

have value is not String {
  raise TypeError("expected string")
}

// ─── string pattern ──────────────────────────────────────
have path startsWith "/api" { log "API route" }
have file endsWith ".ntl"   { log "NTL source" }

// ─── value binding ───────────────────────────────────────
// (captures the value while checking, like ifhave but inline)
have db.find(id) as user {
  log "found:", user.name
} else {
  log "not found"
}

// ─── safe deep access ────────────────────────────────────
// have as expression = null-safe chain
val city = have user.profile.address.city   // never throws
val port = have config.server.port ?? 3000

ifhave — Conditional Binding

// run a block only when value exists (non-null, non-false)
ifhave user.name as name {
  log "Hello,", name
} else {
  log "anonymous"
}

// works with any expression — functions, async, find
ifhave list.find(x => x.active) as item {
  log "active item:", item.id
}

// membership patterns
ifhave token in validTokens {
  log "authorized"
}

// range
ifhave score between 90 100 {
  log "A grade"
}

// regex
ifhave input matches /^\d{4}-\d{2}-\d{2}$/ {
  log "date format valid"
}

Other Safe Operations

// try? — returns null instead of throwing
val parsed = try? JSON.parse(input)
val user   = try? await fetchUser(id)

// ifset — run if variable is defined (not undefined)
ifset config.port as port { log "Port:", port }

// Optional chaining + nullish coalescing
val name = user?.profile?.name ?? "Anonymous"
val port = env?.PORT ?? 3000

Generators

fn* range2(start, end, step) {
  step = step || 1
  var n = start
  while n <= end { yield n; n += step }
}

for val n of range2(0, 10, 2) { log n }   // 0 2 4 6 8 10

async fn* streamLines(path) {
  val content = await fs.readAsync(path)
  for val line of content.split("\n") { yield line }
}

Ranges and Pipelines

range(10)           // [0, 1, 2, ..., 9]
range(1, 6)         // [1, 2, 3, 4, 5]
range(0, 10, 2)     // [0, 2, 4, 6, 8]

// Pipeline operator |>
val result = range(10)
  |> (arr => arr.filter(n => n % 2 === 0))
  |> (arr => arr.map(n => n * n))
  |> (arr => arr.reduce((a, b) => a + b, 0))

Namespaces and Enums

namespace MathUtils {
  val PI = 3.14159265358979
  fn clamp(v, min, max) { return v < min ? min : v > max ? max : v }
  fn lerp(a, b, t) { return a + (b - a) * t }
}

MathUtils.clamp(15, 0, 10)   // 10
MathUtils.lerp(0, 100, 0.5)  // 50

enum Status { Active, Inactive, Pending, Blocked }
enum HttpCode { OK = 200, Created = 201, NotFound = 404, ServerError = 500 }

match user.status {
  case Status.Active  => { process(user) }
  case Status.Blocked => { log "Blocked" }
  default             => { log "Pending review" }
}

Types and Interfaces

interface User {
  id:     number
  name:   string
  email:  string
  active: boolean
}

interface Repository<T> {
  find(id: number)     -> T
  save(item: T)        -> void
  delete(id: number)   -> boolean
}

type ID       = number | string
type Callback = (err: Error | null, result: any) -> void

fn first<T>(list: T[]) -> T { return list[0] }

Special Keywords

// log — shorthand for console.log
log "Server started"
log "User:", user.name, "at", new Date()

// assert — throws if false
assert result !== null, "Result must not be null"
assert list.length > 0, "List cannot be empty"

// sleep — pause async execution
async fn wait() {
  log "Starting..."
  sleep 2000          // wait 2 seconds
  log "Done"
}

// spawn — fire-and-forget async
spawn processQueue()
spawn sendEmail(user, "welcome")

// raise — shorthand for throw
fn requireId(id) {
  if !id { raise "id is required" }
  return id
}

// immutable — deep freeze
immutable CONFIG = {port: 3000, env: "production"}

// delete
delete obj.tempKey
delete cache[key]

Using Node.js Modules

All Node.js built-in modules work natively in NTL:

val path   = require("path")
val fs     = require("fs")
val os     = require("os")
val crypto = require("crypto")

val {EventEmitter} = require("events")
val {promisify}    = require("util")
val {join}         = require("path")

// npm packages also work (install with npm first)
val axios  = require("axios")
val lodash = require("lodash")

log path.join("/home", "user", "docs")
log os.cpus().length, "CPU cores"
log crypto.randomBytes(16).toString("hex")

How NTL Modules Work

NTL built-in modules use the ntl: prefix. They are only available inside NTL programs — the NTL runtime automatically intercepts require("ntl:...") calls and loads the correct module.

// These all work inside any .ntl file:
val http     = require("ntl:http")
val db       = require("ntl:db")
val crypto   = require("ntl:crypto")
val validate = require("ntl:validate")

When you build a NTL file with ntl build, the output JavaScript automatically includes a small runtime preamble that makes ntl: module loading work. You can run the output file directly with Node.js:

ntl build app.ntl -o dist/app.js
node dist/app.js          # works — preamble is included

Important: The compiled .js output is valid Node.js, but it depends on having the NTL runtime available (the ntl-lang package on npm, or a local NTL installation). You cannot copy just the .js file to a machine without NTL installed and expect ntl: modules to resolve. For fully self-contained deployment, use ntl bundle:

ntl bundle app.ntl -o dist/bundle.js    # single-file, no NTL runtime needed

Module Loading Rules

| Prefix | Example | Works in | |--------|---------|----------| | ntl: | require("ntl:http") | NTL files, compiled output with preamble | | ./ | require("./utils") | NTL files — resolves relative to source | | bare name | require("path"), require("axios") | NTL files — Node.js built-ins and npm packages |

Writing Modules in NTL (for NTL)

You can write your own .ntl modules and require() them from other NTL files. They compile and load just like any other module:

my-project/
  src/
    main.ntl          ← require("./utils") works here
    utils.ntl         ← your module
    db/
      queries.ntl     ← require("../utils") works here

Modules auto-export via exports.x = x or module.exports = { ... }. See the Creating Your Own Modules section for full examples.


Compiling to Binary

NTL compiles to standalone executables for Linux, Android, and Windows — every common architecture. No toolchain needed, no Docker, no cross-compilation setup. One command.

ntl binary app.ntl                        # Linux x64 (default)
ntl binary app.ntl --target android-arm64 # Android
ntl binary app.ntl --target windows-x64   # Windows
ntl binary app.ntl --all -o dist/         # every target at once

All Available Targets

| Platform | Targets | |----------|---------| | Linux | linux-x64 linux-arm64 linux-arm32 linux-riscv64 linux-ppc64 linux-s390x linux-mips64 linux-x86 | | Android | android-arm64 android-arm32 android-x64 android-x86 | | Windows | windows-x64 windows-arm64 windows-x86 | | WebAssembly | wasm32 wasm64 |

ntl binary --list-targets   # print all 17 targets with triples

Modes

Shell binary (default) — small, requires Node.js on target:

ntl binary server.ntl -o server --target linux-arm64

Standalone — ~55 MB, bundles the Node runtime. Runs with zero dependencies:

ntl binary app.ntl -o app --standalone --target android-arm64
./app                        # runs on any Android with Termux or direct exec

Build for every target at once:

ntl binary app.ntl --all -o dist/ --standalone
# dist/
#   app_linux_x64
#   app_linux_arm64
#   app_android_arm64
#   app_windows_x64.bat
#   app_wasm32.wasm
#   ... 17 total

How it works

The compiler embeds all used ntl: stdlib modules as pre-compiled strings directly into the binary. The binary is fully self-contained at the NTL level — no external NTL installation needed at runtime. The standalone mode additionally bundles the Node.js runtime, making the output run on any machine without any software installed.

WebAssembly — Real .wasm, No Dependencies

NTL compiles to real WebAssembly binary format (.wasm) without emscripten, wasi-sdk, or any external toolchain. The compiler writes the WebAssembly binary encoding directly.

ntl binary app.ntl -o app --target wasm32
# → app.wasm  (valid WebAssembly binary, 100% spec-compliant)

The output is a true .wasm binary that:

  • Runs in any browser via WebAssembly.instantiate()
  • Runs with wasmtime, wasmer, or wasm3 on any OS
  • Can be imported as a Web Worker
  • Exports _start and memory (WASI-compatible)
// Browser / Node.js
const wasm = await WebAssembly.instantiateStreaming(fetch('app.wasm'));
wasm.instance.exports._start();

// Node.js (direct)
const buf = fs.readFileSync('app.wasm');
const { instance } = await WebAssembly.instantiate(buf);
instance.exports._start();
# CLI runtimes
wasmtime app.wasm
wasmer   app.wasm
wasm3    app.wasm
node     -e "WebAssembly.instantiate(require('fs').readFileSync('app.wasm')).then(r=>r.instance.exports._start())"

Creating Your Own Modules

NTL has a straightforward module system. Any .ntl file is a module. You import with require(), and export with exports.X = X or module.exports = { ... }.

Basic Module

Create math-utils.ntl:

fn add(a, b) { return a + b }
fn sub(a, b) { return a - b }
fn clamp(v, lo, hi) { return Math.min(Math.max(v, lo), hi) }
fn lerp(a, b, t) { return a + (b - a) * t }

exports.add   = add
exports.sub   = sub
exports.clamp = clamp
exports.lerp  = lerp

Use it from another file:

val math = require("./math-utils")

log math.add(2, 3)       // 5
log math.clamp(150, 0, 100) // 100
log math.lerp(0, 100, 0.5)  // 50

Class-based Module

Create logger.ntl:

class Logger {
  constructor(prefix) {
    this.prefix = prefix || "APP"
    this._level = "info"
  }

  setLevel(level) { this._level = level; return this }

  info(msg, ...args)  { console.log(`[${this.prefix}] INFO`, msg, ...args) }
  warn(msg, ...args)  { console.warn(`[${this.prefix}] WARN`, msg, ...args) }
  error(msg, ...args) { console.error(`[${this.prefix}] ERROR`, msg, ...args) }
  debug(msg, ...args) {
    if (this._level === "debug") { console.log(`[${this.prefix}] DEBUG`, msg, ...args) }
  }
}

fn createLogger(prefix) { return new Logger(prefix) }

exports.Logger       = Logger
exports.createLogger = createLogger

Module with State

Create counter.ntl:

var _count = 0

fn increment(by) { _count += (by || 1); return _count }
fn decrement(by) { _count -= (by || 1); return _count }
fn reset()       { _count = 0 }
fn value()       { return _count }

exports.increment = increment
exports.decrement = decrement
exports.reset     = reset
exports.value     = value

Async Module

Create api-client.ntl:

val http = require("ntl:http")

val BASE_URL = "https://api.example.com"

async fn getUser(id) {
  val res = await http.get(BASE_URL + "/users/" + id)
  return res.data
}

async fn createUser(data) {
  val res = await http.post(BASE_URL + "/users", data)
  return res.data
}

async fn updateUser(id, data) {
  val res = await http.put(BASE_URL + "/users/" + id, data)
  return res.data
}

exports.getUser    = getUser
exports.createUser = createUser
exports.updateUser = updateUser

EventEmitter Module

val { EventEmitter } = require("ntl:events")

class TaskQueue extends EventEmitter {
  constructor() {
    super()
    this._queue   = []
    this._running = false
  }

  push(task) {
    this._queue.push(task)
    this.emit("queued", this._queue.length)
    if (!this._running) { this._process() }
    return this
  }

  async _process() {
    this._running = true
    while (this._queue.length > 0) {
      val task = this._queue.shift()
      this.emit("start", task)
      try {
        val result = await task()
        this.emit("done", result)
      } catch (err) {
        this.emit("error", err)
      }
    }
    this._running = false
    this.emit("idle")
  }

  get size() { return this._queue.length }
}

exports.TaskQueue = TaskQueue

Usage:

val { TaskQueue } = require("./task-queue")

val q = new TaskQueue()
q.on("done",  result => log "Finished:", result)
q.on("error", err    => log "Error:", err.message)
q.on("idle",  ()     => log "All done!")

q.push(async () => {
  val res = await http.get("https://api.example.com/data")
  return res.data
})

Module Index File

For larger projects, create index.ntl to re-export from multiple files:

val db       = require("./db")
val auth     = require("./auth")
val mailer   = require("./mailer")
val logger   = require("./logger")

exports.db     = db
exports.auth   = auth
exports.mailer = mailer
exports.log    = logger.createLogger("APP")

Then anywhere in your project:

val { db, auth, log } = require("./lib")

NTL Module Rules

| Rule | Detail | |------|--------| | Declare constants | val (like const) | | Declare variables | var (like let) | | Define functions | fn myFn(args) { } | | Define classes | class MyClass { constructor() {} } | | Export single value | exports.name = value | | Export multiple | module.exports = { a, b, c } | | Import file | require("./path/to/file") | | Import built-in | require("ntl:modulename") | | Import Node.js | require("fs"), require("path"), etc. | | Loops over arrays | each item in array { } | | Avoid as names | fn, val, var, each, range, type, init (reserved) |

Avoid These Patterns in Object Literals

NTL strips quotes from object literal keys. Use bracket notation for hyphenated keys:

// ❌ Wrong — NTL removes quotes from keys
val headers = { "Content-Type": "application/json" }  // compiles to: { Content-Type: ... }

// ✅ Correct — use bracket notation
val headers = {}
headers["Content-Type"] = "application/json"
headers["Authorization"] = "Bearer " + token

// ✅ Also correct — single-word keys work fine  
val options = { method: "POST", body: data }

Arrow Functions in Objects

Object methods must use arrow syntax:

// ✅ Correct
val obj = {
  greet: (name) => "Hello " + name,
  add:   (a, b) => a + b
}

// ❌ Wrong — shorthand methods not supported in objects
val obj = {
  greet(name) { return "Hello " + name }  // may not work
}

// ✅ Use classes for methods
class MyService {
  greet(name) { return "Hello " + name }   // works in class
}

2D Game Development

NTL's built-in game engine supports full 2D and 3D game development with a CPU software rasterizer and optional GPU framebuffer output.

2D Sprite Game

val game = require("ntl:game")

class MyGame {
  constructor() {
    this.player = { x: 100, y: 300, w: 32, h: 32, vx: 0, vy: 0 }
    this.gravity = 800
    this.floor   = 400
    this.score   = 0
    this.ui      = new game.UICanvas({ width: 1280, height: 720 })
  }

  update(dt, input) {
    // Player movement
    if (input.left)  { this.player.vx = -200 }
    if (input.right) { this.player.vx =  200 }
    if (!input.left && !input.right) { this.player.vx = 0 }

    // Jump
    if (input.jump && this.player.y >= this.floor) {
      this.player.vy = -600
    }

    // Gravity
    this.player.vy += this.gravity * dt
    this.player.x  += this.player.vx * dt
    this.player.y  += this.player.vy * dt

    // Floor collision
    if (this.player.y >= this.floor) {
      this.player.y  = this.floor
      this.player.vy = 0
    }

    this.score += dt * 10
  }

  render(ctx) {
    // Clear
    ctx.fillRect(0, 0, 1280, 720, "#1a1a2e")

    // Floor
    ctx.fillRect(0, 432, 1280, 288, "#16213e")

    // Player (colored rectangle as sprite placeholder)
    ctx.fillRect(this.player.x, this.player.y, this.player.w, this.player.h, "#e94560")

    // Score UI
    this.ui.text("Score: " + Math.floor(this.score), 20, 30, { color: "#fff", size: 20 })
  }
}

game.run(MyGame, { width: 1280, height: 720, fps: 60 })

2D Tilemap Game with A* Pathfinding

val game = require("ntl:game")
val { TileMap } = require("ntl:game/tilemap")

val MAP = [
  [1,1,1,1,1,1,1,1,1,1],
  [1,0,0,0,1,0,0,0,0,1],
  [1,0,1,0,1,0,1,1,0,1],
  [1,0,1,0,0,0,0,1,0,1],
  [1,0,0,0,1,0,0,0,0,1],
  [1,1,1,1,1,1,1,1,1,1]
]

class DungeonGame {
  constructor() {
    this.tilemap = new TileMap(MAP, 64)  // 64px tiles
    this.player  = { gridX: 1, gridY: 1 }
    this.enemy   = { gridX: 8, gridY: 4 }
    this._path   = []
  }

  update(dt, input) {
    if (input.pressedThisFrame("w")) { this._movePlayer(0, -1) }
    if (input.pressedThisFrame("s")) { this._movePlayer(0,  1) }
    if (input.pressedThisFrame("a")) { this._movePlayer(-1, 0) }
    if (input.pressedThisFrame("d")) { this._movePlayer( 1, 0) }

    // Enemy chases player with A*
    this._path = this.tilemap.findPath(
      this.enemy.gridX, this.enemy.gridY,
      this.player.gridX, this.player.gridY
    )
  }

  _movePlayer(dx, dy) {
    val nx = this.player.gridX + dx
    val ny = this.player.gridY + dy
    if (MAP[ny] && MAP[ny][nx] === 0) {
      this.player.gridX = nx
      this.player.gridY = ny
    }
  }

  render(ctx) {
    this.tilemap.render(ctx, {
      0: "#1a1a2e",  // floor
      1: "#4a4a6a"   // wall
    })
    // Draw player
    ctx.fillRect(this.player.gridX * 64 + 8, this.player.gridY * 64 + 8, 48, 48, "#e94560")
    // Draw enemy
    ctx.fillRect(this.enemy.gridX * 64 + 8, this.enemy.gridY * 64 + 8, 48, 48, "#f5a623")
  }
}

game.run(DungeonGame, { width: 640, height: 384, fps: 30 })

2D Particle Effects

val game    = require("ntl:game")
val { ParticleSystem } = require("ntl:game/particles")

class FireworkGame {
  constructor() {
    this.particles = new ParticleSystem(5000)  // max 5000 particles
  }

  update(dt, input) {
    if (input.pressedThisFrame("space")) {
      this.particles.emit({
        x: Math.random() * 1280,
        y: Math.random() * 720,
        count:    80,
        speed:    300,
        lifetime: 1.5,
        gravity:  400,
        colors:   ["#ff6b6b", "#ffd93d", "#6bcb77", "#4d96ff"]
      })
    }
    this.particles.update(dt)
  }

  render(ctx) {
    ctx.fillRect(0, 0, 1280, 720, "#0d0d0d")
    this.particles.render(ctx)
    ctx.text("Press SPACE for fireworks!", 400, 680, { color: "#555", size: 16 })
  }
}

game.run(FireworkGame, { width: 1280, height: 720, fps: 60 })

3D Game Development

NTL ships a full CPU software rasterizer with optional GPU framebuffer output (/dev/fb0 on Linux). No GPU driver setup required — it works in a terminal.

First 3D Scene

val game = require("ntl:game")

class My3DGame {
  constructor() {
    this.camera = new game.Camera({
      fov:      75,
      near:     0.1,
      far:      1000,
      position: { x: 0, y: 2, z: 5 }
    })

    this.renderer = new game.Renderer3D({
      width:  1280,
      height: 720,
      shading: "phong"     // "flat" | "phong" | "wireframe"
    })

    this.scene  = new game.Scene()
    this.clock  = new game.Clock()

    // Add a cube
    val cube = game.createCube({ size: 2, color: [0.8, 0.3, 0.3] })
    cube.position = { x: 0, y: 1, z: 0 }
    this.scene.add(cube)
    this.cube = cube

    // Add a plane
    val floor = game.createPlane({ width: 20, height: 20, color: [0.3, 0.6, 0.3] })
    floor.position = { x: 0, y: 0, z: 0 }
    this.scene.add(floor)

    // Lighting
    this.scene.addLight({ kind: "directional", direction: [-1, -2, -1], intensity: 1.0 })
    this.scene.addLight({ kind: "ambient", intensity: 0.3 })

    this.display = game.createDisplay(1280, 720)
  }

  update(dt, input) {
    // Rotate the cube
    this.cube.rotation.y += dt * 1.2
    this.cube.rotation.x += dt * 0.5

    // WASD camera movement
    val speed = 5 * dt
    if (input.w) { this.camera.moveForward(speed) }
    if (input.s) { this.camera.moveForward(-speed) }
    if (input.a) { this.camera.moveRight(-speed) }
    if (input.d) { this.camera.moveRight(speed) }

    // Mouse look
    if (input.mouse.dx !== 0 || input.mouse.dy !== 0) {
      this.camera.rotateYaw(input.mouse.dx * 0.002)
      this.camera.rotatePitch(input.mouse.dy * 0.002)
    }
  }

  render() {
    this.renderer.clear()
    this.scene.render(this.renderer, this.camera)
    this.display.flush(this.renderer.buffer, 1280, 720)
  }
}

game.run(My3DGame, { width: 1280, height: 720, fps: 60 })

3D FPS Game (Raycaster)

val game = require("ntl:game")
val { Raycaster } = require("ntl:game/raycasting")

val MAP = [
  [1,1,1,1,1,1,1,1],
  [1,0,0,0,0,0,0,1],
  [1,0,1,1,0,1,0,1],
  [1,0,0,0,0,0,0,1],
  [1,0,1,0,1,1,0,1],
  [1,0,0,0,0,0,0,1],
  [1,1,1,1,1,1,1,1]
]

class FPSGame {
  constructor() {
    this.raycaster = new Raycaster(MAP, { width: 1280, height: 720, fov: 66 })
    this.player    = { x: 2.5, y: 2.5, angle: 0 }
    this.display   = game.createDisplay(1280, 720)
    this.ui        = new game.UICanvas({ width: 1280, height: 720 })
  }

  update(dt, input) {
    val speed    = 3 * dt
    val turnSpeed = 2.5 * dt

    if (input.left)  { this.player.angle -= turnSpeed }
    if (input.right) { this.player.angle += turnSpeed }

    val dx = Math.cos(this.player.angle) * speed
    val dy = Math.sin(this.player.angle) * speed

    if (input.up) {
      val nx = this.player.x + dx
      val ny = this.player.y + dy
      if (MAP[Math.floor(ny)][Math.floor(nx)] === 0) {
        this.player.x = nx
        this.player.y = ny
      }
    }
    if (input.down) {
      val nx = this.player.x - dx
      val ny = this.player.y - dy
      if (MAP[Math.floor(ny)][Math.floor(nx)] === 0) {
        this.player.x = nx
        this.player.y = ny
      }
    }
  }

  render() {
    val frame = this.raycaster.render(this.player.x, this.player.y, this.player.angle)
    // Crosshair
    this.ui.fillRect(638, 358, 4, 4, "#ffffff")
    // Mini-map
    this.ui.drawMinimap(MAP, this.player, { x: 10, y: 10, scale: 12 })
    this.display.flush(frame, 1280, 720)
  }
}

game.run(FPSGame, { width: 1280, height: 720, fps: 60 })

3D Terrain Game

val game    = require("ntl:game")
val { ProceduralTerrain } = require("ntl:game/terrain")

class TerrainExplorer {
  constructor() {
    this.camera   = new game.Camera({ fov: 80, position: { x: 0, y: 30, z: 0 } })
    this.renderer = new game.Renderer3D({ width: 1280, height: 720, shading: "phong" })
    this.scene    = new game.Scene()
    this.display  = game.createDisplay(1280, 720)

    val terrain = new ProceduralTerrain({
      seed:        42,
      size:        256,
      heightScale: 40,
      waterLevel:  8,
      octaves:     6
    })
    this.scene.add(terrain.mesh)
    this.scene.addLight({ kind: "directional", direction: [-1, -3, -1], intensity: 1.2 })
    this.scene.addLight({ kind: "ambient", intensity: 0.4 })

    this.yaw   = 0
    this.pitch = -0.4
  }

  update(dt, input) {
    val speed = 20 * dt
    if (input.w) { this.camera.moveForward(speed) }
    if (input.s) { this.camera.moveForward(-speed) }
    if (input.a) { this.camera.moveRight(-speed) }
    if (input.d) { this.camera.moveRight(speed) }
    if (input.left)  { this.yaw -= dt * 1.5 }
    if (input.right) { this.yaw += dt * 1.5 }
    this.camera.setRotation(this.pitch, this.yaw)
  }

  render() {
    this.renderer.clear()
    this.scene.render(this.renderer, this.camera)
    this.display.flush(this.renderer.buffer, 1280, 720)
  }
}

game.run(TerrainExplorer, { width: 1280, height: 720, fps: 60 })

3D Physics Simulation

val game = require("ntl:game")
val { PhysicsWorld } = require("ntl:game/physics")

class PhysicsDemo {
  constructor() {
    this.camera   = new game.Camera({ fov: 75, position: { x: 0, y: 5, z: 15 } })
    this.renderer = new game.Renderer3D({ width: 1280, height: 720 })
    this.scene    = new game.Scene()
    this.display  = game.createDisplay(1280, 720)
    this.physics  = new PhysicsWorld({ gravity: -9.81 })

    // Floor
    val floor    = game.createPlane({ width: 30, height: 30, color: [0.4, 0.4, 0.4] })
    val floorBody = this.physics.addStatic(floor, { restitution: 0.5 })
    this.scene.add(floor)

    // Spawn boxes
    this.boxes = []
    var i = 0
    while (i < 15) {
      val box  = game.createCube({ size: 1, color: [Math.random(), Math.random(), Math.random()] })
      box.position = { x: (Math.random() - 0.5) * 10, y: 10 + i * 1.5, z: (Math.random() - 0.5) * 10 }
      val body = this.physics.addRigidBody(box, { mass: 1, restitution: 0.3 })
      this.scene.add(box)
      this.boxes.push({ mesh: box, body })
      i++
    }

    this.scene.addLight({ kind: "directional", direction: [-1, -2, -1], intensity: 1.0 })
    this.scene.addLight({ kind: "ambient", intensity: 0.35 })
  }

  update(dt) {
    this.physics.step(dt)
  }

  render() {
    this.renderer.clear()
    this.scene.render(this.renderer, this.camera)
    this.display.flush(this.renderer.buffer, 1280, 720)
  }
}

game.run(PhysicsDemo, { width: 1280, height: 720, fps: 60 })

Game Engine Module Reference

| Module | Import | What It Does | |--------|--------|-------------| | Core runner | require("ntl:game") | game.run(), Camera, Scene, Clock, mesh builders | | 3D Renderer | require("ntl:game") | .Renderer3D — Phong shading, z-buffer, up to 2K | | Display | require("ntl:game") | createDisplay() — framebuffer / terminal / BMP | | 2D UI | require("ntl:game") | UICanvas — text, rectangles, HUD drawing | | Physics | require("ntl:game/physics") | PhysicsWorld, RigidBody, collision, impulse | | Particles | require("ntl:game/particles") | ParticleSystem — burst, trail, gravity | | Raycaster | require("ntl:game/raycasting") | Wolf3D-style FPS renderer | | Tilemap | require("ntl:game/tilemap") | 2D grid, A* pathfinding, chunked rendering | | Terrain | require("ntl:game/terrain") | Procedural heightmap, multi-octave noise | | Animation | require("ntl:game/animation") | Skeletal, keyframe, blend trees | | Camera | require("ntl:game") | FPS, orbit, follow, cinematic modes | | Tweens | require("ntl:game/tweens") | Easing, chaining, parallel sequences | | Audio | require("ntl:game") | Positional audio, music, sound effects | | Input | require("ntl:game") | Keyboard, mouse, gamepad, touch | | Debug | require("ntl:game/debug") | FPS counter, profiler, wireframe overlay |

Resolution: default 1280×720 (720p), max 2560×1440 (2K). Set with game.run(MyGame, { width: 1920, height: 1080 }).


Real-World Systems

NTL handles real production workloads. Here are complete system patterns.

REST API with Auth and Database

val http   = require("ntl:http")
val db     = require("ntl:db")
val crypto = require("ntl:crypto")
val cache  = require("ntl:cache")

val database = db.connect("./app.db")

database.createTable("users", t => {
  t.id()
  t.text("email",    { unique: true })
  t.text("password_hash")
  t.text("role",     { default: "user" })
  t.timestamps()
})

val Users = database.model("users", { timestamps: true })

val router = new http.Router()

router.use(http.cors({ origin: ["https://myapp.com"] }))
router.use(http.rateLimit({ windowMs: 60000, max: 100 }))

async fn requireAuth(req, res, next) {
  val token = (req.headers["authorization"] || "").replace("Bearer ", "")
  if (!token) { return res.status(401).json({ error: "Unauthorized" }) }
  val payload = crypto.verifyJWT(token, process.env.JWT_SECRET)
  if (!payload) { return res.status(401).json({ error: "Invalid token" }) }
  req.user = payload
  next()
}

router.post("/auth/register", async (req, res) => {
  val { email, password } = req.body
  if (!email || !password) { return res.status(400).json({ error: "Email and password required" }) }
  val existing = Users.findBy("email", email)
  if (existing) { return res.status(409).json({ error: "Email already registered" }) }
  val hash = await crypto.hashPassword(password)
  val user = Users.create({ email, password_hash: hash, role: "user" })
  val token = crypto.signJWT({ id: user.id, email, role: user.role }, process.env.JWT_SECRET, "7d")
  res.status(201).json({ token, user: { id: user.id, email, role: user.role } })
})

router.post("/auth/login", async (req, res) => {
  val { email, password } = req.body
  val user = Users.findBy("email", email)
  if (!user) { return res.status(401).json({ error: "Invalid credentials" }) }
  val ok = await crypto.verifyPassword(password, user.password_hash)
  if (!ok)   { return res.status(401).json({ error: "Invalid credentials" }) }
  val token = crypto.signJWT({ id: user.id, email, role: user.role }, process.env.JWT_SECRET, "7d")
  res.json({ token })
})

router.get("/users/me", requireAuth, (req, res) => {
  val user = Users.find(req.user.id)
  res.json(user)
})

router.get("/users", requireAuth, (req, res) => {
  if (req.user.role !== "admin") { return res.status(403).json({ error: "Admin only" }) }
  val page  = parseInt(req.query.page  || "1")
  val limit = parseInt(req.query.limit || "20")
  val result = Users.paginate(page, limit)
  res.json(result)
})

http.listen(3000, router, () => log "API running at http://localhost:3000")

Real-Time Chat Server (WebSocket)

val http = require("ntl:http")
val ws   = require("ntl:ws")

val router  = new http.Router()
val wss     = new ws.WebSocketServer()
val rooms   = new Map()

router.get("/", (req, res) => {
  res.html("<h1>NTL Chat</h1><p>Connect via WebSocket on /ws</p>")
})

wss.attach(http.createServer(router))

wss.on("connection", (client) => {
  log "Client connected:", client.id

  client.on("message", (msg) => {
    if (!msg || !msg.type) { return }

    if (msg.type === "join") {
      wss.join(client, msg.room)
      client.room = msg.room
      client.name = msg.name || "Anonymous"
      wss.to(msg.room).sendJSON({ type: "system", text: client.name + " joined" })
    }

    if (msg.type === "chat" && client.room) {
      wss.to(client.room).sendJSON({
        type: "chat",
        name: client.name,
        text: msg.text,
        time: Date.now()
      })
    }
  })

  client.on("close", () => {
    if (client.room) {
      wss.leave(client, client.room)
      wss.to(client.room).sendJSON({ type: "system", text: client.name + " left" })
    }
  })
})

http.listen(3000, router, () => log "Chat server at http://localhost:3000")

Background Job Queue

val { EventEmitter } = require("ntl:events")
val db     = require("ntl:db")
val logger = require("ntl:logger")

val log = logger.createLogger("JOBS")

class JobQueue extends EventEmitter {
  constructor(name, options) {
    super()
    this.name      = name
    this._db       = db.connect("./jobs.db")
    this._interval = (options || {}).pollMs || 1000
    this._handlers = new Map()
    this._running  = false

    this._db.createTable("jobs_" + name, t => {
      t.id()
      t.text("jobType")
      t.text("payload",   { default: "{}" })
      t.text("status",    { default: "pending" })
      t.integer("attempts", { default: 0 })
      t.text("error",     { nullable: true })
      t.timestamps()
    })
  }

  handle(jobType, handlerFn) {
    this._handlers.set(jobType, handlerFn)
    return this
  }

  async enqueue(jobType, payload) {
    return this._db.table("jobs_" + this.name).insert({
      jobType,
      payload: JSON.stringify(payload || {})
    })
  }

  start() {
    if (this._running) { return }
    this._running = true
    this._poll()
    log.info("Queue started:", this.name)
    return this
  }

  stop() { this._running = false; return this }

  async _poll() {
    while (this._running) {
      val job = this._db.table("jobs_" + this.name).where("status", "=", "pending").orderBy("id").first()
      if (job) {
        await this._process(job)
      } else {
        await new Promise(res => setTimeout(res, this._interval))
      }
    }
  }

  async _process(job) {
    this._db.table("jobs_" + this.name).where("id", "=", job.id).update({ status: "running" })
    try {
      val handler = this._handlers.get(job.jobType)
      if (!handler) { throw new Error("No handler for job type: " + job.jobType) }
      await handler(JSON.parse(job.payload))
      this._db.table("jobs_" + this.name).where("id", "=", job.id).update({ status: "done" })
      this.emit("done", job)
      log.info("Job done:", job.jobType, job.id)
    } catch (err) {
      val attempts = job.attempts + 1
      val status   = attempts >= 3 ? "failed" : "pending"
      this._db.table("jobs_" + this.name).where("id", "=", job.id).update({
        status, attempts, error: err.message
      })
      this.emit("error", job, err)
      log.error("Job failed:", job.jobType, err.message)
    }
  }
}

val queue = new JobQueue("emails")

queue.handle("send-welcome", async (payload) => {
  val mail = require("ntl:mail")
  val smtp = new mail.SMTPClient({ host: "smtp.example.com", port: 587, user: "[email protected]", pass: process.env.SMTP_PASS })
  await smtp.send({ to: payload.email, subject: "Welcome!", html: "<h1>Welcome to our app!</h1>" })
})

queue.start()

// Enqueue a job from elsewhere:
// await queue.enqueue("send-welcome", { email: "[email protected]" })

CLI Tool

val fs     = require("ntl:fs")
val crypto = require("ntl:crypto")

val args    = process.argv.slice(2)
val command = args[0]

val COMMANDS = {
  init:    cmdInit,
  hash:    cmdHash,
  encrypt: cmdEncrypt,
  decrypt: cmdDecrypt,
  help:    cmdHelp
}

fn cmdInit() {
  val projectName = args[1] || "my-app"
  fs.mkdir("./" + projectName)
  fs.mkdir("./" + projectName + "/src")
  fs.writeJson("./" + projectName + "/ntl.json", {
    name:    projectName,
    version: "1.0.0",
    entry:   "src/main.ntl"
  })
  fs.write("./" + projectName + "/src/main.ntl", 'log "Hello from " + "' + projectName + '"')
  log "Created project:", projectName
}

fn cmdHash() {
  val input = args[1]
  if (!input) { log "Usage: mytool hash <input>"; process.exit(1) }
  log "SHA-256:", crypto.sha256(input)
  log "MD5:    ", crypto.md5(input)
}

async fn cmdEncrypt() {
  val file = args[1]; val key = args[2]
  if (!file || !key) { log "Usage: mytool encrypt <file> <key>"; process.exit(1) }
  val data = fs.read(file)
  val enc  = crypto.aesEncrypt(data, key)
  fs.write(file + ".enc", enc)
  log "Encrypted:", file + ".enc"
}

async fn cmdDecrypt() {
  val file = args[1]; val key = args[2]
  if (!file || !key) { log "Usage: mytool decrypt <file.enc> <key>"; process.exit(1) }
  val data = fs.read(file)
  val dec  = crypto.aesDecrypt(data, key)
  fs.write(file.replace(".enc", ".dec"), dec)
  log "Decrypted:", file.replace(".enc", ".dec")
}

fn cmdHelp() {
  log "Usage: mytool <command> [args]"
  log "Commands: init, hash, encrypt, decrypt, help"
}

val handler = COMMANDS[command]
if (handler) {
  val result = handler()
  if (result && typeof result.then === "function") {
    result.catch(e => { log "Error:", e.message; process.exit(1) })
  }
} else {
  log "Unknown command:", command
  cmdHelp()
  process.exit(1)
}

Built-in Modules

ntl:http

val http = require("ntl:http")
val {Router, cors, rateLimit, staticFiles, listen} = require("ntl:http")

Router

val router = new http.Router()

router.get("/path", handler)
router.post("/path", handler)
router.put("/path", handler)
router.delete("/path", handler)
router.patch("/path", handler)
router.all("/path", handler)

// URL params
router.get("/users/:id/posts/:postId", (req, res) => {
  log req.params.id, req.params.postId
})

// Chained middleware
router.post("/admin", verifyLogin, verifyAdmin, handleRequest)

router.onError((err, req, res) => {
  res.status(500).json({error: err.message})
})

router.notFound((req, res) => {
  res.status(404).json({error: "Not found"})
})

Request (req):

req.method         // "GET" | "POST" | ...
req.path           // "/users/1"
req.params         // { id: "1" }     (URL params)
req.query          // { page: "2" }   (query string)
req.body           // parsed JSON or string
req.headers        // { authorization: "Bearer ..." }
req.ip             // client IP
req.get("header")  // header by name (case-insensitive)
req.cookies        // { session: "abc" }

Response (res):

res.status(201).json({created: true})
res.send("text")
res.html("<h1>Hello</h1>")
res.redirect("/new-path")
res.redirect(301, "/permanent")
res.set("X-Custom", "value")
res.cookie("token", value, {httpOnly: true, secure: true, maxAge: 86400})
res.clearCookie("token")
res.download("/path/file.pdf")
res.sendFile("/path/index.html")
res.end()

Middleware:

router.use(http.cors({
  origin:      ["https://mysite.com"],
  credentials: true
}))

router.use(http.rateLimit({
  windowMs: 60000,
  max:      100,
  message:  "Too many requests"
}))

router.use(http.staticFiles("./public"))
router.use("/assets", http.staticFiles("./assets", {cache: 3600}))

Start server:

http.listen(3000, router, () => log "Running at http://localhost:3000")

http.listenHTTPS(443, router, {
  key:  fs.read("./ssl/key.pem"),
  cert: fs.read("./ssl/cert.pem")
})

val server = http.createServer(router)
server.listen(3000)

HTTP Client:

val res  = await http.fetch("https://api.github.com/users/octocat")
val res  = await http.get("https://api.example.com/data")
val res  = await http.post("https://api.example.com/users", {name: "Alice"})
val res  = await http.put("https://api.example.com/users/1", {name: "Alice"})
val res  = await http.delete("https://api.example.com/users/1")
val res  = await http.patch("https://api.example.com/users/1", {active: false})

// With options
val res  = await http.fetch("https://api.example.com/data", {
  method:  "POST",
  headers: {"Authorization": "Bearer {TOKEN}"},
  body:    {key: "value"},
  timeout: 5000
})
log res.status   // 200
log res.data     // parsed JSON
log res.headers

ntl:db

SQLite database with immutable query builder and ORM. Uses Node.js built-in node:sqlite — zero npm dependencies.

val {Database} = require("ntl:db")
val db = new Database("./app.db")     // file
val db = new Database(":memory:")     // in-memory

Create Tables:

db.createTable("users", (t) => {
  t.id()                                     // INTEGER PRIMARY KEY AUTOINCREMENT
  t.text("name")
  t.text("email", {unique: true})
  t.text("bio",   {nullable: true})
  t.text("role",  {default: "user"})
  t.integer("age")
  t.real("balance",   {default: 0.0})
  t.boolean("active", true)
  t.json("settings")
  t.timestamps()                             // created_at, updated_at
  t.softDelete()                             // deleted_at
  t.references("user_id", "users", "id")    // FK with CASCADE
  t.index("email")
  t.unique("email", "role")
})

Query Builder (every method returns a new instance — immutable):

val q = db.table("users")

// Read
q.all()
q.first()
q.find(id)                    // by primary key
q.findOrFail(id)

// Select columns
q.select("name", "email").all()
q.distinct().select("country").all()

// WHERE
q.where("active", 1).all()
q.where("age", ">", 18).all()
q.where("name", "LIKE", "Ali%").all()
q.whereIn("role", ["admin", "editor"]).all()
q.whereNotIn("status", ["banned"]).all()
q.whereBetween("age", 18, 65).all()
q.whereNull("deleted_at").all()
q.whereNotNull("email_verified_at").all()

// Ordering / Pagination
q.orderBy("name").all()
q.orderByDesc("created_at").all()
q.limit(10).offset(20).all()
q.paginate(1, 20)
// returns { data, total, page, perPage, totalPages, hasNext, hasPrev }

// Aggregations
q.count()
q.sum("balance")
q.avg("age")
q.min("age")
q.max("balance")
q.exists()
q.pluck("email")

// Joins
q.join("posts", "users.id", "=", "posts.user_id").all()
q.leftJoin("profiles", "users.id", "=", "profiles.user_id").all()

// Mutations
q.insert({name: "Alice", email: "[email protected]"})
q.insertMany([{name: "Bob"}, {name: "Carol"}])
q.where("id", 1).update({name: "Alice Smith"})
q.upsert({email: "[email protected]", name: "Alice"}, ["email"])
q.where("id", 5).delete()

Models (ORM):

val Users = db.model("users", {
  hidden:     ["password_hash"],
  timestamps: true,
  softDelete: true,
  casts: {
    active:   "boolean",
    balance:  "number",
    settings: "json",
    created:  "date"
  }
})

Users.all()
Users.find(id)
Users.findOrFail(id)
Users.findBy("email", "[email protected]")
Users.where("active", 1).all()
Users.create({name: "Alice", email: "[email protected]"})
Users.createMany([{name: "Bob"}, {name: "Carol"}])
Users.update(id, {name: "Alice Smith"})
Users.updateOrCreate({email: "[email protected]"}, {name: "Alice"})
Users.delete(id)
Users.restore(id)
Users.count()
Users.paginate(1, 20)

Transactions:

db.transaction(() => {
  db.table("accounts").where("id", 1).update({balance: 900})
  db.table("accounts").where("id", 2).update({balance: 1100})
})

await db.transaction(async () => {
  val id = db.table("orders").insert({user_id: 1, total: 99.90})
  for val item of cart { db.table("order_items").insert({order_id: id, ...item}) }
})

Migrations:

db.migration(1, () => {
  db.createTable("users", (t) => { t.id(); t.text("name"); t.timestamps() })
})
db.migration(2, () => {
  db.exec("ALTER TABLE users ADD COLUMN phone TEXT")
})

db.migrate()     // run pending
db.rollback()    // revert last

ntl:crypto

val crypto = require("ntl:crypto")

// Hashing
crypto.sha256("text")          // hex string
crypto.sha512("text")
crypto.sha1("text")
crypto.md5("text")

crypto.hmacSha256("key", "message")
crypto.hmacSha512("key", "message")

// Random
crypto.randomBytes(16)         // hex string (32 chars)
crypto.randomInt(0, 100)
crypto.uuid()                  // UUID v4

// AES encryption
val cipher = crypto.encryptAES("my secret", "password")
val plain  = crypto.decryptAES(cipher, "password")

// JWT
val token   = crypto.signJWT({userId: 42, role: "admin"}, "secret", 3600)
val payload = crypto.verifyJWT(token, "secret")
// payload = { userId: 42, role: "admin", iat: ..., exp: ... }

// Password hashing (PBKDF2 with random salt)
val hash = crypto.hashPassword("mypassword")
crypto.verifyPassword("mypassword", hash)   // true

// Base64
crypto.base64Encode("text")
crypto.base64Decode("dGV4dA==")
crypto.base64UrlEncode("data")
crypto.base64UrlDecode("ZGF0YQ")
crypto.constantTimeEqual("a", "a")          // timing-safe compare

ntl:env

val {Env} = require("ntl:env")
val env   = new Env({files: [".env", ".env.local"]})

env.str("NAME", "default")
env.int("PORT", 3000)
env.float("RATE", 0.05)
env.bool("DEBUG", false)
env.list("ORIGINS", ",")       // "a,b,c" → ["a","b","c"]
env.json("CONFIG_JSON")
env.url("API_URL")
env.require("DATABASE_URL")    // throws if missing
env.has("