# `registry` — Name-keyed lookup of CurrencyProvider instances _(no submodules)_ ## Purpose Defines `CurrencyProviderRegistry`, the in-memory registry that sibling `@suaveplan/currency-provider-` packages register their providers with and that consumers (the `CurrencyConverter` wiring, future `AggregatorProvider` / `RateLimitedProvider` wrappers, admin UIs that toggle providers on and off) use to resolve providers by name without tracking concrete classes themselves. The registry is **opt-in convenience**, not a required indirection: `new CurrencyConverter({ provider: new EcbProvider() })` continues to work without a registry. The registry exists for callers that wire many providers from configuration, that need a single canonical instance of each provider, or that want a uniform "look up by name" surface to feed into wrappers. ## Features - Five-method API — `register`, `get`, `has`, `list`, `unregister` - Typed `CurrencyProviderError` on every failure mode (no plain `Error`s) - `register` throws `PROVIDER_ALREADY_REGISTERED` on duplicate names — explicit `unregister` + `register` required to replace - `get` throws `PROVIDER_NOT_REGISTERED` on unknown names; `has` is a non-throwing probe for the same condition - `list()` returns a **sorted snapshot** so equal sets of providers produce identical listings regardless of insertion order — keeps downstream golden-file tests deterministic - `unregister` is idempotent — returns `true` when something was removed, `false` otherwise, never throws - Providers stored by reference (no cloning / freezing / defensive copy) ## Usage: Basic Example ```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")], }); ``` ## Usage: Advanced Example Wire providers from a config file, swap them at runtime, and enumerate them deterministically for a status dashboard: ```ts import { CurrencyProviderRegistry } from "@suaveplan/currency-providers"; import type { CurrencyProvider } from "@suaveplan/currency"; interface ProviderModule { readonly create: () => CurrencyProvider; } function wire( modules: Readonly>, enabled: readonly string[] ): CurrencyProviderRegistry { const registry = new CurrencyProviderRegistry(); for (const key of enabled) { const mod = modules[key]; if (mod !== undefined) registry.register(mod.create()); } return registry; } // Swap "ecb" for a different ECB instance with a custom fetch const registry = wire(modules, ["ecb", "coingecko", "kraken"]); registry.unregister("ecb"); registry.register(new EcbProvider({ fetch: instrumentedFetch })); // Dashboard: always shows providers alphabetically for (const name of registry.list()) { console.log(`provider: ${name}`); } ``` ## API Reference - `class CurrencyProviderRegistry` — exported class. Holds a `Map` internally. - `register(provider: CurrencyProvider): void` — store the provider under `provider.name`. Throws `CurrencyProviderError` with `code === "CURRENCY_PROVIDER.PROVIDER_ALREADY_REGISTERED"` and `providerName === provider.name` when the name is already in use. - `get(name: string): CurrencyProvider` — return the registered provider. Throws `CurrencyProviderError` with `code === "CURRENCY_PROVIDER.PROVIDER_NOT_REGISTERED"` and `providerName === name` when nothing is registered under `name`. - `has(name: string): boolean` — non-throwing probe. - `list(): readonly string[]` — sorted snapshot of currently registered names. - `unregister(name: string): boolean` — `true` when something was removed, `false` otherwise. Never throws. ## Implementation Notes - The registry is **single-tenant**: there is no global / singleton instance. Callers construct their own registry. Multiple registries can coexist (e.g. one per tenant in a multi-tenant SaaS), and a provider instance may be registered with more than one registry simultaneously. - `register` is idempotent only at the identity level: registering the same instance under the same name twice still throws `PROVIDER_ALREADY_REGISTERED`, exactly as registering two different instances under the same name throws. Idempotent "replace if newer" semantics belong in a wrapper, not the core, so the contract here stays unambiguous. - `list()` returns a fresh `string[]` on every call (the sort happens in-place on a copy). Callers may treat the result as immutable, but the registry does not freeze it — a `readonly string[]` type hint conveys the intent at the TypeScript boundary. - Both `PROVIDER_NOT_REGISTERED` and `PROVIDER_ALREADY_REGISTERED` carry `providerName` on the thrown `CurrencyProviderError`, so callers writing cross-cutting error handlers (audit logs, telemetry) can structure on the discriminant rather than parsing the message string.