@kood/claude-code 0.2.0 → 0.2.2
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/index.js +62 -18
- package/package.json +1 -1
- package/templates/.claude/agents/code-reviewer.md +31 -0
- package/templates/.claude/agents/debug-detective.md +37 -0
- package/templates/.claude/agents/refactor-advisor.md +44 -0
- package/templates/.claude/agents/test-writer.md +41 -0
- package/templates/.claude/skills/frontend-design/SKILL.md +310 -0
- package/templates/.claude/skills/frontend-design/references/animation-patterns.md +446 -0
- package/templates/.claude/skills/frontend-design/references/colors-2026.md +244 -0
- package/templates/.claude/skills/frontend-design/references/typography-2026.md +302 -0
- package/templates/.claude/skills/gemini-review/SKILL.md +1 -1
- package/templates/hono/docs/library/drizzle/cloudflare-d1.md +247 -0
- package/templates/hono/docs/library/drizzle/config.md +167 -0
- package/templates/hono/docs/library/drizzle/index.md +259 -0
- package/templates/tanstack-start/docs/library/drizzle/cloudflare-d1.md +146 -0
- package/templates/tanstack-start/docs/library/drizzle/config.md +118 -0
- package/templates/tanstack-start/docs/library/drizzle/crud.md +205 -0
- package/templates/tanstack-start/docs/library/drizzle/index.md +79 -0
- package/templates/tanstack-start/docs/library/drizzle/relations.md +202 -0
- package/templates/tanstack-start/docs/library/drizzle/schema.md +154 -0
- package/templates/tanstack-start/docs/library/drizzle/setup.md +95 -0
- package/templates/tanstack-start/docs/library/drizzle/transactions.md +127 -0
- package/templates/tanstack-start/docs/library/tanstack-router/error-handling.md +204 -0
- package/templates/tanstack-start/docs/library/tanstack-router/hooks.md +195 -0
- package/templates/tanstack-start/docs/library/tanstack-router/index.md +150 -0
- package/templates/tanstack-start/docs/library/tanstack-router/navigation.md +150 -0
- package/templates/tanstack-start/docs/library/tanstack-router/route-context.md +203 -0
- package/templates/tanstack-start/docs/library/tanstack-router/search-params.md +213 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# Drizzle ORM - Database ORM
|
|
2
|
+
|
|
3
|
+
> Type-safe, lightweight TypeScript ORM
|
|
4
|
+
|
|
5
|
+
@config.md
|
|
6
|
+
@cloudflare-d1.md
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 설치
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install drizzle-orm pg
|
|
14
|
+
npm install -D drizzle-kit @types/pg
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 스키마 정의
|
|
20
|
+
|
|
21
|
+
### 폴더 구조
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
src/
|
|
25
|
+
├── db/
|
|
26
|
+
│ ├── index.ts # Drizzle client
|
|
27
|
+
│ └── schema/
|
|
28
|
+
│ ├── index.ts # export all
|
|
29
|
+
│ ├── user.ts # User 테이블
|
|
30
|
+
│ └── post.ts # Post 테이블
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### user.ts
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { pgTable, serial, text, timestamp, boolean } from 'drizzle-orm/pg-core'
|
|
37
|
+
|
|
38
|
+
// 사용자 테이블
|
|
39
|
+
export const users = pgTable('users', {
|
|
40
|
+
id: serial('id').primaryKey(),
|
|
41
|
+
email: text('email').notNull().unique(), // 로그인 이메일
|
|
42
|
+
name: text('name'), // 표시 이름
|
|
43
|
+
verified: boolean('verified').notNull().default(false),
|
|
44
|
+
createdAt: timestamp('created_at').notNull().defaultNow(),
|
|
45
|
+
updatedAt: timestamp('updated_at').notNull().defaultNow(),
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// 타입 추론
|
|
49
|
+
export type User = typeof users.$inferSelect
|
|
50
|
+
export type NewUser = typeof users.$inferInsert
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Drizzle Client
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// src/db/index.ts
|
|
59
|
+
import { drizzle } from 'drizzle-orm/node-postgres'
|
|
60
|
+
import * as schema from './schema'
|
|
61
|
+
|
|
62
|
+
export const db = drizzle(process.env.DATABASE_URL!, { schema })
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## CRUD
|
|
68
|
+
|
|
69
|
+
### Create
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import { db } from '@/db'
|
|
73
|
+
import { users } from '@/db/schema'
|
|
74
|
+
|
|
75
|
+
const user = await db.insert(users).values({
|
|
76
|
+
email: 'user@example.com',
|
|
77
|
+
name: 'John',
|
|
78
|
+
}).returning()
|
|
79
|
+
|
|
80
|
+
// Batch insert
|
|
81
|
+
await db.insert(users).values([
|
|
82
|
+
{ email: 'user1@example.com', name: 'User 1' },
|
|
83
|
+
{ email: 'user2@example.com', name: 'User 2' },
|
|
84
|
+
])
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Read
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { eq, and, or, gt, like, desc, asc } from 'drizzle-orm'
|
|
91
|
+
|
|
92
|
+
// 전체 조회
|
|
93
|
+
const allUsers = await db.select().from(users)
|
|
94
|
+
|
|
95
|
+
// 조건 조회
|
|
96
|
+
const user = await db.select().from(users).where(eq(users.id, 1))
|
|
97
|
+
|
|
98
|
+
// 복합 조건
|
|
99
|
+
const filtered = await db.select().from(users).where(
|
|
100
|
+
and(
|
|
101
|
+
eq(users.verified, true),
|
|
102
|
+
gt(users.createdAt, new Date('2024-01-01'))
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
// 정렬 + 페이지네이션
|
|
107
|
+
const paginated = await db.select().from(users)
|
|
108
|
+
.orderBy(desc(users.createdAt))
|
|
109
|
+
.limit(10)
|
|
110
|
+
.offset(0)
|
|
111
|
+
|
|
112
|
+
// 특정 필드만 선택
|
|
113
|
+
const emails = await db.select({ email: users.email }).from(users)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Update
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
await db.update(users)
|
|
120
|
+
.set({ name: 'Updated Name' })
|
|
121
|
+
.where(eq(users.id, 1))
|
|
122
|
+
|
|
123
|
+
// Upsert
|
|
124
|
+
await db.insert(users)
|
|
125
|
+
.values({ email: 'user@example.com', name: 'John' })
|
|
126
|
+
.onConflictDoUpdate({
|
|
127
|
+
target: users.email,
|
|
128
|
+
set: { name: 'John Updated' },
|
|
129
|
+
})
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Delete
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
await db.delete(users).where(eq(users.id, 1))
|
|
136
|
+
await db.delete(users).where(eq(users.verified, false))
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 필터링 연산자
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import { eq, ne, gt, gte, lt, lte, like, ilike, inArray, isNull, between, and, or, not } from 'drizzle-orm'
|
|
145
|
+
|
|
146
|
+
// 비교
|
|
147
|
+
where(eq(users.id, 1)) // =
|
|
148
|
+
where(ne(users.id, 1)) // !=
|
|
149
|
+
where(gt(users.age, 18)) // >
|
|
150
|
+
where(gte(users.age, 18)) // >=
|
|
151
|
+
where(lt(users.age, 65)) // <
|
|
152
|
+
where(lte(users.age, 65)) // <=
|
|
153
|
+
|
|
154
|
+
// 문자열
|
|
155
|
+
where(like(users.name, '%John%'))
|
|
156
|
+
where(ilike(users.name, '%john%')) // 대소문자 무시
|
|
157
|
+
|
|
158
|
+
// 배열
|
|
159
|
+
where(inArray(users.id, [1, 2, 3]))
|
|
160
|
+
|
|
161
|
+
// NULL
|
|
162
|
+
where(isNull(users.deletedAt))
|
|
163
|
+
|
|
164
|
+
// 범위
|
|
165
|
+
where(between(users.age, 18, 65))
|
|
166
|
+
|
|
167
|
+
// 논리
|
|
168
|
+
where(and(eq(users.role, 'admin'), eq(users.verified, true)))
|
|
169
|
+
where(or(eq(users.role, 'admin'), eq(users.role, 'moderator')))
|
|
170
|
+
where(not(eq(users.role, 'guest')))
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 트랜잭션
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
const result = await db.transaction(async (tx) => {
|
|
179
|
+
const [user] = await tx.insert(users)
|
|
180
|
+
.values({ email: 'user@example.com' })
|
|
181
|
+
.returning()
|
|
182
|
+
|
|
183
|
+
await tx.insert(posts).values({ title: 'First Post', authorId: user.id })
|
|
184
|
+
|
|
185
|
+
return user
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
// 롤백
|
|
189
|
+
await db.transaction(async (tx) => {
|
|
190
|
+
const [account] = await tx.select().from(accounts).where(eq(accounts.id, 1))
|
|
191
|
+
if (account.balance < 100) {
|
|
192
|
+
tx.rollback() // 예외 발생, 트랜잭션 롤백
|
|
193
|
+
}
|
|
194
|
+
await tx.update(accounts).set({ balance: account.balance - 100 }).where(eq(accounts.id, 1))
|
|
195
|
+
})
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Hono와 함께 사용
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
import { Hono } from 'hono'
|
|
204
|
+
import { zValidator } from '@hono/zod-validator'
|
|
205
|
+
import { z } from 'zod'
|
|
206
|
+
import { HTTPException } from 'hono/http-exception'
|
|
207
|
+
import { eq } from 'drizzle-orm'
|
|
208
|
+
import { db } from '@/db'
|
|
209
|
+
import { users } from '@/db/schema'
|
|
210
|
+
|
|
211
|
+
const app = new Hono()
|
|
212
|
+
|
|
213
|
+
app.post('/users',
|
|
214
|
+
zValidator('json', z.object({ email: z.email(), name: z.string().optional() })),
|
|
215
|
+
async (c) => {
|
|
216
|
+
const data = c.req.valid('json')
|
|
217
|
+
|
|
218
|
+
const existing = await db.select().from(users).where(eq(users.email, data.email))
|
|
219
|
+
if (existing.length > 0) {
|
|
220
|
+
throw new HTTPException(409, { message: 'Email already exists' })
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const [user] = await db.insert(users).values(data).returning()
|
|
224
|
+
return c.json({ user }, 201)
|
|
225
|
+
}
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
app.get('/users/:id', async (c) => {
|
|
229
|
+
const id = Number(c.req.param('id'))
|
|
230
|
+
const [user] = await db.select().from(users).where(eq(users.id, id))
|
|
231
|
+
|
|
232
|
+
if (!user) {
|
|
233
|
+
throw new HTTPException(404, { message: 'User not found' })
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return c.json({ user })
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
export default app
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## CLI 명령어
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
npx drizzle-kit generate # 마이그레이션 생성
|
|
248
|
+
npx drizzle-kit migrate # 마이그레이션 실행
|
|
249
|
+
npx drizzle-kit push # 스키마 동기화 (개발용)
|
|
250
|
+
npx drizzle-kit pull # DB에서 스키마 가져오기
|
|
251
|
+
npx drizzle-kit studio # GUI 실행
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## 관련 문서
|
|
257
|
+
|
|
258
|
+
- [Config 파일](./config.md) - drizzle.config.ts 설정
|
|
259
|
+
- [Cloudflare D1](./cloudflare-d1.md)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Drizzle - Cloudflare D1
|
|
2
|
+
|
|
3
|
+
SQLite 기반 서버리스 DB. 일반 Drizzle 마이그레이션과 다른 워크플로우.
|
|
4
|
+
|
|
5
|
+
트랜잭션 미지원 | drizzle-kit migrate 불가 - wrangler 사용
|
|
6
|
+
|
|
7
|
+
## 설정
|
|
8
|
+
|
|
9
|
+
### 스키마 (SQLite)
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
// src/db/schema.ts
|
|
13
|
+
import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core'
|
|
14
|
+
|
|
15
|
+
export const users = sqliteTable('users', {
|
|
16
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
17
|
+
email: text('email').notNull().unique(),
|
|
18
|
+
name: text('name'),
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
export type User = typeof users.$inferSelect
|
|
22
|
+
export type NewUser = typeof users.$inferInsert
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### drizzle.config.ts
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { defineConfig } from 'drizzle-kit'
|
|
29
|
+
|
|
30
|
+
export default defineConfig({
|
|
31
|
+
dialect: 'sqlite',
|
|
32
|
+
schema: './src/db/schema.ts',
|
|
33
|
+
out: './drizzle/migrations',
|
|
34
|
+
driver: 'd1-http',
|
|
35
|
+
dbCredentials: {
|
|
36
|
+
accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
|
|
37
|
+
databaseId: process.env.CLOUDFLARE_DATABASE_ID!,
|
|
38
|
+
token: process.env.CLOUDFLARE_D1_TOKEN!,
|
|
39
|
+
},
|
|
40
|
+
})
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### wrangler.jsonc
|
|
44
|
+
|
|
45
|
+
```jsonc
|
|
46
|
+
{
|
|
47
|
+
"d1_databases": [{
|
|
48
|
+
"binding": "DB",
|
|
49
|
+
"database_name": "my-db",
|
|
50
|
+
"database_id": "YOUR_DATABASE_ID",
|
|
51
|
+
"migrations_dir": "drizzle/migrations"
|
|
52
|
+
}]
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## 사용법
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { drizzle } from 'drizzle-orm/d1'
|
|
60
|
+
import * as schema from './db/schema'
|
|
61
|
+
|
|
62
|
+
export interface Env {
|
|
63
|
+
DB: D1Database
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export default {
|
|
67
|
+
async fetch(request: Request, env: Env): Promise<Response> {
|
|
68
|
+
const db = drizzle(env.DB, { schema })
|
|
69
|
+
const users = await db.select().from(schema.users)
|
|
70
|
+
return new Response(JSON.stringify(users))
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## TanStack Start + D1
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
// app/services/user.ts
|
|
79
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
80
|
+
import { drizzle } from 'drizzle-orm/d1'
|
|
81
|
+
import { eq } from 'drizzle-orm'
|
|
82
|
+
import * as schema from '@/db/schema'
|
|
83
|
+
|
|
84
|
+
// D1 바인딩 접근 (Cloudflare Pages/Workers)
|
|
85
|
+
const getDb = () => {
|
|
86
|
+
const env = (globalThis as any).__env__
|
|
87
|
+
return drizzle(env.DB, { schema })
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const getUsers = createServerFn({ method: 'GET' })
|
|
91
|
+
.handler(async () => {
|
|
92
|
+
const db = getDb()
|
|
93
|
+
return db.select().from(schema.users)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
export const createUser = createServerFn({ method: 'POST' })
|
|
97
|
+
.validator((data: { email: string; name?: string }) => data)
|
|
98
|
+
.handler(async ({ data }) => {
|
|
99
|
+
const db = getDb()
|
|
100
|
+
const [user] = await db.insert(schema.users).values(data).returning()
|
|
101
|
+
return user
|
|
102
|
+
})
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## 마이그레이션 워크플로우
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
# 1. D1 생성
|
|
109
|
+
npx wrangler d1 create my-database
|
|
110
|
+
|
|
111
|
+
# 2. 마이그레이션 생성
|
|
112
|
+
npx drizzle-kit generate
|
|
113
|
+
|
|
114
|
+
# 3. 로컬 적용
|
|
115
|
+
npx wrangler d1 migrations apply my-database --local
|
|
116
|
+
|
|
117
|
+
# 4. 원격 적용
|
|
118
|
+
npx wrangler d1 migrations apply my-database --remote
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## 로컬 개발
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
# Worker 실행
|
|
125
|
+
npx wrangler dev
|
|
126
|
+
|
|
127
|
+
# D1 조회
|
|
128
|
+
npx wrangler d1 execute my-database --local \
|
|
129
|
+
--command "SELECT * FROM users;"
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## 배포
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
npx drizzle-kit generate
|
|
136
|
+
npx wrangler d1 migrations apply my-database --remote
|
|
137
|
+
npx wrangler deploy
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## 제한사항
|
|
141
|
+
|
|
142
|
+
| 항목 | 일반 SQLite | D1 |
|
|
143
|
+
|------|-------------|-----|
|
|
144
|
+
| 마이그레이션 | drizzle-kit migrate | wrangler d1 |
|
|
145
|
+
| 트랜잭션 | 지원 | 미지원 |
|
|
146
|
+
| 접속 | 직접 | HTTP (D1 binding) |
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Drizzle - Config 파일
|
|
2
|
+
|
|
3
|
+
drizzle.config.ts 설정.
|
|
4
|
+
|
|
5
|
+
## 기본 설정
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
// drizzle.config.ts
|
|
9
|
+
import 'dotenv/config'
|
|
10
|
+
import { defineConfig } from 'drizzle-kit'
|
|
11
|
+
|
|
12
|
+
export default defineConfig({
|
|
13
|
+
dialect: 'postgresql',
|
|
14
|
+
schema: './src/db/schema/index.ts',
|
|
15
|
+
out: './drizzle/migrations',
|
|
16
|
+
dbCredentials: {
|
|
17
|
+
url: process.env.DATABASE_URL!,
|
|
18
|
+
},
|
|
19
|
+
})
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## 폴더 구조
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
프로젝트/
|
|
26
|
+
├── drizzle.config.ts
|
|
27
|
+
├── drizzle/
|
|
28
|
+
│ └── migrations/
|
|
29
|
+
│ ├── 0000_init.sql
|
|
30
|
+
│ └── meta/
|
|
31
|
+
├── src/
|
|
32
|
+
│ └── db/
|
|
33
|
+
│ └── schema/
|
|
34
|
+
│ ├── index.ts
|
|
35
|
+
│ └── user.ts
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## PostgreSQL
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { defineConfig } from 'drizzle-kit'
|
|
42
|
+
|
|
43
|
+
export default defineConfig({
|
|
44
|
+
dialect: 'postgresql',
|
|
45
|
+
schema: './src/db/schema/index.ts',
|
|
46
|
+
out: './drizzle/migrations',
|
|
47
|
+
dbCredentials: {
|
|
48
|
+
url: process.env.DATABASE_URL!,
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## MySQL
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { defineConfig } from 'drizzle-kit'
|
|
57
|
+
|
|
58
|
+
export default defineConfig({
|
|
59
|
+
dialect: 'mysql',
|
|
60
|
+
schema: './src/db/schema/index.ts',
|
|
61
|
+
out: './drizzle/migrations',
|
|
62
|
+
dbCredentials: {
|
|
63
|
+
url: process.env.DATABASE_URL!,
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## SQLite
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { defineConfig } from 'drizzle-kit'
|
|
72
|
+
|
|
73
|
+
export default defineConfig({
|
|
74
|
+
dialect: 'sqlite',
|
|
75
|
+
schema: './src/db/schema/index.ts',
|
|
76
|
+
out: './drizzle/migrations',
|
|
77
|
+
dbCredentials: {
|
|
78
|
+
url: './sqlite.db',
|
|
79
|
+
},
|
|
80
|
+
})
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## 설정 옵션
|
|
84
|
+
|
|
85
|
+
| 옵션 | 설명 |
|
|
86
|
+
|------|------|
|
|
87
|
+
| `dialect` | DB 종류 (postgresql, mysql, sqlite) |
|
|
88
|
+
| `schema` | 스키마 파일 경로 (glob 패턴 지원) |
|
|
89
|
+
| `out` | 마이그레이션 출력 폴더 |
|
|
90
|
+
| `dbCredentials` | DB 연결 정보 |
|
|
91
|
+
| `driver` | 드라이버 (d1-http 등) |
|
|
92
|
+
| `verbose` | 상세 로그 출력 |
|
|
93
|
+
| `strict` | 엄격 모드 |
|
|
94
|
+
|
|
95
|
+
## 다중 스키마 파일
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
import { defineConfig } from 'drizzle-kit'
|
|
99
|
+
|
|
100
|
+
export default defineConfig({
|
|
101
|
+
dialect: 'postgresql',
|
|
102
|
+
schema: './src/db/schema/*.ts', // glob 패턴
|
|
103
|
+
out: './drizzle/migrations',
|
|
104
|
+
dbCredentials: {
|
|
105
|
+
url: process.env.DATABASE_URL!,
|
|
106
|
+
},
|
|
107
|
+
})
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## 마이그레이션 명령어
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
npx drizzle-kit generate # 마이그레이션 파일 생성
|
|
114
|
+
npx drizzle-kit migrate # 마이그레이션 실행
|
|
115
|
+
npx drizzle-kit push # 스키마 직접 동기화 (개발용)
|
|
116
|
+
npx drizzle-kit pull # DB에서 스키마 가져오기
|
|
117
|
+
npx drizzle-kit studio # GUI 실행
|
|
118
|
+
```
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Drizzle - CRUD 작업
|
|
2
|
+
|
|
3
|
+
## Create
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { db } from '@/lib/db'
|
|
7
|
+
import { users } from '@/db/schema'
|
|
8
|
+
|
|
9
|
+
// 단일
|
|
10
|
+
const [user] = await db.insert(users)
|
|
11
|
+
.values({ email: 'alice@example.com', name: 'Alice' })
|
|
12
|
+
.returning()
|
|
13
|
+
|
|
14
|
+
// Batch insert
|
|
15
|
+
await db.insert(users).values([
|
|
16
|
+
{ email: 'alice@example.com', name: 'Alice' },
|
|
17
|
+
{ email: 'bob@example.com', name: 'Bob' },
|
|
18
|
+
])
|
|
19
|
+
|
|
20
|
+
// returning 없이
|
|
21
|
+
await db.insert(users).values({ email: 'alice@example.com' })
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Read
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { eq, and, or, gt, like, desc, asc, count, sql } from 'drizzle-orm'
|
|
28
|
+
|
|
29
|
+
// 전체
|
|
30
|
+
const allUsers = await db.select().from(users)
|
|
31
|
+
|
|
32
|
+
// 조건
|
|
33
|
+
const [user] = await db.select().from(users).where(eq(users.id, 1))
|
|
34
|
+
|
|
35
|
+
// 복합 조건
|
|
36
|
+
const filtered = await db.select().from(users).where(
|
|
37
|
+
and(
|
|
38
|
+
eq(users.role, 'ADMIN'),
|
|
39
|
+
gt(users.createdAt, new Date('2024-01-01'))
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
// 특정 필드만
|
|
44
|
+
const emails = await db.select({ email: users.email }).from(users)
|
|
45
|
+
|
|
46
|
+
// 정렬
|
|
47
|
+
const sorted = await db.select().from(users).orderBy(desc(users.createdAt))
|
|
48
|
+
|
|
49
|
+
// 페이지네이션
|
|
50
|
+
const paginated = await db.select().from(users)
|
|
51
|
+
.orderBy(asc(users.id))
|
|
52
|
+
.limit(10)
|
|
53
|
+
.offset(0)
|
|
54
|
+
|
|
55
|
+
// 카운트
|
|
56
|
+
const [{ count: total }] = await db.select({ count: count() }).from(users)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Relational Query (관계 포함)
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
// with 사용
|
|
63
|
+
const userWithPosts = await db.query.users.findFirst({
|
|
64
|
+
where: eq(users.id, 1),
|
|
65
|
+
with: {
|
|
66
|
+
posts: true,
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
// 중첩 관계
|
|
71
|
+
const userWithAll = await db.query.users.findFirst({
|
|
72
|
+
where: eq(users.id, 1),
|
|
73
|
+
with: {
|
|
74
|
+
posts: {
|
|
75
|
+
with: {
|
|
76
|
+
comments: true,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
// 조건 + 정렬
|
|
83
|
+
const result = await db.query.users.findMany({
|
|
84
|
+
where: eq(users.verified, true),
|
|
85
|
+
with: {
|
|
86
|
+
posts: {
|
|
87
|
+
where: eq(posts.published, true),
|
|
88
|
+
orderBy: desc(posts.createdAt),
|
|
89
|
+
limit: 5,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
orderBy: asc(users.id),
|
|
93
|
+
limit: 10,
|
|
94
|
+
})
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Update
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
// 단일
|
|
101
|
+
const [updated] = await db.update(users)
|
|
102
|
+
.set({ name: 'Updated Name' })
|
|
103
|
+
.where(eq(users.id, 1))
|
|
104
|
+
.returning()
|
|
105
|
+
|
|
106
|
+
// 다중
|
|
107
|
+
await db.update(users)
|
|
108
|
+
.set({ verified: true })
|
|
109
|
+
.where(eq(users.role, 'ADMIN'))
|
|
110
|
+
|
|
111
|
+
// Upsert (PostgreSQL)
|
|
112
|
+
await db.insert(users)
|
|
113
|
+
.values({ email: 'alice@example.com', name: 'Alice' })
|
|
114
|
+
.onConflictDoUpdate({
|
|
115
|
+
target: users.email,
|
|
116
|
+
set: { name: 'Alice Updated' },
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
// Upsert - 아무것도 안 함
|
|
120
|
+
await db.insert(users)
|
|
121
|
+
.values({ email: 'alice@example.com', name: 'Alice' })
|
|
122
|
+
.onConflictDoNothing({ target: users.email })
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Delete
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
// 단일
|
|
129
|
+
await db.delete(users).where(eq(users.id, 1))
|
|
130
|
+
|
|
131
|
+
// 조건부
|
|
132
|
+
await db.delete(users).where(eq(users.verified, false))
|
|
133
|
+
|
|
134
|
+
// 전체 (주의!)
|
|
135
|
+
await db.delete(users)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## 필터 연산자
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
import {
|
|
142
|
+
eq, // =
|
|
143
|
+
ne, // !=
|
|
144
|
+
gt, // >
|
|
145
|
+
gte, // >=
|
|
146
|
+
lt, // <
|
|
147
|
+
lte, // <=
|
|
148
|
+
like, // LIKE
|
|
149
|
+
ilike, // ILIKE (대소문자 무시)
|
|
150
|
+
inArray, // IN
|
|
151
|
+
notInArray,// NOT IN
|
|
152
|
+
isNull, // IS NULL
|
|
153
|
+
isNotNull, // IS NOT NULL
|
|
154
|
+
between, // BETWEEN
|
|
155
|
+
and, // AND
|
|
156
|
+
or, // OR
|
|
157
|
+
not, // NOT
|
|
158
|
+
} from 'drizzle-orm'
|
|
159
|
+
|
|
160
|
+
// 문자열
|
|
161
|
+
where(like(users.name, '%John%'))
|
|
162
|
+
where(ilike(users.name, '%john%'))
|
|
163
|
+
|
|
164
|
+
// 숫자
|
|
165
|
+
where(gt(users.age, 18))
|
|
166
|
+
where(between(users.age, 18, 65))
|
|
167
|
+
|
|
168
|
+
// 배열
|
|
169
|
+
where(inArray(users.id, [1, 2, 3]))
|
|
170
|
+
|
|
171
|
+
// NULL
|
|
172
|
+
where(isNull(users.deletedAt))
|
|
173
|
+
|
|
174
|
+
// 논리
|
|
175
|
+
where(and(eq(users.role, 'admin'), eq(users.verified, true)))
|
|
176
|
+
where(or(eq(users.role, 'admin'), eq(users.role, 'moderator')))
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Join
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
// Inner join
|
|
183
|
+
const result = await db.select({
|
|
184
|
+
userId: users.id,
|
|
185
|
+
userName: users.name,
|
|
186
|
+
postTitle: posts.title,
|
|
187
|
+
})
|
|
188
|
+
.from(users)
|
|
189
|
+
.innerJoin(posts, eq(users.id, posts.authorId))
|
|
190
|
+
|
|
191
|
+
// Left join
|
|
192
|
+
const result = await db.select()
|
|
193
|
+
.from(users)
|
|
194
|
+
.leftJoin(posts, eq(users.id, posts.authorId))
|
|
195
|
+
|
|
196
|
+
// Aggregation
|
|
197
|
+
const result = await db.select({
|
|
198
|
+
userId: users.id,
|
|
199
|
+
userName: users.name,
|
|
200
|
+
postCount: count(posts.id),
|
|
201
|
+
})
|
|
202
|
+
.from(users)
|
|
203
|
+
.leftJoin(posts, eq(users.id, posts.authorId))
|
|
204
|
+
.groupBy(users.id)
|
|
205
|
+
```
|