The source of truth is the exposed interop
record in InteropDefinitions.elm
.
import TsJson.Decode as TsDecode exposing (Decoder)
import TsJson.Encode as TsEncode exposing (Encoder)
interop :
{ toElm : Decoder ToElm
, fromElm : Encoder FromElm
, flags : Decoder Flags
}
The names of the types, ToElm
, FromElm
, and Flags
are just a convention. elm-ts-interop
only needs a record with an Encoder (fromElm
) and Decoders (toElm
and flags
).
toElm : Sub (Result Json.Decode.Error InteropDefintions.ToElm)
We still need to handle decoding errors because it's possible to bypass the TypeScript compiler, use any
types, etc, see [TypeScript's Blind Spots].
It's possible to use Elm types besides Json.Decode.Value directly in vanilla ports and flags. But your application will crash if your flag types are incorrect, and you then have no way to handle those errors in your Elm code. It also doesn't allow you to handle custom types, or decouple the Elm types from the shape of the JSON. So decoding using JSON decoders is considered a best practice, and elm-ts-interop
builds on this approach and adds extra type-safety and convience.
type Msg =
AuthenticatedUser (Result Decode.Error InteropDefinitions.ToElm)
-- | ...
subscriptions : Model -> Sub Msg
subscriptions _ =
InteropPorts.toElm |> Sub.map AuthenticatedUser
avatarUrl
field in ToElm
type ToElm
= AuthenticatedUser User
type alias User =
{ username : String
, avatarUrl : String
}
toElm : Decoder ToElm
toElm =
TsDecode.discriminatedUnion "tag"
[ ( "authenticatedUser"
, TsDecode.map AuthenticatedUser
(TsDecode.field "user"
(TsDecode.map2 User
(TsDecode.field "username" TsDecode.string)
(TsDecode.field "avatarUrl" TsDecode.string)
)
)
)
]