All notable changes to this project are documented here. The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
0.1.1 - 2026-08-21
No functional changes: the published files are identical to 0.1.0. This release exists to move publishing onto npm trusted publishing (OIDC).
- Released through npm trusted publishing instead of a long-lived automation token.
0.1.0was pushed from a personal account holding a granular token with write access to every package that account could reach; the release now authenticates per-run againstSkyLink-API/SkyLink-API-TypeScript-SDKand no npm credential is stored in the repository at all. - The release job runs on Node 24 (npm 11.17). Node 22 bundles npm 10, which cannot request an OIDC token and silently falls back to token auth — a misconfigured release would have looked green while quietly skipping trusted publishing.
- Still published without a provenance attestation: npm cannot mint one while the source repository is private. Nothing needs to change here for it to appear — the workflow adds the flag on its own once the repository is public.
0.1.0 - 2026-08-17
First public release. Covers the SkyLink API v3.1 surface.
Defects found and fixed during the pre-release audit, before anything shipped.
-
Aborting a request did not interrupt the retry backoff wait.
RequestOptions.signalis documented to cancel the call immediately, and it did — except while the transport was sleeping between retries, where a baresetTimeoutignored the signal entirely. After a 503 withRetry-After: 60a caller'scontroller.abort()would hang for up to a minute before the loop noticed. The backoff wait now uses the same abort-aware sleep as the polling helpers, so an abort rejects immediately withAPIConnectionError; thesleeptest seam gained an optionalAbortSignalsecond parameter. -
briefing.flight()andbriefing.pdf()timed out on every call with the default client. A briefing is composed by a language model over both airports' weather and NOTAMs and takes far longer than the 30 000 msDEFAULT_TIMEOUT_MS: measured live on 2026-08-15,format: "json"took 37–128 s,"markdown"30–50 s,"plain_text"up to 85 s and the PDF ~52 s. Every one of those aborted, and because a timeout is retried the caller waited ~120 s to be told a healthy endpoint had failed. Both routes now carry the newBRIEFING_TIMEOUT_MS(180 000 ms) on the request spec itself.This is the first use of the new
RequestSpec.timeoutfield, an endpoint-level default: a per-call{ timeout }still wins, and every other endpoint keeps the client's timeout untouched. Retries are deliberately left on — a real 503 should still be retried — so cap both yourself where a slow page is worse than no briefing:await sky.briefing.flight( { origin: "KJFK", destination: "KLAX" }, { timeout: 60_000, maxRetries: 0 }, );
Matches the same fix in the Python SDK.
-
batch.metars()andbatch.tafs()acceptparsedthrough the newBatchWeatherOptions, overloaded so{ parsed: true }narrows the result toBatchResult<ParsedMetarResponse>/BatchResult<ParsedTafResponse>. Previously the calls hard-coded an empty params object (weather.metar(icao, {}, options)), so asking for the decoded block was impossible — and everything inskylink-api/weather(flightCategory,ceilingFt, the unit parsers) reads decoded fields and could only answernullon the result. Colouring a board of airports by flight category is the main reason to batch METARs, and it could not be done. Matches the same fix in the Python SDK.
ml.flightTime()takes{ origin, destination }. It previously exposed the endpoint's own query keys{ from, to }, which disagreed with the Python SDK (ml.flight_time(origin=, destination=), where the rename is deliberate and documented) and with this SDK's owncompose.routeBrief(origin, destination)— which calledflightTime({ from, to })internally. Divergence between the two SDKs is a defect by contract, so the names are now the same on both sides.
Documentation catching up with backend fixes shipped in the 2026-08 API release. No behaviour changes — every one of these endpoints already worked through the SDK once the server side was corrected; what was wrong was the SDK telling users they were broken.
CONTINENTS/geo.countries({ continent: "NA" })no longer carry the "accepted and then matches nothing" warning. The backend used to read its reference CSV with pandas, which parses the literalNAas not-a-number, so North America was unqueryable. Verified live on 2026-08-15: 41 countries and 440 regions.compose.northAmericaCountries()is documented as a convenience rather than a workaround, and its@todo(which also wrongly predicted the method would start returning nothing) is gone. The method is kept — it is public API, and its predicate still accepts the historicalnull/""spellings alongside"NA".geo.countries({ continent: "NA" })is now the cheaper route and is documented as such.TicketOffer.original_price/.original_currencyare no longer documented as absent. The ticket service emits them (JFK→LAX:price_usd: 168.52,original_price: 137,original_currency: "CHF"), which finally makes theprice_usd-is-not-always-USD caveat actionable.TicketSearchResponse.countnow warns that the list is long rather than short:JFK→LAXreturned 111 offers andLHR→JFK120.
{ from, to }onml.flightTime(). Still accepted and still works — the parameters are a union of both spellings,origin/destinationwin when both are present — but the type is marked@deprecated. Existing code compiles unchanged.
Client
SkyLinkclient with eager readonly namespaces, resolved configuration onclient.configandclient.baseUrl.- Two distribution channels via
provider:"rapidapi"— the default — (https://skylink-api.p.rapidapi.com,X-RapidAPI-Key+X-RapidAPI-Host,RAPIDAPI_KEYfalling back toSKYLINK_API_KEY) and"direct"(https://data.skylinkapi.com/v3.1,x-api-key,SKYLINK_API_KEYonly). Both expose an identical method surface. baseUrloverride for staging and local backends; supplying it makes the API key optional, so keylessDISABLE_AUTHdeployments work.- Options:
apiKey,provider("rapidapi"),baseUrl,timeout(30 000 ms),maxRetries(3),historyPlan("ultra"),defaultHeaders,fetch. - Per-request
RequestOptions:timeout,maxRetries,headers,signal. client.lastRateLimitfrom the quota headers of every response, error responses included —X-RateLimit-Requests-{Limit,Remaining,Reset}on RapidAPI andX-RateLimit-{Limit,Remaining,Reset}on the direct channel, the former taking precedence. RapidAPI'sX-RateLimit-rapid-free-plans-hard-limit-*headers are ignored: they report the marketplace's free-tier ceiling, not the plan's quota.client.request()/client.requestWithResponse()as escape hatches for untyped endpoints.
Namespaces (21) — weather, airports, airlines, navaids, geo, adsb,
aircraft, charts, delays, notams, schedules, ml, carbon, briefing,
routes, tickets, webhooks, history, plus the three client-side ones
(batch, poll, compose) — and the client-level shortcuts flightStatus() and
distance().
weather—metar,taf(both overloaded onparsed),windsAloft,pireps,airsigmet.airports—search,nearby,byIp,searchText.airlines—search.navaids—list(client-side "at least one filter" check).geo—countries,country,regions,region.adsb—aircraft(the one paginated endpoint),statistics,health.aircraft—byRegistration,byIcao24(discriminated union onfound),performance,databaseStats.charts—byAirport,byCategory,sources; charts typed asPartial<Record<ChartCategory, Chart[]>>.delays—faa(nationwide and per airport).notams—byAirport.schedules—departures,arrivals; flight rows keep their PascalCase wire keys.ml—flightTimewith the wire parameter namesfrom/to.carbon—estimate.tickets—search.briefing—flightoverloaded onformat("json"→ object, text formats →string) andpdf→Uint8Array.routes—byCallsign(union onsource),byAirport,pairs.webhooks—create(201),list,update,delete(204 →void),eventTypes; envelopes unwrapped to arrays.history—flights,flight,track,positions(dispatches ICAO24 vs registration by shape),positionsByIcao24,positionsByRegistration,airportTraffic, all parameterized by theultra/megaplan prefix.
Developer experience
sky.batch—metars,tafs,notams,airports(IATA/ICAO classified per code),flightStatuses: one entry per input identifier holding the response or itsSkyLinkError, five requests in flight by default, duplicates collapsed. Helperssuccesses,failures,isBatchError,throwForErrors,mapConcurrent.sky.compose—airportBrief,flightBrief,routeBrief,enrichAdsb,schedulesWithStatus,northAmericaCountries. Parts are fetched in parallel and a failed part is collected intobrief.errorsunder its own name instead of throwing; only the primary call (airport lookup, flight status, schedule board) propagates.include/excludedecide what is requested, so a deselected part costs no quota. Briefs fetch weather withparsed: true;flightBriefprices CO₂ from the route's ICAO pair and falls back to the callsign;schedulesWithStatusaccepts IATA or ICAO and requests each distinct flight number once.sky.poll—flightStatus(changes only, stops on a terminal status) andadsb(appeared/updated/disappeared diffs plus the full snapshot) as async generators; 429 and 5xx are waited out,interval/maxIterations/AbortSignalend the loop.- Async iterators over the two paginated shapes:
adsb.iterAircraft()(limit/offset, with a repeated-page guard) andhistory.iterFlights()(time windows clamped to the plan's retention). - Zero-dependency helper modules, also published as subpath entry points
(
skylink-api/units,/spatial,/idents,/weather,/geojson,/sentinels,/batch,/cache,/csv): unit conversions and the prose/P6SMparsers, great-circle and bounding-box maths, track statistics and RDP simplification, flight-category/ceiling/wind components, identifier classification, sentinel narrowing, RFC 7946 GeoJSON exporters and RFC 4180toCsv(). - Opt-in response cache (
MemoryCache,CacheProtocol,resolveTtl,CACHE_HIT_HEADER): off unless configured, per-operation TTLs in milliseconds, successful GETs only, LRU eviction on a monotonic clock. - Quota observers
client.onRateLimit()(every response that carried the headers) andclient.onQuotaLow()(once per crossing, re-armed on reset), plusSkyLink.fromEnv()andclient.withOptions()for clones that share the connection pool but not state. webhooks.ensure()for idempotent subscription setup.
Transport
- Zero runtime dependencies: native
fetch,AbortController,URLSearchParams. - Retry policy: 429/500/502/503/504 and transport failures, full-jitter exponential
backoff (
random() * min(8 s, 500 ms * 2^attempt)),Retry-Afterhonoured in both the seconds and HTTP-date forms (capped at 60 s).POSTretried only on 429. - Overall per-attempt deadline enforced with
AbortController; externalAbortSignalcancels immediately without retrying. - Query serialization that drops nullish values, lower-cases booleans, comma-joins
bounding boxes and CSV filters, and formats dates per endpoint (
DD-MM-YYYYfor schedules,YYYY-MM-DDfor tickets, ISO 8601 for history). - Response decoding for JSON, text, binary (
Uint8Array) and empty (204) bodies.
Errors
SkyLinkError→APIConnectionError/APITimeoutErrorandAPIStatusError(status,statusCode,headers,body,code) →BadRequestError,AuthenticationError,PermissionDeniedError,NotFoundError,UnprocessableEntityError(errors[]),RateLimitError(rateLimit,retryAfter),InternalServerError,ServiceUnavailableError.- One parser for the three error-body shapes the API emits: gateway
{ error, message, code },HTTPException{ detail }and validation{ detail: [{ loc, msg, type }] }. - HTTP 200 "not found" sentinels are modelled as types, not exceptions: the
found: true | falseunion on aircraft lookup,erroron IP airport search andnoteon history search.
Types
- Compile-time-only interfaces mirroring the wire exactly —
snake_casefields, PascalCase schedules rows,usageTypeon navaids — with no index signatures and no runtime validation. ISO 8601 values are typedstring; scraped/opaque times (flight status, NOTAM validity, delay durations) are left unparsed.
Packaging
- Dual ESM + CJS build with declaration files for both,
exportsmap,sideEffects: false, Node >= 20. - CI on Node 20/22/24 (Biome,
tsc --noEmit, build, vitest) and tag-triggered publishing with npm provenance. - Ten runnable examples in
examples/— weather, ADS-B, briefings, history, webhooks, batch, compose, polling, helper exports, cache and quota — and an env-gated integration suite.