Documentation
¶
Overview ¶
apikey.go — service-to-service auth helper.
Pattern:
middleware require_api_key {
if (!api_key_auth(request, env("API_KEYS"))) {
return status(401, { error: "invalid api key" })
}
}
group /api {
use require_api_key
get /health { return json({ ok: true }) }
}
Reads `X-API-Key` first, falls back to `Authorization: Bearer <key>`. `allowed_keys` is a comma-separated string of valid keys, typically from env("API_KEYS"). Compare is constant-time so timing leaks can't enumerate keys one byte at a time.
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.
body_validate.go — single-call body validation for POST/PUT handlers.
The pattern handlers used to write:
post /users {
let r = validate(request.body, schema)
if (!r.valid) {
return problem(400, "Validation failed", "", { errors: r.errors, trace_id: request.id })
}
let body = request.body
// ... actual logic
}
collapses to:
post /users {
let r = body_validate(request, schema)
if (!r.ok) { return r.response }
// r.body is the validated body
}
`r.response` is a fully-formed problem+json 400 with the right trace_id from request.id. Same shape as if you'd written it by hand.
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.
config.go — convenience config loader with format auto-detection and env-var interpolation. Common SaaS pattern:
# config.yaml
db:
dsn: ${DATABASE_URL}
pool: 10
stripe:
secret: ${STRIPE_SECRET_KEY}
price: ${STRIPE_PRICE_ID}
# app.mx
let cfg = config.load("./config.yaml")
let db = sql.open(cfg.db.dsn)
Supports .yaml / .yml / .json / .toml — picked by extension. `${NAME}` placeholders expand to env(NAME) before parsing, so a committed config file can carry secret references without the secrets themselves.
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.
debug.go — small `debug.*` namespace for assertions, tracing, and quick inspection. Together with `pp()` and `log.*` this rounds out the dev / panic-driven-development surface so MX programs can short-circuit cleanly when invariants break.
debug.assert(user.subscribed, "user must be subscribed here")
debug.unreachable("expected match arm to handle this")
let result = debug.trace("expensive_query", fn() { ... })
All three throw on failure (so try/catch semantics work the same), rather than calling os.Exit — programs that want hard-stop behavior can wrap them in their own handler.
etag.go — HTTP caching primitives for read-heavy endpoints.
The pattern:
get /users/:id {
let user = sql.first(db, "SELECT * FROM users WHERE id = ?", request.params.id)
let tag = etag(user)
if (request.headers["if-none-match"] == tag) { return not_modified() }
return json(user, { headers: { "ETag": tag, "Cache-Control": "private, max-age=60" } })
}
One round-trip cost on the cold path; zero body bytes on the warm path. Big win for list/detail endpoints that serve the same shape to the same client over and over.
export.go — high-level response helpers for the most common export shapes APIs serve: CSV (for spreadsheets) and NDJSON (for streaming pipelines / log shippers).
Pattern:
get /export/users.csv {
return csv(sql.find(db, "users", {}), { filename: "users.csv" })
}
get /export/events.ndjson {
return ndjson(sql.find(db, "events", { active: true }))
}
`filename` opts triggers `Content-Disposition: attachment` so a browser downloads instead of trying to render.
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.
form.go — application/x-www-form-urlencoded helpers. The HTTP router auto-decodes form bodies into request.body for incoming requests, so mostly these are for outbound calls + tests.
form.parse("a=1&b=2&c=hello%20world")
// -> { a: "1", b: "2", c: "hello world" }
form.encode({ user: "alice", count: 3 })
// -> "count=3&user=alice"
// Building a POST body:
fetch(url, { method: "POST", body: form.encode({ ... }) })
graphql.go — minimal GraphQL handler. Parses a useful subset of the query language (queries, mutations, fields with arguments, nested selection sets, variables, aliases) and dispatches to a user- supplied resolver tree. Skips: fragments, interfaces, unions, directives, introspection, subscriptions. Apollo / urql / Relay clients all work for the common case where you write a backend from scratch.
The schema is implicit in the resolver shape:
graphql.handler({
Query: {
user: fn(args, ctx) {
return sql.query_one(db, "SELECT * FROM users WHERE id = ?", args.id)
},
users: fn(args, ctx) {
return sql.query(db, "SELECT * FROM users LIMIT ?", args.limit ?? 50)
},
},
Mutation: {
create_user: fn(args, ctx) { ... }
},
User: {
posts: fn(parent, args, ctx) {
return sql.query(db, "SELECT * FROM posts WHERE user_id = ?", parent.id)
}
}
})
Routes mount the handler with `route POST /graphql { return graphql.handler(...)(request) }`.
health.go — Kubernetes-flavoured liveness / readiness probes.
route GET /healthz { return health.live() }
route GET /readyz {
return health.ready({
database: fn() { sql.query_one(db, "SELECT 1") != null },
redis: fn() { redis.get(r, "ping") != null }
})
}
The conventions are deliberate: healthz checks "is this process alive enough to keep" — nothing else, returns 200 once the binary is responding to HTTP. readyz checks "is this process ready to serve traffic" — runs every check fn, returns 200 if all pass and 503 with a per-check JSON body otherwise.
http_session.go — stateful HTTP client with a cookie jar. Useful for client SDKs that consume legacy APIs (form login → session cookie → subsequent requests share the auth state). The standard `fetch()` builtin is stateless; `http.session()` returns an object with methods that share a cookie jar and base configuration.
let s = http.session({ base_url: "https://api.example.com" })
s.post("/login", { email: "x", password: "y" })
let user = s.get("/me") // cookies from /login auto-attach
s.close() // optional, clears the jar
ids_objects.go — ID generators (uuid/ulid/nanoid/short) and a small suite of object helpers (pick/omit/merge/deep_merge). All are expression-friendly and copy-rather-than-mutate so chained transformations don't surprise callers.
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.
paginate.go — request → page params, then page params + items → envelope.
Most list endpoints reinvent the same five lines:
let page = num(request.query?.page) || 1
let per_page = math.min(100, num(request.query?.per_page) || 20)
let offset = (page - 1) * per_page
let total = sql.first(db, "SELECT count(*) AS n FROM ...").n
return json({ items: ..., page, per_page, total, total_pages: ceil(...), ... })
paginate() collapses the input parsing, page_response() collapses the envelope. Together they turn a list endpoint into:
get /users {
let p = paginate(request)
let total = sql.first(db, "SELECT count(*) AS n FROM users").n
let items = sql.query(db, "SELECT * FROM users LIMIT ? OFFSET ?", p.limit, p.offset)
return json(page_response(items, p, total))
}
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.
pretty.go — pp(value) pretty-printer. Indented, color-aware, cycle-safe (well, cycles aren't possible in the value model since arrays + objects copy, but we still cap recursion depth).
The REPL uses prettyValue() automatically when displaying results; scripts can call pp() explicitly for debugging output.
problem.go — RFC 7807 application/problem+json responses.
The standard machine-readable error shape:
{ "type": "...", "title": "...", "status": 400, "detail": "...", ... }
Pattern in handlers:
post /users {
let r = validate(request.body, schema)
if (!r.valid) {
return problem(400, "Validation failed", { errors: r.errors })
}
}
get /users/:id {
let u = sql.first(db, "SELECT * FROM users WHERE id = ?", request.params.id)
if (u == null) { return problem(404, "User not found") }
return json(u)
}
Anything beyond status/title/detail goes into the `ext` object and is merged into the top-level response — RFC 7807 §3.2 explicitly allows arbitrary extension members.
proxy.go — server-side reverse-proxy helper. Lets MX route handlers forward an incoming request to an upstream service. Useful for dev workflows where MX is the backend serving the API and a Vite / Next dev server is the frontend at a different port:
get /api/* { return json(handle_api(...)) }
get /* { return proxy("http://localhost:5173", request) }
Streams body bytes through verbatim. Per-hop headers (Connection, Hop-by-Hop) are stripped per RFC 2616 §13.5.1.
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.
s3.go — S3-compatible object storage. Implements AWS Signature V4 directly (no external SDK) so the same builtins work against AWS S3, Cloudflare R2, Backblaze B2, DigitalOcean Spaces, MinIO, Wasabi, and any other S3-compatible store. The API surface is intentionally small — five calls cover every common need:
s3.put(bucket, key, body, opts?) upload s3.get(bucket, key, opts?) download (returns body string) s3.delete(bucket, key, opts?) delete s3.list(bucket, prefix?, opts?) array of keys s3.presign(bucket, key, opts?) presigned GET URL with expiry
All read AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_REGION from the environment by default. opts.endpoint overrides the host for non-AWS providers; opts.region overrides the region for requests outside us-east-1.
search.go — SQLite FTS5 full-text search wrapper. The engine (FTS5) ships in the pure-Go modernc.org/sqlite driver we already depend on, so this namespace is zero-extra-deps. The four calls covered are enough for any "search bar" on top of a SQL table:
search.create(db, "posts_fts", ["title", "body"])
search.index(db, "posts_fts", post.id, post)
let hits = search.query(db, "posts_fts", "lang AND fast", { limit: 20 })
search.delete(db, "posts_fts", post.id)
FTS5 supports BM25 ranking, NEAR proximity search, prefix queries, boolean operators (AND/OR/NOT), and column-scoped queries (`title: lang`). The wrapper passes user queries through unchanged so all of that works out of the box — see the FTS5 docs: https://www.sqlite.org/fts5.html
shell_helpers.go — convenience wrappers around the existing `shell()` builtin. Three patterns:
shell.run(cmd, args?, opts?) -> exit_code (just the exit code) shell.output(cmd, args?, opts?) -> string (stdout, fail on non-zero) shell.bash(script, opts?) -> result (run a multi-line script) shell.which(name) -> string|null (locate on PATH)
All four delegate to the existing builtinShell so they share env / dir / timeout / stdin opts.
snapshot.go — golden-file testing primitive.
test "rendered email looks right" {
let body = render_email(user, plan)
assert_snapshot("welcome_email", body)
}
First run writes the value's pretty repr to __snapshots__/<file>.snap.json under the same directory as the source file, then passes. Subsequent runs read the file back and fail if the rendered value has changed. `mx test -u` (or --update-snapshots) flips the interpreter into update mode, which always overwrites.
Snapshots are committed to source control. They're the test — review changes the same way you'd review a code change.
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.
static.go — single-file static asset helper for routes that have already done their own routing/auth and just want to hand back bytes from disk with a sensible Content-Type.
Pairs naturally with proxy() for SPA setups:
get /* {
if (env("MX_DEV") == "1") { return proxy("http://localhost:5173", request) }
let f = static_file("./web/dist" + request.path)
if (f != null) { return f }
return html(read_file("./web/dist/index.html")) // SPA fallback
}
Returns null when the path is missing or points at a directory so the caller can do its own 404 / fallthrough handling.
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)
upload.go — convenience helpers for the multipart upload story.
`request.files` already gives you parsed file objects with name / size / content_type / content / ext. The friction was the second step: writing them to disk safely. Without save_upload(), users wrote:
let dir = "./uploads" write_file(dir + "/" + uuid() + img.ext, img.content)
— which has to mkdir the parent themselves, and the call is verbose in the hot path of every endpoint that takes uploads. save_upload() collapses that.
vault.go — tiny encrypted secrets store. Lets users keep per-environment secrets out of the binary + out of plaintext .env files. Secrets are AES-256-GCM encrypted at rest with a single master key (read from env), so you can commit the encrypted vault to source control without leaking the values.
$ MX_VAULT_KEY=$(openssl rand -hex 32) mx run app.mx
// app.mx
let stripe = vault.get("stripe_key") // decrypted on read
vault.set / vault.export are the write side: typically you run these once interactively to bootstrap the file, then commit the resulting .vault.json (encrypted, safe to share).
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.
xml.go — minimal XML parser and stringifier. Useful for the legacy APIs SaaS apps still need to integrate with: SOAP-style HTTP, RSS / Atom feeds, sitemaps, podcast feeds, Google Search Console responses, etc.
xml.parse("<root><a>1</a><b>2</b></root>")
// → { tag: "root", attrs: {}, children: [
// { tag: "a", attrs: {}, text: "1", children: [] },
// { tag: "b", attrs: {}, text: "2", children: [] },
// ], text: "" }
xml.stringify({ tag: "rss", attrs: { version: "2.0" }, children: [...] })
// → '<rss version="2.0"><channel>...</channel></rss>'
The shape mirrors what callers actually want: each node carries `tag`, `attrs`, `text` (the immediate text content), and `children`. Mixed-content elements lose ordering between text and children — that's an acceptable trade for the simpler API; users who need full fidelity should drop to `encoding/xml` directly.
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 PrettyDisplay(v Value, colors bool) string
- 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) Benches() []TestInfo
- 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) File() string
- 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) RunBench(idx int) error
- func (i *Interpreter) RunTest(idx int) error
- func (i *Interpreter) SetBytecode(on bool)
- func (i *Interpreter) SetFile(path string)
- func (i *Interpreter) SetPort(p int)
- func (i *Interpreter) SetSnapshotMode(mode string)
- func (i *Interpreter) SnapshotMode() string
- func (i *Interpreter) Tests() []TestInfo
- type MXError
- type Op
- type OrderedMap
- type Response
- type RouteInfo
- type StackFrame
- type TestInfo
- 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 PrettyDisplay ¶ added in v0.78.0
PrettyDisplay returns a colored, indented representation of v suitable for terminal output. Used by the REPL to display results so objects + arrays don't render as one-line JSON. Pass colors=false for plain output (e.g. when piping to a file).
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) Benches ¶ added in v1.32.0
func (i *Interpreter) Benches() []TestInfo
Benches returns the inline `bench "name" { ... }` declarations the loaded program registered, in source order. `mx bench` calls this after Load to discover what to time.
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) File ¶ added in v1.34.0
func (i *Interpreter) File() string
File returns the current source file path. Builtins that need to resolve relative paths (snapshots next to the test file, fixture loaders, etc.) call this to anchor their lookups.
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) RunBench ¶ added in v1.32.0
func (i *Interpreter) RunBench(idx int) error
RunBench evaluates the body of the inline benchmark at the given index once. The CLI's `mx bench` calls this in a tight loop to time it; we don't add timing here because the calibration logic belongs in the runner, not the language.
func (*Interpreter) RunTest ¶ added in v1.29.0
func (i *Interpreter) RunTest(idx int) error
RunTest evaluates the body of the inline test at the given index. Index matches the order returned by Tests(). The body runs in a fresh child scope so `let` bindings inside the test don't leak. Any error (assertion failure, runtime error) propagates back so the caller can format it with file/line context.
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.
func (*Interpreter) SetSnapshotMode ¶ added in v1.34.0
func (i *Interpreter) SetSnapshotMode(mode string)
snapshotMode controls how assert_snapshot resolves a missing or changed snapshot. "" / "compare" (default): fail on miss/diff. "update": overwrite the snapshot from disk and pass.
func (*Interpreter) SnapshotMode ¶ added in v1.34.0
func (i *Interpreter) SnapshotMode() string
func (*Interpreter) Tests ¶ added in v1.29.0
func (i *Interpreter) Tests() []TestInfo
Tests returns the inline `test "name" { ... }` declarations the loaded program registered, in source order. `mx test` calls this after Load to discover what to run.
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) OpAndJump // peek; if falsy: pc = arg (leaves value); else pop + continue OpOrJump // peek; if truthy: pc = arg (leaves value); else pop + continue OpNullishJump // peek; if non-null: pc = arg (leaves value); else pop + continue OpJumpIfNullKeep // peek; if null: pc = arg (leaves null); else continue with value still on stack )
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 TestInfo ¶ added in v1.29.0
TestInfo is the public summary returned by Tests() — name + source line so cmdTest can print nice headers without poking at internals.
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.
`clientSide` is set when the connection was opened via ws.connect: per RFC 6455 §5.3 client frames MUST be masked while server frames MUST NOT, so writeFrame branches on this flag.
func DialWebSocket ¶ added in v0.87.0
DialWebSocket opens an outbound WebSocket connection. Supports both `ws://host:port/path` and `wss://host:port/path`. Returns a WSConn that supports the same WriteText / ReadFrame / WriteClose surface as server-side connections — except writes are masked per the spec.
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.
Source Files
¶
- apikey.go
- auth_passwordless.go
- body_validate.go
- builtins.go
- config.go
- cron.go
- debug.go
- etag.go
- export.go
- extra_crypto_config.go
- form.go
- graphql.go
- health.go
- http_session.go
- ids_objects.go
- interpreter.go
- jobs.go
- metrics.go
- notify.go
- paginate.go
- parse_helper.go
- pretty.go
- problem.go
- proxy.go
- rate_limit.go
- redis.go
- route_trie.go
- s3.go
- search.go
- shell_helpers.go
- snapshot.go
- sql.go
- sql_insert.go
- static.go
- stripe.go
- time_path.go
- upload.go
- vault.go
- vm.go
- websocket.go
- xml.go