207 lines
10 KiB
Markdown
207 lines
10 KiB
Markdown
# Slim @suaveplan/currency-providers to a thin plugin core
|
|
|
|
**Package**: `@suaveplan/currency-providers`
|
|
**Status**: PROPOSED
|
|
**Type**: refactor (modification of the just-shipped 2026-05-17 migration)
|
|
|
|
- **Design**: ./design.md
|
|
- **Spec delta**: ./specs/currency-providers/spec.md
|
|
|
|
## Why
|
|
|
|
The first shipped shape of `@suaveplan/currency-providers` bundled nine concrete
|
|
providers (ECB, Fixer, Open Exchange Rates, Binance, CoinGecko, CoinMarketCap,
|
|
CryptoCompare, Kraken, Virtual) into a single npm artefact. That bundling
|
|
violates the foundation-with-sibling-plugins shape that `@suaveplan/network`
|
|
and `@suaveplan/drive` established for genesis: every concrete plugin is its
|
|
own publishable package so consumers install only what they wire in, and each
|
|
plugin can iterate on its own version cadence without a coordinated bump of
|
|
the umbrella.
|
|
|
|
Consumers that want only ECB pay the install + tree-shake-graph cost of eight
|
|
unused providers. Worse, an upstream API change in (say) CoinMarketCap forces
|
|
a rev of the umbrella that every other provider also ships under. Splitting
|
|
into `currency-providers` (the contract + registry + shared HTTP helper) and
|
|
nine `currency-provider-<name>` siblings (in the follow-up wave) gives
|
|
consumers granular installs, gives each provider an independent surface to
|
|
evolve, and matches the established `network-plugin-*` / `drive-plugin-*`
|
|
shape.
|
|
|
|
This change is the **slim**: it deletes the nine concrete provider modules
|
|
from this package, adds the `CurrencyProviderRegistry` + `defineCurrencyProvider`
|
|
factory + `PROVIDER_NOT_REGISTERED` code that the nine sibling packages will
|
|
consume, and factors the small HTTP utilities (`httpRequest`,
|
|
`composeAbortSignal`) that every provider repeated by hand into a single
|
|
shared helper. The nine sibling packages land in the follow-up wave; each
|
|
will depend on this slimmed core and re-implement only its own provider.
|
|
|
|
## What changes
|
|
|
|
### REMOVED
|
|
|
|
- `EcbProvider` — moves to `@suaveplan/currency-provider-ecb`.
|
|
- `FixerProvider` — moves to `@suaveplan/currency-provider-fixer`.
|
|
- `OpenExchangeRatesProvider` — moves to `@suaveplan/currency-provider-open-exchange-rates`.
|
|
- `BinanceProvider` — moves to `@suaveplan/currency-provider-binance`.
|
|
- `CoinGeckoProvider` + `getCoinGeckoId` — move to `@suaveplan/currency-provider-coingecko`.
|
|
- `CoinMarketCapProvider` — moves to `@suaveplan/currency-provider-coinmarketcap`.
|
|
- `CryptoCompareProvider` — moves to `@suaveplan/currency-provider-cryptocompare`.
|
|
- `KrakenProvider` — moves to `@suaveplan/currency-provider-kraken`.
|
|
- `VirtualCurrencyProvider` + `VirtualCurrencyProviderConfig` — move to
|
|
`@suaveplan/currency-provider-virtual`.
|
|
- All nine `./providers/<name>/<name>` subpath exports drop from
|
|
`package.json`.
|
|
|
|
### ADDED
|
|
|
|
- `CurrencyProviderRegistry` — in-memory registry of `CurrencyProvider`
|
|
instances keyed by `name`. Methods: `register(provider)`, `get(name)`,
|
|
`has(name)`, `list()`, `unregister(name)`. `get(unknown)` throws
|
|
`CurrencyProviderError(PROVIDER_NOT_REGISTERED)`; `register(duplicate)`
|
|
throws `CurrencyProviderError(PROVIDER_ALREADY_REGISTERED)`.
|
|
- `defineCurrencyProvider(definition)` — factory that returns a stable-shape
|
|
`CurrencyProvider`. Lets sibling packages declare a provider as a plain
|
|
object literal without re-implementing the class boilerplate.
|
|
- `httpRequest()` shared helper — issues a request via the injected
|
|
`FetchLike`, applies `HttpFetchOptions.timeoutMs` and `headers`, composes
|
|
the caller's `AbortSignal` with the timeout signal, throws
|
|
`CurrencyProviderError(HTTP_ERROR)` on non-2xx with `status` populated.
|
|
- `composeAbortSignal()` shared helper — merges an optional caller signal
|
|
with an optional timeout-derived signal into a single `AbortSignal`.
|
|
- `PROVIDER_NOT_REGISTERED` and `PROVIDER_ALREADY_REGISTERED` codes added to
|
|
`CURRENCY_PROVIDER_CODES`.
|
|
|
|
### KEPT (unchanged)
|
|
|
|
- `ApiKeyConfig`, `NoKeyConfig`, `HttpFetchOptions`, `ProviderConfigBase`
|
|
shared config types — every sibling provider extends these.
|
|
- `CURRENCY_PROVIDER_CODES` registry (now 8 codes: original 6 plus the 2
|
|
registry codes) + `CurrencyProviderError` class with
|
|
`providerName` / `status` / `upstreamMessage` discriminants.
|
|
- Universal-tier purity, `sideEffects: false`, explicit named barrels,
|
|
`@suaveplan/testing/runner` test imports, 100/100/100/100 coverage gate.
|
|
|
|
## Source mapping
|
|
|
|
This refactor is a MODIFICATION of `openspec/archive/2026-05-17-add-currency-providers/`.
|
|
The provider implementations now slated for removal are at:
|
|
|
|
| Removed module | Source in just-shipped archive |
|
|
|---|---|
|
|
| `EcbProvider` | `src/providers/ecb/ecb.ts` L1-L111 (per archived spec L84-L122) |
|
|
| `FixerProvider` | `src/providers/fixer/fixer.ts` (per archived spec L126-L158) |
|
|
| `OpenExchangeRatesProvider` | `src/providers/open-exchange-rates/open-exchange-rates.ts` (per archived spec L162-L187) |
|
|
| `BinanceProvider` | `src/providers/binance/binance.ts` (per archived spec L191-L217) |
|
|
| `CoinGeckoProvider` + `getCoinGeckoId` | `src/providers/coingecko/coingecko.ts` (per archived spec L221-L249) |
|
|
| `CoinMarketCapProvider` | `src/providers/coinmarketcap/coinmarketcap.ts` (per archived spec L253-L276) |
|
|
| `CryptoCompareProvider` | `src/providers/cryptocompare/cryptocompare.ts` (per archived spec L280-L299) |
|
|
| `KrakenProvider` | `src/providers/kraken/kraken.ts` (per archived spec L303-L325) |
|
|
| `VirtualCurrencyProvider` | `src/providers/virtual/virtual.ts` (per archived spec L329-L375) |
|
|
|
|
The new registry / factory / HTTP helper surface is genesis-original, modelled
|
|
on the established foundation-with-sibling-plugins shapes:
|
|
|
|
| New module | Reference shape |
|
|
|---|---|
|
|
| `CurrencyProviderRegistry` | `packages/network/network/src/manager/` `PluginManager` |
|
|
| `defineCurrencyProvider` | `packages/drive/drive/src/plugin/` factory pattern |
|
|
| `httpRequest` + `composeAbortSignal` | extracted from the duplicated `fetchImpl(url); if (!response.ok) throw HTTP_ERROR` block present in every provider being removed |
|
|
|
|
The enterprise audit reference is `../suaveplan/enterprise/currency-providers.md`;
|
|
the slim shape was not present upstream — it is a genesis-native architectural
|
|
correction.
|
|
|
|
## Impact
|
|
|
|
### Affected dependents
|
|
|
|
Run `bunx turbo ls --filter=...@suaveplan/currency-providers` to enumerate.
|
|
At time of writing there are no current dependents (the package shipped on
|
|
2026-05-17 and the commerce stack downstream is still pending migration):
|
|
|
|
- No current dependents (verified by `bunx turbo ls --filter=...@suaveplan/currency-providers`).
|
|
|
|
The future wave of sibling provider packages will all depend on this slimmed
|
|
core. Each is listed here so the next reviewer can see the blast radius the
|
|
follow-up wave will land in:
|
|
|
|
- `@suaveplan/currency-provider-ecb` — [x] consumes registry / factory / httpRequest / configs / error
|
|
- `@suaveplan/currency-provider-fixer` — [x] consumes registry / factory / httpRequest / configs / error
|
|
- `@suaveplan/currency-provider-open-exchange-rates` — [x] consumes registry / factory / httpRequest / configs / error
|
|
- `@suaveplan/currency-provider-binance` — [x] consumes registry / factory / httpRequest / configs / error
|
|
- `@suaveplan/currency-provider-coingecko` — [x] consumes registry / factory / httpRequest / configs / error
|
|
- `@suaveplan/currency-provider-coinmarketcap` — [x] consumes registry / factory / httpRequest / configs / error
|
|
- `@suaveplan/currency-provider-cryptocompare` — [x] consumes registry / factory / httpRequest / configs / error
|
|
- `@suaveplan/currency-provider-kraken` — [x] consumes registry / factory / httpRequest / configs / error
|
|
- `@suaveplan/currency-provider-virtual` — [x] consumes registry / factory / configs / error (no HTTP)
|
|
- `@suaveplan/currency` — [ ] tests run / [x] docs reviewed / [x] no action needed (the `CurrencyProvider` contract lives in `@suaveplan/currency` and is unchanged by this refactor; the slim core depends on it, not the other way around)
|
|
|
|
## Migration
|
|
|
|
The nine concrete provider classes are gone from `@suaveplan/currency-providers`
|
|
and now live in dedicated sibling packages. Consumers must install the
|
|
specific provider package they need and either instantiate it directly or
|
|
register it with the new `CurrencyProviderRegistry`.
|
|
|
|
### Before
|
|
|
|
```ts
|
|
import { CurrencyConverter } from "@suaveplan/currency";
|
|
import { EcbProvider, CoinGeckoProvider } from "@suaveplan/currency-providers";
|
|
|
|
const converter = new CurrencyConverter({
|
|
provider: new EcbProvider(),
|
|
fallbackProviders: [new CoinGeckoProvider()],
|
|
});
|
|
```
|
|
|
|
### After
|
|
|
|
```ts
|
|
import { CurrencyConverter } from "@suaveplan/currency";
|
|
import { CurrencyProviderRegistry } from "@suaveplan/currency-providers";
|
|
import { EcbProvider } from "@suaveplan/currency-provider-ecb";
|
|
import { CoinGeckoProvider } from "@suaveplan/currency-provider-coingecko";
|
|
|
|
const registry = new CurrencyProviderRegistry();
|
|
registry.register(new EcbProvider());
|
|
registry.register(new CoinGeckoProvider());
|
|
|
|
const converter = new CurrencyConverter({
|
|
provider: registry.get("ecb"),
|
|
fallbackProviders: [registry.get("coingecko")],
|
|
});
|
|
```
|
|
|
|
Direct instantiation without the registry is equally supported — the registry
|
|
is a convenience for callers that wire many providers and want a single
|
|
lookup surface. Sibling packages remain `CurrencyProvider`-compatible so the
|
|
old `new EcbProvider()` form still works once the sibling package is installed.
|
|
|
|
Consumers should run this search command to find call sites that need to add
|
|
sibling-package imports:
|
|
|
|
```bash
|
|
grep -rln "from ['\"]@suaveplan/currency-providers['\"]" \
|
|
packages/ apps/ src/ 2>/dev/null | grep -v "/dist/\|/coverage/"
|
|
```
|
|
|
|
Every hit that uses one of `EcbProvider`, `FixerProvider`,
|
|
`OpenExchangeRatesProvider`, `BinanceProvider`, `CoinGeckoProvider`,
|
|
`CoinMarketCapProvider`, `CryptoCompareProvider`, `KrakenProvider`,
|
|
`VirtualCurrencyProvider`, `VirtualCurrencyProviderConfig`, or
|
|
`getCoinGeckoId` needs its import rewritten to the matching
|
|
`@suaveplan/currency-provider-<name>` package.
|
|
|
|
## Out of scope
|
|
|
|
- The nine sibling `@suaveplan/currency-provider-<name>` packages — each
|
|
ships under its own OpenSpec change in the follow-up wave per AGENTS.md
|
|
§0c "Multi-package / cross-cutting feature sets" (one change per package;
|
|
cross-cutting design lives once, in this package's `design.md`).
|
|
- Rate-limit / retry / historical-rate / WebSocket / on-chain oracle /
|
|
aggregator / health-monitor / OTel surfaces — same deferral as the
|
|
2026-05-17 archive's `## Out of scope`. Nothing in this slim refactor
|
|
changes those plans; once those wrappers land they consume the
|
|
`CurrencyProvider` contract through the registry exactly the same way.
|