@xrystal/core 3.9.3 → 3.9.6

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.6",
5
5
  "description": "Project core for xrystal",
6
6
  "publishConfig": {
7
7
  "access": "public",
@@ -7,6 +7,7 @@ export default class ConfigsService {
7
7
  this._systemService = systemService;
8
8
  const rawConfigs = tmp?.configs || tmp?.configs || {};
9
9
  this.config = {
10
+ debug: process.env.SYSTEM_LOGGER_LAYER,
10
11
  rootFolderPath: rawConfigs.rootFolderPath || 'x',
11
12
  projectName: tmp.project.name,
12
13
  serviceName: rawConfigs.service,
@@ -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
  }
@@ -11,8 +11,16 @@ export declare abstract class Client {
11
11
  protected baseURL: string;
12
12
  protected version: string | null;
13
13
  protected timeout: number;
14
+ protected debug: boolean;
14
15
  protected authConfigs: any;
15
- constructor({ clientName, baseURL, version, timeout, auth }: any);
16
+ protected breaker: {
17
+ failures: number;
18
+ lastFailure: number;
19
+ state: "CLOSED" | "OPEN" | "HALF_OPEN";
20
+ threshold: number;
21
+ cooldown: number;
22
+ };
23
+ constructor({ clientName, baseURL, version, timeout, auth, debug }: any);
16
24
  protected resolvePath(obj: any, path: string): any;
17
25
  static cryptoHashGenerate: ({ algorithm, input, digest }: {
18
26
  algorithm: string;
@@ -24,13 +32,18 @@ export declare class BaseApiClient extends Client {
24
32
  constructor(config: any);
25
33
  request(path: string, options?: RequestInit & {
26
34
  version?: string;
35
+ retries?: number;
36
+ debug?: boolean;
27
37
  }): Promise<Response>;
38
+ private _execute;
28
39
  }
29
40
  export declare abstract class AuthenticatedApiClient extends BaseApiClient {
30
41
  private _authPromise;
31
42
  constructor(config: any);
32
43
  request(path: string, options?: RequestInit & {
33
44
  version?: string;
45
+ retries?: number;
46
+ debug?: boolean;
34
47
  }): Promise<Response>;
35
48
  private _synchronizedAuthentication;
36
49
  private internalLogin;
@@ -1,7 +1,7 @@
1
1
  import { createHash, createHmac } from 'node:crypto';
2
2
  import soap from 'soap';
3
3
  import { ConfigsService, LoggerService } from '../../../loader';
4
- import { x } from '../../index';
4
+ import { LoggerLayerEnum, x } from '../../index';
5
5
  export class ClientStore {
6
6
  static _store = {};
7
7
  static get(clientName) { return this._store[clientName] || {}; }
@@ -16,17 +16,25 @@ export class Client {
16
16
  baseURL;
17
17
  version = null;
18
18
  timeout = 15000;
19
+ debug = Number(this.configService.all.debug) >= LoggerLayerEnum.DEBUG;
19
20
  authConfigs;
20
- constructor({ clientName, baseURL, version, timeout, auth }) {
21
+ breaker = {
22
+ failures: 0,
23
+ lastFailure: 0,
24
+ state: 'CLOSED',
25
+ threshold: 5,
26
+ cooldown: 30000
27
+ };
28
+ constructor({ clientName, baseURL, version, timeout, auth, debug }) {
21
29
  this.clientName = clientName;
22
30
  this.baseURL = baseURL;
23
31
  this.version = version || null;
24
32
  this.authConfigs = auth;
33
+ this.debug = debug ?? this.debug;
25
34
  if (timeout)
26
35
  this.timeout = timeout;
27
- if (auth?.token) {
36
+ if (auth?.token)
28
37
  ClientStore.set(clientName, { accessToken: auth.token });
29
- }
30
38
  }
31
39
  resolvePath(obj, path) {
32
40
  return path.split('.').reduce((prev, curr) => prev ? prev[curr] : undefined, obj);
@@ -38,6 +46,44 @@ export class Client {
38
46
  export class BaseApiClient extends Client {
39
47
  constructor(config) { super(config); }
40
48
  async request(path, options = {}) {
49
+ if (this.breaker.state === 'OPEN') {
50
+ if (Date.now() - this.breaker.lastFailure > this.breaker.cooldown) {
51
+ this.breaker.state = 'HALF_OPEN';
52
+ }
53
+ else {
54
+ throw new Error(`${this.clientName} circuit is OPEN. Request blocked.`);
55
+ }
56
+ }
57
+ const maxRetries = options.retries ?? 3;
58
+ let attempt = 0;
59
+ while (attempt < maxRetries) {
60
+ try {
61
+ const response = await this._execute(path, options);
62
+ if (response.ok || response.status === 401) {
63
+ this.breaker.failures = 0;
64
+ this.breaker.state = 'CLOSED';
65
+ return response;
66
+ }
67
+ if ([502, 503, 504].includes(response.status)) {
68
+ throw new Error(`Server Error: ${response.status}`);
69
+ }
70
+ return response;
71
+ }
72
+ catch (error) {
73
+ attempt++;
74
+ this.breaker.failures++;
75
+ this.breaker.lastFailure = Date.now();
76
+ if (this.breaker.failures >= this.breaker.threshold)
77
+ this.breaker.state = 'OPEN';
78
+ if (attempt >= maxRetries)
79
+ throw error;
80
+ const backoff = Math.pow(2, attempt) * 500;
81
+ await new Promise(res => setTimeout(res, backoff));
82
+ }
83
+ }
84
+ throw new Error(`${this.clientName} request failed after ${maxRetries} attempts.`);
85
+ }
86
+ async _execute(path, options) {
41
87
  const base = this.baseURL.replace(/\/$/, '');
42
88
  const activeVersion = options.version !== undefined ? options.version : this.version;
43
89
  const ver = activeVersion ? `/${activeVersion.replace(/^\//, '')}` : '';
@@ -58,18 +104,21 @@ export class BaseApiClient extends Client {
58
104
  headers.set('x-internal-timestamp', timestamp);
59
105
  headers.set('x-internal-client', this.clientName);
60
106
  }
107
+ const isDebugMode = options.debug !== undefined ? options.debug : this.debug;
108
+ if (isDebugMode) {
109
+ this.logger.winston.info(`${this.clientName} Request Details`, {
110
+ method: options.method || 'GET',
111
+ url,
112
+ headers: Object.fromEntries(headers.entries()),
113
+ body: options.body ? (typeof options.body === 'string' ? JSON.parse(options.body) : 'Data') : null
114
+ });
115
+ }
61
116
  const controller = new AbortController();
62
117
  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
- }
118
+ const { version, retries, debug, ...fetchOptions } = options;
119
+ const response = await fetch(url, { ...fetchOptions, headers, signal: controller.signal });
120
+ clearTimeout(timeoutId);
121
+ return response;
73
122
  }
74
123
  }
75
124
  export class AuthenticatedApiClient extends BaseApiClient {
@@ -78,10 +127,7 @@ export class AuthenticatedApiClient extends BaseApiClient {
78
127
  async request(path, options = {}) {
79
128
  const headerName = this.authConfigs?.header || 'Authorization';
80
129
  const prefix = this.authConfigs?.prefix ?? 'Bearer';
81
- const injectToken = (h, t) => {
82
- const value = prefix ? `${prefix} ${t}` : t;
83
- h.set(headerName, value);
84
- };
130
+ const injectToken = (h, t) => h.set(headerName, prefix ? `${prefix} ${t}` : t);
85
131
  let store = ClientStore.get(this.clientName);
86
132
  const headers = new Headers(options.headers || {});
87
133
  if (store.accessToken)
@@ -90,10 +136,10 @@ export class AuthenticatedApiClient extends BaseApiClient {
90
136
  let response = await super.request(path, options);
91
137
  if (response.status === 401) {
92
138
  await this._synchronizedAuthentication();
93
- store = ClientStore.get(this.clientName);
94
- if (store.accessToken) {
139
+ const freshStore = ClientStore.get(this.clientName);
140
+ if (freshStore.accessToken) {
95
141
  const retryHeaders = new Headers(options.headers);
96
- injectToken(retryHeaders, store.accessToken);
142
+ injectToken(retryHeaders, freshStore.accessToken);
97
143
  return super.request(path, { ...options, headers: retryHeaders });
98
144
  }
99
145
  }
@@ -128,9 +174,7 @@ export class AuthenticatedApiClient extends BaseApiClient {
128
174
  throw error;
129
175
  }
130
176
  }
131
- async authentication() {
132
- return true;
133
- }
177
+ async authentication() { return true; }
134
178
  }
135
179
  export class SoapClient extends Client {
136
180
  constructor(config) { super(config); }