Documentation Index Fetch the complete documentation index at: https://mintlify.com/typescript-exercises/typescript-exercises/llms.txt
Use this file to discover all available pages before exploring further.
Exercise 13: Augmenting Third-Party Module Types
Scenario
You’re using the date-wizard library to format dates. The library comes with type declarations, but they’re incomplete - missing time-related fields and some exported functions.
The Problem
The existing declaration file is incomplete:
// node_modules/date-wizard/index.d.ts (existing, incomplete)
declare module 'date-wizard' {
interface DateDetails {
year : number ;
month : number ;
date : number ;
// ❌ Missing: hours, minutes, seconds
}
function dateWizard ( date : Date , format : string ) : string ;
function dateDetails ( date : Date ) : DateDetails ;
// ❌ Missing: pad function
}
Using the missing features causes errors:
import * as dateWizard from 'date-wizard' ;
function logPerson ( person : Person , index : number ) {
let registeredAt = dateWizard (
person . registered ,
'{date}.{month}.{year} {hours}:{minutes}' // Using hours/minutes
);
let num = `# ${ dateWizard . pad ( index + 1 ) } ` ; // ❌ Error: 'pad' doesn't exist
console . log ( ` ${ num } : ${ person . name } , ${ registeredAt } ` );
}
console . log ( 'Early birds:' );
persons
. filter (( person ) =>
dateWizard . dateDetails ( person . registered ). hours < 10 // ❌ Error: 'hours' doesn't exist
)
. forEach ( logPerson );
You cannot modify the original declaration file in node_modules - it would be overwritten on the next install. You need module augmentation.
Key Concepts
Module Augmentation Extend existing module declarations without modifying the original files
Declaration Merging TypeScript merges multiple declarations of the same interface/module
Understanding Module Augmentation
Module augmentation allows you to add to existing type declarations:
// Your augmentation file
import 'date-wizard' ; // This enables augmentation mode
declare module 'date-wizard' {
// Add missing declarations
// TypeScript merges this with the original
}
The import statement at the top is crucial - it tells TypeScript you’re augmenting an existing module, not declaring a new one.
Solution
Create an augmentation file at module-augmentations/date-wizard/index.ts:
// module-augmentations/date-wizard/index.ts
import 'date-wizard' ;
declare module 'date-wizard' {
// Add your module extensions here.
}
// module-augmentations/date-wizard/index.ts
import 'date-wizard' ;
declare module 'date-wizard' {
const pad : ( ident : number ) => string ;
interface DateDetails {
hours : number ;
minutes : number ;
seconds : number ;
}
}
How Declaration Merging Works
Original Declaration
// node_modules/date-wizard/index.d.ts
interface DateDetails {
year : number ;
month : number ;
date : number ;
}
Your Augmentation
// module-augmentations/date-wizard/index.ts
interface DateDetails {
hours : number ;
minutes : number ;
seconds : number ;
}
Merged Result
// TypeScript sees
interface DateDetails {
year : number ;
month : number ;
date : number ;
hours : number ; // Added
minutes : number ; // Added
seconds : number ; // Added
}
Using the Augmented Module
Now all features work with full type safety:
import * as dateWizard from 'date-wizard' ;
import './module-augmentations/date-wizard' ; // Load augmentations
interface User {
type : 'user' ;
name : string ;
age : number ;
occupation : string ;
registered : Date ;
}
const users : User [] = [
{
type: 'user' ,
name: 'Max Mustermann' ,
age: 25 ,
occupation: 'Chimney sweep' ,
registered: new Date ( '2016-02-15T09:25:13' )
},
{
type: 'user' ,
name: 'Kate Müller' ,
age: 23 ,
occupation: 'Astronaut' ,
registered: new Date ( '2016-03-23T12:47:03' )
}
];
function logPerson ( person : User , index : number ) {
let registeredAt = dateWizard (
person . registered ,
'{date}.{month}.{year} {hours}:{minutes}' // ✅ OK
);
let num = `# ${ dateWizard . pad ( index + 1 ) } ` ; // ✅ OK
console . log ( ` ${ num } : ${ person . name } , ${ person . age } , ${ person . occupation } , ${ registeredAt } ` );
}
console . log ( 'All users:' );
users . forEach ( logPerson );
// Output:
// #01: Max Mustermann, 25, Chimney sweep, 15.02.2016 09:25
// #02: Kate Müller, 23, Astronaut, 23.03.2016 12:47
console . log ( 'Early birds:' );
users
. filter (( person ) =>
dateWizard . dateDetails ( person . registered ). hours < 10 // ✅ OK
)
. forEach ( logPerson );
// Output:
// #01: Max Mustermann, 25, Chimney sweep, 15.02.2016 09:25
When to Use Module Augmentation
Incomplete Types Library has declarations but they’re missing features
Plugin Systems You’ve added plugins that extend the base library
Monkey Patching You’ve extended a library’s prototype at runtime
Global Augmentations Adding to global objects like Window or Array
Common Augmentation Patterns
Add properties to existing interfaces: import 'express' ;
declare module 'express' {
interface Request {
user ?: User ; // Add user property to Express Request
}
}
// Usage
app . get ( '/profile' , ( req , res ) => {
const user = req . user ; // ✅ TypeScript knows about user
});
Add missing functions or constants: import 'my-lib' ;
declare module 'my-lib' {
export function newFunction ( x : number ) : string ;
export const NEW_CONSTANT : string ;
}
Extend built-in globals: declare global {
interface Window {
myApp : {
version : string ;
init : () => void ;
};
}
}
// Usage
window . myApp . init (); // ✅ OK
Add methods to existing classes: import 'my-lib' ;
declare module 'my-lib' {
interface MyClass {
newMethod ( param : string ) : number ;
}
}
Best Practices
Don’t augment with conflicting types // Bad: Changing existing property type
interface DateDetails {
year : string ; // ❌ Was number, now string - breaks existing code
}
// Good: Only add new properties
interface DateDetails {
hours : number ; // ✅ New property
minutes : number ; // ✅ New property
}
Organize augmentations by module Create a dedicated directory for augmentations: module-augmentations/
express/
index.ts
date-wizard/
index.ts
lodash/
index.ts
Real-World Example: Express with Custom Properties
// augmentations/express/index.ts
import 'express' ;
import { User } from '../../types' ;
declare module 'express' {
interface Request {
user ?: User ;
requestId : string ;
}
interface Response {
sendSuccess < T >( data : T ) : void ;
sendError ( error : string , code : number ) : void ;
}
}
// Now you can use these in your Express app
import express from 'express' ;
import './augmentations/express' ;
const app = express ();
app . use (( req , res , next ) => {
req . requestId = generateId (); // ✅ OK
next ();
});
app . get ( '/user' , ( req , res ) => {
if ( ! req . user ) { // ✅ OK
res . sendError ( 'Not authenticated' , 401 ); // ✅ OK
return ;
}
res . sendSuccess ( req . user ); // ✅ OK
});
Troubleshooting
Augmentation not working?
Make sure you:
Import the original module: import 'module-name';
Import your augmentation file in your code
Include the augmentation file in your tsconfig.json
Getting duplicate identifier errors?
You might be declaring new properties instead of augmenting. Make sure:
You have the import statement
You’re using the same interface/type names as the original
Augmentation not visible in other files?
Augmentations are global - once loaded, they affect all imports of that module. Import your augmentation file in your entry point: // index.ts
import './module-augmentations/date-wizard' ;
import './module-augmentations/express' ;
What You Learned
Extend third-party module types without modifying their source code
TypeScript automatically merges multiple declarations of interfaces and modules
The import statement enables augmentation mode instead of creating a new module
Add type information for runtime extensions and monkey patches
Next Steps
Continue to Advanced Type Mapping to learn complex type transformations and manipulations.
Additional Resources