Gajus
Gajus

Reputation: 73808

How to extend TypeScript module without overriding it?

I want to extend fastify request object to include information about the session.

According to the documentation, this should be enough:

declare module "fastify" {
  interface Session {
    user_id: string
    other_key: your_prefer_type
    id?: number
  }
}

However, if I do it that way, then all my type imports start failing.

src/factories/createServer.ts:189:19 - error TS2349: This expression is not callable.
  Type 'typeof import("fastify")' has no call signatures.

189   const fastify = Fastify({

If I do something like this,

declare module "fastify" {
  import Fastify from 'fastify';

  interface Session {
    user_id: string
    other_key: your_prefer_type
    id?: number
  }

  export = Fastify;
}

Then the default import works, but all other imports break, e.g.

src/factories/createServer.ts:11:8 - error TS2305: Module '"fastify"' has no exported member 'FastifyRequest'.

11   type FastifyRequest,

What am I doing wrong here?

Upvotes: 2

Views: 2173

Answers (3)

stouch
stouch

Reputation: 33

In a custom-material.d.ts file :


import "@mui/material/Typography";

declare module '@mui/material/Typography' {
    interface TypographyPropsVariantOverrides {
        h1Bold: true;
    }
}

This extends TypographyPropsVariantOverrides on the actual module.

Upvotes: 1

Gajus
Gajus

Reputation: 73808

My mistake was that I added these declarations to fastify.d.ts file.

If this happens to you, just rename the file to something that is not the exact name of the module, e.g. my-fastify.d.ts.

Upvotes: 2

ghybs
ghybs

Reputation: 53205

There is a not so known difference between non-module / global script declaration, and a classic module:

The JavaScript specification declares that any JavaScript files without an export or top-level await should be considered a script and not a module.

Inside a script file variables and types are declared to be in the shared global scope

What very probably happens in your first attempt, is that you placed the declaration in a file with no import/export statement. Therefore TS considered it as a global declaration, which overwrites the default export of "fastify" module.

// /src/globalDeclaration.d.ts
// NO top-level import/export
// Global declaration type
declare module "fastify" {
  interface Session {
    user_id: string;
    other_key: boolean; // your_prefer_type
    id?: number;
  }
}

// /src/index.ts
import Fastify from "fastify";

const fastify = Fastify(); // Error: Type 'typeof import("fastify")' has no call signatures.ts(2349)

Demo on CodeSandbox: https://codesandbox.io/s/peaceful-resonance-fc1swm?file=/src/index.ts


To fix the issue, then as suggested in the TS docs:

If you have a file that doesn’t currently have any imports or exports, but you want to be treated as a module, add the line:

export {};

which will change the file to be a module exporting nothing.

So let's add the empty export (could be anywhere in the file) to turn the declaration into a module augmentation only.

But then make sure to explicitly import the file, even if there is nothing to "import from" it: we just want TS to perform the side effect of augmenting the module.

// /src/moduleAugmentation.d.ts
export {}; // Make an import or export to turn the file into a module augmentation only

declare module "fastify" {
  interface Session {
    user_id: string;
    other_key: boolean; // your_prefer_type
    id?: number;
  }
}

// /src/index.ts
import Fastify, { FastifyRequest } from "fastify";
import fastifySession from "@fastify/session";
import fastifyCookie from "@fastify/cookie";

import "./moduleAugmentation"; // Now we have to explicitly import the module augmentation file, even just for its side effect

const fastify = Fastify();
fastify.register(fastifyCookie);
fastify.register(fastifySession, {
  secret: "a secret with minimum length of 32 characters"
});

fastify.addHook("preHandler", (request, reply, next) => {
  const b = request.session.other_key;
  //                        ^? (property) Session.other_key: boolean
  next();
});

Demo on CodeSandbox: https://codesandbox.io/s/inspiring-phoebe-ly086x?file=/src/index.ts

Upvotes: 10

Related Questions