Skip to content

Cascading delete

Delete a record and all related records across collections.

Trigger: xrpc.procedure:<nsid>. Needs: happyview-db, happyview-record.

local db = require("happyview.db")
local record = require("happyview.record")
function handle(input, ctx)
if not input.uri then
return { error = "UriRequired", message = "uri is required" }
end
local row = db.get(input.uri)
if not row then
return { error = "RecordNotFound", message = "no record at that uri" }
end
if row.did ~= ctx.caller_did then
return { error = "NotOwner", message = "you can only delete your own records" }
end
-- Find the caller's comments that reference this URI
local comments = db.records("xyz.statusphere.comment")
:where("postUri", "=", input.uri)
:did(ctx.caller_did)
:limit(100)
:run()
local to_delete = { input.uri }
for _, comment in ipairs(comments.records) do
to_delete[#to_delete + 1] = comment.uri
end
for _, uri in ipairs(to_delete) do
record.delete(uri)
end
return { deleted = #to_delete }
end
  1. Check the primary record exists with db.get and belongs to the caller. Return a structured error if it doesn’t. Each error name (UriRequired, RecordNotFound, NotOwner) must be declared under defs.main.errors in the lexicon, as in "errors": [{ "name": "NotOwner" }], for the response to be a 400; an undeclared one is sent as a 200.
  2. Query for related records: comments by the same user whose postUri field is the primary record’s URI. where filters on a field of the stored body, so the match happens in the database rather than in the script.
  3. Delete everything with record.delete. Each call removes the record from the user’s PDS and the local index.

The DPoP proof is signed per request; an app built on the HappyView SDK gets this from its session.

const response = await fetch("http://127.0.0.1:3000/xrpc/xyz.statusphere.deletePost", {
method: "POST",
headers: {
"X-Client-Key": CLIENT_KEY,
Authorization: `DPoP ${ACCESS_TOKEN}`,
DPoP: DPOP_PROOF,
"Content-Type": "application/json",
},
body: JSON.stringify({
uri: "at://did:plc:abc/xyz.statusphere.post/abc123",
}),
});
const data = await response.json();
{
"deleted": 4
}

Use a cascading delete when your data model has parent-child relationships across collections. For example, deleting a post should also clean up its comments, reactions or metadata records.

This only deletes records in the caller’s own repo, because a record write can’t reach another account’s repo. If the related records could have more than 100 matches, paginate through all of them before deleting.