Documentation
¶
Overview ¶
auth_passwordless.go — magic-link tokens (signed, time-limited) and RFC 6238 TOTP (Google Authenticator–compatible). Together they cover the two most common passwordless auth flows. Magic links are great for "send me a sign-in email", TOTP is the second factor.
builtins.go installs the MX Script standard library into the global environment. Every native function is registered here so they're available in every .mx program without an import.
cron.go — minimal but correct Vixie-cron parser + scheduler used by the cron(spec, fn) builtin. Supports the standard 5-field expression (minute hour day-of-month month day-of-week) with `*`, lists, ranges, and `*/step`. Day-of-month and day-of-week combine with OR when both are restricted, matching cron's documented behavior.
extra_crypto_config.go — bcrypt-grade password hashing (Argon2id, scrypt) plus YAML/TOML parsers. These need external deps that the stdlib doesn't ship:
golang.org/x/crypto/argon2 + scrypt gopkg.in/yaml.v3 github.com/BurntSushi/toml
All three are widely-deployed, well-maintained, and pure Go.
Package interpreter is the heart of MX Script. It walks the parsed AST, evaluates expressions, drives the standard library, and (when route declarations are present) starts an HTTP server that dispatches incoming requests to user-defined route bodies.
jobs.go — durable background job queue backed by SQLite.
Two-line worker:
let q = jobs.create({ db: "./jobs.db", queue: "emails" })
q.enqueue({ to: "alice@example.com", subject: "Hi" })
q.process(2, fn(job) { email.send(...job) }) # 2 workers
Each job carries: id, queue, payload (JSON), status (pending | running | done | failed), attempts, last_error, run_at, created_at. Failed jobs retry with exponential backoff up to `max_attempts` (default 3). After that they stay marked `failed` in the table for inspection.
metrics.go — minimal Prometheus-compatible metrics. Three primitives:
metrics.counter(name, value?, labels?) monotonically increasing metrics.gauge(name, value, labels?) point-in-time number metrics.histogram(name, value, labels?) distribution of observations
Plus a built-in /metrics route handler (`metrics.handler()`) that emits the standard Prometheus text exposition format. Drop-in for any scrape-based monitoring stack — Prometheus, Grafana Cloud, VictoriaMetrics, Datadog Agent's openmetrics check, Honeycomb's OTel collector, etc.
Storage is in-process and lives on a single global registry so any goroutine (route handler, spawn block, cron job) can record without passing a context object around. We use sync.Mutex on writes, atomic for counter increments under a lock so reads see consistent state.
notify.go — outbound notification primitives. Each function is a thin one-shot HTTP call to a well-known service so route handlers don't have to ship copy-pasted curl recipes.
Today we cover the three channels real SaaS apps wire up first:
notify.slack(webhook_url, msg) // incoming-webhook post notify.discord(webhook_url, msg) // Discord webhook notify.email(to, subject, body, opts?) // Resend (recommended) or any RESEND_API_KEY-compatible API
Each returns a small response object: { ok: bool, status: number, error: string|null }. Callers stay declarative — `if (!r.ok) ...` reads better than wrapping every call in try/catch.
parse_helper.go wires the interpreter's import statement to the lexer and parser without forcing the consumer of this package to do it themselves.
rate_limit.go — application-level rate limiting via the `rate_limit(key, max, window_seconds)` builtin. Same token-bucket algorithm as the existing server-level rate_limit config, but keyed by an arbitrary string so callers can rate-limit per user, per tenant, per IP, per endpoint — whatever makes sense for the route.
route POST /signup {
if (!rate_limit("signup:" + request.ip, 5, 60)) {
return status(429, { error: "too many requests" })
}
// ... actual signup
}
Buckets live in-process; for distributed rate limiting back this onto Redis with `redis.incr(...)` instead.
redis.go — thin wrapper around go-redis/redis/v9. Exposed as the `redis` namespace:
let r = redis.connect("redis://localhost:6379/0")
redis.set(r, "user:1", "Jassim", { ttl_seconds: 3600 })
let name = redis.get(r, "user:1")
redis.del(r, "user:1")
redis.publish(r, "events", json_stringify({ kind: "ping" }))
Connection objects are dbHandle-style opaque handles. Pipelining and streams aren't surfaced yet; callers who need them can drop into Go or open an issue.
route_trie.go — segment-based trie for HTTP route lookup. Each path segment is a node; static children are stored in a map (O(1) hit), `:param` children are a single slot per node, and a `*` wildcard child catches the remainder.
Built lazily by Interpreter.dispatch on first request, then reused for every subsequent dispatch. Compared to the previous O(n) linear scan over registeredRoute, lookup is now O(segments) with branching limited to method × path-shape.
sql.go — SQLite via the pure-Go modernc.org/sqlite driver. Exposed through the `sql` namespace:
let db = sql.open("./data.db") # opens / creates a database
sql.exec(db, "CREATE TABLE users (...)")
sql.exec(db, "INSERT ... VALUES (?, ?)", "Jassim", 30)
let rows = sql.query(db, "SELECT * FROM users WHERE name = ?", "Jassim")
loop rows as u { print(u.name) }
let one = sql.query_one(db, "SELECT * FROM users WHERE id = ?", 1)
sql.close(db)
Disabled under js/wasm — the sqlite driver depends on cgo-style libc shims that don't build for the browser. sql_wasm.go provides stubs that return clear errors at runtime.
stripe.go — thin one-shot wrappers around the Stripe API. The four calls covered here (checkout session, customer, portal, subscription) are what 90% of SaaS apps actually need. Anything fancier — disputes, refunds, payouts — should drop straight to fetch() with the `STRIPE_SECRET_KEY` env var; the surface here intentionally stays small.
Combined with v0.56's webhooks.verify_stripe, the full Stripe loop (create checkout → user pays → webhook updates DB) is about 30 lines of MX. See examples/stripe.mx.
time_path.go — time, path, and fs.glob builtins. The shapes match what users coming from Python/JS/Ruby expect:
time.parse("2026-05-03T12:00:00Z") -> unix seconds
time.format(unix, "2006-01-02") -> "2026-05-03"
time.add(unix, "24h") -> unix + 86400
time.diff(a, b) -> seconds (b - a)
time.weekday(unix) -> "Sunday"..."Saturday"
time.iso(unix) -> "2026-05-03T12:00:00Z"
time.unix(iso) -> unix seconds (alias of parse)
path.join("/a", "b", "c.txt") -> "/a/b/c.txt"
path.dir("/a/b/c.txt") -> "/a/b"
path.base("/a/b/c.txt") -> "c.txt"
path.ext("/a/b/c.txt") -> ".txt"
fs.glob("*.mx") -> ["app.mx", "auth.mx"]
fs.glob("**/*.mx") -> recursive (max-depth 16)
vm.go — experimental bytecode VM for the hot paths of MX Script. Compiles a useful subset of the AST (arithmetic, comparison, unary, `let`, `=`, `if`, `while`) into a flat slice of stack-machine instructions, then runs them on a tight loop.
Why a subset? Programs that mix routes / spawn / channels / SSE / tool-calling AI / SQLite still want full interpreter semantics because those paths fan out into Go-side IO. The compiler refuses to lower anything it doesn't fully understand, and the runtime falls back to the tree-walker for those programs. The VM is purely an optimisation for tight numeric / data-shuffling code.
Today this ships behind `mx run --bytecode` and `mx bench --bytecode` (off by default) so users opt in. Once we have full coverage and equivalent semantics for every node, --bytecode flips to default.
websocket.go — minimal RFC 6455 WebSocket server in pure stdlib. No extensions, no permessage-deflate, no fragmented messages over 16 MB. Good enough for chat / live updates / collaborative editors. If you need the heavyweight features, drop in gorilla/websocket and call Interpreter.Handler() to keep the rest of the framework.
Index ¶
- Variables
- func DisplayValue(v Value) string
- func IsBuiltin(name string) bool
- func MagicLinkCreate(email, secret string, minutes int) string
- func MagicLinkVerify(token, secret string) (string, error)
- func ParseSource(src string) (*parser.Program, error)
- func TOTPGenerate(secret string) (string, error)
- func TOTPProvisioningURI(account, secret, issuer string) string
- func TOTPVerify(code, secret string, drift int) (bool, error)
- type Channel
- type Compiled
- type Coverage
- type CronSchedule
- type Env
- type Function
- type Instr
- type Interpreter
- func (i *Interpreter) BytecodeEnabled() bool
- func (i *Interpreter) CallByName(name string, args []Value) (Value, error)
- func (i *Interpreter) Coverage() *Coverage
- func (i *Interpreter) EnableCoverage() *Coverage
- func (i *Interpreter) Exec(prog *parser.Program) (Value, error)
- func (i *Interpreter) Globals() *Env
- func (i *Interpreter) Handler() http.Handler
- func (i *Interpreter) HasRoutes() bool
- func (i *Interpreter) Load(prog *parser.Program) error
- func (i *Interpreter) RouteSummary() []RouteInfo
- func (i *Interpreter) Run(prog *parser.Program) error
- func (i *Interpreter) SetBytecode(on bool)
- func (i *Interpreter) SetFile(path string)
- func (i *Interpreter) SetPort(p int)
- type MXError
- type Op
- type OrderedMap
- type Response
- type RouteInfo
- type StackFrame
- type Value
- func ArrayValue(a []Value) Value
- func BoolValue(b bool) Value
- func ChannelValue(c *Channel) Value
- func FunctionValue(f *Function) Value
- func HandleValue(h any) Value
- func NullValue() Value
- func NumberValue(n float64) Value
- func ObjectValue(o *OrderedMap) Value
- func ResponseValue(r *Response) Value
- func StringValue(s string) Value
- type ValueKind
- type WSConn
Constants ¶
This section is empty.
Variables ¶
var PackageResolver func(importPath string) string
PackageResolver lets the host (the `mx` CLI) plug in a function that turns a package-style import path like `github.com/foo/bar` into an on-disk file. Returning "" means "not a package, fall through to relative-path resolution". Wired up in main.go so the interpreter package itself doesn't need to depend on pkg.
Functions ¶
func DisplayValue ¶ added in v0.2.0
DisplayValue formats a value for human-readable output, used by the REPL.
func MagicLinkCreate ¶ added in v0.58.0
MagicLinkCreate builds a signed token for an email that expires in `minutes` minutes. Stateless: pass to user, they click, you verify.
func MagicLinkVerify ¶ added in v0.58.0
MagicLinkVerify returns the email if `token` is a well-formed, untampered, unexpired link signed with `secret`. Otherwise empty.
func ParseSource ¶
ParseSource lexes and parses a string of MX Script source code.
func TOTPGenerate ¶ added in v0.58.0
TOTPGenerate returns the current 6-digit TOTP code for `secret`, which must be a base32-encoded string (with or without padding).
func TOTPProvisioningURI ¶ added in v0.58.0
TOTPProvisioningURI returns the otpauth:// URI suitable for encoding in a QR code so authenticator apps can scan and add the account.
otpauth://totp/MX%20Script:user@host?secret=BASE32&issuer=MX%20Script
func TOTPVerify ¶ added in v0.58.0
TOTPVerify accepts the user-supplied `code` if it matches the current counter, or any counter within ±drift slots (each slot is 30 seconds). Drift defaults to 1 (90-second total window) so a slow user / clock skew doesn't lock them out.
Types ¶
type Channel ¶ added in v0.16.0
type Channel struct {
C chan Value
// contains filtered or unexported fields
}
Channel wraps a Go channel of MX values. Allocated by chan(); operated on with send(ch, v), recv(ch), close_chan(ch).
type Compiled ¶ added in v0.52.0
type Compiled struct {
Code []Instr
Consts []Value
VarNames []string
// contains filtered or unexported fields
}
Compiled is the output of the bytecode compiler. It owns the instruction stream plus the constant pool and the variable name table the instructions index into.
func CompileBlock ¶ added in v0.53.0
CompileBlock lowers a list of statements. Used for the body of `if`, `while`, and (eventually) functions. Returns (nil, false) if any statement uses something the compiler doesn't understand yet.
func CompileExpr ¶ added in v0.52.0
CompileExpr lowers a single expression node into a self-contained program that pushes the value and returns. Used for ad-hoc expression statements at the top level.
func (*Compiled) Run ¶ added in v0.52.0
func (c *Compiled) Run(interp *Interpreter, env *Env) (Value, error)
Run executes the compiled program against the given environment. `interp` is needed for OpCall (function dispatch); pass nil for programs that don't include calls (the runtime errors clearly if a call is hit without an interpreter).
Variables are read from / written to the env via name lookup, so the VM shares state with the tree-walker for free.
type Coverage ¶ added in v0.31.0
type Coverage struct {
// contains filtered or unexported fields
}
Coverage tracks which source lines were executed during a run. Filled in by the interpreter when EnableCoverage is called and queried by the CLI's `mx test --cover` reporter.
func (*Coverage) ExecutedLines ¶ added in v0.31.0
ExecutedLines returns a sorted snapshot of the line numbers that ran.
type CronSchedule ¶ added in v0.57.0
type CronSchedule struct {
Minute uint64 // bits 0..59
Hour uint64 // bits 0..23
Dom uint64 // bits 1..31 (bit 0 unused)
Month uint64 // bits 1..12 (bit 0 unused)
Dow uint64 // bits 0..6 (Sunday = 0)
// contains filtered or unexported fields
}
CronSchedule is a parsed cron expression. Each field is a 64-bit bitmask of allowed values: bit `i` set means value `i` is in the schedule. We use bitmasks so nextFiring's match check is a single AND operation per field per minute.
func ParseCron ¶ added in v0.57.0
func ParseCron(spec string) (*CronSchedule, error)
ParseCron parses a 5-field cron expression and returns a schedule.
"0 9 * * 1-5" every weekday at 09:00 "*/5 * * * *" every 5 minutes "0 0,12 * * *" midnight and noon "15 14 1 * *" 14:15 on the 1st of each month
func (*CronSchedule) Match ¶ added in v0.57.0
func (s *CronSchedule) Match(t time.Time) bool
Match reports whether the schedule fires at minute `t`. Seconds are ignored; callers should align `t` to the minute before checking.
func (*CronSchedule) Next ¶ added in v0.57.0
func (s *CronSchedule) Next(from time.Time) time.Time
Next returns the earliest time at or after `from` (rounded to the next minute) at which the schedule fires. We bound the search at four years to handle pathological specs like "* * 29 2 *" that only fire on a leap-year February 29.
type Env ¶
type Env struct {
// contains filtered or unexported fields
}
Env is the lexical scope. RWMutex protects the vars map so spawned goroutines can read/write the closure safely. (Programs that share MUTABLE state across spawns still race on the values themselves — use channels for coordination.)
func (*Env) Assign ¶
Assign walks up parents until the variable is found, then replaces it. If not found, defines it in the current scope.
type Interpreter ¶
type Interpreter struct {
// Out / Err are the destinations for print/println/write and eprint
// respectively. Default to os.Stdout / os.Stderr; embedders (notably
// the eval() builtin and the playground) can swap in a buffer to
// capture output.
Out io.Writer
Err io.Writer
// contains filtered or unexported fields
}
func (*Interpreter) BytecodeEnabled ¶ added in v0.52.0
func (i *Interpreter) BytecodeEnabled() bool
BytecodeEnabled reports whether the VM is on. Used by the bench reporter so users can see which engine ran the numbers.
func (*Interpreter) CallByName ¶ added in v0.6.0
func (i *Interpreter) CallByName(name string, args []Value) (Value, error)
CallByName invokes a user-defined function in the global scope by name. Used by the test runner to call discovered `test_*` functions.
func (*Interpreter) Coverage ¶ added in v0.31.0
func (i *Interpreter) Coverage() *Coverage
Coverage returns the active coverage tracker (nil if disabled).
func (*Interpreter) EnableCoverage ¶ added in v0.31.0
func (i *Interpreter) EnableCoverage() *Coverage
EnableCoverage turns on per-statement line tracking. Called by `mx test --cover`; off by default for normal runs (zero overhead).
func (*Interpreter) Exec ¶ added in v0.2.0
func (i *Interpreter) Exec(prog *parser.Program) (Value, error)
Exec runs every statement in the program against the global scope and returns the value of the last expression statement (if any). Unlike Run, it does NOT start an HTTP server even if the program defined routes. This is intended for the REPL, where we want immediate feedback.
func (*Interpreter) Globals ¶ added in v0.2.0
func (i *Interpreter) Globals() *Env
Globals returns the interpreter's top-level environment. It's exposed so embedders (notably the REPL) can evaluate statements that read or write the same scope across multiple calls.
func (*Interpreter) Handler ¶ added in v0.13.0
func (i *Interpreter) Handler() http.Handler
Handler returns the fully-wrapped HTTP handler for the loaded program: the route mux composed with the configured CORS, logging, and request body size middleware.
Call Load before Handler so the program's routes, middleware, and server config are registered. Handler is safe to call multiple times; each call returns a freshly-composed handler chain over the same underlying state.
This is the entry point for embedders (e.g. the Vercel adapter) that want to mount an MX Script app inside their own HTTP server.
func (*Interpreter) HasRoutes ¶ added in v0.13.0
func (i *Interpreter) HasRoutes() bool
HasRoutes reports whether the loaded program defined any HTTP routes or static mounts. Useful for embedders deciding whether to start a server.
func (*Interpreter) Load ¶ added in v0.13.0
func (i *Interpreter) Load(prog *parser.Program) error
Load evaluates every top-level statement in the program against the global scope. It registers any `server { ... }` config, route, middleware and `static` mount, but does NOT start an HTTP server.
Embedders that want to mount the resulting routes in their own server (for example, the Vercel adapter generated by `mx build --vercel`) should call Load followed by Handler.
func (*Interpreter) RouteSummary ¶ added in v0.57.0
func (i *Interpreter) RouteSummary() []RouteInfo
RouteSummary returns one entry per registered route, in registration order. Static mounts and middleware declarations are not included — only `route VERB /path { ... }` and shorthand `get /x { ... }`.
func (*Interpreter) Run ¶
func (i *Interpreter) Run(prog *parser.Program) error
Run executes a parsed program. If the program declared any routes, it boots an HTTP server and blocks; otherwise it returns once the top-level statements have all been evaluated.
func (*Interpreter) SetBytecode ¶ added in v0.52.0
func (i *Interpreter) SetBytecode(on bool)
SetBytecode toggles the experimental stack VM. When on, expression statements that compile cleanly are lowered to bytecode and run on the VM; nodes the compiler doesn't understand fall back to the tree-walker transparently. Off by default.
func (*Interpreter) SetFile ¶
func (i *Interpreter) SetFile(path string)
SetFile records the source file path for error messages.
func (*Interpreter) SetPort ¶
func (i *Interpreter) SetPort(p int)
SetPort marks the CLI-provided port. It overrides any port set by the program's `server { port: ... }` block so `mx run --port 3000` always wins.
type MXError ¶
type MXError struct {
Message string
Line int
Col int
File string
Stack []StackFrame
}
MXError is a structured runtime error with file/line context and the active call stack at the moment of failure.
type Op ¶ added in v0.52.0
type Op uint8
const ( OpConst Op = iota // push consts[arg] OpLoadVar // push env.Get(varNames[arg]) OpStoreVar // env.Set(varNames[arg], pop()) — for `let` OpAssignVar // env.Assign(varNames[arg], pop()) — for `=` OpAdd // a + b (numeric or string concat) OpSub // a - b OpMul // a * b OpDiv // a / b OpMod // a % b OpEq // a == b OpNeq // a != b OpLt // a < b OpGt // a > b OpLte // a <= b OpGte // a >= b OpNot // !a OpNeg // -a OpJump // pc = arg (absolute) OpJumpIfFalse // pop; if !truthy: pc = arg OpPop // discard top OpReturn // halt; return top of stack (or null) OpCall // pop arg values + callee; push call result OpGetField // pop obj; push obj.<consts[arg].String> OpMakeArray // pop arg values; push as KindArray OpMakeObject // pop arg*2 values (key, value pairs); push as KindObject OpGetIndex // pop index; pop target; push target[index] OpLength // pop value; push its length (array len, string len, object key count) )
type OrderedMap ¶
OrderedMap preserves insertion order of object keys for predictable JSON output.
func NewOrderedMap ¶
func NewOrderedMap() *OrderedMap
func (*OrderedMap) Set ¶
func (o *OrderedMap) Set(k string, v Value)
type RouteInfo ¶ added in v0.57.0
RouteInfo is a public summary of one route — the shape returned by the `mx routes` CLI and any embedder that wants to introspect routes without poking at unexported fields.
type StackFrame ¶ added in v0.8.0
StackFrame describes one active call when an error fired.
type Value ¶
type Value struct {
Kind ValueKind
Bool bool
Number float64
String string
Array []Value
Object *OrderedMap
Function *Function
Response *Response
Channel *Channel
// Handle is a generic opaque pointer for resources that don't fit
// the standard value shapes — DB connections, future file handles,
// etc. Treated as a black box by the interpreter.
Handle any
}
func ArrayValue ¶
func ChannelValue ¶ added in v0.16.0
func FunctionValue ¶
func HandleValue ¶ added in v0.22.0
func NumberValue ¶
func ObjectValue ¶
func ObjectValue(o *OrderedMap) Value
func ResponseValue ¶
func StringValue ¶
type WSConn ¶ added in v0.21.0
type WSConn struct {
// contains filtered or unexported fields
}
WSConn is the per-connection handle exposed to .mx code. Methods are safe for concurrent use; the underlying conn is guarded by writeMu.
func (*WSConn) ReadMessage ¶ added in v0.21.0
ReadMessage reads one application-level message from the peer. It transparently handles continuation frames, ping/pong, and close frames. Returns the raw payload as a string and a boolean indicating whether the message is text (true) or binary (false). On normal close it returns io.EOF; on protocol error it returns a descriptive error.
func (*WSConn) WriteBinary ¶ added in v0.21.0
WriteBinary sends a binary frame.
func (*WSConn) WriteClose ¶ added in v0.21.0
WriteClose sends a close frame with the given code and reason and then closes the underlying TCP connection. Subsequent calls are no-ops.