Package Exports
- orez
- orez/browser
- orez/s3
- orez/vite
- orez/worker/browser-build-config
- orez/worker/browser-embed
- orez/worker/cf-patches
- orez/worker/index
- orez/worker/shims/fastify
- orez/worker/shims/http-service
- orez/worker/shims/node-stub
- orez/worker/shims/postgres
- orez/worker/shims/postgres-browser
- orez/worker/shims/postgres-socket
- orez/worker/shims/sqlite
- orez/worker/shims/sqlite-browser
- orez/worker/shims/stream-browser
- orez/worker/shims/ws
- orez/worker/shims/ws-browser
- orez/worker/types
- orez/worker/zero-cache-embed
- orez/worker/zero-cache-embed-cf
Readme
oreZ
Run Zero locally with zero native dependencies. No Postgres install, no SQLite compilation, no Docker.
bunx orezoreZ makes Zero work on PGlite (Postgres in WASM) and bedrock-sqlite (SQLite in WASM), bundled together so local development is as simple as bun install && bunx orez.
Requirements
- Bun 1.0+ or Node.js 20+
- Zero 0.18+ (tested with 0.18.x)
Limitations
This is a development tool only. Not suitable for production.
- Single-session per database — queries are serialized through a mutex. Fine for development, would bottleneck under load.
- Trigger overhead — every write fires change-tracking triggers.
- Local filesystem — no replication, no HA. Use
orez pg_dumpfor backups.
Features
bunx orezWhat oreZ handles automatically:
- Zero native deps — both Postgres and SQLite run as WASM. Nothing to compile, nothing platform-specific.
- Memory management — auto-sizes Node heap (~50% RAM, min 4GB), purges consumed WAL, batches restores with CHECKPOINTs
- Real-time replication — changes sync instantly via
pg_notifytriggers, with adaptive polling fallback (20ms catching up, 500ms idle) - Auto-recovery — finds available ports if configured ones are busy, provides reset/restart controls
- PGlite compatibility — rewrites unsupported queries, fakes wire protocol responses, filters unsupported column types
- Admin dashboard — live logs, HTTP request inspector, restart/reset controls, env viewer
- Production restores —
pg_dump/pg_restorewith COPY→INSERT conversion, auto-coordinates with zero-cache - Extensions — pgvector, pg_trgm, pgcrypto, uuid-ossp, citext, hstore, ltree, fuzzystrmatch, btree_gin, btree_gist, cube, earthdistance enabled by default (lazy-loaded, no overhead unless used)
CLI
bunx orez--pg-port=6434 postgresql proxy port
--zero-port=5849 zero-cache port
--data-dir=.orez data directory
--migrations=DIR migrations directory (skipped if not set)
--seed=FILE seed file path
--pg-user=user postgresql user
--pg-password=password postgresql password
--skip-zero-cache run pglite + proxy only, skip zero-cache
--log-level=warn error, warn, info, debug
--s3 also start a local s3-compatible server
--s3-port=9200 s3 server port
--force-wasm-sqlite force wasm sqlite even if native is available
--disable-wasm-sqlite force native sqlite (fail if not available)
--on-db-ready=CMD command to run after db+proxy ready, before zero-cache
--on-healthy=CMD command to run once all services healthy
--disable-admin disable admin dashboard
--admin-port=6477 admin dashboard port (default: 6477)Ports auto-increment if already in use.
Config File
Create an orez.config.ts (or .js / .mjs) in your project root:
import { defineConfig } from 'orez'
export default defineConfig({
pgPort: 5433,
zeroPort: 4848,
adminPort: 6477,
migrations: './db/migrations',
seed: './db/seed.sql',
s3: true,
s3Port: 9300,
logLevel: 'info',
onHealthy: 'echo "ready!"',
})All options mirror the CLI flags in camelCase. CLI flags override config file values, so orez --pg-port 6000 wins over pgPort: 5433 in the config.
| Config key | CLI flag | Default |
|---|---|---|
pgPort |
--pg-port |
6434 |
zeroPort |
--zero-port |
5849 |
adminPort |
--admin-port |
6477 |
s3Port |
--s3-port |
9200 |
dataDir |
--data-dir |
.orez |
pgUser |
--pg-user |
user |
pgPassword |
--pg-password |
password |
migrations |
--migrations |
— |
seed |
--seed |
— |
skipZeroCache |
--skip-zero-cache |
false |
s3 |
--s3 |
false |
disableAdmin |
--disable-admin |
false |
disableWasmSqlite |
--disable-wasm-sqlite |
false |
forceWasmSqlite |
--force-wasm-sqlite |
false |
noWorkerThreads |
--no-worker-threads |
false |
singleDb |
--single-db |
false |
logLevel |
--log-level |
warn |
onDbReady |
--on-db-ready |
— |
onHealthy |
--on-healthy |
— |
.ts config files require Node 22.6+ or Bun. Use .js/.mjs on older Node versions.
Admin Dashboard
Enabled by default at http://localhost:6477.
- Logs — live-streaming logs from zero-cache, filterable by source and level
- HTTP — request/response inspector for zero-cache traffic
- Env — environment variables passed to zero-cache
- Actions — restart zero-cache, reset (wipe replica + resync), full reset (wipe CVR/CDB too)
Logs are also written to separate files in your data directory: zero.log, proxy.log, pglite.log, etc.
bunx orez --disable-admin # disable dashboardProgrammatic API
bun install orezimport { startZeroLite } from 'orez'
const { config, stop, db, instances } = await startZeroLite({
pgPort: 6434,
zeroPort: 5849,
migrationsDir: 'src/database/migrations',
seedFile: 'src/database/seed.sql',
adminPort: 6477, // set to 0 to disable
})
// your app connects to zero-cache at localhost:5849
// database is at postgresql://user:password@localhost:6434/postgres
// db is the postgres PGlite instance (for direct queries)
// instances has all three: { postgres, cvr, cdb }
await stop()All options are optional with sensible defaults.
Lifecycle hooks
| Hook | CLI | Programmatic | When |
|---|---|---|---|
| on-db-ready | --on-db-ready=CMD |
onDbReady: 'CMD' or onDbReady: fn |
after db + proxy ready, before zero |
| on-healthy | --on-healthy=CMD |
onHealthy: 'CMD' or onHealthy: fn |
after all services ready |
Shell commands receive env vars: DATABASE_URL, OREZ_PG_PORT, OREZ_ZERO_PORT. Change tracking triggers are re-installed after onDbReady.
Vite Plugin
import { orezPlugin } from 'orez/vite'
export default {
plugins: [
orezPlugin({
pgPort: 6434,
zeroPort: 5849,
migrationsDir: 'src/database/migrations',
onDbReady: () => console.log('db ready'),
onHealthy: () => console.log('all services healthy'),
}),
],
}Starts oreZ when vite dev starts, stops on close. Supports all startZeroLite options plus s3 and s3Port.
Backup & Restore
Dump and restore your local database — no native Postgres install needed.
bunx orez pg_dump > backup.sql
bunx orez pg_dump --output backup.sql
bunx orez pg_restore backup.sql
bunx orez pg_restore backup.sql --clean # drop public schema firstRestoring into a running instance
When oreZ is running, restore through the wire protocol:
bunx orez pg_restore backup.sql --pg-port 6434This automatically:
- Stops zero-cache before restore (via admin API)
- Clears replication state and shard schemas
- Restores the dump
- Adds all public tables to the publication
- Restarts zero-cache
The --direct flag forces direct PGlite access, skipping wire protocol.
What restore handles
- COPY → INSERT — PGlite doesn't support COPY protocol; converted to batched multi-row INSERTs
- Unsupported extensions —
pg_stat_statements,pg_buffercache,pg_cronetc. silently skipped - Idempotent DDL —
CREATE SCHEMA→IF NOT EXISTS,CREATE FUNCTION→OR REPLACE - Oversized rows — rows >16MB skipped with warning (WASM limit)
- Transaction batching — 200 statements per transaction, CHECKPOINT every 3 batches
- Dollar-quoting — correctly parses
$$and$tag$in function bodies
Standard Postgres tools (pg_dump, pg_restore, psql) also work against the running proxy.
Environment Variables
All ZERO_* env vars are forwarded to zero-cache. oreZ provides defaults:
| Variable | Default | Overridable |
|---|---|---|
NODE_ENV |
development |
yes |
ZERO_LOG_LEVEL |
from --log-level |
yes |
ZERO_NUM_SYNC_WORKERS |
1 |
yes |
ZERO_ENABLE_QUERY_PLANNER |
false |
yes |
ZERO_UPSTREAM_DB |
(managed) | no |
ZERO_CVR_DB |
(managed) | no |
ZERO_CHANGE_DB |
(managed) | no |
ZERO_REPLICA_FILE |
(managed) | no |
ZERO_PORT |
(managed) | no |
Common vars you might set:
ZERO_MUTATE_URL=http://localhost:3000/api/zero/push
ZERO_QUERY_URL=http://localhost:3000/api/zero/pullLocal S3
Since Zero apps often need file uploads and MinIO requires Docker:
bunx orez --s3 # with orez
bunx orez s3 # standaloneimport { 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.
How It Works
Architecture
oreZ runs three components:
- Three PGlite instances — PostgreSQL 17 in WASM, one per database zero-cache expects (postgres, zero_cvr, zero_cdb)
- TCP proxy — speaks PostgreSQL wire protocol, routes to correct PGlite, handles logical replication
- zero-cache — child process connecting to proxy, thinks it's real Postgres
Why three instances?
zero-cache expects three databases with independent transaction contexts. PGlite is single-session — all connections share one session. Without isolation, CVR transactions get corrupted by postgres queries (ConcurrentModificationException).
| Connection database | PGlite instance | Data directory |
|---|---|---|
postgres |
postgres | pgdata-postgres |
zero_cvr |
cvr | pgdata-cvr |
zero_cdb |
cdb | pgdata-cdb |
Replication
PGlite doesn't support logical replication, so oreZ fakes it:
- Triggers capture every mutation into
_orez._zero_changes - Changes are encoded as pgoutput binary protocol
- Streamed to zero-cache through the replication connection
Change notifications use pg_notify for real-time sync. Polling (20ms/500ms adaptive) is fallback only.
SQLite WASM
zero-cache needs SQLite via @rocicorp/zero-sqlite3 (native C addon). oreZ intercepts this at runtime using Node's ESM loader hooks, redirecting to bedrock-sqlite — SQLite's bedrock branch compiled to WASM with BEGIN CONCURRENT and WAL2.
The shim also polyfills the better-sqlite3 API surface zero-cache expects.
Native SQLite mode
For --disable-wasm-sqlite, bootstrap the native addon first:
bun run native:bootstrapInternal Schema
oreZ stores replication state in the _orez schema (survives pg_restore --clean):
_orez._zero_changes— change log for replication_orez._zero_replication_slots— slot tracking_orez._zero_watermark— LSN sequence
Wire Protocol Compatibility
The proxy intercepts and rewrites to make PGlite look like real Postgres:
| Query/Command | What oreZ does |
|---|---|
version() |
Returns PostgreSQL 17.4 on x86_64-pc-linux-gnu... |
current_setting('wal_level') |
Returns logical |
IDENTIFY_SYSTEM |
Returns fake system ID and timeline |
CREATE_REPLICATION_SLOT |
Persists to local table, returns valid LSN |
START_REPLICATION |
Streams changes as pgoutput binary |
pg_replication_slots |
Redirects to local tracking table |
READ ONLY / ISOLATION LEVEL |
Stripped (single-session) |
Workarounds
Things that don't "just work" when replacing Postgres with PGlite and native SQLite with WASM:
Session state bleed
PGlite is single-session — if pg_restore sets search_path = '', every subsequent connection inherits it. On disconnect, oreZ resets search_path, statement_timeout, lock_timeout, and rolls back open transactions.
Query planner disabled
ZERO_ENABLE_QUERY_PLANNER=false because it relies on SQLite scan statistics that cause infinite loops in WASM.
Unsupported column types
Columns with tsvector, tsquery, USER-DEFINED types are filtered from replication messages.
Publication-aware tracking
If ZERO_APP_PUBLICATIONS is set, only tables in that publication get change-tracking triggers.
Broken trigger cleanup
After restore, triggers whose backing functions don't exist are dropped (happens with filtered pg_dump).
Tests
bun run test # orez tests
bun run test:integration:native # native sqlite integration
cd sqlite-wasm && bunx vitest run # bedrock-sqlite testsProject Structure
src/
cli-entry.ts auto heap sizing wrapper
cli.ts cli with citty
index.ts main entry, orchestrates startup
config.ts configuration types and defaults
load-config.ts orez.config.ts file loader
log.ts colored log prefixes, log files
mutex.ts serializing pglite access
port.ts auto port finding
pg-proxy.ts postgresql wire protocol proxy
pglite-manager.ts multi-instance pglite, migrations
s3-local.ts local s3 server (orez/s3)
vite-plugin.ts vite plugin (orez/vite)
admin/
server.ts admin dashboard backend
ui.ts admin dashboard frontend
log-store.ts log aggregation
http-proxy.ts http request logging
replication/
handler.ts replication state machine, adaptive polling
pgoutput-encoder.ts binary pgoutput encoder
change-tracker.ts trigger installation, change purging
integration/
*.test.ts end-to-end tests
sqlite-wasm/
Makefile emscripten build
native/api.js better-sqlite3 compatible API
native/vfs.c custom VFS with SHM for WAL2License
MIT