eve-esi-types 2.2.6 → 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/README.md +25 -0
- package/esi-types-util.md +5 -1
- package/lib/esi-error-types.d.ts +104 -0
- package/lib/request-api.d.mts +27 -0
- package/lib/request-api.mjs +79 -0
- package/{rq-util.d.mts → lib/rq-util.d.mts} +58 -8
- package/{rq-util.mjs → lib/rq-util.mjs} +125 -15
- package/lib/tagged-request-api.d.mts +35 -0
- package/lib/tagged-request-api.mjs +74 -0
- package/minimal-rq.d.mts +1 -29
- package/minimal-rq.mjs +8 -52
- package/package.json +4 -2
- package/tagged-rq.d.mts +1 -0
- package/tagged-rq.mjs +23 -0
- package/tsconfig.json +3 -2
- package/v2/esi-tagged-types.d.ts +153 -0
- package/v2/get_characters_character_id_skillqueue_ok.d.ts +1 -1
- package/v2/index.d.ts +24 -18
- package/v2/response-map.d.ts +207 -7
- package/v2/types-index.d.ts +2 -2
- package/v2/util.d.ts +51 -0
- package/v2.d.mts +5 -1
- package/v2.mjs +16 -46
package/README.md
CHANGED
|
@@ -32,6 +32,31 @@ export declare function fire<
|
|
|
32
32
|
>(mthd: M, endp: EP, pathParams?: P2, opt?: Opt): Promise<R>;
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
+
## New Features in v2.3.0
|
|
36
|
+
|
|
37
|
+
### ESI Tagged Types
|
|
38
|
+
|
|
39
|
+
> Introduced intuitive ESI requests handling using "tags" from the EVE Swagger JSON.
|
|
40
|
+
|
|
41
|
+
### injectESIRequestBody
|
|
42
|
+
|
|
43
|
+
> Utilized `injectESIRequestBody` to generate ESI request API objects with narrowed endpoints by accessing camel-cased "tags".
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import * as taggedApi from "eve-esi-types/lib/tagged-request-api.mjs";
|
|
47
|
+
|
|
48
|
+
const esiRequest = taggedApi.injectESIRequestBody(...);
|
|
49
|
+
const ret = await esiRequest.universe.get("/universe/structures/", { query: { filter: "market" }});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
+ or
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
// Minimal default ESI request body implementation
|
|
56
|
+
import { esi } from "eve-esi-types/lib/tagged-request-api.mjs";
|
|
57
|
+
|
|
58
|
+
const ret = await esi.universe.get("/universe/structures/", { query: { filter: "market" }});
|
|
59
|
+
```
|
|
35
60
|
|
|
36
61
|
## References
|
|
37
62
|
|
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}}
|
|
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 {}
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
import * as util from "./rq-util.mjs";
|
|
9
|
+
import type { IESIRequestFunction } from "../v2";
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {import("../v2").IESIRequestFunction<util.ESIRequestOptions>} IESIRequestFunction
|
|
12
|
+
* @typedef {import("../v2").TESIRequestFunctionMethods<util.ESIRequestOptions>} TESIRequestFunctionMethods
|
|
13
|
+
*/
|
|
14
|
+
/** #### Sample of `TESIRequestFunctionSignature`
|
|
15
|
+
*
|
|
16
|
+
* + This is a minimal implementation using `TESIRequestFunctionSignature`.
|
|
17
|
+
* If the response contains "page", only the first page can be retrieved.
|
|
18
|
+
*
|
|
19
|
+
* @type {IESIRequestFunction}
|
|
20
|
+
* @param method - The HTTP method to use for the request
|
|
21
|
+
* @param endpoint - The Path of the ESI endpoint to send the request to
|
|
22
|
+
* @param pathParams - An object of parameters to include in the request
|
|
23
|
+
* @param options - An object of options to include in the request
|
|
24
|
+
* @returns A Promise object containing the response data
|
|
25
|
+
* @throws {ESIRequesError}
|
|
26
|
+
*/
|
|
27
|
+
export declare const request: IESIRequestFunction<util.ESIRequestOptions>;
|
|
@@ -0,0 +1,79 @@
|
|
|
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
|
+
/// <reference types="../v2"/>
|
|
9
|
+
// - - - - - - - - - - - - - - - - - - - -
|
|
10
|
+
// imports
|
|
11
|
+
// - - - - - - - - - - - - - - - - - - - -
|
|
12
|
+
import * as util from "./rq-util.mjs";
|
|
13
|
+
// - - - - - - - - - - - - - - - - - - - -
|
|
14
|
+
// constants, types
|
|
15
|
+
// - - - - - - - - - - - - - - - - - - - -
|
|
16
|
+
// shorthands
|
|
17
|
+
const log = console.log;
|
|
18
|
+
const DEBUG = util.isDebug();
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {import("../v2").IESIRequestFunction<util.ESIRequestOptions>} IESIRequestFunction
|
|
21
|
+
* @typedef {import("../v2").TESIRequestFunctionMethods<util.ESIRequestOptions>} TESIRequestFunctionMethods
|
|
22
|
+
*/
|
|
23
|
+
// - - - - - - - - - - - - - - - - - - - -
|
|
24
|
+
// main functions
|
|
25
|
+
// - - - - - - - - - - - - - - - - - - - -
|
|
26
|
+
/** #### Sample of `TESIRequestFunctionSignature`
|
|
27
|
+
*
|
|
28
|
+
* + This is a minimal implementation using `TESIRequestFunctionSignature`.
|
|
29
|
+
* If the response contains "page", only the first page can be retrieved.
|
|
30
|
+
*
|
|
31
|
+
* @type {IESIRequestFunction}
|
|
32
|
+
* @param method - The HTTP method to use for the request
|
|
33
|
+
* @param endpoint - The Path of the ESI endpoint to send the request to
|
|
34
|
+
* @param pathParams - An object of parameters to include in the request
|
|
35
|
+
* @param options - An object of options to include in the request
|
|
36
|
+
* @returns A Promise object containing the response data
|
|
37
|
+
* @throws {ESIRequesError}
|
|
38
|
+
*/
|
|
39
|
+
// @ts-expect-error
|
|
40
|
+
export const request = async (method, endpoint, pathParams, opt) => {
|
|
41
|
+
if (typeof pathParams === "number") {
|
|
42
|
+
// @ts-expect-error
|
|
43
|
+
pathParams = [pathParams];
|
|
44
|
+
}
|
|
45
|
+
if (Array.isArray(pathParams)) {
|
|
46
|
+
// @ts-expect-error actualy endp is string
|
|
47
|
+
endpoint = util.replaceCbt(endpoint, pathParams);
|
|
48
|
+
}
|
|
49
|
+
// When only options are provided
|
|
50
|
+
const actualOpt = /** @type {NonNullable<typeof opt>} */ (opt || pathParams || {});
|
|
51
|
+
const { rqopt, qss } = util.initOptions(method, actualOpt);
|
|
52
|
+
// @ts-expect-error actualy endp is string
|
|
53
|
+
const endpointUrl = util.curl(endpoint);
|
|
54
|
+
const up = new URLSearchParams(qss);
|
|
55
|
+
const url = `${endpointUrl}${up.size ? `?${up}` : ""}`;
|
|
56
|
+
DEBUG && log(url);
|
|
57
|
+
try {
|
|
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);
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
log(e);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
//
|
|
73
|
+
// implements rest methods of `request` (IESIRequestFunction)
|
|
74
|
+
//
|
|
75
|
+
/** @type {TESIEntryMethod[]} */ (["get", "post", "put", "delete"]).forEach((method) => {
|
|
76
|
+
request[method] = /** @type {TESIRequestFunctionEachMethod<typeof method>} */ (function (endpoint, params, opt) {
|
|
77
|
+
return this(method, endpoint, params, opt);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
7
7
|
*/
|
|
8
8
|
import "colors.ts";
|
|
9
|
-
import type { TESIRequestFunctionMethods } from "
|
|
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,11 +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
|
+
*/
|
|
111
|
+
export declare const is: (opt: string) => boolean;
|
|
65
112
|
/**
|
|
66
113
|
* @param {string} method
|
|
67
114
|
* @param {ESIRequestOptions} opt
|
|
68
|
-
* @returns
|
|
115
|
+
* @returns {{ rqopt: RequestInit, qss: Record<string, string> }}
|
|
69
116
|
*/
|
|
70
117
|
export declare const initOptions: (method: string, opt: ESIRequestOptions) => {
|
|
71
118
|
rqopt: RequestInit;
|
|
@@ -75,12 +122,13 @@ export declare const initOptions: (method: string, opt: ESIRequestOptions) => {
|
|
|
75
122
|
* fetch the extra pages
|
|
76
123
|
*
|
|
77
124
|
* + if the `x-pages` header property ware more than 1
|
|
78
|
-
*
|
|
125
|
+
* @template {any} T
|
|
79
126
|
* @param {string} endpointUrl
|
|
80
127
|
* @param {RequestInit} rqopt request options
|
|
81
128
|
* @param {URLSearchParams} rqp queries
|
|
82
129
|
* @param {number} pc pageCount
|
|
83
130
|
* @param {(minus?: number) => void=} increment
|
|
131
|
+
* @returns {Promise<T | null>}
|
|
84
132
|
*/
|
|
85
133
|
export declare const fetchP: <T extends unknown>(endpointUrl: string, rqopt: RequestInit, rqp: URLSearchParams, pc: number, increment?: (minus?: Truthy) => void) => Promise<T | null>;
|
|
86
134
|
/** ### replace (C)urly (B)races (T)oken
|
|
@@ -92,13 +140,14 @@ export declare const fetchP: <T extends unknown>(endpointUrl: string, rqopt: Req
|
|
|
92
140
|
*
|
|
93
141
|
* @param {string} endpoint e.g - "/characters/{character_id}/"
|
|
94
142
|
* @param {number[]} ids
|
|
95
|
-
* @returns fragment of qualified endpoint uri or null.
|
|
143
|
+
* @returns {string | null} fragment of qualified endpoint uri or null.
|
|
96
144
|
*/
|
|
97
145
|
export declare const replaceCbt: (endpoint: string, ids: number[]) => string;
|
|
98
146
|
/**
|
|
99
147
|
*
|
|
100
148
|
* @param {string} endp this means endpoint url fragment like `/characters/{character_id}/` or `/characters/{character_id}/agents_research/`
|
|
101
149
|
* + The version parameter is forced to apply `latest`
|
|
150
|
+
* @returns {string}
|
|
102
151
|
*/
|
|
103
152
|
export declare const curl: (endp: string) => string;
|
|
104
153
|
/**
|
|
@@ -115,5 +164,6 @@ export declare function getLogger(): {
|
|
|
115
164
|
* #### Fire a request that does not require authentication.
|
|
116
165
|
*
|
|
117
166
|
* @param {TESIRequestFunctionSignature<ESIRequestOptions> | TESIRequestFunctionMethods} fn
|
|
167
|
+
* @returns {Promise<void>}
|
|
118
168
|
*/
|
|
119
169
|
export declare function fireRequestsDoesNotRequireAuth(fn: TESIRequestFunctionSignature<ESIRequestOptions> | TESIRequestFunctionMethods<ESIRequestOptions>): Promise<void>;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// https://opensource.org/licenses/mit-license.php
|
|
6
6
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
7
7
|
*/
|
|
8
|
-
/// <reference types="
|
|
8
|
+
/// <reference types="../v2"/>
|
|
9
9
|
// - - - - - - - - - - - - - - - - - - - -
|
|
10
10
|
// imports
|
|
11
11
|
// - - - - - - - - - - - - - - - - - - - -
|
|
@@ -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,13 +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
|
+
*/
|
|
162
|
+
export const is = (opt) => process.argv.includes(`-${opt}`);
|
|
52
163
|
/**
|
|
53
164
|
* @param {string} method
|
|
54
165
|
* @param {ESIRequestOptions} opt
|
|
55
|
-
* @returns
|
|
166
|
+
* @returns {{ rqopt: RequestInit, qss: Record<string, string> }}
|
|
56
167
|
*/
|
|
57
168
|
export const initOptions = (method, opt) => {
|
|
58
169
|
/** @type {RequestInit} */
|
|
@@ -63,6 +174,7 @@ export const initOptions = (method, opt) => {
|
|
|
63
174
|
signal: opt.cancelable?.signal,
|
|
64
175
|
headers: {}
|
|
65
176
|
};
|
|
177
|
+
/** @type {Record<string, string>} */
|
|
66
178
|
const qss = {
|
|
67
179
|
// language: "en",
|
|
68
180
|
};
|
|
@@ -86,12 +198,13 @@ export const initOptions = (method, opt) => {
|
|
|
86
198
|
* fetch the extra pages
|
|
87
199
|
*
|
|
88
200
|
* + if the `x-pages` header property ware more than 1
|
|
89
|
-
*
|
|
201
|
+
* @template {any} T
|
|
90
202
|
* @param {string} endpointUrl
|
|
91
203
|
* @param {RequestInit} rqopt request options
|
|
92
204
|
* @param {URLSearchParams} rqp queries
|
|
93
205
|
* @param {number} pc pageCount
|
|
94
206
|
* @param {(minus?: number) => void=} increment
|
|
207
|
+
* @returns {Promise<T | null>}
|
|
95
208
|
*/
|
|
96
209
|
export const fetchP = async (endpointUrl, rqopt, rqp, pc, increment = () => { }) => {
|
|
97
210
|
const rqs = [];
|
|
@@ -112,7 +225,7 @@ export const fetchP = async (endpointUrl, rqopt, rqp, pc, increment = () => { })
|
|
|
112
225
|
for (let i = 0, end = jsons.length; i < end;) {
|
|
113
226
|
combined = combined.concat(jsons[i++]);
|
|
114
227
|
}
|
|
115
|
-
return combined;
|
|
228
|
+
return /** @type {T} */ (combined);
|
|
116
229
|
}
|
|
117
230
|
LOG && log("> > > pages result are object < < < --", jsons);
|
|
118
231
|
return null;
|
|
@@ -127,22 +240,17 @@ export const fetchP = async (endpointUrl, rqopt, rqp, pc, increment = () => { })
|
|
|
127
240
|
*
|
|
128
241
|
* @param {string} endpoint e.g - "/characters/{character_id}/"
|
|
129
242
|
* @param {number[]} ids
|
|
130
|
-
* @returns fragment of qualified endpoint uri or null.
|
|
243
|
+
* @returns {string | null} fragment of qualified endpoint uri or null.
|
|
131
244
|
*/
|
|
132
245
|
export const replaceCbt = (endpoint, ids) => {
|
|
133
|
-
const re = /{([\w]+)}/g;
|
|
134
|
-
/** @type {RegExpExecArray?} */
|
|
135
|
-
let m;
|
|
136
246
|
let idx = 0;
|
|
137
|
-
|
|
138
|
-
endpoint = endpoint.replace(m[0], ids[idx++] + "");
|
|
139
|
-
}
|
|
140
|
-
return endpoint;
|
|
247
|
+
return endpoint.replace(/{([\w]+)}/g, $0 => ids[idx++] + "");
|
|
141
248
|
};
|
|
142
249
|
/**
|
|
143
250
|
*
|
|
144
251
|
* @param {string} endp this means endpoint url fragment like `/characters/{character_id}/` or `/characters/{character_id}/agents_research/`
|
|
145
252
|
* + The version parameter is forced to apply `latest`
|
|
253
|
+
* @returns {string}
|
|
146
254
|
*/
|
|
147
255
|
export const curl = (endp) => {
|
|
148
256
|
endp = endp.replace(/^\/+|\/+$/g, "");
|
|
@@ -159,7 +267,7 @@ export async function getSDEVersion() {
|
|
|
159
267
|
const res = await fetch(sdeZipUrl, { method: "head", mode: "cors" });
|
|
160
268
|
const date = res.headers.get("last-modified");
|
|
161
269
|
if (date) {
|
|
162
|
-
const YMD = new Date(date).toLocaleDateString(
|
|
270
|
+
const YMD = new Date(date).toLocaleDateString("en-CA").replace(/-/g, "");
|
|
163
271
|
return `sde-${YMD}-TRANQUILITY`;
|
|
164
272
|
}
|
|
165
273
|
else {
|
|
@@ -174,12 +282,12 @@ export async function getSDEVersion() {
|
|
|
174
282
|
}
|
|
175
283
|
export function getLogger() {
|
|
176
284
|
const clog = console.log.bind(console, '- - -> Get the character data of "CCP Zoetrope"'.magenta);
|
|
177
|
-
const rlog = console.log.bind(console,
|
|
285
|
+
const rlog = console.log.bind(console, "- - -> Run ESI request".cyan);
|
|
178
286
|
return { clog, rlog };
|
|
179
287
|
}
|
|
180
288
|
/**
|
|
181
289
|
* Need typescript v5.5 later
|
|
182
|
-
* @import * as ESI from "
|
|
290
|
+
* @import * as ESI from "../v2";
|
|
183
291
|
* @typedef {ESI.TESIResponseOKMap} TESIResponseOKMap
|
|
184
292
|
* @typedef {ESI.IESIRequestFunction<ESIRequestOptions>} IESIRequestFunction
|
|
185
293
|
* @typedef {ESI.TESIRequestFunctionMethods<ESIRequestOptions>} TESIRequestFunctionMethods
|
|
@@ -211,6 +319,7 @@ function fireWithoutAuth(fn, method, endpoint, pathParams, opt) {
|
|
|
211
319
|
* #### Fire a request that does not require authentication.
|
|
212
320
|
*
|
|
213
321
|
* @param {TESIRequestFunctionSignature<ESIRequestOptions> | TESIRequestFunctionMethods} fn
|
|
322
|
+
* @returns {Promise<void>}
|
|
214
323
|
*/
|
|
215
324
|
export async function fireRequestsDoesNotRequireAuth(fn) {
|
|
216
325
|
const { clog, rlog } = getLogger();
|
|
@@ -277,6 +386,7 @@ export async function fireRequestsDoesNotRequireAuth(fn) {
|
|
|
277
386
|
},
|
|
278
387
|
});
|
|
279
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.
|
|
280
390
|
await fireWithoutAuth(fn, "get", "/characters/{character_id}/search/");
|
|
281
391
|
log(ok);
|
|
282
392
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
/// <reference types="../v2/esi-tagged-types"/>
|
|
9
|
+
import type { ESIRequestOptions } from "./rq-util.mjs";
|
|
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.
|
|
12
|
+
*
|
|
13
|
+
* + The `ESI request API object` constructed by `injectESIRequestBody` lists narrowed endpoints by accessing the camel-cased "tags" members.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* import * as taggedApi from "eve-esi-types/lib/tagged-request-api.mjs";
|
|
17
|
+
*
|
|
18
|
+
* const esiRequest = taggedApi.injectESIRequestBody(...);
|
|
19
|
+
* const ret = await esiRequest.universe.get("/universe/structures/", { query: { filter: "market" }});
|
|
20
|
+
*
|
|
21
|
+
* @template {Record<string, unknown>} Opt
|
|
22
|
+
* @param {TESIRequestFunctionSignature<{}>} requestBody
|
|
23
|
+
* @returns {XESI.TaggedESIRequestMap}
|
|
24
|
+
* @since 2.3
|
|
25
|
+
*/
|
|
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
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* Injects the minimal implementation of ESI requests into `XESI.TaggedESIRequestMap`.
|
|
32
|
+
* @since 2.3
|
|
33
|
+
* @type {XESI.TaggedESIRequestMap<ESIRequestOptions>}
|
|
34
|
+
*/
|
|
35
|
+
export declare const esi: import("../v2/esi-tagged-types").TaggedESIRequestMap<ESIRequestOptions>;
|