Related work
Overview
CushionDB
CushionDB is a small open-source project created by three software developers: Avshar Kirksall, Daniel Rote and Jaron Truman. It describes itself as an "open-source database for progressive web applications". It especially focuses on offline-first data management and synchronization.
Dexie.js
Dexie.js is one of the most popular open-source libraries for interacting with IndexedDB, the web browser's built-in database for storing and retrieving large amounts of structured data. The author of Dexie.js is David Fahlander. It describes itself as a "Minimalistic Wrapper for IndexedDB". Arguably, it isn't that minimalistic. But it has a lot of neat features such as live queries and browser tab sync, to name a few.
Orbit.js
Orbit.js is an open-source project from Cerebris Corporation, a "small company with a BIG open source presence"6. It describes itself as
"The Universal Data Layer" (website tagline),
"Composable data framework for ambitious web applications" (description on GitHub),
and "Orbit is a composable data framework for managing the complex needs of today's web applications" (first sentence in the README.md).
The author and core maintainer of Orbit.js is Dan Gebhardt6, Principal Software Engineer at and Co-Founder of Cerebris Corporation7. He's also a core maintainer of Ember.js and Glimmer.js and the JSON:API specification.
SQLite as a WebAssembly module
wa-sqlite
by Ryo
Hashimoto is a
WebAssembly build of
SQLite which effectively brings a
fully-fledged relational database to the web platform.
Comparison
Table 1. Related work: Comparison: Modularity
Query | Firestore | CushionDB | Dexie.js | Orbit.js | SQLite | |
---|---|---|---|---|---|---|
Full stack | ✓ | ✓ | ✓ | |||
Backend-agnostic | ✓ | ✓ | ✓ | ✓ | ||
Modular | ✓ |
Table 2. Related work: Comparison: Developer experience
Query | Firestore | CushionDB | Dexie.js | Orbit.js | SQLite | |
---|---|---|---|---|---|---|
1st-class TypeScript support | ✓ | ✓ | ✓ | ✓ | ✓ | |
Graphical developer tools | ✓ |
Table 3. Related work: Comparison: UI framework integrations
Query | Firestore | CushionDB | Dexie.js | Orbit.js | SQLite | |
---|---|---|---|---|---|---|
1st-class React support | ✓ | ✓ | ||||
1st-class Solid support | ✓ | |||||
1st-class Vue support | ✓ | ✓ | ||||
1st-class Svelte support | ✓ | ✓ |
Table 4. Related work: Comparison: ORM capabilities
Query | Firestore | CushionDB | Dexie.js | Orbit.js | SQLite | |
---|---|---|---|---|---|---|
Relationship tracking | ✓ | ✓ | ||||
Live queries | ✓ | ✓ | ✓ |
Table 5. Related work: Comparison: Consideration of asynchronicity and concurrency
Query | Firestore | CushionDB | Dexie.js | Orbit.js | SQLite | |
---|---|---|---|---|---|---|
Optimistic updates | ✓ | ✓ | ✓ | |||
Browser tab sync | ✓ | ✓ | ✓ |
Table 6. Related work: Comparison: Offline support
Query | Firestore | CushionDB | Dexie.js | Orbit.js | SQLite | |
---|---|---|---|---|---|---|
Offline-first | ✓ | ✓ | ✓ | |||
Data persistence | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
Creating when offline, publish when online | ✓ | ✓ | ✓ |
Query stands for TanStack Query.
Firestore stands for the Cloud Firestore client-side SDK.
SQLite stands for SQLite as a WebAssembly module.
Modularity
Full stack
Firestore and CushionDB are both entirely full stack solutions. TanStack Query, Orbit.js and SQLite are entirely client-side solutions. Dexie.js is a client-side solution, but on Dexie.js' home page, as of writing this, you can find a link to Dexie CloudBETA, a cloud-hosted sync service for Dexie.js.
Backend-agnostic
Firestore and CushionDB are not backend-agnostic solutions. TanStack Query, Dexie.js, Orbit.js and SQLite are backend-agnostic solutions.
Modular
Orbit.js is the only solution out of the bunch that is truly modular in the sense that it's shipped as a toolkit where you can pick and use the tools you like and discard the rest and less like a solution that you either must buy into entirely or not at all.
Developer experience
1st-class TypeScript support
TanStack Query,
Firestore,
Dexie.js and Orbit.js are all written in
TypeScript. The source for the
JavaScript bindings in wa-sqlite
is
written in JavaScript, but the library ships with its own
TypeScript type definition
declaration files. CushionDB is written in JavaScript.
Graphical developer tools
TanStack Query is the only solution out of the bunch that has graphical developer tools.
UI framework integrations
1st-class React support
TanStack Query and Dexie.js both have 1st-class support for React:
The GitHub organization Orbit.js has a
small package called
react-orbit
, but it's very
minimal, so I would categorize it as an example of how to use
Orbit.js with React instead of
viewing it as Orbit.js having 1st-class
React support.
Firestore, CushionDB and SQLite don't have 1st-class support for React.
1st-class Solid support
TanStack Query is the only solution out of the bunch which has 1st-class support for Solid:
1st-class Vue support
TanStack Query and Dexie.js both have 1st-class support for Vue:
Firestore, CushionDB, Orbit.js and SQLite don't have 1st-class support for Vue.
1st-class Svelte support
TanStack Query and Dexie.js both have 1st-class support for Svelte:
Firestore, CushionDB, Orbit.js and SQLite don't have 1st-class support for Svelte.
ORM capabilities
Relationship tracking
Orbit.js is aware of relationships in your data. SQLite is naturally aware of relationships in your data as it's a fully-fledged relational database which lets you interact with it using SQL (Structured Query Language). TanStack Query, Firestore, CushionDB and Dexie.js are not aware of relationships in your data.
Live queries
Firestore has live queries
in the form of the onSnapshot
API.
Dexie.js and Orbit.js have live queries:
TanStack Query, CushionDB and SQLite don't have live queries.
Consideration of asynchronicity and concurrency
Optimistic updates
TanStack Query, Firestore and Orbit.js provide mechanisms for doing optimistic updates. CushionDB, Dexie.js and SQLite don't provide any mechanisms for doing optimistic updates.
Browser tab sync
Firestore and Dexie.js synchronize their states across browser tabs. TanStack Query has a plugin, which is as of writing this annotated as "experimental", which synchronizes the QueryClient's state across browser tabs. CushionDB, Orbit.js and SQLite don't synchronize their states across browser tabs.
Offline support
Offline-first
Firestore, CushionDB, and Orbit.js were designed with the enablement of offline-first web experiences in mind. TanStack Query, Dexie.js and SQLite were not designed with the enablement of offline-first web experiences in mind.
Data persistence
All the solutions provide mechanisms to persist data.
Creating when offline, publish when online
Firestore, CushionDB, and Orbit.js provide mechanisms for creating data when offline and publishing it when online. TanStack Query, Dexie.js and SQLite don't provide mechanisms for creating data when offline and publishing it when online, the exception being Dexie.js, if you use it with Dexie Cloud^BETA^.
Analysis
Let's take a closer look at my hypothesis that a recipe for the solution
can be derived from the Cloud
Firestore client-side SDK.
I initially asked myself the question: "What enables the Cloud
Firestore client-side SDK
to have an API such as the onSnapshot
API,
which lets you listen to when the result of a query changes?". But that
is not the right question to ask, because the framework-agnostic core of
TanStack Query has a very similar API to
the Cloud Firestore
client-side SDK's onSnapshot
API in
that you can subscribe to be notified when the result of query changes.
So, the answer to that question is simply: "The same thing that enables
it in TanStack Query: observables and
observers.".
What is then the difference between TanStack Query and the Cloud Firestore client-side SDK? Both have queries and both let you listen to when the result of a query changes.
Let's explore the meaning of the word query in the different libraries. In general, query is, in the context of software development, a request for specific information from a database or other data storage system. In TanStack Query, a query can be more specifically described as an abstraction for an asynchronous read operation, its state, and its cached result. In the Cloud Firestore client-side SDK, a query can be more specifically described as an abstraction which describes the request for specific information itself, meaning the request itself, in a structured way, which the library understands. The Cloud Firestore client-side SDK has a query language, while TanStack Query does not.
In TanStack Query, the meaning of the key
that identifies a query is of no interest to the library. It only sees
the key as a pointer to some value in the
QueryCache
,
which is essentially a key-value store. Similarly, the data stored as
the result of a query is opaque to TanStack
Query. In contrast, the Cloud
Firestore client-side
SDK understands the data that flows through the library. This is what
allows the Cloud
Firestore client-side
SDK to know based on a socket message informing about a change, how the
in-memory data should be mutated to reflect the change and which query
listeners should be notified.
I have decided that Orbit.js is the solution to taking care of the query cache updating in a generic and robust way. I will implement a solution for using the solution (Orbit.js) together with TanStack Query.
Orbit.js is the solution because:
It's modular/composable. It can be used in a way where it just solves the query cache updating problem, but changes little else about the application, or you can go all in, and use it to power offline-first web experiences.
It has a query language, and it understands the data that flows through it, and it lets you listen to when the result of query changes, like the Cloud Firestore client-side SDK. Additionally, it lets you describe relationships in your data, which the Cloud Firestore client-side SDK doesn't let you do to the same degree.
It's an entirely client-side solution and it's backend-agnostic. The solution doesn't require the entire stack to change.
- Cerebris :: Projects, accessed at 2023-05-07 ↩
- Dan Gebhardt | LinkedIn, accessed at 2023-05-07 ↩