Is Declaration Merging and Generic Inheritance at the Same Time Impossible?
Tags: typescript, generics, node, fastify
Published: 2019-12-24 18:58:05

---
title: Is Declaration Merging and Generic Inheritance at the Same Time Impossible?
published: true
description: Spoiler: No it is not impossible! This article analyzes a niche, advanced TypeScript pattern of combining declaration merging and generic inheritance.
tags: typescript, generics, node.js, fastify
---

[Fastify](https://fastify.io) is a fast and low overhead web framework for [Node.js](https://nodejs.org). It supports `http`, `https`, and `http2` server types. The Fastify v3 type system provides a generic property so the user can specify which type of server they want to create; this generic property is then used to determine the type of the server `request` and `reply` objects. 

> The following definitions are a simplified abstraction from the actual Fastify v3 type system. You can explore the complete source code [here](https://github.com/fastify/fastify/blob/next/fastify.d.ts)

The start of the definition defines the list of types the server generic can be, as well as the main function `fastify`.

```typescript
import http from 'http'
import https from 'http'
import http2 from 'http2'

type ServerTypes = http.Server | https.Server | http2.Http2Server

declare function fastify<Server extends ServerTypes>(): FastifyInstance<Server>
```

The `FastifyInstance` definition depends on two generic expressions, one for determining the base `request` type, and the other for `reply`.

```typescript
type RequestExpression<Server extends ServerTypes> = (
  Server extends http.Server | https.Server
  ? http.IncomingMessage
  : http2.Http2ServerRequest 
)

type ReplyExpression<Server extends ServerTypes> = (
  Server extends http.Server | https.Server
  ? http.ServerResponse
  : http2.Http2ServerResponse
)

interface FastifyInstance<
  Server extends ServerTypes,
  Request = RequestExpression<Server>,
  Reply = ReplyExpression<Server>
> {
  request: FastifyRequest<Request>,
  reply: FastifyReply<Reply>
}

```

These `Request` and `Reply` generics are then passed to the  `FastifyRequest` and `FastifyReply` definitions. These make use of **generic inheritance** to add additional properties to the base request and reply types. 

```typescript
type FastifyRequest<Request> = Request & {
  body: unknown,
  query: unknown
}

type FastifyReply<Reply> = Reply & {
  sent: boolean,
  code(c: number): FastifyReply<Reply>
}
```

Fastify supports [plugins](https://www.fastify.io/docs/latest/Plugins/) for decorating the `server`, `request`, and `reply` instances with additional properties from the user. 

```typescript
function myPlugin (inst, opts, next) {
  inst.decorateRequest('myPluginProp', 'super_secret_string')
  inst.decorareReply('myPluginProp', 5000)
}
```

But how can we update the type system to acknowledge these new properties? Additionally, to support module based plugins (i.e. downloadable from npm), the type override should _just work_ by importing the plugin into a project (i.e. `import myPlugin from 'myPlugin'`). We can try to use **declaration merging** on the `FastifyRequest` and `FastifyReply` types:

```typescript
declare module 'fastify' {
  type FastifyRequest = {
    myPluginProp: string
  }
  type FastifyReply = {
    myPluginProp: number
  }
}
```

Sadly this does not work; you cannot merge type declarations. What if we try rewriting the type declarations as interfaces?

```typescript
interface FastifyRequest<Request> extends Request {
  raw: Request;
  body: unknown;
  query: unknown;
}

interface FastifyReply<Reply> extends Reply {
  raw: Reply;
  sent: boolean;
  code(c: number): FastifyReply<Reply>;
}
```

Sadly, this also does not work; it throws an error:

> An interface can only extend an object type or intersection of object types with statically known members.

If we use types, then we can't support declaration merging. And if we use interfaces, then we can't support generic inheritance. 🤔 

💡 What if we use _both_ types and interfaces?

1. Encapsulate the custom properties into interfaces

```typescript
interface FastifyRequestInterface<Request> {
  raw: Request;
  body: unknown;
  query: unknown;
}

interface FastifyReplyInterface<Reply> {
  raw: Reply;
  sent: boolean;
  code(c: number): FastifyReply<Reply>;
}
```

2. Replace the `{ ... }` part of the type declaration with these new interfaces

```typescript
type FastifyRequest<Request> = Request & FastifyRequestInterface<Request>

type FastifyReply<Reply> = Reply & FastifyReplyInterface<Reply>
```

Now, if the user wants to add custom properties too this they can use declaration merging on the `FastifyRequestInterface` and `FastifyReplyInterface` interfaces, and the type declarations can still inherit from the generic parameters! 

```typescript
declare module 'fastify' {
  interface FastifyRequestInterface {
    myPluginProp: string
  }
  interface FastifyReplyInterface {
    myPluginProp: number
  }
}
```

And in some implementation file:
```typescript
import fastify from 'fastify'
import myPlugin from 'myPlugin'

const server = fastify()

server.register(myPlugin)

server.get('/', (request, reply) => {
  request.myPluginProp // -> ✅ string
  reply.myPluginProp // -> ✅ number
})
```

🎉 Thus, **no it is not impossible** to support both declaration merging and generic inheritance at the same time!

--

Thank you for reading! If you enjoyed this article consider following myself on Twitter [@ArrowoodTech](https://twitter.com/ArrowoodTech).

To learn more about Fastify checkout our [GitHub repository](https://github.com/fastify/fastify) or our [website](https://www.fastify.io/).