eve-esi-types 2.3.0 → 2.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/esi-types-util.md CHANGED
@@ -28,7 +28,11 @@ type TESIRequestFunctionSignature<ActualOpt> = <
28
28
  `IfParameterizedPath<EP, Opt>` if parameterized path then specify number type, otherwise will be `Opt` type.
29
29
 
30
30
  ```ts
31
- type IfParameterizedPath<EP, Opt> = EP extends `${string}/{${string}}/${string | ""}` ? number | number[]: Opt;
31
+ type IfParameterizedPath<EP, Opt> = EP extends `${string}/{${string}}${string}`
32
+ ? PickPathParameters<EP> extends never
33
+ ? Opt : InferKeysLen<PickPathParameters<EP>> extends 1
34
+ ? number : [number, number]
35
+ : Opt;
32
36
  ```
33
37
 
34
38
  ### IdentifyParameters
@@ -0,0 +1,104 @@
1
+
2
+ type TEVEErrorBase = {
3
+ /**
4
+ * error message
5
+ */
6
+ error: string;
7
+ };
8
+ /**
9
+ * There are endpoints with status 200 only that contain the "X-Pages" header.
10
+ */
11
+ export type TSuccessStats = 200 | 201 | 204;
12
+ export type TRedirectStats = 304;
13
+ export type TClientErrorStats = 400 | 401 | 403 | 404 | 420 | 422;
14
+ export type TServerErrorStats = 500 | 503 | 504 | 520;
15
+ export type TESIErrorStats = TClientErrorStats | TServerErrorStats;
16
+ export type TStatsAll = TSuccessStats | TRedirectStats | TClientErrorStats | TServerErrorStats;
17
+ export type TESIErrorStatMap = {
18
+ // status 400
19
+ 400: BadRequest;
20
+ 401: Unauthorized;
21
+ 403: Forbidden;
22
+ 404: NotFound;
23
+ 420: ErrorLimited;
24
+ 422: Unprocessable;
25
+
26
+ // status 500
27
+ 500: InternalServerError;
28
+ 503: ServiceUnavailable;
29
+ 504: GatewayTimeout;
30
+ 520: EVEServerError;
31
+ };
32
+ /**
33
+ * ```ts
34
+ * const res = await fetch("<URL with ESI endpoint>");
35
+ * const status = res.status;
36
+ * if (status >= 400) {
37
+ * const errorType: TESIErrorWithStat<typeof status> = {
38
+ * status, ...res.json()
39
+ * };
40
+ * console.log(errorType);
41
+ * throw new Error(`message="${res.statusText}", status=${status}`);
42
+ * }
43
+ * ```
44
+ *
45
+ * @date 2025/3/3
46
+ * @since 2.4.0
47
+ */
48
+ export type TESIErrorWithStat<Stat extends TClientErrorStats | TServerErrorStats> = {
49
+ status: Stat;
50
+ } & TESIErrorStatMap[Stat];
51
+ // declare const bad: TESIErrorWithStat<400>;
52
+
53
+ /**
54
+ * Bad request model 400
55
+ */
56
+ export interface BadRequest extends TEVEErrorBase {}
57
+ /**
58
+ * Not Found model 404
59
+ */
60
+ export interface NotFound extends TEVEErrorBase {}
61
+ /**
62
+ * Unprocessable model 422[Unprocessable entity]
63
+ */
64
+ export interface Unprocessable extends TEVEErrorBase {}
65
+ /**
66
+ * Unauthorized model 401
67
+ */
68
+ export interface Unauthorized extends TEVEErrorBase {}
69
+ /**
70
+ * Forbidden model 403
71
+ */
72
+ export interface Forbidden extends TEVEErrorBase {
73
+ /**
74
+ * status code received from SSO
75
+ */
76
+ sso_status?: number;
77
+ }
78
+
79
+ /**
80
+ * Error limited model 420
81
+ */
82
+ export interface ErrorLimited extends TEVEErrorBase {}
83
+
84
+ /**
85
+ * Internal server error model 500
86
+ */
87
+ export interface InternalServerError extends TEVEErrorBase {}
88
+ /**
89
+ * Service unavailable model 503
90
+ */
91
+ export interface ServiceUnavailable extends TEVEErrorBase {}
92
+ /**
93
+ * Gateway timeout model 504
94
+ */
95
+ export interface GatewayTimeout extends TEVEErrorBase {
96
+ /**
97
+ * number of seconds the request was given
98
+ */
99
+ timeout?: number;
100
+ }
101
+ /**
102
+ * EVE server error model 520
103
+ */
104
+ export interface EVEServerError extends InternalServerError {}
@@ -55,12 +55,15 @@ export const request = async (method, endpoint, pathParams, opt) => {
55
55
  const url = `${endpointUrl}${up.size ? `?${up}` : ""}`;
56
56
  DEBUG && log(url);
57
57
  try {
58
- return await fetch(url, rqopt).then(res => {
59
- if (res.status >= 400) {
60
- throw new util.ESIRequesError(`${res.statusText} (status=${res.status})`);
61
- }
62
- return res.json();
63
- });
58
+ const res = await fetch(url, rqopt);
59
+ const { status } = res;
60
+ // The parameters are different for successful and error responses.
61
+ if (util.isSuccess(status)) {
62
+ return util.handleSuccessResponse(res, endpointUrl, rqopt, up);
63
+ }
64
+ // else if (isError(status)) {}
65
+ // Actually, throw Error
66
+ throw await util.handleESIError(res, endpointUrl, actualOpt.cancelable);
64
67
  }
65
68
  catch (e) {
66
69
  log(e);
package/lib/rq-util.d.mts CHANGED
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import "colors.ts";
9
9
  import type { TESIRequestFunctionMethods } from "../v2";
10
+ import type { TESIErrorStats } from "./esi-error-types";
10
11
  /**
11
12
  * this always `https://esi.evetech.net`
12
13
  */
@@ -25,10 +26,6 @@ export type ESIRequestOptions = {
25
26
  * will need it for `POST` request etc.
26
27
  */
27
28
  body?: any;
28
- /**
29
- * if want response data with ignore error then can be set to `true`.
30
- */
31
- ignoreError?: boolean;
32
29
  /**
33
30
  * cancel request immediately
34
31
  */
@@ -46,6 +43,9 @@ export type ESIRequestOptions = {
46
43
  */
47
44
  auth?: true;
48
45
  };
46
+ /**
47
+ * @typedef {string | number | boolean} Truthy
48
+ */
49
49
  /**
50
50
  * simple named error class.
51
51
  */
@@ -61,12 +61,58 @@ export declare class ESIErrorLimitReachedError extends ESIRequesError {
61
61
  /**
62
62
  * @typedef {import("./rq-util.mjs").ESIRequestOptions} ESIRequestOptions
63
63
  */
64
+ /**
65
+ * #### status: 200 | 201 | 204
66
+ *
67
+ * + Returns a json object only if the status is `200` or `201`.
68
+ *
69
+ * @param {Response} response
70
+ * @param {string} endpointUrl
71
+ * @param {RequestInit} requestOpt
72
+ * @param {URLSearchParams} urlParams
73
+ * @param {(minus?: Truthy) => void=} increment
74
+ * @returns {Promise<any>}
75
+ */
76
+ export declare const handleSuccessResponse: (response: Response, endpointUrl: string, requestOpt: RequestInit, urlParams: URLSearchParams, increment?: (minus?: Truthy) => void) => Promise<any>;
77
+ /**
78
+ * @import {
79
+ * TESIErrorStats,
80
+ * TESIErrorStatMap,
81
+ * TESIErrorWithStat
82
+ * } from "./esi-error-types";
83
+ */
84
+ /**
85
+ * #### status: (400 | 401 | 403 | 404 | 420 | 422) or (500 | 503 | 504 | 520)
86
+ *
87
+ * @param {Response} res
88
+ * @param {string} endpointUrl
89
+ * @param {AbortController} abortable
90
+ * @returns {Promise<void>}
91
+ */
92
+ export declare const handleESIError: (res: Response, endpointUrl: string, abortable?: AbortController) => Promise<void>;
93
+ /**
94
+ * @param {number} stat
95
+ * @returns {stat is 200 | 201 | 204}
96
+ */
97
+ export declare const isSuccess: (stat: number) => stat is 200 | 201 | 204;
98
+ /**
99
+ * @param {number} stat
100
+ * @returns {stat is TESIErrorStats}
101
+ */
102
+ export declare const isError: (stat: number) => stat is TESIErrorStats;
103
+ /**
104
+ * @returns {boolean}
105
+ */
64
106
  export declare const isDebug: () => boolean;
107
+ /**
108
+ * @param {string} opt
109
+ * @returns {boolean}
110
+ */
65
111
  export declare const is: (opt: string) => boolean;
66
112
  /**
67
113
  * @param {string} method
68
114
  * @param {ESIRequestOptions} opt
69
- * @returns
115
+ * @returns {{ rqopt: RequestInit, qss: Record<string, string> }}
70
116
  */
71
117
  export declare const initOptions: (method: string, opt: ESIRequestOptions) => {
72
118
  rqopt: RequestInit;
@@ -76,12 +122,13 @@ export declare const initOptions: (method: string, opt: ESIRequestOptions) => {
76
122
  * fetch the extra pages
77
123
  *
78
124
  * + if the `x-pages` header property ware more than 1
79
- *
125
+ * @template {any} T
80
126
  * @param {string} endpointUrl
81
127
  * @param {RequestInit} rqopt request options
82
128
  * @param {URLSearchParams} rqp queries
83
129
  * @param {number} pc pageCount
84
130
  * @param {(minus?: number) => void=} increment
131
+ * @returns {Promise<T | null>}
85
132
  */
86
133
  export declare const fetchP: <T extends unknown>(endpointUrl: string, rqopt: RequestInit, rqp: URLSearchParams, pc: number, increment?: (minus?: Truthy) => void) => Promise<T | null>;
87
134
  /** ### replace (C)urly (B)races (T)oken
@@ -93,13 +140,14 @@ export declare const fetchP: <T extends unknown>(endpointUrl: string, rqopt: Req
93
140
  *
94
141
  * @param {string} endpoint e.g - "/characters/{character_id}/"
95
142
  * @param {number[]} ids
96
- * @returns fragment of qualified endpoint uri or null.
143
+ * @returns {string | null} fragment of qualified endpoint uri or null.
97
144
  */
98
145
  export declare const replaceCbt: (endpoint: string, ids: number[]) => string;
99
146
  /**
100
147
  *
101
148
  * @param {string} endp this means endpoint url fragment like `/characters/{character_id}/` or `/characters/{character_id}/agents_research/`
102
149
  * + The version parameter is forced to apply `latest`
150
+ * @returns {string}
103
151
  */
104
152
  export declare const curl: (endp: string) => string;
105
153
  /**
@@ -116,5 +164,6 @@ export declare function getLogger(): {
116
164
  * #### Fire a request that does not require authentication.
117
165
  *
118
166
  * @param {TESIRequestFunctionSignature<ESIRequestOptions> | TESIRequestFunctionMethods} fn
167
+ * @returns {Promise<void>}
119
168
  */
120
169
  export declare function fireRequestsDoesNotRequireAuth(fn: TESIRequestFunctionSignature<ESIRequestOptions> | TESIRequestFunctionMethods<ESIRequestOptions>): Promise<void>;
package/lib/rq-util.mjs CHANGED
@@ -24,6 +24,9 @@ let LOG = false;
24
24
  * this always `https://esi.evetech.net`
25
25
  */
26
26
  export const BASE = "https://esi.evetech.net";
27
+ /**
28
+ * @typedef {string | number | boolean} Truthy
29
+ */
27
30
  /**
28
31
  * simple named error class.
29
32
  */
@@ -46,14 +49,121 @@ export class ESIErrorLimitReachedError extends ESIRequesError {
46
49
  // - - - - - - - - - - - - - - - - - - - -
47
50
  // utility functions
48
51
  // - - - - - - - - - - - - - - - - - - - -
52
+ /**
53
+ * #### status: 200 | 201 | 204
54
+ *
55
+ * + Returns a json object only if the status is `200` or `201`.
56
+ *
57
+ * @param {Response} response
58
+ * @param {string} endpointUrl
59
+ * @param {RequestInit} requestOpt
60
+ * @param {URLSearchParams} urlParams
61
+ * @param {(minus?: Truthy) => void=} increment
62
+ * @returns {Promise<any>}
63
+ */
64
+ export const handleSuccessResponse = async (response, endpointUrl, requestOpt, urlParams, increment = () => { }) => {
65
+ // NoContentResponse
66
+ if (response.status === 204)
67
+ return {};
68
+ /** @type {any} */
69
+ const data = await response.json();
70
+ // - - - - x-pages response.
71
+ // +undefined is NaN
72
+ // @ts-expect-error becouse +null is 0
73
+ const pc = +response.headers.get("x-pages");
74
+ // has remaining pages? NaN > 1 === false !isNaN(pageCount)
75
+ if (pc > 1) {
76
+ LOG && log('found "x-pages" header, pages: %d', pc);
77
+ const remData = await fetchP(endpointUrl, requestOpt, urlParams, pc, increment);
78
+ // finally, decide product data.
79
+ if (isArray(data) && isArray(remData)) {
80
+ // DEVNOTE: 2019/7/23 15:01:48 - types
81
+ return data.concat(remData);
82
+ }
83
+ else {
84
+ remData && Object.assign(data, remData);
85
+ }
86
+ }
87
+ return data;
88
+ };
89
+ /**
90
+ * @import {
91
+ * TESIErrorStats,
92
+ * TESIErrorStatMap,
93
+ * TESIErrorWithStat
94
+ * } from "./esi-error-types";
95
+ */
96
+ /**
97
+ * #### status: (400 | 401 | 403 | 404 | 420 | 422) or (500 | 503 | 504 | 520)
98
+ *
99
+ * @param {Response} res
100
+ * @param {string} endpointUrl
101
+ * @param {AbortController} abortable
102
+ * @returns {Promise<void>}
103
+ */
104
+ export const handleESIError = async (res, endpointUrl, abortable) => {
105
+ const status = /** @type {TESIErrorStats} */ (res.status);
106
+ /** @type {TESIErrorStatMap[typeof status]} */
107
+ const esiError = await res.json();
108
+ /** @type {TESIErrorWithStat<typeof status>} */
109
+ const errorType = {
110
+ status, ...esiError
111
+ };
112
+ // log ESI Error details
113
+ console.warn(errorType);
114
+ if (status === 420) {
115
+ abortable && abortable.abort();
116
+ throw new ESIErrorLimitReachedError();
117
+ }
118
+ else {
119
+ // console.log(res);
120
+ throw new ESIRequesError(`${res.statusText} (status=${status}, url=${endpointUrl})`);
121
+ }
122
+ };
123
+ /** @satisfies {TESIErrorStats[]} */
124
+ const ESIErrorStats = [
125
+ // status 400
126
+ 400, // BadRequest;
127
+ 401, // Unauthorized;
128
+ 403, // Forbidden;
129
+ 404, // NotFound;
130
+ 420, // ErrorLimited;
131
+ 422, // Unprocessable;
132
+ // status 500
133
+ 500, // InternalServerError;
134
+ 503, // ServiceUnavailable;
135
+ 504, // GatewayTimeout;
136
+ 520, // EVEServerError;
137
+ ];
138
+ /**
139
+ * @param {number} stat
140
+ * @returns {stat is 200 | 201 | 204}
141
+ */
142
+ export const isSuccess = (stat) => {
143
+ return stat === 200 || stat === 201 || stat === 204;
144
+ };
145
+ /**
146
+ * @param {number} stat
147
+ * @returns {stat is TESIErrorStats}
148
+ */
149
+ export const isError = (stat) => {
150
+ return ESIErrorStats.includes(/** @type {TESIErrorStats} */ (stat));
151
+ };
152
+ /**
153
+ * @returns {boolean}
154
+ */
49
155
  export const isDebug = () => {
50
156
  return process.argv.includes("-debug");
51
157
  };
158
+ /**
159
+ * @param {string} opt
160
+ * @returns {boolean}
161
+ */
52
162
  export const is = (opt) => process.argv.includes(`-${opt}`);
53
163
  /**
54
164
  * @param {string} method
55
165
  * @param {ESIRequestOptions} opt
56
- * @returns
166
+ * @returns {{ rqopt: RequestInit, qss: Record<string, string> }}
57
167
  */
58
168
  export const initOptions = (method, opt) => {
59
169
  /** @type {RequestInit} */
@@ -64,6 +174,7 @@ export const initOptions = (method, opt) => {
64
174
  signal: opt.cancelable?.signal,
65
175
  headers: {}
66
176
  };
177
+ /** @type {Record<string, string>} */
67
178
  const qss = {
68
179
  // language: "en",
69
180
  };
@@ -87,12 +198,13 @@ export const initOptions = (method, opt) => {
87
198
  * fetch the extra pages
88
199
  *
89
200
  * + if the `x-pages` header property ware more than 1
90
- *
201
+ * @template {any} T
91
202
  * @param {string} endpointUrl
92
203
  * @param {RequestInit} rqopt request options
93
204
  * @param {URLSearchParams} rqp queries
94
205
  * @param {number} pc pageCount
95
206
  * @param {(minus?: number) => void=} increment
207
+ * @returns {Promise<T | null>}
96
208
  */
97
209
  export const fetchP = async (endpointUrl, rqopt, rqp, pc, increment = () => { }) => {
98
210
  const rqs = [];
@@ -113,7 +225,7 @@ export const fetchP = async (endpointUrl, rqopt, rqp, pc, increment = () => { })
113
225
  for (let i = 0, end = jsons.length; i < end;) {
114
226
  combined = combined.concat(jsons[i++]);
115
227
  }
116
- return combined;
228
+ return /** @type {T} */ (combined);
117
229
  }
118
230
  LOG && log("> > > pages result are object < < < --", jsons);
119
231
  return null;
@@ -128,22 +240,17 @@ export const fetchP = async (endpointUrl, rqopt, rqp, pc, increment = () => { })
128
240
  *
129
241
  * @param {string} endpoint e.g - "/characters/{character_id}/"
130
242
  * @param {number[]} ids
131
- * @returns fragment of qualified endpoint uri or null.
243
+ * @returns {string | null} fragment of qualified endpoint uri or null.
132
244
  */
133
245
  export const replaceCbt = (endpoint, ids) => {
134
- const re = /{([\w]+)}/g;
135
- /** @type {RegExpExecArray?} */
136
- let m;
137
246
  let idx = 0;
138
- while (m = re.exec(endpoint)) {
139
- endpoint = endpoint.replace(m[0], ids[idx++] + "");
140
- }
141
- return endpoint;
247
+ return endpoint.replace(/{([\w]+)}/g, $0 => ids[idx++] + "");
142
248
  };
143
249
  /**
144
250
  *
145
251
  * @param {string} endp this means endpoint url fragment like `/characters/{character_id}/` or `/characters/{character_id}/agents_research/`
146
252
  * + The version parameter is forced to apply `latest`
253
+ * @returns {string}
147
254
  */
148
255
  export const curl = (endp) => {
149
256
  endp = endp.replace(/^\/+|\/+$/g, "");
@@ -160,7 +267,7 @@ export async function getSDEVersion() {
160
267
  const res = await fetch(sdeZipUrl, { method: "head", mode: "cors" });
161
268
  const date = res.headers.get("last-modified");
162
269
  if (date) {
163
- const YMD = new Date(date).toLocaleDateString('en-CA').replace(/-/g, '');
270
+ const YMD = new Date(date).toLocaleDateString("en-CA").replace(/-/g, "");
164
271
  return `sde-${YMD}-TRANQUILITY`;
165
272
  }
166
273
  else {
@@ -175,7 +282,7 @@ export async function getSDEVersion() {
175
282
  }
176
283
  export function getLogger() {
177
284
  const clog = console.log.bind(console, '- - -> Get the character data of "CCP Zoetrope"'.magenta);
178
- const rlog = console.log.bind(console, '- - -> Run ESI request'.cyan);
285
+ const rlog = console.log.bind(console, "- - -> Run ESI request".cyan);
179
286
  return { clog, rlog };
180
287
  }
181
288
  /**
@@ -212,6 +319,7 @@ function fireWithoutAuth(fn, method, endpoint, pathParams, opt) {
212
319
  * #### Fire a request that does not require authentication.
213
320
  *
214
321
  * @param {TESIRequestFunctionSignature<ESIRequestOptions> | TESIRequestFunctionMethods} fn
322
+ * @returns {Promise<void>}
215
323
  */
216
324
  export async function fireRequestsDoesNotRequireAuth(fn) {
217
325
  const { clog, rlog } = getLogger();
@@ -278,6 +386,7 @@ export async function fireRequestsDoesNotRequireAuth(fn) {
278
386
  },
279
387
  });
280
388
  // TODO: want TypeScript semantics to throw an error because there is a required query parameter, but it's not possible
389
+ // Or rather, I don't know how to do it.
281
390
  await fireWithoutAuth(fn, "get", "/characters/{character_id}/search/");
282
391
  log(ok);
283
392
  }
@@ -6,6 +6,7 @@
6
6
  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
7
7
  */
8
8
  /// <reference types="../v2/esi-tagged-types"/>
9
+ import type { ESIRequestOptions } from "./rq-util.mjs";
9
10
  /**
10
11
  * `esi-tagged-types` and `injectESIRequestBody` allow for more intuitive use of ESI requests based on the "tags" defined in the EVE Swagger JSON.
11
12
  *
@@ -17,13 +18,18 @@
17
18
  * const esiRequest = taggedApi.injectESIRequestBody(...);
18
19
  * const ret = await esiRequest.universe.get("/universe/structures/", { query: { filter: "market" }});
19
20
  *
21
+ * @template {Record<string, unknown>} Opt
20
22
  * @param {TESIRequestFunctionSignature<{}>} requestBody
21
23
  * @returns {XESI.TaggedESIRequestMap}
22
24
  * @since 2.3
23
25
  */
24
- export declare function injectESIRequestBody(requestBody: TESIRequestFunctionSignature<{}>): XESI.TaggedESIRequestMap;
26
+ export declare function injectESIRequestBody<Opt extends Record<string, unknown>>(requestBody: TESIRequestFunctionSignature<Opt>): XESI.TaggedESIRequestMap<Opt>;
27
+ /**
28
+ * @import { ESIRequestOptions } from "./rq-util.mjs";
29
+ */
25
30
  /**
26
31
  * Injects the minimal implementation of ESI requests into `XESI.TaggedESIRequestMap`.
27
32
  * @since 2.3
33
+ * @type {XESI.TaggedESIRequestMap<ESIRequestOptions>}
28
34
  */
29
- export declare const esi: XESI.TaggedESIRequestMap;
35
+ export declare const esi: import("../v2/esi-tagged-types").TaggedESIRequestMap<ESIRequestOptions>;
@@ -10,27 +10,28 @@
10
10
  * @file eve-esi-types/lib/tagged-request-api.mts
11
11
  */
12
12
  import { request } from "./request-api.mjs";
13
- /** @satisfies {XESI.ESITags[]} */
14
- const ESI_TAGs = [
15
- "Alliance", "Assets",
16
- "Calendar", "Character",
17
- "Clones", "Contacts",
18
- "Contracts", "Corporation",
19
- "Dogma", "Faction Warfare",
20
- "Fittings", "Fleets",
21
- "Incursions", "Industry",
22
- "Insurance", "Killmails",
23
- "Location", "Loyalty",
24
- "Mail", "Market",
25
- "Opportunities", "Planetary Interaction",
26
- "Routes", "Search",
27
- "Skills", "Sovereignty",
28
- "Status", "Universe",
29
- "User Interface", "Wallet",
30
- "Wars"
13
+ /**
14
+ * @typedef {`${string}${"" | `,${string}`}`} TMethodList
15
+ */
16
+ /** @satisfies {`${XESI.ESITags}:${TMethodList}`[]} */
17
+ const ESITagsWithMethodList = [
18
+ "Alliance:get", "Assets:get,post",
19
+ "Calendar:get,put", "Character:get,post",
20
+ "Clones:get", "Contacts:get,post,put,delete",
21
+ "Contracts:get", "Corporation:get",
22
+ "Dogma:get", "Faction Warfare:get",
23
+ "Fittings:get,post,delete", "Fleets:get,post,put,delete",
24
+ "Incursions:get", "Industry:get",
25
+ "Insurance:get", "Killmails:get",
26
+ "Location:get", "Loyalty:get",
27
+ "Mail:get,post,put,delete", "Market:get",
28
+ "Opportunities:get", "Planetary Interaction:get",
29
+ "Routes:get", "Search:get",
30
+ "Skills:get", "Sovereignty:get",
31
+ "Status:get", "Universe:get,post",
32
+ "User Interface:post", "Wallet:get",
33
+ "Wars:get"
31
34
  ];
32
- /** @satisfies {TESIEntryMethod[]} */
33
- const METHODs = ["get", "post", "put", "delete"];
34
35
  /**
35
36
  * `esi-tagged-types` and `injectESIRequestBody` allow for more intuitive use of ESI requests based on the "tags" defined in the EVE Swagger JSON.
36
37
  *
@@ -42,28 +43,32 @@ const METHODs = ["get", "post", "put", "delete"];
42
43
  * const esiRequest = taggedApi.injectESIRequestBody(...);
43
44
  * const ret = await esiRequest.universe.get("/universe/structures/", { query: { filter: "market" }});
44
45
  *
46
+ * @template {Record<string, unknown>} Opt
45
47
  * @param {TESIRequestFunctionSignature<{}>} requestBody
46
48
  * @returns {XESI.TaggedESIRequestMap}
47
49
  * @since 2.3
48
50
  */
49
- // Here, functions are implemented for "get", "post", "put", and "delete" for convenience.
50
- // However, in the completion listed by `injectESIRequestBody(<requestBody>).`, only the inferred methods will be listed.
51
51
  export function injectESIRequestBody(requestBody) {
52
- const rq = /** @type {XESI.TaggedESIRequestMap} */ ({});
53
- for (const tag of ESI_TAGs) {
54
- const camel =
55
- /** @type {XESI.LCamelCase<XESI.ESITags>} */ (tag[0].toLowerCase() + tag.slice(1).replace(/\s(\w)/g, "$1"));
56
- const entry = /** @type {XESI.ESITaggedEndpointRequest<typeof tag>} */ ({});
57
- for (const method of METHODs) {
58
- // @ts-ignore ignore ts(2590)
52
+ const rq = /** @type {XESI.TaggedESIRequestMap<Opt>} */ ({});
53
+ for (const tagEntry of ESITagsWithMethodList) {
54
+ const [tag, methodList] = /** @type {[tag: XESI.ESITags, methods: TMethodList]} */ (tagEntry.split(":"));
55
+ const methods = /** @type {TESIEntryMethod[]} */ (methodList.split(","));
56
+ const entry = /** @type {XESI.ESITaggedEndpointRequest<typeof tag, Opt>} */ ({});
57
+ for (const method of methods) {
58
+ // @ts-expect-error
59
59
  entry[method] = /** @satisfies {XESI.TaggedEndpointRequestFunction<typeof method, typeof tag>} */ ((e, params, opt) => requestBody(method, e, params, opt));
60
60
  }
61
- rq[camel] = entry;
61
+ const camelCased = /** @type {XESI.LCamelCase<XESI.ESITags>} */ (tag[0].toLowerCase() + tag.slice(1).replace(/\s(.)/g, "$1"));
62
+ rq[camelCased] = entry;
62
63
  }
63
64
  return rq;
64
65
  }
66
+ /**
67
+ * @import { ESIRequestOptions } from "./rq-util.mjs";
68
+ */
65
69
  /**
66
70
  * Injects the minimal implementation of ESI requests into `XESI.TaggedESIRequestMap`.
67
71
  * @since 2.3
72
+ * @type {XESI.TaggedESIRequestMap<ESIRequestOptions>}
68
73
  */
69
74
  export const esi = injectESIRequestBody(request);
package/minimal-rq.mjs CHANGED
@@ -27,11 +27,10 @@ const log = console.log;
27
27
  // Delegates implementation to `request` (TESIRequestFunctionMethods)
28
28
  //
29
29
  const esiMethods = /** @type {TESIRequestFunctionMethods} */ ({});
30
- ["get", "post", "put", "delete"].forEach((method) => {
31
- esiMethods[method] = function (endpoint, params, opt) {
32
- // @ts-ignore
30
+ /** @type {TESIEntryMethod[]} */ (["get", "post", "put", "delete"]).forEach((method) => {
31
+ esiMethods[method] = /** @type {TESIRequestFunctionEachMethod<typeof method>} */ (function (endpoint, params, opt) {
33
32
  return request(method, endpoint, params, opt);
34
- };
33
+ });
35
34
  });
36
35
  // It should complete correctly.
37
36
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eve-esi-types",
3
- "version": "2.3.0",
3
+ "version": "2.3.2",
4
4
  "description": "Extracted the main type of ESI. use for ESI request response types (version 2 only)",
5
5
  "main": "v2/index.d.ts",
6
6
  "scripts": {
package/tagged-rq.mjs CHANGED
@@ -17,6 +17,7 @@ esiRq.universe.get("/universe/structures/", { query: { filter: "market" } }).the
17
17
  esiRq.universe.post("/universe/ids/", {
18
18
  body: ["the forge", "plex"]
19
19
  }).then(console.log);
20
+ esiRq.fittings.delete("/characters/{character_id}/fittings/{fitting_id}/", [1234, 56789]);
20
21
  esiRq.assets.get("/characters/{character_id}/assets/", 1234, {
21
22
  auth: true
22
23
  }).then(console.log).catch(console.log);
package/tsconfig.json CHANGED
@@ -18,6 +18,7 @@
18
18
  },
19
19
  "include": [
20
20
  "./scripts/**/*.mts",
21
+ "./scripts/**/*.d.ts",
21
22
  "./v2/*.d.ts"
22
23
  ],
23
24
  "exclude": [
@@ -9,7 +9,7 @@
9
9
  * THIS DTS IS AUTO GENERATED, DO NOT EDIT
10
10
  *
11
11
  * @file eve-esi-types/v2/esi-tagged-types.d.ts
12
- * @summary This file is auto-generated and defines version 2.3.0 of the EVE Online ESI response types.
12
+ * @summary This file is auto-generated and defines version 2.3.2 of the EVE Online ESI response types.
13
13
  */
14
14
  import { TESIResponseOKMap } from "./index.d.ts";
15
15
  export * from "./index.d.ts";
@@ -29,6 +29,19 @@ export * from "./index.d.ts";
29
29
  export declare type LCamelCase<S extends string> = S extends `${infer P1} ${infer P2}`
30
30
  ? `${Lowercase<P1>}${Capitalize<P2>}` : Lowercase<S>;
31
31
 
32
+
33
+ declare const enum EInferSomethingBy {
34
+ METHOD,
35
+ TAGs
36
+ }
37
+ declare type InferSomethingBy<Tag, AsType extends EInferSomethingBy = EInferSomethingBy.METHOD> = {
38
+ [M in TESIEntryMethod]: TESIResponseOKMap[M] extends Record<`/${string}/`, { tag: infer ActualTag }>
39
+ ? AsType extends EInferSomethingBy.TAGs
40
+ ? ActualTag : ActualTag extends Tag
41
+ ? M
42
+ : never
43
+ : never;
44
+ }[TESIEntryMethod];
32
45
  // - - - - - - - - - - - - - - - - - - - - - - - - - -
33
46
  // Utility Type `ESITags`
34
47
  // - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -37,10 +50,7 @@ export declare type LCamelCase<S extends string> = S extends `${infer P1} ${infe
37
50
  * @template M - The HTTP method.
38
51
  * @date 2025/2/28
39
52
  */
40
- export declare type ESITags = {
41
- [M in TESIEntryMethod]: TESIResponseOKMap[M] extends Record<`/${string}/`, { tag: infer Tag }>
42
- ? Tag : never
43
- }[TESIEntryMethod];
53
+ export declare type ESITags = InferSomethingBy<never, EInferSomethingBy.TAGs>
44
54
 
45
55
  // - - - - - - - - - - - - - - - - - - - - - - - - - -
46
56
  // Utility Type `InferMethod`
@@ -51,12 +61,7 @@ export declare type ESITags = {
51
61
  * @template Tag - The tag to infer the method for.
52
62
  * @date 2025/2/28
53
63
  */
54
- export declare type InferMethod<Tag> = {
55
- [M in TESIEntryMethod]: TESIResponseOKMap[M] extends Record<`/${string}/`, { tag: infer ActualTag }>
56
- ? ActualTag extends Tag
57
- ? M : never
58
- : never;
59
- }[TESIEntryMethod];
64
+ export declare type InferMethod<Tag> = InferSomethingBy<Tag>
60
65
  // type XAssets = InferMethod<"Assets">;
61
66
  // type XContacts = InferMethod<"Contacts">;
62
67
  // type XPlanetary = InferMethod<"Planetary Interaction">;
@@ -97,9 +102,9 @@ export declare type TaggedEndpointRequestFunction<M extends TESIEntryMethod, Tag
97
102
  * @template Tag - The tag to map.
98
103
  * @date 2025/2/28
99
104
  */
100
- export declare type ESITaggedEndpointRequest<Tag extends ESITags> = {
105
+ export declare type ESITaggedEndpointRequest<Tag extends ESITags, ActualOpt = {}> = {
101
106
  [tag in Tag]: {
102
- [method in InferMethod<Tag>]: TaggedEndpointRequestFunction<method, tag>;
107
+ [method in InferMethod<Tag>]: TaggedEndpointRequestFunction<method, tag, ActualOpt>;
103
108
  };
104
109
  }[Tag];
105
110
 
@@ -129,8 +134,8 @@ export declare type SelectEndpointByTag<
129
134
  * Maps lower camel case tags to their corresponding endpoint request functions.
130
135
  * @date 2025/2/28
131
136
  */
132
- export declare type TaggedESIRequestMap = {
133
- [tag in ESITags as LCamelCase<tag>]: ESITaggedEndpointRequest<tag>;
137
+ export declare type TaggedESIRequestMap<ActualOpt = {}> = {
138
+ [tag in ESITags as LCamelCase<tag>]: ESITaggedEndpointRequest<tag, ActualOpt>;
134
139
  };
135
140
 
136
141
  /**
package/v2/index.d.ts CHANGED
@@ -9,12 +9,14 @@
9
9
  * THIS DTS IS AUTO GENERATED, DO NOT EDIT
10
10
  *
11
11
  * @file eve-esi-types/v2/index.d.ts
12
- * @summary This file is auto-generated and defines version 2.3.0 of the EVE Online ESI response types.
12
+ * @summary This file is auto-generated and defines version 2.3.2 of the EVE Online ESI response types.
13
13
  */
14
14
 
15
15
  import type { TESIResponseOKMap } from "./response-map.d.ts";
16
16
  export type { TESIResponseOKMap } from "./response-map.d.ts";
17
17
 
18
+ import type { PickPathParameters, InferKeysLen } from "./util.d.ts";
19
+
18
20
  /**
19
21
  * Represents a function that can make ESI requests with various HTTP methods.
20
22
  *
@@ -73,6 +75,20 @@ declare global {
73
75
  */
74
76
  type RequireThese<T, K extends keyof T> = T & Required<Pick<T, K>>;
75
77
 
78
+ /**
79
+ * If `EP` (endpoint) is a parameterized path, determines the required number of replacements.
80
+ *
81
+ * @template EP The string representing the endpoint path.
82
+ * @template Opt The type to return if `EP` is not parameterized.
83
+ * @returns {number | [number, number] | Opt}
84
+ * Returns `number` if there is one parameter, `[number, number]` if there are two parameters, otherwise `Opt`.
85
+ */
86
+ type IfParameterizedPath<EP, Opt> = EP extends `${string}/{${string}}${string}`
87
+ ? PickPathParameters<EP> extends never
88
+ ? Opt : InferKeysLen<PickPathParameters<EP>> extends 1
89
+ ? number : [number, number]
90
+ : Opt;
91
+
76
92
  /**
77
93
  * ### ESI request function all in one signature
78
94
  *
@@ -117,15 +133,6 @@ declare global {
117
133
  R extends InferESIResponseResult<M, EP>
118
134
  >(endpoint: EP, pathParams?: P2, options?: Opt) => Promise<R>;
119
135
 
120
- // /**
121
- // * is parameterized path
122
- // */
123
- // type IsParameterizedPath<EP, A, B> = EP extends `${string}/{${string}}/${string | ""}` ? A: B;
124
- /**
125
- * if parameterized path then specify number type, otherwise will be `Opt` type.
126
- */
127
- type IfParameterizedPath<EP, Opt> = EP extends `${string}/{${string}}/${string | ""}` ? number | number[]: Opt;
128
-
129
136
  /**
130
137
  * Identifies the required parameters for a given entry type.
131
138
  *
@@ -158,9 +165,8 @@ declare global {
158
165
 
159
166
  /**
160
167
  * Represents a response with no content (HTTP status 204).
161
- * Although no data is returned, it indicates successful completion by returning a status of 204.
162
168
  */
163
- type NoContentResponse = { status: 204 };
169
+ type NoContentResponse = { /* status: 204 */ };
164
170
 
165
171
  /**
166
172
  * Represents the HTTP methods supported by ESI.
@@ -9,7 +9,7 @@
9
9
  * THIS DTS IS AUTO GENERATED, DO NOT EDIT
10
10
  *
11
11
  * @file eve-esi-types/v2/response-map.d.ts
12
- * @summary This file is auto-generated and defines version 2.3.0 of the EVE Online ESI response types.
12
+ * @summary This file is auto-generated and defines version 2.3.2 of the EVE Online ESI response types.
13
13
  */
14
14
  import "./types-index.d.ts";
15
15
 
@@ -9,7 +9,7 @@
9
9
  * THIS DTS IS AUTO GENERATED, DO NOT EDIT
10
10
  *
11
11
  * @file eve-esi-types/v2/types-index.d.ts
12
- * @summary This file is auto-generated and defines version 2.3.0 of the EVE Online ESI response types.
12
+ * @summary This file is auto-generated and defines version 2.3.2 of the EVE Online ESI response types.
13
13
  */
14
14
  import "./get_wars_ok.d.ts";
15
15
  import "./get_status_ok.d.ts";
package/v2/util.d.ts ADDED
@@ -0,0 +1,51 @@
1
+ /*!
2
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3
+ // Copyright (C) 2025 jeffy-g <hirotom1107@gmail.com>
4
+ // Released under the MIT license
5
+ // https://opensource.org/licenses/mit-license.php
6
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
7
+ */
8
+ /**
9
+ * @file eve-esi-types/v2/util.d.ts
10
+ * @since 2.3.1
11
+ */
12
+
13
+ /**
14
+ * If `Path` is parameterized, return the parameter name, such as `killmail_id`.
15
+ *
16
+ * @template Path The string representing the endpoint path.
17
+ * @returns {string | never} The parameter name if the path is parameterized, otherwise `never`.
18
+ */
19
+ export type PickPathParameters<Path extends string> =
20
+ Path extends `${string}/{${infer Param}}/${infer Rest}`
21
+ ? Param | PickPathParameters<`/${Rest}`>
22
+ : never;
23
+
24
+ /**
25
+ * Convert a union type to an intersection type.
26
+ *
27
+ * @template U The union type to convert.
28
+ * @returns {I} The intersection type.
29
+ */
30
+ type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
31
+
32
+ /**
33
+ * Convert a union type to a tuple.
34
+ *
35
+ * @template T The union type to convert.
36
+ * @returns {Array} The tuple representation of the union type.
37
+ */
38
+ type UnionToTuple<T> = UnionToIntersection<
39
+ T extends any ? () => T : never
40
+ > extends () => infer R ? [...UnionToTuple<Exclude<T, R>>, R] : [];
41
+
42
+ /**
43
+ * #### Build an array of elements from a flattened tuple of type "1" | "2" | "3" ...
44
+ *
45
+ * + Returns the final length of the array.
46
+ *
47
+ * @template T The union type to be converted to a tuple and measured.
48
+ * @returns {number} The length of the tuple.
49
+ * @date 2025/2/11 18:12:02
50
+ */
51
+ export type InferKeysLen<T> = UnionToTuple<T>["length"];
package/v2.d.mts CHANGED
@@ -7,6 +7,10 @@
7
7
  */
8
8
  import type { TESIResponseOKMap } from "./v2";
9
9
  import { type ESIRequestOptions } from "./lib/rq-util.mjs";
10
+ /**
11
+ * @returns Get The Current ESI request pending count.
12
+ */
13
+ export declare const getRequestPending: () => number;
10
14
  /**
11
15
  * fire ESI request
12
16
  * @template {TESIEntryMethod} M
package/v2.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // import type { TESIResponseOKMap } from "eve-esi-types";
2
- import { is, curl, fetchP, replaceCbt, getSDEVersion, ESIRequesError, initOptions, isDebug, ESIErrorLimitReachedError, fireRequestsDoesNotRequireAuth } from "./lib/rq-util.mjs";
2
+ import { is, curl, replaceCbt, getSDEVersion, initOptions, isDebug, fireRequestsDoesNotRequireAuth, isSuccess, handleESIError, handleSuccessResponse } from "./lib/rq-util.mjs";
3
3
  // - - - - - - - - - - - - - - - - - - - -
4
4
  // constants, types
5
5
  // - - - - - - - - - - - - - - - - - - - -
@@ -22,6 +22,10 @@ let LOG = isDebug();
22
22
  */
23
23
  let ax = 0;
24
24
  const incrementAx = (minus) => minus ? ax-- : ax++;
25
+ /**
26
+ * @returns Get The Current ESI request pending count.
27
+ */
28
+ export const getRequestPending = () => ax;
25
29
  // - - - - - - - - - - - - - - - - - - - -
26
30
  // main functions
27
31
  // - - - - - - - - - - - - - - - - - - - -
@@ -64,52 +68,16 @@ export async function fire(mthd, endp, pathParams, opt) {
64
68
  try {
65
69
  const res = await fetch(url, rqopt).finally(() => ax--);
66
70
  const { status } = res;
67
- if (!res.ok && !actualOpt.ignoreError) {
68
- if (status === 420) {
69
- actualOpt.cancelable && actualOpt.cancelable.abort();
70
- throw new ESIErrorLimitReachedError();
71
- }
72
- else {
73
- // console.log(res);
74
- throw new ESIRequesError(`${res.statusText} (status=${status})`);
75
- }
76
- }
77
- else {
78
- // DEVNOTE: - 204 No Content
79
- if (status === 204) {
80
- // this result is empty, decided to return status code.
81
- return /** @type {R} */ ({ status });
82
- }
83
- /** @type {R} */
84
- const data = await res.json();
85
- if (actualOpt.ignoreError) {
86
- // meaning `forceJson`?
87
- return data;
88
- }
89
- // - - - - x-pages response.
90
- // +undefined is NaN
91
- // @ts-expect-error becouse +null is 0
92
- const pc = +res.headers.get("x-pages");
93
- // has remaining pages? NaN > 1 === false !isNaN(pageCount)
94
- if (pc > 1) {
95
- LOG && log('found "x-pages" header, pages: %d', pc);
96
- const remData = await fetchP(endpointUrl, rqopt, up, pc, incrementAx);
97
- // finally, decide product data.
98
- if (isArray(data) && isArray(remData)) {
99
- // DEVNOTE: 2019/7/23 15:01:48 - types
100
- return /** @type {R} */ (data.concat(remData));
101
- }
102
- else {
103
- // @ts-expect-error TODO: fix type
104
- remData && Object.assign(data, remData);
105
- }
106
- }
107
- return data;
71
+ // The parameters are different for successful and error responses.
72
+ if (isSuccess(status)) {
73
+ return handleSuccessResponse(res, endpointUrl, rqopt, up, incrementAx);
108
74
  }
75
+ // else if (isError(status)) {}
76
+ // Actually, throw Error
77
+ throw await handleESIError(res, endpointUrl, actualOpt.cancelable);
109
78
  }
110
79
  catch (e) {
111
- // @ts-expect-error actualy endp is string
112
- throw new ESIRequesError(`message: ${e.message}, endpoint=${endp}`);
80
+ throw e;
113
81
  }
114
82
  }
115
83
  // It should complete correctly.