ACTIAS

Where data goes

Three storage kinds, and which one fits which question.

Key-value

kv maps keys to values within a namespace.

local cache = kv "cache"

on "fetch" (function(request)
    cache:set("banner", { text = "back at 09:00" })
    return { body = cache:get("banner").text }
end)

Use it for small values read by name: flags, counters, cached summaries, session markers. It has no queries, so anything you store must be findable by key alone.

SQL databases

database is a SQLite database with migrations.

local db = database "catalog" { migrations = "migrations/catalog" }

on "fetch" (function(request)
    db:exec("INSERT INTO listings (id, title, seller) VALUES (?, ?, ?)",
        { uuid.v4(), "vase", "ada" })

    local top = db:read(
        "SELECT category, COUNT(*) AS sold FROM sales \
         JOIN listings ON listings.id = sales.listing_id \
         GROUP BY category ORDER BY sold DESC LIMIT 5")
    return { body = json.stringify(top) }
end)

Use it for questions spanning many rows: joins, aggregates, filtered lists. Nothing else here answers those.

Schema comes from the .sql files in the declared directory, applied at the first call that touches the database. Declare no directory and nothing is applied: see migrations.

Objects

An object is a named entity that handles one call at a time and owns a private database.

local Auction = object "Auction" {
    migrations = "migrations/Auction",

    bid = function(state, user, amount)
        state.sql:exec("INSERT INTO bids VALUES (?, ?, ?)",
            { state.now(), user, amount })
    end,
}

on "fetch" (function(request)
    Auction("lot-42"):bid("ada", 25)
    return { status_code = 204 }
end)

Use it when state belongs to one entity, and when concurrent changes to that entity must not interleave.

An object's database lives in one SQLite file that can grow to a gigabyte (OBJECT_DB_MAX_MB; a write past the budget fails, reads keep working). Its durable copy is kept as content-addressed chunks, so a large object that changes a little costs a little to keep durable: what ships after a write is what the write touched.

Choosing

QuestionUse
Small value, looked up by namekv
Search or join across many rowsdatabase
State owned by one entityobject
Two simultaneous changes must not collideobject

Using both an object and a database is normal. An auction keeps its bid history in its own object and writes a row to a shared sales database when it closes. "What sold this week" spans lots.

All three are durable, scoped to the project, declared in code, and browsable in the console.

Gotchas

  • An object's state.sql is private to that instance. No query spans instances, so "the top ten lots across every auction" needs a shared database.
  • kv remembers the type you wrote. A number comes back a number, a boolean a boolean, a table a table. Nothing is stringified on your behalf.
  • Objects: serialization, alarms, placement.
  • database and kv: every method with signatures.