Skip to content

PayPal sandbox only. No real money moves.

BuildersBuildersPackages

server

The single process that hosts the rooms, the HTTP API, the WebSocket gateway and settlement.

Verifiedbacked by a real run or committed evidence

Page last changed 7 Nov 2026 (from the git history)

@gavel/server (packages/server) is where everything meets. One Node process owns the rooms, the HTTP routes, the /ws room socket, the /audio voice socket, the stores and the settlement runner. A Render blueprint for it is in render.yaml.

Responsibilities

  • Compose. boot/compose.ts builds the stores, the engine runtime, the payments gateway (fake or sandbox), the room and the HTTP server, and mounts the sockets. main.ts starts it; config.ts validates the environment before anything starts.
  • Rooms. room/room.ts runs one live auction: join, bid, confirm, hammer, soft close, resync. It talks to the engine only through the ports in @gavel/shared and room/settle-port.ts.
  • The confirm gate. A voice-like bid is held in room/confirm.ts until its bidder answers; it waits CONFIRM_TTL_MS (15 seconds) and the engine does not see it meanwhile. isVoiceLike catches a spoken bid labelled as another source.
  • HTTP. Routes under http/: auctions, control and kill, invites, holds, webhook, read (snapshot and events), demo, ops, health and metrics.
  • Auth. auth/token.ts defines four token kinds (invite, session, auctioneer, screen) with lifetimes of 24 h, 4 h, 12 h and 12 h; the payload schema is strict and an alg field is rejected. ADMIN_KEY is the operator key, not a token role. See threat model.
  • Settlement host. runtime/auction-service.ts and the files beside it call the payments package: hold service, settlement bridge, webhook intake, reconcile and void phase.
  • Stores. store/ has memory, file (file-ledger-store.ts, file-room-store.ts, with a data directory lock) and Postgres (postgres.ts with forward-only migrations from migrations/).
  • Limits. limits/rate-limiter.ts keeps token buckets per client key: bids, frames, joins, HTTP.
  • Voice host. host/audio-ws.ts and voice/ connect the /audio socket to the voice pipeline and its speech-to-text providers.
  • Operations. ops/ (shutdown, fatal errors, metrics), obs/ (log ring, Prometheus text) and warmup/.

Public interface

src/index.ts is small: it re-exports ErrorCode, the settle port (room/settle-port.ts) and runtime/index.ts. The real entry points are processes and routes, not library calls.

SurfaceWhere
Process startsrc/main.ts (npm run start, or start:prod on the built dist/main.js)
Room socket/ws (host/node-ws.ts, WS_PATH)
Voice socket/audio (host/audio-ws.ts, AUDIO_PATH)
Health and metrics/healthz, /metrics, /metrics/prometheus (boot/compose.ts)
API/api/auctions, /api/holds, /api/demo/* and the routes in src/http/

See WebSocket protocol for the messages.

Configuration

Names only, read from src/config.ts; the server stops at boot with one JSON line per problem. Production-like environments need TOKEN_SECRET of at least 32 bytes and ADMIN_KEY of at least 24 characters, and the two must differ.

Prop

Type

The full table is in configuration. The names TOKEN_SECRET, ADMIN_KEY and PAYPAL_CLIENT_SECRET are secrets: never commit a value.

Key files

config.ts
main.ts
migrations/0001_init.sql
FileNote
boot/compose.tsthe single wiring point; chooses the store and the payments mode
room/fence.tsFencedStore: a stale expected sequence fences this host, and later appends fail with host_fenced without touching the store
room/resync.tsa reconnecting client gets a replay of at most 200 missed events; one token holds at most 3 connections; the oldest is closed with code 4011 superseded
room/snapshot-policy.tsa snapshot on a state change, or after 100 events
runtime/webhook-intake.tsstores a verified event, then applies it at most once per event id; an event for an unknown hold is parked and retried by a sweep
http/webhook-guard.tswebhook body limit of 256 KiB; only an event id and an outcome are logged
store/data-dir-lock.tsone process owns the data directory
ops/shutdown.tsgoing-away close code 1001 and a 30 second default deadline (SHUTDOWN_DEADLINE_MS overrides)

Invariants and the tests that guard them

Where it sits

server may import shared, engine, payments and voice (ALLOWED.server in .dependency-cruiser.cjs) and is imported by nothing; the browser reaches it over HTTP and WebSocket. It declares pg and ws as its own runtime dependencies. npm run check:trace -w @gavel/server runs in CI beside the engine's check.

Edit on GitHub

Last updated on