datastake-daf 0.6.486 → 0.6.488

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.
@@ -5096,6 +5096,16 @@ const safeJsonParse = (value, fallback = value) => {
5096
5096
  return fallback;
5097
5097
  }
5098
5098
  };
5099
+ const cleanJSON = json => {
5100
+ for (let key in json) {
5101
+ if (json[key] === undefined) {
5102
+ json[key] = null;
5103
+ } /*else if (typeof json[key] === 'object')
5104
+ json[key] = cleanJSON(json[key]);
5105
+ */
5106
+ }
5107
+ return json;
5108
+ };
5099
5109
 
5100
5110
  function _checkValue$1(wantedValue, match, fieldValue) {
5101
5111
  match = match ? match.trim() : null;
@@ -6016,6 +6026,152 @@ var browser$1 = {
6016
6026
 
6017
6027
  var process = browser$1;
6018
6028
 
6029
+ /**
6030
+ * @description Manages window.storage
6031
+ * @returns storage objects
6032
+ * @class StorageManager
6033
+ */
6034
+ class StorageManager {
6035
+ /**
6036
+ * @description Set or update given value in localStorage
6037
+ * @static
6038
+ * @returns Object
6039
+ * @memberof StorageManager
6040
+ */
6041
+ static set(key, value) {
6042
+ if (key && value) {
6043
+ window.localStorage.setItem(key, value);
6044
+ return {
6045
+ success: true,
6046
+ data: "Storage has been set successfully."
6047
+ };
6048
+ }
6049
+ return {
6050
+ error: true,
6051
+ data: "Storage was not set. Storage key and value is required!"
6052
+ };
6053
+ }
6054
+
6055
+ /**
6056
+ * @description Set or update given value in sessionStorage, used to not remember user after closing opened tab.
6057
+ * @static
6058
+ * @returns Object
6059
+ * @memberof StorageManager
6060
+ */
6061
+ static setToSession(key, value) {
6062
+ if (key && value) {
6063
+ window.sessionStorage.setItem(key, value);
6064
+ return {
6065
+ success: true,
6066
+ data: "Storage has been set successfully."
6067
+ };
6068
+ }
6069
+ return {
6070
+ error: true,
6071
+ data: "Storage was not set. Storage key and value is required!"
6072
+ };
6073
+ }
6074
+
6075
+ /**
6076
+ * @description Fetch data from localStorage
6077
+ * @static
6078
+ * @returns Object
6079
+ * @memberof StorageManager
6080
+ */
6081
+ static get(key, defaultValue = undefined) {
6082
+ if (key) {
6083
+ const val = window.localStorage.getItem(key) || window.sessionStorage.getItem(key);
6084
+ return val || defaultValue;
6085
+ }
6086
+ return {
6087
+ error: true,
6088
+ data: "Storage key is required!"
6089
+ };
6090
+ }
6091
+
6092
+ /**
6093
+ * @description Clear only one value from localStorage
6094
+ * @static
6095
+ * @returns Object
6096
+ * @memberof StorageManager
6097
+ */
6098
+ static clearOne(key) {
6099
+ if (key === 'token') {
6100
+ localStorage.removeItem('trade-type');
6101
+ localStorage.removeItem('trade-value');
6102
+ }
6103
+ if (key) {
6104
+ window.localStorage.removeItem(key) || window.sessionStorage.removeItem(key);
6105
+ }
6106
+ return "Storage key is required!";
6107
+ }
6108
+
6109
+ /**
6110
+ * @description Clear all values from localStorage
6111
+ * @static
6112
+ * @returns Object
6113
+ * @memberof StorageManager
6114
+ */
6115
+ static clearAll() {
6116
+ window.localStorage.clear();
6117
+ window.sessionStorage.clear();
6118
+ }
6119
+ static hasKey(key) {
6120
+ return typeof this.get(key) === 'string';
6121
+ }
6122
+ static saveFilters(module, namespace, filters = {}, merge = false) {
6123
+ let savedFilters = this.get('filters');
6124
+ try {
6125
+ savedFilters = JSON.parse(savedFilters);
6126
+ } catch (e) {
6127
+ savedFilters = {};
6128
+ }
6129
+ if (!savedFilters) {
6130
+ savedFilters = {};
6131
+ }
6132
+ if (!savedFilters[module]) {
6133
+ savedFilters[module] = {};
6134
+ }
6135
+ if (!savedFilters[module][namespace]) {
6136
+ savedFilters[module][namespace] = {};
6137
+ }
6138
+ if (Object.keys(filters).length) {
6139
+ savedFilters[module][namespace] = merge ? Object.assign(savedFilters[module][namespace], filters) : filters;
6140
+ } else {
6141
+ savedFilters[module][namespace] = {};
6142
+ }
6143
+ this.set('filters', JSON.stringify(savedFilters));
6144
+ return savedFilters[module][namespace];
6145
+ }
6146
+ static getFilters(module, namespace) {
6147
+ let savedFilters = this.get('filters');
6148
+ try {
6149
+ savedFilters = JSON.parse(savedFilters);
6150
+ } catch (e) {
6151
+ savedFilters = {};
6152
+ }
6153
+ if (!savedFilters) {
6154
+ savedFilters = {};
6155
+ }
6156
+ if (!savedFilters[module]) {
6157
+ savedFilters[module] = {};
6158
+ }
6159
+ if (!savedFilters[module][namespace]) {
6160
+ savedFilters[module][namespace] = {};
6161
+ }
6162
+ return savedFilters[module][namespace];
6163
+ }
6164
+ }
6165
+
6166
+ const isProxy = () => {
6167
+ const pathname = window.location.pathname;
6168
+ if (pathname.includes('/proxy/pme') || pathname.includes('/proxy/cadd')) {
6169
+ return window.opener || !!StorageManager.get('proxy_token');
6170
+ }
6171
+ return false;
6172
+ };
6173
+ const getToken = () => isProxy() ? StorageManager.get('proxy_token') : StorageManager.get('token');
6174
+
6019
6175
  ({
6020
6176
  config: PropTypes__default["default"].object,
6021
6177
  filters: PropTypes__default["default"].object,
@@ -13431,17 +13587,153 @@ const processConfig = async configArray => {
13431
13587
  return processedConfig;
13432
13588
  };
13433
13589
 
13590
+ /**
13591
+ * Utility functions for URL manipulation
13592
+ */
13593
+
13594
+ /**
13595
+ * Assigns parameters to URL path
13596
+ * Example: assignParamsToUrl('/users/{id}/posts/{postId}', { id: 1, postId: 2 })
13597
+ * Returns: '/users/1/posts/2'
13598
+ */
13599
+ const assignParamsToUrl = (path, params) => {
13600
+ let paramsPath = path;
13601
+ const matches = path.match(/\{\w+\}/gm);
13602
+ if (matches) {
13603
+ for (let param of matches) {
13604
+ const key = param.match(/\w+/)[0];
13605
+ paramsPath = paramsPath.replace(/\{\w+\}/, params[key]);
13606
+ }
13607
+ }
13608
+ return paramsPath;
13609
+ };
13610
+
13611
+ /**
13612
+ * Build query string from object
13613
+ * Example: buildQueryString({ page: 1, limit: 10 })
13614
+ * Returns: '?page=1&limit=10'
13615
+ */
13616
+ const buildQueryString = params => {
13617
+ const query = Object.keys(params).filter(key => params[key] !== null && params[key] !== undefined).map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`).join('&');
13618
+ return query ? `?${query}` : '';
13619
+ };
13620
+
13621
+ /**
13622
+ * Generic error handler factory for axios requests
13623
+ * Highly configurable to adapt to different project needs
13624
+ */
13625
+ const createErrorHandler = (config = {}) => {
13626
+ const {
13627
+ onUnauthorized,
13628
+ handleError,
13629
+ getStorageManager,
13630
+ checkTokenValidity = true,
13631
+ unauthorizedRedirect = '/',
13632
+ tokenStorageKey = 'token',
13633
+ handleNetworkError = true
13634
+ } = config;
13635
+ return (error, customOnUnauthorized) => {
13636
+ // Handle cases where error.response doesn't exist (network errors)
13637
+ if (!error.response) {
13638
+ if (handleNetworkError && handleError) {
13639
+ handleError({
13640
+ status: 0,
13641
+ statusText: "Network Error",
13642
+ data: ["Please check your internet connection."]
13643
+ });
13644
+ }
13645
+ return Promise.reject(error);
13646
+ }
13647
+ const {
13648
+ status,
13649
+ statusText,
13650
+ data: {
13651
+ errors,
13652
+ message
13653
+ } = {}
13654
+ } = error.response || {
13655
+ data: {}
13656
+ };
13657
+
13658
+ // Handle 401 Unauthorized
13659
+ if (status === 401) {
13660
+ if (checkTokenValidity && getStorageManager) {
13661
+ const token = getStorageManager().get(tokenStorageKey);
13662
+ if (token) {
13663
+ if (typeof customOnUnauthorized === 'function') {
13664
+ customOnUnauthorized();
13665
+ } else if (typeof onUnauthorized === 'function') {
13666
+ onUnauthorized();
13667
+ } else {
13668
+ getStorageManager().clearOne(tokenStorageKey);
13669
+ if (typeof window !== 'undefined') {
13670
+ window.location.href = unauthorizedRedirect;
13671
+ }
13672
+ }
13673
+ }
13674
+ }
13675
+ }
13676
+
13677
+ // Handle 4xx and 5xx errors
13678
+ if (status >= 400 && status <= 500) {
13679
+ if (handleError) {
13680
+ handleError({
13681
+ status,
13682
+ statusText: message || statusText,
13683
+ data: errors || []
13684
+ });
13685
+ }
13686
+ }
13687
+ return Promise.reject(error);
13688
+ };
13689
+ };
13690
+
13691
+ /**
13692
+ * Simple error handler using antd message component
13693
+ * Useful for quick error notifications
13694
+ */
13695
+ const handleError = err => {
13696
+ const errorMessage = err?.response?.data?.message || err?.message || 'An error occurred';
13697
+ antd.message.error(errorMessage);
13698
+ };
13699
+
13700
+ /**
13701
+ * Success message handler
13702
+ */
13703
+ const handleSuccess = msg => {
13704
+ antd.message.success(msg || 'Operation successful');
13705
+ };
13706
+
13707
+ /**
13708
+ * Warning message handler
13709
+ */
13710
+ const handleWarning = msg => {
13711
+ antd.message.warning(msg || 'Warning');
13712
+ };
13713
+
13714
+ /**
13715
+ * Info message handler
13716
+ */
13717
+ const handleInfo = msg => {
13718
+ antd.message.info(msg);
13719
+ };
13720
+
13434
13721
  exports.ErrorFormat = ErrorFormat;
13435
13722
  exports.MessageTypes = MessageTypes;
13723
+ exports.StorageManager = StorageManager;
13724
+ exports.assignParamsToUrl = assignParamsToUrl;
13436
13725
  exports.btn = button;
13726
+ exports.buildQueryString = buildQueryString;
13437
13727
  exports.camelCaseToTitle = camelCaseToTitle;
13438
13728
  exports.capitalize = capitalize;
13439
13729
  exports.capitalizeAll = capitalizeAll;
13440
13730
  exports.captureComponentsAsImages = captureComponentsAsImages;
13441
13731
  exports.captureElementsAsImages = captureElementsAsImages;
13442
13732
  exports.changeInputMeta = changeInputMeta;
13733
+ exports.cleanJSON = cleanJSON;
13443
13734
  exports.convertUndefinedToNull = convertUndefinedToNull;
13444
13735
  exports.createDocument = createDocument;
13736
+ exports.createErrorHandler = createErrorHandler;
13445
13737
  exports.defaultFilterKeys = defaultFilterKeys;
13446
13738
  exports.defaultMapConfig = defaultMapConfig;
13447
13739
  exports.defaultTheme = mmtTheme;
@@ -13464,7 +13756,12 @@ exports.getOptionConfig = getOptionConfig;
13464
13756
  exports.getOptionLabel = getOptionLabel;
13465
13757
  exports.getRangeOfTicks = getRangeOfTicks;
13466
13758
  exports.getTagColor = getTagColor;
13759
+ exports.getToken = getToken;
13467
13760
  exports.groupSubsections = groupSubsections;
13761
+ exports.handleError = handleError;
13762
+ exports.handleInfo = handleInfo;
13763
+ exports.handleSuccess = handleSuccess;
13764
+ exports.handleWarning = handleWarning;
13468
13765
  exports.hasNotChanged = hasNotChanged;
13469
13766
  exports.isEmptyOrSpaces = isEmptyOrSpaces;
13470
13767
  exports.isModuleApproved = isModuleApproved;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "datastake-daf",
3
- "version": "0.6.486",
3
+ "version": "0.6.488",
4
4
  "dependencies": {
5
5
  "@ant-design/icons": "^5.2.5",
6
6
  "@antv/g2": "^5.1.1",
package/rollup.config.js CHANGED
@@ -95,6 +95,26 @@ export default [
95
95
  requireReturnsDefault: "auto",
96
96
  }),
97
97
  ],
98
+ },
99
+ {
100
+ input: "src/services.js",
101
+ output: [
102
+ {
103
+ file: "dist/services/index.js",
104
+ format: "cjs",
105
+ },
106
+ ],
107
+ external,
108
+ plugins: [
109
+ nodePolyfills(),
110
+ resolve({ browser: true }),
111
+ babel({ exclude: "node_modules/**", babelrc: true }),
112
+ peerDep(),
113
+ commonjs({
114
+ include: /node_modules/,
115
+ requireReturnsDefault: "auto",
116
+ }),
117
+ ],
98
118
  },
99
119
  {
100
120
  input: "src/hooks.js",
@@ -89,6 +89,13 @@ export const checkCondition = (condition, repeatValues, formsValue) => {
89
89
  };
90
90
  export function showHideInput(input, formsValue, repeatIndex, repeatValues, setValues, ref) {
91
91
  if (input?.meta?.hidden === true) {
92
+ // Set default value for hidden fields
93
+ if (input?.meta?.defaultValue !== undefined && !propHasValue(formsValue[ref])) {
94
+ formsValue[ref] = input.meta.defaultValue;
95
+ if (setValues && typeof setValues === 'function') {
96
+ setValues({ ...formsValue });
97
+ }
98
+ }
92
99
  return false;
93
100
  }
94
101
  if (input.showIf) {
@@ -168,6 +168,34 @@ export default function DynamicForm({
168
168
  }
169
169
  }, [data]);
170
170
 
171
+ // Initialize default values for hidden fields
172
+ useEffect(() => {
173
+ if (Object.keys(form).length > 0) {
174
+ const updatedValues = { ...values };
175
+ let hasChanges = false;
176
+
177
+ // Process all form fields to set default values for hidden fields
178
+ Object.keys(form).forEach(formKey => {
179
+ Object.keys(form[formKey]).forEach(fieldKey => {
180
+ const field = form[formKey][fieldKey];
181
+ if (field?.meta?.hidden === true && field?.meta?.defaultValue !== undefined) {
182
+ const fieldId = field.dataId || fieldKey;
183
+ if (!propHasValue(updatedValues[fieldId])) {
184
+ updatedValues[fieldId] = field.meta.defaultValue;
185
+ hasChanges = true;
186
+ }
187
+ }
188
+ });
189
+ });
190
+
191
+ if (hasChanges) {
192
+ setValues(updatedValues);
193
+ // Also set the values in the Ant Design form
194
+ MainForm.setFieldsValue(updatedValues);
195
+ }
196
+ }
197
+ }, [form]);
198
+
171
199
  const setSelectedForm = (id) => {
172
200
  setForms(Forms.map(form => {
173
201
  (id === form.id)
@@ -267,6 +295,14 @@ export default function DynamicForm({
267
295
  }
268
296
  value = fileList;
269
297
  }
298
+ // Handle default values for hidden fields
299
+ if (input?.meta?.hidden === true && input?.meta?.defaultValue !== undefined && !propHasValue(value)) {
300
+ value = input.meta.defaultValue;
301
+ if (typeof path === 'string') {
302
+ dot.str(path, value, values);
303
+ }
304
+ }
305
+
270
306
  const config = {
271
307
  name,
272
308
  call: input.call ? input.call : input?.meta?.call,
@@ -0,0 +1,229 @@
1
+ import axios from 'axios';
2
+ import { cleanJSON } from '../../helpers/StringHelper.js';
3
+ import { assignParamsToUrl } from '../../helpers/urlHelpers.js';
4
+ import { getToken } from '../../helpers/Token.js';
5
+
6
+ /**
7
+ * Base HTTP Service Class
8
+ * Provides axios wrapper with token management, cancel tokens, and interceptors
9
+ */
10
+ export class BaseHTTPService {
11
+ constructor(config = {}) {
12
+ const {getHeaders, getBaseUrl, onError, timeout = 300000, customAxiosConfig = {}} = config;
13
+
14
+ this.getToken = getToken || (() => null);
15
+ this.getHeaders = getHeaders || (() => ({}));
16
+ this.getBaseUrl = getBaseUrl || (() => null);
17
+ this.onError = onError || (() => null);
18
+ this.cleanJSON = cleanJSON;
19
+ this.timeout = timeout;
20
+ this.customAxiosConfig = customAxiosConfig;
21
+
22
+ // Init
23
+ this.cancelToken = {}
24
+ this.token = this.getToken();
25
+
26
+ this.setupAxios();
27
+ }
28
+
29
+ setupAxios() {
30
+ const headers = {
31
+ Authorization: `Bearer ${this.token}`,
32
+ ...this.getHeaders(),
33
+ };
34
+
35
+ this.api = axios.create({
36
+ baseURL: this.getBaseUrl(),
37
+ headers,
38
+ timeout: this.timeout,
39
+ ...this.customAxiosConfig,
40
+ });
41
+
42
+ this.setupInterceptors();
43
+ }
44
+
45
+ setupInterceptors() {
46
+ this.api.interceptors.response.use((response) => response,
47
+ (error) => Promise.reject(error)
48
+ );
49
+
50
+ // Request interceptor - clean JSON data
51
+ this.api.interceptors.request.use(
52
+ (config) => {
53
+ if (['post', 'put', 'patch'].includes(config.method)) {
54
+ config.data = this.cleanJSON(config.data);
55
+ }
56
+
57
+ return config;
58
+ },
59
+ (error) => Promise.reject(error)
60
+ );
61
+ }
62
+
63
+ reloadAxios() {
64
+ const token = this.getToken();
65
+ this.token = token;
66
+
67
+ const options = {
68
+ baseURL: this.getBaseURL(),
69
+ timeout: this.timeout,
70
+ ...this.customAxiosConfig,
71
+ };
72
+
73
+ if (token) {
74
+ options.headers = {
75
+ Authorization: `Bearer ${token}`,
76
+ ...this.getHeaders(),
77
+ };
78
+ }
79
+
80
+ this.api = axios.create(options);
81
+ this.setupInterceptors();
82
+ }
83
+
84
+ async formatResponse(call) {
85
+ try {
86
+ const res = await call;
87
+ const { data, status, statusText } = res || {};
88
+ this.cancelToken = {};
89
+ return { data, status, statusText };
90
+ } catch (error) {
91
+ return Promise.reject(error);
92
+ }
93
+ }
94
+
95
+ apiPost(options) {
96
+ // Check if there are any previous pending requests
97
+ if (typeof this.cancelToken[options.url] != typeof undefined) {
98
+ try {
99
+ this.cancelToken[options.url].cancel("Operation canceled due to new request.");
100
+ } catch (e) {
101
+ console.log(e);
102
+ return {};
103
+ }
104
+ }
105
+
106
+ this.cancelToken[options.url] = axios.CancelToken.source();
107
+ const token = this.getToken();
108
+
109
+ if (token !== this.token || !token) {
110
+ this.reloadAxios();
111
+ }
112
+
113
+ return this.formatResponse(
114
+ this.api.post(options.url, options.data, {
115
+ baseURL: options.baseURL || this.getBaseURL(options),
116
+ cancelToken: this.cancelToken[options.url].token,
117
+ headers: {
118
+ Authorization: `Bearer ${this.getToken()}`,
119
+ ...this.getHeaders(),
120
+ ...(options?.headers || {}),
121
+ }
122
+ })
123
+ .catch((err) => this.onError(err))
124
+ );
125
+ }
126
+
127
+ apiGet(options) {
128
+ const token = this.getToken();
129
+
130
+ if (token !== this.token || !token) {
131
+ this.reloadAxios();
132
+ }
133
+
134
+ const { url, ...config } = options;
135
+
136
+ return this.formatResponse(
137
+ this.api.get(url, {
138
+ ...config,
139
+ baseURL: options.baseURL || this.getBaseURL(options),
140
+ headers: {
141
+ Authorization: `Bearer ${this.getToken()}`,
142
+ ...this.getHeaders(),
143
+ ...(config?.headers || {}),
144
+ }
145
+ })
146
+ .catch((err) => this.onError(err))
147
+ );
148
+ }
149
+
150
+ apiDelete(options) {
151
+ const token = this.getToken();
152
+
153
+ if (token !== this.token || !token) {
154
+ this.reloadAxios();
155
+ }
156
+
157
+ const { url, ...config } = options;
158
+
159
+ return this.formatResponse(
160
+ this.api.delete(url, {
161
+ ...config,
162
+ baseURL: options.baseURL || this.getBaseURL(options),
163
+ headers: {
164
+ Authorization: `Bearer ${this.getToken()}`,
165
+ ...this.getHeaders(),
166
+ ...(config?.headers || {}),
167
+ }
168
+ })
169
+ .catch((err) => this.onError(err))
170
+ );
171
+ }
172
+
173
+ apiPut(options) {
174
+ if (typeof this.cancelToken[options.url] != typeof undefined) {
175
+ try {
176
+ this.cancelToken[options.url].cancel("Operation canceled due to new request.");
177
+ } catch (e) {
178
+ console.log(e);
179
+ return {};
180
+ }
181
+ }
182
+
183
+ this.cancelToken[options.url] = axios.CancelToken.source();
184
+ const token = this.getToken();
185
+
186
+ if (token !== this.token || !token) {
187
+ this.reloadAxios();
188
+ }
189
+
190
+ return this.formatResponse(
191
+ this.api.put(options.url, options.data, {
192
+ cancelToken: this.cancelToken[options.url].token,
193
+ baseURL: options.baseURL || this.getBaseURL(options),
194
+ headers: {
195
+ Authorization: `Bearer ${this.getToken()}`,
196
+ ...this.getHeaders(),
197
+ ...(options?.headers || {}),
198
+ }
199
+ })
200
+ .catch((err) => this.onError(err, options.onUnauthorized))
201
+ );
202
+ }
203
+
204
+ apiPatch(options) {
205
+ const token = this.getToken();
206
+
207
+ if (token !== this.token || !token) {
208
+ this.reloadAxios();
209
+ }
210
+
211
+ return this.formatResponse(
212
+ this.api.patch(options.url, options.data, {
213
+ baseURL: options.baseURL || this.getBaseURL(options),
214
+ headers: {
215
+ Authorization: `Bearer ${this.getToken()}`,
216
+ ...this.getHeaders(),
217
+ ...(options?.headers || {}),
218
+ }
219
+ })
220
+ .catch((err) => this.onError(err))
221
+ );
222
+ }
223
+
224
+
225
+ // Utility method
226
+ assignParamsToUrl(path, params) {
227
+ return assignParamsToUrl(path, params);
228
+ }
229
+ }
@@ -0,0 +1,43 @@
1
+ export class ErrorHandler {
2
+ constructor(config = {}) {
3
+ this.store = config.store;
4
+ this.addToastAction = config.addToastAction;
5
+ this.formatData = config.formatData || this.defaultFormatData;
6
+ }
7
+
8
+ handle({stats, statusText, data}) {
9
+ if(this.store && this.addToastAction) {
10
+ this.store.dispatch(this.addToastAction({
11
+ title: statusText,
12
+ type: 'danger',
13
+ body: this.formatData(data),
14
+ }));
15
+ } else {
16
+ console.error('Error: ', {stats, statusText, data});
17
+ }
18
+ }
19
+
20
+ defaultFormatData(errors) {
21
+ if (!Array.isArray(errors)) {
22
+ return String(errors);
23
+ }
24
+
25
+ const e = [];
26
+ errors.forEach(error => {
27
+ if (error && typeof error === 'object' && error.constraints) {
28
+ Object.keys(error.constraints)
29
+ .forEach(key => e.push(error.constraints[key]));
30
+ } else if (typeof error === 'string') {
31
+ e.push(error);
32
+ } else {
33
+ e.push(JSON.stringify(error));
34
+ }
35
+ });
36
+
37
+ return e.join('\n');
38
+ }
39
+
40
+ static createInstance(config) {
41
+ return new ErrorHandler(config);
42
+ }
43
+ }