# OSL

OSL is a compiled scripting language. The compiler reads `.osl` source, generates Go, and builds a native executable.

```osl
import "std:serve"

*serve.Router app = serve.New()

app.GET("/", def(*serve.Context context) -> (
  context.string(200, "Hello from OSL")
))

app.run(":8080")
```

Run a source file while working on it:

```bash
osl run main.osl
```

Build an executable when you want a binary:

```bash
osl compile main.osl -o app
./app
```

## Read this first

Start with [Install and run OSL](/start/getting-started). The rest of the guide follows the order in which the language becomes useful:

1. [Programs and variables](/language-guide/programs-and-variables)
2. [Types](/language-guide/types)
3. [Arrays and objects](/language-guide/collections)
4. [Control flow](/language-guide/control-flow)
5. [Functions](/language-guide/functions)
6. [Structs, enums, and classes](/language-guide/nominal-types)
7. [Operators](/language-guide/operators)
8. [Errors and results](/language-guide/errors)
9. [Imports and project structure](/language-guide/imports-and-projects)

The examples use the same style as the production code in `originchats-osl`: type-first declarations, narrow functions, directory imports, typed package handles, explicit assertions at dynamic boundaries, and parentheses around mixed arithmetic.

## Find an API

Language-level functions and methods are listed in [Built-ins and value methods](/tools-and-reference/builtins-and-methods). Imported APIs live in the [standard-library package reference](/standard-library/packages).

Compiler and project commands have separate references:

* [OSL command line](/tools-and-reference/cli)
* [Opal projects](/tools-and-reference/opal)
* [Testing](/tools-and-reference/testing)
* [Editor tooling](/tools-and-reference/editor)

## One rule worth learning now

OSL arrays and strings are 1-indexed. The first item is at index `1`. This affects indexing, loops, slicing, and every example in this guide.


# Install and run OSL

OSL uses Go to build native executables. Install a current Go toolchain on the machine where you compile. The executable produced by OSL does not need Go.

## Install the compiler

```bash
curl -fsSL https://gosl.mistium.com | sh
```

Check the installed commands:

```bash
osl version
opal version
```

To build from source:

```bash
git clone https://git.rotur.dev/osl/.go osl
cd osl
go build -o osl .
./osl setup
```

## Write a program

Create `hello.osl`:

```osl
string name = "world"
log "Hello, " ++ name
```

Run it:

```bash
osl run hello.osl
```

`log` prints a value followed by a newline. `++` converts both operands to text and joins them.

## Build a binary

```bash
osl compile hello.osl
./hello
```

Use `-o` to choose the output path:

```bash
osl compile hello.osl -o bin/hello
```

## Format source

OSL uses two-space indentation. Let the formatter handle it:

```bash
osl fmt hello.osl
osl fmt src/
```

The directory form formats every `.osl` file below that directory.

## Import a package

```osl
import "std:fs"

if fs.exists("message.txt") (
  log fs.readFile("message.txt")
)
```

Packages shipped with the compiler use `std:<name>`. Continue with [Programs and variables](/language-guide/programs-and-variables), or open the [package index](/standard-library/packages) when you need a library API.


# Programs and variables

An OSL file runs from top to bottom. You do not need a `main` function.

```osl
log "first"
log "second"
```

Function, class, struct, and enum declarations are available before their source position. Other statements run where they appear.

## Declarations

Put the type before the name:

```osl
string username = "Ada"
int attempts = 0
number ratio = 0.75
boolean ready = false
string[] tags = ["admin", "active"]
```

Use `auto` when the compiler should infer a concrete type:

```osl
auto count = tags.len
auto upper = username.toUpper()
```

Use `any` for a value that is intentionally dynamic:

```osl
any payload = json.parse(raw)
payload = "invalid"
```

An untyped assignment also creates dynamic storage:

```osl
payload = {ok: true}
```

Production OSL tends to declare types at function boundaries and for long-lived state. Short local values often use `auto` when their type is obvious from the right side.

`auto` is limited to declarations with an initializer. Function parameters cannot use it because there is no declaration-site value to infer from. Use `any` for a dynamically typed parameter or write its explicit type.

## Assignment and mutation

```osl
attempts += 1
attempts++
username ++= " Lovelace"
```

OSL supports `=`, `+=`, `-=`, `*=`, `/=`, `%=`, `++=`, and `??=`. Postfix `++` and `--` are statements. In an expression, `++` means concatenation.

```osl
string label = "attempts: " ++ attempts
```

## Scope

Variables declared inside a function are local to that call. A local declaration may shadow a global.

```osl
string status = "global"

def readStatus() string (
  string status = "local"
  return status
)
```

Blocks do not create a separate variable lifetime in the same way a function does. Keep declarations close to their use and avoid reusing one name for unrelated values.

## Explicit `main`

You can define a zero-argument `main` function. The runtime calls it after top-level statements finish.

```osl
log "setup"

def main() (
  log "main"
)
```

Most applications use top-level code as the composition root. The OriginChats server follows this pattern in `src/main.osl`: it declares shared state, imports feature directories, mounts routes, then starts the server.

## Comments and statement separators

Use `//` for line comments. Newlines separate statements. Semicolons are unnecessary.

```osl
// Requests expire after five minutes.
int timeout = 300
```


# Types

OSL can keep values dynamic or check them against declared types. Types affect diagnostics, method resolution, generated Go, and package-handle calls.

## Core types

| Type                | Example           |
| ------------------- | ----------------- |
| `string`            | `"hello"`         |
| `int`               | `42`              |
| `number`            | `42.5`            |
| `boolean` or `bool` | `true`            |
| `array`             | `[1, "two"]`      |
| `object`            | `{name: "Ada"}`   |
| `any`               | Any runtime value |
| `null`              | `null`            |

An integer literal has type `int`. A decimal literal has type `number`.

## Nullable values

Append `?` when a value may be `null`:

```osl
string? nickname = null

def findName(string id) string? (
  if id == "owner" return "Ada"
  return null
)
```

A trailing nullable function parameter may be omitted. A nullable parameter before a required parameter still has to be passed.

```osl
def greet(string name, string? prefix) string (
  return (prefix ?? "Hello") ++ ", " ++ name
)

log greet("Ada")
```

## Typed arrays and maps

`T[]` is a growable typed array:

```osl
string[] names = []
names.append("Ada")
```

`T[size]` is a fixed-size array. Omitted elements receive the type's zero value, and methods that change the length are compile errors.

```osl
int[3] scores = [10]
scores[2] = 20
```

`key[value]` describes a typed map:

```osl
string[number] totals = {
  apples: 3,
  pears: 5
}
```

Nested forms are allowed:

```osl
string[object[]] messagesByChannel = {}
```

Package handle types use the package name. Pointer handles start with `*`:

```osl
import "std:serve"

*serve.Router app = serve.New()
```

## Assertions

Use `.assert(type)` when a dynamic value must have a specific runtime type:

```osl
any value = loadValue()
object record = value.assert(object)
```

The generic shorthand is equivalent:

```osl
object record = value.<object>
```

`.assertElse(type, fallback)` returns the fallback after a mismatch. When the fallback has an unambiguous type, omit the type argument:

```osl
string name = value.assertElse("")
object data = value.assertElse(object, {})
```

The compiler warns about assertions it can prove redundant and rejects assertions it can prove impossible.

## Narrowing

A `typeof` comparison narrows an `any` or union value inside the matching branch:

```osl
def normalize(any value) string (
  if typeof(value) == "string" (
    return value.trim()
  )
  return value.toStr()
)
```

## Unions and aliases

Join accepted types with `|` and name repeated types with `type`:

```osl
type Identifier = string | int

def display(Identifier id) string (
  return id.toStr()
)
```

## Conversion

The most common conversions are methods:

```osl
string text = value.toStr()
number decimal = text.toNum()
int whole = decimal.toInt()
boolean enabled = value.toBool()
```

Conversion is different from assertion. Conversion attempts to produce another representation. Assertion checks the existing runtime type.


# Arrays and objects

Arrays and strings are 1-indexed. Objects use string keys unless a typed map declares another key type.

## Arrays

```osl
string[] names = ["Ada", "Grace", "Lin"]

log names[1]
names[2] = "Hopper"
names.append("Margaret")
```

Negative positions count from the end. Position `0` is invalid and produces a compile error when the compiler can see it.

Common array methods include `append`, `prepend`, `pop`, `shift`, `insert`, `delete`, `contains`, `index`, `map`, `filter`, `some`, `every`, `sort`, `sortBy`, `reverse`, `join`, `clone`, `min`, `max`, `sum`, and `len`.

```osl
int[] values = [3, 1, 4]
int[] doubled = values.map((int value) int -> value * 2)
int[] ordered = doubled.sort()
```

## Objects

```osl
object user = {
  id: "u1",
  profile: {
    name: "Ada"
  }
}

log user.profile.name
user["active"] = true
```

A missing property returns `null`. Useful object methods include `getKeys`, `getValues`, `getEntries`, `contains`, `insert`, `delete`, `pick`, and `clone`.

## References and copies

Assigning an array, object, or class instance with `=` shares the same mutable value:

```osl
object first = {count: 0}
object second = first
second.count = 1

log first.count
```

This logs `1`. Call `.clone()` for an independent deep copy:

```osl
object second = first.clone()
```

Structs behave differently. They are values, so assigning a struct copies it.

## Merging and spreading

`++` merges arrays and objects as well as concatenating strings:

```osl
object base = {name: "Ada", active: false}
object enabled = base ++ {active: true}
int[] all = [1, 2] ++ [3, 4]
```

Spread values inside literals or function calls:

```osl
int[] first = [1, 2]
int[] all = [...first, 3]
object copy = {...base, role: "owner"}
log max(...all)
```

## Destructuring

```osl
array pair = ["Ada", "Grace"]
[string first, string second] = pair
{id, profile: details} = user
```

Use `_` to discard a position. The source expression runs once.

## Iteration

Use `in` for values and `of` for positions or keys:

```osl
for index, name in names (
  log index ++ ": " ++ name
)

for index of names (
  log index
)
```

Both indexes start at `1` for arrays and strings.


# Control flow

OSL uses parentheses for statement blocks.

## Conditions

```osl
if score >= 90 (
  log "A"
) else if score >= 80 (
  log "B"
) else (
  log "C or below"
)
```

A one-statement guard may stay on one line:

```osl
if user == null return {error: "User not found"}
if message == null continue
if complete break
```

`return`, `continue`, and `break` are the supported inline guard bodies. Use a block for anything else.

## Boolean operators

Use `and`, `or`, and the `!` prefix:

```osl
boolean allowed = authenticated and !banned
```

`and` and `or` short-circuit. `??` checks only for `null`, so it preserves `false`, `0`, and an empty string.

```osl
string display = nickname ?? username
```

## Counted loops

`for name count` counts from `1` through the evaluated count:

```osl
for index 3 (
  log index
)
```

This logs `1`, `2`, and `3`. The compiler evaluates the bound once.

`loop count` repeats a block without declaring an index:

```osl
loop 3 (
  retry()
)
```

## Collection loops

`in` yields values. With two names it yields the 1-based position and value:

```osl
for name in names (
  log name
)

for index, name in names (
  log index ++ ": " ++ name
)
```

`of` yields positions for arrays and strings, or keys for objects:

```osl
for index of names (
  log names[index]
)
```

Use `_` when you do not need one side of a two-value loop:

```osl
for _, value in records (
  process(value)
)
```

## `while` and `until`

```osl
while queue.len > 0 (
  handle(queue.shift())
)

until ready (
  wait 10
)
```

## `match`

`match` is an expression. A non-enum match requires an `_` arm:

```osl
string label = match status (
  200 -> "ok"
  404 -> "missing"
  _ -> "error"
)
```

An arm may use a block and return a value:

```osl
string label = match status (
  200 -> "ok"
  _ -> (
    log "unexpected status"
    return "error"
  )
)
```

Use `match` for new code. `switch` also exists for command-style fallthrough cases, but it is easier to get wrong because cases continue until `break`.


# Functions

Named functions use `def`, type-first parameters, and an optional return type.

```osl
def greet(string name) string (
  return "Hello, " ++ name
)
```

A function with no declared return type may return any value. Declare the type when callers depend on it.

## Lambdas

Use `->` for an anonymous function:

```osl
auto double = (int value) int -> value * 2
int[] doubled = [1, 2, 3].map(double)
```

A block lambda uses `def`:

```osl
auto validate = def(object record) result -> (
  if record.id == null return result.err("missing id")
  return result.ok(record)
)
```

Lambdas capture variables from their surrounding scope.

## Optional and rest parameters

A trailing `T?` parameter may be omitted and receives `null`:

```osl
def page(int limit, string? cursor) object (
  return {limit, cursor}
)

page(20)
```

Use `...name` to collect extra arguments:

```osl
def collect(string prefix, ...values) array (
  return values.map(value -> prefix ++ value)
)
```

Spread an array at a call site:

```osl
log max(...scores)
```

## Function types

Name a reusable signature with `type` and `def`:

```osl
type Formatter def(object) string

def render(object value, Formatter format) string (
  return format(value)
)
```

The compiler checks arguments and return values when a function has a signature type.

## Generics

Generic functions put type parameters after the name. This form is useful when a runtime assertion should preserve the requested type:

```osl
def checked<T>(any value) result<T, string> (
  return try(value.assert(T))
)

result<string, string> name = checked<string>(input)
```

Generic result types preserve both success and error types:

```osl
result<string[object], string> records = checked<string[object]>(input)
```

## Calling and binding

Functions are values. `.call(...)` invokes a function, and `.bind(...)` returns a function with leading arguments fixed.

```osl
def add(int left, int right) int (
  return left + right
)

auto addTen = add.bind(10)
log addTen(5)
```

## Side-effect calls

OSL warns when code discards a meaningful return value. Prefix a call with `void` when discarding the result is deliberate:

```osl
void cache.insert("key", value)
```

This is common for mutating methods that also return the changed value.


# Structs, enums, and classes

OSL has three named data forms. They solve different problems.

## Structs

A struct is a compact typed value. Fields have defaults, and assignment copies the struct.

```osl
struct Point (
  int x = 0
  int y = 0
)

Point origin = Point()
Point cursor = Point(10, 20)
cursor.x = 12
```

A constructor accepts either no arguments or one argument for every field. Convert a struct to a dynamic object explicitly:

```osl
object data = cursor.toObject()
```

The compiler does not implicitly assign a struct to `object`.

## Enums

An enum variant may carry typed data:

```osl
enum LoadState (
  Loading
  Ready(object)
  Failed(string)
)
```

Use an exhaustive `match` to read it:

```osl
def describe(LoadState state) string (
  return match state (
    LoadState.Loading -> "loading"
    LoadState.Ready(data) -> (
      return "ready: " ++ data.len
    )
    LoadState.Failed(message) -> message
  )
)
```

The compiler reports a missing enum variant. Enum values expose a numeric `tag`, and payload fields use the lowercase variant name.

## Classes

A class creates mutable reference objects with methods and inheritance.

```osl
class Counter (
  int count = 0

  def new(int start) (
    self.count = start
  )

  def increment() int (
    self.count++
    return self.count
  )
)

Counter counter = Counter.new(10)
log counter.increment()
```

`self` refers to the instance. A field that starts with `_` is private outside class methods. External access to a private field returns `null`.

Extend another class with `extends`:

```osl
class AdminCounter extends Counter (
  string role = "admin"
)
```

Child classes inherit fields, methods, and constructors. A child method with the same name replaces the inherited method.

Class assignment shares the instance. Use `.clone()` for an independent copy.

## Which form to choose

Use a struct for small typed records that should copy by value. Use an enum when a value must be one of a fixed set of cases. Use a class for identity, shared mutation, methods, private state, or inheritance.


# Operators

## Arithmetic

| Operator | Operation                                                        |
| -------- | ---------------------------------------------------------------- |
| `+`      | Addition, or string concatenation when both operands are strings |
| `-`      | Subtraction                                                      |
| `*`      | Multiplication or repetition                                     |
| `/`      | Division                                                         |
| `%`      | Remainder                                                        |
| `^`      | Power                                                            |

OSL evaluates mixed arithmetic operators from left to right. It does not apply the usual multiplication-before-addition rule.

```osl
number wrong = 10 + 2 * 3
number right = 10 + (2 * 3)
```

The first expression is `(10 + 2) * 3`. The compiler warns about unparenthesized mixed arithmetic. Parenthesize the intended grouping, especially for time, sizes, and persisted values.

Integer overflow raises an error. A divisor that the compiler knows is zero is a compile error.

## Concatenation and merge

`++` converts operands to strings when used with scalar values. It merges arrays or objects when both sides are collections.

```osl
string label = "count: " ++ count
int[] values = [1, 2] ++ [3]
object options = defaults ++ overrides
```

## Comparison

| Operator             | Meaning                                                      |
| -------------------- | ------------------------------------------------------------ |
| `==`, `!=`           | Loose equality and inequality                                |
| `===`, `!==`         | Strict equality and inequality                               |
| `<`, `<=`, `>`, `>=` | Ordering                                                     |
| `in`, `!in`          | Membership                                                   |
| `of`                 | Membership alias in expressions, position iteration in loops |

Loose string equality is case-insensitive and may coerce values. Use `===` when case and runtime type matter.

## Boolean and nullish operators

```osl
boolean valid = ready and !failed
any selected = primary ?? fallback
```

`and` and `or` return according to OSL truthiness and short-circuit. `??` only falls back for `null`. `??=` assigns only when the current value is `null`.

Logical operators have precedence rules, with `and` binding more tightly than `or`. The compiler warns when different logical operators are mixed without parentheses. Parenthesize the intended grouping when an expression uses both.

## Ranges and the ternary form

`start to end` creates an inclusive integer range in either direction.

```osl
int[] forward = 1 to 3
int[] backward = 3 to 1
```

The ternary form has no colon:

```osl
string label = ready ? "ready" "waiting"
```

## Pipe and bitwise operators

`|>` sends the left value to a one-argument function:

```osl
log 10 |> double |> format
```

Bitwise operators are `&`, `|`, `^^`, `<<`, and `>>`.

## Regular-expression literals

Prefix a backtick string with `$` to create a regular expression. Flags follow the closing backtick:

```osl
auto digits = $`\d+`
log digits.test("item-42")
log $`hello`i.test("HELLO")
```


# Errors and results

OSL has thrown errors for exceptional control flow and `result` values for operations where failure is part of the API.

## Throwing

```osl
if config == null (
  throw "Missing configuration"
)
```

A thrown value stops the current operation unless a catch expression handles it.

## Catch expressions

```osl
object data = schema.safeParse(input) catch (
  log _.unwrapErr().message
  return {}
)
```

Inside the catch block, `_` is the failed result or error value supplied by the expression. A catch block returns its replacement value with `return`.

## Result values

Import `std:result` when constructing result values directly:

```osl
import "std:result"

def divide(number left, number right) result<number, string> (
  if right == 0 return result.err("division by zero")
  return result.ok(left / right)
)
```

Inspect and unwrap the result:

```osl
result<number, string> calculated = divide(10, 2)

if calculated.isErr() (
  log calculated.unwrapErr()
  return
)

number value = calculated.unwrap()
```

`unwrap()` and `unwrapErr()` fail when called on the wrong variant. Use `unwrapAs(type)` when a dynamic success value needs an assertion.

## Assertions are runtime checks

`.assert(type)` is also a failure boundary:

```osl
object payload = decoded.assert(object)
```

Use `.assertElse(...)` when a fallback is valid. Do not use it to hide malformed required data. The OriginChats server validates protocol input with schemas, then uses `.assert(...)` after validation has established the type.

## Process exits

The language command `exit status` terminates the process. The `std:process` package also provides `process.exit(status)`.

```osl
if invalidConfig (
  log "Invalid configuration"
  exit 1
)
```

`osl run` returns the program's exit status to the shell.


# Imports and project structure

Imports are relative to the file that contains them.

## Import forms

| Form                        | Meaning                                             |
| --------------------------- | --------------------------------------------------- |
| `import "std:fs"`           | Package embedded in the compiler                    |
| `import "./helpers.osl"`    | One local source file                               |
| `import "helpers"`          | Every `.osl` file directly inside a local directory |
| `import "owner/repository"` | Git package installed by Opal                       |
| `import "go:net/http"`      | Go package                                          |

Directory imports are sorted by filename and are not recursive. Import each child directory explicitly.

## A practical layout

Use one top-level entry file and group the rest by responsibility:

```
src/
  main.osl
  api/
    index.osl
    handlers/
  db/
    users/
      storage.osl
      queries.osl
  helpers/
```

`main.osl` should compose the application. Put feature behavior in imported directories. OriginChats uses this layout at production scale: its entry file imports packages, declares shared types and state, imports feature groups, then starts the HTTP and WebSocket servers.

```osl
// src/main.osl
import "std:serve"
import "api"
import "db"
import "helpers"
```

## Exports

Without an export statement, a local file exposes all declarations. Add exports to define an explicit public API:

```osl
export {createUser, deleteUser}
export {User} from "./models.osl"
export * as validation from "./validation"
```

Consumers can merge exports, select names, or create a namespace:

```osl
import * from "./users.osl"
import {createUser} from "./users.osl"
import * as users from "./users.osl"
```

Sibling files in one directory can share private declarations. A consumer in another directory sees only the explicit exports.

## Module objects

The expression form returns a local module as an object:

```osl
object math = import("./math.osl")
log math.add(2, 3)
```

## Go modules

Native builds look for `go.mod` in the entry file's directory and its parents. The compiler copies the selected module files into its generated workspace. A project without `go.mod` uses a generated module in the OSL cache.

## Opal projects

Opal manages Git and Go dependencies, exact lock data, scripts, and package commands. An Opal project uses `opal.json`, `opal.lock`, and an ignored `.opal/` directory. See [Opal projects](/tools-and-reference/opal).


# Built-ins and value methods

This page covers language-level APIs that do not require an import. Standard-library APIs live under [Packages](/standard-library/packages).

## Core functions

| Function                                                    | Purpose                                                           |
| ----------------------------------------------------------- | ----------------------------------------------------------------- |
| `typeof(value)`                                             | Returns the runtime type name.                                    |
| `len(value)`                                                | Returns the length of a supported value.                          |
| `string(value)`                                             | Converts a value to text. Prefer `.toStr()` in application code.  |
| `number(value)`                                             | Converts a value to a decimal number. Prefer `.toNum()`.          |
| `int(value)`                                                | Converts a value to an integer. Prefer `.toInt()`.                |
| `boolean(value)`                                            | Converts a value using OSL truthiness. Prefer `.toBool()`.        |
| `array(value)`                                              | Converts a supported value to an array.                           |
| `object(value)`                                             | Converts a supported value to an object.                          |
| `min(...values)`                                            | Returns the smallest numeric value.                               |
| `max(...values)`                                            | Returns the largest numeric value.                                |
| `clamp(value, low, high)`                                   | Restricts a number to a range.                                    |
| `round(value)`, `floor(value)`, `ceil(value)`, `abs(value)` | Basic numeric operations.                                         |
| `sqrt(value)`, `pow(base, exponent)`                        | Roots and powers.                                                 |
| `sin(value)`, `cos(value)`, `tan(value)`                    | Trigonometric functions.                                          |
| `random(low, high)`                                         | Returns a random number in the requested range.                   |
| `range(start, end)`                                         | Creates an inclusive range. The `to` operator is usually clearer. |
| `keys(object)`, `values(object)`, `entries(object)`         | Reads object contents.                                            |
| `btoa(value)`, `atob(value)`                                | Base64 encode and decode.                                         |
| `symbol(name)`                                              | Creates a symbol value.                                           |
| `sleep(seconds)`                                            | Blocks for a duration in seconds.                                 |

## Methods on every value

| Method                        | Result                                             |
| ----------------------------- | -------------------------------------------------- |
| `.toStr()`                    | String conversion                                  |
| `.toNum()`                    | Number conversion                                  |
| `.toInt()`                    | Integer conversion                                 |
| `.toBool()`                   | Boolean conversion                                 |
| `.getType()`                  | Runtime type name                                  |
| `.assert(type)`               | Runtime type assertion                             |
| `.assertElse(type, fallback)` | Assertion with a fallback                          |
| `.assertElse(fallback)`       | Assertion with the type inferred from the fallback |
| `.len` or `.len()`            | Length where supported                             |
| `.contains(value)`            | Membership where supported                         |

## Strings

Common string methods include:

```
append       prepend       insert        delete
contains     containsAny   startsWith    endsWith
index        lastIndex     count         match
replace      replaceFirst  split         left          right
trim         trimText      strip         stripStart    stripEnd
toUpper      toLower       toTitle       toMixed
padStart     padEnd        reverse       repeat
toArr        ord           btoa          atob
encodeHex    decodeHex     encodeBin     decodeBin
hashMD5      hashSHA1      hashSHA256    hashSHA512
```

String positions are 1-based. Indexing and iteration use Unicode code points. `.len` counts UTF-8 bytes, so it may be larger than the number of characters.

## Arrays

Common array methods include:

```
append       prepend       insert        delete
pop          shift         fill          swap
contains     index         first         last
left         right         trim          concat
map          filter        some          every
sort         sortBy        reverse       randomOf
join         clone         getKeys       getValues
min          max           sum           product
```

Array positions are 1-based. Mutating methods change the original array. `.clone()` creates an independent deep copy.

## Objects

Common object methods include `getKeys`, `getValues`, `getEntries`, `contains`, `insert`, `delete`, `pick`, `clone`, `toStr`, `jsonParse`, and `getProto`.

Objects return `null` for missing fields. Assignment shares the object; `.clone()` copies it.

## Numbers and booleans

Numbers provide methods such as `round`, `floor`, `ceiling`, `abs`, `sqrt`, `clamp`, `sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `log`, `ln`, `sign`, `isPrime`, and `chr`.

Booleans support the universal conversion, type, assertion, and prototype methods.

## Prototypes

Strings, arrays, objects, numbers, and functions can resolve methods through prototypes. Prefer ordinary functions or named types for application structure. Prototype changes are global to the value type and are harder to trace in a large project.


# OSL command line

Run `osl` without arguments for the command summary.

## Build commands

```
osl run <file.osl> [--no-cache] [-v|--verbose]
osl compile <file.osl> [-o <output>] [--no-cache] [--no-write] [-v|--verbose]
osl transpile <file.osl> [--no-cache] [-v|--verbose] [-o <file>]
```

`run` builds a temporary executable and runs it with the source directory as its working directory. It forwards the program's exit status.

`compile` writes a native executable. Without `-o`, it uses the entry filename without `.osl`. `--no-write` runs the compiler frontend but does not create a generated workspace or binary.

`transpile` stops after Go generation. It prints Go to standard output unless `-o` selects a file.

`--no-cache` disables compiler artifacts, module snapshots, generated workspaces, and native binary reuse for that command. Verbose mode keeps the timing for each compiler stage in the terminal.

## Source tools

```
osl fmt <file-or-directory> [...]
osl ast <file.osl>
osl lsp [check <file>]
osl package <name>
```

`fmt` rewrites valid OSL source with canonical spacing and two-space indentation. It walks directory arguments recursively.

`ast` prints the parsed syntax tree as JSON. `compile`, `run`, and `transpile` can also consume that JSON representation.

`lsp` starts the language server over standard input and output. `lsp check` prints project diagnostics without an editor.

`package` prints the embedded source for a standard package.

## Tests and profiling

```
osl test [path ...]
osl bench <file.osl> [--time 1s] [--runs N] [--top N] [--profile cpu.pprof]
```

`test` discovers `.test.osl` files. See [Testing](/tools-and-reference/testing).

`bench` repeats a program, records a CPU profile, and reports time against OSL source lines. Use `--runs 1` for code with external side effects.

## Cache

```
osl cache status
osl cache clean
```

The cache lives in the platform user-cache directory under `osl`. `OSL_CACHE_DIR` chooses another directory. `OSL_CACHE_DISABLE=1` disables persistent caching.

## Installation

```
osl setup
osl update [-v|--verbose]
osl uninstall
osl version
```

`setup` installs both `osl` and `opal`.


# Opal projects

Opal manages OSL projects and their Git and Go dependencies. The `opal` executable and `osl opal` run the same code.

## Project files

```bash
mkdir my-project
cd my-project
opal init
```

`opal init [name]` writes `opal.json` and updates `.gitignore` in the current directory. The optional name sets the project name. It does not create or enter a directory.

| Path        | Purpose                                                            |
| ----------- | ------------------------------------------------------------------ |
| `opal.json` | Project metadata, dependencies, scripts, and command entries       |
| `opal.lock` | Resolved Git commits, Go module versions, and the dependency graph |
| `.opal/`    | Checked-out packages and compiled project commands                 |

Commit `opal.json` and `opal.lock`. Ignore `.opal/`.

## Manifest

```json
{
  "name": "example",
  "version": "1.0.0",
  "main": "src/main.osl",
  "scripts": {
    "dev": ["osl", "run", "src/main.osl"],
    "test": ["osl", "test", "src"]
  },
  "bin": {
    "example": "src/main.osl"
  }
}
```

Each script is an argument array. Opal runs the first item as the executable and passes the rest as arguments. It does not invoke a shell. Extra arguments after the script name are appended.

```bash
opal run
opal run dev
opal run test ./tests/packages
```

Running `opal run` without a name lists the available scripts.

## Dependencies

```bash
opal add mist/project
opal add mist/project@v1.2.0
opal add gh:owner/repository
opal add gitlab:owner/repository
opal add ../local-package
opal add go:example.com/module@v1.2
opal remove mist/project
opal update [package]
opal sync
```

An unqualified `owner/repository` uses `git.rotur.dev`. Host aliases include `rotur:`, `gh:` or `github:`, `gitlab:`, and `codeberg:`. You can also pass a Git URL, an SSH URL, or a local directory. Append `@ref` to select a branch, tag, or commit. `go:` records a Go module dependency and defaults to `latest` when no version follows `@`.

`opal sync` restores the commits and module versions in `opal.lock`. `opal update` refreshes every direct Git dependency, or one named dependency when given an argument. Pass `--offline` to `add`, `update`, or `sync` to prohibit network access. Offline Git packages must already exist in `.opal/packages` at the locked commit. Offline Go modules must already be in Go's module cache.

Inspect dependency state with:

```bash
opal list
opal graph
opal why mist/project
```

`list`, `graph`, and `why` accept `--json`. `why` exits with status 1 when it cannot find a path to the requested dependency.

## Package commands

A package exposes commands through the manifest's `bin` object:

```json
{
  "bin": {
    "example": "src/main.osl"
  }
}
```

Run a package command once without installing it:

```bash
opal exec mist/tool --help
```

Install commands globally with `opal add -g mist/tool`. Opal compiles them into `$OPAL_HOME/bin`, which defaults to `~/.opal/bin`.

```bash
opal list -g
opal update -g [package]
opal remove -g mist/tool
```

Use `--force` with `add`, `update`, or `sync` when two packages provide the same command name or a file already occupies its destination. Add `$OPAL_HOME/bin` to `PATH` before invoking global commands by name.

`opal clean` removes project-local `.opal` state. The lock file remains, so `opal sync` can restore it.

## Command summary

| Command                | Short form | Purpose                                                    |
| ---------------------- | ---------- | ---------------------------------------------------------- |
| `init [name]`          | `i`        | Create `opal.json` in the current directory                |
| `add <package>`        | `a`        | Add and resolve a Git or Go dependency                     |
| `remove <package>`     | `d`        | Remove a dependency                                        |
| `update [package]`     | `u`        | Resolve new commits for one or all direct Git dependencies |
| `sync`                 | `s`        | Restore the locked dependency tree                         |
| `list`                 | `l`        | List locked dependencies                                   |
| `graph`                | `g`        | Print the Git dependency tree                              |
| `why <package>`        | `w`        | Print paths from the project to a Git dependency           |
| `run [script] [...]`   | `r`        | List scripts or run one                                    |
| `exec <package> [...]` | `e`        | Compile and run a package command in a temporary checkout  |
| `clean`                | `c`        | Remove `.opal`                                             |
| `version`              | `v`        | Print the OSL and Opal version                             |


# Testing

`osl test` discovers files ending in `.test.osl`. With no path, it walks the current directory. Hidden directories, `vendor`, and `node_modules` are skipped.

```bash
osl test
osl test src/
osl test src/users/validation.test.osl
```

Test files are ordinary OSL programs. A thrown error or failed assertion makes the file fail.

## Assertions

```osl
import "std:testing"

testing.equal(add(2, 3), 5)
testing.notEqual(status, "failed")
testing.near(measured, 10, 0.01)
testing.isNull(optional)
testing.notNull(record)
testing.assert(items.len > 0)
testing.panics(def() -> (
  throw "expected"
))
```

Each assertion accepts an optional final message. `testing.fail(message)` fails immediately.

## Direct checks

For small focused tests, direct checks and `throw` are often clearer than an assertion wrapper:

```osl
object parsed = parseInput(source)

if parsed.name != "Ada" (
  throw "parseInput should preserve the name"
)
```

The OriginChats test files use this style for domain behavior. It keeps the failure message next to the rule being tested.

## Discovery and output

The runner sorts discovered paths, compiles each file separately, and prints `PASS` or `FAIL` for each one. It returns status `1` if any file fails.


# Editor tooling

`osl lsp` runs the OSL language server over standard input and output. Configure an editor's language-server client to start this command for `.osl` files.

The server provides diagnostics, completion, hover text, signature help, definitions, type definitions, references, rename, semantic highlighting, inferred-type hints, document symbols, links, folding, selection ranges, call hierarchy, import organization, and formatting.

It keeps one project-aware compiler engine. Unsaved editor buffers become in-memory overlays, so diagnostics can resolve imports without writing those buffers to disk.

## Check from a terminal

```bash
osl lsp check src/main.osl
```

This runs the same project diagnostic path, reports diagnostics from every reachable imported OSL file, and returns a failure status for errors.

## Formatting

The editor and `osl fmt` use the same formatter:

```bash
osl fmt src/
```

The formatter preserves comments and string contents. It leaves a file unchanged when parsing fails.


# Package index

OSL ships its standard library as importable packages:

```osl
import "std:fs"

log fs.readFile("notes.txt")
```

An import adds a package value with the same name. For example, `std:fs` adds `fs`. Use the `std:` prefix in new code. The older `osl/` spelling still works, but the compiler reports a migration warning.

## How imports work

| Form                                   | Meaning                                             |
| -------------------------------------- | --------------------------------------------------- |
| `import "std:fs"`                      | A standard-library package listed below             |
| `import "./helpers.osl"`               | A local OSL file, relative to the importing file    |
| `import "utils"` or `import "./utils"` | Every `.osl` file directly inside a local directory |
| `import "git.rotur.dev/me/tools"`      | A Git dependency installed by Opal                  |
| `import "go:net/http"`                 | A Go package                                        |

Directory imports sort files by name and do not recurse. Import or re-export child directories yourself.

Missing third-party Go dependencies are fetched automatically when you compile.

## Module objects

The statement form merges another file's declarations into the current scope. Its top-level statements run once, where OSL first imports the file. The expression form returns its public API as an object instead:

```osl
object math = import("./math.osl")

log math.add(2, 3)
log math.square(4)
```

Without an export statement, a file exposes all of its declarations. Adding an export statement turns on an explicit public API for that file:

```osl
export {add, subtract as difference}
export {Vector} from "./geometry/vector.osl"
export * as geometry from "./geometry"
```

Consumers can import the full API, selected names, or a namespace:

```osl
import * from "./math.osl"
import {add, subtract as difference} from "./math.osl"
import * as math from "./math.osl"
```

Files in the same directory can share private declarations. Consumers in another directory see only exported declarations. Missing, duplicate, and conflicting exports are compile errors.

```osl
// math.osl
def _double(number n) number (
  return n * 2
)

def square(number n) number (
  return n * n
)

def quadruple(number n) number (
  return _double(_double(n))
)
```

Here `math.square` and `math.quadruple` are callable from outside. `import(...)` works with local `.osl` files whose path is a string literal.

## Returned objects

Some packages give you an **object** to keep working with. For example, `db.open()` returns a database handle, and you call further methods on *that*:

```osl
import "std:db"

*db.DB handle = db.open("app.db")
handle.exec("CREATE TABLE users (id INTEGER, name TEXT)")
array rows = handle.query("SELECT * FROM users")
```

Each package page lists handle methods in a separate section.

## The standard library at a glance

### Web & networking

| Package                                          | Description                                                  |
| ------------------------------------------------ | ------------------------------------------------------------ |
| [serve](/standard-library/web/serve)             | HTTP server / web framework (routing, middleware, contexts). |
| [ws](/standard-library/web/ws)                   | WebSocket client and server.                                 |
| [originchats](/standard-library/web/originchats) | Bot framework for OriginChats servers.                       |
| [requests](/standard-library/web/requests)       | HTTP client methods including `get`, `post`, and `put`.      |
| [net](/standard-library/web/net)                 | Low-level TCP/UDP sockets and DNS lookups.                   |
| [url](/standard-library/web/url)                 | URL parsing, building and query-string handling.             |
| [ftp](/standard-library/web/ftp)                 | FTP file transfers.                                          |
| [ssh](/standard-library/web/ssh)                 | SSH connections, remote commands and SCP.                    |
| [s3](/standard-library/web/s3)                   | S3-compatible object storage client.                         |
| [webpush](/standard-library/web/webpush)         | Web Push notifications (VAPID).                              |

### Data & serialization

| Package                                     | Description                                        |
| ------------------------------------------- | -------------------------------------------------- |
| [json](/standard-library/data/json)         | JSON parsing and encoding.                         |
| [yaml](/standard-library/data/yaml)         | YAML parsing and encoding.                         |
| [schema](/standard-library/data/schema)     | Composable validation and normalization schemas.   |
| [csv](/standard-library/data/csv)           | CSV parsing plus a small dataframe-style toolkit.  |
| [xml](/standard-library/data/xml)           | XML parsing and querying.                          |
| [template](/standard-library/data/template) | Lightweight `{{ }}` templating with HTML escaping. |
| [md](/standard-library/data/md)             | Markdown to HTML (CommonMark + GFM via goldmark).  |
| [mime](/standard-library/data/mime)         | MIME-type lookup and parsing.                      |
| [diff](/standard-library/data/diff)         | Text/line/word diffing.                            |

### Databases & storage

| Package                                  | Description                                           |
| ---------------------------------------- | ----------------------------------------------------- |
| [db](/standard-library/storage/db)       | Embedded SQLite - SQL plus a document/collection API. |
| [save](/standard-library/storage/save)   | Simple persistent key-value storage.                  |
| [cache](/standard-library/storage/cache) | In-memory LRU cache with TTLs.                        |
| [env](/standard-library/storage/env)     | Environment variables and `.env` files.               |

### Filesystem & system

| Package                                     | Description                                                          |
| ------------------------------------------- | -------------------------------------------------------------------- |
| [fs](/standard-library/system/fs)           | Files, directories and path utilities.                               |
| [mem](/standard-library/system/mem)         | Runtime memory counters, heap snapshots, and live pprof diagnostics. |
| [sys](/standard-library/system/sys)         | System info, environment, and running shell commands.                |
| [process](/standard-library/system/process) | Spawn, manage and signal processes.                                  |
| [zip](/standard-library/system/zip)         | ZIP / TAR / GZIP compression.                                        |

### Crypto & security

| Package                                   | Description                                                    |
| ----------------------------------------- | -------------------------------------------------------------- |
| [crypto](/standard-library/crypto/crypto) | Hashing, HMAC, AES, password hashing, file encryption, random. |
| [jwt](/standard-library/crypto/jwt)       | JSON Web Token signing and verification.                       |

### Text, math & time

| Package                                     | Description                                                      |
| ------------------------------------------- | ---------------------------------------------------------------- |
| [emoji](/standard-library/mathtime/emoji)   | Emoji detection, extraction, replacement, flags, and skin tones. |
| [regex](/standard-library/mathtime/regex)   | Regular expressions plus validators and text helpers.            |
| [semver](/standard-library/mathtime/semver) | Semantic-version parsing and comparison.                         |
| [math](/standard-library/mathtime/math)     | Maths, statistics and number theory.                             |
| [random](/standard-library/mathtime/random) | Seedable pseudo-random numbers.                                  |
| [date](/standard-library/mathtime/date)     | Dates, durations and time zones.                                 |
| [cron](/standard-library/mathtime/cron)     | Cron-style job scheduling.                                       |
| [retry](/standard-library/utilities/retry)  | Bounded retries with exponential backoff and jitter.             |

### Terminal & logging

| Package                                     | Description                                                  |
| ------------------------------------------- | ------------------------------------------------------------ |
| [tui](/standard-library/terminal/tui)       | Terminal UI: colours, boxes, tables, prompts, menus, charts. |
| [log](/standard-library/terminal/log)       | Levelled, colourful logging.                                 |
| [notify](/standard-library/terminal/notify) | Desktop notifications.                                       |

### Media & documents

| Package                                  | Description                                             |
| ---------------------------------------- | ------------------------------------------------------- |
| [img](/standard-library/media/img)       | Load, transform and save images.                        |
| [qr](/standard-library/media/qr)         | QR codes and barcodes.                                  |
| [pdf](/standard-library/media/pdf)       | Generate PDF documents.                                 |
| [canvas](/standard-library/media/canvas) | In-memory pixel canvas.                                 |
| [colors](/standard-library/media/colors) | Build colour values (used by image-producing packages). |
| [sound](/standard-library/media/sound)   | Audio playback.                                         |

### Graphics & windowing

| Package                                                             | Description                                                                    |
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| [shader](/standard-library/graphics-and-windowing/shader)           | GLSL and OSL-style shader transpilation and rendering to images and windows.   |
| [raylib](/standard-library/graphics-and-windowing/raylib)           | Native windowing, input, 2D drawing, collision helpers, shaders, and textures. |
| [window](/standard-library/graphics-and-windowing/window)           | Open a window and draw to it (the originOS graphics model).                    |
| [win-buttons](/standard-library/graphics-and-windowing/win-buttons) | Install native window controls.                                                |

### Scripting & concurrency

| Package                                          | Description                                                         |
| ------------------------------------------------ | ------------------------------------------------------------------- |
| [testing](/standard-library/concurrency/testing) | Assertions and the `osl test` workflow.                             |
| [js](/standard-library/concurrency/js)           | Run sandboxed JavaScript with hard resource limits.                 |
| [lua](/standard-library/concurrency/lua)         | Embed and run Lua scripts.                                          |
| [thread](/standard-library/concurrency/thread)   | Background threads, parallel workers, racing, and message channels. |
| [sync](/standard-library/concurrency/sync)       | Named locks, scoped locking, one-time execution, and wait groups.   |

### Utilities & data structures

| Package                                      | Description                                               |
| -------------------------------------------- | --------------------------------------------------------- |
| [box2d](/standard-library/utilities/box2d)   | Box2D-compatible rigid-body worlds, bodies, and fixtures. |
| [map](/standard-library/utilities/map)       | An ordered key-value map type.                            |
| [set](/standard-library/utilities/set)       | A set type.                                               |
| [option](/standard-library/utilities/option) | Optional values (`some`/`none`).                          |
| [result](/standard-library/utilities/result) | Success/error result values.                              |
| [ptr](/standard-library/utilities/ptr)       | Low-level pointer operations.                             |

### More

| Package                                   | Description                        |
| ----------------------------------------- | ---------------------------------- |
| [email](/standard-library/more/email)     | Compose and send email (SMTP).     |
| [torrent](/standard-library/more/torrent) | Create and parse `.torrent` files. |

To read a package's source directly from the CLI:

```bash
osl package fs       # print the fs package source
osl package          # list every available package
```


# Web and networking

Packages for network and web operations.

* [serve](/standard-library/web/serve) - HTTP server / web framework (routing, middleware, contexts).
* [ws](/standard-library/web/ws) - WebSocket client and server.
* [originchats](/standard-library/web/originchats) - Bot framework for OriginChats servers.
* [requests](/standard-library/web/requests) - HTTP client methods including `get`, `post`, and `put`.
* [net](/standard-library/web/net) - Low-level TCP/UDP sockets and DNS lookups.
* [url](/standard-library/web/url) - URL parsing, building and query-string handling.
* [ftp](/standard-library/web/ftp) - FTP file transfers.
* [ssh](/standard-library/web/ssh) - SSH connections, remote commands and SCP.
* [s3](/standard-library/web/s3) - S3-compatible object storage client.
* [webpush](/standard-library/web/webpush) - Web Push notifications (VAPID).


# serve

`serve` is OSL's web framework. It gives you a router, request/response **contexts**, middleware, route groups, static-file serving, WebSockets and TLS.

```osl
import "std:serve"
```

## Quick start

```osl
import "std:serve"

*serve.Router app = serve.new()

app.GET("/", def(*serve.Context c) -> (
  c.string(200, "Hello, World!")
))

app.GET("/ping", def(*serve.Context c) -> (
  c.json(200, { message: "pong" })
))

log "Listening on http://localhost:8080"
app.serve(":8080")
```

A **handler** is a function that takes a `*serve.Context` and writes a response. Create the router with `serve.new()`, register routes, then call `serve(addr)` to start listening (this blocks).

## Routing

Register a handler for each HTTP method:

```osl
app.GET("/users", listUsers)
app.POST("/users", createUser)
app.PUT("/users/:id", replaceUser)
app.PATCH("/users/:id", updateUser)
app.DELETE("/users/:id", deleteUser)
app.ANY("/health", healthCheck)     // any method
```

### Route parameters

Use `:name` in a pattern and read it with `c.param(...)`:

```osl
app.GET("/users/:id", def(*serve.Context c) -> (
  string id = c.param("id")
  c.json(200, { id: id })
))
```

## The Context

The `*serve.Context` (named `c` by convention) is how you read the request and write the response.

### Reading the request

```osl
c.method()                 // "GET", "POST", …
c.path()                   // "/users/42"
c.param("id")              // a route parameter
c.query("q")               // ?q=...   (queryDefault, queryInt, queryBool also exist)
c.header("X-Token")        // a request header
c.bearer()                 // the Bearer token from Authorization
c.body()                   // raw body as a string
c.bodyJSON()               // body parsed as an object
c.cookie("session")        // a cookie value
c.formValue("email")       // a form field
```

### Writing the response

```osl
c.string(200, "plain text")
c.json(200, { ok: true })
c.html(200, "<h1>Hi</h1>")
c.data(200, "image/png", bytes)
c.redirect(302, "/login")
c.noContent()                          // 204
c.file("./public/report.pdf")          // send a file
c.attachment("./r.pdf", "report.pdf")  // force download

// Convenience helpers
c.ok({ data: 1 })                      // 200
c.created({ id: 5 })                   // 201
c.badRequest("missing field")          // 400
c.unauthorized("login required")       // 401
c.notFound("no such user")             // 404
c.internalError("oops")                // 500

// Cookies and headers
c.setHeader("X-App", "osl")
c.setCookie("session", token, 3600, "/", "", true, true)
```

### Per-request values

Middleware and handlers can stash values on the context:

```osl
c.set("userId", 42)
int id = c.getInt("userId")
```

## Middleware

Middleware are handlers that run before your route handler. Register them with `use(...)`; call `c.next()` to continue or one of the response helpers to stop. The framework ships ready-made middleware:

```osl
app.use(serve.logger())
app.use(serve.cors("*", "GET,POST", "Content-Type"))
app.use(serve.recover())               // recover from panics → 500
app.use(serve.rateLimit(100, 60))      // 100 requests per 60s
app.use(serve.requireBearer("secret")) // require a Bearer token
app.use(serve.secureHeaders())
app.use(serve.requestID())
```

Other built-in middleware: `corsOpen()`, `requireHeader(key, value)`, `maxBodySize(bytes)`, `timeout(seconds)`, `basicAuth(user, pass)`, `noCache()`, `setKey(key, value)`.

`timeout` buffers downstream output until the handler completes and cancels the request context at the deadline. Do not place streaming, flushing, or WebSocket handlers behind it.

Even without `serve.recover()`, a handler that throws never crashes the server: the error is printed to stderr as a formatted OSL runtime error (with the source line that caused it) and the client gets a plain `500 Internal Server Error`. Use `serve.recover()` when you want the error text in the response body instead.

Writing your own is just a handler:

```osl
def auth(*serve.Context c) -> (
  if c.bearer() == "" (
    c.unauthorized("no token")
  ) else (
    c.next()
  )
)

app.use(auth)
```

## Route groups

Group related routes under a shared prefix (and shared middleware):

```osl
*serve.Router api = app.group("/api")
api.use(serve.requireBearer("secret"))
api.GET("/users", listUsers)
api.GET("/posts", listPosts)
```

## Static files

```osl
app.static("/assets", "./public")           // serve a directory
app.staticFile("/favicon.ico", "./fav.ico") // serve a single file
```

## HTML templates

Load Go `html/template` files with `loadHTMLGlob`, then render one by name with `c.html(code, name, data)`. Register custom template functions with `setFuncMap`. call it **before** `loadHTMLGlob` so the parsed templates can see them:

```osl
def upper(string s) string (
  return s.toUpper()
)

*serve.Router app = serve.new()
app.setFuncMap({"upper": upper})
app.loadHTMLGlob("templates/**/*.html")

app.GET("/", def(*serve.Context c) -> (
  c.html(200, "home.html", { name: "world" })
))
```

Templates are named by their base filename, plus any `{{define "name"}}` blocks.

## OSL-native pages (`render` + layouts)

For OSL apps, prefer [`osl/template`](/standard-library/data/template) over Go's `html/template`. Point the router at a views directory with `views(dir)`, optionally set a wrapping `layout(name)`, then respond with `c.render(name, data)`. Views are `<dir>/<name>.html` and render through `template.renderHTML`. Values are HTML-escaped by default. Use `{{& field}}` for trusted raw HTML (e.g. Markdown you rendered with [`md`](/standard-library/data/md)).

The layout receives the rendered page as `body`; emit it raw with `{{& body}}`.

```osl
import "std:serve"
import "std:md"

// views/layout.html →  <!doctype html><title>{{ title }}</title><main>{{& body}}</main>
// views/post.html   →  <h1>{{ title }}</h1><article>{{& html}}</article>

*serve.Router app = serve.new()
app.views("views")
app.layout("layout")

app.GET("/", def(*serve.Context c) -> (
  c.render("post", {
    title: "Hello",
    html:  md.toHTML("**bold** and _italic_")   // composed, not reimplemented
  })
))
```

`render` always responds `200`. Set other statuses with `c.html`/`c.string`, or render the body yourself and pass it to `c.send(code, "text/html", body)`.

## CORS preflight

An `OPTIONS` request to a route with no explicit `OPTIONS` handler runs the router's middleware chain (so `serve.cors(...)` / `serve.corsOpen()` can answer the preflight) and responds `204` with an `Allow` header if no middleware wrote a response.

## WebSockets

Attach a [`ws`](/standard-library/web/ws) server to a route with `app.WS`. HTTP and websockets can share a path. Upgrade requests go to the socket, while other requests reach the HTTP handlers. You can also upgrade from inside a handler with `c.isWebsocket()` / `c.upgrade(socket)`.

```osl
import "std:serve"
import "std:ws"

*serve.Router app = serve.new()
auto socket = ws.New()   // no listen address; serve owns the port

socket.OnMessage(def(*ws.Connection conn, string msg) -> (
  conn.Send("echo: " ++ msg)
))

// Dedicated path
app.WS("/chat", socket)

// HTTP and WebSocket routes can share a path:
app.GET("/", def(*serve.Context c) -> (
  c.string(200, "open a websocket on /")
))
app.WS("/", socket)

app.serve(":8080")
```

## HTTPS / TLS

```osl
app.serveTLS(":443", "cert.pem", "key.pem")
```

## Method reference

### Router (`serve.new()` → `*serve.Router`)

* `app.GET(pattern, ...handlers)` · `POST` · `PUT` · `PATCH` · `DELETE` · `OPTIONS` · `HEAD` · `ANY`
* `app.WS(pattern, wsServer)`
* `app.use(...handlers)` → `*serve.Router`
* `app.group(prefix, fn?)` → `*serve.Router`
* `app.static(prefix, dir)` · `app.staticFile(pattern, filepath)`
* `app.setFuncMap(funcs)` - register template functions (call before `loadHTMLGlob`)
* `app.loadHTMLGlob(pattern)` - parse HTML templates for `c.html(code, name, data)`
* `app.views(dir)` - set the directory for `c.render` views (`<dir>/<name>.html`)
* `app.layout(name)` - wrap `c.render` output in `views/<name>.html` via `{{& body}}`
* `app.serve(addr)` - start the server (blocks)
* `app.serveTLS(addr, certFile, keyFile)`
* `app.handler()` → the underlying HTTP handler

### Middleware factories (on `serve`)

`logger()`, `cors(allowOrigin, allowMethods, allowHeaders)`, `corsOpen()`, `rateLimit(max, windowSeconds)`, `requireBearer(token)`, `requireHeader(key, value)`, `maxBodySize(bytes)`, `recover()`, `timeout(seconds)`, `setKey(key, value)`, `basicAuth(user, pass)`, `requestID()`, `secureHeaders()`, `noCache()`.

### Context (`*serve.Context`)

**Read:** `method()`, `path()`, `host()`, `ip()`, `param(k)`, `query(k)`, `queryDefault(k, d)`, `queryInt(k, d)`, `queryBool(k, d)`, `queryArray(k)`, `header(k)`, `headers()`, `bearer()`, `body()`, `bodyBytes()`, `bodyJSON()`, `bodyJSONArray()`, `formValue(k)`, `formFile(k)`, `cookie(k)`, `cookies()`, `userAgent()`, `referer()`, `isJSON()`, `isForm()`, `isWebSocket()` / `isWebsocket()`, `isAjax()`, `contentType()`, `fullURL()`.

**Write:** `status(code)`, `string(code, text)`, `json(code, obj)`, `html(code, body)`, `text(code, body)`, `data(code, contentType, bytes)`, `redirect(code, url)`, `noContent()`, `ok(obj)`, `created(obj)`, `badRequest(msg)`, `unauthorized(msg)`, `forbidden(msg)`, `notFound(msg)`, `internalError(msg)`, `file(path)`, `attachment(path, name)`, `setHeader(k, v)`, `addHeader(k, v)`, `setCookie(...)`, `clearCookie(name)`, `upgrade(wsServer)` / `Upgrade(wsServer)`.

**Flow & state:** `next()`, `abort(...)`, `isAborted()`, `set(k, v)`, `get(k)`, `getString(k)`, `getInt(k)`, `getBool(k)`, `getFloat(k)`.

## Complete API reference

### `serve`

| Method                                                                        | Returns        | Notes                                                                                                                                                |
| ----------------------------------------------------------------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `serve.new()`                                                                 | `*serveRouter` |                                                                                                                                                      |
| `serve.New()`                                                                 | `*serveRouter` |                                                                                                                                                      |
| `serve.logger()`                                                              | `serveHandler` |                                                                                                                                                      |
| `serve.cors(allowOrigin: string, allowMethods: string, allowHeaders: string)` | `serveHandler` |                                                                                                                                                      |
| `serve.corsOpen()`                                                            | `serveHandler` |                                                                                                                                                      |
| `serve.rateLimit(maxRequests: number, windowSeconds: number)`                 | `serveHandler` | Limits requests per client without a background worker; nonpositive windows use one second.                                                          |
| `serve.requireBearer(token: string)`                                          | `serveHandler` |                                                                                                                                                      |
| `serve.requireHeader(key: string, value: string)`                             | `serveHandler` |                                                                                                                                                      |
| `serve.maxBodySize(maxBytes: number)`                                         | `serveHandler` |                                                                                                                                                      |
| `serve.recover()`                                                             | `serveHandler` |                                                                                                                                                      |
| `serve.timeout(seconds: number)`                                              | `serveHandler` | Cancels the downstream request context at the deadline and returns a buffered 503 response without allowing late handler writes to reach the client. |
| `serve.setKey(key: string, value: any)`                                       | `serveHandler` | Sets key.                                                                                                                                            |
| `serve.basicAuth(username: string, password: string)`                         | `serveHandler` |                                                                                                                                                      |
| `serve.requestID()`                                                           | `serveHandler` |                                                                                                                                                      |
| `serve.secureHeaders()`                                                       | `serveHandler` |                                                                                                                                                      |
| `serve.noCache()`                                                             | `serveHandler` |                                                                                                                                                      |

### `serveContext` values

| Method                                                                                                                           | Returns   | Notes                                                                                                                       |
| -------------------------------------------------------------------------------------------------------------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------- |
| `value.status(code: number)`                                                                                                     | `void`    |                                                                                                                             |
| `value.string(code: number, format: string, ...values: any)`                                                                     | `void`    |                                                                                                                             |
| `value.json(code: number, obj: any)`                                                                                             | `void`    |                                                                                                                             |
| `value.html(code: number, body: string, ...data: any)`                                                                           | `void`    |                                                                                                                             |
| `value.HTML(code: number, name: string, data: any)`                                                                              | `void`    |                                                                                                                             |
| `value.render(name: string, data: object)`                                                                                       | `void`    | Renders `views/<name>.html` via `osl/template` (escaped; `{{& x}}` for raw), wraps in the layout if set, responds `200`.    |
| `value.data(code: number, contentType: string, body: byte[])`                                                                    | `void`    | Sends a byte body with the given content type.                                                                              |
| `value.redirect(code: number, url: string)`                                                                                      | `void`    |                                                                                                                             |
| `value.noContent()`                                                                                                              | `void`    |                                                                                                                             |
| `value.ok(obj: any)`                                                                                                             | `void`    |                                                                                                                             |
| `value.created(obj: any)`                                                                                                        | `void`    |                                                                                                                             |
| `value.next()`                                                                                                                   | `void`    |                                                                                                                             |
| `value.abort(...values: any)`                                                                                                    | `void`    |                                                                                                                             |
| `value.isAborted()`                                                                                                              | `boolean` |                                                                                                                             |
| `value.badRequest(message: string)`                                                                                              | `void`    |                                                                                                                             |
| `value.unauthorized(message: string)`                                                                                            | `void`    |                                                                                                                             |
| `value.forbidden(message: string)`                                                                                               | `void`    |                                                                                                                             |
| `value.notFound(message: string)`                                                                                                | `void`    |                                                                                                                             |
| `value.internalError(message: string)`                                                                                           | `void`    |                                                                                                                             |
| `value.flush()`                                                                                                                  | `void`    |                                                                                                                             |
| `value.method()`                                                                                                                 | `string`  |                                                                                                                             |
| `value.path()`                                                                                                                   | `string`  |                                                                                                                             |
| `value.host()`                                                                                                                   | `string`  |                                                                                                                             |
| `value.remoteAddr()`                                                                                                             | `string`  |                                                                                                                             |
| `value.ip()`                                                                                                                     | `string`  |                                                                                                                             |
| `value.isWebSocket()`                                                                                                            | `boolean` | `true` when the request is a WebSocket upgrade.                                                                             |
| `value.isWebsocket()`                                                                                                            | `boolean` | Same as `isWebSocket()` with the more natural OSL casing.                                                                   |
| `value.upgrade(server: *wsServer)`                                                                                               | `boolean` | Hijacks this request into the given websocket server. Returns `false` if already written, not an upgrade, or server is nil. |
| `value.Upgrade(server: *wsServer)`                                                                                               | `boolean` | Alias of `upgrade`.                                                                                                         |
| `value.contentType()`                                                                                                            | `string`  |                                                                                                                             |
| `value.isJSON()`                                                                                                                 | `boolean` |                                                                                                                             |
| `value.isForm()`                                                                                                                 | `boolean` |                                                                                                                             |
| `value.query(key: string)`                                                                                                       | `string`  |                                                                                                                             |
| `value.queryDefault(key: string, def: string)`                                                                                   | `string`  |                                                                                                                             |
| `value.queryInt(key: string, def: number)`                                                                                       | `number`  | Parses an integer query value, returning the default when absent or invalid.                                                |
| `value.queryBool(key: string, def: boolean)`                                                                                     | `boolean` |                                                                                                                             |
| `value.queryAll()`                                                                                                               | `object`  |                                                                                                                             |
| `value.param(key: string)`                                                                                                       | `string`  |                                                                                                                             |
| `value.paramInt(key: string, def: number)`                                                                                       | `number`  | Parses an integer route parameter, returning the default when absent or invalid.                                            |
| `value.header(key: string)`                                                                                                      | `string`  |                                                                                                                             |
| `value.headers()`                                                                                                                | `object`  | Every request header as an object (single values are strings; multi-value headers become arrays).                           |
| `value.Headers()`                                                                                                                | `object`  | Alias of `headers`.                                                                                                         |
| `value.hasHeader(key: string, value: string)`                                                                                    | `boolean` |                                                                                                                             |
| `value.setHeader(key: string, value: string)`                                                                                    | `void`    | Sets header.                                                                                                                |
| `value.addHeader(key: string, value: string)`                                                                                    | `void`    | Adds header.                                                                                                                |
| `value.bearer()`                                                                                                                 | `string`  |                                                                                                                             |
| `value.body()`                                                                                                                   | `string`  |                                                                                                                             |
| `value.bodyBytes()`                                                                                                              | `byte[]`  | Returns the request body bytes.                                                                                             |
| `value.bodyJSON()`                                                                                                               | `object`  |                                                                                                                             |
| `value.bodyJSONArray()`                                                                                                          | `array`   |                                                                                                                             |
| `value.bindJSON(out: any)`                                                                                                       | `error`   |                                                                                                                             |
| `value.formValue(key: string)`                                                                                                   | `string`  |                                                                                                                             |
| `value.formValueDefault(key: string, def: string)`                                                                               | `string`  |                                                                                                                             |
| `value.formFile(key: string)`                                                                                                    | `*Result` |                                                                                                                             |
| `value.cookie(name: string)`                                                                                                     | `string`  |                                                                                                                             |
| `value.setCookie(name: string, value: string, maxAge: number, path: string, domain: string, secure: boolean, httpOnly: boolean)` | `void`    | Sets cookie.                                                                                                                |
| `value.clearCookie(name: string)`                                                                                                | `void`    |                                                                                                                             |
| `value.set(key: string, value: any)`                                                                                             | `void`    | Sets a value.                                                                                                               |
| `value.get(key: string)`                                                                                                         | `any`     | Returns a value.                                                                                                            |
| `value.getString(key: string)`                                                                                                   | `string`  | Returns string.                                                                                                             |
| `value.getBool(key: string)`                                                                                                     | `boolean` | Returns bool.                                                                                                               |
| `value.getInt(key: string)`                                                                                                      | `number`  | Returns int.                                                                                                                |
| `value.written()`                                                                                                                | `boolean` |                                                                                                                             |
| `value.text(code: number, body: string)`                                                                                         | `void`    |                                                                                                                             |
| `value.file(filepath: string)`                                                                                                   | `void`    |                                                                                                                             |
| `value.attachment(filepath: string, filename: string)`                                                                           | `void`    |                                                                                                                             |
| `value.queryArray(key: string)`                                                                                                  | `array`   |                                                                                                                             |
| `value.cookies()`                                                                                                                | `object`  |                                                                                                                             |
| `value.userAgent()`                                                                                                              | `string`  |                                                                                                                             |
| `value.referer()`                                                                                                                | `string`  |                                                                                                                             |
| `value.isAjax()`                                                                                                                 | `boolean` |                                                                                                                             |
| `value.scheme()`                                                                                                                 | `string`  |                                                                                                                             |
| `value.fullURL()`                                                                                                                | `string`  |                                                                                                                             |
| `value.accepts(mimeType: string)`                                                                                                | `boolean` |                                                                                                                             |
| `value.getFloat(key: string)`                                                                                                    | `number`  | Returns float.                                                                                                              |
| `value.redirectPermanent(url: string)`                                                                                           | `void`    |                                                                                                                             |
| `value.basicAuth()`                                                                                                              | `object`  |                                                                                                                             |

### `serveRouter` values

| Method                                                            | Returns        | Notes                                                                                                                             |
| ----------------------------------------------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `value.GET(pattern: string, ...handlers: serveHandler)`           | `void`         | Registers a GET route handler.                                                                                                    |
| `value.POST(pattern: string, ...handlers: serveHandler)`          | `void`         | Registers a POST route handler.                                                                                                   |
| `value.PUT(pattern: string, ...handlers: serveHandler)`           | `void`         | Registers a PUT route handler.                                                                                                    |
| `value.PATCH(pattern: string, ...handlers: serveHandler)`         | `void`         | Registers a PATCH route handler.                                                                                                  |
| `value.DELETE(pattern: string, ...handlers: serveHandler)`        | `void`         | Registers a DELETE route handler.                                                                                                 |
| `value.OPTIONS(pattern: string, ...handlers: serveHandler)`       | `void`         | Registers a OPTIONS route handler.                                                                                                |
| `value.HEAD(pattern: string, ...handlers: serveHandler)`          | `void`         | Registers a HEAD route handler.                                                                                                   |
| `value.ANY(pattern: string, ...handlers: serveHandler)`           | `void`         | Registers a ANY route handler.                                                                                                    |
| `value.WS(pattern: string, server: *wsServer)`                    | `void`         | Mounts a WebSocket server on `pattern`. Upgrades reach the socket; other requests continue to the HTTP handlers on the same path. |
| `value.static(prefix: string, dir: string)`                       | `void`         |                                                                                                                                   |
| `value.staticFile(pattern: string, filepath: string)`             | `void`         |                                                                                                                                   |
| `value.loadHTMLGlob(pattern: string)`                             | `error`        | Loads htmlglob.                                                                                                                   |
| `value.LoadHTMLGlob(pattern: string)`                             | `error`        | Loads htmlglob.                                                                                                                   |
| `value.views(dir: string)`                                        | `*serveRouter` | Sets the views directory for `c.render`.                                                                                          |
| `value.layout(name: string)`                                      | `*serveRouter` | Sets the layout template wrapping `c.render` output.                                                                              |
| `value.use(...handlers: serveHandler)`                            | `*serveRouter` |                                                                                                                                   |
| `value.group(prefix: string, ...fn?: func(router))`               | `*serveRouter` | Creates a route group with optional router callback functions.                                                                    |
| `value.Use(...handlers: serveHandler)`                            | `*serveRouter` |                                                                                                                                   |
| `value.Group(prefix: string, fn?: function)`                      | `*serveRouter` | Creates a route group with an optional setup callback.                                                                            |
| `value.Static(prefix: string, dir: string)`                       | `void`         |                                                                                                                                   |
| `value.StaticFile(pattern: string, filepath: string)`             | `void`         |                                                                                                                                   |
| `value.Run(addr: string)`                                         | `error`        |                                                                                                                                   |
| `value.run(addr: string)`                                         | `error`        |                                                                                                                                   |
| `value.RunTLS(addr: string, certFile: string, keyFile: string)`   | `error`        | Runs tls.                                                                                                                         |
| `value.runTLS(addr: string, certFile: string, keyFile: string)`   | `error`        | Runs tls.                                                                                                                         |
| `value.Handler()`                                                 | `http.Handler` |                                                                                                                                   |
| `value.serve(addr: string)`                                       | `error`        | Starts the active HTTP server and blocks until it stops.                                                                          |
| `value.serveTLS(addr: string, certFile: string, keyFile: string)` | `error`        | Starts the active HTTPS server and blocks until it stops.                                                                         |
| `value.handler()`                                                 | `http.Handler` |                                                                                                                                   |
| `value.stop()`                                                    | `boolean`      | Blocks new WebSocket registrations, closes the HTTP listener, and drains mounted WebSockets; repeated calls are safe.             |

## Notes

* Prefer `import "std:serve"`; the older `import "osl/serve"` spelling remains supported.

## Behavior and limits

Body helpers cache the request bytes, so reading JSON, forms, or a bound value does not consume the body for later helpers. Static routes reject path traversal through symbolic links. Client IP parsing accepts IPv6. Panic recovery and shutdown have time limits.

Shutdown rejects new WebSocket upgrades before closing the listener. When `stop` returns, no upgrade that started during shutdown can leave a connection running.


# ws

Use `ws` for WebSocket clients and servers, connection callbacks, broadcast, and per-connection state.

```osl
import "std:ws"
```

## API reference

### `ws`

| Method                                          | Returns         | Notes                                                                                                                                                          |
| ----------------------------------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ws.Connect(url: string, ...protocols: string)` | `*wsConnection` | Opens a WebSocket client connection.                                                                                                                           |
| `ws.New(...args: string)`                       | `*wsServer`     | Builds a server meant to be mounted on `serve` (`app.WS`, `c.upgrade`). No listen address. Optional: `New(path)` or `New(addr, path)`. Path defaults to `"/"`. |
| `ws.NewServer(addr: string, path: string)`      | `*wsServer`     | Builds a standalone server that can `Start`/`StartTLS` on `addr+path`, or still be mounted on serve like `New()`.                                              |

### `wsConnection` values

| Method                               | Returns   | Notes                                                                                                                             |
| ------------------------------------ | --------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `value.Send(message: any)`           | `void`    |                                                                                                                                   |
| `value.Close()`                      | `void`    |                                                                                                                                   |
| `value.EnableReconnect()`            | `void`    |                                                                                                                                   |
| `value.Connected()`                  | `boolean` | Reports whether the connection currently owns a live WebSocket session. Returns `false` during reconnect backoff and after close. |
| `value.Shutdown()`                   | `void`    | Disables reconnect, cancels pending backoff, and closes the connection without allowing a replacement session.                    |
| `value.Set(key: string, value: any)` | `void`    |                                                                                                                                   |
| `value.Delete(key: string)`          | `void`    |                                                                                                                                   |
| `value.Get(key: string)`             | `any`     | Returns stored connection data.                                                                                                   |
| `value.GetAll()`                     | `object`  | Returns stored connection data.                                                                                                   |
| `value.GetHeader(key: string)`       | `string`  | Returns stored connection data.                                                                                                   |
| `value.GetHeaders()`                 | `object`  | Returns stored connection data.                                                                                                   |
| `value.OnMessage(handler: function)` | `void`    | Registers a callback for incoming messages.                                                                                       |
| `value.OnClose(handler: function)`   | `void`    | Registers a callback for connection close.                                                                                        |

### `wsServer` values

| Method                                              | Returns            | Notes                                                                                                         |
| --------------------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------- |
| `value.OnConnect(handler: function)`                | `void`             | Registers a callback for new connections.                                                                     |
| `value.OnMessage(handler: function)`                | `void`             | Registers a callback for incoming messages.                                                                   |
| `value.OnDisconnect(handler: function)`             | `void`             | Registers a callback for disconnected clients.                                                                |
| `value.AllowAllOrigins()`                           | `void`             | Allows browser WebSocket upgrades from any origin. Call only for intentionally public cross-origin endpoints. |
| `value.Broadcast(message: string)`                  | `void`             |                                                                                                               |
| `value.GetConnections()`                            | `array`            | Returns connections.                                                                                          |
| `value.Start()`                                     | `error`            | Starts the standalone HTTP WebSocket server and blocks.                                                       |
| `value.StartTLS(certFile: string, keyFile: string)` | `error`            | Starts the standalone HTTPS WebSocket server and blocks.                                                      |
| `value.HandleWebSocket()`                           | `http.HandlerFunc` |                                                                                                               |
| `value.Stop()`                                      | `error`            |                                                                                                               |

## Mounting on serve

Prefer `ws.New()` when the HTTP server owns the listener:

```osl
import "std:serve"
import "std:ws"

auto socket = ws.New()
socket.OnMessage(def(*ws.Connection conn, string msg) -> (
  conn.Send("echo: " ++ msg)
))
*serve.Router app = serve.new()
app.WS("/ws", socket)
app.serve(":8080")
```

`ws.NewServer(addr, path)` is for standalone servers that call `Start()` themselves.

#### `ws.send(connection, message)` → `boolean`

Sends through an untyped connection value and returns `false` when the value is not a connection, the connection is closed, the message is invalid, or its outbound queue is full.

#### `ws.closeConn(connection)` → `boolean`

Closes an untyped connection value safely. Repeated closes are harmless.

## Notes

* Prefer `import "std:ws"`; the older `import "osl/ws"` spelling remains supported.

## Behavior and limits

TLS uses normal certificate verification and servers enforce same-origin upgrades by default. Call `AllowAllOrigins()` before mounting or starting a server when it intentionally accepts browser clients from other origins. Upgrade handshakes time out after 10 seconds. Client and reconnect handshakes use the same timeout and requested subprotocols. A panic in a callback is recovered. Closing a connection more than once is safe. The send queue copies byte messages, so changing the caller's array later cannot change data already queued. Standalone servers reject overlapping `Start` or `StartTLS` calls. `Stop` makes the active start call return `null`; listener failures still return their error. Shutdown rejects new upgrades, closes the listener, and drains registered connections. The same server value can be started again afterwards.


# originchats

The `originchats` package is a batteries-included bot framework: it handles the WebSocket connection, the handshake → rotur validator → auth → ready flow, automatic reconnection, slash command registration, and request/response matching, so a bot is just handlers.

```osl
import "std:originchats"
```

A minimal bot:

```osl
import "std:originchats"

*originchats.client client = originchats.new("wss://chats.mistium.com")

client.command("!hello", def(*originchats.message msg) -> (
  msg.reply("Hello " ++ msg.user() ++ "!")
))

client.run(token)
```

The package exposes four types:

* `*originchats.client` - the connection and everything you do with it
* `*originchats.message` - a received (or just-sent) chat message
* `*originchats.slash` - a slash command invocation
* `*originchats.slashCommand` - a fluent builder for registering slash commands

## Creating & running a client

#### `originchats.new(url)` → `*originchats.client`

Creates a client for the server at `url` (e.g. `"wss://chats.mistium.com"`).

#### `client.run(token)`

Connects (retrying with backoff until the server is reachable) and blocks forever. `token` is the bot's rotur account token, used to fetch an auth validator during the handshake - pass `""` to skip rotur auth (e.g. local test servers). The connection auto-reconnects and re-authenticates if it drops. Call this last.

#### `client.connect(token)` → `boolean`

Non-blocking alternative to `run`: dials once and returns whether it connected. Use when your program has its own main loop.

#### `client.stop()`

Disables reconnection and closes the connection.

#### `client.connected()` → `boolean`

Whether the client has an active connection.

#### `client.ready()` → `boolean`

Whether the client has authenticated and received `ready` from the server.

#### `client.password(pw)` → `*originchats.client`

Sets the server password sent with `auth`, for password-protected servers. Chainable.

#### `client.ignoreSelf(v)` → `*originchats.client`

Whether the bot's own messages are skipped by `onMessageNew` and `command` handlers. Defaults to `true`. Raw event handlers always see everything. Chainable.

#### `client.timeout(seconds)` → `*originchats.client`

How long request/response calls (`send`, `reply`, `request`, `channels`, …) wait for the server before giving up. Defaults to 10 seconds. Chainable. Calls made while the socket is closed or waiting to reconnect return `not connected` immediately. Completed requests stop their timers, and `stop()` wakes every pending request with a `stopped` error.

## Event handlers

Handlers may be named functions or lambdas. Handlers run concurrently in their own goroutines, so it's safe to call blocking client methods inside them; a handler that panics logs the error instead of crashing the bot.

#### `client.onReady(handler)`

Called with the client (`def(*originchats.client c)`) once the server accepts authentication. Fires again after every reconnect.

```osl
client.onReady(def(*originchats.client c) -> (
  log "logged in as " ++ c.username()
))
```

#### `client.onMessageNew(handler)`

Called with a `*originchats.message` for every chat message that isn't handled by a prefix command (and isn't the bot's own, unless `ignoreSelf(false)`).

```osl
client.onMessageNew(def(*originchats.message msg) -> (
  log msg.user() ++ ": " ++ msg.content()
))
```

#### Dedicated event registrars

Each of these subscribes to one protocol event. The handler is called with the client and the raw event object: `def(*originchats.client c, object event)`.

* `client.onMessageEdit(handler)` - a message was edited
* `client.onMessageDelete(handler)` - a message was deleted
* `client.onReactionAdd(handler)` - a reaction was added (`event.channel`, `event.id`, `event.emoji`, `event.from`)
* `client.onReactionRemove(handler)` - a reaction was removed
* `client.onTyping(handler)` - a user is typing
* `client.onUserConnect(handler)` - a user connected
* `client.onUserDisconnect(handler)` - a user disconnected
* `client.onUserJoin(handler)` - a user joined the server for the first time
* `client.onUserLeave(handler)` - a user left (deleted their account)
* `client.onError(handler)` - the server sent an error packet
* `client.onRateLimit(handler)` - the bot was rate limited (`event.length` ms to wait)

```osl
client.onReactionAdd(def(*originchats.client c, object event) -> (
  log event.user.toStr() ++ " reacted with " ++ event.emoji.toStr()
))
```

#### `client.on(cmd, handler)`

Generic escape hatch for any protocol packet by its `cmd` name (`"voice_user_joined"`, `"unreads_update"`, …), with the same handler shape as the dedicated registrars. Fires in addition to the built-in handling, including for `message_new` and `slash_call`.

## Commands

#### `client.command(prefix, handler)`

Registers a prefix command. When a message starts with `prefix` (followed by a space or the end of the message), the handler is called with the message; `msg.content()` has the prefix already stripped. Matched messages don't reach `onMessageNew`.

```osl
client.command("!roll", def(*originchats.message msg) -> (
  msg.reply("You rolled a " ++ (random(1, 6)).toStr())
))
```

#### `client.slashCommand(name)` → `*originchats.slashCommand`

Starts a fluent slash command builder. The leading `/` in `name` is optional. Chain option and setting calls, then finish with `.fn(handler)` to register. Commands are sent to the server automatically on `ready` (and after every reconnect).

```osl
client.slashCommand("/weather")
  .description("Get the weather")
  .addInput(originchats.string, "city", "City name")
  .addInput(originchats.boolean, "detailed", "Include the full forecast", false)
  .fn(def(*originchats.slash call) -> (
    call.respond("It is sunny in " ++ call.argStr("city"))
  ))
```

Builder methods (all chainable):

* `.description(text)` - what the command does (defaults to the command name)
* `.addInput(type, name)` / `.addInput(type, name, description)` / `.addInput(type, name, description, required)` - adds an option. `type` is one of the input type constants below. `required` defaults to `true`.
* `.addOption(option)` - adds a raw option object (use for `enum` options with `choices`)
* `.whitelistRoles(roles)` - only these roles may use the command
* `.blacklistRoles(roles)` - these roles may not use the command
* `.ephemeral(v)` - responses are only visible to the caller
* `.fn(handler)` - registers the command; the handler receives a `*originchats.slash`

#### Input type constants

| Constant               | Protocol type | Meaning                                               |
| ---------------------- | ------------- | ----------------------------------------------------- |
| `originchats.string`   | `str`         | Free text                                             |
| `originchats.integer`  | `int`         | Whole number                                          |
| `originchats.number`   | `float`       | Decimal number                                        |
| `originchats.boolean`  | `bool`        | true/false                                            |
| `originchats.username` | `user`        | A username - clients render a member picker           |
| `originchats.choice`   | `enum`        | One of a fixed set (needs `choices` via `.addOption`) |

#### `client.slash(schema, handler)`

Protocol-level registration for when you already have a full slash command schema object (`name`, `description`, `options`, `whitelistRoles`, `blacklistRoles`, `ephemeral`). Adding a command after `ready` resends the client's complete command set because registration replaces the server-side set for that connection. `slashCommand(...)` is sugar over this.

## Sending & editing

Sending methods wait for the server's response (up to `timeout`) and return the created message, so you can chain edits or reactions onto it. They return `null` if the request fails or the server rejects the message.

#### `client.send(channel, content)` → `*originchats.message`

Sends a message to a channel.

```osl
*originchats.message m = client.send("general", "hello!")
m.react("👋")
```

#### `client.sendThread(threadId, content)` → `*originchats.message`

Sends a message into a thread.

#### `client.sendRaw(payload)` → `*originchats.message`

Sends a `message_new` with a payload you build yourself - use this for attachments, pings, or any protocol field the helpers don't cover. `cmd` is set for you.

```osl
client.sendRaw({channel: "general", content: "look", attachments: [{id: att_id}]})
```

#### `client.edit(channel, id, content)` → `object`

Edits a message by id and returns the server response.

#### `client.delete(channel, id)`

Deletes a message by id.

#### `client.react(channel, id, emoji)` / `client.unreact(channel, id, emoji)`

Adds or removes a reaction.

#### `client.pin(channel, id)` / `client.unpin(channel, id)`

Pins or unpins a message.

#### `client.typing(channel)`

Shows the bot's typing indicator in a channel.

## Queries

Each of these performs a round-trip to the server and returns the useful part of the response. On timeout they return an empty value.

#### `client.messages(channel, limit)` → `array`

The most recent messages in a channel.

#### `client.message(channel, id)` → `object`

A single message by id.

#### `client.channels()` → `array`

The server's channel list.

#### `client.users()` → `array`

All known users.

#### `client.usersOnline()` → `array`

Currently connected users.

#### `client.roles()` → `object`

The server's roles, keyed by role name.

#### `client.userRoles(username)` → `array`

Role names of one user.

#### `client.request(payload)` → `object`

Escape hatch for any protocol command: attaches a listener, sends `payload`, and returns the server's response. Returns `{error: "timeout"}` if no response arrives in time.

```osl
object res = client.request({cmd: "unreads_get"})
```

#### `client.sendCmd(payload)`

Fire-and-forget raw packet - like `request` without waiting for a response.

## Client info & state

#### `client.details()` → `object`

Returns the complete server handshake details as a defensive copy. This includes `server`, `limits`, `uploads`, `attachments`, `version`, `validator_key`, `capabilities`, and `permissions`, plus any fields added by newer servers.

```osl
object details = client.details()
log details.limits.post_content
log details.attachments.max_size
```

#### `client.supports(command)` → `boolean`

Whether the server advertised `command` in its handshake capability list. Use this before calling newer commands through `request()` or `sendCmd()`.

```osl
if client.supports("messages_search") (
  object response = client.request({cmd: "messages_search", query: "release"})
)
```

#### `client.username()` → `string`

The bot's username (from `ready`).

#### `client.me()` → `object`

The bot's full user object.

#### `client.server()` → `object`

Server info from the handshake (`name`, etc.).

#### `client.serverUrl()` → `string`

The URL the client was created with.

#### `client.set(key, value)` / `client.get(key)` → `any`

Thread-safe per-client key/value storage, handy for sharing state between handlers.

## Message objects

`*originchats.message` values arrive in `onMessageNew` and `command` handlers and are returned by the sending methods.

#### Accessors

* `msg.user()` → `string` - the sender's username
* `msg.content()` → `string` - the text (prefix already stripped in `command` handlers)
* `msg.channel()` → `string` - channel name (empty for thread messages)
* `msg.threadId()` → `string` - thread id (empty for channel messages)
* `msg.id()` → `string` - message id
* `msg.timestamp()` → `number` - unix timestamp
* `msg.isReply()` → `boolean` - whether this message replies to another
* `msg.replyTo()` → `object` - `{id, user}` of the replied-to message
* `msg.attachments()` → `array` - attachment objects
* `msg.pings()` → `object` - `{users, roles, replies}` ping summary
* `msg.mentions(name)` → `boolean` - whether the message pings or `@`-mentions `name`
* `msg.isAutomated()` → `boolean` - whether it came from a webhook or slash interaction
* `msg.data()` → `object` - the raw message object
* `msg.raw()` → `object` - the whole `message_new` event
* `msg.client()` → `*originchats.client` - the client that received it

#### Actions

All of these share the same owner validation and automatically target the message's own channel or thread.

* `msg.reply(content)` → `*originchats.message` - reply (pings the author)
* `msg.replyNoPing(content)` → `*originchats.message` - reply without pinging
* `msg.send(content)` → `*originchats.message` - plain message to the same channel/thread
* `msg.react(emoji)` - add a reaction
* `msg.edit(content)` - edit this message (must be the bot's own)
* `msg.delete()` - delete this message

```osl
client.command("!ping", def(*originchats.message msg) -> (
  *originchats.message m = msg.reply("pong")
  m.edit("pong 🏓")
))
```

## Slash calls

`*originchats.slash` values arrive in slash command handlers.

* `call.command()` → `string` - the command name
* `call.user()` → `string` - username of the invoker
* `call.invoker()` → `string` - user id of the invoker
* `call.channel()` → `string` - channel it was invoked in
* `call.threadId()` → `string` - thread it was invoked from, if any
* `call.args()` → `object` - all arguments
* `call.arg(name)` → `any` - one argument (`null` if absent)
* `call.argStr(name)` → `string` / `call.argNum(name)` → `number` / `call.argBool(name)` → `boolean` - typed argument access
* `call.has(name)` → `boolean` - whether an argument was provided
* `call.raw()` → `object` - the whole `slash_call` event
* `call.client()` → `*originchats.client` - the client
* `call.respond(text)` - send the command response, correlated with the incoming `slash_call` id

## Multiple servers

Create one client per server - handlers and state are per-client. Since `run` blocks, start extra clients with `connect` first:

```osl
*originchats.client main_chat = originchats.new("wss://chats.mistium.com")
*originchats.client dms = originchats.new("wss://dms.mistium.com")

// ... register handlers on both ...

dms.connect(token)
main_chat.run(token)
```

## Reliability and security

`wss://` connections use normal TLS certificate verification. Malformed frames are ignored, callback panics are contained, duplicate listeners run independently, and client state is synchronized for concurrent handlers. Connections reuse the `ws` dialer, construction, and worker lifecycle. Client state uses shared read/write lock paths, message and slash accessors share one nil-safe projection, and callback fan-out shares one panic boundary. `stop()` is idempotent and releases pending requests with a stopped error.


# requests

Use `requests` for HTTP client calls that return status, headers, body text, and actionable transport errors.

```osl
import "std:requests"
```

## Example

```osl
import "std:requests"

auto res = requests.get("https://example.com")
log res["status"]
```

## API reference

### `requests`

| Method                                                     | Returns           | Notes                                                     |
| ---------------------------------------------------------- | ----------------- | --------------------------------------------------------- |
| `requests.Request(method: any, url: any, ...data: object)` | `object`          | Sends an HTTP request.                                    |
| `requests.get(url: any, ...data: object)`                  | `object`          | Sends an HTTP GET request.                                |
| `requests.post(url: any, data: object)`                    | `object`          | Sends an HTTP POST request.                               |
| `requests.put(url: any, data: object)`                     | `object`          | Sends an HTTP PUT request.                                |
| `requests.patch(url: any, data: object)`                   | `object`          | Sends an HTTP PATCH request.                              |
| `requests.delete(url: any, ...data: object)`               | `object`          | Sends an HTTP DELETE request.                             |
| `requests.options(url: any, ...data: object)`              | `object`          | Sends an HTTP OPTIONS request.                            |
| `requests.head(url: any, ...data: object)`                 | `object`          | Sends an HTTP HEAD request.                               |
| `requests.stream(method: any, url: any, ...data: object)`  | `*requestsStream` | Opens a bounded streaming response with idempotent close. |

## Notes

Optional `headers`, `params`, `body`, `timeout`, and `max_bytes` values use the same request-construction path for regular, HEAD, and streaming requests. Positive timeouts are capped at 300 seconds. Regular responses default to a 16 MiB body limit; `max_bytes` can raise it up to 1 GiB. Use `requests.stream` when the response should not be buffered in memory. Concurrent reads from one stream are serialized in arrival order, while `close` can still unblock a pending read. Regular request results include `error`, which is empty on success and describes malformed URLs, connection failures, timeouts, and response-read failures when `success` is `false`.

* Prefer `import "std:requests"`; the older `import "osl/requests"` spelling remains supported.
* `requests` can be imported alongside `osl/url` in the same program.

## Behavior and limits

Requests honor finite timeouts. URL, transport, timeout, and body-read failures set `success` to `false` and put the cause in `error`. A response stream can be closed more than once. The SSE parser accepts multiline events and a final event without a trailing blank line.


# net

Use `net` for low-level TCP and UDP clients/servers, DNS lookups, and IP/port utilities.

```osl
import "std:net"
```

## Example

```osl
import "std:net"

auto addrs = net.lookupHost("example.com")
log addrs
```

## API reference

### `net`

| Method                                       | Returns    | Notes                             |
| -------------------------------------------- | ---------- | --------------------------------- |
| `net.dial(network: any, address: any)`       | `*TCPConn` |                                   |
| `net.listen(protocol: any, address: any)`    | `*TCPConn` |                                   |
| `net.listenUDP(network: any, address: any)`  | `*UDPConn` |                                   |
| `net.lookupHost(hostname: any)`              | `array`    | Returns the host's DNS addresses. |
| `net.lookupIP(hostname: any)`                | `array`    | Returns the host's IP addresses.  |
| `net.lookupPort(service: any, network: any)` | `number`   |                                   |
| `net.getAddressInfo(hostname: any)`          | `object`   | Returns address info.             |

### `TCPConn` values

| Method                             | Returns   | Notes                                                 |
| ---------------------------------- | --------- | ----------------------------------------------------- |
| `value.write(data: any)`           | `boolean` | Converts the value to text and writes it.             |
| `value.writeBytes(data: byte[])`   | `boolean` | Writes bytes to the connection.                       |
| `value.read(bufferSize: any)`      | `string`  | Reads up to the bounded buffer size and returns text. |
| `value.readBytes(bufferSize: any)` | `byte[]`  | Reads up to the bounded buffer size.                  |
| `value.close()`                    | `boolean` | Closes the resource.                                  |
| `value.remoteAddr()`               | `string`  |                                                       |
| `value.localAddr()`                | `string`  |                                                       |
| `value.setTimeout(seconds: any)`   | `boolean` | Sets a deadline while preserving fractional seconds.  |

### `UDPConn` values

| Method                                       | Returns   | Notes                |
| -------------------------------------------- | --------- | -------------------- |
| `value.write(data: any, targetAddress: any)` | `boolean` |                      |
| `value.read(bufferSize: any)`                | `object`  |                      |
| `value.close()`                              | `boolean` | Closes the resource. |

## Notes

* Prefer `import "std:net"`; the older `import "osl/net"` spelling remains supported.

## Behavior and limits

Invalid ports or addresses return failure values. Timeouts can include fractional seconds. Closed sockets, partial reads, and concurrent close, read, or write calls do not panic. Read buffers are limited to 16 MiB.


# url

Use `url` for parsing, building, joining, escaping, and editing URLs and query strings.

```osl
import "std:url"
```

## Example

```osl
import "std:url"

auto u = url.parse("https://example.com?a=1")
log u["host"]
```

## API reference

### `url`

| Method                                     | Returns   | Notes                                                                                                |
| ------------------------------------------ | --------- | ---------------------------------------------------------------------------------------------------- |
| `url.parse(raw: any)`                      | `object`  | Parses a URL into its components.                                                                    |
| `url.build(parts: object)`                 | `string`  | Builds a URL using the same scalar and repeated query-value applicator as `encode` and `withParams`. |
| `url.encode(m: object)`                    | `string`  | Encodes scalar and array values as a query string.                                                   |
| `url.decode(query: any)`                   | `object`  | Decodes a URL-encoded query string into an object.                                                   |
| `url.escape(s: any)`                       | `string`  |                                                                                                      |
| `url.unescape(s: any)`                     | `string`  |                                                                                                      |
| `url.isValid(raw: any)`                    | `boolean` | Requires a valid URL with both a scheme and host.                                                    |
| `url.join(base: any, ref: any)`            | `string`  |                                                                                                      |
| `url.withParams(raw: any, params: object)` | `string`  | Adds or replaces scalar and repeated query values using the same rules as `encode`.                  |
| `url.param(raw: any, key: any)`            | `string`  |                                                                                                      |

## Notes

* Prefer `import "std:url"`; the older `import "osl/url"` spelling remains supported.

## Behavior and limits

Parsing preserves encoded paths and repeated query keys. Malformed escapes and invalid ports are rejected.


# ftp

Use `ftp` for connecting to FTP servers and transferring, renaming, deleting, or synchronising files and directories.

```osl
import "std:ftp"
```

## API reference

### `ftp`

| Method                                                          | Returns   | Notes                                   |
| --------------------------------------------------------------- | --------- | --------------------------------------- |
| `ftp.connect(host: any, port: any, user: any, password: any)`   | `*FTP`    | Opens a connection.                     |
| `ftp.connectEx(host: any, port: any, user: any, password: any)` | `*FTP`    |                                         |
| `ftp.list(path: any)`                                           | `array`   | Lists entries at the remote path.       |
| `ftp.upload(localFile: any, remotePath: any)`                   | `boolean` |                                         |
| `ftp.download(remotePath: any, localPath: any)`                 | `boolean` |                                         |
| `ftp.delete(remotePath: any)`                                   | `boolean` | Deletes a value.                        |
| `ftp.rename(oldPath: any, newPath: any)`                        | `boolean` |                                         |
| `ftp.createDirectory(path: any)`                                | `boolean` | Creates directory.                      |
| `ftp.deleteDirectory(path: any)`                                | `boolean` | Deletes directory.                      |
| `ftp.changeDirectory(path: any)`                                | `boolean` |                                         |
| `ftp.currentDirectory()`                                        | `string`  |                                         |
| `ftp.isActive()`                                                | `boolean` |                                         |
| `ftp.setTimeout(seconds: any)`                                  | `boolean` | Sets timeout.                           |
| `ftp.getFileSize(path: any)`                                    | `number`  | Returns file size.                      |
| `ftp.exists(path: any)`                                         | `boolean` |                                         |
| `ftp.uploadDirectory(localDir: any, remoteDir: any)`            | `boolean` |                                         |
| `ftp.downloadDirectory(remoteDir: any, localDir: any)`          | `boolean` |                                         |
| `ftp.setMode(path: any, mode: any)`                             | `boolean` | Sets mode.                              |
| `ftp.setModificationTime(path: any, timestamp: any)`            | `boolean` | Sets a remote file's modification time. |
| `ftp.passiveMode(enabled: any)`                                 | `boolean` |                                         |
| `ftp.sync(localDir: any, remoteDir: any)`                       | `boolean` |                                         |
| `ftp.getStatistics()`                                           | `object`  | Returns statistics.                     |

### `FTP` values

| Method               | Returns   |
| -------------------- | --------- |
| `value.login()`      | `boolean` |
| `value.disconnect()` | `boolean` |

## Notes

* Prefer `import "std:ftp"`; the older `import "osl/ftp"` spelling remains supported.

## Connection and transfer behavior

`connect` opens and logs into the server directly; it does not shell out to a system `ftp` executable. The returned client owns its connection state. Credentials and all single-path operations reject command separators. Failed or partial downloads remove their local partial file, and recursive uploads reject symlinks. Connected operations share one state predicate while retaining their existing locks.


# ssh

Use `ssh` for SSH connections, remote commands, SCP transfers, tunnels, and key handling.

```osl
import "std:ssh"
```

## API reference

### `ssh`

| Method                                                                                          | Returns          | Notes                                                                                 |
| ----------------------------------------------------------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------- |
| `ssh.connect(host: any, port: any, user: any, password: any, privateKey: any)`                  | `*SSHClient`     | Opens a connection. Ports must be between 1 and 65,535; an empty port defaults to 22. |
| `ssh.execRemote(host: any, port: any, user: any, password: any, privateKey: any, command: any)` | `object`         |                                                                                       |
| `ssh.scpUpload(client: *SSHClient, localPath: any, remotePath: any)`                            | `boolean`        | Uploads a file over SFTP and reports transfer errors.                                 |
| `ssh.scpDownload(client: *SSHClient, remotePath: any, localPath: any)`                          | `boolean`        | Downloads a file over SFTP and reports transfer errors.                               |
| `ssh.tunnel(localPort: any, remoteHost: any, remotePort: any, sshHost: any, sshPort: any)`      | `*SSHClient`     | Opens a verified tunnel using the same port validation and SSH port default.          |
| `ssh.generateKeyPair(keyType: any)`                                                             | `object`         |                                                                                       |
| `ssh.generateRSAKey()`                                                                          | `string, string` | Returns PEM-encoded RSA private and public keys.                                      |
| `ssh.generateEd25519Key()`                                                                      | `string, string` | Returns encoded Ed25519 private and public keys.                                      |
| `ssh.savePrivateKey(path: any, key: any)`                                                       | `boolean`        | Saves private key.                                                                    |
| `ssh.savePublicKey(path: any, key: any)`                                                        | `boolean`        | Saves public key.                                                                     |
| `ssh.loadPrivateKey(path: any)`                                                                 | `string`         | Loads private key.                                                                    |
| `ssh.fingerprint(publicKey: any)`                                                               | `string`         |                                                                                       |

### `SSHClient` values

| Method                                                                | Returns   | Notes                                                                                                                                                             |
| --------------------------------------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `value.exec(command: any)`                                            | `object`  | Runs for at most 30 seconds and captures at most 16 MiB of combined output.                                                                                       |
| `value.execLimited(command: any, maxBytes: any)`                      | `object`  | Runs for at most 30 seconds with a chosen output limit. Nonpositive values use 16 MiB; values above 1 GiB use 1 GiB.                                              |
| `value.execTimeout(command: any, timeout: any)`                       | `object`  | Runs for at most 300 seconds with the default 16 MiB output limit. Nonpositive timeouts use one millisecond.                                                      |
| `value.execTimeoutLimited(command: any, timeout: any, maxBytes: any)` | `object`  | Runs with both a chosen timeout and output limit.                                                                                                                 |
| `value.startCommand(command: any)`                                    | `boolean` | Starts command.                                                                                                                                                   |
| `value.sendInput(input: any)`                                         | `boolean` | Sends input.                                                                                                                                                      |
| `value.readOutput(timeout: any)`                                      | `string`  | Reads the next ordered output chunk with a fractional-seconds timeout; repeated timeouts share one session-owned reader. Non-positive values use one millisecond. |
| `value.close()`                                                       | `boolean` | Closes the resource.                                                                                                                                              |
| `value.isConnected()`                                                 | `boolean` |                                                                                                                                                                   |

## Notes

* Prefer `import "std:ssh"`; the older `import "osl/ssh"` spelling remains supported.

## Security and transfer behavior

Hosts are verified against `SSH_KNOWN_HOSTS`, or `~/.ssh/known_hosts` when the variable is unset. A missing or mismatched key fails closed. File transfers use SFTP over the verified connection, preserve exact remote paths, and remove partial local files on failure. Saved private keys use mode `0600`. Command results always include `output`, `error`, `timeout`, and `truncated`. Excess output keeps the captured prefix and returns `success: false`. A timeout closes the session and joins its command worker.


# s3

Use `s3` for S3-compatible object storage: buckets, objects, metadata, presigned URLs, and multipart-style helpers.

```osl
import "std:s3"
```

## API reference

### `s3` values

| Method                   | Returns     |
| ------------------------ | ----------- |
| `value.new(cfg: object)` | `*s3Client` |

### `s3Client` values

| Method                                                                    | Returns  | Notes                                                                                                                                                                                                                                                                                                                                                                                                      |
| ------------------------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `value.cfgStr(k: string)`                                                 | `string` |                                                                                                                                                                                                                                                                                                                                                                                                            |
| `value.cfgDef(v: any, d: string)`                                         | `string` |                                                                                                                                                                                                                                                                                                                                                                                                            |
| `value.endpoint()`                                                        | `string` |                                                                                                                                                                                                                                                                                                                                                                                                            |
| `value.bucket()`                                                          | `string` |                                                                                                                                                                                                                                                                                                                                                                                                            |
| `value.access()`                                                          | `string` |                                                                                                                                                                                                                                                                                                                                                                                                            |
| `value.secret()`                                                          | `string` |                                                                                                                                                                                                                                                                                                                                                                                                            |
| `value.region()`                                                          | `string` |                                                                                                                                                                                                                                                                                                                                                                                                            |
| `value.host()`                                                            | `string` |                                                                                                                                                                                                                                                                                                                                                                                                            |
| `value.public(key: string)`                                               | `string` | Builds the encoded object URL from `public_url`, or the endpoint and bucket by default.                                                                                                                                                                                                                                                                                                                    |
| `value.toBytes(v: any)`                                                   | `byte[]` | Converts a value to bytes.                                                                                                                                                                                                                                                                                                                                                                                 |
| `value.sha256hex(b: byte[])`                                              | `string` | Returns the SHA-256 digest as hexadecimal text.                                                                                                                                                                                                                                                                                                                                                            |
| `value.amzDate(t: time.Time)`                                             | `string` |                                                                                                                                                                                                                                                                                                                                                                                                            |
| `value.shortDate(t: time.Time)`                                           | `string` |                                                                                                                                                                                                                                                                                                                                                                                                            |
| `value.sign(method: string, key: string, payload: string, query: string)` | `string` |                                                                                                                                                                                                                                                                                                                                                                                                            |
| `value.sortQuery(q: object)`                                              | `string` | Canonically sorts and encodes query parameters with the standard URL encoder.                                                                                                                                                                                                                                                                                                                              |
| `value.put(input: object)`                                                | `object` | Stores an object. Accepts `key`, `body`, `content_type`, and optional `content_encoding` such as `gzip`.                                                                                                                                                                                                                                                                                                   |
| `value.get(input: object)`                                                | `object` | Returns a value.                                                                                                                                                                                                                                                                                                                                                                                           |
| `value.remove(input: object)`                                             | `object` | Removes a value or resource.                                                                                                                                                                                                                                                                                                                                                                               |
| `value.presign(input: object)`                                            | `object` | Generates a presigned URL through the same canonical Signature V4 pipeline as signed requests. Input keys: `key` (required), `expires` (seconds, default 3600), `method` (HTTP method to sign for, default `GET`; pass `PUT` for direct uploads), `content_length` (optional; signs the Content-Length header so the upload must be exactly that many bytes). Returns `{ok, url}` or `{ok: false, error}`. |

## Examples

```osl
import "std:s3"

client = s3.new({
    "endpoint": "https://accountid.r2.cloudflarestorage.com",
    "bucket": "my-bucket",
    "access_key_id": "...",
    "secret_access_key": "...",
    "region": "auto"
})

download = client.presign({"key": "files/report.pdf", "expires": 3600})
upload = client.presign({"key": "files/report.pdf", "expires": 900, "method": "PUT", "content_length": 52428})
```

## Notes

* Prefer `import "std:s3"`; the older `import "osl/s3"` spelling remains supported.

## Behavior and limits

The client validates object keys, expiry times, payload lengths, session credentials, encoded AWS paths, and HTTP status codes. Requests share a connection pool and are safe across threads. `public_url` changes only the URL returned by `public()`.


# webpush

Use `webpush` to generate VAPID keys and send Web Push notifications to browser push subscriptions.

```osl
import "std:webpush"
```

## API reference

### `webpush`

| Method                                                                                                                         | Returns   | Notes                                                                          |
| ------------------------------------------------------------------------------------------------------------------------------ | --------- | ------------------------------------------------------------------------------ |
| `webpush.generateVAPIDKeys()`                                                                                                  | `object`  |                                                                                |
| `webpush.derivePublicKey(privateKeyPEM: any)`                                                                                  | `string`  | Derives a public key from a validated P-256 private key.                       |
| `webpush.signVapidJWT(audience: any, expiresIn: any, privateKeyPEM: any, claimsEmail: any)`                                    | `string`  | Signs a VAPID JWT with a P-256 private key.                                    |
| `webpush.sendWebPush(endpoint: any, p256dh: any, auth: any, data: any, vapidPrivateKey: any, vapidClaimsEmail: any, ttl: any)` | `object`  | Encrypts and sends a bounded Web Push request.                                 |
| `webpush.verifySubscription(endpoint: any, p256dh: any, auth: any)`                                                            | `boolean` | Validates the endpoint, P-256 key, and authentication secret.                  |
| `webpush.ensureVAPIDKeys(configPath: any, privateKeyPath: any)`                                                                | `object`  | Reuses matching keys or generates replacement files under a process-wide lock. |
| `webpush.verifyVapidJWT(token: any, publicKey: any)`                                                                           | `boolean` | Verifies the signature and required VAPID claim shape.                         |

## Notes

* Prefer `import "std:webpush"`; the older `import "osl/webpush"` spelling remains supported.

## Behavior and limits

The package validates P-256 public keys in subscriptions and JWTs. Invalid VAPID keys, tokens, subscriptions, endpoints, or HTTP responses return failure values. If payload encryption fails, the package does not send plaintext. Expiry times and payload sizes have fixed limits.


# Data and serialization

Packages for parsing and encoding structured data formats.

* [json](/standard-library/data/json) - JSON parsing and encoding.
* [yaml](/standard-library/data/yaml) - YAML parsing and encoding.
* [schema](/standard-library/data/schema) - Validation and normalization schemas.
* [csv](/standard-library/data/csv) - CSV parsing plus a small dataframe-style toolkit.
* [xml](/standard-library/data/xml) - XML parsing and querying.
* [template](/standard-library/data/template) - Lightweight `{{ }}` templating with HTML escaping.
* [md](/standard-library/data/md) - Markdown to HTML (CommonMark + GFM via goldmark).
* [mime](/standard-library/data/mime) - MIME-type lookup and parsing.
* [diff](/standard-library/data/diff) - Text/line/word diffing.


# json

Use `json` for parsing JSON into OSL values, serialising values, pretty-printing, validating JSON strings, and streaming large JSON files.

```osl
import "std:json"
```

## Example

```osl
import "std:json"

auto parsed = json.parse("{\"ok\":true}", {})
if parsed.isOk() (
  log parsed.unwrap()["ok"]
)
```

## API reference

### `json`

| Method                                    | Returns        | Notes                                                                                                        |
| ----------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------ |
| `json.parse(data: any, options: object)`  | `*Result`      | Parses one root value using the same trailing-data validation as streams.                                    |
| `json.parseObject(data: any)`             | `*Result`      | Parses one JSON object, returning an error result for invalid JSON or another root type.                     |
| `json.parseArray(data: any)`              | `*Result`      | Parses one JSON array, returning an error result for invalid JSON or another root type.                      |
| `json.stringify(data: any)`               | `string`       | Serialises a value as compact JSON without HTML escaping. Safe while another OSL thread mutates shared data. |
| `json.format(data: any)`                  | `string`       | Serialises the same value as two-space-indented JSON. Safe while another OSL thread mutates shared data.     |
| `json.isValid(data: any)`                 | `boolean`      |                                                                                                              |
| `json.isObject(data: any)`                | `boolean`      |                                                                                                              |
| `json.isArray(data: any)`                 | `boolean`      |                                                                                                              |
| `json.open(path: any, maxBytes?: number)` | `*json.Stream` | Opens one JSON document for incremental token reading.                                                       |

## Streaming large files

`json.open(path, maxBytes?)` returns a `*json.Stream`. The optional byte limit rejects an oversized file before it is read. Opening and parsing errors are reported by `stream.ok()` and `stream.error()`.

```osl
import "std:json"

*json.Stream stream = json.open("project.json", 1073741824)
while stream.more() (
  object event = stream.next()
  if event.type == "key" and event.value == "extensionURLs" (
    object urls = stream.readStringMap(100).unwrap().assert(object)
    log urls
  )
)
if !stream.ok() (
  log stream.error()
)
stream.close()
```

### Stream tokens

`stream.next()` returns an object with `type`, `value`, and `depth` fields. Object and array boundaries share one container-event path.

| Type                          | Value                                  |
| ----------------------------- | -------------------------------------- |
| `object-start`, `object-end`  | `null`                                 |
| `array-start`, `array-end`    | `null`                                 |
| `key`                         | The object key string                  |
| `string`, `number`, `boolean` | The decoded scalar                     |
| `null`                        | `null`                                 |
| `eof`                         | `null`, returned when no token remains |

Root values have depth `0`. Keys and values directly inside a root object have depth `1`.

### Stream methods

#### `stream.more()` → `boolean`

Returns `true` when another token is available. It returns `false` at the end of the document or after a parsing error. Check `stream.ok()` after a loop to distinguish those cases.

#### `stream.next()` → `object`

Consumes and returns the next token as `{type, value, depth}`. It returns an `eof` token when no token remains.

#### `stream.readScalarMap(maxValues)` → `*Result`

Consumes the next value as a flat JSON object and returns it. Values may be strings, numbers, booleans, or `null`. Nested objects and arrays return an error. `maxValues` bounds the number of object entries.

#### `stream.readStringMap(maxValues)` → `*Result`

Like `readScalarMap`, but every value must be a string.

#### `stream.skip()` → `boolean`

Consumes the next complete JSON value. Objects and arrays are skipped recursively without being loaded into memory. Returns `false` at the end of the document or after a parsing error.

#### `stream.ok()` → `boolean`

Returns `false` if opening or parsing the document failed.

#### `stream.error()` → `string`

Returns the opening or parsing error, or an empty string when there is no error.

#### `stream.close()` → `boolean`

Closes the file and returns whether closing succeeded.

## Notes

* Prefer `import "std:json"`; the older `import "osl/json"` spelling remains supported.
* Serialisation observes a consistent traversal of shared OSL objects and arrays. Concurrent OSL mutations wait until encoding finishes.

## Behavior and limits

The parser accepts any JSON value at the root. It rejects trailing data, overflowing numbers, and excessive nesting. Stream reads have a size limit. JSON numbers use 64-bit floating point, so large integers can lose precision.


# yaml

Use `yaml` for parsing YAML, writing YAML, converting to or from JSON, and editing map values.

```osl
import "std:yaml"
```

## Example

```osl
import "std:yaml"

auto data = yaml.parse("name: Ada")
log data["name"]
```

## API reference

### `yaml`

| Method                                         | Returns   | Notes                                                                    |
| ---------------------------------------------- | --------- | ------------------------------------------------------------------------ |
| `yaml.parse(source: any)`                      | `any`     | Parses input data.                                                       |
| `yaml.stringify(data: any)`                    | `string`  | Serializes an OSL value as YAML.                                         |
| `yaml.toJSON(yamlData: any)`                   | `string`  | Parses YAML and returns JSON text.                                       |
| `yaml.fromJSON(jsonData: any)`                 | `any`     | Parses a JSON object, returning `null` for invalid input.                |
| `yaml.get(data: any, key: any)`                | `any`     | Returns the value at the string-converted key.                           |
| `yaml.set(data: object, key: any, value: any)` | `object`  | Sets a value at the string-converted key.                                |
| `yaml.merge(data1: object, data2: object)`     | `object`  | Clones the first map and recursively merges nested maps from the second. |
| `yaml.keys(data: object)`                      | `array`   | Returns all keys.                                                        |
| `yaml.values(data: object)`                    | `array`   | Collects all values using the standard map iterator.                     |
| `yaml.has(data: object, key: any)`             | `boolean` |                                                                          |
| `yaml.delete(data: object, key: any)`          | `object`  | Deletes a value.                                                         |

## Notes

* Prefer `import "std:yaml"`; the older `import "osl/yaml"` spelling remains supported.

## Behavior and limits

Anchors, aliases, duplicate keys, non-string map keys, multiple documents, and excessive nesting produce defined results or return an error instead of panicking.


# schema

The `schema` package validates unknown values, nested objects, and arrays. Schemas are reusable and immutable: constraint methods return a new schema, leaving the original unchanged.

```osl
import "std:schema"

*schema.Schema createUser = schema.object({
  name: schema.string().trim().minLen(1),
  age: schema.integer().min(0).optional(),
  roles: schema.array(schema.enum(["admin", "member"]))
})

auto checked = createUser.safeParse(payload)
if checked.isErr() (
  return err(checked.unwrapErr().message)
)
object user = checked.unwrap()
```

## Creating schemas

#### `schema.any()` → `*schema.Schema`

Accepts any value, including `null`.

#### `schema.string()` → `*schema.Schema`

Accepts a string.

#### `schema.number()` → `*schema.Schema`

Accepts a finite number.

#### `schema.integer()` → `*schema.Schema`

Accepts a finite whole number.

#### `schema.boolean()` → `*schema.Schema`

Accepts a boolean.

#### `schema.literal(value)` → `*schema.Schema`

Accepts only a value equal to `value`.

```osl
*schema.Schema enabled = schema.literal(true)
```

#### `schema.enum(values)` → `*schema.Schema`

Accepts one of the supplied values. Values may be strings, numbers, booleans, or other OSL values.

```osl
*schema.Schema status = schema.enum(["online", "away", "offline"])
```

#### `schema.array(itemSchema)` → `*schema.Schema`

Accepts an array and validates every element with `itemSchema`. Errors include the failing index, such as `roles[1]`.

```osl
*schema.Schema tags = schema.array(schema.string().minLen(1))
```

#### `schema.oneOrMany(itemSchema)` → `*schema.Schema`

Accepts either one value or an array of values and validates each value with `itemSchema`. The normalized result is always an array, which is useful for APIs that accept scalar shorthand.

```osl
*schema.Schema tags = schema.oneOrMany(schema.string())
array normalized = tags.parse("news") // ["news"]
```

#### `schema.object(shape)` → `*schema.Schema`

Accepts an object and validates the named fields with the schemas in `shape`. Fields not in the shape are preserved unless `.strict()` is used. Nested error paths use dot notation.

```osl
*schema.Schema profile = schema.object({
  id: schema.string().minLen(1),
  settings: schema.object({dark: schema.boolean().defaultValue(false)})
})
```

#### `schema.record(valueSchema)` → `*schema.Schema`

Accepts an object with arbitrary string keys and validates every value with `valueSchema`.

```osl
*schema.Schema scores = schema.record(schema.integer().min(0))
```

#### `schema.union(schemas)` → `*schema.Schema`

Accepts a value when any supplied schema accepts it.

```osl
*schema.Schema id = schema.union([schema.string(), schema.integer().gt(0)])
```

## Schema modifiers

Each modifier returns a new `*schema.Schema` and can be chained.

#### `value.optional()` → `*schema.Schema`

Allows a field to be absent. Because absent object fields read as `null` in OSL, optional schemas also accept `null`.

#### `value.nullable()` → `*schema.Schema`

Allows `null` in addition to the schema's normal type.

#### `value.defaultValue(default)` → `*schema.Schema`

Uses `default` when the input is absent or `null`. The normalized output contains the default.

#### `value.trim()` → `*schema.Schema`

Trims leading and trailing whitespace from a string in the normalized output.

#### `value.min(limit)` / `value.max(limit)` → `*schema.Schema`

Sets an inclusive numeric minimum or maximum on number and integer schemas.

#### `value.minLen(limit)` / `value.maxLen(limit)` → `*schema.Schema`

Sets an inclusive length bound. Strings use Unicode character count; arrays and objects use item or field count.

#### `value.length(size)` → `*schema.Schema`

Requires an exact string character count, array item count, or object field count.

#### `value.gt(limit)` / `value.lt(limit)` → `*schema.Schema`

Sets an exclusive numeric bound. For example, `.gt(0)` accepts positive numbers while `.min(0)` also accepts zero.

#### `value.partial()` → `*schema.Schema`

Returns an object schema where every declared field is optional. This is useful for update payloads.

```osl
*schema.Schema user = schema.object({name: schema.string(), age: schema.integer()})
*schema.Schema userUpdate = user.partial()
```

#### `value.extend(shape)` → `*schema.Schema`

Returns an object schema containing its existing fields plus the supplied fields. Supplied fields replace existing fields with the same name.

```osl
*schema.Schema account = user.extend({active: schema.boolean()})
```

#### `value.requireAny(fields)` → `*schema.Schema`

Requires an object to contain at least one named field. A present field with a `null` value counts, so this works for patches where `null` intentionally clears a value.

```osl
*schema.Schema update = user.partial().requireAny(["name", "age"])
```

#### `value.requireAnyValue(fields)` → `*schema.Schema`

Requires an object to contain a non-null value for at least one named field. Empty strings and arrays still count; combine the field schema with `.minLen(1)` when emptiness should be rejected.

```osl
*schema.Schema message = schema.object({
  content: schema.string().optional(),
  embeds: schema.array(schema.any()).optional()
}).requireAnyValue(["content", "embeds"])
```

Modifier misuse is reported as a schema configuration error instead of silently doing nothing. For example, `schema.string().min(2)` returns `Invalid schema: min() cannot be used with string schemas`.

#### `value.strict()` → `*schema.Schema`

For an object schema, rejects fields not present in its shape. Other object schemas preserve additional fields.

## Validating values

#### `value.safeParse(input)` → `result.Result`

Validates `input` without throwing. Returns `ok(normalizedValue)` on success. On failure it returns `err(error)`, where `error` contains `message`, `path`, and `issues`. The current implementation stops at the first issue.

```osl
auto checked = profile.safeParse(input)
if checked.isErr() (
  object problem = checked.unwrapErr()
  log problem.message // settings.dark: Expected boolean, received string
  log problem.path    // ["settings", "dark"]
)
```

#### `value.parse(input)` → `any`

Returns the normalized value or throws a path-based validation message.

```osl
object user = profile.parse(input)
```

#### `value.isValid(input)` → `boolean`

Returns whether `input` passes the schema without returning the normalized value or throwing. This uses a validation-only fast path: it does not copy arrays or objects, construct normalized values, or allocate successful error paths. Define reusable schemas once rather than rebuilding them inside a frequently called function.


# csv

Use `csv` for CSV parsing, writing, and table-style transforms over arrays of row objects.

```osl
import "std:csv"
```

## Example

```osl
import "std:csv"

array rows = csv.parse("name,age\nAda,36")
log rows[0]["name"]
```

## API reference

### `csv`

| Method                                                                       | Returns          | Notes                                                                              |
| ---------------------------------------------------------------------------- | ---------------- | ---------------------------------------------------------------------------------- |
| `csv.parse(data: any)`                                                       | `array`          | Maps each data row to the header names, filling missing fields with empty strings. |
| `csv.parseRaw(data: any)`                                                    | `array`          | Parses raw.                                                                        |
| `csv.stringify(data: object)`                                                | `string`         | Serialises a value to text.                                                        |
| `csv.stringifyRows(data: array)`                                             | `string`         |                                                                                    |
| `csv.stringifyArray(data: array)`                                            | `string`         |                                                                                    |
| `csv.readFile(path: any)`                                                    | `array`          | Reads and parses keyed rows, returning an empty array on failure.                  |
| `csv.readFileRaw(path: any)`                                                 | `array`          | Reads and parses raw rows, returning an empty array on failure.                    |
| `csv.writeFile(path: any, data: object)`                                     | `boolean`        | Stringifies one object and writes it to a file.                                    |
| `csv.writeFileRows(path: any, data: array)`                                  | `boolean`        | Stringifies keyed rows and writes them to a file.                                  |
| `csv.writeFileArray(path: any, data: array)`                                 | `boolean`        | Stringifies raw rows and writes them to a file.                                    |
| `csv.toRows(data: array)`                                                    | `array`          | Converts keyed objects through the same ordered row mapping used by parsing.       |
| `csv.fromRows(rows: array)`                                                  | `array`          | Creates from rows.                                                                 |
| `csv.getColumn(data: array, column: any)`                                    | `array`          | Returns column.                                                                    |
| `csv.filter(data: array, fn: any)`                                           | `array`          | Returns values accepted by a callback.                                             |
| `csv.mapRows(data: array, fn: any)`                                          | `array`          |                                                                                    |
| `csv.reduce(data: array, initial: any, fn: any)`                             | `any`            | Reduces values with a callback.                                                    |
| `csv.groupBy(data: array, key: any)`                                         | `string[array]`  | Groups rows by the selected field.                                                 |
| `csv.sortBy(data: array, key: any)`                                          | `array`          |                                                                                    |
| `csv.sortByNum(data: array, key: any)`                                       | `array`          |                                                                                    |
| `csv.aggregate(data: array, key: any, value: any, fn: any)`                  | `object`         |                                                                                    |
| `csv.count(data: array, key: any)`                                           | `object`         |                                                                                    |
| `csv.unique(data: array, key: any)`                                          | `array`          |                                                                                    |
| `csv.join(data1: array, data2: array, key: any)`                             | `array`          |                                                                                    |
| `csv.pivot(data: array, keyColumn: any, valueColumn: any, pivotColumn: any)` | `string[object]` | Pivots rows into keyed objects.                                                    |
| `csv.transpose(data: array)`                                                 | `array`          |                                                                                    |
| `csv.stats(data: array, key: any)`                                           | `object`         | Returns usage statistics.                                                          |
| `csv.merge(data1: array, data2: array)`                                      | `array`          |                                                                                    |
| `csv.chunk(data: array, size: any)`                                          | `array`          |                                                                                    |
| `csv.flatten(data: array, separator: any)`                                   | `array`          |                                                                                    |
| `csv.sample(data: array, count: any)`                                        | `array`          |                                                                                    |
| `csv.appendRow(data: array, row: object)`                                    | `array`          |                                                                                    |
| `csv.prependRow(data: array, row: object)`                                   | `array`          |                                                                                    |
| `csv.insertRow(data: array, index: any, row: object)`                        | `array`          |                                                                                    |
| `csv.deleteRow(data: array, index: any)`                                     | `array`          | Deletes row.                                                                       |
| `csv.addColumn(data: array, column: any, defaultValue: any)`                 | `array`          | Adds column.                                                                       |
| `csv.removeColumn(data: array, column: any)`                                 | `array`          | Removes column.                                                                    |
| `csv.renameColumn(data: array, oldColumn: any, newColumn: any)`              | `array`          |                                                                                    |

## Notes

* Prefer `import "std:csv"`; the older `import "osl/csv"` spelling remains supported.

## Behavior and limits

The parser accepts a UTF-8 BOM and rows with different lengths, but rejects duplicate headers. Writers keep header order stable. Column edits do not change the source rows. A partial sample never returns the same source row twice. Sampling and transposing empty or uneven data is supported.


# xml

Use `xml` for parsing XML documents, querying paths, reading attributes, editing text or attributes, and serialising back to XML.

```osl
import "std:xml"
```

## API reference

### `xml`

| Method                                          | Returns    | Notes                                                                         |
| ----------------------------------------------- | ---------- | ----------------------------------------------------------------------------- |
| `xml.toStr()`                                   | `string`   | Converts to str.                                                              |
| `xml.toArr()`                                   | `array`    | Recursively converts the document and its children to arrays and objects.     |
| `xml.findNode(path: any)`                       | `*xmlNode` |                                                                               |
| `xml.getText(path: any)`                        | `any`      | Returns text.                                                                 |
| `xml.getAttr(path: any, attr: any)`             | `any`      | Returns an attribute, or `null` when either the node or attribute is missing. |
| `xml.get(path: any)`                            | `object`   | Returns a value.                                                              |
| `xml.getAll(path: any)`                         | `array`    | Returns all.                                                                  |
| `xml.has(path: any)`                            | `boolean`  |                                                                               |
| `xml.hasAttr(path: any, attr: any)`             | `boolean`  |                                                                               |
| `xml.setText(path: any, value: any)`            | `void`     | Sets text.                                                                    |
| `xml.setAttr(path: any, attr: any, value: any)` | `void`     | Sets attr.                                                                    |
| `xml.count(path: any)`                          | `number`   |                                                                               |
| `xml.remove(path: any)`                         | `void`     | Removes a value or resource.                                                  |
| `xml.clear(path: any)`                          | `void`     | Clears all stored values.                                                     |

## Notes

* Prefer `import "std:xml"`; the older `import "osl/xml"` spelling remains supported.

## Behavior and limits

Malformed or truncated XML returns a parse error without exposing a partial document. Queries handle namespaces, attributes, mixed content, entities, and nested elements. Empty child lists return empty arrays. Serialization sorts attributes for stable output.


# template

Use `template` to render small text or HTML templates with data maps.

```osl
import "std:template"
```

## Example

```osl
import "std:template"

log template.render("Hello {{name}}", {name: "Ada"})
```

## API reference

### `template`

| Method                                         | Returns  | Notes                                                      |
| ---------------------------------------------- | -------- | ---------------------------------------------------------- |
| `template.render(tmpl: any, data: object)`     | `string` | Renders interpolation, loops, and `if` or `unless` blocks. |
| `template.renderHTML(tmpl: any, data: object)` | `string` | Renders a template and HTML-escapes inserted values.       |

## Notes

* Prefer `import "std:template"`; the older `import "osl/template"` spelling remains supported.

## Behavior and limits

`false`, zero, `null`, and a missing value remain distinct. Invalid loops and unclosed directives return errors. HTML rendering escapes all HTML-sensitive characters. A nested block receives its parent scope without leaking local values back into it.


# md

Use `md` to turn Markdown into HTML (and back with [`md.fromHTML`](#mdfromhtmlsource--string)). It is built for serving pages with [`serve`](/standard-library/web/serve) and composing markup with [`template`](/standard-library/data/template).

```osl
import "std:md"
```

## Example

```osl
import "std:md"

log md.toHTML("# Hello\n\n**world**")
// <h1 id="hello">Hello</h1>\n<p><strong>world</strong></p>\n
```

## With `serve`

Pass the HTML string straight to `ctx.html` when you want a full response body:

```osl
import "std:md"
import "std:serve"

*serve.Router app = serve.new()

app.GET("/", def(ctx) -> (
  string body = md.toHTML("# Home\n\nWelcome.")
  ctx.html(200, body)
))

app.run(":8080")
```

Or wrap it in a small page layout first:

```osl
import "std:md"
import "std:template"
import "std:serve"

*serve.Router app = serve.new()

app.GET("/doc", def(ctx) -> (
  string content = md.toHTML("# Docs\n\nSee below.")
  string page = template.renderHTML(`<!doctype html>
<html><body>
{{& content}}
</body></html>`, {content: content})
  ctx.html(200, page)
))

app.run(":8080")
```

## With `template`

`template.renderHTML` escapes interpolated values by default. Use `{{& name}}` when the value is already HTML from `md.toHTML`:

```osl
import "std:md"
import "std:template"

string body = md.toHTML("**hi**")
string out = template.renderHTML("<article>{{& body}}</article>", {body: body})
// <article><p><strong>hi</strong></p>\n</article>
```

Using `{{body}}` (without `&`) would escape the tags and show the HTML source.

## API reference

#### `md.toHTML(source)` → `string`

Renders Markdown `source` to HTML. Enables GitHub Flavored Markdown (tables, strikethrough, autolinks, task lists) and auto heading IDs. Raw HTML tags in the source are **not passed through** (safe default for untrusted content).

```osl
string html = md.toHTML("# Title\n\n| a | b |\n| - | - |\n| 1 | 2 |")
```

#### `md.toHTMLUnsafe(source)` → `string`

Same as [`md.toHTML`](#mdtohtmlsource--string), but raw HTML in the Markdown is left as-is. It shares the safe engine's GFM and heading options; only raw-HTML rendering differs. Only use this with trusted input.

```osl
string html = md.toHTMLUnsafe("Hello <em>world</em>")
// contains a real <em> element
```

#### `md.fromHTML(source)` → `string`

The inverse of [`md.toHTML`](#mdtohtmlsource--string): converts an HTML `source` string into Markdown. Headings, emphasis, links, lists, and other common elements map to their Markdown equivalents; unconvertible input yields an empty string.

```osl
string mdText = md.fromHTML("<h1>Hello</h1><p>a <strong>bold</strong> word</p>")
// # Hello\n\na **bold** word
```

## Notes

* Prefer `import "std:md"`; the older `import "osl/md"` spelling remains supported.
* Rendering is powered by [goldmark](https://github.com/yuin/goldmark).
* Return values are ordinary OSL strings. Pass them to `ctx.html`, `ctx.send`, or template slots as needed.

#### `md.sanitize(source)` → `string`

Sanitizes Markdown by rendering it through `md.toHTML` and converting that safe HTML back to Markdown. Repeated sanitization is idempotent.


# mime

Use `mime` to inspect MIME types, file extensions, media categories, charsets, and content disposition headers.

```osl
import "std:mime"
```

## API reference

### `mime`

| Method                                    | Returns   | Notes                                                                           |
| ----------------------------------------- | --------- | ------------------------------------------------------------------------------- |
| `mime.typeByExt(ext: any)`                | `string`  |                                                                                 |
| `mime.byFilename(name: any)`              | `string`  | Resolves the filename's final extension through `typeByExt`.                    |
| `mime.extByType(mtype: any)`              | `string`  |                                                                                 |
| `mime.parse(contentType: any)`            | `object`  | Parses input data.                                                              |
| `mime.format(mtype: any, params: object)` | `string`  | Formats a value for display.                                                    |
| `mime.resolve(input: any)`                | `string`  | Normalizes a MIME type, extension, or filename using final-extension semantics. |
| `mime.isText(input: any)`                 | `boolean` |                                                                                 |
| `mime.isImage(input: any)`                | `boolean` |                                                                                 |
| `mime.isAudio(input: any)`                | `boolean` |                                                                                 |
| `mime.isVideo(input: any)`                | `boolean` |                                                                                 |

## Notes

* Prefer `import "std:mime"`; the older `import "osl/mime"` spelling remains supported.

## Behavior and limits

MIME parsing accepts parameters, quoted values, and case differences. Unknown extensions and malformed media types return empty or failure values instead of panicking.


# diff

Use `diff` to compare text by line, word, or character and render the result as plain text, HTML, JSON, or unified diff text.

```osl
import "std:diff"
```

## API reference

### `diff`

| Method                                                | Returns      | Notes                                                                         |
| ----------------------------------------------------- | ------------ | ----------------------------------------------------------------------------- |
| `diff.compareLines(oldLines: array, newLines: array)` | `DiffResult` | Compares every positional line, including all trailing additions or removals. |
| `diff.compareWords(oldText: string, newText: string)` | `DiffResult` | Compares whitespace-delimited words by position.                              |
| `diff.tokenize(text: string)`                         | `array`      | Splits text using standard Unicode whitespace rules.                          |
| `diff.text(oldStr: string, newStr: string)`           | `DiffResult` |                                                                               |
| `diff.words(oldStr: string, newStr: string)`          | `DiffResult` |                                                                               |
| `diff.chars(oldStr: string, newStr: string)`          | `DiffResult` |                                                                               |
| `diff.unified(result: DiffResult)`                    | `string`     |                                                                               |
| `diff.html(result: DiffResult)`                       | `string`     | Renders escaped HTML using a reusable single-pass replacer.                   |
| `diff.json(result: DiffResult)`                       | `string`     | Serializes the tagged result directly without an intermediate object.         |

## Notes

* Prefer `import "std:diff"`; the older `import "osl/diff"` spelling remains supported.

Line, word, and Unicode character comparisons share one builder-backed engine.


# Databases and storage

Packages for persistent data storage and caching.

* [db](broken://pages/nM2wCxYYEQqgxQz74rYh) - Embedded SQLite - SQL plus a document/collection API.
* [save](broken://pages/fpj0ircFxGfQutNrj0Jv) - Simple persistent key-value storage.
* [cache](broken://pages/iKCQgCMoRqSjsBAbhekN) - In-memory LRU cache with TTLs.
* [env](broken://pages/MmhktpFvIX3TbWjRHOhL) - Environment variables and `.env` files.


# db

```osl
import "std:db"
```

## Methods

* `db.open(path)` → `DB`
* `db.openMemory()` → `DB`
* `db.close()` → `error`
* `db.exec(query, ...args)` → `boolean`
* `db.query(query, ...args)` → `array`
* `db.queryOne(query, ...args)` → `DBRow`
* `db.queryMap(query, ...args)` → `array`
* `db.queryMapOne(query, ...args)` → `object`
* `db.insert(table, data)` → `number`
* `db.update(table, data, where, ...whereArgs)` → `boolean`
* `db.delete(table, where, ...whereArgs)` → `boolean`
* `db.count(table, where, ...whereArgs)` → `number`
* `db.exists(table, where, ...whereArgs)` → `boolean`
* `db.createTable(table, columns)` → `boolean`
* `db.dropTable(table)` → `boolean`
* `db.getTables()` → `array`
* `db.getColumns(table)` → `array`
* `db.begin()` → `boolean`
* `db.commit()` → `boolean`
* `db.rollback()` → `boolean`
* `db.transaction(fn)` → `error`
* `db.lastInsertId()` → `number`
* `db.rowsAffected(query, ...args)` → `number`
* `db.collection(name)` → `dbCollection`
* `db.collections()` → `array`

## Returned object: `DBRow`

Returned by `db` methods; call these on the value you get back.

* `dBRow.get(colIndex)` → `any`
* `dBRow.getByName(colName)` → `any`
* `dBRow.toMap()` → `object`
* `dBRow.toArray()` → `array`
* `dBRow.isEmpty()` → `boolean`
* `dBRow.count()` → `number`

## Returned object: `dbCollection`

Returned by `db` methods; call these on the value you get back.

* `dbCollection.insertOne(doc)` → `any`
* `dbCollection.insertMany(docs)` → `array`
* `dbCollection.find(filter, ...opts)` → `array`
* `dbCollection.findOne(filter)` → `object`
* `dbCollection.findById(id)` → `object`
* `dbCollection.all()` → `array`
* `dbCollection.count(filter)` → `number`
* `dbCollection.exists(filter)` → `boolean`
* `dbCollection.updateOne(filter, changes)` → `number`
* `dbCollection.updateMany(filter, changes)` → `number`
* `dbCollection.replaceOne(filter, doc)` → `number`
* `dbCollection.deleteOne(filter)` → `number`
* `dbCollection.deleteMany(filter)` → `number`
* `dbCollection.drop()` → `boolean`
* `dbCollection.save(doc)`
* `dbCollection.query()` → `dbQuery`
* `dbCollection.where(field, op, value)` → `dbQuery`
* `dbCollection.fields(...cols)` → `dbQuery`
* `dbCollection.sort(field, dir)` → `dbQuery`

## Returned object: `dbQuery`

Returned by `db` methods; call these on the value you get back.

* `dbQuery.where(field, op, value)` → `dbQuery`
* `dbQuery.and(field, op, value)` → `dbQuery`
* `dbQuery.sort(field, dir)` → `dbQuery`
* `dbQuery.fields(...cols)` → `dbQuery`
* `dbQuery.limit(n)` → `dbQuery`
* `dbQuery.skip(n)` → `dbQuery`
* `dbQuery.matched()` → `array`
* `dbQuery.all()` → `array`
* `dbQuery.get()` → `array`
* `dbQuery.first()` → `object`
* `dbQuery.count()` → `number`
* `dbQuery.exists()` → `boolean`
* `dbQuery.delete()` → `number`
* `dbQuery.set(field, value)` → `dbQuery`
* `dbQuery.unset(field)` → `dbQuery`
* `dbQuery.inc(field, n)` → `dbQuery`
* `dbQuery.mul(field, n)` → `dbQuery`
* `dbQuery.min(field, value)` → `dbQuery`
* `dbQuery.max(field, value)` → `dbQuery`
* `dbQuery.push(field, value)` → `dbQuery`
* `dbQuery.pull(field, value)` → `dbQuery`
* `dbQuery.rename(field, newField)` → `dbQuery`
* `dbQuery.apply()` → `number`

## Complete API reference

### `db`

| Method               | Returns | Notes         |
| -------------------- | ------- | ------------- |
| `db.open(path: any)` | `*DB`   |               |
| `db.openMemory()`    | `*DB`   | Opens memory. |

### `DB` values

| Method                                                                  | Returns         | Notes                |
| ----------------------------------------------------------------------- | --------------- | -------------------- |
| `value.close()`                                                         | `error`         | Closes the resource. |
| `value.exec(query: any, ...args: any)`                                  | `boolean`       |                      |
| `value.query(query: any, ...args: any)`                                 | `array`         |                      |
| `value.queryOne(query: any, ...args: any)`                              | `DBRow`         |                      |
| `value.queryMap(query: any, ...args: any)`                              | `array`         |                      |
| `value.queryMapOne(query: any, ...args: any)`                           | `object`        |                      |
| `value.insert(table: any, data: object)`                                | `number`        |                      |
| `value.update(table: any, data: object, where: any, ...whereArgs: any)` | `boolean`       |                      |
| `value.delete(table: any, where: any, ...whereArgs: any)`               | `boolean`       | Deletes a value.     |
| `value.count(table: any, where: any, ...whereArgs: any)`                | `number`        |                      |
| `value.exists(table: any, where: any, ...whereArgs: any)`               | `boolean`       |                      |
| `value.createTable(table: any, columns: object)`                        | `boolean`       | Creates table.       |
| `value.dropTable(table: any)`                                           | `boolean`       |                      |
| `value.getTables()`                                                     | `array`         | Returns tables.      |
| `value.getColumns(table: any)`                                          | `array`         | Returns columns.     |
| `value.begin()`                                                         | `boolean`       |                      |
| `value.commit()`                                                        | `boolean`       |                      |
| `value.rollback()`                                                      | `boolean`       |                      |
| `value.transaction(fn: any)`                                            | `error`         |                      |
| `value.lastInsertId()`                                                  | `number`        |                      |
| `value.rowsAffected(query: any, ...args: any)`                          | `number`        |                      |
| `value.collection(name: any)`                                           | `*dbCollection` |                      |
| `value.collections()`                                                   | `array`         |                      |

### `DBRow` values

| Method                          | Returns   | Notes                            |
| ------------------------------- | --------- | -------------------------------- |
| `value.get(colIndex: any)`      | `any`     | Returns a value.                 |
| `value.getByName(colName: any)` | `any`     | Returns by name.                 |
| `value.toMap()`                 | `object`  | Converts the value to an object. |
| `value.toArray()`               | `array`   | Converts the value to an array.  |
| `value.isEmpty()`               | `boolean` |                                  |
| `value.count()`                 | `number`  |                                  |

### `dbCollection` values

| Method                                              | Returns    | Notes                                                                   |
| --------------------------------------------------- | ---------- | ----------------------------------------------------------------------- |
| `value.insertOne(doc: object)`                      | `any`      |                                                                         |
| `value.insertMany(docs: array)`                     | `array`    |                                                                         |
| `value.find(filter: object, ...opts: object)`       | `array`    | Returns every matching document after optional sorting and paging.      |
| `value.findOne(filter: object)`                     | `object`   | Returns the first document from the shared matcher, or an empty object. |
| `value.findById(id: any)`                           | `object`   |                                                                         |
| `value.all()`                                       | `array`    |                                                                         |
| `value.count(filter: object)`                       | `number`   |                                                                         |
| `value.exists(filter: object)`                      | `boolean`  |                                                                         |
| `value.updateOne(filter: object, changes: object)`  | `number`   | Updates at most one matching document and returns the count.            |
| `value.updateMany(filter: object, changes: object)` | `number`   | Updates all matching documents and returns the count.                   |
| `value.replaceOne(filter: object, doc: object)`     | `number`   | Replaces at most one matching document, preserving its ID.              |
| `value.deleteOne(filter: object)`                   | `number`   | Deletes at most one matching document and returns the count.            |
| `value.deleteMany(filter: object)`                  | `number`   | Deletes all matching documents and returns the count.                   |
| `value.drop()`                                      | `boolean`  |                                                                         |
| `value.save(doc: object)`                           | `void`     |                                                                         |
| `value.query()`                                     | `*dbQuery` |                                                                         |
| `value.where(field: any, op: any, value: any)`      | `*dbQuery` |                                                                         |
| `value.fields(...cols: any)`                        | `*dbQuery` |                                                                         |
| `value.sort(field: any, dir: any)`                  | `*dbQuery` |                                                                         |

### `dbQuery` values

| Method                                                  | Returns    | Notes            |
| ------------------------------------------------------- | ---------- | ---------------- |
| `value.where(field: any, op: any, value: any)`          | `*dbQuery` |                  |
| `value.and(field: any, op: any, value: any)`            | `*dbQuery` |                  |
| `value.sort(field: any, dir: any)`                      | `*dbQuery` |                  |
| `value.fields(...cols: any)`                            | `*dbQuery` |                  |
| `value.limit(n: any)`                                   | `*dbQuery` |                  |
| `value.skip(n: any)`                                    | `*dbQuery` |                  |
| `value.matched()`                                       | `array`    |                  |
| `value.all()`                                           | `array`    |                  |
| `value.get()`                                           | `array`    | Returns a value. |
| `value.first()`                                         | `object`   |                  |
| `value.count()`                                         | `number`   |                  |
| `value.exists()`                                        | `boolean`  |                  |
| `value.delete()`                                        | `number`   | Deletes a value. |
| `value.addUpdate(kind: string, field: any, value: any)` | `*dbQuery` | Adds update.     |
| `value.set(field: any, value: any)`                     | `*dbQuery` | Sets a value.    |
| `value.unset(field: any)`                               | `*dbQuery` |                  |
| `value.inc(field: any, n: any)`                         | `*dbQuery` |                  |
| `value.mul(field: any, n: any)`                         | `*dbQuery` |                  |
| `value.min(field: any, value: any)`                     | `*dbQuery` |                  |
| `value.max(field: any, value: any)`                     | `*dbQuery` |                  |
| `value.push(field: any, value: any)`                    | `*dbQuery` |                  |
| `value.pull(field: any, value: any)`                    | `*dbQuery` |                  |
| `value.rename(field: any, newField: any)`               | `*dbQuery` |                  |
| `value.apply()`                                         | `number`   |                  |

## Notes

* Prefer `import "std:db"`; the older `import "osl/db"` spelling remains supported.

## Behavior and limits

Methods on a closed database, collection, or query return failure values instead of panicking. Nested transactions are rejected. Query offsets and limits cannot be negative. Document queries support nested paths, and both in-memory and file-backed databases use the same API.


# save

Use `save` for simple persistent key-value data without designing a database schema.

```osl
import "std:save"
```

## Example

```osl
import "std:save"

store = save.create()
store.init("my-app")
store.setItem("theme", "dark")
log store.getItem("theme").data
```

## API reference

### `save`

| Method                                       | Returns   | Notes                                                                        |
| -------------------------------------------- | --------- | ---------------------------------------------------------------------------- |
| `save.init(appName: string)`                 | `boolean` |                                                                              |
| `save.OSL_path(filename: string)`            | `string`  |                                                                              |
| `save.setItem(filename: string, value: any)` | `string`  | Writes an item and returns its path, or an empty string on failure.          |
| `save.getItem(filename: string)`             | `object`  | Returns item.                                                                |
| `save.exists(filename: string)`              | `boolean` | Reports whether the validated item path exists; false before initialization. |
| `save.all()`                                 | `array`   |                                                                              |
| `save.create()`                              | `*save`   | Creates an isolated save value; call `init` before reading or writing.       |

## Notes

* Prefer `import "std:save"`; the older `import "osl/save"` spelling remains supported.

## Behavior and limits

Call `init` before any read or write. Earlier operations return failure values. `init` resolves the current user's home directory each time and creates missing parent directories.


# cache

Use `cache` when you need a small in-memory store with optional TTL expiry and LRU-style capacity limits.

```osl
import "std:cache"
```

## Example

```osl
import "std:cache"

auto c = cache.create(100, 60)
c.set("token", "abc")
log c.get("token")
```

## API reference

### `cache`

| Method                                  | Returns  | Notes                                  |
| --------------------------------------- | -------- | -------------------------------------- |
| `cache.create(capacity: any, ttl: any)` | `*Cache` |                                        |
| `cache.createDefault()`                 | `*Cache` | Creates a cache with default settings. |

### `Cache` values

| Method                                  | Returns   | Notes                                                                 |
| --------------------------------------- | --------- | --------------------------------------------------------------------- |
| `value.set(key: any, value: any)`       | `boolean` | Sets a value.                                                         |
| `value.get(key: any)`                   | `any`     | Returns a value.                                                      |
| `value.getOrSet(key: any, value: any)`  | `any`     | Returns the existing value, or stores and returns `value`.            |
| `value.getOrSetFunc(key: any, fn: any)` | `any`     | Returns the existing value, or calls `fn` once and stores its result. |
| `value.delete(key: any)`                | `boolean` | Deletes a value.                                                      |
| `value.clear()`                         | `boolean` | Clears all stored values.                                             |
| `value.has(key: any)`                   | `boolean` |                                                                       |
| `value.size()`                          | `number`  | Returns the number of stored values.                                  |
| `value.keys()`                          | `array`   | Returns all keys.                                                     |
| `value.values()`                        | `array`   | Returns all values.                                                   |
| `value.entries()`                       | `object`  | Returns key-value entries.                                            |
| `value.cleanupExpired()`                | `void`    | Removes expired cache entries.                                        |
| `value.setTTL(key: any, ttl: any)`      | `boolean` | Sets the key's remaining lifetime in seconds.                         |
| `value.getTTL(key: any)`                | `number`  | Returns the key's remaining lifetime in seconds.                      |
| `value.stats()`                         | `object`  | Returns usage statistics in a fresh OSL object.                       |
| `value.setMany(data: any)`              | `boolean` | Sets every entry from an object; non-object input returns false.      |
| `value.getMany(keys: array)`            | `object`  | Returns the present values for the requested keys.                    |
| `value.deleteMany(keys: array)`         | `boolean` | Deletes each requested key.                                           |
| `value.filter(fn: any)`                 | `object`  | Returns entries for which `fn` is true.                               |
| `value.mapValues(fn: any)`              | `object`  | Applies `fn` to each value and returns the results.                   |
| `value.reduce(initial: any, fn: any)`   | `any`     | Reduces values with a callback.                                       |
| `value.foreach(fn: any)`                | `boolean` | Runs a callback for each value.                                       |
| `value.toArray()`                       | `array`   | Converts the value to an array.                                       |

## Notes

* Prefer `import "std:cache"`; the older `import "osl/cache"` spelling remains supported.

## Behavior and limits

The cache removes expired entries before reads and snapshots. A stored `null` still counts as a present value. Concurrent `getOrSetFunc` calls for the same key run the loader once, while loaders for different keys can run at the same time.


# env

```osl
import "std:env"
```

## Methods

* `env.home()` → `string`
* `env.cwd()` → `string`
* `env.file(path)` → `envFile`
* `env.read(path)` → `envFile`
* `env.parse(text)` → `envFile`
* `env.from(values)` → `envFile`
* `env.stringify(values)` → `string`
* `env.load(...paths)` → `boolean`
* `env.overload(...paths)` → `boolean`
* `env.local()` → `boolean`
* `env.localOverload()` → `boolean`
* `env.get(key)` → `string`
* `env.getDefault(key, def)` → `string`
* `env.value(key)` → `OSLenvValue`
* `env.getInt(key, def)` → `number`
* `env.getFloat(key, def)` → `number`
* `env.getBool(key, def)` → `boolean`
* `env.has(key)` → `boolean`
* `env.set(key, value)` → `boolean`
* `env.unset(key)` → `boolean`
* `env.require(key)` → `string`
* `env.required(...keys)` → `boolean`
* `env.missing(...keys)` → `array`
* `env.all()` → `object`
* `env.keys()` → `array`
* `env.expand(value)` → `string`
* `env.mode()` → `string`
* `env.isDev()` → `boolean`
* `env.isProd()` → `boolean`
* `env.isTest()` → `boolean`

## Returned object: `envValue`

Returned by `env` methods; call these on the value you get back.

* `envValue.key()` → `string`
* `envValue.exists()` → `boolean`
* `envValue.string()` → `string`
* `envValue.fallback(def)` → `string`
* `envValue.int(def)` → `number`
* `envValue.float(def)` → `number`
* `envValue.bool(def)` → `boolean`

## Returned object: `envFile`

Returned by `env` methods; call these on the value you get back.

* `envFile.path()` → `string`
* `envFile.setPath(path)` → `envFile`
* `envFile.loaded()` → `boolean`
* `envFile.read()` → `boolean`
* `envFile.load()` → `boolean`
* `envFile.overload()` → `boolean`
* `envFile.apply()` → `boolean`
* `envFile.applyOverload()` → `boolean`
* `envFile.save()` → `boolean`
* `envFile.text()` → `string`
* `envFile.all()` → `object`
* `envFile.keys()` → `array`
* `envFile.has(key)` → `boolean`
* `envFile.value(key)` → `OSLenvValue`
* `envFile.get(key)` → `string`
* `envFile.getDefault(key, def)` → `string`
* `envFile.getInt(key, def)` → `number`
* `envFile.getFloat(key, def)` → `number`
* `envFile.getBool(key, def)` → `boolean`
* `envFile.set(key, value)` → `envFile`
* `envFile.unset(key)` → `envFile`
* `envFile.clear()` → `envFile`
* `envFile.merge(values)` → `envFile`
* `envFile.expand(key)` → `string`

## Complete API reference

### `env`

| Method                                     | Returns    | Notes                                                               |
| ------------------------------------------ | ---------- | ------------------------------------------------------------------- |
| `env.home()`                               | `string`   |                                                                     |
| `env.cwd()`                                | `string`   |                                                                     |
| `env.file(path: string)`                   | `*envFile` |                                                                     |
| `env.read(path: string)`                   | `*envFile` |                                                                     |
| `env.parse(text: string)`                  | `*envFile` | Parses input data.                                                  |
| `env.from(values: object)`                 | `*envFile` |                                                                     |
| `env.stringify(values: object)`            | `string`   | Serialises a value to text.                                         |
| `env.load(...paths: string)`               | `boolean`  | Loads files in order without replacing existing environment values. |
| `env.overload(...paths: string)`           | `boolean`  | Loads files in order and replaces existing environment values.      |
| `env.local()`                              | `boolean`  |                                                                     |
| `env.localOverload()`                      | `boolean`  |                                                                     |
| `env.get(key: string)`                     | `string`   | Returns a value.                                                    |
| `env.getDefault(key: string, def: string)` | `string`   | Returns default.                                                    |
| `env.value(key: string)`                   | `envValue` |                                                                     |
| `env.getInt(key: string, def: number)`     | `number`   | Returns int.                                                        |
| `env.getFloat(key: string, def: number)`   | `number`   | Returns float.                                                      |
| `env.getBool(key: string, def: boolean)`   | `boolean`  | Returns bool.                                                       |
| `env.has(key: string)`                     | `boolean`  |                                                                     |
| `env.set(key: string, value: any)`         | `boolean`  | Sets a value.                                                       |
| `env.unset(key: string)`                   | `boolean`  |                                                                     |
| `env.require(key: string)`                 | `string`   |                                                                     |
| `env.required(...keys: string)`            | `boolean`  |                                                                     |
| `env.missing(...keys: string)`             | `array`    |                                                                     |
| `env.all()`                                | `object`   |                                                                     |
| `env.keys()`                               | `array`    | Returns all keys.                                                   |
| `env.expand(value: string)`                | `string`   |                                                                     |
| `env.mode()`                               | `string`   |                                                                     |
| `env.isDev()`                              | `boolean`  |                                                                     |
| `env.isProd()`                             | `boolean`  |                                                                     |
| `env.isTest()`                             | `boolean`  |                                                                     |

### `envFile` values

| Method                                       | Returns    | Notes                                                         |
| -------------------------------------------- | ---------- | ------------------------------------------------------------- |
| `value.path()`                               | `string`   |                                                               |
| `value.setPath(path: string)`                | `*envFile` | Sets path.                                                    |
| `value.loaded()`                             | `boolean`  |                                                               |
| `value.read()`                               | `boolean`  |                                                               |
| `value.load()`                               | `boolean`  | Reads and applies the file without replacing existing values. |
| `value.overload()`                           | `boolean`  | Reads and applies the file, replacing existing values.        |
| `value.apply()`                              | `boolean`  | Applies parsed values without replacing existing values.      |
| `value.applyOverload()`                      | `boolean`  | Applies parsed values, replacing existing values.             |
| `value.save()`                               | `boolean`  |                                                               |
| `value.text()`                               | `string`   |                                                               |
| `value.all()`                                | `object`   |                                                               |
| `value.keys()`                               | `array`    | Returns all keys.                                             |
| `value.has(key: string)`                     | `boolean`  |                                                               |
| `value.value(key: string)`                   | `envValue` |                                                               |
| `value.get(key: string)`                     | `string`   | Returns a value.                                              |
| `value.getDefault(key: string, def: string)` | `string`   | Returns default.                                              |
| `value.getInt(key: string, def: number)`     | `number`   | Returns int.                                                  |
| `value.getFloat(key: string, def: number)`   | `number`   | Returns float.                                                |
| `value.getBool(key: string, def: boolean)`   | `boolean`  | Returns bool.                                                 |
| `value.set(key: string, value: any)`         | `*envFile` | Sets a value.                                                 |
| `value.unset(key: string)`                   | `*envFile` |                                                               |
| `value.clear()`                              | `*envFile` | Clears all stored values.                                     |
| `value.merge(values: object)`                | `*envFile` |                                                               |
| `value.expand(key: string)`                  | `string`   |                                                               |

### `envValue` values

| Method                        | Returns   |
| ----------------------------- | --------- |
| `value.key()`                 | `string`  |
| `value.exists()`              | `boolean` |
| `value.string()`              | `string`  |
| `value.fallback(def: string)` | `string`  |
| `value.int(def: number)`      | `number`  |
| `value.float(def: number)`    | `number`  |
| `value.bool(def: boolean)`    | `boolean` |

## Notes

* Prefer `import "std:env"`; the older `import "osl/env"` spelling remains supported.

## Behavior and limits

The parser accepts a UTF-8 BOM, CRLF line endings, and quoted values. It detects expansion cycles. When a file repeats a key, the later value wins. Typed getters return their fallback for missing or malformed values. Key lists are sorted.


# Filesystem and system

Packages for filesystem and system operations.

* [fs](broken://pages/flmOtfRWG1jHsRRUAxfc) - Files, directories and path utilities.
* [mem](broken://pages/sJUbYTusZmUJuSMq4WAy) - Runtime memory counters, heap snapshots, and live pprof diagnostics.
* [sys](broken://pages/Pr6wAgYBvB4kqbQn1eNk) - System info, environment, and running shell commands.
* [process](broken://pages/BaoWk1xsJ4nyU4p8PC1T) - Spawn, manage and signal processes.
* [zip](broken://pages/u78EPLbc29aTD9FRyQJK) - ZIP / TAR / GZIP compression.


# fs

The `fs` package reads and writes files, manages directories, and manipulates path strings.

```osl
import "std:fs"
```

## Reading & writing files

#### `fs.readFile(path)` → `string`

Returns the entire contents of the file at `path` as a string. Returns an empty string if the file can't be read - use [`fs.tryReadFile`](#result-returning-variants) if you need to distinguish errors.

```osl
string text = fs.readFile("notes.txt")
```

#### `fs.readFileBytes(path)` → `byte[]`

Returns the file's raw bytes, for binary data. Returns empty bytes on failure.

```osl
byte[] body = fs.readFileBytes("image.png")
```

#### `fs.writeFile(path, data)` → `boolean`

Writes `data` (a string) to `path`, replacing any existing contents and creating the file if needed. The replacement is atomic: data is written to a temporary file in the same directory and renamed only after the complete file has been flushed and closed. A failed write leaves an existing file unchanged and removes the temporary file. Returns `true` on success.

```osl
fs.writeFile("out.txt", "hello world")
```

#### `fs.writeFileBytes(path, data)` → `boolean`

Writes a `byte[]` or an array of byte numbers to `path` with the same atomic replacement guarantees as `fs.writeFile`. Returns `true` on success.

#### `fs.appendToFile(path, data)` → `boolean`

Appends `data` to the end of `path`, creating the file if it doesn't exist. Returns `true` on success.

## Streaming

For large files, stream instead of loading everything into memory. `fs.open`, `fs.create` and `fs.append` return a buffered file handle; on failure they return `null`, and every handle method is null-safe (reads return `""`, writes return `false`), so a missing file can't crash a stream loop.

#### `fs.eachLine(path, fn)` → `void`

The simplest way to stream a file: calls `fn(line)` for every line (line endings stripped), reading one buffered chunk at a time. Does nothing if the file can't be opened.

```osl
fs.eachLine("big.log", def(line) -> (
  log line
))
```

#### `fs.head(path, n?)` / `fs.tail(path, n?)` / `fs.grep(path, pattern)` → `array`

One-shot conveniences that open the file, run the matching [file handle method](#file-handle-methods) and close it. `n` defaults to 10.

```osl
log fs.tail("app.log", 20).join("\n")
for line in fs.grep("app.log", "^ERROR") ( log line )
```

#### `fs.open(path)` → `file`

Opens `path` for buffered reading. Returns `null` if the file can't be opened.

```osl
auto f = fs.open("big.log")
while !f.eof() (
  log f.readLine()
)
f.close()
```

#### `fs.create(path)` → `file`

Creates (or truncates) `path` and returns a buffered write handle. Returns `null` on failure.

```osl
auto out = fs.create("out.txt")
for i 1000 (
  out.write("row " + i + "\n")
)
out.close() // flushes automatically
```

#### `fs.append(path)` → `file`

Like `fs.create` but appends to the end of `path`, creating it if needed.

### File handle methods

#### `file.readLine()` → `string`

Returns the next line with its line ending stripped, or `""` at end of file. A blank line also returns `""`, so loop on `file.eof()` rather than on the return value.

#### `file.read(n?)` → `string`

Returns the next `n` bytes, or everything remaining when called with no argument. Returns `""` at end of file.

```osl
auto f = fs.open("data.bin")
while !f.eof() (
  chunk = f.read(65536)
  // process chunk
)
f.close()
```

#### `file.eof()` → `boolean`

Reports whether the read handle has reached the end of the file. `true` for write handles and failed opens.

#### `file.head(n?)` → `array`

Returns the next `n` lines (default 10) from the current position, stopping early at end of file. On a fresh handle that's the first `n` lines, read without touching the rest of the file.

#### `file.tail(n?)` → `array`

Returns the last `n` lines (default 10) of the file, reading backwards from the end in 64 KB chunks. A multi-gigabyte log costs the same as a tiny one. It does not move the read position, so you can `tail` and then still read from the top.

#### `file.grep(pattern)` → `array`

Streams the rest of the file and returns lines matching `pattern`. Pass a regular expression or a plain substring if the pattern doesn't compile as one. Only matching lines are held in memory.

```osl
auto f = fs.open("app.log")
errors = f.grep("^ERROR")
f.close()
```

#### `file.write(data)` → `boolean`

Buffers `data` as a string, `byte[]`, or array of byte numbers. Returns `true` on success. Data is flushed when the buffer fills, on `flush()`, and on `close()`.

#### `file.flush()` → `boolean`

Forces buffered writes to disk without closing. Use it for long-lived logs.

#### `file.close()` → `boolean`

Flushes any buffered writes and closes the file. Always call this when done with a handle.

## Files & directories

#### `fs.exists(path)` → `boolean`

Reports whether a file or directory exists at `path`.

#### `fs.isDir(path)` → `boolean`

Reports whether `path` is a directory. Missing paths return `false`.

#### `fs.remove(path)` → `boolean`

Deletes the file or directory at `path` (directories are removed recursively). Returns `true` on success.

#### `fs.rename(oldPath, newPath)` → `boolean`

Renames or moves `oldPath` to `newPath`. Returns `true` on success.

#### `fs.mkdir(path)` → `boolean`

Creates a single directory. Fails if the parent directory doesn't exist.

#### `fs.mkdirAll(path)` → `boolean`

Creates `path` and any missing parent directories.

#### `fs.copy(srcPath, dstPath)` → `boolean`

Copies the file `srcPath` to `dstPath`, streaming so large files aren't loaded into memory and preserving the file's permissions. Fails if `dstPath` already exists.

#### `fs.copyDir(srcPath, dstPath)` → `boolean`

Recursively copies the directory `srcPath` to `dstPath`.

#### `fs.readDir(path)` → `array`

Returns the names of entries directly inside `path`.

```osl
for i fs.readDir(".").len (
  log fs.readDir(".")[i]
)
```

#### `fs.readDirAll(path)` → `array`

Returns entries inside `path` as objects with names, paths, extensions, and types.

#### `fs.glob(pattern)` → `array`

Returns the paths matching a shell glob pattern, e.g. `fs.glob("src/*.osl")`.

#### `fs.walk(path)` → `array`

Recursively walks `path` and returns every file and directory beneath it.

#### `fs.getwd()` → `string`

Returns the current working directory.

#### `fs.chdir(path)` → `boolean`

Changes the current working directory to `path`.

## File metadata

#### `fs.getSize(path)` → `number`

Returns the file's size in bytes, or `0` when it cannot be read.

#### `fs.getModTime(path)` → `number`

Returns the last-modified Unix timestamp, or `0` when it cannot be read.

#### `fs.getStat(path)` → `object`

Returns an object describing the file: size, modification time, whether it's a directory, and so on.

#### `fs.evalSymlinks(path)` → `string`

Resolves any symbolic links in `path` to a real path.

## Path utilities

These operate purely on path strings - they don't touch the filesystem.

#### `fs.joinPath(...path)` → `string`

Joins path segments with the OS separator: `fs.joinPath("a", "b", "c.txt")` → `a/b/c.txt`.

#### `fs.getBase(path)` → `string`

The final element of a path: `"/a/b/c.txt"` → `"c.txt"`.

#### `fs.getDir(path)` → `string`

Everything but the final element: `"/a/b/c.txt"` → `"/a/b"`.

#### `fs.getExt(path)` → `string`

The file extension, including the dot: `"c.txt"` → `".txt"`.

#### `fs.getStem(path)` → `string`

The base name without its extension: `"/a/b/c.txt"` → `"c"`.

#### `fs.getParts(path)` → `array`

Splits a path into its components.

#### `fs.cleanPath(path)` → `string`

Normalises a path, resolving `.` and `..` segments.

> **Note:** passing a path literal that contains `..` directly to a call (e.g. `fs.cleanPath("/x/../y")`) is currently mishandled by the compiler. Assign it to a variable first: `s = "/x/../y"` then `fs.cleanPath(s)`.

#### `fs.isAbs(path)` → `boolean`

Reports whether `path` is absolute.

#### `fs.splitPath(path)` → `array`

Splits a path into `[directory, file]`.

#### `fs.splitExt(path)` → `array`

Splits a path into `[nameWithoutExt, extension]`.

#### `fs.segments(path)` → `array`

Returns the non-empty path segments.

#### `fs.withExt(path, ext)` → `string`

Returns `path` with its extension replaced by `ext`.

#### `fs.withName(path, name)` → `string`

Returns `path` with its final element replaced by `name`.

#### `fs.toPosix(path)` → `string`

Converts OS-specific separators to forward slashes.

#### `fs.relPath(base, target)` → `string`

Returns the path of `target` relative to `base`.

#### `fs.pathStartsWith(path, prefix)` → `boolean`

Reports whether `path` begins with the path `prefix`.

## Result-returning variants

These mirror the methods above but return a [`result`](/standard-library/utilities/result) instead of a bare value, so you can handle errors explicitly rather than checking for `""`/`false`.

#### `fs.tryReadFile(path)` → `result`

Reads a file, returning `ok(contents)` or `err(message)`.

```osl
auto r = fs.tryReadFile("config.json")
if r.isOk() (
  log r.unwrap()
) else (
  log "couldn't read config: " ++ r.unwrapErr()
)

// or with a fallback
string body = fs.tryReadFile("config.json").unwrapOr("{}")
```

#### `fs.tryWriteFile(path, data)` → `result`

Writes a string, returning `ok(true)` or `err(message)`.

#### `fs.tryAppendToFile(path, data)` → `result`

Appends to a file, returning `ok(true)` or `err(message)`.

#### `fs.tryRename(oldPath, newPath)` → `result`

Renames/moves a path, returning `ok(true)` or `err(message)`.

#### `fs.tryRemove(path)` → `result`

Deletes a path, returning `ok(true)` or `err(message)`.

#### `fs.tryMkdirAll(path)` → `result`

Creates directories, returning `ok(true)` or `err(message)`.

#### `fs.tryReadDir(path)` → `result`

Lists a directory, returning `ok(names)` or `err(message)`.

## Complete API reference

### `fs`

| Method                                      | Returns   | Notes                                                         |
| ------------------------------------------- | --------- | ------------------------------------------------------------- |
| `fs.readFile(path: any)`                    | `string`  | Reads text, returning an empty string on failure.             |
| `fs.readFileBytes(path: any)`               | `byte[]`  | Reads bytes, returning an empty byte array on failure.        |
| `fs.writeFile(path: any, data: any)`        | `boolean` | Writes file.                                                  |
| `fs.writeFileBytes(path: any, data: any)`   | `boolean` | Writes file bytes.                                            |
| `fs.appendToFile(path: any, data: any)`     | `boolean` |                                                               |
| `fs.open(path: any)`                        | `file`    | Opens a buffered read stream, `null` on failure.              |
| `fs.create(path: any)`                      | `file`    | Opens a buffered write stream (truncates), `null` on failure. |
| `fs.append(path: any)`                      | `file`    | Opens a buffered append stream, `null` on failure.            |
| `fs.eachLine(path: any, fn: function)`      | `void`    | Calls `fn` for each line of the file.                         |
| `fs.head(path: any, n?: number)`            | `array`   | First `n` lines (default 10).                                 |
| `fs.tail(path: any, n?: number)`            | `array`   | Last `n` lines (default 10), read from the end.               |
| `fs.grep(path: any, pattern: any)`          | `array`   | Lines matching a regex (or substring).                        |
| `fs.copy(srcPath: any, dstPath: any)`       | `boolean` | Streams a file copy; fails if dst exists.                     |
| `fs.glob(pattern: any)`                     | `array`   | Paths matching a glob pattern.                                |
| `fs.rename(oldPath: any, newPath: any)`     | `boolean` |                                                               |
| `fs.exists(path: any)`                      | `boolean` |                                                               |
| `fs.remove(path: any)`                      | `boolean` | Removes a value or resource.                                  |
| `fs.mkdir(path: any)`                       | `boolean` |                                                               |
| `fs.mkdirAll(path: any)`                    | `boolean` |                                                               |
| `fs.copyDir(srcPath: any, dstPath: any)`    | `boolean` |                                                               |
| `fs.readDir(path: any)`                     | `array`   | Reads dir.                                                    |
| `fs.readDirAll(path: any)`                  | `array`   | Reads dir all.                                                |
| `fs.walkDir(path: any, fn: function)`       | `void`    | Walks a directory tree and calls `fn` for each entry.         |
| `fs.walk(path: any)`                        | `array`   |                                                               |
| `fs.isDir(path: any)`                       | `boolean` |                                                               |
| `fs.getwd()`                                | `string`  |                                                               |
| `fs.chdir(path: any)`                       | `boolean` |                                                               |
| `fs.joinPath(...path: any)`                 | `string`  |                                                               |
| `fs.getBase(path: any)`                     | `string`  | Returns base.                                                 |
| `fs.getDir(path: any)`                      | `string`  | Returns dir.                                                  |
| `fs.getExt(path: any)`                      | `string`  | Returns ext.                                                  |
| `fs.getParts(path: any)`                    | `array`   | Returns parts.                                                |
| `fs.getStem(path: any)`                     | `string`  | Returns stem.                                                 |
| `fs.cleanPath(path: any)`                   | `string`  |                                                               |
| `fs.isAbs(path: any)`                       | `boolean` |                                                               |
| `fs.splitPath(path: any)`                   | `array`   |                                                               |
| `fs.splitExt(path: any)`                    | `array`   |                                                               |
| `fs.segments(path: any)`                    | `array`   |                                                               |
| `fs.withExt(path: any, ext: any)`           | `string`  |                                                               |
| `fs.withName(path: any, name: any)`         | `string`  |                                                               |
| `fs.toPosix(path: any)`                     | `string`  | Converts to posix.                                            |
| `fs.relPath(base: any, target: any)`        | `string`  |                                                               |
| `fs.pathStartsWith(path: any, prefix: any)` | `boolean` |                                                               |
| `fs.getSize(path: any)`                     | `number`  | Returns size.                                                 |
| `fs.getModTime(path: any)`                  | `number`  | Returns mod time.                                             |
| `fs.getStat(path: any)`                     | `object`  | Returns stat.                                                 |
| `fs.evalSymlinks(path: any)`                | `string`  |                                                               |
| `fs.tryReadFile(path: any)`                 | `*Result` |                                                               |
| `fs.tryWriteFile(path: any, data: any)`     | `*Result` |                                                               |
| `fs.tryAppendToFile(path: any, data: any)`  | `*Result` |                                                               |
| `fs.tryRename(oldPath: any, newPath: any)`  | `*Result` |                                                               |
| `fs.tryRemove(path: any)`                   | `*Result` |                                                               |
| `fs.tryMkdirAll(path: any)`                 | `*Result` |                                                               |
| `fs.tryReadDir(path: any)`                  | `*Result` |                                                               |

### `file` (stream handle)

Returned by `fs.open`, `fs.create` and `fs.append`; `null` on failure, and all methods are safe to call on a failed handle.

| Method                    | Returns   | Notes                                            |
| ------------------------- | --------- | ------------------------------------------------ |
| `file.read(n?: number)`   | `string`  | Next `n` bytes, or everything remaining.         |
| `file.readLine()`         | `string`  | Next line, ending stripped.                      |
| `file.eof()`              | `boolean` | Whether the read side is exhausted.              |
| `file.head(n?: number)`   | `array`   | Next `n` lines (default 10).                     |
| `file.tail(n?: number)`   | `array`   | Last `n` lines of the file, read from the end.   |
| `file.grep(pattern: any)` | `array`   | Remaining lines matching a regex (or substring). |
| `file.write(data: any)`   | `boolean` | Buffered write of a string, bytes or byte array. |
| `file.flush()`            | `boolean` | Forces buffered writes to disk.                  |
| `file.close()`            | `boolean` | Flushes and closes the handle.                   |

## Notes

* Prefer `import "std:fs"`; the older `import "osl/fs"` spelling remains supported.

## Behavior and limits

`copyDir` rejects attempts to copy a directory into itself. Read helpers cap their allocations. Methods on a closed file handle return failure values instead of panicking. Walk callbacks accept OSL functions. Permission and symbolic-link errors are returned to the caller.


# mem

The `mem` package exposes Go's runtime memory counters and standard pprof profiles. Go's runtime already samples allocations, so importing this package adds no custom instrumentation or continuous work.

```osl
import "std:mem"
```

#### `mem.dump(path)` -> `boolean`

Forces a garbage collection, then writes a heap profile to `path`. Returns `true` when the complete profile was written and closed successfully, or `false` otherwise. The garbage collection and file write only run when `dump` is called. The profile atomically replaces the destination after it has been flushed and closed. A failed dump leaves an existing profile intact and removes its temporary file.

```osl
mem.dump("heap.pprof")
```

Show the functions retaining the most live memory:

```bash
go tool pprof -top heap.pprof
```

Show the functions responsible for the most total allocated memory instead:

```bash
go tool pprof -top -alloc_space heap.pprof
```

Compare a later heap against an earlier baseline to find memory that remained reachable:

```bash
go tool pprof -top -base heap-before.pprof heap-after.pprof
```

#### `mem.dumpProfile(name, path)` -> `boolean`

Writes any named runtime profile to `path`. Returns `false` if the profile name is unknown or the file cannot be written. Useful names for memory investigations are `heap`, `allocs`, and `goroutine`. Heap and allocation dumps force garbage collection first. Successful dumps replace the destination atomically; failures preserve an existing file.

```osl
mem.dumpProfile("goroutine", "goroutines.pprof")
```

#### `mem.stats()` -> `object`

Returns a point-in-time runtime snapshot. Call it at intervals from application code when you need a lightweight time series; it does not start a sampler or retain previous readings.

| Field                                              | Meaning                                                                       |
| -------------------------------------------------- | ----------------------------------------------------------------------------- |
| `alloc`, `heapAlloc`                               | Bytes in reachable heap objects.                                              |
| `totalAlloc`                                       | Cumulative bytes allocated since process start.                               |
| `sys`                                              | Bytes obtained from the operating system for the Go runtime. This is not RSS. |
| `mallocs`, `frees`, `liveObjects`, `heapObjects`   | Object allocation and liveness counts.                                        |
| `heapSys`, `heapIdle`, `heapInuse`, `heapReleased` | Heap reservation and release breakdown.                                       |
| `stackInuse`, `stackSys`                           | Goroutine stack memory.                                                       |
| `mspanInuse`, `mcacheInuse`, `gcSys`, `otherSys`   | Runtime metadata memory.                                                      |
| `nextGC`, `numGC`, `pauseTotalNs`, `gcCPUFraction` | Garbage collector state and cost.                                             |
| `goroutines`                                       | Current goroutine count.                                                      |
| `profileRate`                                      | Average bytes allocated per heap-profile sample.                              |

#### `mem.gc()` -> `object`

Forces garbage collection and returns the same object as `mem.stats()`. If `heapAlloc` keeps growing across comparable post-GC readings, take and compare heap profiles to find the roots.

#### `mem.setProfileRate(bytes)` -> `number`

Sets the average bytes allocated per heap-profile sample and returns the previous rate. The default is normally 524288 bytes. Set it once, as early as possible: smaller values improve profile resolution but add overhead, `1` records every allocation, and `0` disables profiling.

```osl
number oldRate = mem.setProfileRate(65536)
```

#### `mem.serve(address)` -> `boolean`

Starts the standard Go pprof HTTP endpoints on `address` and returns whether listening started. Bind to localhost unless the endpoint is protected; profiles can contain sensitive process data. Header reads time out after 10 seconds. The idle endpoint adds no custom sampling loop.

```osl
mem.serve("127.0.0.1:6060")
```

Open the interactive heap viewer:

```bash
go tool pprof -http=:0 http://127.0.0.1:6060/debug/pprof/heap?gc=1
```

Capture comparable snapshots and a goroutine profile:

```bash
curl -o heap-before.pprof 'http://127.0.0.1:6060/debug/pprof/heap?gc=1'
curl -o heap-after.pprof 'http://127.0.0.1:6060/debug/pprof/heap?gc=1'
curl -o goroutines.pprof http://127.0.0.1:6060/debug/pprof/goroutine
```

The endpoint also exposes CPU profiles and runtime traces through the standard pprof paths.

#### `mem.address()` -> `string`

Returns the pprof listener's bound address. This includes the operating system assigned port when `mem.serve("127.0.0.1:0")` is used. Returns an empty string when the server is stopped.

#### `mem.stop()` -> `boolean`

Closes the pprof listener and its active HTTP connections. Returns `false` when no server is active.


# sys

Use `sys` for process arguments, environment variables, working directories, process IDs, shell commands, and opening URLs.

```osl
import "std:sys"
```

## Example

```osl
import "std:sys"

log sys.getArgs()
log sys.getCwd()
```

## API reference

### `sys`

| Method                                   | Returns   | Notes                                                                                                                                  |
| ---------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `sys.getArgs()`                          | `array`   | Returns args.                                                                                                                          |
| `sys.getEnv(key: string)`                | `string`  | Returns env.                                                                                                                           |
| `sys.setEnv(key: string, value: string)` | `boolean` | Sets env.                                                                                                                              |
| `sys.unsetEnv(key: string)`              | `boolean` | Removes an environment variable.                                                                                                       |
| `sys.getCwd()`                           | `string`  | Returns cwd.                                                                                                                           |
| `sys.chdir(path: string)`                | `boolean` |                                                                                                                                        |
| `sys.getPid()`                           | `number`  | Returns pid.                                                                                                                           |
| `sys.getPpid()`                          | `number`  | Returns ppid.                                                                                                                          |
| `sys.getUid()`                           | `number`  | Returns uid.                                                                                                                           |
| `sys.getGid()`                           | `number`  | Returns gid.                                                                                                                           |
| `sys.getUsername()`                      | `string`  | Returns the current user's name.                                                                                                       |
| `sys.getHomeDir()`                       | `string`  | Returns the current user's home directory.                                                                                             |
| `sys.cmd(cmd: string, ...args: string)`  | `string`  | Runs a command with arguments forwarded directly. Returns stdout up to 16 MiB, or an empty string on command failure or excess output. |
| `sys.getExecutablePath()`                | `string`  | Returns executable path.                                                                                                               |
| `sys.openURL(url: string)`               | `boolean` | Opens a validated URL using the exact platform command.                                                                                |

## Notes

* Prefer `import "std:sys"`; the older `import "osl/sys"` spelling remains supported.

## Behavior and limits

Environment and working-directory changes are safe across threads. User and group ID `0` is reported rather than treated as missing. `openURL` rejects malformed URLs and unsupported schemes.


# process

Use `process` to spawn external commands, capture output, stream input, manage environment variables, and signal processes.

```osl
import "std:process"
```

## Example

```osl
import "std:process"

auto p = process.spawn("echo", "hello")
log p.run().output
```

## API reference

### `process`

| Method                                                      | Returns    | Notes                                                                                                               |
| ----------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------- |
| `process.spawn(command: any, ...args: any)`                 | `*Process` | Creates a process handle without starting it.                                                                       |
| `process.spawnShell(command: any)`                          | `*Process` |                                                                                                                     |
| `process.getPID()`                                          | `number`   | Returns pid.                                                                                                        |
| `process.getPPID()`                                         | `number`   | Returns ppid.                                                                                                       |
| `process.killPID(pid: any)`                                 | `boolean`  |                                                                                                                     |
| `process.signalPID(pid: any, sig: any)`                     | `boolean`  |                                                                                                                     |
| `process.isPIDRunning(pid: any)`                            | `boolean`  |                                                                                                                     |
| `process.list()`                                            | `array`    | Lists processes with a 16 MiB command-output limit. Returns an empty array if discovery fails or exceeds the limit. |
| `process.findByPID(pid: any)`                               | `object`   |                                                                                                                     |
| `process.findByName(name: any)`                             | `array`    |                                                                                                                     |
| `process.killByName(name: any)`                             | `number`   |                                                                                                                     |
| `process.environment()`                                     | `object`   |                                                                                                                     |
| `process.setEnvironment(key: any, value: any)`              | `boolean`  | Sets environment.                                                                                                   |
| `process.getEnvironment(key: any)`                          | `string`   | Returns environment.                                                                                                |
| `process.workingDir()`                                      | `string`   |                                                                                                                     |
| `process.setWorkingDir(path: any)`                          | `boolean`  | Sets working dir.                                                                                                   |
| `process.getArguments()`                                    | `array`    | Returns arguments.                                                                                                  |
| `process.getArg(index: any)`                                | `string`   | Returns arg.                                                                                                        |
| `process.getExecutablePath()`                               | `string`   | Returns executable path.                                                                                            |
| `process.exec(command: any, ...args: any)`                  | `string`   | Runs a command through `sys.cmd`, returning at most 16 MiB of stdout or an empty string.                            |
| `process.execAsUser(user: any, command: any, ...args: any)` | `string`   | Runs a command through `sudo` with a 16 MiB combined-output limit.                                                  |
| `process.pipe(process1: *Process, process2: *Process)`      | `boolean`  |                                                                                                                     |
| `process.background(command: any, ...args: any)`            | `*Process` |                                                                                                                     |
| `process.daemonize(command: any, ...args: any)`             | `boolean`  |                                                                                                                     |
| `process.fork()`                                            | `object`   |                                                                                                                     |
| `process.waitPID(pid: any)`                                 | `object`   |                                                                                                                     |
| `process.getMemoryMB()`                                     | `number`   | Returns memory mb.                                                                                                  |
| `process.getCPUTime()`                                      | `number`   | Returns cputime.                                                                                                    |
| `process.getNumGoroutines()`                                | `number`   | Returns num goroutines.                                                                                             |
| `process.getNumCPU()`                                       | `number`   | Returns num cpu.                                                                                                    |
| `process.setNumCPU(n: any)`                                 | `void`     | Sets num cpu.                                                                                                       |
| `process.sleep(seconds: any)`                               | `number`   |                                                                                                                     |
| `process.exit(code: any)`                                   | `void`     |                                                                                                                     |
| `process.getExitCode()`                                     | `number`   | Returns exit code.                                                                                                  |

### `Process` values

| Method                                                 | Returns   | Notes                                                                                                                                           |
| ------------------------------------------------------ | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `value.run()`                                          | `object`  | Runs the process and captures at most 16 MiB of combined output.                                                                                |
| `value.runLimited(maxBytes: any)`                      | `object`  | Runs with a combined-output limit. Nonpositive values use 16 MiB; values above 1 GiB use 1 GiB.                                                 |
| `value.runTimeout(timeout: any)`                       | `object`  | Runs for at most 300 seconds, captures at most 16 MiB of combined output, and always reaps the child; nonpositive timeouts use one millisecond. |
| `value.runTimeoutLimited(timeout: any, maxBytes: any)` | `object`  | Combines the timeout and output limits from `runTimeout` and `runLimited`.                                                                      |
| `value.start()`                                        | `boolean` | Starts the resource.                                                                                                                            |
| `value.wait()`                                         | `object`  | Waits for a started process and returns the same lifecycle result shape as `run`.                                                               |
| `value.kill()`                                         | `boolean` | Kills the running process.                                                                                                                      |
| `value.signal(sig: any)`                               | `boolean` | Sends a signal to the running process.                                                                                                          |
| `value.isRunning()`                                    | `boolean` | Reports whether the process is running.                                                                                                         |
| `value.getPID()`                                       | `number`  | Returns the process ID, or `0` before start.                                                                                                    |

## Notes

* Prefer `import "std:process"`; the older `import "osl/process"` spelling remains supported.

## Behavior and limits

Missing commands, failed starts, timeouts, invalid process IDs, and repeated `wait`, `kill`, or `signal` calls return failure values instead of panicking. Run results include `timeout` and `truncated` flags. When output exceeds its limit, `output` contains the exact captured prefix, `truncated` is `true`, and `success` is `false` even when the child exits zero. Only one call may start or wait on a given `Process` at a time. `kill`, `signal`, `isRunning`, and `getPID` remain available while `run` waits.


# zip

Use `zip` for ZIP, TAR, and GZIP archives, compressed strings, listing archive contents, and extracting individual files.

```osl
import "std:zip"
```

## Example

```osl
import "std:zip"

zip.compress("dist", "dist.zip")
log zip.list("dist.zip")
```

## API reference

### `zip`

| Method                                                                                             | Returns   | Notes                                                                                                             |
| -------------------------------------------------------------------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------- |
| `zip.compress(sourcePath: any, outputPath: any)`                                                   | `boolean` |                                                                                                                   |
| `zip.decompress(zipPath: any, outputPath: any)`                                                    | `boolean` | Extracts a ZIP archive. Entries that escape `outputPath` are rejected.                                            |
| `zip.decompressLimited(zipPath: any, outputPath: any, maxBytes: number, maxFiles: number)`         | `boolean` | Extracts an archive while enforcing total expanded-byte and entry-count limits.                                   |
| `zip.list(zipPath: any)`                                                                           | `any`     | Lists archive entry metadata.                                                                                     |
| `zip.tar(sourcePath: any, outputPath: any)`                                                        | `boolean` |                                                                                                                   |
| `zip.untar(tarPath: any, outputPath: any)`                                                         | `boolean` | Extracts a tar archive into outputPath. Entries whose names would escape outputPath (path traversal) are skipped. |
| `zip.gzip(sourcePath: any, outputPath: any)`                                                       | `boolean` | Compresses a file with GZIP.                                                                                      |
| `zip.gzipLimited(sourcePath: any, outputPath: any, maxInputBytes: number, maxOutputBytes: number)` | `boolean` | Gzips a file while enforcing input and compressed-output limits. Removes an oversized output.                     |
| `zip.gunzip(sourcePath: any, outputPath: any)`                                                     | `boolean` | Decompresses a GZIP file.                                                                                         |
| `zip.compressString(data: any)`                                                                    | `string`  |                                                                                                                   |
| `zip.decompressString(data: any)`                                                                  | `string`  |                                                                                                                   |
| `zip.fileInfo(zipPath: any, filePath: any)`                                                        | `any`     | Returns metadata for one archive entry.                                                                           |
| `zip.extractFile(zipPath: any, filePath: any, outputPath: any)`                                    | `boolean` | Extracts one archive entry.                                                                                       |
| `zip.addFile(zipPath: any, filePath: any)`                                                         | `boolean` | Atomically rewrites the archive and adds a file or directory tree using the same traversal as `compress`.         |
| `zip.removeFile(zipPath: any, filePath: any)`                                                      | `boolean` | Atomically rewrites the archive without the named entry.                                                          |
| `zip.copyFile(sourcePath: string, outputPath: string)`                                             | `boolean` | Copies a file without compression.                                                                                |
| `zip.create(path: string)`                                                                         | `boolean` |                                                                                                                   |
| `zip.statistics(zipPath: any)`                                                                     | `any`     | Returns archive entry and size totals.                                                                            |
| `zip.gunzipLimited(sourcePath: any, outputPath: any, maxOutputBytes: any)`                         | `boolean` | Decompresses with an output-size limit and removes partial output on failure.                                     |

## Notes

* Prefer `import "std:zip"`; the older `import "osl/zip"` spelling remains supported.

## Behavior and limits

ZIP and TAR extraction reject path traversal and escapes through symbolic links, including when the destination is the current directory. Before writing, extraction rejects duplicate paths and file/directory conflicts. Corrupt or truncated archives, size-limit failures, and write errors remove partial output. Limited GZIP extraction stops at the requested byte limit. Empty archives remain valid.


# Cryptography and security

Packages for cryptography and security operations.

* [crypto](broken://pages/hQFtti9TulwypbVmTWsY) - Hashing, HMAC, AES, password hashing, file encryption, random.
* [jwt](broken://pages/JrNEjoLrEhguWOYnNU5q) - JSON Web Token signing and verification.


# crypto

Use `crypto` for hashing, random values, encoding helpers, password checks, signatures, and file encryption helpers.

```osl
import "std:crypto"
```

## Example

```osl
import "std:crypto"

string digest = crypto.sha256("hello")
string id = crypto.randomUUID()
log digest
```

## API reference

### `crypto`

| Method                                                                                 | Returns   | Notes                                                                                       |
| -------------------------------------------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------- |
| `crypto.sha1(data: any)`                                                               | `string`  | Returns a hexadecimal SHA-1 digest.                                                         |
| `crypto.sha256(data: any)`                                                             | `string`  | Returns a hexadecimal SHA-256 digest.                                                       |
| `crypto.sha512(data: any)`                                                             | `string`  | Returns a hexadecimal SHA-512 digest.                                                       |
| `crypto.md5(data: any)`                                                                | `string`  | Returns a hexadecimal MD5 digest.                                                           |
| `crypto.sha3_256(data: any)`                                                           | `string`  | Returns a hexadecimal SHA3-256 digest.                                                      |
| `crypto.hmacSha256(key: any, data: any)`                                               | `string`  | Returns a hexadecimal HMAC-SHA256 value.                                                    |
| `crypto.hmacSha512(key: any, data: any)`                                               | `string`  | Returns a hexadecimal HMAC-SHA512 value.                                                    |
| `crypto.md5Hash(data: any)`                                                            | `string`  | Alias of `crypto.md5`.                                                                      |
| `crypto.aes256Encrypt(key: any, plaintext: any)`                                       | `string`  | Encrypts with AES-GCM using a normalized 256-bit key.                                       |
| `crypto.aes256Decrypt(key: any, ciphertext: any)`                                      | `string`  | Authenticates and decrypts AES-GCM ciphertext, returning empty on failure.                  |
| `crypto.randomBytes(size: any)`                                                        | `string`  | Generates random bytes.                                                                     |
| `crypto.randomInt(...args: any)`                                                       | `number`  | Returns a secure integer in `[0, max)` or `[min, max)`.                                     |
| `crypto.randomString(length: any)`                                                     | `string`  | Generates random string.                                                                    |
| `crypto.randomFloat(...args: any)`                                                     | `number`  | Generates random float.                                                                     |
| `crypto.uuidv4()`                                                                      | `string`  |                                                                                             |
| `crypto.randomUUID()`                                                                  | `string`  | Generates random uuid.                                                                      |
| `crypto.random(min: any, max: any)`                                                    | `any`     | Returns a secure integer in `[min, max)`.                                                   |
| `crypto.hash(hashFunc: any, data: any)`                                                | `string`  |                                                                                             |
| `crypto.pbkdf2(password: any, salt: any, iterations: any, keyLen: any, hashFunc: any)` | `string`  |                                                                                             |
| `crypto.hexEncode(data: any)`                                                          | `string`  |                                                                                             |
| `crypto.hexDecode(data: any)`                                                          | `string`  |                                                                                             |
| `crypto.binEncode(data: any)`                                                          | `string`  |                                                                                             |
| `crypto.binDecode(data: any)`                                                          | `string`  |                                                                                             |
| `crypto.base64Encode(data: any)`                                                       | `string`  |                                                                                             |
| `crypto.base64Decode(data: any)`                                                       | `string`  |                                                                                             |
| `crypto.ed25519GenerateKeyPair()`                                                      | `object`  | Generates an Ed25519 key pair encoded as unpadded base64url.                                |
| `crypto.ed25519Sign(privateKey: any, data: any)`                                       | `string`  | Signs data with an Ed25519 seed or private key and returns an unpadded base64url signature. |
| `crypto.ed25519Verify(publicKey: any, data: any, signature: any)`                      | `boolean` | Verifies an unpadded base64url Ed25519 signature.                                           |
| `crypto.hashPassword(password: any)`                                                   | `string`  |                                                                                             |
| `crypto.verifyPassword(password: any, storedHash: any)`                                | `boolean` | Verifies password.                                                                          |
| `crypto.bcryptHash(password: any, cost?: any)`                                         | `string`  | Creates a bcrypt password hash using cost 10 by default.                                    |
| `crypto.bcryptVerify(password: any, storedHash: any)`                                  | `boolean` | Verifies a password against a bcrypt hash.                                                  |
| `crypto.generateKeyPair()`                                                             | `object`  |                                                                                             |
| `crypto.sign(key: any, data: any)`                                                     | `string`  |                                                                                             |
| `crypto.verify(key: any, data: any, signature: any)`                                   | `boolean` |                                                                                             |
| `crypto.constantTimeCompare(a: any, b: any)`                                           | `boolean` | Uses the standard constant-time comparison, including unequal-length rejection.             |
| `crypto.encrypt(data: any, password: any)`                                             | `string`  |                                                                                             |
| `crypto.decrypt(data: any, password: any)`                                             | `string`  |                                                                                             |
| `crypto.encryptFile(inputPath: any, outputPath: any, password: any)`                   | `boolean` | Encrypts a file. A failure removes partial output.                                          |
| `crypto.decryptFile(inputPath: any, outputPath: any, password: any)`                   | `boolean` | Decrypts a file. Authentication failure removes partial output.                             |
| `crypto.hashFile(filePath: any)`                                                       | `string`  |                                                                                             |
| `crypto.hashDirectory(dirPath: any)`                                                   | `string`  |                                                                                             |
| `crypto.secureErase(filePath: any)`                                                    | `boolean` |                                                                                             |

## Bcrypt password hashing

#### `crypto.bcryptHash(password, cost?)` → `string`

Hashes the string representation of `password` with bcrypt. The optional cost must be between 4 and 31 and defaults to 10. Invalid costs and passwords longer than bcrypt's 72-byte limit return `""`.

```osl
string stored = crypto.bcryptHash("secret")
```

#### `crypto.bcryptVerify(password, storedHash)` → `boolean`

Verifies the string representation of `password` against a standard bcrypt hash. Both `$2a$` and `$2b$` hashes are accepted. Malformed hashes return `false`.

```osl
boolean valid = crypto.bcryptVerify("secret", stored)
```

## Ed25519 signatures

#### `crypto.ed25519GenerateKeyPair()` → `object`

Generates an Ed25519 key pair. The returned object contains `private`, a 32-byte private seed, and `public`, a 32-byte public key. Both values use unpadded base64url encoding.

```osl
object keys = crypto.ed25519GenerateKeyPair()
```

#### `crypto.ed25519Sign(privateKey, data)` → `string`

Signs the string representation of `data`. `privateKey` may be either a 32-byte Ed25519 seed or a 64-byte private key encoded as unpadded base64url. Returns an unpadded base64url signature, or `""` if the key is malformed.

```osl
string signature = crypto.ed25519Sign(keys.private, "hello")
```

#### `crypto.ed25519Verify(publicKey, data, signature)` → `boolean`

Verifies the signature against the string representation of `data`. The public key and signature must use unpadded base64url encoding. Malformed keys and signatures return `false`.

```osl
boolean genuine = crypto.ed25519Verify(keys.public, "hello", signature)
```

## Notes

* Prefer `import "std:crypto"`; the older `import "osl/crypto"` spelling remains supported.

## Behavior and limits

AES keys are truncated or zero-padded to 32 bytes; nonce, salt, iteration, file, and ciphertext sizes are validated. Invalid authentication data fails closed and partial output is removed.


# jwt

Use `jwt` for signing, verifying, decoding, and inspecting JSON Web Tokens.

```osl
import "std:jwt"
```

## Example

```osl
import "std:jwt"

string token = jwt.sign({user: "ada"}, "secret")
log jwt.verify(token, "secret")
```

## API reference

### `jwt`

| Method                                                            | Returns   | Notes                                                                                      |
| ----------------------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------ |
| `jwt.encode(header: any, payload: any, secret: any)`              | `string`  | Builds a signed three-part token from raw header and payload text.                         |
| `jwt.sign(claims: object, secret: any, expiresIn: any)`           | `string`  | JSON-encodes claims and uses the same token construction as `encode`.                      |
| `jwt.signWithExpiry(claims: object, secret: any, expiresIn: any)` | `string`  | Signs with expiry.                                                                         |
| `jwt.verify(token: any, secret: any)`                             | `object`  | Verifies the signature, JSON structure, and expiry. Signature comparison is constant-time. |
| `jwt.getClaim(token: any, claim: any)`                            | `any`     | Returns claim.                                                                             |
| `jwt.isExpired(token: any)`                                       | `boolean` |                                                                                            |
| `jwt.refresh(token: any, secret: any, expiresIn: any)`            | `string`  |                                                                                            |
| `jwt.decode(token: any)`                                          | `object`  | Decodes the header and claims without verifying the signature.                             |

## Notes

* Prefer `import "std:jwt"`; the older `import "osl/jwt"` spelling remains supported.

## Behavior and limits

Verification rejects malformed tokens, unexpected algorithms, invalid signatures, and invalid or expired time claims. Both verified and unverified decoding limit the token to three bounded parts.


# Text, math, and time

Packages for text processing, math operations, and time handling.

* [emoji](https://github.com/Mistium/OSL-Docs/tree/main/packages/emoji.md) - Emoji detection, extraction, replacement, flags, and skin tones.
* [regex](https://github.com/Mistium/OSL-Docs/tree/main/packages/regex.md) - Regular expressions plus validators and text helpers.
* [semver](https://github.com/Mistium/OSL-Docs/tree/main/packages/semver.md) - Semantic-version parsing and comparison.
* [math](https://github.com/Mistium/OSL-Docs/tree/main/packages/math.md) - Maths, statistics and number theory.
* [random](https://github.com/Mistium/OSL-Docs/tree/main/packages/random.md) - Seedable pseudo-random numbers.
* [date](https://github.com/Mistium/OSL-Docs/tree/main/packages/date.md) - Dates, durations and time zones.
* [cron](https://github.com/Mistium/OSL-Docs/tree/main/packages/cron.md) - Cron-style job scheduling.


# emoji

`std:emoji` detects and edits emoji characters.

```osl
import "std:emoji"

log emoji.count("Hello 👋🌍")
log emoji.remove("Hello 👋")
```

## API

| Method                              | Returns    | Behavior                                                                     |
| ----------------------------------- | ---------- | ---------------------------------------------------------------------------- |
| `emoji.isEmoji(value)`              | `bool`     |                                                                              |
| `emoji.onlyEmoji(value)`            | `bool`     |                                                                              |
| `emoji.count(value)`                | `int`      | Counts supported emoji characters.                                           |
| `emoji.extract(value)`              | `string[]` | Returns supported emoji characters in source order.                          |
| `emoji.remove(value)`               | `string`   | Removes supported emoji characters.                                          |
| `emoji.replace(value, replacement)` | `string`   | Replaces each supported emoji character.                                     |
| `emoji.hasSkinTone(value)`          | `bool`     | Reports whether the value contains a skin-tone modifier.                     |
| `emoji.skinTone(value)`             | `string`   | Returns `light`, `medium-light`, `medium`, `medium-dark`, `dark`, or `none`. |
| `emoji.isZwjSequence(value)`        | `bool`     | Reports whether the value contains a zero-width joiner.                      |
| `emoji.isFlag(value)`               | `bool`     |                                                                              |
| `emoji.isValidReaction(value)`      | `bool`     | Accepts a supported emoji or an OriginChats reaction value.                  |

The package uses a fixed Unicode-range matcher. Combined sequences may be counted as multiple characters.


# regex

Use `regex` for regular expressions, replacements, splitting, validation helpers, and text extraction.

```osl
import "std:regex"
```

## Example

```osl
import "std:regex"

log regex.match("[a-z]+", "hello123")
log regex.findAll("[0-9]+", "abc123def456")
```

## API reference

### `regex`

| Method                                                     | Returns   | Notes                                                         |
| ---------------------------------------------------------- | --------- | ------------------------------------------------------------- |
| `regex.match(pattern: any, text: any)`                     | `boolean` |                                                               |
| `regex.find(pattern: any, text: any)`                      | `string`  |                                                               |
| `regex.findAll(pattern: any, text: any)`                   | `array`   | Returns every non-overlapping match.                          |
| `regex.findSubmatch(pattern: any, text: any)`              | `array`   | Returns the full match and capture groups, or an empty array. |
| `regex.replace(pattern: any, text: any, replacement: any)` | `string`  |                                                               |
| `regex.replaceFunc(pattern: any, text: any, fn: any)`      | `string`  |                                                               |
| `regex.split(pattern: any, text: any)`                     | `array`   |                                                               |
| `regex.count(pattern: any, text: any)`                     | `number`  |                                                               |
| `regex.test(pattern: any)`                                 | `boolean` |                                                               |
| `regex.escape(text: any)`                                  | `string`  |                                                               |
| `regex.isValidEmail(email: any)`                           | `boolean` |                                                               |
| `regex.isValidURL(url: any)`                               | `boolean` |                                                               |
| `regex.isValidIPv4(ip: any)`                               | `boolean` |                                                               |
| `regex.isValidIPv6(ip: any)`                               | `boolean` |                                                               |
| `regex.isValidPhone(phone: any)`                           | `boolean` |                                                               |
| `regex.extractEmail(text: any)`                            | `string`  |                                                               |
| `regex.extractEmails(text: any)`                           | `array`   |                                                               |
| `regex.extractURLs(text: any)`                             | `array`   |                                                               |
| `regex.extractHashtags(text: any)`                         | `array`   |                                                               |
| `regex.extractMentions(text: any)`                         | `array`   |                                                               |
| `regex.extractNumbers(text: any)`                          | `array`   |                                                               |
| `regex.extractWords(text: any)`                            | `array`   |                                                               |
| `regex.isAlpha(text: any)`                                 | `boolean` |                                                               |
| `regex.isAlphanumeric(text: any)`                          | `boolean` |                                                               |
| `regex.isNumeric(text: any)`                               | `boolean` |                                                               |
| `regex.isHexadecimal(text: any)`                           | `boolean` |                                                               |
| `regex.isBase64(text: any)`                                | `boolean` |                                                               |
| `regex.isUUID(text: any)`                                  | `boolean` |                                                               |
| `regex.stripTags(text: any)`                               | `string`  |                                                               |
| `regex.stripWhitespace(text: any)`                         | `string`  |                                                               |
| `regex.truncate(text: any, length: any, suffix: any)`      | `string`  |                                                               |
| `regex.slugify(text: any)`                                 | `string`  |                                                               |
| `regex.camelize(text: any)`                                | `string`  |                                                               |
| `regex.snakeCase(text: any)`                               | `string`  |                                                               |
| `regex.kebabCase(text: any)`                               | `string`  |                                                               |
| `regex.maskEmail(email: any)`                              | `string`  |                                                               |
| `regex.maskPhoneNumber(phone: any)`                        | `string`  |                                                               |
| `regex.highlight(text: any, pattern: any, color: any)`     | `string`  |                                                               |
| `regex.wordCount(text: any)`                               | `number`  |                                                               |
| `regex.charCount(text: any, includeSpaces: any)`           | `number`  |                                                               |
| `regex.sentenceCount(text: any)`                           | `number`  |                                                               |

## Notes

* Prefer `import "std:regex"`; the older `import "osl/regex"` spelling remains supported.

## Behavior and limits

Invalid patterns and replacement callback failures return errors. Matching code makes bounded progress after an empty match. Truncation counts Unicode code points and treats a negative limit as zero, while `charCount` reports UTF-8 bytes. IP and Base64 validators use Go's parsers.


# semver

Use `semver` to parse, compare, bump, sort, and test semantic version strings and ranges.

```osl
import "std:semver"
```

## Example

```osl
import "std:semver"

log semver.compare("1.2.0", "1.1.9")
log semver.satisfies("1.2.3", ">=1.0.0")
```

## API reference

### `semver`

| Method                                      | Returns   | Notes                                                                |
| ------------------------------------------- | --------- | -------------------------------------------------------------------- |
| `semver.parse(v: any)`                      | `object`  | Parses input data.                                                   |
| `semver.isValid(v: any)`                    | `boolean` |                                                                      |
| `semver.compare(a: any, b: any)`            | `number`  |                                                                      |
| `semver.gt(a: any, b: any)`                 | `boolean` |                                                                      |
| `semver.lt(a: any, b: any)`                 | `boolean` |                                                                      |
| `semver.gte(a: any, b: any)`                | `boolean` |                                                                      |
| `semver.lte(a: any, b: any)`                | `boolean` |                                                                      |
| `semver.eq(a: any, b: any)`                 | `boolean` |                                                                      |
| `semver.neq(a: any, b: any)`                | `boolean` |                                                                      |
| `semver.satisfies(v: any, constraint: any)` | `boolean` |                                                                      |
| `semver.inc(v: any, part: any)`             | `string`  |                                                                      |
| `semver.sort(arr: any)`                     | `array`   | Returns a semantically sorted copy of the input.                     |
| `semver.max(arr: any)`                      | `string`  | Returns the greatest version, or an empty string for an empty input. |
| `semver.min(arr: any)`                      | `string`  | Returns the least version, or an empty string for an empty input.    |

## Notes

* Prefer `import "std:semver"`; the older `import "osl/semver"` spelling remains supported.

## Behavior and limits

Parsing follows SemVer rules for prerelease identifiers, build metadata, leading zeros, and ASCII identifier characters. Malformed versions and ranges return failure values. Range boundaries use the same comparison rules as direct version comparisons.


# math

```osl
import "std:math"
```

## Methods

* `math.abs(x)` → `number`
* `math.ceil(x)` → `number`
* `math.floor(x)` → `number`
* `math.round(x)` → `number`
* `math.trunc(x)` → `number`
* `math.sqrt(x)` → `number`
* `math.cbrt(x)` → `number`
* `math.pow(base, exp)` → `number`
* `math.exp(x)` → `number`
* `math.log(x)` → `number`
* `math.log10(x)` → `number`
* `math.log2(x)` → `number`
* `math.sin(x)` → `number`
* `math.cos(x)` → `number`
* `math.tan(x)` → `number`
* `math.asin(x)` → `number`
* `math.acos(x)` → `number`
* `math.atan(x)` → `number`
* `math.atan2(y, x)` → `number`
* `math.sinh(x)` → `number`
* `math.cosh(x)` → `number`
* `math.tanh(x)` → `number`
* `math.min(a, b)` → `number`
* `math.max(a, b)` → `number`
* `math.clamp(value, min, max)` → `number`
* `math.lerp(start, end, t)` → `number`
* `math.sum(numbers)` → `number`
* `math.avg(numbers)` → `number`
* `math.median(numbers)` → `number`
* `math.mode(numbers)` → `array`
* `math.stdDev(numbers)` → `number`
* `math.variance(numbers)` → `number`
* `math.rangeOf(numbers)` → `number`
* `math.factorial(n)` → `number`
* `math.fibonacci(n)` → `number`
* `math.gcd(a, b)` → `number`
* `math.lcm(a, b)` → `number`
* `math.isPrime(n)` → `boolean`
* `math.primes(count)` → `array`
* `math.degrees(x)` → `number`
* `math.radians(x)` → `number`
* `math.random(min, max)` → `number`
* `math.randomInt(min, max)` → `number`
* `math.randomChoice(choices)` → `any`
* `math.randomSeed(seed)`
* `math.hypot(x, y)` → `number`
* `math.mod(a, b)` → `number`
* `math.isNan(x)` → `boolean`
* `math.isInf(x)` → `number`
* `math.sign(x)` → `number`
* `math.pi()` → `number`
* `math.e()` → `number`
* `math.phi()` → `number`
* `math.toFixed(x, decimals)` → `string`
* `math.toPercent(x, total)` → `number`
* `math.percentile(numbers, p)` → `number`
* `math.quantile(numbers, q)` → `number`
* `math.quartiles(numbers)` → `object`
* `math.iqr(numbers)` → `number`
* `math.product(numbers)` → `number`
* `math.minOf(numbers)` → `number`
* `math.maxOf(numbers)` → `number`
* `math.geometricMean(numbers)` → `number`
* `math.harmonicMean(numbers)` → `number`
* `math.covariance(a, b)` → `number`
* `math.correlation(a, b)` → `number`
* `math.zScores(numbers)` → `array`
* `math.normalize(numbers)` → `array`

## Complete API reference

### `math`

| Method                                       | Returns   | Notes                                                                 |
| -------------------------------------------- | --------- | --------------------------------------------------------------------- |
| `math.abs(x: any)`                           | `number`  |                                                                       |
| `math.ceil(x: any)`                          | `number`  |                                                                       |
| `math.floor(x: any)`                         | `number`  |                                                                       |
| `math.round(x: any)`                         | `number`  |                                                                       |
| `math.trunc(x: any)`                         | `number`  |                                                                       |
| `math.sqrt(x: any)`                          | `number`  |                                                                       |
| `math.cbrt(x: any)`                          | `number`  |                                                                       |
| `math.pow(base: any, exp: any)`              | `number`  |                                                                       |
| `math.exp(x: any)`                           | `number`  |                                                                       |
| `math.log(x: any)`                           | `number`  |                                                                       |
| `math.log10(x: any)`                         | `number`  |                                                                       |
| `math.log2(x: any)`                          | `number`  |                                                                       |
| `math.sin(x: any)`                           | `number`  |                                                                       |
| `math.cos(x: any)`                           | `number`  |                                                                       |
| `math.tan(x: any)`                           | `number`  |                                                                       |
| `math.asin(x: any)`                          | `number`  |                                                                       |
| `math.acos(x: any)`                          | `number`  |                                                                       |
| `math.atan(x: any)`                          | `number`  |                                                                       |
| `math.atan2(y: any, x: any)`                 | `number`  |                                                                       |
| `math.sinh(x: any)`                          | `number`  |                                                                       |
| `math.cosh(x: any)`                          | `number`  |                                                                       |
| `math.tanh(x: any)`                          | `number`  |                                                                       |
| `math.min(a: any, b: any)`                   | `number`  |                                                                       |
| `math.max(a: any, b: any)`                   | `number`  |                                                                       |
| `math.clamp(value: any, min: any, max: any)` | `number`  |                                                                       |
| `math.lerp(start: any, end: any, t: any)`    | `number`  |                                                                       |
| `math.sum(numbers: array)`                   | `number`  |                                                                       |
| `math.avg(numbers: array)`                   | `number`  |                                                                       |
| `math.median(numbers: array)`                | `number`  |                                                                       |
| `math.mode(numbers: array)`                  | `array`   |                                                                       |
| `math.stdDev(numbers: array)`                | `number`  | Returns sample standard deviation, or `0` with fewer than two values. |
| `math.variance(numbers: array)`              | `number`  | Returns population variance, or `0` with fewer than two values.       |
| `math.rangeOf(numbers: array)`               | `number`  | Returns maximum minus minimum, or `0` for an empty array.             |
| `math.factorial(n: any)`                     | `number`  |                                                                       |
| `math.fibonacci(n: any)`                     | `number`  |                                                                       |
| `math.gcd(a: any, b: any)`                   | `number`  |                                                                       |
| `math.lcm(a: any, b: any)`                   | `number`  |                                                                       |
| `math.isPrime(n: any)`                       | `boolean` |                                                                       |
| `math.primes(count: any)`                    | `array`   |                                                                       |
| `math.degrees(x: any)`                       | `number`  |                                                                       |
| `math.radians(x: any)`                       | `number`  |                                                                       |
| `math.random(min: any, max: any)`            | `number`  |                                                                       |
| `math.randomInt(min: any, max: any)`         | `number`  | Generates random int.                                                 |
| `math.randomChoice(choices: array)`          | `any`     | Generates random choice.                                              |
| `math.randomSeed(seed: any)`                 | `void`    | Generates random seed.                                                |
| `math.hypot(x: any, y: any)`                 | `number`  |                                                                       |
| `math.mod(a: any, b: any)`                   | `number`  |                                                                       |
| `math.isNan(x: any)`                         | `boolean` |                                                                       |
| `math.isInf(x: any)`                         | `number`  |                                                                       |
| `math.sign(x: any)`                          | `number`  |                                                                       |
| `math.pi()`                                  | `number`  |                                                                       |
| `math.e()`                                   | `number`  |                                                                       |
| `math.phi()`                                 | `number`  |                                                                       |
| `math.toFixed(x: any, decimals: any)`        | `string`  | Converts to fixed.                                                    |
| `math.toPercent(x: any, total: any)`         | `number`  | Converts to percent.                                                  |
| `math.percentile(numbers: array, p: any)`    | `number`  |                                                                       |
| `math.quantile(numbers: array, q: any)`      | `number`  |                                                                       |
| `math.quartiles(numbers: array)`             | `object`  |                                                                       |
| `math.iqr(numbers: array)`                   | `number`  |                                                                       |
| `math.product(numbers: array)`               | `number`  |                                                                       |
| `math.minOf(numbers: array)`                 | `number`  | Returns the minimum, or `0` for an empty array.                       |
| `math.maxOf(numbers: array)`                 | `number`  | Returns the maximum, or `0` for an empty array.                       |
| `math.geometricMean(numbers: array)`         | `number`  |                                                                       |
| `math.harmonicMean(numbers: array)`          | `number`  |                                                                       |
| `math.covariance(a: array, b: array)`        | `number`  |                                                                       |
| `math.correlation(a: array, b: array)`       | `number`  |                                                                       |
| `math.zScores(numbers: array)`               | `array`   |                                                                       |
| `math.normalize(numbers: array)`             | `array`   |                                                                       |

## Notes

* Prefer `import "std:math"`; the older `import "osl/math"` spelling remains supported.

## Behavior and limits

Statistical functions convert inputs to 64-bit floating point. `NaN` and infinity follow Go's floating-point rules. Prime helpers handle zero, negative numbers, and large boundary values. Range functions accept their bounds in either order. Random generation and reseeding are safe across threads.


# random

Use `random` for seedable pseudo-random numbers, choices, shuffling, and generated strings. For security-sensitive randomness, use `crypto`.

```osl
import "std:random"
```

## Example

```osl
import "std:random"

random.seed(123)
log random.int(10)         // integer in [0, 10)
log random.between(1, 10)  // integer in [1, 10] (inclusive)
```

## API reference

### `random`

| Method                               | Returns   | Notes                                                                                      |
| ------------------------------------ | --------- | ------------------------------------------------------------------------------------------ |
| `random.seed(n: any)`                | `void`    |                                                                                            |
| `random.float(...args: any)`         | `number`  |                                                                                            |
| `random.int(n: any)`                 | `number`  | Random integer in `[0, n)`. Extra arguments are ignored. Use `random.between` for a range. |
| `random.between(min: any, max: any)` | `number`  | Random integer in `[min, max]` (inclusive).                                                |
| `random.bool(...args: any)`          | `boolean` | Returns `true` with the given probability, which defaults to `0.5`.                        |
| `random.pick(arr: any)`              | `any`     |                                                                                            |
| `random.shuffle(arr: any)`           | `array`   | Returns a shuffled copy without mutating the input.                                        |
| `random.sample(arr: any, n: any)`    | `array`   | Returns a random sample, clamping the count between zero and the input length.             |
| `random.string(...args: any)`        | `string`  |                                                                                            |
| `random.gaussian(...args: any)`      | `number`  | Draws from a normal distribution. Mean defaults to `0` and standard deviation to `1`.      |

## Notes

* Prefer `import "std:random"`; the older `import "osl/random"` spelling remains supported.

## Behavior and limits

Choice helpers define fallback values for empty collections. Range methods accept reversed bounds, and weighted choice rejects invalid weights. A fixed seed produces a repeatable sequence. The generator remains safe when several threads use or reseed it.


# date

Use `date` for current time, Unix timestamps, durations, time-zone conversion, formatting, and date arithmetic.

```osl
import "std:date"
```

## API reference

### `date`

| Method                                          | Returns        | Notes                      |
| ----------------------------------------------- | -------------- | -------------------------- |
| `date.now()`                                    | `dateDateTime` | Returns current date/time. |
| `date.fromUnix(s: number)`                      | `dateDateTime` | Creates from unix.         |
| `date.fromUnixMs(ms: number)`                   | `dateDateTime` | Creates from unix ms.      |
| `date.duration(value: number)`                  | `dateDuration` |                            |
| `date.isLeap(year: number)`                     | `boolean`      |                            |
| `date.daysInMonth(year: number, month: number)` | `number`       |                            |

### `dateDateTime` values

| Method                                        | Returns             | Notes                        |
| --------------------------------------------- | ------------------- | ---------------------------- |
| `value.unix()`                                | `number`            |                              |
| `value.unixMs()`                              | `number`            |                              |
| `value.iso()`                                 | `string`            |                              |
| `value.format(layout: string)`                | `string`            | Formats a value for display. |
| `value.add(unit: string, value: number)`      | `dateDateTime`      |                              |
| `value.subtract(unit: string, value: number)` | `dateDateTime`      |                              |
| `value.addDuration(v: dateDuration)`          | `dateDateTime`      | Adds duration.               |
| `value.since(other: dateDateTime)`            | `dateDuration`      |                              |
| `value.until(other: dateDateTime)`            | `dateDuration`      |                              |
| `value.with(field: string, value: number)`    | `dateDateTime`      |                              |
| `value.round(unit: string)`                   | `dateDateTime`      |                              |
| `value.inTimezone(tz: string)`                | `dateZonedDateTime` |                              |
| `value.compare(other: dateDateTime)`          | `number`            |                              |
| `value.equals(other: dateDateTime)`           | `boolean`           |                              |
| `value.before(other: dateDateTime)`           | `boolean`           |                              |
| `value.after(other: dateDateTime)`            | `boolean`           |                              |

### `dateDuration` values

| Method                      | Returns  |
| --------------------------- | -------- |
| `value.totalMilliseconds()` | `number` |
| `value.seconds()`           | `number` |
| `value.minutes()`           | `number` |
| `value.hours()`             | `number` |
| `value.days()`              | `number` |

### `dateZonedDateTime` values

| Method                         | Returns  | Notes                                                                |
| ------------------------------ | -------- | -------------------------------------------------------------------- |
| `value.iso()`                  | `string` | Formats the value in its timezone, falling back to UTC when invalid. |
| `value.format(layout: string)` | `string` | Formats the value in its timezone, falling back to UTC.              |

## Notes

* Prefer `import "std:date"`; the older `import "osl/date"` spelling remains supported.

## Behavior and limits

Negative Unix milliseconds round down. Calendar arithmetic handles month ends, leap days, and daylight-saving transitions. Non-finite or overflowing durations are rejected. Format strings can escape text with brackets or backslashes, and meridiem tokens distinguish noon correctly.


# cron

Use `cron` to register named jobs, run them manually, or keep a scheduler loop checking cron-style schedules.

```osl
import "std:cron"
```

## API reference

### `cron`

| Method                                                 | Returns     | Notes                                                                                            |
| ------------------------------------------------------ | ----------- | ------------------------------------------------------------------------------------------------ |
| `cron.create()`                                        | `*Cron`     |                                                                                                  |
| `cron.addJob(name: any, schedule: any, callback: any)` | `boolean`   | Adds job.                                                                                        |
| `cron.removeJob(name: any)`                            | `boolean`   | Removes job.                                                                                     |
| `cron.enableJob(name: any)`                            | `boolean`   | Enables an existing job.                                                                         |
| `cron.disableJob(name: any)`                           | `boolean`   | Disables an existing job through the same synchronized state update.                             |
| `cron.runJob(name: any)`                               | `boolean`   | Runs job.                                                                                        |
| `cron.runAll()`                                        | `object`    | Runs each current job and returns results keyed by job name without intermediate result buffers. |
| `cron.validateSchedule(schedule: any)`                 | `boolean`   | Validates schedule.                                                                              |
| `cron.calculateNextRun(schedule: string)`              | `time.Time` |                                                                                                  |
| `cron.start()`                                         | `chan any`  | Starts a scheduler loop; concurrent loops claim each due job only once.                          |
| `cron.stop(done: chan any)`                            | `void`      | Stops the resource.                                                                              |
| `cron.checkJobs()`                                     | `void`      |                                                                                                  |
| `cron.getJobs()`                                       | `array`     | Returns job snapshots using the same shape as `getJob`.                                          |
| `cron.getJob(name: any)`                               | `object`    | Returns the named job snapshot as a regular OSL object, or `null` when absent.                   |
| `cron.getJobCount()`                                   | `number`    | Returns job count.                                                                               |
| `cron.isEnabled(name: any)`                            | `boolean`   |                                                                                                  |
| `cron.getLastRun(name: any)`                           | `number`    | Returns the last-run Unix timestamp, or `0` when absent.                                         |
| `cron.getNextRun(name: any)`                           | `number`    | Returns the next-run Unix timestamp, or `0` when absent.                                         |
| `cron.getRunCount(name: any)`                          | `number`    | Returns the completed run count, or `0` when absent.                                             |
| `cron.runOnce()`                                       | `object`    | Runs once.                                                                                       |
| `cron.clear()`                                         | `void`      | Clears all stored values.                                                                        |

## Notes

* Prefer `import "std:cron"`; the older `import "osl/cron"` spelling remains supported.

## Behavior and limits

Schedules accept names, ranges, lists, and steps. Invalid schedules return an error instead of stopping the program. The scheduler will not start a second scheduled run of a job that is still running. Calls to `runJob` are independent and can overlap. Calling `stop` more than once is safe.


# Terminal and logging

Packages for terminal UI and logging operations.

* [tui](broken://pages/naLlBPDWkivSuwZJgA47) - Terminal UI: colours, boxes, tables, prompts, menus, charts.
* [log](broken://pages/oSBbVw7rTB6xBB7LJlIP) - Levelled, colourful logging.
* [notify](broken://pages/OSScHESD3NIlb9gfvV8r) - Desktop notifications.


# tui

Use `tui` to build terminal output with cursor movement, colors, boxes, tables, prompts, menus, charts, and key input.

```osl
import "std:tui"
```

## Example

```osl
import "std:tui"

log tui.color("green", "OK")
log tui.table(["Name"], [["Ada"]])
```

## API reference

### `tui`

| Method                                                             | Returns   | Notes                                                                                     |
| ------------------------------------------------------------------ | --------- | ----------------------------------------------------------------------------------------- |
| `tui.write(value: any)`                                            | `void`    | Writes to stdout without adding a newline.                                                |
| `tui.clear()`                                                      | `void`    | Clears all stored values.                                                                 |
| `tui.clearLine()`                                                  | `void`    |                                                                                           |
| `tui.clearLines(count: any)`                                       | `void`    |                                                                                           |
| `tui.moveCursor(x: any, y: any)`                                   | `void`    |                                                                                           |
| `tui.moveUp(n: any)`                                               | `void`    |                                                                                           |
| `tui.moveDown(n: any)`                                             | `void`    |                                                                                           |
| `tui.moveRight(n: any)`                                            | `void`    |                                                                                           |
| `tui.moveLeft(n: any)`                                             | `void`    |                                                                                           |
| `tui.saveCursor()`                                                 | `void`    | Saves cursor.                                                                             |
| `tui.restoreCursor()`                                              | `void`    |                                                                                           |
| `tui.hideCursor()`                                                 | `void`    |                                                                                           |
| `tui.showCursor()`                                                 | `void`    |                                                                                           |
| `tui.color(colorName: string, text: any)`                          | `string`  | Applies a foreground color through shared ANSI wrapping.                                  |
| `tui.bgColor(colorName: string, text: any)`                        | `string`  | Applies a background color through shared ANSI wrapping.                                  |
| `tui.style(styleName: string, text: any)`                          | `string`  | Wraps text with the named ANSI style.                                                     |
| `tui.rgbColor(r: any, g: any, b: any, text: any)`                  | `string`  | Applies RGB foreground color through shared RGB formatting.                               |
| `tui.rgbBg(r: any, g: any, b: any, text: any)`                     | `string`  | Applies RGB background color through shared RGB formatting.                               |
| `tui.progress(current: any, total: any, width: any)`               | `string`  |                                                                                           |
| `tui.spinner(finished: boolean)`                                   | `string`  |                                                                                           |
| `tui.horizontal(width: any)`                                       | `string`  |                                                                                           |
| `tui.vertical(height: any)`                                        | `array`   |                                                                                           |
| `tui.box(title: any, content: any)`                                | `string`  |                                                                                           |
| `tui.drawBox(x: any, y: any, width: any, height: any, title: any)` | `void`    |                                                                                           |
| `tui.table(headers: array, rows: array)`                           | `string`  | Formats headers and data through one padded-row renderer.                                 |
| `tui.tableColored(headers: array, rows: array, colorFn: any)`      | `string`  | Uses the shared row renderer and colors data cells with `colorFn`.                        |
| `tui.Select(prompt: any, options: array)`                          | `any`     |                                                                                           |
| `tui.confirm(prompt: any)`                                         | `boolean` |                                                                                           |
| `tui.menu(title: any, items: array)`                               | `any`     |                                                                                           |
| `tui.input(prompt: any)`                                           | `string`  |                                                                                           |
| `tui.password(prompt: any)`                                        | `string`  |                                                                                           |
| `tui.center(text: any)`                                            | `string`  |                                                                                           |
| `tui.pad(text: any, width: any, align: any)`                       | `string`  |                                                                                           |
| `tui.divider(char: any, width: any, title: any)`                   | `string`  |                                                                                           |
| `tui.status(status: any, message: any)`                            | `string`  | Formats a named status with its icon and color.                                           |
| `tui.frame(text: any, width: any)`                                 | `string`  |                                                                                           |
| `tui.grid(items: array, columns: any)`                             | `string`  |                                                                                           |
| `tui.tree(items: array, prefix: any)`                              | `string`  |                                                                                           |
| `tui.barChart(data: array, width: any, showLabels: any)`           | `string`  |                                                                                           |
| `tui.width()`                                                      | `number`  |                                                                                           |
| `tui.height()`                                                     | `number`  |                                                                                           |
| `tui.size()`                                                       | `array`   | Returns terminal width and height as \[width, height].                                    |
| `tui.newScreen()`                                                  | `*Screen` |                                                                                           |
| `tui.readKey()`                                                    | `string`  | Reads one key, preserving buffered keys from fast typing or pasted input for later calls. |
| `tui.keyPressed()`                                                 | `boolean` |                                                                                           |
| `tui.interactiveSelect(prompt: any, options: array)`               | `any`     |                                                                                           |

### `Screen` values

| Method                                    | Returns | Notes          |
| ----------------------------------------- | ------- | -------------- |
| `value.Set(x: any, y: any, text: string)` | `void`  |                |
| `value.Clear()`                           | `void`  |                |
| `value.Render()`                          | `void`  |                |
| `value.WriteCenter(y: any, text: string)` | `void`  | Writes center. |

## Notes

* Prefer `import "std:tui"`; the older `import "osl/tui"` spelling remains supported.

## Behavior and limits

Table padding uses visible width, so ANSI color codes do not disturb column alignment. Negative progress becomes zero. Invalid dimensions and non-interactive output do not panic, but prompts, menus, and key input still need a real terminal.


# log

Use `log` for levelled terminal logging with configurable formatting, colors, file output, and timing helpers.

```osl
import "std:log"
```

## Example

```osl
import "std:log"

log.info("server started")
log.warn("cache is empty")
```

## API reference

### `log`

| Method                                                              | Returns   | Notes                                                                             |
| ------------------------------------------------------------------- | --------- | --------------------------------------------------------------------------------- |
| `log.setLevel(level: any)`                                          | `void`    | Sets the minimum level. Names and aliases are case-insensitive.                   |
| `log.getLevel()`                                                    | `string`  | Returns level.                                                                    |
| `log.shouldLog(level: LogLevel)`                                    | `boolean` | Compares precomputed level ranks without allocating per message.                  |
| `log.info(message: any, ...args: any)`                              | `void`    | Logs an info message, substituting stringified arguments into formatting verbs.   |
| `log.warn(message: any, ...args: any)`                              | `void`    | Logs a warning message, substituting stringified arguments into formatting verbs. |
| `log.error(message: any, ...args: any)`                             | `void`    | Logs an error message, substituting stringified arguments into formatting verbs.  |
| `log.debug(message: any, ...args: any)`                             | `void`    | Logs a debug message, substituting stringified arguments into formatting verbs.   |
| `log.success(message: any, ...args: any)`                           | `void`    | Logs a success message, substituting stringified arguments into formatting verbs. |
| `log.log(level: any, message: any, ...args: any)`                   | `void`    | Logs at the selected level, defaulting unknown levels to info.                    |
| `log.plain(message: any, ...args: any)`                             | `void`    | Prints an unprefixed message with the same argument formatting.                   |
| `log.json(data: any)`                                               | `void`    |                                                                                   |
| `log.table(headers: array, rows: array)`                            | `void`    |                                                                                   |
| `log.separator()`                                                   | `void`    |                                                                                   |
| `log.clear()`                                                       | `void`    | Clears all stored values.                                                         |
| `log.time(message: any)`                                            | `void`    |                                                                                   |
| `log.timestamp(message: any)`                                       | `void`    |                                                                                   |
| `log.enableHistory()`                                               | `void`    |                                                                                   |
| `log.disableHistory()`                                              | `void`    |                                                                                   |
| `log.getHistory()`                                                  | `array`   | Returns history.                                                                  |
| `log.clearHistory()`                                                | `void`    |                                                                                   |
| `log.countByLevel()`                                                | `object`  |                                                                                   |
| `log.exportHistory(path: any)`                                      | `boolean` |                                                                                   |
| `log.withTimestamp(level: any, message: any, ...args: any)`         | `void`    |                                                                                   |
| `log.group(title: any)`                                             | `void`    |                                                                                   |
| `log.groupEnd()`                                                    | `void`    |                                                                                   |
| `log.progressBar(current: any, total: any, width: any, label: any)` | `void`    | Prints a progress bar with percentage and width clamped to safe bounds.           |
| `log.spinner(message: any, done: boolean)`                          | `void`    | Prints a spinner frame or its completed state.                                    |
| `log.assert(condition: any, message: any)`                          | `void`    |                                                                                   |
| `log.trace(message: any)`                                           | `void`    |                                                                                   |
| `log.fatal(message: any)`                                           | `void`    |                                                                                   |
| `log.countdown(seconds: any, message: any)`                         | `void`    |                                                                                   |

## Notes

* Prefer `import "std:log"`; the older `import "osl/log"` spelling remains supported.

## Behavior and limits

History access is safe across threads. Export methods return `false` when they cannot write the file.


# notify

Use `notify` to show desktop notifications from an OSL program.

```osl
import "std:notify"
```

## API reference

### `notify`

| Method                                                       | Returns   | Notes                                                         |
| ------------------------------------------------------------ | --------- | ------------------------------------------------------------- |
| `notify.send(title: any, message: any)`                      | `boolean` | Sends data.                                                   |
| `notify.sendWithSound(title: any, message: any, sound: any)` | `boolean` | Sends with sound on macOS and otherwise falls back to `send`. |
| `notify.alert(title: any, message: any)`                     | `boolean` |                                                               |
| `notify.isAvailable()`                                       | `boolean` |                                                               |

## Notes

* Prefer `import "std:notify"`; the older `import "osl/notify"` spelling remains supported.

## Behavior and limits

On macOS and Linux, notification text is passed as command arguments rather than shell source. The Windows implementation escapes quotes before building its PowerShell literal. `isAvailable` checks for the platform command, and send failures return `false`.


# Media and documents

Packages for image, PDF, and graphics operations.

* [raylib](broken://pages/7CS2DalczzIE97EXmjlN) - Native windowing, input, 2D drawing, collision helpers, and textures.
* [img](broken://pages/wr1WqwOtxZhpSS30vMWN) - Load, transform and save images.
* [qr](broken://pages/F4BJ3emjmMzAMm6djgzS) - QR codes and barcodes.
* [pdf](broken://pages/7wOzSd2BE0VzvDuSSLA0) - Generate PDF documents.
* [canvas](broken://pages/Y3NhAeHbQ5nrxGBY7OtG) - In-memory pixel canvas.
* [colors](broken://pages/0gIZoCHypJ6UeITl24Da) - Build colour values (used by image-producing packages).
* [sound](broken://pages/mqfk0Wnw9MgfIkJ4a9GH) - Audio playback.


# img

Use `img` for loading, creating, resizing, drawing, encoding, and saving raster images.

```osl
import "std:img"
```

## API reference

### `img`

| Method                                                               | Returns          | Notes                                                                   |
| -------------------------------------------------------------------- | ---------------- | ----------------------------------------------------------------------- |
| `img.open(path: string)`                                             | `*imgImage`      | Opens a PNG or JPEG file.                                               |
| `img.openSize(path: string)`                                         | `number, number` | Reads width and height without decoding the pixels.                     |
| `img.new(w: number, h: number)`                                      | `*imgImage`      | Creates a transparent image.                                            |
| `img.clone(i: *imgImage)`                                            | `*imgImage`      | Copies an image and its pixels.                                         |
| `img.resize(i: *imgImage, w: number, h: number)`                     | `*imgImage`      | Resizes with Lanczos interpolation.                                     |
| `img.resizeFast(i: *imgImage, w: number, h: number)`                 | `*imgImage`      | Resizes with bilinear interpolation.                                    |
| `img.resizeWidth(i: *imgImage, w: number)`                           | `*imgImage`      | Changes the width and preserves the aspect ratio.                       |
| `img.resizeHeight(i: *imgImage, h: number)`                          | `*imgImage`      | Changes the height and preserves the aspect ratio.                      |
| `img.resizeFit(i: *imgImage, maxW: number, maxH: number)`            | `*imgImage`      | Fits an image inside the given bounds.                                  |
| `img.draw(dst: *imgImage, src: *imgImage, x: number, y: number)`     | `boolean`        | Replaces destination pixels with the source image.                      |
| `img.drawOver(dst: *imgImage, src: *imgImage, x: number, y: number)` | `boolean`        | Alpha-composites the source over the destination.                       |
| `img.rotate(i: *imgImage, angle: number)`                            | `*imgImage`      | Returns an image rotated by degrees.                                    |
| `img.fill(i: *imgImage, r: number, g: number, b: number, a: number)` | `boolean`        | Fills the image with an RGBA color. Channels must be between 0 and 255. |
| `img.savePNG(i: *imgImage, path: string)`                            | `boolean`        | Saves an image as PNG.                                                  |
| `img.saveJPEG(i: *imgImage, path: string, quality: number)`          | `boolean`        | Saves an image as JPEG.                                                 |
| `img.decodeBytes(data: byte[])`                                      | `*imgImage`      | Decodes PNG or JPEG bytes.                                              |
| `img.encodePNGBytes(i: *imgImage)`                                   | `byte[]`         | Encodes an image as PNG.                                                |
| `img.encodeJPEGBytes(i: *imgImage, q: number)`                       | `byte[]`         | Encodes an image as JPEG.                                               |

### `imgImage` values

| Method           | Returns  | Notes                                               |
| ---------------- | -------- | --------------------------------------------------- |
| `value.Close()`  | `void`   | Releases the pixels. Repeated calls are safe.       |
| `value.Width()`  | `number` | Returns the width, or zero after `Close`.           |
| `value.Height()` | `number` | Returns the height, or zero after `Close`.          |
| `value.Size()`   | `object` | Returns `{w, h}`, or an empty object after `Close`. |

## Notes

* Prefer `import "std:img"`; the older `import "osl/img"` spelling remains supported.

## Behavior and limits

The decoder checks image dimensions before allocating the full image. Invalid sizes, non-finite rotation angles, corrupt input, and write errors return failure values. Closing an image releases its pixel data. Save methods report encoding and file-close errors.


# qr

Use `qr` for QR codes, simple barcode-like outputs, and saving generated codes as image files.

```osl
import "std:qr"
```

## API reference

### `qr`

| Method                                                                     | Returns   | Notes                                                                    |
| -------------------------------------------------------------------------- | --------- | ------------------------------------------------------------------------ |
| `qr.generate(data: any, size: any, outputFile: any)`                       | `boolean` | Writes a QR code to a PNG file.                                          |
| `qr.generateColored(data: any, size: any, colorArg: any, outputFile: any)` | `boolean` | Uses the same payload/size normalization with a custom foreground color. |
| `qr.generateToDataURL(data: any, size: any)`                               | `string`  | Uses the same normalized raster as a base64 PNG data URL.                |
| `qr.calculateModuleCount(data: string)`                                    | `number`  |                                                                          |
| `qr.generateQRMatrix(data: string, size: number)`                          | `[]array` |                                                                          |
| `qr.calculateModules(data: string)`                                        | `array`   |                                                                          |
| `qr.addFinderPatterns(matrix: []array)`                                    | `void`    | Adds finder patterns.                                                    |
| `qr.addAlignmentPatterns(matrix: []array)`                                 | `void`    | Adds alignment patterns.                                                 |
| `qr.addTimingPatterns(matrix: []array)`                                    | `void`    | Adds timing patterns.                                                    |
| `qr.addVersionInfo(matrix: []array)`                                       | `void`    | Adds version info.                                                       |
| `qr.getAlignmentPositions(size: number)`                                   | `array`   | Returns alignment positions.                                             |
| `qr.shouldAvoidAlignment(x: number, y: number, size: number)`              | `boolean` | Reports whether a position overlaps a finder-pattern margin.             |
| `qr.generate128(data: any)`                                                | `boolean` | Validates data and writes a Code 128-style barcode PNG.                  |
| `qr.generateEAN13(data: any)`                                              | `boolean` | Validates 12 digits and writes an EAN-13 barcode PNG.                    |
| `qr.generateUPCA(data: any)`                                               | `boolean` | Validates 11 digits and writes a UPC-A barcode PNG.                      |
| `qr.generateCode39(data: any)`                                             | `boolean` | Validates Code 39 characters and writes a barcode PNG.                   |
| `qr.generateBarcode(data: any, length: any)`                               | `string`  | Generates a numeric barcode string of the requested length.              |
| `qr.generateSimpleBarcode(data: string)`                                   | `string`  |                                                                          |
| `qr.generateCode39Barcode(data: any)`                                      | `string`  | Uppercases and validates Code 39 text before adding start/stop markers.  |
| `qr.calculateChecksum(data: string)`                                       | `string`  |                                                                          |
| `qr.verifyBarcode(data: any)`                                              | `boolean` | Verifies barcode.                                                        |
| `qr.writeBarcode(barcode: string, data: any)`                              | `boolean` | Writes barcode.                                                          |
| `qr.scanBarcode(imagePath: any)`                                           | `string`  |                                                                          |
| `qr.getInfo(filePath: any)`                                                | `object`  | Returns info.                                                            |
| `qr.decode(imagePath: any)`                                                | `string`  | Returns the current explicit not-implemented diagnostic.                 |

## Notes

* Prefer `import "std:qr"`; the older `import "osl/qr"` spelling remains supported.

## Behavior and limits

QR payloads are limited to 2,953 bytes and generated images to 4,096 pixels per side. Barcode helpers reject invalid numeric input. PNG creation, encoding, and file-close errors are reported.


# pdf

Use `pdf` to build PDF documents with text, lines, shapes, images, pages, metadata, and simple layout helpers.

```osl
import "std:pdf"
```

## Example

```osl
import "std:pdf"

auto doc = pdf.create()
doc.textAt(40, 40, "Hello")
doc.save("hello.pdf")
```

## API reference

### `pdf`

| Method                                                                     | Returns   | Notes                                                                             |
| -------------------------------------------------------------------------- | --------- | --------------------------------------------------------------------------------- |
| `pdf.create()`                                                             | `*PDF`    | Creates a document through `createCustom` with 612 by 792 dimensions.             |
| `pdf.createCustom(width: any, height: any)`                                | `*PDF`    | Creates a custom-sized document, falling back to default dimensions when invalid. |
| `pdf.save(path: any)`                                                      | `boolean` | Finishes the current page and writes the document.                                |
| `pdf.generateContent()`                                                    | `string`  | Serializes page and content objects in combined output blocks.                    |
| `pdf.generateHeader()`                                                     | `string`  | Serializes the catalog and page-tree header.                                      |
| `pdf.generateKids()`                                                       | `string`  |                                                                                   |
| `pdf.addPage()`                                                            | `void`    | Finishes the current page and starts another.                                     |
| `pdf.text(content: any)`                                                   | `string`  |                                                                                   |
| `pdf.textAt(x: any, y: any, content: any)`                                 | `string`  | Emits the positioned text command directly into the page buffer.                  |
| `pdf.fontSize()`                                                           | `number`  |                                                                                   |
| `pdf.setFontSize(size: any)`                                               | `void`    | Sets font size.                                                                   |
| `pdf.setMargin(margin: any)`                                               | `void`    | Sets margin.                                                                      |
| `pdf.newLine()`                                                            | `void`    |                                                                                   |
| `pdf.paragraph(text: any)`                                                 | `string`  |                                                                                   |
| `pdf.line(x1: any, y1: any, x2: any, y2: any)`                             | `boolean` |                                                                                   |
| `pdf.rectangle(x: any, y: any, width: any, height: any)`                   | `boolean` | Draws a rectangle using shared coordinate conversion.                             |
| `pdf.fillRectangle(x: any, y: any, width: any, height: any, color: any)`   | `boolean` | Draws a filled rectangle using the same coordinate conversion.                    |
| `pdf.circle(x: any, y: any, radius: any)`                                  | `boolean` |                                                                                   |
| `pdf.image(x: any, y: any, width: any, height: any, imagePath: any)`       | `boolean` |                                                                                   |
| `pdf.addImageBytes(x: any, y: any, width: any, height: any, data: byte[])` | `boolean` | Adds an image from encoded bytes.                                                 |
| `pdf.table(headers: array, rows: array)`                                   | `boolean` |                                                                                   |
| `pdf.escapeString(str: string)`                                            | `string`  |                                                                                   |
| `pdf.setMetadata(title: any, author: any, subject: any)`                   | `void`    | Sets metadata.                                                                    |
| `pdf.addWatermark(text: any)`                                              | `string`  | Adds watermark.                                                                   |
| `pdf.getPageCount()`                                                       | `number`  | Returns page count.                                                               |
| `pdf.merge(pdfFiles: array)`                                               | `*PDF`    |                                                                                   |
| `pdf.split(pdfPath: any, outputDir: any)`                                  | `boolean` |                                                                                   |
| `pdf.addBookmark(level: any, title: any, page: any)`                       | `boolean` | Adds bookmark.                                                                    |
| `pdf.getPageText(pageNum: any)`                                            | `string`  | Returns page text.                                                                |
| `pdf.getInfo(filePath: any)`                                               | `object`  | Returns info.                                                                     |

## Notes

* Prefer `import "std:pdf"`; the older `import "osl/pdf"` spelling remains supported.

## Behavior and limits

The writer escapes text before adding it to PDF content streams. Invalid page dimensions use the defaults. Missing images are rejected. Saving an empty document produces one blank page. Write errors are reported.


# canvas

Use `canvas` for simple in-memory pixel buffers that can be filled, edited by pixel index or coordinate, and exported.

```osl
import "std:canvas"
```

## API reference

### Factory

Call `canvas(w, h, bgHex)` to create a new canvas instance, then call methods on it:

```osl
auto c = canvas(100, 100, "#fff")
log c.width()
```

### Canvas instance

After calling `canvas(w, h, bgHex)` to create a canvas, call these methods on the instance:

| Method                                        | Returns  | Notes                                                      |
| --------------------------------------------- | -------- | ---------------------------------------------------------- |
| `c.width()`                                   | `number` | Returns canvas width.                                      |
| `c.height()`                                  | `number` | Returns canvas height.                                     |
| `c.pixels()`                                  | `number` | Returns number of pixels.                                  |
| `c.setPixel(idx: any, hexColor: any)`         | `void`   | Maps a 0-based index to coordinates and uses `setPixelAt`. |
| `c.getPixel(idx: any)`                        | `string` | Maps a 0-based index to coordinates and uses `getPixelAt`. |
| `c.setPixelAt(x: any, y: any, hexColor: any)` | `void`   | Sets pixel at x, y (0-based).                              |
| `c.getPixelAt(x: any, y: any)`                | `string` | Returns pixel at x, y (0-based).                           |
| `c.fill(hexColor: any)`                       | `void`   | Fills every pixel with the given color.                    |
| `c.clear()`                                   | `void`   | Restores every pixel to the original background color.     |
| `c.stretch(newW: any, newH: any)`             | `void`   | Resizes canvas.                                            |
| `c.toURL()`                                   | `string` | Converts to data URL.                                      |
| `c.toArr()`                                   | `array`  | Returns the pixels as an array.                            |

## Notes

* Pixel positions are **0-based** (unlike OSL arrays and strings, which are 1-indexed): linear indices run from `0` to `c.pixels() - 1`, and coordinates from `(0, 0)` to `(c.width() - 1, c.height() - 1)`. Out-of-range reads return `"#000000"`.
* Prefer `import "std:canvas"`; the older `import "osl/canvas"` spelling remains supported.


# colors

Use `colors` to construct color values for image, canvas, QR, PDF, and window drawing APIs.

```osl
import "std:colors"
```

## Example

```osl
import "std:colors"

auto red = colors.rgb(255, 0, 0)
auto transparent = colors.RGBA(0, 0, 0, 128)
```

## API reference

### `colors`

| Method                                         | Returns       | Notes                                                               |
| ---------------------------------------------- | ------------- | ------------------------------------------------------------------- |
| `colors.RGBA(r: any, g: any, b: any, a: any)`  | `color.RGBA`  | Builds a color from red, green, blue, and alpha channels.           |
| `colors.rgb(r: any, g: any, b: any)`           | `color.RGBA`  | Builds an RGBA color with alpha fixed to `255`.                     |
| `colors.gray(v: any)`                          | `color.Gray`  |                                                                     |
| `colors.nrgba(r: any, g: any, b: any, a: any)` | `color.NRGBA` | Builds a non-premultiplied color through the same channel coercion. |
| `colors.hex(hex: any)`                         | `color.RGBA`  |                                                                     |

## Notes

* Prefer `import "std:colors"`; the older `import "osl/colors"` spelling remains supported.


# sound

Use `sound` for loading and controlling audio playback.

```osl
import "std:sound"
```

## API reference

### `sound`

| Method                                  | Returns   | Notes                                                    |
| --------------------------------------- | --------- | -------------------------------------------------------- |
| `sound.new(url: any)`                   | `string`  |                                                          |
| `sound.load(url: any)`                  | `string`  | Alias of `sound.new`.                                    |
| `sound.play(id: any)`                   | `boolean` | Starts or resumes playback.                              |
| `sound.start(id: any)`                  | `boolean` | Alias of `sound.play`.                                   |
| `sound.pause(id: any)`                  | `boolean` | Pauses active playback.                                  |
| `sound.unpause(id: any)`                | `boolean` | Resumes paused playback.                                 |
| `sound.unload(id: any)`                 | `boolean` | Removes the loaded sound.                                |
| `sound.clear(id: any)`                  | `boolean` | Alias of `sound.unload`.                                 |
| `sound.volume(id: any, value: number)`  | `boolean` | Stores the sound's reported volume, clamped from 0 to 1. |
| `sound.currentTime(id: any)`            | `number`  | Returns the current playback time in seconds.            |
| `sound.loaded(id: any)`                 | `boolean` |                                                          |
| `sound.playing(id: any)`                | `boolean` |                                                          |
| `sound.duration(id: any)`               | `number`  |                                                          |
| `sound.percent(id: any)`                | `number`  |                                                          |
| `sound.info(id: string, field: string)` | `number`  |                                                          |

## Notes

* Prefer `import "std:sound"`; the older `import "osl/sound"` spelling remains supported.

## Behavior and limits

Audio downloads and in-memory sources have size limits. HTTP status codes are checked. Speaker setup waits until the first playback, and audio with a different sample rate is resampled. Pause state belongs to each sound value. Calling `unload` or `clear` more than once is safe.


# Graphics and windowing


# shader

`osl/shader` allows writing fragment and vertex shaders in OSL syntax or standard GLSL, compiling them into valid GLSL code, and rendering procedural graphics directly to images or raylib windows.

```osl
import "std:shader"
import "std:img"

string plasma = "def mainImage(fragCoord) (
    vec2 uv = fragCoord / iResolution.xy
    number t = iTime * 1.5
    number v = sin(uv.x * 10.0 + t) + sin(uv.y * 10.0 + t)
    vec3 col = 0.5 + 0.5 * cos(v + vec3(0.0, 2.0, 4.0))
    return vec4(col, 1.0)
)"

// Render directly to an image without opening a window
auto image = shader.renderImage(plasma, 400, 300, {time: 1.0})
img.savePNG(image, "plasma.png")
```

## Functions

### `shader.toGLSL(code, [type])`

Transpiles OSL-style shader syntax into complete, valid GLSL shader source code. Accepts `"frag"` (default) or `"vert"`.

OSL syntax features supported:

* Block delimiters `(` ... `)` mapped to `{` ... `}`
* Functions: `def mainImage(fragCoord) ( ... )` and `def name(args) returnType ( ... )`
* Type keywords: `number` mapped to `float`, `vec2`, `vec3`, `vec4`, `mat2`, `mat3`, `mat4`
* Automatic semicolon insertion for line endings
* Standard uniform injection (`iResolution`, `iTime`, `iTimeDelta`, `iFrame`, `iMouse`, `iDate`, `iChannel0..3`, `texture0`, `colDiffuse`)
* Automatic `main()` entrypoint generation

### `shader.renderImage(code, width, height, [uniforms])`

Evaluates the shader across a `width` by `height` pixel grid and returns an `*img.Image` object. `uniforms` is an optional object supporting `time` (`iTime`) and `mouse` coordinates.

### `shader.vertexDefault()`

Returns the standard Raylib-compatible 2D vertex shader source code.

### `shader.plasma()`

Returns a built-in OSL-style plasma fragment shader source string.

### `shader.gradient()`

Returns a built-in cosine gradient fragment shader source string.

### `shader.eval(expr, [context])`

Evaluates a mathematical shader expression with the provided uniform context.


# raylib

`osl/raylib` wraps raylib-go with OSL values and a compact frame-loop API. It is for native desktop builds. Use `osl compile` or `osl run`; browser builds intentionally reject this package.

```osl
import "std:raylib"

player = {x: 20}
raylib.run({width: 800, height: 450, title: "OSL raylib", fps: 60}, def(dt) -> (
  if raylib.keyDown("right") player.x += 200 * dt
), def() -> (
  raylib.clear("#181825")
  raylib.drawRectangle(player.x, 200, 40, 40, "#89b4fa")
  raylib.drawText("Move with the arrow keys", 20, 20, 24, "white")
))
```

## Values and window lifecycle

* `raylib.color(value)` accepts named colours, `#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`, arrays, or `{r, g, b, a}` objects.
* `raylib.vec(x, y)`, `raylib.vec3(x, y, z)`, and `raylib.rect(x, y, width, height)` create geometry objects.
* `initWindow(width, height, title)`, `closeWindow()`, `windowReady()`, and `windowShouldClose()` expose manual lifecycle control.
* `run(options, update, draw)` manages the window and frame loop.
* `setTargetFPS(fps)`, `fps()`, `frameTime()`, `time()`, `screenSize()`, `setWindowTitle(title)`, and `setWindowSize(width, height)` manage timing and the window.
* `disableCursor()`, `enableCursor()`, and `cursorHidden()` manage captured mouse input.
* `setConfigFlags(flags)` configures window initialization flags before `initWindow` (e.g. `"highdpi"`, `"resizable"`, `"undecorated"`, `"vsync"`, `"transparent"`, `"msaa"`).
* `setGPUPreference(pref)` sets GPU power mode (`"integrated"` or `"discrete"`). Defaults to `"integrated"`.
* `gpuPreference()` returns the current GPU preference.

## Drawing

Use `clear`, `drawPixel`, `drawLine`, `drawRectangle`, `drawRectangleLines`, `drawCircle`, `drawCircleLines`, `drawTriangle`, `drawText`, and `measureText`. For manual loops, `beginDrawing()`, `endDrawing()`, and `draw(fn)` are available.

## 3D cameras and drawing

Create a perspective camera with `raylib.camera(options)`. Options may include `position`, `target`, `up`, `fovy`, and `projection`. The returned camera provides `update(mode)`, `position()`, `target()`, `setPosition(value)`, and `setTarget(value)`. Camera modes are `custom`, `free`, `orbital`, `first_person`, and `third_person`.

Use `begin3D(camera)` and `end3D()` around 3D drawing, or call `draw3D(camera, frame)`. The 3D drawing helpers are `drawCube(center, size, color)`, `drawCubeWires(center, size, color)`, `drawSphere(center, radius, color)`, `drawSphereWires(center, radius, rings, slices, color)`, `drawCylinder(position, radiusTop, radiusBottom, height, slices, color)`, `drawPlane(center, size, color)`, `drawLine3D(start, end, color)`, `drawBillboard(camera, texture, position, size, tint)`, and `drawGrid(slices, spacing)`.

## Rays and box picking

* `raylib.ray(origin, direction)` creates a ray from two 3D vectors.
* `raylib.screenRay(point, camera)` creates a world ray through a screen position.
* `raylib.centerRay(camera)` creates a ray through the center of the window.
* `ray.box(center, size)` tests an axis-aligned box and returns `{hit, distance, point, normal}`.

## Input and collision

* Keyboard: `keyPressed`, `keyDown`, and `keyReleased`
* Mouse: `mousePosition`, `mouseDelta()`, `setMousePosition(x, y)`, `mousePressed`, `mouseDown`, `mouseReleased`, and `mouseWheel`
* Collision: `rectanglesCollide`, `circlesCollide`, and `pointInRectangle`

Key names include letters, arrows, space, escape, enter, modifiers, and F1 through F12.

## Audio

* `initAudioDevice()` and `closeAudioDevice()` manage native audio device lifecycle.
* `audioReady()` checks if audio device is initialized.
* `setMasterVolume(volume)` adjusts master volume between `0.0` and `1.0`.
* `loadSound(path)` loads a WAV/OGG/MP3 sound handle with `play()`, `stop()`, `setVolume(vol)`, `setPitch(pitch)`, `unload()`, and `valid()`.

## Textures and Render Textures

* `raylib.loadTexture(path)` returns a texture with `valid()`, `width()`, `height()`, `draw(x, y, rotation, scale, tint)`, and `unload()` methods.
* `raylib.renderTexture(width, height)` returns an offscreen frame buffer with `valid()`, `begin()`, `end()`, `width()`, `height()`, `texture()`, `draw(x, y, rotation, scale, tint)`, and `unload()` methods.
* `raylib.drawToTexture(target, drawFunc)` executes a drawing callback into the render target.

## Shaders

* `raylib.loadShader(vs, fs)` and `raylib.loadFragmentShader(fs)` load GLSL or OSL-style shaders from code strings or file paths.
* `raylib.shader(code)` is a shorthand for loading a fragment shader.
* `raylib.beginShader(s)` and `raylib.endShader()` toggle active shader mode.
* `raylib.drawShader(s, x, y, width, height)` draws a rectangle using the active shader.

### Returned Shader object methods

* `valid()` returns boolean indicating if the shader compiled and loaded.
* `begin()` and `end()` activate and deactivate the shader.
* `setValue(name, value)` sets float, vector (`[x, y]`, `[x, y, z]`, `[x, y, z, w]`), or texture uniform values.
* `setUniform(name, value)` is an alias for `setValue`.
* `draw(x, y, width, height)` draws a rectangle filled by the shader.
* `drawFullscreen()` draws a fullscreen quad using the shader.
* `unload()` frees the shader from GPU memory.


# window

The `window` package brings OSL's original graphical model to compiled programs: you open a real desktop window and draw into it every frame using OSL's **rendering commands** (the "draw cursor", shapes, text, icons and 3D). This is the same drawing model that powered originOS apps.

> **Migration:** renderer plumbing such as `batch*`, `icn*`, `drawIconCached`, and `executeIconCommands` is now internal. Use the stable drawing commands and methods such as `line`, `rect`, `window.Icon`, and `window.Text`.

```osl
import "std:window"
```

## Program structure

A windowed program has two parts:

1. **Setup** - top-level statements that run once. Configure the window and load assets here.
2. **The main loop** - everything after the `mainloop:` label runs **once per frame**. This is where you read input and draw.

```osl
import "std:window"

window.setTitle("Hello OSL")
window.setColor("#ffffff")
window.show()
window.resize(800, 450)

number x = 0

mainloop:

x += 1
goto x window.top - 40
centext "hello world" 10 : c#000
```

`mainloop:` is a bare label - it has no parentheses and no body brackets. Everything below it is the per-frame loop, and it keeps running until the window closes.

## Window properties & methods

Read the window's size and edges as **properties** (no parentheses):

```osl
window.width      window.height
window.left       window.right
window.top        window.bottom
```

Control the window with **methods**:

| Method                                          | Purpose                                |
| ----------------------------------------------- | -------------------------------------- |
| `window.setTitle(title)`                        | Set the title bar text.                |
| `window.setColor(hex)`                          | Set the background colour.             |
| `window.show()` / `window.hide()`               | Show or hide the window.               |
| `window.resize(w, h)`                           | Resize the window.                     |
| `window.setResizable(bool)`                     | Allow or block user resizing.          |
| `window.fullscreen()` / `window.isFullscreen()` | Toggle / query fullscreen.             |
| `window.minimise()`                             | Minimise.                              |
| `window.close()`                                | Close the window and stop the program. |
| `window.on(event, handler)`                     | Listen for window events.              |

## Drawing

Inside the loop you draw with **rendering commands**. They operate on a moving "draw cursor":

```osl
goto x y                 // move the draw cursor
change_x n   change_y n  // move it relatively
c "#ff0000"              // set the draw colour
icon iconString size     // draw an ICN icon
image url w h            // draw an image
text "hello" 10          // draw text from the cursor
centext "hello" 10       // draw text centred on the cursor
```

Many commands accept an inline modifier after a colon - for example `: c#000` sets the colour just for that element:

```osl
centext "Score: " ++ score 10 : c#000
```

The rendering commands, modifiers, clipping operations, and input globals are documented on this page because they are part of `std:window`, not the core language.

## Input

Keyboard keys are queried by name:

```osl
if "space".isKeyDown() (
  jump = true
)
move_left  = "a".isKeyDown()
move_right = "d".isKeyDown()
```

The window package also exposes per-frame **global variables** you can read directly in the loop:

| Global                     | Meaning                                               |
| -------------------------- | ----------------------------------------------------- |
| `mouse_x`, `mouse_y`       | Mouse position.                                       |
| `x_position`, `y_position` | The current draw-cursor position.                     |
| `direction`                | The draw cursor's heading (for `pen`/turtle drawing). |
| `timer`                    | A steadily increasing timer, handy for timing events. |

## A complete example

```osl
import "std:window"

object player = { x: 0, y: 0 }

window.setColor("#fff")
window.setTitle("Move me")
window.show()
window.resize(800, 450)

mainloop:

if "a".isKeyDown() (
  player.x -= 4
)
if "d".isKeyDown() (
  player.x += 4
)
if "w".isKeyDown() (
  player.y += 4
)
if "s".isKeyDown() (
  player.y -= 4
)

goto player.x.toNum() player.y.toNum()
icon "c #000 square 0 0 20 20" 2

goto 0 window.top - 30
centext "use WASD" 10 : c#000
```

## Companion packages

* [`win-buttons`](/standard-library/packages) - add clickable buttons to a window (`import "std:win-buttons"`).
* [`sound`](/standard-library/media/sound) - play audio in a windowed app.

> **Heads-up:** the `window` package depends on native graphics libraries, so the first compile pulls in extra dependencies. Server and CLI programs don't need any of this.

## Complete API reference

### `window`

| Method                                    | Returns   | Notes                                                    |
| ----------------------------------------- | --------- | -------------------------------------------------------- |
| `window.on(name: any, callback: any)`     | `void`    |                                                          |
| `window.emit(name: any, ...args: any)`    | `void`    |                                                          |
| `window.show()`                           | `void`    |                                                          |
| `window.Create()`                         | `void`    |                                                          |
| `window.Goto(x: any, y: any)`             | `void`    |                                                          |
| `window.hide()`                           | `void`    |                                                          |
| `window.resize(width: any, height: any)`  | `void`    |                                                          |
| `window.close()`                          | `void`    | Closes the resource.                                     |
| `window.minimise()`                       | `void`    | Minimizes the current native window.                     |
| `window.fullscreen()`                     | `void`    | Toggles fullscreen on the current native window.         |
| `window.isFullscreen()`                   | `boolean` | Reports whether the current native window is fullscreen. |
| `window.color()`                          | `string`  |                                                          |
| `window.setColor(col: any)`               | `void`    | Sets color.                                              |
| `window.setTitle(title: any)`             | `void`    | Sets title.                                              |
| `window.keyPressed(key: string)`          | `boolean` |                                                          |
| `window.setResizable(resizable: boolean)` | `void`    | Sets resizable.                                          |
| `window.setDragbox(box: any)`             | `void`    | Sets dragbox.                                            |
| `window.dragbox()`                        | `array`   |                                                          |

### `Window` values

| Method                                   | Returns   | Notes                                                  |
| ---------------------------------------- | --------- | ------------------------------------------------------ |
| `value.Run(loop: function)`              | `void`    | Runs the window loop callback until the window closes. |
| `value.width()`                          | `number`  |                                                        |
| `value.height()`                         | `number`  |                                                        |
| `value.left()`                           | `number`  |                                                        |
| `value.right()`                          | `number`  |                                                        |
| `value.top()`                            | `number`  |                                                        |
| `value.bottom()`                         | `number`  |                                                        |
| `value.SetTitle(title: string)`          | `void`    | Sets title.                                            |
| `value.resize(width: any, height: any)`  | `void`    |                                                        |
| `value.setResizable(resizable: boolean)` | `void`    | Sets resizable.                                        |
| `value.Clear(col: color.Color)`          | `void`    |                                                        |
| `value.Update()`                         | `void`    |                                                        |
| `value.KeyPressed(key: string)`          | `boolean` |                                                        |

### `winRender` values

| Method                                                                     | Returns       | Notes                                                          |
| -------------------------------------------------------------------------- | ------------- | -------------------------------------------------------------- |
| `value.Hex(hex: any)`                                                      | `color.RGBA`  |                                                                |
| `value.Color(col: any)`                                                    | `void`        |                                                                |
| `value.Goto(x: any, y: any)`                                               | `void`        |                                                                |
| `value.Loc(a: any, b: any, c: any, d: any)`                                | `void`        |                                                                |
| `value.toScreen(x: number, y: number)`                                     | `pixel.Vec`   | Converts to screen.                                            |
| `value.drawColor()`                                                        | `color.Color` |                                                                |
| `value.Effect(name: any, value: any)`                                      | `void`        | Sets an effect; transparency is clamped when rendered.         |
| `value.penSize()`                                                          | `number`      |                                                                |
| `value.beginElement()`                                                     | `void`        |                                                                |
| `value.updateLast(minX: number, minY: number, maxX: number, maxY: number)` | `void`        |                                                                |
| `value.checkClick()`                                                       | `void`        |                                                                |
| `value.LineTo(endX: number, endY: number)`                                 | `void`        |                                                                |
| `value.Rect(...args: any)`                                                 | `void`        |                                                                |
| `value.Icon(icon: any, size: number)`                                      | `void`        |                                                                |
| `value.Text(text: string, size: any)`                                      | `void`        |                                                                |
| `value.Centext(text: string, size: any)`                                   | `void`        |                                                                |
| `value.SetThickness(thickness: number)`                                    | `void`        | Sets thickness.                                                |
| `value.Change(offsetX: number, offsetY: number)`                           | `void`        |                                                                |
| `value.Direction(dirFloat: number)`                                        | `void`        |                                                                |
| `value.Turnright(angle: number)`                                           | `void`        |                                                                |
| `value.Turnleft(angle: number)`                                            | `void`        |                                                                |
| `value.Pointat(x: number, y: number)`                                      | `void`        |                                                                |
| `value.Image(key: string, w: any, h: any)`                                 | `void`        |                                                                |
| `window.off(name: any, callback: any)`                                     | `boolean`     | Removes one event callback without disturbing other listeners. |

## Notes

* Prefer `import "std:window"`; the older `import "osl/window"` spelling remains supported.

## Behavior and limits

Empty colors and invalid image data return failure values instead of panicking. PNG and JPEG data use Go's image decoders. A failed asynchronous image load releases its queue slot. Icon commands split on whitespace and reject operations with the wrong number of numeric arguments.


# win-buttons

```osl
import "std:win-buttons"
```

Importing this package installs close, minimise, and maximise controls into an OSL window. It exposes no callable package methods and requires a graphical display. Drawing and typed hit-test tables use the same fixed control order.


# Concurrency and embedded languages

Packages for scripting, background threads, and synchronization.

* [testing](/standard-library/concurrency/testing) - Assertions for `osl test` files.
* [js](/standard-library/concurrency/js) - Run sandboxed JavaScript with hard resource limits.
* [lua](/standard-library/concurrency/lua) - Embed and run Lua scripts.
* [thread](/standard-library/concurrency/thread) - Background threads, parallel workers, racing, and message channels.
* [sync](/standard-library/concurrency/sync) - Named locks, scoped locking, one-time execution, and wait groups.


# testing

The `testing` package provides assertion helpers that stop the current test with an `AssertionError`. Test files end in `.test.osl` and can be run with `osl test`. Reserve that suffix for executable assertion suites; name ordinary demos and manually run programs with the plain `.osl` suffix so test discovery does not run them.

```osl
import "std:testing"

testing.equal(2 + 2, 4)
testing.assert("hello".contains("ell"))
```

Run every test below the current directory, or pass specific files and directories:

```bash
osl test
osl test tests/math.test.osl tests/integration
```

#### `testing.assert(condition, message?)` → `boolean`

Requires `condition` to be true. The optional message replaces the default failure text.

#### `testing.equal(actual, expected, message?)` → `boolean`

Requires the two values to be equal using OSL equality semantics. Arrays and objects are compared by value.

```osl
testing.equal([1, 2].map(def(n) -> ( n * 2 )), [2, 4])
```

#### `testing.notEqual(actual, expected, message?)` → `boolean`

Requires the two values not to be equal.

#### `testing.near(actual, expected, tolerance, message?)` → `boolean`

Requires two numeric values to differ by no more than `tolerance`. This is useful for floating-point results and simulations.

```osl
testing.near(0.1 + 0.2, 0.3, 0.000001)
```

#### `testing.isNull(value, message?)` → `boolean`

Requires `value` to be `null`.

#### `testing.notNull(value, message?)` → `boolean`

Requires `value` not to be `null`.

#### `testing.panics(fn, message?)` → `boolean`

Calls a zero-argument function and requires it to panic or throw.

```osl
testing.panics(def() -> (
  throw "expected failure"
))
```

#### `testing.fail(message)` → `boolean`

Immediately fails with the supplied message. It is useful for branches that should be unreachable.


# js

```osl
import "std:js"
```

| Method                               | Returns  | Notes                                                                                                        |
| ------------------------------------ | -------- | ------------------------------------------------------------------------------------------------------------ |
| `js.eval(code: any, timeoutMs: any)` | `object` | Runs QuickJS with a hard timeout and memory limit. Returns `{success, result}` or `{success: false, error}`. |

The sandbox has no filesystem or network access. Timeouts are clamped to a finite maximum so infinite loops cannot wedge the host process.


# lua

Use `lua` to create an embedded Lua state, execute Lua code, register OSL callbacks, and exchange values.

```osl
import "std:lua"
```

## API reference

### `lua`

| Method                                    | Returns   | Notes                                                                                 |
| ----------------------------------------- | --------- | ------------------------------------------------------------------------------------- |
| `lua.create()`                            | `*State`  |                                                                                       |
| `lua.doString(code: any)`                 | `*State`  | Creates a state with `lua.create()`, runs code, and returns it.                       |
| `lua.run(code: any)`                      | `*Result` |                                                                                       |
| `lua.runFile(path: any)`                  | `object`  | Runs a file and returns flat `success` and `error` fields.                            |
| `lua.get(code: any, name: any)`           | `any`     | Runs the code and returns the named Lua global.                                       |
| `lua.eval(code: any)`                     | `any`     | Evaluates an expression, or executes a statement and returns its result when present. |
| `lua.newTable()`                          | `any`     |                                                                                       |
| `lua.version()`                           | `string`  |                                                                                       |
| `lua.runTimeout(code: any, timeout: any)` | `result`  | Runs code with a timeout and returns an error result on failure.                      |

### `State` values

| Method                                    | Returns   | Notes                |
| ----------------------------------------- | --------- | -------------------- |
| `value.close()`                           | `void`    | Closes the resource. |
| `value.doString(code: any)`               | `boolean` |                      |
| `value.doFile(path: any)`                 | `boolean` |                      |
| `value.getGlobal(name: any)`              | `any`     | Returns global.      |
| `value.setGlobal(name: any, value: any)`  | `void`    | Sets global.         |
| `value.register(name: any, fn: any)`      | `void`    |                      |
| `value.call(funcName: any, ...args: any)` | `any`     |                      |
| `value.getError()`                        | `string`  | Returns error.       |
| `value.loadString(code: any)`             | `boolean` | Loads string.        |
| `value.loadFile(path: any)`               | `boolean` | Loads file.          |

## Notes

* Prefer `import "std:lua"`; the older `import "osl/lua"` spelling remains supported.

## Behavior and limits

Lua execution has a default timeout and a source-size limit. A state remains safe to reuse or close after a timeout. `eval` first tries an expression, then a setup-and-return snippet, and finally a statement. All three forms use the same timeout.


# thread

Use `thread` to run functions in the background, coordinate parallel work, and pass messages.

```osl
import "std:thread"
```

## Example

```osl
import "std:thread"

auto t = thread.new(def() -> ( return 42 ))
log t.wait()
```

## API reference

### `thread`

| Method                                                   | Returns    | Notes                                                                                                                                                                                                                  |
| -------------------------------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `thread.new(fn: any, ...args: any)`                      | `*Thread`  | Creates and starts a new thread.                                                                                                                                                                                       |
| `thread.wait()`                                          | `any`      | Waits for the result. If the task failed, rethrows its error with the original task and spawn locations.                                                                                                               |
| `thread.timeout(ms: number)`                             | `any`      | Waits up to `ms` milliseconds for the result; returns the result if the task finished in time, otherwise `null`. If a completed task failed, rethrows its error. A timeout stops waiting; it does not cancel the task. |
| `thread.isDone()`                                        | `boolean`  |                                                                                                                                                                                                                        |
| `thread.age()`                                           | `number`   | Returns the age of the thread in milliseconds.                                                                                                                                                                         |
| `thread.waitAll(threads: array)`                         | `array`    | Waits for every thread and preserves `null` positions for non-thread entries. If tasks fail, waits for the rest before rethrowing the first error with additional failures attached.                                   |
| `thread.race(threads: array)`                            | `any`      | Waits for the first thread in the array to complete and returns its result. If the winning task failed, rethrows its error.                                                                                            |
| `thread.parallel(items: array, fn: any, limit?: number)` | `array`    | Runs `fn(item, index)` across `items` in parallel with an optional worker concurrency `limit`, returning results in input index order.                                                                                 |
| `thread.channel(capacity?: number)`                      | `*Channel` | Creates a thread-safe message channel with optional buffer capacity.                                                                                                                                                   |

### `*Channel`

| Method                     | Returns   | Notes                                                                                                                                  |
| -------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `channel.send(value: any)` | `boolean` | Sends a value into the channel. Returns `false` if closed, otherwise `true`.                                                           |
| `channel.recv()`           | `any`     | Receives a value from the channel, blocking until an item is available or the channel is closed. Returns `null` when closed and empty. |
| `channel.tryRecv()`        | `any`     | Non-blocking receive. Returns the next item or `null` if empty or closed.                                                              |
| `channel.close()`          | `void`    | Closes the channel.                                                                                                                    |
| `channel.isClosed()`       | `boolean` |                                                                                                                                        |
| `channel.len()`            | `number`  | Returns the number of buffered items currently in the channel.                                                                         |

## Thread safety

OSL automatically makes concurrent programs memory-safe. The compiler identifies named functions that can run as scoped threads, including their transitive calls, and guards concurrent operations with shared read/write statement and collection locks. Dynamic callbacks use a safe whole-program fallback. This means:

* Two threads touching the **same** object, array, `map()`, or `set()` will never crash the program or corrupt memory.
* Every OSL statement is atomic, including an index write, `.append`, `.set`, key read, or scalar read-modify-write such as `count += 1`.

```osl
import "std:thread"

object shared = {}
array threads = []
for t 8 (
    threads.append(thread.new(def(o, id) (
        for i 1000 ( o[id ++ "_" ++ i] = i )
        return 0
    ), shared, t))
)
void thread.waitAll(threads)
log shared.len   // 8000, deterministic
```

Automatic safety does **not** combine multiple statements into one transaction. For example, another thread can change `count` between an `if count < limit` check and the assignment inside its block. Guard multi-statement critical sections with [`osl/sync`](/standard-library/concurrency/sync).

**Performance:** programs that never start a thread skip capture analysis and pay nothing; the locking is compiled out entirely. Named thread functions use a constant-time function-only scope lookup without reading their captured globals during thread creation. Generic, typed, read, and write array operations share the same lock path, and read-only statements can run in parallel. Shared scalar reads in polling conditions and pure conversions use that read boundary as well. Mutable statements use a shared statement boundary so scalar and collection updates remain atomic. Method and package calls use their own value/package synchronization and run outside that boundary, allowing HTTP, WebSocket, and similar callbacks to update captured values without deadlocking their caller.

Automatic collection locking uses a single world lock, so growing or replacing an array's backing storage needs no lock-identity propagation and cyclic values need no recursive registration. Explicit locks from [`osl/sync`](/standard-library/concurrency/sync) are still your responsibility: always release them and use a consistent order when acquiring more than one.

## Notes

* Prefer `import "std:thread"`; the older `import "osl/thread"` spelling remains supported.
* `defer <statement>` runs a statement when the enclosing function returns (like Go's `defer`), which is handy for releasing an `osl/sync` lock taken inside a thread.
* Panicking tasks still complete their handle. `wait` and a completed `timeout` rethrow the failure; `waitAll` waits for every task before rethrowing.


# sync

Use `sync` for named locks, scoped locking, one-time execution, and wait groups across threads.

```osl
import "std:sync"
```

## API reference

### `sync`

| Method                                 | Returns      | Notes                                                                                             |
| -------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------- |
| `sync.lock(name: string)`              | `void`       | Acquires the initialized named lock, waiting if it is held.                                       |
| `sync.tryLock(name: string)`           | `boolean`    | Attempts to acquire the named lock without blocking. Returns `true` if acquired, `false` if held. |
| `sync.unlock(name: string)`            | `void`       | Releases a named lock; missing names are ignored.                                                 |
| `sync.withLock(name: string, fn: any)` | `any`        | Acquires `name`, executes `fn()`, and guarantees lock release on exit. Returns `fn()`'s result.   |
| `sync.once(name: string, fn: any)`     | `any`        | Runs `fn()` at most once across all threads for the given `name`.                                 |
| `sync.waitGroup()`                     | `*WaitGroup` | Creates a new WaitGroup for coordinating multiple asynchronous tasks.                             |

### `*WaitGroup`

| Method                          | Returns | Notes                                               |
| ------------------------------- | ------- | --------------------------------------------------- |
| `waitGroup.add(delta?: number)` | `void`  | Adds `delta` (default 1) to the wait group counter. |
| `waitGroup.done()`              | `void`  | Decrements the wait group counter by 1.             |
| `waitGroup.wait()`              | `void`  | Blocks until the wait group counter reaches 0.      |

## Notes

* Prefer `import "std:sync"`; the older `import "osl/sync"` spelling remains supported.
* Importing `thread` activates concurrent compilation; server-style packages share the pin-trigger path.


# Utilities and data structures

Packages for data structures and utility types.

* [box2d](broken://pages/BDmKrusmA3RbP3aoPxOb) - Box2D-compatible rigid-body worlds, bodies, and fixtures.
* [map](broken://pages/fFdeZ86hUnuvD5t7FFh9) - An ordered key-value map type.
* [set](broken://pages/SPancST3eXLBnGshhpaN) - A set type.
* [option](broken://pages/5dGdZDBYLwmRqPjB7Tag) - Optional values (`some`/`none`).
* [result](broken://pages/HFboZ6GNIKnIHu9fAhwR) - Success/error result values.
* [retry](broken://pages/R7AoWBss6kIQKNyOEUrK) - Bounded retries with exponential backoff and jitter.
* [ptr](broken://pages/gJpHJflBoy9QPKdoijH7) - Low-level pointer operations.


# box2d

`osl/box2d` provides worlds, bodies, and box, circle, or polygon fixtures using a pure-Go engine.

```osl
import "std:box2d"

world = box2d.newWorld({x: 0, y: 10})
ground = world.createBody({type: "static", x: 0, y: 10})
void ground.addBox(20, 1, {friction: 0.4})

player = world.createBody({type: "dynamic", x: 0, y: 0})
void player.addBox(1, 1, {density: 1, friction: 0.4})

loop 120 (
  void world.step(1.0 / 60)
)

log player.position()
```

## Package

* `box2d.vec(x, y)` creates a vector object.
* `box2d.newWorld(gravity)` creates a world. Gravity accepts `{x, y}` or `[x, y]`.

## World

* `createBody(options)` creates a `static`, `kinematic`, or `dynamic` body.
* `step(dt, velocityIterations?, positionIterations?)` advances the simulation. Defaults are 8 and 3.
* `gravity()` and `setGravity(vector)` read or change gravity.
* `bodyCount()` and `contactCount()` report world state.
* `destroyBody(body)` removes one body. `destroy()` invalidates the world.

Body options include `position`, `x`, `y`, `angle`, `velocity`, `angularVelocity`, damping, `allowSleep`, `awake`, `fixedRotation`, `bullet`, `active`, and `gravityScale`.

## Body

* `addBox(width, height, options)`, `addCircle(radius, options)`, and `addPolygon(points, options)` add fixtures.
* Fixture options include `density`, `friction`, `restitution`, and `sensor`.
* `position()`, `angle()`, `linearVelocity()`, `mass()`, `bodyType()`, and `awake()` inspect state.
* `setTransform(position, angle)` and `setLinearVelocity(vector)` change state.
* `applyForce(vector)` and `applyImpulse(vector)` affect dynamic bodies.

Destroyed body handles return safe zero values or `false` instead of accessing invalid engine state.


# map

Use `map` when you need a mutable key-value map object with explicit methods for reading keys and values.

```osl
import "std:map"
```

## Example

```osl
import "std:map"

map users = map()
users.set("ada", 36)
log users.get("ada")
```

## API reference

### `Map` values

| Method                      | Returns  | Notes                                                 |
| --------------------------- | -------- | ----------------------------------------------------- |
| `value.set(k: any, v: any)` | `*Map`   | Sets a value. Keys must be comparable.                |
| `value.get(k: any)`         | `any`    | Reads through the same comparable-key guard.          |
| `value.delete(k: any)`      | `void`   | Deletes through the same comparable-key guard.        |
| `value.size()`              | `number` | Returns the number of stored values.                  |
| `value.clear()`             | `void`   | Clears all stored values.                             |
| `value.getKeys()`           | `K[]`    | Returns keys while preserving the map's key type.     |
| `value.getValues()`         | `V[]`    | Returns values while preserving the map's value type. |

## Notes

* Prefer `import "std:map"`; the older `import "osl/map"` spelling remains supported.

Composite and cyclic keys are checked safely, and maps synchronize concurrent reads and writes.


# set

Use `set` when you need a collection of unique values with membership checks.

```osl
import "std:set"
```

## Example

```osl
import "std:set"

set names = set()
names.add("ada")
log names.contains("ada")
```

## API reference

### `Set` values

| Method                   | Returns   | Notes                                     |
| ------------------------ | --------- | ----------------------------------------- |
| `value.add(v: any)`      | `*Set`    | Adds a comparable value.                  |
| `value.delete(v: any)`   | `error`   | Deletes through the same guard.           |
| `value.contains(v: any)` | `boolean` | Checks membership through the same guard. |
| `value.size()`           | `number`  | Returns the number of stored values.      |
| `value.clear()`          | `void`    | Clears all stored values.                 |
| `value.toArr()`          | `array`   | Converts the value to an array.           |

## Notes

* Prefer `import "std:set"`; the older `import "osl/set"` spelling remains supported.

Composite and cyclic values are compared safely. Sets synchronize concurrent reads and writes.


# option

Use `option` to model a value that may be present (`some`) or absent (`none`) without relying on `null`.

```osl
import "std:option"
```

## Example

```osl
import "std:option"

auto value = some(42)
log value.unwrapOr(0)
```

## API reference

### `Option` values

| Method                   | Returns   | Notes                                                         |
| ------------------------ | --------- | ------------------------------------------------------------- |
| `value.isSome()`         | `boolean` |                                                               |
| `value.isNone()`         | `boolean` |                                                               |
| `value.unwrap()`         | `T`       | Returns the stored value, or fails for `none`.                |
| `value.unwrapOr(def: T)` | `T`       | Returns the contained value or a fallback.                    |
| `value.expect(msg: any)` | `T`       | Uses the same checked accessor with a custom failure message. |

## Notes

* Prefer `import "std:option"`; the older `import "osl/option"` spelling remains supported.

## Behavior and limits

`some(null)` remains a present option; presence is not inferred from whether the stored value is null.


# result

Use `result` to return either a success value or an error value from APIs that should not throw immediately.

```osl
import "std:result"
```

## Example

```osl
import "std:result"

auto ok = result.ok(42)
log ok.unwrapOr(0)
```

## API reference

### `result`

| Method               | Returns   |
| -------------------- | --------- |
| `result.ok(v: any)`  | `*Result` |
| `result.err(e: any)` | `*Result` |

### `Result` values

Methods available on `Result` values returned by this package or constructed by the language. An unparameterized `result` preserves success and error values as `any`. A `result<T>` keeps the success type and defaults its error accessor to `string`; use `result<T, E>` to specify both sides.

| Method                               | Returns   | Notes                                                            |
| ------------------------------------ | --------- | ---------------------------------------------------------------- |
| `value.isOk()`                       | `boolean` |                                                                  |
| `value.isErr()`                      | `boolean` |                                                                  |
| `value.unwrap()`                     | `any`     | Returns the success value, or fails for an error result.         |
| `value.unwrapOr(def: any)`           | `any`     | Returns the contained value or a fallback.                       |
| `value.expect(msg: any)`             | `any`     | Returns the contained value or fails with a custom message.      |
| `value.unwrapErr()`                  | `any`     | Returns the error value, or fails for a success result.          |
| `value.expectErr(msg: any)`          | `any`     | Uses the same error-side accessor with a custom failure message. |
| `value.fromGo(val: any, err: error)` | `*Result` | Creates from go.                                                 |

## Notes

* Prefer `import "std:result"`; the older `import "osl/result"` spelling remains supported.


# retry

Use `retry` around transient operations that can be attempted again safely.

```osl
import "std:retry"
import "std:result"

auto outcome = retry.run(def(int attempt) -> (
  if attempt < 3 ( return result.err("not ready") )
  return result.ok("ready")
), {attempts: 5, delay: 0.1, factor: 2, max_delay: 2, jitter: 0.2})

if outcome.success ( log outcome.value )
```

## API reference

| Method                                             | Returns  | Notes                                                                                                                                            |
| -------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `retry.run(callback: function, options?: object)`  | `object` | Calls the callback until it returns a regular value or `result.ok`, or the attempt limit is reached. Panics and `result.err` values are retried. |
| `retry.backoff(attempt: number, options?: object)` | `number` | Returns the bounded delay in seconds for an attempt.                                                                                             |

Options are `attempts` (default 3, maximum 100), `delay` in seconds (default 0), `factor` (default 2), `max_delay` in seconds (default 30, maximum 300), and `jitter` from 0 to 1. The callback may accept the one-based attempt number; zero-argument callbacks also work.

`run` returns `{success, value, error, attempts, elapsed}`. Attempts stop immediately on an ordinary return value or `result.ok`. The final panic text or `result.err` value is preserved in `error` when all attempts fail.


# ptr

Use `ptr` for low-level pointer-style references when you need explicit mutation through a handle.

```osl
import "std:ptr"
```

## API reference

### `ptr`

| Method                                  | Returns         | Notes                                                                                                 |
| --------------------------------------- | --------------- | ----------------------------------------------------------------------------------------------------- |
| `ptr.pointer(v: any)`                   | `number`        | Returns the value's address.                                                                          |
| `ptr.deref(ptr: any)`                   | `any`           | Returns the pointed-to value, or `null` for a nil pointer.                                            |
| `ptr.ref(v: any)`                       | `*Pointer`      |                                                                                                       |
| `ptr.set(ptr: any, v: any)`             | `boolean`       | Sets a value when assignable, returning false instead of panicking for incompatible reflected values. |
| `ptr.alloc(v: any)`                     | `*Pointer`      |                                                                                                       |
| `ptr.allocTyped(typeName: any, v: any)` | `*TypedPointer` |                                                                                                       |
| `ptr.isNull(ptr: any)`                  | `boolean`       |                                                                                                       |
| `ptr.addressOf(v: any)`                 | `number`        | Returns the same address representation as `ptr.pointer`.                                             |
| `ptr.equalPointers(a: any, b: any)`     | `boolean`       | Reports whether two pointers have the same address.                                                   |
| `ptr.sizeOf(v: any)`                    | `number`        | Returns the reflected size, or the element size for slices and maps.                                  |
| `ptr.alignOf(v: any)`                   | `number`        | Returns the value's memory alignment in bytes.                                                        |
| `ptr.offsetOf(v: any, field: any)`      | `number`        |                                                                                                       |
| `ptr.swap(a: any, b: any)`              | `boolean`       |                                                                                                       |
| `ptr.copy(dst: any, src: any)`          | `boolean`       |                                                                                                       |
| `ptr.sliceData(arr: array)`             | `number`        |                                                                                                       |
| `ptr.stringData(s: string)`             | `number`        |                                                                                                       |
| `ptr.sliceLen(arr: array)`              | `number`        |                                                                                                       |
| `ptr.sliceCap(arr: array)`              | `number`        |                                                                                                       |

### `TypedPointer` values

| Method                   | Returns   | Notes       |
| ------------------------ | --------- | ----------- |
| `value.deref()`          | `any`     |             |
| `value.setValue(v: any)` | `boolean` | Sets value. |

## Notes

* Prefer `import "std:ptr"`; the older `import "osl/ptr"` spelling remains supported.

## Behavior and limits

Nil pointers, empty slices, wrong pointee types, invalid offsets, and overlapping memory operations return failure values. Boolean pointers use OSL's normal boolean conversion.


# Other packages

Additional utility packages.

* [email](broken://pages/wK2qwDTx8HW8n2Oin0XY) - Compose and send email (SMTP).
* [torrent](broken://pages/TKheF8WvEhdp1ZmWo4lb) - Create and parse `.torrent` files.


# email

Use `email` to compose SMTP messages, set recipients and bodies, attach files, preview messages, and send through common SMTP providers.

```osl
import "std:email"
```

## API reference

### `email`

| Method                                                                              | Returns   | Notes                                                                     |
| ----------------------------------------------------------------------------------- | --------- | ------------------------------------------------------------------------- |
| `email.create()`                                                                    | `*Email`  |                                                                           |
| `email.setFrom(addr: any)`                                                          | `boolean` | Sets from.                                                                |
| `email.addTo(recipient: any)`                                                       | `boolean` | Adds a validated, non-duplicate To recipient.                             |
| `email.addToMany(recipients: array)`                                                | `boolean` | Adds to many.                                                             |
| `email.setTo(recipients: any)`                                                      | `boolean` | Replaces To recipients. Invalid or duplicate addresses return `false`.    |
| `email.getTo()`                                                                     | `array`   | Returns to.                                                               |
| `email.addCc(recipient: any)`                                                       | `boolean` | Adds a validated, non-duplicate Cc recipient.                             |
| `email.setCc(recipients: any)`                                                      | `boolean` | Replaces Cc recipients, skipping invalid or duplicate entries.            |
| `email.getCc()`                                                                     | `array`   | Returns cc.                                                               |
| `email.addBcc(recipient: any)`                                                      | `boolean` | Adds a validated, non-duplicate Bcc recipient.                            |
| `email.setBcc(recipients: any)`                                                     | `boolean` | Replaces Bcc recipients, skipping invalid or duplicate entries.           |
| `email.getBcc()`                                                                    | `array`   | Returns bcc.                                                              |
| `email.setSubject(subject: any)`                                                    | `void`    | Sets subject.                                                             |
| `email.getSubject()`                                                                | `string`  | Returns subject.                                                          |
| `email.setBody(body: any)`                                                          | `void`    | Sets body.                                                                |
| `email.getBody()`                                                                   | `string`  | Returns body.                                                             |
| `email.setHTML(html: any)`                                                          | `boolean` | Sets html.                                                                |
| `email.setText(text: any)`                                                          | `void`    | Sets text.                                                                |
| `email.isHTML()`                                                                    | `boolean` |                                                                           |
| `email.attachFile(filePath: any)`                                                   | `boolean` | Adds a readable file unless its basename is already attached.             |
| `email.attachContent(files: object)`                                                | `boolean` | Adds named inline attachments through the same duplicate-name validation. |
| `email.sendWithSmtp(host: any, port: any, username: any, password: any, auth: any)` | `object`  | Sends with SMTP and returns `{success}` or `{success: false, error}`.     |
| `email.sendGmail(username: any, password: any)`                                     | `object`  | Sends gmail.                                                              |
| `email.sendOutlook(username: any, password: any)`                                   | `object`  | Sends outlook.                                                            |
| `email.sendOffice365(username: any, password: any)`                                 | `object`  | Sends office365.                                                          |
| `email.sendLocalhost()`                                                             | `object`  | Sends localhost.                                                          |
| `email.toMap()`                                                                     | `object`  | Converts the value to an object.                                          |
| `email.fromMap(data: object)`                                                       | `*Email`  | Creates from map.                                                         |
| `email.validate()`                                                                  | `boolean` | Validates the current value.                                              |
| `email.getRecipients()`                                                             | `array`   | Returns the combined To, Cc, and Bcc recipients.                          |
| `email.getRecipientCount()`                                                         | `number`  | Returns recipient count.                                                  |
| `email.clear()`                                                                     | `void`    | Clears all stored values.                                                 |
| `email.reset()`                                                                     | `void`    |                                                                           |
| `email.queue()`                                                                     | `object`  |                                                                           |
| `email.preview()`                                                                   | `string`  |                                                                           |
| `email.wrapText(width: any)`                                                        | `boolean` |                                                                           |
| `email.getHeaders()`                                                                | `object`  | Returns headers.                                                          |
| `email.hasRecipient(recipient: string)`                                             | `boolean` | Searches the same combined To, Cc, and Bcc recipient list.                |

## Notes

* Prefer `import "std:email"`; the older `import "osl/email"` spelling remains supported.

## Behavior and limits

Each email value has its own recipient list and can be used safely across threads. Header values containing newlines are rejected. Attachment errors are reported. The Gmail, Outlook, and Office 365 helpers use those providers' standard SMTP submission hosts.


# torrent

Use `torrent` to create, inspect, edit, and serialise torrent metadata.

```osl
import "std:torrent"
```

## API reference

### `torrent`

| Method                                      | Returns    | Notes                   |
| ------------------------------------------- | ---------- | ----------------------- |
| `torrent.createFromDirectory(dirPath: any)` | `*Torrent` | Creates from directory. |
| `torrent.parse(torrentData: any)`           | `*Torrent` | Parses input data.      |

### `Torrent` values

| Method                                                    | Returns    | Notes                                                                                               |
| --------------------------------------------------------- | ---------- | --------------------------------------------------------------------------------------------------- |
| `value.create(name: any, files: array, pieceLength: any)` | `*Torrent` |                                                                                                     |
| `value.save(path: any)`                                   | `boolean`  |                                                                                                     |
| `value.addTracker(tracker: any)`                          | `boolean`  | Adds a tracker unless it is already present.                                                        |
| `value.removeTracker(tracker: any)`                       | `boolean`  | Removes a tracker.                                                                                  |
| `value.getTrackers()`                                     | `array`    | Returns trackers.                                                                                   |
| `value.getInfo()`                                         | `object`   | Returns info.                                                                                       |
| `value.addMetadata(key: any, value: any)`                 | `void`     | Adds metadata.                                                                                      |
| `value.getMetadata(key: any)`                             | `string`   | Returns metadata.                                                                                   |
| `value.getAllMetadata()`                                  | `object`   | Returns all metadata.                                                                               |
| `value.getFileIndex(path: any)`                           | `number`   | Returns file index.                                                                                 |
| `value.getPieceHashes()`                                  | `array`    | Returns piece hashes.                                                                               |
| `value.setPieceHash(index: any, hash: any)`               | `boolean`  | Sets piece hash.                                                                                    |
| `value.validate()`                                        | `boolean`  | Validates the current value.                                                                        |
| `value.download(torrentPath: any, outputPath: any)`       | `boolean`  | Compatibility method that reports whether the current torrent validates; it does not transfer data. |
| `value.seed(torrentPath: any, port: any)`                 | `boolean`  | Compatibility no-op that returns `true`; it does not transfer data.                                 |
| `value.getMagnetLink()`                                   | `string`   | Returns magnet link.                                                                                |
| `value.calculateInfoHash()`                               | `string`   |                                                                                                     |
| `value.getFiles()`                                        | `array`    | Returns files.                                                                                      |
| `value.setFiles(files: array)`                            | `void`     | Sets files.                                                                                         |
| `value.getFileCount()`                                    | `number`   | Returns file count.                                                                                 |
| `value.getTotalSize()`                                    | `number`   | Returns total size.                                                                                 |
| `value.getPieceCount()`                                   | `number`   | Returns piece count.                                                                                |
| `value.generatePeerID()`                                  | `string`   |                                                                                                     |
| `value.getMagnetURI()`                                    | `string`   | Returns magnet uri.                                                                                 |
| `value.exportInfo(path: any)`                             | `boolean`  |                                                                                                     |
| `value.clone()`                                           | `*Torrent` | Returns an independent copy of the metadata and file list.                                          |
| `value.merge(otherTorrent: *Torrent)`                     | `*Torrent` | Returns a copy containing files from both torrents.                                                 |
| `value.strip(metadata: any)`                              | `*Torrent` |                                                                                                     |
| `value.buildTorrent()`                                    | `string`   | Builds the bencoded torrent after validating its file and piece metadata.                           |

## Notes

* Prefer `import "std:torrent"`; the older `import "osl/torrent"` spelling remains supported.

## Behavior and limits

Directory scans avoid symbolic-link loops and tolerate files that disappear during the scan. `validate` checks the piece length, piece count, total size, and binary piece hashes. Parsing uses the package's bencode decoder.


# What changed

OSL began as the scripting language inside originOS. That runtime supplied browser windows, camera access, simulated input, inter-window messages, permissions, global UI state, and its own file model.

The current OSL compiler builds standalone native programs. It does not include the originOS desktop environment, so those APIs do not carry over simply because the language syntax looks similar.

## What changed

| originOS feature                   | Current OSL replacement                                                                                                                                              |
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Browser window commands            | [`std:window`](https://github.com/Mistium/OSL-Docs/tree/main/packages/window.md) or [`std:raylib`](https://github.com/Mistium/OSL-Docs/tree/main/packages/raylib.md) |
| originOS filesystem                | [`std:fs`](https://github.com/Mistium/OSL-Docs/tree/main/packages/fs.md)                                                                                             |
| Browser HTTP and WebSocket methods | [`std:requests`](https://github.com/Mistium/OSL-Docs/tree/main/packages/requests.md) and [`std:ws`](https://github.com/Mistium/OSL-Docs/tree/main/packages/ws.md)    |
| Desktop notifications              | [`std:notify`](https://github.com/Mistium/OSL-Docs/tree/main/packages/notify.md)                                                                                     |
| Embedded sound                     | [`std:sound`](https://github.com/Mistium/OSL-Docs/tree/main/packages/sound.md)                                                                                       |
| Running Lua                        | [`std:lua`](https://github.com/Mistium/OSL-Docs/tree/main/packages/lua.md)                                                                                           |
| originOS save database             | [`std:save`](https://github.com/Mistium/OSL-Docs/tree/main/packages/save.md) or [`std:db`](https://github.com/Mistium/OSL-Docs/tree/main/packages/db.md)             |

Camera access, simulated system input, originOS permissions, and inter-window desktop messages have no direct compiler equivalent.

## What remains here

The pages in this section preserve a small amount of old syntax for people reading historical originOS programs. They are not part of the current language guide and should not be used as a source for new OSL code.


# Legacy debug commands


# Legacy prototype helpers


# Original types

OSL supports six fundamental data types that can be used to represent various kinds of data.

## String

Text values enclosed in double quotation marks.

```osl
// Basic string
name = "Hello"

// String with spaces
message = "Hello, World!"

// String with special characters
path = "C:/Users/Documents"
```

## Boolean

Logical values representing true or false (case-insensitive).

```osl
// Boolean values
isTrue = true
isFalse = false

// In conditions
if true (
    log "This will execute"
)

// Boolean operations
result = true and false  // false
```

## Number

Numeric values including integers and decimals.

```osl
// Integers
count = 42
negative = -10

// Decimals
price = 19.99
temperature = -2.5

// In calculations
total = 10.5 + 20  // 30.5
```

## Array

Ordered collections of values enclosed in square brackets.

```osl
// Simple array
names = ["Alice", "Bob", "Charlie"]

// Mixed type array
data = [1, "two", true, 4.5]

// Nested array
matrix = [[1, 2], [3, 4]]

// Empty array
empty = []
```

## Object

Key-value collections enclosed in curly braces.

```osl
// Simple object
person = {
    name: "John",
    age: 30
}

// Nested object
user = {
    info: {
        id: 123,
        email: "user@example.com"
    },
    settings: {
        theme: "dark"
    }
}

// Object with arrays
data = {
    numbers: [1, 2, 3],
    flags: [true, false]
}
```

## null

Represents an empty or undefined value.

```osl
// Explicit null
value = null

// Checking for null
if value == null (
    log "Value is null"
)

// In objects
user = {
    name: "John",
    middleName: null
}
```

## Type Checking

You can check the type of a value using the `typeof()` function:

```osl
 typeof("Hello")     // "string"
typeof(42)          // "number"
typeof(true)        // "boolean"
typeof([1,2,3])     // "array"
typeof({x:1})       // "object"
typeof(null)        // "null"
```

## Important Notes

* Strings must use double quotes (`"`)
* Booleans are case-insensitive (`True` or `true`)
* Numbers must match the pattern `[0-9.\-]+`
* Arrays can contain mixed types
* Object keys don't need quotes
* `null` represents absence of value
* All types support the `.getType()` method


# Original local scoping

This is the original originOS local-scoping page. It describes the old `local` keyword and `this` scope object, which are not part of the current OSL compiler.

### About Variables

In osl all variables are global scope and to use locally scoped variables you must use the keyword `local`

### Where can I use `local`?

You can local scope inside of any function context

### How does `local` work?

The local keyword creates a key on an json object that's unique to the current context/scope that's accessible using the `this keyword`

```js
local key = "1234"

log this
// returns {"key":"1234"}
```

`this` has a different value depending on the scope of when you access it

### Local variables can be re-assigned without the local keyword

```osl
def myFunc() (
  local v = 0
  v ++
  return v
)

log myFunc()
// returns 1
```

### Example script

```js
def "test_cmd" (
  local hello = "Greetings!"
  log hello
  // logs "Greetings!"
  hello ++= " I'm Mistium"
  log hello
  // logs "Greetings! I'm Mistium"
)

local hello = "hello world"

test_cmd

log hello
// logs "hello world"
```

If a local variable and a global variable exist with the same name, it will access the local variable first.


