ACTIAS

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
ServiceWhere
apihttp://localhost:3001
consolehttp://localhost:3000
workerhttp://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
ArgumentMeaning
auctionsthe folder to create
basicthe 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 getWhat it is
main.luathe entry point
script.jsonthe 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.

CheckerWhenWhat it sees
language serviceactias-luau is beside the binary, on your PATH, or named by ACTIAS_LUAUresolves require against your bundle; a type crossing a module boundary is checked
luau-analyzea stock luau-analyze is on your PATHresolves against the filesystem; every import is any
noneneither is installedthe 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 check cannot 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.