@lgriffin/esi.ts 10.2.0 → 11.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 (431) hide show
  1. package/CHANGELOG.md +414 -32
  2. package/LICENSE +667 -19
  3. package/NOTICE +19 -0
  4. package/README.md +149 -902
  5. package/dist/EsiClient.d.mts +5 -36
  6. package/dist/EsiClient.d.ts +5 -36
  7. package/dist/EsiClient.d.ts.map +1 -1
  8. package/dist/EsiClientBuilder.d.mts +128 -1
  9. package/dist/EsiClientBuilder.d.ts +128 -1
  10. package/dist/EsiClientBuilder.d.ts.map +1 -1
  11. package/dist/adapters/PipelineTransport.d.ts +16 -0
  12. package/dist/adapters/PipelineTransport.d.ts.map +1 -0
  13. package/dist/auth/EsiTokenManager.d.mts +62 -21
  14. package/dist/auth/EsiTokenManager.d.ts +62 -21
  15. package/dist/auth/EsiTokenManager.d.ts.map +1 -1
  16. package/dist/auth/EveSsoClient.d.mts +9 -9
  17. package/dist/auth/EveSsoClient.d.ts +9 -9
  18. package/dist/auth/EveSsoClient.d.ts.map +1 -1
  19. package/dist/auth/errors.d.mts +2 -2
  20. package/dist/auth/errors.d.ts +2 -2
  21. package/dist/auth/errors.d.ts.map +1 -1
  22. package/dist/auth/jwt.d.mts +7 -7
  23. package/dist/auth/jwt.d.ts +7 -7
  24. package/dist/auth/jwt.d.ts.map +1 -1
  25. package/dist/auth/storage/FileTokenStorage.d.mts +1 -1
  26. package/dist/auth/storage/FileTokenStorage.d.ts +1 -1
  27. package/dist/auth/storage/FileTokenStorage.d.ts.map +1 -1
  28. package/dist/{chunk-BBSHKVPI.js → chunk-CM63VGGF.js} +1 -1
  29. package/dist/chunk-CM63VGGF.js.map +1 -0
  30. package/dist/chunk-DFRTIZK4.js +275 -0
  31. package/dist/chunk-DFRTIZK4.js.map +1 -0
  32. package/dist/{chunk-VZS32UQ3.mjs → chunk-HXS7ZVEL.mjs} +129 -28
  33. package/dist/chunk-HXS7ZVEL.mjs.map +1 -0
  34. package/dist/chunk-JNFQXEWZ.mjs +275 -0
  35. package/dist/chunk-JNFQXEWZ.mjs.map +1 -0
  36. package/dist/chunk-KIY3XA5Q.mjs +2980 -0
  37. package/dist/chunk-KIY3XA5Q.mjs.map +1 -0
  38. package/dist/{chunk-R7D7M2LQ.mjs → chunk-MGOE2YNH.mjs} +1 -1
  39. package/dist/chunk-MGOE2YNH.mjs.map +1 -0
  40. package/dist/{chunk-SAQBXXTM.js → chunk-MRKZ55XI.js} +130 -29
  41. package/dist/chunk-MRKZ55XI.js.map +1 -0
  42. package/dist/chunk-NKFIHGC6.js +2980 -0
  43. package/dist/chunk-NKFIHGC6.js.map +1 -0
  44. package/dist/{chunk-MSJHIJXA.js → chunk-S73PKM2Y.js} +20 -5
  45. package/dist/chunk-S73PKM2Y.js.map +1 -0
  46. package/dist/{chunk-3ZI5A37L.mjs → chunk-YUHGSPQ7.mjs} +19 -4
  47. package/dist/chunk-YUHGSPQ7.mjs.map +1 -0
  48. package/dist/client/identity.d.mts +20 -0
  49. package/dist/client/identity.d.ts +20 -0
  50. package/dist/client/identity.d.ts.map +1 -0
  51. package/dist/client/index.d.mts +11 -0
  52. package/dist/client/index.d.ts +11 -0
  53. package/dist/client/index.d.ts.map +1 -0
  54. package/dist/client/index.js +6607 -0
  55. package/dist/client/index.js.map +1 -0
  56. package/dist/client/index.mjs +6607 -0
  57. package/dist/client/index.mjs.map +1 -0
  58. package/dist/client/runtime.d.mts +67 -0
  59. package/dist/client/runtime.d.ts +67 -0
  60. package/dist/client/runtime.d.ts.map +1 -0
  61. package/dist/clients/BaseEsiClient.d.ts.map +1 -1
  62. package/dist/clients/ClientRegistry.d.mts +45 -0
  63. package/dist/clients/ClientRegistry.d.ts +45 -0
  64. package/dist/clients/ClientRegistry.d.ts.map +1 -0
  65. package/dist/clients/CorporationsClient.d.mts +3 -3
  66. package/dist/clients/CorporationsClient.d.ts +3 -3
  67. package/dist/clients/CorporationsClient.d.ts.map +1 -1
  68. package/dist/clients/MailClient.d.mts +2 -2
  69. package/dist/clients/MailClient.d.ts +2 -2
  70. package/dist/clients/MailClient.d.ts.map +1 -1
  71. package/dist/clients/MetaClient.d.mts +3 -2
  72. package/dist/clients/MetaClient.d.ts +3 -2
  73. package/dist/clients/MetaClient.d.ts.map +1 -1
  74. package/dist/clients/MilitaryCampaignsClient.d.mts +14 -7
  75. package/dist/clients/MilitaryCampaignsClient.d.ts +14 -7
  76. package/dist/clients/MilitaryCampaignsClient.d.ts.map +1 -1
  77. package/dist/clients/RouteClient.d.mts +4 -4
  78. package/dist/clients/RouteClient.d.ts +4 -4
  79. package/dist/clients/RouteClient.d.ts.map +1 -1
  80. package/dist/clients/SkillsClient.d.mts +1 -1
  81. package/dist/clients/SkillsClient.d.ts +1 -1
  82. package/dist/clients/SkillsClient.d.ts.map +1 -1
  83. package/dist/clients/SkyhooksClient.d.mts +4 -3
  84. package/dist/clients/SkyhooksClient.d.ts +4 -3
  85. package/dist/clients/SkyhooksClient.d.ts.map +1 -1
  86. package/dist/core/ApiClient.d.mts +11 -0
  87. package/dist/core/ApiClient.d.ts +11 -0
  88. package/dist/core/ApiClient.d.ts.map +1 -1
  89. package/dist/core/ApiClientBuilder.d.ts.map +1 -1
  90. package/dist/core/ApiRequestHandler.d.ts +2 -1
  91. package/dist/core/ApiRequestHandler.d.ts.map +1 -1
  92. package/dist/core/BatchRequestHandler.d.mts +13 -2
  93. package/dist/core/BatchRequestHandler.d.ts +13 -2
  94. package/dist/core/BatchRequestHandler.d.ts.map +1 -1
  95. package/dist/core/EsiClientConfig.d.mts +60 -0
  96. package/dist/core/EsiClientConfig.d.ts +60 -0
  97. package/dist/core/EsiClientConfig.d.ts.map +1 -0
  98. package/dist/core/RetryStrategy.d.mts +3 -3
  99. package/dist/core/RetryStrategy.d.ts +3 -3
  100. package/dist/core/RetryStrategy.d.ts.map +1 -1
  101. package/dist/core/cache/ETagCacheManager.d.mts +9 -4
  102. package/dist/core/cache/ETagCacheManager.d.ts +9 -4
  103. package/dist/core/cache/ETagCacheManager.d.ts.map +1 -1
  104. package/dist/core/cache/cacheKey.d.ts +31 -6
  105. package/dist/core/cache/cacheKey.d.ts.map +1 -1
  106. package/dist/core/circuitBreaker/CircuitBreaker.d.mts +8 -12
  107. package/dist/core/circuitBreaker/CircuitBreaker.d.ts +8 -12
  108. package/dist/core/circuitBreaker/CircuitBreaker.d.ts.map +1 -1
  109. package/dist/core/clock.d.ts +8 -0
  110. package/dist/core/clock.d.ts.map +1 -0
  111. package/dist/core/configureApiClient.d.mts +1 -1
  112. package/dist/core/configureApiClient.d.ts +1 -1
  113. package/dist/core/configureApiClient.d.ts.map +1 -1
  114. package/dist/core/constants.d.ts +3 -3
  115. package/dist/core/constants.d.ts.map +1 -1
  116. package/dist/core/endpoints/EndpointDefinition.d.mts +17 -0
  117. package/dist/core/endpoints/EndpointDefinition.d.ts +17 -0
  118. package/dist/core/endpoints/EndpointDefinition.d.ts.map +1 -1
  119. package/dist/core/endpoints/assetEndpoints.d.mts +12 -0
  120. package/dist/core/endpoints/assetEndpoints.d.ts +12 -0
  121. package/dist/core/endpoints/assetEndpoints.d.ts.map +1 -1
  122. package/dist/core/endpoints/characterEndpoints.d.mts +10 -0
  123. package/dist/core/endpoints/characterEndpoints.d.ts +10 -0
  124. package/dist/core/endpoints/characterEndpoints.d.ts.map +1 -1
  125. package/dist/core/endpoints/contactEndpoints.d.mts +1 -0
  126. package/dist/core/endpoints/contactEndpoints.d.ts +1 -0
  127. package/dist/core/endpoints/contactEndpoints.d.ts.map +1 -1
  128. package/dist/core/endpoints/contractEndpoints.d.mts +2 -0
  129. package/dist/core/endpoints/contractEndpoints.d.ts +2 -0
  130. package/dist/core/endpoints/contractEndpoints.d.ts.map +1 -1
  131. package/dist/core/endpoints/corporationEndpoints.d.mts +16 -3
  132. package/dist/core/endpoints/corporationEndpoints.d.ts +16 -3
  133. package/dist/core/endpoints/corporationEndpoints.d.ts.map +1 -1
  134. package/dist/core/endpoints/createClient.d.mts +2 -2
  135. package/dist/core/endpoints/createClient.d.ts +2 -2
  136. package/dist/core/endpoints/createClient.d.ts.map +1 -1
  137. package/dist/core/endpoints/esi-cache-ttls.generated.d.ts.map +1 -1
  138. package/dist/core/endpoints/esi-rate-limit-groups.generated.d.ts.map +1 -1
  139. package/dist/core/endpoints/esi-scopes.generated.d.ts.map +1 -1
  140. package/dist/core/endpoints/fittingEndpoints.d.mts +3 -0
  141. package/dist/core/endpoints/fittingEndpoints.d.ts +3 -0
  142. package/dist/core/endpoints/fittingEndpoints.d.ts.map +1 -1
  143. package/dist/core/endpoints/fleetEndpoints.d.mts +6 -0
  144. package/dist/core/endpoints/fleetEndpoints.d.ts +6 -0
  145. package/dist/core/endpoints/fleetEndpoints.d.ts.map +1 -1
  146. package/dist/core/endpoints/industryEndpoints.d.mts +2 -2
  147. package/dist/core/endpoints/industryEndpoints.d.ts +2 -2
  148. package/dist/core/endpoints/mailEndpoints.d.mts +2 -0
  149. package/dist/core/endpoints/mailEndpoints.d.ts +2 -0
  150. package/dist/core/endpoints/mailEndpoints.d.ts.map +1 -1
  151. package/dist/core/endpoints/mercenaryEndpoints.d.mts +1 -1
  152. package/dist/core/endpoints/mercenaryEndpoints.d.ts +1 -1
  153. package/dist/core/endpoints/metaEndpoints.d.mts +23 -8
  154. package/dist/core/endpoints/metaEndpoints.d.ts +23 -8
  155. package/dist/core/endpoints/metaEndpoints.d.ts.map +1 -1
  156. package/dist/core/endpoints/militaryCampaignEndpoints.d.mts +78 -49
  157. package/dist/core/endpoints/militaryCampaignEndpoints.d.ts +78 -49
  158. package/dist/core/endpoints/militaryCampaignEndpoints.d.ts.map +1 -1
  159. package/dist/core/endpoints/paragonHubEndpoints.d.mts +5 -5
  160. package/dist/core/endpoints/paragonHubEndpoints.d.ts +5 -5
  161. package/dist/core/endpoints/skyhookEndpoints.d.mts +10 -8
  162. package/dist/core/endpoints/skyhookEndpoints.d.ts +10 -8
  163. package/dist/core/endpoints/skyhookEndpoints.d.ts.map +1 -1
  164. package/dist/core/logger/DefaultLogger.d.mts +20 -1
  165. package/dist/core/logger/DefaultLogger.d.ts +20 -1
  166. package/dist/core/logger/DefaultLogger.d.ts.map +1 -1
  167. package/dist/core/logger/ILogger.d.mts +9 -0
  168. package/dist/core/logger/ILogger.d.ts +9 -0
  169. package/dist/core/logger/ILogger.d.ts.map +1 -1
  170. package/dist/core/logger/NoopLogger.d.ts.map +1 -1
  171. package/dist/core/logger/clientLog.d.ts.map +1 -1
  172. package/dist/core/logger/loggerUtil.d.ts.map +1 -1
  173. package/dist/core/logger/redactLog.d.ts +23 -0
  174. package/dist/core/logger/redactLog.d.ts.map +1 -0
  175. package/dist/core/logger/resolveLogger.d.ts +3 -3
  176. package/dist/core/logger/resolveLogger.d.ts.map +1 -1
  177. package/dist/core/pagination/AsyncPaginationIterator.d.ts.map +1 -1
  178. package/dist/core/pagination/CursorPaginationHandler.d.mts +2 -1
  179. package/dist/core/pagination/CursorPaginationHandler.d.ts +2 -1
  180. package/dist/core/pagination/CursorPaginationHandler.d.ts.map +1 -1
  181. package/dist/core/ports/CacheStore.d.mts +32 -0
  182. package/dist/core/ports/CacheStore.d.ts +32 -0
  183. package/dist/core/ports/CacheStore.d.ts.map +1 -0
  184. package/dist/core/ports/Clock.d.mts +13 -0
  185. package/dist/core/ports/Clock.d.ts +13 -0
  186. package/dist/core/ports/Clock.d.ts.map +1 -0
  187. package/dist/core/ports/HttpTransport.d.mts +7 -0
  188. package/dist/core/ports/HttpTransport.d.ts +7 -0
  189. package/dist/core/ports/HttpTransport.d.ts.map +1 -0
  190. package/dist/core/ports/Identity.d.mts +31 -0
  191. package/dist/core/ports/Identity.d.ts +31 -0
  192. package/dist/core/ports/Identity.d.ts.map +1 -0
  193. package/dist/core/ports/Logger.d.mts +17 -0
  194. package/dist/core/ports/Logger.d.ts +17 -0
  195. package/dist/core/ports/Logger.d.ts.map +1 -0
  196. package/dist/core/ports/OperationTransport.d.mts +42 -0
  197. package/dist/core/ports/OperationTransport.d.ts +42 -0
  198. package/dist/core/ports/OperationTransport.d.ts.map +1 -0
  199. package/dist/core/ports/TokenProvider.d.mts +7 -0
  200. package/dist/core/ports/TokenProvider.d.ts +7 -0
  201. package/dist/core/ports/TokenProvider.d.ts.map +1 -0
  202. package/dist/core/ports/index.d.mts +14 -0
  203. package/dist/core/ports/index.d.ts +14 -0
  204. package/dist/core/ports/index.d.ts.map +1 -0
  205. package/dist/core/rateLimiter/RateLimiter.d.mts +11 -4
  206. package/dist/core/rateLimiter/RateLimiter.d.ts +11 -4
  207. package/dist/core/rateLimiter/RateLimiter.d.ts.map +1 -1
  208. package/dist/core/requestPipeline/cachePolicy.d.ts +5 -3
  209. package/dist/core/requestPipeline/cachePolicy.d.ts.map +1 -1
  210. package/dist/core/requestPipeline/fetchExecution.d.ts +3 -1
  211. package/dist/core/requestPipeline/fetchExecution.d.ts.map +1 -1
  212. package/dist/core/requestPipeline/headers.d.ts +1 -1
  213. package/dist/core/requestPipeline/headers.d.ts.map +1 -1
  214. package/dist/core/requestPipeline/index.d.ts +3 -5
  215. package/dist/core/requestPipeline/index.d.ts.map +1 -1
  216. package/dist/core/requestPipeline/paginationOrchestration.d.ts +1 -1
  217. package/dist/core/requestPipeline/paginationOrchestration.d.ts.map +1 -1
  218. package/dist/core/requestPipeline/statusHandling.d.ts +3 -3
  219. package/dist/core/requestPipeline/statusHandling.d.ts.map +1 -1
  220. package/dist/core/util/callerIdentity.d.ts +10 -0
  221. package/dist/core/util/callerIdentity.d.ts.map +1 -0
  222. package/dist/core/util/concurrency.d.ts +2 -2
  223. package/dist/core/util/concurrency.d.ts.map +1 -1
  224. package/dist/core/util/error.d.mts +92 -3
  225. package/dist/core/util/error.d.ts +92 -3
  226. package/dist/core/util/error.d.ts.map +1 -1
  227. package/dist/core/util/retry.d.mts +4 -4
  228. package/dist/core/util/retry.d.ts +4 -4
  229. package/dist/core/util/retry.d.ts.map +1 -1
  230. package/dist/core/util/validation.d.ts +6 -0
  231. package/dist/core/util/validation.d.ts.map +1 -1
  232. package/dist/errors.d.mts +2 -3
  233. package/dist/errors.d.ts +2 -3
  234. package/dist/errors.d.ts.map +1 -1
  235. package/dist/errors.js +29 -3
  236. package/dist/errors.js.map +1 -1
  237. package/dist/errors.mjs +28 -2
  238. package/dist/generated/operations.generated.d.mts +9379 -0
  239. package/dist/generated/operations.generated.d.ts +9379 -0
  240. package/dist/generated/operations.generated.d.ts.map +1 -0
  241. package/dist/index.d.mts +4 -4
  242. package/dist/index.d.ts +4 -4
  243. package/dist/index.d.ts.map +1 -1
  244. package/dist/index.js +699 -2865
  245. package/dist/index.js.map +1 -1
  246. package/dist/index.mjs +359 -2525
  247. package/dist/index.mjs.map +1 -1
  248. package/dist/schemas/character.d.mts +5 -0
  249. package/dist/schemas/character.d.ts +5 -0
  250. package/dist/schemas/character.d.ts.map +1 -1
  251. package/dist/schemas/contacts.d.mts +2 -0
  252. package/dist/schemas/contacts.d.ts +2 -0
  253. package/dist/schemas/contacts.d.ts.map +1 -1
  254. package/dist/schemas/corporation.d.mts +24 -3
  255. package/dist/schemas/corporation.d.ts +24 -3
  256. package/dist/schemas/corporation.d.ts.map +1 -1
  257. package/dist/schemas/fittings.d.mts +4 -0
  258. package/dist/schemas/fittings.d.ts +4 -0
  259. package/dist/schemas/fittings.d.ts.map +1 -1
  260. package/dist/schemas/fleet.d.mts +8 -0
  261. package/dist/schemas/fleet.d.ts +8 -0
  262. package/dist/schemas/fleet.d.ts.map +1 -1
  263. package/dist/schemas/index.js +24 -2
  264. package/dist/schemas/index.js.map +1 -1
  265. package/dist/schemas/index.mjs +23 -1
  266. package/dist/schemas/industry.d.mts +2 -2
  267. package/dist/schemas/industry.d.ts +2 -2
  268. package/dist/schemas/mail.d.mts +4 -0
  269. package/dist/schemas/mail.d.ts +4 -0
  270. package/dist/schemas/mail.d.ts.map +1 -1
  271. package/dist/schemas/mercenary.d.mts +1 -1
  272. package/dist/schemas/mercenary.d.ts +1 -1
  273. package/dist/schemas/meta.d.mts +18 -9
  274. package/dist/schemas/meta.d.ts +18 -9
  275. package/dist/schemas/meta.d.ts.map +1 -1
  276. package/dist/schemas/military-campaigns.d.mts +54 -10
  277. package/dist/schemas/military-campaigns.d.ts +54 -10
  278. package/dist/schemas/military-campaigns.d.ts.map +1 -1
  279. package/dist/schemas/paragon-hub.d.mts +4 -4
  280. package/dist/schemas/paragon-hub.d.ts +4 -4
  281. package/dist/schemas/skyhooks.d.mts +18 -6
  282. package/dist/schemas/skyhooks.d.ts +18 -6
  283. package/dist/schemas/skyhooks.d.ts.map +1 -1
  284. package/dist/sde/clock.d.ts +11 -0
  285. package/dist/sde/clock.d.ts.map +1 -0
  286. package/dist/sde/domain/characters/schemas.d.ts +149 -0
  287. package/dist/sde/domain/characters/schemas.d.ts.map +1 -0
  288. package/dist/sde/domain/characters/types.d.mts +163 -0
  289. package/dist/sde/domain/characters/types.d.ts +163 -0
  290. package/dist/sde/domain/characters/types.d.ts.map +1 -0
  291. package/dist/sde/domain/content/schemas.d.ts +84 -0
  292. package/dist/sde/domain/content/schemas.d.ts.map +1 -0
  293. package/dist/sde/domain/content/types.d.mts +91 -0
  294. package/dist/sde/domain/content/types.d.ts +91 -0
  295. package/dist/sde/domain/content/types.d.ts.map +1 -0
  296. package/dist/sde/domain/corporations/schemas.d.ts +113 -0
  297. package/dist/sde/domain/corporations/schemas.d.ts.map +1 -0
  298. package/dist/sde/domain/corporations/types.d.mts +118 -0
  299. package/dist/sde/domain/corporations/types.d.ts +118 -0
  300. package/dist/sde/domain/corporations/types.d.ts.map +1 -0
  301. package/dist/sde/domain/dogma/schemas.d.ts +76 -0
  302. package/dist/sde/domain/dogma/schemas.d.ts.map +1 -0
  303. package/dist/sde/domain/dogma/types.d.mts +80 -0
  304. package/dist/sde/domain/dogma/types.d.ts +80 -0
  305. package/dist/sde/domain/dogma/types.d.ts.map +1 -0
  306. package/dist/sde/domain/industry/schemas.d.ts +204 -0
  307. package/dist/sde/domain/industry/schemas.d.ts.map +1 -0
  308. package/dist/sde/domain/industry/types.d.mts +88 -0
  309. package/dist/sde/domain/industry/types.d.ts +88 -0
  310. package/dist/sde/domain/industry/types.d.ts.map +1 -0
  311. package/dist/sde/domain/market/schemas.d.ts +24 -0
  312. package/dist/sde/domain/market/schemas.d.ts.map +1 -0
  313. package/dist/sde/domain/market/types.d.mts +25 -0
  314. package/dist/sde/domain/market/types.d.ts +25 -0
  315. package/dist/sde/domain/market/types.d.ts.map +1 -0
  316. package/dist/sde/domain/schemas.d.ts +15 -0
  317. package/dist/sde/domain/schemas.d.ts.map +1 -0
  318. package/dist/sde/domain/skins/schemas.d.ts +109 -0
  319. package/dist/sde/domain/skins/schemas.d.ts.map +1 -0
  320. package/dist/sde/domain/skins/types.d.mts +121 -0
  321. package/dist/sde/domain/skins/types.d.ts +121 -0
  322. package/dist/sde/domain/skins/types.d.ts.map +1 -0
  323. package/dist/sde/domain/types/schemas.d.ts +163 -0
  324. package/dist/sde/domain/types/schemas.d.ts.map +1 -0
  325. package/dist/sde/domain/types/types.d.mts +182 -0
  326. package/dist/sde/domain/types/types.d.ts +182 -0
  327. package/dist/sde/domain/types/types.d.ts.map +1 -0
  328. package/dist/sde/domain/types.d.mts +16 -0
  329. package/dist/sde/domain/types.d.ts +16 -0
  330. package/dist/sde/domain/types.d.ts.map +1 -0
  331. package/dist/sde/domain/ui/schemas.d.ts +42 -0
  332. package/dist/sde/domain/ui/schemas.d.ts.map +1 -0
  333. package/dist/sde/domain/ui/types.d.mts +45 -0
  334. package/dist/sde/domain/ui/types.d.ts +45 -0
  335. package/dist/sde/domain/ui/types.d.ts.map +1 -0
  336. package/dist/sde/domain/universe/schemas.d.ts +239 -0
  337. package/dist/sde/domain/universe/schemas.d.ts.map +1 -0
  338. package/dist/sde/domain/universe/types.d.mts +206 -0
  339. package/dist/sde/domain/universe/types.d.ts +206 -0
  340. package/dist/sde/domain/universe/types.d.ts.map +1 -0
  341. package/dist/sde/domain/version/schemas.d.ts +11 -0
  342. package/dist/sde/domain/version/schemas.d.ts.map +1 -0
  343. package/dist/sde/errors.d.mts +1 -1
  344. package/dist/sde/errors.d.ts +1 -1
  345. package/dist/sde/errors.d.ts.map +1 -1
  346. package/dist/sde/index.d.mts +7 -6
  347. package/dist/sde/index.d.ts +7 -6
  348. package/dist/sde/index.d.ts.map +1 -1
  349. package/dist/sde/index.js +31 -21
  350. package/dist/sde/index.js.map +1 -1
  351. package/dist/sde/index.mjs +26 -16
  352. package/dist/sde/index.mjs.map +1 -1
  353. package/dist/sde/ingestion/SdeDatabaseBuilder.d.ts +11 -0
  354. package/dist/sde/ingestion/SdeDatabaseBuilder.d.ts.map +1 -1
  355. package/dist/sde/ingestion/SdeDownloader.d.ts +2 -2
  356. package/dist/sde/ingestion/SdeDownloader.d.ts.map +1 -1
  357. package/dist/sde/memory.d.mts +5 -5
  358. package/dist/sde/memory.d.ts +5 -5
  359. package/dist/sde/memory.d.ts.map +1 -1
  360. package/dist/sde/memory.js +2 -2
  361. package/dist/sde/memory.mjs +1 -1
  362. package/dist/sde/{IStaticDataProvider.d.mts → ports/IStaticDataProvider.d.mts} +9 -2
  363. package/dist/sde/{IStaticDataProvider.d.ts → ports/IStaticDataProvider.d.ts} +9 -2
  364. package/dist/sde/ports/IStaticDataProvider.d.ts.map +1 -0
  365. package/dist/sde/{MemorySdeProvider.d.mts → providers/memory/MemorySdeProvider.d.mts} +3 -3
  366. package/dist/sde/{MemorySdeProvider.d.ts → providers/memory/MemorySdeProvider.d.ts} +3 -3
  367. package/dist/sde/providers/memory/MemorySdeProvider.d.ts.map +1 -0
  368. package/dist/sde/providers/order.d.ts +10 -0
  369. package/dist/sde/providers/order.d.ts.map +1 -0
  370. package/dist/sde/{SdeDataProvider.d.mts → providers/yaml/SdeDataProvider.d.mts} +14 -5
  371. package/dist/sde/{SdeDataProvider.d.ts → providers/yaml/SdeDataProvider.d.ts} +14 -5
  372. package/dist/sde/providers/yaml/SdeDataProvider.d.ts.map +1 -0
  373. package/dist/sde/{SdeTestDataFactory.d.mts → testing/SdeTestDataFactory.d.mts} +3 -3
  374. package/dist/sde/{SdeTestDataFactory.d.ts → testing/SdeTestDataFactory.d.ts} +3 -3
  375. package/dist/sde/testing/SdeTestDataFactory.d.ts.map +1 -0
  376. package/dist/sde/version.d.mts +1 -1
  377. package/dist/sde/version.d.ts +1 -1
  378. package/dist/sde/version.d.ts.map +1 -1
  379. package/dist/testing/TestDataFactory.d.mts +21 -3
  380. package/dist/testing/TestDataFactory.d.ts +21 -3
  381. package/dist/testing/TestDataFactory.d.ts.map +1 -1
  382. package/dist/testing/index.d.mts +1 -0
  383. package/dist/testing/index.d.ts +1 -0
  384. package/dist/testing/index.d.ts.map +1 -1
  385. package/dist/testing/index.js +157 -10
  386. package/dist/testing/index.js.map +1 -1
  387. package/dist/testing/index.mjs +155 -8
  388. package/dist/testing/index.mjs.map +1 -1
  389. package/dist/testing/mockTransport.d.mts +87 -0
  390. package/dist/testing/mockTransport.d.ts +87 -0
  391. package/dist/testing/mockTransport.d.ts.map +1 -0
  392. package/dist/types/generated/esi-spec.generated.d.mts +230 -10
  393. package/dist/types/generated/esi-spec.generated.d.ts +230 -10
  394. package/dist/types/generated/esi-spec.generated.d.ts.map +1 -1
  395. package/dist/types/military-campaigns.d.mts +4 -1
  396. package/dist/types/military-campaigns.d.ts +4 -1
  397. package/dist/types/military-campaigns.d.ts.map +1 -1
  398. package/dist/types/skyhooks.d.mts +2 -1
  399. package/dist/types/skyhooks.d.ts +2 -1
  400. package/dist/types/skyhooks.d.ts.map +1 -1
  401. package/package.json +167 -131
  402. package/dist/chunk-3ZI5A37L.mjs.map +0 -1
  403. package/dist/chunk-BBSHKVPI.js.map +0 -1
  404. package/dist/chunk-D5L5HZVF.mjs +0 -417
  405. package/dist/chunk-D5L5HZVF.mjs.map +0 -1
  406. package/dist/chunk-MSJHIJXA.js.map +0 -1
  407. package/dist/chunk-R7D7M2LQ.mjs.map +0 -1
  408. package/dist/chunk-SAQBXXTM.js.map +0 -1
  409. package/dist/chunk-VZS32UQ3.mjs.map +0 -1
  410. package/dist/chunk-XIB2OXEZ.js +0 -417
  411. package/dist/chunk-XIB2OXEZ.js.map +0 -1
  412. package/dist/config/jest/globalSetup.d.ts +0 -2
  413. package/dist/config/jest/globalSetup.d.ts.map +0 -1
  414. package/dist/config/jest/globalTeardown.d.ts +0 -2
  415. package/dist/config/jest/globalTeardown.d.ts.map +0 -1
  416. package/dist/config/jest/jest.setup.d.ts +0 -3
  417. package/dist/config/jest/jest.setup.d.ts.map +0 -1
  418. package/dist/core/ClientRegistry.d.mts +0 -45
  419. package/dist/core/ClientRegistry.d.ts +0 -45
  420. package/dist/core/ClientRegistry.d.ts.map +0 -1
  421. package/dist/core/logger/logger.d.ts +0 -12
  422. package/dist/core/logger/logger.d.ts.map +0 -1
  423. package/dist/sde/IStaticDataProvider.d.ts.map +0 -1
  424. package/dist/sde/MemorySdeProvider.d.ts.map +0 -1
  425. package/dist/sde/SdeDataProvider.d.ts.map +0 -1
  426. package/dist/sde/SdeTestDataFactory.d.ts.map +0 -1
  427. package/dist/sde/schemas.d.ts +0 -1121
  428. package/dist/sde/schemas.d.ts.map +0 -1
  429. package/dist/sde/types.d.mts +0 -1039
  430. package/dist/sde/types.d.ts +0 -1039
  431. package/dist/sde/types.d.ts.map +0 -1
package/README.md CHANGED
@@ -4,979 +4,226 @@
4
4
  [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
5
5
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.4%2B-blue)](https://www.typescriptlang.org/)
6
6
  [![CI/CD Pipeline](https://github.com/lgriffin/ESI.ts/actions/workflows/ci.yml/badge.svg)](https://github.com/lgriffin/ESI.ts/actions/workflows/ci.yml)
7
- [![Coverage](https://img.shields.io/badge/coverage-95%25%2B-brightgreen)](https://github.com/lgriffin/ESI.ts)
7
+ [![Coverage](https://img.shields.io/badge/coverage-97.8%25-brightgreen)](guides/TESTING.md#where-the-suite-stands)
8
+ [![Mutation score](https://img.shields.io/badge/mutation%20score-84%25%20core-brightgreen)](guides/TESTING.md#where-the-scores-stand)
9
+ [![Tests](https://img.shields.io/badge/tests-10%2C045%20offline-brightgreen)](guides/TESTING.md#where-the-suite-stands)
10
+ [![EARS requirements](https://img.shields.io/badge/EARS%20requirements-566-blue)](tests/bdd/README.md)
8
11
  [![npm downloads](https://img.shields.io/npm/dm/%40lgriffin/esi.ts)](https://www.npmjs.com/package/@lgriffin/esi.ts)
9
12
  [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/lgriffin/ESI.ts/badge)](https://scorecard.dev/viewer/?uri=github.com/lgriffin/ESI.ts)
10
13
 
11
- A production-grade TypeScript client for the [EVE Online ESI API](https://esi.evetech.net/), built on the **OpenAPI 3.1 spec**, with runtime validation, intelligent caching, and full endpoint coverage.
14
+ A TypeScript client for the [EVE Online ESI API](https://esi.evetech.net/), built as an engineered product rather than a generated wrapper. It covers every operation in the ESI OpenAPI specification. Every response is validated at runtime, and caching, rate limiting, retry and pagination follow the rules ESI actually enforces. The claims on this page are backed by a test suite that is itself tested.
12
15
 
13
- **[Documentation Site](https://lgriffin.github.io/ESI.ts/)** — guides, API reference, interactive endpoint explorer, and runnable examples.
16
+ **Documentation site: [lgriffin.github.io/ESI.ts](https://lgriffin.github.io/ESI.ts/)**, with these guides, a page for every runnable example, and the [API reference](https://lgriffin.github.io/ESI.ts/api/) generated from the TSDoc.
14
17
 
15
- **v9.5.2** — Supply chain security hardening: all GitHub Actions pinned by SHA, npm publish with SLSA provenance attestations, least-privilege workflow permissions, script injection prevention, and ETag cache cross-tenant isolation.
18
+ > **Release line.** Version <!-- metric:version -->11.0.0<!-- /metric --> <!-- x-release-please-version -->
19
+ > is current on npm and supports Node 18 and later. **11.0.0 is in progress.** It raises the floor to Node 22 and adds a new client built on one shared runtime, with typed public and per-character views. Nothing documented here is removed in 11.0. [What 11.0 changes](guides/USAGE.md#9-what-1100-changes) · [Roadmap and release gate](guides/ROADMAP.md)
16
20
 
17
- **v9.5.0** — Adds 12 new ESI endpoints: CosmeticsClient (SKINR licenses, components, design lookup), ParagonHubClient (marketplace listings with cursor pagination), plus detail endpoints for Mercenary Dens, Tactical Operations, Skyhooks, and Sovereignty Hubs.
18
-
19
- **235 endpoint definitions — 206 from the public ESI OpenAPI spec, plus 29 for newer EVE features (Equinox sovereignty, orbital skyhooks, mercenary dens, access lists, freelance jobs, military campaigns, corporation projects, SKINR cosmetics, Paragon Hub marketplace). All exercisable endpoints validated against live Tranquility.**
20
-
21
- ## Why ESI.ts vs. OpenAPI-Generated Clients?
22
-
23
- Tools like `openapi-typescript` or `openapi-generator` can produce a typed client from the ESI OpenAPI spec in minutes. They're a reasonable starting point — but they stop at type generation. ESI.ts is a purpose-built SDK that handles the problems you hit _after_ the types compile.
24
-
25
- ### What generators give you
26
-
27
- - TypeScript interfaces from the OpenAPI spec
28
- - Basic request/response typing
29
- - A thin HTTP wrapper
30
-
31
- ### What ESI.ts gives you on top of that
32
-
33
- | Capability | openapi-typescript | ESI.ts |
34
- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
35
- | **Runtime response validation** | None — types are erased at compile time. If CCP changes a field, you get silent data corruption. | Every GET response is validated at runtime via [Zod](https://zod.dev/) schemas — all 200 GET endpoints have schemas. Schema mismatches throw `EsiValidationError` immediately. |
36
- | **Intelligent caching** | None — you build your own. | Three-tier: spec-aware TTL (zero HTTP calls within ESI's `x-cached-seconds` window), ETag conditional GETs, stale-on-error fallback on 5xx. Write operations auto-invalidate related GET caches. |
37
- | **Rate limiting** | None — you build your own. | 36 per-group token buckets extracted from the ESI spec at build time. Market requests can't starve wallet requests. Optional per-user bucketing for multi-character apps. |
38
- | **Pagination** | Manual — you write the page loop. | Automatic offset pagination, cursor-based pagination (Equinox-era endpoints), and streaming `AsyncGenerator` pagination for memory-efficient processing of large datasets. |
39
- | **Retry & resilience** | None. | Exponential backoff with jitter, circuit breaker (closed/open/half-open), automatic 401 token refresh with concurrent coalescing. |
40
- | **Wire format correctness** | Generates from spec, but ESI's spec has inconsistencies (query params documented as body, missing required fields). | Every endpoint tested against live ESI. Wire format bugs (query params vs. body, field naming) are caught and fixed — see the contacts and UI endpoint fixes in v6.1.0. |
41
- | **Batch operations** | None. | `batch()` with bounded concurrency for GET fan-out, `batchPost()` with auto-chunking for large POST payloads. |
42
- | **Domain knowledge** | None — generic HTTP client. | 39 domain clients with typed methods, JSDoc documentation, and input validation (e.g., fleet wing/squad names are capped at 10 characters before hitting the API). |
43
- | **Streaming pagination** | None. | 21 domain clients with 73+ `stream*` methods via `AsyncGenerator` — process large datasets page-by-page without loading everything into memory. |
44
- | **Testing** | Whatever you write. | 171 test suites, 4,957 tests across 9 tiers including property-based fuzzing (fast-check), mutation testing (Stryker), deep contract tests against live OpenAPI spec, and consumer type tests (tsd). 52 runnable example scripts. |
45
-
46
- ### The real problem with generated clients
47
-
48
- The ESI OpenAPI spec is not a perfect source of truth. During live endpoint validation against the OpenAPI 3.1 spec, we discovered:
49
-
50
- - `addContacts`, `editContacts`, and 4 UI endpoints document parameters as request body when ESI actually expects query parameters
51
- - `deleteCharacterContacts` expects comma-separated contact IDs as a query param, not a JSON body
52
- - Fleet wing/squad names have a 10-character limit not documented in the spec
53
- - The `updateMailMetadata` endpoint uses the field name `read`, not `is_read`
54
-
55
- A generated client faithfully reproduces these spec bugs. ESI.ts fixes them.
56
-
57
- ## Installation
21
+ ## Install
58
22
 
59
23
  ```bash
60
24
  npm install @lgriffin/esi.ts
61
25
  ```
62
26
 
63
- Requires Node.js 18 or later. TypeScript projects need TypeScript 5.4 or later, with ES module or CommonJS code under `node16`, `nodenext` or `bundler` module resolution; the consumer contract checks each of those against the published tarball.
64
-
65
- ### Building from Source
66
-
67
- ```bash
68
- git clone https://github.com/lgriffin/ESI.ts.git
69
- cd ESI.ts
70
- npm install # installs dependencies and compiles (via the prepare script)
71
- ```
72
-
73
- If you've already installed and just need to recompile:
74
-
75
- ```bash
76
- npm run build
77
- ```
78
-
79
- Verify everything works:
80
-
81
- ```bash
82
- npm run example:status # quick smoke test — checks ESI is reachable
83
- npm test # run the full test suite (171 suites, 4,957 tests)
84
- ```
85
-
86
- ## Sub-path Exports
87
-
88
- ESI.ts provides sub-path exports for targeted imports, reducing bundle size when you only need specific parts of the library:
89
-
90
- ```typescript
91
- // Zod schemas for runtime validation
92
- import { MarketOrderSchema } from '@lgriffin/esi.ts/schemas';
93
-
94
- // Error classes and type guards
95
- import { EsiError, isRetryable } from '@lgriffin/esi.ts/errors';
96
-
97
- // Test utilities
98
- import { TestDataFactory } from '@lgriffin/esi.ts/testing';
99
- ```
100
-
101
- ## Static Data Export (SDE) Module
102
-
103
- ESI.ts includes a standalone module for querying CCP's EVE Online Static Data Export — 102 YAML files loaded into in-memory Maps with 109 typed interfaces, Zod validation, and ~97 query methods. No database, no external services.
104
-
105
- Reading SDE files needs two optional peer dependencies, which `npm install @lgriffin/esi.ts` does not install:
106
-
107
- ```bash
108
- npm install js-yaml # SdeDataProvider.fromDirectory and fromZip (parses the YAML)
109
- npm install adm-zip # SdeDataProvider.fromZip (reads the ZIP archive)
110
- ```
111
-
112
- `@lgriffin/esi.ts/sde` loads without them, and `MemorySdeProvider` never needs them. A method that needs one that is missing throws an `SdeError` naming the package and the install command.
113
-
114
- ```typescript
115
- import { SdeDataProvider } from '@lgriffin/esi.ts/sde';
116
-
117
- const sde = SdeDataProvider.fromDirectory('./sde-data');
118
-
119
- const tritanium = sde.getType(34);
120
- console.log(tritanium?.name); // "Tritanium"
121
-
122
- const jita = sde.getSolarSystem(30000142);
123
- const minerals = sde.getTypesByGroup(18);
124
- const caldari = sde.getFaction(500001);
125
-
126
- sde.close();
127
- ```
128
-
129
- Download SDE data with: `npx ts-node scripts/sde-ingest.ts --output sde-data`
130
-
131
- | Document | Description |
132
- | -------------------------------------------------- | --------------------------------------------------------------------------------------------- |
133
- | [SDE README](src/sde/README.md) | Module overview, quick start, full API reference (~97 methods), entity coverage table |
134
- | [Architecture](src/sde/docs/ARCHITECTURE.md) | C4 diagrams (context, container, component), data flow sequence, ER diagram, design decisions |
135
- | [Usage Guide](src/sde/docs/USAGE.md) | Provider patterns, query examples, error handling |
136
- | [Developer Guide](src/sde/docs/DEVELOPER_GUIDE.md) | Project structure, new entity checklist, field normalization, testing patterns |
137
- | [API Contracts](src/sde/docs/API_CONTRACTS.md) | Complete method reference for all IStaticDataProvider methods |
138
-
139
- ## Quick Start
140
-
141
- ```typescript
142
- import { EsiClient } from '@lgriffin/esi.ts';
143
-
144
- const client = new EsiClient();
145
-
146
- // Public data — no auth required
147
- const alliances = await client.alliance.getAlliances();
148
- const character = await client.characters.getCharacterPublicInfo(1689391488);
149
- const system = await client.universe.getSystemById(30000142);
150
- const prices = await client.market.getMarketPrices();
151
-
152
- // Authenticated data — token read from ESI_ACCESS_TOKEN env var
153
- const authedClient = new EsiClient();
154
- const assets = await authedClient.assets.getCharacterAssets(characterId);
155
- const wallet = await authedClient.wallet.getCharacterWallet(characterId);
156
-
157
- // Clean up when done
158
- await client.shutdown();
159
- ```
160
-
161
- ## Guides
162
-
163
- The README orients; the guides are canonical. Each one opens with the [engineering charter](guides/CHARTER.md) requirements it implements.
164
-
165
- | Guide | Covers |
166
- | -------------------------------------------------- | ----------------------------------------------------------------------------------- |
167
- | [Architecture](guides/ARCHITECTURE.md) | Layers, request path, caching, retry, rate limiting, circuit breaker, interceptors |
168
- | [Design rules](guides/DESIGN-RULES.md) | Naming and schema conventions, adding an endpoint, adding a client, generated files |
169
- | [Errors](guides/ERRORS.md) | Error classes, type guards, retryability, token refresh, safe mode |
170
- | [Logging](guides/LOGGING.md) | `ILogger`, per-client loggers, pino, `ESI_LOG_LEVEL`, silencing in tests |
171
- | [Pagination](guides/PAGINATION.md) | Offset and cursor pagination, `stream*`, `fetchAll*`, batch helpers |
172
- | [Runtime validation](guides/RUNTIME-VALIDATION.md) | Zod response and request validation |
173
- | [Security](guides/SECURITY.md) | Runtime defences and supply-chain controls ([policy](SECURITY.md)) |
174
- | [Testing](guides/TESTING.md) | Test tiers, coverage, EARS specification |
175
- | [Mutation testing](guides/MUTATION-TESTING.md) | Stryker configuration and scores |
176
- | [Quality gates](guides/QUALITY-GATES.md) | What runs at commit, push, PR, nightly and release; every workflow and script |
177
- | [Release](guides/RELEASE.md) | Cutting a release, changelog, provenance, signatures, supported versions |
178
- | [Semantic versioning](guides/SEMVER.md) | What is public, major/minor/patch decisions, breaking-change commits, merge buttons |
179
- | [OKF bundle](guides/OKF.md) | The generated Open Knowledge Format catalogue of ESI |
180
- | [Documentation](guides/DOCUMENTATION.md) | Documentation surfaces and the TypeDoc reference |
181
- | [Beads](guides/BEADS.md) | Issue tracking workflow |
182
-
183
- ## Configuration
184
-
185
- ```typescript
186
- const client = new EsiClient({
187
- clientId: 'my-app', // User-Agent identifier (default: 'esi-client')
188
- accessToken: 'your-token', // EVE SSO token for authenticated endpoints
189
- baseUrl: 'https://esi.evetech.net', // ESI base URL (default)
190
- onTokenRefresh: async () => newToken, // Auto-refresh on 401 (optional)
191
- language: 'en', // Accept-Language header: en, de, fr, ja, ru, zh, ko, es (default: none)
192
- timeout: 30000, // Request timeout in ms (default: 30000)
193
- retryConfig: {
194
- maxRetries: 3, // Max retry attempts for transient errors (default: 3)
195
- baseDelayMs: 1000, // Initial backoff delay (default: 1000)
196
- maxDelayMs: 30000, // Maximum backoff delay (default: 30000)
197
- retryMutations: false, // Retry POST/PUT/DELETE (default: false, GET only)
198
- },
199
- enableETagCache: true, // ETag caching (default: true)
200
- etagCacheConfig: {
201
- maxEntries: 1000, // Max cached responses (default: 1000)
202
- defaultTtl: 300000, // Fallback TTL in ms (default: 5 min)
203
- cleanupInterval: 60000, // Expired entry cleanup interval (default: 1 min)
204
- },
205
- validateResponse: true, // Runtime Zod validation of ESI responses (default: true)
206
- validateRequest: false, // Opt-in request body Zod validation for POST/PUT/DELETE (default: false)
207
- retryStrategy: customRetryStrategy, // Injectable IRetryStrategy (default: built-in exponential backoff)
208
- enableCircuitBreaker: false, // Opt-in circuit breaker (default: false); circuitBreakerConfig is ignored unless true
209
- circuitBreakerConfig: {
210
- keyStrategy: 'resolved', // CB keying: 'resolved' (per-URL) or 'template' (per-route) (default: 'resolved')
211
- cleanupIntervalMs: 3600000, // Stale circuit cleanup interval (default: disabled)
212
- },
213
- });
214
- ```
215
-
216
- Retry is enabled by default (`maxRetries: 3`). Transient errors (502, 503, 504, timeout, rate limit) are retried with exponential backoff and jitter. The circuit breaker is respected — requests are not retried when the circuit is open. Set `maxRetries: 0` to disable retry.
217
-
218
- The access token can be updated at runtime:
219
-
220
- ```typescript
221
- client.setAccessToken('new-token');
222
- ```
223
-
224
- ## Authentication
225
-
226
- Many ESI endpoints require an EVE SSO access token. There are three ways to provide one:
27
+ You need Node.js 22.12 or later from 11.0.0; 10.x, current on npm, supports Node 18 and 20. TypeScript projects need TypeScript 5.4 or later, under `node16`, `nodenext` or `bundler` module resolution. The consumer contract checks every one of those combinations against the published tarball, as ES module and CommonJS.
227
28
 
228
- ### 1. Environment variable (recommended)
29
+ ## Quick start
229
30
 
230
- Set `ESI_ACCESS_TOKEN` in your environment or a `.env` file. The client reads it automatically — no token in source code.
231
-
232
- ```bash
233
- # Copy the example and fill in your token
234
- cp .env.example .env
235
- ```
236
-
237
- ```env
238
- ESI_ACCESS_TOKEN=your-eve-sso-access-token
239
- ESI_CLIENT_ID=my-app-name
240
- ```
241
-
242
- If you use a `.env` loader like [dotenv](https://www.npmjs.com/package/dotenv), load it before creating the client:
243
-
244
- ```typescript
245
- import 'dotenv/config';
31
+ ```typescript runnable
246
32
  import { EsiClient } from '@lgriffin/esi.ts';
247
33
 
248
- const client = new EsiClient();
249
- // Token is picked up from process.env.ESI_ACCESS_TOKEN
250
- ```
251
-
252
- ### 2. Constructor parameter
253
-
254
- Pass the token directly (useful for apps that manage tokens themselves):
255
-
256
- ```typescript
257
- const client = new EsiClient({ accessToken: token });
34
+ const client = new EsiClient({ userAgent: 'my-app/1.0 (you@example.com)' });
35
+ try {
36
+ const status = await client.status.getStatus();
37
+ console.log(`${status.players} pilots online`);
38
+ } finally {
39
+ client.shutdown();
40
+ }
258
41
  ```
259
42
 
260
- ### 3. Runtime update
261
-
262
- Set or refresh the token after construction:
43
+ Public data needs no token. For character data, pass an EVE SSO access token, or set `ESI_ACCESS_TOKEN`, and the client attaches it only to the calls that declare a scope:
263
44
 
264
45
  ```typescript
265
- client.setAccessToken(newToken);
266
- ```
267
-
268
- ### Getting an EVE SSO token
269
-
270
- 1. Register an application at [EVE Developers](https://developers.eveonline.com/)
271
- 2. Set a callback URL and select the ESI scopes your app needs
272
- 3. Implement the [OAuth2 flow](https://docs.esi.evetech.net/docs/sso/) to obtain an access token
273
- 4. Access tokens expire — use the refresh token to get new ones
274
-
275
- ### Automatic Token Refresh
276
-
277
- EVE SSO access tokens expire after 20 minutes. Instead of manually tracking expiry, you can provide a refresh callback — the client will automatically call it on 401, update the token, and retry the request:
278
-
279
- ```typescript
280
- const client = new EsiClient({
281
- accessToken: initialToken,
282
- onTokenRefresh: async () => {
283
- const response = await fetch('https://login.eveonline.com/v2/oauth/token', {
284
- method: 'POST',
285
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
286
- body: new URLSearchParams({
287
- grant_type: 'refresh_token',
288
- refresh_token: myRefreshToken,
289
- client_id: myClientId,
290
- }),
291
- });
292
- const { access_token } = await response.json();
293
- return access_token;
294
- },
295
- });
296
-
297
- // Requests now auto-refresh on 401 — no manual token management needed
298
- const location = await client.location.getCharacterLocation(characterId);
299
- ```
300
-
301
- The token provider can also be set or changed at runtime:
46
+ const character = await client.characters.getCharacterPublicInfo(characterId);
47
+ const prices = await client.market.getMarketPrices();
302
48
 
303
- ```typescript
304
- client.setTokenProvider(myRefreshFunction);
305
- client.setTokenProvider(undefined); // disable auto-refresh
49
+ const authed = new EsiClient({ accessToken: token });
50
+ const wallet = await authed.wallet.getCharacterWallet(characterId);
306
51
  ```
307
52
 
308
- Key behaviors:
53
+ Next: [Using the client](guides/USAGE.md) for configuration and every domain client, and [Authentication](guides/AUTHENTICATION.md) for SSO, refresh and many characters.
309
54
 
310
- - Only retries **once** per request — if the refreshed token also gets a 401, the error is thrown
311
- - **Concurrent coalescing** — if multiple requests hit 401 simultaneously, only one refresh call is made
312
- - If the refresh callback throws (e.g., refresh token revoked), a `TOKEN_REFRESH_FAILED` error is raised
313
- - Without a token provider, 401 errors throw immediately as before
55
+ ### Multiple characters
314
56
 
315
- ### Token Manager (multi-character, persistent)
316
-
317
- The refresh callback above is the low-level hook. For applications that hold tokens for one or many characters, `EsiTokenManager` does the whole lifecycle: the SSO code exchange, persistence through a pluggable storage adapter, proactive refresh ahead of expiry, coalescing of concurrent refreshes, persistence of the rotated refresh token, revocation tracking, and bulk refresh with a concurrency cap.
57
+ An application that acts for many characters has one relationship with ESI, so `@lgriffin/esi.ts/client` builds one runtime and a view per identity. The views share the rate limiter, the error budget and the ETag cache; each identity's authenticated entries stay apart. `esi.public` is typed so that an authenticated call does not compile.
318
58
 
319
59
  ```typescript
320
- import {
321
- EsiTokenManager,
322
- FileTokenStorage,
323
- generateState,
324
- } from '@lgriffin/esi.ts';
325
-
326
- const tokens = new EsiTokenManager({
327
- clientId: process.env.ESI_SSO_CLIENT_ID!,
328
- clientSecret: process.env.ESI_SSO_CLIENT_SECRET, // omit for a public (PKCE) client
329
- callbackUrl: 'https://my-app.example/callback',
330
- storage: new FileTokenStorage('./tokens.json'), // or MemoryTokenStorage, or your own
331
- });
332
-
333
- // 1. Send the player to SSO
334
- const state = generateState();
335
- const loginUrl = tokens.getAuthorizationUrl({
336
- scopes: ['esi-wallet.read_character_wallet.v1'],
337
- state,
338
- });
60
+ import { createEsi } from '@lgriffin/esi.ts/client';
339
61
 
340
- // 2. On the callback, exchange the code. The character id, name and scopes
341
- // are decoded from the token; you never have to say who just logged in.
342
- const stored = await tokens.addCharacter(codeFromCallback);
343
- console.log(`Added ${stored.characterName} (${stored.characterId})`);
344
-
345
- // 3. Get a client bound to that character. Its token is refreshed before
346
- // expiry, and again on a 401, through the manager.
347
- const client = await tokens.createClient(stored.characterId);
348
- const wallet = await client.wallet.getCharacterWallet(stored.characterId);
349
-
350
- // Or just the access token, for use elsewhere
351
- const accessToken = await tokens.getToken(stored.characterId);
62
+ const esi = createEsi({ userAgent: 'my-app/1.0 (you@example.com)' });
63
+ const status = await esi.public.status.get();
64
+ const wallet = await esi
65
+ .as(tokens.identity(characterId))
66
+ .character(characterId)
67
+ .wallet.get();
352
68
  ```
353
69
 
354
- Public clients (desktop and CLI tools that cannot keep a secret) use PKCE:
355
-
356
- ```typescript
357
- import { generatePkcePair } from '@lgriffin/esi.ts';
70
+ [Many characters](guides/MULTI-CHARACTER.md) has the identities (`EsiTokenManager`, a raw token, a `TokenProvider`), what the views share, and the move from `tokens.createClient`.
358
71
 
359
- const pkce = generatePkcePair();
360
- const loginUrl = tokens.getAuthorizationUrl({
361
- scopes,
362
- state,
363
- codeChallenge: pkce.codeChallenge,
364
- });
365
- // ...later, on the callback:
366
- await tokens.addCharacter(code, { codeVerifier: pkce.codeVerifier });
367
- ```
72
+ ### Testing your application without ESI
368
73
 
369
- #### Bulk refresh
74
+ `createMockTransport()` from `@lgriffin/esi.ts/testing` answers requests from a table of routes and records what your code sent. Everything between your call and the transport is the real pipeline, so this runs as written:
370
75
 
371
- Applications holding many characters (corporation tools, alliance services) refresh in bulk. Per-character failures never reject the call; each character gets its own result. The one exception is a storage adapter that cannot list tokens, which rejects with the storage error.
76
+ ```typescript runnable
77
+ import { createEsi, identityFromToken } from '@lgriffin/esi.ts/client';
78
+ import { createMockTransport } from '@lgriffin/esi.ts/testing';
372
79
 
373
- ```typescript
374
- const results = await tokens.refreshAll({
375
- concurrency: 5, // simultaneous SSO requests (default 5)
376
- expiringWithinMs: 5 * 60_000, // only tokens expiring in the next 5 minutes; omit for all
80
+ const transport = createMockTransport().respond({
81
+ method: 'GET',
82
+ path: '/characters/{character_id}/wallet',
83
+ body: 1234567.89,
377
84
  });
378
-
379
- for (const r of results) {
380
- switch (r.status) {
381
- case 'refreshed':
382
- break;
383
- case 'skipped':
384
- break; // not stale, or the run was aborted
385
- case 'revoked':
386
- console.log(`${r.characterId} must log in again`);
387
- break;
388
- case 'failed':
389
- if (r.retryable) scheduleRetry(r.characterId);
390
- break;
391
- }
392
- }
393
- ```
394
-
395
- #### Storage adapters
396
-
397
- `ITokenStorage` is four async methods keyed by character id: `get`, `set`, `delete`, `list`. Two adapters ship with the library:
398
-
399
- | Adapter | Use for |
400
- | -------------------- | ---------------------------------------------------------------------- |
401
- | `MemoryTokenStorage` | Tests, CLIs that log in every run, a cache in front of a durable store |
402
- | `FileTokenStorage` | Single-process apps; atomic temp-file-and-rename writes, `0600` mode |
403
-
404
- Implement the interface over Redis, Postgres, or a keychain for anything else. One rule matters: `set` must be durable before it resolves, because the manager persists the rotated refresh token before returning the new access token, and SSO invalidates the previous one.
405
-
406
- Key behaviors:
407
-
408
- - **One token per character** — re-authorizing replaces the stored token rather than accumulating a second one; a warning is logged if the new consent drops scopes
409
- - **Proactive refresh** — `getToken` refreshes when the token is inside `refreshSkewMs` of expiry (default 60 s), so requests are never sent with a token about to fail
410
- - **Coalescing** — concurrent refreshes for the same character share one SSO call, which matters because SSO rotates the refresh token on every use
411
- - **Revocation tracking** — an `invalid_grant` from SSO marks the character revoked; later calls throw `TokenRevokedError` locally instead of hitting SSO again
412
- - **Hooks** — `onRefresh`, `onRefreshError`, and `onRevoked` for logging, metrics, or prompting a re-login
413
- - **No JWT signature verification** — tokens are trusted because they arrive directly from SSO over TLS; do not use `decodeAccessToken` to authenticate tokens presented by third parties
414
- - **Single process per store** — two processes sharing one `FileTokenStorage` would each rotate refresh tokens the other cannot see
415
-
416
- ### Environment variables reference
417
-
418
- | Variable | Description | Default |
419
- | ------------------ | -------------------------------------------- | ------------------------- |
420
- | `ESI_ACCESS_TOKEN` | EVE SSO access token | none |
421
- | `ESI_CLIENT_ID` | User-Agent identifier | `esi-client` |
422
- | `ESI_BASE_URL` | ESI API base URL | `https://esi.evetech.net` |
423
- | `ESI_LOG_LEVEL` | Log level (`error`, `warn`, `info`, `debug`) | `warn` |
424
-
425
- ## Available APIs
426
-
427
- All clients are accessed as properties on the `EsiClient` instance. Authenticated endpoints require an access token.
428
-
429
- | Client | Property | Auth | Examples |
430
- | ------------------ | ---------------------------- | ---- | ------------------------------------------------------------------------------------- |
431
- | Alliance | `client.alliance` | Some | `getAlliances()`, `getAllianceById(id)` |
432
- | Assets | `client.assets` | Yes | `getCharacterAssets(id)` |
433
- | Calendar | `client.calendar` | Yes | `getCalendarEvents(id)` |
434
- | Characters | `client.characters` | Some | `getCharacterPublicInfo(id)`, `getCharacterPortrait(id)` |
435
- | Clones | `client.clones` | Yes | `getCharacterClones(id)` |
436
- | Contacts | `client.contacts` | Yes | `getCharacterContacts(id)`, `postCharacterContacts(id, standing, contactIds)` |
437
- | Contracts | `client.contracts` | Yes | `getCharacterContracts(id)` |
438
- | Corp Projects | `client.corporationProjects` | Yes | `getCorporationProjects(corpId)`, `getCorporationProject(corpId, projectId)` |
439
- | Corporations | `client.corporations` | Some | `getCorporationInfo(id)`, `getCorporationMembers(id)` |
440
- | Dogma | `client.dogma` | No | `getDogmaAttributes()`, `getDynamicItemInfo(typeId, itemId)` |
441
- | Factions | `client.factions` | Some | `getFactionWarStats()` |
442
- | Fittings | `client.fittings` | Yes | `getFittings(id)`, `createFitting(id, body)` |
443
- | Fleets | `client.fleets` | Yes | `getFleetInformation(id)`, `getFleetMembers(id)` |
444
- | Incursions | `client.incursions` | No | `getIncursions()` |
445
- | Industry | `client.industry` | Some | `getCharacterIndustryJobs(id)` |
446
- | Insurance | `client.insurance` | No | `getInsurancePrices()` |
447
- | Killmails | `client.killmails` | Some | `getKillmail(id, hash)` |
448
- | Location | `client.location` | Yes | `getCharacterLocation(id)` |
449
- | Loyalty | `client.loyalty` | Yes | `getCharacterLoyaltyPoints(id)` |
450
- | Mail | `client.mail` | Yes | `getCharacterMail(id)`, `sendMail(id, body)` |
451
- | Market | `client.market` | Some | `getMarketPrices()`, `getMarketOrders(regionId)` |
452
- | Military Campaigns | `client.militaryCampaigns` | Some | `getMilitaryCampaigns()`, `getMilitaryCampaignById(id)` |
453
- | PI | `client.pi` | Yes | `getCharacterPlanets(id)` |
454
- | Route | `client.route` | No | `getRoute(origin, destination)` |
455
- | Search | `client.search` | Some | `search(characterId, query)` |
456
- | Skills | `client.skills` | Yes | `getCharacterSkills(id)` |
457
- | Sovereignty | `client.sovereignty` | No | `getSovereigntySystems()`, `getSovereigntyMap()` |
458
- | Skyhooks | `client.skyhooks` | Some | `getSovereigntyHubs(corpId)`, `getSkyhookDetail(corpId, id)`, `getRaidableSkyhooks()` |
459
- | Mercenary | `client.mercenary` | Yes | `getMercenaryDens(charId)`, `getMercenaryDenDetail(charId, denId)` |
460
- | Cosmetics | `client.cosmetics` | Some | `getSkinr(id)`, `getCharacterSkinr(charId)`, `getCharacterSkinrComponents(charId)` |
461
- | Paragon Hub | `client.paragonHub` | Some | `getPublicListings()`, `getCharacterListings(charId)`, `getAllianceListings(id)` |
462
- | Access Lists | `client.accessLists` | Yes | `getAccessList(id)` |
463
- | Status | `client.status` | No | `getStatus()` |
464
- | UI | `client.ui` | Yes | `setAutopilotWaypoint(destId, addToBeginning, clear)`, `openNewMailWindow(body)` |
465
- | Universe | `client.universe` | Some | `getSystemById(id)`, `getTypeById(id)` |
466
- | Wallet | `client.wallet` | Yes | `getCharacterWallet(id)` |
467
- | Wars | `client.wars` | No | `getWars()`, `getWarById(id)` |
468
- | Freelance Jobs | `client.freelanceJobs` | Some | `getFreelanceJobs()`, `getFreelanceJobById(id)` |
469
- | Meta | `client.meta` | No | `getOpenApiJson()`, `getOpenApiYaml()` |
470
-
471
- ## Runtime Response Validation
472
-
473
- ESI.ts validates API responses at runtime using [Zod](https://zod.dev/) schemas. All GET endpoints have schemas — these are the endpoints that return data your application consumes, where a silent shape change from CCP would cause bugs. POST/PUT/DELETE mutations typically return `204 No Content` (no body to validate) or simple confirmation values, so schemas are omitted where there is nothing meaningful to validate.
474
-
475
- Validation is **on by default**. Extra fields from ESI are preserved via `z.looseObject()` passthrough mode, so new fields added by CCP won't break your application — they flow through to your code untouched.
476
-
477
- ```typescript
478
- import {
479
- EsiClient,
480
- EsiValidationError,
481
- isValidationError,
482
- schemas,
483
- } from '@lgriffin/esi.ts';
484
-
485
- const client = new EsiClient();
486
-
487
- // Validation happens automatically on every request
488
- const character = await client.characters.getCharacterPublicInfo(12345);
489
-
490
- // Disable validation globally if needed
491
- const rawClient = new EsiClient({ validateResponse: false });
492
-
493
- // Use schemas directly for your own validation
494
- const result = schemas.CharacterInfoSchema.safeParse(someData);
495
- if (result.success) {
496
- console.log(result.data.name);
497
- }
498
- ```
499
-
500
- ### Request Body Validation
501
-
502
- For POST/PUT/DELETE endpoints, opt-in request body validation ensures outgoing payloads match the endpoint's `requestSchema` before the request is sent:
503
-
504
- ```typescript
505
- // Opt-in request body validation for POST/PUT/DELETE
506
- const client = new EsiClient({ validateRequest: true });
507
-
508
- // Throws EsiValidationError if the request body doesn't match the endpoint's requestSchema
509
- await client.mail.sendMail(characterId, {
510
- recipients: [{ recipient_id: 12345, recipient_type: 'character' }],
511
- subject: 'Hello',
512
- body: 'Message body',
85
+ const esi = createEsi({
86
+ userAgent: 'my-app/1.0 (you@example.com)',
87
+ transport,
513
88
  });
514
- ```
515
-
516
- See [guides/RUNTIME-VALIDATION.md](guides/RUNTIME-VALIDATION.md) for the full guide on schemas, error handling, and extending schemas.
517
-
518
- ## Caching
519
-
520
- ETag caching is on by default and works in three tiers: a GET inside the spec-defined TTL is answered from cache with no HTTP call, an older entry is revalidated with `If-None-Match`, and a 5xx with a cached copy serves the stale body instead of throwing. Authenticated cache entries are isolated per token.
521
-
522
- ```typescript
523
- const client = new EsiClient({ etagCacheConfig: { maxEntries: 2000 } });
524
- client.getCacheStats();
525
- client.clearCache();
526
- ```
527
-
528
- See [Caching in the architecture guide](guides/ARCHITECTURE.md#4-caching) for TTL precedence, invalidation, keys and configuration.
529
-
530
- ## Batch Requests
531
-
532
- Fetch data for multiple IDs with bounded concurrency using `batch()`, or chunk large POST payloads with `batchPost()`:
533
-
534
- ```typescript
535
- import { EsiClient } from '@lgriffin/esi.ts';
536
-
537
- const client = new EsiClient();
538
-
539
- // Fetch 500 type details with at most 10 concurrent requests (default 20)
540
- const result = await client.batch(
541
- typeIds,
542
- (id) => client.universe.getTypeById(id),
543
- {
544
- concurrency: 10,
545
- onProgress: (done, total) => console.log(`${done}/${total}`),
546
- },
547
- );
548
-
549
- // result.results: Map<number, T> — successful responses
550
- // result.errors: Map<number, Error> — failed requests
551
- console.log(`${result.results.size} succeeded, ${result.errors.size} failed`);
552
- ```
553
-
554
- For POST endpoints that accept arrays (e.g., `postNamesAndCategories` with a 1000-ID limit), `batchPost` auto-chunks and concatenates:
555
-
556
- ```typescript
557
- const allNames = await client.batchPost(
558
- largeIdArray,
559
- (chunk) => client.universe.postNamesAndCategories(chunk),
560
- 1000, // chunk size
561
- );
562
- ```
563
-
564
- ## Streaming Pagination
565
-
566
- Paginated endpoints can be consumed three ways: the plain method fetches every page and returns one array, `stream*` methods yield one validated page at a time, and `fetchAll*` methods fetch the remaining pages concurrently.
567
-
568
- ```typescript
569
- for await (const page of client.market.streamMarketOrders(10000002)) {
570
- console.log(
571
- `Page ${page.page}/${page.totalPages}: ${page.data.length} orders`,
572
- );
573
- if (page.page >= 3) break; // stops fetching the remaining pages
574
- }
575
- ```
576
-
577
- Try it: `npm run example:streaming`. See [guides/PAGINATION.md](guides/PAGINATION.md) for the full method list, concurrency defaults and failure behaviour.
578
-
579
- ## Cursor-based Pagination
580
-
581
- Newer ESI routes such as Freelance Jobs page with opaque `before` / `after` cursor tokens instead of page numbers. `fetchAllCursorPages` follows them to the end of the dataset, and a saved `after` token can be polled later for changed records.
582
-
583
- See [guides/PAGINATION.md](guides/PAGINATION.md) for cursor semantics and examples.
584
-
585
- ## Generated Types
586
-
587
- The library includes TypeScript interfaces generated directly from the ESI OpenAPI 3.1 spec, available as the `EsiSpec` namespace. These are guaranteed to match the live spec and complement the hand-written types:
588
-
589
- ```typescript
590
- import { EsiSpec } from '@lgriffin/esi.ts';
591
-
592
- // Generated type — uses OpenAPI schema names (v7.0.0+)
593
- const order: EsiSpec.MarketsRegionIdOrdersGet = {
594
- order_id: 123,
595
- type_id: 34,
596
- price: 5.5,
597
- volume_remain: 1000,
598
- volume_total: 5000,
599
- is_buy_order: false,
600
- duration: 90,
601
- issued: '2026-09-01T12:00:00Z',
602
- location_id: 60003760,
603
- system_id: 30000142,
604
- min_volume: 1,
605
- range: 'region',
606
- };
607
- ```
608
-
609
- To regenerate types from the latest ESI spec:
610
-
611
- ```bash
612
- npm run generate:types # fetches OpenAPI spec, generates 161 interfaces + cache TTL map + rate limit groups + scope map
613
- npm run validate:esi # reports type drift between hand-written and generated types
614
- ```
615
-
616
- ## ESI Scopes
617
-
618
- The library includes a generated scope-to-endpoint mapping extracted from the ESI OpenAPI spec. Use it to check which OAuth scopes an endpoint requires before making a request:
619
-
620
- ```typescript
621
- import { esiEndpointScopes, EsiScope } from '@lgriffin/esi.ts';
622
-
623
- // Look up scopes for a specific endpoint
624
- const walletScopes = esiEndpointScopes['GET:characters/{character_id}/wallet'];
625
- // → ['esi-wallet.read_character_wallet.v1']
626
-
627
- // Check if an endpoint requires auth
628
- const isPublic = !esiEndpointScopes['GET:universe/types/{type_id}'];
629
- // → true (public endpoint, no scopes needed)
630
-
631
- // Type-safe scope values
632
- const scope: EsiScope = 'esi-assets.read_assets.v1';
633
- ```
634
-
635
- ## Error Handling
636
-
637
- Failed calls throw `EsiError` (with `statusCode`, a sanitised `url` and `retryable`) or one of its subclasses, `TimeoutError` and `EsiValidationError`. An open circuit throws `CircuitOpenError`. Type guards such as `isRetryable`, `isTimeout`, `isValidationError` and `isCircuitOpen` narrow them, and `withSafeMode()` returns a result envelope instead of throwing.
638
-
639
- ```typescript
640
- import { EsiError, isCircuitOpen } from '@lgriffin/esi.ts';
641
-
642
89
  try {
643
- await client.alliance.getAllianceById(99999999);
644
- } catch (err) {
645
- if (isCircuitOpen(err)) console.log(`Retry in ${err.retryAfterMs} ms`);
646
- else if (err instanceof EsiError) console.log(err.statusCode, err.retryable);
90
+ const view = esi.as(identityFromToken('an-access-token'));
91
+ const wallet = await view.character(2114794365).wallet.get();
92
+ console.log(wallet); // 1234567.89
93
+ console.log(transport.sent[0]?.headers['authorization']); // Bearer an-access-token
94
+ } finally {
95
+ esi.shutdown();
647
96
  }
648
97
  ```
649
98
 
650
- See [guides/ERRORS.md](guides/ERRORS.md) for the class hierarchy, retryability rules and safe mode.
651
-
652
- ## Response Metadata
99
+ A request no route answers is rejected with an `EsiConfigurationError` naming the request, without a retry, and appears in `transport.unrouted`. [Testing](guides/TESTING.md#testing-your-application) has the route options and the record.
653
100
 
654
- Use `withMetadata()` to get response headers, cache status, rate limit info, and timing alongside the data:
101
+ ## What you get
655
102
 
656
- ```typescript
657
- const metaClient = client.alliance.withMetadata();
658
- const result = await metaClient.getAllianceById(99000001);
659
-
660
- console.log(result.data.name); // "Goonswarm Federation"
661
- console.log(result.meta.fromCache); // true if served from cache
662
- console.log(result.meta.cacheHitType); // 'spec-ttl' | 'etag-304' | 'stale-on-error'
663
- console.log(result.meta.responseTimeMs); // milliseconds
664
- console.log(result.meta.rateLimit); // { remaining, limit, used, group }
665
- console.log(result.meta.requestId); // ESI request ID for debugging
666
- ```
103
+ | Capability | What ESI.ts does |
104
+ | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
105
+ | **Full coverage** | <!-- metric:clients -->39<!-- /metric --> domain clients and <!-- metric:routes -->235<!-- /metric --> routes. They cover all <!-- metric:operations -->233<!-- /metric --> operations in the ESI specification at compatibility date <!-- metric:compatibilityDate -->2026-08-18<!-- /metric -->, plus the specification documents themselves. `spec:coverage` fails the build if one is missing. |
106
+ | **Runtime validation** | Every GET response is checked against a hand-written Zod schema. Unknown fields pass through, so an additive change from CCP never breaks you. A changed shape throws `EsiValidationError` instead of corrupting your data quietly. |
107
+ | **Caching** | A GET inside ESI's cache window makes no HTTP call. Older entries are revalidated with ETags, and a 5xx serves the stale copy. A write invalidates the reads it affects. Keys are hashed per token. |
108
+ | **Rate limiting** | One bucket per ESI rate-limit group: 46 of them, generated from the spec. The limiter learns from ESI's headers and honours `Retry-After`. A 420 or 429 blocks only its own group. |
109
+ | **Resilience** | Exponential backoff with jitter, a single coalesced token refresh on 401, deduplication of identical in-flight GETs, and an opt-in circuit breaker. Each one is an interface you can replace. |
110
+ | **Pagination** | Offset and cursor paging. `stream*` yields one validated page at a time, and `fetchAll*` fetches pages concurrently. `batch` and `batchPost` handle fan-out. |
111
+ | **Authentication** | EVE SSO with PKCE, and a token manager that handles storage, proactive refresh, rotation, revocation and bulk refresh for many characters. |
112
+ | **Static data** | `./sde` answers offline queries over CCP's Static Data Export: 99 typed lookups with no database. It shares no code with the HTTP pipeline, and a lint rule enforces that. |
113
+ | **Correct wire format** | Where the specification is wrong about how ESI reads a request (parameters in the query rather than the body, undocumented length limits), the definitions follow ESI. The specification documents this and live validation proved it. |
667
114
 
668
- The `meta` object includes:
115
+ ## The engineering stance
669
116
 
670
- | Field | Type | Description |
671
- | ---------------- | ------------------------ | ------------------------------------------------- |
672
- | `headers` | `Record<string, string>` | Raw response headers |
673
- | `fromCache` | `boolean` | Whether data was served from cache |
674
- | `stale` | `boolean` | Whether cached data is stale (5xx fallback) |
675
- | `cacheHitType` | `string?` | `'spec-ttl'`, `'etag-304'`, or `'stale-on-error'` |
676
- | `rateLimit` | `RateLimitMeta?` | Rate limit status from ESI headers |
677
- | `responseTimeMs` | `number?` | Request duration in milliseconds |
678
- | `requestId` | `string?` | ESI request ID |
679
- | `warning` | `object?` | ESI deprecation warning |
117
+ The project is run to a written [engineering charter](guides/CHARTER.md). It has <!-- metric:charterRequirements -->62<!-- /metric --> numbered requirements, each in the same EARS form as the test specification, and each with a status that says whether a machine enforces it: <!-- metric:charterEnforced -->46<!-- /metric --> are **Enforced**, <!-- metric:charterPractised -->6<!-- /metric --> Practised, <!-- metric:charterPartial -->9<!-- /metric --> Partial and <!-- metric:charterGap -->1<!-- /metric --> Gap. A gap is recorded, never hidden. Seven positions explain most of the choices:
680
118
 
681
- ## Rate Limiting
119
+ 1. **The OpenAPI spec is upstream.** Types, cache TTLs, rate-limit groups, scopes and <!-- metric:operations -->233<!-- /metric --> typed operations are generated from it, and CI fails when they go stale.
120
+ 2. **Hand-write where judgement matters.** Method names, argument shapes and validation strictness are product decisions. Drift reports keep them honest against the spec.
121
+ 3. **Tolerate additive change.** New fields and enum members from CCP never break a consumer. A removal is a breaking change.
122
+ 4. **Resilience is pluggable.** Retry, rate limiting, circuit breaking, deduplication, caching and transport are interfaces, not imports.
123
+ 5. **Secure by construction.** HTTPS and a host allowlist are checked at construction, tokens are attached only where a scope is declared, URLs are redacted in every error, and cache keys are hashed per token. Each control is a test.
124
+ 6. **The specification executes.** Behaviour is written as EARS requirements in Gherkin, one per `Rule:`, and verified by scenarios that mock only the transport. The audit fails a pull request that weakens the wording.
125
+ 7. **Verifiable supply chain.** Every action is SHA-pinned and every token least-privilege. Releases carry npm provenance, a signed SBOM, cosign signatures and checksums.
682
126
 
683
- Rate limiting is always on and needs no configuration. Each ESI rate-limit group from the OpenAPI spec gets its own bucket, the limiter learns remaining tokens from ESI's response headers, and a 420 or 429 blocks only the affected group. Multi-character applications can give each token its own buckets:
127
+ ### By the numbers
684
128
 
685
- ```typescript
686
- const client = new EsiClient({
687
- rateLimiterConfig: {
688
- userKeyExtractor: (headers) => headers['Authorization'] ?? 'anon',
689
- },
690
- });
691
- ```
129
+ Measured on `master` at `9486fe2c` on 2026-09-28 with the commands shown; the mutation and Scorecard rows cite the run that produced them. [TESTING.md](guides/TESTING.md) has the full breakdown.
692
130
 
693
- See [Rate limiting in the architecture guide](guides/ARCHITECTURE.md#6-rate-limiting) for the throttling rules, per-endpoint overrides and monitoring. Retry, deduplication, the opt-in [circuit breaker](guides/ARCHITECTURE.md#7-circuit-breaker) and [request/response interceptors](guides/ARCHITECTURE.md#8-interceptors) are documented alongside it.
131
+ | Measure | Value | Reproduce |
132
+ | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
133
+ | Tests run offline | 10,045 tests in 319 suites; 9,837 run, 0 failing (208 live tests skip without credentials or an SDE export) | `npm test`, `fuzz`, `faults`, `contract:replay`, `test:integration` |
134
+ | Unit, BDD and composition | 291 suites, 8,308 tests in about two and a half minutes | `npm test` |
135
+ | Coverage | 97.8% statements, 95.9% branches, 93.6% functions, 97.9% lines | `npm run coverage` (floors 90 / 80 / 75 / 90) |
136
+ | Executable specification | <!-- metric:requirements -->566<!-- /metric --> EARS requirements, <!-- metric:scenarios -->726<!-- /metric --> scenarios, <!-- metric:featureFiles -->73<!-- /metric --> feature files | `npm run ears` |
137
+ | Property and fuzz tests | 1,240 tests; 10,000 runs a property nightly | `npm run fuzz` |
138
+ | Transport fault catalogue | 148 faults through the real pipeline | `npm run faults` |
139
+ | Recorded ESI payloads | 121 replay tests, re-recorded nightly with a drift PR | `npm run contract:replay` |
140
+ | Mutation score | 84.1% of 1,355 mutants in `src/core` killed by the unit suite ([nightly of 2026-09-27](https://github.com/lgriffin/ESI.ts/actions/runs/36307924185)); 100% in `middleware`, `pagination` and `util`; floors per directory, ratcheted nightly, changed files on every PR | `npm run mutation:ratchet` |
141
+ | CI | One required check (`ci-success`) over the full matrix; 28 workflows, 17 of them scheduled | [QUALITY-GATES.md](guides/QUALITY-GATES.md) |
142
+ | OpenSSF Scorecard | 8.3 / 10 on 2026-09-28; the checks still open need settings only the maintainer can change | [SECURITY.md](guides/SECURITY.md#5-settings-only-the-maintainer-can-change) |
694
143
 
695
- ## Lightweight Clients
144
+ Every tier has to prove it can fail: a negative fixture, a killed mutant or a caught fault. Every floor is a one-way ratchet.
696
145
 
697
- All three client creation patterns (`EsiClient`, `CustomEsiClient`, `EsiApiFactory`) now get identical middleware defaults (cache, request deduplication, rate limiter) thanks to `configureApiClient()`. Previously `CustomEsiClient` and `EsiApiFactory` only configured the rate limiter.
146
+ ## Packages in the box
698
147
 
699
- If you only need a subset of APIs, use `CustomEsiClient` or `EsiClientBuilder` to load only what you need:
148
+ | Import | What it holds |
149
+ | ----------------------------- | ------------------------------------------------------------------------------------------------------------ |
150
+ | `@lgriffin/esi.ts` | `EsiClient`, `EsiClientBuilder`, `EsiApiFactory`, domain clients, auth, errors, generated types and scopes |
151
+ | `@lgriffin/esi.ts/schemas` | The Zod response schemas |
152
+ | `@lgriffin/esi.ts/errors` | Error classes and type guards, including the auth errors |
153
+ | `@lgriffin/esi.ts/testing` | `createMockTransport` and `TestDataFactory` for your own tests |
154
+ | `@lgriffin/esi.ts/client` | `createEsi`: one shared runtime, `esi.public` (authenticated calls do not compile) and `esi.as(identity)` |
155
+ | `@lgriffin/esi.ts/sde` | `SdeDataProvider` (YAML and ZIP, through the optional peers `js-yaml` and `adm-zip`) and `MemorySdeProvider` |
156
+ | `@lgriffin/esi.ts/sde/memory` | `MemorySdeProvider` alone, with no file-system or parser code, for browsers and bundles |
700
157
 
701
158
  ```typescript
702
- import { EsiClientBuilder } from '@lgriffin/esi.ts';
703
-
704
- const client = new EsiClientBuilder()
705
- .addClients(['market', 'universe', 'characters'])
706
- .withClientId('my-trading-bot')
707
- .withAccessToken('your-token')
708
- .build();
159
+ import { MarketOrderSchema } from '@lgriffin/esi.ts/schemas';
160
+ import { EsiError, isRetryable } from '@lgriffin/esi.ts/errors';
161
+ import { SdeDataProvider } from '@lgriffin/esi.ts/sde';
709
162
 
710
- const prices = await client.market?.getMarketPrices();
711
- const system = await client.universe?.getSystemById(30000142);
163
+ const sdeData = SdeDataProvider.fromDirectory('./sde-data');
164
+ console.log(sdeData.getType(34)?.name); // "Tritanium"
165
+ sdeData.close();
712
166
  ```
713
167
 
714
- Or create standalone single-API clients:
715
-
716
- ```typescript
717
- import { EsiApiFactory } from '@lgriffin/esi.ts';
718
-
719
- const marketClient = EsiApiFactory.createMarketClient({
720
- clientId: 'price-checker',
721
- });
722
- const prices = await marketClient.getMarketPrices();
723
- ```
168
+ ## Guides
724
169
 
725
- ## Endpoint Coverage
726
-
727
- All 235 endpoint definitions have been validated against live Tranquility using the **OpenAPI 3.1 spec** — 206 from the public ESI spec plus 29 for newer EVE features. Full output is captured in [`openapi.output.md`](openapi.output.md).
728
-
729
- | Category | Endpoints | Method |
730
- | --------------------------- | --------- | -------------------------------------------------- |
731
- | Public GETs | 86 | 52 runnable example scripts with captured output |
732
- | Authenticated GETs | 114 | Example scripts + live testing with EVE SSO tokens |
733
- | Contacts (POST/PUT/DELETE) | 3 | Live create/edit/delete lifecycle |
734
- | Fittings (POST/DELETE) | 2 | Live create/delete lifecycle |
735
- | Mail (POST/PUT/DELETE) | 5 | Live send/label/metadata/delete lifecycle |
736
- | UI (POST) | 5 | Live testing with EVE client running |
737
- | Calendar (PUT) | 1 | Live RSVP to event |
738
- | Fleet (GET/POST/PUT/DELETE) | 14 | Live fleet with fleet commander + squad members |
739
- | Assets POST | 3 | Live asset location/name queries |
740
- | CSPA (POST) | 1 | Live charge cost calculation |
741
- | Dogma dynamic (GET) | 1 | Live mutaplasmid (Abyssal) item query |
742
- | Universe POST helpers | 3 | Live name resolution and affiliation |
743
- | Freelance Jobs (GET) | 4 | Live queries (graceful 404 for no active jobs) |
170
+ The README orients and the guides are canonical. Each guide opens with the charter requirements it implements.
171
+
172
+ | Using ESI.ts | |
173
+ | -------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
174
+ | [Using the client](guides/USAGE.md) | Construction, configuration, every domain client, metadata, batching, examples, what 11.0 changes |
175
+ | [Authentication](guides/AUTHENTICATION.md) | Tokens, refresh on 401, SSO with PKCE, the multi-character token manager |
176
+ | [Many characters](guides/MULTI-CHARACTER.md) | One runtime, `esi.public`, `esi.as(identity)`, what the views share, moving from `createClient` |
177
+ | [Pagination](guides/PAGINATION.md) | Offset and cursor paging, `stream*`, `fetchAll*`, failure behaviour |
178
+ | [Errors](guides/ERRORS.md) | Error classes, type guards, retryability, safe mode |
179
+ | [Runtime validation](guides/RUNTIME-VALIDATION.md) | Zod response and request validation |
180
+ | [Logging](guides/LOGGING.md) | `ILogger`, per-client loggers, pino, `ESI_LOG_LEVEL` |
181
+ | [Static data (SDE)](guides/SDE.md) | The offline Static Data Export module: its role, isolation, API ([module README](src/sde/README.md)) |
182
+
183
+ | How it is built | |
184
+ | ------------------------------------------------------ | ------------------------------------------------------------------------------- |
185
+ | [Engineering charter](guides/CHARTER.md) | The requirements the project holds itself to, with status and gap register |
186
+ | [Roadmap to 11.0.0](guides/ROADMAP.md) | Phases, definitions of done, the SDE programme, the release gate |
187
+ | [Lean decisions](guides/LEAN-DECISIONS.md) | Why it is run this way: every decision since v7, with value stream maps |
188
+ | [Architecture](guides/ARCHITECTURE.md) | Layers, ports, the request path, caching, retry, rate limiting, circuit breaker |
189
+ | [Design rules](guides/DESIGN-RULES.md) | Naming, schemas, adding an endpoint or a client, generated files |
190
+ | [Testing](guides/TESTING.md) | Every test tier, what it proves, how to run it |
191
+ | [Mutation testing](guides/TESTING.md#mutation-testing) | Stryker shards, floors and the ratchet |
192
+ | [Quality gates](guides/QUALITY-GATES.md) | What runs at commit, push, PR, nightly and release; every workflow |
193
+ | [Security](guides/SECURITY.md) | Runtime defences and supply-chain controls ([policy](SECURITY.md)) |
194
+ | [Semantic versioning](guides/SEMVER.md) | What is public; major, minor or patch; breaking-change commits |
195
+ | [Release](guides/RELEASE.md) | Cutting a release, changelog, provenance, signatures, support window |
196
+ | [Audit](guides/AUDIT.md) | The Phase 0 measured baseline for 11.0 |
197
+ | [OKF bundle](guides/OKF.md) | The generated Open Knowledge Format catalogue of ESI |
198
+ | [Documentation](guides/DOCUMENTATION.md) | Documentation surfaces, checked examples, TypeDoc |
199
+ | [Beads](guides/BEADS.md) | Issue tracking workflow |
744
200
 
745
201
  ## Examples
746
202
 
747
- 52 runnable examples are in the `examples/` directory.
748
-
749
- ### Public Endpoints (no auth needed)
203
+ `examples/` has <!-- metric:examples -->58<!-- /metric --> runnable scripts, each with an npm script. The public ones run against live ESI every night, and a failure opens an issue.
750
204
 
751
205
  ```bash
752
- npm run example:status # Server status — quickest smoke test
753
- npm run example:character # Character public info, portrait, corporation
754
- npm run example:universe # Solar system, constellation, region, station
755
- npm run example:market # Average prices + Tritanium price history
756
- npm run example:alliance # Alliance info + member corporations
757
- npm run example:route # Jita-to-Amarr route with system names
758
- npm run example:wars # Recent wars with aggressor/defender details
759
- npm run example:sovereignty # Nullsec sovereignty map + active campaigns
760
- npm run example:industry # Industry facilities, cost indices, insurance
761
- npm run example:incursions # Active incursions + faction warfare stats
762
- npm run example:dogma # Item type details + dogma attributes
763
- npm run example:contracts # Public region contracts + auction bids/items
764
- npm run example:rate-limiting # Rate limiter & pagination demonstration
765
- npm run example:cursor-pagination # Freelance Jobs with cursor pagination
766
- npm run example:streaming # Streaming pagination for large datasets
767
- npm run example:token-refresh # Automatic token refresh on 401
768
- npm run example:universe-encyclopedia # Ancestries, bloodlines, races, celestials
769
- npm run example:dogma-meta-sov # Dogma effects, sovereignty, meta endpoint
770
- npm run example:faction-details # Faction warfare leaderboards and stats
206
+ npm run example:status # quickest smoke test, no token
207
+ npm run example:market # prices and history
208
+ npm run example:streaming # stream* over a large region
209
+ npm run example # full character profile (ESI_ACCESS_TOKEN)
771
210
  ```
772
211
 
773
- ### Authenticated Endpoints (require ESI_ACCESS_TOKEN)
212
+ The full list is in [USAGE.md](guides/USAGE.md#8-examples), and the [examples showcase](https://lgriffin.github.io/ESI.ts/examples/) has a page for each with its source, what it needs and the command that runs it.
774
213
 
775
- ```bash
776
- npm run example # Full character profile assembly
777
- npm run example:wallet # Wallet balance, journal, transactions
778
- npm run example:skills # Trained skills, queue, attributes
779
- npm run example:assets # Asset inventory with bulk name lookup
780
- npm run example:killmails # Recent killmails + full details
781
- npm run example:fleet # Fleet info, members, wing/squad structure
782
- npm run example:mail # Inbox headers, labels, mailing lists
783
- npm run example:location # Current system, online status, ship
784
- npm run example:fittings # Saved fittings + clone state + implants
785
- npm run example:contacts # Contact list with standings + labels
786
- npm run example:character-details # Blueprints, roles, standings, medals
787
- npm run example:corporation-details # Corp members, divisions, structures
788
- npm run example:calendar-search # Calendar events + character search
789
- npm run example:loyalty-pi # Loyalty points + planetary interaction
790
- npm run example:industry-mining # Industry jobs + mining ledger
791
- npm run example:market-orders # Character/corp market orders
792
- npm run example:corp-contracts-wallet # Corp contracts, contacts, wallets
793
- ```
794
-
795
- ### Write Operations (require specific scopes + caution)
796
-
797
- ```bash
798
- npm run example:write-ops # Contacts, fittings, mail, UI lifecycle tests
799
- npm run example:universe-posts # Name resolution + character affiliation (public)
800
- npm run example:freelance-jobs # Freelance job queries
801
- ```
802
-
803
- ### Parallel Requests
804
-
805
- ```typescript
806
- const [character, portrait, corp] = await Promise.all([
807
- client.characters.getCharacterPublicInfo(characterId),
808
- client.characters.getCharacterPortrait(characterId),
809
- client.corporations.getCorporationInfo(corporationId),
810
- ]);
811
-
812
- console.log(`${character.name} [${corp.ticker}]`);
813
- ```
814
-
815
- ### Market Analysis
816
-
817
- ```typescript
818
- const [orders, history] = await Promise.all([
819
- client.market.getMarketOrders(regionId),
820
- client.market.getMarketHistory(regionId, typeId),
821
- ]);
822
-
823
- const buyOrders = orders.filter((o) => o.is_buy_order);
824
- const sellOrders = orders.filter((o) => !o.is_buy_order);
825
-
826
- console.log(`Best buy: ${Math.max(...buyOrders.map((o) => o.price))}`);
827
- console.log(`Best sell: ${Math.min(...sellOrders.map((o) => o.price))}`);
828
- ```
829
-
830
- ## Resource Management
831
-
832
- Always call `shutdown()` when you're done to clean up cache timers:
833
-
834
- ```typescript runnable
835
- import { EsiClient } from '@lgriffin/esi.ts';
836
-
837
- const client = new EsiClient();
838
- try {
839
- const status = await client.status.getStatus();
840
- console.log(status.server_version);
841
- } finally {
842
- await client.shutdown();
843
- }
844
- ```
845
-
846
- ## Testing
847
-
848
- ESI.ts has a comprehensive multi-tier testing strategy with 171 suites and 4,957 tests:
849
-
850
- | Tier | Tests | Purpose |
851
- | -------------------------- | ---------------- | ------------------------------------------------------------------ |
852
- | **TDD unit tests** | 130 files | Every client method, endpoint path, query param, and body format |
853
- | **BDD scenario tests** | 41 feature files | Behavioral specifications in Gherkin (Given/When/Then) |
854
- | **Mocked integration** | Full suite | Cross-layer request flow with jest-fetch-mock |
855
- | **Live smoke tests** | 46 examples | Every endpoint against live Tranquility |
856
- | **ESI spec contract** | 15 tests | Endpoint definitions validated against live OpenAPI spec |
857
- | **Deep contract tests** | 8 categories | Path params, query params, body, auth, schemas, pagination vs spec |
858
- | **Property-based fuzzing** | 601 tests | fast-check fuzzing of validation, URL construction, Zod schemas |
859
- | **Mutation testing** | Stryker | Validates test suite kills code mutants |
860
- | **Type-level tests** | tsd | Consumer API type correctness via tsd |
861
- | **Gated auth tests** | 33 tests | Authenticated endpoints with real tokens |
862
- | **Construction parity** | Per-surface | Verifies all client surfaces get identical middleware defaults |
863
- | **Spec-alignment** | Type assertions | Ensures hand-written types align with generated OpenAPI types |
864
-
865
- ```bash
866
- npm test # Unit + BDD tests (171 suites, 4,957 tests)
867
- npm run coverage # Tests with coverage report (thresholds enforced)
868
- npm run bdd # BDD scenario tests only
869
- npm run contract # Contract tests (skipped without ESI_LIVE_TESTS=true)
870
- npm run fuzz # Property-based fuzz tests (601 tests)
871
- npm run mutation # Mutation testing (Stryker)
872
- npm run benchmark # Micro-benchmarks (mitata); npm run soak for the heap soak
873
- npm run test:types # tsd consumer type tests
874
- ```
875
-
876
- Coverage: statements 98.37%, branches 95.14%, functions 96.09%, lines 98.17%. Thresholds enforced in CI: branches 80%, functions 75%, lines 90%, statements 90%.
877
-
878
- See [guides/TESTING.md](guides/TESTING.md) for the full testing guide, and [guides/ARCHITECTURE.md](guides/ARCHITECTURE.md) for architecture diagrams.
879
-
880
- ## Development
881
-
882
- ### Prerequisites
883
-
884
- - Node.js 18+
885
- - npm
886
-
887
- ### Code Quality Tools
888
-
889
- The project uses a comprehensive suite of static analysis and code quality tools:
890
-
891
- | Tool | Purpose | Command |
892
- | ------------------------------------------------------------------------------------ | ------------------------------------------------------- | ------------------------------ |
893
- | [ESLint](https://eslint.org/) | Linting with TypeScript, security, and code smell rules | `npm run lint` |
894
- | [Prettier](https://prettier.io/) | Code formatting | `npm run format:check` |
895
- | [knip](https://knip.dev/) | Dead code and unused export detection | `npm run knip` |
896
- | [eslint-plugin-security](https://github.com/eslint-community/eslint-plugin-security) | Security anti-pattern detection | Integrated into `npm run lint` |
897
- | [eslint-plugin-sonarjs](https://github.com/SonarSource/eslint-plugin-sonarjs) | Cognitive complexity and code smell detection | Integrated into `npm run lint` |
898
- | [husky](https://typicode.github.io/husky/) | Git pre-commit hooks | Automatic on commit |
899
- | [lint-staged](https://github.com/lint-staged/lint-staged) | Run linters on staged files only | Automatic on commit |
900
- | [Redocly CLI](https://redocly.com/docs/cli/) | OpenAPI spec validation and linting | `npm run validate:spec` |
901
-
902
- ### Available Scripts
903
-
904
- ```bash
905
- # Development
906
- npm run build # Compile TypeScript
907
- npm run lint # Run ESLint
908
- npm run lint:fix # Run ESLint with auto-fix
909
- npm run format # Format code with Prettier
910
- npm run format:check # Check formatting without modifying
911
-
912
- # Testing
913
- npm test # Unit tests (171 suites, 4,957 tests)
914
- npm run test:all # Unit + BDD + integration + fuzz + type tests
915
- npm run coverage # Tests with coverage report (thresholds enforced)
916
- npm run bdd # BDD scenario tests
917
- ESI_LIVE_TESTS=true npm run contract:live # Deep contract tests against live ESI spec (fails without the variable)
918
- npm run fuzz # Property-based fuzz tests (fast-check)
919
- npm run mutation # Mutation testing (Stryker)
920
- npm run benchmark # Micro-benchmarks (mitata)
921
- npm run test:types # Consumer type tests (tsd)
922
- npm run mock:esi # Start Prism mock ESI server on port 4010
923
-
924
- # Static Analysis
925
- npm run knip # Detect dead code and unused exports
926
- npm run validate:esi # Validate endpoints against live ESI OpenAPI spec
927
- npm run validate:spec # Lint ESI OpenAPI spec with Redocly (structural + best practices)
928
- npm run validate:auth-scopes # Auth/scope cross-validation
929
- npm run schema:drift # Schema drift detection (hand-written vs OpenAPI spec)
930
- npm run validate # Run all checks: lint, format, build, coverage, knip
931
- npm run generate:types # Regenerate TypeScript interfaces from ESI OpenAPI spec
932
- npm run generate:endpoints # Regenerate endpoint definitions from ESI OpenAPI spec
933
- npm run generate:all # Run all generators (types + endpoints + OKF)
934
- npm run generate:okf # Generate OKF knowledge bundle from ESI OpenAPI spec
935
-
936
- # Documentation
937
- npm run docs # Generate TypeDoc API documentation
938
- npm run docs:serve # Serve docs locally on port 8080
939
- ```
940
-
941
- ### ESI Endpoint Validation
942
-
943
- To verify that the codebase endpoint definitions match the live ESI OpenAPI spec:
214
+ ## Contributing
944
215
 
945
216
  ```bash
946
- npm run validate:esi
217
+ git clone https://github.com/lgriffin/ESI.ts.git && cd ESI.ts
218
+ npm ci
219
+ npm run check:local -- --fast # every offline CI tier, minus the build
947
220
  ```
948
221
 
949
- This fetches the ESI OpenAPI spec and reports:
950
-
951
- - Endpoints in the codebase that are no longer in the ESI spec
952
- - Endpoints in the ESI spec that the codebase doesn't cover
953
- - HTTP method mismatches between codebase and spec
954
-
955
- ### Pre-commit Hooks
956
-
957
- The project uses husky with lint-staged to run ESLint and Prettier on staged files before each commit. This is set up automatically when you run `npm install`.
958
-
959
- ### CI/CD
960
-
961
- Every push runs lint, format, build, typecheck and unit tests; pull requests to `master` run the full matrix behind a single Quality Gate check. Actions are SHA-pinned, packages publish with npm provenance, and release assets are cosign-signed.
962
-
963
- See [guides/QUALITY-GATES.md](guides/QUALITY-GATES.md) for the gate matrix and every workflow, and [guides/SECURITY.md](guides/SECURITY.md) for the supply-chain controls.
964
-
965
- ## Contributing
966
-
967
- 1. Fork the repository
968
- 2. Create a feature branch
969
- 3. Write tests for your changes
970
- 4. Run `npm run validate` to check everything passes
971
- 5. Open a Pull Request
972
-
973
- Work is tracked with [beads](https://github.com/gastownhall/beads) (`bd`). Run
974
- `bd ready` to see available work — see [guides/BEADS.md](guides/BEADS.md) for the
975
- full workflow.
222
+ Read [CONTRIBUTING.md](CONTRIBUTING.md) first. Every behaviour change starts with an EARS requirement and a failing scenario. Every commit is a conventional commit classified by [SEMVER.md](guides/SEMVER.md). No floor is ever lowered. Work is tracked in GitHub issues labelled by release and phase, mirrored in [beads](guides/BEADS.md).
976
223
 
977
224
  ## License
978
225
 
979
- GPL-3.0-or-later - see the [LICENSE](LICENSE) file for details.
226
+ GPL-3.0-or-later. See [LICENSE](LICENSE) for the licence text and [NOTICE](NOTICE) for the copyright line and CCP's trademark notice; EVE Online is the property of CCP hf. and this project is not affiliated with or endorsed by them.
980
227
 
981
228
  ---
982
229