Skip to content

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 })
end
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 },
);
}
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 },
);
}
  1. Use the client’s rkey if it sent one, otherwise make a new TID with tids.create(). Leaving rkey out always creates; sending one allows an update.
  2. Build the record’s AT URI from the caller’s DID, the target collection and the rkey, and look it up with db.get.
  3. If the record exists, change its fields and write it back with record.put.
  4. 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.

Terminal window
# Create: no rkey, so a new TID is used
curl -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 record
curl -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" }'