@etsoo/shared 1.2.51 → 1.2.54

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.
Files changed (94) hide show
  1. package/.github/workflows/main.yml +6 -5
  2. package/README.md +1 -1
  3. package/lib/cjs/ActionResult.d.ts +1 -1
  4. package/lib/cjs/ActionResult.js +3 -3
  5. package/lib/cjs/ArrayUtils.d.ts +1 -1
  6. package/lib/cjs/ArrayUtils.js +4 -4
  7. package/lib/cjs/ColorUtils.d.ts +1 -1
  8. package/lib/cjs/ColorUtils.js +2 -2
  9. package/lib/cjs/DataTypes.d.ts +6 -6
  10. package/lib/cjs/DataTypes.js +50 -51
  11. package/lib/cjs/DateUtils.d.ts +1 -1
  12. package/lib/cjs/DateUtils.js +27 -28
  13. package/lib/cjs/DomUtils.d.ts +3 -3
  14. package/lib/cjs/DomUtils.js +64 -73
  15. package/lib/cjs/ExtendUtils.d.ts +1 -1
  16. package/lib/cjs/ExtendUtils.js +6 -6
  17. package/lib/cjs/IActionResult.d.ts +15 -2
  18. package/lib/cjs/NumberUtils.d.ts +1 -1
  19. package/lib/cjs/NumberUtils.js +9 -9
  20. package/lib/cjs/StorageUtils.js +2 -2
  21. package/lib/cjs/Utils.d.ts +4 -4
  22. package/lib/cjs/Utils.js +58 -62
  23. package/lib/cjs/index.d.ts +22 -22
  24. package/lib/cjs/storage/WindowStorage.d.ts +1 -1
  25. package/lib/cjs/types/ContentDisposition.d.ts +2 -2
  26. package/lib/cjs/types/ContentDisposition.js +11 -13
  27. package/lib/cjs/types/EColor.js +5 -7
  28. package/lib/cjs/types/EHistory.d.ts +3 -3
  29. package/lib/cjs/types/EHistory.js +4 -4
  30. package/lib/cjs/types/ErrorData.d.ts +1 -1
  31. package/lib/cjs/types/EventClass.js +1 -1
  32. package/lib/mjs/ActionResult.d.ts +1 -1
  33. package/lib/mjs/ActionResult.js +3 -3
  34. package/lib/mjs/ArrayUtils.d.ts +1 -1
  35. package/lib/mjs/ArrayUtils.js +5 -5
  36. package/lib/mjs/ColorUtils.d.ts +1 -1
  37. package/lib/mjs/ColorUtils.js +3 -3
  38. package/lib/mjs/DataTypes.d.ts +6 -6
  39. package/lib/mjs/DataTypes.js +50 -51
  40. package/lib/mjs/DateUtils.d.ts +1 -1
  41. package/lib/mjs/DateUtils.js +27 -28
  42. package/lib/mjs/DomUtils.d.ts +3 -3
  43. package/lib/mjs/DomUtils.js +67 -76
  44. package/lib/mjs/ExtendUtils.d.ts +1 -1
  45. package/lib/mjs/ExtendUtils.js +6 -6
  46. package/lib/mjs/IActionResult.d.ts +15 -2
  47. package/lib/mjs/NumberUtils.d.ts +1 -1
  48. package/lib/mjs/NumberUtils.js +9 -9
  49. package/lib/mjs/StorageUtils.js +4 -4
  50. package/lib/mjs/Utils.d.ts +4 -4
  51. package/lib/mjs/Utils.js +61 -65
  52. package/lib/mjs/index.d.ts +22 -22
  53. package/lib/mjs/index.js +22 -22
  54. package/lib/mjs/storage/WindowStorage.d.ts +1 -1
  55. package/lib/mjs/storage/WindowStorage.js +2 -2
  56. package/lib/mjs/types/ContentDisposition.d.ts +2 -2
  57. package/lib/mjs/types/ContentDisposition.js +12 -14
  58. package/lib/mjs/types/EColor.js +5 -7
  59. package/lib/mjs/types/EHistory.d.ts +3 -3
  60. package/lib/mjs/types/EHistory.js +5 -5
  61. package/lib/mjs/types/ErrorData.d.ts +1 -1
  62. package/lib/mjs/types/EventClass.js +1 -1
  63. package/package.json +61 -63
  64. package/src/ActionResult.ts +23 -23
  65. package/src/ArrayUtils.ts +164 -172
  66. package/src/ColorUtils.ts +80 -82
  67. package/src/DataTypes.ts +745 -754
  68. package/src/DateUtils.ts +266 -268
  69. package/src/DomUtils.ts +806 -831
  70. package/src/ExtendUtils.ts +191 -191
  71. package/src/IActionResult.ts +55 -40
  72. package/src/Keyboard.ts +258 -258
  73. package/src/NumberUtils.ts +135 -135
  74. package/src/StorageUtils.ts +117 -117
  75. package/src/Utils.ts +908 -930
  76. package/src/index.ts +22 -22
  77. package/src/node/Storage.ts +53 -53
  78. package/src/storage/IStorage.ts +62 -62
  79. package/src/storage/WindowStorage.ts +140 -140
  80. package/src/types/ContentDisposition.ts +59 -63
  81. package/src/types/DataError.ts +15 -15
  82. package/src/types/DelayedExecutorType.ts +15 -15
  83. package/src/types/EColor.ts +241 -248
  84. package/src/types/EHistory.ts +151 -151
  85. package/src/types/ErrorData.ts +11 -11
  86. package/src/types/EventClass.ts +220 -220
  87. package/src/types/FormData.ts +25 -25
  88. package/src/types/ParsedPath.ts +5 -5
  89. package/tsconfig.cjs.json +16 -16
  90. package/tsconfig.json +16 -16
  91. package/.eslintignore +0 -3
  92. package/.eslintrc.json +0 -29
  93. package/.prettierignore +0 -5
  94. package/.prettierrc +0 -6
package/src/Utils.ts CHANGED
@@ -1,1021 +1,999 @@
1
- import { DataTypes, IdType } from './DataTypes';
2
- import isEqual from 'lodash.isequal';
3
- import { DateUtils } from './DateUtils';
4
- import { ParsedPath } from './types/ParsedPath';
1
+ import { DataTypes, IdType } from "./DataTypes";
2
+ import isEqual from "lodash.isequal";
3
+ import { DateUtils } from "./DateUtils";
4
+ import { ParsedPath } from "./types/ParsedPath";
5
5
 
6
6
  declare global {
7
- interface String {
8
- /**
9
- * Add parameter to URL
10
- * @param this URL to add parameter
11
- * @param name Parameter name
12
- * @param value Parameter value
13
- * @param arrayFormat Array format to array style or not to multiple fields
14
- * @returns New URL
15
- */
16
- addUrlParam(
17
- this: string,
18
- name: string,
19
- value: DataTypes.Simple,
20
- arrayFormat?: boolean | string
21
- ): string;
22
-
23
- /**
24
- * Add parameters to URL
25
- * @param this URL to add parameters
26
- * @param data Parameters
27
- * @param arrayFormat Array format to array style or not to multiple fields
28
- * @returns New URL
29
- */
30
- addUrlParams(
31
- this: string,
32
- data: DataTypes.SimpleObject,
33
- arrayFormat?: boolean | string
34
- ): string;
35
-
36
- /**
37
- * Add parameters to URL
38
- * @param this URL to add parameters
39
- * @param params Parameters string
40
- * @returns New URL
41
- */
42
- addUrlParams(this: string, params: string): string;
43
-
44
- /**
45
- * Check the input string contains Chinese character or not
46
- * @param this Input
47
- * @param test Test string
48
- */
49
- containChinese(this: string): boolean;
50
-
51
- /**
52
- * Check the input string contains Korean character or not
53
- * @param this Input
54
- * @param test Test string
55
- */
56
- containKorean(this: string): boolean;
57
-
58
- /**
59
- * Check the input string contains Japanese character or not
60
- * @param this Input
61
- * @param test Test string
62
- */
63
- containJapanese(this: string): boolean;
64
-
65
- /**
66
- * Format string with parameters
67
- * @param this Input template
68
- * @param parameters Parameters to fill the template
69
- */
70
- format(this: string, ...parameters: string[]): string;
71
-
72
- /**
73
- * Format inital character to lower case or upper case
74
- * @param this Input string
75
- * @param upperCase To upper case or lower case
76
- */
77
- formatInitial(this: string, upperCase: boolean): string;
78
-
79
- /**
80
- * Hide data
81
- * @param this Input string
82
- * @param endChar End char
83
- */
84
- hideData(this: string, endChar?: string): string;
85
-
86
- /**
87
- * Hide email data
88
- * @param this Input email
89
- */
90
- hideEmail(this: string): string;
91
-
92
- /**
93
- * Is digits string
94
- * @param this Input string
95
- * @param minLength Minimum length
96
- */
97
- isDigits(this: string, minLength?: number): boolean;
98
-
99
- /**
100
- * Is email string
101
- * @param this Input string
102
- */
103
- isEmail(this: string): boolean;
104
-
105
- /**
106
- * Remove non letters (0-9, a-z, A-Z)
107
- * @param this Input string
108
- */
109
- removeNonLetters(this: string): string;
110
- }
7
+ interface String {
8
+ /**
9
+ * Add parameter to URL
10
+ * @param this URL to add parameter
11
+ * @param name Parameter name
12
+ * @param value Parameter value
13
+ * @param arrayFormat Array format to array style or not to multiple fields
14
+ * @returns New URL
15
+ */
16
+ addUrlParam(
17
+ this: string,
18
+ name: string,
19
+ value: DataTypes.Simple,
20
+ arrayFormat?: boolean | string
21
+ ): string;
22
+
23
+ /**
24
+ * Add parameters to URL
25
+ * @param this URL to add parameters
26
+ * @param data Parameters
27
+ * @param arrayFormat Array format to array style or not to multiple fields
28
+ * @returns New URL
29
+ */
30
+ addUrlParams(
31
+ this: string,
32
+ data: DataTypes.SimpleObject,
33
+ arrayFormat?: boolean | string
34
+ ): string;
35
+
36
+ /**
37
+ * Add parameters to URL
38
+ * @param this URL to add parameters
39
+ * @param params Parameters string
40
+ * @returns New URL
41
+ */
42
+ addUrlParams(this: string, params: string): string;
43
+
44
+ /**
45
+ * Check the input string contains Chinese character or not
46
+ * @param this Input
47
+ * @param test Test string
48
+ */
49
+ containChinese(this: string): boolean;
50
+
51
+ /**
52
+ * Check the input string contains Korean character or not
53
+ * @param this Input
54
+ * @param test Test string
55
+ */
56
+ containKorean(this: string): boolean;
57
+
58
+ /**
59
+ * Check the input string contains Japanese character or not
60
+ * @param this Input
61
+ * @param test Test string
62
+ */
63
+ containJapanese(this: string): boolean;
64
+
65
+ /**
66
+ * Format string with parameters
67
+ * @param this Input template
68
+ * @param parameters Parameters to fill the template
69
+ */
70
+ format(this: string, ...parameters: string[]): string;
71
+
72
+ /**
73
+ * Format inital character to lower case or upper case
74
+ * @param this Input string
75
+ * @param upperCase To upper case or lower case
76
+ */
77
+ formatInitial(this: string, upperCase: boolean): string;
78
+
79
+ /**
80
+ * Hide data
81
+ * @param this Input string
82
+ * @param endChar End char
83
+ */
84
+ hideData(this: string, endChar?: string): string;
85
+
86
+ /**
87
+ * Hide email data
88
+ * @param this Input email
89
+ */
90
+ hideEmail(this: string): string;
91
+
92
+ /**
93
+ * Is digits string
94
+ * @param this Input string
95
+ * @param minLength Minimum length
96
+ */
97
+ isDigits(this: string, minLength?: number): boolean;
98
+
99
+ /**
100
+ * Is email string
101
+ * @param this Input string
102
+ */
103
+ isEmail(this: string): boolean;
104
+
105
+ /**
106
+ * Remove non letters (0-9, a-z, A-Z)
107
+ * @param this Input string
108
+ */
109
+ removeNonLetters(this: string): string;
110
+ }
111
111
  }
112
112
 
113
113
  String.prototype.addUrlParam = function (
114
- this: string,
115
- name: string,
116
- value: DataTypes.Simple,
117
- arrayFormat?: boolean | string
114
+ this: string,
115
+ name: string,
116
+ value: DataTypes.Simple,
117
+ arrayFormat?: boolean | string
118
118
  ) {
119
- return this.addUrlParams({ [name]: value }, arrayFormat);
119
+ return this.addUrlParams({ [name]: value }, arrayFormat);
120
120
  };
121
121
 
122
122
  String.prototype.addUrlParams = function (
123
- this: string,
124
- data: DataTypes.SimpleObject | string,
125
- arrayFormat?: boolean | string
123
+ this: string,
124
+ data: DataTypes.SimpleObject | string,
125
+ arrayFormat?: boolean | string
126
126
  ) {
127
- if (typeof data === 'string') {
128
- let url = this;
129
- if (url.includes('?')) {
130
- url += '&';
127
+ if (typeof data === "string") {
128
+ let url = this;
129
+ if (url.includes("?")) {
130
+ url += "&";
131
+ } else {
132
+ if (!url.endsWith("/")) url = url + "/";
133
+ url += "?";
134
+ }
135
+ return url + data;
136
+ }
137
+
138
+ // Simple check
139
+ if (typeof URL === "undefined" || !this.includes("://")) {
140
+ const params = Object.entries(data)
141
+ .map(([key, value]) => {
142
+ let v: string;
143
+ if (Array.isArray(value)) {
144
+ if (arrayFormat == null || arrayFormat === false) {
145
+ return value
146
+ .map((item) => `${key}=${encodeURIComponent(`${item}`)}`)
147
+ .join("&");
148
+ } else {
149
+ v = value.join(arrayFormat ? "," : arrayFormat);
150
+ }
151
+ } else if (value instanceof Date) {
152
+ v = value.toJSON();
131
153
  } else {
132
- if (!url.endsWith('/')) url = url + '/';
133
- url += '?';
154
+ v = value == null ? "" : `${value}`;
134
155
  }
135
- return url + data;
136
- }
137
156
 
138
- // Simple check
139
- if (typeof URL === 'undefined' || !this.includes('://')) {
140
- const params = Object.entries(data)
141
- .map(([key, value]) => {
142
- let v: string;
143
- if (Array.isArray(value)) {
144
- if (arrayFormat == null || arrayFormat === false) {
145
- return value
146
- .map(
147
- (item) =>
148
- `${key}=${encodeURIComponent(`${item}`)}`
149
- )
150
- .join('&');
151
- } else {
152
- v = value.join(arrayFormat ? ',' : arrayFormat);
153
- }
154
- } else if (value instanceof Date) {
155
- v = value.toJSON();
156
- } else {
157
- v = value == null ? '' : `${value}`;
158
- }
159
-
160
- return `${key}=${encodeURIComponent(v)}`;
161
- })
162
- .join('&');
163
-
164
- return this.addUrlParams(params);
165
- } else {
166
- const urlObj = new URL(this);
167
- Object.entries(data).forEach(([key, value]) => {
168
- if (Array.isArray(value)) {
169
- if (arrayFormat == null || arrayFormat === false) {
170
- value.forEach((item) => {
171
- urlObj.searchParams.append(key, `${item}`);
172
- });
173
- } else {
174
- urlObj.searchParams.append(
175
- key,
176
- value.join(arrayFormat ? ',' : arrayFormat)
177
- );
178
- }
179
- } else if (value instanceof Date) {
180
- urlObj.searchParams.append(key, value.toJSON());
181
- } else {
182
- urlObj.searchParams.append(
183
- key,
184
- `${value == null ? '' : value}`
185
- );
186
- }
187
- });
188
- return urlObj.toString();
189
- }
157
+ return `${key}=${encodeURIComponent(v)}`;
158
+ })
159
+ .join("&");
160
+
161
+ return this.addUrlParams(params);
162
+ } else {
163
+ const urlObj = new URL(this);
164
+ Object.entries(data).forEach(([key, value]) => {
165
+ if (Array.isArray(value)) {
166
+ if (arrayFormat == null || arrayFormat === false) {
167
+ value.forEach((item) => {
168
+ urlObj.searchParams.append(key, `${item}`);
169
+ });
170
+ } else {
171
+ urlObj.searchParams.append(
172
+ key,
173
+ value.join(arrayFormat ? "," : arrayFormat)
174
+ );
175
+ }
176
+ } else if (value instanceof Date) {
177
+ urlObj.searchParams.append(key, value.toJSON());
178
+ } else {
179
+ urlObj.searchParams.append(key, `${value == null ? "" : value}`);
180
+ }
181
+ });
182
+ return urlObj.toString();
183
+ }
190
184
  };
191
185
 
192
186
  String.prototype.containChinese = function (this: string): boolean {
193
- const regExp =
194
- /[\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uff66-\uff9f]/g;
195
- return regExp.test(this);
187
+ const regExp =
188
+ /[\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uff66-\uff9f]/g;
189
+ return regExp.test(this);
196
190
  };
197
191
 
198
192
  String.prototype.containKorean = function (this: string): boolean {
199
- const regExp =
200
- /[\uac00-\ud7af\u1100-\u11ff\u3130-\u318f\ua960-\ua97f\ud7b0-\ud7ff\u3400-\u4dbf]/g;
201
- return regExp.test(this);
193
+ const regExp =
194
+ /[\uac00-\ud7af\u1100-\u11ff\u3130-\u318f\ua960-\ua97f\ud7b0-\ud7ff\u3400-\u4dbf]/g;
195
+ return regExp.test(this);
202
196
  };
203
197
 
204
198
  String.prototype.containJapanese = function (this: string): boolean {
205
- const regExp = /[\u3040-\u309f\u30a0-\u30ff\uff00-\uff9f\u4e00-\u9faf]/g;
206
- return regExp.test(this);
199
+ const regExp = /[\u3040-\u309f\u30a0-\u30ff\uff00-\uff9f\u4e00-\u9faf]/g;
200
+ return regExp.test(this);
207
201
  };
208
202
 
209
203
  String.prototype.format = function (
210
- this: string,
211
- ...parameters: string[]
204
+ this: string,
205
+ ...parameters: string[]
212
206
  ): string {
213
- let template = this;
214
- parameters.forEach((value, index) => {
215
- template = template.replace(new RegExp(`\\{${index}\\}`, 'g'), value);
216
- });
217
- return template;
207
+ let template = this;
208
+ parameters.forEach((value, index) => {
209
+ template = template.replace(new RegExp(`\\{${index}\\}`, "g"), value);
210
+ });
211
+ return template;
218
212
  };
219
213
 
220
214
  String.prototype.formatInitial = function (
221
- this: string,
222
- upperCase: boolean = false
215
+ this: string,
216
+ upperCase: boolean = false
223
217
  ) {
224
- const initial = this.charAt(0);
225
- return (
226
- (upperCase ? initial.toUpperCase() : initial.toLowerCase()) +
227
- this.slice(1)
228
- );
218
+ const initial = this.charAt(0);
219
+ return (
220
+ (upperCase ? initial.toUpperCase() : initial.toLowerCase()) + this.slice(1)
221
+ );
229
222
  };
230
223
 
231
224
  String.prototype.hideData = function (this: string, endChar?: string) {
232
- if (this.length === 0) return this;
225
+ if (this.length === 0) return this;
233
226
 
234
- if (endChar != null) {
235
- const index = this.indexOf(endChar);
236
- if (index === -1) return this.hideData();
237
- return this.substring(0, index).hideData() + this.substring(index);
238
- }
227
+ if (endChar != null) {
228
+ const index = this.indexOf(endChar);
229
+ if (index === -1) return this.hideData();
230
+ return this.substring(0, index).hideData() + this.substring(index);
231
+ }
239
232
 
240
- var len = this.length;
241
- if (len < 4) return this.substring(0, 1) + '***';
242
- if (len < 6) return this.substring(0, 2) + '***';
243
- if (len < 8) return this.substring(0, 2) + '***' + this.slice(-2);
244
- if (len < 12) return this.substring(0, 3) + '***' + this.slice(-3);
233
+ var len = this.length;
234
+ if (len < 4) return this.substring(0, 1) + "***";
235
+ if (len < 6) return this.substring(0, 2) + "***";
236
+ if (len < 8) return this.substring(0, 2) + "***" + this.slice(-2);
237
+ if (len < 12) return this.substring(0, 3) + "***" + this.slice(-3);
245
238
 
246
- return this.substring(0, 4) + '***' + this.slice(-4);
239
+ return this.substring(0, 4) + "***" + this.slice(-4);
247
240
  };
248
241
 
249
242
  String.prototype.hideEmail = function (this: string) {
250
- return this.hideData('@');
243
+ return this.hideData("@");
251
244
  };
252
245
 
253
246
  String.prototype.isDigits = function (this: string, minLength?: number) {
254
- return this.length >= (minLength ?? 0) && /^\d+$/.test(this);
247
+ return this.length >= (minLength ?? 0) && /^\d+$/.test(this);
255
248
  };
256
249
 
257
250
  String.prototype.isEmail = function (this: string) {
258
- const re =
259
- /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
260
- return re.test(this.toLowerCase());
251
+ const re =
252
+ /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
253
+ return re.test(this.toLowerCase());
261
254
  };
262
255
 
263
256
  String.prototype.removeNonLetters = function (this: string) {
264
- return this.replace(/[^a-zA-Z0-9]/g, '');
257
+ return this.replace(/[^a-zA-Z0-9]/g, "");
265
258
  };
266
259
 
267
260
  /**
268
261
  * Utilities
269
262
  */
270
263
  export namespace Utils {
271
- /**
272
- * Add blank item to collection
273
- * @param options Options
274
- * @param idField Id field, default is id
275
- * @param labelField Label field, default is label
276
- * @param blankLabel Blank label, default is ---
277
- */
278
- export function addBlankItem<T extends object>(
279
- options: T[],
280
- idField?: string | keyof T,
281
- labelField?: unknown,
282
- blankLabel?: string
283
- ) {
284
- // Avoid duplicate blank items
285
- idField ??= 'id';
286
- if (options.length === 0 || Reflect.get(options[0], idField) !== '') {
287
- const blankItem: any = {
288
- [idField]: '',
289
- [typeof labelField === 'string' ? labelField : 'label']:
290
- blankLabel ?? '---'
291
- };
292
- options.unshift(blankItem);
293
- }
294
-
295
- return options;
264
+ /**
265
+ * Add blank item to collection
266
+ * @param options Options
267
+ * @param idField Id field, default is id
268
+ * @param labelField Label field, default is label
269
+ * @param blankLabel Blank label, default is ---
270
+ */
271
+ export function addBlankItem<T extends object>(
272
+ options: T[],
273
+ idField?: string | keyof T,
274
+ labelField?: unknown,
275
+ blankLabel?: string
276
+ ) {
277
+ // Avoid duplicate blank items
278
+ idField ??= "id";
279
+ if (options.length === 0 || Reflect.get(options[0], idField) !== "") {
280
+ const blankItem: any = {
281
+ [idField]: "",
282
+ [typeof labelField === "string" ? labelField : "label"]:
283
+ blankLabel ?? "---"
284
+ };
285
+ options.unshift(blankItem);
296
286
  }
297
287
 
298
- /**
299
- * Base64 chars to number
300
- * @param base64Chars Base64 chars
301
- * @returns Number
302
- */
303
- export function charsToNumber(base64Chars: string) {
304
- const chars =
305
- typeof Buffer === 'undefined'
306
- ? [...atob(base64Chars)].map((char) => char.charCodeAt(0))
307
- : [...Buffer.from(base64Chars, 'base64')];
308
-
309
- return chars.reduce((previousValue, currentValue, currentIndex) => {
310
- return previousValue + currentValue * Math.pow(128, currentIndex);
311
- }, 0);
288
+ return options;
289
+ }
290
+
291
+ /**
292
+ * Base64 chars to number
293
+ * @param base64Chars Base64 chars
294
+ * @returns Number
295
+ */
296
+ export function charsToNumber(base64Chars: string) {
297
+ const chars =
298
+ typeof Buffer === "undefined"
299
+ ? [...atob(base64Chars)].map((char) => char.charCodeAt(0))
300
+ : [...Buffer.from(base64Chars, "base64")];
301
+
302
+ return chars.reduce((previousValue, currentValue, currentIndex) => {
303
+ return previousValue + currentValue * Math.pow(128, currentIndex);
304
+ }, 0);
305
+ }
306
+
307
+ /**
308
+ * Correct object's property value type
309
+ * @param input Input object
310
+ * @param fields Fields to correct
311
+ */
312
+ export function correctTypes<
313
+ T extends object,
314
+ F extends { [P in keyof T]?: DataTypes.BasicNames }
315
+ >(input: T, fields: F) {
316
+ for (const field in fields) {
317
+ const type = fields[field];
318
+ if (type == null) continue;
319
+ const value = Reflect.get(input, field);
320
+ const newValue = DataTypes.convertByType(value, type);
321
+ if (newValue !== value) {
322
+ Reflect.set(input, field, newValue);
323
+ }
312
324
  }
313
-
314
- /**
315
- * Correct object's property value type
316
- * @param input Input object
317
- * @param fields Fields to correct
318
- */
319
- export function correctTypes<
320
- T extends object,
321
- F extends { [P in keyof T]?: DataTypes.BasicNames }
322
- >(input: T, fields: F) {
323
- for (const field in fields) {
324
- const type = fields[field];
325
- if (type == null) continue;
326
- const value = Reflect.get(input, field);
327
- const newValue = DataTypes.convertByType(value, type);
328
- if (newValue !== value) {
329
- Reflect.set(input, field, newValue);
330
- }
331
- }
325
+ }
326
+
327
+ /**
328
+ * Two values equal
329
+ * @param v1 Value 1
330
+ * @param v2 Value 2
331
+ * @param strict Strict level, 0 with ==, 1 === but null equal undefined, 2 ===
332
+ */
333
+ export function equals(v1: unknown, v2: unknown, strict = 1) {
334
+ // Null and undefined case
335
+ if (v1 == null || v2 == null) {
336
+ if (strict <= 1 && v1 == v2) return true;
337
+ return v1 === v2;
332
338
  }
333
339
 
334
- /**
335
- * Two values equal
336
- * @param v1 Value 1
337
- * @param v2 Value 2
338
- * @param strict Strict level, 0 with ==, 1 === but null equal undefined, 2 ===
339
- */
340
- export function equals(v1: unknown, v2: unknown, strict = 1) {
341
- // Null and undefined case
342
- if (v1 == null || v2 == null) {
343
- if (strict <= 1 && v1 == v2) return true;
344
- return v1 === v2;
340
+ // For date, array and object
341
+ if (typeof v1 === "object") return isEqual(v1, v2);
342
+
343
+ // 1 and '1' case
344
+ if (strict === 0) return v1 == v2;
345
+
346
+ // Strict equal
347
+ return v1 === v2;
348
+ }
349
+
350
+ /**
351
+ * Exclude specific items
352
+ * @param items Items
353
+ * @param field Filter field
354
+ * @param excludedValues Excluded values
355
+ * @returns Result
356
+ */
357
+ export function exclude<
358
+ T extends { [P in D]: IdType },
359
+ D extends string = "id"
360
+ >(items: T[], field: D, ...excludedValues: T[D][]) {
361
+ return items.filter((item) => !excludedValues.includes(item[field]));
362
+ }
363
+
364
+ /**
365
+ * Async exclude specific items
366
+ * @param items Items
367
+ * @param field Filter field
368
+ * @param excludedValues Excluded values
369
+ * @returns Result
370
+ */
371
+ export async function excludeAsync<
372
+ T extends { [P in D]: IdType },
373
+ D extends string = "id"
374
+ >(items: Promise<T[] | undefined>, field: D, ...excludedValues: T[D][]) {
375
+ const result = await items;
376
+ if (result == null) return result;
377
+ return exclude(result, field, ...excludedValues);
378
+ }
379
+
380
+ /**
381
+ * Format inital character to lower case or upper case
382
+ * @param input Input string
383
+ * @param upperCase To upper case or lower case
384
+ */
385
+ export function formatInitial(input: string, upperCase: boolean = false) {
386
+ return input.formatInitial(upperCase);
387
+ }
388
+
389
+ /**
390
+ * Format string with parameters
391
+ * @param template Template with {0}, {1}, ...
392
+ * @param parameters Parameters to fill the template
393
+ * @returns Result
394
+ */
395
+ export function formatString(template: string, ...parameters: string[]) {
396
+ return template.format(...parameters);
397
+ }
398
+
399
+ /**
400
+ * Get data changed fields with input data updated
401
+ * @param input Input data
402
+ * @param initData Initial data
403
+ * @param ignoreFields Ignore fields
404
+ * @returns
405
+ */
406
+ export function getDataChanges(
407
+ input: object,
408
+ initData: object,
409
+ ignoreFields: string[] = ["id"]
410
+ ): string[] {
411
+ // Changed fields
412
+ const changes: string[] = [];
413
+
414
+ Object.entries(input).forEach(([key, value]) => {
415
+ // Ignore fields, no process
416
+ if (ignoreFields.includes(key)) return;
417
+
418
+ // Compare with init value
419
+ const initValue = Reflect.get(initData, key);
420
+
421
+ if (value == null && initValue == null) {
422
+ // Both are null, it's equal
423
+ Reflect.deleteProperty(input, key);
424
+ return;
425
+ }
426
+
427
+ if (initValue != null) {
428
+ // Date when meets string
429
+ if (value instanceof Date) {
430
+ if (value.valueOf() === DateUtils.parse(initValue)?.valueOf()) {
431
+ Reflect.deleteProperty(input, key);
432
+ return;
433
+ }
434
+ changes.push(key);
435
+ return;
345
436
  }
346
437
 
347
- // For date, array and object
348
- if (typeof v1 === 'object') return isEqual(v1, v2);
438
+ const newValue = DataTypes.convert(value, initValue);
439
+ if (Utils.equals(newValue, initValue)) {
440
+ Reflect.deleteProperty(input, key);
441
+ return;
442
+ }
349
443
 
350
- // 1 and '1' case
351
- if (strict === 0) return v1 == v2;
444
+ // Update
445
+ Reflect.set(input, key, newValue);
446
+ }
352
447
 
353
- // Strict equal
354
- return v1 === v2;
355
- }
448
+ // Remove empty property
449
+ if (value == null || value === "") {
450
+ Reflect.deleteProperty(input, key);
451
+ }
356
452
 
357
- /**
358
- * Exclude specific items
359
- * @param items Items
360
- * @param field Filter field
361
- * @param excludedValues Excluded values
362
- * @returns Result
363
- */
364
- export function exclude<
365
- T extends { [P in D]: IdType },
366
- D extends string = 'id'
367
- >(items: T[], field: D, ...excludedValues: T[D][]) {
368
- return items.filter((item) => !excludedValues.includes(item[field]));
369
- }
370
-
371
- /**
372
- * Async exclude specific items
373
- * @param items Items
374
- * @param field Filter field
375
- * @param excludedValues Excluded values
376
- * @returns Result
377
- */
378
- export async function excludeAsync<
379
- T extends { [P in D]: IdType },
380
- D extends string = 'id'
381
- >(items: Promise<T[] | undefined>, field: D, ...excludedValues: T[D][]) {
382
- const result = await items;
383
- if (result == null) return result;
384
- return exclude(result, field, ...excludedValues);
385
- }
453
+ // Hold the key
454
+ changes.push(key);
455
+ });
386
456
 
387
- /**
388
- * Format inital character to lower case or upper case
389
- * @param input Input string
390
- * @param upperCase To upper case or lower case
391
- */
392
- export function formatInitial(input: string, upperCase: boolean = false) {
393
- return input.formatInitial(upperCase);
394
- }
457
+ return changes;
458
+ }
459
+
460
+ /**
461
+ * Get nested value from object
462
+ * @param data Data
463
+ * @param name Field name, support property chain like 'jsonData.logSize'
464
+ * @returns Result
465
+ */
466
+ export function getNestedValue(data: object, name: string) {
467
+ const properties = name.split(".");
468
+ const len = properties.length;
469
+ if (len === 1) {
470
+ return Reflect.get(data, name);
471
+ } else {
472
+ let curr = data;
473
+ for (let i = 0; i < len; i++) {
474
+ const property = properties[i];
395
475
 
396
- /**
397
- * Format string with parameters
398
- * @param template Template with {0}, {1}, ...
399
- * @param parameters Parameters to fill the template
400
- * @returns Result
401
- */
402
- export function formatString(template: string, ...parameters: string[]) {
403
- return template.format(...parameters);
404
- }
476
+ if (i + 1 === len) {
477
+ return Reflect.get(curr, property);
478
+ } else {
479
+ let p = Reflect.get(curr, property);
480
+ if (p == null) {
481
+ return undefined;
482
+ }
405
483
 
406
- /**
407
- * Get data changed fields with input data updated
408
- * @param input Input data
409
- * @param initData Initial data
410
- * @param ignoreFields Ignore fields
411
- * @returns
412
- */
413
- export function getDataChanges(
414
- input: object,
415
- initData: object,
416
- ignoreFields: string[] = ['id']
417
- ): string[] {
418
- // Changed fields
419
- const changes: string[] = [];
420
-
421
- Object.entries(input).forEach(([key, value]) => {
422
- // Ignore fields, no process
423
- if (ignoreFields.includes(key)) return;
424
-
425
- // Compare with init value
426
- const initValue = Reflect.get(initData, key);
427
-
428
- if (value == null && initValue == null) {
429
- // Both are null, it's equal
430
- Reflect.deleteProperty(input, key);
431
- return;
432
- }
433
-
434
- if (initValue != null) {
435
- // Date when meets string
436
- if (value instanceof Date) {
437
- if (
438
- value.valueOf() ===
439
- DateUtils.parse(initValue)?.valueOf()
440
- ) {
441
- Reflect.deleteProperty(input, key);
442
- return;
443
- }
444
- changes.push(key);
445
- return;
446
- }
447
-
448
- const newValue = DataTypes.convert(value, initValue);
449
- if (Utils.equals(newValue, initValue)) {
450
- Reflect.deleteProperty(input, key);
451
- return;
452
- }
453
-
454
- // Update
455
- Reflect.set(input, key, newValue);
456
- }
457
-
458
- // Remove empty property
459
- if (value == null || value === '') {
460
- Reflect.deleteProperty(input, key);
461
- }
462
-
463
- // Hold the key
464
- changes.push(key);
465
- });
466
-
467
- return changes;
484
+ curr = p;
485
+ }
486
+ }
468
487
  }
469
-
470
- /**
471
- * Get nested value from object
472
- * @param data Data
473
- * @param name Field name, support property chain like 'jsonData.logSize'
474
- * @returns Result
475
- */
476
- export function getNestedValue(data: object, name: string) {
477
- const properties = name.split('.');
478
- const len = properties.length;
479
- if (len === 1) {
480
- return Reflect.get(data, name);
481
- } else {
482
- let curr = data;
483
- for (let i = 0; i < len; i++) {
484
- const property = properties[i];
485
-
486
- if (i + 1 === len) {
487
- return Reflect.get(curr, property);
488
- } else {
489
- let p = Reflect.get(curr, property);
490
- if (p == null) {
491
- return undefined;
492
- }
493
-
494
- curr = p;
495
- }
496
- }
488
+ }
489
+
490
+ /**
491
+ * Get input function or value result
492
+ * @param input Input function or value
493
+ * @param args Arguments
494
+ * @returns Result
495
+ */
496
+ export const getResult = <R, T = DataTypes.Func<R> | R>(
497
+ input: T,
498
+ ...args: T extends DataTypes.Func<R> ? Parameters<typeof input> : never | []
499
+ ): T extends DataTypes.Func<R> ? ReturnType<T> : T => {
500
+ return typeof input === "function" ? input(...args) : input;
501
+ };
502
+
503
+ /**
504
+ * Get time zone
505
+ * @returns Timezone
506
+ */
507
+ export const getTimeZone = () => {
508
+ // If Intl supported
509
+ if (typeof Intl === "object" && typeof Intl.DateTimeFormat === "function")
510
+ return Intl.DateTimeFormat().resolvedOptions().timeZone;
511
+ };
512
+
513
+ /**
514
+ * Is digits string
515
+ * @param input Input string
516
+ * @param minLength Minimum length
517
+ * @returns Result
518
+ */
519
+ export const isDigits = (input?: string, minLength?: number) => {
520
+ if (input == null) return false;
521
+ return input.isDigits(minLength);
522
+ };
523
+
524
+ /**
525
+ * Is email string
526
+ * @param input Input string
527
+ * @returns Result
528
+ */
529
+ export const isEmail = (input?: string) => {
530
+ if (input == null) return false;
531
+ return input.isEmail();
532
+ };
533
+
534
+ /**
535
+ * Join items as a string
536
+ * @param items Items
537
+ * @param joinPart Join string
538
+ */
539
+ export const joinItems = (
540
+ items: (string | undefined)[],
541
+ joinPart: string = ", "
542
+ ) =>
543
+ items
544
+ .reduce((items, item) => {
545
+ if (item) {
546
+ const newItem = item.trim();
547
+ if (newItem) items.push(newItem);
497
548
  }
549
+ return items;
550
+ }, [] as string[])
551
+ .join(joinPart);
552
+
553
+ /**
554
+ * Merge class names
555
+ * @param classNames Class names
556
+ */
557
+ export const mergeClasses = (...classNames: (string | undefined)[]) =>
558
+ joinItems(classNames, " ");
559
+
560
+ /**
561
+ * Create a GUID
562
+ */
563
+ export function newGUID() {
564
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
565
+ const r = (Math.random() * 16) | 0,
566
+ v = c === "x" ? r : (r & 0x3) | 0x8;
567
+ return v.toString(16);
568
+ });
569
+ }
570
+
571
+ /**
572
+ * Number to base64 chars
573
+ * @param num Input number
574
+ * @returns Result
575
+ */
576
+ export function numberToChars(num: number) {
577
+ const codes = [];
578
+ while (num > 0) {
579
+ const code = num % 128;
580
+ codes.push(code);
581
+ num = (num - code) / 128;
498
582
  }
499
583
 
500
- /**
501
- * Get input function or value result
502
- * @param input Input function or value
503
- * @param args Arguments
504
- * @returns Result
505
- */
506
- export const getResult = <R, T = DataTypes.Func<R> | R>(
507
- input: T,
508
- ...args: T extends DataTypes.Func<R>
509
- ? Parameters<typeof input>
510
- : never | []
511
- ): T extends DataTypes.Func<R> ? ReturnType<T> : T => {
512
- return typeof input === 'function' ? input(...args) : input;
513
- };
514
-
515
- /**
516
- * Get time zone
517
- * @returns Timezone
518
- */
519
- export const getTimeZone = () => {
520
- // If Intl supported
521
- if (
522
- typeof Intl === 'object' &&
523
- typeof Intl.DateTimeFormat === 'function'
524
- )
525
- return Intl.DateTimeFormat().resolvedOptions().timeZone;
526
- };
527
-
528
- /**
529
- * Is digits string
530
- * @param input Input string
531
- * @param minLength Minimum length
532
- * @returns Result
533
- */
534
- export const isDigits = (input?: string, minLength?: number) => {
535
- if (input == null) return false;
536
- return input.isDigits(minLength);
537
- };
538
-
539
- /**
540
- * Is email string
541
- * @param input Input string
542
- * @returns Result
543
- */
544
- export const isEmail = (input?: string) => {
545
- if (input == null) return false;
546
- return input.isEmail();
547
- };
548
-
549
- /**
550
- * Join items as a string
551
- * @param items Items
552
- * @param joinPart Join string
553
- */
554
- export const joinItems = (
555
- items: (string | undefined)[],
556
- joinPart: string = ', '
557
- ) =>
558
- items
559
- .reduce((items, item) => {
560
- if (item) {
561
- const newItem = item.trim();
562
- if (newItem) items.push(newItem);
563
- }
564
- return items;
565
- }, [] as string[])
566
- .join(joinPart);
567
-
568
- /**
569
- * Merge class names
570
- * @param classNames Class names
571
- */
572
- export const mergeClasses = (...classNames: (string | undefined)[]) =>
573
- joinItems(classNames, ' ');
574
-
575
- /**
576
- * Create a GUID
577
- */
578
- export function newGUID() {
579
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
580
- const r = (Math.random() * 16) | 0,
581
- v = c === 'x' ? r : (r & 0x3) | 0x8;
582
- return v.toString(16);
583
- });
584
+ if (typeof Buffer === "undefined") {
585
+ return btoa(String.fromCharCode(...codes));
586
+ } else {
587
+ const buffer = Buffer.from(codes);
588
+ return buffer.toString("base64");
584
589
  }
585
-
586
- /**
587
- * Number to base64 chars
588
- * @param num Input number
589
- * @returns Result
590
- */
591
- export function numberToChars(num: number) {
592
- const codes = [];
593
- while (num > 0) {
594
- const code = num % 128;
595
- codes.push(code);
596
- num = (num - code) / 128;
597
- }
598
-
599
- if (typeof Buffer === 'undefined') {
600
- return btoa(String.fromCharCode(...codes));
601
- } else {
602
- const buffer = Buffer.from(codes);
603
- return buffer.toString('base64');
604
- }
590
+ }
591
+
592
+ /**
593
+ * Test two objects are equal or not
594
+ * @param obj1 Object 1
595
+ * @param obj2 Object 2
596
+ * @param ignoreFields Ignored fields
597
+ * @param strict Strict level, 0 with ==, 1 === but null equal undefined, 2 ===
598
+ * @returns Result
599
+ */
600
+ export function objectEqual(
601
+ obj1: object,
602
+ obj2: object,
603
+ ignoreFields: string[] = [],
604
+ strict = 1
605
+ ) {
606
+ // Unique keys
607
+ const keys = Utils.objectKeys(obj1, obj2, ignoreFields);
608
+
609
+ for (const key of keys) {
610
+ // Values
611
+ const v1 = Reflect.get(obj1, key);
612
+ const v2 = Reflect.get(obj2, key);
613
+
614
+ if (!Utils.equals(v1, v2, strict)) return false;
605
615
  }
606
616
 
607
- /**
608
- * Test two objects are equal or not
609
- * @param obj1 Object 1
610
- * @param obj2 Object 2
611
- * @param ignoreFields Ignored fields
612
- * @param strict Strict level, 0 with ==, 1 === but null equal undefined, 2 ===
613
- * @returns Result
614
- */
615
- export function objectEqual(
616
- obj1: object,
617
- obj2: object,
618
- ignoreFields: string[] = [],
619
- strict = 1
620
- ) {
621
- // Unique keys
622
- const keys = Utils.objectKeys(obj1, obj2, ignoreFields);
623
-
624
- for (const key of keys) {
625
- // Values
626
- const v1 = Reflect.get(obj1, key);
627
- const v2 = Reflect.get(obj2, key);
628
-
629
- if (!Utils.equals(v1, v2, strict)) return false;
630
- }
617
+ return true;
618
+ }
619
+
620
+ /**
621
+ * Get two object's unqiue properties
622
+ * @param obj1 Object 1
623
+ * @param obj2 Object 2
624
+ * @param ignoreFields Ignored fields
625
+ * @returns Unique properties
626
+ */
627
+ export function objectKeys(
628
+ obj1: object,
629
+ obj2: object,
630
+ ignoreFields: string[] = []
631
+ ) {
632
+ // All keys
633
+ const allKeys = [...Object.keys(obj1), ...Object.keys(obj2)].filter(
634
+ (item) => !ignoreFields.includes(item)
635
+ );
631
636
 
632
- return true;
637
+ // Unique keys
638
+ return new Set(allKeys);
639
+ }
640
+
641
+ /**
642
+ * Get the new object's updated fields contrast to the previous object
643
+ * @param objNew New object
644
+ * @param objPre Previous object
645
+ * @param ignoreFields Ignored fields
646
+ * @param strict Strict level, 0 with ==, 1 === but null equal undefined, 2 ===
647
+ * @returns Updated fields
648
+ */
649
+ export function objectUpdated(
650
+ objNew: object,
651
+ objPrev: object,
652
+ ignoreFields: string[] = [],
653
+ strict = 1
654
+ ) {
655
+ // Fields
656
+ const fields: string[] = [];
657
+
658
+ // Unique keys
659
+ const keys = Utils.objectKeys(objNew, objPrev, ignoreFields);
660
+
661
+ for (const key of keys) {
662
+ // Values
663
+ const vNew = Reflect.get(objNew, key);
664
+ const vPrev = Reflect.get(objPrev, key);
665
+
666
+ if (!Utils.equals(vNew, vPrev, strict)) {
667
+ fields.push(key);
668
+ }
633
669
  }
634
670
 
635
- /**
636
- * Get two object's unqiue properties
637
- * @param obj1 Object 1
638
- * @param obj2 Object 2
639
- * @param ignoreFields Ignored fields
640
- * @returns Unique properties
641
- */
642
- export function objectKeys(
643
- obj1: object,
644
- obj2: object,
645
- ignoreFields: string[] = []
646
- ) {
647
- // All keys
648
- const allKeys = [...Object.keys(obj1), ...Object.keys(obj2)].filter(
649
- (item) => !ignoreFields.includes(item)
650
- );
651
-
652
- // Unique keys
653
- return new Set(allKeys);
671
+ return fields;
672
+ }
673
+
674
+ /**
675
+ * Try to parse JSON input to array
676
+ * @param input JSON input
677
+ * @param checkValue Type check value
678
+ * @returns Result
679
+ */
680
+ export function parseJsonArray<T>(
681
+ input: string,
682
+ checkValue?: T
683
+ ): T[] | undefined {
684
+ try {
685
+ if (!input.startsWith("[")) input = `[${input}]`;
686
+ const array = JSON.parse(input);
687
+ const type = typeof checkValue;
688
+ if (
689
+ Array.isArray(array) &&
690
+ (checkValue == null || !array.some((item) => typeof item !== type))
691
+ ) {
692
+ return array;
693
+ }
694
+ } catch (e) {
695
+ console.error(`Utils.parseJsonArray ${input} with error`, e);
654
696
  }
655
-
656
- /**
657
- * Get the new object's updated fields contrast to the previous object
658
- * @param objNew New object
659
- * @param objPre Previous object
660
- * @param ignoreFields Ignored fields
661
- * @param strict Strict level, 0 with ==, 1 === but null equal undefined, 2 ===
662
- * @returns Updated fields
663
- */
664
- export function objectUpdated(
665
- objNew: object,
666
- objPrev: object,
667
- ignoreFields: string[] = [],
668
- strict = 1
669
- ) {
670
- // Fields
671
- const fields: string[] = [];
672
-
673
- // Unique keys
674
- const keys = Utils.objectKeys(objNew, objPrev, ignoreFields);
675
-
676
- for (const key of keys) {
677
- // Values
678
- const vNew = Reflect.get(objNew, key);
679
- const vPrev = Reflect.get(objPrev, key);
680
-
681
- if (!Utils.equals(vNew, vPrev, strict)) {
682
- fields.push(key);
683
- }
684
- }
685
-
686
- return fields;
697
+ return;
698
+ }
699
+
700
+ /**
701
+ * Parse string (JSON) to specific type, no type conversion
702
+ * For type conversion, please use DataTypes.convert
703
+ * @param input Input string
704
+ * @returns Parsed value
705
+ */
706
+ export function parseString<T>(
707
+ input: string | undefined | null
708
+ ): T | undefined;
709
+
710
+ /**
711
+ * Parse string (JSON) to specific type, no type conversion
712
+ * For type conversion, please use DataTypes.convert
713
+ * @param input Input string
714
+ * @param defaultValue Default value
715
+ * @returns Parsed value
716
+ */
717
+ export function parseString<T>(
718
+ input: string | undefined | null,
719
+ defaultValue: T
720
+ ): T;
721
+
722
+ /**
723
+ * Parse string (JSON) to specific type, no type conversion
724
+ * When return type depends on parameter value, uses function overloading, otherwise uses conditional type
725
+ * For type conversion, please use DataTypes.convert
726
+ * @param input Input string
727
+ * @param defaultValue Default value
728
+ * @returns Parsed value
729
+ */
730
+ export function parseString<T>(
731
+ input: string | undefined | null,
732
+ defaultValue?: T
733
+ ): T | undefined {
734
+ // Undefined and empty case, return default value
735
+ if (input == null || input === "") return <T>defaultValue;
736
+
737
+ // String
738
+ if (typeof defaultValue === "string") return <any>input;
739
+
740
+ try {
741
+ // Date
742
+ if (defaultValue instanceof Date) {
743
+ const date = new Date(input);
744
+ if (date == null) return <any>defaultValue;
745
+ return <any>date;
746
+ }
747
+
748
+ // JSON
749
+ const json = JSON.parse(input);
750
+
751
+ // Return
752
+ return <T>json;
753
+ } catch {
754
+ if (defaultValue == null) return <any>input;
755
+ return <T>defaultValue;
687
756
  }
688
-
689
- /**
690
- * Try to parse JSON input to array
691
- * @param input JSON input
692
- * @param checkValue Type check value
693
- * @returns Result
694
- */
695
- export function parseJsonArray<T>(
696
- input: string,
697
- checkValue?: T
698
- ): T[] | undefined {
699
- try {
700
- if (!input.startsWith('[')) input = `[${input}]`;
701
- const array = JSON.parse(input);
702
- const type = typeof checkValue;
703
- if (
704
- Array.isArray(array) &&
705
- (checkValue == null ||
706
- !array.some((item) => typeof item !== type))
707
- ) {
708
- return array;
709
- }
710
- } catch (e) {
711
- console.error(`Utils.parseJsonArray ${input} with error`, e);
712
- }
713
- return;
757
+ }
758
+
759
+ /**
760
+ * Remove empty values (null, undefined, '') from the input object
761
+ * @param input Input object
762
+ */
763
+ export function removeEmptyValues(input: object) {
764
+ Object.keys(input).forEach((key) => {
765
+ const value = Reflect.get(input, key);
766
+ if (value == null || value === "") {
767
+ Reflect.deleteProperty(input, key);
768
+ }
769
+ });
770
+ }
771
+
772
+ /**
773
+ * Remove non letters
774
+ * @param input Input string
775
+ * @returns Result
776
+ */
777
+ export const removeNonLetters = (input?: string) => {
778
+ return input?.removeNonLetters();
779
+ };
780
+
781
+ /**
782
+ * Replace null or empty with default value
783
+ * @param input Input string
784
+ * @param defaultValue Default value
785
+ * @returns Result
786
+ */
787
+ export const replaceNullOrEmpty = (
788
+ input: string | null | undefined,
789
+ defaultValue: string
790
+ ) => {
791
+ if (input == null || input.trim() === "") return defaultValue;
792
+ return input;
793
+ };
794
+
795
+ /**
796
+ * Set source with new labels
797
+ * @param source Source
798
+ * @param labels Labels
799
+ * @param reference Key reference dictionary
800
+ */
801
+ export const setLabels = (
802
+ source: DataTypes.StringRecord,
803
+ labels: DataTypes.StringRecord,
804
+ reference?: Readonly<DataTypes.StringDictionary>
805
+ ) => {
806
+ Object.keys(source).forEach((key) => {
807
+ // Reference key
808
+ const labelKey = reference == null ? key : reference[key] ?? key;
809
+
810
+ // Label
811
+ const label = labels[labelKey];
812
+
813
+ if (label != null) {
814
+ // If found, update
815
+ Reflect.set(source, key, label);
816
+ }
817
+ });
818
+ };
819
+
820
+ /**
821
+ * Snake name to works, 'snake_name' to 'Snake Name'
822
+ * @param name Name text
823
+ * @param firstOnly Only convert the first word to upper case
824
+ */
825
+ export const snakeNameToWord = (name: string, firstOnly: boolean = false) => {
826
+ const items = name.split("_");
827
+ if (firstOnly) {
828
+ items[0] = items[0].formatInitial(true);
829
+ return items.join(" ");
714
830
  }
715
831
 
716
- /**
717
- * Parse string (JSON) to specific type, no type conversion
718
- * For type conversion, please use DataTypes.convert
719
- * @param input Input string
720
- * @returns Parsed value
721
- */
722
- export function parseString<T>(
723
- input: string | undefined | null
724
- ): T | undefined;
832
+ return items.map((part) => part.formatInitial(true)).join(" ");
833
+ };
834
+
835
+ function getSortValue(n1: number, n2: number) {
836
+ if (n1 === n2) return 0;
837
+ if (n1 === -1) return 1;
838
+ if (n2 === -1) return -1;
839
+ return n1 - n2;
840
+ }
841
+
842
+ /**
843
+ * Set nested value to object
844
+ * @param data Data
845
+ * @param name Field name, support property chain like 'jsonData.logSize'
846
+ * @param value Value
847
+ * @param keepNull Keep null value or not
848
+ */
849
+ export function setNestedValue(
850
+ data: object,
851
+ name: string,
852
+ value: unknown,
853
+ keepNull?: boolean
854
+ ) {
855
+ const properties = name.split(".");
856
+ const len = properties.length;
857
+ if (len === 1) {
858
+ if (value == null && keepNull !== true) {
859
+ Reflect.deleteProperty(data, name);
860
+ } else {
861
+ Reflect.set(data, name, value);
862
+ }
863
+ } else {
864
+ let curr = data;
865
+ for (let i = 0; i < len; i++) {
866
+ const property = properties[i];
725
867
 
726
- /**
727
- * Parse string (JSON) to specific type, no type conversion
728
- * For type conversion, please use DataTypes.convert
729
- * @param input Input string
730
- * @param defaultValue Default value
731
- * @returns Parsed value
732
- */
733
- export function parseString<T>(
734
- input: string | undefined | null,
735
- defaultValue: T
736
- ): T;
868
+ if (i + 1 === len) {
869
+ setNestedValue(curr, property, value, keepNull);
870
+ // Reflect.set(curr, property, value);
871
+ } else {
872
+ let p = Reflect.get(curr, property);
873
+ if (p == null) {
874
+ p = {};
875
+ Reflect.set(curr, property, p);
876
+ }
737
877
 
738
- /**
739
- * Parse string (JSON) to specific type, no type conversion
740
- * When return type depends on parameter value, uses function overloading, otherwise uses conditional type
741
- * For type conversion, please use DataTypes.convert
742
- * @param input Input string
743
- * @param defaultValue Default value
744
- * @returns Parsed value
745
- */
746
- export function parseString<T>(
747
- input: string | undefined | null,
748
- defaultValue?: T
749
- ): T | undefined {
750
- // Undefined and empty case, return default value
751
- if (input == null || input === '') return <T>defaultValue;
752
-
753
- // String
754
- if (typeof defaultValue === 'string') return <any>input;
755
-
756
- try {
757
- // Date
758
- if (defaultValue instanceof Date) {
759
- const date = new Date(input);
760
- if (date == null) return <any>defaultValue;
761
- return <any>date;
762
- }
763
-
764
- // JSON
765
- const json = JSON.parse(input);
766
-
767
- // Return
768
- return <T>json;
769
- } catch {
770
- if (defaultValue == null) return <any>input;
771
- return <T>defaultValue;
878
+ curr = p;
772
879
  }
880
+ }
773
881
  }
774
-
775
- /**
776
- * Remove empty values (null, undefined, '') from the input object
777
- * @param input Input object
778
- */
779
- export function removeEmptyValues(input: object) {
780
- Object.keys(input).forEach((key) => {
781
- const value = Reflect.get(input, key);
782
- if (value == null || value === '') {
783
- Reflect.deleteProperty(input, key);
784
- }
785
- });
882
+ }
883
+
884
+ /**
885
+ * Parse path similar with node.js path.parse
886
+ * @param path Input path
887
+ */
888
+ export const parsePath = (path: string): ParsedPath => {
889
+ // Two formats or mixed
890
+ // /home/user/dir/file.txt
891
+ // C:\\path\\dir\\file.txt
892
+ const lastIndex = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
893
+
894
+ let root = "",
895
+ dir = "",
896
+ base: string,
897
+ ext: string,
898
+ name: string;
899
+
900
+ if (lastIndex === -1) {
901
+ base = path;
902
+ } else {
903
+ base = path.substring(lastIndex + 1);
904
+ const index1 = path.indexOf("/");
905
+ const index2 = path.indexOf("\\");
906
+ const index =
907
+ index1 === -1
908
+ ? index2
909
+ : index2 === -1
910
+ ? index1
911
+ : Math.min(index1, index2);
912
+ root = path.substring(0, index + 1);
913
+ dir = path.substring(0, lastIndex);
914
+ if (dir === "") dir = root;
786
915
  }
787
916
 
788
- /**
789
- * Remove non letters
790
- * @param input Input string
791
- * @returns Result
792
- */
793
- export const removeNonLetters = (input?: string) => {
794
- return input?.removeNonLetters();
795
- };
796
-
797
- /**
798
- * Replace null or empty with default value
799
- * @param input Input string
800
- * @param defaultValue Default value
801
- * @returns Result
802
- */
803
- export const replaceNullOrEmpty = (
804
- input: string | null | undefined,
805
- defaultValue: string
806
- ) => {
807
- if (input == null || input.trim() === '') return defaultValue;
808
- return input;
809
- };
810
-
811
- /**
812
- * Set source with new labels
813
- * @param source Source
814
- * @param labels Labels
815
- * @param reference Key reference dictionary
816
- */
817
- export const setLabels = (
818
- source: DataTypes.StringRecord,
819
- labels: DataTypes.StringRecord,
820
- reference?: Readonly<DataTypes.StringDictionary>
821
- ) => {
822
- Object.keys(source).forEach((key) => {
823
- // Reference key
824
- const labelKey = reference == null ? key : reference[key] ?? key;
825
-
826
- // Label
827
- const label = labels[labelKey];
828
-
829
- if (label != null) {
830
- // If found, update
831
- Reflect.set(source, key, label);
832
- }
833
- });
834
- };
835
-
836
- /**
837
- * Snake name to works, 'snake_name' to 'Snake Name'
838
- * @param name Name text
839
- * @param firstOnly Only convert the first word to upper case
840
- */
841
- export const snakeNameToWord = (
842
- name: string,
843
- firstOnly: boolean = false
844
- ) => {
845
- const items = name.split('_');
846
- if (firstOnly) {
847
- items[0] = items[0].formatInitial(true);
848
- return items.join(' ');
849
- }
850
-
851
- return items.map((part) => part.formatInitial(true)).join(' ');
852
- };
853
-
854
- function getSortValue(n1: number, n2: number) {
855
- if (n1 === n2) return 0;
856
- if (n1 === -1) return 1;
857
- if (n2 === -1) return -1;
858
- return n1 - n2;
917
+ const extIndex = base.lastIndexOf(".");
918
+ if (extIndex === -1) {
919
+ name = base;
920
+ ext = "";
921
+ } else {
922
+ name = base.substring(0, extIndex);
923
+ ext = base.substring(extIndex);
859
924
  }
860
925
 
861
- /**
862
- * Set nested value to object
863
- * @param data Data
864
- * @param name Field name, support property chain like 'jsonData.logSize'
865
- * @param value Value
866
- * @param keepNull Keep null value or not
867
- */
868
- export function setNestedValue(
869
- data: object,
870
- name: string,
871
- value: unknown,
872
- keepNull?: boolean
873
- ) {
874
- const properties = name.split('.');
875
- const len = properties.length;
876
- if (len === 1) {
877
- if (value == null && keepNull !== true) {
878
- Reflect.deleteProperty(data, name);
879
- } else {
880
- Reflect.set(data, name, value);
881
- }
882
- } else {
883
- let curr = data;
884
- for (let i = 0; i < len; i++) {
885
- const property = properties[i];
886
-
887
- if (i + 1 === len) {
888
- setNestedValue(curr, property, value, keepNull);
889
- // Reflect.set(curr, property, value);
890
- } else {
891
- let p = Reflect.get(curr, property);
892
- if (p == null) {
893
- p = {};
894
- Reflect.set(curr, property, p);
895
- }
896
-
897
- curr = p;
898
- }
899
- }
900
- }
926
+ return { root, dir, base, ext, name };
927
+ };
928
+
929
+ /**
930
+ * Sort array by favored values
931
+ * @param items Items
932
+ * @param favored Favored values
933
+ * @returns Sorted array
934
+ */
935
+ export const sortByFavor = <T>(items: T[], favored: T[]) => {
936
+ return items.sort((r1, r2) => {
937
+ const n1 = favored.indexOf(r1);
938
+ const n2 = favored.indexOf(r2);
939
+ return getSortValue(n1, n2);
940
+ });
941
+ };
942
+
943
+ /**
944
+ * Sort array by favored field values
945
+ * @param items Items
946
+ * @param field Field to sort
947
+ * @param favored Favored field values
948
+ * @returns Sorted array
949
+ */
950
+ export const sortByFieldFavor = <T, F extends keyof T>(
951
+ items: T[],
952
+ field: F,
953
+ favored: T[F][]
954
+ ) => {
955
+ return items.sort((r1, r2) => {
956
+ const n1 = favored.indexOf(r1[field]);
957
+ const n2 = favored.indexOf(r2[field]);
958
+ return getSortValue(n1, n2);
959
+ });
960
+ };
961
+
962
+ /**
963
+ * Trim chars
964
+ * @param input Input string
965
+ * @param chars Trim chars
966
+ * @returns Result
967
+ */
968
+ export const trim = (input: string, ...chars: string[]) => {
969
+ return trimEnd(trimStart(input, ...chars), ...chars);
970
+ };
971
+
972
+ /**
973
+ * Trim end chars
974
+ * @param input Input string
975
+ * @param chars Trim chars
976
+ * @returns Result
977
+ */
978
+ export const trimEnd = (input: string, ...chars: string[]) => {
979
+ let char: string | undefined;
980
+ while ((char = chars.find((char) => input.endsWith(char))) != null) {
981
+ input = input.substring(0, input.length - char.length);
901
982
  }
902
-
903
- /**
904
- * Parse path similar with node.js path.parse
905
- * @param path Input path
906
- */
907
- export const parsePath = (path: string): ParsedPath => {
908
- // Two formats or mixed
909
- // /home/user/dir/file.txt
910
- // C:\\path\\dir\\file.txt
911
- const lastIndex = Math.max(
912
- path.lastIndexOf('/'),
913
- path.lastIndexOf('\\')
914
- );
915
-
916
- let root = '',
917
- dir = '',
918
- base: string,
919
- ext: string,
920
- name: string;
921
-
922
- if (lastIndex === -1) {
923
- base = path;
924
- } else {
925
- base = path.substring(lastIndex + 1);
926
- const index1 = path.indexOf('/');
927
- const index2 = path.indexOf('\\');
928
- const index =
929
- index1 === -1
930
- ? index2
931
- : index2 === -1
932
- ? index1
933
- : Math.min(index1, index2);
934
- root = path.substring(0, index + 1);
935
- dir = path.substring(0, lastIndex);
936
- if (dir === '') dir = root;
937
- }
938
-
939
- const extIndex = base.lastIndexOf('.');
940
- if (extIndex === -1) {
941
- name = base;
942
- ext = '';
943
- } else {
944
- name = base.substring(0, extIndex);
945
- ext = base.substring(extIndex);
946
- }
947
-
948
- return { root, dir, base, ext, name };
949
- };
950
-
951
- /**
952
- * Sort array by favored values
953
- * @param items Items
954
- * @param favored Favored values
955
- * @returns Sorted array
956
- */
957
- export const sortByFavor = <T>(items: T[], favored: T[]) => {
958
- return items.sort((r1, r2) => {
959
- const n1 = favored.indexOf(r1);
960
- const n2 = favored.indexOf(r2);
961
- return getSortValue(n1, n2);
962
- });
963
- };
964
-
965
- /**
966
- * Sort array by favored field values
967
- * @param items Items
968
- * @param field Field to sort
969
- * @param favored Favored field values
970
- * @returns Sorted array
971
- */
972
- export const sortByFieldFavor = <T, F extends keyof T>(
973
- items: T[],
974
- field: F,
975
- favored: T[F][]
976
- ) => {
977
- return items.sort((r1, r2) => {
978
- const n1 = favored.indexOf(r1[field]);
979
- const n2 = favored.indexOf(r2[field]);
980
- return getSortValue(n1, n2);
981
- });
982
- };
983
-
984
- /**
985
- * Trim chars
986
- * @param input Input string
987
- * @param chars Trim chars
988
- * @returns Result
989
- */
990
- export const trim = (input: string, ...chars: string[]) => {
991
- return trimEnd(trimStart(input, ...chars), ...chars);
992
- };
993
-
994
- /**
995
- * Trim end chars
996
- * @param input Input string
997
- * @param chars Trim chars
998
- * @returns Result
999
- */
1000
- export const trimEnd = (input: string, ...chars: string[]) => {
1001
- let char: string | undefined;
1002
- while ((char = chars.find((char) => input.endsWith(char))) != null) {
1003
- input = input.substring(0, input.length - char.length);
1004
- }
1005
- return input;
1006
- };
1007
-
1008
- /**
1009
- * Trim start chars
1010
- * @param input Input string
1011
- * @param chars Trim chars
1012
- * @returns Result
1013
- */
1014
- export const trimStart = (input: string, ...chars: string[]) => {
1015
- let char: string | undefined;
1016
- while ((char = chars.find((char) => input.startsWith(char))) != null) {
1017
- input = input.substring(char.length);
1018
- }
1019
- return input;
1020
- };
983
+ return input;
984
+ };
985
+
986
+ /**
987
+ * Trim start chars
988
+ * @param input Input string
989
+ * @param chars Trim chars
990
+ * @returns Result
991
+ */
992
+ export const trimStart = (input: string, ...chars: string[]) => {
993
+ let char: string | undefined;
994
+ while ((char = chars.find((char) => input.startsWith(char))) != null) {
995
+ input = input.substring(char.length);
996
+ }
997
+ return input;
998
+ };
1021
999
  }