Search code examples
typescriptmodule-augmentation

Augmenting nested properties in TypeScript module augmentation


I have 3 files in the same directory: usage.ts, animal.types.d.ts and augment-animal.d.ts:

usage.ts file:

import './augment-animal.types';
import { Animal } from './animal.types';

const animal: Animal['Schemas']['Dog'] = {
  id: 'someId',
  name: 'someName' // <------ throws an error because Dog doesn't have such property
}

animal.types.d.ts

export interface Animal {
  Schemas: {
    Dog: {
      id: string;
    };
  }
}

augment-animal.types.d.ts

declare module './animal.types' {
  interface Animal {
    topLevelProperty: string, // <--- it works
    Schemas: {
      Dog: {
        name: string; // <--- it doesn't work
      };
    }
  }
}

How I can augment nested properties using TypeScript module augmentation?


Solution

  • There are two problems there:

    1. Order of imports matters: in particular, an interface imported later augments one imported earlier, so I would have expected "animal.types.d.ts" imported before "augment-animal.d.ts", since the latter, by the name of it, is the one meant to augment the former.

    2. You are allowed in TS to augment existing interfaces, not change what is already there (somewhat ambiguously the docs talk of interfaces being "extendable", anyway it is about augmentation): https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces

    How I can augment nested properties using TypeScript module augmentation?

    So, the short answer to that question is you cannot. All you can do is define a new interface or type that extends the given one.

    Here is some example code inspired by yours that should make all of the above clearer:

    // Uncomment to see the errors:
    
    export interface Animal {  // "first import"
      id: string;
      schema: { name: string };
    }
    
    export interface Animal {  // "augmentation"
      //id: number;  // ERROR: cannot redefine `id`!
      schema: {      // ERROR: cannot redefine `schema`!
        name: string;
        //title: string;
      };
      name: string;  // OK: `Animal` can be augmented
    }
    
    const animal: Animal = {
      id: 'someId',
      name: 'someName',
      schema: { name: 'name' },
    };
    
    export interface Animal2 extends Animal {  // "extension"
      schema: {
        //name: number;  // ERROR: 'Animal2' incorrectly extends 'Animal'
        name: "NAME";  // OK: (sub-)property type narrowing, if one wants
        title: string;  // OK: new (sub-)property
      };
      anything?: unknown;  // OK: new property
    }
    
    const animal2: Animal2 = {
      id: 'someId',
      name: 'someName',
      schema: {
        name: "NAME",
        title: "title",
      },
    };
    

    Link to playground