Skip to main content

Overview

Autonome uses a hybrid data flow architecture combining oRPC procedures for request/response patterns and Server-Sent Events (SSE) for real-time updates. This ensures type-safe data fetching while maintaining instant UI reactivity.

oRPC Request/Response Flow

Client → Server Data Fetching

The primary pattern for fetching data from the backend:

Step-by-Step Breakdown

1. Component Query

What happens:
  • TanStack Query checks cache (15s staleTime for positions)
  • If stale, triggers oRPC client request
  • Suspends component until data arrives

2. oRPC Client Configuration

Key points:
  • RouterClient<typeof router> infers full type signature from backend
  • getRpcUrl() uses VITE_API_URL env var (or Vite proxy in dev)
  • createTanstackQueryUtils() generates .queryOptions() for every procedure

3. API URL Resolution

Environment-specific behavior:

4. Vite Proxy (Development Only)

Flow:
  • Browser requests http://localhost:5173/api/rpc/trading.getPositions
  • Vite intercepts and forwards to http://localhost:8081/api/rpc/trading.getPositions
  • Backend responds, Vite forwards back to browser
The proxy eliminates CORS issues in development. In production, the frontend directly calls the VPS API URL.

5. Hono API Server

Routing logic:
  • POST /api/rpc/trading.getPositions → router.trading.getPositions
  • POST /api/rpc/simulator.placeOrder → router.simulator.placeOrder
  • Wildcard matching for nested procedures

6. oRPC Procedure Handler

Handler responsibilities:
  1. Input validation: Zod parses and validates input
  2. Business logic: Calls feature module (e.g., fetchPositions)
  3. Output validation: Zod validates return value matches schema
  4. Error handling: Zod validation errors auto-convert to 400 responses
  5. Observability: Sentry span tracks execution time

7. Feature Module (Business Logic)

Data flow:
  • Drizzle query with join (Orders + Models)
  • Filter for OPEN orders (positions, not trades)
  • Enrich with live price data (unrealized P&L)
  • Return typed array

8. Response Flow

The response travels back through the stack:

TanStack Query Caching Strategy

TanStack Query caches responses to reduce server load:
Cache behavior: Stale time recommendations:
  • High-frequency updates (positions, trades): 15s
  • Moderate updates (portfolio history): 30s
  • Rarely changing (models, variants): 5min

Server-Sent Events (SSE) Streaming

Why SSE?

SSE provides unidirectional real-time updates from server to client: Autonome’s choice: SSE is simpler than WebSockets for one-way updates and automatically reconnects on network issues.

SSE Architecture

Backend: Event Broadcasting

EventEmitter Pattern

Key design choices:
  • In-memory cache: Stores latest event for immediate SSE hydration
  • Max listeners: Increased from default (10) to support many SSE clients
  • Cleanup function: Returned by subscribe for proper unsubscription

SSE Endpoint Handler

Flow:
  1. Cache hydration: Fetch latest positions from DB
  2. Initial data: Send current cache to new subscriber
  3. Subscription: Register listener for future events
  4. Heartbeat: Send ping every 15s to prevent proxy timeout
  5. Cleanup: Remove listener when client disconnects
  6. Keep-alive: Never-resolving promise keeps stream open

Event Emission from Schedulers

Trigger points:
  • Schedulers: Price tracker (10s), trade executor (60s)
  • Simulator: Auto-close triggers (stop-loss, take-profit)
  • Manual actions: User places/closes orders

Frontend: Event Consumption

EventSource Subscription

Usage in component:
How it works:
  1. Component mounts → usePositionsStream() subscribes to SSE
  2. Server emits position update → SSE pushes to client
  3. onmessage handler → updates TanStack Query cache
  4. Cache update → React re-renders component with new data
  5. Component unmounts → EventSource.close() called

SSE Endpoints Overview

State Management Layers

Autonome uses a multi-layer state architecture:

1. Server State (TanStack Query)

For data fetched from the backend:
Managed by: TanStack Query cache
Persistence: In-memory (cleared on refresh)
Synchronization: oRPC + SSE

2. URL State (TanStack Router)

For navigation and shareable state:
Managed by: TanStack Router
Persistence: URL (shareable, bookmarkable)
Use cases: Filters, tabs, pagination

3. Local State (TanStack Store)

For ephemeral UI state:
Managed by: TanStack Store
Persistence: In-memory (cleared on refresh)
Use cases: Sidebar expanded state, theme preference

4. Component State (React)

For isolated component logic:
Managed by: React useState/useReducer
Persistence: Component lifecycle only
Use cases: Form inputs, modals, tooltips

Optimistic Updates

For instant UI feedback on mutations:
Flow:
  1. User clicks “Close” → UI updates instantly (optimistic)
  2. Mutation sent to server → oRPC handler executes
  3. If success → SSE confirms update (cache already correct)
  4. If error → Rollback optimistic update (restore previous state)
  5. onSettled → Refetch to guarantee server truth

Data Consistency Guarantees

Single Source of Truth: Orders Table

The "Orders" table in PostgreSQL is the canonical source for positions:
  • OPEN orders = active positions
  • CLOSED orders = completed trades
All derived data (UI, simulator state, analytics) must reconcile with this table.

Simulator State Restoration

On server restart, the simulator rehydrates from the database:
Guarantees:
  • Simulator state matches DB on startup
  • Auto-close triggers work after restart
  • No position data loss during deployments

Cache Invalidation Strategy

Explicit invalidation after mutations:
SSE-driven invalidation:
Time-based staleness: TanStack Query refetches stale data automatically.

Performance Optimizations

1. Query Deduplication

TanStack Query deduplicates concurrent requests:

2. Selective Cache Updates

SSE updates only changed data:

3. Background Refetching

TanStack Query refetches stale data without blocking UI:

4. Partial Hydration

SSE endpoints hydrate cache before sending initial data:

Error Handling

oRPC Errors

SSE Reconnection

EventSource automatically reconnects on disconnect:

Mutation Rollback

Optimistic updates rollback on error (see Optimistic Updates section).

Summary

Autonome’s data flow architecture balances:
  • Type safety: oRPC procedures with Zod validation
  • Performance: TanStack Query caching and background refetching
  • Real-time: SSE for instant UI updates
  • Consistency: Single source of truth (Orders table)
  • Resilience: Automatic reconnection and error recovery
This hybrid approach provides the best of both worlds: type-safe request/response for data fetching and low-latency SSE for real-time updates.

Next Steps