Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to merge namespace has no exported interface in TypeScript

I am using the queue lib Bull in TypeScript. Its definition is:

node_modules/@types/bull/index.d.ts

declare const Bull: {
  (queueName: string, opts?: Bull.QueueOptions): Bull.Queue;
  // something like above
};

declare namespace Bull: {
  interface Queue {}
  interface Job {}

  // some other non-exported interfaces
}

export = Bull

I want to merge the namespace Bull in my library and use it in another app.

node_modules/myLib/index.d.ts

import { Queue } from 'bull'

declare namespace Bull: {
  export interface Queues {}
}

export interface myInterface {
  foo: Queue | Bull.Queues
}

export = Bull

myApp/foo.ts

import { Job, Queues } from 'myLib' // Error, 'myLib' has no exported member 'Job'

According to the doc, namespace is a GLOBAL variable, and namespaces of the same name will merge their EXPORTED interfaces. So, how can I merge the namespace Bull from @types/bull? Thanks!

like image 661
Brick Yang Avatar asked Apr 09 '18 02:04

Brick Yang


1 Answers

Well, the truth is that @types\bull is not really declaring a namespace.

Well, it is, but just to group a list of related types and export them together as the default export, so, what it really exports are the contents of the namespace, not the namespace itself. That's why you can import Queue, and use Queueand not Bull.Queue, which is what you should have to do if Queue truly belonged to a namespace.

Besides, I don't know what version of TypeScript you're using, but you shouldn't be able to use export (...) and export = (...) in the same file. Besides, when you add export to a file, it turns into a declaration file for a module, so, in the end, you have a module that exports a namespace as default, and then you can import Queues from myLib, but not Job, as Job does not appear anywhere in the file and therefore it is not exported.

To be able to merge namespaces in different files, you can't use imports or exports, just declarations, because two modules can never contribute names to the same namespace. By using export, you're turning your files into modules, and once you do so, namespaces in them do not belong to the global scope anymore, so even if they have the same names, they really belong to the scope of their own module and do not merge.

To do what you're trying to do, you'd have to have:

bull:

declare const Bull: {
    (queueName: string, opts?: any): Bull.Queue;
    // something like above
  };

declare namespace Bull {
    interface Queue {}
    interface Job {}

    // some other non-exported interfaces
}

myLib:

declare namespace Bull {
  export interface Queues {}
}

declare interface myInterface {
  foo: Bull.Queue | Bull.Queues
}

And now you truly have one namespace with the contents of both declarations:

test.ts:

const a: Bull.Queues = {};
const g: Bull.Queue = {};
const b: Bull.Job = {};

This way it would work, but, unfortunately, is not what you have. You should define myLib as:

import * as Bull from './bull';

export interface Queues {};


export interface myInterface {
  foo: Bull.Queue | Queues;
}

and then you can use:

import * as Bull from './bull';
import { Queues, myInterface } from './myLib';

const a: Queues = {};
const g: Bull.Queue = {};
const b: Bull.Job = {};

const instance: myInterface = null;

or, if you prefer,

import * as Bull from './bull';
import * as ExtendedBull from './myLib';

const a: ExtendedBull.Queues = {};
const g: Bull.Queue = {};
const b: Bull.Job = {};

const instance: ExtendedBull.myInterface;

But, in any case, you need to import from both bull and myLib.

like image 152
Oscar Paz Avatar answered Oct 02 '22 00:10

Oscar Paz