patx/relay-lang
Update README.md
Commit 414626c · Harrison Erd · 2026-02-09T21:36:36-05:00
Comments
No comments yet.
Diff
diff --git a/README.md b/README.md
index 35c6c75..ae089a8 100644
--- a/README.md
+++ b/README.md
@@ -1,413 +1,1854 @@
-# Relay v0.1
+# Relay Programming Language
-**Relay** is a networking-first, async-by-default programming language with a simple, Python-like syntax and Node-style non-blocking semantics.
+**Version:** 0.1
+**License:** MIT
-There is **no `await` keyword**.
-Async work starts immediately and only blocks **when a value is actually needed**.
+> A networking-first, async-by-default programming language with Python-like syntax and Node.js-style non-blocking semantics.
-> Everything is async, but nothing waits unless it has to.
+Relay is designed for building high-performance web services, APIs, and network applications with minimal boilerplate. Everything is asynchronous by default, but there's **no `await` keyword**—async operations start immediately and only block when their values are actually needed.
----
-## Why Relay?
-Most languages make async:
-- verbose (`await` everywhere),
-- fragile (easy to accidentally block),
-- or bolted on later.
+## Table of Contents
-Relay flips the model:
+- [Features](#features)
+- [Quick Start](#quick-start)
+- [Installation](#installation)
+- [Language Guide](#language-guide)
+ - [Syntax Fundamentals](#syntax-fundamentals)
+ - [Data Types](#data-types)
+ - [Control Flow](#control-flow)
+ - [Functions](#functions)
+ - [Async Model](#async-model)
+- [API Reference](#api-reference)
+ - [Core Functions](#core-functions)
+ - [Async & Concurrency](#async--concurrency)
+ - [File System](#file-system)
+ - [HTTP Client](#http-client)
+ - [Web Server](#web-server)
+ - [Database (MongoDB)](#database-mongodb)
+- [Examples](#examples)
+- [How It Works](#how-it-works)
+- [Best Practices](#best-practices)
+- [Troubleshooting](#troubleshooting)
+- [Contributing](#contributing)
-- IO is **always non-blocking**
-- Async values resolve **implicitly**
-- Concurrency is the default, not an opt-in
-- Networking and web servers are built in
-Relay feels like:
-- Python syntax
-- Node.js runtime behavior
-- Rust-level correctness under the hood
----
+## Features
-## Quick start
+- **Implicit Async**: No `await` keyword needed—async operations resolve automatically when values are accessed
+- **Python-like Syntax**: Clean, indentation-based syntax that's easy to read and write
+- **Built-in Web Server**: Flask/FastAPI-style decorators with automatic routing
+- **HTTP Client**: Simple, async HTTP requests built-in
+- **MongoDB Integration**: Native async MongoDB support
+- **Session Management**: Built-in session handling with HttpOnly cookies
+- **Template Rendering**: Lightweight Jinja-style templating (`{{ variable }}`)
+- **Type Hints**: Optional runtime type checking for function parameters
+- **Concurrency Primitives**: `spawn`, `all`, `race`, `timeout`, and `cancel` for parallel execution
-Install the CLI locally and run a `.ry` file:
+
+
+## Quick Start
+
+### Installation
```bash
+# Clone the repository
+git clone https://github.com/yourusername/relay.git
+cd relay
+
+# Install the Relay CLI
cargo install --path .
-relay path/to/file.ry
+
+# Run a Relay program
+relay path/to/app.ry
```
-If you're iterating on the compiler, you can still run it directly:
+### Hello World
-```bash
-cargo run -- path/to/file.ry
+```relay
+print("Hello, Relay!")
+```
+
+### Async Hello World
+
+```relay
+sleep(2000, print("world"))
+print("hello")
+```
+
+**Output:**
+```
+hello
+world
```
-### Project layout
+Notice how "hello" prints immediately while "world" waits 2 seconds—all without blocking the main thread.
-Relay is currently a single-file interpreter:
+### Simple Web Server
-- `src/main.rs` implements the lexer, parser, evaluator, stdlib, and runtime.
-- `examples/` can hold `.ry` samples (see below).
+```relay
+app = WebApp()
+server = WebServer()
-### CLI usage
[email protected]("/")
+fn index()
+ return {"message": "Hello, Relay!"}
-```bash
-relay path/to/app.ry
+server.run(app)
```
-If you run a web server, you can override the bind address:
+Visit `http://localhost:3000/` to see your JSON response.
+
+
+
+## Installation
+
+### Prerequisites
+
+- Rust 1.70 or later
+- Cargo (included with Rust)
+
+### From Source
```bash
-RELAY_BIND=0.0.0.0:8080 relay path/to/server.ry
-```
+# Clone the repository
+git clone https://github.com/yourusername/relay.git
+cd relay
-Hello world with non-blocking order:
+# Build and install
+cargo install --path .
-```relay
-sleep(2000, print("world"))
-print("hello")
+# Verify installation
+relay --version
```
-Output:
+### Development Mode
+
+If you're developing the Relay compiler itself:
+```bash
+cargo run -- path/to/file.ry
```
-hello
-world
+
+### Environment Variables
+
+- `RELAY_BIND`: Override the default bind address for web servers (default: `127.0.0.1:3000`)
+
+Example:
+```bash
+RELAY_BIND=0.0.0.0:8080 relay server.ry
```
----
-## Language tour
-### Program structure
+## Language Guide
-Relay is indentation-based (4 spaces per level) and uses expression statements:
+### Syntax Fundamentals
+
+Relay uses **indentation-based syntax** with 4 spaces per indentation level. Tabs are not allowed.
+
+```relay
+fn example()
+ x = 10
+ if (x > 5)
+ print("x is greater than 5")
+ else
+ print("x is 5 or less")
+```
+
+**Key Rules:**
+- Indentation must be exactly 4 spaces per level
+- No tabs allowed
+- Function bodies, control flow blocks, and loops all require indentation
+- Comments start with `#` and continue to the end of the line
```relay
-fn main()
- print("hello")
- print("world")
+# This is a comment
+x = 42 # This is also a comment
```
-### Values, collections, and operators
+### Data Types
+
+#### Primitives
```relay
+# Integers
+age = 25
+count = -10
+
+# Floats
+pi = 3.14159
+temperature = -273.15
+
+# Strings
name = "Relay"
-version = 1
-pi = 3.1415
-is_async = True
+message = "Hello, world!"
+
+# Booleans
+is_active = True
+is_complete = False
+
+# None
+result = None
+```
+
+#### Collections
+
+**Lists:**
+```relay
+numbers = [1, 2, 3, 4, 5]
+mixed = [1, "two", 3.0, True]
+nested = [[1, 2], [3, 4]]
+
+# Access elements
+first = numbers[0] # 1
+last = numbers[4] # 5
+
+# Lists are mutable
+numbers[0] = 10
+```
+
+**Dictionaries:**
+```relay
+user = {"name": "Ada", "age": 30, "active": True}
-nums = [1, 2, 3]
-user = {"name": "Ada", "id": 42}
+# Access values
+name = user["name"] # "Ada"
+age = user["age"] # 30
-print(name + " v" + str(version))
-print(nums[0])
-print(user["name"])
+# Keys are always strings
+# Values can be any type
```
-Operators: `+ - * /`, comparisons (`== != < <= > >=`), and `not` for unary negation.
+**Important:** Dictionary keys are automatically stringified. `{1: "value"}` becomes `{"1": "value"}`.
+
+### Control Flow
-### Control flow
+#### If-Else
```relay
-if (version >= 1)
- print("stable")
+if (condition)
+ # then block
+ print("condition is true")
else
- print("experimental")
+ # else block
+ print("condition is false")
+```
+
+**Note:** The `else` block is optional.
+
+```relay
+if (x > 0)
+ print("positive")
+```
-sum = 0
-for (n in [1, 2, 3])
- sum = sum + n
+#### While Loops
+```relay
i = 0
-while (i < 3)
+while (i < 5)
print(i)
i = i + 1
```
-### Functions, defaults, and type hints
+Augmented assignment operators are supported:
+```relay
+i = 0
+while (i < 5)
+ print(i)
+ i =+ 1 # equivalent to i = i + 1
+```
+
+#### For Loops
+
+```relay
+# Iterate over lists
+for (item in [1, 2, 3, 4, 5])
+ print(item)
+
+# Iterate over dictionary keys
+user = {"name": "Ada", "age": 30}
+for (key in user)
+ print(key + ": " + str(user[key]))
+```
+
+**Note:** For loops iterate over collection elements or dictionary keys.
+
+### Functions
+
+#### Basic Functions
+
+```relay
+fn greet(name)
+ return "Hello, " + name
+
+message = greet("World")
+print(message) # Hello, World
+```
+
+#### Default Parameters
+
+```relay
+fn greet(name: str = "World")
+ return "Hello, " + name
+
+print(greet()) # Hello, World
+print(greet("Relay")) # Hello, Relay
+```
+
+#### Type Hints
+
+Relay supports runtime type checking for parameters:
+
+```relay
+fn add(a: int, b: int)
+ return a + b
+
+result = add(5, 10) # OK
+result = add("5", "10") # Type error!
+```
+
+**Supported Types:**
+- `str`: String
+- `int`: Integer
+- `float`: Float
+- `json` or `Json`: JSON object
+
+```relay
+fn process_data(data: json)
+ return data["key"]
+```
+
+#### Return Values
+
+Functions can return any value:
```relay
-fn greet(name: str = "world")
- return "hello " + name
+fn get_user()
+ return {"name": "Ada", "id": 1}
-print(greet())
-print(greet("relay"))
+fn calculate()
+ return 42
+
+fn do_work()
+ # No explicit return = returns None
+ print("Working...")
```
-Type hints are enforced at runtime for `str`, `int`, `float`, and `json`/`Json`.
+### Async Model
+
+Relay's async model is unique: **there is no `await` keyword**. Instead, async operations return `Deferred` values that automatically resolve when you try to use them.
-### Dictionaries and JSON
+#### Expression Statements Don't Block
-Relay dicts use stringified keys and can be serialized as JSON:
+When you call an async function without using its return value, it runs in the background:
```relay
-profile = {"name": "Ada", "lang": "Relay"}
-save_json(profile, "profile.json")
-loaded = read_json("profile.json")
-print(loaded["name"])
+sleep(1000, print("delayed"))
+print("immediate")
```
-### Error handling
+**Output:**
+```
+immediate
+delayed
+```
+
+#### Deferred Values
+
+When you assign the result of an async operation, you get a `Deferred` value. The operation starts immediately but doesn't block:
-Relay surfaces runtime errors with a message and location for syntax issues. Some examples:
+```relay
+x = sleep(1000, 10) # Returns immediately with Deferred<10>
+y = sleep(1000, 20) # Returns immediately with Deferred<20>
+print(x + y) # Waits for both, then prints 30
+```
-- Type errors: invalid operators on mismatched types.
-- Name errors: use of undefined variables.
-- Runtime errors: invalid IO or HTTP failures.
+Both sleeps start at the same time, so this takes ~1 second, not 2.
----
+#### How Deferred Resolution Works
-## Async model (no `await`)
+A `Deferred` value automatically resolves (waits for the async operation to complete) when:
-### Expression statements don't block
+1. **Used in an expression:**
+```relay
+result = deferred_value + 10
+```
+2. **Passed to a function:**
```relay
-sleep(1000, "done")
-print("started")
+print(deferred_value)
```
-### Deferred values resolve when needed
+3. **Returned from a function:**
+```relay
+fn get_data()
+ return http.get("https://api.example.com")
+```
+4. **Used in a comparison:**
```relay
-x = sleep(1000, 10)
-y = sleep(1000, 20)
-print(x + y)
+if (deferred_value > 10)
+ print("greater than 10")
```
-### Tasks and concurrency
+#### Concurrency Primitives
+**`spawn(expr)`** - Run an expression in parallel:
```relay
fn work(n)
sleep(500, n * 2)
-jobs = [spawn(work(2)), spawn(work(5))]
-results = all(jobs)
-print(results)
+task1 = spawn(work(5))
+task2 = spawn(work(10))
+result1 = task1.join() # Wait for completion
+result2 = task2.join()
+```
+
+**`all(tasks)`** - Wait for all tasks to complete:
+```relay
+tasks = [spawn(work(1)), spawn(work(2)), spawn(work(3))]
+results = all(tasks) # [2, 4, 6]
+```
+
+**`race(tasks)`** - Wait for the first task to complete:
+```relay
+tasks = [spawn(sleep(1000, "slow")), spawn(sleep(100, "fast"))]
+winner = race(tasks) # "fast"
+```
+
+**`timeout(expr, ms)`** - Add a timeout to any operation:
+```relay
+result = timeout(http.get("https://slow-api.com"), 5000)
+```
+
+**`cancel(task)`** - Cancel a running task:
+```relay
+task = spawn(long_operation())
+cancel(task)
```
-Other concurrency helpers:
-- `timeout(expr, ms)`
-- `race([..])`
-- `cancel(task)`
-- `task.join()` and `deferred.resolve()`
-### Tasks vs Deferred values
-- `sleep`, IO, and HTTP calls return `Deferred` values.
-- `spawn(expr)` returns a `Task`, which can be `join()`-ed.
-- Expression statements run without blocking: any returned `Deferred` or `Task` keeps running.
+## API Reference
-### Built-ins and type coercion
+### Core Functions
-Relay provides lightweight coercions via built-ins:
+#### `print(value, ...)`
+Print values to stdout. Multiple arguments are printed space-separated.
```relay
-print(str(123))
-print(int("42"))
-print(float(3))
+print("Hello") # Hello
+print("x =", 42) # x = 42
+print("a", "b", "c") # a b c
```
----
+#### `str(value)`
+Convert any value to a string.
+
+```relay
+str(123) # "123"
+str(3.14) # "3.14"
+str(True) # "True"
+str([1, 2, 3]) # "[1, 2, 3]"
+```
-## Files + JSON
+#### `int(value)`
+Convert a value to an integer.
```relay
-text = read_file("notes.txt")
-print(text)
+int("42") # 42
+int(3.9) # 3
+int(True) # 1
+int("invalid") # Runtime error
+```
-save_file("hello", "out.txt")
+#### `float(value)`
+Convert a value to a float.
-data = {"name": "Relay", "tags": ["async", "io"]}
-save_json(data, "data.json")
-parsed = read_json("data.json")
-print(parsed["name"])
+```relay
+float("3.14") # 3.14
+float(42) # 42.0
+float("2.5e3") # 2500.0
```
----
+### Async & Concurrency
-## HTTP client
+#### `sleep(milliseconds, value=None)`
+**Returns:** `Deferred<value>`
+
+Sleep for the specified duration, then resolve to `value`.
```relay
-http = Http()
-resp = http.get("https://example.com")
-print(resp.status)
-print(resp.text)
+sleep(1000, print("done")) # Print after 1 second
+result = sleep(2000, 42) # Wait 2s, result = 42
+```
+
+#### `spawn(expression)`
+**Returns:** `Task`
-api = http.post("https://httpbin.org/post", {"hello": "relay"})
-json = api.json()
-print(json["json"]["hello"])
+Execute an expression in parallel. Returns a `Task` object.
+
+```relay
+task = spawn(expensive_computation())
+# Do other work...
+result = task.join()
```
-`resp.json()` parses the response body to JSON.
+**Task Methods:**
+- `task.join()` - Wait for the task to complete and return its value
----
+#### `all(tasks)`
+**Returns:** List of results
-## MongoDB client
+Wait for all tasks to complete. Returns results in order.
```relay
-mongo = Mongo("mongodb://localhost:27017")
-db = mongo.db("relay_demo")
-users = db.collection("users")
+tasks = [spawn(work(1)), spawn(work(2)), spawn(work(3))]
+results = all(tasks) # Wait for all, returns [result1, result2, result3]
+```
-users.insert_one({"name": "Ada", "active": True})
-user = users.find_one({"name": "Ada"})
-print(user)
+#### `race(tasks)`
+**Returns:** First completed result
+
+Wait for the first task to complete, return its result.
+
+```relay
+tasks = [
+ spawn(http.get("https://api1.com")),
+ spawn(http.get("https://api2.com"))
+]
+fastest = race(tasks) # Returns whichever completes first
```
-MongoDB helpers return `Deferred` values, so they run asynchronously until the result is needed.
+#### `timeout(expression, milliseconds)`
+**Returns:** `Deferred<value>` or timeout error
+
+Add a timeout to any async operation.
+
+```relay
+result = timeout(http.get("https://slow.com"), 5000) # 5 second timeout
+```
----
+If the timeout is exceeded, a runtime error is raised.
-## Web server
+#### `cancel(task)`
+Cancel a running task.
```relay
-app = WebApp()
-server = WebServer()
+task = spawn(long_running_operation())
+cancel(task)
+```
[email protected]("/")
-fn index()
- return read_file("docs/index.html")
+### File System
[email protected]("/")
-fn create_paste(paste)
- id = "abc123"
- return app.redirect("/" + id)
+#### `read_file(path)`
+**Returns:** `Deferred<string>`
-server.run(app)
+Read a file's contents as a UTF-8 string.
+
+```relay
+content = read_file("data.txt")
+print(content)
```
-Notes:
-- Flask/FastAPI-like decorators: `@app.get`, `@app.post`, `@app.put`, `@app.patch`, `@app.delete`.
-- Path params use `<name>` syntax, e.g. `@app.get("/p/<pid>")`.
-- Handler params are auto-bound from query/body/path (path wins over body, body wins over query).
-- `app.redirect(url)` returns an HTTP 302 response with a `Location` header.
-- `request`, `cookies`, and `session` are injected into each handler scope.
-- Returning dict/list/json produces JSON; strings become text/html/plain automatically.
-- `session` persists via an HttpOnly `relay_sid` cookie.
+#### `save_file(content, path)`
+**Returns:** `Deferred<None>`
-### Custom responses
+Write a string to a file.
```relay
[email protected]("/status")
-fn status()
- return Response({"ok": True}, status=201, content_type="application/json")
+save_file("Hello, World!", "output.txt")
```
-Relay will infer `content_type` when possible; use `Response(...)` to force status codes/content types.
+#### `read_json(path)`
+**Returns:** `Deferred<dict>`
-### Pastebin-style example (Mongo + redirect)
+Read and parse a JSON file.
```relay
-app = WebApp()
-server = WebServer()
-mongo = Mongo("mongodb://localhost:27017")
-db = mongo.db("relay_demo")
-pastes = db.collection("pastes")
+data = read_json("config.json")
+print(data["api_key"])
+```
[email protected]("/")
-fn index()
- return read_file("docs/index.html")
+#### `save_json(data, path)`
+**Returns:** `Deferred<None>`
[email protected]("/<pid>")
-fn view_paste(pid)
- paste = pastes.find_one({"_id": pid})
- if (paste == None)
- return Response("not found", status=404)
- return str(paste["paste_content"])
+Serialize data to JSON and write to a file.
[email protected]("/")
-fn new_paste(paste)
- inserted = pastes.insert_one({"paste_content": paste})
- pid = str(inserted["inserted_id"])
- return app.redirect("/" + pid)
+```relay
+config = {"host": "localhost", "port": 8080}
+save_json(config, "config.json")
+```
-server.run(app)
+### HTTP Client
+
+#### `Http()`
+Create an HTTP client instance.
+
+```relay
+http = Http()
```
----
+#### `http.get(url, headers=None)`
+**Returns:** `Deferred<Response>`
-## Example apps
+Send a GET request.
-### 1) Basic JSON API
+```relay
+http = Http()
+resp = http.get("https://api.example.com/users")
+print(resp.status) # 200
+print(resp.text) # Response body as string
+```
+**With headers:**
```relay
-app = WebApp()
-server = WebServer()
+headers = {"Authorization": "Bearer token123"}
+resp = http.get("https://api.example.com/protected", headers)
+```
[email protected]("/health")
-fn health()
- return {"ok": True, "service": "relay"}
+#### `http.post(url, data=None, headers=None)`
+**Returns:** `Deferred<Response>`
-server.run(app)
+Send a POST request with JSON body.
+
+```relay
+http = Http()
+payload = {"name": "Ada", "email": "[email protected]"}
+resp = http.post("https://api.example.com/users", payload)
```
-### 2) Echo service with path params and query
+#### `http.put(url, data=None, headers=None)`
+**Returns:** `Deferred<Response>`
+
+Send a PUT request with JSON body.
```relay
-app = WebApp()
-server = WebServer()
+http = Http()
+update = {"status": "active"}
+resp = http.put("https://api.example.com/users/123", update)
+```
[email protected]("/echo/<name>")
-fn echo(name, greeting: str = "hello")
- return greeting + " " + name
+#### `http.patch(url, data=None, headers=None)`
+**Returns:** `Deferred<Response>`
-server.run(app)
+Send a PATCH request with JSON body.
+
+```relay
+http = Http()
+patch = {"email": "[email protected]"}
+resp = http.patch("https://api.example.com/users/123", patch)
```
-### 3) Background work with concurrency
+#### `http.delete(url, headers=None)`
+**Returns:** `Deferred<Response>`
-```relay
-fn work(n)
- sleep(500, n * 2)
+Send a DELETE request.
-jobs = [spawn(work(2)), spawn(work(5)), spawn(work(7))]
-result = all(jobs)
-print(result)
+```relay
+http = Http()
+resp = http.delete("https://api.example.com/users/123")
```
-### 4) HTTP client + JSON processing
+#### Response Object
+
+HTTP responses have the following properties:
+
+- `resp.status` - HTTP status code (int)
+- `resp.text` - Response body as string
+- `resp.json()` - Parse response body as JSON
+- `resp.headers` - Response headers (dict)
```relay
http = Http()
-resp = http.get("https://httpbin.org/json")
+resp = http.get("https://api.github.com/users/octocat")
+
+print(resp.status) # 200
+print(resp.headers["content-type"]) # application/json
data = resp.json()
-print(data["slideshow"])
+print(data["login"]) # octocat
+```
+
+### Web Server
+
+#### `WebApp()`
+Create a web application instance.
+
+```relay
+app = WebApp()
```
-### 5) Static HTML response with templating
+#### Route Decorators
+
+Define HTTP endpoints using decorators:
+
+- `@app.get(path)`
+- `@app.post(path)`
+- `@app.put(path)`
+- `@app.patch(path)`
+- `@app.delete(path)`
```relay
app = WebApp()
-server = WebServer()
-title = "Relay"
@app.get("/")
fn index()
- return "<h1>{{ title }}</h1>"
+ return {"message": "Welcome to Relay"}
-server.run(app)
[email protected]("/users")
+fn create_user(name: str, email: str)
+ return {"id": 123, "name": name, "email": email}
```
----
+#### Path Parameters
+
+Use `<name>` syntax to capture path segments:
-## Standard library at a glance
+```relay
[email protected]("/users/<user_id>")
+fn get_user(user_id)
+ return {"id": user_id, "name": "Ada"}
-**Core**: `print`, `str`, `int`, `float`
[email protected]("/posts/<post_id>/comments/<comment_id>")
+fn get_comment(post_id, comment_id)
+ return {"post": post_id, "comment": comment_id}
+```
-**Async + concurrency**: `sleep`, `timeout`, `spawn`, `cancel`, `all`, `race`
+#### Handler Parameters
-**Files + JSON**: `read_file`, `save_file`, `read_json`, `save_json`
+Handler parameters are automatically bound from:
+1. **Path parameters** (highest priority)
+2. **Request body** (JSON)
+3. **Query parameters** (lowest priority)
-**Web**: `WebApp`, `WebServer`, `Response`, `Http`
+```relay
+# GET /search?q=relay&limit=10
[email protected]("/search")
+fn search(q: str, limit: int = 20)
+ return {"query": q, "limit": limit}
+
+# POST /users with JSON body {"name": "Ada", "email": "[email protected]"}
[email protected]("/users")
+fn create_user(name: str, email: str)
+ return {"name": name, "email": email}
+
+# GET /users/123
[email protected]("/users/<user_id>")
+fn get_user(user_id)
+ return {"id": user_id}
+```
-**Database**: `Mongo`
+#### Type Hints in Handlers
----
+Use type hints to enforce parameter types and enable automatic coercion:
-## License
+```relay
[email protected]("/calculate")
+fn calculate(a: int, b: int)
+ return {"result": a + b}
+
+# POST /calculate with {"a": "5", "b": "10"}
+# Automatically converts strings to ints: {"result": 15}
+```
+
+**Supported types:**
+- `str` - String
+- `int` - Integer
+- `float` - Float
+- `json` or `Json` - Full JSON body (for POST/PUT/PATCH)
+
+#### Request Object
+
+Every handler has access to a `request` dictionary:
+
+```relay
[email protected]("/debug")
+fn debug_request()
+ print(request["method"]) # GET
+ print(request["path"]) # /debug
+ print(request["query"]) # Query parameters dict
+ print(request["headers"]) # Headers dict
+ print(request["cookies"]) # Cookies dict
+ return "OK"
+```
+
+**Request fields:**
+- `method` - HTTP method (string)
+- `path` - Request path (string)
+- `query` - Query parameters (dict)
+- `headers` - Request headers (dict)
+- `cookies` - Cookies (dict)
+- `json` - Parsed JSON body (if present)
+
+#### Cookies
+
+Access cookies via the `cookies` dict:
+
+```relay
[email protected]("/")
+fn index()
+ user_id = cookies["user_id"]
+ return "User ID: " + user_id
+```
+
+#### Sessions
+
+Relay provides built-in session management with HttpOnly cookies:
+
+```relay
[email protected]("/login")
+fn login(username: str)
+ session["user"] = username
+ session["logged_in"] = True
+ return "Logged in"
+
[email protected]("/profile")
+fn profile()
+ if (session["logged_in"] == True)
+ return "Welcome, " + session["user"]
+ else
+ return app.redirect("/login")
+```
+
+**Session features:**
+- Automatically persisted across requests
+- Stored server-side (not in cookies)
+- Uses HttpOnly `relay_sid` cookie
+- Session data is a dictionary that persists modifications
+
+#### Response Types
+
+Handlers can return various types:
+
+**JSON (automatic):**
+```relay
[email protected]("/api/user")
+fn get_user()
+ return {"name": "Ada", "id": 123} # Auto-serialized to JSON
+```
+
+**Plain text:**
+```relay
[email protected]("/")
+fn index()
+ return "Hello, World!" # Content-Type: text/plain
+```
+
+**HTML:**
+```relay
[email protected]("/")
+fn index()
+ return "<h1>Welcome</h1>" # Content-Type: text/html
+```
+
+**Custom Response:**
+```relay
[email protected]("/custom")
+fn custom()
+ return Response(
+ {"error": "Not found"},
+ status=404,
+ content_type="application/json"
+ )
+```
+
+**Redirect:**
+```relay
[email protected]("/old-path")
+fn old_endpoint()
+ return app.redirect("/new-path")
+```
+
+#### `Response(body, status=200, content_type=None)`
+Create a custom HTTP response.
+
+```relay
[email protected]("/xml")
+fn get_xml()
+ xml = "<root><item>data</item></root>"
+ return Response(xml, status=200, content_type="application/xml")
+```
+
+**Parameters:**
+- `body` - Response body (string, dict, list, or bytes)
+- `status` - HTTP status code (default: 200)
+- `content_type` - Content-Type header (auto-detected if not specified)
+
+#### `app.redirect(url)`
+**Returns:** `Response` with 302 status
+
+Create a redirect response.
+
+```relay
[email protected]("/submit")
+fn submit(data)
+ # Process data...
+ return app.redirect("/success")
+```
+
+#### Template Rendering
+
+Return strings with `{{ variable }}` syntax for simple templating:
+
+```relay
+name = "Relay"
+version = "0.1"
+
[email protected]("/")
+fn index()
+ return "<h1>{{ name }} v{{ version }}</h1>"
+ # Renders: <h1>Relay v0.1</h1>
+```
+
+Templates have access to all variables in the handler's scope.
+
+#### `WebServer()`
+Create a web server instance.
+
+```relay
+server = WebServer()
+```
+
+#### `server.run(app)`
+Start the web server.
+
+```relay
+app = WebApp()
+server = WebServer()
+
[email protected]("/")
+fn index()
+ return "Hello, World!"
+
+server.run(app) # Starts server on 127.0.0.1:3000
+```
+
+**Configuration:**
+- Default bind address: `127.0.0.1:3000`
+- Override with `RELAY_BIND` environment variable:
+ ```bash
+ RELAY_BIND=0.0.0.0:8080 relay server.ry
+ ```
+
+### Database (MongoDB)
+
+#### `Mongo(connection_string)`
+Create a MongoDB client.
+
+```relay
+mongo = Mongo("mongodb://localhost:27017")
+```
+
+**Connection string format:**
+```
+mongodb://[username:password@]host[:port][/database]
+```
+
+Examples:
+```relay
+# Local MongoDB
+mongo = Mongo("mongodb://localhost:27017")
+
+# MongoDB Atlas
+mongo = Mongo("mongodb+srv://user:[email protected]/")
+
+# With authentication
+mongo = Mongo("mongodb://admin:password@localhost:27017")
+```
+
+#### `mongo.db(database_name)`
+**Returns:** Database instance
+
+Access a database.
+
+```relay
+mongo = Mongo("mongodb://localhost:27017")
+db = mongo.db("my_app")
+```
+
+#### `db.collection(collection_name)`
+**Returns:** Collection instance
+
+Access a collection.
+
+```relay
+users = db.collection("users")
+posts = db.collection("posts")
+```
+
+#### `collection.insert_one(document)`
+**Returns:** `Deferred<dict>` with `inserted_id`
+
+Insert a single document.
+
+```relay
+users = db.collection("users")
+result = users.insert_one({"name": "Ada", "email": "[email protected]"})
+print(result["inserted_id"]) # ObjectId as string
+```
+
+#### `collection.insert_many(documents)`
+**Returns:** `Deferred<dict>` with `inserted_ids`
+
+Insert multiple documents.
+
+```relay
+users = db.collection("users")
+docs = [
+ {"name": "Ada", "email": "[email protected]"},
+ {"name": "Grace", "email": "[email protected]"}
+]
+result = users.insert_many(docs)
+print(result["inserted_ids"]) # List of ObjectIds
+```
+
+#### `collection.find_one(filter)`
+**Returns:** `Deferred<dict>` or `None`
+
+Find a single document matching the filter.
+
+```relay
+users = db.collection("users")
+user = users.find_one({"email": "[email protected]"})
+if (user != None)
+ print(user["name"])
+```
+
+**Filter examples:**
+```relay
+# Exact match
+user = users.find_one({"name": "Ada"})
+
+# Multiple conditions (implicit AND)
+user = users.find_one({"name": "Ada", "active": True})
+
+# By ObjectId
+user = users.find_one({"_id": "507f1f77bcf86cd799439011"})
+```
+
+#### `collection.find(filter)`
+**Returns:** `Deferred<list>` of documents
+
+Find all documents matching the filter.
+
+```relay
+users = db.collection("users")
+active_users = users.find({"active": True})
+for (user in active_users)
+ print(user["name"])
+```
+
+**Find all documents:**
+```relay
+all_users = users.find({})
+```
+
+#### `collection.update_one(filter, update)`
+**Returns:** `Deferred<dict>` with `matched_count` and `modified_count`
+
+Update a single document.
+
+```relay
+users = db.collection("users")
+result = users.update_one(
+ {"email": "[email protected]"},
+ {"$set": {"active": True}}
+)
+print(result["modified_count"]) # 1
+```
+
+**Update operators:**
+```relay
+# Set fields
+users.update_one({"_id": id}, {"$set": {"status": "active"}})
+
+# Increment
+users.update_one({"_id": id}, {"$inc": {"login_count": 1}})
+
+# Unset fields
+users.update_one({"_id": id}, {"$unset": {"temp_field": ""}})
+```
+
+#### `collection.update_many(filter, update)`
+**Returns:** `Deferred<dict>` with `matched_count` and `modified_count`
+
+Update multiple documents.
+
+```relay
+users = db.collection("users")
+result = users.update_many(
+ {"active": False},
+ {"$set": {"status": "inactive"}}
+)
+print(result["modified_count"])
+```
+
+#### `collection.delete_one(filter)`
+**Returns:** `Deferred<dict>` with `deleted_count`
+
+Delete a single document.
+
+```relay
+users = db.collection("users")
+result = users.delete_one({"email": "[email protected]"})
+print(result["deleted_count"]) # 1 or 0
+```
+
+#### `collection.delete_many(filter)`
+**Returns:** `Deferred<dict>` with `deleted_count`
+
+Delete multiple documents.
+
+```relay
+users = db.collection("users")
+result = users.delete_many({"active": False})
+print(result["deleted_count"]) # Number of deleted documents
+```
+
+
+
+## Examples
+
+### 1. Hello World (Async)
+
+```relay
+sleep(2000, print("world"))
+print("hello")
+```
+
+**Output:**
+```
+hello
+world
+```
+
+### 2. Simple Web API
+
+```relay
+app = WebApp()
+server = WebServer()
+
[email protected]("/health")
+fn health()
+ return {"status": "ok", "service": "relay-api"}
+
[email protected]("/users/<user_id>")
+fn get_user(user_id)
+ return {"id": user_id, "name": "Ada Lovelace"}
+
+server.run(app)
+```
+
+### 3. Pastebin Service
+
+```relay
+app = WebApp()
+server = WebServer()
+mongo = Mongo("mongodb://localhost:27017")
+db = mongo.db("pastebin")
+pastes = db.collection("pastes")
+
[email protected]("/")
+fn index()
+ return read_file("static/index.html")
+
[email protected]("/")
+fn create_paste(content: str)
+ result = pastes.insert_one({"content": content})
+ paste_id = str(result["inserted_id"])
+ return app.redirect("/" + paste_id)
+
[email protected]("/<paste_id>")
+fn view_paste(paste_id)
+ paste = pastes.find_one({"_id": paste_id})
+ if (paste == None)
+ return Response("Not found", status=404)
+ return paste["content"]
+
+server.run(app)
+```
+
+### 4. Concurrent HTTP Requests
+
+```relay
+http = Http()
+
+fn fetch_user(user_id)
+ resp = http.get("https://api.example.com/users/" + str(user_id))
+ return resp.json()
+
+# Fetch 5 users concurrently
+tasks = []
+i = 1
+while (i <= 5)
+ tasks =+ [spawn(fetch_user(i))]
+ i =+ 1
+
+users = all(tasks)
+for (user in users)
+ print(user["name"])
+```
+
+### 5. File Processing Pipeline
+
+```relay
+fn process_file(filename)
+ content = read_file(filename)
+ lines = len(content.split("\n"))
+ return {"file": filename, "lines": lines}
+
+files = ["data1.txt", "data2.txt", "data3.txt"]
+tasks = []
+for (f in files)
+ tasks =+ [spawn(process_file(f))]
+
+results = all(tasks)
+save_json(results, "report.json")
+print("Processing complete!")
+```
+
+### 6. Session-based Authentication
+
+```relay
+app = WebApp()
+server = WebServer()
+mongo = Mongo("mongodb://localhost:27017")
+db = mongo.db("auth_demo")
+users = db.collection("users")
+
[email protected]("/")
+fn index()
+ if (session["authenticated"] == True)
+ return "Welcome, " + session["username"]
+ return app.redirect("/login")
+
[email protected]("/login")
+fn login(username: str, password: str)
+ user = users.find_one({"username": username})
+ if (user == None)
+ return Response("Invalid credentials", status=401)
+
+ # In production, use proper password hashing!
+ if (user["password"] == password)
+ session["authenticated"] = True
+ session["username"] = username
+ return app.redirect("/")
+
+ return Response("Invalid credentials", status=401)
+
[email protected]("/logout")
+fn logout()
+ session["authenticated"] = False
+ session["username"] = None
+ return app.redirect("/login")
+
+server.run(app)
+```
+
+### 7. REST API with MongoDB
+
+```relay
+app = WebApp()
+server = WebServer()
+mongo = Mongo("mongodb://localhost:27017")
+db = mongo.db("blog")
+posts = db.collection("posts")
+
[email protected]("/posts")
+fn list_posts()
+ all_posts = posts.find({})
+ return all_posts
+
[email protected]("/posts")
+fn create_post(title: str, content: str, author: str)
+ result = posts.insert_one({
+ "title": title,
+ "content": content,
+ "author": author
+ })
+ return {"id": str(result["inserted_id"])}
+
[email protected]("/posts/<post_id>")
+fn get_post(post_id)
+ post = posts.find_one({"_id": post_id})
+ if (post == None)
+ return Response("Post not found", status=404)
+ return post
+
[email protected]("/posts/<post_id>")
+fn update_post(post_id, title: str, content: str)
+ result = posts.update_one(
+ {"_id": post_id},
+ {"$set": {"title": title, "content": content}}
+ )
+ if (result["matched_count"] == 0)
+ return Response("Post not found", status=404)
+ return {"updated": True}
+
[email protected]("/posts/<post_id>")
+fn delete_post(post_id)
+ result = posts.delete_one({"_id": post_id})
+ if (result["deleted_count"] == 0)
+ return Response("Post not found", status=404)
+ return {"deleted": True}
+
+server.run(app)
+```
+
+### 8. Timeout and Error Handling
+
+```relay
+http = Http()
+
+fn fetch_with_timeout(url)
+ return timeout(http.get(url), 5000)
+
+# Try to fetch with 5 second timeout
+result = fetch_with_timeout("https://slow-api.com/data")
+print(result.text)
+```
+
+### 9. Race Condition Example
+
+```relay
+http = Http()
+
+# Fetch from multiple mirrors, use whichever responds first
+mirrors = [
+ "https://mirror1.example.com/data",
+ "https://mirror2.example.com/data",
+ "https://mirror3.example.com/data"
+]
+
+tasks = []
+for (url in mirrors)
+ tasks =+ [spawn(http.get(url))]
+
+fastest = race(tasks)
+print("Fastest mirror returned:", fastest.text)
+```
+
+### 10. Background Task Processing
+
+```relay
+fn process_item(item)
+ sleep(1000, print("Processed: " + str(item)))
+
+items = [1, 2, 3, 4, 5]
+
+# Spawn all tasks without waiting
+for (item in items)
+ spawn(process_item(item))
+
+print("All tasks started, continuing...")
+# Tasks run in background
+```
+
+
+
+## How It Works
+
+### Architecture Overview
+
+Relay is built on:
+- **Rust**: The interpreter is written in Rust for performance and safety
+- **Tokio**: Async runtime for non-blocking I/O
+- **Axum**: High-performance web framework for the built-in server
+- **MongoDB driver**: Native async MongoDB support
+
+### Compilation Pipeline
+
+1. **Lexer**: Tokenizes source code with indentation-aware parsing
+2. **Parser**: Builds an Abstract Syntax Tree (AST)
+3. **Evaluator**: Interprets the AST with async/await translation
+
+### The Async Model in Detail
+
+#### Deferred Values
+
+When you call an async function, Relay immediately starts the operation and returns a `Deferred` value:
+
+```relay
+# This starts the HTTP request immediately
+response = http.get("https://api.example.com")
+# response is Deferred<Response>
+
+# The request is already in-flight here
+print("Request started")
+
+# Only when we access response.status does it wait
+print(response.status) # <-- Blocks here if not complete
+```
+
+#### Auto-Resolution
+
+`Deferred` values automatically resolve when:
+
+1. **Used in operations:**
+```relay
+x = sleep(1000, 10)
+y = x + 5 # Waits for x to resolve
+```
+
+2. **Passed to functions:**
+```relay
+result = sleep(1000, 42)
+print(result) # Waits before printing
+```
+
+3. **Used in control flow:**
+```relay
+data = http.get("https://api.example.com")
+if (data.status == 200) # Waits before comparison
+ print("Success")
+```
+
+4. **Indexed:**
+```relay
+resp = http.get("https://api.example.com")
+json_data = resp.json()
+print(json_data["key"]) # Waits for json() before indexing
+```
+
+#### Expression Statements
+
+Expression statements (expressions not assigned to variables) run without blocking:
+
+```relay
+# This starts the sleep but doesn't wait
+sleep(1000, print("delayed"))
+
+# This prints immediately
+print("immediate")
+
+# Output:
+# immediate
+# delayed (after 1 second)
+```
+
+### Concurrency Model
+
+Relay uses Tokio's work-stealing scheduler to run tasks concurrently:
+
+```relay
+# Start 3 HTTP requests concurrently
+task1 = spawn(http.get("https://api1.com"))
+task2 = spawn(http.get("https://api2.com"))
+task3 = spawn(http.get("https://api3.com"))
+
+# Wait for all to complete
+results = all([task1, task2, task3])
+```
+
+All three requests run in parallel, completing in the time of the slowest request (not 3× the time).
+
+### Web Server Architecture
+
+The web server uses Axum's routing system:
+
+1. **Route Registration**: Decorators like `@app.get("/path")` register handlers
+2. **Request Handling**: Incoming requests are matched against registered routes
+3. **Parameter Binding**: Path/query/body parameters are extracted and bound to handler parameters
+4. **Type Coercion**: Type hints trigger automatic type conversion
+5. **Response Generation**: Return values are automatically serialized to appropriate content types
+
+### Session Storage
+
+Sessions are stored server-side in an in-memory hash map:
+- Session ID is generated using UUID
+- `relay_sid` cookie stores the session ID (HttpOnly, SameSite=Lax)
+- Session data persists across requests for the same session ID
+- Sessions are stored in memory (cleared on server restart)
+
+**Note:** In production, you'd want to persist sessions to a database.
+
+### MongoDB Integration
+
+MongoDB operations return `Deferred` values that resolve when the database operation completes:
+
+```relay
+# This starts the query immediately
+users = collection.find({"active": True})
+
+# The query is running in the background here
+print("Query started")
+
+# Only when we iterate do we wait for results
+for (user in users) # <-- Blocks here
+ print(user["name"])
+```
+
+
+
+## Best Practices
+
+### 1. Leverage Concurrent Execution
+
+Instead of:
+```relay
+# Sequential (slow)
+result1 = http.get("https://api1.com")
+result2 = http.get("https://api2.com")
+result3 = http.get("https://api3.com")
+```
+
+Do:
+```relay
+# Concurrent (fast)
+tasks = [
+ spawn(http.get("https://api1.com")),
+ spawn(http.get("https://api2.com")),
+ spawn(http.get("https://api3.com"))
+]
+results = all(tasks)
+```
+
+### 2. Use Type Hints for API Handlers
+
+Type hints provide automatic validation and coercion:
+
+```relay
[email protected]("/calculate")
+fn calculate(a: int, b: int, operation: str = "add")
+ if (operation == "add")
+ return {"result": a + b}
+ else
+ return {"result": a - b}
+```
+
+### 3. Handle Missing Data Gracefully
+
+Always check for `None` when querying databases or processing optional parameters:
+
+```relay
[email protected]("/users/<user_id>")
+fn get_user(user_id)
+ user = users.find_one({"_id": user_id})
+ if (user == None)
+ return Response("User not found", status=404)
+ return user
+```
+
+### 4. Use Sessions for State Management
+
+Don't try to maintain state in global variables. Use sessions:
+
+```relay
+# Bad
+current_user = None
+
[email protected]("/login")
+fn login(username)
+ current_user = username # Won't work across requests
+
+# Good
[email protected]("/login")
+fn login(username)
+ session["user"] = username
+```
+
+### 5. Implement Timeouts for External Calls
+
+Always add timeouts to external HTTP requests:
+
+```relay
+fn fetch_data(url)
+ return timeout(http.get(url), 10000) # 10 second timeout
+```
+
+### 6. Structure Large Applications
+
+Split handlers into logical groups:
+
+```relay
+app = WebApp()
+server = WebServer()
+
+# Auth routes
[email protected]("/auth/login")
+fn login(username, password)
+ # ...
+
[email protected]("/auth/logout")
+fn logout()
+ # ...
+
+# User routes
[email protected]("/users/<user_id>")
+fn get_user(user_id)
+ # ...
+
[email protected]("/users")
+fn create_user(name, email)
+ # ...
+
+# Post routes
[email protected]("/posts")
+fn list_posts()
+ # ...
+
+server.run(app)
+```
+
+### 7. Use Augmented Assignment
+
+For cleaner counter increments:
+
+```relay
+# Instead of
+i = i + 1
+
+# Use
+i =+ 1
+```
+
+### 8. Return Early for Error Cases
+
+Structure handlers with early returns for error cases:
+
+```relay
[email protected]("/posts/<post_id>")
+fn get_post(post_id)
+ post = posts.find_one({"_id": post_id})
+ if (post == None)
+ return Response("Not found", status=404)
+
+ if (post["published"] == False)
+ return Response("Not published", status=403)
+
+ return post
+```
+
+
+
+## Troubleshooting
+
+### Common Errors
+
+#### "Indentation must be 4 spaces per level"
+
+**Cause:** Relay requires exactly 4 spaces per indentation level.
+
+**Fix:** Ensure all indentation uses 4 spaces (not tabs, not 2 spaces).
+
+```relay
+# Wrong
+fn example()
+ print("hello") # 2 spaces
+
+# Right
+fn example()
+ print("hello") # 4 spaces
+```
+
+#### "Tabs are not allowed (spaces only)"
+
+**Cause:** Relay does not support tabs for indentation.
+
+**Fix:** Configure your editor to use spaces instead of tabs.
+
+#### "Type error: Cannot add int and str"
+
+**Cause:** Attempting to use incompatible types in an operation.
+
+**Fix:** Use explicit type conversion:
+
+```relay
+# Wrong
+x = 10 + "5"
+
+# Right
+x = 10 + int("5")
+```
+
+#### "Name error: Undefined variable 'x'"
+
+**Cause:** Using a variable before it's defined.
+
+**Fix:** Ensure variables are assigned before use:
+
+```relay
+# Wrong
+print(x)
+x = 10
+
+# Right
+x = 10
+print(x)
+```
+
+#### "Runtime error: Index out of bounds"
+
+**Cause:** Accessing a list index that doesn't exist.
+
+**Fix:** Check list length before accessing:
+
+```relay
+items = [1, 2, 3]
+if (len(items) > 5)
+ print(items[5])
+```
+
+### Debugging Tips
+
+1. **Use print statements:** Relay's simplest debugging tool
+```relay
+fn process_data(data)
+ print("Processing:", data) # Debug output
+ result = transform(data)
+ print("Result:", result) # Debug output
+ return result
+```
+
+2. **Check async resolution:** If something seems to hang, check if you're waiting for a `Deferred` value
+```relay
+# This might hang if the HTTP request never completes
+result = http.get("https://unreachable.com")
+print(result.status)
+
+# Add a timeout:
+result = timeout(http.get("https://unreachable.com"), 5000)
+```
+
+3. **Verify MongoDB connection:** Test your connection string in the MongoDB shell first
+
+4. **Check file paths:** File operations use paths relative to where you run the `relay` command
+
+5. **Inspect request objects:** Log the request object to debug handler issues
+```relay
[email protected]("/debug")
+fn debug()
+ print(request)
+ return "OK"
+```
+
+### Performance Tips
+
+1. **Batch database operations:** Use `insert_many` instead of multiple `insert_one` calls
+```relay
+# Slow
+for (item in items)
+ collection.insert_one(item)
+
+# Fast
+collection.insert_many(items)
+```
+
+2. **Use `spawn` for I/O-heavy tasks:** Parallelize independent operations
+```relay
+# Serial: 5 seconds total
+sleep(1000, "a")
+sleep(1000, "b")
+sleep(1000, "c")
+sleep(1000, "d")
+sleep(1000, "e")
+
+# Parallel: 1 second total
+all([
+ spawn(sleep(1000, "a")),
+ spawn(sleep(1000, "b")),
+ spawn(sleep(1000, "c")),
+ spawn(sleep(1000, "d")),
+ spawn(sleep(1000, "e"))
+])
+```
+
+3. **Minimize synchronous operations:** Keep handlers fast to avoid blocking the event loop
+
+## Contributing
+
+Contributions are welcome! Here's how to get started:
+
+### Development Setup
+
+```bash
+# Clone the repo
+git clone https://github.com/yourusername/relay.git
+cd relay
+
+# Build in debug mode
+cargo build
+
+# Run tests
+cargo test
+
+# Run with examples
+cargo run -- examples/hello.ry
+```
+
+### Adding Features
+
+1. **Lexer changes:** Modify the `Lexer` struct and `tokenize()` method
+2. **Parser changes:** Update the `Parser` struct and AST types
+3. **Runtime changes:** Modify the `Evaluator` and `install_stdlib()` function
+4. **Testing:** Add example programs to `examples/`
+
+### Coding Standards
+
+- Follow Rust conventions and `rustfmt` formatting
+- Add comments for complex logic
+- Keep the single-file architecture for now (v0.1)
+- Update this README for any user-facing changes
+
+### Reporting Issues
+
+Found a bug? [Open an issue](https://github.com/patx/relay-lang/issues) with:
+- Relay version
+- Operating system
+- Minimal reproducible example
+- Expected vs. actual behavior
+
+
+## Roadmap
+
+**v0.2 (Planned):**
+- [ ] Multiple file support and imports
+- [ ] List comprehensions
+- [ ] Destructuring assignment
+- [ ] Error handling with try/except
+- [ ] WebSocket support
+- [ ] Static file serving
+- [ ] Middleware support
+
+**v0.3 (Future):**
+- [ ] Package manager
+- [ ] Standard library expansion
+- [ ] SQL database support (PostgreSQL, SQLite)
+- [ ] Redis integration
+- [ ] GraphQL support
+
+
+## License
+
+MIT License
+
+Copyright (c) 2026 Harrison Erd
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+
+## Support
-MIT
+- **Documentation:** This README
+- **Examples:** See `examples/` directory
+- **Issues:** [GitHub Issues](https://github.com/patx/relay-lang/issues)
+- **Discussions:** [GitHub Discussions](https://github.com/patx/relay-lang/discussions)