@simplysm/sd-claude 14.0.88 → 14.0.89
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/claude/references/sd-simplysm14/README.md +17 -17
- package/claude/references/sd-simplysm14/apis/angular/README.md +27 -53
- package/claude/references/sd-simplysm14/apis/angular/controls.md +37 -105
- package/claude/references/sd-simplysm14/apis/angular/crud.md +46 -43
- package/claude/references/sd-simplysm14/apis/angular/directives.md +22 -32
- package/claude/references/sd-simplysm14/apis/angular/features.md +40 -55
- package/claude/references/sd-simplysm14/apis/angular/infra.md +40 -40
- package/claude/references/sd-simplysm14/apis/angular/layout.md +25 -53
- package/claude/references/sd-simplysm14/apis/angular/overlay.md +70 -82
- package/claude/references/sd-simplysm14/apis/angular/routing-appstructure.md +44 -39
- package/claude/references/sd-simplysm14/apis/angular/selection-managers.md +21 -36
- package/claude/references/sd-simplysm14/apis/angular/shared-data.md +52 -65
- package/claude/references/sd-simplysm14/apis/angular/sheet.md +65 -70
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +33 -35
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-file-system/README.md +7 -7
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +29 -29
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +45 -50
- package/claude/references/sd-simplysm14/apis/core-browser/README.md +42 -55
- package/claude/references/sd-simplysm14/apis/core-browser/dom-element.md +62 -0
- package/claude/references/sd-simplysm14/apis/core-browser/indexed-db.md +13 -12
- package/claude/references/sd-simplysm14/apis/core-common/README.md +222 -98
- package/claude/references/sd-simplysm14/apis/core-common/array-ext.md +102 -53
- package/claude/references/sd-simplysm14/apis/core-common/async-runtime.md +128 -0
- package/claude/references/sd-simplysm14/apis/core-common/datetime.md +98 -64
- package/claude/references/sd-simplysm14/apis/core-common/errors.md +91 -0
- package/claude/references/sd-simplysm14/apis/core-common/json-transfer.md +34 -28
- package/claude/references/sd-simplysm14/apis/core-common/obj.md +104 -40
- package/claude/references/sd-simplysm14/apis/core-node/README.md +11 -8
- package/claude/references/sd-simplysm14/apis/core-node/consola.md +23 -31
- package/claude/references/sd-simplysm14/apis/core-node/cpx.md +33 -22
- package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +28 -25
- package/claude/references/sd-simplysm14/apis/core-node/fsx.md +39 -53
- package/claude/references/sd-simplysm14/apis/core-node/pathx.md +26 -29
- package/claude/references/sd-simplysm14/apis/core-node/worker.md +27 -29
- package/claude/references/sd-simplysm14/apis/excel/README.md +14 -14
- package/claude/references/sd-simplysm14/apis/lint/README.md +27 -21
- package/claude/references/sd-simplysm14/apis/lint/rules.md +89 -49
- package/claude/references/sd-simplysm14/apis/orm-common/README.md +5 -59
- package/claude/references/sd-simplysm14/apis/orm-common/db-context.md +98 -67
- package/claude/references/sd-simplysm14/apis/orm-common/expr.md +107 -92
- package/claude/references/sd-simplysm14/apis/orm-common/queryable.md +99 -65
- package/claude/references/sd-simplysm14/apis/orm-common/schema.md +83 -98
- package/claude/references/sd-simplysm14/apis/orm-common/types.md +62 -52
- package/claude/references/sd-simplysm14/apis/orm-node/README.md +62 -25
- package/claude/references/sd-simplysm14/apis/orm-node/db-conn.md +27 -27
- package/claude/references/sd-simplysm14/apis/sd-cli/README.md +12 -15
- package/claude/references/sd-simplysm14/apis/sd-cli/SdTsCompiler.md +92 -45
- package/claude/references/sd-simplysm14/apis/sd-cli/sd-config-types.md +226 -108
- package/claude/references/sd-simplysm14/apis/service-client/README.md +84 -86
- package/claude/references/sd-simplysm14/apis/service-client/orm.md +14 -11
- package/claude/references/sd-simplysm14/apis/service-client/transport.md +33 -10
- package/claude/references/sd-simplysm14/apis/service-common/README.md +37 -23
- package/claude/references/sd-simplysm14/apis/service-common/app-structure.md +9 -9
- package/claude/references/sd-simplysm14/apis/service-common/protocol.md +13 -13
- package/claude/references/sd-simplysm14/apis/service-server/README.md +81 -65
- package/claude/references/sd-simplysm14/apis/service-server/service-authoring.md +32 -35
- package/claude/references/sd-simplysm14/apis/service-server/transport-internals.md +44 -33
- package/claude/references/sd-simplysm14/apis/service-server/v1-legacy.md +34 -45
- package/claude/references/sd-simplysm14/apis/storage/README.md +24 -18
- package/claude/skills/sd-demo/SKILL.md +6 -0
- package/claude/skills/sd-impl/SKILL.md +4 -7
- package/claude/skills/sd-spec/SKILL.md +31 -858
- package/claude/skills/sd-spec/references/spec-authoring.md +519 -0
- package/claude/workflows/sd-docs.js +84 -0
- package/package.json +1 -1
- package/claude/references/sd-simplysm14/apis/orm-common/query-builder.md +0 -29
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/.specs/inventory/spec.md +0 -99
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/package.json +0 -12
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/index.ts +0 -3
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/screens/inbound/inbound.list.ts +0 -150
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/screens/inventory/inventory-master.list.ts +0 -143
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/screens/outbound/outbound.list.ts +0 -150
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/pnpm-workspace.yaml +0 -2
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/sd.config.ts +0 -12
- package/claude/skills/sd-demo/evals/golden.jsonl +0 -1
- package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/package.json +0 -8
- package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/src/.gitkeep +0 -0
- package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/tests/.gitkeep +0 -0
- package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/tsconfig.json +0 -10
- package/claude/skills/sd-dev/evals/golden.jsonl +0 -1
- package/claude/skills/sd-docs/SKILL.md +0 -46
- package/claude/skills/sd-docs/evals/fixtures/new-write/.claude/references/sd-simplysm14/README.md +0 -7
- package/claude/skills/sd-docs/evals/fixtures/new-write/packages/bar/package.json +0 -5
- package/claude/skills/sd-docs/evals/fixtures/new-write/packages/bar/src/index.ts +0 -3
- package/claude/skills/sd-docs/evals/fixtures/new-write/packages/baz/package.json +0 -6
- package/claude/skills/sd-docs/evals/fixtures/new-write/packages/baz/src/index.ts +0 -1
- package/claude/skills/sd-docs/evals/fixtures/new-write/packages/foo/package.json +0 -5
- package/claude/skills/sd-docs/evals/fixtures/new-write/packages/foo/src/index.ts +0 -8
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/.claude/references/sd-simplysm14/README.md +0 -7
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/.claude/references/sd-simplysm14/apis/foo/README.md +0 -3
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/bar/package.json +0 -5
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/bar/src/index.ts +0 -3
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/baz/package.json +0 -6
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/baz/src/index.ts +0 -1
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/foo/package.json +0 -5
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/foo/src/index.ts +0 -8
- package/claude/skills/sd-docs/evals/golden.jsonl +0 -2
- package/claude/skills/sd-impl/evals/fixtures/case-a-new-screen/.specs/260513120000_warehouse/spec.md +0 -101
- package/claude/skills/sd-impl/evals/fixtures/case-b-update-with-demo/.specs/260513120000_warehouse/spec.md +0 -101
- package/claude/skills/sd-impl/evals/fixtures/case-b-update-with-demo/packages/app/src/screens/box-register/box-register.view.ts +0 -46
- package/claude/skills/sd-impl/evals/fixtures/case-c-new-cross/.specs/260513120000_warehouse/spec.md +0 -89
- package/claude/skills/sd-impl/evals/fixtures/case-d-spec-modify/.specs/260513120000_warehouse/spec.md +0 -101
- package/claude/skills/sd-impl/evals/golden.jsonl +0 -4
- package/claude/skills/sd-manual/evals/fixtures/new-manual/src/notification.ts +0 -25
- package/claude/skills/sd-manual/evals/fixtures/update-manual/.claude/references/sd-simplysm14/manuals/notification.md +0 -14
- package/claude/skills/sd-manual/evals/fixtures/update-manual/src/notification.ts +0 -37
- package/claude/skills/sd-manual/evals/golden.jsonl +0 -2
- package/claude/skills/sd-review/evals/fixtures/code-review/src/foo.ts +0 -7
- package/claude/skills/sd-review/evals/fixtures/doc-review/docs/foo.md +0 -4
- package/claude/skills/sd-review/evals/golden.jsonl +0 -2
- package/claude/skills/sd-skill/evals/fixtures/existing-skill/.claude/skills/todo-format/SKILL.md +0 -14
- package/claude/skills/sd-skill/evals/fixtures/new-skill/.gitkeep +0 -0
- package/claude/skills/sd-skill/evals/golden.jsonl +0 -2
- package/claude/skills/sd-spec/evals/fixtures/case-a-split//355/232/214/354/235/230/353/241/235.md +0 -20
- package/claude/skills/sd-spec/evals/fixtures/case-b-detail/.specs/260513120000_warehouse/spec.md +0 -95
- package/claude/skills/sd-spec/evals/golden.jsonl +0 -2
- package/claude/skills/sd-unpack/evals/fixtures/eml-with-text-attachment/meeting.eml +0 -21
- package/claude/skills/sd-unpack/evals/fixtures/simple-eml/meeting.eml +0 -10
- package/claude/skills/sd-unpack/evals/golden.jsonl +0 -2
- package/claude/skills/sd-use/evals/fixtures/empty/.gitkeep +0 -0
- package/claude/skills/sd-use/evals/golden.jsonl +0 -6
- /package/claude/{skills/sd-docs/references/doc-rules.md → workflows/sd-docs.rules.md} +0 -0
|
@@ -1,72 +1,121 @@
|
|
|
1
|
-
# @simplysm/core-common —
|
|
1
|
+
# @simplysm/core-common — Array 확장 메서드
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
패키지를 import 하면 부수효과로 `Array.prototype` 에 메서드가 주입된다(`enumerable: false`, `for...in` 비노출). 함수 호출이 아니라 배열 인스턴스 메서드로 직접 사용. 조회/그룹/정렬/diff 등 읽기 메서드(`ReadonlyArrayExt`)는 새 배열을 반환하고, 변형 메서드(`MutableArrayExt`, 이름에 `This` 가 붙거나 insert/remove/toggle/clear)는 원본을 직접 수정한다.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 조회·필터
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
```typescript
|
|
8
|
+
single(predicate?): TItem | undefined; // 조건 일치 1건. 2건 이상이면 ArgumentError
|
|
9
|
+
first(predicate?): TItem | undefined; // 첫 일치(없으면 undefined)
|
|
10
|
+
last(predicate?): TItem | undefined; // 마지막 일치
|
|
11
|
+
filterExists(): NonNullable<TItem>[]; // null/undefined 제거
|
|
12
|
+
ofType(type: PrimitiveTypeStr | Type<T>): T[]; // 타입별 필터
|
|
13
|
+
filterAsync(predicate: (item, i) => Promise<boolean>): Promise<TItem[]>; // 순차 비동기 필터
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
- `single(predicate)` — "정확히 1건" 보장이 필요할 때. 조건 일치가 2건 이상이면 `ArgumentError` throw, 0건이면 `undefined`. predicate 생략 시 배열 전체 대상.
|
|
17
|
+
- `first`/`last` — predicate 생략 시 각각 `[0]`/마지막 요소.
|
|
18
|
+
- `ofType(type)` — `"string"|"number"|"boolean"|"DateTime"|"DateOnly"|"Time"|"Uuid"|"Bytes"` 문자열이면 해당 원시 타입으로, 생성자(`Type<N>`)면 `instanceof`/constructor 일치로 필터. 혼합 배열에서 특정 타입만 뽑을 때.
|
|
19
|
+
- `filterAsync` — predicate 가 Promise 인 경우. 병렬이 아니라 **순차** 실행(부수효과 순서 보장).
|
|
14
20
|
|
|
15
|
-
|
|
16
|
-
- `filterAsync(predicate)` / `mapAsync(selector)` — 순차 실행(await 보장). 호출 순서가 중요할 때.
|
|
17
|
-
- `parallelAsync(fn)` — `Promise.all` 병렬. 하나라도 reject 면 전체 즉시 reject.
|
|
18
|
-
- `mapMany(selector?)` — 매핑 후 1단계 평탄화 + `filterExists`. selector 생략 시 자기 자신 평탄화.
|
|
19
|
-
- `mapManyAsync(selector?)` — `mapMany` 의 비동기(순차) 버전.
|
|
21
|
+
## 매핑·평탄화
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
```typescript
|
|
24
|
+
mapAsync(selector: (item, i) => Promise<R>): Promise<R[]>; // 순차
|
|
25
|
+
parallelAsync(fn: (item, i) => Promise<R>): Promise<R[]>; // Promise.all 병렬
|
|
26
|
+
mapMany(): U[]; // 중첩 배열 1단 평탄화
|
|
27
|
+
mapMany(selector: (item, i) => R[]): R[]; // 매핑 후 평탄화
|
|
28
|
+
mapManyAsync(selector: (item, i) => Promise<R[]>): Promise<R[]>; // 순차 매핑 후 평탄화
|
|
29
|
+
```
|
|
22
30
|
|
|
23
|
-
- `
|
|
24
|
-
- `
|
|
25
|
-
- `toMapAsync(...)` — `toMap` 의 비동기(순차) 버전. selector 가 Promise 반환 가능.
|
|
26
|
-
- `toArrayMap(keySelector, valueSelector?)` — `Map<K, V[]>`. 같은 key 값들을 배열로 누적.
|
|
27
|
-
- `toSetMap(keySelector, valueSelector?)` — `Map<K, Set<V>>`. 값 중복 자동 제거.
|
|
28
|
-
- `toMapValues(keySelector, valueSelector)` — key 별로 모은 `items[]` 를 `valueSelector(items)` 로 집계해 `Map<K,V>`.
|
|
29
|
-
- `toObject(keySelector, valueSelector?)` — `Record<string,V>`. key(문자열) 중복 시 `ArgumentError`(단 기존 값이 null 이면 덮어쓰기 허용).
|
|
30
|
-
- `toTree(keyProp, parentKey)` — 평면 배열 → 트리. `parentKey` 가 null/undefined 인 항목이 루트, 각 노드에 `children` 추가(원본은 clone). 반환 `TreeArray<T>[]`.
|
|
31
|
+
- `mapAsync` vs `parallelAsync` — 둘 다 비동기 매핑이나 `mapAsync` 는 한 건씩 순차, `parallelAsync` 는 `Promise.all` 동시 실행. `parallelAsync` 는 하나라도 reject 되면 전체 즉시 reject.
|
|
32
|
+
- `mapMany` — 평탄화 시 내부적으로 `filterExists` 가 적용되어 null/undefined 도 제거됨. selector 없으면 자신을 1단 flat.
|
|
31
33
|
|
|
32
|
-
##
|
|
34
|
+
## 그룹화·Map/객체 변환
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
```typescript
|
|
37
|
+
groupBy(keySelector, valueSelector?): { key; values }[];
|
|
38
|
+
toMap(keySelector, valueSelector?): Map<K, V>;
|
|
39
|
+
toMapAsync(keySelector, valueSelector?): Promise<Map<K, V>>;
|
|
40
|
+
toArrayMap(keySelector, valueSelector?): Map<K, V[]>;
|
|
41
|
+
toSetMap(keySelector, valueSelector?): Map<K, Set<V>>;
|
|
42
|
+
toMapValues(keySelector, valueSelector: (items) => V): Map<K, V>;
|
|
43
|
+
toObject(keySelector: (item, i) => string, valueSelector?): Record<string, V>;
|
|
44
|
+
```
|
|
39
45
|
|
|
40
|
-
|
|
46
|
+
- `groupBy(keySelector)` — `{ key, values }` 배열. 원시 key 는 Map 으로 O(n), 객체 key 는 깊은 비교로 O(n²). 원시 key 만 필요하면 `toArrayMap` 이 항상 O(n).
|
|
47
|
+
- `toMap` — key→단일 값. key 중복 시 뒤 값이 덮어씀. `toArrayMap`/`toSetMap` — key→배열/Set(다대일 집계).
|
|
48
|
+
- `toMapValues(keySelector, valueSelector)` — 같은 key 의 항목 배열을 받아 단일 값으로 집계(`(items) => items.sum(...)` 등).
|
|
49
|
+
- `toObject` — key 가 반드시 string. `valueSelector` 생략 시 값은 원소 자체.
|
|
41
50
|
|
|
42
|
-
|
|
43
|
-
- `distinctThis(options?)` — 원본에서 중복 제거(역순 splice).
|
|
44
|
-
- `orderByThis(selector?)` / `orderByDescThis(selector?)` — 원본 in-place 정렬.
|
|
45
|
-
- `insert(index, ...items)` — 지정 위치 삽입.
|
|
46
|
-
- `remove(itemOrSelector)` — 값 일치 또는 조건 함수에 맞는 항목 전부 제거(역순 순회).
|
|
47
|
-
- `toggle(item)` — 있으면 제거, 없으면 push.
|
|
48
|
-
- `clear()` — 전부 비움.
|
|
51
|
+
## 트리·중복·정렬
|
|
49
52
|
|
|
50
|
-
|
|
53
|
+
```typescript
|
|
54
|
+
toTree(keyProp: K, parentKey: P): TreeArray<TItem>[]; // TreeArray<T> = T & { children: TreeArray<T>[] }
|
|
55
|
+
distinct(options?: boolean | { matchAddress?: boolean; keyFn?: (item) => string | number }): TItem[];
|
|
56
|
+
orderBy(selector?): TItem[];
|
|
57
|
+
orderByDesc(selector?): TItem[];
|
|
58
|
+
```
|
|
51
59
|
|
|
52
|
-
- `
|
|
53
|
-
- `
|
|
60
|
+
- `toTree(keyProp, parentKey)` — 평면 배열을 `children` 트리로. `parentKey` 값이 null/undefined 인 항목이 루트. 내부 `toArrayMap` 사용 O(n), 원본은 복사되고 `children` 추가.
|
|
61
|
+
- `distinct(options)` — 중복 제거. `true`/`{matchAddress:true}` 면 참조(Set) 비교, `keyFn` 지정 시 key 기준 O(n). 객체 배열을 옵션 없이 쓰면 깊은 비교 O(n²)(대량 데이터엔 `keyFn` 권장).
|
|
62
|
+
- `orderBy`/`orderByDesc(selector)` — selector 반환 타입은 `string|number|DateOnly|DateTime|Time|undefined`. 새 배열 반환. 결측은 비교에서 그대로 처리.
|
|
54
63
|
|
|
55
|
-
|
|
64
|
+
```typescript
|
|
65
|
+
const tree = items.toTree("id", "parentId");
|
|
66
|
+
const uniq = users.distinct({ keyFn: (u) => u.id });
|
|
67
|
+
```
|
|
56
68
|
|
|
57
|
-
|
|
58
|
-
- `update(key, updateFn)` — 현재 값(`v | undefined`)을 받아 새 값을 설정. key 가 없어도 `updateFn(undefined)` 호출됨. 카운터 증가·배열 누적 등에.
|
|
69
|
+
## diff·merge
|
|
59
70
|
|
|
60
|
-
|
|
71
|
+
```typescript
|
|
72
|
+
type ArrayDiffsResult<O, T> =
|
|
73
|
+
| { source: undefined; target: T } // INSERT
|
|
74
|
+
| { source: O; target: undefined } // DELETE
|
|
75
|
+
| { source: O; target: T }; // UPDATE
|
|
76
|
+
type ArrayOneWayDiffResult<T> =
|
|
77
|
+
| { type: "create"; item: T; orgItem: undefined }
|
|
78
|
+
| { type: "update"; item: T; orgItem: T }
|
|
79
|
+
| { type: "same"; item: T; orgItem: T };
|
|
80
|
+
|
|
81
|
+
diffs(target, options?): ArrayDiffsResult<TItem, TOther>[];
|
|
82
|
+
oneWayDiffs(orgItems, keyPropNameOrGetValFn, options?): ArrayOneWayDiffResult<TItem>[];
|
|
83
|
+
merge(target, options?): (TItem | TOther | (TItem & TOther))[];
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
- `diffs(target, options)` — 두 배열을 비교해 INSERT/DELETE/UPDATE 분류. `options.keys` 매칭 key 목록, `options.excludes` 비교 제외 속성. target 에 중복 key 가 있으면 첫 매칭만 사용. 서버 동기화 대상 산출에 사용.
|
|
87
|
+
- `oneWayDiffs(orgItems, keyPropNameOrGetValFn, options)` — 현재(this) 배열을 원본(`orgItems`, 배열 또는 `Map`) 대비 create/update/same 으로 분류. `keyPropNameOrGetValFn` 은 key 속성명 또는 key 추출 함수. `options.includeSame` 동일건 포함 여부, `excludes`/`includes` 비교 속성 제한.
|
|
88
|
+
- `merge(target, options)` — 두 배열을 key 기준 병합(없으면 추가, 있으면 속성 합침). `options.keys`/`excludes` 는 `diffs` 와 동일 의미.
|
|
89
|
+
|
|
90
|
+
## 집계
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
sum(selector?: (item, i) => number): number; // 빈 배열이면 0
|
|
94
|
+
min(selector?): TProp | undefined;
|
|
95
|
+
max(selector?): TProp | undefined;
|
|
96
|
+
shuffle(): TItem[];
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
- `sum` — selector 생략 시 원소를 숫자로 더함. 빈 배열은 0.
|
|
100
|
+
- `min`/`max` — selector 없으면 원소가 `number|string` 일 때만. 비면 `undefined`.
|
|
101
|
+
- `shuffle` — 무작위 순서의 새 배열.
|
|
102
|
+
|
|
103
|
+
## 변형 메서드 (@mutates 원본 직접 수정)
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
distinctThis(options?): TItem[]; // distinct 의 in-place 판
|
|
107
|
+
orderByThis(selector?): TItem[]; // 오름차순 in-place
|
|
108
|
+
orderByDescThis(selector?): TItem[]; // 내림차순 in-place
|
|
109
|
+
insert(index: number, ...items: TItem[]): this;
|
|
110
|
+
remove(item: TItem): this;
|
|
111
|
+
remove(selector: (item, i) => boolean): this;
|
|
112
|
+
toggle(item: TItem): this; // 있으면 제거, 없으면 추가
|
|
113
|
+
clear(): this; // 비우기
|
|
114
|
+
```
|
|
61
115
|
|
|
62
|
-
- `
|
|
63
|
-
- `
|
|
64
|
-
- `TreeArray<TNode>` — `TNode & { children: TreeArray<TNode>[] }`.
|
|
65
|
-
- `ComparableType` — `string | number | boolean | DateTime | DateOnly | Time | undefined`. 정렬/비교 가능 타입.
|
|
116
|
+
- `*This` 계열·`insert`/`remove`/`toggle`/`clear` 는 원본 배열을 직접 바꾸고 `this`(또는 배열)를 반환해 체이닝 가능. 새 배열이 필요하면 `This` 없는 `orderBy`/`distinct` 사용.
|
|
117
|
+
- `remove` — 값 또는 조건 함수 오버로드. `toggle` — 멤버십 토글로 선택 상태 관리에 사용.
|
|
66
118
|
|
|
67
119
|
```typescript
|
|
68
|
-
|
|
69
|
-
const sorted = items.orderBy((it) => it.createdAt).distinct({ keyFn: (it) => it.id });
|
|
70
|
-
const tree = rows.toTree("id", "parentId");
|
|
71
|
-
const changes = current.oneWayDiffs(original, "id"); // create/update 분류
|
|
120
|
+
list.remove((x) => x.deleted).orderByThis((x) => x.seq);
|
|
72
121
|
```
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# @simplysm/core-common — 비동기 런타임
|
|
2
|
+
|
|
3
|
+
비동기 실행 흐름·이벤트·캐시·로깅을 다룰 때 함께 읽히는 묶음. `DebounceQueue`/`SerialQueue`(실행 큐)는 `EventEmitter` 를 상속해 `error` 이벤트를 발행하며, `LazyGcMap`(자동 만료 캐시)·`createLogger`(태그 로거)와 함께 쓰인다.
|
|
4
|
+
|
|
5
|
+
## EventEmitter
|
|
6
|
+
|
|
7
|
+
브라우저/Node 공통의 타입 안전 이벤트 이미터(내부적으로 `EventTarget` 사용).
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
class EventEmitter<TEvents extends { [K in keyof TEvents]: unknown } = Record<string, unknown>> {
|
|
11
|
+
on<K extends keyof TEvents & string>(type: K, listener: (data: TEvents[K]) => void): void;
|
|
12
|
+
off<K extends keyof TEvents & string>(type: K, listener: (data: TEvents[K]) => void): void;
|
|
13
|
+
emit<K extends keyof TEvents & string>(type: K, ...args: TEvents[K] extends void ? [] : [data: TEvents[K]]): void;
|
|
14
|
+
listenerCount<K extends keyof TEvents & string>(type: K): number;
|
|
15
|
+
dispose(): void;
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- `TEvents` — 이벤트명→데이터 타입 맵. `void` 타입 이벤트는 `emit("done")` 처럼 인자 없이 발행.
|
|
20
|
+
- `on` — 같은 (type, listener) 조합 중복 등록은 무시. `off` — 해당 listener 제거.
|
|
21
|
+
- `emit` — 등록 리스너에 데이터 디스패치. `listenerCount(type)` — 현재 리스너 수(없으면 0). `dispose` — 전체 리스너 해제.
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
interface MyEvents { data: string; done: void; }
|
|
25
|
+
class My extends EventEmitter<MyEvents> {}
|
|
26
|
+
const e = new My();
|
|
27
|
+
e.on("data", (s) => console.log(s));
|
|
28
|
+
e.emit("data", "hi");
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## DebounceQueue
|
|
32
|
+
|
|
33
|
+
짧은 시간 내 여러 호출 중 **마지막만** 실행하는 디바운스 큐. `EventEmitter<{ error: SdError }>` 상속.
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
class DebounceQueue extends EventEmitter<{ error: SdError }> {
|
|
37
|
+
constructor(delay?: number); // 지연 ms (생략 시 다음 이벤트 루프에 즉시)
|
|
38
|
+
run(fn: () => void | Promise<void>): void; // 대기 작업 등록(기존 대기 작업 교체)
|
|
39
|
+
override dispose(): void; // 타이머·대기 작업 정리
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- `constructor(delay)` — 디바운스 지연. 생략하면 0(다음 틱 실행).
|
|
44
|
+
- `run(fn)` — 호출할 때마다 이전 대기 fn 을 교체. 지연 후 마지막 fn 만 실행. **실행 중**에 들어온 추가 요청은 지연 없이 현재 실행 직후 즉시 처리(의도적 설계 — 누락 방지).
|
|
45
|
+
- 에러 처리: fn 이 throw 하면 `SdError` 로 감싸, `error` 리스너가 있으면 `emit("error")`, 없으면 내부 로거로 출력. (문제 발생 = error 심각도)
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
const q = new DebounceQueue(300);
|
|
49
|
+
q.on("error", (e) => console.error(e));
|
|
50
|
+
input.addEventListener("input", () => q.run(() => search(input.value)));
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## SerialQueue
|
|
54
|
+
|
|
55
|
+
추가된 작업을 **순차** 실행하는 큐(이전 완료 후 다음 시작). 에러가 나도 후속 작업은 계속 진행. `EventEmitter<{ error: SdError }>` 상속.
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
class SerialQueue extends EventEmitter<{ error: SdError }> {
|
|
59
|
+
constructor(gap?: number); // 작업 사이 간격 ms (기본 0)
|
|
60
|
+
run(fn: () => void | Promise<void>): void; // 큐에 추가하고 실행 시작
|
|
61
|
+
override dispose(): void; // 대기 큐 비움(실행 중 작업은 완료됨)
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
- `constructor(gap)` — 각 작업 사이 대기 간격(ms). 0 이면 연속 실행.
|
|
66
|
+
- `run(fn)` — FIFO 로 순차 실행. 한 작업이 throw 하면 `SdError` 로 감싸 `error` 이벤트 발행(리스너 없으면 로그) 후 다음 작업 진행 — 후속 작업을 막지 않으려는 의도.
|
|
67
|
+
- `dispose` — 아직 시작 안 한 대기분만 제거(실행 중인 건은 완료).
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
const q = new SerialQueue();
|
|
71
|
+
q.run(async () => save(a)); // 순서 보장
|
|
72
|
+
q.run(async () => save(b));
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## LazyGcMap
|
|
76
|
+
|
|
77
|
+
마지막 접근 이후 일정 시간이 지나면 항목을 자동 삭제하는 Map(LRU 접근 시간 갱신). 타이머를 쓰므로 사용 후 `dispose()` 필수.
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
class LazyGcMap<TKey, TValue> {
|
|
81
|
+
constructor(options: {
|
|
82
|
+
gcInterval?: number; // GC 주기 ms (기본: expireTime/10, 최소 1000)
|
|
83
|
+
expireTime: number; // 만료 시간 ms (마지막 접근 이후)
|
|
84
|
+
onExpire?: (key: TKey, value: TValue) => void | Promise<void>;
|
|
85
|
+
});
|
|
86
|
+
get size: number;
|
|
87
|
+
has(key): boolean; // 접근 시간 갱신 안 함
|
|
88
|
+
get(key): TValue | undefined; // 접근 시간 갱신(LRU)
|
|
89
|
+
set(key, value): void; // 저장 + GC 타이머 시작
|
|
90
|
+
delete(key): boolean; // 비면 타이머 중지
|
|
91
|
+
getOrCreate(key, factory: () => TValue): TValue; // dispose 후 호출 시 throw
|
|
92
|
+
clear(): void; // 항목만 비움(재사용 가능)
|
|
93
|
+
dispose(): void; // 타이머 중지 + 비움(이후 set/get 무력화)
|
|
94
|
+
values(): IterableIterator<TValue>;
|
|
95
|
+
keys(): IterableIterator<TKey>;
|
|
96
|
+
entries(): IterableIterator<[TKey, TValue]>;
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
- `expireTime` — 필수. 마지막 접근 후 이 시간이 지나면 만료. `gcInterval` — 만료 스캔 주기(미지정 시 `expireTime/10`, 최소 1000ms).
|
|
101
|
+
- `onExpire(key, value)` — 만료 직전 호출(비동기 가능). 콜백이 throw 해도 로그만 남기고 GC 계속. 콜백 도중 같은 key 가 재등록되면 새 항목은 삭제하지 않음.
|
|
102
|
+
- `get` 은 접근 시간을 갱신(LRU), `has` 는 갱신하지 않음. `set` 이 호출돼야 GC 타이머가 시작되고, 항목이 모두 비면 타이머가 자동 중지.
|
|
103
|
+
- `getOrCreate` 는 dispose 이후 호출하면 throw(silent 동작 금지). `dispose` 미호출 시 타이머가 계속 돌아 메모리 누수.
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
const cache = new LazyGcMap<string, Session>({ expireTime: 60000 });
|
|
107
|
+
try {
|
|
108
|
+
const s = cache.getOrCreate(id, () => loadSession(id));
|
|
109
|
+
} finally {
|
|
110
|
+
cache.dispose();
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## createLogger
|
|
115
|
+
|
|
116
|
+
`consola` 기반 태그 로거를 지연 생성하는 팩토리. 모듈 최상위에서 호출해도 안전(첫 메서드 접근 시점까지 `consola.withTag` 생성을 미뤄, 이후 `setupConsola()` 의 level/reporter 변경이 반영됨).
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
createLogger(tag: string): ConsolaInstance;
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
- `tag` — 로그에 붙는 태그(클래스/모듈명 등). 반환값은 `consola` 인스턴스라 `.info`/`.warn`/`.error`/`.success` 등을 그대로 사용.
|
|
123
|
+
- 즉시 `consola.withTag()` 를 부르면 호출 시점 옵션이 고정되는 문제를 피하기 위한 Proxy 래퍼. 테스트의 `vi.spyOn` 과도 호환.
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
const logger = createLogger("MyService");
|
|
127
|
+
logger.error("처리 실패", err);
|
|
128
|
+
```
|
|
@@ -1,95 +1,129 @@
|
|
|
1
1
|
# @simplysm/core-common — 날짜·시간
|
|
2
2
|
|
|
3
|
-
날짜/시간
|
|
3
|
+
불변 날짜/시간 값 `DateTime`(날짜+시간)·`DateOnly`(날짜)·`Time`(시간)과 포맷 문자열을 처리하는 `dt` 네임스페이스. 세 클래스 모두 로컬 타임존 기준이며, 모든 set/add 메서드는 원본을 변경하지 않고 새 인스턴스를 반환한다. 파싱 실패 시 `ArgumentError` throw.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
날짜+시간(밀리초 정밀도) 불변 클래스. 내부에 `readonly date: Date` 보유.
|
|
8
|
-
|
|
9
|
-
생성자:
|
|
10
|
-
- `new DateTime()` — 현재 시각.
|
|
11
|
-
- `new DateTime(year, month, day, hour?, minute?, second?, millisecond?)` — `month` 는 1~12(내부에서 0-base 로 변환). 생략한 시/분/초/밀리초는 0.
|
|
12
|
-
- `new DateTime(tick: number)` — epoch 밀리초.
|
|
13
|
-
- `new DateTime(date: Date)` — Date 복사(원본과 분리).
|
|
5
|
+
공통 포맷 토큰(`toFormatString` 인자): `yyyy`/`yy`(연), `MM`/`M`(월), `ddd`(요일 한글: 일~토)/`dd`/`d`(일), `tt`(AM/PM), `hh`/`h`(12시간), `HH`/`H`(24시간), `mm`/`m`(분), `ss`/`s`(초), `fff`/`ff`/`f`(밀리초), `zzz`/`zz`/`z`(타임존 오프셋, DateTime 만). 긴 토큰이 먼저 치환됨.
|
|
14
6
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
읽기 전용 getter:
|
|
18
|
-
- `year`/`month`(1~12)/`day`/`hour`/`minute`/`second`/`millisecond` — 각 구성요소.
|
|
19
|
-
- `tick: number` — epoch 밀리초. 두 시점 비교·차이 계산에 사용.
|
|
20
|
-
- `dayOfWeek: number` — 요일(일=0 ~ 토=6).
|
|
21
|
-
- `timezoneOffsetMinutes: number` — UTC 대비 오프셋 분(KST=+540). `Date.getTimezoneOffset()` 의 부호 반대.
|
|
22
|
-
- `isValid: boolean` — 유효한 날짜인지. 잘못된 tick 으로 만든 인스턴스 검증에 사용.
|
|
7
|
+
## DateTime
|
|
23
8
|
|
|
24
|
-
|
|
25
|
-
- `setYear/setMonth/setHour/setMinute/setSecond/setMillisecond(n)` — 해당 구성요소만 교체. `setMonth` 는 범위 밖 월을 연도로 넘기고, 대상 월 일수 초과 시 말일로 보정(1/31 → setMonth(2) → 2/28·29).
|
|
26
|
-
- `setDay(n)` — 일 교체. 월 범위를 벗어나는 일은 JS Date 동작대로 다음/이전 월로 넘어감(1월 day=32 → 2/1).
|
|
9
|
+
날짜+시간을 ms 정밀도로 담는 불변 클래스. 내부에 `readonly date: Date` 보유.
|
|
27
10
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
11
|
+
```typescript
|
|
12
|
+
class DateTime {
|
|
13
|
+
constructor(); // 현재 시각
|
|
14
|
+
constructor(year, month, day, hour?, minute?, second?, millisecond?); // month 는 1~12
|
|
15
|
+
constructor(tick: number); // epoch ms
|
|
16
|
+
constructor(date: Date);
|
|
17
|
+
static parse(str: string): DateTime;
|
|
18
|
+
|
|
19
|
+
readonly date: Date;
|
|
20
|
+
get year/month/day/hour/minute/second/millisecond/tick: number;
|
|
21
|
+
get dayOfWeek: number; // 0(일)~6(토)
|
|
22
|
+
get timezoneOffsetMinutes: number; // UTC 대비 분 (KST = +540)
|
|
23
|
+
get isValid: boolean;
|
|
24
|
+
|
|
25
|
+
setYear/setMonth/setDay/setHour/setMinute/setSecond/setMillisecond(v: number): DateTime;
|
|
26
|
+
addYears/addMonths/addDays/addHours/addMinutes/addSeconds/addMilliseconds(n: number): DateTime;
|
|
27
|
+
toFormatString(formatStr: string): string;
|
|
28
|
+
toString(): string; // "yyyy-MM-ddTHH:mm:ss.fffzzz"
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
- `
|
|
34
|
-
- `
|
|
32
|
+
- 생성자 `month` 인자는 1~12(내부에서 `month-1` 로 Date 에 전달). `tick`/`Date` 오버로드는 단일 숫자/Date 로 구분.
|
|
33
|
+
- `parse` 지원 형식: `yyyy-MM-dd HH:mm:ss[.fff]`, `yyyyMMddHHmmss`, `yyyy-MM-dd AM/PM HH:mm:ss`, 한국어 `yyyy-MM-dd 오전/오후 HH:mm:ss`, ISO 8601. 먼저 `Date.parse` 를 시도.
|
|
34
|
+
- `setMonth(month)` — 1~12 밖이면 연도로 흡수, 대상 월 일수보다 현재 일이 크면 말일로 보정(1/31 → setMonth(2) → 2/28). `setYear` 도 윤년 말일 보정.
|
|
35
|
+
- `addMonths`/`addDays` 는 각각 `setMonth`/`setDay` 기반이라 월말 보정/오버플로 규칙을 따름. `addHours` 이하는 tick 가산이라 타임존 전환 영향 없음.
|
|
36
|
+
- `isValid` — 내부 Date 가 NaN 이 아닌지. `parse` 가 아닌 잘못된 tick/Date 로 만든 경우 점검용.
|
|
35
37
|
|
|
36
38
|
```typescript
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
new DateTime(2025, 1, 31).setMonth(2).toFormatString("yyyy-MM-dd"); // "2025-02-28"
|
|
40
|
+
DateTime.parse("2025-01-15 오후 2:30:00").hour; // 14
|
|
39
41
|
```
|
|
40
42
|
|
|
41
43
|
## DateOnly
|
|
42
44
|
|
|
43
|
-
시간
|
|
44
|
-
|
|
45
|
-
생성자: `new DateOnly()`(오늘) / `(year, month, day)` / `(tick)` / `(date)` — 모두 시간 부분을 버리고 자정으로 정규화.
|
|
46
|
-
|
|
47
|
-
- `DateOnly.parse(str): DateOnly` — `yyyy-MM-dd`/`yyyyMMdd`(타임존 무관, 문자열 직접 추출) 또는 ISO 8601(UTC 해석 후 로컬 변환). 서버·클라 타임존이 다르면 `yyyy-MM-dd` 권장. 실패 시 `ArgumentError`.
|
|
48
|
-
|
|
49
|
-
getter: `isValid`/`year`/`month`(1~12)/`day`/`tick`/`dayOfWeek`(일=0~토=6).
|
|
50
|
-
|
|
51
|
-
변환/산술: `setYear/setMonth/setDay`, `addYears/addMonths/addDays` — DateTime 과 동일한 말일·월 넘김 규칙.
|
|
45
|
+
시간 정보 없이 날짜만 담는 불변 클래스(`readonly date: Date`, 자정 고정). 주차 계산 API 포함.
|
|
52
46
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
47
|
+
```typescript
|
|
48
|
+
class DateOnly {
|
|
49
|
+
constructor(); // 오늘
|
|
50
|
+
constructor(year, month, day); // month 1~12
|
|
51
|
+
constructor(tick: number);
|
|
52
|
+
constructor(date: Date);
|
|
53
|
+
static parse(str: string): DateOnly;
|
|
54
|
+
static getDateByYearWeekSeq(arg: { year: number; month?: number; weekSeq: number }, weekStartDay?: number, minDaysInFirstWeek?: number): DateOnly;
|
|
55
|
+
|
|
56
|
+
readonly date: Date;
|
|
57
|
+
get year/month/day/tick/dayOfWeek: number; // dayOfWeek 0(일)~6(토)
|
|
58
|
+
get isValid: boolean;
|
|
59
|
+
setYear/setMonth/setDay(v: number): DateOnly;
|
|
60
|
+
addYears/addMonths/addDays(n: number): DateOnly;
|
|
61
|
+
|
|
62
|
+
getBaseYearMonthSeqForWeekSeq(weekStartDay?: number, minDaysInFirstWeek?: number): { year: number; monthSeq: number };
|
|
63
|
+
getWeekSeqStartDate(weekStartDay?: number, minDaysInFirstWeek?: number): DateOnly;
|
|
64
|
+
getWeekSeqOfYear(weekStartDay?: number, minDaysInFirstWeek?: number): { year: number; weekSeq: number };
|
|
65
|
+
getWeekSeqOfMonth(weekStartDay?: number, minDaysInFirstWeek?: number): { year: number; monthSeq: number; weekSeq: number };
|
|
66
|
+
|
|
67
|
+
toFormatString(formatStr: string): string;
|
|
68
|
+
toString(): string; // "yyyy-MM-dd"
|
|
69
|
+
}
|
|
70
|
+
```
|
|
59
71
|
|
|
60
|
-
|
|
72
|
+
- `parse` 형식: `yyyy-MM-dd`·`yyyyMMdd`(둘 다 타임존 무관, 문자열에서 직접 추출), ISO 8601(UTC 해석 후 로컬 변환). 서버/클라 타임존이 다르면 `yyyy-MM-dd` 권장.
|
|
73
|
+
- 주차 계산 공통 인자: `weekStartDay` 주 시작 요일(0=일~6=토, 기본 1=월), `minDaysInFirstWeek` 첫 주로 인정할 최소 일수(1~7, 기본 4=ISO 8601). 미국식은 `(0, 1)`.
|
|
74
|
+
- `getWeekSeqOfYear` — 연 기준 몇 째 주인지. `getWeekSeqOfMonth` — 월 기준 주차(+기준 연·월). `getWeekSeqStartDate` — 이 날짜가 속한 주의 시작일. `getBaseYearMonthSeqForWeekSeq` — 주차 귀속 기준 연·월(월 경계 조정).
|
|
75
|
+
- `getDateByYearWeekSeq(arg, ...)` — 정적. `{ year, weekSeq }`(연 주차) 또는 `{ year, month, weekSeq }`(월 주차)로 해당 주 시작일 역산.
|
|
61
76
|
|
|
62
77
|
```typescript
|
|
63
|
-
new DateOnly(2025, 1,
|
|
78
|
+
new DateOnly(2025, 1, 6).getWeekSeqOfYear(); // { year: 2025, weekSeq: 2 }
|
|
79
|
+
DateOnly.getDateByYearWeekSeq({ year: 2025, weekSeq: 2 }); // 2025-01-06 (월)
|
|
64
80
|
```
|
|
65
81
|
|
|
66
82
|
## Time
|
|
67
83
|
|
|
68
|
-
날짜
|
|
69
|
-
|
|
70
|
-
생성자: `new Time()`(현재 시각의 시간부) / `(hour, minute, second?, millisecond?)` / `(tick)`(하루 내 밀리초) / `(date)`(Date 의 시간부만).
|
|
84
|
+
날짜 없이 시각(HH:mm:ss.fff)만 담는 불변 클래스. 24시간을 넘거나 음수인 tick 은 24시간 순환으로 정규화된다.
|
|
71
85
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
86
|
+
```typescript
|
|
87
|
+
class Time {
|
|
88
|
+
constructor(); // 현재 시각의 시간 부분
|
|
89
|
+
constructor(hour, minute, second?, millisecond?);
|
|
90
|
+
constructor(tick: number); // 자정 기준 ms
|
|
91
|
+
constructor(date: Date); // Date 의 시간 부분만
|
|
92
|
+
static parse(str: string): Time;
|
|
93
|
+
|
|
94
|
+
get hour/minute/second/millisecond/tick: number;
|
|
95
|
+
get isValid: boolean;
|
|
96
|
+
setHour/setMinute/setSecond/setMillisecond(v: number): Time;
|
|
97
|
+
addHours/addMinutes/addSeconds/addMilliseconds(n: number): Time; // 24시간 순환
|
|
98
|
+
toFormatString(formatStr: string): string;
|
|
99
|
+
toString(): string; // "HH:mm:ss.fff"
|
|
100
|
+
}
|
|
101
|
+
```
|
|
75
102
|
|
|
76
|
-
|
|
77
|
-
|
|
103
|
+
- 생성자 다중 인자(`hour, minute, ...`)와 단일 `tick`/`Date` 오버로드. 결과 tick 은 항상 `[0, 24h)` 범위로 wrap.
|
|
104
|
+
- `parse` 형식: `HH:mm:ss[.fff]`, `AM/PM HH:mm:ss[.fff]`, ISO 8601(시간 부분만 추출, 타임존 변환은 Date 위임).
|
|
105
|
+
- `addHours` 등 산술은 24시간을 넘으면 다시 0시부터(`23:00` + 2h → `01:00`). 날짜 개념이 없으므로 일자 carry 는 버려짐.
|
|
78
106
|
|
|
79
|
-
|
|
107
|
+
```typescript
|
|
108
|
+
Time.parse("AM 10:30:00").addHours(15).toString(); // "01:30:00.000"
|
|
109
|
+
```
|
|
80
110
|
|
|
81
|
-
## dt
|
|
111
|
+
## dt 네임스페이스
|
|
82
112
|
|
|
83
|
-
`import { dt } from "@simplysm/core-common"`. 위
|
|
113
|
+
`import { dt } from "@simplysm/core-common"`. 위 클래스들이 내부에서 쓰는 포맷/정규화 함수를 직접 노출.
|
|
84
114
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
115
|
+
```typescript
|
|
116
|
+
dt.format(formatString: string, args: { year?; month?; day?; hour?; minute?; second?; millisecond?; timezoneOffsetMinutes?: number }): string;
|
|
117
|
+
dt.normalizeMonth(year: number, month: number, day: number): { year: number; month: number; day: number };
|
|
118
|
+
dt.convert12To24(rawHour: number, isPM: boolean): number;
|
|
119
|
+
interface DtNormalizedMonth { year: number; month: number; day: number; }
|
|
120
|
+
```
|
|
89
121
|
|
|
90
|
-
|
|
122
|
+
- `dt.format(fmt, args)` — 위 공통 토큰 표를 따르는 저수준 포맷터. 누락한 구성요소(예: `hour` 만)는 해당 토큰만 치환하고 나머지는 원문 유지. `DateTime`/`DateOnly`/`Time` 의 `toFormatString` 이 이 함수를 호출.
|
|
123
|
+
- `dt.normalizeMonth(year, month, day)` — 월이 1~12 밖이면 연도로 흡수, 일이 대상 월 일수 초과면 말일로 보정한 `{year, month, day}`. `setMonth` 의 보정 규칙 그대로.
|
|
124
|
+
- `dt.convert12To24(rawHour, isPM)` — 12시간(1~12)+AM/PM 을 0~23 으로. `12 AM`→0, `12 PM`→12.
|
|
91
125
|
|
|
92
126
|
```typescript
|
|
93
|
-
dt.format("yyyy-MM-dd
|
|
94
|
-
|
|
127
|
+
dt.format("yyyy-MM-dd (ddd)", { year: 2024, month: 3, day: 15 }); // "2024-03-15 (금)"
|
|
128
|
+
dt.normalizeMonth(2025, 13, 15); // { year: 2026, month: 1, day: 15 }
|
|
95
129
|
```
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# @simplysm/core-common — 에러 클래스
|
|
2
|
+
|
|
3
|
+
원인 체인(`cause`)을 가진 에러를 throw 하거나 타입별로 분기할 때 함께 읽히는 묶음. 모두 `SdError` 를 베이스로 하며 각 클래스는 `name` 을 자기 클래스명으로 설정해 `instanceof`·`name` 양쪽으로 식별 가능.
|
|
4
|
+
|
|
5
|
+
## SdError
|
|
6
|
+
|
|
7
|
+
ES2024 `cause` 를 활용해 에러를 트리로 감싸는 베이스 클래스. 메시지는 **역순으로 결합**되어 상위(가장 바깥) 메시지가 앞에 온다.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
class SdError extends Error {
|
|
11
|
+
override cause?: Error;
|
|
12
|
+
constructor(cause: Error, ...messages: string[]); // 원인 에러 + 상위 메시지들
|
|
13
|
+
constructor(...messages: string[]); // 메시지들만
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
- `cause: Error` — 첫 인자가 `Error` 면 원인으로 저장되고 그 stack 이 현재 stack 뒤에 `---- cause stack ----` 로 이어붙음. 첫 인자가 문자열/기타면 일반 메시지로 취급.
|
|
18
|
+
- `...messages` — 추가 설명 메시지들. 결합 시 `messages` 가 먼저 reverse 되어 `상위 => 하위 => 원인` 순으로 `" => "` 결합. null/undefined 메시지는 제외.
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
throw new SdError(err, "API 호출 실패", "사용자 로드 실패");
|
|
22
|
+
// message: "사용자 로드 실패 => API 호출 실패 => 원본 에러 메시지"
|
|
23
|
+
|
|
24
|
+
throw new SdError("잘못된 상태", "처리 불가");
|
|
25
|
+
// message: "처리 불가 => 잘못된 상태"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
주의: 하위 에러를 잡아 컨텍스트를 덧붙여 다시 throw 할 때 사용. 원본 에러를 삼키지 말고 첫 인자로 넘겨 체인을 보존.
|
|
29
|
+
|
|
30
|
+
## ArgumentError
|
|
31
|
+
|
|
32
|
+
유효하지 않은 인자를 받았을 때 throw. 인자 객체를 YAML 로 직렬화해 메시지에 포함(디버깅용). `SdError` 상속.
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
class ArgumentError extends SdError {
|
|
36
|
+
constructor(argObj: Record<string, unknown>); // 기본 메시지 + 인자
|
|
37
|
+
constructor(message: string, argObj: Record<string, unknown>); // 커스텀 메시지 + 인자
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- `argObj` — 문제가 된 인자들을 담은 객체. `YAML.stringify` 결과가 메시지 본문 뒤에 두 줄 띄고 붙음. 어떤 값이 잘못됐는지 그대로 노출하려는 의도.
|
|
42
|
+
- `message` — 생략 시 `"잘못된 인자입니다."` 가 기본값.
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
throw new ArgumentError("잘못된 사용자", { userId: 123, name: null });
|
|
46
|
+
// "잘못된 사용자\n\nuserId: 123\nname: null"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
이 패키지 내부(`Uuid`, `bytes`, `num` 파싱, `obj` 체인 함수 등)에서 입력 검증 실패 시 광범위하게 사용된다.
|
|
50
|
+
|
|
51
|
+
## NotImplementedError
|
|
52
|
+
|
|
53
|
+
아직 구현되지 않은 코드 경로가 호출됐을 때 throw. `SdError` 상속.
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
class NotImplementedError extends SdError {
|
|
57
|
+
constructor(message?: string); // "미구현" 또는 "미구현: <message>"
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- `message` — 어떤 기능이 미구현인지 보조 설명. 생략 시 메시지는 `"미구현"`.
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
switch (type) {
|
|
65
|
+
case "A": return handleA();
|
|
66
|
+
case "B": throw new NotImplementedError(`타입 ${type} 처리`); // "미구현: 타입 B 처리"
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
추상 메서드 스텁이나 미완성 분기에 의도적 throw 로 두어 silent skip 을 막는 용도.
|
|
71
|
+
|
|
72
|
+
## TimeoutError
|
|
73
|
+
|
|
74
|
+
대기 시간이 초과됐을 때 throw. `wait.until()` 이 `maxCount` 초과 시 자동으로 발생시킨다. `SdError` 상속.
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
class TimeoutError extends SdError {
|
|
78
|
+
constructor(count?: number, message?: string); // "대기 시간 초과(<count>회 시도): <message>"
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- `count` — 시도 횟수. 지정 시 `(N회 시도)` 가 메시지에 삽입됨.
|
|
83
|
+
- `message` — 무엇을 대기하다 실패했는지 보조 설명.
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
try {
|
|
87
|
+
await wait.until(() => isReady, 100, 50);
|
|
88
|
+
} catch (e) {
|
|
89
|
+
if (e instanceof TimeoutError) { /* 타임아웃 분기 */ }
|
|
90
|
+
}
|
|
91
|
+
```
|