@simplysm/core-common 13.0.100 → 14.0.4
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 +86 -92
- package/dist/common.types.d.ts +14 -14
- package/dist/common.types.js +2 -1
- package/dist/common.types.js.map +1 -6
- package/dist/env.d.ts +8 -1
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +13 -9
- package/dist/env.js.map +1 -6
- 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 +31 -14
- package/dist/errors/argument-error.js.map +1 -6
- package/dist/errors/not-implemented-error.d.ts +8 -8
- package/dist/errors/not-implemented-error.js +30 -12
- package/dist/errors/not-implemented-error.js.map +1 -6
- package/dist/errors/sd-error.d.ts +10 -10
- package/dist/errors/sd-error.d.ts.map +1 -1
- package/dist/errors/sd-error.js +45 -24
- package/dist/errors/sd-error.js.map +1 -6
- package/dist/errors/timeout-error.d.ts +10 -10
- package/dist/errors/timeout-error.js +34 -15
- package/dist/errors/timeout-error.js.map +1 -6
- package/dist/extensions/arr-ext.d.ts +2 -2
- package/dist/extensions/arr-ext.helpers.d.ts +10 -10
- package/dist/extensions/arr-ext.helpers.js +112 -89
- package/dist/extensions/arr-ext.helpers.js.map +1 -6
- package/dist/extensions/arr-ext.js +458 -422
- package/dist/extensions/arr-ext.js.map +1 -6
- package/dist/extensions/arr-ext.types.d.ts +57 -57
- package/dist/extensions/arr-ext.types.d.ts.map +1 -1
- package/dist/extensions/arr-ext.types.js +6 -1
- package/dist/extensions/arr-ext.types.js.map +1 -6
- package/dist/extensions/map-ext.d.ts +16 -16
- package/dist/extensions/map-ext.js +27 -22
- package/dist/extensions/map-ext.js.map +1 -6
- package/dist/extensions/set-ext.d.ts +11 -11
- package/dist/extensions/set-ext.js +32 -25
- package/dist/extensions/set-ext.js.map +1 -6
- package/dist/features/debounce-queue.d.ts +17 -17
- package/dist/features/debounce-queue.js +98 -70
- package/dist/features/debounce-queue.js.map +1 -6
- package/dist/features/event-emitter.d.ts +20 -20
- package/dist/features/event-emitter.js +101 -78
- package/dist/features/event-emitter.js.map +1 -6
- package/dist/features/serial-queue.d.ts +11 -11
- package/dist/features/serial-queue.js +78 -57
- package/dist/features/serial-queue.js.map +1 -6
- package/dist/globals.d.ts +4 -4
- package/dist/globals.js +9 -1
- package/dist/globals.js.map +1 -6
- package/dist/index.js +28 -27
- package/dist/index.js.map +1 -6
- 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 +263 -252
- package/dist/types/date-only.js.map +1 -6
- package/dist/types/date-time.d.ts +36 -36
- package/dist/types/date-time.d.ts.map +1 -1
- package/dist/types/date-time.js +196 -288
- package/dist/types/date-time.js.map +1 -6
- 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 +202 -159
- package/dist/types/lazy-gc-map.js.map +1 -6
- package/dist/types/time.d.ts +23 -23
- package/dist/types/time.d.ts.map +1 -1
- package/dist/types/time.js +169 -158
- package/dist/types/time.js.map +1 -6
- package/dist/types/uuid.d.ts +11 -11
- package/dist/types/uuid.d.ts.map +1 -1
- package/dist/types/uuid.js +95 -70
- package/dist/types/uuid.js.map +1 -6
- package/dist/utils/bytes.d.ts +17 -17
- package/dist/utils/bytes.js +137 -81
- package/dist/utils/bytes.js.map +1 -6
- package/dist/utils/date-format.d.ts +40 -40
- package/dist/utils/date-format.js +187 -101
- package/dist/utils/date-format.js.map +1 -6
- package/dist/utils/error.d.ts +4 -4
- package/dist/utils/error.js +11 -6
- package/dist/utils/error.js.map +1 -6
- package/dist/utils/json.d.ts +19 -19
- package/dist/utils/json.js +187 -135
- package/dist/utils/json.js.map +1 -6
- package/dist/utils/num.d.ts +20 -20
- package/dist/utils/num.js +76 -34
- package/dist/utils/num.js.map +1 -6
- package/dist/utils/obj.d.ts +111 -111
- package/dist/utils/obj.d.ts.map +1 -1
- package/dist/utils/obj.js +706 -496
- package/dist/utils/obj.js.map +1 -6
- package/dist/utils/path.d.ts +10 -10
- package/dist/utils/path.js +35 -18
- package/dist/utils/path.js.map +1 -6
- package/dist/utils/primitive.d.ts +5 -5
- package/dist/utils/primitive.js +34 -14
- package/dist/utils/primitive.js.map +1 -6
- package/dist/utils/str.d.ts +38 -38
- package/dist/utils/str.js +217 -113
- package/dist/utils/str.js.map +1 -6
- package/dist/utils/template-strings.d.ts +26 -26
- package/dist/utils/template-strings.js +113 -40
- package/dist/utils/template-strings.js.map +1 -6
- package/dist/utils/transferable.d.ts +18 -18
- package/dist/utils/transferable.js +218 -151
- package/dist/utils/transferable.js.map +1 -6
- package/dist/utils/wait.d.ts +9 -9
- package/dist/utils/wait.js +30 -15
- package/dist/utils/wait.js.map +1 -6
- package/dist/utils/xml.d.ts +13 -13
- package/dist/utils/xml.js +84 -46
- package/dist/utils/xml.js.map +1 -6
- package/dist/utils/zip.d.ts +22 -22
- package/dist/utils/zip.js +172 -148
- package/dist/utils/zip.js.map +1 -6
- package/docs/array-extensions.md +430 -0
- package/docs/env.md +52 -0
- package/docs/errors.md +41 -56
- package/docs/features.md +82 -97
- package/docs/type-utilities.md +91 -0
- package/docs/types.md +221 -201
- package/docs/utils.md +319 -435
- package/package.json +7 -5
- package/src/common.types.ts +14 -14
- package/src/env.ts +12 -3
- 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 +16 -16
- package/src/extensions/arr-ext.ts +35 -35
- package/src/extensions/arr-ext.types.ts +57 -57
- package/src/extensions/map-ext.ts +16 -16
- package/src/extensions/set-ext.ts +11 -11
- package/src/features/debounce-queue.ts +23 -23
- 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 +5 -5
- package/src/types/date-only.ts +84 -83
- package/src/types/date-time.ts +43 -42
- package/src/types/lazy-gc-map.ts +44 -44
- package/src/types/time.ts +29 -29
- package/src/types/uuid.ts +15 -15
- package/src/utils/bytes.ts +35 -35
- package/src/utils/date-format.ts +59 -59
- package/src/utils/error.ts +4 -4
- package/src/utils/json.ts +41 -41
- package/src/utils/num.ts +20 -20
- 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 +48 -48
- package/src/utils/template-strings.ts +29 -29
- package/src/utils/transferable.ts +38 -38
- package/src/utils/wait.ts +10 -10
- package/src/utils/xml.ts +19 -19
- package/src/utils/zip.ts +25 -25
- package/docs/extensions.md +0 -387
- package/tests/errors/errors.spec.ts +0 -80
- package/tests/extensions/array-extension.spec.ts +0 -654
- package/tests/extensions/map-extension.spec.ts +0 -117
- package/tests/extensions/set-extension.spec.ts +0 -67
- package/tests/types/date-only.spec.ts +0 -533
- package/tests/types/date-time.spec.ts +0 -246
- package/tests/types/lazy-gc-map.spec.ts +0 -606
- package/tests/types/time.spec.ts +0 -428
- package/tests/types/uuid.spec.ts +0 -74
- package/tests/utils/bytes-utils.spec.ts +0 -197
- package/tests/utils/date-format.spec.ts +0 -350
- package/tests/utils/debounce-queue.spec.ts +0 -226
- package/tests/utils/json.spec.ts +0 -400
- package/tests/utils/number.spec.ts +0 -136
- package/tests/utils/object.spec.ts +0 -810
- package/tests/utils/path.spec.ts +0 -70
- package/tests/utils/primitive.spec.ts +0 -43
- package/tests/utils/sd-event-emitter.spec.ts +0 -189
- package/tests/utils/serial-queue.spec.ts +0 -305
- package/tests/utils/string.spec.ts +0 -265
- package/tests/utils/template-strings.spec.ts +0 -48
- package/tests/utils/transferable.spec.ts +0 -639
- package/tests/utils/wait.spec.ts +0 -123
- package/tests/utils/xml.spec.ts +0 -146
- package/tests/utils/zip.spec.ts +0 -221
package/src/types/lazy-gc-map.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import consola from "consola";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* 자동 만료 기능이 있는 Map
|
|
5
|
+
* LRU 방식으로 접근 시간을 갱신하고, 지정된 시간 동안 접근하지 않으면 자동 삭제
|
|
6
6
|
*
|
|
7
|
-
* @note
|
|
8
|
-
*
|
|
7
|
+
* @note 사용 후 반드시 dispose()를 호출하거나 'using' 문을 사용해야 함.
|
|
8
|
+
* 그렇지 않으면 GC 타이머가 계속 실행되어 메모리 누수 발생.
|
|
9
9
|
*
|
|
10
10
|
* @example
|
|
11
|
-
* // using
|
|
11
|
+
* // using 문 (권장)
|
|
12
12
|
* using map = new LazyGcMap({ gcInterval: 10000, expireTime: 60000 });
|
|
13
13
|
*
|
|
14
|
-
* //
|
|
14
|
+
* // 또는 명시적 dispose() 호출
|
|
15
15
|
* const map = new LazyGcMap({ gcInterval: 10000, expireTime: 60000 });
|
|
16
16
|
* try {
|
|
17
|
-
* // ...
|
|
17
|
+
* // ... 사용
|
|
18
18
|
* } finally {
|
|
19
19
|
* map.dispose();
|
|
20
20
|
* }
|
|
@@ -22,21 +22,21 @@ import consola from "consola";
|
|
|
22
22
|
export class LazyGcMap<TKey, TValue> {
|
|
23
23
|
private static readonly _logger = consola.withTag("LazyGcMap");
|
|
24
24
|
|
|
25
|
-
//
|
|
25
|
+
// 실제 데이터와 마지막 접근 시간을 함께 저장
|
|
26
26
|
private readonly _map = new Map<TKey, { value: TValue; lastAccess: number }>();
|
|
27
27
|
|
|
28
|
-
// GC
|
|
28
|
+
// GC 타이머
|
|
29
29
|
private _gcTimer?: ReturnType<typeof setInterval>;
|
|
30
|
-
//
|
|
30
|
+
// 중복 GC 실행 방지 플래그
|
|
31
31
|
private _isGcRunning = false;
|
|
32
|
-
//
|
|
32
|
+
// dispose()가 호출되었는지 여부
|
|
33
33
|
private _isDestroyed = false;
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
* @param _options
|
|
37
|
-
* @param _options.gcInterval GC
|
|
38
|
-
* @param _options.expireTime
|
|
39
|
-
* @param _options.onExpire
|
|
36
|
+
* @param _options 설정 옵션
|
|
37
|
+
* @param _options.gcInterval GC 간격 (밀리초). 기본값: expireTime의 1/10 (최소 1000ms)
|
|
38
|
+
* @param _options.expireTime 만료 시간 (밀리초). 마지막 접근 이후 이 시간이 지나면 삭제됨. 예: 60000 (60초)
|
|
39
|
+
* @param _options.onExpire 만료 시 호출되는 콜백. 비동기 함수 가능, 에러 발생 시 로그 출력 후 계속 실행
|
|
40
40
|
*/
|
|
41
41
|
constructor(
|
|
42
42
|
private readonly _options: {
|
|
@@ -46,48 +46,48 @@ export class LazyGcMap<TKey, TValue> {
|
|
|
46
46
|
},
|
|
47
47
|
) {}
|
|
48
48
|
|
|
49
|
-
/**
|
|
49
|
+
/** 저장된 항목 수 */
|
|
50
50
|
get size(): number {
|
|
51
51
|
return this._map.size;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
/**
|
|
54
|
+
/** key 존재 여부 확인 (접근 시간 갱신하지 않음) */
|
|
55
55
|
has(key: TKey): boolean {
|
|
56
56
|
if (this._isDestroyed) return false;
|
|
57
57
|
return this._map.has(key);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
/**
|
|
60
|
+
/** 값 조회 (접근 시간 갱신) */
|
|
61
61
|
get(key: TKey): TValue | undefined {
|
|
62
62
|
if (this._isDestroyed) return undefined;
|
|
63
63
|
const item = this._map.get(key);
|
|
64
64
|
if (item == null) return undefined;
|
|
65
65
|
|
|
66
|
-
//
|
|
66
|
+
// 조회 시 접근 시간 갱신 (LRU)
|
|
67
67
|
item.lastAccess = Date.now();
|
|
68
68
|
return item.value;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
/**
|
|
71
|
+
/** 값 저장 (접근 시간 설정 및 GC 타이머 시작) */
|
|
72
72
|
set(key: TKey, value: TValue): void {
|
|
73
73
|
if (this._isDestroyed) return;
|
|
74
74
|
this._map.set(key, { value, lastAccess: Date.now() });
|
|
75
|
-
//
|
|
75
|
+
// 데이터 추가 시 GC 타이머 시작
|
|
76
76
|
this._startGc();
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
/**
|
|
79
|
+
/** 항목 삭제 (비어있으면 GC 타이머 중지) */
|
|
80
80
|
delete(key: TKey): boolean {
|
|
81
81
|
if (this._isDestroyed) return false;
|
|
82
82
|
const result = this._map.delete(key);
|
|
83
|
-
//
|
|
83
|
+
// 비어있으면 타이머 중지
|
|
84
84
|
if (this._map.size === 0) {
|
|
85
85
|
this._stopGc();
|
|
86
86
|
}
|
|
87
87
|
return result;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
/**
|
|
90
|
+
/** 인스턴스 정리 (GC 타이머 중지 및 데이터 삭제) */
|
|
91
91
|
dispose(): void {
|
|
92
92
|
if (this._isDestroyed) return;
|
|
93
93
|
this._isDestroyed = true;
|
|
@@ -95,13 +95,13 @@ export class LazyGcMap<TKey, TValue> {
|
|
|
95
95
|
this._stopGc();
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
/**
|
|
98
|
+
/** 'using' 문 지원 */
|
|
99
99
|
[Symbol.dispose](): void {
|
|
100
100
|
this.dispose();
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
/**
|
|
104
|
-
*
|
|
104
|
+
* 모든 항목 삭제 (인스턴스는 계속 사용 가능)
|
|
105
105
|
*/
|
|
106
106
|
clear(): void {
|
|
107
107
|
if (this._isDestroyed) return;
|
|
@@ -110,14 +110,14 @@ export class LazyGcMap<TKey, TValue> {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
/**
|
|
113
|
-
*
|
|
114
|
-
* @param key
|
|
115
|
-
* @param factory
|
|
116
|
-
* @returns
|
|
113
|
+
* key에 대한 값을 반환하거나, 없으면 팩토리로 생성하여 저장 후 반환
|
|
114
|
+
* @param key 조회할 key
|
|
115
|
+
* @param factory key가 없을 때 값을 생성하는 함수
|
|
116
|
+
* @returns 기존 값 또는 새로 생성된 값
|
|
117
117
|
*/
|
|
118
118
|
getOrCreate(key: TKey, factory: () => TValue): TValue {
|
|
119
119
|
if (this._isDestroyed) {
|
|
120
|
-
throw new Error("LazyGcMap
|
|
120
|
+
throw new Error("LazyGcMap이 이미 dispose되었습니다.");
|
|
121
121
|
}
|
|
122
122
|
const item = this._map.get(key);
|
|
123
123
|
if (item == null) {
|
|
@@ -130,7 +130,7 @@ export class LazyGcMap<TKey, TValue> {
|
|
|
130
130
|
return item.value;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
/**
|
|
133
|
+
/** 값만 순회 (Iterator) */
|
|
134
134
|
*values(): IterableIterator<TValue> {
|
|
135
135
|
if (this._isDestroyed) return;
|
|
136
136
|
for (const item of this._map.values()) {
|
|
@@ -138,13 +138,13 @@ export class LazyGcMap<TKey, TValue> {
|
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
/**
|
|
141
|
+
/** key만 순회 (Iterator) */
|
|
142
142
|
*keys(): IterableIterator<TKey> {
|
|
143
143
|
if (this._isDestroyed) return;
|
|
144
144
|
yield* this._map.keys();
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
/**
|
|
147
|
+
/** 엔트리 순회 (Iterator) */
|
|
148
148
|
*entries(): IterableIterator<[TKey, TValue]> {
|
|
149
149
|
if (this._isDestroyed) return;
|
|
150
150
|
for (const [key, item] of this._map.entries()) {
|
|
@@ -165,14 +165,14 @@ export class LazyGcMap<TKey, TValue> {
|
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
private async _runGc(): Promise<void> {
|
|
168
|
-
//
|
|
168
|
+
// 이미 실행 중이면 건너뜀 (onExpire 콜백이 오래 걸릴 때 중복 실행 방지)
|
|
169
169
|
if (this._isGcRunning) return;
|
|
170
170
|
this._isGcRunning = true;
|
|
171
171
|
|
|
172
172
|
try {
|
|
173
173
|
const now = Date.now();
|
|
174
174
|
|
|
175
|
-
// 1.
|
|
175
|
+
// 1. 만료된 항목 수집 (삭제 전)
|
|
176
176
|
const expiredEntries: { key: TKey; item: { value: TValue; lastAccess: number } }[] = [];
|
|
177
177
|
for (const [key, item] of this._map) {
|
|
178
178
|
if (now - item.lastAccess > this._options.expireTime) {
|
|
@@ -180,33 +180,33 @@ export class LazyGcMap<TKey, TValue> {
|
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
// 2.
|
|
183
|
+
// 2. 각 항목에 대해 콜백 실행 후 삭제
|
|
184
184
|
for (const { key, item } of expiredEntries) {
|
|
185
|
-
//
|
|
185
|
+
// 콜백 전 현재 상태 확인 (이미 교체되었거나 삭제된 경우 건너뜀)
|
|
186
186
|
const currentItem = this._map.get(key);
|
|
187
187
|
if (currentItem !== item) {
|
|
188
188
|
continue;
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
//
|
|
191
|
+
// 만료 콜백 실행
|
|
192
192
|
if (this._options.onExpire != null) {
|
|
193
193
|
try {
|
|
194
194
|
await this._options.onExpire(key, item.value);
|
|
195
195
|
} catch (err) {
|
|
196
|
-
LazyGcMap._logger.error("onExpire
|
|
196
|
+
LazyGcMap._logger.error("onExpire 콜백 오류", err);
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
//
|
|
200
|
+
// 콜백 후 항목이 다시 등록되었는지 확인
|
|
201
|
+
// 시나리오: onExpire 콜백이 같은 key로 set()을 호출하면,
|
|
202
|
+
// 새로 등록된 항목을 삭제하면 안 됨. 항목 참조가 같으면 재등록되지 않은 것이므로 삭제.
|
|
203
203
|
const afterItem = this._map.get(key);
|
|
204
204
|
if (afterItem === item) {
|
|
205
205
|
this._map.delete(key);
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
//
|
|
209
|
+
// 정리 후 비어있으면 GC 중지
|
|
210
210
|
if (this._map.size === 0) {
|
|
211
211
|
this._stopGc();
|
|
212
212
|
}
|
package/src/types/time.ts
CHANGED
|
@@ -2,10 +2,10 @@ import { ArgumentError } from "../errors/argument-error";
|
|
|
2
2
|
import { convert12To24, format } from "../utils/date-format";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* 시간 클래스 (날짜 제외: HH:mm:ss.fff, 불변)
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* 날짜 정보 없이 시간만 저장하는 불변 클래스.
|
|
8
|
+
* 24시간을 초과하거나 음수인 값은 자동으로 정규화됨.
|
|
9
9
|
*
|
|
10
10
|
* @example
|
|
11
11
|
* const now = new Time();
|
|
@@ -17,13 +17,13 @@ export class Time {
|
|
|
17
17
|
|
|
18
18
|
private readonly _tick: number;
|
|
19
19
|
|
|
20
|
-
/**
|
|
20
|
+
/** 현재 시간으로 생성 */
|
|
21
21
|
constructor();
|
|
22
|
-
/**
|
|
22
|
+
/** 시, 분, 초, 밀리초로 생성 */
|
|
23
23
|
constructor(hour: number, minute: number, second?: number, millisecond?: number);
|
|
24
|
-
/**
|
|
24
|
+
/** tick (밀리초)으로 생성 */
|
|
25
25
|
constructor(tick: number);
|
|
26
|
-
/**
|
|
26
|
+
/** Date 객체에서 시간 부분만 추출하여 생성 */
|
|
27
27
|
constructor(date: Date);
|
|
28
28
|
constructor(arg1?: number | Date, arg2?: number, arg3?: number, arg4?: number) {
|
|
29
29
|
if (arg1 === undefined) {
|
|
@@ -55,11 +55,11 @@ export class Time {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
|
-
*
|
|
58
|
+
* 문자열을 파싱하여 Time 인스턴스 생성
|
|
59
59
|
*
|
|
60
|
-
* @param str
|
|
61
|
-
* @returns
|
|
62
|
-
* @throws ArgumentError
|
|
60
|
+
* @param str 시간 문자열
|
|
61
|
+
* @returns 파싱된 Time 인스턴스
|
|
62
|
+
* @throws ArgumentError 지원하지 않는 형식인 경우
|
|
63
63
|
*
|
|
64
64
|
* @example
|
|
65
65
|
* Time.parse("10:30:00") // HH:mm:ss
|
|
@@ -91,8 +91,8 @@ export class Time {
|
|
|
91
91
|
);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
// ISO 8601
|
|
95
|
-
//
|
|
94
|
+
// ISO 8601 형식 (예: 2025-01-15T10:30:00.123Z, 2025-01-15T10:30:00+09:00)
|
|
95
|
+
// 타임존 변환 처리를 위해 Date 객체 사용
|
|
96
96
|
const isoMatch = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.exec(str);
|
|
97
97
|
if (isoMatch != null) {
|
|
98
98
|
const date = new Date(str);
|
|
@@ -107,12 +107,12 @@ export class Time {
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
throw new ArgumentError(
|
|
110
|
-
|
|
110
|
+
`시간 형식 파싱 실패. 지원 형식: 'HH:mm:ss', 'HH:mm:ss.fff', 'AM/PM HH:mm:ss', ISO 8601`,
|
|
111
111
|
{ input: str },
|
|
112
112
|
);
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
//#region Getters (
|
|
115
|
+
//#region Getters (읽기 전용)
|
|
116
116
|
|
|
117
117
|
get hour(): number {
|
|
118
118
|
return Math.floor(this._tick / (60 * 60 * 1000));
|
|
@@ -134,61 +134,61 @@ export class Time {
|
|
|
134
134
|
return this._tick;
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
/**
|
|
137
|
+
/** 시간이 올바르게 설정되었는지 여부 */
|
|
138
138
|
get isValid(): boolean {
|
|
139
139
|
return !Number.isNaN(this._tick);
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
//#endregion
|
|
143
143
|
|
|
144
|
-
//#region
|
|
144
|
+
//#region 불변 변환 메서드 (새 인스턴스 반환)
|
|
145
145
|
|
|
146
|
-
/**
|
|
146
|
+
/** 지정된 시로 새 인스턴스 반환 */
|
|
147
147
|
setHour(hour: number): Time {
|
|
148
148
|
return new Time(hour, this.minute, this.second, this.millisecond);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
/**
|
|
151
|
+
/** 지정된 분으로 새 인스턴스 반환 */
|
|
152
152
|
setMinute(minute: number): Time {
|
|
153
153
|
return new Time(this.hour, minute, this.second, this.millisecond);
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
/**
|
|
156
|
+
/** 지정된 초로 새 인스턴스 반환 */
|
|
157
157
|
setSecond(second: number): Time {
|
|
158
158
|
return new Time(this.hour, this.minute, second, this.millisecond);
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
/**
|
|
161
|
+
/** 지정된 밀리초로 새 인스턴스 반환 */
|
|
162
162
|
setMillisecond(millisecond: number): Time {
|
|
163
163
|
return new Time(this.hour, this.minute, this.second, millisecond);
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
//#endregion
|
|
167
167
|
|
|
168
|
-
//#region
|
|
168
|
+
//#region 산술 메서드 (새 인스턴스 반환)
|
|
169
169
|
|
|
170
|
-
/**
|
|
170
|
+
/** 지정된 시간을 더한 새 인스턴스 반환 (24시간 순환) */
|
|
171
171
|
addHours(hours: number): Time {
|
|
172
172
|
let newTick = (this._tick + hours * 60 * 60 * 1000) % Time.MS_PER_DAY;
|
|
173
173
|
if (newTick < 0) newTick += Time.MS_PER_DAY;
|
|
174
174
|
return new Time(newTick);
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
/**
|
|
177
|
+
/** 지정된 분을 더한 새 인스턴스 반환 (24시간 순환) */
|
|
178
178
|
addMinutes(minutes: number): Time {
|
|
179
179
|
let newTick = (this._tick + minutes * 60 * 1000) % Time.MS_PER_DAY;
|
|
180
180
|
if (newTick < 0) newTick += Time.MS_PER_DAY;
|
|
181
181
|
return new Time(newTick);
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
/**
|
|
184
|
+
/** 지정된 초를 더한 새 인스턴스 반환 (24시간 순환) */
|
|
185
185
|
addSeconds(seconds: number): Time {
|
|
186
186
|
let newTick = (this._tick + seconds * 1000) % Time.MS_PER_DAY;
|
|
187
187
|
if (newTick < 0) newTick += Time.MS_PER_DAY;
|
|
188
188
|
return new Time(newTick);
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
/**
|
|
191
|
+
/** 지정된 밀리초를 더한 새 인스턴스 반환 (24시간 순환) */
|
|
192
192
|
addMilliseconds(milliseconds: number): Time {
|
|
193
193
|
let newTick = (this._tick + milliseconds) % Time.MS_PER_DAY;
|
|
194
194
|
if (newTick < 0) newTick += Time.MS_PER_DAY;
|
|
@@ -200,9 +200,9 @@ export class Time {
|
|
|
200
200
|
//#region Formatting
|
|
201
201
|
|
|
202
202
|
/**
|
|
203
|
-
*
|
|
204
|
-
* @param format
|
|
205
|
-
* @see dtFormat
|
|
203
|
+
* 지정된 형식으로 문자열 변환
|
|
204
|
+
* @param format 형식 문자열
|
|
205
|
+
* @see dtFormat 지원되는 형식 문자열 참조
|
|
206
206
|
*/
|
|
207
207
|
toFormatString(formatStr: string): string {
|
|
208
208
|
return format(formatStr, {
|
package/src/types/uuid.ts
CHANGED
|
@@ -2,16 +2,16 @@ import type { Bytes } from "../common.types";
|
|
|
2
2
|
import { ArgumentError } from "../errors/argument-error";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* UUID v4
|
|
5
|
+
* UUID v4 클래스
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* crypto.getRandomValues 기반으로 암호학적으로 안전한 UUID를 생성. (Chrome 79+, Node.js 호환)
|
|
8
8
|
*
|
|
9
9
|
* @example
|
|
10
10
|
* const id = Uuid.generate();
|
|
11
11
|
* const fromStr = new Uuid("550e8400-e29b-41d4-a716-446655440000");
|
|
12
12
|
*/
|
|
13
13
|
export class Uuid {
|
|
14
|
-
//
|
|
14
|
+
// 0x00 ~ 0xFF에 대한 hex 문자열 사전 계산 (256개 항목)
|
|
15
15
|
private static readonly _hexTable: string[] = Array.from({ length: 256 }, (_, i) =>
|
|
16
16
|
i.toString(16).padStart(2, "0"),
|
|
17
17
|
);
|
|
@@ -19,7 +19,7 @@ export class Uuid {
|
|
|
19
19
|
private static readonly _uuidRegex =
|
|
20
20
|
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
21
21
|
|
|
22
|
-
/**
|
|
22
|
+
/** 16바이트 배열을 UUID 문자열로 변환 */
|
|
23
23
|
private static _bytesToUuidStr(bytes: Uint8Array): string {
|
|
24
24
|
const h = Uuid._hexTable;
|
|
25
25
|
return (
|
|
@@ -46,12 +46,12 @@ export class Uuid {
|
|
|
46
46
|
);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
/**
|
|
49
|
+
/** 새 UUID v4 인스턴스 생성 */
|
|
50
50
|
static generate(): Uuid {
|
|
51
51
|
const bytes = new Uint8Array(16);
|
|
52
52
|
crypto.getRandomValues(bytes);
|
|
53
53
|
|
|
54
|
-
//
|
|
54
|
+
// UUID v4 비트 설정
|
|
55
55
|
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
|
56
56
|
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
57
57
|
|
|
@@ -59,13 +59,13 @@ export class Uuid {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
|
-
*
|
|
63
|
-
* @param bytes 16
|
|
64
|
-
* @throws {ArgumentError}
|
|
62
|
+
* 16바이트 Uint8Array로 UUID 생성
|
|
63
|
+
* @param bytes 16바이트 배열
|
|
64
|
+
* @throws {ArgumentError} 바이트 크기가 16이 아닌 경우
|
|
65
65
|
*/
|
|
66
66
|
static fromBytes(bytes: Bytes): Uuid {
|
|
67
67
|
if (bytes.length !== 16) {
|
|
68
|
-
throw new ArgumentError("UUID
|
|
68
|
+
throw new ArgumentError("UUID 바이트 크기는 16이어야 합니다.", { length: bytes.length });
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
return new Uuid(Uuid._bytesToUuidStr(bytes));
|
|
@@ -74,22 +74,22 @@ export class Uuid {
|
|
|
74
74
|
private readonly _uuid: string;
|
|
75
75
|
|
|
76
76
|
/**
|
|
77
|
-
* @param uuid UUID
|
|
78
|
-
* @throws {ArgumentError}
|
|
77
|
+
* @param uuid UUID 문자열 (형식: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
|
|
78
|
+
* @throws {ArgumentError} 형식이 유효하지 않은 경우
|
|
79
79
|
*/
|
|
80
80
|
constructor(uuid: string) {
|
|
81
81
|
if (!Uuid._uuidRegex.test(uuid)) {
|
|
82
|
-
throw new ArgumentError("
|
|
82
|
+
throw new ArgumentError("유효하지 않은 UUID 형식입니다.", { uuid });
|
|
83
83
|
}
|
|
84
84
|
this._uuid = uuid;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
/**
|
|
87
|
+
/** UUID를 문자열로 변환 */
|
|
88
88
|
toString(): string {
|
|
89
89
|
return this._uuid;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
/**
|
|
92
|
+
/** UUID를 16바이트 Uint8Array로 변환 */
|
|
93
93
|
toBytes(): Bytes {
|
|
94
94
|
const u = this._uuid;
|
|
95
95
|
// UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12)
|
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 유틸리티 함수 (복잡한 연산만)
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* - concat:
|
|
9
|
-
* - toHex:
|
|
10
|
-
* - fromHex:
|
|
11
|
-
* - toBase64:
|
|
12
|
-
* - fromBase64:
|
|
7
|
+
* 기능:
|
|
8
|
+
* - concat: 여러 Uint8Array 결합
|
|
9
|
+
* - toHex: Uint8Array를 hex 문자열로 변환
|
|
10
|
+
* - fromHex: hex 문자열을 Uint8Array로 변환
|
|
11
|
+
* - toBase64: Uint8Array를 base64 문자열로 변환
|
|
12
|
+
* - fromBase64: base64 문자열을 Uint8Array로 변환
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
/**
|
|
15
|
+
/** hex 변환용 조회 테이블 (성능 최적화) */
|
|
16
16
|
const hexTable: string[] = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
|
|
17
17
|
|
|
18
|
-
/** Base64
|
|
18
|
+
/** Base64 인코딩 테이블 */
|
|
19
19
|
const BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
20
20
|
|
|
21
|
-
/** Base64
|
|
21
|
+
/** Base64 디코딩 조회 테이블 (O(1) 조회, 모든 바이트 값 포함) */
|
|
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 Uint8Array
|
|
30
|
-
* @returns
|
|
28
|
+
* 여러 Uint8Array 결합
|
|
29
|
+
* @param arrays 결합할 Uint8Array 배열
|
|
30
|
+
* @returns 결합된 새 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 concat(arrays: Bytes[]): Bytes {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
|
-
*
|
|
50
|
-
* @param bytes Uint8Array
|
|
51
|
-
* @returns
|
|
49
|
+
* hex 문자열로 변환
|
|
50
|
+
* @param bytes 변환할 Uint8Array
|
|
51
|
+
* @returns 소문자 hex 문자열
|
|
52
52
|
* @example
|
|
53
53
|
* toHex(new Uint8Array([255, 0, 127]));
|
|
54
54
|
* // "ff007f"
|
|
@@ -63,20 +63,20 @@ export function toHex(bytes: Bytes): string {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
67
|
-
* @param hex
|
|
68
|
-
* @returns
|
|
69
|
-
* @throws {ArgumentError}
|
|
66
|
+
* hex 문자열을 Uint8Array로 변환
|
|
67
|
+
* @param hex 변환할 hex 문자열 (대소문자 허용)
|
|
68
|
+
* @returns 변환된 Uint8Array
|
|
69
|
+
* @throws {ArgumentError} 홀수 길이이거나 유효하지 않은 hex 문자가 포함된 경우
|
|
70
70
|
* @example
|
|
71
71
|
* fromHex("ff007f");
|
|
72
72
|
* // Uint8Array([255, 0, 127])
|
|
73
73
|
*/
|
|
74
74
|
export function fromHex(hex: string): Bytes {
|
|
75
75
|
if (hex.length % 2 !== 0) {
|
|
76
|
-
throw new ArgumentError("
|
|
76
|
+
throw new ArgumentError("hex 문자열은 짝수 길이여야 합니다", { hex });
|
|
77
77
|
}
|
|
78
78
|
if (hex.length > 0 && !/^[0-9a-fA-F]+$/.test(hex)) {
|
|
79
|
-
throw new ArgumentError("
|
|
79
|
+
throw new ArgumentError("유효하지 않은 hex 문자가 포함되어 있습니다", { 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 fromHex(hex: string): Bytes {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
/**
|
|
89
|
-
*
|
|
90
|
-
* @param bytes Uint8Array
|
|
91
|
-
* @returns Base64
|
|
89
|
+
* Bytes를 base64 문자열로 변환
|
|
90
|
+
* @param bytes 변환할 Uint8Array
|
|
91
|
+
* @returns Base64 인코딩된 문자열
|
|
92
92
|
* @example
|
|
93
93
|
* toBase64(new Uint8Array([72, 101, 108, 108, 111]));
|
|
94
94
|
* // "SGVsbG8="
|
|
@@ -113,33 +113,33 @@ export function toBase64(bytes: Bytes): string {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
/**
|
|
116
|
-
*
|
|
117
|
-
* @param base64
|
|
118
|
-
* @returns
|
|
119
|
-
* @throws {ArgumentError}
|
|
116
|
+
* base64 문자열을 Bytes로 변환
|
|
117
|
+
* @param base64 변환할 base64 문자열
|
|
118
|
+
* @returns 디코딩된 Uint8Array
|
|
119
|
+
* @throws {ArgumentError} 유효하지 않은 base64 문자가 포함된 경우
|
|
120
120
|
* @example
|
|
121
121
|
* fromBase64("SGVsbG8=");
|
|
122
122
|
* // Uint8Array([72, 101, 108, 108, 111])
|
|
123
123
|
*/
|
|
124
124
|
export function fromBase64(base64: string): Bytes {
|
|
125
|
-
//
|
|
125
|
+
// 공백 제거 및 패딩 정규화
|
|
126
126
|
const cleanBase64 = base64.replace(/\s/g, "").replace(/=+$/, "");
|
|
127
127
|
|
|
128
|
-
//
|
|
128
|
+
// 빈 문자열 처리
|
|
129
129
|
if (cleanBase64.length === 0) {
|
|
130
130
|
return new Uint8Array(0);
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
//
|
|
133
|
+
// 유효성 검사: 문자
|
|
134
134
|
if (!/^[A-Za-z0-9+/]+$/.test(cleanBase64)) {
|
|
135
|
-
throw new ArgumentError("
|
|
135
|
+
throw new ArgumentError("유효하지 않은 base64 문자가 포함되어 있습니다", {
|
|
136
136
|
base64: base64.substring(0, 20),
|
|
137
137
|
});
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
//
|
|
140
|
+
// 유효성 검사: 길이 (패딩 제거 후 나머지가 1이면 유효하지 않음)
|
|
141
141
|
if (cleanBase64.length % 4 === 1) {
|
|
142
|
-
throw new ArgumentError("
|
|
142
|
+
throw new ArgumentError("유효하지 않은 base64 길이입니다", { length: cleanBase64.length });
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
const len = cleanBase64.length;
|