@simplysm/sd-claude 14.0.82 → 14.0.84
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-requirement-source-handling.md +20 -20
- package/claude/references/sd-simplysm14/README.md +13 -13
- package/claude/references/sd-simplysm14/manuals/client-component.md +92 -92
- package/claude/references/sd-simplysm14/manuals/client-crud.md +11 -11
- package/claude/references/sd-simplysm14/manuals/client-demo.md +28 -28
- package/claude/references/sd-simplysm14/manuals/client-rules.md +1 -1
- package/claude/references/sd-simplysm14/manuals/client-setup.md +21 -21
- package/claude/references/sd-simplysm14/manuals/client-tab.md +3 -3
- package/claude/references/sd-simplysm14/manuals/logging.md +15 -15
- package/claude/references/sd-simplysm14/manuals/orm-union.md +6 -6
- package/claude/references/sd-simplysm14/manuals/orm.md +19 -19
- package/claude/references/sd-simplysm14/manuals/test.md +33 -33
- package/claude/rules/sd-design-rules.md +18 -18
- package/claude/sd-system-prompt.md +369 -0
- package/claude/skills/sd-commit/SKILL.md +10 -10
- package/claude/skills/sd-config/SKILL.md +2 -2
- package/claude/skills/sd-demo/SKILL.md +45 -45
- package/claude/skills/sd-dev/SKILL.md +15 -15
- package/claude/skills/sd-docs/SKILL.md +7 -7
- package/claude/skills/sd-docs/references/subagent-prompt.md +33 -33
- package/claude/skills/sd-impl/SKILL.md +60 -60
- package/claude/skills/sd-review/SKILL.md +9 -9
- package/claude/skills/sd-skill/SKILL.md +74 -74
- package/claude/skills/sd-skill/evals/fixtures/existing-skill/.claude/skills/todo-format/SKILL.md +1 -1
- package/claude/skills/sd-spec/SKILL.md +354 -319
- package/claude/skills/sd-spec/references/example-spec.md +104 -104
- package/claude/skills/sd-unpack/SKILL.md +34 -34
- package/claude/skills/sd-unpack/scripts/handlers/__pycache__/office_com.cpython-314.pyc +0 -0
- package/claude/skills/sd-unpack/scripts/handlers/office_com.py +234 -159
- package/claude/skills/sd-use/SKILL.md +4 -4
- package/package.json +1 -1
- package/claude/references/sd-simplysm14/apis/angular/README.md +0 -37
- package/claude/references/sd-simplysm14/apis/angular/app-structure.md +0 -92
- package/claude/references/sd-simplysm14/apis/angular/buttons.md +0 -88
- package/claude/references/sd-simplysm14/apis/angular/crud.md +0 -100
- package/claude/references/sd-simplysm14/apis/angular/forms.md +0 -200
- package/claude/references/sd-simplysm14/apis/angular/infrastructure.md +0 -231
- package/claude/references/sd-simplysm14/apis/angular/kanban.md +0 -80
- package/claude/references/sd-simplysm14/apis/angular/layout.md +0 -92
- package/claude/references/sd-simplysm14/apis/angular/modal.md +0 -115
- package/claude/references/sd-simplysm14/apis/angular/routing.md +0 -107
- package/claude/references/sd-simplysm14/apis/angular/select-dropdown.md +0 -35
- package/claude/references/sd-simplysm14/apis/angular/selection-managers.md +0 -82
- package/claude/references/sd-simplysm14/apis/angular/shared-data.md +0 -134
- package/claude/references/sd-simplysm14/apis/angular/sheet.md +0 -127
- package/claude/references/sd-simplysm14/apis/angular/toast.md +0 -97
- package/claude/references/sd-simplysm14/apis/angular/visual.md +0 -167
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +0 -79
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-file-system/README.md +0 -83
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +0 -91
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +0 -49
- package/claude/references/sd-simplysm14/apis/core-browser/README.md +0 -143
- package/claude/references/sd-simplysm14/apis/core-common/README.md +0 -58
- package/claude/references/sd-simplysm14/apis/core-common/extensions.md +0 -88
- package/claude/references/sd-simplysm14/apis/core-common/features.md +0 -51
- package/claude/references/sd-simplysm14/apis/core-common/types.md +0 -88
- package/claude/references/sd-simplysm14/apis/core-common/utils.md +0 -189
- package/claude/references/sd-simplysm14/apis/core-node/README.md +0 -12
- package/claude/references/sd-simplysm14/apis/core-node/consola.md +0 -59
- package/claude/references/sd-simplysm14/apis/core-node/cpx.md +0 -44
- package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +0 -42
- package/claude/references/sd-simplysm14/apis/core-node/fsx.md +0 -53
- package/claude/references/sd-simplysm14/apis/core-node/pathx.md +0 -24
- package/claude/references/sd-simplysm14/apis/core-node/worker.md +0 -65
- package/claude/references/sd-simplysm14/apis/excel/README.md +0 -193
- package/claude/references/sd-simplysm14/apis/lint/README.md +0 -94
- package/claude/references/sd-simplysm14/apis/orm-common/README.md +0 -58
- package/claude/references/sd-simplysm14/apis/orm-common/db-context.md +0 -77
- package/claude/references/sd-simplysm14/apis/orm-common/executable.md +0 -20
- package/claude/references/sd-simplysm14/apis/orm-common/expr.md +0 -92
- package/claude/references/sd-simplysm14/apis/orm-common/queryable.md +0 -98
- package/claude/references/sd-simplysm14/apis/orm-common/schema-builders.md +0 -128
- package/claude/references/sd-simplysm14/apis/orm-node/README.md +0 -69
- package/claude/references/sd-simplysm14/apis/sd-claude/README.md +0 -32
- package/claude/references/sd-simplysm14/apis/sd-cli/README.md +0 -80
- package/claude/references/sd-simplysm14/apis/sd-cli/sd-config.md +0 -155
- package/claude/references/sd-simplysm14/apis/service-client/README.md +0 -131
- package/claude/references/sd-simplysm14/apis/service-common/README.md +0 -29
- package/claude/references/sd-simplysm14/apis/service-common/app-structure.md +0 -63
- package/claude/references/sd-simplysm14/apis/service-common/messages.md +0 -56
- package/claude/references/sd-simplysm14/apis/service-common/protocol.md +0 -64
- package/claude/references/sd-simplysm14/apis/service-common/service-types.md +0 -43
- package/claude/references/sd-simplysm14/apis/service-server/README.md +0 -13
- package/claude/references/sd-simplysm14/apis/service-server/auth.md +0 -39
- package/claude/references/sd-simplysm14/apis/service-server/builtin-services.md +0 -71
- package/claude/references/sd-simplysm14/apis/service-server/define-service.md +0 -55
- package/claude/references/sd-simplysm14/apis/service-server/internals.md +0 -82
- package/claude/references/sd-simplysm14/apis/service-server/server.md +0 -57
- package/claude/references/sd-simplysm14/apis/storage/README.md +0 -71
- package/claude/rules/sd-base-rules.md +0 -306
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## `sd-crud-list`
|
|
6
6
|
|
|
7
|
-
목록 화면의 표준 골격.
|
|
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` | 상단(
|
|
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
|
|
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)"
|
|
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
|
-
단일 레코드 편집 화면의 표준 골격. 폼
|
|
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
|
|
3
|
+
`@simplysm/angular` v14 로 spec.md 의 화면 정의 섹션(4번 섹션) 을 **데모** 로 옮길 때의 추가 지침. 컴포넌트 일반 규약(파일명·시그널·DI(의존성 주입)·핸들러·시트·폼·버튼·모달 호출·합성 패턴 등) 은 [client-component.md](./client-component.md) 를 따름. 본 문서는 spec 섹션과 산출물의 매핑 및 데모 한정 패턴만 다룸.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 화면 정의 섹션(4번 섹션) 의 화면 유형별 파일 역할
|
|
6
6
|
|
|
7
|
-
|
|
|
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
|
-
| 모달 (
|
|
13
|
+
| 모달 (동작 섹션에 `→ [화면.X] 을 모달로 띄움` 표기) | `<domain>.modal.ts` |
|
|
14
14
|
| 프린트 양식 | `<domain>.print-template.ts` |
|
|
15
15
|
|
|
16
|
-
`<domain>`
|
|
16
|
+
`<domain>` 은 화면명을 dash-case 영문으로 음역한 슬러그. 같은 도메인 폴더에 같은 역할의 파일이 2개 이상이면 `<domain>-<갈래>.<역할>.ts` 형식 사용.
|
|
17
17
|
|
|
18
|
-
## 와이어프레임
|
|
18
|
+
## 와이어프레임 기준
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
화면 정의 섹션(4번 섹션) 의 와이어프레임이 모든 시각 요소(버튼·필터·시트·탭·검색) 의 **존재·영역·순서** 결정의 1순위. 표준 슬롯이나 기본 UI(사용자 인터페이스) 와 충돌하면 와이어프레임에 맞춰 슬롯을 비우거나 컴포넌트를 교체. 줄 수·픽셀 좌표는 기준이 아님 (폼 inline 자동 wrap 등의 표현 한계 때문).
|
|
21
21
|
|
|
22
|
-
`sd-crud-list` 의 표준 출력(`(create)/(delete)/(restore)`) 이
|
|
22
|
+
`sd-crud-list` 의 표준 출력(`(create)/(delete)/(restore)`) 이 와이어프레임에 명시된 버튼 위치를 가린다면 표준 출력 사용을 포기하고 슬롯 안에 `sd-button` 으로 직접 배치.
|
|
23
23
|
|
|
24
|
-
##
|
|
24
|
+
## 항목표의 `종류` 컬럼을 입력 컨트롤로 매핑
|
|
25
25
|
|
|
26
|
-
[client-component.md "표준 입력 컨트롤"](./client-component.md)
|
|
26
|
+
[client-component.md "표준 입력 컨트롤"](./client-component.md) 의 매핑 표를 적용. spec 의 `종류` 값이 매핑 표에 없는 표기이면 가장 가까운 매핑을 적용하고 `// sd-demo: 종류 매핑 임의 — 확인 필요` 마커를 부착.
|
|
27
27
|
|
|
28
28
|
## 도메인 데이터 (더미)
|
|
29
29
|
|
|
30
|
-
- 컴포넌트 파일 안에 `interface DemoXxx { ... }`
|
|
31
|
-
- list 의 `items`
|
|
32
|
-
- 더미 데이터 1
|
|
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` 인라인
|
|
45
|
+
`restricted`/`readonly` 인라인 전달 방식은 [client-component.md "권한 (perms)"](./client-component.md) 의 규약을 그대로 따름.
|
|
46
46
|
|
|
47
|
-
## 액션 핸들러 (시뮬레이션
|
|
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
|
-
영역 한정 호출
|
|
72
|
+
영역 한정 호출(`→ [화면.Y] 의 <영역> — 선택 전용` 등) 은 모달의 입력 시그널(`selectMode` 등) 로 전달.
|
|
73
73
|
|
|
74
|
-
피호출
|
|
74
|
+
피호출 모달은 `<sd-crud-detail>` 을 루트로 사용 (`viewType='modal'` 자동 주입). 임의 `close` output 규약을 만들지 말 것 — `_sdModal.showAsync` 의 페이로드 반환 규약만 사용.
|
|
75
75
|
|
|
76
|
-
**동반 모달**:
|
|
76
|
+
**동반 모달**: 동작 섹션에 `→ [화면.Y] 을 모달로 띄움` 으로 등장하는 모든 모달은 같은 호출에서 함께 생성. 이미 존재하면 재사용.
|
|
77
77
|
|
|
78
|
-
## 양식 매핑 (엑셀
|
|
78
|
+
## 양식 매핑 (엑셀 업로드·다운로드)
|
|
79
79
|
|
|
80
|
-
|
|
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
|
|
112
|
+
list 와 detail 의 합성 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
|
-
|
|
128
|
+
기존 화면 1개의 라우팅·메뉴 등록 위치·방식을 그대로 따름. 라우트 경로는 화면명을 dash-case 영문으로 음역한 슬러그 사용 (프로젝트의 기존 슬러그 규칙이 일관되어 있다면 그 규칙을 따름). 메뉴 라벨은 spec 의 화면명을 그대로 사용.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# 클라이언트 환경 셋업 매뉴얼
|
|
2
2
|
|
|
3
|
-
화면 작성 시점에는 거의 건드리지
|
|
3
|
+
화면 작성 시점에는 거의 건드리지 않음. 새 앱 부트스트랩 시 또는 새 서비스/마스터 데이터를 추가할 때만 참조.
|
|
4
4
|
|
|
5
5
|
## AppServiceProvider
|
|
6
6
|
|
|
7
|
-
`@simplysm/service-client` 위에 앱이 만드는 root provider.
|
|
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
|
-
-
|
|
45
|
-
- 서비스: `client.getService<XxxServiceType>("XxxName")
|
|
46
|
-
- ORM 커넥터: `createOrmClientConnector(this.client)`
|
|
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
|
|
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 는
|
|
82
|
-
-
|
|
83
|
-
-
|
|
81
|
+
- DbContext 는 앱별로 정의 (예: `@adtek/db-main` 의 `MainDbContext`).
|
|
82
|
+
- 기본 메서드는 `connectAsync` (트랜잭션 포함). `connectWithoutTransAsync` 는 initialize 등 트랜잭션 안에서 동작하지 않는 작업 전용 헬퍼.
|
|
83
|
+
- 콜백의 반환값이 그대로 메서드의 반환값이 됨.
|
|
84
84
|
|
|
85
85
|
## AppSharedDataProvider
|
|
86
86
|
|
|
87
|
-
`@simplysm/angular` 의 `SdSharedDataProvider` 를
|
|
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" })
|
|
146
|
-
- 등록은 `override initialize()` 안에서 `this.register(name, opts)
|
|
147
|
-
- 각
|
|
148
|
-
- getter 의 select 결과에 매직
|
|
149
|
-
- `__valueKey` —
|
|
150
|
-
- `__searchText` —
|
|
151
|
-
- `__isHidden` — 숨김 여부 (
|
|
152
|
-
- `getter(changeKeys)`
|
|
153
|
-
- `orderBy` 는 정렬 키를 반환하는 함수 (`(item) => item.code`).
|
|
154
|
-
- `useSharedSignal<K>(name)` 헬퍼 함수를
|
|
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>` 은
|
|
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 으로
|
|
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
|
-
- 모든 로그는
|
|
8
|
-
- ESLint `no-console` 규칙은 의도된 게이트 — `eslint-disable
|
|
9
|
-
-
|
|
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
|
|
48
|
-
- **Browser·Capacitor 진입점**: `setupConsola` 호출
|
|
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
|
|
52
|
+
모듈 레벨에서 `consola.withTag()` 를 직접 호출하면 호출 시점의 options(level·reporters)가 스냅샷으로 고정되어, 이후 `setupConsola()` 가 reporters 를 갱신해도 child instance 에는 반영되지 않음.
|
|
53
53
|
|
|
54
|
-
- **해결**: `createLogger(tag)` 사용 (
|
|
55
|
-
- 모든 환경(Node·브라우저·Capacitor)
|
|
56
|
-
- `consola.withTag()` 직접 호출 금지. 발견 시
|
|
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 */` 파일
|
|
60
|
+
다음 경우에 한해 `/* eslint-disable no-console */` 파일 헤더 허용. 그 외는 모두 consola 로 교체.
|
|
61
61
|
|
|
62
|
-
- **CLI 도움말·yargs help 텍스트** 처럼 stdout
|
|
63
|
-
- **ErrorHandler 마지막 안전망** 등 consola 자체가 죽었을 가능성이 있는 catch 블록 — 해당
|
|
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) 규칙을 지키면서 **서로 다른 엔티티를 한 목록으로** 보여줘야 할 때 씀. 코드에서
|
|
3
|
+
[단일 쿼리 우선](./orm.md) 규칙을 지키면서 **서로 다른 엔티티를 한 목록으로** 보여줘야 할 때 씀. 두 쿼리 결과를 애플리케이션 코드에서 배열로 합치지 말고, select 결과 형태(컬럼 이름·타입·순서)를 동일하게 맞춘 두 Queryable 을 `Queryable.union(...)` 으로 DB 단에서 합침.
|
|
4
4
|
|
|
5
5
|
## 규칙
|
|
6
6
|
|
|
7
7
|
- 두 쿼리의 select 컬럼 이름·타입·순서가 정확히 일치해야 함.
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
- 필터(`where`)는 union 전에 각 쿼리에 같은 조건으로 적용함 — 각 소스에서 미리 행 수를 줄여야 union 비용이 작음 (predicate pushdown). union 결과에 필터를 걸면 두 소스를 다 읽은 뒤 거르게 되고, select
|
|
11
|
-
- 총 건수는 가능하면 **각 소스에서 따로 `count` 후 합산**함. union 결과의 `count` 는 양쪽 SELECT 를 모두 실행해
|
|
12
|
-
- `Queryable.union(...)` 결과는 의미상
|
|
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` 로 부착 — 본 행에
|
|
18
|
+
2. 필요한 연관 데이터를 `joinSingle` 로 부착 — 본 행에 직접 부착해야 하는 컬럼만. join 내부 쿼리도 동일 흐름(`from → joinSingle → where → select`)을 따름.
|
|
19
19
|
3. `.select((p) => ({ ...도출 컬럼들... }))` — coalesce / CASE WHEN / 산식 등 모든 도출을 한 번에 projection. select 콜백 안에서 로컬 `const` 로 산식을 잘게 나눠 가독성 확보.
|
|
20
|
-
4. WHERE 는
|
|
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
|
|
24
|
+
WHERE 와 SELECT 양쪽에서 동일 도출 산식을 쓰겠다고 `buildDerived(p)` 같은 helper 함수를 만들지 말 것 — 3번 단계의 projected 컬럼이 자동으로 그 역할을 함.
|
|
25
25
|
|
|
26
|
-
화면 첫 진입 1회만 필요하고 refresh / 필터 변경에 무관한 데이터
|
|
26
|
+
화면 첫 진입 1회만 필요하고 refresh / 필터 변경에 무관한 데이터(필터 dropdown 옵션 등)는 본 목록 쿼리에 섞지 말 것. 별도의 1회성 effect 로 분리해 init 시점에만 로드함.
|
|
27
27
|
|
|
28
28
|
## 안티패턴
|
|
29
29
|
|
|
30
|
-
### SELECT 절
|
|
30
|
+
### SELECT 절 내부에 `expr.subquery` / `expr.exists` 사용 금지
|
|
31
31
|
|
|
32
|
-
도메인 boolean
|
|
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 가
|
|
58
|
+
`wrap()` 은 framework 가 명시적으로 요구하는 경우에만 사용 (예: `distinct()` / `groupBy()` 이후 `count()` 호출).
|
|
59
59
|
|
|
60
|
-
도출 컬럼 위에서
|
|
60
|
+
도출 컬럼 위에서 필터·정렬을 걸기 위해 wrap 을 끼우는 패턴은 불필요 — `.select(...).where((r) => [...])` 만으로 framework 가 projected ExprUnit AST 를 WHERE / ORDER BY 에 inline 함.
|
|
61
61
|
|
|
62
|
-
"Layer 1 = materialize, Layer 2 = derive" 같은 다층 wrap 구조도 군더더기. 단일 select
|
|
62
|
+
"Layer 1 = materialize, Layer 2 = derive" 같은 다층 wrap 구조도 군더더기. 단일 select 안에서 로컬 `const` 로 산식을 분리하면 동일한 SQL 이 생성됨.
|
|
63
63
|
|
|
64
64
|
## 스키마 정의
|
|
65
65
|
|
|
66
|
-
컬럼은 `NOT NULL` 기본. `.nullable()
|
|
66
|
+
컬럼은 `NOT NULL` 기본. `.nullable()` / `.default(...)` 는 도메인 근거가 있을 때만 사용.
|
|
67
67
|
|
|
68
|
-
- `.nullable()`: 도메인상 값이 없을 수 있을 때만 (선택 입력, 미발생 이벤트 시각, 선택적 FK).
|
|
69
|
-
- `.default(...)`: 사용자가
|
|
70
|
-
- "초기값 애매", "마이그레이션 중간 단계", "넣을 값 모름" 은 nullable/default
|
|
68
|
+
- `.nullable()`: 도메인상 값이 없을 수 있을 때만 사용 (선택 입력, 미발생 이벤트 시각, 선택적 FK 등).
|
|
69
|
+
- `.default(...)`: 사용자가 명시적으로 지시한 경우에만 사용.
|
|
70
|
+
- "초기값 애매", "마이그레이션 중간 단계", "넣을 값 모름" 은 nullable / default 근거가 아님. 호출자가 값을 넣도록 강제하거나 backfill 후 `NOT NULL` 로 전환.
|
|
71
71
|
|
|
72
72
|
## 삭제 전략
|
|
73
73
|
|
|
74
|
-
- **기초정보(마스터)**: soft delete (`isDisabled` 등)
|
|
75
|
-
- **프로세스 문서(트랜잭션)**: 물리 delete. 상세
|
|
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
|
|
14
|
-
- import 경로: 워크스페이스 패키지는 `@simplysm/<pkg
|
|
15
|
-
- 환경 변수: `vitest.config.ts` 진입 시 `process.env.DEV=true
|
|
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`
|
|
19
|
+
`vitest.config.ts` 의 `include`/`exclude` 설정이 각 스펙 파일이 어느 project 에서 실행될지를 결정. 새 패키지 추가 시:
|
|
20
20
|
|
|
21
21
|
- **Node 전용**: 기본 `node` project 가 자동 include — 추가 작업 없음.
|
|
22
|
-
- **브라우저 환경 필요** (DOM·`window`·Worker 등 사용): `node` project 의 `exclude` 에 해당 패키지
|
|
23
|
-
- **Angular**: `packages/angular` 전용 project (TestBed + AOT
|
|
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
|
|
27
|
+
`tests/<name>/` 디렉토리 1개가 vitest project 1개에 대응. 추가 절차:
|
|
28
28
|
|
|
29
|
-
1. `tests/<name>/package.json` — `name: "@simplysm-test/<name>"`, `"private": true`, `"type": "module"`. 필요한 `@simplysm/*`
|
|
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
|
-
- 외부 자원
|
|
35
|
-
- 외부 자원이 단일
|
|
36
|
-
- 브라우저
|
|
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`
|
|
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 })`
|
|
45
|
-
- `teardown()`
|
|
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
|
-
|
|
52
|
+
스펙에서는 `inject("key")` 로 값 수신.
|
|
53
53
|
|
|
54
|
-
기동 명령에 외부 도구(`docker compose`, `execa` 등)
|
|
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 직접
|
|
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
|
|
64
|
+
- 스펙은 `import { TestBed }` 후 `TestBed.configureTestingModule(...)` 호출로 시작.
|
|
65
|
+
- TestEnvironment 초기화와 `beforeEach` 의 `resetTestingModule` 호출은 project setupFile 에서 이미 처리됨. 스펙에서 다시 호출하지 말 것.
|
|
66
66
|
|
|
67
67
|
### ORM 다이얼렉트 매트릭스
|
|
68
68
|
|
|
69
|
-
-
|
|
70
|
-
-
|
|
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()` 으로 랜덤 포트
|
|
74
|
+
`tests/service` 의 globalSetup 이 `createServiceServer({ port: 0, ... }).listen()` 으로 랜덤 포트 서버를 기동한 뒤 `provide("servicePort", port)` 로 전달. 스펙에서는 `const TEST_PORT = inject("servicePort")` 로 포트를 받아 `createServiceClient` 에 연결.
|
|
75
75
|
|
|
76
76
|
## 안티패턴
|
|
77
77
|
|
|
78
|
-
- 스펙
|
|
79
|
-
- `*.verify.md`
|
|
80
|
-
- 통합 테스트 project
|
|
81
|
-
- 같은 외부 자원을 여러 project 가
|
|
78
|
+
- 스펙 파일 안에 외부 자원(DB·서버) 기동 코드를 직접 작성 — globalSetup 으로 옮길 것.
|
|
79
|
+
- 자동화 가능한 검증을 `*.verify.md` 에 작성 — 자동화 가능하면 `.spec.ts` 로 작성할 것.
|
|
80
|
+
- 통합 테스트 project 의 `include` 가 단위 스펙까지 흡수한 뒤 `exclude` 로 빼내는 구성 — 단위 스펙은 패키지의 `tests/` 에 두고, 통합 project 의 `include` 패턴이 단위 스펙을 처음부터 잡지 않도록 분리할 것.
|
|
81
|
+
- 같은 외부 자원을 여러 project 가 동시에 점유 — `fileParallelism: false` 를 적용한 단일 project 로 통합하거나, project 별로 자원을 분리할 것.
|