JSPM

  • Created
  • Published
  • Downloads 1730
  • Score
    100M100P100Q113630F
  • License MIT

PGlite-powered zero-sync development backend. No Docker required.

Package Exports

  • orez
  • orez/s3

Readme

orez

Drop-in replacement for the Docker-based development backend that Rocicorp's Zero requires. Instead of running PostgreSQL and zero-cache in Docker containers, orez bundles everything into a single process using PGlite (PostgreSQL compiled to WASM).

The goal is simple: bun install && bun dev with zero system dependencies.

How it works

orez starts three things in one process:

  1. A PGlite instance (full PostgreSQL 16 running in-process via WASM)
  2. A TCP proxy that speaks the PostgreSQL wire protocol, including logical replication
  3. A zero-cache child process that connects to the proxy thinking it's a real Postgres server

The trick is in the TCP proxy. zero-cache needs logical replication to stay in sync with the upstream database. PGlite doesn't support logical replication natively, so orez fakes it. Every mutation is captured by triggers into a changes table, then encoded into the pgoutput binary protocol and streamed to zero-cache through the replication connection. zero-cache can't tell the difference.

The proxy also handles multi-database routing. zero-cache expects three separate databases (upstream, CVR, change), but PGlite is a single database. orez maps database names to schemas, so zero_cvr becomes the zero_cvr schema and zero_cdb becomes zero_cdb.

Install

npm install orez

or with bun:

bun add orez

You also need @rocicorp/zero installed in your project for the zero-cache binary.

Usage

import { startZeroLite } from 'orez'

const { config, stop } = await startZeroLite({
  pgPort: 6434,
  zeroPort: 5849,
  migrationsDir: 'src/database/migrations',
  seedFile: 'src/database/seed.sql',
})

// your app connects to zero-cache at localhost:5849
// database is at postgresql://user:password@localhost:6434/postgres

// when done
await stop()

All options are optional and have sensible defaults. See src/config.ts for the full list.

CLI

Run directly with npx/bunx:

npx orez

This starts PGlite, the TCP proxy, and zero-cache. Install @rocicorp/zero alongside orez to get the zero-cache binary (it's an optional peer dep, only needed if you're running zero-cache).

Options:

--pg-port        postgresql proxy port (default: 6434)
--zero-port      zero-cache port (default: 5849)
--web-port       web server port for zero-cache mutate/query urls (default: 8081)
--data-dir       data directory (default: .zero-lite)
--migrations     migrations directory (default: src/database/migrations)
--seed           seed file path
--pg-user        postgresql user (default: user)
--pg-password    postgresql password (default: password)
--skip-zero-cache  run pglite + proxy only, skip zero-cache

S3 subcommand for a local S3-compatible server:

npx orez s3
npx orez s3 --port 9200 --data-dir .orez

What gets faked

The proxy intercepts several things to convince zero-cache it's talking to a real PostgreSQL server with logical replication enabled:

  • IDENTIFY_SYSTEM returns a fake system ID and timeline
  • CREATE_REPLICATION_SLOT persists slot info in a local table and returns a valid LSN
  • START_REPLICATION enters streaming mode, encoding changes as pgoutput binary messages
  • current_setting('wal_level') always returns logical
  • pg_replication_slots queries are redirected to a local tracking table
  • SET TRANSACTION SNAPSHOT is silently accepted (PGlite doesn't support imported snapshots)
  • ALTER ROLE ... REPLICATION returns success
  • READ ONLY is stripped from transaction starts to avoid PGlite serialization issues

The pgoutput encoder produces spec-compliant binary messages: Begin, Relation, Insert, Update, Delete, Commit, and Keepalive. All column values are encoded as text (typeOid 25), which zero-cache handles fine since it re-maps types downstream anyway.

Tests

80 unit tests across 5 test files covering the full stack from binary encoding to TCP-level integration:

bun test

The test suite includes a zero-cache compatibility layer that decodes pgoutput messages into the same typed format that zero-cache's PgoutputParser produces, validating end-to-end compatibility.

Limitations

This is a development tool. It is not suitable for production use.

  • PGlite runs single-threaded. All queries are serialized through a mutex. This is fine for development but would be a bottleneck under real load.
  • Column types are all encoded as text in the replication stream. Zero-cache handles this, but other pgoutput consumers might not.
  • Triggers add overhead to every write. Again, fine for development.
  • PGlite stores data on the local filesystem. No replication, no backups, no high availability.

Project structure

src/
  index.ts              main entry, orchestrates startup
  config.ts             configuration with defaults
  pg-proxy.ts           tcp proxy with query rewriting
  pglite-manager.ts     pglite instance and migration runner
  s3-local.ts           standalone local s3 server (orez/s3)
  replication/
    handler.ts          replication protocol state machine
    pgoutput-encoder.ts binary pgoutput message encoder
    change-tracker.ts   trigger installation and change reader

Extra

The other annoying dep we found ourselves needing often was s3, so we're exporting orez/s3. Its likewise a tiny, dev-only helper for avoiding heavy docker deps like minio.

import { startS3Local } from 'orez/s3'

const server = await startS3Local({
  port: 9200,
  dataDir: '.orez',
})

Handles GET, PUT, DELETE, HEAD with CORS. Files stored on disk. No multipart, no ACLs, no versioning.

License

MIT