Skip to main content

Documentation Index

Fetch the complete documentation index at: https://285e39fd5e337e58f16290.sightscreen.app/llms.txt

Use this file to discover all available pages before exploring further.

Backend layer diagram

The backend follows a layered architecture with clear dependency boundaries. LA v2 introduces a Providers layer for event detection, display state building, and delivery coordination.

Event-driven pipeline

The core of LA v2. Data flows through the pipeline on every poll tick: Tier0 events are high-priority (wickets, centuries, hat-tricks) and get apns-priority: 10 for immediate delivery. Tier1 events are routine updates (score ticks, over completions) and get apns-priority: 5. CooldownFilter prevents event flooding (e.g., suppressing repeated wicket alerts within a window). StalenessFilter drops events derived from data that has not actually changed since the last push.

Layer responsibilities

Routes

HTTP handlers that parse requests, validate input with Zod schemas, call the appropriate service, and return responses. Routes contain no business logic.

Middleware

Runs before route handlers. Handles:
  • Auth — verifies Cognito JWT tokens, extracts user context
  • Logging — request/response logging with correlation IDs

Services

Business logic for API-facing operations. Services orchestrate between repositories, providers, and other services. HubDataService builds data for the admin UI (Pavilion).

Providers

The event detection and delivery layer. This is where the LA v2 pipeline lives:
ProviderResponsibility
MatchEventBusRoutes raw poll data through the detection pipeline
LACoordinatorCoordinates APNs delivery with priority and batching
Tier0EventDetectorDetects high-priority events (wickets, milestones)
Tier1EventDetectorDetects routine score changes and over completions
CooldownFilterSuppresses repeated events within a cooldown window
StalenessFilterDrops events when underlying data has not changed
DisplayStateBuilderBuilds UnifiedDisplayState from match data
ApnsDeliveryServiceClean interface for sending APNs pushes
LivescoreProviderFetches and normalizes live score data from SportMonks
LiveMatchReconcilerReconciles matches that drop off SportMonks livescores

Clients

Thin wrappers around external HTTP APIs. Handle request formatting, auth headers, rate limiting, and response parsing. The SportMonks client lives here, wrapped in Cockatiel resilience policies.

Repositories

CRUD abstractions over DynamoDB tables. Each repository owns one table and exposes typed methods (getById, query, put, delete). Key repositories in LA v2:
  • ActiveMatchRepository — owns the active-matches table (match processing state, previous scores, cooldowns)
  • LiveActivityRepository — owns the live-activities table (LA sessions, update tokens)

Schemas

Zod schemas for request validation, response shaping, and internal data type definitions. Shared across layers.

Jobs

Background tasks that run on schedules or triggers:
JobResponsibility
MatchOrchestratorManages full match lifecycle (replaces prematch job). Starts/stops pollers, handles transitions.
ScorePollerReusable poller with Cockatiel resilience. Runs for live and replay matches.
MarqueePollerPolls the marquee league at higher frequency with ball-by-ball data.
FixtureSyncJobSyncs fixture lists from SportMonks on a schedule.

Dependency injection via factories

The backend uses a factory-based dependency injection pattern defined in factories.ts. The composition order is:
  1. createRepositories() — instantiates all repository instances (ActiveMatchRepository, LiveActivityRepository, etc.)
  2. createClients() — instantiates external API clients (SportMonks)
  3. createServices() — instantiates services with repository and client dependencies
  4. createProviders() — instantiates providers (MatchEventBus, LACoordinator, detectors, filters, DisplayStateBuilder) with service and repository dependencies
Each factory function creates an instance and injects the required dependencies via constructor parameters. Routes and jobs receive fully-wired service and provider instances from the factories.This means:
  • No layer creates its own dependencies
  • Swapping implementations (e.g., for testing) only requires changing the factory
  • Circular dependencies are caught at startup, not at runtime
  • The full dependency graph is visible in one file
If you add a new service, repository, or provider, you must register it in factories.ts. The app will not pick it up automatically.

Rules of thumb

  • Routes never import repositories or clients directly
  • Services never make HTTP calls — that is what clients and providers are for
  • Repositories never contain business logic or call other repositories
  • Providers own the event detection pipeline — services call providers, not the other way around
  • Jobs always go through services or providers, never bypass them
  • If you are unsure where code belongs, ask: “Does this make a decision?” If yes, it belongs in a service. “Does this detect or deliver?” If yes, it belongs in a provider.