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
| Question | Use |
|---|---|
| Small value, looked up by name | kv |
| Search or join across many rows | database |
| State owned by one entity | object |
| Two simultaneous changes must not collide | object |
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.sqlis 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.