@xrystal/core 3.9.3 → 3.9.4
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/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { ProtocolEnum } from '../../index';
|
|
2
2
|
export interface CustomRequest {
|
|
3
3
|
accounts?: any;
|
|
4
|
-
query: Record<string, any>;
|
|
5
4
|
url: string;
|
|
6
5
|
method: string;
|
|
7
|
-
headers: Record<string,
|
|
6
|
+
headers: Record<string, any>;
|
|
8
7
|
body?: any;
|
|
9
|
-
params
|
|
10
|
-
|
|
8
|
+
params: Record<string, any>;
|
|
9
|
+
query: Record<string, any>;
|
|
10
|
+
t?: any;
|
|
11
11
|
}
|
|
12
12
|
export interface CustomResponse {
|
|
13
13
|
status: (code: number) => CustomResponse;
|
|
@@ -15,45 +15,11 @@ export interface CustomResponse {
|
|
|
15
15
|
json: (data: any) => any;
|
|
16
16
|
locals: Record<string, any>;
|
|
17
17
|
}
|
|
18
|
-
export interface ReturnChecksCallbackFuncInterface {
|
|
19
|
-
status?: boolean;
|
|
20
|
-
message?: string;
|
|
21
|
-
payload?: any;
|
|
22
|
-
code?: number;
|
|
23
|
-
response?: Function;
|
|
24
|
-
invalid?: boolean;
|
|
25
|
-
}
|
|
26
|
-
export interface ReturnLogicCallbackFuncInterface {
|
|
27
|
-
status?: boolean;
|
|
28
|
-
message?: string;
|
|
29
|
-
payload?: any;
|
|
30
|
-
code?: number;
|
|
31
|
-
response?: Function;
|
|
32
|
-
}
|
|
33
|
-
export interface ReturnResponseCallbackFuncInterface {
|
|
34
|
-
message: Array<string>;
|
|
35
|
-
response?: Function;
|
|
36
|
-
}
|
|
37
|
-
export type Sort = {
|
|
38
|
-
key: string;
|
|
39
|
-
value: number;
|
|
40
|
-
};
|
|
41
18
|
declare abstract class Controller {
|
|
42
19
|
private logger;
|
|
43
20
|
protected protocol: ProtocolEnum | null;
|
|
44
21
|
protected req: CustomRequest | null;
|
|
45
22
|
protected res: CustomResponse | null;
|
|
46
|
-
protected locals: Record<string, any>;
|
|
47
|
-
protected accounts: any;
|
|
48
|
-
protected startDate: string | null;
|
|
49
|
-
protected date: string | null;
|
|
50
|
-
protected dateStart: string | null;
|
|
51
|
-
protected endDate: string | null;
|
|
52
|
-
protected sort: Sort | null;
|
|
53
|
-
protected offset: string | null;
|
|
54
|
-
protected limit: string | null;
|
|
55
|
-
defaultLimitSize: number;
|
|
56
|
-
maxLimitSize: number;
|
|
57
23
|
constructor({ protocol, req, res, ctx }: {
|
|
58
24
|
protocol: ProtocolEnum;
|
|
59
25
|
req?: any;
|
|
@@ -62,7 +28,6 @@ declare abstract class Controller {
|
|
|
62
28
|
});
|
|
63
29
|
protected responseProtocolSwitch: ({ res, resStatus, context, req }: any) => Promise<any>;
|
|
64
30
|
protected parsedQuerys: (url: string) => Record<string, any>;
|
|
65
|
-
protected log: (level: string, message: string) => Promise<void>;
|
|
66
31
|
}
|
|
67
32
|
export declare class ControllerSchema extends Controller {
|
|
68
33
|
schema({ checks, logic, response, }: {
|
|
@@ -6,78 +6,64 @@ class Controller {
|
|
|
6
6
|
protocol = null;
|
|
7
7
|
req = null;
|
|
8
8
|
res = null;
|
|
9
|
-
locals = {};
|
|
10
|
-
accounts = null;
|
|
11
|
-
startDate = null;
|
|
12
|
-
date = null;
|
|
13
|
-
dateStart = null;
|
|
14
|
-
endDate = null;
|
|
15
|
-
sort = null;
|
|
16
|
-
offset = null;
|
|
17
|
-
limit = null;
|
|
18
|
-
defaultLimitSize = 20;
|
|
19
|
-
maxLimitSize = 100;
|
|
20
9
|
constructor({ protocol, req, res, ctx }) {
|
|
21
10
|
this.protocol = protocol;
|
|
22
11
|
if (ctx) {
|
|
23
12
|
this.req = {
|
|
24
13
|
url: ctx.request?.url || '',
|
|
25
14
|
method: ctx.request?.method || '',
|
|
26
|
-
headers: ctx.headers ||
|
|
15
|
+
headers: ctx.headers || {},
|
|
27
16
|
body: ctx.body,
|
|
28
|
-
params: ctx.params,
|
|
17
|
+
params: ctx.params || {},
|
|
29
18
|
query: ctx.query || {},
|
|
30
19
|
accounts: ctx.user || ctx.accounts,
|
|
31
20
|
t: ctx.t
|
|
32
21
|
};
|
|
33
22
|
this.res = {
|
|
34
|
-
locals:
|
|
35
|
-
status
|
|
23
|
+
locals: {},
|
|
24
|
+
status(code) {
|
|
36
25
|
this.locals._code = code;
|
|
37
|
-
return this
|
|
26
|
+
return this;
|
|
38
27
|
},
|
|
39
|
-
send
|
|
40
|
-
if (
|
|
28
|
+
send(data) {
|
|
29
|
+
if (protocol === ProtocolEnum.WEBSOCKET)
|
|
41
30
|
return data;
|
|
42
|
-
}
|
|
43
31
|
return new Response(JSON.stringify(data), {
|
|
44
32
|
status: this.locals._code || 200,
|
|
45
33
|
headers: { 'content-type': 'application/json' }
|
|
46
34
|
});
|
|
47
35
|
},
|
|
48
|
-
json
|
|
36
|
+
json(data) {
|
|
37
|
+
return this.send(data);
|
|
38
|
+
}
|
|
49
39
|
};
|
|
50
40
|
}
|
|
51
41
|
else {
|
|
52
42
|
this.req = {
|
|
53
|
-
url: req?.originalUrl || req?.url,
|
|
54
|
-
method: req?.method,
|
|
43
|
+
url: req?.originalUrl || req?.url || '',
|
|
44
|
+
method: req?.method || 'GET',
|
|
55
45
|
headers: req?.headers || {},
|
|
56
46
|
body: req?.body,
|
|
57
|
-
params: req?.params,
|
|
47
|
+
params: req?.params || {},
|
|
58
48
|
query: req?.query || {},
|
|
59
49
|
accounts: req?.accounts,
|
|
60
50
|
t: req?.t
|
|
61
51
|
};
|
|
62
|
-
this.locals = res?.locals || {};
|
|
63
52
|
this.res = {
|
|
64
|
-
locals:
|
|
65
|
-
status
|
|
66
|
-
if (res
|
|
53
|
+
locals: res?.locals || {},
|
|
54
|
+
status(code) {
|
|
55
|
+
if (res?.status)
|
|
67
56
|
res.status(code);
|
|
68
|
-
|
|
69
|
-
return this.res;
|
|
57
|
+
return this;
|
|
70
58
|
},
|
|
71
|
-
send
|
|
72
|
-
if (res
|
|
59
|
+
send(data) {
|
|
60
|
+
if (res?.send)
|
|
73
61
|
return res.send(data);
|
|
74
|
-
}
|
|
75
62
|
return data;
|
|
76
63
|
},
|
|
77
|
-
json
|
|
78
|
-
if (res
|
|
64
|
+
json(data) {
|
|
65
|
+
if (res?.json)
|
|
79
66
|
return res.json(data);
|
|
80
|
-
}
|
|
81
67
|
return data;
|
|
82
68
|
}
|
|
83
69
|
};
|
|
@@ -91,12 +77,11 @@ class Controller {
|
|
|
91
77
|
const queryString = url.includes('?') ? url.split('?')[1] : '';
|
|
92
78
|
return queryString ? qs.parse(queryString, { decoder: decodeURIComponent }) : {};
|
|
93
79
|
};
|
|
94
|
-
log = async (level, message) => {
|
|
95
|
-
this.logger.winston.log({ level, message });
|
|
96
|
-
};
|
|
97
80
|
}
|
|
98
81
|
export class ControllerSchema extends Controller {
|
|
99
82
|
async schema({ checks, logic, response, }) {
|
|
83
|
+
if (!this.req || !this.res)
|
|
84
|
+
return;
|
|
100
85
|
const payload = { req: this.req, res: this.res };
|
|
101
86
|
const convertedPayload = {
|
|
102
87
|
...payload,
|
|
@@ -168,7 +153,6 @@ export class ControllerSchema extends Controller {
|
|
|
168
153
|
}
|
|
169
154
|
}
|
|
170
155
|
catch (error) {
|
|
171
|
-
await this.log('error', `Schema Error: ${error.message}`);
|
|
172
156
|
return this.res.status(500).send({ status: false, message: error.message });
|
|
173
157
|
}
|
|
174
158
|
}
|
|
@@ -12,6 +12,13 @@ export declare abstract class Client {
|
|
|
12
12
|
protected version: string | null;
|
|
13
13
|
protected timeout: number;
|
|
14
14
|
protected authConfigs: any;
|
|
15
|
+
protected breaker: {
|
|
16
|
+
failures: number;
|
|
17
|
+
lastFailure: number;
|
|
18
|
+
state: "CLOSED" | "OPEN" | "HALF_OPEN";
|
|
19
|
+
threshold: number;
|
|
20
|
+
cooldown: number;
|
|
21
|
+
};
|
|
15
22
|
constructor({ clientName, baseURL, version, timeout, auth }: any);
|
|
16
23
|
protected resolvePath(obj: any, path: string): any;
|
|
17
24
|
static cryptoHashGenerate: ({ algorithm, input, digest }: {
|
|
@@ -24,13 +31,16 @@ export declare class BaseApiClient extends Client {
|
|
|
24
31
|
constructor(config: any);
|
|
25
32
|
request(path: string, options?: RequestInit & {
|
|
26
33
|
version?: string;
|
|
34
|
+
retries?: number;
|
|
27
35
|
}): Promise<Response>;
|
|
36
|
+
private _execute;
|
|
28
37
|
}
|
|
29
38
|
export declare abstract class AuthenticatedApiClient extends BaseApiClient {
|
|
30
39
|
private _authPromise;
|
|
31
40
|
constructor(config: any);
|
|
32
41
|
request(path: string, options?: RequestInit & {
|
|
33
42
|
version?: string;
|
|
43
|
+
retries?: number;
|
|
34
44
|
}): Promise<Response>;
|
|
35
45
|
private _synchronizedAuthentication;
|
|
36
46
|
private internalLogin;
|
|
@@ -17,6 +17,13 @@ export class Client {
|
|
|
17
17
|
version = null;
|
|
18
18
|
timeout = 15000;
|
|
19
19
|
authConfigs;
|
|
20
|
+
breaker = {
|
|
21
|
+
failures: 0,
|
|
22
|
+
lastFailure: 0,
|
|
23
|
+
state: 'CLOSED',
|
|
24
|
+
threshold: 5,
|
|
25
|
+
cooldown: 30000
|
|
26
|
+
};
|
|
20
27
|
constructor({ clientName, baseURL, version, timeout, auth }) {
|
|
21
28
|
this.clientName = clientName;
|
|
22
29
|
this.baseURL = baseURL;
|
|
@@ -24,9 +31,8 @@ export class Client {
|
|
|
24
31
|
this.authConfigs = auth;
|
|
25
32
|
if (timeout)
|
|
26
33
|
this.timeout = timeout;
|
|
27
|
-
if (auth?.token)
|
|
34
|
+
if (auth?.token)
|
|
28
35
|
ClientStore.set(clientName, { accessToken: auth.token });
|
|
29
|
-
}
|
|
30
36
|
}
|
|
31
37
|
resolvePath(obj, path) {
|
|
32
38
|
return path.split('.').reduce((prev, curr) => prev ? prev[curr] : undefined, obj);
|
|
@@ -38,6 +44,45 @@ export class Client {
|
|
|
38
44
|
export class BaseApiClient extends Client {
|
|
39
45
|
constructor(config) { super(config); }
|
|
40
46
|
async request(path, options = {}) {
|
|
47
|
+
if (this.breaker.state === 'OPEN') {
|
|
48
|
+
if (Date.now() - this.breaker.lastFailure > this.breaker.cooldown) {
|
|
49
|
+
this.breaker.state = 'HALF_OPEN';
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
throw new Error(`${this.clientName} circuit is OPEN. Request blocked.`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const maxRetries = options.retries ?? 3;
|
|
56
|
+
let attempt = 0;
|
|
57
|
+
while (attempt < maxRetries) {
|
|
58
|
+
try {
|
|
59
|
+
const response = await this._execute(path, options);
|
|
60
|
+
if (response.ok || response.status === 401) {
|
|
61
|
+
this.breaker.failures = 0;
|
|
62
|
+
this.breaker.state = 'CLOSED';
|
|
63
|
+
return response;
|
|
64
|
+
}
|
|
65
|
+
if ([502, 503, 504].includes(response.status)) {
|
|
66
|
+
throw new Error(`Server Error: ${response.status}`);
|
|
67
|
+
}
|
|
68
|
+
return response;
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
attempt++;
|
|
72
|
+
this.breaker.failures++;
|
|
73
|
+
this.breaker.lastFailure = Date.now();
|
|
74
|
+
if (this.breaker.failures >= this.breaker.threshold) {
|
|
75
|
+
this.breaker.state = 'OPEN';
|
|
76
|
+
}
|
|
77
|
+
if (attempt >= maxRetries)
|
|
78
|
+
throw error;
|
|
79
|
+
const backoff = Math.pow(2, attempt) * 500;
|
|
80
|
+
await new Promise(res => setTimeout(res, backoff));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
throw new Error(`${this.clientName} request failed after ${maxRetries} attempts.`);
|
|
84
|
+
}
|
|
85
|
+
async _execute(path, options) {
|
|
41
86
|
const base = this.baseURL.replace(/\/$/, '');
|
|
42
87
|
const activeVersion = options.version !== undefined ? options.version : this.version;
|
|
43
88
|
const ver = activeVersion ? `/${activeVersion.replace(/^\//, '')}` : '';
|
|
@@ -60,16 +105,10 @@ export class BaseApiClient extends Client {
|
|
|
60
105
|
}
|
|
61
106
|
const controller = new AbortController();
|
|
62
107
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return response;
|
|
68
|
-
}
|
|
69
|
-
catch (error) {
|
|
70
|
-
this.logger.winston.error(`${this.clientName} HTTP Request Failure: ${error.message}`, { url });
|
|
71
|
-
throw error;
|
|
72
|
-
}
|
|
108
|
+
const { version, retries, ...fetchOptions } = options;
|
|
109
|
+
const response = await fetch(url, { ...fetchOptions, headers, signal: controller.signal });
|
|
110
|
+
clearTimeout(timeoutId);
|
|
111
|
+
return response;
|
|
73
112
|
}
|
|
74
113
|
}
|
|
75
114
|
export class AuthenticatedApiClient extends BaseApiClient {
|
|
@@ -78,10 +117,7 @@ export class AuthenticatedApiClient extends BaseApiClient {
|
|
|
78
117
|
async request(path, options = {}) {
|
|
79
118
|
const headerName = this.authConfigs?.header || 'Authorization';
|
|
80
119
|
const prefix = this.authConfigs?.prefix ?? 'Bearer';
|
|
81
|
-
const injectToken = (h, t) => {
|
|
82
|
-
const value = prefix ? `${prefix} ${t}` : t;
|
|
83
|
-
h.set(headerName, value);
|
|
84
|
-
};
|
|
120
|
+
const injectToken = (h, t) => h.set(headerName, prefix ? `${prefix} ${t}` : t);
|
|
85
121
|
let store = ClientStore.get(this.clientName);
|
|
86
122
|
const headers = new Headers(options.headers || {});
|
|
87
123
|
if (store.accessToken)
|
|
@@ -90,10 +126,10 @@ export class AuthenticatedApiClient extends BaseApiClient {
|
|
|
90
126
|
let response = await super.request(path, options);
|
|
91
127
|
if (response.status === 401) {
|
|
92
128
|
await this._synchronizedAuthentication();
|
|
93
|
-
|
|
94
|
-
if (
|
|
129
|
+
const freshStore = ClientStore.get(this.clientName);
|
|
130
|
+
if (freshStore.accessToken) {
|
|
95
131
|
const retryHeaders = new Headers(options.headers);
|
|
96
|
-
injectToken(retryHeaders,
|
|
132
|
+
injectToken(retryHeaders, freshStore.accessToken);
|
|
97
133
|
return super.request(path, { ...options, headers: retryHeaders });
|
|
98
134
|
}
|
|
99
135
|
}
|
|
@@ -128,9 +164,7 @@ export class AuthenticatedApiClient extends BaseApiClient {
|
|
|
128
164
|
throw error;
|
|
129
165
|
}
|
|
130
166
|
}
|
|
131
|
-
async authentication() {
|
|
132
|
-
return true;
|
|
133
|
-
}
|
|
167
|
+
async authentication() { return true; }
|
|
134
168
|
}
|
|
135
169
|
export class SoapClient extends Client {
|
|
136
170
|
constructor(config) { super(config); }
|