P
Prisma5mo ago
Swyftey

Is there no accelerate client/extended client type?

Hi, With all the types Prisma provides via generation and all, there's got to be a TypeScript type for an extended (specifically accelerated) client, right? Right?! If not, what is the easiest way to make a type for it? I like typing everything I can. Thanks for any help.
24 Replies
Prisma AI Help
Prisma AI Help5mo ago
You selected the carefully hand-crafted route. A dev artisan will respond soon. Meanwhile, the #ask-ai channel awaits if you're curious!
Swyftey
SwyfteyOP5mo ago
I might've found a solution for anyone using NestJS, but I'm not quite sure. I just have it in a way where I don't necessarily need to set a type, I don't think.
No description
Swyftey
SwyfteyOP5mo ago
Nope, nevermind
armful
armful5mo ago
did you find anything for this? i had the same issue a while back with pulse, ended up with a hacky solution that worked but wasn't ideal
Swyftey
SwyfteyOP5mo ago
Nope It's insane how they have all these types/type features yet no official type for extensions, especially their own extension and honestly due to accelerate costing money, I've decided I'm not going to use it because without the type I found it to be a hassle sticking with just prisma and postgres
armful
armful5mo ago
// ...

private readonly pulseClient = this.$extends(
withPulse({ apiKey: env.PULSE_API_KEY }),
)

// ...

get pulse() {
return this.pulseClient
}

// ...
// ...

private readonly pulseClient = this.$extends(
withPulse({ apiKey: env.PULSE_API_KEY }),
)

// ...

get pulse() {
return this.pulseClient
}

// ...
this was my solution its not at all ideal, but gave me access to pulse within my client
Swyftey
SwyfteyOP5mo ago
If it works, that's great. I personally like to have and set a type for everything which should be typed for clarity
armful
armful5mo ago
yeah it gave me types but i wasn't able to extend the client itself
Swyftey
SwyfteyOP5mo ago
But I see you didn't set the return type
armful
armful5mo ago
it was inferred
Swyftey
SwyfteyOP5mo ago
that's what I mean I like to set types, even if they are already inferred OCD thing or something
armful
armful5mo ago
right, but with this i'd be able to retrieve the type. if i just extended the client, it acted like the extension didn't exist it's been an issue for a very long time it seems
armful
armful5mo ago
GitHub
Ability to extend PrismaClient class w/ Client Extensions before ...
Problem Within NestJS, the common solution to implementing an injectable Prisma Client instance is to extend PrismaClient and add the onModuleInit and enableShutdownHooks functions required by Nest...
Swyftey
SwyfteyOP5mo ago
Oh, yes, yes. That was part of the issue I noticed. I thought that was because of me trying to set a type to it somehow, ykyk
armful
armful5mo ago
yeah no it just kills the extended part from the returned type somehow
Swyftey
SwyfteyOP5mo ago
thank you for this. If I decide to end up using it, I'll keep this in mind yeah, really weird
armful
armful5mo ago
it's sadly the best solution i found at the time might revisit and post back here if i figure out a better way
Swyftey
SwyfteyOP5mo ago
Rip. But yes, do feel free to share a better solution if you find one
armful
armful5mo ago
import {
Injectable,
Logger,
OnApplicationShutdown,
OnModuleInit,
} from '@nestjs/common'
import { withPulse } from '@prisma/extension-pulse'
import {
PrismaClientKnownRequestError,
prismaError,
} from 'prisma-better-errors'

import { PrismaClient } from '@workspace/prisma'

import { env } from '@/env'

@Injectable()
export class PrismaService
extends PrismaClient
implements OnModuleInit, OnApplicationShutdown
{
private connectionPromise: Promise<PrismaService> | null = null
private readonly logger = new Logger(PrismaService.name)
private static instance: PrismaService | null = null
private readonly retryDelay = 5000
private readonly maxRetries = 5
private isConnected = false

private readonly pulseClient = this.$extends(
withPulse({ apiKey: env.PULSE_API_KEY }),
)

constructor() {
super({
log:
env.NODE_ENV === 'production'
? ['error']
: ['query', 'warn', 'error', 'info'],
errorFormat: 'pretty',
})

if (!PrismaService.instance) {
PrismaService.instance = this
this.logger.log('PrismaService instance created')
return this
}

return PrismaService.instance
}

public static getInstance(): PrismaService {
if (!PrismaService.instance) {
PrismaService.instance = new PrismaService()
}
return PrismaService.instance
}

async onModuleInit(): Promise<void> {
try {
await this.getConnection()
} catch (error) {
this.logger.error('Failed to initialize database connection', error)
throw error
}
}

async onApplicationShutdown() {
try {
if (this.isConnected) {
await this.$disconnect()
this.isConnected = false
this.logger.log('Database connection closed')
}
} catch (error) {
this.logger.error('Error during database disconnection:', error)
}
}
import {
Injectable,
Logger,
OnApplicationShutdown,
OnModuleInit,
} from '@nestjs/common'
import { withPulse } from '@prisma/extension-pulse'
import {
PrismaClientKnownRequestError,
prismaError,
} from 'prisma-better-errors'

import { PrismaClient } from '@workspace/prisma'

import { env } from '@/env'

@Injectable()
export class PrismaService
extends PrismaClient
implements OnModuleInit, OnApplicationShutdown
{
private connectionPromise: Promise<PrismaService> | null = null
private readonly logger = new Logger(PrismaService.name)
private static instance: PrismaService | null = null
private readonly retryDelay = 5000
private readonly maxRetries = 5
private isConnected = false

private readonly pulseClient = this.$extends(
withPulse({ apiKey: env.PULSE_API_KEY }),
)

constructor() {
super({
log:
env.NODE_ENV === 'production'
? ['error']
: ['query', 'warn', 'error', 'info'],
errorFormat: 'pretty',
})

if (!PrismaService.instance) {
PrismaService.instance = this
this.logger.log('PrismaService instance created')
return this
}

return PrismaService.instance
}

public static getInstance(): PrismaService {
if (!PrismaService.instance) {
PrismaService.instance = new PrismaService()
}
return PrismaService.instance
}

async onModuleInit(): Promise<void> {
try {
await this.getConnection()
} catch (error) {
this.logger.error('Failed to initialize database connection', error)
throw error
}
}

async onApplicationShutdown() {
try {
if (this.isConnected) {
await this.$disconnect()
this.isConnected = false
this.logger.log('Database connection closed')
}
} catch (error) {
this.logger.error('Error during database disconnection:', error)
}
}
private async connectWithRetry(
retries = this.maxRetries,
delay = this.retryDelay,
): Promise<PrismaService> {
if (this.isConnected) return this

for (let attempt = 1; attempt <= retries; attempt++) {
try {
this.logger.debug(
`Attempting database connection (${attempt}/${retries})`,
)
await this.$connect()
this.isConnected = true
this.logger.log('Database connection established')
return this
} catch (error) {
const isLastAttempt = attempt === retries
this.logger.error(
`Connection attempt ${attempt}/${retries} failed: ${error.message}`,
isLastAttempt ? error.stack : undefined,
)

if (isLastAttempt) {
throw new Error(
'Maximum connection attempts reached. Unable to establish database connection',
)
}

this.logger.debug(`Retrying connection in ${delay / 1000} seconds`)
await new Promise((resolve) => setTimeout(resolve, delay))
}
}

throw new Error('Connection attempts exhausted')
}

async getConnection(): Promise<PrismaService> {
if (this.isConnected) return this

if (!this.connectionPromise) {
this.connectionPromise = this.connectWithRetry().finally(() => {
this.connectionPromise = null
})
}

return this.connectionPromise
}

get pulse() {
return this.pulseClient
}

handleException(error: PrismaClientKnownRequestError): never {
this.logger.error(`Prisma error: ${error.message}`, {
code: error.code,
meta: error.meta,
stack: env.NODE_ENV !== 'production' ? error.stack : undefined,
})
throw new prismaError(error)
}
}
private async connectWithRetry(
retries = this.maxRetries,
delay = this.retryDelay,
): Promise<PrismaService> {
if (this.isConnected) return this

for (let attempt = 1; attempt <= retries; attempt++) {
try {
this.logger.debug(
`Attempting database connection (${attempt}/${retries})`,
)
await this.$connect()
this.isConnected = true
this.logger.log('Database connection established')
return this
} catch (error) {
const isLastAttempt = attempt === retries
this.logger.error(
`Connection attempt ${attempt}/${retries} failed: ${error.message}`,
isLastAttempt ? error.stack : undefined,
)

if (isLastAttempt) {
throw new Error(
'Maximum connection attempts reached. Unable to establish database connection',
)
}

this.logger.debug(`Retrying connection in ${delay / 1000} seconds`)
await new Promise((resolve) => setTimeout(resolve, delay))
}
}

throw new Error('Connection attempts exhausted')
}

async getConnection(): Promise<PrismaService> {
if (this.isConnected) return this

if (!this.connectionPromise) {
this.connectionPromise = this.connectWithRetry().finally(() => {
this.connectionPromise = null
})
}

return this.connectionPromise
}

get pulse() {
return this.pulseClient
}

handleException(error: PrismaClientKnownRequestError): never {
this.logger.error(`Prisma error: ${error.message}`, {
code: error.code,
meta: error.meta,
stack: env.NODE_ENV !== 'production' ? error.stack : undefined,
})
throw new prismaError(error)
}
}
here's the full service for context will do
Swyftey
SwyfteyOP5mo ago
Neat If nestjs services are singletons by default, is what you're doing by setting an instance of the client still proper practice? or redundant
armful
armful5mo ago
i guess you could ditch that part. just what i'm used to as this was a framework agnostic service i ported to nestjs
Swyftey
SwyfteyOP5mo ago
I see nothing wrong with your code, was just curious
armful
armful5mo ago
yeah no that makes sense i will check it out again here soon
Swyftey
SwyfteyOP5mo ago
alr, sounds good

Did you find this page helpful?