@simplysm/core-common 13.0.69 → 13.0.71
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/README.md +66 -267
- package/dist/common.types.d.ts +14 -14
- package/dist/errors/argument-error.d.ts +10 -10
- package/dist/errors/argument-error.d.ts.map +1 -1
- package/dist/errors/argument-error.js +2 -2
- package/dist/errors/argument-error.js.map +1 -1
- package/dist/errors/not-implemented-error.d.ts +8 -8
- package/dist/errors/not-implemented-error.js +2 -2
- package/dist/errors/not-implemented-error.js.map +1 -1
- package/dist/errors/sd-error.d.ts +10 -10
- package/dist/errors/sd-error.d.ts.map +1 -1
- package/dist/errors/timeout-error.d.ts +10 -10
- package/dist/errors/timeout-error.js +3 -3
- package/dist/errors/timeout-error.js.map +1 -1
- package/dist/extensions/arr-ext.d.ts +2 -2
- package/dist/extensions/arr-ext.helpers.d.ts +8 -8
- package/dist/extensions/arr-ext.helpers.js +1 -1
- package/dist/extensions/arr-ext.helpers.js.map +1 -1
- package/dist/extensions/arr-ext.js +13 -13
- package/dist/extensions/arr-ext.js.map +1 -1
- package/dist/extensions/arr-ext.types.d.ts +57 -57
- package/dist/extensions/arr-ext.types.d.ts.map +1 -1
- package/dist/extensions/map-ext.d.ts +16 -16
- package/dist/extensions/set-ext.d.ts +11 -11
- package/dist/features/debounce-queue.d.ts +17 -15
- package/dist/features/debounce-queue.d.ts.map +1 -1
- package/dist/features/debounce-queue.js +6 -6
- package/dist/features/debounce-queue.js.map +1 -1
- package/dist/features/event-emitter.d.ts +20 -20
- package/dist/features/event-emitter.js +17 -17
- package/dist/features/serial-queue.d.ts +11 -11
- package/dist/features/serial-queue.js +5 -5
- package/dist/features/serial-queue.js.map +1 -1
- package/dist/globals.d.ts +4 -4
- package/dist/types/date-only.d.ts +64 -64
- package/dist/types/date-only.d.ts.map +1 -1
- package/dist/types/date-only.js +63 -63
- package/dist/types/date-time.d.ts +37 -37
- package/dist/types/date-time.d.ts.map +1 -1
- package/dist/types/date-time.js +54 -37
- package/dist/types/date-time.js.map +1 -1
- package/dist/types/lazy-gc-map.d.ts +26 -26
- package/dist/types/lazy-gc-map.d.ts.map +1 -1
- package/dist/types/lazy-gc-map.js +26 -26
- package/dist/types/lazy-gc-map.js.map +1 -1
- package/dist/types/time.d.ts +25 -25
- package/dist/types/time.d.ts.map +1 -1
- package/dist/types/time.js +25 -25
- package/dist/types/time.js.map +1 -1
- package/dist/types/uuid.d.ts +11 -11
- package/dist/types/uuid.d.ts.map +1 -1
- package/dist/types/uuid.js +12 -12
- package/dist/types/uuid.js.map +1 -1
- package/dist/utils/bytes.d.ts +17 -17
- package/dist/utils/bytes.js +4 -4
- package/dist/utils/bytes.js.map +1 -1
- package/dist/utils/date-format.d.ts +45 -45
- package/dist/utils/date-format.js +1 -1
- package/dist/utils/date-format.js.map +1 -1
- package/dist/utils/error.d.ts +4 -4
- package/dist/utils/json.d.ts +17 -17
- package/dist/utils/json.js +3 -3
- package/dist/utils/json.js.map +1 -1
- package/dist/utils/num.d.ts +23 -23
- package/dist/utils/obj.d.ts +111 -111
- package/dist/utils/obj.d.ts.map +1 -1
- package/dist/utils/obj.js +3 -3
- package/dist/utils/obj.js.map +1 -1
- package/dist/utils/path.d.ts +10 -10
- package/dist/utils/primitive.d.ts +5 -5
- package/dist/utils/primitive.js +1 -1
- package/dist/utils/primitive.js.map +1 -1
- package/dist/utils/str.d.ts +46 -46
- package/dist/utils/str.d.ts.map +1 -1
- package/dist/utils/str.js +5 -5
- package/dist/utils/str.js.map +1 -1
- package/dist/utils/template-strings.d.ts +26 -26
- package/dist/utils/transferable.d.ts +18 -18
- package/dist/utils/transferable.js +1 -1
- package/dist/utils/transferable.js.map +1 -1
- package/dist/utils/wait.d.ts +9 -9
- package/dist/utils/xml.d.ts +13 -13
- package/dist/utils/xml.d.ts.map +1 -1
- package/dist/utils/xml.js +1 -0
- package/dist/utils/xml.js.map +1 -1
- package/dist/zip/sd-zip.d.ts +22 -22
- package/dist/zip/sd-zip.js +16 -16
- package/package.json +4 -4
- package/src/common.types.ts +17 -17
- package/src/errors/argument-error.ts +15 -15
- package/src/errors/not-implemented-error.ts +9 -9
- package/src/errors/sd-error.ts +12 -12
- package/src/errors/timeout-error.ts +12 -12
- package/src/extensions/arr-ext.helpers.ts +10 -10
- package/src/extensions/arr-ext.ts +57 -57
- package/src/extensions/arr-ext.types.ts +59 -59
- package/src/extensions/map-ext.ts +16 -16
- package/src/extensions/set-ext.ts +11 -11
- package/src/features/debounce-queue.ts +21 -19
- package/src/features/event-emitter.ts +25 -25
- package/src/features/serial-queue.ts +13 -13
- package/src/globals.ts +4 -4
- package/src/index.ts +1 -1
- package/src/types/date-only.ts +83 -83
- package/src/types/date-time.ts +64 -44
- package/src/types/lazy-gc-map.ts +45 -45
- package/src/types/time.ts +34 -34
- package/src/types/uuid.ts +17 -17
- package/src/utils/bytes.ts +35 -35
- package/src/utils/date-format.ts +65 -65
- package/src/utils/error.ts +4 -4
- package/src/utils/json.ts +39 -39
- package/src/utils/num.ts +23 -23
- package/src/utils/obj.ts +138 -138
- package/src/utils/path.ts +10 -10
- package/src/utils/primitive.ts +6 -6
- package/src/utils/str.ts +260 -261
- package/src/utils/template-strings.ts +29 -29
- package/src/utils/transferable.ts +284 -284
- package/src/utils/wait.ts +10 -10
- package/src/utils/xml.ts +20 -19
- package/src/zip/sd-zip.ts +25 -25
- package/tests/errors/errors.spec.ts +80 -0
- package/tests/extensions/array-extension.spec.ts +796 -0
- package/tests/extensions/map-extension.spec.ts +147 -0
- package/tests/extensions/set-extension.spec.ts +74 -0
- package/tests/types/date-only.spec.ts +638 -0
- package/tests/types/date-time.spec.ts +391 -0
- package/tests/types/lazy-gc-map.spec.ts +692 -0
- package/tests/types/time.spec.ts +559 -0
- package/tests/types/uuid.spec.ts +74 -0
- package/tests/utils/bytes-utils.spec.ts +230 -0
- package/tests/utils/date-format.spec.ts +373 -0
- package/tests/utils/debounce-queue.spec.ts +272 -0
- package/tests/utils/json.spec.ts +486 -0
- package/tests/utils/number.spec.ts +157 -0
- package/tests/utils/object.spec.ts +829 -0
- package/tests/utils/path.spec.ts +78 -0
- package/tests/utils/primitive.spec.ts +43 -0
- package/tests/utils/sd-event-emitter.spec.ts +216 -0
- package/tests/utils/serial-queue.spec.ts +365 -0
- package/tests/utils/string.spec.ts +281 -0
- package/tests/utils/template-strings.spec.ts +57 -0
- package/tests/utils/transferable.spec.ts +703 -0
- package/tests/utils/wait.spec.ts +145 -0
- package/tests/utils/xml.spec.ts +146 -0
- package/tests/zip/sd-zip.spec.ts +238 -0
- package/docs/extensions.md +0 -503
- package/docs/features.md +0 -109
- package/docs/types.md +0 -486
- package/docs/utils.md +0 -780
package/src/utils/bytes.ts
CHANGED
|
@@ -2,32 +2,32 @@ import type { Bytes } from "../common.types";
|
|
|
2
2
|
import { ArgumentError } from "../errors/argument-error";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Uint8Array
|
|
5
|
+
* Uint8Array utility functions (complex operations only)
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* - bytesConcat:
|
|
9
|
-
* - bytesToHex: Uint8Array
|
|
10
|
-
* - bytesFromHex: hex
|
|
11
|
-
* - bytesToBase64: Uint8Array
|
|
12
|
-
* - bytesFromBase64: base64
|
|
7
|
+
* Features:
|
|
8
|
+
* - bytesConcat: Concatenate multiple Uint8Arrays
|
|
9
|
+
* - bytesToHex: Convert Uint8Array to hex string
|
|
10
|
+
* - bytesFromHex: Convert hex string to Uint8Array
|
|
11
|
+
* - bytesToBase64: Convert Uint8Array to base64 string
|
|
12
|
+
* - bytesFromBase64: Convert base64 string to Uint8Array
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
/**
|
|
15
|
+
/** Lookup table for hex conversion (performance optimization) */
|
|
16
16
|
const hexTable: string[] = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
|
|
17
17
|
|
|
18
|
-
/**
|
|
18
|
+
/** Base64 encoding table */
|
|
19
19
|
const BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
20
20
|
|
|
21
|
-
/**
|
|
21
|
+
/** Base64 decoding lookup table (O(1) lookup, covers all byte values) */
|
|
22
22
|
const BASE64_LOOKUP: number[] = Array.from({ length: 256 }, (_, i) => {
|
|
23
23
|
const idx = BASE64_CHARS.indexOf(String.fromCharCode(i));
|
|
24
24
|
return idx === -1 ? 0 : idx;
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
29
|
-
* @param arrays
|
|
30
|
-
* @returns
|
|
28
|
+
* Concatenate multiple Uint8Arrays
|
|
29
|
+
* @param arrays Uint8Array array to concatenate
|
|
30
|
+
* @returns New concatenated Uint8Array
|
|
31
31
|
* @example
|
|
32
32
|
* const a = new Uint8Array([1, 2]);
|
|
33
33
|
* const b = new Uint8Array([3, 4]);
|
|
@@ -46,9 +46,9 @@ export function bytesConcat(arrays: Bytes[]): Bytes {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
|
-
* hex
|
|
50
|
-
* @param bytes
|
|
51
|
-
* @returns
|
|
49
|
+
* Convert to hex string
|
|
50
|
+
* @param bytes Uint8Array to convert
|
|
51
|
+
* @returns Lowercase hex string
|
|
52
52
|
* @example
|
|
53
53
|
* bytesToHex(new Uint8Array([255, 0, 127]));
|
|
54
54
|
* // "ff007f"
|
|
@@ -63,20 +63,20 @@ export function bytesToHex(bytes: Bytes): string {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
* hex
|
|
67
|
-
* @param hex
|
|
68
|
-
* @returns
|
|
69
|
-
* @throws {ArgumentError}
|
|
66
|
+
* Convert from hex string to Uint8Array
|
|
67
|
+
* @param hex Hex string to convert (lowercase and uppercase allowed)
|
|
68
|
+
* @returns Converted Uint8Array
|
|
69
|
+
* @throws {ArgumentError} If odd length or invalid hex characters are present
|
|
70
70
|
* @example
|
|
71
71
|
* bytesFromHex("ff007f");
|
|
72
72
|
* // Uint8Array([255, 0, 127])
|
|
73
73
|
*/
|
|
74
74
|
export function bytesFromHex(hex: string): Bytes {
|
|
75
75
|
if (hex.length % 2 !== 0) {
|
|
76
|
-
throw new ArgumentError("
|
|
76
|
+
throw new ArgumentError("Hex string must have even length", { hex });
|
|
77
77
|
}
|
|
78
78
|
if (hex.length > 0 && !/^[0-9a-fA-F]+$/.test(hex)) {
|
|
79
|
-
throw new ArgumentError("
|
|
79
|
+
throw new ArgumentError("Invalid hex character included", { hex });
|
|
80
80
|
}
|
|
81
81
|
const bytes = new Uint8Array(hex.length / 2);
|
|
82
82
|
for (let i = 0; i < bytes.length; i++) {
|
|
@@ -86,9 +86,9 @@ export function bytesFromHex(hex: string): Bytes {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
/**
|
|
89
|
-
* Bytes
|
|
90
|
-
* @param bytes
|
|
91
|
-
* @returns
|
|
89
|
+
* Convert Bytes to base64 string
|
|
90
|
+
* @param bytes Uint8Array to convert
|
|
91
|
+
* @returns Base64 encoded string
|
|
92
92
|
* @example
|
|
93
93
|
* bytesToBase64(new Uint8Array([72, 101, 108, 108, 111]));
|
|
94
94
|
* // "SGVsbG8="
|
|
@@ -113,33 +113,33 @@ export function bytesToBase64(bytes: Bytes): string {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
/**
|
|
116
|
-
* base64
|
|
117
|
-
* @param base64
|
|
118
|
-
* @returns
|
|
119
|
-
* @throws {ArgumentError}
|
|
116
|
+
* Convert base64 string to Bytes
|
|
117
|
+
* @param base64 Base64 string to convert
|
|
118
|
+
* @returns Decoded Uint8Array
|
|
119
|
+
* @throws {ArgumentError} If invalid base64 character is present
|
|
120
120
|
* @example
|
|
121
121
|
* bytesFromBase64("SGVsbG8=");
|
|
122
122
|
* // Uint8Array([72, 101, 108, 108, 111])
|
|
123
123
|
*/
|
|
124
124
|
export function bytesFromBase64(base64: string): Bytes {
|
|
125
|
-
//
|
|
125
|
+
// Remove whitespace and normalize padding
|
|
126
126
|
const cleanBase64 = base64.replace(/\s/g, "").replace(/=+$/, "");
|
|
127
127
|
|
|
128
|
-
//
|
|
128
|
+
// Handle empty string
|
|
129
129
|
if (cleanBase64.length === 0) {
|
|
130
130
|
return new Uint8Array(0);
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
//
|
|
133
|
+
// Validation: characters
|
|
134
134
|
if (!/^[A-Za-z0-9+/]+$/.test(cleanBase64)) {
|
|
135
|
-
throw new ArgumentError("
|
|
135
|
+
throw new ArgumentError("Invalid base64 character included", {
|
|
136
136
|
base64: base64.substring(0, 20),
|
|
137
137
|
});
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
//
|
|
140
|
+
// Validation: length (remainder of 1 after padding removal is invalid)
|
|
141
141
|
if (cleanBase64.length % 4 === 1) {
|
|
142
|
-
throw new ArgumentError("
|
|
142
|
+
throw new ArgumentError("Invalid base64 length", { length: cleanBase64.length });
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
const len = cleanBase64.length;
|
package/src/utils/date-format.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Result of year/month/day normalization when setting month
|
|
3
3
|
*/
|
|
4
4
|
export interface DtNormalizedMonth {
|
|
5
5
|
year: number;
|
|
@@ -8,27 +8,27 @@ export interface DtNormalizedMonth {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
12
|
-
* -
|
|
13
|
-
* -
|
|
11
|
+
* Normalize year/month/day when setting month
|
|
12
|
+
* - Adjust year if month is outside 1-12 range
|
|
13
|
+
* - Adjust to last day of target month if current day is greater than the number of days in the target month
|
|
14
14
|
*
|
|
15
|
-
* @param year
|
|
16
|
-
* @param month
|
|
17
|
-
* @param day
|
|
18
|
-
* @returns
|
|
15
|
+
* @param year Base year
|
|
16
|
+
* @param month Month to set (values outside 1-12 range allowed)
|
|
17
|
+
* @param day Base day
|
|
18
|
+
* @returns Normalized year, month, day
|
|
19
19
|
*
|
|
20
20
|
* @example
|
|
21
21
|
* normalizeMonth(2025, 13, 15) // { year: 2026, month: 1, day: 15 }
|
|
22
22
|
* normalizeMonth(2025, 2, 31) // { year: 2025, month: 2, day: 28 }
|
|
23
23
|
*/
|
|
24
24
|
export function normalizeMonth(year: number, month: number, day: number): DtNormalizedMonth {
|
|
25
|
-
//
|
|
26
|
-
// month
|
|
25
|
+
// Normalize month overflow/underflow
|
|
26
|
+
// Adjust year if month is outside 1-12 range
|
|
27
27
|
const normalizedYear = year + Math.floor((month - 1) / 12);
|
|
28
|
-
// JavaScript %
|
|
28
|
+
// JavaScript % operator returns negative for negative, so use (% 12 + 12) % 12 pattern to ensure 0-11 range, then convert to 1-12
|
|
29
29
|
const normalizedMonth = ((((month - 1) % 12) + 12) % 12) + 1;
|
|
30
30
|
|
|
31
|
-
//
|
|
31
|
+
// Get last day of target month
|
|
32
32
|
const lastDay = new Date(normalizedYear, normalizedMonth, 0).getDate();
|
|
33
33
|
const normalizedDay = Math.min(day, lastDay);
|
|
34
34
|
|
|
@@ -36,13 +36,13 @@ export function normalizeMonth(year: number, month: number, day: number): DtNorm
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
|
-
* 12
|
|
40
|
-
* -
|
|
41
|
-
* -
|
|
39
|
+
* Convert 12-hour format to 24-hour format
|
|
40
|
+
* - 12:00 AM = 0:00, 12:00 PM = 12:00
|
|
41
|
+
* - 1-11 AM = 1-11, 1-11 PM = 13-23
|
|
42
42
|
*
|
|
43
|
-
* @param rawHour 12
|
|
44
|
-
* @param isPM
|
|
45
|
-
* @returns 24
|
|
43
|
+
* @param rawHour 12-hour format hour (1-12)
|
|
44
|
+
* @param isPM Whether PM
|
|
45
|
+
* @returns 24-hour format hour (0-23)
|
|
46
46
|
*/
|
|
47
47
|
export function convert12To24(rawHour: number, isPM: boolean): number {
|
|
48
48
|
if (rawHour === 12) {
|
|
@@ -51,15 +51,15 @@ export function convert12To24(rawHour: number, isPM: boolean): number {
|
|
|
51
51
|
return isPM ? rawHour + 12 : rawHour;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
//#region
|
|
54
|
+
//#region Regex caching (created once at module load)
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
|
-
*
|
|
57
|
+
* Format pattern regex
|
|
58
58
|
*
|
|
59
|
-
*
|
|
60
|
-
* dtFormat()
|
|
61
|
-
*
|
|
62
|
-
*
|
|
59
|
+
* Order is important:
|
|
60
|
+
* In the dtFormat() function, longer patterns (yyyy, MM, dd, etc.) must be processed first
|
|
61
|
+
* to prevent shorter patterns (y, M, d, etc.) from being partially matched.
|
|
62
|
+
* Example: If "yyyy" is not processed first, "yy" may be matched twice
|
|
63
63
|
*/
|
|
64
64
|
const patterns = {
|
|
65
65
|
yyyy: /yyyy/g,
|
|
@@ -91,49 +91,49 @@ const weekStrings = ["일", "월", "화", "수", "목", "금", "토"];
|
|
|
91
91
|
//#endregion
|
|
92
92
|
|
|
93
93
|
/**
|
|
94
|
-
*
|
|
94
|
+
* Convert date/time to string according to format string
|
|
95
95
|
*
|
|
96
|
-
* @param formatString
|
|
97
|
-
* @param args
|
|
96
|
+
* @param formatString Format string
|
|
97
|
+
* @param args Date/time components
|
|
98
98
|
*
|
|
99
99
|
* @remarks
|
|
100
|
-
*
|
|
100
|
+
* Supports the same format strings as C#:
|
|
101
101
|
*
|
|
102
|
-
* |
|
|
103
|
-
*
|
|
104
|
-
* | yyyy | 4
|
|
105
|
-
* | yy | 2
|
|
106
|
-
* | MM |
|
|
107
|
-
* | M |
|
|
108
|
-
* | ddd |
|
|
109
|
-
* | dd |
|
|
110
|
-
* | d |
|
|
111
|
-
* | tt |
|
|
112
|
-
* | hh |
|
|
113
|
-
* | h | 12
|
|
114
|
-
* | HH |
|
|
115
|
-
* | H | 24
|
|
116
|
-
* | mm |
|
|
117
|
-
* | m |
|
|
118
|
-
* | ss |
|
|
119
|
-
* | s |
|
|
120
|
-
* | fff |
|
|
121
|
-
* | ff |
|
|
122
|
-
* | f |
|
|
123
|
-
* | zzz |
|
|
124
|
-
* | zz |
|
|
125
|
-
* | z |
|
|
102
|
+
* | Format | Description | Example |
|
|
103
|
+
* |--------|-------------|---------|
|
|
104
|
+
* | yyyy | 4-digit year | 2024 |
|
|
105
|
+
* | yy | 2-digit year | 24 |
|
|
106
|
+
* | MM | Zero-padded month | 01~12 |
|
|
107
|
+
* | M | Month | 1~12 |
|
|
108
|
+
* | ddd | Day of week | Sun, Mon, Tue, Wed, Thu, Fri, Sat |
|
|
109
|
+
* | dd | Zero-padded day | 01~31 |
|
|
110
|
+
* | d | Day | 1~31 |
|
|
111
|
+
* | tt | AM/PM | AM, PM |
|
|
112
|
+
* | hh | Zero-padded 12-hour | 01~12 |
|
|
113
|
+
* | h | 12-hour | 1~12 |
|
|
114
|
+
* | HH | Zero-padded 24-hour | 00~23 |
|
|
115
|
+
* | H | 24-hour | 0~23 |
|
|
116
|
+
* | mm | Zero-padded minute | 00~59 |
|
|
117
|
+
* | m | Minute | 0~59 |
|
|
118
|
+
* | ss | Zero-padded second | 00~59 |
|
|
119
|
+
* | s | Second | 0~59 |
|
|
120
|
+
* | fff | Milliseconds (3 digits) | 000~999 |
|
|
121
|
+
* | ff | Milliseconds (2 digits) | 00~99 |
|
|
122
|
+
* | f | Milliseconds (1 digit) | 0~9 |
|
|
123
|
+
* | zzz | Timezone offset (±HH:mm) | +09:00 |
|
|
124
|
+
* | zz | Timezone offset (±HH) | +09 |
|
|
125
|
+
* | z | Timezone offset (±H) | +9 |
|
|
126
126
|
*
|
|
127
127
|
* @example
|
|
128
128
|
* ```typescript
|
|
129
129
|
* formatDate("yyyy-MM-dd", { year: 2024, month: 3, day: 15 });
|
|
130
130
|
* // "2024-03-15"
|
|
131
131
|
*
|
|
132
|
-
* formatDate("yyyy
|
|
133
|
-
* // "2024
|
|
132
|
+
* formatDate("yyyy-M-d (ddd)", { year: 2024, month: 3, day: 15 });
|
|
133
|
+
* // "2024-3-15 (Fri)"
|
|
134
134
|
*
|
|
135
135
|
* formatDate("tt h:mm:ss", { hour: 14, minute: 30, second: 45 });
|
|
136
|
-
* // "
|
|
136
|
+
* // "PM 2:30:45"
|
|
137
137
|
* ```
|
|
138
138
|
*/
|
|
139
139
|
export function formatDate(
|
|
@@ -165,35 +165,35 @@ export function formatDate(
|
|
|
165
165
|
|
|
166
166
|
let result = formatString;
|
|
167
167
|
|
|
168
|
-
//
|
|
168
|
+
// Year
|
|
169
169
|
if (year !== undefined) {
|
|
170
170
|
const yearStr = year.toString();
|
|
171
171
|
result = result.replace(patterns.yyyy, yearStr);
|
|
172
172
|
result = result.replace(patterns.yy, yearStr.substring(2, 4));
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
//
|
|
175
|
+
// Month
|
|
176
176
|
if (month !== undefined) {
|
|
177
177
|
const monthStr = month.toString();
|
|
178
178
|
result = result.replace(patterns.MM, monthStr.padStart(2, "0"));
|
|
179
179
|
result = result.replace(patterns.M, monthStr);
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
//
|
|
182
|
+
// Day of week
|
|
183
183
|
if (week !== undefined) {
|
|
184
184
|
result = result.replace(patterns.ddd, weekStrings[week]);
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
//
|
|
187
|
+
// Day
|
|
188
188
|
if (day !== undefined) {
|
|
189
189
|
const dayStr = day.toString();
|
|
190
190
|
result = result.replace(patterns.dd, dayStr.padStart(2, "0"));
|
|
191
191
|
result = result.replace(patterns.d, dayStr);
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
//
|
|
194
|
+
// Hour
|
|
195
195
|
if (hour !== undefined) {
|
|
196
|
-
result = result.replace(patterns.tt, hour < 12 ? "
|
|
196
|
+
result = result.replace(patterns.tt, hour < 12 ? "AM" : "PM");
|
|
197
197
|
|
|
198
198
|
const hour12 = hour % 12 || 12;
|
|
199
199
|
const hour12Str = hour12.toString();
|
|
@@ -205,21 +205,21 @@ export function formatDate(
|
|
|
205
205
|
result = result.replace(patterns.H, hourStr);
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
-
//
|
|
208
|
+
// Minute
|
|
209
209
|
if (minute !== undefined) {
|
|
210
210
|
const minuteStr = minute.toString();
|
|
211
211
|
result = result.replace(patterns.mm, minuteStr.padStart(2, "0"));
|
|
212
212
|
result = result.replace(patterns.m, minuteStr);
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
//
|
|
215
|
+
// Second
|
|
216
216
|
if (second !== undefined) {
|
|
217
217
|
const secondStr = second.toString();
|
|
218
218
|
result = result.replace(patterns.ss, secondStr.padStart(2, "0"));
|
|
219
219
|
result = result.replace(patterns.s, secondStr);
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
//
|
|
222
|
+
// Millisecond
|
|
223
223
|
if (millisecond !== undefined) {
|
|
224
224
|
const msStr = millisecond.toString().padStart(3, "0");
|
|
225
225
|
result = result.replace(patterns.fff, msStr);
|
|
@@ -227,7 +227,7 @@ export function formatDate(
|
|
|
227
227
|
result = result.replace(patterns.f, msStr.substring(0, 1));
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
-
//
|
|
230
|
+
// Timezone
|
|
231
231
|
if (offsetSign !== undefined && offsetHour !== undefined && offsetMinute !== undefined) {
|
|
232
232
|
result = result.replace(
|
|
233
233
|
patterns.zzz,
|
package/src/utils/error.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Utility to extract message from unknown type error.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Returns the message property if it's an Error instance, otherwise returns String conversion.
|
|
5
5
|
*
|
|
6
|
-
* @param err -
|
|
7
|
-
* @returns
|
|
6
|
+
* @param err - Unknown error from catch block
|
|
7
|
+
* @returns Error message string
|
|
8
8
|
*/
|
|
9
9
|
export function errorMessage(err: unknown): string {
|
|
10
10
|
return err instanceof Error ? err.message : String(err);
|
package/src/utils/json.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* JSON
|
|
3
|
-
*
|
|
2
|
+
* JSON conversion utility
|
|
3
|
+
* JSON serialization/deserialization supporting custom types (DateTime, DateOnly, Time, Uuid, etc.)
|
|
4
4
|
*/
|
|
5
5
|
import { DateTime } from "../types/date-time";
|
|
6
6
|
import { DateOnly } from "../types/date-only";
|
|
@@ -19,19 +19,19 @@ interface TypedObject {
|
|
|
19
19
|
//#region stringify
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
23
|
-
* DateTime, DateOnly, Time, Uuid, Set, Map, Error, Uint8Array
|
|
22
|
+
* Serialize object to JSON string
|
|
23
|
+
* Supports custom types like DateTime, DateOnly, Time, Uuid, Set, Map, Error, Uint8Array, etc.
|
|
24
24
|
*
|
|
25
|
-
* @param obj
|
|
26
|
-
* @param options
|
|
27
|
-
* @param options.space JSON
|
|
28
|
-
* @param options.replacer
|
|
29
|
-
* @param options.redactBytes true
|
|
25
|
+
* @param obj Object to serialize
|
|
26
|
+
* @param options Serialization options
|
|
27
|
+
* @param options.space JSON indentation (number: number of spaces, string: indentation string)
|
|
28
|
+
* @param options.replacer Custom replacer function. Called before default type conversion
|
|
29
|
+
* @param options.redactBytes If true, replace Uint8Array contents with "__hidden__" (for logging). Results serialized with this option cannot restore original Uint8Array via jsonParse()
|
|
30
30
|
*
|
|
31
31
|
* @remarks
|
|
32
|
-
* -
|
|
33
|
-
* -
|
|
34
|
-
* -
|
|
32
|
+
* - Objects with circular references throw TypeError
|
|
33
|
+
* - If object has toJSON method, it is called and the result is used (except for custom types like Date, DateTime)
|
|
34
|
+
* - Safe in Worker environments as global prototypes are not modified
|
|
35
35
|
*/
|
|
36
36
|
export function jsonStringify(
|
|
37
37
|
obj: unknown,
|
|
@@ -41,21 +41,21 @@ export function jsonStringify(
|
|
|
41
41
|
redactBytes?: boolean;
|
|
42
42
|
},
|
|
43
43
|
): string {
|
|
44
|
-
//
|
|
44
|
+
// WeakSet for circular reference detection
|
|
45
45
|
const seen = new WeakSet<object>();
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
|
-
*
|
|
48
|
+
* Recursively traverse object and convert special types to __type__ format
|
|
49
49
|
*
|
|
50
|
-
* JSON.stringify
|
|
51
|
-
* Date
|
|
52
|
-
*
|
|
50
|
+
* JSON.stringify's replacer receives values after toJSON call,
|
|
51
|
+
* so types like Date must be converted beforehand for proper handling.
|
|
52
|
+
* This approach is safe in Worker environments as global prototypes are not modified.
|
|
53
53
|
*
|
|
54
|
-
* @param key
|
|
55
|
-
* @param value
|
|
54
|
+
* @param key Key of current value (root is undefined)
|
|
55
|
+
* @param value Value to convert
|
|
56
56
|
*/
|
|
57
57
|
const convertSpecialTypes = (key: string | undefined, value: unknown): unknown => {
|
|
58
|
-
//
|
|
58
|
+
// Apply custom replacer
|
|
59
59
|
const currValue = options?.replacer !== undefined ? options.replacer(key, value) : value;
|
|
60
60
|
|
|
61
61
|
if (currValue instanceof Date) {
|
|
@@ -108,9 +108,9 @@ export function jsonStringify(
|
|
|
108
108
|
return { __type__: "Uint8Array", data: bytesToHex(currValue) };
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
//
|
|
111
|
+
// Array processing
|
|
112
112
|
if (Array.isArray(currValue)) {
|
|
113
|
-
//
|
|
113
|
+
// Detect circular reference
|
|
114
114
|
if (seen.has(currValue)) {
|
|
115
115
|
throw new TypeError("Converting circular structure to JSON");
|
|
116
116
|
}
|
|
@@ -120,15 +120,15 @@ export function jsonStringify(
|
|
|
120
120
|
return result;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
//
|
|
123
|
+
// Generic object processing
|
|
124
124
|
if (currValue !== null && typeof currValue === "object") {
|
|
125
|
-
//
|
|
125
|
+
// Detect circular reference
|
|
126
126
|
if (seen.has(currValue)) {
|
|
127
127
|
throw new TypeError("Converting circular structure to JSON");
|
|
128
128
|
}
|
|
129
129
|
seen.add(currValue);
|
|
130
130
|
|
|
131
|
-
// toJSON
|
|
131
|
+
// Call toJSON method if present (custom types like Date, DateTime already handled above)
|
|
132
132
|
if (
|
|
133
133
|
"toJSON" in currValue &&
|
|
134
134
|
typeof (currValue as { toJSON: unknown }).toJSON === "function"
|
|
@@ -141,7 +141,7 @@ export function jsonStringify(
|
|
|
141
141
|
const result: Record<string, unknown> = {};
|
|
142
142
|
for (const [k, v] of Object.entries(currValue)) {
|
|
143
143
|
const converted = convertSpecialTypes(k, v);
|
|
144
|
-
// undefined
|
|
144
|
+
// undefined is excluded from JSON
|
|
145
145
|
if (converted !== undefined) {
|
|
146
146
|
result[k] = converted;
|
|
147
147
|
}
|
|
@@ -153,8 +153,8 @@ export function jsonStringify(
|
|
|
153
153
|
return currValue;
|
|
154
154
|
};
|
|
155
155
|
|
|
156
|
-
//
|
|
157
|
-
//
|
|
156
|
+
// Convert entire object first, then call JSON.stringify
|
|
157
|
+
// This approach is safe in concurrent environments as Date.prototype.toJSON is not modified
|
|
158
158
|
const converted = convertSpecialTypes(undefined, obj);
|
|
159
159
|
return JSON.stringify(converted, null, options?.space);
|
|
160
160
|
}
|
|
@@ -164,23 +164,23 @@ export function jsonStringify(
|
|
|
164
164
|
//#region parse
|
|
165
165
|
|
|
166
166
|
/**
|
|
167
|
-
* JSON
|
|
168
|
-
* DateTime, DateOnly, Time, Uuid, Set, Map, Error, Uint8Array
|
|
167
|
+
* Deserialize JSON string to object
|
|
168
|
+
* Restore custom types like DateTime, DateOnly, Time, Uuid, Set, Map, Error, Uint8Array, etc.
|
|
169
169
|
*
|
|
170
170
|
* @remarks
|
|
171
|
-
* `__type__
|
|
172
|
-
*
|
|
173
|
-
*
|
|
171
|
+
* Objects with `__type__` and `data` keys are used for type restoration.
|
|
172
|
+
* Be careful if user data contains objects with `{ __type__: "Date" | "DateTime" | "DateOnly" | "Time" | "Uuid" | "Set" | "Map" | "Error" | "Uint8Array", data: ... }`
|
|
173
|
+
* format as they may be unintentionally converted to types.
|
|
174
174
|
*
|
|
175
|
-
* @security
|
|
176
|
-
*
|
|
175
|
+
* @security In development mode (`__DEV__`), the error message includes the entire JSON string.
|
|
176
|
+
* In production mode, only JSON length is included.
|
|
177
177
|
*/
|
|
178
178
|
export function jsonParse<TResult = unknown>(json: string): TResult {
|
|
179
179
|
try {
|
|
180
180
|
return objNullToUndefined(
|
|
181
181
|
JSON.parse(json, (_key, value: unknown) => {
|
|
182
182
|
if (value != null && typeof value === "object") {
|
|
183
|
-
//
|
|
183
|
+
// Restore types based on __type__ marker
|
|
184
184
|
if ("__type__" in value && "data" in value) {
|
|
185
185
|
const typed = value as TypedObject;
|
|
186
186
|
if (typed.__type__ === "Date" && typeof typed.data === "string") {
|
|
@@ -213,7 +213,7 @@ export function jsonParse<TResult = unknown>(json: string): TResult {
|
|
|
213
213
|
if (typed.__type__ === "Uint8Array" && typeof typed.data === "string") {
|
|
214
214
|
if (typed.data === "__hidden__") {
|
|
215
215
|
throw new SdError(
|
|
216
|
-
"redactBytes
|
|
216
|
+
"Uint8Array serialized with redactBytes option cannot be restored via parse",
|
|
217
217
|
);
|
|
218
218
|
}
|
|
219
219
|
return bytesFromHex(typed.data);
|
|
@@ -226,9 +226,9 @@ export function jsonParse<TResult = unknown>(json: string): TResult {
|
|
|
226
226
|
) as TResult;
|
|
227
227
|
} catch (err) {
|
|
228
228
|
if (env.DEV) {
|
|
229
|
-
throw new SdError(err, "JSON
|
|
229
|
+
throw new SdError(err, "JSON parsing error: \n" + json);
|
|
230
230
|
}
|
|
231
|
-
throw new SdError(err, `JSON
|
|
231
|
+
throw new SdError(err, `JSON parsing error (length: ${json.length})`);
|
|
232
232
|
}
|
|
233
233
|
}
|
|
234
234
|
|