Skip to content

Commit f7d21b2

Browse files
chore: migrate Prisma to v7
1 parent cfee1fa commit f7d21b2

9 files changed

Lines changed: 611 additions & 99 deletions

File tree

app/external/src/database/database-client.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
import {Injectable, OnModuleInit} from "@nestjs/common"
22
import {PrismaClient} from "@prisma/client"
33
import {ConfigProvider} from "../config"
4+
import {PrismaPg} from "@prisma/adapter-pg"
45

56
@Injectable()
67
export class DatabaseClient extends PrismaClient implements OnModuleInit {
78
constructor(readonly config: ConfigProvider) {
89
super({
9-
datasources: {
10-
db: {
11-
url: config.dbConnectionUrl
12-
}
13-
}
10+
adapter: new PrismaPg({
11+
connectionString: config.dbConnectionUrl
12+
})
1413
})
1514
}
1615

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,56 @@
11
import {Prisma} from "@prisma/client"
2+
import {DriverAdapterError, isDriverAdapterError} from "@prisma/driver-adapter-utils"
3+
4+
// Reference for error structure https://github.com/prisma/prisma/blob/7.2.0/packages/driver-adapter-utils/src/types.ts
5+
//
6+
// The cause of the DriverAdapterError is a discriminated union of types that can be narrowed down
7+
// to extract the specific error details.
8+
9+
/**
10+
* Helper to safely extract driver-specific details from the adapter error.
11+
*/
12+
function getDriverAdapterError(error: Prisma.PrismaClientKnownRequestError): DriverAdapterError | undefined {
13+
const adapterError = error.meta?.driverAdapterError
14+
if (adapterError && isDriverAdapterError(adapterError)) return adapterError
15+
return undefined
16+
}
217

318
export function isPrismaUniqueConstraintError(error: unknown, fields: string[]): boolean {
4-
const uniqueFields = new Set(fields)
19+
if (!(error instanceof Prisma.PrismaClientKnownRequestError) || error.code !== "P2002") return false
20+
21+
const adapterError = getDriverAdapterError(error)
22+
if (!adapterError) return false
523

6-
if (uniqueFields.size !== fields.length) throw new Error("Fields array contains duplicates")
7-
if (!(error instanceof Prisma.PrismaClientKnownRequestError)) return false
8-
if (error.code !== "P2002" || !Array.isArray(error.meta?.target)) return false
24+
const cause = adapterError.cause
25+
if (cause.kind !== "UniqueConstraintViolation") return false
926

10-
const violatedFields = error.meta.target as string[]
27+
const violatedFields = cause.constraint && "fields" in cause.constraint ? cause.constraint.fields : []
1128
return fields.length === violatedFields.length && fields.every(field => violatedFields.includes(field))
1229
}
1330

14-
export function isPrismaForeignKeyConstraintError(error: unknown, constraint: string): boolean {
15-
if (!(error instanceof Prisma.PrismaClientKnownRequestError)) return false
16-
if (error.code !== "P2003" || !error.meta?.constraint) return false
31+
export function isPrismaForeignKeyConstraintError(error: unknown, constraintName: string): boolean {
32+
if (!(error instanceof Prisma.PrismaClientKnownRequestError) || error.code !== "P2003") return false
1733

18-
const violatedField = error.meta.constraint as string
19-
return constraint === violatedField
20-
}
34+
const adapterError = getDriverAdapterError(error)
35+
if (!adapterError) return false
2136

22-
export function isPrismaRecordNotFoundError(error: unknown, modelName: Prisma.ModelName): boolean {
23-
if (!(error instanceof Prisma.PrismaClientKnownRequestError)) return false
24-
if (error.code !== "P2025" || !error.meta?.modelName) return false
37+
const cause = adapterError.cause
38+
if (cause.kind !== "ForeignKeyConstraintViolation") return false
2539

26-
const violatedModel = error.meta.modelName as string
27-
return modelName === violatedModel
40+
return cause.constraint !== undefined && "index" in cause.constraint && cause.constraint.index === constraintName
2841
}
2942

30-
export function isPrismaCheckConstraintError(error: unknown, constraint: string): boolean {
31-
if (!(error instanceof Prisma.PrismaClientKnownRequestError)) return false
32-
if (error.code !== "P2004" || !error.meta?.constraint) return false
43+
export function isPrismaRecordNotFoundError(error: unknown, modelName: Prisma.ModelName): boolean {
44+
if (!(error instanceof Prisma.PrismaClientKnownRequestError) || error.code !== "P2025") return false
45+
46+
/**
47+
* P2025 "Record not found" is slightly different. Usually, the DB doesn't throw
48+
* an error for a missing record (it just returns 0 rows), so Prisma's engine
49+
* generates this error.
50+
*
51+
* Based on your captured debug log, 'modelName' is at the root of 'meta'.
52+
*/
53+
const violatedModel = error.meta?.modelName as string | undefined
3354

34-
const violatedConstraint = error.meta.constraint as string
35-
return constraint === violatedConstraint
55+
return modelName === violatedModel
3656
}

app/external/src/database/vote.repository.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {TaskEither} from "fp-ts/TaskEither"
88
import {none, Option, some} from "fp-ts/lib/Option"
99
import {pipe} from "fp-ts/lib/function"
1010
import {DatabaseClient} from "./database-client"
11-
import {isPrismaForeignKeyConstraintError, isPrismaCheckConstraintError} from "./errors"
11+
import {isPrismaForeignKeyConstraintError} from "./errors"
1212
import {traverseArray} from "fp-ts/lib/Either"
1313

1414
@Injectable()
@@ -50,13 +50,7 @@ export class VoteDbRepository implements VoteRepository {
5050
if (isPrismaForeignKeyConstraintError(error, "fk_votes_workflow")) return "workflow_not_found"
5151
if (isPrismaForeignKeyConstraintError(error, "fk_votes_user")) return "voter_not_found"
5252
if (isPrismaForeignKeyConstraintError(error, "fk_votes_agent")) return "voter_not_found"
53-
if (isPrismaCheckConstraintError(error, "chk_votes_single_voter")) {
54-
Logger.error(
55-
`Voter constraint violation for workflow ${vote.workflowId} and voter ${vote.voter.entityId}`,
56-
error
57-
)
58-
return "unknown_error"
59-
}
53+
6054
Logger.error(
6155
`Error saving vote for workflow ${vote.workflowId} and voter ${getNormalizedEntityId(vote.voter)}`,
6256
error

app/test/database.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,19 @@
11
import {PrismaClient} from "@prisma/client"
22
import {randomUUID} from "crypto"
3+
import {PrismaPg} from "@prisma/adapter-pg"
34
// eslint-disable-next-line node/no-unpublished-import
45
import Redis from "ioredis"
56

67
/** Create a duplicated database using the reference database as template
78
* @returns the connection string to the new database
89
*/
910
export async function prepareDatabase(): Promise<string> {
10-
const referenceDb = process.env.DATABASE_URL
11-
// Create a client connected to the reference database
12-
const prismaClient = new PrismaClient({
13-
datasources: {
14-
db: {
15-
url: referenceDb
16-
}
17-
}
11+
const adapter = new PrismaPg({
12+
connectionString: process.env.DATABASE_URL
1813
})
1914

15+
const prismaClient = new PrismaClient({adapter})
16+
2017
// Generate a unique database name to isolate test runs
2118
const databaseName = `integration_test_${randomUUID().replace(/-/g, "")}`
2219

package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"liquibase:update:test": "docker run --network host --rm -v ./db-migrations:/liquibase/changelog liquibase/liquibase:4.31.1 --defaultsFile=/liquibase/changelog/integration-test.liquibase.docker.properties update",
2121
"lint": "yarn eslint --cache --fix .",
2222
"format:prettier": "yarn prettier --config .prettierrc . --write",
23-
"prisma:pull": "yarn prisma db pull && prisma-case-format --file prisma/schema.prisma -p --table-case pascal,singular --field-case camel",
23+
"prisma:pull": "yarn dotenv -e .env.local -- yarn prisma db pull && prisma-case-format --file prisma/schema.prisma -p --table-case pascal,singular --field-case camel",
2424
"prisma:generate": "yarn prisma generate",
2525
"build": "nest build backend && nest build worker",
2626
"start:worker": "dotenv -e .env.local -- nest start worker",
@@ -39,7 +39,9 @@
3939
"@nestjs/jwt": "11.0.2",
4040
"@nestjs/passport": "11.0.5",
4141
"@nestjs/platform-express": "11.1.9",
42-
"@prisma/client": "6.19.1",
42+
"@prisma/adapter-pg": "7.2.0",
43+
"@prisma/client": "7.2.0",
44+
"@prisma/driver-adapter-utils": "7.2.0",
4345
"axios": "1.13.2",
4446
"bull": "4.16.5",
4547
"class-transformer": "0.5.1",
@@ -81,7 +83,7 @@
8183
"node-loader": "^2.1.0",
8284
"prettier": "3.7.4",
8385
"pretty-format": "30.2.0",
84-
"prisma": "6.19.1",
86+
"prisma": "7.2.0",
8587
"prisma-case-format": "2.2.1",
8688
"rxjs": "7.8.2",
8789
"supertest": "7.1.4",

prisma.config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default {
2+
datasource: {
3+
url: process.env.DATABASE_URL
4+
}
5+
}

prisma/schema.prisma

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ generator client {
55

66
datasource db {
77
provider = "postgresql"
8-
url = "postgresql://developer:Safe1!@localhost:5432/approvio?schema=public"
98
}
109

1110
model Databasechangelog {

scripts/testing/bootstrap-env.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import axios, {AxiosInstance, isAxiosError} from "axios"
99
import {PrismaClient} from "../../generated/prisma/client"
10+
import {PrismaPg} from "@prisma/adapter-pg"
1011
import * as crypto from "crypto"
1112

1213
// Configuration
@@ -25,7 +26,11 @@ const VOTER_USER = {
2526
displayName: "Voter User"
2627
}
2728

28-
const prisma = new PrismaClient()
29+
const prisma = new PrismaClient({
30+
adapter: new PrismaPg({
31+
connectionString: process.env.DATABASE_URL
32+
})
33+
})
2934

3035
// Helper Types
3136
interface Entity {

0 commit comments

Comments
 (0)