Ability to extend PrismaClient class w/ Client Extensions before instantiation

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 NestJS.
This typically looks like:
import { Injectable, OnModuleInit, INestApplication } from '@nestjs/common'
import { PrismaClient } from '@prisma/client'

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  async onModuleInit() {
    await this.$connect()
  }

  async enableShutdownHooks(app: INestApplication) {
    this.$on('beforeExit', async () => {
      await app.close()
    })
  }
}

You can use clientExtensions to add these functions, however, they can not adjust the type of PrismaClient and this scenario requires you to export a properly typed, uninstantiated class for NestJS to use.
An example of adding the functions is below:
const prisma = new PrismaClient().$extends({
  client: {
    async onModuleInit() {
      await Prisma.getExtensionContext(this).$connect();
    },
    async enableShutdownHooks(app: INestApplication) {
      Prisma.getExtensionContext(this).$on('beforeExit', async () => {
        await app.close();
      });
    },
  },
});

Suggested solution
Add a static method to PrismaClient that is available when using the clientExtensions preview features that allows you to add methods to the class (and adjust the type) before instantiation.
This resulting class describe above would then look something like:
const ExtendedClient = PrismaClient.$extends({
  client: {
    async onModuleInit() {
      await Prisma.getExtensionContext(this).$connect();
    },
    async enableShutdownHooks(app: INestApplication) {
      Prisma.getExtensionContext(this).$on('beforeExit', async () => {
        await app.close();
      });
    },
  },
})

export ExtendedClient

Alternatives
Additional context
This example focuses on NestJS, however, this functionality could prove useful elsewhere as well.

Hi @sabinadams, have you found any temporary workaround for this?

 The trick was to use a helper function that would create a class while preserving type-safety.
class MyService extends getExtendedClient() {
    async getUserInfo(id: number) {
        const user = await this.user.findFirst({
            where: { id },
        })
    }
}

function getExtendedClient() {
    const client = () => new PrismaClient().$extends({
       // your extension configuration
    })

    return class { // wrapper with type-safety 🎉
        constructor() { return client() }
    } as (new () => ReturnType<typeof client>)
}

Let me know if that works for you.

@millsp this does not work, as getUserInfo is not part of the this object returned from the constructor declared inside getExtendedClient. In other words, inheritance is broken.
I tried your code and got the following error:
await prismaService.enableShutdownHooks(app);
                        ^
TypeError: prismaService.enableShutdownHooks is not a function

I got it to work thanks to your efforts, just needed to go a bit further, here's the solution I ended up with:
function extendPrismaClient() {
  const logger = new Logger('Prisma');
  const prisma = new PrismaClient();
  return prisma.$extends({
    client: {
      async onModuleInit() {
        // Uncomment this to establish a connection on startup, this is generally not necessary
        // https://www.prisma.io/docs/concepts/components/prisma-client/working-with-prismaclient/connection-management#connect
        // await Prisma.getExtensionContext(this).$connect();
      },
      async enableShutdownHooks(app: INestApplication) {
        Prisma.getExtensionContext(prisma).$on('beforeExit', async () => {
          await app.close();
        });
      }
    },
    query: {
      $allModels: {
        async $allOperations({ operation, model, args, query }) {
          const start = performance.now();
          const result = await query(args);
          const end = performance.now();
          const time = end - start;
          logger.debug(`${model}.${operation} took ${time}ms`);
          return result;
        }
      }
    }
  });
}

// https://github.com/prisma/prisma/issues/18628
const ExtendedPrismaClient = class {
  constructor() {
    return extendPrismaClient();
  }
} as new () => ReturnType<typeof extendPrismaClient>;

@Injectable()
export class PrismaService extends ExtendedPrismaClient {}

@Global()
@Module({
  exports: [PrismaService],
  providers: [PrismaService]
})
export class PrismaModule {}

I agree that this is sub-optimal not Nest users. We will get to future requests when we get to prioritizing them. In the mean time I'd like to be able to provide a decent-enough workaround for NestJS users. I am not super happy that you had to move Nest logic into the Prisma Client extension. Let's see if I can help further. I created this snippet, would you mind trying it?
import { Prisma, PrismaClient } from "@prisma/client";

class MyService extends getExtendedClient() {
  async displayUserModelName() {
    this.user.test()
  }
}

function getExtendedClient() {
  const client = () => new PrismaClient().$extends({
    // your extension configuration (example)
    model: {
      $allModels: {
        test() {
          const ctx = Prisma.getExtensionContext(this)
          console.log(`${ctx.name} is the model being called`)
        }
      }
     }
  })

  // some proxy magic to make the workaround
  return new Proxy(class {}, {
    construct(target, args, newTarget) {
      return Object.assign(Reflect.construct(target, args, newTarget), client())
    }
  }) as (new () => ReturnType<typeof client>)
}

const service = new MyService()

async function main() {
  const data = await service.displayUserModelName()
}

void main()

Thanks for your suggestion @millsp, it didn't work off the bat for me, but with a bit more effort I ended up with a solution that I'm quite happy with:
function extendPrismaClient() {
  const logger = new Logger('Prisma');
  const prisma = new PrismaClient();
  return prisma.$extends({
    client: {
      async onModuleInit() {
        // Uncomment this to establish a connection on startup, this is generally not necessary
        // https://www.prisma.io/docs/concepts/components/prisma-client/working-with-prismaclient/connection-management#connect
        // await Prisma.getExtensionContext(this).$connect();
      },
      async enableShutdownHooks(app: INestApplication) {
        Prisma.getExtensionContext(prisma).$on('beforeExit', async () => {
          await app.close();
        });
      }
    },
    query: {
      $allModels: {
        async $allOperations({ operation, model, args, query }) {
          const start = performance.now();
          const result = await query(args);
          const end = performance.now();
          const time = end - start;
          logger.debug(`${model}.${operation} took ${time}ms`);
          return result;
        }
      }
    }
  });
}

// https://github.com/prisma/prisma/issues/18628
const ExtendedPrismaClient = class {
  constructor() {
    return extendPrismaClient();
  }
} as new () => ReturnType<typeof extendPrismaClient>;

@Injectable()
export class PrismaService extends ExtendedPrismaClient {}

@Global()
@Module({
  exports: [PrismaService],
  providers: [PrismaService]
})
export class PrismaModule {}

I'm surprised your ts compiler isn't yelling about the typing issue on extendPrismaClient. Mine is yelling the following:
The inferred type of this node exceeds the maximum length the compiler will serialize. An explicit type annotation is needed.ts(7056)

I'm using the latest TypeScript version (5.1.3), @millsp unfortunately still doesn't work for me:
function getExtendedClient() {
  const logger = new Logger('Prisma');
  const client = () =>
    new PrismaClient().$extends({
      query: PRISMA_QUERY_LOG_LEVEL
        ? {
            $allModels: {
              async $allOperations({ operation, model, args, query }) {
                const start = performance.now();
                const result = await query(args);
                const end = performance.now();
                const time = end - start;
                logger[PRISMA_QUERY_LOG_LEVEL](`${model}.${operation}(${JSON.stringify(args)}) took ${time}ms`);
                return result;
              }
            }
          }
        : undefined
    });

  // some proxy magic to make the workaround
  return new Proxy(class {}, {
    construct(target, args, newTarget) {
      return Object.assign(Reflect.construct(target, args, newTarget), client());
    }
  }) as new () => ReturnType<typeof client>;
}

@Injectable()
export class PrismaService extends getExtendedClient() {
  async onModuleInit() {
    // Uncomment this to establish a connection on startup, this is generally not necessary
    // https://www.prisma.io/docs/concepts/components/prisma-client/working-with-prismaclient/connection-management#connect
    // await Prisma.getExtensionContext(this).$connect();
  }
  async enableShutdownHooks(app: INestApplication) {
    Prisma.getExtensionContext(this as unknown as PrismaClient).$on('beforeExit', async () => {
      await app.close();
    });
  }
}

I get a runtime error when getting the extension context (also need a forced typecast to get around typing issues):
/home/samuel/Dev/Personal/dokku_app/src/server/src/lib/prisma.ts:41
    Prisma.getExtensionContext(this as unknown as PrismaClient).$on('beforeExit', async () => {
                                                                ^
TypeError: client_1.Prisma.getExtensionContext(...).$on is not a function
    at PrismaService.enableShutdownHooks (/home/samuel/Dev/Personal/dokku_app/src/server/src/lib/prisma.ts:41:65)
    at bootstrap (/home/samuel/Dev/Personal/dokku_app/src/server/src/boot_server.ts:77:23)
    at processTicksAndRejections (node:internal/process/task_queues:95:5)
2
Waiting for the debugger to disconnect...
node:internal/process/promises:288
            triggerUncaughtException(err, true /* fromPromise */);

Scratch that, I was able to get it to work. Can't say I love wrapping all prisma operations in another Proxy though.
I'm looking forward to seeing a more elegant solution to integrate extended prisma clients with NestJS.
Here's the resulting working code:
const logger = new Logger('Prisma');
const prisma = new PrismaClient();

function getExtendedClient() {
  const client = () =>
    prisma.$extends({
      query: PRISMA_QUERY_LOG_LEVEL
        ? {
            $allModels: {
              async $allOperations({ operation, model, args, query }) {
                const start = performance.now();
                const result = await query(args);
                const end = performance.now();
                const time = end - start;
                logger[PRISMA_QUERY_LOG_LEVEL](`${model}.${operation}(${JSON.stringify(args)}) took ${time}ms`);
                return result;
              }
            }
          }
        : undefined
    });

  // https://github.com/prisma/prisma/issues/18628#issuecomment-1601958220
  return new Proxy(class {}, {
    construct(target, args, newTarget) {
      return Object.assign(Reflect.construct(target, args, newTarget), client());
    }
  }) as new () => ReturnType<typeof client>;
}

@Injectable()
export class PrismaService extends getExtendedClient() {
  async onModuleInit() {
    // Uncomment this to establish a connection on startup, this is generally not necessary
    // https://www.prisma.io/docs/concepts/components/prisma-client/working-with-prismaclient/connection-management#connect
    // await Prisma.getExtensionContext(this).$connect();
  }
  async enableShutdownHooks(app: INestApplication) {
    Prisma.getExtensionContext(prisma).$on('beforeExit', async () => {
      await app.close();
    });
  }
}

Yes @gamedevsam I expect this feature request to be top-priority once we get to implementing more features for Client Extensions.

@andrewmurraydavid are you using the latest version 4.16.1?

@millsp could you provide a suggestion to extend the solution @gamedevsam most recently posted to include getting access to an injected service  in the extendedClient function.
For example, if we get the isProd variable from the configService from '@nestjs/config' and conditionally ran the performance debug log how would we get access to the configService instance?

@andrewmurraydavid are you using the latest version 4.16.1?

At the time of writing my first comment, I was using 4.16.0. I can confirm that 4.16.1 fixed the typing issue. Thanks!

Scratch that, I was able to get it to work. Can't say I love the idea of wrapping all prisma operations in another Proxy, but it's definitely an improvement over the first attempt. I'm looking forward to seeing a more elegant / official solution to integrate extended prisma clients with NestJS.
Here's the resulting working code:
const prisma = new PrismaClient();
const logger = new Logger("Prisma");

function extendedClient() {
  const extendClient = () =>
    prisma.$extends({
      query: {
        $allModels: {
          async $allOperations({ operation, model, args, query }) {
            const start = performance.now();
            const result = await query(args);
            const end = performance.now();
            const time = end - start;
            logger.debug(
              `${model}.${operation}(${JSON.stringify(args)}) took ${time}ms`
            );
            return result;
          },
        },
      },
    });

  // https://github.com/prisma/prisma/issues/18628#issuecomment-1601958220
  return new Proxy(class {}, {
    construct(target, args, newTarget) {
      return Object.assign(
        Reflect.construct(target, args, newTarget),
        extendClient()
      );
    },
  }) as new () => ReturnType<typeof extendClient>;
}

@Injectable()
export class PrismaService extends extendedClient() {
  async onModuleInit() {
    // Uncomment this to establish a connection on startup, this is generally not necessary
    // https://www.prisma.io/docs/concepts/components/prisma-client/working-with-prismaclient/connection-management#connect
    // await Prisma.getExtensionContext(prisma).$connect();
  }
  async enableShutdownHooks(app: INestApplication) {
    Prisma.getExtensionContext(prisma).$on("beforeExit", async () => {
      await app.close();
    });
  }
}

this code makes $queryRaw undefined.
sad that there isn't a way to make this in a good way

@ThallesP that's not happening for me, for some reason I cannot Ctrl+Click to go to definition, but I do get proper types for the function:

You should double check that both Prisma and TypeScript are updated to their latest version.

Ohh yeah that could be, I didn't test this out at runtime as I don't yet have a use for $queryRaw. I think the Prisma team deprecared the $use method of extending Prisma a bit prematurely seeing that the extension system doesn't yet play nicely with the ecosystem at large.

The health check package @nestjs/terminus  utilises $runCommandRaw and $queryRawUnsafe and since those methods don't seem to be working with this implementation it breaks using the prisma service in health checks.
You can work around it by creating a separate export of the client (before extending) and using it directly in the health controller but that avoids using nest's dependency injection and strays from documentation.

hello guys i came across this issue too but i didnt face any issue with the following code
@prisma/client: 4.16.2
@nestjs/core: ^10.0.0
prisma.module.ts
import { DynamicModule, Logger, Module } from '@nestjs/common';
import { PRISMA_MASTER_CONNECTION, PRISMA_READ_CONNECTION } from './constants';
import { PrismaService } from './prisma.service';
import { prismaExtensionFactory } from './prisma.extension';
import { ConfigService } from '@nestjs/config';

@Module({})
export class PrismaModule {
  static forRoot(): DynamicModule {
    return {
      global: true,
      module: PrismaModule,
      providers: [
        {
          provide: PRISMA_MASTER_CONNECTION,
          useFactory: () => {
            const logger = new Logger(PRISMA_MASTER_CONNECTION);

            return prismaExtensionFactory(
              new PrismaService({
                log: [{ emit: 'event', level: 'query' }],
              }),
              logger,
            );
          },
        },
        {
          provide: PRISMA_READ_CONNECTION,
          useFactory: (configService: ConfigService) => {
            const logger = new Logger(PRISMA_READ_CONNECTION);

            return prismaExtensionFactory(
              new PrismaService({
                datasources: {
                  db: {
                    url: configService.get('READ_REPLICATION_DATABASE_URL'),
                  },
                },
                log: [{ emit: 'event', level: 'query' }],
              }),
              logger,
            );
          },
          inject: [ConfigService],
        },
      ],
      exports: [PRISMA_MASTER_CONNECTION, PRISMA_READ_CONNECTION],
    };
  }
}

prisma.service.ts
import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  async onModuleInit() {
    await this.$connect();
  }
}

prisma.extension.ts
import { INestApplication, Logger } from '@nestjs/common';
import { Prisma, PrismaClient } from '@prisma/client';

export const prismaExtensionFactory = (
  client: PrismaClient<Prisma.PrismaClientOptions, 'query'>,
  logger: Logger,
) => {
  client.$on('query', (e) => {
    logger.debug(`${e.query} -- ${e.params} duration: ${e.duration}ms`);
  });

  return client.$extends({
    client: {
      async enableShutdownHooks(app: INestApplication) {
        Prisma.getExtensionContext(client).$on('beforeExit', async () => {
          logger.log('Gracefully shutdown prisma');

          await app.close();
        });
      },
    },
  });
};

export type ExtendedPrismaClient = ReturnType<typeof prismaExtensionFactory>;

@jl9404 The approach you shared is typesafe and didn't throw errors in the healthcheck module, and allows the use of injected dependencies by passing them through to the factory. Works well for me

In my case, using @jl9404’s approach with EventEmitter (@nestjs/@nestjs/event-emitter) throws an error.
/myproject/node_modules/reflect-metadata/Reflect.js:354
                throw new TypeError();
                      ^
TypeError:
    at Reflect.getMetadata (/myproject/node_modules/reflect-metadata/Reflect.js:354:23)
    at Reflector.get (/myproject/node_modules/@nestjs/core/services/reflector.service.js:24:24)
    at EventsMetadataAccessor.getEventHandlerMetadata (/myproject/node_modules/@nestjs/event-emitter/lib/events-metadata.accessor.ts:13:37)
    at EventSubscribersLoader.subscribeToEventIfListener (/myproject/node_modules/@nestjs/event-emitter/lib/event-subscribers.loader.ts:74:29)
    at /myproject/node_modules/@nestjs/event-emitter/lib/event-subscribers.loader.ts:57:18
    at MetadataScanner.scanFromPrototype (/myproject/node_modules/@nestjs/core/metadata-scanner.js:34:31)
    at /myproject/node_modules/@nestjs/event-emitter/lib/event-subscribers.loader.ts:53:30
    at Array.forEach (<anonymous>)
    at EventSubscribersLoader.loadEventListeners (/myproject/node_modules/@nestjs/event-emitter/lib/event-subscribers.loader.ts:49:8)
    at EventSubscribersLoader.onApplicationBootstrap (/myproject/node_modules/@nestjs/event-emitter/lib/event-subscribers.loader.ts:37:10)

Just import modules in AppModule like below.
…
@Module({
  imports: [
    PrismaModule.forRoot(),
    EventEmitterModule.forRoot(),
    …
  ],
  …
})
export class AppModule {}
…

Am I using it wrong? Please give me a solution.

In my case, using @jl9404’s approach with EventEmitter (@nestjs/@nestjs/event-emitter) throws an error.
/myproject/node_modules/reflect-metadata/Reflect.js:354
                throw new TypeError();
                      ^
TypeError:
    at Reflect.getMetadata (/myproject/node_modules/reflect-metadata/Reflect.js:354:23)
    at Reflector.get (/myproject/node_modules/@nestjs/core/services/reflector.service.js:24:24)
    at EventsMetadataAccessor.getEventHandlerMetadata (/myproject/node_modules/@nestjs/event-emitter/lib/events-metadata.accessor.ts:13:37)
    at EventSubscribersLoader.subscribeToEventIfListener (/myproject/node_modules/@nestjs/event-emitter/lib/event-subscribers.loader.ts:74:29)
    at /myproject/node_modules/@nestjs/event-emitter/lib/event-subscribers.loader.ts:57:18
    at MetadataScanner.scanFromPrototype (/myproject/node_modules/@nestjs/core/metadata-scanner.js:34:31)
    at /myproject/node_modules/@nestjs/event-emitter/lib/event-subscribers.loader.ts:53:30
    at Array.forEach (<anonymous>)
    at EventSubscribersLoader.loadEventListeners (/myproject/node_modules/@nestjs/event-emitter/lib/event-subscribers.loader.ts:49:8)
    at EventSubscribersLoader.onApplicationBootstrap (/myproject/node_modules/@nestjs/event-emitter/lib/event-subscribers.loader.ts:37:10)

Just import modules in AppModule like below.
…
@Module({
  imports: [
    PrismaModule.forRoot(),
    EventEmitterModule.forRoot(),
    …
  ],
  …
})
export class AppModule {}
…

Am I using it wrong? Please give me a solution.

i guess you have to patch the package cause the event emitter module scan the provider and check the prototype of prisma client
diff --git<EMAIL_ADDRESS><EMAIL_ADDRESS>index 53f038e..c06eae9 100644
---<EMAIL_ADDRESS>+++<EMAIL_ADDRESS>@@ -18,6 +18,9 @@ let EventsMetadataAccessor = class EventsMetadataAccessor {
         this.reflector = reflector;
     }
     getEventHandlerMetadata(target) {
+        if (!target) {
+            return undefined;
+        }
         const metadata = this.reflector.get(constants_1.EVENT_LISTENER_METADATA, target);
         if (!metadata) {
             return undefined;

I think I have an alternative workaround that's a bit simpler. But please let me know if I'm missing something here.
// prisma.service.ts

import { INestApplication, Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService implements OnModuleInit {
  private readonly baseClient: PrismaClient;
  readonly client;

  constructor() {
    this.baseClient = new PrismaClient();
    this.client = this.getExtendedClient(this.baseClient);
  }

  async onModuleInit() {
    await this.client.$connect();
  }

  async enableShutdownHooks(app: INestApplication) {
    this.baseClient.$on('beforeExit', async () => {
      await app.close();
    });
  }

  private getExtendedClient(baseClient: PrismaClient) {
    return baseClient.$extends({
      // ...
    });
  }
}

The con here is that you have a nested field client (see example below). But it seems to be type safe from the little testing I've done.
this.prismaService.client.user.findMany();

Hello!
Has anyone found a solution to pass a Prisma Extension dynamically?
In theory, the below in my screenshot works but I lose type-inference because it seems like Prisma is only happy if you pass the $extends() object inside of prisma.$extends({ ... }) of the method itself and not as a parameter.
Need to find a way to pass the $extends hook as a parameter..

maybe its time for an official nestjs-prisma wrapper?!

@renatoaraujoc that code might work, but hot damn is it convoluted.

@renatoaraujoc any ideas for using with multiple extensions

@renatoaraujoc that code might work, but hot damn is it convoluted.

@gamedevsam, I believe my code is a starting point to others to understand that you need to "route" prisma types when extending and also doing other stuff and this might open doors for more type-safe/fluent apis.
The code can always be improved, it took me hours to understand the inner workings of Prisma and come up with it, at least all types are maintained.

@renatoaraujoc any ideas for using with multiple extensions

@paulwer I believe the code has to be improved to be able to chain prisma extensions, the issue is that Typescript (neither Javascript) allow multiple extends like Java (and others) does.
Like I mentioned above, it's a starting point.

@jl9404 your solution definitely seems like it could help me. I am having a bit of a hard time wrapping my head around how I actually import the Prisma Service into another service and why you have a read and master connection? Would you be able to share an implementation example?

@renatoaraujoc Hi, thanks for your solution.
I have an issue with it, some methods and internal properties doesn't work anymore.
Such as $queryRaw.

@renatoaraujoc Hi, thanks for your solution. I have an issue with it, some methods such as $queryRaw and internal properties doesn't work anymore. I believe it related to these lines
return Object.assign(
                Reflect.construct(target, _args, newTarget),
                extendedPrisma
            );

It doesn't copy non-enumerable properties.

Hey @ozhuravlov1,
It's pretty hard to route the types in the Prisma API, because afaik that's what you got to do.
The best thing we can do is to wait the Prisma team to leverage the integration of Prisma with solutions like NestJS and provide a better way to "class"ify everything.
I'm sorry my solution didnt't work :(

To give an update to the group, I have reverted back to using the deprecated middleware api on my project. None of the proposed solutions are satisfactory for me, they are either too complex or cause regressions in the TypeScript experience. I'm hoping the Prisma team will offer Nest users a viable solution that doesn't come with those tradeoffs before they remove middleware entirely.

Just wanna thank everyone in this thread that provided examples and PoC's for the rest of us to jam on. Your contributions are appreciated. I have a pretty complicated use case but your efforts give me a jump start to build off of. If I can make something that works well I'll repay the favor.

 Here's my use case... I need to override the actual client so that I can make use of read/writes. We also use middleware attached in the onModuleInit which seems to overwrite the client extension. My thought is to just instantiate the client inside a module with the extension init'd and then import that in my data repos.
Are there downsides besides adding other injectables to the module? Performance hits?

@crice88 Do I understand correctly that you're thinking you'll add something like this in the module:
providers: {
  provide: "PRISMA_TOKEN",
  useFactory: () => {
    const client = new PrismaClient();
    // do some setup to client
    return client;
  }
},
exports: ["PRISMA_TOKEN"]

and then you can depend on that module from other modules and @Inject("PRISMA_TOKEN") to get access back to your PrismaClient?
There's no reason you can't do that - indeed my solution we use in production basically does that, I just used a service-like class to punt the setup for the client to another file. However, I'm not sure it will solve the ultimate issue that middleware are both deprecated and also have issues playing nicely with client extension types. I fear that at some point we're still going to have to convince Prisma org to solve the client extension typing problem and also rewrite our middleware and extensions to play nicely together in the v5 world.

@zackdotcomputer no this is it! Yeah we have a ton of middlewares built out that would need to be refactored to move them to extensions, just don't have the time to do it yet. Thanks for the reply!

Probably late to the party, but better late than never. here's my hack, now I'm not sure how buggy this could be, I'm open to suggestions
import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  async onModuleInit() {
    await this.$connect();

    Object.assign(
      this,
      this.$extends({
        query: {
          $allModels: {
            async findMany({ model, operation, args, query }) {
              // set `take` and fill with the rest of `args`
              console.log(model, operation, args, query);

              return query(args);
            },
          },
        },
      }),
    );
  }
}

@crice88 Do I understand correctly that you're thinking you'll add something like this in the module:
providers: {
  provide: "PRISMA_TOKEN",
  useFactory: () => {
    const client = new PrismaClient();
    // do some setup to client
    return client;
  }
},
exports: ["PRISMA_TOKEN"]

and then you can depend on that module from other modules and @Inject("PRISMA_TOKEN") to get access back to your PrismaClient?
There's no reason you can't do that - indeed my solution we use in production basically does that, I just used a service-like class to punt the setup for the client to another file. However, I'm not sure it will solve the ultimate issue that middleware are both deprecated and also have issues playing nicely with client extension types. I fear that at some point we're still going to have to convince Prisma org to solve the client extension typing problem and also rewrite our middleware and extensions to play nicely together in the v5 world.

Nestjs Prisma seems to be doing the same thing
https://nestjs-prisma.dev/docs/prisma-client-extensions/

Probably late to the party, but better late than never. here's my hack, now I'm not sure how buggy this could be, I'm open to suggestions
import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  async onModuleInit() {
    await this.$connect();

    Object.assign(
      this,
      this.$extends({
        query: {
          $allModels: {
            async findMany({ model, operation, args, query }) {
              // set `take` and fill with the rest of `args`
              console.log(model, operation, args, query);

              return query(args);
            },
          },
        },
      }),
    );
  }
}

This solution worked perfectly for me, without the need to change too much in my project.
Thank you for your suggestion @struckchure.

@crice88 Do I understand correctly that you're thinking you'll add something like this in the module:
providers: {
  provide: "PRISMA_TOKEN",
  useFactory: () => {
    const client = new PrismaClient();
    // do some setup to client
    return client;
  }
},
exports: ["PRISMA_TOKEN"]

and then you can depend on that module from other modules and @Inject("PRISMA_TOKEN") to get access back to your PrismaClient?
There's no reason you can't do that - indeed my solution we use in production basically does that, I just used a service-like class to punt the setup for the client to another file. However, I'm not sure it will solve the ultimate issue that middleware are both deprecated and also have issues playing nicely with client extension types. I fear that at some point we're still going to have to convince Prisma org to solve the client extension typing problem and also rewrite our middleware and extensions to play nicely together in the v5 world.

Nestjs Prisma seems to be doing the same thing https://nestjs-prisma.dev/docs/prisma-client-extensions/

I can't get this one to initialize properly, the creator has added a few examples but it is a bit confusing on how to setup the base and replica DB URLs

My extended Prisma Client code for the NestJS looks like this. It works perfectly.
I'm using like databaseService.client.user.softDelete({ id })
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common'

import { PrismaClientExtended } from '@/database/clients/prisma/prisma-client-extended'

@Injectable()
export class DatabaseService extends PrismaClientExtended implements OnModuleInit, OnModuleDestroy {
  async onModuleInit() {
    await this.$connect()
  }

  async onModuleDestroy() {
    await this.$disconnect()
  }
}

import { Prisma, PrismaClient } from '@prisma/client'

import { CustomPrismaClient } from '@/types/database/prisma-client-extended'

export const customPrismaClient = (prismaClient: PrismaClient) => {
  return prismaClient.$extends({
    model: {
      $allModels: {
        async softDelete<M, A>(
          this: M,
          where: Prisma.Args<M, 'update'>['where'],
        ): Promise<Prisma.Result<M, A, 'update'>> {
          const context = Prisma.getExtensionContext(this)

          // eslint-disable-next-line @typescript-eslint/no-explicit-any -- There is no way to type a Prisma model
          return (context as any).update({
            where,
            data: {
              deletedAt: new Date(),
            },
          })
        },
      },
    },
  })
}

export class PrismaClientExtended extends PrismaClient {
  customPrismaClient: CustomPrismaClient

  get client() {
    if (!this.customPrismaClient) this.customPrismaClient = customPrismaClient(this)

    return this.customPrismaClient
  }
}

import { customPrismaClient } from '@/database/clients/prisma/prisma-client-extended'

export type CustomPrismaClient = ReturnType<typeof customPrismaClient>

https://github.com/prisma/prisma/discussions/21361

@leandromatos I finally spent some time trying out your solution and didn't love the ergonomics of having to do this.prismaService.client.model.findUnique(), whereas with my solution I could simply do: this.prismaService.model.findUnique().
I've resolved the issue and can preserve my development ergonomics while also using Prisma extensions and preserving all type safety. My updated solution gives me the ability to treat the prismaService instance as a PrismaClient, while also having the ability to access typesafe extensions like so: this.prismaService.extendedClient, check out my solution:
const logger = new Logger('Prisma');

const client = new PrismaClient({
  log: PRISMA_QUERY_LOG_EVENTS
    ? [
        {
          emit: 'event',
          level: 'query'
        }
      ]
    : undefined
});

if (PRISMA_QUERY_LOG_EVENTS) {
  // @ts-expect-error
  client.$on('query', (e) => {
    logger[PRISMA_QUERY_LOG_EVENTS](`${JSON.stringify(e).replaceAll(/\\?"|"/g, '')}`);
  });
}

const extendedClient = client.$extends({
  query: PRISMA_QUERY_LOG_QUERIES
    ? {
        $allModels: {
          $allOperations: async ({ operation, model, args, query }) => {
            const start = performance.now();
            const result = await query(args);
            const end = performance.now();
            const time = end - start;
            logger[PRISMA_QUERY_LOG_QUERIES](`${model}.${operation}(${JSON.stringify(args)}) took ${time}ms`);
            return result;
          }
        }
      }
    : undefined
});

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  readonly extendedClient = extendedClient;

  constructor() {
    super({ datasourceUrl: '', datasources: { db: { url: '' } } });
    return new Proxy(this, {
      get: (target: any, key: string) => Reflect.get(key in extendedClient ? extendedClient : target, key)
    });
  }
  async onModuleInit() {
    // Uncomment this to establish a connection on startup, this is generally not necessary
    // https://www.prisma.io/docs/concepts/components/prisma-client/working-with-prismaclient/connection-management#connect
    // await Prisma.getExtensionContext(client).$connect();
  }
  async enableShutdownHooks(app: INestApplication) {
    async function waitForAppClose() {
      await app.close();
    }
    // https://prisma.io/docs/guides/upgrade-guides/upgrading-versions/upgrading-to-prisma-5#removal-of-the-beforeexit-hook-from-the-library-engine
    process.on('exit', waitForAppClose);
    process.on('beforeExit', waitForAppClose);
    process.on('SIGINT', waitForAppClose);
    process.on('SIGTERM', waitForAppClose);
    process.on('SIGUSR2', waitForAppClose);
  }
}

@Module({
  exports: [PrismaService],
  providers: [PrismaService]
})
export class PrismaModule {}

Probably late to the party, but better late than never. here's my hack, now I'm not sure how buggy this could be, I'm open to suggestions
import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  async onModuleInit() {
    await this.$connect();

    Object.assign(
      this,
      this.$extends({
        query: {
          $allModels: {
            async findMany({ model, operation, args, query }) {
              // set `take` and fill with the rest of `args`
              console.log(model, operation, args, query);

              return query(args);
            },
          },
        },
      }),
    );
  }
}

how to destuct req in extends

Great, @gamedevsam!
Have you tried to create a new operation for all models, like softDelete, using type-safe? Using your solution, I couldn't do that.

With this extended client definition:
const extendedClient = client.$extends({
  model: {
    $allModels: {
      async exists<T>(this: T, where: Prisma.Args<T, 'findFirst'>['where']): Promise<boolean> {
        // Get the current model at runtime
        const context = Prisma.getExtensionContext(this);

        const result = await (context as any).findFirst({ where });
        return result !== null;
      }
    }
  },
  query: PRISMA_QUERY_LOG_QUERIES
    ? {
        $allModels: {
          $allOperations: async ({ operation, model, args, query }) => {
            const start = performance.now();
            const result = await query(args);
            const end = performance.now();
            const time = end - start;
            logger[PRISMA_QUERY_LOG_QUERIES](`${model}.${operation}(${JSON.stringify(args)}) took ${time}ms`);
            return result;
          }
        }
      }
    : undefined
});

I can do this in my services (not this.prisma points to the injected PrismaService):
this.prisma.extendedClient.entity.exists({ id });

It works perfectly well and is typesafe, just keep in mind to access extended methods, you need to use the extendedClient field of the service.

Good job, @gamedevsam!
I decided to get your implementation and mix it up with mine. Here is what I got and 100% type-safe.
// database.module.ts
import { Global, Module } from '@nestjs/common'

import { DatabaseService } from<EMAIL_ADDRESS>
@Global()
@Module({
  providers: [DatabaseService],
  exports: [DatabaseService],
})
export class DatabaseModule {}

// database.service.ts
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common'
import { Prisma, PrismaClient } from '@prisma/client'

export const prismaExtendedClient = (prismaClient: PrismaClient) =>
  prismaClient.$extends({
    model: {
      $allModels: {
        async softDelete<M, A>(
          this: M,
          where: Prisma.Args<M, 'update'>['where'],
        ): Promise<Prisma.Result<M, A, 'update'>> {
          const context = Prisma.getExtensionContext(this)

          // eslint-disable-next-line @typescript-eslint/no-explicit-any -- There is no way to type a Prisma model
          return (context as any).update({
            where,
            data: {
              deletedAt: new Date(),
            },
          })
        },
        async isDeleted<M>(this: M, where: Prisma.Args<M, 'findUnique'>['where']): Promise<boolean> {
          const context = Prisma.getExtensionContext(this)

          // eslint-disable-next-line @typescript-eslint/no-explicit-any -- There is no way to type a Prisma model
          const result = await (context as any).findUnique({ where })

          return !!result.deletedAt
        },
      },
    },
  })

@Injectable()
export class DatabaseService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  readonly extendedClient = prismaExtendedClient(this)

  constructor() {
    super()

    new Proxy(this, {
      get: (target, property) => Reflect.get(property in this.extendedClient ? this.extendedClient : target, property),
    })
  }

  async onModuleInit() {
    await this.$connect()
  }

  async onModuleDestroy() {
    await this.$disconnect()
  }
}

Now, I can use the extended methods like this:
await this.databaseService.extendedClient.user.softDelete({ id })

And, I also can use like the normal way:
return await this.databaseService.user.findFirstOrThrow({
    where: { id },
})

Nicely done, I feel we could keep trimming things down and try to keep everything contained within the service, but to be frank the core of the solution is the Proxy + separate extended client for type safety. Everything else is just icing on the cake. Hopefully the Prisma team takes this pattern and makes it more widely available to NestJS users via official docs.

Just a last tip. It's possible to split all extended Prisma methods into separate files, but you must declare the methods using a function instead of an arrow function. Doing that will make it much easier to maintain the code.
const softDelete = async function <M, A>(
  this: M,
  where: Prisma.Args<M, 'update'>['where'],
): Promise<Prisma.Result<M, A, 'update'>> {
  const context = Prisma.getExtensionContext(this)
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- There is no way to type a Prisma model
  const result = (context as any).update({
    where,
    data: {
      deletedAt: new Date(),
    },
  })

  return result
}

const isDeleted = async function <M>(this: M, where: Prisma.Args<M, 'findUnique'>['where']): Promise<boolean> {
  const context = Prisma.getExtensionContext(this)
  // eslint-disable-next-line -- There is no way to type a Prisma model
  const result = await (context as any).findUnique({ where })

  return !!result.deletedAt
}

export const prismaExtendedClient = (prismaClient: PrismaClient) =>
  prismaClient.$extends({
    model: {
      $allModels: {
        softDelete,
        isDeleted,
      },
    },
  })

Probably late to the party, but better late than never. here's my hack, now I'm not sure how buggy this could be, I'm open to suggestions
import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  async onModuleInit() {
    await this.$connect();

    Object.assign(
      this,
      this.$extends({
        query: {
          $allModels: {
            async findMany({ model, operation, args, query }) {
              // set `take` and fill with the rest of `args`
              console.log(model, operation, args, query);

              return query(args);
            },
          },
        },
      }),
    );
  }
}

Thanks, looks like the simplest solution. Any way to make it work with typescript?

@MustagheesButt look here. The code works with Typescript, and it is type-safe.

I feel my solution, or Leandro's or some combination (they are largely the same, with minor differences in style / conciseness) is a "good enough" solution. I recommend closing this issue.

Good job, @gamedevsam!
I decided to get your implementation and mix it up with mine. Here is what I got and 100% type-safe.
// database.module.ts
import { Global, Module } from '@nestjs/common'

import { DatabaseService } from<EMAIL_ADDRESS>
@Global()
@Module({
  providers: [DatabaseService],
  exports: [DatabaseService],
})
export class DatabaseModule {}

// database.service.ts
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common'
import { Prisma, PrismaClient } from '@prisma/client'

export const prismaExtendedClient = (prismaClient: PrismaClient) =>
  prismaClient.$extends({
    model: {
      $allModels: {
        async softDelete<M, A>(
          this: M,
          where: Prisma.Args<M, 'update'>['where'],
        ): Promise<Prisma.Result<M, A, 'update'>> {
          const context = Prisma.getExtensionContext(this)

          // eslint-disable-next-line @typescript-eslint/no-explicit-any -- There is no way to type a Prisma model
          return (context as any).update({
            where,
            data: {
              deletedAt: new Date(),
            },
          })
        },
        async isDeleted<M>(this: M, where: Prisma.Args<M, 'findUnique'>['where']): Promise<boolean> {
          const context = Prisma.getExtensionContext(this)

          // eslint-disable-next-line @typescript-eslint/no-explicit-any -- There is no way to type a Prisma model
          const result = await (context as any).findUnique({ where })

          return !!result.deletedAt
        },
      },
    },
  })

@Injectable()
export class DatabaseService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  readonly extendedClient = prismaExtendedClient(this)

  constructor() {
    super()

    new Proxy(this, {
      get: (target, property) => Reflect.get(property in this.extendedClient ? this.extendedClient : target, property),
    })
  }

  async onModuleInit() {
    await this.$connect()
  }

  async onModuleDestroy() {
    await this.$disconnect()
  }
}

Now, I can use the extended methods like this:
await this.databaseService.extendedClient.user.softDelete({ id })

And, I also can use like the normal way:
return await this.databaseService.user.findFirstOrThrow({
    where: { id },
})

@Global()
@Module({
providers: [
{
provide: PrismaService,
useFactory: async (config: ConfigService<EnvironmentVariables, true>) => {
return new PrismaService(config).initPrismaWithClientExtension();
},
inject: [ConfigService<EnvironmentVariables, true>],
},
],
exports: [PrismaService],
})
export class PrismaModule {}

This worked for me.
@Global()
@Module({
  providers: [
    {
      provide: PrismaService,
      useFactory: async (config: ConfigService<EnvironmentVariables, true>) => {
        return new PrismaService(config).initPrismaWithClientExtension();
      },
      inject: [ConfigService<EnvironmentVariables, true>],
    },
  ],
  exports: [PrismaService],
})
export class PrismaModule {}

import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Prisma, PrismaClient } from '@prisma/client';
import { EnvironmentVariables } from '../config';
import { prismaExtendedClient } from './prisma.extension';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  private static instance: PrismaService;
  private readonly logger = new Logger(PrismaService.name);

  constructor(
    private readonly config: ConfigService<EnvironmentVariables, true>,
  ) {
    if (PrismaService.instance) {
      return PrismaService.instance;
    }

    super({
      log: [
        { emit: 'event', level: 'query' },
        { emit: 'stdout', level: 'error' },
        { emit: 'stdout', level: 'info' },
        { emit: 'stdout', level: 'warn' },
      ],
      datasources: { db: { url: config.get('DATABASE_URL', { infer: true }) } },
    });

    PrismaService.instance = this;
  }

  async initPrismaWithClientExtension() {
    return await prismaExtendedClient(this);
  }

  async onModuleInit() {
    await this.$connect();

    if (this.config.get('PRISMA_LOG', { infer: true })) {
      //@ts-expect-error: known bug in prism missing proper type
      this.$on('query', (e: Prisma.QueryEvent) => {
        this.logger.log(
          JSON.stringify({
            duration: `${e.duration}ms`,
            query: e.query,
            params: e.params,
          }),
        );
      });
    }
  }
}

export const prismaExtendedClient = async (client: PrismaClient) =>
  client.$extends({})

Good job, @gamedevsam!
I decided to get your implementation and mix it up with mine. Here is what I got and 100% type-safe.
// database.module.ts
import { Global, Module } from '@nestjs/common'

import { DatabaseService } from<EMAIL_ADDRESS>
@Global()
@Module({
  providers: [DatabaseService],
  exports: [DatabaseService],
})
export class DatabaseModule {}

// database.service.ts
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common'
import { Prisma, PrismaClient } from '@prisma/client'

export const prismaExtendedClient = (prismaClient: PrismaClient) =>
  prismaClient.$extends({
    model: {
      $allModels: {
        async softDelete<M, A>(
          this: M,
          where: Prisma.Args<M, 'update'>['where'],
        ): Promise<Prisma.Result<M, A, 'update'>> {
          const context = Prisma.getExtensionContext(this)

          // eslint-disable-next-line @typescript-eslint/no-explicit-any -- There is no way to type a Prisma model
          return (context as any).update({
            where,
            data: {
              deletedAt: new Date(),
            },
          })
        },
        async isDeleted<M>(this: M, where: Prisma.Args<M, 'findUnique'>['where']): Promise<boolean> {
          const context = Prisma.getExtensionContext(this)

          // eslint-disable-next-line @typescript-eslint/no-explicit-any -- There is no way to type a Prisma model
          const result = await (context as any).findUnique({ where })

          return !!result.deletedAt
        },
      },
    },
  })

@Injectable()
export class DatabaseService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  readonly extendedClient = prismaExtendedClient(this)

  constructor() {
    super()

    new Proxy(this, {
      get: (target, property) => Reflect.get(property in this.extendedClient ? this.extendedClient : target, property),
    })
  }

  async onModuleInit() {
    await this.$connect()
  }

  async onModuleDestroy() {
    await this.$disconnect()
  }
}

Now, I can use the extended methods like this:
await this.databaseService.extendedClient.user.softDelete({ id })

And, I also can use like the normal way:
return await this.databaseService.user.findFirstOrThrow({
    where: { id },
})

does this still work? I've just tried it with Prisma 5.6.0 and Prisma client 5.6.0 and it hangs at Prisma.getExtensionContext()

Try a solution based on this example, customize it for your needs. I haven't yet tried to update Prisma, but previous examples were not good starting points imo (avoid anything with this keyword used outside of class methods, it's a bad idea, and will lead you to errors / tricky bugs).

Also this thread if getting out of hand, anyone can close it? Not everyone needs to share their minor variation on the same solution. Use mine as a starting point and customize it as you like. Only share your solution if it's significantly different plz (to combat noise).

I think there needs to be a solution from Prisma to this issue because the variants on @gamedevsam's solution still don't address the underlying issue - until Prisma gives us a way to create a PrismaClient that is extended from the start, we will not have complete typing on PrismaService. Until we get that upstream solution, we will always have to do one or more of the hack-arounds of:

Requiring the developer to call into the prisma service's stored extendedClient for at least some calls - i.e. this.prismaService.extendedClient.model.call().
Requiring the use of the @Inject directive, like my solution did, to inject the results of the extend function directly in place of a normal service dependency.
Keep a persistent extendedClient instance in the static memory space of the server, rather than letting Nest's dependency management choose when to create the service/client.

None of our solutions thus far (afaict) avoid all three of these, and I think that's why we want to keep this issue open until Prisma gets around to giving us an extension tool that can be typed prior to instantiation.

Is the issue resolved?

Also this thread if getting out of hand, anyone can close it? Not everyone needs to share their minor variation on the same solution. Use mine as a starting point and customize it as you like. Only share your solution if it's significantly different plz (to combat noise).

This thread is not out of hand.
While your solution is somewhat acceptable, it's not the best way to make things work.
The best way is Prisma team to take a look on this and provide a way to "class"ify the client and provide ways to extend it thru this mechanism while keeping all the types.

Good job, @gamedevsam!
I decided to get your implementation and mix it up with mine. Here is what I got and 100% type-safe.
// database.module.ts
import { Global, Module } from '@nestjs/common'

import { DatabaseService } from<EMAIL_ADDRESS>
@Global()
@Module({
  providers: [DatabaseService],
  exports: [DatabaseService],
})
export class DatabaseModule {}

// database.service.ts
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common'
import { Prisma, PrismaClient } from '@prisma/client'

export const prismaExtendedClient = (prismaClient: PrismaClient) =>
  prismaClient.$extends({
    model: {
      $allModels: {
        async softDelete<M, A>(
          this: M,
          where: Prisma.Args<M, 'update'>['where'],
        ): Promise<Prisma.Result<M, A, 'update'>> {
          const context = Prisma.getExtensionContext(this)

          // eslint-disable-next-line @typescript-eslint/no-explicit-any -- There is no way to type a Prisma model
          return (context as any).update({
            where,
            data: {
              deletedAt: new Date(),
            },
          })
        },
        async isDeleted<M>(this: M, where: Prisma.Args<M, 'findUnique'>['where']): Promise<boolean> {
          const context = Prisma.getExtensionContext(this)

          // eslint-disable-next-line @typescript-eslint/no-explicit-any -- There is no way to type a Prisma model
          const result = await (context as any).findUnique({ where })

          return !!result.deletedAt
        },
      },
    },
  })

@Injectable()
export class DatabaseService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  readonly extendedClient = prismaExtendedClient(this)

  constructor() {
    super()

    new Proxy(this, {
      get: (target, property) => Reflect.get(property in this.extendedClient ? this.extendedClient : target, property),
    })
  }

  async onModuleInit() {
    await this.$connect()
  }

  async onModuleDestroy() {
    await this.$disconnect()
  }
}

Now, I can use the extended methods like this:
await this.databaseService.extendedClient.user.softDelete({ id })

And, I also can use like the normal way:
return await this.databaseService.user.findFirstOrThrow({
    where: { id },
})

does this still work? I've just tried it with Prisma 5.6.0 and Prisma client 5.6.0 and it hangs at Prisma.getExtensionContext()

Same problem, have u solved this ?

I ran up against this issue today and ended up with a solution that seems to meet my needs. The actual extension and some types were adapted from this comment.
I'm a bit late to the party and this thread has gotten pretty long, so I apologize in advance if I'm rehashing a solution that's already been discussed. But I believe this approach addresses all 3 of @zackdotcomputer's concerns:

Extended methods can be called directly like this.prismaService.model.method()
No additional @Inject directives are required
Nest still manages the DI

Additionally:

No proxying required
No Nest-specific code has to be moved into the Prisma client
Seems nice and type-safe

For reference, I'm using<EMAIL_ADDRESS>// src/prisma/extensions/find-many-count.extension.ts

import { Prisma } from "@prisma/client";

import type { PrismaClient } from "@prisma/client";

export function createFindManyAndCountExtension<TModel = any, TArgs = any>(prisma: PrismaClient) {
  return {
    name: "findManyAndCount",
    model: {
      $allModels: {
        findManyAndCount(
          this: TModel,
          args?: Prisma.Exact<TArgs, Prisma.Args<TModel, "findMany">>
        ): Promise<[Prisma.Result<TModel, TArgs, "findMany">, number]> {
          const context = Prisma.getExtensionContext(this);

          return prisma.$transaction([
            (context as any).findMany(args),
            (context as any).count({ where: (args as any)?.where }),
          ]);
        },
      },
    },
  };
}

export type FindManyAndCount<TModel, TArgs> = ReturnType<
  typeof createFindManyAndCountExtension<TModel, TArgs>
>["model"]["$allModels"]["findManyAndCount"];

// src/prisma/extended-client.ts

import { PrismaClient } from "@prisma/client";

import { createFindManyAndCountExtension, type FindManyAndCount } from "./extensions/find-many-count.extension";

type ModelsWithExtensions = {
  [Model in keyof PrismaClient]: PrismaClient[Model] extends { findMany: (args: infer TArgs) => Promise<any> }
    ? {
        findManyAndCount: FindManyAndCount<PrismaClient[Model], TArgs>;
      } & PrismaClient[Model]
    : PrismaClient[Model];
};

class UntypedExtendedClient extends PrismaClient {
  constructor(options?: ConstructorParameters<typeof PrismaClient>[0]) {
    super(options);

    return this.$extends(createFindManyAndCountExtension(this)) as this;
  }
}

const ExtendedPrismaClient = UntypedExtendedClient as unknown as new (
  options?: ConstructorParameters<typeof PrismaClient>[0]
) => PrismaClient & ModelsWithExtensions;

export { ExtendedPrismaClient };

// src/prisma/prisma.service.ts

import type { OnModuleInit, OnModuleDestroy } from "@nestjs/common";
import { Injectable } from "@nestjs/common";

import { ExtendedPrismaClient } from "./extended-client";

@Injectable()
export class PrismaService extends ExtendedPrismaClient implements OnModuleInit, OnModuleDestroy {
  async onModuleInit() {
    await this.$connect();
  }

  async onModuleDestroy() {
    await this.$disconnect();
  }
}

// src/prisma/prisma.module.ts

import { Global, Module } from "@nestjs/common";

import { PrismaService } from "./prisma.service";

@Global()
@Module({
  providers: [PrismaService],
  exports: [PrismaService],
})
export class PrismaModule {}

I'm sure there's a gotcha here somewhere that I haven't hit yet, but hopefully this helps!

Good job, @gamedevsam!
I decided to get your implementation and mix it up with mine. Here is what I got and 100% type-safe.
// database.module.ts
import { Global, Module } from '@nestjs/common'

import { DatabaseService } from<EMAIL_ADDRESS>
@Global()
@Module({
  providers: [DatabaseService],
  exports: [DatabaseService],
})
export class DatabaseModule {}

// database.service.ts
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common'
import { Prisma, PrismaClient } from '@prisma/client'

export const prismaExtendedClient = (prismaClient: PrismaClient) =>
  prismaClient.$extends({
    model: {
      $allModels: {
        async softDelete<M, A>(
          this: M,
          where: Prisma.Args<M, 'update'>['where'],
        ): Promise<Prisma.Result<M, A, 'update'>> {
          const context = Prisma.getExtensionContext(this)

          // eslint-disable-next-line @typescript-eslint/no-explicit-any -- There is no way to type a Prisma model
          return (context as any).update({
            where,
            data: {
              deletedAt: new Date(),
            },
          })
        },
        async isDeleted<M>(this: M, where: Prisma.Args<M, 'findUnique'>['where']): Promise<boolean> {
          const context = Prisma.getExtensionContext(this)

          // eslint-disable-next-line @typescript-eslint/no-explicit-any -- There is no way to type a Prisma model
          const result = await (context as any).findUnique({ where })

          return !!result.deletedAt
        },
      },
    },
  })

@Injectable()
export class DatabaseService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  readonly extendedClient = prismaExtendedClient(this)

  constructor() {
    super()

    new Proxy(this, {
      get: (target, property) => Reflect.get(property in this.extendedClient ? this.extendedClient : target, property),
    })
  }

  async onModuleInit() {
    await this.$connect()
  }

  async onModuleDestroy() {
    await this.$disconnect()
  }
}

Now, I can use the extended methods like this:
await this.databaseService.extendedClient.user.softDelete({ id })

And, I also can use like the normal way:
return await this.databaseService.user.findFirstOrThrow({
    where: { id },
})

does this still work? I've just tried it with Prisma 5.6.0 and Prisma client 5.6.0 and it hangs at Prisma.getExtensionContext()

yeah this works completely fine!

Solution without Proxy.
// prisma.extension.ts
import { PrismaClient } from "@prisma/client";
import { pagination } from "prisma-extension-pagination";

export type PrismaClientExtended = ReturnType<typeof prismaClientExtended>;

export const prismaClientExtended = (prismaClient: PrismaClient) => {
  return prismaClient.$extends(pagination());
};

// prisma.service.ts
import { Injectable } from "@nestjs/common";
import { PrismaClient } from "@prisma/client";
import {
  PrismaClientExtended,
  prismaClientExtended,
} from "./prisma.extension.js";

const PrismaClientExtension = PrismaClient as unknown as new (
  options?: ConstructorParameters<typeof PrismaClient>[0],
) => PrismaClientExtended;

@Injectable()
export class PrismaService extends PrismaClientExtension {
  constructor() {
    super();
    return prismaClientExtended(this as unknown as PrismaClient) as this;
  }

  async onModuleInit() {
    await this.$connect();
  }

  async onModuleDestroy() {
    await this.$disconnect();
  }
}

Just wanted to follow up on my earlier reply with some improvements. That solution wasn't leveraging Prisma's built-in extension helpers, and it relied on some pretty ugly manual typing that very quickly became a pain to manage.
This modified approach provides all the same benefits, but leans more heavily on Prisma's built-in types and requires much less boilerplate:
// src/prisma/extensions/exists.extension.ts

import { Prisma } from "@prisma/client";

export const existsExtension = Prisma.defineExtension({
  name: "exists",
  model: {
    $allModels: {
      async exists<TModel, TArgs extends Prisma.Args<TModel, "findUniqueOrThrow">>(
        this: TModel,
        args?: Prisma.Exact<TArgs, Prisma.Args<TModel, "findUniqueOrThrow">>
      ): Promise<boolean> {
        const context = Prisma.getExtensionContext(this);

        try {
          await (context as any).findUniqueOrThrow(args);
          return true;
        } catch {
          return false;
        }
      },
    },
  },
});

// src/prisma/extensions/find-many-count.extension.ts

import { Prisma } from "@prisma/client";

import type { FindManyAndCountResult } from "~types";

export const findManyAndCountExtension = Prisma.defineExtension((client) => {
  return client.$extends({
    name: "findManyAndCount",
    model: {
      $allModels: {
        async findManyAndCount<TModel, TArgs extends Prisma.Args<TModel, "findMany">>(
          this: TModel,
          args?: Prisma.Exact<TArgs, Prisma.Args<TModel, "findMany">>
        ): Promise<FindManyAndCountResult<Prisma.Result<TModel, TArgs, "findMany">>> {
          const context = Prisma.getExtensionContext(this);

          const [records, totalRecords] = await client.$transaction([
            (context as any).findMany(args),
            (context as any).count({ where: (args as any)?.where }),
          ]);

          const take = (args as any)?.take;
          let totalPages = totalRecords === 0 ? 0 : 1;

          if (take === 0) {
            totalPages = 0;
          } else if (typeof take === "number") {
            totalPages = Math.ceil(totalRecords / take);
          }

          return [records, totalRecords, totalPages];
        },
      },
    },
  });
});

// src/prisma/extended-client.ts

import { PrismaClient } from "@prisma/client";

import { existsExtension } from "./extensions/exists.extension";
import { findManyAndCountExtension } from "./extensions/find-many-count.extension";

function extendClient(base: PrismaClient) {
  // Add as many as you'd like - no ugly types required!
  return base.$extends(existsExtension).$extends(findManyAndCountExtension);
}

class UntypedExtendedClient extends PrismaClient {
  constructor(options?: ConstructorParameters<typeof PrismaClient>[0]) {
    super(options);

    return extendClient(this) as this;
  }
}

const ExtendedPrismaClient = UntypedExtendedClient as unknown as new (
  options?: ConstructorParameters<typeof PrismaClient>[0]
) => ReturnType<typeof extendClient>;

export { ExtendedPrismaClient };

// src/prisma/prisma.service.ts

import type { OnModuleInit, OnModuleDestroy } from "@nestjs/common";
import { Injectable } from "@nestjs/common";

import { ExtendedPrismaClient } from "./extended-client";

@Injectable()
export class PrismaService extends ExtendedPrismaClient implements OnModuleInit, OnModuleDestroy {
  async onModuleInit() {
    await this.$connect();
  }

  async onModuleDestroy() {
    await this.$disconnect();
  }
}

// src/prisma/prisma.module.ts

import { Global, Module } from "@nestjs/common";

import { PrismaService } from "./prisma.service";

@Global()
@Module({
  providers: [PrismaService],
  exports: [PrismaService],
})
export class PrismaModule {}

I'm using this method until there's a better one：
import { Injectable, OnModuleInit, Global } from '@nestjs/common';
import { Prisma, PrismaClient } from '@prisma/client';
import * as runtime from '@prisma/client/runtime/library';
import $Result = runtime.Types.Result;
@Global()
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  async onModuleInit() {
    await this.$connect();
  }
  constructor() {
    super({
      log: ['query', 'info'],
      errorFormat: 'colorless',
    });
  }
  findAndCount<T extends Prisma.ModelName>(tableName: T) {
    return async <
      K extends Prisma.TypeMap['model'][T]['operations']['findMany']['args'],
    >({
      take,
      page,
      ...props
    }: {
      take: number;
      page: number;
    } & K) => {
      const prismaClient: any = this[tableName];
      const [list, count]: [
        $Result.GetResult<Prisma.TypeMap['model'][T]['payload'], K, 'findMany'>,
        number,
      ] = await this.$transaction([
        prismaClient.findMany({
          ...props,
          skip: (page - 1) * take,
          take: take,
        }),
        prismaClient.count({ where: props.where }),
      ]);
      return {
        take,
        page,
        total: count,
        list,
      };
    };
  }
}

Usage for Nestjs
 const { take, page, count, list } = await this.prismaService.table( 'employee')({
      page: getEmployeeDto.page,
      take: getEmployeeDto.take,
});

Implemented an example of custom client integration for NestJS: https://github.com/dminglv/nestjs-prisma-postgres-cluster

Would be nice to solve this issue, given that's been open for more than a year.

I have issue; with custom extend method not founded when used nestjs with  Prisma code generated type

My simple type-safe solution (query helper as extension example, user service as usage example)
// prisma.module.ts
import { Global, Module } from '@nestjs/common';

import { PrismaQueryHelperService } from './query-helper.service';
import { PrismaClientProvider } from './client.provider';
import { PrismaService } from './prisma.service';

@Global()
@Module({
  providers: [
    PrismaQueryHelperService,
    PrismaClientProvider,
    PrismaService,
  ],
  exports: [PrismaService],
})
export class PrismaModule {}

// query-helper.service.ts
import { Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';

@Injectable()
export class PrismaQueryHelperService {
  //any logic

  existsExtension = Prisma.defineExtension({
    name: 'exists-extension',
    model: {
      $allModels: {
        async exists<T>(this: T, where: Prisma.Args<T, 'findFirst'>['where']): Promise<boolean> {
          const context = Prisma.getExtensionContext(this);
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          const result = await (context as any).findFirst({ where });
          return result !== null;
        },
      },
    },
  });
}

// client.provider.ts
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

import { CaslService } from '@/core/security';

import { PrismaQueryHelperService } from './query-helper.service';

@Injectable()
export class PrismaClientProvider extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  constructor(
    private readonly queryHelper: PrismaQueryHelperService,
    private readonly caslService: CaslService,
  ) {
    super({
      log: ['query'],
    });
  }

  withExtensions() {
    return this.$extends(this.queryHelper.existsExtension)
      .$extends(this.caslService.subjectExtension)
      .$extends(this.caslService.accessRecordExtension);
  }

  onModuleInit() {
    return this.$connect();
  }

  onModuleDestroy() {
    return this.onModuleInit();
  }
}

// prisma.service.ts
import { Injectable, Type } from '@nestjs/common';

import { PrismaClientProvider } from './client.provider';

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

@Injectable()
export class PrismaService extends buildPrismaService() {
  constructor(provider: PrismaClientProvider) {
    super(provider);
  }
}

// user.service.ts
import { Injectable } from '@nestjs/common';

import { PrismaService } from '@/core/prisma';

@Injectable()
export class UserService {
  readonly model;

  constructor(prisma: PrismaService) {
    this.model = prisma.user;
  }

  async exists(id: string): Promise<boolean> {
    return this.model.exists({ id });
  }

  // ...
}

I've been trying to play around with Client Extensions, but I got some issues with it (maybe somebody else have had this problem before).
Problem
Our project uses @nestjs/schedule package, and there seems to be an conflict via reflect-metadata package (by even registering the Schedule module in the App - ScheduleModule.forRoot()).
Once I call .$extends method for Prisma Client - the app starts to fail during the startup:
/project/node_modules/reflect-metadata/Reflect.js:354
                throw new TypeError();
                      ^
TypeError:
    at Reflect.getMetadata (/project/node_modules/reflect-metadata/Reflect.js:354:23)
    at Reflector.get (/project/node_modules/@nestjs/core/services/reflector.service.js:24:24)
    at SchedulerMetadataAccessor.getSchedulerType (/project/node_modules/@nestjs/schedule/dist/schedule-metadata.accessor.js:21:31)
    at ScheduleExplorer.lookupSchedulers (/project/node_modules/@nestjs/schedule/dist/schedule.explorer.js:46:48)
    at /project/node_modules/@nestjs/schedule/dist/schedule.explorer.js:40:24
    at MetadataScanner.scanFromPrototype (/project/node_modules/@nestjs/core/metadata-scanner.js:34:31)
    at /project/node_modules/@nestjs/schedule/dist/schedule.explorer.js:39:34
    at Array.forEach (<anonymous>)
    at ScheduleExplorer.explore (/project/node_modules/@nestjs/schedule/dist/schedule.explorer.js:34:26)
    at ScheduleExplorer.onModuleInit (/project/node_modules/@nestjs/schedule/dist/schedule.explorer.js:27:14)
^C

When I remove @nestjs/schedule - everything works as expected (well, except for the schedule itself). When I remove .$extends - everything works (except for Prisma Client extensions).
Does anyone have an idea about this?

nestjs - latest v10
prisma - latest 4.16.1
node v18.16

Reproduce
I've got a playground repo with the reproduced issue: https://github.com/alex-deity/test-nest-prisma (the main file is app.module.ts).

for anyone with similar issue
add .$extends({}) before next extends, like this:
.$extends({})
.$extends(
createSoftDeleteExtension{}
)

Hi, I don't know if it's the best implementation, but in my case it worked and it might help you.
first we create the customized PrismaClient
then we override PrismaClient's default values using Object.assign

My simple type-safe solution (query helper as extension example, user service as usage example)
// prisma.module.ts
import { Global, Module } from '@nestjs/common';

import { PrismaQueryHelperService } from './query-helper.service';
import { PrismaClientProvider } from './client.provider';
import { PrismaService } from './prisma.service';

@Global()
@Module({
  providers: [
    PrismaQueryHelperService,
    PrismaClientProvider,
    PrismaService,
  ],
  exports: [PrismaService],
})
export class PrismaModule {}

// query-helper.service.ts
import { Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';

@Injectable()
export class PrismaQueryHelperService {
  //any logic

  existsExtension = Prisma.defineExtension({
    name: 'exists-extension',
    model: {
      $allModels: {
        async exists<T>(this: T, where: Prisma.Args<T, 'findFirst'>['where']): Promise<boolean> {
          const context = Prisma.getExtensionContext(this);
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          const result = await (context as any).findFirst({ where });
          return result !== null;
        },
      },
    },
  });
}

// client.provider.ts
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

import { CaslService } from '@/core/security';

import { PrismaQueryHelperService } from './query-helper.service';

@Injectable()
export class PrismaClientProvider extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  constructor(
    private readonly queryHelper: PrismaQueryHelperService,
    private readonly caslService: CaslService,
  ) {
    super({
      log: ['query'],
    });
  }

  withExtensions() {
    return this.$extends(this.queryHelper.existsExtension)
      .$extends(this.caslService.subjectExtension)
      .$extends(this.caslService.accessRecordExtension);
  }

  onModuleInit() {
    return this.$connect();
  }

  onModuleDestroy() {
    return this.onModuleInit();
  }
}

// prisma.service.ts
import { Injectable, Type } from '@nestjs/common';

import { PrismaClientProvider } from './client.provider';

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

@Injectable()
export class PrismaService extends buildPrismaService() {
  constructor(provider: PrismaClientProvider) {
    super(provider);
  }
}

// user.service.ts
import { Injectable } from '@nestjs/common';

import { PrismaService } from '@/core/prisma';

@Injectable()
export class UserService {
  readonly model;

  constructor(prisma: PrismaService) {
    this.model = prisma.user;
  }

  async exists(id: string): Promise<boolean> {
    return this.model.exists({ id });
  }

  // ...
}

looks like a good approach, how would I use the read replica extension this way? kinda stuck with the types bit

@rohanrajpal we have implemented it like this:
// Prisma Client according to: https://github.com/prisma/prisma/issues/18628
import { Prisma, PrismaClient } from '@prisma/generated/data-warehouse';
import { INestApplication, Inject, Injectable, OnModuleDestroy, OnModuleInit, Type } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import prismaReadonlyExtension from './extensions/prisma-readonly.extension';
import prismaExistsFnExtension from './extensions/prisma-exists-fn.extension';
import cacheExtension from '@knaus94/prisma-extension-cache-manager';
import { Cache, CACHE_MANAGER } from '@nestjs/cache-manager';
import { ClsService } from 'nestjs-cls';

@Injectable()
export class PrismaDatawarehouseProvider extends PrismaClient implements OnModuleInit, OnModuleDestroy {
    constructor(
        readonly config: ConfigService,
        readonly cls: ClsService,
        @Inject(CACHE_MANAGER)
        private cacheManager: Cache,
    ) {
        super({
            datasources: {
                db: {
                    url: config.getOrThrow<string>('DATABASE_URL_DATAWAREHOUSE').toString(),
                },
            },
        });
    }

    withExtensions() {
        return this
            .$extends(cacheExtension({ cache: this.cacheManager, defaultTTL: 60 * 60 * 1000 }))
            .$extends(prismaReadonlyExtension)
            .$extends(prismaExistsFnExtension)
    }

    async onModuleInit() {
        // Uncomment this to establish a connection on startup, this is generally not necessary
        // https://www.prisma.io/docs/concepts/components/prisma-client/working-with-prismaclient/connection-management#connect
        // await Prisma.getExtensionContext(client).$connect();
    }
    async enableShutdownHooks(app: INestApplication) {
        async function waitForAppClose() {
            await app.close();
        }
        // https://prisma.io/docs/guides/upgrade-guides/upgrading-versions/upgrading-to-prisma-5#removal-of-the-beforeexit-hook-from-the-library-engine
        process.on('exit', waitForAppClose);
        process.on('beforeExit', waitForAppClose);
        process.on('SIGINT', waitForAppClose);
        process.on('SIGTERM', waitForAppClose);
        process.on('SIGUSR2', waitForAppClose);
    }

    onModuleDestroy() {
        return this.onModuleInit();
    }
}

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

@Injectable()
export class PrismaDatawarehouseService extends buildPrismaService() {
    constructor(private readonly provider: PrismaDatawarehouseProvider) {
        super(provider);
    }
}

@rohanrajpal we have implemented it like this:
// Prisma Client according to: https://github.com/prisma/prisma/issues/18628
import { Prisma, PrismaClient } from '@prisma/generated/data-warehouse';
import { INestApplication, Inject, Injectable, OnModuleDestroy, OnModuleInit, Type } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import prismaReadonlyExtension from './extensions/prisma-readonly.extension';
import prismaExistsFnExtension from './extensions/prisma-exists-fn.extension';
import cacheExtension from '@knaus94/prisma-extension-cache-manager';
import { Cache, CACHE_MANAGER } from '@nestjs/cache-manager';
import { ClsService } from 'nestjs-cls';

@Injectable()
export class PrismaDatawarehouseProvider extends PrismaClient implements OnModuleInit, OnModuleDestroy {
    constructor(
        readonly config: ConfigService,
        readonly cls: ClsService,
        @Inject(CACHE_MANAGER)
        private cacheManager: Cache,
    ) {
        super({
            datasources: {
                db: {
                    url: config.getOrThrow<string>('DATABASE_URL_DATAWAREHOUSE').toString(),
                },
            },
        });
    }

    withExtensions() {
        return this
            .$extends(cacheExtension({ cache: this.cacheManager, defaultTTL: 60 * 60 * 1000 }))
            .$extends(prismaReadonlyExtension)
            .$extends(prismaExistsFnExtension)
    }

    async onModuleInit() {
        // Uncomment this to establish a connection on startup, this is generally not necessary
        // https://www.prisma.io/docs/concepts/components/prisma-client/working-with-prismaclient/connection-management#connect
        // await Prisma.getExtensionContext(client).$connect();
    }
    async enableShutdownHooks(app: INestApplication) {
        async function waitForAppClose() {
            await app.close();
        }
        // https://prisma.io/docs/guides/upgrade-guides/upgrading-versions/upgrading-to-prisma-5#removal-of-the-beforeexit-hook-from-the-library-engine
        process.on('exit', waitForAppClose);
        process.on('beforeExit', waitForAppClose);
        process.on('SIGINT', waitForAppClose);
        process.on('SIGTERM', waitForAppClose);
        process.on('SIGUSR2', waitForAppClose);
    }

    onModuleDestroy() {
        return this.onModuleInit();
    }
}

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

@Injectable()
export class PrismaDatawarehouseService extends buildPrismaService() {
    constructor(private readonly provider: PrismaDatawarehouseProvider) {
        super(provider);
    }
}

thanks for the quick responxd @paulwer ! i dont see read replica in the code tho, finally implemented it like this
// client.provider.ts
import { EnvironmentValues } from<EMAIL_ADDRESS>import { PrismaClient } from '@/generated/client';
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

import { readReplicas } from '@prisma/extension-read-replicas';

@Injectable()
export class PrismaClientProvider extends PrismaClient implements OnModuleInit, OnModuleDestroy {
    constructor(private config: ConfigService) {
        super({
            // log: ['query'],
        });
    }

    withExtensions() {
        const dbUrl = this.config.get(EnvironmentValues.DATABASE_URL_REPLICA);
        const replicaClient = new PrismaClient({
            datasourceUrl: dbUrl,
            log: [{ level: 'query', emit: 'event' }],
        });

        // replicaClient.$on('query', (event) => console.log('Replica event', event));
        return this.$extends(readReplicas({ replicas: [replicaClient] }));
        // return this.$extends(readReplicas({ url: dbUrl }));
    }

    async onModuleInit() {
        await this.$connect();
    }

    onModuleDestroy() {
        return this.onModuleInit();
    }
}

Let’s be honest the best choice is to go with middlewares ($use) until Prisma does something to answer this issue.

I'm personally very happy with my solution using $extends: https://gist.github.com/gamedevsam/3930258b947679bc1bdb09d001105a67

Hi, I don't know if it's the best implementation, but in my case it worked and it might help you.
first we create the customized PrismaClient
export const customPrismaClient = new PrismaClient().$extends({
  query : {
    $allOperations: async ({ query, args }) => {
        console.log({args})
        return query(args);
    }
  }
});

then we override PrismaClient's default values using Object.assign
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  async onModuleInit() {
    Object.assign(this, customPrismaClient);
    await this.$connect();
  }
}

this way dont have custom typescript support but don't have difficult to build a custom type

This breaks transactions for us. After adding this, we're getting an error on each transaction attempt:
Transaction not found. Transaction ID is invalid, refers to an old closed transaction Prisma doesn't have information about anymore, or was obtained before disconnecting.

Hey, my solution earlier solves these issues. I am able to have type safety and all functionality of prisma in a Nest service, unfortunately this conversation is getting very long and there is no consensus correct response, but I like my solution:
import { Global, INestApplication, Injectable, Logger, Module, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import { Prisma, PrismaClient } from '@prisma/client';
import { ITXClientDenyList } from '@prisma/client/runtime/library';

const PRISMA_SQL_LOG_LEVEL = process.env.PRISMA_SQL_LOG_LEVEL;
const PRISMA_ORM_LOG_LEVEL = process.env.PRISMA_ORM_LOG_LEVEL;

export type PrismaTransaction = Omit<PrismaClient, ITXClientDenyList>;

const logger = new Logger('prisma');

export const logQueriesExtension = Prisma.defineExtension((client) => {
  return client.$extends({
    name: 'log_queries',
    query: PRISMA_ORM_LOG_LEVEL
      ? {
          $allModels: {
            async $allOperations({ operation, model, args, query }) {
              const start = performance.now();
              const result = await query(args);
              const time = performance.now() - start;
              logger[PRISMA_ORM_LOG_LEVEL](
                `${model}.${operation}(${JSON.stringify(args)}) => ${Array.isArray(result) ? result.length : !!result ? 1 : 0} result(s) in ${time.toFixed(2)}ms`.replaceAll(
                  /\\?"|"/g,
                  '',
                ),
              );
              return result;
            },
          },
        }
      : undefined,
  });
});

function extendClient(base: PrismaClient) {
  // Add as many as you'd like - no ugly types required!
  return base.$extends(logQueriesExtension); //.$extends(findManyAndCountExtension);
}

class UntypedExtendedClient extends PrismaClient {
  constructor(options?: ConstructorParameters<typeof PrismaClient>[0]) {
    super(options);
    if (PRISMA_SQL_LOG_LEVEL) {
      // @ts-expect-error: https://www.prisma.io/docs/orm/prisma-client/client-extensions#usage-of-on-and-use-with-extended-clients
      this.$on('query', ({ query, params }: Prisma.QueryEvent) => {
        let workingQuery = query;
        const paramsArray = JSON.parse(params);
        for (let i = 0; i < paramsArray.length; ++i) {
          workingQuery = workingQuery.replace(
            `$${i + 1}`,
            `'${typeof paramsArray[i] === 'object' ? JSON.stringify(paramsArray[i]) : paramsArray[i]}'`,
          );
        }
        logger[PRISMA_SQL_LOG_LEVEL](workingQuery);
      });
    }
    return extendClient(this) as this;
  }
}

const ExtendedPrismaClient = UntypedExtendedClient as unknown as new (
  options?: ConstructorParameters<typeof PrismaClient>[0],
) => PrismaClient & ReturnType<typeof extendClient>;

@Injectable()
export class PrismaService extends ExtendedPrismaClient implements OnModuleInit, OnModuleDestroy {
  constructor() {
    super(
      PRISMA_SQL_LOG_LEVEL
        ? {
            log: [
              {
                emit: 'event',
                level: 'query',
              },
            ],
          }
        : undefined,
    );
  }
  async onModuleInit() {
    // Uncomment this to establish a connection on startup, this is generally not necessary
    // https://www.prisma.io/docs/concepts/components/prisma-client/working-with-prismaclient/connection-management#connect
    // await this.$connect();
  }
  async onModuleDestroy() {
    await this.$disconnect();
  }
  async enableShutdownHooks(app: INestApplication) {
    async function waitForAppClose() {
      await app.close();
    }
    // https://prisma.io/docs/guides/upgrade-guides/upgrading-versions/upgrading-to-prisma-5#removal-of-the-beforeexit-hook-from-the-library-engine
    process.on('exit', waitForAppClose);
    process.on('beforeExit', waitForAppClose);
    process.on('SIGINT', waitForAppClose);
    process.on('SIGTERM', waitForAppClose);
    process.on('SIGUSR2', waitForAppClose);
  }
}

@Global()
@Module({
  exports: [PrismaService],
  providers: [PrismaService],
})
export class PrismaModule {}

This thread has gotten very long, and so unfortunately there doesn't seem to be a consensus around an ideal solution. With that said, I'm very happy with my solution, which allows me to keep full type safety, while also using a NestJS service as an extended prisma client:
import { Global, INestApplication, Injectable, Logger, Module, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import { Prisma, PrismaClient } from '@prisma/client';
import { ITXClientDenyList } from '@prisma/client/runtime/library';

export type PrismaTransaction = Omit<PrismaClient, ITXClientDenyList>;

const logger = new Logger('prisma');

const PRISMA_ORM_LOG_LEVEL = process.env.PRISMA_ORM_LOG_LEVEL;
const PRISMA_SQL_LOG_LEVEL = process.env.PRISMA_SQL_LOG_LEVEL;

export const logQueriesExtension = Prisma.defineExtension((client) => {
  return client.$extends({
    name: 'log_queries',
    query: PRISMA_ORM_LOG_LEVEL
      ? {
          $allModels: {
            async $allOperations({ operation, model, args, query }) {
              const start = performance.now();
              const result = await query(args);
              const time = performance.now() - start;
              logger[PRISMA_ORM_LOG_LEVEL](
                `${model}.${operation}(${JSON.stringify(args)}) => ${Array.isArray(result) ? result.length : !!result ? 1 : 0} result(s) in ${time.toFixed(2)}ms`.replaceAll(
                  /\\?"|"/g,
                  '',
                ),
              );
              return result;
            },
          },
        }
      : undefined,
  });
});

function extendClient(base: PrismaClient) {
  // Add as many as you'd like - no ugly types required!
  return base.$extends(logQueriesExtension); //.$extends(findManyAndCountExtension);
}

class UntypedExtendedClient extends PrismaClient {
  constructor(options?: ConstructorParameters<typeof PrismaClient>[0]) {
    super(options);
    if (PRISMA_SQL_LOG_LEVEL) {
      // @ts-expect-error: https://www.prisma.io/docs/orm/prisma-client/client-extensions#usage-of-on-and-use-with-extended-clients
      this.$on('query', ({ query, params }: Prisma.QueryEvent) => {
        let workingQuery = query;
        const paramsArray = JSON.parse(params);
        for (let i = 0; i < paramsArray.length; ++i) {
          workingQuery = workingQuery.replace(
            `$${i + 1}`,
            `'${typeof paramsArray[i] === 'object' ? JSON.stringify(paramsArray[i]) : paramsArray[i]}'`,
          );
        }
        logger[PRISMA_SQL_LOG_LEVEL](workingQuery);
      });
    }
    return extendClient(this) as this;
  }
}

const ExtendedPrismaClient = UntypedExtendedClient as unknown as new (
  options?: ConstructorParameters<typeof PrismaClient>[0],
) => PrismaClient & ReturnType<typeof extendClient>;

@Injectable()
export class PrismaService extends ExtendedPrismaClient implements OnModuleInit, OnModuleDestroy {
  constructor() {
    super(
      PRISMA_SQL_LOG_LEVEL
        ? {
            log: [
              {
                emit: 'event',
                level: 'query',
              },
            ],
          }
        : undefined,
    );
  }
  async onModuleInit() {
    // Uncomment this to establish a connection on startup, this is generally not necessary
    // https://www.prisma.io/docs/concepts/components/prisma-client/working-with-prismaclient/connection-management#connect
    // await this.$connect();
  }
  async onModuleDestroy() {
    await this.$disconnect();
  }
  async enableShutdownHooks(app: INestApplication) {
    async function waitForAppClose() {
      await app.close();
    }
    // https://prisma.io/docs/guides/upgrade-guides/upgrading-versions/upgrading-to-prisma-5#removal-of-the-beforeexit-hook-from-the-library-engine
    process.on('exit', waitForAppClose);
    process.on('beforeExit', waitForAppClose);
    process.on('SIGINT', waitForAppClose);
    process.on('SIGTERM', waitForAppClose);
    process.on('SIGUSR2', waitForAppClose);
  }
}

@Global()
@Module({
  exports: [PrismaService],
  providers: [PrismaService],
})
export class PrismaModule {}


Propagating labels already containing a subdomain prefix causes an error

Labels in Kubernetes conform to RFC 1123 (docs). This means that using the prefix pyrra.dev/ disables the option of propagating a label that already contains a subdomain prefix since this breaks the RFC constraints.
Could a possible option be to use pyrra.dev- or simply pyrra- as the prefix for propagating labels?

Doing more testing it seems that I was wrong about the naming of labels and should do more investigation before anyone spends time on this, I will go ahead and close the issue.


node/p2p: consider deprecating TCP usage

After #913 is merged. we will have both TCP and QUIC support, and we should consider dropping TCP support entirely.

I'd recommend not prioritizing this. It might be good to make this a post-launch concern even.

No objections.
Also note that this can be done in two simple stages:

Removing 2 loc from default config to prevent nodes listening on TCP by default

New nodes won't run it
Still able to connect to TCP nodes
Suggest removing TCP from configs to existing nodes

Removing transport support entirely from libp2p constructor

Once we know there is no TCP nodes running(e.g. via Crawler)


git-delete-merged-branches: 7.0.0 -> 7.2.0

Description of changes

Things done

Built on platform(s)

[ ] x86_64-linux
[ ] aarch64-linux
[ ] x86_64-darwin
[ ] aarch64-darwin

[ ] For non-Linux: Is sandbox = true set in nix.conf? (See Nix manual)
[ ] Tested, as applicable:

NixOS test(s) (look inside nixos/tests)
and/or package tests
or, for functions and "core" functionality, tests in lib/tests or pkgs/test
made sure NixOS tests are linked to the relevant packages

[ ] Tested compilation of all packages that depend on this change using nix-shell -p nixpkgs-review --run "nixpkgs-review rev HEAD". Note: all changes have to be committed, also see nixpkgs-review usage
[ ] Tested basic functionality of all binary files (usually in ./result/bin/)
22.11 Release Notes (or backporting 22.05 Release notes)

[ ] (Package updates) Added a release notes entry if the change is major or breaking
[ ] (Module updates) Added a release notes entry if the change is significant
[ ] (Module addition) Added a release notes entry if adding a new NixOS module
[ ] (Release notes changes) Ran nixos/doc/manual/md-to-db.sh to update generated release notes

[ ] Fits CONTRIBUTING.md.

Result of nixpkgs-review pr 187338 run on x86_64-darwin 1

1 package built:

git-delete-merged-branches


Separate About Page Design

Currently, the Home and About pages contain similar content, which may be redundant for users. Creating a distinct About page would enhance user experience by providing dedicated information about the platform, team, or mission.
Expected Outcome:
Improved user experience with unique and organized content on each page
If you’re open to this change, I’d be happy to work on designing and implementing it!
@ANSHIKA-26

we dont need an about page, we had one , didnt work out !

Ok, no problem.
On Fri, 25 Oct 2024 at 20:45, ANSHIKA-26 @.***> wrote:

we dont need an about page, we had one , didnt work out !
—
Reply to this email directly, view it on GitHub
https://github.com/ANSHIKA-26/WordWise/issues/1400#issuecomment-2438434462,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/A7CDYHLV37C5PY75BDKZJDDZ5J7UHAVCNFSM6AAAAABQTJLBEWVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDIMZYGQZTINBWGI
.
You are receiving this because you were mentioned.Message ID:
@.***>


issue(modal): the modal title and content is empty

app
export class App {

  isMenuCollapsed:boolean = false;
  viewContainerRef:ViewContainerRef;

  public constructor(
                private state:AppState,
                private imageLoader:BaImageLoaderService,
                private spinner:BaThemeSpinner,
                private config:BaThemeConfig,
                viewContainerRef:ViewContainerRef) {
    this.viewContainerRef = viewContainerRef;
    this.loadImages();

    this.state.subscribe('menu.isCollapsed', (isCollapsed) => {
      this.isMenuCollapsed = isCollapsed;
    });
  }

  public ngAfterViewInit():void {
    BaThemePreloader.load().then((values) => {
      this.spinner.hide();
    });
  }

  private loadImages():void {
    // register some loaders
    BaThemePreloader.registerLoader(this.imageLoader.load(layoutPaths.images.root + 'sky-bg.jpg'));
  }
}

modal.component
import { Component } from '@angular/core';
import {MODAL_DIRECTVES, BS_VIEW_PROVIDERS} from 'ng2-bootstrap/ng2-bootstrap';

@Component({
  selector: 'common-modals',
  directives: [MODAL_DIRECTVES],
  viewProviders:[BS_VIEW_PROVIDERS],
  template: require('./modals.html')
})

export class CommonModalComponent {

}

template
<button class="btn btn-primary" (click)="lgModal.show()">Large modal</button>

        <button type="button" class="close" (click)="lgModal.hide()" aria-label="Close">
          ×
        </button>

Large modal

        ...

@qq83387856 can you add some more information? Perhaps a screenshot or better, a plnkr.

viewContainerRef:ViewContainerRef;
this.viewContainerRef = viewContainerRef;
why you need this?

@vmironovs The modal needs access to the viewContainerRef of the RootComponent.

yes, need , but in my project i put only
constructor(private viewContainerRef: ViewContainerRef) {
      super();

ant it was enough

@vmironovs when you write constructor(private viewContainerRef: ViewContainerRef) {
it will convert it to private class property like this
private viewContainerRef: ViewContainerRef;
constructor(private viewContainerRef: ViewContainerRef) {
super();
this.viewContainerRef = viewContainerRef;
}

but I prefer explicit declaration :)

Just in case, right now modal component doesn't parse html inside of it
so the only possible issue is css
Are you using any custom bootstrap theme?

this wayis right, the reson js that font-color default white,so I can't see it .... So I changed the style.

I've spent hours troubleshooting this but had to settle on a horrible workaround.  I had the same issue where text wouldn't show, buttons and images would show.  I fixed it by doing the following right after this.model.show()
this.modal.element.nativeElement.firstElementChild.click();
The reason why I did this, is i noticed the text would always show after clicking the dialog.


[Question] Feature question: extract graph as of README

Description
I noticed there is a graph in the README file showing the Control Flow Graph (CFG) of the decompiled smart contract bytecode. Is there a built-in feature or method that extracts a graph?
Additional Context

README Location: README.md
Graph Type: splash.png

I've searched through existing issues and documentation but haven't found information about this functionality. Thank you for any guidance!

Hi @andreas-electi, there is no built-in method to extract a graph visualization since this is out of scope. However, for some tests we use graph snapshot tests. This is the one you can find the README.
You can find the code to build the graph here https://github.com/acuarica/evm/blob/402028ca8c3a33dbb8498f0200d9af2efbf4f792/test/utils/cfg.ts.
Let me know if this works for you.

Hey @andreas-electi, just pinging here to check whether you were able to solve the issue.
Just to add to the last comment https://github.com/acuarica/evm/issues/148#issuecomment-2539798280, I'm reluctant to include any specific visualization because it would need to highly customizable. Any graph visualization would be better off in a different library/project using the sevm API.

Thanks @acuarica - really appreciate the detailed response. I'll take a look at the code example you shared.