Your first script
Install the CLI, create a project, publish a script, and watch it answer a request.
The tools
cargo build --release -p actias-cli # needs protoc on your PATH
export PATH="$PWD/target/release:$PATH" # the binary is target/release/actias
The download page has the same instructions. A local platform to publish to is one command in the repository root:
docker compose up -d --build
| Service | Where |
|---|---|
| api | http://localhost:3001 |
| console | http://localhost:3000 |
| worker | http://localhost:3002 |
An account and a project
Register at the console, http://localhost:3000/register. A local
compose accepts registrations; a deployment with INVITE_ONLY=true
needs an invite from an admin.
actias login
actias project create auction-house
login asks for the server URL, which is the api origin
(http://localhost:3001, no /api), then your username or email and
password. The login lives in your user config directory under
actias/settings.json; running login again replaces it.
project create prints the project's id and the init line that uses
it. A project owns scripts and everything they declare; kv
namespaces, databases, objects and secrets are scoped to it. Project
names are 6 to 36 characters. actias projects lists the ones you can
reach; the console shows the same set.
A script
actias init auctions basic <project-id>
cd auctions
| Argument | Meaning |
|---|---|
auctions | the folder to create |
basic | the template: basic, router or todo; omitted, the cli asks |
<project-id> | registers the script under the project, so script.json carries its id; omitted, the first publish asks |
| What you get | What it is |
|---|---|
main.lua | the entry point |
script.json | the script's id, its entry point, which files belong to the bundle, and an optional build command run before each publish |
tests/ | tests actias test runs |
definitions/ | the platform's types for your editor; rewritten by every check, publish and dev, so never edited |
.luaurc, .vscode/ | point the editor at those definitions |
main.lua from the basic template:
-- `on` declares an event handler; declarations live at the top level.
on "fetch" (function(request)
return {
body = json.stringify({hello = "world"}),
headers = {["Content-Type"] = "application/json"}
}
end)
Check
actias check
Validated main.lua
Handles fetch
Checked types (language service)
check runs the declaration pass the server runs at publish, then
type-checks every .lua file in the bundle. The printed list is the
script's contract: kv namespaces, handled events, secrets, object
classes, databases, queues, connections, workflows, lifecycle hooks,
streams published and received, and migrations.
The last line names the type checker that ran.
| Checker | When | What it sees |
|---|---|---|
language service | actias-luau is beside the binary, on your PATH, or named by ACTIAS_LUAU | resolves require against your bundle; a type crossing a module boundary is checked |
luau-analyze | a stock luau-analyze is on your PATH | resolves against the filesystem; every import is any |
| none | neither is installed | the type check is skipped, with a warning |
actias-luau is built from the repository's luau-web/ directory;
there is no packaged release of it yet.
Gotchas
- A publish can still be refused for something
checkcannot see: another script in the project already declaring the same class, queue or workflow.
Check in an editor
actias lsp speaks the language server protocol over stdio and
answers with the same checker as check. Diagnostics, hover,
completion and go-to-definition resolve through your bundle, so a
method call lands on the file that defines it.
Attach it to .lua files in a directory holding a script.json. In
Neovim:
vim.api.nvim_create_autocmd("FileType", {
pattern = "lua",
callback = function()
vim.lsp.start({
name = "actias",
cmd = { "actias", "lsp" },
root_dir = vim.fs.root(0, { "script.json" }),
})
end,
})
The server needs actias-luau, found the same three ways check
finds it; without it the server refuses to start and says so.
Publish
actias publish
Uploading 1 of 1 files
Published auctions (a68e112a-…)
revision 2f0c…
preview /_rev/auctions/2f0c…
Only changed files upload. The server derives the contract from your
code again and the revision goes live at <worker-url>/<identifier>/:
curl http://localhost:3002/auctions/
{"hello":"world"}
Run it live while editing
actias dev
dev opens a live session on the worker (--worker-url, default
http://127.0.0.1:3002) and prints its URL,
<worker-url>/_live/<identifier>/<session>/. Every save in the project
directory uploads the bundle again, so the URL always serves the file
on disk. The script needs an id first, so init with a project id, or
publish once.
Something that remembers
Declare a key-value namespace at the top level and use it inside the handler:
local visits = kv "visits"
on "fetch" (function(request)
local count = (visits:get("count") or 0) + 1
visits:set("count", count)
return {
headers = { ["content-type"] = "application/json" },
body = json.stringify({ visits = count }),
}
end)
Publish again and refresh a few times. The counter survives requests, restarts and deploys, and the console's KV page shows it changing.
Related
- How a request runs: the execution model, one concept per chapter.
- The Lua reference: every namespace with signatures and examples.