← Back to list

TypeScript: Enhance Variable Types with Declaration Merging

This story is about an often applied technique by libraries that offer full TypeScript support. Although as a consumer of these things…

applied.math.coding in Bits and Pieces · 2022-01-13 03:22 · 98 claps · 3.5 min read paywalled
#typescript #javascript #declaration-merging #web-development #software-development
Open on Medium ↗
Wiki topics: 🌐 · Web Development

TypeScript: Enhance Variable Types with Declaration Merging

[embed]Join Medium with my referral link - applied.math.coding Get access to all of my stories and to thousands more on Medium from other writers. As my strong believe is, Medium is…medium.com

This story is about an often applied technique by libraries that offer full TypeScript support. Although as a consumer of these things, one most often does not have to implement such techniques — everything just works like magic. Nevertheless, it is always good to have a basic understanding of what is going on in the hidden layer especially in times where things go wrong.

One of TypeScript’s features is the so called declaration merging (see here). Applied on an interface, this in principle is merging two declarations of the same interface:

interface TheInterface{
  a: string;
}
interface TheInterface{
  b: boolean;
}
let t: TheInterface = {
  a: 'a',
  b: true
};

Typically, when we use external libraries that come with their own type declarations it sometimes is necessary to extend a type with some custom fields.

For instance, let us consider a session store:

interface Session{}

As a consumer we want to store custom properties like authToken, socketId, ... in a user’s session but of course the library doesn’t cope for these fields.

We could use the as any trick and hence making the compiler allowing us to store any values on the session:

(session as any).authToken = ...

Obviously, this is not desirable since we loose compiler checks. Somebody could by accident write (session as any).authTypoToken = ... and no one would recognize this until a runtime error appears.

So, let us assume we are using some external library which types are imported like this:

import { Session } from 'path-to-extern-module'

In case Session is an interface we can enhance its type by adding the following to our source code:

declare module 'path-to-extern-module'{
  interface Session{
    authToken: string;
    socketId: number;
  }
}

Alternatively, and this is the cleaner approach, we can store this in a xxx.d.ts file (type declarations) and put it into our source folder.

Let us consider all this in more detail by looking at the following example:

Suppose we have a declaration file named some-module.d.ts:

export interface CustomTypes { };
export type Session =
  CustomTypes extends {session: infer T} ? T : unknown;
export function fn(clb: (sess: Session) => void);

This intends to ‘simulate’ an external library. If you don’t know about the infer keyword you can find basic information here. The ternary in this code is like pattern matching. CustomTypes is checked to match a type that extends the type {session: infer T}. The infer T is just a placeholder for the type of session and assigns the latter to the generic T. People coming from logic programming will appreciate this pattern.

Having only this code, Session obviously would turn out to be the type unknown. But if someone, extends CustomTypes by using declaration merging, things can change:

In our source code we could import these types and use them like so:

import { Session, fn } from './some-module';
interface MySession {
  authToken: string;
  socketId: number;
}
declare module './some-module' {
  interface CustomTypes {
    session: MySession;
  }
}
fn(sess => console.log(sess.authToken)); // compiles!

This way sess would have the type MySession, since the declaration of CustomerTypes has been merged.

If we would leave away the declaration merging, that is:

import { fn } from './some-module';
fn(sess => console.log(sess.authToken)); // error!

Then this resulted in a compilation error since the variable sess would be of type unknown.

Conclusion

There you have it, we’ve answered the age-old question of whether APIs and Microservices were the same things.

As it turns out, all we had to do was to carefully look at the definition of each one. In the end, they’re not the same, but they work incredibly well together.

What do you think? Do you still have questions? Leave them in the comments!

Build composable web applications

Don’t build web monoliths. Use Bit to create and compose decoupled software components — in your favorite frameworks like React or Node. Build scalable frontends and backends with a powerful and enjoyable dev experience.

Bring your team to Bit Cloud to host and collaborate on components together, and greatly speed up, scale, and standardize development as a team. Start with composable frontends like a Design System or Micro Frontends, or explore the composable backend. **Give it a try →**

Learn More

[embed]How We Build Micro Frontends Building micro-frontends to speed up and scale our web development process.blog.bitsrc.io

[embed]How we Build a Component Design System Building a design system with components to standardize and scale our UI development process.blog.bitsrc.io

[embed]The Composable Enterprise: A Guide To deliver in 2022, the modern enterprise must become composable.blog.bitsrc.io

[embed]7 Tools for Faster Frontend Development in 2022 Tools you should know to build modern Frontend applications faster, and have more fun.blog.bitsrc.io


메타데이터
post_id
9f0ce0af309d
slug
enhancing-the-type-of-a-variable-declaration-merging-9f0ce0af309d
url
https://medium.com/bitsrc/enhancing-the-type-of-a-variable-declaration-merging-9f0ce0af309d
canonical_url
https://medium.com/bitsrc/enhancing-the-type-of-a-variable-declaration-merging-9f0ce0af309d
author_url
https://medium.com/@applied-math-coding
status
ok
fetched_at
2026-08-04 05:53:42