localfirst.fm
All episodes
May 6, 2025

#24 – Ben Holmes: Astro, Simple Sync Engine & Warp

#24 – Ben Holmes: Astro, Simple Sync Engine & Warp
Sponsored byJazz
Show notes

Transcript

0:00:00 Intro
0:00:00 What if I'm on my phone, I type a thing and then I go back to
0:00:02 my computer and I try to see it?
0:00:04 Is it gonna merge it together?
0:00:06 Is it gonna break?
0:00:07 I have no idea because.
0:00:10 Client side stores don't help you with those problems.
0:00:12 They're not designed for those problems.
0:00:14 They're designed for like ephemeral state that can disappear at a moment's
0:00:18 notice if it needs to for some reason.
0:00:21 so that led me down exploring local-first technologies and I found pretty quickly
0:00:26 how capable SQLite is for these use cases, including in the browser.
0:00:32 Welcome to the localfirst.fm podcast.
0:00:34 I'm your host, Johannes Schickling, and I'm a web developer, a
0:00:37 startup founder, and I love the craft of software engineering.
0:00:41 For the past few years, I've been on a journey to build a modern, high quality
0:00:45 music app using web technologies, and in doing so, I've been falling down the
0:00:49 rabbit hole of local-first software.
0:00:52 This podcast is your invitation to join me on that journey.
0:00:55 In this episode, I'm speaking to Ben Holmes.
0:00:58 A senior web developer and educator known for his whiteboard videos
0:01:03 after having spent most of his career building server centric applications.
0:01:07 Ben recently explored local-first software by building a simple sync engine
0:01:11 from scratch before getting started.
0:01:14 Also, a big thank you to Jazz for supporting this podcast,
0:01:18 and now my interview with Ben.
0:01:20 Hey Ben, so nice to have you on the show.
0:01:22 How are you doing?
0:01:24 Hey, I'm doing great.
0:01:26 Yeah, how are you doing?
0:01:27 I'm doing fantastic.
0:01:29 Super excited to have you on the show.
0:01:31 I'm very certain that most of the audience already are familiar with who
0:01:34 you are since you have quite a reach on, Twitter, x, other platforms, et
0:01:40 cetera, where I think you're doing an excellent job of taking like novel
0:01:45 concepts and breaking them down in a very simple and approachable way.
0:01:49 And I think you've done the same for some local-first related topics recently.
0:01:54 So maybe some of the audience are already familiar with your work in that regard.
0:01:59 But, for those who are new to you, would you mind giving a background who you are?
0:02:04 Yeah, totally.
0:02:05 I'd be shocked if everyone knows, but if you've seen a guy with a whiteboard
0:02:09 around the internet, it might be me, especially if it's a vertical video.
0:02:13 so I've been doing a lot of work in just the engineering space for a long time.
0:02:20 So been at Astro for a few years and we've been building the
0:02:26 framework for content sites.
0:02:28 And I know actually you have some experience working with Contentlayer
0:02:31 and other things, but content sites are a nice place to get started
0:02:34 because they're a nice, well-defined use case for web technologies and
0:02:39 Astro was trying to spearhead being like the simplest way to do that.
0:02:43 so was able to contribute to that for a long time and also produce a lot of
0:02:48 videos on learnings in the process.
0:02:51 So it started with taking a rock band microphone and a whiteboard outta my
0:02:55 closet and just recording stuff and just seeing where it went and been doing it for
0:03:00 years now and kind of honing the craft.
0:03:03 And we've covered all sorts of topics, including, different
0:03:07 web frameworks, Tailwind tips, database providers using SQLite.
0:03:12 Most recently putting SQLite in the browser, which is gonna be
0:03:16 very relevant today, I'm sure.
0:03:18 and most recently I've made the jump to Warp Terminal, so I'm gonna
0:03:23 be, well, I have joined their team as a product engineer and we're
0:03:28 sort of spearheading the future of bringing AI and agent workflows to
0:03:33 everything you do in the terminal.
0:03:35 So if you forget a command, need to kill a port, need to go through
0:03:38 a full get workflow or even edit files in your projects, you can just
0:03:43 ask Warp to do that for you or fall back to all the features that make
0:03:46 Warp just a really nice terminal.
0:03:48 It's a really great fit.
0:03:49 Been working with them for a while and now get to do that full time.
0:03:53 We'll continue to do all the videos that you see around the internet.
0:03:56 That is awesome.
0:03:57 I'm certainly coming back to Warp since I wanna learn more about that as well,
0:04:01 but taking things one step at a time.
0:04:04 you've been digging into local-first related, you've been mentioning
0:04:08 SQLite running SQLite in the browser.
0:04:10 Looking into that over the course of the last year or so.
0:04:14 I'm very curious, like what led you to explore that since with Astro, et
0:04:19 cetera, you're probably, that's like a different part of building websites,
0:04:24 really where you maybe like Astro is famous for introducing the island
0:04:28 architecture where you go like very light on bringing JavaScript into the, website.
0:04:35 And if it's almost like the other extreme where you want to bring a lot of
0:04:40 JavaScript into the web app, deemphasize the server part, and also more on
0:04:45 the spectrum from website to web app.
0:04:48 Certainly much heavier on the web app spectrum.
0:04:51 So yeah.
0:04:51 Can you share more what led you to, to this path?
0:04:55 well the past year has been kind of like an existential crisis of how are you
0:04:59 supposed to build websites, and I think we all went through that as an industry,
0:05:03 as things kind of shifted back from client side, heavy react apps towards things
0:05:09 that are more server rendered and people are slowly trying to question that idea
0:05:14 again and see what we can learn from storing more information in the client
0:05:19 and sinking it back to your servers.
0:05:21 So I also noticed a wave that's completely opposite.
0:05:26 Of local-first apps, which would be something like HTMX, where everything is
0:05:32 purely server driven, state stateless.
0:05:36 Everything is from like the REST API protocol.
0:05:40 And there's a really nice simplicity to it where a state lives in one
0:05:45 place, the server's a source of truth for everything, and you use different
0:05:50 return values for each HTML sheet to decide what is going to render next.
0:05:55 And you accept that there's going to be network latency for most interactions
0:06:00 on the site, except for very small dropdown toggles and the like.
0:06:04 But anything that involves state is always going to go back to
0:06:07 the server and map it back.
0:06:09 That obviously has trade-offs that people have tried to get away from
0:06:12 with client side architectures.
0:06:14 But the reason it's so nice is you don't have to think about, you have
0:06:18 this server state, you have this client state, and you're constantly trying
0:06:22 to keep them sort of melded together.
0:06:24 And I think that's something that a lot of server side rendered applications have
0:06:30 run into most recently react trying to add on, like use optimistic hooks and
0:06:37 libraries like TRPC, letting you show data optimistically and then replace it
0:06:41 with the server value when it comes in.
0:06:43 And these are important principles, but it's a lot of manual effort to first send
0:06:49 a request to the server and also keep the client in some optimistic version
0:06:53 of that to cut down on network latency.
0:06:56 You're having to pull levers on both sides and you don't really
0:06:58 know where the truth lives.
0:07:00 So, from my experience that's led to a lot of manual work, writing and
0:07:06 rewriting local stores, maybe with Redux back in like the mid 2010s now
0:07:11 using, query hooks, even GraphQL if you're racing for those kinds of tools.
0:07:16 So it's, very messy using those things that are isomorphic, AKA, things that run
0:07:23 both on the server and on the client, and trying to think of it in the same way.
0:07:28 So one response to that is just, we don't need client side JavaScript.
0:07:32 We're gonna do everything on the server.
0:07:34 And that's very easy to understand because it goes back to like how
0:07:38 rest was designed in the eighties.
0:07:40 And then there's the other reaction, which is the, to other side, the
0:07:43 client is the source of truth for pretty much everything that's going on.
0:07:46 And the server's just a broker to keep different clients in sync
0:07:50 and to push changes so everyone can stay up to date or even doing
0:07:54 decentralized servers if you go further.
0:07:57 But it's that other side of the coin.
0:08:00 Of we want one source of truth.
0:08:02 We don't want to think about, we have this server and this client and we constantly
0:08:05 have to write logic to glue them together.
0:08:07 No, you either store all the state on the server, kind of like an HTMX style,
0:08:12 or you store all the state on the client using local-first technologies.
0:08:16 And having explored the first one for a long time, since Astro is meant to be
0:08:21 like a static website or server driven website, I was excited to explore the
0:08:26 other side of storing everything on the client, keeping it up to date, and then
0:08:30 figuring out how synchronization happens.
0:08:33 That's also been around for a long time.
0:08:35 If you look back to just like the first calendar app on your phone or on your
0:08:40 computer, this challenge has been around since probably SQLlite was created.
0:08:44 but it's only now that web devs are starting to get a lot of footholds
0:08:48 to also apply this to websites if that's something that you need.
0:08:53 I think you've summarized it really well.
0:08:55 And, I would go even as far as saying there is an elephant in the room that
0:09:00 most people are aware that there is an elephant, but they really don't
0:09:04 have yet the, the right terminology.
0:09:06 And I would say the term here of the elephant is distributed systems.
0:09:10 We have distributed states and distributed systems are really, like,
0:09:14 that's a core discipline of computer science that doesn't really have
0:09:19 just like the good answer, but it's a really, really hard problem alongside
0:09:24 of like naming things and so on.
0:09:26 But, that part, like everyone who's building a web app,
0:09:30 anything that has state.
0:09:32 On the server, like even in the server, typically you have by definition
0:09:37 also already a distributed system where you have states in your API
0:09:41 throughout the request lifecycle, but then you also have it in the database.
0:09:45 You might have like concurrent requests.
0:09:48 So there you already have a distributed system.
0:09:50 Typically there it's much less bad because they're like, you can just
0:09:55 trust the server already less.
0:09:57 So you just trust the database.
0:09:59 So you basically push it all down to the single choke point.
0:10:02 But if you wanna trust the client even more now all the distributed
0:10:07 parts are get the distance grows and therefore the divergence.
0:10:12 And I have in university, when I studied computer science, I don't
0:10:15 think I've actually taken a class on distributed systems and I've missed a
0:10:20 memo where someone would've instilled it in me is like, Hey, everything
0:10:24 that are you gonna build will suffer from distributed systems, make sure to
0:10:28 understand this problem properly and then design the architecture around it.
0:10:31 And I think most web devs are not aware of that.
0:10:35 And what you've just laid out, I think is exactly suffering from this problem.
0:10:41 And this is what I think where some framework creators are making
0:10:45 the very smart decision to empower the server as much as possible.
0:10:50 Because if you live by that sort of maxim, at that point, the implied
0:10:57 damage that you can cause by building it in a certain way is minimized.
0:11:02 And, my journey over the last five years or so has been almost like
0:11:06 intentionally letting the pendulum swing to the most other extreme where all the
0:11:11 state is governed by the client, since I'm pretty convinced that the middle
0:11:16 ground is just pain and suffering.
0:11:19 So I'm pretty convinced that you should really analyze the use case that you have.
0:11:24 And if it's a daily driver productivity app, you probably wanna move as much
0:11:29 state to the client as possible.
0:11:31 Where it happens, like you produce most of your state in your calendar app or in
0:11:35 your Notion, or in your other notes app or whatever you want to have it declined.
0:11:41 And, if it's something like New York Times, then you don't
0:11:45 produce any of that date.
0:11:46 So it should be on the server.
0:11:48 And, I, feel like more people should, start with that assumption, then
0:11:52 build an architecture around it.
0:11:54 But, yeah, I think you summarized it super well.
0:11:58 Yeah.
0:11:58 And it is tough.
0:12:00 To prescribe either side because there are neat buckets like content sites
0:12:06 and client side apps like Notion.
0:12:09 But the classic example is, well what about e-commerce where things are
0:12:15 server driven until you're in the add to cart flow and now it's client driven.
0:12:18 So what do you do then?
0:12:19 How do you architect it?
0:12:21 We actually met this challenge building the Astro storefront front template,
0:12:26 which was mostly server driven with some client side components sprinkled in.
0:12:32 I think the answer there was, it's still fine to leave things server
0:12:36 side and not rely on optimistic updates too much, except for like
0:12:42 small examples like increase and decrease quantity in your cart.
0:12:47 Do you want that to feel instant and happen on a delay or a debounce but
0:12:51 everything else is server driven.
0:12:53 There is no perfect architecture, I guess is what I'm saying.
0:12:56 It's more painful when you really try to blend it 50 50 and nail every use case.
0:13:01 But if you can keep it 80 20, where 80% is in one realm and 20% is business logic in
0:13:07 the other realm, like 80% server driven, 20% client side complexity or less, then
0:13:13 you're kind of minimizing the footprint of pain that you could run into since I've
0:13:18 certainly noticed that with some of the newer patterns and older patterns with.
0:13:23 Client side apps.
0:13:24 but there was a second thing you mentioned about distributed
0:13:26 systems that I, totally agree with.
0:13:29 I was reading the book Designing Data Intensive Applications, the big
0:13:35 O'Reilly book, probably the first one you find looking up like CS principles.
0:13:40 And I think there was one section about distributed systems where it walked you
0:13:44 from like single leader replication.
0:13:47 You got one database and if you want to have some caches to make reads
0:13:52 faster, you just put all your rights to one database and then it'll replicate
0:13:56 the reads out to everyone else.
0:13:58 So you can't write to a bunch of different regions.
0:14:00 You can write data to one region and then it'll sort of.
0:14:04 Push out all of the clones and allow eventual consistency to work.
0:14:09 but there are cases where that doesn't work anymore.
0:14:12 Like in Notion, I cannot wait for the server to update the document.
0:14:16 It's just gonna update when I'm typing and when I add blocks and when people
0:14:19 are invited to join the document.
0:14:21 All that stuff matters.
0:14:22 So you need some way to be able to write to maybe the nearest node.
0:14:28 Like if you go fancy with CloudFlare, you could have really localized,
0:14:31 durable objects that are two feet away from your computer and that reduces
0:14:35 latency in some sort of magical way.
0:14:38 but then it kind of explains well if you just put a data replica just on the
0:14:43 client device, that's another version of multi leader replication, where
0:14:49 instead of replicating in some really edge node, you're just replicating
0:14:53 on the person's computer and everyone is like a mini server unto itself,
0:14:57 where you just read and write data and they'll all report back to some
0:15:01 centralized source of truth later on.
0:15:04 So when you're working with, like, is it a distributed system?
0:15:07 It is.
0:15:08 Even when it's running on your device, if there's some sort of synchronization
0:15:12 layer, you're just moving it closer and closer and closer to the computer
0:15:15 until it iSQLiterally in the computer.
0:15:18 It's inside the house.
0:15:20 I've, heard a, very interesting framing of sort of like that
0:15:24 elephant in the room and like that problem that we just talked about.
0:15:27 I think it's by Carl who worked on SQLSync and recently, released Graft,
0:15:32 which is another fascinating project.
0:15:35 And he has written a blog post about, don't recall the exact name we're gonna
0:15:39 put it in the, show notes, but it was basically along the lines of like, your
0:15:43 application is a database and that's bad.
0:15:47 I gotta look it up.
0:15:48 what the, exactly the title was.
0:15:49 But it was basically, he's making the point that every app that is sufficiently
0:15:56 getting more complicated will basically build its own version of a ad hoc database
0:16:03 and if you're just using React useState or something else enough, and then
0:16:08 you try to add persistence, et cetera.
0:16:10 What your app is basically becoming is a poor implementation of a database
0:16:15 where like all the things that a database does a lot of like R&D great
0:16:21 work to make it fast, to make it correct, to make it like nicely, like
0:16:27 transactionally correct, et cetera.
0:16:29 All of those things you're now trying to handle through, useState and useEffect
0:16:35 and like when this thing changes, also change that thing, et cetera.
0:16:40 And, I just thought that framing was so elegant and, his conclusion, which
0:16:45 I agree with is like, hey, if let's try to make the app about what the app tries
0:16:50 to do and let's leverage a database so we can focus on the actual like
0:16:55 features, et cetera we want to implement.
0:16:58 And I think that's another kinda articulation of the elephant in the room
0:17:03 that we're accidentally and without being aware of it Building databases as that
0:17:09 are sort of camouflaged as apps and, we should embrace it more if we're starting
0:17:14 to see those signs of those problems.
0:17:17 So you've mentioned that you've gone down this path, a lot out of curiosity and
0:17:22 just because you've, pretty exhaustively explored the more server centric
0:17:27 paths, can you share more about like what were your frustrations and pain
0:17:31 points that you felt where you wanted to go more, like embrace the client
0:17:35 more, but still trying to do that with the more server centric architecture.
0:17:41 So you've mentioned optimistic state, et cetera, maybe can motivate
0:17:45 this through a concrete app you wanted to build where you just felt
0:17:51 The pain points of embracing the client
0:17:51 Yeah, I mean I've played with all sorts of side projects as we all have.
0:17:56 and I was working on a few different things.
0:18:00 One was like a localized note taking app, and I was also playing with local
0:18:06 LLMs and other pieces to add vector search into a local environment because
0:18:12 I was running up on the limits of using Notion and being very frustrated with
0:18:17 loading spinners and offline warnings when I was trying to use the app.
0:18:21 I hear that's being changed and rectified as they build out their
0:18:26 own, like SQLite replication.
0:18:29 I know that's in there.
0:18:29 There's some fascinating videos about that on the internet.
0:18:33 but I still do see the value of just open up Apple Notes, you type things and it's
0:18:39 just kind of there in a SQLlite store.
0:18:40 You can find it on your file system.
0:18:42 All your Apple Notes are just in a SQLlite store and it's great.
0:18:45 so I thought, well, it seems like that's the answer.
0:18:48 If I were to go the traditional server route, I would sit here
0:18:51 waiting for all these updates to persist, which just wouldn't work.
0:18:55 Or I'd be sort of gluing together a bunch of useState calls and figuring
0:18:59 out how do I update this server and what if someone else updates it?
0:19:02 What if I'm on my phone, I type a thing and then I go back to
0:19:04 my computer and I try to see it?
0:19:06 Is it gonna merge it together?
0:19:08 Is it gonna break?
0:19:09 I have no idea because.
0:19:12 Client side stores don't help you with those problems.
0:19:14 They're not designed for those problems.
0:19:16 They're designed for like ephemeral state that can disappear at a moment's
0:19:21 notice if it needs to for some reason.
0:19:23 so that led me down exploring local-first technologies and I found pretty quickly
0:19:28 how capable SQLite is for these use cases, including in the browser.
0:19:34 you can load up SQLite with like a wasm build as you're
0:19:38 very aware and use it even with.
0:19:41 Like very nice SQLite libraries.
0:19:45 If you wanted to use Drizzle for example, which is a common SQL querying library
0:19:50 and JavaScript, it matches onto the browser version of SQLite perfectly.
0:19:55 So you can actually make declarative, find many notes and
0:19:59 it'll just do the little SQL query.
0:20:01 It'll join it up with all the authors of the post and it'll
0:20:04 just give it back to you.
0:20:05 Kind of like you're on the server, but you're on the client.
0:20:08 And you can use client side ORMs or query builders, whatever
0:20:12 your flavor of preference.
0:20:14 So that was very empowering to see.
0:20:16 Yeah, you can bring all of these niceties you get from server side data
0:20:20 querying and bring it into the client.
0:20:23 And I tried to stretch it a little bit further by asking,
0:20:26 what about SQL extensions?
0:20:28 Could I add a vector search plugin, for example?
0:20:32 The answer is yes.
0:20:33 There actually is a vector search plugin that I think is developed
0:20:36 by someone on the Mozilla team.
0:20:38 So it is pretty battle tested in different languages.
0:20:41 I think they're sponsored, yeah I've had the chance to meet them in October
0:20:47 when I was in LA super lovely person.
0:20:50 And, they have some sponsorship from the Mozilla team currently.
0:20:55 Nice.
0:20:56 Yeah, and I was playing with the, rust flavor of that since, well now
0:21:01 I'm working at Warp, and Warp is a rust powered terminal, so naturally I
0:21:05 need to get up on the Rust knowledge.
0:21:08 And also if you build apps with Tori, which is a native desktop application
0:21:13 tool that uses Rust as well, so.
0:21:16 Side tangent, but it is nice to use tools that could work in JavaScript as well
0:21:19 as rust in very efficient languages.
0:21:22 so I reached for that.
0:21:23 I put vector search in.
0:21:24 I was also able to run an entire LLM across the data in the
0:21:29 browser and just load up like the entire vector search setup thing.
0:21:35 and I just said, all right, I'm gonna backport all of my
0:21:38 markdown files into this thing.
0:21:40 I'm gonna vectorize all of them in the browser.
0:21:42 I'm gonna search them in the browser.
0:21:44 And I was able to get it working with like next key search.
0:21:48 And it was absolutely mind blowing.
0:21:49 Like this is like an actual AI powered search tool.
0:21:53 And you can get like with every keystroke new results
0:21:57 And is isn't that wild like that this machine that we have like sitting
0:22:01 on our laps or like this machine here in my hands that is capable
0:22:07 of all of those things and just the way how we kind of build web apps.
0:22:11 Over the last decade or so, we've kind of forgotten or denied the
0:22:16 capabilities of our client devices.
0:22:18 And we only just like trust the server and we've almost like, why did no
0:22:22 one tell us that this is possible?
0:22:25 And that's so magical when you see that this is working and just the
0:22:28 stuff that's currently, in the works with like web, LLM, et cetera, where
0:22:32 it can run like a full blown like Llama model locally in your browser.
0:22:37 running on web GPU is absolutely wild to the capabilities that we have.
0:22:43 But I think what's holding us back is where does our data
0:22:47 live and everything else.
0:22:48 Kinda like, it's almost like a second order effect from where the data lives.
0:22:52 And this is what, your anecdote really nicely highlights of like, you go with
0:22:57 SQLite with your data and then you like bring in another superpower of like SQLite
0:23:02 Vec with the vector embedding, et cetera.
0:23:05 And I think it all starts with where the data is, how your
0:23:08 application is being shaped.
0:23:10 Yeah.
0:23:11 And it is good to find that.
0:23:13 Just common data layer.
0:23:15 SQLite is the easy answer.
0:23:17 PG light is a more robust exploration of bringing Postgres to local devices.
0:23:22 That's a bit earlier on.
0:23:24 but I think we're entering a world where software is just so easy to spin
0:23:28 up that you will very quickly have a web client, a mobile app client,
0:23:33 a desktop client, and they're all talking to the same sync server.
0:23:38 And when you're in that world, it's nice to just reach for SQLite.
0:23:40 'cause I can run SQLlite on my iPhone.
0:23:42 I can run SQLlite on my Android device, I can run it in the browser and I
0:23:46 could run it in a desktop application.
0:23:48 So as long as you just have this concept of clients write to SQL servers and
0:23:54 those SQL servers have some way to talk to each other, then you can build
0:23:58 these multi-platform applications very quickly, even across different languages.
0:24:04 And just figure out what that sync layer looks like, which
0:24:07 we can probably talk about.
0:24:09 that would've been my next question since, I think what you started with
0:24:12 was probably without the sync part yet, where you can just locally in
0:24:17 your browser web app, you successfully ran SQLite using the wasm build.
0:24:23 Then you brought in SQLite Vec, you could, get the AI magic to work.
0:24:27 You saw like how insanely fast everything feels.
0:24:30 You write something, you reload the browser.
0:24:33 Even if you're offline, it's all still there.
0:24:35 Great.
0:24:36 But now you're thinking, okay, it works on like local host 3000.
0:24:41 how do I get it deployed and how do I get it so that, if I accidentally
0:24:47 open this in a cognitive tab and I close it, poof, everything is gone.
0:24:52 and how do I get it show up on my, phone so this is kind of
0:24:56 collaborating with yourself, but on also collaborating with others, which
0:25:00 we maybe punt on that for a moment.
0:25:03 But yeah.
0:25:03 How did you go from, it works locally almost like if you use like just
0:25:09 local storage to trying to share the goodness across your devices.
0:25:15 From working locally to working across multiple devices
0:25:15 Well, I think we both have the privilege of just ask Andrew on the Rocicorp team
0:25:19 and he'll point you to some resources.
0:25:21 if you don't have that, I do recommend that.
0:25:25 Well, the one approach that I used was just reading through the Replicache docs.
0:25:31 That explain how their sync engine works on a very high level.
0:25:35 And Replicache is a batteries included library, well some batteries included
0:25:42 library that sets up a simple key value store for you where you can write and read
0:25:48 keys on the client and you can author code that will post those changes to a server
0:25:54 that you own and pull changes back from the server whenever changes are detected.
0:25:59 And you can implement that on a SQLite or anything else if you just have
0:26:04 their simplified mental model, which is very inspired by just how Git works.
0:26:09 So in Git, if you had to change locally and you wanted to push it up to the
0:26:14 main line, you would run Git push.
0:26:17 And the same kind of thing happens with their sync engine service, where
0:26:21 anytime you make a change locally and you can decide what that means,
0:26:25 maybe it's a debounce as you're editing a document, maybe it's when
0:26:30 you click on a status toggle in Linear and you wanna change that status.
0:26:34 Whenever the trigger is, you can use that to call push, which would probably call
0:26:40 an endpoint written on your own server.
0:26:43 That's simply a push endpoint and that can receive whatever event you ran
0:26:49 on the client so that the server can replay it, back it up into its own
0:26:53 logs and tell other clients about it whenever they try to pull those changes
0:26:58 back down so you can run your own little push whenever you make a change.
0:27:03 And then other clients on an interval or a web socket or some
0:27:07 other connection can pull for data whenever they want to get the most
0:27:11 recent changes made by other people.
0:27:14 Now the question would be, what am I pushing and what am I pulling?
0:27:18 Like what data?
0:27:20 Needs to be sent across.
0:27:22 And there are a couple different methods.
0:27:25 I know Replicache uses a data snapshot mechanism.
0:27:30 I don't fully know the intricacies of it, but I know that because they use
0:27:35 like a key value storage, which is much simpler than like a full-blown database.
0:27:39 They can take a snapshot of what the server looks like right now and then the
0:27:44 client has its own copy of that server snapshot and it can just run a little
0:27:47 diff the same way you'd run a get diff to see what code changes you have made.
0:27:52 It can run that diff and you can see, all right, I added this key.
0:27:55 All right, updated this status flag, and then tell the server,
0:27:58 this is the diff, this is the change that was made to the data.
0:28:02 And the server can receive that request and say, okay, this is the change
0:28:06 that I need to make against my copy.
0:28:08 And then I will tell other clients to also apply that diff whenever they pull.
0:28:13 it's a very Git inspired model, and it works if you're able to diff data easily.
0:28:19 If you are working with like a full blown SQLlite table, you run table
0:28:23 migrations, tables, change shapes, that's a very hard thing to keep track of.
0:28:27 So another option that I implemented for the, learning resource I
0:28:33 created called Simple Sync Engine.
0:28:35 If you find that on my GitHub, probably in the show notes, you can see that.
0:28:40 But it was meant to be a very basic implementation of this pattern
0:28:43 that uses event sourcing instead.
0:28:46 So rather than sending a diff of how the data should change, instead sends
0:28:52 an event that describes the change that is made like a function call or
0:28:57 an RPC, however you wanna think about it, where you would tell the server
0:29:01 I updated the status to this value.
0:29:05 And in our implementation we create these little like function helpers that
0:29:09 can describe what those events are.
0:29:11 So you might have like an add status event.
0:29:13 So like the type of the event is add status and that accepts a few arguments.
0:29:19 It accepts the status you wanna change it to, and the ID of the
0:29:22 record that has that status currently.
0:29:25 So the server receives that event, it sees, okay, the type was set status.
0:29:30 I see two arguments here.
0:29:31 The ID of the record.
0:29:33 And the new status that should be applied.
0:29:35 I'm gonna go ahead and run that event as a database update.
0:29:39 So it has that mapping understanding of, I received this event, I know that
0:29:43 maps to this SQL query, so I'm gonna go ahead and make that change on my copy.
0:29:48 And then whenever people poll, you can send that event log out, or you
0:29:52 can send whatever events the client hasn't received up until that point.
0:29:57 You can kind of think of those like Git commits where you're pulling
0:30:00 the latest commits on the branch and the server's able to tell you, here
0:30:04 are the latest events that were run.
0:30:06 Go ahead and run those on your replicas so both the server
0:30:10 and client know what events.
0:30:12 Actually mean, like this event means I need to make this SQL query.
0:30:16 And it's able to do that mapping.
0:30:17 and you can have a bit of freedom there.
0:30:19 If you had like a very specific kind of data store on the server, like
0:30:24 MongoDB, you could customize it to say, whenever I receive this event, it
0:30:28 means this MongoDB query and this call to the Century error logging system,
0:30:33 or whatever middleware you wanna do.
0:30:35 As long as server and client agree on what events exist and what changes they
0:30:40 make in the data stores, then everyone can be on the same page whenever
0:30:44 they're syncing things up and down.
0:30:46 you're very familiar with event sourcing as well.
0:30:49 I'm curious if there's things that I've missed or important edge cases
0:30:53 that we should probably talk about.
0:30:55 I think you very elegantly, described how simple the foundation of this can be and
0:31:00 hence the name of like Simple Sync Engine.
0:31:03 I think this has served as a great learning resource for you and
0:31:06 I'm sure for many, many others.
0:31:08 And once you like, start pulling more on that thread, you realize, oh shit like,
0:31:13 okay, that thing I didn't think about.
0:31:15 Oh, what about this?
0:31:16 So you've mentioned already the reason why you preferred, event sourcing over
0:31:22 the snapshot approach because like with SQLlite, What would you actually compare?
0:31:27 This is where I would give a shout out again, to Carl's work with Graft.
0:31:32 this is what he's been working on.
0:31:33 We should have him on the show highlighting this as well.
0:31:36 But, where he's built a new kinda sync engine that, is all about that
0:31:41 diffing of like blocks of storage.
0:31:45 I think all focused around SQLite and that gives, that does the heavy work
0:31:50 of like, diffing and then sending out the minimal amount of changes
0:31:55 to make all of that efficient.
0:31:56 Since there's this, famous saying of like make it work, make it right
0:32:02 or correct and then make it fast.
0:32:04 And working on all of this has really given me a deep appreciation for all
0:32:09 of this since like, and I'm sure you probably also went through those stages
0:32:15 with the Simple Sync Engine, like with making something work in the first place
0:32:19 was already quite the accomplishment.
0:32:21 But then you also realize, ah, okay, in those cases this is not quite yet correct.
0:32:25 And then you could go back and like try to iterate and then you also realize,
0:32:30 okay, so now it has worked for my little to-do app, but if I, now, depending
0:32:36 on the architecture, if I roll this out and put in hack news and suddenly
0:32:40 have like 5,000 people there on the same time, this thing will break apart.
0:32:45 Will A not be correct in some ways you didn't anticipate and
0:32:48 will also not be fast enough.
0:32:50 So now making this fast.
0:32:52 It is at the end of the day is like really reinventing.
0:32:55 It's like your app is becoming a little database and you want
0:33:00 to like move as much of that database burden to the sync engine.
0:33:05 This is why folks like Rocicorp, ElectricSQL, et cetera, they're
0:33:09 doing a fantastic job trying to absorb as much of that complexity.
0:33:13 But building something like this by yourself really gives you an understanding
0:33:18 and an appreciation for what is going on.
0:33:21 I love the Git analogy that you've used, but just a, a couple of
0:33:25 points just similarly to how, your sync engine works is actually very
0:33:30 analogous to how the Livestore architecture on a high level works.
0:33:36 But I've had to, before I arrived at that, I really wanted to think through a lot
0:33:42 of like the more further down the road.
0:33:45 Like, what if situations since, one that I'm curious whether you've
0:33:50 already run into, whether you resolved in some way or left for the
0:33:53 future, is how would you impose a total order of your change events?
0:33:59 So this is where When you have, like, let's say this to-do app
0:34:04 or like a mini Linear, app.
0:34:06 Let's say you create an issue and then you say you, complete the issue or
0:34:12 you toggle the issue state, et cetera.
0:34:14 It can mean something very different, if one happens first and then the
0:34:19 other, or the other way around.
0:34:21 And for that, where you have your events of like, hey, the issue was created,
0:34:26 the issue status was changed, this, the issue status was changed, that,
0:34:30 the order really matters in which way in which order everything happened.
0:34:36 And, this might be not so bad if you're the absolutely only person using your
0:34:40 app and typically only on one device.
0:34:43 But when you then do stuff between multiple devices or multiple
0:34:46 users, then it's no longer in your single user's control.
0:34:50 That stuff happens concurrently.
0:34:53 And then it really matters that everyone has the same order of the events.
0:34:58 And this is where you need to impose what's called a total order.
0:35:02 And I'm very curious whether you've already, hit that point, where you thought
0:35:06 that through and whether you found a mechanism to impose it since there's many,
0:35:10 many downstream consequences of that.
0:35:13 Imposing a total order
0:35:13 Right.
0:35:13 And I definitely did hit it and.
0:35:16 You will find in the resource, it's not addressing that issue right now.
0:35:20 It's in a very naive state of when change is made, send, fetch, call to server.
0:35:26 And there are a few problems with that, even if you're the only client.
0:35:30 Because first off, if you send one event per request, it's very possible that you
0:35:35 send a lot of very quick secession events in like an order, and then they reach the
0:35:41 server in a different order because maybe you pushed the first change on low network
0:35:47 latency, the next one on high latency, or actually the reverse of that where
0:35:50 the first change hits after the second change because that's just the speed of
0:35:54 the network and that's what happened.
0:35:56 And also you need to think about offline capabilities.
0:36:00 If you push changes when they happen, how do you queue them
0:36:04 when you're not connected to the internet and then run through that
0:36:07 queue once you're back online?
0:36:09 That's another consideration you kind of have to think about.
0:36:12 Could be solved with just like an in-memory event log and
0:36:15 just kind of work with that.
0:36:16 But you still have the order issue.
0:36:19 I'm familiar with atomic clocks as a method to do this.
0:36:23 There are even SQLite extensions that'll sort of enforce that, having
0:36:28 not implemented atomic clocks.
0:36:30 Is it kind of this silver bullet to that problem or are there more
0:36:34 considerations to think about than just reaching for something like that?
0:36:38 Right.
0:36:39 I suppose you're referring to vector clocks or logical
0:36:42 clocks on a more higher level?
0:36:43 Yeah.
0:36:44 since the atomic clocks, at least my understanding is like that's
0:36:47 actually what's, at least in some super high-end hardware is like
0:36:51 an atomic clock that is, like that actually gives us like the wall clock.
0:36:56 So Right, right now is like.
0:36:58 Uh, 6:30 PM on my time, but this clock might drift, and this
0:37:03 is what makes it so difficult.
0:37:04 So what you were referring to with logical clocks, this is where it basically,
0:37:09 instead of saying like, Hey, it's 6:30 with this time zone, which makes
0:37:14 everything even more complicated, I'm keeping track of my time is like 1, 2, 3.
0:37:20 It like might just be a logical counter, like much simpler
0:37:24 actually than wall clock time.
0:37:26 but this is easier to reason about and there might be no weird issues of
0:37:31 like, Daylight saving where certainly like the, the clock is going backwards
0:37:36 or someone tinkers with the time, this is why you need logical clocks.
0:37:40 And, there, at least the mechanism that I've also landed on to
0:37:44 implement, to impose a total order.
0:37:47 But then it's also tricky, how do you exchange that?
0:37:50 how does your client know what like three means in my client, et cetera?
0:37:54 And the answer that I found to this is to like that we all trust.
0:38:00 A single, authority in the system.
0:38:02 So this is where, and I think this is also what you're going for, and with the Git
0:38:07 analogy, what we are trusting as authority in that system is GitHub or GitLab.
0:38:13 And this is where we are basically, we could theoretically, you could
0:38:17 send me your IP address and I could try to like pull directly from you.
0:38:20 It would work, and that would also work with the system that you've built.
0:38:25 However, there might still be, they're called network petitions,
0:38:29 where like the two of us have like, synced up, but some others haven't.
0:38:33 So as long as we're all connected to the same, like main upstream node, that
0:38:39 is the easiest way to, to model this.
0:38:41 An alternative would be to go full on peer to peer, which makes everything
0:38:46 a lot, lot, lot more complicated.
0:38:49 And this is where like something, like an extension of logical clocks called
0:38:53 vector clocks, can come in handy.
0:38:55 you've mentioned the, the book, designing dataset intensive application by Martin
0:39:00 Kleppman had him on the show before.
0:39:02 he's actually working on the version two of that book right now, but he's also done
0:39:06 a fantastic free course about distributed systems where he is walking through all of
0:39:12 that, with a whiteboard, I actually think so, I think does what, what the two of
0:39:18 you have very much like you've both nailed the, craft of like showing with simple
0:39:24 strokes, some very complicated matters.
0:39:27 so highly recommend to anyone who wants to learn more there.
0:39:31 Like, learn it from, from Martin.
0:39:33 He's, like an absolute master of explaining those difficult
0:39:37 concepts in a simple way.
0:39:40 But, yeah, a lot of things go kind of downstream from that total order.
0:39:45 So just to, go together on like one little journey to understand like a downstream
0:39:51 problem of this, let's say we have implemented the queuing of those events.
0:39:56 So let's say you're currently on a plane ride and, you're like.
0:40:00 Writing your blog post, you're very happy with it.
0:40:03 You have now like a thousand of events of like change
0:40:07 events that captures your work.
0:40:09 Your SQLite database is up to date.
0:40:12 but you didn't just create this new blog post, but you maybe while you're still at
0:40:16 the airport, like you created the initial version with it with like TBD in the body.
0:40:21 And your coworker thought like, oh, actually I have a lot of thoughts on this.
0:40:26 And they also started writing down some notes in there.
0:40:29 And now, the worlds have like, kind of drifted apart.
0:40:33 Your coworker.
0:40:35 Has written down some important things they don't want to lose,
0:40:38 and you've written down some things you are not aware of the other ones
0:40:42 neither are they, and at some point the semantic merge needs to happen.
0:40:48 But how do you even make that happen in this sync engine thing here?
0:40:52 And this is where you need the total order, where you basically, in the worst
0:40:57 case, this is what decides, like who, gets a say in this, who gets the last say, in
0:41:04 which order those events have happened.
0:41:07 The model that I've landed on, and I think that's similar to what Git
0:41:12 does with rebasing, is basically that before you get to push your own stuff,
0:41:18 you need to pull down the events first, and then you need to reconcile
0:41:22 your kind of stash local changes.
0:41:26 On top of the work that whoever has gotten the, who got lucky enough to push
0:41:32 first without being told to pull first.
0:41:35 So in that case, it might have been your coworker because they've
0:41:39 stayed online and kept pushing.
0:41:41 And now it sort of like falls on you to reconcile that.
0:41:46 And I've implemented a, like an actual rebase mechanism for this,
0:41:51 where you now have this set of new events that your coworker has
0:41:56 produced and you still have your set of events that, reflect your changes.
0:42:01 And now you need to reconcile this.
0:42:03 So that is purely on the.
0:42:05 Event log level, but given that we both, want to use SQLite now, we don't
0:42:12 need to just think about going forward with SQLite, but we also now need to
0:42:17 think about like, Hey, how do we go?
0:42:19 Like in Git you have like, you have this stack of events, right?
0:42:24 So you have like a commit, which has a parent of another commit, which
0:42:27 has a parent of another commit.
0:42:29 It's very similar to how your events and this event log look like, except it's now
0:42:36 no longer just one event log, but you also get this little branch from your coworker.
0:42:41 So now you need to go to the last common ancestor.
0:42:44 And from there you need to figure out like.
0:42:46 How do I linearize this?
0:42:49 I've opted for a model where everything that was pushed once cannot be
0:42:53 overwritten, so there's no force push.
0:42:55 So you basically just get to append stuff at the end.
0:42:59 But, in order to get there, you need to first roll back your own stuff, then
0:43:05 play forward what you've gotten first.
0:43:08 and then on top add those.
0:43:10 And the rolling back with SQLite is a, thing that I've like put a lot of
0:43:15 time into where I've been using another SQLite extension, called the SQLite
0:43:21 Sessions extension, which allows you, per SQLite write, to basically, record
0:43:27 what has the thing actually done.
0:43:30 So instead of storing, insert.
0:43:33 Into issues, blah, blah, blah.
0:43:35 when running that, you get a blob of let's say 30 bytes, and that has
0:43:40 recorded on SQLite level, what has happened to the SQLite database.
0:43:46 And I store that alongside of each change event, that sits in the event log.
0:43:53 And the very cool thing about this is, I can use that to replay it
0:43:57 on top of another database, but to kind of catch it up more quickly.
0:44:01 But I can also invert it.
0:44:03 So now I have basically this like, let's say 20 events.
0:44:07 And for each, I've recorded what has happened on SQLite level,
0:44:11 and now I can basically say.
0:44:13 When I need to roll back, I can revisit each of those, invert each of those
0:44:17 change sets, apply them again on the SQLite database, and then I'll end up
0:44:22 where I was before and that's how I've implemented rollback on top of SQL Lite.
0:44:27 So this is as mentioned when you're going, down the, rabbit hole
0:44:32 of like imposing a total order.
0:44:34 There's a lot of downstream things you need to do that makes
0:44:37 this even more complicated.
0:44:39 But, from what I can see, you're, on the right track if
0:44:43 you wanna pursue this further.
0:44:45 Yeah.
0:44:45 And I do have a rebasing mechanism in place in mind that's more,
0:44:52 just kind of a sledgehammer.
0:44:53 I got two SQLite databases in mind.
0:44:56 in the same way that on Git you have like your local copy of the main line and your
0:45:00 local copy of your work, there's always this local copy of Main, that's just
0:45:05 whatever events have come from the server.
0:45:07 So this is the source of truth that the server has told me about and that was
0:45:12 something I forgot to mention earlier.
0:45:13 Explaining all of this is the server is the source of truth.
0:45:16 It has that main line of the order of all of the events, and that is
0:45:21 what all the clients use to trust.
0:45:23 But yeah, it has like that local copy, and then when it pulls from
0:45:27 the server, it'll update that copy.
0:45:29 It'll look at all the events that are kind of ahead in the client,
0:45:33 and then it'll say, okay, I'm gonna roll back my client copy of my
0:45:39 branch to whatever the server is.
0:45:41 And it's literally just a file right call.
0:45:43 So it just overwrites.
0:45:46 Your like client SQLlite file with a copy of the server one.
0:45:50 And then we look at the events that the server didn't acknowledge yet
0:45:53 and then we replay those on top as a very basic way to pull and make
0:45:58 sure, because it's very possible that you made some changes locally that
0:46:02 the server hasn't acknowledged yet.
0:46:04 Like you've pushed them up still in process and you pull down the
0:46:08 latest changes and you don't see all of that stuff that you pushed
0:46:11 up yet because of network latency.
0:46:14 So this sort of avoids that problem where you pull down from the server
0:46:18 and now you need to replay whatever you did on the client that the
0:46:21 server hasn't acknowledged yet.
0:46:23 It hasn't received that network request.
0:46:25 So that was a very basic need to have some rebasing, but it does
0:46:30 get a lot more complicated when you have collaborators on a document.
0:46:34 I've seen a few different versions of this.
0:46:37 CRDTs is the fun, like magic wand.
0:46:40 It does everything.
0:46:42 but there are also solutions from Figma, for example, where they
0:46:47 say everything in Figma is kind of its own little data structure.
0:46:50 Like you can put some text and that's its own little data field.
0:46:54 You have rectangles.
0:46:54 Those are a data field.
0:46:56 And whenever you update a rectangle, like you update the pixel width of
0:47:01 a rectangle, that's like an update event on some SQL table that stores
0:47:05 all the rectangles for this document.
0:47:07 So whenever you make that update, it'll update the pixel value of whatever
0:47:12 that row entry is, and then it'll push it up for other people to receive.
0:47:17 And when you pull it down, it's last right wins.
0:47:20 In other words, whoever the last person is in that order that the
0:47:24 server decided on that total order.
0:47:26 That's a new word I know about now.
0:47:28 Didn't know it was called total order, but yeah, that, once you pull it down,
0:47:31 whatever the server said was the order of events, that's gonna be the final
0:47:35 state of that rectangle on your device.
0:47:38 The only time it becomes a problem, and you may have experienced this, if you're
0:47:41 ever working on like a fig jam together with a bunch of people, if you're all
0:47:45 typing in the same text box, everyone's just like overriding each other and a
0:47:48 text box glitches out and changes to whatever's on the other person's screen.
0:47:52 You can't see people's cursors because you're fighting to update
0:47:55 the exact same entry in the database and it can't reconcile those changes.
0:48:00 so it only works up to, like you're editing different things
0:48:04 in the file and you're not really stepping on each other too much.
0:48:08 As soon as you're stepping on each other trying to edit like the same text field,
0:48:12 then you wanna reach for something that's very, very fancy, like CRDTs.
0:48:17 Which will try to merge elegantly all of the changes that you're
0:48:20 typing into the same database field.
0:48:23 It's maybe over-prescribed because of how powerful it is, but for those specific
0:48:28 scenarios, it's really nice to reach for, and we can talk about them if you want.
0:48:32 I only have a high level understanding of what CRDTs do, but it would be
0:48:36 something to apply that kind of problem.
0:48:39 my takeaway from where to apply, CRDTs versus where I would apply event sourcing
0:48:45 is, CR DTs great for in two scenarios.
0:48:51 One, if you don't quite know yet where you want to go.
0:48:54 And where in the past you might've reached for, let's say, Firebase to
0:48:59 just like have a backend of service.
0:49:00 You know, you might want to change it later, but you just, for now,
0:49:04 you just want to get going and, you can, particularly if you
0:49:08 don't have like a strict schema across your entire application.
0:49:12 So you just try to like, not go off the rails too much, but at least the
0:49:17 data is like, mostly, like across the applications in a good spot.
0:49:22 But as you roll this out in production, and, we are shipping
0:49:26 an iOS app as well, that someone is, running an old version on.
0:49:31 Now you don't quite know, oh, this document, this data document that has
0:49:35 been synced around here, this might not yet have this field that the
0:49:39 newer application version depends on.
0:49:42 So now you have, like, this is where time drifts in a more significant
0:49:47 way and in the more traditional application architecture approach
0:49:52 you would, this way you don't trust the client in the first place.
0:49:54 Then you have like your API endpoint and the APIs, versioned, et cetera, and
0:49:58 everything is governed through the, API.
0:50:01 But now you also need to tame that problem somehow.
0:50:03 So at this point you're already, going a little bit beyond where I
0:50:07 think CRDTs shine right now, which brings me to my next kind of more
0:50:12 evergreen scenario for CRDTs, which are like very specific, tasks.
0:50:19 And so text editing, particularly rich text editing.
0:50:22 Is such a scenario where I think CRDTs are just like a very, very good, approach.
0:50:28 There's also like, you can also use ot, like operational transform, which
0:50:32 is, somewhat related under the covers, works a bit differently, but the way how
0:50:37 you would use it is pretty similarly.
0:50:40 And, related to rich text editing is also when you have like complex
0:50:45 list structures where you wanna move things within the list.
0:50:49 So if you want to go for the, Figma scenario, let's say you change the
0:50:55 order of like multiple rectangles, like where do they sit in that layer order?
0:51:01 how do you convey how you wanna change that?
0:51:04 You could always, have like maybe an array of all the IDs that give
0:51:08 you this perfect order, but if this kind of happens concurrently,
0:51:13 then you need to reconcile that.
0:51:14 So that's not great.
0:51:16 And this is where CRDTs are also like a very, special purpose
0:51:20 tool, which works super well.
0:51:23 And so what I've landed on is use event sourcing for everything except
0:51:28 where I need those special purpose tools, and this is where them reach
0:51:33 for CRDTs or for something else.
0:51:35 That's kind of the conclusion I, took away if you like the event sourcing approach.
0:51:41 But, I think ultimately it really comes down to what is the application
0:51:46 that you're building and what are, like, what is the domain of what
0:51:51 you're building and which sort of trade-offs does this require?
0:51:54 So I think in Figma.
0:51:56 The real timeness is really important and it is recognized that those different
0:52:02 pieces that are floating around, they're like pretty, independent from each other.
0:52:07 So, and if they're independent, then you don't need that total order
0:52:10 between that, which makes everything a lot easier in terms of scalability,
0:52:14 in terms of correctness, and then you don't need to rebase as much.
0:52:18 distributed systems is the ultimate case of it depends.
0:52:22 and I think trying to build one like you did, I think is a very good way
0:52:28 to like build a better understanding.
0:52:30 And also I think that opens your eyes of like, ah, now I understand why Figma
0:52:35 has this shortcoming or Notion if we are trying to change the same line, change the
0:52:40 same block as where last writers, applies.
0:52:43 Whereas in Google Docs, for example, we could easily change the, same word even.
0:52:49 And it would reconcile that in a, in a better way.
0:52:52 But, maybe you have some advice for people like yourself when you're
0:52:57 just getting started on that journey.
0:53:00 What would you tell people what they should do maybe shouldn't yet do?
0:53:05 today 2025?
0:53:07 There's more technologies out there now.
0:53:11 Beginner advice
0:53:12 Depends on the type of learner you are.
0:53:13 Sometimes some are very.
0:53:16 outcome driven, like I need to see an app running in production for me to
0:53:21 really get excited about this Tech.
0:53:24 Other people are very first principles driven.
0:53:26 Like I want to like screw in every nut and bolt myself to
0:53:30 get excited about this thing.
0:53:32 I tend to fall into the first camp where I think it is very useful to just
0:53:35 look at the docs for something like Replicache and see how would you implement
0:53:39 this kind of protocol step by step.
0:53:42 Like how would you set up the event sourcing?
0:53:45 How would you put the SQLlite store in the browser in the first place?
0:53:48 Like what capabilities are there?
0:53:50 And then try to think through those edge cases.
0:53:53 As you run into them trying to build something, I use Linear as my sort
0:53:58 of learning example, but you could use pretty much anything you want.
0:54:02 so that's definitely one approach.
0:54:03 Now there's just so many resources for how these things work under
0:54:07 the hood that you can easily learn about the intricacies yourself.
0:54:11 Another, resource is the talks given by who's the engineer at Linear.
0:54:18 I think you've had him on the show.
0:54:19 Tuomas?
0:54:20 Yes.
0:54:21 Yeah.
0:54:21 He gave a few really helpful talks about how the Linear sync engine works on a high
0:54:28 level, and that one's more opinionated.
0:54:30 It reaches for technologies like MobX, which is a react specific state
0:54:36 store, and also MongoDB for documents.
0:54:39 but you still get a high level of how they think about the
0:54:41 problem, which is really nice.
0:54:43 the other option, if you're really results driven, you wanna
0:54:46 see a local-first step running.
0:54:48 You can reach for all sorts of frameworks and libraries at this point.
0:54:52 Zero is the one that I've played with most recently, and it is Alpha Software you'll
0:54:58 run into, it holds your hands, plugging in every battery and setting up everything.
0:55:03 But error codes could be very confusing.
0:55:06 but luckily their Discord is very welcoming and will answer any question
0:55:10 that you have since their only goal is for everyone to get excited about
0:55:13 the tech and use it in production.
0:55:15 So I think Zero is a really great starting point as just, I wanna build an app.
0:55:20 I'm gonna reach for a library.
0:55:22 It will give you a query builder.
0:55:24 So instead of writing raw SQL, it'll help you write SQL queries
0:55:28 with some JavaScript functions.
0:55:30 And it also works you through very common problems that you do hit at some point.
0:55:35 And the big one is data migrations and, well, not data migrations, schema
0:55:38 migrations, because when you have a data store on the client and you have a source
0:55:44 of truth on the server everyone has to agree on how that data is shaped if
0:55:49 you're using a SQL model and not something that's Firebasey as you were mentioning.
0:55:54 So in those cases, you have to know like the four or five step
0:55:57 process of update the server schema to add the new field, then update
0:56:02 the client to add that new field.
0:56:04 And then if you're trying to delete an old field for some reason, you would
0:56:08 need to execute those on client, then server in the correct order, and then
0:56:13 manage a database version so that if a client tries to connect with really,
0:56:17 really old application code, the server can say, sorry, I only accept people who
0:56:22 are on version five of this SQL schema.
0:56:26 You're on version three, so I'm just gonna hard refresh your webpage and
0:56:29 get you up to the latest version.
0:56:31 all of these challenges are really interesting to think about and Zero
0:56:35 helps you think through them out of the box and presents docs on all of these
0:56:40 problems before you run into them.
0:56:42 but I happen to be the type that wants to run into as many brick
0:56:45 walls as possible without someone telling me what to worry about.
0:56:49 I just wanna worry about it.
0:56:51 so I think the Simple Sync Engine resource is great just because it
0:56:57 doesn't do very much and there's a lot left up to the reader to go off
0:57:01 and try to run into those challenges.
0:57:03 I'm sure splunking through like the LiveStore implementation, I would
0:57:07 find 50 ways that I could improve what I'm doing to get to that next step of
0:57:12 like resilience, schema, migrations.
0:57:14 I literally didn't even touch schema migrations.
0:57:17 there's so much that you need to think about that just crawling
0:57:20 through open source libraries is really, really helpful with, so
0:57:23 that's my preferred learning approach.
0:57:25 I just like going that way.
0:57:26 I completely agree.
0:57:27 And I also like, it's, it's sort of a bit of convincing yourself,
0:57:31 is this entire thing worth it?
0:57:33 And what I always appreciate if someone knows a little thing about
0:57:38 me and then tells me, you know what?
0:57:41 I don't think this is for you.
0:57:43 I wouldn't hold anything back for someone who wants to look into this.
0:57:47 to say like, this might not be what you're looking for.
0:57:49 If someone is very happy with like building web apps with Vite Astro NextJS,
0:57:57 et cetera, and they're productive, they're building this, e-commerce
0:58:02 platform, or they're building a more static website, I don't think there's
0:58:08 anything really where local-first would change their work situation.
0:58:14 But if they're frustrated with like actual apps that they use day to day, when you're
0:58:21 frustrated like yourself, when you're frustrated with Notion being too slow,
0:58:25 et cetera, and you're building those more productivity daily driver apps yourself.
0:58:30 For me that was like a music app.
0:58:32 I got frustrated with Spotify and other music apps.
0:58:35 I think this is the, right scenario where like local-first has something to offer,
0:58:40 but, and I think it has also the potential to become a lot simpler and easier over
0:58:47 has already become a lot simpler and easier or the past couple of years, and
0:58:51 it's gonna be even more so in the future.
0:58:53 And there will be use cases where it's actually simpler.
0:58:57 To use local-first to build something, then using Next for something.
0:59:02 but that won't apply to all scenarios.
0:59:05 And so it is not a silver bullet.
0:59:07 the closest thing you'll get to a silver bullet is the right architecture for
0:59:12 the right application scenario, but by default there is no silver bullet.
0:59:17 Neither is local-first.
0:59:19 And I think someone should evaluate, Hey, is this even for me?
0:59:23 that's, I think should be the starting point.
0:59:25 Yeah, and a meta comment just because now I'm in the agent coding space, Warp
0:59:34 is getting more capable by the day of actually editing files and scaffolding new
0:59:38 applications for you, from the terminal.
0:59:41 I've found it's less valuable to know the syntax of how all of these libraries work
0:59:47 and a lot more valuable to just know high level, what are they doing, what's the
0:59:51 architecture and how would I debug it?
0:59:53 because these agents are very good at spitting out the syntax,
0:59:56 if you draw a very clear picture.
0:59:59 So if you go off and read designing data intensive applications, and you
1:00:03 start diagramming to yourself how all of these systems are distributed, you
1:00:06 could bring that diagram to Warp or just the cloud website if you want, and
1:00:12 say, I wanna build this kind of app.
1:00:13 Here's how the architecture's gonna work.
1:00:15 This is gonna talk to this, and I know about this library.
1:00:19 I know LiveStore uses event sourcing, so I would like you to implement that
1:00:24 and use React, but follow the handrails because I understand the architecture.
1:00:29 It'll give you a way better application than if you were to just say, give
1:00:32 me a local-first app with React.
1:00:35 It would probably maybe not struggle in the beginning, but definitely
1:00:38 struggle as you try to figure out what it is doing or debug whatever sort
1:00:43 of system level issues you're having.
1:00:45 I fully agree.
1:00:46 And given that the both of us are not just application developers but also
1:00:50 tool creators, we spend a lot of time thinking about like, how do I leverage
1:00:55 the degree of freedom that I have here in the API, the way how I design the API
1:01:00 that is intuitive for someone that they like, ideally that this becomes like a
1:01:05 pit of success where they intuitively use it the right way, but also if they
1:01:09 use it the wrong way, how do they notice?
1:01:11 Do they notice like as early on as possible through type safety or only
1:01:17 if they're already in production and they felt like, wait, no,
1:01:20 this, like, this was a path that I've wrongly taken six months ago.
1:01:24 so you want to design all of this in a way that you like learn as early as possible
1:01:30 whether you're in the right track or not.
1:01:31 And I think you can't get better than simplicity than going for simplicity.
1:01:36 And this is why I love the path that you've taken with the Simple Sync Engine.
1:01:42 Through the push pull model because that's already, that is deeply familiar for
1:01:46 developers and that is how we're using Git and that has really been proven.
1:01:51 And there you can't really get much simpler than that.
1:01:54 And I think simple is great for everyone.
1:01:57 and once we have a simple foundation, we have a reliable foundation.
1:02:00 We can build fast and nice things on top of it.
1:02:03 But particularly mentioning AI systems, I make a lot of design trade offs
1:02:08 now differently, where I care less about how much effort it will be
1:02:12 to write or to discover that thing.
1:02:15 Since we have now LLMs, do TXT, et cetera, I care a lot more about like,
1:02:20 how does, how will you even spot where like this doesn't seem right.
1:02:24 The robot has given me something weird and just doesn't match my,
1:02:29 like, primitive understanding of how this entire thing fits together.
1:02:33 And that should also help the robot to like not go in the wrong
1:02:37 direction in, in the first place.
1:02:39 So, yeah, I love that.
1:02:41 Like, and going for a simple design decision, the simple like overall
1:02:47 system architecture that's gonna help you as a future programmer, observing
1:02:52 little robots, building things, a lot more to know what's going on.
1:02:57 So maybe we use that as a last segue to hear a little bit more
1:03:01 about what you're doing now at Warp in regards to agents, et cetera.
1:03:06 I've been using Warp for, a little bit.
1:03:09 I still use it, for my standalone, terminal, but most of my terminal
1:03:15 work is also happening within Cursor, which is integrated in like the
1:03:20 agent thing and Cursor, et cetera.
1:03:22 Maybe can, yeah, help me a little bit of like how I bring those two together
1:03:29 Warp
1:03:29 Yeah, and it's an interesting world of sort of agentic coding solutions.
1:03:35 It feels like there's a new approach to it every other day.
1:03:38 before joining Warp, I was also using Kline a lot, which is a VS Code
1:03:42 extension that's fully open source that will, from my experience, give
1:03:47 you a more quality agent output.
1:03:49 Since it has these two phases, you can flip on a plan switch and
1:03:54 it'll use a reasoning model to take whatever you tell it and turn
1:03:59 it into like a step-by-step plan.
1:04:02 And you could walk through like, no, that architecture doesn't make sense,
1:04:04 or Let me upload this six cal draw.
1:04:06 I actually want to work like this.
1:04:07 And you can go back and forth on like a design doc and then you can
1:04:11 flip it to act mode and that engages Claude to go build that for you.
1:04:15 And it's very hit or miss actually doing like the code edits.
1:04:19 We're all struggling with that.
1:04:21 but it was like this really nice mental model of, oh yeah, we're
1:04:24 gonna plan it out together.
1:04:26 We're gonna design jam, how this thing's gonna work and then we're
1:04:29 gonna go build it and I can just let it run and see how it ends up working.
1:04:34 and Warp is doing something similar but not within the confines of VS code.
1:04:39 And also with the addition of a voice button that I've been using a lot more
1:04:43 recently because you can talk faster than you can type is generally what I found.
1:04:48 So I can just speak into my terminal, here's how I want the app to
1:04:52 work, this, that, and the other.
1:04:54 And then it will, depending on how complicated the question is, it
1:04:58 will reach for the planning step.
1:05:00 Otherwise it'll just give you an answer right away.
1:05:01 So if it, see that's kind of complicated, let me plan it out.
1:05:04 It'll give you that same kind of like document, here's how it's going to work.
1:05:08 And then you can say, okay, do it.
1:05:10 And then it will go off and tell Claude to make file edits and do other
1:05:15 things on your machine, which is much further than Warp has gone in the past.
1:05:19 and I've really enjoyed using this to build Swift apps recently.
1:05:23 Since I was just fascinated with like, how could I build a really
1:05:27 slick desktop client like ChatGPT there's this desktop shortcut to
1:05:31 like pull up a little chat bar.
1:05:33 Like I want something that integrated.
1:05:36 I don't wanna be confined to Google Chrome anymore.
1:05:38 I wanna break out of it.
1:05:40 but if you open up XCode, you're just met with this like decade old auto
1:05:45 complete that doesn't have anything that you want in order to get stuff done.
1:05:50 but Warp is just a terminal.
1:05:51 So I'm like, okay, I'll just open the Swift project in Warp
1:05:54 and say implement this feature.
1:05:56 And it doesn't it, it can literally just enter any directory that you
1:06:00 have and just start doing things.
1:06:02 I've also used it to like migrate my open source projects from a mono repo to a
1:06:07 set of micro repos on my system and says, oh yeah, I'll just make a new directory.
1:06:12 I'll move all those files over and I'll make the necessary file edits with Cloud.
1:06:17 Very like hit or miss quality.
1:06:19 We're dialing it in.
1:06:20 But this idea of you're not constrained to the IDE anymore, you can kind
1:06:25 of just pull up your terminal and ask it to modify anything from the
1:06:30 simplest request of, help me get revert, whatever the heck I just did.
1:06:34 And it'll help you get to something more complicated, like, why
1:06:38 isn't my Postgres server running?
1:06:40 And then it'll check your Homebrew installation.
1:06:43 And then you can take it one step further to, I actually want to fix this
1:06:47 error I see in my dev server right now.
1:06:50 Because you're running the dev server in your terminal, it can say, all
1:06:53 right, pause the server, debug, debug, debug, restart the server.
1:06:57 And then if something fails again, I'll go back to debugging.
1:07:01 So it's like watching your terminal session and figuring
1:07:04 out how to help you do something.
1:07:06 It feels like it's this natural next step of let's go from you're in an
1:07:10 editor typing code quickly to like, this is a general purpose tool on
1:07:14 your machine to edit all the software that you're writing in any setting.
1:07:19 so I'm just very excited about that kind of future.
1:07:22 And we've been moving very, very quickly towards it.
1:07:25 Just in the past month, it's gone from barely usable to, I'm actually using
1:07:30 this a lot for projects in a language that I don't even know how to speak.
1:07:34 Swift.
1:07:35 it's been kind of crazy how far you can get from zero to one
1:07:39 without a lot of field knowledge.
1:07:41 And intuitively this makes a lot of sense since like Eternal is kind of like the
1:07:46 OG chat up in a way where like all the way back to like IRC, et cetera, but
1:07:51 also now with, using ChatGPT a lot or, or other LLM chat products, like yes,
1:07:57 you're, chatting with the thing, like you write a command, the command happens
1:08:01 to be like plain English or another language, and you get something back.
1:08:06 But the, kinda like back and forth.
1:08:08 And the interplay is very similar and it makes so much more, so much sense that you
1:08:14 now bring that into the terminal as well, where you get the best of both worlds.
1:08:19 You can like ride out things in a fuzzy way.
1:08:22 The terminal helps you to like put that into proper computer speak.
1:08:27 but then you also get the efficiency and the correctness from what you can do in
1:08:32 a terminal and with all of like just a top-notch craft that you get within Warp
1:08:38 as a terminal with like blocks, et cetera.
1:08:41 So yeah, highly recommend everyone to, give it a try.
1:08:44 Yeah.
1:08:44 And it's free to reach for all those things.
1:08:47 And anyone who is just bothered by AI in their terminal, you can turn it
1:08:50 off and just use Warp as a really good terminal, which is how I started using
1:08:55 it way back, probably like, 2021, I think is when it said I created my account.
1:09:01 I just used it because I wanted something that looked nice and
1:09:04 now it's going a lot deeper.
1:09:06 And yeah, the, chat app analogy is perfect.
1:09:09 There's literally a toggle between typing out commands and asking it a question,
1:09:14 and it'll even flip back and forth based on like natural language, which is fancy.
1:09:19 I mean, I'll just hit the keyboard shortcut, but why
1:09:22 not make it a little flashier?
1:09:24 Outro
1:09:24 That is awesome.
1:09:25 Well, I've already seen, a bunch of your recent videos,
1:09:28 about content related to that.
1:09:30 I'm looking forward to many more of those.
1:09:32 Is there anything else that you wanna share related to your local-first,
1:09:38 explorations or otherwise?
1:09:40 Yeah.
1:09:41 so my profile is bholmesdev everywhere.
1:09:44 So any learning resources I've put out like videos on local-first and
1:09:50 conference talks, bholmesdev on YouTube and Twitter and Bluesky.
1:09:55 And also on GitHub, so these Simple Sync Engine project I mentioned,
1:09:59 that's on my personal GitHub.
1:10:00 Also under bholmesdev.
1:10:02 You should see it as one of the star repos if you look up the profile.
1:10:06 That's it.
1:10:06 Perfect.
1:10:07 I've also put it in the show notes, so everything, you'll find it there as well.
1:10:11 But, I'm really, really excited you have put in the effort to create this project,
1:10:17 because I think there's no better way to learn something than trying to do it.
1:10:22 And you've done that and you've allowed other people to follow
1:10:25 your footsteps, and I think that's a fantastic learning resource.
1:10:29 So thank you so much for doing that and for, yeah, helping others learn
1:10:34 and, sharing what you've learned and for coming on the show today.
1:10:37 Thank you.
1:10:38 Yeah, thanks so much.
1:10:39 This was like a really far reaching conversation.
1:10:42 I hope it turns out good.
1:10:43 Thank you for listening to the localfirst.fm podcast.
1:10:46 If you've enjoyed this episode and haven't done so already, please
1:10:49 subscribe and leave a review.
1:10:51 Please also share this episode with your friends and colleagues.
1:10:54 Spreading the word about the podcast is a great way to support
1:10:57 it and to help me keep it going.
1:10:59 A special thanks again to Jazz for supporting this podcast.