Upsert a record
Create a new record, or update an existing one when the client provides its rkey.
Trigger: xrpc.procedure:<nsid>. Needs: happyview-db, happyview-record.
local time = require("internal.time")local tids = require("internal.tids")local db = require("happyview.db")local record = require("happyview.record")
function handle(input, ctx) local rkey = input.rkey or tids.create() local uri = "at://" .. ctx.caller_did .. "/" .. ctx.collection .. "/" .. rkey local ts = time.to_iso8601(time.now())
local existing = db.get(uri) if existing then local body = existing.record body.status = input.status body.updatedAt = ts return record.put(uri, body) end
return record.create(ctx.collection, { status = input.status, createdAt = ts, updatedAt = ts, }, { rkey = rkey })endJavaScript
Section titled “JavaScript”import time from "internal.time";import tids from "internal.tids";import db from "happyview.db";import record from "happyview.record";
export default async function handle(input, ctx) { const rkey = input.rkey ?? tids.create(); const uri = `at://${ctx.caller_did}/${ctx.collection}/${rkey}`; const ts = time.to_iso8601(time.now());
const existing = await db.get(uri); if (existing) { const body = { ...existing.record, status: input.status, updatedAt: ts }; return record.put(uri, body); }
return record.create( ctx.collection, { status: input.status, createdAt: ts, updatedAt: ts }, { rkey }, );}TypeScript
Section titled “TypeScript”import time from "internal.time";import tids from "internal.tids";import db from "happyview.db";import record from "happyview.record";
interface Input { rkey?: string; status: string;}
interface Context { caller_did: string; collection: string;}
export default async function handle(input: Input, ctx: Context) { const rkey = input.rkey ?? tids.create(); const uri = `at://${ctx.caller_did}/${ctx.collection}/${rkey}`; const ts = time.to_iso8601(time.now());
const existing = await db.get(uri); if (existing) { const body = { ...existing.record, status: input.status, updatedAt: ts }; return record.put(uri, body); }
return record.create( ctx.collection, { status: input.status, createdAt: ts, updatedAt: ts }, { rkey }, );}How it works
Section titled “How it works”- Use the client’s
rkeyif it sent one, otherwise make a new TID withtids.create(). Leavingrkeyout always creates; sending one allows an update. - Build the record’s AT URI from the caller’s DID, the target collection and the rkey, and look it up with
db.get. - If the record exists, change its fields and write it back with
record.put. - If not, create it with
record.create, passing the rkey in the options so the record gets that key. Both writes return{ uri, cid }.
internal.time and internal.tids are synchronous in every language. Only library calls, such as db.get and record.put, return promises in JavaScript and TypeScript.
Writing by a known key like this is also how to keep record scripts idempotent. See Create scripts may run more than once.
# Create: no rkey, so a new TID is usedcurl -X POST http://127.0.0.1:3000/xrpc/xyz.statusphere.setStatus \ -H "X-Client-Key: $CLIENT_KEY" -H "Authorization: DPoP $ACCESS_TOKEN" \ -H "DPoP: $DPOP_PROOF" \ -H "Content-Type: application/json" \ -d '{ "status": "hello" }'
# Update: send the rkey of the existing recordcurl -X POST http://127.0.0.1:3000/xrpc/xyz.statusphere.setStatus \ -H "X-Client-Key: $CLIENT_KEY" -H "Authorization: DPoP $ACCESS_TOKEN" \ -H "DPoP: $DPOP_PROOF" \ -H "Content-Type: application/json" \ -d '{ "rkey": "3abc123", "status": "updated" }'
