verdverm.com
09/16/2025, 7:55 PMts
//
// DUAL WRITE PROBLEM
//
await dualWriteTransaction({
ctx,
repo: repoDid,
// (DWP/1) writes to the repo database
actorOps: async (actorTxn: ActorStoreTransactor) => {
// space (self/profile) record
actorTxn.space.insertRecord({
uri: spaceRecord.uri,
space: spaceRecord.uri.space,
collection: spaceRecord.uri.collection,
rkey: spaceRecord.uri.rkey,
cid: spaceRecord.cid,
record: spaceRecord.record,
did: reqDid,
})
// space (owner/parent) record
actorTxn.space.insertRecord({
uri: spaceRelation.uri,
space: spaceRelation.uri.space,
collection: spaceRelation.uri.collection,
rkey: spaceRelation.uri.rkey,
cid: spaceRelation.cid,
record: spaceRelation.record,
did: reqDid,
})
},
// (DWP/2) writes to the authz service
authzOps: async (spicedbClient) => {
await createRelationship(spicedbClient, resource, relation, subject)
},
})
// TODO, (DWP/3) background reconciliation processverdverm.com
09/16/2025, 7:56 PMts
export async function dualWriteTransaction({
ctx,
repo,
actorOps,
authzOps,
}: {
ctx: AppContext,
repo: string,
// writes: async (actorTxn) => Promise<void>[],
actorOps: (actorTxn) => Promise<void>,
authzOps: (spicedbClient) => Promise<void>,
}) {
// DUAL WRITE PROBLEM
// errors could occur anywhere in here
// and we need to unroll or resolve towards eventual consistency
// (depending on where there error occurs?)
//
// We do the writes in this order
// (1) actor db (sqlite)
// (2) spicedb (service)
// Since we also store the relation in a record in the actor db.
// we can later ensure that the relation is created in spicedb as well,
// should the failure happen between the two writes.
try {
await ctx.actorStore.transact(repo, async (actorTxn) => {
// (1) Write our transaction to the database
await actorOps(actorTxn)
})
} catch (err) {
console.error('error during actor txn', err)
throw err
}
try {
// (2) Write our relations to the database
await authzOps(ctx.spicedbClient)
// we don't roll back because we run a reconciliation process
// we should support some form of retry, initiated by the user
// or they do something which starts a reconcilation process for that data
// with ownership in spice, the account owners should be able to see everything
// regardless if an operation fails on some operation under their account
// (i.e. account has god mode over everything under their root space, made at creation time)
// the reconciliation process is also scoped to an account
// so it should be very easy to trigger with an xrpc call (yet to be created)
// additionally, this error handler could notify the reconcilation system
// that this account / record / operation should be looked at
// so that we might shorten the loop in eventual consistency
} catch (err) {
console.error('error during authz txn', err)
throw err
}
}verdverm.com
09/16/2025, 7:58 PMyetitwo
09/16/2025, 8:05 PMyetitwo
09/16/2025, 8:06 PMyetitwo
09/16/2025, 8:07 PMverdverm.com
09/16/2025, 8:23 PMverdverm.com
09/16/2025, 8:23 PMverdverm.com
09/16/2025, 8:27 PMverdverm.com
09/16/2025, 8:27 PMyetitwo
09/16/2025, 9:15 PM