Skip to content

Commit

Permalink
add elm-duet readme sample
Browse files Browse the repository at this point in the history
  • Loading branch information
BrianHicks committed Apr 23, 2024
1 parent e0f4194 commit 66d36dd
Showing 1 changed file with 121 additions and 0 deletions.
121 changes: 121 additions & 0 deletions content/micro/thing-a-month-04-02.md
@@ -0,0 +1,121 @@
+++
title = "elm-duet"
date = 2024-04-23

[extra]
project = "thing-a-month (awareness)"
+++

Just a little update on tinyping: I have a system that mostly works! Yay! It doesn't do reporting, but it'll alert you about new pings correctly.

This actually doesn't feel like a huge win to me because it's so, so messy. I'm still using Automerge for this, and I think it's a solid decision, but taking the distributed nature of the system into account while building this has been both tricky and instructive.

<!-- more -->

Anyway, while making what I have of tinyping so far, I kept running into issues with syncing information across the Elm/TS boundary. It's really annoying to have to update the types on both sides. This has bugged me for a while across multiple projects, even at work, so I decided to dive down the rabbit hole and try to fix this. Here's a preview in the form of the current README of the project:

---

## elm-duet

Elm is great, and TypeScript is great, but the flags and ports between them are hard to use safely.
They're the only part of the a system between those two languages that aren't typed by default.

You can get around this in various ways, of course, either by maintaining a definitions by hand or generating one side from the other.
In general, though, you run into a couple different issues:

- It's easy for one side or the other to get out of date and errors to slip through CI and code review into production.
- Definitions in one language may not be translatable to the other (despite the two type systems having pretty good overlap.)

`elm-duet` tries to get around this by creating a single source of truth to generate both TypeScript definitions and Elm types with decoders.
We use [JSON Type Definitions](https://jsontypedef.com/) (JTD, [five-minute tutorial](https://jsontypedef.com/docs/jtd-in-5-minutes/)) to say precisely what we want and generate ergonomic types on both sides (plus helpers like encoders to make testing easy!)

Here's an example for an app that stores a [jwt](https://jwt.io/) in `localStorage` or similar to present to Elm:

```json
{
"modules": {
"Main": {
"flags": {
"properties": {
"currentJwt": {
"type": "string",
"nullable": true
}
}
},
"ports": {
"newJwt": {
"metadata": {
"direction": "ElmToJs"
},
"type": "string"
},
"logout": {
"metadata": {
"direction": "ElmToJs"
}
}
}
}
}
}

```

You can generate code from this by calling `elm-duet path/to/your/schema.json`:

```
$ elm-duet examples/jwt_schema.json --typescript-dest examples/jwt_schema.ts
wrote examples/jwt_schema.ts
```

Which results in this schema:

```typescript
// Warning: this file is automatically generated. Don't edit by hand!

declare module Elm {
namespace Main {
type Flags = {
currentJwt: string | null;
}

type Ports = {
logout: {
subscribe: (callback: (value: Record<string, never>) => void) => void;
};
newJwt: {
subscribe: (callback: (value: string) => void) => void;
};
}

function init(config: {
flags: Flags;
node: HTMLElement;
}): void
}
}
```
(Elm code generation is currently TODO.)
Here's the full help to give you an idea of what you can do with the tool:
```
$ elm-duet --help
Generate Elm and TypeScript types from a single shared definition.

Usage: elm-duet [OPTIONS] <SOURCE>

Arguments:
<SOURCE> Location of the definition file

Options:
--typescript-dest <TYPESCRIPT_DEST> Destination for TypeScript types [default: elm.ts]
--elm-dest <ELM_DEST> Destination for Elm types [default: src/]
--elm-prefix <ELM_PREFIX> Prefix for Elm module path [default: Interop]
-h, --help Print help
-V, --version Print version

```

0 comments on commit 66d36dd

Please sign in to comment.