@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,7 +1,7 @@
1
1
  {
2
2
  "author": "Yusuf Yasir KAYGUSUZ",
3
3
  "name": "@xrystal/core",
4
- "version": "3.9.3",
4
+ "version": "3.9.4",
5
5
  "description": "Project core for xrystal",
6
6
  "publishConfig": {
7
7
  "access": "public",
@@ -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, string>;
6
+ headers: Record<string, any>;
8
7
  body?: any;
9
- params?: Record<string, string>;
10
- t?: Function;
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 || (ctx.request?.headers ? Object.fromEntries(ctx.request.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: this.locals,
35
- status: (code) => {
23
+ locals: {},
24
+ status(code) {
36
25
  this.locals._code = code;
37
- return this.res;
26
+ return this;
38
27
  },
39
- send: (data) => {
40
- if (this.protocol === ProtocolEnum.WEBSOCKET) {
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: (data) => this.res.send(data)
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: this.locals,
65
- status: (code) => {
66
- if (res && typeof res.status === 'function') {
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: (data) => {
72
- if (res && typeof res.send === 'function') {
59
+ send(data) {
60
+ if (res?.send)
73
61
  return res.send(data);
74
- }
75
62
  return data;
76
63
  },
77
- json: (data) => {
78
- if (res && typeof res.json === 'function') {
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
- try {
64
- const { version, ...fetchOptions } = options;
65
- const response = await fetch(url, { ...fetchOptions, headers, signal: controller.signal });
66
- clearTimeout(timeoutId);
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
- store = ClientStore.get(this.clientName);
94
- if (store.accessToken) {
129
+ const freshStore = ClientStore.get(this.clientName);
130
+ if (freshStore.accessToken) {
95
131
  const retryHeaders = new Headers(options.headers);
96
- injectToken(retryHeaders, store.accessToken);
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); }