| yeah we specifically decided not to be local/offline-first because these add huge complexity that is not needed for the type of apps we want to support. I spoke about this a bit here if you are interested: https://www.youtube.com/watch?v=86NmEerklTs&t=1764s As for ZQL: a) basically all of our customers already use Drizzle/Prisma. So they are very used to custom DSLs, and like them. I know, I was surprised to! b) You typically use the same code client-side and server-side. There's no branching. The example you pasted is showing an escape hatch for when you want to use custom SQL. The option is there, but it's not the common experience. This is what a typical mutator looks like: ```ts
// src/mutators.ts
import {defineMutators, defineMutator} from '@rocicorp/zero'
import {z} from 'zod'
export const mutators = defineMutators({
updateIssue: defineMutator(
z.object({
id: z.string(),
title: z.string()
}),
async ({tx, args: {id, title}}) => {
if (title.length > 100) {
throw new Error(`Title is too long`)
}
await tx.mutate.issue.update({
id,
title
})
}
)
})
```We are trying to make apps like Notion, Linear, Superhuman easier to create. These apps all uses custom-built sync engines that took their teams many person-years of effort to construct. Whether this complexity is worth it depends on how badly you want instantaneous response. If you do, you will end up using sync one way or other, and you will end up with something roughly like Zero mutators. |
I would use these DSL if they provide 10x improvement but it seems to me like a downgrade in every way I will need to rely on you to keep this thing running 10 years down the road and hope you are still in business. Whereas raw SQL will probably work as is since past performance is usually indicator of future and sqlite/postgres are 25 years old and if I recall correctly you already had this similar project that is now no longer maintained: https://replicache.dev/ so this by default makes me trust this project less since it will touch critical parts of my app.
Also, imo the custom sync engine path is usually better because most of this turns into logical replication unless you are syncing simple notes and then teams already know what they need and a last-write-wins + row_id,table => changes_log tables isn't that hard the issue is usually that the client and server will need to duplicate functionality and I will be a lot more comfortable duplicating those using raw sql queries since you write those ones and use on both sides. Any half-sync or other optimizations usually just end up causing a lot of headaches in a relational db with foreign keys on.
So, I would use something like this only if it is a sidecar process like litestream seamlessly doing it's thing vs becoming a main concern in my app core. But that again is the issue logical replication vs physical replication and how can a sidecar know the intent.