Queries and procedures
Read first: getting-started/lexicons
This page adds endpoints to your app. 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>.
A query that lists records
Section titled “A query that lists records”The simplest query needs no script. 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" } } }, "output": { "encoding": "application/json" } } }}Then tell HappyView which collection it lists, in happyview.toml:
[lexicons."xyz.statusphere.listStatuses"]target_collection = "xyz.statusphere.status"Save, and it answers:
curl 'http://127.0.0.1:3000/xrpc/xyz.statusphere.listStatuses?limit=5'Without a script, a query returns records from its target_collection, the most recently indexed first:
limit: how many, 20 by default and 100 at most.cursor: from the previous page’s response, to get the next page. A response has acursoronly when there may be more.did: only that account’s records.uri: one record by its URI, answered as{"record": {…}}, or a 404.
The response is {"records": [ … ], "cursor": "…"}, each record with its uri added.
A query with a script
Section titled “A query with a script”To answer anything else, put a script beside the lexicon with the same name. The project’s com.example.hello already has one:
-- src/lexicons/com/example/hello.luafunction handle(input, ctx) return { message = "Hello, " .. (input.name or "world") .. "!" }endexport default function handle(input, ctx) { return { message: `Hello, ${input.name ?? "world"}!` };}import type { Params, Output } from "happyview:lexicons/com.example.hello";import type { QueryCtx } from "happyview:ctx";
export default function handle(input: Params, ctx: QueryCtx): Output { return { message: `Hello, ${input.name ?? "world"}!` };}inputis the query’s parameters. Integers, numbers and booleans arrive as those types, not strings.ctxsays who’s calling (ctx.caller_did, absent for an anonymous call), thetarget_collection(ctx.collection),ctx.env, and more. The script contract lists all of it.- What
handlereturns 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 adds it and shows a query built on it.
A procedure
Section titled “A procedure”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.
Without a script, a procedure writes its body as a record to the caller’s own PDS, in its target_collection:
[lexicons."xyz.statusphere.setStatus"]target_collection = "xyz.statusphere.status"By default it creates a record, or replaces one when the body has a uri. action = "create", "update" or "delete" under the same table makes it do only that.
With a script, the procedure decides what to write. Writing to the caller’s repository uses the happyview.record library:
-- src/lexicons/xyz/statusphere/setStatus.lualocal record = require("happyview.record")
function handle(input, ctx) return record.create(ctx.collection, { status = input.status, createdAt = input.createdAt, })endimport record from "happyview.record";
export default function handle(input, ctx) { return record.create(ctx.collection, { status: input.status, createdAt: input.createdAt, });}import record from "happyview.record";
export default function handle( input: { status: string; createdAt: string }, ctx: { collection: string },) { return record.create(ctx.collection, { status: input.status, createdAt: input.createdAt, });}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.
Errors
Section titled “Errors”- Returning an error. Return
{ error = "NotFound", message = "…" }(in Lua; an object in JavaScript) and the response is that object. If the lexicon declares the name undererrors, the status is400; otherwise it’s200. - A script that fails answers
500with the error, its type and its line:{"error": "script_error", "errorType": "runtime", "message": "…", "line": 4, …}. A script that runs too long answers408. - No interpreter for the script’s language answers
503.
The script contract has every case.

