Use XState With Netlify Edge Functions
State machines at the edge? Yes please! David Khourshid is back to show us how to deploy and visualize workflows in real-time!
Links & Resources
Click to expand the full transcript
Captions provided by White Coat Captioning (https://whitecoatcaptioning.com/). Communication Access Realtime Translation (CART) is provided in order to facilitate communication accessibility and may not be a totally verbatim record of the proceedings.
JASON: Hello, everyone. And welcome to another episode of Learn With Jason. Today on the show, we're bringing back David Khourshid. How you doing, David?
DAVID: I'm doing great. How are you doing?
JASON: I'm doing great. I'm super excited to have you back on the show. It's always a blast whenever you're around. And so I'm especially excited for the stack we're going to play with today. Sounds like science fiction. So I'm very excited for that, but before we jump into what that is, let's talk a little bit about you. For folks who haven't seen the show before or aren't familiar with your work, do you want to give us a bit of a background?
DAVID: Yeah, sure. So I am an ex-Microsoft engineer, I guess you could call it that. Now I'm the founder of a company called Stately.ai. What we're doing at Stately is I created this lab called XState which allows you make state machines and use them on the web, both on the front end, and as we'll see today, they're also useful on the server side. So basically, I turned that into a company, where we're just trying to make application logic visual and collaborative to everyone. So yeah, that's what I've been up to.
JASON: That's very -- yeah, and so state machines are one of those things that -- I have always been fascinated by state machines. I think you were the one who introduced me to them. Because they take things that are really hard to explain or things that seem really easy to explain but actually aren't and make them visual. For me, I always do a better job of understanding things when I can sort of see a float chart. I'm always asking somebody, can you show me, how did these steps fit together. What I love about state machines is that's a process that I would have traditionally done on a white board. I'd start drawing boxes and arrows and labeling everything out. You always miss a step. You always forget something. You jump over. You're like, oh, yeah, then they log in and you just skip all the steps in the middle where somebody has to log in and all the failure states or invalid states or those starts of things. Then you approve the scope. Oh, yeah, this is going to be no problem at all. Start building the thing. Oh, my god, we skipped so many steps. When you start doing this in a state machine, you suddenly realize, oh, wait, you can't just jump from not logged in to on the dashboard. There's the whole flow to get somebody through. And it makes, you know -- I think you put it in a way that stuck in my brain, which is it makes hidden complexity visible in a way that our standard programming models don't. Like in a standard programming model, you would just make a Boolean or just kind of jump ahead, right. But with state machines, what I found really interesting is that you do this work of diagramming. You're writing down all these pieces, all these steps, all these flows. You show somebody, and they go, great, this makes sense. Let's build it. You go, I already did. And you just put that into your app. Now your state machine powers all the logic in your app. So this isn't a new concept, though, right? State machines have been around for a long time.
DAVID: Yeah, so they've been around for -- I mean, modern computers wouldn't even be around without state machines. They're that fundamental, both at the hardware and software level. And it's only been recently, like in the last couple decades, where we've really started to see state machines being used more and more in the UI. So before they were used a lot in game logic, in embedded electronics, and things like that. So I think the real revolution nowadays is just using state machines for things that we would typically just code normally. By code normally, I mean we're already implicitly making these state machines. By using state machines, like you said -- well, like you said what I said -- we're making that logic clear, right? So yeah, and so that's why a lot of people say, like, oh, this state machine, it seems a lot more complicated than if I were just to write a long series of async await statements. However, the state machine isn't introducing any complexity. It's just revealing that complexity and saying, hey, you might have missed something. I posted a tweet recently, by the way, where basically I was talking about async awaits. Like, I've seen it so often where we would have a chain of await statements without any catches. Or we would assume it's a linear process. You do this, then that, then that, and then eventually you get some sort of end result. But I mean, the fact of the matter is that logic is not always that simple. So something we're seeing more and more at Stately is that, yes, the argument can be made, especially if you're doing apps in the front end that, oh, my components or my logic is too simple to warrant a state machine. However, looking on the server side, all of a sudden you have a lot of complex business logic where essentially you do need a state machine, and I've even found, honestly, there's more use cases for state machines on the back end than there are on the front end.
JASON: Oh, interesting.
DAVID: Yeah, that's one of the big realizations we've made.
JASON: Yeah, that's really fascinating. And so that's sort of what we're talking about today, right? And because I'm just going to say out loud what you said to me. When you said it, my whole brain was like, what? But you were talking about building a state machine that runs at the edge, so in Netlify Edge Functions, and we're going to power that with PlanetScale's new serverless database driver. So we're building a database edge state machine. And what did you -- you had a name for it that I missed when you were giving me the original pitch here. You were saying like what it is. Like a workflow or something.
DAVID: Yeah, yeah, I guess a serverless state machine powered work flow on the edge. I actually forgot what I called it. We can go with that.
JASON: Whatever it is, I'm really, really excited about this because it's just a use case I'd never really considered. And I know we saw, was it Eric Rasmussen, gave this really wonderful talk about how your router can go a state machine and how you can use the actual URLs in your app as a state machine. It was kind of put together this really fascinating talk. I should find that and share a link. But there's these really amazing ways that are unexpected to me that state machines start to play in. It's just really, really fascinating. So let me open this up here. Don't make any noise. It's going to try to make noise. Don't you do it. All right. So here's -- ah, damn it. Just always making noise. So here's a link to Eric's talk if anybody wants to watch that. He gave that talk at Remix Conf. It was a great talk. And we're going to be doing something that is sort of similar but not the same at all. Like, we're going to be playing with the same idea of state machines somewhere that's not front end. Obviously, in retrospect, I'm like of course, state machines work everywhere. But I think until I saw that talk from Eric, it had never actually -- like the lightbulb hadn't gone on. Where I was like, oh, yeah, state machines are super useful in all sorts of applications, not just like making sure that my dropdowns are never in an impossible state of being, both open and closed. Or whatever it is that you can do if you've only got --
DAVID: So many more use cases than that, yeah.
JASON: So let's talk a little bit through just the abstract of what we're going to try to build today. So we've got a state machine. We've got Netlify Edge Functions. We've got a database. What do we want to accomplish by combining these tools?
DAVID: Basically, just a disclaimer. This is going to be proof of concept. You obviously want to write more robust code. The long story short is let's say you have some business logic that isn't just a straightforward async await. You have many steps. It can pause. You want to add human in the loop. You want to add branching to your logic. Basically, Zappier on steroids, but in a visual way. So let's say you have this application logic. You want to represent it as a state machine just because, like you said, it's a flowchart. You could show it to your PM, designer, and be like this is the application logic that you told us and that we're representing faithfully in code. So let's say that you have that in a state machine. Let's say that you want to run that on the edge. So we're going to see why it's actually really cool that it's running on the edge rather than just another serverless function.
JASON: Mm-hmm. That's so cool.
DAVID: So that's how all the parts come together basically.
JASON: I love it. I love it. I can't wait. Okay. So I feel like there's a lot that I could talk about in the abstract, but it's going to be easier if we just look at it. So let me switch us over into code mode. We'll head over to pair programming. I'm going to pull over -- we need one browser window here. Let's get this one over. And make it the right size. There we go. And I'm going to just do a quick shout out to our live captioning. We've got Rachel here with us today. Thank you very much, Rachel, from White Coat Captioning. And that is made possible through the support of our sponsors, Netlify, Nx, and New Relic. If you want to follow along with the live captioning, it is on the homepage of the site, like it is every episode. And we are talking today to David Khourshid, who is Davidkpiano on the old tweeter. Make sure you go and give a follow. Okay. So where's the right place to start here? Where should we kick off?
DAVID: So, I would say the first thing we're going to do is we're just going to create a new Netlify Edge Function.
JASON: Okay, great. Let me -- let's make a new directory. We're going to call this -- we'll call this state machine edge function workflow. Sure. And then we're going to go into it. I'm going to git init. And then we'll Netlify init.
DAVID: I'm actually probably going to learn some stuff here too.
JASON: You know what, I'm going to actually hold off on that because we're not quite ready. We'll deploy to git first. So we've gotten a empty folder here. What I'm going to do is open this up in my VS code.. so I'm going to create Netlify edge functions, and what should we call this?
DAVID: Workflow. Let's just call it workflow.
JASON: Workflow.ts so we've got ourselves an edge function here. And then the next thing that we need to do is we'll get a Netlify.toml so that we can actually use this. I need to look this up now. Is it edge function or edge functions? I always forget.
DAVID: So it's plural, funny enough.
JASON: Okay. It's plural. Great, great, great. Oh, right, because it's an array. So we are pummeling our way to victory. And we're going to choose a path. Our path for now can be the route. And our function will be -- let me check my TOML one more time here just to make sure I'm doing this right. Documentation. Path function, yes. Okay. So we do a function. That function is going to be workflow. No extension required. Now, whenever we run this, whenever we hit the root path, it's going to execute this workflow function. So we can start async function. Just going to double check this one more time to make sure I'm not missing any pieces. Export default, yes. Okay. So we're going to export default function. It's going to return a new response that says "hello from the edge." So that's our default function. This should be about as bare minimum as we can get. So let's try to run it.
DAVID: I love how easy it is, by the way. I only started playing with Netlify Edge Functions recently. It's just a breeze.
JASON: This is like one of the pieces that always makes me smile. I remember, you know, years ago trying to do this stuff where you'd see something cool, and you're like, I want to try this. Then you'd have to spend a whole Saturday trying to get your development environment configured so it could run, then you got to play. I love with new tools, we just get to -- okay, one file, I'm running. I love it. (Laughter) So here we go. We've got our edge function running. Whenever you hit the homepage, it's that. And this is all there is to it. We just have to put together the edge function itself and tell Netlify to run it. And we're off to the races here. What should we do next?
DAVID: Yeah, so we've done the hello world with Netlify Edge Functions, so let's do a simple hello world with XState.
DAVID: And I've been living in Node world for so long, so I actually had to figure out how to do things the Deno way. But we're going to create a machine from XState. So that's going to be imports create machine. Just pull that from the object. And that's going to be from https://cdn.skypack.dev/XState. Unless there's a better one.
JASON: I don't think it's matters, as long as it's an es module.
DAVID: Which this one should be.
DAVID: All right. So now above our function, let's make a machine. So I think you have the XState extension.
JASON: I do.
DAVID: So delete line three. There's something new that one of our engineers actually --
JASON: I'm just going to show everybody -- where is it? Search. Where you at? Okay. So this is the extension, for anybody who's interested. And I will open it over here so I can share a link. This will give you XState power-ups in VSCode. So I'm going to delete line three.
DAVID: Yeah, type xsm. I wonder if you get this snippet. There you go. Press enter.
JASON: Oh, what?
DAVID: So now you have a machine right there.
JASON: Unbelievable. Okay.
DAVID: And so this is going to add a machine with your TS types and everything and your schema. You don't really need all that. Let's just delete the context for now, making this simpler.
JASON: No context.
DAVID: Yeah, and so I actually want to make a traffic light. We're going to see why when we get to the end of the stream, but it's going to be really cool. Traffic light is like the hello world of state machines, I guess. We could do this manually. I know people have been complaining, like, oh, I'm editing a big JSON object. So you should see that there's an open visual editor thing object top left, right above create machine.
JASON: Oh. Oh!
DAVID: Right now you do have to log in via GitHub.
JASON: Come on.
DAVID: No, no, honestly, we are removing that pretty soon.
JASON: Sorry, no, that's not what I meant. I meant it's so freaking cool this is just -- look at it go! Oh, my god. This is incredible.
DAVID: Let's close those panels just because they're taking up a lot of real estate.
JASON: These ones?
DAVID: Yeah, those two. In the extension, we have our little state machine. We have our initial state over there. So let's make a simple traffic light. To create a new state, inside that traffic light box, if you double click within it, then you could just create green, yellow, red. You can change the name there too.
JASON: Oh, double click within it. Got it. Green --
DAVID: Yellow, red.
JASON: Yellow. Red. Unbelievable. Look at it. And it's updating in the code live. Okay.
DAVID: So we can probably delete that initial state. And there we go. It's going to go to green. Now let's add some transitions. So I would say green. Next go to yellow. Next go to red.
JASON: Okay. This is so incredible. Okay.
DAVID: You would just drag that white box over to -- yeah, it's sort of weird. You might need to space things out a little bit.
JASON: Yeah, let's do a little spacing.
DAVID: There we go.
JASON: To yellow, to red.
JASON: So good.
DAVID: Of course, traffic lights go on forever. So let's make sure that we have an event from red back to green.
JASON: Got it. I'm going to do a lot more spacing here so we can see a little more easily. And then we'll take our red and go back to green. I want this one to live down here.
DAVID: And there we go. We have our traffic light.
DAVID: Obviously, this is a very contrived example, but we're going to be using this to power our serverless workflow. Like, this is going to be technically our application logic. Now, if you want anymore real-life scenario, even though I think this is pretty fun, you could think of this as an approval workflow. Let's say you send documents out for approval, and you're waiting for someone or some web hook to be called and send an event to it. That's just one of the ways I would use Netlify Edge Functions with XState to power this kind of business logic. But for now, this works. Yeah.
JASON: This is wonderful. Okay. So we have a working thing. So I think the -- okay. So how do we actually test this, I guess. What do we do with it?
DAVID: All right. So let's -- that's a good question. We're just putting everything together right now. I think the third step is sort of a hello world for PlanetScale.
JASON: Okay, great.
DAVID: Let's do this. So let's go inside the edge function.
JASON: Okay. So we're inside -- should I shut this down?
JASON: So we'll close this down. We can always get it back by hitting that visual editor. Unbelievable. So now we have our basic setup here. We've got our event. We've got our states. Beautiful. I am going to collapse that for now. Because we're going to need to use environment variables, because we're connecting to a database and can't show that on stream, before we do this, I'm going to actually deploy this. So let me git add everything. And we've got our workflow. We've got a type gen. Then we've got all these pieces here. I'm going to git commit and say work in progress, edge function plus state machine POC. Great. And then I'm going to GitHub repo create. And I want to push the local repository up. I'm going to use the full name so it puts it under the right org. No description. That's the GitHub CLI we're using. We'll add a remote. We want it to be called origin, and I want to push right now. Okay. So that all did what we want. Now if I click this button, the code is now up for anybody who wants to follow along. Look at all these friendly faces in the chat. I see Nicky. I saw Chance a minute ago. What's up, everybody? Vinny, how you doing? Vinny, remembering when I used to accidently share all of my secrets. I know, I know. So let's go in and actually set this up. So I'm going to take us into the Netlify app. I'm going to create a new site, and we'll import an existing project from GitHub. Don't need the Netlify stuff because it's on the Learn With Jason account. So we'll go to Learn With Jason right here. And then we're going to search for state because that was the first word. And here it is. Okay. So now, not really anything we need to do. We can just deploy. Because there's only an edge function in here, this should deploy in just a few seconds, at which point I can start doing environment variables. So I'm going to use a feature that I've enabled in Netlify Labs. If you haven't enabled it, it is this one here. By enabling that, we get this extra tab here, which means that we can use this nice little environment variable setup. So I'm going to set this one up here and call this PlanetScale -- what was it called -- database connection string or something?
DAVID: Something like that. I was just using database_ URL. Or that works too.
JASON: Let's just do that. So we'll call it that. We have the ability to hide these now, so I'll drop that out. I'll go to our super top-secret spot and copy/paste that in. Actually, let's do that. We're going to turn it off. We don't want it to be available anywhere except in our functions. I'm going to turn it off in all of those scopes. If we wanted to as well, we could change this. I'm not going to click this because it might make it visible. But you can change it to be a different value when you're in local dev versus a deploy preview versus a branch versus your production URL. But we're going to use the same value for all of them because this is a proof of concept. But now we've got this PlanetScale DB URL. When I come back here and run Netlify link -- and because I'm already here, I'm going to use the site ID because it's nice and fast. And when I run Netlify dev, here comes our database URL. You can see it was loaded from Netlify. Now we don't have to manage that locally. And why did that happen?
DAVID: Let's get rid of the context and TS types, just to make TypeScript happy.
JASON: Okay. So we go back. Don't need the context.
DAVID: Yeah, and the TS types you can delete as well.
JASON: Okay. Delete that. And then happy now? Moved edge function, yeah, yeah, yeah. Okay. Seems happier now.
DAVID: Mm-hmm. Yeah, so let's do some PlanetScale stuff now. The environment variable is for my database I've already provisioned on PlanetScale. That database actually already has some state machines in it, but I want to overwrite the first one. Again, this is very much like hackathon project level. But we're going to override the first one with an ID of one with Jason's state machine. So to do that, what I'm thinking of doing is if the request.method is put, then let's override it. That's going to be basically our overwrite the machine from scratch command. I would do that in the edge function.
JASON: Okay. So let's move that on down. Oh, too far.
DAVID: Yeah, quite the journey.
JASON: Never mind. Let's do a traditional copy/paste. How about that? All right. So here's what we want to do. Then I need -- let's see. I think I have it open so I can just copy/paste the PlanetScale -- where were you? Over here. Here's my PlanetScale. We can get the driver out of -- driver's code and documentation. Nope. That's not it. I want the example. Example application. That's not the code. That's the example code. So the PlanetScale database, this is what we want. So I'm going to come back over here, get the database. Then we need to --
DAVID: Create a connection. Yep. That's right. Yep.
JASON: Then we go Deno.env. I need to run (indiscernible) here to get this to stop yelling at me. And we called that --
DAVID: PlanetScale_ DB_URL, I believe.
JASON: So now we have a database. Just to get this -- recipes vscode. This will just get these Deno errors to go away because it'll set up VSCode to have Deno bindings.
DAVID: That's awesome.
JASON: And now it's mad about things that are different. Oh, wait. Yeah, yeah. Those are errors we actually want.
DAVID: All right. So pretend that I've already provisioned the database with the schema and everything. So this is reading from the workflows table. We have a couple columns, among others, but it's state and the machine. So when we do a put, what I want to do is -- yeah, let's do await db.execute.
JASON: Okay. So I'm assuming we need to check the request method.
DAVID: Right, yeah.
JASON: So we'll -- so const data equals await, DB.execute. Then we can put any query in here.
DAVID: Exactly. Yeah. And so the query we're going to do is we're going to update workflows. Lower case work workflows. Set state equals question mark, comma machine equals question mark. So it's just a plain question mark.
JASON: Oh, right. We're doing -- what are these called?
DAVID: Yeah, we're doing PlanetScale. I'm not sure, honestly.
JASON: These have a name. I can't remember. Back in my PHP days, I would know this.
DAVID: Yeah, where id equals question mark. So we're interpolating three different things. So this is actually a lot simpler than it might seem. The first thing we're going to put is the state. That's going to be JSON.stringify machine.initial state.
JASON: And it's the traffic light machine, right?
DAVID: Yeah. Then we're going to JSON.stringify the machine itself. Yep.
DAVID: Then it's going to be one. So we're just going to be overriding the first row.
JASON: Got it, got it.
DAVID: Which I think is fine.
JASON: All right. So then we need to check whether or not this is put, right?
DAVID: Yeah, exactly.
JASON: So I will if request -- is it method? Equals put. Then we're going to do this bit. Then do we need to store this? Are we doing anything this with data?
JASON: The old skipperooni. Then do we want to -- do we need to return anything? What's the response to a successful put? 200?
DAVID: Yeah, I would just do 200.
JASON: Response. We can do like an ok and status of 200.
JASON: Okay. So if we send a put, we will override this state machine.
DAVID: If all goes well.
JASON: Nicky called the question mark SQL inject-me-nots. (Laughter)
DAVID: Yeah. Actually, this is really fun because I haven't touched my SQL in age. PlanetScale really brought me back.
JASON: It's like a little time capsule.
JASON: Okay. So we have the ability now, if I run this again, we should be in a position to make a put request.
DAVID: Using whichever method you want.
JASON: What invalid URL? What are you yelling about? Connection database URL. It doesn't like my --
DAVID: Oh, are you on local? It's probably because you don't have an environment variable set up locally. Or sorry, you don't have that env file set up. I ran into that.
JASON: So it's here.
DAVID: Oh, okay.
JASON: Maybe I copy/pasted it wrong. I'm going to pull this off screen real quick and check that I didn't copy/paste the wrong thing in. Scope to functions. Oh, yeah, I screwed it up. Let me edit. What I ended up doing is I actually copied like database URL equals instead of just the connection string. So that's my bad. All right. So let's try that again.
DAVID: Fingers crossed.
JASON: And hello from the edge. So, no anger. So we're good from there. Then we can open up a new tab and is it X put?
DAVID: I honestly just use Postman.
JASON: You know what, I'm wearing the Postman T-shirt today. I should probably just use Postman.
DAVID: We are advertising so many companies today.
JASON: (Laughter). All right. So we got this one here. Let's do a put. And it has -- I don't think it's got anything in it. So let's just send. And we get back an ok. And I don't know -- I'm assuming it succeeded because it didn't yell. We didn't log anything to get the result back or anything.
DAVID: Right. Let me show you something real quick. I'm going to copy/paste a link into the chat, just to prove it worked. I'm also doing something experimental on the XState this side. I'm actually really excited because this does work. So disregard the URL. We're going to replace that with our own edge function. But this is just sort of my already have a big -- a cake in the oven. But we're going to replace that pretty soon.
JASON: Let me get back to here, open this up.
DAVID: Yeah, so this is your traffic light, the one you created with event one, event two, et cetera. And right now, it's not going to do anything. So we have to sort of connect it up because it's just showing the machine which -- I mean, it's pretty cool on its own, but we want this to actually be sort of a functioning workflow. So there's only a couple more parts we need to add in order to get that to work.
JASON: Okay. I'm ready.
DAVID: All right. So we have a put. Now what we need to do is post. So this post is going to be for events. Yeah, so unless we change the machine, we don't need to really call that put again. But it's still useful to have there.
JASON: Yeah. If we get a post --
DAVID: So the assumption is we're going to be sending our event which has a type and a string and potentially some other payload. For now, we're going to keep it simple and just give it a type. So we're going to be pulling that from the body. So I like just using await request.json to grab that data.
JASON: Yeah, data equals await request.json. And we can, like, console log that data. That way we'll be able to see what comes in. And what do we want to do with that?
DAVID: There were a couple steps to do. The first step is we need to read what the current state is. And so that's going to be another SQL query. So we're just going to -- and this one we are going to want the results. So you could just call this results equals await db.execute. So we're going to select star because I'm lazy. I know it's not best practice. Star from workflows where id equals question mark. Actually, you could hard code one. But we'll do it the safe way. And I don't know if the semicolon is necessary. I just don't want things to blow up.
JASON: I don't think it is. My old SQL days are like, wait, you have to put that in or you're going to get in trouble.
DAVID: When you're in the PlanetScale command, whatever it's called, I think you do need the semicolons. For here, I think it's just hassle. So assuming we get workflow data, this is going to return a result. So that result is going to have rows. I mean, we should be smart to make sure there's rows and stuff, but whatever. Let's just be optimistic for time's sake. So we're going to read the workflow data from the first item in those rows. So row zero.
JASON: Okay. So that's our --
DAVID: We'll call it workflow data.
JASON: Equals rows. Zero?
DAVID: Now here's the fun part. We have to actually figure out the next state. So the next state is going to be our traffic light machine.transition.
JASON: Next state. Traffic light machine.transition.
DAVID: So the first is going to be -- and this is a bit of a (indiscernible) API, but that's because we're reading in a raw result. And we have to make it to something that the machine actually understands. So the first argument is traffic light machine.resolve state. Then we pass in the workflow data.state. So this is the JSON representation of the current state. Then for the second argument, that's the data we already have. Nothing to do there, just passing data. Sorry, that's the data. The event data.
JASON: Oh, the event data. I got you.
DAVID: So this is going to be our next state. We could console log or whatever else you want here, but once we have that next state, we're going to do another SQL statement where we're going to actually update that row with the updated state.
JASON: Okay. We will await DB execute. And we're going to update to just workflow.
JASON: Update workflows. And we're going to set --
DAVID: Yep, state. And that's all we need to do. ID equals -- yeah.
JASON: Okay. Then we're going to get one of these. And that is next state, right?
JASON: Then we just send in that one.
JASON: So that's our updated data. And do we even need this?
DAVID: No, we don't.
JASON: And Tony was asking how did Stately pick up our machine? The reason it was able to pick up the machine is what we're doing is writing to a database. We're actually putting the machine into the database. And then David's URL here -- where was it -- here, is reading from that same database. So basically, we put a stringified state machine into a database that's being read somewhere else, which is really dang cool. I think that's fun. So now we've got the ability to actually post a new state, and it will cause us to move to the next state. Should we just give that a shot?
DAVID: Yeah, yeah, let's go ahead and give it a try.
JASON: So now we can post.
DAVID: Do we have a response, by the way?
JASON: Oh, not yet. Let's do that. So we can return new response. Whoops.
DAVID: I would say the one thing that did get me, and I don't know if this is local or if it's going to happen in Netlify when we deploy it, but I did need to put like an access allow origin header.
JASON: Yes, that is true. Because we don't want to default. You do want the core stuff. So access control allow origin. And we need to set that to -- we'll set it to star so that anybody can put what they want. In a production application, we absolutely would not set that to star.
JASON: Because that would allow anybody, anywhere to just send a post to our database and break it. But now we should get back our updated. That should be good. And I think we're happy. Do I need to include that here for any preflights?
DAVID: I'm not sure. Probably.
JASON: I'm going to do it just in case because I don't want to get yelled at. So anybody can send a request from anywhere. That way an options request should get the preflight. But now we have raw. We can set it to JSON. And I want to send an event. So my event is going to be -- is it like a type?
DAVID: Yes, type and whatever event name you gave. So if you want to change the event name, like right now it's just capital event space one, we can change that to timer or next or something like that. If you want to. You don't have to.
JASON: That's a good idea so they're sensical. So I could say --
JASON: Is it always next?
DAVID: Yeah, yeah. Why not.
JASON: That way we send an event of next and it loops around.
DAVID: Exactly. Makes things easier from the postman side.
JASON: Absolutely. Then if we open from our visualizer.
DAVID: Arrows are a bit funky. I'm working on that.
JASON: But now we go next. Then we go next and get to next. And they all just kind of move, which is great. So then I need to, in order to make this work --
DAVID: Put request.
JASON: Then I'm going to send that put request. The body is going to get ignored anyway. So we did that. And now if I go back to this visualizer here and reload, we should see the update. Next, next, next. And we can now click the buttons to get to the states. Bada-bing, bada-boom. Very cool. So if I want to go in here and send an event of type next -- do I need to send a payload of any sort?
DAVID: Um, I don't think you do, no. Shouldn't need to.
JASON: Okay. So let's just send it and see what happens. So I'm going to refresh the page here and make sure we know where we are. We should be in green.
DAVID: Yeah. Oh, sorry. So I was playing around with this on my thing, and it overrode that machine. If you could do another put request.
JASON: Okay, I will put.
DAVID: That's my bad.
JASON: So here's a put. Here's our state. We're in green. I'm going to post, send, reload. Oh, it auto did it. Nice. Okay.
DAVID: Yeah, yeah. So what's happening --
JASON: I didn't realize it was realtime.
DAVID: Yeah, I meant realtime. It's a bit naive, but we're just doing polling. So we're reading the database and seeing, okay, what is the current state. And so this will actually probably work both ways. Actually, no. So before that, we do want to actually deploy our Netlify edge function. So we can grab that URL.
JASON: Chat, are you having fun? This is freaking cool.
DAVID: Oh, but before we do that, there's one more missing piece. Actually, this is the easiest one. So we, of course, need a way to read our state. So we need a get handler.
JASON: So that'll be kind of our default here, right?
DAVID: Yeah, yeah.
DAVID: And this is just going to be a simple, you know, await db.execute, just select star from workflows where id equals whatever. Then we just dump that into a JSON, the results from that first row, and return that response.
JASON: We're going to select all from workflows where ID equals one.
DAVID: So I would call that results because that's going to be a -- we're going to get results.rows to zero. So we're going to get the first item in there. Yeah. So I think if we just JSON stringify result rows zero, we should be good to go. So this is useful as well because we could go into Postman, make a git request, and query what is the current state of our workflow.
JASON: I think we'll actually even be able to see it here on the homepage. There it is.
JASON: So when you just hit this, because we set it to be the root, it'll just dump the current state. But you could make this, you know, a sub-route, make it /API/get current state or something like that. But now that we know that works, we can git add everything and say git commit and say, you know, state machine for DB setup. Okay. Let's push that. And now where is my -- oh, I pulled it off screen because I was going to look at values. So instead, I'm going to come back over here. Got stuck. Here we go. And that's building now. They're doing work on my house today, and so there's like drills and stuff in the background. Okay. So this should be live in just a second. There it goes. And we can now open this up and ta-da!
JASON: There's a state machine.
DAVID: So now the big, ugly URL I gave you for the visualizer -- yeah, so at the end there, there's a query pram that has URL. So let's just switch over to yours.
DAVID: Okay, good. We're still in business. All right. Can you do me a favor and copy/paste that big URL over to me?
DAVID: All right. Really hope this works.
DAVID: So here's the thing. You have the traffic light on your screen. I have it on my screen. And this is where just deploying functions to the edge really comes in handy. Because right now I'm going to click next. I really hope this works. So if I click next, it will transition.
JASON: Look at it go. No hands, everybody.
DAVID: Exactly. So now you could click next, it's going to show up on my thing. So we're using PlanetScale, Netlify Edge Functions in order to have a workflow that works in a serverless way.
JASON: That's so good. This is so good. I'm honestly surprised this is not just spinning really fast because I actually gave the chat the URL and not a single person has -- oh, there it goes. (Laughter) so good. It's so good. So this is freaking cool, though. If you think about a -- like a traffic light is obviously a very hello world, as you mentioned, but what we just did here is we're doing things like imagine you've got a collaborative app where you need to be able to put things into locked or unlocked states because only one person can edit a text field at one time. Or you want to do something where people are looking at a screen, and you want to update like, hey, this talk is live now. Or you want everybody to get live poll results and things. Each of those things has a little state machine attached to it where you want to be in have you responded or are you viewing results, are you doing this or doing that. So you get this combination of local UI state. I've clicked buttons or not. And the state that's kind of shared among everybody using the app that can go live in the edge with the database. Holy crap. Like -- and this actually gets back to a question that somebody asked earlier. I can't remember who asked, so I apologize. Somebody said do state machines remove your need to write tests. And I think the answer is obviously no, but does it reduce the burden of writing tests?
DAVID: I would say so. So the reason is because state machines remove a lot of defensive code. I'll just talk really quickly about why you need to use defensive code if you're not using state machines. It's because when you have something like this kind of workflow, whether you're using complex logic on the front end or back end, anything can happen at any time. For example, you might have a submit button. The user can press that submit button at any time, and you might forget to disable it or something. So guess what, now you have to write an if statement to be like f the form is submitting, like if we have some is submitting flag, then make sure that clicking that submit button won't do anything. However, this is an innate property of state machines. We're defining -- instead of it being defensive logic, we're just saying, hey, this button, doing the submit event is not going to do anything if it's not in this state. So we don't really need an if statement in that case. So a lot of the time when we're writing these unit tests, we are testing that defensive logic because it gets really complicated, and it all lives in our head. So state machines provide that level of safety, and they actually allow you to think about tests more at the user level, the end user level. It's like does this actually match the business logic that I intend. So when you get to that level, tests become a lot more valuable.
DAVID: Because you don't have to worry about your implementation details.
JASON: I think that maybe, to me, is potentially the crux of why I think a lot of folks push back against tests. Depending on how you've written your code, a lot of time you're testing whether or not your code was implemented well, not whether your application is doing what you want it to do. And what we should be trying to test is the outcomes. You're trying to test that the user can accomplish this thing. Instead, we're like -- like you said, you're writing defensive code. Then you have to test your defensive code works. So you're trying to hammer all these things. Whereas with a state machine, you've explicitly defined, it is impossible for your app to get from one state to the next without sending this event. So you can't double click the submit button because there's no way after you've sent the submit event to send it again. The state machine doesn't allow that. Those types of things -- I don't know if this has happened to y'all, but I've definitely been guilty of the payment form that I didn't realize was doing anything, so I click the button again. Then I double paid. Right? It happens less and less these days, but that used to be a real problem, even like five years ago.
DAVID: Even today. Promise you.
JASON: It's still possible today, yeah. But this removes that, and it's not by like remembering to write a really, really clean component. It's by saying, no, the application logic literally can't do that.
DAVID: And that's actually a perfect example, I think. Again, even today we see messages that say submitting payment or submitting form, please do not refresh your browser window. So the reason that that's happening is because yes, the logic does live on the server side, at least partially. The browser is making an API request. But on the server side, the implicit state machine that they have, whenever that payment request is made or that form is submitted, it's going to run some logic, and it's not going to check didn't they just make a submission already. So if you have something like this -- and this is why I say that state machines are actually very common in back-end logic. Like you could represent most of back-end logic with workflows and these flowcharts and ultimately state machines. You prevent that need to be like, hey, please don't submit this twice because you can have logic like this to make sure you are following the proper state transitions and you're not going to double charge the user or something like that.
JASON: Right, right. And you can see so many use cases for this. I'm thinking about in a CMS, I might have -- you know, maybe I'm an author. I can write posts. And the editor would need to be able to review and mark whether that is approved for deployment or actually deployed or it's published or in draft and all these sorts of things. There are different things I can do. If something is published, I shouldn't be able to wildly make an edit as an author. That would allow me to go in and make a big mess on somebody's website with no oversight. So when it's in draft state, I can do whatever I want. When it's in publish state, it needs to do something different. If you're an editor, you need different permissions. If you have this role, then you can pass through this event. Otherwise, it'll bounce back to an error state that says, hey, you don't have permissions. And these are the sorts of things we always end up building. But if you're not building it in a way that is very clearly visualized, every single system I've ever built that didn't have a state machine leads to us getting 90% of the way through development and then realizing, crap, we forgot an edge case. Then you got to rip out some logic and build in more stuff. And you always kind of find yourself in these very complex situations. This is how tech debt is born, right. You find that edge case, and you go, I don't want to rebuild this whole system. So you build a loop. Well, here's some conditional logic. We'll go out here and come back. Now this is a decoupled system that sits on top of your original system that sits on top of a different system. Now it's like, oh, man, this is never going to be debugable. (Laughter)
DAVID: Right, right. I completely agree. And oh, by the way, I realize that it might look like there are some invalid transitions being made over here, but that's probably because a lot of people are sending events at the same time.
JASON: That's true. And we're polling.
DAVID: Yeah, so, we're polling. That's why this is something that we want to experiment more in the future with Stately. Like maybe running a web socket server. I don't believe it can be done using Netlify Edge Functions, but this is honestly just an experiment/project that I want to take a lot further. We've been thinking about the serverside use case for XState a lot. So another thing I was excited about, I think earlier this year, is when Netlify announced chron jobs. So one of the reasons people like using XState is because it makes side effects declarative too. And a very popular side effect is timers. People want to say after one second, after two seconds, do this. But with this, now you can say after a week or after a day or every single day. You could actually make use of, I guess, what would you call it, like a serverless chron service.
JASON: We call them scheduled functions.
DAVID: Okay. So a scheduled function. All of that could just remain serverless. So you're moving a lot of what you would have to be doing in a long-lived app over to serverless. So basically, this proof of concept is to show you that you could do the same with workflows. And this is a traffic light. This is long running. Imagine that this is a real-life traffic light that's going to live until it's decommissioned. So this could last for years. So your first thought might not be this traffic light is going to live in a serverless function, which by definite is very short lived, but because we're persisting the state somewhere, we could have basically long-lived logic running in a serverless function. So that's what's exciting to me about all of this.
JASON: Yeah, there's some really, really incredible stuff here when you start thinking about job queueing and some of those things where the event that's getting sent is putting something in the database. Then there's just a job queue running that looks to see, oh, there's nothing in the queue. I'm not going to do anything. Then it notices, oh, something got put into my queue. I can now, like, tee that up and say this happens at this time. So you can even do things like the polling here that's in place, if it was running on a server. It just has a little loop that runs every ten seconds or something. You can just pick out a job and say, oh, I have to run a job now. And that job is to change it to yellow. There's so many cool things that we could do without actually having to run the server 24 hours a day. You can still do it in like these little polling loops.
DAVID: Yeah, and so you can imagine that each of these functions, like green, yellow, and red -- and we won't do it on the stream today because it'll take too long, but you can imagine each of these is an async request. So it might be fetching data from an API, doing some sort of operation, waiting for -- you know, doing that loop pattern and waiting for a web hook event to be received. So you could basically model a sequence of tasks using this. So it doesn't just need to be states an events. Could be anything, really.
JASON: Yeah, some really, really cool opportunities here. And kind of thinking about it, too, from a UI standpoint. If we have something like this running in the background, then our UI can update a global state by just posting to our state machine as opposed to, you know, writing directly to the database. Stuff like that, that would be really -- yeah, you talked about this earlier, about how many applications there are for state machines and back-end logic. Now I'm thinking about form or contact submission handling and just how many little bonus things we could do. Instead of saying update the database with this contact submission, I'm instead saying, hit the state machine and say submit form and here are the fields. And we can have the guards and conditions. It's got a valid, hey, you're missing data state and a valid, hey, the database write failed, you need to hit submit again state. All those pieces that you'll eventually write when you hit that edge case and somebody files a ticket for it. But until then, you're like we'll just hope for the best I guess. (Laughter)
DAVID: Yeah, yeah. And that's actually a cool idea, just representing multistep forms as these back-end workflows.
JASON: And they're resumable, right. Because if I'm putting -- like, I'm imagining now -- so, okay, we've got a multistep form. When I submit it, we're going to create a unique ID for this form submission anyway. So form submission ID goes in. The ID goes in, and my URL gets updated. I walk away, I come back, the state remembers where it is, and the data is already in the database. So I hit that URL form. It pulls back wherever I was. I was on step two of five, and it just puts me there. I get to then continue my form. I don't have to do this thing where a 15-step form that I really don't have an hour to fill out, I have to do it in one shot or it times out. I can actually do a little bit of work at a time and make my way through. I love that.
JASON: You got my gears turning here.
DAVID: Yeah, no, there's so many ideas for this. Someone mentioned kind of like temporal on the edge. So if you don't know what Temporal is, it's a really cool -- I was about to say serverless. It is not serverless. It's sort of the opposite. It's like many server, Kubernetes and all of that. But it's this technology that's basically a workflow engine in the cloud. It's used for just really robust workflows. So the philosophy, like how it works, is a little different than What we showed here. So with temporal, what it's going to do in order to have that resumable state is it's basically going to replay everything that has happened, sort of like event sourcing in a way. I don't think they call it that directly, but it is similar to that. So they replay everything. They make sure, oh, this is calling a side effect, but we already called that side effect. So we're not going to call it again. We're just going to continue through the function. Then eventually end up at, you know, whatever the function outputs. So the difference between this and XState is XState, we're literally taking a snapshot of the state. Because we're writing it as a state machine, we can do that. So we don't need to do a lot of replaying. Not saying that's an advantage or disadvantage, but it's just a different technique. All we have to do is say, hey, here is this snapshot of the state that we just had. And so you could resume it at any time. I think Temporal is really cool. In one of their latest office hours, there was actually an insane demo of a company that used XState and Temporal together, and they had their own custom visualizer and it was 3D. It was super cool. And they were also able to basically read the server state in realtime, sort of do the same techniques that we did today, except with different technologies, of course, and basically map out simulated user flows in realtime in this 3D visualizer. So just imagine if XState was in 3D. It was super cool. So I really recommend people check that out.
JASON: That is very, very cool. So yeah, just to recap here for anybody who joined late -- I just saw that friends raided. Thank you very much. Welcome, everybody. We've been plaiting with state machines on edge functions today. So What we're looking at here is a state machine we built. State machines, for those who aren't familiar, are a really old computer science, like a foundational computer science concept of mapping complexity in code. It's logical mapping, basically. So you've got states. You've got events. Things can only move through your program by moving between these states in the allowed fashion. So what we did was we used the XState state machine library and built a machine here that's a traffic light. So green, yellow, red. They move together with the next action. But we use the XState VSCode extension so that we get this visual editor, which is legitimately blowing my mind. This is so cool. We have now a visual editor for our state machines. We can see very quickly what's going on and how things work. It's updating our code for us as we go. And that gave us a state machine. So then I can come back in here and look, and we've got database connections. So we're using PlanetScale. We are allowing to set up our machine by just updating it. So if you send a put, it resets the state of the machine. If you send a post, we're able to send an event, which right now you send the type of next because we set next as the event that moves between all of them. And that updates to where the state is. Then if you hit it with a get, it shows the actual state. People in the chat I think are still sending post requests. So me with no hands, we're seeing the events move through. So people are sending post requests. It's causing the light to change. And we're able to see what the state is in the app in near realtime. We're polling here, but you can set this up with a stream or something like that as well. And we did all that in -- we probably spent more time talking than coding. This was really fast to put together.
DAVID: Yeah, I was really surprised how quickly this all came together, yeah. Just to mention, this is not part of the XState visualizer yet. Again, this is very hackathon, experimental, but we hope to have this been a real thing one day.
JASON: Yeah, and this is one of the things that I find really, really cool. You know, we -- this piece, being realtime, is wonderful. But we don't even necessarily need that piece because we're getting the state itself here, which allows us to -- we can just copy/paste this right into the visualizer, which I guess we can just try. Is it just Stately.ai? Where is your visualizer now?
DAVID: Yeah, there you go.
JASON: And then if I go to this machine and I drop this in and I visualize -- oh, what did I miss?
JASON: I don't know what I copy/pasted. Weird. All right. Let's try this again.
DAVID: It's all good.
JASON: There we go. Let's visualize. What are you mad at? Oh, I like double create machined. I made a whole mess. Really having issues today. There we are. So here's our state machine. Copy/pasted from logic. Everything that we're doing here is being visualized in realtime. So I can go to my team and say, all right, when somebody comes to this screen, they can take one of these two actions. When they do that, they end up here, which gives them new actions. Did we miss any steps in this flow? And you can discuss this with anybody. You don't have to be a developer. You don't have to get out a white board. You don't have to, like, find a another medium of communication. And this is code. This is the actual source of truth. This is what happens when you run the application. And that, I think, is the major -- this is a selling point to me. The fact that it's not -- like, we all know docs are important. We know visualizations are important. But they drift as soon as we fix one big, and now it's slightly out of sync. If somebody doesn't have time, they don't go back and update it. Or they update it slightly differently than the code was updated. All these things. Almost immediately, any non-code artifact goes out of date in a sufficiently complex system. But if your code is the visualization, it cannot go out of date. It will always represent itself because it is building from its own logic.
DAVID: Right. That's exactly right.
JASON: Freaking cool.
DAVID: Yeah, so long term at Stately, what we really want to do is have the visual editor able to be like -- if you want to create a state machine, you should be able to just press deploy. And that can deploy to any target that you want. Especially now that I've seen how easy it is to use Netlify Edge Functions, like that being a first-class citizen just seems really, really promising. But a lot of the time we, as developers, create these serverside workflows either just in the code, we're sort of creating our own hackie versions of state machines to run these, whether it's long chains of async awaits or even more over-engineering. But instead, you know, developers might use some third-party service like Zappier for simple things. There's other ones like Pipedream. There's a whole ton of services for having these workflows. But the problem is now you're stuck using those services, and you're behold on to them. It's like you don't really own the code. So I think our philosophy is that if you create a state machine for your business logic, you should be able to run it however you want. In this case, on Netlify Edge Functions. So that's our philosophy as well, and we really want to make that happen in the easiest way possible.
JASON: And I think the part that I find really encouraging about this is, you know, we're not introducing a new spec here. Like state machines are -- the state machine that we're looking at on Stately.ai is the same state machine that we would have seen in a paper from the '70s on how state machines function. So that's the piece that's really cool. This is a style of building that's durable not just like cross framework but cross language, cross platform. It's kind of an enduring computer science concept, which means that you're not only not locked into a tool, you're not locked into a language. You could port this from -- you know, there are state machine libraries in just about all of the programming languages, I think.
DAVID: Yeah, yeah.
DAVID: Or worst-case scenario, you take this JSON object and literally just generate the code, whether it's a switch statement or an object. Whatever way your language of choice uses. Again, you're going to be a I believe to find examples of that in any language. Just because it is a fundamental pattern. It's not one that we talk about too much as developers. Unless you talk to me. Then I talk about it way too much.
JASON: (Laughter) That's true. That's true. Yeah, let's see. So we've got a couple questions in here. Is the connect function from PlanetScale what's facilitating the realtime connection? I believe the answer is no.
DAVID: No. Yeah, so we're doing polling. I actually briefly looked like if you could do some sort of subscription with PlanetScale. I don't think it's possible right now. It would be really cool for them to offer. I could be wrong. Maybe I didn't read the docs deep enough. But right now we're just doing polling.
JASON: Then another question here. Is XState recommended to be used with simple state like UI changes? I think yes. That's where I started with it. That's where I really felt the power.
DAVID: Yeah, yeah. So to be fair, a lot of people might say, hey, I just have a very simple toggle component. I only need one Boolean to power. Do I really need all of XState for that? I would say no, at least not right now, just because you want to basically use the right tool for the job. So think of XState as a car. If you're walking over to your neighbor's house, do you really need a car? Not really. But if you need to make a long-distance journey, then yeah, you would really, really need a car for that. So with that said, in XState version five, state machines are going to be only one -- well, it's going to be the main way you define this logical behavior, but it's going to be one of many ways. So if your logic can be easily described in an async function or a reducer, then we have some exciting things down the pike for XState version five. So actors are going to be like the main unit in version five. Basically, all these sources of state are going to be able to communicate with each other in this event-driven way, and we're going to be generating more visuals like sequence diagrams and activity diagrams and things like that. Of course, you're going to get the most visual benefit from using state machines, but it's not going to be like a requirement. We're not going to force everyone to write everything in state machines. It's going to be like a gradual adoption strategy.
JASON: And that's great because I think a lot of times what'll happen is you'll look at something and say, oh, it's just a check box. You'll say, well, I don't need a state machine for that. Then it's like, okay, but it's a check box that affects some form of the UI, and then the UI needs to be checking on the state of that check box. Then something else depends on that. It's like this gradual creep. All the sudden you're like, okay, that felt really simple, but there are four or five interdependencies here. We have to write defensive code to catch all of those, you know, potential states. Like we can't show this UI if this check box is unchecked and also they're not logged in. Right there, that's enough logic that you're kind of like, well, maybe a state machine is worth it now.
JASON: And it happens fast.
DAVID: Yeah, and I would say the value is largely in the visualization aspect. The fact you could document your state machine as a visual diagram, or we're coming out soon with a way to export your states and events in a markdown file. So you could just show it and explain to people, hey, this is what you could do in the app. Here's all the events outlined, all the states, et cetera.
JASON: And the fact this is fully visual in the sense that I want a new state, I just double click in here. And okay, we're inventing a new traffic light. Now it's purple. And we want to -- okay, maybe this one goes to here instead.
DAVID: Exactly. So you could just drag.
JASON: And we have now completely changed the logic of our thing. And you can see exactly what happened. It goes red to purple, then to green. So I can save this. Then we come out here and do a put. And we come back to our visualizer. This is going to update in a second here, once it polls. Assuming my -- oh, my connection was refused. Oh, it was refused because I stopped the local server. So let me pull up the actual app.
DAVID: While you do that, I saw a comment. 99% usage of state machines is resulting in over-engineering. Convince me I'm wrong. I've heard this a million times. So honestly, it's not whether you're using state machines or not. You're programming. Computers are state machines. Programming languages are basically state machines. If you're using regX, that's a state machine just in a different form. You're always using state machines at some implicit level. So it's not whether you're using state machines or not. It's whether you want to make your state machines implicit or explicit. And so this is just an explicit way of making these state machines. And I would say especially in so much code that I've seen, when you decide, you know, I'm just going to write this code the way that I normally write it, like a string of async await statements with lots of defensive code in there, you are going to miss something. You are going to have some race condition. So when you have a race condition or some missing logic, that results in bugs, and that's because of under-engineering. So state machines, whether you use a library -- and I'm not saying you have to use XState for everything. But just think of things in terms of possible states my app or logic can be in. That is literally exactly the right level of engineering. Now, is it possible to get too crazy and make too many states or transitions or just overly -- like, with any tool, you could use it too much. So that would be over-engineering, but I would say that not thinking about your application logic -- and we're not even talking about state machines. But not thinking about your application logic in a modeled way where you could understand everything that's happening is under-engineering. So at the point where you could explain everything that your app could do, that's the exact right amount of engineering, whether you're using a state machine library or not.
JASON: Right. Right, right. Yeah, this is great. I got some love for the keyframers, for your useEffect talk. Really love that. Are you also doing the Keyframers?
DAVID: We took a break just because of life. I have a startup where I'm working 30 hours a day. So.
JASON: (Laughter) That's too many hours, Dave.
DAVID: Yeah. But we do want to start it back up again. That might be happening soon.
JASON: Keyframers is super fan. For y'all who haven't heard of it, definitely worth checking out the backlog. It was David and Steven doing some just really, really fun collaborative coding, a lot of music visualization. Just cool, cool stuff. So definitely go and check that out. I think that brings us to a good stopping point. Like, we -- this was awesome. I always feel like I walk away from these really inspired because you just start to see the way that dots connect. And how much of this stuff is -- you know, when you say that code is communication, there are easy ways and hard ways to communicate about code. I feel like this is what we've been talking about the whole episode. One of the things I find really inspiring about using a state machine is that it cuts out these manual steps to communicate about your code because it's easily visualizable, because it's something you can serialize and port to another language. It suddenly becomes something where writing this code once becomes a communication tool. I thought through my logic. I visualized my logic. I can talk through it with all of the stakeholders, whether or not they can read code. And we can get to a point where we all agree. Then I can move forward with confidence that the things that the app can do are actually described well. So that to me definitely gets me really excited to go out. Makes me wish I had more time to write code. But with that, let me do a little shout out. If you want more wonderful content like this, go head out and give David a follow. This episode, like every episode, has been live captioned. Thank you to Rachel from White Coat Captioning for being here today. And that's made possible through the support of our sponsors, Netlify, Nx, New Relic all kicking in to make this show more accessible to more people. And we have a lot of good stuff coming up. Check the schedule. You can add it on Google Calendar. You can follow me here on Twitch. You can go to YouTube and subscribe there. We have Alex coming on next week. Then we have a few other episodes that haven't made it on the site yet. We're going to play with all sorts of really, really fun things. So please, please make sure you stay tuned. We're going to go find somebody to raid. David, any final words before we wrap this one up?
DAVID: None for me. If you're interested in updates from Stately, we also have a Twitter. StatelyAI. We might be launching something the end of September. So about a month from now. Keep your eyes out for that.
JASON: Oh, oh, always fun. All right, y'all. That's it for us today. Thank you. We will see you next time.
Closed captioning and more are made possible by our sponsors: