Dev Logs
/TypeScript/ Declaration Merging and Ambient Declarations
Chapters
  • 01Introduction to TypeScript
  • 02TypeScript Setup and Configuration
  • 03Basic Type Annotations
  • 04Interfaces and Type Aliases
  • 05Functions and Type Safety
  • 06Arrays, Tuples, and Enums
  • 07Union and Intersection Types
  • 08Type Inference and Narrowing
  • 09Optional and Readonly Properties
  • 10Classes and Object-Oriented Programming
  • 11Generics and Reusable Code
  • 12Type Guards and Advanced Type Checking
  • 13Advanced TypeScript Features
  • 14Modules and Namespaces
  • 15Error Handling in TypeScript
  • 16Utility Types and Type Manipulations
  • 17Declaration Merging and Ambient Declarations
    • Declaration Merging
    • Interface Merging
    • Namespace Merging
    • Module Augmentation
    • Third-Party Library Augmentation
    • Ambient Declarations
    • Basic Ambient Declarations
    • Environment-Specific Declarations
    • Creating Type Definition Files
    • Working with @types Packages
    • Installing and Using Type Definitions
    • Creating Custom @types Packages
    • Best Practices
    • Good Practices
    • Avoid
    • Summary Checklist
    • Next Steps
All chapters

Declaration Merging and Ambient Declarations

Learn how to extend existing types, work with third-party libraries, and create ambient declarations for JavaScript code

Declaration Merging

Declaration merging allows TypeScript to combine multiple declarations with the same name into a single definition.

Interface Merging

typescript
// Basic interface merging
interface User {
  id: number;
  name: string;
}

interface User {
  email: string;
  age: number;
}

// Merged interface has all properties
const user: User = {
  id: 1,
  name: "John Doe",
  email: "john@example.com",
  age: 30,
};

// Interface merging with methods
interface Calculator {
  add(a: number, b: number): number;
}

interface Calculator {
  subtract(a: number, b: number): number;
  multiply(a: number, b: number): number;
}

interface Calculator {
  divide(a: number, b: number): number;
}

// All methods are available
const calc: Calculator = {
  add: (a, b) => a + b,
  subtract: (a, b) => a - b,
  multiply: (a, b) => a * b,
  divide: (a, b) => a / b,
};

// Interface merging with generics
interface Container<T> {
  value: T;
}

interface Container<T> {
  getValue(): T;
  setValue(value: T): void;
}

const stringContainer: Container<string> = {
  value: "hello",
  getValue() {
    return this.value;
  },
  setValue(value) {
    this.value = value;
  },
};

// Interface merging with function overloads
interface EventEmitter {
  on(event: "data", listener: (data: string) => void): void;
}

interface EventEmitter {
  on(event: "error", listener: (error: Error) => void): void;
}

interface EventEmitter {
  on(event: "close", listener: () => void): void;
}

// All overloads are merged
const emitter: EventEmitter = {
  on(event: any, listener: any) {
    // Implementation
  },
};

emitter.on("data", (data) => console.log(data)); // data is string
emitter.on("error", (error) => console.error(error)); // error is Error
emitter.on("close", () => console.log("closed")); // no parameters

Namespace Merging

typescript
// Basic namespace merging
namespace Utils {
  export function formatDate(date: Date): string {
    return date.toISOString().split("T")[0];
  }
}

namespace Utils {
  export function formatTime(date: Date): string {
    return date.toTimeString().split(" ")[0];
  }
}

namespace Utils {
  export interface Config {
    dateFormat: string;
    timeFormat: string;
  }

  export const defaultConfig: Config = {
    dateFormat: "YYYY-MM-DD",
    timeFormat: "HH:mm:ss",
  };
}

// All exports are available
const formattedDate = Utils.formatDate(new Date());
const formattedTime = Utils.formatTime(new Date());
const config = Utils.defaultConfig;

// Namespace merging with classes
namespace Database {
  export class Connection {
    constructor(public connectionString: string) {}

    connect(): void {
      console.log("Connecting to database...");
    }
  }
}

namespace Database {
  export interface ConnectionOptions {
    ssl: boolean;
    timeout: number;
    retries: number;
  }

  export function createConnection(
    connectionString: string,
    options?: ConnectionOptions
  ): Connection {
    return new Connection(connectionString);
  }
}

// Both class and function are available
const connection = Database.createConnection(
  "postgresql://localhost:5432/mydb"
);
const directConnection = new Database.Connection(
  "postgresql://localhost:5432/mydb"
);

Module Augmentation

typescript
// Augmenting existing modules

// global.d.ts - Augmenting global scope
declare global {
  interface Window {
    myApp: {
      version: string;
      config: Record<string, any>;
      utils: {
        formatCurrency(amount: number): string;
        debounce<T extends (...args: any[]) => any>(fn: T, delay: number): T;
      };
    };
  }

  interface Array<T> {
    last(): T | undefined;
    first(): T | undefined;
    isEmpty(): boolean;
  }

  interface String {
    toTitleCase(): string;
    truncate(length: number, suffix?: string): string;
  }

  interface Number {
    toPercent(decimals?: number): string;
    toCurrency(currency?: string): string;
  }
}

// Implementation (would be in a separate .ts file)
if (typeof window !== "undefined") {
  window.myApp = {
    version: "1.0.0",
    config: {},
    utils: {
      formatCurrency: (amount: number) => `$${amount.toFixed(2)}`,
      debounce: <T extends (...args: any[]) => any>(
        fn: T,
        delay: number
      ): T => {
        let timeoutId: NodeJS.Timeout;
        return ((...args: any[]) => {
          clearTimeout(timeoutId);
          timeoutId = setTimeout(() => fn(...args), delay);
        }) as T;
      },
    },
  };
}

Array.prototype.last = function <T>(this: T[]): T | undefined {
  return this[this.length - 1];
};

Array.prototype.first = function <T>(this: T[]): T | undefined {
  return this[0];
};

Array.prototype.isEmpty = function <T>(this: T[]): boolean {
  return this.length === 0;
};

String.prototype.toTitleCase = function (this: string): string {
  return this.replace(
    /\w\S*/g,
    (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()
  );
};

String.prototype.truncate = function (
  this: string,
  length: number,
  suffix = "..."
): string {
  return this.length > length ? this.substring(0, length) + suffix : this;
};

Number.prototype.toPercent = function (this: number, decimals = 2): string {
  return `${(this * 100).toFixed(decimals)}%`;
};

Number.prototype.toCurrency = function (
  this: number,
  currency = "USD"
): string {
  return new Intl.NumberFormat("en-US", {
    style: "currency",
    currency,
  }).format(this);
};

// Usage
const numbers = [1, 2, 3, 4, 5];
console.log(numbers.first()); // 1
console.log(numbers.last()); // 5
console.log([].isEmpty()); // true

const text = "hello world";
console.log(text.toTitleCase()); // 'Hello World'
console.log(text.truncate(5)); // 'hello...'

const value = 0.1234;
console.log(value.toPercent()); // '12.34%'
console.log(value.toCurrency()); // '$0.12'

Third-Party Library Augmentation

typescript
// Augmenting Express.js
import { User } from "./types/user";

declare module "express-serve-static-core" {
  interface Request {
    user?: User;
    requestId: string;
    startTime: number;
    correlationId?: string;
  }

  interface Response {
    success(data?: any): Response;
    error(message: string, code?: number): Response;
  }
}

// Implementation (middleware)
import express from "express";

const app = express();

// Add custom methods to Response
app.use((req, res, next) => {
  res.success = function (data?: any) {
    return this.json({
      success: true,
      data,
      timestamp: new Date().toISOString(),
      requestId: req.requestId,
    });
  };

  res.error = function (message: string, code = 500) {
    return this.status(code).json({
      success: false,
      error: { message },
      timestamp: new Date().toISOString(),
      requestId: req.requestId,
    });
  };

  next();
});

// Add request tracking
app.use((req, res, next) => {
  req.requestId = Math.random().toString(36).substr(2, 9);
  req.startTime = Date.now();
  req.correlationId = req.headers["x-correlation-id"] as string;
  next();
});

// Usage with augmented types
app.get("/users/:id", async (req, res) => {
  try {
    const userId = req.params.id;
    const user = await getUserById(userId);

    if (!user) {
      return res.error("User not found", 404);
    }

    res.success(user);
  } catch (error) {
    res.error("Internal server error");
  }
});

// Augmenting Lodash
import _ from "lodash";

declare module "lodash" {
  interface LoDashStatic {
    isValidEmail(value: string): boolean;
    formatPhoneNumber(value: string): string;
    generateId(prefix?: string): string;
  }

  interface LoDashImplicitWrapper<TValue> {
    isValidEmail(): boolean;
    formatPhoneNumber(): LoDashImplicitWrapper<string>;
  }
}

// Implementation
_.mixin({
  isValidEmail: (value: string): boolean => {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
  },

  formatPhoneNumber: (value: string): string => {
    const cleaned = value.replace(/\D/g, "");
    const match = cleaned.match(/^(\d{3})(\d{3})(\d{4})$/);
    return match ? `(${match[1]}) ${match[2]}-${match[3]}` : value;
  },

  generateId: (prefix = "id"): string => {
    return `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  },
});

// Usage
const email = "user@example.com";
console.log(_.isValidEmail(email)); // true
console.log(_(email).isValidEmail()); // true

const phone = "1234567890";
console.log(_.formatPhoneNumber(phone)); // '(123) 456-7890'
console.log(_(phone).formatPhoneNumber().value()); // '(123) 456-7890'

const id = _.generateId("user"); // 'user_1634567890123_abc123def'

function getUserById(id: string): Promise<User | null> {
  // Implementation
  return Promise.resolve(null);
}

Ambient Declarations

Ambient declarations describe the shape of existing JavaScript code without providing implementation.

Basic Ambient Declarations

typescript
// Declaring global variables
declare const VERSION: string;
declare const BUILD_TIME: number;
declare const IS_PRODUCTION: boolean;

// Declaring global functions
declare function gtag(command: string, targetId: string, config?: object): void;
declare function fbq(
  command: string,
  eventName: string,
  parameters?: object
): void;

// Declaring global objects
declare const analytics: {
  track(event: string, properties?: Record<string, any>): void;
  identify(userId: string, traits?: Record<string, any>): void;
  page(name?: string, properties?: Record<string, any>): void;
};

// Usage (no implementation needed)
console.log(`Version: ${VERSION}`);
gtag("config", "GA_MEASUREMENT_ID");
analytics.track("page_view", { page: "/home" });

// Declaring classes
declare class ThirdPartyWidget {
  constructor(element: HTMLElement, options?: WidgetOptions);
  render(): void;
  destroy(): void;
  on(event: string, callback: Function): void;
  off(event: string, callback?: Function): void;
}

interface WidgetOptions {
  theme?: "light" | "dark";
  size?: "small" | "medium" | "large";
  autoplay?: boolean;
}

// Usage
const widget = new ThirdPartyWidget(document.getElementById("widget")!, {
  theme: "dark",
  size: "large",
});

widget.render();
widget.on("ready", () => console.log("Widget is ready"));

// Declaring modules
declare module "legacy-library" {
  export interface Config {
    apiKey: string;
    baseUrl: string;
    timeout?: number;
  }

  export class Client {
    constructor(config: Config);
    request(endpoint: string, data?: any): Promise<any>;
    upload(file: File, options?: UploadOptions): Promise<UploadResult>;
  }

  export interface UploadOptions {
    onProgress?: (progress: number) => void;
    maxSize?: number;
  }

  export interface UploadResult {
    id: string;
    url: string;
    size: number;
  }

  export function initialize(config: Config): Client;
  export const version: string;
}

// Usage
import { initialize, Client, Config } from "legacy-library";

const config: Config = {
  apiKey: "your-api-key",
  baseUrl: "https://api.example.com",
  timeout: 5000,
};

const client = initialize(config);
client.request("/users").then((users) => console.log(users));

Environment-Specific Declarations

typescript
// Node.js environment declarations
declare namespace NodeJS {
  interface ProcessEnv {
    NODE_ENV: "development" | "production" | "test";
    PORT: string;
    DATABASE_URL: string;
    JWT_SECRET: string;
    REDIS_URL?: string;
    SMTP_HOST?: string;
    SMTP_PORT?: string;
    SMTP_USER?: string;
    SMTP_PASS?: string;
    AWS_ACCESS_KEY_ID?: string;
    AWS_SECRET_ACCESS_KEY?: string;
    AWS_REGION?: string;
    STRIPE_SECRET_KEY?: string;
    STRIPE_WEBHOOK_SECRET?: string;
  }

  interface Global {
    __DEV__: boolean;
    __TEST__: boolean;
    __PROD__: boolean;
  }
}

// Browser environment declarations
declare interface Window {
  gtag?: (
    command: "config" | "event" | "set",
    targetIdOrName: string,
    configOrParameters?: object
  ) => void;

  fbq?: (
    command: "track" | "trackCustom" | "init",
    eventNameOrPixelId: string,
    parameters?: object
  ) => void;

  dataLayer?: any[];

  Stripe?: {
    (publishableKey: string, options?: StripeOptions): StripeInstance;
  };

  PayPal?: {
    Buttons(options: PayPalButtonsOptions): PayPalButtonsInstance;
  };

  grecaptcha?: {
    ready(callback: () => void): void;
    execute(siteKey: string, options: { action: string }): Promise<string>;
  };
}

interface StripeOptions {
  apiVersion?: string;
  locale?: string;
}

interface StripeInstance {
  elements(): StripeElements;
  confirmCardPayment(clientSecret: string, data?: any): Promise<any>;
  createToken(element: any, data?: any): Promise<any>;
}

interface StripeElements {
  create(type: string, options?: any): StripeElement;
}

interface StripeElement {
  mount(selector: string): void;
  on(event: string, callback: Function): void;
}

interface PayPalButtonsOptions {
  createOrder?: (data: any, actions: any) => Promise<string>;
  onApprove?: (data: any, actions: any) => Promise<void>;
  onError?: (error: any) => void;
  style?: {
    layout?: "vertical" | "horizontal";
    color?: "gold" | "blue" | "silver" | "white" | "black";
    shape?: "rect" | "pill";
    label?: "paypal" | "checkout" | "buynow" | "pay";
  };
}

interface PayPalButtonsInstance {
  render(selector: string): Promise<void>;
}

// Usage
if (typeof window !== "undefined") {
  // Google Analytics
  window.gtag?.("config", "GA_MEASUREMENT_ID");

  // Facebook Pixel
  window.fbq?.("track", "PageView");

  // Stripe
  if (window.Stripe) {
    const stripe = window.Stripe("pk_test_...");
    const elements = stripe.elements();
    const cardElement = elements.create("card");
    cardElement.mount("#card-element");
  }

  // PayPal
  window.PayPal?.Buttons({
    createOrder: async (data, actions) => {
      // Implementation
      return "order-id";
    },
    onApprove: async (data, actions) => {
      // Implementation
    },
  }).render("#paypal-button-container");
}

// Node.js usage
if (typeof process !== "undefined") {
  const port = process.env.PORT || "3000";
  const isDev = process.env.NODE_ENV === "development";
  const dbUrl = process.env.DATABASE_URL;
}

Creating Type Definition Files

typescript
// types/my-library.d.ts

// For a simple JavaScript library
declare module "simple-validator" {
  interface ValidationRule {
    required?: boolean;
    minLength?: number;
    maxLength?: number;
    pattern?: RegExp;
    custom?: (value: any) => boolean | string;
  }

  interface ValidationSchema {
    [field: string]: ValidationRule;
  }

  interface ValidationResult {
    isValid: boolean;
    errors: Record<string, string[]>;
  }

  export class Validator {
    constructor(schema: ValidationSchema);
    validate(data: Record<string, any>): ValidationResult;
    addRule(name: string, rule: ValidationRule): void;
  }

  export function validate(
    data: Record<string, any>,
    schema: ValidationSchema
  ): ValidationResult;

  export const rules: {
    email: ValidationRule;
    url: ValidationRule;
    numeric: ValidationRule;
    alpha: ValidationRule;
    alphanumeric: ValidationRule;
  };
}

// For a library with default export
declare module "date-formatter" {
  interface FormatOptions {
    locale?: string;
    timezone?: string;
    format?: string;
  }

  interface DateFormatter {
    format(date: Date, options?: FormatOptions): string;
    parse(dateString: string, format?: string): Date;
    addDays(date: Date, days: number): Date;
    addMonths(date: Date, months: number): Date;
    addYears(date: Date, years: number): Date;
    isBefore(date1: Date, date2: Date): boolean;
    isAfter(date1: Date, date2: Date): boolean;
    isSame(date1: Date, date2: Date, unit?: "day" | "month" | "year"): boolean;
  }

  const formatter: DateFormatter;
  export = formatter;
}

// For a library with mixed exports
declare module "http-client" {
  interface RequestConfig {
    method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
    headers?: Record<string, string>;
    timeout?: number;
    retries?: number;
    baseURL?: string;
  }

  interface Response<T = any> {
    data: T;
    status: number;
    statusText: string;
    headers: Record<string, string>;
  }

  interface HttpClient {
    get<T = any>(url: string, config?: RequestConfig): Promise<Response<T>>;
    post<T = any>(
      url: string,
      data?: any,
      config?: RequestConfig
    ): Promise<Response<T>>;
    put<T = any>(
      url: string,
      data?: any,
      config?: RequestConfig
    ): Promise<Response<T>>;
    delete<T = any>(url: string, config?: RequestConfig): Promise<Response<T>>;
    patch<T = any>(
      url: string,
      data?: any,
      config?: RequestConfig
    ): Promise<Response<T>>;
  }

  export function create(config?: RequestConfig): HttpClient;
  export function get<T = any>(
    url: string,
    config?: RequestConfig
  ): Promise<Response<T>>;
  export function post<T = any>(
    url: string,
    data?: any,
    config?: RequestConfig
  ): Promise<Response<T>>;

  export default create;
}

// For a jQuery plugin
declare namespace JQuery {
  interface JQuery {
    myPlugin(options?: MyPluginOptions): JQuery;
    myPlugin(method: "destroy"): JQuery;
    myPlugin(method: "update", data: any): JQuery;
    myPlugin(method: "getValue"): any;
  }
}

interface MyPluginOptions {
  theme?: "light" | "dark";
  animation?: boolean;
  duration?: number;
  onInit?: () => void;
  onChange?: (value: any) => void;
}

// Usage examples
import { Validator, validate, rules } from "simple-validator";
import dateFormatter from "date-formatter";
import httpClient, { create, get } from "http-client";

// Simple validator
const validator = new Validator({
  email: { ...rules.email, required: true },
  password: { required: true, minLength: 8 },
});

const result = validator.validate({
  email: "user@example.com",
  password: "password123",
});

// Date formatter
const formatted = dateFormatter.format(new Date(), {
  locale: "en-US",
  format: "YYYY-MM-DD",
});

// HTTP client
const client = create({ baseURL: "https://api.example.com" });
const response = await client.get<User[]>("/users");

// jQuery plugin
$("#my-element").myPlugin({
  theme: "dark",
  animation: true,
  onChange: (value) => console.log("Value changed:", value),
});

Working with @types Packages

Installing and Using Type Definitions

typescript
// Install type definitions for popular libraries
// npm install --save-dev @types/node @types/express @types/lodash @types/jest

// Using Express with types
import express, { Request, Response, NextFunction } from "express";
import { User } from "./types/user";

const app = express();

// Middleware with proper typing
const authMiddleware = (req: Request, res: Response, next: NextFunction) => {
  const token = req.headers.authorization;

  if (!token) {
    return res.status(401).json({ error: "No token provided" });
  }

  // Verify token and attach user
  req.user = { id: "1", name: "John Doe" } as User;
  next();
};

// Route handlers with typing
app.get("/users/:id", authMiddleware, async (req: Request, res: Response) => {
  const userId = req.params.id;
  const currentUser = req.user; // TypeScript knows this exists due to augmentation

  try {
    const user = await getUserById(userId);
    res.json(user);
  } catch (error) {
    res.status(500).json({ error: "Internal server error" });
  }
});

// Using Lodash with types
import _ from "lodash";

const users: User[] = [
  {
    id: "1",
    name: "John",
    email: "john@example.com",
    age: 30,
    isActive: true,
    createdAt: new Date(),
    updatedAt: new Date(),
  },
  {
    id: "2",
    name: "Jane",
    email: "jane@example.com",
    age: 25,
    isActive: false,
    createdAt: new Date(),
    updatedAt: new Date(),
  },
];

// TypeScript knows the return types
const activeUsers = _.filter(users, "isActive"); // User[]
const userNames = _.map(users, "name"); // string[]
const groupedByAge = _.groupBy(users, "age"); // Record<string, User[]>
const sortedUsers = _.sortBy(users, ["age", "name"]); // User[]

// Using Jest with types
import { describe, it, expect, beforeEach, afterEach } from "@jest/globals";

describe("User Service", () => {
  let userService: UserService;

  beforeEach(() => {
    userService = new UserService();
  });

  afterEach(() => {
    // Cleanup
  });

  it("should create a user", async () => {
    const userData = {
      name: "Test User",
      email: "test@example.com",
      age: 25,
      isActive: true,
    };

    const user = await userService.createUser(userData);

    expect(user).toBeDefined();
    expect(user.name).toBe(userData.name);
    expect(user.email).toBe(userData.email);
  });

  it("should throw error for invalid email", async () => {
    const userData = {
      name: "Test User",
      email: "invalid-email",
      age: 25,
      isActive: true,
    };

    await expect(userService.createUser(userData)).rejects.toThrow(
      "Invalid email"
    );
  });
});

class UserService {
  async createUser(
    userData: Omit<User, "id" | "createdAt" | "updatedAt">
  ): Promise<User> {
    // Implementation
    return {
      id: "1",
      ...userData,
      createdAt: new Date(),
      updatedAt: new Date(),
    };
  }
}

Creating Custom @types Packages

typescript
// package.json for @types/my-custom-library
{
  "name": "@types/my-custom-library",
  "version": "1.0.0",
  "description": "TypeScript definitions for my-custom-library",
  "types": "index.d.ts",
  "typesPublisherContentHash": "...",
  "typeScriptVersion": "4.0",
  "dependencies": {},
  "devDependencies": {
    "typescript": "^4.0.0"
  }
}

// index.d.ts
// Type definitions for my-custom-library 2.1
// Project: https://github.com/example/my-custom-library
// Definitions by: Your Name <https://github.com/yourusername>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped

export interface LibraryConfig {
  apiKey: string;
  baseUrl?: string;
  timeout?: number;
  debug?: boolean;
}

export interface ApiResponse<T = any> {
  success: boolean;
  data?: T;
  error?: string;
  timestamp: string;
}

export class MyLibrary {
  constructor(config: LibraryConfig);

  get<T = any>(endpoint: string): Promise<ApiResponse<T>>;
  post<T = any>(endpoint: string, data: any): Promise<ApiResponse<T>>;
  put<T = any>(endpoint: string, data: any): Promise<ApiResponse<T>>;
  delete<T = any>(endpoint: string): Promise<ApiResponse<T>>;

  setConfig(config: Partial<LibraryConfig>): void;
  getConfig(): LibraryConfig;
}

export function createClient(config: LibraryConfig): MyLibrary;
export function isValidConfig(config: any): config is LibraryConfig;

export const version: string;
export const defaultConfig: Partial<LibraryConfig>;

// For libraries with global side effects
declare global {
  interface Window {
    MyLibrary?: typeof MyLibrary;
  }
}

// tests/index.ts - Test the type definitions
import { MyLibrary, createClient, LibraryConfig, ApiResponse } from 'my-custom-library';

// Test basic usage
const config: LibraryConfig = {
  apiKey: 'test-key',
  baseUrl: 'https://api.example.com',
  timeout: 5000
};

const client = new MyLibrary(config);
const client2 = createClient(config);

// Test API calls
client.get<{ users: any[] }>('/users').then((response: ApiResponse<{ users: any[] }>) => {
  if (response.success && response.data) {
    console.log(response.data.users);
  }
});

client.post('/users', { name: 'John' }).then((response: ApiResponse) => {
  console.log(response.success);
});

// Test configuration
client.setConfig({ timeout: 10000 });
const currentConfig: LibraryConfig = client.getConfig();

Best Practices

✅ Good Practices

typescript
// Use interface merging for extending existing types
interface User {
  id: string;
  name: string;
}

interface User {
  email: string; // Extends the User interface
}

// Create specific ambient declarations for third-party libraries
declare module "specific-library" {
  export interface Config {
    apiKey: string;
    baseUrl: string;
  }

  export class Client {
    constructor(config: Config);
    request(endpoint: string): Promise<any>;
  }
}

// Use namespace merging for organizing related functionality
namespace Utils {
  export function formatDate(date: Date): string {
    return date.toISOString();
  }
}

namespace Utils {
  export function parseDate(dateString: string): Date {
    return new Date(dateString);
  }
}

// Augment global types carefully and specifically
declare global {
  interface Window {
    mySpecificLibrary?: {
      version: string;
      init(): void;
    };
  }
}

// Use proper type constraints in ambient declarations
declare function processData<T extends Record<string, any>>(data: T): T;

❌ Avoid

typescript
// Don't over-augment global types
declare global {
  interface Window {
    [key: string]: any; // ❌ Too broad
  }

  interface Array<T> {
    customMethod(): any; // ❌ Pollutes all arrays
  }
}

// Don't create conflicting interface merges
interface User {
  id: string;
}

interface User {
  id: number; // ❌ Conflicts with previous declaration
}

// Don't use any in ambient declarations
declare module "some-library" {
  export function doSomething(data: any): any; // ❌ Not type-safe
}

// Don't create overly broad ambient declarations
declare const globalVariable: any; // ❌ Should be more specific

// Don't ignore existing type definitions
// If @types/library exists, don't create your own unless necessary

Summary Checklist

  • Use interface merging to extend existing types
  • Leverage namespace merging for organizing code
  • Create ambient declarations for JavaScript libraries
  • Augment third-party library types when needed
  • Use module augmentation for extending external modules
  • Create proper type definition files for custom libraries
  • Install and use @types packages for popular libraries
  • Be specific and careful with global augmentations
  • Test your type definitions thoroughly
  • Document your ambient declarations clearly

Next Steps

Now that you understand declaration merging and ambient declarations, let's explore working with third-party types and creating custom type definitions.


Continue to: Working with Third-Party Types

PreviousUtility Types and Type Manipulations

Open source, free forever. Built by iammhador.

Contribute on GitHub