fastify / help

Need help with Fastify? File an Issue here.
https://www.fastify.io/
65 stars 8 forks source link

Help Wanted: Generic types when registering a plugin #743

Open sabinadams opened 2 years ago

sabinadams commented 2 years ago

What are you trying to achieve, or the steps to reproduce?

I'm trying to create a plugin to register a new function on the Fastify instance which will hold an instance of Prisma

// plugin.ts

import { FastifyPluginAsync, FastifyPluginOptions } from "fastify";
import fp from "fastify-plugin";

declare module "fastify" {
  interface FastifyInstance {
    prisma; // Should be the type of the instantiated `PrismaClient`
  }
}

interface PrismaPluginOptions extends FastifyPluginOptions {
  client; // Should be the type of the `client` key's value passed via `options`
}

const prismaPlugin: FastifyPluginAsync<PrismaPluginOptions> = async (
  fastify,
  options
) => {
  const PrismaClient = options.client;
  if (!PrismaClient) {
    throw new Error(
      "`client` parameter is required. Please provide a generated PrismaClient class."
    );
  }

  if (!fastify.prisma) {
    const prisma = new PrismaClient();
    fastify.decorate("prisma", prisma);
    fastify.addHook("onClose", async (server) => {
      await server.prisma.$disconnect();
    });
  }
};

export default fp(prismaPlugin, "3.x");

The goal is to be able to register the plugin and provide the PrismaClient class via a client key. Within the actual server's code, I'd expect fastify.prisma to be of the type of the instantiated class PrismaClient.

// server.ts

import Fastify from "fastify";
import prismaPlugin from "../index";
import { PrismaClient } from "@prisma/client";

const fastify = Fastify();

fastify.register(prismaPlugin, {
  client: PrismaClient,
});

fastify.get("/", async (request, reply) => {
  const data = fastify.prisma // Should be of the type `PrismaClient`
  return { count: 1 };
});

fastify.listen({ port: 3000 });

What was the result you received?

I'm having trouble figuring out how to use generic types to ensure fastify.prisma is the type of the provided class.

What did you expect?

After registering the plugin and providing the class via the client option, I'd want the data variable here to be of the type PrismaClient:

fastify.get("/", async (request, reply) => {
  const data = fastify.prisma
 // ...
});

The result should be the same as if I had done:

declare module "fastify" {
  interface FastifyInstance {
    prisma: PrismaClient;
  }
}

fastify.decorate("prisma", new PrismaClient());

fastify.get("/", async (request, reply) => {
  const data = fastify.prisma; // type: PrismaClient
  return { count: 1 };
});

Context

shiftyp commented 2 years ago

Merged declarations must have the same type parameters, so I can't think of a way you can add a parameter to an existing type without extending the interface to create a new one. The best pattern I can come up with is do the plugin registration within a function you export, that returns a new interface which extends FastifyInstance and includes the generic parameter:

interface PrismaFastifyInstance<Client> extends FastifyInstance {
  prisma: Client
}

export const register = <Client>(fastify: FastifyInstance, client: Client) : PrismaFastifyInstance<Client> => {
  fastify.register(prismaPlugin, {
    client,
 })
 return fastify
}