@simplysm/core-common 13.0.15 → 13.0.18
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 +89 -90
- package/dist/common.types.d.ts +4 -4
- package/dist/common.types.d.ts.map +1 -1
- package/dist/extensions/arr-ext.types.d.ts +66 -66
- package/dist/extensions/arr-ext.types.d.ts.map +1 -1
- package/dist/features/event-emitter.d.ts +7 -7
- package/dist/features/event-emitter.d.ts.map +1 -1
- package/dist/features/event-emitter.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/types/lazy-gc-map.d.ts +10 -17
- package/dist/types/lazy-gc-map.d.ts.map +1 -1
- package/dist/types/lazy-gc-map.js +0 -14
- package/dist/types/lazy-gc-map.js.map +1 -1
- package/dist/utils/json.d.ts +1 -1
- package/dist/utils/json.d.ts.map +1 -1
- package/dist/utils/json.js.map +1 -1
- package/dist/utils/obj.d.ts +14 -14
- package/dist/utils/obj.d.ts.map +1 -1
- package/dist/utils/obj.js.map +1 -1
- package/package.json +2 -6
- package/src/common.types.ts +4 -4
- package/src/extensions/arr-ext.types.ts +74 -65
- package/src/features/event-emitter.ts +6 -6
- package/src/index.ts +2 -2
- package/src/types/lazy-gc-map.ts +13 -31
- package/src/utils/json.ts +2 -2
- package/src/utils/obj.ts +48 -40
|
@@ -9,63 +9,63 @@ import type { Time } from "../types/time";
|
|
|
9
9
|
|
|
10
10
|
//#region 인터페이스
|
|
11
11
|
|
|
12
|
-
export interface ReadonlyArrayExt<
|
|
12
|
+
export interface ReadonlyArrayExt<TItem> {
|
|
13
13
|
/**
|
|
14
14
|
* 조건에 맞는 단일 요소 반환
|
|
15
15
|
* @param predicate 필터 조건 (생략 시 배열 전체 대상)
|
|
16
16
|
* @returns 요소가 없으면 undefined
|
|
17
17
|
* @throws ArgumentError 조건에 맞는 요소가 2개 이상이면 발생
|
|
18
18
|
*/
|
|
19
|
-
single(predicate?: (item:
|
|
19
|
+
single(predicate?: (item: TItem, index: number) => boolean): TItem | undefined;
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* 첫 번째 요소 반환
|
|
23
23
|
* @param predicate 필터 조건 (생략 시 첫 번째 요소 반환)
|
|
24
24
|
* @returns 요소가 없으면 undefined
|
|
25
25
|
*/
|
|
26
|
-
first(predicate?: (item:
|
|
26
|
+
first(predicate?: (item: TItem, index: number) => boolean): TItem | undefined;
|
|
27
27
|
|
|
28
28
|
/** 비동기 필터 (순차 실행) */
|
|
29
|
-
filterAsync(predicate: (item:
|
|
29
|
+
filterAsync(predicate: (item: TItem, index: number) => Promise<boolean>): Promise<TItem[]>;
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* 마지막 요소 반환
|
|
33
33
|
* @param predicate 필터 조건 (생략 시 마지막 요소 반환)
|
|
34
34
|
* @returns 요소가 없으면 undefined
|
|
35
35
|
*/
|
|
36
|
-
last(predicate?: (item:
|
|
36
|
+
last(predicate?: (item: TItem, index: number) => boolean): TItem | undefined;
|
|
37
37
|
|
|
38
38
|
/** null/undefined 제거 */
|
|
39
|
-
filterExists(): NonNullable<
|
|
39
|
+
filterExists(): NonNullable<TItem>[];
|
|
40
40
|
|
|
41
41
|
/** 특정 타입의 요소만 필터링 (PrimitiveTypeStr 또는 생성자 타입) */
|
|
42
|
-
ofType<K extends PrimitiveTypeStr>(type: K): Extract<
|
|
43
|
-
ofType<N extends
|
|
42
|
+
ofType<K extends PrimitiveTypeStr>(type: K): Extract<TItem, PrimitiveTypeMap[K]>[];
|
|
43
|
+
ofType<N extends TItem>(type: Type<N>): N[];
|
|
44
44
|
|
|
45
45
|
/** 비동기 매핑 (순차 실행) */
|
|
46
|
-
mapAsync<R>(selector: (item:
|
|
46
|
+
mapAsync<R>(selector: (item: TItem, index: number) => Promise<R>): Promise<R[]>;
|
|
47
47
|
|
|
48
48
|
/** 중첩 배열 평탄화 */
|
|
49
|
-
mapMany():
|
|
49
|
+
mapMany(): TItem extends readonly (infer U)[] ? U[] : TItem;
|
|
50
50
|
|
|
51
51
|
/** 매핑 후 평탄화 */
|
|
52
|
-
mapMany<R>(selector: (item:
|
|
52
|
+
mapMany<R>(selector: (item: TItem, index: number) => R[]): R[];
|
|
53
53
|
|
|
54
54
|
/** 비동기 매핑 후 평탄화 (순차 실행) */
|
|
55
|
-
mapManyAsync<R>(selector: (item:
|
|
55
|
+
mapManyAsync<R>(selector: (item: TItem, index: number) => Promise<R[]>): Promise<R[]>;
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* 비동기 병렬 처리 (Promise.all 사용)
|
|
59
59
|
* @note 하나라도 reject되면 전체가 fail-fast로 reject됨 (Promise.all 동작)
|
|
60
60
|
*/
|
|
61
|
-
parallelAsync<R>(fn: (item:
|
|
61
|
+
parallelAsync<R>(fn: (item: TItem, index: number) => Promise<R>): Promise<R[]>;
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
64
|
* 키 기준 그룹화
|
|
65
65
|
* @param keySelector 그룹 키 선택 함수
|
|
66
66
|
* @note O(n²) 복잡도 (객체 키 지원을 위해 깊은 비교 사용). primitive 키만 필요하면 toArrayMap()이 O(n)으로 더 효율적
|
|
67
67
|
*/
|
|
68
|
-
groupBy<K>(keySelector: (item:
|
|
68
|
+
groupBy<K>(keySelector: (item: TItem, index: number) => K): { key: K; values: TItem[] }[];
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
71
|
* 키 기준 그룹화 (값 변환 포함)
|
|
@@ -74,44 +74,47 @@ export interface ReadonlyArrayExt<T> {
|
|
|
74
74
|
* @note O(n²) 복잡도 (객체 키 지원을 위해 깊은 비교 사용). primitive 키만 필요하면 toArrayMap()이 O(n)으로 더 효율적
|
|
75
75
|
*/
|
|
76
76
|
groupBy<K, V>(
|
|
77
|
-
keySelector: (item:
|
|
78
|
-
valueSelector: (item:
|
|
77
|
+
keySelector: (item: TItem, index: number) => K,
|
|
78
|
+
valueSelector: (item: TItem, index: number) => V,
|
|
79
79
|
): {
|
|
80
80
|
key: K;
|
|
81
81
|
values: V[];
|
|
82
82
|
}[];
|
|
83
83
|
|
|
84
|
-
toMap<K>(keySelector: (item:
|
|
84
|
+
toMap<K>(keySelector: (item: TItem, index: number) => K): Map<K, TItem>;
|
|
85
85
|
|
|
86
|
-
toMap<K, V>(
|
|
86
|
+
toMap<K, V>(
|
|
87
|
+
keySelector: (item: TItem, index: number) => K,
|
|
88
|
+
valueSelector: (item: TItem, index: number) => V,
|
|
89
|
+
): Map<K, V>;
|
|
87
90
|
|
|
88
|
-
toMapAsync<K>(keySelector: (item:
|
|
91
|
+
toMapAsync<K>(keySelector: (item: TItem, index: number) => Promise<K>): Promise<Map<K, TItem>>;
|
|
89
92
|
|
|
90
93
|
toMapAsync<K, V>(
|
|
91
|
-
keySelector: (item:
|
|
92
|
-
valueSelector: (item:
|
|
94
|
+
keySelector: (item: TItem, index: number) => Promise<K> | K,
|
|
95
|
+
valueSelector: (item: TItem, index: number) => Promise<V> | V,
|
|
93
96
|
): Promise<Map<K, V>>;
|
|
94
97
|
|
|
95
|
-
toArrayMap<K>(keySelector: (item:
|
|
98
|
+
toArrayMap<K>(keySelector: (item: TItem, index: number) => K): Map<K, TItem[]>;
|
|
96
99
|
|
|
97
100
|
toArrayMap<K, V>(
|
|
98
|
-
keySelector: (item:
|
|
99
|
-
valueSelector: (item:
|
|
101
|
+
keySelector: (item: TItem, index: number) => K,
|
|
102
|
+
valueSelector: (item: TItem, index: number) => V,
|
|
100
103
|
): Map<K, V[]>;
|
|
101
104
|
|
|
102
|
-
toSetMap<K>(keySelector: (item:
|
|
105
|
+
toSetMap<K>(keySelector: (item: TItem, index: number) => K): Map<K, Set<TItem>>;
|
|
103
106
|
toSetMap<K, V>(
|
|
104
|
-
keySelector: (item:
|
|
105
|
-
valueSelector: (item:
|
|
107
|
+
keySelector: (item: TItem, index: number) => K,
|
|
108
|
+
valueSelector: (item: TItem, index: number) => V,
|
|
106
109
|
): Map<K, Set<V>>;
|
|
107
110
|
|
|
108
|
-
toMapValues<K, V>(keySelector: (item:
|
|
111
|
+
toMapValues<K, V>(keySelector: (item: TItem, index: number) => K, valueSelector: (items: TItem[]) => V): Map<K, V>;
|
|
109
112
|
|
|
110
|
-
toObject(keySelector: (item:
|
|
113
|
+
toObject(keySelector: (item: TItem, index: number) => string): Record<string, TItem>;
|
|
111
114
|
|
|
112
115
|
toObject<V>(
|
|
113
|
-
keySelector: (item:
|
|
114
|
-
valueSelector: (item:
|
|
116
|
+
keySelector: (item: TItem, index: number) => string,
|
|
117
|
+
valueSelector: (item: TItem, index: number) => V,
|
|
115
118
|
): Record<string, V>;
|
|
116
119
|
|
|
117
120
|
/**
|
|
@@ -150,18 +153,18 @@ export interface ReadonlyArrayExt<T> {
|
|
|
150
153
|
* // ]}]
|
|
151
154
|
* ```
|
|
152
155
|
*/
|
|
153
|
-
toTree<K extends keyof
|
|
156
|
+
toTree<K extends keyof TItem, P extends keyof TItem>(keyProp: K, parentKey: P): TreeArray<TItem>[];
|
|
154
157
|
|
|
155
158
|
/**
|
|
156
159
|
* 중복 제거
|
|
157
160
|
* @param options matchAddress: 주소 비교 (true면 Set 사용), keyFn: 커스텀 키 함수 (O(n) 성능)
|
|
158
161
|
* @note 객체 배열에서 keyFn 없이 사용 시 O(n²) 복잡도. 대량 데이터는 keyFn 사용 권장
|
|
159
162
|
*/
|
|
160
|
-
distinct(options?: boolean | { matchAddress?: boolean; keyFn?: (item:
|
|
163
|
+
distinct(options?: boolean | { matchAddress?: boolean; keyFn?: (item: TItem) => string | number }): TItem[];
|
|
161
164
|
|
|
162
|
-
orderBy(selector?: (item:
|
|
165
|
+
orderBy(selector?: (item: TItem) => string | number | DateOnly | DateTime | Time | undefined): TItem[];
|
|
163
166
|
|
|
164
|
-
orderByDesc(selector?: (item:
|
|
167
|
+
orderByDesc(selector?: (item: TItem) => string | number | DateOnly | DateTime | Time | undefined): TItem[];
|
|
165
168
|
|
|
166
169
|
/**
|
|
167
170
|
* 두 배열 비교 (INSERT/DELETE/UPDATE)
|
|
@@ -169,68 +172,74 @@ export interface ReadonlyArrayExt<T> {
|
|
|
169
172
|
* @param options keys: 키 비교용, excludes: 비교 제외 속성
|
|
170
173
|
* @note target에 중복 키가 있으면 첫 번째 매칭만 사용됨
|
|
171
174
|
*/
|
|
172
|
-
diffs<
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
175
|
+
diffs<TOtherItem>(
|
|
176
|
+
target: TOtherItem[],
|
|
177
|
+
options?: { keys?: string[]; excludes?: string[] },
|
|
178
|
+
): ArrayDiffsResult<TItem, TOtherItem>[];
|
|
179
|
+
|
|
180
|
+
oneWayDiffs<K extends keyof TItem>(
|
|
181
|
+
orgItems: TItem[] | Map<TItem[K], TItem>,
|
|
182
|
+
keyPropNameOrFn: K | ((item: TItem) => K),
|
|
177
183
|
options?: {
|
|
178
184
|
includeSame?: boolean;
|
|
179
185
|
excludes?: string[];
|
|
180
186
|
includes?: string[];
|
|
181
187
|
},
|
|
182
|
-
): ArrayDiffs2Result<
|
|
188
|
+
): ArrayDiffs2Result<TItem>[];
|
|
183
189
|
|
|
184
|
-
merge<
|
|
190
|
+
merge<TOtherItem>(
|
|
191
|
+
target: TOtherItem[],
|
|
192
|
+
options?: { keys?: string[]; excludes?: string[] },
|
|
193
|
+
): (TItem | TOtherItem | (TItem & TOtherItem))[];
|
|
185
194
|
|
|
186
195
|
/**
|
|
187
196
|
* 요소의 합계 반환
|
|
188
197
|
* @param selector 값 선택 함수 (생략 시 요소 자체를 number로 사용)
|
|
189
198
|
* @returns 빈 배열인 경우 0 반환
|
|
190
199
|
*/
|
|
191
|
-
sum(selector?: (item:
|
|
200
|
+
sum(selector?: (item: TItem, index: number) => number): number;
|
|
192
201
|
|
|
193
|
-
min():
|
|
202
|
+
min(): TItem extends number | string ? TItem | undefined : never;
|
|
194
203
|
|
|
195
|
-
min<P extends number | string>(selector?: (item:
|
|
204
|
+
min<P extends number | string>(selector?: (item: TItem, index: number) => P): P | undefined;
|
|
196
205
|
|
|
197
|
-
max():
|
|
206
|
+
max(): TItem extends number | string ? TItem | undefined : never;
|
|
198
207
|
|
|
199
|
-
max<P extends number | string>(selector?: (item:
|
|
208
|
+
max<P extends number | string>(selector?: (item: TItem, index: number) => P): P | undefined;
|
|
200
209
|
|
|
201
|
-
shuffle():
|
|
210
|
+
shuffle(): TItem[];
|
|
202
211
|
}
|
|
203
212
|
|
|
204
213
|
/**
|
|
205
214
|
* 원본 배열을 변경하는 확장 메서드
|
|
206
215
|
* @mutates 모든 메서드가 원본 배열을 직접 변경합니다
|
|
207
216
|
*/
|
|
208
|
-
export interface MutableArrayExt<
|
|
217
|
+
export interface MutableArrayExt<TItem> {
|
|
209
218
|
/**
|
|
210
219
|
* 원본 배열에서 중복 제거
|
|
211
220
|
* @param options matchAddress: 주소 비교 (true면 Set 사용), keyFn: 커스텀 키 함수 (O(n) 성능)
|
|
212
221
|
* @note 객체 배열에서 keyFn 없이 사용 시 O(n²) 복잡도. 대량 데이터는 keyFn 사용 권장
|
|
213
222
|
* @mutates
|
|
214
223
|
*/
|
|
215
|
-
distinctThis(options?: boolean | { matchAddress?: boolean; keyFn?: (item:
|
|
224
|
+
distinctThis(options?: boolean | { matchAddress?: boolean; keyFn?: (item: TItem) => string | number }): TItem[];
|
|
216
225
|
|
|
217
226
|
/** 원본 배열 오름차순 정렬 @mutates */
|
|
218
|
-
orderByThis(selector?: (item:
|
|
227
|
+
orderByThis(selector?: (item: TItem) => string | number | DateOnly | DateTime | Time | undefined): TItem[];
|
|
219
228
|
|
|
220
229
|
/** 원본 배열 내림차순 정렬 @mutates */
|
|
221
|
-
orderByDescThis(selector?: (item:
|
|
230
|
+
orderByDescThis(selector?: (item: TItem) => string | number | DateOnly | DateTime | Time | undefined): TItem[];
|
|
222
231
|
|
|
223
232
|
/** 원본 배열에 항목 삽입 @mutates */
|
|
224
|
-
insert(index: number, ...items:
|
|
233
|
+
insert(index: number, ...items: TItem[]): this;
|
|
225
234
|
|
|
226
235
|
/** 원본 배열에서 항목 제거 @mutates */
|
|
227
|
-
remove(item:
|
|
236
|
+
remove(item: TItem): this;
|
|
228
237
|
|
|
229
238
|
/** 원본 배열에서 조건에 맞는 항목 제거 @mutates */
|
|
230
|
-
remove(selector: (item:
|
|
239
|
+
remove(selector: (item: TItem, index: number) => boolean): this;
|
|
231
240
|
|
|
232
241
|
/** 원본 배열에서 항목 토글 (있으면 제거, 없으면 추가) @mutates */
|
|
233
|
-
toggle(item:
|
|
242
|
+
toggle(item: TItem): this;
|
|
234
243
|
|
|
235
244
|
/** 원본 배열 비우기 @mutates */
|
|
236
245
|
clear(): this;
|
|
@@ -240,17 +249,17 @@ export interface MutableArrayExt<T> {
|
|
|
240
249
|
|
|
241
250
|
//#region 내보내기 타입
|
|
242
251
|
|
|
243
|
-
export type ArrayDiffsResult<
|
|
244
|
-
| { source: undefined; target:
|
|
245
|
-
| { source:
|
|
246
|
-
| { source:
|
|
252
|
+
export type ArrayDiffsResult<TOriginal, TOther> =
|
|
253
|
+
| { source: undefined; target: TOther } // INSERT
|
|
254
|
+
| { source: TOriginal; target: undefined } // DELETE
|
|
255
|
+
| { source: TOriginal; target: TOther }; // UPDATE
|
|
247
256
|
|
|
248
|
-
export type ArrayDiffs2Result<
|
|
249
|
-
| { type: "create"; item:
|
|
250
|
-
| { type: "update"; item:
|
|
251
|
-
| { type: "same"; item:
|
|
257
|
+
export type ArrayDiffs2Result<TItem> =
|
|
258
|
+
| { type: "create"; item: TItem; orgItem: undefined }
|
|
259
|
+
| { type: "update"; item: TItem; orgItem: TItem }
|
|
260
|
+
| { type: "same"; item: TItem; orgItem: TItem };
|
|
252
261
|
|
|
253
|
-
export type TreeArray<
|
|
262
|
+
export type TreeArray<TNode> = TNode & { children: TreeArray<TNode>[] };
|
|
254
263
|
|
|
255
264
|
/** 정렬/비교 가능한 타입 */
|
|
256
265
|
export type ComparableType = string | number | boolean | DateTime | DateOnly | Time | undefined;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 브라우저와 Node.js 모두에서 사용 가능한 타입 안전한 이벤트 에미터이다.
|
|
5
5
|
* 내부적으로 EventTarget을 사용하여 구현되어 있다.
|
|
6
6
|
*
|
|
7
|
-
* @typeParam
|
|
7
|
+
* @typeParam TEvents 이벤트 타입 맵. 키는 이벤트 이름, 값은 이벤트 데이터 타입
|
|
8
8
|
*
|
|
9
9
|
* @example
|
|
10
10
|
* interface MyEvents {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* emitter.emit("data", "hello");
|
|
21
21
|
* emitter.emit("done"); // void 타입은 인자 없이 호출
|
|
22
22
|
*/
|
|
23
|
-
export class EventEmitter<
|
|
23
|
+
export class EventEmitter<TEvents extends { [K in keyof TEvents]: unknown } = Record<string, unknown>> {
|
|
24
24
|
private readonly _target = new EventTarget();
|
|
25
25
|
// 이벤트 타입별로 리스너 맵 관리 (같은 리스너를 다른 이벤트에 등록 가능)
|
|
26
26
|
// 다형적 리스너 관리를 위해 Function 타입 사용
|
|
@@ -33,7 +33,7 @@ export class EventEmitter<T extends { [K in keyof T]: unknown } = Record<string,
|
|
|
33
33
|
* @param listener 이벤트 핸들러
|
|
34
34
|
* @note 같은 리스너를 같은 이벤트에 중복 등록하면 무시됨
|
|
35
35
|
*/
|
|
36
|
-
on<K extends keyof
|
|
36
|
+
on<K extends keyof TEvents & string>(type: K, listener: (data: TEvents[K]) => void): void {
|
|
37
37
|
// 이벤트 타입별 맵 가져오거나 생성
|
|
38
38
|
let typeMap = this._listenerMap.get(type);
|
|
39
39
|
if (!typeMap) {
|
|
@@ -55,7 +55,7 @@ export class EventEmitter<T extends { [K in keyof T]: unknown } = Record<string,
|
|
|
55
55
|
* @param type 이벤트 타입
|
|
56
56
|
* @param listener 제거할 이벤트 핸들러
|
|
57
57
|
*/
|
|
58
|
-
off<K extends keyof
|
|
58
|
+
off<K extends keyof TEvents & string>(type: K, listener: (data: TEvents[K]) => void): void {
|
|
59
59
|
const typeMap = this._listenerMap.get(type);
|
|
60
60
|
if (!typeMap) return;
|
|
61
61
|
|
|
@@ -77,7 +77,7 @@ export class EventEmitter<T extends { [K in keyof T]: unknown } = Record<string,
|
|
|
77
77
|
* @param type 이벤트 타입
|
|
78
78
|
* @param args 이벤트 데이터 (void 타입이면 생략)
|
|
79
79
|
*/
|
|
80
|
-
emit<K extends keyof
|
|
80
|
+
emit<K extends keyof TEvents & string>(type: K, ...args: TEvents[K] extends void ? [] : [data: TEvents[K]]): void {
|
|
81
81
|
this._target.dispatchEvent(new CustomEvent(type, { detail: args[0] }));
|
|
82
82
|
}
|
|
83
83
|
|
|
@@ -87,7 +87,7 @@ export class EventEmitter<T extends { [K in keyof T]: unknown } = Record<string,
|
|
|
87
87
|
* @param type 이벤트 타입
|
|
88
88
|
* @returns 등록된 리스너 수
|
|
89
89
|
*/
|
|
90
|
-
listenerCount<K extends keyof
|
|
90
|
+
listenerCount<K extends keyof TEvents & string>(type: K): number {
|
|
91
91
|
return this._listenerMap.get(type)?.size ?? 0;
|
|
92
92
|
}
|
|
93
93
|
|
package/src/index.ts
CHANGED
|
@@ -7,8 +7,8 @@ import "./extensions/map-ext";
|
|
|
7
7
|
|
|
8
8
|
export * from "./env";
|
|
9
9
|
|
|
10
|
-
// arr-extension
|
|
11
|
-
export
|
|
10
|
+
// arr-extension re-export
|
|
11
|
+
export * from "./extensions/arr-ext";
|
|
12
12
|
|
|
13
13
|
//#region errors
|
|
14
14
|
export * from "./errors/sd-error";
|
package/src/types/lazy-gc-map.ts
CHANGED
|
@@ -19,23 +19,11 @@ import consola from "consola";
|
|
|
19
19
|
* map.dispose();
|
|
20
20
|
* }
|
|
21
21
|
*/
|
|
22
|
-
export class LazyGcMap<
|
|
22
|
+
export class LazyGcMap<TKey, TValue> {
|
|
23
23
|
private static readonly _logger = consola.withTag("LazyGcMap");
|
|
24
24
|
|
|
25
|
-
/**
|
|
26
|
-
* dispose() 미호출 감지용 registry
|
|
27
|
-
* @note FinalizationRegistry는 Chrome 84+, Node 14.6+ 지원
|
|
28
|
-
* 미지원 환경에서는 경고 없이 동작하지만, dispose() 미호출 시 메모리 누수 가능
|
|
29
|
-
*/
|
|
30
|
-
private static readonly _registry =
|
|
31
|
-
typeof FinalizationRegistry !== "undefined"
|
|
32
|
-
? new FinalizationRegistry<string>((id) => {
|
|
33
|
-
LazyGcMap._logger.warn(`LazyGcMap(${id})이 dispose() 없이 가비지 수집됨. 메모리 누수 가능성 있음.`);
|
|
34
|
-
})
|
|
35
|
-
: undefined;
|
|
36
|
-
|
|
37
25
|
// 실제 데이터와 마지막 접근 시간을 함께 저장
|
|
38
|
-
private readonly _map = new Map<
|
|
26
|
+
private readonly _map = new Map<TKey, { value: TValue; lastAccess: number }>();
|
|
39
27
|
|
|
40
28
|
// GC 타이머
|
|
41
29
|
private _gcTimer?: ReturnType<typeof setInterval>;
|
|
@@ -43,8 +31,6 @@ export class LazyGcMap<K, V> {
|
|
|
43
31
|
private _isGcRunning = false;
|
|
44
32
|
// destroy 호출 여부
|
|
45
33
|
private _isDestroyed = false;
|
|
46
|
-
// 인스턴스 식별자 (경고 메시지용)
|
|
47
|
-
private readonly _instanceId = Math.random().toString(36).slice(2);
|
|
48
34
|
|
|
49
35
|
/**
|
|
50
36
|
* @param _options 설정 옵션
|
|
@@ -56,11 +42,9 @@ export class LazyGcMap<K, V> {
|
|
|
56
42
|
private readonly _options: {
|
|
57
43
|
gcInterval?: number;
|
|
58
44
|
expireTime: number;
|
|
59
|
-
onExpire?: (key:
|
|
45
|
+
onExpire?: (key: TKey, value: TValue) => void | Promise<void>;
|
|
60
46
|
},
|
|
61
|
-
) {
|
|
62
|
-
LazyGcMap._registry?.register(this, this._instanceId);
|
|
63
|
-
}
|
|
47
|
+
) {}
|
|
64
48
|
|
|
65
49
|
/** 저장된 항목 수 */
|
|
66
50
|
get size(): number {
|
|
@@ -68,13 +52,13 @@ export class LazyGcMap<K, V> {
|
|
|
68
52
|
}
|
|
69
53
|
|
|
70
54
|
/** 키 존재 여부 확인 (접근 시간 갱신 안함) */
|
|
71
|
-
has(key:
|
|
55
|
+
has(key: TKey): boolean {
|
|
72
56
|
if (this._isDestroyed) return false;
|
|
73
57
|
return this._map.has(key);
|
|
74
58
|
}
|
|
75
59
|
|
|
76
60
|
/** 값 조회 (접근 시간 갱신됨) */
|
|
77
|
-
get(key:
|
|
61
|
+
get(key: TKey): TValue | undefined {
|
|
78
62
|
if (this._isDestroyed) return undefined;
|
|
79
63
|
const item = this._map.get(key);
|
|
80
64
|
if (item == null) return undefined;
|
|
@@ -85,7 +69,7 @@ export class LazyGcMap<K, V> {
|
|
|
85
69
|
}
|
|
86
70
|
|
|
87
71
|
/** 값 저장 (접근 시간 설정 및 GC 타이머 시작) */
|
|
88
|
-
set(key:
|
|
72
|
+
set(key: TKey, value: TValue): void {
|
|
89
73
|
if (this._isDestroyed) return;
|
|
90
74
|
this._map.set(key, { value, lastAccess: Date.now() });
|
|
91
75
|
// 데이터가 들어왔으므로 GC 타이머 가동
|
|
@@ -93,7 +77,7 @@ export class LazyGcMap<K, V> {
|
|
|
93
77
|
}
|
|
94
78
|
|
|
95
79
|
/** 항목 삭제 (비었으면 GC 타이머 중지) */
|
|
96
|
-
delete(key:
|
|
80
|
+
delete(key: TKey): boolean {
|
|
97
81
|
if (this._isDestroyed) return false;
|
|
98
82
|
const result = this._map.delete(key);
|
|
99
83
|
// 비었으면 타이머 중지
|
|
@@ -107,8 +91,6 @@ export class LazyGcMap<K, V> {
|
|
|
107
91
|
dispose(): void {
|
|
108
92
|
if (this._isDestroyed) return;
|
|
109
93
|
this._isDestroyed = true;
|
|
110
|
-
LazyGcMap._registry?.unregister(this);
|
|
111
|
-
|
|
112
94
|
this._map.clear();
|
|
113
95
|
this._stopGc();
|
|
114
96
|
}
|
|
@@ -133,7 +115,7 @@ export class LazyGcMap<K, V> {
|
|
|
133
115
|
* @param factory 키가 없을 때 값을 생성하는 함수
|
|
134
116
|
* @returns 기존 값 또는 새로 생성된 값
|
|
135
117
|
*/
|
|
136
|
-
getOrCreate(key:
|
|
118
|
+
getOrCreate(key: TKey, factory: () => TValue): TValue {
|
|
137
119
|
if (this._isDestroyed) {
|
|
138
120
|
throw new Error("LazyGcMap이 이미 dispose되었습니다.");
|
|
139
121
|
}
|
|
@@ -149,7 +131,7 @@ export class LazyGcMap<K, V> {
|
|
|
149
131
|
}
|
|
150
132
|
|
|
151
133
|
/** 값들만 순회 (Iterator) */
|
|
152
|
-
*values(): IterableIterator<
|
|
134
|
+
*values(): IterableIterator<TValue> {
|
|
153
135
|
if (this._isDestroyed) return;
|
|
154
136
|
for (const item of this._map.values()) {
|
|
155
137
|
yield item.value;
|
|
@@ -157,13 +139,13 @@ export class LazyGcMap<K, V> {
|
|
|
157
139
|
}
|
|
158
140
|
|
|
159
141
|
/** 키들만 순회 (Iterator) */
|
|
160
|
-
*keys(): IterableIterator<
|
|
142
|
+
*keys(): IterableIterator<TKey> {
|
|
161
143
|
if (this._isDestroyed) return;
|
|
162
144
|
yield* this._map.keys();
|
|
163
145
|
}
|
|
164
146
|
|
|
165
147
|
/** 엔트리 순회 (Iterator) */
|
|
166
|
-
*entries(): IterableIterator<[
|
|
148
|
+
*entries(): IterableIterator<[TKey, TValue]> {
|
|
167
149
|
if (this._isDestroyed) return;
|
|
168
150
|
for (const [key, item] of this._map.entries()) {
|
|
169
151
|
yield [key, item.value];
|
|
@@ -191,7 +173,7 @@ export class LazyGcMap<K, V> {
|
|
|
191
173
|
const now = Date.now();
|
|
192
174
|
|
|
193
175
|
// 1. 만료된 항목 수집 (삭제 전)
|
|
194
|
-
const expiredEntries: { key:
|
|
176
|
+
const expiredEntries: { key: TKey; item: { value: TValue; lastAccess: number } }[] = [];
|
|
195
177
|
for (const [key, item] of this._map) {
|
|
196
178
|
if (now - item.lastAccess > this._options.expireTime) {
|
|
197
179
|
expiredEntries.push({ key, item });
|
package/src/utils/json.ts
CHANGED
|
@@ -172,7 +172,7 @@ export function jsonStringify(
|
|
|
172
172
|
* @security 개발 모드(`__DEV__`)에서만 에러 메시지에 JSON 문자열 전체가 포함된다.
|
|
173
173
|
* 프로덕션 모드에서는 JSON 길이만 포함된다.
|
|
174
174
|
*/
|
|
175
|
-
export function jsonParse<
|
|
175
|
+
export function jsonParse<TResult = unknown>(json: string): TResult {
|
|
176
176
|
try {
|
|
177
177
|
return objNullToUndefined(
|
|
178
178
|
JSON.parse(json, (_key, value: unknown) => {
|
|
@@ -218,7 +218,7 @@ export function jsonParse<T = unknown>(json: string): T {
|
|
|
218
218
|
|
|
219
219
|
return value;
|
|
220
220
|
}),
|
|
221
|
-
) as
|
|
221
|
+
) as TResult;
|
|
222
222
|
} catch (err) {
|
|
223
223
|
if (env.DEV) {
|
|
224
224
|
throw new SdError(err, "JSON 파싱 에러: \n" + json);
|