@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.
Files changed (122) hide show
  1. package/claude/references/sd-simplysm14/README.md +17 -17
  2. package/claude/references/sd-simplysm14/apis/angular/README.md +27 -53
  3. package/claude/references/sd-simplysm14/apis/angular/controls.md +37 -105
  4. package/claude/references/sd-simplysm14/apis/angular/crud.md +46 -43
  5. package/claude/references/sd-simplysm14/apis/angular/directives.md +22 -32
  6. package/claude/references/sd-simplysm14/apis/angular/features.md +40 -55
  7. package/claude/references/sd-simplysm14/apis/angular/infra.md +40 -40
  8. package/claude/references/sd-simplysm14/apis/angular/layout.md +25 -53
  9. package/claude/references/sd-simplysm14/apis/angular/overlay.md +70 -82
  10. package/claude/references/sd-simplysm14/apis/angular/routing-appstructure.md +44 -39
  11. package/claude/references/sd-simplysm14/apis/angular/selection-managers.md +21 -36
  12. package/claude/references/sd-simplysm14/apis/angular/shared-data.md +52 -65
  13. package/claude/references/sd-simplysm14/apis/angular/sheet.md +65 -70
  14. package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +33 -35
  15. package/claude/references/sd-simplysm14/apis/capacitor-plugin-file-system/README.md +7 -7
  16. package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +29 -29
  17. package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +45 -50
  18. package/claude/references/sd-simplysm14/apis/core-browser/README.md +42 -55
  19. package/claude/references/sd-simplysm14/apis/core-browser/dom-element.md +62 -0
  20. package/claude/references/sd-simplysm14/apis/core-browser/indexed-db.md +13 -12
  21. package/claude/references/sd-simplysm14/apis/core-common/README.md +222 -98
  22. package/claude/references/sd-simplysm14/apis/core-common/array-ext.md +102 -53
  23. package/claude/references/sd-simplysm14/apis/core-common/async-runtime.md +128 -0
  24. package/claude/references/sd-simplysm14/apis/core-common/datetime.md +98 -64
  25. package/claude/references/sd-simplysm14/apis/core-common/errors.md +91 -0
  26. package/claude/references/sd-simplysm14/apis/core-common/json-transfer.md +34 -28
  27. package/claude/references/sd-simplysm14/apis/core-common/obj.md +104 -40
  28. package/claude/references/sd-simplysm14/apis/core-node/README.md +11 -8
  29. package/claude/references/sd-simplysm14/apis/core-node/consola.md +23 -31
  30. package/claude/references/sd-simplysm14/apis/core-node/cpx.md +33 -22
  31. package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +28 -25
  32. package/claude/references/sd-simplysm14/apis/core-node/fsx.md +39 -53
  33. package/claude/references/sd-simplysm14/apis/core-node/pathx.md +26 -29
  34. package/claude/references/sd-simplysm14/apis/core-node/worker.md +27 -29
  35. package/claude/references/sd-simplysm14/apis/excel/README.md +14 -14
  36. package/claude/references/sd-simplysm14/apis/lint/README.md +27 -21
  37. package/claude/references/sd-simplysm14/apis/lint/rules.md +89 -49
  38. package/claude/references/sd-simplysm14/apis/orm-common/README.md +5 -59
  39. package/claude/references/sd-simplysm14/apis/orm-common/db-context.md +98 -67
  40. package/claude/references/sd-simplysm14/apis/orm-common/expr.md +107 -92
  41. package/claude/references/sd-simplysm14/apis/orm-common/queryable.md +99 -65
  42. package/claude/references/sd-simplysm14/apis/orm-common/schema.md +83 -98
  43. package/claude/references/sd-simplysm14/apis/orm-common/types.md +62 -52
  44. package/claude/references/sd-simplysm14/apis/orm-node/README.md +62 -25
  45. package/claude/references/sd-simplysm14/apis/orm-node/db-conn.md +27 -27
  46. package/claude/references/sd-simplysm14/apis/sd-cli/README.md +12 -15
  47. package/claude/references/sd-simplysm14/apis/sd-cli/SdTsCompiler.md +92 -45
  48. package/claude/references/sd-simplysm14/apis/sd-cli/sd-config-types.md +226 -108
  49. package/claude/references/sd-simplysm14/apis/service-client/README.md +84 -86
  50. package/claude/references/sd-simplysm14/apis/service-client/orm.md +14 -11
  51. package/claude/references/sd-simplysm14/apis/service-client/transport.md +33 -10
  52. package/claude/references/sd-simplysm14/apis/service-common/README.md +37 -23
  53. package/claude/references/sd-simplysm14/apis/service-common/app-structure.md +9 -9
  54. package/claude/references/sd-simplysm14/apis/service-common/protocol.md +13 -13
  55. package/claude/references/sd-simplysm14/apis/service-server/README.md +81 -65
  56. package/claude/references/sd-simplysm14/apis/service-server/service-authoring.md +32 -35
  57. package/claude/references/sd-simplysm14/apis/service-server/transport-internals.md +44 -33
  58. package/claude/references/sd-simplysm14/apis/service-server/v1-legacy.md +34 -45
  59. package/claude/references/sd-simplysm14/apis/storage/README.md +24 -18
  60. package/claude/skills/sd-demo/SKILL.md +6 -0
  61. package/claude/skills/sd-impl/SKILL.md +4 -7
  62. package/claude/skills/sd-spec/SKILL.md +31 -858
  63. package/claude/skills/sd-spec/references/spec-authoring.md +519 -0
  64. package/claude/workflows/sd-docs.js +84 -0
  65. package/package.json +1 -1
  66. package/claude/references/sd-simplysm14/apis/orm-common/query-builder.md +0 -29
  67. package/claude/skills/sd-demo/evals/fixtures/inventory-list/.specs/inventory/spec.md +0 -99
  68. package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/package.json +0 -12
  69. package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/index.ts +0 -3
  70. package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/screens/inbound/inbound.list.ts +0 -150
  71. package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/screens/inventory/inventory-master.list.ts +0 -143
  72. package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/screens/outbound/outbound.list.ts +0 -150
  73. package/claude/skills/sd-demo/evals/fixtures/inventory-list/pnpm-workspace.yaml +0 -2
  74. package/claude/skills/sd-demo/evals/fixtures/inventory-list/sd.config.ts +0 -12
  75. package/claude/skills/sd-demo/evals/golden.jsonl +0 -1
  76. package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/package.json +0 -8
  77. package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/src/.gitkeep +0 -0
  78. package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/tests/.gitkeep +0 -0
  79. package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/tsconfig.json +0 -10
  80. package/claude/skills/sd-dev/evals/golden.jsonl +0 -1
  81. package/claude/skills/sd-docs/SKILL.md +0 -46
  82. package/claude/skills/sd-docs/evals/fixtures/new-write/.claude/references/sd-simplysm14/README.md +0 -7
  83. package/claude/skills/sd-docs/evals/fixtures/new-write/packages/bar/package.json +0 -5
  84. package/claude/skills/sd-docs/evals/fixtures/new-write/packages/bar/src/index.ts +0 -3
  85. package/claude/skills/sd-docs/evals/fixtures/new-write/packages/baz/package.json +0 -6
  86. package/claude/skills/sd-docs/evals/fixtures/new-write/packages/baz/src/index.ts +0 -1
  87. package/claude/skills/sd-docs/evals/fixtures/new-write/packages/foo/package.json +0 -5
  88. package/claude/skills/sd-docs/evals/fixtures/new-write/packages/foo/src/index.ts +0 -8
  89. package/claude/skills/sd-docs/evals/fixtures/update-mixed/.claude/references/sd-simplysm14/README.md +0 -7
  90. package/claude/skills/sd-docs/evals/fixtures/update-mixed/.claude/references/sd-simplysm14/apis/foo/README.md +0 -3
  91. package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/bar/package.json +0 -5
  92. package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/bar/src/index.ts +0 -3
  93. package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/baz/package.json +0 -6
  94. package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/baz/src/index.ts +0 -1
  95. package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/foo/package.json +0 -5
  96. package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/foo/src/index.ts +0 -8
  97. package/claude/skills/sd-docs/evals/golden.jsonl +0 -2
  98. package/claude/skills/sd-impl/evals/fixtures/case-a-new-screen/.specs/260513120000_warehouse/spec.md +0 -101
  99. package/claude/skills/sd-impl/evals/fixtures/case-b-update-with-demo/.specs/260513120000_warehouse/spec.md +0 -101
  100. package/claude/skills/sd-impl/evals/fixtures/case-b-update-with-demo/packages/app/src/screens/box-register/box-register.view.ts +0 -46
  101. package/claude/skills/sd-impl/evals/fixtures/case-c-new-cross/.specs/260513120000_warehouse/spec.md +0 -89
  102. package/claude/skills/sd-impl/evals/fixtures/case-d-spec-modify/.specs/260513120000_warehouse/spec.md +0 -101
  103. package/claude/skills/sd-impl/evals/golden.jsonl +0 -4
  104. package/claude/skills/sd-manual/evals/fixtures/new-manual/src/notification.ts +0 -25
  105. package/claude/skills/sd-manual/evals/fixtures/update-manual/.claude/references/sd-simplysm14/manuals/notification.md +0 -14
  106. package/claude/skills/sd-manual/evals/fixtures/update-manual/src/notification.ts +0 -37
  107. package/claude/skills/sd-manual/evals/golden.jsonl +0 -2
  108. package/claude/skills/sd-review/evals/fixtures/code-review/src/foo.ts +0 -7
  109. package/claude/skills/sd-review/evals/fixtures/doc-review/docs/foo.md +0 -4
  110. package/claude/skills/sd-review/evals/golden.jsonl +0 -2
  111. package/claude/skills/sd-skill/evals/fixtures/existing-skill/.claude/skills/todo-format/SKILL.md +0 -14
  112. package/claude/skills/sd-skill/evals/fixtures/new-skill/.gitkeep +0 -0
  113. package/claude/skills/sd-skill/evals/golden.jsonl +0 -2
  114. package/claude/skills/sd-spec/evals/fixtures/case-a-split//355/232/214/354/235/230/353/241/235.md +0 -20
  115. package/claude/skills/sd-spec/evals/fixtures/case-b-detail/.specs/260513120000_warehouse/spec.md +0 -95
  116. package/claude/skills/sd-spec/evals/golden.jsonl +0 -2
  117. package/claude/skills/sd-unpack/evals/fixtures/eml-with-text-attachment/meeting.eml +0 -21
  118. package/claude/skills/sd-unpack/evals/fixtures/simple-eml/meeting.eml +0 -10
  119. package/claude/skills/sd-unpack/evals/golden.jsonl +0 -2
  120. package/claude/skills/sd-use/evals/fixtures/empty/.gitkeep +0 -0
  121. package/claude/skills/sd-use/evals/golden.jsonl +0 -6
  122. /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
- `import "@simplysm/core-common"`(또는 패키지의 다른 심볼 import) 부수효과로 `Array`/`Set`/`Map` 프로토타입에 메서드가 주입된다. 별도 함수 호출이 아니라 인스턴스 메서드로 바로 쓴다. 메서드들은 `enumerable: false` 정의되어 `for...in` 노출되지 않음. 컬렉션을 그룹화·중복제거·정렬·diff 함께 읽힌다.
3
+ 패키지를 import 하면 부수효과로 `Array.prototype` 메서드가 주입된다(`enumerable: false`, `for...in` 비노출). 함수 호출이 아니라 배열 인스턴스 메서드로 직접 사용. 조회/그룹/정렬/diff 읽기 메서드(`ReadonlyArrayExt`)는 배열을 반환하고, 변형 메서드(`MutableArrayExt`, 이름에 `This` 붙거나 insert/remove/toggle/clear)는 원본을 직접 수정한다.
4
4
 
5
- ## Array — 조회 (ReadonlyArrayExt)
5
+ ## 조회·필터
6
6
 
7
- - `single(predicate?)` — 조건에 맞는 단일 요소. 없으면 `undefined`, 2개 이상이면 `ArgumentError` throw. "있다면 하나뿐" 을 단언할 때.
8
- - `first(predicate?)` / `last(predicate?)` 첫/마지막 요소(predicate 생략 인덱스 끝). 없으면 `undefined`.
9
- - `filterExists()` `null`/`undefined` 제거. 반환 타입 `NonNullable<T>[]`.
10
- - `ofType(type)` 특정 타입 요소만. `type` 은 `PrimitiveTypeStr`("string"|"number"|"boolean"|"DateTime"|"DateOnly"|"Time"|"Uuid"|"Bytes") 또는 생성자(`Type<N>`). 문자열은 typeof/instanceof, 생성자는 `instanceof` + `constructor` 일치로 판정.
11
- - `sum(selector?)` 합계. 비어 있으면 0. 숫자가 아니면 `ArgumentError`.
12
- - `min(selector?)` / `max(selector?)` — 최소/최대(문자열·숫자). 비어 있으면 `undefined`. 그 외 타입은 `ArgumentError`.
13
- - `shuffle()` Fisher-Yates 셔플한 배열.
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
- ## Array — 그룹화·변환
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
- - `groupBy(keySelector, valueSelector?)` — key `{ key, values }[]`. 원시 keyO(n)(Map), 객체 keyO(n²)(깊은 비교). 객체 key 필요 없으면 `toArrayMap` 권장.
24
- - `toMap(keySelector, valueSelector?)` — `Map<K,V>`. key 중복 `ArgumentError`.
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
- ## Array — 중복제거·정렬·diff
34
+ ## 그룹화·Map/객체 변환
33
35
 
34
- - `distinct(options?)` — 중복 제거(새 배열). `options`: `boolean`(=`{matchAddress}`) 또는 `{ matchAddress?, keyFn? }`. `matchAddress:true` 면 참조 비교(O(n)), `keyFn` 이면 key 비교(O(n)), 둘 다 없으면 객체는 깊은 비교(O(n²)).
35
- - `orderBy(selector?)` / `orderByDesc(selector?)` 오름/내림차순 배열. selector 는 string|number|DateTime|DateOnly|Time|undefined 반환. null/undefined 는 오름차순에서 앞.
36
- - `diffs(target, options?)` — 두 배열 비교 결과 `ArrayDiffsResult<T,P>[]`. `options.keys`(key 비교 속성)·`options.excludes`(비교 제외 속성). 전체 일치 우선, 없으면 key 일치를 UPDATE 로. target 잔여는 INSERT, source 잔여는 DELETE.
37
- - `oneWayDiffs(orgItems, keyPropNameOrGetValFn, options?)` — source(this) 를 기준으로 원본 대비 변경 분류. `keyPropNameOrGetValFn`: 키 속성명 또는 `(item) => 키값`. key 값이 없거나 원본에 없으면 `create`, 다르면 `update`, 같으면 `same`(옵션 `includeSame:true` 때만 포함). `options.excludes`/`options.includes` 로 비교 범위 조정. 반환 `ArrayOneWayDiffResult<T>[]`.
38
- - `merge(target, options?)` `diffs` 결과를 적용해 병합한 새 배열. UPDATE 는 `obj.merge` 로 깊은 병합, INSERT 는 추가. `options` 는 `diffs` 와 동일.
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
- ## Array원본 변경 (MutableArrayExt, @mutates)
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
- 원본 배열을 직접 수정하고 보통 `this` 를 반환(체이닝).
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
- ## Set 확장
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
- - `adds(...values)` — 여러 일괄 추가, `this` 반환.
53
- - `toggle(value, addOrDel?)` — `addOrDel` 생략자동 토글(있으면 제거/없으면 추가), `"add"`=강제 추가, `"del"`=강제 제거. 조건부 추가/제거를 줄로. `this` 반환.
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
- ## Map 확장
64
+ ```typescript
65
+ const tree = items.toTree("id", "parentId");
66
+ const uniq = users.distinct({ keyFn: (u) => u.id });
67
+ ```
56
68
 
57
- - `getOrCreate(key, newValueOrFactory)` — key 가 없으면 값을 설정 후 반환, 있으면 기존 값. 두 번째 인자가 함수면 팩토리로 인식해 호출됨 — 함수 자체를 값으로 저장하려면 `() => fn` 처럼 팩토리로 감쌀 것.
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
- - `ArrayDiffsResult<TOriginal, TOther>` `{ source: undefined, target }`(INSERT) | `{ source, target: undefined }`(DELETE) | `{ source, target }`(UPDATE).
63
- - `ArrayOneWayDiffResult<TItem>``{ type: "create"|"update"|"same", item, orgItem }` (create `orgItem: undefined`).
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
- const orders = items.toArrayMap((it) => it.customerId); // Map<id, item[]>
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
- 날짜/시간 값을 다룰 때 함께 읽히는 묶음. 불변 클래스 `DateTime`/`DateOnly`/`Time` 포맷 문자열을 다루는 `dt` 네임스페이스. 세 클래스 모두 로컬 타임존 기준으로 동작하며, 변환/산술 메서드는 원본을 바꾸지 않고 새 인스턴스를 반환한다.
3
+ 불변 날짜/시간 `DateTime`(날짜+시간)·`DateOnly`(날짜)·`Time`(시간)과 포맷 문자열을 처리하는 `dt` 네임스페이스. 세 클래스 모두 로컬 타임존 기준이며, 모든 set/add 메서드는 원본을 변경하지 않고 새 인스턴스를 반환한다. 파싱 실패 시 `ArgumentError` throw.
4
4
 
5
- ## DateTime
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
- - `DateTime.parse(str): DateTime` — 문자열 파싱. 지원: ISO 8601, `yyyy-MM-dd HH:mm:ss(.fff)`, `yyyyMMddHHmmss`, `yyyy-MM-dd AM/PM HH:mm:ss`, 한국어 `오전/오후`. 실패 시 `ArgumentError` throw.
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
- - `addYears/addMonths(n)` — `setYear/setMonth` 경유라 말일 보정 규칙을 따름.
30
- - `addDays/addHours/addMinutes/addSeconds/addMilliseconds(n)` 음수 가능. 시 이하는 tick 기반 가산이라 DST 경계를 그대로 통과.
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
- - `toFormatString(formatStr): string` 포맷 문자열로 변환(아래 `dt.format` 토큰 참조).
34
- - `toString(): string` — `yyyy-MM-ddTHH:mm:ss.fffzzz` 형식.
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
- const d = DateTime.parse("2025-01-15 10:30:00");
38
- d.addDays(1).toFormatString("yyyy-MM-dd (ddd)"); // "2025-01-16 (목)"
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
- 시간 제외 날짜만(`yyyy-MM-dd`) 불변 클래스. 주차(week) 계산 메서드 포함.
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
- 주차 계산 — 공통 인자 `weekStartDay`(주 시작 요일, 0=일~6=토, 기본 1=월)·`minDaysInFirstWeek`(첫 주로 인정할 최소 일수 1~7, 기본 4=ISO 8601 표준):
54
- - `getBaseYearMonthSeqForWeekSeq(weekStartDay?, minDaysInFirstWeek?): { year, monthSeq }` — 이 날짜가 속한 주의 기준 연·월. 월 경계 주를 이전/다음 달 중 어디로 귀속할지 판정.
55
- - `getWeekSeqStartDate(weekStartDay?, minDaysInFirstWeek?): DateOnly` — 이 날짜가 속한 주의 시작일.
56
- - `getWeekSeqOfYear(weekStartDay?, minDaysInFirstWeek?): { year, weekSeq }` 연 기준 주차 번호.
57
- - `getWeekSeqOfMonth(weekStartDay?, minDaysInFirstWeek?): { year, monthSeq, weekSeq }` — 월 기준 주차 번호.
58
- - `DateOnly.getDateByYearWeekSeq(arg, weekStartDay?, minDaysInFirstWeek?): DateOnly` — `arg: { year, month?, weekSeq }` 로 해당 주의 시작일 역산. `month` 생략 시 연 단위 주차.
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
- 포맷: `toFormatString(formatStr)`, `toString()` `yyyy-MM-dd`.
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, 15).getWeekSeqOfMonth(); // { year: 2025, monthSeq: 1, weekSeq: 3 }
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
- 날짜 제외 시간만(`HH:mm:ss.fff`) 불변 클래스. 24시간을 넘거나 음수인 값은 자동으로 0~24 범위로 순환 정규화됨.
69
-
70
- 생성자: `new Time()`(현재 시각의 시간부) / `(hour, minute, second?, millisecond?)` / `(tick)`(하루 내 밀리초) / `(date)`(Date 의 시간부만).
84
+ 날짜 없이 시각(HH:mm:ss.fff) 담는 불변 클래스. 24시간을 넘거나 음수인 tick 24시간 순환으로 정규화된다.
71
85
 
72
- - `Time.parse(str): Time` — `HH:mm:ss(.fff)`, `AM/PM HH:mm:ss`, ISO 8601(시간부만 추출) 지원. 실패 시 `ArgumentError`.
73
-
74
- getter: `hour`/`minute`/`second`/`millisecond`/`tick`(하루 밀리초)/`isValid`.
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
- 변환: `setHour/setMinute/setSecond/setMillisecond(n)`.
77
- 산술: `addHours/addMinutes/addSeconds/addMilliseconds(n)` 24시간 순환(23:30 + 1h 00:30).
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
- 포맷: `toFormatString(formatStr)`, `toString()` → `HH:mm:ss.fff`.
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"`. 위 클래스의 `toFormatString` 내부 구현이자, 직접 호출도 가능한 포맷터.
113
+ `import { dt } from "@simplysm/core-common"`. 위 클래스들이 내부에서 쓰는 포맷/정규화 함수를 직접 노출.
84
114
 
85
- - `dt.format(formatString, args): string` — `args: { year?, month?, day?, hour?, minute?, second?, millisecond?, timezoneOffsetMinutes? }` 중 제공된 구성요소만 치환(미제공 토큰은 원문 유지). 요일(`ddd`)은 year·month·day 가 모두 있을 때만 계산.
86
- - `dt.normalizeMonth(year, month, day): { year, month, day }` 1~12 범위 월을 연도로 넘기고 말일 보정.
87
- - `dt.convert12To24(rawHour, isPM): number` 12시간제(1~12)+오전/오후 24시간제(0~23). 12 AM=0, 12 PM=12.
88
- - 타입 `dt.DtNormalizedMonth = { year, month, day }`.
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
- 포맷 토큰(C# 호환): `yyyy`(4자리 )·`yy`(2자리), `MM`/`M`(0채움/일반 월), `ddd`(요일 한글 일~토), `dd`/`d`(일), `tt`(AM/PM), `hh`/`h`(12시간), `HH`/`H`(24시간), `mm`/`m`(분), `ss`/`s`(초), `fff`/`ff`/`f`(밀리초 3/2/1자리), `zzz`(±HH:mm)·`zz`(±HH)·`z`(±H) 타임존 오프셋.
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 tt h:mm", { year: 2024, month: 3, day: 15, hour: 14, minute: 30 });
94
- // "2024-03-15 PM 2:30"
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
+ ```