Ask HN: Why hasn't x86 caught up with Apple M series?
437 points by stephenheron 3d ago 616 comments
Ask HN: Best codebases to study to learn software design?
106 points by pixelworm 5d ago 92 comments
Offline-First Landscape – 2025
53 Onavo 20 8/29/2025, 4:20:31 PM marcoapp.io ↗
Initially I built only a desktop client, because I didn't like IndexedDB. After the app got into HN, someone recommended to check for OPFS (Origin Private File System).
Now we have a full offline-first app in web using SQLite on top of OPFS. We didn't test it with large scale yet, but so far looks very promising. The good thing is that we use Kysely as an abstraction for performing queries in SQLite which helps us share most of the code across both platforms (electron + web) with some minor abstractions.
You can check the implementation in Github: https://github.com/colanode/colanode
It's oriented around event sourcing and syncs the events, which get materialized into local table views on clients. It's got pretty slick devtools too.
"The root cause is that all of these offline-first tools for web are essentially hacks. PowerSync itself is WASM SQLite... On top of IndexedDB."
But there's a new web storage API in town, Origin Private File System. https://developer.mozilla.org/en-US/docs/Web/API/File_System... "It provides access to a special kind of file that is highly optimized for performance and offers in-place write access to its content."
OPFS reached Baseline "Newly Available" in March 2023; it will be "Widely Available" in September.
WASM sqlite on OPFS is, finally, not a hack, and is pretty much exactly what the author needed in the first place.
WebSQL was a clunky API, but not as clunky as IndexedDB which is truly yucky and very easy to get wrong in modern apps that use promises.
It's much better than WebSQL could ever be. You get the full power of modern SQLite, with the version, compile options, additional extensions, all under your control.
It would be nice to have WebSQL though, even if it has to be spec'd as "it's sqlite".
As mentioned, everything fast(ish) is using SQLite under the hood. If you don’t already know, SQLite has a limited set of types, and some funky defaults. How are you going to take this loosey-goosey typed data and store it in a backend database when you sync? What about foreign key constraints, etc., can you live without those? Some of the sync solutions don’t support enforcing them on the client.
Also, the SQLite query planner isn’t great in my experience, even when you’re only joining on ids/indexes.
Document databases seem more friendly/natural, but as mentioned indexeddb is slow.
I wish this looked at https://rxdb.info/ more. They have some posts that lead me to believe they have a good grasp on the issues in this space at least
Also, OPFS is a newish thing everyone is using to store SQLite directly instead of wrapping IndexedDB for better performance.
Notion is a very async collaborative application and we rely on a form of transactions. When you make a change in Notion like moving a bunch of blocks from one page to another, we compose the transaction client-side given the client's in-memory snapshot view of the universe, and send the transaction to the server. If the transaction turns out to violate some server-side validation (like a permissions issue), we reject the change as a unit and roll back the client.
I'm not sure how we'd do this kind of thing with RxDb. If we model it as a delete in one document and an insert into another document, we'd get data loss. Maybe they'd tell us our app shouldn't have that feature.
There’s just an assumption that these client databases don’t need mature tools and migration strategies as “it’s just a web client, you can always just re-sync with a server”. Few client db felt mature enough to warrant building my entire app on as they’re not the easiet to migrate off of.
I also tried LokiJS which is mentioned in the OP. I even forked (renamed it SylvieJS lol) it to rewrite it in TS and update some of the adapters. I ultimately moved away from it as well. I found an in memory db will struggle past a few hundred mbs which I hit pretty quickly.
No matter what db you use, you’re realistically using indexed db behind the hood. What surprised me was that a query to indexed db can be slower than a network call. Like what.
We've had great success with Replicache+Orama since this was written. We're keen to give Zero a spin once it's a bit more stable.
Triplit has essentially folded as a "company" and become some sort of open-source initiative instead.
InstantDB has matured massively and is definitely worth a look for anyone starting a new project.
Zero (and I believe Replicache as well) layer their own SQL-like semantics on top of an arbitrary KV store, much like the layering of SQLite-over-IndexedDB discussed; like SQLite-over-IndexedDB, I believe they are storing binary byte pages in the underlying KV store and each page contains data for one-or-more Replicache/Zero records. The big difference between SQLite-over-IndexedDB and Zero-over-IndexedDB is that Zero is written with sympathy to IndexedDB's performance characteristics, whereas SQLite is written with sympathy to conventional filesystem performance.
On the subject of "keep whole thing in memory", this is what Zero does for its instant performance, and why they suggest limiting your working set / data desired at app boot to ~40MB, although I can't find a reference for this. Zero is smart though and will pick the 40MB for you though. Hopefully Zero folks come by and corrects me if I'm wrong.
> Zero (and I believe Replicache as well) layer their own SQL-like semantics on top of an arbitrary KV store, much like the layering of SQLite-over-IndexedDB discussed
Replicache exposes only a kv interface. Zero does expose a SQL-like interface.
> I believe they are storing binary byte pages in the underlying KV store and each page contains data for one-or-more Replicache/Zero records.
The pages are JSON values not binary encoded, but that's an impl detail. At a big picture, you're right that both Replicache and Zero aggregate many values into pages that are stored in IDB (or SQLite in React Native).
> On the subject of "keep whole thing in memory", this is what Zero does for its instant performance, and why they suggest limiting your working set / data desired at app boot to ~40MB, although I can't find a reference for this. Zero is smart though and will pick the 40MB for you though. Hopefully Zero folks come by and corrects me if I'm wrong.
Replicache and Zero are a bit different here. Replicache keeps only up to 64MB in memory. It uses an LRU cache to manage this. The rest is paged in and out of IDB.
This ended up being a really big perf cliff because bigger applications would thrash against this limit.
In Zero, we just keep the entire client datastore in memory. Basically we use IDB/SQLite as a backup/restore target. We don't page in and out of it.
This might sound worse, but the difference is Zero's query-driven sync. Queries automatically fallback to the server and sync. So the whole model is different. You don't sync everything, you just sync what you need. From some upcoming docs:
https://i.imgur.com/y91qFrx.png
The constructor allows you to pass in any arbitrary KVStore provider, and we happen to use op-sqlite as its performance is exceptional.
There is no "different data layer" per se, just a different storage mechanism.
Replicache also holds a mem cache that is limited to ~50MB if I recall. Our use case is extremely data-heavy, so we might end up never migrating to Zero – who knows.
Perhaps I misunderstood your question, let me know if I can clarify further.
Notion always* has a webview component, even in native apps, but we also have a substantial amount of "true native" Swift/Kotlin. We can't use Replicache/Zero today because our native code and our webview share the SQLite database and both need to be able to read and write the data there; if we use Replicache that would make our persisted data opaque bytes to Swift/Kotlin.
*There's many screens of the Android/iOS app that are entirely native but the editor will probably remain a webview for a while yet.