Skip to content

AlertStorageManager

CRUD operations for alerts with an in-memory cache indexed by symbol.

High-Level Design

AlertStorageManager is the persistence and caching layer for all alerts in a PriceAlert Durable Object. Its core responsibility is keeping two data stores — SQLite (durable) and an in-memory map (fast) — perfectly synchronized through a dual-write pattern.

Data Flow

                    ┌──────────────────────────────────────────┐
                    │         AlertStorageManager              │
  registerAlert()   │                                          │
  ─────────────────►│  1. INSERT INTO alerts (...)             │
                    │  2. alertsBySymbol[symbol].push(alert)   │
                    │  3. alert_metrics.total_alerts += 1      │
                    │                                          │
  deregisterAlert() │                                          │
  ─────────────────►│  1. DELETE FROM alerts WHERE id = ?      │
                    │  2. splice alert from cache              │
                    │  3. prune empty symbol entries           │
                    │                                          │
  getAlertsBySymbol │                                          │
  ─────────────────►│  return alertsBySymbol (cache only)      │
                    └──────────────────────────────────────────┘

Read path: Always from the in-memory Map<SymbolIdentifiers, Alert[]> — zero database queries per evaluation tick.

Write path: Every mutation applies to both SQLite and cache in sequence. This dual-write is safe because the single-threaded Durable Object guarantees no concurrent writes.

Cache Lifecycle

DO starts → initialize() → SELECT * FROM alerts → build alertsBySymbol map

         register/deregister mutations ◄─────────────────┘
         keep SQLite + cache in sync                     │

DO evicts → cache lost → next alarm restarts → initialize() rebuilds from SQLite

The cache is fully rebuilt from SQLite on every DO start. Between starts, all mutations maintain both stores. If the DO evicts, no data is lost — SQLite is the source of truth and the cache is a derived view.

Reverse Iteration for Safe Bulk Deletion

deregisterAllForUser() iterates each symbol's alert array in reverse when splicing, preventing the index-shifting bug that occurs with forward iteration and splice(). Empty symbol entries are pruned afterward to prevent unbounded map growth.

String Precision for Monetary Values

targetPrice and sizeUsd are stored as TEXT in SQLite rather than REAL. JavaScript numbers lose precision beyond 15 significant digits, and these values cross RPC boundaries as BigNumber strings (up to 18 decimal places). Storing as text preserves full precision through the round-trip.

Error Containment

All public methods catch exceptions and return { success: false } rather than throwing. This ensures the 1-second alarm loop is never interrupted by a storage failure — the evaluation continues on the next tick.

State & Storage

In-Memory State

PropertyTypeLifecycle
alertsBySymbolMap<SymbolIdentifiers, Alert[]>Populated on initialize(), mutated on register/deregister.

SQLite Tables

alerts

ColumnTypePurpose
idtext (PK)Unique alert identifier
user_idtextOwner user ID
symboltextTrading pair (e.g., BTCUSD)
typetextLIMIT_ORDER, STOP_LOSS, TAKE_PROFIT, LIQUIDATION_WATCH
directiontextLONG or SHORT
target_pricetextTarget price as string for precision
size_usdtext (nullable)Order size in USD
leverageinteger (nullable)Leverage multiplier
one_timeinteger1 if alert should be deregistered after triggering
created_atintegerUnix timestamp of creation
metadatatext (nullable)Arbitrary JSON metadata
account_idintegerAccount identifier

Indexes: idx_symbol on symbol (cache rebuild, grouped queries), idx_user_id on user_id (bulk user deletion).

Interactions

Depends On

  • BaseDurableManager (base class)
  • PriceAlert.state.storage.sql (SQLite access)

Depended By

  • AlertEvaluationManager: reads getAlertsBySymbol() every tick; calls deregisterAlert() for triggered/zombie alerts.
  • PriceAlert RPC methods: calls registerAlert(), deregisterAlert(), deregisterAllForUser().

See Also