How to define the key type for Js.Dict.t?


I’m trying to write bindings for xstate, and it expects the following data structure:

const lightMachine = Machine({
  key: 'light',
  initial: 'green',
  states: {
    green: {
      on: {
        TIMER: 'yellow',
    yellow: {
      on: {
        TIMER: 'red',
    red: {
      on: {
        TIMER: 'green',

The keys in the states dict, and the values of the on dict must be values of the same “set”. I tried modeling this set as a variant, and using Js.Dict.t for on, and states with the following code:

type machine;

type gameState =
    | PickFromSpot
    | PlaceToHolding

[@bs.deriving abstract]
type state = {
    name: gameState,
    on: Js.Dict.t(gameState)

[@bs.deriving abstract]
type machineSettings = {
    key: string,
    initial: gameState,
    parallel: bool,
    states: Js.Dict.t(state)

[@bs.module "xstate"] external machine: machineSettings => machine = "Machine";

let state1 = state(

let states = Js.Dict.fromList([
    ("state1", state1) /* This is the messed up part */

let machine = machine(machineSettings(

link to playground

It does not work, since on line 31 I would need to refer to a gameState, which I can’t.

I feel like the solution would be to use some kind of Map<gameState, state>, but I can’t find a way to do that with Js.Dict.t.

Any suggestions?


Using a variant for the states will save you a good chunk of typing.

type light =
  | Green
  | Yellow
  | Red;

// Your state calls the next light in the cycle
let timerSetFor = x => switch (x) {
  | Green => Yellow
  | Yellow => Red
  | Red => Green

type state = {
  timer: Js.Date.t,
  light: light

Because of pattern matching you can always iterate through the light types values and generate matching json to correspond with it.

If you want to go Map route, is probably more robust than Js.Dict (and you can use arbitrary values as keys). It’ll just potentially be more work to integrate with existing JS libs.