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>.
A query that lists records
Section titled “A query that lists records”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:
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()endsrc › lexicons › xyz › statusphere › listStatuses.js
import db from "happyview.db";
export default async function handle(input, ctx) { if (input.uri) { const entry = await db.get(input.uri); if (!entry) { return { error: "NotFound", message: `no record at ${input.uri}` }; } return { uri: entry.uri, cid: entry.cid, value: entry.record }; }
// Query params arrive as strings unless the lexicon types them, and a // chain step given undefined is skipped, so absent filters read as no filter. const limit = Number(input.limit); return db.records("xyz.statusphere.status") .did(input.did) .limit(Number.isFinite(limit) ? limit : undefined) .cursor(input.cursor) .run();}src › lexicons › xyz › statusphere › listStatuses.ts
import db from "happyview.db";import type { Params } from "happyview:lexicons/xyz.statusphere.listStatuses";import type { QueryCtx } from "happyview:ctx";
export default async function handle(input: Params, ctx: QueryCtx) { if (input.uri) { const entry = await db.get(input.uri); if (!entry) { return { error: "NotFound", message: `no record at ${input.uri}` }; } return { uri: entry.uri, cid: entry.cid, value: entry.record }; }
return db.records("xyz.statusphere.status") .did(input.did) .limit(input.limit) .cursor(input.cursor) .run();}The script needs the happyview.db library: hpy happyview add happyview.db, then restart dev. Save, and it answers:
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 aNotFounderror.- Otherwise, a page of
xyz.statusphere.statusrecords:limitcaps how many,cursor(from the previous page’s response) gets the next page, anddidkeeps 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 with a script
Section titled “A query with a script”A query’s script can answer anything, not only records. The project’s com.example.hello has one:
src › lexicons › com › example › hello.lua
function handle(input, ctx) return { message = "Hello, " .. (input.name or "world") .. "!" }endsrc › lexicons › com › example › hello.js
export default function handle(input, ctx) { return { message: `Hello, ${input.name ?? "world"}!` };}src › lexicons › com › example › hello.ts
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. A parameter the lexicon declares as an integer or a boolean arrives as that type, not as a string.ctxsays who’s calling (ctx.caller_did, absent for an anonymous call),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 covers libraries and shows another 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.
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:
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, })endsrc › lexicons › xyz › statusphere › setStatus.js
import record from "happyview.record";
export default function handle(input, ctx) { return record.create("xyz.statusphere.status", { status: input.status, createdAt: input.createdAt, });}src › lexicons › xyz › statusphere › setStatus.ts
import record from "happyview.record";import type { ProcedureCtx } from "happyview:ctx";
export default function handle( input: { status: string; createdAt: string }, ctx: ProcedureCtx,) { return record.create("xyz.statusphere.status", { status: input.status, createdAt: input.createdAt, });}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.
Lexicon checks
Section titled “Lexicon checks”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, gets400 {"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
defaultarrives as that default. knownValuesisn’t enforced, and parameters the lexicon doesn’t declare pass through untouched.
Errors
Section titled “Errors”- 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 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. - A script whose language has no interpreter answers
503.
The script contract has every case.
Next steps
Section titled “Next steps”- 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.
© HappyProto

