'Typescript: Changing the definition of some third party types d.ts

I am a little bit lost how to do the following.

I am trying to change the definitions of some third party types by creating a new file thirdParty.d.ts.

Let's suppose the third party returns Class A

class A {
    // ...
}

In my third-party.d.ts file, I want to introduce two new parameters.

import * as thirdParty from 'thirdParty'
decalre module 'thirdParty' {
    export interface A extends thirdParty.A {
        newParam1: number
        newParam2: number
    }
}

Then, let's overwrite class A by adding newParam1 and newParam2

class ExtendedA extends A {
    newParam1 = 1
    newParam2 = 2
}

So now everything looks good. Every instance of class A recognizes the new parameters. In any function or class it's possible to call newParam1 without casting.

randomMethod() {
 console.log(this.a.newParam1) // Returns 1. No need to cast (this.a as ExtendedA).newParam1 !
}

However, since I changed the definition of class A. And ExtendedA extends it. Deleting the new parameters will not generate errors. Which worries me. I am looking for a way to force ExtendedA to decalre the new parameters.

// This is bad :(    
class ExtendedA extends A { // implements Interface will not work either
    // newParam1 = 1 // Commented but there is no errors ! Which is bad
    // newParam2 = 2
}

I am sure the fix is pretty easy but I am really lost



Solution 1:[1]

You are trying to merge a class definition. This is at the moment disallowed. check here: https://www.typescriptlang.org/docs/handbook/declaration-merging.html#disallowed-merges

As pointed out in the docs... you can extend the third party class with mixins. But you would be essentially generating a new class: From the docs:

// To get started, we need a type which we'll use to extend
// other classes from. The main responsibility is to declare
// that the type being passed in is a class.
 
type Constructor = new (...args: any[]) => {};
 
// This mixin adds a scale property, with getters and setters
// for changing it with an encapsulated private property:
 
function Scale<TBase extends Constructor>(Base: TBase) {
  return class Scaling extends Base {
    // Mixins may not declare private/protected properties
    // however, you can use ES2020 private fields
    _scale = 1;
 
    setScale(scale: number) {
      this._scale = scale;
    }
 
    get scale(): number {
      return this._scale;
    }
  };
}

What will work as you epect is if you try to create a new Class that implements the original, like so:

//========= ./third-party/third-party =========
export class A {
  foo = 'foo';
}

//========= index.ts =========
import {A} from './third-party/third-party';

declare module './third-party/third-party' {
    interface A {
        newParam1: string;
        newParam2: number
    }
}
// ideally you should extend the functionality also:
// those are type safe;
// A.prototype.newParam1 = 'something';
// A.prototype.newParam2 = 20;

class B implements A {
  foo='else';
  newParam1='';
  newParam2=20;
}

const b = new B();
console.log(b.newParam1)

But for that you would need to implement all Class A functionalities or come up with a delegation pattern or something.

Here's a simple repl to use as playground: https://replit.com/@tiagobnobrega/module-extension#index.ts

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Tiago Nobrega