resync is an early alpha monorepo for building universal Reason React applications with synchronized store state, realtime delivery, websocket mutation commands, and optional client persistence on top of a Dream server.
The repository currently ships with ecommerce, todo, and todo-multiplayer demos that exercise the shared packages.
Note: parts of this README and docs/ are AI-generated drafts. Human review and editing are required before treating them as finalized documentation.
This project is still a prototype.
- APIs, package boundaries, and schemas are not stable and are subject to change
- the ecommerce app is a demo, not a production-ready product
- expect refactors while the core abstractions settle
packages/
reason-realtime/
dream-middleware/ Dream websocket middleware with pluggable adapters
pgnotify-adapter/ PostgreSQL LISTEN/NOTIFY adapter
universal-reason-react/
components/ Universal document components for SSR + hydration
lucide-icons/ Universal Lucide icon rendering for SSR + hydration
router/ Shared nested routing for Dream + Reason React
store/ Composable store layers: core, state, sync, persist
demos/
ecommerce/
server/ Dream server demo
shared/ Shared ecommerce domain types for native + Melange
ui/ Reason React / Melange frontend demo
todo/
server/ Minimal SSR + hydration demo
ui/ Minimal client demo
todo-multiplayer/
server/ PostgreSQL-backed realtime todo demo
shared/ Shared schema + model types
ui/ Realtime todo frontend
packages/universal-reason-react/store is designed so stores do not have to use realtime sync.
Current layers:
StoreCore- schema-driven store construction, projections, and React contextStoreState- SSR serialization and hydrationStoreSync- realtime websocket sync for schema-backed storesStorePersist- client persistence adapters, currentlylocalStorage
The ecommerce demo uses these layers in two different ways:
- inventory store = core + state + sync
- cart store = core + persist
The packages in this monorepo are meant to be used together to build a universal server-reason-react application in an opinionated way.
packages/ocaml-icu4cis a highly experimental package that uses OCaml FFI to bind to icu4c. It is used to provide parity to the JavaScript Intl API.packages/universal-reason-react/intlgives you a universal API for calling JavaScript Intl API from native OCaml and JS using ocaml-icu4c under the hood.packages/universal-reason-react/routergives you one route tree for Dream and the browser, including SSR entrypoints.packages/universal-reason-react/storegives you the Tilia-backed store authoring model, hydration, persistence, and realtime sync.packages/universal-reason-react/lucide-iconskeeps SVG icon rendering consistent across server and client.packages/reason-realtime/dream-middlewareexposes the Dream websocket endpoint and middleware plumbing.packages/reason-realtime/pgnotify-adapterturns PostgreSQLLISTEN/NOTIFYinto realtime events that can feed the store.
In practice, the opinionated stack looks like this:
- Dream handles HTTP routing, SSR, and websocket endpoints
universal-reason-react/routershares route definitions between Dream and Reason Reactuniversal-reason-react/componentsrenders the document shell consistently on server and clientuniversal-reason-react/storehydrates initial state and keeps it reactive in the browserreason-realtime/*pushes server-side changes into the client store after the initial SSR render and accepts mutation commands over the same websocket connection
If you want to understand how the pieces fit together, start with the ecommerce demo and the package-level docs under docs/.
Realtime support is split into packages under packages/reason-realtime.
dream-middlewareexposes the Dream websocket middleware and adapter interfacepgnotify-adapteris the first adapter and uses PostgreSQLLISTEN/NOTIFY
The ecommerce demo uses this stack to stream inventory updates into the client store.
The todo-multiplayer demo uses the same stack for both reads and writes:
- initial state is rendered into the HTML payload
- the browser subscribes to
/_events - UI actions send
mutation <name> <json>commands over that websocket - PostgreSQL triggers broadcast patches back to the subscribed list channel
Current Dune public libraries publish under the resync.* namespace, including:
resync.reason_realtime_*resync.common_*resync.universal_reason_react_*
Universal document helpers live in packages/universal-reason-react/components.
Universal icon rendering lives in packages/universal-reason-react/lucide-icons.
These packages are used by the ecommerce demo for:
- server-rendered HTML shell generation
- client hydration
- matching Lucide SVG output on server and client
- injected scripts and serialized store state
- OCaml / Reason syntax
- Dream
- server-reason-react
- Melange
- React
- Tilia
- PostgreSQL
- pnpm
- esbuild
Install JavaScript dependencies from the repo root:
pnpm installTo verify the demos build in a Linux container:
docker compose run --rm linux-checkTo run the demos in Linux containers:
docker compose up ecommerce-demo
docker compose up todo-demoThe root docker-compose.yml provisions a Debian-based image with opam, pnpm,
and the native build dependencies, then builds the monorepo using container
volumes for the opam root, _build, and node_modules.
The postgres service also initializes the ecommerce schema from
demos/ecommerce/server/sql. Because Postgres only runs init scripts for a new
data directory, rerun with a fresh volume when you need to re-seed:
docker compose down -v
docker compose up ecommerce-demoStart PostgreSQL:
docker compose up -d postgresConfigure environment variables. For development, .envrc is recommended:
# .envrc
export DB_URL="postgres://executor:executor-password@localhost:5432/executor_db"
export API_BASE_URL="http://localhost:8899"
export ECOMMERCE_DOC_ROOT="./_build/default/demos/ecommerce/ui/src"
export TODO_DOC_ROOT="./_build/default/demos/todo/ui/src"
export TODO_MP_DOC_ROOT="./_build/default/demos/todo-multiplayer/ui/src"The server fails fast unless DB_URL and ECOMMERCE_DOC_ROOT are set.
Run the demo (requires two terminals):
# Terminal 1: Build watch
pnpm ecommerce:dune:watch
# Terminal 2: Server with restart on UI changes
pnpm ecommerce:watchNote: Dune's built-in watch (
dune exec -w) is not recommended for live development. Usepnpm ecommerce:watchinstead, which restarts the server when UI artifacts change.
Open:
http://localhost:8899
Run the demo (requires two terminals):
# Terminal 1: Build watch
pnpm todo:dune:watch
# Terminal 2: Run server (restarts on UI changes)
pnpm todo:watchNote: Set
TODO_DOC_ROOTin.envrcas shown above. The server fails fast unless this is set.
Note: Dune's built-in watch (
dune exec -w) is not recommended for live development. Usepnpm todo:watchinstead, which restarts the server when UI artifacts change.
Open:
http://localhost:8080
The todo-multiplayer demo shares the root PostgreSQL service and applies both schema DDL and generated realtime triggers.
Run the demo (requires two terminals):
# One-time or after schema changes
pnpm todo-mp:db:init
# Terminal 1: Build watch
pnpm todo-mp:dune:watch
# Terminal 2: Run server (restarts on UI changes)
pnpm todo-mp:watchNote: Set
TODO_MP_DOC_ROOTin.envrcas shown above. The server fails fast unless this is set.
Note:
pnpm todo-mp:db:initregeneratesdemos/todo-multiplayer/server/sql/generated/realtime.sqlbefore applying the schema and triggers.
Open:
http://localhost:8898
| Script | Description |
|---|---|
ecommerce:dune:watch |
Dune build watch for ecommerce |
ecommerce:watch |
Run ecommerce server, restart on .build_stamp changes |
todo:dune:watch |
Dune build watch for todo |
todo:watch |
Run todo server, restart on .build_stamp changes |
todo-mp:schema:generate |
Generate todo-multiplayer realtime SQL artifacts |
todo-mp:db:init |
Apply todo-multiplayer schema and generated realtime triggers |
todo-mp:dune:watch |
Dune build watch for todo-multiplayer |
todo-mp:watch |
Run todo-multiplayer server, restart on .build_stamp changes |
Build specific demos from the repo root:
dune build @ecommerce-app @ecommerce-server
dune build @todo-app @todo-server
dune build demos/todo-multiplayer/ui/src/.build_stamp demos/todo-multiplayer/server/src/server.exeOr build all:
dune build @all-apps @all-serversThe ecommerce demo currently uses:
ECOMMERCE_DOC_ROOT=./_build/default/demos/ecommerce/ui/srcDB_URL=postgres://executor:executor-password@localhost:5432/executor_dbAPI_BASE_URL=http://localhost:8899- server port
8899
The todo demo uses:
TODO_DOC_ROOT=./_build/default/demos/todo/ui/src- server port
8080
The todo-multiplayer demo uses:
TODO_MP_DOC_ROOT=./_build/default/demos/todo-multiplayer/ui/srcDB_URL=postgres://executor:executor-password@localhost:5432/executor_db- server port
8898
- inventory is rendered on the server and hydrated in the browser
- inventory realtime updates come from PostgreSQL notifications over websockets
- the cart is client-side and persisted to
localStorage - the demo still uses
reason-react-day-pickerin the storefront UI
- the current build flow is Dune + Melange + esbuild
- the universal packages intentionally keep separate
js/andnative/layouts to preserve the existing build pipeline - the repo is mid-refactor, so README details should follow the code and scripts in the repo rather than older planning notes
The long-term goal is to make the Dream + universal Reason React + store stack reusable across multiple apps, with demos living beside the shared packages instead of inside one application tree.