@simplysm/core-common 14.0.49 → 14.0.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +57 -152
  2. package/dist/errors/sd-error.d.ts.map +1 -1
  3. package/dist/errors/sd-error.js +1 -1
  4. package/dist/errors/sd-error.js.map +1 -1
  5. package/dist/utils/json.js.map +1 -1
  6. package/dist/utils/obj.js.map +1 -1
  7. package/docs/errors/argument-error.md +26 -0
  8. package/docs/errors/not-implemented-error.md +33 -0
  9. package/docs/errors/sd-error.md +38 -0
  10. package/docs/errors/timeout-error.md +36 -0
  11. package/docs/extensions/array.md +125 -0
  12. package/docs/extensions/map.md +43 -0
  13. package/docs/extensions/set.md +35 -0
  14. package/docs/features/debounce-queue.md +48 -0
  15. package/docs/features/event-emitter.md +52 -0
  16. package/docs/features/serial-queue.md +44 -0
  17. package/docs/type-utils/common-types.md +100 -0
  18. package/docs/type-utils/env.md +42 -0
  19. package/docs/types/date-only.md +86 -0
  20. package/docs/types/date-time.md +106 -0
  21. package/docs/types/lazy-gc-map.md +59 -0
  22. package/docs/types/time.md +62 -0
  23. package/docs/types/uuid.md +41 -0
  24. package/docs/utils/bytes.md +36 -0
  25. package/docs/utils/dt.md +60 -0
  26. package/docs/utils/err.md +26 -0
  27. package/docs/utils/json.md +58 -0
  28. package/docs/utils/num.md +56 -0
  29. package/docs/utils/obj.md +107 -0
  30. package/docs/utils/path.md +30 -0
  31. package/docs/utils/primitive.md +28 -0
  32. package/docs/utils/str.md +63 -0
  33. package/docs/utils/template-strings.md +49 -0
  34. package/docs/utils/transfer.md +35 -0
  35. package/docs/utils/wait.md +35 -0
  36. package/docs/utils/xml.md +49 -0
  37. package/docs/utils/zip-archive.md +77 -0
  38. package/package.json +1 -1
  39. package/src/errors/sd-error.ts +1 -4
  40. package/src/utils/json.ts +1 -1
  41. package/src/utils/obj.ts +2 -2
  42. package/docs/errors.md +0 -82
  43. package/docs/extensions.md +0 -167
  44. package/docs/features.md +0 -136
  45. package/docs/types.md +0 -245
  46. package/docs/utils.md +0 -591
@@ -0,0 +1,125 @@
1
+ # Array Extensions
2
+
3
+ `@simplysm/core-common`을 import하면 `Array.prototype`에 확장 메서드가 자동 등록된다. `ReadonlyArray<T>`와 `Array<T>` 모두에 적용된다.
4
+
5
+ side-effect import로 활성화:
6
+
7
+ ```typescript
8
+ import "@simplysm/core-common";
9
+ ```
10
+
11
+ ## 불변(Immutable) 메서드
12
+
13
+ 원본 배열을 변경하지 않고 새 배열 또는 값을 반환한다.
14
+
15
+ | Method | Signature | Description |
16
+ |--------|-----------|-------------|
17
+ | `single` | `(predicate?) => T \| undefined` | 조건에 맞는 단일 요소 반환. 2개 이상이면 `ArgumentError` 발생 |
18
+ | `first` | `(predicate?) => T \| undefined` | 첫 번째 요소 반환 |
19
+ | `last` | `(predicate?) => T \| undefined` | 마지막 요소 반환 |
20
+ | `filterExists` | `() => NonNullable<T>[]` | null/undefined 제거 |
21
+ | `ofType` | `(type: PrimitiveTypeStr \| Type<N>) => N[]` | 특정 타입의 요소만 필터 |
22
+ | `filterAsync` | `(predicate) => Promise<T[]>` | 비동기 필터 (순차 실행) |
23
+ | `mapAsync` | `(selector) => Promise<R[]>` | 비동기 매핑 (순차 실행) |
24
+ | `mapMany` | `(selector?) => R[]` | 매핑 후 평탄화 (또는 중첩 배열 평탄화) |
25
+ | `mapManyAsync` | `(selector?) => Promise<R[]>` | 비동기 매핑 후 평탄화 (순차 실행) |
26
+ | `parallelAsync` | `(fn) => Promise<R[]>` | 비동기 병렬 처리 (`Promise.all` 사용). 하나라도 reject되면 전체 reject |
27
+ | `groupBy` | `(keySelector, valueSelector?) => { key, values }[]` | key 기준 그룹화. 객체 key 지원을 위해 O(n²) 복잡도. 원시 key는 O(n) |
28
+ | `toMap` | `(keySelector, valueSelector?) => Map<K, V>` | Map으로 변환. 중복 key이면 `ArgumentError` 발생 |
29
+ | `toMapAsync` | `(keySelector, valueSelector?) => Promise<Map<K, V>>` | 비동기 Map 변환 |
30
+ | `toArrayMap` | `(keySelector, valueSelector?) => Map<K, V[]>` | 배열 값 Map으로 변환 (O(n)) |
31
+ | `toSetMap` | `(keySelector, valueSelector?) => Map<K, Set<V>>` | Set 값 Map으로 변환 |
32
+ | `toMapValues` | `(keySelector, valueSelector) => Map<K, V>` | 그룹별 집계 Map으로 변환 |
33
+ | `toObject` | `(keySelector, valueSelector?) => Record<string, V>` | 객체로 변환. 중복 key이면 `ArgumentError` 발생 |
34
+ | `toTree` | `(keyProp, parentKey) => TreeArray<T>[]` | 평면 배열을 트리 구조로 변환 (O(n)). `parentKey`가 null/undefined인 항목이 루트 |
35
+ | `distinct` | `(options?) => T[]` | 중복 제거. 객체 배열에서 keyFn 없이 사용하면 O(n²) |
36
+ | `orderBy` | `(selector?) => T[]` | 오름차순 정렬 (새 배열 반환) |
37
+ | `orderByDesc` | `(selector?) => T[]` | 내림차순 정렬 (새 배열 반환) |
38
+ | `diffs` | `(target, options?) => ArrayDiffsResult<T, P>[]` | 두 배열 비교 (INSERT/DELETE/UPDATE) |
39
+ | `oneWayDiffs` | `(orgItems, keyPropNameOrGetValFn, options?) => ArrayOneWayDiffResult<T>[]` | 단방향 비교 (create/update/same) |
40
+ | `merge` | `(target, options?) => (T \| P \| T & P)[]` | 두 배열 병합 (`diffs` 기반) |
41
+ | `sum` | `(selector?) => number` | 합계. 빈 배열이면 0 반환 |
42
+ | `min` | `(selector?) => T \| undefined` | 최솟값 |
43
+ | `max` | `(selector?) => T \| undefined` | 최댓값 |
44
+ | `shuffle` | `() => T[]` | 무작위 순서로 섞은 새 배열 반환 |
45
+
46
+ ## 가변(Mutable) 메서드
47
+
48
+ 원본 배열을 직접 수정하고 `this`를 반환한다.
49
+
50
+ | Method | Signature | Description |
51
+ |--------|-----------|-------------|
52
+ | `remove` | `(item: T) => this` | 항목 제거 |
53
+ | `remove` | `(selector: (item, index) => boolean) => this` | 조건에 맞는 항목 제거 |
54
+ | `insert` | `(index: number, ...items: T[]) => this` | 특정 위치에 항목 삽입 |
55
+ | `toggle` | `(item: T) => this` | 항목 토글 (있으면 제거, 없으면 추가) |
56
+ | `clear` | `() => this` | 배열 비우기 |
57
+ | `distinctThis` | `(options?) => T[]` | 원본 배열에서 중복 제거 |
58
+ | `orderByThis` | `(selector?) => T[]` | 원본 배열 오름차순 정렬 |
59
+ | `orderByDescThis` | `(selector?) => T[]` | 원본 배열 내림차순 정렬 |
60
+
61
+ ## Related Types
62
+
63
+ ### `ArrayDiffsResult<TOriginal, TOther>`
64
+
65
+ `diffs()` 결과 타입. Discriminated union:
66
+
67
+ ```typescript
68
+ export type ArrayDiffsResult<TOriginal, TOther> =
69
+ | { source: undefined; target: TOther } // INSERT (target에만 있음)
70
+ | { source: TOriginal; target: undefined } // DELETE (source에만 있음)
71
+ | { source: TOriginal; target: TOther }; // UPDATE (양쪽에 있고 다름)
72
+ ```
73
+
74
+ ### `ArrayOneWayDiffResult<TItem>`
75
+
76
+ `oneWayDiffs()` 결과 타입. Discriminated union (`type` 필드로 분기):
77
+
78
+ ```typescript
79
+ export type ArrayOneWayDiffResult<TItem> =
80
+ | { type: "create"; item: TItem; orgItem: undefined }
81
+ | { type: "update"; item: TItem; orgItem: TItem }
82
+ | { type: "same"; item: TItem; orgItem: TItem };
83
+ ```
84
+
85
+ ### `TreeArray<TNode>`
86
+
87
+ `toTree()` 결과 타입. 원본 타입에 `children` 속성이 추가된다:
88
+
89
+ ```typescript
90
+ export type TreeArray<TNode> = TNode & { children: TreeArray<TNode>[] };
91
+ ```
92
+
93
+ ### `ComparableType`
94
+
95
+ `orderBy`/`orderByDesc` selector의 반환 타입:
96
+
97
+ ```typescript
98
+ export type ComparableType = string | number | boolean | DateTime | DateOnly | Time | undefined;
99
+ ```
100
+
101
+ ## Usage
102
+
103
+ ```typescript
104
+ import "@simplysm/core-common";
105
+
106
+ const users = [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }];
107
+
108
+ // 불변 메서드
109
+ const alice = users.single((u) => u.id === 1);
110
+ const sorted = users.orderBy((u) => u.name);
111
+ const grouped = users.groupBy((u) => u.name[0]);
112
+ const diffs = newUsers.diffs(oldUsers, { keys: ["id"] });
113
+
114
+ // toTree
115
+ const items = [
116
+ { id: 1, parentId: undefined, name: "root" },
117
+ { id: 2, parentId: 1, name: "child" },
118
+ ];
119
+ const tree = items.toTree("id", "parentId");
120
+
121
+ // 가변 메서드
122
+ const list = [1, 2, 3, 4, 5];
123
+ list.remove((x) => x % 2 === 0); // [1, 3, 5]
124
+ list.insert(1, 10); // [1, 10, 3, 5]
125
+ ```
@@ -0,0 +1,43 @@
1
+ # Map Extensions
2
+
3
+ `@simplysm/core-common`을 import하면 `Map.prototype`에 확장 메서드가 자동 등록된다.
4
+
5
+ side-effect import로 활성화:
6
+
7
+ ```typescript
8
+ import "@simplysm/core-common";
9
+ ```
10
+
11
+ ## Methods
12
+
13
+ | Method | Signature | Description |
14
+ |--------|-----------|-------------|
15
+ | `getOrCreate` | `(key: K, newValue: V) => V` | key가 없으면 값을 설정하고 반환 |
16
+ | `getOrCreate` | `(key: K, newValueFn: () => V) => V` | key가 없으면 팩토리 함수를 호출하여 값을 설정하고 반환 |
17
+ | `update` | `(key: K, updateFn: (v: V \| undefined) => V) => void` | 현재 값을 받아 새 값으로 업데이트. key가 없으면 `undefined`가 전달됨 |
18
+
19
+ ## Usage
20
+
21
+ ```typescript
22
+ import "@simplysm/core-common";
23
+
24
+ // getOrCreate — 값으로
25
+ const map = new Map<string, number[]>();
26
+ const arr = map.getOrCreate("key", []); // 없으면 [] 설정 후 반환
27
+
28
+ // getOrCreate — 팩토리로 (비용이 큰 연산에 사용)
29
+ const val = map.getOrCreate("key", () => expensiveComputation());
30
+
31
+ // 함수를 값으로 저장 시 팩토리로 감싸야 함
32
+ const fnMap = new Map<string, () => void>();
33
+ const myFn = () => console.log("hello");
34
+ fnMap.getOrCreate("key", () => myFn); // 팩토리로 감싸기
35
+
36
+ // update — 카운터
37
+ const countMap = new Map<string, number>();
38
+ countMap.update("key", (v) => (v ?? 0) + 1);
39
+
40
+ // update — 배열에 추가
41
+ const arrayMap = new Map<string, string[]>();
42
+ arrayMap.update("key", (v) => [...(v ?? []), "item"]);
43
+ ```
@@ -0,0 +1,35 @@
1
+ # Set Extensions
2
+
3
+ `@simplysm/core-common`을 import하면 `Set.prototype`에 확장 메서드가 자동 등록된다.
4
+
5
+ side-effect import로 활성화:
6
+
7
+ ```typescript
8
+ import "@simplysm/core-common";
9
+ ```
10
+
11
+ ## Methods
12
+
13
+ | Method | Signature | Description |
14
+ |--------|-----------|-------------|
15
+ | `adds` | `(...values: T[]) => this` | 여러 값을 한 번에 추가 |
16
+ | `toggle` | `(value: T, addOrDel?: "add" \| "del") => this` | 값 토글 (있으면 제거, 없으면 추가). `addOrDel`로 강제 추가/제거 가능 |
17
+
18
+ ## Usage
19
+
20
+ ```typescript
21
+ import "@simplysm/core-common";
22
+
23
+ const set = new Set<number>([1, 2, 3]);
24
+
25
+ // adds — 여러 항목 추가
26
+ set.adds(4, 5, 6); // {1, 2, 3, 4, 5, 6}
27
+
28
+ // toggle — 자동
29
+ set.toggle(2); // 2가 있으므로 제거 → {1, 3, 4, 5, 6}
30
+ set.toggle(10); // 10이 없으므로 추가 → {1, 3, 4, 5, 6, 10}
31
+
32
+ // toggle — 강제
33
+ const isAdmin = true;
34
+ set.toggle(5, isAdmin ? "add" : "del"); // 강제 추가
35
+ ```
@@ -0,0 +1,48 @@
1
+ # DebounceQueue
2
+
3
+ 비동기 디바운스 큐. 짧은 시간 내에 여러 번 호출되면 마지막 요청만 실행하고 이전 요청은 무시한다. 입력 필드 자동완성, 연속적인 상태 변경 일괄 처리 등에 유용하다. [`EventEmitter`](./event-emitter.md)를 상속한다.
4
+
5
+ ```typescript
6
+ export class DebounceQueue extends EventEmitter<{ error: SdError }> {
7
+ /**
8
+ * @param _delay 디바운스 지연 시간 (밀리초). 생략 시 즉시 실행 (다음 이벤트 루프)
9
+ */
10
+ constructor(_delay?: number);
11
+
12
+ run(fn: () => void | Promise<void>): void;
13
+ override dispose(): void;
14
+ }
15
+ ```
16
+
17
+ 에러 발생 시 `"error"` 이벤트로 전파되며, 리스너가 없으면 `consola`로 로그 출력된다.
18
+
19
+ 실행 중에 추가된 요청은 디바운스 지연 없이 현재 실행이 완료된 직후 즉시 처리된다. 이는 실행 완료 전에 도착한 요청을 놓치지 않기 위한 의도적인 설계다.
20
+
21
+ ## Members
22
+
23
+ | Member | Kind | Type | Description |
24
+ |--------|------|------|-------------|
25
+ | `run` | method | `(fn: () => void \| Promise<void>) => void` | 큐에 함수 추가. 이전에 추가된 함수가 있으면 교체됨 |
26
+ | `dispose` | method | `() => void` | 대기 중인 작업과 타이머 정리 후 부모 `dispose()` 호출 |
27
+
28
+ ## Usage
29
+
30
+ ```typescript
31
+ import { DebounceQueue } from "@simplysm/core-common";
32
+
33
+ const queue = new DebounceQueue(300); // 300ms 지연
34
+
35
+ // 에러 처리
36
+ queue.on("error", (err) => console.error(err));
37
+
38
+ queue.run(() => console.log("1")); // 무시됨
39
+ queue.run(() => console.log("2")); // 무시됨
40
+ queue.run(() => console.log("3")); // 300ms 후 실행
41
+
42
+ // 자원 정리
43
+ try {
44
+ queue.run(() => saveData());
45
+ } finally {
46
+ queue.dispose();
47
+ }
48
+ ```
@@ -0,0 +1,52 @@
1
+ # EventEmitter
2
+
3
+ 타입 안전 EventEmitter. 내부적으로 `EventTarget`을 사용하며 브라우저와 Node.js 모두에서 사용 가능하다. `events`/`eventemitter3` 대신 이 클래스를 사용한다.
4
+
5
+ ```typescript
6
+ export class EventEmitter<
7
+ TEvents extends { [K in keyof TEvents]: unknown } = Record<string, unknown>,
8
+ > {
9
+ on<TEventName extends keyof TEvents & string>(type: TEventName, listener: (data: TEvents[TEventName]) => void): void;
10
+ off<TEventName extends keyof TEvents & string>(type: TEventName, listener: (data: TEvents[TEventName]) => void): void;
11
+ emit<TEventName extends keyof TEvents & string>(type: TEventName, ...args: TEvents[TEventName] extends void ? [] : [data: TEvents[TEventName]]): void;
12
+ listenerCount<TEventName extends keyof TEvents & string>(type: TEventName): number;
13
+ dispose(): void;
14
+ }
15
+ ```
16
+
17
+ ## Members
18
+
19
+ | Member | Kind | Type | Description |
20
+ |--------|------|------|-------------|
21
+ | `on` | method | `(type, listener) => void` | 이벤트 리스너 등록. 같은 리스너를 같은 이벤트에 중복 등록하면 무시됨 |
22
+ | `off` | method | `(type, listener) => void` | 이벤트 리스너 제거 |
23
+ | `emit` | method | `(type, ...args) => void` | 이벤트 발행. `void` 타입 이벤트는 인자 없이 호출 |
24
+ | `listenerCount` | method | `(type) => number` | 특정 이벤트의 등록된 리스너 수 반환 |
25
+ | `dispose` | method | `() => void` | 모든 이벤트 리스너 제거 |
26
+
27
+ ## Usage
28
+
29
+ ```typescript
30
+ import { EventEmitter } from "@simplysm/core-common";
31
+
32
+ // 이벤트 타입 정의
33
+ interface MyEvents {
34
+ data: string;
35
+ error: Error;
36
+ done: void;
37
+ }
38
+
39
+ // EventEmitter를 상속하여 사용
40
+ class MyService extends EventEmitter<MyEvents> {
41
+ async load() {
42
+ this.emit("data", "Loading...");
43
+ this.emit("done"); // void 타입은 인자 없이 호출
44
+ }
45
+ }
46
+
47
+ const svc = new MyService();
48
+ svc.on("data", (data) => { /* data: string */ });
49
+ svc.on("done", () => { /* ... */ });
50
+ await svc.load();
51
+ svc.dispose(); // 모든 리스너 정리
52
+ ```
@@ -0,0 +1,44 @@
1
+ # SerialQueue
2
+
3
+ 비동기 직렬 큐. 큐에 추가된 함수들은 순차적으로 실행된다. 하나의 작업이 완료된 후에야 다음 작업이 시작된다. 에러가 발생해도 후속 작업은 계속 실행된다. [`EventEmitter`](./event-emitter.md)를 상속한다.
4
+
5
+ ```typescript
6
+ export class SerialQueue extends EventEmitter<{ error: SdError }> {
7
+ /**
8
+ * @param _gap 각 작업 사이의 간격 (ms). 기본값: 0
9
+ */
10
+ constructor(_gap?: number);
11
+
12
+ run(fn: () => void | Promise<void>): void;
13
+ override dispose(): void;
14
+ }
15
+ ```
16
+
17
+ 에러 발생 시 `"error"` 이벤트로 전파되며, 리스너가 없으면 `consola`로 로그 출력된다.
18
+
19
+ ## Members
20
+
21
+ | Member | Kind | Type | Description |
22
+ |--------|------|------|-------------|
23
+ | `run` | method | `(fn: () => void \| Promise<void>) => void` | 큐에 함수 추가하고 실행 예약 |
24
+ | `dispose` | method | `() => void` | 대기 중인 큐 비우기 (현재 실행 중인 작업은 완료됨) |
25
+
26
+ ## Usage
27
+
28
+ ```typescript
29
+ import { SerialQueue } from "@simplysm/core-common";
30
+
31
+ const queue = new SerialQueue();
32
+
33
+ // 에러 처리
34
+ queue.on("error", (err) => console.error(err));
35
+
36
+ queue.run(async () => { await fetch("/api/1"); });
37
+ queue.run(async () => { await fetch("/api/2"); }); // 1 완료 후 실행
38
+ queue.run(async () => { await fetch("/api/3"); }); // 2 완료 후 실행
39
+
40
+ // 간격 있는 큐
41
+ const gapQueue = new SerialQueue(100); // 각 작업 사이 100ms 간격
42
+ gapQueue.run(() => step1());
43
+ gapQueue.run(() => step2()); // step1 완료 후 100ms 뒤에 실행
44
+ ```
@@ -0,0 +1,100 @@
1
+ # Common Types
2
+
3
+ 공유 타입 정의. `Buffer` 대신 사용하는 바이너리 타입, ORM과 공유하는 원시 타입, 범용 유틸리티 타입을 제공한다.
4
+
5
+ ```typescript
6
+ import { Bytes, PrimitiveTypeMap, PrimitiveTypeStr, PrimitiveType, DeepPartial, Type } from "@simplysm/core-common";
7
+ ```
8
+
9
+ ## Types
10
+
11
+ ### `Bytes`
12
+
13
+ ```typescript
14
+ export type Bytes = Uint8Array;
15
+ ```
16
+
17
+ `Buffer` 대신 사용하는 바이너리 타입 별칭.
18
+
19
+ ### `PrimitiveTypeMap`
20
+
21
+ ```typescript
22
+ export type PrimitiveTypeMap = {
23
+ string: string;
24
+ number: number;
25
+ boolean: boolean;
26
+ DateTime: DateTime;
27
+ DateOnly: DateOnly;
28
+ Time: Time;
29
+ Uuid: Uuid;
30
+ Bytes: Bytes;
31
+ };
32
+ ```
33
+
34
+ 원시 타입 문자열 key → 실제 타입 매핑. `@simplysm/orm-common`과 공유한다.
35
+
36
+ ### `PrimitiveTypeStr`
37
+
38
+ ```typescript
39
+ export type PrimitiveTypeStr = keyof PrimitiveTypeMap;
40
+ // "string" | "number" | "boolean" | "DateTime" | "DateOnly" | "Time" | "Uuid" | "Bytes"
41
+ ```
42
+
43
+ 원시 타입 문자열 key union.
44
+
45
+ ### `PrimitiveType`
46
+
47
+ ```typescript
48
+ export type PrimitiveType = PrimitiveTypeMap[PrimitiveTypeStr] | undefined;
49
+ // string | number | boolean | DateTime | DateOnly | Time | Uuid | Bytes | undefined
50
+ ```
51
+
52
+ 원시 타입 union.
53
+
54
+ ### `DeepPartial<TObject>`
55
+
56
+ ```typescript
57
+ export type DeepPartial<TObject> = Partial<{
58
+ [K in keyof TObject]: TObject[K] extends PrimitiveType ? TObject[K] : DeepPartial<TObject[K]>;
59
+ }>;
60
+ ```
61
+
62
+ 객체의 모든 속성을 재귀적으로 optional로 변환한다. 원시 타입(`PrimitiveType`)은 그대로 유지하고, object/array 타입에만 재귀적으로 `Partial`을 적용한다.
63
+
64
+ ### `Type<TInstance>`
65
+
66
+ ```typescript
67
+ export interface Type<TInstance> extends Function {
68
+ new (...args: unknown[]): TInstance;
69
+ }
70
+ ```
71
+
72
+ 생성자 타입. 의존성 주입, 팩토리 패턴, instanceof 체크 등에 활용한다.
73
+
74
+ ## Usage
75
+
76
+ ```typescript
77
+ import { Bytes, PrimitiveTypeStr, DeepPartial, Type } from "@simplysm/core-common";
78
+
79
+ // Bytes
80
+ const data: Bytes = new Uint8Array([1, 2, 3]);
81
+
82
+ // PrimitiveTypeStr
83
+ const typeStr: PrimitiveTypeStr = "DateTime";
84
+
85
+ // DeepPartial
86
+ interface Config {
87
+ server: { host: string; port: number };
88
+ db: { name: string; user: string };
89
+ }
90
+ const partial: DeepPartial<Config> = {
91
+ server: { host: "localhost" }, // port 생략 가능
92
+ };
93
+
94
+ // Type<T>
95
+ function create<T>(ctor: Type<T>): T {
96
+ return new ctor();
97
+ }
98
+ class MyService {}
99
+ const svc = create(MyService);
100
+ ```
@@ -0,0 +1,42 @@
1
+ # env / parseBoolEnv
2
+
3
+ 환경변수 접근 유틸리티 함수. `process.env`/`import.meta.env` 직접 접근 대신 이 함수를 사용해야 한다.
4
+
5
+ ```typescript
6
+ export function env(key: string): string | undefined;
7
+ export function env(key: string, value: string): void;
8
+
9
+ export function parseBoolEnv(value: unknown): boolean;
10
+ ```
11
+
12
+ 직접 named import로 사용한다:
13
+
14
+ ```typescript
15
+ import { env, parseBoolEnv } from "@simplysm/core-common";
16
+ ```
17
+
18
+ ## Functions
19
+
20
+ | Function | Signature | Description |
21
+ |----------|-----------|-------------|
22
+ | `env` | `(key: string) => string \| undefined` | 환경변수 값 읽기. `process.env` 우선, fallback `import.meta.env` |
23
+ | `env` | `(key: string, value: string) => void` | 환경변수 값 쓰기 (`process.env`에 저장) |
24
+ | `parseBoolEnv` | `(value: unknown) => boolean` | 환경변수 값을 boolean으로 파싱. `"true"`, `"1"`, `"yes"`, `"on"` (대소문자 무시) → `true`, 그 외 → `false` |
25
+
26
+ ## Usage
27
+
28
+ ```typescript
29
+ import { env, parseBoolEnv } from "@simplysm/core-common";
30
+
31
+ // 읽기
32
+ const apiUrl = env("API_URL"); // string | undefined
33
+
34
+ // 쓰기
35
+ env("DEBUG", "true");
36
+
37
+ // boolean 파싱
38
+ parseBoolEnv(env("DEBUG")); // true
39
+ parseBoolEnv("yes"); // true
40
+ parseBoolEnv("false"); // false
41
+ parseBoolEnv("0"); // false
42
+ ```
@@ -0,0 +1,86 @@
1
+ # DateOnly
2
+
3
+ 불변 날짜 클래스 (시간 제외: `yyyy-MM-dd`). 시간 정보 없이 날짜만 저장하며 로컬 타임존 기준으로 동작한다. 주차 계산을 지원한다. 수정 메서드는 모두 새 인스턴스를 반환한다.
4
+
5
+ ```typescript
6
+ export class DateOnly {
7
+ readonly date: Date;
8
+
9
+ constructor();
10
+ constructor(year: number, month: number, day: number);
11
+ constructor(tick: number);
12
+ constructor(date: Date);
13
+
14
+ static parse(str: string): DateOnly;
15
+ static getDateByYearWeekSeq(
16
+ arg: { year: number; month?: number; weekSeq: number },
17
+ weekStartDay?: number,
18
+ minDaysInFirstWeek?: number,
19
+ ): DateOnly;
20
+ }
21
+ ```
22
+
23
+ ## Members
24
+
25
+ | Member | Kind | Type | Description |
26
+ |--------|------|------|-------------|
27
+ | `date` | property | `Date` | 내부 Date 객체 (시간 부분은 항상 00:00:00) |
28
+ | `year` | getter | `number` | 연도 |
29
+ | `month` | getter | `number` | 월 (1-12) |
30
+ | `day` | getter | `number` | 일 (1-31) |
31
+ | `tick` | getter | `number` | Unix 타임스탬프 (밀리초) |
32
+ | `dayOfWeek` | getter | `number` | 요일 (일요일=0 ~ 토요일=6) |
33
+ | `isValid` | getter | `boolean` | 날짜가 올바르게 설정되었는지 여부 |
34
+ | `parse` | static | `(str: string) => DateOnly` | 문자열을 파싱하여 DateOnly 생성 |
35
+ | `getDateByYearWeekSeq` | static | `(arg, weekStartDay?, minDaysInFirstWeek?) => DateOnly` | 연도·(월)·주차로 해당 주의 시작 날짜 반환 |
36
+ | `setYear` | method | `(year: number) => DateOnly` | 지정된 연도로 새 인스턴스 반환 |
37
+ | `setMonth` | method | `(month: number) => DateOnly` | 지정된 월로 새 인스턴스 반환. 현재 일이 대상 월의 일수보다 크면 마지막 일로 조정됨 |
38
+ | `setDay` | method | `(day: number) => DateOnly` | 지정된 일로 새 인스턴스 반환 |
39
+ | `addYears` | method | `(years: number) => DateOnly` | 지정된 연수를 더한 새 인스턴스 반환 |
40
+ | `addMonths` | method | `(months: number) => DateOnly` | 지정된 월수를 더한 새 인스턴스 반환 |
41
+ | `addDays` | method | `(days: number) => DateOnly` | 지정된 일수를 더한 새 인스턴스 반환 |
42
+ | `getBaseYearMonthSeqForWeekSeq` | method | `(weekStartDay?, minDaysInFirstWeek?) => { year: number; monthSeq: number }` | 이 날짜가 포함된 주의 기준 연도와 월 반환 |
43
+ | `getWeekSeqStartDate` | method | `(weekStartDay?, minDaysInFirstWeek?) => DateOnly` | 이 날짜가 포함된 주의 시작 날짜 반환 |
44
+ | `getWeekSeqOfYear` | method | `(weekStartDay?, minDaysInFirstWeek?) => { year: number; weekSeq: number }` | 연도와 주차 번호 반환 |
45
+ | `getWeekSeqOfMonth` | method | `(weekStartDay?, minDaysInFirstWeek?) => { year: number; monthSeq: number; weekSeq: number }` | 연도, 월, 해당 월 내의 주차 번호 반환 |
46
+ | `toFormatString` | method | `(formatStr: string) => string` | 지정된 형식 문자열로 변환 (형식 토큰은 [`DateTime`](./date-time.md) 참조) |
47
+ | `toString` | method | `() => string` | `"yyyy-MM-dd"` 형식 문자열 반환 |
48
+
49
+ ## `parse` — 지원 형식
50
+
51
+ | 형식 | 예시 | 타임존 |
52
+ |------|------|--------|
53
+ | `yyyy-MM-dd` | `"2025-01-15"` | 타임존 무관 (직접 추출) |
54
+ | `yyyyMMdd` | `"20250115"` | 타임존 무관 (직접 추출) |
55
+ | ISO 8601 | `"2025-01-15T00:00:00Z"` | UTC로 해석 후 로컬 타임존 변환 |
56
+
57
+ 서버/클라이언트 타임존이 다른 경우 `yyyy-MM-dd` 형식 권장.
58
+
59
+ ## 주차 계산 파라미터
60
+
61
+ | 파라미터 | 기본값 | 설명 |
62
+ |----------|--------|------|
63
+ | `weekStartDay` | `1` (월요일) | 주 시작 요일 (0=일요일, 1=월요일, ..., 6=토요일) |
64
+ | `minDaysInFirstWeek` | `4` | 첫 번째 주로 간주되기 위한 최소 일수 (ISO 8601 표준) |
65
+
66
+ ## Usage
67
+
68
+ ```typescript
69
+ import { DateOnly } from "@simplysm/core-common";
70
+
71
+ const today = new DateOnly();
72
+ const specific = new DateOnly(2025, 1, 15);
73
+ const parsed = DateOnly.parse("2025-01-15");
74
+
75
+ // 불변 수정
76
+ const nextWeek = today.addDays(7);
77
+ const firstDayOfMonth = today.setDay(1);
78
+
79
+ // 주차 계산 (ISO 8601 기본값: 월요일 시작, 첫 주 최소 4일)
80
+ const { year, weekSeq } = new DateOnly(2025, 1, 6).getWeekSeqOfYear();
81
+ // { year: 2025, weekSeq: 2 }
82
+
83
+ // 주차로 날짜 계산
84
+ const weekStart = DateOnly.getDateByYearWeekSeq({ year: 2025, weekSeq: 2 });
85
+ // 2025-01-06 (월요일)
86
+ ```