nhle-api 0.5.4 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,62 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ ## [0.7.0] - 2026-10-19
11
+
12
+ ### Added
13
+
14
+ - `EdgeByTheNumbers.highDangerSavesGoalie`, the goalie with the most high-danger saves, newly returned by `adv.byTheNumbers`
15
+ - Live-game fields on `Scoreboard` games: `clock`, `situation` and each team's `sog`
16
+ - `DraftPositionCode` (`C`, `LW`, `RW`, `D`, `G`, `F`, `C/LW`, `C/RW`, `LW/RW`), used by the `draft.picks`, `draft.rankings` and `draft.tracker` response types
17
+ - The drift check flags position codes a response's types don't declare, and `--update` won't accept them
18
+
19
+ ### Changed
20
+
21
+ - `savePctgLast10` and `savePctg5v5Last10` on `adv.goalies.compare` and `adv.goalies.savePercentage5v5` are typed as `EdgeGoalieGame[]` and `EdgeGoalieGameDecision[]` instead of `unknown[]`, now that the API fills them
22
+ - **Breaking:** `PositionCode` is now `'C' | 'L' | 'R' | 'D' | 'G'`. Outside the draft endpoints the API sends `L`/`R` for wingers, never `LW`/`RW`
23
+
24
+ ## [0.6.0] - 2026-10-03
25
+
26
+ ### Added
27
+
28
+ - `stats` namespace for the Stats API (`api.nhle.com/stats/rest`): skater, goalie and team reports, leaders, milestones, teams, seasons, games, shift charts, drafts, config, countries, glossary and franchises
29
+ - `getStats(report, params)` for skaters, goalies and teams, with `SkaterReport`, `GoalieReport` and `TeamReport` unions of the report names
30
+ - Response types for every `adv` function (`EdgeSkaterDetail`, `EdgeTeamZoneTime`, ...) and `LocalizedText`
31
+ - `video.metadata(videoId)` for Brightcove video titles, descriptions and sources
32
+ - Root exports: `APIResult`, `NHLClient`, `createNHLClient`, `BASE_URLS`, the error classes (`NHLError`, `NotFoundError`, `ValidationError`...), `ErrorCategory`, `ErrorLogLevel`, and the Cayenne query builder
33
+ - `nhle-api/toolkit` entry point with constants, date helpers, `resolvePath`, `normalizeAbbrev`, the Cayenne query builder, and team branding (colors and logo URLs)
34
+ - `NHLClient` accepts an options object (`baseUrl`, `timeout`, `language`, `headers`, `errorConfig`)
35
+ - Level-aware `logger` and `writeLog`
36
+ - Daily API drift check (`bun run drift`, GitHub workflow "API drift") covering every public function
37
+ - CI on pull requests (lint, typecheck, tests, build, publint and are-the-types-wrong) and a tag-triggered release workflow that publishes with npm trusted publishing
38
+
39
+ ### Changed
40
+
41
+ - `config` is read on every request, so changing it at runtime takes effect; environment variable configuration was removed
42
+ - Invalid `adv` parameters resolve to a failed result with a `ValidationError` instead of rejecting
43
+ - Date parameters use the NHL's timezone (America/New_York)
44
+ - `require` resolves `.d.cts` type declarations; the package declares `sideEffects: false` and `engines.node >= 18`
45
+ - Stats API: `getPlayerInfo`, `getLeaders`, `getGames`, `teams.getAll` and `teams.getById` take query params before `lang`; `teams.getById` returns a list, matching the API
46
+
47
+ ### Fixed
48
+
49
+ - The build failed under TypeScript 7; builds use TypeScript 5.9
50
+ - Published types left out `config`, `logConfig`, `logger` and `writeLog`
51
+ - Edge skater and team paths, goalie top-10 save percentage paths and top-10 filter codes (23 endpoints)
52
+ - `postalLookup` sends Canadian postal codes in the only form the API matches
53
+ - `stats.skaters.getStats()` requested an unresolved `{report}` path, report functions sent no `cayenneExp` (HTTP 500), and `getStatsWithFilters` dropped most filters
54
+ - Functions that default to the current season used 2025-26 (the last hardcoded season); the default now follows today's date
55
+
56
+ ### Deprecated
57
+
58
+ - `gc.game.whereToWatch()`: the endpoint now returns 404
59
+
60
+ ### Removed
61
+
62
+ - The unused `NHLScoreboard` type
63
+
8
64
  ## [0.5.4] - 2026-02-10
9
65
 
10
66
  ### Added
package/README.md CHANGED
@@ -1,234 +1,319 @@
1
- # NHLe API Library
2
-
3
- [![npm version](https://img.shields.io/npm/v/nhle-api.svg)](https://www.npmjs.com/package/nhle-api)
4
- [![npm downloads](https://img.shields.io/npm/dm/nhle-api.svg)](https://www.npmjs.com/package/nhle-api)
5
- [![license](https://img.shields.io/github/license/spiflicate/nhle-api.svg)](LICENSE)
6
-
7
- A modern TypeScript wrapper around the public NHL GameCenter and EdgeStats APIs with simple, composable functions and strong TypeScript typing.
8
-
9
- ## Overview
10
-
11
- The library exposes a small, functional surface over the NHL "Game Center" and related APIs and ships with rich response types for all Game Center endpoints.
12
-
13
- - Written in TypeScript and published as ESM/CJS
14
- - Thin functional wrappers over official NHL API routes
15
- - Fully-typed Game Center responses (game, team, player, draft, misc)
16
- - Simple environment-based configuration for timeouts, language and logging
17
-
18
- ## Installation
19
-
20
- ```bash
21
- npm install nhle-api
22
- ```
23
-
24
- The package ships both ESM and CJS builds; use standard `import`/`require` according to your toolchain.
25
-
26
- ## Quick Start
27
-
28
- The primary entrypoint is the Game Center API namespace `gc`:
29
-
30
- ```ts
31
- import { gc } from 'nhle-api';
32
-
33
- // Get game landing page data
34
- const gameInfo = await gc.game.landing(2023020001);
35
-
36
- // Get live play-by-play
37
- const playByPlay = await gc.game.playByPlay(2023020001);
38
-
39
- // Get boxscore
40
- const boxscore = await gc.game.boxscore(2023020001);
41
-
42
- // Get today's scores
43
- const scores = await gc.score(new Date());
44
-
45
- // Get current scoreboard
46
- const scoreboard = await gc.scoreboard();
47
-
48
- // Get team roster
49
- const roster = await gc.team.roster('TOR');
50
-
51
- // Get player landing page (Connor McDavid)
52
- const player = await gc.player.landing(8478402);
53
- ```
54
-
55
- ## Configuration
56
-
57
- Basic behavior is configured via environment variables and the exported `envConfig` helper from `config/env`:
58
-
59
- ```bash
60
- # Request timeout in milliseconds (default: 5000)
61
- export NHLE_API_TIMEOUT=10000
62
-
63
- # Language for responses ("en" or "fr", default: "en")
64
- export NHLE_API_LANGUAGE=en
65
-
66
- # Logging level: silent | error | warn | info | debug (default: "warn")
67
- export NHLE_API_LOGLEVEL=debug
68
- ```
69
-
70
- You can inspect the resolved configuration at runtime:
71
-
72
- ```ts
73
- import { envConfig, logEnvConfig } from 'nhle-api';
74
-
75
- console.log(envConfig.timeout, envConfig.language, envConfig.logLevel);
76
- logEnvConfig(); // logs only variables that are explicitly set
77
- ```
78
-
79
- See `docs/ENVIRONMENT_CONFIG.md` for more details.
80
-
81
- ## Top-Level Exports
82
-
83
- The package root `nhle-api` re-exports the main API namespaces, configuration utilities, constants, and public types:
84
-
85
- ```ts
86
- // Game Center API (fully typed)
87
- import { gc } from 'nhle-api';
88
-
89
- // Edge Advanced stats (in progress, API surface may change)
90
- import { adv } from 'nhle-api';
91
-
92
- // Environment configuration helpers
93
- import { envConfig, logEnvConfig } from 'nhle-api';
94
-
95
- // Shared type exports (Game Center response types, parameter types, etc.)
96
- import type { GameLandingResponse } from 'nhle-api';
97
- ```
98
-
99
- ### Notes on Edge APIs
100
-
101
- - `adv` (Edge Advanced) is available but still considered in development, but ready for preliminary use.
102
- - The `stats` (Edge Stats) namespace is not exported at the moment and will be introduced in a future release once implementation is ready.
103
-
104
- ## Game Center API (`gc`)
105
-
106
- `gc` is the primary namespace for accessing live and historical NHL data.
107
-
108
- ### `gc.game` – Game Data
109
-
110
- - `playByPlay(gameId)` – Full live play-by-play event stream
111
- - `boxscore(gameId)` – Boxscore and team/player stats for a game
112
- - `landing(gameId)` – Game landing page data (summary, lines, etc.)
113
- - `reports(gameId)` – Game reports / right-rail data
114
- - `schedule(date?)` – Schedule for a given date (defaults to today)
115
- - `scheduleCalendar(date?)` – Calendar-style schedule for a date
116
- - `playoffBracket(year?)` – Playoff bracket for a given year
117
- - `playoffSeries(season?)` – Playoff series information for a season
118
- - `playoffSeriesSchedule(season, seriesLetter)` – Schedule for a playoff series
119
- - `whereToWatch()` – Regional broadcast / streaming info
120
- - `networkTVSchedule(date?)` – National TV schedule
121
- - `wsc.gameStory(gameId)` – Game story from the web service collection
122
- - `wsc.playByPlay(gameId)` – Play-by-play from the web service collection
123
- - `pptReplay.goal(gameId, eventId)` – Goal replay data for a specific event
124
- - `pptReplay.event(date?)` – Replay events for a given date (does not appear to be functional at this time)
125
-
126
- ### `gc.score` – Scores & Scoreboards
127
-
128
- - `score(date?)` – Scores for a given date (defaults to today)
129
- - `scoreboard()` – Current live scoreboard
130
-
131
- ### `gc.team` – Team Data
132
-
133
- - `roster(teamCode, season?)` – Team roster for a given season
134
- - `rosterSeasons(teamCode)` – Available roster seasons for a team
135
- - `prospects(teamCode)` – Team prospects
136
- - `clubStats(teamCode, season?)` – Club stats for a team/season
137
- - `clubStatsSeason(teamCode)` – Season-level club stats
138
- - `standings(date?)` – Standings for a given date
139
- - `standingsSeason()` – Season standings
140
- - `schedule.now(teamCode)` – Current schedule segment for a team
141
- - `schedule.month(teamCode, month)` – Team schedule for a given month
142
- - `schedule.season(teamCode, season)` – Full season schedule for a team
143
-
144
- ### `gc.player` – Player Data
145
-
146
- - `landing(playerId)` – Player landing page data
147
- - `gameLog(playerId, season?, gameType?)` – Game log for a player
148
- - `spotlight()` – Featured players (spotlight carousel)
149
- - `search(query)` – Player search
150
- - `statsLeaders.season(season?, gameType?, category?, limit?)` – Season stat leaders
151
- - `statsLeaders.current(gameType?, category?, limit?)` – Current stat leaders
152
-
153
- ### `gc.draft` – Draft Data
154
-
155
- - `picks(year?)` – Draft picks for a given year
156
- - `tracker()` – Draft tracker
157
- - `rankings(year?)` – Draft rankings (all, NA/International, skaters/goalies)
158
-
159
- ### `gc.misc` – Miscellaneous
160
-
161
- - `seasons()` – All NHL seasons
162
- - `meta.game(gameId)` – Game metadata
163
- - `meta.gameVideo(gameId)` – Game video metadata
164
- - `postalLookup(postalCode)` – Postal/ZIP lookup (location info)
165
- - `location()` – Location info for current context
166
- - `partnerGame(country, date?)` – Partner game information
167
-
168
- ## Types
169
-
170
- The library exports strongly-typed response shapes and parameter types for all Game Center endpoints from the `types` bundle:
171
-
172
- - Response types for `gc.game.*`, `gc.team.*`, `gc.player.*`, `gc.draft.*`, `gc.misc.*`
173
- - Common enums and helper types (position codes, team codes, schedule state, etc.)
174
-
175
-
176
-
177
- ### Error Handling and `NHLError`
178
-
179
- Under the hood, low-level HTTP requests use a shared `NHLClient` and `ErrorHandler` which normalize HTTP/network failures into a single `NHLError` shape.
180
-
181
- - All non-2xx responses and network/timeout failures are converted to an `NHLError` instance.
182
- - Errors are categorized (client/server/network) with optional context (endpoint, method, status code).
183
- - High-level helpers expose these via the `APIResponse<T>` union so you can safely branch on `status` without catching exceptions.
184
-
185
- Typical usage pattern:
186
-
187
- ```ts
188
- import { gc } from 'nhle-api';
189
- import type { APIResponse, GamecenterLanding } from 'nhle-api';
190
-
191
- const result: APIResponse<GamecenterLanding> = await gc.game.landing(2023020001);
192
-
193
- if (result.success) {
194
- console.log(result.data);
195
- } else {
196
- // NHLError instance with rich metadata
197
- console.error(result.error.message);
198
- }
199
- ```
200
-
201
- ## Roadmap / Status
202
-
203
- - ✅ Game Center endpoints fully wired with response types
204
- - ✅ Environment configuration utilities (`envConfig`, `logEnvConfig`)
205
- - ✅ Error handling via `NHLError` and structured error responses internally
206
- - 🚧 Edge Advanced (`adv`) API surface and types are in development
207
- - ⏳ Edge Stats (`stats`) API will be added in a future minor release
208
-
209
- For a detailed list of changes and recent work on response types and endpoint coverage, see `CHANGELOG.md`.
210
-
211
- ## Docs
212
-
213
- - [Environment Configuration](docs/ENVIRONMENT_CONFIG.md) - Environment configuration and default values
214
- - [Query Builder Guide](docs/QUERY_BUILDER_GUIDE.md) - Cayenne query builder for stats APIs
215
-
216
- ## Contributing
217
-
218
- This project is still evolving and feedback is very welcome.
219
-
220
- - **Bug reports / issues:** If an endpoint returns unexpected data, response parsing fails, or types don’t match the real API, please open a GitHub issue with the endpoint, parameters, and (sanitized) sample payloads if possible.
221
- - **Type improvements:** PRs or issues that improve response coverage, fix missing/incorrect fields, or refine enums and unions are particularly helpful.
222
- - **JSDoc / docs:** Additions or corrections to JSDoc on public types and functions, or clarifications to the guides in `docs/`, are also very much appreciated.
223
-
224
- Before filing an issue, check the `CHANGELOG.md` for recent changes and breaking notes, and include the library version you’re using.
225
-
226
- ## Support
227
-
228
- If you find this library helpful, consider supporting its development:
229
-
230
- [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/R6R01DV0JD)
231
-
232
- ## License
233
-
234
- This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
1
+ # NHLe API Library
2
+
3
+ [![npm version](https://img.shields.io/npm/v/nhle-api.svg)](https://www.npmjs.com/package/nhle-api)
4
+ [![npm downloads](https://img.shields.io/npm/dm/nhle-api.svg)](https://www.npmjs.com/package/nhle-api)
5
+ [![license](https://img.shields.io/github/license/spiflicate/nhle-api.svg)](LICENSE)
6
+
7
+ A modern TypeScript wrapper around the public NHL GameCenter and EdgeStats APIs with simple, composable functions and strong TypeScript typing.
8
+
9
+ ## Overview
10
+
11
+ The library exposes a small, functional surface over the NHL "Game Center", Edge and Stats APIs and ships with response types for every endpoint.
12
+
13
+ - Written in TypeScript and published as ESM/CJS
14
+ - Thin functional wrappers over official NHL API routes
15
+ - Typed responses for Game Center (`gc`), Edge advanced stats (`adv`), the Stats API (`stats`) and video metadata (`video`)
16
+ - Every function resolves to an `APIResult` instead of throwing
17
+ - Runtime configuration for timeouts, language and logging
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install nhle-api
23
+ ```
24
+
25
+ The package ships both ESM and CJS builds; use standard `import`/`require` according to your toolchain.
26
+
27
+ ## Quick Start
28
+
29
+ The primary entrypoint is the Game Center API namespace `gc`:
30
+
31
+ ```ts
32
+ import { gc } from 'nhle-api';
33
+
34
+ // Get game landing page data
35
+ const gameInfo = await gc.game.landing(2023020001);
36
+
37
+ // Get live play-by-play
38
+ const playByPlay = await gc.game.playByPlay(2023020001);
39
+
40
+ // Get boxscore
41
+ const boxscore = await gc.game.boxscore(2023020001);
42
+
43
+ // Get today's scores
44
+ const scores = await gc.score(new Date());
45
+
46
+ // Get current scoreboard
47
+ const scoreboard = await gc.scoreboard();
48
+
49
+ // Get team roster
50
+ const roster = await gc.team.roster('TOR');
51
+
52
+ // Get player landing page (Connor McDavid)
53
+ const player = await gc.player.landing(8478402);
54
+ ```
55
+
56
+ ## Configuration
57
+
58
+ `config` holds the shared defaults. Change it at runtime; every request reads it, so changes apply even after the API modules are loaded:
59
+
60
+ ```ts
61
+ import { config, logConfig } from 'nhle-api';
62
+
63
+ config.timeout = 10000; // ms, default 5000
64
+ config.language = 'fr'; // 'en' (default) or 'fr'
65
+ config.logLevel = 'error'; // 'silent' | 'error' | 'warn' (default) | 'info' | 'debug'
66
+
67
+ logConfig();
68
+ ```
69
+
70
+ See `docs/CONFIGURATION.md` for per-client settings.
71
+
72
+ ## Top-Level Exports
73
+
74
+ The package root `nhle-api` re-exports the API namespaces, configuration, the HTTP client, error classes, constants and types:
75
+
76
+ ```ts
77
+ // API namespaces
78
+ import { adv, gc, stats, video } from 'nhle-api';
79
+
80
+ // Configuration and logging
81
+ import { config, logConfig, logger } from 'nhle-api';
82
+
83
+ // Result type and errors
84
+ import { NHLError, NotFoundError, ValidationError } from 'nhle-api';
85
+ import type { APIResult } from 'nhle-api';
86
+
87
+ // Your own client (custom base URL, headers, timeout, error handling)
88
+ import { NHLClient, createNHLClient } from 'nhle-api';
89
+
90
+ // Cayenne filter builder for `stats`
91
+ import { CayenneQueryBuilder } from 'nhle-api';
92
+
93
+ // Response and parameter types
94
+ import type { EdgeSkaterDetail, GamecenterLanding, Season } from 'nhle-api';
95
+ ```
96
+
97
+ ## Toolkit Entry Point
98
+
99
+ Use the `nhle-api/toolkit` subpath when you only need NHL constants and shared helpers. It does not load the API clients or configuration:
100
+
101
+ ```ts
102
+ import {
103
+ NHL,
104
+ getCurrentSeason,
105
+ normalizeAbbrev,
106
+ resolvePath,
107
+ } from 'nhle-api/toolkit';
108
+
109
+ const season = getCurrentSeason();
110
+ const team = normalizeAbbrev('TB'); // 'TBL'
111
+ const teamPath = resolvePath('/teams/{team}', { team });
112
+ ```
113
+
114
+ This entry point exports `NHL`, date helpers, `normalizeAbbrev`, `resolvePath`,
115
+ the Cayenne query builder, team branding data, historical team palettes, and
116
+ official logo URL helpers.
117
+
118
+ ## Game Center API (`gc`)
119
+
120
+ `gc` is the primary namespace for accessing live and historical NHL data.
121
+
122
+ ### `gc.game` – Game Data
123
+
124
+ - `playByPlay(gameId)` – Full live play-by-play event stream
125
+ - `boxscore(gameId)` – Boxscore and team/player stats for a game
126
+ - `landing(gameId)` – Game landing page data (summary, lines, etc.)
127
+ - `reports(gameId)` – Game reports / right-rail data
128
+ - `schedule(date?)` – Schedule for a given date (defaults to today)
129
+ - `scheduleCalendar(date?)` – Calendar-style schedule for a date
130
+ - `playoffBracket(year?)` – Playoff bracket for a given year
131
+ - `playoffSeries(season?)` – Playoff series information for a season
132
+ - `playoffSeriesSchedule(season, seriesLetter)` – Schedule for a playoff series
133
+ - `whereToWatch()` – Regional broadcast / streaming info (deprecated: the NHL retired this endpoint and it returns 404)
134
+ - `networkTVSchedule(date?)` – National TV schedule
135
+ - `wsc.gameStory(gameId)` – Game story from the web service collection
136
+ - `wsc.playByPlay(gameId)` – Play-by-play from the web service collection
137
+ - `pptReplay.goal(gameId, eventId)` – Goal replay data for a specific event
138
+ - `pptReplay.event(date?)` – Replay events for a given date (does not appear to be functional at this time)
139
+
140
+ ### `gc.score` – Scores & Scoreboards
141
+
142
+ - `score(date?)` – Scores for a given date (defaults to today)
143
+ - `scoreboard()` – Current live scoreboard
144
+
145
+ ### `gc.team` – Team Data
146
+
147
+ - `roster(teamCode, season?)` – Team roster for a given season
148
+ - `rosterSeasons(teamCode)` – Available roster seasons for a team
149
+ - `prospects(teamCode)` – Team prospects
150
+ - `clubStats(teamCode, season?)` – Club stats for a team/season
151
+ - `clubStatsSeason(teamCode)` – Season-level club stats
152
+ - `standings(date?)` – Standings for a given date
153
+ - `standingsSeason()` – Season standings
154
+ - `schedule.now(teamCode)` – Current schedule segment for a team
155
+ - `schedule.month(teamCode, month)` – Team schedule for a given month
156
+ - `schedule.season(teamCode, season)` – Full season schedule for a team
157
+
158
+ ### `gc.player` – Player Data
159
+
160
+ - `landing(playerId)` – Player landing page data
161
+ - `gameLog(playerId, season?, gameType?)` – Game log for a player
162
+ - `spotlight()` – Featured players (spotlight carousel)
163
+ - `search(query)` – Player search
164
+ - `statsLeaders.season(season?, gameType?, category?, limit?)` – Season stat leaders
165
+ - `statsLeaders.current(gameType?, category?, limit?)` – Current stat leaders
166
+
167
+ ### `gc.draft` – Draft Data
168
+
169
+ - `picks(year?)` – Draft picks for a given year
170
+ - `tracker()` – Draft tracker
171
+ - `rankings(year?)` – Draft rankings (all, NA/International, skaters/goalies)
172
+
173
+ ### `gc.misc` – Miscellaneous
174
+
175
+ - `seasons()` – All NHL seasons
176
+ - `meta.game(gameId)` – Game metadata
177
+ - `meta.gameVideo(gameId)` – Game video metadata
178
+ - `postalLookup(postalCode)` – Postal/ZIP lookup (location info)
179
+ - `location()` – Location info for current context
180
+ - `partnerGame(country, date?)` – Partner game information
181
+
182
+ ## Edge Advanced Stats (`adv`)
183
+
184
+ NHL Edge puck and player tracking data (`api-web.nhle.com/v1/edge`), with response types for every function (`EdgeSkaterDetail`, `EdgeTeamZoneTime`, ...).
185
+
186
+ - `adv.skaters`: `detail`, `comparison`, `leaders`, `shotLocation`, `shotSpeed`, `skatingDistance`, `skatingSpeed`, `zoneTime`, and `top10.{distance, shotLocation, shotSpeed, speed, zoneTime}`
187
+ - `adv.goalies`: `player`, `compare`, `leaders`, `savePercentage`, `savePercentage5v5`, `saveLocation`, and `top10.{savePercentage, savePercentage5v5, saveLocation}`
188
+ - `adv.teams`: `stats`, `compare`, `leaders`, `shotLocation`, `shotSpeed`, `skatingDistance`, `skatingSpeed`, `zoneTime`, and `top10.{shotLocation, shotSpeed, skatingDistance, skatingSpeed, zoneTime}`
189
+ - `adv.byTheNumbers()`: the Edge home page's daily highlights
190
+
191
+ ```ts
192
+ const result = await adv.skaters.detail(8478402, 20242025, 2);
193
+ if (result.success) console.log(result.data.skatingSpeed.speedMax.imperial);
194
+ ```
195
+
196
+ ## Stats API (`stats`)
197
+
198
+ The stats.nhl.com reports (`api.nhle.com/stats/rest`). List endpoints answer `{ data, total }` and accept `cayenneExp`, `sort`, `dir`, `limit` and `start`.
199
+
200
+ - `stats.skaters` / `stats.goalies` / `stats.teams`: `getStats(report, params)` for any report (`summary`, `realtime`, `powerplay`...; `SkaterReport`, `GoalieReport` and `TeamReport` list them), plus `getStatsWithParams`, `getStatsWithBuilder` and `getStatsWithFilters`
201
+ - `stats.skaters.getPlayerInfo(params)`, `getLeaders(category, params)`, `getMilestones()`; the same leaders and milestones for goalies
202
+ - `stats.teams.getAll(params)`, `getById(teamId, params)`
203
+ - `stats.season`: `getSeasons()`, `getGames(params)`, `getShiftChart(gameId)`, `getDraft()`
204
+ - `stats.misc`: `getConfig()` (every report's columns), `getCountries()`, `getGlossary()`, `getFranchises()`
205
+
206
+ ```ts
207
+ import { stats } from 'nhle-api';
208
+
209
+ const result = await stats.skaters.getStats('summary', {
210
+ cayenneExp: 'seasonId=20242025 and gameTypeId=2',
211
+ sort: 'points',
212
+ dir: 'desc',
213
+ limit: 10,
214
+ });
215
+
216
+ // Or with the query builder
217
+ const goalies = await stats.goalies.getStatsWithBuilder('summary', (q) => ({
218
+ cayenneExp: q.equals('seasonId', 20242025).greaterThan('gamesPlayed', 20).build(),
219
+ sort: 'savePct',
220
+ dir: 'desc',
221
+ }));
222
+ ```
223
+
224
+ See [docs/QUERY_BUILDER_GUIDE.md](docs/QUERY_BUILDER_GUIDE.md) for the builder.
225
+
226
+ ## Video (`video`)
227
+
228
+ - `video.metadata(videoId)`: title, descriptions, duration, poster and playable sources (with the best MP4 as `url`) for a Brightcove video id, such as a goal's `highlightClip` in `gc.game.landing`
229
+
230
+ ## Types
231
+
232
+ Response types are exported for every endpoint: Game Center (`GamecenterLanding`, `TeamRoster`...), Edge (`Edge*`) and the Stats API (`stats.SkaterStats`, `stats.Team`...), along with parameter types (`Season`, `GameId`, `TeamAbbrev`...) and shared shapes such as `LocalizedText`.
233
+
234
+ ### Results and errors
235
+
236
+ Functions never throw for API failures. Each resolves to an `APIResult<T>`:
237
+
238
+ - `{ success: true, data }` on success.
239
+ - `{ success: false, error }` otherwise, where `error` is an `NHLError` subclass: `NotFoundError` (404), `ClientError` (other 4xx), `ServerError` (5xx), `RateLimitError` (429), `NetworkError`, or `ValidationError` for parameters rejected before any request.
240
+ - `error.category` and `error.context` (endpoint, status code, response body) carry the details.
241
+
242
+ ```ts
243
+ import { gc, NotFoundError } from 'nhle-api';
244
+ import type { APIResult, GamecenterLanding } from 'nhle-api';
245
+
246
+ const result: APIResult<GamecenterLanding> = await gc.game.landing(2023020001);
247
+
248
+ if (result.success) {
249
+ console.log(result.data.venue.default);
250
+ } else if (result.error instanceof NotFoundError) {
251
+ console.log('No such game');
252
+ } else {
253
+ console.error(result.error.category, result.error.message);
254
+ }
255
+ ```
256
+
257
+ ## Roadmap / Status
258
+
259
+ - ✅ Game Center endpoints with response types
260
+ - ✅ Edge Advanced (`adv`) endpoints with response types
261
+ - ✅ Stats API (`stats`) endpoints with typed reports
262
+ - ✅ Runtime configuration (`config`) and per-client settings (`NHLClient`)
263
+ - ✅ Error classes and `APIResult` exported
264
+ - ✅ Daily drift check against the live APIs
265
+
266
+ For a detailed list of changes, see `CHANGELOG.md`.
267
+
268
+ ## Docs
269
+
270
+ - [Configuration](docs/CONFIGURATION.md) - Code-defined configuration and default values
271
+ - [Query Builder Guide](docs/QUERY_BUILDER_GUIDE.md) - Cayenne query builder for stats APIs
272
+
273
+ ## Contributing
274
+
275
+ This project is still evolving and feedback is very welcome.
276
+
277
+ - **Bug reports / issues:** If an endpoint returns unexpected data, response parsing fails, or types don’t match the real API, please open a GitHub issue with the endpoint, parameters, and (sanitized) sample payloads if possible.
278
+ - **Type improvements:** PRs or issues that improve response coverage, fix missing/incorrect fields, or refine enums and unions are particularly helpful.
279
+ - **JSDoc / docs:** Additions or corrections to JSDoc on public types and functions, or clarifications to the guides in `docs/`, are also very much appreciated.
280
+
281
+ Before filing an issue, check the `CHANGELOG.md` for recent changes and breaking notes, and include the library version you’re using.
282
+
283
+ ## API drift check
284
+
285
+ The NHL APIs are undocumented and change without notice. `bun run drift` calls every public function against the live APIs (with a finished season, game and draft) and compares each response's structure with `scripts/drift/baseline.json`. It fails when an endpoint stops answering, an error response changes, or a field is added, removed or changes type. Values are ignored, and so are fields that turn `null`. Translations in localized strings (`{ default, fr, cs, ... }`) count as one field.
286
+
287
+ The **API drift** workflow runs it daily and on demand (Actions → API drift → Run workflow). A failure opens one `api-drift` issue with the report, which the next passing run closes. It never runs on PRs, so upstream changes don't block unrelated work.
288
+
289
+ When an API really has changed:
290
+
291
+ 1. Update the response types in `src/types/responses/`.
292
+ 2. Accept the new shapes with `bun run drift --update` and commit the baseline.
293
+
294
+ Checks for date- or location-dependent data (scoreboard, draft tracker) are marked `volatile`: a field missing today is only noted, and `--update` adds to their baseline instead of replacing it. When you add a library function, add a check in `scripts/drift/checks.ts`; `test/unit/drift.test.ts` fails if a public function has none.
295
+
296
+ ## Releasing
297
+
298
+ 1. Move the `Unreleased` notes in `CHANGELOG.md` under the new version.
299
+ 2. Bump the version and tag it: `npm version patch` (or `minor`/`major`).
300
+ 3. Push the commit and tag: `git push --follow-tags`.
301
+
302
+ 4. Approve the staged version with 2FA, on npmjs.com (the package's **Staged Packages** tab) or with `npm stage approve <stage-id>`.
303
+
304
+ The **Release** workflow checks that the tag matches `package.json`, runs the full CI suite and `check:package`, stages the version on npm with provenance, and creates a GitHub release. It uses [npm trusted publishing](https://docs.npmjs.com/trusted-publishers), so there is no npm token in the repo, and [staged publishing](https://docs.npmjs.com/staged-publishing), so nothing goes live until a maintainer approves it.
305
+
306
+ One-time setup on npmjs.com:
307
+
308
+ 1. Open the `nhle-api` package's **Settings → Trusted publishing** and add GitHub Actions with user `spiflicate`, repository `nhle-api` and workflow `release.yml`. Leave "Allow npm publish" and "Allow npm dist-tag" unchecked, so CI can only stage.
309
+ 2. Under **Publishing access**, choose "Require two-factor authentication and disallow tokens".
310
+
311
+ ## Support
312
+
313
+ If you find this library helpful, consider supporting its development:
314
+
315
+ [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/R6R01DV0JD)
316
+
317
+ ## License
318
+
319
+ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.