@lgriffin/esi.ts 4.0.0 → 5.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 (343) hide show
  1. package/CHANGELOG.md +156 -0
  2. package/LICENSE +26 -26
  3. package/README.md +562 -557
  4. package/dist/EsiClient.d.ts +8 -2
  5. package/dist/EsiClient.d.ts.map +1 -1
  6. package/dist/EsiClient.js +9 -0
  7. package/dist/EsiClientBuilder.d.ts +4 -1
  8. package/dist/EsiClientBuilder.d.ts.map +1 -1
  9. package/dist/EsiClientBuilder.js +9 -0
  10. package/dist/clients/AccessListsClient.d.ts +15 -0
  11. package/dist/clients/AccessListsClient.d.ts.map +1 -0
  12. package/dist/clients/AccessListsClient.js +20 -0
  13. package/dist/clients/MercenaryClient.d.ts +20 -0
  14. package/dist/clients/MercenaryClient.d.ts.map +1 -0
  15. package/dist/clients/MercenaryClient.js +27 -0
  16. package/dist/clients/SkyhooksClient.d.ts +26 -0
  17. package/dist/clients/SkyhooksClient.d.ts.map +1 -0
  18. package/dist/clients/SkyhooksClient.js +35 -0
  19. package/dist/clients/SovereigntyClient.d.ts +4 -10
  20. package/dist/clients/SovereigntyClient.d.ts.map +1 -1
  21. package/dist/clients/SovereigntyClient.js +4 -12
  22. package/dist/clients/UniverseClient.d.ts +2 -2
  23. package/dist/clients/UniverseClient.d.ts.map +1 -1
  24. package/dist/clients/UniverseClient.js +3 -3
  25. package/dist/config/configManager.d.ts.map +1 -1
  26. package/dist/config/configManager.js +12 -6
  27. package/dist/config/jest/jest.setup.js +0 -1
  28. package/dist/core/ApiRequestHandler.d.ts.map +1 -1
  29. package/dist/core/ApiRequestHandler.js +6 -2
  30. package/dist/core/ClientRegistry.d.ts +6 -4
  31. package/dist/core/ClientRegistry.d.ts.map +1 -1
  32. package/dist/core/ClientRegistry.js +11 -2
  33. package/dist/core/circuitBreaker/CircuitBreaker.js +2 -2
  34. package/dist/core/constants.d.ts +3 -3
  35. package/dist/core/constants.js +2 -2
  36. package/dist/core/endpoints/accessListEndpoints.d.ts +9 -0
  37. package/dist/core/endpoints/accessListEndpoints.d.ts.map +1 -0
  38. package/dist/core/endpoints/accessListEndpoints.js +11 -0
  39. package/dist/core/endpoints/assetEndpoints.d.ts +4 -12
  40. package/dist/core/endpoints/assetEndpoints.d.ts.map +1 -1
  41. package/dist/core/endpoints/assetEndpoints.js +4 -4
  42. package/dist/core/endpoints/contactEndpoints.d.ts +1 -3
  43. package/dist/core/endpoints/contactEndpoints.d.ts.map +1 -1
  44. package/dist/core/endpoints/contactEndpoints.js +1 -1
  45. package/dist/core/endpoints/mercenaryEndpoints.d.ts +13 -0
  46. package/dist/core/endpoints/mercenaryEndpoints.d.ts.map +1 -0
  47. package/dist/core/endpoints/mercenaryEndpoints.js +15 -0
  48. package/dist/core/endpoints/skyhookEndpoints.d.ts +18 -0
  49. package/dist/core/endpoints/skyhookEndpoints.d.ts.map +1 -0
  50. package/dist/core/endpoints/skyhookEndpoints.js +20 -0
  51. package/dist/core/endpoints/sovereigntyEndpoints.d.ts +2 -7
  52. package/dist/core/endpoints/sovereigntyEndpoints.d.ts.map +1 -1
  53. package/dist/core/endpoints/sovereigntyEndpoints.js +2 -7
  54. package/dist/core/endpoints/universeEndpoints.d.ts +2 -6
  55. package/dist/core/endpoints/universeEndpoints.d.ts.map +1 -1
  56. package/dist/core/endpoints/universeEndpoints.js +2 -2
  57. package/dist/core/util/testHelpers.d.ts +0 -2
  58. package/dist/core/util/testHelpers.d.ts.map +1 -1
  59. package/dist/core/util/testHelpers.js +3 -19
  60. package/dist/core/util/validation.js +2 -2
  61. package/dist/index.d.ts +3 -0
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +8 -2
  64. package/dist/testing/TestDataFactory.d.ts +8 -2
  65. package/dist/testing/TestDataFactory.d.ts.map +1 -1
  66. package/dist/testing/TestDataFactory.js +86 -1
  67. package/dist/types/access-lists.d.ts +11 -0
  68. package/dist/types/access-lists.d.ts.map +1 -0
  69. package/dist/types/api-responses.d.ts +3 -0
  70. package/dist/types/api-responses.d.ts.map +1 -1
  71. package/dist/types/api-responses.js +3 -0
  72. package/dist/types/mercenary.d.ts +19 -0
  73. package/dist/types/mercenary.d.ts.map +1 -0
  74. package/dist/types/mercenary.js +2 -0
  75. package/dist/types/skyhooks.d.ts +27 -0
  76. package/dist/types/skyhooks.d.ts.map +1 -0
  77. package/dist/types/skyhooks.js +2 -0
  78. package/dist/types/sovereignty.d.ts +11 -9
  79. package/dist/types/sovereignty.d.ts.map +1 -1
  80. package/package.json +181 -156
  81. package/dist/api/alliances/getAllianceById.js +0 -17
  82. package/dist/api/alliances/getAllianceContactLabels.js +0 -13
  83. package/dist/api/alliances/getAllianceContacts.js +0 -13
  84. package/dist/api/alliances/getAllianceCorporations.js +0 -13
  85. package/dist/api/alliances/getAllianceIcons.js +0 -13
  86. package/dist/api/alliances/getAlliances.js +0 -13
  87. package/dist/api/assets/getCharacterAssets.js +0 -13
  88. package/dist/api/assets/getCorporationAssets.js +0 -13
  89. package/dist/api/assets/postCharacterAssetLocations.js +0 -13
  90. package/dist/api/assets/postCharacterAssetNames.js +0 -13
  91. package/dist/api/assets/postCorporationAssetLocations.js +0 -13
  92. package/dist/api/assets/postCorporationAssetNames.js +0 -13
  93. package/dist/api/bookmarks/getCharacterBookmarkFolders.js +0 -13
  94. package/dist/api/bookmarks/getCharacterBookmarks.js +0 -13
  95. package/dist/api/bookmarks/getCorporationBookmarkFolders.js +0 -13
  96. package/dist/api/bookmarks/getCorporationBookmarks.js +0 -13
  97. package/dist/api/calendar/getCalendarEventById.js +0 -13
  98. package/dist/api/calendar/getCalendarEvents.js +0 -13
  99. package/dist/api/calendar/getEventAttendees.js +0 -13
  100. package/dist/api/calendar/respondToCalendarEvent.js +0 -14
  101. package/dist/api/characters/getAgentsResearch.js +0 -13
  102. package/dist/api/characters/getBlueprints.js +0 -13
  103. package/dist/api/characters/getCharacterPublicInfo.js +0 -13
  104. package/dist/api/characters/getCharacterRoles.js +0 -13
  105. package/dist/api/characters/getCharacterStandings.js +0 -13
  106. package/dist/api/characters/getCharacterTitles.js +0 -13
  107. package/dist/api/characters/getContactNotifications.js +0 -13
  108. package/dist/api/characters/getCorporationHistory.js +0 -13
  109. package/dist/api/characters/getJumpFatigue.js +0 -13
  110. package/dist/api/characters/getMedals.js +0 -13
  111. package/dist/api/characters/getNotifications.js +0 -13
  112. package/dist/api/characters/getPortrait.js +0 -13
  113. package/dist/api/characters/postCSPAChargeCost.js +0 -13
  114. package/dist/api/characters/postCharacterAffiliations.js +0 -14
  115. package/dist/api/clones/getClones.js +0 -14
  116. package/dist/api/clones/getImplants.js +0 -14
  117. package/dist/api/clones/postJumpCloneActivation.js +0 -14
  118. package/dist/api/contacts/deleteCharacterContacts.js +0 -13
  119. package/dist/api/contacts/getAllianceContactLabels.js +0 -13
  120. package/dist/api/contacts/getAllianceContacts.js +0 -13
  121. package/dist/api/contacts/getCharacterContactLabels.js +0 -13
  122. package/dist/api/contacts/getCharacterContacts.js +0 -13
  123. package/dist/api/contacts/getCorporationContactLabels.js +0 -13
  124. package/dist/api/contacts/getCorporationContacts.js +0 -13
  125. package/dist/api/contacts/postCharacterContacts.js +0 -13
  126. package/dist/api/contacts/putCharacterContacts.js +0 -13
  127. package/dist/api/contracts/getCharacterContractBids.js +0 -13
  128. package/dist/api/contracts/getCharacterContractItems.js +0 -13
  129. package/dist/api/contracts/getCharacterContracts.js +0 -13
  130. package/dist/api/contracts/getCorporationContractBids.js +0 -13
  131. package/dist/api/contracts/getCorporationContractItems.js +0 -13
  132. package/dist/api/contracts/getCorporationContracts.js +0 -13
  133. package/dist/api/contracts/getPublicContractBids.js +0 -13
  134. package/dist/api/contracts/getPublicContractItems.js +0 -13
  135. package/dist/api/contracts/getPublicContracts.js +0 -13
  136. package/dist/api/corporations/getCorporationAllianceHistory.js +0 -13
  137. package/dist/api/corporations/getCorporationAlscLogs.js +0 -13
  138. package/dist/api/corporations/getCorporationBlueprints.js +0 -13
  139. package/dist/api/corporations/getCorporationDivisions.js +0 -13
  140. package/dist/api/corporations/getCorporationFacilities.js +0 -13
  141. package/dist/api/corporations/getCorporationIcon.js +0 -13
  142. package/dist/api/corporations/getCorporationInfo.js +0 -13
  143. package/dist/api/corporations/getCorporationIssuedMedals.js +0 -13
  144. package/dist/api/corporations/getCorporationMedals.js +0 -13
  145. package/dist/api/corporations/getCorporationMemberLimit.js +0 -13
  146. package/dist/api/corporations/getCorporationMemberRoles.js +0 -13
  147. package/dist/api/corporations/getCorporationMemberRolesHistory.js +0 -13
  148. package/dist/api/corporations/getCorporationMemberTracking.js +0 -13
  149. package/dist/api/corporations/getCorporationMembers.js +0 -13
  150. package/dist/api/corporations/getCorporationMembersTitles.js +0 -13
  151. package/dist/api/corporations/getCorporationProjects.js +0 -13
  152. package/dist/api/corporations/getCorporationShareholders.js +0 -13
  153. package/dist/api/corporations/getCorporationStandings.js +0 -13
  154. package/dist/api/corporations/getCorporationStarbaseDetail.js +0 -13
  155. package/dist/api/corporations/getCorporationStarbases.js +0 -13
  156. package/dist/api/corporations/getCorporationStructures.js +0 -13
  157. package/dist/api/corporations/getCorporationTitles.js +0 -13
  158. package/dist/api/corporations/getNpcCorporations.js +0 -13
  159. package/dist/api/dogma/getDogmaAttributeById.js +0 -13
  160. package/dist/api/dogma/getDogmaAttributes.js +0 -13
  161. package/dist/api/dogma/getDogmaDynamicItemAttributes.js +0 -13
  162. package/dist/api/dogma/getDogmaEffectById.js +0 -13
  163. package/dist/api/dogma/getDogmaEffects.js +0 -13
  164. package/dist/api/factions/getCharacterFactionWarfareStats.js +0 -13
  165. package/dist/api/factions/getCharacterLeaderboards.js +0 -13
  166. package/dist/api/factions/getCorporationFactionWarfareStats.js +0 -13
  167. package/dist/api/factions/getCorporationLeaderboards.js +0 -13
  168. package/dist/api/factions/getFactionLeaderboards.js +0 -13
  169. package/dist/api/factions/getFactionWarfareStats.js +0 -13
  170. package/dist/api/factions/getFactionWarfareSystems.js +0 -13
  171. package/dist/api/factions/getFactionWarfareWars.js +0 -13
  172. package/dist/api/fittings/deleteCharacterFitting.js +0 -13
  173. package/dist/api/fittings/getCharacterFittings.js +0 -13
  174. package/dist/api/fittings/postCharacterFittings.js +0 -13
  175. package/dist/api/fleets/deleteFleetMember.js +0 -13
  176. package/dist/api/fleets/deleteFleetSquad.js +0 -13
  177. package/dist/api/fleets/deleteFleetWing.js +0 -13
  178. package/dist/api/fleets/getCharacterFleetInfo.js +0 -13
  179. package/dist/api/fleets/getFleetInfo.js +0 -13
  180. package/dist/api/fleets/getFleetMembers.js +0 -13
  181. package/dist/api/fleets/getFleetWings.js +0 -13
  182. package/dist/api/fleets/postFleetInvitation.js +0 -13
  183. package/dist/api/fleets/postFleetSquad.js +0 -14
  184. package/dist/api/fleets/postFleetWing.js +0 -13
  185. package/dist/api/fleets/putFleetMember.js +0 -13
  186. package/dist/api/fleets/putFleetSquad.js +0 -13
  187. package/dist/api/fleets/putFleetWing.js +0 -14
  188. package/dist/api/fleets/updateFleet.js +0 -13
  189. package/dist/api/incursions/getIncursions.js +0 -13
  190. package/dist/api/industry/getCharacterIndustryJobs.js +0 -13
  191. package/dist/api/industry/getCharacterMiningLedger.js +0 -13
  192. package/dist/api/industry/getCorporationIndustryJobs.js +0 -13
  193. package/dist/api/industry/getCorporationMiningObserver.js +0 -13
  194. package/dist/api/industry/getCorporationMiningObservers.js +0 -13
  195. package/dist/api/industry/getIndustryFacilities.js +0 -13
  196. package/dist/api/industry/getIndustrySystems.js +0 -13
  197. package/dist/api/industry/getMoonExtractionTimers.js +0 -13
  198. package/dist/api/insurance/getInsurancePrices.js +0 -13
  199. package/dist/api/killmails/getCharacterRecentKillmails.js +0 -13
  200. package/dist/api/killmails/getCorporationRecentKillmails.js +0 -13
  201. package/dist/api/killmails/getKillmail.js +0 -13
  202. package/dist/api/location/getCharacterLocation.js +0 -13
  203. package/dist/api/location/getCharacterOnline.js +0 -13
  204. package/dist/api/location/getCharacterShip.js +0 -13
  205. package/dist/api/loyalty/getLoyaltyPoints.js +0 -13
  206. package/dist/api/loyalty/getLoyaltyStoreOffers.js +0 -13
  207. package/dist/api/mail/deleteCharacterMail.js +0 -13
  208. package/dist/api/mail/deleteCharacterMailLabel.js +0 -13
  209. package/dist/api/mail/deleteCharacterMailLabel.test.js +0 -21
  210. package/dist/api/mail/getCharacerMailingLists.test.js +0 -33
  211. package/dist/api/mail/getCharacterMail.js +0 -13
  212. package/dist/api/mail/getCharacterMailHeaders.js +0 -13
  213. package/dist/api/mail/getCharacterMailLabels.js +0 -13
  214. package/dist/api/mail/getCharacterMailingLists.js +0 -13
  215. package/dist/api/mail/postCharacterMail.js +0 -13
  216. package/dist/api/mail/postCharacterMailLabels.js +0 -13
  217. package/dist/api/mail/putCharacterMail.js +0 -13
  218. package/dist/api/market/getCharacterOrderHistory.js +0 -13
  219. package/dist/api/market/getCharacterOrders.js +0 -13
  220. package/dist/api/market/getCorporationOrderHistory.js +0 -13
  221. package/dist/api/market/getCorporationOrders.js +0 -13
  222. package/dist/api/market/getMarketGroupInformation.js +0 -13
  223. package/dist/api/market/getMarketGroups.js +0 -13
  224. package/dist/api/market/getMarketHistory.js +0 -13
  225. package/dist/api/market/getMarketOrders.js +0 -13
  226. package/dist/api/market/getMarketOrdersInStructure.js +0 -13
  227. package/dist/api/market/getMarketPrices.js +0 -13
  228. package/dist/api/market/getMarketTypes.js +0 -13
  229. package/dist/api/meta/getSwaggerJson.js +0 -13
  230. package/dist/api/meta/getSwaggerYaml.js +0 -37
  231. package/dist/api/opportunities/getCharacterOpportunities.js +0 -13
  232. package/dist/api/opportunities/getOpportunitiesGroupById.js +0 -13
  233. package/dist/api/opportunities/getOpportunitiesGroups.js +0 -13
  234. package/dist/api/opportunities/getOpportunitiesTaskById.js +0 -13
  235. package/dist/api/opportunities/getOpportunitiesTasks.js +0 -13
  236. package/dist/api/pi/getColonies.js +0 -13
  237. package/dist/api/pi/getColonyLayout.js +0 -13
  238. package/dist/api/pi/getCorporationCustomsOffices.js +0 -13
  239. package/dist/api/pi/getSchematicInformation.js +0 -13
  240. package/dist/api/route/getRoute.js +0 -13
  241. package/dist/api/search/getCharacterSearch.js +0 -13
  242. package/dist/api/skills/getCharacterAttributes.js +0 -13
  243. package/dist/api/skills/getCharacterSkillQueue.js +0 -13
  244. package/dist/api/skills/getCharacterSkills.js +0 -13
  245. package/dist/api/sovereignty/getSovereigntyCampaigns.js +0 -13
  246. package/dist/api/sovereignty/getSovereigntyMap.js +0 -13
  247. package/dist/api/sovereignty/getSovereigntyStructures.js +0 -13
  248. package/dist/api/status/getStatus.js +0 -13
  249. package/dist/api/ui/postAutopilotWaypoint.js +0 -13
  250. package/dist/api/ui/postOpenContractWindow.js +0 -13
  251. package/dist/api/ui/postOpenInformationWindow.js +0 -13
  252. package/dist/api/ui/postOpenMarketDetailsWindow.js +0 -13
  253. package/dist/api/ui/postOpenNewMailWindow.js +0 -13
  254. package/dist/api/universe/getAncestries.js +0 -13
  255. package/dist/api/universe/getAsteroidBeltInfo.js +0 -13
  256. package/dist/api/universe/getBloodlines.js +0 -13
  257. package/dist/api/universe/getConstellationById.js +0 -13
  258. package/dist/api/universe/getConstellations.js +0 -13
  259. package/dist/api/universe/getFactions.js +0 -13
  260. package/dist/api/universe/getGraphicById.js +0 -13
  261. package/dist/api/universe/getGraphics.js +0 -13
  262. package/dist/api/universe/getItemCategories.js +0 -13
  263. package/dist/api/universe/getItemCategoryById.js +0 -13
  264. package/dist/api/universe/getItemGroupById.js +0 -13
  265. package/dist/api/universe/getItemGroups.js +0 -13
  266. package/dist/api/universe/getMoonById.js +0 -13
  267. package/dist/api/universe/getPlanetById.js +0 -13
  268. package/dist/api/universe/getRaces.js +0 -13
  269. package/dist/api/universe/getRegionById.js +0 -13
  270. package/dist/api/universe/getRegions.js +0 -13
  271. package/dist/api/universe/getSchematicById.js +0 -13
  272. package/dist/api/universe/getStarById.js +0 -13
  273. package/dist/api/universe/getStargateById.js +0 -13
  274. package/dist/api/universe/getStationById.js +0 -13
  275. package/dist/api/universe/getStructureById.js +0 -13
  276. package/dist/api/universe/getStructures.js +0 -13
  277. package/dist/api/universe/getSystemById.js +0 -13
  278. package/dist/api/universe/getSystemJumps.js +0 -13
  279. package/dist/api/universe/getSystemKills.js +0 -13
  280. package/dist/api/universe/getSystems.js +0 -13
  281. package/dist/api/universe/getTypeById.js +0 -13
  282. package/dist/api/universe/getTypes.js +0 -13
  283. package/dist/api/universe/postBulkNamesToIds.js +0 -14
  284. package/dist/api/universe/postNamesAndCategories.js +0 -14
  285. package/dist/api/wallet/getCharacterWallet.js +0 -13
  286. package/dist/api/wallet/getCharacterWalletJournal.js +0 -13
  287. package/dist/api/wallet/getCharacterWalletTransactions.js +0 -13
  288. package/dist/api/wallet/getCorporationWalletJournal.js +0 -13
  289. package/dist/api/wallet/getCorporationWalletTransactions.js +0 -13
  290. package/dist/api/wallet/getCorporationWallets.js +0 -13
  291. package/dist/api/wars/getWarById.js +0 -13
  292. package/dist/api/wars/getWarKillmails.js +0 -13
  293. package/dist/api/wars/getWars.js +0 -13
  294. package/dist/builders/AllianceApiBuilder.js +0 -13
  295. package/dist/builders/AssetsApiBuilder.js +0 -13
  296. package/dist/builders/BookmarkApiBuilder.js +0 -13
  297. package/dist/builders/CalendarApiBuilder.js +0 -13
  298. package/dist/builders/CharacterApiBuilder.js +0 -13
  299. package/dist/builders/ClonesApiBuilder.js +0 -13
  300. package/dist/builders/ContactsApiBuilder.js +0 -13
  301. package/dist/builders/ContractsApiBuilder.js +0 -13
  302. package/dist/builders/CorporationsApiBuilder.js +0 -13
  303. package/dist/builders/DogmaApiBuilder.js +0 -13
  304. package/dist/builders/FactionApiBuilder.js +0 -13
  305. package/dist/builders/FittingsApiBuilder.js +0 -13
  306. package/dist/builders/FleetApiBuilder.js +0 -13
  307. package/dist/builders/IncursionsApiBuilder.js +0 -13
  308. package/dist/builders/IndustryApiBuilder.js +0 -13
  309. package/dist/builders/InsuranceApiBuilder.js +0 -13
  310. package/dist/builders/KillmailsBuilder.js +0 -13
  311. package/dist/builders/LocationApiBuilder.js +0 -13
  312. package/dist/builders/LoyaltyApiBuilder.js +0 -13
  313. package/dist/builders/MailApiBuilder.js +0 -13
  314. package/dist/builders/MarketApiBuilder.js +0 -13
  315. package/dist/builders/MetaApiBuilder.js +0 -13
  316. package/dist/builders/OpportunitiesApiBuilder.js +0 -13
  317. package/dist/builders/PiApiBuilder.js +0 -13
  318. package/dist/builders/RouteApiBuilder.js +0 -13
  319. package/dist/builders/SearchApiBuilder.js +0 -13
  320. package/dist/builders/SkillsApiBuilder.js +0 -13
  321. package/dist/builders/SovereigntyApiBuilder.js +0 -13
  322. package/dist/builders/StatusApiBuilder.js +0 -13
  323. package/dist/builders/UiApiBuilder.js +0 -13
  324. package/dist/builders/UniverseApiBuilder.js +0 -13
  325. package/dist/builders/WalletApiBuilder.js +0 -13
  326. package/dist/builders/WarsAPIBuilder.js +0 -13
  327. package/dist/clients/BookmarkClient.js +0 -28
  328. package/dist/clients/OpportunitiesClient.js +0 -33
  329. package/dist/config/constants.js +0 -11
  330. package/dist/core/ApiError.js +0 -11
  331. package/dist/core/container/Container.js +0 -145
  332. package/dist/core/errors/ApiError.js +0 -129
  333. package/dist/core/factory/ApiFactory.js +0 -170
  334. package/dist/core/logger/index.d.ts +0 -2
  335. package/dist/core/logger/index.d.ts.map +0 -1
  336. package/dist/core/logger/index.js +0 -8
  337. package/dist/core/util/file.js +0 -26
  338. package/dist/core/util/inputValidation.js +0 -9
  339. package/dist/core/util/network.js +0 -15
  340. package/dist/core/util/request.js +0 -21
  341. package/dist/testing/TestHelpers.js +0 -205
  342. package/dist/tsconfig.tsbuildinfo +0 -1
  343. /package/dist/{core/IAPIBuilder.js → types/access-lists.js} +0 -0
package/README.md CHANGED
@@ -1,557 +1,562 @@
1
- # ESI.ts
2
-
3
- [![npm version](https://badge.fury.io/js/%40lgriffin%2Fesi.ts.svg)](https://badge.fury.io/js/%40lgriffin%2Fesi.ts)
4
- [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
5
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.0%2B-blue)](https://www.typescriptlang.org/)
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
- [![PR Validation](https://github.com/lgriffin/ESI.ts/actions/workflows/pr-validation.yml/badge.svg)](https://github.com/lgriffin/ESI.ts/actions/workflows/pr-validation.yml)
8
-
9
- A type-safe TypeScript client for the [EVE Online ESI API](https://esi.evetech.net/).
10
-
11
- - Typed responses for all endpoints
12
- - ETag caching with Cache-Control TTL, stale-on-error, and write invalidation
13
- - Automatic offset-based pagination and cursor-based pagination support
14
- - Rate limiting with header-driven backoff
15
- - Automatic token refresh with 401 retry and concurrent coalescing
16
- - 32 domain clients covering the full ESI surface
17
-
18
- ## Installation
19
-
20
- ```bash
21
- npm install @lgriffin/esi.ts
22
- ```
23
-
24
- ### Building from Source
25
-
26
- ```bash
27
- git clone https://github.com/lgriffin/ESI.ts.git
28
- cd ESI.ts
29
- npm install # installs dependencies and compiles (via the prepare script)
30
- ```
31
-
32
- If you've already installed and just need to recompile:
33
-
34
- ```bash
35
- npm run build
36
- ```
37
-
38
- Verify everything works:
39
-
40
- ```bash
41
- npm run example:status # quick smoke test — checks ESI is reachable
42
- npm test # run the full test suite
43
- ```
44
-
45
- ## Quick Start
46
-
47
- ```typescript
48
- import { EsiClient } from '@lgriffin/esi.ts';
49
-
50
- const client = new EsiClient();
51
-
52
- // Public data — no auth required
53
- const alliances = await client.alliance.getAlliances();
54
- const character = await client.characters.getCharacterPublicInfo(1689391488);
55
- const system = await client.universe.getSystemById(30000142);
56
- const prices = await client.market.getMarketPrices();
57
-
58
- // Authenticated data — token read from ESI_ACCESS_TOKEN env var
59
- const authedClient = new EsiClient();
60
- const assets = await authedClient.assets.getCharacterAssets(characterId);
61
- const wallet = await authedClient.wallet.getCharacterWallet(characterId);
62
-
63
- // Clean up when done
64
- await client.shutdown();
65
- ```
66
-
67
- ## Configuration
68
-
69
- ```typescript
70
- const client = new EsiClient({
71
- clientId: 'my-app', // User-Agent identifier (default: 'esi-client')
72
- accessToken: 'your-token', // EVE SSO token for authenticated endpoints
73
- baseUrl: 'https://esi.evetech.net', // ESI base URL (default)
74
- onTokenRefresh: async () => newToken, // Auto-refresh on 401 (optional)
75
- timeout: 30000, // Request timeout in ms (default: 30000)
76
- retryAttempts: 3, // Retry count (default: 3)
77
- enableETagCache: true, // ETag caching (default: true)
78
- etagCacheConfig: {
79
- maxEntries: 1000, // Max cached responses (default: 1000)
80
- defaultTtl: 300000, // Fallback TTL in ms (default: 5 min)
81
- cleanupInterval: 60000, // Expired entry cleanup interval (default: 1 min)
82
- },
83
- });
84
- ```
85
-
86
- The access token can be updated at runtime:
87
-
88
- ```typescript
89
- client.setAccessToken('new-token');
90
- ```
91
-
92
- ## Authentication
93
-
94
- Many ESI endpoints require an EVE SSO access token. There are three ways to provide one:
95
-
96
- ### 1. Environment variable (recommended)
97
-
98
- Set `ESI_ACCESS_TOKEN` in your environment or a `.env` file. The client reads it automatically — no token in source code.
99
-
100
- ```bash
101
- # Copy the example and fill in your token
102
- cp .env.example .env
103
- ```
104
-
105
- ```env
106
- ESI_ACCESS_TOKEN=your-eve-sso-access-token
107
- ESI_CLIENT_ID=my-app-name
108
- ```
109
-
110
- If you use a `.env` loader like [dotenv](https://www.npmjs.com/package/dotenv), load it before creating the client:
111
-
112
- ```typescript
113
- import 'dotenv/config';
114
- import { EsiClient } from '@lgriffin/esi.ts';
115
-
116
- const client = new EsiClient();
117
- // Token is picked up from process.env.ESI_ACCESS_TOKEN
118
- ```
119
-
120
- ### 2. Constructor parameter
121
-
122
- Pass the token directly (useful for apps that manage tokens themselves):
123
-
124
- ```typescript
125
- const client = new EsiClient({ accessToken: token });
126
- ```
127
-
128
- ### 3. Runtime update
129
-
130
- Set or refresh the token after construction:
131
-
132
- ```typescript
133
- client.setAccessToken(newToken);
134
- ```
135
-
136
- ### Getting an EVE SSO token
137
-
138
- 1. Register an application at [EVE Developers](https://developers.eveonline.com/)
139
- 2. Set a callback URL and select the ESI scopes your app needs
140
- 3. Implement the [OAuth2 flow](https://docs.esi.evetech.net/docs/sso/) to obtain an access token
141
- 4. Access tokens expire — use the refresh token to get new ones
142
-
143
- ### Automatic Token Refresh
144
-
145
- 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:
146
-
147
- ```typescript
148
- const client = new EsiClient({
149
- accessToken: initialToken,
150
- onTokenRefresh: async () => {
151
- const response = await fetch('https://login.eveonline.com/v2/oauth/token', {
152
- method: 'POST',
153
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
154
- body: new URLSearchParams({
155
- grant_type: 'refresh_token',
156
- refresh_token: myRefreshToken,
157
- client_id: myClientId,
158
- }),
159
- });
160
- const { access_token } = await response.json();
161
- return access_token;
162
- },
163
- });
164
-
165
- // Requests now auto-refresh on 401 — no manual token management needed
166
- const location = await client.location.getCharacterLocation(characterId);
167
- ```
168
-
169
- The token provider can also be set or changed at runtime:
170
-
171
- ```typescript
172
- client.setTokenProvider(myRefreshFunction);
173
- client.setTokenProvider(undefined); // disable auto-refresh
174
- ```
175
-
176
- Key behaviors:
177
-
178
- - Only retries **once** per request — if the refreshed token also gets a 401, the error is thrown
179
- - **Concurrent coalescing** — if multiple requests hit 401 simultaneously, only one refresh call is made
180
- - If the refresh callback throws (e.g., refresh token revoked), a `TOKEN_REFRESH_FAILED` error is raised
181
- - Without a token provider, 401 errors throw immediately as before
182
-
183
- ### Environment variables reference
184
-
185
- | Variable | Description | Default |
186
- | ------------------ | -------------------------------------------- | ------------------------- |
187
- | `ESI_ACCESS_TOKEN` | EVE SSO access token | none |
188
- | `ESI_CLIENT_ID` | User-Agent identifier | `esi-client` |
189
- | `ESI_BASE_URL` | ESI API base URL | `https://esi.evetech.net` |
190
- | `ESI_LOG_LEVEL` | Log level (`error`, `warn`, `info`, `debug`) | `warn` |
191
-
192
- ## Available APIs
193
-
194
- All clients are accessed as properties on the `EsiClient` instance. Authenticated endpoints require an access token.
195
-
196
- | Client | Property | Auth | Examples |
197
- | -------------- | ---------------------- | ---- | -------------------------------------------------------- |
198
- | Alliance | `client.alliance` | Some | `getAlliances()`, `getAllianceById(id)` |
199
- | Assets | `client.assets` | Yes | `getCharacterAssets(id)` |
200
- | Calendar | `client.calendar` | Yes | `getCharacterCalendar(id)` |
201
- | Characters | `client.characters` | Some | `getCharacterPublicInfo(id)`, `getCharacterPortrait(id)` |
202
- | Clones | `client.clones` | Yes | `getCharacterClones(id)` |
203
- | Contacts | `client.contacts` | Yes | `getCharacterContacts(id)` |
204
- | Contracts | `client.contracts` | Yes | `getCharacterContracts(id)` |
205
- | Corporations | `client.corporations` | Some | `getCorporationInfo(id)`, `getCorporationMembers(id)` |
206
- | Dogma | `client.dogma` | No | `getDogmaAttributes()`, `getDogmaEffects()` |
207
- | Factions | `client.factions` | Some | `getFactionWarStats()` |
208
- | Fittings | `client.fittings` | Yes | `getFittings(id)`, `createFitting(id, body)` |
209
- | Fleets | `client.fleets` | Yes | `getFleet(id)`, `getFleetMembers(id)` |
210
- | Incursions | `client.incursions` | No | `getIncursions()` |
211
- | Industry | `client.industry` | Some | `getCharacterIndustryJobs(id)` |
212
- | Insurance | `client.insurance` | No | `getInsurancePrices()` |
213
- | Killmails | `client.killmails` | Some | `getKillmail(id, hash)` |
214
- | Location | `client.location` | Yes | `getCharacterLocation(id)` |
215
- | Loyalty | `client.loyalty` | Yes | `getCharacterLoyaltyPoints(id)` |
216
- | Mail | `client.mail` | Yes | `getCharacterMail(id)` |
217
- | Market | `client.market` | Some | `getMarketPrices()`, `getMarketOrders(regionId)` |
218
- | PI | `client.pi` | Yes | `getCharacterPlanets(id)` |
219
- | Route | `client.route` | No | `getRoute(origin, destination)` |
220
- | Search | `client.search` | Some | `search(characterId, query)` |
221
- | Skills | `client.skills` | Yes | `getCharacterSkills(id)` |
222
- | Sovereignty | `client.sovereignty` | No | `getSovereigntyMap()` |
223
- | Status | `client.status` | No | `getStatus()` |
224
- | UI | `client.ui` | Yes | `setWaypoint(id)` |
225
- | Universe | `client.universe` | Some | `getSystemById(id)`, `getTypeById(id)` |
226
- | Wallet | `client.wallet` | Yes | `getCharacterWallet(id)` |
227
- | Wars | `client.wars` | No | `getWars()`, `getWarById(id)` |
228
- | Freelance Jobs | `client.freelanceJobs` | Some | `getFreelanceJobs()`, `getFreelanceJobById(id)` |
229
- | Meta | `client.meta` | No | `getOpenApiJson()`, `getOpenApiYaml()` |
230
-
231
- ## Caching
232
-
233
- ETag caching is enabled by default. The client automatically:
234
-
235
- 1. Stores ETag and response data on GET requests
236
- 2. Sends `If-None-Match` on subsequent requests
237
- 3. Returns cached data on `304 Not Modified`
238
- 4. Parses `Cache-Control: max-age` from ESI for per-endpoint TTL
239
- 5. Serves stale cached data when ESI returns 5xx errors
240
- 6. Invalidates related GET caches when POST/PUT/DELETE requests are made
241
-
242
- ```typescript
243
- // Cache stats
244
- const stats = client.getCacheStats();
245
- console.log(`${stats.totalEntries}/${stats.maxEntries} entries cached`);
246
-
247
- // Manual cache operations
248
- client.clearCache();
249
- client.updateCacheConfig({ maxEntries: 2000 });
250
-
251
- // Disable caching entirely
252
- const uncachedClient = new EsiClient({ enableETagCache: false });
253
- ```
254
-
255
- ## Cursor-based Pagination
256
-
257
- Newer ESI routes (Freelance Jobs, and future routes) use cursor-based pagination with opaque `before`/`after` tokens in the response body. See the [ESI blog post](https://developers.eveonline.com/blog/changing-pagination-turning-a-new-page) for background.
258
-
259
- ```typescript
260
- import { EsiClient, fetchAllCursorPages } from '@lgriffin/esi.ts';
261
-
262
- const client = new EsiClient();
263
-
264
- // Fetch first page — returns { cursor: { before, after }, freelance_jobs: [...] }
265
- const page = await client.freelanceJobs.getFreelanceJobs();
266
- console.log(page.freelance_jobs); // job records
267
- console.log(page.cursor.after); // opaque token for next page
268
-
269
- // Fetch next page using the cursor
270
- const nextPage = await client.freelanceJobs.getFreelanceJobs(
271
- undefined,
272
- page.cursor.after,
273
- );
274
-
275
- // Auto-fetch all pages in one call
276
- const allJobs = await fetchAllCursorPages(
277
- (before, after) => client.freelanceJobs.getFreelanceJobs(before, after),
278
- (response) => response.freelance_jobs,
279
- (response) => response.cursor,
280
- );
281
-
282
- // Authenticated endpoints — character/corporation freelance jobs
283
- const authedClient = new EsiClient({ accessToken: 'your-token' });
284
- const myJobs =
285
- await authedClient.freelanceJobs.getCharacterFreelanceJobs(characterId);
286
- const corpJobs =
287
- await authedClient.freelanceJobs.getCorporationFreelanceJobs(corporationId);
288
- ```
289
-
290
- **Polling for changes** — cursor tokens persist across sessions, so you can save the last `after` token and poll later to get only records that changed:
291
-
292
- ```typescript
293
- // After initial scan, save the final cursor
294
- let savedCursor = lastPage.cursor.after;
295
-
296
- // Later: check for updates (hours, days, or weeks later)
297
- const updates = await client.freelanceJobs.getFreelanceJobs(
298
- undefined,
299
- savedCursor,
300
- );
301
- if (updates.freelance_jobs.length > 0) {
302
- // Process changed records — duplicates are expected for modified records
303
- savedCursor = updates.cursor.after;
304
- }
305
- ```
306
-
307
- Key points:
308
-
309
- - Cursor tokens are **opaque strings** — never parse or validate them
310
- - An **empty result array** signals the end of the dataset (not a short page)
311
- - **Duplicates across pages** are expected when records are modified between requests
312
- - Existing offset-based routes (`getMarketOrders`, etc.) are unchanged
313
-
314
- ## Error Handling
315
-
316
- API errors throw `EsiError` with `statusCode`, `message`, and `url` properties:
317
-
318
- ```typescript
319
- import { EsiError } from '@lgriffin/esi.ts';
320
-
321
- try {
322
- const alliance = await client.alliance.getAllianceById(99999999);
323
- console.log('Alliance:', alliance.name);
324
- } catch (err) {
325
- if (err instanceof EsiError) {
326
- console.log(`ESI error ${err.statusCode}: ${err.message}`);
327
- // e.g. "ESI error 404: Resource not found"
328
- } else {
329
- console.error('Network or parse error:', err);
330
- }
331
- }
332
- ```
333
-
334
- - **204 No Content** — returns `undefined` (valid for DELETE/POST actions)
335
- - **304 Not Modified** — handled internally, returns cached data
336
- - **4xx/5xx** — throws `EsiError`
337
- - **5xx with cache** — returns stale cached data instead of throwing
338
-
339
- ## Lightweight Clients
340
-
341
- If you only need a subset of APIs, use `CustomEsiClient` or `EsiClientBuilder` to load only what you need:
342
-
343
- ```typescript
344
- import { EsiClientBuilder } from '@lgriffin/esi.ts';
345
-
346
- const client = new EsiClientBuilder()
347
- .addClients(['market', 'universe', 'characters'])
348
- .withClientId('my-trading-bot')
349
- .withAccessToken('your-token')
350
- .build();
351
-
352
- const prices = await client.market?.getMarketPrices();
353
- const system = await client.universe?.getSystemById(30000142);
354
- ```
355
-
356
- Or create standalone single-API clients:
357
-
358
- ```typescript
359
- import { EsiApiFactory } from '@lgriffin/esi.ts';
360
-
361
- const marketClient = EsiApiFactory.createMarketClient({
362
- clientId: 'price-checker',
363
- });
364
- const prices = await marketClient.getMarketPrices();
365
- ```
366
-
367
- ## Examples
368
-
369
- Runnable examples are in the `examples/` directory.
370
-
371
- ### Public Endpoints (no auth needed)
372
-
373
- ```bash
374
- npm run example:status # Server status — quickest smoke test
375
- npm run example:character # Character public info, portrait, corporation
376
- npm run example:universe # Solar system, constellation, region, station
377
- npm run example:market # Average prices + Tritanium price history
378
- npm run example:alliance # Alliance info + member corporations
379
- npm run example:route # Jita-to-Amarr route with system names
380
- npm run example:wars # Recent wars with aggressor/defender details
381
- npm run example:sovereignty # Nullsec sovereignty map + active campaigns
382
- npm run example:industry # Industry facilities, cost indices, insurance
383
- npm run example:incursions # Active incursions + faction warfare stats
384
- npm run example:dogma # Item type details + dogma attributes
385
- npm run example:contracts # Public region contracts + auction bids/items
386
- npm run example:rate-limiting # Rate limiter & pagination demonstration
387
- npm run example:cursor-pagination # Freelance Jobs with cursor pagination
388
- npm run example:token-refresh # Automatic token refresh on 401
389
- ```
390
-
391
- ### Authenticated Endpoints (require ESI_ACCESS_TOKEN)
392
-
393
- These examples require an EVE SSO token with the listed scopes. Set `ESI_ACCESS_TOKEN` in your environment or `.env` file.
394
-
395
- ```bash
396
- npm run example # Full character profile assembly
397
- npm run example:wallet # Wallet balance, journal, transactions (esi-wallet.read_character_wallet.v1)
398
- npm run example:skills # Trained skills, queue, attributes (esi-skills.read_skills.v1, esi-skills.read_skillqueue.v1)
399
- npm run example:assets # Asset inventory with bulk name lookup (esi-assets.read_assets.v1)
400
- npm run example:killmails # Recent killmails + full details (esi-killmails.read_killmails.v1)
401
- npm run example:fleet # Fleet info, members, wing/squad structure (esi-fleets.read_fleet.v1)
402
- npm run example:mail # Inbox headers, labels, mailing lists (esi-mail.read_mail.v1)
403
- npm run example:location # Current system, online status, ship (esi-location.read_location.v1)
404
- npm run example:fittings # Saved fittings + clone state + implants (esi-fittings.read_fittings.v1, esi-clones.read_clones.v1)
405
- npm run example:contacts # Contact list with standings + labels (esi-characters.read_contacts.v1)
406
- ```
407
-
408
- ### Parallel Requests
409
-
410
- ```typescript
411
- const [character, portrait, corp] = await Promise.all([
412
- client.characters.getCharacterPublicInfo(characterId),
413
- client.characters.getCharacterPortrait(characterId),
414
- client.corporations.getCorporationInfo(corporationId),
415
- ]);
416
-
417
- console.log(`${character.name} [${corp.ticker}]`);
418
- ```
419
-
420
- ### Market Analysis
421
-
422
- ```typescript
423
- const [orders, history] = await Promise.all([
424
- client.market.getMarketOrders(regionId),
425
- client.market.getMarketHistory(regionId, typeId),
426
- ]);
427
-
428
- const buyOrders = orders.filter((o) => o.is_buy_order);
429
- const sellOrders = orders.filter((o) => !o.is_buy_order);
430
-
431
- console.log(`Best buy: ${Math.max(...buyOrders.map((o) => o.price))}`);
432
- console.log(`Best sell: ${Math.min(...sellOrders.map((o) => o.price))}`);
433
- ```
434
-
435
- ## Resource Management
436
-
437
- Always call `shutdown()` when you're done to clean up cache timers:
438
-
439
- ```typescript
440
- const client = new EsiClient();
441
- try {
442
- const status = await client.status.getStatus();
443
- console.log(status.server_version);
444
- } finally {
445
- await client.shutdown();
446
- }
447
- ```
448
-
449
- ## Development
450
-
451
- ### Prerequisites
452
-
453
- - Node.js 18+
454
- - npm
455
-
456
- ### Code Quality Tools
457
-
458
- The project uses a comprehensive suite of static analysis and code quality tools:
459
-
460
- | Tool | Purpose | Command |
461
- | ------------------------------------------------------------------------------------ | ------------------------------------------------------- | ------------------------------ |
462
- | [ESLint](https://eslint.org/) | Linting with TypeScript, security, and code smell rules | `npm run lint` |
463
- | [Prettier](https://prettier.io/) | Code formatting | `npm run format:check` |
464
- | [knip](https://knip.dev/) | Dead code and unused export detection | `npm run knip` |
465
- | [eslint-plugin-security](https://github.com/eslint-community/eslint-plugin-security) | Security anti-pattern detection | Integrated into `npm run lint` |
466
- | [eslint-plugin-sonarjs](https://github.com/SonarSource/eslint-plugin-sonarjs) | Cognitive complexity and code smell detection | Integrated into `npm run lint` |
467
- | [husky](https://typicode.github.io/husky/) | Git pre-commit hooks | Automatic on commit |
468
- | [lint-staged](https://github.com/lint-staged/lint-staged) | Run linters on staged files only | Automatic on commit |
469
-
470
- ### Available Scripts
471
-
472
- ```bash
473
- # Development
474
- npm run build # Compile TypeScript
475
- npm run lint # Run ESLint
476
- npm run lint:fix # Run ESLint with auto-fix
477
- npm run format # Format code with Prettier
478
- npm run format:check # Check formatting without modifying
479
-
480
- # Testing
481
- npm test # Unit tests
482
- npm run test:all # Unit + improved + BDD tests
483
- npm run coverage # Tests with coverage report (thresholds enforced)
484
- npm run bdd # BDD scenario tests
485
-
486
- # Static Analysis
487
- npm run knip # Detect dead code and unused exports
488
- npm run validate:esi # Validate endpoints against live ESI swagger spec
489
- npm run validate # Run all checks: lint, format, build, coverage, knip
490
-
491
- # Documentation
492
- npm run docs # Generate TypeDoc API documentation
493
- npm run docs:serve # Serve docs locally on port 8080
494
- ```
495
-
496
- ### ESI Endpoint Validation
497
-
498
- To verify that the codebase endpoint definitions match the live ESI swagger spec:
499
-
500
- ```bash
501
- npm run validate:esi
502
- ```
503
-
504
- This fetches `https://esi.evetech.net/latest/swagger.json` and reports:
505
-
506
- - Endpoints in the codebase that are no longer in the ESI spec
507
- - Endpoints in the ESI spec that the codebase doesn't cover
508
- - HTTP method mismatches between codebase and spec
509
-
510
- ### Pre-commit Hooks
511
-
512
- 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`.
513
-
514
- ### CI/CD
515
-
516
- Every pull request runs the full validation suite:
517
-
518
- - ESLint (with security and sonarjs plugins)
519
- - Prettier formatting check
520
- - TypeScript compilation
521
- - Unit tests across Node.js 18, 20, and 22
522
- - BDD scenario tests
523
- - Coverage threshold enforcement (branches: 50%, functions: 50%, lines: 65%, statements: 65%)
524
- - Dead code detection via knip
525
- - npm security audit
526
-
527
- See [.github/workflows/README.md](.github/workflows/README.md) for full workflow details.
528
-
529
- ## Testing
530
-
531
- ```bash
532
- npm test # Unit + integration tests (73 suites, 577 tests)
533
- npm run coverage # Tests with coverage report (thresholds enforced)
534
- npm run bdd # BDD scenario tests only
535
- ```
536
-
537
- To verify against the live ESI API:
538
-
539
- ```bash
540
- npm run example:status # Confirms ESI connectivity and server status
541
- ```
542
-
543
- ## Contributing
544
-
545
- 1. Fork the repository
546
- 2. Create a feature branch
547
- 3. Write tests for your changes
548
- 4. Run `npm run validate` to check everything passes
549
- 5. Open a Pull Request
550
-
551
- ## License
552
-
553
- GPL-3.0-or-later - see the [LICENSE](LICENSE) file for details.
554
-
555
- ---
556
-
557
- **o7**
1
+ # ESI.ts
2
+
3
+ [![npm version](https://badge.fury.io/js/%40lgriffin%2Fesi.ts.svg)](https://badge.fury.io/js/%40lgriffin%2Fesi.ts)
4
+ [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-6.0%2B-blue)](https://www.typescriptlang.org/)
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
+ [![PR Validation](https://github.com/lgriffin/ESI.ts/actions/workflows/pr-validation.yml/badge.svg)](https://github.com/lgriffin/ESI.ts/actions/workflows/pr-validation.yml)
8
+ [![Coverage](https://img.shields.io/badge/coverage-65%25%2B-brightgreen)](https://github.com/lgriffin/ESI.ts)
9
+ [![npm downloads](https://img.shields.io/npm/dm/%40lgriffin/esi.ts)](https://www.npmjs.com/package/@lgriffin/esi.ts)
10
+
11
+ A type-safe TypeScript client for the [EVE Online ESI API](https://esi.evetech.net/).
12
+
13
+ - Typed responses for all endpoints
14
+ - ETag caching with Cache-Control TTL, stale-on-error, and write invalidation
15
+ - Automatic offset-based pagination and cursor-based pagination support
16
+ - Rate limiting with header-driven backoff
17
+ - Automatic token refresh with 401 retry and concurrent coalescing
18
+ - 35 domain clients covering the full ESI surface
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install @lgriffin/esi.ts
24
+ ```
25
+
26
+ ### Building from Source
27
+
28
+ ```bash
29
+ git clone https://github.com/lgriffin/ESI.ts.git
30
+ cd ESI.ts
31
+ npm install # installs dependencies and compiles (via the prepare script)
32
+ ```
33
+
34
+ If you've already installed and just need to recompile:
35
+
36
+ ```bash
37
+ npm run build
38
+ ```
39
+
40
+ Verify everything works:
41
+
42
+ ```bash
43
+ npm run example:status # quick smoke test — checks ESI is reachable
44
+ npm test # run the full test suite
45
+ ```
46
+
47
+ ## Quick Start
48
+
49
+ ```typescript
50
+ import { EsiClient } from '@lgriffin/esi.ts';
51
+
52
+ const client = new EsiClient();
53
+
54
+ // Public data — no auth required
55
+ const alliances = await client.alliance.getAlliances();
56
+ const character = await client.characters.getCharacterPublicInfo(1689391488);
57
+ const system = await client.universe.getSystemById(30000142);
58
+ const prices = await client.market.getMarketPrices();
59
+
60
+ // Authenticated data — token read from ESI_ACCESS_TOKEN env var
61
+ const authedClient = new EsiClient();
62
+ const assets = await authedClient.assets.getCharacterAssets(characterId);
63
+ const wallet = await authedClient.wallet.getCharacterWallet(characterId);
64
+
65
+ // Clean up when done
66
+ await client.shutdown();
67
+ ```
68
+
69
+ ## Configuration
70
+
71
+ ```typescript
72
+ const client = new EsiClient({
73
+ clientId: 'my-app', // User-Agent identifier (default: 'esi-client')
74
+ accessToken: 'your-token', // EVE SSO token for authenticated endpoints
75
+ baseUrl: 'https://esi.evetech.net', // ESI base URL (default)
76
+ onTokenRefresh: async () => newToken, // Auto-refresh on 401 (optional)
77
+ timeout: 30000, // Request timeout in ms (default: 30000)
78
+ retryAttempts: 3, // Retry count (default: 3)
79
+ enableETagCache: true, // ETag caching (default: true)
80
+ etagCacheConfig: {
81
+ maxEntries: 1000, // Max cached responses (default: 1000)
82
+ defaultTtl: 300000, // Fallback TTL in ms (default: 5 min)
83
+ cleanupInterval: 60000, // Expired entry cleanup interval (default: 1 min)
84
+ },
85
+ });
86
+ ```
87
+
88
+ The access token can be updated at runtime:
89
+
90
+ ```typescript
91
+ client.setAccessToken('new-token');
92
+ ```
93
+
94
+ ## Authentication
95
+
96
+ Many ESI endpoints require an EVE SSO access token. There are three ways to provide one:
97
+
98
+ ### 1. Environment variable (recommended)
99
+
100
+ Set `ESI_ACCESS_TOKEN` in your environment or a `.env` file. The client reads it automatically — no token in source code.
101
+
102
+ ```bash
103
+ # Copy the example and fill in your token
104
+ cp .env.example .env
105
+ ```
106
+
107
+ ```env
108
+ ESI_ACCESS_TOKEN=your-eve-sso-access-token
109
+ ESI_CLIENT_ID=my-app-name
110
+ ```
111
+
112
+ If you use a `.env` loader like [dotenv](https://www.npmjs.com/package/dotenv), load it before creating the client:
113
+
114
+ ```typescript
115
+ import 'dotenv/config';
116
+ import { EsiClient } from '@lgriffin/esi.ts';
117
+
118
+ const client = new EsiClient();
119
+ // Token is picked up from process.env.ESI_ACCESS_TOKEN
120
+ ```
121
+
122
+ ### 2. Constructor parameter
123
+
124
+ Pass the token directly (useful for apps that manage tokens themselves):
125
+
126
+ ```typescript
127
+ const client = new EsiClient({ accessToken: token });
128
+ ```
129
+
130
+ ### 3. Runtime update
131
+
132
+ Set or refresh the token after construction:
133
+
134
+ ```typescript
135
+ client.setAccessToken(newToken);
136
+ ```
137
+
138
+ ### Getting an EVE SSO token
139
+
140
+ 1. Register an application at [EVE Developers](https://developers.eveonline.com/)
141
+ 2. Set a callback URL and select the ESI scopes your app needs
142
+ 3. Implement the [OAuth2 flow](https://docs.esi.evetech.net/docs/sso/) to obtain an access token
143
+ 4. Access tokens expire — use the refresh token to get new ones
144
+
145
+ ### Automatic Token Refresh
146
+
147
+ 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:
148
+
149
+ ```typescript
150
+ const client = new EsiClient({
151
+ accessToken: initialToken,
152
+ onTokenRefresh: async () => {
153
+ const response = await fetch('https://login.eveonline.com/v2/oauth/token', {
154
+ method: 'POST',
155
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
156
+ body: new URLSearchParams({
157
+ grant_type: 'refresh_token',
158
+ refresh_token: myRefreshToken,
159
+ client_id: myClientId,
160
+ }),
161
+ });
162
+ const { access_token } = await response.json();
163
+ return access_token;
164
+ },
165
+ });
166
+
167
+ // Requests now auto-refresh on 401 — no manual token management needed
168
+ const location = await client.location.getCharacterLocation(characterId);
169
+ ```
170
+
171
+ The token provider can also be set or changed at runtime:
172
+
173
+ ```typescript
174
+ client.setTokenProvider(myRefreshFunction);
175
+ client.setTokenProvider(undefined); // disable auto-refresh
176
+ ```
177
+
178
+ Key behaviors:
179
+
180
+ - Only retries **once** per request — if the refreshed token also gets a 401, the error is thrown
181
+ - **Concurrent coalescing** — if multiple requests hit 401 simultaneously, only one refresh call is made
182
+ - If the refresh callback throws (e.g., refresh token revoked), a `TOKEN_REFRESH_FAILED` error is raised
183
+ - Without a token provider, 401 errors throw immediately as before
184
+
185
+ ### Environment variables reference
186
+
187
+ | Variable | Description | Default |
188
+ | ------------------ | -------------------------------------------- | ------------------------- |
189
+ | `ESI_ACCESS_TOKEN` | EVE SSO access token | none |
190
+ | `ESI_CLIENT_ID` | User-Agent identifier | `esi-client` |
191
+ | `ESI_BASE_URL` | ESI API base URL | `https://esi.evetech.net` |
192
+ | `ESI_LOG_LEVEL` | Log level (`error`, `warn`, `info`, `debug`) | `warn` |
193
+
194
+ ## Available APIs
195
+
196
+ All clients are accessed as properties on the `EsiClient` instance. Authenticated endpoints require an access token.
197
+
198
+ | Client | Property | Auth | Examples |
199
+ | -------------- | ---------------------- | ---- | -------------------------------------------------------- |
200
+ | Alliance | `client.alliance` | Some | `getAlliances()`, `getAllianceById(id)` |
201
+ | Assets | `client.assets` | Yes | `getCharacterAssets(id)` |
202
+ | Calendar | `client.calendar` | Yes | `getCharacterCalendar(id)` |
203
+ | Characters | `client.characters` | Some | `getCharacterPublicInfo(id)`, `getCharacterPortrait(id)` |
204
+ | Clones | `client.clones` | Yes | `getCharacterClones(id)` |
205
+ | Contacts | `client.contacts` | Yes | `getCharacterContacts(id)` |
206
+ | Contracts | `client.contracts` | Yes | `getCharacterContracts(id)` |
207
+ | Corporations | `client.corporations` | Some | `getCorporationInfo(id)`, `getCorporationMembers(id)` |
208
+ | Dogma | `client.dogma` | No | `getDogmaAttributes()`, `getDogmaEffects()` |
209
+ | Factions | `client.factions` | Some | `getFactionWarStats()` |
210
+ | Fittings | `client.fittings` | Yes | `getFittings(id)`, `createFitting(id, body)` |
211
+ | Fleets | `client.fleets` | Yes | `getFleet(id)`, `getFleetMembers(id)` |
212
+ | Incursions | `client.incursions` | No | `getIncursions()` |
213
+ | Industry | `client.industry` | Some | `getCharacterIndustryJobs(id)` |
214
+ | Insurance | `client.insurance` | No | `getInsurancePrices()` |
215
+ | Killmails | `client.killmails` | Some | `getKillmail(id, hash)` |
216
+ | Location | `client.location` | Yes | `getCharacterLocation(id)` |
217
+ | Loyalty | `client.loyalty` | Yes | `getCharacterLoyaltyPoints(id)` |
218
+ | Mail | `client.mail` | Yes | `getCharacterMail(id)` |
219
+ | Market | `client.market` | Some | `getMarketPrices()`, `getMarketOrders(regionId)` |
220
+ | PI | `client.pi` | Yes | `getCharacterPlanets(id)` |
221
+ | Route | `client.route` | No | `getRoute(origin, destination)` |
222
+ | Search | `client.search` | Some | `search(characterId, query)` |
223
+ | Skills | `client.skills` | Yes | `getCharacterSkills(id)` |
224
+ | Sovereignty | `client.sovereignty` | No | `getSovereigntySystems()`, `getSovereigntyMap()` |
225
+ | Skyhooks | `client.skyhooks` | No | `getSovereigntyHubs()`, `getRaidableSkyhooks()` |
226
+ | Mercenary | `client.mercenary` | No | `getMercenaryDens()`, `getMercenaryTacticalOperations()` |
227
+ | Access Lists | `client.accessLists` | Yes | `getAccessList(id)` |
228
+ | Status | `client.status` | No | `getStatus()` |
229
+ | UI | `client.ui` | Yes | `setWaypoint(id)` |
230
+ | Universe | `client.universe` | Some | `getSystemById(id)`, `getTypeById(id)` |
231
+ | Wallet | `client.wallet` | Yes | `getCharacterWallet(id)` |
232
+ | Wars | `client.wars` | No | `getWars()`, `getWarById(id)` |
233
+ | Freelance Jobs | `client.freelanceJobs` | Some | `getFreelanceJobs()`, `getFreelanceJobById(id)` |
234
+ | Meta | `client.meta` | No | `getOpenApiJson()`, `getOpenApiYaml()` |
235
+
236
+ ## Caching
237
+
238
+ ETag caching is enabled by default. The client automatically:
239
+
240
+ 1. Stores ETag and response data on GET requests
241
+ 2. Sends `If-None-Match` on subsequent requests
242
+ 3. Returns cached data on `304 Not Modified`
243
+ 4. Parses `Cache-Control: max-age` from ESI for per-endpoint TTL
244
+ 5. Serves stale cached data when ESI returns 5xx errors
245
+ 6. Invalidates related GET caches when POST/PUT/DELETE requests are made
246
+
247
+ ```typescript
248
+ // Cache stats
249
+ const stats = client.getCacheStats();
250
+ console.log(`${stats.totalEntries}/${stats.maxEntries} entries cached`);
251
+
252
+ // Manual cache operations
253
+ client.clearCache();
254
+ client.updateCacheConfig({ maxEntries: 2000 });
255
+
256
+ // Disable caching entirely
257
+ const uncachedClient = new EsiClient({ enableETagCache: false });
258
+ ```
259
+
260
+ ## Cursor-based Pagination
261
+
262
+ Newer ESI routes (Freelance Jobs, and future routes) use cursor-based pagination with opaque `before`/`after` tokens in the response body. See the [ESI blog post](https://developers.eveonline.com/blog/changing-pagination-turning-a-new-page) for background.
263
+
264
+ ```typescript
265
+ import { EsiClient, fetchAllCursorPages } from '@lgriffin/esi.ts';
266
+
267
+ const client = new EsiClient();
268
+
269
+ // Fetch first page — returns { cursor: { before, after }, freelance_jobs: [...] }
270
+ const page = await client.freelanceJobs.getFreelanceJobs();
271
+ console.log(page.freelance_jobs); // job records
272
+ console.log(page.cursor.after); // opaque token for next page
273
+
274
+ // Fetch next page using the cursor
275
+ const nextPage = await client.freelanceJobs.getFreelanceJobs(
276
+ undefined,
277
+ page.cursor.after,
278
+ );
279
+
280
+ // Auto-fetch all pages in one call
281
+ const allJobs = await fetchAllCursorPages(
282
+ (before, after) => client.freelanceJobs.getFreelanceJobs(before, after),
283
+ (response) => response.freelance_jobs,
284
+ (response) => response.cursor,
285
+ );
286
+
287
+ // Authenticated endpoints — character/corporation freelance jobs
288
+ const authedClient = new EsiClient({ accessToken: 'your-token' });
289
+ const myJobs =
290
+ await authedClient.freelanceJobs.getCharacterFreelanceJobs(characterId);
291
+ const corpJobs =
292
+ await authedClient.freelanceJobs.getCorporationFreelanceJobs(corporationId);
293
+ ```
294
+
295
+ **Polling for changes** — cursor tokens persist across sessions, so you can save the last `after` token and poll later to get only records that changed:
296
+
297
+ ```typescript
298
+ // After initial scan, save the final cursor
299
+ let savedCursor = lastPage.cursor.after;
300
+
301
+ // Later: check for updates (hours, days, or weeks later)
302
+ const updates = await client.freelanceJobs.getFreelanceJobs(
303
+ undefined,
304
+ savedCursor,
305
+ );
306
+ if (updates.freelance_jobs.length > 0) {
307
+ // Process changed records — duplicates are expected for modified records
308
+ savedCursor = updates.cursor.after;
309
+ }
310
+ ```
311
+
312
+ Key points:
313
+
314
+ - Cursor tokens are **opaque strings** — never parse or validate them
315
+ - An **empty result array** signals the end of the dataset (not a short page)
316
+ - **Duplicates across pages** are expected when records are modified between requests
317
+ - Existing offset-based routes (`getMarketOrders`, etc.) are unchanged
318
+
319
+ ## Error Handling
320
+
321
+ API errors throw `EsiError` with `statusCode`, `message`, and `url` properties:
322
+
323
+ ```typescript
324
+ import { EsiError } from '@lgriffin/esi.ts';
325
+
326
+ try {
327
+ const alliance = await client.alliance.getAllianceById(99999999);
328
+ console.log('Alliance:', alliance.name);
329
+ } catch (err) {
330
+ if (err instanceof EsiError) {
331
+ console.log(`ESI error ${err.statusCode}: ${err.message}`);
332
+ // e.g. "ESI error 404: Resource not found"
333
+ } else {
334
+ console.error('Network or parse error:', err);
335
+ }
336
+ }
337
+ ```
338
+
339
+ - **204 No Content** — returns `undefined` (valid for DELETE/POST actions)
340
+ - **304 Not Modified** — handled internally, returns cached data
341
+ - **4xx/5xx** — throws `EsiError`
342
+ - **5xx with cache** — returns stale cached data instead of throwing
343
+
344
+ ## Lightweight Clients
345
+
346
+ If you only need a subset of APIs, use `CustomEsiClient` or `EsiClientBuilder` to load only what you need:
347
+
348
+ ```typescript
349
+ import { EsiClientBuilder } from '@lgriffin/esi.ts';
350
+
351
+ const client = new EsiClientBuilder()
352
+ .addClients(['market', 'universe', 'characters'])
353
+ .withClientId('my-trading-bot')
354
+ .withAccessToken('your-token')
355
+ .build();
356
+
357
+ const prices = await client.market?.getMarketPrices();
358
+ const system = await client.universe?.getSystemById(30000142);
359
+ ```
360
+
361
+ Or create standalone single-API clients:
362
+
363
+ ```typescript
364
+ import { EsiApiFactory } from '@lgriffin/esi.ts';
365
+
366
+ const marketClient = EsiApiFactory.createMarketClient({
367
+ clientId: 'price-checker',
368
+ });
369
+ const prices = await marketClient.getMarketPrices();
370
+ ```
371
+
372
+ ## Examples
373
+
374
+ Runnable examples are in the `examples/` directory.
375
+
376
+ ### Public Endpoints (no auth needed)
377
+
378
+ ```bash
379
+ npm run example:status # Server status — quickest smoke test
380
+ npm run example:character # Character public info, portrait, corporation
381
+ npm run example:universe # Solar system, constellation, region, station
382
+ npm run example:market # Average prices + Tritanium price history
383
+ npm run example:alliance # Alliance info + member corporations
384
+ npm run example:route # Jita-to-Amarr route with system names
385
+ npm run example:wars # Recent wars with aggressor/defender details
386
+ npm run example:sovereignty # Nullsec sovereignty map + active campaigns
387
+ npm run example:industry # Industry facilities, cost indices, insurance
388
+ npm run example:incursions # Active incursions + faction warfare stats
389
+ npm run example:dogma # Item type details + dogma attributes
390
+ npm run example:contracts # Public region contracts + auction bids/items
391
+ npm run example:rate-limiting # Rate limiter & pagination demonstration
392
+ npm run example:cursor-pagination # Freelance Jobs with cursor pagination
393
+ npm run example:token-refresh # Automatic token refresh on 401
394
+ ```
395
+
396
+ ### Authenticated Endpoints (require ESI_ACCESS_TOKEN)
397
+
398
+ These examples require an EVE SSO token with the listed scopes. Set `ESI_ACCESS_TOKEN` in your environment or `.env` file.
399
+
400
+ ```bash
401
+ npm run example # Full character profile assembly
402
+ npm run example:wallet # Wallet balance, journal, transactions (esi-wallet.read_character_wallet.v1)
403
+ npm run example:skills # Trained skills, queue, attributes (esi-skills.read_skills.v1, esi-skills.read_skillqueue.v1)
404
+ npm run example:assets # Asset inventory with bulk name lookup (esi-assets.read_assets.v1)
405
+ npm run example:killmails # Recent killmails + full details (esi-killmails.read_killmails.v1)
406
+ npm run example:fleet # Fleet info, members, wing/squad structure (esi-fleets.read_fleet.v1)
407
+ npm run example:mail # Inbox headers, labels, mailing lists (esi-mail.read_mail.v1)
408
+ npm run example:location # Current system, online status, ship (esi-location.read_location.v1)
409
+ npm run example:fittings # Saved fittings + clone state + implants (esi-fittings.read_fittings.v1, esi-clones.read_clones.v1)
410
+ npm run example:contacts # Contact list with standings + labels (esi-characters.read_contacts.v1)
411
+ ```
412
+
413
+ ### Parallel Requests
414
+
415
+ ```typescript
416
+ const [character, portrait, corp] = await Promise.all([
417
+ client.characters.getCharacterPublicInfo(characterId),
418
+ client.characters.getCharacterPortrait(characterId),
419
+ client.corporations.getCorporationInfo(corporationId),
420
+ ]);
421
+
422
+ console.log(`${character.name} [${corp.ticker}]`);
423
+ ```
424
+
425
+ ### Market Analysis
426
+
427
+ ```typescript
428
+ const [orders, history] = await Promise.all([
429
+ client.market.getMarketOrders(regionId),
430
+ client.market.getMarketHistory(regionId, typeId),
431
+ ]);
432
+
433
+ const buyOrders = orders.filter((o) => o.is_buy_order);
434
+ const sellOrders = orders.filter((o) => !o.is_buy_order);
435
+
436
+ console.log(`Best buy: ${Math.max(...buyOrders.map((o) => o.price))}`);
437
+ console.log(`Best sell: ${Math.min(...sellOrders.map((o) => o.price))}`);
438
+ ```
439
+
440
+ ## Resource Management
441
+
442
+ Always call `shutdown()` when you're done to clean up cache timers:
443
+
444
+ ```typescript
445
+ const client = new EsiClient();
446
+ try {
447
+ const status = await client.status.getStatus();
448
+ console.log(status.server_version);
449
+ } finally {
450
+ await client.shutdown();
451
+ }
452
+ ```
453
+
454
+ ## Development
455
+
456
+ ### Prerequisites
457
+
458
+ - Node.js 18+
459
+ - npm
460
+
461
+ ### Code Quality Tools
462
+
463
+ The project uses a comprehensive suite of static analysis and code quality tools:
464
+
465
+ | Tool | Purpose | Command |
466
+ | ------------------------------------------------------------------------------------ | ------------------------------------------------------- | ------------------------------ |
467
+ | [ESLint](https://eslint.org/) | Linting with TypeScript, security, and code smell rules | `npm run lint` |
468
+ | [Prettier](https://prettier.io/) | Code formatting | `npm run format:check` |
469
+ | [knip](https://knip.dev/) | Dead code and unused export detection | `npm run knip` |
470
+ | [eslint-plugin-security](https://github.com/eslint-community/eslint-plugin-security) | Security anti-pattern detection | Integrated into `npm run lint` |
471
+ | [eslint-plugin-sonarjs](https://github.com/SonarSource/eslint-plugin-sonarjs) | Cognitive complexity and code smell detection | Integrated into `npm run lint` |
472
+ | [husky](https://typicode.github.io/husky/) | Git pre-commit hooks | Automatic on commit |
473
+ | [lint-staged](https://github.com/lint-staged/lint-staged) | Run linters on staged files only | Automatic on commit |
474
+
475
+ ### Available Scripts
476
+
477
+ ```bash
478
+ # Development
479
+ npm run build # Compile TypeScript
480
+ npm run lint # Run ESLint
481
+ npm run lint:fix # Run ESLint with auto-fix
482
+ npm run format # Format code with Prettier
483
+ npm run format:check # Check formatting without modifying
484
+
485
+ # Testing
486
+ npm test # Unit tests
487
+ npm run test:all # Unit + improved + BDD tests
488
+ npm run coverage # Tests with coverage report (thresholds enforced)
489
+ npm run bdd # BDD scenario tests
490
+
491
+ # Static Analysis
492
+ npm run knip # Detect dead code and unused exports
493
+ npm run validate:esi # Validate endpoints against live ESI swagger spec
494
+ npm run validate # Run all checks: lint, format, build, coverage, knip
495
+
496
+ # Documentation
497
+ npm run docs # Generate TypeDoc API documentation
498
+ npm run docs:serve # Serve docs locally on port 8080
499
+ ```
500
+
501
+ ### ESI Endpoint Validation
502
+
503
+ To verify that the codebase endpoint definitions match the live ESI swagger spec:
504
+
505
+ ```bash
506
+ npm run validate:esi
507
+ ```
508
+
509
+ This fetches `https://esi.evetech.net/latest/swagger.json` and reports:
510
+
511
+ - Endpoints in the codebase that are no longer in the ESI spec
512
+ - Endpoints in the ESI spec that the codebase doesn't cover
513
+ - HTTP method mismatches between codebase and spec
514
+
515
+ ### Pre-commit Hooks
516
+
517
+ 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`.
518
+
519
+ ### CI/CD
520
+
521
+ Every pull request runs the full validation suite:
522
+
523
+ - ESLint (with security and sonarjs plugins)
524
+ - Prettier formatting check
525
+ - TypeScript compilation
526
+ - Unit tests across Node.js 18, 20, and 22
527
+ - BDD scenario tests
528
+ - Coverage threshold enforcement (branches: 50%, functions: 50%, lines: 65%, statements: 65%)
529
+ - Dead code detection via knip
530
+ - npm security audit
531
+
532
+ See [.github/workflows/README.md](.github/workflows/README.md) for full workflow details.
533
+
534
+ ## Testing
535
+
536
+ ```bash
537
+ npm test # Unit + integration tests (73 suites, 577 tests)
538
+ npm run coverage # Tests with coverage report (thresholds enforced)
539
+ npm run bdd # BDD scenario tests only
540
+ ```
541
+
542
+ To verify against the live ESI API:
543
+
544
+ ```bash
545
+ npm run example:status # Confirms ESI connectivity and server status
546
+ ```
547
+
548
+ ## Contributing
549
+
550
+ 1. Fork the repository
551
+ 2. Create a feature branch
552
+ 3. Write tests for your changes
553
+ 4. Run `npm run validate` to check everything passes
554
+ 5. Open a Pull Request
555
+
556
+ ## License
557
+
558
+ GPL-3.0-or-later - see the [LICENSE](LICENSE) file for details.
559
+
560
+ ---
561
+
562
+ **o7**