Instantiate Javascript classes that expect "new" keyword on KotlinJS

717 Views Asked by At

considering the following javascript code (partially taken from Apollo Server documentation), it creates an instance of ApolloServer and start it.


const {ApolloServer} = require('apollo-server')

const server = new ApolloServer({ ... });

server.listen().then(({ url }) => {
  console.log(`Server ready at ${url}`);
});

Now consider to replicate the same behaviour using KotlinJS. Firstly, Kotlin doesn't have the "new" keyword and calling ApolloServer() as expected, won't work but raise an error (TypeError: Class constructor ApolloServer cannot be invoked without 'new').

// We can banally represent part of the code above like:
external fun require(module: String): dynamic
val ApolloServer = require("apollo-server").ApolloServer

// ApolloServer is a js class

Declaring an external class like:

external open class ApolloServer() {
    open fun listen(vararg opts: Any): Promise<Any>
    operator fun invoke(): Any
}

and set it as ApolloServer type doesn't help.

How do we replicate "new ApolloServer()" call?

1

There are 1 best solutions below

3
Yak O'Poe On BEST ANSWER

To solve this problem I found an interesting approach based on JsModule annotation. We need to create a Kotlin file that represent that javascript module we want to import, in my case "apollo-server".

@file:JsModule("apollo-server")
@file:JsNonModule
package com.package

import kotlin.js.Promise

external interface ServerInfo {
    var address: String
    var family: String
    var url: String
    var subscriptionsUrl: String
    var port: dynamic /* Number | String */
        get() = definedExternally
        set(value) = definedExternally
    var subscriptionsPath: String
    var server: Any
}

external open class ApolloServer(config: Any? /* ApolloServerExpressConfig & `T$0` */) : Any {
    open var httpServer: Any
    open var cors: Any
    open var onHealthCheck: Any
    open var createServerInfo: Any
    open fun applyMiddleware()
    open fun listen(vararg opts: Any): Promise<ServerInfo>
    open fun stop(): Promise<Unit>
}

With the above code we are basically describing what we expect to find in the apollo-server module and how to map it into Kotlin.

In our Kotlin main function we don't have to specify any require(...) but just use our ApolloServer class like:

    ApolloServer(null).listen().then {
       console.log(it)
    }

Using this approach Kotlin would transpile it correctly, using the new keyword in javascript.

Transpiled version extract:

  function main$lambda(it) {
    console.log(it);
    return Unit;
  }
  function main() {
    (new ApolloServer(null)).listen().then(main$lambda);
  }

This code is just an example, ApolloServer won't be initialized without a proper configuration, this case, for example, contains a nullable configuration.