/** * @fileoverview BLS API v2 service. Wraps `POST /timeseries/data` (batch series * fetch with optional calculations), `GET /timeseries/data/{id}?latest=true` * (single-series latest observation), and `GET /surveys` / `GET /surveys/{abbr}` * (survey metadata). Applies retry with 1–2s backoff. Surfaces an invalid API * key, quota exhaustion, series-not-found, locked-series, no-data, * calculations-not-supported, and otherwise-unrecognized request rejections as * typed error data so calling tools can produce the right `ctx.fail` reason. * Deterministic failures (quota exhaustion, request rejection) carry * `retryable: false` so the framework's `withRetry` fails fast instead of * burning quota on doomed attempts. * * Every upstream message is redacted as it is read: BLS echoes the submitted key * back when it rejects one, and these messages flow into thrown errors' `data` * and message strings, both of which reach the client. * * When `BLS_OBSERVATIONS_MIRROR_ENABLED=true` and the mirror has completed at * least one full sync, `fetchSeries` and `fetchLatest` are routed through the * local SQLite mirror instead of the BLS API, bypassing the 500/day quota cap. * Series IDs missing from the mirror fall back to the live API when * `BLS_OBSERVATIONS_MIRROR_FALLBACK_LIVE=true` (the default). * @module services/bls-api/bls-api-service */ import type { Context } from '@cyanheads/mcp-ts-core'; import type { AppConfig } from '@cyanheads/mcp-ts-core/config'; import type { SeriesData, SurveyMeta } from './types.js'; export interface BatchFetchOptions { /** * Request BLS's annual-average rows (period M13/Q05/S03) alongside the real * periods. Off unless asked: the rows are a year's mean, not an extra period, * so a caller reducing `observations` would double-count each year. */ annualAverage?: boolean; calculations?: boolean; endYear?: number; seriesIds: string[]; startYear?: number; } export declare class BlsApiService { private readonly baseUrl; private readonly apiKey; private readonly userAgent; private surveyCache; constructor(apiKey: string, baseUrl: string, userAgent: string); /** Batch-fetch 1–50 series. One API query regardless of series count. */ fetchSeries(options: BatchFetchOptions, ctx: Context): Promise; /** Live API batch fetch — the original implementation. */ private fetchSeriesLive; /** Fetch the single most recent observation for one series. */ fetchLatest(seriesId: string, ctx: Context): Promise; /** * Convert mirror observation rows to SeriesData, hydrating catalog metadata * (title, area, item, seasonal) from the in-memory catalog index. * The LABSTAT data files carry only raw observation values — catalog metadata * must be joined from the catalog service's in-memory series index. */ private mirrorRowsToSeriesData; /** List all surveys. Cached in-memory for 30 days per process. */ listSurveys(ctx: Context): Promise; /** * Read BLS's message array with the configured key masked out of it. * * BLS embeds the submitted key verbatim when rejecting one, and callers * forward these strings into error `data` and, in places, the error message * itself — both reach the client. Redacting here, where the messages are read, * covers every throw site and stays independent of BLS's wording: a key echoed * in some future message is masked without a new pattern to match. */ private redactMessages; /** * The configured `BLS_API_KEY` is rejected — a configuration failure, not a * quota or availability one: nothing upstream is wrong and no amount of waiting * fixes it. `ConfigurationError` also sits outside `withRetry`'s transient set, * so the request stops re-sending a key that cannot start working. * * The upstream message is deliberately not attached: beyond what `reason` * already says, the key is all it carries. */ private invalidApiKeyError; private parseSeriesResponse; private normalizeObs; } export declare function initBlsApiService(_config: AppConfig, _storage: unknown): void; export declare function getBlsApiService(): BlsApiService; //# sourceMappingURL=bls-api-service.d.ts.map