Package Exports
- orez
- orez/s3
- orez/vite
Readme
orez
Zero development backend powered by PGlite. Bundles PostgreSQL and zero-cache into a single process — no Docker, no Postgres install.
npx orezStarts PGlite, the TCP proxy, and zero-cache. Ports auto-increment if already in use.
Exports a CLI, programmatic API, and Vite plugin.
Install
npm install orez@rocicorp/zero is included as a dependency and provides the zero-cache binary.
CLI
npx orez--pg-port postgresql proxy port (default: 6434)
--zero-port zero-cache port (default: 5849)
--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
--log-level error, warn, info, debug (default: info)
--s3 also start a local s3-compatible server
--s3-port s3 server port (default: 9200)S3 can also run standalone:
npx orez s3
npx orez s3 --port 9200 --data-dir .orezProgrammatic
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 with sensible defaults. Ports auto-find if in use.
Vite plugin
import orez from 'orez/vite'
export default {
plugins: [
orez({
pgPort: 6434,
zeroPort: 5849,
migrationsDir: 'src/database/migrations',
s3: true,
}),
],
}Starts orez when vite dev server starts, stops on close. Pass s3: true to also start a local s3 server.
How it works
orez starts three things in one process:
- A PGlite instance (full PostgreSQL 16 running in-process via WASM)
- A TCP proxy that speaks the PostgreSQL wire protocol, including logical replication
- 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.
Environment variables
Your entire environment is forwarded to the zero-cache child process. This means any ZERO_* env vars you set are passed through automatically.
orez provides sensible defaults for a few variables:
| Variable | Default | Overridable |
|---|---|---|
NODE_ENV |
development |
yes |
ZERO_LOG_LEVEL |
from --log-level |
yes |
ZERO_NUM_SYNC_WORKERS |
1 |
yes |
ZERO_UPSTREAM_DB |
(managed by orez) | no |
ZERO_CVR_DB |
(managed by orez) | no |
ZERO_CHANGE_DB |
(managed by orez) | no |
ZERO_REPLICA_FILE |
(managed by orez) | no |
ZERO_PORT |
(managed by orez) | no |
The --log-level flag controls both zero-cache (ZERO_LOG_LEVEL) and PGlite's debug output. Setting it to debug enables verbose logging from both.
The layering is: orez defaults → your env → orez-managed connection vars. So setting ZERO_LOG_LEVEL=debug in your shell overrides the --log-level default, but you can't override the database connection strings (orez needs to point zero-cache at its own proxy).
Common vars you might want to set:
ZERO_MUTATE_URL=http://localhost:3000/api/zero/push
ZERO_QUERY_URL=http://localhost:3000/api/zero/pull
ZERO_LOG_LEVEL=debugWhat gets faked
The proxy intercepts several things to convince zero-cache it's talking to a real PostgreSQL server with logical replication enabled:
IDENTIFY_SYSTEMreturns a fake system ID and timelineCREATE_REPLICATION_SLOTpersists slot info in a local table and returns a valid LSNSTART_REPLICATIONenters streaming mode, encoding changes as pgoutput binary messagescurrent_setting('wal_level')always returnslogicalpg_replication_slotsqueries are redirected to a local tracking tableSET TRANSACTION SNAPSHOTis silently accepted (PGlite doesn't support imported snapshots)ALTER ROLE ... REPLICATIONreturns successREAD ONLYis 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.
Extra: orez/s3
Local s3-compatible server for dev. Avoids needing Docker or MinIO.
import { startS3Local } from 'orez/s3'
const server = await startS3Local({
port: 9200,
dataDir: '.orez',
})Or via CLI: npx orez --s3 or standalone npx orez s3.
Handles GET, PUT, DELETE, HEAD with CORS. Files stored on disk. No multipart, no ACLs, no versioning.
Tests
82 tests across 6 test files covering the full stack from binary encoding to TCP-level integration:
bun testThe 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
cli.ts cli with citty
config.ts configuration with defaults
log.ts colored log prefixes
port.ts auto port finding
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)
vite-plugin.ts vite dev server plugin (orez/vite)
replication/
handler.ts replication protocol state machine
pgoutput-encoder.ts binary pgoutput message encoder
change-tracker.ts trigger installation and change readerLicense
MIT