zid
zid8mo ago

transactional inserts

For some reason, I had always assumed that if one of the db operations (ex:db.insert) fail within a function, convex enforces transactional behavior across all operations to fail or revert. Upon testing, this does not seem to be the case and we would need to implement our own rollback mechanisms. Given the behavior, it's seems obvious but just incase, I would be grateful for just a confirmation, thank you.
12 Replies
lee
lee8mo ago
convex enforces transactional behavior across all operations to fail or revert
this is true (i.e. you should not need to worry about partial commits or custom rollback logic). can you provide an example of the behavior you're seeing?
zid
zidOP8mo ago
Oh? hmm, for my case, when a new user is created, there are 7 inserts. The first 3 are inserted successfully, while the remaining are not. It seems as though once the error hits, the remaining operations are not run as I only see the error for the first failing operation. For context, the error is a schema validation error: Error: Failed to insert or update a document in table...because it does not match the schema:...
lee
lee8mo ago
can you share your mutation code (as small as possible that repros). note if you are catching the error and returning a result, then the mutation is considered successful and will commit
zid
zidOP8mo ago
Ah, you know what, i'm sorry, the wrapping function is my own function, not a convex fn XD. ultimately, its invoked within a mutation but its a few invocations down the tree
lee
lee8mo ago
yeah that should still be transactional. if you could share the repro that would be helpful
zid
zidOP8mo ago
you got it, give me a moment
// begins here on the client
const initUser = useMutation(api.auth.initUser);

// backend
export const initUser = mutation(async ({ db, auth }) => {
// ...

const userId = await createUser({
db,
identity,
});

// ...
}


export const createUser = async ({ db, identity }) => {
const userId = await db.insert("users", {...

await db.insert("userAccounts", {...

await db.insert("userStats", {...

// Below fails, and error logged
await db.insert("somethingElseOne", {...

// Does not get invoked
await db.insert("somethingElseTwo", {...
}

convex logs: auth.initUser is successful
// begins here on the client
const initUser = useMutation(api.auth.initUser);

// backend
export const initUser = mutation(async ({ db, auth }) => {
// ...

const userId = await createUser({
db,
identity,
});

// ...
}


export const createUser = async ({ db, identity }) => {
const userId = await db.insert("users", {...

await db.insert("userAccounts", {...

await db.insert("userStats", {...

// Below fails, and error logged
await db.insert("somethingElseOne", {...

// Does not get invoked
await db.insert("somethingElseTwo", {...
}

convex logs: auth.initUser is successful
lee
lee8mo ago
if logs say that the mutation is successful, where are you seeing the schema validation error
zid
zidOP8mo ago
Right before
No description
lee
lee8mo ago
is it possible that you're not awaiting some promise? is createUser > error a log line that you're doing? if you're catching the error and not re-throwing it, then the mutation will be considered successful
zid
zidOP8mo ago
Ahhh, you're right --- I am awaiting every promise, but i did not rethrow/propagate the error, that explains it
Abhishek
Abhishek8mo ago
@zid if possible could you pls tell what was causing the issue in the code and how did you solve it ?
zid
zidOP8mo ago
@Abhishek the issue stemmed from my lack of understanding in javascript, specifically around error handling and how that can ultimately relate to convex's transactional guarantee. In my case, i was catching an error but not rethrowing it. This allows the error to propagate up the call stack and ensures that Convex treats the entire operation as a failure, reverting any changes made during the function execution.

Did you find this page helpful?