PhaseLock – the obvious sync engine
One event log plus replication.
______________________________
| Frontend |
| _________ _______ ____ |
| | | | | | | |
| | Reducer | | State | | UI | |
| |_________| |_______| |____| |
|___^____________________|_____|
| |
| Events | Commands
| are | are
| read | written
___|____________________v_____
| |
| Backend |
|______________________________|
PhaseLock assumes you save all your events (like, "user X did Y") in a log.
Then you write a reducer function that reads events and builds state in a key-value store.
Then the sync engine behavior is obvious:
- An offline-capable client streams events since it last connected.
- The reducer updates local state, triggering UI updates.
- User actions (called commands until they're accepted as events) are queued in an outbox until they can be sent.
- Optimistic updates reuse the reducer you already wrote.
PhaseLock is really just tooling around this architecture.
We're building a cloud!
Deploy a PhaseLock app with a single command. Sign up for development updates and to be the first to try it out.
FAQ
Can I use PhaseLock right now?
PhaseLock is ready for anybody willing to play with alpha software.
While it is still lacking serious documentation, it does have an agent skill to help you get started.
Does PhaseLock work well with coding agents?
PhaseLock is designed to work well with coding agents. Try our agent skill:
(for any agent, as a skill:)
npx skills add phaselock-io/phaselock
(for Claude, as a plugin:)
/plugin marketplace add phaselock-io/phaselock
/plugin install phaselock@phaselock
Additionally, the pattern of storing events and
deriving state (called "event sourcing") gives your agent
time-travel capabilities to help debug issues that arise in production.
Think git bisect but for database history.
How is PhaseLock different from a normal CRUD app?
In a normal CRUD app, client-requested mutations (Create, Update, and Delete) are applied directly to your state-based database, often a relational database like Postgres or MySQL.
In CRUD world, your database is the One True Arbiter of state, and clients can only know about state through a REST API. Updates to your database can be arbitrarily complicated and keeping a client up-to-date either means bolting on some change detection machinery or, frequently, just non-stop polling the REST API.
In contrast, PhaseLock assumes you store incoming mutation requests as events in a log when you receive them rather than apply them to your database immediately. That means you can build (or rebuild) your state at any time, and keeping clients up-to-date just means broadcasting events as they arrive. The challenge of "how do I make sure every client rebuilds the same state" is solved by writing your reducers once and running them everywhere.
Is PhaseLock open-source?
PhaseLock is open-source under the Apache License 2.0. Check us out on GitHub.
When will PhaseLock Cloud be ready?
We are aiming for the free tier of PhaseLock Cloud sometime in October 2026, with the paid tier to follow.
Does PhaseLock lock me in to a single cloud or backend?
PhaseLock is a sync engine based on events, not based on any particular backend or cloud. How you deliver those events is up to you. PhaseLock was developed against KurrentDB. You could also make Postgres work, though our examples don't show that yet.
We are also building a hosted offering, PhaseLock Cloud, which we hope to make the easiest and simplest way to deploy PhaseLock apps. But PhaseLock Cloud will never be required for PhaseLock to work.
What frontend frameworks does PhaseLock support?
PhaseLock is designed to deliver fresh, reactive state to any UI written in any language or framework.
PhaseLock is written in TypeScript but designed to be embedded in other languages. Code generation is used to achieve cross-language strong typing.
Today PhaseLock works on web (the examples are in React) and React Native, with official iOS and Android support coming soon. New languages and frameworks are relatively cheap to add. You can have your coding agent analyze our generators and write a new one for your language today.
Is PhaseLock only for frontends?
Not only is PhaseLock great for frontends, it is an excellent tool for backend workers. It can maintain up-to-date local state and trigger event-based workflows just as easily as it triggers UI updates in a frontend app. In fact, PhaseLock Cloud will be a PhaseLock app.
Does PhaseLock support fully-offline mobile apps and PWAs?
PhaseLock supports fully-offline applications, both mobile apps and PWAs. While offline, user actions are saved as commands into the outbox. When you reconnect, the outbox is sent to the server and new events are downloaded, bringing both sides in sync.
Optimistic updates are simple and reuse the reducer you already wrote. You write a "forecaster" function that predicts the event(s) the server will save for each command, and PhaseLock handles the rest: automatically calling the forecaster function when saving commands to the outbox, running forecasted events through your reducer, and applying updates to a state overlay to update the UI.
Won't my event log grow forever?
Well yes, that's the point. It's OK though, it's 2026 and disk is cheap.
Additionally, mature event sourcing databases like KurrentDB offer retention policies to limit how much disk is spent on event streams where history isn't valuable, such as presence indicators. Eventually you are left with a rich history that you care about, not a zillion events of noise.
Do I have to sync my entire database to every client?
Each client subscribes only to the events it needs to do its job.
You may not want to send very large or sensitive data at all. For those situations, PhaseLock supports server-side queries, so a client can request a named query with parameters, and the server can execute the query and stream the results. Our todo-thin example demonstrates this feature.
How does PhaseLock handle permissions?
Currently, PhaseLock expects that you implement a server between your clients and your event log. That server enforces which events should be blocked or sanitized for each client, and which commands a client is allowed to submit.
Additionally, PhaseLock supports server-side queries, which are implemented and executed by the server, so sensitive info can be kept server-side and exposed through explicit queries.
Won't the first page load take a long time?
When a webpage first loads, or a mobile app is first booted, it has no initial local state and may have to download a substantial amount of data before it starts working. Your users may not appreciate a long loading screen.
The solution in PhaseLock is a hybrid app, capable of running queries either server-side or locally. PhaseLock makes it easy to switch from server-side results to local results when the local state is fully hydrated.
Another improvement would be to bootstrap new clients with a snapshot instead of directly from the event log. This strategy would not return results faster than server-side queries, but it would reduce how long it takes a hybrid app to switch over after startup. PhaseLock does not assist with any snapshotting system today, but we hope to in the future.
How does PhaseLock resolve conflicts?
PhaseLock gives you tools for handling conflicts in various ways, but does not prescribe how you do it. The best part is that you get to decide how to handle each case according to the specifics of that case.
Many conflicts are well-served by a last-write wins solution. In this case, you can write all attempts to the event log, and the reducer will naturally process the final attempt last.
In many collaborative cases, CRDT-based solutions exist. JavaScript has mature CRDT libraries like Yjs and Automerge that you can import and use in your reducers. In this case, the event log becomes merely a transmission medium for CRDT updates.
In more complex cases, and frequently with offline-capable apps, it is important to capture the human effort associated with a command first (saving it to the event log) and then resolve conflicts afterwards. This can be done in reducers, or if the conflict resolution requires more visibility than the clients will have, you can have a separate backend worker that tails the log looking for conflicts and emits clear decision events that clients can see.
In other cases, like account sign-ups, the feature is expected to only work online and the server can simply refuse a command that would create a conflict. In Postgres, this would be done in a transaction. In KurrentDB, you would use optimistic concurrency control.
Does PhaseLock use CRDTs?
PhaseLock itself does not use CRDTs, but PhaseLock can help you use CRDTs whenever they make sense for you.
PhaseLock operates on ordered events replicated to every client, but it is your reducer that converts events to state. Depending on your app needs, some or all of your events may be CRDT updates and your reducer can import Yjs or Automerge or a JavaScript CRDT library of your choice to handle them.
How does PhaseLock compare to Convex?
Convex is a realtime database designed to serve the needs of both reactive UIs and backend workers, much like PhaseLock.
Convex also supports end-to-end type safety, like PhaseLock.
Convex is limited to server-side queries, while PhaseLock supports both server-side and local queries. This means PhaseLock supports offline-capable apps while Convex does not (though Convex is working on Curvilinear, their own offline sync engine).
PhaseLock apps keep an event log and explicit reducers, meaning a PhaseLock app's history can be meaningfully traversed, while Convex can only show you the current state at any time.
How does PhaseLock compare to Zero?
Zero is a sync engine focused on high-performance, low-latency access to huge backend stores. Zero's architecture is like PhaseLock's server-side queries pattern, plus a smart local cache for returning initial results without a round-trip to the server.
However, for offline apps, Zero only supports reads; offline writes are explicitly not supported. PhaseLock has full support for offline reads and writes with optimistic updates.
Additionally, Zero only supports JavaScript environments, so web and React Native. PhaseLock is written in TypeScript but specifically designed to be embedded in other languages.
PhaseLock apps keep an event log and explicit reducers, meaning a PhaseLock app's history can be meaningfully traversed, while Zero can only show you the current state at any time.
How does PhaseLock compare to Electric + TanStack DB?
Electric is a read-side sync engine over Postgres, and TanStack is its natural client-side counterpart. They can be added to existing Postgres-based applications easily, while PhaseLock is meant to be used from the beginning of an app's development.
Electric supports queries written in SQL, while PhaseLock supports a key-value storage model with queries written as plain functions.
Additionally, while Electric works in non-JavaScript environments, TanStack doesn't, so offline apps are limited to web or React Native. PhaseLock is written in TypeScript but specifically designed to be embedded in other languages.
PhaseLock apps keep an event log and explicit reducers, meaning a PhaseLock app's history can be meaningfully traversed, while Electric + TanStack can only show you the current state at any time.