@simplysm/core-common 13.0.0-beta.2 → 13.0.0-beta.21

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 (84) hide show
  1. package/dist/common.types.js +4 -4
  2. package/dist/errors/argument-error.js +1 -1
  3. package/dist/errors/not-implemented-error.js +1 -1
  4. package/dist/errors/timeout-error.js +1 -1
  5. package/dist/extensions/arr-ext.helpers.js +4 -4
  6. package/dist/extensions/arr-ext.js +9 -9
  7. package/dist/features/debounce-queue.js +2 -2
  8. package/dist/features/serial-queue.js +3 -3
  9. package/dist/index.js +30 -30
  10. package/dist/types/date-only.js +2 -2
  11. package/dist/types/date-time.js +2 -2
  12. package/dist/types/time.js +2 -2
  13. package/dist/types/uuid.js +1 -1
  14. package/dist/utils/bytes.js +1 -1
  15. package/dist/utils/json.js +8 -8
  16. package/dist/utils/obj.js +5 -5
  17. package/dist/utils/primitive.js +5 -5
  18. package/dist/utils/transferable.js +4 -4
  19. package/dist/utils/wait.js +1 -1
  20. package/package.json +7 -4
  21. package/.cache/typecheck-browser.tsbuildinfo +0 -1
  22. package/.cache/typecheck-node.tsbuildinfo +0 -1
  23. package/.cache/typecheck-tests-browser.tsbuildinfo +0 -1
  24. package/.cache/typecheck-tests-node.tsbuildinfo +0 -1
  25. package/src/common.types.ts +0 -91
  26. package/src/env.ts +0 -11
  27. package/src/errors/argument-error.ts +0 -40
  28. package/src/errors/not-implemented-error.ts +0 -32
  29. package/src/errors/sd-error.ts +0 -53
  30. package/src/errors/timeout-error.ts +0 -36
  31. package/src/extensions/arr-ext.helpers.ts +0 -53
  32. package/src/extensions/arr-ext.ts +0 -777
  33. package/src/extensions/arr-ext.types.ts +0 -258
  34. package/src/extensions/map-ext.ts +0 -86
  35. package/src/extensions/set-ext.ts +0 -68
  36. package/src/features/debounce-queue.ts +0 -116
  37. package/src/features/event-emitter.ts +0 -112
  38. package/src/features/serial-queue.ts +0 -94
  39. package/src/globals.ts +0 -12
  40. package/src/index.ts +0 -55
  41. package/src/types/date-only.ts +0 -329
  42. package/src/types/date-time.ts +0 -294
  43. package/src/types/lazy-gc-map.ts +0 -244
  44. package/src/types/time.ts +0 -210
  45. package/src/types/uuid.ts +0 -113
  46. package/src/utils/bytes.ts +0 -160
  47. package/src/utils/date-format.ts +0 -239
  48. package/src/utils/json.ts +0 -230
  49. package/src/utils/num.ts +0 -97
  50. package/src/utils/obj.ts +0 -956
  51. package/src/utils/path.ts +0 -40
  52. package/src/utils/primitive.ts +0 -33
  53. package/src/utils/str.ts +0 -252
  54. package/src/utils/template-strings.ts +0 -132
  55. package/src/utils/transferable.ts +0 -269
  56. package/src/utils/wait.ts +0 -40
  57. package/src/utils/xml.ts +0 -105
  58. package/src/zip/sd-zip.ts +0 -218
  59. package/tests/errors/errors.spec.ts +0 -196
  60. package/tests/extensions/array-extension.spec.ts +0 -790
  61. package/tests/extensions/map-extension.spec.ts +0 -147
  62. package/tests/extensions/set-extension.spec.ts +0 -74
  63. package/tests/types/date-only.spec.ts +0 -636
  64. package/tests/types/date-time.spec.ts +0 -391
  65. package/tests/types/lazy-gc-map.spec.ts +0 -692
  66. package/tests/types/time.spec.ts +0 -559
  67. package/tests/types/types.spec.ts +0 -55
  68. package/tests/types/uuid.spec.ts +0 -91
  69. package/tests/utils/bytes-utils.spec.ts +0 -230
  70. package/tests/utils/date-format.spec.ts +0 -371
  71. package/tests/utils/debounce-queue.spec.ts +0 -272
  72. package/tests/utils/json.spec.ts +0 -475
  73. package/tests/utils/number.spec.ts +0 -184
  74. package/tests/utils/object.spec.ts +0 -827
  75. package/tests/utils/path.spec.ts +0 -78
  76. package/tests/utils/primitive.spec.ts +0 -55
  77. package/tests/utils/sd-event-emitter.spec.ts +0 -216
  78. package/tests/utils/serial-queue.spec.ts +0 -365
  79. package/tests/utils/string.spec.ts +0 -294
  80. package/tests/utils/template-strings.spec.ts +0 -96
  81. package/tests/utils/transferable.spec.ts +0 -698
  82. package/tests/utils/wait.spec.ts +0 -145
  83. package/tests/utils/xml.spec.ts +0 -146
  84. package/tests/zip/sd-zip.spec.ts +0 -234
@@ -1,239 +0,0 @@
1
- /**
2
- * 월 설정 시 연도/월/일 정규화 결과
3
- */
4
- export interface DtNormalizedMonth {
5
- year: number;
6
- month: number;
7
- day: number;
8
- }
9
-
10
- /**
11
- * 월 설정 시 연도/월/일을 정규화
12
- * - 월이 1-12 범위를 벗어나면 연도를 조정
13
- * - 대상 월의 일수보다 현재 일자가 크면 해당 월의 마지막 날로 조정
14
- *
15
- * @param year 기준 연도
16
- * @param month 설정할 월 (1-12 범위 외의 값도 허용)
17
- * @param day 기준 일자
18
- * @returns 정규화된 연도, 월, 일
19
- *
20
- * @example
21
- * normalizeMonth(2025, 13, 15) // { year: 2026, month: 1, day: 15 }
22
- * normalizeMonth(2025, 2, 31) // { year: 2025, month: 2, day: 28 }
23
- */
24
- export function normalizeMonth(year: number, month: number, day: number): DtNormalizedMonth {
25
- // 월 오버플로우/언더플로우 정규화
26
- // month가 1-12 범위를 벗어나면 연도를 조정
27
- const normalizedYear = year + Math.floor((month - 1) / 12);
28
- // JavaScript % 연산자는 음수에서 음수를 반환하므로 (% 12 + 12) % 12 패턴으로 0-11 범위를 보장 후 1-12로 변환
29
- const normalizedMonth = ((((month - 1) % 12) + 12) % 12) + 1;
30
-
31
- // 대상 월의 마지막 날 구하기
32
- const lastDay = new Date(normalizedYear, normalizedMonth, 0).getDate();
33
- const normalizedDay = Math.min(day, lastDay);
34
-
35
- return { year: normalizedYear, month: normalizedMonth, day: normalizedDay };
36
- }
37
-
38
- /**
39
- * 12시간제를 24시간제로 변환
40
- * - 오전 12시 = 0시, 오후 12시 = 12시
41
- * - 오전 1-11시 = 1-11시, 오후 1-11시 = 13-23시
42
- *
43
- * @param rawHour 12시간제 시 (1-12)
44
- * @param isPM 오후 여부
45
- * @returns 24시간제 시 (0-23)
46
- */
47
- export function convert12To24(rawHour: number, isPM: boolean): number {
48
- if (rawHour === 12) {
49
- return isPM ? 12 : 0;
50
- }
51
- return isPM ? rawHour + 12 : rawHour;
52
- }
53
-
54
- //#region 정규식 캐싱 (모듈 로드 시 1회만 생성)
55
-
56
- /**
57
- * 포맷 패턴 정규식
58
- *
59
- * 순서 중요:
60
- * dtFormat() 함수에서 긴 패턴(yyyy, MM, dd 등)을 먼저 처리해야
61
- * 짧은 패턴(y, M, d 등)이 부분 매칭되는 것을 방지합니다.
62
- * 예: "yyyy"를 먼저 처리하지 않으면 "yy"가 두 번 매칭될 수 있음
63
- */
64
- const patterns = {
65
- yyyy: /yyyy/g,
66
- yy: /yy/g,
67
- MM: /MM/g,
68
- M: /M/g,
69
- ddd: /ddd/g,
70
- dd: /dd/g,
71
- d: /d/g,
72
- tt: /tt/g,
73
- hh: /hh/g,
74
- h: /h/g,
75
- HH: /HH/g,
76
- H: /H/g,
77
- mm: /mm/g,
78
- m: /m/g,
79
- ss: /ss/g,
80
- s: /s/g,
81
- fff: /fff/g,
82
- ff: /ff/g,
83
- f: /f/g,
84
- zzz: /zzz/g,
85
- zz: /zz/g,
86
- z: /z/g,
87
- };
88
-
89
- const weekStrings = ["일", "월", "화", "수", "목", "금", "토"];
90
-
91
- //#endregion
92
-
93
- /**
94
- * 포맷 문자열에 따라 날짜/시간을 문자열로 변환한다
95
- *
96
- * @param formatString 포맷 문자열
97
- * @param args 날짜/시간 구성 요소
98
- *
99
- * @remarks
100
- * C#과 동일한 포맷 문자열을 지원한다:
101
- *
102
- * | 포맷 | 설명 | 예시 |
103
- * |------|------|------|
104
- * | yyyy | 4자리 연도 | 2024 |
105
- * | yy | 2자리 연도 | 24 |
106
- * | MM | 0으로 패딩된 월 | 01~12 |
107
- * | M | 월 | 1~12 |
108
- * | ddd | 요일 (한글) | 일, 월, 화, 수, 목, 금, 토 |
109
- * | dd | 0으로 패딩된 일 | 01~31 |
110
- * | d | 일 | 1~31 |
111
- * | tt | 오전/오후 | 오전, 오후 |
112
- * | hh | 0으로 패딩된 12시간 | 01~12 |
113
- * | h | 12시간 | 1~12 |
114
- * | HH | 0으로 패딩된 24시간 | 00~23 |
115
- * | H | 24시간 | 0~23 |
116
- * | mm | 0으로 패딩된 분 | 00~59 |
117
- * | m | 분 | 0~59 |
118
- * | ss | 0으로 패딩된 초 | 00~59 |
119
- * | s | 초 | 0~59 |
120
- * | fff | 밀리초 (3자리) | 000~999 |
121
- * | ff | 밀리초 (2자리) | 00~99 |
122
- * | f | 밀리초 (1자리) | 0~9 |
123
- * | zzz | 타임존 오프셋 (±HH:mm) | +09:00 |
124
- * | zz | 타임존 오프셋 (±HH) | +09 |
125
- * | z | 타임존 오프셋 (±H) | +9 |
126
- *
127
- * @example
128
- * ```typescript
129
- * formatDate("yyyy-MM-dd", { year: 2024, month: 3, day: 15 });
130
- * // "2024-03-15"
131
- *
132
- * formatDate("yyyy년 M월 d일 (ddd)", { year: 2024, month: 3, day: 15 });
133
- * // "2024년 3월 15일 (금)"
134
- *
135
- * formatDate("tt h:mm:ss", { hour: 14, minute: 30, second: 45 });
136
- * // "오후 2:30:45"
137
- * ```
138
- */
139
- export function formatDate(
140
- formatString: string,
141
- args: {
142
- year?: number;
143
- month?: number;
144
- day?: number;
145
- hour?: number;
146
- minute?: number;
147
- second?: number;
148
- millisecond?: number;
149
- timezoneOffsetMinutes?: number;
150
- },
151
- ): string {
152
- const { year, month, day, hour, minute, second, millisecond, timezoneOffsetMinutes } = args;
153
-
154
- const absOffsetMinutes = timezoneOffsetMinutes !== undefined ? Math.abs(timezoneOffsetMinutes) : undefined;
155
- const offsetHour = absOffsetMinutes !== undefined ? Math.floor(absOffsetMinutes / 60) : undefined;
156
- const offsetMinute = absOffsetMinutes !== undefined ? absOffsetMinutes % 60 : undefined;
157
- const offsetSign = timezoneOffsetMinutes !== undefined ? (timezoneOffsetMinutes >= 0 ? "+" : "-") : undefined;
158
-
159
- const week =
160
- year !== undefined && month !== undefined && day !== undefined
161
- ? new Date(year, month - 1, day).getDay()
162
- : undefined;
163
-
164
- let result = formatString;
165
-
166
- // 연도
167
- if (year !== undefined) {
168
- const yearStr = year.toString();
169
- result = result.replace(patterns.yyyy, yearStr);
170
- result = result.replace(patterns.yy, yearStr.substring(2, 4));
171
- }
172
-
173
- // 월
174
- if (month !== undefined) {
175
- const monthStr = month.toString();
176
- result = result.replace(patterns.MM, monthStr.padStart(2, "0"));
177
- result = result.replace(patterns.M, monthStr);
178
- }
179
-
180
- // 요일
181
- if (week !== undefined) {
182
- result = result.replace(patterns.ddd, weekStrings[week]);
183
- }
184
-
185
- // 일
186
- if (day !== undefined) {
187
- const dayStr = day.toString();
188
- result = result.replace(patterns.dd, dayStr.padStart(2, "0"));
189
- result = result.replace(patterns.d, dayStr);
190
- }
191
-
192
- // 시간
193
- if (hour !== undefined) {
194
- result = result.replace(patterns.tt, hour < 12 ? "오전" : "오후");
195
-
196
- const hour12 = hour % 12 || 12;
197
- const hour12Str = hour12.toString();
198
- result = result.replace(patterns.hh, hour12Str.padStart(2, "0"));
199
- result = result.replace(patterns.h, hour12Str);
200
-
201
- const hourStr = hour.toString();
202
- result = result.replace(patterns.HH, hourStr.padStart(2, "0"));
203
- result = result.replace(patterns.H, hourStr);
204
- }
205
-
206
- // 분
207
- if (minute !== undefined) {
208
- const minuteStr = minute.toString();
209
- result = result.replace(patterns.mm, minuteStr.padStart(2, "0"));
210
- result = result.replace(patterns.m, minuteStr);
211
- }
212
-
213
- // 초
214
- if (second !== undefined) {
215
- const secondStr = second.toString();
216
- result = result.replace(patterns.ss, secondStr.padStart(2, "0"));
217
- result = result.replace(patterns.s, secondStr);
218
- }
219
-
220
- // 밀리초
221
- if (millisecond !== undefined) {
222
- const msStr = millisecond.toString().padStart(3, "0");
223
- result = result.replace(patterns.fff, msStr);
224
- result = result.replace(patterns.ff, msStr.substring(0, 2));
225
- result = result.replace(patterns.f, msStr.substring(0, 1));
226
- }
227
-
228
- // 타임존
229
- if (offsetSign !== undefined && offsetHour !== undefined && offsetMinute !== undefined) {
230
- result = result.replace(
231
- patterns.zzz,
232
- `${offsetSign}${offsetHour.toString().padStart(2, "0")}:${offsetMinute.toString().padStart(2, "0")}`,
233
- );
234
- result = result.replace(patterns.zz, `${offsetSign}${offsetHour.toString().padStart(2, "0")}`);
235
- result = result.replace(patterns.z, `${offsetSign}${offsetHour}`);
236
- }
237
-
238
- return result;
239
- }
package/src/utils/json.ts DELETED
@@ -1,230 +0,0 @@
1
- /**
2
- * JSON 변환 유틸리티
3
- * 커스텀 타입(DateTime, DateOnly, Time, Uuid 등)을 지원하는 JSON 직렬화/역직렬화
4
- */
5
- import { DateTime } from "../types/date-time";
6
- import { DateOnly } from "../types/date-only";
7
- import { Time } from "../types/time";
8
- import { Uuid } from "../types/uuid";
9
- import { objNullToUndefined } from "./obj";
10
- import { SdError } from "../errors/sd-error";
11
- import { bytesToHex, bytesFromHex } from "./bytes";
12
- import { env } from "../env";
13
-
14
- interface TypedObject {
15
- __type__: string;
16
- data: unknown;
17
- }
18
-
19
- //#region stringify
20
-
21
- /**
22
- * 객체를 JSON 문자열로 직렬화
23
- * DateTime, DateOnly, Time, Uuid, Set, Map, Error, Uint8Array 등 커스텀 타입 지원
24
- *
25
- * @param obj 직렬화할 객체
26
- * @param options 직렬화 옵션
27
- * @param options.space JSON 들여쓰기 (숫자: 공백 수, 문자열: 들여쓰기 문자열)
28
- * @param options.replacer 커스텀 replacer 함수. 기본 타입 변환 전에 호출됨
29
- * @param options.redactBytes true 시 Uint8Array 내용을 "__hidden__"으로 대체 (로깅용). 이 옵션으로 직렬화한 결과는 jsonParse()로 원본 Uint8Array를 복원할 수 없음
30
- *
31
- * @remarks
32
- * - 순환 참조가 있는 객체는 TypeError를 던짐
33
- * - 객체의 toJSON 메서드가 있으면 호출하여 결과를 사용함 (Date, DateTime 등 커스텀 타입 제외)
34
- * - 전역 프로토타입을 수정하지 않아 Worker 환경에서도 안전함
35
- */
36
- export function jsonStringify(
37
- obj: unknown,
38
- options?: {
39
- space?: string | number;
40
- replacer?: (key: string | undefined, value: unknown) => unknown;
41
- redactBytes?: boolean;
42
- },
43
- ): string {
44
- // 순환 참조 감지를 위한 WeakSet
45
- const seen = new WeakSet<object>();
46
-
47
- /**
48
- * 재귀적으로 객체를 순회하며 특수 타입을 __type__ 형식으로 변환
49
- *
50
- * JSON.stringify의 replacer는 toJSON 호출 후의 값을 받으므로,
51
- * Date 등의 타입을 올바르게 처리하려면 미리 변환해야 함.
52
- * 이 방식은 전역 프로토타입을 수정하지 않아 Worker 환경에서도 안전함.
53
- *
54
- * @param key 현재 값의 키 (루트는 undefined)
55
- * @param value 변환할 값
56
- */
57
- const convertSpecialTypes = (key: string | undefined, value: unknown): unknown => {
58
- // 커스텀 replacer 적용
59
- const currValue = options?.replacer !== undefined ? options.replacer(key, value) : value;
60
-
61
- if (currValue instanceof Date) {
62
- return { __type__: "Date", data: currValue.toISOString() };
63
- }
64
- if (currValue instanceof DateTime) {
65
- return { __type__: "DateTime", data: currValue.toString() };
66
- }
67
- if (currValue instanceof DateOnly) {
68
- return { __type__: "DateOnly", data: currValue.toString() };
69
- }
70
- if (currValue instanceof Time) {
71
- return { __type__: "Time", data: currValue.toString() };
72
- }
73
- if (currValue instanceof Uuid) {
74
- return { __type__: "Uuid", data: currValue.toString() };
75
- }
76
- if (currValue instanceof Set) {
77
- return {
78
- __type__: "Set",
79
- data: Array.from(currValue).map((item, i) => convertSpecialTypes(String(i), item)),
80
- };
81
- }
82
- if (currValue instanceof Map) {
83
- return {
84
- __type__: "Map",
85
- data: Array.from(currValue.entries()).map(([k, v], i) => [
86
- convertSpecialTypes(String(i), k),
87
- convertSpecialTypes(String(i), v),
88
- ]),
89
- };
90
- }
91
- if (currValue instanceof Error) {
92
- return {
93
- __type__: "Error",
94
- data: {
95
- name: currValue.name,
96
- message: currValue.message,
97
- stack: currValue.stack,
98
- ...("code" in currValue ? { code: currValue.code } : {}),
99
- ...("detail" in currValue ? { detail: currValue.detail } : {}),
100
- ...("cause" in currValue ? { cause: convertSpecialTypes("cause", currValue.cause) } : {}),
101
- },
102
- };
103
- }
104
- if (currValue instanceof Uint8Array) {
105
- if (options?.redactBytes === true) {
106
- return { __type__: "Uint8Array", data: "__hidden__" };
107
- }
108
- return { __type__: "Uint8Array", data: bytesToHex(currValue) };
109
- }
110
-
111
- // 배열 처리
112
- if (Array.isArray(currValue)) {
113
- // 순환 참조 감지
114
- if (seen.has(currValue)) {
115
- throw new TypeError("Converting circular structure to JSON");
116
- }
117
- seen.add(currValue);
118
- const result = currValue.map((item, i) => convertSpecialTypes(String(i), item));
119
- seen.delete(currValue);
120
- return result;
121
- }
122
-
123
- // 일반 객체 처리
124
- if (currValue !== null && typeof currValue === "object") {
125
- // 순환 참조 감지
126
- if (seen.has(currValue)) {
127
- throw new TypeError("Converting circular structure to JSON");
128
- }
129
- seen.add(currValue);
130
-
131
- // toJSON 메서드가 있으면 호출 (Date, DateTime 등 커스텀 타입은 이미 위에서 처리됨)
132
- if ("toJSON" in currValue && typeof (currValue as { toJSON: unknown }).toJSON === "function") {
133
- const toJsonResult = (currValue as { toJSON: (key?: string) => unknown }).toJSON(key);
134
- seen.delete(currValue);
135
- return convertSpecialTypes(key, toJsonResult);
136
- }
137
-
138
- const result: Record<string, unknown> = {};
139
- for (const [k, v] of Object.entries(currValue)) {
140
- const converted = convertSpecialTypes(k, v);
141
- // undefined는 JSON에서 제외됨
142
- if (converted !== undefined) {
143
- result[k] = converted;
144
- }
145
- }
146
- seen.delete(currValue);
147
- return result;
148
- }
149
-
150
- return currValue;
151
- };
152
-
153
- // 전체 객체를 미리 변환 후 JSON.stringify 호출
154
- // 이 방식은 Date.prototype.toJSON을 수정하지 않아 동시성 환경에서 안전함
155
- const converted = convertSpecialTypes(undefined, obj);
156
- return JSON.stringify(converted, null, options?.space);
157
- }
158
-
159
- //#endregion
160
-
161
- //#region parse
162
-
163
- /**
164
- * JSON 문자열을 객체로 역직렬화
165
- * DateTime, DateOnly, Time, Uuid, Set, Map, Error, Uint8Array 등 커스텀 타입 복원
166
- *
167
- * @remarks
168
- * `__type__`과 `data` 키를 가진 객체는 타입 복원에 사용된다.
169
- * 사용자 데이터에 `{ __type__: "Date" | "DateTime" | "DateOnly" | "Time" | "Uuid" | "Set" | "Map" | "Error" | "Uint8Array", data: ... }`
170
- * 형태가 있으면 의도치 않게 타입 변환될 수 있으므로 주의한다.
171
- *
172
- * @security 개발 모드(`__DEV__`)에서만 에러 메시지에 JSON 문자열 전체가 포함된다.
173
- * 프로덕션 모드에서는 JSON 길이만 포함된다.
174
- */
175
- export function jsonParse<T = unknown>(json: string): T {
176
- try {
177
- return objNullToUndefined(
178
- JSON.parse(json, (_key, value: unknown) => {
179
- if (value != null && typeof value === "object") {
180
- // __type__ 기반 타입 복원
181
- if ("__type__" in value && "data" in value) {
182
- const typed = value as TypedObject;
183
- if (typed.__type__ === "Date" && typeof typed.data === "string") {
184
- return new Date(Date.parse(typed.data));
185
- }
186
- if (typed.__type__ === "DateTime" && typeof typed.data === "string") {
187
- return DateTime.parse(typed.data);
188
- }
189
- if (typed.__type__ === "DateOnly" && typeof typed.data === "string") {
190
- return DateOnly.parse(typed.data);
191
- }
192
- if (typed.__type__ === "Time" && typeof typed.data === "string") {
193
- return Time.parse(typed.data);
194
- }
195
- if (typed.__type__ === "Uuid" && typeof typed.data === "string") {
196
- return new Uuid(typed.data);
197
- }
198
- if (typed.__type__ === "Set" && Array.isArray(typed.data)) {
199
- return new Set(typed.data);
200
- }
201
- if (typed.__type__ === "Map" && Array.isArray(typed.data)) {
202
- return new Map(typed.data as [unknown, unknown][]);
203
- }
204
- if (typed.__type__ === "Error" && typeof typed.data === "object") {
205
- const errorData = typed.data as Record<string, unknown>;
206
- const error = new Error(errorData["message"] as string);
207
- Object.assign(error, errorData);
208
- return error;
209
- }
210
- if (typed.__type__ === "Uint8Array" && typeof typed.data === "string") {
211
- if (typed.data === "__hidden__") {
212
- throw new SdError("redactBytes 옵션으로 직렬화된 Uint8Array는 parse로 복원할 수 없습니다");
213
- }
214
- return bytesFromHex(typed.data);
215
- }
216
- }
217
- }
218
-
219
- return value;
220
- }),
221
- ) as T;
222
- } catch (err) {
223
- if (env.DEV) {
224
- throw new SdError(err, "JSON 파싱 에러: \n" + json);
225
- }
226
- throw new SdError(err, `JSON 파싱 에러 (length: ${json.length})`);
227
- }
228
- }
229
-
230
- //#endregion
package/src/utils/num.ts DELETED
@@ -1,97 +0,0 @@
1
- /**
2
- * 숫자 유틸리티 함수
3
- */
4
-
5
- //#region numParseInt / numParseFloat / numParseRoundedInt
6
-
7
- /**
8
- * 문자열을 정수로 파싱
9
- * 숫자가 아닌 문자(0-9, -, . 제외)는 제거 후 파싱
10
- *
11
- * @note 소수점이 포함된 문자열은 정수 부분만 반환됩니다 (예: '12.34' → 12).
12
- * 반올림이 필요하면 {@link numParseRoundedInt}를 사용하세요.
13
- * @note 문자열 중간의 `-`도 유지되므로 의도치 않은 음수가 될 수 있습니다.
14
- * 예: `"가-123나"` → `-123`
15
- */
16
- export function numParseInt(text: unknown): number | undefined {
17
- if (typeof text === "number") return Math.trunc(text);
18
- if (typeof text !== "string") return undefined;
19
- const txt = text.replace(/[^0-9.\-]/g, "").trim();
20
- if (txt === "") return undefined;
21
- const result = Number.parseInt(txt, 10);
22
- if (Number.isNaN(result)) return undefined;
23
- return result;
24
- }
25
-
26
- /**
27
- * 문자열을 실수로 파싱 후 반올림하여 정수 반환
28
- */
29
- export function numParseRoundedInt(text: unknown): number | undefined {
30
- const float = numParseFloat(text);
31
- return float !== undefined ? Math.round(float) : undefined;
32
- }
33
-
34
- /**
35
- * 문자열을 실수로 파싱
36
- * 숫자가 아닌 문자는 제거 후 파싱
37
- */
38
- export function numParseFloat(text: unknown): number | undefined {
39
- if (typeof text === "number") return text;
40
- if (typeof text !== "string") return undefined;
41
- const txt = text.replace(/[^0-9.\-]/g, "").trim();
42
- if (txt === "") return undefined;
43
- const result = Number.parseFloat(txt);
44
- if (Number.isNaN(result)) return undefined;
45
- return result;
46
- }
47
-
48
- //#endregion
49
-
50
- //#region numIsNullOrEmpty
51
-
52
- /**
53
- * undefined, null, 0 체크 (타입 가드)
54
- *
55
- * 타입 가드로 동작하여, true 반환 시 `val`이 `0 | undefined`임을 보장합니다.
56
- * false 반환 시 `val`이 0이 아닌 유효한 숫자임이 보장됩니다.
57
- *
58
- * @param val 체크할 값
59
- * @returns undefined, null, 0이면 true
60
- * @example
61
- * const count: number | undefined = getValue();
62
- * if (numIsNullOrEmpty(count)) {
63
- * // count: 0 | undefined
64
- * console.log("비어있음");
65
- * } else {
66
- * // count: number (0이 아닌 값)
67
- * console.log(`개수: ${count}`);
68
- * }
69
- */
70
- export function numIsNullOrEmpty(val: number | undefined): val is 0 | undefined {
71
- return val == null || val === 0;
72
- }
73
-
74
- //#endregion
75
-
76
- //#region numFormat
77
-
78
- /**
79
- * 숫자를 천단위 구분자가 포함된 문자열로 포맷팅
80
- * @param val 포맷팅할 숫자
81
- * @param digit 소수점 자릿수 옵션
82
- * @param digit.max 최대 소수점 자릿수
83
- * @param digit.min 최소 소수점 자릿수 (부족하면 0으로 채움)
84
- * @example
85
- * numFormat(1234.567, { max: 2 }) // "1,234.57"
86
- * numFormat(1234, { min: 2 }) // "1,234.00"
87
- */
88
- export function numFormat(val: number, digit?: { max?: number; min?: number }): string;
89
- export function numFormat(val: number | undefined, digit?: { max?: number; min?: number }): string | undefined;
90
- export function numFormat(val: number | undefined, digit?: { max?: number; min?: number }): string | undefined {
91
- return val?.toLocaleString(undefined, {
92
- maximumFractionDigits: digit?.max,
93
- minimumFractionDigits: digit?.min,
94
- });
95
- }
96
-
97
- //#endregion