@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.
Files changed (151) hide show
  1. package/README.md +66 -267
  2. package/dist/common.types.d.ts +14 -14
  3. package/dist/errors/argument-error.d.ts +10 -10
  4. package/dist/errors/argument-error.d.ts.map +1 -1
  5. package/dist/errors/argument-error.js +2 -2
  6. package/dist/errors/argument-error.js.map +1 -1
  7. package/dist/errors/not-implemented-error.d.ts +8 -8
  8. package/dist/errors/not-implemented-error.js +2 -2
  9. package/dist/errors/not-implemented-error.js.map +1 -1
  10. package/dist/errors/sd-error.d.ts +10 -10
  11. package/dist/errors/sd-error.d.ts.map +1 -1
  12. package/dist/errors/timeout-error.d.ts +10 -10
  13. package/dist/errors/timeout-error.js +3 -3
  14. package/dist/errors/timeout-error.js.map +1 -1
  15. package/dist/extensions/arr-ext.d.ts +2 -2
  16. package/dist/extensions/arr-ext.helpers.d.ts +8 -8
  17. package/dist/extensions/arr-ext.helpers.js +1 -1
  18. package/dist/extensions/arr-ext.helpers.js.map +1 -1
  19. package/dist/extensions/arr-ext.js +13 -13
  20. package/dist/extensions/arr-ext.js.map +1 -1
  21. package/dist/extensions/arr-ext.types.d.ts +57 -57
  22. package/dist/extensions/arr-ext.types.d.ts.map +1 -1
  23. package/dist/extensions/map-ext.d.ts +16 -16
  24. package/dist/extensions/set-ext.d.ts +11 -11
  25. package/dist/features/debounce-queue.d.ts +17 -15
  26. package/dist/features/debounce-queue.d.ts.map +1 -1
  27. package/dist/features/debounce-queue.js +6 -6
  28. package/dist/features/debounce-queue.js.map +1 -1
  29. package/dist/features/event-emitter.d.ts +20 -20
  30. package/dist/features/event-emitter.js +17 -17
  31. package/dist/features/serial-queue.d.ts +11 -11
  32. package/dist/features/serial-queue.js +5 -5
  33. package/dist/features/serial-queue.js.map +1 -1
  34. package/dist/globals.d.ts +4 -4
  35. package/dist/types/date-only.d.ts +64 -64
  36. package/dist/types/date-only.d.ts.map +1 -1
  37. package/dist/types/date-only.js +63 -63
  38. package/dist/types/date-time.d.ts +37 -37
  39. package/dist/types/date-time.d.ts.map +1 -1
  40. package/dist/types/date-time.js +54 -37
  41. package/dist/types/date-time.js.map +1 -1
  42. package/dist/types/lazy-gc-map.d.ts +26 -26
  43. package/dist/types/lazy-gc-map.d.ts.map +1 -1
  44. package/dist/types/lazy-gc-map.js +26 -26
  45. package/dist/types/lazy-gc-map.js.map +1 -1
  46. package/dist/types/time.d.ts +25 -25
  47. package/dist/types/time.d.ts.map +1 -1
  48. package/dist/types/time.js +25 -25
  49. package/dist/types/time.js.map +1 -1
  50. package/dist/types/uuid.d.ts +11 -11
  51. package/dist/types/uuid.d.ts.map +1 -1
  52. package/dist/types/uuid.js +12 -12
  53. package/dist/types/uuid.js.map +1 -1
  54. package/dist/utils/bytes.d.ts +17 -17
  55. package/dist/utils/bytes.js +4 -4
  56. package/dist/utils/bytes.js.map +1 -1
  57. package/dist/utils/date-format.d.ts +45 -45
  58. package/dist/utils/date-format.js +1 -1
  59. package/dist/utils/date-format.js.map +1 -1
  60. package/dist/utils/error.d.ts +4 -4
  61. package/dist/utils/json.d.ts +17 -17
  62. package/dist/utils/json.js +3 -3
  63. package/dist/utils/json.js.map +1 -1
  64. package/dist/utils/num.d.ts +23 -23
  65. package/dist/utils/obj.d.ts +111 -111
  66. package/dist/utils/obj.d.ts.map +1 -1
  67. package/dist/utils/obj.js +3 -3
  68. package/dist/utils/obj.js.map +1 -1
  69. package/dist/utils/path.d.ts +10 -10
  70. package/dist/utils/primitive.d.ts +5 -5
  71. package/dist/utils/primitive.js +1 -1
  72. package/dist/utils/primitive.js.map +1 -1
  73. package/dist/utils/str.d.ts +46 -46
  74. package/dist/utils/str.d.ts.map +1 -1
  75. package/dist/utils/str.js +5 -5
  76. package/dist/utils/str.js.map +1 -1
  77. package/dist/utils/template-strings.d.ts +26 -26
  78. package/dist/utils/transferable.d.ts +18 -18
  79. package/dist/utils/transferable.js +1 -1
  80. package/dist/utils/transferable.js.map +1 -1
  81. package/dist/utils/wait.d.ts +9 -9
  82. package/dist/utils/xml.d.ts +13 -13
  83. package/dist/utils/xml.d.ts.map +1 -1
  84. package/dist/utils/xml.js +1 -0
  85. package/dist/utils/xml.js.map +1 -1
  86. package/dist/zip/sd-zip.d.ts +22 -22
  87. package/dist/zip/sd-zip.js +16 -16
  88. package/package.json +4 -4
  89. package/src/common.types.ts +17 -17
  90. package/src/errors/argument-error.ts +15 -15
  91. package/src/errors/not-implemented-error.ts +9 -9
  92. package/src/errors/sd-error.ts +12 -12
  93. package/src/errors/timeout-error.ts +12 -12
  94. package/src/extensions/arr-ext.helpers.ts +10 -10
  95. package/src/extensions/arr-ext.ts +57 -57
  96. package/src/extensions/arr-ext.types.ts +59 -59
  97. package/src/extensions/map-ext.ts +16 -16
  98. package/src/extensions/set-ext.ts +11 -11
  99. package/src/features/debounce-queue.ts +21 -19
  100. package/src/features/event-emitter.ts +25 -25
  101. package/src/features/serial-queue.ts +13 -13
  102. package/src/globals.ts +4 -4
  103. package/src/index.ts +1 -1
  104. package/src/types/date-only.ts +83 -83
  105. package/src/types/date-time.ts +64 -44
  106. package/src/types/lazy-gc-map.ts +45 -45
  107. package/src/types/time.ts +34 -34
  108. package/src/types/uuid.ts +17 -17
  109. package/src/utils/bytes.ts +35 -35
  110. package/src/utils/date-format.ts +65 -65
  111. package/src/utils/error.ts +4 -4
  112. package/src/utils/json.ts +39 -39
  113. package/src/utils/num.ts +23 -23
  114. package/src/utils/obj.ts +138 -138
  115. package/src/utils/path.ts +10 -10
  116. package/src/utils/primitive.ts +6 -6
  117. package/src/utils/str.ts +260 -261
  118. package/src/utils/template-strings.ts +29 -29
  119. package/src/utils/transferable.ts +284 -284
  120. package/src/utils/wait.ts +10 -10
  121. package/src/utils/xml.ts +20 -19
  122. package/src/zip/sd-zip.ts +25 -25
  123. package/tests/errors/errors.spec.ts +80 -0
  124. package/tests/extensions/array-extension.spec.ts +796 -0
  125. package/tests/extensions/map-extension.spec.ts +147 -0
  126. package/tests/extensions/set-extension.spec.ts +74 -0
  127. package/tests/types/date-only.spec.ts +638 -0
  128. package/tests/types/date-time.spec.ts +391 -0
  129. package/tests/types/lazy-gc-map.spec.ts +692 -0
  130. package/tests/types/time.spec.ts +559 -0
  131. package/tests/types/uuid.spec.ts +74 -0
  132. package/tests/utils/bytes-utils.spec.ts +230 -0
  133. package/tests/utils/date-format.spec.ts +373 -0
  134. package/tests/utils/debounce-queue.spec.ts +272 -0
  135. package/tests/utils/json.spec.ts +486 -0
  136. package/tests/utils/number.spec.ts +157 -0
  137. package/tests/utils/object.spec.ts +829 -0
  138. package/tests/utils/path.spec.ts +78 -0
  139. package/tests/utils/primitive.spec.ts +43 -0
  140. package/tests/utils/sd-event-emitter.spec.ts +216 -0
  141. package/tests/utils/serial-queue.spec.ts +365 -0
  142. package/tests/utils/string.spec.ts +281 -0
  143. package/tests/utils/template-strings.spec.ts +57 -0
  144. package/tests/utils/transferable.spec.ts +703 -0
  145. package/tests/utils/wait.spec.ts +145 -0
  146. package/tests/utils/xml.spec.ts +146 -0
  147. package/tests/zip/sd-zip.spec.ts +238 -0
  148. package/docs/extensions.md +0 -503
  149. package/docs/features.md +0 -109
  150. package/docs/types.md +0 -486
  151. package/docs/utils.md +0 -780
@@ -6,13 +6,13 @@ import { ArgumentError } from "../errors/argument-error";
6
6
  import type { PrimitiveTypeMap, PrimitiveTypeStr } from "../common.types";
7
7
 
8
8
  /**
9
- * 값에서 PrimitiveTypeStr 추론
9
+ * Infer PrimitiveTypeStr from a value
10
10
  *
11
- * 런타임에서 값의 타입을 검사하여 해당하는 PrimitiveTypeStr을 반환합니다.
11
+ * Checks the type of a value at runtime and returns the corresponding PrimitiveTypeStr.
12
12
  *
13
- * @param value 타입을 추론할
14
- * @returns 값에 해당하는 PrimitiveTypeStr
15
- * @throws ArgumentError 지원하지 않는 타입인 경우
13
+ * @param value The value to infer the type from
14
+ * @returns The PrimitiveTypeStr corresponding to the value
15
+ * @throws ArgumentError If the type is not supported
16
16
  *
17
17
  * @example
18
18
  * getPrimitiveTypeStr("hello") // "string"
@@ -29,5 +29,5 @@ export function getPrimitiveTypeStr(value: PrimitiveTypeMap[PrimitiveTypeStr]):
29
29
  if (value instanceof Time) return "Time";
30
30
  if (value instanceof Uuid) return "Uuid";
31
31
  if (value instanceof Uint8Array) return "Bytes";
32
- throw new ArgumentError(" 없는 값 타입입니다.", { type: typeof value });
32
+ throw new ArgumentError("Unknown value type.", { type: typeof value });
33
33
  }
package/src/utils/str.ts CHANGED
@@ -1,261 +1,260 @@
1
- /**
2
- * 문자열 유틸리티 함수
3
- */
4
-
5
- //#region 한글 조사 처리
6
-
7
- // 한글 조사 매핑 테이블 (모듈 로드 1회만 생성)
8
- const suffixTable = {
9
- 을: { t: "을", f: "를" },
10
- 은: { t: "은", f: "는" },
11
- 이: { t: "이", f: "가" },
12
- 와: { t: "과", f: "와" },
13
- 랑: { t: "이랑", f: "랑" },
14
- 로: { t: "으로", f: "로" },
15
- 라: { t: "이라", f: "라" },
16
- };
17
-
18
- /**
19
- * 한글 조사를 받침에 따라 적절히 반환
20
- * @param text 텍스트
21
- * @param type 조사 타입
22
- * - `"을"`: 을/를
23
- * - `"은"`: 은/는
24
- * - `"이"`: 이/가
25
- * - `"와"`: 과/와
26
- * - `"랑"`: 이랑/랑
27
- * - `"로"`: 으로/로
28
- * - `"라"`: 이라/라
29
- *
30
- * @example
31
- * getSuffix("사과", "을") // "를"
32
- * getSuffix("책", "이") // "이"
33
- */
34
- export function strGetSuffix(
35
- text: string,
36
- type: "을" | "은" | "이" | "와" | "랑" | "로" | "라",
37
- ): string {
38
- const table = suffixTable;
39
-
40
- // 문자열 또는 마지막 글자가 한글이 아닌 경우 받침 없음으로 처리
41
- if (text.length === 0) {
42
- return table[type].f;
43
- }
44
-
45
- const lastCharCode = text.charCodeAt(text.length - 1);
46
-
47
- // 한글 범위 체크 (0xAC00 ~ 0xD7A3)
48
- if (lastCharCode < 0xac00 || lastCharCode > 0xd7a3) {
49
- return table[type].f;
50
- }
51
-
52
- // 받침 존재 여부 종성 인덱스 계산
53
- const jongseongIndex = (lastCharCode - 0xac00) % 28;
54
- const hasLast = jongseongIndex !== 0;
55
-
56
- // "로" 조사는 받침 ㄹ(종성 인덱스 8) 때 "로"로 처리
57
- if (type === "로" && jongseongIndex === 8) {
58
- return table[type].f;
59
- }
60
-
61
- return hasLast ? table[type].t : table[type].f;
62
- }
63
-
64
- //#endregion
65
-
66
- //#region 전각→반각 변환
67
-
68
- // 전각 반각 매핑 테이블 (모듈 로드 시 1회만 생성)
69
- const specialCharMap: Record<string, string> = {
70
- "": "A",
71
- "": "B",
72
- "": "C",
73
- "": "D",
74
- "": "E",
75
- "": "F",
76
- "": "G",
77
- "": "H",
78
- "": "I",
79
- "": "J",
80
- "": "K",
81
- "": "L",
82
- "": "M",
83
- "": "N",
84
- "": "O",
85
- "": "P",
86
- "": "Q",
87
- "": "R",
88
- "": "S",
89
- "": "T",
90
- "": "U",
91
- "": "V",
92
- "": "W",
93
- "": "X",
94
- "": "Y",
95
- "": "Z",
96
- "": "a",
97
- "": "b",
98
- "": "c",
99
- "": "d",
100
- "": "e",
101
- "": "f",
102
- "": "g",
103
- "": "h",
104
- "": "i",
105
- "": "j",
106
- "": "k",
107
- "": "l",
108
- "": "m",
109
- "": "n",
110
- "": "o",
111
- "": "p",
112
- "": "q",
113
- "": "r",
114
- "": "s",
115
- "": "t",
116
- "": "u",
117
- "": "v",
118
- "": "w",
119
- "": "x",
120
- "": "y",
121
- "": "z",
122
- "": "0",
123
- "": "1",
124
- "": "2",
125
- "": "3",
126
- "": "4",
127
- "": "5",
128
- "": "6",
129
- "": "7",
130
- "": "8",
131
- "": "9",
132
- " ": " ",
133
- "": ")",
134
- "(": "(",
135
- };
136
-
137
- // 정규식도 1회만 생성
138
- const specialCharRegex = new RegExp(`[${Object.keys(specialCharMap).join("")}]`, "g");
139
-
140
- /**
141
- * 전각(Full-width) 문자를 반각(Half-width) 문자로 변환
142
- *
143
- * 변환 대상:
144
- * - 전각 영문 대문자 (A-ZA-Z)
145
- * - 전각 영문 소문자 (a-za-z)
146
- * - 전각 숫자 (0-9 0-9)
147
- * - 전각 공백 (  일반 공백)
148
- * - 전각 괄호 (() → ())
149
- *
150
- * @example
151
- * replaceSpecialDefaultChar("A123") // "A123"
152
- * replaceSpecialDefaultChar("(株)") // "(株)"
153
- */
154
- export function strReplaceFullWidth(str: string): string {
155
- return str.replace(specialCharRegex, (char) => specialCharMap[char] ?? char);
156
- }
157
-
158
- //#endregion
159
-
160
- //#region 케이스 변환
161
-
162
- /**
163
- * PascalCase로 변환
164
- * @example "hello-world" → "HelloWorld"
165
- * @example "hello_world" → "HelloWorld"
166
- * @example "hello.world" → "HelloWorld"
167
- */
168
- export function strToPascalCase(str: string): string {
169
- return str
170
- .replace(/[-._][a-z]/g, (m) => m[1].toUpperCase())
171
- .replace(/^[a-z]/, (m) => m.toUpperCase());
172
- }
173
-
174
- /**
175
- * camelCase로 변환
176
- * @example "hello-world" → "helloWorld"
177
- * @example "hello_world" → "helloWorld"
178
- * @example "HelloWorld" → "helloWorld"
179
- */
180
- export function strToCamelCase(str: string): string {
181
- return str
182
- .replace(/[-._][a-z]/g, (m) => m[1].toUpperCase())
183
- .replace(/^[A-Z]/, (m) => m.toLowerCase());
184
- }
185
-
186
- /**
187
- * kebab-case로 변환
188
- *
189
- * @example "HelloWorld" → "hello-world"
190
- * @example "helloWorld" → "hello-world"
191
- * @example "hello_world" → "hello_world" (소문자만 있으면 변환 안됨)
192
- * @example "Hello_World" → "hello-_world" (기존 분리자는 유지됨)
193
- * @example "Hello-World" → "hello--world" (기존 분리자는 유지됨)
194
- * @example "XMLParser" → "x-m-l-parser" (연속된 대문자는 각각 분리됨)
195
- */
196
- export function strToKebabCase(str: string): string {
197
- return toCaseWithSeparator(str, "-");
198
- }
199
-
200
- /**
201
- * snake_case로 변환
202
- *
203
- * @example "HelloWorld" → "hello_world"
204
- * @example "helloWorld" → "hello_world"
205
- * @example "hello-world" → "hello-world" (소문자만 있으면 변환 안됨)
206
- * @example "Hello-World" → "hello_-world" (기존 분리자는 유지됨)
207
- * @example "Hello_World" → "hello__world" (기존 분리자는 유지됨)
208
- * @example "XMLParser" → "x_m_l_parser" (연속된 대문자는 각각 분리됨)
209
- */
210
- export function strToSnakeCase(str: string): string {
211
- return toCaseWithSeparator(str, "_");
212
- }
213
-
214
- function toCaseWithSeparator(str: string, separator: string): string {
215
- return str
216
- .replace(/^[A-Z]/, (m) => m.toLowerCase())
217
- .replace(/[-_]?[A-Z]/g, (m) => separator + m.toLowerCase());
218
- }
219
-
220
- //#endregion
221
-
222
- //#region 기타
223
-
224
- /**
225
- * undefined 또는 빈 문자열 여부 체크 (타입 가드)
226
- *
227
- * @param str 체크할 문자열
228
- * @returns undefined, null, 빈 문자열이면 true
229
- *
230
- * @example
231
- * const name: string | undefined = getValue();
232
- * if (strIsNullOrEmpty(name)) {
233
- * // name: "" | undefined
234
- * console.log("이름이 비어있습니다");
235
- * } else {
236
- * // name: string (비어있지 않은 문자열)
237
- * console.log(`이름: ${name}`);
238
- * }
239
- */
240
- export function strIsNullOrEmpty(str: string | undefined): str is "" | undefined {
241
- return str == null || str === "";
242
- }
243
-
244
- /**
245
- * 문자열 특정 위치에 삽입
246
- *
247
- * @param str 원본 문자열
248
- * @param index 삽입할 위치 (0부터 시작)
249
- * @param insertString 삽입할 문자열
250
- * @returns 삽입된 새 문자열
251
- *
252
- * @example
253
- * strInsert("Hello World", 5, ","); // "Hello, World"
254
- * strInsert("abc", 0, "X"); // "Xabc"
255
- * strInsert("abc", 3, "X"); // "abcX"
256
- */
257
- export function strInsert(str: string, index: number, insertString: string): string {
258
- return str.substring(0, index) + insertString + str.substring(index);
259
- }
260
-
261
- //#endregion
1
+ /**
2
+ * String utility functions
3
+ */
4
+
5
+ //#region Korean particle handling
6
+
7
+ // Korean particle mapping table (created only once when module loads)
8
+ const suffixTable = {
9
+ 을: { t: "을", f: "를" },
10
+ 은: { t: "은", f: "는" },
11
+ 이: { t: "이", f: "가" },
12
+ 와: { t: "과", f: "와" },
13
+ 랑: { t: "이랑", f: "랑" },
14
+ 로: { t: "으로", f: "로" },
15
+ 라: { t: "이라", f: "라" },
16
+ };
17
+
18
+ /**
19
+ * Return the appropriate Korean particle based on the final consonant
20
+ * @param text The text to check
21
+ * @param type The particle type
22
+ * - `"을"`: 을/를 (eul/reul - object particle)
23
+ * - `"은"`: 은/는 (eun/neun - subject particle)
24
+ * - `"이"`: 이/가 (i/ga - subject particle)
25
+ * - `"와"`: 과/와 (gwa/wa - and particle)
26
+ * - `"랑"`: 이랑/랑 (irang/rang - and particle)
27
+ * - `"로"`: 으로/로 (euro/ro - instrumental particle)
28
+ * - `"라"`: 이라/라 (ira/ra - copula particle)
29
+ *
30
+ * @example
31
+ * koreanGetSuffix("Apple", "을") // "를"
32
+ * koreanGetSuffix("책", "이") // "이"
33
+ */
34
+ export function koreanGetSuffix(
35
+ text: string,
36
+ type: "을" | "은" | "이" | "와" | "랑" | "로" | "라",
37
+ ): string {
38
+ const table = suffixTable;
39
+
40
+ if (text.length === 0) {
41
+ return table[type].f;
42
+ }
43
+
44
+ const lastCharCode = text.charCodeAt(text.length - 1);
45
+
46
+ // Hangul range check (0xAC00 ~ 0xD7A3)
47
+ if (lastCharCode < 0xac00 || lastCharCode > 0xd7a3) {
48
+ return table[type].f;
49
+ }
50
+
51
+ // Determine if final consonant (jongseong) exists
52
+ const jongseongIndex = (lastCharCode - 0xac00) % 28;
53
+ const hasLast = jongseongIndex !== 0;
54
+
55
+ // Special handling for "로" particle: when final consonant is ㄹ (jongseong index 8), use "로"
56
+ if (type === "로" && jongseongIndex === 8) {
57
+ return table[type].f;
58
+ }
59
+
60
+ return hasLast ? table[type].t : table[type].f;
61
+ }
62
+
63
+ //#endregion
64
+
65
+ //#region Full-width to half-width conversion
66
+
67
+ // Full-width to half-width mapping table (created only once when module loads)
68
+ const fullWidthCharMap: Record<string, string> = {
69
+ "A": "A",
70
+ "": "B",
71
+ "": "C",
72
+ "": "D",
73
+ "": "E",
74
+ "": "F",
75
+ "": "G",
76
+ "": "H",
77
+ "": "I",
78
+ "": "J",
79
+ "": "K",
80
+ "": "L",
81
+ "": "M",
82
+ "": "N",
83
+ "": "O",
84
+ "": "P",
85
+ "": "Q",
86
+ "": "R",
87
+ "": "S",
88
+ "": "T",
89
+ "": "U",
90
+ "": "V",
91
+ "": "W",
92
+ "": "X",
93
+ "": "Y",
94
+ "": "Z",
95
+ "": "a",
96
+ "": "b",
97
+ "": "c",
98
+ "": "d",
99
+ "": "e",
100
+ "": "f",
101
+ "": "g",
102
+ "": "h",
103
+ "": "i",
104
+ "": "j",
105
+ "": "k",
106
+ "": "l",
107
+ "": "m",
108
+ "": "n",
109
+ "": "o",
110
+ "": "p",
111
+ "": "q",
112
+ "": "r",
113
+ "": "s",
114
+ "": "t",
115
+ "": "u",
116
+ "": "v",
117
+ "": "w",
118
+ "": "x",
119
+ "": "y",
120
+ "": "z",
121
+ "": "0",
122
+ "": "1",
123
+ "": "2",
124
+ "": "3",
125
+ "": "4",
126
+ "": "5",
127
+ "": "6",
128
+ "": "7",
129
+ "": "8",
130
+ "": "9",
131
+ " ": " ",
132
+ "": ")",
133
+ "": "(",
134
+ };
135
+
136
+ // Regex also created only once
137
+ const fullWidthCharRegex = new RegExp(`[${Object.keys(fullWidthCharMap).join("")}]`, "g");
138
+
139
+ /**
140
+ * Convert full-width characters to half-width characters
141
+ *
142
+ * Conversion targets:
143
+ * - Full-width uppercase letters (A-Z → A-Z)
144
+ * - Full-width lowercase letters (a-za-z)
145
+ * - Full-width digits (0-90-9)
146
+ * - Full-width space (  regular space)
147
+ * - Full-width parentheses (() ())
148
+ *
149
+ * @example
150
+ * strReplaceFullWidth("A123") // "A123"
151
+ * strReplaceFullWidth("(株)") // "(株)"
152
+ */
153
+ export function strReplaceFullWidth(str: string): string {
154
+ return str.replace(fullWidthCharRegex, (char) => fullWidthCharMap[char] ?? char);
155
+ }
156
+
157
+ //#endregion
158
+
159
+ //#region Case conversion
160
+
161
+ /**
162
+ * Convert to PascalCase
163
+ * @example "hello-world" → "HelloWorld"
164
+ * @example "hello_world" → "HelloWorld"
165
+ * @example "hello.world" → "HelloWorld"
166
+ */
167
+ export function strToPascalCase(str: string): string {
168
+ return str
169
+ .replace(/[-._][a-z]/g, (m) => m[1].toUpperCase())
170
+ .replace(/^[a-z]/, (m) => m.toUpperCase());
171
+ }
172
+
173
+ /**
174
+ * Convert to camelCase
175
+ * @example "hello-world" → "helloWorld"
176
+ * @example "hello_world" → "helloWorld"
177
+ * @example "HelloWorld" → "helloWorld"
178
+ */
179
+ export function strToCamelCase(str: string): string {
180
+ return str
181
+ .replace(/[-._][a-z]/g, (m) => m[1].toUpperCase())
182
+ .replace(/^[A-Z]/, (m) => m.toLowerCase());
183
+ }
184
+
185
+ /**
186
+ * Convert to kebab-case
187
+ *
188
+ * @example "HelloWorld" → "hello-world"
189
+ * @example "helloWorld" → "hello-world"
190
+ * @example "hello_world" → "hello_world" (no conversion if only lowercase)
191
+ * @example "Hello_World" → "hello-_world" (existing separators are preserved)
192
+ * @example "Hello-World" → "hello--world" (existing separators are preserved)
193
+ * @example "XMLParser" → "x-m-l-parser" (consecutive uppercase letters are separated)
194
+ */
195
+ export function strToKebabCase(str: string): string {
196
+ return toCaseWithSeparator(str, "-");
197
+ }
198
+
199
+ /**
200
+ * Convert to snake_case
201
+ *
202
+ * @example "HelloWorld" → "hello_world"
203
+ * @example "helloWorld" → "hello_world"
204
+ * @example "hello-world" → "hello-world" (no conversion if only lowercase)
205
+ * @example "Hello-World" → "hello_-world" (existing separators are preserved)
206
+ * @example "Hello_World" → "hello__world" (existing separators are preserved)
207
+ * @example "XMLParser" → "x_m_l_parser" (consecutive uppercase letters are separated)
208
+ */
209
+ export function strToSnakeCase(str: string): string {
210
+ return toCaseWithSeparator(str, "_");
211
+ }
212
+
213
+ function toCaseWithSeparator(str: string, separator: string): string {
214
+ return str
215
+ .replace(/^[A-Z]/, (m) => m.toLowerCase())
216
+ .replace(/[-_]?[A-Z]/g, (m) => separator + m.toLowerCase());
217
+ }
218
+
219
+ //#endregion
220
+
221
+ //#region Other
222
+
223
+ /**
224
+ * Check if string is undefined or empty (type guard)
225
+ *
226
+ * @param str The string to check
227
+ * @returns true if undefined, null, or empty string
228
+ *
229
+ * @example
230
+ * const name: string | undefined = getValue();
231
+ * if (strIsNullOrEmpty(name)) {
232
+ * // name: "" | undefined
233
+ * console.log("Name is empty");
234
+ * } else {
235
+ * // name: string (non-empty string)
236
+ * console.log(`Name: ${name}`);
237
+ * }
238
+ */
239
+ export function strIsNullOrEmpty(str: string | undefined): str is "" | undefined {
240
+ return str == null || str === "";
241
+ }
242
+
243
+ /**
244
+ * Insert a string at a specific position
245
+ *
246
+ * @param str The original string
247
+ * @param index The position to insert at (0-based)
248
+ * @param insertString The string to insert
249
+ * @returns A new string with the insertion applied
250
+ *
251
+ * @example
252
+ * strInsert("Hello World", 5, ","); // "Hello, World"
253
+ * strInsert("abc", 0, "X"); // "Xabc"
254
+ * strInsert("abc", 3, "X"); // "abcX"
255
+ */
256
+ export function strInsert(str: string, index: number, insertString: string): string {
257
+ return str.substring(0, index) + insertString + str.substring(index);
258
+ }
259
+
260
+ //#endregion