- Reference
Architecture
Guide for contributors working on HappyView itself. For a user-facing overview, see the Introduction.
System overview
Section titled “System overview”graph LR
Application
Application -->|"GET /xrpc/{method}"| Query
Application -->|"POST /xrpc/{method}"| Procedure
subgraph HappyView
Query["Query Handler<br/><small>Lua Script (Optional)</small>"]
Procedure["Procedure Handler<br/><small>Lua Script (Optional)</small>"]
Spaces["Spaces<br/><small>Permissioned Data</small>"]
end
Procedure --> DB
Query --> DB
Spaces --> DB
Procedure -->|proxy write| PDS["User PDS"]
DB[("SQLite / PostgreSQL<br/><small>records · lexicons · spaces</small>")]
Jetstream["Jetstream<br/><small>WebSocket</small>"] -->|record events| DB
Relay["Relay<br/><small>listReposByCollection</small>"] -->|repo discovery| Backfill
Backfill["Backfill Worker"] -->|listRecords| PDS
Backfill --> DB
Labeler["Labeler<br/><small>WebSocket (out-of-band)</small>"] -->|label events| DB
Queries go through the query handler to the database (SQLite by default, or Postgres). Writes go through the procedure handler to the user’s PDS, then HappyView indexes the record locally. Real-time record events stream in via Jetstream; historical records are backfilled in-process by discovering repos via the relay’s listReposByCollection and fetching records directly from each PDS. Labelers are external services that emit content labels over a direct WebSocket connection — they operate out-of-band, outside the relay/repo system. Spaces provide permissioned data containers with membership-gated access, per-user repo state tracking (LtHash + signed commits), and cross-service credential-based authentication.
Request flow
Section titled “Request flow”Reads (queries)
Section titled “Reads (queries)”sequenceDiagram
participant C as Client
participant X as xrpc_get()
participant R as LexiconRegistry
participant L as Lua Script
participant D as Database
C->>X: GET /xrpc/{method}?params
X->>R: Lookup (must be Query type)
alt Lua script attached
R->>L: Execute script
L->>D: db.query / db.get / db.raw
D-->>L: Results
L-->>X: Response table
else No script
R->>D: Default SQL query (collection from target_collection)
D-->>X: Results
end
X-->>C: JSON response
Writes (procedures)
Section titled “Writes (procedures)”sequenceDiagram
participant C as Client
participant A as Claims Extractor
participant X as xrpc_post()
participant R as LexiconRegistry
participant L as Lua Script
participant S as OAuth Session
participant P as User PDS
participant D as Database
C->>A: POST /xrpc/{method} + DPoP auth + X-Client-Key
A->>X: Validated claims
X->>R: Lookup (must be Procedure type)
alt Lua script attached
R->>L: Execute script (Record API)
L->>S: Record:save()
else No script
R->>S: Default create/update (auto-detect from uri field)
end
S->>P: Proxy write (createRecord or putRecord)
P-->>S: PDS response
S->>D: Upsert record locally
S-->>C: Forward PDS response
Admin endpoints
Section titled “Admin endpoints”sequenceDiagram
participant C as Client
participant A as AdminAuth Extractor
participant U as Users Table
participant H as Admin Handler
participant D as Database
C->>A: Request + Bearer token
A->>A: Validate claims (API key or service auth JWT)
A->>U: DID lookup
alt Users table empty
U-->>A: Auto-bootstrap as super user
else User found
U-->>A: Load permissions
end
A->>A: Permission check (403 if missing)
A->>H: Authorized request
H->>D: Database operation
D-->>H: Result
H-->>C: JSON response
Data flow
Section titled “Data flow”Real-time indexing
Section titled “Real-time indexing”sequenceDiagram
participant J as Jetstream WebSocket
participant H as HappyView
participant D as Database
participant R as LexiconRegistry
H->>J: Connect (collection filters from indexed lexicons)
loop Stream events
J->>H: Record commit event
alt create / update
H->>D: UPSERT into records table
else delete
H->>D: DELETE from records table
end
end
J->>H: Lexicon schema event (com.atproto.lexicon.schema)
H->>D: Update tracked network lexicons
H->>R: Update in-memory registry
Note over H,D: Cursor persisted to instance_settings for resume on reconnect
Note over H,J: Reconnects on collection filter changes (lexicon add/remove)
Backfill
Section titled “Backfill”sequenceDiagram
participant A as Admin
participant H as HappyView
participant D as Database
participant Relay as Relay
participant PLC as PLC Directory
participant PDS as User PDS
A->>H: POST /admin/backfill
H->>D: Create backfill_jobs record (status = running)
rect rgb(240, 248, 255)
note over H,Relay: Phase 1: Discovery
H->>Relay: listReposByCollection (paginated)
Relay-->>H: List of DIDs
H->>D: Insert backfill_repos rows
end
rect rgb(240, 255, 240)
note over H,PDS: Phase 2: Pipelined resolve + fetch (concurrent)
par Resolver task
loop Unresolved DIDs
H->>PLC: Resolve DID document
PLC-->>H: PDS endpoint
H->>D: Update backfill_repos.pds_endpoint
end
and Fetcher task
loop Resolved DIDs (as they arrive)
H->>PDS: listRecords (paginated)
PDS-->>H: Records
H->>D: UPSERT each record
end
end
end
H->>D: Mark job completed (or failed)
Database schema
Section titled “Database schema”records
Section titled “records”| Column | Type | Description |
|---|---|---|
uri | text (PK) | AT URI (at://did/collection/rkey) |
did | text | Author DID |
collection | text | Lexicon NSID |
rkey | text | Record key |
record | jsonb | Record value |
cid | text | Content identifier |
indexed_ | timestamptz | When HappyView indexed this record |
lexicons
Section titled “lexicons”| Column | Type | Description |
|---|---|---|
id | text (PK) | Lexicon NSID |
revision | integer | Incremented on upsert |
lexicon_ | jsonb | Raw lexicon definition |
lexicon_ | text | record, query, procedure, definitions |
backfill | boolean | Whether to backfill on upload |
target_ | text | For queries/procedures: which record collection |
created_ | timestamptz | |
updated_ | timestamptz |
| Column | Type | Description |
|---|---|---|
id | uuid (PK) | |
did | text (unique) | User’s atproto DID |
is_ | boolean | Whether this is the super user (only one allowed) |
created_ | timestamptz | |
last_ | timestamptz | Updated on each authenticated request |
user_permissions
Section titled “user_permissions”| Column | Type | Description |
|---|---|---|
user_ | uuid (FK) | References users.id |
permission | text | Permission string (e.g. lexicons:create) |
| (PK) | Composite primary key: (user_id, permission) |
api_keys
Section titled “api_keys”| Column | Type | Description |
|---|---|---|
id | uuid (PK) | |
user_ | uuid (FK) | References users.id |
name | text | Descriptive label |
key_ | text | SHA-256 hash of the full key |
key_ | text | First 11 characters for display |
permissions | text[] | Permissions granted to this key |
created_ | timestamptz | |
last_ | timestamptz | |
revoked_ | timestamptz | Set when revoked (soft delete) |
oauth_sessions
Section titled “oauth_sessions”| Column | Type | Description |
|---|---|---|
did | text (PK) | User’s atproto DID |
session_ | text | Serialized OAuth session (managed by atrium) |
created_ | timestamptz | |
updated_ | timestamptz |
oauth_state
Section titled “oauth_state”| Column | Type | Description |
|---|---|---|
state_ | text (PK) | OAuth state parameter |
state_ | text | Serialized state (managed by atrium) |
created_ | timestamptz |
instance_settings
Section titled “instance_settings”| Column | Type | Description |
|---|---|---|
key | text (PK) | Setting name (e.g. app_name) |
value | text | Setting value |
updated_ | timestamptz | Last modified |
event_logs
Section titled “event_logs”| Column | Type | Description |
|---|---|---|
id | uuid (PK) | |
event_ | text | Category.action format (e.g. user.created) |
severity | text | info, warn, or error |
actor_ | text | DID of the user who triggered the event |
subject | text | What was affected (DID, NSID, URI, etc.) |
detail | jsonb | Event-specific data |
created_ | timestamptz |
script_variables
Section titled “script_variables”| Column | Type | Description |
|---|---|---|
key | text (PK) | Variable name |
value | text | Variable value (encrypted at rest) |
created_ | timestamptz | |
updated_ | timestamptz |
spaces
Section titled “spaces”| Column | Type | Description |
|---|---|---|
id | text (PK) | Internal space identifier |
did | text | DID in the space URI; the authority’s DID |
authority_ | text | The space authority: the instance DID, or the creator’s DID when the instance has no service identity |
creator_ | text | DID of the user who created and administers the space |
type_ | text | Space type as an NSID |
skey | text | Space key (differentiates spaces of the same type) |
display_ | text | Human-readable name (optional) |
description | text | Description (optional) |
read_ | text (JSON) | Policy gating credential issuance ($type-tagged) |
write_ | text (JSON) | Policy gating which writers the space tracks ($type-tagged) |
app_ | text (JSON) | {"$type":"com.atproto.simplespace.defs#open"} or {"$type":"com.atproto.simplespace.defs#allowList","allowed":[...]} |
config | text (JSON) | Space config (membership_public, records_public, extras) |
revision | text | Current revision TID |
created_ | text | |
updated_ | text |
space_members
Section titled “space_members”| Column | Type | Description |
|---|---|---|
id | text (PK) | |
space_ | text (FK) | References spaces.id |
did | text | Member’s DID (or space URI for delegation) |
can_ | boolean | Whether the member can read the space |
can_ | boolean | Whether the member can write to the space |
read_ | boolean | Whether reads are limited to the member’s own records |
is_ | boolean | Whether this member is a delegated space |
granted_ | text | DID of who granted membership |
created_ | text |
space_records
Section titled “space_records”| Column | Type | Description |
|---|---|---|
uri | text (PK) | at:// URI of the record |
space_ | text (FK) | References spaces.id |
author_ | text | DID of the record author |
collection | text | Lexicon NSID |
rkey | text | Record key |
record | jsonb | Record value |
cid | text | Content identifier |
indexed_ | text |
space_repo_state
Section titled “space_repo_state”| Column | Type | Description |
|---|---|---|
id | text (PK) | |
space_ | text (FK) | References spaces.id |
author_ | text | DID of the repo author |
lthash_ | bytea | 2048-byte LtHash state |
rev | text | Current revision |
hash | bytea | Content hash |
ikm | bytea | Input keying material for deniable signatures |
sig | bytea | Signature |
mac | bytea | Message authentication code |
updated_ | text |
space_record_oplog
Section titled “space_record_oplog”| Column | Type | Description |
|---|---|---|
id | text (PK) | |
space_ | text (FK) | References spaces.id |
author_ | text | DID of the operation author |
rev | text | Revision this operation belongs to |
idx | integer | Index within the revision |
action | text | create, update, or delete |
collection | text | Lexicon NSID |
rkey | text | Record key |
cid | text | Content identifier (for create/update) |
prev | text | Previous CID (for update/delete) |
created_ | text |
space_notify_registrations
Section titled “space_notify_registrations”| Column | Type | Description |
|---|---|---|
id | text (PK) | |
space_ | text (FK) | References spaces.id |
service | text | Subscribing service; one registration per space |
endpoint | text | Resolved service endpoint, or webhook URL |
delivery | text | xrpc (notifyWrite calls) or webhook (legacy payload) |
registered_ | text | DID of who registered |
expires_ | text | When the registration expires |
created_ | text |
space_writers
Section titled “space_writers”| Column | Type | Description |
|---|---|---|
space_ | text (FK) | References spaces.id |
repo_ | text | DID of a repo the space tracks |
rev | text | The repo’s latest revision |
hash | blob | The repo’s latest LtHash digest |
space_ | text | Space revision assigned to this update |
updated_ | text |
space_delegation_uses
Section titled “space_delegation_uses”| Column | Type | Description |
|---|---|---|
issuer | text | Issuer of an exchanged delegation token |
jti | text | The token’s jti; a token is exchanged once |
expires_ | text | When the token expires |
space_invites
Section titled “space_invites”| Column | Type | Description |
|---|---|---|
id | text (PK) | |
space_ | text (FK) | References spaces.id |
token_ | text | SHA-256 hash of the invite token |
created_ | text | DID of the user who created the invite |
access | text | Access level granted: read, read_self, write |
max_ | integer? | Maximum number of uses (null = unlimited) |
uses | integer | Current use count |
expires_ | text? | Expiry timestamp (null = never) |
revoked | boolean | Whether the invite has been revoked |
created_ | text |
space_credentials
Section titled “space_credentials”| Column | Type | Description |
|---|---|---|
id | text (PK) | |
space_ | text (FK) | References spaces.id |
issued_ | text | DID the credential was issued to |
token_ | text | Hash of the credential token |
jti | text | The credential’s jti claim |
expires_ | text | When the credential expires |
revoked_ | text? | When the credential was revoked |
created_ | text |
space_dids
Section titled “space_dids”| Column | Type | Description |
|---|---|---|
id | text (PK) | |
did | text | The space’s DID |
space_ | text (FK) | References spaces.id |
signing_ | text | Encrypted signing key (AES-256-GCM) |
rotation_ | text | Encrypted rotation key (AES-256-GCM) |
created_ | text | DID of who provisioned the key |
created_ | text |
service_identity
Section titled “service_identity”| Column | Type | Description |
|---|---|---|
id | integer (PK) | Always 1 (singleton) |
mode | text | did_web, did_plc, or linked_account |
did | text | The service’s DID |
signing_ | text | Encrypted signing key |
rotation_ | text? | Encrypted rotation key (did:plc only) |
attached_ | text? | Linked account DID (linked_account mode) |
setup_ | boolean | Whether setup has been finalized |
created_ | text | |
updated_ | text |
service_entries
Section titled “service_entries”| Column | Type | Description |
|---|---|---|
id | integer (PK) | |
fragment_ | text | DID document fragment identifier |
service_ | text | Service type (e.g. AtprotoAppView) |
access_ | text | all or scoped to specific XRPCs |
created_ | text | |
updated_ | text |
service_entry_xrpcs
Section titled “service_entry_xrpcs”| Column | Type | Description |
|---|---|---|
service_ | integer (FK) | References service_entries.id |
lexicon_ | text | Lexicon NSID this entry handles |
verification_methods
Section titled “verification_methods”| Column | Type | Description |
|---|---|---|
id | text (PK) | |
fragment_ | text | DID document fragment (e.g. #atproto_space) |
key_ | text | Always Multikey |
public_ | text | Public key in multibase encoding |
private_ | text | Encrypted private key (AES-256-GCM) |
created_ | text |
backfill_jobs
Section titled “backfill_jobs”| Column | Type | Description |
|---|---|---|
id | uuid (PK) | |
collection | text | Target collection (null = all) |
did | text | Target DID (null = all) |
status | text | pending, running, pausing, paused, cancelling, cancelled, completed, failed |
stage | text | pending, discovering_repos, resolving_and_fetching, completed, failed, cancelled |
total_ | integer | Total DIDs discovered |
resolved_ | integer | DIDs with PDS endpoint resolved |
processed_ | integer | DIDs with records fetched |
total_ | integer | Total records indexed |
error | text | Error message if failed |
started_ | timestamptz | |
completed_ | timestamptz | |
created_ | timestamptz |
Testing
Section titled “Testing”# Unit tests (no database needed)cargo test --lib
# All tests including end-to-end (SQLite by default)cargo test
# Or run against Postgresdocker compose -f docker-compose.test.yml up -dTEST_DATABASE_URL=postgres://happyview:happyview@localhost:5433/happyview_test cargo testdocker compose -f docker-compose.test.yml downEnd-to-end tests use wiremock to mock external services (PLC directory, PDSes) and a real database for full integration coverage. By default tests use SQLite; set TEST_DATABASE_URL to a Postgres connection string to test against Postgres.
© HappyProto

