looking into building a cross platform http client...
# javascript
b
looking into building a cross platform http client, there's significant friction with regards to ktor and kotlin APIs like Result which are not exportable to JS; do you have any recommendations on how to handle instantiating the client from the JS side and converting json responses from Ktor to JS? Typically you throw an exception containing the parsed JSON body in case of a 400/500 response, but RuntimeExceptions can't have type parameters, nor do we have union types
1
e
The only thing I can tell you is you'll write a lot of wrapping code. I do something similar with TCP sockets (tho, not with Ktor).
b
I've been thinking about going with sealed interfaces to model the different response types
e
Don't do that yet. You'll pollute your TS codebase with
instanceof
checks.
b
the other approach would be a Result<200Reponse, AllErrorResponses>
but Result is not exportable
e
The problem is you can't model type guards from Kotlin. You'll always need a small layer of TS code for proper ergonomic types.
b
right, you'd need something like function is400(response): response is CustomError { return response.code === 400;}
e
Correct. Don't overengineer right now, that's my suggestion. Also, think if what you want to accomplish makes sense from a point of view that isn't yours. Are you 100% sure you need this to be exported to TS?
b
yes
could always go with dynamic ofc
e
Then you're kinda limited in the tool set. Even with dynamic you can't model all TS types, so you're still limited.
b
and js inline JSON.parse()
the other issue I'm having is creating and sharing a single instance of a Ktor Http client
do people usually use a wrapper class for that?
that needs to expose the client to kotlin though in some fashion
but not to JS
e
I would not attempt to self-manage instances. In my case I went with factories, and I delegate instance management to the consumer, so that there is full clarity on how the code will be compiled/bundled.
b
what do you return from those factories?
e
A fresh socket instance. I don't manage it internally.
The socket instance can be passed to other exported functions through its interface, if necessary.
Global state in JS is too brittle, that's why I avoid it.
b
Copy code
@JsExport
class KtorClientWrapper(val someConfig: String) {
    internal val client = HttpClient {
        install(ContentNegotiation) {
            json(Json {
                explicitNulls = false
            })
        }
    }
}

@JsExport
class SomeClient(val client: KtorClientWrapper) {
    fun request() {
        <http://client.client.post|client.client.post> { ... }
    }
}
thinking of something like this
e
Another example: pooling. I don't manage pooling on my own. I export a factory to create a pool, which is then managed by the API consumer.
Yes, more or less. I tend to prefer exporting functions + interfaces instead of classes.
b
yeah, I understand, thank you for your inputs
blob no problem 1
e
Classes limit your extensibility and refactoring abilities. You'll always need to refactor two languages. Functions and interfaces give you more freedom over time.
Downside: more Kotlin code.