Move docs/ package mirror into document-navigator plugin
Relocate the ~6k-file docs tree from the repo root into plugins/document-navigator/docs/ so the plugin is self-contained and portable. docs/superpowers/ (specs and plans) stays at the repo root. Moved via cp+rm rather than git mv because directory rename() fails with EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the identical-content files at commit time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
# @suaveplan/currency-provider-coinmarketcap - CoinMarketCap crypto-rates provider for @suaveplan/currency — sibling of @suaveplan/currency-providers. Authenticates via X-CMC_PRO_API_KEY header against the /cryptocurrency/quotes/latest endpoint. Universal (no node:/bun:/DOM imports).
|
||||
|
||||
CoinMarketCap crypto-rates sibling provider for `@suaveplan/currency`. One
|
||||
of the nine `@suaveplan/currency-provider-<name>` sibling packages spun
|
||||
out of the 2026-05-17 slim refactor of `@suaveplan/currency-providers`.
|
||||
Consumes the slim core's `CurrencyProviderError` registry, `ApiKeyConfig`
|
||||
type, and shared `httpRequest()` HTTP helper; talks to
|
||||
`pro-api.coinmarketcap.com` via the `X-CMC_PRO_API_KEY` header.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [coinmarketcap](./src/coinmarketcap/coinmarketcap.md) — `CoinMarketCapProvider` is the `@suaveplan/currency-providers` sibling that fetches cryptocurrency spot prices from the…
|
||||
- [coinmarketcap](./src/coinmarketcap/coinmarketcap.md) — `CoinMarketCapProvider` is the `@suaveplan/currency-providers` sibling that fetches cryptocurrency spot prices from the…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
bun add @suaveplan/currency-provider-coinmarketcap @suaveplan/currency-providers @suaveplan/currency
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
```ts
|
||||
import { CurrencyConverter } from "@suaveplan/currency";
|
||||
import { CurrencyProviderRegistry } from "@suaveplan/currency-providers";
|
||||
import { CoinMarketCapProvider } from "@suaveplan/currency-provider-coinmarketcap";
|
||||
|
||||
const registry = new CurrencyProviderRegistry();
|
||||
registry.register(
|
||||
new CoinMarketCapProvider({ apiKey: process.env.CMC_API_KEY ?? "" })
|
||||
);
|
||||
|
||||
const converter = new CurrencyConverter({
|
||||
provider: registry.get("coinmarketcap"),
|
||||
});
|
||||
|
||||
const rate = await converter.getRate("BTC", "USD");
|
||||
```
|
||||
|
||||
Direct instantiation works equally well:
|
||||
|
||||
```ts
|
||||
import { CurrencyConverter } from "@suaveplan/currency";
|
||||
import { CoinMarketCapProvider } from "@suaveplan/currency-provider-coinmarketcap";
|
||||
|
||||
const converter = new CurrencyConverter({
|
||||
provider: new CoinMarketCapProvider({ apiKey: process.env.CMC_API_KEY ?? "" }),
|
||||
});
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Implements `CurrencyProvider` (`name = "coinmarketcap"`,
|
||||
`supportsCrypto = true`, `supportsFiat = false`)
|
||||
- `ApiKeyConfig` constructor — `apiKey` required, `baseUrl` optional
|
||||
(defaults to `https://pro-api.coinmarketcap.com/v1`), injectable
|
||||
`FetchLike` from `@suaveplan/types`
|
||||
- Authenticated via the upstream `X-CMC_PRO_API_KEY` header; `Accept`
|
||||
set to `application/json` on every request
|
||||
- Default target list covers the top 10 crypto by market cap
|
||||
(`BTC,ETH,SOL,USDT,USDC,BNB,XRP,ADA,DOGE,DOT`); override via
|
||||
`options.targets`
|
||||
- Default `base` is `USD`; any fiat conversion currency works via
|
||||
`options.base`
|
||||
- Prices are inverted (`1 / price`) into the converter's expected
|
||||
`1 fiat = X crypto` form
|
||||
- Typed errors via `CurrencyProviderError` —
|
||||
`MISSING_CONFIG` (empty `apiKey`), `HTTP_ERROR` (non-2xx),
|
||||
`PARSE_ERROR` (invalid JSON / missing data), `UPSTREAM_ERROR`
|
||||
(`status.error_code` populated), `UNKNOWN_CURRENCY` (no symbol
|
||||
resolved). Every error carries `providerName: "coinmarketcap"`
|
||||
- HTTP plumbing (abort-signal composition, timeout, non-2xx mapping)
|
||||
delegated to the slim core's `httpRequest()` — uniform with every
|
||||
other sibling provider
|
||||
- Universal (`sideEffects: false`); 100/100/100/100 istanbul
|
||||
coverage; 2 subpath exports
|
||||
|
||||
## Modules
|
||||
|
||||
- [`src/coinmarketcap/coinmarketcap.md`](./src/coinmarketcap/coinmarketcap.md)
|
||||
— `CoinMarketCapProvider`
|
||||
|
||||
## Status
|
||||
|
||||
`0.1.0` (pre-graduation). Sibling provider landed alongside the
|
||||
2026-05-17 slim refactor of `@suaveplan/currency-providers`. See
|
||||
the design proposal for
|
||||
the change record.
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
# `CoinMarketCapProvider` — crypto-rates sibling provider
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`CoinMarketCapProvider` is the
|
||||
`@suaveplan/currency-providers` sibling that fetches cryptocurrency
|
||||
spot prices from the CoinMarketCap professional REST API
|
||||
(`pro-api.coinmarketcap.com`). It implements `CurrencyProvider` from
|
||||
`@suaveplan/currency` so it slots directly into `CurrencyConverter`
|
||||
either by direct instantiation (`new CurrencyConverter({ provider: new
|
||||
CoinMarketCapProvider(...) })`) or by registration with
|
||||
`CurrencyProviderRegistry`. The class exists so consumers that need
|
||||
CoinMarketCap as a primary or fallback rate source install exactly one
|
||||
provider package instead of pulling in the eight other sibling
|
||||
providers they do not wire in.
|
||||
|
||||
The class is crypto-only — `supportsCrypto = true`, `supportsFiat =
|
||||
false` — because the upstream `/cryptocurrency/quotes/latest` endpoint
|
||||
is a crypto quotes feed; the request still accepts any fiat conversion
|
||||
currency via the `convert` query parameter, so a USD-base, EUR-base, or
|
||||
GBP-base call is equally valid. Consumers that need fiat rates should
|
||||
register a fiat sibling alongside (`currency-provider-ecb`,
|
||||
`currency-provider-fixer`, `currency-provider-open-exchange-rates`).
|
||||
|
||||
## Features
|
||||
|
||||
- Implements `CurrencyProvider` with stable identity (`name =
|
||||
"coinmarketcap"`, `supportsCrypto = true`, `supportsFiat = false`).
|
||||
- Constructor accepts `ApiKeyConfig` (`apiKey`, optional `baseUrl`,
|
||||
optional injectable `FetchLike`); empty / whitespace `apiKey`
|
||||
immediately raises `CurrencyProviderError(MISSING_CONFIG)`.
|
||||
- `X-CMC_PRO_API_KEY` + `Accept: application/json` headers carried on
|
||||
every request through the shared `httpRequest()` helper, so abort
|
||||
signals, timeouts, and HTTP error mapping are uniform with every
|
||||
other sibling provider.
|
||||
- Default target list covers the top 10 cryptocurrencies by market cap
|
||||
(`BTC,ETH,SOL,USDT,USDC,BNB,XRP,ADA,DOGE,DOT`); callers can override
|
||||
via `options.targets` to query any supported symbol set.
|
||||
- Default `base` is `USD`; any fiat conversion currency supported by
|
||||
CoinMarketCap is accepted via `options.base`.
|
||||
- Prices are inverted from CoinMarketCap's `1 crypto = X fiat` form to
|
||||
the converter's expected `1 fiat = (1 / X) crypto` form before being
|
||||
written into `RateFetchResult.rates`.
|
||||
- Typed error mapping — `HTTP_ERROR` (non-2xx), `PARSE_ERROR` (invalid
|
||||
JSON / missing `data` field / non-object data), `UPSTREAM_ERROR`
|
||||
(`status.error_code` populated), `UNKNOWN_CURRENCY` (no requested
|
||||
symbol resolved), `MISSING_CONFIG` (empty `apiKey`) — every error
|
||||
carries `providerName: "coinmarketcap"`.
|
||||
- Universal — no `node:` / `bun:` / DOM imports; runs unchanged
|
||||
under Node 18+, Bun, modern browsers, and Web Workers via the
|
||||
injectable `FetchLike` seam.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { CurrencyConverter } from "@suaveplan/currency";
|
||||
import { CoinMarketCapProvider } from "@suaveplan/currency-provider-coinmarketcap";
|
||||
|
||||
const provider = new CoinMarketCapProvider({
|
||||
apiKey: process.env.CMC_API_KEY ?? "",
|
||||
});
|
||||
|
||||
const converter = new CurrencyConverter({ provider });
|
||||
const rate = await converter.getRate("BTC", "USD");
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Inject a `FetchLike` for testing, point at the CoinMarketCap sandbox,
|
||||
restrict the target list, and forward an `AbortSignal` for cancellation:
|
||||
|
||||
```ts
|
||||
import type { FetchLike } from "@suaveplan/types";
|
||||
import { CurrencyProviderRegistry } from "@suaveplan/currency-providers";
|
||||
import { CoinMarketCapProvider } from "@suaveplan/currency-provider-coinmarketcap";
|
||||
|
||||
const recordingFetch: FetchLike = async (url, init) => {
|
||||
console.log("→", url);
|
||||
return await fetch(url, init);
|
||||
};
|
||||
|
||||
const provider = new CoinMarketCapProvider({
|
||||
apiKey: process.env.CMC_SANDBOX_KEY ?? "",
|
||||
baseUrl: "https://sandbox-api.coinmarketcap.com/v1",
|
||||
fetch: recordingFetch,
|
||||
});
|
||||
|
||||
const registry = new CurrencyProviderRegistry();
|
||||
registry.register(provider);
|
||||
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), 2_000);
|
||||
try {
|
||||
const result = await registry.get("coinmarketcap").fetchRates({
|
||||
base: "EUR",
|
||||
targets: ["BTC", "ETH", "SOL"],
|
||||
signal: controller.signal,
|
||||
});
|
||||
console.log(result.rates);
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `class CoinMarketCapProvider implements CurrencyProvider`
|
||||
|
||||
- `readonly name: "coinmarketcap"`
|
||||
- `readonly supportsCrypto: true`
|
||||
- `readonly supportsFiat: false`
|
||||
- `constructor(config: ApiKeyConfig)` — `config.apiKey` is required;
|
||||
whitespace-only and empty strings raise
|
||||
`CurrencyProviderError(MISSING_CONFIG)`. `config.baseUrl` defaults to
|
||||
`https://pro-api.coinmarketcap.com/v1`. `config.fetch` defaults to
|
||||
`globalThis.fetch`.
|
||||
- `fetchRates(options?: RateFetchOptions): Promise<RateFetchResult>` —
|
||||
issues `GET <baseUrl>/cryptocurrency/quotes/latest?symbol=<csv>&convert=<base>`
|
||||
through `@suaveplan/currency-providers`'s `httpRequest()`. Returns
|
||||
`{ base, rates, timestamp }` with rates inverted into base-per-unit
|
||||
form. Forwards `options.signal` to the underlying fetch.
|
||||
|
||||
### Error codes (all from `CURRENCY_PROVIDER_CODES`)
|
||||
|
||||
- `MISSING_CONFIG` — empty / whitespace `apiKey` at construction.
|
||||
- `HTTP_ERROR` — non-2xx HTTP response (raised by `httpRequest()`).
|
||||
- `PARSE_ERROR` — invalid JSON, non-object payload, or missing `data`.
|
||||
- `UPSTREAM_ERROR` — payload `status.error_code` populated and non-zero.
|
||||
- `UNKNOWN_CURRENCY` — none of the requested symbols resolved.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The default target list (`BTC,ETH,SOL,USDT,USDC,BNB,XRP,ADA,DOGE,DOT`)
|
||||
mirrors the upstream `../suaveplan/...coinmarketcap.ts` source —
|
||||
these are the top ten cryptocurrencies by market cap and are the
|
||||
same set every consumer of the upstream package received by default.
|
||||
- Price inversion (`1 / price`) is applied per-symbol so a symbol with
|
||||
a missing, non-numeric, or non-positive `price` is silently dropped
|
||||
from the result. If the upstream returns `{ data: {} }` or every
|
||||
symbol fails the numeric guard, the whole call raises
|
||||
`UNKNOWN_CURRENCY` — the converter then falls back to its next
|
||||
registered provider rather than caching a zero-entry result.
|
||||
- `status.error_code` is treated as "present and non-zero" — many
|
||||
CoinMarketCap success responses carry `status.error_code: 0`, which
|
||||
is not a real error. The dedicated `assertNoUpstreamError` guard
|
||||
preserves that distinction.
|
||||
- The `parsePayload` step is intentionally lenient about the order of
|
||||
validation: when both `status.error_code` and a missing `data` field
|
||||
are present, the upstream error wins so consumers see the actionable
|
||||
`UPSTREAM_ERROR` (and `upstreamMessage`) rather than a generic
|
||||
`PARSE_ERROR`.
|
||||
- All HTTP plumbing — abort-signal composition, timeout derivation,
|
||||
non-2xx mapping to `HTTP_ERROR` — is delegated to
|
||||
`@suaveplan/currency-providers`'s `httpRequest()`. The provider
|
||||
never calls the injected `FetchLike` directly, so timeout / abort
|
||||
behaviour is identical to every other sibling.
|
||||
Reference in New Issue
Block a user