P
Prisma2mo ago
NicoFish

Extending client not typesafe

I'm working in a NestJS project and need to extend the prisma client to replace all Date with string. I have this code:
import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '~/types/prisma-schema';

export type WithSerializedDates<T> = T extends Date
? string
: T extends Array<infer R>
? Array<WithSerializedDates<R>>
: T extends object
? { [K in keyof T]: WithSerializedDates<T[K]> }
: T;

export const withoutDates = <T>(model: T): WithSerializedDates<T> =>
JSON.parse(JSON.stringify(model)) as WithSerializedDates<T>;

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit(): Promise<void> {
await this.$extends({
query: {
$allModels: {
$allOperations: async ({ query, args }) => {
return withoutDates(await query(args));
},
},
},
}).$connect();
}
}
import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '~/types/prisma-schema';

export type WithSerializedDates<T> = T extends Date
? string
: T extends Array<infer R>
? Array<WithSerializedDates<R>>
: T extends object
? { [K in keyof T]: WithSerializedDates<T[K]> }
: T;

export const withoutDates = <T>(model: T): WithSerializedDates<T> =>
JSON.parse(JSON.stringify(model)) as WithSerializedDates<T>;

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit(): Promise<void> {
await this.$extends({
query: {
$allModels: {
$allOperations: async ({ query, args }) => {
return withoutDates(await query(args));
},
},
},
}).$connect();
}
}
But when using the client, it does not apply the extension types.
5 Replies
NicoFish
NicoFishOP2mo ago
@Jon Harrell sorry for the ping, do you have any idea? Changing return type of query extension does not change the return type when calling the function
SHG-TV
SHG-TV2mo 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...
SHG-TV
SHG-TV2mo ago
we are currently using the following pattern:
@Injectable()
export class PrismaProvider
extends PrismaClient
implements OnModuleInit, OnModuleDestroy {
constructor(
readonly config: ConfigService, // TODO: check if this can be private?!
readonly cls: ClsService, // TODO: check if this can be private?!
@Inject(CACHE_MANAGER)
private cacheManager: Cache,
) {
const url = new URL(config.getOrThrow<string>('DATABASE_URL'));
url.searchParams.append('connection_limit', '80'); // should be the number of maximal concurrent requests => TODO: maybe replace with env

super({
datasources: {
db: {
url: url.toString(),
},
},
});
}

withExtensions() {
return this.$extends(prismaExistsFnExtension)
.$extends(cacheExtension({ cache: this.cacheManager })) // this may require to use Prisma.Args<PrismaService['file'], 'findMany'>['include'] for several places
.$extends(prismaSessionAuthExtension(this))
.$extends(prismaAddressGeocodeExtension(this));
}
}

const buildPrismaService = () => {
return class {
constructor(provider: PrismaProvider) {
return provider.withExtensions();
}
} as Type<ReturnType<PrismaProvider['withExtensions']>>;
};

@Injectable()
export class PrismaService extends buildPrismaService() {
constructor(private readonly provider: PrismaProvider) {
super(provider);
}
}
@Injectable()
export class PrismaProvider
extends PrismaClient
implements OnModuleInit, OnModuleDestroy {
constructor(
readonly config: ConfigService, // TODO: check if this can be private?!
readonly cls: ClsService, // TODO: check if this can be private?!
@Inject(CACHE_MANAGER)
private cacheManager: Cache,
) {
const url = new URL(config.getOrThrow<string>('DATABASE_URL'));
url.searchParams.append('connection_limit', '80'); // should be the number of maximal concurrent requests => TODO: maybe replace with env

super({
datasources: {
db: {
url: url.toString(),
},
},
});
}

withExtensions() {
return this.$extends(prismaExistsFnExtension)
.$extends(cacheExtension({ cache: this.cacheManager })) // this may require to use Prisma.Args<PrismaService['file'], 'findMany'>['include'] for several places
.$extends(prismaSessionAuthExtension(this))
.$extends(prismaAddressGeocodeExtension(this));
}
}

const buildPrismaService = () => {
return class {
constructor(provider: PrismaProvider) {
return provider.withExtensions();
}
} as Type<ReturnType<PrismaProvider['withExtensions']>>;
};

@Injectable()
export class PrismaService extends buildPrismaService() {
constructor(private readonly provider: PrismaProvider) {
super(provider);
}
}
NicoFish
NicoFishOP2mo ago
@SHG-TV does it return a typesafe controller? I tried it with $allModels $allOperations and it didn't work
SHG-TV
SHG-TV2mo ago
when your extension is valid yes. I think you confused the purpose of the method. The $extends method will return a seperate client and will not change the current one.
Want results from more Discord servers?
Add your server