01 · Before and afterThe pattern
await db.$transaction(async (tx) => {
await tx.$queryRaw`SELECT id FROM Member WHERE id = ${memberId} FOR UPDATE`;
const e = await tx.membership.findUnique({ where: { memberId_planId } });
if (e && !CAN_APPLY.includes(e.status)) throw new Error("not allowed");
if (await tx.planRequest.findFirst({ where: { memberId, status: "pending" } }))
throw new Error("already pending");
await tx.planRequest.create({ data });
});02 · Run it on your appThe check
If you add a “re-apply” or “upgrade” path, did you list which current statuses may use it (and test banned users)?
If the honest answer is no or not sure, this is worth a look before it reaches production.