A reactive, local-first application platform on standard Postgres.
A Rust reactivity & sync engine and an end-to-end-typed TypeScript SDK — write your schema and server functions in TypeScript, call them from a fully-inferred client, and your queries update in realtime.
Reactive backends give you a wonderful developer experience — write query /
mutation / action functions, define a schema, call them from a typed client,
and reads update in realtime with no manual cache invalidation. The catch is
that the great ones make you give up your database: a proprietary store you
can't reach with psql, pg_dump, BI tools, extensions like PostGIS or
pgvector, or plain SQL joins and window functions. Your data lives inside
someone else's engine.
Pulse keeps the database of record a standard Postgres you fully own — and puts the reactive programming model and end-to-end-typed DX on top of it:
- ✍️ Author in TypeScript. Write
query/mutation/actionfunctions and your schema with runtime validators that double as types. - 🔗 Call with zero codegen. The client is inferred straight from your contract — an oRPC-style API that plugs into TanStack Query.
- ⚡ Realtime by default. Reactive queries re-run and push over SSE only when a write actually touches their read-set — precise, not table-wide.
- 📴 Local-first. A durable IndexedDB offline queue, optimistic overlay with rebase, and a persisted read cache mean the UI works with the server down.
- 🔒 Correct under contention. Mutations are atomic and
SERIALIZABLEwith automatic40001/40P01retry; writes are exactly-once via idempotency keys. - 🤝 Conflict-free collaboration.
v.collab()fields are Yjs CRDTs that merge concurrent and offline edits instead of clobbering. - 📊 Real SQL when you need it. Heavy analytical queries run on an isolated
pool;
ctx.sqlis the raw escape hatch for joins, CTEs, and window functions. - 📈 Scales out. Run many engine nodes on one Postgres; the change bus routes each write only to the nodes interested in the tables it touched — O(1) per write, not O(nodes). Tunable with great defaults.
A write travels node → Postgres → reactor → SSE. The Rust engine owns the
Postgres pools, lowers the document API to SQL, and captures each procedure's
read-set and write-set; a committed write's change-set is matched — via a
table index plus per-row predicates — against only the subscriptions it could
affect, so just those queries re-run. Across multiple engine nodes it scales out
over a Postgres LISTEN/NOTIFY bus that routes each change only to the nodes
interested in the tables it touched.
See docs/ARCHITECTURE.md for the full design and
docs/decisions/ for the architecture decision records.
Create a fully-configured app — schema, contract, handlers, a Vite client, and Docker Postgres — with one command:
npx @onveloz/pulse new my-app
cd my-app
pnpm install
pnpm db # start Postgres (docker compose up -d)
pnpm gen # generate the typed data model
pnpm engine # run the Rust engine
pnpm dev # start the appTo hack on Pulse itself or run the bundled example:
# 1. Install deps and start Postgres (logical replication enabled)
pnpm install
docker compose up -d
# 2. Build the engine
cargo build -p pulse-server
# 3. Generate the typed data model from your schema
pnpm pulse gen packages/examples-chat/src/schema.ts
# 4. Run the engine against the example app
pnpm pulse dev packages/examples-chat/src/app.tspulse new <name> scaffold a fully-configured app
pulse gen <schema.ts> [out.ts] generate the Doc/Id data model
pulse migrate <schema.ts> [--out f.sql] idempotent DDL from a schema
pulse migrate <schema.ts> --diff diff schema vs the live DB → migration
pulse dev <app.ts> [--port P] run the engine against an app
pulse deploy <app.ts> [--out dir] build a release bundle (schema + run)
crates/ Rust workspace — the engine
pulse-core domain types: Lsn, ChangeSet, ReadSet, ProcedureKind
pulse-sql OLTP/OLAP pools, query builder → SQL, read-set capture
pulse-collab Yjs CRDT merge (yrs) for v.collab() fields
pulse-cdc cross-node change bus (LISTEN/NOTIFY)
pulse-reactor subscription registry: read-set matching + invalidation
pulse-sse SSE transport
pulse-jsruntime TS query/mutation execution + SERIALIZABLE tx + retry
pulse-server the engine binary (axum app, wiring)
packages/ TypeScript workspace — the SDK
@pulse/schema defineSchema / defineTable + v validators
@pulse/contract the oc contract builder (dependency-free)
@pulse/server os builder, implement(), middleware, handler ctx
@pulse/client createClient inference, local-first, offline queue
@pulse/react TanStack Query React bindings
@pulse/runtime-node the handler/action worker entrypoint
@pulse/cli pulse gen | migrate | dev | deploy
@pulse/examples-chat end-to-end example app
pnpm typecheck && pnpm test # TypeScript: typecheck + unit
cargo test # Rust: unit + DB-backed suites
pnpm test:integration # full engine + Postgres (needs Docker)The integration suite proves the correctness story end-to-end: cross-node convergence, retry exhaustion into clean conflicts, money-transfer deadlock + conservation, local-first overlay rebase, and exactly-once mutations.
MIT