Skip to content

Commit b234366

Browse files
committed
fix(api): fix Prisma P2028 transaction errors on Neon
- Cache PrismaClient in production (was creating new client per request) - Move heavy include outside task creation transaction to minimize lock duration - Add maxWait/timeout options to all interactive transactions
1 parent 840f6d5 commit b234366

File tree

6 files changed

+15
-12
lines changed

6 files changed

+15
-12
lines changed

apps/api/src/routes/auth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ router.post('/register', async (req: Request, res: Response) => {
128128
});
129129

130130
return newUser;
131-
});
131+
}, { maxWait: 10000, timeout: 15000 });
132132

133133
const token = jwt.sign(
134134
{ userId: user.id, username: user.username },

apps/api/src/routes/projects.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ router.patch('/:id', requireAuth, async (req: AuthRequest, res: Response) => {
234234
},
235235
include: projectInclude,
236236
});
237-
});
237+
}, { maxWait: 10000, timeout: 15000 });
238238

239239
const result = transformProject(project);
240240
broadcast('project:updated', result);

apps/api/src/routes/tasks.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,9 @@ router.post('/', async (req: Request, res: Response) => {
219219
let task: any;
220220

221221
if (resolvedTeamId) {
222-
task = await prisma.$transaction(async (tx) => {
222+
// Create task inside transaction (for atomic issue number increment),
223+
// but keep the include outside to minimize lock duration.
224+
const created = await prisma.$transaction(async (tx) => {
223225
const team = await tx.team.update({
224226
where: { id: resolvedTeamId },
225227
data: { nextIssueNumber: { increment: 1 } },
@@ -228,7 +230,7 @@ router.post('/', async (req: Request, res: Response) => {
228230
const number = team.nextIssueNumber - 1;
229231
const identifier = `${team.key}-${number}`;
230232

231-
const created = await tx.task.create({
233+
return tx.task.create({
232234
data: {
233235
title,
234236
description,
@@ -243,9 +245,13 @@ router.post('/', async (req: Request, res: Response) => {
243245
create: labelIds.map((labelId) => ({ labelId })),
244246
},
245247
},
246-
include: taskInclude,
248+
select: { id: true },
247249
});
248-
return created;
250+
}, { maxWait: 10000, timeout: 15000 });
251+
252+
task = await prisma.task.findUniqueOrThrow({
253+
where: { id: created.id },
254+
include: taskInclude,
249255
});
250256
} else {
251257
task = await prisma.task.create({

apps/api/src/routes/teams.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ router.delete('/:id', optionalAuth, async (req: AuthRequest, res: Response) => {
216216
await tx.projectTeam.deleteMany({ where: { teamId: id } });
217217
await tx.task.updateMany({ where: { teamId: id }, data: { teamId: null } });
218218
await tx.team.delete({ where: { id } });
219-
});
219+
}, { maxWait: 10000, timeout: 15000 });
220220
broadcast('team:deleted', { id });
221221
res.status(204).send();
222222
} catch (error: unknown) {

apps/desktop-ui/next-env.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// <reference types="next" />
22
/// <reference types="next/image-types/global" />
3-
import "./.next/dev/types/routes.d.ts";
3+
import "./.next/types/routes.d.ts";
44

55
// NOTE: This file should not be edited
66
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

packages/db/src/client.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,7 @@ function getPrismaClient(): ExtendedPrismaClient {
6565
if (globalForPrisma.prisma) return globalForPrisma.prisma;
6666

6767
const client = createPrismaClient();
68-
69-
if (process.env.NODE_ENV !== "production") {
70-
globalForPrisma.prisma = client;
71-
}
68+
globalForPrisma.prisma = client;
7269

7370
return client;
7471
}

0 commit comments

Comments
 (0)