eve-esi-types 3.2.6 → 3.2.8

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/lib/rq-util.mjs CHANGED
@@ -27,7 +27,11 @@ let LOG = false;
27
27
  */
28
28
  export const BASE = "https://esi.evetech.net";
29
29
  /**
30
- * @typedef {string | number | boolean} Truthy
30
+ * @import * as ESIUtil from "./rq-util.mjs";
31
+ * @typedef {ESIUtil.Truthy} Truthy
32
+ * @typedef {ESIUtil.TAccessToken} TAccessToken
33
+ * @typedef {ESIUtil.TJWTPayload} TJWTPayload
34
+ * @typedef {ESIUtil.ESIRequestOptions} ESIRequestOptions
31
35
  */
32
36
  /**
33
37
  * simple named error class.
@@ -45,20 +49,18 @@ export class ESIErrorLimitReachedError extends ESIRequestError {
45
49
  return 420;
46
50
  }
47
51
  }
48
- /**
49
- * @typedef {import("./rq-util.mjs").ESIRequestOptions} ESIRequestOptions
50
- */
51
52
  // - - - - - - - - - - - - - - - - - - - -
52
53
  // utility functions
53
54
  // - - - - - - - - - - - - - - - - - - - -
54
55
  /**
55
56
  * @template T
57
+ * @template {Record<string, unknown>} O
56
58
  * @param {[T] | [(T | undefined)?]} opt
57
- * @returns {NonNullable<T>}
59
+ * @returns {NonNullable<T> & O}
58
60
  */
59
61
  export const normalizeOptions = (opt) => {
60
62
  //* ctt
61
- return /** @type {NonNullable<T>} */ (opt.length ? (opt[0] ?? {}) : {});
63
+ return /** @type {NonNullable<T> & O} */ (opt.length ? (opt[0] ?? {}) : {});
62
64
  /*/
63
65
  const r = /** @type {NonNullable<T>} * /(opt.length ? (opt[0] ?? {}): {}) as NonNullable<T>;
64
66
  log(`normalizeOptions::[${JSON.stringify(r)}]`);
@@ -74,30 +76,33 @@ export const normalizeOptions = (opt) => {
74
76
  * @param {string} endpointUrl
75
77
  * @param {RequestInit} requestOpt
76
78
  * @param {URLSearchParams} urlParams
77
- * @param {(minus?: Truthy) => void=} increment
79
+ * @param {(minus?: Truthy) => void=} progress
80
+ * @param {true=} allowFetchPages 2025/4/26
78
81
  * @returns {Promise<any>}
79
82
  */
80
- export const handleSuccessResponse = async (response, endpointUrl, requestOpt, urlParams, increment = () => { }) => {
83
+ export const handleSuccessResponse = async (response, endpointUrl, requestOpt, urlParams, progress = () => { }, allowFetchPages) => {
81
84
  // NoContentResponse
82
85
  if (response.status === 204)
83
86
  return {};
84
87
  /** @type {any} */
85
88
  const data = await response.json();
86
- // - - - - x-pages response.
87
- // +undefined is NaN
88
- // @ts-expect-error becouse +null is 0
89
- const pc = +response.headers.get("x-pages");
90
- // has remaining pages? NaN > 1 === false !isNaN(pageCount)
91
- if (pc > 1) {
92
- LOG && log('found "x-pages" header, pages: %d', pc);
93
- const remData = await fetchP(endpointUrl, requestOpt, urlParams, pc, increment);
94
- // finally, decide product data.
95
- if (isArray(data) && isArray(remData)) {
96
- // DEVNOTE: 2019/7/23 15:01:48 - types
97
- return data.concat(remData);
98
- }
99
- else {
100
- remData && Object.assign(data, remData);
89
+ if (allowFetchPages) {
90
+ // - - - - x-pages response.
91
+ // +undefined is NaN
92
+ // @ts-expect-error becouse +null is 0
93
+ const pc = +response.headers.get("x-pages");
94
+ // has remaining pages? NaN > 1 === false !isNaN(pageCount)
95
+ if (pc > 1) {
96
+ LOG && log('found "x-pages" header, pages: %d', pc);
97
+ const remData = await fetchP(endpointUrl, requestOpt, urlParams, pc, progress);
98
+ // finally, decide product data.
99
+ if (isArray(data) && isArray(remData)) {
100
+ // DEVNOTE: 2019/7/23 15:01:48 - types
101
+ return data.concat(remData);
102
+ }
103
+ else {
104
+ remData && Object.assign(data, remData);
105
+ }
101
106
  }
102
107
  }
103
108
  return data;
@@ -228,6 +233,9 @@ export const initOptions = (method, opt) => {
228
233
  qss[key] = oqs[key];
229
234
  }
230
235
  if (opt.auth) {
236
+ if (!opt.token) {
237
+ throw new Error("Authentication required: missing `token`");
238
+ }
231
239
  // @ts-ignore The header is indeed an object
232
240
  rqopt.headers.authorization = `Bearer ${opt.token}`;
233
241
  }
@@ -247,19 +255,19 @@ export const initOptions = (method, opt) => {
247
255
  * @param {RequestInit} rqopt request options
248
256
  * @param {URLSearchParams} usp queries
249
257
  * @param {number} pc pageCount
250
- * @param {(minus?: number) => void=} increment
258
+ * @param {(minus?: number) => void=} progress
251
259
  * @returns {Promise<T | null>}
252
260
  */
253
- export const fetchP = async (endpointUrl, rqopt, usp, pc, increment = () => { }) => {
261
+ export const fetchP = async (endpointUrl, rqopt, usp, pc, progress = () => { }) => {
254
262
  const rqs = [];
255
263
  for (let i = 2; i <= pc;) {
256
264
  usp.set("page", (i++) + "");
257
- increment();
265
+ progress();
258
266
  rqs.push(fetch(`${endpointUrl}?${usp + ""}`, rqopt).then(res => res.json()).catch(reason => {
259
267
  console.warn(reason);
260
268
  return [];
261
269
  }).finally(() => {
262
- increment(1);
270
+ progress(1);
263
271
  }));
264
272
  }
265
273
  return Promise.all(rqs).then(jsons => {
@@ -276,7 +284,10 @@ export const fetchP = async (endpointUrl, rqopt, usp, pc, increment = () => { })
276
284
  return null;
277
285
  });
278
286
  };
287
+ const CBT_RE = /{\w+}/g;
279
288
  /** ### replace (C)urly (B)races (T)oken
289
+ *
290
+ * + Replace each `{…}` placeholder in the endpoint string with the corresponding ID.
280
291
  *
281
292
  * @example
282
293
  * "/characters/{character_id}/skills"
@@ -284,24 +295,51 @@ export const fetchP = async (endpointUrl, rqopt, usp, pc, increment = () => { })
284
295
  * "/characters/<char.character_id>/skills"
285
296
  *
286
297
  * @template {unknown} T
287
- * @param {T} endpoint e.g - "/characters/{character_id}/"
288
- * @param {number[]} ids
289
- * @returns {T} fragment of qualified endpoint uri or null.
298
+ * @param {T} endpoint An endpoint template, e.g. "/characters/{character_id}/skills"
299
+ * @param {number[]} ids An array of numbers to fill into each placeholder, in order of appearance
300
+ * @returns {T} A fully-qualified endpoint string with all `{…}` tokens replaced by their IDs
290
301
  */
291
302
  export const replaceCbt = (endpoint, ids) => {
292
303
  let idx = 0;
293
- // @ts-expect-error
294
- return endpoint.replace(/{([\w]+)}/g, () => ids[idx++] + "");
304
+ return /** @type {T} */ (/** @type {string} */ (endpoint).replace(CBT_RE, () => String(ids[idx++])));
295
305
  };
296
306
  /**
297
307
  * @template {unknown} T
298
- * @param {T} endp this means endpoint url fragment like `/characters/{character_id}/` or `/characters/{character_id}/agents_research/`
308
+ * @param {T} endpoint this means endpoint url fragment like `/characters/{character_id}/` or `/characters/{character_id}/agents_research/`
299
309
  * + The version parameter is forced to apply `latest`
300
310
  * @returns {string}
301
311
  */
302
- export const curl = (endp) => {
303
- // @ts-expect-error
304
- return `${BASE}/latest/${endp.replace(/^\/+|\/+$/g, "")}/`;
312
+ export const curl = (endpoint) => {
313
+ return `${BASE}/latest/${/** @type {string} */ (endpoint).replace(/^\/+|\/+$/g, "")}/`;
314
+ };
315
+ /**
316
+ * Type guard that checks whether the given object has a `pathParams` property
317
+ * of type `number` or `number[]`.
318
+ *
319
+ * @template {Record<string, unknown>} T - The type of the object being checked.
320
+ * @param {T} opt - The object to inspect.
321
+ * @returns {opt is (T & { pathParams: number | number[] })}
322
+ * `true` if `opt` contains a `pathParams` property whose value is either
323
+ * a single number or an array of numbers, otherwise `false`.
324
+ *
325
+ * @date 2025/4/28
326
+ */
327
+ export function hasPathParams(opt) {
328
+ if (typeof opt !== "object" || opt === null)
329
+ return false;
330
+ return "pathParams" in opt && (typeof opt.pathParams === "number" || Array.isArray(opt.pathParams));
331
+ }
332
+ /**
333
+ *
334
+ * @param {string} accessToken OAuth 2.0 access token
335
+ * @returns {TJWTPayload}
336
+ */
337
+ export const getJWTPayload = (accessToken) => {
338
+ // const [header, payload, sig] = accessToken.split(".");
339
+ // const headerJson = a2b(header); maybe always {"alg":"RS256","kid":"JWT-Signature-Key","typ":"JWT"}
340
+ // const sigJson = a2b(sig);
341
+ const json = atob(accessToken.split(".")[1]);
342
+ return JSON.parse(json); // as NsEVEOAuth.TJWTPayload;
305
343
  };
306
344
  /**
307
345
  * @date 2020/03/31
@@ -345,30 +383,51 @@ export function getLogger() {
345
383
  */
346
384
  /**
347
385
  * @typedef {TESIRequestFunctionSignature2<ESIRequestOptions> | TESIRequestFunctionMethods2} TPrependParams
386
+ * @typedef {ReturnType<typeof fireWithoutAuth>} TFireReturn
348
387
  */
349
388
  /**
350
389
  * #### Fire a request that does not require authentication.
351
390
  *
352
391
  * @type {TESIEnhancedRequestFunctionSignature<TPrependParams, ESIRequestOptions>}
353
392
  */
393
+ // Resolved type error (ts(2322)) using the `TFireReturn` type assertion (2025/4/29)
354
394
  const fireWithoutAuth = (fn, method, endpoint, ...opt) => {
355
395
  const arg = opt.length ? opt[0] : void 0;
356
396
  if (typeof fn === "function") {
357
- return fn(method, endpoint, /** @type {Parameters<typeof fn>[2]} */ (arg));
397
+ return /** @type {TFireReturn} */ (
398
+ // @ts-expect-error TODO: ts(2345) The argument type does not match the type of the specified parameter
399
+ fn(method, endpoint, arg));
358
400
  }
401
+ return /** @type {TFireReturn} */ (
359
402
  // @ts-expect-error TODO: ts(2345) The argument type does not match the type of the specified parameter
360
- return fn[method](endpoint, arg);
403
+ fn[method](endpoint, arg));
361
404
  };
362
- const token = `token.token.token`;
405
+ // /**
406
+ // * ```
407
+ // * process.env.OAUTH_TOKEN // Spedify valid OAuth token
408
+ // * ```
409
+ // * @param envName
410
+ // */
411
+ // const getEnvValue = (envName: string) => process.env[envName] ?? null;
412
+ /** @type {TAccessToken} */
413
+ const token = /** @type {TAccessToken} */ (process.env.OAUTH_TOKEN ?? "token.token.token");
414
+ const ID_SomeEVECharacter = (() => {
415
+ if (token !== "token.token.token") {
416
+ const payload = getJWTPayload(token);
417
+ return +payload.sub.split(":")[2];
418
+ }
419
+ return 9000;
420
+ })();
363
421
  /**
364
422
  * #### Fire a request that does not require authentication.
365
423
  *
424
+ * + __CAVEAT:__ This function should only be used for testing.
425
+ *
366
426
  * @param {TPrependParams} fn
367
427
  * @returns {Promise<void>}
368
428
  */
369
429
  export async function fireRequestsDoesNotRequireAuth(fn) {
370
430
  const { clog, rlog } = getLogger();
371
- const ID_SomeEVECharacter = 90000;
372
431
  const ID_CCP_Zoetrope = 2112625428;
373
432
  try {
374
433
  // - - - - - - - - - - - -
@@ -391,17 +450,21 @@ export async function fireRequestsDoesNotRequireAuth(fn) {
391
450
  rlog("get:/incursions/".green);
392
451
  await fireWithoutAuth(fn, "get", "/incursions/").then(log);
393
452
  if (is("withError")) {
453
+ log(`Try character ${ID_SomeEVECharacter} assets request`);
394
454
  await fireWithoutAuth(fn, "get", "/characters/{character_id}/assets/", {
395
- auth: true,
455
+ auth: true, token,
396
456
  pathParams: ID_SomeEVECharacter,
397
- token
398
457
  }).then(assets => {
399
458
  log(assets.slice(0, 5));
400
459
  });
401
- await fireWithoutAuth(fn, "delete", `/characters/${1234}/fittings/${56789}/`, {
460
+ log(`Try character ${ID_SomeEVECharacter} fittings request`);
461
+ await fireWithoutAuth(fn, "get", `/characters/${ID_SomeEVECharacter}/fittings/`, {
402
462
  auth: true,
403
- // pathParams: [1234, 56789], // ✅ At this point, the expected semantic error is successfully triggered as intended.
404
- }).catch(log);
463
+ // pathParams: [ID_SomeEVECharacter, 56789], // ✅ At this point, the expected semantic error is successfully triggered as intended.
464
+ token
465
+ }).then(fittings => {
466
+ log(fittings.slice(0, 5));
467
+ });
405
468
  }
406
469
  }
407
470
  // - - - - - - - - - - - -
@@ -434,8 +497,7 @@ export async function fireRequestsDoesNotRequireAuth(fn) {
434
497
  // Authentication is required, so an error will occur.
435
498
  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
436
499
  let willFailed = await fireWithoutAuth(fn, "get", `/characters/${ID_SomeEVECharacter}/ship/`, {
437
- auth: true,
438
- token //: "token.token.token"
500
+ auth: true, token
439
501
  });
440
502
  log(`get:/characters/${ID_SomeEVECharacter}/ship/, returns:`, willFailed);
441
503
  // in this case, "categories" and "search" is required
package/minimal-rq.mjs CHANGED
@@ -29,8 +29,8 @@ const log = util.getUniversalLogger("[request-mini]: ");
29
29
  const esiMethods = /** @type {TESIRequestFunctionMethods2} */ ({});
30
30
  /** @satisfies {TESIEntryMethod[]} */ (["get", "post", "put", "delete"]).forEach((method) => {
31
31
  esiMethods[method] = /** @type {TESIRequestFunctionEachMethod2<typeof method, util.ESIRequestOptions>} */ ((endpoint, opt) => {
32
- // @ts-expect -error ts(2345)
33
- return request2(method, endpoint, /** @type {Parameters<typeof request2>[2]} */ (opt));
32
+ // @ts-expect-error ts(2345)
33
+ return request2(method, endpoint, opt);
34
34
  });
35
35
  });
36
36
  // It should complete correctly.
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "eve-esi-types",
3
- "version": "3.2.6",
3
+ "version": "3.2.8",
4
4
  "description": "Extracted the main type of ESI. use for ESI request response types (version 2 only)",
5
5
  "main": "dist/v2/index.d.ts",
6
6
  "scripts": {
7
- "start": "tsc -p jsconfig.json",
7
+ "start": "tsc",
8
8
  "test": "node request-v3.mjs -debug",
9
9
  "test:mini": "node minimal-rq.mjs -debug"
10
10
  },
@@ -21,7 +21,7 @@
21
21
  "LICENSE",
22
22
  "*.md",
23
23
  "package.json",
24
- "jsconfig.json"
24
+ "tsconfig.json"
25
25
  ],
26
26
  "keywords": [
27
27
  "api",
package/request-v3.mjs CHANGED
@@ -13,7 +13,7 @@
13
13
  // - - - - - - - - - - - - - - - - - - - -
14
14
  // imports
15
15
  // - - - - - - - - - - - - - - - - - - - -
16
- import { is, curl, replaceCbt, getSDEVersion, normalizeOptions, initOptions, isDebug, fireRequestsDoesNotRequireAuth, isSuccess, handleESIError, handleSuccessResponse, } from "./lib/rq-util.mjs";
16
+ import { is, curl, replaceCbt, hasPathParams, getSDEVersion, normalizeOptions, initOptions, isDebug, fireRequestsDoesNotRequireAuth, isSuccess, handleESIError, handleSuccessResponse, } from "./lib/rq-util.mjs";
17
17
  // - - - - - - - - - - - - - - - - - - - -
18
18
  // constants, types
19
19
  // - - - - - - - - - - - - - - - - - - - -
@@ -37,7 +37,7 @@ const LOG = isDebug();
37
37
  */
38
38
  let ax = 0;
39
39
  /** @type {function(Truthy=): number} */
40
- const incrementAx = (minus) => minus ? ax-- : ax++;
40
+ const progress = (minus) => minus ? ax-- : ax++;
41
41
  /**
42
42
  * @returns Get The Current ESI request pending count.
43
43
  */
@@ -53,16 +53,12 @@ export const getRequestPending = () => ax;
53
53
  */
54
54
  export const fire = /** @type {TESIRequestFunctionSignature2<ESIRequestOptions>} */ (async (mthd, endp, ...opt) => {
55
55
  // When only options are provided
56
- const actualOpt = normalizeOptions(opt);
57
- /** @type {number[]=} */
58
- let pathParams;
59
- if (actualOpt.pathParams) {
60
- pathParams = typeof actualOpt.pathParams === "number" ? [actualOpt.pathParams] : isArray(actualOpt.pathParams) ? actualOpt.pathParams : void 0;
61
- }
62
- if (isArray(pathParams)) {
56
+ const nOpt = normalizeOptions(opt);
57
+ if (hasPathParams(nOpt)) {
58
+ const pathParams = Array.isArray(nOpt.pathParams) ? nOpt.pathParams : [nOpt.pathParams];
63
59
  endp = replaceCbt(endp, pathParams);
64
60
  }
65
- const { rqopt, qss } = initOptions(mthd, actualOpt);
61
+ const { rqopt, qss } = initOptions(mthd, nOpt);
66
62
  const endpointUrl = curl(endp);
67
63
  const up = new URLSearchParams(qss);
68
64
  const url = `${endpointUrl}${up.size ? `?${up}` : ""}`;
@@ -72,11 +68,11 @@ export const fire = /** @type {TESIRequestFunctionSignature2<ESIRequestOptions>}
72
68
  const res = await fetch(url, rqopt).finally(() => ax--);
73
69
  // The parameters are different for successful and error responses.
74
70
  if (isSuccess(res.status)) {
75
- return handleSuccessResponse(res, endpointUrl, rqopt, up, incrementAx);
71
+ return handleSuccessResponse(res, endpointUrl, rqopt, up, progress);
76
72
  }
77
73
  // else if (isError(status)) {}
78
74
  // Actually, throw Error
79
- throw await handleESIError(res, endpointUrl, actualOpt.cancelable);
75
+ throw await handleESIError(res, endpointUrl, nOpt.cancelable);
80
76
  }
81
77
  catch (e) {
82
78
  throw e;
package/tagged-rq.mjs CHANGED
@@ -42,8 +42,9 @@ if (util.is("withError")) {
42
42
  esi.assets.get(`/characters/${ID_CCP_Zoetrope}/assets/`, {
43
43
  auth: true
44
44
  }).then(console.log).catch(console.log);
45
- esi.mail.post(`/characters/${ID_CCP_Zoetrope}/mail/`, /** @satisfies {Pick<TESIResponsePostEntry<"/characters/{character_id}/mail/">, "auth" | "body">} */ ({
45
+ esi.mail.post("/characters/{character_id}/mail/", ({
46
46
  auth: true,
47
+ pathParams: ID_CCP_Zoetrope, // ✅ At this point, the expected semantic error is successfully triggered as intended.
47
48
  body: {
48
49
  subject: "test!!",
49
50
  body: "",
@@ -52,7 +53,7 @@ if (util.is("withError")) {
52
53
  }]
53
54
  },
54
55
  // token: "s.s.s"
55
- })).then(console.log).catch(console.log);
56
+ }) /* satisfies Pick<TESIResponsePostEntry<"/characters/{character_id}/mail/">, "auth" | "body"> & { pathParams: number } */).then(console.log).catch(console.log);
56
57
  esi.fittings.delete("/characters/1234/fittings/56789/", {
57
58
  // pathParams: [1234, 56789], // ✅ At this point, the expected semantic error is successfully triggered as intended.
58
59
  auth: true
File without changes