Skip to content

Queries and procedures

Read first: Lexicons

This page adds endpoints to your project. A query lexicon becomes a read endpoint your app calls with GET; a procedure lexicon becomes a write endpoint it calls with POST. Both answer at /xrpc/<NSID>.

Add a query lexicon next to your record lexicon, say src/lexicons/xyz/statusphere/listStatuses.json:

{
"lexicon": 1,
"id": "xyz.statusphere.listStatuses",
"defs": {
"main": {
"type": "query",
"parameters": {
"type": "params",
"properties": {
"limit": { "type": "integer" },
"cursor": { "type": "string" },
"did": { "type": "string" },
"uri": { "type": "string" }
}
},
"output": { "encoding": "application/json" }
}
}
}

Then put a script beside it with the same name. The script names the collection it lists:

listStatuses.lua

src › lexicons › xyz › statusphere › listStatuses.lua

local log = require("internal.logging")
local db = require("happyview.db")
function handle(input, ctx)
log.info("handling query", { uri = input.uri })
if input.uri then
local entry = db.get(input.uri)
if not entry then
return { error = "NotFound", message = "no record at " .. input.uri }
end
return { uri = entry.uri, cid = entry.cid, value = entry.record }
end
-- Query params arrive as strings unless the lexicon types them, and a
-- chain step given nil is skipped, so absent filters read as no filter.
return db.records("xyz.statusphere.status")
:did(input.did)
:limit(tonumber(input.limit))
:cursor(input.cursor)
:run()
end

The script needs the happyview.db library: hpy happyview add happyview.db, then restart dev. Save, and it answers:

curl
curl 'http://127.0.0.1:3000/xrpc/xyz.statusphere.listStatuses?limit=5'
  • uri: one record by its URI, answered as { uri, cid, value }, or a NotFound error.
  • Otherwise, a page of xyz.statusphere.status records: limit caps how many, cursor (from the previous page’s response) gets the next page, and did keeps only that account’s records.

The response is {"records": [ … ], "cursor": "…"}. A response has a cursor only when there may be more.

A query with no script answers only ?uri=<at-uri>, and any other call gets a 501; see Method has no script. hpy happyview scaffold, in hpy 1.0.0-alpha.27 and later on the alpha channel, writes this script, and the procedure script below, beside each query or procedure lexicon that has none. The beta channel gets it with its next release.

A query’s script can answer anything, not only records. The project’s com.example.hello has one:

hello.lua

src › lexicons › com › example › hello.lua

function handle(input, ctx)
return { message = "Hello, " .. (input.name or "world") .. "!" }
end
  • input is the query’s parameters. A parameter the lexicon declares as an integer or a boolean arrives as that type, not as a string.
  • ctx says who’s calling (ctx.caller_did, absent for an anonymous call), ctx.env, and more. The script contract lists all of it.
  • What handle returns is the response, as JSON.

A query can be called anonymously, so a script can’t assume a caller. To read records, a script uses the happyview.db library; Plugins and libraries covers libraries and shows another query built on it.

A procedure changes something, and always runs as a signed-in user. A call with no user gets a 401. How your app’s users sign in is in Authentication.

A procedure lexicon declares the body it takes as input. Here is src/lexicons/xyz/statusphere/setStatus.json:

{
"lexicon": 1,
"id": "xyz.statusphere.setStatus",
"defs": {
"main": {
"type": "procedure",
"input": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["status", "createdAt"],
"properties": {
"status": { "type": "string" },
"createdAt": { "type": "string", "format": "datetime" }
}
}
},
"output": { "encoding": "application/json" }
}
}
}

A procedure needs a script too; one with no script answers every call with a 501. The script decides what to write. Writing to the caller’s repository uses the happyview.record library, and the script names the collection it writes:

setStatus.lua

src › lexicons › xyz › statusphere › setStatus.lua

local record = require("happyview.record")
function handle(input, ctx)
return record.create("xyz.statusphere.status", {
status = input.status,
createdAt = input.createdAt,
})
end

The script needs the happyview.record library: hpy happyview add happyview.record, then restart dev. record.create writes to the caller’s PDS and returns the new record’s { uri, cid }. HappyView also indexes the record straight away, so a query sees it before the network echoes it back. A script can only write to the caller’s own repository; writing to repositories you’ve been granted is Linked repos.

To try a procedure before your app can sign users in, call it from a test, which can act as any user: see Testing.

HappyView checks each call against its lexicon before a script runs:

  • A call whose parameters or input break the lexicon, such as a missing required parameter or a value past maxLength, gets 400 {"error":"InvalidRequest"} with a message naming the first problem. The script doesn’t run.
  • A parameter the lexicon declares as an array, sent once, arrives as a one-item array.
  • A missing parameter with a default arrives as that default.
  • knownValues isn’t enforced, and parameters the lexicon doesn’t declare pass through untouched.
  • To return an error, return { error = "NotFound", message = "…" } (in Lua; an object in JavaScript), and the response is that object. If the lexicon declares the name under errors, the status is 400; otherwise it’s 200.
  • A script that fails answers 500 with the error, its type and its line: {"error": "script_error", "errorType": "runtime", "message": "…", "line": 4, …}. A script that runs too long answers 408.
  • A script whose language has no interpreter answers 503.

The script contract has every case.

  • Scripts: how scripts run, and the libraries they reach.
  • Authentication: sign your app’s users in, so they can call procedures.
  • XRPC API: every endpoint an instance serves.