Files
aris/docs/backend-spec.md
kenneth b744af9c51 feat(backend): init aris-backend with Hono
- Add apps/aris-backend package with Hono server
- Add /health endpoint
- Add backend-spec.md with design decisions

Co-authored-by: Ona <no-reply@ona.com>
2026-01-25 14:54:08 +00:00

6.6 KiB

ARIS Backend Specification

Problem Statement

ARIS needs a backend service that manages per-user FeedEngine instances and delivers real-time feed updates to clients. The backend must handle authentication, maintain WebSocket connections for live updates, and accept context updates (like location) that trigger feed recalculations.

Requirements

Authentication

  • Email/password authentication using BetterAuth
  • PostgreSQL for session and user storage
  • Session tokens validated via Authorization: Bearer <token> header
  • Auth endpoints exposed via BetterAuth's built-in routes

FeedEngine Management

  • Each authenticated user gets their own FeedEngine instance
  • Instances are cached in memory with a 30-minute TTL
  • TTL resets on any activity (WebSocket message, location update)
  • Default sources registered for each user: LocationSource, WeatherSource, TflSource
  • Source configuration is hardcoded initially (customization deferred)

WebSocket Connection

  • Single endpoint: GET /ws (upgrades to WebSocket)
  • Authentication via Authorization: Bearer <token> header on upgrade request
  • Rejected before upgrade if token is invalid
  • Multiple connections per user allowed (e.g., multiple devices)
  • All connections for a user receive the same feed updates
  • On connect: immediately send current feed state

JSON-RPC Protocol

All WebSocket communication uses JSON-RPC 2.0.

Client → Server (Requests):

{ "jsonrpc": "2.0", "method": "location.update", "params": { "lat": 51.5, "lng": -0.1, "accuracy": 10, "timestamp": "2025-01-01T12:00:00Z" }, "id": 1 }
{ "jsonrpc": "2.0", "method": "feed.refresh", "params": {}, "id": 2 }

Server → Client (Responses):

{ "jsonrpc": "2.0", "result": { "ok": true }, "id": 1 }

Server → Client (Notifications - no id):

{ "jsonrpc": "2.0", "method": "feed.update", "params": { "items": [...], "errors": [...] } }

JSON-RPC Methods

Method Params Description
location.update { lat, lng, accuracy, timestamp } Push location update, triggers feed refresh
feed.refresh {} Force manual feed refresh

Server Notifications

Method Params Description
feed.update { context, items, errors } Feed state changed
error { code, message, data? } Source or system error

Error Handling

  • Source failures during refresh are reported via error notification
  • Format: { "jsonrpc": "2.0", "method": "error", "params": { "code": -32000, "message": "...", "data": { "sourceId": "weather" } } }

Acceptance Criteria

  1. Auth Flow

    • User can sign up with email/password via POST /api/auth/sign-up
    • User can sign in via POST /api/auth/sign-in and receive session token
    • Invalid credentials return 401
  2. WebSocket Connection

    • GET /ws with valid Authorization header upgrades to WebSocket
    • GET /ws without valid token returns 401 (no upgrade)
    • On successful connect, client receives feed.update notification with current state
    • Multiple connections from same user all receive updates
  3. FeedEngine Lifecycle

    • First connection for a user creates FeedEngine with default sources
    • Subsequent connections reuse the same FeedEngine
    • FeedEngine is destroyed after 30 minutes of inactivity
    • Activity (any WebSocket message) resets the TTL
  4. JSON-RPC Methods

    • location.update updates LocationSource and triggers feed refresh
    • feed.refresh triggers manual refresh
    • Both return { "ok": true } on success
    • Invalid method returns JSON-RPC error
  5. Feed Updates

    • FeedEngine subscription pushes updates to all user's WebSocket connections
    • Updates include context, items, and errors

Implementation Approach

Phase 1: Project Setup

  1. Create apps/aris-backend with Hono
  2. Configure TypeScript, add dependencies (hono, better-auth, postgres driver)
  3. Set up database connection and BetterAuth

Phase 2: Authentication

  1. Configure BetterAuth with email/password provider
  2. Mount BetterAuth routes at /api/auth/*
  3. Create session validation helper for extracting user from token

Phase 3: FeedEngine Manager

  1. Create FeedEngineManager class:
    • getOrCreate(userId): FeedEngine - returns cached or creates new
    • touch(userId) - resets TTL
    • destroy(userId) - manual cleanup
    • Internal TTL cleanup loop
  2. Factory function to create FeedEngine with default sources

Phase 4: WebSocket Handler

  1. Create WebSocket upgrade endpoint at /ws
  2. Validate Authorization header before upgrade
  3. On connect: register connection, send initial feed state
  4. On disconnect: unregister connection

Phase 5: JSON-RPC Handler

  1. Create JSON-RPC message parser and dispatcher
  2. Implement location.update method
  3. Implement feed.refresh method
  4. Wire FeedEngine subscription to broadcast feed.update to all user connections

Phase 6: Connection Manager

  1. Create ConnectionManager to track WebSocket connections per user
  2. Broadcast helper to send to all connections for a user

Phase 7: Integration & Testing

  1. Integration test: auth → connect → location update → receive feed
  2. Test multiple connections receive same updates
  3. Test TTL cleanup

Package Structure

apps/aris-backend/
├── package.json
├── src/
│   ├── index.ts              # Entry point, Hono app
│   ├── auth.ts               # BetterAuth configuration
│   ├── db.ts                 # Database connection
│   ├── ws/
│   │   ├── handler.ts        # WebSocket upgrade & message handling
│   │   ├── jsonrpc.ts        # JSON-RPC parser & types
│   │   └── methods.ts        # Method implementations
│   ├── feed/
│   │   ├── manager.ts        # FeedEngineManager (TTL cache)
│   │   ├── factory.ts        # Creates FeedEngine with default sources
│   │   └── connections.ts    # ConnectionManager (user → WebSocket[])
│   └── types.ts              # Shared types

Dependencies

{
  "dependencies": {
    "hono": "^4",
    "better-auth": "^1",
    "postgres": "^3",
    "@aris/core": "workspace:*",
    "@aris/source-location": "workspace:*",
    "@aris/source-weatherkit": "workspace:*",
    "@aris/data-source-tfl": "workspace:*"
  }
}

Open Questions (Deferred)

  • User source configuration storage (database schema)
  • Rate limiting on WebSocket methods
  • Reconnection handling (client-side concern)
  • Horizontal scaling (would need Redis for shared state)