eve-esi-types 3.2.6 → 3.2.7

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
@@ -351,24 +389,43 @@ export function getLogger() {
351
389
  *
352
390
  * @type {TESIEnhancedRequestFunctionSignature<TPrependParams, ESIRequestOptions>}
353
391
  */
392
+ // @ts-ignore ts(2322) ignore on ts file, bad js file is OK?
354
393
  const fireWithoutAuth = (fn, method, endpoint, ...opt) => {
355
394
  const arg = opt.length ? opt[0] : void 0;
356
395
  if (typeof fn === "function") {
357
- return fn(method, endpoint, /** @type {Parameters<typeof fn>[2]} */ (arg));
396
+ // @ts-expect-error TODO: ts(2345) The argument type does not match the type of the specified parameter
397
+ return fn(method, endpoint, arg);
358
398
  }
359
399
  // @ts-expect-error TODO: ts(2345) The argument type does not match the type of the specified parameter
360
400
  return fn[method](endpoint, arg);
361
401
  };
362
- const token = `token.token.token`;
402
+ // /**
403
+ // * ```
404
+ // * process.env.CID // Specify EVE Character id
405
+ // * process.env.OAUTH_TOKEN // Spedify valid OAuth token
406
+ // * ```
407
+ // * @param envName
408
+ // */
409
+ // const getEnvValue = (envName: string) => process.env[envName] ?? null;
410
+ /** @type {TAccessToken} */
411
+ const token = /** @type {TAccessToken} */ (process.env.OAUTH_TOKEN ?? "token.token.token");
412
+ const ID_SomeEVECharacter = (() => {
413
+ if (token !== "token.token.token") {
414
+ const payload = getJWTPayload(token);
415
+ return +payload.sub.split(":")[2];
416
+ }
417
+ return 9000;
418
+ })();
363
419
  /**
364
420
  * #### Fire a request that does not require authentication.
365
421
  *
422
+ * + __CAVEAT:__ This function should only be used for testing.
423
+ *
366
424
  * @param {TPrependParams} fn
367
425
  * @returns {Promise<void>}
368
426
  */
369
427
  export async function fireRequestsDoesNotRequireAuth(fn) {
370
428
  const { clog, rlog } = getLogger();
371
- const ID_SomeEVECharacter = 90000;
372
429
  const ID_CCP_Zoetrope = 2112625428;
373
430
  try {
374
431
  // - - - - - - - - - - - -
@@ -391,17 +448,21 @@ export async function fireRequestsDoesNotRequireAuth(fn) {
391
448
  rlog("get:/incursions/".green);
392
449
  await fireWithoutAuth(fn, "get", "/incursions/").then(log);
393
450
  if (is("withError")) {
451
+ log(`Try character ${ID_SomeEVECharacter} assets request`);
394
452
  await fireWithoutAuth(fn, "get", "/characters/{character_id}/assets/", {
395
- auth: true,
453
+ auth: true, token,
396
454
  pathParams: ID_SomeEVECharacter,
397
- token
398
455
  }).then(assets => {
399
456
  log(assets.slice(0, 5));
400
457
  });
401
- await fireWithoutAuth(fn, "delete", `/characters/${1234}/fittings/${56789}/`, {
458
+ log(`Try character ${ID_SomeEVECharacter} fittings request`);
459
+ await fireWithoutAuth(fn, "get", `/characters/${ID_SomeEVECharacter}/fittings/`, {
402
460
  auth: true,
403
- // pathParams: [1234, 56789], // ✅ At this point, the expected semantic error is successfully triggered as intended.
404
- }).catch(log);
461
+ // pathParams: [ID_SomeEVECharacter, 56789], // ✅ At this point, the expected semantic error is successfully triggered as intended.
462
+ token
463
+ }).then(fittings => {
464
+ log(fittings.slice(0, 5));
465
+ });
405
466
  }
406
467
  }
407
468
  // - - - - - - - - - - - -
@@ -434,8 +495,7 @@ export async function fireRequestsDoesNotRequireAuth(fn) {
434
495
  // Authentication is required, so an error will occur.
435
496
  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
436
497
  let willFailed = await fireWithoutAuth(fn, "get", `/characters/${ID_SomeEVECharacter}/ship/`, {
437
- auth: true,
438
- token //: "token.token.token"
498
+ auth: true, token
439
499
  });
440
500
  log(`get:/characters/${ID_SomeEVECharacter}/ship/, returns:`, willFailed);
441
501
  // 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.7",
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