dillonkearns / elm-ts-json Goto Github PK
View Code? Open in Web Editor NEWLicense: BSD 3-Clause "New" or "Revised" License
License: BSD 3-Clause "New" or "Revised" License
Currently I use
TsJson.Codec.stringUnion
[ ( "0", Reliable )
, ( "1", Lossy )
, ( "-1", Unrecognized )
]
to encode data for a JS library. The resulting type is "0" | "1" | "-1"
and I have to call parseInt
on the JS side to prepare data for the library. It would be nice to simply write
TsJson.Codec.intUnion
[ ( 0, Reliable )
, ( 1, Lossy )
, ( -1, Unrecognized )
]
to get 0 | 1 | -1
type and avoid extra conversion on the JS side.
Currently this library allow one to define typescript type and elm type and encoder & decoder from combinator (source code) written in elm.
Is there another way. That allow one to define elm type and encoder & decoder from typescript type (parse from source code) or combinator functions / ast written in typescript ?
Hi here, the CLI does not generate the ToElm type for my code.
Heres my Definitions file:
module PortDefinition exposing (Flags, FromElm(..), ToElm(..), interop)
import TsJson.Decode as TsDecode exposing (Decoder)
import TsJson.Encode as TsEncode exposing (Encoder)
interop :
{ toElm : Decoder ToElm
, fromElm : Encoder FromElm
, flags : Decoder Flags
}
interop =
{ toElm = toElm
, fromElm = fromElm
, flags = flags
}
type FromElm
= RegisterSounds (List Sound)
| PlaySound { sound : Sound, looping : Bool }
type ToElm
= SoundEnded Sound
type alias Flags =
{}
fromElm : Encoder FromElm
fromElm =
TsEncode.union
(\playSound registerSounds value ->
case value of
RegisterSounds list ->
registerSounds list
PlaySound args ->
playSound args
)
|> TsEncode.variantTagged "playSound"
(TsEncode.object
[ TsEncode.required "sound" (\obj -> obj.sound |> toString) TsEncode.string
, TsEncode.required "looping" .looping TsEncode.bool
]
)
|> TsEncode.variantTagged "registerSounds"
(TsEncode.list (TsEncode.string |> TsEncode.map toString))
|> TsEncode.buildUnion
toElm : Decoder ToElm
toElm =
TsDecode.discriminatedUnion "type"
[ ( "soundEnded"
, TsDecode.string
|> TsDecode.andThen
(TsDecode.andThenInit
(\string ->
string
|> fromString
|> Maybe.map (\sound -> SoundEnded sound |> TsDecode.succeed)
|> Maybe.withDefault (TsDecode.fail ("Unkown sound ended: " ++ string))
)
)
)
]
flags : Decoder Flags
flags =
TsDecode.null {}
--------------------------------------
-- Sound.elm
--------------------------------------
{-| Reprentation of Sound
-}
type Sound
= ClickButton
{-| List of all playable sounds
-}
asList : List Sound
asList =
[ ClickButton ]
{-| returns the path to the sound
-}
toString : Sound -> String
toString sound =
case sound of
ClickButton ->
"ClickButton.mp3"
fromString : String -> Maybe Sound
fromString string =
case string of
"ClickButton.mp3" ->
Just ClickButton
_ ->
Nothing
Heres the result:
export type JsonObject = { [Key in string]?: JsonValue };
export type JsonArray = JsonValue[];
/**
Matches any valid JSON value.
Source: https://github.com/sindresorhus/type-fest/blob/master/source/basic.d.ts
*/
export type JsonValue =
| string
| number
| boolean
| null
| JsonObject
| JsonArray;
export interface ElmApp {
ports: {
interopFromElm: PortFromElm<FromElm>;
interopToElm: PortToElm<ToElm>;
[key: string]: UnknownPort;
};
}
export type FromElm = { data : string[]; tag : "registerSounds" } | { data : { looping : boolean; sound : string }; tag : "playSound" };
export type ToElm = never;
export type Flags = null;
export namespace Main {
function init(options: { node?: HTMLElement | null; flags: Flags }): ElmApp;
}
export as namespace Elm;
export { Elm };
export type UnknownPort = PortFromElm<unknown> | PortToElm<unknown> | undefined;
export type PortFromElm<Data> = {
subscribe(callback: (fromElm: Data) => void): void;
unsubscribe(callback: (fromElm: Data) => void): void;
};
export type PortToElm<Data> = { send(data: Data): void };
As you can see ToElm = never
instead of generating my type.
Im using elm-ts-interop v0.0.8 and dillonkearns/elm-ts-json v2.1.1.
@neurodynamic thanks again for the feedback on the API.
To follow up on the Discourse discussion, I think it would make sense to use an opaque type like the UnionEncodeValue type.
The benefit is that the library then guarantees that all possible decoders that may continue on from the andThen
have been "registered". Otherwise, you could use any decoder within the andThen
as it is in its current state, and that will cause the decoder to have inaccurate type information.
Here's an example with the current API:
field "version" int
|> andThen
(andThenInit
(\version ->
case version of
1 ->
field "firstName" string
_ ->
at [ "name", "first" ] string
)
)
|> expectDecodes
{ input = """{"version": 2, "name": {"first": "Jane"}}"""
, output = "Jane"
, typeDef = "{ version : number }"
Note that the type def doesn't have any information about the firstName
or name.first
fields. This is inaccurate.
If you "register" the continuation decoders, as is intended, then you get nice, accurate TypeScript type information:
field "version" int
|> andThen
(andThenInit
(\v1Decoder v2Decoder version ->
case version of
1 ->
v1Decoder
_ ->
v2Decoder
)
|> andThenDecoder (field "firstName" string)
|> andThenDecoder (at [ "name", "first" ] string)
)
|> expectDecodes
{ input = """{"version": 2, "name": {"first": "Jane"}}"""
, output = "Jane"
, typeDef = "({ version : number } & { name : { first : string } } | { firstName : string })"
}
I think this is quite nice! The improvement here would be for the API to enforce using the API in this intended way.
That's where the opaque type comes in. Right now, the continuation takes a TsJson.Decode.Decoder
. If we require it to use a simple wrapper type around that type (let's call it AndThenDecoder
), then we can guarantee that the only way to get that wrapped type is by passing it through andThenDecoder ...
.
type AndThenDecoder a = AndThenDecoder (Decoder a)
succeed
and fail
I think these two types may be special cases. You can't "register" either of these with andThenDecoder
, because that would force you to have the success value or failure message when you register it. But that defeats the purpose of andThen
.
I think adding these functions to the API will solve that problem:
andThenSucceed : a -> AndThenDecoder a
andThenSucceed a =
AndThenDecoder (succeed a)
andThenFail : String -> AndThenDecoder a
andThenFail errorMessage =
AndThenDecoder (fail errorMessage)
As far as the resulting TypeScript type defs from a Decoder, using succeed
or fail
within andThen
doesn't change that. So I think this design will work nicely!
The generated typescript file contains the following lines:
export as namespace Elm;
export { Elm };
which raises the following error when I try to build:
gen/mainBack.d.ts:36:10 - error TS2686: 'Elm' refers to a UMD global, but the current file is a module. Consider adding an import instead.
36 export { Elm };
~~~
removing this export {Elm }
line makes Typescript happy.... Is it necessary to add this export { Elm }
in the generated file? If yes, how could I solve this error and be able to build without performing a ugly sed
?
If you have a custom type and Codec like this:
type MyType
= MyType Int
codec : Codec MyType
codec =
Codec.custom Nothing
(\vMyType value ->
case value of
MyType int ->
vMyType int
)
|> Codec.namedVariant1 "MyType" MyType ( "needs-quotes", Codec.int )
|> Codec.buildCustom
it will generate this TypeScript type (missing quotes around missing-quotes
):
{ needs-quotes : number; tag : "MyType" }
here is an ellie
BTW: thanks for this project ๐
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.