What steps should I take to import a module with type definitions? (receiving error TS2656: ...not a module)

I am currently working on enhancing the type definitions for a simple npm module called emitter20. The source code of this module spans 20 lines and looks like this:

module.exports = function() {
  var subscribers = []
  return {
    on: function (eventName, cb) {
      subscribers.push({
        eventName: eventName,
        cb: cb
      })
    },
    trigger: function (eventName, data) {
      subscribers
        .filter(function (subscriber) {
          return subscriber.eventName === eventName
        })
        .forEach(function (subscriber) {
          subscriber.cb(data)
        })
    }
  }
}

Within the emitter20 project root, there is an index.d.ts file structured as follows:

declare module 'emitter20' {
  interface IEmitter {
    on: (eventName: string, cb: (data?: any) => void) => void;
    trigger: (eventName: string, data?: any) => void;
  }
  interface EmitterFactory {
    new(): IEmitter;
  }
  export = IEmitter;
}

In my attempts to refine the type definitions, I have also experimented with the following approach:

declare module 'emitter20' {
  export interface IEmitter {
    on: (eventName: string, cb: (data?: any) => void) => void;
    trigger: (eventName: string, data?: any) => void;
  }
  export interface EmitterFactory {
    new(): IEmitter;
  }
}

When trying to import this module into my project using the code snippet below:

import IEmitter = require('emitter20')

export interface SwapManager extends IEmitter {
  manager: any;
}

I encountered the following error message:

error TS2656: Exported external package typings file './node_modules/emitter20/index.d.ts' is not a module. Please contact the package author to update the package definition.

How can I properly define and import the type definition for the emitter20 module?

(On a side note: Typescript imports/exports... definitely a challenging aspect to grasp!)

Answer №1

The issue at hand is that this JS module does not follow the typical structure and does not explicitly expose any types, making it difficult to subclass... I believe.

If this were a standard or ES6 JS module that returned classes, you could simply use export statements like

export this; export that; export default function factory()
, but in this case, it is a function with the use of export = as described in my approach:

To maintain compatibility with CommonJS and AMD style modules, TypeScript also supports export-equals declarations like export = Point. Unlike default export declarations that are shorthand for an export named default, export-equals declarations specify an entity to be exported instead of the actual module itself.

I am aware of two methods to address this "typing" issue:

Method 1: Implementing an Interface:

Pros:

  • Provides intellisense/autocomplete and type safety

Cons:

  • Does not expose the interface for explicit type declarations (unable to declare a variable with that type)

main.ts

import * as emitterFactory from 'emitter20';

var emitterInstance = emitterFactory();
emitterInstance.on(...);

types.d.ts

declare module 'emitter20' {
    interface IEmitter {
        on: (eventName: string, cb: (data?: any) => void) => void;
        trigger: (eventName: string, data?: any) => void;
    }

    interface IEmitterFactory {
        (): IEmitter;
    }

    var EmitterFactory : IEmitterFactory;

    export = EmitterFactory;
}

Method 2: Function + Namespace Combination

Pros:

  • Offers intellisense/autocomplete and type safety
  • Exposes the interface

Cons:

  • May seem mysterious? :)

main.ts

import * as emitter from 'emitter20';

var emitterInstance : emitter.IEmitter = emitter();
emitterInstance.on("event", (data : any) => {
    console.log(data.foo);
})
emitterInstance.trigger("event", {"foo": "bar"});

Output:

bar

types.d.ts

declare module 'emitter20' {

    function Emitter(): Emitter.IEmitter;
    namespace Emitter {
        interface IEmitter {
            on: (eventName: string, cb: (data?: any) => void) => void;
            trigger: (eventName: string, data?: any) => void;
        }
    }


    export = Emitter;
}

Method 3: Declare a Global Namespace

Pros:

  • Provides intellisense/autocomplete and type safety
  • Exposes the interface
  • Does not appear mysterious

Cons:

  • The Emitter namespace becomes global and visible everywhere (global ambient declaration)

main.ts

import * as emitter from 'emitter20';

var emitterInstance : Emitter.IEmitter = emitter();

types.d.ts

declare namespace Emitter
{
    export interface IEmitter {
        on: (eventName: string, cb: (data?: any) => void) => void;
        trigger: (eventName: string, data?: any) => void;
    }

    export interface IEmitterFactory {
        (): Emitter.IEmitter;
    }
}

declare module 'emitter20' {
    var EmitterFactory : Emitter.IEmitterFactory;

    export = EmitterFactory;
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Ways to change a value into int8, int16, int32, uint8, uint16, or uint32

In TypeScript, the number variable is floating point by default. However, there are situations where it's necessary to restrict the variable to a specific size or type similar to other programming languages. For instance, types like int8, int16, int32 ...

Throw TypeError: The `pipe` property of `ngrx/store` is undefined during testing

Here is the code snippet from my TypeScript file: this.store.pipe(select(subscribe.getRegCategories)).pipe(takeUntil(this.ngUnsubscribe)).subscribe(data => { if (data && data.length) { this.allRegCategories = data; ...

Different Ways to Modify Data with the Change Event in Angular 8

How can I dynamically change data using the (change) event? I'm attempting to alter the gallery items based on a matching value. By default, I want to display all gallery items. public items = [{ value: 'All', name: 'All Item ...

The predicament with arranging arrays

I'm working with an array that looks like this: [ { "TaskID": 303, "TaskName": "Test1", "TaskType": "Internal", "Status": "Processing", "IsApproved": false, "RowNumber": 1 }, { "TaskID": 304, ...

Elevate a counter value within a nested for loop in Angular framework

I need to update a count within a nested loop and display the value (e.g. 1,2,3,4) when the table loads. The challenge is that my objects have varying lengths, so with 100 rows in the table, the counter column should display numbers from 1 to 100. <ng- ...

Encountering a challenge with triggering a dialog box from an onClick event on a pie chart in Angular 8 when utilizing chart.js

I am currently using a chart.js pie chart to showcase some data. I have managed to display the required information in an alert box when a slice of the pie is clicked. However, I am now looking for a way to present this data in a dialog box instead. &a ...

How to Decode JSON Data in Angular 2/4 with the Help of HttpClientModule

I am receiving this JSON structure from my asp.net core API: { "contentType": null, "serializerSettings": null, "statusCode": null, "value": { "productName": "Test", "shortDescription": "Test 123", "imageUri": "https://bla.com/bla", ...

Unable to successfully import Node, JS, or Electron library into Angular Typescript module despite numerous attempts

I'm still getting the hang of using stack overflow, so please forgive me if my question isn't formulated correctly. I've been doing a lot of research on both stack overflow and Google, but I can't seem to figure out how to import Electr ...

Having difficulty forming queries correctly using TypeScript, React, and GraphQL

Apologies for the potentially naive question, but I am new to working with GraphQL and React. I am attempting to create a component that contains a GraphQL query and incoming props. The props consist of a query that should be passed into the GraphQL query. ...

Effectively managing intricate and nested JSON objects within Angular's API service

As I work on creating an API service for a carwash, I am faced with the challenge of handling a large and complex json object (referred to as the Carwash object). Each property within this object is essentially another object that consists of a mix of simp ...

NextAuth credentials are undefined and authentication is malfunctioning in React

I encountered the following issue: This is the code snippet that I am using: return ( <> {Object.values(providers).map((provider) => { if (provider.id === "credentials") { return null; } retu ...

Arrange the columns in Angular Material Table in various directions

Is there a way to sort all columns in an Angular material table by descending order, while keeping the active column sorted in ascending order? I have been trying to achieve this using the code below: @ViewChild(MatSort) sort: MatSort; <table matSort ...

Incorporating numerous query parameters in Angular version 14

I am currently working on developing a multi-item filter feature for my application and I am faced with the challenge of sending multiple query parameters in the API request to retrieve filtered items. My main concern is whether there is a more efficient ...

Sending the chosen dropdown ID to a different component

In my application, there is a component named list where I am showcasing all the names of my customers in a dropdown, as illustrated below: When a particular item (i.e., customer) is selected from the dropdown, I would like to emit that id to a method/fun ...

The 'data' property is absent in the 'never[]' type, whereas it is necessary in the type of product data

Hello, I am new to TypeScript and I'm struggling with fixing this error message: Property 'data' is missing in type 'never[]' but required in type '{ data: { products: []; }; }'. Here is my code snippet: let medias :[] ...

How can I achieve this using JavaScript?

I am attempting to create a TypeScript script that will produce the following JavaScript output. This script is intended for a NodeJS server that operates with controllers imported during initialization. (Desired JavaScript output) How can I achieve this? ...

Enhancing current interfaces

I'm exploring Koa and the module system in Node.js. Although I'm not asking about a specific koa question, all the code I'm working with involves using koa. In Koa, every request is defined by the Request interface: declare module "koa" { ...

What is the recommended way to handle data upon retrieval from a Trino database?

My goal is to retrieve data from a Trino database. Upon sending my initial query to the database, I receive a NextURI. Subsequently, in a while loop, I check the NextURI to obtain portions of the data until the Trino connection completes sending the entire ...

Retrieve a specific item from the ngrx/store

My Reducer implementation in my Angular 2 app is designed to store state items related to price offers for Financial Instruments, such as stocks and currencies. This is the implementation of my Reducer: export const offersStore = (state = new Array<Of ...

TS1086: Attempting to declare an accessor within an ambient context is not allowed

While using Angular, I encountered the error TS1086: An accessor cannot be declared in an ambient context. when using Javascript getters and setters in this Abstract Typescript class. Here is the code snippet causing the issue: /** * The current id ...