0

I'm trying to define the TypeScript type definitions for the StarUML tool. I've managed to make it work for a good part of the API but I'm getting stuck on the following problem: how to make the link between a (JavaScript) global variable ("type" below) and a TypeScript namespace containing classes?

(A) The problem to solve

StarUML makes available a global variable type that register hundreds of class coming from unknown places. For instance type.Element is a class (not an Element!) as well as type.Model. We use these two types as an example below:

  • In JavaScript, these classes are mostly used in statements like if (x instanceof type.Element).
  • With TypeScript, I want to be able to define signatures like f(e : type.Element) (I would be happy to remove the type prefix but that's another story) and want to have intelli-sense for expressions like myElement._id (_id is an attribute of the class Element).

(B) First attempt: modeling "type" as a variable

I first tried to define type as being a variable (as this is actually a variable):

// type.d.ts

declare class Element {
    _id: string
    // ...
}
declare class Model extends Element {
    name: string
    // ...
}

declare const type = {
    "Element" = Element,
    "Model" = Model
    // ...
}

This does not work, as it produces the following error:

S1254: A 'const' initializer in an ambient context must be a string or numeric literal or literal enum reference

I mention this solution because it makes it clear what type is all about: a register that gives for each class name (string), and the class itself. The classes are defined somewhere else in an unknown place.

(C) Second attempt: modeling "type" as a Namespace.

After reading the TypeScript documentation, and after various trials, I came up with the following TypeScript file types.d.ts (That might be where I'm wrong).

// types.ts
export namespace type {
    class Element {
        _id: string
        // ...
    }
    class Model extends Element {
        name: string
    }
    // ...
}

(D) Client code

Below is an example code (main.ts) that uses this API definition. To simplify the file type.d.ts and main.ts are at both at the top level.

// (1)     /// <reference path="./types.d.ts" />
// (2)     import {type} from "./types"
// (3)     declare var type

function hello(m: type.Element): void {
    console.log("    hello: (" + e._id + ')')
}
console.log(type)
console.log(type.Element)

I don't manage to "make it work" I've tried various combinations uncommenting some of the first 3 lines (see below).

(D.2) My expectations

  • (a) the type in the function hello should be defined properly (TypeScript)
  • (b) the intelli sense should work on the next line e._id (TypeScript)
  • (c) the last line should display the type.Element class (JavaScript)

I can't make it work all at the same time, irrespective of the "importing" first lines.

(D.3) What I got

  • (1) I didn't manage to make line (1) /// <reference ... "work" at all. I also tried solutions provided in forums such as using tsconfig with typeRoots and paths. I don't know if the solution should come from there.

  • (2) import {type} ... is ok for the namespace but then line console.log(type.element) return undefined at runtime.

  • (3) declare var type makes the JavaScript code run ok, but in conflict with (2).

When (2) and (3) are present at the same time, a TypeScript error is generated because of conflict between type as namespace and type as variable.

(D.4) What is the solution?

After reading the TypeScript documentation and some other blogs I'm still confused. I don't know if the problem is in my approach (C), "modeling the variable type" as a namespace, or I don't know how to call this namespace/variable at compilation/runtime.

Toastrackenigma
  • 7,604
  • 4
  • 45
  • 55

1 Answers1

0

Caveat: I'm only middling-good at TypeScript, and less so with regard to .d.ts files. But this worked locally and seems to match up with the documentation, in particular this example.

A .d.ts file defines ambient declarations, you don't use export in it (as far as I know), you just declare things in it, since the purpose is to declare things that already exist (in your case, because of the SmartUML library creating that type global and its properties at runtime).

So in your .d.ts file, you declare a namespace containing those classes:

/**
 * SmartUML namespace global.
 */
declare namespace type {
    /**
     * SmartUML `Element` class.
     */
    class Element {
        /**
         * The ID fo the element.
         */
        _id: string;
        // ...
    }
    /**
     * SmartUML `Model` class.
     */
    class Model extends Element {
        /**
         * The name of the model.
         */
        name: string;
        // ...
    }
    // ...
}
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • thanks you very much ! This is what I needed. No everything is fine. I had also issues with my but now thanks to your explanationI much better understand how it works. Thanks ! – escribis Jul 16 '22 at 16:44