Appearance
SymbolStorageManager
Core CRUD operations and database row mapping for the symbols table.
Purpose
SymbolStorageManager is the foundational data access layer for the SymbolRegistry. It owns the symbols table schema, the SymbolRow type definition, and the rowToConfig() mapping function used by all other managers to convert database rows into the SymbolConfig API type.
It handles seeding core symbols from static constants on first boot (idempotent via existence checks or INSERT OR IGNORE), querying symbols with optional filters (enabled status, category), and updating individual symbol properties with validation.
The manager provides both granular operations (enableSymbol, disableSymbol, updateSymbol) and bulk read operations (getSymbols, getSymbolsByCategory). All enable/disable operations are idempotent, returning success if the symbol is already in the desired state.
High-Level Design
Role Within the System
SymbolStorageManager is the canonical owner of the symbols table — it defines the schema, the SymbolRow type, and the rowToConfig() mapping function. However, ownership is by convention, not enforcement: other managers (SymbolAdminManager, RankingsManager) write directly to the same table for their domain-specific operations. SymbolStorageManager covers core CRUD and seeding; it does not handle ranking crawls, admin promote/demote, or downstream propagation.
Static Constants (26 symbols)
│
│ [first boot, table empty]
▼
seedCoreSymbolsInternal()
│
▼
symbols table ◄── updateSymbol / enable / disable (this manager)
◄── promote / demote / batch updates (SymbolAdminManager)
◄── insertDiscoveredSymbol / rankings (RankingsManager)
──► getEnabledSymbolsWithMetadata() (PushNotificationManager)
│
▼
PriceCollector + PriceAlert DOsSeeding: Two Paths
There are two seeding methods because they serve different callers:
seedCoreSymbolsInternal()— Called during DOinitialize()when the table is empty. UsesINSERT OR IGNOREin a loop over all 26SymbolIdentifiersfrom static constants, settingis_core_symbol = 1. Fast, no per-row existence check.seedCoreSymbols()— Public RPC method for manual re-seeding. Checks each symbol with aSELECTbefore inserting, skipping existing rows. Returns a count of newly seeded symbols so the caller knows what changed.
Both paths are idempotent. The split exists because the internal path optimizes for the cold-start case (no reads needed), while the public path provides feedback. Note: seedCoreSymbolsInternal() explicitly sets is_core_symbol = 1, while seedCoreSymbols() relies on the DB default (0) — so symbols seeded via the public RPC won't be marked as core.
Shared Table, Distributed Writes
The symbols table is the single source of truth for symbol metadata, but multiple managers write directly to it for their domain-specific operations. There are no transactions spanning managers — each SQL statement is independent. This is safe because the data volume is small (26 core + a handful of discovered symbols) and concurrent writes to the same row are unlikely. See each manager's doc for which columns it owns.
rowToConfig() as the Mapping Boundary
The rowToConfig() instance method is the single point where SymbolRow (snake_case DB row) is converted to SymbolConfig (camelCase API type). The parent DO wraps it in a closure and passes it to other managers (e.g., SymbolAdminManager), keeping the type mapping centralized even though multiple managers read the table.
Enable/Disable as Soft State
There is no delete operation. Symbols are never removed from the table. Disabling (is_enabled = 0) is the closest equivalent and is fully reversible. The enabled_at timestamp records the last disable-to-enable transition within this manager — it is set when enabling a disabled symbol but not cleared on disable, giving a "last activated" timestamp. Note that other code paths (e.g., SymbolAdminManager's promoteSymbol) may set enabled_at unconditionally.
Validation Boundaries
SymbolStorageManager validates inputs only for its own update operations:
- Category: Checked against the
SymbolCategoryenum - maxLeverageOverride: Range 1–100, or
nullto clear - iconUrl: Validated with
new URL()constructor
Seeding and discovery inserts (via RankingsManager) skip validation because they use trusted static constants or crawler-verified data.
Edge Cases & Error Handling
enableSymbol/disableSymbolare idempotent -- no error if already in desired state.updateSymbolwith no valid fields to update returns current state without writing.seedCoreSymbolsskips existing symbols via existence check;seedCoreSymbolsInternalusesINSERT OR IGNORE.enabled_atonly set on the transition from disabled to enabled (not on repeated enable calls viaupdateSymbol).iconUrlvalidated withnew URL()constructor; invalid formats rejected.maxLeverageOverrideacceptsnullto clear the override.getSymbolsByCategoryalways returns all 5 category keys, even if empty.
See Also
- SymbolAdminManager - admin promote/demote operations
- RankingsManager - ranking and discovery persistence
- PushNotificationManager - reads enabled symbols for fan-out
- Parent DO:
SymbolRegistry