yfinance: Free Yahoo Finance market data (quotes, history, fundamentals, options, news, movers) via a bundled MCP server, with five views.
| 1 | // Yahoo's JSON read the forgiving way: a field can be missing, null, a plain number, or a number |
| 2 | // wrapped as { raw, fmt } (quoteSummary and the market summary still wrap some with formatted=false). |
| 3 | // State a view reads is the same: main stores it as written. Shared by the server and the views, so |
| 4 | // nothing here touches Node or the DOM. |
| 5 | |
| 6 | export type Row = Record<string, unknown> |
| 7 | |
| 8 | export function record(value: unknown): Row | null { |
| 9 | return typeof value === 'object' && value !== null && !Array.isArray(value) ? (value as Row) : null |
| 10 | } |
| 11 | |
| 12 | export function list(value: unknown): unknown[] { |
| 13 | return Array.isArray(value) ? value : [] |
| 14 | } |
| 15 | |
| 16 | /** The plain objects in a list. */ |
| 17 | export function records(value: unknown): Row[] { |
| 18 | return list(value).filter((item): item is Row => record(item) !== null) |
| 19 | } |
| 20 | |
| 21 | /** A finite number, taken out of { raw } when wrapped; null otherwise. */ |
| 22 | export function num(value: unknown): number | null { |
| 23 | const raw = record(value) ? (value as Row)['raw'] : value |
| 24 | return typeof raw === 'number' && Number.isFinite(raw) ? raw : null |
| 25 | } |
| 26 | |
| 27 | /** A string with something in it, trimmed; null otherwise. */ |
| 28 | export function text(value: unknown): string | null { |
| 29 | if (typeof value !== 'string') return null |
| 30 | const trimmed = value.trim() |
| 31 | return trimmed === '' ? null : trimmed |
| 32 | } |
| 33 | |
| 34 | /** A fraction as percent (0.0797 is 7.97), rounded past float noise. */ |
| 35 | export function percentOf(fraction: number | null): number | null { |
| 36 | return fraction === null ? null : Math.round(fraction * 1e6) / 1e4 |
| 37 | } |
| 38 | |
| 39 | export function int(value: unknown): number | undefined { |
| 40 | return typeof value === 'number' && Number.isInteger(value) ? value : undefined |
| 41 | } |
| 42 | |
| 43 | export function clamp(value: number, min: number, max: number): number { |
| 44 | return Math.min(max, Math.max(min, value)) |
| 45 | } |