0:53:19 Yeah, I think for me, this is
actually kind of like a, a definition
0:53:24 of a kind of project category.
0:53:26 Like not every project needs
to like prettier lived on and
0:53:30 now it's like super common.
0:53:32 And like, I think that's one very
successful way how a project can evolve.
0:53:37 But I think with absurd-sql, you've built
something it's even more than a proof of
0:53:41 concept because you used it in production.
0:53:44 I, I use it for a while in production.
0:53:46 Other people use it for a while in
production, but, uh, like you said, like
0:53:50 it became a very influential project.
0:53:53 And I think it showed to the world,
like, Hey, we don't need to settle for
0:53:57 saving six megabytes or 50 megabytes
for every little change as you do.
0:54:02 Want to do persistence in the web before
people would just like ride, like.
0:54:07 80 megabyte JSON files, like all the
time to, to index to be, and then we're
0:54:12 then wonder why their, the app is slow
and their, their CPU is going crazy.
0:54:16 So you aiming for higher, I
think that has a massive impact.
0:54:20 And I think that's now those
ideas are still like leveraged
0:54:24 now in other projects.
0:54:25 Whether I think.
0:54:26 The wa-sqlite, um, project that is,
I think also using some, some similar
0:54:32 approaches and by now we also have a
different storage mechanism and the web
0:54:37 that is more and more common, namely
OPFS, original private file system.
0:54:43 Which also already gives you a file
system representation where you can
0:54:47 lock individual files, et cetera.
0:54:49 I think the details are still being
figured out right now, but that's, for
0:54:53 example, what I'm using for Overtone and
in production, that's already quite nice.
0:54:57 But I think you've really, you, you went
super early on that and very daring from
0:55:03 first principles and that's so admirable
and that, that like very inspiring.
0:55:07 Thank you very much.
0:55:07 I appreciate that.
0:55:08 Sometimes I, I don't know.
0:55:10 I, I don't really track.
0:55:12 The, like some of the fallout sometimes,
like I, I just had to invest in my job
0:55:16 a lot for the next like six months.
0:55:18 And so sometimes I'm, I'm not sure.
0:55:20 I don't, I don't know how much influence
it has, but it's, it's good to hear
0:55:22 feedback that work is impactful.
0:55:25 I, I would love to hear about your
experience with, with it, with OPFS.
0:55:29 And, and, uh, are you using
the file system native access
0:55:31 stuff as well and in your app?
0:55:33 How's that going?
0:55:34 The latter, not yet.
0:55:35 I'm also planning to, since like
for Overtone, which is a music
0:55:39 player, I do want to also support
you bringing your own music that
0:55:43 you might have like in a download
folder on a music folder somewhere.
0:55:47 Right now I don't use that
particular browser APIs yet.
0:55:51 But I'm using OPFS for many purposes.
0:55:54 I'm using OPFS for persistence
of a SQLite database, where you
0:55:59 would have used IndexedDB before.
0:56:01 And I think some people still
use IndexedDB for targeting
0:56:05 older browsers as well.
0:56:06 But I'm also using OPFS for like what
you would use a file system for as
0:56:11 well, which is like storing files.
0:56:13 So for example, Before displaying
images in the app, I actually don't
0:56:18 use like just an image tag and
then point to an external URL since
0:56:22 those URLs might go away, et cetera.
0:56:24 So I actually download those images, store
them in OPFS and then pre process them.
0:56:30 Um, on a worker and sent them
over on a, on an off screen
0:56:34 canvas to the main thread.
0:56:35 So I'm like using some, some more
like native development practices here
0:56:40 and trying to bring them to the web.
0:56:41 Very, very much sharing the same
opinion, like your spicy take from,
0:56:46 for me, not so spicy take that
we should aim higher in the web.
0:56:49 And I think we can learn quite a lot from.
0:56:52 More native development backgrounds.
0:56:54 And this is, I think this is how we
get, uh, after all pretty fast web apps.
0:57:00 Figma is another notable example
there where how you get actually a
0:57:04 really high performance app that feels
nice is by aiming higher and, uh,
0:57:10 bringing some of those methodologies
from other environments to the web.
0:57:14 So I think.
0:57:16 There's still a couple of
interesting aspects in absurd-sql
0:57:19 that we haven't yet gotten into.
0:57:21 So you've mentioned that you're
using index to be, uh, with, uh, the
0:57:26 block, um, the, the block storage
and the index to be transactions,
0:57:30 but to actually write that you could.
0:57:33 I guess either do, could you do
that on the main thread since you've
0:57:37 chosen to do that on a worker?
0:57:38 So maybe you can talk a little bit
about the, the threading model and
0:57:42 also how you even went beyond IndexedDB
transactions and using atomics and
0:57:49 shared array buffers to still slay some
dragons that needed to be slayed here.
0:57:54 Yeah, there is one little trick that I
discovered and I think I was actually
0:57:59 a live stream and I think I have a
recording of it, which was kind of fun.
0:58:01 It's like a fun little blew
my mind at, um, at the time.
0:58:04 And I think this is a trick
that was independently
0:58:07 discovered by several people.
0:58:08 I think there's a, uh, there's another
library that I think from is from some,
0:58:12 some Google folks about how, like,
like loading in third party things
0:58:15 on, you know, In like an iframe or
something to kind of like sandbox things
0:58:19 that that uses this same technique.
0:58:21 But essentially here's,
here's the problem.
0:58:24 When you compile SQLite through
WebAssembly, you can intercept
0:58:28 the API calls that it makes.
0:58:30 So you can intercept the like read
The read and write command, like
0:58:33 there's a C API is reading right
for reading and writing files.
0:58:37 And so you can say, okay, I'm going to
implement the read command for like,
0:58:41 through WebAssembly, the WebAssembly
compiled version of this SQLite.
0:58:45 I'm going to implement the
read command and like read some
0:58:47 data for it and give it back.
0:58:48 So I can just read from index, from index
db and everything is going to work right.
0:58:52 The, the problem is, is that
the read command from C is
0:58:55 completely synchronous, right?
0:58:56 So the WebAssembly compiled binary
expects that to be synchronous.
0:59:00 Like you can, it'll call out to
JavaScript and you can implement the read
0:59:03 command, but you cannot await in there.
0:59:06 Like you literally have to, in that event
loop, synchronously return some data.
0:59:10 And so, uh, when I hit that, I was
like, I thought I was dead in the water.
0:59:13 I was like this, this.
0:59:14 I just can't do this.
0:59:15 Maybe, maybe it wasn't that bad
actually, because I think I did
0:59:18 see, so one option is you can in
WebAssembly, I think it has a way to
0:59:22 convert synchronous APIs to async.
0:59:24 I think it's called like
asynchronify or something like that.
0:59:27 I was terrified of it to be honest.
0:59:29 And so I thought it just
wasn't going to work.
0:59:30 I was terrified about the performance.
0:59:32 I've had experiences in some
codebases that overuse of asynchronous
0:59:36 APIs really killed performance
because waiting one promise tick.
0:59:41 In every single layer of the
abstraction is very not noticeable
0:59:46 at a small scale, but it very
becomes noticeable at a large scale.
0:59:50 And the really pernicious thing about it
is that it's death by not a thousand cuts.
0:59:54 It's death by a million cuts.
0:59:55 Like, it's, you can't
go in there and remove.
0:59:58 20 by 20 awaits, right?
0:59:59 And like, it'll be fine.
1:00:00 The thing that I don't like about
async code is that as you abstract
1:00:03 things away, so if you take it from
two abstractions to 10 abstract, like
1:00:06 you split a two async functions into
10 async functions, well, every one
1:00:10 of them has to await on each other.
1:00:12 And so it's literally making it slower.
1:00:14 And that's not the
case , with synchronous stuff.
1:00:16 You can make it two functions.
1:00:17 You can make it 500 functions.
1:00:19 You can abstract things
the way that you want to.
1:00:21 The shape of your abstraction
literally impacts performance when
1:00:25 you're in, in, um, async world.
1:00:26 So I have experience with code bases that
like, that did things in a really weird
1:00:31 way, like awaited literally everything.
1:00:33 And it was, it was causing
all sorts of perf problems.
1:00:36 And so that, For that reason, from my
experience, I was terrified of it because
1:00:40 I thought it made every single function
in C in the WebAssembly binary async.
1:00:44 It turns out that I was probably wrong
and I probably should have done my
1:00:48 own investigation in like performance
profiling because I got some feedback.
1:00:52 Later on, like months after I released
absurd-sql, I think from some people
1:00:56 that I respected pretty well that
were saying, uh, the async stuff is
1:01:00 actually fine, like it actually has
a very negligible performance impact.
1:01:04 So there's probably some tricks
that they, they, they do that.
1:01:07 So it's probably fine.
1:01:07 And I think that WA SQLite
project that you mentioned, I
1:01:11 think it actually uses this mode.
1:01:14 And the good thing about that is
that it doesn't need atomics, which
1:01:17 means it doesn't require HTTPS.
1:01:19 So I'm probably.
1:01:21 Let me jump back a little bit to
explain why atomics are even needed.
1:01:24 So let's go back to that
synchronous method, right?
1:01:26 So I need to call into IndexedDB
and get and do an async API.
1:01:30 How am I going to do that?
1:01:31 Well, there's a little trick that
you can do by making an async call.
1:01:37 And I believe that it's Has
to be on a different thread.
1:01:42 So there's like two
background threads, right?
1:01:44 There's like the normal backend thread.
1:01:46 And then there's another separate thread.
1:01:48 That's like the thread that reads and
writes from index CB like asynchronously.
1:01:51 Right.
1:01:52 Between those two threads, you
share a shared array buffer, which
1:01:55 is this really low level, really
interesting thing on the web, which
1:01:59 is shared memory across threads.
1:02:01 And on top of that, you, uh, there's
APIs you can use called atomics, which
1:02:05 allow you to interact with this shared
array buffer, and you can actually
1:02:08 coordinate across the threads and do
some really, really powerful things.
1:02:12 So one of those things that you can
do is you can write to that shared
1:02:15 array buffer in a certain way.
1:02:17 And then in another thread,
you can call atomics.Wait.
1:02:20 And what atomics.wait does is it literally
synchronously blocks that thread from
1:02:24 running like it, you call that thread.
1:02:26 And if it does not wake up,
then it won't wake up ever.
1:02:29 Like you call atomic weight and you
tell it, wait for this bit to be
1:02:33 flipped in the shared array buffer.
1:02:34 And then the other thread can
flip that bit when it's not.
1:02:37 done, and then the other
thing can continue executing.
1:02:40 Using that technique, we can read from an
asynchronous thing in the second thread,
1:02:44 do whatever we need to, and store that
data in some sort of buffer somewhere.
1:02:49 And then while the first thread is
blocked on that atomic set, wait.
1:02:52 And then once the, once that
bit is flipped and it continues
1:02:55 executing, then it can read from that
buffer and actually get the data.
1:02:58 And so using that technique, Um, I
use that technique in every single API
1:03:02 in, in Upstairs SQL to turn an async
function into a synchronous function.
1:03:06 And that's how it can
interface with IndexedDB.
1:03:09 The downside though, is that to use
atomic set weight, it's one of those
1:03:13 newer APIs that Chrome and other browsers
force HTTPS a secure context on you.
1:03:18 And so your app has to be running under
HTTPS, which I've always said, like.
1:03:22 Who cares?
1:03:23 Like, of course you're going to
be running under that anyway.
1:03:25 It's been enough of a,
kind of a pain point.
1:03:27 Like people are using like weird
reverse proxies or doing their
1:03:30 own things where suppose, I guess
some people just don't really care
1:03:32 and they just want to run HTTP.
1:03:33 And so Actual, like the open source
version of Actual can't run under HTTP.
1:03:37 And that's like, you always
have to be setting up to me.
1:03:40 It's, I don't know, I still am a
little bit like, I don't really care.
1:03:42 Like you can just like set up
HTTPS, but it's enough of a source
1:03:45 of a pain that I can see benefits
and not, not having to require it.
1:03:49 Right.
1:03:49 Yeah, I think this was also related
to the, uh, Spectre exploit at
1:03:53 some point, uh, vulnerability.
1:03:55 And I think it's not just that you need
to run on HTTPS by now, but I think you
1:03:59 also need to have a few HTTP headers set.
1:04:03 I think like cross origin, uh,
open policy and embedders policy.
1:04:07 And I think there was also like
a limitation that Safari didn't
1:04:10 support it well for, for some time.
1:04:12 So yeah, there it's, Still, browsers
are still growing up, but I think at
1:04:17 some point this can be assumed that, uh,
this will just work, but on the other
1:04:21 side, I think those tricks might also,
you mentioned that asyncify approach.
1:04:26 So that is also another option.
1:04:28 And I think there's even a new
approach stabilizing right now.
1:04:32 Where WebAssembly natively
can integrate with Promises.
1:04:36 So I think that's a, that's a new, um,
development around WASM and browsers.
1:04:42 So I think there's, you, you certainly
use quite the bleeding edge there.
1:04:47 And so, yeah, right now it's
getting more stabilized.
1:04:51 But I do think there is some truth
to what you've mentioned in regards
1:04:55 to avoiding asynchronous code when
possible, since it's not just like
1:04:59 a potential performance overhead.
1:05:02 So, and I think some people go even
as far as saying that going from
1:05:06 callbacks to async await and promises.
1:05:09 Was one of the biggest
mistakes in JavaScript.
1:05:12 I think that can also be
considered a hot take.
1:05:14 Let's see where we'll end up
in a couple of years on that.
1:05:17 But, uh, aside from the performance,
I think another common downside
1:05:22 of asynchronous code is that it
basically introduces distributed
1:05:25 systems problems into, into your code.
1:05:28 In this case, where we basically
just wrap an API, I think it's okay.
1:05:32 Um, But, uh, that's a, that's another
notable difference of like how, what
1:05:37 we've been exploring with LiveStore
and, and Riffle is by really making
1:05:42 the, like in a browser context, still
from the main thread, allowing for
1:05:46 synchronous SQL queries, which return
very fast and therefore like you can just
1:05:52 write your normal JavaScript as you do.
1:05:54 comes at a risk of potentially
blocking the main thread.
1:05:56 That's a, that's a different
challenge, but if you, if you have
1:06:01 performance under control, it gives
you a much simpler programming model.
1:06:04 So that's another weak one.
1:06:05 Yeah, that's, I'm all here
for synchronous SQL queries.
1:06:09 I think it's crazy.
1:06:11 I think there's been no libraries
before that integrated with SQLite 3 and
1:06:14 like made all of the APIs, um, async.
1:06:16 It was crazy.
1:06:17 Like it's such a in
memory super close local.
1:06:20 thing and , you really want at the
low level to, to provide the at
1:06:24 least option to be synchronous.
1:06:25 And it, so that I just remembered
actually, it's not just
1:06:28 wrapping an async function.
1:06:30 So like it's, it's an internal
implementation detail.
1:06:33 If you go the asynchronous by route,
which turns C functions into asynchronous
1:06:37 functions, therefore you can interact
with like asynchronous stuff.
1:06:41 Believe that that, that forces you
when you call a SQLite by like method,
1:06:47 that method is an asynchronous method.
1:06:49 You cannot synchronously execute a SQL.
1:06:51 That was a big reason why I also really
did not want to use WI SQL because I
1:06:56 wanted my functions to be synchronous.
1:06:58 My app depends on many of them being
synchronous and just my workflows.
1:07:01 And it just, it just greatly
simplifies the entire workflow.
1:07:04 There's no reason for
me to make this async.
1:07:06 This is a single client.
1:07:08 App, there's one request
coming through at a time.
1:07:11 I can control it entirely, right?
1:07:13 This is not a web server handling
thousands of requests at a time
1:07:17 to take on the complexities of
asynchronous code with the performance
1:07:21 hits was just ridiculous to me.
1:07:23 And so I, This technique I still think
has has merit, actually, the more I think
1:07:28 about it, and it's something that I owe
this community like a great deal of, of
1:07:34 like blog posts and and and research.
1:07:36 I need to sit down and really
like go through what the latest
1:07:39 and greatest is and really.
1:07:40 Vetted and see, see what people
are landing on, because if it's
1:07:44 still not possible to do that,
I think that's a huge downside.
1:07:47 I do.
1:07:47 I did just see that the, the
file system access APIs, I
1:07:51 believe they finally converted.
1:07:53 Originally there were some of them
in the, in a worker thread that
1:07:56 were supposed to be synchronous, but
in the spec, we're actually async.
1:08:00 And that made me really, really not happy.
1:08:03 And I filed like a GitHub ticket.
1:08:05 I think they finally, if I.
1:08:07 Just checked earlier this morning.
1:08:08 They finally have on the MDN
page says that they are now
1:08:11 synchronous, which is great.
1:08:12 So hopefully we're moving
in the right direction.
1:08:14 But I think it's, yeah, I think it's
really, I'm all about synchronous 'cause
1:08:17 it means that you can use them in context
that like are synchronous, like there you
1:08:21 might be removing a lot of functionality
because once something is async, the thing
1:08:27 that uses it has to be async as well.
1:08:29 And a lot of these cases for local-first
apps especially, it just is, there's,
1:08:32 there's literally no benefit to it.
1:08:33 , it's a local app.
1:08:35 There's not a second user that
can come be querying this.
1:08:38 I don't know.
1:08:38 I think I get it.
1:08:40 I think we've, we've over
indexed a little bit on this.
1:08:42 And I do think that I
forgot about the headers.
1:08:45 That is because like reverse
proxies can like drop those headers
1:08:48 and then like a user is like,
why isn't this app even loading?
1:08:50 So I, I don't know.
1:08:51 It's a trade off.
1:08:52 I still probably lean a little
bit towards it's worth it.
1:08:55 But yeah, I completely agree with, uh,
I think this is the same theme again,
1:09:00 where in web development, we've kind
of gotten so used to some practices.
1:09:06 And I think it's one is, uh,
being efficient and performance
1:09:09 minded, but another is just
like the programming models, how
1:09:13 they've kind of eroded over time.
1:09:15 we need to deal with distributed
systems problems where they're
1:09:18 just completely accidental.
1:09:20 And , we're just so used to, to like
so many things being asynchronous,
1:09:25 which doesn't need to be asynchronous.
1:09:27 We kind of went from callback
hell to async hell in a way.
1:09:30 And in React we use, useEffect for
so many things where we shouldn't.
1:09:35 And is just so wild
that like most of our.
1:09:38 Data interactions are asynchronous,
like in a way it's almost like if
1:09:43 react would not just give you a value
right away, but would give you like a
1:09:48 tuple of like either it's loading or
a value is like we're already halfway
1:09:52 there just in, in such a bad direction.
1:09:55 So I, uh, I think this is kind of a
subtle difference for people to understand
1:10:00 how much synchronous code execution
can simplify your app development.
1:10:05 But I think I'm, I'm preaching
to, to the choir here.
1:10:09 Yeah, sure.
1:10:10 Totally.
1:10:11 I mean, it helps with debugging.
1:10:12 Like if you're stepping over code,
whenever you're hitting like async
1:10:15 code, Chrome tries to do this thing
where it like will step over the await,
1:10:19 but it only works like half the time.
1:10:21 Like it's yeah, it's super annoying.
1:10:23 I do get this needed probably most of
the time, but it makes me sad when I just
1:10:27 see it like applied without any thought.
1:10:29 So after fulfilling your goal with
bringing Actual to the web through
1:10:35 absurd-sql, which I think is like
just to look back, like how much
1:10:39 pioneering work you've really
done to make this app happen.
1:10:42 Like you started this journey before.
1:10:45 The term local-first was there.
1:10:47 You've built one of the first credible
local-first apps and really invented
1:10:51 so many things along the way, like all
by yourself with the help from, from
1:10:56 some of your, your friends, but you,
like you figured out like how to use
1:11:00 SQLite in like even in a web context in
a reactive way, but then also made it
1:11:05 collaborative through your sync engine and
ultimately brought all of this to the web.
1:11:09 That is an impressive journey.
1:11:11 And I think you've been on the journey,
not just building all of this full
1:11:15 time, but you actually had like some,
some, some Actual full time job next
1:11:20 to that, which I think at some point
was just too much, which led you to,
1:11:24 to at some point, uh, hand over the
project to the broader community.