jun-claude-code 0.6.3 → 0.6.4
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/dist/__tests__/metadata.test.d.ts +1 -0
- package/dist/__tests__/metadata.test.js +178 -0
- package/dist/__tests__/update.test.d.ts +1 -0
- package/dist/__tests__/update.test.js +154 -0
- package/dist/cli.js +25 -0
- package/dist/copy.d.ts +36 -0
- package/dist/copy.js +26 -11
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/metadata.d.ts +29 -0
- package/dist/metadata.js +112 -0
- package/dist/update.d.ts +16 -0
- package/dist/update.js +433 -0
- package/package.json +1 -1
- package/templates/global/skills/Backend/SKILL.md +3 -76
- package/templates/global/skills/TypeORM/SKILL.md +189 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: TypeORM
|
|
3
|
+
description: TypeORM 사용 시 참조. find > queryBuilder > rawQuery 우선순위, 가독성 기반 선택 기준, Migration 생성 규칙 제공.
|
|
4
|
+
keywords: [TypeORM, find, queryBuilder, rawQuery, Repository, 쿼리, ORM, migration]
|
|
5
|
+
user-invocable: false
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# TypeORM 사용 규칙
|
|
9
|
+
|
|
10
|
+
<rules>
|
|
11
|
+
|
|
12
|
+
## 쿼리 작성 우선순위
|
|
13
|
+
|
|
14
|
+
> **가독성을 최우선으로, find > QueryBuilder > Raw Query 순서로 사용한다.**
|
|
15
|
+
|
|
16
|
+
| 우선순위 | 방식 | 사용 조건 |
|
|
17
|
+
|---------|------|----------|
|
|
18
|
+
| 1 | **find 메서드** | 기본 CRUD, 단순 조건, relations |
|
|
19
|
+
| 2 | **QueryBuilder** | groupBy, 집계, 커스텀 JOIN — 가독성이 유지되는 경우 |
|
|
20
|
+
| 3 | **Raw Query** | QueryBuilder의 서브쿼리 등으로 가독성이 떨어지는 경우 |
|
|
21
|
+
|
|
22
|
+
### 핵심 판단 기준: 가독성
|
|
23
|
+
|
|
24
|
+
QueryBuilder가 허용되는 케이스라도, 서브쿼리 중첩 등으로 **코드 가독성이 떨어지면 Raw Query를 사용한다.**
|
|
25
|
+
|
|
26
|
+
## find 메서드 (우선순위 1)
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
// ✅ 기본 조회
|
|
30
|
+
const user = await this.userRepository.findOneBy({ id });
|
|
31
|
+
const users = await this.userRepository.find({
|
|
32
|
+
where: { status: 'active' },
|
|
33
|
+
relations: ['orders'],
|
|
34
|
+
order: { createdAt: 'DESC' },
|
|
35
|
+
take: 10,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// ✅ 조건 조합
|
|
39
|
+
const users = await this.userRepository.find({
|
|
40
|
+
where: [
|
|
41
|
+
{ status: 'active', role: 'admin' },
|
|
42
|
+
{ status: 'active', role: 'manager' },
|
|
43
|
+
],
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## QueryBuilder (우선순위 2)
|
|
48
|
+
|
|
49
|
+
**다음 경우에 QueryBuilder 사용 (가독성이 유지될 때):**
|
|
50
|
+
|
|
51
|
+
| 케이스 | 예시 |
|
|
52
|
+
|--------|------|
|
|
53
|
+
| **groupBy** | 집계 쿼리 |
|
|
54
|
+
| **getRawMany/getRawOne** | 원시 데이터 필요 |
|
|
55
|
+
| **커스텀 JOIN 조건** | ON 절 커스텀 |
|
|
56
|
+
| **단순 서브쿼리** | 가독성이 유지되는 서브쿼리 |
|
|
57
|
+
|
|
58
|
+
## Raw Query (우선순위 3)
|
|
59
|
+
|
|
60
|
+
**QueryBuilder로 작성 시 가독성이 떨어지는 경우 Raw Query 사용:**
|
|
61
|
+
|
|
62
|
+
| 케이스 | 이유 |
|
|
63
|
+
|--------|------|
|
|
64
|
+
| **복잡한 서브쿼리 중첩** | QueryBuilder 체이닝이 읽기 어려움 |
|
|
65
|
+
| **복잡한 CTE (WITH 절)** | QueryBuilder로 표현 시 과도한 중첩 |
|
|
66
|
+
| **DB 특화 문법** | QueryBuilder가 지원하지 않는 문법 |
|
|
67
|
+
|
|
68
|
+
</rules>
|
|
69
|
+
|
|
70
|
+
<examples>
|
|
71
|
+
|
|
72
|
+
### find vs QueryBuilder
|
|
73
|
+
|
|
74
|
+
<example type="bad">
|
|
75
|
+
```typescript
|
|
76
|
+
// ❌ 불필요한 QueryBuilder 사용
|
|
77
|
+
const user = await this.userRepository
|
|
78
|
+
.createQueryBuilder('user')
|
|
79
|
+
.where('user.id = :id', { id })
|
|
80
|
+
.getOne();
|
|
81
|
+
```
|
|
82
|
+
</example>
|
|
83
|
+
<example type="good">
|
|
84
|
+
```typescript
|
|
85
|
+
// ✅ find로 대체
|
|
86
|
+
const user = await this.userRepository.findOneBy({ id });
|
|
87
|
+
```
|
|
88
|
+
</example>
|
|
89
|
+
|
|
90
|
+
### QueryBuilder 허용
|
|
91
|
+
|
|
92
|
+
<example type="good">
|
|
93
|
+
```typescript
|
|
94
|
+
// ✅ QueryBuilder 허용: groupBy + getRawMany (가독성 유지)
|
|
95
|
+
const stats = await this.orderRepository
|
|
96
|
+
.createQueryBuilder('order')
|
|
97
|
+
.select('order.status', 'status')
|
|
98
|
+
.addSelect('COUNT(*)', 'count')
|
|
99
|
+
.addSelect('SUM(order.amount)', 'total')
|
|
100
|
+
.groupBy('order.status')
|
|
101
|
+
.getRawMany();
|
|
102
|
+
```
|
|
103
|
+
</example>
|
|
104
|
+
|
|
105
|
+
### QueryBuilder → Raw Query 전환
|
|
106
|
+
|
|
107
|
+
<example type="bad">
|
|
108
|
+
```typescript
|
|
109
|
+
// ❌ 서브쿼리 중첩으로 가독성 저하
|
|
110
|
+
const result = await this.orderRepository
|
|
111
|
+
.createQueryBuilder('order')
|
|
112
|
+
.where((qb) => {
|
|
113
|
+
const subQuery = qb
|
|
114
|
+
.subQuery()
|
|
115
|
+
.select('oi.orderId')
|
|
116
|
+
.from(OrderItem, 'oi')
|
|
117
|
+
.where((qb2) => {
|
|
118
|
+
const subQuery2 = qb2
|
|
119
|
+
.subQuery()
|
|
120
|
+
.select('p.id')
|
|
121
|
+
.from(Product, 'p')
|
|
122
|
+
.where('p.category = :cat', { cat: 'electronics' })
|
|
123
|
+
.getQuery();
|
|
124
|
+
return 'oi.productId IN ' + subQuery2;
|
|
125
|
+
})
|
|
126
|
+
.getQuery();
|
|
127
|
+
return 'order.id IN ' + subQuery;
|
|
128
|
+
})
|
|
129
|
+
.getMany();
|
|
130
|
+
```
|
|
131
|
+
</example>
|
|
132
|
+
<example type="good">
|
|
133
|
+
```typescript
|
|
134
|
+
// ✅ Raw Query로 가독성 확보
|
|
135
|
+
const result = await this.orderRepository.query(
|
|
136
|
+
`SELECT o.*
|
|
137
|
+
FROM orders o
|
|
138
|
+
WHERE o.id IN (
|
|
139
|
+
SELECT oi.order_id
|
|
140
|
+
FROM order_items oi
|
|
141
|
+
WHERE oi.product_id IN (
|
|
142
|
+
SELECT p.id FROM products p WHERE p.category = $1
|
|
143
|
+
)
|
|
144
|
+
)`,
|
|
145
|
+
['electronics'],
|
|
146
|
+
);
|
|
147
|
+
```
|
|
148
|
+
</example>
|
|
149
|
+
|
|
150
|
+
</examples>
|
|
151
|
+
|
|
152
|
+
<rules>
|
|
153
|
+
|
|
154
|
+
## Migration 생성 규칙
|
|
155
|
+
|
|
156
|
+
> **Migration 파일은 직접 생성하지 않고, `yarn migration:create` 명령어로 생성한 뒤 수정한다.**
|
|
157
|
+
|
|
158
|
+
### 이유
|
|
159
|
+
|
|
160
|
+
직접 파일을 생성하면 타임스탬프 충돌이 발생할 수 있다. CLI 명령어를 통해 생성해야 TypeORM이 타임스탬프를 자동 관리한다.
|
|
161
|
+
|
|
162
|
+
### 생성 방법
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
# package.json에 migration:create가 정의된 경우
|
|
166
|
+
yarn migration:create src/migrations/MigrationName
|
|
167
|
+
|
|
168
|
+
# 정의되지 않은 경우 직접 실행
|
|
169
|
+
yarn run typeorm migration:create src/migrations/MigrationName
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### 작업 순서
|
|
173
|
+
|
|
174
|
+
1. `yarn migration:create`로 빈 migration 파일 생성
|
|
175
|
+
2. 생성된 파일의 `up()` / `down()` 메서드 작성
|
|
176
|
+
3. `yarn migration:run`으로 적용 확인
|
|
177
|
+
|
|
178
|
+
</rules>
|
|
179
|
+
|
|
180
|
+
<checklist>
|
|
181
|
+
|
|
182
|
+
## 체크리스트
|
|
183
|
+
|
|
184
|
+
- [ ] find 메서드를 우선 사용하는가?
|
|
185
|
+
- [ ] QueryBuilder는 find로 불가능한 경우에만 사용하는가?
|
|
186
|
+
- [ ] QueryBuilder 서브쿼리 중첩으로 가독성이 떨어지면 Raw Query로 전환했는가?
|
|
187
|
+
- [ ] Migration 파일을 `yarn migration:create`로 생성했는가? (직접 파일 생성 금지)
|
|
188
|
+
|
|
189
|
+
</checklist>
|