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:
2026-05-30 01:10:00 +01:00
parent fe3fedf42e
commit 9bf56a806b
6022 changed files with 12437 additions and 0 deletions

View File

@@ -0,0 +1,89 @@
# @suaveplan/currency-provider-fixer - Fixer.io fiat-rates CurrencyProvider sibling of @suaveplan/currency-providers. API-key required; 170+ fiat currencies; maps upstream error codes (105 → UNSUPPORTED_BASE, 201/202 → UNKNOWN_CURRENCY, other success:false → UPSTREAM_ERROR) to the slim core's typed CurrencyProviderError. Universal, FetchLike-injectable.
[Fixer.io](https://fixer.io/) fiat-rates sibling provider for the slim
`@suaveplan/currency-providers` core. Implements the `CurrencyProvider`
contract from `@suaveplan/currency`, requires an API key, and maps the
upstream `{ success, error: { code, info } }` envelope to typed
`CurrencyProviderError` codes — including the free-tier
`base_currency_access_restricted` case (`UNSUPPORTED_BASE`).
<!-- begin:auto-docs -->
## Documentation tree
- [fixer](./src/fixer/fixer.md) — `FixerProvider` is the Fixer.io sibling provider for the slim `@suaveplan/currency-providers` core. It implements the `…
- [fixer](./src/fixer/fixer.md) — `FixerProvider` is the Fixer.io sibling provider for the slim `@suaveplan/currency-providers` core. It implements the `…
<!-- end:auto-docs -->
## Install
```bash
bun add @suaveplan/currency-provider-fixer \
@suaveplan/currency-providers \
@suaveplan/currency
```
## Quick start
```ts
import { CurrencyConverter } from "@suaveplan/currency";
import { CurrencyProviderRegistry } from "@suaveplan/currency-providers";
import { FixerProvider } from "@suaveplan/currency-provider-fixer";
const registry = new CurrencyProviderRegistry();
registry.register(new FixerProvider({ apiKey: process.env.FIXER_KEY! }));
const converter = new CurrencyConverter({
provider: registry.get("fixer"),
staleDurationMs: 3_600_000,
});
const rate = await converter.getRate("EUR", "GBP");
```
Direct instantiation without the registry is equally supported:
```ts
const provider = new FixerProvider({ apiKey: process.env.FIXER_KEY! });
const result = await provider.fetchRates();
```
## Features
- Implements `CurrencyProvider` (`name = "fixer"`, `supportsFiat = true`,
`supportsCrypto = false`).
- Constructor takes the slim core's shared `ApiKeyConfig`; an empty
`apiKey` raises `CURRENCY_PROVIDER.MISSING_CONFIG` synchronously
before any network call (no wasted quota).
- HTTP transport via the slim core's `httpRequest` — `AbortSignal`
composition, non-2xx → typed `HTTP_ERROR`, `providerName` / `status`
populated on the raised error.
- Typed upstream-code mapping: `105` → `UNSUPPORTED_BASE`, `201` / `202`
→ `UNKNOWN_CURRENCY`, any other `success: false` → `UPSTREAM_ERROR`
with `upstreamMessage` carrying the upstream `error.info`.
- `PARSE_ERROR` for malformed JSON, non-object JSON, and successful
envelopes missing required fields.
- Universal — runs unchanged under Node 18+, Bun, modern browsers,
and Web Workers.
- One subpath export, one feature module; explicit named barrels;
100/100/100/100 istanbul coverage.
## Modules
- [`src/fixer/fixer.md`](./src/fixer/fixer.md) — `FixerProvider` class,
envelope handling, error-code mapping table.
## Sibling packages
Part of the foundation-with-sibling-plugins family established by
the design document. Other concrete
providers (ECB, Open Exchange Rates, Binance, CoinGecko, CoinMarketCap,
CryptoCompare, Kraken, Virtual) ship as separate
`@suaveplan/currency-provider-<name>` packages.
## Status
`0.1.0` (pre-graduation). New native sibling landed on
2026-05-17 against the slimmed core. See
the design proposal
for the proposal, design, and parity checklist.

View File

@@ -0,0 +1,154 @@
# FixerProvider
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`FixerProvider` is the [Fixer.io](https://fixer.io/) sibling provider for
the slim `@suaveplan/currency-providers` core. It implements the
`CurrencyProvider` contract from `@suaveplan/currency`, fetches the
latest fiat exchange rates from the Fixer `/latest` REST endpoint, and
maps the upstream JSON envelope into the canonical `RateFetchResult`
shape the converter expects.
Fixer.io requires an API key on every request. Free-tier accounts are
restricted to `base = "EUR"` and a fixed monthly request quota; paid plans
unlock arbitrary base currencies and higher throughput. The provider
surfaces the free-tier restriction as a typed `UNSUPPORTED_BASE` error so
consumers can dispatch on the discriminator rather than parsing the
upstream message string.
## Features
- Implements `CurrencyProvider` with stable identity flags
(`name = "fixer"`, `supportsFiat = true`, `supportsCrypto = false`).
- Constructor takes the slim core's shared `ApiKeyConfig` (`apiKey`,
optional `baseUrl`, optional injectable `fetch`). An empty `apiKey`
raises `CURRENCY_PROVIDER.MISSING_CONFIG` synchronously before any
network call is issued, preventing wasted quota.
- Delegates HTTP transport to the slim core's `httpRequest` helper —
`AbortSignal` composition, non-2xx → typed `HTTP_ERROR` mapping, and
`providerName` / `status` carry on the raised error.
- Maps the upstream `{ success: false, error: { code, info } }` envelope
to typed codes: `105` → `UNSUPPORTED_BASE`, `201` / `202` →
`UNKNOWN_CURRENCY`, any other code → `UPSTREAM_ERROR`. The upstream
`error.info` is preserved on `upstreamMessage` so diagnostics survive.
- Distinguishes parse failures (non-JSON, non-object JSON, success
envelope missing required fields) under a single `PARSE_ERROR` code.
- Universal — no `node:` / `bun:` / DOM imports; runs unchanged
under Node 18+, Bun, modern browsers, and Web Workers.
## Usage: Basic
```ts
import { FixerProvider } from "@suaveplan/currency-provider-fixer";
const provider = new FixerProvider({ apiKey: process.env.FIXER_KEY! });
// Free-tier: base is implicitly EUR.
const result = await provider.fetchRates();
console.log(result.base, result.rates.GBP, result.timestamp);
```
## Usage: Advanced
Wire the provider through the slim core's `CurrencyProviderRegistry` and
use it as the primary source for a `CurrencyConverter`. The injected
`FetchLike` makes the provider trivial to unit-test against a fixture
fetch closure:
```ts
import { CurrencyConverter } from "@suaveplan/currency";
import { CurrencyProviderRegistry } from "@suaveplan/currency-providers";
import { FixerProvider } from "@suaveplan/currency-provider-fixer";
const registry = new CurrencyProviderRegistry();
registry.register(
new FixerProvider({
apiKey: process.env.FIXER_KEY!,
// Optional: proxy through your own gateway for caching / auditing.
baseUrl: "https://rates.internal/fixer/api",
// Optional: inject a wrapped fetch for retries or logging.
fetch: async (url, init) => {
const response = await globalThis.fetch(url, init);
return response;
},
}),
);
const converter = new CurrencyConverter({
provider: registry.get("fixer"),
staleDurationMs: 3_600_000,
});
// Free-tier accounts MUST keep base = "EUR" — supplying any other base
// raises CurrencyProviderError(UNSUPPORTED_BASE) via the typed mapping
// from upstream error code 105.
const rate = await converter.getRate("EUR", "GBP");
```
## API Reference
### `class FixerProvider implements CurrencyProvider`
- `readonly name = "fixer"`
- `readonly supportsCrypto = false`
- `readonly supportsFiat = true`
#### `constructor(config: ApiKeyConfig)`
Validates `config.apiKey` is non-empty (throws
`CurrencyProviderError(MISSING_CONFIG)` otherwise) and caches
`config.baseUrl ?? "https://data.fixer.io/api"` and
`config.fetch ?? globalThis.fetch`.
**Throws**: `CurrencyProviderError(MISSING_CONFIG)` when `apiKey === ""`.
#### `fetchRates(options?: RateFetchOptions): Promise<RateFetchResult>`
Builds `?access_key=…&base=…[&symbols=…]`, delegates to the slim core's
`httpRequest`, parses the JSON envelope, and returns the canonical
`RateFetchResult` with `timestamp` converted from Unix seconds to
milliseconds.
**Parameters** (all optional): `base` (default `"EUR"`), `targets`
(array of currency codes — joined into `symbols`), `signal`
(`AbortSignal` forwarded to the underlying fetch).
**Returns**: `{ base, rates, timestamp }` where `timestamp` is
milliseconds since epoch.
**Throws** (all `CurrencyProviderError`):
- `MISSING_CONFIG` — at construction when `apiKey` is empty.
- `HTTP_ERROR` — non-2xx upstream response (via `httpRequest`).
- `PARSE_ERROR` — body is not JSON, parsed value is not an object, or
a `success: true` envelope is missing `base` / `rates` / `timestamp`.
- `UNSUPPORTED_BASE` — upstream code `105` (free-tier base restriction).
- `UNKNOWN_CURRENCY` — upstream code `201` (`invalid_base_currency`) or
`202` (`invalid_currency_codes`).
- `UPSTREAM_ERROR` — any other `success: false` envelope, including the
case where `error` is absent.
## Implementation Notes
- Fixer.io returns HTTP 200 even for application-level errors; the
provider must therefore parse every body and discriminate on
`success`. The non-2xx path (`HTTP_ERROR`) is reserved for genuine
network / gateway failures.
- The upstream `error.code` integer is the discriminator, not
`error.type` — the integer is one comparison and avoids string
allocation. The human-readable `error.info` is preserved on
`upstreamMessage` for diagnostics.
- The `apiKey` non-empty guard at construction is suaveplan-native — the
upstream skeleton allowed an empty string and let the API reject on the
wire, wasting the request against the monthly quota.
- `fetchRates` forwards `options.signal` through `httpRequest` so the
caller can cancel an in-flight request from outside the provider; this
too is suaveplan-native (upstream did not propagate the signal).
- The package is universal and free of side effects (`sideEffects:
false`). Importing the package never mutates any global registry — the
caller decides whether to register the instance with
`CurrencyProviderRegistry`.