@simplysm/sd-claude 14.0.82 → 14.0.83

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 (87) hide show
  1. package/claude/references/sd-requirement-source-handling.md +20 -20
  2. package/claude/references/sd-simplysm14/README.md +13 -13
  3. package/claude/references/sd-simplysm14/manuals/client-component.md +92 -92
  4. package/claude/references/sd-simplysm14/manuals/client-crud.md +11 -11
  5. package/claude/references/sd-simplysm14/manuals/client-demo.md +28 -28
  6. package/claude/references/sd-simplysm14/manuals/client-rules.md +1 -1
  7. package/claude/references/sd-simplysm14/manuals/client-setup.md +21 -21
  8. package/claude/references/sd-simplysm14/manuals/client-tab.md +3 -3
  9. package/claude/references/sd-simplysm14/manuals/logging.md +15 -15
  10. package/claude/references/sd-simplysm14/manuals/orm-union.md +6 -6
  11. package/claude/references/sd-simplysm14/manuals/orm.md +19 -19
  12. package/claude/references/sd-simplysm14/manuals/test.md +33 -33
  13. package/claude/rules/sd-base-rules.md +44 -43
  14. package/claude/rules/sd-design-rules.md +18 -18
  15. package/claude/skills/sd-commit/SKILL.md +10 -10
  16. package/claude/skills/sd-config/SKILL.md +2 -2
  17. package/claude/skills/sd-demo/SKILL.md +45 -45
  18. package/claude/skills/sd-dev/SKILL.md +15 -15
  19. package/claude/skills/sd-docs/SKILL.md +7 -7
  20. package/claude/skills/sd-docs/references/subagent-prompt.md +33 -33
  21. package/claude/skills/sd-impl/SKILL.md +60 -60
  22. package/claude/skills/sd-review/SKILL.md +9 -9
  23. package/claude/skills/sd-skill/SKILL.md +74 -74
  24. package/claude/skills/sd-skill/evals/fixtures/existing-skill/.claude/skills/todo-format/SKILL.md +1 -1
  25. package/claude/skills/sd-spec/SKILL.md +355 -319
  26. package/claude/skills/sd-spec/references/example-spec.md +104 -104
  27. package/claude/skills/sd-unpack/SKILL.md +34 -34
  28. package/claude/skills/sd-use/SKILL.md +4 -4
  29. package/package.json +1 -1
  30. package/claude/references/sd-simplysm14/apis/angular/README.md +0 -37
  31. package/claude/references/sd-simplysm14/apis/angular/app-structure.md +0 -92
  32. package/claude/references/sd-simplysm14/apis/angular/buttons.md +0 -88
  33. package/claude/references/sd-simplysm14/apis/angular/crud.md +0 -100
  34. package/claude/references/sd-simplysm14/apis/angular/forms.md +0 -200
  35. package/claude/references/sd-simplysm14/apis/angular/infrastructure.md +0 -231
  36. package/claude/references/sd-simplysm14/apis/angular/kanban.md +0 -80
  37. package/claude/references/sd-simplysm14/apis/angular/layout.md +0 -92
  38. package/claude/references/sd-simplysm14/apis/angular/modal.md +0 -115
  39. package/claude/references/sd-simplysm14/apis/angular/routing.md +0 -107
  40. package/claude/references/sd-simplysm14/apis/angular/select-dropdown.md +0 -35
  41. package/claude/references/sd-simplysm14/apis/angular/selection-managers.md +0 -82
  42. package/claude/references/sd-simplysm14/apis/angular/shared-data.md +0 -134
  43. package/claude/references/sd-simplysm14/apis/angular/sheet.md +0 -127
  44. package/claude/references/sd-simplysm14/apis/angular/toast.md +0 -97
  45. package/claude/references/sd-simplysm14/apis/angular/visual.md +0 -167
  46. package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +0 -79
  47. package/claude/references/sd-simplysm14/apis/capacitor-plugin-file-system/README.md +0 -83
  48. package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +0 -91
  49. package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +0 -49
  50. package/claude/references/sd-simplysm14/apis/core-browser/README.md +0 -143
  51. package/claude/references/sd-simplysm14/apis/core-common/README.md +0 -58
  52. package/claude/references/sd-simplysm14/apis/core-common/extensions.md +0 -88
  53. package/claude/references/sd-simplysm14/apis/core-common/features.md +0 -51
  54. package/claude/references/sd-simplysm14/apis/core-common/types.md +0 -88
  55. package/claude/references/sd-simplysm14/apis/core-common/utils.md +0 -189
  56. package/claude/references/sd-simplysm14/apis/core-node/README.md +0 -12
  57. package/claude/references/sd-simplysm14/apis/core-node/consola.md +0 -59
  58. package/claude/references/sd-simplysm14/apis/core-node/cpx.md +0 -44
  59. package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +0 -42
  60. package/claude/references/sd-simplysm14/apis/core-node/fsx.md +0 -53
  61. package/claude/references/sd-simplysm14/apis/core-node/pathx.md +0 -24
  62. package/claude/references/sd-simplysm14/apis/core-node/worker.md +0 -65
  63. package/claude/references/sd-simplysm14/apis/excel/README.md +0 -193
  64. package/claude/references/sd-simplysm14/apis/lint/README.md +0 -94
  65. package/claude/references/sd-simplysm14/apis/orm-common/README.md +0 -58
  66. package/claude/references/sd-simplysm14/apis/orm-common/db-context.md +0 -77
  67. package/claude/references/sd-simplysm14/apis/orm-common/executable.md +0 -20
  68. package/claude/references/sd-simplysm14/apis/orm-common/expr.md +0 -92
  69. package/claude/references/sd-simplysm14/apis/orm-common/queryable.md +0 -98
  70. package/claude/references/sd-simplysm14/apis/orm-common/schema-builders.md +0 -128
  71. package/claude/references/sd-simplysm14/apis/orm-node/README.md +0 -69
  72. package/claude/references/sd-simplysm14/apis/sd-claude/README.md +0 -32
  73. package/claude/references/sd-simplysm14/apis/sd-cli/README.md +0 -80
  74. package/claude/references/sd-simplysm14/apis/sd-cli/sd-config.md +0 -155
  75. package/claude/references/sd-simplysm14/apis/service-client/README.md +0 -131
  76. package/claude/references/sd-simplysm14/apis/service-common/README.md +0 -29
  77. package/claude/references/sd-simplysm14/apis/service-common/app-structure.md +0 -63
  78. package/claude/references/sd-simplysm14/apis/service-common/messages.md +0 -56
  79. package/claude/references/sd-simplysm14/apis/service-common/protocol.md +0 -64
  80. package/claude/references/sd-simplysm14/apis/service-common/service-types.md +0 -43
  81. package/claude/references/sd-simplysm14/apis/service-server/README.md +0 -13
  82. package/claude/references/sd-simplysm14/apis/service-server/auth.md +0 -39
  83. package/claude/references/sd-simplysm14/apis/service-server/builtin-services.md +0 -71
  84. package/claude/references/sd-simplysm14/apis/service-server/define-service.md +0 -55
  85. package/claude/references/sd-simplysm14/apis/service-server/internals.md +0 -82
  86. package/claude/references/sd-simplysm14/apis/service-server/server.md +0 -57
  87. package/claude/references/sd-simplysm14/apis/storage/README.md +0 -71
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## `sd-crud-list`
6
6
 
7
- 목록 화면의 표준 골격. 시트 + 검색 + 등록/삭제/복구 버튼 + CTRL+S 저장 + 모달 선택 모드를 한꺼번에 처리.
7
+ 목록 화면의 표준 골격. 다음 기능을 일괄 제공: 시트, 검색 폼, 등록/삭제/복구 버튼, CTRL+S 단축키 저장, 모달 선택 모드.
8
8
 
9
9
  ### 표준 호출
10
10
 
@@ -39,14 +39,14 @@
39
39
  </sd-crud-list>
40
40
  ```
41
41
 
42
- ### 슬롯 약속
42
+ ### 슬롯 규약
43
43
 
44
44
  | 슬롯 | 용도 |
45
45
  | ------------------- | -------------------------------------------------------------------------- |
46
46
  | `#filterTpl` | 검색 폼 필드. 있으면 상단에 조회 버튼과 함께 노출. |
47
47
  | `#toolTpl` | 등록/삭제 버튼 옆 추가 도구 버튼. |
48
- | `#commandTpl` | 상단(또는 modal/control 모드의 명령 영역) 추가 액션 버튼. |
49
- | `#bottomCommandTpl` | modal 하단 좌측 영역. modal + selectMode "선택 해제/확인" 함께 표시. |
48
+ | `#commandTpl` | 상단 명령 영역(viewType 이 `modal`·`control` 인 경우 해당 모드의 명령 영역) 추가 액션 버튼. |
49
+ | `#bottomCommandTpl` | modal 하단 좌측 영역. modal + selectMode 경우 "선택 해제/확인" 버튼과 함께 표시. |
50
50
 
51
51
  `<sd-sheet-column>` 은 `<sd-crud-list>` 의 직속 자식으로 두면 내부 시트로 자동 투영됨.
52
52
 
@@ -54,20 +54,20 @@
54
54
 
55
55
  - **`'page'`** — 라우팅 진입 단위. 상단에 저장 버튼.
56
56
  - **`'control'`** — view 안에 임베드. 명령 영역에 저장 버튼.
57
- - **`'modal'`** — 모달. `selectMode` 와 함께 쓰면 close 페이로드 `{ selectedKeys }` 자동 처리.
57
+ - **`'modal'`** — 모달. `selectMode` 와 함께 쓰면 close `{ selectedKeys }` 페이로드를 자동 전달.
58
58
 
59
59
  ### 모달 선택 모드
60
60
 
61
61
  `viewType="modal"` + `selectMode` 지정 시:
62
62
 
63
63
  - `single` — 행 클릭 즉시 modal close.
64
- - `multi` — 하단 "확인(N)" 버튼이 close 발생.
64
+ - `multi` — 하단 "확인(N)" 버튼 클릭으로 modal close.
65
65
 
66
- 호출측은 `_sdModal.showAsync(...)` 결과로 `{ selectedKeys }` 페이로드 회수.
66
+ 호출측은 `_sdModal.showAsync(...)` 결과로 `{ selectedKeys }` 페이로드 수신.
67
67
 
68
68
  ## `sd-crud-detail`
69
69
 
70
- 단일 레코드 편집 화면의 표준 골격. 폼 래핑 + CTRL+S/저장 버튼 + 모달의 "확인" 버튼 자동 처리.
70
+ 단일 레코드 편집 화면의 표준 골격. 다음 기능을 일괄 제공: 래핑, CTRL+S 단축키 저장, 저장 버튼, 모달의 "확인" 버튼 자동 처리.
71
71
 
72
72
  ### 표준 호출
73
73
 
@@ -87,11 +87,11 @@
87
87
  </sd-crud-detail>
88
88
  ```
89
89
 
90
- ### 슬롯 약속
90
+ ### 슬롯 규약
91
91
 
92
92
  | 슬롯 | 용도 |
93
93
  | -------------------- | ----------------------------------------------------------------- |
94
- | `#contentTpl` (필수) | 폼 본문. `readonly` 면 `<sd-form>` 래핑 없이 그냥 표시된다. |
94
+ | `#contentTpl` (필수) | 폼 본문. `readonly` 면 `<sd-form>` 래핑 없이 그대로 표시. |
95
95
  | `#commandTpl` | 상단/명령 영역 추가 버튼. |
96
96
  | `#bottomCommandTpl` | modal 하단 좌측. modal 일 때 우측 "확인" 버튼이 항상 자동 추가됨. |
97
97
 
@@ -99,4 +99,4 @@
99
99
 
100
100
  - **`'page'`** — 라우팅 진입 단위. 상단에 저장 버튼.
101
101
  - **`'control'`** — view 안에 임베드. 명령 영역에 저장 버튼.
102
- - **`'modal'`** — 모달. 하단 우측 "확인" 버튼이 자동.
102
+ - **`'modal'`** — 모달. 하단 우측에 "확인" 버튼이 자동으로 추가.
@@ -1,39 +1,39 @@
1
1
  # 클라이언트 데모 작성 매뉴얼
2
2
 
3
- `@simplysm/angular` v14 로 spec.md §4.x 화면을 **데모** 로 옮길 때의 추가 처방. 컴포넌트 일반 규약(파일명·시그널·DI·핸들러·시트·폼·버튼·모달 호출·합성 패턴 등)은 [client-component.md](./client-component.md). 본 문서는 spec § 산출물 매핑과 데모 한정 패턴만 다룸.
3
+ `@simplysm/angular` v14 로 spec.md 의 화면 정의 섹션(4 섹션) **데모** 로 옮길 때의 추가 지침. 컴포넌트 일반 규약(파일명·시그널·DI(의존성 주입)·핸들러·시트·폼·버튼·모달 호출·합성 패턴 등) 은 [client-component.md](./client-component.md) 를 따름. 본 문서는 spec 섹션과 산출물의 매핑 데모 한정 패턴만 다룸.
4
4
 
5
- ## §4.x 화면 유형 파일 역할
5
+ ## 화면 정의 섹션(4 섹션) 의 화면 유형별 파일 역할
6
6
 
7
- | §4.x 패턴 | 파일 역할 |
7
+ | 화면 유형 패턴 | 파일 역할 |
8
8
  | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
9
9
  | 마스터(체크박스·`[E N]`·5버튼바) / 시트 단일 | `<domain>.list.ts` |
10
10
  | 단건 입력 폼 | `<domain>.detail.ts` |
11
11
  | 좌 목록 + 우 단건 | `<domain>.view.ts` + `.list.ts` + `.detail.ts` |
12
12
  | 좌 헤더 목록 + 우(헤더 정보 + 라인 시트) 마스터-라인 | `<domain>.view.ts` + `.list.ts` + `.detail.ts` — 우 라인 영역은 `.detail.ts` (헤더 단건 + 라인) |
13
- | 모달 (§동작에 `→ [화면.X] 을 모달로 띄움`) | `<domain>.modal.ts` |
13
+ | 모달 (동작 섹션에 `→ [화면.X] 을 모달로 띄움` 표기) | `<domain>.modal.ts` |
14
14
  | 프린트 양식 | `<domain>.print-template.ts` |
15
15
 
16
- `<domain>` = 화면명 dash-case 영문 음역 슬러그. 같은 도메인 폴더에 같은 역할이 2개 이상이면 `<domain>-<갈래>.<역할>.ts`.
16
+ `<domain>` 화면명을 dash-case 영문으로 음역한 슬러그. 같은 도메인 폴더에 같은 역할의 파일이 2개 이상이면 `<domain>-<갈래>.<역할>.ts` 형식 사용.
17
17
 
18
- ## 와이어프레임 권위
18
+ ## 와이어프레임 기준
19
19
 
20
- §4.x 와이어프레임이 모든 시각 요소(버튼·필터·시트·탭·검색)의 **존재·영역·순서** 결정의 1순위. 표준 슬롯/기본 UI 와 충돌 와이어프레임에 맞춰 슬롯을 비우거나 컴포넌트 교체. 줄 수·픽셀 좌표는 권위 X (폼 inline 자동 wrap 표현 한계).
20
+ 화면 정의 섹션(4 섹션) 의 와이어프레임이 모든 시각 요소(버튼·필터·시트·탭·검색) 의 **존재·영역·순서** 결정의 1순위. 표준 슬롯이나 기본 UI(사용자 인터페이스) 충돌하면 와이어프레임에 맞춰 슬롯을 비우거나 컴포넌트를 교체. 줄 수·픽셀 좌표는 기준이 아님 (폼 inline 자동 wrap 등의 표현 한계 때문).
21
21
 
22
- `sd-crud-list` 의 표준 출력(`(create)/(delete)/(restore)`) 이 와이어프레임의 명시 버튼 위치를 가린다면 표준 출력 채택을 포기하고 슬롯 안에 `sd-button` 으로 직접 배치.
22
+ `sd-crud-list` 의 표준 출력(`(create)/(delete)/(restore)`) 이 와이어프레임에 명시된 버튼 위치를 가린다면 표준 출력 사용을 포기하고 슬롯 안에 `sd-button` 으로 직접 배치.
23
23
 
24
- ## 항목표 `종류` 입력 컨트롤
24
+ ## 항목표의 `종류` 컬럼을 입력 컨트롤로 매핑
25
25
 
26
- [client-component.md "표준 입력 컨트롤"](./client-component.md) 적용. spec 의 `종류` 표기면 가장 가까운 매핑 + `// sd-demo: 종류 매핑 임의 — 확인 필요` 마커.
26
+ [client-component.md "표준 입력 컨트롤"](./client-component.md) 매핑 표를 적용. spec 의 `종류` 값이 매핑 표에 없는 표기이면 가장 가까운 매핑을 적용하고 `// sd-demo: 종류 매핑 임의 — 확인 필요` 마커를 부착.
27
27
 
28
28
  ## 도메인 데이터 (더미)
29
29
 
30
- - 컴포넌트 파일 안에 `interface DemoXxx { ... }` spec §7 필드명만 일치하게 선언. 위에 `// sd-demo: 더미 타입 — 구현 단계에서 @모델/X 로 교체` 마커.
31
- - list 의 `items` / detail 의 `data` 는 이 인라인 타입으로 signal 선언.
32
- - 더미 데이터 1~수건 인라인 + `// sd-demo: 더미 — 구현 단계에서 교체` 마커.
30
+ - 컴포넌트 파일 안에 `interface DemoXxx { ... }` 형태로, spec 의 데이터 모델 섹션(7 섹션) 필드명과 일치하게 선언. 인터페이스 위에 `// sd-demo: 더미 타입 — 구현 단계에서 @모델/X 로 교체` 마커를 부착.
31
+ - list 의 `items` detail 의 `data` 는 이 인라인 타입으로 signal 선언.
32
+ - 더미 데이터 1 이상을 인라인으로 두고 `// sd-demo: 더미 — 구현 단계에서 교체` 마커를 부착.
33
33
 
34
34
  ## 권한 path
35
35
 
36
- spec 에 권한 path 명시 없으면 임의 추정 + 마커:
36
+ spec 에 권한 path 명시되지 않으면 임의 추정값을 사용하고 마커를 부착:
37
37
 
38
38
  ```ts
39
39
  perms = injectPermsSignal(
@@ -42,11 +42,11 @@ perms = injectPermsSignal(
42
42
  );
43
43
  ```
44
44
 
45
- `restricted`/`readonly` 인라인 전달은 [client-component.md "권한 (perms)"](./client-component.md) 그대로.
45
+ `restricted`/`readonly` 인라인 전달 방식은 [client-component.md "권한 (perms)"](./client-component.md) 의 규약을 그대로 따름.
46
46
 
47
- ## 액션 핸들러 (시뮬레이션 X)
47
+ ## 액션 핸들러 (시뮬레이션 금지)
48
48
 
49
- 데이터 변경 결과 반영 금지. 본문은 마커만:
49
+ 데이터 변경 결과를 화면에 반영하지 말 것. 핸들러 본문은 마커만 둘 것:
50
50
 
51
51
  ```ts
52
52
  async onSaveClick() {
@@ -61,7 +61,7 @@ async onSaveClick() {
61
61
 
62
62
  ## 모달
63
63
 
64
- 호출 측 후처리도 마커:
64
+ 호출 측 후처리도 마커만 둘 것:
65
65
 
66
66
  ```ts
67
67
  const result = await this._sdModal.showAsync({ ... });
@@ -69,15 +69,15 @@ if (!result) return;
69
69
  // sd-demo: 미구현 — 동작 자리
70
70
  ```
71
71
 
72
- 영역 한정 호출 (`→ [화면.Y] 의 <영역> — 선택 전용` 등) 은 모달 입력 시그널(`selectMode` 등)로 전달.
72
+ 영역 한정 호출(`→ [화면.Y] 의 <영역> — 선택 전용` 등) 은 모달의 입력 시그널(`selectMode` 등) 로 전달.
73
73
 
74
- 피호출 모달: `<sd-crud-detail>` 루트(`viewType='modal'` 자동 주입). 임의 `close` output 약속 만들지 X — `_sdModal.showAsync` 의 페이로드 회수 약속만 사용.
74
+ 피호출 모달은 `<sd-crud-detail>` 을 루트로 사용 (`viewType='modal'` 자동 주입). 임의 `close` output 규약을 만들지 — `_sdModal.showAsync` 의 페이로드 반환 규약만 사용.
75
75
 
76
- **동반 모달**: §동작에 `→ [화면.Y] 을 모달로 띄움` 으로 등장하는 모든 모달은 같은 호출에서 함께 생성. 이미 존재하면 재사용.
76
+ **동반 모달**: 동작 섹션에 `→ [화면.Y] 을 모달로 띄움` 으로 등장하는 모든 모달은 같은 호출에서 함께 생성. 이미 존재하면 재사용.
77
77
 
78
- ## 양식 매핑 (엑셀 업/다운로드)
78
+ ## 양식 매핑 (엑셀 업로드·다운로드)
79
79
 
80
- §4.x 에 양식 매핑 있으면 `#toolTpl` 에 버튼만 (핸들러 본문은 마커):
80
+ 화면 정의 섹션(4 섹션) 에 양식 매핑 표가 있으면 `#toolTpl` 에 버튼만 배치 (핸들러 본문은 마커만):
81
81
 
82
82
  ```html
83
83
  <ng-template #toolTpl>
@@ -92,7 +92,7 @@ if (!result) return;
92
92
 
93
93
  ## 상태별 와이어프레임
94
94
 
95
- spec `와이어프레임 (확정 전):` / `와이어프레임 (확정 후):` 로 분리하면:
95
+ spec 에서 와이어프레임이 `와이어프레임 (확정 전):` `와이어프레임 (확정 후):` 로 분리되어 있으면 다음과 같이 처리:
96
96
 
97
97
  ```ts
98
98
  // sd-demo: 더미 — 구현 단계에서 교체
@@ -109,7 +109,7 @@ state = signal<"draft" | "confirmed">("draft");
109
109
 
110
110
  ## "선택하세요." 빈 화면
111
111
 
112
- list+detail 합성 view 빈 상태는 시연 시 눈에 띄게:
112
+ listdetail 합성 view 에서 항목 미선택 빈 상태는 시연 시 눈에 띄도록 작성:
113
113
 
114
114
  ```html
115
115
  <div
@@ -121,8 +121,8 @@ list+detail 합성 view 의 빈 상태는 시연 시 눈에 띄게:
121
121
  </div>
122
122
  ```
123
123
 
124
- [client-component.md "list + detail 합성"](./client-component.md) 의 단순 `<div class="flex-fill p-default">선택하세요.</div>` 사례로 대체.
124
+ [client-component.md "list + detail 합성"](./client-component.md) 의 단순 `<div class="flex-fill p-default">선택하세요.</div>` 코드를 사례로 대체.
125
125
 
126
- ## 라우팅·메뉴 답습
126
+ ## 라우팅·메뉴 따라가기
127
127
 
128
- 답습 화면 1개의 라우팅·메뉴 등록 위치·방식 그대로. 라우트 경로는 화면명 dash-case 영문 음역 슬러그 (기존 슬러그 규칙 일관 답습). 메뉴 라벨은 spec 의 화면명 그대로.
128
+ 기존 화면 1개의 라우팅·메뉴 등록 위치·방식을 그대로 따름. 라우트 경로는 화면명을 dash-case 영문으로 음역한 슬러그 사용 (프로젝트의 기존 슬러그 규칙이 일관되어 있다면 그 규칙을 따름). 메뉴 라벨은 spec 의 화면명을 그대로 사용.
@@ -4,4 +4,4 @@
4
4
 
5
5
  ## Angular template 규칙
6
6
 
7
- - **`$any` 사용 금지** (lint error). template 에서 타입이 풀리면 컴포넌트 클래스의 타입 설계를 바로잡아 해결. `eslint-disable-next-line` 으로 우회 금지.
7
+ - **`$any` 사용 금지** (lint 규칙 위반). template 에서 타입 오류가 발생하면 컴포넌트 클래스의 타입 설계를 바로잡아 해결. `eslint-disable-next-line` 으로 우회 금지.
@@ -1,10 +1,10 @@
1
1
  # 클라이언트 환경 셋업 매뉴얼
2
2
 
3
- 화면 작성 시점에는 거의 건드리지 않으며, 새 앱 부트스트랩이나 새 서비스/마스터 데이터 추가 시에만 봄.
3
+ 화면 작성 시점에는 거의 건드리지 않음. 새 앱 부트스트랩 시 또는 새 서비스/마스터 데이터를 추가할 때만 참조.
4
4
 
5
5
  ## AppServiceProvider
6
6
 
7
- `@simplysm/service-client` 위에 앱이 만드는 root provider. 서비스/이벤트 프록시와 ORM 커넥터를 lazy 캐싱으로 노출함.
7
+ `@simplysm/service-client` 위에 앱이 만드는 root provider. 서비스·이벤트 프록시와 ORM(Object-Relational Mapping) 커넥터를 lazy 캐싱으로 노출.
8
8
 
9
9
  ```ts
10
10
  @Injectable({ providedIn: "root" })
@@ -41,14 +41,14 @@ export class AppServiceProvider {
41
41
  **약속**:
42
42
 
43
43
  - `@Injectable({ providedIn: "root" })`.
44
- - 서비스/이벤트는 `private _xxx?` 캐시 필드 + getter 로 노출. lazy 초기화는 `??=` 패턴.
45
- - 서비스: `client.getService<XxxServiceType>("XxxName")`. 이벤트: `client.getEvent<typeof XxxEvent>("XxxName")`.
46
- - ORM 커넥터: `createOrmClientConnector(this.client)` `orm` getter.
47
- - `connectAsync()` — 앱 부트스트랩 서버 연결.
44
+ - 서비스·이벤트는 `private _xxx?` 캐시 필드 + getter 로 노출. lazy 초기화는 `??=` 패턴 사용.
45
+ - 서비스: `client.getService<XxxServiceType>("XxxName")` 호출. 이벤트: `client.getEvent<typeof XxxEvent>("XxxName")` 호출.
46
+ - ORM 커넥터: `createOrmClientConnector(this.client)` 결과를 `orm` getter 로 노출.
47
+ - `connectAsync()` — 앱 부트스트랩 시점에 서버 연결 수행.
48
48
 
49
49
  ## AppOrmProvider
50
50
 
51
- `AppServiceProvider.orm` 위에 앱별 DB 설정(DbContext / 데이터베이스 / 스키마)을 고정해 둔 root provider.
51
+ `AppServiceProvider.orm` 위에 앱별 DB(데이터베이스) 설정(DbContext·데이터베이스명·스키마명)을 고정해 둔 root provider.
52
52
 
53
53
  ```ts
54
54
  @Injectable({ providedIn: "root" })
@@ -78,13 +78,13 @@ export class AppOrmProvider {
78
78
  **약속**:
79
79
 
80
80
  - `@Injectable({ providedIn: "root" })`.
81
- - DbContext 는 앱별 정의 (예: `@adtek/db-main` 의 `MainDbContext`).
82
- - 기본은 `connectAsync` (트랜잭션). `connectWithoutTransAsync` 는 initialize 등 트랜잭션 안에서 동작하지 않는 작업용 헬퍼.
83
- - 콜백 반환값이 그대로 메서드 반환값.
81
+ - DbContext 는 앱별로 정의 (예: `@adtek/db-main` 의 `MainDbContext`).
82
+ - 기본 메서드는 `connectAsync` (트랜잭션 포함). `connectWithoutTransAsync` 는 initialize 등 트랜잭션 안에서 동작하지 않는 작업 전용 헬퍼.
83
+ - 콜백의 반환값이 그대로 메서드의 반환값이 됨.
84
84
 
85
85
  ## AppSharedDataProvider
86
86
 
87
- `@simplysm/angular` 의 `SdSharedDataProvider` 를 상속해, 화면에서 자주 참조하는 마스터 데이터(고객사·품목·로케이션 등)를 등록한 root provider.
87
+ `@simplysm/angular` 의 `SdSharedDataProvider` 를 상속하여 화면에서 자주 참조하는 마스터 데이터(고객사·품목·로케이션 등)를 등록한 root provider.
88
88
 
89
89
  ```ts
90
90
  export function useSharedSignal<K extends keyof TAppSharedData>(
@@ -142,13 +142,13 @@ export interface ISharedCustomer extends SharedDataBase<number> {
142
142
 
143
143
  **약속**:
144
144
 
145
- - `@Injectable({ providedIn: "root" })`, `SdSharedDataProvider<TAppSharedData>` 상속.
146
- - 등록은 `override initialize()` 안에서 `this.register(name, opts)`.
147
- - 각 항목 인터페이스는 `SharedDataBase<TKey>` 상속.
148
- - getter 의 select 결과에 매직 필드 포함:
149
- - `__valueKey` — 항목 키.
150
- - `__searchText` — 검색 텍스트.
151
- - `__isHidden` — 숨김 여부 (예시: `isDeleted`).
152
- - `getter(changeKeys)` 있으면 키들만 다시 조회 (incremental refresh).
153
- - `orderBy` 는 정렬 키를 반환하는 함수 (`(item) => item.code`).
154
- - `useSharedSignal<K>(name)` 헬퍼 함수를 같이 export — 컴포넌트는 inject 없이 이름만으로 접근.
145
+ - `@Injectable({ providedIn: "root" })` 사용, `SdSharedDataProvider<TAppSharedData>` 상속.
146
+ - 등록은 `override initialize()` 안에서 `this.register(name, opts)` 호출로 수행.
147
+ - 각 항목의 인터페이스는 `SharedDataBase<TKey>` 상속.
148
+ - getter 의 select 결과에 다음 매직 필드를 포함:
149
+ - `__valueKey` — 항목의 키.
150
+ - `__searchText` — 검색용 텍스트.
151
+ - `__isHidden` — 숨김 여부 (예: `isDeleted` 값으로 지정).
152
+ - `getter(changeKeys)` `changeKeys` 인자가 주어지면 해당 키들만 다시 조회 (incremental refresh).
153
+ - `orderBy` 는 정렬 키를 반환하는 함수 (예: `(item) => item.code`).
154
+ - `useSharedSignal<K>(name)` 헬퍼 함수를 함께 export — 컴포넌트는 inject 없이 이름만으로 접근 가능.
@@ -1,6 +1,6 @@
1
1
  # 탭 컨트롤 사용법
2
2
 
3
- `<sd-tab>` 은 **선택기**. 콘텐츠 컨테이너가 아님. 콘텐츠는 바깥에서 `@if` / `@switch` 분기 렌더함.
3
+ `<sd-tab>` 은 **현재 탭 값을 고르는 선택 컨트롤**. 콘텐츠 컨테이너 아님. 각 탭에 대응하는 콘텐츠는 `<sd-tab>` 바깥에서 Angular 제어 흐름(`@if` / `@switch`)으로 분기 렌더링.
4
4
 
5
5
  ## API
6
6
 
@@ -13,7 +13,7 @@
13
13
 
14
14
  ## 표준 패턴
15
15
 
16
- 탭은 **상단 고정 + 본문 fill** 구조. flex-column 으로 탭바를 상단 고정, 본문은 `flex-fill` 안에 `@if` 분기.
16
+ 탭은 **상단 고정 + 본문 fill** 구조. 바깥 컨테이너는 `flex-column` 으로 두고, 탭바는 상단에 고정, 본문은 `flex-fill` 영역 안에서 `@if` 분기.
17
17
 
18
18
  ```html
19
19
  <div class="flex-column fill">
@@ -39,4 +39,4 @@ activeTab = signal<"info" | "history">("info");
39
39
  ```
40
40
 
41
41
  - 시그널 타입은 **literal union** 으로 잡아 오타·리네이밍 안전성 확보.
42
- - 초기값은 반드시 `<sd-tab-item>` 중 하나의 `value` 와 일치 ( 맞으면 모두 미선택 상태로 시작).
42
+ - 초기값은 반드시 자식 `<sd-tab-item>` 중 하나의 `[value]` 와 일치하도록 설정 (어떤 항목과도 일치하지 않으면 모든 탭이 미선택 상태로 시작).
@@ -4,9 +4,9 @@
4
4
 
5
5
  ## 원칙
6
6
 
7
- - 모든 로그는 `createLogger(tag)` (`@simplysm/core-common`) 로 생성한 인스턴스로 출력. `console.*` 직접 호출 금지.
8
- - ESLint `no-console` 규칙은 의도된 게이트 — `eslint-disable`/`eslint-disable-next-line no-console` 우회 금지.
9
- - 메시지에 `[패키지]` 같은 수동 prefix 금지. tag 가 그 역할.
7
+ - 모든 로그는 `@simplysm/core-common` 의 `createLogger(tag)` 로 생성한 인스턴스로 출력. `console.*` 직접 호출 금지.
8
+ - ESLint `no-console` 규칙은 의도된 게이트 — `eslint-disable`·`eslint-disable-next-line no-console` 우회 금지.
9
+ - 메시지 본문에 `[패키지명]` 같은 수동 prefix 추가 금지. prefix 역할은 tag 가 담당.
10
10
 
11
11
  ## 권장 패턴
12
12
 
@@ -21,8 +21,8 @@ logger.warn("유효하지 않은 semver, 업데이트 건너뜀");
21
21
  logger.error("checkPermissions 실패", err);
22
22
  ```
23
23
 
24
- - tag 형식: `<도메인>:<역할>` 또는 `<패키지명>` 단일 토큰. 짧고 일관.
25
- - logger 변수는 모듈 최상단에서 1회 선언, 모듈 내부에서 그 변수 사용.
24
+ - tag 형식: `<도메인>:<역할>` 또는 `<패키지명>` 단일 토큰. 짧고 일관되게 작성.
25
+ - logger 변수는 모듈 최상단에서 1회 선언한 뒤, 해당 모듈 내부에서 그 변수를 재사용.
26
26
 
27
27
  ## 금지 패턴
28
28
 
@@ -44,22 +44,22 @@ console.error("[X] 실패:", err);
44
44
 
45
45
  ## 환경별 셋업
46
46
 
47
- - **Node 진입점(서버·CLI)**: 진입점에서 `setupConsola()` 1회. 자세히 [apis/core-node/consola.md](../apis/core-node/consola.md).
48
- - **Browser·Capacitor 진입점**: `setupConsola` 호출 X (Node 전용). consola 기본 reporter 가 브라우저 콘솔로 출력. tag/level/통일된 호출면 충족.
47
+ - **Node 진입점(서버·CLI)**: 진입점에서 `setupConsola()` 1 호출. 상세는 [apis/core-node/consola.md](../apis/core-node/consola.md) 참조.
48
+ - **Browser·Capacitor 진입점**: `setupConsola` 호출 금지 (Node 전용 API). consola 기본 reporter 가 브라우저 콘솔로 출력하며, tag·level·호출 방식의 일관성은 그대로 충족.
49
49
 
50
50
  ## 모듈-레벨 logger 주의
51
51
 
52
- 모듈 레벨에서 `consola.withTag()` 를 직접 호출하면 호출 시점의 options(level/reporters)가 스냅샷으로 고정되어, 이후 `setupConsola()` 가 reporters 를 갱신해도 child instance 반영되지 않음.
52
+ 모듈 레벨에서 `consola.withTag()` 를 직접 호출하면 호출 시점의 options(level·reporters)가 스냅샷으로 고정되어, 이후 `setupConsola()` 가 reporters 를 갱신해도 child instance 에는 반영되지 않음.
53
53
 
54
- - **해결**: `createLogger(tag)` 사용 (`@simplysm/core-common`, 내부 구현은 lazy Proxy 첫 메서드 접근 시점까지 `withTag` 생성을 지연).
55
- - 모든 환경(Node·브라우저·Capacitor)에서 위치(모듈 레벨·함수 내부·class field) 관계없이 `createLogger` 로 통일.
56
- - `consola.withTag()` 직접 호출 금지. 발견 시 **`createLogger` 로의 코드 교체가 무조건 의무**. tag 인자 유지·재선정 여부는 후의 부수 결정이며, "tag 그대로 쓸 수 있다" 같은 판단으로 *교체 행위 자체* 를 생략 금지.
54
+ - **해결**: `@simplysm/core-common` 의 `createLogger(tag)` 사용 (내부 구현이 lazy Proxy 첫 메서드 접근 시점까지 `withTag` 생성을 지연).
55
+ - 모든 환경(Node·브라우저·Capacitor)에서, 선언 위치(모듈 레벨·함수 내부·class field) 무관하게 `createLogger` 로 통일.
56
+ - `consola.withTag()` 직접 호출 금지. 발견 시 `createLogger` 로의 코드 교체 의무. tag 인자를 유지할지 재선정할지는 교체 이후의 부수 결정이며, "tag 그대로 쓸 수 있다" 같은 판단으로 *교체 행위 자체* 를 생략 금지.
57
57
 
58
58
  ## 예외 — `eslint-disable no-console` 가 정당화되는 자리
59
59
 
60
- 다음 경우에 한해 `/* eslint-disable no-console */` 파일 헤더를 허용함. 그 외는 모두 consola 로 교체.
60
+ 다음 경우에 한해 `/* eslint-disable no-console */` 파일 헤더 허용. 그 외는 모두 consola 로 교체.
61
61
 
62
- - **CLI 도움말·yargs help 텍스트** 처럼 stdout 자체를 사용자 출력으로 쓰는 경우 (예: `packages/sd-cli/src/sd-cli-entry.ts` 의 `collectYargsHelp`).
63
- - **ErrorHandler 마지막 안전망** 등 consola 자체가 죽었을 가능성이 있는 catch 블록 — 해당 자리만 `eslint-disable-next-line no-console` + 이유 주석.
62
+ - **CLI 도움말·yargs help 텍스트** 처럼 stdout 자체를 사용자 출력 채널로 쓰는 경우 (예: `packages/sd-cli/src/sd-cli-entry.ts` 의 `collectYargsHelp`).
63
+ - **ErrorHandler 마지막 안전망** 등 consola 자체가 죽었을 가능성이 있는 catch 블록 — 해당 라인만 `eslint-disable-next-line no-console` + 사유 주석.
64
64
 
65
- 예외 적용 시 disable 주석 위에 사유를 1줄로 남김.
65
+ 예외 적용 시 disable 주석 바로 위에 사유를 1줄로 기재.
@@ -1,15 +1,15 @@
1
1
  # ORM UNION 사용법
2
2
 
3
- [단일 쿼리 우선](./orm.md) 규칙을 지키면서 **서로 다른 엔티티를 한 목록으로** 보여줘야 할 때 씀. 코드에서 merge 하지 말고, select shape 동일하게 맞춘 두 Queryable 을 `Queryable.union(...)` 으로 합침.
3
+ [단일 쿼리 우선](./orm.md) 규칙을 지키면서 **서로 다른 엔티티를 한 목록으로** 보여줘야 할 때 씀. 두 쿼리 결과를 애플리케이션 코드에서 배열로 합치지 말고, select 결과 형태(컬럼 이름·타입·순서)를 동일하게 맞춘 두 Queryable 을 `Queryable.union(...)` 으로 DB 단에서 합침.
4
4
 
5
5
  ## 규칙
6
6
 
7
7
  - 두 쿼리의 select 컬럼 이름·타입·순서가 정확히 일치해야 함.
8
- - 종류 구분 리터럴 값은 JS 값을 그대로 씀.
9
- - 한쪽에만 있는 컬럼은 NULL 자리채움이 필요한데 타입 추론이 안 되므로 `expr.raw("<type>")\`NULL\`` 명시함.
10
- - 필터(`where`)는 union 전에 각 쿼리에 같은 조건으로 적용함 — 각 소스에서 미리 행 수를 줄여야 union 비용이 작음 (predicate pushdown). union 결과에 필터를 걸면 두 소스를 다 읽은 뒤 거르게 되고, select 컬럼만 남아 join 컬럼으로 필터도 불가함.
11
- - 총 건수는 가능하면 **각 소스에서 따로 `count` 후 합산**함. union 결과의 `count` 는 양쪽 SELECT 를 모두 실행해 머티리얼라이즈한 뒤 세지만, 각각 세면 단순 집계로 끝남. 중복 제거가 필요해 합산이 부정확해질 때만 union 후 count.
12
- - `Queryable.union(...)` 결과는 의미상 wrap 된 derived table 로, 그 위에서 호출되는 모든 fluent 연산자(`orderBy` / `limit` / `top` / `distinct` / `groupBy` / `having` / `where` / `select` / `join` 등)는 외부 union 위에 자동 적용됨. sub 적용(predicate pushdown 등)을 원하면 union 전에 각 sub-Queryable 에 미리 호출함.
8
+ - 행이 어느 소스에서 왔는지를 구분하는 식별 리터럴(예: `rowType: "IN"`)은 JS 값을 select 결과 객체에 그대로 넣음.
9
+ - 쿼리 중 한쪽 select 에만 존재하는 컬럼은 나머지 쪽에서 NULL 자리채움이 필요한데, 그냥 `null` 만 쓰면 SQL 타입 추론이 안 되므로 `` expr.raw("<type>")`NULL` `` 으로 타입을 명시함.
10
+ - 필터(`where`)는 union 전에 각 쿼리에 같은 조건으로 적용함 — 각 소스에서 미리 행 수를 줄여야 union 비용이 작음 (predicate pushdown). union 결과에 필터를 걸면 두 소스를 다 읽은 뒤 거르게 되고, select 남긴 컬럼만 접근 가능하여 원본 엔티티의 join 경로 컬럼으로는 필터링 불가함.
11
+ - 총 건수는 가능하면 **각 소스에서 따로 `count` 후 합산**함. union 결과의 `count` 는 양쪽 SELECT 를 모두 실행해 결과 집합을 만든 뒤 세지만, 각각 세면 단순 집계로 끝남. 두 소스 간 중복 제거가 필요해 단순 합산이 부정확해질 때만 union 후 count 사용.
12
+ - `Queryable.union(...)` 결과는 의미상 derived table(서브쿼리 결과를 감싼 가상 테이블)로 취급되어, 그 위에서 호출되는 모든 fluent 연산자(`orderBy` / `limit` / `top` / `distinct` / `groupBy` / `having` / `where` / `select` / `join` 등)는 외부 union 결과 위에 자동 적용됨. union 에 합쳐지기 전 각 소스(sub-Queryable, union 인자로 넘기는 개별 Queryable)에 개별 적용(predicate pushdown 등)을 원하면 `Queryable.union(...)` 호출 전에 각 Queryable 에 미리 호출함.
13
13
 
14
14
  ## 예시
15
15
 
@@ -6,7 +6,7 @@
6
6
 
7
7
  ORM 빌더로 표현 불가능한 경우, 작성 전 사용자 보고 후 중단.
8
8
 
9
- 예외: raw query로도 표현 불가하거나 단일 쿼리화가 명백히 비효율일 때만. 사유를 코드 주석에 남김.
9
+ 예외: raw query 로도 표현 불가하거나 단일 쿼리화가 명백히 비효율인 경우에 한함. 사유를 코드 주석에 남김.
10
10
 
11
11
  이종 엔티티(예: 입고 + 출고)를 한 목록으로 보여줘야 할 때 두 결과를 코드에서 merge 하지 않으려면 → [orm-union.md](./orm-union.md) 참조.
12
12
 
@@ -15,21 +15,21 @@ ORM 빌더로 표현 불가능한 경우, 작성 전 사용자 보고 후 중단
15
15
  list / sheet 화면의 본 쿼리는 다음 순서로 작성:
16
16
 
17
17
  1. root queryable 빌드 (`db.X()`).
18
- 2. 필요한 연관 데이터를 `joinSingle` 로 부착 — 본 행에 곧장 부착하지 않으면 안 되는 컬럼만. join 내부 쿼리도 동일 흐름 (`from → joinSingle → where → select`).
18
+ 2. 필요한 연관 데이터를 `joinSingle` 로 부착 — 본 행에 직접 부착해야 하는 컬럼만. join 내부 쿼리도 동일 흐름(`from → joinSingle → where → select`)을 따름.
19
19
  3. `.select((p) => ({ ...도출 컬럼들... }))` — coalesce / CASE WHEN / 산식 등 모든 도출을 한 번에 projection. select 콜백 안에서 로컬 `const` 로 산식을 잘게 나눠 가독성 확보.
20
- 4. WHERE 는 step 3 projected 컬럼 이름으로 직접 참조 (`r.psd`, `r.isCanceled`, `r.status`, ). framework 가 projected ExprUnit AST 를 WHERE 절에 그대로 inline 하므로 wrap 없이도 도출 컬럼 필터링이 동작함.
21
- 5. `count()` 로 총 건수.
22
- 6. `orderBy(...).limit(page * size, size).execute()`.
20
+ 4. WHERE 는 3 단계의 projected 컬럼 이름으로 직접 참조 (`r.psd`, `r.isCanceled`, `r.status` ). framework 가 projected ExprUnit AST 를 WHERE 절에 그대로 inline 하므로 wrap 없이도 도출 컬럼 필터링이 동작함.
21
+ 5. `count()` 로 총 건수 산출.
22
+ 6. `orderBy(...).limit(page * size, size).execute()` 로 페이지 결과 조회.
23
23
 
24
- WHERE / SELECT 양쪽에서 동일 도출 산식을 쓰겠다고 `buildDerived(p)` 같은 helper 함수 만들지 말 것 — step 3 projected 컬럼이 자동으로 그 역할을 함.
24
+ WHERE SELECT 양쪽에서 동일 도출 산식을 쓰겠다고 `buildDerived(p)` 같은 helper 함수를 만들지 말 것 — 3 단계의 projected 컬럼이 자동으로 그 역할을 함.
25
25
 
26
- 화면 첫 진입 1회만 필요하고 refresh / 필터 변경에 무관한 데이터 (필터 dropdown 옵션 등) 는 본 목록 쿼리에 섞지 말 것. 별도 1 effect 로 분리해 init 시점에만 로드함.
26
+ 화면 첫 진입 1회만 필요하고 refresh / 필터 변경에 무관한 데이터(필터 dropdown 옵션 등)는 본 목록 쿼리에 섞지 말 것. 별도의 1회성 effect 로 분리해 init 시점에만 로드함.
27
27
 
28
28
  ## 안티패턴
29
29
 
30
- ### SELECT 절 `expr.subquery` / `expr.exists` 박지 말 것
30
+ ### SELECT 절 내부에 `expr.subquery` / `expr.exists` 사용 금지
31
31
 
32
- 도메인 boolean (`isCompleted`, `hasAny` 등) / 집계 (`SUM`, `COUNT`, `MAX`) 가 필요하면 `joinSingle` 안에서 `from + where + select(aggregate)` 로 묶어 outer 행에 컬럼으로 부착함. SELECT column subquery / exists 박으면 outer 행마다 inner N 회 실행됨.
32
+ 도메인 boolean(`isCompleted`, `hasAny` 등)이나 집계(`SUM`, `COUNT`, `MAX`)가 필요하면 `joinSingle` 안에서 `from + where + select(aggregate)` 로 묶어 outer 행에 컬럼으로 부착함. SELECT 컬럼에 subquery / exists 넣으면 outer 행마다 inner 쿼리가 N 회 실행됨.
33
33
 
34
34
  ```ts
35
35
  // 나쁜 예 — 행당 subquery N회
@@ -53,23 +53,23 @@ WHERE / SELECT 양쪽에서 동일 도출 산식을 쓰겠다고 `buildDerived(p
53
53
  }))
54
54
  ```
55
55
 
56
- ### 불필요한 `wrap()`
56
+ ### 불필요한 `wrap()` 사용 금지
57
57
 
58
- `wrap()` 은 framework 가 명시 요구하는 경우에만 (대표적으로 `count()` after `distinct()` / `groupBy()` 호출 같은 명시 요구).
58
+ `wrap()` 은 framework 가 명시적으로 요구하는 경우에만 사용 (예: `distinct()` / `groupBy()` 이후 `count()` 호출).
59
59
 
60
- 도출 컬럼 위에서 필터/정렬을 걸기 위해 wrap 을 끼우는 패턴은 불필요 — `.select(...).where((r) => [...])` 만으로 framework 가 projected ExprUnit AST 를 WHERE / ORDER BY 에 inline 함.
60
+ 도출 컬럼 위에서 필터·정렬을 걸기 위해 wrap 을 끼우는 패턴은 불필요 — `.select(...).where((r) => [...])` 만으로 framework 가 projected ExprUnit AST 를 WHERE / ORDER BY 에 inline 함.
61
61
 
62
- "Layer 1 = materialize, Layer 2 = derive" 같은 다층 wrap 구조도 군더더기. 단일 select 로컬 `const` 로 산식 분리하면 동일 SQL 이 나옴.
62
+ "Layer 1 = materialize, Layer 2 = derive" 같은 다층 wrap 구조도 군더더기. 단일 select 안에서 로컬 `const` 로 산식을 분리하면 동일한 SQL 이 생성됨.
63
63
 
64
64
  ## 스키마 정의
65
65
 
66
- 컬럼은 `NOT NULL` 기본. `.nullable()`/`.default(...)` 는 도메인 근거가 있을 때만 붙임.
66
+ 컬럼은 `NOT NULL` 기본. `.nullable()` / `.default(...)` 는 도메인 근거가 있을 때만 사용.
67
67
 
68
- - `.nullable()`: 도메인상 값이 없을 수 있을 때만 (선택 입력, 미발생 이벤트 시각, 선택적 FK).
69
- - `.default(...)`: 사용자가 명시 지시한 경우에만.
70
- - "초기값 애매", "마이그레이션 중간 단계", "넣을 값 모름" 은 nullable/default 근거 아님. 호출자가 넣도록 강제하거나 backfill 후 `NOT NULL`로 전환.
68
+ - `.nullable()`: 도메인상 값이 없을 수 있을 때만 사용 (선택 입력, 미발생 이벤트 시각, 선택적 FK).
69
+ - `.default(...)`: 사용자가 명시적으로 지시한 경우에만 사용.
70
+ - "초기값 애매", "마이그레이션 중간 단계", "넣을 값 모름" 은 nullable / default 근거가 아님. 호출자가 값을 넣도록 강제하거나 backfill 후 `NOT NULL` 전환.
71
71
 
72
72
  ## 삭제 전략
73
73
 
74
- - **기초정보(마스터)**: soft delete (`isDisabled` 등). FK 참조 무결성 보존.
75
- - **프로세스 문서(트랜잭션)**: 물리 delete. 상세 포함 캐스케이드. 단, 다른 테이블이 FK로 참조 중이면 삭제 차단하고 최종 사용자에게 toast 등으로 사유 안내.
74
+ - **기초정보(마스터)**: soft delete (`isDisabled` 등) 사용. FK 참조 무결성 보존.
75
+ - **프로세스 문서(트랜잭션)**: 물리 delete. 상세 행을 포함해 캐스케이드. 단, 다른 테이블이 FK 로 참조 중이면 삭제를 차단하고 최종 사용자에게 toast 등으로 사유 안내.
@@ -1,6 +1,6 @@
1
1
  # 테스트 작성
2
2
 
3
- `@simplysm/*` v14 모노레포의 패키지 테스트(`packages/<pkg>/tests/`)와 통합 테스트(`tests/<name>/`) 작성 시 따름. Vitest project 구성·실행 명령은 루트 `CLAUDE.md` 의 "Vitest 프로젝트 구조" 참조 — 여기서는 작성 규약만 다룸.
3
+ `@simplysm/*` v14 모노레포의 패키지 테스트(`packages/<pkg>/tests/`)와 통합 테스트(`tests/<name>/`) 작성 시 따름. Vitest project 구성·실행 명령은 루트 `CLAUDE.md` 의 "Vitest 프로젝트 구조" 섹션 참조 — 문서는 작성 규약만 다룸.
4
4
 
5
5
  ## 파일 규약
6
6
 
@@ -9,73 +9,73 @@
9
9
  - 통합: `tests/<name>/src/**/*.spec.ts`.
10
10
  - 확장자.
11
11
  - `*.spec.ts` — vitest 실행 대상.
12
- - `*.acc.spec.ts` — Acceptance 단위 spec. 동일 project 에서 함께 실행.
13
- - `*.verify.md` — LLM 수동 검증 항목. vitest 실행 대상 아님. 자동화로 잡기 어려운 검증(JSDoc 표현·문서 일관성 등)에만.
14
- - import 경로: 워크스페이스 패키지는 `@simplysm/<pkg>`. 검증 목적상 내부 구현이 필요하면 상대 경로(`../src/...`).
15
- - 환경 변수: `vitest.config.ts` 진입 시 `process.env.DEV=true`/`VER=1.0.0-test` 동일 값의 `import.meta.env.*` define 자동 주입. 스펙에서 별도 설정 불필요.
12
+ - `*.acc.spec.ts` — 인수(Acceptance) 단위 spec. `*.spec.ts` 와 동일 project 에서 함께 실행.
13
+ - `*.verify.md` — LLM 수동으로 검증할 항목 기술 파일. vitest 실행 대상 아님. 자동화로 잡기 어려운 검증(JSDoc 표현·문서 일관성 등)에만 사용.
14
+ - import 경로: 워크스페이스 패키지는 `@simplysm/<pkg>` 로 import. 검증 목적상 내부 구현(public export 가 아닌 모듈)이 필요한 경우에 한해 상대 경로(`../src/...`) 허용.
15
+ - 환경 변수: `vitest.config.ts` 진입 시 `process.env.DEV=true`, `process.env.VER=1.0.0-test`, 그리고 동일 값의 `import.meta.env.*` define 으로 자동 주입됨. 스펙에서 별도 설정 불필요.
16
16
 
17
17
  ## Vitest project 매핑 (패키지 추가 시)
18
18
 
19
- `vitest.config.ts` 의 `include`/`exclude` 어느 project 에서 도는지 결정. 새 패키지 추가 시:
19
+ `vitest.config.ts` 의 `include`/`exclude` 설정이 각 스펙 파일이 어느 project 에서 실행될지를 결정. 새 패키지 추가 시:
20
20
 
21
21
  - **Node 전용**: 기본 `node` project 가 자동 include — 추가 작업 없음.
22
- - **브라우저 환경 필요** (DOM·`window`·Worker 등 사용): `node` project 의 `exclude` 에 해당 패키지 경로 추가. `browser` project 가 자동 include 함.
23
- - **Angular**: `packages/angular` 전용 project (TestBed + AOT plugin + setupFile) — 다른 패키지에서 따라하지 말 것.
22
+ - **브라우저 환경 필요** (DOM·`window`·Worker 등 사용): `node` project 의 `exclude` 에 해당 패키지 경로를 추가. `browser` project 가 자동으로 include 함.
23
+ - **Angular**: `packages/angular` 전용 project (TestBed + AOT 플러그인 + setupFile 구성) — 다른 패키지에서 이 구성을 따라하지 말 것.
24
24
 
25
25
  ## 통합 테스트 project 추가 절차
26
26
 
27
- `tests/<name>/` 디렉토리 1 = vitest project 1개. 추가 시:
27
+ `tests/<name>/` 디렉토리 1개가 vitest project 1개에 대응. 추가 절차:
28
28
 
29
- 1. `tests/<name>/package.json` — `name: "@simplysm-test/<name>"`, `"private": true`, `"type": "module"`. 필요한 `@simplysm/*` `workspace:*` 로 devDependency 등재.
30
- 2. `tests/<name>/tsconfig.json` — `extends: "../../tsconfig.json"`, `compilerOptions.typeRoots: ["./node_modules/@types"]`.
31
- 3. `tests/<name>/src/**/*.spec.ts` 스펙.
32
- 4. `vitest.config.ts` `projects[]` 에 entry 추가.
33
- - `name: "<name>"`, `include: ["tests/<name>/**/*.spec.ts"]`.
34
- - 외부 자원 기동 필요시 `globalSetup: "./tests/<name>/vitest.setup.ts"` + `setup`/`teardown` export.
35
- - 외부 자원이 단일 인스턴스 공유면 `fileParallelism: false` (스펙 파일 직렬 실행).
36
- - 브라우저 런타임 필요시 `browser: { provider: playwright(), enabled: true, headless: true, instances: [{ browser: "chromium", viewport: { width: 1920, height: 1080 } }] }`.
29
+ 1. `tests/<name>/package.json` 작성 — `name: "@simplysm-test/<name>"`, `"private": true`, `"type": "module"`. 필요한 `@simplysm/*` 패키지는 `workspace:*` 로 devDependency 등재.
30
+ 2. `tests/<name>/tsconfig.json` 작성 — `extends: "../../tsconfig.json"`, `compilerOptions.typeRoots: ["./node_modules/@types"]`.
31
+ 3. `tests/<name>/src/**/*.spec.ts` 스펙 작성.
32
+ 4. `vitest.config.ts` `projects[]` 에 entry 추가.
33
+ - `name: "<name>"`, `include: ["tests/<name>/**/*.spec.ts"]` 지정.
34
+ - 외부 자원(DB·서버 등) 기동이 필요한 경우 `globalSetup: "./tests/<name>/vitest.setup.ts"` 함께 setup 파일에서 `setup`/`teardown` 함수를 export.
35
+ - 외부 자원이 단일 인스턴스를 공유하는 경우 `fileParallelism: false` 지정 (스펙 파일 직렬 실행).
36
+ - 브라우저 런타임이 필요한 경우 `browser: { provider: playwright(), enabled: true, headless: true, instances: [{ browser: "chromium", viewport: { width: 1920, height: 1080 } }] }` 지정.
37
37
 
38
- `pnpm-workspace.yaml` 이미 `tests/*` 를 포함하므로 `pnpm install` 하면 워크스페이스 인식.
38
+ `pnpm-workspace.yaml` 이미 `tests/*` 를 포함하므로 `pnpm install` 만으로 워크스페이스에 인식됨.
39
39
 
40
40
  ### globalSetup 패턴
41
41
 
42
42
  `tests/<name>/vitest.setup.ts`:
43
43
 
44
- - `setup({ provide })` 에서 외부 자원 기동(DB 컨테이너·테스트 서버 등). 동적 값(랜덤 포트 등)은 `provide("key", value)` 로 스펙에 전달.
45
- - `teardown()` 에서 정리. 실패해도 다음 실행이 망가지지 않도록 best-effort.
46
- - 타입 보강: 파일 상단에.
44
+ - `setup({ provide })` 함수에서 외부 자원(DB 컨테이너·테스트 서버 등) 기동. 동적으로 결정되는 값(랜덤 포트 등)은 `provide("key", value)` 로 스펙에 전달.
45
+ - `teardown()` 함수에서 자원 정리. 정리 실패가 다음 실행을 망가뜨리지 않도록 best-effort 로 처리.
46
+ - 타입 보강: 파일 상단에 다음 선언 추가.
47
47
  ```ts
48
48
  declare module "vitest" {
49
49
  export interface ProvidedContext { key: T }
50
50
  }
51
51
  ```
52
- 스펙에서 `inject("key")` 로 수신.
52
+ 스펙에서는 `inject("key")` 로 수신.
53
53
 
54
- 기동 명령에 외부 도구(`docker compose`, `execa` 등) 사용 timeout 명시. 재시도가 필요한 자원(MSSQL 초기화 등)은 횟수 한정 retry, 한도 초과 시 throw — silent skip 금지.
54
+ 기동 명령에 외부 도구(`docker compose`, `execa` 등) 사용할 때는 timeout 명시. 재시도가 필요한 자원(MSSQL 초기화 등)은 횟수 제한을 retry 로 처리하고, 한도 초과 시 throw — silent skip 금지.
55
55
 
56
56
  ## 공통 패턴
57
57
 
58
58
  ### Lint 룰 테스트
59
59
 
60
- `@typescript-eslint/rule-tester` 는 vitest 직접 지원 함. `vitest.setup.ts` 에서 `RuleTester.describe`/`it`/`afterAll` 에 vitest 함수 바인딩. 스펙은 첫 줄에 `import "./vitest.setup"` `new RuleTester().run(...)`.
60
+ `@typescript-eslint/rule-tester` 는 vitest 직접 지원하지 않음. `vitest.setup.ts` 에서 `RuleTester.describe`/`it`/`afterAll` 에 vitest 함수를 바인딩. 스펙 파일의 첫 줄에 `import "./vitest.setup"` 둔 뒤 `new RuleTester().run(...)` 호출.
61
61
 
62
62
  ### Angular 테스트
63
63
 
64
- - 스펙은 `import { TestBed }` 후 `TestBed.configureTestingModule(...)` 부터 시작.
65
- - TestEnvironment 초기화 + `beforeEach` 의 `resetTestingModule` project setupFile 이미 처리. 스펙에서 다시 하지 말 것.
64
+ - 스펙은 `import { TestBed }` 후 `TestBed.configureTestingModule(...)` 호출로 시작.
65
+ - TestEnvironment 초기화와 `beforeEach` 의 `resetTestingModule` 호출은 project setupFile 에서 이미 처리됨. 스펙에서 다시 호출하지 말 것.
66
66
 
67
67
  ### ORM 다이얼렉트 매트릭스
68
68
 
69
- - **단위** (`packages/orm-common/tests`): dialect 별 기대 SQL 비교. 패턴 — `dialects` 상수(`["mysql", "mssql", "postgresql"]`) + `it.each(dialects)` + 커스텀 `toMatchSql` matcher(whitespace 정규화). 기대 SQL 은 같은 폴더 `*.expected.ts` dialect 객체로.
70
- - **통합** (`tests/orm/src`): 실 DB 3종. `describe.each(dbCases)` 로 mysql/postgresql/mssql 반복. dialect 별 DDL 차이는 `tests/orm/src/setup/db-helpers.ts` 가 흡수 — 새 모델 추가 시 이 파일의 CREATE/DROP SQL 에 dialect 분기 추가.
69
+ - **단위 테스트** (`packages/orm-common/tests`): dialect 별 기대 SQL 비교. 패턴 — `dialects` 상수(`["mysql", "mssql", "postgresql"]`) + `it.each(dialects)` + 커스텀 `toMatchSql` matcher(whitespace 정규화). 기대 SQL 은 같은 폴더의 `*.expected.ts` 파일에 dialect 키로 하는 객체 형태로 작성.
70
+ - **통합 테스트** (`tests/orm/src`): 실 DB 3 사용. `describe.each(dbCases)` 로 mysql/postgresql/mssql 반복. dialect 별 DDL 차이는 `tests/orm/src/setup/db-helpers.ts` 가 흡수 — 새 모델 추가 시 이 파일의 CREATE/DROP SQL 에 dialect 분기 추가.
71
71
 
72
72
  ### Service 통합 테스트
73
73
 
74
- `tests/service` globalSetup 이 `createServiceServer({ port: 0, ... }).listen()` 으로 랜덤 포트 서버 기동 `provide("servicePort", port)`. 스펙은 `const TEST_PORT = inject("servicePort")` 로 받아 `createServiceClient` 연결.
74
+ `tests/service` globalSetup 이 `createServiceServer({ port: 0, ... }).listen()` 으로 랜덤 포트 서버를 기동한 `provide("servicePort", port)` 전달. 스펙에서는 `const TEST_PORT = inject("servicePort")` 로 포트를 받아 `createServiceClient` 연결.
75
75
 
76
76
  ## 안티패턴
77
77
 
78
- - 스펙 파일에 직접 외부 자원(DB·서버) 기동 코드 박기 — globalSetup 으로 옮길 것.
79
- - `*.verify.md` 자리에 자동화 가능한 검증 작성 — 자동화 가능하면 `.spec.ts` 로.
80
- - 통합 테스트 project 에서 `exclude` 로 단위 스펙 빼내기 단위는 패키지 `tests/` 에 두고 통합 project 절대 include 하지 못하게 분리.
81
- - 같은 외부 자원을 여러 project 가 동시 점유 — `fileParallelism: false` + project 1개로 통합하거나, project 별로 자원 분리.
78
+ - 스펙 파일 안에 외부 자원(DB·서버) 기동 코드를 직접 작성 — globalSetup 으로 옮길 것.
79
+ - 자동화 가능한 검증을 `*.verify.md` 작성 — 자동화 가능하면 `.spec.ts` 로 작성할 것.
80
+ - 통합 테스트 project `include` 가 단위 스펙까지 흡수한 뒤 `exclude` 로 빼내는 구성단위 스펙은 패키지의 `tests/` 에 두고, 통합 project `include` 패턴이 단위 스펙을 처음부터 잡지 않도록 분리할 것.
81
+ - 같은 외부 자원을 여러 project 가 동시에 점유 — `fileParallelism: false` 적용한 단일 project 통합하거나, project 별로 자원을 분리할 것.