Using Xstate with Deno

6/24/2021

Deno is very nice and it is here for a while. And it is very easy to use JavaScript or TypeScript code from anywhere that could be fetchable.

I'll start creating a new directory called xstate-deno and jumping right into in:

$ mkdir xstate-deno && cd xstate-deno

Then I'll create the machine.ts file and import the createMachine function from xstate:

machine.ts
import { createMachine } from "https://esm.sh/xstate@4.18.0"

Yeah! We can import any library that do not depend of any Node.js or Browser specific code.

Now I will specify the lightMachine:

machine.ts
import { createMachine } from "https://esm.sh/xstate@4.18.0"

export const lightMachine = createMachine({
  id: "toggle",
  initial: "inactive",
  states: {
    inactive: { on: { TOGGLE: "active" } },
    active: { on: { TOGGLE: "inactive" } },
  },
});

Personally I didn't like to have those long importings using the full url to the dependency. With Deno import maps it is possible to define a lookup table, where Deno will use to find dependencies during the compilation time.

I will create a json file called import_map.json:

import_map.json
{
  "imports": {
    "xstate": "https://esm.sh/xstate@4.18.0"
  }
}

Now it is possible to import createMachine right from xstate as we could to in Node.js. I will update the machine.ts file with the mapped import.

machine.ts
import { createMachine } from "xstate";

export const lightMachine = createMachine({
  id: "toggle",
  initial: "inactive",
  states: {
    inactive: { on: { TOGGLE: "active" } },
    active: { on: { TOGGLE: "inactive" } },
  },
});

Cool, right? 🔥

Now I will create the server.ts file and write a simple Deno web server.

server.ts
const server = Deno.listen({ port: 8000 });

async function main() {
  for await (const conn of server) {
    const httpConn = Deno.serveHttp(conn);
    for await (const requestEvent of httpConn) {
      if (/\/toggle$/.test(requestEvent.request.url)) {
        requestEvent.respondWith(
          new Response("toggle", {
            status: 200,
          })
        );
        continue;
      }

      requestEvent.respondWith(
        new Response("404", {
          status: 404,
        })
      );
    }
  }
}

await main();

So... now I will import the lightMachine and start the interpretation service.

server.ts
import { interpret } from "xstate";
import { lightMachine } from "./machine.ts";

const server = Deno.listen({ port: 8000 });

async function main() {
  const lightService = interpret(lightMachine)
    .onTransition((state: any) => {
      console.log(state.value);
    })
    .start();

  for await (const conn of server) {
    const httpConn = Deno.serveHttp(conn);
    for await (const requestEvent of httpConn) {
      if (/\/toggle$/.test(requestEvent.request.url)) {
        lightService.send("TOGGLE");
        requestEvent.respondWith(
          new Response("toggle", {
            status: 200,
          })
        );
        continue;
      }

      requestEvent.respondWith(
        new Response("404", {
          status: 404,
        })
      );
    }
  }
}

await main();

Now I can start the server in the command line:

$ xstate-deno> deno run --unstable --importmap=import_map.json --allow-net server.ts
Check file:///C:/Users/gustavo/Workspace/code/xstate-deno/server.ts
inactive

The initial state of the machine is inactive. and it is logged to the standard out when the interpretation start. But if I make a get request at http://localhost:8000/toggle, the TOGGLE event will be sent to the machine and the state will change from inactive to active.


👈 All blog posts📝 Edit this page

Have a sublime day