We use cookies to enhance your experience on the site
CodeWorlds

Namespaces, Declaration Merging, and Ambient Types

"In Jurassic Park, we have dozens of systems - monitoring, genetics, security, logistics," explains Dennis Nedry, the park's lead programmer. "Each system has hundreds of variables, types, functions. Without proper organization, names would start repeating, and the chaos would become unmanageable. We need a way to separate each system while still allowing them to cooperate."

In TypeScript, there are several mechanisms for organizing code at the type level: namespaces, declaration merging, and ambient types. These techniques allow building well-organized, extensible type systems.

Namespaces

A namespace is a way of grouping related types, interfaces, functions, and variables under a common name. It prevents name collisions and logically organizes code.

1// Namespace for the security system
2namespace Security {
3  export interface Camera {
4    id: string;
5    zone: string;
6    isActive: boolean;
7  }
8
9  export interface Guard {
10    id: string;
11    name: string;
12    assignedZone: string;
13  }
14
15  export type AlertLevel = "green" | "yellow" | "red" | "black";
16
17  export function getAlertDescription(level: AlertLevel): string {
18    switch (level) {
19      case "green": return "All normall";
20      case "yellow": return "Elevated vigilance";
21      case "red": return "Threat - evacuate visitors";
22      case "black": return "Critical - full evacuation";
23    }
24  }
25}
26
27// Namespace for the genetics system
28namespace Genetics {
29  export interface Sample {
30    id: string;
31    species: string;
32    purity: number;
33  }
34
35  // This "Sample" does not collide with Security.Camera
36  // because it's in a different namespace
37  export function analyzeSample(sample: Sample): string {
38    return `Sample analysis ${sample.id}: purity ${sample.purity}%`;
39  }
40}
41
42// Usage - referencing through the namespace name
43const camera: Security.Camera = { id: "CAM-01", zone: "T-Rex Zone", isActive: true };
44const sample: Genetics.Sample = { id: "DNA-42", species: "Velociraptor", purity: 87 };
45
46console.log(Security.getAlertDescription("red"));
47console.log(Genetics.analyzeSample(sample));

Nested Namespaces

Namespaces can be nested, creating a hierarchical structure:

1namespace JurassicPark {
2  export namespace Monitoring {
3    export interface SensorReading {
4      sensorId: string;
5      temperature: number;
6      humidity: number;
7      timestamp: Date;
8    }
9
10    export function isReadingNormal(reading: SensorReading): boolean {
11      return reading.temperature >= 20 && reading.temperature <= 35;
12    }
13  }
14
15  export namespace Feeding {
16    export interface Schedule {
17      dinosaurId: string;
18      food: string;
19      amountKg: number;
20      time: string;
21    }
22
23    export function createSchedule(dinoId: string, food: string, kg: number, time: string): Schedule {
24      return { dinosaurId: dinoId, food, amountKg: kg, time };
25    }
26  }
27}
28
29// Usage
30const reading: JurassicPark.Monitoring.SensorReading = {
31  sensorId: "TEMP-A3",
32  temperature: 28,
33  humidity: 65,
34  timestamp: new Date()
35};
36
37const schedule = JurassicPark.Feeding.createSchedule("DINO-001", "Goat", 50, "08:00");
38console.log("Temperature normall:", JurassicPark.Monitoring.isReadingNormal(reading));
39console.log("Schedule:", JSON.stringify(schedule));

Declaration Merging

Declaration merging is a TypeScript mechanism where the compiler combines two or more separate declarations with the same name into a single definition. This is especially useful for extending existing types.

Interface Merging

The most common type of declaration merging is interface merging. When you declare the same interface multiple times, TypeScript merges them into one:

1// Base dinosaur interface
2interface ParkDinosaur {
3  id: string;
4  name: string;
5  species: string;
6}
7
8// Extending the same interface - automatically merged
9interface ParkDinosaur {
10  weight: number;
11  diet: "carnivore" | "herbivore" | "omnivore";
12}
13
14// Another extension
15interface ParkDinosaur {
16  enclosureId: string;
17  dangerLevel: number;
18}
19
20// The resulting ParkDinosaur type has ALL fields from all declarations
21const rex: ParkDinosaur = {
22  id: "D-001",
23  name: "Rexy",
24  species: "Tyrannosaurus Rex",
25  weight: 8000,
26  diet: "carnivore",
27  enclosureId: "ENC-A1",
28  dangerLevel: 10
29};
30
31console.log(`${rex.name}: ${rex.weight}kg, danger: ${rex.dangerLevel}/10`);

Namespace Merging

Namespaces with the same name also merge:

1namespace ParkSystems {
2  export function getVersion(): string {
3    return "1.0.0";
4  }
5}
6
7namespace ParkSystems {
8  export function getStatus(): string {
9    return "Operational";
10  }
11}
12
13// Both exports are available
14console.log(ParkSystems.getVersion());
15console.log(ParkSystems.getStatus());

Namespace Merging with a Class or Function

A namespace can extend a class with static properties:

1class Enclosure {
2  name: string;
3  capacity: number;
4
5  constructor(name: string, capacity: number) {
6    this.name = name;
7    this.capacity = capacity;
8  }
9}
10
11// A namespace with the same name adds static members
12namespace Enclosure {
13  export interface Config {
14    fenceVoltage: number;
15    cameraCount: number;
16    guardCount: number;
17  }
18
19  export const DEFAULT_CONFIG: Config = {
20    fenceVoltage: 5000,
21    cameraCount: 4,
22    guardCount: 2
23  };
24
25  export function createHighSecurity(name: string, capacity: number): Enclosure {
26    const enc = new Enclosure(name, capacity);
27    // Use the default configuration...
28    return enc;
29  }
30}
31
32// Using the class and namespace together
33const enc = new Enclosure("Raptor Zone", 6);
34const config = Enclosure.DEFAULT_CONFIG;
35const secureEnc = Enclosure.createHighSecurity("T-Rex Zone", 2);
36
37console.log(`Enclosure: ${enc.name}, Default voltage: ${config.fenceVoltage}V`);

Ambient Declarations

The

declare
keyword tells TypeScript that a given variable, function, or class exists at runtime but is defined elsewhere (e.g., in a global script, external library). The compiler does not generate code for
declare
- it only checks types.

1// Global variable declaration
2declare const PARK_VERSION: string;
3declare const MAX_DINOSAURS: number;
4
5// Function declaration from an external library
6declare function trackDinosaur(id: string): { x: number; y: number };
7declare function sendAlert(message: string, zone: string): void;
8
9// External module declaration
10declare module "jurassic-utils" {
11  export function calculateFeedingAmount(weight: number, species: string): number;
12  export function getEnclosureRecommendation(dangerLevel: number): string;
13}
14
15// Class declaration from an external system
16declare class ExternalTrackingSystem {
17  connect(apiKey: string): Promise<void>;
18  getPosition(dinoId: string): { lat: number; lng: number };
19  disconnect(): void;
20}

.d.ts Files - Type Declaration Files

Files with the

.d.ts
extension contain ONLY type declarations - no executable code. They serve to describe types for JavaScript libraries that don't have built-in types.

1// Example jurassic-park.d.ts file
2// (declarations only, no implementation)
3
4declare namespace JurassicParkAPI {
5  interface DinosaurResponse {
6    id: string;
7    name: string;
8    species: string;
9    location: { lat: number; lng: number };
10  }
11
12  interface EnclosureResponse {
13    id: string;
14    name: string;
15    status: "normall" | "lockdown" | "breach";
16    dinosaurIds: string[];
17  }
18
19  function fetchDinosaur(id: string): Promise<DinosaurResponse>;
20  function fetchEnclosure(id: string): Promise<EnclosureResponse>;
21  function getAllDinosaurs(): Promise<DinosaurResponse[]>;
22}

Extending Existing Modules (Module Augmentation)

You can add types to existing modules using

declare module
:

1// Adding fields to the Express Request interface (common case)
2// File: types/express.d.ts
3declare module "express" {
4  interface Request {
5    parkUser?: {
6      id: string;
7      role: "visitor" | "staff" | "admin";
8      clearanceLevel: number;
9    };
10  }
11}
12
13// Extending global types
14declare global {
15  interface Array<T> {
16    // Add a method to Array
17    findDinosaur(predicate: (item: T) => boolean): T | undefined;
18  }
19
20  interface Window {
21    parkConfig: {
22      apiUrl: string;
23      version: string;
24      debugMode: boolean;
25    };
26  }
27}
28
29// Important: the file must have at least one import/export
30// for TypeScript to treat it as a module
31export {};

Practical Example - Organizing the Park System

Below is an example combining namespaces, declaration merging, and ambient declarations in one system:

1// Namespace with base types
2namespace Park {
3  export interface Entity {
4    id: string;
5    createdAt: Date;
6  }
7
8  export type Priority = "low" | "medium" | "high" | "critical";
9}
10
11// Declaration merging - extending Park with new types
12namespace Park {
13  export interface Dinosaur extends Entity {
14    name: string;
15    species: string;
16    dangerLevel: number;
17  }
18
19  export interface Enclosure extends Entity {
20    name: string;
21    capacity: number;
22    residents: string[];
23  }
24}
25
26// Another extension - helper functions
27namespace Park {
28  export function isDangerous(dino: Dinosaur): boolean {
29    return dino.dangerLevel >= 7;
30  }
31
32  export function getEnclosureStatus(enc: Enclosure): string {
33    const ratio = enc.residents.length / enc.capacity;
34    if (ratio >= 1) return "FULL";
35    if (ratio >= 0.75) return "Almost full";
36    return "OK";
37  }
38}
39
40// Usage - all extensions work together
41const trex: Park.Dinosaur = {
42  id: "D-001",
43  createdAt: new Date(),
44  name: "Rexy",
45  species: "T-Rex",
46  dangerLevel: 10
47};
48
49const enclosure: Park.Enclosure = {
50  id: "E-001",
51  createdAt: new Date(),
52  name: "T-Rex Zone",
53  capacity: 2,
54  residents: ["D-001"]
55};
56
57console.log(`${trex.name} dangerous: ${Park.isDangerous(trex)}`);
58console.log(`Enclosure ${enclosure.name}: ${Park.getEnclosureStatus(enclosure)}`);

Summary

Type organization mechanisms in TypeScript are powerful tools for building large, scalable systems:

  1. Namespaces - grouping related types and functions, avoiding name collisions
  2. Declaration merging - extending existing interfaces and namespaces with new fields and methods
  3. Ambient declarations (declare) - describing types for code defined elsewhere
  4. .d.ts files - files containing only type declarations (no code)
  5. Module augmentation - extending types in existing modules (e.g., Express, external libraries)

As Dennis Nedry says: "The system must be organized like dinosaur DNA - every gene in its place, but all cooperating in one organism. Namespaces are chromosomes, declaration merging is gene expression, and ambient types are information about the environment."

Go to CodeWorlds