@lyniaca/lynstock 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +197 -0
  3. package/dist/cryptoSymbols.d.ts +9 -0
  4. package/dist/cryptoSymbols.d.ts.map +1 -0
  5. package/dist/cryptoSymbols.js +23 -0
  6. package/dist/cryptoSymbols.js.map +1 -0
  7. package/dist/errors.d.ts +28 -0
  8. package/dist/errors.d.ts.map +1 -0
  9. package/dist/errors.js +50 -0
  10. package/dist/errors.js.map +1 -0
  11. package/dist/historicalCache.d.ts +23 -0
  12. package/dist/historicalCache.d.ts.map +1 -0
  13. package/dist/historicalCache.js +33 -0
  14. package/dist/historicalCache.js.map +1 -0
  15. package/dist/index.d.ts +6 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +6 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/lynstock.d.ts +45 -0
  20. package/dist/lynstock.d.ts.map +1 -0
  21. package/dist/lynstock.js +131 -0
  22. package/dist/lynstock.js.map +1 -0
  23. package/dist/sources/alphaVantage.d.ts +3 -0
  24. package/dist/sources/alphaVantage.d.ts.map +1 -0
  25. package/dist/sources/alphaVantage.js +79 -0
  26. package/dist/sources/alphaVantage.js.map +1 -0
  27. package/dist/sources/binance.d.ts +3 -0
  28. package/dist/sources/binance.d.ts.map +1 -0
  29. package/dist/sources/binance.js +68 -0
  30. package/dist/sources/binance.js.map +1 -0
  31. package/dist/sources/coinCap.d.ts +3 -0
  32. package/dist/sources/coinCap.d.ts.map +1 -0
  33. package/dist/sources/coinCap.js +37 -0
  34. package/dist/sources/coinCap.js.map +1 -0
  35. package/dist/sources/coinGecko.d.ts +3 -0
  36. package/dist/sources/coinGecko.d.ts.map +1 -0
  37. package/dist/sources/coinGecko.js +76 -0
  38. package/dist/sources/coinGecko.js.map +1 -0
  39. package/dist/sources/coinbase.d.ts +3 -0
  40. package/dist/sources/coinbase.d.ts.map +1 -0
  41. package/dist/sources/coinbase.js +71 -0
  42. package/dist/sources/coinbase.js.map +1 -0
  43. package/dist/sources/finnhub.d.ts +3 -0
  44. package/dist/sources/finnhub.d.ts.map +1 -0
  45. package/dist/sources/finnhub.js +35 -0
  46. package/dist/sources/finnhub.js.map +1 -0
  47. package/dist/sources/kraken.d.ts +3 -0
  48. package/dist/sources/kraken.d.ts.map +1 -0
  49. package/dist/sources/kraken.js +79 -0
  50. package/dist/sources/kraken.js.map +1 -0
  51. package/dist/sources/stooq.d.ts +3 -0
  52. package/dist/sources/stooq.d.ts.map +1 -0
  53. package/dist/sources/stooq.js +83 -0
  54. package/dist/sources/stooq.js.map +1 -0
  55. package/dist/sources/twelveData.d.ts +3 -0
  56. package/dist/sources/twelveData.d.ts.map +1 -0
  57. package/dist/sources/twelveData.js +80 -0
  58. package/dist/sources/twelveData.js.map +1 -0
  59. package/dist/sources/yahoo.d.ts +3 -0
  60. package/dist/sources/yahoo.d.ts.map +1 -0
  61. package/dist/sources/yahoo.js +65 -0
  62. package/dist/sources/yahoo.js.map +1 -0
  63. package/dist/time.d.ts +4 -0
  64. package/dist/time.d.ts.map +1 -0
  65. package/dist/time.js +10 -0
  66. package/dist/time.js.map +1 -0
  67. package/dist/types.d.ts +47 -0
  68. package/dist/types.d.ts.map +1 -0
  69. package/dist/types.js +2 -0
  70. package/dist/types.js.map +1 -0
  71. package/package.json +43 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lyniaca
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,197 @@
1
+ # @lyniaca/lynstock
2
+
3
+ Normalized, multi-source market data for stocks and crypto. Ask for a quote or
4
+ some history and get the same shape back no matter which upstream API
5
+ actually answered — LynStock tries sources in priority order and falls
6
+ through automatically on a rate limit, an outage, or a missing API key.
7
+
8
+ ```ts
9
+ import { LynStock } from "@lyniaca/lynstock";
10
+
11
+ const lynstock = new LynStock();
12
+
13
+ const quote = await lynstock.getQuote("AAPL", "stock");
14
+ // { symbol: "AAPL", assetClass: "stock", price: 227.4, currency: "USD", timestamp: 1735689600000, source: "yahoo" }
15
+
16
+ const btc = await lynstock.getQuote("BTC", "crypto");
17
+ // { symbol: "BTC", assetClass: "crypto", price: 98213, currency: "USD", timestamp: 1735689600000, source: "coingecko" }
18
+ ```
19
+
20
+ ## Install
21
+
22
+ ```
23
+ npm install @lyniaca/lynstock
24
+ ```
25
+
26
+ ESM only (`"type": "module"`). Node 18+ for global `fetch`.
27
+
28
+ ## Why
29
+
30
+ Free/cheap market data APIs (Alpha Vantage, Yahoo, IEX, CoinGecko, etc.) each
31
+ have their own rate limits, outages, and response shapes. LynStock is a thin
32
+ layer that hides all of that: your code asks one interface for a quote or a
33
+ history window, and LynStock handles trying sources in order, skipping ones
34
+ that are rate-limited or missing a required API key, and normalizing whatever
35
+ comes back into one schema.
36
+
37
+ ## Quotes
38
+
39
+ ```ts
40
+ const quote = await lynstock.getQuote(symbol, assetClass);
41
+ ```
42
+
43
+ Every source normalizes into:
44
+
45
+ ```ts
46
+ interface Quote {
47
+ symbol: string;
48
+ assetClass: "stock" | "crypto";
49
+ price: number;
50
+ currency: string;
51
+ timestamp: number; // ms since epoch, when LynStock received it
52
+ source: string; // which upstream actually answered, e.g. "yahoo"
53
+ }
54
+ ```
55
+
56
+ ## Historical data
57
+
58
+ ```ts
59
+ const history = await lynstock.getHistorical("AAPL", "stock", {
60
+ from: new Date("2024-01-01"),
61
+ to: new Date("2024-02-01"),
62
+ });
63
+ ```
64
+
65
+ Returns daily OHLCV candles:
66
+
67
+ ```ts
68
+ interface HistoricalResult {
69
+ symbol: string;
70
+ assetClass: "stock" | "crypto";
71
+ source: string;
72
+ candles: Candle[];
73
+ }
74
+
75
+ interface Candle {
76
+ timestamp: number; // start of the day, ms since epoch
77
+ open: number;
78
+ high: number;
79
+ low: number;
80
+ close: number;
81
+ volume?: number; // not every free-tier source exposes volume (e.g. CoinGecko's OHLC endpoint)
82
+ }
83
+ ```
84
+
85
+ Only daily granularity is supported for now.
86
+
87
+ ### Caching
88
+
89
+ A historical range that ends before today is fully elapsed — none of these
90
+ providers revise a past day's close after the fact — so LynStock caches that
91
+ result in memory and never re-fetches it. A range that reaches into today is
92
+ never cached, since today's candle is still moving.
93
+
94
+ ```ts
95
+ // First call hits the network.
96
+ await lynstock.getHistorical("AAPL", "stock", { from: jan1, to: jan31 });
97
+ // Second call for the exact same symbol/range/asset class returns instantly, no network call.
98
+ await lynstock.getHistorical("AAPL", "stock", { from: jan1, to: jan31 });
99
+ ```
100
+
101
+ The default cache is an unbounded in-memory `Map`, fine for a single process.
102
+ Pass your own implementation of the `HistoricalCache` interface (e.g. backed
103
+ by Redis) via the `historicalCache` option if you need it shared across
104
+ processes or persisted across restarts.
105
+
106
+ ## Supported sources
107
+
108
+ | Source | Asset class | API key | Historical | Notes |
109
+ |---|---|---|---|---|
110
+ | Yahoo Finance | stock | no | yes | Default first choice for stocks. |
111
+ | Alpha Vantage | stock | yes (free signup) | yes | Free tier: 25 requests/day. |
112
+ | Finnhub | stock | yes (free signup) | no | Free tier no longer includes historical candles. |
113
+ | Twelve Data | stock | yes (free signup) | yes | |
114
+ | CoinGecko | crypto | no | yes | Default first choice for crypto. Historical has no volume on the free tier. |
115
+ | Binance | crypto | no | yes | Blocked (HTTP 451) from some regions, including the US, at the network level — not something this library can work around. |
116
+ | Coinbase | crypto | no | yes | Uses the public Exchange candles endpoint for history, not the spot-price endpoint. |
117
+ | Kraken | crypto | no | yes | |
118
+ | Stooq | stock | no | yes | **Not in the default priority.** Its quote endpoint currently 404s and its historical endpoint serves a JS anti-bot challenge instead of CSV. The adapter is implemented and opt-in via `setPriority` in case that changes. |
119
+ | CoinCap | crypto | no | no (price-only, no OHLC) | **Not in the default priority.** `api.coincap.io` no longer resolves — the free v2 API was shut down. |
120
+
121
+ Default priority order:
122
+
123
+ ```ts
124
+ DEFAULT_STOCK_PRIORITY = ["yahoo", "alphavantage", "finnhub", "twelvedata"];
125
+ DEFAULT_CRYPTO_PRIORITY = ["coingecko", "binance", "coinbase", "kraken"];
126
+ ```
127
+
128
+ ## Configuration
129
+
130
+ ```ts
131
+ const lynstock = new LynStock({
132
+ apiKeys: {
133
+ alphavantage: process.env.ALPHA_VANTAGE_KEY,
134
+ finnhub: process.env.FINNHUB_KEY,
135
+ twelvedata: process.env.TWELVE_DATA_KEY,
136
+ },
137
+ priorities: {
138
+ stock: ["yahoo", "twelvedata", "alphavantage"],
139
+ crypto: ["kraken", "coingecko", "binance"],
140
+ },
141
+ disabledSources: ["binance"], // e.g. you know you're on a blocked region
142
+ });
143
+ ```
144
+
145
+ All of it can also change after construction:
146
+
147
+ ```ts
148
+ lynstock.setApiKey("finnhub", "...");
149
+ lynstock.setPriority("stock", ["yahoo"]);
150
+ lynstock.disableSource("binance");
151
+ lynstock.enableSource("binance");
152
+ lynstock.getPriority("crypto"); // ["coingecko", "binance", "coinbase", "kraken"]
153
+ ```
154
+
155
+ A source that requires a key you haven't set is skipped automatically (it
156
+ throws internally and LynStock moves on to the next one in priority order) —
157
+ you don't need to filter it out of the priority list yourself.
158
+
159
+ ## Error handling
160
+
161
+ If every source in the priority list fails, `getQuote`/`getHistorical` throws
162
+ `AllSourcesFailedError`, which carries the per-source failure reasons:
163
+
164
+ ```ts
165
+ try {
166
+ await lynstock.getQuote("AAPL", "stock");
167
+ } catch (error) {
168
+ if (error instanceof AllSourcesFailedError) {
169
+ for (const { source, error: cause } of error.causes) {
170
+ console.log(source, cause.message);
171
+ }
172
+ }
173
+ }
174
+ ```
175
+
176
+ Other exported error types, mainly useful if you're writing your own source
177
+ adapter: `RateLimitError`, `SourceError`, `MissingApiKeyError`,
178
+ `UnsupportedSymbolError` (thrown for a crypto symbol with no known mapping to
179
+ a given provider's id format — see `CRYPTO_SYMBOLS`).
180
+
181
+ ## Development
182
+
183
+ ```
184
+ npm install
185
+ npm run build # tsc -> dist/
186
+ npm test # vitest, all mocked — no network calls
187
+ npm run live-check # hits real upstream APIs, prints pass/fail per source
188
+ ```
189
+
190
+ `live-check` is not part of the automated test suite on purpose — it depends
191
+ on the real internet and on providers' current rate limits/regional blocks,
192
+ so its output will vary run to run. Run it by hand before a release to catch
193
+ response-shape drift the mocked unit tests can't see.
194
+
195
+ ## License
196
+
197
+ MIT
@@ -0,0 +1,9 @@
1
+ export interface CryptoSymbolMap {
2
+ coingecko?: string;
3
+ binance?: string;
4
+ coinbase?: string;
5
+ coincap?: string;
6
+ kraken?: string;
7
+ }
8
+ export declare const CRYPTO_SYMBOLS: Record<string, CryptoSymbolMap>;
9
+ //# sourceMappingURL=cryptoSymbols.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cryptoSymbols.d.ts","sourceRoot":"","sources":["../src/cryptoSymbols.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAqB1D,CAAC"}
@@ -0,0 +1,23 @@
1
+ export const CRYPTO_SYMBOLS = {
2
+ BTC: { coingecko: "bitcoin", binance: "BTCUSDT", coinbase: "BTC-USD", coincap: "bitcoin", kraken: "XBTUSD" },
3
+ ETH: { coingecko: "ethereum", binance: "ETHUSDT", coinbase: "ETH-USD", coincap: "ethereum", kraken: "ETHUSD" },
4
+ SOL: { coingecko: "solana", binance: "SOLUSDT", coinbase: "SOL-USD", coincap: "solana", kraken: "SOLUSD" },
5
+ ADA: { coingecko: "cardano", binance: "ADAUSDT", coinbase: "ADA-USD", coincap: "cardano", kraken: "ADAUSD" },
6
+ XRP: { coingecko: "ripple", binance: "XRPUSDT", coinbase: "XRP-USD", coincap: "xrp", kraken: "XRPUSD" },
7
+ DOGE: { coingecko: "dogecoin", binance: "DOGEUSDT", coinbase: "DOGE-USD", coincap: "dogecoin", kraken: "DOGEUSD" },
8
+ LTC: { coingecko: "litecoin", binance: "LTCUSDT", coinbase: "LTC-USD", coincap: "litecoin", kraken: "LTCUSD" },
9
+ BCH: { coingecko: "bitcoin-cash", binance: "BCHUSDT", coinbase: "BCH-USD", coincap: "bitcoin-cash", kraken: "BCHUSD" },
10
+ DOT: { coingecko: "polkadot", binance: "DOTUSDT", coinbase: "DOT-USD", coincap: "polkadot", kraken: "DOTUSD" },
11
+ MATIC: { coingecko: "matic-network", binance: "MATICUSDT", coinbase: "MATIC-USD", coincap: "polygon", kraken: "MATICUSD" },
12
+ AVAX: { coingecko: "avalanche-2", binance: "AVAXUSDT", coinbase: "AVAX-USD", coincap: "avalanche", kraken: "AVAXUSD" },
13
+ LINK: { coingecko: "chainlink", binance: "LINKUSDT", coinbase: "LINK-USD", coincap: "chainlink", kraken: "LINKUSD" },
14
+ UNI: { coingecko: "uniswap", binance: "UNIUSDT", coinbase: "UNI-USD", coincap: "uniswap", kraken: "UNIUSD" },
15
+ ATOM: { coingecko: "cosmos", binance: "ATOMUSDT", coinbase: "ATOM-USD", coincap: "cosmos", kraken: "ATOMUSD" },
16
+ XLM: { coingecko: "stellar", binance: "XLMUSDT", coinbase: "XLM-USD", coincap: "stellar", kraken: "XLMUSD" },
17
+ ETC: { coingecko: "ethereum-classic", binance: "ETCUSDT", coinbase: "ETC-USD", coincap: "ethereum-classic", kraken: "ETCUSD" },
18
+ FIL: { coingecko: "filecoin", binance: "FILUSDT", coinbase: "FIL-USD", coincap: "filecoin", kraken: "FILUSD" },
19
+ ARB: { coingecko: "arbitrum", binance: "ARBUSDT", coinbase: "ARB-USD", coincap: "arbitrum", kraken: "ARBUSD" },
20
+ OP: { coingecko: "optimism", binance: "OPUSDT", coinbase: "OP-USD", coincap: "optimism", kraken: "OPUSD" },
21
+ APT: { coingecko: "aptos", binance: "APTUSDT", coinbase: "APT-USD", coincap: "aptos", kraken: "APTUSD" },
22
+ };
23
+ //# sourceMappingURL=cryptoSymbols.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cryptoSymbols.js","sourceRoot":"","sources":["../src/cryptoSymbols.ts"],"names":[],"mappings":"AAQA,MAAM,CAAC,MAAM,cAAc,GAAoC;IAC7D,GAAG,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE;IAC5G,GAAG,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE;IAC9G,GAAG,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE;IAC1G,GAAG,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE;IAC5G,GAAG,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE;IACvG,IAAI,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE;IAClH,GAAG,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE;IAC9G,GAAG,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE;IACtH,GAAG,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE;IAC9G,KAAK,EAAE,EAAE,SAAS,EAAE,eAAe,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE;IAC1H,IAAI,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE;IACtH,IAAI,EAAE,EAAE,SAAS,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE;IACpH,GAAG,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE;IAC5G,IAAI,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE;IAC9G,GAAG,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE;IAC5G,GAAG,EAAE,EAAE,SAAS,EAAE,kBAAkB,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,EAAE,QAAQ,EAAE;IAC9H,GAAG,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE;IAC9G,GAAG,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE;IAC9G,EAAE,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE;IAC1G,GAAG,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE;CACzG,CAAC"}
@@ -0,0 +1,28 @@
1
+ export declare class RateLimitError extends Error {
2
+ readonly source: string;
3
+ constructor(source: string, message?: string);
4
+ }
5
+ export declare class SourceError extends Error {
6
+ readonly source: string;
7
+ constructor(source: string, message: string);
8
+ }
9
+ export declare class UnsupportedSymbolError extends Error {
10
+ readonly source: string;
11
+ readonly symbol: string;
12
+ constructor(source: string, symbol: string);
13
+ }
14
+ export declare class MissingApiKeyError extends Error {
15
+ readonly source: string;
16
+ constructor(source: string);
17
+ }
18
+ export interface SourceFailure {
19
+ source: string;
20
+ error: Error;
21
+ }
22
+ export declare class AllSourcesFailedError extends Error {
23
+ readonly symbol: string;
24
+ readonly assetClass: string;
25
+ readonly causes: SourceFailure[];
26
+ constructor(symbol: string, assetClass: string, causes: SourceFailure[]);
27
+ }
28
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,cAAe,SAAQ,KAAK;aACX,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM;CAI7D;AAED,qBAAa,WAAY,SAAQ,KAAK;aACR,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAI5D;AAED,qBAAa,sBAAuB,SAAQ,KAAK;aACnB,MAAM,EAAE,MAAM;aAAkB,MAAM,EAAE,MAAM;gBAA9C,MAAM,EAAE,MAAM,EAAkB,MAAM,EAAE,MAAM;CAI3E;AAED,qBAAa,kBAAmB,SAAQ,KAAK;aACf,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM;CAI3C;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,KAAK,CAAC;CACd;AAED,qBAAa,qBAAsB,SAAQ,KAAK;aAE5B,MAAM,EAAE,MAAM;aACd,UAAU,EAAE,MAAM;aAClB,MAAM,EAAE,aAAa,EAAE;gBAFvB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,aAAa,EAAE;CAU1C"}
package/dist/errors.js ADDED
@@ -0,0 +1,50 @@
1
+ export class RateLimitError extends Error {
2
+ source;
3
+ constructor(source, message) {
4
+ super(message ?? `${source} rate limit exceeded`);
5
+ this.source = source;
6
+ this.name = "RateLimitError";
7
+ }
8
+ }
9
+ export class SourceError extends Error {
10
+ source;
11
+ constructor(source, message) {
12
+ super(message);
13
+ this.source = source;
14
+ this.name = "SourceError";
15
+ }
16
+ }
17
+ export class UnsupportedSymbolError extends Error {
18
+ source;
19
+ symbol;
20
+ constructor(source, symbol) {
21
+ super(`${source} does not support symbol "${symbol}"`);
22
+ this.source = source;
23
+ this.symbol = symbol;
24
+ this.name = "UnsupportedSymbolError";
25
+ }
26
+ }
27
+ export class MissingApiKeyError extends Error {
28
+ source;
29
+ constructor(source) {
30
+ super(`${source} requires an API key`);
31
+ this.source = source;
32
+ this.name = "MissingApiKeyError";
33
+ }
34
+ }
35
+ export class AllSourcesFailedError extends Error {
36
+ symbol;
37
+ assetClass;
38
+ causes;
39
+ constructor(symbol, assetClass, causes) {
40
+ super(`All sources failed for ${symbol} (${assetClass}): ` +
41
+ (causes.length === 0
42
+ ? "no sources configured for this asset class"
43
+ : causes.map((c) => `${c.source}: ${c.error.message}`).join("; ")));
44
+ this.symbol = symbol;
45
+ this.assetClass = assetClass;
46
+ this.causes = causes;
47
+ this.name = "AllSourcesFailedError";
48
+ }
49
+ }
50
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,cAAe,SAAQ,KAAK;IACX;IAA5B,YAA4B,MAAc,EAAE,OAAgB;QAC1D,KAAK,CAAC,OAAO,IAAI,GAAG,MAAM,sBAAsB,CAAC,CAAC;QADxB,WAAM,GAAN,MAAM,CAAQ;QAExC,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAED,MAAM,OAAO,WAAY,SAAQ,KAAK;IACR;IAA5B,YAA4B,MAAc,EAAE,OAAe;QACzD,KAAK,CAAC,OAAO,CAAC,CAAC;QADW,WAAM,GAAN,MAAM,CAAQ;QAExC,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5B,CAAC;CACF;AAED,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IACnB;IAAgC;IAA5D,YAA4B,MAAc,EAAkB,MAAc;QACxE,KAAK,CAAC,GAAG,MAAM,6BAA6B,MAAM,GAAG,CAAC,CAAC;QAD7B,WAAM,GAAN,MAAM,CAAQ;QAAkB,WAAM,GAAN,MAAM,CAAQ;QAExE,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACvC,CAAC;CACF;AAED,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IACf;IAA5B,YAA4B,MAAc;QACxC,KAAK,CAAC,GAAG,MAAM,sBAAsB,CAAC,CAAC;QADb,WAAM,GAAN,MAAM,CAAQ;QAExC,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF;AAOD,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAE5B;IACA;IACA;IAHlB,YACkB,MAAc,EACd,UAAkB,EAClB,MAAuB;QAEvC,KAAK,CACH,0BAA0B,MAAM,KAAK,UAAU,KAAK;YAClD,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;gBAClB,CAAC,CAAC,4CAA4C;gBAC9C,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CACvE,CAAC;QATc,WAAM,GAAN,MAAM,CAAQ;QACd,eAAU,GAAV,UAAU,CAAQ;QAClB,WAAM,GAAN,MAAM,CAAiB;QAQvC,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF"}
@@ -0,0 +1,23 @@
1
+ import type { AssetClass, HistoricalQuery, HistoricalResult } from "./types.js";
2
+ export interface HistoricalCache {
3
+ get(key: string): HistoricalResult | undefined;
4
+ set(key: string, value: HistoricalResult): void;
5
+ }
6
+ /** Simple unbounded in-memory cache. Fine for a single process; swap in a real
7
+ * cache (Redis, etc.) via the LynStock `historicalCache` option for anything
8
+ * longer-lived than one process. */
9
+ export declare class MemoryHistoricalCache implements HistoricalCache {
10
+ private readonly store;
11
+ get(key: string): HistoricalResult | undefined;
12
+ set(key: string, value: HistoricalResult): void;
13
+ clear(): void;
14
+ get size(): number;
15
+ }
16
+ export declare function buildHistoricalCacheKey(symbol: string, assetClass: AssetClass, query: HistoricalQuery): string;
17
+ /**
18
+ * A range ending before the start of today is fully elapsed: none of the
19
+ * providers revise past daily closes, so a result for that range is safe to
20
+ * cache indefinitely instead of re-fetching the same history on every call.
21
+ */
22
+ export declare function isFullyElapsed(query: HistoricalQuery): boolean;
23
+ //# sourceMappingURL=historicalCache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"historicalCache.d.ts","sourceRoot":"","sources":["../src/historicalCache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAGhF,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAAC;IAC/C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAC;CACjD;AAED;;qCAEqC;AACrC,qBAAa,qBAAsB,YAAW,eAAe;IAC3D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAuC;IAE7D,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IAI9C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAI/C,KAAK,IAAI,IAAI;IAIb,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF;AAED,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,EACtB,KAAK,EAAE,eAAe,GACrB,MAAM,CAER;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAI9D"}
@@ -0,0 +1,33 @@
1
+ import { toMs } from "./time.js";
2
+ /** Simple unbounded in-memory cache. Fine for a single process; swap in a real
3
+ * cache (Redis, etc.) via the LynStock `historicalCache` option for anything
4
+ * longer-lived than one process. */
5
+ export class MemoryHistoricalCache {
6
+ store = new Map();
7
+ get(key) {
8
+ return this.store.get(key);
9
+ }
10
+ set(key, value) {
11
+ this.store.set(key, value);
12
+ }
13
+ clear() {
14
+ this.store.clear();
15
+ }
16
+ get size() {
17
+ return this.store.size;
18
+ }
19
+ }
20
+ export function buildHistoricalCacheKey(symbol, assetClass, query) {
21
+ return `${assetClass}:${symbol.toUpperCase()}:${toMs(query.from)}:${toMs(query.to)}`;
22
+ }
23
+ /**
24
+ * A range ending before the start of today is fully elapsed: none of the
25
+ * providers revise past daily closes, so a result for that range is safe to
26
+ * cache indefinitely instead of re-fetching the same history on every call.
27
+ */
28
+ export function isFullyElapsed(query) {
29
+ const startOfToday = new Date();
30
+ startOfToday.setUTCHours(0, 0, 0, 0);
31
+ return toMs(query.to) < startOfToday.getTime();
32
+ }
33
+ //# sourceMappingURL=historicalCache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"historicalCache.js","sourceRoot":"","sources":["../src/historicalCache.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAOjC;;qCAEqC;AACrC,MAAM,OAAO,qBAAqB;IACf,KAAK,GAAG,IAAI,GAAG,EAA4B,CAAC;IAE7D,GAAG,CAAC,GAAW;QACb,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;IAED,GAAG,CAAC,GAAW,EAAE,KAAuB;QACtC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC7B,CAAC;IAED,KAAK;QACH,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;IACzB,CAAC;CACF;AAED,MAAM,UAAU,uBAAuB,CACrC,MAAc,EACd,UAAsB,EACtB,KAAsB;IAEtB,OAAO,GAAG,UAAU,IAAI,MAAM,CAAC,WAAW,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;AACvF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,KAAsB;IACnD,MAAM,YAAY,GAAG,IAAI,IAAI,EAAE,CAAC;IAChC,YAAY,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACrC,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC,OAAO,EAAE,CAAC;AACjD,CAAC"}
@@ -0,0 +1,6 @@
1
+ export * from "./types.js";
2
+ export * from "./errors.js";
3
+ export * from "./lynstock.js";
4
+ export { CRYPTO_SYMBOLS, type CryptoSymbolMap } from "./cryptoSymbols.js";
5
+ export { MemoryHistoricalCache, buildHistoricalCacheKey, isFullyElapsed, type HistoricalCache, } from "./historicalCache.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,cAAc,EACd,KAAK,eAAe,GACrB,MAAM,sBAAsB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export * from "./types.js";
2
+ export * from "./errors.js";
3
+ export * from "./lynstock.js";
4
+ export { CRYPTO_SYMBOLS } from "./cryptoSymbols.js";
5
+ export { MemoryHistoricalCache, buildHistoricalCacheKey, isFullyElapsed, } from "./historicalCache.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,cAAc,EAAwB,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,cAAc,GAEf,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,45 @@
1
+ import type { AssetClass, FetchLike, HistoricalQuery, HistoricalResult, Quote } from "./types.js";
2
+ import { type HistoricalCache } from "./historicalCache.js";
3
+ export type SourceName = "yahoo" | "stooq" | "alphavantage" | "finnhub" | "twelvedata" | "coingecko" | "binance" | "coinbase" | "coincap" | "kraken";
4
+ export declare const DEFAULT_STOCK_PRIORITY: SourceName[];
5
+ export declare const DEFAULT_CRYPTO_PRIORITY: SourceName[];
6
+ export interface LynStockOptions {
7
+ /** API keys for sources that require one (alphavantage, finnhub, twelvedata). */
8
+ apiKeys?: Partial<Record<SourceName, string>>;
9
+ /** Override the fallback order tried for a given asset class. */
10
+ priorities?: Partial<Record<AssetClass, SourceName[]>>;
11
+ /** Sources to skip entirely, regardless of priority. */
12
+ disabledSources?: SourceName[];
13
+ /** Inject a custom fetch implementation (used for testing). */
14
+ fetchImpl?: FetchLike;
15
+ /** Defaults to an in-memory cache; pass your own (e.g. Redis-backed) for multi-process reuse. */
16
+ historicalCache?: HistoricalCache;
17
+ }
18
+ export declare class LynStock {
19
+ private readonly sources;
20
+ private readonly apiKeys;
21
+ private readonly disabled;
22
+ private readonly priorities;
23
+ private readonly historicalCache;
24
+ constructor(options?: LynStockOptions);
25
+ /** Reorder (or restrict) which sources are tried, and in what order, for an asset class. */
26
+ setPriority(assetClass: AssetClass, order: SourceName[]): void;
27
+ getPriority(assetClass: AssetClass): SourceName[];
28
+ setApiKey(source: SourceName, apiKey: string): void;
29
+ disableSource(source: SourceName): void;
30
+ enableSource(source: SourceName): void;
31
+ /**
32
+ * Fetches a quote, walking the configured priority list for the asset class and
33
+ * falling through to the next source whenever the current one errors (including
34
+ * on rate limits) so a single degraded upstream never fails the whole call.
35
+ */
36
+ getQuote(symbol: string, assetClass: AssetClass): Promise<Quote>;
37
+ /**
38
+ * Fetches daily OHLCV history, same priority/fallback behavior as getQuote.
39
+ * Results for a range that's fully in the past are cached indefinitely, since
40
+ * none of the upstream providers revise a closed day's candle after the fact —
41
+ * repeatedly asking for the same historical window never has to hit the network twice.
42
+ */
43
+ getHistorical(symbol: string, assetClass: AssetClass, query: HistoricalQuery): Promise<HistoricalResult>;
44
+ }
45
+ //# sourceMappingURL=lynstock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lynstock.d.ts","sourceRoot":"","sources":["../src/lynstock.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,eAAe,EAAE,gBAAgB,EAAE,KAAK,EAAe,MAAM,YAAY,CAAC;AAE/G,OAAO,EAIL,KAAK,eAAe,EACrB,MAAM,sBAAsB,CAAC;AAY9B,MAAM,MAAM,UAAU,GAClB,OAAO,GACP,OAAO,GACP,cAAc,GACd,SAAS,GACT,YAAY,GACZ,WAAW,GACX,SAAS,GACT,UAAU,GACV,SAAS,GACT,QAAQ,CAAC;AAMb,eAAO,MAAM,sBAAsB,EAAE,UAAU,EAK9C,CAAC;AAIF,eAAO,MAAM,uBAAuB,EAAE,UAAU,EAAmD,CAAC;AAEpG,MAAM,WAAW,eAAe;IAC9B,iFAAiF;IACjF,OAAO,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9C,iEAAiE;IACjE,UAAU,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC;IACvD,wDAAwD;IACxD,eAAe,CAAC,EAAE,UAAU,EAAE,CAAC;IAC/B,+DAA+D;IAC/D,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,iGAAiG;IACjG,eAAe,CAAC,EAAE,eAAe,CAAC;CACnC;AAED,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA+B;IACvD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;IAC9D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkB;IAC3C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAmC;IAC9D,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;gBAEtC,OAAO,GAAE,eAAoB;IAyBzC,4FAA4F;IAC5F,WAAW,CAAC,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,IAAI;IAI9D,WAAW,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,EAAE;IAIjD,SAAS,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAInD,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAIvC,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAItC;;;;OAIG;IACG,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC;IAuBtE;;;;;OAKG;IACG,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAgC/G"}
@@ -0,0 +1,131 @@
1
+ import { AllSourcesFailedError } from "./errors.js";
2
+ import { buildHistoricalCacheKey, isFullyElapsed, MemoryHistoricalCache, } from "./historicalCache.js";
3
+ import { createYahooSource } from "./sources/yahoo.js";
4
+ import { createStooqSource } from "./sources/stooq.js";
5
+ import { createAlphaVantageSource } from "./sources/alphaVantage.js";
6
+ import { createFinnhubSource } from "./sources/finnhub.js";
7
+ import { createTwelveDataSource } from "./sources/twelveData.js";
8
+ import { createCoinGeckoSource } from "./sources/coinGecko.js";
9
+ import { createBinanceSource } from "./sources/binance.js";
10
+ import { createCoinbaseSource } from "./sources/coinbase.js";
11
+ import { createCoinCapSource } from "./sources/coinCap.js";
12
+ import { createKrakenSource } from "./sources/kraken.js";
13
+ // Stooq is excluded by default: its quote endpoint currently 404s and its
14
+ // historical endpoint serves a JS proof-of-work challenge a plain fetch can't
15
+ // pass. The adapter is still exported for anyone who wants to opt in manually
16
+ // via setPriority, in case that changes.
17
+ export const DEFAULT_STOCK_PRIORITY = [
18
+ "yahoo",
19
+ "alphavantage",
20
+ "finnhub",
21
+ "twelvedata",
22
+ ];
23
+ // CoinCap is excluded by default: api.coincap.io no longer resolves (the free
24
+ // v2 API was shut down). Same opt-in-if-it-comes-back reasoning as Stooq above.
25
+ export const DEFAULT_CRYPTO_PRIORITY = ["coingecko", "binance", "coinbase", "kraken"];
26
+ export class LynStock {
27
+ sources;
28
+ apiKeys;
29
+ disabled;
30
+ priorities;
31
+ historicalCache;
32
+ constructor(options = {}) {
33
+ const fetchImpl = options.fetchImpl ?? fetch;
34
+ this.sources = new Map([
35
+ ["yahoo", createYahooSource(fetchImpl)],
36
+ ["stooq", createStooqSource(fetchImpl)],
37
+ ["alphavantage", createAlphaVantageSource(fetchImpl)],
38
+ ["finnhub", createFinnhubSource(fetchImpl)],
39
+ ["twelvedata", createTwelveDataSource(fetchImpl)],
40
+ ["coingecko", createCoinGeckoSource(fetchImpl)],
41
+ ["binance", createBinanceSource(fetchImpl)],
42
+ ["coinbase", createCoinbaseSource(fetchImpl)],
43
+ ["coincap", createCoinCapSource(fetchImpl)],
44
+ ["kraken", createKrakenSource(fetchImpl)],
45
+ ]);
46
+ this.apiKeys = { ...options.apiKeys };
47
+ this.disabled = new Set(options.disabledSources ?? []);
48
+ this.priorities = {
49
+ stock: options.priorities?.stock ?? [...DEFAULT_STOCK_PRIORITY],
50
+ crypto: options.priorities?.crypto ?? [...DEFAULT_CRYPTO_PRIORITY],
51
+ };
52
+ this.historicalCache = options.historicalCache ?? new MemoryHistoricalCache();
53
+ }
54
+ /** Reorder (or restrict) which sources are tried, and in what order, for an asset class. */
55
+ setPriority(assetClass, order) {
56
+ this.priorities[assetClass] = [...order];
57
+ }
58
+ getPriority(assetClass) {
59
+ return [...this.priorities[assetClass]];
60
+ }
61
+ setApiKey(source, apiKey) {
62
+ this.apiKeys[source] = apiKey;
63
+ }
64
+ disableSource(source) {
65
+ this.disabled.add(source);
66
+ }
67
+ enableSource(source) {
68
+ this.disabled.delete(source);
69
+ }
70
+ /**
71
+ * Fetches a quote, walking the configured priority list for the asset class and
72
+ * falling through to the next source whenever the current one errors (including
73
+ * on rate limits) so a single degraded upstream never fails the whole call.
74
+ */
75
+ async getQuote(symbol, assetClass) {
76
+ const order = this.priorities[assetClass];
77
+ const causes = [];
78
+ for (const sourceName of order) {
79
+ if (this.disabled.has(sourceName)) {
80
+ continue;
81
+ }
82
+ const source = this.sources.get(sourceName);
83
+ if (!source || !source.assetClasses.includes(assetClass)) {
84
+ continue;
85
+ }
86
+ try {
87
+ return await source.getQuote(symbol, this.apiKeys[sourceName]);
88
+ }
89
+ catch (error) {
90
+ causes.push({ source: sourceName, error: error });
91
+ }
92
+ }
93
+ throw new AllSourcesFailedError(symbol, assetClass, causes);
94
+ }
95
+ /**
96
+ * Fetches daily OHLCV history, same priority/fallback behavior as getQuote.
97
+ * Results for a range that's fully in the past are cached indefinitely, since
98
+ * none of the upstream providers revise a closed day's candle after the fact —
99
+ * repeatedly asking for the same historical window never has to hit the network twice.
100
+ */
101
+ async getHistorical(symbol, assetClass, query) {
102
+ const cacheKey = buildHistoricalCacheKey(symbol, assetClass, query);
103
+ const cached = this.historicalCache.get(cacheKey);
104
+ if (cached) {
105
+ return cached;
106
+ }
107
+ const order = this.priorities[assetClass];
108
+ const causes = [];
109
+ for (const sourceName of order) {
110
+ if (this.disabled.has(sourceName)) {
111
+ continue;
112
+ }
113
+ const source = this.sources.get(sourceName);
114
+ if (!source || !source.assetClasses.includes(assetClass) || !source.getHistorical) {
115
+ continue;
116
+ }
117
+ try {
118
+ const result = await source.getHistorical(symbol, query, this.apiKeys[sourceName]);
119
+ if (isFullyElapsed(query)) {
120
+ this.historicalCache.set(cacheKey, result);
121
+ }
122
+ return result;
123
+ }
124
+ catch (error) {
125
+ causes.push({ source: sourceName, error: error });
126
+ }
127
+ }
128
+ throw new AllSourcesFailedError(symbol, assetClass, causes);
129
+ }
130
+ }
131
+ //# sourceMappingURL=lynstock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lynstock.js","sourceRoot":"","sources":["../src/lynstock.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { FetchLike, QuoteSource } from "../types.js";
2
+ export declare function createAlphaVantageSource(fetchImpl?: FetchLike): QuoteSource;
3
+ //# sourceMappingURL=alphaVantage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alphaVantage.d.ts","sourceRoot":"","sources":["../../src/sources/alphaVantage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAU,MAAM,aAAa,CAAC;AAIlE,wBAAgB,wBAAwB,CAAC,SAAS,GAAE,SAAiB,GAAG,WAAW,CA6ElF"}