Architecture
Repository layoutβ
/
βββ back/ β PartyKit backend (Durable Object)
β βββ game.ts β thin dispatcher β routes messages to handlers
β βββ game-context.ts β GameContext interface
β βββ types.ts β exported types & interfaces
β βββ constants.ts β word lists (25Γ25), category prompts
β βββ utils.ts β pure utilities: generateIdentity(), shuffle()
β βββ scoring.ts β pure scoring functions
β βββ question.ts β buildQuestionPayload, broadcast helpers
β βββ persistence.ts β saveState, upsertKvSession, endGame
β βββ handlers/
β βββ session.ts β create_session, rejoin_host, join_session, rejoin_session
β βββ game-flow.ts β host control handlers + submit_answer
β βββ timer.ts β startTimer, handleTimerExpiry
βββ front/ β Astro + React frontend
β βββ src/
β βββ lib/
β β βββ socket.ts β PartySocket factory (createSocket(room))
β β βββ rejoin.ts β shared localStorage credential helpers
β βββ types/events.ts β shared event payload types
β βββ stores/
β β βββ hostStore.ts
β β βββ participantStore.ts
β βββ hooks/
β β βββ useHostSocket.ts
β β βββ useParticipantSocket.ts
β β βββ useUrlSync.ts β URL β store sync for host & participant
β βββ components/
β βββ host/
β βββ participant/
βββ docs-site/ β Docusaurus docs
βββ proxy-worker/
β βββ index.ts β Cloudflare Worker: routes /docs/* to docs Pages
βββ partykit.json β PartyKit config + KV binding
βββ package.json β root: partykit + partysocket
βββ docker-compose.yml β Mode B: full-stack with PartyKit in Docker
β οΈ After any backend change (files under
back/), runnpx partykit deploy(from repo root) orcd back && npm run deployto push to Cloudflare. Theproxy-workerdoes not need redeployment for backend-only changes.
Runtime communicationβ
Browser (Host) Browser (Participant)
β β
β PartySocket ws β PartySocket ws
βΌ βΌ
ββββββββββββββββββββββββββββββββββββββββββ
β PartyKit Durable Object β
β room = 4-digit session code β
β β
β this.room.storage β game state β
β this.room.broadcast β fan-out msgs β
ββββββββββββββββββββ¬ββββββββββββββββββββββ
β KV write
βΌ
Cloudflare KV
SOMMELIER_HOSTS
key: host:{TANNIC-FALCON}
Durable Object lifecycleβ
| Hook | Called when | What we do |
|---|---|---|
onStart() | DO wakes from eviction | Restore SavedState from storage |
onConnect(ws) | Client opens WebSocket | Register connection (host or participant) |
onMessage(ws, msg) | Message arrives | Parse { type }, dispatch to handler |
alarm() | Timer alarm fires | Auto-reveal current question |
onClose(ws) | Client disconnects | Mark participant offline; detect host drop |
Host vs participant socket flowβ
Host joins β sends rejoin_host { hostId } on socket open β server validates hostId against stored value β sends host:state_snapshot
Participant joins β sends join_session {} β server creates participant with a unique ADJECTIVE-NOUN pseudonym, broadcasts lobby:updated (the session code is the WebSocket room ID, not a message field)
Participant rejoins β localStorage { id, code } detected on mount β sends rejoin_session { pseudonym } β server sends participant:state_snapshot
Docker vs PartyKit devβ
| Mode A (partykit dev) | Mode B (docker) | |
|---|---|---|
| Backend | npx partykit dev on :1999 | partykit Docker container on :1999 |
| Frontend | npm run dev on :4321 | nginx on :4321 |
| Docs | cd docs-site && npm start | Docker container on :3002 |