• Blog
  • Docs
  • Pricing
  • We’re hiring!
Log inSign up
kamalnrf

kamalnrf

living-stories-self-registration

Registration for sessions at LSC Events
Public
Like
living-stories-self-registration
Home
Code
8
db
3
.vtignore
AGENTS.md
CLAUDE.md
api.ts
deno.json
H
index.ts
seed.ts
Environment variables
Branches
1
Pull requests
Remixes
History
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
CLAUDE.md
Code
/
CLAUDE.md
Search
2/12/2026
Viewing readonly version of main branch: v47
View latest version
CLAUDE.md

Living Library — Self-Registration System

A self-service registration app for Living Library events in Hyderabad. Readers register with name + email, browse available "books" (people sharing their stories), and claim reading slots — all in real-time as 250+ people sign up simultaneously.

Architecture

┌─────────────────────────────────────────────────────────┐
│  Client (React + Pico CSS)                              │
│  ┌──────────┐  ┌──────────────┐  ┌──────────────────┐  │
│  │ Register │→ │ Browse Books │→ │ Library Card View │  │
│  │ (name +  │  │ (polls every │  │ (reading history) │  │
│  │  email)  │  │  3 seconds)  │  │                   │  │
│  └──────────┘  └──────┬───────┘  └──────────────────┘  │
│                       │                                  │
│  localStorage: { id, name, email }                      │
└───────────────────────┼─────────────────────────────────┘
                        │ HTTP
┌───────────────────────┼─────────────────────────────────┐
│  Server (Hono)        │                                  │
│                       ▼                                  │
│  /api/events/active          GET  → active event         │
│  /api/events/:id/availability GET → sessions + slot counts│
│  /api/reader                 POST → find-or-create reader│
│  /api/register               POST → atomic slot booking  │
│  /api/register               DELETE → cancel booking     │
│  /api/readers/:id/card       GET  → library card data    │
│                                                          │
│  DB Layer: Drizzle ORM (libsql/web → Val Town SQLite, val-scoped) │
└──────────────────────────────────────────────────────────┘

Key Design Decisions

Polling over SSE/WebSocket

Val Town's serverless model keeps a Deno isolate alive per SSE connection. With 250 concurrent readers, that's 250 long-lived isolates — unsustainable. Instead, we poll every 3s. That's ~83 req/s of fast SQLite reads, which Val Town handles fine.

Atomic slot booking (race condition safety)

The critical path is two readers grabbing the last slot simultaneously. We solve this with a conditional INSERT:

INSERT INTO ll_registrations (reader_id, book_session_id, session_id) SELECT ?, ?, ? WHERE (SELECT COUNT(*) FROM ll_registrations WHERE book_session_id = ?) < (SELECT max_slots FROM ll_book_sessions WHERE id = ?)

If the slot was taken between the reader's last poll and their click, 0 rows are affected → friendly error.

Drizzle ORM with libsql/web

Val Town's SQLite is Turso/libSQL under the hood. We use the official pattern from Val Town docs: drizzle(sqlite as any) via drizzle-orm/libsql/web. The val-scoped import (std/sqlite/main.ts) isolates this project's DB from other vals on the same account. Raw sqlite.execute() is used for hot paths (availability polling) and atomic transactions (registration) where we need precise SQL control.

One book per session per reader

Enforced at the DB level with UNIQUE(reader_id, session_id) on ll_registrations. The API deletes any existing registration for a session before inserting a new one (in a transaction), so changing your pick is seamless.

Data Model

ll_events          → An event (e.g. "Living Library Hyderabad #6")
ll_sessions        → Time slots within an event (Session 1, 2, 3...)
ll_books           → "Books" (people) available at an event
ll_book_sessions   → Junction: which books are in which sessions (with max_slots)
ll_readers         → Registered readers (name + email, unique on email)
ll_registrations   → Reader ↔ BookSession assignments

File Structure

├── CLAUDE.md              ← You are here
├── AGENTS.md              ← Val Town agent instructions
├── db/
│   ├── schema.ts          ← Drizzle table definitions (source of truth)
│   ├── client.ts          ← Drizzle instance via sqlite-proxy
│   └── queries.ts         ← All query functions
├── api.ts                 ← Hono API routes
├── views.ts               ← React SPA (HTML shell + embedded React)
├── index.ts               ← HTTP entry point (Hono app)
└── seed.ts                ← Script: populate sample data

Development

Seeding data

Edit seed.ts with your event/book/session details, then run it as a script in Val Town. Re-running clears and re-inserts.

Schema changes

  1. Update db/schema.ts (Drizzle table definitions)
  2. Update db/queries.ts if query logic changed
  3. The schema is applied via CREATE TABLE IF NOT EXISTS on cold start — for breaking changes, either drop tables manually or bump table names

Adding a new API endpoint

  1. Add the query function in db/queries.ts
  2. Add the route in api.ts
  3. Update the React app in views.ts if it needs to call the new endpoint

Trust model

We trust readers to provide accurate name/email. No auth, no verification. The UNIQUE(email) constraint on readers means returning with the same email resumes their session.

FeaturesVersion controlCode intelligenceCLIMCP
Use cases
TeamsAI agentsSlackGTM
DocsShowcaseTemplatesNewestTrendingAPI examplesNPM packages
PricingNewsletterBlogAboutCareers
We’re hiring!
Brandhi@val.townStatus
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Open Source Pledge
Terms of usePrivacy policyAbuse contact
© 2026 Val Town, Inc.