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.
Table overview
All persistent storage lives in DynamoDB. There are no relational databases. Each table is owned by a single repository, though multiple services may read from it via that repository.
LA v2 replaced several tables: activity-subscriptions is now live-activities, match-processing-state is now active-matches, and prematch-notifications and state-hashes have been removed entirely.
Table details
cricket-fixtures
The central table. Stores every match the system knows about.
| Key | Type | Description |
|---|
matchId (PK) | String | SportMonks fixture ID |
GSIs:
| GSI name | Partition key | Sort key | Purpose |
|---|
leagueCode-startTime-index | leagueCode | startTime | Query fixtures by league, sorted by start time |
Accessed by: FixtureService (read/write), FixtureSyncJob (write), DisplayStateBuilder (read)
Access patterns:
| Pattern | Type | Operation | Key condition | Triggered by | Frequency |
|---|
| Get fixture by ID | Both | GetItem | PK=matchId | GET /matches/:id, ScorePoller | Per request / every poll tick |
| Batch get fixtures | Both | BatchGetItem | PK=matchId (up to 100) | GET /matches, FixtureSyncJob | Per request / per sync cycle |
| Get matches by date range | User | Query (GSI) | leagueCode-startTime-index, leagueCode = :code AND startTime BETWEEN :from AND :to | GET /matches?league=&from=&to= | Per user request |
| Batch write fixtures | System | BatchWriteItem | PK=matchId (batches of 25) | FixtureSyncJob | Per sync cycle |
| Update fixture fields | System | UpdateItem | PK=matchId with ConditionExpression: attribute_exists(matchId) | ScorePoller, FixtureSyncJob | Per live match per tick |
| Archive fixture | System | UpdateItem | PK=matchId, SETs archivedAt, REMOVEs liveUpdatedAt | LiveMatchReconciler | When match leaves livescores |
The live-index GSI has been removed. The active-matches table now serves as the source of truth for which matches are currently being polled, replacing the sparse GSI pattern.
live-activities
Tracks Live Activity sessions. When a user starts a Live Activity on iOS, their update token and session metadata are stored here. Replaces the old activity-subscriptions table.
| Key | Type | Description |
|---|
matchId (PK) | String | Fixture match ID |
userId#deviceId (SK) | String | Composite sort key: Cognito user ID + device identifier |
GSIs:
| GSI name | Partition key | Sort key | Purpose |
|---|
userId-index | userId | - | Query all Live Activities for a user across matches |
Key attributes: updateToken (APNs Live Activity push token), status (active/ended/suspended), sessionStartedAt, endedAt, endReason, suspendedAt, ttl
TTL: Yes — sessions expire after the match ends.
Accessed by: LiveActivityRepository (read/write), LACoordinator (read), ApnsDeliveryService (read)
Access patterns:
| Pattern | Type | Operation | Key condition | Triggered by | Frequency |
|---|
| Get all LA sessions for match | System | Query | PK=matchId | LACoordinator delivery | Every poll tick per live match |
| Get user’s LA sessions | User | Query (GSI) | userId-index, userId = :uid | GET /user/activities | Per request |
| Register LA session | User | UpdateItem (upsert) | PK=matchId, SK=userId#deviceId | POST /user/activity/:matchId | When iOS starts Live Activity |
| Update token | User | UpdateItem | PK=matchId, SK=userId#deviceId | Token rotation from iOS | On token refresh |
| End LA session | Both | UpdateItem | PK=matchId, SK=userId#deviceId, SET status=ended, endedAt, endReason | DELETE /user/activity/:matchId, match completion | User action / match end |
| Suspend LA session | System | UpdateItem | PK=matchId, SK=userId#deviceId, SET status=suspended, suspendedAt | APNs token rejection | On push failure |
The SK is userId#deviceId (composite), enabling multi-device support. A user with iPhone + iPad has 2 separate session records for the same match. The userId-index GSI allows querying all of a user’s active sessions across matches.
active-matches
Tracks all matches currently being polled, along with their processing state. Replaces both the old match-processing-state table and the live-index GSI on fixtures. Each record is approximately 5KB and contains everything the event detection pipeline needs to detect changes.
| Key | Type | Description |
|---|
matchId (PK) | String | Fixture match ID |
Key attributes: status, instanceId (which server instance owns this match), lastTickAt, matchPhase, previousBallId, previousScore, previousStatus, previousBatting, previousBowling, last30Balls, cooldowns, ttl
TTL: Yes — records expire after the match ends + buffer.
Accessed by: ActiveMatchRepository (read/write), MatchOrchestrator (write), ScorePoller (read/write), MatchEventBus (read)
Access patterns:
| Pattern | Type | Operation | Key condition | Triggered by | Frequency |
|---|
| Get active match state | System | GetItem | PK=matchId | ScorePoller, MatchEventBus | Every poll tick per match |
| Get all active matches | System | Scan | Full table scan (typically 5-20 items) | MatchOrchestrator reconciliation | Every reconciliation cycle |
| Register active match | System | PutItem (conditional) | PK=matchId with ConditionExpression: attribute_not_exists(matchId) OR status = :idle | MatchOrchestrator | When match goes live |
| Update match state | System | UpdateItem | PK=matchId, SET previousScore, previousBatting, previousBowling, last30Balls, cooldowns, lastTickAt | ScorePoller after each tick | Every poll tick |
| Update cooldowns | System | UpdateItem | PK=matchId, SET cooldowns | CooldownFilter | After event filtering |
| Deactivate match | System | UpdateItem | PK=matchId, SET status=ended, ttl | MatchOrchestrator | When match completes |
| Delete active match | System | DeleteItem | PK=matchId | Cleanup after TTL expiry | Rare, manual |
The previousScore, previousBatting, previousBowling, and last30Balls fields store the state from the last poll tick. The event detection pipeline diffs current data against these previous values to determine what changed. The cooldowns map tracks per-event-type cooldown expiry timestamps.
The Scan on this table is intentional and acceptable — at peak there are only 5-20 active matches, so a full scan costs less than 1 RCU. This replaces the old sparse GSI pattern on the fixtures table.
cricket-teams
Static team reference data synced from SportMonks.
| Key | Type | Description |
|---|
teamId (PK) | String | SportMonks team ID |
Accessed by: TeamService (read/write), DisplayStateBuilder (read)
Access patterns:
| Pattern | Type | Operation | Key condition | Triggered by | Frequency |
|---|
| Get team by ID | Both | GetItem | PK=teamId | GET /teams/:id, DisplayStateBuilder | Per request / per enrichment |
| Get all teams | User | Scan | Full table scan (< 100 items) | GET /teams | Per request, cached |
| Batch write teams | System | BatchWriteItem | PK=teamId (batches of 25) | TeamSyncJob | Per sync cycle |
| Update team fields | User | UpdateItem | PK=teamId with ConditionExpression: attribute_exists(teamId) | PATCH /teams/:id (admin) | Rare, admin-only |
cricket-team-seasons
Per-season stats for each team within a league season.
| Key | Type | Description |
|---|
seasonId (PK) | String | SportMonks season ID |
teamId (SK) | String | SportMonks team ID |
Accessed by: TeamSeasonService (read/write)
Access patterns:
| Pattern | Type | Operation | Key condition | Triggered by | Frequency |
|---|
| Get teams for a season | Both | Query | PK=seasonId | GET /seasons/:id/teams, DisplayStateBuilder | Per request / per enrichment |
| Write season-team records | System | BatchWriteItem | PK=seasonId, SK=teamId (batches of 25) | TeamSyncJob | Per sync cycle |
cricket-leagues
League/competition reference data.
| Key | Type | Description |
|---|
leagueId (PK) | String | SportMonks league ID |
Accessed by: LeagueService (read/write), DisplayStateBuilder (read)
Access patterns:
| Pattern | Type | Operation | Key condition | Triggered by | Frequency |
|---|
| Get league by ID | Both | GetItem | PK=leagueId | GET /leagues/:id, DisplayStateBuilder | Per request / per enrichment |
| Get all leagues | User | Scan | Full table scan (< 30 items) | GET /leagues | Per request, cached in-memory |
| Get active leagues | System | Scan + filter | FilterExpression: active = true | MatchOrchestrator, FixtureSyncJob | Per job cycle, cached |
| Create/replace league | System | PutItem | PK=leagueId | LeagueSyncJob | Per sync cycle |
| Update league fields | User | UpdateItem | PK=leagueId with ConditionExpression: attribute_exists(leagueId) | PATCH /leagues/:id (admin) | Rare, admin-only |
| Delete league | User | DeleteItem | PK=leagueId | Admin endpoint | Rare |
cricket-devices
Maps users to their registered push notification device tokens. Now supports multi-device with additional device metadata.
| Key | Type | Description |
|---|
userId (PK) | String | Cognito user ID |
deviceToken (SK) | String | APNs device token |
Key attributes: platform, apnsToken, pushToStartToken, osVersion, appVersion, ttl
TTL: Yes — stale device registrations expire automatically.
Accessed by: DeviceService (read/write), ApnsDeliveryService (read)
Access patterns:
| Pattern | Type | Operation | Key condition | Triggered by | Frequency |
|---|
| Get device for user | Both | Query | PK=userId, Limit: 1 | GET /user/device, ApnsDeliveryService | Per push send / per request |
| Register device (upsert) | User | Query + Delete + UpdateItem | Query PK=userId to find stale records, Delete orphans, then UpdateItem PK=userId, SK=deviceToken with osVersion, appVersion | POST /user/device | On app launch |
| Update push-to-start token | User | GetItem + UpdateItem | PK=userId, SK=deviceToken | PUT /user/device/push-to-start | On token refresh |
| Delete device | User | DeleteItem | PK=userId, SK=deviceToken with ReturnValues: ALL_OLD | DELETE /user/device | User action |
Device registration enforces single-device-per-user: it queries all records for the user, deletes any with a different deviceToken, then upserts the current one. The osVersion and appVersion fields are captured on registration for debugging push delivery issues.
cricket-match-follows
Tracks which users are following which matches (for push notifications on score changes).
| Key | Type | Description |
|---|
matchId (PK) | String | Fixture match ID |
userId (SK) | String | Cognito user ID |
Accessed by: MatchFollowService (read/write), ApnsDeliveryService (read)
Access patterns:
| Pattern | Type | Operation | Key condition | Triggered by | Frequency |
|---|
| Check if user follows match | User | GetItem | PK=matchId, SK=userId | GET /user/follow/:matchId | Per request |
| Get all followers for match | System | Query | PK=matchId (paginated) | LACoordinator | Every poll tick per live match |
| Follow a match | User | UpdateItem | PK=matchId, SK=userId, SET teamId, expiresAt, createdAt = if_not_exists(...) | POST /user/follow/:matchId | User action |
| Unfollow a match | User | DeleteItem | PK=matchId, SK=userId with ReturnValues: ALL_OLD | DELETE /user/follow/:matchId | User action |
| Update follow team | User | UpdateItem | PK=matchId, SK=userId with ConditionExpression: attribute_exists(matchId) | PATCH /user/follow/:matchId | User action |
| Remove all follows for match | System | BatchDeleteItem | Query PK=matchId, then batch delete all | Match completion cleanup | Once per match end |
expiresAt is set to match start time + 48h buffer. DynamoDB TTL automatically cleans up follow records for completed matches — no cron job needed.
cricket-locks
Distributed locking table. Prevents multiple backend instances from processing the same match concurrently.
| Key | Type | Description |
|---|
lockKey (PK) | String | Lock identifier (usually match:{matchId}) |
Key attributes: owner (instance ID), expiresAt (epoch timestamp for auto-release)
Accessed by: LockService (read/write)
Access patterns:
| Pattern | Type | Operation | Key condition | Triggered by | Frequency |
|---|
| Acquire lock | System | PutItem (conditional) | PK=lockId with ConditionExpression: attribute_not_exists(lockId) OR expiresAt < :now | ScorePoller, MatchOrchestrator | Every poll tick per live match |
| Release lock | System | DeleteItem (conditional) | PK=lockId with ConditionExpression: holder = :holder | ScorePoller completion | After each processing tick |
| Extend lock | System | UpdateItem (conditional) | PK=lockId with ConditionExpression: holder = :holder | Long-running jobs | During processing |
All three operations use conditional writes for atomicity. ConditionalCheckFailedException on acquire means another instance holds the lock — this is expected behavior, not an error. Default TTL is 10 seconds, acting as an auto-release safety net if the holder crashes.
DisplayStateCache
Caches the fully-computed display state for a match. Avoids recomputing on every API request.
| Key | Type | Description |
|---|
matchId (PK) | String | Fixture match ID |
TTL: Yes — cache entries expire and are rebuilt on next request.
Accessed by: DisplayStateCacheService (read/write)
Access patterns:
| Pattern | Type | Operation | Key condition | Triggered by | Frequency |
|---|
| Get cached display state | Both | GetItem | PK=cacheKey (composite: DISPLAY#{matchId}#{teamId}) | GET /matches/:id/display, ScorePoller | Per request / per tick |
| Set cached display state | Both | PutItem | PK=cacheKey with TTL | DisplayStateBuilder after computation | After each state computation |
The cache key is DISPLAY#{matchId}#{teamId}, allowing per-team display state caching. Client-side TTL validation is performed in addition to DynamoDB TTL — if ttl has passed, the GetItem returns null even before DynamoDB deletes the record.
cricket-fixture-cache
Caches raw fixture data from SportMonks to reduce API calls.
| Key | Type | Description |
|---|
matchId (PK) | String | Fixture match ID |
TTL: Yes — cached data expires based on match state (live matches have shorter TTLs).
Accessed by: FixtureCacheService (read/write), SportMonksClient (via provider)
GSIs:
| GSI name | Partition key | Sort key | Purpose |
|---|
leagueId-startingAt-index | leagueId | startingAt | Query cached fixtures by league |
Access patterns:
| Pattern | Type | Operation | Key condition | Triggered by | Frequency |
|---|
| Get cached fixture | Both | GetItem | PK=matchId | GET /matches/:id, ScorePoller | Per request / per tick |
| Batch get cached fixtures | Both | BatchGetItem | PK=matchId (up to 100, with retry) | GET /matches, FixtureSyncJob | Per request / per sync |
| Get fixtures by league | Both | Query (GSI) | leagueId-startingAt-index, leagueId = :lid with filter ttl > :now | GET /matches?league=, ScorePoller | Per request / per tick |
| Get all cached fixtures | System | Scan | Full table scan (30-50 items) | FixtureSyncJob | Per sync cycle |
| Put single fixture | System | PutItem | PK=matchId | ScorePoller | Per live match per tick |
| Batch put fixtures | System | BatchWriteItem | PK=matchId (batches of 25, with retry) | FixtureSyncJob | Per sync cycle |
Removed tables
The following tables existed pre-LA v2 and have been removed:
| Old table | Replaced by | Reason |
|---|
cricket-activity-subscriptions | live-activities | New table with composite SK (userId#deviceId), GSI, and session lifecycle tracking |
match-processing-state | active-matches | New table stores full previous state for event detection diffing |
cricket-prematch-notifications | MatchOrchestrator | Orchestrator manages the full match lifecycle including pre-match, no separate tracking needed |
cricket-state-hashes | (removed) | The pipeline skips delivery if nothing changed — no need for explicit hash comparison |
Access pattern summary
| Service / Provider | Tables accessed | Mode |
|---|
| FixtureService | cricket-fixtures | Read/Write |
| TeamService | cricket-teams | Read/Write |
| TeamSeasonService | cricket-team-seasons | Read/Write |
| LeagueService | cricket-leagues | Read/Write |
| DeviceService | cricket-devices | Read/Write |
| MatchFollowService | cricket-match-follows | Read/Write |
| LiveActivityRepository | live-activities | Read/Write |
| ActiveMatchRepository | active-matches | Read/Write |
| LockService | cricket-locks | Read/Write |
| DisplayStateCacheService | DisplayStateCache | Read/Write |
| FixtureCacheService | cricket-fixture-cache | Read/Write |
| LACoordinator | live-activities, cricket-match-follows | Read |
| ApnsDeliveryService | cricket-devices, live-activities | Read |
| DisplayStateBuilder | cricket-fixtures, cricket-teams, cricket-leagues | Read |
| MatchOrchestrator | active-matches, cricket-fixtures | Read/Write |
| ScorePoller | active-matches, cricket-fixture-cache | Read/Write |
| HubDataService | active-matches, live-activities, cricket-fixtures | Read |