@lgriffin/esi.ts 9.6.0 → 10.0.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.
Files changed (259) hide show
  1. package/CHANGELOG.md +787 -388
  2. package/LICENSE +26 -26
  3. package/README.md +974 -1030
  4. package/dist/EsiClient.d.ts +4 -0
  5. package/dist/EsiClient.d.ts.map +1 -1
  6. package/dist/EsiClientBuilder.d.ts.map +1 -1
  7. package/dist/auth/EsiTokenManager.d.ts +200 -0
  8. package/dist/auth/EsiTokenManager.d.ts.map +1 -0
  9. package/dist/auth/EveSsoClient.d.ts +88 -0
  10. package/dist/auth/EveSsoClient.d.ts.map +1 -0
  11. package/dist/auth/errors.d.ts +42 -0
  12. package/dist/auth/errors.d.ts.map +1 -0
  13. package/dist/auth/index.d.ts +14 -0
  14. package/dist/auth/index.d.ts.map +1 -0
  15. package/dist/auth/jwt.d.ts +43 -0
  16. package/dist/auth/jwt.d.ts.map +1 -0
  17. package/dist/auth/pkce.d.ts +21 -0
  18. package/dist/auth/pkce.d.ts.map +1 -0
  19. package/dist/auth/storage/FileTokenStorage.d.ts +44 -0
  20. package/dist/auth/storage/FileTokenStorage.d.ts.map +1 -0
  21. package/dist/auth/storage/MemoryTokenStorage.d.ts +18 -0
  22. package/dist/auth/storage/MemoryTokenStorage.d.ts.map +1 -0
  23. package/dist/auth/types.d.ts +42 -0
  24. package/dist/auth/types.d.ts.map +1 -0
  25. package/dist/chunk-3ZI5A37L.mjs +1065 -0
  26. package/dist/chunk-3ZI5A37L.mjs.map +1 -0
  27. package/dist/chunk-7A72QFDF.mjs +416 -0
  28. package/dist/chunk-7A72QFDF.mjs.map +1 -0
  29. package/dist/chunk-BBSHKVPI.js +71 -0
  30. package/dist/chunk-BBSHKVPI.js.map +1 -0
  31. package/dist/chunk-HPSACXX3.js +416 -0
  32. package/dist/chunk-HPSACXX3.js.map +1 -0
  33. package/dist/chunk-JIW4BXLP.mjs +17 -0
  34. package/dist/chunk-JIW4BXLP.mjs.map +1 -0
  35. package/dist/chunk-MSJHIJXA.js +1065 -0
  36. package/dist/chunk-MSJHIJXA.js.map +1 -0
  37. package/dist/chunk-PZ5AY32C.js +10 -0
  38. package/dist/chunk-PZ5AY32C.js.map +1 -0
  39. package/dist/chunk-R7D7M2LQ.mjs +71 -0
  40. package/dist/chunk-R7D7M2LQ.mjs.map +1 -0
  41. package/dist/chunk-SAQBXXTM.js +2614 -0
  42. package/dist/chunk-SAQBXXTM.js.map +1 -0
  43. package/dist/chunk-VZS32UQ3.mjs +2614 -0
  44. package/dist/chunk-VZS32UQ3.mjs.map +1 -0
  45. package/dist/clients/AllianceClient.d.ts +2 -0
  46. package/dist/clients/AllianceClient.d.ts.map +1 -1
  47. package/dist/clients/AssetsClient.d.ts +2 -0
  48. package/dist/clients/AssetsClient.d.ts.map +1 -1
  49. package/dist/clients/BaseEsiClient.d.ts +1 -0
  50. package/dist/clients/BaseEsiClient.d.ts.map +1 -1
  51. package/dist/clients/CalendarClient.d.ts +2 -0
  52. package/dist/clients/CalendarClient.d.ts.map +1 -1
  53. package/dist/clients/CharacterClient.d.ts +8 -0
  54. package/dist/clients/CharacterClient.d.ts.map +1 -1
  55. package/dist/clients/ClonesClient.d.ts +1 -0
  56. package/dist/clients/ClonesClient.d.ts.map +1 -1
  57. package/dist/clients/ContactsClient.d.ts +6 -0
  58. package/dist/clients/ContactsClient.d.ts.map +1 -1
  59. package/dist/clients/ContractsClient.d.ts +14 -8
  60. package/dist/clients/ContractsClient.d.ts.map +1 -1
  61. package/dist/clients/CorporationProjectsClient.d.ts +22 -12
  62. package/dist/clients/CorporationProjectsClient.d.ts.map +1 -1
  63. package/dist/clients/CorporationsClient.d.ts +17 -0
  64. package/dist/clients/CorporationsClient.d.ts.map +1 -1
  65. package/dist/clients/FactionClient.d.ts +4 -4
  66. package/dist/clients/FactionClient.d.ts.map +1 -1
  67. package/dist/clients/FittingsClient.d.ts +1 -0
  68. package/dist/clients/FittingsClient.d.ts.map +1 -1
  69. package/dist/clients/FleetClient.d.ts +2 -0
  70. package/dist/clients/FleetClient.d.ts.map +1 -1
  71. package/dist/clients/FreelanceJobsClient.d.ts +3 -3
  72. package/dist/clients/FreelanceJobsClient.d.ts.map +1 -1
  73. package/dist/clients/IndustryClient.d.ts +11 -3
  74. package/dist/clients/IndustryClient.d.ts.map +1 -1
  75. package/dist/clients/KillmailsClient.d.ts +2 -0
  76. package/dist/clients/KillmailsClient.d.ts.map +1 -1
  77. package/dist/clients/LoyaltyClient.d.ts +2 -0
  78. package/dist/clients/LoyaltyClient.d.ts.map +1 -1
  79. package/dist/clients/MailClient.d.ts +8 -3
  80. package/dist/clients/MailClient.d.ts.map +1 -1
  81. package/dist/clients/MarketClient.d.ts +6 -0
  82. package/dist/clients/MarketClient.d.ts.map +1 -1
  83. package/dist/clients/MetaClient.d.ts +3 -2
  84. package/dist/clients/MetaClient.d.ts.map +1 -1
  85. package/dist/clients/PiClient.d.ts +2 -0
  86. package/dist/clients/PiClient.d.ts.map +1 -1
  87. package/dist/clients/SkillsClient.d.ts +1 -0
  88. package/dist/clients/SkillsClient.d.ts.map +1 -1
  89. package/dist/clients/WalletClient.d.ts +7 -3
  90. package/dist/clients/WalletClient.d.ts.map +1 -1
  91. package/dist/clients/WarsClient.d.ts +2 -0
  92. package/dist/clients/WarsClient.d.ts.map +1 -1
  93. package/dist/core/ApiClient.d.ts +8 -0
  94. package/dist/core/ApiClient.d.ts.map +1 -1
  95. package/dist/core/ApiClientBuilder.d.ts +3 -1
  96. package/dist/core/ApiClientBuilder.d.ts.map +1 -1
  97. package/dist/core/ApiRequestHandler.d.ts.map +1 -1
  98. package/dist/core/BatchRequestHandler.d.ts.map +1 -1
  99. package/dist/core/RequestDeduplicator.d.ts +2 -0
  100. package/dist/core/RequestDeduplicator.d.ts.map +1 -1
  101. package/dist/core/RetryStrategy.d.ts +2 -0
  102. package/dist/core/RetryStrategy.d.ts.map +1 -1
  103. package/dist/core/cache/ETagCacheManager.d.ts +4 -0
  104. package/dist/core/cache/ETagCacheManager.d.ts.map +1 -1
  105. package/dist/core/circuitBreaker/CircuitBreaker.d.ts +3 -0
  106. package/dist/core/circuitBreaker/CircuitBreaker.d.ts.map +1 -1
  107. package/dist/core/configureApiClient.d.ts.map +1 -1
  108. package/dist/core/constants.d.ts +2 -2
  109. package/dist/core/constants.d.ts.map +1 -1
  110. package/dist/core/endpoints/EndpointDefinition.d.ts +2 -2
  111. package/dist/core/endpoints/EndpointDefinition.d.ts.map +1 -1
  112. package/dist/core/endpoints/calendarEndpoints.d.ts +7 -7
  113. package/dist/core/endpoints/characterEndpoints.d.ts +2 -2
  114. package/dist/core/endpoints/cloneEndpoints.d.ts +2 -2
  115. package/dist/core/endpoints/contractEndpoints.d.ts +15 -22
  116. package/dist/core/endpoints/contractEndpoints.d.ts.map +1 -1
  117. package/dist/core/endpoints/corporationEndpoints.d.ts +19 -22
  118. package/dist/core/endpoints/corporationEndpoints.d.ts.map +1 -1
  119. package/dist/core/endpoints/corporationProjectEndpoints.d.ts +75 -23
  120. package/dist/core/endpoints/corporationProjectEndpoints.d.ts.map +1 -1
  121. package/dist/core/endpoints/createClient.d.ts +2 -2
  122. package/dist/core/endpoints/createClient.d.ts.map +1 -1
  123. package/dist/core/endpoints/dogmaEndpoints.d.ts +2 -2
  124. package/dist/core/endpoints/factionEndpoints.d.ts +36 -36
  125. package/dist/core/endpoints/factionEndpoints.d.ts.map +1 -1
  126. package/dist/core/endpoints/fleetEndpoints.d.ts +1 -1
  127. package/dist/core/endpoints/freelanceJobsEndpoints.d.ts +131 -122
  128. package/dist/core/endpoints/freelanceJobsEndpoints.d.ts.map +1 -1
  129. package/dist/core/endpoints/industryEndpoints.d.ts +6 -6
  130. package/dist/core/endpoints/industryEndpoints.d.ts.map +1 -1
  131. package/dist/core/endpoints/mailEndpoints.d.ts +3 -5
  132. package/dist/core/endpoints/mailEndpoints.d.ts.map +1 -1
  133. package/dist/core/endpoints/mercenaryEndpoints.d.ts +1 -1
  134. package/dist/core/endpoints/metaEndpoints.d.ts +5 -1
  135. package/dist/core/endpoints/metaEndpoints.d.ts.map +1 -1
  136. package/dist/core/endpoints/piEndpoints.d.ts +2 -2
  137. package/dist/core/endpoints/statusEndpoints.d.ts +1 -1
  138. package/dist/core/endpoints/universeEndpoints.d.ts +22 -22
  139. package/dist/core/endpoints/walletEndpoints.d.ts +2 -3
  140. package/dist/core/endpoints/walletEndpoints.d.ts.map +1 -1
  141. package/dist/core/logger/DefaultLogger.d.ts +31 -0
  142. package/dist/core/logger/DefaultLogger.d.ts.map +1 -0
  143. package/dist/core/logger/ILogger.d.ts +14 -4
  144. package/dist/core/logger/ILogger.d.ts.map +1 -1
  145. package/dist/core/logger/NoopLogger.d.ts +7 -0
  146. package/dist/core/logger/NoopLogger.d.ts.map +1 -0
  147. package/dist/core/logger/clientLog.d.ts +11 -0
  148. package/dist/core/logger/clientLog.d.ts.map +1 -0
  149. package/dist/core/logger/logger.d.ts +11 -3
  150. package/dist/core/logger/logger.d.ts.map +1 -1
  151. package/dist/core/logger/loggerUtil.d.ts +8 -6
  152. package/dist/core/logger/loggerUtil.d.ts.map +1 -1
  153. package/dist/core/logger/resolveLogger.d.ts +10 -0
  154. package/dist/core/logger/resolveLogger.d.ts.map +1 -0
  155. package/dist/core/pagination/AsyncPaginationIterator.d.ts +1 -0
  156. package/dist/core/pagination/AsyncPaginationIterator.d.ts.map +1 -1
  157. package/dist/core/pagination/CursorPaginationHandler.d.ts.map +1 -1
  158. package/dist/core/pagination/PaginationHandler.d.ts.map +1 -1
  159. package/dist/core/rateLimiter/RateLimiter.d.ts +6 -0
  160. package/dist/core/rateLimiter/RateLimiter.d.ts.map +1 -1
  161. package/dist/core/requestPipeline/cachePolicy.d.ts +14 -0
  162. package/dist/core/requestPipeline/cachePolicy.d.ts.map +1 -1
  163. package/dist/core/requestPipeline/fetchExecution.d.ts +1 -1
  164. package/dist/core/requestPipeline/fetchExecution.d.ts.map +1 -1
  165. package/dist/core/requestPipeline/headers.d.ts.map +1 -1
  166. package/dist/core/requestPipeline/index.d.ts +2 -2
  167. package/dist/core/requestPipeline/index.d.ts.map +1 -1
  168. package/dist/core/requestPipeline/paginationOrchestration.d.ts.map +1 -1
  169. package/dist/core/requestPipeline/statusHandling.d.ts +8 -2
  170. package/dist/core/requestPipeline/statusHandling.d.ts.map +1 -1
  171. package/dist/core/util/concurrency.d.ts +29 -0
  172. package/dist/core/util/concurrency.d.ts.map +1 -0
  173. package/dist/errors.d.ts +1 -0
  174. package/dist/errors.d.ts.map +1 -1
  175. package/dist/errors.js +53 -195
  176. package/dist/errors.js.map +1 -1
  177. package/dist/errors.mjs +37 -129
  178. package/dist/errors.mjs.map +1 -1
  179. package/dist/index.d.ts +10 -5
  180. package/dist/index.d.ts.map +1 -1
  181. package/dist/index.js +2597 -3480
  182. package/dist/index.js.map +1 -1
  183. package/dist/index.mjs +2250 -3024
  184. package/dist/index.mjs.map +1 -1
  185. package/dist/schemas/calendar.d.ts +9 -7
  186. package/dist/schemas/calendar.d.ts.map +1 -1
  187. package/dist/schemas/character.d.ts +2 -2
  188. package/dist/schemas/clones.d.ts +2 -2
  189. package/dist/schemas/common.d.ts +11 -1
  190. package/dist/schemas/common.d.ts.map +1 -1
  191. package/dist/schemas/contracts.d.ts +69 -6
  192. package/dist/schemas/contracts.d.ts.map +1 -1
  193. package/dist/schemas/corporation-projects.d.ts +96 -9
  194. package/dist/schemas/corporation-projects.d.ts.map +1 -1
  195. package/dist/schemas/corporation.d.ts +34 -22
  196. package/dist/schemas/corporation.d.ts.map +1 -1
  197. package/dist/schemas/dogma.d.ts +2 -2
  198. package/dist/schemas/faction-warfare.d.ts +125 -12
  199. package/dist/schemas/faction-warfare.d.ts.map +1 -1
  200. package/dist/schemas/fleet.d.ts +1 -1
  201. package/dist/schemas/freelance-jobs.d.ts +52 -22
  202. package/dist/schemas/freelance-jobs.d.ts.map +1 -1
  203. package/dist/schemas/index.js +420 -2474
  204. package/dist/schemas/index.js.map +1 -1
  205. package/dist/schemas/index.mjs +225 -2060
  206. package/dist/schemas/index.mjs.map +1 -1
  207. package/dist/schemas/industry.d.ts +33 -0
  208. package/dist/schemas/industry.d.ts.map +1 -1
  209. package/dist/schemas/mail.d.ts +24 -5
  210. package/dist/schemas/mail.d.ts.map +1 -1
  211. package/dist/schemas/mercenary.d.ts +1 -1
  212. package/dist/schemas/meta.d.ts +12 -1
  213. package/dist/schemas/meta.d.ts.map +1 -1
  214. package/dist/schemas/pi.d.ts +2 -2
  215. package/dist/schemas/status.d.ts +1 -1
  216. package/dist/schemas/universe.d.ts +22 -22
  217. package/dist/schemas/universe.d.ts.map +1 -1
  218. package/dist/schemas/wallet.d.ts +16 -0
  219. package/dist/schemas/wallet.d.ts.map +1 -1
  220. package/dist/sde/SdeDataProvider.d.ts.map +1 -1
  221. package/dist/sde/index.js +95 -1162
  222. package/dist/sde/index.js.map +1 -1
  223. package/dist/sde/index.mjs +73 -1091
  224. package/dist/sde/index.mjs.map +1 -1
  225. package/dist/sde/ingestion/SdeExtractor.d.ts.map +1 -1
  226. package/dist/sde/ingestion/metadata.d.ts +13 -0
  227. package/dist/sde/ingestion/metadata.d.ts.map +1 -0
  228. package/dist/sde/memory.js +21 -1095
  229. package/dist/sde/memory.js.map +1 -1
  230. package/dist/sde/memory.mjs +13 -1051
  231. package/dist/sde/memory.mjs.map +1 -1
  232. package/dist/sde/optionalPeers.d.ts +25 -0
  233. package/dist/sde/optionalPeers.d.ts.map +1 -0
  234. package/dist/testing/TestDataFactory.d.ts +28 -1
  235. package/dist/testing/TestDataFactory.d.ts.map +1 -1
  236. package/dist/testing/index.js +116 -125
  237. package/dist/testing/index.js.map +1 -1
  238. package/dist/testing/index.mjs +110 -82
  239. package/dist/testing/index.mjs.map +1 -1
  240. package/dist/types/contracts.d.ts +4 -1
  241. package/dist/types/contracts.d.ts.map +1 -1
  242. package/dist/types/corporation-projects.d.ts +5 -1
  243. package/dist/types/corporation-projects.d.ts.map +1 -1
  244. package/dist/types/faction-warfare.d.ts +9 -1
  245. package/dist/types/faction-warfare.d.ts.map +1 -1
  246. package/dist/types/freelance-jobs.d.ts +2 -1
  247. package/dist/types/freelance-jobs.d.ts.map +1 -1
  248. package/dist/types/generated/esi-spec.generated.d.ts +12 -12
  249. package/dist/types/generated/esi-spec.generated.d.ts.map +1 -1
  250. package/dist/types/generated/spec-alignment.check.d.ts +2 -2
  251. package/dist/types/industry.d.ts +2 -1
  252. package/dist/types/industry.d.ts.map +1 -1
  253. package/dist/types/mail.d.ts +2 -1
  254. package/dist/types/mail.d.ts.map +1 -1
  255. package/dist/types/meta.d.ts +2 -1
  256. package/dist/types/meta.d.ts.map +1 -1
  257. package/dist/types/wallet.d.ts +2 -1
  258. package/dist/types/wallet.d.ts.map +1 -1
  259. package/package.json +326 -289
package/CHANGELOG.md CHANGED
@@ -1,388 +1,787 @@
1
- # Changelog
2
-
3
- All notable changes to this project will be documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ## [9.1.0] - 2026-08-14
9
-
10
- ### Added
11
-
12
- - **Schema rejection tests** — 104 new tests verifying Zod schemas correctly reject invalid input shapes
13
- - **Domain property fuzz tests** — property-based fuzz testing across domain clients using fast-check
14
- - **Schema validation benchmarks** — performance benchmarks for Zod schema validation paths
15
- - **Domain response type tests** — compile-time type tests for domain client response types via tsd
16
-
17
- ### Changed
18
-
19
- - **Expanded documentation** — updated examples, architecture guide, and testing guide with broader coverage
20
- - **README refreshed** — updated feature descriptions and endpoint counts
21
-
22
- ### Fixed
23
-
24
- - **Fuzz test date handling** — switched to integer-based date arbitrary to avoid invalid `Date` values in property-based tests
25
-
26
- ## [9.0.0] - 2026-08-12
27
-
28
- ### Breaking Changes
29
-
30
- - **Default retry count changed from 0 to 3** — transient failures (502, 503, 504, timeout, rate limit) now retry automatically with exponential backoff and jitter. Set `maxRetries: 0` in `retryConfig` to restore the previous behavior
31
- - **Generated Zod schemas removed** — the `src/schemas/generated/` directory has been removed; only hand-written schemas in `src/schemas/` remain
32
-
33
- ### Added
34
-
35
- - **Sub-path exports** — targeted imports for reduced bundle size:
36
- - `@lgriffin/esi.ts/schemas` — Zod schemas for runtime validation
37
- - `@lgriffin/esi.ts/errors` — error classes and type guards
38
- - `@lgriffin/esi.ts/testing` — `TestDataFactory` for test mock data
39
- - **`isCircuitOpen()` type guard** — checks whether an error is a `CircuitOpenError`, complementing the existing `isTimeout()`, `isRetryable()`, and `isValidationError()` guards
40
- - **`generate:all` script** — runs all generators (types, endpoints, OKF) in one command
41
- - **`generate:endpoints` script** — regenerates endpoint definitions from the ESI OpenAPI spec
42
-
43
- ### Changed
44
-
45
- - **Cursor pagination routed through full pipeline** — cursor-based pagination now goes through the same middleware pipeline (rate limiter, circuit breaker, retry, caching) as offset pagination
46
- - **Pagination retry unified with `IRetryStrategy`** — pagination requests now use the injectable retry strategy instead of a separate retry path
47
-
48
- ### Fixed
49
-
50
- - **Response interceptor status fix** — response interceptors previously received a hardcoded 200 status; they now receive the actual HTTP status code from the response
51
- - **CI consolidated** — `pr-validation.yml` merged into `ci.yml`; all PR validation now runs through the main CI pipeline
52
-
53
- ## [7.4.0] - 2026-07-17
54
-
55
- ### Added
56
-
57
- - **`withSafeMode()` on all domain clients** — mirrors existing `withMetadata()`, surfaces the `EsiResult<T>` discriminated union (`{ ok: true, data, meta } | { ok: false, error }`) without needing to call `createClient()` directly
58
- - **`responseSchema` on `routeEndpoints`** — was the only endpoint file without runtime response validation; now validated with `z.looseObject({ route: z.array(z.number()) })`
59
-
60
- ### Changed
61
-
62
- - **ESLint 8 → 10 flat config migration** — replaced `.eslintrc.cjs` with `eslint.config.mjs`, switched to unified `typescript-eslint` package, dropped `eslint-plugin-prettier` (redundant with lint-staged)
63
- - **jest-fetch-mock 3 → 4** — updated null-body status mocks (204/304) to use `new Response(null, ...)` per Fetch spec
64
- - Updated 11 minor/patch dependencies: @commitlint/cli, @microsoft/api-extractor, @redocly/cli, @types/node, @typescript-eslint/*, eslint-plugin-sonarjs, fast-check, knip, prettier, typedoc
65
-
66
- ### Fixed
67
-
68
- - CI: aligned `codeql.yml` branch targets to `[master, main, develop]`
69
- - CI: pinned `jest-coverage-comment@main` → `@v1.0.34` (supply-chain risk)
70
- - CI: added schema drift and generated types freshness checks to release pipeline
71
-
72
- ### Deprecated
73
-
74
- - `AllianceClient.getContacts()` — use `ContactsClient.getAllianceContacts()` instead
75
- - `AllianceClient.getContactLabels()` — use `ContactsClient.getAllianceContactLabels()` instead
76
-
77
- ## [7.3.0] - 2026-07-14
78
-
79
- ### Added
80
-
81
- - **`EsiResult<T>` discriminated union** and `safeMode` option for error-safe API calls
82
- - **Branded ID types** (16 types) for type-safe ESI entity references
83
- - **Expanded type-level tests** with tsd for error guards, endpoints, and domain types
84
- - **Compile-time spec-to-Zod type alignment checks**
85
- - **Schema drift detection** as a blocking CI check
86
- - **Comprehensive testing gap closure** (+1233 tests)
87
-
88
- ### Fixed
89
-
90
- - Resolved three CI jobs failing with continue-on-error
91
- - Normalized CRLF in API surface check
92
- - Fixed schemathesis report permissions and `--url` flag
93
- - Fixed API surface ordering issues
94
- - Added missing `system_id` to `MarketOrderSchema` test data
95
-
96
- ## [7.2.0] - 2026-07-08
97
-
98
- ### Added
99
-
100
- - **Contract testing infrastructure** — deep validation of all endpoint definitions against the live ESI OpenAPI spec (`npm run contract:live`). Checks path parameter alignment, required query params, request body consistency, auth requirements, response schema coverage, HTTP methods, pagination metadata, and deprecation sync. 8 contract validation categories with known-exception tracking.
101
- - **Property-based fuzz testing** with [fast-check](https://github.com/dubzzz/fast-check) — 601 tests fuzzing `validatePathParam()`, `validateQueryParam()`, `buildEndpointPath()`, and all Zod schemas with random/adversarial inputs (`npm run fuzz`)
102
- - **OpenAPI spec snapshot & diff** — `npm run contract:snapshot` saves a baseline; `npm run contract:diff` detects breaking changes via [oasdiff](https://github.com/Tufin/oasdiff) (Docker)
103
- - **Consumer type tests** with [tsd](https://github.com/tsdjs/tsd) — verifies public API type correctness (`npm run test:types`)
104
- - **Prism mock server** — `npm run mock:esi` starts a spec-conformant ESI mock on port 4010 via [@stoplight/prism-cli](https://stoplight.io/open-source/prism)
105
- - **Schemathesis fuzz runner** — `npm run fuzz:api` runs Schemathesis against the Prism mock (Docker, weekly CI)
106
- - Contract and fuzz test CI jobs added to `ci.yml` quality gate
107
- - Weekly spec drift detection job added to `maintenance.yml`
108
- - `jest.contract.config.cjs` and `jest.fuzz.config.cjs` test configurations
109
-
110
- ### Dependencies
111
-
112
- - Added `fast-check` (dev) — property-based testing framework
113
- - Added `@stoplight/prism-cli` (dev) — OpenAPI mock server
114
- - Added `tsd` (dev) — TypeScript type testing
115
-
116
- ## [7.1.0] - 2026-07-08
117
-
118
- ### Added
119
-
120
- - **Redocly CLI integration** — lints the live ESI OpenAPI spec for structural validity and best-practice compliance (`npm run validate:spec`). Catches spec breakage from CCP before it breaks generated types, cache TTLs, or scopes. Baseline: 0 errors, 328 warnings (all known CCP spec issues).
121
- - `redocly.yaml` config with tuned rulesets for ESI — structural rules as errors, CCP spec quirks as warnings
122
- - `validate:spec` npm script added to `check:all` pipeline
123
-
124
- ## [7.0.0] - 2026-07-08
125
-
126
- ### Breaking Changes
127
-
128
- - **Swagger 2.0 → OpenAPI 3.1 migration** — all generated types, cache TTLs, scopes, and rate limit groups are now sourced from the ESI OpenAPI 3.1 spec (`/meta/openapi.json`) instead of the deprecated Swagger 2.0 spec (`/latest/swagger.json`). See [esi-issues#1490](https://github.com/esi/esi-issues/issues/1490).
129
- - **Generated interface names changed** — `EsiSpec` namespace types now use OpenAPI schema names (e.g., `AllianceDetail` instead of `GetAlliancesAllianceIdOk`). These are generated types; hand-written consumer types are unchanged.
130
- - **Cache TTL metadata key** — internally changed from `x-cached-seconds` to `x-cache-age`. No consumer-facing impact (cache behavior is identical).
131
-
132
- ### Changed
133
-
134
- - Single OpenAPI spec fetch instead of dual Swagger + OpenAPI fetches
135
- - Updated all scripts, tests, and documentation to reference OpenAPI spec
136
- - Generated types now include 161 interfaces (up from 147), 126 cache TTLs, 70 scopes
137
-
138
- ## [6.1.0] - 2026-07-07
139
-
140
- ### Added
141
-
142
- - **Fleet wing/squad name validation** — `renameFleetWing()` and `renameFleetSquad()` now reject names exceeding ESI's 10-character limit before sending the request, with a clear error message
143
- - **43 runnable example scripts** covering all 210 ESI endpoints against live Tranquility (10 new example files: character-details, corporation-details, calendar-search, loyalty-pi, faction-details, industry-mining, market-orders, universe-encyclopedia, corp-contracts-wallet, dogma-meta-sov)
144
- - **3 write-operation example scripts** — `write-operations.ts` (contacts, fittings, mail, UI lifecycle), `universe-post-helpers.ts` (name resolution, affiliation), `freelance-jobs.ts` (cursor-paginated queries)
145
- - Live output captured for all example scripts in `examples/output/`
146
- - 3 new TDD test files and 1 new BDD feature file (81 TDD files, 40 BDD features total)
147
- - 2 new fleet validation unit tests
148
-
149
- ### Fixed
150
-
151
- - **Fleet test babel parse errors** — replaced TypeScript cast syntax `(result as any[]).forEach(...)` with direct index access in fleet tests (pre-existing bug unmasked by stricter transpilation)
152
- - **Fleet rename test names** — test mock names shortened to respect ESI's 10-character limit (`'New Squad Name'` → `'New Squad'`)
153
-
154
- ### Changed
155
-
156
- - README rewritten with "Why ESI.ts vs. OpenAPI-generated clients" comparison, full endpoint coverage table, and updated architecture/testing references
157
- - `guides/ARCHITECTURE.md`, `guides/TESTING.md`, and `TESTING.md` updated to current test counts (121 suites, 3,224 tests)
158
- - Autopilot example waypoint changed from Jita to Rens
159
-
160
- ### Schemas
161
-
162
- - Multiple Zod schema fixes discovered during live endpoint validation: added missing enum values, corrected optional fields, and adjusted types to match actual ESI responses
163
-
164
- ## [6.0.0] - 2026-07-03
165
-
166
- ### Added
167
-
168
- - **Runtime response validation** via [Zod](https://zod.dev/) schemas — every ESI endpoint response is validated at runtime, catching shape mismatches before they propagate to consumer code
169
- - Zod schemas for all 31 domain modules (133 interfaces) in `src/schemas/`, exported under the `schemas` namespace
170
- - `EsiValidationError` class (extends `EsiError`) thrown when response data doesn't match the expected schema
171
- - `isValidationError()` type guard for catching validation errors
172
- - `validateResponse` option on `EsiClientConfig` — on by default, can be disabled globally
173
- - `responseSchema` field on `EndpointDefinition` — wires schemas into the request pipeline via `createClient()`
174
- - New developer guide: `guides/RUNTIME-VALIDATION.md`
175
- - Response Validation Pipeline diagram in `guides/ARCHITECTURE.md`
176
- - Comprehensive TDD tests for schema parsing, validation integration, and common schemas (94 new tests)
177
- - BDD feature and step definitions for 9 runtime validation scenarios
178
-
179
- ### Changed
180
-
181
- - All TypeScript types in `src/types/` are now derived from Zod schemas via `z.infer<>` — schemas are the single source of truth
182
- - Schemas use `.passthrough()` mode so extra fields from ESI are preserved, not rejected
183
- - Test mock data across 25 test files updated to be spec-accurate (required by runtime validation)
184
- - Path-parameter IDs (e.g., `character_id`, `alliance_id`) are now optional in schemas, matching ESI which omits them from response bodies
185
-
186
- ### Dependencies
187
-
188
- - Added `zod` as a production dependency
189
-
190
- ## [5.3.0] - 2026-06-30
191
-
192
- ### Added
193
-
194
- - **Accept-Language configuration** — `language` option on `EsiClientConfig` injects the `Accept-Language` header for localized ESI responses (en, de, fr, ja, ru, zh, ko, es); changeable at runtime via `ApiClient.setLanguage()`
195
- - **ESI scope metadata** — generated `esi-scopes.generated.ts` with `EsiScope` union type (63 scopes) and `esiEndpointScopes` record mapping 119 authenticated endpoints to their required OAuth scopes
196
- - Exported `EsiScope` type and `esiEndpointScopes` map from package root
197
- - **Streaming pagination** — `stream*` methods on domain clients yield `PageResult<T>` one page at a time via `AsyncGenerator`, enabling backpressure and early termination for large paginated datasets
198
- - Streaming methods added to `MarketClient` (6), `ContractsClient` (3), `WalletClient` (3), `AssetsClient` (2), `KillmailsClient` (2)
199
- - `buildEndpointPath()` utility extracted from `createClient.ts` and exported from package root
200
- - `streamEndpoint()` protected method on `BaseEsiClient` for building custom streaming domain clients
201
- - Streaming pagination example (`npm run example:streaming`)
202
-
203
- ## [5.2.0] - 2026-06-29
204
-
205
- ### Added
206
-
207
- - **Spec-driven type generation** from ESI swagger spec (`npm run generate:types`) — 147 TypeScript interfaces + cache TTL map for 119 endpoints
208
- - **Spec-aware cache bypass** — GET requests within ESI-specified `x-cached-seconds` TTL return cached data with zero HTTP calls, layered on top of ETag caching
209
- - **`batch()` and `batchPost()` methods** on `EsiClient` — bounded concurrency for multi-ID fetches, auto-chunking for POST endpoints
210
- - **`EsiSpec` namespace export** with generated response types alongside hand-written types
211
- - **Type drift detection** in `npm run validate:esi` — compares hand-written types against generated spec types
212
- - CI step to verify generated types are up to date
213
- - **Retry with exponential backoff** — configurable retry for transient 5xx, timeout, and rate limit errors with jitter; respects circuit breaker state; GET-only by default with `retryMutations` opt-in
214
- - **`TimeoutError`** subclass of `EsiError` — typed timeout errors with `timeoutMs` property; per-request timeout override via `handleRequest()`
215
- - **Enhanced response metadata** via `withMetadata()` — rate limit info (`RateLimitMeta`), response timing (`responseTimeMs`), and cache hit type (`cacheHitType`: `'spec-ttl'` | `'etag-304'` | `'stale-on-error'`)
216
- - `RetryConfig` interface and `retryConfig` option on `EsiClientConfig`
217
- - `CircuitOpenError` passthrough in request handler (previously wrapped as generic Error)
218
- - **Per-group rate limiting** — 36 ESI rate limit groups extracted from the OpenAPI meta spec at build time; each group gets its own token bucket instead of a single global counter, preventing a burst of market requests from starving unrelated endpoints
219
- - **Optional per-user bucketing** — `userKeyExtractor` config option creates separate bucket sets per user key, supporting multi-character EVE applications
220
- - **Group-aware rate limit status** — `getGroupStatus(group)` and `getAllGroupStatuses()` methods for fine-grained rate limit monitoring; `isBlocked(group?)` accepts an optional group name
221
- - Generated `esi-rate-limit-groups.generated.ts` with 146 endpoint-to-group mappings
222
- - Exported `RateLimitGroupStatus` and `RateLimitGroupSpec` types
223
-
224
- ## [5.1.0] - 2026-06-26
225
-
226
- ### Added
227
-
228
- - **`noUncheckedIndexedAccess`** compiler flag — array/record indexing now returns `T | undefined`, catching unguarded index access at compile time
229
- - **`noImplicitReturns`** compiler flag — all function code paths must explicitly return a value
230
- - **`noImplicitOverride`** compiler flag — `override` keyword required when overriding base class methods
231
- - **`tsconfig.test.json`** — separate TypeScript config for tests, relaxing `noUncheckedIndexedAccess` for test utility patterns
232
-
233
- ### Changed
234
-
235
- - `RateLimiter` token cost lookup inlined (removed unnecessary `Record` indirection)
236
- - Jest configs (`jest.unit.config.cjs`, `jest.integration.config.cjs`) now use `tsconfig.test.json`
237
-
238
- ### Fixed
239
-
240
- - Unguarded indexed access in `ApiRequestHandler`, `CircuitBreaker`, `RateLimiter`, and `headersUtil`
241
-
242
- ## [5.0.0] - 2026-06-26
243
-
244
- ### Breaking Changes
245
-
246
- - **Removed `SovereigntyClient.getSovereigntyMap()`** — sunset ESI endpoint; use `getSovereigntySystems()` instead
247
- - **Removed `SovereigntyClient.getSovereigntyStructures()`** — sunset ESI endpoint; use `getSovereigntySystems()` instead
248
-
249
- ### Added
250
-
251
- - **Dependabot** — automated weekly dependency update PRs with grouped ESLint and testing ecosystems
252
- - **CodeQL Analysis** — GitHub-native security scanning workflow
253
- - **Commitlint** — conventional commit message validation via husky hook
254
- - **Version consistency script** — `npm run validate:versions` checks `package.json` matches `constants.ts`
255
- - **`npm run check:all`** — comprehensive validation including ESI endpoint and version checks
256
- - Coverage and npm download badges in README
257
- - `.editorconfig`, `.nvmrc`, `CONTRIBUTING.md`, `SECURITY.md`
258
- - ClientRegistry test coverage for all 35 client types
259
-
260
- ### Fixed
261
-
262
- - **POST body format** for asset and contact endpoints — request body was incorrectly structured
263
- - **POST body format** for `/universe/ids` and `/universe/names` — same issue
264
- - **Circuit breaker** now treats HTTP 420/429 rate-limit responses as failures
265
- - **configManager** uses `require.resolve` instead of `process.cwd()` fallback for reliable path resolution
266
- - **User-Agent version** — ESI requests were sending `esi.ts/3.4.0` instead of current version
267
- - **Compatibility date** — updated from `2025-12-16` to `2026-05-19` (Equinox)
268
- - TypeScript badge in README updated from 5.0+ to 6.0+
269
-
270
- ### Removed
271
-
272
- - `src/TODO` — fully completed roadmap
273
- - `jest.improved.config.cjs` — dead config matching zero test files
274
- - `docs/` — generated TypeDoc output removed from git tracking (CI builds as artifact)
275
- - Unused `getHeaders` test helper
276
-
277
- ### Changed
278
-
279
- - `package.json`: added `keywords`, `homepage`, `bugs` URLs, `files` includes README/LICENSE/CHANGELOG
280
- - Moved `docs/architecture.md` to `guides/ARCHITECTURE.md`
281
- - Updated `guides/TESTING.md` and `guides/DOCUMENTATION.md` to current state
282
- - Test coverage raised from 75% to 91%+
283
-
284
- ### Dependencies
285
-
286
- - `@typescript-eslint/eslint-plugin`: 7.18.0 → 8.x
287
- - `@typescript-eslint/parser`: 7.18.0 → 8.x
288
- - `@types/node`: 18.x → 26.x
289
- - `@commitlint/cli`: 19.x → 21.x
290
- - `eslint-config-prettier`: 9.x → 10.x
291
- - `lint-staged`: 16.x → 17.x
292
- - `jest-junit`: 16.x → 17.x
293
- - GitHub Actions: checkout v4→v7, setup-node v4→v6, upload-artifact v4→v7, codeql-action v3→v4, gh-pages v3→v4, action-gh-release v1→v3
294
-
295
- ## [4.1.1] - 2026-06-08
296
-
297
- ### Changed
298
-
299
- - **TypeScript 5.9 → 6.0** — upgraded to TypeScript 6.0.3, the last version before the Go-based TS7 compiler
300
- - `tsconfig.json`: added explicit `moduleResolution: "bundler"` (TS6 changed the default from `node` to `bundler`)
301
- - `tsconfig.json`: added explicit `rootDir: "./src"` (TS6 requires this when emitting)
302
- - `tsconfig.json`: removed `esModuleInterop: true` (always-on in TS6)
303
-
304
- ## [4.1.0] - 2026-06-08
305
-
306
- ### Added
307
-
308
- - **Equinox ESI compliance** — new endpoints and types for the [Equinox expansion](https://developers.eveonline.com/blog/equinox-on-esi-structures-sovereignty-and-access-lists) (compatibility date 2026-05-19)
309
- - **`SovereigntyClient.getSovereigntySystems()`** — combined sovereignty systems route with separate ADM indices (`military_index`, `industry_index`, `strategic_index`), occupancy data, and anchored structures in a single response
310
- - **`SkyhooksClient`** — new domain client with `getSovereigntyHubs()`, `getOrbitalSkyhooks()`, and `getRaidableSkyhooks()` endpoints for Upwell sovereignty structures
311
- - **`MercenaryClient`** — new domain client with `getMercenaryDens()` and `getMercenaryTacticalOperations()` endpoints for mercenary content
312
- - **`AccessListsClient`** — new domain client with `getAccessList(id)` for reading access list (ACL) contents including character, corporation, and alliance entries
313
- - `TestDataFactory` methods for all new Equinox types: `createSovereigntySystem()`, `createSovereigntyHub()`, `createOrbitalSkyhook()`, `createRaidableSkyhook()`, `createMercenaryDen()`, `createMercenaryTacticalOperation()`, `createAccessListEntry()`
314
- - TDD and BDD test coverage for all new endpoints
315
-
316
- ### Changed
317
-
318
- - `SovereigntyClient.getSovereigntyMap()` and `getSovereigntyStructures()` marked as deprecated — use `getSovereigntySystems()` instead
319
- - Domain client count increased from 32 to 35
320
-
321
- ### Dependencies
322
-
323
- - `ts-jest`: 29.4.9 → 29.4.11
324
- - `eslint-plugin-prettier`: 5.5.5 → 5.5.6
325
-
326
- ## [4.0.0] - 2026-05-15
327
-
328
- ### Breaking Changes
329
-
330
- - **Removed `RateLimiter.getInstance()` singleton** - Create instances with `new RateLimiter()` instead
331
- - **Removed global cache/circuit breaker functions** - `initializeETagCache()`, `getETagCache()`, `resetETagCache()`, `initializeCircuitBreaker()`, `getCircuitBreaker()`, `resetCircuitBreaker()` are no longer exported from `ApiRequestHandler`
332
- - Each `EsiClient` and `ApiClientBuilder` now creates its own `RateLimiter`, `ETagCacheManager`, and `CircuitBreaker` instances
333
-
334
- ### Added
335
-
336
- - **`BaseEsiClient` base class** — eliminates ~650 lines of repeated constructor/field/`withMetadata()` boilerplate across all 33 domain clients
337
- - **`RequestDeduplicator`** — coalesces concurrent identical GET requests into a single in-flight fetch, sharing the result across all callers (enabled by default; disable with `enableRequestDeduplication: false`)
338
- - **`EsiDiagnostics` API** — `client.diagnostics` accessor for cache/circuit-breaker stats, moved out of the main `EsiClient` API surface
339
- - **`fetchPages()` async generator** — memory-efficient page-by-page iteration over paginated ESI responses
340
- - **Request timeouts** — `config.timeout` now wired to `AbortController` (default 30s); previously the field existed but was never connected to `fetch()` calls
341
- - **`EsiError` retry helpers** — `isTimeout()`, `retryable` getter, and `isRetryable()` guard for smarter consumer retry logic
342
- - **`RateLimiterConfig`** — `minDelayMs` and `decelerationThreshold` exposed via `EsiClientConfig` for consumer-tunable rate limiting
343
- - TypeScript declaration files (`.d.ts`) now emitted with builds
344
- - `exports` field in `package.json` for modern Node.js module resolution
345
- - `engines` field specifying Node.js >= 18.0.0
346
- - `publishConfig` with public access for scoped package
347
- - `RateLimiter`, `ETagCacheManager`, and `CircuitBreaker` classes exported from main index
348
- - `ApiClientBuilder.setRateLimiter()`, `.setCache()`, `.setCircuitBreaker()` builder methods
349
- - `validateBaseUrl()` for SSRF protection — validates ESI host allowlist and HTTPS
350
- - `unsafeAllowCustomHost` config option to bypass base URL validation
351
- - URL sanitization in `EsiError` — sensitive query params (`token`, `access_token`, `api_key`) are redacted
352
- - Path parameters encoded with `encodeURIComponent()` for defense-in-depth
353
- - URL assertions in all client unit tests — every test now verifies the correct endpoint URL
354
- - Endpoint definition contract tests — 1800+ tests validating path templates, params, methods
355
- - Test coverage for `RequestDeduplicator`, `EsiDiagnostics`, `AsyncPaginationIterator`, and extended `EsiError` tests
356
- - `CHANGELOG.md` following Keep a Changelog format
357
- - Changelog validation step in release workflow
358
-
359
- ### Fixed
360
-
361
- - `.d.ts` files not generated during build (`declaration: true` added to `tsconfig.json`)
362
- - Release pipeline CNAME placeholder removed from GitHub Pages deployment
363
- - `ContractsClient.ts` test file renamed to `.test.ts` so Jest actually runs it
364
-
365
- ### Changed
366
-
367
- - `RateLimiter` constructor is now public
368
- - Cache, rate limiter, and circuit breaker are instance-based per client (no global shared state)
369
- - `ApiClientBuilder.build()` auto-creates a `RateLimiter` if none was explicitly set
370
- - `api-responses.ts` (1366 lines) split into 29 domain-specific type files with barrel re-export for backward compatibility
371
- - `RateLimiter` uses `logWarn` instead of `console.warn` for consistent observability
372
- - Reduced allocations and deduplicated fetch/sleep logic across core modules
373
-
374
- ## [3.4.0] - 2026-04-29
375
-
376
- ### Added
377
-
378
- - ESI response header best practices documentation
379
- - Dogma test coverage (10 TDD + 9 BDD tests)
380
- - Gated authenticated integration tests (32 tests across 14 endpoint groups)
381
- - EVE SSO token creator for local integration testing
382
- - Search endpoint `categories` query parameter
383
-
384
- ### Fixed
385
-
386
- - 3 industry endpoint paths (`corporation` -> `corporations`) for mining routes
387
- - Pagination middleware bypass: pages 2+ now route through request pipeline
388
- - Consolidated duplicate alliance contact endpoints
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [10.0.0](https://github.com/lgriffin/ESI.ts/compare/v9.9.0...v10.0.0) (2026-09-17)
9
+
10
+
11
+ ### ⚠ BREAKING CHANGES
12
+
13
+ * **schemas:** four response fields are now required, a runtime tightening.
14
+ - CharacterFleetInfo.fleet_boss_id, CustomsOffice.allow_alliance_access,
15
+ CustomsOffice.allow_access_with_standings and SolarSystemInfo.position no
16
+ longer include `undefined` in their types, and a body without one throws
17
+ EsiValidationError. ESI always sends them, so live responses are
18
+ unaffected; test doubles that stub GET /characters/{id}/fleet,
19
+ GET /corporations/{id}/customs_offices or GET /universe/systems/{id}
20
+ must include them. `?? fallback` guards on these reads can be removed.
21
+ * **schemas:** these response fields widen to `T | undefined`, so code that reads them may stop compiling under strict null checks; guard the reads (`?.`, `??` or an explicit check). Bodies without them no longer throw.
22
+ - CalendarEvent: event_id, event_date, title, importance, event_response.
23
+ - CalendarEventAttendee: character_id, event_response.
24
+ - CharacterTitle: title_id, name.
25
+ - CloneInfo.home_location: location_id, location_type.
26
+ - DogmaAttribute.name, DogmaEffect.name.
27
+ - BulkIdResult (postBulkNamesToIds): id and name of each entry in agents,
28
+ alliances, characters, constellations, corporations, factions,
29
+ inventory_types, regions, systems and stations.
30
+ * **wallet:** corporation wallet transactions have their own type. getCorporationWalletTransactions, fetchAllCorporationWalletTransactions and streamCorporationWalletTransactions return CorporationWalletTransaction instead of WalletTransaction; it has no is_personal, which ESI never sent on this route. Remove reads of is_personal on corporation trades and retype `WalletTransaction[]` annotations on these calls. New exports: CorporationWalletTransaction, CorporationWalletTransactionSchema, and TestDataFactory.createCorporationWalletTransaction in @lgriffin/esi.ts/testing.
31
+ * **meta:** MetaStatus is { routes: MetaRouteStatus[] } instead of { status: string }. meta.getStatus() used to throw on every real response, so no working code read `status`; read `routes` and filter by `route.status !== 'OK'` to find degraded routes. New exports: MetaRouteStatus, MetaRouteStatusSchema. (Server status from client.status.getStatus() is unchanged.)
32
+ * **mail:** mail headers and messages have separate types.
33
+ - getMailHeaders, fetchAllMailHeaders and streamMailHeaders return
34
+ MailHeader (new) instead of MailMessage. MailHeader has the same fields
35
+ minus body, which ESI never sent in headers.
36
+ - MailMessage (getMail) no longer has mail_id or is_read, which ESI never
37
+ sent on this route; read the new `read` flag instead of is_read, and use
38
+ the mailId you passed in.
39
+ - MailLabel.label_id and MailLabel.name are `| undefined`; guard reads.
40
+ New exports: MailHeader, MailHeaderSchema.
41
+ * **industry:** corporation industry jobs have their own type. getCorporationIndustryJobs, fetchAllCorporationIndustryJobs and streamCorporationIndustryJobs return CorporationIndustryJob instead of IndustryJob. It has location_id (number) and no station_id; ESI never sent station_id on this route, so code reading it got undefined and should read location_id. Retype `IndustryJob[]` annotations on these calls to `CorporationIndustryJob[]`. New exports: CorporationIndustryJob, CorporationIndustryJobSchema, and TestDataFactory.createCorporationIndustryJob in @lgriffin/esi.ts/testing.
42
+ * **corporation:** corporation response types now match ESI.
43
+ - CorporationMedal.date is renamed created_at.
44
+ - CorporationIssuedMedal no longer has title or description; join on
45
+ medal_id to getCorporationMedals for them.
46
+ - CorporationRoleHistory.before and .after are renamed old_roles and
47
+ new_roles.
48
+ - CorporationStarbaseDetail no longer has state (read it from
49
+ getCorporationStarbases). Its eleven access and defence settings listed
50
+ above are required: types drop `| undefined`, and a body without one
51
+ throws EsiValidationError.
52
+ - CorporationStarbase.state, CorporationTitle.title_id, the division number
53
+ in CorporationDivisions.hangar[] and .wallet[], and
54
+ CorporationMemberTracking.start_date are now `| undefined`; guard reads
55
+ of them.
56
+ * **freelance-jobs:** freelance job participation, participants, detail and cursor types now match ESI.
57
+ - FreelanceJobParticipation is { state, contributed, last_modified }
58
+ instead of { job_id, character_id, status, contributions,
59
+ last_contribution? }. Read state (e.g. 'Committed') for status,
60
+ contributed for contributions and last_modified for last_contribution;
61
+ the job and character IDs are the ones you passed in.
62
+ - getCorporationFreelanceJobParticipants returns
63
+ FreelanceJobParticipantsListing ({ participants, cursor? }) instead of
64
+ FreelanceJobParticipant[]. Read listing.participants.
65
+ FreelanceJobParticipant is { id, name, state, contributed }: character_id
66
+ -> id, status -> state, contributions -> contributed; corporation_id and
67
+ last_contribution are gone.
68
+ - EsiCursor.before and EsiCursor.after are string | undefined, not
69
+ string | null, on every freelance listing. Replace `=== null` checks with
70
+ a falsy or `=== undefined` check; a body with a null token now throws
71
+ EsiValidationError.
72
+ - FreelanceJobDetail.contribution, details.expires and
73
+ access_and_visibility.broadcast_locations may be undefined; guard reads
74
+ (e.g. `detail.contribution?.max_committed_participants`). New optional
75
+ details.finished, contribution.contribution_per_participant_limit,
76
+ contribution.submission_limit and access_and_visibility.restrictions.
77
+ New exports: FreelanceJobParticipantsListing,
78
+ FreelanceJobParticipantsListingSchema.
79
+ * **contracts:** public contract bids and items have their own types, and three contract fields are required.
80
+ - getPublicContractBids returns PublicContractBid[] ({ bid_id, date_bid,
81
+ amount }) instead of ContractBid[]. There is no bidder_id: ESI never sent
82
+ one here, so code reading it got undefined. Retype annotations from
83
+ ContractBid to PublicContractBid.
84
+ - getPublicContractItems returns PublicContractItem[] instead of
85
+ ContractItem[]: no is_singleton or raw_quantity; new optional item_id,
86
+ is_blueprint_copy, material_efficiency, time_efficiency and runs.
87
+ - Contract.assignee_id, Contract.acceptor_id (number) and
88
+ Contract.for_corporation (boolean) are no longer `| undefined`; a
89
+ character or corporation contract body without them throws
90
+ EsiValidationError. Test doubles must include all three (0 for an unset
91
+ assignee or acceptor).
92
+ - ContractItem (character and corporation items) no longer declares
93
+ is_blueprint_copy; read it from PublicContractItem, where ESI sends it.
94
+ New exports: PublicContractBid, PublicContractItem, PublicContractBidSchema,
95
+ PublicContractItemSchema.
96
+ * **testing:** default payloads from @lgriffin/esi.ts/testing changed.
97
+ - createAllianceInfo(), createCorporationInfo(), createStar() and
98
+ createStructure() no longer set alliance_id, corporation_id, star_id or
99
+ structure_id. Pass the ID as an override if your test reads it from the
100
+ payload.
101
+ - createItemType() no longer sets category_id. Read the category from
102
+ createItemGroup(), or pass { category_id } as an override.
103
+ - createSearchResults() returns { solar_system, station, structure,
104
+ character, corporation, alliance } instead of { systems, stations,
105
+ structures, characters, corporations, alliances }. Rename the keys you read
106
+ or override (e.g. { solar_system: [30000142] }).
107
+ - createSolarSystem() adds position and createContract() adds
108
+ acceptor_id: 0. Tests comparing a builder's whole output with toEqual must
109
+ expect the new fields.
110
+ * **testing:** default payloads from @lgriffin/esi.ts/testing changed.
111
+ - createCharacterInfo() no longer sets character_id. Pass
112
+ { character_id } as an override if your test reads it.
113
+ - createFleetInfo() no longer sets fleet_id or fleet_boss_id; pass them
114
+ as overrides if needed (GET /characters/{id}/fleet is where ESI sends
115
+ them).
116
+ - createFleetWing() returns { id, name, squads: [{ id, name }] }; read
117
+ wing.id and squad.id instead of wing_id and squad_id, and override `id`
118
+ instead of `wing_id`.
119
+ - createFleetMember() adds wing_id: -1, squad_id: -1,
120
+ role_name: 'Fleet Commander (Boss)', join_time and takes_fleet_warp;
121
+ override them for a squad member.
122
+ - createIndustryJob(), createCharacterAsset(), createCorporationAsset(),
123
+ createCharacterMedal(), createCorporationStructure() and createStation()
124
+ add the fields listed above. Tests comparing a builder's whole output
125
+ with toEqual must expect the new fields.
126
+ * **schemas:** three response fields are now required and public contracts have their own type.
127
+ - ServerStatus.vip is `boolean`, not `boolean | undefined`, and a status
128
+ body without vip now throws EsiValidationError. Drop `?? false`
129
+ fallbacks if you like; test doubles that stub /status must include vip.
130
+ - Contract.status and Contract.availability (character and corporation
131
+ contracts) are required; bodies without them throw EsiValidationError.
132
+ Test doubles must include both.
133
+ - getPublicContracts, fetchAllPublicContracts and streamPublicContracts
134
+ return PublicContract (new, with PublicContractSchema) instead of
135
+ Contract. PublicContract has no status, availability, assignee_id,
136
+ acceptor_id, date_accepted or date_completed, which ESI never sent for
137
+ public contracts; code reading them got undefined and can remove those
138
+ reads. Annotations of `Contract[]` on these calls become
139
+ `PublicContract[]`.
140
+ * **factions:** faction warfare leaderboard types changed.
141
+ - getLeaderboardsOverall() returns FactionWarfareFactionLeaderboard,
142
+ getLeaderboardsCharacters() FactionWarfareCharacterLeaderboard and
143
+ getLeaderboardsCorporations() FactionWarfareCorporationLeaderboard,
144
+ instead of FactionWarfareLeaderboard for all three.
145
+ - Entry `id` is gone: read `faction_id`, `character_id` or
146
+ `corporation_id` for the board you called.
147
+ - Entry `amount` is now `number | undefined`: default it
148
+ (`entry.amount ?? 0`) or skip unscored entries.
149
+ - FactionWarfareLeaderboard and FactionWarfareLeaderboardSchema are
150
+ deprecated and no longer validate any endpoint; each new board type is
151
+ assignable to FactionWarfareLeaderboard. Switch to the per-board type or
152
+ schema (FactionWarfareFactionLeaderboardSchema,
153
+ FactionWarfareCharacterLeaderboardSchema,
154
+ FactionWarfareCorporationLeaderboardSchema).
155
+ * **corporation-projects:** corporation project types, schemas and methods now match ESI, and the old shapes are gone.
156
+ - getCorporationProjects(corporationId, before?, after?) returns
157
+ CorporationProjectsListing ({ projects: CorporationProjectSummary[],
158
+ cursor?: { before?, after? } }) instead of CorporationProject[].
159
+ Read listing.projects; pass listing.cursor?.after as `after` for the next
160
+ page.
161
+ - getCorporationProjectContributors(corporationId, projectId, before?,
162
+ after?) returns CorporationProjectContributorsListing ({ contributors,
163
+ cursor? }) instead of CorporationProjectContributor[]. Read
164
+ listing.contributors.
165
+ - projectId is a string (the project UUID) in getCorporationProject,
166
+ getCorporationProjectContribution and getCorporationProjectContributors.
167
+ Pass project.id from a listing.
168
+ - CorporationProject / CorporationProjectSchema: project_id -> id (string),
169
+ progress number -> { current, desired } (fraction = current / desired),
170
+ start_time -> details.created, finish_time -> details.finished. New
171
+ required fields name, last_modified, creator, details, configuration;
172
+ optional reward and contribution. state is an esiEnum ('Active',
173
+ 'Completed', ...), capitalised as ESI sends it.
174
+ - CorporationProjectContributor / Schema: character_id -> id,
175
+ contribution -> contributed, plus name.
176
+ - CorporationProjectContribution / Schema: { character_id, contribution }
177
+ -> { contributed, last_modified? }; the character ID is the one you
178
+ passed in.
179
+ New exports: CorporationProjectsListing, CorporationProjectSummary,
180
+ CorporationProjectContributorsListing, CorporationProjectCursor and their
181
+ schemas.
182
+
183
+ ### Added
184
+
185
+ * **auth:** add EsiTokenManager with pluggable storage and bulk refresh ([1b6ca0a](https://github.com/lgriffin/ESI.ts/commit/1b6ca0a1bb7c460e74b26ee753660f1ee20342b0))
186
+ * **auth:** add EsiTokenManager with pluggable storage and bulk refresh ([91de317](https://github.com/lgriffin/ESI.ts/commit/91de317460b229547eb6fcbc78edd31582e6448d)), closes [#185](https://github.com/lgriffin/ESI.ts/issues/185) [#187](https://github.com/lgriffin/ESI.ts/issues/187)
187
+ * **logging:** per-client structured logging, engineering charter, release 9.9.0 ([ca9773b](https://github.com/lgriffin/ESI.ts/commit/ca9773bc9106741f9400690e4a14dbc6d39168fa))
188
+ * ramp-up phases 1, 3, 4 and 5 — load-bearing spec, CI gate, agent governance ([8453187](https://github.com/lgriffin/ESI.ts/commit/8453187e836dedcefc1d686a8371e19835c97bd2))
189
+ * **skills:** gate ears-gherkin-dev changes with an eval suite ([18865c8](https://github.com/lgriffin/ESI.ts/commit/18865c8848cde16b7243f3f17b60b270a98f3cca))
190
+ * **spec-consistency:** check Rule titles against response schemas ([eb4ed10](https://github.com/lgriffin/ESI.ts/commit/eb4ed105c28e33814cffb8bcdae9689cd6b74f6c))
191
+
192
+
193
+ ### Fixed
194
+
195
+ * **auth:** address review findings on SSO exchange, refresh races and storage ([e8172f1](https://github.com/lgriffin/ESI.ts/commit/e8172f1efd781040bffc0a29ecca683fd61aeba6))
196
+ * **build:** let release-please bump PACKAGE_VERSION ([8ef9c19](https://github.com/lgriffin/ESI.ts/commit/8ef9c1994671891a67be438c5efd0cc2b4f5ffba))
197
+ * **build:** share one copy of each class across sub-path entries ([8110d73](https://github.com/lgriffin/ESI.ts/commit/8110d73cfd7016aebfe9bed5a85aed84fc12dec7))
198
+ * **build:** share one copy of each class across sub-path entries ([2f0ec0a](https://github.com/lgriffin/ESI.ts/commit/2f0ec0aa73051615a2bad3ed3a42c0e952c98d97))
199
+ * **cache:** evict a response body that fails schema validation ([22b0d0b](https://github.com/lgriffin/ESI.ts/commit/22b0d0b31276bf6526d52eb9ad3c7db43d3b8d7e))
200
+ * **cache:** evict cached reads after writes answered with 201 or 204 ([7e41381](https://github.com/lgriffin/ESI.ts/commit/7e413819ceaa591d961c254124fb4b4f25d16288))
201
+ * **cache:** keep entries an hour past their freshness TTL for stale-on-error ([e4f7e9a](https://github.com/lgriffin/ESI.ts/commit/e4f7e9a9dfe09afc7aead67d7810fd677de9bc47))
202
+ * **cache:** keep entries an hour past their freshness TTL for stale-on-error ([fec657d](https://github.com/lgriffin/ESI.ts/commit/fec657d28b4806a1ba6bddd04a722065bd247523))
203
+ * **ci:** publish releases created by release-please and sign with cosign bundles ([befc639](https://github.com/lgriffin/ESI.ts/commit/befc6392f347dbd2f9c76c1fd76af96ccfd76083))
204
+ * **ci:** unblock the v10.0.0 release: drift baseline, version marker, dispatchable publish, cosign bundles ([5271515](https://github.com/lgriffin/ESI.ts/commit/527151572053fb07ff9690b603743e7a65806efd))
205
+ * **contracts:** give public bids and items their spec shape; require acceptor ([3ab3198](https://github.com/lgriffin/ESI.ts/commit/3ab31987abbd64acb578af92fc1dcaaf13ea9d0b))
206
+ * **corporation-projects:** match project schemas, types and methods to ESI ([afa7e8f](https://github.com/lgriffin/ESI.ts/commit/afa7e8ff37a24395beb591e7698da25a6cf1dee7))
207
+ * **corporation:** match medals, role history, starbases, titles and tracking to ESI ([16ee3d7](https://github.com/lgriffin/ESI.ts/commit/16ee3d7c3f0c96022abe3e861358e13526a3f739))
208
+ * **errors:** keep ESI's reason in the error message ([9ad8902](https://github.com/lgriffin/ESI.ts/commit/9ad8902ba5c67b45386a6f70a6edfa61ca17cad4))
209
+ * **errors:** surface network failures and timeouts as EsiError ([cd661a3](https://github.com/lgriffin/ESI.ts/commit/cd661a39a9dd46810ab8cbc8be96f67bb03d4158))
210
+ * **factions:** give each faction warfare leaderboard its spec entry shape ([b997d2c](https://github.com/lgriffin/ESI.ts/commit/b997d2cd4f86929dc9007615d7d604c435d522a7))
211
+ * **freelance-jobs:** match participation, participants, detail and cursor to ESI ([e49c3d1](https://github.com/lgriffin/ESI.ts/commit/e49c3d11cb400f0620d45821228543f25defab12))
212
+ * **industry:** place corporation industry jobs by location_id as ESI does ([8ebe25d](https://github.com/lgriffin/ESI.ts/commit/8ebe25d48270d62b5a860822eef1a024a216cc32))
213
+ * **mail:** split mail headers from the full message and type its read flag ([600472b](https://github.com/lgriffin/ESI.ts/commit/600472baeebc741a172460ab6cc03721a9327c6d))
214
+ * **meta:** give getStatus the per-route shape ESI sends ([7b72f40](https://github.com/lgriffin/ESI.ts/commit/7b72f40a44205d94a7b708e83f15795c5a00625c))
215
+ * **mutation:** satisfy noUncheckedIndexedAccess in the ratchet script ([07ae337](https://github.com/lgriffin/ESI.ts/commit/07ae337107001ec3cdff319edc35a0bc190c65c6))
216
+ * ramp-up phase 6 — library safety gates and a validation cache fix ([a2629ad](https://github.com/lgriffin/ESI.ts/commit/a2629ada8d7cb1b896211f6762f185ef46667359))
217
+ * **release:** tag releases vX.Y.Z and start the changelog at 9.9.0 ([2067882](https://github.com/lgriffin/ESI.ts/commit/2067882e9920e60d3e3b5bda3d373187671baa30))
218
+ * **release:** tag releases vX.Y.Z and start the changelog at 9.9.0 ([15e62d6](https://github.com/lgriffin/ESI.ts/commit/15e62d66a742325ef05b90aeb3c590fa1b2c661e))
219
+ * **schemas:** require fleet boss, customs office access flags and system position ([00fff7c](https://github.com/lgriffin/ESI.ts/commit/00fff7c06d946043e833833894f9f885093393c5))
220
+ * **schemas:** require vip, contract status and availability as ESI does ([24c8c35](https://github.com/lgriffin/ESI.ts/commit/24c8c35de56e92ceb821383808cc2359acfb6659))
221
+ * **schemas:** stop requiring fields the ESI spec marks optional ([fc8e489](https://github.com/lgriffin/ESI.ts/commit/fc8e489274a0b40700fd0f63818830a44db3bd60))
222
+ * **scripts:** drop drift baseline entries resolved by v10 part 2 ([1c54a30](https://github.com/lgriffin/ESI.ts/commit/1c54a3098d591c06479b3baee83c9efe23c3ea51))
223
+ * **scripts:** drop the 83 drift baseline entries [#317](https://github.com/lgriffin/ESI.ts/issues/317) resolved ([2cb7d18](https://github.com/lgriffin/ESI.ts/commit/2cb7d18779d70ecddff6b814715a50a2af5f7953))
224
+ * **scripts:** make schema:drift compare what it reports ([3dc4c53](https://github.com/lgriffin/ESI.ts/commit/3dc4c532d61fe100e842fda7d25f16093d19fe41))
225
+ * **scripts:** make schema:drift compare what it reports, with a ratcheted baseline ([7f428f9](https://github.com/lgriffin/ESI.ts/commit/7f428f926ca5b4e6ce43ef2dd626578409c1552c))
226
+ * **sde:** load js-yaml and adm-zip lazily as optional peer dependencies ([22afdce](https://github.com/lgriffin/ESI.ts/commit/22afdce1cabc9c6bbe860b14194222c3c9e39872))
227
+ * **sde:** load js-yaml and adm-zip lazily as optional peer dependencies ([526587b](https://github.com/lgriffin/ESI.ts/commit/526587beb77612daaf77e7c2da581fa21cf7ce69))
228
+ * **sde:** read nested _sde.yaml metadata from ZIP archives ([1f46860](https://github.com/lgriffin/ESI.ts/commit/1f46860834a6ab9f9e4320f9eee8259e1d2b8931))
229
+ * **sde:** read nested _sde.yaml metadata from ZIP archives ([13d04cb](https://github.com/lgriffin/ESI.ts/commit/13d04cbc3ce74b431f94688ad293163a97f29489))
230
+ * **spec-audit:** close five holes that let a non-compliant spec pass ([9106978](https://github.com/lgriffin/ESI.ts/commit/910697878270e34450af40431b90d9fa605b94b9))
231
+ * **spec-audit:** close five holes that let a non-compliant spec pass ([df06b64](https://github.com/lgriffin/ESI.ts/commit/df06b64cd1a116c885627240931fb734954e7b2f))
232
+ * **spec-audit:** load Cucumber through dynamic import so the audit runs on Node 18 ([4f0f819](https://github.com/lgriffin/ESI.ts/commit/4f0f819262aa7d7f0de70bbcdfbc4b143d3b2cde))
233
+ * **spec-audit:** split the pure checks out so Jest can load them ([34100d7](https://github.com/lgriffin/ESI.ts/commit/34100d77101bc1d2d4c5998b1cf70b69dbeeef9d))
234
+ * **spec-consistency:** run on shallow CI checkouts and on Node 18 ([98be8be](https://github.com/lgriffin/ESI.ts/commit/98be8bee5b2271a7086d4dbd8cb83792ef6ed9ed))
235
+ * **spec:** qualify Rule titles that promise optional response fields ([227ebbf](https://github.com/lgriffin/ESI.ts/commit/227ebbfd5f62de1f47b450d0a75db4fc3799da89))
236
+ * **spec:** reconcile domain failure Rules with retry and stale-on-error ([a2695dd](https://github.com/lgriffin/ESI.ts/commit/a2695ddb777ee53d3ef1f65b6cee105c61103646))
237
+ * **testing:** drop invented IDs and add required fields in TestDataFactory ([d59388c](https://github.com/lgriffin/ESI.ts/commit/d59388c15cbe8ffb9752b99a53cb31da538bbc1a))
238
+ * **testing:** make TestDataFactory builders emit schema-valid ESI payloads ([8d81bb8](https://github.com/lgriffin/ESI.ts/commit/8d81bb8af09e6cebfc25a2c74e558242861533b2))
239
+ * **wallet:** stop requiring is_personal on corporation wallet transactions ([c013e88](https://github.com/lgriffin/ESI.ts/commit/c013e88c9e0b7558a26757eb25c8e4d0c4ebdffe))
240
+
241
+
242
+ ### Changed
243
+
244
+ * add CODEOWNERS covering bot-touched manifests, workflows and release config ([892bcbc](https://github.com/lgriffin/ESI.ts/commit/892bcbc4da005f116d3900f6171e57f6ee8f929a))
245
+ * **beads:** export the ramp-up phase notes and follow-up issues ([6fef0bc](https://github.com/lgriffin/ESI.ts/commit/6fef0bc7085256563afc29a135d30031c334c5d0))
246
+ * **ci:** bump github/codeql-action to 4.38.0 ([741000c](https://github.com/lgriffin/ESI.ts/commit/741000c28c2af735fd33442b809057afdaa23382))
247
+ * **ci:** fail pull requests that add a public export no test references ([4981c9d](https://github.com/lgriffin/ESI.ts/commit/4981c9db189cbe5e02e958a062c2fc4df8709f11))
248
+ * **ci:** file an issue when the spec drift check fails, not a red pull request ([ad06498](https://github.com/lgriffin/ESI.ts/commit/ad06498a9767732ff57954e31e60fdcb07ea6a64))
249
+ * **ci:** hold the API SemVer gate to squash merges ([bb0bdba](https://github.com/lgriffin/ESI.ts/commit/bb0bdbac1ae05ae8222d54c4d62431fdcd6187ba))
250
+ * **ci:** require a breaking-change commit when the public API report loses a line ([6363b31](https://github.com/lgriffin/ESI.ts/commit/6363b3139c40a8881fbcf3cce0a919d3fef14130))
251
+ * **ci:** run schema drift against its ratcheted baseline ([e113851](https://github.com/lgriffin/ESI.ts/commit/e1138514a174bf31f97e9303bb74c7e1e2cbcda2))
252
+ * **deps:** add a Dependabot cooldown for npm and GitHub Actions updates ([f595daa](https://github.com/lgriffin/ESI.ts/commit/f595daa66c0a68fe83d220694cc6d88d71cc3a2e))
253
+ * **deps:** bump @cucumber/gherkin from 28.0.0 to 42.0.1 ([899f128](https://github.com/lgriffin/ESI.ts/commit/899f128fbd230a81ea2613e558e16bb8ff05f28b))
254
+ * **deps:** bump @cucumber/messages from 24.1.0 to 34.2.1 ([bc563d4](https://github.com/lgriffin/ESI.ts/commit/bc563d4d3afedaa5ab1d914fa2afccb0486636da))
255
+ * **deps:** bump @cucumber/messages from 24.1.0 to 34.2.1 ([bf2414d](https://github.com/lgriffin/ESI.ts/commit/bf2414d8add5b0b872db7da21b0218a7f2cce742))
256
+ * **deps:** bump the minor-and-patch group with 7 updates ([7039ca7](https://github.com/lgriffin/ESI.ts/commit/7039ca7a64dae5013eb426e63905dcd36af9bf8e))
257
+ * **deps:** bump zod to 4.6.4 and js-yaml to 5.4.2 ([09dd9f0](https://github.com/lgriffin/ESI.ts/commit/09dd9f03888962f823f194bf7ae88ec1b6a276ca))
258
+ * **deps:** bundle Dependabot updates (zod, js-yaml, codeql-action) ([ea7a211](https://github.com/lgriffin/ESI.ts/commit/ea7a2113e24f90ecc3a5591bff8a23eb8b2ee5f4))
259
+ * **lint:** load the seam lint parser from the listed typescript-eslint package ([c87e31b](https://github.com/lgriffin/ESI.ts/commit/c87e31bd9183406838f7fbbfa7f2b984f4527d41))
260
+ * **lint:** ratchet wall-clock, timer and Math.random use in src/ ([8a9fb4f](https://github.com/lgriffin/ESI.ts/commit/8a9fb4fb5018c5744e57ab82f1107bdb455cdd50))
261
+ * **lint:** ratchet wall-clock, timer and Math.random use in src/ ([37ede1d](https://github.com/lgriffin/ESI.ts/commit/37ede1d541aa1ffad4998ef9c870ef15d75b210c))
262
+ * **scripts:** move the schema drift comparison into a testable core ([ba293c1](https://github.com/lgriffin/ESI.ts/commit/ba293c1f47a209161402103f924b3dc154a4bada))
263
+ * **scripts:** plain plurals in the schema drift ratchet messages ([99724b8](https://github.com/lgriffin/ESI.ts/commit/99724b8b578e30c858b4d3ac676d8abfa5c36477))
264
+
265
+
266
+ ### Documentation
267
+
268
+ * **agents:** write the reviewer checklist and per-area agent notes ([113b145](https://github.com/lgriffin/ESI.ts/commit/113b14555986f9ef696a73c93a1606f467039048))
269
+ * **bdd:** list the batch 2 domains as converted ([446f914](https://github.com/lgriffin/ESI.ts/commit/446f914dd0d18f1655586cb1ee140b6b727d7e85))
270
+ * **bdd:** record the runner decision and the one-step-per-file layout ([266c6ea](https://github.com/lgriffin/ESI.ts/commit/266c6ead4762df12a20887b531135f625baa758d))
271
+ * **bdd:** state which leg carries the 100ms latency in the fan-out Rule ([b7ac123](https://github.com/lgriffin/ESI.ts/commit/b7ac123faff9b1d36919affea8ee8d5c0ebc893a))
272
+ * **guides:** describe ci-success, the live tier guard, cooldown and the no-retry nightly ([605333b](https://github.com/lgriffin/ESI.ts/commit/605333b8a745a02153fc1fa810edea35f34d2aaf))
273
+ * **guides:** document the seam lint and the BDD-only mutation ratchet ([9d228ad](https://github.com/lgriffin/ESI.ts/commit/9d228ad549ef2c8b542eb9247bff3ebe9f8591fb))
274
+ * **guides:** write the charter guides and retire docs/ ([8ffb88f](https://github.com/lgriffin/ESI.ts/commit/8ffb88f471e5259ad8f31fdee67ca1624194078e))
275
+ * **guides:** write the charter guides and retire docs/ ([5b61eaa](https://github.com/lgriffin/ESI.ts/commit/5b61eaa41929efadf5a7233a922adc7202fd4dde))
276
+ * **quality-gates:** document schema drift matching, guard and baseline ([ccb1ce1](https://github.com/lgriffin/ESI.ts/commit/ccb1ce10d45c29f4c820f7c8de6779c5afb5669d))
277
+ * **sde:** document js-yaml and adm-zip as optional peer dependencies ([d8d8843](https://github.com/lgriffin/ESI.ts/commit/d8d884364f997d91c1bdc2d540a9170939af3832))
278
+ * **semver:** add a semantic versioning guide and enforce it in CLAUDE.md ([48fedaa](https://github.com/lgriffin/ESI.ts/commit/48fedaa8fdac0ddba5326dc89b31e6b781cb68a2))
279
+ * **semver:** add a semantic versioning guide and enforce it in CLAUDE.md ([87d391b](https://github.com/lgriffin/ESI.ts/commit/87d391b14633262c4b58e1eb3b24af1f22cbe10c))
280
+
281
+
282
+ ### Testing
283
+
284
+ * **api-semver-gate:** build the empty-report commit without moving HEAD ([b697a31](https://github.com/lgriffin/ESI.ts/commit/b697a3180c445f4cb199ed16a15dbdc409bf3e7c))
285
+ * **bdd:** add a runner-agnostic step library and a Jest binder ([d16b169](https://github.com/lgriffin/ESI.ts/commit/d16b1696078ad2658257777c854d8c09e2eb3b66))
286
+ * **bdd:** add the HTTP transport seam and ban client-method mocks ([1112b81](https://github.com/lgriffin/ESI.ts/commit/1112b815b59eba99a846f21a3104cd7b1ace57e4))
287
+ * **bdd:** assert only what the reconciled Rules promise ([3366028](https://github.com/lgriffin/ESI.ts/commit/336602818ec8544eb024f43bc93036e4b88149ac))
288
+ * **bdd:** convert access-lists, alliance, clones, cosmetics, dogma, insurance, meta and route to one step per file ([8117957](https://github.com/lgriffin/ESI.ts/commit/81179572c97f7d61bbc37427de0634f576b949a6))
289
+ * **bdd:** convert wars, killmails, wallet, mail and universe to one step per file ([997bc63](https://github.com/lgriffin/ESI.ts/commit/997bc63df4d71c7a6a52ee47551fc968ef250043))
290
+ * **bdd:** move every domain step file onto the transport seam ([538fdee](https://github.com/lgriffin/ESI.ts/commit/538fdee76aaea55df56a7f939ef8e1a4c6a22545))
291
+ * **bdd:** name the access-list 401 scenario for the token it uses ([41ef2cd](https://github.com/lgriffin/ESI.ts/commit/41ef2cd24f2159b1eb33b9c7aab1f7bbef990fe5))
292
+ * **bdd:** name two converted steps for the domain they assert ([602c0f9](https://github.com/lgriffin/ESI.ts/commit/602c0f9a46211cd2f2158d63fb25bef8f59f9439))
293
+ * **bdd:** ramp-up phase 2 — one step per file on a Jest step library, with a dry run ([a9a154d](https://github.com/lgriffin/ESI.ts/commit/a9a154d83f0ff55ae3d4dc8d760eff42695a4e13))
294
+ * **bdd:** send 204, 205 and 304 through the seam with no body ([643ab69](https://github.com/lgriffin/ESI.ts/commit/643ab69c98850ff3c5bf0c5be3581cbf43ccd4ef))
295
+ * **bdd:** split the access-lists steps into one file per step ([3eedda5](https://github.com/lgriffin/ESI.ts/commit/3eedda58fdf3962537e545ab800df83be479284a))
296
+ * **bdd:** split the alliance steps into one file per step ([49c6068](https://github.com/lgriffin/ESI.ts/commit/49c6068ffb7f6121253998cb6c194f6643637e06))
297
+ * **bdd:** split the clones steps into one file per step ([d75dda7](https://github.com/lgriffin/ESI.ts/commit/d75dda7630319f73160e8ffcb5d6b3c2fa8025bd))
298
+ * **bdd:** split the cosmetics steps into one file per step ([2bdb41d](https://github.com/lgriffin/ESI.ts/commit/2bdb41d447d7f82720a84928e1d0edfe8a337215))
299
+ * **bdd:** split the dogma steps into one file per step ([1b0a7e5](https://github.com/lgriffin/ESI.ts/commit/1b0a7e573edf8432caa3fa6d55234136fce31fc7))
300
+ * **bdd:** split the insurance steps into one file per step ([2baf51b](https://github.com/lgriffin/ESI.ts/commit/2baf51b9b45b84dcc9f6fe158cf7997a55d34dbd))
301
+ * **bdd:** split the killmails steps into one file per step ([d437485](https://github.com/lgriffin/ESI.ts/commit/d43748599a5fdfa4c4179a21fc2f1afeedff3cba))
302
+ * **bdd:** split the mail steps into one file per step ([7f4395e](https://github.com/lgriffin/ESI.ts/commit/7f4395ed1118bb338e89c028fe0534671ffe1203))
303
+ * **bdd:** split the market steps into one file per step ([a6af26e](https://github.com/lgriffin/ESI.ts/commit/a6af26e9999e0df2615db48dbab3fb91e1d505ff))
304
+ * **bdd:** split the meta steps into one file per step ([51fbcbb](https://github.com/lgriffin/ESI.ts/commit/51fbcbbf17bcbfea418a9d1f0a8a9e2ef4c0dd66))
305
+ * **bdd:** split the route steps into one file per step ([606b0e2](https://github.com/lgriffin/ESI.ts/commit/606b0e20fa466fcf3fd72b641a835d8816faac77))
306
+ * **bdd:** split the universe steps into one file per step ([4241d68](https://github.com/lgriffin/ESI.ts/commit/4241d68a4642703ff1b640860f2b7d0ffde25fa5))
307
+ * **bdd:** split the wallet steps into one file per step ([32c5305](https://github.com/lgriffin/ESI.ts/commit/32c5305d00a8fb941fbc3ad7b687bae9d7c0ca64))
308
+ * **bdd:** split the wars steps into one file per step ([76dfbff](https://github.com/lgriffin/ESI.ts/commit/76dfbffb0c17198120f9229a5850edc1dc9a6fa2))
309
+ * **bdd:** sync the transport seam with the integration branch ([8c0baa6](https://github.com/lgriffin/ESI.ts/commit/8c0baa6403e710839838d0e5342da58e084821b3))
310
+ * **bdd:** time the real pipeline in the performance Rules ([eb32df5](https://github.com/lgriffin/ESI.ts/commit/eb32df53bb7e9ba083240ca34789c2f4742b76f6))
311
+ * **cache:** specify entry retention past the freshness TTL ([cc4c04c](https://github.com/lgriffin/ESI.ts/commit/cc4c04c14a56c55ddf5abd2bb72fde5e2ebd319a))
312
+ * **clients:** give each shared error case its own ApiClient ([11259e3](https://github.com/lgriffin/ESI.ts/commit/11259e345af95b1ae3f6506512161920be8cb3e8))
313
+ * **consumer:** assert ./errors shares class identity with the root entry ([cb7575a](https://github.com/lgriffin/ESI.ts/commit/cb7575abc9f187733023bf57b286d7c3e90e3a6b))
314
+ * **consumer:** install the packed tarball into a clean consumer and run it ([517ce18](https://github.com/lgriffin/ESI.ts/commit/517ce18d1e023ea72b028e73caa56d686cbc0fd9))
315
+ * **consumer:** load SDE metadata nested under sde: in the optional-peers probe ([8375d14](https://github.com/lgriffin/ESI.ts/commit/8375d1440ab4870aba8ba51b65fd7ecc8cdd34d4))
316
+ * **etag-cache:** specify stale-on-error over HTTP ([ded4537](https://github.com/lgriffin/ESI.ts/commit/ded45377795087318e438a1315d0a41e096089eb))
317
+ * **export-coverage:** fail when a public export has no test referencing it ([c6920ce](https://github.com/lgriffin/ESI.ts/commit/c6920ceea24a2b9aa574a267bc61e620a103b8bd))
318
+ * **export-coverage:** report public exports that no test references ([ff5bf1f](https://github.com/lgriffin/ESI.ts/commit/ff5bf1f2f43d7221874c2b546b8f543a6bb7b5b0))
319
+ * **fuzz:** inject schema-violating ESI bodies through the transport seam ([e4cd35d](https://github.com/lgriffin/ESI.ts/commit/e4cd35dce60e643996a3368c1fda8b5b229ecfc9))
320
+ * make client suites order-independent and randomise the nightly no-retry run ([02e9b77](https://github.com/lgriffin/ESI.ts/commit/02e9b770981f3caaa2a5b349406c8dbfeb263fa7))
321
+ * make the live integration and contract tiers fail loudly without ESI_LIVE_TESTS ([79aee8b](https://github.com/lgriffin/ESI.ts/commit/79aee8b3b9a47917d16aa8a1a09a55f814532535))
322
+ * **mutation:** add a BDD-only Stryker run with a per-directory ratchet ([c47dde1](https://github.com/lgriffin/ESI.ts/commit/c47dde17ecff0d196c4c03d88a92faf062c4b641))
323
+ * **resilience:** specify retry classes, circuit states and dedupe over HTTP ([bf42a7a](https://github.com/lgriffin/ESI.ts/commit/bf42a7ac9bc0d6652dc2aa2b5d1d5c24e386f3a4))
324
+ * **scripts:** schema drift reports drift behind definition-style paths ([46bb854](https://github.com/lgriffin/ESI.ts/commit/46bb854dda03b8eaffdab7d979a28df04c0e3e12))
325
+ * **skills:** build the review fixture's EsiError with status first ([5f3553f](https://github.com/lgriffin/ESI.ts/commit/5f3553f77c28933282923fae09d59ae39218ef1f))
326
+ * **spec-audit:** hold step files to one step per file and ratchet the legacy ones ([405298e](https://github.com/lgriffin/ESI.ts/commit/405298e75bede0da43a83ce14ad2ba2294511475))
327
+ * **spec-audit:** skip the CLI fixtures where the audit cannot start ([3cb173e](https://github.com/lgriffin/ESI.ts/commit/3cb173eb3ed7a0587b4a87c9b517c0405f258fba))
328
+
329
+ ## [Unreleased]
330
+
331
+ ### Added
332
+
333
+ - **`EsiTokenManager`** — higher-level auth abstraction that owns the SSO token lifecycle for one or many characters (#185). Exchanges an authorization code, decodes the character id, name and scopes from the token, persists it through a pluggable `ITokenStorage`, refreshes ahead of expiry (`refreshSkewMs`, default 60 s), coalesces concurrent refreshes per character, persists the rotated refresh token before returning, and records an SSO `invalid_grant` so later calls fail locally with `TokenRevokedError`. `createClient(characterId)` returns an `EsiClient` wired with the character's token and a refresh provider bound to the manager; `tokenProviderFor(characterId)` exposes that provider for clients built by hand
334
+ - **Bulk refresh** — `refreshAll({ concurrency, expiringWithinMs, signal })` refreshes stored tokens with a concurrency cap (default 5), isolates failures per character, skips tokens outside an optional staleness window, and flags SSO 429/5xx failures as `retryable` (#187). Per-character failures never reject; every character gets a `RefreshResult`. A throwing `onProgress` callback and a non-finite `concurrency` value are tolerated; only a storage adapter that cannot list tokens rejects the call
335
+ - **`EveSsoClient`** — zero-dependency client for `login.eveonline.com`: `getAuthorizationUrl`, `exchangeCode`, `refresh`, `revoke`. `exchangeCode` repeats the `redirect_uri` from the authorization request (configured `callbackUrl` or a per-request `redirectUri`); an `invalid_grant` on a code exchange or revoke stays an `SsoError` and only a refresh maps it to `TokenRevokedError`; a 2xx body that is not a JSON object with both tokens is reported as `SsoError` `invalid_response`. Confidential clients authenticate with HTTP Basic; public clients use PKCE (`generatePkcePair`, `generateState`). SSO errors surface as `SsoError` (status + OAuth error code) or `TokenRevokedError`
336
+ - **Storage adapters** — `MemoryTokenStorage` and `FileTokenStorage` (atomic temp-file-and-rename writes, `0600` mode, serialised in-process writes, malformed entries skipped on load, `invalidate()` fenced against reads already in progress)
337
+ - **`runWithConcurrency`** internal utility in `src/core/util/concurrency.ts`
338
+ - `tests/bdd/features/core/0054-token-management.feature` — 26 EARS requirements covering the above, including the refresh-versus-removal and refresh-versus-re-authorization races, which the manager resolves in favour of the newer state
339
+ - `examples/token-manager.ts` and `npm run example:token-manager`
340
+
341
+ ## [9.9.0] - 2026-09-15
342
+
343
+ ### Added
344
+
345
+ - **Per-client structured logging** — `EsiClientConfig.logger` and `EsiClientConfig.logLevel` attach an `ILogger` to one client. `ILogger` gains `fatal` and `trace` levels and a second `context` argument; the pipeline now logs structured fields (`endpoint`, `method`, `url`, `status`) instead of interpolated strings. New root exports: `createDefaultLogger`, `toPinoLogger`, `getLogger`, `logFatal`, `logError`, `logWarn`, `logInfo`, `logDebug`, `logTrace`, `LogContext`, `LogLevel`
346
+ - **`guides/CHARTER.md`** — the engineering charter: architecture, design rules, testing tiers, quality gates, security, documentation, release and process as numbered EARS requirements with their enforcing script or CI job. Its gap register and guide roadmap are tracked as beads under `esi-l38` and GitHub issues #262–#287
347
+
348
+ ### Changed
349
+
350
+ - The global `setLogger()` now applies to every client that has no logger of its own. Resolution order is per-client logger, then the global logger, then the built-in pino default at `ESI_LOG_LEVEL` (default `warn`)
351
+ - The default export of `core/logger/logger` is deprecated in favour of `createDefaultLogger(level?)`
352
+ - TypeDoc output moved from `docs/` to `docs-site/public/api/` (git-ignored). `npm run clean` and `npm run docs` no longer delete the hand-written markdown in `docs/`
353
+ - Version sources realigned: `src/core/constants.ts` and the release-please manifest had stayed at 9.8.0 while `package.json` moved to 9.8.1
354
+
355
+ ### Fixed
356
+
357
+ - `toPinoLogger` detached pino's methods from their instance, so every log call through the default logger threw `Cannot read properties of undefined (reading 'Symbol(pino.msgPrefix)')`
358
+
359
+ ## [9.8.0] - 2026-09-10
360
+
361
+ No change to the published API surface — this release is entirely about how
362
+ the library's behaviour is specified and enforced. Consumers upgrading from
363
+ 9.7.0 get identical runtime code.
364
+
365
+ ### Added
366
+
367
+ - **EARS specification for the BDD suite** — all 52 feature files in `tests/bdd/` are now written as 327 atomic requirements in Easy Approach to Requirements Syntax, one per Gherkin `Rule:` block, with the scenarios that verify each nested beneath it. The same 401 scenarios run as before; no test was added, dropped, or merged
368
+ - **`npm run spec:audit`** — parses the Gherkin AST and enforces requirement form: exactly one `shall` per Rule, no vague or unmeasurable language, correct EARS grammar for `If`/`While`/`When`/`Where`, no requirement hidden in prose, no scenario outside a Rule, no feature without a description. Also `npm run spec:audit:verbose`. Runs as a required CI check (`EARS Spec Audit`) and as the final step of `npm run check:all`
369
+ - **`tests/bdd/GUIDE.md` and `tests/bdd/README.md`** — the conventions and a practical guide to writing requirements, including the pattern decision procedure, a finding-by-finding fix table, and the anti-pattern catalogue
370
+
371
+ ### Changed
372
+
373
+ - **Requirements now state what the tests actually assert.** Deriving each requirement from its assertions rather than its old scenario title surfaced scenarios whose titles claimed coverage the assertions never provided — a "transitions to half-open" check that asserts the circuit is closed, an "empty result" case that expects a 404 rejection, performance titles with no stated bound. Those requirements are now written narrowly and honestly, and the gaps are tracked as issues rather than papered over
374
+ - Four Gherkin step texts corrected where they misnamed the fixture or the method under test
375
+ - `@cucumber/gherkin` and `@cucumber/messages` promoted from transitive to explicit devDependencies
376
+ - Types regenerated from the ESI spec (hash only; no interface changes)
377
+
378
+ ## [9.7.0] - 2026-09-09
379
+
380
+ First release since 9.6.0. Versions 9.6.1 and 9.6.2 were bumped in
381
+ `package.json` but never published, so upgrading from 9.6.0 picks up
382
+ everything below.
383
+
384
+ ### Added
385
+
386
+ - **`fetchAllPages` concurrent pagination** — fetch every page of a paginated endpoint in parallel with configurable concurrency (default 8). Adds `fetchAllEndpoint()` on `BaseEsiClient` and 73 `fetchAll*` convenience methods across all 19 domain clients, mirroring the existing `stream*` methods ([#180](https://github.com/lgriffin/ESI.ts/issues/180))
387
+ - **Typed response headers on `EsiResponseMeta`** — `etag`, `pages`, `expires`, `errorLimitRemain` and `errorLimitReset` are now typed fields, so consumers get autocomplete and type safety instead of raw string header lookups ([#179](https://github.com/lgriffin/ESI.ts/issues/179))
388
+ - **Per-endpoint rate limit overrides** — `endpointOverrides` in `RateLimiterConfig` lets you set endpoint-specific limits that take precedence over the generated group specs, for tightening sensitive endpoints such as market history ([#181](https://github.com/lgriffin/ESI.ts/issues/181))
389
+ - **`FetchLike` injection and `createNoopLogger()`** — `ApiClient` accepts an injectable `fetch` via `setFetch()` / `getFetch()` so tests can substitute in-memory doubles, and `createNoopLogger()` gives silent test output. Ships with an `InMemoryFetch` test helper ([#213](https://github.com/lgriffin/ESI.ts/issues/213), [#214](https://github.com/lgriffin/ESI.ts/issues/214))
390
+ - **`npm run help`** — a grouped, intent-organised command reference replacing the flat ~100-entry script listing, with keyword search (`npm run help wallet`)
391
+
392
+ ### Changed
393
+
394
+ - **Actionable auth error messages** — `NO_AUTH_TOKEN`, 401 and 403 errors now name the likely cause (missing `ESI_ACCESS_TOKEN`, expired token, missing OAuth scopes) and the specific fix (env var, `setAccessToken`, `onTokenRefresh` callback, or scope configuration)
395
+ - **`npm audit` moved off the PR merge path** — the merge path now runs a diff-aware `Dependency Audit` job that compares base against head and fails only on advisories a PR _introduces_. Pre-existing advisories are the nightly audit's responsibility, so a third-party disclosure no longer turns unrelated PRs red ([#248](https://github.com/lgriffin/ESI.ts/pull/248))
396
+ - **Audit acceptance allowlist** — reviewed known risks are recorded in `scripts/audit-exceptions.json` with a reason and a mandatory expiry date, honoured by the PR gate, the nightly audit and the release gate. An entry past its expiry is a hard failure
397
+ - **Schemathesis moved off the PR path** to a nightly run ([#234](https://github.com/lgriffin/ESI.ts/pull/234))
398
+ - **Types regenerated from the ESI spec** — upstream renamed `AllianceDetail` to `AlliancesDetail`
399
+ - Dependency updates: zod, eslint, jest, knip, lint-staged, `@redocly/cli`, `@types/node`
400
+
401
+ ### Fixed
402
+
403
+ - **`USER_AGENT` reported a stale version** — `src/core/constants.ts` had drifted to `9.2.0` while `package.json` was on `9.6.1`, so requests identified themselves to CCP as `esi.ts/9.2.0`. `package.json`, `constants.ts` and `.release-please-manifest.json` are now aligned
404
+ - **Nightly audit report corruption** — `nightly-audit.yml` merged stderr into its JSON report, so a single warning line would have made every `jq` query silently report zero vulnerabilities
405
+ - Zod v4 deprecation: `z.ZodTypeAny` replaced with `z.ZodType`
406
+
407
+ ## [9.1.0] - 2026-08-14
408
+
409
+ ### Added
410
+
411
+ - **Schema rejection tests** — 104 new tests verifying Zod schemas correctly reject invalid input shapes
412
+ - **Domain property fuzz tests** — property-based fuzz testing across domain clients using fast-check
413
+ - **Schema validation benchmarks** — performance benchmarks for Zod schema validation paths
414
+ - **Domain response type tests** — compile-time type tests for domain client response types via tsd
415
+
416
+ ### Changed
417
+
418
+ - **Expanded documentation** — updated examples, architecture guide, and testing guide with broader coverage
419
+ - **README refreshed** — updated feature descriptions and endpoint counts
420
+
421
+ ### Fixed
422
+
423
+ - **Fuzz test date handling** — switched to integer-based date arbitrary to avoid invalid `Date` values in property-based tests
424
+
425
+ ## [9.0.0] - 2026-08-12
426
+
427
+ ### Breaking Changes
428
+
429
+ - **Default retry count changed from 0 to 3** — transient failures (502, 503, 504, timeout, rate limit) now retry automatically with exponential backoff and jitter. Set `maxRetries: 0` in `retryConfig` to restore the previous behavior
430
+ - **Generated Zod schemas removed** — the `src/schemas/generated/` directory has been removed; only hand-written schemas in `src/schemas/` remain
431
+
432
+ ### Added
433
+
434
+ - **Sub-path exports** — targeted imports for reduced bundle size:
435
+ - `@lgriffin/esi.ts/schemas` — Zod schemas for runtime validation
436
+ - `@lgriffin/esi.ts/errors` — error classes and type guards
437
+ - `@lgriffin/esi.ts/testing` — `TestDataFactory` for test mock data
438
+ - **`isCircuitOpen()` type guard** — checks whether an error is a `CircuitOpenError`, complementing the existing `isTimeout()`, `isRetryable()`, and `isValidationError()` guards
439
+ - **`generate:all` script** — runs all generators (types, endpoints, OKF) in one command
440
+ - **`generate:endpoints` script** — regenerates endpoint definitions from the ESI OpenAPI spec
441
+
442
+ ### Changed
443
+
444
+ - **Cursor pagination routed through full pipeline** — cursor-based pagination now goes through the same middleware pipeline (rate limiter, circuit breaker, retry, caching) as offset pagination
445
+ - **Pagination retry unified with `IRetryStrategy`** — pagination requests now use the injectable retry strategy instead of a separate retry path
446
+
447
+ ### Fixed
448
+
449
+ - **Response interceptor status fix** — response interceptors previously received a hardcoded 200 status; they now receive the actual HTTP status code from the response
450
+ - **CI consolidated** — `pr-validation.yml` merged into `ci.yml`; all PR validation now runs through the main CI pipeline
451
+
452
+ ## [7.4.0] - 2026-07-17
453
+
454
+ ### Added
455
+
456
+ - **`withSafeMode()` on all domain clients** — mirrors existing `withMetadata()`, surfaces the `EsiResult<T>` discriminated union (`{ ok: true, data, meta } | { ok: false, error }`) without needing to call `createClient()` directly
457
+ - **`responseSchema` on `routeEndpoints`** — was the only endpoint file without runtime response validation; now validated with `z.looseObject({ route: z.array(z.number()) })`
458
+
459
+ ### Changed
460
+
461
+ - **ESLint 8 → 10 flat config migration** — replaced `.eslintrc.cjs` with `eslint.config.mjs`, switched to unified `typescript-eslint` package, dropped `eslint-plugin-prettier` (redundant with lint-staged)
462
+ - **jest-fetch-mock 3 → 4** — updated null-body status mocks (204/304) to use `new Response(null, ...)` per Fetch spec
463
+ - Updated 11 minor/patch dependencies: @commitlint/cli, @microsoft/api-extractor, @redocly/cli, @types/node, @typescript-eslint/*, eslint-plugin-sonarjs, fast-check, knip, prettier, typedoc
464
+
465
+ ### Fixed
466
+
467
+ - CI: aligned `codeql.yml` branch targets to `[master, main, develop]`
468
+ - CI: pinned `jest-coverage-comment@main` → `@v1.0.34` (supply-chain risk)
469
+ - CI: added schema drift and generated types freshness checks to release pipeline
470
+
471
+ ### Deprecated
472
+
473
+ - `AllianceClient.getContacts()` — use `ContactsClient.getAllianceContacts()` instead
474
+ - `AllianceClient.getContactLabels()` — use `ContactsClient.getAllianceContactLabels()` instead
475
+
476
+ ## [7.3.0] - 2026-07-14
477
+
478
+ ### Added
479
+
480
+ - **`EsiResult<T>` discriminated union** and `safeMode` option for error-safe API calls
481
+ - **Branded ID types** (16 types) for type-safe ESI entity references
482
+ - **Expanded type-level tests** with tsd for error guards, endpoints, and domain types
483
+ - **Compile-time spec-to-Zod type alignment checks**
484
+ - **Schema drift detection** as a blocking CI check
485
+ - **Comprehensive testing gap closure** (+1233 tests)
486
+
487
+ ### Fixed
488
+
489
+ - Resolved three CI jobs failing with continue-on-error
490
+ - Normalized CRLF in API surface check
491
+ - Fixed schemathesis report permissions and `--url` flag
492
+ - Fixed API surface ordering issues
493
+ - Added missing `system_id` to `MarketOrderSchema` test data
494
+
495
+ ## [7.2.0] - 2026-07-08
496
+
497
+ ### Added
498
+
499
+ - **Contract testing infrastructure** — deep validation of all endpoint definitions against the live ESI OpenAPI spec (`npm run contract:live`). Checks path parameter alignment, required query params, request body consistency, auth requirements, response schema coverage, HTTP methods, pagination metadata, and deprecation sync. 8 contract validation categories with known-exception tracking.
500
+ - **Property-based fuzz testing** with [fast-check](https://github.com/dubzzz/fast-check) — 601 tests fuzzing `validatePathParam()`, `validateQueryParam()`, `buildEndpointPath()`, and all Zod schemas with random/adversarial inputs (`npm run fuzz`)
501
+ - **OpenAPI spec snapshot & diff** — `npm run contract:snapshot` saves a baseline; `npm run contract:diff` detects breaking changes via [oasdiff](https://github.com/Tufin/oasdiff) (Docker)
502
+ - **Consumer type tests** with [tsd](https://github.com/tsdjs/tsd) — verifies public API type correctness (`npm run test:types`)
503
+ - **Prism mock server** — `npm run mock:esi` starts a spec-conformant ESI mock on port 4010 via [@stoplight/prism-cli](https://stoplight.io/open-source/prism)
504
+ - **Schemathesis fuzz runner** — `npm run fuzz:api` runs Schemathesis against the Prism mock (Docker, weekly CI)
505
+ - Contract and fuzz test CI jobs added to `ci.yml` quality gate
506
+ - Weekly spec drift detection job added to `maintenance.yml`
507
+ - `jest.contract.config.cjs` and `jest.fuzz.config.cjs` test configurations
508
+
509
+ ### Dependencies
510
+
511
+ - Added `fast-check` (dev) — property-based testing framework
512
+ - Added `@stoplight/prism-cli` (dev) — OpenAPI mock server
513
+ - Added `tsd` (dev) — TypeScript type testing
514
+
515
+ ## [7.1.0] - 2026-07-08
516
+
517
+ ### Added
518
+
519
+ - **Redocly CLI integration** — lints the live ESI OpenAPI spec for structural validity and best-practice compliance (`npm run validate:spec`). Catches spec breakage from CCP before it breaks generated types, cache TTLs, or scopes. Baseline: 0 errors, 328 warnings (all known CCP spec issues).
520
+ - `redocly.yaml` config with tuned rulesets for ESI — structural rules as errors, CCP spec quirks as warnings
521
+ - `validate:spec` npm script added to `check:all` pipeline
522
+
523
+ ## [7.0.0] - 2026-07-08
524
+
525
+ ### Breaking Changes
526
+
527
+ - **Swagger 2.0 → OpenAPI 3.1 migration** — all generated types, cache TTLs, scopes, and rate limit groups are now sourced from the ESI OpenAPI 3.1 spec (`/meta/openapi.json`) instead of the deprecated Swagger 2.0 spec (`/latest/swagger.json`). See [esi-issues#1490](https://github.com/esi/esi-issues/issues/1490).
528
+ - **Generated interface names changed** — `EsiSpec` namespace types now use OpenAPI schema names (e.g., `AllianceDetail` instead of `GetAlliancesAllianceIdOk`). These are generated types; hand-written consumer types are unchanged.
529
+ - **Cache TTL metadata key** — internally changed from `x-cached-seconds` to `x-cache-age`. No consumer-facing impact (cache behavior is identical).
530
+
531
+ ### Changed
532
+
533
+ - Single OpenAPI spec fetch instead of dual Swagger + OpenAPI fetches
534
+ - Updated all scripts, tests, and documentation to reference OpenAPI spec
535
+ - Generated types now include 161 interfaces (up from 147), 126 cache TTLs, 70 scopes
536
+
537
+ ## [6.1.0] - 2026-07-07
538
+
539
+ ### Added
540
+
541
+ - **Fleet wing/squad name validation** — `renameFleetWing()` and `renameFleetSquad()` now reject names exceeding ESI's 10-character limit before sending the request, with a clear error message
542
+ - **43 runnable example scripts** covering all 210 ESI endpoints against live Tranquility (10 new example files: character-details, corporation-details, calendar-search, loyalty-pi, faction-details, industry-mining, market-orders, universe-encyclopedia, corp-contracts-wallet, dogma-meta-sov)
543
+ - **3 write-operation example scripts** — `write-operations.ts` (contacts, fittings, mail, UI lifecycle), `universe-post-helpers.ts` (name resolution, affiliation), `freelance-jobs.ts` (cursor-paginated queries)
544
+ - Live output captured for all example scripts in `examples/output/`
545
+ - 3 new TDD test files and 1 new BDD feature file (81 TDD files, 40 BDD features total)
546
+ - 2 new fleet validation unit tests
547
+
548
+ ### Fixed
549
+
550
+ - **Fleet test babel parse errors** — replaced TypeScript cast syntax `(result as any[]).forEach(...)` with direct index access in fleet tests (pre-existing bug unmasked by stricter transpilation)
551
+ - **Fleet rename test names** — test mock names shortened to respect ESI's 10-character limit (`'New Squad Name'` → `'New Squad'`)
552
+
553
+ ### Changed
554
+
555
+ - README rewritten with "Why ESI.ts vs. OpenAPI-generated clients" comparison, full endpoint coverage table, and updated architecture/testing references
556
+ - `guides/ARCHITECTURE.md`, `guides/TESTING.md`, and `TESTING.md` updated to current test counts (121 suites, 3,224 tests)
557
+ - Autopilot example waypoint changed from Jita to Rens
558
+
559
+ ### Schemas
560
+
561
+ - Multiple Zod schema fixes discovered during live endpoint validation: added missing enum values, corrected optional fields, and adjusted types to match actual ESI responses
562
+
563
+ ## [6.0.0] - 2026-07-03
564
+
565
+ ### Added
566
+
567
+ - **Runtime response validation** via [Zod](https://zod.dev/) schemas — every ESI endpoint response is validated at runtime, catching shape mismatches before they propagate to consumer code
568
+ - Zod schemas for all 31 domain modules (133 interfaces) in `src/schemas/`, exported under the `schemas` namespace
569
+ - `EsiValidationError` class (extends `EsiError`) thrown when response data doesn't match the expected schema
570
+ - `isValidationError()` type guard for catching validation errors
571
+ - `validateResponse` option on `EsiClientConfig` — on by default, can be disabled globally
572
+ - `responseSchema` field on `EndpointDefinition` — wires schemas into the request pipeline via `createClient()`
573
+ - New developer guide: `guides/RUNTIME-VALIDATION.md`
574
+ - Response Validation Pipeline diagram in `guides/ARCHITECTURE.md`
575
+ - Comprehensive TDD tests for schema parsing, validation integration, and common schemas (94 new tests)
576
+ - BDD feature and step definitions for 9 runtime validation scenarios
577
+
578
+ ### Changed
579
+
580
+ - All TypeScript types in `src/types/` are now derived from Zod schemas via `z.infer<>` — schemas are the single source of truth
581
+ - Schemas use `.passthrough()` mode so extra fields from ESI are preserved, not rejected
582
+ - Test mock data across 25 test files updated to be spec-accurate (required by runtime validation)
583
+ - Path-parameter IDs (e.g., `character_id`, `alliance_id`) are now optional in schemas, matching ESI which omits them from response bodies
584
+
585
+ ### Dependencies
586
+
587
+ - Added `zod` as a production dependency
588
+
589
+ ## [5.3.0] - 2026-06-30
590
+
591
+ ### Added
592
+
593
+ - **Accept-Language configuration** — `language` option on `EsiClientConfig` injects the `Accept-Language` header for localized ESI responses (en, de, fr, ja, ru, zh, ko, es); changeable at runtime via `ApiClient.setLanguage()`
594
+ - **ESI scope metadata** — generated `esi-scopes.generated.ts` with `EsiScope` union type (63 scopes) and `esiEndpointScopes` record mapping 119 authenticated endpoints to their required OAuth scopes
595
+ - Exported `EsiScope` type and `esiEndpointScopes` map from package root
596
+ - **Streaming pagination** — `stream*` methods on domain clients yield `PageResult<T>` one page at a time via `AsyncGenerator`, enabling backpressure and early termination for large paginated datasets
597
+ - Streaming methods added to `MarketClient` (6), `ContractsClient` (3), `WalletClient` (3), `AssetsClient` (2), `KillmailsClient` (2)
598
+ - `buildEndpointPath()` utility extracted from `createClient.ts` and exported from package root
599
+ - `streamEndpoint()` protected method on `BaseEsiClient` for building custom streaming domain clients
600
+ - Streaming pagination example (`npm run example:streaming`)
601
+
602
+ ## [5.2.0] - 2026-06-29
603
+
604
+ ### Added
605
+
606
+ - **Spec-driven type generation** from ESI swagger spec (`npm run generate:types`) — 147 TypeScript interfaces + cache TTL map for 119 endpoints
607
+ - **Spec-aware cache bypass** — GET requests within ESI-specified `x-cached-seconds` TTL return cached data with zero HTTP calls, layered on top of ETag caching
608
+ - **`batch()` and `batchPost()` methods** on `EsiClient` — bounded concurrency for multi-ID fetches, auto-chunking for POST endpoints
609
+ - **`EsiSpec` namespace export** with generated response types alongside hand-written types
610
+ - **Type drift detection** in `npm run validate:esi` — compares hand-written types against generated spec types
611
+ - CI step to verify generated types are up to date
612
+ - **Retry with exponential backoff** — configurable retry for transient 5xx, timeout, and rate limit errors with jitter; respects circuit breaker state; GET-only by default with `retryMutations` opt-in
613
+ - **`TimeoutError`** subclass of `EsiError` — typed timeout errors with `timeoutMs` property; per-request timeout override via `handleRequest()`
614
+ - **Enhanced response metadata** via `withMetadata()` — rate limit info (`RateLimitMeta`), response timing (`responseTimeMs`), and cache hit type (`cacheHitType`: `'spec-ttl'` | `'etag-304'` | `'stale-on-error'`)
615
+ - `RetryConfig` interface and `retryConfig` option on `EsiClientConfig`
616
+ - `CircuitOpenError` passthrough in request handler (previously wrapped as generic Error)
617
+ - **Per-group rate limiting** — 36 ESI rate limit groups extracted from the OpenAPI meta spec at build time; each group gets its own token bucket instead of a single global counter, preventing a burst of market requests from starving unrelated endpoints
618
+ - **Optional per-user bucketing** — `userKeyExtractor` config option creates separate bucket sets per user key, supporting multi-character EVE applications
619
+ - **Group-aware rate limit status** — `getGroupStatus(group)` and `getAllGroupStatuses()` methods for fine-grained rate limit monitoring; `isBlocked(group?)` accepts an optional group name
620
+ - Generated `esi-rate-limit-groups.generated.ts` with 146 endpoint-to-group mappings
621
+ - Exported `RateLimitGroupStatus` and `RateLimitGroupSpec` types
622
+
623
+ ## [5.1.0] - 2026-06-26
624
+
625
+ ### Added
626
+
627
+ - **`noUncheckedIndexedAccess`** compiler flag — array/record indexing now returns `T | undefined`, catching unguarded index access at compile time
628
+ - **`noImplicitReturns`** compiler flag — all function code paths must explicitly return a value
629
+ - **`noImplicitOverride`** compiler flag — `override` keyword required when overriding base class methods
630
+ - **`tsconfig.test.json`** — separate TypeScript config for tests, relaxing `noUncheckedIndexedAccess` for test utility patterns
631
+
632
+ ### Changed
633
+
634
+ - `RateLimiter` token cost lookup inlined (removed unnecessary `Record` indirection)
635
+ - Jest configs (`jest.unit.config.cjs`, `jest.integration.config.cjs`) now use `tsconfig.test.json`
636
+
637
+ ### Fixed
638
+
639
+ - Unguarded indexed access in `ApiRequestHandler`, `CircuitBreaker`, `RateLimiter`, and `headersUtil`
640
+
641
+ ## [5.0.0] - 2026-06-26
642
+
643
+ ### Breaking Changes
644
+
645
+ - **Removed `SovereigntyClient.getSovereigntyMap()`** — sunset ESI endpoint; use `getSovereigntySystems()` instead
646
+ - **Removed `SovereigntyClient.getSovereigntyStructures()`** — sunset ESI endpoint; use `getSovereigntySystems()` instead
647
+
648
+ ### Added
649
+
650
+ - **Dependabot** — automated weekly dependency update PRs with grouped ESLint and testing ecosystems
651
+ - **CodeQL Analysis** — GitHub-native security scanning workflow
652
+ - **Commitlint** — conventional commit message validation via husky hook
653
+ - **Version consistency script** — `npm run validate:versions` checks `package.json` matches `constants.ts`
654
+ - **`npm run check:all`** — comprehensive validation including ESI endpoint and version checks
655
+ - Coverage and npm download badges in README
656
+ - `.editorconfig`, `.nvmrc`, `CONTRIBUTING.md`, `SECURITY.md`
657
+ - ClientRegistry test coverage for all 35 client types
658
+
659
+ ### Fixed
660
+
661
+ - **POST body format** for asset and contact endpoints — request body was incorrectly structured
662
+ - **POST body format** for `/universe/ids` and `/universe/names` — same issue
663
+ - **Circuit breaker** now treats HTTP 420/429 rate-limit responses as failures
664
+ - **configManager** uses `require.resolve` instead of `process.cwd()` fallback for reliable path resolution
665
+ - **User-Agent version** — ESI requests were sending `esi.ts/3.4.0` instead of current version
666
+ - **Compatibility date** — updated from `2025-12-16` to `2026-05-19` (Equinox)
667
+ - TypeScript badge in README updated from 5.0+ to 6.0+
668
+
669
+ ### Removed
670
+
671
+ - `src/TODO` — fully completed roadmap
672
+ - `jest.improved.config.cjs` — dead config matching zero test files
673
+ - `docs/` — generated TypeDoc output removed from git tracking (CI builds as artifact)
674
+ - Unused `getHeaders` test helper
675
+
676
+ ### Changed
677
+
678
+ - `package.json`: added `keywords`, `homepage`, `bugs` URLs, `files` includes README/LICENSE/CHANGELOG
679
+ - Moved `docs/architecture.md` to `guides/ARCHITECTURE.md`
680
+ - Updated `guides/TESTING.md` and `guides/DOCUMENTATION.md` to current state
681
+ - Test coverage raised from 75% to 91%+
682
+
683
+ ### Dependencies
684
+
685
+ - `@typescript-eslint/eslint-plugin`: 7.18.0 → 8.x
686
+ - `@typescript-eslint/parser`: 7.18.0 → 8.x
687
+ - `@types/node`: 18.x → 26.x
688
+ - `@commitlint/cli`: 19.x → 21.x
689
+ - `eslint-config-prettier`: 9.x → 10.x
690
+ - `lint-staged`: 16.x → 17.x
691
+ - `jest-junit`: 16.x → 17.x
692
+ - GitHub Actions: checkout v4→v7, setup-node v4→v6, upload-artifact v4→v7, codeql-action v3→v4, gh-pages v3→v4, action-gh-release v1→v3
693
+
694
+ ## [4.1.1] - 2026-06-08
695
+
696
+ ### Changed
697
+
698
+ - **TypeScript 5.9 → 6.0** — upgraded to TypeScript 6.0.3, the last version before the Go-based TS7 compiler
699
+ - `tsconfig.json`: added explicit `moduleResolution: "bundler"` (TS6 changed the default from `node` to `bundler`)
700
+ - `tsconfig.json`: added explicit `rootDir: "./src"` (TS6 requires this when emitting)
701
+ - `tsconfig.json`: removed `esModuleInterop: true` (always-on in TS6)
702
+
703
+ ## [4.1.0] - 2026-06-08
704
+
705
+ ### Added
706
+
707
+ - **Equinox ESI compliance** — new endpoints and types for the [Equinox expansion](https://developers.eveonline.com/blog/equinox-on-esi-structures-sovereignty-and-access-lists) (compatibility date 2026-05-19)
708
+ - **`SovereigntyClient.getSovereigntySystems()`** — combined sovereignty systems route with separate ADM indices (`military_index`, `industry_index`, `strategic_index`), occupancy data, and anchored structures in a single response
709
+ - **`SkyhooksClient`** — new domain client with `getSovereigntyHubs()`, `getOrbitalSkyhooks()`, and `getRaidableSkyhooks()` endpoints for Upwell sovereignty structures
710
+ - **`MercenaryClient`** — new domain client with `getMercenaryDens()` and `getMercenaryTacticalOperations()` endpoints for mercenary content
711
+ - **`AccessListsClient`** — new domain client with `getAccessList(id)` for reading access list (ACL) contents including character, corporation, and alliance entries
712
+ - `TestDataFactory` methods for all new Equinox types: `createSovereigntySystem()`, `createSovereigntyHub()`, `createOrbitalSkyhook()`, `createRaidableSkyhook()`, `createMercenaryDen()`, `createMercenaryTacticalOperation()`, `createAccessListEntry()`
713
+ - TDD and BDD test coverage for all new endpoints
714
+
715
+ ### Changed
716
+
717
+ - `SovereigntyClient.getSovereigntyMap()` and `getSovereigntyStructures()` marked as deprecated — use `getSovereigntySystems()` instead
718
+ - Domain client count increased from 32 to 35
719
+
720
+ ### Dependencies
721
+
722
+ - `ts-jest`: 29.4.9 → 29.4.11
723
+ - `eslint-plugin-prettier`: 5.5.5 → 5.5.6
724
+
725
+ ## [4.0.0] - 2026-05-15
726
+
727
+ ### Breaking Changes
728
+
729
+ - **Removed `RateLimiter.getInstance()` singleton** - Create instances with `new RateLimiter()` instead
730
+ - **Removed global cache/circuit breaker functions** - `initializeETagCache()`, `getETagCache()`, `resetETagCache()`, `initializeCircuitBreaker()`, `getCircuitBreaker()`, `resetCircuitBreaker()` are no longer exported from `ApiRequestHandler`
731
+ - Each `EsiClient` and `ApiClientBuilder` now creates its own `RateLimiter`, `ETagCacheManager`, and `CircuitBreaker` instances
732
+
733
+ ### Added
734
+
735
+ - **`BaseEsiClient` base class** — eliminates ~650 lines of repeated constructor/field/`withMetadata()` boilerplate across all 33 domain clients
736
+ - **`RequestDeduplicator`** — coalesces concurrent identical GET requests into a single in-flight fetch, sharing the result across all callers (enabled by default; disable with `enableRequestDeduplication: false`)
737
+ - **`EsiDiagnostics` API** — `client.diagnostics` accessor for cache/circuit-breaker stats, moved out of the main `EsiClient` API surface
738
+ - **`fetchPages()` async generator** — memory-efficient page-by-page iteration over paginated ESI responses
739
+ - **Request timeouts** — `config.timeout` now wired to `AbortController` (default 30s); previously the field existed but was never connected to `fetch()` calls
740
+ - **`EsiError` retry helpers** — `isTimeout()`, `retryable` getter, and `isRetryable()` guard for smarter consumer retry logic
741
+ - **`RateLimiterConfig`** — `minDelayMs` and `decelerationThreshold` exposed via `EsiClientConfig` for consumer-tunable rate limiting
742
+ - TypeScript declaration files (`.d.ts`) now emitted with builds
743
+ - `exports` field in `package.json` for modern Node.js module resolution
744
+ - `engines` field specifying Node.js >= 18.0.0
745
+ - `publishConfig` with public access for scoped package
746
+ - `RateLimiter`, `ETagCacheManager`, and `CircuitBreaker` classes exported from main index
747
+ - `ApiClientBuilder.setRateLimiter()`, `.setCache()`, `.setCircuitBreaker()` builder methods
748
+ - `validateBaseUrl()` for SSRF protection — validates ESI host allowlist and HTTPS
749
+ - `unsafeAllowCustomHost` config option to bypass base URL validation
750
+ - URL sanitization in `EsiError` — sensitive query params (`token`, `access_token`, `api_key`) are redacted
751
+ - Path parameters encoded with `encodeURIComponent()` for defense-in-depth
752
+ - URL assertions in all client unit tests — every test now verifies the correct endpoint URL
753
+ - Endpoint definition contract tests — 1800+ tests validating path templates, params, methods
754
+ - Test coverage for `RequestDeduplicator`, `EsiDiagnostics`, `AsyncPaginationIterator`, and extended `EsiError` tests
755
+ - `CHANGELOG.md` following Keep a Changelog format
756
+ - Changelog validation step in release workflow
757
+
758
+ ### Fixed
759
+
760
+ - `.d.ts` files not generated during build (`declaration: true` added to `tsconfig.json`)
761
+ - Release pipeline CNAME placeholder removed from GitHub Pages deployment
762
+ - `ContractsClient.ts` test file renamed to `.test.ts` so Jest actually runs it
763
+
764
+ ### Changed
765
+
766
+ - `RateLimiter` constructor is now public
767
+ - Cache, rate limiter, and circuit breaker are instance-based per client (no global shared state)
768
+ - `ApiClientBuilder.build()` auto-creates a `RateLimiter` if none was explicitly set
769
+ - `api-responses.ts` (1366 lines) split into 29 domain-specific type files with barrel re-export for backward compatibility
770
+ - `RateLimiter` uses `logWarn` instead of `console.warn` for consistent observability
771
+ - Reduced allocations and deduplicated fetch/sleep logic across core modules
772
+
773
+ ## [3.4.0] - 2026-04-29
774
+
775
+ ### Added
776
+
777
+ - ESI response header best practices documentation
778
+ - Dogma test coverage (10 TDD + 9 BDD tests)
779
+ - Gated authenticated integration tests (32 tests across 14 endpoint groups)
780
+ - EVE SSO token creator for local integration testing
781
+ - Search endpoint `categories` query parameter
782
+
783
+ ### Fixed
784
+
785
+ - 3 industry endpoint paths (`corporation` -> `corporations`) for mining routes
786
+ - Pagination middleware bypass: pages 2+ now route through request pipeline
787
+ - Consolidated duplicate alliance contact endpoints