# Spark

> Spark is a backend-focused programming language with a batteries-included
> standard library (31 namespaces, 310 documented methods): SQLite and
> optional PostgreSQL, an HTTP server, HTML templating, auth, caching, and
> multi-provider LLM access — all reachable without installing a package.
> Discord support is a package (`spark add discord`), not part of the runtime.
> It ships as a single CLI (`spark`) plus a VS Code extension that gets its
> IntelliSense from the same generated spec the interpreter uses, so the
> editor and the runtime can't drift apart.

This file is the single LLM-oriented reference for Spark. It distinguishes
**available now** behavior from **planned** behavior so an assistant must not
invent APIs or claim a security guarantee that Spark has not implemented.
The standard-library and syntax portions are generated from `spark-spec.json`.

## Current capability summary

Spark currently includes: a source interpreter, a bytecode VM/compiler and
validated `.sbc` artifacts; strict static checking; formatter; REPL; tests;
content-addressed builds; path/private package dependencies with lockfiles and
offline cache; SQLite and optional PostgreSQL connectors; HTTP routes,
middleware, CORS, static assets, auth, metrics, request IDs, and health
checks; VS Code language-server/debugger support; VM profiling; first-party
learning materials; and signed-release/offline-capsule build tooling.

Use `spark help` as the authority for installed CLI commands. Use
`spark-spec.json` as the authority for namespace methods. Do not assume a
public package registry, a cloud service, or an internet connection exists.

## Status vocabulary for assistants

- **Implemented** means code in this repository supports it now.
- **Optional** means it needs an external local dependency/configuration, such
  as a PostgreSQL driver/server or an LLM provider key.
- **Planned** means it is deliberately not valid Spark API yet. Never emit it
  as runnable code without marking it as proposed syntax.

## Module execution and import authority — implemented

Spark separates loading declarations from authorizing startup behavior.

```spark
-- helpers.spk: declarations are inert until a caller invokes them.
export func double(value<Int>) -> Int executes:
    returns value * 2;
end;

-- main.spk
import "helpers.spk";
term::print(double(21));
```

`import "file.spk";` loads exported declarations without deliberately running
the module's top-level startup flow. `use "file.spk";` is for a module that
must participate in startup; a used module must end in one explicit
declaration:

```spark
runs start();
```

The currently implemented `runs` form names one function. Proposed ordered
multiple startup functions are **not implemented** yet; do not emit
`runs [a(), b()];` as working code. `exclude term;` (or another builtin
namespace) removes that namespace from the remainder of the file only.

An imported exported property must be a literal (including literal List/Map).
Computed exported values are rejected at import time; expose them through an
exported function instead. This prevents an import from evaluating a hidden
initializer such as an environment, file, process, or network call.

This model reduces accidental startup execution, but it is not a complete
malware defense. Treat every local source file and dependency as untrusted
until reviewed and integrity-verified.

## Security and sealed distribution — implemented foundation

Spark has package/artifact hashes, safe static-file containment, lockfile
integrity checks, release checksum/signature tooling, and `spark seal` /
`spark verify` today. The current seal format embeds the resolved source graph
and VM bytecode, verifies every member, rejects unexpected files, and runs
without consulting loose imports. Ed25519 signatures are supported through
`spark keygen`, `spark seal --sign private.key`, and
`spark verify --public-key public.key`. The language API is:

```spark
sec::seal({
    entry: "app.spk",
    output: "dist/app.sparkpkg",
    sign_with: "release-key",
    include_imports: true,
    strip_source: true
});
```

Source stripping is supported, but it raises reverse-engineering cost rather
than making code impossible to inspect. The sealed runtime already executes only the complete embedded
verified graph and ignores loose neighboring modules. Any altered artifact
must fail closed; it must never run a mixture of verified and modified code.

`sec::gen_obf()` builds a source-stripped sealed bundle; it does not promise
impossible deobfuscation. Executable code can be inspected. Future obfuscation
may add internal-symbol renaming as IP friction, but signatures and verified
manifests—not obfuscation—provide integrity. A local
tamper-history file alone is also not durable against an attacker who controls
the filesystem; durable history needs a separately protected or remote
append-only log.

## Install and offline distribution — implemented

`bash setup.sh` installs a downloaded source distribution locally. For a
network-free transfer, `spark-offline-setup.sh` is a self-contained shell
capsule holding the Spark source tree as compressed Base64 plus SHA-256. It
reconstructs and verifies the files locally; it makes no network request.
Spark itself requires Python 3.11+.

Run a file with `spark run file.spk`, or just `spark file.spk`. Every
statement ends with `;`. Comments start with `--` and run to end of line.

```spark
property set(const) name: "Spark";

func greet(who<String>, greeting<String>:"Hello") -> String executes:
    returns `${greeting}, ${who}!`;
end;

term::print(greet(name));
```

---

# Language

## Values are properties

```spark
property set(const) license: "LIC-1234";   -- never changes
property set(var) score: 0;                -- can change
property set(list) names: ["a", "b"];
property set(map) user: { name: "Corbin" };
```

The word in parens is the **qualifier** — it says what kind of property this
is. It is NOT a type slot: `property set(const<String>) x: "hi";` is
invalid syntax (a common mistake) — qualifiers never take a `<Type>`
annotation; that only exists on function parameters.

| Qualifier | Meaning |
|---|---|
| `const` | Create + Read only. Cannot be updated or deleted, ever. |
| `var` | Can be updated with `property update`, `:=`, or `+=`/`-=`/`*=`/`/=`. |
| `list` | An ordered collection: `[1, 2, 3]`. |
| `map` | Key-value pairs: `{ name: "Corbin" }`. |
| `define` | Define a reusable type, like TypeScript's `type` (see below). |

`const` is permanent — nothing can change or delete it, not `property
update`, not `:=`, not anything:

```spark
property set(const) license: "LIC-1234";
license := "hacked";
-- error: cannot quickset const property 'license'
--   help: const is Create + Read only. Use `var` if it needs to change.
```

Changing a `var` — three equivalent forms:

```spark
property set(var) count: 0;
property update(var) count: 5;   -- the long form
count := 10;                     -- quickset (most common)
count += 1;                      -- compound assignment
```

## Text

```spark
property set(const) name: "Lily";
term::print("Hello, " + name + "!");                       -- concatenation
term::print(`Hello ${name}, you scored ${95} points`);      -- template string
term::print(`Double is ${95 * 2}`);                          -- any expression inside ${...}
```

String prefixes:

| Form | Name | What it does |
|---|---|---|
| `"..."` | Plain | Escapes like `\n` and `\t` are processed. |
| `` `...` `` | Template | Interpolates `${...}`. |
| `v"..."` | Variable | Interpolates `${...}`, same as a template — handy when backticks are awkward to type. |
| `r"..."` | Raw | No escape processing, backslashes stay literal. Use for regex and Windows paths. |

```spark
term::print(regex::match(r"\bcat\b", "the cat sat"));   -- true
term::print(regex::match("\bcat\b", "the cat sat"));    -- false! \b became a control char
```

## Numbers

```spark
term::print(2 + 3 * 4);      -- 14, multiplication first
term::print((2 + 3) * 4);    -- 20, parens win
term::print(2 ^ 10);         -- 1024
term::print(17 % 5);         -- 2, remainder
```

You don't need `math::` for basic arithmetic — plain operators work.

## Lists

```spark
property set(list) names: ["Corbin", "Eva", "Aria"];
term::print(names[0]);              -- Corbin
term::print(names[-1]);             -- Aria, counting from the end
term::print(list::length(names));   -- 3
```

## Maps and field access

```spark
property set(map) user: { name: "Corbin", score: 100 };
term::print(user->name);    -- Corbin, errors if the field is missing (catches typos)
term::print(user:>email);   -- null if missing, never errors
term::print(user:>email ?? "none given");   -- ?? supplies a fallback for null
property set(var) city: user:>address:>city ?? "unknown";   -- chain safely through nested data
```

`->` (strict) vs `:>` (safe) is a general rule, not just for maps: `::`
calls a method on a namespace, and the result is then navigated/called with
`->` or `:>`, chaining freely: `namespace::method()->result->result`. Same
pattern everywhere — e.g. `LLM::anthropic->messages->create(...)` and
`db::table_name->findone({...})->field` (see Database section).

## Comparisons and booleans

| Comparison | Result | Why |
|---|---|---|
| `1 == 1` | `true` | same value |
| `"a" == "a"` | `true` | same value |
| `"1" == 1` | `false` | a String is not an Int |
| `"1" === 1` | `false` | `===` also requires the types to match |

Use `===`/`!==` when the type matters (e.g. checking a license key, where
`"1234"` and `1234` should not be treated as the same thing). Combine
conditions with `and`, `or`, `not` — both short-circuit.

## Control flow

```spark
if age < 13 do
    term::print("child");
elif age < 20 do
    term::print("teenager");
else
    term::print("adult");
end;
```

Every block opens with `do` (or the alternative `then`) and closes with
`end;`. Many branches — `switch` is clearer than a chain of `elif`:

```spark
switch day do
    case "sat", "sun": term::print("weekend");
    case "mon":        term::print("ugh");
    default:           term::print("weekday");
end;
```

## Loops

```spark
for temp(name) in names:      -- over a list; temp(...) scopes the var to the loop
    term::print(name);
end;

for i in 0..5:        -- 0,1,2,3,4 (exclusive end)
    term::print(i);
end;
for i in 1..=3:        -- 1,2,3 (inclusive end)
    term::print(i);
end;

while n > 0 do
    n -= 1;
end;
```

`break` exits a loop early; `continue` skips to the next iteration.

## Functions

```spark
func greet(name<String>) executes:
    term::print(`Hello, ${name}!`);
end;
greet("Lily");

func add(a<Int>, b<Int>) -> Int executes:
    returns a + b;
end;
term::print(add(20, 22));   -- 42
```

`name<String>` is a typed parameter — Spark checks it and reports a clear
error on mismatch. `-> Type` (optional) declares the return type.

Defaults, optional, and rest parameters:

```spark
func greet(name<String>, greeting<String>:"Hello") executes:   -- default value
    term::print(`${greeting}, ${name}!`);
end;
greet("Lily");                  -- Hello, Lily!
greet("Lily", "Hey");           -- Hey, Lily!

func log(msg<String>, level<String>?) executes:   -- ? = optional, defaults to null
    if level is null do
        term::print(`[info] ${msg}`);
    else
        term::print(`[${level}] ${msg}`);
    end;
end;

func total(label<String>, ...nums<List>) executes:   -- ...collects remaining args into a List
    term::print(`${label}: ${list::sum(nums)}`);
end;
total("scores", 10, 20, 30);    -- scores: 60
```

Named arguments (order doesn't matter, typos are caught with a suggestion):

```spark
greet(name: "Corbin", greeting: "Hello");
```

Two kinds of function literals:

```spark
fn(x<Int>) => x * 2                          -- short lambda: ONE expression, no `end`
func(x<Int>) executes: returns x * 2; end    -- multi-statement anonymous function (FUNC keyword, not FN)
```

```spark
property set(list) nums: [1, 2, 3, 4, 5];
term::print(list::map(nums, fn(n) => n * 2));
term::print(list::filter(nums, fn(n) => n % 2 == 0));
```

## Error handling

```spark
func risky(n<Int>) executes:
    if n < 0 do
        throw "negative numbers not allowed";
    end;
    returns n * 2;
end;

try:
    term::print(risky(-1));
catch(err):
    term::print(`Something went wrong: ${err}`);
finally:
    term::print("done either way");   -- always runs
end;
```

## Custom types (`define`)

```spark
property set(define) Status: "active" | "banned" | "pending";
property set(define) User: { name: String, age: Int };
property set(define) Scores: [Int];

property set(Status) state: "active";      -- fine — use the type name as the qualifier
property set(Status) bad: "superadmin";    -- error: 'Status' accepts: 'active' | 'banned' | 'pending'
```

## Classes

```spark
class Animal do
    property set(var) name: "";

    func init(name<String>) executes:
        self.name := name;
    end;

    func speak() executes:
        returns `${self.name} makes a sound`;
    end;
end;

class Dog extends Animal do
    func speak() executes:
        returns `${self.name} barks`;
    end;
end;

property set(var) d: new Dog("Rex");
term::print(d.speak());
```

## Namespaces (user-defined)

```spark
namespace mathutils do
    func square(x<Int>) -> Int executes:
        returns x * x;
    end;
end;

term::print(mathutils::square(5));   -- 25
```

Note: a `property set(const)` declared inside a `namespace ... do ... end`
block is only visible by its BARE name from functions inside that same
namespace — it is NOT reachable from outside as `ns::CONSTNAME`. Only
`func`s are exposed via `::`.

## Splitting code across files

`src/utils.spk`:
```spark
export func greet(name<String>) -> String executes:
    returns `Hello, ${name}!`;
end;
```
`main.spk`:
```spark
import "src/utils.spk";
term::print(greet("Corbin"));
```
Spark only imports `.spk` and `.spark` files.

For `use` module semantics and the security boundary between declaration
loading and startup authorization, see the authoritative module section near
the beginning of this file.

## Keywords (complete)

| Keyword | Usage | What it does |
|---|---|---|
| `and` | `a and b` | True when both sides are true. Short-circuits. |
| `break` | `break;` | Exit the nearest loop. |
| `case` | `case "a", "b": ...` | One branch of a switch. Multiple values allowed. |
| `catch` | `catch(err): ...` | Handle an error raised in the try block. |
| `class` | `class Name do ... end;` | Define a class with fields and methods. |
| `continue` | `continue;` | Skip to the next iteration. |
| `database` | `database new(SQLITE) x;` / `database new(POSTGRE) x;` / `database delete x;` | Create/remove a database connection. SQLite is built in; PostgreSQL is optional and requires a configured driver/server. |
| `default` | `default: ...` | The fallback branch of a switch. |
| `do` | `if x do` | Opens a block. |
| `elif` | `elif condition do ...` | Another condition to try if the previous one failed. |
| `else` | `else ...` | Run when no earlier condition matched. |
| `end` | `end;` | Closes a block. |
| `executes` | `func name() executes:` | Opens a function body. |
| `export` | `export func name(...) executes: ... end;` | Make a function or property available to importers. |
| `extends` | `class Dog extends Animal do ...` | Inherit fields and methods from another class. |
| `false` | `false` | The boolean false. |
| `finally` | `finally: ...` | Always runs, whether or not there was an error. |
| `fn` | `fn(x<Int>) => x * 2` | A short one-expression function. |
| `for` | `for temp(item) in get(list): ... end;` | Loop over a List, Map, String, or range. |
| `func` | `func name(a<Int>) -> Int executes: ... end;` | Define a function. `-> Type` is optional. |
| `if` | `if condition do ... end;` | Run a block when a condition is true. |
| `import` | `import package;` / `import "file.spk";` | Load another Spark file or package. |
| `use` | `use "startup.spk";` | Load a module and authorize its final `runs function();` entry declaration. |
| `runs` | `runs start();` | Required final entry declaration in a module loaded through `use`. |
| `exclude` | `exclude term;` | Make a builtin namespace unavailable after this point in the file. |
| `in` | `for x in list:` | Used in for loops. |
| `is` | `value is null` | Check whether a value is null. |
| `namespace` | `namespace name do ... end;` | Group functions under a name. |
| `new` | `new ClassName(args)` | Create an instance of a class. |
| `not` | `not value` | Invert a boolean. |
| `null` | `null` | The absence of a value. |
| `or` | `a or b` | True when either side is true. Short-circuits. |
| `property` | `property set(qualifier) name: value;` | Declare, update, or delete a property. |
| `returns` | `returns value;` | Return a value from a function. |
| `self` | `self.field` | The current instance, inside a method. |
| `switch` | `switch value do case "a": ... default: ... end;` | Branch on a value. |
| `temp` | `temp(name): value;` | A variable that only exists inside the current block. |
| `then` | `if x then ... end;` | Opens a block — an accepted alternative to `do`. |
| `throw` | `throw "message";` | Raise an error that a catch block can handle. |
| `true` | `true` | The boolean true. |
| `try` | `try: ... catch(e): ... end;` | Catch errors instead of stopping the program. |
| `while` | `while condition do ... end;` | Loop while a condition holds. |

## Operators (complete)

| Operator | Name | What it does |
|---|---|---|
| `:>` | Safe field access | Read a field, returning null instead of erroring. |
| `??` | Null fallback | Use the right side when the left is null. |
| `:=` | Quickset | Update an existing var in place. Cannot be used on const. |
| `==` | Equality | Compare values. |
| `===` | Strict equality | Values *and* types must match. |
| `!=` | Inequality | True when values differ. |
| `!==` | Strict inequality | True when values or types differ. |
| `+=` `-=` `*=` `/=` | Compound assign | `count += 1;` etc. |
| `..` | Range | Up to but not including the end. |
| `..=` | Inclusive range | Including the end. |
| `\|` | Union type | Combine types in a `define`. |
| `->` | Strict field access/call | `res->body`. Errors if missing. |
| `=>` | Short lambda body | Separates a short lambda's params from its expression. |
| `...` | Rest parameter | Collects remaining arguments into a List. |
| `?` | Optional parameter | Marks a parameter optional; defaults to null. |

Precedence (highest binds tightest — works like normal math):
`^` (9, right-assoc) > `* / %` (8) > `+ -` (7) > `.. ..=` (6) >
`< > <= >=` (5) > `== != === !==` (4) > `??` (3) > `and` (2) > `or` (1).

## Types

| Type | Meaning |
|---|---|
| `String` | Text, like "hello". |
| `Int` | A whole number, like 42. |
| `Float` | A decimal number, like 3.14. |
| `Number` | An Int or a Float. |
| `Bool` | true or false. |
| `List` | An ordered collection. |
| `Map` | Key-value pairs. |
| `Null` | The absence of a value. |
| `Any` | Accepts any type. |
| `Func` | A function value. |

## Reserved words

Never usable as names: `and`, `break`, `case`, `catch`, `class`,
`continue`, `database`, `default`, `do`, `elif`, `else`, `end`, `executes`,
`export`, `extends`, `false`, `finally`, `fn`, `for`, `func`, `if`,
`import`, `in`, `is`, `namespace`, `new`, `not`, `null`, `or`, `property`,
`returns`, `self`, `switch`, `temp`, `then`, `throw`, `true`, `try`,
`while`.

Only special in certain positions, so usable as variable names: `as`,
`cast`, `const`, `define`, `delete`, `from`, `get`, `list`, `map`, `math`,
`of`, `route`, `serve`, `set`, `update`, `var`.

---

# Standard library

31 namespaces, 310 documented methods. Every namespace below is a **built-in
module** — always available, zero install step, zero `import`. Most are
implemented in the Python interpreter; `assert` and `discord` are plain
Spark source shipped in this repo's `stdlib/` directory — both kinds are
indistinguishable to a script, and both get full editor support.

## term — terminal input and output

```spark
term::print("Name:", "Lily", "Age:", 16);
property set(var) answer: term::input("What's your name? ");
term::print(term::color("Success", "green"));
term::table([{ name: "Corbin", score: 100 }, { name: "Eva", score: 95 }]);
```

| Function | What it does |
|---|---|
| `term::print(...values<List>) -> Null` | Print values to the terminal, separated by spaces. |
| `term::input(prompt<String>?) -> String` | Read a line of input. Always returns a String. |
| `term::clear() -> Null` | Clear the terminal screen. |
| `term::write(...values<List>) -> Null` | Print without a trailing newline. |
| `term::error(...values<List>) -> Null` | Print to stderr instead of stdout. |
| `term::color(text<String>, color<String>) -> String` | Wrap text in an ANSI color: red, green, yellow, blue, magenta, cyan, white, gray. |
| `term::bold(text<String>) -> String` | Wrap text in ANSI bold. |
| `term::table(rows<List>) -> Null` | Print a List of Maps as an aligned table. |

## str — working with text

| Function | What it does |
|---|---|
| `str::upper(s<String>) -> String` | Convert to UPPERCASE. |
| `str::lower(s<String>) -> String` | Convert to lowercase. |
| `str::length(s<String>) -> Int` | Number of characters. |
| `str::trim(s<String>) -> String` | Remove whitespace from both ends. |
| `str::reverse(s<String>) -> String` | Reverse the string. |
| `str::contains(s<String>, sub<String>) -> Bool` | True if s contains sub. |
| `str::replace(s<String>, from<String>, to<String>) -> String` | Replace every occurrence. |
| `str::split(s<String>, sep<String>?) -> List` | Split into a List. Splits on whitespace if no separator. |
| `str::starts(s<String>, prefix<String>) -> Bool` | True if s begins with prefix. |
| `str::ends(s<String>, suffix<String>) -> Bool` | True if s ends with suffix. |
| `str::repeat(s<String>, n<Int>) -> String` | Repeat the string n times. |
| `str::index(s<String>, sub<String>) -> Int` | Index of sub in s, or -1 if absent. |
| `str::at(s<String>, i<Int>) -> String` | Character at index i. Negative counts from the end. |
| `str::slice(s<String>, start<Int>, end<Int>?) -> String` | Substring from start up to end. |
| `str::pad(s<String>, width<Int>, char<String>?) -> String` | Pad on the left to the given width. |
| `str::padend(s<String>, width<Int>, char<String>?) -> String` | Pad on the right to the given width. |
| `str::title(s<String>) -> String` | Capitalize The First Letter Of Each Word. |
| `str::capitalize(s<String>) -> String` | Capitalize only the first letter. |
| `str::count(s<String>, sub<String>) -> Int` | How many times sub appears in s. |
| `str::chars(s<String>) -> List` | Split into a List of single characters. |
| `str::isnum(s<String>) -> Bool` | True if the string looks like a number. |
| `str::isalpha(s<String>) -> Bool` | True if every character is a letter. |
| `str::isempty(s<String>) -> Bool` | True if empty or only whitespace. |
| `str::lstrip(s<String>, chars<String>?) -> String` | Remove characters from the left. |
| `str::rstrip(s<String>, chars<String>?) -> String` | Remove characters from the right. |
| `str::format(template<String>, ...values<List>) -> String` | Replace {0}, {1}, ... with the given values. |
| `str::encode(s<String>) -> String` | URL-encode the string. |
| `str::decode(s<String>) -> String` | URL-decode the string. |

## list — working with lists

| Function | What it does |
|---|---|
| `list::length(lst<List>) -> Int` | Number of items. |
| `list::first(lst<List>) -> Any` | First item, or null if empty. |
| `list::last(lst<List>) -> Any` | Last item, or null if empty. |
| `list::push(lst<List>, item<Any>) -> List` | Append an item. Modifies in place. |
| `list::pop(lst<List>) -> Any` | Remove and return the last item. |
| `list::contains(lst<List>, x<Any>) -> Bool` | True if the list contains x. |
| `list::join(lst<List>, sep<String>?) -> String` | Join items into a String. |
| `list::reverse(lst<List>) -> List` | A reversed copy. |
| `list::slice(lst<List>, start<Int>, end<Int>) -> List` | A sublist from start up to end. |
| `list::index(lst<List>, x<Any>) -> Int` | Index of x, or -1 if absent. |
| `list::sum(lst<List>) -> Number` | Add up all the numbers. |
| `list::sort(lst<List>) -> List` | A sorted copy. |
| `list::unique(lst<List>) -> List` | A copy with duplicates removed. |
| `list::flat(lst<List>) -> List` | Flatten one level of nesting. |
| `list::map(lst<List>, fn<Func>) -> List` | Apply fn to every item and collect the results. |
| `list::filter(lst<List>, fn<Func>) -> List` | Keep only items where fn returns true. |
| `list::find(lst<List>, fn<Func>) -> Any` | First item where fn returns true, or null. |
| `list::every(lst<List>, fn<Func>) -> Bool` | True if fn returns true for every item. |
| `list::some(lst<List>, fn<Func>) -> Bool` | True if fn returns true for any item. |
| `list::reduce(lst<List>, fn<Func>, start<Any>?) -> Any` | Combine all items into one value. |
| `list::insert(lst<List>, i<Int>, item<Any>) -> List` | A copy with item inserted at index i. |
| `list::removeat(lst<List>, i<Int>) -> List` | A copy with the item at index i removed. |
| `list::remove(lst<List>, x<Any>) -> List` | A copy with the first matching x removed. |
| `list::concat(lst<List>, other<List>, ...rest<List>) -> List` | Join lists together. |
| `list::count(lst<List>, x<Any>) -> Int` | How many times x appears. |
| `list::max(lst<List>) -> Any` | Largest item. |
| `list::min(lst<List>) -> Any` | Smallest item. |
| `list::avg(lst<List>) -> Number` | Mean of all the numbers. |
| `list::isempty(lst<List>) -> Bool` | True if the list has no items. |
| `list::range(start<Int>, end<Int>?, step<Int>?) -> List` | Build a list of numbers. Or use `0..10` syntax. |

## map — key-value maps

| Function | What it does |
|---|---|
| `map::keys(m<Map>) -> List` | All keys as a List. |
| `map::values(m<Map>) -> List` | All values as a List. |
| `map::has(m<Map>, key<String>) -> Bool` | True if the key exists. |
| `map::get(m<Map>, key<String>, default<Any>?) -> Any` | Read a key, falling back to default. |
| `map::set(m<Map>, key<String>, value<Any>) -> Map` | A copy with the key set. |
| `map::delete(m<Map>, key<String>) -> Map` | A copy with the key removed. |
| `map::merge(a<Map>, b<Map>, ...rest<List>) -> Map` | Merge maps. Later values win. |
| `map::size(m<Map>) -> Int` | Number of keys. |
| `map::entries(m<Map>) -> List` | A List of [key, value] pairs. |
| `map::isempty(m<Map>) -> Bool` | True if the map has no keys. |
| `map::invert(m<Map>) -> Map` | Swap keys and values. |
| `map::pick(m<Map>, keys<List>) -> Map` | A copy containing only the listed keys. |
| `map::omit(m<Map>, keys<List>) -> Map` | A copy without the listed keys. |

## math — numbers and arithmetic

| Function | What it does |
|---|---|
| `math::add(a<Number>, b<Number>) -> Number` | Add two numbers. |
| `math::sub(a<Number>, b<Number>) -> Number` | Subtract b from a. |
| `math::mul(a<Number>, b<Number>) -> Number` | Multiply two numbers. |
| `math::div(a<Number>, b<Number>) -> Number` | Divide a by b. |
| `math::pow(a<Number>, b<Number>) -> Number` | Raise a to the power of b. |
| `math::abs(n<Number>) -> Number` | Absolute value. |
| `math::sqrt(n<Number>) -> Number` | Square root. |
| `math::floor(n<Number>) -> Int` | Round down to an Int. |
| `math::ceil(n<Number>) -> Int` | Round up to an Int. |
| `math::round(n<Number>, places<Int>?) -> Number` | Round to the given number of decimal places. |
| `math::max(a<Number>, b<Number>, ...rest<List>) -> Number` | Largest of the given values. |
| `math::min(a<Number>, b<Number>, ...rest<List>) -> Number` | Smallest of the given values. |
| `math::sin(n<Number>) -> Float` | Sine, in radians. |
| `math::cos(n<Number>) -> Float` | Cosine, in radians. |
| `math::tan(n<Number>) -> Float` | Tangent, in radians. |
| `math::log(n<Number>, base<Number>?) -> Float` | Logarithm. Natural log if no base given. |
| `math::log10(n<Number>) -> Float` | Base-10 logarithm. |
| `math::exp(n<Number>) -> Float` | e raised to the power of n. |
| `math::pi() -> Float` | The constant pi. |
| `math::e() -> Float` | Euler's number. |
| `math::sign(n<Number>) -> Int` | Returns -1, 0, or 1. |
| `math::clamp(n<Number>, lo<Number>, hi<Number>) -> Number` | Constrain n to the range lo..hi. |
| `math::mod(a<Number>, b<Number>) -> Number` | Remainder of a divided by b. |
| `math::trunc(n<Number>) -> Int` | Drop the decimal part. |
| `math::isnan(n<Number>) -> Bool` | True if the value is not a number. |

## type — checking what a value is

| Function | What it does |
|---|---|
| `type::of(value<Any>) -> String` | The type name as a String: Int, String, List, Map, ... |
| `type::isString(v<Any>) -> Bool` | True if the value is a String. |
| `type::isInt(v<Any>) -> Bool` | True if the value is an Int. |
| `type::isFloat(v<Any>) -> Bool` | True if the value is a Float. |
| `type::isNum(v<Any>) -> Bool` | True if the value is an Int or Float. |
| `type::isBool(v<Any>) -> Bool` | True if the value is true or false. |
| `type::isList(v<Any>) -> Bool` | True if the value is a List. |
| `type::isMap(v<Any>) -> Bool` | True if the value is a Map. |
| `type::isNull(v<Any>) -> Bool` | True if the value is null. |
| `type::isFunc(v<Any>) -> Bool` | True if the value is a function. |
| `type::is(v<Any>, name<String>) -> Bool` | True if the value's type matches the given name. |

## json — reading and writing JSON

| Function | What it does |
|---|---|
| `json::parse(text<String>) -> Any` | Parse a JSON string into a Map or List. |
| `json::stringify(value<Any>, indent<Int>?) -> String` | Convert a value to a JSON string. |
| `json::valid(text<String>) -> Bool` | True if the string is valid JSON. |
| `json::pretty(value<Any>) -> String` | Convert to JSON with 2-space indentation. |

## time — dates, clocks, and timers

| Function | What it does |
|---|---|
| `time::now() -> Int` | Unix timestamp in seconds. |
| `time::ms() -> Int` | Unix timestamp in milliseconds. |
| `time::sleep(seconds<Number>) -> Null` | Pause for the given number of seconds. |
| `time::date(format<String>?) -> String` | Formatted date. Defaults to %Y-%m-%d. |
| `time::iso() -> String` | Current time as an ISO 8601 string. |
| `time::year() -> Int` | Current year. |
| `time::month() -> Int` | Current month, 1-12. |
| `time::day() -> Int` | Current day of the month. |
| `time::hour() -> Int` | Current hour, 0-23. |
| `time::minute() -> Int` | Current minute. |
| `time::second() -> Int` | Current second. |
| `time::format(ts<Int>, format<String>?) -> String` | Format a timestamp as a String. |
| `time::parse(text<String>, format<String>?) -> Int` | Parse a date String into a timestamp. |
| `time::weekday() -> String` | Day of the week, e.g. Monday. |
| `time::time() -> String` | Current clock time as HH:MM:SS. |
| `time::diff(a<Int>, b<Int>) -> Int` | Seconds between two timestamps. |
| `time::add(ts<Int>, n<Int>, unit<String>?) -> Int` | Add time. Unit: seconds, minutes, hours, days. |

## rand — random numbers and IDs

| Function | What it does |
|---|---|
| `rand::int(lo<Int>, hi<Int>) -> Int` | Random integer between lo and hi, inclusive. |
| `rand::float() -> Float` | Random Float between 0 and 1. |
| `rand::choice(lst<List>) -> Any` | A random item from the list. |
| `rand::shuffle(lst<List>) -> List` | A shuffled copy of the list. |
| `rand::sample(lst<List>, n<Int>) -> List` | n random items, without repeats. |
| `rand::uuid() -> String` | A random UUID v4 String. |
| `rand::bool() -> Bool` | Random true or false. |
| `rand::string(length<Int>?) -> String` | Random alphanumeric String. Defaults to 16. |

## file — reading and writing files

| Function | What it does |
|---|---|
| `file::read(path<String>) -> String` | Read the whole file as a String. |
| `file::write(path<String>, text<String>) -> Null` | Write to a file, replacing its contents. |
| `file::append(path<String>, text<String>) -> Null` | Add text to the end of a file. |
| `file::exists(path<String>) -> Bool` | True if the path exists. |
| `file::isfile(path<String>) -> Bool` | True if the path is a file. |
| `file::isdir(path<String>) -> Bool` | True if the path is a directory. |
| `file::delete(path<String>) -> Bool` | Delete a file or directory. |
| `file::mkdir(path<String>) -> Null` | Create a directory, including parents. |
| `file::list(dir<String>?) -> List` | Directory contents as a List of names. |
| `file::copy(from<String>, to<String>) -> Null` | Copy a file. |
| `file::move(from<String>, to<String>) -> Null` | Move or rename a file. |
| `file::size(path<String>) -> Int` | File size in bytes. |
| `file::glob(pattern<String>) -> List` | Find files matching a pattern like *.spk. |
| `file::lines(path<String>) -> List` | Read a file as a List of lines. |

## io — file shortcuts (smaller alias of common file:: ops)

| Function | What it does |
|---|---|
| `io::read(path<String>) -> String` | Read a file as a String. Same as file::read. |
| `io::write(path<String>, text<String>) -> Null` | Write to a file. Same as file::write. |
| `io::append(path<String>, text<String>) -> Null` | Append to a file. |
| `io::exists(path<String>) -> Bool` | True if the path exists. |

## conn — making HTTP requests

```spark
property set(var) res: conn::get("https://jsonplaceholder.typicode.com/posts/1");
if res->ok do
    term::print(res->body->title);
else
    term::print(`request failed: ${res->status}`);
end;
conn::post("https://api.example.com/users", {
    body: { name: "Corbin", role: "admin" }, headers: { "Authorization": "Bearer token" }
});
```

| Function | What it does |
|---|---|
| `conn::get(url<String>, options<Map>?) -> Map` | HTTP GET. Returns `{ status, ok, body, headers, raw }`. |
| `conn::post(url<String>, options<Map>?) -> Map` | HTTP POST. Put your payload in options.body. |
| `conn::put(url<String>, options<Map>?) -> Map` | HTTP PUT. |
| `conn::patch(url<String>, options<Map>?) -> Map` | HTTP PATCH. |
| `conn::delete(url<String>, options<Map>?) -> Map` | HTTP DELETE. |
| `conn::head(url<String>, options<Map>?) -> Map` | HTTP HEAD — headers only, no body. |
| `conn::options(url<String>, options<Map>?) -> Map` | HTTP OPTIONS. |

Response shape: `status<Int>`, `ok<Bool>` (true when 2xx), `body<Map\|String>`
(parsed JSON or raw text), `headers<Map>`, `raw<String>` (unparsed body).

## serve — running an HTTP server (full guide below)

| Function | What it does |
|---|---|
| `serve::route(method<String>, path<String>, handler<Func>) -> Null` | Register a route with a function handler. |
| `serve::use(fn<Func>) -> Null` | Add middleware that runs before every route. |
| `serve::static(dir<String>, prefix<String>?) -> Null` | Serve files from a directory. |
| `serve::cors(options<Map>?) -> Null` | Turn on CORS: Access-Control-* headers + automatic OPTIONS preflight. |
| `serve::listen(port<Int>, host<String>?) -> Null` | Start the server. Blocks until Ctrl+C. |
| `serve::routes() -> List` | Every registered route as a List. |

## db — SQLite databases (full guide below)

| Function | What it does |
|---|---|
| `db::open(path<String>) -> Any` | Open or create a SQLite database. Returns a handle (low-level API — see the Database section for the higher-level `database new` API, which is what you want most of the time). |
| `db::run(handle<Any>, sql<String>, params<List>?) -> Map` | Run INSERT/UPDATE/DELETE/CREATE. Returns `{ changes, lastid }`. |
| `db::query(handle<Any>, sql<String>, params<List>?) -> List` | Run a SELECT. Returns a List of Maps. |
| `db::close(handle<Any>) -> Null` | Close the database handle. |

Note: naming a `database new(...)` variable exactly `db` shadows this
low-level namespace entirely (by design — a user-declared database always
takes priority over a same-named builtin namespace).

## auth — user accounts and sessions

```spark
database new(SQL, "app", ".") db;
auth::init(db, { hashkey: "change-me", session_ttl: 604800 });
auth::register({ username: "corbin", password: "s3cret", email: "c@example.com" });
property set(var) token: auth::login({ username: "corbin", password: "s3cret" });
if auth::verify(token) do
    property set(var) user: auth::current_user(token);
    term::print(`Logged in as ${user->username}`);
end;
auth::logout(token);
```

| Function | What it does |
|---|---|
| `auth::init(db<Any>, options<Map>?) -> Null` | Set up users/sessions tables on a database. Pass `{ instance: "name" }` to run a second, independent auth instance alongside "default". |
| `auth::register(user<Map>, instance<String>?) -> Int` | Create a user. Password hashed with PBKDF2-HMAC-SHA256, per-user salt. |
| `auth::login(credentials<Map>, instance<String>?) -> String` | Check credentials and return a session token. |
| `auth::current_user(token<String>, instance<String>?) -> Map` | The user for a token, or null. Never includes the password hash. |
| `auth::logout(token<String>, instance<String>?) -> Bool` | End a session. |
| `auth::verify(token<String>, instance<String>?) -> Bool` | True when the token is valid and unexpired. |

## cache — in-memory caching with TTL

```spark
cache::set("user:42", { name: "Corbin" }, 300);   -- expires in 5 minutes
term::print(cache::get("user:42")->name);
property set(var) total: cache::remember("sum", 60, fn() => list::sum([1, 2, 3]));
```

| Function | What it does |
|---|---|
| `cache::init(db<Any>) -> Null` | Back the cache with a database (from `database new(...)`), so values survive a restart. |
| `cache::set(key<String>, value<Any>, ttl<Int>?) -> Any` | Store a value. ttl in seconds; 0/omitted = never expires. |
| `cache::get(key<String>, fallback<Any>?) -> Any` | Read a value. Returns fallback (or null) when missing/expired. |
| `cache::has(key<String>) -> Bool` | True when the key exists and hasn't expired. |
| `cache::delete(key<String>) -> Bool` | Remove one key. True if it was there. |
| `cache::clear() -> Null` | Remove everything from the cache. |
| `cache::remember(key<String>, ttl<Int>, fn<Func>) -> Any` | Return the cached value, or call fn, store its result, and return it. |

## LLM — AI completions (Anthropic, OpenAI, xAI, Google, Vertex)

```spark
LLM::Config({ anthropic: "sk-ant-...", openai: "sk-...", google: "...", xai: "..." });

-- Each provider keeps its OWN native API and request/response shape:
property set(var) a: LLM::anthropic->messages->create({
    model: "claude-opus-5", max_tokens: 1024, system: "You are terse.",
    messages: [{ role: "user", content: "Say hi." }]
});
term::print(a->content[0]->text);

property set(var) o: LLM::openai->chat->completions->create({
    model: "gpt-5", messages: [{ role: "user", content: "Say hi." }]
});
term::print(o->choices[0]->message->content);

property set(var) g: LLM::google->models->generateContent({
    model: "gemini-2.5-flash", contents: [{ role: "user", parts: [{ text: "Say hi." }] }]
});

-- Prefer one portable shape across providers? LLM::complete normalizes it.
property set(var) r: LLM::complete({ provider: "anthropic", model: "claude-opus-5",
    messages: [{ role: "user", content: "Say hi." }], max_tokens: 64 });
term::print(r->text);
```

| Function | What it does |
|---|---|
| `LLM::Config(settings<Map>) -> Null` | Register API keys once. Lookup order: request's api_key, then LLM::Config, then environment. |
| `LLM::config(settings<Map>) -> Null` | Lowercase alias of LLM::Config. |
| `LLM::complete(request<Map>) -> Map` | Send a completion request. Returns `{ text, model, stop_reason, usage, raw }`. |
| `LLM::stream(request<Map>, callback<Func>) -> Map` | Stream a completion, calling callback(chunk) with each piece of text. |
| `LLM::client(provider<String>) -> Map` | The provider's native client, same as `LLM::<provider>`. |
| `LLM::clients` | Every provider's native client, keyed by name. |
| `LLM::providers() -> List` | The list of supported providers. |
| `LLM::anthropic->messages->create({...})` | Anthropic's native API — POST /v1/messages shape. |
| `LLM::openai->chat->completions->create({...})` | OpenAI's native API — chat completions shape. |
| `LLM::xai->chat->completions->create({...})` | xAI — OpenAI-compatible. |
| `LLM::google->models->generateContent({...})` | Gemini — contents/parts, not messages. |
| `LLM::vertex->models->generateContent({...})` | Gemini on Vertex AI. Same contents/parts, per-project URL, OAuth bearer. Needs `project`; `location` defaults `us-central1`. |
| `LLM::gemini->...` | Alias of google. `LLM::claude->...` alias of anthropic. `LLM::grok->...` alias of xai. `LLM::gpt->...` alias of openai. `LLM::vertexai->...`/`LLM::vertex_ai->...` alias of vertex. |

## crypto — hashing and encoding

| Function | What it does |
|---|---|
| `crypto::md5(s<String>) -> String` | MD5 hash as hex. Not secure — prefer sha256. |
| `crypto::sha1(s<String>) -> String` | SHA-1 hash as hex. Not secure — prefer sha256. |
| `crypto::sha256(s<String>) -> String` | SHA-256 hash as a hex String. |
| `crypto::sha512(s<String>) -> String` | SHA-512 hash as a hex String. |
| `crypto::base64(s<String>) -> String` | Base64-encode a String. |
| `crypto::unbase64(s<String>) -> String` | Decode a Base64 String. |
| `crypto::hmac(key<String>, message<String>) -> String` | HMAC-SHA256 signature as hex. |
| `crypto::token(bytes<Int>?) -> String` | A secure random hex token. |
| `crypto::compare(a<String>, b<String>) -> Bool` | Timing-safe String comparison, for secrets. |

No Ed25519 primitive exists yet (relevant if you're trying to verify
webhook signatures, e.g. Discord interactions — see `discord::` below).

## regex — pattern matching

| Function | What it does |
|---|---|
| `regex::match(pattern<String>, s<String>) -> Bool` | True if the pattern is found anywhere in s. |
| `regex::find(pattern<String>, s<String>) -> String` | The first match as a String, or null. |
| `regex::findall(pattern<String>, s<String>) -> List` | Every match as a List. |
| `regex::replace(pattern<String>, to<String>, s<String>) -> String` | Replace every match. |
| `regex::split(pattern<String>, s<String>) -> List` | Split the String on the pattern. |
| `regex::groups(pattern<String>, s<String>) -> List` | Capture groups from the first match. |

Use `r"..."` raw strings for patterns (see String prefixes above).

## process — running shell commands

| Function | What it does |
|---|---|
| `process::run(command<String>) -> Map` | Run a shell command. Returns `{ code, out, err, ok }`. |
| `process::cwd() -> String` | Current working directory. |
| `process::pid() -> Int` | Current process ID. |

## os / sys — the OS and environment (`os` is a canonical alias of `sys`, identical functions)

```spark
sys::loadenv();                          -- reads ./.env; missing file is fine
term::print(sys::env("APP_NAME"));
term::print(sys::env("PORT", "8080"));   -- with a fallback
-- Keys land in the environment, so LLM:: picks them up automatically.
```

| Function | What it does |
|---|---|
| `sys::exit(code<Int>?) -> Null` | Stop the program with an exit code. |
| `sys::args() -> List` | Command-line arguments as a List. |
| `sys::env(name<String>?, default<Any>?) -> Any` | Read an environment variable, or the whole environment when called with no name. |
| `sys::setenv(name<String>, value<String>) -> Null` | Set an environment variable. |
| `sys::unsetenv(name<String>) -> Null` | Remove an environment variable. |
| `sys::loadenv(path<String>?, override<Bool>?) -> Map` | Load a .env file. Defaults ./.env. Returns loaded keys; missing file is not an error. |
| `sys::platform() -> String` / `sys::name() -> String` | The OS: win32, linux, darwin. |
| `sys::version() -> String` | The Spark version. |
| `sys::hostname() -> String` | The machine's hostname. |
| `sys::username() -> String` | The current user's name. |
| `sys::home() -> String` | The user's home directory. |
| `sys::tmpdir() -> String` | The system temp directory. |
| `sys::cwd() -> String` | Current working directory. |
| `sys::cd(path<String>) -> Null` / `sys::chdir(path<String>) -> Null` | Change the working directory. |
| `sys::cpus() -> Int` | Number of CPU cores. |
| `sys::pid() -> Int` | The current process ID. |

(All of the above are also callable as `os::exit`, `os::args`, `os::env`, etc.)

## parallel — running functions concurrently on a thread pool

| Function | What it does |
|---|---|
| `parallel::map(items<List>, fn<Func>) -> List` | Call fn(item) for every item at once, on a thread pool. Same order as items. Best for I/O-bound work. |
| `parallel::run(fns<List>) -> List` | Call every zero-argument function in fns at once. Same order as fns. |

## assert — test assertions (always available, not just in `spark test`)

```spark
assert::equals(1 + 1, 2);
assert::isTrue(list::contains([1, 2, 3], 2));
assert::deepEquals({ a: 1, b: [1, 2] }, { a: 1, b: [1, 2] });
assert::approx(0.1 + 0.2, 0.3, 0.0001);   -- float compare with tolerance
assert::throws(func() executes: throw "boom"; end, "boom");
```

| Function | What it does |
|---|---|
| `assert::equals(actual<Any>, expected<Any>)` | Throw unless `actual === expected` (use deepEquals for Maps/Lists). |
| `assert::notEquals(actual<Any>, expected<Any>)` | Throw if `actual === expected`. |
| `assert::deepEquals(actual<Any>, expected<Any>)` | Throw unless structurally equal — recurses into Maps/Lists. |
| `assert::approx(actual<Number>, expected<Number>, tolerance<Number>:0.0001)` | Throw unless within tolerance. |
| `assert::isTrue(value<Any>)` | Throw unless `value === true`. |
| `assert::isFalse(value<Any>)` | Throw unless `value === false`. |
| `assert::isNull(value<Any>)` | Throw unless value is null. |
| `assert::isNotNull(value<Any>)` | Throw if value is null. |
| `assert::contains(haystack<Any>, needle<Any>)` | Throw unless haystack contains needle (List element or String substring). |
| `assert::fails(action<Func>)` | Throw unless calling action() throws. |
| `assert::throws(action<Func>, messageContains<String>:"")` | Like fails, but also checks the error message contains a substring. |

## discord — building Discord bots (REST only, no Gateway/websocket)

**This is a package, not a built-in namespace.** It used to ship inside the
runtime; a chat API should follow Discord's release cadence rather than the
language's. Nothing below resolves until it is installed:

```bash
spark add discord@^1.0
```

Then import it like any package — the names are unchanged:

```spark
import "discord" as discord;

discord::Config({ token: sys::env("DISCORD_BOT_TOKEN") });   -- set once
property set(const) CHANNEL: "123456789012345678";

discord::sendMessage(CHANNEL, "hello from Spark!");

property set(var) e: discord::embed({
    title: "Build finished", description: "All tests passed.", color: 65280
});
discord::sendEmbed(CHANNEL, e);

property set(var) msg: discord::sendMessage(CHANNEL, "reacting to this");
discord::addReaction(CHANNEL, msg->id, "\u{1F44D}");

-- override the configured token for one call (e.g. a second bot)
discord::sendMessage(CHANNEL, "from bot #2", null, sys::env("BOT_2_TOKEN"));
```

| Function | What it does |
|---|---|
| `discord::Config(options<Map>)` | Set `token` (and optionally `applicationId`) once so other calls can omit it. Calling again only overwrites the keys you pass. |
| `discord::sendMessage(channelId<String>, content<String>, options<Map>?, token<String>?)` | Post a message. `options` merges into the request body (tts, allowed_mentions, components, ...). |
| `discord::sendEmbed(channelId<String>, embed<Map>, content<String>?, token<String>?)` | Post an embed, optionally with text. |
| `discord::embed(options<Map>?)` | Build an embed Map — a pass-through helper. |
| `discord::getMessage(channelId<String>, messageId<String>, token<String>?)` | Fetch a message. |
| `discord::editMessage(channelId<String>, messageId<String>, content<String>, token<String>?)` | Replace a message's content (bot must have sent it). |
| `discord::deleteMessage(channelId<String>, messageId<String>, token<String>?)` | Delete a message. |
| `discord::addReaction(channelId<String>, messageId<String>, emoji<String>, token<String>?)` | React with an emoji (unicode char, or "name:id" for custom). |
| `discord::removeReaction(channelId<String>, messageId<String>, emoji<String>, token<String>?)` | Remove the bot's own reaction. |
| `discord::getChannel(channelId<String>, token<String>?)` | Fetch a channel's info. |
| `discord::sendTyping(channelId<String>, token<String>?)` | Trigger "is typing...". |
| `discord::getUser(userId<String>?, token<String>?)` | Fetch a user. Omit userId for the bot itself. |
| `discord::getGuild(guildId<String>, token<String>?)` | Fetch a server's info. |
| `discord::getGuildMember(guildId<String>, userId<String>, token<String>?)` | Fetch a member's info within a guild. |
| `discord::registerCommand(command<Map>, guildId<String>?, applicationId<String>?, token<String>?)` | Register a slash command. Global by default (~1hr to propagate); pass guildId for instant, guild-scoped. |
| `discord::listCommands(guildId<String>?, applicationId<String>?, token<String>?)` | List registered slash commands. |
| `discord::deleteCommand(commandId<String>, guildId<String>?, applicationId<String>?, token<String>?)` | Delete a slash command. |
| `discord::interactionReply(content<String>, options<Map>?)` | Build a type-4 (CHANNEL_MESSAGE_WITH_SOURCE) response body for an interactions webhook route. |

Limits: no realtime event listening (would need the Gateway/websocket,
not implemented). Slash-command webhook replies work, but Discord's
inbound Ed25519 signature verification isn't implemented (`crypto::` has
no Ed25519) — don't expose an interactions endpoint publicly for anything
where a spoofed request would matter.

---

# Databases (`database new`, the high-level API)

```spark
database new(SQL, "app", "./data") data1;
data1::create_table("users", { name: String, age: Int });
data1::users::insert({ name: "Corbin", age: 16 });
property set(var) all: data1::users::findmany();
term::print(all[0]->name);   -- Corbin
data1::users::update({ name: "Corbin" }, { age: 17 });
data1::users::delete({ name: "Eva" });
term::print(data1::users::count());
```

`database new(SQL, "filename", "parent/directory") name;` creates
`parent/directory/filename.sqlite` (or opens it). `database delete name;`
closes it. `SQL`/`SQLITE` use the built-in SQLite connector. `POSTGRE`,
`POSTGRES`, and `POSTGRESQL` select the optional PostgreSQL connector, which
requires a configured psycopg-compatible driver and database server. MongoDB
is not implemented.

Tables need no migration step — the first `insert` with a new column adds
that column automatically (`exists: "merge"` on create_table does this
explicitly for existing tables too).

**`data1::` — the database itself:**

| Function | What it does |
|---|---|
| `db::create_table(name, schema, exists?)` | schema is a Map of column→type, e.g. `{ name: String, age: Int }`. exists: "ignore" (default, do nothing if exists), "error", "override" (drop+recreate, loses data), "merge" (add new columns). |
| `db::drop_table(name, if_missing?)` | if_missing: "ignore" (default) or "error". |
| `db::has_table(name)` | True if the table exists. |
| `db::list_tables()` | Every table name as a List. |
| `db::rename_table(old, new, if_missing?)` | if_missing: "error" (default) or "ignore". |
| `db::raw(sql, params?)` | Run raw SQL. Returns a List of Maps for SELECT, or `{ changes, lastid }` otherwise. Always pass values through params, never build SQL by joining strings. |

**`data1::tablename::` — once a table exists (also available as `data1::tablename->method(...)`, see below):**

| Function | What it does |
|---|---|
| `table::insert(row, on_duplicate?)` | on_duplicate: "error" (default), "ignore", "replace". Returns the inserted row with its id. |
| `table::insert_many(rows, on_duplicate?)` | Insert a List of Maps. Returns the inserted rows. |
| `table::findmany(where?, order?, limit?, offset?)` | where is a Map; omit for every row. order is a column name, or "-name" for descending. |
| `table::findone(where?)` | First matching row, or null. |
| `table::count(where?)` | Count matching (or all) rows. |
| `table::exists(where)` | True if any row matches. |
| `table::update(where, changes, if_missing?)` | if_missing: "ignore" (default), "error", or "insert" (upsert: insert where+changes combined if nothing matched). |
| `table::update_many(where, changes, if_missing?)` | Same as update. |
| `table::delete(where, if_missing?)` | if_missing: "ignore" (default) or "error". |
| `table::clear(confirm)` | Delete every row. Requires `confirm: true`. |
| `table::columns()` | Every column as `{ name, type }`. |
| `table::add_column(name, type, default?)` | Add a column to an existing table. |
| `table::drop_column(name, if_missing?)` | if_missing: "ignore" (default) or "error". |

**Two equivalent syntaxes** — `db::table::method(...)` (3-segment) and
`db::table->method(...)` (arrow, table-client object) call the exact same
code and can be freely mixed:

```spark
data1::users::findone({ name: "Corbin" });      -- 3-segment
data1::users->findone({ name: "Corbin" });      -- arrow — same thing

-- arrow chains further, same rule as everywhere else in the language
term::print(data1::users->findone({ name: "Eva" })->age);   -- 17
```

Raw SQL when you need joins/aggregates/custom indexes:

```spark
property set(var) rows: data1::raw(
    "SELECT name, COUNT(*) as total FROM users GROUP BY name HAVING total > ?", [1]);
```

Always pass values through the `params` list, never build the SQL string
by joining in user input.

---

# Building a web server

```spark
route "GET" "/" do
    returns { status: 200, body: "Hello from Spark!" };
end;
serve::listen(8080);
```

A response is a Map: `{ status, body, headers? }`. **Body type decides
content-type**: a Map/List response sends `application/json`; a String
sends `text/html`.

Inside a route block, four variables are free: `req` (whole request),
`params` (`:name` path segments — always Strings, `cast(params->id, Int)`
if you need a number), `query` (query string values), `body` (request
body, parsed as JSON when it is JSON).

```spark
route "GET" "/user/:id" do
    returns { status: 200, body: { id: params->id } };
end;

route "GET" "/search" do
    returns { status: 200, body: { term: query:>q ?? "", page: query:>page ?? "1" } };
end;

route "POST" "/notes" do
    property set(var) text: body:>text ?? "";
    if str::isempty(text) do
        returns { status: 400, body: { error: "text is required" } };
    end;
    returns { status: 201, body: { saved: text } };
end;
```

Middleware — runs before every route; return `req` to continue, or a
response Map to stop the request there (how you'd do auth):

```spark
serve::use(func(req<Map>) executes:
    if str::starts(req->path, "/admin") do
        property set(var) auth: req->headers:>Authorization ?? "";
        if auth != "Bearer secret-token" do
            returns { status: 401, body: { error: "unauthorized" } };
        end;
    end;
    returns req;
end);
```

Serving static files: `serve::static("public");` (serves `./public` at
`/`), or `serve::static("assets", "/static");` (custom prefix). Path
traversal is blocked.

Alternative route syntax (useful for building routes programmatically):

```spark
serve::route("GET", "/hello", func(req<Map>) executes:
    returns { status: 200, body: { path: req->path } };
end);
```

Calling other APIs from inside a route — same `conn::` as anywhere else:

```spark
route "GET" "/weather/:city" do
    property set(var) res: conn::get(`https://api.example.com/weather/${params->city}`);
    if not res->ok do
        returns { status: 502, body: { error: "upstream failed" } };
    end;
    returns { status: 200, body: { city: params->city, temp: res->body:>main:>temp ?? "unknown" } };
end;
```

Notes on production: the built-in server is threaded, fine for dev/
internal tools/small deployments; put a reverse proxy in front for heavy
public traffic. Keep secrets in the environment (`sys::env("API_KEY")`),
validate input before it reaches the database, always use `?` placeholders
in raw SQL, return correct status codes.

---

# Testing

Write functions starting with `test_` and run `spark test`. `assert::` is
always available (it's a real built-in namespace, not a special test-only
injection) — see the `assert` section under Standard library above for the
full list.

```spark
-- tests/test_math.spk
func test_addition() executes:
    assert::equals(2 + 2, 4);
end;

func test_precedence() executes:
    assert::equals(2 + 3 * 4, 14);
end;
```

```
spark test
tests/test_math.spk
  ✓ addition
  ✓ precedence
2 passed in 0.01s
```

The runner strips the `test_` prefix and turns underscores into spaces
(`test_user_can_log_in` displays as *user can log in*) — name tests as
sentences.

Test discovery, automatic: anything in a `tests/` folder, or files matching
`test_*.spk` / `*_test.spk` anywhere in the project.

Testing an expected failure — wrap it in a function:

```spark
func divide(a<Int>, b<Int>) -> Int executes:
    if b == 0 do
        throw "cannot divide by zero";
    end;
    returns a / b;
end;

func test_divide_by_zero_throws() executes:
    assert::fails(fn() => divide(1, 0));
end;
```

Testing your own code — import the file under test:

```spark
-- tests/test_utils.spk
import "../src/utils.spk";
func test_greet_uses_the_name() executes:
    assert::equals(greet("Lily"), "Hello, Lily!");
end;
```

Running a subset: `spark test` (everything), `spark test tests/test_math.spk`
(one file), `spark test divide` (only tests whose name contains "divide").

`spark check <file>` parses and statically checks a file without running
it (name resolution, arity, const-reassignment) — good for a commit hook
or CI. `spark fmt --check` exits non-zero if formatting would change
anything.

---

# CLI commands

| Command | What it does |
|---|---|
| `spark run <file.spk>` / `spark <file.spk>` | Run a file. |
| `spark run --vm <file.spk>` | Compile supported source and run it in the bytecode VM. |
| `spark compile <file.spk>` | Write a validated versioned `.sbc` bytecode artifact. |
| `spark seal <file.spk> [-o file] [--sign key]` | Build a self-contained `.sparkpkg`, optionally with Ed25519 publisher signature. |
| `spark keygen <private> <public>` | Create a Spark Ed25519 signing key pair. |
| `spark verify <file.sparkpkg>` | Fail-closed verification of every sealed member. |
| `spark build [file.spk]` | Deterministic cached project build. |
| `spark profile <file.spk> --format folded\|json` | Profile VM opcodes; folded output is usable by flamegraph tools. |
| `spark init [name]` | Create a new Spark project (`spark.toml`, `main.spk`, `src/utils.spk`, `README.md`). |
| `spark new <cli\|api\|database\|library> <directory>` | Create a runnable project template. |
| `spark check <file.spk>` | Parse & static-check a file without running it. |
| `spark test [path] [name]` | Run `test_*.spk` files, optionally filtered. |
| `spark fmt [--check] [path]` | Format source (line-based; preserves comments). |
| `spark fmt --ast [path]` | Format from the parsed AST — opt-in only, **deletes comments** (fundamental lexer limitation: `--` comments are discarded before the parser sees them). |
| `spark repl` | Interactive session. |
| `spark learn [lesson]` | Browse the built-in eight-lesson Learn Spark course. |
| `spark doctor` | Check runtime/project installation health. |
| `spark vsc [dir] [--port=N] [--no-open]` | Open real VS Code in your browser (wraps code-server, auto-installs the Spark extension into it). Defaults to a fresh empty scratch directory under `/var/www/temp-files/<uuid>/` unless a path is given. |
| `spark install [package]` / `spark i` / `spark add` | Install packages (all deps if no name given). |
| `spark uninstall <package>` / `spark remove` / `spark rm` | Remove a package. |
| `spark list` / `spark ls` | List project dependencies. |
| `spark publish` | Publish this package to the registry. |
| `spark version` | Show version info. |
| `spark help` | Command list. |

**No public package registry is running yet** — `install`/`publish` talk
to `SPARK_REGISTRY` (default `registry.sparklang.dev`, which has no server
behind it right now). Vendor dependencies by hand into
`spark_modules/<name>/`, imported by name (`import sparkhttp;`) or by path
(`import "src/utils.spk";`), or point `SPARK_REGISTRY` at a server you run
yourself.

The VS Code extension (`extension/` in this repo) provides completion, hover,
signature help, diagnostics, symbols, definitions, references, rename,
formatting, a protocol language server, and VM debug-adapter support. Its
generated editor assets use `spark-spec.json` (built by `gen_spec.py`) — the
same source of truth used by the runtime reference.
