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,84 @@
|
||||
# @suaveplan/currency-provider-binance - Universal Binance public-ticker crypto-rates provider for @suaveplan/currency. Sibling provider package: implements the CurrencyProvider contract from @suaveplan/currency on top of the slim @suaveplan/currency-providers core (httpRequest, defineCurrencyProvider, CurrencyProviderError, NoKeyConfig). No API key required.
|
||||
|
||||
Binance public-ticker crypto-rates provider for `@suaveplan/currency`.
|
||||
Sibling package in the foundation-with-sibling-plugins shape: implements
|
||||
the `CurrencyProvider` contract on top of the slim
|
||||
`@suaveplan/currency-providers` core (`httpRequest`,
|
||||
`CurrencyProviderError`, `NoKeyConfig`). Universal — runs unchanged
|
||||
under Node 18+, Bun, modern browsers, and Web Workers. No API key
|
||||
required.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [binance](./src/binance/binance.md) — `BinanceProvider` is the Binance sibling implementation of the `CurrencyProvider` contract from `@suaveplan/currency`.…
|
||||
- [binance](./src/binance/binance.md) — `BinanceProvider` is the Binance sibling implementation of the `CurrencyProvider` contract from `@suaveplan/currency`.…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
bun add @suaveplan/currency-provider-binance @suaveplan/currency @suaveplan/currency-providers
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
```ts
|
||||
import { CurrencyConverter } from "@suaveplan/currency";
|
||||
import { CurrencyProviderRegistry } from "@suaveplan/currency-providers";
|
||||
import { BinanceProvider } from "@suaveplan/currency-provider-binance";
|
||||
|
||||
const registry = new CurrencyProviderRegistry();
|
||||
registry.register(new BinanceProvider());
|
||||
|
||||
const converter = new CurrencyConverter({
|
||||
provider: registry.get("binance"),
|
||||
staleDurationMs: 30_000,
|
||||
});
|
||||
|
||||
const rate = await converter.getRate("USDT", "BTC");
|
||||
```
|
||||
|
||||
Direct instantiation without the registry works exactly the same:
|
||||
|
||||
```ts
|
||||
const converter = new CurrencyConverter({
|
||||
provider: new BinanceProvider(),
|
||||
});
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Public Binance spot ticker endpoint (`/api/v3/ticker/price`) — free,
|
||||
no API key, no signature, no OAuth
|
||||
- Default base `USDT`; any suffix-style base supported by Binance works
|
||||
(`EUR`, `BTC`, `ETH`, etc.) by passing `base` to `fetchRates`
|
||||
- Optional `targets` filter raises typed `UNKNOWN_CURRENCY` for any
|
||||
target absent from the upstream listing — surfaces missing pairs
|
||||
eagerly rather than silently dropping them
|
||||
- Sanitised pricing: zero, negative, and non-finite ticker prices are
|
||||
skipped (delisted-but-published symbols stay out of the cache)
|
||||
- Rate inversion: every rate stored as `1 / price` so the cache answers
|
||||
"1 unit of base = N units of crypto"
|
||||
- Injectable `FetchLike` for tests, proxies, and edge-runtime adapters;
|
||||
optional `timeoutMs` flows through to the shared `httpRequest` helper
|
||||
- Typed errors via `CurrencyProviderError` carrying one of `HTTP_ERROR`,
|
||||
`PARSE_ERROR`, `UPSTREAM_ERROR`, `UNKNOWN_CURRENCY` — no plain
|
||||
`Error` ever escapes
|
||||
- Universal; explicit named barrel; one subpath export beyond the
|
||||
root; 100/100/100/100 istanbul coverage
|
||||
|
||||
## Modules
|
||||
|
||||
- [`src/binance/binance.md`](./src/binance/binance.md) — `BinanceProvider`
|
||||
class, `BINANCE_PROVIDER_NAME`, `BINANCE_DEFAULT_BASE_URL`
|
||||
|
||||
## Status
|
||||
|
||||
`0.1.0` (pre-graduation). Follow-up to the 2026-05-17 slim refactor of
|
||||
`@suaveplan/currency-providers` — see
|
||||
the design proposal
|
||||
for the per-package change record and
|
||||
the design document for the
|
||||
cross-cutting sibling-shape architecture.
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
# BinanceProvider
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`BinanceProvider` is the Binance sibling implementation of the
|
||||
`CurrencyProvider` contract from `@suaveplan/currency`. It fetches the
|
||||
public Binance spot ticker (`GET /api/v3/ticker/price`) — a free,
|
||||
key-less endpoint that returns the latest mid-price for every listed
|
||||
trading pair — and decodes it into a `RateFetchResult` ready for the
|
||||
`CurrencyConverter`'s cache. The provider is universal with a
|
||||
shape that mirrors every other `@suaveplan/currency-provider-<name>`
|
||||
sibling: a `NoKeyConfig`-shaped constructor with optional `baseUrl`
|
||||
override and optional injectable `FetchLike`, a `fetchRates` method that
|
||||
delegates HTTP transport to the slim core's `httpRequest` helper, and
|
||||
typed `CurrencyProviderError`s for every failure mode (no plain
|
||||
`Error`).
|
||||
|
||||
## Features
|
||||
|
||||
- Public Binance ticker endpoint — no API key, no OAuth, no signature
|
||||
- Default base `USDT`; any other suffix-style base supported by Binance
|
||||
(e.g. `EUR`, `BTC`, `ETH`) works by passing `base` to `fetchRates`
|
||||
- Optional `targets` filter raises `UNKNOWN_CURRENCY` for any target the
|
||||
upstream does not list against the requested base — surfaces missing
|
||||
pairs eagerly rather than silently dropping them
|
||||
- Suffix matching: a ticker `BTCUSDT` becomes `BTC` when base is `USDT`;
|
||||
symbols equal to the suffix (`USDTUSDT` quirks) are defensively skipped
|
||||
- Sanitised pricing: zero, negative, and non-finite prices are skipped
|
||||
- Rate inversion: every rate is stored as `1 / price` so the cache
|
||||
answers "1 unit of base = N units of crypto"
|
||||
- Injectable `FetchLike` for tests, proxies, and edge-runtime adapters
|
||||
- Optional `timeoutMs` flows through to `httpRequest` and derives an
|
||||
`AbortSignal.timeout(ms)` composed with the caller's `signal`
|
||||
- Typed errors via `CurrencyProviderError` + the 8-code
|
||||
`CURRENCY_PROVIDER_*` registry — `HTTP_ERROR` (non-2xx), `PARSE_ERROR`
|
||||
(non-JSON body), `UPSTREAM_ERROR` (unexpected shape),
|
||||
`UNKNOWN_CURRENCY` (target absent from upstream listing)
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { CurrencyConverter } from "@suaveplan/currency";
|
||||
import { BinanceProvider } from "@suaveplan/currency-provider-binance";
|
||||
|
||||
const provider = new BinanceProvider();
|
||||
const converter = new CurrencyConverter({
|
||||
provider,
|
||||
staleDurationMs: 30_000,
|
||||
});
|
||||
|
||||
const rate = await converter.getRate("USDT", "BTC");
|
||||
console.log(rate);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { CurrencyProviderRegistry } from "@suaveplan/currency-providers";
|
||||
import {
|
||||
BinanceProvider,
|
||||
BINANCE_PROVIDER_NAME,
|
||||
} from "@suaveplan/currency-provider-binance";
|
||||
import type { FetchLike } from "@suaveplan/types";
|
||||
|
||||
// Wire a constrained FetchLike (corporate proxy, retry harness, etc.).
|
||||
const proxiedFetch: FetchLike = async (url, init) => {
|
||||
return await fetch(`https://proxy.example.com/${String(url)}`, init);
|
||||
};
|
||||
|
||||
const binance = new BinanceProvider({
|
||||
fetch: proxiedFetch,
|
||||
baseUrl: "https://api.binance.com/api/v3",
|
||||
timeoutMs: 8_000,
|
||||
});
|
||||
|
||||
const registry = new CurrencyProviderRegistry();
|
||||
registry.register(binance);
|
||||
|
||||
// Fetch only the pairs we care about — UNKNOWN_CURRENCY surfaces any
|
||||
// upstream listing changes immediately.
|
||||
const result = await registry
|
||||
.get(BINANCE_PROVIDER_NAME)
|
||||
.fetchRates({ base: "USDT", targets: ["BTC", "ETH", "SOL"] });
|
||||
console.log(result.rates);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `BINANCE_PROVIDER_NAME: "binance"`
|
||||
|
||||
Stable provider identifier — surfaced through `provider.name` and used
|
||||
as the registry key.
|
||||
|
||||
### `BINANCE_DEFAULT_BASE_URL: "https://api.binance.com/api/v3"`
|
||||
|
||||
Default REST base URL. Overridable via the constructor `baseUrl` for
|
||||
staging or proxy deployments.
|
||||
|
||||
### `class BinanceProvider implements CurrencyProvider`
|
||||
|
||||
Constructor: `new BinanceProvider(config?: NoKeyConfig & { timeoutMs?: number })`.
|
||||
|
||||
- `config.baseUrl` — overrides `BINANCE_DEFAULT_BASE_URL`
|
||||
- `config.fetch` — optional injectable `FetchLike`; defaults to
|
||||
`globalThis.fetch`
|
||||
- `config.timeoutMs` — optional per-request timeout in milliseconds; when
|
||||
set, derives an `AbortSignal.timeout` composed with the caller's
|
||||
`signal`
|
||||
|
||||
Properties:
|
||||
|
||||
- `name === "binance"` (literal)
|
||||
- `supportsCrypto === true`
|
||||
- `supportsFiat === false`
|
||||
|
||||
Method: `fetchRates(options?: RateFetchOptions): Promise<RateFetchResult>`.
|
||||
|
||||
- `options.base` — base currency suffix (default `"USDT"`)
|
||||
- `options.targets` — optional list of target currencies; raises
|
||||
`UNKNOWN_CURRENCY` for any target absent from the upstream listing
|
||||
- `options.signal` — optional `AbortSignal` forwarded to `httpRequest`
|
||||
|
||||
Throws `CurrencyProviderError` with one of:
|
||||
|
||||
- `CURRENCY_PROVIDER.HTTP_ERROR` — non-2xx response from Binance
|
||||
- `CURRENCY_PROVIDER.PARSE_ERROR` — body is not valid JSON
|
||||
- `CURRENCY_PROVIDER.UPSTREAM_ERROR` — payload is not the expected
|
||||
`Array<{ symbol: string; price: string }>` shape
|
||||
- `CURRENCY_PROVIDER.UNKNOWN_CURRENCY` — a requested target is not
|
||||
listed against the requested base
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- Suffix matching: `endsWith(base)` plus a length check rejects symbols
|
||||
whose entire name equals the suffix (`USDTUSDT`-style edge cases),
|
||||
which would otherwise decode to an empty `""` rate key
|
||||
- Pricing: `Number.parseFloat` plus `Number.isFinite && price > 0` is
|
||||
the canonical "is this a usable mid-price" filter — zero, negative,
|
||||
and `NaN` values are silently skipped (not surfaced as errors) because
|
||||
Binance occasionally returns those for delisted-but-still-published
|
||||
symbols
|
||||
- Rate inversion: storing `1 / price` keeps the `RateFetchResult.rates`
|
||||
map answering "1 unit of base = N units of asset", matching the
|
||||
convention used by the rest of `@suaveplan/currency`
|
||||
- Transport: HTTP is delegated entirely to the slim core's
|
||||
`httpRequest` so every sibling provider raises identical `HTTP_ERROR`
|
||||
payloads, supports the same abort/timeout composition, and benefits
|
||||
from any future improvements (retry, circuit breaker) made there
|
||||
- JSON parsing: `response.json()` is wrapped so non-JSON bodies become
|
||||
`PARSE_ERROR` rather than leaking a runtime `SyntaxError`; payload
|
||||
shape validation is a typed structural check (no runtime Zod cost in
|
||||
the universal core)
|
||||
- IP-based rate limiting still applies upstream; the provider is
|
||||
intentionally rate-limit-agnostic — wrap it in the future
|
||||
`RateLimitedProvider` when that ships
|
||||
Reference in New Issue
Block a user