clinyong
clinyong

Reputation: 1845

How can I extend other module's types in typescript

I am writing a library using ts, and get some trouble when trying to extend core module type. Here is the project structure.

- src
  - core
    - core.ts
  - plugins
    - insert
      - index.ts

code in core.ts

export interface Commands {}

export class Core {
  commands: Commands;

  constructor() {
    this.commands = {};
  }

  registerCommands(name: string, func: any) {
    // @ts-ignore
    this.commands[name] = func;
  }
}

code in insert/index.ts

import { Core } from "../../core/core";

export class InsertPlugin {
  constructor(core: Core) {
    core.registerCommands("insert", this.insert.bind(this));
  }

  insert(value: string) {
    console.log("insert " + value);
  }
}

InsertPlugin using core.registerCommands to register a command insert to core.

My question is how can some one using my library get types for insert command. For example

// assume the name of the library is core
import { createCore } from 'core'

const core = createCore()
// How can i get types for insert method
core.commands.insert()

I have create a complete demo for above code, https://github.com/clinyong/ts-extends-core-commands.

Also I have read the typescript handbook for plugin types, https://www.typescriptlang.org/docs/handbook/declaration-files/templates/module-plugin-d-ts.html.

Upvotes: 0

Views: 227

Answers (1)

Sam Lanning
Sam Lanning

Reputation: 636

What's interesting with your example is that accessing core.commands.insert() is only valid after you create a new InsertPlugin(core) instance (which you don't do in your example).

Given this, you can actually encode this requirement if you were to use a function with an assertion return type, rather than a class.

insert/index.ts:

import { Core } from "../../core/core";

function registerInsertPlugin(core: Core): asserts core is (Core & {
  commands: {
    insert: typeof insert
  }
}) {

  const insert = (value: string) => {
    console.log("insert " + value);
  }

  core.registerCommands("insert", insert);
}

Then you can use it like this:

import { createCore } from 'core'
import { registerInsertPlugin } from 'insert'

const core = createCore();
registerInsertPlugin(core);
core.commands.insert('hello world');

Forgetting to call registerInsertPlugin will result in a valid type error:


import { createCore } from 'core'

const core = createCore();
core.commands.insert('hello world');
// ^ Property 'insert' does not exist on type 'Commands'

Beyond this, you will then be able to also create multiple "plugins", each of which will expand core.commands with the relevant types:

const core = createCore();
registerInsertPlugin(core);
registerUpsertPlugin(core);
core.commands.insert('hello world');
core.commands.upsert("hello world");

/* Resulting Type:
 * const core: Core & {
 *     commands: {
 *         insert: (value: string) => void;
 *     };
 * } & {
 *     commands: {
 *         upsert: (value: string) => void;
 *     };
 * }
 */

Upvotes: 1

Related Questions