@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.
- package/dist/common.types.js +4 -4
- package/dist/errors/argument-error.js +1 -1
- package/dist/errors/not-implemented-error.js +1 -1
- package/dist/errors/timeout-error.js +1 -1
- package/dist/extensions/arr-ext.helpers.js +4 -4
- package/dist/extensions/arr-ext.js +9 -9
- package/dist/features/debounce-queue.js +2 -2
- package/dist/features/serial-queue.js +3 -3
- package/dist/index.js +30 -30
- package/dist/types/date-only.js +2 -2
- package/dist/types/date-time.js +2 -2
- package/dist/types/time.js +2 -2
- package/dist/types/uuid.js +1 -1
- package/dist/utils/bytes.js +1 -1
- package/dist/utils/json.js +8 -8
- package/dist/utils/obj.js +5 -5
- package/dist/utils/primitive.js +5 -5
- package/dist/utils/transferable.js +4 -4
- package/dist/utils/wait.js +1 -1
- package/package.json +7 -4
- package/.cache/typecheck-browser.tsbuildinfo +0 -1
- package/.cache/typecheck-node.tsbuildinfo +0 -1
- package/.cache/typecheck-tests-browser.tsbuildinfo +0 -1
- package/.cache/typecheck-tests-node.tsbuildinfo +0 -1
- package/src/common.types.ts +0 -91
- package/src/env.ts +0 -11
- package/src/errors/argument-error.ts +0 -40
- package/src/errors/not-implemented-error.ts +0 -32
- package/src/errors/sd-error.ts +0 -53
- package/src/errors/timeout-error.ts +0 -36
- package/src/extensions/arr-ext.helpers.ts +0 -53
- package/src/extensions/arr-ext.ts +0 -777
- package/src/extensions/arr-ext.types.ts +0 -258
- package/src/extensions/map-ext.ts +0 -86
- package/src/extensions/set-ext.ts +0 -68
- package/src/features/debounce-queue.ts +0 -116
- package/src/features/event-emitter.ts +0 -112
- package/src/features/serial-queue.ts +0 -94
- package/src/globals.ts +0 -12
- package/src/index.ts +0 -55
- package/src/types/date-only.ts +0 -329
- package/src/types/date-time.ts +0 -294
- package/src/types/lazy-gc-map.ts +0 -244
- package/src/types/time.ts +0 -210
- package/src/types/uuid.ts +0 -113
- package/src/utils/bytes.ts +0 -160
- package/src/utils/date-format.ts +0 -239
- package/src/utils/json.ts +0 -230
- package/src/utils/num.ts +0 -97
- package/src/utils/obj.ts +0 -956
- package/src/utils/path.ts +0 -40
- package/src/utils/primitive.ts +0 -33
- package/src/utils/str.ts +0 -252
- package/src/utils/template-strings.ts +0 -132
- package/src/utils/transferable.ts +0 -269
- package/src/utils/wait.ts +0 -40
- package/src/utils/xml.ts +0 -105
- package/src/zip/sd-zip.ts +0 -218
- package/tests/errors/errors.spec.ts +0 -196
- package/tests/extensions/array-extension.spec.ts +0 -790
- package/tests/extensions/map-extension.spec.ts +0 -147
- package/tests/extensions/set-extension.spec.ts +0 -74
- package/tests/types/date-only.spec.ts +0 -636
- package/tests/types/date-time.spec.ts +0 -391
- package/tests/types/lazy-gc-map.spec.ts +0 -692
- package/tests/types/time.spec.ts +0 -559
- package/tests/types/types.spec.ts +0 -55
- package/tests/types/uuid.spec.ts +0 -91
- package/tests/utils/bytes-utils.spec.ts +0 -230
- package/tests/utils/date-format.spec.ts +0 -371
- package/tests/utils/debounce-queue.spec.ts +0 -272
- package/tests/utils/json.spec.ts +0 -475
- package/tests/utils/number.spec.ts +0 -184
- package/tests/utils/object.spec.ts +0 -827
- package/tests/utils/path.spec.ts +0 -78
- package/tests/utils/primitive.spec.ts +0 -55
- package/tests/utils/sd-event-emitter.spec.ts +0 -216
- package/tests/utils/serial-queue.spec.ts +0 -365
- package/tests/utils/string.spec.ts +0 -294
- package/tests/utils/template-strings.spec.ts +0 -96
- package/tests/utils/transferable.spec.ts +0 -698
- package/tests/utils/wait.spec.ts +0 -145
- package/tests/utils/xml.spec.ts +0 -146
- package/tests/zip/sd-zip.spec.ts +0 -234
package/src/utils/date-format.ts
DELETED
|
@@ -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
|