Skip to content
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b29a7bf
Prisma 7
CarlosGamero Feb 12, 2026
072ae76
Adjusting client generation for prisma 7
CarlosGamero Feb 12, 2026
c8d4f22
Adjusting commands
CarlosGamero Feb 12, 2026
f388646
Update db position
CarlosGamero Feb 12, 2026
91b3231
Fix imports
CarlosGamero Feb 12, 2026
348b56d
More import fixes
CarlosGamero Feb 12, 2026
200cb49
Fix factory
CarlosGamero Feb 12, 2026
8c7d6ee
Factory tests fixed
CarlosGamero Feb 12, 2026
70b6604
Lint fixes
CarlosGamero Feb 12, 2026
0336555
Fix transaction isolation level
CarlosGamero Feb 12, 2026
e4da28b
Import fixes
CarlosGamero Feb 14, 2026
a391710
Improving file org
CarlosGamero Feb 14, 2026
5cbac3b
minor fixes
CarlosGamero Feb 14, 2026
6ceb16f
Minor transaction improvements
CarlosGamero Feb 14, 2026
20f5d71
Removing metrics plugin as it is no longer supported with prisma 7
CarlosGamero Feb 14, 2026
5cb0f21
plugin removal
CarlosGamero Feb 14, 2026
f6895ad
Adding support for prisma query metrics
CarlosGamero Feb 14, 2026
3516643
Removing unused deps
CarlosGamero Feb 14, 2026
7c856f9
node-core update + lint fix
CarlosGamero Feb 14, 2026
00233cb
Lint fixes
CarlosGamero Feb 14, 2026
b7c26b8
using extended client on factory
CarlosGamero Feb 15, 2026
7cbf4e2
readme changes
CarlosGamero Feb 15, 2026
3c94f56
Lint fixes
CarlosGamero Feb 15, 2026
db530d0
Fix CI lint
CarlosGamero Feb 15, 2026
b282309
Merge branch 'main' into chore/prisma_7
CarlosGamero Feb 18, 2026
90a6fc0
Merge branch 'main' into chore/prisma_7
CarlosGamero Feb 18, 2026
067096c
PR feedback
CarlosGamero Feb 18, 2026
7d7ff39
Merge branch 'main' into chore/prisma_7
CarlosGamero Feb 18, 2026
6454bd3
Fixing CI
CarlosGamero Feb 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
"@lokalise/biome-config/configs/biome-package.jsonc"
],
"files": {
"includes": ["**", "!**/dist", "!**/coverage"]
"includes": ["**", "!**/dist", "!**/coverage", "!packages/app/prisma-utils/test/db-client"]
}
}
2 changes: 1 addition & 1 deletion packages/app/prisma-utils/.env
Original file line number Diff line number Diff line change
@@ -1 +1 @@
DATABASE_URL=postgresql://testuser:pass@localhost:26257/test
DATABASE_URL=postgresql://testuser:pass@localhost:26257/test?sslmode=no-verify
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 change: 1 addition & 0 deletions packages/app/prisma-utils/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ node_modules
dist
coverage
.eslintcache
test/db-client/**
141 changes: 129 additions & 12 deletions packages/app/prisma-utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,143 @@

This package provides reusable helpers for Prisma query builder.

# Usage
## Installation

```bash
npm install @lokalise/prisma-utils
```

## Features

### Prisma Client Factory

Factory function to create a Prisma client instance with default configuration and optional Prometheus metrics integration.

**Without metrics:**

```typescript
import { prismaClientFactory } from '@lokalise/prisma-utils'
import { PrismaClient } from '@prisma/client'

const prisma = prismaClientFactory(PrismaClient, {
// Your Prisma client options
})
```

**With Prometheus metrics:**

```typescript
import { prismaClientFactory } from '@lokalise/prisma-utils'
import { PrismaClient } from '@prisma/client'
import * as promClient from 'prom-client'

const prisma = prismaClientFactory(
PrismaClient,
{
// Your Prisma client options
},
{ promClient }
)
```

The factory automatically:
- Sets default transaction isolation level to `ReadCommitted`
- Extends the client with Prometheus metrics when `promClient` is provided

#### Prisma Metrics Collection

When you provide `promClient` to the factory, the following metrics are automatically collected:

- **`prisma_queries_total`**: Total number of Prisma queries executed
- Labels: `model`, `operation`, `status` (success/error)
- **`prisma_query_duration_seconds`**: Duration of Prisma queries in seconds
- Labels: `model`, `operation`
- Buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 5]
- **`prisma_errors_total`**: Total number of Prisma query errors
- Labels: `model`, `operation`, `error_code`

These metrics are automatically exposed through the Prometheus registry and can be scraped by your monitoring system.

### Prisma Transactions

Helper for executing Prisma transactions with intelligent automatic retry mechanism, optimized for distributed databases like CockroachDB.

#### Basic Usage

**With multiple operations:**

```typescript
import { prismaTransaction } from '@lokalise/prisma-utils'
import type { Either } from '@lokalise/node-core'

const result: Either<unknown, [Item, Segment]> = await prismaTransaction(prisma, [
prisma.item.create({ data: TEST_ITEM_1 }),
prisma.segment.create({ data: TEST_SEGMENT_1 }),
])
const result: Either<unknown, [Item, Segment]> = await prismaTransaction(
prisma,
{ dbDriver: 'CockroachDb' },
[
prisma.item.create({ data: { value: 'item-1' } }),
prisma.segment.create({ data: { name: 'segment-1' } }),
]
)
```

This implementation will retry the transaction on P2034 error, which satisfies Prisma recommendations for distributed databases such as CockroachDB.
**With transaction callback:**

```typescript
const result: Either<unknown, User> = await prismaTransaction(
prisma,
{ dbDriver: 'CockroachDb' },
async (tx) => {
const user = await tx.user.create({ data: { name: 'John' } })
await tx.profile.create({ data: { userId: user.id, bio: 'Developer' } })
return user
}
)
```

### Prisma metrics plugin
#### Automatic Retry Mechanism

Plugin to collect and send metrics to prometheus. Prisma metrics will be added to our app metrics.
The transaction helper automatically retries on the following error conditions:

Add the plugin to your Fastify instance by registering it with the following options:
**Prisma Error Codes:**
- **P2034**: Write conflict / Serialization failure (common in distributed databases)
- **P2028**: Transaction API error
- **P1017**: Server closed the connection

**CockroachDB-specific:**
- Automatically detects and retries CockroachDB retry transaction errors.

#### Retry Strategy

1. **Exponential Backoff**: Delay between retries increases exponentially
- Formula: `2^(retry-1) × baseRetryDelayMs`
- Example: 100ms → 200ms → 400ms → 800ms...

2. **Smart Timeout Adjustment**: If transaction times out (P2028 with "transaction is closed"), the timeout is
automatically doubled on retry (up to `maxTimeout`)

3. **Configurable Options**:

```typescript
const result = await prismaTransaction(
prisma,
{
dbDriver: 'CockroachDb', // Enable CockroachDB-specific retries
retriesAllowed: 2, // Default: 2 (total 3 attempts)
baseRetryDelayMs: 100, // Default: 100ms
maxRetryDelayMs: 30000, // Default: 30s (max delay between retries)
timeout: 5000, // Default: 5s (transaction timeout)
maxTimeout: 30000, // Default: 30s (max transaction timeout)
},
[
// Your transaction operations
]
)
```

- `isEnabled`;
- `collectionOptions` (by default we collect metrics `every 5 seconds`) to override default collector behaviour
#### Why This Matters

Once the plugin has been added to your Fastify instance and loaded, we will start collection prisma metrics.
Distributed databases like CockroachDB use optimistic concurrency control, which can result in serialization errors
when multiple transactions compete for the same resources. This helper abstracts away the complexity of handling these
errors, providing a robust and efficient way to ensure your transactions succeed even under high contention.
By automatically retrying failed transactions with an intelligent backoff strategy, you can significantly improve the
reliability and performance of your application when using distributed databases.
22 changes: 10 additions & 12 deletions packages/app/prisma-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,37 +23,35 @@
"clean": "rimraf dist",
"lint": "biome check . && tsc",
"lint:fix": "biome check --write",
"db:migration:dev": "prisma migrate dev",
"db:migration:dev": "prisma migrate dev --config=./prisma/prisma.config",
"db:update-client": "prisma generate",
"db:wait": "while ! echo \"SELECT 1;\" | prisma db execute --stdin --schema prisma/schema.prisma; do sleep 1; done",
"test": "vitest run",
"test:migrate": "cross-env NODE_ENV=test prisma migrate reset --force",
"test:migrate": "cross-env NODE_ENV=test prisma migrate reset --config=./prisma/prisma.config --force",
"pretest:ci": "docker compose up -d && npm run db:wait && npm run test:migrate",
"test:ci": "npm run test",
"posttest:ci": "docker compose down",
"prepublishOnly": "npm run build",
"package-version": "echo $npm_package_version",
"postversion": "biome check --write package.json"
},
"dependencies": {
"@lokalise/node-core": "^14.4.2"
},
"peerDependencies": {
"@prisma/client": ">=5.0.0 <7.0.0",
"prisma": ">=5.0.0 <7.0.0"
"@lokalise/node-core": ">=14.7.4",
"@prisma/client": "^7.0.0",
"prisma": "^7.0.0",
"prom-client": ">=15.0.0"
},
"devDependencies": {
"@biomejs/biome": "^2.3.7",
"@lokalise/biome-config": "^3.1.0",
"@lokalise/fastify-extras": "30.6.0",
"@lokalise/tsconfig": "^3.1.0",
"@prisma/client": "~6.19.0",
"@prisma/adapter-pg": "^7.4.0",
"@prisma/client": "^7.4.0",
"@vitest/coverage-v8": "^4.0.15",
"cross-env": "^10.0.0",
"prisma": "~6.19.0",
"prisma": "^7.4.0",
"rimraf": "^6.0.1",
"typescript": "5.9.3",
"vitest": "^4.0.15",
"zod": "^4.3.6"
"vitest": "^4.0.15"
}
}
11 changes: 11 additions & 0 deletions packages/app/prisma-utils/prisma/prisma.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import 'dotenv/config'
import { defineConfig, env } from 'prisma/config'

// biome-ignore lint/style/noDefaultExport: prisma config requires default export
export default defineConfig({
datasource: { url: env('DATABASE_URL') },
schema: './schema.prisma',
migrations: {
path: './migrations',
},
})
11 changes: 5 additions & 6 deletions packages/app/prisma-utils/prisma/schema.prisma
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
provider = "prisma-client-js"
previewFeatures = ["metrics"]
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed from Prisma 7

}

datasource db {
provider = "cockroachdb"
url = env("DATABASE_URL")
relationMode = "prisma"
}

generator client {
provider = "prisma-client"
output = "../test/db-client"
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Output is now mandatory

}

model Item1 {
id String @id @unique @default(dbgenerated("gen_random_ulid()")) @db.Uuid
value String
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library'
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/client'
import { describe, expect, it } from 'vitest'
import { isCockroachDBRetryTransaction } from './cockroachdbError.ts'
import { PRISMA_SERIALIZATION_ERROR } from './prismaError.ts'
Expand Down
2 changes: 1 addition & 1 deletion packages/app/prisma-utils/src/errors/cockroachdbError.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { PrismaClientKnownRequestError } from '@prisma/client/runtime/library'
import type { PrismaClientKnownRequestError } from '@prisma/client/runtime/client'

/**
* https://www.cockroachlabs.com/docs/stable/transaction-retry-error-reference#:~:text=To%20indicate%20that%20a%20transaction,the%20string%20%22restart%20transaction%22%20.
Expand Down
2 changes: 1 addition & 1 deletion packages/app/prisma-utils/src/errors/prismaError.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { isError } from '@lokalise/node-core'
import type { PrismaClientKnownRequestError } from '@prisma/client/runtime/library'
import type { PrismaClientKnownRequestError } from '@prisma/client/runtime/client'

/**
* What is checked?
Expand Down
Loading