Initial Commit
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
# Add @suaveplan/currency-provider-open-exchange-rates
|
||||
|
||||
**Package**: `@suaveplan/currency-provider-open-exchange-rates`
|
||||
**Status**: PROPOSED
|
||||
**Type**: new-package
|
||||
|
||||
- **Spec delta**: ./specs/currency-provider-open-exchange-rates/spec.md
|
||||
|
||||
## Why
|
||||
|
||||
The 2026-05-17 slim of `@suaveplan/currency-providers` removed the nine bundled
|
||||
provider implementations and committed to landing each as an independent
|
||||
sibling package on the foundation-with-sibling-plugins shape established by
|
||||
`@suaveplan/network-plugin-*` and `@suaveplan/drive-plugin-*`. This change
|
||||
lands the Open Exchange Rates (OXR) provider so consumers that only need OXR
|
||||
install one small package instead of the bundled umbrella, and OXR can iterate
|
||||
on its own version cadence when the upstream API changes (free-plan base-only,
|
||||
historical endpoints, currency-list updates).
|
||||
|
||||
## What changes
|
||||
|
||||
- ADDED: `OpenExchangeRatesProvider` — class implementing `CurrencyProvider`
|
||||
from `@suaveplan/currency`; consumes `httpRequest()` from
|
||||
`@suaveplan/currency-providers`; raises `CurrencyProviderError` with codes
|
||||
`HTTP_ERROR`, `PARSE_ERROR`, `UPSTREAM_ERROR`, `MISSING_CONFIG`,
|
||||
`UNSUPPORTED_BASE`, `UNKNOWN_CURRENCY`.
|
||||
- ADDED: subpath export `./provider/provider` for tree-shakeable import.
|
||||
- ADDED: package root barrel re-exporting `OpenExchangeRatesProvider` and the
|
||||
type alias `OpenExchangeRatesProviderConfig` (= `ApiKeyConfig` from
|
||||
`@suaveplan/currency-providers`).
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Historical-rate endpoint (`/historical/<date>.json`) — deferred to a
|
||||
follow-up change. The upstream slim refactor's `## Out of scope` block
|
||||
explicitly defers historical rates for every provider in the wave; this
|
||||
package keeps parity with that boundary.
|
||||
- Retry / backoff / rate-limit-header surfacing / OTel — deferred to the same
|
||||
cross-cutting follow-up that the slim core deferred them to.
|
||||
- The five other OXR endpoints (`/currencies.json`, `/usage.json`,
|
||||
`/convert/{value}/{from}/{to}`, `/ohlc.json`, `/time-series.json`) — the
|
||||
`CurrencyProvider` contract only exposes bulk-fetch via `/latest.json`; the
|
||||
rest are out of contract.
|
||||
|
||||
## Source mapping
|
||||
|
||||
This change derives from `../suaveplan/enterprise/currency-providers.md` and
|
||||
the upstream OXR implementation at
|
||||
`../suaveplan/packages/finance/currency-providers/src/fiat/open-exchange-rates.ts`.
|
||||
|
||||
| Requirement | Source lines | Notes |
|
||||
|---|---|---|
|
||||
| OXR-001 implements `CurrencyProvider` with `supportsFiat=true`, `supportsCrypto=false`, `name="open-exchange-rates"` | `open-exchange-rates.ts` L15-L19 | verbatim, plus tier purity (no Node-only APIs) |
|
||||
| OXR-002 fetches `/latest.json?app_id=<key>&base=<base>` via `httpRequest()` | `open-exchange-rates.ts` L28-L37 | reworded — upstream called `fetch()` directly; genesis routes through slim-core `httpRequest()` per slim spec |
|
||||
| OXR-003 maps response `{ base, rates, timestamp }` to `RateFetchResult` with `timestamp * 1000` to convert OXR's Unix seconds to ms | `open-exchange-rates.ts` L39-L43, L56-L60 | verbatim |
|
||||
| OXR-004 honours `RateFetchOptions.targets` by filtering `rates` map | `open-exchange-rates.ts` L46-L54 | verbatim |
|
||||
| OXR-005 free-tier limits base to USD; explicit non-USD base raises `UNSUPPORTED_BASE` | enterprise audit L12, OXR.md L11-L13, L41-L43 | extended — upstream silently let upstream API reject; genesis pre-rejects with a stable error code (the `UNSUPPORTED_BASE` code is already registered in slim core) |
|
||||
|
||||
### Requirements with no source line
|
||||
|
||||
| Requirement | Justification |
|
||||
|---|---|
|
||||
| OXR-006 raises `MISSING_CONFIG` on empty `apiKey` | `MISSING_CONFIG` code exists in slim-core registry per archived slim spec; surfacing it from constructor is the genesis baseline for all api-key providers |
|
||||
| OXR-007 raises `PARSE_ERROR` when upstream payload is not the expected shape | Defensive validation — upstream library threw a generic JSON-parse error; genesis maps to typed `PARSE_ERROR` |
|
||||
| OXR-008 raises `UPSTREAM_ERROR` when upstream returns 2xx with `error: true` body | OXR documented `error/status/message` payload shape; upstream library missed it. Genesis surfaces typed `UPSTREAM_ERROR` with `upstreamMessage` populated |
|
||||
| OXR-009 raises `UNKNOWN_CURRENCY` when `options.targets` requests a code not in upstream response | `UNKNOWN_CURRENCY` code exists in slim-core registry; surfacing it instead of silently dropping the target gives callers a typed handle |
|
||||
| OXR-010 honours `RateFetchOptions.signal` for cancellation | Universal-tier abort propagation — upstream did not honour signal at all; genesis composes via `httpRequest()` which forwards to the injected fetch |
|
||||
| OXR-011 universal-tier purity — no Node APIs, no DOM APIs | Genesis-wide tier policy |
|
||||
| OXR-012 100% test coverage, sibling `.md` on every implementation file | Genesis-wide quality policy |
|
||||
|
||||
## Verification approach
|
||||
|
||||
Every requirement carries a `**Verified by:**` line in the spec delta pointing
|
||||
at concrete `it(...)` titles. The test surface is:
|
||||
|
||||
- `src/provider/provider.test.ts` — covers OXR-001 through OXR-011 with
|
||||
injected `FetchLike` recording calls and returning canned responses.
|
||||
- `tests/dep-graph.test.ts` — pins dependency invariants (only consumes
|
||||
`@suaveplan/currency`, `@suaveplan/currency-providers`, `@suaveplan/error`,
|
||||
`@suaveplan/types`).
|
||||
|
||||
## Migration impact
|
||||
|
||||
None — additive only. No current consumers reference this package; the slim
|
||||
core's `## Affected dependents` block listed this package as one of nine
|
||||
sibling consumers that would land in the follow-up wave.
|
||||
Reference in New Issue
Block a user