In the new JS IR, is there a Kotlin type (or an `e...
# javascript
a
In the new JS IR, is there a Kotlin type (or an
external interface
definition) that gets exported as a JS/TS
Map
with specific key/value types?
b
Hmm, isn't Map doing that already? Otherwise you could try declaring external Record<T> ftom ts stdlib
a
Hm if I can use Kotlin Maps, that would be slick, but I was assuming it wouldn't work based off of this:
Kotlin collections (List, Set, Map, and so on) are not mapped to any specific JavaScript type.
I think what I really want is Index Signatures: https://www.typescriptlang.org/docs/handbook/2/objects.html#index-signatures Record is similar to that though so it might be a good option
b
You could build something custom on top of Json type (since it's mapped to anonymous js objects)
a
Yeah, I already got things in a shape that's compatible with Record and Index-Signature! Now I'm just trying to figure out how to get them exported the “right” in TS is 😄 I can pull a Record definition from kotlinx-wrappers and it works, but I can't find one for Index Signatures, which seems to be preferred in our TS codebase
Aha! So with a Record, TS assumes that you handle ALL values of the key type. If I say I'm exporting a
Record<string, number>
then TS assumes that any string you put in will emit a non-null number. I think I instead want a
Partial<Record<string, number>>
b
Copy code
Partial<Record<string, number>> == Record<string, number | undefined> 
// Which in kotlin could be expressed as either `external interface Record<String, dynamic>` or `external interface Record<String, Number?>`
a
I knew I could count on @Big Chungus to pop in with the answer! Thanks! 🙇
😀 2
So within TS, a a
Record<K, V>
has required keys, and is supposed to be exhaustive over
K
Partial<Record<K, V>>
has optional (not just nullable) keys. My goal was to extend my types by replacing
K
with a type within my TS codebase, but using
Record
wasn't happy because it was forcing me to be exhaustive in TS I was able to work around it by just defining an •
external interface Partial<T>
and •
typealias JsMapLikeObject<K, V> = Partial<Record<K, V>>
b
If only kotlin would introduce undefined as a keyword for js same as dynamic... But hey, you got there anyways!
s
@ankushg How did you replace
kotlin.collections.Map<>
in
d.ts
file to something that would work in
TS
? In our case, we use
kotlinx.serialization
and we have a
JsExported
data class which has a
Map
object. We're struggling to make this work for all platforms smoothly, and we're defining this class in
commonMain
sourceSet.
Same thing happens with
kotlin.js.Promise
where it shows up in generated TS definition file but typescript compilation errors out because it can't find
kotlin.js.Promise
. Replacing it with just
Promise
works
a
For maps. you can convert them to a `Partial<Record<K, V>>`: https://gist.github.com/ankushg/a5f8531e8548fb27ece4d65450eb0c37 very rough — been meanign to polish it up for a blog post
s
Where would that conversion happen though? I'm confused about that part. For example, if we define a class like this in
commonMain
Copy code
@JsExport
@Serializable
data class Xyz(val map: Map<String, String>)
it would show up as
map: kotlin.collections.Map
in
d.ts
file
I understand that if we're just exposing something from
JsMain
with
JsExport
then I can convert kotlin Map into
Record
using your gist code
a
Yeah.. we currently have to convert classes that contain Maps to a JS-specific shape for exporting to JS 😕
👍 1
s
Thanks for responding @ankushg.That makes sense then
We're not alone in this situation 😞
b
github.com/mpetuska/kon has bidirectional converters for maps that might be useful here
a
What does the typescript definition look like for that? Is it just
any
or
unknown
or something?
I like that my jank
Partial<Record<K, V>>
approach enforces some amount of typed-map-like behavior when consuming in TS, but would love to drop it if there’s a better way to do it 🙂
b
Good point. Currently it's reusing Json type from kotlin stdlib, so not sure what it exports as. Shame there's no TS Record in kotlin stdlib
a
Even with a TS
Record
, there’s a sneaky assumption that every key will return a value, which doesn’t match how a Map works. With a Kotlin or JS Map,
myMap[someKey]
returns a nullable/optional type if
someKey
isn’t in the map With a TS
Record
type, every
myRecord[someKey]
is expected to always have a non-null matching value. The TS typechecker not only doesn’t tell you to null-check the value, but it actively discourages you from trying to null-check it, which can easily crop up some bugs The reason I use
Partial<Record<K, V>>
is so because that exposes a type with map-like semantics, so the TS consumer is expected to actually null-check when accessing a key from the map
b
How about Record<K, undefined | V>
Less verbose, and semantically equal to partial mixin