@uk-energy/core
v0.3.0
Published
Shared HTTP layer for the uk-energy SDKs: retries with jittered backoff, per-host rate limiting, pagination iterators, pluggable cache, recorded-fixture fetch for tests.
Maintainers
Readme
@uk-energy/core
Shared HTTP layer for the uk-energy SDKs. You normally don't install this directly; the
source packages (@uk-energy/elexon, @uk-energy/neso, @uk-energy/carbon-intensity, …)
depend on it. Use it on its own if you want the same retry, rate-limit and cache behaviour
against an API we don't cover yet.
Data returned through this client belongs to its publisher. Keep the attribution lines from the repo README wherever you surface it.
Python mirror: uk_energy.core in the uk-energy distribution.
What it does
- One
HttpClientper API with abaseUrl, default headers and a user agent. - Per-host rate limiting via a token bucket shared across every client in the process,
so two clients for
data.elexon.co.ukdraw on one budget. - Retries with jittered backoff on 408/425/429/5xx, timeouts and network errors.
Retry-Afteris honoured. A 429 is never surfaced to the caller on the first hit. - Pluggable cache for GETs (
MemoryCacheincluded; theCacheinterface is four methods). - Pagination iterators that are lazy: stop consuming and no more pages are fetched.
- Recorded-fixture fetch so tests never touch a live API; one fixture file serves both the TypeScript and Python test suites.
Usage
import { HttpClient, MemoryCache, paginate, collect } from "@uk-energy/core";
const elexon = new HttpClient({
baseUrl: "https://data.elexon.co.uk/bmrs/api/v1",
rateLimit: { requestsPerSecond: 5, burst: 10 },
cache: new MemoryCache(),
cacheTtlMs: 60_000,
});
// Plain GET, typed by you (the source packages give you generated types)
const fuel = await elexon.get<{ data: FuelRow[] }>("datasets/FUELINST", {
publishDateTimeFrom: new Date("2026-10-20T00:00Z"),
publishDateTimeTo: new Date("2026-10-21T00:00Z"),
});
// Full response when you need status/headers
const res = await elexon.request<Bmu[]>("reference/bmunits/all");
res.status; res.headers.get("x-ratelimit-remaining"); res.fromCache; res.attempts;
// Paginate lazily
const rows = paginate<Page, Row, number>({
fetchPage: (offset) => neso.get("datastore_search", { resource_id, limit: 1000, offset }),
items: (p) => p.result.records,
nextCursor: offsetCursor(1000, (p) => p.result.records.length),
});
for await (const r of rows) { /* ... */ }Errors: HttpError (status, body, attempts, retryable), TimeoutError,
NetworkError, FixtureMissingError; all extend UkEnergyError.
Tests: fixtures, not live calls
import { fixtureFetch } from "@uk-energy/core/fixtures";
const client = new HttpClient({ baseUrl, fetch: fixtureFetch() });fixtureFetch replays fixtures/<host>/<sha1>.json from the repo. With
UK_ENERGY_LIVE=1 UK_ENERGY_RECORD=1 it records anything missing from the real API and
strips authorization / x-api-key / cookies before writing. Format and key derivation are
in fixtures/README.md.
Options
| HttpClientOptions | Default | |
| --- | --- | --- |
| baseUrl | required | |
| fetch | globalThis.fetch | inject fixtureFetch() in tests |
| headers, userAgent | accept: application/json, uk-energy-sdk/<version> | |
| timeoutMs | 30 000 | per request |
| retry.retries | 3 | after the first attempt; a first-hit 429 is retried once even at 0 |
| retry.baseMs / maxMs / factor / jitter | 500 / 30 000 / 2 / "full" | maxMs also caps Retry-After |
| retry.respectRetryAfter | true | |
| rateLimiter | process-wide shared | |
| rateLimit | limiter default (5 rps, burst 5) | registers this host's limit |
| cache, cacheTtlMs | NoopCache, 0 | GET only |
| onRequest, onResponse | — | hooks for logging/metrics |
