@simplysm/core-common 13.0.84 → 13.0.86

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 CHANGED
@@ -1,35 +1,113 @@
1
1
  # @simplysm/core-common
2
2
 
3
- > Simplysm package - Core module (common)
3
+ Simplysm 프레임워크의 기반 유틸리티 패키지. 플랫폼 중립적인 타입, 유틸리티 함수, 에러 클래스, 확장 메서드를 제공한다.
4
4
 
5
- A platform-neutral (Node.js and browser) utility library providing immutable date/time types, rich Array/Map/Set extensions, typed error classes, object manipulation, serialization (JSON/XML/ZIP), and async control primitives. It serves as the leaf dependency for the entire Simplysm monorepo.
6
-
7
- ## Installation
5
+ ## 설치
8
6
 
9
7
  ```bash
10
8
  npm install @simplysm/core-common
11
9
  ```
12
10
 
13
- ## Documentation
14
-
15
- | Category | Description |
16
- |----------|-------------|
17
- | [Types](docs/types.md) | Immutable date/time classes (`DateTime`, `DateOnly`, `Time`), `Uuid`, `LazyGcMap`, and shared type aliases |
18
- | [Errors](docs/errors.md) | Error class hierarchy: `SdError`, `ArgumentError`, `NotImplementedError`, `TimeoutError` |
19
- | [Features](docs/features.md) | Async control primitives: `EventEmitter`, `DebounceQueue`, `SerialQueue` |
20
- | [Extensions](docs/extensions.md) | Prototype extensions for `Array`, `Map`, and `Set` |
21
- | [Object Utilities](docs/object-utilities.md) | `obj` namespace: `clone`, `equal`, `merge`, `merge3`, `omit`, `pick`, chain access, and type-safe Object helpers |
22
- | [String Utilities](docs/string-utilities.md) | `str` namespace: case conversion, Korean particles, full-width conversion, and string helpers |
23
- | [Number Utilities](docs/number-utilities.md) | `num` namespace: robust parsing (`parseInt`, `parseFloat`) and locale-aware formatting |
24
- | [Byte Utilities](docs/byte-utilities.md) | `bytes` namespace: `concat`, hex/base64 encode/decode for `Uint8Array` |
25
- | [Path Utilities](docs/path-utilities.md) | `path` namespace: browser-safe `join`, `basename`, `extname` (POSIX only) |
26
- | [JSON Utilities](docs/json-utilities.md) | `json` namespace: `stringify` / `parse` with custom type support (`DateTime`, `Set`, `Map`, `Error`, etc.) |
27
- | [XML Utilities](docs/xml-utilities.md) | `xml` namespace: `parse` / `stringify` via fast-xml-parser |
28
- | [Wait Utilities](docs/wait-utilities.md) | `wait` namespace: `time` (sleep) and `until` (poll with timeout) |
29
- | [Transfer Utilities](docs/transfer-utilities.md) | `transfer` namespace: Worker-safe `encode` / `decode` for custom types |
30
- | [Date Format Utilities](docs/date-format-utilities.md) | `dt` namespace: C#-style format strings and month normalization |
31
- | [Primitive Utilities](docs/primitive-utilities.md) | `primitive` namespace: runtime type-string inference |
32
- | [Error Utilities](docs/error-utilities.md) | `err` namespace: extract message from unknown catch values |
33
- | [Template Strings](docs/template-strings.md) | Tagged template literals (`js`, `ts`, `html`, `tsql`, `mysql`, `pgsql`) with auto-indent normalization |
34
- | [ZIP Archive](docs/zip-archive.md) | `ZipArchive` class for reading, writing, and compressing ZIP files |
35
- | [Environment](docs/environment.md) | `env` object exposing `DEV` and `VER` from `process.env` |
11
+ ## 의존성
12
+
13
+ - `@zip.js/zip.js` -- ZIP 파일 처리
14
+ - `consola` -- 로깅
15
+ - `fast-xml-parser` -- XML 파싱/직렬화
16
+ - `yaml` -- YAML 직렬화 (ArgumentError 메시지용)
17
+
18
+ ## 문서
19
+
20
+ | 카테고리 | 설명 |
21
+ |---------|------|
22
+ | [타입](docs/types.md) | DateTime, DateOnly, Time, Uuid, LazyGcMap 불변 타입 |
23
+ | [유틸리티](docs/utilities.md) | obj, str, num, bytes, json, xml, zip 네임스페이스 유틸리티 |
24
+ | [기능](docs/features.md) | EventEmitter, DebounceQueue, SerialQueue, 에러 클래스, 확장 메서드 |
25
+
26
+ ## 빠른 시작
27
+
28
+ ### 날짜/시간 타입
29
+
30
+ ```typescript
31
+ import { DateTime, DateOnly, Time } from "@simplysm/core-common";
32
+
33
+ const now = new DateTime();
34
+ const tomorrow = now.addDays(1);
35
+ const formatted = now.toFormatString("yyyy-MM-dd HH:mm:ss");
36
+
37
+ const today = new DateOnly();
38
+ const weekInfo = today.getWeekSeqOfYear(); // { year, weekSeq }
39
+
40
+ const time = Time.parse("14:30:00");
41
+ const later = time.addHours(2); // 24시간 래핑
42
+ ```
43
+
44
+ ### 유틸리티 함수
45
+
46
+ ```typescript
47
+ import { obj, str, json, bytes } from "@simplysm/core-common";
48
+
49
+ // 객체 딥 클론/비교/병합
50
+ const cloned = obj.clone(source);
51
+ const isEqual = obj.equal(a, b);
52
+ const merged = obj.merge(source, target);
53
+
54
+ // 문자열 변환
55
+ str.toPascalCase("hello-world"); // "HelloWorld"
56
+ str.toKebabCase("HelloWorld"); // "hello-world"
57
+
58
+ // JSON (커스텀 타입 지원)
59
+ const serialized = json.stringify({ date: new DateTime() });
60
+ const parsed = json.parse<{ date: DateTime }>(serialized);
61
+
62
+ // 바이너리
63
+ const hex = bytes.toHex(data);
64
+ const b64 = bytes.toBase64(data);
65
+ ```
66
+
67
+ ### 이벤트 에미터
68
+
69
+ ```typescript
70
+ import { EventEmitter } from "@simplysm/core-common";
71
+
72
+ const emitter = new EventEmitter<{ change: string; error: Error }>();
73
+ emitter.on("change", (data) => { /* ... */ });
74
+ emitter.emit("change", "updated");
75
+ ```
76
+
77
+ ### 배열 확장 메서드
78
+
79
+ ```typescript
80
+ import "@simplysm/core-common"; // side-effect import
81
+
82
+ const items = [1, 2, 3, 4, 5];
83
+ items.first(); // 1
84
+ items.last(); // 5
85
+ items.distinct(); // 중복 제거
86
+ items.orderBy((x) => x); // 정렬
87
+ items.groupBy((x) => x % 2); // 그룹화
88
+ items.sum(); // 15
89
+ ```
90
+
91
+ ### ZIP 처리
92
+
93
+ ```typescript
94
+ import { ZipArchive } from "@simplysm/core-common";
95
+
96
+ await using zip = new ZipArchive(data);
97
+ const content = await zip.get("file.txt");
98
+ zip.write("new-file.txt", bytes);
99
+ const compressed = await zip.compress();
100
+ ```
101
+
102
+ ### 에러 클래스
103
+
104
+ ```typescript
105
+ import { SdError, ArgumentError, TimeoutError } from "@simplysm/core-common";
106
+
107
+ // 에러 체인 (=> 구분자로 조인)
108
+ throw new SdError(originalError, "추가 컨텍스트");
109
+ // 메시지: "추가 컨텍스트 => 원본 에러 메시지"
110
+
111
+ // 인자 에러 (YAML 형식)
112
+ throw new ArgumentError({ userId: -1, name: "" });
113
+ ```
package/dist/utils/xml.js CHANGED
@@ -9,7 +9,7 @@ function parse(str, options) {
9
9
  textNodeName: "_",
10
10
  htmlEntities: true,
11
11
  isArray: (_tagName, jPath, _isLeafNode, isAttribute) => {
12
- return !isAttribute && jPath.split(".").length > 1;
12
+ return !isAttribute && typeof jPath === "string" && jPath.split(".").length > 1;
13
13
  }
14
14
  }).parse(str);
15
15
  return (options == null ? void 0 : options.stripTagPrefix) ? stripTagPrefix(result) : result;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/utils/xml.ts"],
4
- "mappings": "AAIA,SAAS,YAAY,iBAAiB;AAiB/B,SAAS,MAAM,KAAa,SAAiD;AAClF,QAAM,SAAS,IAAI,UAAU;AAAA,IAC3B,kBAAkB;AAAA,IAClB,qBAAqB;AAAA,IACrB,qBAAqB;AAAA,IACrB,qBAAqB;AAAA,IACrB,eAAe;AAAA,IACf,cAAc;AAAA,IACd,cAAc;AAAA,IACd,SAAS,CAAC,UAAkB,OAAe,aAAsB,gBAAyB;AACxF,aAAO,CAAC,eAAe,MAAM,MAAM,GAAG,EAAE,SAAS;AAAA,IACnD;AAAA,EACF,CAAC,EAAE,MAAM,GAAG;AACZ,UAAO,mCAAS,kBAAiB,eAAe,MAAM,IAAI;AAC5D;AAoBO,SAAS,UAAU,KAAc,SAAqC;AAC3E,SAAO,IAAI,WAAW;AAAA,IACpB,kBAAkB;AAAA,IAClB,qBAAqB;AAAA,IACrB,qBAAqB;AAAA,IACrB,2BAA2B;AAAA,IAC3B,cAAc;AAAA,IACd,GAAG;AAAA,EACL,CAAC,EAAE,MAAM,GAAG;AACd;AAYA,SAAS,eAAe,KAAuB;AAC7C,MAAI,MAAM,QAAQ,GAAG,GAAG;AACtB,WAAO,IAAI,IAAI,CAAC,SAAS,eAAe,IAAI,CAAC;AAAA,EAC/C;AAEA,MAAI,OAAO,QAAQ,YAAY,QAAQ,MAAM;AAC3C,UAAM,SAAkC,CAAC;AACzC,UAAM,SAAS;AAEf,eAAW,OAAO,OAAO,KAAK,MAAM,GAAG;AACrC,YAAM,QAAQ,OAAO,GAAG;AAGxB,UAAI,QAAQ,KAAK;AACf,eAAO,GAAG,IAAI;AAAA,MAChB,OAAO;AAEL,cAAM,aAAa,IAAI,QAAQ,GAAG;AAClC,cAAM,WAAW,eAAe,KAAK,IAAI,MAAM,aAAa,CAAC,IAAI;AACjE,eAAO,QAAQ,IAAI,eAAe,KAAK;AAAA,MACzC;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;",
4
+ "mappings": "AAIA,SAAS,YAAY,iBAAiB;AAiB/B,SAAS,MAAM,KAAa,SAAiD;AAClF,QAAM,SAAS,IAAI,UAAU;AAAA,IAC3B,kBAAkB;AAAA,IAClB,qBAAqB;AAAA,IACrB,qBAAqB;AAAA,IACrB,qBAAqB;AAAA,IACrB,eAAe;AAAA,IACf,cAAc;AAAA,IACd,cAAc;AAAA,IACd,SAAS,CAAC,UAAkB,OAAgB,aAAsB,gBAAyB;AACzF,aAAO,CAAC,eAAe,OAAO,UAAU,YAAY,MAAM,MAAM,GAAG,EAAE,SAAS;AAAA,IAChF;AAAA,EACF,CAAC,EAAE,MAAM,GAAG;AACZ,UAAO,mCAAS,kBAAiB,eAAe,MAAM,IAAI;AAC5D;AAoBO,SAAS,UAAU,KAAc,SAAqC;AAC3E,SAAO,IAAI,WAAW;AAAA,IACpB,kBAAkB;AAAA,IAClB,qBAAqB;AAAA,IACrB,qBAAqB;AAAA,IACrB,2BAA2B;AAAA,IAC3B,cAAc;AAAA,IACd,GAAG;AAAA,EACL,CAAC,EAAE,MAAM,GAAG;AACd;AAYA,SAAS,eAAe,KAAuB;AAC7C,MAAI,MAAM,QAAQ,GAAG,GAAG;AACtB,WAAO,IAAI,IAAI,CAAC,SAAS,eAAe,IAAI,CAAC;AAAA,EAC/C;AAEA,MAAI,OAAO,QAAQ,YAAY,QAAQ,MAAM;AAC3C,UAAM,SAAkC,CAAC;AACzC,UAAM,SAAS;AAEf,eAAW,OAAO,OAAO,KAAK,MAAM,GAAG;AACrC,YAAM,QAAQ,OAAO,GAAG;AAGxB,UAAI,QAAQ,KAAK;AACf,eAAO,GAAG,IAAI;AAAA,MAChB,OAAO;AAEL,cAAM,aAAa,IAAI,QAAQ,GAAG;AAClC,cAAM,WAAW,eAAe,KAAK,IAAI,MAAM,aAAa,CAAC,IAAI;AACjE,eAAO,QAAQ,IAAI,eAAe,KAAK;AAAA,MACzC;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;",
5
5
  "names": []
6
6
  }
package/docs/features.md CHANGED
@@ -1,88 +1,331 @@
1
- # Features
2
-
3
- Async control primitives: type-safe event emitter, debounce queue, and serial queue.
1
+ # 기능
4
2
 
5
3
  ## EventEmitter
6
4
 
5
+ 타입 안전한 이벤트 에미터. `EventTarget` 기반으로 브라우저와 Node.js 모두에서 사용 가능하다.
6
+
7
7
  ```typescript
8
- class EventEmitter<TEvents extends { [K in keyof TEvents]: unknown } = Record<string, unknown>> {
9
- on<K extends keyof TEvents & string>(type: K, listener: (data: TEvents[K]) => void): void;
10
- off<K extends keyof TEvents & string>(type: K, listener: (data: TEvents[K]) => void): void;
11
- emit<K extends keyof TEvents & string>(type: K, ...args: TEvents[K] extends void ? [] : [data: TEvents[K]]): void;
12
- listenerCount<K extends keyof TEvents & string>(type: K): number;
13
- dispose(): void;
14
- [Symbol.dispose](): void;
15
- }
8
+ import { EventEmitter } from "@simplysm/core-common";
9
+
10
+ const emitter = new EventEmitter<{
11
+ change: string;
12
+ error: Error;
13
+ ready: void;
14
+ }>();
15
+
16
+ // 리스너 등록 (동일 리스너 중복 등록 무시)
17
+ emitter.on("change", (data) => { /* data: string */ });
18
+
19
+ // 리스너 제거
20
+ emitter.off("change", listener);
21
+
22
+ // 이벤트 발생 (void 타입은 인자 없이 호출)
23
+ emitter.emit("change", "updated");
24
+ emitter.emit("ready");
25
+
26
+ // 리스너 수 확인
27
+ emitter.listenerCount("change");
28
+
29
+ // 정리 (Disposable 지원)
30
+ emitter.dispose();
31
+ // 또는
32
+ using emitter2 = new EventEmitter<{ tick: number }>();
16
33
  ```
17
34
 
18
- A type-safe event emitter that works in both browsers and Node.js. Internally implemented using `EventTarget`. Duplicate registration of the same listener to the same event is ignored.
35
+ ### 상속 패턴
19
36
 
20
- Use `void` as the event data type for events that carry no data -- `emit("done")` is called without arguments.
37
+ ```typescript
38
+ interface MyEvents {
39
+ data: string;
40
+ error: Error;
41
+ }
42
+
43
+ class MyService extends EventEmitter<MyEvents> {
44
+ process(): void {
45
+ this.emit("data", "result");
46
+ }
47
+ }
48
+ ```
21
49
 
22
50
  ---
23
51
 
24
52
  ## DebounceQueue
25
53
 
54
+ 디바운스된 비동기 작업 실행. delay 내 마지막 요청만 실행된다.
55
+
26
56
  ```typescript
27
- class DebounceQueue extends EventEmitter<{ error: SdError }> {
28
- constructor(delay?: number);
29
- run(fn: () => void | Promise<void>): void;
30
- dispose(): void;
31
- [Symbol.dispose](): void;
32
- }
33
- ```
57
+ import { DebounceQueue } from "@simplysm/core-common";
34
58
 
35
- When called multiple times within a short time, only the last request is executed. Previous requests are replaced. If `delay` is omitted, executes on the next event loop tick.
59
+ const queue = new DebounceQueue(300); // 300ms 디바운스 (생략 즉시 실행)
60
+
61
+ queue.on("error", (err) => { /* SdError */ });
62
+
63
+ // 300ms 내 마지막 호출만 실행
64
+ queue.run(async () => {
65
+ await saveData();
66
+ });
67
+
68
+ queue.dispose();
69
+ // 또는
70
+ using queue2 = new DebounceQueue(100);
71
+ ```
36
72
 
37
- Requests added during execution are processed immediately after the current execution completes (no debounce delay for mid-execution arrivals). Errors are emitted as `"error"` events; if no listener is registered, they are logged.
73
+ 동작 방식:
74
+ - delay 내 새 요청이 들어오면 이전 요청을 취소하고 새 요청만 실행
75
+ - 실행 중 새 요청이 들어오면 실행 완료 후 **즉시** 새 요청 처리 (디바운스 delay 없이)
76
+ - 에러 발생 시 "error" 이벤트 리스너가 있으면 이벤트 발생, 없으면 consola로 로깅
38
77
 
39
78
  ---
40
79
 
41
80
  ## SerialQueue
42
81
 
82
+ 순차 비동기 작업 실행. 작업 간 선택적 간격(gap) 설정 가능. 에러가 발생해도 후속 작업은 계속 실행된다.
83
+
43
84
  ```typescript
44
- class SerialQueue extends EventEmitter<{ error: SdError }> {
45
- constructor(gap?: number);
46
- run(fn: () => void | Promise<void>): void;
47
- dispose(): void;
48
- [Symbol.dispose](): void;
49
- }
85
+ import { SerialQueue } from "@simplysm/core-common";
86
+
87
+ const queue = new SerialQueue(100); // 작업 100ms 간격 (기본값: 0)
88
+
89
+ queue.on("error", (err) => { /* SdError */ });
90
+
91
+ queue.run(async () => await task1());
92
+ queue.run(async () => await task2()); // task1 완료 + 100ms 후 실행
93
+
94
+ queue.dispose(); // 대기 중인 큐 비우기 (현재 실행 중인 작업은 완료)
95
+ // 또는
96
+ using queue2 = new SerialQueue();
97
+ ```
98
+
99
+ ---
100
+
101
+ ## 에러 클래스
102
+
103
+ ### SdError
104
+
105
+ 에러 체인을 지원하는 기본 에러 클래스. ES2024 `cause` 속성을 활용한다. 메시지는 역순으로 `=>` 구분자로 조인된다.
106
+
107
+ ```typescript
108
+ import { SdError } from "@simplysm/core-common";
109
+
110
+ // 메시지만
111
+ throw new SdError("작업 실패");
112
+
113
+ // 여러 메시지 (역순 조인)
114
+ throw new SdError("하위 메시지", "상위 메시지");
115
+ // 메시지: "상위 메시지 => 하위 메시지"
116
+
117
+ // 원인 에러 래핑
118
+ throw new SdError(originalError, "추가 컨텍스트");
119
+ // 메시지: "추가 컨텍스트 => 원본 에러 메시지"
120
+ // cause 속성에 originalError 저장
121
+ // stack에 원인 에러 스택도 포함 (---- cause stack ---- 구분자)
122
+ ```
123
+
124
+ ### ArgumentError
125
+
126
+ 잘못된 인자를 YAML 형식으로 표시하는 에러. `SdError`를 상속한다.
127
+
128
+ ```typescript
129
+ import { ArgumentError } from "@simplysm/core-common";
130
+
131
+ throw new ArgumentError({ userId: -1, name: "" });
132
+ // 메시지: "Invalid arguments.\n\nuserId: -1\nname: ''"
133
+
134
+ throw new ArgumentError("유효하지 않은 입력", { userId: -1 });
135
+ // 메시지: "유효하지 않은 입력\n\nuserId: -1"
136
+ ```
137
+
138
+ ### NotImplementedError
139
+
140
+ 미구현 기능을 표시하는 에러. `SdError`를 상속한다.
141
+
142
+ ```typescript
143
+ import { NotImplementedError } from "@simplysm/core-common";
144
+
145
+ throw new NotImplementedError();
146
+ // 메시지: "Not implemented"
147
+
148
+ throw new NotImplementedError("이 기능은 아직 구현되지 않았습니다");
149
+ // 메시지: "Not implemented: 이 기능은 아직 구현되지 않았습니다"
150
+ ```
151
+
152
+ ### TimeoutError
153
+
154
+ 타임아웃 에러. `SdError`를 상속한다.
155
+
156
+ ```typescript
157
+ import { TimeoutError } from "@simplysm/core-common";
158
+
159
+ throw new TimeoutError();
160
+ // 메시지: "Waiting time exceeded"
161
+
162
+ throw new TimeoutError(3);
163
+ // 메시지: "Waiting time exceeded(3 attempts)"
164
+
165
+ throw new TimeoutError(3, "API 응답 대기 초과");
166
+ // 메시지: "Waiting time exceeded(3 attempts): API 응답 대기 초과"
167
+ ```
168
+
169
+ ---
170
+
171
+ ## 배열 확장 메서드
172
+
173
+ `import "@simplysm/core-common"` (side-effect import) 시 `Array.prototype`에 추가되는 메서드.
174
+
175
+ ### 요소 접근
176
+
177
+ ```typescript
178
+ [1, 2, 3].first(); // 1
179
+ [1, 2, 3].first((x) => x > 1); // 2
180
+ [1, 2, 3].last(); // 3
181
+ [1, 2, 3].last((x) => x < 3); // 2
182
+ [1, 2, 3].single((x) => x === 2); // 2 (복수 매칭 시 ArgumentError)
183
+ ```
184
+
185
+ ### 필터링
186
+
187
+ ```typescript
188
+ [1, null, 2, undefined].filterExists(); // [1, 2]
189
+ items.ofType("string"); // string 타입만 필터
190
+ items.ofType(MyClass); // MyClass 인스턴스만 필터
191
+ await items.filterAsync(async (x) => check(x)); // 순차 비동기 필터
192
+ ```
193
+
194
+ ### 변환
195
+
196
+ ```typescript
197
+ items.mapMany(); // 2차원 -> 1차원 평탄화 (null 제거)
198
+ items.mapMany((x) => x.children); // 매핑 후 평탄화
199
+ await items.mapAsync(async (x) => transform(x)); // 순차 비동기 매핑
200
+ await items.mapManyAsync(async (x) => getChildren(x)); // 순차 비동기 매핑 후 평탄화
201
+ await items.parallelAsync(async (x) => process(x)); // Promise.all 병렬 처리
202
+ ```
203
+
204
+ ### 그룹화/매핑
205
+
206
+ ```typescript
207
+ items.groupBy((x) => x.category); // { key, values }[]
208
+ items.groupBy((x) => x.cat, (x) => x.name); // 값 변환 포함
209
+
210
+ items.toMap((x) => x.id); // Map<id, item> (중복 키 시 ArgumentError)
211
+ items.toMap((x) => x.id, (x) => x.name); // Map<id, name>
212
+ await items.toMapAsync(async (x) => x.id); // 비동기 키 매핑
213
+
214
+ items.toArrayMap((x) => x.category); // Map<category, item[]>
215
+ items.toArrayMap((x) => x.cat, (x) => x.name); // Map<category, name[]>
216
+
217
+ items.toSetMap((x) => x.category); // Map<category, Set<item>>
218
+ items.toMapValues((x) => x.cat, (arr) => arr.length); // Map<cat, count>
219
+
220
+ items.toObject((x) => x.id); // Record<string, item>
221
+ items.toObject((x) => x.id, (x) => x.name); // Record<string, name>
222
+
223
+ items.toTree("id", "parentId"); // TreeArray<T>[] (parentId가 null인 항목이 루트)
224
+ ```
225
+
226
+ ### 정렬/고유
227
+
228
+ ```typescript
229
+ items.distinct(); // 고유 요소 (객체: 딥 비교 O(n^2))
230
+ items.distinct({ matchAddress: true }); // 참조 비교 (Set 기반 O(n))
231
+ items.distinct({ keyFn: (x) => x.id }); // 커스텀 키 (O(n))
232
+
233
+ items.orderBy((x) => x.name); // 오름차순 (null/undefined 우선)
234
+ items.orderByDesc((x) => x.score); // 내림차순
235
+
236
+ items.shuffle(); // 랜덤 셔플 (Fisher-Yates)
237
+ ```
238
+
239
+ ### 집계
240
+
241
+ ```typescript
242
+ items.sum((x) => x.price); // 합계 (빈 배열이면 0)
243
+ items.min((x) => x.date); // 최솟값 (number | string)
244
+ items.max((x) => x.date); // 최댓값 (number | string)
50
245
  ```
51
246
 
52
- Functions added to the queue are executed sequentially -- the next task starts only after the previous one completes. Subsequent tasks continue even if an error occurs. The optional `gap` parameter adds a delay between each task. Errors are emitted as `"error"` events.
247
+ ### 비교/병합
248
+
249
+ ```typescript
250
+ // 배열 diff (INSERT/DELETE/UPDATE)
251
+ items.diffs(target);
252
+ // 반환: { source, target }[]
253
+ // source만 있으면 DELETE, target만 있으면 INSERT, 둘 다 있으면 UPDATE
254
+
255
+ items.diffs(target, { keys: ["id"] }); // 키 기준 매칭
256
+ items.diffs(target, { excludes: ["updatedAt"] }); // 비교 제외 키
257
+
258
+ // 단방향 diff
259
+ items.oneWayDiffs(orgItems, "id");
260
+ items.oneWayDiffs(orgItems, (x) => x.id, { includeSame: true });
261
+ // 반환: { type: "create"|"update"|"same", item, orgItem }[]
262
+
263
+ // 배열 병합
264
+ items.merge(target);
265
+ items.merge(target, { keys: ["id"], excludes: ["updatedAt"] });
266
+ ```
267
+
268
+ ### 원본 변경 메서드 (@mutates)
269
+
270
+ ```typescript
271
+ items.distinctThis(); // 원본 배열에서 중복 제거
272
+ items.orderByThis((x) => x.name); // 원본 배열 오름차순 정렬
273
+ items.orderByDescThis((x) => x.score); // 원본 배열 내림차순 정렬
274
+ items.insert(2, newItem1, newItem2); // 인덱스 2에 삽입
275
+ items.remove(item); // 참조 일치 항목 제거
276
+ items.remove((x) => x.expired); // 조건 일치 항목 제거
277
+ items.toggle(item); // 있으면 제거, 없으면 추가
278
+ items.clear(); // 전체 제거
279
+ ```
280
+
281
+ ### 내보내는 타입
282
+
283
+ ```typescript
284
+ import type {
285
+ ArrayDiffsResult, // { source, target } 유니온
286
+ ArrayOneWayDiffResult, // { type, item, orgItem } 유니온
287
+ TreeArray, // T & { children: TreeArray<T>[] }
288
+ ComparableType, // string | number | boolean | DateTime | DateOnly | Time | undefined
289
+ } from "@simplysm/core-common";
290
+ ```
53
291
 
54
292
  ---
55
293
 
56
- ## Usage Examples
294
+ ## Map 확장 메서드
57
295
 
58
296
  ```typescript
59
- import { EventEmitter, DebounceQueue, SerialQueue } from "@simplysm/core-common";
297
+ const map = new Map<string, number[]>();
60
298
 
61
- // EventEmitter
62
- interface MyEvents {
63
- data: string;
64
- error: Error;
65
- done: void;
66
- }
299
+ // 없으면 생성 (값 또는 팩토리 함수)
300
+ map.getOrCreate("key", []); // 직접 값
301
+ map.getOrCreate("key", () => []); // 팩토리 함수
302
+ // 주의: V 타입이 함수이면 팩토리로 인식되므로 () => myFn 형태로 래핑 필요
303
+
304
+ // 값 갱신 (키가 없으면 prev는 undefined)
305
+ map.update("key", (prev) => [...(prev ?? []), 1]);
306
+ ```
307
+
308
+ ---
309
+
310
+ ## Set 확장 메서드
311
+
312
+ ```typescript
313
+ const set = new Set<string>();
314
+
315
+ set.adds("a", "b", "c"); // 다수 추가 (체이닝)
316
+
317
+ set.toggle("a"); // 있으면 제거, 없으면 추가
318
+ set.toggle("b", "add"); // 강제 추가
319
+ set.toggle("b", "del"); // 강제 제거
320
+ ```
321
+
322
+ ---
323
+
324
+ ## 환경 변수
325
+
326
+ ```typescript
327
+ import { env } from "@simplysm/core-common";
67
328
 
68
- class MyEmitter extends EventEmitter<MyEvents> {}
69
-
70
- const emitter = new MyEmitter();
71
- emitter.on("data", (data) => { /* data: string */ });
72
- emitter.emit("data", "hello");
73
- emitter.emit("done"); // void type, no arguments
74
-
75
- // DebounceQueue
76
- using queue = new DebounceQueue(300); // 300ms delay
77
- queue.on("error", (err) => { /* handle error */ });
78
- queue.run(() => { /* ignored */ });
79
- queue.run(() => { /* ignored */ });
80
- queue.run(() => { /* executed after 300ms */ });
81
-
82
- // SerialQueue
83
- using serial = new SerialQueue();
84
- serial.on("error", (err) => { /* handle error */ });
85
- serial.run(async () => { await fetch("/api/1"); });
86
- serial.run(async () => { await fetch("/api/2"); }); // runs after 1 completes
87
- serial.run(async () => { await fetch("/api/3"); }); // runs after 2 completes
329
+ env.DEV; // boolean -- 개발 모드 여부 (process.env.DEV를 JSON.parse)
330
+ env.VER; // string | undefined -- 버전 문자열 (process.env.VER)
88
331
  ```