first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,75 @@
# @suaveplan/currency-provider-cryptocompare - Universal CryptoCompare crypto-rates provider for @suaveplan/currency. Sibling provider package on the slim @suaveplan/currency-providers core (httpRequest, defineCurrencyProvider, CurrencyProviderError, NoKeyConfig). Optional API key — free tier works keyless; key boosts the rate limit and is sent via `authorization: Apikey <key>` header.
Universal CryptoCompare crypto-rates provider for `@suaveplan/currency`.
Sibling provider package on top of the slim `@suaveplan/currency-providers`
core (`httpRequest`, `CurrencyProviderError`, `NoKeyConfig`). Optional API
key — CryptoCompare's free tier works fully keyless; supplying a key boosts
the upstream rate limit and is sent on every request via the
`authorization: Apikey <key>` header.
<!-- begin:auto-docs -->
## Documentation tree
- [cryptocompare](./src/cryptocompare/cryptocompare.md) — `CryptoCompareProvider` implements the `CurrencyProvider` contract from `@suaveplan/currency` on top of CryptoCompare's…
- [cryptocompare](./src/cryptocompare/cryptocompare.md) — `CryptoCompareProvider` implements the `CurrencyProvider` contract from `@suaveplan/currency` on top of CryptoCompare's…
<!-- end:auto-docs -->
## Install
```bash
bun add @suaveplan/currency-provider-cryptocompare \
@suaveplan/currency \
@suaveplan/currency-providers
```
## Quick start
```ts
import { CurrencyConverter } from "@suaveplan/currency";
import { CryptoCompareProvider } from "@suaveplan/currency-provider-cryptocompare";
const provider = new CryptoCompareProvider({
apiKey: process.env.CRYPTOCOMPARE_API_KEY, // optional
});
const converter = new CurrencyConverter({
provider,
staleDurationMs: 60_000,
});
const conversion = await converter.convert(amount, "BTC");
```
Or register through the slim-core registry:
```ts
import { CurrencyProviderRegistry } from "@suaveplan/currency-providers";
import { CryptoCompareProvider } from "@suaveplan/currency-provider-cryptocompare";
const registry = new CurrencyProviderRegistry();
registry.register(new CryptoCompareProvider());
const provider = registry.get("cryptocompare");
```
## Modules
- [`cryptocompare/cryptocompare`](./src/cryptocompare/cryptocompare.md) —
`CryptoCompareProvider` class, `CryptoCompareConfig` interface, error
mapping for `HTTP_ERROR` / `PARSE_ERROR` / `UPSTREAM_ERROR` /
`UNKNOWN_CURRENCY`.
## Errors
Every failure path raises `CurrencyProviderError` from the slim core:
| Code | Raised when |
|---|---|
| `CURRENCY_PROVIDER.HTTP_ERROR` | Non-2xx HTTP response (shared `httpRequest`) |
| `CURRENCY_PROVIDER.PARSE_ERROR` | Body not valid JSON, or payload not a JSON object |
| `CURRENCY_PROVIDER.UPSTREAM_ERROR` | Payload carries `Response: "Error"` |
| `CURRENCY_PROVIDER.UNKNOWN_CURRENCY` | No requested symbol produced a finite positive quote |
Dispatch on `error.code`, not the message string. The instance also
carries `providerName: "cryptocompare"`, optional `status` (for HTTP_ERROR),
and optional `upstreamMessage` (for UPSTREAM_ERROR).

View File

@@ -0,0 +1,146 @@
# `cryptocompare` — CryptoCompare crypto-rates provider
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`CryptoCompareProvider` implements the `CurrencyProvider` contract from
`@suaveplan/currency` on top of CryptoCompare's public
`min-api.cryptocompare.com/data/pricemulti` endpoint. It returns inverted
spot prices (`1 / price`) for a configurable set of crypto symbols against
a fiat or crypto base currency, suitable for direct registration with
`CurrencyProviderRegistry` or instantiation into `CurrencyConverter`.
The provider is sibling to the slim `@suaveplan/currency-providers` core:
all HTTP plumbing (timeout / abort composition / typed-error mapping) lives
in the shared `httpRequest` helper, the typed error class and code registry
come from `CurrencyProviderError` + `CURRENCY_PROVIDER_CODES`, and the
config shape extends `NoKeyConfig` with one extra optional field. The
provider is universal — no Node, DOM, or framework dependencies — so
it runs untouched in browsers, Workers, Bun, and Node 18+.
## Features
- Sibling crypto-rates provider — `name: "cryptocompare"`,
`supportsCrypto: true`, `supportsFiat: false`
- Optional `apiKey` — free tier works fully keyless; key boosts the
upstream rate limit and is sent via `authorization: Apikey <key>` header
- 15 sensible default crypto symbols (BTC, ETH, SOL, USDT, USDC, BNB, XRP,
ADA, DOGE, DOT, AVAX, LINK, UNI, ATOM, LTC) when callers omit `targets`
- Injectable `FetchLike` from `@suaveplan/types` — tests pass a controlled
closure without `vi.stubGlobal('fetch', ...)`
- Typed errors via the slim core registry: `HTTP_ERROR`, `PARSE_ERROR`,
`UPSTREAM_ERROR`, `UNKNOWN_CURRENCY` — every failure path carries
`providerName: "cryptocompare"` for log dispatch
- Caller `AbortSignal` from `RateFetchOptions.signal` is composed through
`httpRequest`; whitespace-only `apiKey` strings are treated as absent
## Usage: Basic Example
```ts
import { CryptoCompareProvider } from "@suaveplan/currency-provider-cryptocompare";
const provider = new CryptoCompareProvider();
const result = await provider.fetchRates({ base: "USD" });
console.log(result.rates.BTC); // 1 / spot price of BTC in USD
```
## Usage: Advanced Example
Register the provider with `CurrencyProviderRegistry`, use the keyed
upstream for higher rate limits, and dispatch on typed errors:
```ts
import { CurrencyConverter } from "@suaveplan/currency";
import {
CURRENCY_PROVIDER_CODES,
CurrencyProviderError,
CurrencyProviderRegistry,
} from "@suaveplan/currency-providers";
import { CryptoCompareProvider } from "@suaveplan/currency-provider-cryptocompare";
const registry = new CurrencyProviderRegistry();
registry.register(
new CryptoCompareProvider({
apiKey: process.env.CRYPTOCOMPARE_API_KEY,
baseUrl: "https://min-api.cryptocompare.com/data",
}),
);
const converter = new CurrencyConverter({
provider: registry.get("cryptocompare"),
staleDurationMs: 60_000,
});
try {
const conversion = await converter.convert(amount, "BTC");
console.log(conversion);
} catch (error) {
if (
error instanceof CurrencyProviderError &&
error.code === CURRENCY_PROVIDER_CODES.UPSTREAM_ERROR
) {
console.warn("CryptoCompare upstream:", error.upstreamMessage);
} else {
throw error;
}
}
```
## API Reference
- `interface CryptoCompareConfig extends NoKeyConfig`
- `apiKey?: string` — optional API key. Sent on every request via
`authorization: Apikey <key>` when present and non-whitespace.
Whitespace-only strings are treated as absent.
- `baseUrl?: string` — overrides the default
`https://min-api.cryptocompare.com/data` endpoint.
- `fetch?: FetchLike` — injectable WHATWG-shape fetch. Defaults to
`globalThis.fetch`.
- `class CryptoCompareProvider implements CurrencyProvider`
- `name: "cryptocompare"`, `supportsCrypto: true`,
`supportsFiat: false`.
- `constructor(config?: CryptoCompareConfig)` — every config field is
optional. Constructing without a config wires `globalThis.fetch`, the
default base URL, and no API key.
- `fetchRates(options?: RateFetchOptions): Promise<RateFetchResult>` —
issues a single `GET <baseUrl>/pricemulti?fsyms=<targets>&tsyms=<base>`,
inverts each price, and returns `{ base, rates, timestamp }`.
`options.base` defaults to `"USD"`; `options.targets` defaults to the
15 built-in symbols when omitted or empty; `options.signal` is
forwarded into the shared `httpRequest` helper.
- Raised `CurrencyProviderError` codes:
- `HTTP_ERROR` — non-2xx response (raised by shared `httpRequest`).
- `PARSE_ERROR` — body is not valid JSON, or the parsed payload is not
a JSON object.
- `UPSTREAM_ERROR` — payload carries `Response: "Error"`; the
`Message` field (or `"unknown upstream error"` when absent) is copied
to `error.upstreamMessage`.
- `UNKNOWN_CURRENCY` — no requested symbol produced a finite positive
quote in the requested base currency.
## Implementation Notes
- CryptoCompare's `pricemulti` endpoint returns HTTP 200 even for
per-symbol upstream errors — the payload carries `Response: "Error"` and
a human-readable `Message`. The provider distinguishes that from a
successful (possibly empty) quote map and surfaces it as
`UPSTREAM_ERROR` rather than masquerading as `HTTP_ERROR`.
- The free tier permits ~100,000 calls per month without a key; supplying
an `apiKey` boosts that quota. The provider authenticates via the
`authorization: Apikey <key>` header per CryptoCompare's documentation
(Apikey scheme, not Bearer). Empty or whitespace-only keys are silently
treated as absent so callers can pass `process.env.CRYPTOCOMPARE_API_KEY`
directly without a guard.
- Prices are inverted (`1 / price`) so the result map answers "one unit of
`base` is worth X units of `symbol`" — consistent with every other
sibling provider. Non-finite or non-positive quotes are skipped.
- The default symbol list covers the top 15 crypto assets by market cap at
package-publish time; callers wanting a different set always pass
`targets` explicitly. The defaults exist only as a developer-friendly
fallback for ad-hoc usage.
- The provider does not wrap retry, backoff, or rate-limit semantics —
compose `@suaveplan/retry` / future `RateLimitedProvider` wrappers at a
higher layer, exactly the same way as every other sibling provider.